Merged with main

This commit is contained in:
WBHarry 2025-06-20 13:06:16 +02:00
commit f80244a773
199 changed files with 8977 additions and 6490 deletions

View file

@ -1,4 +1,4 @@
export { default as DhpPCSheet } from './sheets/pc.mjs';
export { default as DhCharacterSheet } from './sheets/character.mjs';
export { default as DhpAdversarySheet } from './sheets/adversary.mjs';
export { default as DhpClassSheet } from './sheets/items/class.mjs';
export { default as DhpSubclass } from './sheets/items/subclass.mjs';
@ -12,3 +12,4 @@ export { default as DhpWeapon } from './sheets/items/weapon.mjs';
export { default as DhpArmor } from './sheets/items/armor.mjs';
export { default as DhpChatMessage } from './chatMessage.mjs';
export { default as DhpEnvironment } from './sheets/environment.mjs';
export { default as DhActiveEffectConfig } from './sheets/activeEffectConfig.mjs';

View file

@ -0,0 +1,496 @@
import { abilities } from '../config/actorConfig.mjs';
import { burden } from '../config/generalConfig.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DhCharacterCreation extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(character) {
super({});
this.character = character;
this.setup = {
traits: this.character.system.traits,
ancestry: this.character.system.ancestry ?? {},
community: this.character.system.community ?? {},
class: this.character.system.class?.value ?? {},
subclass: this.character.system.class?.subclass ?? {},
experiences: {
[foundry.utils.randomID()]: { description: '', value: 2 },
[foundry.utils.randomID()]: { description: '', value: 2 }
},
domainCards: {
[foundry.utils.randomID()]: {},
[foundry.utils.randomID()]: {}
},
visibility: 1
};
this.equipment = {
armor: {},
primaryWeapon: {},
secondaryWeapon: {},
inventory: {
take: {},
choiceA: {},
choiceB: {}
}
};
this._dragDrop = this._createDragDropHandlers();
}
get title() {
return game.i18n.format('DAGGERHEART.CharacterCreation.Title', { actor: this.character.name });
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dialog', 'dh-style', 'character-creation'],
position: { width: 800, height: 'auto' },
actions: {
viewCompendium: this.viewCompendium,
viewItem: this.viewItem,
useSuggestedTraits: this.useSuggestedTraits,
equipmentChoice: this.equipmentChoice,
finish: this.finish
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
},
dragDrop: [
{ dragSelector: null, dropSelector: '.ancestry-card' },
{ dragSelector: null, dropSelector: '.community-card' },
{ dragSelector: null, dropSelector: '.class-card' },
{ dragSelector: null, dropSelector: '.subclass-card' },
{ dragSelector: null, dropSelector: '.domain-card' },
{ dragSelector: null, dropSelector: '.armor-card' },
{ dragSelector: null, dropSelector: '.primary-weapon-card' },
{ dragSelector: null, dropSelector: '.secondary-weapon-card' },
{ dragSelector: '.suggestion-inner-container', dropSelector: '.selections-container' }
]
};
static PARTS = {
tabs: { template: 'systems/daggerheart/templates/views/characterCreation/tabs.hbs' },
setup: { template: 'systems/daggerheart/templates/views/characterCreation/tabs/setup.hbs' },
equipment: { template: 'systems/daggerheart/templates/views/characterCreation/tabs/equipment.hbs' },
// story: { template: 'systems/daggerheart/templates/views/characterCreation/tabs/story.hbs' },
footer: { template: 'systems/daggerheart/templates/views/characterCreation/footer.hbs' }
};
static TABS = {
setup: {
active: true,
cssClass: '',
group: 'primary',
id: 'setup',
label: 'DAGGERHEART.CharacterCreation.Tabs.Setup'
},
equipment: {
active: false,
cssClass: '',
group: 'primary',
id: 'equipment',
label: 'DAGGERHEART.CharacterCreation.Tabs.Equipment',
optional: true
}
// story: {
// active: false,
// cssClass: '',
// group: 'primary',
// id: 'story',
// label: 'DAGGERHEART.CharacterCreation.Tabs.Story',
// optional: true
// }
};
_getTabs(tabs) {
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
v.cssClass = v.active ? 'active' : '';
switch (v.id) {
case 'setup':
const classFinished = this.setup.class.uuid && this.setup.subclass.uuid;
const heritageFinished = this.setup.ancestry.uuid && this.setup.community.uuid;
const traitsFinished = Object.values(this.setup.traits).every(x => x.value !== null);
const experiencesFinished = Object.values(this.setup.experiences).every(x => x.description);
const domainCardsFinished = Object.values(this.setup.domainCards).every(x => x.uuid);
v.finished =
classFinished &&
heritageFinished &&
traitsFinished &&
experiencesFinished &&
domainCardsFinished;
break;
case 'equipment':
const armorFinished = this.equipment.armor?.uuid;
const primaryFinished = this.equipment.primaryWeapon?.uuid;
const secondaryFinished =
this.equipment.secondaryWeapon?.uuid ||
(primaryFinished && this.equipment.primaryWeapon.system.burden == burden.twoHanded.value);
const choiceAFinished = this.equipment.inventory.choiceA?.uuid;
const choiceBFinished = this.equipment.inventory.choiceB?.uuid;
v.finished =
armorFinished && primaryFinished && secondaryFinished && choiceAFinished && choiceBFinished;
}
}
tabs.equipment.cssClass = tabs.setup.finished ? tabs.equipment.cssClass : 'disabled';
// tabs.story.cssClass = tabs.setup.finished ? tabs.story.cssClass : 'disabled';
return tabs;
}
changeTab(tab, group, options) {
super.changeTab(tab, group, options);
for (var listTab of Object.keys(this.constructor.TABS)) {
const marker = options.navElement.querySelector(`a[data-action="tab"].${listTab} .finish-marker`);
if (listTab === tab) {
marker.classList.add('active');
} else {
marker.classList.remove('active');
}
}
}
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
this._dragDrop.forEach(d => d.bind(htmlElement));
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.tabs = this._getTabs(this.constructor.TABS);
return context;
}
async _preparePartContext(partId, context) {
switch (partId) {
case 'setup':
const availableTraitModifiers = game.settings
.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Homebrew)
.traitArray.map(trait => ({ key: trait, name: trait }));
for (let trait of Object.values(this.setup.traits).filter(x => x.value !== null)) {
const index = availableTraitModifiers.findIndex(x => x.key === trait.value);
if (index !== -1) {
availableTraitModifiers.splice(index, 1);
}
}
context.suggestedTraits = this.setup.class.system
? Object.keys(this.setup.class.system.characterGuide.suggestedTraits).map(traitKey => {
const trait = this.setup.class.system.characterGuide.suggestedTraits[traitKey];
return `${game.i18n.localize(`DAGGERHEART.Abilities.${traitKey}.short`)} ${trait > 0 ? `+${trait}` : trait}`;
})
: [];
context.traits = {
values: Object.keys(this.setup.traits).map(traitKey => {
const trait = this.setup.traits[traitKey];
const options = [...availableTraitModifiers];
if (trait.value !== null && !options.some(x => x.key === trait.value))
options.push({ key: trait.value, name: trait.value });
return {
...trait,
key: traitKey,
name: game.i18n.localize(abilities[traitKey].label),
options: options
};
})
};
context.traits.nrTotal = Object.keys(context.traits.values).length;
context.traits.nrSelected = Object.values(context.traits.values).reduce(
(acc, trait) => acc + (trait.value !== null ? 1 : 0),
0
);
context.experience = {
values: this.setup.experiences,
nrTotal: Object.keys(this.setup.experiences).length,
nrSelected: Object.values(this.setup.experiences).reduce(
(acc, exp) => acc + (exp.description ? 1 : 0),
0
)
};
context.ancestry = { ...this.setup.ancestry, compendium: 'ancestries' };
context.community = { ...this.setup.community, compendium: 'communities' };
context.class = { ...this.setup.class, compendium: 'classes' };
context.subclass = { ...this.setup.subclass, compendium: 'subclasses' };
context.domainCards = Object.keys(this.setup.domainCards).reduce((acc, x) => {
acc[x] = { ...this.setup.domainCards[x], compendium: 'domains' };
return acc;
}, {});
context.visibility = this.setup.visibility;
break;
case 'equipment':
const suggestions = await this.getEquipmentSuggestions(
this.equipment.inventory.choiceA,
this.equipment.inventory.choiceB
);
context.armor = {
...this.equipment.armor,
suggestion: { ...suggestions.armor, taken: suggestions.armor?.uuid === this.equipment.armor?.uuid },
compendium: 'armors'
};
context.primaryWeapon = {
...this.equipment.primaryWeapon,
suggestion: {
...suggestions.primaryWeapon,
taken: suggestions.primaryWeapon?.uuid === this.equipment.primaryWeapon?.uuid
},
compendium: 'weapons'
};
context.secondaryWeapon = {
...this.equipment.secondaryWeapon,
suggestion: {
...suggestions.secondaryWeapon,
taken: suggestions.secondaryWeapon?.uuid === this.equipment.secondaryWeapon?.uuid
},
disabled: this.equipment.primaryWeapon?.system?.burden === burden.twoHanded.value,
compendium: 'weapons'
};
context.inventory = {
take: suggestions.inventory.take,
choiceA: { suggestions: suggestions.inventory.choiceA, compendium: 'consumables' },
choiceB: { suggestions: suggestions.inventory.choiceB, compendium: 'general-items' }
};
break;
}
return context;
}
static async updateForm(event, _, formData) {
this.setup = foundry.utils.mergeObject(this.setup, formData.object);
this.setup.visibility = this.getUpdateVisibility();
this.render();
}
getUpdateVisibility() {
switch (this.setup.visibility) {
case 5:
return 5;
case 4:
return Object.values(this.setup.experiences).every(x => x.description) ? 5 : 4;
case 3:
return Object.values(this.setup.traits).every(x => x.value !== null) ? 4 : 3;
case 2:
return this.setup.ancestry.uuid && this.setup.community.uuid ? 3 : 2;
case 1:
return this.setup.class.uuid && this.setup.subclass.uuid ? 2 : 1;
}
}
async getEquipmentSuggestions(choiceA, choiceB) {
if (!this.setup.class.uuid) return { inventory: { take: [] } };
const { inventory, characterGuide } = this.setup.class.system;
return {
armor: characterGuide.suggestedArmor ?? null,
primaryWeapon: characterGuide.suggestedPrimaryWeapon ?? null,
secondaryWeapon:
{ ...characterGuide.suggestedSecondaryWeapon, uuid: characterGuide.suggestedSecondaryWeapon.uuid } ??
null,
inventory: {
take: inventory.take ?? [],
choiceA:
inventory.choiceA?.map(x => ({ ...x, uuid: x.uuid, selected: x.uuid === choiceA?.uuid })) ?? [],
choiceB: inventory.choiceB?.map(x => ({ ...x, uuid: x.uuid, selected: x.uuid === choiceB?.uuid })) ?? []
}
};
}
_createDragDropHandlers() {
return this.options.dragDrop.map(d => {
d.callbacks = {
dragstart: this._onDragStart.bind(this),
drop: this._onDrop.bind(this)
};
return new foundry.applications.ux.DragDrop.implementation(d);
});
}
static async viewCompendium(_, target) {
(await game.packs.get(`daggerheart.${target.dataset.compendium}`))?.render(true);
}
static async viewItem(_, target) {
(await foundry.utils.fromUuid(target.dataset.uuid)).sheet.render(true);
}
static useSuggestedTraits() {
this.setup.traits = Object.keys(this.setup.traits).reduce((acc, traitKey) => {
acc[traitKey] = {
...this.setup.traits[traitKey],
value: this.setup.class.system.characterGuide.suggestedTraits[traitKey]
};
return acc;
}, {});
this.setup.visibility = this.getUpdateVisibility();
this.render();
}
static async equipmentChoice(_, target) {
this.equipment.inventory[target.dataset.path] = await foundry.utils.fromUuid(target.dataset.uuid);
this.render();
}
static async finish() {
const embeddedAncestries = await this.character.createEmbeddedDocuments('Item', [this.setup.ancestry]);
const embeddedCommunities = await this.character.createEmbeddedDocuments('Item', [this.setup.community]);
await this.character.createEmbeddedDocuments('Item', [this.setup.class]);
await this.character.createEmbeddedDocuments('Item', [this.setup.subclass]);
await this.character.createEmbeddedDocuments('Item', Object.values(this.setup.domainCards));
if (this.equipment.armor.uuid)
await this.character.createEmbeddedDocuments('Item', [
{ ...this.equipment.armor, system: { ...this.equipment.armor.system, equipped: true } }
]);
if (this.equipment.primaryWeapon.uuid)
await this.character.createEmbeddedDocuments('Item', [
{ ...this.equipment.primaryWeapon, system: { ...this.equipment.primaryWeapon.system, equipped: true } }
]);
if (this.equipment.secondaryWeapon.uuid)
await this.character.createEmbeddedDocuments('Item', [
{
...this.equipment.secondaryWeapon,
system: { ...this.equipment.secondaryWeapon.system, equipped: true }
}
]);
if (this.equipment.inventory.choiceA.uuid)
await this.character.createEmbeddedDocuments('Item', [this.equipment.inventory.choiceA]);
if (this.equipment.inventory.choiceB.uuid)
await this.character.createEmbeddedDocuments('Item', [this.equipment.inventory.choiceB]);
await this.character.createEmbeddedDocuments('Item', this.setup.class.system.inventory.take);
await this.character.update({
system: {
traits: this.setup.traits,
experiences: this.setup.experiences,
ancestry: embeddedAncestries[0].uuid,
community: embeddedCommunities[0].uuid
}
});
this.close();
}
async _onDragStart(event) {
const target = event.currentTarget;
event.dataTransfer.setData('text/plain', JSON.stringify(target.dataset));
event.dataTransfer.setDragImage(target, 60, 0);
}
async _onDrop(event) {
const data = TextEditor.getDragEventData(event);
const item = await foundry.utils.fromUuid(data.uuid);
if (item.type === 'ancestry' && event.target.closest('.ancestry-card')) {
this.setup.ancestry = { ...item, uuid: item.uuid };
} else if (item.type === 'community' && event.target.closest('.community-card')) {
this.setup.community = { ...item, uuid: item.uuid };
} else if (item.type === 'class' && event.target.closest('.class-card')) {
this.setup.class = { ...item, uuid: item.uuid };
this.setup.subclass = {};
this.setup.domainCards = {
[foundry.utils.randomID()]: {},
[foundry.utils.randomID()]: {}
};
} else if (item.type === 'subclass' && event.target.closest('.subclass-card')) {
if (this.setup.class.system.subclasses.every(subclass => subclass.uuid !== item.uuid)) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.SubclassNotInClass')
);
return;
}
this.setup.subclass = { ...item, uuid: item.uuid };
} else if (item.type === 'domainCard' && event.target.closest('.domain-card')) {
if (!this.setup.class.uuid) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.MissingClass'));
return;
}
if (!this.setup.class.system.domains.includes(item.system.domain)) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.WrongDomain'));
return;
}
if (item.system.level > 1) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.CardTooHighLevel')
);
return;
}
if (Object.values(this.setup.domainCards).some(card => card.uuid === item.uuid)) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.DuplicateCard'));
return;
}
this.setup.domainCards[event.target.closest('.domain-card').dataset.card] = { ...item, uuid: item.uuid };
} else if (item.type === 'armor' && event.target.closest('.armor-card')) {
if (item.system.tier > 1) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.ItemTooHighTier')
);
return;
}
this.equipment.armor = { ...item, uuid: item.uuid };
} else if (item.type === 'weapon' && event.target.closest('.primary-weapon-card')) {
if (item.system.secondary) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.NotPrimary'));
return;
}
if (item.system.tier > 1) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.ItemTooHighTier')
);
return;
}
this.equipment.primaryWeapon = { ...item, uuid: item.uuid };
} else if (item.type === 'weapon' && event.target.closest('.secondary-weapon-card')) {
if (this.equipment.primaryWeapon?.system?.burden === burden.twoHanded.value) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.PrimaryIsTwoHanded')
);
return;
}
if (!item.system.secondary) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.NotSecondary'));
return;
}
if (item.system.tier > 1) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.CharacterCreation.Notifications.ItemTooHighTier')
);
return;
}
this.equipment.secondaryWeapon = { ...item, uuid: item.uuid };
} else {
return;
}
this.setup.visibility = this.getUpdateVisibility();
this.render();
}
}

View file

@ -1,24 +1,20 @@
import DhpDualityRoll from '../data/dualityRoll.mjs';
import { DualityRollColor } from '../data/settings/Appearance.mjs';
import DHDualityRoll from '../data/chat-message/dualityRoll.mjs';
export default class DhpChatMessage extends ChatMessage {
export default class DhpChatMessage extends foundry.documents.ChatMessage {
async renderHTML() {
if (this.type === 'dualityRoll' || this.type === 'adversaryRoll' || this.type === 'abilityUse') {
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. */
const html = await super.renderHTML();
if (
this.type === 'dualityRoll' &&
game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).dualityColorScheme ===
DualityRollColor.colorful.value
) {
if (this.type === 'dualityRoll') {
html.classList.add('duality');
const dualityResult = this.system.dualityResult;
if (dualityResult === DhpDualityRoll.dualityResult.hope) html.classList.add('hope');
else if (dualityResult === DhpDualityRoll.dualityResult.fear) html.classList.add('fear');
if (dualityResult === DHDualityRoll.dualityResult.hope) html.classList.add('hope');
else if (dualityResult === DHDualityRoll.dualityResult.fear) html.classList.add('fear');
else html.classList.add('critical');
}

View file

@ -1,7 +1,7 @@
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
const { ApplicationV2 } = foundry.applications.api;
export default class DaggerheartActionConfig extends DaggerheartSheet(ApplicationV2) {
export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
constructor(action) {
super({});
@ -9,21 +9,25 @@ export default class DaggerheartActionConfig extends DaggerheartSheet(Applicatio
this.openSection = null;
}
// get title(){
// return `Action - ${this.action.name}`;
// }
static DEFAULT_OPTIONS = {
tag: 'form',
id: 'daggerheart-action',
classes: ['daggerheart', 'views', 'action'],
position: { width: 600, height: 'auto' },
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: {
handler: this.updateForm,
closeOnSubmit: true
submitOnChange: true,
closeOnSubmit: false
}
};
@ -36,16 +40,9 @@ export default class DaggerheartActionConfig extends DaggerheartSheet(Applicatio
_getTabs() {
const tabs = {
effects: { active: true, cssClass: '', group: 'primary', id: 'effects', icon: null, label: 'Effects' },
useage: { active: false, cssClass: '', group: 'primary', id: 'useage', icon: null, label: 'Useage' },
conditions: {
active: false,
cssClass: '',
group: 'primary',
id: 'conditions',
icon: null,
label: 'Conditions'
}
base: { active: true, cssClass: '', group: 'primary', id: 'base', icon: null, label: 'Base' },
config: { active: false, cssClass: '', group: 'primary', id: 'config', icon: null, label: 'Configuration' },
effect: { active: false, cssClass: '', group: 'primary', id: 'effect', icon: null, label: 'Effect' }
};
for (const v of Object.values(tabs)) {
@ -58,9 +55,13 @@ export default class DaggerheartActionConfig extends DaggerheartSheet(Applicatio
async _prepareContext(_options) {
const context = await super._prepareContext(_options, 'action');
context.source = this.action.toObject(false);
context.openSection = this.openSection;
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;
}
@ -69,15 +70,91 @@ export default class DaggerheartActionConfig extends DaggerheartSheet(Applicatio
this.render(true);
}
static async updateForm(event, _, formData) {
const data = foundry.utils.expandObject(
foundry.utils.mergeObject(this.action.toObject(), foundry.utils.expandObject(formData.object))
);
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 });
getRealIndex(index) {
const data = this.action.toObject(false);
return data.damage.parts.find(d => d.base) ? index - 1 : index;
}
_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 = foundry.utils.getProperty(this.action.parent, this.action.systemPath).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.${this.action.systemPath}`]: newActions });
if (!updates) return;
this.action = foundry.utils.getProperty(updates.system, this.action.systemPath)[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

@ -1,4 +1,4 @@
import { abilities } from '../config/actorConfig.mjs';
import { abilities, subclassFeatureLabels } from '../config/actorConfig.mjs';
import { domains } from '../config/domainConfig.mjs';
import { DhLevelup } from '../data/levelup.mjs';
import { getDeleteKeys, tagifyElement } from '../helpers/utils.mjs';
@ -35,6 +35,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
viewCompendium: this.viewCompendium,
selectPreview: this.selectPreview,
selectDomain: this.selectDomain,
selectSubclass: this.selectSubclass,
updateCurrentLevel: this.updateCurrentLevel,
activatePart: this.activatePart
},
@ -149,7 +150,10 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
const experienceIncreaseValues = experienceIncreases
.filter(exp => exp.data.length > 0)
.flatMap(exp =>
exp.data.map(data => this.actor.system.experiences.find(x => x.id === data).description)
exp.data.map(data => {
const experience = Object.keys(this.actor.system.experiences).find(x => x === data);
return this.actor.system.experiences[experience].description;
})
);
context.experienceIncreases = {
values: experienceIncreaseValues,
@ -175,6 +179,20 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
};
const allDomainCardKeys = Object.keys(allDomainCards);
const classDomainsData = this.actor.system.class.value.system.domains.map(domain => ({
domain,
multiclass: false
}));
const multiclassDomainsData = (this.actor.system.multiclass?.value?.system?.domains ?? []).map(
domain => ({ domain, multiclass: true })
);
const domainsData = [...classDomainsData, ...multiclassDomainsData];
const multiclassDomain = this.levelup.classUpgradeChoices?.multiclass?.domain;
if (multiclassDomain) {
if (!domainsData.some(x => x.domain === multiclassDomain))
domainsData.push({ domain: multiclassDomain, multiclass: true });
}
context.domainCards = [];
for (var key of allDomainCardKeys) {
const domainCard = allDomainCards[key];
@ -185,35 +203,56 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
context.domainCards.push({
...(card.toObject?.() ?? card),
emptySubtext: game.i18n.format(
'DAGGERHEART.Application.LevelUp.Selections.emptyDomainCardHint',
{ level: domainCard.level }
),
emptySubtexts: domainsData.map(domain => {
const levelBase = domain.multiclass
? Math.ceil(this.levelup.currentLevel / 2)
: this.levelup.currentLevel;
const levelMax = domainCard.secondaryData?.limit
? Math.min(domainCard.secondaryData.limit, levelBase)
: levelBase;
return game.i18n.format('DAGGERHEART.Application.LevelUp.Selections.emptyDomainCardHint', {
domain: game.i18n.localize(domains[domain.domain].label),
level: levelMax
});
}),
path: domainCard.data
? `${domainCard.path}.data`
: `levels.${domainCard.level}.achievements.domainCards.${key}.uuid`,
limit: domainCard.level,
limit: domainCard.secondaryData?.limit ?? null,
compendium: 'domains'
});
}
const subclassSelections = advancementChoices.subclass?.flatMap(x => x.data) ?? [];
const possibleSubclasses = [this.actor.system.class.subclass];
if (this.actor.system.multiclass?.subclass) {
possibleSubclasses.push(this.actor.system.multiclass.subclass);
}
const multiclassSubclass = this.actor.system.multiclass?.system?.subclasses?.[0];
const possibleSubclasses = [
this.actor.system.subclass,
...(multiclassSubclass ? [multiclassSubclass] : [])
];
const selectedSubclasses = possibleSubclasses.filter(x => subclassSelections.includes(x.uuid));
context.subclassCards = [];
if (advancementChoices.subclass?.length > 0) {
const featureStateIncrease = Object.values(this.levelup.levels).reduce((acc, level) => {
acc += Object.values(level.choices).filter(choice => {
return Object.values(choice).every(checkbox => checkbox.type === 'subclass');
}).length;
return acc;
}, 0);
for (var subclass of possibleSubclasses) {
const choice =
advancementChoices.subclass.find(x => x.data[0] === subclass.uuid) ??
advancementChoices.subclass.find(x => x.data.length === 0);
const featureState = subclass.system.featureState + featureStateIncrease;
const data = await foundry.utils.fromUuid(subclass.uuid);
const selected = selectedSubclasses.some(x => x.uuid === data.uuid);
context.subclassCards.push({
...data.toObject(),
path: choice?.path,
uuid: data.uuid,
selected: selected
selected: subclassSelections.includes(subclass.uuid),
featureState: featureState,
featureLabel: game.i18n.localize(subclassFeatureLabels[featureState]),
isMulticlass: subclass.system.isMulticlass ? 'true' : 'false'
});
}
}
@ -230,14 +269,23 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
domains:
multiclass?.system?.domains.map(key => {
const domain = domains[key];
const alreadySelected = this.actor.system.class.system.domains.includes(key);
const alreadySelected = this.actor.system.class.value.system.domains.includes(key);
return {
...domain,
selected: key === data.secondaryData,
disabled: (data.secondaryData && key !== data.secondaryData) || alreadySelected
selected: key === data.secondaryData.domain,
disabled:
(data.secondaryData.domain && key !== data.secondaryData.domain) ||
alreadySelected
};
}) ?? [],
subclasses:
multiclass?.system?.subclasses.map(subclass => ({
...subclass,
uuid: subclass.uuid,
selected: data.secondaryData.subclass === subclass.uuid,
disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid
})) ?? [],
compendium: 'classes',
limit: 1
};
@ -274,8 +322,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
context.achievements = {
proficiency: {
old: this.actor.system.proficiency.value,
new: this.actor.system.proficiency.value + achivementProficiency,
old: this.actor.system.proficiency.total,
new: this.actor.system.proficiency.total + achivementProficiency,
shown: achivementProficiency > 0
},
damageThresholds: {
@ -319,6 +367,13 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
? advancement[choiceKey] + Number(checkbox.value)
: Number(checkbox.value);
break;
case 'trait':
if (!advancement[choiceKey]) advancement[choiceKey] = {};
for (var traitKey of checkbox.data) {
if (!advancement[choiceKey][traitKey]) advancement[choiceKey][traitKey] = 0;
advancement[choiceKey][traitKey] += 1;
}
break;
case 'domainCard':
if (!advancement[choiceKey]) advancement[choiceKey] = [];
if (checkbox.data.length === 1) {
@ -328,12 +383,41 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
break;
case 'experience':
if (!advancement[choiceKey]) advancement[choiceKey] = [];
const data = checkbox.data.map(
data =>
this.actor.system.experiences.find(x => x.id === data)?.description ?? ''
);
const data = checkbox.data.map(data => {
const experience = Object.keys(this.actor.system.experiences).find(
x => x === data
);
return this.actor.system.experiences[experience]?.description ?? '';
});
advancement[choiceKey].push({ data: data, value: checkbox.value });
break;
case 'subclass':
if (checkbox.data[0]) {
const subclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
if (!advancement[choiceKey]) advancement[choiceKey] = [];
advancement[choiceKey].push({
...subclassItem.toObject(),
featureLabel: game.i18n.localize(
subclassFeatureLabels[Number(checkbox.secondaryData.featureState)]
)
});
}
break;
case 'multiclass':
const multiclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
const subclass = multiclassItem
? await foundry.utils.fromUuid(checkbox.secondaryData.subclass)
: null;
advancement[choiceKey] = multiclassItem
? {
...multiclassItem.toObject(),
domain: checkbox.secondaryData.domain
? game.i18n.localize(domains[checkbox.secondaryData.domain].label)
: null,
subclass: subclass ? subclass.name : null
}
: {};
break;
}
}
}
@ -346,26 +430,35 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
},
hitPoints: {
old: this.actor.system.resources.hitPoints.max,
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
old: this.actor.system.resources.hitPoints.maxTotal,
new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0)
},
stress: {
old: this.actor.system.resources.stress.max,
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
old: this.actor.system.resources.stress.maxTotal,
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
},
evasion: {
old: this.actor.system.evasion.value,
new: this.actor.system.evasion.value + (advancement.evasion ?? 0)
old: this.actor.system.evasion.total,
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
}
},
traits:
advancement.trait?.flatMap(x =>
x.data.map(data => game.i18n.localize(abilities[data].label))
) ?? [],
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
if (advancement.trait?.[traitKey]) {
if (!acc) acc = {};
acc[traitKey] = {
label: game.i18n.localize(abilities[traitKey].label),
old: this.actor.system.traits[traitKey].total,
new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey]
};
}
return acc;
}, null),
domainCards: advancement.domainCard ?? [],
experiences:
advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ??
[]
[],
multiclass: advancement.multiclass,
subclass: advancement.subclass
};
context.advancements.statistics.proficiency.shown =
@ -414,15 +507,16 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
const traitsTagify = htmlElement.querySelector('.levelup-trait-increases');
if (traitsTagify) {
tagifyElement(traitsTagify, abilities, this.tagifyUpdate('trait').bind(this));
tagifyElement(traitsTagify, this.levelup.unmarkedTraits, this.tagifyUpdate('trait').bind(this));
}
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
if (experienceIncreaseTagify) {
tagifyElement(
experienceIncreaseTagify,
this.actor.system.experiences.reduce((acc, experience) => {
acc[experience.id] = { label: experience.description };
Object.keys(this.actor.system.experiences).reduce((acc, id) => {
const experience = this.actor.system.experiences[id];
acc[id] = { label: experience.description };
return acc;
}, {}),
@ -479,8 +573,10 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
if (event.target.closest('.domain-cards')) {
const target = event.target.closest('.card-preview-container');
if (item.type === 'domainCard') {
const { multiclass } = this.levelup.classUpgradeChoices;
const isMulticlass = !multiclass ? false : item.system.domain === multiclass.domain;
if (
!this.actor.system.class.system.domains.includes(item.system.domain) &&
!this.actor.system.domains.includes(item.system.domain) &&
this.levelup.classUpgradeChoices?.multiclass?.domain !== item.system.domain
) {
ui.notifications.error(
@ -489,7 +585,9 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
return;
}
if (item.system.level > Number(target.dataset.limit)) {
const levelBase = isMulticlass ? Math.ceil(this.levelup.currentLevel / 2) : this.levelup.currentLevel;
const levelMax = target.dataset.limit ? Math.min(Number(target.dataset.limit), levelBase) : levelBase;
if (levelMax < item.system.level) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.domainCardToHighLevel')
);
@ -522,7 +620,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
} else if (event.target.closest('.multiclass-cards')) {
const target = event.target.closest('.multiclass-cards');
if (item.type === 'class') {
if (item.name === this.actor.system.class.name) {
if (item.name === this.actor.system.class.value.name) {
ui.notifications.error(
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.alreadySelectedClass')
);
@ -541,8 +639,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
amount: target.dataset.amount ? Number(target.dataset.amount) : null,
value: target.dataset.value,
type: target.dataset.type,
data: item.uuid,
secondaryData: null
data: item.uuid
}
});
this.render();
@ -556,16 +653,16 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
const update = {};
if (!button.checked) {
if (button.dataset.cost > 1) {
const basePath = `levels.${this.levelup.currentLevel}.choices`;
const current = foundry.utils.getProperty(this.levelup, `${basePath}.${button.dataset.option}`);
if (Number(button.dataset.cost) > 1 || Object.keys(current).length === 1) {
// Simple handling that doesn't cover potential Custom LevelTiers.
update[`levels.${this.levelup.currentLevel}.choices.-=${button.dataset.option}`] = null;
update[`${basePath}.-=${button.dataset.option}`] = null;
} else {
update[
`levels.${this.levelup.currentLevel}.choices.${button.dataset.option}.-=${button.dataset.checkboxNr}`
] = null;
update[`${basePath}.${button.dataset.option}.-=${button.dataset.checkboxNr}`] = null;
}
} else {
if (!this.levelup.levels[this.levelup.currentLevel].nrSelections.available) {
if (this.levelup.levels[this.levelup.currentLevel].nrSelections.available < Number(button.dataset.cost)) {
ui.notifications.info(
game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.info.insufficentAdvancements')
);
@ -573,15 +670,23 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
return;
}
update[
`levels.${this.levelup.currentLevel}.choices.${button.dataset.option}.${button.dataset.checkboxNr}`
] = {
const updateData = {
tier: Number(button.dataset.tier),
minCost: Number(button.dataset.cost),
amount: button.dataset.amount ? Number(button.dataset.amount) : null,
value: button.dataset.value,
type: button.dataset.type
};
if (button.dataset.type === 'domainCard') {
updateData.secondaryData = {
limit: Math.max(...this.levelup.tiers[button.dataset.tier].belongingLevels)
};
}
update[
`levels.${this.levelup.currentLevel}.choices.${button.dataset.option}.${button.dataset.checkboxNr}`
] = updateData;
}
await this.levelup.updateSource(update);
@ -594,24 +699,35 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
static async selectPreview(_, button) {
const remove = button.dataset.selected;
const selectionData = Object.values(this.levelup.selectionData);
const option = remove
? selectionData.find(x => x.type === 'subclass' && x.data.includes(button.dataset.uuid))
: selectionData.find(x => x.type === 'subclass' && x.data.length === 0);
if (!option) return;
await this.levelup.updateSource({
[`${button.dataset.path}`]: {
data: remove ? [] : [button.dataset.uuid],
secondaryData: {
featureState: button.dataset.featureState,
isMulticlass: button.dataset.isMulticlass
}
}
});
const path = `tiers.${option.tier}.levels.${option.level}.optionSelections.${option.optionKey}.${option.checkboxNr}.data`;
await this.levelup.updateSource({ [path]: remove ? [] : button.dataset.uuid });
this.render();
}
static async selectDomain(_, button) {
const option = foundry.utils.getProperty(this.levelup, button.dataset.path);
const domain = option.secondaryData ? null : button.dataset.domain;
const domain = option.secondaryData.domain ? null : button.dataset.domain;
await this.levelup.updateSource({
multiclass: { domain },
[`${button.dataset.path}.secondaryData`]: domain
[`${button.dataset.path}.secondaryData.domain`]: domain
});
this.render();
}
static async selectSubclass(_, button) {
const option = foundry.utils.getProperty(this.levelup, button.dataset.path);
const subclass = option.secondaryData.subclass ? null : button.dataset.subclass;
await this.levelup.updateSource({
[`${button.dataset.path}.secondaryData.subclass`]: subclass
});
this.render();
}

View file

@ -1,113 +0,0 @@
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DhpMulticlassDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(actorName, actorClass, resolve) {
super({});
this.actorName = actorName;
this.actorClass = actorClass;
this.resolve = resolve;
this.classChoices = Array.from(
game.items.reduce((acc, x) => {
if (x.type === 'class' && x.name !== actorClass.name) {
acc.add(x);
}
return acc;
}, new Set())
);
this.subclassChoices = [];
this.domainChoices = [];
this.data = {
class: null,
subclass: null,
domain: null
};
}
get title() {
return `${this.actorName} - Multiclass`;
}
static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'views', 'multiclass'],
position: { width: 600, height: 'auto' },
actions: {
selectClass: this.selectClass,
selectSubclass: this.selectSubclass,
selectDomain: this.selectDomain,
finish: this.finish
}
};
static PARTS = {
form: {
id: 'levelup',
template: 'systems/daggerheart/templates/views/multiclass.hbs'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.classChoices = this.classChoices;
context.subclassChoices = this.subclassChoices;
context.domainChoices = this.domainChoices;
context.disabledFinish = !this.data.class || !this.data.subclass || !this.data.domain;
context.data = this.data;
return context;
}
static async selectClass(_, button) {
const oldClass = this.data.class;
this.data.class = this.data.class?.uuid === button.dataset.class ? null : await fromUuid(button.dataset.class);
if (oldClass !== button.dataset.class) {
this.data.subclass = null;
this.data.domain = null;
this.subclassChoices = this.data.class ? this.data.class.system.subclasses : [];
this.domainChoices = this.data.class
? this.data.class.system.domains.map(x => {
const config = SYSTEM.DOMAIN.domains[x];
return {
name: game.i18n.localize(config.name),
id: config.id,
img: config.src,
disabled: this.actorClass.system.domains.includes(config.id)
};
})
: [];
}
this.render(true);
}
static async selectSubclass(_, button) {
this.data.subclass =
this.data.subclass?.uuid === button.dataset.subclass
? null
: this.subclassChoices.find(x => x.uuid === button.dataset.subclass);
this.render(true);
}
static async selectDomain(_, button) {
const domain =
this.data.domain?.id === button.dataset.domain
? null
: this.domainChoices.find(x => x.id === button.dataset.domain);
if (domain?.disabled) return;
this.data.domain = domain;
this.render(true);
}
static finish() {
this.close({}, this.data);
}
async close(options = {}, data = null) {
this.resolve(data);
super.close(options);
}
}

View file

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

View file

@ -50,7 +50,7 @@ export default class Resources extends HandlebarsApplicationMixin(ApplicationV2)
}
get maxFear() {
return game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.MaxFear);
return game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Homebrew).maxFear;
}
/* -------------------------------------------- */
@ -59,7 +59,7 @@ export default class Resources extends HandlebarsApplicationMixin(ApplicationV2)
/** @override */
async _prepareContext(_options) {
const display = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.DisplayFear),
const display = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).displayFear,
current = this.currentFear,
max = this.maxFear,
percent = (current / max) * 100,

View file

@ -1,7 +1,7 @@
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
export default class RollSelectionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(experiences, bonusDamage, hopeResource, resolve, isNpc) {
constructor(experiences, hopeResource, resolve) {
super({}, {});
this.experiences = experiences;
@ -16,35 +16,21 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl
hope: ['d12'],
fear: ['d12'],
advantage: null,
disadvantage: null,
bonusDamage: bonusDamage.reduce((acc, x) => {
if (x.appliesOn === SYSTEM.EFFECTS.applyLocations.attackRoll.id) {
acc.push({
...x,
hopeUses: 0
});
}
return acc;
}, []),
hopeResource: hopeResource
};
}
static DEFAULT_OPTIONS = {
tag: 'form',
id: 'roll-selection', //Having an id causes a new instance to overwrite previous.
id: 'roll-selection',
classes: ['daggerheart', 'views', 'roll-selection'],
position: {
width: 400,
height: 'auto'
},
actions: {
updateIsAdvantage: this.updateIsAdvantage,
selectExperience: this.selectExperience,
decreaseHopeUse: this.decreaseHopeUse,
increaseHopeUse: this.increaseHopeUse,
setAdvantage: this.setAdvantage,
setDisadvantage: this.setDisadvantage,
finish: this.finish
},
form: {
@ -73,28 +59,14 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl
context.hope = this.data.hope;
context.fear = this.data.fear;
context.advantage = this.data.advantage;
context.disadvantage = this.data.disadvantage;
context.experiences = this.experiences.map(x => ({
...x,
selected: this.selectedExperiences.find(selected => selected.id === x.id)
}));
context.bonusDamage = this.data.bonusDamage;
context.experiences = Object.keys(this.experiences).map(id => ({ id, ...this.experiences[id] }));
context.hopeResource = this.data.hopeResource + 1;
context.hopeUsed = this.getHopeUsed();
return context;
}
static updateSelection(event, _, formData) {
const { bonusDamage, ...rest } = foundry.utils.expandObject(formData.object);
for (var index in bonusDamage) {
this.data.bonusDamage[index].initiallySelected = bonusDamage[index].initiallySelected;
if (bonusDamage[index].hopeUses) {
const value = Number.parseInt(bonusDamage[index].hopeUses);
if (!Number.isNaN(value)) this.data.bonusDamage[index].hopeUses = value;
}
}
const { ...rest } = foundry.utils.expandObject(formData.object);
this.data = foundry.utils.mergeObject(this.data, rest);
this.render();
@ -104,56 +76,24 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl
if (this.selectedExperiences.find(x => x.id === button.dataset.key)) {
this.selectedExperiences = this.selectedExperiences.filter(x => x.id !== button.dataset.key);
} else {
this.selectedExperiences = [
...this.selectedExperiences,
this.experiences.find(x => x.id === button.dataset.key)
];
this.selectedExperiences = [...this.selectedExperiences, button.dataset.key];
}
this.render();
}
getHopeUsed() {
return this.data.bonusDamage.reduce((acc, x) => acc + x.hopeUses, 0);
}
static decreaseHopeUse(_, button) {
const index = Number.parseInt(button.dataset.index);
if (this.data.bonusDamage[index].hopeUses - 1 >= 0) {
this.data.bonusDamage[index].hopeUses -= 1;
this.render(true);
}
}
static increaseHopeUse(_, button) {
const index = Number.parseInt(button.dataset.index);
if (this.data.bonusDamage[index].hopeUses <= this.data.hopeResource + 1) {
this.data.bonusDamage[index].hopeUses += 1;
this.render(true);
}
}
static setAdvantage() {
this.data.advantage = this.data.advantage ? null : 'd6';
this.data.disadvantage = null;
this.render(true);
}
static setDisadvantage() {
this.data.advantage = null;
this.data.disadvantage = this.data.disadvantage ? null : 'd6';
this.render(true);
static updateIsAdvantage(_, button) {
const advantage = Boolean(button.dataset.advantage);
this.data.advantage = this.data.advantage === advantage ? null : advantage;
this.render();
}
static async finish() {
const { diceOptions, ...rest } = this.data;
this.resolve({
...rest,
experiences: this.selectedExperiences,
hopeUsed: this.getHopeUsed(),
bonusDamage: this.data.bonusDamage.reduce((acc, x) => acc.concat(` + ${1 + x.hopeUses}${x.value}`), '')
experiences: this.selectedExperiences.map(x => ({ id: x, ...this.experiences[x] }))
});
this.close();
}

View file

@ -1,181 +1,122 @@
import { DualityRollColor } from '../config/settingsConfig.mjs';
import { defaultLevelTiers, DhLevelTiers } from '../data/levelTier.mjs';
import DhAppearance from '../data/settings/Appearance.mjs';
import DHAppearanceSettings from './settings/appearanceSettings.mjs';
import DhVariantRules from '../data/settings/VariantRules.mjs';
import DHVariantRuleSettings from './settings/variantRuleSettings.mjs';
import DhCountdowns from '../data/countdowns.mjs';
class DhpAutomationSettings extends FormApplication {
constructor(object = {}, options = {}) {
super(object, options);
}
static get defaultOptions() {
const defaults = super.defaultOptions;
const overrides = {
height: 'auto',
width: 400,
id: 'daggerheart-automation-settings',
template: 'systems/daggerheart/templates/views/automation-settings.hbs',
closeOnSubmit: true,
submitOnChange: false,
classes: ['daggerheart', 'views', 'settings']
};
const mergedOptions = foundry.utils.mergeObject(defaults, overrides);
return mergedOptions;
}
async getData() {
const context = super.getData();
context.settings = SYSTEM.SETTINGS.gameSettings.Automation;
context.hope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope);
context.actionPoints = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.ActionPoints);
return context;
}
activateListeners(html) {
super.activateListeners(html);
}
async _updateObject(_, formData) {
const data = foundry.utils.expandObject(formData);
const updateSettingsKeys = Object.keys(data);
for (var i = 0; i < updateSettingsKeys.length; i++) {
await game.settings.set(SYSTEM.id, updateSettingsKeys[i], data[updateSettingsKeys[i]]);
}
}
}
class DhpHomebrewSettings extends FormApplication {
constructor(object = {}, options = {}) {
super(object, options);
}
static get defaultOptions() {
const defaults = super.defaultOptions;
const overrides = {
height: 'auto',
width: 400,
id: 'daggerheart-homebrew-settings',
template: 'systems/daggerheart/templates/views/homebrew-settings.hbs',
closeOnSubmit: true,
submitOnChange: false,
classes: ['daggerheart', 'views', 'settings']
};
const mergedOptions = foundry.utils.mergeObject(defaults, overrides);
return mergedOptions;
}
async getData() {
const context = super.getData();
context.settings = SYSTEM.SETTINGS.gameSettings.General;
context.abilityArray = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.General.AbilityArray);
return context;
}
activateListeners(html) {
super.activateListeners(html);
}
async _updateObject(_, formData) {
const data = foundry.utils.expandObject(formData);
const updateSettingsKeys = Object.keys(data);
for (var i = 0; i < updateSettingsKeys.length; i++) {
await game.settings.set(SYSTEM.id, updateSettingsKeys[i], data[updateSettingsKeys[i]]);
}
}
}
class DhpRangeSettings extends FormApplication {
constructor(object = {}, options = {}) {
super(object, options);
this.range = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.General.RangeMeasurement);
}
static get defaultOptions() {
const defaults = super.defaultOptions;
const overrides = {
height: 'auto',
width: 400,
id: 'daggerheart-range-settings',
template: 'systems/daggerheart/templates/views/range-settings.hbs',
closeOnSubmit: false,
submitOnChange: true,
classes: ['daggerheart', 'views', 'settings']
};
const mergedOptions = foundry.utils.mergeObject(defaults, overrides);
return mergedOptions;
}
async getData() {
const context = super.getData();
context.settings = SYSTEM.SETTINGS.gameSettings.General;
context.range = this.range;
context.disabled =
context.range.enabled &&
[
context.range.melee,
context.range.veryClose,
context.range.close,
context.range.far,
context.range.veryFar
].some(x => x === null || x === false);
return context;
}
activateListeners(html) {
super.activateListeners(html);
html.find('.range-reset').click(this.reset.bind(this));
html.find('.save').click(this.save.bind(this));
html.find('.close').click(this.close.bind(this));
}
async _updateObject(_, formData) {
const data = foundry.utils.expandObject(formData, { disabled: true });
this.range = foundry.utils.mergeObject(this.range, data);
this.render(true);
}
reset() {
this.range = {
enabled: false,
melee: 5,
veryClose: 15,
close: 30,
far: 60,
veryFar: 120
};
this.render(true);
}
async save() {
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.General.RangeMeasurement, this.range);
this.close();
}
}
import {
DhAppearance,
DhAutomation,
DhHomebrew,
DhRangeMeasurement,
DhVariantRules
} from '../data/settings/_module.mjs';
import {
DhAppearanceSettings,
DhAutomationSettings,
DhHomebrewSettings,
DhRangeMeasurementSettings,
DhVariantRuleSettings
} from './settings/_module.mjs';
export const registerDHSettings = () => {
// const debouncedReload = foundry.utils.debounce(() => window.location.reload(), 100);
registerMenuSettings();
registerMenus();
registerNonConfigSettings();
};
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.General.AbilityArray, {
name: game.i18n.localize('DAGGERHEART.Settings.General.AbilityArray.Name'),
hint: game.i18n.localize('DAGGERHEART.Settings.General.AbilityArray.Hint'),
const registerMenuSettings = () => {
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.variantRules, {
scope: 'world',
config: false,
type: String,
default: '[2,1,1,0,0,-1]'
type: DhVariantRules
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation, {
scope: 'world',
config: false,
type: DhAutomation
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Homebrew, {
scope: 'world',
config: false,
type: DhHomebrew,
onChange: value => {
if (value.maxFear) {
if (ui.resources) ui.resources.render({ force: true });
}
}
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance, {
scope: 'client',
config: false,
type: DhAppearance,
onChange: value => {
if (value.displayFear) {
if (ui.resources) {
if (value.displayFear === 'hide') ui.resources.close({ allowed: true });
else ui.resources.render({ force: true });
}
}
}
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.RangeMeasurement, {
scope: 'client',
config: false,
type: DhRangeMeasurement
});
};
const registerMenus = () => {
game.settings.registerMenu(SYSTEM.id, SYSTEM.SETTINGS.menu.Automation.Name, {
name: game.i18n.localize('DAGGERHEART.Settings.Menu.Automation.Name'),
label: game.i18n.localize('DAGGERHEART.Settings.Menu.Automation.Label'),
hint: game.i18n.localize('DAGGERHEART.Settings.Menu.Automation.Hint'),
icon: SYSTEM.SETTINGS.menu.Automation.Icon,
type: DhAutomationSettings,
restricted: true
});
game.settings.registerMenu(SYSTEM.id, SYSTEM.SETTINGS.menu.Homebrew.Name, {
name: game.i18n.localize('DAGGERHEART.Settings.Menu.Homebrew.Name'),
label: game.i18n.localize('DAGGERHEART.Settings.Menu.Homebrew.Label'),
hint: game.i18n.localize('DAGGERHEART.Settings.Menu.Homebrew.Hint'),
icon: SYSTEM.SETTINGS.menu.Homebrew.Icon,
type: DhHomebrewSettings,
restricted: true
});
game.settings.registerMenu(SYSTEM.id, SYSTEM.SETTINGS.menu.Range.Name, {
name: game.i18n.localize('DAGGERHEART.Settings.Menu.Range.Name'),
label: game.i18n.localize('DAGGERHEART.Settings.Menu.Range.Label'),
hint: game.i18n.localize('DAGGERHEART.Settings.Menu.Range.Hint'),
icon: SYSTEM.SETTINGS.menu.Range.Icon,
type: DhRangeMeasurementSettings,
restricted: true
});
game.settings.registerMenu(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance, {
name: game.i18n.localize('DAGGERHEART.Settings.Menu.Appearance.title'),
label: game.i18n.localize('DAGGERHEART.Settings.Menu.Appearance.label'),
hint: game.i18n.localize('DAGGERHEART.Settings.Menu.Appearance.hint'),
icon: 'fa-solid fa-palette',
type: DhAppearanceSettings,
restricted: false
});
game.settings.registerMenu(SYSTEM.id, SYSTEM.SETTINGS.menu.VariantRules.Name, {
name: game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.title'),
label: game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.label'),
hint: game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.hint'),
icon: SYSTEM.SETTINGS.menu.VariantRules.Icon,
type: DhVariantRuleSettings,
restricted: false
});
};
const registerNonConfigSettings = () => {
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers, {
scope: 'world',
config: false,
type: DhLevelTiers,
default: defaultLevelTiers
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear, {
@ -191,149 +132,9 @@ export const registerDHSettings = () => {
}
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.MaxFear, {
name: game.i18n.localize('DAGGERHEART.Settings.Resources.MaxFear.Name'),
hint: game.i18n.localize('DAGGERHEART.Settings.Resources.MaxFear.Hint'),
scope: 'world',
config: true,
type: Number,
default: 12,
onChange: () => {
if (ui.resources) ui.resources.render({ force: true });
}
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.DisplayFear, {
name: game.i18n.localize('DAGGERHEART.Settings.Resources.DisplayFear.Name'),
hint: game.i18n.localize('DAGGERHEART.Settings.Resources.DisplayFear.Hint'),
scope: 'client',
config: true,
type: String,
choices: {
token: 'Tokens',
bar: 'Bar',
hide: 'Hide'
},
default: 'token',
onChange: value => {
if (ui.resources) {
if (value === 'hide') ui.resources.close({ allowed: true });
else ui.resources.render({ force: true });
}
}
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope, {
name: game.i18n.localize('DAGGERHEART.Settings.Automation.Hope.Name'),
hint: game.i18n.localize('DAGGERHEART.Settings.Automation.Hope.Hint'),
scope: 'world',
config: false,
type: Boolean,
default: false
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.ActionPoints, {
name: game.i18n.localize('DAGGERHEART.Settings.Automation.ActionPoints.Name'),
hint: game.i18n.localize('DAGGERHEART.Settings.Automation.ActionPoints.Hint'),
scope: 'world',
config: false,
type: Boolean,
default: true
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.General.RangeMeasurement, {
name: game.i18n.localize('DAGGERHEART.Settings.General.RangeMeasurement.Name'),
hint: game.i18n.localize('DAGGERHEART.Settings.General.RangeMeasurement.Hint'),
scope: 'world',
config: false,
type: Object,
default: {
enabled: true,
melee: 5,
veryClose: 15,
close: 30,
far: 60,
veryFar: 120
}
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.variantRules, {
scope: 'world',
config: false,
type: DhVariantRules,
default: DhVariantRules.defaultSchema
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance, {
scope: 'client',
config: false,
type: DhAppearance,
default: DhAppearance.defaultSchema
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.DualityRollColor, {
name: game.i18n.localize('DAGGERHEART.Settings.DualityRollColor.Name'),
hint: game.i18n.localize('DAGGERHEART.Settings.DualityRollColor.Hint'),
scope: 'world',
config: true,
type: Number,
choices: Object.values(DualityRollColor),
default: DualityRollColor.colorful.value
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers, {
scope: 'world',
config: false,
type: DhLevelTiers,
default: defaultLevelTiers
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns, {
scope: 'world',
config: false,
type: DhCountdowns
});
game.settings.registerMenu(SYSTEM.id, SYSTEM.SETTINGS.menu.Automation.Name, {
name: game.i18n.localize('DAGGERHEART.Settings.Menu.Automation.Name'),
label: game.i18n.localize('DAGGERHEART.Settings.Menu.Automation.Label'),
hint: game.i18n.localize('DAGGERHEART.Settings.Menu.Automation.Hint'),
icon: SYSTEM.SETTINGS.menu.Automation.Icon,
type: DhpAutomationSettings,
restricted: true
});
game.settings.registerMenu(SYSTEM.id, SYSTEM.SETTINGS.menu.Homebrew.Name, {
name: game.i18n.localize('DAGGERHEART.Settings.Menu.Homebrew.Name'),
label: game.i18n.localize('DAGGERHEART.Settings.Menu.Homebrew.Label'),
hint: game.i18n.localize('DAGGERHEART.Settings.Menu.Homebrew.Hint'),
icon: SYSTEM.SETTINGS.menu.Homebrew.Icon,
type: DhpHomebrewSettings,
restricted: true
});
game.settings.registerMenu(SYSTEM.id, SYSTEM.SETTINGS.menu.Range.Name, {
name: game.i18n.localize('DAGGERHEART.Settings.Menu.Range.Name'),
label: game.i18n.localize('DAGGERHEART.Settings.Menu.Range.Label'),
hint: game.i18n.localize('DAGGERHEART.Settings.Menu.Range.Hint'),
icon: SYSTEM.SETTINGS.menu.Range.Icon,
type: DhpRangeSettings,
restricted: true
});
game.settings.registerMenu(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance, {
name: game.i18n.localize('DAGGERHEART.Settings.Menu.Appearance.title'),
label: game.i18n.localize('DAGGERHEART.Settings.Menu.Appearance.label'),
hint: game.i18n.localize('DAGGERHEART.Settings.Menu.Appearance.hint'),
icon: 'fa-solid fa-palette',
type: DHAppearanceSettings,
restricted: false
});
game.settings.registerMenu(SYSTEM.id, SYSTEM.SETTINGS.menu.VariantRules.Name, {
name: game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.title'),
label: game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.label'),
hint: game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.hint'),
icon: SYSTEM.SETTINGS.menu.VariantRules.Icon,
type: DHVariantRuleSettings,
restricted: false
});
};

View file

@ -0,0 +1,13 @@
import DhAppearanceSettings from './appearanceSettings.mjs';
import DhAutomationSettings from './automationSettings.mjs';
import DhHomebrewSettings from './homebrewSettings.mjs';
import DhRangeMeasurementSettings from './rangeMeasurementSettings.mjs';
import DhVariantRuleSettings from './variantRuleSettings.mjs';
export {
DhAppearanceSettings,
DhAutomationSettings,
DhHomebrewSettings,
DhRangeMeasurementSettings,
DhVariantRuleSettings
};

View file

@ -1,4 +1,4 @@
import DhAppearance from '../../data/settings/Appearance.mjs';
import DhAppearance, { DualityRollColor } from '../../data/settings/Appearance.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -54,6 +54,12 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
static async save() {
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance, this.settings.toObject());
document.body.classList.toggle(
'theme-colorful',
game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance).dualityColorScheme ===
DualityRollColor.colorful.value
);
this.close();
}
}

View file

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

View file

@ -0,0 +1,60 @@
import { DhHomebrew } from '../../data/settings/_module.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DhAutomationSettings extends HandlebarsApplicationMixin(ApplicationV2) {
constructor() {
super({});
this.settings = new DhHomebrew(game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Homebrew).toObject());
}
get title() {
return game.i18n.localize('DAGGERHEART.Settings.Menu.Homebrew.Name');
}
static DEFAULT_OPTIONS = {
tag: 'form',
id: 'daggerheart-homebrew-settings',
classes: ['daggerheart', 'setting', 'dh-style'],
position: { width: '600', height: 'auto' },
actions: {
reset: this.reset,
save: this.save
},
form: { handler: this.updateData, submitOnChange: true }
};
static PARTS = {
main: {
template: 'systems/daggerheart/templates/settings/homebrew-settings.hbs'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.settingFields = this.settings;
return context;
}
static async updateData(event, element, formData) {
const updatedSettings = foundry.utils.expandObject(formData.object);
await this.settings.updateSource({
...updatedSettings,
traitArray: Object.values(updatedSettings.traitArray)
});
this.render();
}
static async reset() {
this.settings = new DhHomebrew();
this.render();
}
static async save() {
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
this.close();
}
}

View file

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

View file

@ -0,0 +1,62 @@
export default class DhActiveEffectConfig extends foundry.applications.sheets.ActiveEffectConfig {
static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'sheet', 'dh-style']
};
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' },
tabs: { template: 'templates/generic/tab-navigation.hbs' },
details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] },
duration: { template: 'systems/daggerheart/templates/sheets/activeEffect/duration.hbs' },
changes: {
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
scrollable: ['ol[data-changes]']
},
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
};
static TABS = {
sheet: {
tabs: [
{ id: 'details', icon: 'fa-solid fa-book' },
{ id: 'duration', icon: 'fa-solid fa-clock' },
{ id: 'changes', icon: 'fa-solid fa-gears' }
],
initial: 'details',
labelPrefix: 'EFFECT.TABS'
}
};
async _preparePartContext(partId, context) {
const partContext = await super._preparePartContext(partId, context);
switch (partId) {
case 'changes':
const fieldPaths = [];
const validFieldPath = fieldPath => this.validFieldPath(fieldPath, this.#unapplicablePaths);
context.document.parent.system.schema.apply(function () {
if (!(this instanceof foundry.data.fields.SchemaField)) {
if (validFieldPath(this.fieldPath)) {
fieldPaths.push(this.fieldPath);
}
}
});
context.fieldPaths = fieldPaths;
break;
}
return partContext;
}
#unapplicablePaths = ['story', 'pronouns', 'description'];
validFieldPath(fieldPath, unapplicablePaths) {
const splitPath = fieldPath.split('.');
if (splitPath.length > 1 && unapplicablePaths.includes(splitPath[1])) return false;
/* The current value of a resource should not be modified */
if (new RegExp(/resources.*\.value/).exec(fieldPath)) return false;
return true;
}
}

View file

@ -1,219 +1,12 @@
// import DhpApplicationMixin from '../daggerheart-sheet.mjs';
// export class Teest extends DhpApplicationMixin(ActorSheet) {
// static documentType = "adversary";
// constructor(options){
// super(options);
// this.editMode = false;
// }
// /** @override */
// static get defaultOptions() {
// return foundry.utils.mergeObject(super.defaultOptions, {
// classes: ["daggerheart", "sheet", "adversary"],
// width: 600,
// height: 'auto',
// resizable: false,
// });
// }
// async getData() {
// const context = super.getData();
// context.config = SYSTEM;
// context.editMode = this.editMode;
// context.title = `${this.actor.name} - ${game.i18n.localize(SYSTEM.ACTOR.adversaryTypes[this.actor.system.type].name)}`;
// context.data = {
// description: this.object.system.description,
// motivesAndTactics: this.object.system.motivesAndTactics.join(', '),
// tier: this.object.system.tier,
// type: game.i18n.localize(SYSTEM.ACTOR.adversaryTypes[this.object.system.type].name),
// attack: {
// name: this.object.system.attack.name,
// attackModifier: this.object.system.attackModifier,
// range: this.object.system.attack.range ? game.i18n.localize(SYSTEM.GENERAL.range[this.object.system.attack.range].name) : null,
// damage: {
// value: this.object.system.attack.damage.value,
// type: this.object.system.attack.damage.type,
// typeName: this.object.system.attack.damage.type ? game.i18n.localize(SYSTEM.GENERAL.damageTypes[this.object.system.attack.damage.type].abbreviation).toLowerCase() : null,
// },
// },
// damageThresholds: this.object.system.damageThresholds,
// difficulty: this.object.system.difficulty,
// hp: { ...this.object.system.resources.health, lastRowIndex: Math.floor(this.object.system.resources.health.max/5)*5 },
// stress: { ...this.object.system.resources.stress, lastRowIndex: Math.floor(this.object.system.resources.stress.max/5)*5 },
// moves: this.object.system.moves,
// };
// return context;
// }
// async _handleAction(action, event, button) {
// switch(action){
// case 'viewMove':
// await this.viewMove(button);
// break;
// case 'addMove':
// this.addMove();
// break;
// case 'removeMove':
// await this.removeMove(button);
// break;
// case 'toggleSlider':
// this.toggleEditMode();
// break;
// case 'addMotive':
// await this.addMotive();
// break;
// case 'removeMotive':
// await this.removeMotive(button);
// break;
// case 'reactionRoll':
// await this.reactionRoll(event);
// break;
// case 'attackRoll':
// await this.attackRoll(event);
// break;
// case 'addExperience':
// await this.addExperience();
// break;
// case 'removeExperience':
// await this.removeExperience(button);
// break;
// case 'toggleHP':
// await this.toggleHP(button);
// break;
// case 'toggleStress':
// await this.toggleStress(button);
// break;
// }
// }
// async viewMove(button){
// const move = await fromUuid(button.dataset.move);
// move.sheet.render(true);
// }
// async addMove(){
// const result = await this.object.createEmbeddedDocuments("Item", [{
// name: game.i18n.localize('DAGGERHEART.Sheets.Adversary.NewMove'),
// type: 'feature',
// }]);
// await result[0].sheet.render(true);
// }
// async removeMove(button){
// await this.object.items.find(x => x.uuid === button.dataset.move).delete();
// }
// toggleEditMode(){
// this.editMode = !this.editMode;
// this.render();
// }
// async addMotive(){
// await this.object.update({ "system.motivesAndTactics": [...this.object.system.motivesAndTactics, ''] });
// }
// async removeMotive(button){
// await this.object.update({ "system.motivesAndTactics": this.object.system.motivesAndTactics.filter((_, index) => index !== Number.parseInt(button.dataset.motive) )});
// }
// async reactionRoll(event){
// const { roll, diceResults, modifiers } = await this.actor.diceRoll({ title: `${this.actor.name} - Reaction Roll`, value: 0 }, event.shiftKey);
// const cls = getDocumentClass("ChatMessage");
// const msg = new cls({
// type: 'adversaryRoll',
// system: {
// roll: roll._formula,
// total: roll._total,
// modifiers: modifiers,
// diceResults: diceResults,
// },
// content: "systems/daggerheart/templates/chat/adversary-roll.hbs",
// rolls: [roll]
// });
// cls.create(msg.toObject());
// }
// async attackRoll(event){
// const modifier = Number.parseInt(event.currentTarget.dataset.value);
// const { roll, diceResults, modifiers } = await this.actor.diceRoll({ title: `${this.actor.name} - Attack Roll`, 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 cls = getDocumentClass("ChatMessage");
// const msg = new cls({
// type: 'adversaryRoll',
// system: {
// roll: roll._formula,
// total: roll._total,
// modifiers: modifiers,
// diceResults: diceResults,
// targets: targets,
// damage: { value: event.currentTarget.dataset.damage, type: event.currentTarget.dataset.damageType },
// },
// content: "systems/daggerheart/templates/chat/adversary-attack-roll.hbs",
// rolls: [roll]
// });
// cls.create(msg.toObject());
// }
// async addExperience(){
// await this.object.update({ "system.experiences": [...this.object.system.experiences, { name: 'Experience', value: 1 }] });
// }
// async removeExperience(button){
// await this.object.update({ "system.experiences": this.object.system.experiences.filter((_, index) => index !== Number.parseInt(button.dataset.experience) )});
// }
// async toggleHP(button){
// const index = Number.parseInt(button.dataset.index);
// const newHP = index < this.object.system.resources.health.value ? index : index+1;
// await this.object.update({ "system.resources.health.value": newHP });
// }
// async toggleStress(button){
// const index = Number.parseInt(button.dataset.index);
// const newStress = index < this.object.system.resources.stress.value ? index : index+1;
// await this.object.update({ "system.resources.stress.value": newStress });
// }
// }
import DaggerheartSheet from './daggerheart-sheet.mjs';
const { ActorSheetV2 } = foundry.applications.sheets;
export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
constructor(options = {}) {
super(options);
this.editMode = false;
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'adversary'],
position: { width: 600 },
classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'adversary'],
position: { width: 450, height: 1000 },
actions: {
viewMove: this.viewMove,
addMove: this.addMove,
removeMove: this.removeMove,
toggleSlider: this.toggleEditMode,
addMotive: this.addMotive,
removeMotive: this.removeMotive,
reactionRoll: this.reactionRoll,
attackRoll: this.attackRoll,
addExperience: this.addExperience,
@ -229,54 +22,35 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
};
static PARTS = {
form: {
id: 'feature',
template: 'systems/daggerheart/templates/sheets/adversary.hbs'
header: { template: 'systems/daggerheart/templates/sheets/actors/adversary/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
main: { template: 'systems/daggerheart/templates/sheets/actors/adversary/main.hbs' },
information: { template: 'systems/daggerheart/templates/sheets/actors/adversary/information.hbs' }
};
static TABS = {
main: {
active: true,
cssClass: '',
group: 'primary',
id: 'main',
icon: null,
label: 'DAGGERHEART.Sheets.Adversary.Tabs.Main'
},
information: {
active: false,
cssClass: '',
group: 'primary',
id: 'information',
icon: null,
label: 'DAGGERHEART.Sheets.Adversary.Tabs.Information'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.config = SYSTEM;
context.editMode = this.editMode;
context.title = `${this.actor.name} - ${game.i18n.localize(SYSTEM.ACTOR.adversaryTypes[this.actor.system.type].name)}`;
context.data = {
description: this.document.system.description,
motivesAndTactics: this.document.system.motivesAndTactics.join(', '),
tier: this.document.system.tier,
type: game.i18n.localize(SYSTEM.ACTOR.adversaryTypes[this.document.system.type].name),
attack: {
name: this.document.system.attack.name,
attackModifier: this.document.system.attackModifier,
range: this.document.system.attack.range
? game.i18n.localize(SYSTEM.GENERAL.range[this.document.system.attack.range].name)
: null,
damage: {
value: this.document.system.attack.damage.value,
type: this.document.system.attack.damage.type,
typeName: this.document.system.attack.damage.type
? game.i18n
.localize(
SYSTEM.GENERAL.damageTypes[this.document.system.attack.damage.type].abbreviation
)
.toLowerCase()
: null
}
},
damageThresholds: this.document.system.damageThresholds,
difficulty: this.document.system.difficulty,
hp: {
...this.document.system.resources.health,
lastRowIndex: Math.floor(this.document.system.resources.health.max / 5) * 5
},
stress: {
...this.document.system.resources.stress,
lastRowIndex: Math.floor(this.document.system.resources.stress.max / 5) * 5
},
moves: this.document.system.moves
};
context.tabs = super._getTabs(this.constructor.TABS);
return context;
}
@ -286,109 +60,43 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
this.render();
}
static async viewMove(_, button) {
const move = await fromUuid(button.dataset.move);
move.sheet.render(true);
}
static async addMove() {
const result = await this.document.createEmbeddedDocuments('Item', [
{
name: game.i18n.localize('DAGGERHEART.Sheets.Adversary.NewMove'),
type: 'feature'
}
]);
await result[0].sheet.render(true);
}
static async removeMove(_, button) {
await this.document.items.find(x => x.uuid === button.dataset.move).delete();
}
static toggleEditMode() {
this.editMode = !this.editMode;
this.render();
}
static async addMotive() {
await this.document.update({ 'system.motivesAndTactics': [...this.document.system.motivesAndTactics, ''] });
}
static async removeMotive(button) {
await this.document.update({
'system.motivesAndTactics': this.document.system.motivesAndTactics.filter(
(_, index) => index !== Number.parseInt(button.dataset.motive)
)
});
}
static async reactionRoll(event) {
const { roll, diceResults, modifiers } = await this.actor.diceRoll(
{ title: `${this.actor.name} - Reaction Roll`, value: 0 },
event.shiftKey
);
const cls = getDocumentClass('ChatMessage');
const systemData = {
roll: roll._formula,
total: roll._total,
modifiers: modifiers,
diceResults: diceResults
const config = {
event: event,
title: `${this.actor.name} - Reaction Roll`,
roll: {
modifier: null,
type: 'reaction'
},
chatMessage: {
type: 'adversaryRoll',
template: 'systems/daggerheart/templates/chat/adversary-roll.hbs',
mute: true
}
};
const msg = new cls({
type: 'adversaryRoll',
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/adversary-roll.hbs',
systemData
),
rolls: [roll]
});
cls.create(msg.toObject());
this.actor.diceRoll(config);
}
static async attackRoll(event, button) {
const modifier = Number.parseInt(button.dataset.value);
const { roll, dice, advantageState, modifiers } = await this.actor.diceRoll(
{ title: `${this.actor.name} - Attack Roll`, 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.value
}));
const cls = getDocumentClass('ChatMessage');
const systemData = {
title: button.dataset.name,
origin: this.document.id,
roll: roll._formula,
advantageState,
total: roll._total,
modifiers: modifiers,
dice: dice,
targets: targets,
damage: { value: button.dataset.damage, type: button.dataset.damageType }
};
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 attackRoll(event) {
const { modifier, damage, name: attackName } = this.actor.system.attack,
config = {
event: event,
title: attackName,
roll: {
modifier: modifier,
type: 'action'
},
chatMessage: {
type: 'adversaryRoll',
template: 'systems/daggerheart/templates/chat/adversary-attack-roll.hbs'
},
damage: {
value: damage.value,
type: damage.type
},
checkTarget: true
};
this.actor.diceRoll(config);
}
static async addExperience() {

View file

@ -0,0 +1,698 @@
import { capitalize } from '../../helpers/utils.mjs';
import DhpDeathMove from '../deathMove.mjs';
import DhpDowntime from '../downtime.mjs';
import AncestrySelectionDialog from '../ancestrySelectionDialog.mjs';
import DaggerheartSheet from './daggerheart-sheet.mjs';
import { abilities } from '../../config/actorConfig.mjs';
import DhlevelUp from '../levelup.mjs';
import DHDualityRoll from '../../data/chat-message/dualityRoll.mjs';
const { ActorSheetV2 } = foundry.applications.sheets;
const { TextEditor } = foundry.applications.ux;
export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
constructor(options = {}) {
super(options);
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'pc'],
position: { width: 810, height: 1080 },
actions: {
attributeRoll: this.rollAttribute,
toggleMarks: this.toggleMarks,
toggleHP: this.toggleHP,
toggleStress: this.toggleStress,
toggleHope: this.toggleHope,
toggleGold: this.toggleGold,
attackRoll: this.attackRoll,
useDomainCard: this.useDomainCard,
removeCard: this.removeDomainCard,
selectClass: this.selectClass,
selectSubclass: this.selectSubclass,
selectAncestry: this.selectAncestry,
selectCommunity: this.selectCommunity,
viewObject: this.viewObject,
useItem: this.useItem,
useFeature: this.useFeature,
takeShortRest: this.takeShortRest,
takeLongRest: this.takeLongRest,
deleteItem: this.deleteItem,
addScar: this.addScar,
deleteScar: this.deleteScar,
makeDeathMove: this.makeDeathMove,
itemQuantityDecrease: (_, button) => this.setItemQuantity(button, -1),
itemQuantityIncrease: (_, button) => this.setItemQuantity(button, 1),
useAbility: this.useAbility,
useAdvancementCard: this.useAdvancementCard,
useAdvancementAbility: this.useAdvancementAbility,
toggleEquipItem: this.toggleEquipItem,
levelup: this.openLevelUp
},
window: {
minimizable: false,
resizable: true
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
},
dragDrop: [
{ dragSelector: null, dropSelector: '.weapon-section' },
{ dragSelector: null, dropSelector: '.armor-section' },
{ dragSelector: '.item-list .item', dropSelector: null }
]
};
static PARTS = {
form: {
id: 'character',
template: 'systems/daggerheart/templates/sheets/character/character.hbs'
}
};
_getTabs() {
const setActive = tabs => {
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
v.cssClass = v.active ? 'active' : '';
}
};
const primaryTabs = {
features: {
active: true,
cssClass: '',
group: 'primary',
id: 'features',
icon: null,
label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Features')
},
loadout: {
active: false,
cssClass: '',
group: 'primary',
id: 'loadout',
icon: null,
label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Loadout')
},
inventory: {
active: false,
cssClass: '',
group: 'primary',
id: 'inventory',
icon: null,
label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Inventory')
},
story: {
active: false,
cssClass: '',
group: 'primary',
id: 'story',
icon: null,
label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Story')
}
};
const secondaryTabs = {
foundation: {
active: true,
cssClass: '',
group: 'secondary',
id: 'foundation',
icon: null,
label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Foundation')
},
loadout: {
active: false,
cssClass: '',
group: 'secondary',
id: 'loadout',
icon: null,
label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Loadout')
},
vault: {
active: false,
cssClass: '',
group: 'secondary',
id: 'vault',
icon: null,
label: game.i18n.localize('DAGGERHEART.Sheets.PC.Tabs.Vault')
}
};
setActive(primaryTabs);
setActive(secondaryTabs);
return { primary: primaryTabs, secondary: secondaryTabs };
}
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
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) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.tabs = this._getTabs();
context.config = SYSTEM;
const selectedAttributes = Object.values(this.document.system.traits).map(x => x.base);
context.abilityScoreArray = await game.settings
.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Homebrew)
.traitArray.reduce((acc, x) => {
const selectedIndex = selectedAttributes.indexOf(x);
if (selectedIndex !== -1) {
selectedAttributes.splice(selectedIndex, 1);
} else {
acc.push({ name: x, value: x });
}
return acc;
}, []);
if (!context.abilityScoreArray.includes(0)) context.abilityScoreArray.push({ name: 0, value: 0 });
context.abilityScoresFinished = context.abilityScoreArray.every(x => x.value === 0);
context.attributes = Object.keys(this.document.system.traits).reduce((acc, key) => {
acc[key] = {
...this.document.system.traits[key],
name: game.i18n.localize(SYSTEM.ACTOR.abilities[key].name),
verbs: SYSTEM.ACTOR.abilities[key].verbs.map(x => game.i18n.localize(x))
};
return acc;
}, {});
const ancestry = await this.mapFeatureType(
this.document.system.ancestry ? [this.document.system.ancestry] : [],
SYSTEM.GENERAL.objectTypes
);
const community = await this.mapFeatureType(
this.document.system.community ? [this.document.system.community] : [],
SYSTEM.GENERAL.objectTypes
);
const foundation = {
ancestry: ancestry[0],
community: community[0],
advancement: {}
};
const nrLoadoutCards = this.document.system.domainCards.loadout.length;
const loadout = await this.mapFeatureType(this.document.system.domainCards.loadout, SYSTEM.DOMAIN.cardTypes);
const vault = await this.mapFeatureType(this.document.system.domainCards.vault, SYSTEM.DOMAIN.cardTypes);
context.abilities = {
foundation: foundation,
loadout: {
top: loadout.slice(0, Math.min(2, nrLoadoutCards)),
bottom: nrLoadoutCards > 2 ? loadout.slice(2, Math.min(5, nrLoadoutCards)) : [],
nrTotal: nrLoadoutCards
},
vault: vault.map(x => ({
...x,
uuid: x.uuid,
sendToLoadoutDisabled: this.document.system.domainCards.loadout.length >= 5
}))
};
context.inventory = {
consumable: {
titles: {
name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.ConsumableTitle'),
quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle')
},
items: this.document.items.filter(x => x.type === 'consumable')
},
miscellaneous: {
titles: {
name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.MiscellaneousTitle'),
quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle')
},
items: this.document.items.filter(x => x.type === 'miscellaneous')
},
weapons: {
titles: {
name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.WeaponsTitle'),
quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle')
},
items: this.document.items.filter(x => x.type === 'weapon')
},
armor: {
titles: {
name: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.ArmorsTitle'),
quantity: game.i18n.localize('DAGGERHEART.Sheets.PC.InventoryTab.QuantityTitle')
},
items: this.document.items.filter(x => x.type === 'armor')
}
};
if (context.inventory.length === 0) {
context.inventory = Array(1).fill(Array(5).fill([]));
}
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
}
async mapFeatureType(data, configType) {
return await Promise.all(
data.map(async x => {
const abilities = x.system.abilities
? await Promise.all(x.system.abilities.map(async x => await fromUuid(x.uuid)))
: [];
return {
...x,
uuid: x.uuid,
system: {
...x.system,
abilities: abilities,
type: game.i18n.localize(configType[x.system.type ?? x.type].label)
}
};
})
);
}
static async rollAttribute(event, button) {
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 },
event.shiftKey
);
const cls = getDocumentClass('ChatMessage');
const systemContent = new DHDualityRoll({
title: game.i18n.format('DAGGERHEART.Chat.DualityRoll.AbilityCheckTitle', {
ability: game.i18n.localize(abilities[button.dataset.attribute].label)
}),
origin: this.document.id,
roll: roll._formula,
modifiers: modifiers,
hope: hope,
fear: fear,
advantage: advantage,
disadvantage: disadvantage
});
await cls.create({
type: 'dualityRoll',
sound: CONFIG.sounds.dice,
system: systemContent,
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/duality-roll.hbs',
systemContent
),
rolls: [roll]
}); */
}
static async toggleMarks(_, button) {
const markValue = Number.parseInt(button.dataset.value);
const newValue = this.document.system.armor.system.marks.value >= markValue ? markValue - 1 : markValue;
await this.document.system.armor.update({ 'system.marks.value': newValue });
}
static async toggleHP(_, button) {
const healthValue = Number.parseInt(button.dataset.value);
const newValue = this.document.system.resources.hitPoints.value >= healthValue ? healthValue - 1 : healthValue;
await this.document.update({ 'system.resources.hitPoints.value': newValue });
}
static async toggleStress(_, button) {
const healthValue = Number.parseInt(button.dataset.value);
const newValue = this.document.system.resources.stress.value >= healthValue ? healthValue - 1 : healthValue;
await this.document.update({ 'system.resources.stress.value': newValue });
}
static async toggleHope(_, button) {
const hopeValue = Number.parseInt(button.dataset.value);
const newValue = this.document.system.resources.hope.value >= hopeValue ? hopeValue - 1 : hopeValue;
await this.document.update({ 'system.resources.hope.value': newValue });
}
static async toggleGold(_, button) {
const goldValue = Number.parseInt(button.dataset.value);
const goldType = button.dataset.type;
const newValue = this.document.system.gold[goldType] >= goldValue ? goldValue - 1 : goldValue;
const update = `system.gold.${goldType}`;
await this.document.update({ [update]: newValue });
}
static async attackRoll(event, button) {
const weapon = await fromUuid(button.dataset.weapon);
if (!weapon) return;
weapon.use(event);
}
static openLevelUp() {
if (!this.document.system.class.value || !this.document.system.class.subclass) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.Sheets.PC.Errors.missingClassOrSubclass'));
return;
}
new DhlevelUp(this.document).render(true);
}
static async useDomainCard(_, button) {
const card = this.document.items.find(x => x.uuid === button.dataset.key);
const cls = getDocumentClass('ChatMessage');
const systemData = {
title: `${game.i18n.localize('DAGGERHEART.Chat.DomainCard.Title')} - ${capitalize(button.dataset.domain)}`,
origin: this.document.id,
img: card.img,
name: card.name,
description: card.system.effect,
actions: card.system.actions
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/ability-use.hbs',
systemData
),
system: systemData
});
cls.create(msg.toObject());
}
static async removeDomainCard(_, button) {
if (button.dataset.type === 'domainCard') {
const card = this.document.items.find(x => x.uuid === button.dataset.key);
await card.delete();
}
}
static async selectClass() {
(await game.packs.get('daggerheart.classes'))?.render(true);
}
static async selectSubclass() {
(await game.packs.get('daggerheart.subclasses'))?.render(true);
}
static async selectAncestry() {
const dialogClosed = new Promise((resolve, _) => {
new AncestrySelectionDialog(resolve).render(true);
});
const result = await dialogClosed;
for (var ancestry of this.document.items.filter(x => x => x.type === 'ancestry')) {
await ancestry.delete();
}
const createdItems = [];
for (var feature of this.document.items.filter(
x => x.type === 'feature' && x.system.type === SYSTEM.ITEM.featureTypes.ancestry.id
)) {
await feature.delete();
}
createdItems.push(result.data);
await this.document.createEmbeddedDocuments('Item', createdItems);
}
static async selectCommunity() {
(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) {
const object = await fromUuid(button.dataset.value);
if (!object) return;
const tab = button.dataset.tab;
if (tab && object.sheet._tabs) object.sheet._tabs[0].active = tab;
if (object.sheet.editMode) object.sheet.editMode = false;
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() {
await new DhpDowntime(this.document, true).render(true);
await this.minimize();
}
static async takeLongRest() {
await new DhpDowntime(this.document, false).render(true);
await this.minimize();
}
static async addScar() {
if (this.document.system.story.scars.length === 5) return;
await this.document.update({
'system.story.scars': [
...this.document.system.story.scars,
{ name: game.i18n.localize('DAGGERHEART.Sheets.PC.NewScar'), description: '' }
]
});
}
static async deleteScar(event, button) {
event.stopPropagation();
await this.document.update({
'system.story.scars': this.document.system.story.scars.filter(
(_, index) => index !== Number.parseInt(button.currentTarget.dataset.scar)
)
});
}
static async makeDeathMove() {
if (this.document.system.resources.hitPoints.value === this.document.system.resources.hitPoints.max) {
await new DhpDeathMove(this.document).render(true);
await this.minimize();
}
}
async itemUpdate(event) {
const name = event.currentTarget.dataset.item;
const item = await fromUuid($(event.currentTarget).closest('[data-item-id]')[0].dataset.itemId);
await item.update({ [name]: event.currentTarget.value });
}
async onLevelChange(event) {
await this.document.updateLevel(Number(event.currentTarget.value));
this.render();
}
static async deleteItem(_, button) {
const item = await fromUuid($(button).closest('[data-item-id]')[0].dataset.itemId);
await item.delete();
}
static async setItemQuantity(button, value) {
const item = await fromUuid($(button).closest('[data-item-id]')[0].dataset.itemId);
await item.update({ 'system.quantity': Math.max(item.system.quantity + value, 1) });
}
static async useFeature(_, button) {
const item = await fromUuid(button.dataset.id);
const cls = getDocumentClass('ChatMessage');
const systemData = {
title: game.i18n.localize('DAGGERHEART.Chat.FeatureTitle'),
origin: this.document.id,
img: item.img,
name: item.name,
description: item.system.description,
actions: item.system.actions
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/ability-use.hbs',
systemData
),
system: systemData
});
cls.create(msg.toObject());
}
static async useAbility(_, button) {
const item = await fromUuid(button.dataset.feature);
const type = button.dataset.type;
const cls = getDocumentClass('ChatMessage');
const systemData = {
title:
type === 'ancestry'
? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.AncestryTitle')
: type === 'community'
? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.CommunityTitle')
: game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'),
origin: this.document.id,
img: item.img,
name: item.name,
description: item.system.description,
actions: []
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/ability-use.hbs',
systemData
)
});
cls.create(msg.toObject());
}
static async useAdvancementCard(_, button) {
const item =
button.dataset.multiclass === 'true'
? this.document.system.multiclass.subclass
: this.document.system.class.subclass;
const ability = item.system[`${button.dataset.key}Feature`];
const title = `${item.name} - ${game.i18n.localize(`DAGGERHEART.Sheets.PC.DomainCard.${capitalize(button.dataset.key)}Title`)}`;
const cls = getDocumentClass('ChatMessage');
const systemData = {
title: game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'),
origin: this.document.id,
name: title,
img: item.img,
description: ability.description
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/ability-use.hbs',
systemData
)
});
cls.create(msg.toObject());
}
static async useAdvancementAbility(_, button) {
const item = this.document.items.find(x => x.uuid === button.dataset.id);
const cls = getDocumentClass('ChatMessage');
const systemData = {
title: game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'),
origin: this.document.id,
name: item.name,
img: item.img,
description: item.system.description
};
const msg = new cls({
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/ability-use.hbs',
systemData
)
});
cls.create(msg.toObject());
}
static async toggleEquipItem(_, button) {
const item = this.document.items.get(button.id);
if (item.system.equipped) {
await item.update({ 'system.equipped': false });
return;
}
switch (item.type) {
case 'armor':
const currentArmor = this.document.system.armor;
if (currentArmor) {
await currentArmor.update({ 'system.equipped': false });
}
await item.update({ 'system.equipped': true });
break;
case 'weapon':
await this.document.system.constructor.unequipBeforeEquip.bind(this.document.system)(item);
await item.update({ 'system.equipped': true });
break;
}
this.render();
}
async _onDragStart(_, event) {
super._onDragStart(event);
}
async _onDrop(event) {
super._onDrop(event);
this._onDropItem(event, TextEditor.getDragEventData(event));
}
async _onDropItem(event, data) {
const item = await Item.implementation.fromDropData(data);
const itemData = item.toObject();
if (item.type === 'domainCard' && this.document.system.domainCards.loadout.length >= 5) {
itemData.system.inVault = true;
}
if (this.document.uuid === item.parent?.uuid) return this._onSortItem(event, itemData);
const createdItem = await this._onDropItemCreate(itemData);
return createdItem;
}
async _onDropItemCreate(itemData, event) {
itemData = itemData instanceof Array ? itemData : [itemData];
return this.document.createEmbeddedDocuments('Item', itemData);
}
}

View file

@ -27,7 +27,7 @@ export default function DhpApplicationMixin(Base) {
async _prepareContext(_options, objectPath = 'document') {
const context = await super._prepareContext(_options);
context.source = this[objectPath].toObject();
context.source = this[objectPath];
context.fields = this[objectPath].schema.fields;
context.systemFields = this[objectPath].system ? this[objectPath].system.schema.fields : {};

View file

@ -1,78 +1,60 @@
import DaggerheartSheet from './daggerheart-sheet.mjs';
const { DocumentSheetV2 } = foundry.applications.api;
export default class DhpEnvironment extends DaggerheartSheet(DocumentSheetV2) {
constructor(options) {
super(options);
this.editMode = false;
}
const { ActorSheetV2 } = foundry.applications.sheets;
export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) {
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'adversary', 'environment'],
classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'environment'],
position: {
width: 600,
height: 'auto'
width: 450,
height: 1000
},
actions: {
toggleSlider: this.toggleSlider,
viewFeature: this.viewFeature,
addAdversary: this.addAdversary,
addFeature: this.addFeature,
removeFeature: this.removeFeature,
addTone: this.addTone,
removeTone: this.removeTone,
useFeature: this.useFeature
deleteProperty: this.deleteProperty,
viewAdversary: this.viewAdversary
},
form: {
handler: this._updateForm,
closeOnSubmit: false,
submitOnChange: true
}
submitOnChange: true,
closeOnSubmit: false
},
dragDrop: [{ dragSelector: null, dropSelector: '.adversary-container' }]
};
/** @override */
static PARTS = {
form: {
id: 'form',
template: 'systems/daggerheart/templates/sheets/environment.hbs'
}
header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
main: { template: 'systems/daggerheart/templates/sheets/actors/environment/main.hbs' },
information: { template: 'systems/daggerheart/templates/sheets/actors/environment/information.hbs' }
};
/* -------------------------------------------- */
/** @inheritDoc */
get title() {
return `${game.i18n.localize('Environment')} - ${this.document.name}`;
}
static TABS = {
main: {
active: true,
cssClass: '',
group: 'primary',
id: 'main',
icon: null,
label: 'DAGGERHEART.Sheets.Environment.Tabs.Main'
},
information: {
active: false,
cssClass: '',
group: 'primary',
id: 'information',
icon: null,
label: 'DAGGERHEART.Sheets.Environment.Tabs.Information'
}
};
async _prepareContext(_options) {
return {
title: `${this.document.name} - ${game.i18n.localize(SYSTEM.ACTOR.adversaryTypes[this.document.system.type].name)}`,
user: this.document,
source: this.document.toObject(),
fields: this.document.schema.fields,
data: {
type: game.i18n.localize(SYSTEM.ACTOR.adversaryTypes[this.document.system.type].name),
features: this.document.items.reduce((acc, x) => {
if (x.type === 'feature') {
const feature = x.toObject();
acc.push({
...feature,
system: {
...feature.system,
actionType: game.i18n.localize(SYSTEM.ITEM.actionTypes[feature.system.actionType].name)
},
uuid: x.uuid
});
}
const context = await super._prepareContext(_options);
context.document = this.document;
context.tabs = super._getTabs(this.constructor.TABS);
return acc;
}, [])
},
editMode: this.editMode,
config: SYSTEM
};
return context;
}
static async _updateForm(event, _, formData) {
@ -80,60 +62,41 @@ export default class DhpEnvironment extends DaggerheartSheet(DocumentSheetV2) {
this.render();
}
static toggleSlider() {
this.editMode = !this.editMode;
static async addAdversary() {
await this.document.update({
[`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize(
'DAGGERHEART.Sheets.Environment.newAdversary'
)
});
this.render();
}
static async viewFeature(_, button) {
const move = await fromUuid(button.dataset.feature);
move.sheet.render(true);
}
static async addFeature() {
const result = await this.document.createEmbeddedDocuments('Item', [
{
name: game.i18n.localize('DAGGERHEART.Sheets.Environment.NewFeature'),
type: 'feature'
}
]);
await result[0].sheet.render(true);
ui.notifications.error('Not Implemented yet. Awaiting datamodel rework');
}
static async removeFeature(_, button) {
await this.document.items.find(x => x.uuid === button.dataset.feature).delete();
static async deleteProperty(_, target) {
await this.document.update({ [`${target.dataset.path}.-=${target.id}`]: null });
this.render();
}
static async addTone() {
await this.document.update({ 'system.toneAndFeel': [...this.document.system.toneAndFeel, ''] });
static async viewAdversary(_, button) {
const adversary = foundry.utils.getProperty(
this.document.system.potentialAdversaries,
`${button.dataset.potentialAdversary}.adversaries.${button.dataset.adversary}`
);
adversary.sheet.render(true);
}
static async removeTone(button) {
await this.document.update({
'system.toneAndFeel': this.document.system.toneAndFeel.filter(
(_, index) => index !== Number.parseInt(button.dataset.tone)
)
});
}
static async useFeature(_, button) {
const item = this.document.items.find(x => x.uuid === button.dataset.feature);
const cls = getDocumentClass('ChatMessage');
const msg = new cls({
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/ability-use.hbs',
{
title: game.i18n.format('DAGGERHEART.Chat.EnvironmentTitle', {
actionType: button.dataset.actionType
}),
card: { name: item.name, img: item.img, description: item.system.description }
}
)
});
cls.create(msg.toObject());
async _onDrop(event) {
const data = TextEditor.getDragEventData(event);
const item = await fromUuid(data.uuid);
if (item.type === 'adversary') {
const target = event.target.closest('.adversary-container');
const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries.${item.id}`;
await this.document.update({
[path]: item.uuid
});
}
}
}

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,88 +1,13 @@
import DaggerheartSheet from '../daggerheart-sheet.mjs';
import DHHeritageSheetV2 from './heritage.mjs';
const { ItemSheetV2 } = foundry.applications.sheets;
export default class AncestrySheet extends DaggerheartSheet(ItemSheetV2) {
export default class AncestrySheet extends DHHeritageSheetV2(ItemSheetV2) {
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'ancestry'],
position: { width: 450, height: 700 },
actions: {
editFeature: this.editFeature,
deleteFeature: this.deleteFeature
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
},
dragDrop: [{ dragSelector: null, dropSelector: null }]
classes: ['ancestry']
};
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/ancestry/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
features: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-feature-section.hbs',
scrollable: ['.features']
}
...super.PARTS
};
static TABS = {
description: {
active: true,
cssClass: '',
group: 'primary',
id: 'description',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Description'
},
features: {
active: false,
cssClass: '',
group: 'primary',
id: 'features',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Features'
}
};
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();
}
static async editFeature(_, target) {
const feature = await fromUuid(target.dataset.feature);
feature.sheet.render(true);
}
static async deleteFeature(event, target) {
event.preventDefault();
event.stopPropagation();
await this.item.update({
'system.abilities': this.item.system.abilities.filter(x => x.uuid !== target.dataset.feature)
});
}
async _onDrop(event) {
const data = TextEditor.getDragEventData(event);
const item = await fromUuid(data.uuid);
if (item.type === 'feature' && item.system.type === SYSTEM.ITEM.featureTypes.ancestry.id) {
await this.document.update({
'system.abilities': [
...this.document.system.abilities,
{ img: item.img, name: item.name, uuid: item.uuid }
]
});
}
}
}

View file

@ -1,16 +1,11 @@
import DaggerheartSheet from '../daggerheart-sheet.mjs';
import { armorFeatures } from '../../../config/itemConfig.mjs';
import { tagifyElement } from '../../../helpers/utils.mjs';
import DHItemSheetV2 from '../item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets;
export default class ArmorSheet extends DaggerheartSheet(ItemSheetV2) {
export default class ArmorSheet extends DHItemSheetV2(ItemSheetV2) {
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'armor'],
position: { width: 600 },
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
},
classes: ['armor'],
dragDrop: [{ dragSelector: null, dropSelector: null }]
};
@ -18,42 +13,37 @@ export default class ArmorSheet extends DaggerheartSheet(ItemSheetV2) {
header: { template: 'systems/daggerheart/templates/sheets/items/armor/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.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: {
template: 'systems/daggerheart/templates/sheets/items/armor/settings.hbs',
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 _preparePartContext(partId, context) {
super._preparePartContext(partId, context);
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.config = CONFIG.daggerheart;
context.tabs = super._getTabs(this.constructor.TABS);
switch (partId) {
case 'settings':
context.features = this.document.system.features.map(x => x.value);
break;
}
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
const featureInput = htmlElement.querySelector('.features-input');
tagifyElement(featureInput, armorFeatures, this.onFeatureSelect.bind(this));
}
async onFeatureSelect(features) {
await this.document.update({ 'system.features': features.map(x => ({ value: x.value })) });
this.render(true);
}
}

View file

@ -1,8 +1,11 @@
import { actionsTypes } from '../../../data/_module.mjs';
import { tagifyElement } from '../../../helpers/utils.mjs';
import DHActionConfig from '../../config/Action.mjs';
import DaggerheartSheet from '../daggerheart-sheet.mjs';
import Tagify from '@yaireo/tagify';
const { ItemSheetV2 } = foundry.applications.sheets;
const { TextEditor } = foundry.applications.ux;
export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) {
static DEFAULT_OPTIONS = {
tag: 'form',
@ -11,8 +14,9 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) {
actions: {
removeSubclass: this.removeSubclass,
viewSubclass: this.viewSubclass,
removeFeature: this.removeFeature,
viewFeature: this.viewFeature,
addFeature: this.addFeature,
editFeature: this.editFeature,
deleteFeature: this.deleteFeature,
removeItem: this.removeItem,
viewItem: this.viewItem,
removePrimaryWeapon: this.removePrimaryWeapon,
@ -72,55 +76,14 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) {
super._attachPartListeners(partId, htmlElement, options);
const domainInput = htmlElement.querySelector('.domain-input');
const domainTagify = new Tagify(domainInput, {
tagTextProp: 'name',
enforceWhitelist: true,
whitelist: Object.keys(SYSTEM.DOMAIN.domains).map(key => {
const domain = SYSTEM.DOMAIN.domains[key];
return {
value: key,
name: game.i18n.localize(domain.label),
src: domain.src,
background: domain.background
};
}),
maxTags: 2,
callbacks: { invalid: this.onAddTag },
dropdown: {
mapValueTo: 'name',
searchKeys: ['name'],
enabled: 0,
maxItems: 20,
closeOnSelect: true,
highlightFirst: false
},
templates: {
tag(tagData) {
//z-index: unset; background-image: ${tagData.background}; Maybe a domain specific background for the chips?
return `<tag title="${tagData.title || tagData.value}"
contenteditable='false'
spellcheck='false'
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
${this.getAttributes(tagData)}>
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
<div>
<span class="${this.settings.classNames.tagText}">${tagData[this.settings.tagTextProp] || tagData.value}</span>
<img src="${tagData.src}"></i>
</div>
</tag>`;
}
}
});
domainTagify.on('change', this.onDomainSelect.bind(this));
tagifyElement(domainInput, SYSTEM.DOMAIN.domains, this.onDomainSelect.bind(this));
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.tabs = super._getTabs(this.constructor.TABS);
context.domains = this.document.system.domains.map(x => SYSTEM.DOMAIN.domains[x].label);
context.domains = this.document.system.domains;
return context;
}
@ -136,8 +99,7 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) {
}
}
async onDomainSelect(event) {
const domains = event.detail?.value ? JSON.parse(event.detail.value) : [];
async onDomainSelect(domains) {
await this.document.update({ 'system.domains': domains.map(x => x.value) });
this.render(true);
}
@ -153,13 +115,13 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) {
subclass.sheet.render(true);
}
static async removeFeature(_, button) {
static async deleteFeature(_, button) {
await this.document.update({
'system.features': this.document.system.features.filter(x => x.uuid !== button.dataset.feature)
'system.features': this.document.system.features.map(x => x.uuid).filter(x => x !== button.dataset.feature)
});
}
static async viewFeature(_, button) {
static async editFeature(_, button) {
const feature = await fromUuid(button.dataset.feature);
feature.sheet.render(true);
}
@ -193,75 +155,118 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) {
await this.document.update({ 'system.characterGuide.suggestedArmor': null }, { diff: false });
}
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 });
return data;
},
rejectClose: false
});
}
getActionPath(type) {
return type === 'hope' ? 'hopeFeatures' : 'classFeatures';
}
static async addFeature(_, target) {
const actionPath = this.getActionPath(target.dataset.type);
const actionType = await this.selectActionType();
const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack,
action = new cls(
{
_id: foundry.utils.randomID(),
systemPath: actionPath,
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.${actionPath}`]: [...this.document.system[actionPath], action] });
}
static async editFeature(_, target) {
const action = this.document.system[this.getActionPath(target.dataset.type)].find(
x => x._id === target.dataset.feature
);
await new DHActionConfig(action).render(true);
}
static async deleteFeature(_, target) {
const actionPath = this.getActionPath(target.dataset.type);
await this.document.update({
[`system.${actionPath}`]: this.document.system[actionPath].filter(
action => action._id !== target.dataset.feature
)
});
}
async _onDrop(event) {
const data = TextEditor.getDragEventData(event);
const item = await fromUuid(data.uuid);
const target = event.target.closest('fieldset.drop-section');
if (item.type === 'subclass') {
await this.document.update({
'system.subclasses': [
...this.document.system.subclasses,
{ img: item.img, name: item.name, uuid: item.uuid }
]
});
} else if (item.type === 'feature') {
await this.document.update({
'system.features': [
...this.document.system.features,
{ img: item.img, name: item.name, uuid: item.uuid }
]
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
});
} else if (item.type === 'weapon') {
if (event.currentTarget.classList.contains('primary-weapon-section')) {
if (target.classList.contains('primary-weapon-section')) {
if (!this.document.system.characterGuide.suggestedPrimaryWeapon && !item.system.secondary)
await this.document.update({
'system.characterGuide.suggestedPrimaryWeapon': {
img: item.img,
name: item.name,
uuid: item.uuid
}
'system.characterGuide.suggestedPrimaryWeapon': item.uuid
});
} else if (event.currentTarget.classList.contains('secondary-weapon-section')) {
} else if (target.classList.contains('secondary-weapon-section')) {
if (!this.document.system.characterGuide.suggestedSecondaryWeapon && item.system.secondary)
await this.document.update({
'system.characterGuide.suggestedSecondaryWeapon': {
img: item.img,
name: item.name,
uuid: item.uuid
}
'system.characterGuide.suggestedSecondaryWeapon': item.uuid
});
}
} else if (item.type === 'armor') {
if (event.currentTarget.classList.contains('armor-section')) {
if (target.classList.contains('armor-section')) {
if (!this.document.system.characterGuide.suggestedArmor)
await this.document.update({
'system.characterGuide.suggestedArmor': { img: item.img, name: item.name, uuid: item.uuid }
'system.characterGuide.suggestedArmor': item.uuid
});
}
} else if (event.currentTarget.classList.contains('choice-a-section')) {
} else if (target.classList.contains('choice-a-section')) {
if (item.type === 'miscellaneous' || item.type === 'consumable') {
if (this.document.system.inventory.choiceA.length < 2)
await this.document.update({
'system.inventory.choiceA': [
...this.document.system.inventory.choiceA,
{ img: item.img, name: item.name, uuid: item.uuid }
...this.document.system.inventory.choiceA.map(x => x.uuid),
item.uuid
]
});
}
} else if (item.type === 'miscellaneous') {
if (event.currentTarget.classList.contains('take-section')) {
if (target.classList.contains('take-section')) {
if (this.document.system.inventory.take.length < 3)
await this.document.update({
'system.inventory.take': [
...this.document.system.inventory.take,
{ img: item.img, name: item.name, uuid: item.uuid }
]
'system.inventory.take': [...this.document.system.inventory.take.map(x => x.uuid), item.uuid]
});
} else if (event.currentTarget.classList.contains('choice-b-section')) {
} else if (target.classList.contains('choice-b-section')) {
if (this.document.system.inventory.choiceB.length < 2)
await this.document.update({
'system.inventory.choiceB': [
...this.document.system.inventory.choiceB,
{ img: item.img, name: item.name, uuid: item.uuid }
...this.document.system.inventory.choiceB.map(x => x.uuid),
item.uuid
]
});
}

View file

@ -1,88 +1,13 @@
import DaggerheartSheet from '../daggerheart-sheet.mjs';
import DHHeritageSheetV2 from './heritage.mjs';
const { ItemSheetV2 } = foundry.applications.sheets;
export default class CommunitySheet extends DaggerheartSheet(ItemSheetV2) {
export default class CommunitySheet extends DHHeritageSheetV2(ItemSheetV2) {
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'community'],
position: { width: 450, height: 700 },
actions: {
editFeature: this.editFeature,
deleteFeature: this.deleteFeature
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
},
dragDrop: [{ dragSelector: null, dropSelector: null }]
classes: ['community']
};
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/community/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
features: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-feature-section.hbs',
scrollable: ['.features']
}
...super.PARTS
};
static TABS = {
description: {
active: true,
cssClass: '',
group: 'primary',
id: 'description',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Description'
},
features: {
active: false,
cssClass: '',
group: 'primary',
id: 'features',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Features'
}
};
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();
}
static async editFeature(_, target) {
const feature = await fromUuid(target.dataset.feature);
feature.sheet.render(true);
}
static async deleteFeature(event, target) {
event.preventDefault();
event.stopPropagation();
await this.item.update({
'system.abilities': this.item.system.abilities.filter(x => x.uuid !== target.dataset.feature)
});
}
async _onDrop(event) {
const data = TextEditor.getDragEventData(event);
const item = await fromUuid(data.uuid);
if (item.type === 'feature' && item.system.type === SYSTEM.ITEM.featureTypes.community.id) {
await this.document.update({
'system.abilities': [
...this.document.system.abilities,
{ img: item.img, name: item.name, uuid: item.uuid }
]
});
}
}
}

View file

@ -1,57 +1,23 @@
import DaggerheartSheet from '../daggerheart-sheet.mjs';
import DHItemSheetV2 from '../item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets;
export default class ConsumableSheet extends DaggerheartSheet(ItemSheetV2) {
export default class ConsumableSheet extends DHItemSheetV2(ItemSheetV2) {
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'consumable'],
position: { width: 550 },
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
classes: ['consumable'],
position: { width: 550 }
};
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/consumable/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.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: {
template: 'systems/daggerheart/templates/sheets/items/consumable/settings.hbs',
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 DaggerheartActionConfig from '../../config/Action.mjs';
import DaggerheartSheet from '../daggerheart-sheet.mjs';
import DHItemSheetV2 from '../item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets;
export default class DomainCardSheet extends DaggerheartSheet(ItemSheetV2) {
export default class DomainCardSheet extends DHItemSheetV2(ItemSheetV2) {
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'domain-card'],
position: { width: 450, height: 700 },
actions: {
addAction: this.addAction,
editAction: this.editAction,
removeAction: this.removeAction
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
classes: ['domain-card'],
position: { width: 450, height: 700 }
};
static PARTS = {
@ -33,74 +20,4 @@ export default class DomainCardSheet extends DaggerheartSheet(ItemSheetV2) {
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 DaggerheartActionConfig from '../../config/Action.mjs';
import DaggerheartSheet from '../daggerheart-sheet.mjs';
import DHItemSheetV2 from '../item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets;
export default class FeatureSheet extends DaggerheartSheet(ItemSheetV2) {
export default class FeatureSheet extends DHItemSheetV2(ItemSheetV2) {
constructor(options = {}) {
super(options);
@ -11,22 +9,13 @@ export default class FeatureSheet extends DaggerheartSheet(ItemSheetV2) {
}
static DEFAULT_OPTIONS = {
tag: 'form',
id: 'daggerheart-feature',
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'feature'],
classes: ['feature'],
position: { width: 600, height: 600 },
window: { resizable: true },
actions: {
addEffect: this.addEffect,
removeEffect: this.removeEffect,
addAction: this.addAction,
editAction: this.editAction,
removeAction: this.removeAction
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
removeEffect: this.removeEffect
}
};
@ -49,30 +38,7 @@ export default class FeatureSheet extends DaggerheartSheet(ItemSheetV2) {
};
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'
},
...super.TABS,
effects: {
active: false,
cssClass: '',
@ -102,11 +68,6 @@ export default class FeatureSheet extends DaggerheartSheet(ItemSheetV2) {
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
}
effectSelect(event) {
this.selectedEffectType = event.currentTarget.value;
this.render(true);
@ -130,26 +91,4 @@ export default class FeatureSheet extends DaggerheartSheet(ItemSheetV2) {
const path = `system.effects.-=${button.dataset.effect}`;
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

@ -0,0 +1,147 @@
import { actionsTypes } from '../../../data/_module.mjs';
import DHActionConfig from '../../config/Action.mjs';
import DHItemMixin from '../item.mjs';
export default function DHHeritageMixin(Base) {
return class DHHeritageSheetV2 extends DHItemMixin(Base) {
static DEFAULT_OPTIONS = {
tag: 'form',
position: { width: 450, height: 700 },
actions: {
addAction: this.addAction,
editAction: this.editAction,
removeAction: this.removeAction,
addEffect: this.addEffect,
editEffect: this.editEffect,
removeEffect: this.removeEffect
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
};
static PARTS = {
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.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']
},
effects: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
scrollable: ['.effects']
}
};
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'
},
effects: {
active: false,
cssClass: '',
group: 'primary',
id: 'effects',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Effects'
}
};
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();
}
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 });
return data;
},
rejectClose: false
});
}
static async addAction() {
const actionType = await this.selectActionType();
const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack,
action = new cls(
{
_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] });
}
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)
)
});
}
static async addEffect() {
await this.document.createEmbeddedDocuments('ActiveEffect', [
{ name: game.i18n.localize('DAGGERHEART.Feature.NewEffect') }
]);
}
static async editEffect(_, target) {
const effect = this.document.effects.get(target.dataset.effect);
effect.sheet.render(true);
}
static async removeEffect(_, target) {
await this.document.effects.get(target.dataset.effect).delete();
}
};
}

View file

@ -1,57 +1,23 @@
import DaggerheartSheet from '../daggerheart-sheet.mjs';
import DHItemSheetV2 from '../item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets;
export default class MiscellaneousSheet extends DaggerheartSheet(ItemSheetV2) {
export default class MiscellaneousSheet extends DHItemSheetV2(ItemSheetV2) {
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'miscellaneous'],
position: { width: 550 },
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
classes: ['miscellaneous'],
position: { width: 550 }
};
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/miscellaneous/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.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: {
template: 'systems/daggerheart/templates/sheets/items/miscellaneous/settings.hbs',
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,29 +1,24 @@
import DaggerheartSheet from '../daggerheart-sheet.mjs';
import DaggerheartFeature from '../../../data/feature.mjs';
import { actionsTypes } from '../../../data/_module.mjs';
import DHActionConfig from '../../config/Action.mjs';
import DhpApplicationMixin from '../daggerheart-sheet.mjs';
const { ItemSheetV2 } = foundry.applications.sheets;
const { TextEditor } = foundry.applications.ux;
const { duplicate, getProperty } = foundry.utils;
export default class SubclassSheet extends DaggerheartSheet(ItemSheetV2) {
export default class SubclassSheet extends DhpApplicationMixin(ItemSheetV2) {
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'subclass'],
position: { width: 600 },
window: { resizable: false },
actions: {
editAbility: this.editAbility,
deleteFeatureAbility: this.deleteFeatureAbility
addFeature: this.addFeature,
editFeature: this.editFeature,
deleteFeature: this.deleteFeature
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
},
dragDrop: [
{ dragSelector: null, dropSelector: '.foundation-tab' },
{ dragSelector: null, dropSelector: '.specialization-tab' },
{ dragSelector: null, dropSelector: '.mastery-tab' }
]
}
};
static PARTS = {
@ -81,41 +76,99 @@ export default class SubclassSheet extends DaggerheartSheet(ItemSheetV2) {
this.render();
}
static async editAbility(_, button) {
const feature = await fromUuid(button.dataset.ability);
feature.sheet.render(true);
static addFeature(_, target) {
if (target.dataset.type === 'action') this.addAction(target.dataset.level);
else this.addEffect(target.dataset.level);
}
static async deleteFeatureAbility(event, button) {
event.preventDefault();
event.stopPropagation();
const feature = button.dataset.feature;
const newAbilities = this.document.system[`${feature}Feature`].abilities.filter(
x => x.uuid !== button.dataset.ability
);
const path = `system.${feature}Feature.abilities`;
await this.document.update({ [path]: newAbilities });
static async editFeature(_, target) {
if (target.dataset.type === 'action') this.editAction(target.dataset.level, target.dataset.feature);
else this.editEffect(target.dataset.feature);
}
async _onDrop(event) {
event.preventDefault();
const data = TextEditor.getDragEventData(event);
const item = await fromUuid(data.uuid);
if (!(item.type === 'feature' && item.system.type === SYSTEM.ITEM.featureTypes.subclass.id)) return;
static async deleteFeature(_, target) {
if (target.dataset.type === 'action') this.removeAction(target.dataset.level, target.dataset.feature);
else this.removeEffect(target.dataset.level, target.dataset.feature);
}
let featureField;
if (event.currentTarget.classList.contains('foundation-tab')) featureField = 'foundation';
else if (event.currentTarget.classList.contains('specialization-tab')) featureField = 'specialization';
else if (event.currentTarget.classList.contains('mastery-tab')) featureField = 'mastery';
else return;
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 });
return data;
},
rejectClose: false
});
}
const path = `system.${featureField}Feature.abilities`;
const abilities = duplicate(getProperty(this.document, path)) || [];
const featureData = { name: item.name, img: item.img, uuid: item.uuid };
abilities.push(featureData);
async addAction(level) {
const actionType = await this.#selectActionType();
const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack,
action = new cls(
{
_id: foundry.utils.randomID(),
systemPath: `${level}.actions`,
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.${level}.actions`]: [...this.document.system[level].actions, action] });
await new DHActionConfig(
this.document.system[level].actions[this.document.system[level].actions.length - 1]
).render(true);
}
await this.document.update({ [path]: abilities });
async addEffect(level) {
const embeddedItems = await this.document.createEmbeddedDocuments('ActiveEffect', [
{ name: game.i18n.localize('DAGGERHEART.Feature.NewEffect') }
]);
await this.document.update({
[`system.${level}.effects`]: [
...this.document.system[level].effects.map(x => x.uuid),
embeddedItems[0].uuid
]
});
}
async editAction(level, id) {
const action = this.document.system[level].actions.find(x => x._id === id);
await new DHActionConfig(action).render(true);
}
async editEffect(id) {
const effect = this.document.effects.get(id);
effect.sheet.render(true);
}
async removeAction(level, id) {
await this.document.update({
[`system.${level}.actions`]: this.document.system[level].actions.filter(action => action._id !== id)
});
}
async removeEffect(level, id) {
await this.document.effects.get(id).delete();
await this.document.update({
[`system.${level}.effects`]: this.document.system[level].effects
.filter(x => x && x.id !== id)
.map(effect => effect.uuid)
});
}
}

View file

@ -1,58 +1,48 @@
import DaggerheartSheet from '../daggerheart-sheet.mjs';
import { weaponFeatures } from '../../../config/itemConfig.mjs';
import { tagifyElement } from '../../../helpers/utils.mjs';
import DHItemSheetV2 from '../item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets;
export default class WeaponSheet extends DaggerheartSheet(ItemSheetV2) {
export default class WeaponSheet extends DHItemSheetV2(ItemSheetV2) {
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'weapon'],
position: { width: 600 },
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
classes: ['weapon']
};
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/weapon/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.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: {
template: 'systems/daggerheart/templates/sheets/items/weapon/settings.hbs',
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 _preparePartContext(partId, context) {
super._preparePartContext(partId, context);
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.config = CONFIG.daggerheart;
context.tabs = super._getTabs(this.constructor.TABS);
switch (partId) {
case 'settings':
context.features = this.document.system.features.map(x => x.value);
break;
}
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
const featureInput = htmlElement.querySelector('.features-input');
tagifyElement(featureInput, weaponFeatures, this.onFeatureSelect.bind(this));
}
async onFeatureSelect(features) {
await this.document.update({ 'system.features': features.map(x => ({ value: x.value })) });
this.render(true);
}
}

File diff suppressed because it is too large Load diff