Merged with main

This commit is contained in:
WBHarry 2025-08-08 00:44:08 +02:00
commit 9b9632cf94
150 changed files with 1077 additions and 930 deletions

View file

@ -1,7 +1,7 @@
import { abilities } from '../../config/actorConfig.mjs';
import { burden } from '../../config/generalConfig.mjs';
import { createEmbeddedItemWithEffects } from '../../helpers/utils.mjs';
import { ItemBrowser } from '../ui/itemBrowser.mjs';
import { createEmbeddedItemsWithEffects, createEmbeddedItemWithEffects } from '../../helpers/utils.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -21,8 +21,8 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
class: this.character.system.class?.value ?? {},
subclass: this.character.system.class?.subclass ?? {},
experiences: {
[foundry.utils.randomID()]: { name: '', value: 2 },
[foundry.utils.randomID()]: { name: '', value: 2 }
[foundry.utils.randomID()]: { name: '', value: 2, core: true },
[foundry.utils.randomID()]: { name: '', value: 2, core: true }
},
domainCards: {
[foundry.utils.randomID()]: {},
@ -373,13 +373,18 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
);
context.armor = {
...this.equipment.armor,
suggestion: { ...suggestions.armor, taken: suggestions.armor?.uuid === this.equipment.armor?.uuid },
suggestion: {
...suggestions.armor,
uuid: suggestions.armor?.uuid,
taken: suggestions.armor?.uuid === this.equipment.armor?.uuid
},
compendium: 'armors'
};
context.primaryWeapon = {
...this.equipment.primaryWeapon,
suggestion: {
...suggestions.primaryWeapon,
uuid: suggestions.primaryWeapon?.uuid,
taken: suggestions.primaryWeapon?.uuid === this.equipment.primaryWeapon?.uuid
},
compendium: 'weapons'
@ -388,6 +393,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
...this.equipment.secondaryWeapon,
suggestion: {
...suggestions.secondaryWeapon,
uuid: suggestions.secondaryWeapon?.uuid,
taken: suggestions.secondaryWeapon?.uuid === this.equipment.secondaryWeapon?.uuid
},
disabled: this.equipment.primaryWeapon?.system?.burden === burden.twoHanded.value,
@ -490,22 +496,22 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
static async viewCompendium(event, target) {
const type = target.dataset.compendium ?? target.dataset.type;
const presets = {
compendium: "daggerheart",
compendium: 'daggerheart',
folder: type,
render: {
noFolder: true
}
};
if(type == "domains")
if (type == 'domains')
presets.filter = {
'level.max': { key: 'level.max', value: 1 },
'system.domain': { key: 'system.domain', value: this.setup.class?.system.domains ?? null },
'system.domain': { key: 'system.domain', value: this.setup.class?.system.domains ?? null }
};
return this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true });
return (this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true }));
}
static async viewItem(_, target) {
@ -574,13 +580,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
await createEmbeddedItemWithEffects(this.character, this.setup.community);
await createEmbeddedItemWithEffects(this.character, this.setup.class);
await createEmbeddedItemWithEffects(this.character, this.setup.subclass);
await this.character.createEmbeddedDocuments(
'Item',
Object.values(this.setup.domainCards).map(x => ({
...x,
effects: x.effects?.map(effect => effect.toObject())
}))
);
await createEmbeddedItemsWithEffects(this.character, Object.values(this.setup.domainCards));
if (this.equipment.armor.uuid)
await createEmbeddedItemWithEffects(this.character, this.equipment.armor, {
@ -602,24 +602,28 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
if (this.equipment.inventory.choiceB.uuid)
await createEmbeddedItemWithEffects(this.character, this.equipment.inventory.choiceB);
await this.character.createEmbeddedDocuments(
'Item',
this.setup.class.system.inventory.take
.filter(x => x)
.map(x => ({
...x,
effects: x.effects?.map(effect => effect.toObject())
}))
await createEmbeddedItemsWithEffects(
this.character,
this.setup.class.system.inventory.take.filter(x => x)
);
await this.character.update({
system: {
traits: this.setup.traits,
experiences: this.setup.experiences
}
});
await this.character.update(
{
system: {
traits: this.setup.traits,
experiences: {
...this.setup.experiences,
...Object.keys(this.character.system.experiences).reduce((acc, key) => {
acc[`-=${key}`] = null;
return acc;
}, {})
}
}
},
{ overwrite: true }
);
if(this.itemBrowser) this.itemBrowser.close();
if (this.itemBrowser) this.itemBrowser.close();
this.close();
}
@ -710,6 +714,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
return;
}
if (item.system.burden === CONFIG.DH.GENERAL.burden.twoHanded.value) {
this.equipment.secondaryWeapon = {};
}
this.equipment.primaryWeapon = { ...item, uuid: item.uuid };
} else if (item.type === 'weapon' && event.target.closest('.secondary-weapon-card')) {
if (this.equipment.primaryWeapon?.system?.burden === burden.twoHanded.value) {

View file

@ -2,7 +2,6 @@ export { default as BeastformDialog } from './beastformDialog.mjs';
export { default as d20RollDialog } from './d20RollDialog.mjs';
export { default as DamageDialog } from './damageDialog.mjs';
export { default as DamageReductionDialog } from './damageReductionDialog.mjs';
export { default as DamageSelectionDialog } from './damageSelectionDialog.mjs';
export { default as DeathMove } from './deathMove.mjs';
export { default as Downtime } from './downtime.mjs';
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';

View file

@ -38,7 +38,9 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
};
get title() {
return game.i18n.localize(`DAGGERHEART.EFFECTS.ApplyLocations.${this.config.isHealing ? 'healing' : 'damage'}Roll.name`);
return game.i18n.localize(
`DAGGERHEART.EFFECTS.ApplyLocations.${this.config.hasHealing ? 'healing' : 'damage'}Roll.name`
);
}
async _prepareContext(_options) {
@ -46,7 +48,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
context.config = CONFIG.DH;
context.title = this.config.title ?? this.title;
context.formula = this.roll.constructFormula(this.config);
context.isHealing = this.config.isHealing;
context.hasHealing = this.config.hasHealing;
context.directDamage = this.config.directDamage;
context.selectedRollMode = this.config.selectedRollMode;
context.rollModes = Object.entries(CONFIG.Dice.rollModes).map(([action, { label, icon }]) => ({

View file

@ -1,128 +0,0 @@
// TO DELETE ?
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
export default class DamageSelectionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(rollString, bonusDamage, resolve, hope = 0) {
super({});
this.data = {
rollString,
bonusDamage: bonusDamage.reduce((acc, x) => {
if (x.appliesOn === CONFIG.DH.EFFECTS.applyLocations.damageRoll.id) {
acc.push({
...x,
hopeUses: 0
});
}
return acc;
}, []),
hope
};
this.resolve = resolve;
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'],
position: {
width: 400,
height: 'auto'
},
actions: {
decreaseHopeUse: this.decreaseHopeUse,
increaseHopeUse: this.increaseHopeUse,
rollDamage: this.rollDamage
},
form: {
handler: this.updateSelection,
submitOnChange: true,
closeOnSubmit: false
}
};
/** @override */
static PARTS = {
damageSelection: {
id: 'damageSelection',
template: 'systems/daggerheart/templates/dialogs/dice-roll/damageSelection.hbs'
}
};
/* -------------------------------------------- */
/** @inheritDoc */
get title() {
return `Damage Options`;
}
async _prepareContext(_options) {
return {
rollString: this.getRollString(),
bonusDamage: this.data.bonusDamage,
hope: this.data.hope + 1,
hopeUsed: this.getHopeUsed()
};
}
static updateSelection(event, _, formData) {
const { bonusDamage, ...rest } = foundry.utils.expandObject(formData.object);
for (var index in bonusDamage) {
this.data.bonusDamage[index].initiallySelected = bonusDamage[index].initiallySelected;
if (bonusDamage[index].hopeUses) {
const value = Number.parseInt(bonusDamage[index].hopeUses);
if (!Number.isNaN(value)) this.data.bonusDamage[index].hopeUses = value;
}
}
this.data = foundry.utils.mergeObject(this.data, rest);
this.render(true);
}
getRollString() {
return this.data.rollString.concat(
this.data.bonusDamage.reduce((acc, x) => {
if (x.initiallySelected) {
const nr = 1 + x.hopeUses;
const baseDamage = x.value;
return acc.concat(` + ${nr}${baseDamage}`);
}
return acc;
}, '')
);
}
getHopeUsed() {
return this.data.bonusDamage.reduce((acc, x) => acc + x.hopeUses, 0);
}
static decreaseHopeUse(_, button) {
const index = Number.parseInt(button.dataset.index);
if (this.data.bonusDamage[index].hopeUses - 1 >= 0) {
this.data.bonusDamage[index].hopeUses -= 1;
this.render(true);
}
}
static increaseHopeUse(_, button) {
const index = Number.parseInt(button.dataset.index);
if (this.data.bonusDamage[index].hopeUses <= this.data.hope + 1) {
this.data.bonusDamage[index].hopeUses += 1;
this.render(true);
}
}
static rollDamage(event) {
event.preventDefault();
this.resolve({
rollString: this.getRollString(),
bonusDamage: this.data.bonusDamage,
hopeUsed: this.getHopeUsed()
});
this.close();
}
}

View file

@ -94,7 +94,10 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
const actionItems = this.actor.items.reduce((acc, x) => {
if (x.system.actions) {
const recoverable = x.system.actions.reduce((acc, action) => {
if (action.uses.recovery && (action.uses.recovery === 'shortRest') === this.shortrest) {
if (
(action.uses.recovery && (action.uses.recovery === 'longRest') === !this.shortrest) ||
action.uses.recovery === 'shortRest'
) {
acc.push({
title: x.name,
name: action.name,
@ -116,7 +119,8 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
if (
x.system.resource &&
x.system.resource.type &&
(x.system.resource.recovery === 'shortRest') === this.shortrest
((x.system.resource.recovery === 'longRest') === !this.shortrest ||
x.system.resource.recovery === 'shortRest')
) {
acc.push({
title: game.i18n.localize(`TYPES.Item.${x.type}`),
@ -226,14 +230,18 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
) {
for (var data of this.refreshables.actionItems) {
const action = await foundry.utils.fromUuid(data.uuid);
await action.parent.parent.update({ [`system.actions.${action.id}.uses.value`]: action.uses.max ?? 1 });
await action.parent.parent.update({ [`system.actions.${action.id}.uses.value`]: 0 });
}
for (var data of this.refreshables.resourceItems) {
const feature = await foundry.utils.fromUuid(data.uuid);
const increasing =
feature.system.resource.progression === CONFIG.DH.ITEM.itemResourceProgression.increasing.id;
const resetValue = increasing ? 0 : (feature.system.resource.max ?? 0);
const resetValue = increasing
? 0
: feature.system.resource.max
? Roll.replaceFormulaData(feature.system.resource.max, this.actor)
: 0;
await feature.update({ 'system.resource.value': resetValue });
}

View file

@ -289,7 +289,7 @@ export default class DhCharacterLevelUp extends LevelUpBase {
const experience = Object.keys(this.actor.system.experiences).find(
x => x === data
);
return this.actor.system.experiences[experience]?.description ?? '';
return this.actor.system.experiences[experience]?.name ?? '';
});
advancement[choiceKey].push({ data: data, value: checkbox.value });
break;

View file

@ -47,10 +47,12 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
static PARTS = {
tabs: { template: 'systems/daggerheart/templates/levelup/tabs/tab-navigation.hbs' },
advancements: { template: 'systems/daggerheart/templates/levelup/tabs/advancements.hbs' },
advancements: {
template: 'systems/daggerheart/templates/levelup/tabs/advancements.hbs'
},
selections: {
template: 'systems/daggerheart/templates/levelup/tabs/selections.hbs',
scrollable: ['.selections']
scrollable: ['.levelup-selections-container']
},
summary: { template: 'systems/daggerheart/templates/levelup/tabs/summary.hbs' },
footer: { template: 'systems/daggerheart/templates/levelup/tabs/footer.hbs' }
@ -536,29 +538,28 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
static async viewCompendium(event, target) {
const type = target.dataset.compendium ?? target.dataset.type;
const presets = {
compendium: "daggerheart",
compendium: 'daggerheart',
folder: type,
render: {
noFolder: true
}
};
if(type == "domains") {
if (type == 'domains') {
const domains = this.actor.system.domains,
multiclassDomain = this.levelup.classUpgradeChoices?.multiclass?.domain;
if (multiclassDomain) {
if (!domains.includes(x => x === multiclassDomain))
domains.push(multiclassDomain);
if (!domains.includes(x => x === multiclassDomain)) domains.push(multiclassDomain);
}
presets.filter = {
'level.max': { key: 'level.max', value: this.levelup.currentLevel },
'system.domain': { key: 'system.domain', value: domains },
'system.domain': { key: 'system.domain', value: domains }
};
}
return this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true });
return (this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true }));
}
static async selectPreview(_, button) {
@ -659,7 +660,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
}, {});
await this.actor.levelUp(levelupData);
if(this.itemBrowser) this.itemBrowser.close();
if (this.itemBrowser) this.itemBrowser.close();
this.close();
}
}

View file

@ -1,5 +1,4 @@
export { default as DhAppearanceSettings } from './appearanceSettings.mjs';
export { default as DhAutomationSettings } from './automationSettings.mjs';
export { default as DhHomebrewSettings } from './homebrewSettings.mjs';
export { default as DhRangeMeasurementSettings } from './rangeMeasurementSettings.mjs';
export { default as DhVariantRuleSettings } from './variantRuleSettings.mjs';

View file

@ -1,66 +0,0 @@
import { DhRangeMeasurement } from '../../data/settings/_module.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DhRangeMeasurementSettings extends HandlebarsApplicationMixin(ApplicationV2) {
constructor() {
super({});
this.settings = new DhRangeMeasurement(
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.RangeMeasurement).toObject()
);
}
get title() {
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
}
static DEFAULT_OPTIONS = {
tag: 'form',
id: 'daggerheart-automation-settings',
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'],
position: { width: '600', height: 'auto' },
window: {
icon: 'fa-solid fa-gears'
},
actions: {
reset: this.reset,
save: this.save
},
form: { handler: this.updateData, submitOnChange: true }
};
static PARTS = {
main: {
template: 'systems/daggerheart/templates/settings/range-measurement-settings.hbs'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.settingFields = this.settings;
return context;
}
static async updateData(event, element, formData) {
const updatedSettings = foundry.utils.expandObject(formData.object);
await this.settings.updateSource(updatedSettings);
this.render();
}
static async reset() {
this.settings = new DhRangeMeasurement();
this.render();
}
static async save() {
await game.settings.set(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.RangeMeasurement,
this.settings.toObject()
);
this.close();
}
}

View file

@ -27,7 +27,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
toggleResourceDice: CharacterSheet.#toggleResourceDice,
handleResourceDice: CharacterSheet.#handleResourceDice,
useDowntime: this.useDowntime,
tempBrowser: CharacterSheet.#tempBrowser,
tempBrowser: CharacterSheet.#tempBrowser
},
window: {
resizable: true
@ -158,7 +158,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
currency: {
title: game.i18n.localize('DAGGERHEART.CONFIG.Gold.title'),
coins: game.i18n.localize('DAGGERHEART.CONFIG.Gold.coins'),
handfulls: game.i18n.localize('DAGGERHEART.CONFIG.Gold.handfulls'),
handfuls: game.i18n.localize('DAGGERHEART.CONFIG.Gold.handfuls'),
bags: game.i18n.localize('DAGGERHEART.CONFIG.Gold.bags'),
chests: game.i18n.localize('DAGGERHEART.CONFIG.Gold.chests')
}
@ -180,6 +180,13 @@ export default class CharacterSheet extends DHBaseActorSheet {
async _preparePartContext(partId, context, options) {
context = await super._preparePartContext(partId, context, options);
switch (partId) {
case 'header':
const { playerCanEditSheet, levelupAuto } = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Automation
);
context.showSettings = game.user.isGM || !levelupAuto || (levelupAuto && playerCanEditSheet);
break;
case 'loadout':
await this._prepareLoadoutContext(context, options);
break;
@ -190,6 +197,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
await this._prepareBiographyContext(context, options);
break;
}
return context;
}
@ -596,7 +604,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
const { key } = button.dataset;
const presets = {
compendium: "daggerheart",
compendium: 'daggerheart',
folder: key,
render: {
noFolder: true

View file

@ -7,7 +7,11 @@ export default class DhpEnvironment extends DHBaseActorSheet {
static DEFAULT_OPTIONS = {
classes: ['environment'],
position: {
width: 500
width: 500,
height: 725
},
window: {
resizable: true
},
actions: {},
dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }]

View file

@ -85,7 +85,7 @@ export default function DHApplicationMixin(Base) {
toggleEffect: DHSheetV2.#toggleEffect,
toggleExtended: DHSheetV2.#toggleExtended,
addNewItem: DHSheetV2.#addNewItem,
browseItem: DHSheetV2.#browseItem,
browseItem: DHSheetV2.#browseItem
},
contextMenus: [
{
@ -327,17 +327,31 @@ export default function DHApplicationMixin(Base) {
if (usable)
options.unshift({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
icon: 'fa-solid fa-burst',
name: 'DAGGERHEART.GENERAL.damage',
icon: 'fa-solid fa-explosion',
condition: target => {
const doc = getDocFromElementSync(target);
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length;
},
callback: async (target, event) => (await getDocFromElement(target)).use(event)
callback: async (target, event) => {
const doc = await getDocFromElement(target),
action = doc?.system?.attack ?? doc;
return action && action.use(event, { byPassRoll: true });
}
});
options.unshift({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
icon: 'fa-solid fa-burst',
condition: target => {
const doc = getDocFromElementSync(target);
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
},
callback: async (target, event) => (await getDocFromElement(target)).use(event)
});
if (toChat)
options.unshift({
options.push({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
icon: 'fa-solid fa-message',
callback: async target => (await getDocFromElement(target)).toChat(this.document.id)
@ -419,25 +433,22 @@ export default function DHApplicationMixin(Base) {
classes: ['dh-style', 'two-big-buttons'],
buttons: [
{
action: "create",
label: "Create Item",
icon: "fa-solid fa-plus"
action: 'create',
label: 'Create Item',
icon: 'fa-solid fa-plus'
},
{
action: "browse",
label: "Browse Compendium",
icon: "fa-solid fa-book"
action: 'browse',
label: 'Browse Compendium',
icon: 'fa-solid fa-book'
}
]
});
if(!createChoice) return;
if(createChoice === "browse")
return DHSheetV2.#browseItem.call(this, event, target);
else
return DHSheetV2.#createDoc.call(this, event, target);
if (!createChoice) return;
if (createChoice === 'browse') return DHSheetV2.#browseItem.call(this, event, target);
else return DHSheetV2.#createDoc.call(this, event, target);
}
static async #browseItem(event, target) {
@ -450,8 +461,8 @@ export default function DHApplicationMixin(Base) {
case 'consumable':
case 'armor':
case 'weapon':
presets.compendium = "daggerheart";
presets.folder = "equipments";
presets.compendium = 'daggerheart';
presets.folder = 'equipments';
presets.render = {
noFolder: true
};
@ -460,14 +471,14 @@ export default function DHApplicationMixin(Base) {
};
break;
case 'domainCard':
presets.compendium = "daggerheart";
presets.folder = "domains";
presets.compendium = 'daggerheart';
presets.folder = 'domains';
presets.render = {
noFolder: true
};
presets.filter = {
'level.max': { key: 'level.max', value: this.document.system.levelData.level.current },
'system.domain': { key: 'system.domain', value: this.document.system.domains },
'system.domain': { key: 'system.domain', value: this.document.system.domains }
};
break;
default:

View file

@ -22,7 +22,8 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
},
actions: {
openSettings: DHBaseActorSheet.#openSettings,
sendExpToChat: DHBaseActorSheet.#sendExpToChat
sendExpToChat: DHBaseActorSheet.#sendExpToChat,
increaseActionUses: event => DHBaseActorSheet.#modifyActionUses(event, true)
},
contextMenus: [
{
@ -70,6 +71,15 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
return context;
}
/**@inheritdoc */
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelectorAll('.item-button .action-uses-button').forEach(element => {
element.addEventListener('contextmenu', DHBaseActorSheet.#modifyActionUses);
});
}
/**
* Prepare render context for the Effect part.
* @param {ApplicationRenderContext} context
@ -154,6 +164,19 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
cls.create(msg);
}
/**
*
*/
static async #modifyActionUses(event, increase) {
event.stopPropagation();
event.preventDefault();
const actionId = event.target.dataset.itemUuid;
const action = await foundry.utils.fromUuid(actionId);
const newValue = (action.uses.value ?? 0) + (increase ? 1 : -1);
await action.update({ 'uses.value': Math.min(Math.max(newValue, 0), action.uses.max ?? 0) });
}
/* -------------------------------------------- */
/* Application Drag/Drop */
/* -------------------------------------------- */

View file

@ -3,7 +3,6 @@ import DHBaseItemSheet from '../api/base-item.mjs';
export default class FeatureSheet extends DHBaseItemSheet {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
id: 'daggerheart-feature',
classes: ['feature'],
actions: {}
};

View file

@ -5,7 +5,7 @@ export default class SubclassSheet extends DHBaseItemSheet {
static DEFAULT_OPTIONS = {
classes: ['subclass'],
position: { width: 600 },
window: { resizable: false }
window: { resizable: true }
};
/**@override */

View file

@ -194,8 +194,12 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
event.stopPropagation();
const item = await foundry.utils.fromUuid(message.system.origin);
const action = item.system.actions.get(event.currentTarget.id);
await item.use(action);
const action =
item.system.attack?.id === event.currentTarget.id
? item.system.attack
: item.system.actions.get(event.currentTarget.id);
if (event.currentTarget.dataset.directDamage) action.use(event, { byPassRoll: true });
else action.use(event);
}
async actionUseButton(event, message) {

View file

@ -88,7 +88,10 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
}
}
await this.viewed.update({ turn: this.viewed.turn === toggleTurn ? null : toggleTurn });
await this.viewed.update({
turn: this.viewed.turn === toggleTurn ? null : toggleTurn,
round: this.viewed.round + 1
});
await combatant.update(update);
}

View file

@ -4,8 +4,8 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur
const rangeMeasurementSettings = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.RangeMeasurement
);
CONFIG.DH.SETTINGS.gameSettings.variantRules
).rangeMeasurement;
if (rangeMeasurementSettings.enabled) {
const splitRulerText = this.ruler.text.split(' ');
if (splitRulerText.length > 0) {
@ -29,7 +29,7 @@ export default class DhMeasuredTemplate extends foundry.canvas.placeables.Measur
if (distance <= settings.far) {
return game.i18n.localize('DAGGERHEART.CONFIG.Range.far.name');
}
if (distance <= settings.veryFar) {
if (distance > settings.far) {
return game.i18n.localize('DAGGERHEART.CONFIG.Range.veryFar.name');
}

View file

@ -5,7 +5,7 @@ export default class DhpRuler extends foundry.canvas.interaction.Ruler {
const context = super._getWaypointLabelContext(waypoint, state);
if (!context) return;
const range = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.RangeMeasurement);
const range = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
if (range.enabled) {
const distance = DhMeasuredTemplate.getDistanceLabel(waypoint.measurement.distance.toNearest(0.01), range);

View file

@ -53,4 +53,40 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
this.effects.renderable = true;
this.renderFlags.set({ refreshEffects: true });
}
/** @inheritDoc */
_drawBar(number, bar, data) {
const val = Number(data.value);
const pct = Math.clamp(val, 0, data.max) / data.max;
// Determine sizing
const { width, height } = this.document.getSize();
const s = canvas.dimensions.uiScale;
const bw = width;
const bh = 8 * (this.document.height >= 2 ? 1.5 : 1) * s;
// Determine the color to use
const fillColor =
number === 0 ? foundry.utils.Color.fromRGB([1, 0, 0]) : foundry.utils.Color.fromString('#0032b1');
// Draw the bar
const widthUnit = bw / data.max;
bar.clear().lineStyle(s, 0x000000, 1.0);
const sections = [...Array(data.max).keys()];
for (let mark of sections) {
const x = mark * widthUnit;
const marked = mark + 1 <= data.value;
const color = marked ? fillColor : foundry.utils.Color.fromRGB([0, 0, 0]);
if (mark === 0 || mark === sections.length - 1) {
bar.beginFill(color, marked ? 1.0 : 0.5).drawRect(x, 0, widthUnit, bh, 2 * s); // Would like drawRoundedRect, but it's very troublsome with the corners. Leaving for now.
} else {
bar.beginFill(color, marked ? 1.0 : 0.5).drawRect(x, 0, widthUnit, bh, 2 * s);
}
}
// Set position
const posY = number === 0 ? height - bh : 0;
bar.position.set(0, posY);
return true;
}
}

View file

@ -5,7 +5,7 @@ export default class DhpTokenRuler extends foundry.canvas.placeables.tokens.Toke
const context = super._getWaypointLabelContext(waypoint, state);
if (!context) return;
const range = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.RangeMeasurement);
const range = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement;
if (range.enabled) {
const distance = DhMeasuredTemplate.getDistanceLabel(waypoint.measurement.distance.toNearest(0.01), range);

View file

@ -21,7 +21,7 @@ export const ruleChoice = {
}
};
export const range = {
export const templateRanges = {
self: {
id: 'self',
short: 's',
@ -56,7 +56,11 @@ export const range = {
label: 'DAGGERHEART.CONFIG.Range.far.name',
description: 'DAGGERHEART.CONFIG.Range.far.description',
distance: 20
},
}
};
export const range = {
...templateRanges,
veryFar: {
id: 'veryFar',
short: 'vf',
@ -479,7 +483,8 @@ export const multiplierTypes = {
cast: 'Spellcast',
scale: 'Cost Scaling',
result: 'Roll Result',
flat: 'Flat'
flat: 'Flat',
tier: 'Tier'
};
export const diceSetNumbers = {

View file

@ -857,7 +857,7 @@ export const weaponFeatures = {
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description',
img: 'icons/commodities/currency/coins-crown-stack-gold.webp',
// Should cost handfull of gold,
// Should cost handful of gold,
effects: [
{
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.actions.greed.name',

View file

@ -20,7 +20,6 @@ export const menu = {
export const gameSettings = {
Automation: 'Automation',
Homebrew: 'Homebrew',
RangeMeasurement: 'RangeMeasurement',
appearance: 'Appearance',
variantRules: 'VariantRules',
Resources: {

View file

@ -34,8 +34,8 @@ export default class DHAttackAction extends DHDamageAction {
};
}
async use(event, ...args) {
const result = await super.use(event, args);
async use(event, options) {
const result = await super.use(event, options);
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id);

View file

@ -111,12 +111,13 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return actorData;
}
async use(event, ...args) {
async use(event, options = {}) {
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
if (this.chatDisplay) await this.toChat();
let config = this.prepareConfig(event);
let { byPassRoll } = options,
config = this.prepareConfig(event, byPassRoll);
for (let i = 0; i < this.constructor.extraSchemas.length; i++) {
let clsField = this.constructor.getActionField(this.constructor.extraSchemas[i]);
if (clsField?.prepareConfig) {
@ -133,14 +134,14 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
if (!config) return;
}
if (this.hasRoll) {
if (config.hasRoll) {
const rollConfig = this.prepareRoll(config);
config.roll = rollConfig;
config = await this.actor.diceRoll(config);
if (!config) return;
}
if (this.doFollowUp()) {
if (this.doFollowUp(config)) {
if (this.rollDamage && this.damage.parts.length) await this.rollDamage(event, config);
else if (this.trigger) await this.trigger(event, config);
else if (this.hasSave || this.hasEffect) {
@ -160,7 +161,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
}
/* */
prepareConfig(event) {
prepareConfig(event, byPass = false) {
const hasRoll = this.getUseHasRoll(byPass);
return {
event,
title: `${this.item.name}: ${this.name}`,
@ -170,10 +172,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
actor: this.actor.uuid
},
dialog: {
configure: this.hasRoll
configure: hasRoll
},
type: this.type,
hasRoll: this.hasRoll,
hasRoll: hasRoll,
hasDamage: this.damage?.parts?.length && this.type !== 'healing',
hasHealing: this.damage?.parts?.length && this.type === 'healing',
hasEffect: !!this.effects?.length,
@ -182,12 +184,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
selectedRollMode: game.settings.get('core', 'rollMode'),
isFastForward: event.shiftKey,
data: this.getRollData(),
evaluate: this.hasRoll
evaluate: hasRoll
};
}
requireConfigurationDialog(config) {
return !config.event.shiftKey && !this.hasRoll && (config.costs?.length || config.uses);
return !config.event.shiftKey && !config.hasRoll && (config.costs?.length || config.uses);
}
prepareRoll() {
@ -205,7 +207,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
}
doFollowUp(config) {
return !this.hasRoll;
return !config.hasRoll;
}
async consume(config, successCost = false) {
@ -220,16 +222,13 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
};
}
}
const resources = config.costs
.filter(c =>
c.enabled !== false
&&
(
(!successCost && (!c.consumeOnSuccess || config.roll?.success))
||
(successCost && c.consumeOnSuccess)
)
.filter(
c =>
c.enabled !== false &&
((!successCost && (!c.consumeOnSuccess || config.roll?.success)) ||
(successCost && c.consumeOnSuccess))
)
.map(c => {
const resource = usefulResources[c.key];
@ -242,21 +241,23 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
});
await this.actor.modifyResource(resources);
if (config.uses?.enabled
&&
(
(!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success))
||
(successCost && config.uses?.consumeOnSuccess)
)
) this.update({ 'uses.value': this.uses.value + 1 });
if (
config.uses?.enabled &&
((!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success)) ||
(successCost && config.uses?.consumeOnSuccess))
)
this.update({ 'uses.value': this.uses.value + 1 });
if(config.roll?.success || successCost)
(config.message ?? config.parent).update({'system.successConsumed': true})
if (config.roll?.success || successCost)
(config.message ?? config.parent).update({ 'system.successConsumed': true });
}
/* */
/* ROLL */
getUseHasRoll(byPass = false) {
return this.hasRoll && !byPass;
}
get hasRoll() {
return !!this.roll?.type;
}
@ -301,11 +302,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
}
async applyEffect(effect, actor) {
const origin = effect.parent?.parent ? effect.parent.parent.uuid : effect.parent.uuid;
// Enable an existing effect on the target if it originated from this effect
const existingEffect = actor.effects.find(e => e.origin === origin);
const existingEffect = actor.effects.find(e => e.origin === effect.uuid);
if (existingEffect) {
return existingEffect.update(
return effect.update(
foundry.utils.mergeObject({
...effect.constructor.getInitialDuration(),
disabled: false
@ -318,7 +317,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
...effect.toObject(),
disabled: false,
transfer: false,
origin: origin
origin: effect.uuid
});
await ActiveEffect.implementation.create(effectData, { parent: actor });
}

View file

@ -4,7 +4,7 @@ import DHBaseAction from './baseAction.mjs';
export default class DhBeastformAction extends DHBaseAction {
static extraSchemas = [...super.extraSchemas, 'beastform'];
async use(event, ...args) {
async use(event, options) {
const beastformConfig = this.prepareBeastformConfig();
const abort = await this.handleActiveTransformations();
@ -20,7 +20,7 @@ export default class DhBeastformAction extends DHBaseAction {
const { selected, evolved, hybrid } = await BeastformDialog.configure(beastformConfig, this.item);
if (!selected) return;
const result = await super.use(event, args);
const result = await super.use(event, options);
if (!result) return;
await this.transform(selected, evolved, hybrid);

View file

@ -6,7 +6,7 @@ export default class DHDamageAction extends DHBaseAction {
getFormulaValue(part, data) {
let formulaValue = part.value;
if (this.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
if (data.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
const isAdversary = this.actor.type === 'adversary';
if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
@ -51,7 +51,7 @@ export default class DHDamageAction extends DHBaseAction {
dialog: {},
data: this.getRollData(),
targetSelection: systemData.targets.length > 0
}
};
if (this.hasSave) config.onSave = this.save.damageMod;
if (data.system) {
config.source.message = data._id;

View file

@ -10,8 +10,6 @@ export default class DHMacroAction extends DHBaseAction {
}
async trigger(event, ...args) {
// const config = await super.use(event, args);
// if (['error', 'warning'].includes(config.type)) return;
const fixUUID = !this.documentUUID.includes('Macro.') ? `Macro.${this.documentUUID}` : this.documentUUID,
macro = await fromUuid(fixUUID);
try {

View file

@ -11,7 +11,6 @@ export default class DHSummonAction extends DHBaseAction {
async trigger(event, ...args) {
if (!this.canSummon || !canvas.scene) return;
// const config = await super.use(event, args);
}
get canSummon() {

View file

@ -68,12 +68,13 @@ export default class DhCharacter extends BaseDataActor {
new fields.SchemaField({
name: new fields.StringField(),
value: new fields.NumberField({ integer: true, initial: 0 }),
description: new fields.StringField()
description: new fields.StringField(),
core: new fields.BooleanField({ initial: false })
})
),
gold: new fields.SchemaField({
coins: new fields.NumberField({ initial: 0, integer: true }),
handfulls: new fields.NumberField({ initial: 0, integer: true }),
handfuls: new fields.NumberField({ initial: 1, integer: true }),
bags: new fields.NumberField({ initial: 0, integer: true }),
chests: new fields.NumberField({ initial: 0, integer: true })
}),
@ -573,7 +574,10 @@ export default class DhCharacter extends BaseDataActor {
case 'experience':
selection.data.forEach(id => {
const experience = this.experiences[id];
if (experience) experience.value += selection.value;
if (experience) {
experience.value += selection.value;
experience.leveledUp = true;
}
});
break;
}
@ -620,6 +624,23 @@ export default class DhCharacter 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.companion) {
this.companion.updateLevel(1);

View file

@ -1,20 +1,21 @@
const fields = foundry.data.fields;
const targetsField = () => new fields.ArrayField(
new fields.SchemaField({
id: new fields.StringField({}),
actorId: new fields.StringField({}),
name: new fields.StringField({}),
img: new fields.StringField({}),
difficulty: new fields.NumberField({ integer: true, nullable: true }),
evasion: new fields.NumberField({ integer: true }),
hit: new fields.BooleanField({ initial: false }),
saved: new fields.SchemaField({
result: new fields.NumberField(),
success: new fields.BooleanField({ nullable: true, initial: null })
const targetsField = () =>
new fields.ArrayField(
new fields.SchemaField({
id: new fields.StringField({}),
actorId: new fields.StringField({}),
name: new fields.StringField({}),
img: new fields.StringField({}),
difficulty: new fields.NumberField({ integer: true, nullable: true }),
evasion: new fields.NumberField({ integer: true }),
hit: new fields.BooleanField({ initial: false }),
saved: new fields.SchemaField({
result: new fields.NumberField(),
success: new fields.BooleanField({ nullable: true, initial: null })
})
})
})
)
);
export default class DHActorRoll extends foundry.abstract.TypeDataModel {
targetHook = null;
@ -40,27 +41,25 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
action: new fields.StringField()
}),
damage: new fields.ObjectField(),
costs: new fields.ArrayField(
new fields.ObjectField()
),
costs: new fields.ArrayField(new fields.ObjectField()),
successConsumed: new fields.BooleanField({ initial: false })
};
}
get actionActor() {
if(!this.source.actor) return null;
if (!this.source.actor) return null;
return fromUuidSync(this.source.actor);
}
get actionItem() {
const actionActor = this.actionActor;
if(!actionActor || !this.source.item) return null;
if (!actionActor || !this.source.item) return null;
return actionActor.items.get(this.source.item);
}
get action() {
const actionItem = this.actionItem;
if(!actionItem || !this.source.action) return null;
if (!actionItem || !this.source.action) return null;
return actionItem.system.actionsList?.find(a => a.id === this.source.action);
}
@ -76,90 +75,85 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
this.targetSelection = mode;
this.updateTargets();
this.registerTargetHook();
this.parent.update(
{
system: {
targetSelection: this.targetSelection,
oldTargets: this.oldTargets
}
this.parent.update({
system: {
targetSelection: this.targetSelection,
oldTargets: this.oldTargets
}
);
});
}
get hitTargets() {
return this.currentTargets.filter(t => (t.hit || !this.hasRoll || !this.targetSelection));
return this.currentTargets.filter(t => t.hit || !this.hasRoll || !this.targetSelection);
}
async updateTargets() {
this.currentTargets = this.getTargetList();
if(!this.targetSelection) {
if (!this.targetSelection) {
this.currentTargets.forEach(ct => {
if(this.targets.find(t => t.actorId === ct.actorId)) return;
if (this.targets.find(t => t.actorId === ct.actorId)) return;
const indexTarget = this.oldTargets.findIndex(ot => ot.actorId === ct.actorId);
if(indexTarget === -1)
this.oldTargets.push(ct);
if (indexTarget === -1) this.oldTargets.push(ct);
});
if(this.hasSave) this.setPendingSaves();
if(this.currentTargets.length) {
if(!this.parent._id) return;
const updates = await this.parent.update(
{
system: {
oldTargets: this.oldTargets
}
if (this.hasSave) this.setPendingSaves();
if (this.currentTargets.length) {
if (!this.parent._id) return;
const updates = await this.parent.update({
system: {
oldTargets: this.oldTargets
}
);
if(!updates && ui.chat.collection.get(this.parent.id))
ui.chat.updateMessage(this.parent);
});
if (!updates && ui.chat.collection.get(this.parent.id)) ui.chat.updateMessage(this.parent);
}
}
}
registerTargetHook() {
if(this.targetSelection && this.targetHook !== null) {
Hooks.off("targetToken", this.targetHook);
if (this.targetSelection && this.targetHook !== null) {
Hooks.off('targetToken', this.targetHook);
this.targetHook = null;
} else if(!this.targetSelection && this.targetHook === null) {
this.targetHook = Hooks.on("targetToken", foundry.utils.debounce(this.updateTargets.bind(this), 50));
} else if (!this.targetSelection && this.targetHook === null) {
this.targetHook = Hooks.on('targetToken', foundry.utils.debounce(this.updateTargets.bind(this), 50));
}
}
prepareDerivedData() {
if(this.hasTarget) {
if (this.hasTarget) {
this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0;
this.updateTargets();
this.registerTargetHook();
if(this.targetSelection === true) {
this.targetShort = this.targets.reduce((a,c) => {
if(c.hit) a.hit += 1;
else c.miss += 1;
return a;
}, {hit: 0, miss: 0})
if (this.targetSelection === true) {
this.targetShort = this.targets.reduce(
(a, c) => {
if (c.hit) a.hit += 1;
else a.miss += 1;
return a;
},
{ hit: 0, miss: 0 }
);
}
if(this.hasSave) this.setPendingSaves();
if (this.hasSave) this.setPendingSaves();
}
this.canViewSecret = this.parent.speakerActor?.testUserPermission(game.user, 'OBSERVER');
}
getTargetList() {
return this.targetSelection !== true
? Array.from(game.user.targets).map(t =>{
const target = game.system.api.fields.ActionFields.TargetField.formatTarget(t),
oldTarget = this.targets.find(ot => ot.actorId === target.actorId) ?? this.oldTargets.find(ot => ot.actorId === target.actorId);
if(oldTarget) return oldTarget;
return target;
})
? Array.from(game.user.targets).map(t => {
const target = game.system.api.fields.ActionFields.TargetField.formatTarget(t),
oldTarget =
this.targets.find(ot => ot.actorId === target.actorId) ??
this.oldTargets.find(ot => ot.actorId === target.actorId);
if (oldTarget) return oldTarget;
return target;
})
: this.targets;
}
setPendingSaves() {
this.pendingSaves = this.targetSelection
? this.targets.filter(
target => target.hit && target.saved.success === null
).length > 0
: this.currentTargets.filter(
target => target.saved.success === null
).length > 0;
? this.targets.filter(target => target.hit && target.saved.success === null).length > 0
: this.currentTargets.filter(target => target.saved.success === null).length > 0;
}
}

View file

@ -4,7 +4,25 @@ export default class BeastformField extends fields.SchemaField {
constructor(options = {}, context = {}) {
const beastformFields = {
tierAccess: new fields.SchemaField({
exact: new fields.NumberField({ integer: true, nullable: true, initial: null })
exact: new fields.NumberField({
integer: true,
nullable: true,
initial: null,
choices: () => {
const settingsTiers = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.LevelTiers
).tiers;
return Object.values(settingsTiers).reduce(
(acc, tier) => {
acc[tier.tier] = game.i18n.localize(tier.name);
return acc;
},
{ 1: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') }
);
},
hint: 'DAGGERHEART.ACTIONS.Config.beastform.exactHint'
})
})
};
super(beastformFields, options, context);

View file

@ -12,7 +12,10 @@ export default class UsesField extends fields.SchemaField {
initial: null,
nullable: true
}),
consumeOnSuccess: new fields.BooleanField({ initial: false, label: "DAGGERHEART.ACTIONS.Settings.consumeOnSuccess.label" })
consumeOnSuccess: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.ACTIONS.Settings.consumeOnSuccess.label'
})
};
super(usesFields, options, context);
}
@ -30,6 +33,7 @@ export default class UsesField extends fields.SchemaField {
if (!uses) return null;
return {
...uses,
remaining: this.remainingUses,
enabled: uses.hasOwnProperty('enabled') ? uses.enabled : true
};
}

View file

@ -1,4 +1,5 @@
import DHActionConfig from '../../applications/sheets-configs/action-config.mjs';
import { itemAbleRollParse } from '../../helpers/utils.mjs';
import MappingField from './mappingField.mjs';
/**
@ -164,6 +165,15 @@ export function ActionMixin(Base) {
return foundry.utils.getProperty(this.parent, this.systemPath) instanceof Collection;
}
get remainingUses() {
if (!this.uses) return null;
return Math.max(
(this.uses.max ? itemAbleRollParse(this.uses.max, this.actor) : 0) - (this.uses.value ?? 0),
0
);
}
static async create(data, operation = {}) {
const { parent, renderSheet } = operation;
let { type } = data;

View file

@ -88,6 +88,26 @@ export default class DHBeastform extends BaseDataItem {
/* -------------------------------------------- */
get beastformAttackData() {
const effect = this.parent.effects.find(x => x.type === 'beastform');
if (!effect) return null;
const traitBonus = effect.changes.find(x => x.key === `system.traits.${this.mainTrait}.value`)?.value ?? 0;
const evasionBonus = effect.changes.find(x => x.key === 'system.evasion')?.value ?? 0;
const damageDiceIndex = effect.changes.find(x => x.key === 'system.rules.attack.damage.diceIndex');
const damageDice = damageDiceIndex ? Object.keys(CONFIG.DH.GENERAL.diceTypes)[damageDiceIndex.value] : null;
const damageBonus = effect.changes.find(x => x.key === 'system.rules.attack.damage.bonus')?.value ?? 0;
return {
trait: game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.mainTrait].label),
traitBonus: traitBonus ? Number(traitBonus).signedString() : '',
evasionBonus: evasionBonus ? Number(evasionBonus).signedString() : '',
damageDice: damageDice,
damageBonus: damageBonus ? `${Number(damageBonus).signedString()}` : ''
};
}
async _preCreate() {
if (!this.actor) return;

View file

@ -45,6 +45,11 @@ export default class DhAutomation extends foundry.abstract.DataModel {
required: true,
initial: true,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.resourceScrollTexts.label'
}),
playerCanEditSheet: new fields.BooleanField({
required: true,
initial: false,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.playerCanEditSheet.label'
})
};
}

View file

@ -45,10 +45,10 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
initial: 'Coins',
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.coinName'
}),
handfulls: new fields.StringField({
handfuls: new fields.StringField({
required: true,
initial: 'Handfulls',
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.handfullName'
initial: 'Handfuls',
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName'
}),
bags: new fields.StringField({
required: true,

View file

@ -1,25 +0,0 @@
export default class DhRangeMeasurement extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
enabled: new fields.BooleanField({ required: true, initial: true, label: 'DAGGERHEART.GENERAL.enabled' }),
melee: new fields.NumberField({ required: true, initial: 5, label: 'DAGGERHEART.CONFIG.Range.melee.name' }),
veryClose: new fields.NumberField({
required: true,
initial: 15,
label: 'DAGGERHEART.CONFIG.Range.veryClose.name'
}),
close: new fields.NumberField({
required: true,
initial: 30,
label: 'DAGGERHEART.CONFIG.Range.close.name'
}),
far: new fields.NumberField({ required: true, initial: 60, label: 'DAGGERHEART.CONFIG.Range.far.name' }),
veryFar: new fields.NumberField({
required: true,
initial: 120,
label: 'DAGGERHEART.CONFIG.Range.veryFar.name'
})
};
}
}

View file

@ -17,9 +17,28 @@ export default class DhVariantRules extends foundry.abstract.DataModel {
label: 'DAGGERHEART.SETTINGS.VariantRules.FIELDS.actionTokens.tokens.label'
})
}),
useCoins: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.SETTINGS.VariantRules.FIELDS.useCoins.label'
rangeMeasurement: new fields.SchemaField({
enabled: new fields.BooleanField({
required: true,
initial: true,
label: 'DAGGERHEART.GENERAL.enabled'
}),
melee: new fields.NumberField({
required: true,
initial: 5,
label: 'DAGGERHEART.CONFIG.Range.melee.name'
}),
veryClose: new fields.NumberField({
required: true,
initial: 15,
label: 'DAGGERHEART.CONFIG.Range.veryClose.name'
}),
close: new fields.NumberField({
required: true,
initial: 30,
label: 'DAGGERHEART.CONFIG.Range.close.name'
}),
far: new fields.NumberField({ required: true, initial: 60, label: 'DAGGERHEART.CONFIG.Range.far.name' })
})
};
}

View file

@ -1,5 +1,4 @@
export { default as DhAppearance } from './Appearance.mjs';
export { default as DhAutomation } from './Automation.mjs';
export { default as DhHomebrew } from './Homebrew.mjs';
export { default as DhRangeMeasurement } from './RangeMeasurement.mjs';
export { default as DhVariantRules } from './VariantRules.mjs';

View file

@ -18,9 +18,7 @@ export default class D20Roll extends DHRoll {
static DefaultDialog = D20RollDialog;
get title() {
return game.i18n.localize(
"DAGGERHEART.GENERAL.d20Roll"
);
return game.i18n.localize('DAGGERHEART.GENERAL.d20Roll');
}
get d20() {
@ -145,9 +143,9 @@ export default class D20Roll extends DHRoll {
config.targetSelection = true;
config.targets.forEach(target => {
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
target.hit = this.isCritical || roll.total >= difficulty;
target.hit = roll.isCritical || roll.total >= difficulty;
});
data.success = config.targets.some(target => target.hit)
data.success = config.targets.some(target => target.hit);
} else if (config.roll.difficulty) {
data.difficulty = config.roll.difficulty;
data.success = roll.isCritical || roll.total >= config.roll.difficulty;

View file

@ -9,9 +9,8 @@ export default class DamageRoll extends DHRoll {
static DefaultDialog = DamageDialog;
static async buildEvaluate(roll, config = {}, message = {}) {
if (config.evaluate !== false)
for (const roll of config.roll) await roll.roll.evaluate();
if (config.evaluate !== false) for (const roll of config.roll) await roll.roll.evaluate();
roll._evaluated = true;
const parts = config.roll.map(r => this.postEvaluate(r));
@ -42,7 +41,7 @@ export default class DamageRoll extends DHRoll {
if (config.source?.message) {
const chatMessage = ui.chat.collection.get(config.source.message);
chatMessage.update({ 'system.damage': config.damage });
}
}
}
static unifyDamageRoll(rolls) {
@ -84,11 +83,11 @@ export default class DamageRoll extends DHRoll {
applyBaseBonus(part) {
const modifiers = [],
type = this.options.messageType ?? (this.options.isHealing ? 'healing' : 'damage'),
type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage'),
options = part ?? this.options;
modifiers.push(...this.getBonus(`${type}`, `${type.capitalize()} Bonus`));
if (!this.options.isHealing) {
if (!this.options.hasHealing) {
options.damageTypes?.forEach(t => {
modifiers.push(...this.getBonus(`${type}.${t}`, `${t.capitalize()} ${type.capitalize()} Bonus`));
});

View file

@ -75,7 +75,8 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
if (isOriginTarget && change.effect.origin) {
change.value = change.value.replaceAll(/origin\.@/gi, '@');
try {
const doc = foundry.utils.fromUuidSync(change.effect.origin);
const effect = foundry.utils.fromUuidSync(change.effect.origin);
const doc = effect.parent?.parent;
if (doc) parseModel = doc;
} catch (_) {}
}

View file

@ -202,7 +202,8 @@ export default class DhpActor extends Actor {
await this.update({
[`system.experiences.${experienceKey}`]: {
name: experience.name,
value: experience.modifier
value: experience.modifier,
core: true
}
});
@ -210,7 +211,8 @@ export default class DhpActor extends Actor {
await this.system.companion.update({
[`system.experiences.${experienceKey}`]: {
name: '',
value: experience.modifier
value: experience.modifier,
core: true
}
});
}
@ -559,8 +561,8 @@ export default class DhpActor extends Actor {
updates.forEach(
u =>
(u.value =
u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value)
(u.value =
u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value)
);
await this.modifyResource(updates);
@ -606,9 +608,9 @@ export default class DhpActor extends Actor {
updates.forEach(
u =>
(u.value = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false)
? u.value * -1
: u.value)
(u.value = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false)
? u.value * -1
: u.value)
);
await this.modifyResource(updates);

View file

@ -74,8 +74,8 @@ export default class DHItem extends foundry.documents.Item {
isInventoryItem === true
? 'Inventory Items' //TODO localize
: isInventoryItem === false
? 'Character Items' //TODO localize
: 'Other'; //TODO localize
? 'Character Items' //TODO localize
: 'Other'; //TODO localize
return { value: type, label, group };
}
@ -130,7 +130,6 @@ export default class DHItem extends foundry.documents.Item {
/* -------------------------------------------- */
async use(event) {
const actions = new Set(this.system.actionsList);
if (actions?.size) {
@ -152,10 +151,10 @@ export default class DHItem extends foundry.documents.Item {
this.type === 'ancestry'
? game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.ancestryTitle')
: this.type === 'community'
? game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.communityTitle')
: this.type === 'feature'
? game.i18n.localize('TYPES.Item.feature')
: game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.subclassFeatureTitle'),
? game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.communityTitle')
: this.type === 'feature'
? game.i18n.localize('TYPES.Item.feature')
: game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.subclassFeatureTitle'),
origin: origin,
img: this.img,
item: {
@ -163,7 +162,7 @@ export default class DHItem extends foundry.documents.Item {
img: this.img,
tags: this._getTags()
},
actions: item.system.actions,
actions: item.system.actionsList,
description: this.system.description
};

View file

@ -161,7 +161,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
for (const [index, itemValue] of pathValue.entries()) {
const itemIsAction = itemValue instanceof game.system.api.models.actions.actionsTypes.base;
const value = itemIsAction || !itemValue?.item ? itemValue : itemValue.item;
const enrichedValue = await TextEditor.enrichHTML(value.description);
const enrichedValue = await TextEditor.enrichHTML(value.system?.description ?? value.description);
if (itemIsAction) value.enrichedDescription = enrichedValue;
else foundry.utils.setProperty(item, `${basePath}.${index}.enrichedDescription`, enrichedValue);
}

View file

@ -1,5 +1,3 @@
import { range as configRange } from '../config/generalConfig.mjs';
export default function DhTemplateEnricher(match, _options) {
const parts = match[1].split('|').map(x => x.trim());
@ -17,7 +15,7 @@ export default function DhTemplateEnricher(match, _options) {
type = matchedType;
break;
case 'range':
const matchedRange = Object.values(configRange).find(
const matchedRange = Object.values(CONFIG.DH.GENERAL.templateRanges).find(
x => x.id.toLowerCase() === split[1] || x.short === split[1]
);
range = matchedRange?.id;
@ -55,7 +53,9 @@ export const renderMeasuredTemplate = async event => {
? '180'
: undefined;
const baseDistance = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.RangeMeasurement)[range];
const baseDistance = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).rangeMeasurement[
range
];
const distance = type === CONFIG.DH.GENERAL.templateTypes.EMANATION ? baseDistance + 2.5 : baseDistance;
const { width, height } = game.canvas.scene.dimensions;

View file

@ -313,8 +313,10 @@ export const itemAbleRollParse = (value, actor, item) => {
const isItemTarget = value.toLowerCase().includes('item.@');
const slicedValue = isItemTarget ? value.replaceAll(/item\.@/gi, '@') : value;
const model = isItemTarget ? item : actor;
try {
return Roll.replaceFormulaData(slicedValue, isItemTarget ? item : actor);
return Roll.replaceFormulaData(slicedValue, model?.getRollData?.() ?? model);
} catch (_) {
return '';
}
@ -362,6 +364,7 @@ export async function createEmbeddedItemWithEffects(actor, baseData, update) {
const [doc] = await actor.createEmbeddedDocuments('Item', [
{
...(update ?? data),
...baseData,
id: data.id,
uuid: data.uuid,
effects: data.effects?.map(effect => effect.toObject())
@ -371,6 +374,21 @@ export async function createEmbeddedItemWithEffects(actor, baseData, update) {
return doc;
}
export async function createEmbeddedItemsWithEffects(actor, baseData) {
const effectData = [];
for (let d of baseData) {
const data = d.uuid.startsWith('Compendium') ? await foundry.utils.fromUuid(d.uuid) : d;
effectData.push({
...data,
id: data.id,
uuid: data.uuid,
effects: data.effects?.map(effect => effect.toObject())
});
}
await actor.createEmbeddedDocuments('Item', effectData);
}
export const slugify = name => {
return name.toLowerCase().replaceAll(' ', '-').replaceAll('.', '');
};

View file

@ -1,17 +1,10 @@
import { defaultLevelTiers, DhLevelTiers } from '../data/levelTier.mjs';
import DhCountdowns from '../data/countdowns.mjs';
import {
DhAppearance,
DhAutomation,
DhHomebrew,
DhRangeMeasurement,
DhVariantRules
} from '../data/settings/_module.mjs';
import { DhAppearance, DhAutomation, DhHomebrew, DhVariantRules } from '../data/settings/_module.mjs';
import {
DhAppearanceSettings,
DhAutomationSettings,
DhHomebrewSettings,
DhRangeMeasurementSettings,
DhVariantRuleSettings
} from '../applications/settings/_module.mjs';
@ -58,12 +51,6 @@ const registerMenuSettings = () => {
}
}
});
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.RangeMeasurement, {
scope: 'world',
config: false,
type: DhRangeMeasurement
});
};
const registerMenus = () => {
@ -83,14 +70,6 @@ const registerMenus = () => {
type: DhHomebrewSettings,
restricted: true
});
game.settings.registerMenu(CONFIG.DH.id, CONFIG.DH.SETTINGS.menu.Range.Name, {
name: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.range.name'),
label: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.range.label'),
hint: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.range.hint'),
icon: CONFIG.DH.SETTINGS.menu.Range.Icon,
type: DhRangeMeasurementSettings,
restricted: true
});
game.settings.registerMenu(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, {
name: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.appearance.title'),