Merge branch 'main' into feature/death-moves

This commit is contained in:
Chris Ryan 2026-01-03 11:28:29 +10:00
commit 702de3d42a
32 changed files with 310 additions and 116 deletions

View file

@ -326,6 +326,7 @@
"equip": "Equip", "equip": "Equip",
"sendToChat": "Send To Chat", "sendToChat": "Send To Chat",
"toLoadout": "Send to Loadout", "toLoadout": "Send to Loadout",
"recall": "Recall",
"toVault": "Send to Vault", "toVault": "Send to Vault",
"unequip": "Unequip", "unequip": "Unequip",
"useItem": "Use Item" "useItem": "Use Item"
@ -1800,7 +1801,9 @@
"label": "Long Rest: Bonus Long Rest Moves", "label": "Long Rest: Bonus Long Rest Moves",
"hint": "The number of extra Long Rest Moves the character can take during a Long Rest." "hint": "The number of extra Long Rest Moves the character can take during a Long Rest."
} }
} },
"target": "Target",
"targetSelf": "Self"
}, },
"maxLoadout": { "maxLoadout": {
"label": "Max Loadout Cards Bonus" "label": "Max Loadout Cards Bonus"

View file

@ -278,19 +278,26 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
'close', 'close',
async () => { async () => {
const selected = app.selected.toObject(); const selected = app.selected.toObject();
const evolved = app.evolved.form ? app.evolved.form.toObject() : null;
const data = await game.system.api.data.items.DHBeastform.getWildcardImage( const data = await game.system.api.data.items.DHBeastform.getWildcardImage(
app.configData.data.parent, app.configData.data.parent,
app.selected evolved ?? app.selected
); );
if (data) { if (data) {
if (!data.selectedImage) selected = null; if (!data.selectedImage) selected = null;
else { else {
if (data.usesDynamicToken) selected.system.tokenRingImg = data.selectedImage; const imageSource = evolved ?? selected;
else selected.system.tokenImg = data.selectedImage; if (imageSource.usesDynamicToken) imageSource.system.tokenRingImg = data.selectedImage;
else imageSource.system.tokenImg = data.selectedImage;
} }
} }
resolve({ selected: selected, evolved: app.evolved, hybrid: app.hybrid, item: featureItem }); resolve({
selected: selected,
evolved: { ...app.evolved, form: evolved },
hybrid: app.hybrid,
item: featureItem
});
}, },
{ once: true } { once: true }
); );

View file

@ -181,12 +181,17 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
.filter(x => category.moves[x].selected) .filter(x => category.moves[x].selected)
.flatMap(key => { .flatMap(key => {
const move = category.moves[key]; const move = category.moves[key];
const needsTarget = move.actions.filter(x => x.target?.type && x.target.type !== 'self').length > 0;
return [...Array(move.selected).keys()].map(_ => ({ return [...Array(move.selected).keys()].map(_ => ({
...move, ...move,
movePath: `${categoryKey}.moves.${key}` movePath: `${categoryKey}.moves.${key}`,
needsTarget: needsTarget
})); }));
}); });
}); });
const characters = game.actors.filter(x => x.type === 'character')
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
.filter(x => x.uuid !== this.actor.uuid);
const cls = getDocumentClass('ChatMessage'); const cls = getDocumentClass('ChatMessage');
const msg = { const msg = {
@ -206,7 +211,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title` `DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
), ),
actor: { name: this.actor.name, img: this.actor.img }, actor: { name: this.actor.name, img: this.actor.img },
moves: moves moves: moves,
characters: characters,
selfId: this.actor.uuid
} }
), ),
flags: { flags: {

View file

@ -51,6 +51,19 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
} }
}; };
async _prepareContext(options) {
const context = await super._prepareContext(options);
const featureForms = ['passive', 'action', 'reaction'];
context.features = context.document.system.features.sort((a, b) =>
a.system.featureForm !== b.system.featureForm
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
: a.sort - b.sort
);
return context;
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**

View file

@ -49,6 +49,19 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
} }
}; };
async _prepareContext(options) {
const context = await super._prepareContext(options);
const featureForms = ['passive', 'action', 'reaction'];
context.features = context.document.system.features.sort((a, b) =>
a.system.featureForm !== b.system.featureForm
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
: a.sort - b.sort
);
return context;
}
/** /**
* Adds a new category entry to the actor. * Adds a new category entry to the actor.
* @type {ApplicationClickAction} * @type {ApplicationClickAction}

View file

@ -26,7 +26,12 @@ export default class AdversarySheet extends DHBaseActorSheet {
} }
] ]
}, },
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }] dragDrop: [
{
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
dropSelector: null
}
],
}; };
static PARTS = { static PARTS = {
@ -88,6 +93,13 @@ export default class AdversarySheet extends DHBaseActorSheet {
context.resources.stress.emptyPips = context.resources.stress.emptyPips =
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0; context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
const featureForms = ['passive', 'action', 'reaction'];
context.features = this.document.system.features.sort((a, b) =>
a.system.featureForm !== b.system.featureForm
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
: a.sort - b.sort
);
return context; return context;
} }
@ -164,6 +176,16 @@ export default class AdversarySheet extends DHBaseActorSheet {
}); });
} }
/** @inheritdoc */
async _onDragStart(event) {
const inventoryItem = event.currentTarget.closest('.inventory-item');
if (inventoryItem) {
event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0);
}
super._onDragStart(event);
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/* Application Clicks Actions */ /* Application Clicks Actions */
/* -------------------------------------------- */ /* -------------------------------------------- */

View file

@ -318,6 +318,40 @@ export default class CharacterSheet extends DHBaseActorSheet {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
} }
}, },
{
name: 'recall',
icon: 'fa-solid fa-bolt-lightning',
condition: target => {
const doc = getDocFromElementSync(target);
return doc && doc.system.inVault;
},
callback: async (target, event) => {
const doc = await getDocFromElement(target);
const actorLoadout = doc.actor.system.loadoutSlot;
if (!actorLoadout.available) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
return;
}
if (doc.system.recallCost == 0) {
return doc.update({ 'system.inVault': false });
}
const type = 'effect';
const cls = game.system.api.models.actions.actionsTypes[type];
const action = new cls({
...cls.getSourceConfig(doc.system),
type: type,
chatDisplay: false,
cost: [{
key: 'stress',
value: doc.system.recallCost
}]
}, { parent: doc.system });
const config = await action.use(event);
if (config) {
return doc.update({ 'system.inVault': false });
}
}
},
{ {
name: 'toVault', name: 'toVault',
icon: 'fa-solid fa-arrow-down', icon: 'fa-solid fa-arrow-down',

View file

@ -25,7 +25,12 @@ export default class DhpEnvironment extends DHBaseActorSheet {
toggleResourceDice: DhpEnvironment.#toggleResourceDice, toggleResourceDice: DhpEnvironment.#toggleResourceDice,
handleResourceDice: DhpEnvironment.#handleResourceDice handleResourceDice: DhpEnvironment.#handleResourceDice
}, },
dragDrop: [{ dragSelector: '.inventory-item', dropSelector: null }] dragDrop: [
{
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
dropSelector: null
}
]
}; };
/**@override */ /**@override */
@ -74,6 +79,9 @@ export default class DhpEnvironment extends DHBaseActorSheet {
case 'header': case 'header':
await this._prepareHeaderContext(context, options); await this._prepareHeaderContext(context, options);
break;
case 'features':
await this._prepareFeaturesContext(context, options);
break; break;
case 'notes': case 'notes':
await this._prepareNotesContext(context, options); await this._prepareNotesContext(context, options);
@ -110,6 +118,22 @@ export default class DhpEnvironment extends DHBaseActorSheet {
} }
} }
/**
* Prepare render context for the features part.
* @param {ApplicationRenderContext} context
* @param {ApplicationRenderOptions} options
* @returns {Promise<void>}
* @protected
*/
async _prepareFeaturesContext(context, _options) {
const featureForms = ['passive', 'action', 'reaction'];
context.features = this.document.system.features.sort((a, b) =>
a.system.featureForm !== b.system.featureForm
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
: a.sort - b.sort
);
}
/** /**
* Prepare render context for the Header part. * Prepare render context for the Header part.
* @param {ApplicationRenderContext} context * @param {ApplicationRenderContext} context

View file

@ -134,7 +134,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
async actionUseButton(event, message) { async actionUseButton(event, message) {
const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset; const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset;
const parent = await foundry.utils.fromUuid(message.system.actor); const targetUuid = event.currentTarget.closest('.action-use-button-parent').querySelector('select')?.value;
const parent = await foundry.utils.fromUuid(targetUuid || message.system.actor)
const actionType = message.system.moves[moveIndex].actions[actionIndex]; const actionType = message.system.moves[moveIndex].actions[actionIndex];
const cls = game.system.api.models.actions.actionsTypes[actionType.type]; const cls = game.system.api.models.actions.actionsTypes[actionType.type];
const action = new cls( const action = new cls(
@ -146,7 +148,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
type: CONFIG.DH.ITEM.originItemType.restMove, type: CONFIG.DH.ITEM.originItemType.restMove,
itemPath: movePath, itemPath: movePath,
actionIndex: actionIndex actionIndex: actionIndex
} },
targetUuid: targetUuid
}, },
{ parent: parent.system } { parent: parent.system }
); );

View file

@ -232,7 +232,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'self' type: 'friendly'
}, },
damage: { damage: {
parts: [ parts: [
@ -298,7 +298,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'self' type: 'friendly'
}, },
damage: { damage: {
parts: [ parts: [
@ -341,7 +341,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'self' type: 'friendly'
}, },
damage: { damage: {
parts: [ parts: [
@ -407,7 +407,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'self' type: 'friendly'
}, },
damage: { damage: {
parts: [ parts: [

View file

@ -33,7 +33,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
initial: 'action', initial: 'action',
nullable: false, nullable: false,
required: true required: true
}) }),
targetUuid: new fields.StringField({ initial: undefined })
}; };
this.extraSchemas.forEach(s => { this.extraSchemas.forEach(s => {
@ -241,7 +242,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
selectedRollMode: game.settings.get('core', 'rollMode'), selectedRollMode: game.settings.get('core', 'rollMode'),
data: this.getRollData(), data: this.getRollData(),
evaluate: this.hasRoll, evaluate: this.hasRoll,
resourceUpdates: new ResourceUpdateMap(this.actor) resourceUpdates: new ResourceUpdateMap(this.actor),
targetUuid: this.targetUuid
}; };
DHBaseAction.applyKeybindings(config); DHBaseAction.applyKeybindings(config);

View file

@ -66,12 +66,20 @@ export default class BeastformEffect extends BaseEffect {
}; };
const updateToken = token => { const updateToken = token => {
const { x, y } = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid( let x = null,
token.object.scene.grid, y = null;
{ x: token.x, y: token.y, elevation: token.elevation }, if (token.object?.scene?.grid) {
baseUpdate.width, const positionData = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid(
baseUpdate.height token.object.scene.grid,
); { x: token.x, y: token.y, elevation: token.elevation },
baseUpdate.width,
baseUpdate.height
);
x = positionData.x;
y = positionData.y;
}
return { return {
...baseUpdate, ...baseUpdate,
x, x,

View file

@ -1,6 +1,6 @@
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs'; import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
import { ActionField } from '../fields/actionField.mjs'; import { ActionField } from '../fields/actionField.mjs';
import BaseDataActor from './base.mjs'; import BaseDataActor, { commonActorRules } from './base.mjs';
import { resourceField, bonusField } from '../fields/actorField.mjs'; import { resourceField, bonusField } from '../fields/actorField.mjs';
export default class DhpAdversary extends BaseDataActor { export default class DhpAdversary extends BaseDataActor {
@ -56,25 +56,11 @@ export default class DhpAdversary extends BaseDataActor {
}) })
}), }),
resources: new fields.SchemaField({ resources: new fields.SchemaField({
hitPoints: resourceField( hitPoints: resourceField(0, 0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
0, stress: resourceField(0, 0, 'DAGGERHEART.GENERAL.stress', true)
0,
'DAGGERHEART.GENERAL.HitPoints.plural',
true
),
stress: resourceField(
0,
0,
'DAGGERHEART.GENERAL.stress',
true
)
}), }),
rules: new fields.SchemaField({ rules: new fields.SchemaField({
conditionImmunities: new fields.SchemaField({ ...commonActorRules()
hidden: new fields.BooleanField({ initial: false }),
restrained: new fields.BooleanField({ initial: false }),
vulnerable: new fields.BooleanField({ initial: false })
})
}), }),
attack: new ActionField({ attack: new ActionField({
initial: { initial: {

View file

@ -2,21 +2,23 @@ import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs
import DHItem from '../../documents/item.mjs'; import DHItem from '../../documents/item.mjs';
import { getScrollTextData } from '../../helpers/utils.mjs'; import { getScrollTextData } from '../../helpers/utils.mjs';
const fields = foundry.data.fields;
const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) => const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
new foundry.data.fields.SchemaField({ new fields.SchemaField({
resistance: new foundry.data.fields.BooleanField({ resistance: new fields.BooleanField({
initial: false, initial: false,
label: `${resistanceLabel}.label`, label: `${resistanceLabel}.label`,
hint: `${resistanceLabel}.hint`, hint: `${resistanceLabel}.hint`,
isAttributeChoice: true isAttributeChoice: true
}), }),
immunity: new foundry.data.fields.BooleanField({ immunity: new fields.BooleanField({
initial: false, initial: false,
label: `${immunityLabel}.label`, label: `${immunityLabel}.label`,
hint: `${immunityLabel}.hint`, hint: `${immunityLabel}.hint`,
isAttributeChoice: true isAttributeChoice: true
}), }),
reduction: new foundry.data.fields.NumberField({ reduction: new fields.NumberField({
integer: true, integer: true,
initial: 0, initial: 0,
label: `${reductionLabel}.label`, label: `${reductionLabel}.label`,
@ -24,6 +26,25 @@ const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
}) })
}); });
/* Common rules applying to Characters and Adversaries */
export const commonActorRules = (extendedData = { damageReduction: {} }) => ({
conditionImmunities: new fields.SchemaField({
hidden: new fields.BooleanField({ initial: false }),
restrained: new fields.BooleanField({ initial: false }),
vulnerable: new fields.BooleanField({ initial: false })
}),
damageReduction: new fields.SchemaField({
thresholdImmunities: new fields.SchemaField({
minor: new fields.BooleanField({ initial: false })
}),
reduceSeverity: new fields.SchemaField({
magical: new fields.NumberField({ initial: 0, min: 0 }),
physical: new fields.NumberField({ initial: 0, min: 0 })
}),
...extendedData.damageReduction
})
});
/** /**
* Describes metadata about the actor data model type * Describes metadata about the actor data model type
* @typedef {Object} ActorDataModelMetadata * @typedef {Object} ActorDataModelMetadata
@ -54,7 +75,6 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
/** @inheritDoc */ /** @inheritDoc */
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields;
const schema = {}; const schema = {};
if (this.metadata.hasAttribution) { if (this.metadata.hasAttribution) {

View file

@ -1,7 +1,7 @@
import { burden } from '../../config/generalConfig.mjs'; import { burden } from '../../config/generalConfig.mjs';
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
import DhLevelData from '../levelData.mjs'; import DhLevelData from '../levelData.mjs';
import BaseDataActor from './base.mjs'; import BaseDataActor, { commonActorRules } from './base.mjs';
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
import { ActionField } from '../fields/actionField.mjs'; import { ActionField } from '../fields/actionField.mjs';
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs'; import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
@ -212,44 +212,41 @@ export default class DhCharacter extends BaseDataActor {
}), }),
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }), companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
rules: new fields.SchemaField({ rules: new fields.SchemaField({
damageReduction: new fields.SchemaField({ ...commonActorRules({
maxArmorMarked: new fields.SchemaField({ damageReduction: {
value: new fields.NumberField({ magical: new fields.BooleanField({ initial: false }),
required: true, physical: new fields.BooleanField({ initial: false }),
maxArmorMarked: new fields.SchemaField({
value: new fields.NumberField({
required: true,
integer: true,
initial: 1,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
}),
stressExtra: new fields.NumberField({
required: true,
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label',
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint'
})
}),
stressDamageReduction: new fields.SchemaField({
severe: stressDamageReductionRule(
'DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'
),
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
}),
increasePerArmorMark: new fields.NumberField({
integer: true, integer: true,
initial: 1, initial: 1,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus' label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
}), }),
stressExtra: new fields.NumberField({ disabledArmor: new fields.BooleanField({ intial: false })
required: true, }
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label',
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint'
})
}),
stressDamageReduction: new fields.SchemaField({
severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'),
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
}),
increasePerArmorMark: new fields.NumberField({
integer: true,
initial: 1,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
}),
magical: new fields.BooleanField({ initial: false }),
physical: new fields.BooleanField({ initial: false }),
thresholdImmunities: new fields.SchemaField({
minor: new fields.BooleanField({ initial: false })
}),
reduceSeverity: new fields.SchemaField({
magical: new fields.NumberField({ initial: 0, min: 0 }),
physical: new fields.NumberField({ initial: 0, min: 0 })
}),
disabledArmor: new fields.BooleanField({ intial: false })
}), }),
attack: new fields.SchemaField({ attack: new fields.SchemaField({
damage: new fields.SchemaField({ damage: new fields.SchemaField({
@ -278,11 +275,6 @@ export default class DhCharacter extends BaseDataActor {
}) })
}) })
}), }),
conditionImmunities: new fields.SchemaField({
hidden: new fields.BooleanField({ initial: false }),
restrained: new fields.BooleanField({ initial: false }),
vulnerable: new fields.BooleanField({ initial: false })
}),
runeWard: new fields.BooleanField({ initial: false }), runeWard: new fields.BooleanField({ initial: false }),
burden: new fields.SchemaField({ burden: new fields.SchemaField({
ignore: new fields.BooleanField() ignore: new fields.BooleanField()
@ -451,8 +443,7 @@ export default class DhCharacter extends BaseDataActor {
if ( if (
item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.foundation || item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.foundation ||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization && (item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) ||
subclassState >= 2) ||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3) (item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
) { ) {
return true; return true;

View file

@ -76,7 +76,7 @@ export default class BeastformField extends fields.SchemaField {
* @returns * @returns
*/ */
static async transform(selectedForm, evolvedData, hybridData) { static async transform(selectedForm, evolvedData, hybridData) {
const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm; const formData = evolvedData?.form ?? selectedForm;
const beastformEffect = formData.effects.find(x => x.type === 'beastform'); const beastformEffect = formData.effects.find(x => x.type === 'beastform');
if (!beastformEffect) { if (!beastformEffect) {
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect'); ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');

View file

@ -25,9 +25,12 @@ export default class TargetField extends fields.SchemaField {
config.hasTarget = true; config.hasTarget = true;
let targets; let targets;
// If the Action is configured as self-targeted, set targets as the owner. Probably better way than to fallback to getDependentTokens // If the Action is configured as self-targeted, set targets as the owner. Probably better way than to fallback to getDependentTokens
if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id) if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id) {
targets = [this.actor.token ?? this.actor.prototypeToken]; targets = [this.actor.token ?? this.actor.prototypeToken];
else { } else if (config.targetUuid) {
const actor = fromUuidSync(config.targetUuid);
targets = [actor.token ?? actor.prototypeToken];
} else {
targets = Array.from(game.user.targets); targets = Array.from(game.user.targets);
if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) { if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) {
targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type)); targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type));

View file

@ -218,12 +218,20 @@ export default class DHBeastform extends BaseDataItem {
} }
}; };
const tokenUpdate = token => { const tokenUpdate = token => {
const { x, y } = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid( let x = null,
token.object.scene.grid, y = null;
{ x: token.x, y: token.y, elevation: token.elevation }, if (token.object?.scene?.grid) {
width ?? token.width, const positionData = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid(
height ?? token.height token.object.scene.grid,
); { x: token.x, y: token.y, elevation: token.elevation },
width ?? token.width,
height ?? token.height
);
x = positionData.x;
y = positionData.y;
}
return { return {
...prototypeTokenUpdate, ...prototypeTokenUpdate,
x, x,

View file

@ -329,7 +329,7 @@ export default class DualityRoll extends D20Roll {
if (looseSpotlight && game.combat?.active) { if (looseSpotlight && game.combat?.active) {
const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId); const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId);
if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id); if (currentCombatant?.actorId == config.data.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
} }
} }

View file

@ -1,7 +1,7 @@
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs'; import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
import { LevelOptionType } from '../data/levelTier.mjs'; import { LevelOptionType } from '../data/levelTier.mjs';
import DHFeature from '../data/item/feature.mjs'; import DHFeature from '../data/item/feature.mjs';
import { createScrollText, damageKeyToNumber } from '../helpers/utils.mjs'; import { createScrollText, damageKeyToNumber, getDamageKey } from '../helpers/utils.mjs';
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs'; import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
import { ResourceUpdateMap } from '../data/action/baseAction.mjs'; import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
@ -543,6 +543,7 @@ export default class DhpActor extends Actor {
/* system gets repeated infinately which causes issues when trying to use the data for document creation */ /* system gets repeated infinately which causes issues when trying to use the data for document creation */
delete rollData.system; delete rollData.system;
rollData.id = this.id;
rollData.name = this.name; rollData.name = this.name;
rollData.system = this.system.getRollData(); rollData.system = this.system.getRollData();
rollData.prof = this.system.proficiency ?? 1; rollData.prof = this.system.proficiency ?? 1;
@ -630,6 +631,19 @@ export default class DhpActor extends Actor {
} }
} }
} }
if (this.type === 'adversary') {
const reducedSeverity = hpDamage.damageTypes.reduce((value, curr) => {
return Math.max(this.system.rules.damageReduction.reduceSeverity[curr], value);
}, 0);
hpDamage.value = Math.max(hpDamage.value - reducedSeverity, 0);
if (
hpDamage.value &&
this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)]
) {
hpDamage.value -= 1;
}
}
} }
updates.forEach( updates.forEach(

View file

@ -99,12 +99,35 @@
} }
} }
.action-use-button-parent {
width: 100%;
.action-use-target {
display:flex;
align-items: center;
justify-content: space-between;
gap: 4px;
width: 100%;
padding: 4px 8px 10px 40px;
font-size: var(--font-size-12);
label {
font-weight: bold;
}
select {
flex: 1;
}
}
}
.action-use-button { .action-use-button {
width: -webkit-fill-available; width: -webkit-fill-available;
margin: 0 8px; margin: 0 8px;
font-weight: 600; font-weight: 600;
height: 40px; height: 40px;
} }
} }
} }
} }

View file

@ -2,7 +2,7 @@
"id": "daggerheart", "id": "daggerheart",
"title": "Daggerheart", "title": "Daggerheart",
"description": "An unofficial implementation of the Daggerheart system", "description": "An unofficial implementation of the Daggerheart system",
"version": "1.4.2", "version": "1.4.4",
"compatibility": { "compatibility": {
"minimum": "13.346", "minimum": "13.346",
"verified": "13.351", "verified": "13.351",

View file

@ -1,5 +1,5 @@
<section <section
class="tab {{tabs.domains.cssClass}} {{tabs.domains.id}}" class="tab {{tabs.domains.cssClass}} {{tabs.domains.id}} scrollable"
data-tab="{{tabs.domains.id}}" data-tab="{{tabs.domains.id}}"
data-group="{{tabs.domains.group}}" data-group="{{tabs.domains.group}}"
> >

View file

@ -1,5 +1,5 @@
<section <section
class="tab {{tabs.downtime.cssClass}} {{tabs.downtime.id}}" class="tab {{tabs.downtime.cssClass}} {{tabs.downtime.id}} scrollable"
data-tab="{{tabs.downtime.id}}" data-tab="{{tabs.downtime.id}}"
data-group="{{tabs.downtime.group}}" data-group="{{tabs.downtime.group}}"
> >

View file

@ -1,5 +1,5 @@
<section <section
class="tab {{tabs.itemFeatures.cssClass}} {{tabs.itemFeatures.id}}" class="tab {{tabs.itemFeatures.cssClass}} {{tabs.itemFeatures.id}} scrollable"
data-tab="{{tabs.itemFeatures.id}}" data-tab="{{tabs.itemFeatures.id}}"
data-group="{{tabs.itemFeatures.group}}" data-group="{{tabs.itemFeatures.group}}"
> >

View file

@ -1,5 +1,5 @@
<section <section
class="tab {{tabs.settings.cssClass}} {{tabs.settings.id}}" class="tab {{tabs.settings.cssClass}} {{tabs.settings.id}} scrollable"
data-tab="{{tabs.settings.id}}" data-tab="{{tabs.settings.id}}"
data-group="{{tabs.settings.group}}" data-group="{{tabs.settings.group}}"
> >

View file

@ -1,5 +1,5 @@
<section <section
class="tab {{tabs.types.cssClass}} {{tabs.types.id}}" class="tab {{tabs.types.cssClass}} {{tabs.types.id}} scrollable"
data-tab="{{tabs.types.id}}" data-tab="{{tabs.types.id}}"
data-group="{{tabs.types.group}}" data-group="{{tabs.types.group}}"
> >

View file

@ -9,7 +9,7 @@
<fieldset> <fieldset>
<legend>{{localize tabs.features.label}}</legend> <legend>{{localize tabs.features.label}}</legend>
<ul class="feature-list"> <ul class="feature-list">
{{#each document.system.features as |feature|}} {{#each @root.features as |feature|}}
<li class="feature-item" id="{{feature.id}}" draggable="true"> <li class="feature-item" id="{{feature.id}}" draggable="true">
<img src="{{feature.img}}" alt=""> <img src="{{feature.img}}" alt="">
<div class="label"> <div class="label">

View file

@ -9,7 +9,7 @@
<fieldset> <fieldset>
<legend>{{localize tabs.features.label}}</legend> <legend>{{localize tabs.features.label}}</legend>
<ul class="feature-list"> <ul class="feature-list">
{{#each document.system.features as |feature|}} {{#each @root.features as |feature|}}
<li class="feature-item" id="{{feature.id}}"> <li class="feature-item" id="{{feature.id}}">
<img src="{{feature.img}}" alt=""> <img src="{{feature.img}}" alt="">
<div class="label"> <div class="label">

View file

@ -4,7 +4,7 @@
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title=tabs.features.label title=tabs.features.label
type='feature' type='feature'
collection=document.system.features collection=@root.features
hideContextMenu=true hideContextMenu=true
canCreate=true canCreate=true
showActions=true showActions=true

View file

@ -7,7 +7,7 @@
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title=tabs.features.label title=tabs.features.label
type='feature' type='feature'
collection=document.system.features collection=@root.features
hideContextMenu=true hideContextMenu=true
canCreate=true canCreate=true
showActions=true showActions=true

View file

@ -15,9 +15,22 @@
</div> </div>
</details> </details>
{{#each move.actions as | action index |}} {{#each move.actions as | action index |}}
<button class="action-use-button" data-move-index="{{@../key}}" data-action-index="{{index}}" data-move-path="{{../movePath}}" > <div class="action-use-button-parent">
<span>{{localize action.name}}</span> <button class="action-use-button" data-move-index="{{@../key}}" data-action-index="{{index}}" data-move-path="{{../movePath}}" >
</button> <span>{{localize action.name}}</span>
</button>
{{#if move.needsTarget}}
<div class="action-use-target">
<label>{{localize "DAGGERHEART.GENERAL.Bonuses.rest.target"}}:</label>
<select>
<option value="{{../../selfId}}">{{localize "DAGGERHEART.GENERAL.Bonuses.rest.targetSelf"}}</option>
{{#each ../../characters as | character |}}
<option value="{{character.uuid}}">{{character.name}}</option>
{{/each}}
</select>
</div>
{{/if}}
</div>
{{/each}} {{/each}}
{{/each}} {{/each}}
</ul> </ul>