diff --git a/daggerheart.mjs b/daggerheart.mjs index eedf278f..9b149fbf 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -13,6 +13,7 @@ import { dualityRollEnricher } from './module/enrichers/DualityRollEnricher.mjs' import { getCommandTarget, rollCommandToJSON, setDiceSoNiceForDualityRoll } from './module/helpers/utils.mjs'; import { abilities } from './module/config/actorConfig.mjs'; import Resources from './module/applications/resources.mjs'; +import DHDualityRoll from './module/data/chat-message/dualityRoll.mjs'; globalThis.SYSTEM = SYSTEM; @@ -41,7 +42,7 @@ Hooks.once('init', () => { CONFIG.Item.dataModels = models.items.config; const { Items, Actors } = foundry.documents.collections; - Items.unregisterSheet('core', foundry.appv1.sheets.ItemSheet); + Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2); Items.registerSheet(SYSTEM.id, applications.DhpAncestry, { types: ['ancestry'], makeDefault: true }); Items.registerSheet(SYSTEM.id, applications.DhpCommunity, { types: ['community'], makeDefault: true }); Items.registerSheet(SYSTEM.id, applications.DhpClassSheet, { types: ['class'], makeDefault: true }); @@ -54,16 +55,23 @@ Hooks.once('init', () => { Items.registerSheet(SYSTEM.id, applications.DhpArmor, { types: ['armor'], makeDefault: true }); CONFIG.Actor.documentClass = documents.DhpActor; - CONFIG.Actor.dataModels = { - pc: models.DhpPC, - adversary: models.DhpAdversary, - environment: models.DhpEnvironment - }; - Actors.unregisterSheet('core', foundry.appv1.sheets.ActorSheet); - Actors.registerSheet(SYSTEM.id, applications.DhpPCSheet, { types: ['pc'], makeDefault: true }); + CONFIG.Actor.dataModels = models.actors.config; + + Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2); + Actors.registerSheet(SYSTEM.id, applications.DhCharacterSheet, { types: ['character'], makeDefault: true }); Actors.registerSheet(SYSTEM.id, applications.DhpAdversarySheet, { types: ['adversary'], makeDefault: true }); Actors.registerSheet(SYSTEM.id, applications.DhpEnvironment, { types: ['environment'], makeDefault: true }); + CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect; + DocumentSheetConfig.unregisterSheet( + CONFIG.ActiveEffect.documentClass, + 'core', + foundry.applications.sheets.ActiveEffectConfig + ); + DocumentSheetConfig.registerSheet(CONFIG.ActiveEffect.documentClass, SYSTEM.id, applications.DhActiveEffectConfig, { + makeDefault: true + }); + CONFIG.Combat.dataModels = { base: models.DhCombat }; @@ -138,22 +146,28 @@ const renderDualityButton = async event => { title: button.dataset.label, value: rollModifier }); + + const systemData = new DHDualityRoll({ + title: button.dataset.label, + origin: target.id, + roll: roll._formula, + modifiers: modifiers, + hope: hope, + fear: fear, + advantage: advantage, + disadvantage: disadvantage + }); + const cls = getDocumentClass('ChatMessage'); const msgData = { type: 'dualityRoll', sound: CONFIG.sounds.dice, - system: { - title: button.dataset.label, - origin: target.id, - roll: roll._formula, - modifiers: modifiers, - hope: hope, - fear: fear, - advantage: advantage, - disadvantage: disadvantage - }, + system: systemData, user: game.user.id, - content: 'systems/daggerheart/templates/chat/duality-roll.hbs', + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/chat/duality-roll.hbs', + systemData + ), rolls: [roll] }; @@ -227,29 +241,34 @@ Hooks.on('chatMessage', (_, message) => { : undefined, title }); - }).then(({ roll, attribute, title }) => { + }).then(async ({ roll, attribute, title }) => { const cls = getDocumentClass('ChatMessage'); + const systemData = new DHDualityRoll({ + title: title, + origin: target?.id, + roll: roll._formula, + modifiers: attribute ? [attribute] : [], + hope: { dice: rollCommand.hope ?? 'd12', value: roll.dice[0].total }, + fear: { dice: rollCommand.fear ?? 'd12', value: roll.dice[1].total }, + advantage: + rollCommand.advantage && !rollCommand.disadvantage + ? { dice: 'd6', value: roll.dice[2].total } + : undefined, + disadvantage: + rollCommand.disadvantage && !rollCommand.advantage + ? { dice: 'd6', value: roll.dice[2].total } + : undefined + }); + const msgData = { type: 'dualityRoll', sound: CONFIG.sounds.dice, - system: { - title: title, - origin: target?.id, - roll: roll._formula, - modifiers: attribute ? [attribute] : [], - hope: { dice: rollCommand.hope ?? 'd12', value: roll.dice[0].total }, - fear: { dice: rollCommand.fear ?? 'd12', value: roll.dice[1].total }, - advantage: - rollCommand.advantage && !rollCommand.disadvantage - ? { dice: 'd6', value: roll.dice[2].total } - : undefined, - disadvantage: - rollCommand.disadvantage && !rollCommand.advantage - ? { dice: 'd6', value: roll.dice[2].total } - : undefined - }, + system: systemData, user: game.user.id, - content: 'systems/daggerheart/templates/chat/duality-roll.hbs', + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/chat/duality-roll.hbs', + systemData + ), rolls: [roll] }; @@ -276,10 +295,10 @@ const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/sheets/parts/heritage.hbs', 'systems/daggerheart/templates/sheets/parts/subclassFeature.hbs', 'systems/daggerheart/templates/sheets/parts/effects.hbs', - 'systems/daggerheart/templates/sheets/pc/sections/inventory.hbs', - 'systems/daggerheart/templates/sheets/pc/sections/loadout.hbs', - 'systems/daggerheart/templates/sheets/pc/parts/heritageCard.hbs', - 'systems/daggerheart/templates/sheets/pc/parts/advancementCard.hbs', + 'systems/daggerheart/templates/sheets/character/sections/inventory.hbs', + 'systems/daggerheart/templates/sheets/character/sections/loadout.hbs', + 'systems/daggerheart/templates/sheets/character/parts/heritageCard.hbs', + 'systems/daggerheart/templates/sheets/character/parts/advancementCard.hbs', 'systems/daggerheart/templates/components/card-preview.hbs', 'systems/daggerheart/templates/views/levelup/parts/selectable-card-preview.hbs', 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs', diff --git a/lang/en.json b/lang/en.json index eb5ecd62..363a4e63 100755 --- a/lang/en.json +++ b/lang/en.json @@ -13,8 +13,7 @@ "armor": "Armor" }, "Actor": { - "pc": "PC", - "npc": "NPC", + "character": "Character", "adversary": "Adversary", "environment": "Environment" } @@ -110,8 +109,14 @@ }, "VariantRules": { "ActionTokens": { - "Name": "Action Tokens", - "Hint": "Give each player action tokens to use in combat" + "label": "Action Tokens", + "hint": "Give each player action tokens to use in combat" + }, + "FIELDS": { + "useCoins": { + "label": "Use Coins", + "hint": "test" + } } }, "DualityRollColor": { @@ -132,10 +137,6 @@ "AttackTargetDoesNotExist": "The target token no longer exists" }, "Error": { - "NoClassSelected": "Your character has no class selected!", - "LacksDomain": "Your character doesn't have the domain of the card!", - "MaxLoadoutReached": "You can't have any more domain cards at this level!", - "DuplicateDomainCard": "You already have a domain card with that name!", "ActionRequiresTarget": "The action requires at least one target", "NoAssignedPlayerCharacter": "You have no assigned character.", "NoSelectedToken": "You have no selected token", @@ -261,46 +262,54 @@ "Description": "When an effect makes a creature Restrained, it means they cannot move until this condition is cleared.\nThey can still take actions from their current position." } }, + "Tiers": { + "tier1": "Tier 1", + "tier2": "Tier 2", + "tier3": "Tier 3", + "tier4": "Tier 4" + }, "Adversary": { - "Bruiser": { - "Name": "Bruiser", - "Description": "Tough adversaries with powerful attacks." - }, - "Horde": { - "Name": "Horde", - "Description": "A Horde represents a number of foes working in a group." - }, - "Leader": { - "Name": "Leader", - "Description": "Adversaries that command and summon other adversaries." - }, - "Minion": { - "Name": "Minion", - "Description": "Basic enemies that are easily dispatched but dangerous in numbers." - }, - "Ranged": { - "Name": "Ranged", - "Description": "Adversaries that attack from a distance." - }, - "Skulker": { - "Name": "Skulker", - "Description": "Adversaries that maneuver and exploit opportunities to ambush their opponents." - }, - "Social": { - "Name": "Social", - "Description": "Adversaries that are primarily interpersonal threats or challenges." - }, - "Solo": { - "Name": "Solo", - "Description": "Designed to present a challenge to a whole party." - }, - "Standard": { - "Name": "Standard", - "Description": "Rank and File adversaries." - }, - "Support": { - "Name": "Support", - "Description": "Enemies that enhance their allies and/or disrupt their opponents." + "Type": { + "Bruiser": { + "label": "Bruiser", + "Description": "Tough adversaries with powerful attacks." + }, + "Horde": { + "label": "Horde", + "Description": "A Horde represents a number of foes working in a group." + }, + "Leader": { + "label": "Leader", + "Description": "Adversaries that command and summon other adversaries." + }, + "Minion": { + "label": "Minion", + "Description": "Basic enemies that are easily dispatched but dangerous in numbers." + }, + "Ranged": { + "label": "Ranged", + "Description": "Adversaries that attack from a distance." + }, + "Skulk": { + "label": "Skulk", + "Description": "Adversaries that maneuver and exploit opportunities to ambush their opponents." + }, + "Social": { + "label": "Social", + "Description": "Adversaries that are primarily interpersonal threats or challenges." + }, + "Solo": { + "label": "Solo", + "Description": "Designed to present a challenge to a whole party." + }, + "Standard": { + "label": "Standard", + "Description": "Rank and File adversaries." + }, + "Support": { + "label": "Support", + "Description": "Enemies that enhance their allies and/or disrupt their opponents." + } }, "Trait": { "Relentless": { @@ -320,6 +329,26 @@ } } }, + "Environment": { + "Type": { + "Exploration": { + "label": "Exploration", + "description": "" + }, + "Social": { + "label": "Social", + "description": "" + }, + "Traversal": { + "label": "Traversal", + "description": "" + }, + "Event": { + "label": "Event", + "description": "" + } + } + }, "Domains": { "Arcana": { "Description": "This is the domain of the innate or instinctual use of magic. Those who walk this path tap into the raw, enigmatic forces of the realms to manipulate both the elements and their own energy. Arcana offers wielders a volatile power, but it is incredibly potent when correctly channeled." @@ -999,40 +1028,81 @@ } }, "Adversary": { - "Description": "Description", - "MotivesAndTactics": "Motives & Tactics", - "Tier": "Tier", - "Type": "Type", - "Attack": { - "Title": "Attack", - "Modifier": "Attack Modifier", - "Name": "Name", - "Range": "Range", - "Damage": { - "Title": "Damage", - "Value": "Value", - "Type": "Type" + "FIELDS": { + "tier": { "label": "Tier" }, + "type": { "label": "Type" }, + "description": { "label": "Description" }, + "motivesAndTactics": { "label": "Motives & Tactics" }, + "difficulty": { "label": "Difficulty" }, + "damageThresholds": { + "major": { "label": "Major" }, + "severe": { "label": "Severe" } + }, + "resources": { + "hitPoints": { + "value": { "label": "Current" }, + "max": { "label": "Max" } + }, + "stress": { + "value": { "label": "Current" }, + "max": { "label": "Max" } + } + }, + "experiences": { + "element": { + "name": { "label": "Name" }, + "value": { "label": "Modifier" } + } + }, + "attack": { + "name": { "label": "Name" }, + "modifier": { "label": "Modifier" }, + "range": { "label": "Range" }, + "damage": { + "value": { "label": "Damage" }, + "type": { "label": "Damage Type" } + } } }, - "Difficulty": "Difficulty", - "Reaction": "Reaction Roll", - "DamageThresholds": { - "Title": "Damage Thresholds", - "Minor": "Minor", - "Major": "Major", - "Severe": "Severe" + "Tabs": { + "Main": "Data", + "Information": "Information" }, - "HP": "HP", + "General": "General", + "DamageThresholds": "Damage Thresholds", + "HitPoints": "Hit Points", "Stress": "Stress", - "Experience": "Experience", "Experiences": "Experiences", - "Features": "Features", - "NewFeature": "New Feature" + "Attack": "Attack" }, "Environment": { - "ToneAndFeel": "Tone And feel", - "PotentialAdversaries": "Potential Adversaries", - "NewFeature": "New Feature" + "FIELDS": { + "tier": { + "label": "Tier" + }, + "type": { + "label": "Type" + }, + "difficulty": { + "label": "Difficulty" + } + }, + "Tabs": { + "Main": "Data", + "Information": "Information" + }, + "general": "General", + "newAdversary": "New Adversary", + "newFeature": "New feature", + "description": "Description", + "impulses": "Impulses", + "potentialAdversaries": { + "label": "Potential Adversaries", + "placeholder": "Optionally drag and drop adversaries here" + }, + "features": { + "label": "Features" + } }, "Armor": { "baseScore": "Base Score", @@ -1183,7 +1253,11 @@ "MissingClass": "The character is missing a class", "SubclassNotInClass": "The subclass does not belong to the character's class", "ClassAlreadySelected": "The character already has a class", - "SubclassAlreadySelected": "The character already has a subclass for that class." + "SubclassAlreadySelected": "The character already has a subclass for that class.", + "NoClassSelected": "Your character has no class selected!", + "LacksDomain": "Your character doesn't have the domain of the card!", + "MaxLoadoutReached": "You can't have any more domain cards at this level!", + "DuplicateDomainCard": "You already have a domain card with that name!" } }, "Effects": { diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index c879f315..5ee13189 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -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,5 +12,6 @@ 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'; -export * as pseudoDocumentSheet from "./sheets/pseudo-documents/_module.mjs"; \ No newline at end of file +export * as pseudoDocumentSheet from './sheets/pseudo-documents/_module.mjs'; diff --git a/module/applications/chatMessage.mjs b/module/applications/chatMessage.mjs index 30bd0a27..aef05bae 100644 --- a/module/applications/chatMessage.mjs +++ b/module/applications/chatMessage.mjs @@ -1,12 +1,8 @@ import { DualityRollColor } from '../data/settings/Appearance.mjs'; -import DHDualityRoll from "../data/chat-message/dualityRoll.mjs"; +import DHDualityRoll from '../data/chat-message/dualityRoll.mjs'; export default class DhpChatMessage extends foundry.documents.ChatMessage { async renderHTML() { - if (this.type === 'dualityRoll' || this.type === 'adversaryRoll' || this.type === 'abilityUse') { - 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(); diff --git a/module/applications/levelup.mjs b/module/applications/levelup.mjs index 22c6d33e..61a85677 100644 --- a/module/applications/levelup.mjs +++ b/module/applications/levelup.mjs @@ -149,7 +149,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, @@ -201,7 +204,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) const multiclassSubclass = this.actor.system.multiclass?.system?.subclasses?.[0]; const possibleSubclasses = [ - this.actor.system.subclass, + this.actor.system.class.subclass, ...(multiclassSubclass ? [multiclassSubclass] : []) ]; const selectedSubclasses = possibleSubclasses.filter(x => subclassSelections.includes(x.uuid)); @@ -274,8 +277,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, + new: this.actor.system.proficiency + achivementProficiency, shown: achivementProficiency > 0 }, damageThresholds: { @@ -328,10 +331,12 @@ 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; } @@ -354,8 +359,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) new: this.actor.system.resources.stress.max + (advancement.stress ?? 0) }, evasion: { - old: this.actor.system.evasion.value, - new: this.actor.system.evasion.value + (advancement.evasion ?? 0) + old: this.actor.system.evasion, + new: this.actor.system.evasion + (advancement.evasion ?? 0) } }, traits: @@ -421,8 +426,9 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) 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; }, {}), diff --git a/module/applications/multiclassDialog.mjs b/module/applications/multiclassDialog.mjs deleted file mode 100644 index f9e53c17..00000000 --- a/module/applications/multiclassDialog.mjs +++ /dev/null @@ -1,115 +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 : []; - - //FIXME - 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); - } -} diff --git a/module/applications/rollSelectionDialog.mjs b/module/applications/rollSelectionDialog.mjs index 3230cf72..e97d7cf1 100644 --- a/module/applications/rollSelectionDialog.mjs +++ b/module/applications/rollSelectionDialog.mjs @@ -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; @@ -17,23 +17,13 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl 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, @@ -41,8 +31,6 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl }, actions: { selectExperience: this.selectExperience, - decreaseHopeUse: this.decreaseHopeUse, - increaseHopeUse: this.increaseHopeUse, setAdvantage: this.setAdvantage, setDisadvantage: this.setDisadvantage, finish: this.finish @@ -74,27 +62,14 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl 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,35 +79,12 @@ 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; @@ -149,11 +101,10 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl 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(); } diff --git a/module/applications/sheets/activeEffectConfig.mjs b/module/applications/sheets/activeEffectConfig.mjs new file mode 100644 index 00000000..585086a1 --- /dev/null +++ b/module/applications/sheets/activeEffectConfig.mjs @@ -0,0 +1,62 @@ +export default class DhActiveEffectConfig extends 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; + } +} diff --git a/module/applications/sheets/adversary.mjs b/module/applications/sheets/adversary.mjs index b9180bf3..0196a8c8 100644 --- a/module/applications/sheets/adversary.mjs +++ b/module/applications/sheets/adversary.mjs @@ -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,43 +60,6 @@ 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 }, @@ -349,9 +86,8 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { cls.create(msg.toObject()); } - static async attackRoll(event, button) { - const modifier = Number.parseInt(button.dataset.value); - + static async attackRoll() { + const { modifier, damage, name: attackName } = this.actor.system.attack; const { roll, dice, advantageState, modifiers } = await this.actor.diceRoll( { title: `${this.actor.name} - Attack Roll`, value: modifier }, event.shiftKey @@ -362,12 +98,12 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { name: x.actor.name, img: x.actor.img, difficulty: x.actor.system.difficulty, - evasion: x.actor.system.evasion.value + evasion: x.actor.system.evasion })); const cls = getDocumentClass('ChatMessage'); const systemData = { - title: button.dataset.name, + title: attackName, origin: this.document.id, roll: roll._formula, advantageState, @@ -375,7 +111,7 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { modifiers: modifiers, dice: dice, targets: targets, - damage: { value: button.dataset.damage, type: button.dataset.damageType } + damage: { value: damage.value, type: damage.type } }; const msg = new cls({ type: 'adversaryRoll', diff --git a/module/applications/sheets/character.mjs b/module/applications/sheets/character.mjs new file mode 100644 index 00000000..8eaf67aa --- /dev/null +++ b/module/applications/sheets/character.mjs @@ -0,0 +1,703 @@ +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, + 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)); + } + + 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 = JSON.parse( + await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.General.AbilityArray) + ).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 { 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); + const damage = { + value: `${this.document.system.proficiency}${weapon.system.damage.value}`, + type: weapon.system.damage.type + }; + const modifier = this.document.system.traits[weapon.system.trait].value; + + const { roll, hope, fear, advantage, disadvantage, modifiers } = await this.document.dualityRoll( + { title: game.i18n.localize(abilities[weapon.system.trait].label), value: modifier }, + event.shiftKey + ); + + const targets = Array.from(game.user.targets).map(x => ({ + id: x.id, + name: x.actor.name, + img: x.actor.img, + difficulty: x.actor.system.difficulty, + evasion: x.actor.system.evasion + })); + + const systemData = new DHDualityRoll({ + title: weapon.name, + origin: this.document.id, + roll: roll._formula, + modifiers: modifiers, + hope: hope, + fear: fear, + advantage: advantage, + disadvantage: disadvantage, + damage: damage, + targets: targets + }); + + const cls = getDocumentClass('ChatMessage'); + const msg = new cls({ + type: 'dualityRoll', + sound: CONFIG.sounds.dice, + system: systemData, + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/chat/attack-roll.hbs', + systemData + ), + rolls: [roll] + }); + + await cls.create(msg.toObject()); + } + + static openLevelUp() { + 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 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); + } + + 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); + } +} diff --git a/module/applications/sheets/environment.mjs b/module/applications/sheets/environment.mjs index 8799d41a..7c59fd55 100644 --- a/module/applications/sheets/environment.mjs +++ b/module/applications/sheets/environment.mjs @@ -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 + }); + } } } diff --git a/module/applications/sheets/pc.mjs b/module/applications/sheets/pc.mjs deleted file mode 100644 index 7433e3f3..00000000 --- a/module/applications/sheets/pc.mjs +++ /dev/null @@ -1,1241 +0,0 @@ -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'; - -const { ActorSheetV2 } = foundry.applications.sheets; -const { TextEditor } = foundry.applications.ux; -export default class PCSheet extends DaggerheartSheet(ActorSheetV2) { - constructor(options = {}) { - super(options); - - this.editAttributes = false; - this.onVaultTab = false; - this.currentInventoryPage = 0; - this.selectedScar = null; - this.storyEditor = null; - this.dropItemBlock = false; - this.multiclassFeatureSetSelected = false; - } - - static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'pc'], - position: { width: 810, height: 1080 }, - actions: { - toggleEditAttributes: this.toggleEditAttributes, - attributeRoll: this.rollAttribute, - toggleMarks: this.toggleMarks, - toggleAttributeMark: this.toggleAttributeMark, - toggleHP: this.toggleHP, - toggleStress: this.toggleStress, - toggleHope: this.toggleHope, - toggleGold: this.toggleGold, - attackRoll: this.attackRoll, - tabToLoadout: () => this.domainCardsTab(false), - tabToVault: () => this.domainCardsTab(true), - sendToVault: this.moveDomainCard, - sendToLoadout: this.moveDomainCard, - useDomainCard: this.useDomainCard, - removeCard: this.removeDomainCard, - selectClass: this.selectClass, - selectSubclass: this.selectSubclass, - selectAncestry: this.selectAncestry, - selectCommunity: this.selectCommunity, - viewObject: this.viewObject, - useFeature: this.useFeature, - takeShortRest: this.takeShortRest, - takeLongRest: this.takeLongRest, - addMiscItem: this.addMiscItem, - deleteItem: this.deleteItem, - addScar: this.addScar, - selectScar: this.selectScar, - deleteScar: this.deleteScar, - makeDeathMove: this.makeDeathMove, - toggleFeatureDice: this.toggleFeatureDice, - setStoryEditor: this.setStoryEditor, - itemQuantityDecrease: (_, button) => this.setItemQuantity(button, -1), - itemQuantityIncrease: (_, button) => this.setItemQuantity(button, 1), - useAbility: this.useAbility, - useAdvancementCard: this.useAdvancementCard, - useAdvancementAbility: this.useAdvancementAbility, - selectFeatureSet: this.selectFeatureSet, - toggleEquipItem: this.toggleEquipItem - }, - 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: null, dropSelector: '.inventory-weapon-section-first' }, - { dragSelector: null, dropSelector: '.inventory-weapon-section-second' }, - { dragSelector: '.item-list .item', dropSelector: null } - ] - }; - - static PARTS = { - form: { - id: 'pc', - template: 'systems/daggerheart/templates/sheets/pc/pc.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 - .querySelectorAll('.attribute-value') - .forEach(element => element.addEventListener('change', this.attributeChange.bind(this))); - htmlElement - .querySelectorAll('.tab-selector') - .forEach(element => element.addEventListener('click', this.tabSwitch.bind(this))); - htmlElement.querySelector('.level-title.levelup')?.addEventListener('click', this.openLevelUp.bind(this)); - htmlElement - .querySelectorAll('.feature-input') - .forEach(element => element.addEventListener('change', this.onFeatureInputBlur.bind(this))); - htmlElement - .querySelectorAll('.experience-description') - .forEach(element => element.addEventListener('change', this.experienceDescriptionChange.bind(this))); - htmlElement - .querySelectorAll('.experience-value') - .forEach(element => element.addEventListener('change', this.experienceValueChange.bind(this))); - htmlElement.querySelector('.level-value').addEventListener('change', this.onLevelChange.bind(this)); - } - - async _prepareContext(_options) { - const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = this._getTabs(); - - context.config = SYSTEM; - context.editAttributes = this.editAttributes; - context.onVaultTab = this.onVaultTab; - context.selectedScar = this.selectedScar; - context.storyEditor = this.storyEditor; - context.multiclassFeatureSetSelected = this.multiclassFeatureSetSelected; - - const selectedAttributes = Object.values(this.document.system.traits).map(x => x.base); - context.abilityScoreArray = JSON.parse( - await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.General.AbilityArray) - ).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); - - //FIXME: - context.domains = this.document.system.class.value - ? { - first: this.document.system.class.value.system.domains[0] - ? SYSTEM.DOMAIN.domains[this.document.system.class.value.system.domains[0]].src - : null, - second: this.document.system.class.value.system.domains[1] - ? SYSTEM.DOMAIN.domains[this.document.system.class.value.system.domains[1]].src - : null - } - : {}; - - 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: { - ...this.mapAdvancementFeatures(this.document, SYSTEM) - } - }; - - 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 >= this.document.system.domainData.maxLoadout - })) - }; - - 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([])); - } - - context.classFeatures = ( - this.multiclassFeatureSetSelected - ? this.document.system.multiclassFeatures - : this.document.system.classFeatures - ).map(x => { - if (x.system.featureType.type !== 'dice') { - return x; - } - - return { - ...x, - uuid: x.uuid, - system: { - ...x.system, - featureType: { - ...x.system.featureType, - data: { - ...x.system.featureType.data, - property: this.document.system.subclass - ? SYSTEM.ACTOR.featureProperties[x.system.featureType.data.property].path(this.document) - : 0 - } - } - } - }; - }); - - 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) - } - }; - }) - ); - } - - mapAdvancementFeatures(actor, config) { - if (!actor.system.class.value || !actor.system.class.subclass) return { foundation: null, advancements: [] }; - - const { subclass, multiclassSubclass } = actor.system.subclassFeatures; - - const foundation = { - type: 'foundation', - multiclass: false, - img: actor.system.subclass.img, - subtitle: game.i18n.localize('DAGGERHEART.Sheets.PC.DomainCard.FoundationTitle'), - domains: actor.system.class.value.system.domains.map(x => config.DOMAIN.domains[x].src), - className: actor.system.class.value.name, - subclassUuid: actor.system.subclass.uuid, - subclassName: actor.system.subclass.name, - spellcast: config.ACTOR.abilities[actor.system.subclass.system.spellcastingTrait]?.name ?? null, - description: actor.system.subclass.system.foundationFeature.description, - abilities: subclass.foundation, - abilityKey: 'foundationFeature' - }; - - const firstKey = - actor.system.subclass.system.specializationFeature.unlocked && - actor.system.subclass.system.specializationFeature.tier === 2 - ? 'sub' - : actor.system.multiclass?.system?.multiclassTier === 2 - ? 'multi' - : null; - const firstType = firstKey === 'sub' ? 'specialization' : 'foundation'; - const firstBase = - firstKey === 'sub' ? actor.system.subclass : firstKey === 'multi' ? actor.system.multiclassSubclass : null; - const first = !firstBase - ? null - : { - type: firstType, - multiclass: firstKey === 'multi', - img: firstBase.img, - subtitle: - firstKey === 'sub' - ? game.i18n.localize('DAGGERHEART.Sheets.PC.DomainCard.SpecializationTitle') - : game.i18n.localize('DAGGERHEART.Sheets.PC.DomainCard.FoundationTitle'), - domains: - firstKey === 'sub' - ? actor.system.class.value.system.domains.map(x => config.DOMAIN.domains[x].src) - : actor.system.multiclass.system.domains.map(x => config.DOMAIN.domains[x].src), - className: firstKey === 'sub' ? actor.system.class.value.name : actor.system.multiclass.name, - subclassUuid: firstBase.uuid, - subclassName: firstBase.name, - spellcast: - firstKey === 'sub' - ? null - : (config.ACTOR.abilities[firstBase.system.spellcastingTrait]?.name ?? null), - description: - firstKey === 'sub' - ? firstBase.system.specializationFeature.description - : firstBase.system.foundationFeature.description, - abilities: firstKey === 'sub' ? subclass.specialization : multiclassSubclass.foundation, - abilityKey: firstKey === 'sub' ? 'specializationFeature' : 'foundationFeature' - }; - - const secondKey = - (actor.system.subclass.system.specializationFeature.unlocked && - actor.system.subclass.system.specializationFeature.tier === 3) || - (actor.system.subclass.system.masteryFeature.unlocked && - actor.system.subclass.system.masteryFeature.tier === 3) - ? 'sub' - : actor.system.multiclass?.system?.multiclassTier === 3 || - actor.system.multiclassSubclass?.system?.specializationFeature?.unlocked - ? 'multi' - : null; - const secondBase = - secondKey === 'sub' - ? actor.system.subclass - : secondKey === 'multi' - ? actor.system.multiclassSubclass - : null; - const secondAbilities = secondKey === 'sub' ? subclass : multiclassSubclass; - const secondType = secondBase - ? secondBase.system.masteryFeature.unlocked - ? 'mastery' - : secondBase.system.specializationFeature.unlocked - ? 'specialization' - : 'foundation' - : null; - const second = !secondBase - ? null - : { - type: secondType, - multiclass: secondKey === 'multi', - img: secondBase.img, - subtitle: secondBase.system.masteryFeature.unlocked - ? game.i18n.localize('DAGGERHEART.Sheets.PC.DomainCard.MasteryTitle') - : secondBase.system.specializationFeature.unlocked - ? game.i18n.localize('DAGGERHEART.Sheets.PC.DomainCard.SpecializationTitle') - : game.i18n.localize('DAGGERHEART.Sheets.PC.DomainCard.FoundationTitle'), - domains: - secondKey === 'sub' - ? actor.system.class.value.system.domains.map(x => config.DOMAIN.domains[x].src) - : actor.system.multiclass.system.domains.map(x => config.DOMAIN.domains[x].src), - className: secondKey === 'sub' ? actor.system.class.value.name : actor.system.multiclass.name, - subclassUuid: secondBase.uuid, - subclassName: secondBase.name, - spellcast: - secondKey === 'sub' || secondBase.system.specializationFeature.unlocked - ? null - : (config.ACTOR.abilities[firstBase.system.spellcastingTrait]?.name ?? null), - description: secondBase.system.masteryFeature.unlocked - ? secondBase.system.masteryFeature.description - : secondBase.system.specializationFeature.unlocked - ? secondBase.system.specializationFeature.description - : firstBase.system.foundationFeature.description, - abilities: secondBase.system.masteryFeature.unlocked - ? secondAbilities.mastery - : secondBase.system.specializationFeature.unlocked - ? secondAbilities.specialization - : secondAbilities.foundation, - abilityKey: secondBase.system.masteryFeature.unlocked - ? 'masteryFeature' - : secondBase.system.specializationFeature.unlocked - ? 'specializationFeature' - : 'foundationFeature' - }; - - return { - foundation: foundation, - first: first, - second: second - }; - } - - async attributeChange(event) { - const path = `system.traits.${event.currentTarget.dataset.attribute}.base`; - await this.document.update({ [path]: event.currentTarget.value }); - } - - static toggleEditAttributes() { - this.editAttributes = !this.editAttributes; - this.render(); - } - - static async rollAttribute(event, button) { - 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 = { - 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 - }; - - const msg = new cls({ - 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] - }); - - await cls.create(msg.toObject()); - } - - 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 toggleAttributeMark(_, button) { - const attribute = this.document.system.traits[button.dataset.attribute]; - const newMark = this.document.system.availableAttributeMarks - .filter(x => x > Math.max.apply(null, this.document.system.traits[button.dataset.attribute].levelMarks)) - .sort((a, b) => (a > b ? 1 : -1))[0]; - - if (attribute.levelMark || !newMark) return; - - const path = `system.traits.${button.dataset.attribute}.levelMarks`; - await this.document.update({ [path]: [...attribute.levelMarks, newMark] }); - } - - 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); - const damage = { - value: `${this.document.system.proficiency.value}${weapon.system.damage.value}`, - type: weapon.system.damage.type, - bonusDamage: this.document.system.bonuses.damage - }; - const modifier = this.document.system.traits[weapon.system.trait].value; - - const { roll, hope, fear, advantage, disadvantage, modifiers, bonusDamageString } = - await this.document.dualityRoll( - { title: game.i18n.localize(abilities[weapon.system.trait].label), value: modifier }, - event.shiftKey, - damage.bonusDamage - ); - - damage.value = damage.value.concat(bonusDamageString); - - 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 systemData = { - title: weapon.name, - origin: this.document.id, - roll: roll._formula, - modifiers: modifiers, - hope: hope, - fear: fear, - advantage: advantage, - disadvantage: disadvantage, - damage: damage, - targets: targets - }; - - const cls = getDocumentClass('ChatMessage'); - const msg = new cls({ - type: 'dualityRoll', - sound: CONFIG.sounds.dice, - system: systemData, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/chat/attack-roll.hbs', - systemData - ), - rolls: [roll] - }); - - await cls.create(msg.toObject()); - } - - tabSwitch(event) { - const tab = event.currentTarget.dataset.tab; - if (tab !== 'loadout') { - this.onVaultTab = false; - } - - this.render(); - } - - openLevelUp() { - if (!this.document.system.class.value || !this.document.system.subclass) { - ui.notifications.error(game.i18n.localize('DAGGERHEART.Sheets.PC.Errors.missingClassOrSubclass')); - return; - } - - new DhlevelUp(this.document).render(true); - } - - static domainCardsTab(toVault) { - this.onVaultTab = toVault; - this.render(); - } - - static async moveDomainCard(_, button) { - const toVault = button.dataset.action === 'sendToVault'; - if (!toVault && this.document.system.domainCards.loadout.length >= this.document.system.domainData.maxLoadout) { - return; - } - - const card = this.document.items.find(x => x.uuid === button.dataset.domain); - await card.update({ 'system.inVault': toVault }); - } - - 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; - - // await this.emulateItemDrop({ type: 'item', data: result }); - 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.system.abilities); - createdItems.push(result.data); - - await this.document.createEmbeddedDocuments('Item', createdItems); - - // await this.document.createEmbeddedDocuments("Item", [result.toObject()]); - // (await game.packs.get('daggerheart.playtest-ancestries'))?.render(true); - } - - static async selectCommunity() { - (await game.packs.get('daggerheart.communities'))?.render(true); - } - - 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); - } - - 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 addMiscItem() { - const result = await this.document.createEmbeddedDocuments('Item', [ - { - name: game.i18n.localize('DAGGERHEART.Sheets.PC.NewItem'), - type: 'miscellaneous' - } - ]); - - await result[0].sheet.render(true); - } - - 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 selectScar(_, button) { - this.selectedScar = Number.parseInt(button.dataset.value); - this.render(); - } - - 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(); - } - } - - static async toggleFeatureDice(_, button) { - const index = Number.parseInt(button.dataset.index); - const feature = this.document.system.classFeatures.find(x => x.uuid === button.dataset.feature); - const path = `system.featureType.data.numbers.${index}`; - if (feature.system.featureType.data.numbers[index]?.used) return; - - if (Object.keys(feature.system.featureType.data.numbers).length <= index) { - const roll = new Roll(feature.system.featureType.data.value); - const rollData = await roll.evaluate(); - const cls = getDocumentClass('ChatMessage'); - const msg = new cls({ - user: game.user.id, - rolls: [roll] - }); - - await cls.create(msg.toObject()); - - await feature.update({ [path]: { value: Number.parseInt(rollData.total), used: false } }); - } else { - await Dialog.confirm({ - title: game.i18n.localize('Confirm feature use'), - content: `Are you sure you want to use ${feature.name}?`, - yes: async () => { - await feature.update({ [path]: { used: true } }); - - 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.localize('DAGGERHEART.Chat.FeatureTitle'), - card: { - name: `${feature.name} - Roll Of ${feature.system.featureType.data.numbers[index].value}`, - img: feature.img - } - } - ) - }); - - cls.create(msg.toObject()); - }, - no: () => { - return; - }, - defaultYes: false - }); - } - } - - async onFeatureInputBlur(event) { - const feature = this.document.system.classFeatures.find(x => x.uuid === event.currentTarget.dataset.feature); - const value = Number.parseInt(event.currentTarget.value); - if (!Number.isNaN(value)) await feature?.update({ 'system.featureType.data.value': value }); - } - - async experienceDescriptionChange(event) { - const newExperiences = [...this.document.system.experiences]; - newExperiences[event.currentTarget.dataset.index].description = event.currentTarget.value; - await this.document.update({ 'system.experiences': newExperiences }); - } - - async experienceValueChange(event) { - const newExperiences = [...this.document.system.experiences]; - newExperiences[event.currentTarget.dataset.index].value = event.currentTarget.value; - await this.document.update({ 'system.experiences': newExperiences }); - } - - static setStoryEditor(_, button) { - this.storyEditor = this.storyEditor === button.dataset.value ? null : button.dataset.value; - this.render(); - } - - 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.multiclassSubclass - : this.document.system.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 = await fromUuid(button.dataset.id); - 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 selectFeatureSet(_, button) { - const multiclass = button.dataset.multiclass === 'true'; - this.multiclassFeatureSetSelected = multiclass; - this.render(); - } - - 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(); - } - - static async close(options) { - this.onVaultTab = false; - super.close(options); - } - - async _onDragStart(_, event) { - if (event.currentTarget.classList.contains('inventory-item')) { - if (!['weapon', 'armor'].includes(event.currentTarget.dataset.type)) { - return; - } - - const targets = { - weapon: ['weapon-section', 'inventory-weapon-section'], - armor: ['armor-section', 'inventory-armor-section'] - }; - - event.dataTransfer.setData( - 'text/plain', - JSON.stringify({ - uuid: event.currentTarget.dataset.item, - internal: true, - targets: targets[event.currentTarget.dataset.type] - }) - ); - } - - super._onDragStart(event); - } - - async _onDrop(event) { - const itemData = event.dataTransfer?.getData('text/plain'); - const item = itemData ? JSON.parse(itemData) : null; - if (item?.internal) { - let target = null; - event.currentTarget.classList.forEach(x => { - if (item.targets.some(target => target === x)) { - target = x; - } - }); - if (target) { - const itemObject = await fromUuid(item.uuid); - switch (target) { - case 'weapon-section': - if ( - itemObject.system.secondary && - this.document.system.equippedWeapons.burden === 'twoHanded' - ) { - ui.notifications.info( - game.i18n.localize('DAGGERHEART.Notification.Info.SecondaryEquipWhileTwohanded') - ); - return; - } else if ( - itemObject.system.burden === 'twoHanded' && - this.document.system.equippedWeapons.secondary - ) { - ui.notifications.info( - game.i18n.localize('DAGGERHEART.Notification.Info.TwohandedEquipWhileSecondary') - ); - return; - } - - const existingWeapon = this.document.items.find( - x => x.system.active && x.system.secondary === itemObject.system.secondary - ); - await existingWeapon?.update({ 'system.active': false }); - await itemObject.update({ 'system.active': true }); - break; - case 'armor-section': - const existingArmor = this.document.items.find(x => x.type === 'armor' && x.system.active); - await existingArmor?.update({ 'system.active': false }); - await itemObject.update({ 'system.active': true }); - break; - case 'inventory-weapon-section': - /* FIXME inventoryWeapon is no longer a field - const existingInventoryWeapon = this.document.items.find(x => x.system.inventoryWeapon); - await existingInventoryWeapon?.update({ 'system.inventoryWeapon': false }); - await itemObject.update({ 'system.inventoryWeapon': true }); - break; - */ - case 'inventory-armor-section': - const existingInventoryArmor = this.document.items.find(x => x.system.inventoryArmor); - await existingInventoryArmor?.update({ 'system.inventoryArmor': false }); - await itemObject.update({ 'system.inventoryArmor': true }); - break; - } - } - } else { - super._onDrop(event); - this._onDropItem(event, TextEditor.getDragEventData(event)); - } - } - - async _onDropItem(event, data) { - if (this.dropItemBlock) { - return; - } else { - this.dropItemBlock = true; - setTimeout(() => (this.dropItemBlock = false), 500); - } - - const element = event.currentTarget; - const item = await Item.implementation.fromDropData(data); - const itemData = item.toObject(); - - const createdItems = []; - - if (item.type === 'domainCard') { - if (!this.document.system.class.value) { - ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.NoClassSelected')); - return; - } - - if (!this.document.system.domains.find(x => x === item.system.domain)) { - ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.LacksDomain')); - return; - } - - if (this.document.system.domainCards.total.length === this.document.system.domainData.maxCards) { - ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.MaxLoadoutReached')); - return; - } - - if (this.document.system.domainCards.total.find(x => x.name === item.name)) { - ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.DuplicateDomainCard')); - return; - } - - if (this.document.system.domainCards.loadout.length >= this.document.system.domainData.maxLoadout) { - itemData.system.inVault = true; - } - - if (this.document.uuid === item.parent?.uuid) return this._onSortItem(event, itemData); - const createdItem = await this._onDropItemCreate(itemData); - - return createdItem; - } else { - if (item.type === 'ancestry') { - for (var feature of this.document.items.filter( - x => x.type === 'feature' && x.system.type === SYSTEM.ITEM.featureTypes.ancestry.id - )) { - await feature.delete(); - } - - for (var feature of item.system.abilities) { - const data = (await fromUuid(feature.uuid)).toObject(); - const itemData = await this._onDropItemCreate(data); - createdItems.push(itemData); - } - } else if (item.type === 'community') { - for (var feature of this.document.items.filter( - x => x.type === 'feature' && x.system.type === SYSTEM.ITEM.featureTypes.community.id - )) { - await feature.delete(); - } - - for (var feature of item.system.abilities) { - const data = (await fromUuid(feature.uuid)).toObject(); - const itemData = await this._onDropItemCreate(data); - createdItems.push(itemData); - } - } - - if (this.document.uuid === item.parent?.uuid) return this._onSortItem(event, item); - - if (item.type === 'weapon') { - if (!element) return; - - if (element.classList.contains('weapon-section')) { - await this.document.system.constructor.unequipBeforeEquip.bind(this.document.system)(itemData); - itemData.system.equipped = true; - } - } - - if (item.type === 'armor') { - if (!element) return; - - if (element.classList.contains('armor-section')) { - const existing = this.document.system.armor - ? await fromUuid(this.document.system.armor.uuid) - : null; - await existing?.update({ 'system.equipped': false }); - itemData.system.equipped = true; - } - } - - const createdItem = await this._onDropItemCreate(itemData); - createdItems.push(createdItem); - - return createdItems; - } - } - - async _onDropItemCreate(itemData, event) { - itemData = itemData instanceof Array ? itemData : [itemData]; - return this.document.createEmbeddedDocuments('Item', itemData); - } - - async emulateItemDrop(data) { - const event = new DragEvent('drop', { altKey: game.keyboard.isModifierActive('Alt') }); - return this._onDropItem(event, data); - } -} diff --git a/module/config/actorConfig.mjs b/module/config/actorConfig.mjs index 4db5ca9c..3698d19e 100644 --- a/module/config/actorConfig.mjs +++ b/module/config/actorConfig.mjs @@ -76,53 +76,82 @@ export const featureProperties = { }, spellcastingTrait: { name: 'DAGGERHEART.FeatureProperty.SpellcastingTrait', - path: actor => actor.system.traits[actor.system.subclass.system.spellcastingTrait].data.value + path: actor => actor.system.traits[actor.system.class.subclass.system.spellcastingTrait].data.value } }; export const adversaryTypes = { bruiser: { - name: 'DAGGERHEART.Adversary.Bruiser.Name', + id: 'bruiser', + label: 'DAGGERHEART.Adversary.Type.Bruiser.label', description: 'DAGGERHEART.Adversary.Bruiser.Description' }, horde: { - name: 'DAGGERHEART.Adversary.Horde.Name', + id: 'horde', + label: 'DAGGERHEART.Adversary.Type.Horde.label', description: 'DAGGERHEART.Adversary.Horde.Description' }, leader: { - name: 'DAGGERHEART.Adversary.Leader.Name', + id: 'leader', + label: 'DAGGERHEART.Adversary.Type.Leader.label', description: 'DAGGERHEART.Adversary.Leader.Description' }, minion: { - name: 'DAGGERHEART.Adversary.Minion.Name', + id: 'minion', + label: 'DAGGERHEART.Adversary.Type.Minion.label', description: 'DAGGERHEART.Adversary.Minion.Description' }, ranged: { - name: 'DAGGERHEART.Adversary.Ranged.Name', + id: 'ranged', + label: 'DAGGERHEART.Adversary.Type.Ranged.label', description: 'DAGGERHEART.Adversary.Ranged.Description' }, - skulker: { - name: 'DAGGERHEART.Adversary.Skulker.Name', - description: 'DAGGERHEART.Adversary.Skulker.Description' + skulk: { + id: 'skulk', + label: 'DAGGERHEART.Adversary.Type.Skulk.label', + description: 'DAGGERHEART.Adversary.Skulk.Description' }, social: { - name: 'DAGGERHEART.Adversary.Social.Name', + id: 'social', + label: 'DAGGERHEART.Adversary.Type.Social.label', description: 'DAGGERHEART.Adversary.Social.Description' }, solo: { - name: 'DAGGERHEART.Adversary.Solo.Name', + id: 'solo', + label: 'DAGGERHEART.Adversary.Type.Solo.label', description: 'DAGGERHEART.Adversary.Solo.Description' }, standard: { - name: 'DAGGERHEART.Adversary.Standard.Name', + id: 'standard', + label: 'DAGGERHEART.Adversary.Type.Standard.label', description: 'DAGGERHEART.Adversary.Standard.Description' }, support: { - name: 'DAGGERHEART.Adversary.Support.Name', + id: 'support', + label: 'DAGGERHEART.Adversary.Type.Support.label', description: 'DAGGERHEART.Adversary.Support.Description' } }; +export const environmentTypes = { + exploration: { + label: 'DAGGERHEART.Environment.Type.Exploration.label', + description: 'DAGGERHEART.Environment.Type.Exploration.description' + }, + social: { + label: 'DAGGERHEART.Environment.Type.Social.label', + description: 'DAGGERHEART.Environment.Type.Social.description' + }, + traversal: { + label: 'DAGGERHEART.Environment.Type.Traversal.label', + description: 'DAGGERHEART.Environment.Type.Traversal.description' + }, + event: { + label: 'DAGGERHEART.Environment.Type.Event.label', + description: 'DAGGERHEART.Environment.Type.Event.description' + } +}; + export const adversaryTraits = { relentless: { name: 'DAGGERHEART.Adversary.Trait..Name', diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index fb596347..6526392f 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -1,25 +1,30 @@ export const range = { melee: { + id: 'melee', label: 'DAGGERHEART.Range.melee.name', description: 'DAGGERHEART.Range.melee.description', distance: 1 }, veryClose: { + id: 'veryClose', label: 'DAGGERHEART.Range.veryClose.name', description: 'DAGGERHEART.Range.veryClose.description', distance: 3 }, close: { + id: 'close', label: 'DAGGERHEART.Range.close.name', description: 'DAGGERHEART.Range.close.description', distance: 10 }, far: { + id: 'far', label: 'DAGGERHEART.Range.far.name', description: 'DAGGERHEART.Range.far.description', distance: 20 }, veryFar: { + id: 'veryFar', label: 'DAGGERHEART.Range.veryFar.name', description: 'DAGGERHEART.Range.veryFar.description', distance: 30 @@ -175,31 +180,27 @@ export const deathMoves = { }; export const tiers = { - 0: { - key: 0, - id: 'tier0', - name: 'DAGGERHEART.General.Tier.0' - }, - 1: { - key: 1, + tier1: { id: 'tier1', - name: 'DAGGERHEART.General.Tier.1' + label: 'DAGGERHEART.Tiers.tier1' }, - 2: { - key: 2, + tier2: { id: 'tier2', - name: 'DAGGERHEART.General.Tier.2' + label: 'DAGGERHEART.Tiers.tier2' }, - 3: { - key: 3, + tier3: { id: 'tier3', - name: 'DAGGERHEART.General.Tier.3' + label: 'DAGGERHEART.Tiers.tier3' + }, + tier4: { + id: 'tier4', + label: 'DAGGERHEART.Tiers.tier4' } }; export const objectTypes = { - pc: { - name: 'TYPES.Actor.pc' + character: { + name: 'TYPES.Actor.character' }, npc: { name: 'TYPES.Actor.npc' diff --git a/module/data/_module.mjs b/module/data/_module.mjs index a38b24fc..78109d0e 100644 --- a/module/data/_module.mjs +++ b/module/data/_module.mjs @@ -1,11 +1,9 @@ -export { default as DhpPC } from './pc.mjs'; export { default as DhClass } from './item/class.mjs'; export { default as DhSubclass } from './item/subclass.mjs'; export { default as DhCombat } from './combat.mjs'; export { default as DhCombatant } from './combatant.mjs'; -export { default as DhpAdversary } from './adversary.mjs'; -export { default as DhpEnvironment } from './environment.mjs'; +export * as actors from './actor/_module.mjs'; export * as items from './item/_module.mjs'; export * as messages from './chat-message/_modules.mjs'; export * as fields from './fields/_module.mjs'; diff --git a/module/data/actor/_module.mjs b/module/data/actor/_module.mjs new file mode 100644 index 00000000..cdbf7178 --- /dev/null +++ b/module/data/actor/_module.mjs @@ -0,0 +1,11 @@ +import DhCharacter from './character.mjs'; +import DhAdversary from './adversary.mjs'; +import DhEnvironment from './environment.mjs'; + +export { DhCharacter, DhAdversary, DhEnvironment }; + +export const config = { + character: DhCharacter, + adversary: DhAdversary, + environment: DhEnvironment +}; diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs new file mode 100644 index 00000000..834c5f17 --- /dev/null +++ b/module/data/actor/adversary.mjs @@ -0,0 +1,68 @@ +import BaseDataActor from './base.mjs'; + +const resourceField = () => + new foundry.data.fields.SchemaField({ + value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), + max: new foundry.data.fields.NumberField({ initial: 0, integer: true }) + }); + +export default class DhpAdversary extends BaseDataActor { + static LOCALIZATION_PREFIXES = ['DAGGERHEART.Sheets.Adversary']; + + static get metadata() { + return foundry.utils.mergeObject(super.metadata, { + label: 'TYPES.Actor.adversary', + type: 'adversary' + }); + } + + static defineSchema() { + const fields = foundry.data.fields; + return { + tier: new fields.StringField({ + required: true, + choices: SYSTEM.GENERAL.tiers, + initial: SYSTEM.GENERAL.tiers.tier1.id + }), + type: new fields.StringField({ + required: true, + choices: SYSTEM.ACTOR.adversaryTypes, + initial: SYSTEM.ACTOR.adversaryTypes.standard.id + }), + motivesAndTactics: new fields.HTMLField(), + difficulty: new fields.NumberField({ required: true, initial: 1, integer: true }), + damageThresholds: new fields.SchemaField({ + major: new fields.NumberField({ required: true, initial: 0, integer: true }), + severe: new fields.NumberField({ required: true, initial: 0, integer: true }) + }), + resources: new fields.SchemaField({ + hitPoints: resourceField(), + stress: resourceField() + }), + attack: new fields.SchemaField({ + name: new fields.StringField({}), + modifier: new fields.NumberField({ required: true, integer: true, initial: 0 }), + range: new fields.StringField({ + required: true, + choices: SYSTEM.GENERAL.range, + initial: SYSTEM.GENERAL.range.melee.id + }), + damage: new fields.SchemaField({ + value: new fields.StringField(), + type: new fields.StringField({ + required: true, + choices: SYSTEM.GENERAL.damageTypes, + initial: SYSTEM.GENERAL.damageTypes.physical.id + }) + }) + }), + experiences: new fields.TypedObjectField( + new fields.SchemaField({ + name: new fields.StringField(), + value: new fields.NumberField({ required: true, integer: true, initial: 1 }) + }) + ) + /* Features waiting on pseudo-document data model addition */ + }; + } +} diff --git a/module/data/actor/base.mjs b/module/data/actor/base.mjs new file mode 100644 index 00000000..442c6e93 --- /dev/null +++ b/module/data/actor/base.mjs @@ -0,0 +1,34 @@ +/** + * Describes metadata about the actor data model type + * @typedef {Object} ActorDataModelMetadata + * @property {string} label - A localizable label used on application. + * @property {string} type - The system type that this data model represents. + */ +export default class BaseDataActor extends foundry.abstract.TypeDataModel { + /** @returns {ActorDataModelMetadata}*/ + static get metadata() { + return { + label: 'Base Actor', + type: 'base' + }; + } + + /** @inheritDoc */ + static defineSchema() { + const fields = foundry.data.fields; + + return { + description: new fields.HTMLField({ required: true, nullable: true }) + }; + } + + /** + * Obtain a data object used to evaluate any dice rolls associated with this Item Type + * @param {object} [options] - Options which modify the getRollData method. + * @returns {object} + */ + getRollData() { + const data = { ...this }; + return data; + } +} diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs new file mode 100644 index 00000000..def502b5 --- /dev/null +++ b/module/data/actor/character.mjs @@ -0,0 +1,240 @@ +import { burden } from '../../config/generalConfig.mjs'; +import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; +import { LevelOptionType } from '../levelTier.mjs'; +import BaseDataActor from './base.mjs'; + +const attributeField = () => + new foundry.data.fields.SchemaField({ + value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), + tierMarked: new foundry.data.fields.BooleanField({ initial: false }) + }); + +const resourceField = max => + new foundry.data.fields.SchemaField({ + value: new foundry.data.fields.NumberField({ initial: 0, integer: true }), + max: new foundry.data.fields.NumberField({ initial: max, integer: true }) + }); + +export default class DhCharacter extends BaseDataActor { + static get metadata() { + return foundry.utils.mergeObject(super.metadata, { + label: 'TYPES.Actor.character', + type: 'character' + }); + } + + static defineSchema() { + const fields = foundry.data.fields; + + return { + resources: new fields.SchemaField({ + hitPoints: resourceField(6), + stress: resourceField(6), + hope: resourceField(6) + }), + traits: new fields.SchemaField({ + agility: attributeField(), + strength: attributeField(), + finesse: attributeField(), + instinct: attributeField(), + presence: attributeField(), + knowledge: attributeField() + }), + proficiency: new fields.NumberField({ initial: 1, integer: true }), + evasion: new fields.NumberField({ initial: 0, integer: true }), + experiences: new fields.TypedObjectField( + new fields.SchemaField({ + description: new fields.StringField({}), + value: new fields.NumberField({ integer: true, nullable: true, initial: null }) + }), + { + initial: { + [foundry.utils.randomID()]: { description: '', value: 2 }, + [foundry.utils.randomID()]: { description: '', value: 2 } + } + } + ), + gold: new fields.SchemaField({ + coins: new fields.NumberField({ initial: 0, integer: true }), + handfulls: new fields.NumberField({ initial: 0, integer: true }), + bags: new fields.NumberField({ initial: 0, integer: true }), + chests: new fields.NumberField({ initial: 0, integer: true }) + }), + pronouns: new fields.StringField({}), + scars: new fields.TypedObjectField( + new fields.SchemaField({ + name: new fields.StringField({}), + description: new fields.HTMLField() + }) + ), + story: new fields.HTMLField(), + class: new fields.SchemaField({ + value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }), + subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }) + }), + multiclass: new fields.SchemaField({ + value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }), + subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }) + }), + levelData: new fields.EmbeddedDataField(DhPCLevelData) + }; + } + + get ancestry() { + return this.parent.items.find(x => x.type === 'ancestry') ?? null; + } + + get community() { + return this.parent.items.find(x => x.type === 'community') ?? null; + } + + get domains() { + const classDomains = this.class ? this.class.system.domains : []; + const multiclassDomains = this.multiclass ? this.multiclass.system.domains : []; + return [...classDomains, ...multiclassDomains]; + } + + get domainCards() { + const domainCards = this.parent.items.filter(x => x.type === 'domainCard'); + const loadout = domainCards.filter(x => !x.system.inVault); + const vault = domainCards.filter(x => x.system.inVault); + + return { + loadout: loadout, + vault: vault, + total: [...loadout, ...vault] + }; + } + + get armor() { + return this.parent.items.find(x => x.type === 'armor' && x.system.equipped); + } + + get primaryWeapon() { + return this.parent.items.find(x => x.type === 'weapon' && x.system.equipped && !x.system.secondary); + } + + get secondaryWeapon() { + return this.parent.items.find(x => x.type === 'weapon' && x.system.equipped && x.system.secondary); + } + + get getWeaponBurden() { + return this.primaryWeapon?.system?.burden === burden.twoHanded.value || + (this.primaryWeapon && this.secondaryWeapon) + ? burden.twoHanded.value + : this.primaryWeapon || this.secondaryWeapon + ? burden.oneHanded.value + : null; + } + + get refreshableFeatures() { + return this.parent.items.reduce( + (acc, x) => { + if (x.type === 'feature' && x.system.refreshData?.type === 'feature' && x.system.refreshData?.type) { + acc[x.system.refreshData.type].push(x); + } + + return acc; + }, + { shortRest: [], longRest: [] } + ); + } + + static async unequipBeforeEquip(itemToEquip) { + const primary = this.primaryWeapon, + secondary = this.secondaryWeapon; + if (itemToEquip.system.secondary) { + if (primary && primary.burden === SYSTEM.GENERAL.burden.twoHanded.value) { + await primary.update({ 'system.equipped': false }); + } + + if (secondary) { + await secondary.update({ 'system.equipped': false }); + } + } else { + if (secondary && itemToEquip.system.burden === SYSTEM.GENERAL.burden.twoHanded.value) { + await secondary.update({ 'system.equipped': false }); + } + + if (primary) { + await primary.update({ 'system.equipped': false }); + } + } + } + + prepareBaseData() { + for (var attributeKey in this.traits) { + const attribute = this.traits[attributeKey]; + /* Levleup handling */ + } + + const armor = this.armor; + this.damageThresholds = { + major: armor + ? armor.system.baseThresholds.major + this.levelData.level.current + : this.levelData.level.current, + severe: armor + ? armor.system.baseThresholds.severe + this.levelData.level.current + : this.levelData.level.current * 2 + }; + } + + prepareDerivedData() { + this.resources.hope.max -= Object.keys(this.scars).length; + this.resources.hope.value = Math.min(this.resources.hope.value, this.resources.hope.max); + } +} + +class DhPCLevelData extends foundry.abstract.DataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + level: new fields.SchemaField({ + current: new fields.NumberField({ required: true, integer: true, initial: 1 }), + changed: new fields.NumberField({ required: true, integer: true, initial: 1 }) + }), + levelups: new fields.TypedObjectField( + new fields.SchemaField({ + achievements: new fields.SchemaField( + { + experiences: new fields.TypedObjectField( + new fields.SchemaField({ + name: new fields.StringField({ required: true }), + modifier: new fields.NumberField({ required: true, integer: true }) + }) + ), + domainCards: new fields.ArrayField( + new fields.SchemaField({ + uuid: new fields.StringField({ required: true }), + itemUuid: new fields.StringField({ required: true }) + }) + ), + proficiency: new fields.NumberField({ integer: true }) + }, + { nullable: true, initial: null } + ), + selections: new fields.ArrayField( + new fields.SchemaField({ + tier: new fields.NumberField({ required: true, integer: true }), + level: new fields.NumberField({ required: true, integer: true }), + optionKey: new fields.StringField({ required: true }), + type: new fields.StringField({ required: true, choices: LevelOptionType }), + checkboxNr: new fields.NumberField({ required: true, integer: true }), + value: new fields.NumberField({ integer: true }), + minCost: new fields.NumberField({ integer: true }), + amount: new fields.NumberField({ integer: true }), + data: new fields.ArrayField(new fields.StringField({ required: true })), + secondaryData: new fields.StringField(), + itemUuid: new fields.StringField({ required: true }) + }) + ) + }) + ) + }; + } + + get canLevelUp() { + return this.level.current < this.level.changed; + } +} diff --git a/module/data/actor/environment.mjs b/module/data/actor/environment.mjs new file mode 100644 index 00000000..3209dfdc --- /dev/null +++ b/module/data/actor/environment.mjs @@ -0,0 +1,35 @@ +import { environmentTypes } from '../../config/actorConfig.mjs'; +import BaseDataActor from './base.mjs'; +import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; + +export default class DhEnvironment extends BaseDataActor { + static LOCALIZATION_PREFIXES = ['DAGGERHEART.Sheets.Environment']; + + static get metadata() { + return foundry.utils.mergeObject(super.metadata, { + label: 'TYPES.Actor.environment', + type: 'environment' + }); + } + + static defineSchema() { + const fields = foundry.data.fields; + return { + tier: new fields.StringField({ + required: true, + choices: SYSTEM.GENERAL.tiers, + initial: SYSTEM.GENERAL.tiers.tier1.id + }), + type: new fields.StringField({ choices: environmentTypes }), + impulses: new fields.HTMLField(), + difficulty: new fields.NumberField({ required: true, initial: 11, integer: true }), + potentialAdversaries: new fields.TypedObjectField( + new fields.SchemaField({ + label: new fields.StringField(), + adversaries: new fields.TypedObjectField(new ForeignDocumentUUIDField({ type: 'Actor' })) + }) + ) + /* Features pending datamodel rework */ + }; + } +} diff --git a/module/data/adversary.mjs b/module/data/adversary.mjs deleted file mode 100644 index 3e8cdaf6..00000000 --- a/module/data/adversary.mjs +++ /dev/null @@ -1,52 +0,0 @@ -export default class DhpAdversary extends foundry.abstract.TypeDataModel { - static defineSchema() { - const fields = foundry.data.fields; - return { - resources: new fields.SchemaField({ - health: new fields.SchemaField({ - value: new fields.NumberField({ initial: 0, integer: true }), - min: new fields.NumberField({ initial: 0, integer: true }), - max: new fields.NumberField({ initial: 0, integer: true }) - }), - stress: new fields.SchemaField({ - value: new fields.NumberField({ initial: 0, integer: true }), - min: new fields.NumberField({ initial: 0, integer: true }), - max: new fields.NumberField({ initial: 0, integer: true }) - }) - }), - tier: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.tiers), integer: false }), - type: new fields.StringField({ - choices: Object.keys(SYSTEM.ACTOR.adversaryTypes), - integer: false, - initial: Object.keys(SYSTEM.ACTOR.adversaryTypes).find(x => x === 'standard') - }), - description: new fields.StringField({}), - motivesAndTactics: new fields.ArrayField(new fields.StringField({})), - attackModifier: new fields.NumberField({ integer: true, nullabe: true, initial: null }), - attack: new fields.SchemaField({ - name: new fields.StringField({}), - range: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.range), integer: false }), - damage: new fields.SchemaField({ - value: new fields.StringField({}), - type: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.damageTypes), integer: false }) - }) - }), - difficulty: new fields.NumberField({ initial: 1, integer: true }), - damageThresholds: new fields.SchemaField({ - major: new fields.NumberField({ initial: 0, integer: true }), - severe: new fields.NumberField({ initial: 0, integer: true }) - }), - experiences: new fields.TypedObjectField( - new fields.SchemaField({ - id: new fields.StringField({ required: true }), - name: new fields.StringField(), - value: new fields.NumberField({ integer: true, nullable: true, initial: null }) - }) - ) - }; - } - - get features() { - return this.parent.items.filter(x => x.type === 'feature'); - } -} diff --git a/module/data/environment.mjs b/module/data/environment.mjs deleted file mode 100644 index 23e9cd25..00000000 --- a/module/data/environment.mjs +++ /dev/null @@ -1,22 +0,0 @@ -export default class DhpEnvironment extends foundry.abstract.TypeDataModel { - static defineSchema() { - const fields = foundry.data.fields; - return { - resources: new fields.SchemaField({}), - tier: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.tiers), integer: false }), - type: new fields.StringField({ - choices: Object.keys(SYSTEM.ACTOR.adversaryTypes), - integer: false, - initial: Object.keys(SYSTEM.ACTOR.adversaryTypes).find(x => x === 'standard') - }), - description: new fields.StringField({}), - toneAndFeel: new fields.StringField({}), - difficulty: new fields.NumberField({ initial: 1, integer: true }), - potentialAdversaries: new fields.StringField({}) - }; - } - - get features() { - return this.parent.items.filter(x => x.type === 'feature'); - } -} diff --git a/module/data/item/class.mjs b/module/data/item/class.mjs index 423ad8a5..0584f1db 100644 --- a/module/data/item/class.mjs +++ b/module/data/item/class.mjs @@ -56,7 +56,7 @@ export default class DHClass extends BaseDataItem { const allowed = await super._preCreate(data, options, user); if (allowed === false) return; - if (this.actor?.type === 'pc') { + if (this.actor?.type === 'character') { const path = data.system.isMulticlass ? 'system.multiclass.value' : 'system.class.value'; if (foundry.utils.getProperty(this.actor, path)) { ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.ClassAlreadySelected')); @@ -67,7 +67,7 @@ export default class DHClass extends BaseDataItem { _onCreate(data, options, userId) { super._onCreate(data, options, userId); - if (options.parent?.type === 'pc') { + if (options.parent?.type === 'character') { const path = `system.${data.system.isMulticlass ? 'multiclass.value' : 'class.value'}`; options.parent.update({ [path]: `${options.parent.uuid}.Item.${data._id}` }); } @@ -76,7 +76,7 @@ export default class DHClass extends BaseDataItem { _onDelete(options, userId) { super._onDelete(options, userId); - if (options.parent?.type === 'pc') { + if (options.parent?.type === 'character') { const path = `system.${this.isMulticlass ? 'multiclass' : 'class'}`; options.parent.update({ [`${path}.value`]: null diff --git a/module/data/item/domainCard.mjs b/module/data/item/domainCard.mjs index 6986708f..b5880aad 100644 --- a/module/data/item/domainCard.mjs +++ b/module/data/item/domainCard.mjs @@ -1,13 +1,13 @@ -import DaggerheartAction from "../action.mjs"; -import BaseDataItem from "./base.mjs"; +import DaggerheartAction from '../action.mjs'; +import BaseDataItem from './base.mjs'; export default class DHDomainCard extends BaseDataItem { /** @inheritDoc */ static get metadata() { return foundry.utils.mergeObject(super.metadata, { - label: "TYPES.Item.domainCard", - type: "domainCard", - hasDescription: true, + label: 'TYPES.Item.domainCard', + type: 'domainCard', + hasDescription: true }); } @@ -19,10 +19,37 @@ export default class DHDomainCard extends BaseDataItem { domain: new fields.StringField({ choices: SYSTEM.DOMAIN.domains, required: true, blank: true }), level: new fields.NumberField({ initial: 1, integer: true }), recallCost: new fields.NumberField({ initial: 0, integer: true }), - type: new fields.StringField({ choices: SYSTEM.DOMAIN.cardTypes, required: true, blank: true}), + type: new fields.StringField({ choices: SYSTEM.DOMAIN.cardTypes, required: true, blank: true }), foundation: new fields.BooleanField({ initial: false }), inVault: new fields.BooleanField({ initial: false }), actions: new fields.ArrayField(new fields.EmbeddedDataField(DaggerheartAction)) }; } + + async _preCreate(data, options, user) { + const allowed = await super._preCreate(data, options, user); + if (allowed === false) return; + + if (this.actor?.type === 'character') { + if (!this.actor.system.class.value) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.NoClassSelected')); + return false; + } + + if (!this.actor.system.domains.find(x => x === item.system.domain)) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.LacksDomain')); + return false; + } + + if (this.actor.system.domainCards.total.length === 5) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.MaxLoadoutReached')); + return false; + } + + if (this.actor.system.domainCards.total.find(x => x.name === item.name)) { + ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.DuplicateDomainCard')); + return false; + } + } + } } diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 883df064..ea506efa 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -33,7 +33,7 @@ export default class DHSubclass extends BaseDataItem { const allowed = await super._preCreate(data, options, user); if (allowed === false) return; - if (this.actor?.type === 'pc') { + if (this.actor?.type === 'character') { const path = data.system.isMulticlass ? 'system.multiclass' : 'system.class'; const classData = foundry.utils.getProperty(this.actor, path); if (!classData.value) { @@ -52,7 +52,7 @@ export default class DHSubclass extends BaseDataItem { _onCreate(data, options, userId) { super._onCreate(data, options, userId); - if (options.parent?.type === 'pc') { + if (options.parent?.type === 'character') { const path = `system.${data.system.isMulticlass ? 'multiclass.subclass' : 'class.subclass'}`; options.parent.update({ [path]: `${options.parent.uuid}.Item.${data._id}` }); } @@ -61,7 +61,7 @@ export default class DHSubclass extends BaseDataItem { _onDelete(options, userId) { super._onDelete(options, userId); - if (options.parent?.type === 'pc') { + if (options.parent?.type === 'character') { const path = `system.${this.isMulticlass ? 'multiclass.subclass' : 'class.subclass'}`; options.parent.update({ [path]: null }); } diff --git a/module/data/pc.mjs b/module/data/pc.mjs deleted file mode 100644 index 740b51eb..00000000 --- a/module/data/pc.mjs +++ /dev/null @@ -1,413 +0,0 @@ -import { getPathValue } from '../helpers/utils.mjs'; -import ForeignDocumentUUIDField from './fields/foreignDocumentUUIDField.mjs'; -import { LevelOptionType } from './levelTier.mjs'; - -const fields = foundry.data.fields; - -const attributeField = () => - new fields.SchemaField({ - bonus: new fields.NumberField({ initial: 0, integer: true }), - base: new fields.NumberField({ initial: 0, integer: true }), - tierMarked: new fields.BooleanField({ required: true, initial: false }) - }); - -const resourceField = max => - new fields.SchemaField({ - value: new fields.NumberField({ initial: 0, integer: true }), - bonus: new fields.NumberField({ initial: 0, integer: true }), - min: new fields.NumberField({ initial: 0, integer: true }), - baseMax: new fields.NumberField({ initial: max, integer: true }) - }); - -export default class DhpPC extends foundry.abstract.TypeDataModel { - static defineSchema() { - return { - resources: new fields.SchemaField({ - hitPoints: resourceField(6), - stress: resourceField(6), - hope: new fields.SchemaField({ - value: new fields.NumberField({ initial: -1, integer: true }), // FIXME. Logic is gte and needs -1 in PC/Hope. Change to 0 - min: new fields.NumberField({ initial: 0, integer: true }) - }) - }), - bonuses: new fields.SchemaField({ - damage: new fields.ArrayField( - new fields.SchemaField({ - value: new fields.NumberField({ integer: true, initial: 0 }), - type: new fields.StringField({ nullable: true }), - initiallySelected: new fields.BooleanField(), - hopeIncrease: new fields.StringField({ initial: null, nullable: true }), - description: new fields.StringField({}) - }) - ) - }), - traits: new fields.SchemaField({ - agility: attributeField(), - strength: attributeField(), - finesse: attributeField(), - instinct: attributeField(), - presence: attributeField(), - knowledge: attributeField() - }), - proficiency: new fields.SchemaField({ - base: new fields.NumberField({ required: true, initial: 1, integer: true }), - bonus: new fields.NumberField({ required: true, initial: 0, integer: true }) - }), - evasion: new fields.SchemaField({ - bonus: new fields.NumberField({ initial: 0, integer: true }) - }), - experiences: new fields.ArrayField( - new fields.SchemaField({ - id: new fields.StringField({ required: true }), - description: new fields.StringField({}), - value: new fields.NumberField({ integer: true, nullable: true, initial: null }) - }), - { - initial: [ - { id: foundry.utils.randomID(), description: '', value: 2 }, - { id: foundry.utils.randomID(), description: '', value: 2 } - ] - } - ), - gold: new fields.SchemaField({ - coins: new fields.NumberField({ initial: 0, integer: true }), - handfulls: new fields.NumberField({ initial: 0, integer: true }), - bags: new fields.NumberField({ initial: 0, integer: true }), - chests: new fields.NumberField({ initial: 0, integer: true }) - }), - pronouns: new fields.StringField({}), - domainData: new fields.SchemaField({ - maxLoadout: new fields.NumberField({ initial: 2, integer: true }), - maxCards: new fields.NumberField({ initial: 2, integer: true }) - }), - story: new fields.SchemaField({ - background: new fields.HTMLField(), - appearance: new fields.HTMLField(), - connections: new fields.HTMLField(), - scars: new fields.ArrayField( - new fields.SchemaField({ - name: new fields.StringField({}), - description: new fields.HTMLField() - }) - ) - }), - description: new fields.StringField({}), - //Temporary until new FoundryVersion fix --> See Armor.Mjs DataPreparation - armorMarks: new fields.SchemaField({ - max: new fields.NumberField({ initial: 6, integer: true }), - value: new fields.NumberField({ initial: 0, integer: true }) - }), - class: new fields.SchemaField({ - value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }), - subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }) - }), - multiclass: new fields.SchemaField({ - value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }), - subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }) - }), - levelData: new fields.EmbeddedDataField(DhPCLevelData) - }; - } - - get tier() { - return this.#getTier(this.levelData.currentLevel); - } - - get ancestry() { - return this.parent.items.find(x => x.type === 'ancestry') ?? null; - } - - get multiclassSubclass() { - return this.parent.items.find(x => x.type === 'subclass' && x.system.multiclass) ?? null; - } - - get subclass() { - return this.parent.items.find(x => x.type === 'subclass' && !x.system.multiclass) ?? null; - } - - get subclassFeatures() { - const subclass = this.subclass; - const multiclass = this.multiclassSubclass; - const subclassItems = this.parent.items.filter(x => x.type === 'feature' && x.system.type === 'subclass'); - return { - subclass: !subclass - ? {} - : { - foundation: subclassItems.filter(x => - subclass.system.foundationFeature.abilities.some(ability => ability.uuid === x.uuid) - ), - specialization: subclassItems.filter(x => - subclass.system.specializationFeature.abilities.some(ability => ability.uuid === x.uuid) - ), - mastery: subclassItems.filter(x => - subclass.system.masteryFeature.abilities.some(ability => ability.uuid === x.uuid) - ) - }, - multiclassSubclass: !multiclass - ? {} - : { - foundation: subclassItems.filter(x => - multiclass.system.foundationFeature.abilities.some(ability => ability.uuid === x.uuid) - ), - specialization: subclassItems.filter(x => - multiclass.system.specializationFeature.abilities.some(ability => ability.uuid === x.uuid) - ), - mastery: subclassItems.filter(x => - multiclass.system.masteryFeature.abilities.some(ability => ability.uuid === x.uuid) - ) - } - }; - } - - get community() { - return this.parent.items.find(x => x.type === 'community') ?? null; - } - - get classFeatures() { - return this.parent.items.filter( - x => x.type === 'feature' && x.system.type === SYSTEM.ITEM.featureTypes.class.id && !x.system.multiclass - ); - } - - get multiclassFeatures() { - return this.parent.items.filter( - x => x.type === 'feature' && x.system.type === SYSTEM.ITEM.featureTypes.class.id && x.system.multiclass - ); - } - - get domains() { - const classDomains = this.class ? this.class.system.domains : []; - const multiclassDomains = this.multiclass ? this.multiclass.system.domains : []; - return [...classDomains, ...multiclassDomains]; - } - - get domainCards() { - const domainCards = this.parent.items.filter(x => x.type === 'domainCard'); - const loadout = domainCards.filter(x => !x.system.inVault); - const vault = domainCards.filter(x => x.system.inVault); - - return { - loadout: loadout, - vault: vault, - total: [...loadout, ...vault] - }; - } - - get armor() { - return this.parent.items.find(x => x.type === 'armor' && x.system.equipped); - } - - get equippedWeapons() { - const primaryWeapon = this.parent.items.find( - x => x.type === 'weapon' && x.system.equipped && !x.system.secondary - ); - const secondaryWeapon = this.parent.items.find( - x => x.type === 'weapon' && x.system.equipped && x.system.secondary - ); - return { - primary: this.#weaponData(primaryWeapon), - secondary: this.#weaponData(secondaryWeapon), - burden: this.getBurden(primaryWeapon, secondaryWeapon) - }; - } - - static async unequipBeforeEquip(itemToEquip) { - const equippedWeapons = this.equippedWeapons; - - if (itemToEquip.system.secondary) { - if (equippedWeapons.primary && equippedWeapons.primary.burden === SYSTEM.GENERAL.burden.twoHanded.value) { - await this.parent.items.get(equippedWeapons.primary.id).update({ 'system.equipped': false }); - } - - if (equippedWeapons.secondary) { - await this.parent.items.get(equippedWeapons.secondary.id).update({ 'system.equipped': false }); - } - } else { - if (equippedWeapons.secondary && itemToEquip.system.burden === SYSTEM.GENERAL.burden.twoHanded.value) { - await this.parent.items.get(equippedWeapons.secondary.id).update({ 'system.equipped': false }); - } - - if (equippedWeapons.primary) { - await this.parent.items.get(equippedWeapons.primary.id).update({ 'system.equipped': false }); - } - } - } - - get effects() { - return this.parent.items.reduce((acc, item) => { - const effects = item.system.effectData; - if (effects && !item.system.disabled) { - for (var key in effects) { - const effect = effects[key]; - for (var effectEntry of effect) { - if (!acc[key]) acc[key] = []; - acc[key].push({ name: item.name, value: effectEntry }); - } - } - } - - return acc; - }, {}); - } - - get refreshableFeatures() { - return this.parent.items.reduce( - (acc, x) => { - if (x.type === 'feature' && x.system.refreshData?.type === 'feature' && x.system.refreshData?.type) { - acc[x.system.refreshData.type].push(x); - } - - return acc; - }, - { shortRest: [], longRest: [] } - ); - } - - //Should not be done in data? - //TODO: REMOVE THIS - #weaponData(weapon) { - return weapon - ? { - id: weapon.id, - name: weapon.name, - trait: game.i18n.localize(CONFIG.daggerheart.ACTOR.abilities[weapon.system.trait].label), - range: CONFIG.daggerheart.GENERAL.range[weapon.system.range], - damage: { - value: weapon.system.damage.value, - type: CONFIG.daggerheart.GENERAL.damageTypes[weapon.system.damage.type] - }, - burden: weapon.system.burden, - feature: CONFIG.daggerheart.ITEM.weaponFeatures[weapon.system.feature], - img: weapon.img, - uuid: weapon.uuid - } - : null; - } - - prepareBaseData() { - this.resources.hitPoints.max = this.resources.hitPoints.baseMax + this.resources.hitPoints.bonus; - this.resources.stress.max = this.resources.stress.baseMax + this.resources.stress.bonus; - this.evasion.value = (this.class?.system?.evasion ?? 0) + this.evasion.bonus; - this.proficiency.value = this.proficiency.base + this.proficiency.bonus; - - for (var attributeKey in this.traits) { - const attribute = this.traits[attributeKey]; - attribute.value = attribute.base + attribute.bonus; - } - } - - prepareDerivedData() { - this.resources.hope.max = 6 - this.story.scars.length; - if (this.resources.hope.value >= this.resources.hope.max) { - this.resources.hope.value = Math.max(this.resources.hope.max - 1, 0); - } - - const armor = this.armor; - this.damageThresholds = { - major: armor - ? armor.system.baseThresholds.major + this.levelData.level.current - : this.levelData.level.current, - severe: armor - ? armor.system.baseThresholds.severe + this.levelData.level.current - : this.levelData.level.current * 2 - }; - - this.applyEffects(); - } - - applyEffects() { - const effects = this.effects; - for (var key in effects) { - const effectType = effects[key]; - for (var effect of effectType) { - switch (key) { - case SYSTEM.EFFECTS.effectTypes.health.id: - this.resources.hitPoints.bonus += effect.value.valueData.value; - break; - case SYSTEM.EFFECTS.effectTypes.stress.id: - this.resources.stress.bonus += effect.value.valueData.value; - break; - case SYSTEM.EFFECTS.effectTypes.damage.id: - this.bonuses.damage.push({ - value: getPathValue(effect.value.valueData.value, this), - type: 'physical', - description: effect.name, - hopeIncrease: effect.value.valueData.hopeIncrease, - initiallySelected: effect.value.initiallySelected, - appliesOn: effect.value.appliesOn - }); - } - } - } - } - - getBurden(primary, secondary) { - const twoHanded = - primary?.system?.burden === 'twoHanded' || - secondary?.system?.burden === 'twoHanded' || - (primary?.system?.burden === 'oneHanded' && secondary?.system?.burden === 'oneHanded'); - const oneHanded = - !twoHanded && (primary?.system?.burden === 'oneHanded' || secondary?.system?.burden === 'oneHanded'); - - return twoHanded ? 'twoHanded' : oneHanded ? 'oneHanded' : null; - } - - #getTier(level) { - if (level >= 8) return 3; - else if (level >= 5) return 2; - else if (level >= 2) return 1; - else return 0; - } -} - -class DhPCLevelData extends foundry.abstract.DataModel { - static defineSchema() { - return { - level: new fields.SchemaField({ - current: new fields.NumberField({ required: true, integer: true, initial: 1 }), - changed: new fields.NumberField({ required: true, integer: true, initial: 1 }) - }), - levelups: new fields.TypedObjectField( - new fields.SchemaField({ - achievements: new fields.SchemaField( - { - experiences: new fields.TypedObjectField( - new fields.SchemaField({ - name: new fields.StringField({ required: true }), - modifier: new fields.NumberField({ required: true, integer: true }) - }) - ), - domainCards: new fields.ArrayField( - new fields.SchemaField({ - uuid: new fields.StringField({ required: true }), - itemUuid: new fields.StringField({ required: true }) - }) - ), - proficiency: new fields.NumberField({ integer: true }) - }, - { nullable: true, initial: null } - ), - selections: new fields.ArrayField( - new fields.SchemaField({ - tier: new fields.NumberField({ required: true, integer: true }), - level: new fields.NumberField({ required: true, integer: true }), - optionKey: new fields.StringField({ required: true }), - type: new fields.StringField({ required: true, choices: LevelOptionType }), - checkboxNr: new fields.NumberField({ required: true, integer: true }), - value: new fields.NumberField({ integer: true }), - minCost: new fields.NumberField({ integer: true }), - amount: new fields.NumberField({ integer: true }), - data: new fields.ArrayField(new fields.StringField({ required: true })), - secondaryData: new fields.StringField(), - itemUuid: new fields.StringField({ required: true }) - }) - ) - }) - ) - }; - } - - get canLevelUp() { - return this.level.current < this.level.changed; - } -} diff --git a/module/data/settings/VariantRules.mjs b/module/data/settings/VariantRules.mjs index 2a1f948d..7d28a1d7 100644 --- a/module/data/settings/VariantRules.mjs +++ b/module/data/settings/VariantRules.mjs @@ -1,11 +1,14 @@ export default class DhVariantRules extends foundry.abstract.DataModel { + static LOCALIZATION_PREFIXES = ['DAGGERHEART.Settings.VariantRules']; + static defineSchema() { const fields = foundry.data.fields; return { actionTokens: new fields.SchemaField({ enabled: new fields.BooleanField({ required: true, initial: false }), tokens: new fields.NumberField({ required: true, integer: true, initial: 3 }) - }) + }), + useCoins: new fields.BooleanField({ initial: false }) }; } diff --git a/module/documents/_module.mjs b/module/documents/_module.mjs index f374372d..03237ee5 100644 --- a/module/documents/_module.mjs +++ b/module/documents/_module.mjs @@ -1,3 +1,4 @@ export { default as DhpActor } from './actor.mjs'; export { default as DhpItem } from './item.mjs'; export { default as DhpCombat } from './combat.mjs'; +export { default as DhActiveEffect } from './activeEffect.mjs'; diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs new file mode 100644 index 00000000..d6671501 --- /dev/null +++ b/module/documents/activeEffect.mjs @@ -0,0 +1,14 @@ +export default class DhActiveEffect extends ActiveEffect { + async _preCreate(data, options, user) { + const update = {}; + if (!data.img) { + update.img = 'icons/magic/life/heart-cross-blue.webp'; + } + + if (Object.keys(update).length > 0) { + await this.updateSource(update); + } + + await super._preCreate(data, options, user); + } +} diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index bc116550..55b00634 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -10,7 +10,7 @@ export default class DhpActor extends Actor { // Configure prototype token settings const prototypeToken = {}; - if (this.type === 'pc') + if (this.type === 'character') Object.assign(prototypeToken, { sight: { enabled: true }, actorLink: true, @@ -28,7 +28,7 @@ export default class DhpActor extends Actor { } async updateLevel(newLevel) { - if (this.type !== 'pc' || newLevel === this.system.levelData.level.changed) return; + if (this.type !== 'character' || newLevel === this.system.levelData.level.changed) return; if (newLevel > this.system.levelData.level.current) { await this.update({ 'system.levelData.level.changed': newLevel }); @@ -124,7 +124,7 @@ export default class DhpActor extends Actor { } async diceRoll(modifier, shiftKey) { - if (this.type === 'pc') { + if (this.type === 'character') { return await this.dualityRoll(modifier, shiftKey); } else { return await this.npcRoll(modifier, shiftKey); @@ -173,12 +173,11 @@ export default class DhpActor extends Actor { return { roll, dice: dice[0], modifiers, advantageState: advantage === true ? 1 : advantage === false ? 2 : 0 }; } - async dualityRoll(modifier, shiftKey, bonusDamage = []) { + async dualityRoll(modifier, shiftKey) { let hopeDice = 'd12', fearDice = 'd12', advantageDice = null, - disadvantageDice = null, - bonusDamageString = ''; + disadvantageDice = null; const modifiers = modifier.value !== null @@ -195,12 +194,9 @@ export default class DhpActor extends Actor { : []; if (!shiftKey) { const dialogClosed = new Promise((resolve, _) => { - new RollSelectionDialog( - this.system.experiences, - bonusDamage, - this.system.resources.hope.value, - resolve - ).render(true); + new RollSelectionDialog(this.system.experiences, this.system.resources.hope.value, resolve).render( + true + ); }); const result = await dialogClosed; (hopeDice = result.hope), @@ -214,7 +210,6 @@ export default class DhpActor extends Actor { title: x.description }) ); - bonusDamageString = result.bonusDamage; const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope); @@ -268,8 +263,7 @@ export default class DhpActor extends Actor { fear: { dice: fearDice, value: fear }, advantage: { dice: advantageDice, value: advantage }, disadvantage: { dice: disadvantageDice, value: disadvantage }, - modifiers: modifiers, - bonusDamageString + modifiers: modifiers }; } @@ -401,11 +395,6 @@ export default class DhpActor extends Actor { } } - async emulateItemDrop(data) { - const event = new DragEvent('drop', { altKey: game.keyboard.isModifierActive('Alt') }); - return this.sheet._onDropItem(event, { data: data }); - } - //Move to action-scope? async useAction(action) { const userTargets = Array.from(game.user.targets); diff --git a/module/helpers/handlebarsHelper.mjs b/module/helpers/handlebarsHelper.mjs index 87d1fb7f..25dd0e5e 100644 --- a/module/helpers/handlebarsHelper.mjs +++ b/module/helpers/handlebarsHelper.mjs @@ -11,6 +11,7 @@ export default class RegisterHandlebarsHelpers { includes: this.includes, debug: this.debug, signedNumber: this.signedNumber, + length: this.length, switch: this.switch, case: this.case }); @@ -82,6 +83,10 @@ export default class RegisterHandlebarsHelpers { return number >= 0 ? `+${number}` : number; } + static length(obj) { + return Object.keys(obj).length; + } + static switch(value, options) { this.switch_value = value; this.switch_break = false; diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 3fbe89c3..d764210a 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -114,7 +114,7 @@ export const getCommandTarget = () => { ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.NoSelectedToken')); return null; } - if (target.type !== 'pc') { + if (target.type !== 'character') { ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.OnlyUseableByPC')); return null; } diff --git a/styles/daggerheart.css b/styles/daggerheart.css index 78af5ecc..b7bfc21d 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -2370,228 +2370,8 @@ div.daggerheart.views.multiclass { align-items: center; gap: 5px; } -.daggerheart.sheet.adversary .adversary-header-container { - position: relative; - background-color: grey; - display: flex; -} -.daggerheart.sheet.adversary .adversary-header-container .adversary-header { - flex: 1; -} -.daggerheart.sheet.adversary .adversary-header-container .adversary-header img { - height: 60px; - width: 60px; -} -.daggerheart.sheet.adversary .adversary-header-container .adversary-header .adversary-title { - display: flex; - align-items: center; - text-align: center; - font-size: 28px; -} -.daggerheart.sheet.adversary .adversary-header-container .adversary-header .adversary-title .title-text { - width: 100%; -} -.daggerheart.sheet.adversary .adversary-header-container .adversary-header .adversary-title input { - font-size: 28px; - border: 0; - height: 100%; -} -.daggerheart.sheet.adversary .adversary-header-container .adversary-toggle { - position: absolute; - top: 0; - right: 0; - background-color: white; - color: black; - flex: 0; -} -.daggerheart.sheet.adversary .motive-container { - background: lightgrey; - margin-bottom: 8px; - padding-bottom: 4px; -} -.daggerheart.sheet.adversary .motive-container .motive-title { - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; -} -.daggerheart.sheet.adversary .motive-container .motive-title .motive-title-base { - font-size: 21px; -} -.daggerheart.sheet.adversary .motive-container .motive-title .motive-title-value { - font-style: italic; - position: relative; - top: 2px; -} -.daggerheart.sheet.adversary .motive-container .motive-title i { - margin-left: 4px; - cursor: pointer; -} -.daggerheart.sheet.adversary .motive-container .motive-title i:hover { - filter: drop-shadow(0 0 3px red); -} -.daggerheart.sheet.adversary .adversary-content-container { - display: flex; - align-items: baseline; -} -.daggerheart.sheet.adversary .adversary-statistics-container { - flex: 1; - margin-right: 24px; - display: flex; - flex-direction: column; - gap: 12px; -} -.daggerheart.sheet.adversary .adversary-statistics-container .statistic-title { - flex: 0; +.application.sheet.daggerheart.dh-style.active-effect-config label { white-space: nowrap; - font-weight: bold; -} -.daggerheart.sheet.adversary .adversary-statistics-container .statistic-row { - display: flex; - align-items: center; -} -.daggerheart.sheet.adversary .adversary-statistics-container .statistic-row .statistic-value { - flex: 0; - white-space: nowrap; - margin-left: 4px; -} -.daggerheart.sheet.adversary .adversary-statistics-container .statistic-row .adversary-roll { - border: 0; - width: 16px; - margin-left: 4px; - align-self: baseline; - transition: transform 0.2s; -} -.daggerheart.sheet.adversary .adversary-statistics-container .statistic-row .adversary-roll:hover { - transform: rotate(30deg); - filter: drop-shadow(0px 0px 3px red); - cursor: pointer; -} -.daggerheart.sheet.adversary .adversary-statistics-container .statistic-resource-container { - display: flex; - align-items: center; -} -.daggerheart.sheet.adversary .adversary-statistics-container .statistic-resource-container label { - min-width: 44px; -} -.daggerheart.sheet.adversary .adversary-statistics-container .statistic-resource-container .statistic-resource-inner-container { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 4px; -} -.daggerheart.sheet.adversary .adversary-statistics-container .statistic-resource-container .resource-title { - align-self: center; - font-weight: bold; -} -.daggerheart.sheet.adversary .adversary-statistics-container .statistic-resource-container .statistic-resource-input { - margin: 0; - flex: 0; - min-width: 16px; -} -.daggerheart.sheet.adversary .adversary-statistics-container .attack-container { - border: 1px solid black dotted; -} -.daggerheart.sheet.adversary .adversary-statistics-container .experience-row { - display: flex; -} -.daggerheart.sheet.adversary .adversary-statistics-container .experience-row * { - flex: 0; - white-space: nowrap; -} -.daggerheart.sheet.adversary .adversary-statistics-container .experience-container i { - margin-left: 4px; - cursor: pointer; -} -.daggerheart.sheet.adversary .adversary-statistics-container .experience-container i:hover { - filter: drop-shadow(0 0 3px red); -} -.daggerheart.sheet.adversary .adversary-statistics-container .experience-chip { - border: 2px solid #708090; - border-radius: 6px; - display: flex; - align-items: center; - padding: 4px; - margin-bottom: 6px; -} -.daggerheart.sheet.adversary .adversary-statistics-container .experience-chip .experience-text { - flex: 1; -} -.daggerheart.sheet.adversary .adversary-statistics-container .experience-chip .experience-value { - flex: 0; - min-width: 26px; - margin: 0 4px; -} -.daggerheart.sheet.adversary .adversary-statistics-container .experience-chip .experience-button { - flex: 0; - border-radius: 50%; - height: 20px; - width: 20px; - display: flex; - align-items: center; - justify-content: center; - padding: 12px; -} -.daggerheart.sheet.adversary .adversary-damage-threshold-container input { - min-width: 26px; -} -.daggerheart.sheet.adversary .adversary-moves-container { - flex: 2.5; -} -.daggerheart.sheet.adversary .adversary-moves-container .moves-title { - text-decoration: underline; - font-weight: bold; -} -.daggerheart.sheet.adversary .adversary-moves-container .move-container { - cursor: pointer; -} -.daggerheart.sheet.adversary .adversary-moves-container .move-container:hover { - background: #2f4f4f40; -} -.daggerheart.sheet.adversary .adversary-moves-container .move-container .moves-name { - font-weight: bold; - text-decoration: none; -} -.daggerheart.sheet.adversary .adversary-moves-container .move-container .move-description p { - margin-top: 0; -} -.daggerheart.sheet.adversary .adversary-moves-container .moves-edit-container i { - margin-left: 4px; - cursor: pointer; -} -.daggerheart.sheet.adversary .adversary-moves-container .moves-edit-container i:hover { - filter: drop-shadow(0 0 3px red); -} -.daggerheart.sheet.adversary .chip-container { - display: flex; - align-items: center; - justify-content: space-between; - background: #778899; - padding: 8px; - border: 2px solid black; - border-radius: 6px; -} -.daggerheart.sheet.adversary .chip-container:not(:last-child) { - margin-bottom: 8px; -} -.daggerheart.sheet.adversary .chip-container .chip-inner-container { - display: flex; - align-items: center; -} -.daggerheart.sheet.adversary .chip-container .chip-inner-container img { - height: 40px; - width: 40px; - margin-right: 8px; -} -.daggerheart.sheet.adversary .chip-container .chip-inner-container .chip-title { - font-size: 22px; - font-weight: bold; - font-style: italic; -} -.daggerheart.sheet.adversary .chip-container button { - height: 40px; - width: 40px; - background: white; } .daggerheart.sheet .title-container { display: flex; @@ -2997,7 +2777,7 @@ div.daggerheart.views.multiclass { justify-content: center; align-items: center; width: 3rem; - background-color: var(rgba(9, 71, 179, 0.75)); + background-color: rgba(9, 71, 179, 0.75); -webkit-box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.75); box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.75); color: #d3d3d3; @@ -3078,6 +2858,31 @@ div.daggerheart.views.multiclass { #resources:has(.fear-bar) { min-width: 200px; } +.application.sheet.daggerheart.actor.dh-style.adversary .window-content { + overflow: auto; +} +.daggerheart.sheet.actor.environment .potential-adversary-container { + width: 100%; + height: 50px; +} +.daggerheart.sheet.actor.environment .potential-adversary-container .adversary-placeholder { + font-style: italic; + text-align: center; + opacity: 0.6; +} +.daggerheart.sheet.actor.environment .potential-adversary-container .adversaries-container { + display: flex; + gap: 8px; +} +.daggerheart.sheet.actor.environment .potential-adversary-container .adversaries-container .adversary-container { + border: 1px solid var(--color-dark-5); + border-radius: 6px; + padding: 0 2px; + font-weight: bold; + cursor: pointer; + background-image: url(../assets/parchments/dh-parchment-dark.png); + color: var(--color-light-3); +} .application.sheet.daggerheart.dh-style.feature .item-sheet-header { display: flex; } @@ -3354,6 +3159,12 @@ div.daggerheart.views.multiclass { grid-template-columns: 1fr 2fr; gap: 10px; } +.application.sheet.dh-style fieldset.two-columns.even { + grid-template-columns: 1fr 1fr; +} +.application.sheet.dh-style fieldset.two-columns .full-width { + grid-column: span 2; +} .application.sheet.dh-style fieldset legend { font-family: 'Montserrat', sans-serif; font-weight: bold; @@ -3385,6 +3196,14 @@ div.daggerheart.views.multiclass { font-weight: bold; font-size: smaller; } +.application.sheet.dh-style .two-columns { + display: grid; + grid-template-columns: 1fr 2fr; + gap: 10px; +} +.application.sheet.dh-style .two-columns.even { + grid-template-columns: 1fr 1fr; +} .application.sheet.dh-style line-div { display: block; height: 1px; @@ -3592,6 +3411,16 @@ div.daggerheart.views.multiclass { text-shadow: none; font-family: 'Montserrat', sans-serif; } +.sheet.daggerheart.dh-style .tab-form-footer { + display: flex; + padding: 0 10px; + position: relative; + bottom: -32px; +} +.sheet.daggerheart.dh-style .tab-form-footer button { + flex: 1; + border-width: 2px; +} .sheet.daggerheart.dh-style .tab.actions .actions-list { display: flex; flex-direction: column; diff --git a/styles/daggerheart.less b/styles/daggerheart.less index 6869e316..988f02cf 100755 --- a/styles/daggerheart.less +++ b/styles/daggerheart.less @@ -13,6 +13,10 @@ @import './resources.less'; // new styles imports +@import './less/actors/character.less'; +@import './less/actors/adversary.less'; +@import './less/actors/environment.less'; + @import './less/items/feature.less'; @import './less/items/domainCard.less'; @import './less/items/class.less'; @@ -23,6 +27,7 @@ @import './less/global/sheet.less'; @import './less/global/elements.less'; @import './less/global/tab-navigation.less'; +@import './less/global/tab-form-footer.less'; @import './less/global/tab-actions.less'; @import './less/global/item-header.less'; @import './less/global/feature-section.less'; diff --git a/styles/less/actors/adversary.less b/styles/less/actors/adversary.less new file mode 100644 index 00000000..5b4feb26 --- /dev/null +++ b/styles/less/actors/adversary.less @@ -0,0 +1,5 @@ +.application.sheet.daggerheart.actor.dh-style.adversary { + .window-content { + overflow: auto; + } +} diff --git a/styles/less/actors/character.less b/styles/less/actors/character.less new file mode 100644 index 00000000..e69de29b diff --git a/styles/less/actors/environment.less b/styles/less/actors/environment.less new file mode 100644 index 00000000..d534de38 --- /dev/null +++ b/styles/less/actors/environment.less @@ -0,0 +1,27 @@ +.daggerheart.sheet.actor.environment { + .potential-adversary-container { + width: 100%; + height: 50px; + + .adversary-placeholder { + font-style: italic; + text-align: center; + opacity: 0.6; + } + + .adversaries-container { + display: flex; + gap: 8px; + + .adversary-container { + border: 1px solid var(--color-dark-5); + border-radius: 6px; + padding: 0 2px; + font-weight: bold; + cursor: pointer; + background-image: url(../assets/parchments/dh-parchment-dark.png); + color: var(--color-light-3); + } + } + } +} diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 077d2226..d6e02335 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -103,6 +103,14 @@ display: grid; grid-template-columns: 1fr 2fr; gap: 10px; + + &.even { + grid-template-columns: 1fr 1fr; + } + + .full-width { + grid-column: span 2; + } } legend { @@ -143,6 +151,16 @@ } } + .two-columns { + display: grid; + grid-template-columns: 1fr 2fr; + gap: 10px; + + &.even { + grid-template-columns: 1fr 1fr; + } + } + line-div { display: block; height: 1px; diff --git a/styles/less/global/tab-form-footer.less b/styles/less/global/tab-form-footer.less new file mode 100644 index 00000000..3607d463 --- /dev/null +++ b/styles/less/global/tab-form-footer.less @@ -0,0 +1,13 @@ +.sheet.daggerheart.dh-style { + .tab-form-footer { + display: flex; + padding: 0 10px; + position: relative; + bottom: -32px; + + button { + flex: 1; + border-width: 2px; + } + } +} diff --git a/styles/resources.less b/styles/resources.less index a1679dab..1f609c5e 100644 --- a/styles/resources.less +++ b/styles/resources.less @@ -29,7 +29,7 @@ justify-content: center; align-items: center; width: 3rem; - background-color: var(@primary-color-fear); + background-color: @primary-color-fear; -webkit-box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.75); box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.75); color: #d3d3d3; diff --git a/styles/sheets/activeEffect.less b/styles/sheets/activeEffect.less new file mode 100644 index 00000000..86a29244 --- /dev/null +++ b/styles/sheets/activeEffect.less @@ -0,0 +1,5 @@ +.application.sheet.daggerheart.dh-style.active-effect-config { + label { + white-space: nowrap; + } +} diff --git a/styles/sheets/adversary.less b/styles/sheets/adversary.less deleted file mode 100644 index 657ce218..00000000 --- a/styles/sheets/adversary.less +++ /dev/null @@ -1,280 +0,0 @@ -.daggerheart.sheet.adversary { - .adversary-header-container { - position: relative; - background-color: grey; - display: flex; - - .adversary-header { - flex: 1; - - img { - height: 60px; - width: 60px; - } - - .adversary-title { - display: flex; - align-items: center; - text-align: center; - font-size: 28px; - - .title-text { - width: 100%; - } - - input { - font-size: 28px; - border: 0; - height: 100%; - } - } - } - - .adversary-toggle { - position: absolute; - top: 0; - right: 0; - background-color: white; - color: black; - flex: 0; - } - } - - .motive-container { - background: lightgrey; - margin-bottom: @fullMargin; - padding-bottom: @fullPadding; - - .motive-title { - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - - .motive-title-base { - font-size: 21px; - } - - .motive-title-value { - font-style: italic; - position: relative; - top: 2px; - } - - i { - margin-left: 4px; - cursor: pointer; - - &:hover { - filter: drop-shadow(0 0 3px red); - } - } - } - } - - .adversary-content-container { - display: flex; - align-items: baseline; - } - - .adversary-statistics-container { - flex: 1; - margin-right: 24px; - display: flex; - flex-direction: column; - gap: @mediumMargin; - - .statistic-title { - flex: 0; - white-space: nowrap; - font-weight: bold; - } - - .statistic-row { - display: flex; - align-items: center; - - .statistic-value { - flex: 0; - white-space: nowrap; - margin-left: 4px; - } - - .adversary-roll { - border: 0; - width: 16px; - margin-left: 4px; - align-self: baseline; - transition: transform 0.2s; - - &:hover { - transform: rotate(30deg); - filter: drop-shadow(0px 0px 3px red); - cursor: pointer; - } - } - } - - .statistic-resource-container { - display: flex; - align-items: center; - - label { - min-width: 44px; - } - - .statistic-resource-inner-container { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: @halfMargin; - } - - .resource-title { - align-self: center; - font-weight: bold; - } - - .statistic-resource-input { - margin: 0; - flex: 0; - min-width: 16px; - } - } - - .attack-container { - border: 1px solid black dotted; - } - - .experience-row { - display: flex; - - * { - flex: 0; - white-space: nowrap; - } - } - - .experience-container { - i { - margin-left: 4px; - cursor: pointer; - - &:hover { - filter: drop-shadow(0 0 3px red); - } - } - } - - .experience-chip { - border: 2px solid @secondaryAccent; - border-radius: 6px; - display: flex; - align-items: center; - padding: 4px; - margin-bottom: 6px; - - .experience-text { - flex: 1; - } - - .experience-value { - flex: 0; - min-width: @inputSingleMinWidth; - margin: 0 4px; - } - - .experience-button { - flex: 0; - border-radius: 50%; - height: 20px; - width: 20px; - display: flex; - align-items: center; - justify-content: center; - padding: 12px; - } - } - } - - .adversary-damage-threshold-container { - input { - min-width: @inputSingleMinWidth; - } - } - - .adversary-moves-container { - flex: 2.5; - - .moves-title { - text-decoration: underline; - font-weight: bold; - } - .move-container { - cursor: pointer; - - &:hover { - background: @hoverBackground; - } - - .moves-name { - font-weight: bold; - text-decoration: none; - } - - .move-description { - p { - margin-top: 0; - } - } - } - - .moves-edit-container { - i { - margin-left: 4px; - cursor: pointer; - - &:hover { - filter: drop-shadow(0 0 3px red); - } - } - } - } - - .chip-container { - display: flex; - align-items: center; - justify-content: space-between; - background: @primaryAccent; - padding: 8px; - border: 2px solid black; - border-radius: 6px; - - &:not(:last-child) { - margin-bottom: 8px; - } - - .chip-inner-container { - display: flex; - align-items: center; - - img { - height: 40px; - width: 40px; - margin-right: 8px; - } - - .chip-title { - font-size: 22px; - font-weight: bold; - font-style: italic; - } - } - - button { - height: 40px; - width: 40px; - background: white; - } - } -} diff --git a/styles/sheets/sheets.less b/styles/sheets/sheets.less index 19c76980..5c9b43a1 100644 --- a/styles/sheets/sheets.less +++ b/styles/sheets/sheets.less @@ -1,6 +1,6 @@ @import './heritage.less'; @import './class.less'; -@import './adversary.less'; +@import './activeEffect.less'; .daggerheart.sheet { .title-container { diff --git a/system.json b/system.json index f90f53c8..35f69cde 100644 --- a/system.json +++ b/system.json @@ -163,10 +163,7 @@ "name": "Daggerheart", "sorting": "m", "color": "#08718c", - "packs": [ - "adversaries", - "environments" - ], + "packs": ["adversaries", "environments"], "folders": [ { "name": "Character Options", @@ -186,12 +183,7 @@ "name": "Items", "sorting": "m", "color": "#000000", - "packs": [ - "weapons", - "armors", - "consumables", - "general-items" - ] + "packs": ["weapons", "armors", "consumables", "general-items"] } ] } @@ -211,9 +203,15 @@ }, "documentTypes": { "Actor": { - "pc": {}, - "adversary": {}, - "environment": {} + "character": { + "htmlFields": ["story", "description", "scars.*.description"] + }, + "adversary": { + "htmlFields": ["description", "motivesAndTactics"] + }, + "environment": { + "htmlFields": ["description", "impulses"] + } }, "Item": { "ancestry": { diff --git a/templates/chat/adversary-attack-roll.hbs b/templates/chat/adversary-attack-roll.hbs index 3e805de9..d7cd9ecc 100644 --- a/templates/chat/adversary-attack-roll.hbs +++ b/templates/chat/adversary-attack-roll.hbs @@ -39,7 +39,7 @@ {{/if}}