This commit is contained in:
Dapoolp 2025-06-27 18:00:17 +02:00
parent 8423ab6776
commit ddbb6c4fd8
34 changed files with 609 additions and 378 deletions

View file

@ -316,9 +316,12 @@ const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/views/actionTypes/uuid.hbs',
'systems/daggerheart/templates/views/actionTypes/uses.hbs',
'systems/daggerheart/templates/views/actionTypes/roll.hbs',
'systems/daggerheart/templates/views/actionTypes/save.hbs',
'systems/daggerheart/templates/views/actionTypes/cost.hbs',
'systems/daggerheart/templates/views/actionTypes/range-target.hbs',
'systems/daggerheart/templates/views/actionTypes/effect.hbs',
'systems/daggerheart/templates/settings/components/settings-item-line.hbs'
'systems/daggerheart/templates/settings/components/settings-item-line.hbs',
'systems/daggerheart/templates/chat/parts/target-chat.hbs'
]);
};

View file

@ -1,18 +1,13 @@
import { DualityRollColor } from '../data/settings/Appearance.mjs';
import DHDualityRoll from '../data/chat-message/dualityRoll.mjs';
export default class DhpChatMessage extends foundry.documents.ChatMessage {
async renderHTML() {
if (this.type === 'dualityRoll' || this.type === 'adversaryRoll') {
this.content = await foundry.applications.handlebars.renderTemplate(this.content, this.system);
}
if(this.system.messageTemplate) this.content = await foundry.applications.handlebars.renderTemplate(this.system.messageTemplate, this.system);
/* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */
const html = await super.renderHTML();
this.applyPermission(html);
if (this.type === 'dualityRoll') {
html.classList.add('duality');
/* const dualityResult = this.system.dualityResult; */
switch (this.system.roll.result.duality) {
case 1:
html.classList.add('hope');
@ -24,11 +19,18 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
html.classList.add('critical');
break;
}
/* if (dualityResult === DHDualityRoll.dualityResult.hope) html.classList.add('hope');
else if (dualityResult === DHDualityRoll.dualityResult.fear) html.classList.add('fear');
else html.classList.add('critical'); */
}
return html;
}
applyPermission(html) {
const elements = html.querySelectorAll('[data-perm-id]');
elements.forEach(e => {
const uuid = e.dataset.permId,
document = fromUuidSync(uuid);
e.setAttribute('data-view-perm', document.testUserPermission(game.user, 'OBSERVER'));
e.setAttribute('data-use-perm', document.testUserPermission(game.user, 'OWNER'));
});
}
}

View file

@ -65,9 +65,10 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack')
context.hasBaseDamage = !!this.action.parent.damage;
context.getRealIndex = this.getRealIndex.bind(this);
context.getEffectDetails = this.getEffectDetails.bind(this);
context.disableOption = this.disableOption.bind(this);
context.isNPC = this.action.actor && this.action.actor.type !== 'character';
context.hasRoll = this.action.hasRoll();
context.hasRoll = this.action.hasRoll;
console.log(context)
return context;
}
@ -90,24 +91,16 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
return data.damage.parts.find(d => d.base) ? index - 1 : index;
}
getEffectDetails(id) {
return this.action.item.effects.get(id);
}
_prepareSubmitData(event, formData) {
const submitData = foundry.utils.expandObject(formData.object);
for ( const keyPath of this.constructor.CLEAN_ARRAYS ) {
const data = foundry.utils.getProperty(submitData, keyPath);
if ( data ) foundry.utils.setProperty(submitData, keyPath, Object.values(data));
/* const data = foundry.utils.getProperty(submitData, keyPath),
originalData = foundry.utils.getProperty(this.action.toObject(), keyPath);
if ( data ) {
const aData = Object.values(data);
originalData.forEach((v,i) => {
aData[i] = {...originalData[i], ...aData[i]};
})
foundry.utils.setProperty(submitData, keyPath, aData);
} */
}
// this.element.querySelectorAll("fieldset[disabled] :is(input, select)").forEach(input => {
// foundry.utils.setProperty(submitData, input.name, input.value);
// });
return submitData;
}
@ -138,6 +131,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
}
static removeElement(event) {
event.stopPropagation();
const data = this.action.toObject(),
key = event.target.closest('.action-category-data').dataset.key,
index = event.target.dataset.index;
@ -192,5 +186,8 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
}
static editEffect(event) {}
static editEffect(event) {
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
this.action.item.effects.get(id).sheet.render(true);
}
}

View file

@ -16,7 +16,7 @@ export class DHRoll extends Roll {
if (!roll) return;
await this.buildEvaluate(roll, config, (message = {}));
await this.buildPost(roll, config, (message = {}));
return roll;
return config;
}
static async buildConfigure(config = {}, message = {}) {
@ -58,7 +58,7 @@ export class DHRoll extends Roll {
if (message.data) {
} else {
const messageData = {};
await this.toMessage(roll, config);
config.message = await this.toMessage(roll, config);
}
}
@ -71,10 +71,9 @@ export class DHRoll extends Roll {
user: game.user.id,
sound: config.mute ? null : CONFIG.sounds.dice,
system: config,
content: await this.messageTemplate(config),
rolls: [roll]
};
await cls.create(msg);
return await cls.create(msg);
}
static applyKeybindings(config) {
@ -110,12 +109,6 @@ export class D20Roll extends DHRoll {
static messageType = 'adversaryRoll';
// static messageTemplate = 'systems/daggerheart/templates/chat/adversary-roll.hbs';
static messageTemplate = async config => {
return 'systems/daggerheart/templates/chat/adversary-roll.hbs';
};
static CRITICAL_TRESHOLD = 20;
static DefaultDialog = D20RollDialog;
@ -214,9 +207,9 @@ export class D20Roll extends DHRoll {
if (config.targets?.length) {
config.targets.forEach(target => {
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
target.hit = roll.total >= difficulty;
target.hit = this.isCritical || roll.total >= difficulty;
});
} else if (config.roll.difficulty) roll.success = roll.total >= config.roll.difficulty;
} else if (config.roll.difficulty) config.roll.success = roll.isCritical || roll.total >= config.roll.difficulty;
config.roll.total = roll.total;
config.roll.formula = roll.formula;
config.roll.advantage = {
@ -260,12 +253,6 @@ export class DualityRoll extends D20Roll {
static messageType = 'dualityRoll';
// static messageTemplate = 'systems/daggerheart/templates/chat/duality-roll.hbs';
static messageTemplate = async config => {
return 'systems/daggerheart/templates/chat/duality-roll.hbs';
};
static DefaultDialog = D20RollDialog;
get dHope() {
@ -395,21 +382,22 @@ export class DamageRoll extends DHRoll {
static messageType = 'damageRoll';
// static messageTemplate = 'systems/daggerheart/templates/chat/damage-roll.hbs';
static messageTemplate = async config => {
return await foundry.applications.handlebars.renderTemplate(
config.messageTemplate ?? 'systems/daggerheart/templates/chat/damage-roll.hbs',
config
);
};
static DefaultDialog = DamageDialog;
static async postEvaluate(roll, config = {}) {
config.roll = {
result: roll.total,
dice: roll.dice,
total: roll.total,
formula: roll.formula,
type: config.type
};
config.roll.dice = [];
roll.dice.forEach(d => {
config.roll.dice.push({
dice: d.denomination,
total: d.total,
formula: d.formula,
results: d.results
});
});
}
}

View file

@ -54,7 +54,9 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
context.document = this.document;
context.tabs = super._getTabs(this.constructor.TABS);
context.systemFields.attack.fields = this.document.system.attack.schema.fields;
context.getEffectDetails = this.getEffectDetails.bind(this);
context.isNPC = true;
console.log(context)
return context;
}
@ -80,6 +82,10 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
this.actor.diceRoll(config);
}
getEffectDetails(id) {
return {};
}
static async attackRoll(event) {
this.actor.system.attack.use(event);
}

View file

@ -5,7 +5,6 @@ import AncestrySelectionDialog from '../ancestrySelectionDialog.mjs';
import DaggerheartSheet from './daggerheart-sheet.mjs';
import { abilities } from '../../config/actorConfig.mjs';
import DhlevelUp from '../levelup.mjs';
import DHDualityRoll from '../../data/chat-message/dualityRoll.mjs';
const { ActorSheetV2 } = foundry.applications.sheets;
const { TextEditor } = foundry.applications.ux;
@ -370,47 +369,9 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
title: game.i18n.format('DAGGERHEART.Chat.DualityRoll.AbilityCheckTitle', { ability: abilityLabel }),
roll: {
trait: button.dataset.attribute
/* label: abilityLabel,
modifier: button.dataset.value */
}
/* chatMessage: {
template: 'systems/daggerheart/templates/chat/duality-roll.hbs'
} */
};
this.document.diceRoll(config);
// Delete when new roll logic test done
/* const { roll, hope, fear, advantage, disadvantage, modifiers } = await this.document.dualityRoll(
{ title: game.i18n.localize(abilities[button.dataset.attribute].label), value: button.dataset.value },
event.shiftKey
);
const cls = getDocumentClass('ChatMessage');
const systemContent = new DHDualityRoll({
title: game.i18n.format('DAGGERHEART.Chat.DualityRoll.AbilityCheckTitle', {
ability: game.i18n.localize(abilities[button.dataset.attribute].label)
}),
origin: this.document.id,
roll: roll._formula,
modifiers: modifiers,
hope: hope,
fear: fear,
advantage: advantage,
disadvantage: disadvantage
});
await cls.create({
type: 'dualityRoll',
sound: CONFIG.sounds.dice,
system: systemContent,
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/duality-roll.hbs',
systemContent
),
rolls: [roll]
}); */
}
static async toggleMarks(_, button) {

View file

@ -53,6 +53,7 @@ export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.tabs = super._getTabs(this.constructor.TABS);
context.getEffectDetails = this.getEffectDetails.bind(this);
return context;
}
@ -62,6 +63,10 @@ export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) {
this.render();
}
getEffectDetails(id) {
return {};
}
static async addAdversary() {
await this.document.update({
[`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize(

View file

@ -111,16 +111,18 @@ export default function DHItemMixin(Base) {
}
}
static async editAction(_, button) {
const action = this.document.system.actions[button.dataset.index];
static async editAction(event, button) {
const index = event.target.closest('[data-index]').dataset.index,
action = this.document.system.actions[index];
await new DHActionConfig(action).render(true);
}
static async removeAction(event, button) {
event.stopPropagation();
const action = event.target.closest('[data-index]').dataset.index;
await this.document.update({
'system.actions': this.document.system.actions.filter(
(_, index) => index !== Number.parseInt(button.dataset.index)
(_, index) => index !== Number.parseInt(action)
)
});
}

View file

@ -59,3 +59,21 @@ export const targetTypes = {
label: 'Any'
}
};
export const damageOnSave = {
none: {
id: 'none',
label: 'None',
mod: 0
},
half: {
id: 'half',
label: 'Half Damage',
mod: 0.5
},
full: {
id: 'full',
label: 'Full damage',
mod: 1
}
}

View file

@ -1,52 +1,24 @@
import DamageSelectionDialog from '../../applications/damageSelectionDialog.mjs';
import CostSelectionDialog from '../../applications/costSelectionDialog.mjs';
import { abilities } from '../../config/actorConfig.mjs';
import { DHActionDiceData, DHDamageData, DHDamageField } from './actionDice.mjs';
import DhpActor from '../../documents/actor.mjs';
import D20RollDialog from '../../dialogs/d20RollDialog.mjs';
const fields = foundry.data.fields;
/*
!!! I'm currently refactoring the whole Action thing, it's a WIP !!!
*/
/*
ToDo
- Add setting for Hope/Fear result on Damage, Heal, Resource (Handle Roll result as part of formula if needed)
- Add setting and/or checkbox for cost and damage like
- Target Check / Target Picker
- Range Check
- Area of effect and measurement placement
- Summon Action create method
- Create classes form Target, Cost, etc ?
Other
- Add optionnal Role for Healing ?
- Auto use action <= Into Roll
Done
- Cost Check
- Auto use costs
- Auto disable selected Cost from other cost list
- Apply ActiveEffect => Add to Chat message like Damage Button ?
- Add Drag & Drop for documentUUID field (Macro & Summon)
Activity Types List
- Attack => Weapon Attack, Spell Attack, etc...
- Effects => Like Attack without damage
- Damage => Like Attack without roll
- Healing
- Resource => Merge Healing & Resource ?
- Summon
- Sequencer => Trigger a list of Activities set on the item one by one
- Macro
Actor Modifier
- Weapon Attack
- Spell Attack
- Weapon Damage
- Magical Damage
- Physical Damage ?
- Magical Damage ?
- Healing
- Bard Rally (Math.ceil(LeveL / 5))
*/
export class DHBaseAction extends foundry.abstract.DataModel {
@ -105,7 +77,8 @@ export class DHBaseAction extends foundry.abstract.DataModel {
}),
save: new fields.SchemaField({
trait: new fields.StringField({ nullable: true, initial: null, choices: SYSTEM.ACTOR.abilities }),
difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 })
difficulty: new fields.NumberField({ nullable: true, initial: 10, integer: true, min: 0 }),
damageMod: new fields.StringField({ initial: SYSTEM.ACTIONS.damageOnSave.none.id, choices: SYSTEM.ACTIONS.damageOnSave })
}),
target: new fields.SchemaField({
type: new fields.StringField({
@ -118,7 +91,8 @@ export class DHBaseAction extends foundry.abstract.DataModel {
}),
effects: new fields.ArrayField( // ActiveEffect
new fields.SchemaField({
_id: new fields.DocumentIdField()
_id: new fields.DocumentIdField(),
onSave: new fields.BooleanField({ initial: false })
})
),
healing: new fields.SchemaField({
@ -186,22 +160,122 @@ export class DHBaseAction extends foundry.abstract.DataModel {
getRollData() {
const actorData = this.actor.getRollData(false);
return {
...actorData.toObject(),
prof: actorData.proficiency?.value ?? 1,
cast: actorData.spellcast?.value ?? 1,
scale: this.cost.length
// Remove when included directly in Actor getRollData
actorData.prof = actorData.proficiency?.value ?? 1,
actorData.cast = actorData.spellcast?.value ?? 1,
actorData.scale = this.cost.length
? this.cost.reduce((a, c) => {
a[c.type] = c.value;
return a;
}, {})
: 1,
roll: {}
};
actorData.roll = {}
return actorData;
}
async use(event, ...args) {
const isFastForward = event.shiftKey || (!this.hasRoll && !this.hasSave);
// Prepare base Config
const initConfig = this.initActionConfig(event);
// let config = this.initActionConfig(event);
// Prepare Targets
const targetConfig = this.prepareTarget();
if (isFastForward && !targetConfig) return ui.notifications.warn('Too many targets selected for that actions.');
// config = this.prepareTarget(config);
// Prepare Range
const rangeConfig = this.prepareRange();
// config = this.prepareRange(config);
// Prepare Costs
const costsConfig = this.prepareCost();
if(isFastForward && !this.hasCost(costsConfig)) return ui.notifications.warn("You don't have the resources to use that action.");
// config = this.prepareUseCost(config)
// Prepare Uses
const usesConfig = this.prepareUse();
if(isFastForward && !this.hasUses(usesConfig)) return ui.notifications.warn("That action doesn't have remaining uses.");
// config = this.prepareUseCost(config)
// Prepare Roll Data
const actorData = this.getRollData();
let config = {
...initConfig,
targets: targetConfig,
range: rangeConfig,
costs: costsConfig,
uses: usesConfig,
data: actorData
}
if ( Hooks.call(`${SYSTEM.id}.preUseAction`, this, config) === false ) return;
// Display configuration window if necessary
if ( config.dialog.configure && this.requireConfigurationDialog(config) ) {
config = await D20RollDialog.configure(config);
if (!config) return;
}
if ( this.hasRoll ) {
const rollConfig = this.prepareRoll(config);
config.roll = rollConfig;
config = await this.actor.diceRoll(config);
if (!config) return;
}
if( this.hasSave ) {
/* config.targets.forEach((t) => {
if(t.hit) {
const target = game.canvas.tokens.get(t.id),
actor = target?.actor;
console.log(actor)
if(!actor) return;
actor.saveRoll({
event,
title: 'Roll Save',
roll: {
trait: this.save.trait,
difficulty: this.save.difficulty
},
dialog: {
configure: false
},
data: actor.getRollData()
}).then(async (result) => {
t.saved = result;
setTimeout(async () => {
const message = ui.chat.collection.get(config.message.id),
msgTargets = message.system.targets,
msgTarget = msgTargets.find(mt => mt.id === t.id);
msgTarget.saved = result;
await message.update({'system.targets': msgTargets});
},100)
})
}
}) */
}
if ( this.doFollowUp() ) {
if(this.rollDamage) await this.rollDamage(event, config);
if(this.rollHealing) await this.rollHealing(event, config);
if(this.trigger) await this.trigger(event, config);
}
// Consume resources
await this.consume(config);
if ( Hooks.call(`${SYSTEM.id}.postUseAction`, this, config) === false ) return;
return config;
}
/* */
initActionConfig(event) {
return {
event,
title: this.item.name,
source: {
@ -209,79 +283,93 @@ export class DHBaseAction extends foundry.abstract.DataModel {
action: this._id
// action: this
},
dialog: {
configure: true
},
type: this.type,
hasDamage: !!this.damage?.parts?.length,
hasHealing: !!this.healing,
hasEffect: !!this.effects?.length
};
// this.proceedChatDisplay(config);
// Filter selected targets based on Target parameters
config.targets = await this.getTarget(config);
if (!config.targets) return ui.notifications.warn('Too many targets selected for that actions.');
// Filter selected targets based on Range parameters
config.range = await this.checkRange(config);
if (!config.range.hasRange) return ui.notifications.warn('No Target within range.');
// Display Uses/Costs Dialog & Check if Actor get enough resources
config = {
...config,
...(await this.getCost(config))
};
if ((!this.hasRoll() || config.event.shiftKey) && (!this.hasCost(config.costs) || !this.hasUses(config.uses)))
return ui.notifications.warn("You don't have the resources to use that action.");
// Proceed with Roll
config = await this.proceedRoll(config);
if (this.roll && !config.roll.result) return;
// Update Actor resources based on Action Cost configuration
this.spendCost(config.costs.values);
this.spendUses(config.uses);
return config;
hasEffect: !!this.effects?.length,
hasSave: this.hasSave
}
}
requireConfigurationDialog(config) {
return !config.event.shiftkey && !this.hasRoll && (config.costs?.length || config.uses);
}
prepareCost() {
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
return costs;
}
prepareUse() {
const uses = this.uses?.max ? foundry.utils.deepClone(this.uses) : null;
if (uses && !uses.value) uses.value = 0;
return uses;
}
prepareTarget() {
let targets;
if (this.target?.type === SYSTEM.ACTIONS.targetTypes.self.id)
targets = this.formatTarget(this.actor.token ?? this.actor.prototypeToken);
targets = Array.from(game.user.targets);
// foundry.CONST.TOKEN_DISPOSITIONS.FRIENDLY
if (this.target?.type && this.target.type !== SYSTEM.ACTIONS.targetTypes.any.id) {
targets = targets.filter(t => this.isTargetFriendly(t));
if (this.target.amount && targets.length > this.target.amount) targets = [];
}
targets = targets.map(t => this.formatTarget(t));
return targets;
}
prepareRange() {
const range = this.range ?? null;
return range;
}
prepareRoll() {
const roll = {
modifiers: [],
trait: this.roll?.trait,
label: 'Attack',
type: this.actionType,
difficulty: this.roll?.difficulty
};
return roll;
}
doFollowUp(config) {
return !this.hasRoll;
}
async consume(config) {
const resources = config.costs.filter(c => c.enabled !== false).map(c => {
return { type: c.type, value: c.total * -1 };
});
await this.actor.modifyResource(resources);
if(config.uses?.enabled) {
const newActions = foundry.utils.getProperty(this.item.system, this.systemPath).map(x => x.toObject());
newActions[this.index].uses.value++;
await this.item.update({ [`system.${this.systemPath}`]: newActions });
}
}
/* */
/* ROLL */
hasRoll() {
// return this.roll?.type && this.roll?.trait;
get hasRoll() {
return !!this.roll?.type;
}
async proceedRoll(config) {
if (!this.hasRoll()) return config;
// const modifierValue = this.actor.system.traits[this.roll.trait].value;
config = {
...config,
roll: {
modifiers: [],
trait: this.roll?.trait,
// label: game.i18n.localize(abilities[this.roll.trait].label),
label: 'Attack',
type: this.actionType,
difficulty: this.roll?.difficulty
}
};
// config = await this.actor.diceRoll(config, this);
return this.actor.diceRoll(config, this);
}
/* ROLL */
/* COST */
async getCost(config) {
let costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
let uses = this.getUses();
if (!config.event.shiftKey && !this.hasRoll() && !(!costs.length && !uses)) {
const dialogClosed = new Promise((resolve, _) => {
new CostSelectionDialog(costs, uses, this, resolve).render(true);
});
({ costs, uses } = await dialogClosed);
}
return { costs, uses };
/* SAVE */
get hasSave() {
return !!this.save?.trait;
}
/* SAVE */
/* COST */
getRealCosts(costs) {
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
@ -302,28 +390,9 @@ export class DHBaseAction extends foundry.abstract.DataModel {
const realCosts = this.getRealCosts(costs);
return realCosts.reduce((a, c) => a && this.actor.system.resources[c.type]?.value >= (c.total ?? c.value), true);
}
async spendCost(config) {
if (!config.costs?.values?.length) return;
return await this.actor.modifyResource(config.costs.values);
}
/* COST */
/* USES */
async spendUses(config) {
if (!this.uses.max || config.enabled === false) return;
const newActions = foundry.utils.getProperty(this.item.system, this.systemPath).map(x => x.toObject());
newActions[this.index].uses.value++;
await this.item.update({ [`system.${this.systemPath}`]: newActions });
}
getUses() {
if (!this.uses?.max) return null;
const uses = foundry.utils.deepClone(this.uses);
if (!uses.value) uses.value = 0;
return uses;
}
calcUses(uses) {
if(!uses) return null;
return {
@ -339,18 +408,6 @@ export class DHBaseAction extends foundry.abstract.DataModel {
/* USES */
/* TARGET */
async getTarget(config) {
if (this.target?.type === SYSTEM.ACTIONS.targetTypes.self.id)
return this.formatTarget(this.actor.token ?? this.actor.prototypeToken);
let targets = Array.from(game.user.targets);
// foundry.CONST.TOKEN_DISPOSITIONS.FRIENDLY
if (this.target?.type && this.target.type !== SYSTEM.ACTIONS.targetTypes.any.id) {
targets = targets.filter(t => this.isTargetFriendly(t));
if (this.target.amount && targets.length > this.target.amount) return false;
}
return targets.map(t => this.formatTarget(t));
}
isTargetFriendly(target) {
const actorDisposition = this.actor.token
? this.actor.token.disposition
@ -365,6 +422,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
formatTarget(actor) {
return {
id: actor.id,
actorId: actor.actor.uuid,
name: actor.actor.name,
img: actor.actor.img,
difficulty: actor.actor.system.difficulty,
@ -374,18 +432,20 @@ export class DHBaseAction extends foundry.abstract.DataModel {
/* TARGET */
/* RANGE */
async checkRange(config) {
if (!this.range || !this.actor) return true;
return { values: [], hasRange: true };
}
/* RANGE */
/* EFFECTS */
async applyEffects(event, data, force = false) {
if (!this.effects?.length || !data.system.targets.length) return;
let effects = this.effects;
data.system.targets.forEach(async token => {
if (!token.hit && !force) return;
this.effects.forEach(async e => {
if(this.hasSave && token.saved.success === true) {
effects = this.effects.filter(e => e.onSave === true)
}
if(!effects.length) return;
effects.forEach(async e => {
const actor = canvas.tokens.get(token.id)?.actor,
effect = this.item.effects.get(e._id);
if (!actor || !effect) return;
@ -417,28 +477,55 @@ export class DHBaseAction extends foundry.abstract.DataModel {
}
/* EFFECTS */
/* CHAT */
async proceedChatDisplay(config) {
if (!this.chatDisplay) return;
/* SAVE */
async rollSave(target, event, message) {
if(!target?.actor) return;
target.actor.diceRoll({
event,
title: 'Roll Save',
roll: {
trait: this.save.trait,
difficulty: this.save.difficulty,
type: "reaction"
},
data: target.actor.getRollData()
}).then(async (result) => {
this.updateChatMessage(message, target.id, {result: result.roll.total, success: result.roll.success});
})
}
/* CHAT */
async updateChatMessage(message, targetId, changes, chain=true) {
setTimeout(async () => {
const chatMessage = ui.chat.collection.get(message._id),
msgTargets = chatMessage.system.targets,
msgTarget = msgTargets.find(mt => mt.id === targetId);
msgTarget.saved = changes;
await chatMessage.update({'system.targets': msgTargets});
},100);
if(chain) {
if(message.system.source.message) this.updateChatMessage(ui.chat.collection.get(message.system.source.message), targetId, changes, false);
const relatedChatMessages = ui.chat.collection.filter(c => c.system.source.message === message._id);
relatedChatMessages.forEach(c => {
this.updateChatMessage(c, targetId, changes, false);
})
}
}
/* SAVE */
}
export class DHDamageAction extends DHBaseAction {
directDamage = true;
static extraSchemas = ['damage', 'target', 'effects'];
async use(event, ...args) {
/* async use(event, ...args) {
const config = await super.use(event, args);
if (!config || ['error', 'warning'].includes(config.type)) return;
if (!this.directDamage) return;
return await this.rollDamage(event, config);
}
} */
getFormulaValue(part, data) {
let formulaValue = part.value;
if(this.hasRoll() && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
if(this.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
return formulaValue;
}
@ -448,25 +535,25 @@ export class DHDamageAction extends DHBaseAction {
if (!formula || formula == '') return;
let roll = { formula: formula, total: formula },
bonusDamage = [];
const config = {
title: game.i18n.format('DAGGERHEART.Chat.DamageRoll.Title', { damage: this.name }),
formula,
targets: (data.system?.targets ?? data.targets).map(x => ({
id: x.id,
name: x.name,
img: x.img,
hit: true
}))
targets: (data.system?.targets.filter(t => t.hit) ?? data.targets),
hasSave: this.hasSave,
source: data.system?.source
};
if(this.hasSave) config.onSave = this.save.damageMod;
if(data.system) {
config.source.message = data._id;
config.directDamage = false;
}
roll = CONFIG.Dice.daggerheart.DamageRoll.build(config);
}
}
export class DHAttackAction extends DHDamageAction {
directDamage = false;
static extraSchemas = [...super.extraSchemas, ...['roll', 'save']];
static getRollType(parent) {
@ -505,16 +592,9 @@ export class DHHealingAction extends DHBaseAction {
return 'spellcast';
}
async use(event, ...args) {
const config = await super.use(event, args);
if (!config || ['error', 'warning'].includes(config.type)) return;
if (this.hasRoll()) return;
return await this.rollHealing(event, config);
}
getFormulaValue(data) {
let formulaValue = this.healing.value;
if(this.hasRoll() && this.healing.resultBased && data.system.roll.result.duality === -1) return this.healing.valueAlt;
if(this.hasRoll && this.healing.resultBased && data.system.roll.result.duality === -1) return this.healing.valueAlt;
return formulaValue;
}
@ -531,13 +611,8 @@ export class DHHealingAction extends DHBaseAction {
healing: game.i18n.localize(SYSTEM.GENERAL.healingTypes[this.healing.type].label)
}),
formula,
targets: (data.system?.targets ?? data.targets).map(x => ({
id: x.id,
name: x.name,
img: x.img,
hit: true
})),
messageTemplate: 'systems/daggerheart/templates/chat/healing-roll.hbs',
targets: (data.system?.targets ?? data.targets).filter(t => t.hit),
messageType: 'healing',
type: this.healing.type
};
@ -557,9 +632,9 @@ export class DHSummonAction extends DHBaseAction {
};
}
async use(event, ...args) {
async trigger(event, ...args) {
if (!this.canSummon || !canvas.scene) return;
const config = await super.use(event, args);
// const config = await super.use(event, args);
}
get canSummon() {
@ -614,9 +689,9 @@ export class DHMacroAction extends DHBaseAction {
};
}
async use(event, ...args) {
const config = await super.use(event, args);
if (['error', 'warning'].includes(config.type)) return;
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

@ -1,6 +1,3 @@
import DhpActor from '../../documents/actor.mjs';
import ActionField from '../fields/actionField.mjs';
export default class DHAdversaryRoll extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
@ -11,16 +8,22 @@ export default class DHAdversaryRoll extends foundry.abstract.TypeDataModel {
targets: 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 })
hit: new fields.BooleanField({ initial: false }),
saved: new fields.SchemaField({
result: new fields.NumberField(),
success: new fields.BooleanField({ nullable: true, initial: null })
})
})
),
hasDamage: new fields.BooleanField({ initial: false }),
hasHealing: new fields.BooleanField({ initial: false }),
hasEffect: new fields.BooleanField({ initial: false }),
hasSave: new fields.BooleanField({ initial: false }),
source: new fields.SchemaField({
actor: new fields.StringField(),
item: new fields.StringField(),
@ -28,4 +31,8 @@ export default class DHAdversaryRoll extends foundry.abstract.TypeDataModel {
})
};
}
get messageTemplate() {
return 'systems/daggerheart/templates/chat/adversary-roll.hbs';
}
}

View file

@ -3,34 +3,35 @@ export default class DHDamageRoll extends foundry.abstract.TypeDataModel {
const fields = foundry.data.fields;
return {
messageType: new fields.StringField({initial: 'damage'}),
title: new fields.StringField(),
// roll: new fields.StringField({ required: true }),
roll: new fields.DataField({}),
/* damage: new fields.SchemaField({
total: new fields.NumberField({ required: true, integer: true }),
type: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.damageTypes), integer: false })
}),
dice: new fields.ArrayField(
new fields.SchemaField({
type: new fields.StringField({ required: true }),
rolls: new fields.ArrayField(new fields.NumberField({ required: true, integer: true })),
total: new fields.NumberField({ integer: true })
})
),
modifiers: new fields.ArrayField(
new fields.SchemaField({
value: new fields.NumberField({ required: true, integer: true }),
operator: new fields.StringField({ required: true, choices: ['+', '-', '*', '/'] })
})
), */
targets: new fields.ArrayField(
new fields.SchemaField({
id: new fields.StringField({ required: true }),
actorId: new fields.StringField({}),
name: new fields.StringField(),
img: new fields.StringField(),
hit: new fields.BooleanField({ initial: false })
hit: new fields.BooleanField({ initial: false }),
saved: new fields.SchemaField({
result: new fields.NumberField(),
success: new fields.BooleanField({ nullable: true, initial: null })
})
})
)
),
hasSave: new fields.BooleanField({ initial: false }),
onSave: new fields.StringField(),
source: new fields.SchemaField({
actor: new fields.StringField(),
item: new fields.StringField(),
action: new fields.StringField(),
message: new fields.StringField()
}),
directDamage: new fields.BooleanField({initial: true})
};
}
get messageTemplate() {
return `systems/daggerheart/templates/chat/${this.messageType}-roll.hbs`;
}
}

View file

@ -14,16 +14,22 @@ export default class DHDualityRoll extends foundry.abstract.TypeDataModel {
targets: 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 })
hit: new fields.BooleanField({ initial: false }),
saved: new fields.SchemaField({
result: new fields.NumberField(),
success: new fields.BooleanField({ nullable: true, initial: null })
})
})
),
hasDamage: new fields.BooleanField({ initial: false }),
hasHealing: new fields.BooleanField({ initial: false }),
hasEffect: new fields.BooleanField({ initial: false }),
hasSave: new fields.BooleanField({ initial: false }),
source: new fields.SchemaField({
actor: new fields.StringField(),
item: new fields.StringField(),
@ -31,4 +37,8 @@ export default class DHDualityRoll extends foundry.abstract.TypeDataModel {
})
};
}
get messageTemplate() {
return 'systems/daggerheart/templates/chat/duality-roll.hbs';
}
}

View file

@ -50,6 +50,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.hasRoll = !!this.config.roll;
context.experiences = Object.keys(this.config.data.experiences).map(id => ({
id,
...this.config.data.experiences[id]
@ -67,6 +68,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.uses = this.action.calcUses(this.config.uses);
context.canRoll = context.canRoll && this.action.hasUses(context.uses);
}
console.log(context, _options)
return context;
}
@ -101,9 +103,9 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
if (!options.submitted) this.config = false;
}
static async configure(config = {}) {
static async configure(config = {}, options={}) {
return new Promise(resolve => {
const app = new this(config);
const app = new this(config, options);
app.addEventListener('close', () => resolve(app.config), { once: true });
app.render({ force: true });
});

View file

@ -1,9 +1,5 @@
import DamageSelectionDialog from '../applications/damageSelectionDialog.mjs';
import NpcRollSelectionDialog from '../applications/npcRollSelectionDialog.mjs';
import RollSelectionDialog from '../applications/rollSelectionDialog.mjs';
import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs';
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
import DHDualityRoll from '../data/chat-message/dualityRoll.mjs';
import DamageReductionDialog from '../applications/damageReductionDialog.mjs';
export default class DhpActor extends Actor {
@ -265,12 +261,14 @@ export default class DhpActor extends Actor {
* @param {object} [config.targets]
* @param {object} [config.costs]
*/
async diceRoll(config, action) {
// config.source = {...(config.source ?? {}), actor: this._id};
async diceRoll(config) {
config.source = {...(config.source ?? {}), actor: this.uuid};
config.data = this.getRollData()
const roll = await CONFIG.Dice.daggerheart[this.type === 'character' ? 'DualityRoll' : 'D20Roll'].build(config)
return config;
config.data = this.getRollData();
return await this.rollClass.build(config);
}
get rollClass() {
return CONFIG.Dice.daggerheart[this.type === 'character' ? 'DualityRoll' : 'D20Roll'];
}
getRollData() {

View file

@ -22,6 +22,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
html.querySelectorAll('.duality-action-healing').forEach(element =>
element.addEventListener('click', event => this.onRollHealing(event, data.message))
);
html.querySelectorAll('.target-save-container').forEach(element =>
element.addEventListener('click', event => this.onRollSave(event, data.message))
);
html.querySelectorAll('.duality-action-effect').forEach(element =>
element.addEventListener('click', event => this.onApplyEffect(event, data.message))
);
@ -98,6 +101,20 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
}
};
onRollSave = async (event, message) => {
event.stopPropagation();
const actor = await this.getActor(message.system.source.actor),
tokenId = event.target.closest('[data-token]')?.dataset.token,
token = game.canvas.tokens.get(tokenId);
if (!token?.actor || !token.isOwner) return true;
console.log(token.actor.canUserModify(game.user, 'update'));
if (message.system.source.item && message.system.source.action) {
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
if (!action || !action?.hasSave) return;
action.rollSave(token, event, message);
}
};
onApplyEffect = async (event, message) => {
event.stopPropagation();
const actor = await this.getActor(message.system.source.actor);
@ -137,10 +154,24 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
? message.system.targets.map(target => game.canvas.tokens.get(target.id))
: Array.from(game.user.targets);
if(message.system.onSave && event.currentTarget.dataset.targetHit) {
console.log(message.system.targets)
const pendingingSaves = message.system.targets.filter(target => target.hit && target.saved.success === null);
if(pendingingSaves.length) {
const confirm = await foundry.applications.api.DialogV2.confirm({
window: {title: "Pending Reaction Rolls found"},
content: `<p>Some Tokens still need to roll their Reaction Roll.</p><p>Are you sure you want to continue ?</p><p><i>Undone reaction rolls will be considered as failed</i></p>`
});
if ( !confirm ) return;
}
}
if (targets.length === 0)
ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected'));
for (var target of targets) {
await target.actor.takeDamage(message.system.roll.result, message.system.roll.type);
for (let target of targets) {
let damage = message.system.roll.total;
if(message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true) damage = Math.ceil(damage * (SYSTEM.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1));
await target.actor.takeDamage(damage, message.system.roll.type);
}
};
@ -152,7 +183,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected'));
for (var target of targets) {
await target.actor.takeHealing([{ value: message.system.roll.result, type: message.system.roll.type }]);
await target.actor.takeHealing([{ value: message.system.roll.total, type: message.system.roll.type }]);
}
};

View file

@ -6,6 +6,13 @@
}
}
fieldset.daggerheart.chat {
padding: 0;
border-left-width: 0;
border-right-width: 0;
border-bottom-width: 0;
}
.daggerheart.chat {
&.downtime {
display: flex;
@ -227,19 +234,35 @@
background: @miss;
}
img {
flex: 0;
img, .target-save-container {
width: 22px;
height: 22px;
margin-left: 8px;
align-self: center;
border-color: transparent;
}
img {
flex: 0;
margin-left: 8px;
}
.target-save-container {
margin-right: 8px;
justify-content: center;
display: flex;
align-items: center;
min-height: unset;
border: 1px solid black;
}
.target-inner-container {
flex: 1;
display: flex;
justify-content: center;
font-size: var(--font-size-16);
}
&:not(:has(.target-save-container)) .target-inner-container {
margin-right: @hugeMargin;
}
}
@ -313,12 +336,29 @@
width: 80px;
}
}
[data-use-perm='false'] {
pointer-events: none;
border-color: transparent;
}
[data-view-perm='false'] {
> * {
display: none;
}
&::after {
content: "??";
}
}
}
.theme-colorful {
.chat-message.duality {
border-color: black;
padding: 8px 0 0 0;
fieldset.daggerheart.chat {
border-top-width: 0;
display: contents;
}
.message-header {
color: var(--color-light-3);
padding: 0 8px;

View file

@ -1399,6 +1399,12 @@
.chat-message .dice-title {
display: none;
}
fieldset.daggerheart.chat {
padding: 0;
border-left-width: 0;
border-right-width: 0;
border-bottom-width: 0;
}
.daggerheart.chat.downtime {
display: flex;
flex-direction: column;
@ -1552,18 +1558,32 @@
.daggerheart.chat.roll .target-section .target-container.miss {
background: #ff0000;
}
.daggerheart.chat.roll .target-section .target-container img {
flex: 0;
.daggerheart.chat.roll .target-section .target-container img,
.daggerheart.chat.roll .target-section .target-container .target-save-container {
width: 22px;
height: 22px;
margin-left: 8px;
align-self: center;
border-color: transparent;
}
.daggerheart.chat.roll .target-section .target-container img {
flex: 0;
margin-left: 8px;
}
.daggerheart.chat.roll .target-section .target-container .target-save-container {
margin-right: 8px;
justify-content: center;
display: flex;
align-items: center;
min-height: unset;
border: 1px solid black;
}
.daggerheart.chat.roll .target-section .target-container .target-inner-container {
flex: 1;
display: flex;
justify-content: center;
font-size: var(--font-size-16);
}
.daggerheart.chat.roll .target-section .target-container:not(:has(.target-save-container)) .target-inner-container {
margin-right: 32px;
}
.daggerheart.chat.roll .dice-actions {
@ -1620,10 +1640,24 @@
.daggerheart.chat.domain-card img {
width: 80px;
}
.daggerheart.chat [data-use-perm='false'] {
pointer-events: none;
border-color: transparent;
}
.daggerheart.chat [data-view-perm='false'] > * {
display: none;
}
.daggerheart.chat [data-view-perm='false']::after {
content: "??";
}
.theme-colorful .chat-message.duality {
border-color: black;
padding: 8px 0 0 0;
}
.theme-colorful .chat-message.duality fieldset.daggerheart.chat {
border-top-width: 0;
display: contents;
}
.theme-colorful .chat-message.duality .message-header {
color: var(--color-light-3);
padding: 0 8px;

View file

@ -26,26 +26,18 @@
</section>
</div>
</div>
<div class="dice-total">
<div class="dice-total-value">{{roll.total}}</div>
</div>
{{#if (gt targets.length 0)}}
<div class="target-section">
{{#each targets as |target|}}
<div class="dice-total target-container {{#if target.hit}}hit{{else}}miss{{/if}}" data-token="{{target.id}}">
<img src="{{target.img}}" />
<div class="target-inner-container">
{{#if target.hit}}{{localize "Hit"}}{{else}}{{localize "Miss"}}{{/if}}
</div>
</div>
{{/each}}
</div>
{{/if}}
{{#if hasDamage}}
</div>
</div>
{{> 'systems/daggerheart/templates/chat/parts/target-chat.hbs'}}
{{#if hasDamage}}
<div class="dice-roll daggerheart chat roll">
<div class="dice-result">
<div class="flexrow">
<button class="duality-action duality-action-damage" data-value="{{roll.total}}"><span>Roll Damage</span></button>
</div>
{{/if}}
</div>
</div>
</div>
{{/if}}

View file

@ -1,15 +1,14 @@
<div class="dice-roll daggerheart chat roll" data-action="expandRoll">
<div class="dice-flavor">{{title}}</div>
<div class="dice-result">
<div class="dice-formula">{{formula}}</div>
<div class="dice-formula">{{roll.formula}}</div>
<div class="dice-tooltip">
<div class="wrapper">
<section class="tooltip-part">
{{#each roll.dice}}
<div class="dice">
<header class="part-header flexrow">
<span class="part-formula">{{formula}}</span>
<span class="part-formula">{{formula}}</span>
<span class="part-total">{{total}}</span>
</header>
<ol class="dice-rolls">
@ -22,7 +21,12 @@
</section>
</div>
</div>
<div class="dice-total">{{roll.result}}</div>
<div class="dice-total">{{roll.total}}</div>
</div>
</div>
{{> 'systems/daggerheart/templates/chat/parts/target-chat.hbs'}}
<div class="dice-roll daggerheart chat roll">
<div class="dice-result">
<div class="dice-actions">
<button class="damage-button" data-target-hit="true" {{#if (eq targets.length 0)}}disabled{{/if}}>{{localize "DAGGERHEART.Chat.DamageRoll.DealDamageToTargets"}}</button>
<button class="damage-button">{{localize "DAGGERHEART.Chat.DamageRoll.DealDamage"}}</button>

View file

@ -92,18 +92,11 @@
{{roll.total}}
</div>
</div>
{{#if (gt targets.length 0)}}
<div class="target-section">
{{#each targets as |target|}}
<div class="dice-total target-container {{#if target.hit}}hit{{else}}{{#if (not ../total.alternate)}}miss{{/if}}{{/if}}" data-token="{{target.id}}">
<img src="{{target.img}}" />
<div class="target-inner-container">
{{#if target.hit}}{{localize "Hit"}}{{else}}{{#if (not ../total.alternate)}}{{localize "Miss"}}{{else}}?{{/if}}{{/if}}
</div>
</div>
{{/each}}
</div>
{{/if}}
</div>
</div>
{{> 'systems/daggerheart/templates/chat/parts/target-chat.hbs'}}
<div class="dice-roll daggerheart chat roll">
<div class="dice-result">
<div class="dice-actions{{#unless (or hasDamage hasHealing)}} duality-alone{{/unless}}">
{{#if hasDamage}}
<button class="duality-action duality-action-damage" data-value="{{roll.total}}"><span>{{localize "DAGGERHEART.Chat.AttackRoll.RollDamage"}}</span></button>

View file

@ -1,7 +1,7 @@
<div class="dice-roll daggerheart chat roll" data-action="expandRoll">
<div class="dice-flavor">{{title}}</div>
<div class="dice-result">
<div class="dice-formula">{{formula}}</div>
<div class="dice-formula">{{roll.formula}}</div>
<div class="dice-tooltip">
<div class="wrapper">
<section class="tooltip-part">
@ -9,7 +9,6 @@
<div class="dice">
<header class="part-header flexrow">
<span class="part-formula">{{formula}}</span>
<span class="part-total">{{total}}</span>
</header>
<ol class="dice-rolls">
@ -22,7 +21,7 @@
</section>
</div>
</div>
<div class="dice-total">{{roll.result}}</div>
<div class="dice-total">{{roll.total}}</div>
<div class="flexrow">
<button class="healing-button"><span>{{localize "DAGGERHEART.Chat.HealingRoll.Heal"}}</span></button>
</div>

View file

@ -0,0 +1,32 @@
{{#if (gt targets.length 0)}}
<fieldset class="dice-roll daggerheart chat roll expanded" data-action="expandRoll">
<legend class="dice-flavor">Targets</legend>
<div class="dice-result">
<div class="dice-tooltip">
<div class="wrapper">
<div class="target-section">
{{#each targets as |target|}}
<div class="dice-total target-container {{#if target.hit}}hit{{else}}{{#if (not ../total.alternate)}}miss{{/if}}{{/if}}" data-token="{{target.id}}">
<img src="{{target.img}}" />
<div class="target-inner-container">
{{#if ../directDamage}}
<div data-perm-id="{{target.actorId}}"><span>{{target.name}}</span></div>
{{else}}
{{#if target.hit}}{{localize "Hit"}}{{else}}{{#if (not ../total.alternate)}}{{localize "Miss"}}{{else}}?{{/if}}{{/if}}
{{/if}}
</div>
{{#if ../hasSave}}
<button class="target-save-container{{#if target.saved.result includeZero=true}} is-rolled{{/if}}"{{#unless target.hit}} style="visibility: hidden;"{{/unless}} data-perm-id="{{target.actorId}}">
{{!-- {{target.saved.result}} --}}
<i class="fa-solid {{#if target.saved.result includeZero=true}}{{#if target.saved.success}}fa-check{{else}}fa-xmark{{/if}}{{else}}fa-shield{{/if}}">
</i>
</button>
{{/if}}
</div>
{{/each}}
</div>
</div>
</div>
</div>
</fieldset>
{{/if}}

View file

@ -73,7 +73,7 @@
</div>
{{> 'systems/daggerheart/templates/views/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=source.system.attack.damage path="system.attack."}}
<div style="grid-column: 1 / -1;">
{{> 'systems/daggerheart/templates/views/actionTypes/effect.hbs'}}
{{> 'systems/daggerheart/templates/views/actionTypes/effect.hbs' fields=systemFields.attack.fields.effects.element.fields source=source.system.attack.effects}}
</div>
</fieldset>
</div>

View file

@ -67,8 +67,8 @@
<div class="hope-section">
<h4>{{localize "DAGGERHEART.General.Hope"}}</h4>
{{#times document.system.resources.hope.max}}
<span class='hope-value' data-action='toggleHope' data-value="{{this}}">
{{#if (gte ../document.system.resources.hope.value this)}}
<span class='hope-value' data-action='toggleHope' data-value="{{add this 1}}">
{{#if (gte ../document.system.resources.hope.value (add this 1))}}
<i class='fa-solid fa-diamond'></i>
{{else}}
<i class='fa-regular fa-circle'></i>

View file

@ -7,12 +7,14 @@
<legend>{{localize "DAGGERHEART.Sheets.Global.Actions"}} <a><i class="fa-solid fa-plus icon-button" data-action="addAction"></i></a></legend>
<div class="actions-list">
{{#each document.system.actions as |action index|}}
<div class="action-item">
<div class="action-item"
data-action="editAction"
data-index="{{index}}"
>
<img class="image" src="{{action.img}}" />
<span>{{action.name}}</span>
<div class="controls">
<a data-action="editAction" data-index="{{index}}"><i class="fa-solid fa-pen-to-square"></i></a>
<a data-action="removeAction" data-index="{{index}}"><i class="fa-solid fa-trash"></i></a>
<a data-action="removeAction"><i class="fa-solid fa-trash"></i></a>
</div>
</div>
{{/each}}

View file

@ -6,7 +6,7 @@
<div class="hope-container">
{{#times 6}}
<span class="hope-inner-container">
<input class="hope-value" type="checkbox" data-action="toggleHope" data-value="{{this}}" {{ checked (gte ../document.system.resources.hope.value this) }} {{#if (gte this ../document.system.resources.hope.max)}}disabled{{/if}} />
<input class="hope-value" type="checkbox" data-action="toggleHope" data-value="{{add this 1}}" {{ checked (gte ../document.system.resources.hope.value (add this 1)) }} {{#if (gte this ../document.system.resources.hope.max)}}disabled{{/if}} />
{{#if (gte this ../document.system.resources.hope.max)}}<i class="fa-solid fa-droplet-slash hope-scar"></i>{{/if}}
</span>
{{/times}}

View file

@ -41,11 +41,12 @@
</div>
<div class="tab {{this.tabs.effect.cssClass}}" data-group="primary" data-tab="effect">
{{#if fields.roll}}{{> 'systems/daggerheart/templates/views/actionTypes/roll.hbs' fields=fields.roll.fields source=source.roll}}{{/if}}
{{#if fields.save}}{{> 'systems/daggerheart/templates/views/actionTypes/save.hbs' fields=fields.save.fields source=source.save}}{{/if}}
{{#if fields.damage}}{{> 'systems/daggerheart/templates/views/actionTypes/damage.hbs' fields=fields.damage.fields.parts.element.fields source=source.damage}}{{/if}}
{{#if fields.healing}}{{> 'systems/daggerheart/templates/views/actionTypes/healing.hbs' fields=fields.healing.fields source=source.healing}}{{/if}}
{{#if fields.resource}}{{> 'systems/daggerheart/templates/views/actionTypes/resource.hbs' fields=fields.resource.fields source=source.resource}}{{/if}}
{{#if fields.documentUUID}}{{> 'systems/daggerheart/templates/views/actionTypes/uuid.hbs' fields=fields.documentUUID source=source.documentUUID}}{{/if}}
{{#if fields.effects}}{{> 'systems/daggerheart/templates/views/actionTypes/effect.hbs'}}{{/if}}
{{#if fields.effects}}{{> 'systems/daggerheart/templates/views/actionTypes/effect.hbs' fields=fields.effects.element.fields source=source.effects}}{{/if}}
</div>
</section>
</div>

View file

@ -14,15 +14,15 @@
{{/unless}}
{{#each source.parts as |dmg index|}}
{{#if @root.isNPC}}
{{formField ../fields.custom.fields.enabled value=dmg.custom.enabled name=(concat ../path "damage.parts." index ".custom.enabled")}}
<input type="hidden" name="{{../path}}damage.parts.{{index}}.multiplier" value="{{dmg.multiplier}}">
{{#if dmg.custom.enabled}}
{{formField ../fields.custom.fields.formula value=dmg.custom.formula name=(concat ../path "damage.parts." index ".custom.formula") localize=true}}
{{formField ../fields.value.fields.custom.fields.enabled value=dmg.value.custom.enabled name=(concat ../path "damage.parts." index ".value.custom.enabled")}}
<input type="hidden" name="{{../path}}damage.parts.{{index}}.value.multiplier" value="{{dmg.value.multiplier}}">
{{#if dmg.value.custom.enabled}}
{{formField ../fields.value.fields.custom.fields.formula value=dmg.value.custom.formula name=(concat ../path "damage.parts." index ".value.custom.formula") localize=true}}
{{else}}
<div class="multi-display">
{{formField ../fields.flatMultiplier value=dmg.flatMultiplier name=(concat ../path "damage.parts." index ".flatMultiplier") label="Multiplier" }}
{{formField ../fields.dice value=dmg.dice name=(concat ../path "damage.parts." index ".dice")}}
{{formField ../fields.bonus value=dmg.bonus name=(concat ../path "damage.parts." index ".bonus") localize=true}}
{{formField ../fields.value.fields.flatMultiplier value=dmg.value.flatMultiplier name=(concat ../path "damage.parts." index ".value.flatMultiplier") label="Multiplier" }}
{{formField ../fields.value.fields.dice value=dmg.value.dice name=(concat ../path "damage.parts." index ".value.dice")}}
{{formField ../fields.value.fields.bonus value=dmg.value.bonus name=(concat ../path "damage.parts." index ".value.bonus") localize=true}}
</div>
{{/if}}
{{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." index ".type") localize=true}}

View file

@ -4,15 +4,31 @@
</legend>
<div class="action-category-data open" data-key="effects">
<div class="fas fa-plus icon-button" data-action="addEffect"></div>
{{#each @root.effects as | effect index | }}
{{!-- {{#each @root.effects as | effect index | }}
<fieldset>
{{!-- <div class="multi-display"> --}}
<div class="multi-display">
<div class="form-group">
<img src="{{img}}">
<label data-action="editEffect">{{name}}</label>
<div class="fas fa-trash" data-action="removeEffect" data-index="{{index}}"></div>
</div>
{{!-- </div> --}}
{{formfield }}
</div>
</fieldset>
{{/each}} --}}
{{#each source as | effect index |}}
<fieldset data-effect-id="{{effect._id}}">
<div class="multi-display">
{{#with (@root.getEffectDetails effect._id) as | details |}}
<div class="form-group" data-action="editEffect">
<img src="{{img}}">
<label>{{name}}</label>
<div class="fas fa-trash" data-action="removeEffect" data-index="{{index}}"></div>
</div>
{{/with}}
<input type="hidden" name="effects.{{index}}._id" value="{{effect._id}}">
{{formField ../fields.onSave value=effect.onSave name=(concat "effects." index ".onSave")}}
</div>
</fieldset>
{{/each}}
</div>

View file

@ -7,8 +7,8 @@
{{formField fields.bonus label="Bonus" name="roll.bonus" value=source.bonus}}
{{else}}
{{formField fields.type label="Type" name="roll.type" value=source.type localize=true}}
{{formField fields.trait label="Trait" name="roll.trait" value=source.trait localize=true}}
{{formField fields.difficulty label="Difficulty" name="roll.difficulty" value=source.difficulty}}
{{formField fields.trait label="Trait" name="roll.trait" value=source.trait localize=true disabled=(not source.type)}}
{{formField fields.difficulty label="Difficulty" name="roll.difficulty" value=source.difficulty disabled=(not source.type)}}
{{/if}}
</div>
</fieldset>

View file

@ -0,0 +1,10 @@
<fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="roll">
<div>Save</div>
</legend>
<div class="action-category-data open">
{{formField fields.trait label="Trait" name="save.trait" value=source.trait localize=true}}
{{formField fields.difficulty label="Difficulty" name="save.difficulty" value=source.difficulty disabled=(not source.trait)}}
{{formField fields.damageMod label="Damage on Save" name="save.damageMod" value=source.damageMod localize=true disabled=(not source.trait)}}
</div>
</fieldset>

View file

@ -18,7 +18,7 @@
</div>
</div>
{{/each}}
<footer>
{{!-- <footer>
<button data-action="sendCost"{{#unless canUse}} disabled{{/unless}}>Accept</button>
</footer>
</footer> --}}
</div>

View file

@ -1,4 +1,5 @@
<div>
{{#if @root.hasRoll}}
<div class="roll-dialog-container">
<div class="flexcol">
<div class="roll-dialog-experience-container">
@ -34,8 +35,9 @@
</div> --}}
{{!-- {{/if}} --}}
</div>
<footer>
<button data-action="submitRoll"{{#unless canRoll}} disabled{{/unless}}>Roll</button>
</footer>
</div>
{{/if}}
<footer>
<button data-action="submitRoll"{{#unless canRoll}} disabled{{/unless}}>Roll</button>
</footer>
</div>