Merged with data-models-structure branch

This commit is contained in:
WBHarry 2025-06-13 13:45:52 +02:00
commit d1c968d0a2
62 changed files with 1760 additions and 956 deletions

3
.gitignore vendored
View file

@ -3,4 +3,5 @@ node_modules
/packs /packs
Build Build
/build /build
foundry foundry
styles/daggerheart.css

View file

@ -135,43 +135,24 @@ Hooks.on(socketEvent.GMUpdate, async (action, uuid, update) => {
}); });
const renderDualityButton = async event => { const renderDualityButton = async event => {
const button = event.currentTarget; const button = event.currentTarget,
const attributeValue = button.dataset.attribute?.toLowerCase(); traitValue = button.dataset.trait?.toLowerCase(),
target = getCommandTarget();
const target = getCommandTarget();
if (!target) return; if (!target) return;
const rollModifier = attributeValue ? target.system.attributes[attributeValue].data.value : null; const config = {
const { roll, hope, fear, advantage, disadvantage, modifiers } = await target.diceRoll({ event: event,
title: button.dataset.label, title: button.dataset.title,
value: rollModifier roll: {
}); modifier: traitValue ? target.system.traits[traitValue].value : null,
label: button.dataset.label,
const systemData = new DHDualityRoll({ type: button.dataset.actionType ?? null // Need check
title: button.dataset.label, },
origin: target.id, chatMessage: {
roll: roll._formula, template: 'systems/daggerheart/templates/chat/duality-roll.hbs'
modifiers: modifiers, }
hope: hope,
fear: fear,
advantage: advantage,
disadvantage: disadvantage
});
const cls = getDocumentClass('ChatMessage');
const msgData = {
type: 'dualityRoll',
sound: CONFIG.sounds.dice,
system: systemData,
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/duality-roll.hbs',
systemData
),
rolls: [roll]
}; };
await target.diceRoll(config);
await cls.create(msgData);
}; };
Hooks.on('renderChatMessageHTML', (_, element) => { Hooks.on('renderChatMessageHTML', (_, element) => {
@ -200,64 +181,54 @@ Hooks.on('chatMessage', (_, message) => {
return false; return false;
} }
const attributeValue = rollCommand.attribute?.toLowerCase(); const traitValue = rollCommand.trait?.toLowerCase();
const advantageState = rollCommand.advantage ? true : rollCommand.disadvantage ? false : null;
// Target not required if an attribute is not used. // Target not required if an attribute is not used.
const target = attributeValue ? getCommandTarget() : undefined; const target = traitValue ? getCommandTarget() : undefined;
if (target || !attributeValue) { if (target || !traitValue) {
new Promise(async (resolve, reject) => { new Promise(async (resolve, reject) => {
const attribute = target ? target.system.attributes[attributeValue] : undefined; const trait = target ? target.system.traits[traitValue] : undefined;
if (attributeValue && !attribute) { if (traitValue && !trait) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.AttributeFaulty')); ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.AttributeFaulty'));
reject(); reject();
return; return;
} }
const title = attributeValue const title = traitValue
? game.i18n.format('DAGGERHEART.Chat.DualityRoll.AbilityCheckTitle', { ? game.i18n.format('DAGGERHEART.Chat.DualityRoll.AbilityCheckTitle', {
ability: game.i18n.localize(abilities[attributeValue].label) ability: game.i18n.localize(abilities[traitValue].label)
}) })
: game.i18n.localize('DAGGERHEART.General.Duality'); : game.i18n.localize('DAGGERHEART.General.Duality');
const hopeAndFearRoll = `1${rollCommand.hope ?? 'd12'}+1${rollCommand.fear ?? 'd12'}`; const hopeAndFearRoll = `1${rollCommand.hope ?? 'd12'}+1${rollCommand.fear ?? 'd12'}`;
const advantageRoll = `${rollCommand.advantage && !rollCommand.disadvantage ? '+d6' : rollCommand.disadvantage && !rollCommand.advantage ? '-d6' : ''}`; const advantageRoll = `${advantageState === true ? '+d6' : advantageState === false ? '-d6' : ''}`;
const attributeRoll = `${attribute?.data?.value ? `${attribute.data.value > 0 ? `+${attribute.data.value}` : `${attribute.data.value}`}` : ''}`; const attributeRoll = `${trait?.value ? `${trait.value > 0 ? `+${trait.value}` : `${trait.value}`}` : ''}`;
const roll = new Roll(`${hopeAndFearRoll}${advantageRoll}${attributeRoll}`); const roll = await Roll.create(`${hopeAndFearRoll}${advantageRoll}${attributeRoll}`).evaluate();
await roll.evaluate();
setDiceSoNiceForDualityRoll( setDiceSoNiceForDualityRoll(roll, advantageState);
roll,
rollCommand.advantage && !rollCommand.disadvantage,
rollCommand.disadvantage && !rollCommand.advantage
);
resolve({ resolve({
roll, roll,
attribute: attribute trait: trait
? { ? {
value: attribute.data.value, value: trait.value,
label: `${game.i18n.localize(abilities[attributeValue].label)} ${attribute.data.value >= 0 ? `+` : ``}${attribute.data.value}` label: `${game.i18n.localize(abilities[traitValue].label)} ${trait.value >= 0 ? `+` : ``}${trait.value}`
} }
: undefined, : undefined,
title title
}); });
}).then(async ({ roll, attribute, title }) => { }).then(async ({ roll, trait, title }) => {
const cls = getDocumentClass('ChatMessage'); const cls = getDocumentClass('ChatMessage');
const systemData = new DHDualityRoll({ const systemData = new DHDualityRoll({
title: title, title: title,
origin: target?.id, origin: target?.id,
roll: roll._formula, roll: roll,
modifiers: attribute ? [attribute] : [], modifiers: trait ? [trait] : [],
hope: { dice: rollCommand.hope ?? 'd12', value: roll.dice[0].total }, hope: { dice: rollCommand.hope ?? 'd12', value: roll.dice[0].total },
fear: { dice: rollCommand.fear ?? 'd12', value: roll.dice[1].total }, fear: { dice: rollCommand.fear ?? 'd12', value: roll.dice[1].total },
advantage: advantage: advantageState !== null ? { dice: 'd6', value: roll.dice[2].total } : undefined,
rollCommand.advantage && !rollCommand.disadvantage advantageState
? { dice: 'd6', value: roll.dice[2].total }
: undefined,
disadvantage:
rollCommand.disadvantage && !rollCommand.advantage
? { dice: 'd6', value: roll.dice[2].total }
: undefined
}); });
const msgData = { const msgData = {
@ -265,10 +236,7 @@ Hooks.on('chatMessage', (_, message) => {
sound: CONFIG.sounds.dice, sound: CONFIG.sounds.dice,
system: systemData, system: systemData,
user: game.user.id, user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate( content: 'systems/daggerheart/templates/chat/duality-roll.hbs',
'systems/daggerheart/templates/chat/duality-roll.hbs',
systemData
),
rolls: [roll] rolls: [roll]
}; };
@ -302,6 +270,15 @@ const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/components/card-preview.hbs', 'systems/daggerheart/templates/components/card-preview.hbs',
'systems/daggerheart/templates/views/levelup/parts/selectable-card-preview.hbs', 'systems/daggerheart/templates/views/levelup/parts/selectable-card-preview.hbs',
'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs', 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs',
'systems/daggerheart/templates/ui/combat/combatTrackerSection.hbs' 'systems/daggerheart/templates/ui/combat/combatTrackerSection.hbs',
'systems/daggerheart/templates/views/actionTypes/damage.hbs',
'systems/daggerheart/templates/views/actionTypes/healing.hbs',
'systems/daggerheart/templates/views/actionTypes/resource.hbs',
'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/cost.hbs',
'systems/daggerheart/templates/views/actionTypes/range-target.hbs',
'systems/daggerheart/templates/views/actionTypes/effect.hbs'
]); ]);
}; };

View file

@ -146,6 +146,7 @@
} }
}, },
"General": { "General": {
"Name": "Name",
"Hope": "Hope", "Hope": "Hope",
"Fear": "Fear", "Fear": "Fear",
"Duality": "Duality", "Duality": "Duality",
@ -478,6 +479,10 @@
"twoHanded": "Two-Handed" "twoHanded": "Two-Handed"
}, },
"Range": { "Range": {
"self": {
"name": "Self",
"description": "means yourself."
},
"melee": { "melee": {
"name": "Melee", "name": "Melee",
"description": "means a character is within touching distance of the target. PCs can generally touch targets up to a few feet away from them, but melee range may be greater for especially large NPCs." "description": "means a character is within touching distance of the target. PCs can generally touch targets up to a few feet away from them, but melee range may be greater for especially large NPCs."
@ -1284,10 +1289,48 @@
} }
} }
}, },
"Tooltip": { "Tooltip": {
"openItemWorld": "Open Item World", "openItemWorld": "Open Item World",
"delete": "Delete" "delete": "Delete"
},
"Actions": {
"Types": {
"Attack": {
"Name": "Attack"
},
"Spellcast": {
"Name": "Spellcast"
},
"Resource": {
"Name": "Resource"
},
"Damage": {
"Name": "Damage"
},
"Healing": {
"Name": "Healing"
},
"Summon": {
"Name": "Summon"
},
"Effect": {
"Name": "Effect"
},
"Macro": {
"Name": "Macro"
}
}
},
"RollTypes": {
"ability": {
"name": "Ability"
},
"weapon": {
"name": "Weapon"
},
"spellcast": {
"name": "SpellCast"
}
} }
} }
} }

View file

@ -3,6 +3,10 @@ import DHDualityRoll from '../data/chat-message/dualityRoll.mjs';
export default class DhpChatMessage extends foundry.documents.ChatMessage { export default class DhpChatMessage extends foundry.documents.ChatMessage {
async renderHTML() { async renderHTML() {
if (this.type === 'dualityRoll' || this.type === 'adversaryRoll') {
this.content = await foundry.applications.handlebars.renderTemplate(this.content, this.system);
}
/* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */ /* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */
const html = await super.renderHTML(); const html = await super.renderHTML();

View file

@ -1,7 +1,7 @@
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs'; import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
const { ApplicationV2 } = foundry.applications.api; const { ApplicationV2 } = foundry.applications.api;
export default class DaggerheartActionConfig extends DaggerheartSheet(ApplicationV2) { export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
constructor(action) { constructor(action) {
super({}); super({});
@ -9,21 +9,25 @@ export default class DaggerheartActionConfig extends DaggerheartSheet(Applicatio
this.openSection = null; this.openSection = null;
} }
// get title(){
// return `Action - ${this.action.name}`;
// }
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', tag: 'form',
id: 'daggerheart-action', id: 'daggerheart-action',
classes: ['daggerheart', 'views', 'action'], classes: ['daggerheart', 'views', 'action'],
position: { width: 600, height: 'auto' }, position: { width: 600, height: 'auto' },
actions: { actions: {
toggleSection: this.toggleSection toggleSection: this.toggleSection,
addEffect: this.addEffect,
removeEffect: this.removeEffect,
addElement: this.addElement,
removeElement: this.removeElement,
editEffect: this.editEffect,
addDamage: this.addDamage,
removeDamage: this.removeDamage
}, },
form: { form: {
handler: this.updateForm, handler: this.updateForm,
closeOnSubmit: true submitOnChange: true,
closeOnSubmit: false
} }
}; };
@ -36,16 +40,9 @@ export default class DaggerheartActionConfig extends DaggerheartSheet(Applicatio
_getTabs() { _getTabs() {
const tabs = { const tabs = {
effects: { active: true, cssClass: '', group: 'primary', id: 'effects', icon: null, label: 'Effects' }, base: { active: true, cssClass: '', group: 'primary', id: 'base', icon: null, label: 'Base' },
useage: { active: false, cssClass: '', group: 'primary', id: 'useage', icon: null, label: 'Useage' }, config: { active: false, cssClass: '', group: 'primary', id: 'config', icon: null, label: 'Configuration' },
conditions: { effect: { active: false, cssClass: '', group: 'primary', id: 'effect', icon: null, label: 'Effect' }
active: false,
cssClass: '',
group: 'primary',
id: 'conditions',
icon: null,
label: 'Conditions'
}
}; };
for (const v of Object.values(tabs)) { for (const v of Object.values(tabs)) {
@ -58,9 +55,13 @@ export default class DaggerheartActionConfig extends DaggerheartSheet(Applicatio
async _prepareContext(_options) { async _prepareContext(_options) {
const context = await super._prepareContext(_options, 'action'); const context = await super._prepareContext(_options, 'action');
context.source = this.action.toObject(false);
context.openSection = this.openSection; context.openSection = this.openSection;
context.tabs = this._getTabs(); context.tabs = this._getTabs();
context.config = SYSTEM;
if (!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id));
if (this.action.damage?.hasOwnProperty('includeBase')) context.hasBaseDamage = !!this.action.parent.damage;
context.getRealIndex = this.getRealIndex.bind(this);
return context; return context;
} }
@ -69,15 +70,91 @@ export default class DaggerheartActionConfig extends DaggerheartSheet(Applicatio
this.render(true); this.render(true);
} }
static async updateForm(event, _, formData) { getRealIndex(index) {
const data = foundry.utils.expandObject( const data = this.action.toObject(false);
foundry.utils.mergeObject(this.action.toObject(), foundry.utils.expandObject(formData.object)) return data.damage.parts.find(d => d.base) ? index - 1 : index;
);
const newActions = this.action.parent.actions.map(x => x.toObject());
if (!newActions.findSplice(x => x.id === data.id, data)) {
newActions.push(data);
}
await this.action.parent.parent.update({ 'system.actions': newActions });
} }
_prepareSubmitData(event, formData) {
const submitData = foundry.utils.expandObject(formData.object);
// this.element.querySelectorAll("fieldset[disabled] :is(input, select)").forEach(input => {
// foundry.utils.setProperty(submitData, input.name, input.value);
// });
return submitData;
}
static async updateForm(event, _, formData) {
const submitData = this._prepareSubmitData(event, formData),
data = foundry.utils.expandObject(foundry.utils.mergeObject(this.action.toObject(), submitData)),
newActions = this.action.parent.actions.map(x => x.toObject()); // Find better way
if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data);
const updates = await this.action.parent.parent.update({ 'system.actions': newActions });
if (!updates) return;
this.action = updates.system.actions[this.action.index];
this.render();
}
static addElement(event) {
const data = this.action.toObject(),
key = event.target.closest('.action-category-data').dataset.key;
if (!this.action[key]) return;
data[key].push({});
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static removeElement(event) {
const data = this.action.toObject(),
key = event.target.closest('.action-category-data').dataset.key,
index = event.target.dataset.index;
data[key].splice(index, 1);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static addDamage(event) {
if (!this.action.damage.parts) return;
const data = this.action.toObject();
data.damage.parts.push({});
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static removeDamage(event) {
if (!this.action.damage.parts) return;
const data = this.action.toObject(),
index = event.target.dataset.index;
data.damage.parts.splice(index, 1);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
static async addEffect(event) {
if (!this.action.effects) return;
const effectData = this._addEffectData.bind(this)(),
[created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], { render: false }),
data = this.action.toObject();
data.effects.push({ _id: created._id });
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}
/**
* The data for a newly created applied effect.
* @returns {object}
* @protected
*/
_addEffectData() {
return {
name: this.action.item.name,
img: this.action.item.img,
origin: this.action.item.uuid,
transfer: false
};
}
static removeEffect(event) {
if (!this.action.effects) return;
const index = event.target.dataset.index,
effectId = this.action.effects[index]._id;
this.constructor.removeElement.bind(this)(event);
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
}
static editEffect(event) {}
} }

View file

@ -14,7 +14,7 @@ export default class NpcRollSelectionDialog extends HandlebarsApplicationMixin(A
} }
get title() { get title() {
return game.i18n.localize('DAGGERHEART.Application.Settings.Title'); return game.i18n.localize('DAGGERHEART.Application.RollSelection.Title');
} }
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {

View file

@ -16,7 +16,6 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl
hope: ['d12'], hope: ['d12'],
fear: ['d12'], fear: ['d12'],
advantage: null, advantage: null,
disadvantage: null,
hopeResource: hopeResource hopeResource: hopeResource
}; };
} }
@ -30,9 +29,8 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl
height: 'auto' height: 'auto'
}, },
actions: { actions: {
updateIsAdvantage: this.updateIsAdvantage,
selectExperience: this.selectExperience, selectExperience: this.selectExperience,
setAdvantage: this.setAdvantage,
setDisadvantage: this.setDisadvantage,
finish: this.finish finish: this.finish
}, },
form: { form: {
@ -61,7 +59,6 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl
context.hope = this.data.hope; context.hope = this.data.hope;
context.fear = this.data.fear; context.fear = this.data.fear;
context.advantage = this.data.advantage; context.advantage = this.data.advantage;
context.disadvantage = this.data.disadvantage;
context.experiences = Object.keys(this.experiences).map(id => ({ id, ...this.experiences[id] })); context.experiences = Object.keys(this.experiences).map(id => ({ id, ...this.experiences[id] }));
context.hopeResource = this.data.hopeResource + 1; context.hopeResource = this.data.hopeResource + 1;
@ -85,18 +82,10 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl
this.render(); this.render();
} }
static setAdvantage() { static updateIsAdvantage(_, button) {
this.data.advantage = this.data.advantage ? null : 'd6'; const advantage = Boolean(button.dataset.advantage);
this.data.disadvantage = null; this.data.advantage = this.data.advantage === advantage ? null : advantage;
this.render();
this.render(true);
}
static setDisadvantage() {
this.data.advantage = null;
this.data.disadvantage = this.data.disadvantage ? null : 'd6';
this.render(true);
} }
static async finish() { static async finish() {

View file

@ -166,8 +166,6 @@ class DhpRangeSettings extends FormApplication {
} }
export const registerDHSettings = () => { export const registerDHSettings = () => {
// const debouncedReload = foundry.utils.debounce(() => window.location.reload(), 100);
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.General.AbilityArray, { game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.General.AbilityArray, {
name: game.i18n.localize('DAGGERHEART.Settings.General.AbilityArray.Name'), name: game.i18n.localize('DAGGERHEART.Settings.General.AbilityArray.Name'),
hint: game.i18n.localize('DAGGERHEART.Settings.General.AbilityArray.Hint'), hint: game.i18n.localize('DAGGERHEART.Settings.General.AbilityArray.Hint'),
@ -274,7 +272,7 @@ export const registerDHSettings = () => {
name: game.i18n.localize('DAGGERHEART.Settings.DualityRollColor.Name'), name: game.i18n.localize('DAGGERHEART.Settings.DualityRollColor.Name'),
hint: game.i18n.localize('DAGGERHEART.Settings.DualityRollColor.Hint'), hint: game.i18n.localize('DAGGERHEART.Settings.DualityRollColor.Hint'),
scope: 'world', scope: 'world',
config: true, config: false,
type: Number, type: Number,
choices: Object.values(DualityRollColor), choices: Object.values(DualityRollColor),
default: DualityRollColor.colorful.value default: DualityRollColor.colorful.value

View file

@ -54,6 +54,20 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
static async save() { static async save() {
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance, this.settings.toObject()); await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance, this.settings.toObject());
const reload = await foundry.applications.api.DialogV2.confirm({
id: 'reload-world-confirm',
modal: true,
rejectClose: false,
window: { title: 'SETTINGS.ReloadPromptTitle' },
position: { width: 400 },
content: `<p>${game.i18n.localize('SETTINGS.ReloadPromptBody')}</p>`
});
if (reload) {
await game.socket.emit('reload');
foundry.utils.debouncedReload();
}
this.close(); this.close();
} }
} }

View file

@ -61,70 +61,42 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
} }
static async reactionRoll(event) { static async reactionRoll(event) {
const { roll, diceResults, modifiers } = await this.actor.diceRoll( const config = {
{ title: `${this.actor.name} - Reaction Roll`, value: 0 }, event: event,
event.shiftKey title: `${this.actor.name} - Reaction Roll`,
); roll: {
modifier: null,
const cls = getDocumentClass('ChatMessage'); type: 'reaction'
const systemData = { },
roll: roll._formula, chatMessage: {
total: roll._total, type: 'adversaryRoll',
modifiers: modifiers, template: 'systems/daggerheart/templates/chat/adversary-roll.hbs',
diceResults: diceResults mute: true
}
}; };
const msg = new cls({ this.actor.diceRoll(config);
type: 'adversaryRoll',
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/adversary-roll.hbs',
systemData
),
rolls: [roll]
});
cls.create(msg.toObject());
} }
static async attackRoll() { static async attackRoll(event) {
const { modifier, damage, name: attackName } = this.actor.system.attack; const { modifier, damage, name: attackName } = this.actor.system.attack,
const { roll, dice, advantageState, modifiers } = await this.actor.diceRoll( config = {
{ title: `${this.actor.name} - Attack Roll`, value: modifier }, event: event,
event.shiftKey title: attackName,
); roll: {
modifier: modifier,
const targets = Array.from(game.user.targets).map(x => ({ type: 'action'
id: x.id, },
name: x.actor.name, chatMessage: {
img: x.actor.img, type: 'adversaryRoll',
difficulty: x.actor.system.difficulty, template: 'systems/daggerheart/templates/chat/adversary-attack-roll.hbs'
evasion: x.actor.system.evasion },
})); damage: {
value: damage.value,
const cls = getDocumentClass('ChatMessage'); type: damage.type
const systemData = { },
title: attackName, checkTarget: true
origin: this.document.id, };
roll: roll._formula, this.actor.diceRoll(config);
advantageState,
total: roll._total,
modifiers: modifiers,
dice: dice,
targets: targets,
damage: { value: damage.value, type: damage.type }
};
const msg = new cls({
type: 'adversaryRoll',
sound: CONFIG.sounds.dice,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/adversary-attack-roll.hbs',
systemData
),
rolls: [roll]
});
cls.create(msg.toObject());
} }
static async addExperience() { static async addExperience() {

View file

@ -33,6 +33,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
selectAncestry: this.selectAncestry, selectAncestry: this.selectAncestry,
selectCommunity: this.selectCommunity, selectCommunity: this.selectCommunity,
viewObject: this.viewObject, viewObject: this.viewObject,
useItem: this.useItem,
useFeature: this.useFeature, useFeature: this.useFeature,
takeShortRest: this.takeShortRest, takeShortRest: this.takeShortRest,
takeLongRest: this.takeLongRest, takeLongRest: this.takeLongRest,
@ -150,6 +151,10 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
super._attachPartListeners(partId, htmlElement, options); super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelector('.level-value').addEventListener('change', this.onLevelChange.bind(this)); htmlElement.querySelector('.level-value').addEventListener('change', this.onLevelChange.bind(this));
// To Remove when ContextMenu Handler is made
htmlElement
.querySelectorAll('[data-item-id]')
.forEach(element => element.addEventListener('contextmenu', this.editItem.bind(this)));
} }
async _prepareContext(_options) { async _prepareContext(_options) {
@ -280,7 +285,24 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
} }
static async rollAttribute(event, button) { static async rollAttribute(event, button) {
const { roll, hope, fear, advantage, disadvantage, modifiers } = await this.document.dualityRoll( const abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label);
const config = {
event: event,
title: game.i18n.format('DAGGERHEART.Chat.DualityRoll.AbilityCheckTitle', {
ability: abilityLabel
}),
roll: {
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 }, { title: game.i18n.localize(abilities[button.dataset.attribute].label), value: button.dataset.value },
event.shiftKey event.shiftKey
); );
@ -310,7 +332,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
systemContent systemContent
), ),
rolls: [roll] rolls: [roll]
}); }); */
} }
static async toggleMarks(_, button) { static async toggleMarks(_, button) {
@ -348,51 +370,8 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
static async attackRoll(event, button) { static async attackRoll(event, button) {
const weapon = await fromUuid(button.dataset.weapon); const weapon = await fromUuid(button.dataset.weapon);
const damage = { if (!weapon) return;
value: `${this.document.system.proficiency}${weapon.system.damage.value}`, weapon.use(event);
type: weapon.system.damage.type
};
const modifier = this.document.system.traits[weapon.system.trait].value;
const { roll, hope, fear, advantage, disadvantage, modifiers } = await this.document.dualityRoll(
{ title: game.i18n.localize(abilities[weapon.system.trait].label), value: modifier },
event.shiftKey
);
const targets = Array.from(game.user.targets).map(x => ({
id: x.id,
name: x.actor.name,
img: x.actor.img,
difficulty: x.actor.system.difficulty,
evasion: x.actor.system.evasion
}));
const systemData = new DHDualityRoll({
title: weapon.name,
origin: this.document.id,
roll: roll._formula,
modifiers: modifiers,
hope: hope,
fear: fear,
advantage: advantage,
disadvantage: disadvantage,
damage: damage,
targets: targets
});
const cls = getDocumentClass('ChatMessage');
const msg = new cls({
type: 'dualityRoll',
sound: CONFIG.sounds.dice,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/attack-roll.hbs',
systemData
),
rolls: [roll]
});
await cls.create(msg.toObject());
} }
static openLevelUp() { static openLevelUp() {
@ -470,6 +449,12 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
(await game.packs.get('daggerheart.communities'))?.render(true); (await game.packs.get('daggerheart.communities'))?.render(true);
} }
static useItem(event) {
const uuid = event.target.closest('[data-item-id]').dataset.itemId,
item = this.document.items.find(i => i.uuid === uuid);
item.use(event);
}
static async viewObject(_, button) { static async viewObject(_, button) {
const object = await fromUuid(button.dataset.value); const object = await fromUuid(button.dataset.value);
if (!object) return; if (!object) return;
@ -482,6 +467,16 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
object.sheet.render(true); object.sheet.render(true);
} }
editItem(event) {
const uuid = event.target.closest('[data-item-id]').dataset.itemId,
item = this.document.items.find(i => i.uuid === uuid);
if (!item) return;
if (item.sheet.editMode) item.sheet.editMode = false;
item.sheet.render(true);
}
static async takeShortRest() { static async takeShortRest() {
await new DhpDowntime(this.document, true).render(true); await new DhpDowntime(this.document, true).render(true);
await this.minimize(); await this.minimize();

View file

@ -0,0 +1,132 @@
import DhpApplicationMixin from './daggerheart-sheet.mjs';
import DHActionConfig from '../config/Action.mjs';
import { actionsTypes } from '../../data/_module.mjs';
export default function DHItemMixin(Base) {
return class DHItemSheetV2 extends DhpApplicationMixin(Base) {
constructor(options = {}) {
super(options);
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'item', 'dh-style'],
position: { width: 600 },
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
},
actions: {
addAction: this.addAction,
editAction: this.editAction,
removeAction: this.removeAction
}
};
static TABS = {
description: {
active: true,
cssClass: '',
group: 'primary',
id: 'description',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Description'
},
actions: {
active: false,
cssClass: '',
group: 'primary',
id: 'actions',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Actions'
},
settings: {
active: false,
cssClass: '',
group: 'primary',
id: 'settings',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.config = CONFIG.daggerheart;
context.tabs = super._getTabs(this.constructor.TABS);
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
}
static async selectActionType() {
const content = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/views/actionType.hbs',
{ types: SYSTEM.ACTIONS.actionTypes }
),
title = 'Select Action Type',
type = 'form',
data = {};
return Dialog.prompt({
title,
label: title,
content,
type,
callback: html => {
const form = html[0].querySelector('form'),
fd = new foundry.applications.ux.FormDataExtended(form);
foundry.utils.mergeObject(data, fd.object, { inplace: true });
// if (!data.name?.trim()) data.name = game.i18n.localize(SYSTEM.ACTIONS.actionTypes[data.type].name);
return data;
},
rejectClose: false
});
}
static async addAction() {
const actionType = await DHItemSheetV2.selectActionType(),
actionIndexes = this.document.system.actions.map(x => x._id.split('-')[2]).sort((a, b) => a - b);
try {
const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack,
action = new cls(
{
// id: `${this.document.id}-Action-${actionIndexes.length > 0 ? actionIndexes[0] + 1 : 1}`
_id: foundry.utils.randomID(),
type: actionType.type,
name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name),
...cls.getSourceConfig(this.document)
},
{
parent: this.document
}
);
await this.document.update({ 'system.actions': [...this.document.system.actions, action] });
await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render(
true
);
} catch (error) {
console.log(error);
}
}
static async editAction(_, button) {
const action = this.document.system.actions[button.dataset.index];
await new DHActionConfig(action).render(true);
}
static async removeAction(event, button) {
event.stopPropagation();
await this.document.update({
'system.actions': this.document.system.actions.filter(
(_, index) => index !== Number.parseInt(button.dataset.index)
)
});
}
};
}

View file

@ -1,16 +1,9 @@
import DaggerheartSheet from '../daggerheart-sheet.mjs'; import DHItemSheetV2 from '../item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; const { ItemSheetV2 } = foundry.applications.sheets;
export default class ArmorSheet extends DaggerheartSheet(ItemSheetV2) { export default class ArmorSheet extends DHItemSheetV2(ItemSheetV2) {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['armor'],
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'armor'],
position: { width: 600 },
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
},
dragDrop: [{ dragSelector: null, dropSelector: null }] dragDrop: [{ dragSelector: null, dropSelector: null }]
}; };
@ -18,42 +11,13 @@ export default class ArmorSheet extends DaggerheartSheet(ItemSheetV2) {
header: { template: 'systems/daggerheart/templates/sheets/items/armor/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/armor/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' }, description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
actions: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
scrollable: ['.actions']
},
settings: { settings: {
template: 'systems/daggerheart/templates/sheets/items/armor/settings.hbs', template: 'systems/daggerheart/templates/sheets/items/armor/settings.hbs',
scrollable: ['.settings'] scrollable: ['.settings']
} }
}; };
static TABS = {
description: {
active: true,
cssClass: '',
group: 'primary',
id: 'description',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Description'
},
settings: {
active: false,
cssClass: '',
group: 'primary',
id: 'settings',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.config = CONFIG.daggerheart;
context.tabs = super._getTabs(this.constructor.TABS);
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
}
} }

View file

@ -1,57 +1,23 @@
import DaggerheartSheet from '../daggerheart-sheet.mjs'; import DHItemSheetV2 from '../item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; const { ItemSheetV2 } = foundry.applications.sheets;
export default class ConsumableSheet extends DaggerheartSheet(ItemSheetV2) { export default class ConsumableSheet extends DHItemSheetV2(ItemSheetV2) {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['consumable'],
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'consumable'], position: { width: 550 }
position: { width: 550 },
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
}; };
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/consumable/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/consumable/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' }, description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
actions: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
scrollable: ['.actions']
},
settings: { settings: {
template: 'systems/daggerheart/templates/sheets/items/consumable/settings.hbs', template: 'systems/daggerheart/templates/sheets/items/consumable/settings.hbs',
scrollable: ['.settings'] scrollable: ['.settings']
} }
}; };
static TABS = {
description: {
active: true,
cssClass: '',
group: 'primary',
id: 'description',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Description'
},
settings: {
active: false,
cssClass: '',
group: 'primary',
id: 'settings',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.tabs = super._getTabs(this.constructor.TABS);
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
}
} }

View file

@ -1,23 +1,10 @@
import DaggerheartAction from '../../../data/action.mjs'; import DHItemSheetV2 from '../item.mjs';
import DaggerheartActionConfig from '../../config/Action.mjs';
import DaggerheartSheet from '../daggerheart-sheet.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; const { ItemSheetV2 } = foundry.applications.sheets;
export default class DomainCardSheet extends DaggerheartSheet(ItemSheetV2) { export default class DomainCardSheet extends DHItemSheetV2(ItemSheetV2) {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['domain-card'],
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'domain-card'], position: { width: 450, height: 700 }
position: { width: 450, height: 700 },
actions: {
addAction: this.addAction,
editAction: this.editAction,
removeAction: this.removeAction
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
}; };
static PARTS = { static PARTS = {
@ -33,74 +20,4 @@ export default class DomainCardSheet extends DaggerheartSheet(ItemSheetV2) {
scrollable: ['.settings'] scrollable: ['.settings']
} }
}; };
static TABS = {
description: {
active: true,
cssClass: '',
group: 'primary',
id: 'description',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Description'
},
actions: {
active: false,
cssClass: '',
group: 'primary',
id: 'actions',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Actions'
},
settings: {
active: false,
cssClass: '',
group: 'primary',
id: 'settings',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.config = CONFIG.daggerheart;
context.tabs = super._getTabs(this.constructor.TABS);
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
}
static async addAction() {
const actionIndexes = this.document.system.actions.map(x => x.id.split('-')[2]).sort((a, b) => a - b);
const action = await new DaggerheartAction(
{
id: `${this.document.id}-Action-${actionIndexes.length > 0 ? actionIndexes[0] + 1 : 1}`
},
{
parent: this.document
}
);
await this.document.update({ 'system.actions': [...this.document.system.actions, action] });
await new DaggerheartActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render(
true
);
}
static async editAction(_, button) {
const action = this.document.system.actions[button.dataset.index];
await new DaggerheartActionConfig(action).render(true);
}
static async removeAction(event, button) {
event.stopPropagation();
await this.document.update({
'system.actions': this.document.system.actions.filter(
(_, index) => index !== Number.parseInt(button.dataset.index)
)
});
}
} }

View file

@ -1,9 +1,7 @@
import DaggerheartAction from '../../../data/action.mjs'; import DHItemSheetV2 from '../item.mjs';
import DaggerheartActionConfig from '../../config/Action.mjs';
import DaggerheartSheet from '../daggerheart-sheet.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; const { ItemSheetV2 } = foundry.applications.sheets;
export default class FeatureSheet extends DaggerheartSheet(ItemSheetV2) { export default class FeatureSheet extends DHItemSheetV2(ItemSheetV2) {
constructor(options = {}) { constructor(options = {}) {
super(options); super(options);
@ -11,22 +9,13 @@ export default class FeatureSheet extends DaggerheartSheet(ItemSheetV2) {
} }
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form',
id: 'daggerheart-feature', id: 'daggerheart-feature',
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'feature'], classes: ['feature'],
position: { width: 600, height: 600 }, position: { width: 600, height: 600 },
window: { resizable: true }, window: { resizable: true },
actions: { actions: {
addEffect: this.addEffect, addEffect: this.addEffect,
removeEffect: this.removeEffect, removeEffect: this.removeEffect
addAction: this.addAction,
editAction: this.editAction,
removeAction: this.removeAction
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
} }
}; };
@ -49,30 +38,7 @@ export default class FeatureSheet extends DaggerheartSheet(ItemSheetV2) {
}; };
static TABS = { static TABS = {
description: { ...super.TABS,
active: true,
cssClass: '',
group: 'primary',
id: 'description',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Description'
},
actions: {
active: false,
cssClass: '',
group: 'primary',
id: 'actions',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Actions'
},
settings: {
active: false,
cssClass: '',
group: 'primary',
id: 'settings',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings'
},
effects: { effects: {
active: false, active: false,
cssClass: '', cssClass: '',
@ -102,11 +68,6 @@ export default class FeatureSheet extends DaggerheartSheet(ItemSheetV2) {
return context; return context;
} }
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
}
effectSelect(event) { effectSelect(event) {
this.selectedEffectType = event.currentTarget.value; this.selectedEffectType = event.currentTarget.value;
this.render(true); this.render(true);
@ -130,26 +91,4 @@ export default class FeatureSheet extends DaggerheartSheet(ItemSheetV2) {
const path = `system.effects.-=${button.dataset.effect}`; const path = `system.effects.-=${button.dataset.effect}`;
await this.item.update({ [path]: null }); await this.item.update({ [path]: null });
} }
static async addAction() {
const action = await new DaggerheartAction({ img: this.document.img }, { parent: this.document });
await this.document.update({ 'system.actions': [...this.document.system.actions, action] });
await new DaggerheartActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render(
true
);
}
static async editAction(_, button) {
const action = this.document.system.actions[button.dataset.index];
await new DaggerheartActionConfig(action).render(true);
}
static async removeAction(event, button) {
event.stopPropagation();
await this.document.update({
'system.actions': this.document.system.actions.filter(
(_, index) => index !== Number.parseInt(button.dataset.index)
)
});
}
} }

View file

@ -1,57 +1,23 @@
import DaggerheartSheet from '../daggerheart-sheet.mjs'; import DHItemSheetV2 from '../item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; const { ItemSheetV2 } = foundry.applications.sheets;
export default class MiscellaneousSheet extends DaggerheartSheet(ItemSheetV2) { export default class MiscellaneousSheet extends DHItemSheetV2(ItemSheetV2) {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['miscellaneous'],
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'miscellaneous'], position: { width: 550 }
position: { width: 550 },
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
}; };
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/miscellaneous/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/miscellaneous/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' }, description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
actions: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
scrollable: ['.actions']
},
settings: { settings: {
template: 'systems/daggerheart/templates/sheets/items/miscellaneous/settings.hbs', template: 'systems/daggerheart/templates/sheets/items/miscellaneous/settings.hbs',
scrollable: ['.settings'] scrollable: ['.settings']
} }
}; };
static TABS = {
description: {
active: true,
cssClass: '',
group: 'primary',
id: 'description',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Description'
},
settings: {
active: false,
cssClass: '',
group: 'primary',
id: 'settings',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.tabs = super._getTabs(this.constructor.TABS);
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
}
} }

View file

@ -1,58 +1,22 @@
import DaggerheartSheet from '../daggerheart-sheet.mjs'; import DHItemSheetV2 from '../item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; const { ItemSheetV2 } = foundry.applications.sheets;
export default class WeaponSheet extends DaggerheartSheet(ItemSheetV2) { export default class WeaponSheet extends DHItemSheetV2(ItemSheetV2) {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['weapon']
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'weapon'],
position: { width: 600 },
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
}; };
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/weapon/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/weapon/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' }, description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
actions: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
scrollable: ['.actions']
},
settings: { settings: {
template: 'systems/daggerheart/templates/sheets/items/weapon/settings.hbs', template: 'systems/daggerheart/templates/sheets/items/weapon/settings.hbs',
scrollable: ['.settings'] scrollable: ['.settings']
} }
}; };
static TABS = {
description: {
active: true,
cssClass: '',
group: 'primary',
id: 'description',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Description'
},
settings: {
active: false,
cssClass: '',
group: 'primary',
id: 'settings',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.config = CONFIG.daggerheart;
context.tabs = super._getTabs(this.constructor.TABS);
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
}
} }

View file

@ -1,7 +1,43 @@
export const actionTypes = { export const actionTypes = {
attack: {
id: 'attack',
name: 'DAGGERHEART.Actions.Types.Attack.Name',
icon: 'fa-swords'
},
spellcast: {
id: 'spellcast',
name: 'DAGGERHEART.Actions.Types.Spellcast.Name',
icon: 'fa-book-sparkles'
},
healing: {
id: 'healing',
name: 'DAGGERHEART.Actions.Types.Healing.Name',
icon: 'fa-kit-medical'
},
resource: {
id: 'resource',
name: 'DAGGERHEART.Actions.Types.Resource.Name',
icon: 'fa-honey-pot'
},
damage: { damage: {
id: 'damage', id: 'damage',
name: 'DAGGERHEART.Effects.Types.Health.Name' name: 'DAGGERHEART.Actions.Types.Damage.Name',
icon: 'fa-bone-break'
},
summon: {
id: 'summon',
name: 'DAGGERHEART.Actions.Types.Summon.Name',
icon: 'fa-ghost'
},
effect: {
id: 'effect',
name: 'DAGGERHEART.Actions.Types.Effect.Name',
icon: 'fa-person-rays'
},
macro: {
id: 'macro',
name: 'DAGGERHEART.Actions.Types.Macro.Name',
icon: 'fa-scroll'
} }
}; };

View file

@ -1,4 +1,9 @@
export const range = { export const range = {
self: {
label: 'DAGGERHEART.Range.self.name',
description: 'DAGGERHEART.Range.self.description',
distance: 0
},
melee: { melee: {
id: 'melee', id: 'melee',
label: 'DAGGERHEART.Range.melee.name', label: 'DAGGERHEART.Range.melee.name',
@ -248,6 +253,11 @@ export const diceTypes = {
d20: 'd20' d20: 'd20'
}; };
export const multiplierTypes = {
proficiency: 'Proficiency',
spellcast: 'Spellcast'
};
export const getDiceSoNicePresets = () => { export const getDiceSoNicePresets = () => {
const { diceSoNice } = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance); const { diceSoNice } = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance);
@ -312,3 +322,18 @@ export const abilityCosts = {
label: 'Stress' label: 'Stress'
} }
}; };
export const rollTypes = {
weapon: {
id: 'weapon',
label: 'DAGGERHEART.RollTypes.weapon.name'
},
spellcast: {
id: 'spellcast',
label: 'DAGGERHEART.RollTypes.spellcast.name'
},
ability: {
id: 'ability',
label: 'DAGGERHEART.RollTypes.ability.name'
}
};

View file

@ -5,6 +5,7 @@ export { default as DhCombatant } from './combatant.mjs';
export * as actors from './actor/_module.mjs'; export * as actors from './actor/_module.mjs';
export * as items from './item/_module.mjs'; export * as items from './item/_module.mjs';
export { actionsTypes } from './action/_module.mjs';
export * as messages from './chat-message/_modules.mjs'; export * as messages from './chat-message/_modules.mjs';
export * as fields from './fields/_module.mjs'; export * as fields from './fields/_module.mjs';
export * as pseudoDocuments from './pseudo-documents/_module.mjs'; export * as pseudoDocuments from './pseudo-documents/_module.mjs';

View file

@ -1,34 +0,0 @@
export default class DaggerheartAction extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
id: new fields.DocumentIdField(),
name: new fields.StringField({ initial: 'New Action' }),
damage: new fields.SchemaField({
type: new fields.StringField({ choices: SYSTEM.GENERAL.damageTypes, nullable: true, initial: null }),
value: new fields.StringField({})
}),
healing: new fields.SchemaField({
type: new fields.StringField({ choices: SYSTEM.GENERAL.healingTypes, nullable: true, initial: null }),
value: new fields.StringField()
}),
conditions: new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField(),
icon: new fields.StringField(),
description: new fields.StringField()
})
),
cost: new fields.SchemaField({
type: new fields.StringField({ choices: SYSTEM.GENERAL.abilityCosts, nullable: true, initial: null }),
value: new fields.NumberField({ nullable: true, initial: null })
}),
target: new fields.SchemaField({
type: new fields.StringField({
choices: SYSTEM.ACTIONS.targetTypes,
initial: SYSTEM.ACTIONS.targetTypes.other.id
})
})
};
}
}

View file

@ -0,0 +1,23 @@
import {
DHAttackAction,
DHBaseAction,
DHDamageAction,
DHEffectAction,
DHHealingAction,
DHMacroAction,
DHResourceAction,
DHSpellCastAction,
DHSummonAction
} from './action.mjs';
export const actionsTypes = {
base: DHBaseAction,
attack: DHAttackAction,
spellcast: DHSpellCastAction,
resource: DHResourceAction,
damage: DHDamageAction,
healing: DHHealingAction,
summon: DHSummonAction,
effect: DHEffectAction,
macro: DHMacroAction
};

View file

@ -0,0 +1,386 @@
import { abilities } from '../../config/actorConfig.mjs';
import { DHActionDiceData, DHDamageData, DHDamageField } from './actionDice.mjs';
export default class DHAction extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
id: new fields.DocumentIdField(),
name: new fields.StringField({ initial: 'New Action' }),
damage: new fields.SchemaField({
type: new fields.StringField({ choices: SYSTEM.GENERAL.damageTypes, nullable: true, initial: null }),
value: new fields.StringField({})
}),
healing: new fields.SchemaField({
type: new fields.StringField({ choices: SYSTEM.GENERAL.healingTypes, nullable: true, initial: null }),
value: new fields.StringField()
}),
conditions: new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField(),
icon: new fields.StringField(),
description: new fields.StringField()
})
),
cost: new fields.SchemaField({
type: new fields.StringField({ choices: SYSTEM.GENERAL.abilityCosts, nullable: true, initial: null }),
value: new fields.NumberField({ nullable: true, initial: null })
}),
target: new fields.SchemaField({
type: new fields.StringField({
choices: SYSTEM.ACTIONS.targetTypes,
initial: SYSTEM.ACTIONS.targetTypes.other.id
})
})
};
}
}
const fields = foundry.data.fields;
/*
ToDo
- Apply ActiveEffect => Add to Chat message like Damage Button ?
- Add Drag & Drop for documentUUID field (Macro & Summon)
- Add optionnal Role for Healing ?
- Handle Roll result as part of formula if needed
- Target Check
- Cost Check
- Range Check
- Area of effect and measurement placement
- Auto use costs and action
*/
export class DHBaseAction extends foundry.abstract.DataModel {
static defineSchema() {
return {
_id: new fields.DocumentIdField(),
type: new fields.StringField({ initial: undefined, readonly: true, required: true }),
name: new fields.StringField({ initial: undefined }),
img: new fields.FilePathField({ initial: undefined, categories: ['IMAGE'], base64: false }),
actionType: new fields.StringField({ choices: SYSTEM.ITEM.actionTypes, initial: 'action', nullable: true }),
cost: new fields.ArrayField(
new fields.SchemaField({
type: new fields.StringField({
choices: SYSTEM.GENERAL.abilityCosts,
nullable: false,
required: true,
initial: 'hope'
}),
value: new fields.NumberField({ nullable: true, initial: 1 }),
scalable: new fields.BooleanField({ initial: false }),
step: new fields.NumberField({ nullable: true, initial: null })
})
),
uses: new fields.SchemaField({
value: new fields.NumberField({ nullable: true, initial: null }),
max: new fields.NumberField({ nullable: true, initial: null }),
recovery: new fields.StringField({
choices: SYSTEM.GENERAL.refreshTypes,
initial: null,
nullable: true
})
}),
range: new fields.StringField({
choices: SYSTEM.GENERAL.range,
required: true,
blank: false,
initial: 'self'
})
};
}
prepareData() {}
get index() {
return this.parent.actions.indexOf(this);
}
get item() {
return this.parent.parent;
}
get actor() {
return this.item?.actor;
}
get chatTemplate() {
return 'systems/daggerheart/templates/chat/attack-roll.hbs';
}
static getRollType() {
return 'ability';
}
static getSourceConfig(parent) {
const updateSource = {};
updateSource.img ??= parent?.img ?? parent?.system?.img;
if (parent?.system?.trait) {
updateSource['roll'] = {
type: this.getRollType(),
trait: parent.system.trait
};
}
if (parent?.system?.range) {
updateSource['range'] = parent?.system?.range;
}
return updateSource;
}
async use(event) {
if (this.roll.type && this.roll.trait) {
const modifierValue = this.actor.system.traits[this.roll.trait].value;
const config = {
event: event,
title: this.item.name,
roll: {
modifier: modifierValue,
label: game.i18n.localize(abilities[this.roll.trait].label),
type: this.actionType,
difficulty: this.roll?.difficulty
},
chatMessage: {
template: this.chatTemplate
}
};
if (this.target?.type) config.checkTarget = true;
if (this.damage.parts.length) {
config.damage = {
value: this.damage.parts.map(p => p.getFormula(this.actor)).join(' + '),
type: this.damage.parts[0].type
};
}
if (this.effects.length) {
// Apply Active Effects. In Chat Message ?
}
return this.actor.diceRoll(config);
}
}
}
const extraDefineSchema = (field, option) => {
return {
[field]: {
// damage: new fields.SchemaField({
// parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData))
// }),
damage: new DHDamageField(option),
roll: new fields.SchemaField({
type: new fields.StringField({ nullable: true, initial: null, choices: SYSTEM.GENERAL.rollTypes }),
trait: new fields.StringField({ nullable: true, initial: null, choices: SYSTEM.ACTOR.abilities }),
difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 })
}),
target: new fields.SchemaField({
type: new fields.StringField({
choices: SYSTEM.ACTIONS.targetTypes,
initial: SYSTEM.ACTIONS.targetTypes.other.id
})
}),
effects: new fields.ArrayField( // ActiveEffect
new fields.SchemaField({
_id: new fields.DocumentIdField()
})
)
}[field]
};
};
export class DHAttackAction extends DHBaseAction {
static defineSchema() {
return {
...super.defineSchema(),
...extraDefineSchema('damage', true),
...extraDefineSchema('roll'),
...extraDefineSchema('target'),
...extraDefineSchema('effects')
};
}
static getRollType() {
return 'weapon';
}
prepareData() {
super.prepareData();
if (this.damage.includeBase && !!this.item?.system?.damage) {
const baseDamage = this.getParentDamage();
this.damage.parts.unshift(new DHDamageData(baseDamage));
}
}
getParentDamage() {
return {
multiplier: 'proficiency',
dice: this.item?.system?.damage.value,
bonus: this.item?.system?.damage.bonus ?? 0,
type: this.item?.system?.damage.type,
base: true
};
}
// Temporary until full formula parser
// getDamageFormula() {
// return this.damage.parts.map(p => p.formula).join(' + ');
// }
}
export class DHSpellCastAction extends DHBaseAction {
static defineSchema() {
return {
...super.defineSchema(),
...extraDefineSchema('damage'),
...extraDefineSchema('roll'),
...extraDefineSchema('target'),
...extraDefineSchema('effects')
};
}
static getRollType() {
return 'spellcast';
}
}
export class DHDamageAction extends DHBaseAction {
static defineSchema() {
return {
...super.defineSchema(),
...extraDefineSchema('damage', false),
...extraDefineSchema('target'),
...extraDefineSchema('effects')
};
}
async use(event) {
const formula = this.damage.parts.map(p => p.getFormula(this.actor)).join(' + ');
if (!formula || formula == '') return;
let roll = { formula: formula, total: formula };
if (isNaN(formula)) {
roll = await new Roll(formula).evaluate();
}
const cls = getDocumentClass('ChatMessage');
const msg = new cls({
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/damage-roll.hbs',
{
roll: roll.formula,
total: roll.total,
type: this.damage.parts.map(p => p.type)
}
)
});
cls.create(msg.toObject());
}
}
export class DHHealingAction extends DHBaseAction {
static defineSchema() {
return {
...super.defineSchema(),
healing: new fields.SchemaField({
type: new fields.StringField({
choices: SYSTEM.GENERAL.healingTypes,
required: true,
blank: false,
initial: SYSTEM.GENERAL.healingTypes.health.id,
label: 'Healing'
}),
value: new fields.EmbeddedDataField(DHActionDiceData)
}),
...extraDefineSchema('target'),
...extraDefineSchema('effects')
};
}
async use(event) {
const formula = this.healing.value.getFormula(this.actor);
if (!formula || formula == '') return;
// const roll = await super.use(event);
let roll = { formula: formula, total: formula };
if (isNaN(formula)) {
roll = await new Roll(formula).evaluate();
}
const cls = getDocumentClass('ChatMessage');
const msg = new cls({
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/healing-roll.hbs',
{
roll: roll.formula,
total: roll.total,
type: this.healing.type
}
)
});
cls.create(msg.toObject());
}
get chatTemplate() {
return 'systems/daggerheart/templates/chat/healing-roll.hbs';
}
}
export class DHResourceAction extends DHBaseAction {
static defineSchema() {
return {
...super.defineSchema(),
// ...extraDefineSchema('roll'),
...extraDefineSchema('target'),
...extraDefineSchema('effects'),
resource: new fields.SchemaField({
type: new fields.StringField({
choices: [],
blank: true,
required: false,
initial: '',
label: 'Resource'
}),
value: new fields.NumberField({ initial: 0, label: 'Value' })
})
};
}
}
export class DHSummonAction extends DHBaseAction {
static defineSchema() {
return {
...super.defineSchema(),
documentUUID: new fields.StringField({ blank: true, initial: '', placeholder: 'Enter a Creature UUID' })
};
}
}
export class DHEffectAction extends DHBaseAction {
static defineSchema() {
return {
...super.defineSchema(),
...extraDefineSchema('effects')
};
}
}
export class DHMacroAction extends DHBaseAction {
static defineSchema() {
return {
...super.defineSchema(),
documentUUID: new fields.StringField({ blank: true, initial: '', placeholder: 'Enter a macro UUID' })
};
}
async use(event) {
const fixUUID = !this.documentUUID.includes('Macro.') ? `Macro.${this.documentUUID}` : this.documentUUID,
macro = await fromUuid(fixUUID);
try {
if (!macro) throw new Error(`No macro found for the UUID: ${this.documentUUID}.`);
macro.execute();
} catch (error) {
ui.notifications.error(error);
}
}
}

View file

@ -0,0 +1,55 @@
import FormulaField from '../fields/formulaField.mjs';
const fields = foundry.data.fields;
export class DHActionDiceData extends foundry.abstract.DataModel {
/** @override */
static defineSchema() {
return {
multiplier: new fields.StringField({
choices: SYSTEM.GENERAL.multiplierTypes,
initial: 'proficiency',
label: 'Multiplier'
}),
dice: new fields.StringField({ choices: SYSTEM.GENERAL.diceTypes, initial: 'd6', label: 'Formula' }),
bonus: new fields.NumberField({ nullable: true, initial: null, label: 'Bonus' }),
custom: new fields.SchemaField({
enabled: new fields.BooleanField({ label: 'Custom Formula' }),
formula: new FormulaField({ label: 'Formula' })
})
};
}
getFormula(actor) {
return this.custom.enabled
? this.custom.formula
: `${actor.system[this.multiplier] ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`;
}
}
export class DHDamageField extends fields.SchemaField {
constructor(hasBase, options, context = {}) {
const damageFields = {
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData))
};
if (hasBase) damageFields.includeBase = new fields.BooleanField({ initial: true });
super(damageFields, options, context);
}
}
export class DHDamageData extends DHActionDiceData {
/** @override */
static defineSchema() {
return {
...super.defineSchema(),
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
type: new fields.StringField({
choices: SYSTEM.GENERAL.damageTypes,
initial: 'physical',
label: 'Type',
nullable: false,
required: true
})
};
}
}

View file

@ -5,17 +5,19 @@ export default class DHAdversaryRoll extends foundry.abstract.TypeDataModel {
return { return {
title: new fields.StringField(), title: new fields.StringField(),
origin: new fields.StringField({ required: true }), origin: new fields.StringField({ required: true }),
roll: new fields.StringField({}), dice: new fields.DataField(),
total: new fields.NumberField({ integer: true }), roll: new fields.DataField(),
modifiers: new fields.ArrayField( modifiers: new fields.ArrayField(
new fields.SchemaField({ new fields.SchemaField({
value: new fields.NumberField({ integer: true }), value: new fields.NumberField({ integer: true }),
label: new fields.StringField({}), label: new fields.StringField({})
title: new fields.StringField({})
}) })
), ),
advantageState: new fields.NumberField({ required: true, choices: [0, 1, 2], initial: 0 }), advantageState: new fields.BooleanField({ nullable: true, initial: null }),
dice: new fields.EmbeddedDataField(DhpAdversaryRollDice), advantage: new fields.SchemaField({
dice: new fields.StringField({}),
value: new fields.NumberField({ integer: true })
}),
targets: new fields.ArrayField( targets: new fields.ArrayField(
new fields.SchemaField({ new fields.SchemaField({
id: new fields.StringField({}), id: new fields.StringField({}),
@ -37,42 +39,8 @@ export default class DHAdversaryRoll extends foundry.abstract.TypeDataModel {
} }
prepareDerivedData() { prepareDerivedData() {
const diceKeys = Object.keys(this.dice.rolls);
const highestDiceIndex =
diceKeys.length < 2
? null
: this.dice.rolls[diceKeys[0]].value > this.dice.rolls[diceKeys[1]].value
? 0
: 1;
if (highestDiceIndex !== null) {
this.dice.rolls = this.dice.rolls.map((roll, index) => ({
...roll,
discarded: this.advantageState === 1 ? index !== highestDiceIndex : index === highestDiceIndex
}));
}
this.targets.forEach(target => { this.targets.forEach(target => {
target.hit = target.difficulty ? this.total >= target.difficulty : this.total >= target.evasion; target.hit = target.difficulty ? this.total >= target.difficulty : this.total >= target.evasion;
}); });
} }
} }
class DhpAdversaryRollDice extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
type: new fields.StringField({ required: true }),
rolls: new fields.ArrayField(
new fields.SchemaField({
value: new fields.NumberField({ required: true, integer: true }),
discarded: new fields.BooleanField({ initial: false })
})
)
};
}
get rollTotal() {
return this.rolls.reduce((acc, roll) => acc + (!roll.discarded ? roll.value : 0), 0);
}
}

View file

@ -9,7 +9,13 @@ export default class DHDamageRoll extends foundry.abstract.TypeDataModel {
total: new fields.NumberField({ required: true, integer: true }), total: new fields.NumberField({ required: true, integer: true }),
type: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.damageTypes), integer: false }) type: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.damageTypes), integer: false })
}), }),
dice: new fields.ArrayField(new fields.EmbeddedDataField(DhpDamageDice)), 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( modifiers: new fields.ArrayField(
new fields.SchemaField({ new fields.SchemaField({
value: new fields.NumberField({ required: true, integer: true }), value: new fields.NumberField({ required: true, integer: true }),
@ -26,18 +32,3 @@ export default class DHDamageRoll extends foundry.abstract.TypeDataModel {
}; };
} }
} }
class DhpDamageDice extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
type: new fields.StringField({ required: true }),
rolls: new fields.ArrayField(new fields.NumberField({ required: true, integer: true }))
};
}
get rollTotal() {
return this.rolls.reduce((acc, roll) => acc + roll, 0);
}
}

View file

@ -1,4 +1,4 @@
import { DualityRollColor } from "../settings/Appearance.mjs"; import { DualityRollColor } from '../settings/Appearance.mjs';
const fields = foundry.data.fields; const fields = foundry.data.fields;
const diceField = () => const diceField = () =>
@ -18,18 +18,17 @@ export default class DHDualityRoll extends foundry.abstract.TypeDataModel {
return { return {
title: new fields.StringField(), title: new fields.StringField(),
origin: new fields.StringField({ required: true }), origin: new fields.StringField({ required: true }),
roll: new fields.StringField({}), roll: new fields.DataField({}),
modifiers: new fields.ArrayField( modifiers: new fields.ArrayField(
new fields.SchemaField({ new fields.SchemaField({
value: new fields.NumberField({ integer: true }), value: new fields.NumberField({ integer: true }),
label: new fields.StringField({}), label: new fields.StringField({})
title: new fields.StringField({})
}) })
), ),
hope: diceField(), hope: diceField(),
fear: diceField(), fear: diceField(),
advantageState: new fields.BooleanField({ nullable: true, initial: null }),
advantage: diceField(), advantage: diceField(),
disadvantage: diceField(),
targets: new fields.ArrayField( targets: new fields.ArrayField(
new fields.SchemaField({ new fields.SchemaField({
id: new fields.StringField({}), id: new fields.StringField({}),
@ -64,15 +63,6 @@ export default class DHDualityRoll extends foundry.abstract.TypeDataModel {
}; };
} }
get total() {
const advantage = this.advantage.value
? this.advantage.value
: this.disadvantage.value
? -this.disadvantage.value
: 0;
return this.diceTotal + advantage + this.modifierTotal.value;
}
get diceTotal() { get diceTotal() {
return this.hope.value + this.fear.value; return this.hope.value + this.fear.value;
} }
@ -112,13 +102,7 @@ export default class DHDualityRoll extends foundry.abstract.TypeDataModel {
} }
prepareDerivedData() { prepareDerivedData() {
const total = this.total;
this.hope.discarded = this.hope.value < this.fear.value; this.hope.discarded = this.hope.value < this.fear.value;
this.fear.discarded = this.fear.value < this.hope.value; this.fear.discarded = this.fear.value < this.hope.value;
this.targets.forEach(target => {
target.hit = target.difficulty ? total >= target.difficulty : total >= target.evasion;
});
} }
} }

View file

@ -0,0 +1,40 @@
import { actionsTypes } from '../action/_module.mjs';
// Temporary Solution
export default class ActionField extends foundry.data.fields.ObjectField {
getModel(value) {
return actionsTypes[value.type] ?? actionsTypes.attack;
}
/* -------------------------------------------- */
/** @override */
_cleanType(value, options) {
if (!(typeof value === 'object')) value = {};
const cls = this.getModel(value);
if (cls) return cls.cleanData(value, options);
return value;
}
/* -------------------------------------------- */
/** @override */
initialize(value, model, options = {}) {
const cls = this.getModel(value);
if (cls) return new cls(value, { parent: model, ...options });
return foundry.utils.deepClone(value);
}
/* -------------------------------------------- */
/**
* Migrate this field's candidate source data.
* @param {object} sourceData Candidate source data of the root model.
* @param {any} fieldData The value of this field within the source data.
*/
migrateSource(sourceData, fieldData) {
const cls = this.getModel(fieldData);
if (cls) cls.migrateDataSafe(fieldData);
}
}

View file

@ -10,27 +10,27 @@ import DHSubclass from './subclass.mjs';
import DHWeapon from './weapon.mjs'; import DHWeapon from './weapon.mjs';
export { export {
DHAncestry, DHAncestry,
DHArmor, DHArmor,
DHClass, DHClass,
DHCommunity, DHCommunity,
DHConsumable, DHConsumable,
DHDomainCard, DHDomainCard,
DHFeature, DHFeature,
DHMiscellaneous, DHMiscellaneous,
DHSubclass, DHSubclass,
DHWeapon, DHWeapon
} };
export const config = { export const config = {
ancestry: DHAncestry, ancestry: DHAncestry,
armor: DHArmor, armor: DHArmor,
class: DHClass, class: DHClass,
community: DHCommunity, community: DHCommunity,
consumable: DHConsumable, consumable: DHConsumable,
domainCard: DHDomainCard, domainCard: DHDomainCard,
feature: DHFeature, feature: DHFeature,
miscellaneous: DHMiscellaneous, miscellaneous: DHMiscellaneous,
subclass: DHSubclass, subclass: DHSubclass,
weapon: DHWeapon, weapon: DHWeapon
}; };

View file

@ -1,4 +1,4 @@
import DaggerheartAction from '../action.mjs'; import DHAction from '../action/action.mjs';
import BaseDataItem from './base.mjs'; import BaseDataItem from './base.mjs';
export default class DHDomainCard extends BaseDataItem { export default class DHDomainCard extends BaseDataItem {
@ -22,7 +22,7 @@ export default class DHDomainCard extends BaseDataItem {
type: new fields.StringField({ choices: SYSTEM.DOMAIN.cardTypes, required: true, blank: true }), type: new fields.StringField({ choices: SYSTEM.DOMAIN.cardTypes, required: true, blank: true }),
foundation: new fields.BooleanField({ initial: false }), foundation: new fields.BooleanField({ initial: false }),
inVault: new fields.BooleanField({ initial: false }), inVault: new fields.BooleanField({ initial: false }),
actions: new fields.ArrayField(new fields.EmbeddedDataField(DaggerheartAction)) actions: new fields.ArrayField(new fields.EmbeddedDataField(DHAction))
}; };
} }

View file

@ -1,14 +1,14 @@
import { getTier } from '../../helpers/utils.mjs'; import { getTier } from '../../helpers/utils.mjs';
import DaggerheartAction from '../action.mjs'; import DHAction from '../action/action.mjs';
import BaseDataItem from './base.mjs'; import BaseDataItem from './base.mjs';
export default class DHFeature extends BaseDataItem { export default class DHFeature extends BaseDataItem {
/** @inheritDoc */ /** @inheritDoc */
static get metadata() { static get metadata() {
return foundry.utils.mergeObject(super.metadata, { return foundry.utils.mergeObject(super.metadata, {
label: "TYPES.Item.feature", label: 'TYPES.Item.feature',
type: "feature", type: 'feature',
hasDescription: true, hasDescription: true
}); });
} }
@ -17,7 +17,7 @@ export default class DHFeature extends BaseDataItem {
const fields = foundry.data.fields; const fields = foundry.data.fields;
return { return {
...super.defineSchema(), ...super.defineSchema(),
//A type of feature seems unnecessary //A type of feature seems unnecessary
type: new fields.StringField({ choices: SYSTEM.ITEM.featureTypes }), type: new fields.StringField({ choices: SYSTEM.ITEM.featureTypes }),
@ -26,7 +26,7 @@ export default class DHFeature extends BaseDataItem {
choices: SYSTEM.ITEM.actionTypes, choices: SYSTEM.ITEM.actionTypes,
initial: SYSTEM.ITEM.actionTypes.passive.id initial: SYSTEM.ITEM.actionTypes.passive.id
}), }),
//TODO: remove featureType field //TODO: remove featureType field
featureType: new fields.SchemaField({ featureType: new fields.SchemaField({
type: new fields.StringField({ type: new fields.StringField({
choices: SYSTEM.ITEM.valueTypes, choices: SYSTEM.ITEM.valueTypes,
@ -75,9 +75,10 @@ export default class DHFeature extends BaseDataItem {
{ nullable: true, initial: null } { nullable: true, initial: null }
), ),
dataField: new fields.StringField({}), dataField: new fields.StringField({}),
appliesOn: new fields.StringField({ appliesOn: new fields.StringField(
choices: SYSTEM.EFFECTS.applyLocations, {
}, choices: SYSTEM.EFFECTS.applyLocations
},
{ nullable: true, initial: null } { nullable: true, initial: null }
), ),
applyLocationChoices: new fields.TypedObjectField(new fields.StringField({}), { applyLocationChoices: new fields.TypedObjectField(new fields.StringField({}), {
@ -92,7 +93,7 @@ export default class DHFeature extends BaseDataItem {
}) })
}) })
), ),
actions: new fields.ArrayField(new fields.EmbeddedDataField(DaggerheartAction)) actions: new fields.ArrayField(new fields.EmbeddedDataField(DHAction))
}; };
} }

View file

@ -2,6 +2,7 @@ import BaseDataItem from './base.mjs';
import FormulaField from '../fields/formulaField.mjs'; import FormulaField from '../fields/formulaField.mjs';
import PseudoDocumentsField from '../fields/pseudoDocumentsField.mjs'; import PseudoDocumentsField from '../fields/pseudoDocumentsField.mjs';
import BaseFeatureData from '../pseudo-documents/feature/baseFeatureData.mjs'; import BaseFeatureData from '../pseudo-documents/feature/baseFeatureData.mjs';
import ActionField from '../fields/actionField.mjs';
export default class DHWeapon extends BaseDataItem { export default class DHWeapon extends BaseDataItem {
/** @inheritDoc */ /** @inheritDoc */
@ -38,14 +39,15 @@ export default class DHWeapon extends BaseDataItem {
initial: 'physical' initial: 'physical'
}) })
}), }),
feature: new fields.StringField({ choices: SYSTEM.ITEM.weaponFeatures, blank: true }), feature: new fields.StringField({ choices: SYSTEM.ITEM.weaponFeatures, blank: true }),
featureTest: new PseudoDocumentsField(BaseFeatureData, { featureTest: new PseudoDocumentsField(BaseFeatureData, {
required: true, required: true,
nullable: true, nullable: true,
max: 1, max: 1,
validTypes: ['weapon'] validTypes: ['weapon']
}) }),
// actions: new fields.ArrayField(new fields.EmbeddedDataField(DHAttackAction))
actions: new fields.ArrayField(new ActionField())
}; };
} }
} }

View file

@ -3,6 +3,7 @@ import NpcRollSelectionDialog from '../applications/npcRollSelectionDialog.mjs';
import RollSelectionDialog from '../applications/rollSelectionDialog.mjs'; import RollSelectionDialog from '../applications/rollSelectionDialog.mjs';
import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs'; import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs';
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
import DHDualityRoll from '../data/chat-message/dualityRoll.mjs';
export default class DhpActor extends Actor { export default class DhpActor extends Actor {
async _preCreate(data, options, user) { async _preCreate(data, options, user) {
@ -123,87 +124,52 @@ export default class DhpActor extends Actor {
}); });
} }
async diceRoll(modifier, shiftKey) { /**
if (this.type === 'character') { * @param {object} config
return await this.dualityRoll(modifier, shiftKey); * @param {Event} config.event
} else { * @param {string} config.title
return await this.npcRoll(modifier, shiftKey); * @param {object} config.roll
} * @param {number} config.roll.modifier
} * @param {boolean} [config.roll.simple=false]
* @param {string} [config.roll.type]
async npcRoll(modifier, shiftKey) { * @param {number} [config.roll.difficulty]
let advantage = null; * @param {any} [config.damage]
* @param {object} [config.chatMessage]
const modifiers = [ * @param {string} config.chatMessage.template
{ * @param {boolean} [config.chatMessage.mute]
value: Number.parseInt(modifier.value), * @param {boolean} [config.checkTarget]
label: modifier.value >= 0 ? `+${modifier.value}` : `-${modifier.value}`, */
title: modifier.title async diceRoll(config) {
}
];
if (!shiftKey) {
const dialogClosed = new Promise((resolve, _) => {
new NpcRollSelectionDialog(this.system.experiences, resolve).render(true);
});
const result = await dialogClosed;
advantage = result.advantage;
result.experiences.forEach(x =>
modifiers.push({
value: x.value,
label: x.value >= 0 ? `+${x.value}` : `-${x.value}`,
title: x.description
})
);
}
const roll = Roll.create(
`${advantage === true || advantage === false ? 2 : 1}d20${advantage === true ? 'kh' : advantage === false ? 'kl' : ''} ${modifiers.map(x => `+ ${x.value}`).join(' ')}`
);
let rollResult = await roll.evaluate();
const dice = [];
for (var i = 0; i < rollResult.terms.length; i++) {
const term = rollResult.terms[i];
if (term.faces) {
dice.push({ type: `d${term.faces}`, rolls: term.results.map(x => ({ value: x.result })) });
}
}
// There is Only ever one dice term here
return { roll, dice: dice[0], modifiers, advantageState: advantage === true ? 1 : advantage === false ? 2 : 0 };
}
async dualityRoll(modifier, shiftKey) {
let hopeDice = 'd12', let hopeDice = 'd12',
fearDice = 'd12', fearDice = 'd12',
advantageDice = null, advantageDice = 'd6',
disadvantageDice = null; disadvantageDice = 'd6',
advantage = config.event.altKey ? true : config.event.ctrlKey ? false : null,
targets,
damage = config.damage,
modifiers = this.formatRollModifier(config.roll),
rollConfig,
formula,
hope,
fear;
const modifiers = if (!config.event.shiftKey && !config.event.altKey && !config.event.ctrlKey) {
modifier.value !== null
? [
{
value: modifier.value ? Number.parseInt(modifier.value) : 0,
label:
modifier.value >= 0
? `${modifier.title} +${modifier.value}`
: `${modifier.title} ${modifier.value}`,
title: modifier.title
}
]
: [];
if (!shiftKey) {
const dialogClosed = new Promise((resolve, _) => { const dialogClosed = new Promise((resolve, _) => {
new RollSelectionDialog(this.system.experiences, this.system.resources.hope.value, resolve).render( this.type === 'character'
true ? new RollSelectionDialog(
); this.system.experiences,
this.system.resources.hope.value,
resolve
).render(true)
: new NpcRollSelectionDialog(this.system.experiences, resolve).render(true);
}); });
const result = await dialogClosed; rollConfig = await dialogClosed;
(hopeDice = result.hope),
(fearDice = result.fear), advantage = rollConfig.advantage;
(advantageDice = result.advantage), hopeDice = rollConfig.hope;
(disadvantageDice = result.disadvantage); fearDice = rollConfig.fear;
result.experiences.forEach(x =>
rollConfig.experiences.forEach(x =>
modifiers.push({ modifiers.push({
value: x.value, value: x.value,
label: x.value >= 0 ? `+${x.value}` : `-${x.value}`, label: x.value >= 0 ? `+${x.value}` : `-${x.value}`,
@ -211,60 +177,123 @@ export default class DhpActor extends Actor {
}) })
); );
const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope); if (this.type === 'character') {
const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope);
if (automateHope && result.hopeUsed) { if (automateHope && result.hopeUsed) {
await this.update({ await this.update({
'system.resources.hope.value': this.system.resources.hope.value - result.hopeUsed 'system.resources.hope.value': this.system.resources.hope.value - result.hopeUsed
}); });
}
} }
} }
const roll = new Roll(
`1${hopeDice} + 1${fearDice}${advantageDice ? ` + 1${advantageDice}` : disadvantageDice ? ` - 1${disadvantageDice}` : ''} ${modifiers.map(x => `+ ${x.value}`).join(' ')}`
);
let rollResult = await roll.evaluate();
setDiceSoNiceForDualityRoll(rollResult, advantageDice, disadvantageDice);
const hope = rollResult.dice[0].results[0].result; if (this.type === 'character') {
const fear = rollResult.dice[1].results[0].result; formula = `1${hopeDice} + 1${fearDice}${advantage === true ? ` + 1d6` : advantage === false ? ` - 1d6` : ''}`;
const advantage = advantageDice ? rollResult.dice[2].results[0].result : null; } else {
const disadvantage = disadvantageDice ? rollResult.dice[2].results[0].result : null; formula = `${advantage === true || advantage === false ? 2 : 1}d20${advantage === true ? 'kh' : advantage === false ? 'kl' : ''}`;
if (disadvantage) {
rollResult = { ...rollResult, total: rollResult.total - Math.max(hope, disadvantage) };
}
if (advantage) {
rollResult = { ...rollResult, total: 'Select Hope Die' };
} }
formula += ` ${modifiers.map(x => `+ ${x.value}`).join(' ')}`;
const roll = await Roll.create(formula).evaluate();
const dice = roll.dice.flatMap(dice => ({
denomination: dice.denomination,
number: dice.number,
total: dice.total,
results: dice.results.map(result => ({ result: result.result, discarded: !result.active }))
}));
const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope); if (this.type === 'character') {
if (automateHope && hope > fear) { setDiceSoNiceForDualityRoll(roll, advantage);
await this.update({ hope = roll.dice[0].results[0].result;
'system.resources.hope.value': Math.min( fear = roll.dice[1].results[0].result;
this.system.resources.hope.value + 1, if (
this.system.resources.hope.max game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope) &&
) config.roll.type === 'action'
}); ) {
} if (hope > fear) {
await this.update({
if (automateHope && hope === fear) { 'system.resources.hope.value': Math.min(
await this.update({ this.system.resources.hope.value + 1,
'system.resources': { this.system.resources.hope.max
'hope.value': Math.min(this.system.resources.hope.value + 1, this.system.resources.hope.max), )
'stress.value': Math.max(this.system.resources.stress.value - 1, 0) });
} else if (hope === fear) {
await this.update({
'system.resources': {
'hope.value': Math.min(
this.system.resources.hope.value + 1,
this.system.resources.hope.max
),
'stress.value': Math.max(this.system.resources.stress.value - 1, 0)
}
});
} }
}
}
if (config.checkTarget) {
targets = Array.from(game.user.targets).map(x => {
const target = {
id: x.id,
name: x.actor.name,
img: x.actor.img,
difficulty: x.actor.system.difficulty,
evasion: x.actor.system.evasion?.value
};
target.hit = target.difficulty ? roll.total >= target.difficulty : roll.total >= target.evasion;
return target;
}); });
} }
return { if (config.chatMessage) {
roll, const configRoll = {
rollResult, title: config.title,
hope: { dice: hopeDice, value: hope }, origin: this.id,
fear: { dice: fearDice, value: fear }, dice,
advantage: { dice: advantageDice, value: advantage }, roll,
disadvantage: { dice: disadvantageDice, value: disadvantage }, modifiers: modifiers.filter(x => x.label),
modifiers: modifiers advantageState: advantage
}; };
if (this.type === 'character') {
configRoll.hope = { dice: hopeDice, value: hope };
configRoll.fear = { dice: fearDice, value: fear };
configRoll.advantage = { dice: advantageDice, value: roll.dice[2]?.results[0].result ?? null };
}
if (damage) configRoll.damage = damage;
if (targets) configRoll.targets = targets;
const systemData =
this.type === 'character' && !config.roll.simple ? new DHDualityRoll(configRoll) : configRoll,
cls = getDocumentClass('ChatMessage'),
msg = new cls({
type: config.chatMessage.type ?? 'dualityRoll',
sound: config.chatMessage.mute ? null : CONFIG.sounds.dice,
system: systemData,
content: config.chatMessage.template,
rolls: [roll]
});
await cls.create(msg.toObject());
}
return roll;
}
formatRollModifier(roll) {
const modifier = roll.modifier !== null ? Number.parseInt(roll.modifier) : null;
return modifier !== null
? [
{
value: modifier,
label: roll.label
? modifier >= 0
? `${roll.label} +${modifier}`
: `${roll.label} ${modifier}`
: null,
title: roll.label
}
]
: [];
} }
async damageRoll(title, damage, targets, shiftKey) { async damageRoll(title, damage, targets, shiftKey) {
@ -294,7 +323,11 @@ export default class DhpActor extends Actor {
for (var i = 0; i < rollResult.terms.length; i++) { for (var i = 0; i < rollResult.terms.length; i++) {
const term = rollResult.terms[i]; const term = rollResult.terms[i];
if (term.faces) { if (term.faces) {
dice.push({ type: `d${term.faces}`, rolls: term.results.map(x => x.result) }); dice.push({
type: `d${term.faces}`,
rolls: term.results.map(x => x.result),
total: term.results.reduce((acc, x) => acc + x.result, 0)
});
} else if (term.operator) { } else if (term.operator) {
} else if (term.number) { } else if (term.number) {
const operator = i === 0 ? '' : rollResult.terms[i - 1].operator; const operator = i === 0 ? '' : rollResult.terms[i - 1].operator;

View file

@ -9,6 +9,12 @@ export default class DhpItem extends Item {
return super.getEmbeddedDocument(embeddedName, id, { invalid, strict }); return super.getEmbeddedDocument(embeddedName, id, { invalid, strict });
} }
/** @inheritDoc */
prepareEmbeddedDocuments() {
super.prepareEmbeddedDocuments();
for (const action of this.system.actions ?? []) action.prepareData();
}
/** /**
* @inheritdoc * @inheritdoc
* @param {object} options - Options which modify the getRollData method. * @param {object} options - Options which modify the getRollData method.
@ -92,4 +98,47 @@ export default class DhpItem extends Item {
options options
}); });
} }
async selectActionDialog() {
const content = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/views/actionSelect.hbs',
{ actions: this.system.actions }
),
title = 'Select Action',
type = 'div',
data = {};
return Dialog.prompt({
title,
// label: title,
content,
type,
callback: html => {
const form = html[0].querySelector('form'),
fd = new foundry.applications.ux.FormDataExtended(form);
return this.system.actions.find(a => a._id === fd.object.actionId);
},
rejectClose: false
});
}
async use(event) {
const actions = this.system.actions;
let response;
if (actions?.length) {
let action = actions[0];
if (actions.length > 1 && !event?.shiftKey) {
// Actions Choice Dialog
action = await this.selectActionDialog();
}
if (action) response = action.use(event);
// Check Target
// If action.roll => Roll Dialog
// Else If action.cost => Cost Dialog
// Then
// Apply Cost
// Apply Effect
}
// Display Item Card in chat
return response;
}
} }

View file

@ -9,21 +9,26 @@ export function dualityRollEnricher(match, _options) {
} }
export function getDualityMessage(roll) { export function getDualityMessage(roll) {
const attributeLabel = const traitLabel =
roll.attribute && abilities[roll.attribute] roll.trait && abilities[roll.trait]
? game.i18n.format('DAGGERHEART.General.Check', { ? game.i18n.format('DAGGERHEART.General.Check', {
check: game.i18n.localize(abilities[roll.attribute].label) check: game.i18n.localize(abilities[roll.trait].label)
}) })
: null; : null;
const label = attributeLabel ?? game.i18n.localize('DAGGERHEART.General.Duality');
const label = traitLabel ?? game.i18n.localize('DAGGERHEART.General.Duality');
const dataLabel = traitLabel
? game.i18n.localize(abilities[roll.trait].label)
: game.i18n.localize('DAGGERHEART.General.Duality');
const dualityElement = document.createElement('span'); const dualityElement = document.createElement('span');
dualityElement.innerHTML = ` dualityElement.innerHTML = `
<button class="duality-roll-button" <button class="duality-roll-button"
data-label="${label}" data-title="${label}"
data-label="${dataLabel}"
data-hope="${roll.hope ?? 'd12'}" data-hope="${roll.hope ?? 'd12'}"
data-fear="${roll.fear ?? 'd12'}" data-fear="${roll.fear ?? 'd12'}"
${roll.attribute && abilities[roll.attribute] ? `data-attribute="${roll.attribute}"` : ''} ${roll.trait && abilities[roll.trait] ? `data-trait="${roll.trait}"` : ''}
${roll.advantage ? 'data-advantage="true"' : ''} ${roll.advantage ? 'data-advantage="true"' : ''}
${roll.disadvantage ? 'data-disadvantage="true"' : ''} ${roll.disadvantage ? 'data-disadvantage="true"' : ''}
> >

View file

@ -122,14 +122,16 @@ export const getCommandTarget = () => {
return target; return target;
}; };
export const setDiceSoNiceForDualityRoll = (rollResult, advantage, disadvantage) => { export const setDiceSoNiceForDualityRoll = (rollResult, advantageState) => {
const diceSoNicePresets = getDiceSoNicePresets(); const diceSoNicePresets = getDiceSoNicePresets();
rollResult.dice[0].options.appearance = diceSoNicePresets.hope; rollResult.dice[0].options.appearance = diceSoNicePresets.hope;
rollResult.dice[1].options.appearance = diceSoNicePresets.fear; rollResult.dice[1].options.appearance = diceSoNicePresets.fear;
if (advantage) { if (rollResult.dice[2]) {
rollResult.dice[2].options.appearance = diceSoNicePresets.advantage; if (advantageState === true) {
} else if (disadvantage) { rollResult.dice[2].options.appearance = diceSoNicePresets.advantage;
rollResult.dice[2].options.appearance = diceSoNicePresets.disadvantage; } else if (advantageState === false) {
rollResult.dice[2].options.appearance = diceSoNicePresets.disadvantage;
}
} }
}; };
@ -222,3 +224,14 @@ export const getDeleteKeys = (property, innerProperty, innerPropertyDefaultValue
return acc; return acc;
}, {}); }, {});
}; };
// Fix on Foundry native formula replacement for DH
const nativeReplaceFormulaData = Roll.replaceFormulaData;
Roll.replaceFormulaData = function (formula, data, { missing, warn = false } = {}) {
const terms = [
{ term: 'prof', default: 1 },
{ term: 'cast', default: 1 }
];
formula = terms.reduce((a, c) => a.replaceAll(`@${c.term}`, data[c.term] ?? c.default), formula);
return nativeReplaceFormulaData(formula, data, { missing, warn });
};

View file

@ -452,6 +452,37 @@ div.daggerheart.views.multiclass {
&.open { &.open {
max-height: initial; max-height: initial;
} }
.multi-display {
display: flex;
gap: 1rem;
align-items: center;
.form-group {
flex: 1;
}
}
.form-group {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
label {
flex: 2;
}
.form-fields {
flex: 3;
}
img {
width: 1.5rem;
height: 1.5rem;
}
}
.data-form-array {
border: 1px solid var(--color-fieldset-border);
padding: 0.5rem;
margin-bottom: 0.5rem;
}
} }
} }
} }

View file

@ -2216,6 +2216,34 @@ div.daggerheart.views.multiclass {
.daggerheart.views.action .action-category .action-category-data.open { .daggerheart.views.action .action-category .action-category-data.open {
max-height: initial; max-height: initial;
} }
.daggerheart.views.action .action-category .action-category-data .multi-display {
display: flex;
gap: 1rem;
align-items: center;
}
.daggerheart.views.action .action-category .action-category-data .multi-display .form-group {
flex: 1;
}
.daggerheart.views.action .action-category .action-category-data .form-group {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
.daggerheart.views.action .action-category .action-category-data .form-group label {
flex: 2;
}
.daggerheart.views.action .action-category .action-category-data .form-group .form-fields {
flex: 3;
}
.daggerheart.views.action .action-category .action-category-data .form-group img {
width: 1.5rem;
height: 1.5rem;
}
.daggerheart.views.action .action-category .action-category-data .data-form-array {
border: 1px solid var(--color-fieldset-border);
padding: 0.5rem;
margin-bottom: 0.5rem;
}
.daggerheart.views.ancestry-selection .ancestry-section { .daggerheart.views.ancestry-selection .ancestry-section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -3705,3 +3733,34 @@ div.daggerheart.views.multiclass {
cursor: pointer; cursor: pointer;
filter: drop-shadow(0 0 3px red); filter: drop-shadow(0 0 3px red);
} }
.unlist {
list-style: none;
padding-inline-start: 0;
}
.list-select {
margin: 1rem;
}
.list-select li:not(:last-child) {
border-bottom: 1px solid #bbb;
}
.list-select li label {
padding: 4px 8px;
display: flex;
align-items: center;
gap: 1rem;
cursor: pointer;
}
.list-select li label > span {
flex: 1;
font-weight: bold;
font-size: var(--font-size-16);
}
dh-icon,
dh-icon > img {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: x-large;
}

View file

@ -130,3 +130,39 @@
} }
} }
} }
.unlist {
list-style: none;
padding-inline-start: 0;
}
.list-select {
margin: 1rem;
li {
&:not(:last-child) {
border-bottom: 1px solid #bbb;
}
label {
padding: 4px 8px;
display: flex;
align-items: center;
gap: 1rem;
cursor: pointer;
> span {
flex: 1;
font-weight: bold;
font-size: var(--font-size-16);
}
}
}
}
dh-icon,
dh-icon > img {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: x-large;
}

View file

@ -1,30 +1,32 @@
<div class="dice-roll daggerheart chat roll" data-action="expandRoll"> <div class="dice-roll daggerheart chat roll" data-action="expandRoll">
<div class="dice-flavor">{{localize "DAGGERHEART.Chat.AttackRoll.Title" attack=this.title}}</div> <div class="dice-flavor">{{localize "DAGGERHEART.Chat.AttackRoll.Title" attack=this.title}}</div>
<div class="dice-result"> <div class="dice-result">
<div class="dice-formula">{{roll}}</div> <div class="dice-formula">{{roll.formula}}</div>
<div class="dice-tooltip"> <div class="dice-tooltip">
<div class="wrapper"> <div class="wrapper">
<section class="tooltip-part"> <section class="tooltip-part">
<div class="dice"> <div class="dice">
<header class="part-header flexrow"> {{#each dice}}
<span class="part-formula">{{this.dice.rolls.length}}{{this.dice.type}}</span> <header class="part-header flexrow">
<span class="part-total">{{this.dice.rollTotal}}</span> <span class="part-formula">{{number}}{{denomination}}</span>
</header> <span class="part-total">{{total}}</span>
<div class="flexrow"> </header>
<ol class="dice-rolls"> <div class="flexrow">
{{#each this.dice.rolls}} <ol class="dice-rolls">
<li class="roll die {{../dice.type}} {{#if this.discarded}}discarded{{/if}} min">{{this.value}}</li> {{#each results}}
{{/each}} <li class="roll die {{../denomination}}{{#if discarded}} discarded{{/if}} min">{{result}}</li>
</ol> {{/each}}
<div class="attack-roll-advantage-container">{{#if (eq this.advantageState 1)}}{{localize "DAGGERHEART.General.Advantage.Full"}}{{/if}}{{#if (eq this.advantageState 2)}}{{localize "DAGGERHEART.General.Disadvantage.Full"}}{{/if}}</div> </ol>
</div> <div class="attack-roll-advantage-container">{{#if ../advantageState}}{{localize "DAGGERHEART.General.Advantage.Full"}}{{/if}}{{#if (eq ../advantageState false)}}{{localize "DAGGERHEART.General.Disadvantage.Full"}}{{/if}}</div>
</div>
{{/each}}
</div> </div>
</section> </section>
</div> </div>
</div> </div>
<div class="dice-total"> <div class="dice-total">
<div class="dice-total-value">{{this.total}}</div> <div class="dice-total-value">{{roll.total}}</div>
</div> </div>
{{#if (gt targets.length 0)}} {{#if (gt targets.length 0)}}
<div class="target-section"> <div class="target-section">
@ -39,7 +41,7 @@
</div> </div>
{{/if}} {{/if}}
<div class="flexrow"> <div class="flexrow">
<button class="duality-action" data-value="{{this.total.normal}}" data-damage="{{this.damage.value}}" data-damage-type="{{this.damage.type}}" {{#if this.damage.disabled}}disabled{{/if}}><span>Roll Damage</span></button> <button class="duality-action" data-value="{{roll.total}}" data-damage="{{damage.value}}" data-damage-type="{{damage.type}}"{{#if damage.disabled}} disabled{{/if}}><span>Roll Damage</span></button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,22 +1,33 @@
<div class="dice-roll daggerheart chat roll"> <div class="dice-roll daggerheart chat roll" data-action="expandRoll">
<div class="dice-result"> <div class="dice-result">
<div class="dice-formula">{{roll}}</div> <div class="dice-formula">{{roll.formula}}</div>
<div class="dice-tooltip"> <div class="dice-tooltip">
<ol class="dice-rolls"> <ol class="dice-rolls">
<div class="dice-hope-container"> <div class="dice-hope-container">
{{#each diceResults}} {{#each dice}}
<li class="roll die d20 {{#if this.discarded}}discarded{{/if}}">{{this.value}}</li> <header class="part-header flexrow">
{{/each}} <span class="part-formula">{{number}}{{denomination}}</span>
<span class="part-total">{{total}}</span>
</header>
<div class="flexrow">
<ol class="dice-rolls">
{{#each results}}
<li class="roll die {{../denomination}}{{#if discarded}} discarded{{/if}} min">{{result}}</li>
{{/each}}
</ol>
<div class="attack-roll-advantage-container">{{#if ../advantageState}}{{localize "DAGGERHEART.General.Advantage.Full"}}{{/if}}{{#if (eq ../advantageState false)}}{{localize "DAGGERHEART.General.Disadvantage.Full"}}{{/if}}</div>
</div>
{{/each}}
</div> </div>
<div class="modifiers-container"> <div class="modifiers-container">
{{#each modifiers}} {{#each modifiers}}
<li class="modifier-value" data-value="{{this.value}}" title="{{this.title}}">{{this.label}}</li> <li class="modifier-value" data-value="{{value}}" title="{{title}}">{{label}}</li>
{{/each}} {{/each}}
</div> </div>
</ol> </ol>
</div> </div>
<div class="dice-total"> <div class="dice-total">
<div class="dice-total-value">{{total}}</div> <div class="dice-total-value">{{roll.total}}</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,27 +1,27 @@
{{#if this.colorful}} {{#if colorful}}
<div class="daggerheart chat roll" data-action="expandRoll"> <div class="daggerheart chat roll" data-action="expandRoll">
<div class="duality-data"> <div class="duality-data">
<div class="duality-title"> <div class="duality-title">
<div>{{localize "DAGGERHEART.Chat.AttackRoll.Title" attack=this.title}}</div> <div>{{localize "DAGGERHEART.Chat.AttackRoll.Title" attack=title}}</div>
</div> </div>
<div class="duality-modifiers"> <div class="duality-modifiers">
{{#each this.modifiers}} {{#each modifiers}}
<div class="duality-modifier"> <div class="duality-modifier">
{{this.label}} {{label}}
</div> </div>
{{/each}} {{/each}}
{{#if this.advantage.value}} {{#if advantageState}}
<div class="duality-modifier"> <div class="duality-modifier">
{{localize "DAGGERHEART.General.Advantage.Full"}} {{localize "DAGGERHEART.General.Advantage.Full"}}
</div> </div>
{{/if}} {{/if}}
{{#if this.disadvantage.value}} {{#if (eq advantageState false)}}
<div class="duality-modifier"> <div class="duality-modifier">
{{localize "DAGGERHEART.General.Disadvantage.Full"}} {{localize "DAGGERHEART.General.Disadvantage.Full"}}
</div> </div>
{{/if}} {{/if}}
</div> </div>
<div class="duality-line {{#if (not this.damage.value)}}simple{{/if}}"> <div class="duality-line {{#if (not damage.value)}}simple{{/if}}">
<div class="dice-outer-container"> <div class="dice-outer-container">
<div class="dice-container"> <div class="dice-container">
<div class="dice-title">{{localize "DAGGERHEART.General.Hope"}}</div> <div class="dice-title">{{localize "DAGGERHEART.General.Hope"}}</div>
@ -41,27 +41,27 @@
<div class="dice-value">{{fear.value}}</div> <div class="dice-value">{{fear.value}}</div>
</div> </div>
</div> </div>
{{#if this.advantage.value}} {{#if advantageState}}
<div class="advantage-container advantage"> <div class="advantage-container advantage">
<div class="dice-wrapper"> <div class="dice-wrapper">
<img class="dice" src="../icons/svg/d6-grey.svg"/> <img class="dice" src="../icons/svg/d6-grey.svg"/>
<div class="dice-value">{{this.advantage.value}}</div> <div class="dice-value">{{advantage.value}}</div>
</div> </div>
</div> </div>
{{/if}} {{/if}}
{{#if this.disadvantage.value}} {{#if (eq advantageState false)}}
<div class="advantage-container disadvantage"> <div class="advantage-container disadvantage">
<div class="dice-wrapper"> <div class="dice-wrapper">
<img class="dice" src="../icons/svg/d6-grey.svg"/> <img class="dice" src="../icons/svg/d6-grey.svg"/>
<div class="dice-value">{{this.disadvantage.value}}</div> <div class="dice-value">{{advantage.value}}</div>
</div> </div>
</div> </div>
{{/if}} {{/if}}
{{#if this.modifierTotal.value}}<div class="duality-modifier">{{this.modifierTotal.label}}</div>{{/if}} {{#if modifierTotal.value}}<div class="duality-modifier">{{modifierTotal.label}}</div>{{/if}}
</div> </div>
{{#if (not this.damage.value)}} {{#if (not damage.value)}}
<div class="duality-result"> <div class="duality-result">
<div>{{this.total}} {{#if (eq dualityResult 1)}}With Hope{{else}}{{#if (eq dualityResult 2)}}With Fear{{else}}Critical Success{{/if}}{{/if}}</div> <div>{{roll.total}} {{#if (eq dualityResult 1)}}With Hope{{else}}{{#if (eq dualityResult 2)}}With Fear{{else}}Critical Success{{/if}}{{/if}}</div>
</div> </div>
{{/if}} {{/if}}
</div> </div>
@ -78,18 +78,18 @@
{{/each}} {{/each}}
</div> </div>
{{/if}} {{/if}}
{{#if this.damage.value}} {{#if damage.value}}
<div class="duality-actions"> <div class="duality-actions">
<button class="duality-action" data-value="{{this.total}}" data-damage="{{this.damage.value}}" data-damage-type="{{this.damage.type}}" {{#if this.damage.disabled}}disabled{{/if}}><span>Roll Damage</span></button> <button class="duality-action" data-value="{{roll.total}}" data-damage="{{damage.value}}" data-damage-type="{{damage.type}}" {{#if damage.disabled}}disabled{{/if}}><span>Roll Damage</span></button>
<div class="duality-result"> <div class="duality-result">
<div>{{this.total}} {{#if (eq dualityResult 1)}}With Hope{{else}}{{#if (eq dualityResult 2)}}With Fear{{else}}Critical Success{{/if}}{{/if}}</div> <div>{{roll.total}} {{#if (eq dualityResult 1)}}With Hope{{else}}{{#if (eq dualityResult 2)}}With Fear{{else}}Critical Success{{/if}}{{/if}}</div>
</div> </div>
</div> </div>
{{/if}} {{/if}}
</div> </div>
{{else}} {{else}}
<div class="dice-roll daggerheart chat roll" data-action="expandRoll"> <div class="dice-roll daggerheart chat roll" data-action="expandRoll">
<div class="dice-flavor">{{localize "DAGGERHEART.Chat.AttackRoll.Title" attack=this.title}}</div> <div class="dice-flavor">{{localize "DAGGERHEART.Chat.AttackRoll.Title" attack=title}}</div>
<div class="dice-result"> <div class="dice-result">
<div class="dice-formula">{{roll}}</div> <div class="dice-formula">{{roll}}</div>
@ -103,7 +103,7 @@
| |
<span>1{{fear.dice}}</span> <span>1{{fear.dice}}</span>
</span> </span>
<span class="part-total">{{this.diceTotal}}</span> <span class="part-total">{{diceTotal}}</span>
</header> </header>
<div class="flexrow"> <div class="flexrow">
<ol class="dice-rolls duality"> <ol class="dice-rolls duality">
@ -112,7 +112,7 @@
</ol> </ol>
</div> </div>
</div> </div>
{{#if advantage.value}} {{#if (eq advantageState 1)}}
<div class="dice"> <div class="dice">
<header class="part-header flexrow"> <header class="part-header flexrow">
<span class="part-formula"> <span class="part-formula">
@ -127,17 +127,17 @@
</div> </div>
</div> </div>
{{/if}} {{/if}}
{{#if disadvantage.value}} {{#if (eq advantageState 2)}}
<div class="dice"> <div class="dice">
<header class="part-header flexrow"> <header class="part-header flexrow">
<span class="part-formula"> <span class="part-formula">
<span>1{{disadvantage.dice}}</span> <span>1{{advantage.dice}}</span>
</span> </span>
<span class="part-total">{{disadvantage.value}}</span> <span class="part-total">{{advantage.value}}</span>
</header> </header>
<div class="flexrow"> <div class="flexrow">
<ol class="dice-rolls"> <ol class="dice-rolls">
<li class="roll die {{disadvantage.dice}} hope min">{{disadvantage.value}}</li> <li class="roll die {{advantage.dice}} hope min">{{advantage.value}}</li>
</ol> </ol>
</div> </div>
</div> </div>
@ -148,7 +148,7 @@
<div class="dice-total duality {{#if fear.discarded}}hope{{else}}{{#if hope.discarded}}fear{{else}}critical{{/if}}{{/if}}"> <div class="dice-total duality {{#if fear.discarded}}hope{{else}}{{#if hope.discarded}}fear{{else}}critical{{/if}}{{/if}}">
<div class="dice-total-label">{{totalLabel}}</div> <div class="dice-total-label">{{totalLabel}}</div>
<div class="dice-total-value"> <div class="dice-total-value">
{{this.total}} {{roll.total}}
</div> </div>
</div> </div>
{{#if (gt targets.length 0)}} {{#if (gt targets.length 0)}}
@ -164,7 +164,7 @@
</div> </div>
{{/if}} {{/if}}
<div class="dice-actions"> <div class="dice-actions">
<button class="duality-action" data-value="{{this.total}}" data-damage="{{this.damage.value}}" data-damage-type="{{this.damage.type}}" {{#if this.damage.disabled}}disabled{{/if}}><span>{{localize "DAGGERHEART.Chat.AttackRoll.RollDamage"}}</span></button> <button class="duality-action" data-value="{{roll.total}}" data-damage="{{damage.value}}" data-damage-type="{{damage.type}}" {{#if damage.disabled}}disabled{{/if}}><span>{{localize "DAGGERHEART.Chat.AttackRoll.RollDamage"}}</span></button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,7 +1,7 @@
<div class="dice-roll daggerheart chat roll" data-action="expandRoll"> <div class="dice-roll daggerheart chat roll" data-action="expandRoll">
<div class="dice-flavor">{{this.title}}</div> <div class="dice-flavor">{{title}}</div>
<div class="dice-result"> <div class="dice-result">
<div class="dice-formula">{{this.roll}}</div> <div class="dice-formula">{{roll}}</div>
<div class="dice-tooltip"> <div class="dice-tooltip">
<div class="wrapper"> <div class="wrapper">
@ -9,12 +9,12 @@
{{#each dice}} {{#each dice}}
<div class="dice"> <div class="dice">
<header class="part-header flexrow"> <header class="part-header flexrow">
<span class="part-formula">{{this.rolls.length}}{{this.type}}</span> <span class="part-formula">{{rolls.length}}{{type}}</span>
<span class="part-total">{{this.rollTotal}}</span> <span class="part-total">{{this.total}}</span>
</header> </header>
<ol class="dice-rolls"> <ol class="dice-rolls">
{{#each this.rolls}} {{#each rolls}}
<li class="roll die {{../type}} min">{{this}}</li> <li class="roll die {{../type}} min">{{this}}</li>
{{/each}} {{/each}}
</ol> </ol>
@ -23,9 +23,9 @@
</section> </section>
</div> </div>
</div> </div>
<div class="dice-total">{{this.damage.total}}</div> <div class="dice-total">{{damage.total}}</div>
<div class="dice-actions"> <div class="dice-actions">
<button class="damage-button" data-target-hit="true" {{#if (eq this.targets.length 0)}}disabled{{/if}}>{{localize "DAGGERHEART.Chat.DamageRoll.DealDamageToTargets"}}</button> <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> <button class="damage-button">{{localize "DAGGERHEART.Chat.DamageRoll.DealDamage"}}</button>
</div> </div>
</div> </div>

View file

@ -1,27 +1,27 @@
{{#if this.colorful}} {{#if colorful}}
<div class="daggerheart chat roll" data-action="expandRoll"> <div class="daggerheart chat roll" data-action="expandRoll">
<div class="duality-data"> <div class="duality-data">
<div class="duality-title"> <div class="duality-title">
<div>{{this.title}}</div> <div>{{title}}</div>
</div> </div>
<div class="duality-modifiers"> <div class="duality-modifiers">
{{#each this.modifiers}} {{#each modifiers}}
<div class="duality-modifier"> <div class="duality-modifier">
{{this.label}} {{label}}
</div> </div>
{{/each}} {{/each}}
{{#if this.advantage.value}} {{#if advantageState}}
<div class="duality-modifier"> <div class="duality-modifier">
{{localize "DAGGERHEART.General.Advantage.Full"}} {{localize "DAGGERHEART.General.Advantage.Full"}}
</div> </div>
{{/if}} {{/if}}
{{#if this.disadvantage.value}} {{#if (eq advantageState false)}}
<div class="duality-modifier"> <div class="duality-modifier">
{{localize "DAGGERHEART.General.Disadvantage.Full"}} {{localize "DAGGERHEART.General.Disadvantage.Full"}}
</div> </div>
{{/if}} {{/if}}
</div> </div>
<div class="duality-line {{#if (not this.damage.value)}}simple{{/if}}"> <div class="duality-line {{#if (not damage.value)}}simple{{/if}}">
<div class="dice-outer-container"> <div class="dice-outer-container">
<div class="dice-container"> <div class="dice-container">
<div class="dice-title">{{localize "DAGGERHEART.General.Hope"}}</div> <div class="dice-title">{{localize "DAGGERHEART.General.Hope"}}</div>
@ -41,32 +41,32 @@
<div class="dice-value">{{fear.value}}</div> <div class="dice-value">{{fear.value}}</div>
</div> </div>
</div> </div>
{{#if this.advantage.value}} {{#if advantageState}}
<div class="advantage-container advantage"> <div class="advantage-container advantage">
<div class="dice-wrapper"> <div class="dice-wrapper">
<img class="dice" src="../icons/svg/d6-grey.svg"/> <img class="dice" src="../icons/svg/d6-grey.svg"/>
<div class="dice-value">{{this.advantage.value}}</div> <div class="dice-value">{{advantage.value}}</div>
</div> </div>
</div> </div>
{{/if}} {{/if}}
{{#if this.disadvantage.value}} {{#if (eq advantageState false)}}
<div class="advantage-container disadvantage"> <div class="advantage-container disadvantage">
<div class="dice-wrapper"> <div class="dice-wrapper">
<img class="dice" src="../icons/svg/d6-grey.svg"/> <img class="dice" src="../icons/svg/d6-grey.svg"/>
<div class="dice-value">{{this.disadvantage.value}}</div> <div class="dice-value">{{advantage.value}}</div>
</div> </div>
</div> </div>
{{/if}} {{/if}}
{{#if this.modifierTotal.value}}<div class="duality-modifier">{{this.modifierTotal.label}}</div>{{/if}} {{#if modifierTotal.value}}<div class="duality-modifier">{{modifierTotal.label}}</div>{{/if}}
</div> </div>
{{#if (not this.damage.value)}} {{#if (not damage.value)}}
<div class="duality-result"> <div class="duality-result">
<div>{{this.total}} {{#if (eq dualityResult 1)}}With Hope{{else}}{{#if (eq dualityResult 2)}}With Fear{{else}}Critical Success{{/if}}{{/if}}</div> <div>{{roll.total}} {{#if (eq dualityResult 1)}}With Hope{{else}}{{#if (eq dualityResult 2)}}With Fear{{else}}Critical Success{{/if}}{{/if}}</div>
</div> </div>
{{/if}} {{/if}}
</div> </div>
</div> </div>
{{#if this.damage.value}} {{#if damage.value}}
<div class="duality-actions"> <div class="duality-actions">
<div></div> <div></div>
<div class="duality-result"> <div class="duality-result">
@ -77,9 +77,9 @@
</div> </div>
{{else}} {{else}}
<div class="dice-roll daggerheart chat roll" data-action="expandRoll"> <div class="dice-roll daggerheart chat roll" data-action="expandRoll">
<div class="dice-flavor">{{this.title}}</div> <div class="dice-flavor">{{title}}</div>
<div class="dice-result"> <div class="dice-result">
<div class="dice-formula">{{roll}}</div> <div class="dice-formula">{{roll.formula}}</div>
<div class="dice-tooltip"> <div class="dice-tooltip">
<div class="wrapper"> <div class="wrapper">
<section class="tooltip-part"> <section class="tooltip-part">
@ -90,7 +90,7 @@
| |
<span>1{{fear.dice}}</span> <span>1{{fear.dice}}</span>
</span> </span>
<span class="part-total">{{this.diceTotal}}</span> <span class="part-total">{{diceTotal}}</span>
</header> </header>
<div class="flexrow"> <div class="flexrow">
<ol class="dice-rolls duality"> <ol class="dice-rolls duality">
@ -99,7 +99,7 @@
</ol> </ol>
</div> </div>
</div> </div>
{{#if advantage.value}} {{#if (eq advantageState 1)}}
<div class="dice"> <div class="dice">
<header class="part-header flexrow"> <header class="part-header flexrow">
<span class="part-formula"> <span class="part-formula">
@ -114,17 +114,17 @@
</div> </div>
</div> </div>
{{/if}} {{/if}}
{{#if disadvantage.value}} {{#if (eq advantageState 2)}}
<div class="dice"> <div class="dice">
<header class="part-header flexrow"> <header class="part-header flexrow">
<span class="part-formula"> <span class="part-formula">
<span>1{{disadvantage.dice}}</span> <span>1{{advantage.dice}}</span>
</span> </span>
<span class="part-total">{{disadvantage.value}}</span> <span class="part-total">{{advantage.value}}</span>
</header> </header>
<div class="flexrow"> <div class="flexrow">
<ol class="dice-rolls"> <ol class="dice-rolls">
<li class="roll die {{disadvantage.dice}} hope min">{{disadvantage.value}}</li> <li class="roll die {{advantage.dice}} hope min">{{advantage.value}}</li>
</ol> </ol>
</div> </div>
</div> </div>
@ -135,7 +135,7 @@
<div class="dice-total duality {{#if fear.discarded}}hope{{else}}{{#if hope.discarded}}fear{{else}}critical{{/if}}{{/if}}"> <div class="dice-total duality {{#if fear.discarded}}hope{{else}}{{#if hope.discarded}}fear{{else}}critical{{/if}}{{/if}}">
<div class="dice-total-label">{{totalLabel}}</div> <div class="dice-total-label">{{totalLabel}}</div>
<div class="dice-total-value"> <div class="dice-total-value">
{{total}} {{roll.total}}
</div> </div>
</div> </div>
</div> </div>

View file

@ -3,15 +3,15 @@
data-tab='{{tabs.information.id}}' data-tab='{{tabs.information.id}}'
data-group='{{tabs.information.group}}' data-group='{{tabs.information.group}}'
> >
<fieldset> {{!-- <fieldset>
<legend>{{localize "DAGGERHEART.Sheets.Adversary.Description" }}</legend> <legend>{{localize "DAGGERHEART.Sheets.Adversary.FIELDS.description.label" }}</legend>
{{formGroup systemFields.description value=source.system.description}} {{formInput systemFields.description value=source.system.description}}
</fieldset> </fieldset> --}}
<fieldset> <fieldset>
<legend>{{localize "DAGGERHEART.Sheets.Adversary.MotivesAndTactics" }}</legend> <legend>{{localize "DAGGERHEART.Sheets.Adversary.FIELDS.motivesAndTactics.label" }}</legend>
{{formGroup systemFields.motivesAndTactics value=source.system.motivesAndTactics}} {{formInput systemFields.motivesAndTactics value=source.system.motivesAndTactics}}
</fieldset> </fieldset>
</section> </section>

View file

@ -4,6 +4,7 @@
data-group='{{tabs.main.group}}' data-group='{{tabs.main.group}}'
> >
<div class="adversary-container"> <div class="adversary-container">
<button data-action="reactionRoll">Reaction Test</button>
<fieldset class="two-columns even"> <fieldset class="two-columns even">
<legend>{{localize "DAGGERHEART.Sheets.Adversary.General"}}</legend> <legend>{{localize "DAGGERHEART.Sheets.Adversary.General"}}</legend>

View file

@ -13,7 +13,7 @@
<li class="item inventory-item"> <li class="item inventory-item">
<div class="inventory-row" data-item-id="{{item.uuid}}"> <div class="inventory-row" data-item-id="{{item.uuid}}">
<div class="inventory-item-title-container"> <div class="inventory-item-title-container">
<div data-action="viewObject" data-value="{{item.uuid}}" class="inventory-item-title"> <div data-action="useItem" data-value="{{item.uuid}}" class="inventory-item-title">
<img src="{{item.img}}" /> <img src="{{item.img}}" />
{{item.name}} {{item.name}}
</div> </div>

View file

@ -1,6 +1,6 @@
<div> <div>
<header> <header>
{{formField fields.name value=source.name label="Name" name="name" rootId=partId}} {{!-- {{formField fields.name value=source.name label="Name" name="name" rootId=partId}} --}}
<nav class="sheet-tabs tabs"> <nav class="sheet-tabs tabs">
{{#each tabs as |tab|}} {{#each tabs as |tab|}}
<a class="{{tab.cssClass}}" data-action="tab" data-group="{{tab.group}}" data-tab="{{tab.id}}"> <a class="{{tab.cssClass}}" data-action="tab" data-group="{{tab.group}}" data-tab="{{tab.id}}">
@ -11,51 +11,30 @@
</nav> </nav>
</header> </header>
<section> <section>
<div class="tab {{this.tabs.effects.cssClass}}" data-group="primary" data-tab="effects"> <div class="tab {{this.tabs.base.cssClass}}" data-group="primary" data-tab="base">
<fieldset class="action-category"> <fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="damage"> <legend class="action-category-label" data-action="toggleSection" data-section="identity">
<div>Damage</div> <div>Identity</div>
</legend> </legend>
<div class="action-category-data open"> <div class="action-category-data open">
{{formField fields.damage.fields.type value=source.damage.type label="Damage Type" name="damage.type" rootId=partId localize=true}} {{formField fields.name value=source.name label="Name" name="name"}}
{{formField fields.damage.fields.value value=source.damage.value label="Damage" name="damage.value" rootId=partId localize=true}} {{formField fields.img value=source.img label="Icon" name="img"}}
</div> {{formField fields.actionType value=source.actionType label="Type" name="actionType" localize=true}}
</fieldset>
<fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="healing">
<div>Healing</div>
</legend>
<div class="action-category-data open ">
{{formField fields.healing.fields.type value=source.healing.type label="Healing Type" name="healing.type" rootId=partId localize=true}}
{{formField fields.healing.fields.value value=source.healing.value label="Healing" name="healing.value" rootId=partId localize=true}}
</div> </div>
</fieldset> </fieldset>
{{#if fields.roll}}{{> 'systems/daggerheart/templates/views/actionTypes/roll.hbs' fields=fields.roll.fields source=source.roll}}{{/if}}
</div> </div>
<div class="tab {{this.tabs.useage.cssClass}}" data-group="primary" data-tab="useage"> <div class="tab {{this.tabs.config.cssClass}}" data-group="primary" data-tab="config">
<fieldset class="action-category"> {{> 'systems/daggerheart/templates/views/actionTypes/uses.hbs' fields=fields.uses.fields source=source.uses}}
<legend class="action-category-label" data-action="toggleSection" data-section="cost"> {{> 'systems/daggerheart/templates/views/actionTypes/cost.hbs' fields=fields.cost.element.fields source=source.cost}}
<div>Cost</div> {{#if fields.target}}{{> 'systems/daggerheart/templates/views/actionTypes/range-target.hbs' fields=(object range=fields.range target=fields.target.fields) source=(object target=source.target range=source.range)}}{{/if}}
</legend>
<div class="action-category-data open ">
{{formField fields.cost.fields.type value=source.cost.type label="Cost Type" name="cost.type" rootId=partId}}
{{formField fields.cost.fields.value value=source.cost.value label="Value" name="cost.value" rootId=partId}}
</div>
</fieldset>
{{formField fields.target.fields.type value=source.target.type label="Target Type" name="target.type" rootId=partId}}
</div> </div>
<div class="tab {{this.tabs.conditions.cssClass}}" data-group="primary" data-tab="conditions"> <div class="tab {{this.tabs.effect.cssClass}}" data-group="primary" data-tab="effect">
{{!-- <h2> {{#if fields.damage}}{{> 'systems/daggerheart/templates/views/actionTypes/damage.hbs' fields=fields.damage.fields.parts.element.fields source=source.damage}}{{/if}}
{{localize "Conditions"}} {{#if fields.healing}}{{> 'systems/daggerheart/templates/views/actionTypes/healing.hbs' fields=fields.healing.fields source=source.healing}}{{/if}}
<select class="effect-select"> {{#if fields.resource}}{{> 'systems/daggerheart/templates/views/actionTypes/resource.hbs' fields=fields.resource.fields source=source.resource}}{{/if}}
{{selectOptions this.config.effectTypes selected=this.selectedEffectType labelAttr="name" localize=true blank=""}} {{#if fields.documentUUID}}{{> 'systems/daggerheart/templates/views/actionTypes/uuid.hbs' fields=fields.documentUUID source=source.documentUUID}}{{/if}}
</select> {{#if fields.effects}}{{> 'systems/daggerheart/templates/views/actionTypes/effect.hbs'}}{{/if}}
<i class="fa-solid fa-plus icon-button {{#if (not this.selectedEffectType)}}disabled{{/if}}" data-action="addCondition"></i>
</h2> --}}
</div> </div>
</section> </section>
<button type="submit">Save</button>
</div> </div>

View file

@ -0,0 +1,13 @@
<form id="item-action-select">
<ul class="unlist list-select">
{{#each actions}}
<li>
<label>
<dh-icon><img src="{{ img }}"></dh-icon>
<span>{{ name }}</span>
<input type="radio" name="actionId" value="{{_id}}" {{#if (eq @index 0)}}checked{{/if}}>
</label>
</li>
{{/each}}
</ul>
</form>

View file

@ -0,0 +1,13 @@
<form id="action-type-select">
<ul class="unlist list-select">
{{#each types}}
<li>
<label>
<dh-icon class="dh-icon fas {{icon}}"></dh-icon>
<span>{{localize name}}</span>
<input type="radio" name="type" value="{{id}}" {{#if (eq @index 0)}}checked{{/if}}>
</label>
</li>
{{/each}}
</ul>
</form>

View file

@ -0,0 +1,21 @@
<fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="cost">
<div>Cost</div>
</legend>
<div class="action-category-data open" data-key="cost">
<div class="fas fa-plus icon-button" data-action="addElement"></div>
{{#each source as |cost index|}}
<fieldset>
<div class="multi-display">
{{formField ../fields.type label="Resource" value=cost.type name=(concat "cost." index ".type") localize=true}}
{{formField ../fields.value label="Value" value=cost.value name=(concat "cost." index ".value")}}
</div>
<div class="multi-display">
{{formField ../fields.scalable label="Scalable" value=cost.scalable name=(concat "cost." index ".scalable")}}
{{formField ../fields.step label="Step" value=cost.step name=(concat "cost." index ".step")}}
</div>
<div class="fas fa-trash" data-action="removeElement" data-index="{{index}}"></div>
</fieldset>
{{/each}}
</div>
</fieldset>

View file

@ -0,0 +1,36 @@
<fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="effects">
<div>Damage</div>
</legend>
<div class="action-category-data open">
<div class="fas fa-plus icon-button" data-action="addDamage"></div>
{{#if @root.hasBaseDamage}}
<div>
{{!-- <input type="checkbox" data-action="addBaseDamage"{{#if @root.hasBaseDamage}} checked{{/if}}> --}}
{{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase label="Include Item Damage" name="damage.includeBase" }}
</div>
{{/if}}
{{#each source.parts as |dmg index|}}
{{#with (@root.getRealIndex index) as | realIndex |}}
<fieldset{{#if dmg.base}} disabled{{/if}}>
{{#unless dmg.base}}
{{formField ../../fields.custom.fields.enabled value=dmg.custom.enabled name=(concat "damage.parts." realIndex ".custom.enabled")}}
{{/unless}}
{{#if dmg.custom.enabled}}
{{formField ../../fields.custom.fields.formula value=dmg.custom.formula name=(concat "damage.parts." realIndex ".custom.formula") localize=true}}
{{else}}
<div class="multi-display">
{{formField ../../fields.multiplier value=dmg.multiplier name=(concat "damage.parts." realIndex ".multiplier") localize=true}}
{{formField ../../fields.dice value=dmg.dice name=(concat "damage.parts." realIndex ".dice")}}
{{formField ../../fields.bonus value=dmg.bonus name=(concat "damage.parts." realIndex ".bonus") localize=true}}
</div>
{{/if}}
{{formField ../../fields.type value=dmg.type name=(concat "damage.parts." realIndex ".type") localize=true}}
<input type="hidden" name="damage.parts.{{realIndex}}.base" value="{{dmg.base}}">
{{#unless dmg.base}}<div class="fas fa-trash" data-action="removeDamage" data-index="{{realIndex}}"></div>{{/unless}}
</fieldset>
{{/with}}
{{/each}}
</div>
</fieldset>

View file

@ -0,0 +1,19 @@
<fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="effects">
<div>Effects</div>
</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 | }}
<fieldset>
{{!-- <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> --}}
</fieldset>
{{/each}}
</div>
</fieldset>

View file

@ -0,0 +1,21 @@
<fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="effects">
<div>Healing</div>
</legend>
<div class="action-category-data open">
<fieldset>
{{formField fields.type value=source.type name="healing.type" localize=true}}
<div class="multi-display">
{{formField fields.value.fields.custom.fields.enabled value=source.value.custom.enabled name="healing.value.custom.enabled"}}
{{#if source.value.custom.enabled}}
{{formField fields.value.fields.custom.fields.formula value=source.value.custom.formula name="healing.value.custom.formula" localize=true}}
{{else}}
{{formField fields.value.fields.multiplier value=source.value.multiplier name="healing.value.multiplier" localize=true}}
{{formField fields.value.fields.dice value=source.value.dice name="healing.value.dice"}}
{{formField fields.value.fields.bonus value=source.value.bonus name="healing.value.bonus" localize=true}}
{{/if}}
</div>
</fieldset>
</div>
</fieldset>

View file

@ -0,0 +1,13 @@
<fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="range">
<div>Range{{#if fields.target}} & Target{{/if}}</div>
</legend>
<div class="action-category-data open">
{{formField fields.range value=source.range label="Range" name="range" localize=true}}
</div>
{{#if fields.target}}
<div class="action-category-data open">
{{formField fields.target.type value=source.target.type label="Target" name="target.type" localize=true}}
</div>
{{/if}}
</fieldset>

View file

@ -0,0 +1,14 @@
<fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="effects">
<div>Resource</div>
</legend>
<div class="action-category-data open">
<fieldset>
<div class="multi-display">
{{formField fields.type value=source.type name="resource.type" localize=true}}
{{formField fields.value value=source.value name="resource.value"}}
</div>
</fieldset>
</div>
</fieldset>

View file

@ -0,0 +1,10 @@
<fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="roll">
<div>Roll</div>
</legend>
<div class="action-category-data open">
{{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}}
</div>
</fieldset>

View file

@ -0,0 +1,8 @@
<fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="target">
<div>Target</div>
</legend>
<div class="action-category-data open">
{{formField targetField.type label="Target" name="target" rootId=partId localize=true}}
</div>
</fieldset>

View file

@ -0,0 +1,12 @@
<fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="uses">
<div>Uses</div>
</legend>
<div class="action-category-data open">
<div class="multi-display">
{{formField fields.value label="Value" value=source.value name="uses.value" rootId=partId}}
{{formField fields.max label="Max" value=source.max name="uses.max" rootId=partId}}
</div>
{{formField fields.recovery label="Recovery" value=source.recovery name="uses.recovery" rootId=partId localize=true}}
</div>
</fieldset>

View file

@ -0,0 +1,9 @@
<fieldset class="action-category">
<legend class="action-category-label" data-action="toggleSection" data-section="effects">
<div>Macro</div>
</legend>
<div class="action-category-data open">
{{formInput fields value=source name="documentUUID" placeholder=fields.options.placeholder}}
</div>
</fieldset>

View file

@ -12,8 +12,8 @@
{{/each}} {{/each}}
</div> </div>
<div class="flexrow"> <div class="flexrow">
<button class="disadvantage flex1 {{#if this.advantage}}selected{{/if}}" data-action="setAdvantage">Advantage</button> <button class="disadvantage flex1 {{#if this.advantage}}selected{{/if}}" data-action="updateIsAdvantage" data-advantage="true">{{localize "DAGGERHEART.General.Advantage.Full"}}</button>
<button class="disadvantage flex1 {{#if this.disadvantage}}selected{{/if}}" data-action="setDisadvantage">Disadvantage</button> <button class="disadvantage flex1 {{#if (eq this.advantage false)}}selected{{/if}}" data-action="updateIsAdvantage">{{localize "DAGGERHEART.General.Disadvantage.Full"}}</button>
</div> </div>
{{#if (not this.isNpc)}} {{#if (not this.isNpc)}}
<div class="form-group"> <div class="form-group">