diff --git a/README.md b/README.md index 5fecbd3e..760194f4 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,24 @@ # Daggerheart + ## Table of Contents + - [Overview](#overview) - [User Install Guide](#user-install) - [Developer Setup](#developer-setup) - [Contribution Info](#contributing) -## Overivew +## Overview + This is a community repo for a Foundry VTT implementation of Daggerheart. It is not associated with Critical Role or Darrington Press. ## User Install + 1. **(Not Yet Supported - No Releases Yet)** Pasting `https://raw.githubusercontent.com/Foundryborne/daggerheart/refs/heads/main/system.json` into the Install System dialog on the Setup menu of the application. 2. **(Not Yet Supported - No Releases Yet)** Browsing the repository's Releases page, where you can copy any system.json link for use in the Install System dialog. 3. **(Not Yet Supported - No Releases Yet)** Downloading one of the .zip archives from the Releases page and extracting it into your foundry Data folder, under Data/systems/daggerheart. ## Development Setup + - Open a terminal in the directory with the repo `cd //` - NOTE: The repo should be placed in the system files are or somewhere else and a link (if on linux) is placed in the system directory - NOTE: Linux link can be made using `ln -snf daggerheart` inside the system folder @@ -33,8 +38,8 @@ This is a community repo for a Foundry VTT implementation of Daggerheart. It is Now you should be able to build the app using `npm start` [Foundry VTT Website][1] -[1]: https://foundryvtt.com/ +[1]: https://foundryvtt.com/ -## Contributing +## Contributing Looking to contribute to the project? Look no further, check out our [contributing guide](contributing.md), and keep the [Code of Conduct](coc.md) in mind when working on things. diff --git a/daggerheart.mjs b/daggerheart.mjs index 909324b4..ac444c44 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -85,6 +85,7 @@ Hooks.once('init', () => { Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2); Actors.registerSheet(SYSTEM.id, applications.DhCharacterSheet, { types: ['character'], makeDefault: true }); + Actors.registerSheet(SYSTEM.id, applications.DhCompanionSheet, { types: ['companion'], makeDefault: true }); Actors.registerSheet(SYSTEM.id, applications.DhpAdversarySheet, { types: ['adversary'], makeDefault: true }); Actors.registerSheet(SYSTEM.id, applications.DhpEnvironment, { types: ['environment'], makeDefault: true }); @@ -123,6 +124,7 @@ Hooks.once('init', () => { CONFIG.ui.resources = Resources; CONFIG.ux.ContextMenu = applications.DhContextMenu; + CONFIG.ux.TooltipManager = applications.DhTooltipManager; game.socket.on(`system.${SYSTEM.id}`, handleSocketEvent); @@ -211,46 +213,27 @@ Hooks.on('chatMessage', (_, message) => { }) : game.i18n.localize('DAGGERHEART.General.Duality'); - const hopeAndFearRoll = `1${rollCommand.hope ?? 'd12'}+1${rollCommand.fear ?? 'd12'}`; - const advantageRoll = `${advantageState === true ? '+d6' : advantageState === false ? '-d6' : ''}`; - const attributeRoll = `${trait?.value ? `${trait.value > 0 ? `+${trait.value}` : `${trait.value}`}` : ''}`; - const roll = await Roll.create(`${hopeAndFearRoll}${advantageRoll}${attributeRoll}`).evaluate(); - - setDiceSoNiceForDualityRoll(roll, advantageState); - - resolve({ - roll, - trait: trait - ? { - value: trait.value, - label: `${game.i18n.localize(abilities[traitValue].label)} ${trait.value >= 0 ? `+` : ``}${trait.value}` - } - : undefined, - title - }); - }).then(async ({ roll, trait, title }) => { - const cls = getDocumentClass('ChatMessage'); - const systemData = new DHDualityRoll({ + const config = { title: title, - origin: target?.id, - roll: roll, - modifiers: trait ? [trait] : [], - hope: { dice: rollCommand.hope ?? 'd12', value: roll.dice[0].total }, - fear: { dice: rollCommand.fear ?? 'd12', value: roll.dice[1].total }, - advantage: advantageState !== null ? { dice: 'd6', value: roll.dice[2].total } : undefined, - advantageState - }); - - const msgData = { - type: 'dualityRoll', - sound: CONFIG.sounds.dice, - system: systemData, - user: game.user.id, - content: 'systems/daggerheart/templates/chat/duality-roll.hbs', - rolls: [roll] + roll: { + trait: traitValue + }, + data: { + traits: { + [traitValue]: trait + } + }, + source: target, + hasSave: false, + dialog: { configure: false }, + evaluate: true, + advantage: rollCommand.advantage == true, + disadvantage: rollCommand.disadvantage == true }; - cls.create(msgData); + await CONFIG.Dice.daggerheart['DualityRoll'].build(config); + + resolve(); }); } @@ -285,9 +268,9 @@ const preloadHandlebarsTemplates = async function () { return foundry.applications.handlebars.loadTemplates([ 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs', 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs', + 'systems/daggerheart/templates/sheets/global/partials/action-item.hbs', 'systems/daggerheart/templates/sheets/global/partials/domain-card-item.hbs', 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs', - 'systems/daggerheart/templates/sheets/parts/attributes.hbs', 'systems/daggerheart/templates/sheets/parts/defense.hbs', 'systems/daggerheart/templates/sheets/parts/armor.hbs', @@ -301,10 +284,6 @@ 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/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/sheets/items/subclass/parts/subclass-features.hbs', 'systems/daggerheart/templates/sheets/items/subclass/parts/subclass-feature.hbs', 'systems/daggerheart/templates/components/card-preview.hbs', @@ -322,7 +301,7 @@ const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/views/actionTypes/range-target.hbs', 'systems/daggerheart/templates/views/actionTypes/effect.hbs', 'systems/daggerheart/templates/settings/components/settings-item-line.hbs', - + 'systems/daggerheart/templates/chat/parts/damage-chat.hbs', 'systems/daggerheart/templates/chat/parts/target-chat.hbs' ]); }; diff --git a/lang/en.json b/lang/en.json index dae24038..7867265d 100755 --- a/lang/en.json +++ b/lang/en.json @@ -14,11 +14,17 @@ }, "Actor": { "character": "Character", + "companion": "Companion", "adversary": "Adversary", "environment": "Environment" } }, "DAGGERHEART": { + "UI": { + "notifications": { + "adversaryMissing": "The linked adversary doesn't exist in the world." + } + }, "Settings": { "Menu": { "Automation": { @@ -191,6 +197,10 @@ "Full": "Disadvantage", "Short": "Dis" }, + "Neutral": { + "Full": "None", + "Short": "no" + }, "OK": "OK", "Cancel": "Cancel", "Or": "Or", @@ -222,12 +232,22 @@ "Major": "Major", "Minor": "Minor", "None": "None" - } + }, + "tabs": { + "details": "Details", + "attack": "Attack", + "experiences": "Experiences", + "features": "Features", + "actions": "Actions", + "potentialAdversaries": "Potential Adversaries", + "adversaries": "Adversaries" + }, + "basics": "Basics" }, "ActionType": { - "Passive": "Passive", - "Action": "Action", - "Reaction": "Reaction" + "passive": "Passive", + "action": "Action", + "reaction": "Reaction" }, "Abilities": { "agility": { @@ -311,45 +331,45 @@ }, "Adversary": { "Type": { - "Bruiser": { + "bruiser": { "label": "Bruiser", - "Description": "Tough adversaries with powerful attacks." + "description": "Tough adversaries with powerful attacks." }, - "Horde": { + "horde": { "label": "Horde", - "Description": "A Horde represents a number of foes working in a group." + "description": "A Horde represents a number of foes working in a group." }, - "Leader": { + "leader": { "label": "Leader", - "Description": "Adversaries that command and summon other adversaries." + "description": "Adversaries that command and summon other adversaries." }, - "Minion": { + "minion": { "label": "Minion", - "Description": "Basic enemies that are easily dispatched but dangerous in numbers." + "description": "Basic enemies that are easily dispatched but dangerous in numbers." }, - "Ranged": { + "ranged": { "label": "Ranged", - "Description": "Adversaries that attack from a distance." + "description": "Adversaries that attack from a distance." }, - "Skulk": { + "skulk": { "label": "Skulk", - "Description": "Adversaries that maneuver and exploit opportunities to ambush their opponents." + "description": "Adversaries that maneuver and exploit opportunities to ambush their opponents." }, - "Social": { + "social": { "label": "Social", - "Description": "Adversaries that are primarily interpersonal threats or challenges." + "description": "Adversaries that are primarily interpersonal threats or challenges." }, - "Solo": { + "solo": { "label": "Solo", - "Description": "Designed to present a challenge to a whole party." + "description": "Designed to present a challenge to a whole party." }, - "Standard": { + "standard": { "label": "Standard", - "Description": "Rank and File adversaries." + "description": "Rank and File adversaries." }, - "Support": { + "support": { "label": "Support", - "Description": "Enemies that enhance their allies and/or disrupt their opponents." + "description": "Enemies that enhance their allies and/or disrupt their opponents." } }, "Trait": { @@ -372,19 +392,19 @@ }, "Environment": { "Type": { - "Exploration": { + "exploration": { "label": "Exploration", "description": "" }, - "Social": { + "social": { "label": "Social", "description": "" }, - "Traversal": { + "traversal": { "label": "Traversal", "description": "" }, - "Event": { + "event": { "label": "Event", "description": "" } @@ -490,7 +510,29 @@ "evasion": "Permanently gain a +1 bonus to your Evasion.", "subclass": "Take an upgraded subclass card. Then cross out the multiclass option for this tier.", "proficiency": "Increase your Proficiency by +1.", - "multiclass": "Multiclass: Choose an additional class for your character, then cross out an unused “Take an upgraded subclass card” and the other multiclass option on this sheet." + "multiclass": "Multiclass: Choose an additional class for your character, then cross out an unused “Take an upgraded subclass card” and the other multiclass option on this sheet.", + "intelligent": "Your companion gains a permanent +1 bonus to a Companion Experience of your choice.", + "lightInTheDark": "Gain an additional Hope slot for your character.", + "creatureComfort": "Once per rest, when you take time during a quiet moment to give your companion love and attention, you can gain a Hope or you can both clear a Stress.", + "armored": "When your companion takes damage, you can mark one of your Armor Slots instead of marking one of their Stress.", + "vicious": "Increase your companion's damage dice or range by one step (d6 to d8, Close to Far, etc.)", + "resilient": "Your companion gains an additional Stress slot.", + "bonded": "When you mark your last Hit Point, your companion rushes to your side to comfort you. Roll a number of d6s equal to the unmarked Stress slots they have and mark them. If any roll a 6, your companion helps you up. Clear your last Hit Point and return to the scene.", + "aware": "Your companion gains a permanent +2 bonus to their Evasion." + }, + "Actions": { + "CreatureComfort": { + "Name": "Creature Comfort", + "Description": "Once per rest, when you take time during a quiet moment to give your companion love and attention, you can gain a Hope or you can both clear a Stress." + }, + "Armored": { + "Name": "Armored", + "Description": "When your companion takes damage, you can mark one of your Armor Slots instead of marking one of their Stress." + }, + "Bonded": { + "Name": "Bonded", + "Description": "When you mark your last Hit Point, your companion rushes to your side to comfort you. Roll a number of d6s equal to the unmarked Stress slots they have and mark them. If any roll a 6, your companion helps you up. Clear your last Hit Point and return to the scene." + } }, "Tier2": { "Label": "Levels 2-4", @@ -961,7 +1003,9 @@ "content": "Returning to the previous level selection will remove all selections made for this level. Do you want to proceed?" }, "Selections": { - "emptyDomainCardHint": "{domain} level {level} or below" + "emptyDomainCardHint": "{domain} level {level} or below", + "viciousDamage": "Damage", + "viciousRange": "Range" }, "summary": { "levelAchievements": "Level Achievements", @@ -979,7 +1023,11 @@ "multiclass": "Multiclass", "traits": "Increased Traits", "experienceIncreases": "Experience Increases", - "damageThresholds": "Damage Thresholds" + "damageThresholds": "Damage Thresholds", + "vicious": "Vicious", + "damageIncreased": "Damage Increased: {damage}", + "rangeIncreased": "Range Increased: {range}", + "simpleFeature": "Feature: {feature}" }, "notifications": { "info": { @@ -1034,7 +1082,10 @@ "DamageRoll": { "Title": "Damage - {damage}", "DealDamageToTargets": "Damage Hit Targets", - "DealDamage": "Deal Damage" + "DealDamage": "Deal Damage", + "RollDamage": "Roll Damage", + "HitTarget": "Hit Targets", + "SelectedTarget": "Selected" }, "ApplyEffect": { "Title": "Apply Effects - {name}" @@ -1087,6 +1138,7 @@ "RemoveCountdownText": "Are you sure you want to remove the countdown: {name}?", "OpenOwnership": "Edit Player Ownership", "Title": "{type} Countdowns", + "ToggleSimple": "Toggle Simple View", "Types": { "narrative": "Narrative", "encounter": "Encounter" @@ -1132,6 +1184,8 @@ "CharacterSetup": "Character setup isn't done yet", "Level": "Level", "LevelUp": "You can level up", + "Features": "Features", + "CompanionFeatures": "Companion Features", "Tabs": { "Features": "Features", "Inventory": "Inventory", @@ -1176,9 +1230,6 @@ "Experience": { "Title": "Experience" }, - "Features": { - "Title": "Class Features" - }, "Gold": { "Title": "Gold", "Coins": "Coins", @@ -1245,6 +1296,24 @@ "tooLowLevel": "You cannot lower the character level below starting level" } }, + "Companion": { + "FIELDS": { + "partner": { "label": "Partner" }, + "evasion": { + "value": { "label": "Evasion" } + }, + "resources": { + "stress": { + "value": { "label": "Stress" } + } + }, + "attack": { + "name": { "label": "Attack Name" } + } + }, + "Experiences": "Experiences", + "Level": "Level" + }, "Adversary": { "FIELDS": { "tier": { "label": "Tier" }, @@ -1284,14 +1353,18 @@ }, "Tabs": { "Main": "Data", - "Information": "Information" + "Information": "Information", + "features": "Features", + "notes": "Notes", + "effects": "Effects" }, "General": "General", "DamageThresholds": "Damage Thresholds", "HitPoints": "Hit Points", "Stress": "Stress", "Experiences": "Experiences", - "Attack": "Attack" + "Attack": "Attack", + "horderHp": "Horde/HP" }, "Environment": { "FIELDS": { @@ -1303,6 +1376,12 @@ }, "difficulty": { "label": "Difficulty" + }, + "impulses": { + "label": "Impulses" + }, + "description": { + "label": "Description" } }, "Tabs": { @@ -1516,9 +1595,11 @@ }, "Tooltip": { "openItemWorld": "Open Item World", + "openActorWorld": "Open Actor World", "sendToChat": "Send to Chat", "moreOptions": "More Options", "equip": "Equip", + "edit": "Edit", "unequip": "Unequip", "delete": "Delete", "sendToVault": "Send to Vault", @@ -1526,29 +1607,29 @@ }, "Actions": { "Types": { - "Attack": { - "Name": "Attack" + "attack": { + "name": "Attack" }, - "Spellcast": { - "Name": "Spellcast" + "spellcast": { + "name": "Spellcast" }, - "Resource": { - "Name": "Resource" + "resource": { + "name": "Resource" }, - "Damage": { - "Name": "Damage" + "damage": { + "name": "Damage" }, - "Healing": { - "Name": "Healing" + "healing": { + "name": "Healing" }, - "Summon": { - "Name": "Summon" + "summon": { + "name": "Summon" }, - "Effect": { - "Name": "Effect" + "effect": { + "name": "Effect" }, - "Macro": { - "Name": "Macro" + "macro": { + "name": "Macro" } }, "Settings": { diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index 4e6f0667..1ee7f37a 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -1,5 +1,6 @@ -export { default as DhCharacterSheet } from './sheets/character.mjs'; -export { default as DhpAdversarySheet } from './sheets/adversary.mjs'; +export { default as DhCharacterSheet } from './sheets/actors/character.mjs'; +export { default as DhpAdversarySheet } from './sheets/actors/adversary.mjs'; +export { default as DhCompanionSheet } from './sheets/companion.mjs'; export { default as DhpClassSheet } from './sheets/items/class.mjs'; export { default as DhpSubclass } from './sheets/items/subclass.mjs'; export { default as DhpFeatureSheet } from './sheets/items/feature.mjs'; @@ -11,9 +12,10 @@ export { default as DhpConsumable } from './sheets/items/consumable.mjs'; 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 DhpEnvironment } from './sheets/actors/environment.mjs'; export { default as DhActiveEffectConfig } from './sheets/activeEffectConfig.mjs'; export { default as DhContextMenu } from './contextMenu.mjs'; +export { default as DhTooltipManager } from './tooltipManager.mjs'; export * as api from './sheets/api/_modules.mjs'; export * as ux from "./ux/_module.mjs"; diff --git a/module/applications/ancestrySelectionDialog.mjs b/module/applications/ancestrySelectionDialog.mjs index 0cdb0dd9..bc2f6b5e 100644 --- a/module/applications/ancestrySelectionDialog.mjs +++ b/module/applications/ancestrySelectionDialog.mjs @@ -143,7 +143,7 @@ export default class AncestrySelectionDialog extends HandlebarsApplicationMixin( } static _onEditImage() { - const fp = new FilePicker({ + const fp = new foundry.applications.apps.FilePicker.implementation({ current: this.data.ancestryInfo.img, type: 'image', redirectToRoot: ['icons/svg/mystery-man.svg'], diff --git a/module/applications/chatMessage.mjs b/module/applications/chatMessage.mjs index 1328de57..ef76d18f 100644 --- a/module/applications/chatMessage.mjs +++ b/module/applications/chatMessage.mjs @@ -1,6 +1,10 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { async renderHTML() { - if(this.system.messageTemplate) this.content = await foundry.applications.handlebars.renderTemplate(this.system.messageTemplate, this.system); + if (this.system.messageTemplate) + this.content = await foundry.applications.handlebars.renderTemplate( + this.system.messageTemplate, + this.system + ); /* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */ const html = await super.renderHTML(); @@ -14,7 +18,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { break; case -1: html.classList.add('fear'); - break; + break; default: html.classList.add('critical'); break; diff --git a/module/applications/contextMenu.mjs b/module/applications/contextMenu.mjs index ff171bfe..f7658d42 100644 --- a/module/applications/contextMenu.mjs +++ b/module/applications/contextMenu.mjs @@ -1,4 +1,4 @@ -export default class DhContextMenu extends ContextMenu { +export default class DhContextMenu extends foundry.applications.ux.ContextMenu.implementation { constructor(container, selector, menuItems, options) { super(container, selector, menuItems, options); @@ -26,10 +26,16 @@ export default class DhContextMenu extends ContextMenu { event.preventDefault(); event.stopPropagation(); const { clientX, clientY } = event; - const selector = "[data-item-id]"; + const selector = '[data-item-id]'; const target = event.target.closest(selector) ?? event.currentTarget.closest(selector); - target?.dispatchEvent(new PointerEvent("contextmenu", { - view: window, bubbles: true, cancelable: true, clientX, clientY - })); + target?.dispatchEvent( + new PointerEvent('contextmenu', { + view: window, + bubbles: true, + cancelable: true, + clientX, + clientY + }) + ); } } diff --git a/module/applications/countdowns.mjs b/module/applications/countdowns.mjs index 0eac145f..54c999a9 100644 --- a/module/applications/countdowns.mjs +++ b/module/applications/countdowns.mjs @@ -1,5 +1,6 @@ import { countdownTypes } from '../config/generalConfig.mjs'; import { GMUpdateEvent, RefreshType, socketEvent } from '../helpers/socket.mjs'; +import constructHTMLButton from '../helpers/utils.mjs'; import OwnershipSelection from './ownershipSelection.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -25,14 +26,15 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) { frame: true, title: 'Countdowns', resizable: true, - minimizable: true + minimizable: false }, actions: { addCountdown: this.addCountdown, removeCountdown: this.removeCountdown, editImage: this.onEditImage, openOwnership: this.openOwnership, - openCountdownOwnership: this.openCountdownOwnership + openCountdownOwnership: this.openCountdownOwnership, + toggleSimpleView: this.toggleSimpleView }, form: { handler: this.updateData, submitOnChange: true } }; @@ -53,11 +55,47 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) { }); } - async _onFirstRender(context, options) { - super._onFirstRender(context, options); + async _preFirstRender(context, options) { + options.position = + game.user.getFlag(SYSTEM.id, SYSTEM.FLAGS[`${this.basePath}Countdown`].position) ?? + Countdowns.DEFAULT_OPTIONS.position; - this.element.querySelector('.expanded-view').classList.toggle('hidden'); - this.element.querySelector('.minimized-view').classList.toggle('hidden'); + const viewSetting = + game.user.getFlag(SYSTEM.id, SYSTEM.FLAGS[`${this.basePath}Countdown`].simple) ?? !game.user.isGM; + this.simpleView = + game.user.isGM || !this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER) ? viewSetting : true; + context.simple = this.simpleView; + } + + _onPosition(position) { + game.user.setFlag(SYSTEM.id, SYSTEM.FLAGS[`${this.basePath}Countdown`].position, position); + } + + async _renderFrame(options) { + const frame = await super._renderFrame(options); + + if (this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER)) { + const button = constructHTMLButton({ + label: '', + classes: ['header-control', 'icon', 'fa-solid', 'fa-wrench'], + dataset: { action: 'toggleSimpleView', tooltip: 'DAGGERHEART.Countdown.ToggleSimple' } + }); + this.window.controls.after(button); + } + + return frame; + } + + testUserPermission(level, exact, altSettings) { + if (game.user.isGM) return true; + + const settings = + altSettings ?? game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath]; + const defaultAllowed = exact ? settings.ownership.default === level : settings.ownership.default >= level; + const userAllowed = exact + ? settings.playerOwnership[game.user.id]?.value === level + : settings.playerOwnership[game.user.id]?.value >= level; + return defaultAllowed || userAllowed; } async _prepareContext(_options) { @@ -67,15 +105,17 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) { context.isGM = game.user.isGM; context.base = this.basePath; - context.canCreate = countdownData.playerOwnership[game.user.id].value === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER; + context.canCreate = this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER, true); context.source = { ...countdownData, countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => { const countdown = countdownData.countdowns[key]; - const ownershipValue = countdown.playerOwnership[game.user.id].value; - if (ownershipValue > CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) { - acc[key] = { ...countdown, canEdit: ownershipValue === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER }; + if (this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.LIMITED, false, countdown)) { + acc[key] = { + ...countdown, + canEdit: this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER, true, countdown) + }; } return acc; @@ -83,7 +123,7 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) { }; context.systemFields = countdownData.schema.fields; context.countdownFields = context.systemFields.countdowns.element.fields; - context.minimized = this.minimized || _options.isFirstRender; + context.simple = this.simpleView; return context; } @@ -110,28 +150,6 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) { } } - async minimize() { - await super.minimize(); - - this.element.querySelector('.expanded-view').classList.toggle('hidden'); - this.element.querySelector('.minimized-view').classList.toggle('hidden'); - } - - async maximize() { - if (this.minimized) { - const settings = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath]; - if (settings.playerOwnership[game.user.id].value <= CONST.DOCUMENT_OWNERSHIP_LEVELS.LIMITED) { - ui.notifications.info(game.i18n.localize('DAGGERHEART.Countdown.Notifications.LimitedOwnership')); - return; - } - - this.element.querySelector('.expanded-view').classList.toggle('hidden'); - this.element.querySelector('.minimized-view').classList.toggle('hidden'); - } - - await super.maximize(); - } - async updateSetting(update) { if (game.user.isGM) { await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns, update); @@ -160,7 +178,7 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) { static onEditImage(_, target) { const setting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath]; const current = setting.countdowns[target.dataset.countdown].img; - const fp = new FilePicker({ + const fp = new foundry.applications.apps.FilePicker.implementation({ current, type: 'image', callback: async path => this.updateImage.bind(this)(path, target.dataset.countdown), @@ -213,11 +231,17 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) { }); } + static async toggleSimpleView() { + this.simpleView = !this.simpleView; + await game.user.setFlag(SYSTEM.id, SYSTEM.FLAGS[`${this.basePath}Countdown`].simple, this.simpleView); + this.render(); + } + async updateCountdownValue(event, increase) { const countdownSetting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns); const countdown = countdownSetting[this.basePath].countdowns[event.currentTarget.dataset.countdown]; - if (countdown.playerOwnership[game.user.id] < CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) { + if (!this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)) { return; } diff --git a/module/applications/levelup/characterLevelup.mjs b/module/applications/levelup/characterLevelup.mjs new file mode 100644 index 00000000..26a425e1 --- /dev/null +++ b/module/applications/levelup/characterLevelup.mjs @@ -0,0 +1,383 @@ +import LevelUpBase from './levelup.mjs'; +import { DhLevelup } from '../../data/levelup.mjs'; +import { domains } from '../../config/domainConfig.mjs'; +import { abilities } from '../../config/actorConfig.mjs'; + +export default class DhCharacterLevelUp extends LevelUpBase { + constructor(actor) { + super(actor); + + this.levelTiers = this.addBonusChoices(game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers)); + const playerLevelupData = actor.system.levelData; + this.levelup = new DhLevelup(DhLevelup.initializeData(this.levelTiers, playerLevelupData)); + } + + async _preparePartContext(partId, context) { + await super._preparePartContext(partId, context); + + const currentLevel = this.levelup.levels[this.levelup.currentLevel]; + switch (partId) { + case 'selections': + const advancementChoices = Object.keys(currentLevel.choices).reduce((acc, choiceKey) => { + Object.keys(currentLevel.choices[choiceKey]).forEach(checkboxNr => { + const checkbox = currentLevel.choices[choiceKey][checkboxNr]; + const data = { + ...checkbox, + path: `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}`, + level: this.levelup.currentLevel + }; + + if (!acc[choiceKey]) acc[choiceKey] = []; + acc[choiceKey].push(data); + }); + + return acc; + }, {}); + + const traits = Object.values(advancementChoices.trait ?? {}); + const traitValues = traits.filter(trait => trait.data.length > 0).flatMap(trait => trait.data); + context.traits = { + values: traitValues, + active: traits.length > 0, + progress: { + selected: traitValues.length, + max: traits.reduce((acc, exp) => acc + exp.amount, 0) + } + }; + + const experienceIncreases = Object.values(advancementChoices.experience ?? {}); + const experienceIncreaseValues = experienceIncreases + .filter(exp => exp.data.length > 0) + .flatMap(exp => + exp.data.map(data => { + const experience = Object.keys(this.actor.system.experiences).find(x => x === data); + return this.actor.system.experiences[experience].name; + }) + ); + context.experienceIncreases = { + values: experienceIncreaseValues, + active: experienceIncreases.length > 0, + progress: { + selected: experienceIncreaseValues.length, + max: experienceIncreases.reduce((acc, exp) => acc + exp.amount, 0) + } + }; + + context.newExperiences = Object.keys(currentLevel.achievements.experiences).map(key => { + const experience = currentLevel.achievements.experiences[key]; + return { + ...experience, + level: this.levelup.currentLevel, + key: key + }; + }); + + const allDomainCards = { + ...advancementChoices.domainCard, + ...currentLevel.achievements.domainCards + }; + const allDomainCardKeys = Object.keys(allDomainCards); + + const classDomainsData = this.actor.system.class.value.system.domains.map(domain => ({ + domain, + multiclass: false + })); + const multiclassDomainsData = (this.actor.system.multiclass?.value?.system?.domains ?? []).map( + domain => ({ domain, multiclass: true }) + ); + const domainsData = [...classDomainsData, ...multiclassDomainsData]; + const multiclassDomain = this.levelup.classUpgradeChoices?.multiclass?.domain; + if (multiclassDomain) { + if (!domainsData.some(x => x.domain === multiclassDomain)) + domainsData.push({ domain: multiclassDomain, multiclass: true }); + } + + context.domainCards = []; + for (var key of allDomainCardKeys) { + const domainCard = allDomainCards[key]; + if (domainCard.level > this.levelup.endLevel) continue; + + const uuid = domainCard.data?.length > 0 ? domainCard.data[0] : domainCard.uuid; + const card = uuid ? await foundry.utils.fromUuid(uuid) : {}; + + context.domainCards.push({ + ...(card.toObject?.() ?? card), + emptySubtexts: domainsData.map(domain => { + const levelBase = domain.multiclass + ? Math.ceil(this.levelup.currentLevel / 2) + : this.levelup.currentLevel; + const levelMax = domainCard.secondaryData?.limit + ? Math.min(domainCard.secondaryData.limit, levelBase) + : levelBase; + + return game.i18n.format('DAGGERHEART.Application.LevelUp.Selections.emptyDomainCardHint', { + domain: game.i18n.localize(domains[domain.domain].label), + level: levelMax + }); + }), + path: domainCard.data + ? `${domainCard.path}.data` + : `levels.${domainCard.level}.achievements.domainCards.${key}.uuid`, + limit: domainCard.secondaryData?.limit ?? null, + compendium: 'domains' + }); + } + + const subclassSelections = advancementChoices.subclass?.flatMap(x => x.data) ?? []; + const possibleSubclasses = [this.actor.system.class.subclass]; + if (this.actor.system.multiclass?.subclass) { + possibleSubclasses.push(this.actor.system.multiclass.subclass); + } + + context.subclassCards = []; + if (advancementChoices.subclass?.length > 0) { + const featureStateIncrease = Object.values(this.levelup.levels).reduce((acc, level) => { + acc += Object.values(level.choices).filter(choice => { + return Object.values(choice).every(checkbox => checkbox.type === 'subclass'); + }).length; + return acc; + }, 0); + + for (var subclass of possibleSubclasses) { + const choice = + advancementChoices.subclass.find(x => x.data[0] === subclass.uuid) ?? + advancementChoices.subclass.find(x => x.data.length === 0); + const featureState = subclass.system.featureState + featureStateIncrease; + const data = await foundry.utils.fromUuid(subclass.uuid); + context.subclassCards.push({ + ...data.toObject(), + path: choice?.path, + uuid: data.uuid, + selected: subclassSelections.includes(subclass.uuid), + featureState: featureState, + featureLabel: game.i18n.localize(subclassFeatureLabels[featureState]), + isMulticlass: subclass.system.isMulticlass ? 'true' : 'false' + }); + } + } + + const multiclasses = Object.values(advancementChoices.multiclass ?? {}); + if (multiclasses?.[0]) { + const data = multiclasses[0]; + const multiclass = data.data.length > 0 ? await foundry.utils.fromUuid(data.data[0]) : {}; + + context.multiclass = { + ...data, + ...(multiclass.toObject?.() ?? multiclass), + uuid: multiclass.uuid, + domains: + multiclass?.system?.domains.map(key => { + const domain = domains[key]; + const alreadySelected = this.actor.system.class.value.system.domains.includes(key); + + return { + ...domain, + selected: key === data.secondaryData.domain, + disabled: + (data.secondaryData.domain && key !== data.secondaryData.domain) || + alreadySelected + }; + }) ?? [], + subclasses: + multiclass?.system?.subclasses.map(subclass => ({ + ...subclass, + uuid: subclass.uuid, + selected: data.secondaryData.subclass === subclass.uuid, + disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid + })) ?? [], + compendium: 'classes', + limit: 1 + }; + } + + break; + case 'summary': + const { current: currentActorLevel, changed: changedActorLevel } = this.actor.system.levelData.level; + const actorArmor = this.actor.system.armor; + const levelKeys = Object.keys(this.levelup.levels); + let achivementProficiency = 0; + const achievementCards = []; + let achievementExperiences = []; + for (var levelKey of levelKeys) { + const level = this.levelup.levels[levelKey]; + if (Number(levelKey) < this.levelup.startLevel) continue; + + achivementProficiency += level.achievements.proficiency ?? 0; + const cards = level.achievements.domainCards ? Object.values(level.achievements.domainCards) : null; + if (cards) { + for (var card of cards) { + const itemCard = await foundry.utils.fromUuid(card.uuid); + achievementCards.push(itemCard); + } + } + + achievementExperiences = level.achievements.experiences + ? Object.values(level.achievements.experiences).reduce((acc, experience) => { + if (experience.name) acc.push(experience); + return acc; + }, []) + : []; + } + + context.achievements = { + proficiency: { + old: this.actor.system.proficiency.total, + new: this.actor.system.proficiency.total + achivementProficiency, + shown: achivementProficiency > 0 + }, + damageThresholds: { + major: { + old: this.actor.system.damageThresholds.major, + new: this.actor.system.damageThresholds.major + changedActorLevel - currentActorLevel + }, + severe: { + old: this.actor.system.damageThresholds.severe, + new: + this.actor.system.damageThresholds.severe + + (actorArmor + ? changedActorLevel - currentActorLevel + : (changedActorLevel - currentActorLevel) * 2) + }, + unarmored: !actorArmor + }, + domainCards: { + values: achievementCards, + shown: achievementCards.length > 0 + }, + experiences: { + values: achievementExperiences, + shown: achievementExperiences.length > 0 + } + }; + + const advancement = {}; + for (var levelKey of levelKeys) { + const level = this.levelup.levels[levelKey]; + if (Number(levelKey) < this.levelup.startLevel) continue; + + for (var choiceKey of Object.keys(level.choices)) { + const choice = level.choices[choiceKey]; + for (var checkbox of Object.values(choice)) { + switch (choiceKey) { + case 'proficiency': + case 'hitPoint': + case 'stress': + case 'evasion': + advancement[choiceKey] = advancement[choiceKey] + ? advancement[choiceKey] + Number(checkbox.value) + : Number(checkbox.value); + break; + case 'trait': + if (!advancement[choiceKey]) advancement[choiceKey] = {}; + for (var traitKey of checkbox.data) { + if (!advancement[choiceKey][traitKey]) advancement[choiceKey][traitKey] = 0; + advancement[choiceKey][traitKey] += 1; + } + break; + case 'domainCard': + if (!advancement[choiceKey]) advancement[choiceKey] = []; + if (checkbox.data.length === 1) { + const choiceItem = await foundry.utils.fromUuid(checkbox.data[0]); + advancement[choiceKey].push(choiceItem.toObject()); + } + break; + case 'experience': + if (!advancement[choiceKey]) advancement[choiceKey] = []; + const data = checkbox.data.map(data => { + const experience = Object.keys(this.actor.system.experiences).find( + x => x === data + ); + return this.actor.system.experiences[experience]?.description ?? ''; + }); + advancement[choiceKey].push({ data: data, value: checkbox.value }); + break; + case 'subclass': + if (checkbox.data[0]) { + const subclassItem = await foundry.utils.fromUuid(checkbox.data[0]); + if (!advancement[choiceKey]) advancement[choiceKey] = []; + advancement[choiceKey].push({ + ...subclassItem.toObject(), + featureLabel: game.i18n.localize( + subclassFeatureLabels[Number(checkbox.secondaryData.featureState)] + ) + }); + } + break; + case 'multiclass': + const multiclassItem = await foundry.utils.fromUuid(checkbox.data[0]); + const subclass = multiclassItem + ? await foundry.utils.fromUuid(checkbox.secondaryData.subclass) + : null; + advancement[choiceKey] = multiclassItem + ? { + ...multiclassItem.toObject(), + domain: checkbox.secondaryData.domain + ? game.i18n.localize(domains[checkbox.secondaryData.domain].label) + : null, + subclass: subclass ? subclass.name : null + } + : {}; + break; + } + } + } + } + + context.advancements = { + statistics: { + proficiency: { + old: context.achievements.proficiency.new, + new: context.achievements.proficiency.new + (advancement.proficiency ?? 0) + }, + hitPoints: { + old: this.actor.system.resources.hitPoints.maxTotal, + new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0) + }, + stress: { + old: this.actor.system.resources.stress.maxTotal, + new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0) + }, + evasion: { + old: this.actor.system.evasion.total, + new: this.actor.system.evasion.total + (advancement.evasion ?? 0) + } + }, + traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => { + if (advancement.trait?.[traitKey]) { + if (!acc) acc = {}; + acc[traitKey] = { + label: game.i18n.localize(abilities[traitKey].label), + old: this.actor.system.traits[traitKey].total, + new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey] + }; + } + return acc; + }, null), + domainCards: advancement.domainCard ?? [], + experiences: + advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ?? + [], + multiclass: advancement.multiclass, + subclass: advancement.subclass + }; + + context.advancements.statistics.proficiency.shown = + context.advancements.statistics.proficiency.new > context.advancements.statistics.proficiency.old; + context.advancements.statistics.hitPoints.shown = + context.advancements.statistics.hitPoints.new > context.advancements.statistics.hitPoints.old; + context.advancements.statistics.stress.shown = + context.advancements.statistics.stress.new > context.advancements.statistics.stress.old; + context.advancements.statistics.evasion.shown = + context.advancements.statistics.evasion.new > context.advancements.statistics.evasion.old; + context.advancements.statistics.shown = + context.advancements.statistics.proficiency.shown || + context.advancements.statistics.hitPoints.shown || + context.advancements.statistics.stress.shown || + context.advancements.statistics.evasion.shown; + + break; + } + + return context; + } +} diff --git a/module/applications/levelup/companionLevelup.mjs b/module/applications/levelup/companionLevelup.mjs new file mode 100644 index 00000000..64cbef82 --- /dev/null +++ b/module/applications/levelup/companionLevelup.mjs @@ -0,0 +1,163 @@ +import BaseLevelUp from './levelup.mjs'; +import { defaultCompanionTier, LevelOptionType } from '../../data/levelTier.mjs'; +import { DhLevelup } from '../../data/levelup.mjs'; +import { diceTypes, range } from '../../config/generalConfig.mjs'; + +export default class DhCompanionLevelUp extends BaseLevelUp { + constructor(actor) { + super(actor); + + this.levelTiers = this.addBonusChoices(defaultCompanionTier); + const playerLevelupData = actor.system.levelData; + this.levelup = new DhLevelup(DhLevelup.initializeData(this.levelTiers, playerLevelupData)); + } + + async _preparePartContext(partId, context) { + await super._preparePartContext(partId, context); + + const currentLevel = this.levelup.levels[this.levelup.currentLevel]; + switch (partId) { + case 'selections': + const advancementChoices = Object.keys(currentLevel.choices).reduce((acc, choiceKey) => { + Object.keys(currentLevel.choices[choiceKey]).forEach(checkboxNr => { + const checkbox = currentLevel.choices[choiceKey][checkboxNr]; + const data = { + ...checkbox, + path: `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}`, + level: this.levelup.currentLevel + }; + + if (!acc[choiceKey]) acc[choiceKey] = []; + acc[choiceKey].push(data); + }); + + return acc; + }, {}); + + const experienceIncreases = Object.values(advancementChoices.experience ?? {}); + const experienceIncreaseValues = experienceIncreases + .filter(exp => exp.data.length > 0) + .flatMap(exp => + exp.data.map(data => { + const experience = Object.keys(this.actor.system.experiences).find(x => x === data); + return this.actor.system.experiences[experience].name; + }) + ); + context.experienceIncreases = { + values: experienceIncreaseValues, + active: experienceIncreases.length > 0, + progress: { + selected: experienceIncreaseValues.length, + max: experienceIncreases.reduce((acc, exp) => acc + exp.amount, 0) + } + }; + + context.newExperiences = Object.keys(currentLevel.achievements.experiences).map(key => { + const experience = currentLevel.achievements.experiences[key]; + return { + ...experience, + level: this.levelup.currentLevel, + key: key + }; + }); + + context.vicious = advancementChoices.vicious ? Object.values(advancementChoices.vicious) : null; + context.viciousChoices = { + damage: game.i18n.localize('DAGGERHEART.Application.LevelUp.Selections.viciousDamage'), + range: game.i18n.localize('DAGGERHEART.Application.LevelUp.Selections.viciousRange') + }; + + break; + case 'summary': + const levelKeys = Object.keys(this.levelup.levels); + const actorDamageDice = this.actor.system.attack.damage.parts[0].value.dice; + const actorRange = this.actor.system.attack.range; + const advancement = {}; + for (var levelKey of levelKeys) { + const level = this.levelup.levels[levelKey]; + if (Number(levelKey) < this.levelup.startLevel) continue; + + for (var choiceKey of Object.keys(level.choices)) { + const choice = level.choices[choiceKey]; + for (var checkbox of Object.values(choice)) { + switch (choiceKey) { + case 'stress': + case 'evasion': + advancement[choiceKey] = advancement[choiceKey] + ? advancement[choiceKey] + Number(checkbox.value) + : Number(checkbox.value); + break; + case 'experience': + if (!advancement[choiceKey]) advancement[choiceKey] = []; + const data = checkbox.data.map(data => { + const experience = Object.keys(this.actor.system.experiences).find( + x => x === data + ); + return this.actor.system.experiences[experience]?.name ?? ''; + }); + advancement[choiceKey].push({ data: data, value: checkbox.value }); + break; + case 'vicious': + if (!advancement[choiceKey]) advancement[choiceKey] = { damage: null, range: null }; + const isDamage = checkbox.data[0] === 'damage'; + const options = isDamage ? diceTypes : range; + const keys = Object.keys(options); + const actorKey = keys.indexOf(isDamage ? actorDamageDice : actorRange); + const currentIndex = advancement[choiceKey][checkbox.data[0]] + ? keys.indexOf(advancement[choiceKey][checkbox.data[0]]) + : actorKey; + advancement[choiceKey][checkbox.data[0]] = + options[keys[Math.min(currentIndex + 1, keys.length - 1)]]; + default: + if (!advancement.simple) advancement.simple = {}; + advancement.simple[choiceKey] = game.i18n.localize( + LevelOptionType[checkbox.type].label + ); + break; + } + } + } + } + + context.advancements = { + statistics: { + stress: { + old: this.actor.system.resources.stress.maxTotal, + new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0) + }, + evasion: { + old: this.actor.system.evasion.total, + new: this.actor.system.evasion.total + (advancement.evasion ?? 0) + } + }, + experiences: + advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ?? + [], + vicious: { + damage: advancement.vicious?.damage + ? { + old: actorDamageDice, + new: advancement.vicious.damage + } + : null, + range: advancement.vicious?.range + ? { + old: game.i18n.localize(`DAGGERHEART.Range.${actorRange}.name`), + new: game.i18n.localize(advancement.vicious.range.label) + } + : null + }, + simple: advancement.simple ?? {} + }; + + context.advancements.statistics.stress.shown = + context.advancements.statistics.stress.new > context.advancements.statistics.stress.old; + context.advancements.statistics.evasion.shown = + context.advancements.statistics.evasion.new > context.advancements.statistics.evasion.old; + context.advancements.statistics.shown = + context.advancements.statistics.stress.shown || context.advancements.statistics.evasion.shown; + } + + return context; + } +} diff --git a/module/applications/levelup.mjs b/module/applications/levelup/levelup.mjs similarity index 71% rename from module/applications/levelup.mjs rename to module/applications/levelup/levelup.mjs index 3094c5bb..02291514 100644 --- a/module/applications/levelup.mjs +++ b/module/applications/levelup/levelup.mjs @@ -1,7 +1,6 @@ -import { abilities, subclassFeatureLabels } from '../config/actorConfig.mjs'; -import { domains } from '../config/domainConfig.mjs'; -import { DhLevelup } from '../data/levelup.mjs'; -import { getDeleteKeys, tagifyElement } from '../helpers/utils.mjs'; +import { abilities, subclassFeatureLabels } from '../../config/actorConfig.mjs'; +import { domains } from '../../config/domainConfig.mjs'; +import { getDeleteKeys, tagifyElement } from '../../helpers/utils.mjs'; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; @@ -10,10 +9,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) super({}); this.actor = actor; - this.levelTiers = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers); - - const playerLevelupData = actor.system.levelData; - this.levelup = new DhLevelup(DhLevelup.initializeData(this.levelTiers, playerLevelupData, actor.system.level)); this._dragDrop = this._createDragDropHandlers(); this.tabGroups.primary = 'advancements'; @@ -81,6 +76,21 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) } }; + addBonusChoices(levelTiers) { + for (var tierKey in levelTiers.tiers) { + const tier = levelTiers.tiers[tierKey]; + tier.maxSelections = [...Array(tier.levels.end - tier.levels.start + 1).keys()].reduce((acc, index) => { + const level = tier.levels.start + index; + const bonus = this.actor.system.levelData.level.bonuses[level]; + acc[level] = tier.availableOptions + (bonus ?? 0); + + return acc; + }, {}); + } + + return levelTiers; + } + async _prepareContext(_options) { const context = await super._prepareContext(_options); context.levelup = this.levelup; @@ -118,181 +128,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections }; context.showTabs = this.tabGroups.primary !== 'summary'; break; - case 'selections': - const advancementChoices = Object.keys(currentLevel.choices).reduce((acc, choiceKey) => { - Object.keys(currentLevel.choices[choiceKey]).forEach(checkboxNr => { - const checkbox = currentLevel.choices[choiceKey][checkboxNr]; - const data = { - ...checkbox, - path: `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}`, - level: this.levelup.currentLevel - }; - - if (!acc[choiceKey]) acc[choiceKey] = []; - acc[choiceKey].push(data); - }); - - return acc; - }, {}); - - const traits = Object.values(advancementChoices.trait ?? {}); - const traitValues = traits.filter(trait => trait.data.length > 0).flatMap(trait => trait.data); - context.traits = { - values: traitValues, - active: traits.length > 0, - progress: { - selected: traitValues.length, - max: traits.reduce((acc, exp) => acc + exp.amount, 0) - } - }; - - const experienceIncreases = Object.values(advancementChoices.experience ?? {}); - const experienceIncreaseValues = experienceIncreases - .filter(exp => exp.data.length > 0) - .flatMap(exp => - 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, - active: experienceIncreases.length > 0, - progress: { - selected: experienceIncreaseValues.length, - max: experienceIncreases.reduce((acc, exp) => acc + exp.amount, 0) - } - }; - - context.newExperiences = Object.keys(currentLevel.achievements.experiences).map(key => { - const experience = currentLevel.achievements.experiences[key]; - return { - ...experience, - level: this.levelup.currentLevel, - key: key - }; - }); - - const allDomainCards = { - ...advancementChoices.domainCard, - ...currentLevel.achievements.domainCards - }; - const allDomainCardKeys = Object.keys(allDomainCards); - - const classDomainsData = this.actor.system.class.value.system.domains.map(domain => ({ - domain, - multiclass: false - })); - const multiclassDomainsData = (this.actor.system.multiclass?.value?.system?.domains ?? []).map( - domain => ({ domain, multiclass: true }) - ); - const domainsData = [...classDomainsData, ...multiclassDomainsData]; - const multiclassDomain = this.levelup.classUpgradeChoices?.multiclass?.domain; - if (multiclassDomain) { - if (!domainsData.some(x => x.domain === multiclassDomain)) - domainsData.push({ domain: multiclassDomain, multiclass: true }); - } - - context.domainCards = []; - for (var key of allDomainCardKeys) { - const domainCard = allDomainCards[key]; - if (domainCard.level > this.levelup.endLevel) continue; - - const uuid = domainCard.data?.length > 0 ? domainCard.data[0] : domainCard.uuid; - const card = uuid ? await foundry.utils.fromUuid(uuid) : {}; - - context.domainCards.push({ - ...(card.toObject?.() ?? card), - emptySubtexts: domainsData.map(domain => { - const levelBase = domain.multiclass - ? Math.ceil(this.levelup.currentLevel / 2) - : this.levelup.currentLevel; - const levelMax = domainCard.secondaryData?.limit - ? Math.min(domainCard.secondaryData.limit, levelBase) - : levelBase; - - return game.i18n.format('DAGGERHEART.Application.LevelUp.Selections.emptyDomainCardHint', { - domain: game.i18n.localize(domains[domain.domain].label), - level: levelMax - }); - }), - path: domainCard.data - ? `${domainCard.path}.data` - : `levels.${domainCard.level}.achievements.domainCards.${key}.uuid`, - limit: domainCard.secondaryData?.limit ?? null, - compendium: 'domains' - }); - } - - const subclassSelections = advancementChoices.subclass?.flatMap(x => x.data) ?? []; - const possibleSubclasses = [this.actor.system.class.subclass]; - if (this.actor.system.multiclass?.subclass) { - possibleSubclasses.push(this.actor.system.multiclass.subclass); - } - - context.subclassCards = []; - if (advancementChoices.subclass?.length > 0) { - const featureStateIncrease = Object.values(this.levelup.levels).reduce((acc, level) => { - acc += Object.values(level.choices).filter(choice => { - return Object.values(choice).every(checkbox => checkbox.type === 'subclass'); - }).length; - return acc; - }, 0); - - for (var subclass of possibleSubclasses) { - const choice = - advancementChoices.subclass.find(x => x.data[0] === subclass.uuid) ?? - advancementChoices.subclass.find(x => x.data.length === 0); - const featureState = subclass.system.featureState + featureStateIncrease; - const data = await foundry.utils.fromUuid(subclass.uuid); - context.subclassCards.push({ - ...data.toObject(), - path: choice?.path, - uuid: data.uuid, - selected: subclassSelections.includes(subclass.uuid), - featureState: featureState, - featureLabel: game.i18n.localize(subclassFeatureLabels[featureState]), - isMulticlass: subclass.system.isMulticlass ? 'true' : 'false' - }); - } - } - - const multiclasses = Object.values(advancementChoices.multiclass ?? {}); - if (multiclasses?.[0]) { - const data = multiclasses[0]; - const multiclass = data.data.length > 0 ? await foundry.utils.fromUuid(data.data[0]) : {}; - - context.multiclass = { - ...data, - ...(multiclass.toObject?.() ?? multiclass), - uuid: multiclass.uuid, - domains: - multiclass?.system?.domains.map(key => { - const domain = domains[key]; - const alreadySelected = this.actor.system.class.value.system.domains.includes(key); - - return { - ...domain, - selected: key === data.secondaryData.domain, - disabled: - (data.secondaryData.domain && key !== data.secondaryData.domain) || - alreadySelected - }; - }) ?? [], - subclasses: - multiclass?.system?.subclasses.map(subclass => ({ - ...subclass, - uuid: subclass.uuid, - selected: data.secondaryData.subclass === subclass.uuid, - disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid - })) ?? [], - compendium: 'classes', - limit: 1 - }; - } - - break; - case 'summary': const { current: currentActorLevel, changed: changedActorLevel } = this.actor.system.levelData.level; const actorArmor = this.actor.system.armor; const levelKeys = Object.keys(this.levelup.levels); @@ -516,7 +351,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) experienceIncreaseTagify, Object.keys(this.actor.system.experiences).reduce((acc, id) => { const experience = this.actor.system.experiences[id]; - acc[id] = { label: experience.description }; + acc[id] = { label: experience.name }; return acc; }, {}), @@ -594,20 +429,20 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2) return; } - if ( - Object.values(this.levelup.levels).some(level => { - const achievementExists = Object.values(level.achievements.domainCards).some( - card => card.uuid === item.uuid - ); - const advancementExists = Object.keys(level.choices).some(choiceKey => { - if (choiceKey !== 'domainCard') return false; - const choice = level.choices[choiceKey]; - return Object.values(choice).some(checkbox => checkbox.data.includes(item.uuid)); - }); + const cardExistsInCharacter = this.actor.items.find(x => x.name === item.name); // Any other way to check? The item is a copy so different ids + const cardExistsInLevelup = Object.values(this.levelup.levels).some(level => { + const achievementExists = Object.values(level.achievements.domainCards).some( + card => card.uuid === item.uuid + ); + const advancementExists = Object.keys(level.choices).some(choiceKey => { + if (choiceKey !== 'domainCard') return false; + const choice = level.choices[choiceKey]; + return Object.values(choice).some(checkbox => checkbox.data.includes(item.uuid)); + }); - return achievementExists || advancementExists; - }) - ) { + return achievementExists || advancementExists; + }); + if (cardExistsInCharacter || cardExistsInLevelup) { ui.notifications.error( game.i18n.localize('DAGGERHEART.Application.LevelUp.notifications.error.domainCardDuplicate') ); diff --git a/module/applications/npcRollSelectionDialog.mjs b/module/applications/npcRollSelectionDialog.mjs index 7c8290fb..1a56f12a 100644 --- a/module/applications/npcRollSelectionDialog.mjs +++ b/module/applications/npcRollSelectionDialog.mjs @@ -1,3 +1,5 @@ +/** NOT USED ANYMORE - TO BE DELETED **/ + const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; export default class NpcRollSelectionDialog extends HandlebarsApplicationMixin(ApplicationV2) { diff --git a/module/applications/roll.mjs b/module/applications/roll.mjs index 51a02dc3..321ead6f 100644 --- a/module/applications/roll.mjs +++ b/module/applications/roll.mjs @@ -1,5 +1,7 @@ +import DHDamageRoll from '../data/chat-message/damageRoll.mjs'; import D20RollDialog from '../dialogs/d20RollDialog.mjs'; import DamageDialog from '../dialogs/damageDialog.mjs'; +import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; /* - Damage & other resources roll @@ -7,6 +9,7 @@ import DamageDialog from '../dialogs/damageDialog.mjs'; */ export class DHRoll extends Roll { + baseTerms = []; constructor(formula, data, options) { super(formula, data, options); } @@ -29,17 +32,18 @@ export class DHRoll extends Roll { for (const hook of config.hooks) { if (Hooks.call(`${SYSTEM.id}.preRoll${hook.capitalize()}`, config, message) === false) return null; } - + this.applyKeybindings(config); let roll = new this(config.roll.formula, config.data, config); if (config.dialog.configure !== false) { // Open Roll Dialog const DialogClass = config.dialog?.class ?? this.DefaultDialog; + console.log(roll, config); const configDialog = await DialogClass.configure(roll, config, message); if (!configDialog) return; } - + for (const hook of config.hooks) { if (Hooks.call(`${SYSTEM.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false) return []; @@ -58,7 +62,7 @@ export class DHRoll extends Roll { } // Create Chat Message - if (message.data) { + if (config.source?.message) { } else { const messageData = {}; config.message = await this.toMessage(roll, config); @@ -66,7 +70,7 @@ export class DHRoll extends Roll { } static postEvaluate(roll, config = {}) { - if(!config.roll) config.roll = {}; + if (!config.roll) config.roll = {}; config.roll.total = roll.total; config.roll.formula = roll.formula; config.roll.dice = []; @@ -93,12 +97,31 @@ export class DHRoll extends Roll { } static applyKeybindings(config) { - config.dialog.configure ??= !(config.event.shiftKey || config.event.altKey || config.event.ctrlKey); + if (config.event) + config.dialog.configure ??= !(config.event.shiftKey || config.event.altKey || config.event.ctrlKey); + } + + formatModifier(modifier) { + const numTerm = modifier < 0 ? '-' : '+'; + return [ + new foundry.dice.terms.OperatorTerm({ operator: numTerm }), + new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) }) + ]; + } + + getFaces(faces) { + return Number(faces.startsWith('d') ? faces.replace('d', '') : faces); } constructFormula(config) { - // const formula = Roll.replaceFormulaData(this.options.roll.formula, config.data); - this.terms = Roll.parse(this.options.roll.formula, config.data) + this.terms = Roll.parse(this.options.roll.formula, config.data); + + if (this.options.extraFormula) { + this.terms.push( + new foundry.dice.terms.OperatorTerm({ operator: '+' }), + ...this.constructor.parse(this.options.extraFormula, this.options.data) + ); + } return (this._formula = this.constructor.getFormula(this.terms)); } } @@ -112,10 +135,6 @@ export class DualityDie extends foundry.dice.terms.Die { export class D20Roll extends DHRoll { constructor(formula, data = {}, options = {}) { super(formula, data, options); - // this.createBaseDice(); - // this.configureModifiers(); - - // this._formula = this.resetFormula(); this.constructFormula(); } @@ -138,7 +157,7 @@ export class D20Roll extends DHRoll { set d20(faces) { if (!(this.terms[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); - this.terms[0].faces = faces; + this.terms[0].faces = this.getFaces(faces); } get dAdvantage() { @@ -151,36 +170,82 @@ export class D20Roll extends DHRoll { } get hasAdvantage() { - return this.options.advantage === this.constructor.ADV_MODE.ADVANTAGE; + return this.options.roll.advantage === this.constructor.ADV_MODE.ADVANTAGE; } get hasDisadvantage() { - return this.options.advantage === this.constructor.ADV_MODE.DISADVANTAGE; + return this.options.roll.advantage === this.constructor.ADV_MODE.DISADVANTAGE; } static applyKeybindings(config) { - const keys = { - normal: config.event.shiftKey || config.event.altKey || config.event.ctrlKey, - advantage: config.event.altKey, - disadvantage: config.event.ctrlKey + let keys = { + normal: true, + advantage: false, + disadvantage: false }; + if (config.event) { + keys = { + normal: config.event.shiftKey || config.event.altKey || config.event.ctrlKey, + advantage: config.event.altKey, + disadvantage: config.event.ctrlKey + }; + } + // Should the roll configuration dialog be displayed? config.dialog.configure ??= !Object.values(keys).some(k => k); // Determine advantage mode - const advantage = config.advantage || keys.advantage; - const disadvantage = config.disadvantage || keys.disadvantage; - if (advantage && !disadvantage) config.advantage = this.ADV_MODE.ADVANTAGE; - else if (!advantage && disadvantage) config.advantage = this.ADV_MODE.DISADVANTAGE; - else config.advantage = this.ADV_MODE.NORMAL; + const advantage = config.roll.advantage === this.ADV_MODE.ADVANTAGE || keys.advantage || config.advantage; + const disadvantage = + config.roll.advantage === this.ADV_MODE.DISADVANTAGE || keys.disadvantage || config.disadvantage; + if (advantage && !disadvantage) config.roll.advantage = this.ADV_MODE.ADVANTAGE; + else if (!advantage && disadvantage) config.roll.advantage = this.ADV_MODE.DISADVANTAGE; + else config.roll.advantage = this.ADV_MODE.NORMAL; + } + + constructFormula(config) { + // this.terms = []; + this.createBaseDice(); + this.configureModifiers(); + this.resetFormula(); + return this._formula; } createBaseDice() { - if (this.terms[0] instanceof foundry.dice.terms.Die) return; + if (this.terms[0] instanceof foundry.dice.terms.Die) { + this.terms = [this.terms[0]]; + return; + } this.terms[0] = new foundry.dice.terms.Die({ faces: 20 }); } + configureModifiers() { + this.applyAdvantage(); + this.applyBaseBonus(); + + this.options.experiences?.forEach(m => { + if (this.options.data.experiences?.[m]) + this.options.roll.modifiers.push({ + label: this.options.data.experiences[m].name, + value: this.options.data.experiences[m].total ?? this.options.data.experiences[m].value + }); + }); + + this.options.roll.modifiers?.forEach(m => { + this.terms.push(...this.formatModifier(m.value)); + }); + + this.baseTerms = foundry.utils.deepClone(this.terms); + + if (this.options.extraFormula) { + this.terms.push( + new foundry.dice.terms.OperatorTerm({ operator: '+' }), + ...this.constructor.parse(this.options.extraFormula, this.options.data) + ); + } + } + applyAdvantage() { this.d20.modifiers.findSplice(m => ['kh', 'kl'].includes(m)); if (!this.hasAdvantage && !this.hasDisadvantage) this.number = 1; @@ -190,45 +255,26 @@ export class D20Roll extends DHRoll { } } - // Trait bonus != Adversary - configureModifiers() { - this.applyAdvantage(); - // this.options.roll.modifiers = []; - this.applyBaseBonus(); - - this.options.experiences?.forEach(m => { - if (this.options.data.experiences?.[m]) - this.options.roll.modifiers.push({ - label: this.options.data.experiences[m].name, - value: this.options.data.experiences[m].total ?? this.options.data.experiences[m].value - }); - }); - this.options.roll.modifiers?.forEach(m => { - this.terms.push(...this.formatModifier(m.value)); - }); - - if (this.options.extraFormula) { - this.terms.push( - new foundry.dice.terms.OperatorTerm({ operator: '+' }), - ...this.constructor.parse(this.options.extraFormula, this.getRollData()) - ); - } - // this.resetFormula(); - } - - constructFormula(config) { - this.terms = []; - this.createBaseDice(); - this.configureModifiers(); - this.resetFormula(); - return this._formula; - } - applyBaseBonus() { - this.options.roll.modifiers = [{ - label : 'Bonus to Hit', - value: Roll.replaceFormulaData('@attackBonus', this.data) - }]; + this.options.roll.modifiers = []; + if (!this.options.roll.bonus) return; + this.options.roll.modifiers.push({ + label: 'Bonus to Hit', + value: this.options.roll.bonus + // value: Roll.replaceFormulaData('@attackBonus', this.data) + }); + } + + static async buildEvaluate(roll, config = {}, message = {}) { + if (config.evaluate !== false) await roll.evaluate(); + const advantageState = + config.roll.advantage == this.ADV_MODE.ADVANTAGE + ? true + : config.roll.advantage == this.ADV_MODE.DISADVANTAGE + ? false + : null; + setDiceSoNiceForDualityRoll(roll, advantageState); + this.postEvaluate(roll, config); } static postEvaluate(roll, config = {}) { @@ -238,25 +284,30 @@ export class D20Roll extends DHRoll { const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion; target.hit = this.isCritical || roll.total >= difficulty; }); - } else if (config.roll.difficulty) config.roll.success = roll.isCritical || roll.total >= config.roll.difficulty; + } else if (config.roll.difficulty) + config.roll.success = roll.isCritical || roll.total >= config.roll.difficulty; config.roll.advantage = { - type: config.advantage, + type: config.roll.advantage, dice: roll.dAdvantage?.denomination, value: roll.dAdvantage?.total }; - config.roll.modifierTotal = config.roll.modifiers.reduce((a, c) => a + Number(c.value), 0); - } - - getRollData() { - return this.options.data; - } - - formatModifier(modifier) { - const numTerm = modifier < 0 ? '-' : '+'; - return [ - new foundry.dice.terms.OperatorTerm({ operator: numTerm }), - new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) }) - ]; + config.roll.extra = roll.dice + .filter(d => !roll.baseTerms.includes(d)) + .map(d => { + return { + dice: d.denomination, + value: d.total + }; + }); + config.roll.modifierTotal = 0; + for (let i = 0; i < roll.terms.length; i++) { + if ( + roll.terms[i] instanceof foundry.dice.terms.NumericTerm && + !!roll.terms[i - 1] && + roll.terms[i - 1] instanceof foundry.dice.terms.OperatorTerm + ) + config.roll.modifierTotal += Number(`${roll.terms[i - 1].operator}${roll.terms[i].total}`); + } } resetFormula() { @@ -265,6 +316,8 @@ export class D20Roll extends DHRoll { } export class DualityRoll extends D20Roll { + _advantageFaces = 6; + constructor(formula, data = {}, options = {}) { super(formula, data, options); } @@ -282,7 +335,7 @@ export class DualityRoll extends D20Roll { set dHope(faces) { if (!(this.dice[0] instanceof CONFIG.Dice.daggerheart.DualityDie)) this.createBaseDice(); - this.terms[0].faces = faces; + this.terms[0].faces = this.getFaces(faces); // this.#hopeDice = `d${face}`; } @@ -295,7 +348,7 @@ export class DualityRoll extends D20Roll { set dFear(faces) { if (!(this.dice[1] instanceof CONFIG.Dice.daggerheart.DualityDie)) this.createBaseDice(); - this.dice[1].faces = faces; + this.dice[1].faces = this.getFaces(faces); // this.#fearDice = `d${face}`; } @@ -303,6 +356,14 @@ export class DualityRoll extends D20Roll { return this.dice[2]; } + get advantageFaces() { + return this._advantageFaces; + } + + set advantageFaces(faces) { + this._advantageFaces = this.getFaces(faces); + } + get isCritical() { if (!this.dHope._evaluated || !this.dFear._evaluated) return; return this.dHope.total === this.dFear.total; @@ -332,25 +393,27 @@ export class DualityRoll extends D20Roll { return game.i18n.localize(label); } + updateFormula() {} + createBaseDice() { if ( this.dice[0] instanceof CONFIG.Dice.daggerheart.DualityDie && this.dice[1] instanceof CONFIG.Dice.daggerheart.DualityDie - ) + ) { + this.terms = [this.terms[0], this.terms[1], this.terms[2]]; return; - if (!(this.dice[0] instanceof CONFIG.Dice.daggerheart.DualityDie)) - this.terms[0] = new CONFIG.Dice.daggerheart.DualityDie(); + } + this.terms[0] = new CONFIG.Dice.daggerheart.DualityDie(); this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' }); - if (!(this.dice[2] instanceof CONFIG.Dice.daggerheart.DualityDie)) - this.terms[2] = new CONFIG.Dice.daggerheart.DualityDie(); + this.terms[2] = new CONFIG.Dice.daggerheart.DualityDie(); } applyAdvantage() { - const dieFaces = 6, + const dieFaces = this.advantageFaces, bardRallyFaces = this.hasBarRally, advDie = new foundry.dice.terms.Die({ faces: dieFaces }); if (this.hasAdvantage || this.hasDisadvantage || bardRallyFaces) - this.terms.push(new foundry.dice.terms.OperatorTerm({ operator: (this.hasDisadvantage ? '-' : '+') })); + this.terms.push(new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' })); if (bardRallyFaces) { const rallyDie = new foundry.dice.terms.Die({ faces: bardRallyFaces }); if (this.hasAdvantage) { @@ -367,10 +430,12 @@ export class DualityRoll extends D20Roll { } applyBaseBonus() { - this.options.roll.modifiers = [{ - label : `DAGGERHEART.Abilities.${this.options.roll.trait}.name`, + this.options.roll.modifiers = []; + if (!this.options.roll.trait) return; + this.options.roll.modifiers.push({ + label: `DAGGERHEART.Abilities.${this.options.roll.trait}.name`, value: Roll.replaceFormulaData(`@traits.${this.options.roll.trait}.total`, this.data) - }]; + }); } static postEvaluate(roll, config = {}) { @@ -388,7 +453,6 @@ export class DualityRoll extends D20Roll { total: roll.dHope.total + roll.dFear.total, label: roll.totalLabel }; - console.log(roll, config) } } @@ -404,5 +468,9 @@ export class DamageRoll extends DHRoll { static async postEvaluate(roll, config = {}) { super.postEvaluate(roll, config); config.roll.type = config.type; + if (config.source?.message) { + const chatMessage = ui.chat.collection.get(config.source.message); + chatMessage.update({ 'system.damage': config }); + } } } diff --git a/module/applications/rollSelectionDialog.mjs b/module/applications/rollSelectionDialog.mjs index 0a1972aa..fbc77d2b 100644 --- a/module/applications/rollSelectionDialog.mjs +++ b/module/applications/rollSelectionDialog.mjs @@ -1,3 +1,5 @@ +/** NOT USED ANYMORE - TO BE DELETED **/ + const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; export default class RollSelectionDialog extends HandlebarsApplicationMixin(ApplicationV2) { diff --git a/module/applications/settings/components/settingsActionsView.mjs b/module/applications/settings/components/settingsActionsView.mjs index 9b223ec5..ff0f0286 100644 --- a/module/applications/settings/components/settingsActionsView.mjs +++ b/module/applications/settings/components/settingsActionsView.mjs @@ -81,7 +81,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App } static onEditImage() { - const fp = new FilePicker({ + const fp = new foundry.applications.apps.FilePicker.implementation({ current: this.img, type: 'image', callback: async path => { diff --git a/module/applications/sheets/adversary.mjs b/module/applications/sheets/actors/adversary.mjs similarity index 53% rename from module/applications/sheets/adversary.mjs rename to module/applications/sheets/actors/adversary.mjs index 26791298..e2f5c978 100644 --- a/module/applications/sheets/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -1,20 +1,23 @@ -import DHActionConfig from '../config/Action.mjs'; -import DaggerheartSheet from './daggerheart-sheet.mjs'; +import DHActionConfig from '../../config/Action.mjs'; +import DaggerheartSheet from '../daggerheart-sheet.mjs'; +import DHAdversarySettings from '../applications/adversary-settings.mjs'; const { ActorSheetV2 } = foundry.applications.sheets; export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { static DEFAULT_OPTIONS = { tag: 'form', classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'adversary'], - position: { width: 450, height: 1000 }, + position: { width: 660, height: 766 }, actions: { reactionRoll: this.reactionRoll, - attackRoll: this.attackRoll, + useItem: this.useItem, + toChat: this.toChat, attackConfigure: this.attackConfigure, addExperience: this.addExperience, removeExperience: this.removeExperience, toggleHP: this.toggleHP, - toggleStress: this.toggleStress + toggleStress: this.toggleStress, + openSettings: this.openSettings }, form: { handler: this.updateForm, @@ -24,28 +27,37 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { }; static PARTS = { + sidebar: { template: 'systems/daggerheart/templates/sheets/actors/adversary/sidebar.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' } + actions: { template: 'systems/daggerheart/templates/sheets/actors/adversary/actions.hbs' }, + notes: { template: 'systems/daggerheart/templates/sheets/actors/adversary/notes.hbs' }, + effects: { template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs' } }; static TABS = { - main: { + actions: { active: true, cssClass: '', group: 'primary', - id: 'main', + id: 'actions', icon: null, - label: 'DAGGERHEART.Sheets.Adversary.Tabs.Main' + label: 'DAGGERHEART.General.tabs.actions' }, - information: { + notes: { active: false, cssClass: '', group: 'primary', - id: 'information', + id: 'notes', icon: null, - label: 'DAGGERHEART.Sheets.Adversary.Tabs.Information' + label: 'DAGGERHEART.Sheets.Adversary.Tabs.notes' + }, + effects: { + active: false, + cssClass: '', + group: 'primary', + id: 'effects', + icon: null, + label: 'DAGGERHEART.Sheets.Adversary.Tabs.effects' } }; @@ -56,10 +68,15 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { context.systemFields.attack.fields = this.document.system.attack.schema.fields; context.getEffectDetails = this.getEffectDetails.bind(this); context.isNPC = true; - console.log(context) return context; } + getAction(element) { + const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId, + item = this.document.system.actions.find(x => x.id === itemId); + return item; + } + static async updateForm(event, _, formData) { await this.document.update(formData.object); this.render(); @@ -70,7 +87,7 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { event: event, title: `${this.actor.name} - Reaction Roll`, roll: { - modifier: null, + // modifier: null, type: 'reaction' }, chatMessage: { @@ -86,8 +103,40 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { return {}; } - static async attackRoll(event) { - this.actor.system.attack.use(event); + static async openSettings() { + await new DHAdversarySettings(this.document).render(true); + } + + static async useItem(event) { + const action = this.getAction(event) ?? this.actor.system.attack; + action.use(event); + } + + static async toChat(event, button) { + if (button?.dataset?.type === 'experience') { + const experience = this.document.system.experiences[button.dataset.uuid]; + const cls = getDocumentClass('ChatMessage'); + const systemData = { + name: game.i18n.localize('DAGGERHEART.General.Experience.Single'), + description: `${experience.name} ${ + experience.modifier < 0 ? experience.modifier : `+${experience.modifier}` + }` + }; + 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()); + } else { + const item = this.getAction(event) ?? this.document.system.attack; + item.toChat(this.document.id); + } } static async attackConfigure(event) { diff --git a/module/applications/sheets/character.mjs b/module/applications/sheets/actors/character.mjs similarity index 78% rename from module/applications/sheets/character.mjs rename to module/applications/sheets/actors/character.mjs index 49d5035f..73941547 100644 --- a/module/applications/sheets/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -1,12 +1,12 @@ -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 DhCharacterCreation from '../characterCreation.mjs'; -import FilterMenu from '../ux/filter-menu.mjs'; +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 DhCharacterlevelUp from '../../levelup/characterLevelup.mjs'; +import DhCharacterCreation from '../../characterCreation.mjs'; +import FilterMenu from '../../ux/filter-menu.mjs'; const { ActorSheetV2 } = foundry.applications.sheets; const { TextEditor } = foundry.applications.ux; @@ -305,8 +305,11 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } getItem(element) { - const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId, - item = this.document.items.get(itemId); + const listElement = (element.target ?? element).closest('[data-item-id]'); + const document = listElement.dataset.companion ? this.document.system.companion : this.document; + + const itemId = listElement.dataset.itemId, + item = document.items.get(itemId); return item; } @@ -315,7 +318,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } static _onEditImage() { - const fp = new FilePicker({ + const fp = new foundry.applications.apps.FilePicker.implementation({ current: this.document.img, type: 'image', redirectToRoot: ['icons/svg/mystery-man.svg'], @@ -330,25 +333,8 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { const context = await super._prepareContext(_options); context.document = this.document; context.tabs = super._getTabs(this.constructor.TABS); - context.config = SYSTEM; - const selectedAttributes = Object.values(this.document.system.traits).map(x => x.base); - context.abilityScoreArray = await game.settings - .get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Homebrew) - .traitArray.reduce((acc, x) => { - const selectedIndex = selectedAttributes.indexOf(x); - if (selectedIndex !== -1) { - selectedAttributes.splice(selectedIndex, 1); - } else { - acc.push({ name: x, value: x }); - } - - return acc; - }, []); - if (!context.abilityScoreArray.includes(0)) context.abilityScoreArray.push({ name: 0, value: 0 }); - context.abilityScoresFinished = context.abilityScoreArray.every(x => x.value === 0); - context.attributes = Object.keys(this.document.system.traits).reduce((acc, key) => { acc[key] = { ...this.document.system.traits[key], @@ -359,67 +345,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { 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, - listView: game.user.getFlag(SYSTEM.id, SYSTEM.FLAGS.displayDomainCardsAsList) - }, - 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') - }, currency: { title: game.i18n.localize('DAGGERHEART.Sheets.PC.Gold.Title'), coins: game.i18n.localize('DAGGERHEART.Sheets.PC.Gold.Coins'), @@ -557,102 +483,6 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } } - /* -------------------------------------------- */ - /* Filter Menus */ - /* -------------------------------------------- */ - - _createFilterMenus() { - //Menus could be a application option if needed - const menus = [ - { - key: 'inventory', - container: '[data-application-part="inventory"]', - content: '.items-section', - callback: this._onMenuFilterInventory.bind(this), - target: '.filter-button', - filters: FilterMenu.invetoryFilters - }, - { - key: 'loadout', - container: '[data-application-part="loadout"]', - content: '.items-section', - callback: this._onMenuFilterLoadout.bind(this), - target: '.filter-button', - filters: FilterMenu.cardsFilters - } - ]; - - menus.forEach(m => { - const container = this.element.querySelector(m.container); - this.#menu[m.key] = new FilterMenu(container, m.target, m.filters, m.callback, { - contentSelector: m.content - }); - }); - } - - /** - * Callback when filters change - * @param {PointerEvent} event - * @param {HTMLElement} html - * @param {import('../ux/filter-menu.mjs').FilterItem[]} filters - */ - _onMenuFilterInventory(event, html, filters) { - this.#filteredItems.inventory.menu.clear(); - - for (const li of html.querySelectorAll('.inventory-item')) { - const item = this.document.items.get(li.dataset.itemId); - - const matchesMenu = - filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f)); - if (matchesMenu) this.#filteredItems.inventory.menu.add(item.id); - - const { search } = this.#filteredItems.inventory; - li.hidden = !(search.has(item.id) && matchesMenu); - } - } - - /** - * Callback when filters change - * @param {PointerEvent} event - * @param {HTMLElement} html - * @param {import('../ux/filter-menu.mjs').FilterItem[]} filters - */ - _onMenuFilterLoadout(event, html, filters) { - this.#filteredItems.loadout.menu.clear(); - - for (const li of html.querySelectorAll('.items-list .inventory-item, .card-list .card-item')) { - const item = this.document.items.get(li.dataset.itemId); - - const matchesMenu = - filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f)); - if (matchesMenu) this.#filteredItems.loadout.menu.add(item.id); - - const { search } = this.#filteredItems.loadout; - li.hidden = !(search.has(item.id) && matchesMenu); - } - } - /* -------------------------------------------- */ - - async mapFeatureType(data, configType) { - return await Promise.all( - data.map(async x => { - const abilities = x.system.abilities - ? await Promise.all(x.system.abilities.map(async x => await fromUuid(x.uuid))) - : []; - - return { - ...x, - uuid: x.uuid, - system: { - ...x.system, - abilities: abilities, - type: game.i18n.localize(configType[x.system.type ?? x.type].label) - } - }; - }) - ); - } - static async rollAttribute(event, button) { const abilityLabel = game.i18n.localize(abilities[button.dataset.attribute].label); const config = { @@ -738,7 +568,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { return; } - new DhlevelUp(this.document).render(true); + new DhCharacterlevelUp(this.document).render(true); } static async useDomainCard(event, button) { @@ -804,15 +634,22 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { static async useItem(event, button) { const item = this.getItem(button); if (!item) return; - const wasUsed = await item.use(event); - if (wasUsed && item.type === 'weapon') { - Hooks.callAll(SYSTEM.HOOKS.characterAttack, {}); + + // Should dandle its actions. Or maybe they'll be separate buttons as per an Issue on the board + if (item.type === 'feature') { + item.toChat(); + } else { + const wasUsed = await item.use(event); + if (wasUsed && item.type === 'weapon') { + Hooks.callAll(SYSTEM.HOOKS.characterAttack, {}); + } } } - static async viewObject(event, button) { + static async viewObject(event) { const item = this.getItem(event); if (!item) return; + item.sheet.render(true); } @@ -866,9 +703,10 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { this.render(); } - static async deleteItem(event, button) { + static async deleteItem(event) { const item = this.getItem(event); if (!item) return; + await item.delete(); } @@ -910,9 +748,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { const cls = getDocumentClass('ChatMessage'); const systemData = { name: game.i18n.localize('DAGGERHEART.General.Experience.Single'), - description: `${experience.description} ${ - experience.total < 0 ? experience.total : `+${experience.total}` - }` + description: `${experience.name} ${experience.total < 0 ? experience.total : `+${experience.total}`}` }; const msg = new cls({ type: 'abilityUse', diff --git a/module/applications/sheets/actors/environment.mjs b/module/applications/sheets/actors/environment.mjs new file mode 100644 index 00000000..dc0da8e9 --- /dev/null +++ b/module/applications/sheets/actors/environment.mjs @@ -0,0 +1,140 @@ +import DaggerheartSheet from '../daggerheart-sheet.mjs'; +import DHEnvironmentSettings from '../applications/environment-settings.mjs'; + +const { ActorSheetV2 } = foundry.applications.sheets; +export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) { + static DEFAULT_OPTIONS = { + tag: 'form', + classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'environment'], + position: { + width: 500 + }, + actions: { + addAdversary: this.addAdversary, + deleteProperty: this.deleteProperty, + openSettings: this.openSettings, + useItem: this.useItem, + toChat: this.toChat + }, + form: { + handler: this._updateForm, + submitOnChange: true, + closeOnSubmit: false + }, + dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }] + }; + + static PARTS = { + header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' }, + actions: { template: 'systems/daggerheart/templates/sheets/actors/environment/actions.hbs' }, + potentialAdversaries: { + template: 'systems/daggerheart/templates/sheets/actors/environment/potentialAdversaries.hbs' + }, + notes: { template: 'systems/daggerheart/templates/sheets/actors/environment/notes.hbs' } + }; + + static TABS = { + actions: { + active: true, + cssClass: '', + group: 'primary', + id: 'actions', + icon: null, + label: 'DAGGERHEART.General.tabs.actions' + }, + potentialAdversaries: { + active: false, + cssClass: '', + group: 'primary', + id: 'potentialAdversaries', + icon: null, + label: 'DAGGERHEART.General.tabs.potentialAdversaries' + }, + notes: { + active: false, + cssClass: '', + group: 'primary', + id: 'notes', + icon: null, + label: 'DAGGERHEART.Sheets.Adversary.Tabs.notes' + } + }; + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.document = this.document; + context.tabs = super._getTabs(this.constructor.TABS); + context.getEffectDetails = this.getEffectDetails.bind(this); + + return context; + } + + getAction(element) { + const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId, + item = this.document.system.actions.find(x => x.id === itemId); + return item; + } + + static async openSettings() { + await new DHEnvironmentSettings(this.document).render(true); + } + + static async _updateForm(event, _, formData) { + await this.document.update(formData.object); + this.render(); + } + + getEffectDetails(id) { + return {}; + } + + static async addAdversary() { + await this.document.update({ + [`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize( + 'DAGGERHEART.Sheets.Environment.newAdversary' + ) + }); + this.render(); + } + + static async deleteProperty(_, target) { + await this.document.update({ [`${target.dataset.path}.-=${target.id}`]: null }); + this.render(); + } + + async viewAdversary(_, button) { + const target = button.closest('[data-item-uuid]'); + const adversary = await foundry.utils.fromUuid(target.dataset.itemUuid); + if (!adversary) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.adversaryMissing')); + return; + } + + adversary.sheet.render(true); + } + + static async useItem(event, button) { + const action = this.getAction(event); + if (!action) { + await this.viewAdversary(event, button); + } else { + action.use(event); + } + } + + static async toChat(event) { + const item = this.getAction(event); + item.toChat(this.document.id); + } + + async _onDragStart(event) { + const item = event.currentTarget.closest('.inventory-item'); + + if (item) { + const adversary = game.actors.find(x => x.type === 'adversary' && x.id === item.dataset.itemId); + const adversaryData = { type: 'Actor', uuid: adversary.uuid }; + event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData)); + event.dataTransfer.setDragImage(item, 60, 0); + } + } +} diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs index 1af01b61..1299b208 100644 --- a/module/applications/sheets/api/base-item.mjs +++ b/module/applications/sheets/api/base-item.mjs @@ -46,19 +46,18 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { switch (partId) { case 'description': - const value = foundry.utils.getProperty(this.document, "system.description") ?? ""; + const value = foundry.utils.getProperty(this.document, 'system.description') ?? ''; context.enrichedDescription = await TextEditor.enrichHTML(value, { relativeTo: this.item, rollData: this.item.getRollData(), secrets: this.item.isOwner - }) + }); break; } return context; } - /* -------------------------------------------- */ /* Application Clicks Actions */ /* -------------------------------------------- */ @@ -70,11 +69,11 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { */ static async selectActionType() { const content = await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/views/actionType.hbs', - { types: SYSTEM.ACTIONS.actionTypes } - ), - title = 'Select Action Type' - + 'systems/daggerheart/templates/views/actionType.hbs', + { types: SYSTEM.ACTIONS.actionTypes } + ), + title = 'Select Action Type'; + return foundry.applications.api.DialogV2.prompt({ window: { title }, content, @@ -92,7 +91,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { */ static async #addAction(_event, _button) { const actionType = await DHBaseItemSheet.selectActionType(); - if(!actionType) return; + if (!actionType) return; try { const cls = actionsTypes[actionType] ?? actionsTypes.attack, action = new cls( @@ -134,9 +133,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { event.stopPropagation(); const actionIndex = button.closest('[data-index]').dataset.index; await this.document.update({ - 'system.actions': this.document.system.actions.filter( - (_, index) => index !== Number.parseInt(actionIndex) - ) + 'system.actions': this.document.system.actions.filter((_, index) => index !== Number.parseInt(actionIndex)) }); } } diff --git a/module/applications/sheets/applications/adversary-settings.mjs b/module/applications/sheets/applications/adversary-settings.mjs new file mode 100644 index 00000000..cc18d7f6 --- /dev/null +++ b/module/applications/sheets/applications/adversary-settings.mjs @@ -0,0 +1,181 @@ +import DHActionConfig from '../../config/Action.mjs'; +import DHBaseItemSheet from '../api/base-item.mjs'; +import { actionsTypes } from '../../../data/_module.mjs'; + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; + +export default class DHAdversarySettings extends HandlebarsApplicationMixin(ApplicationV2) { + constructor(actor) { + super({}); + + this.actor = actor; + } + + get title() { + return `${game.i18n.localize('DAGGERHEART.Sheets.TABS.settings')}`; + } + + static DEFAULT_OPTIONS = { + tag: 'form', + classes: ['daggerheart', 'dh-style', 'dialog', 'adversary-settings'], + window: { + icon: 'fa-solid fa-wrench', + resizable: false + }, + position: { width: 455, height: 'auto' }, + actions: { + addExperience: this.#addExperience, + removeExperience: this.#removeExperience, + addAction: this.#addAction, + editAction: this.#editAction, + removeAction: this.#removeAction + }, + form: { + handler: this.updateForm, + submitOnChange: true, + closeOnSubmit: false + } + }; + + static PARTS = { + header: { + id: 'header', + template: 'systems/daggerheart/templates/sheets/applications/adversary-settings/header.hbs' + }, + tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, + details: { + id: 'details', + template: 'systems/daggerheart/templates/sheets/applications/adversary-settings/details.hbs' + }, + attack: { + id: 'attack', + template: 'systems/daggerheart/templates/sheets/applications/adversary-settings/attack.hbs' + }, + experiences: { + id: 'experiences', + template: 'systems/daggerheart/templates/sheets/applications/adversary-settings/experiences.hbs' + }, + actions: { + id: 'actions', + template: 'systems/daggerheart/templates/sheets/applications/adversary-settings/actions.hbs' + } + }; + + static TABS = { + details: { + active: true, + cssClass: '', + group: 'primary', + id: 'details', + icon: null, + label: 'DAGGERHEART.General.tabs.details' + }, + attack: { + active: false, + cssClass: '', + group: 'primary', + id: 'attack', + icon: null, + label: 'DAGGERHEART.General.tabs.attack' + }, + experiences: { + active: false, + cssClass: '', + group: 'primary', + id: 'experiences', + icon: null, + label: 'DAGGERHEART.General.tabs.experiences' + }, + actions: { + active: false, + cssClass: '', + group: 'primary', + id: 'actions', + icon: null, + label: 'DAGGERHEART.General.tabs.actions' + } + }; + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.document = this.actor; + context.tabs = this._getTabs(this.constructor.TABS); + context.systemFields = this.actor.system.schema.fields; + context.systemFields.attack.fields = this.actor.system.attack.schema.fields; + context.isNPC = true; + console.log(context) + + return context; + } + + _getTabs(tabs) { + for (const v of Object.values(tabs)) { + v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active; + v.cssClass = v.active ? 'active' : ''; + } + + return tabs; + } + + static async #addExperience() { + const newExperience = { + name: 'Experience', + modifier: 0 + }; + await this.actor.update({ [`system.experiences.${foundry.utils.randomID()}`]: newExperience }); + this.render(); + } + + static async #removeExperience(_, target) { + await this.actor.update({ [`system.experiences.-=${target.dataset.experience}`]: null }); + this.render(); + } + + static async #addAction(_event, _button) { + const actionType = await DHBaseItemSheet.selectActionType(); + if (!actionType) return; + try { + const cls = actionsTypes[actionType] ?? actionsTypes.attack, + action = new cls( + { + _id: foundry.utils.randomID(), + type: actionType, + name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType].name), + ...cls.getSourceConfig(this.actor) + }, + { + parent: this.actor + } + ); + await this.actor.update({ 'system.actions': [...this.actor.system.actions, action] }); + await new DHActionConfig(this.actor.system.actions[this.actor.system.actions.length - 1]).render({ + force: true + }); + this.render(); + } catch (error) { + console.log(error); + } + } + + static async #editAction(event, target) { + event.stopPropagation(); + const actionIndex = target.dataset.index; + await new DHActionConfig(this.actor.system.actions[actionIndex]).render({ + force: true + }); + } + + static async #removeAction(event, target) { + event.stopPropagation(); + const actionIndex = target.dataset.index; + await this.actor.update({ + 'system.actions': this.actor.system.actions.filter((_, index) => index !== Number.parseInt(actionIndex)) + }); + this.render(); + } + + static async updateForm(event, _, formData) { + await this.actor.update(formData.object); + this.render(); + } +} diff --git a/module/applications/sheets/applications/environment-settings.mjs b/module/applications/sheets/applications/environment-settings.mjs new file mode 100644 index 00000000..fa58893b --- /dev/null +++ b/module/applications/sheets/applications/environment-settings.mjs @@ -0,0 +1,220 @@ +import DHActionConfig from '../../config/Action.mjs'; +import DHBaseItemSheet from '../api/base-item.mjs'; +import { actionsTypes } from '../../../data/_module.mjs'; + +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; + +export default class DHEnvironmentSettings extends HandlebarsApplicationMixin(ApplicationV2) { + constructor(actor) { + super({}); + + this.actor = actor; + this._dragDrop = this._createDragDropHandlers(); + } + + get title() { + return `${game.i18n.localize('DAGGERHEART.Sheets.TABS.settings')}`; + } + + static DEFAULT_OPTIONS = { + tag: 'form', + classes: ['daggerheart', 'dh-style', 'dialog', 'environment-settings'], + window: { + icon: 'fa-solid fa-wrench', + resizable: false + }, + position: { width: 455, height: 'auto' }, + actions: { + addAction: this.#addAction, + editAction: this.#editAction, + removeAction: this.#removeAction, + addCategory: this.#addCategory, + deleteProperty: this.#deleteProperty, + viewAdversary: this.#viewAdversary, + deleteAdversary: this.#deleteAdversary + }, + form: { + handler: this.updateForm, + submitOnChange: true, + closeOnSubmit: false + }, + dragDrop: [{ dragSelector: null, dropSelector: '.category-container' }] + }; + + static PARTS = { + header: { + id: 'header', + template: 'systems/daggerheart/templates/sheets/applications/environment-settings/header.hbs' + }, + tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, + details: { + id: 'details', + template: 'systems/daggerheart/templates/sheets/applications/environment-settings/details.hbs' + }, + actions: { + id: 'actions', + template: 'systems/daggerheart/templates/sheets/applications/environment-settings/actions.hbs' + }, + adversaries: { + id: 'adversaries', + template: 'systems/daggerheart/templates/sheets/applications/environment-settings/adversaries.hbs' + } + }; + + static TABS = { + details: { + active: true, + cssClass: '', + group: 'primary', + id: 'details', + icon: null, + label: 'DAGGERHEART.General.tabs.details' + }, + actions: { + active: false, + cssClass: '', + group: 'primary', + id: 'actions', + icon: null, + label: 'DAGGERHEART.General.tabs.actions' + }, + adversaries: { + active: false, + cssClass: '', + group: 'primary', + id: 'adversaries', + icon: null, + label: 'DAGGERHEART.General.tabs.adversaries' + } + }; + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.document = this.actor; + context.tabs = this._getTabs(this.constructor.TABS); + context.systemFields = this.actor.system.schema.fields; + context.isNPC = true; + + return context; + } + + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + + this._dragDrop.forEach(d => d.bind(htmlElement)); + } + + _createDragDropHandlers() { + return this.options.dragDrop.map(d => { + d.callbacks = { + drop: this._onDrop.bind(this) + }; + return new foundry.applications.ux.DragDrop.implementation(d); + }); + } + + _getTabs(tabs) { + for (const v of Object.values(tabs)) { + v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active; + v.cssClass = v.active ? 'active' : ''; + } + + return tabs; + } + + static async #addAction(_event, _button) { + const actionType = await DHBaseItemSheet.selectActionType(); + if (!actionType) return; + try { + const cls = actionsTypes[actionType] ?? actionsTypes.attack, + action = new cls( + { + _id: foundry.utils.randomID(), + type: actionType, + name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType].name), + ...cls.getSourceConfig(this.actor) + }, + { + parent: this.actor + } + ); + await this.actor.update({ 'system.actions': [...this.actor.system.actions, action] }); + await new DHActionConfig(this.actor.system.actions[this.actor.system.actions.length - 1]).render({ + force: true + }); + this.render(); + } catch (error) { + console.log(error); + } + } + + static async #editAction(event, target) { + event.stopPropagation(); + const actionIndex = target.dataset.index; + await new DHActionConfig(this.actor.system.actions[actionIndex]).render({ + force: true + }); + } + + static async #removeAction(event, target) { + event.stopPropagation(); + const actionIndex = target.dataset.index; + await this.actor.update({ + 'system.actions': this.actor.system.actions.filter((_, index) => index !== Number.parseInt(actionIndex)) + }); + this.render(); + } + + static async #addCategory() { + await this.actor.update({ + [`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize( + 'DAGGERHEART.Sheets.Environment.newAdversary' + ) + }); + this.render(); + } + + static async #deleteProperty(_, target) { + await this.actor.update({ [`${target.dataset.path}.-=${target.id}`]: null }); + this.render(); + } + + static async #viewAdversary(_, button) { + const adversary = await foundry.utils.fromUuid(button.dataset.adversary); + if (!adversary) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.adversaryMissing')); + return; + } + + adversary.sheet.render(true); + } + + static async #deleteAdversary(event, target) { + const adversaryKey = target.dataset.adversary; + const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`; + const newAdversaries = foundry.utils + .getProperty(this.actor, path) + .filter(x => x && (x?.uuid ?? x) !== adversaryKey); + await this.actor.update({ [path]: newAdversaries }); + this.render(); + } + + async _onDrop(event) { + const data = TextEditor.getDragEventData(event); + const item = await fromUuid(data.uuid); + if (item.type === 'adversary') { + const target = event.target.closest('.category-container'); + const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`; + const current = foundry.utils.getProperty(this.actor, path).map(x => x.uuid); + await this.actor.update({ + [path]: [...current, item.uuid] + }); + this.render(); + } + } + + static async updateForm(event, _, formData) { + await this.actor.update(formData.object); + this.render(); + } +} diff --git a/module/applications/sheets/companion.mjs b/module/applications/sheets/companion.mjs new file mode 100644 index 00000000..46080814 --- /dev/null +++ b/module/applications/sheets/companion.mjs @@ -0,0 +1,86 @@ +import { GMUpdateEvent, socketEvent } from '../../helpers/socket.mjs'; +import DhCompanionlevelUp from '../levelup/companionLevelup.mjs'; +import DaggerheartSheet from './daggerheart-sheet.mjs'; + +const { ActorSheetV2 } = foundry.applications.sheets; +export default class DhCompanionSheet extends DaggerheartSheet(ActorSheetV2) { + static DEFAULT_OPTIONS = { + tag: 'form', + classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'companion'], + position: { width: 700, height: 1000 }, + actions: { + attackRoll: this.attackRoll, + levelUp: this.levelUp + }, + form: { + handler: this.updateForm, + submitOnChange: true, + closeOnSubmit: false + } + }; + + static PARTS = { + sidebar: { template: 'systems/daggerheart/templates/sheets/actors/companion/tempMain.hbs' } + }; + + _attachPartListeners(partId, htmlElement, options) { + super._attachPartListeners(partId, htmlElement, options); + + htmlElement.querySelector('.partner-value')?.addEventListener('change', this.onPartnerChange.bind(this)); + } + + async _prepareContext(_options) { + const context = await super._prepareContext(_options); + context.document = this.document; + context.playerCharacters = game.actors + .filter( + x => + x.type === 'character' && + (x.ownership.default === 3 || + x.ownership[game.user.id] === 3 || + this.document.system.partner?.uuid === x.uuid) + ) + .map(x => ({ key: x.uuid, name: x.name })); + + return context; + } + + static async updateForm(event, _, formData) { + await this.document.update(formData.object); + this.render(); + } + + async onPartnerChange(event) { + const partnerDocument = event.target.value + ? await foundry.utils.fromUuid(event.target.value) + : this.document.system.partner; + const partnerUpdate = { 'system.companion': event.target.value ? this.document.uuid : null }; + + if (!partnerDocument.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)) { + await game.socket.emit(`system.${SYSTEM.id}`, { + action: socketEvent.GMUpdate, + data: { + action: GMUpdateEvent.UpdateDocument, + uuid: partnerDocument.uuid, + update: update + } + }); + } else { + await partnerDocument.update(partnerUpdate); + } + + await this.document.update({ 'system.partner': event.target.value }); + + if (!event.target.value) { + await this.document.updateLevel(1); + } + } + + static async attackRoll(event) { + this.actor.system.attack.use(event); + } + + static async levelUp() { + new DhCompanionlevelUp(this.document).render(true); + } +} diff --git a/module/applications/sheets/daggerheart-sheet.mjs b/module/applications/sheets/daggerheart-sheet.mjs index 4810b0a7..aed1dccc 100644 --- a/module/applications/sheets/daggerheart-sheet.mjs +++ b/module/applications/sheets/daggerheart-sheet.mjs @@ -38,7 +38,7 @@ export default function DhpApplicationMixin(Base) { const attr = target.dataset.edit; const current = foundry.utils.getProperty(this.document, attr); const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}; - const fp = new FilePicker({ + const fp = new foundry.applications.apps.FilePicker.implementation({ current, type: 'image', redirectToRoot: img ? [img] : [], @@ -60,7 +60,7 @@ export default function DhpApplicationMixin(Base) { // drop: this._canDragDrop.bind(this) // }; d.callbacks = { - // dragstart: this._onDragStart.bind(this), + dragstart: this._onDragStart.bind(this), // dragover: this._onDragOver.bind(this), drop: this._onDrop.bind(this) }; @@ -68,6 +68,7 @@ export default function DhpApplicationMixin(Base) { }); } + async _onDragStart(event) {} _onDrop(event) {} _getTabs(tabs) { diff --git a/module/applications/sheets/environment.mjs b/module/applications/sheets/environment.mjs deleted file mode 100644 index fca33f4d..00000000 --- a/module/applications/sheets/environment.mjs +++ /dev/null @@ -1,107 +0,0 @@ -import DaggerheartSheet from './daggerheart-sheet.mjs'; - -const { ActorSheetV2 } = foundry.applications.sheets; -export default class DhpEnvironment extends DaggerheartSheet(ActorSheetV2) { - static DEFAULT_OPTIONS = { - tag: 'form', - classes: ['daggerheart', 'sheet', 'actor', 'dh-style', 'environment'], - position: { - width: 450, - height: 1000 - }, - actions: { - addAdversary: this.addAdversary, - addFeature: this.addFeature, - deleteProperty: this.deleteProperty, - viewAdversary: this.viewAdversary - }, - form: { - handler: this._updateForm, - submitOnChange: true, - closeOnSubmit: false - }, - dragDrop: [{ dragSelector: null, dropSelector: '.adversary-container' }] - }; - - static PARTS = { - 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' } - }; - - 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) { - const context = await super._prepareContext(_options); - context.document = this.document; - context.tabs = super._getTabs(this.constructor.TABS); - context.getEffectDetails = this.getEffectDetails.bind(this); - - return context; - } - - static async _updateForm(event, _, formData) { - await this.document.update(formData.object); - this.render(); - } - - getEffectDetails(id) { - return {}; - } - - static async addAdversary() { - await this.document.update({ - [`system.potentialAdversaries.${foundry.utils.randomID()}.label`]: game.i18n.localize( - 'DAGGERHEART.Sheets.Environment.newAdversary' - ) - }); - this.render(); - } - - static async addFeature() { - ui.notifications.error('Not Implemented yet. Awaiting datamodel rework'); - } - - static async deleteProperty(_, target) { - await this.document.update({ [`${target.dataset.path}.-=${target.id}`]: null }); - this.render(); - } - - static async viewAdversary(_, button) { - const adversary = foundry.utils.getProperty( - this.document.system.potentialAdversaries, - `${button.dataset.potentialAdversary}.adversaries.${button.dataset.adversary}` - ); - adversary.sheet.render(true); - } - - 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/items/feature.mjs b/module/applications/sheets/items/feature.mjs index 655e4542..a5b8bd8f 100644 --- a/module/applications/sheets/items/feature.mjs +++ b/module/applications/sheets/items/feature.mjs @@ -6,11 +6,7 @@ export default class FeatureSheet extends DHBaseItemSheet { id: 'daggerheart-feature', classes: ['feature'], position: { height: 600 }, - window: { resizable: true }, - actions: { - addEffect: this.addEffect, - removeEffect: this.removeEffect - } + window: { resizable: true } }; /**@override */ @@ -22,27 +18,16 @@ export default class FeatureSheet extends DHBaseItemSheet { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs', scrollable: ['.actions'] }, - settings: { - template: 'systems/daggerheart/templates/sheets/items/feature/settings.hbs', - scrollable: ['.settings'] - }, effects: { - template: 'systems/daggerheart/templates/sheets/items/feature/effects.hbs', + template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs', scrollable: ['.effects'] } }; - /** - * Internally tracks the selected effect type from the select. - * @type {String} - * @private - */ - _selectedEffectType; - /**@override */ static TABS = { primary: { - tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'settings' }, { id: 'effects' }], + tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'effects' }], initial: 'description', labelPrefix: 'DAGGERHEART.Sheets.TABS' } @@ -50,68 +35,10 @@ export default class FeatureSheet extends DHBaseItemSheet { /* -------------------------------------------- */ - /**@inheritdoc*/ - _attachPartListeners(partId, htmlElement, options) { - super._attachPartListeners(partId, htmlElement, options); - if (partId === 'effects') - htmlElement.querySelector('.effect-select')?.addEventListener('change', this._effectSelect.bind(this)); - } - - /** - * Handles selection of a new effect type. - * @param {Event} event - Change Event - */ - _effectSelect(event) { - const value = event.currentTarget.value; - this._selectedEffectType = value; - this.render({ parts: ['effects'] }); - } - - /* -------------------------------------------- */ - /**@inheritdoc */ async _prepareContext(_options) { const context = await super._prepareContext(_options); - context.properties = CONFIG.daggerheart.ACTOR.featureProperties; - context.dice = CONFIG.daggerheart.GENERAL.diceTypes; - context.effectConfig = CONFIG.daggerheart.EFFECTS; - - context.selectedEffectType = this._selectedEffectType; return context; } - - /* -------------------------------------------- */ - /* Application Clicks Actions */ - /* -------------------------------------------- */ - - /** - * Adds a new effect to the item, based on the selected effect type. - * @param {PointerEvent} _event - The originating click event - * @param {HTMLElement} _target - The capturing HTML element which defines the [data-action] - * @returns - */ - static async addEffect(_event, _target) { - const type = this._selectedEffectType; - if (!type) return; - const { id, name, ...rest } = CONFIG.daggerheart.EFFECTS.effectTypes[type]; - await this.item.update({ - [`system.effects.${foundry.utils.randomID()}`]: { - type, - value: '', - ...rest - } - }); - } - - /** - * Removes an effect from the item. - * @param {PointerEvent} _event - The originating click event - * @param {HTMLElement} target - The capturing HTML element which defines the [data-action] - * @returns - */ - static async removeEffect(_event, target) { - const path = `system.effects.-=${target.dataset.effect}`; - await this.item.update({ [path]: null }); - } } diff --git a/module/applications/tooltipManager.mjs b/module/applications/tooltipManager.mjs new file mode 100644 index 00000000..d7d3117c --- /dev/null +++ b/module/applications/tooltipManager.mjs @@ -0,0 +1,16 @@ +export default class DhTooltipManager extends TooltipManager { + async activate(element, options = {}) { + let html = options.html; + if (element.dataset.tooltip.startsWith('#item#')) { + const item = await foundry.utils.fromUuid(element.dataset.tooltip.slice(6)); + if (item) { + html = await foundry.applications.handlebars.renderTemplate( + `systems/daggerheart/templates/tooltip/${item.type}.hbs`, + item + ); + } + } + + super.activate(element, { ...options, html: html }); + } +} diff --git a/module/config/actionConfig.mjs b/module/config/actionConfig.mjs index 91004bdd..e83040f5 100644 --- a/module/config/actionConfig.mjs +++ b/module/config/actionConfig.mjs @@ -1,42 +1,42 @@ export const actionTypes = { attack: { id: 'attack', - name: 'DAGGERHEART.Actions.Types.Attack.Name', + name: 'DAGGERHEART.Actions.Types.attack.name', icon: 'fa-swords' }, // spellcast: { // id: 'spellcast', - // name: 'DAGGERHEART.Actions.Types.Spellcast.Name', + // name: 'DAGGERHEART.Actions.Types.spellcast.name', // icon: 'fa-book-sparkles' // }, healing: { id: 'healing', - name: 'DAGGERHEART.Actions.Types.Healing.Name', + name: 'DAGGERHEART.Actions.Types.healing.name', icon: 'fa-kit-medical' }, // resource: { // id: 'resource', - // name: 'DAGGERHEART.Actions.Types.Resource.Name', + // name: 'DAGGERHEART.Actions.Types.resource.name', // icon: 'fa-honey-pot' // }, damage: { id: 'damage', - name: 'DAGGERHEART.Actions.Types.Damage.Name', + name: 'DAGGERHEART.Actions.Types.damage.name', icon: 'fa-bone-break' }, summon: { id: 'summon', - name: 'DAGGERHEART.Actions.Types.Summon.Name', + name: 'DAGGERHEART.Actions.Types.summon.name', icon: 'fa-ghost' }, effect: { id: 'effect', - name: 'DAGGERHEART.Actions.Types.Effect.Name', + name: 'DAGGERHEART.Actions.Types.effect.name', icon: 'fa-person-rays' }, macro: { id: 'macro', - name: 'DAGGERHEART.Actions.Types.Macro.Name', + name: 'DAGGERHEART.Actions.Types.macro.name', icon: 'fa-scroll' } }; @@ -76,7 +76,7 @@ export const damageOnSave = { label: 'Full damage', mod: 1 } -} +}; export const diceCompare = { below: { @@ -104,4 +104,19 @@ export const diceCompare = { label: 'Above', operator: '>' } +}; + +export const advandtageState = { + disadvantage: { + label: 'DAGGERHEART.General.Disadvantage.Full', + value: -1 + }, + neutral: { + label: 'DAGGERHEART.General.Neutral.Full', + value: 0 + }, + advantage: { + label: 'DAGGERHEART.General.Advantage.Full', + value: 1 + } } diff --git a/module/config/actorConfig.mjs b/module/config/actorConfig.mjs index 6af480d7..5b5169ad 100644 --- a/module/config/actorConfig.mjs +++ b/module/config/actorConfig.mjs @@ -83,72 +83,72 @@ export const featureProperties = { export const adversaryTypes = { bruiser: { id: 'bruiser', - label: 'DAGGERHEART.Adversary.Type.Bruiser.label', - description: 'DAGGERHEART.Adversary.Bruiser.Description' + label: 'DAGGERHEART.Adversary.Type.bruiser.label', + description: 'DAGGERHEART.Adversary.bruiser.description' }, horde: { id: 'horde', - label: 'DAGGERHEART.Adversary.Type.Horde.label', - description: 'DAGGERHEART.Adversary.Horde.Description' + label: 'DAGGERHEART.Adversary.Type.horde.label', + description: 'DAGGERHEART.Adversary.horde.description' }, leader: { id: 'leader', - label: 'DAGGERHEART.Adversary.Type.Leader.label', - description: 'DAGGERHEART.Adversary.Leader.Description' + label: 'DAGGERHEART.Adversary.Type.leader.label', + description: 'DAGGERHEART.Adversary.leader.description' }, minion: { id: 'minion', - label: 'DAGGERHEART.Adversary.Type.Minion.label', - description: 'DAGGERHEART.Adversary.Minion.Description' + label: 'DAGGERHEART.Adversary.Type.minion.label', + description: 'DAGGERHEART.Adversary.minion.description' }, ranged: { id: 'ranged', - label: 'DAGGERHEART.Adversary.Type.Ranged.label', - description: 'DAGGERHEART.Adversary.Ranged.Description' + label: 'DAGGERHEART.Adversary.Type.ranged.label', + description: 'DAGGERHEART.Adversary.ranged.description' }, skulk: { id: 'skulk', - label: 'DAGGERHEART.Adversary.Type.Skulk.label', - description: 'DAGGERHEART.Adversary.Skulk.Description' + label: 'DAGGERHEART.Adversary.Type.skulk.label', + description: 'DAGGERHEART.Adversary.skulk.description' }, social: { id: 'social', - label: 'DAGGERHEART.Adversary.Type.Social.label', - description: 'DAGGERHEART.Adversary.Social.Description' + label: 'DAGGERHEART.Adversary.Type.social.label', + description: 'DAGGERHEART.Adversary.social.description' }, solo: { id: 'solo', - label: 'DAGGERHEART.Adversary.Type.Solo.label', - description: 'DAGGERHEART.Adversary.Solo.Description' + label: 'DAGGERHEART.Adversary.Type.solo.label', + description: 'DAGGERHEART.Adversary.solo.description' }, standard: { id: 'standard', - label: 'DAGGERHEART.Adversary.Type.Standard.label', - description: 'DAGGERHEART.Adversary.Standard.Description' + label: 'DAGGERHEART.Adversary.Type.standard.label', + description: 'DAGGERHEART.Adversary.standard.description' }, support: { id: 'support', - label: 'DAGGERHEART.Adversary.Type.Support.label', - description: 'DAGGERHEART.Adversary.Support.Description' + 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' + 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' + 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' + 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' + label: 'DAGGERHEART.Environment.Type.event.label', + description: 'DAGGERHEART.Environment.Type.event.description' } }; diff --git a/module/config/flagsConfig.mjs b/module/config/flagsConfig.mjs index b06a36e1..252863f1 100644 --- a/module/config/flagsConfig.mjs +++ b/module/config/flagsConfig.mjs @@ -1 +1,9 @@ export const displayDomainCardsAsList = 'displayDomainCardsAsList'; +export const narrativeCountdown = { + simple: 'countdown-narrative-simple', + position: 'countdown-narrative-position' +}; +export const encounterCountdown = { + simple: 'countdown-encounter-simple', + position: 'countdown-encounter-position' +}; diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index a6484b61..6710d3ed 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -261,48 +261,6 @@ export const tiers = { } }; -export const objectTypes = { - character: { - name: 'TYPES.Actor.character' - }, - npc: { - name: 'TYPES.Actor.npc' - }, - adversary: { - name: 'TYPES.Actor.adversary' - }, - ancestry: { - name: 'TYPES.Item.ancestry' - }, - community: { - name: 'TYPES.Item.community' - }, - class: { - name: 'TYPES.Item.class' - }, - subclass: { - name: 'TYPES.Item.subclass' - }, - feature: { - name: 'TYPES.Item.feature' - }, - domainCard: { - name: 'TYPES.Item.domainCard' - }, - consumable: { - name: 'TYPES.Item.consumable' - }, - miscellaneous: { - name: 'TYPES.Item.miscellaneous' - }, - weapon: { - name: 'TYPES.Item.weapon' - }, - armor: { - name: 'TYPES.Item.armor' - } -}; - export const diceTypes = { d4: 'd4', d6: 'd6', @@ -325,7 +283,7 @@ export const diceSetNumbers = { cast: 'Spellcast', scale: 'Cost Scaling', flat: 'Flat' -} +}; export const getDiceSoNicePresets = () => { const { diceSoNice } = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance); @@ -384,39 +342,53 @@ export const refreshTypes = { export const abilityCosts = { hope: { id: 'hope', - label: 'Hope' + label: 'Hope', + group: 'TYPES.Actor.character' }, stress: { id: 'stress', - label: 'DAGGERHEART.HealingType.Stress.Name' + label: 'DAGGERHEART.HealingType.Stress.Name', + group: 'TYPES.Actor.character' }, armor: { id: 'armor', - label: 'Armor Stack' + label: 'Armor Stack', + group: 'TYPES.Actor.character' }, hp: { id: 'hp', - label: 'DAGGERHEART.HealingType.HitPoints.Name' + label: 'DAGGERHEART.HealingType.HitPoints.Name', + group: 'TYPES.Actor.character' }, prayer: { id: 'prayer', - label: 'Prayer Dice' + label: 'Prayer Dice', + group: 'TYPES.Actor.character' }, favor: { id: 'favor', - label: 'Favor Points' + label: 'Favor Points', + group: 'TYPES.Actor.character' }, slayer: { id: 'slayer', - label: 'Slayer Dice' + label: 'Slayer Dice', + group: 'TYPES.Actor.character' }, tide: { id: 'tide', - label: 'Tide' + label: 'Tide', + group: 'TYPES.Actor.character' }, chaos: { id: 'chaos', - label: 'Chaos' + label: 'Chaos', + group: 'TYPES.Actor.character' + }, + fear: { + id: 'fear', + label: 'Fear', + group: 'TYPES.Actor.adversary' } }; diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index a2a786b9..6d8488ba 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -712,14 +712,14 @@ export const valueTypes = { export const actionTypes = { passive: { id: 'passive', - label: 'DAGGERHEART.ActionType.Passive' + label: 'DAGGERHEART.ActionType.passive' }, action: { id: 'action', - label: 'DAGGERHEART.ActionType.Action' + label: 'DAGGERHEART.ActionType.action' }, reaction: { id: 'reaction', - label: 'DAGGERHEART.ActionType.Reaction' + label: 'DAGGERHEART.ActionType.reaction' } }; diff --git a/module/data/action/action.mjs b/module/data/action/action.mjs index ee9bbabe..7bda6089 100644 --- a/module/data/action/action.mjs +++ b/module/data/action/action.mjs @@ -1,4 +1,3 @@ -import CostSelectionDialog from '../../applications/costSelectionDialog.mjs'; import { DHActionDiceData, DHActionRollData, DHDamageData, DHDamageField } from './actionDice.mjs'; import DhpActor from '../../documents/actor.mjs'; import D20RollDialog from '../../dialogs/d20RollDialog.mjs'; @@ -73,7 +72,10 @@ export class DHBaseAction extends foundry.abstract.DataModel { save: new fields.SchemaField({ trait: new fields.StringField({ nullable: true, initial: null, choices: SYSTEM.ACTOR.abilities }), difficulty: new fields.NumberField({ nullable: true, initial: 10, integer: true, min: 0 }), - damageMod: new fields.StringField({ initial: SYSTEM.ACTIONS.damageOnSave.none.id, choices: SYSTEM.ACTIONS.damageOnSave }) + damageMod: new fields.StringField({ + initial: SYSTEM.ACTIONS.damageOnSave.none.id, + choices: SYSTEM.ACTIONS.damageOnSave + }) }), target: new fields.SchemaField({ type: new fields.StringField({ @@ -98,9 +100,12 @@ export class DHBaseAction extends foundry.abstract.DataModel { initial: SYSTEM.GENERAL.healingTypes.hitPoints.id, label: 'Healing' }), - resultBased: new fields.BooleanField({ initial: false, label: "DAGGERHEART.Actions.Settings.ResultBased.label" }), + resultBased: new fields.BooleanField({ + initial: false, + label: 'DAGGERHEART.Actions.Settings.ResultBased.label' + }), value: new fields.EmbeddedDataField(DHActionDiceData), - valueAlt: new fields.EmbeddedDataField(DHActionDiceData), + valueAlt: new fields.EmbeddedDataField(DHActionDiceData) }) }, extraSchemas = {}; @@ -124,7 +129,11 @@ export class DHBaseAction extends foundry.abstract.DataModel { } get actor() { - return this.item instanceof DhpActor ? this.item : this.item?.actor; + return this.item instanceof DhpActor + ? this.item + : this.item?.parent instanceof DhpActor + ? this.item.parent + : this.item?.actor; } get chatTemplate() { @@ -153,7 +162,7 @@ export class DHBaseAction extends foundry.abstract.DataModel { return updateSource; } - getRollData(data={}) { + getRollData(data = {}) { const actorData = this.actor.getRollData(false); // Remove when included directly in Actor getRollData @@ -166,11 +175,11 @@ export class DHBaseAction extends foundry.abstract.DataModel { return a; }, {}) : 1; */ - actorData.scale = data.costs?.length // Right now only return the first scalable cost. - ? (data.costs.find(c => c.scalable)?.total ?? 1) - : 1; + actorData.scale = data.costs?.length // Right now only return the first scalable cost. + ? (data.costs.find(c => c.scalable)?.total ?? 1) + : 1; actorData.roll = {}; - + return actorData; } @@ -191,12 +200,14 @@ export class DHBaseAction extends foundry.abstract.DataModel { // Prepare Costs const costsConfig = this.prepareCost(); - if(isFastForward && !this.hasCost(costsConfig)) return ui.notifications.warn("You don't have the resources to use that action."); + if (isFastForward && !this.hasCost(costsConfig)) + return ui.notifications.warn("You don't have the resources to use that action."); // config = this.prepareUseCost(config) // Prepare Uses const usesConfig = this.prepareUse(); - if(isFastForward && !this.hasUses(usesConfig)) return ui.notifications.warn("That action doesn't have remaining uses."); + if (isFastForward && !this.hasUses(usesConfig)) + return ui.notifications.warn("That action doesn't have remaining uses."); // config = this.prepareUseCost(config) // Prepare Roll Data @@ -209,24 +220,24 @@ export class DHBaseAction extends foundry.abstract.DataModel { costs: costsConfig, uses: usesConfig, data: actorData - } - - if ( Hooks.call(`${SYSTEM.id}.preUseAction`, this, config) === false ) return; + }; + + if (Hooks.call(`${SYSTEM.id}.preUseAction`, this, config) === false) return; // Display configuration window if necessary - if ( config.dialog?.configure && this.requireConfigurationDialog(config) ) { + if (config.dialog?.configure && this.requireConfigurationDialog(config)) { config = await D20RollDialog.configure(config); if (!config) return; } - if ( this.hasRoll ) { + if (this.hasRoll) { const rollConfig = this.prepareRoll(config); config.roll = rollConfig; config = await this.actor.diceRoll(config); if (!config) return; } - if( this.hasSave ) { + if (this.hasSave) { /* config.targets.forEach((t) => { if(t.hit) { const target = game.canvas.tokens.get(t.id), @@ -258,16 +269,16 @@ export class DHBaseAction extends foundry.abstract.DataModel { }) */ } - if ( this.doFollowUp() ) { - if(this.rollDamage) await this.rollDamage(event, config); - if(this.rollHealing) await this.rollHealing(event, config); - if(this.trigger) await this.trigger(event, config); + if (this.doFollowUp()) { + if (this.rollDamage) await this.rollDamage(event, config); + if (this.rollHealing) await this.rollHealing(event, config); + if (this.trigger) await this.trigger(event, config); } // Consume resources await this.consume(config); - - if ( Hooks.call(`${SYSTEM.id}.postUseAction`, this, config) === false ) return; + + if (Hooks.call(`${SYSTEM.id}.postUseAction`, this, config) === false) return; return config; } @@ -287,7 +298,7 @@ export class DHBaseAction extends foundry.abstract.DataModel { hasHealing: !!this.healing, hasEffect: !!this.effects?.length, hasSave: this.hasSave - } + }; } requireConfigurationDialog(config) { @@ -308,16 +319,15 @@ export class DHBaseAction extends foundry.abstract.DataModel { prepareTarget() { let targets; if (this.target?.type === SYSTEM.ACTIONS.targetTypes.self.id) - targets = this.formatTarget(this.actor.token ?? this.actor.prototypeToken); + targets = this.constructor.formatTarget(this.actor.token ?? this.actor.prototypeToken); targets = Array.from(game.user.targets); // foundry.CONST.TOKEN_DISPOSITIONS.FRIENDLY if (this.target?.type && this.target.type !== SYSTEM.ACTIONS.targetTypes.any.id) { targets = targets.filter(t => this.isTargetFriendly(t)); if (this.target.amount && targets.length > this.target.amount) targets = []; } - targets = targets.map(t => this.formatTarget(t)); + targets = targets.map(t => this.constructor.formatTarget(t)); return targets; - } prepareRange() { @@ -326,16 +336,18 @@ export class DHBaseAction extends foundry.abstract.DataModel { } prepareRoll() { - const roll = { + const roll = { modifiers: [], trait: this.roll?.trait, label: 'Attack', type: this.actionType, difficulty: this.roll?.difficulty, - formula: this.roll.getFormula() + formula: this.roll.getFormula(), + bonus: this.roll.bonus, + advantage: SYSTEM.ACTIONS.advandtageState[this.roll.advState].value }; - if(this.roll?.type === 'diceSet') roll.lite = true; - + if (this.roll?.type === 'diceSet') roll.lite = true; + return roll; } @@ -344,12 +356,14 @@ export class DHBaseAction extends foundry.abstract.DataModel { } async consume(config) { - const resources = config.costs.filter(c => c.enabled !== false).map(c => { - return { type: c.type, value: (c.total ?? c.value) * -1 }; - }); - + const resources = config.costs + .filter(c => c.enabled !== false) + .map(c => { + return { type: c.type, value: (c.total ?? c.value) * -1 }; + }); + await this.actor.modifyResource(resources); - if(config.uses?.enabled) { + if (config.uses?.enabled) { const newActions = foundry.utils.getProperty(this.item.system, this.systemPath).map(x => x.toObject()); newActions[this.index].uses.value++; await this.item.update({ [`system.${this.systemPath}`]: newActions }); @@ -359,7 +373,7 @@ export class DHBaseAction extends foundry.abstract.DataModel { /* ROLL */ get hasRoll() { - return !!this.roll?.type; + return !!this.roll?.type || !!this.roll?.bonus; } /* ROLL */ @@ -387,14 +401,26 @@ export class DHBaseAction extends foundry.abstract.DataModel { } hasCost(costs) { - const realCosts = this.getRealCosts(costs); - return realCosts.reduce((a, c) => a && this.actor.system.resources[c.type]?.value >= (c.total ?? c.value), true); + const realCosts = this.getRealCosts(costs), + hasFearCost = realCosts.findIndex(c => c.type === 'fear'); + if (hasFearCost > -1) { + const fearCost = realCosts.splice(hasFearCost, 1); + if ( + !game.user.isGM || + fearCost[0].total > game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear) + ) + return false; + } + return realCosts.reduce( + (a, c) => a && this.actor.system.resources[c.type]?.value >= (c.total ?? c.value), + true + ); } /* COST */ /* USES */ calcUses(uses) { - if(!uses) return null; + if (!uses) return null; return { ...uses, enabled: uses.hasOwnProperty('enabled') ? uses.enabled : true @@ -402,7 +428,7 @@ export class DHBaseAction extends foundry.abstract.DataModel { } hasUses(uses) { - if(!uses) return true; + if (!uses) return true; return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= uses.max; } /* USES */ @@ -419,7 +445,7 @@ export class DHBaseAction extends foundry.abstract.DataModel { ); } - formatTarget(actor) { + static formatTarget(actor) { return { id: actor.id, actorId: actor.actor.uuid, @@ -432,19 +458,20 @@ export class DHBaseAction extends foundry.abstract.DataModel { /* TARGET */ /* RANGE */ - + /* RANGE */ /* EFFECTS */ - async applyEffects(event, data, force = false) { - if (!this.effects?.length || !data.system.targets.length) return; + async applyEffects(event, data, targets) { + targets ??= data.system.targets; + if (!this.effects?.length || !targets.length) return; let effects = this.effects; - data.system.targets.forEach(async token => { + targets.forEach(async token => { if (!token.hit && !force) return; - if(this.hasSave && token.saved.success === true) { - effects = this.effects.filter(e => e.onSave === true) + if (this.hasSave && token.saved.success === true) { + effects = this.effects.filter(e => e.onSave === true); } - if(!effects.length) return; + if (!effects.length) return; effects.forEach(async e => { const actor = canvas.tokens.get(token.id)?.actor, effect = this.item.effects.get(e._id); @@ -479,53 +506,76 @@ export class DHBaseAction extends foundry.abstract.DataModel { /* SAVE */ async rollSave(target, event, message) { - if(!target?.actor) return; - target.actor.diceRoll({ - event, - title: 'Roll Save', - roll: { - trait: this.save.trait, - difficulty: this.save.difficulty, - type: "reaction" - }, - data: target.actor.getRollData() - }).then(async (result) => { - if(result) this.updateChatMessage(message, target.id, {result: result.roll.total, success: result.roll.success}); - }) + if (!target?.actor) return; + return target.actor + .diceRoll({ + event, + title: 'Roll Save', + roll: { + trait: this.save.trait, + difficulty: this.save.difficulty, + type: 'reaction' + }, + data: target.actor.getRollData() + }) + .then(async result => { + if (result) + this.updateChatMessage(message, target.id, { + result: result.roll.total, + success: result.roll.success + }); + }); } - async updateChatMessage(message, targetId, changes, chain=true) { + async updateChatMessage(message, targetId, changes, chain = true) { setTimeout(async () => { const chatMessage = ui.chat.collection.get(message._id), msgTargets = chatMessage.system.targets, msgTarget = msgTargets.find(mt => mt.id === targetId); msgTarget.saved = changes; - await chatMessage.update({'system.targets': msgTargets}); - },100); - if(chain) { - if(message.system.source.message) this.updateChatMessage(ui.chat.collection.get(message.system.source.message), targetId, changes, false); + await chatMessage.update({ 'system.targets': msgTargets }); + }, 100); + if (chain) { + if (message.system.source.message) + this.updateChatMessage(ui.chat.collection.get(message.system.source.message), targetId, changes, false); const relatedChatMessages = ui.chat.collection.filter(c => c.system.source.message === message._id); relatedChatMessages.forEach(c => { this.updateChatMessage(c, targetId, changes, false); - }) + }); } } /* SAVE */ + + async toChat(origin) { + const cls = getDocumentClass('ChatMessage'); + const systemData = { + title: game.i18n.localize('DAGGERHEART.ActionType.action'), + origin: origin, + img: this.img, + name: this.name, + description: this.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()); + } } export class DHDamageAction extends DHBaseAction { static extraSchemas = ['damage', 'target', 'effects']; - /* async use(event, ...args) { - const config = await super.use(event, args); - if (!config || ['error', 'warning'].includes(config.type)) return; - if (!this.directDamage) return; - return await this.rollDamage(event, config); - } */ - getFormulaValue(part, data) { let formulaValue = part.value; - if(this.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt; + if (this.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt; return formulaValue; } @@ -535,19 +585,19 @@ export class DHDamageAction extends DHBaseAction { if (!formula || formula == '') return; let roll = { formula: formula, total: formula }, bonusDamage = []; - - if(isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(data.system ?? data)); + + if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(data.system ?? data)); const config = { title: game.i18n.format('DAGGERHEART.Chat.DamageRoll.Title', { damage: this.name }), - roll: {formula}, - targets: (data.system?.targets.filter(t => t.hit) ?? data.targets), + roll: { formula }, + targets: data.system?.targets.filter(t => t.hit) ?? data.targets, hasSave: this.hasSave, source: data.system?.source, event }; - if(this.hasSave) config.onSave = this.save.damageMod; - if(data.system) { + if (this.hasSave) config.onSave = this.save.damageMod; + if (data.system) { config.source.message = data._id; config.directDamage = false; } @@ -597,7 +647,8 @@ export class DHHealingAction extends DHBaseAction { getFormulaValue(data) { let formulaValue = this.healing.value; - if(this.hasRoll && this.healing.resultBased && data.system.roll.result.duality === -1) return this.healing.valueAlt; + if (this.hasRoll && this.healing.resultBased && data.system.roll.result.duality === -1) + return this.healing.valueAlt; return formulaValue; } @@ -608,12 +659,12 @@ export class DHHealingAction extends DHBaseAction { if (!formula || formula == '') return; let roll = { formula: formula, total: formula }, bonusDamage = []; - + const config = { title: game.i18n.format('DAGGERHEART.Chat.HealingRoll.Title', { healing: game.i18n.localize(SYSTEM.GENERAL.healingTypes[this.healing.type].label) }), - roll: {formula}, + roll: { formula }, targets: (data.system?.targets ?? data.targets).filter(t => t.hit), messageType: 'healing', type: this.healing.type, diff --git a/module/data/action/actionDice.mjs b/module/data/action/actionDice.mjs index e422b856..adf00461 100644 --- a/module/data/action/actionDice.mjs +++ b/module/data/action/actionDice.mjs @@ -11,7 +11,8 @@ export class DHActionRollData extends foundry.abstract.DataModel { type: new fields.StringField({ nullable: true, initial: null, choices: SYSTEM.GENERAL.rollTypes }), trait: new fields.StringField({ nullable: true, initial: null, choices: SYSTEM.ACTOR.abilities }), difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }), - bonus: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }), + bonus: new fields.NumberField({ nullable: true, initial: null, integer: true }), + advState: new fields.StringField({ choices: SYSTEM.ACTIONS.advandtageState, initial: 'neutral' }), diceRolling: new fields.SchemaField({ multiplier: new fields.StringField({ choices: SYSTEM.GENERAL.diceSetNumbers, @@ -25,17 +26,20 @@ export class DHActionRollData extends foundry.abstract.DataModel { initial: 'above', label: 'Should be' }), - treshold: new fields.NumberField({ initial: 1, integer: true, min: 1, label: 'Treshold' }), + treshold: new fields.NumberField({ initial: 1, integer: true, min: 1, label: 'Treshold' }) }) }; } getFormula() { - if(!this.type) return; + if (!this.type) return; let formula = ''; switch (this.type) { case 'diceSet': - const multiplier = this.diceRolling.multiplier === 'flat' ? this.diceRolling.flatMultiplier : `@${this.diceRolling.multiplier}`; + const multiplier = + this.diceRolling.multiplier === 'flat' + ? this.diceRolling.flatMultiplier + : `@${this.diceRolling.multiplier}`; formula = `${multiplier}${this.diceRolling.dice}cs${SYSTEM.ACTIONS.diceCompare[this.diceRolling.compare].operator}${this.diceRolling.treshold}`; break; default: @@ -59,7 +63,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel { label: 'Multiplier' }), flatMultiplier: new fields.NumberField({ nullable: true, initial: 1, label: 'Flat Multiplier' }), - dice: new fields.StringField({ choices: SYSTEM.GENERAL.diceTypes, initial: 'd6', label: 'Formula' }), + dice: new fields.StringField({ choices: SYSTEM.GENERAL.diceTypes, initial: 'd6', label: 'Dice' }), bonus: new fields.NumberField({ nullable: true, initial: null, label: 'Bonus' }), custom: new fields.SchemaField({ enabled: new fields.BooleanField({ label: 'Custom Formula' }), @@ -75,9 +79,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel { : `${multiplier ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`; */ const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`, bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''; - return this.custom.enabled - ? this.custom.formula - : `${multiplier ?? 1}${this.dice}${bonus}`; + return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`; } } @@ -105,9 +107,12 @@ export class DHDamageData extends foundry.abstract.DataModel { nullable: false, required: true }), - resultBased: new fields.BooleanField({ initial: false, label: "DAGGERHEART.Actions.Settings.ResultBased.label" }), + resultBased: new fields.BooleanField({ + initial: false, + label: 'DAGGERHEART.Actions.Settings.ResultBased.label' + }), value: new fields.EmbeddedDataField(DHActionDiceData), - valueAlt: new fields.EmbeddedDataField(DHActionDiceData), + valueAlt: new fields.EmbeddedDataField(DHActionDiceData) }; } } diff --git a/module/data/actor/_module.mjs b/module/data/actor/_module.mjs index cdbf7178..c19036eb 100644 --- a/module/data/actor/_module.mjs +++ b/module/data/actor/_module.mjs @@ -1,11 +1,13 @@ import DhCharacter from './character.mjs'; +import DhCompanion from './companion.mjs'; import DhAdversary from './adversary.mjs'; import DhEnvironment from './environment.mjs'; -export { DhCharacter, DhAdversary, DhEnvironment }; +export { DhCharacter, DhCompanion, DhAdversary, DhEnvironment }; export const config = { character: DhCharacter, + companion: DhCompanion, adversary: DhAdversary, environment: DhEnvironment }; diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index b1ed29ec..6e94d38e 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -30,8 +30,11 @@ export default class DhpAdversary extends BaseDataActor { choices: SYSTEM.ACTOR.adversaryTypes, initial: SYSTEM.ACTOR.adversaryTypes.standard.id }), - motivesAndTactics: new fields.HTMLField(), + description: new fields.StringField(), + motivesAndTactics: new fields.StringField(), + notes: new fields.HTMLField(), difficulty: new fields.NumberField({ required: true, initial: 1, integer: true }), + hordeHp: 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 }) @@ -40,6 +43,7 @@ export default class DhpAdversary extends BaseDataActor { hitPoints: resourceField(), stress: resourceField() }), + actions: new fields.ArrayField(new ActionField()), attack: new ActionField({ initial: { name: 'Attack', @@ -66,7 +70,7 @@ export default class DhpAdversary extends BaseDataActor { experiences: new fields.TypedObjectField( new fields.SchemaField({ name: new fields.StringField(), - value: new fields.NumberField({ required: true, integer: true, initial: 1 }) + modifier: new fields.NumberField({ required: true, integer: true, initial: 1 }) }) ), bonuses: new fields.SchemaField({ diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index fb3466bb..7b1ec939 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -1,6 +1,7 @@ import { burden } from '../../config/generalConfig.mjs'; +import ActionField from '../fields/actionField.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; -import { LevelOptionType } from '../levelTier.mjs'; +import DhLevelData from '../levelData.mjs'; import BaseDataActor from './base.mjs'; const attributeField = () => @@ -96,7 +97,8 @@ export default class DhCharacter extends BaseDataActor { value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }), subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }) }), - levelData: new fields.EmbeddedDataField(DhPCLevelData), + actions: new fields.ArrayField(new ActionField()), + levelData: new fields.EmbeddedDataField(DhLevelData), bonuses: new fields.SchemaField({ armorScore: new fields.NumberField({ integer: true, initial: 0 }), damageThresholds: new fields.SchemaField({ @@ -115,6 +117,7 @@ export default class DhCharacter extends BaseDataActor { magic: new fields.NumberField({ integer: true, initial: 0 }) }) }), + companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }), rules: new fields.SchemaField({ maxArmorMarked: new fields.SchemaField({ value: new fields.NumberField({ required: true, integer: true, initial: 1 }), @@ -154,10 +157,25 @@ export default class DhCharacter extends BaseDataActor { return this.parent.items.find(x => x.type === 'community') ?? null; } + get features() { + return this.parent.items.filter(x => x.type === 'feature') ?? []; + } + + get companionFeatures() { + return this.companion ? this.companion.items.filter(x => x.type === 'feature') : []; + } + get needsCharacterSetup() { return !this.class.value || !this.class.subclass; } + get spellcastingModifiers() { + return { + main: this.class.subclass?.system?.spellcastingTrait, + multiclass: this.multiclass.subclass?.system?.spellcastingTrait + }; + } + get domains() { const classDomains = this.class.value ? this.class.value.system.domains : []; const multiclassDomains = this.multiclass.value ? this.multiclass.value.system.domains : []; @@ -197,6 +215,12 @@ export default class DhCharacter extends BaseDataActor { : null; } + get deathMoveViable() { + return ( + this.resources.hitPoints.maxTotal > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.maxTotal + ); + } + static async unequipBeforeEquip(itemToEquip) { const primary = this.primaryWeapon, secondary = this.secondaryWeapon; @@ -307,58 +331,10 @@ export default class DhCharacter extends BaseDataActor { level: this.levelData.level.current }; } -} -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.TypedObjectField(new fields.StringField({ required: true })), - itemUuid: new fields.StringField({ required: true }) - }) - ) - }) - ) - }; - } - - get canLevelUp() { - return this.level.current < this.level.changed; + async _preDelete() { + if (this.companion) { + this.companion.updateLevel(1); + } } } diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs new file mode 100644 index 00000000..abc38e93 --- /dev/null +++ b/module/data/actor/companion.mjs @@ -0,0 +1,139 @@ +import BaseDataActor from './base.mjs'; +import DhLevelData from '../levelData.mjs'; +import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; +import ActionField from '../fields/actionField.mjs'; +import { adjustDice, adjustRange } from '../../helpers/utils.mjs'; + +export default class DhCompanion extends BaseDataActor { + static LOCALIZATION_PREFIXES = ['DAGGERHEART.Sheets.Companion']; + + static get metadata() { + return foundry.utils.mergeObject(super.metadata, { + label: 'TYPES.Actor.companion', + type: 'companion' + }); + } + + static defineSchema() { + const fields = foundry.data.fields; + + return { + partner: new ForeignDocumentUUIDField({ type: 'Actor' }), + resources: new fields.SchemaField({ + stress: new fields.SchemaField({ + value: new fields.NumberField({ initial: 0, integer: true }), + bonus: new fields.NumberField({ initial: 0, integer: true }), + max: new fields.NumberField({ initial: 3, integer: true }) + }), + hope: new fields.NumberField({ initial: 0, integer: true }) + }), + evasion: new fields.SchemaField({ + value: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }), + bonus: new fields.NumberField({ initial: 0, integer: true }) + }), + experiences: new fields.TypedObjectField( + new fields.SchemaField({ + name: new fields.StringField({}), + value: new fields.NumberField({ integer: true, initial: 0 }), + bonus: new fields.NumberField({ integer: true, initial: 0 }) + }), + { + initial: { + experience1: { value: 2 }, + experience2: { value: 2 } + } + } + ), + attack: new ActionField({ + initial: { + name: 'Attack', + _id: foundry.utils.randomID(), + systemPath: 'attack', + type: 'attack', + range: 'melee', + target: { + type: 'any', + amount: 1 + }, + roll: { + type: 'weapon', + bonus: 0 + }, + damage: { + parts: [ + { + multiplier: 'flat', + value: { + dice: 'd6', + multiplier: 'flat' + } + } + ] + } + } + }), + actions: new fields.ArrayField(new ActionField()), + levelData: new fields.EmbeddedDataField(DhLevelData) + }; + } + + get attackBonus() { + return this.attack.roll.bonus ?? 0; + } + + prepareBaseData() { + const partnerSpellcastingModifier = this.partner?.system?.spellcastingModifiers?.main; + const spellcastingModifier = this.partner?.system?.traits?.[partnerSpellcastingModifier]?.total; + this.attack.roll.bonus = spellcastingModifier ?? 0; // Needs to expand on which modifier it is that should be used because of multiclassing; + + for (let levelKey in this.levelData.levelups) { + const level = this.levelData.levelups[levelKey]; + for (let selection of level.selections) { + switch (selection.type) { + case 'hope': + this.resources.hope += selection.value; + break; + case 'vicious': + if (selection.data[0] === 'damage') { + this.attack.damage.parts[0].value.dice = adjustDice(this.attack.damage.parts[0].value.dice); + } else { + this.attack.range = adjustRange(this.attack.range); + } + break; + case 'stress': + this.resources.stress.bonus += selection.value; + break; + case 'evasion': + this.evasion.bonus += selection.value; + break; + case 'experience': + Object.keys(this.experiences).forEach(key => { + const experience = this.experiences[key]; + experience.bonus += selection.value; + }); + break; + } + } + } + } + + prepareDerivedData() { + for (var experienceKey in this.experiences) { + var experience = this.experiences[experienceKey]; + experience.total = experience.value + experience.bonus; + } + + if (this.partner) { + this.partner.system.resources.hope.max += this.resources.hope; + } + + this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus; + this.evasion.total = this.evasion.value + this.evasion.bonus; + } + + async _preDelete() { + if (this.partner) { + await this.partner.update({ 'system.companion': null }); + } + } +} diff --git a/module/data/actor/environment.mjs b/module/data/actor/environment.mjs index 3209dfdc..4b7e0716 100644 --- a/module/data/actor/environment.mjs +++ b/module/data/actor/environment.mjs @@ -1,6 +1,8 @@ import { environmentTypes } from '../../config/actorConfig.mjs'; import BaseDataActor from './base.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; +import ActionField from '../fields/actionField.mjs'; +import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; export default class DhEnvironment extends BaseDataActor { static LOCALIZATION_PREFIXES = ['DAGGERHEART.Sheets.Environment']; @@ -21,15 +23,17 @@ export default class DhEnvironment extends BaseDataActor { initial: SYSTEM.GENERAL.tiers.tier1.id }), type: new fields.StringField({ choices: environmentTypes }), - impulses: new fields.HTMLField(), + description: new fields.StringField(), + impulses: new fields.StringField(), 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' })) + adversaries: new ForeignDocumentUUIDArrayField({ type: 'Actor' }) }) - ) - /* Features pending datamodel rework */ + ), + actions: new fields.ArrayField(new ActionField()), + notes: new fields.HTMLField() }; } } diff --git a/module/data/chat-message/adversaryRoll.mjs b/module/data/chat-message/adversaryRoll.mjs index d97783c8..8a54af48 100644 --- a/module/data/chat-message/adversaryRoll.mjs +++ b/module/data/chat-message/adversaryRoll.mjs @@ -1,7 +1,9 @@ +import { DHBaseAction } from '../action/action.mjs'; + +const fields = foundry.data.fields; + export default class DHAdversaryRoll extends foundry.abstract.TypeDataModel { static defineSchema() { - const fields = foundry.data.fields; - return { title: new fields.StringField(), roll: new fields.DataField(), @@ -20,6 +22,7 @@ export default class DHAdversaryRoll extends foundry.abstract.TypeDataModel { }) }) ), + targetSelection: new fields.BooleanField({ initial: true }), hasDamage: new fields.BooleanField({ initial: false }), hasHealing: new fields.BooleanField({ initial: false }), hasEffect: new fields.BooleanField({ initial: false }), @@ -28,11 +31,20 @@ export default class DHAdversaryRoll extends foundry.abstract.TypeDataModel { actor: new fields.StringField(), item: new fields.StringField(), action: new fields.StringField() - }) + }), + damage: new fields.ObjectField() }; } get messageTemplate() { return 'systems/daggerheart/templates/chat/adversary-roll.hbs'; } + + prepareDerivedData() { + this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0; + this.currentTargets = + this.targetSelection !== true + ? Array.from(game.user.targets).map(t => DHBaseAction.formatTarget(t)) + : this.targets; + } } diff --git a/module/data/chat-message/damageRoll.mjs b/module/data/chat-message/damageRoll.mjs index 29ad6fbd..5e692d97 100644 --- a/module/data/chat-message/damageRoll.mjs +++ b/module/data/chat-message/damageRoll.mjs @@ -3,7 +3,7 @@ export default class DHDamageRoll extends foundry.abstract.TypeDataModel { const fields = foundry.data.fields; return { - messageType: new fields.StringField({initial: 'damage'}), + messageType: new fields.StringField({ initial: 'damage' }), title: new fields.StringField(), roll: new fields.DataField({}), targets: new fields.ArrayField( @@ -19,6 +19,7 @@ export default class DHDamageRoll extends foundry.abstract.TypeDataModel { }) }) ), + targetSelection: new fields.BooleanField({ initial: true }), hasSave: new fields.BooleanField({ initial: false }), onSave: new fields.StringField(), source: new fields.SchemaField({ @@ -27,11 +28,19 @@ export default class DHDamageRoll extends foundry.abstract.TypeDataModel { action: new fields.StringField(), message: new fields.StringField() }), - directDamage: new fields.BooleanField({initial: true}) + directDamage: new fields.BooleanField({ initial: true }) }; } get messageTemplate() { return `systems/daggerheart/templates/chat/${this.messageType}-roll.hbs`; } + + prepareDerivedData() { + this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0; + this.currentTargets = + this.targetSelection !== true + ? Array.from(game.user.targets).map(t => DHBaseAction.formatTarget(t)) + : this.targets; + } } diff --git a/module/data/chat-message/dualityRoll.mjs b/module/data/chat-message/dualityRoll.mjs index 1fdb537c..a6ffd85b 100644 --- a/module/data/chat-message/dualityRoll.mjs +++ b/module/data/chat-message/dualityRoll.mjs @@ -1,44 +1,6 @@ -const fields = foundry.data.fields; - -export default class DHDualityRoll extends foundry.abstract.TypeDataModel { - static dualityResult = { - hope: 1, - fear: 2, - critical: 3 - }; - - static defineSchema() { - return { - title: new fields.StringField(), - roll: new fields.DataField({}), - targets: new fields.ArrayField( - new fields.SchemaField({ - id: new fields.StringField({}), - actorId: new fields.StringField({}), - name: new fields.StringField({}), - img: new fields.StringField({}), - difficulty: new fields.NumberField({ integer: true, nullable: true }), - evasion: new fields.NumberField({ integer: true }), - hit: new fields.BooleanField({ initial: false }), - saved: new fields.SchemaField({ - result: new fields.NumberField(), - success: new fields.BooleanField({ nullable: true, initial: null }) - }) - }) - ), - costs: new fields.ArrayField(new fields.ObjectField()), - hasDamage: new fields.BooleanField({ initial: false }), - hasHealing: new fields.BooleanField({ initial: false }), - hasEffect: new fields.BooleanField({ initial: false }), - hasSave: new fields.BooleanField({ initial: false }), - source: new fields.SchemaField({ - actor: new fields.StringField(), - item: new fields.StringField(), - action: new fields.StringField() - }) - }; - } +import DHAdversaryRoll from './adversaryRoll.mjs'; +export default class DHDualityRoll extends DHAdversaryRoll { get messageTemplate() { return 'systems/daggerheart/templates/chat/duality-roll.hbs'; } diff --git a/module/data/chat-message/hitRoll.mjs b/module/data/chat-message/hitRoll.mjs new file mode 100644 index 00000000..e69de29b diff --git a/module/data/countdowns.mjs b/module/data/countdowns.mjs index e71cda55..a61a1aab 100644 --- a/module/data/countdowns.mjs +++ b/module/data/countdowns.mjs @@ -36,7 +36,8 @@ class DhCountdownData extends foundry.abstract.DataModel { }) }) ) - }) + }), + window: new fields.SchemaField({}) }; } diff --git a/module/data/fields/actionField.mjs b/module/data/fields/actionField.mjs index da520fd1..3628bbae 100644 --- a/module/data/fields/actionField.mjs +++ b/module/data/fields/actionField.mjs @@ -1,9 +1,9 @@ -import { actionsTypes } from '../action/_module.mjs'; +// import { actionsTypes } from '../action/_module.mjs'; // Temporary Solution export default class ActionField extends foundry.data.fields.ObjectField { getModel(value) { - return actionsTypes[value.type] ?? actionsTypes.attack; + return game.system.api.models.actionsTypes[value.type] ?? game.system.api.models.actionsTypes.attack; } /* -------------------------------------------- */ diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index ffd00a23..9c19fb80 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -10,7 +10,7 @@ export default class DHArmor extends BaseDataItem { type: 'armor', hasDescription: true, isQuantifiable: true, - isInventoryItem: true, + isInventoryItem: true }); } diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index 0df64a3e..4d41d731 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -1,4 +1,4 @@ -import { actionsTypes } from '../action/_module.mjs'; +// import { actionsTypes } from '../action/_module.mjs'; /** * Describes metadata about the item data model type @@ -20,7 +20,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { type: 'base', hasDescription: false, isQuantifiable: false, - isInventoryItem: false, + isInventoryItem: false }; } @@ -54,13 +54,14 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { const data = { ...actorRollData, item: { ...this } }; return data; } - + async _preCreate(data, options, user) { - if(!this.constructor.metadata.hasInitialAction || !foundry.utils.isEmpty(this.actions)) return; + if (!this.constructor.metadata.hasInitialAction || !foundry.utils.isEmpty(this.actions)) return; const actionType = { weapon: 'attack' }[this.constructor.metadata.type], - cls = actionsTypes.attack, + cls = game.system.api.models.actionsTypes[actionType], + // cls = actionsTypes.attack, action = new cls( { _id: foundry.utils.randomID(), @@ -72,6 +73,6 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { parent: this.parent } ); - this.updateSource({actions: [action]}); + this.updateSource({ actions: [action] }); } } diff --git a/module/data/item/consumable.mjs b/module/data/item/consumable.mjs index cb8a13b5..3e70f97a 100644 --- a/module/data/item/consumable.mjs +++ b/module/data/item/consumable.mjs @@ -9,7 +9,7 @@ export default class DHConsumable extends BaseDataItem { type: 'consumable', hasDescription: true, isQuantifiable: true, - isInventoryItem: true, + isInventoryItem: true }); } diff --git a/module/data/item/feature.mjs b/module/data/item/feature.mjs index 0e502eb6..85a9c42f 100644 --- a/module/data/item/feature.mjs +++ b/module/data/item/feature.mjs @@ -1,4 +1,3 @@ -import { getTier } from '../../helpers/utils.mjs'; import BaseDataItem from './base.mjs'; import ActionField from '../fields/actionField.mjs'; @@ -17,135 +16,7 @@ export default class DHFeature extends BaseDataItem { const fields = foundry.data.fields; return { ...super.defineSchema(), - - //A type of feature seems unnecessary - type: new fields.StringField({ choices: SYSTEM.ITEM.featureTypes }), - - //TODO: remove actionType field - actionType: new fields.StringField({ - choices: SYSTEM.ITEM.actionTypes, - initial: SYSTEM.ITEM.actionTypes.passive.id - }), - //TODO: remove featureType field - featureType: new fields.SchemaField({ - type: new fields.StringField({ - choices: SYSTEM.ITEM.valueTypes, - initial: Object.keys(SYSTEM.ITEM.valueTypes).find(x => x === 'normal') - }), - data: new fields.SchemaField({ - value: new fields.StringField({}), - property: new fields.StringField({ - choices: SYSTEM.ACTOR.featureProperties, - initial: Object.keys(SYSTEM.ACTOR.featureProperties).find(x => x === 'spellcastingTrait') - }), - max: new fields.NumberField({ initial: 1, integer: true }), - numbers: new fields.TypedObjectField( - new fields.SchemaField({ - value: new fields.NumberField({ integer: true }), - used: new fields.BooleanField({ initial: false }) - }) - ) - }) - }), - refreshData: new fields.SchemaField( - { - type: new fields.StringField({ choices: SYSTEM.GENERAL.refreshTypes }), - uses: new fields.NumberField({ initial: 1, integer: true }), - //TODO: remove refreshed field - refreshed: new fields.BooleanField({ initial: true }) - }, - { nullable: true, initial: null } - ), - //TODO: remove refreshed field - multiclass: new fields.NumberField({ initial: null, nullable: true, integer: true }), - disabled: new fields.BooleanField({ initial: false }), - - //TODO: re do it completely or just remove it - effects: new fields.TypedObjectField( - new fields.SchemaField({ - type: new fields.StringField({ choices: SYSTEM.EFFECTS.effectTypes }), - valueType: new fields.StringField({ choices: SYSTEM.EFFECTS.valueTypes }), - parseType: new fields.StringField({ choices: SYSTEM.EFFECTS.parseTypes }), - initiallySelected: new fields.BooleanField({ initial: true }), - options: new fields.ArrayField( - new fields.SchemaField({ - name: new fields.StringField({}), - value: new fields.StringField({}) - }), - { nullable: true, initial: null } - ), - dataField: new fields.StringField({}), - appliesOn: new fields.StringField( - { - choices: SYSTEM.EFFECTS.applyLocations - }, - { nullable: true, initial: null } - ), - applyLocationChoices: new fields.TypedObjectField(new fields.StringField({}), { - nullable: true, - initial: null - }), - valueData: new fields.SchemaField({ - value: new fields.StringField({}), - fromValue: new fields.StringField({ initial: null, nullable: true }), - type: new fields.StringField({ initial: null, nullable: true }), - hopeIncrease: new fields.StringField({ initial: null, nullable: true }) - }) - }) - ), actions: new fields.ArrayField(new ActionField()) }; } - - get multiclassTier() { - return getTier(this.multiclass); - } - - async refresh() { - if (this.refreshData) { - if (this.featureType.type === SYSTEM.ITEM.valueTypes.dice.id) { - const update = { 'system.refreshData.refreshed': true }; - Object.keys(this.featureType.data.numbers).forEach( - x => (update[`system.featureType.data.numbers.-=${x}`] = null) - ); - await this.parent.update(update); - } else { - await this.parent.update({ 'system.refreshData.refreshed': true }); - } - } - } - - get effectData() { - const effectValues = Object.values(this.effects); - const effectCategories = Object.keys(SYSTEM.EFFECTS.effectTypes).reduce((acc, effectType) => { - acc[effectType] = effectValues.reduce((acc, effect) => { - if (effect.type === effectType) { - acc.push({ ...effect, valueData: this.#parseValues(effect.parseType, effect.valueData) }); - } - - return acc; - }, []); - - return acc; - }, {}); - - return effectCategories; - } - - #parseValues(parseType, values) { - return Object.keys(values).reduce((acc, prop) => { - acc[prop] = this.#parseValue(parseType, values[prop]); - - return acc; - }, {}); - } - - #parseValue(parseType, value) { - switch (parseType) { - case SYSTEM.EFFECTS.parseTypes.number.id: - return Number.parseInt(value); - default: - return value; - } - } } diff --git a/module/data/item/miscellaneous.mjs b/module/data/item/miscellaneous.mjs index 529cf9a9..cad07f48 100644 --- a/module/data/item/miscellaneous.mjs +++ b/module/data/item/miscellaneous.mjs @@ -9,7 +9,7 @@ export default class DHMiscellaneous extends BaseDataItem { type: 'miscellaneous', hasDescription: true, isQuantifiable: true, - isInventoryItem: true, + isInventoryItem: true }); } diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index 005f08af..9154eb31 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -2,7 +2,7 @@ import BaseDataItem from './base.mjs'; import FormulaField from '../fields/formulaField.mjs'; import ActionField from '../fields/actionField.mjs'; import { weaponFeatures } from '../../config/itemConfig.mjs'; -import { actionsTypes } from '../action/_module.mjs'; +import { actionsTypes } from '../action/_module.mjs'; export default class DHWeapon extends BaseDataItem { /** @inheritDoc */ @@ -13,7 +13,7 @@ export default class DHWeapon extends BaseDataItem { hasDescription: true, isQuantifiable: true, isInventoryItem: true, - hasInitialAction: true, + hasInitialAction: true }); } diff --git a/module/data/levelData.mjs b/module/data/levelData.mjs new file mode 100644 index 00000000..2432a313 --- /dev/null +++ b/module/data/levelData.mjs @@ -0,0 +1,61 @@ +import { LevelOptionType } from './levelTier.mjs'; + +export default class DhLevelData 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 }), + bonuses: new fields.TypedObjectField(new fields.NumberField({ integer: true, nullable: false })) + }), + 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.TypedObjectField(new fields.StringField({ required: true })), + itemUuid: new fields.DocumentUUIDField({ required: true }), + featureIds: new fields.ArrayField(new fields.StringField()) + }) + ) + }) + ) + }; + } + + get actions() { + return Object.values(this.levelups).flatMap(level => level.selections.flatMap(s => s.actions)); + } + + get canLevelUp() { + return this.level.current < this.level.changed; + } +} diff --git a/module/data/levelTier.mjs b/module/data/levelTier.mjs index 6cf11252..d29ce09e 100644 --- a/module/data/levelTier.mjs +++ b/module/data/levelTier.mjs @@ -58,6 +58,58 @@ class DhLevelOption extends foundry.abstract.DataModel { } } +export const CompanionLevelOptionType = { + hope: { + id: 'hope', + label: 'Light In The Dark' + }, + creatureComfort: { + id: 'creatureComfort', + label: 'Creature Comfort', + features: [ + { + name: 'DAGGERHEART.LevelUp.Actions.CreatureComfort.Name', + img: 'icons/magic/life/heart-cross-purple-orange.webp', + description: 'DAGGERHEART.LevelUp.Actions.CreatureComfort.Description' + } + ] + }, + armored: { + id: 'armored', + label: 'Armored', + features: [ + { + name: 'DAGGERHEART.LevelUp.Actions.Armored.Name', + img: 'icons/equipment/shield/kite-wooden-oak-glow.webp', + description: 'DAGGERHEART.LevelUp.Actions.Armored.Description' + } + ] + }, + vicious: { + id: 'vicious', + label: 'Viscious' + }, + resilient: { + id: 'resilient', + label: 'Resilient' + }, + bonded: { + id: 'bonded', + label: 'Bonded', + features: [ + { + name: 'DAGGERHEART.LevelUp.Actions.Bonded.Name', + img: 'icons/magic/life/heart-red-blue.webp', + description: 'DAGGERHEART.LevelUp.Actions.Bonded.Description' + } + ] + }, + aware: { + id: 'aware', + label: 'Aware' + } +}; + export const LevelOptionType = { trait: { id: 'trait', @@ -106,7 +158,8 @@ export const LevelOptionType = { multiclass: { id: 'multiclass', label: 'Multiclass' - } + }, + ...CompanionLevelOptionType }; export const defaultLevelTiers = { @@ -338,3 +391,80 @@ export const defaultLevelTiers = { } } }; + +export const defaultCompanionTier = { + tiers: { + 2: { + tier: 2, + name: 'Companion Choices', + levels: { + start: 2, + end: 10 + }, + initialAchievements: {}, + availableOptions: 1, + domainCardByLevel: 0, + options: { + experience: { + label: 'DAGGERHEART.LevelUp.Options.intelligent', + checkboxSelections: 3, + minCost: 1, + type: LevelOptionType.experience.id, + value: 1, + amount: 1 + }, + hope: { + label: 'DAGGERHEART.LevelUp.Options.lightInTheDark', + checkboxSelections: 1, + minCost: 1, + type: CompanionLevelOptionType.hope.id, + value: 1 + }, + creatureComfort: { + label: 'DAGGERHEART.LevelUp.Options.creatureComfort', + checkboxSelections: 1, + minCost: 1, + type: CompanionLevelOptionType.creatureComfort.id, + value: 1 + }, + armored: { + label: 'DAGGERHEART.LevelUp.Options.armored', + checkboxSelections: 1, + minCost: 1, + type: CompanionLevelOptionType.armored.id, + value: 1 + }, + vicious: { + label: 'DAGGERHEART.LevelUp.Options.vicious', + checkboxSelections: 3, + minCost: 1, + type: CompanionLevelOptionType.vicious.id, + value: 1, + amount: 1 + }, + stress: { + label: 'DAGGERHEART.LevelUp.Options.resilient', + checkboxSelections: 3, + minCost: 1, + type: LevelOptionType.stress.id, + value: 1 + }, + bonded: { + label: 'DAGGERHEART.LevelUp.Options.bonded', + checkboxSelections: 1, + minCost: 1, + type: CompanionLevelOptionType.bonded.id, + value: 1 + }, + evasion: { + label: 'DAGGERHEART.LevelUp.Options.aware', + checkboxSelections: 3, + minCost: 1, + type: LevelOptionType.evasion.id, + value: 2, + amount: 1 + } + } + } + } +}; diff --git a/module/data/levelup.mjs b/module/data/levelup.mjs index a964d716..0f248f45 100644 --- a/module/data/levelup.mjs +++ b/module/data/levelup.mjs @@ -32,7 +32,7 @@ export class DhLevelup extends foundry.abstract.DataModel { return acc; }, {}); - levels[i] = DhLevelupLevel.initializeData(pcLevelData.levelups[i], tier.availableOptions, { + levels[i] = DhLevelupLevel.initializeData(pcLevelData.levelups[i], tier.maxSelections[i], { ...initialAchievements, experiences, domainCards @@ -46,7 +46,7 @@ export class DhLevelup extends foundry.abstract.DataModel { name: tier.name, belongingLevels: belongingLevels, options: Object.keys(tier.options).reduce((acc, key) => { - acc[key] = tier.options[key].toObject(); + acc[key] = tier.options[key].toObject?.() ?? tier.options[key]; return acc; }, {}) }; @@ -98,6 +98,7 @@ export class DhLevelup extends foundry.abstract.DataModel { case 'experience': case 'domainCard': case 'subclass': + case 'vicious': return checkbox.data.length === (checkbox.amount ?? 1); case 'multiclass': const classSelected = checkbox.data.length === 1; diff --git a/module/dialogs/d20RollDialog.mjs b/module/dialogs/d20RollDialog.mjs index 30c0c40e..b21e79df 100644 --- a/module/dialogs/d20RollDialog.mjs +++ b/module/dialogs/d20RollDialog.mjs @@ -9,7 +9,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.config.experiences = []; if (config.source?.action) { - this.item = config.data.parent.items.get(config.source.item); + console.log(config) + this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent; this.action = config.data.attack?._id == config.source.action ? config.data.attack @@ -50,15 +51,18 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio }; async _prepareContext(_options) { + console.log(this.config, this.roll) const context = await super._prepareContext(_options); context.hasRoll = !!this.config.roll; + context.roll = this.roll; + context.rollType = this.roll?.constructor.name; context.experiences = Object.keys(this.config.data.experiences).map(id => ({ id, ...this.config.data.experiences[id] })); context.selectedExperiences = this.config.experiences; - context.advantage = this.config.advantage; - /* context.diceOptions = this.diceOptions; */ + context.advantage = this.config.roll?.advantage; + context.diceOptions = SYSTEM.GENERAL.diceTypes; context.canRoll = true; context.isLite = this.config.roll?.lite; if (this.config.costs?.length) { @@ -71,7 +75,9 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio context.uses = this.action.calcUses(this.config.uses); context.canRoll = context.canRoll && this.action.hasUses(context.uses); } + context.extraFormula = this.config.extraFormula; context.formula = this.roll.constructFormula(this.config); + return context; } @@ -81,12 +87,18 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.config.costs = foundry.utils.mergeObject(this.config.costs, rest.costs); } if (this.config.uses) this.config.uses = foundry.utils.mergeObject(this.config.uses, rest.uses); + if(rest.roll?.dice) { + Object.entries(rest.roll.dice).forEach(([key, value]) => { + this.roll[key] = value; + }) + } + this.config.extraFormula = rest.extraFormula; this.render(); } static updateIsAdvantage(_, button) { const advantage = Number(button.dataset.advantage); - this.config.advantage = this.config.advantage === advantage ? 0 : advantage; + this.config.roll.advantage = this.config.roll.advantage === advantage ? 0 : advantage; this.render(); } @@ -96,7 +108,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio } else { this.config.experiences = [...this.config.experiences, button.dataset.key]; } */ - this.config.experiences = this.config.experiences.indexOf(button.dataset.key) > -1 ? this.config.experiences.filter(x => x !== button.dataset.key) : [...this.config.experiences, button.dataset.key]; + this.config.experiences = + this.config.experiences.indexOf(button.dataset.key) > -1 + ? this.config.experiences.filter(x => x !== button.dataset.key) + : [...this.config.experiences, button.dataset.key]; this.render(); } @@ -109,7 +124,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio if (!options.submitted) this.config = false; } - static async configure(roll, config = {}, options={}) { + static async configure(roll, config = {}, options = {}) { return new Promise(resolve => { const app = new this(roll, config, options); app.addEventListener('close', () => resolve(app.config), { once: true }); diff --git a/module/dialogs/damageDialog.mjs b/module/dialogs/damageDialog.mjs index d3c571a9..4882475d 100644 --- a/module/dialogs/damageDialog.mjs +++ b/module/dialogs/damageDialog.mjs @@ -1,7 +1,7 @@ const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; export default class DamageDialog extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(roll, config={}, options={}) { + constructor(roll, config = {}, options = {}) { super(options); this.roll = roll; @@ -37,24 +37,31 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application async _prepareContext(_options) { const context = await super._prepareContext(_options); context.title = this.config.title; - context.formula = this.config.roll.formula; + context.extraFormula = this.config.extraFormula; + context.formula = this.roll.constructFormula(this.config);; return context; } + static updateRollConfiguration(event, _, formData) { + const { ...rest } = foundry.utils.expandObject(formData.object); + this.config.extraFormula = rest.extraFormula; + this.render(); + } + static async submitRoll() { - await this.close({ submitted: true }); + await this.close({ submitted: true }); } /** @override */ - _onClose(options={}) { - if ( !options.submitted ) this.config = false; + _onClose(options = {}) { + if (!options.submitted) this.config = false; } - static async configure(roll, config={}) { + static async configure(roll, config = {}) { return new Promise(resolve => { const app = new this(roll, config); - app.addEventListener("close", () => resolve(app.config), { once: true }); + app.addEventListener('close', () => resolve(app.config), { once: true }); app.render({ force: true }); }); } -} \ No newline at end of file +} diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 6e475d7e..541b76d0 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -1,6 +1,8 @@ import DamageSelectionDialog from '../applications/damageSelectionDialog.mjs'; import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs'; import DamageReductionDialog from '../applications/damageReductionDialog.mjs'; +import { LevelOptionType } from '../data/levelTier.mjs'; +import DHFeature from '../data/item/feature.mjs'; export default class DhpActor extends Actor { async _preCreate(data, options, user) { @@ -8,7 +10,7 @@ export default class DhpActor extends Actor { // Configure prototype token settings const prototypeToken = {}; - if (this.type === 'character') + if (['character', 'companion'].includes(this.type)) Object.assign(prototypeToken, { sight: { enabled: true }, actorLink: true, @@ -18,7 +20,7 @@ export default class DhpActor extends Actor { } async updateLevel(newLevel) { - if (this.type !== 'character' || newLevel === this.system.levelData.level.changed) return; + if (!['character', 'companion'].includes(this.type) || newLevel === this.system.levelData.level.changed) return; if (newLevel > this.system.levelData.level.current) { const maxLevel = Object.values( @@ -41,6 +43,7 @@ export default class DhpActor extends Actor { return acc; }, {}); + const featureIds = []; const domainCards = []; const experiences = []; const subclassFeatureState = { class: null, multiclass: null }; @@ -53,6 +56,7 @@ export default class DhpActor extends Actor { const advancementCards = level.selections.filter(x => x.type === 'domainCard').map(x => x.itemUuid); domainCards.push(...achievementCards, ...advancementCards); experiences.push(...Object.keys(level.achievements.experiences)); + featureIds.push(...level.selections.flatMap(x => x.featureIds)); const subclass = level.selections.find(x => x.type === 'subclass'); if (subclass) { @@ -66,13 +70,21 @@ export default class DhpActor extends Actor { multiclass = level.selections.find(x => x.type === 'multiclass'); }); + for (let featureId of featureIds) { + this.items.get(featureId).delete(); + } + if (experiences.length > 0) { - this.update({ + const getUpdate = () => ({ 'system.experiences': experiences.reduce((acc, key) => { acc[`-=${key}`] = null; return acc; }, {}) }); + this.update(getUpdate()); + if (this.system.companion) { + this.system.companion.update(getUpdate()); + } } if (subclassFeatureState.class) { @@ -114,10 +126,15 @@ export default class DhpActor extends Actor { } } }); + + if (this.system.companion) { + this.system.companion.updateLevel(newLevel); + } } } async levelUp(levelupData) { + const actions = []; const levelups = {}; for (var levelKey of Object.keys(levelupData)) { const level = levelupData[levelKey]; @@ -126,13 +143,23 @@ export default class DhpActor extends Actor { const experience = level.achievements.experiences[experienceKey]; await this.update({ [`system.experiences.${experienceKey}`]: { - description: experience.name, + name: experience.name, value: experience.modifier } }); + + if (this.system.companion) { + await this.system.companion.update({ + [`system.experiences.${experienceKey}`]: { + name: '', + value: experience.modifier + } + }); + } } let multiclass = null; + const featureAdditions = []; const domainCards = []; const subclassFeatureState = { class: null, multiclass: null }; const selections = []; @@ -141,7 +168,18 @@ export default class DhpActor extends Actor { for (var checkboxNr of Object.keys(selection)) { const checkbox = selection[checkboxNr]; - if (checkbox.type === 'multiclass') { + const tierOption = LevelOptionType[checkbox.type]; + if (tierOption.features?.length > 0) { + featureAdditions.push({ + checkbox: { + ...checkbox, + level: Number(levelKey), + optionKey: optionKey, + checkboxNr: Number(checkboxNr) + }, + features: tierOption.features + }); + } else if (checkbox.type === 'multiclass') { multiclass = { ...checkbox, level: Number(levelKey), @@ -174,6 +212,28 @@ export default class DhpActor extends Actor { } } + for (var addition of featureAdditions) { + for (var featureData of addition.features) { + const feature = new DHFeature({ + ...featureData, + description: game.i18n.localize(featureData.description) + }); + const embeddedItem = await this.createEmbeddedDocuments('Item', [ + { + ...featureData, + name: game.i18n.localize(featureData.name), + type: 'feature', + system: feature + } + ]); + addition.checkbox.featureIds = !addition.checkbox.featureIds + ? [embeddedItem[0].id] + : [...addition.checkbox.featureIds, embeddedItem[0].id]; + } + + selections.push(addition.checkbox); + } + if (multiclass) { const subclassItem = await foundry.utils.fromUuid(multiclass.secondaryData.subclass); const subclassData = subclassItem.toObject(); @@ -238,6 +298,7 @@ export default class DhpActor extends Actor { await this.update({ system: { + actions: [...this.system.actions, ...actions], levelData: { level: { current: this.system.levelData.level.changed @@ -246,6 +307,10 @@ export default class DhpActor extends Actor { } } }); + + if (this.system.companion) { + this.system.companion.updateLevel(this.system.levelData.level.changed); + } } /** @@ -266,7 +331,7 @@ export default class DhpActor extends Actor { * @param {object} [config.costs] */ async diceRoll(config) { - config.source = {...(config.source ?? {}), actor: this.uuid}; + config.source = { ...(config.source ?? {}), actor: this.uuid }; config.data = this.getRollData(); const rollClass = config.roll.lite ? CONFIG.Dice.daggerheart['DHRoll'] : this.rollClass; return await rollClass.build(config); @@ -364,6 +429,11 @@ export default class DhpActor extends Actor { } async takeDamage(damage, type) { + if (this.type === 'companion') { + await this.modifyResource([{ value: 1, type: 'stress' }]); + return; + } + const hpDamage = damage >= this.system.damageThresholds.severe ? 3 @@ -414,6 +484,11 @@ export default class DhpActor extends Actor { let updates = { actor: { target: this, resources: {} }, armor: { target: this.system.armor, resources: {} } }; resources.forEach(r => { switch (r.type) { + case 'fear': + ui.resources.updateFear( + game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear) + r.value + ); + break; case 'armorStack': updates.armor.resources['system.marks.value'] = Math.max( Math.min(this.system.armor.system.marks.value + r.value, this.system.armorScore), @@ -422,14 +497,16 @@ export default class DhpActor extends Actor { break; default: updates.actor.resources[`system.resources.${r.type}.value`] = Math.max( - Math.min(this.system.resources[r.type].value + r.value, (this.system.resources[r.type].maxTotal ?? this.system.resources[r.type].max)), + Math.min( + this.system.resources[r.type].value + r.value, + this.system.resources[r.type].maxTotal ?? this.system.resources[r.type].max + ), 0 ); break; } }); Object.values(updates).forEach(async u => { - console.log(updates, u) if (Object.keys(u.resources).length > 0) { if (game.user.isGM) { await u.target.update(u.resources); diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 6158db7b..0671e7e3 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -55,8 +55,8 @@ export default class DHItem extends foundry.documents.Item { isInventoryItem === true ? 'Inventory Items' : isInventoryItem === false - ? 'Character Items' - : 'Other'; + ? 'Character Items' + : 'Other'; return { value: type, label, group }; } @@ -83,13 +83,14 @@ export default class DHItem extends foundry.documents.Item { { actions: this.system.actions } ), title = 'Select Action'; - + return foundry.applications.api.DialogV2.prompt({ window: { title }, content, ok: { label: title, - callback: (event, button, dialog) => this.system.actions.find(a => a._id === button.form.elements.actionId.value) + callback: (event, button, dialog) => + this.system.actions.find(a => a._id === button.form.elements.actionId.value) } }); } @@ -115,7 +116,9 @@ export default class DHItem extends foundry.documents.Item { this.type === 'ancestry' ? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.AncestryTitle') : this.type === 'community' - ? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.CommunityTitle') + ? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.CommunityTitle') + : this.type === 'feature' + ? game.i18n.localize('TYPES.Item.feature') : game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'), origin: origin, img: this.img, diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index d812f043..62248af6 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -1,4 +1,4 @@ -import { getDiceSoNicePresets } from '../config/generalConfig.mjs'; +import { diceTypes, getDiceSoNicePresets, range } from '../config/generalConfig.mjs'; import Tagify from '@yaireo/tagify'; export const loadCompendiumOptions = async compendiums => { @@ -124,13 +124,13 @@ export const getCommandTarget = () => { export const setDiceSoNiceForDualityRoll = (rollResult, advantageState) => { const diceSoNicePresets = getDiceSoNicePresets(); - rollResult.dice[0].options.appearance = diceSoNicePresets.hope; - rollResult.dice[1].options.appearance = diceSoNicePresets.fear; + rollResult.dice[0].options = { appearance: diceSoNicePresets.hope }; + rollResult.dice[1].options = { appearance: diceSoNicePresets.fear }; //diceSoNicePresets.fear; if (rollResult.dice[2]) { if (advantageState === true) { - rollResult.dice[2].options.appearance = diceSoNicePresets.advantage; + rollResult.dice[2].options = { appearance: diceSoNicePresets.advantage }; } else if (advantageState === false) { - rollResult.dice[2].options.appearance = diceSoNicePresets.disadvantage; + rollResult.dice[2].options = { appearance: diceSoNicePresets.disadvantage }; } } }; @@ -225,10 +225,10 @@ export const getDeleteKeys = (property, innerProperty, innerPropertyDefaultValue // Fix on Foundry native formula replacement for DH const nativeReplaceFormulaData = Roll.replaceFormulaData; -Roll.replaceFormulaData = function (formula, data={}, { missing, warn = false } = {}) { +Roll.replaceFormulaData = function (formula, data = {}, { missing, warn = false } = {}) { const terms = Object.keys(SYSTEM.GENERAL.multiplierTypes).map(type => { - return { term: type, default: 1} - }) + return { term: type, default: 1 }; + }); formula = terms.reduce((a, c) => a.replaceAll(`@${c.term}`, data[c.term] ?? c.default), formula); return nativeReplaceFormulaData(formula, data, { missing, warn }); }; @@ -258,3 +258,39 @@ export const damageKeyToNumber = key => { return 0; } }; + +export default function constructHTMLButton({ + label, + dataset = {}, + classes = [], + icon = '', + type = 'button', + disabled = false +}) { + const button = document.createElement('button'); + button.type = type; + + for (const [key, value] of Object.entries(dataset)) { + button.dataset[key] = value; + } + button.classList.add(...classes); + if (icon) icon = ` `; + if (disabled) button.disabled = true; + button.innerHTML = `${icon}${label}`; + + return button; +} + +export const adjustDice = (dice, decrease) => { + const diceKeys = Object.keys(diceTypes); + const index = diceKeys.indexOf(dice); + const newIndex = decrease ? Math.max(index - 1, 0) : Math.min(index + 1, diceKeys.length - 1); + return diceTypes[diceKeys[newIndex]]; +}; + +export const adjustRange = (rangeVal, decrease) => { + const rangeKeys = Object.keys(range); + const index = rangeKeys.indexOf(rangeVal); + const newIndex = decrease ? Math.max(index - 1, 0) : Math.min(index + 1, rangeKeys.length - 1); + return range[rangeKeys[newIndex]]; +}; diff --git a/module/ui/chatLog.mjs b/module/ui/chatLog.mjs index dfcc1b8e..14d99734 100644 --- a/module/ui/chatLog.mjs +++ b/module/ui/chatLog.mjs @@ -25,6 +25,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo html.querySelectorAll('.target-save-container').forEach(element => element.addEventListener('click', event => this.onRollSave(event, data.message)) ); + html.querySelectorAll('.roll-all-save-button').forEach(element => + element.addEventListener('click', event => this.onRollAllSave(event, data.message)) + ); html.querySelectorAll('.duality-action-effect').forEach(element => element.addEventListener('click', event => this.onApplyEffect(event, data.message)) ); @@ -33,6 +36,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo element.addEventListener('mouseleave', this.unhoverTarget); element.addEventListener('click', this.clickTarget); }); + html.querySelectorAll('.button-target-selection').forEach(element => { + element.addEventListener('click', event => this.onTargetSelection(event, data.message)); + }); html.querySelectorAll('.damage-button').forEach(element => element.addEventListener('click', event => this.onDamage(event, data.message)) ); @@ -107,7 +113,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo tokenId = event.target.closest('[data-token]')?.dataset.token, token = game.canvas.tokens.get(tokenId); if (!token?.actor || !token.isOwner) return true; - console.log(token.actor.canUserModify(game.user, 'update')); if (message.system.source.item && message.system.source.action) { const action = this.getAction(actor, message.system.source.item, message.system.source.action); if (!action || !action?.hasSave) return; @@ -115,6 +120,16 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo } }; + onRollAllSave = async (event, message) => { + event.stopPropagation(); + const targets = event.target.parentElement.querySelectorAll( + '.target-section > [data-token] .target-save-container' + ); + targets.forEach(el => { + el.dispatchEvent(new PointerEvent('click', { shiftKey: true })); + }); + }; + onApplyEffect = async (event, message) => { event.stopPropagation(); const actor = await this.getActor(message.system.source.actor); @@ -122,10 +137,38 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo if (message.system.source.item && message.system.source.action) { const action = this.getAction(actor, message.system.source.item, message.system.source.action); if (!action || !action?.applyEffects) return; - await action.applyEffects(event, message); + const { isHit, targets } = this.getTargetList(event, message); + if (targets.length === 0) + ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected')); + await action.applyEffects(event, message, targets); } }; + onTargetSelection = async (event, message) => { + event.stopPropagation(); + const targetSelection = Boolean(event.target.dataset.targetHit), + msg = ui.chat.collection.get(message._id); + if (msg.system.targetSelection === targetSelection) return; + if (targetSelection !== true && !Array.from(game.user.targets).length) + return ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected')); + msg.system.targetSelection = targetSelection; + msg.system.prepareDerivedData(); + ui.chat.updateMessage(msg); + }; + + getTargetList = (event, message) => { + const targetSelection = event.target + .closest('.message-content') + .querySelector('.button-target-selection.target-selected'), + isHit = Boolean(targetSelection.dataset.targetHit); + return { + isHit, + targets: isHit + ? message.system.targets.filter(t => t.hit === true).map(target => game.canvas.tokens.get(target.id)) + : Array.from(game.user.targets) + }; + }; + hoverTarget = event => { event.stopPropagation(); const token = canvas.tokens.get(event.currentTarget.dataset.token); @@ -150,18 +193,18 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo onDamage = async (event, message) => { event.stopPropagation(); - const targets = event.currentTarget.dataset.targetHit - ? message.system.targets.map(target => game.canvas.tokens.get(target.id)) - : Array.from(game.user.targets); + const { isHit, targets } = this.getTargetList(event, message); - if(message.system.onSave && event.currentTarget.dataset.targetHit) { - const pendingingSaves = message.system.targets.filter(target => target.hit && target.saved.success === null); - if(pendingingSaves.length) { + if (message.system.onSave && isHit) { + const pendingingSaves = message.system.targets.filter( + target => target.hit && target.saved.success === null + ); + if (pendingingSaves.length) { const confirm = await foundry.applications.api.DialogV2.confirm({ - window: {title: "Pending Reaction Rolls found"}, + window: { title: 'Pending Reaction Rolls found' }, content: `

Some Tokens still need to roll their Reaction Roll.

Are you sure you want to continue ?

Undone reaction rolls will be considered as failed

` }); - if ( !confirm ) return; + if (!confirm) return; } } @@ -169,8 +212,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected')); for (let target of targets) { let damage = message.system.roll.total; - if(message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true) damage = Math.ceil(damage * (SYSTEM.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1)); - + if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true) + damage = Math.ceil(damage * (SYSTEM.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1)); + await target.actor.takeDamage(damage, message.system.roll.type); } }; @@ -181,7 +225,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo if (targets.length === 0) ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected')); - + for (var target of targets) { await target.actor.takeHealing([{ value: message.system.roll.total, type: message.system.roll.type }]); } diff --git a/styles/chat.less b/styles/chat.less index 15ff39e7..f454bec7 100644 --- a/styles/chat.less +++ b/styles/chat.less @@ -11,6 +11,25 @@ fieldset.daggerheart.chat { border-left-width: 0; border-right-width: 0; border-bottom-width: 0; + legend { + display: flex; + align-items: center; + gap: 5px; + &:before, + &:after { + content: '\f0d8'; + font-family: 'Font Awesome 6 Pro'; + } + } + &.expanded { + legend:before, + legend:after { + content: '\f0d7'; + } + } + .daggerheart.chat { + margin-top: 5px; + } } .daggerheart.chat { @@ -209,6 +228,28 @@ fieldset.daggerheart.chat { margin: -@fullMargin 0; } + .target-selection { + display: flex; + justify-content: space-around; + input[type='radio'] { + display: none; + &:checked + label { + text-shadow: 0px 0px 4px #ce5937; + } + &:not(:checked) + label { + opacity: 0.75; + } + } + label { + cursor: pointer; + opacity: 0.75; + &.target-selected { + text-shadow: 0px 0px 4px #ce5937; + opacity: 1; + } + } + } + .target-section { margin-top: 5px; @@ -234,7 +275,8 @@ fieldset.daggerheart.chat { background: @miss; } - img, .target-save-container { + img, + .target-save-container { width: 22px; height: 22px; align-self: center; @@ -284,6 +326,10 @@ fieldset.daggerheart.chat { margin-top: 5px; } } + + &:not(.expanded) .dice-tooltip { + grid-template-rows: 0fr; + } } &.domain-card { @@ -337,6 +383,17 @@ fieldset.daggerheart.chat { width: 80px; } } + button { + &.inner-button { + --button-size: 1.25rem; + --input-height: 1.25rem; + padding: 0 0.25rem; + margin: 5px 1px -4px auto; + &.inner-button-right { + margin-left: auto; + } + } + } [data-use-perm='false'] { pointer-events: none; @@ -347,7 +404,7 @@ fieldset.daggerheart.chat { display: none; } &::after { - content: "??"; + content: '??'; } } } @@ -359,6 +416,12 @@ fieldset.daggerheart.chat { fieldset.daggerheart.chat { border-top-width: 0; display: contents; + legend { + &:before, + &:after { + display: none; + } + } } .message-header { color: var(--color-light-3); @@ -436,14 +499,16 @@ fieldset.daggerheart.chat { .dice { .dice-rolls { margin-bottom: 0; - li { - display: flex; - align-items: center; - justify-content: center; - position: relative; - background: unset; - line-height: unset; - font-weight: unset; + &.duality { + li { + display: flex; + align-items: center; + justify-content: center; + position: relative; + background: unset; + line-height: unset; + font-weight: unset; + } } } } @@ -457,6 +522,11 @@ fieldset.daggerheart.chat { } } } + .target-selection { + label { + color: var(--color-light-1); + } + } .target-section { margin: 4px 0; border: 2px solid; @@ -500,6 +570,31 @@ fieldset.daggerheart.chat { margin-right: -8px; } } + .duality-result { + display: flex; + color: var(--color-light-1); + text-shadow: 0 0 1px black; + font-weight: bold; + background: var(--color-dark-1); + padding: 4px; + border-color: black; + min-height: unset; + height: 26px; + flex: unset; + margin: 0; + margin-left: auto; + align-self: center; + border-radius: 6px; + } + } + } + button { + &.inner-button { + color: var(--color-light-1); + text-shadow: 0 0 1px black; + font-weight: bold; + background: var(--color-dark-1); + border-color: black; } } } diff --git a/styles/countdown.less b/styles/countdown.less index dd80acc4..336805a9 100644 --- a/styles/countdown.less +++ b/styles/countdown.less @@ -1,6 +1,6 @@ .theme-light { .daggerheart.dh-style.countdown { - &.minimized .minimized-view .mini-countdown-container { + .minimized-view .mini-countdown-container { background-image: url('../assets/parchments/dh-parchment-dark.png'); } } @@ -26,51 +26,38 @@ } } - &.minimized { - height: auto !important; - max-height: unset !important; - max-width: 740px !important; - width: auto !important; + .minimized-view { + display: flex; + gap: 8px; + flex-wrap: wrap; - .window-content { - display: flex; - padding: 4px 8px; - justify-content: center; - } - - .minimized-view { + .mini-countdown-container { + width: fit-content; display: flex; + align-items: center; gap: 8px; - flex-wrap: wrap; + border: 2px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + padding: 0 4px 0 0; + background-image: url('../assets/parchments/dh-parchment-light.png'); + color: light-dark(@beige, @dark); + cursor: pointer; - .mini-countdown-container { - width: fit-content; - display: flex; - align-items: center; - gap: 8px; - border: 2px solid light-dark(@dark-blue, @golden); - border-radius: 6px; - padding: 0 4px 0 0; - background-image: url('../assets/parchments/dh-parchment-light.png'); - color: light-dark(@beige, @dark); - cursor: pointer; + &.disabled { + cursor: initial; + } - &.disabled { - cursor: initial; - } + img { + width: 30px; + height: 30px; + border-radius: 6px 0 0 6px; + } - img { - width: 30px; - height: 30px; - border-radius: 6px 0 0 6px; - } + .mini-countdown-name { + white-space: nowrap; + } - .mini-countdown-name { - white-space: nowrap; - } - - .mini-countdown-value { - } + .mini-countdown-value { } } } diff --git a/styles/daggerheart.css b/styles/daggerheart.css index 7555ae44..763b7e7d 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -1406,6 +1406,23 @@ fieldset.daggerheart.chat { border-right-width: 0; border-bottom-width: 0; } +fieldset.daggerheart.chat legend { + display: flex; + align-items: center; + gap: 5px; +} +fieldset.daggerheart.chat legend:before, +fieldset.daggerheart.chat legend:after { + content: '\f0d8'; + font-family: 'Font Awesome 6 Pro'; +} +fieldset.daggerheart.chat.expanded legend:before, +fieldset.daggerheart.chat.expanded legend:after { + content: '\f0d7'; +} +fieldset.daggerheart.chat .daggerheart.chat { + margin-top: 5px; +} .daggerheart.chat.downtime { display: flex; flex-direction: column; @@ -1538,6 +1555,27 @@ fieldset.daggerheart.chat { font-variant: all-small-caps; margin: -8px 0; } +.daggerheart.chat.roll .target-selection { + display: flex; + justify-content: space-around; +} +.daggerheart.chat.roll .target-selection input[type='radio'] { + display: none; +} +.daggerheart.chat.roll .target-selection input[type='radio']:checked + label { + text-shadow: 0px 0px 4px #ce5937; +} +.daggerheart.chat.roll .target-selection input[type='radio']:not(:checked) + label { + opacity: 0.75; +} +.daggerheart.chat.roll .target-selection label { + cursor: pointer; + opacity: 0.75; +} +.daggerheart.chat.roll .target-selection label.target-selected { + text-shadow: 0px 0px 4px #ce5937; + opacity: 1; +} .daggerheart.chat.roll .target-section { margin-top: 5px; } @@ -1599,6 +1637,9 @@ fieldset.daggerheart.chat { .daggerheart.chat.roll .dice-result .duality-action { margin-top: 5px; } +.daggerheart.chat.roll:not(.expanded) .dice-tooltip { + grid-template-rows: 0fr; +} .daggerheart.chat.domain-card { display: flex; flex-direction: column; @@ -1642,6 +1683,15 @@ fieldset.daggerheart.chat { .daggerheart.chat.domain-card img { width: 80px; } +.daggerheart.chat button.inner-button { + --button-size: 1.25rem; + --input-height: 1.25rem; + padding: 0 0.25rem; + margin: 5px 1px -4px auto; +} +.daggerheart.chat button.inner-button.inner-button-right { + margin-left: auto; +} .daggerheart.chat [data-use-perm='false'] { pointer-events: none; border-color: transparent; @@ -1650,7 +1700,7 @@ fieldset.daggerheart.chat { display: none; } .daggerheart.chat [data-view-perm='false']::after { - content: "??"; + content: '??'; } .theme-colorful .chat-message.duality { border-color: black; @@ -1660,6 +1710,10 @@ fieldset.daggerheart.chat { border-top-width: 0; display: contents; } +.theme-colorful .chat-message.duality fieldset.daggerheart.chat legend:before, +.theme-colorful .chat-message.duality fieldset.daggerheart.chat legend:after { + display: none; +} .theme-colorful .chat-message.duality .message-header { color: var(--color-light-3); padding: 0 8px; @@ -1735,7 +1789,7 @@ fieldset.daggerheart.chat { .theme-colorful .chat-message.duality .message-content .dice-result .dice-tooltip .wrapper .tooltip-part .dice .dice-rolls { margin-bottom: 0; } -.theme-colorful .chat-message.duality .message-content .dice-result .dice-tooltip .wrapper .tooltip-part .dice .dice-rolls li { +.theme-colorful .chat-message.duality .message-content .dice-result .dice-tooltip .wrapper .tooltip-part .dice .dice-rolls.duality li { display: flex; align-items: center; justify-content: center; @@ -1751,6 +1805,9 @@ fieldset.daggerheart.chat { text-shadow: 0 0 1px black; font-size: var(--font-size-16); } +.theme-colorful .chat-message.duality .message-content .dice-result .target-selection label { + color: var(--color-light-1); +} .theme-colorful .chat-message.duality .message-content .dice-result .target-section { margin: 4px 0; border: 2px solid; @@ -1794,6 +1851,29 @@ fieldset.daggerheart.chat { border-radius: 6px 0 0 0; margin-right: -8px; } +.theme-colorful .chat-message.duality .message-content .dice-result .duality-result { + display: flex; + color: var(--color-light-1); + text-shadow: 0 0 1px black; + font-weight: bold; + background: var(--color-dark-1); + padding: 4px; + border-color: black; + min-height: unset; + height: 26px; + flex: unset; + margin: 0; + margin-left: auto; + align-self: center; + border-radius: 6px; +} +.theme-colorful .chat-message.duality button.inner-button { + color: var(--color-light-1); + text-shadow: 0 0 1px black; + font-weight: bold; + background: var(--color-dark-1); + border-color: black; +} .daggerheart.sheet.feature .editable { display: flex; flex-direction: column; @@ -2935,6 +3015,7 @@ div.daggerheart.views.multiclass { display: flex; flex-direction: column; gap: 8px; + margin-top: 8px; } .daggerheart.levelup .levelup-navigation-container { display: flex; @@ -3096,6 +3177,13 @@ div.daggerheart.views.multiclass { align-items: center; gap: 4px; } +.daggerheart.levelup .levelup-selections-container .levelup-radio-choices { + display: flex; + gap: 8px; +} +.daggerheart.levelup .levelup-selections-container .levelup-radio-choices label { + flex: 0; +} .daggerheart.levelup .levelup-summary-container .level-achievements-container, .daggerheart.levelup .levelup-summary-container .level-advancements-container { display: flex; @@ -3386,7 +3474,7 @@ div.daggerheart.views.multiclass { #resources:has(.fear-bar) { min-width: 200px; } -.theme-light .daggerheart.dh-style.countdown.minimized .minimized-view .mini-countdown-container { +.theme-light .daggerheart.dh-style.countdown .minimized-view .mini-countdown-container { background-image: url('../assets/parchments/dh-parchment-dark.png'); } .daggerheart.dh-style.countdown { @@ -3406,23 +3494,12 @@ div.daggerheart.views.multiclass { .daggerheart.dh-style.countdown fieldset legend a { text-shadow: none; } -.daggerheart.dh-style.countdown.minimized { - height: auto !important; - max-height: unset !important; - max-width: 740px !important; - width: auto !important; -} -.daggerheart.dh-style.countdown.minimized .window-content { - display: flex; - padding: 4px 8px; - justify-content: center; -} -.daggerheart.dh-style.countdown.minimized .minimized-view { +.daggerheart.dh-style.countdown .minimized-view { display: flex; gap: 8px; flex-wrap: wrap; } -.daggerheart.dh-style.countdown.minimized .minimized-view .mini-countdown-container { +.daggerheart.dh-style.countdown .minimized-view .mini-countdown-container { width: fit-content; display: flex; align-items: center; @@ -3434,15 +3511,15 @@ div.daggerheart.views.multiclass { color: light-dark(#efe6d8, #222); cursor: pointer; } -.daggerheart.dh-style.countdown.minimized .minimized-view .mini-countdown-container.disabled { +.daggerheart.dh-style.countdown .minimized-view .mini-countdown-container.disabled { cursor: initial; } -.daggerheart.dh-style.countdown.minimized .minimized-view .mini-countdown-container img { +.daggerheart.dh-style.countdown .minimized-view .mini-countdown-container img { width: 30px; height: 30px; border-radius: 6px 0 0 6px; } -.daggerheart.dh-style.countdown.minimized .minimized-view .mini-countdown-container .mini-countdown-name { +.daggerheart.dh-style.countdown .minimized-view .mini-countdown-container .mini-countdown-name { white-space: nowrap; } .daggerheart.dh-style.countdown .hidden { @@ -3783,8 +3860,6 @@ div.daggerheart.views.multiclass { } .application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .portrait { position: relative; - height: 235px; - width: 275px; border-bottom: 1px solid light-dark(#18162e, #f3c267); cursor: pointer; } @@ -3888,6 +3963,7 @@ div.daggerheart.views.multiclass { border: 1px solid light-dark(#18162e, #f3c267); border-radius: 6px; z-index: 1; + background: #18162e; } .application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .info-section .resources-section .status-bar .progress-bar::-webkit-progress-bar { border: none; @@ -3902,12 +3978,9 @@ div.daggerheart.views.multiclass { background: linear-gradient(15deg, #823b01 0%, #fc8e45 65%, #be0000 100%); border-radius: 6px; } -.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .info-section .resources-section .status-bar .progress-bar::-moz-progress-value, -.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .info-section .resources-section .status-bar .progress-bar::-moz-progress-bar { - border-radius: 6px; -} .application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .info-section .resources-section .status-bar .progress-bar::-moz-progress-bar { background: linear-gradient(15deg, #46140a 0%, #be0000 42%, #fcb045 100%); + border-radius: 6px; } .application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .info-section .resources-section .status-bar .progress-bar.stress-color::-moz-progress-bar { background: linear-gradient(15deg, #823b01 0%, #fc8e45 65%, #be0000 100%); @@ -4053,9 +4126,6 @@ div.daggerheart.views.multiclass { grid-row: 2; grid-column: 2; } -.application.sheet.daggerheart.actor.dh-style.character .window-content .old-sheet { - width: 500px; -} .application.sheet.daggerheart.actor.dh-style.character .tab.inventory .search-section { display: flex; gap: 10px; @@ -4215,6 +4285,718 @@ div.daggerheart.views.multiclass { scrollbar-width: thin; scrollbar-color: light-dark(#18162e, #f3c267) transparent; } +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-header-sheet { + padding: 0 15px; + padding-top: 36px; + width: 100%; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-header-sheet .name-row { + display: flex; + gap: 5px; + align-items: center; + justify-content: space-between; + padding: 0; + padding-top: 5px; + padding-bottom: 8px; + flex: 1; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-header-sheet .name-row input[type='text'] { + font-size: 32px; + height: 42px; + text-align: start; + border: 1px solid transparent; + outline: 2px solid transparent; + transition: all 0.3s ease; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-header-sheet .name-row input[type='text']:hover { + outline: 2px solid light-dark(#222, #f3c267); +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-header-sheet .tags { + display: flex; + gap: 10px; + padding-bottom: 16px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-header-sheet .tags .tag { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: 12px; + font: 'Montserrat', sans-serif; + background: light-dark(#22222215, #efe6d815); + border: 1px solid light-dark(#222, #efe6d8); + border-radius: 3px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-header-sheet .tags .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: 12px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-header-sheet .adversary-info { + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px 0; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-header-sheet .adversary-info .description, +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-header-sheet .adversary-info .motives-and-tatics { + font-family: 'Montserrat', sans-serif; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-header-sheet .adversary-navigation { + display: flex; + gap: 8px; + align-items: center; +} +.application.sheet.daggerheart.actor.dh-style.adversary .window-content { + display: grid; + grid-template-columns: 275px 1fr; + grid-template-rows: 283px 1fr; + gap: 15px 0; + height: 100%; + width: 100%; +} +.application.sheet.daggerheart.actor.dh-style.adversary .window-content .adversary-sidebar-sheet { + grid-row: 1 / span 2; + grid-column: 1; +} +.application.sheet.daggerheart.actor.dh-style.adversary .window-content .adversary-header-sheet { + grid-row: 1; + grid-column: 2; +} +.application.sheet.daggerheart.actor.dh-style.adversary .window-content .tab { + grid-row: 2; + grid-column: 2; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet { + width: 275px; + min-width: 275px; + border-right: 1px solid light-dark(#18162e, #f3c267); + background-image: url('../assets/parchments/dh-parchment-dark.png'); +} +.theme-light .application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet { + background: transparent; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .portrait { + position: relative; + border-bottom: 1px solid light-dark(#18162e, #f3c267); + cursor: pointer; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .portrait img { + height: 235px; + width: 275px; + object-fit: cover; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .portrait .death-roll-btn { + display: none; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .portrait.death-roll { + filter: grayscale(1); +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .portrait.death-roll .death-roll-btn { + display: flex; + position: absolute; + top: 30%; + right: 30%; + font-size: 6rem; + color: #efe6d8; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .portrait.death-roll .death-roll-btn:hover { + text-shadow: 0 0 8px #efe6d8; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .threshold-section { + position: relative; + display: flex; + gap: 10px; + background-color: light-dark(transparent, #18162e); + color: light-dark(#18162e, #f3c267); + padding: 5px 10px; + border: 1px solid light-dark(#18162e, #f3c267); + border-radius: 6px; + align-items: center; + width: fit-content; + height: 30px; + margin-top: 16px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .threshold-section h4 { + font-size: 14px; + font-weight: bold; + text-transform: uppercase; + color: light-dark(#18162e, #f3c267); +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .threshold-section h4.threshold-value { + color: light-dark(#222, #efe6d8); +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .threshold-section .threshold-legend { + position: absolute; + bottom: -21px; + color: light-dark(#f3c267, #18162e); + background-color: light-dark(#18162e, #f3c267); + padding: 3px; + justify-self: anchor-center; + border-radius: 0 0 3px 3px; + text-transform: capitalize; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .threshold-section .hope-value { + display: flex; + cursor: pointer; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section { + position: relative; + display: flex; + flex-direction: column; + top: -20px; + gap: 16px; + margin-bottom: -10px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section { + display: flex; + justify-content: space-evenly; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar { + position: relative; + width: 100px; + height: 40px; + justify-items: center; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .status-label { + position: relative; + top: 40px; + height: 22px; + width: 79px; + clip-path: path('M0 0H79L74 16.5L39 22L4 16.5L0 0Z'); + background: light-dark(#18162e, #f3c267); +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .status-label h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + color: light-dark(#efe6d8, #18162e); +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .status-value { + position: absolute; + display: flex; + padding: 0 6px; + font-size: 1.5rem; + align-items: center; + width: 100px; + height: 40px; + justify-content: center; + text-align: center; + z-index: 2; + color: #efe6d8; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .status-value input[type='number'] { + background: transparent; + font-size: 1.5rem; + width: 40px; + height: 30px; + text-align: center; + border: none; + outline: 2px solid transparent; + color: #efe6d8; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .status-value input[type='number'].bar-input { + padding: 0; + color: #efe6d8; + backdrop-filter: none; + background: transparent; + transition: all 0.3s ease; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .status-value input[type='number'].bar-input:hover, +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .status-value input[type='number'].bar-input:focus { + background: rgba(24, 22, 46, 0.33); + backdrop-filter: blur(9.5px); +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .status-value .bar-label { + width: 40px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .progress-bar { + position: absolute; + appearance: none; + width: 100px; + height: 40px; + border: 1px solid light-dark(#18162e, #f3c267); + border-radius: 6px; + z-index: 1; + background: #18162e; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .progress-bar::-webkit-progress-bar { + border: none; + background: #18162e; + border-radius: 6px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .progress-bar::-webkit-progress-value { + background: linear-gradient(15deg, #46140a 0%, #be0000 42%, #fcb045 100%); + border-radius: 6px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .progress-bar.stress-color::-webkit-progress-value { + background: linear-gradient(15deg, #823b01 0%, #fc8e45 65%, #be0000 100%); + border-radius: 6px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .progress-bar::-moz-progress-bar { + background: linear-gradient(15deg, #46140a 0%, #be0000 42%, #fcb045 100%); + border-radius: 6px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .resources-section .status-bar .progress-bar.stress-color::-moz-progress-bar { + background: linear-gradient(15deg, #823b01 0%, #fc8e45 65%, #be0000 100%); + border-radius: 6px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .status-section { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: center; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .status-section .status-number { + justify-items: center; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .status-section .status-number .status-value { + position: relative; + display: flex; + width: 50px; + height: 30px; + border: 1px solid light-dark(#18162e, #f3c267); + border-bottom: none; + border-radius: 6px 6px 0 0; + padding: 0 6px; + font-size: 1.2rem; + align-items: center; + justify-content: center; + background: light-dark(transparent, #18162e); + z-index: 2; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .status-section .status-number .status-value.armor-slots { + width: 80px; + height: 30px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .status-section .status-number .status-label { + padding: 2px 10px; + width: 100%; + border-radius: 3px; + background: light-dark(#18162e, #f3c267); +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .info-section .status-section .status-number .status-label h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + font-size: 12px; + color: light-dark(#efe6d8, #18162e); +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .items-sidebar-list { + display: flex; + flex-direction: column; + gap: 5px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .items-sidebar-list .inventory-item { + padding: 0 10px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .attack-section .title { + display: flex; + gap: 15px; + align-items: center; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .attack-section .title h3 { + font-size: 20px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .attack-section .items-list { + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .experience-section { + margin-bottom: 20px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .experience-section .title { + display: flex; + gap: 15px; + align-items: center; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .experience-section .title h3 { + font-size: 20px; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .experience-section .experience-list { + display: flex; + flex-direction: column; + gap: 5px; + width: 100%; + margin-top: 10px; + align-items: center; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .experience-section .experience-list .experience-row { + display: flex; + gap: 5px; + width: 250px; + align-items: center; + justify-content: space-between; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .experience-section .experience-list .experience-row .experience-name { + width: 180px; + text-align: start; + font-size: 14px; + font-family: 'Montserrat', sans-serif; + color: light-dark(#222, #efe6d8); +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .experience-section .experience-list .experience-value { + height: 25px; + width: 35px; + font-size: 14px; + font-family: 'Montserrat', sans-serif; + color: light-dark(#222, #efe6d8); + align-content: center; + text-align: center; + background: url(../assets/svg/experience-shield.svg) no-repeat; +} +.theme-light .application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .experience-section .experience-list .experience-value { + background: url('../assets/svg/experience-shield-light.svg') no-repeat; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .reaction-section { + display: flex; + padding: 0 10px; + margin-top: 20px; + width: 100%; +} +.application.sheet.daggerheart.actor.dh-style.adversary .adversary-sidebar-sheet .reaction-section button { + width: 100%; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet { + display: flex; + flex-direction: column; + justify-content: start; + text-align: center; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .profile { + width: 100%; + height: 235px; + object-fit: cover; + mask-image: linear-gradient(0deg, transparent 0%, black 10%); + cursor: pointer; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .item-container { + display: flex; + align-items: center; + position: relative; + top: -45px; + gap: 20px; + padding: 0 20px; + margin-bottom: -30px; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .item-container .item-info { + display: flex; + flex-direction: column; + gap: 8px; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .item-container .item-info .tags { + display: flex; + gap: 10px; + padding-bottom: 0; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .item-container .item-info .tags .tag { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: 12px; + font: 'Montserrat', sans-serif; + background: light-dark(#22222215, #efe6d815); + border: 1px solid light-dark(#222, #efe6d8); + border-radius: 3px; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .item-container .item-info .tags .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: 12px; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .item-container .status-number { + justify-items: center; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .item-container .status-number .status-value { + position: relative; + display: flex; + width: 50px; + height: 30px; + border: 1px solid light-dark(#18162e, #f3c267); + border-bottom: none; + border-radius: 6px 6px 0 0; + padding: 0 6px; + font-size: 1.2rem; + align-items: center; + justify-content: center; + background: light-dark(transparent, #18162e); + z-index: 2; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .item-container .status-number .status-value.armor-slots { + width: 80px; + height: 30px; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .item-container .status-number .status-label { + padding: 2px 10px; + width: 100%; + border-radius: 3px; + background: light-dark(#18162e, #f3c267); +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .item-container .status-number .status-label h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + font-size: 12px; + color: light-dark(#efe6d8, #18162e); +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .item-container .item-name input[type='text'] { + font-size: 32px; + height: 42px; + text-align: start; + transition: all 0.3s ease; + outline: 2px solid transparent; + border: 1px solid transparent; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .item-container .item-name input[type='text']:hover[type='text'], +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .item-container .item-name input[type='text']:focus[type='text'] { + box-shadow: none; + outline: 2px solid light-dark(#18162e, #f3c267); +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .environment-info { + display: flex; + flex-direction: column; + gap: 12px; + padding: 10px 20px; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .environment-info .description, +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .environment-info .impulses { + text-align: start; + font-family: 'Montserrat', sans-serif; +} +.application.sheet.daggerheart.actor.dh-style.environment .environment-header-sheet .environment-navigation { + display: flex; + gap: 20px; + align-items: center; + padding: 0 20px; +} +.theme-light .application.sheet.daggerheart.actor.dh-style.environment { + background: url('../assets/parchments/dh-parchment-light.png'); +} +.theme-dark .application.sheet.daggerheart.actor.dh-style.environment { + background-image: url('../assets/parchments/dh-parchment-dark.png'); +} +.application.sheet.daggerheart.actor.dh-style.environment .tab { + max-height: 300px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: light-dark(#18162e, #f3c267) transparent; +} +.application.daggerheart.dh-style.dialog .window-content .dialog-header { + width: 100%; + padding-bottom: 16px; +} +.application.daggerheart.dh-style.dialog .window-content .dialog-header h1 { + font-family: 'Cinzel', serif; + font-style: normal; + font-weight: 700; + font-size: 24px; + margin: 0; + text-align: center; + color: light-dark(#18162e, #f3c267); +} +.application.daggerheart.dh-style.dialog .tab.details.active, +.application.daggerheart.dh-style.dialog .tab.attack.active { + display: flex; + flex-direction: column; + gap: 16px; +} +.application.daggerheart.dh-style.dialog .tab .fieldsets-section { + display: flex; + gap: 16px; +} +.application.daggerheart.dh-style.dialog .tab.experiences .add-experience-btn { + width: 100%; + margin-bottom: 12px; +} +.application.daggerheart.dh-style.dialog .tab.experiences .experience-list { + display: flex; + flex-direction: column; + gap: 10px; +} +.application.daggerheart.dh-style.dialog .tab.experiences .experience-list .experience-item { + display: grid; + grid-template-columns: 3fr 1fr 30px; + align-items: center; + gap: 5px; +} +.application.daggerheart.dh-style.dialog .tab.experiences .experience-list .experience-item a { + text-align: center; +} +.application.daggerheart.dh-style.dialog .tab.actions { + max-height: 450px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: light-dark(#18162e, #f3c267) transparent; +} +.application.daggerheart.dh-style.dialog .tab.actions .add-action-btn { + width: 100%; + margin-bottom: 12px; +} +.application.daggerheart.dh-style.dialog .tab.actions .action-list { + display: flex; + flex-direction: column; + gap: 10px; +} +.application.daggerheart.dh-style.dialog .tab.actions .action-list .action-item { + display: grid; + grid-template-columns: 40px 1fr auto; + align-items: center; + gap: 5px; + border-radius: 3px; +} +.application.daggerheart.dh-style.dialog .tab.actions .action-list .action-item img { + height: 40px; + width: 40px; + object-fit: cover; +} +.application.daggerheart.dh-style.dialog .tab.actions .action-list .action-item .label { + font-family: 'Montserrat', sans-serif; +} +.application.daggerheart.dh-style.dialog .tab.actions .action-list .action-item .label .tags { + display: flex; + gap: 10px; +} +.application.daggerheart.dh-style.dialog .tab.actions .action-list .action-item .label .tags .tag { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: 12px; + background: light-dark(#22222215, #efe6d815); + border: 1px solid light-dark(#222, #efe6d8); + border-radius: 3px; +} +.application.daggerheart.dh-style.dialog .tab.actions .action-list .action-item .label .tags .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: 12px; +} +.application.daggerheart.dh-style.dialog .tab.actions .action-list .action-item .controls { + display: flex; + gap: 5px; +} +.application.daggerheart.dh-style.dialog .tab.actions .action-list .action-item .controls a { + text-align: center; +} +.application.daggerheart.dh-style.dialog.environment-settings .tab.actions { + max-height: 450px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: light-dark(#18162e, #f3c267) transparent; +} +.application.daggerheart.dh-style.dialog.environment-settings .tab.actions .add-action-btn { + width: 100%; + margin-bottom: 12px; +} +.application.daggerheart.dh-style.dialog.environment-settings .tab.actions .action-list { + display: flex; + flex-direction: column; + gap: 10px; +} +.application.daggerheart.dh-style.dialog.environment-settings .tab.actions .action-list .action-item { + display: grid; + grid-template-columns: 40px 1fr auto; + align-items: center; + gap: 5px; +} +.application.daggerheart.dh-style.dialog.environment-settings .tab.actions .action-list .action-item img { + height: 40px; + width: 40px; + object-fit: cover; + border-radius: 3px; +} +.application.daggerheart.dh-style.dialog.environment-settings .tab.actions .action-list .action-item .label { + font-family: 'Montserrat', sans-serif; +} +.application.daggerheart.dh-style.dialog.environment-settings .tab.actions .action-list .action-item .label .tags { + display: flex; + gap: 10px; +} +.application.daggerheart.dh-style.dialog.environment-settings .tab.actions .action-list .action-item .label .tags .tag { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: 12px; + background: light-dark(#22222215, #efe6d815); + border: 1px solid light-dark(#222, #efe6d8); + border-radius: 3px; +} +.application.daggerheart.dh-style.dialog.environment-settings .tab.actions .action-list .action-item .label .tags .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: 12px; +} +.application.daggerheart.dh-style.dialog.environment-settings .tab.actions .action-list .action-item .controls { + display: flex; + gap: 5px; +} +.application.daggerheart.dh-style.dialog.environment-settings .tab.actions .action-list .action-item .controls a { + text-align: center; +} +.application.daggerheart.dh-style.dialog .tab.adversaries { + max-height: 450px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: light-dark(#18162e, #f3c267) transparent; +} +.application.daggerheart.dh-style.dialog .tab.adversaries .add-action-btn { + width: 100%; + margin-bottom: 12px; +} +.application.daggerheart.dh-style.dialog .tab.adversaries .category-container { + display: flex; + flex-direction: column; + align-items: start; + gap: 8px; +} +.application.daggerheart.dh-style.dialog .tab.adversaries .category-container .category-name { + display: flex; + align-items: center; + gap: 10px; + width: 100%; +} +.application.daggerheart.dh-style.dialog .tab.adversaries .category-container .adversaries-container { + display: flex; + flex-direction: column; + gap: 6px; + width: 100%; +} +.application.daggerheart.dh-style.dialog .tab.adversaries .adversaries-dragger { + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + width: 100%; + height: 40px; + border: 1px dashed light-dark(#18162e50, #efe6d850); + border-radius: 3px; + color: light-dark(#18162e50, #efe6d850); + font-family: 'Montserrat', sans-serif; +} +.application.sheet.daggerheart.actor.dh-style.companion .profile { + height: 80px; + width: 80px; +} +.application.sheet.daggerheart.actor.dh-style.companion .temp-container { + position: relative; + top: 32px; +} .application.sheet.daggerheart.actor.dh-style.adversary .window-content { overflow: auto; } @@ -4450,11 +5232,43 @@ div.daggerheart.views.multiclass { padding: 0 15px; overflow-y: hidden; } -.application.sheet.dh-style { +.theme-light .application.dialog.dh-style { + background-image: url('../assets/parchments/dh-parchment-light.png'); + background-repeat: no-repeat; + background-position: center; +} +.theme-dark .application.dialog.dh-style { + background-image: url('../assets/parchments/dh-parchment-dark.png'); + background-repeat: no-repeat; + background-position: center; +} +.application.dialog.dh-style { + border: none; +} +.application.dialog.dh-style .window-header { + background: transparent; + border-bottom: none; + color: light-dark(#18162e, #efe6d8); +} +.application.dialog.dh-style .window-header h1 { + color: light-dark(#18162e, #efe6d8); + font-family: 'Montserrat', sans-serif; +} +.application.dialog.dh-style .window-header button { + color: light-dark(#18162e, #efe6d8); + background: light-dark(transparent, #0e0d15); + border: 1px solid light-dark(#18162e, transparent); + padding: 0; +} +.application.dialog.dh-style .window-header button:hover { + border: 1px solid light-dark(#18162e, #f3c267); + color: light-dark(#18162e, #f3c267); +} +.application.dh-style { border: 1px solid light-dark(#18162e, #f3c267); } -.application.sheet.dh-style input[type='text'], -.application.sheet.dh-style input[type='number'] { +.application.dh-style input[type='text'], +.application.dh-style input[type='number'] { background: light-dark(transparent, transparent); border-radius: 6px; box-shadow: 0 4px 30px rgba(0, 0, 0, 0.05); @@ -4464,170 +5278,191 @@ div.daggerheart.views.multiclass { color: light-dark(#18162e, #f3c267); border: 1px solid light-dark(#222, #efe6d8); } -.application.sheet.dh-style input[type='text']:hover[type='text'], -.application.sheet.dh-style input[type='number']:hover[type='text'], -.application.sheet.dh-style input[type='text']:hover[type='number'], -.application.sheet.dh-style input[type='number']:hover[type='number'], -.application.sheet.dh-style input[type='text']:focus[type='text'], -.application.sheet.dh-style input[type='number']:focus[type='text'], -.application.sheet.dh-style input[type='text']:focus[type='number'], -.application.sheet.dh-style input[type='number']:focus[type='number'] { +.application.dh-style input[type='text']:hover[type='text'], +.application.dh-style input[type='number']:hover[type='text'], +.application.dh-style input[type='text']:hover[type='number'], +.application.dh-style input[type='number']:hover[type='number'], +.application.dh-style input[type='text']:focus[type='text'], +.application.dh-style input[type='number']:focus[type='text'], +.application.dh-style input[type='text']:focus[type='number'], +.application.dh-style input[type='number']:focus[type='number'] { background: light-dark(rgba(0, 0, 0, 0.05), rgba(24, 22, 46, 0.33)); box-shadow: none; outline: 2px solid light-dark(#222, #efe6d8); } -.application.sheet.dh-style input[type='checkbox']:checked::after { +.application.dh-style input[type='checkbox']:checked::after { color: light-dark(#222, #f3c267); } -.application.sheet.dh-style input[type='checkbox']:checked::before { +.application.dh-style input[type='checkbox']:checked::before { color: light-dark(transparent, #18162e); } -.application.sheet.dh-style input[type='checkbox']::before { +.application.dh-style input[type='checkbox']::before { color: light-dark(#222, #efe6d8); } -.application.sheet.dh-style button { +.application.dh-style button { background: light-dark(transparent, #f3c267); border: 1px solid light-dark(#18162e, #18162e); color: light-dark(#18162e, #18162e); outline: none; box-shadow: none; } -.application.sheet.dh-style button:hover { +.application.dh-style button:hover { background: light-dark(rgba(0, 0, 0, 0.3), #18162e); color: light-dark(#18162e, #f3c267); } -.application.sheet.dh-style button.glow { +.application.dh-style button.glow { animation: glow 0.75s infinite alternate; } -.application.sheet.dh-style select { +.application.dh-style select { background: light-dark(transparent, transparent); color: light-dark(#222, #efe6d8); font-family: 'Montserrat', sans-serif; outline: 2px solid transparent; border: 1px solid light-dark(#222, #efe6d8); } -.application.sheet.dh-style select:focus, -.application.sheet.dh-style select:hover { +.application.dh-style select:focus, +.application.dh-style select:hover { outline: 2px solid light-dark(#222, #efe6d8); box-shadow: none; } -.application.sheet.dh-style select option { +.application.dh-style select option { color: #efe6d8; background-color: #18162e; border-radius: 6px; } -.application.sheet.dh-style p { +.application.dh-style p { margin: 0; } -.application.sheet.dh-style ul { +.application.dh-style ul { margin: 0; padding: 0; list-style: none; } -.application.sheet.dh-style li { +.application.dh-style li { margin: 0; } -.application.sheet.dh-style a:hover, -.application.sheet.dh-style a.active { +.application.dh-style a:hover, +.application.dh-style a.active { + font-weight: bold; text-shadow: 0 0 8px light-dark(#18162e, #f3c267); } -.application.sheet.dh-style fieldset { +.application.dh-style fieldset { align-items: center; margin-top: 5px; border-radius: 6px; border-color: light-dark(#18162e, #f3c267); } -.application.sheet.dh-style fieldset.glassy { +.application.dh-style fieldset.glassy { background-color: light-dark(#18162e10, #f3c26710); border-color: transparent; } -.application.sheet.dh-style fieldset.glassy legend { +.application.dh-style fieldset.glassy legend { padding: 2px 12px; border-radius: 3px; background-color: light-dark(#18162e, #f3c267); color: light-dark(#efe6d8, #18162e); } -.application.sheet.dh-style fieldset.flex { +.application.dh-style fieldset.fit-height { + height: 95%; +} +.application.dh-style fieldset.flex { display: flex; gap: 20px; } -.application.sheet.dh-style fieldset.one-column { +.application.dh-style fieldset.flex.wrap { + flex-wrap: wrap; + gap: 10px 20px; +} +.application.dh-style fieldset.flex .inline-child { + flex: 1; +} +.application.dh-style fieldset.flex .checkbox { + display: flex; + align-items: center; + gap: 20px; +} +.application.dh-style fieldset.one-column { display: flex; flex-direction: column; align-items: start; gap: 10px; min-height: 64px; } -.application.sheet.dh-style fieldset.two-columns { +.application.dh-style fieldset.two-columns { display: grid; grid-template-columns: 1fr 2fr; gap: 10px; } -.application.sheet.dh-style fieldset.two-columns.even { +.application.dh-style fieldset.two-columns.even { grid-template-columns: 1fr 1fr; } -.application.sheet.dh-style fieldset.two-columns .full-width { +.application.dh-style fieldset.two-columns .full-width { grid-column: span 2; } -.application.sheet.dh-style fieldset legend { +.application.dh-style fieldset legend { font-family: 'Montserrat', sans-serif; font-weight: bold; color: light-dark(#18162e, #f3c267); } -.application.sheet.dh-style fieldset input[type='text'], -.application.sheet.dh-style fieldset input[type='number'] { +.application.dh-style fieldset input[type='text'], +.application.dh-style fieldset input[type='number'] { color: light-dark(#222, #efe6d8); font-family: 'Montserrat', sans-serif; transition: all 0.3s ease; outline: 2px solid transparent; } -.application.sheet.dh-style fieldset input[type='text']:focus, -.application.sheet.dh-style fieldset input[type='number']:focus, -.application.sheet.dh-style fieldset input[type='text']:hover, -.application.sheet.dh-style fieldset input[type='number']:hover { +.application.dh-style fieldset input[type='text']:focus, +.application.dh-style fieldset input[type='number']:focus, +.application.dh-style fieldset input[type='text']:hover, +.application.dh-style fieldset input[type='number']:hover { outline: 2px solid light-dark(#222, #efe6d8); } -.application.sheet.dh-style fieldset .nest-inputs { +.application.dh-style fieldset .nest-inputs { display: flex; align-items: center; + width: 100%; gap: 5px; } -.application.sheet.dh-style fieldset .form-group label { +.application.dh-style fieldset .form-group { + width: 100%; +} +.application.dh-style fieldset .form-group label { font-family: 'Montserrat', sans-serif; font-weight: bold; font-size: smaller; } -.application.sheet.dh-style .two-columns { +.application.dh-style .two-columns { display: grid; grid-template-columns: 1fr 2fr; gap: 10px; } -.application.sheet.dh-style .two-columns.even { +.application.dh-style .two-columns.even { grid-template-columns: 1fr 1fr; } -.application.sheet.dh-style line-div { +.application.dh-style line-div { display: block; height: 1px; width: 100%; border-bottom: 1px solid light-dark(#18162e, #f3c267); mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); } -.application.sheet.dh-style side-line-div { +.application.dh-style side-line-div { display: block; height: 1px; width: 100%; border-bottom: 1px solid light-dark(#18162e, #f3c267); mask-image: linear-gradient(270deg, transparent 0%, black 100%); } -.application.sheet.dh-style side-line-div.invert { +.application.dh-style side-line-div.invert { mask-image: linear-gradient(270deg, black 0%, transparent 100%); } -.application.sheet.dh-style .item-description { +.application.dh-style .item-description { opacity: 1; transform: translateY(0); + grid-column: 1/-1; transition: opacity 0.3s ease-out, transform 0.3s ease-out; } -.application.sheet.dh-style .item-description.invisible { +.application.dh-style .item-description.invisible { height: 0; opacity: 0; overflow: hidden; @@ -4813,14 +5648,15 @@ div.daggerheart.views.multiclass { position: relative; right: 2px; } -.sheet.daggerheart.dh-style .tab-navigation { +.daggerheart.dh-style .tab-navigation { margin: 5px 0; height: 40px; + width: 100%; } -.sheet.daggerheart.dh-style .tab-navigation .feature-tab { +.daggerheart.dh-style .tab-navigation .feature-tab { border: none; } -.sheet.daggerheart.dh-style .tab-navigation .feature-tab a { +.daggerheart.dh-style .tab-navigation .feature-tab a { color: light-dark(#18162e, #f3c267); font-family: 'Montserrat', sans-serif; } @@ -4849,6 +5685,12 @@ div.daggerheart.views.multiclass { grid-template-columns: 1fr 4fr 1fr; cursor: pointer; } +.sheet.daggerheart.dh-style .tab.actions .actions-list .action-item img { + height: 40px; + width: 40px; + object-fit: cover; + border-radius: 3px; +} .sheet.daggerheart.dh-style .tab.actions .actions-list .action-item h4 { font-family: 'Montserrat', sans-serif; font-weight: lighter; @@ -5070,13 +5912,13 @@ div.daggerheart.views.multiclass { .sheet.daggerheart.dh-style.item .tab.features .feature-list .feature-item .feature-line .controls a { text-shadow: none; } -.application.sheet.daggerheart.actor.dh-style.character .inventory-item { +.application.daggerheart.dh-style .inventory-item { display: grid; grid-template-columns: 40px 1fr 60px; gap: 10px; width: 100%; } -.application.sheet.daggerheart.actor.dh-style.character .inventory-item .item-img { +.application.daggerheart.dh-style .inventory-item .item-img { height: 40px; width: 40px; border-radius: 3px; @@ -5084,20 +5926,23 @@ div.daggerheart.views.multiclass { cursor: pointer; object-fit: cover; } -.application.sheet.daggerheart.actor.dh-style.character .inventory-item .item-label { +.application.daggerheart.dh-style .inventory-item .item-img.actor-img { + border-radius: 50%; +} +.application.daggerheart.dh-style .inventory-item .item-label { font-family: 'Montserrat', sans-serif; align-self: center; } -.application.sheet.daggerheart.actor.dh-style.character .inventory-item .item-label .item-name { +.application.daggerheart.dh-style .inventory-item .item-label .item-name { font-size: 14px; } -.application.sheet.daggerheart.actor.dh-style.character .inventory-item .item-label .item-tags, -.application.sheet.daggerheart.actor.dh-style.character .inventory-item .item-label .item-labels { +.application.daggerheart.dh-style .inventory-item .item-label .item-tags, +.application.daggerheart.dh-style .inventory-item .item-label .item-labels { display: flex; gap: 10px; } -.application.sheet.daggerheart.actor.dh-style.character .inventory-item .item-label .item-tags .tag, -.application.sheet.daggerheart.actor.dh-style.character .inventory-item .item-label .item-labels .tag { +.application.daggerheart.dh-style .inventory-item .item-label .item-tags .tag, +.application.daggerheart.dh-style .inventory-item .item-label .item-labels .tag { display: flex; flex-direction: row; justify-content: center; @@ -5108,27 +5953,27 @@ div.daggerheart.views.multiclass { border: 1px solid light-dark(#222, #efe6d8); border-radius: 3px; } -.application.sheet.daggerheart.actor.dh-style.character .inventory-item .item-label .item-tags .label, -.application.sheet.daggerheart.actor.dh-style.character .inventory-item .item-label .item-labels .label { +.application.daggerheart.dh-style .inventory-item .item-label .item-tags .label, +.application.daggerheart.dh-style .inventory-item .item-label .item-labels .label { display: flex; flex-direction: row; justify-content: center; align-items: center; font-size: 12px; } -.application.sheet.daggerheart.actor.dh-style.character .inventory-item .controls { +.application.daggerheart.dh-style .inventory-item .controls { display: flex; align-items: center; justify-content: end; gap: 8px; } -.application.sheet.daggerheart.actor.dh-style.character .inventory-item .controls a { +.application.daggerheart.dh-style .inventory-item .controls a { text-align: center; } -.application.sheet.daggerheart.actor.dh-style.character .inventory-item .controls a.unequipped { +.application.daggerheart.dh-style .inventory-item .controls a.unequipped { opacity: 0.4; } -.application.sheet.daggerheart.actor.dh-style.character .card-item { +.application.daggerheart.dh-style .card-item { position: relative; height: 120px; width: 100px; @@ -5136,21 +5981,21 @@ div.daggerheart.views.multiclass { border-radius: 6px; cursor: pointer; } -.application.sheet.daggerheart.actor.dh-style.character .card-item:hover .card-label { +.application.daggerheart.dh-style .card-item:hover .card-label { padding-top: 15px; } -.application.sheet.daggerheart.actor.dh-style.character .card-item:hover .card-label .controls { +.application.daggerheart.dh-style .card-item:hover .card-label .controls { opacity: 1; visibility: visible; transition: all 0.3s ease; max-height: 16px; } -.application.sheet.daggerheart.actor.dh-style.character .card-item .card-img { +.application.daggerheart.dh-style .card-item .card-img { height: 100%; width: 100%; object-fit: cover; } -.application.sheet.daggerheart.actor.dh-style.character .card-item .card-label { +.application.daggerheart.dh-style .card-item .card-label { display: flex; flex-direction: column; height: fit-content; @@ -5164,7 +6009,7 @@ div.daggerheart.views.multiclass { bottom: 0; mask-image: linear-gradient(180deg, transparent 0%, black 20%); } -.application.sheet.daggerheart.actor.dh-style.character .card-item .card-label .card-name { +.application.daggerheart.dh-style .card-item .card-label .card-name { font-family: 'Montserrat', sans-serif; font-style: normal; font-weight: 400; @@ -5172,7 +6017,7 @@ div.daggerheart.views.multiclass { line-height: 15px; color: #efe6d8; } -.application.sheet.daggerheart.actor.dh-style.character .card-item .card-label .controls { +.application.daggerheart.dh-style .card-item .card-label .controls { display: flex; gap: 15px; align-items: center; @@ -5182,18 +6027,37 @@ div.daggerheart.views.multiclass { transition: all 0.3s ease; color: #efe6d8; } -.application.sheet.daggerheart.actor.dh-style.character .items-list { +.application.daggerheart.dh-style .items-list { display: flex; flex-direction: column; gap: 10px; align-items: center; } -.application.sheet.daggerheart.actor.dh-style.character .card-list { +.application.daggerheart.dh-style .card-list { display: flex; flex-direction: row; gap: 10px; align-items: center; } +.application prose-mirror { + height: 100% !important; +} +.application prose-mirror .editor-menu { + background-color: transparent; +} +.application prose-mirror .editor-content { + scrollbar-width: thin; + scrollbar-color: light-dark(#18162e, #f3c267) transparent; +} +.application prose-mirror .editor-content h1 { + font-size: 36px; +} +.application prose-mirror .editor-content h2 { + font-size: 32px; +} +.application prose-mirror .editor-content h3 { + font-size: 24px; +} .filter-menu { width: auto; } diff --git a/styles/daggerheart.less b/styles/daggerheart.less index 891dc5e1..8903dcf0 100755 --- a/styles/daggerheart.less +++ b/styles/daggerheart.less @@ -25,6 +25,23 @@ @import './less/actors/character/biography.less'; @import './less/actors/character/features.less'; +@import './less/actors/adversary/header.less'; +@import './less/actors/adversary/sheet.less'; +@import './less/actors/adversary/sidebar.less'; + +@import './less/actors/environment/header.less'; +@import './less/actors/environment/sheet.less'; + +@import './less/applications/header.less'; +@import './less/applications/adversary-settings/sheet.less'; +@import './less/applications/adversary-settings/experiences.less'; +@import './less/applications/adversary-settings/actions.less'; + +@import './less/applications/environment-settings/actions.less'; +@import './less/applications/environment-settings/adversaries.less'; + +@import './less/actors/companion/sheet.less'; + @import './less/actors/adversary.less'; @import './less/actors/environment.less'; @@ -36,6 +53,7 @@ @import './less/utils/fonts.less'; @import './less/global/sheet.less'; +@import './less/global/dialog.less'; @import './less/global/elements.less'; @import './less/global/tab-navigation.less'; @import './less/global/tab-form-footer.less'; @@ -45,6 +63,7 @@ @import './less/global/feature-section.less'; @import './less/global/inventory-item.less'; @import './less/global/inventory-fieldset-items.less'; +@import './less/global/prose-mirror.less'; @import "./less/global/filter-menu.less"; @import '../node_modules/@yaireo/tagify/dist/tagify.css'; diff --git a/styles/less/actors/adversary/actions.less b/styles/less/actors/adversary/actions.less new file mode 100644 index 00000000..97bb45e7 --- /dev/null +++ b/styles/less/actors/adversary/actions.less @@ -0,0 +1,20 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.adversary { + .tab.actions { + .action-section { + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: auto; + mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); + padding: 20px 0; + padding-top: 10px; + height: 95%; + + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + } + } +} diff --git a/styles/less/actors/adversary/header.less b/styles/less/actors/adversary/header.less new file mode 100644 index 00000000..4b962466 --- /dev/null +++ b/styles/less/actors/adversary/header.less @@ -0,0 +1,80 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.adversary { + .adversary-header-sheet { + padding: 0 15px; + padding-top: 36px; + width: 100%; + + .name-row { + display: flex; + gap: 5px; + align-items: center; + justify-content: space-between; + padding: 0; + padding-top: 5px; + padding-bottom: 8px; + flex: 1; + + input[type='text'] { + font-size: 32px; + height: 42px; + text-align: start; + border: 1px solid transparent; + outline: 2px solid transparent; + transition: all 0.3s ease; + + &:hover { + outline: 2px solid light-dark(@dark, @golden); + } + } + } + + .tags { + display: flex; + gap: 10px; + padding-bottom: 16px; + + .tag { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: 12px; + font: @font-body; + + background: light-dark(@dark-15, @beige-15); + border: 1px solid light-dark(@dark, @beige); + border-radius: 3px; + } + + .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: 12px; + } + } + + .adversary-info { + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px 0; + + .description, + .motives-and-tatics { + font-family: @font-body; + } + } + + .adversary-navigation { + display: flex; + gap: 8px; + align-items: center; + } + } +} diff --git a/styles/less/actors/adversary/sheet.less b/styles/less/actors/adversary/sheet.less new file mode 100644 index 00000000..87bb9652 --- /dev/null +++ b/styles/less/actors/adversary/sheet.less @@ -0,0 +1,28 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.adversary { + .window-content { + display: grid; + grid-template-columns: 275px 1fr; + grid-template-rows: 283px 1fr; + gap: 15px 0; + height: 100%; + width: 100%; + + .adversary-sidebar-sheet { + grid-row: 1 / span 2; + grid-column: 1; + } + + .adversary-header-sheet { + grid-row: 1; + grid-column: 2; + } + + .tab { + grid-row: 2; + grid-column: 2; + } + } +} diff --git a/styles/less/actors/adversary/sidebar.less b/styles/less/actors/adversary/sidebar.less new file mode 100644 index 00000000..df2ca161 --- /dev/null +++ b/styles/less/actors/adversary/sidebar.less @@ -0,0 +1,341 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.adversary { + .adversary-sidebar-sheet { + width: 275px; + min-width: 275px; + border-right: 1px solid light-dark(@dark-blue, @golden); + background-image: url('../assets/parchments/dh-parchment-dark.png'); + + .theme-light & { + background: transparent; + } + + .portrait { + position: relative; + border-bottom: 1px solid light-dark(@dark-blue, @golden); + cursor: pointer; + + img { + height: 235px; + width: 275px; + object-fit: cover; + } + + .death-roll-btn { + display: none; + } + + &.death-roll { + filter: grayscale(1); + + .death-roll-btn { + display: flex; + position: absolute; + top: 30%; + right: 30%; + font-size: 6rem; + color: @beige; + + &:hover { + text-shadow: 0 0 8px @beige; + } + } + } + } + + .threshold-section { + position: relative; + display: flex; + gap: 10px; + background-color: light-dark(transparent, @dark-blue); + color: light-dark(@dark-blue, @golden); + padding: 5px 10px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + align-items: center; + width: fit-content; + height: 30px; + margin-top: 16px; + + h4 { + font-size: 14px; + font-weight: bold; + text-transform: uppercase; + color: light-dark(@dark-blue, @golden); + + &.threshold-value { + color: light-dark(@dark, @beige); + } + } + + .threshold-legend { + position: absolute; + bottom: -21px; + color: light-dark(@golden, @dark-blue); + background-color: light-dark(@dark-blue, @golden); + padding: 3px; + justify-self: anchor-center; + border-radius: 0 0 3px 3px; + text-transform: capitalize; + } + + .hope-value { + display: flex; + cursor: pointer; + } + } + + .info-section { + position: relative; + display: flex; + flex-direction: column; + top: -20px; + gap: 16px; + margin-bottom: -10px; + + .resources-section { + display: flex; + justify-content: space-evenly; + + .status-bar { + position: relative; + width: 100px; + height: 40px; + justify-items: center; + + .status-label { + position: relative; + top: 40px; + height: 22px; + width: 79px; + clip-path: path('M0 0H79L74 16.5L39 22L4 16.5L0 0Z'); + background: light-dark(@dark-blue, @golden); + + h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + color: light-dark(@beige, @dark-blue); + } + } + .status-value { + position: absolute; + display: flex; + padding: 0 6px; + font-size: 1.5rem; + align-items: center; + width: 100px; + height: 40px; + justify-content: center; + text-align: center; + z-index: 2; + color: @beige; + + input[type='number'] { + background: transparent; + font-size: 1.5rem; + width: 40px; + height: 30px; + text-align: center; + border: none; + outline: 2px solid transparent; + color: @beige; + + &.bar-input { + padding: 0; + color: @beige; + backdrop-filter: none; + background: transparent; + transition: all 0.3s ease; + + &:hover, + &:focus { + background: @semi-transparent-dark-blue; + backdrop-filter: blur(9.5px); + } + } + } + + .bar-label { + width: 40px; + } + } + .progress-bar { + position: absolute; + appearance: none; + width: 100px; + height: 40px; + border: 1px solid light-dark(@dark-blue, @golden); + border-radius: 6px; + z-index: 1; + background: @dark-blue; + + &::-webkit-progress-bar { + border: none; + background: @dark-blue; + border-radius: 6px; + } + &::-webkit-progress-value { + background: @gradient-hp; + border-radius: 6px; + } + &.stress-color::-webkit-progress-value { + background: @gradient-stress; + border-radius: 6px; + } + &::-moz-progress-bar { + background: @gradient-hp; + border-radius: 6px; + } + &.stress-color::-moz-progress-bar { + background: @gradient-stress; + border-radius: 6px; + } + } + } + } + + .status-section { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: center; + + .status-number { + justify-items: center; + + .status-value { + position: relative; + display: flex; + width: 50px; + height: 30px; + border: 1px solid light-dark(@dark-blue, @golden); + border-bottom: none; + border-radius: 6px 6px 0 0; + padding: 0 6px; + font-size: 1.2rem; + align-items: center; + justify-content: center; + background: light-dark(transparent, @dark-blue); + z-index: 2; + + &.armor-slots { + width: 80px; + height: 30px; + } + } + + .status-label { + padding: 2px 10px; + width: 100%; + border-radius: 3px; + background: light-dark(@dark-blue, @golden); + + h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + font-size: 12px; + color: light-dark(@beige, @dark-blue); + } + } + } + } + } + + .items-sidebar-list { + display: flex; + flex-direction: column; + gap: 5px; + + .inventory-item { + padding: 0 10px; + } + } + + .attack-section { + .title { + display: flex; + gap: 15px; + align-items: center; + + h3 { + font-size: 20px; + } + } + .items-list { + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; + } + } + + .experience-section { + margin-bottom: 20px; + + .title { + display: flex; + gap: 15px; + align-items: center; + + h3 { + font-size: 20px; + } + } + + .experience-list { + display: flex; + flex-direction: column; + gap: 5px; + width: 100%; + margin-top: 10px; + align-items: center; + + .experience-row { + display: flex; + gap: 5px; + width: 250px; + align-items: center; + justify-content: space-between; + + .experience-name { + width: 180px; + text-align: start; + font-size: 14px; + font-family: @font-body; + color: light-dark(@dark, @beige); + } + } + + .experience-value { + height: 25px; + width: 35px; + font-size: 14px; + font-family: @font-body; + color: light-dark(@dark, @beige); + align-content: center; + text-align: center; + background: url(../assets/svg/experience-shield.svg) no-repeat; + + .theme-light & { + background: url('../assets/svg/experience-shield-light.svg') no-repeat; + } + } + } + } + + .reaction-section { + display: flex; + padding: 0 10px; + margin-top: 20px; + width: 100%; + + button { + width: 100%; + } + } + } +} diff --git a/styles/less/actors/character/inventory.less b/styles/less/actors/character/inventory.less index c1583046..516b01b0 100644 --- a/styles/less/actors/character/inventory.less +++ b/styles/less/actors/character/inventory.less @@ -1,70 +1,70 @@ -@import '../../utils/colors.less'; -@import '../../utils/fonts.less'; - -.application.sheet.daggerheart.actor.dh-style.character { - .tab.inventory { - .search-section { - display: flex; - gap: 10px; - align-items: center; - - .search-bar { - position: relative; - color: light-dark(@dark-blue-50, @beige-50); - width: 100%; - padding-top: 5px; - - input { - border-radius: 50px; - font-family: @font-body; - background: light-dark(@dark-blue-10, @golden-10); - border: none; - outline: 2px solid transparent; - transition: all 0.3s ease; - padding: 0 20px; - - &:hover { - outline: 2px solid light-dark(@dark, @golden); - } - - &:placeholder { - color: light-dark(@dark-blue-50, @beige-50); - } - - &::-webkit-search-cancel-button { - -webkit-appearance: none; - display: none; - } - } - - .icon { - align-content: center; - height: 32px; - position: absolute; - right: 20px; - font-size: 16px; - z-index: 1; - color: light-dark(@dark-blue-50, @beige-50); - } - } - } - - .items-section { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); - padding: 20px 0; - height: 80%; - - scrollbar-width: thin; - scrollbar-color: light-dark(@dark-blue, @golden) transparent; - } - - .currency-section { - display: flex; - gap: 10px; - } - } -} +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.character { + .tab.inventory { + .search-section { + display: flex; + gap: 10px; + align-items: center; + + .search-bar { + position: relative; + color: light-dark(@dark-blue-50, @beige-50); + width: 100%; + padding-top: 5px; + + input { + border-radius: 50px; + font-family: @font-body; + background: light-dark(@dark-blue-10, @golden-10); + border: none; + outline: 2px solid transparent; + transition: all 0.3s ease; + padding: 0 20px; + + &:hover { + outline: 2px solid light-dark(@dark, @golden); + } + + &:placeholder { + color: light-dark(@dark-blue-50, @beige-50); + } + + &::-webkit-search-cancel-button { + -webkit-appearance: none; + display: none; + } + } + + .icon { + align-content: center; + height: 32px; + position: absolute; + right: 20px; + font-size: 16px; + z-index: 1; + color: light-dark(@dark-blue-50, @beige-50); + } + } + } + + .items-section { + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: auto; + mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); + padding: 20px 0; + height: 80%; + + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + } + + .currency-section { + display: flex; + gap: 10px; + } + } +} diff --git a/styles/less/actors/character/sheet.less b/styles/less/actors/character/sheet.less index 6ca1ca4f..0959d146 100644 --- a/styles/less/actors/character/sheet.less +++ b/styles/less/actors/character/sheet.less @@ -24,9 +24,5 @@ grid-row: 2; grid-column: 2; } - - .old-sheet { - width: 500px; - } } } diff --git a/styles/less/actors/character/sidebar.less b/styles/less/actors/character/sidebar.less index bde3996c..e5783cbb 100644 --- a/styles/less/actors/character/sidebar.less +++ b/styles/less/actors/character/sidebar.less @@ -14,8 +14,6 @@ .portrait { position: relative; - height: 235px; - width: 275px; border-bottom: 1px solid light-dark(@dark-blue, @golden); cursor: pointer; @@ -130,6 +128,7 @@ border: 1px solid light-dark(@dark-blue, @golden); border-radius: 6px; z-index: 1; + background: @dark-blue; &::-webkit-progress-bar { border: none; @@ -144,13 +143,9 @@ background: @gradient-stress; border-radius: 6px; } - &::-moz-progress-value, - &::-moz-progress-bar { - border-radius: 6px; - } - &::-moz-progress-bar { background: @gradient-hp; + border-radius: 6px; } &.stress-color::-moz-progress-bar { background: @gradient-stress; diff --git a/styles/less/actors/companion/sheet.less b/styles/less/actors/companion/sheet.less new file mode 100644 index 00000000..1beb28a7 --- /dev/null +++ b/styles/less/actors/companion/sheet.less @@ -0,0 +1,11 @@ +.application.sheet.daggerheart.actor.dh-style.companion { + .profile { + height: 80px; + width: 80px; + } + + .temp-container { + position: relative; + top: 32px; + } +} diff --git a/styles/less/actors/environment/header.less b/styles/less/actors/environment/header.less new file mode 100644 index 00000000..fce7943f --- /dev/null +++ b/styles/less/actors/environment/header.less @@ -0,0 +1,140 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.environment { + .environment-header-sheet { + display: flex; + flex-direction: column; + justify-content: start; + text-align: center; + + .profile { + width: 100%; + height: 235px; + object-fit: cover; + mask-image: linear-gradient(0deg, transparent 0%, black 10%); + cursor: pointer; + } + + .item-container { + display: flex; + align-items: center; + position: relative; + top: -45px; + gap: 20px; + padding: 0 20px; + margin-bottom: -30px; + + .item-info { + display: flex; + flex-direction: column; + gap: 8px; + + .tags { + display: flex; + gap: 10px; + padding-bottom: 0; + + .tag { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: 12px; + font: @font-body; + + background: light-dark(@dark-15, @beige-15); + border: 1px solid light-dark(@dark, @beige); + border-radius: 3px; + } + + .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: 12px; + } + } + } + + .status-number { + justify-items: center; + + .status-value { + position: relative; + display: flex; + width: 50px; + height: 30px; + border: 1px solid light-dark(@dark-blue, @golden); + border-bottom: none; + border-radius: 6px 6px 0 0; + padding: 0 6px; + font-size: 1.2rem; + align-items: center; + justify-content: center; + background: light-dark(transparent, @dark-blue); + z-index: 2; + + &.armor-slots { + width: 80px; + height: 30px; + } + } + + .status-label { + padding: 2px 10px; + width: 100%; + border-radius: 3px; + background: light-dark(@dark-blue, @golden); + + h4 { + font-weight: bold; + text-align: center; + line-height: 18px; + font-size: 12px; + color: light-dark(@beige, @dark-blue); + } + } + } + + .item-name { + input[type='text'] { + font-size: 32px; + height: 42px; + text-align: start; + transition: all 0.3s ease; + outline: 2px solid transparent; + border: 1px solid transparent; + + &:hover[type='text'], + &:focus[type='text'] { + box-shadow: none; + outline: 2px solid light-dark(@dark-blue, @golden); + } + } + } + } + + .environment-info { + display: flex; + flex-direction: column; + gap: 12px; + padding: 10px 20px; + + .description, + .impulses { + text-align: start; + font-family: @font-body; + } + } + + .environment-navigation { + display: flex; + gap: 20px; + align-items: center; + padding: 0 20px; + } + } +} diff --git a/styles/less/actors/environment/sheet.less b/styles/less/actors/environment/sheet.less new file mode 100644 index 00000000..5e604188 --- /dev/null +++ b/styles/less/actors/environment/sheet.less @@ -0,0 +1,18 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.sheet.daggerheart.actor.dh-style.environment { + .theme-light & { + background: url('../assets/parchments/dh-parchment-light.png'); + } + .theme-dark & { + background-image: url('../assets/parchments/dh-parchment-dark.png'); + } + + .tab { + max-height: 300px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + } +} diff --git a/styles/less/applications/adversary-settings/actions.less b/styles/less/applications/adversary-settings/actions.less new file mode 100644 index 00000000..55cf2828 --- /dev/null +++ b/styles/less/applications/adversary-settings/actions.less @@ -0,0 +1,74 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.daggerheart.dh-style.dialog { + .tab.actions { + max-height: 450px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + + .add-action-btn { + width: 100%; + margin-bottom: 12px; + } + + .action-list { + display: flex; + flex-direction: column; + gap: 10px; + + .action-item { + display: grid; + grid-template-columns: 40px 1fr auto; + align-items: center; + gap: 5px; + border-radius: 3px; + + img { + height: 40px; + width: 40px; + object-fit: cover; + } + + .label { + font-family: @font-body; + + .tags { + display: flex; + gap: 10px; + + .tag { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: 12px; + + background: light-dark(@dark-15, @beige-15); + border: 1px solid light-dark(@dark, @beige); + border-radius: 3px; + } + + .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: 12px; + } + } + } + + .controls { + display: flex; + gap: 5px; + a { + text-align: center; + } + } + } + } + } +} diff --git a/styles/less/applications/adversary-settings/experiences.less b/styles/less/applications/adversary-settings/experiences.less new file mode 100644 index 00000000..36fe85cb --- /dev/null +++ b/styles/less/applications/adversary-settings/experiences.less @@ -0,0 +1,28 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.daggerheart.dh-style.dialog { + .tab.experiences { + .add-experience-btn { + width: 100%; + margin-bottom: 12px; + } + + .experience-list { + display: flex; + flex-direction: column; + gap: 10px; + + .experience-item { + display: grid; + grid-template-columns: 3fr 1fr 30px; + align-items: center; + gap: 5px; + + a { + text-align: center; + } + } + } + } +} diff --git a/styles/less/applications/adversary-settings/sheet.less b/styles/less/applications/adversary-settings/sheet.less new file mode 100644 index 00000000..5fd80b60 --- /dev/null +++ b/styles/less/applications/adversary-settings/sheet.less @@ -0,0 +1,18 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.daggerheart.dh-style.dialog { + .tab { + &.details.active, + &.attack.active { + display: flex; + flex-direction: column; + gap: 16px; + } + + .fieldsets-section { + display: flex; + gap: 16px; + } + } +} diff --git a/styles/less/applications/environment-settings/actions.less b/styles/less/applications/environment-settings/actions.less new file mode 100644 index 00000000..e4886ea9 --- /dev/null +++ b/styles/less/applications/environment-settings/actions.less @@ -0,0 +1,74 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.daggerheart.dh-style.dialog.environment-settings { + .tab.actions { + max-height: 450px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + + .add-action-btn { + width: 100%; + margin-bottom: 12px; + } + + .action-list { + display: flex; + flex-direction: column; + gap: 10px; + + .action-item { + display: grid; + grid-template-columns: 40px 1fr auto; + align-items: center; + gap: 5px; + + img { + height: 40px; + width: 40px; + object-fit: cover; + border-radius: 3px; + } + + .label { + font-family: @font-body; + + .tags { + display: flex; + gap: 10px; + + .tag { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 3px 5px; + font-size: 12px; + + background: light-dark(@dark-15, @beige-15); + border: 1px solid light-dark(@dark, @beige); + border-radius: 3px; + } + + .label { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + font-size: 12px; + } + } + } + + .controls { + display: flex; + gap: 5px; + a { + text-align: center; + } + } + } + } + } +} diff --git a/styles/less/applications/environment-settings/adversaries.less b/styles/less/applications/environment-settings/adversaries.less new file mode 100644 index 00000000..a60314ff --- /dev/null +++ b/styles/less/applications/environment-settings/adversaries.less @@ -0,0 +1,50 @@ +@import '../../utils/colors.less'; +@import '../../utils/fonts.less'; + +.application.daggerheart.dh-style.dialog { + .tab.adversaries { + max-height: 450px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + + .add-action-btn { + width: 100%; + margin-bottom: 12px; + } + + .category-container { + display: flex; + flex-direction: column; + align-items: start; + gap: 8px; + + .category-name { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + } + + .adversaries-container { + display: flex; + flex-direction: column; + gap: 6px; + width: 100%; + } + } + + .adversaries-dragger { + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + width: 100%; + height: 40px; + border: 1px dashed light-dark(@dark-blue-50, @beige-50); + border-radius: 3px; + color: light-dark(@dark-blue-50, @beige-50); + font-family: @font-body; + } + } +} diff --git a/styles/less/applications/header.less b/styles/less/applications/header.less new file mode 100644 index 00000000..209037ef --- /dev/null +++ b/styles/less/applications/header.less @@ -0,0 +1,20 @@ +@import '../utils/colors.less'; +@import '../utils/fonts.less'; + +.application.daggerheart.dh-style.dialog { + .window-content { + .dialog-header { + width: 100%; + padding-bottom: 16px; + h1 { + font-family: @font-subtitle; + font-style: normal; + font-weight: 700; + font-size: 24px; + margin: 0; + text-align: center; + color: light-dark(@dark-blue, @golden); + } + } + } +} diff --git a/styles/less/global/dialog.less b/styles/less/global/dialog.less new file mode 100644 index 00000000..5db641b2 --- /dev/null +++ b/styles/less/global/dialog.less @@ -0,0 +1,45 @@ +@import '../utils/colors.less'; +@import '../utils/fonts.less'; + +.theme-light { + .application.dialog.dh-style { + background-image: url('../assets/parchments/dh-parchment-light.png'); + background-repeat: no-repeat; + background-position: center; + } +} + +.theme-dark { + .application.dialog.dh-style { + background-image: url('../assets/parchments/dh-parchment-dark.png'); + background-repeat: no-repeat; + background-position: center; + } +} + +.application.dialog.dh-style { + border: none; + + .window-header { + background: transparent; + border-bottom: none; + color: light-dark(@dark-blue, @beige); + + h1 { + color: light-dark(@dark-blue, @beige); + font-family: @font-body; + } + + button { + color: light-dark(@dark-blue, @beige); + background: light-dark(transparent, @deep-black); + border: 1px solid light-dark(@dark-blue, transparent); + padding: 0; + + &:hover { + border: 1px solid light-dark(@dark-blue, @golden); + color: light-dark(@dark-blue, @golden); + } + } + } +} diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index c4745a16..1f6e5988 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -1,7 +1,7 @@ @import '../utils/colors.less'; @import '../utils/fonts.less'; -.application.sheet.dh-style { +.application.dh-style { border: 1px solid light-dark(@dark-blue, @golden); input[type='text'], @@ -90,6 +90,7 @@ a:hover, a.active { + font-weight: bold; text-shadow: 0 0 8px light-dark(@dark-blue, @golden); } @@ -111,9 +112,25 @@ } } + &.fit-height { + height: 95%; + } + &.flex { display: flex; gap: 20px; + &.wrap { + flex-wrap: wrap; + gap: 10px 20px; + } + .inline-child { + flex: 1; + } + .checkbox { + display: flex; + align-items: center; + gap: 20px; + } } &.one-column { @@ -160,10 +177,12 @@ .nest-inputs { display: flex; align-items: center; + width: 100%; gap: 5px; } .form-group { + width: 100%; label { font-family: @font-body; font-weight: bold; @@ -206,6 +225,8 @@ opacity: 1; transform: translateY(0); + grid-column: 1/-1; //For item descriptions in sheet elements + transition: opacity 0.3s ease-out, transform 0.3s ease-out; diff --git a/styles/less/global/inventory-fieldset-items.less b/styles/less/global/inventory-fieldset-items.less index 0c427bf7..1cbc9353 100644 --- a/styles/less/global/inventory-fieldset-items.less +++ b/styles/less/global/inventory-fieldset-items.less @@ -1,7 +1,7 @@ @import '../utils/colors.less'; @import '../utils/fonts.less'; -.application.sheet.daggerheart.actor.dh-style.character { +.application.daggerheart.dh-style { .items-list { display: flex; flex-direction: column; diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index a14bd83e..8d521c82 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -1,7 +1,7 @@ @import '../utils/colors.less'; @import '../utils/fonts.less'; -.application.sheet.daggerheart.actor.dh-style.character { +.application.daggerheart.dh-style { .inventory-item { display: grid; grid-template-columns: 40px 1fr 60px; @@ -15,6 +15,10 @@ border: none; cursor: pointer; object-fit: cover; + + &.actor-img { + border-radius: 50%; + } } .item-label { diff --git a/styles/less/global/prose-mirror.less b/styles/less/global/prose-mirror.less new file mode 100644 index 00000000..33e942e8 --- /dev/null +++ b/styles/less/global/prose-mirror.less @@ -0,0 +1,24 @@ +@import '../utils/colors.less'; + +.application { + prose-mirror { + height: 100% !important; + + .editor-menu { + background-color: transparent; + } + .editor-content { + scrollbar-width: thin; + scrollbar-color: light-dark(@dark-blue, @golden) transparent; + h1 { + font-size: 36px; + } + h2 { + font-size: 32px; + } + h3 { + font-size: 24px; + } + } + } +} \ No newline at end of file diff --git a/styles/less/global/tab-actions.less b/styles/less/global/tab-actions.less index 7b225d4a..0617016d 100644 --- a/styles/less/global/tab-actions.less +++ b/styles/less/global/tab-actions.less @@ -18,6 +18,13 @@ grid-template-columns: 1fr 4fr 1fr; cursor: pointer; + img { + height: 40px; + width: 40px; + object-fit: cover; + border-radius: 3px; + } + h4 { font-family: @font-body; font-weight: lighter; diff --git a/styles/less/global/tab-navigation.less b/styles/less/global/tab-navigation.less index a13c7ea2..2880711d 100755 --- a/styles/less/global/tab-navigation.less +++ b/styles/less/global/tab-navigation.less @@ -1,10 +1,11 @@ @import '../utils/colors.less'; @import '../utils/fonts.less'; -.sheet.daggerheart.dh-style { +.daggerheart.dh-style { .tab-navigation { margin: 5px 0; height: 40px; + width: 100%; .feature-tab { border: none; diff --git a/styles/levelup.less b/styles/levelup.less index 3363d0a0..0f7949ba 100644 --- a/styles/levelup.less +++ b/styles/levelup.less @@ -25,6 +25,7 @@ display: flex; flex-direction: column; gap: 8px; + margin-top: 8px; } } @@ -217,6 +218,15 @@ align-items: center; gap: 4px; } + + .levelup-radio-choices { + display: flex; + gap: 8px; + + label { + flex: 0; + } + } } .levelup-summary-container { diff --git a/system.json b/system.json index 073315eb..c1a5c501 100644 --- a/system.json +++ b/system.json @@ -209,6 +209,7 @@ "character": { "htmlFields": ["story", "description", "scars.*.description"] }, + "companion": {}, "adversary": { "htmlFields": ["description", "motivesAndTactics"] }, diff --git a/templates/chat/adversary-roll.hbs b/templates/chat/adversary-roll.hbs index 92bb1c96..0004e586 100644 --- a/templates/chat/adversary-roll.hbs +++ b/templates/chat/adversary-roll.hbs @@ -6,7 +6,7 @@
- {{#each roll.dice}} + {{#each roll.dice as | dice index |}}
{{formula}} {{total}} @@ -17,9 +17,11 @@
  • {{result}}
  • {{/each}} + {{#if (eq index 0)}}
    {{#if (eq ../roll.advantage.type 1)}}{{localize "DAGGERHEART.General.Advantage.Full"}}{{/if}}{{#if (eq ../roll.advantage.type -1)}}{{localize "DAGGERHEART.General.Disadvantage.Full"}}{{/if}}
    + {{/if}}
    {{/each}}
    @@ -31,13 +33,30 @@ +
    + Damage +
    +
    +
    + {{> 'systems/daggerheart/templates/chat/parts/damage-chat.hbs' damage=damage noTitle=true}} +
    +
    +
    +
    {{> 'systems/daggerheart/templates/chat/parts/target-chat.hbs'}} {{#if hasDamage}}
    -
    - + {{#if damage.roll}} +
    + {{!-- --}} +
    + {{else}} +
    + +
    + {{/if}}
    {{/if}} \ No newline at end of file diff --git a/templates/chat/damage-roll.hbs b/templates/chat/damage-roll.hbs index 9442fe5a..2b6452b7 100644 --- a/templates/chat/damage-roll.hbs +++ b/templates/chat/damage-roll.hbs @@ -1,29 +1,4 @@ -
    -
    {{title}}
    -
    -
    {{roll.formula}}
    -
    -
    -
    - {{#each roll.dice}} -
    -
    - {{formula}} - {{total}} -
    -
      - {{#each results}} -
    1. {{result}}
    2. - {{/each}} -
    -
    - {{/each}} -
    -
    -
    -
    {{roll.total}}
    -
    -
    +{{> 'systems/daggerheart/templates/chat/parts/damage-chat.hbs' damage=this}} {{> 'systems/daggerheart/templates/chat/parts/target-chat.hbs'}}
    diff --git a/templates/chat/duality-roll.hbs b/templates/chat/duality-roll.hbs index 2d77f815..ff1e9894 100644 --- a/templates/chat/duality-roll.hbs +++ b/templates/chat/duality-roll.hbs @@ -82,6 +82,30 @@
    {{/if}} + {{#each roll.extra as | extra | }} +
    +
    + + 1{{extra.dice}} + + {{extra.value}} +
    +
    +
      +
    1. +
      +
      +
      + +
      +
      {{extra.value}}
      +
      +
      +
    2. +
    +
    +
    + {{/each}} {{#if roll.modifierTotal}}
    {{#if (gt roll.modifierTotal 0)}}+{{/if}}{{roll.modifierTotal}}
    {{/if}}
    @@ -94,12 +118,26 @@ +
    + Damage +
    +
    +
    + {{!-- {{> 'systems/daggerheart/templates/chat/parts/damage-chat.hbs' damage=damage noTitle=true}} --}} +
    +
    +
    +
    {{> 'systems/daggerheart/templates/chat/parts/target-chat.hbs'}}
    {{#if hasDamage}} + {{#if damage.roll}} + + {{else}} + {{/if}} {{else}} {{#if hasHealing}} diff --git a/templates/chat/parts/damage-chat.hbs b/templates/chat/parts/damage-chat.hbs new file mode 100644 index 00000000..096f09dc --- /dev/null +++ b/templates/chat/parts/damage-chat.hbs @@ -0,0 +1,27 @@ +
    + {{#unless noTitle}}
    {{damage.title}}
    {{/unless}} +
    +
    {{damage.roll.formula}}
    +
    +
    +
    + {{#each damage.roll.dice}} +
    +
    + {{formula}} + {{total}} +
    +
      + {{#each results}} +
    1. {{result}}
    2. + {{/each}} +
    +
    + {{/each}} +
    Total: {{damage.roll.total}}
    +
    +
    +
    +
    {{damage.roll.total}}
    +
    +
    \ No newline at end of file diff --git a/templates/chat/parts/target-chat.hbs b/templates/chat/parts/target-chat.hbs index d2fc81ea..9ac993cc 100644 --- a/templates/chat/parts/target-chat.hbs +++ b/templates/chat/parts/target-chat.hbs @@ -1,22 +1,29 @@ -{{#if (gt targets.length 0)}} +{{#if (gt currentTargets.length 0)}}
    Targets
    +
    + + +
    + {{#if (and hasSave @root.targetSelection @root.hasHitTarget)}} + + {{/if}}
    - {{#each targets as |target|}} + {{#each currentTargets as |target|}}
    - {{#if ../directDamage}} + {{#if (or ../directDamage (not @root.targetSelection))}}
    {{target.name}}
    {{else}} {{#if target.hit}}{{localize "Hit"}}{{else}}{{#if (not ../total.alternate)}}{{localize "Miss"}}{{else}}?{{/if}}{{/if}} {{/if}}
    - {{#if ../hasSave}} - +
    \ No newline at end of file diff --git a/templates/sheets/actors/adversary/information.hbs b/templates/sheets/actors/adversary/information.hbs deleted file mode 100644 index 085f200c..00000000 --- a/templates/sheets/actors/adversary/information.hbs +++ /dev/null @@ -1,17 +0,0 @@ -
    - {{!--
    - {{localize "DAGGERHEART.Sheets.Adversary.FIELDS.description.label" }} - - {{formInput systemFields.description value=source.system.description}} -
    --}} - -
    - {{localize "DAGGERHEART.Sheets.Adversary.FIELDS.motivesAndTactics.label" }} - - {{formInput systemFields.motivesAndTactics value=source.system.motivesAndTactics}} -
    -
    diff --git a/templates/sheets/actors/adversary/main.hbs b/templates/sheets/actors/adversary/main.hbs deleted file mode 100644 index 541c5aca..00000000 --- a/templates/sheets/actors/adversary/main.hbs +++ /dev/null @@ -1,80 +0,0 @@ -
    -
    - -
    - {{localize "DAGGERHEART.Sheets.Adversary.General"}} - - {{formGroup systemFields.tier value=source.system.tier localize=true}} - {{formGroup systemFields.type value=source.system.type localize=true}} -
    {{formGroup systemFields.difficulty value=source.system.difficulty}}
    - -
    - {{localize "DAGGERHEART.Sheets.Adversary.DamageThresholds"}} - - {{formGroup systemFields.damageThresholds.fields.major value=source.system.damageThresholds.major}} - {{formGroup systemFields.damageThresholds.fields.severe value=source.system.damageThresholds.severe}} -
    - -
    - {{localize "DAGGERHEART.Sheets.Adversary.HitPoints"}} - - {{formGroup systemFields.resources.fields.hitPoints.fields.value value=source.system.resources.hitPoints.value}} - {{formGroup systemFields.resources.fields.hitPoints.fields.max value=source.system.resources.hitPoints.max}} -
    - -
    - {{localize "DAGGERHEART.Sheets.Adversary.Stress"}} - - {{formGroup systemFields.resources.fields.stress.fields.value value=source.system.resources.stress.value}} - {{formGroup systemFields.resources.fields.stress.fields.max value=source.system.resources.stress.max}} -
    - -
    - {{localize "DAGGERHEART.Sheets.Adversary.Experiences"}} - - {{#each source.system.experiences}} -
    - {{this.name}} - - {{formGroup @root.systemFields.experiences.element.fields.name name=(concat "system.experiences." @key ".name") value=this.name }} - {{formGroup @root.systemFields.experiences.element.fields.value name=(concat "system.experiences." @key ".value") value=this.value }} -
    - {{/each}} -
    -
    - -
    - {{localize "DAGGERHEART.Sheets.Adversary.Attack"}} - - -
    - -
    Name
    -
    -
    - {{formGroup systemFields.attack.fields.name value=source.system.attack.name name="system.attack.name"}} - {{formGroup systemFields.attack.fields.img value=source.img label="Icon" name="system.attack.img"}} -
    -
    -
    -
    - -
    Bonus to Hit
    -
    -
    - {{formField systemFields.attack.fields.roll.fields.bonus value=source.system.attack.roll.bonus name="system.attack.roll.bonus"}} -
    -
    - {{> 'systems/daggerheart/templates/views/actionTypes/range-target.hbs' fields=(object range=systemFields.attack.fields.range target=systemFields.attack.fields.target.fields) source=(object target=source.system.attack.target range=source.system.attack.range) path="system.attack."}} -
    - {{> 'systems/daggerheart/templates/views/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=source.system.attack.damage path="system.attack."}} -
    - {{> 'systems/daggerheart/templates/views/actionTypes/effect.hbs' fields=systemFields.attack.fields.effects.element.fields source=source.system.attack.effects}} -
    -
    -
    -
    diff --git a/templates/sheets/actors/adversary/notes.hbs b/templates/sheets/actors/adversary/notes.hbs new file mode 100644 index 00000000..effa7240 --- /dev/null +++ b/templates/sheets/actors/adversary/notes.hbs @@ -0,0 +1,10 @@ +
    +
    + {{localize tabs.notes.label}} + {{formInput systemFields.notes value=document.system.notes enriched=document.system.notes localize=true toggled=true}} +
    +
    \ No newline at end of file diff --git a/templates/sheets/actors/adversary/sidebar.hbs b/templates/sheets/actors/adversary/sidebar.hbs new file mode 100644 index 00000000..ce2b20be --- /dev/null +++ b/templates/sheets/actors/adversary/sidebar.hbs @@ -0,0 +1,111 @@ + \ No newline at end of file diff --git a/templates/sheets/actors/character/features.hbs b/templates/sheets/actors/character/features.hbs index b2851193..1ac18a1e 100644 --- a/templates/sheets/actors/character/features.hbs +++ b/templates/sheets/actors/character/features.hbs @@ -10,6 +10,12 @@ {{#if document.system.class.subclass}} {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(concat (localize 'TYPES.Item.subclass') ' - ' document.system.class.subclass.name) type='subclass'}} {{/if}} + {{#if document.system.features}} + {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize "DAGGERHEART.Sheets.PC.Features") type='features'}} + {{/if}} + {{#if document.system.companionFeatures}} + {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize "DAGGERHEART.Sheets.PC.CompanionFeatures") type='companionFeatures'}} + {{/if}} {{#if document.system.community}} {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(concat (localize 'TYPES.Item.community') ' - ' document.system.community.name) type='community'}} {{/if}} diff --git a/templates/sheets/actors/character/sidebar.hbs b/templates/sheets/actors/character/sidebar.hbs index 4060caeb..1f54be0e 100644 --- a/templates/sheets/actors/character/sidebar.hbs +++ b/templates/sheets/actors/character/sidebar.hbs @@ -1,5 +1,5 @@
    - +
    diff --git a/templates/tooltip/armor.hbs b/templates/tooltip/armor.hbs new file mode 100644 index 00000000..c2972700 --- /dev/null +++ b/templates/tooltip/armor.hbs @@ -0,0 +1,5 @@ +
    +
    {{name}}
    + +
    {{{system.description}}}
    +
    \ No newline at end of file diff --git a/templates/tooltip/domainCard.hbs b/templates/tooltip/domainCard.hbs new file mode 100644 index 00000000..c2972700 --- /dev/null +++ b/templates/tooltip/domainCard.hbs @@ -0,0 +1,5 @@ +
    +
    {{name}}
    + +
    {{{system.description}}}
    +
    \ No newline at end of file diff --git a/templates/tooltip/weapon.hbs b/templates/tooltip/weapon.hbs new file mode 100644 index 00000000..c2972700 --- /dev/null +++ b/templates/tooltip/weapon.hbs @@ -0,0 +1,5 @@ +
    +
    {{name}}
    + +
    {{{system.description}}}
    +
    \ No newline at end of file diff --git a/templates/views/action.hbs b/templates/views/action.hbs index 45373815..9a3a25b4 100644 --- a/templates/views/action.hbs +++ b/templates/views/action.hbs @@ -33,10 +33,8 @@
    - {{#unless isNPC}} {{> 'systems/daggerheart/templates/views/actionTypes/uses.hbs' fields=fields.uses.fields source=source.uses}} {{> 'systems/daggerheart/templates/views/actionTypes/cost.hbs' fields=fields.cost.element.fields source=source.cost}} - {{/unless}} {{#if fields.target}}{{> 'systems/daggerheart/templates/views/actionTypes/range-target.hbs' fields=(object range=fields.range target=fields.target.fields) source=(object target=source.target range=source.range)}}{{/if}}
    diff --git a/templates/views/actionTypes/damage.hbs b/templates/views/actionTypes/damage.hbs index 54fdb6b1..01dc533a 100644 --- a/templates/views/actionTypes/damage.hbs +++ b/templates/views/actionTypes/damage.hbs @@ -1,61 +1,57 @@ -
    - -
    Damage
    -
    -
    - {{#unless @root.isNPC}} -
    - {{#if @root.hasBaseDamage}} -
    - {{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase label="Include Item Damage" name="damage.includeBase" }} -
    - {{/if}} - {{/unless}} - {{#each source.parts as |dmg index|}} - {{#if @root.isNPC}} - {{formField ../fields.value.fields.custom.fields.enabled value=dmg.value.custom.enabled name=(concat ../path "damage.parts." index ".value.custom.enabled")}} - - {{#if dmg.value.custom.enabled}} - {{formField ../fields.value.fields.custom.fields.formula value=dmg.value.custom.formula name=(concat ../path "damage.parts." index ".value.custom.formula") localize=true}} - {{else}} -
    - {{formField ../fields.value.fields.flatMultiplier value=dmg.value.flatMultiplier name=(concat ../path "damage.parts." index ".value.flatMultiplier") label="Multiplier" }} - {{formField ../fields.value.fields.dice value=dmg.value.dice name=(concat ../path "damage.parts." index ".value.dice")}} - {{formField ../fields.value.fields.bonus value=dmg.value.bonus name=(concat ../path "damage.parts." index ".value.bonus") localize=true}} -
    - {{/if}} - {{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." index ".type") localize=true}} +
    + Damage + {{#unless (eq path 'system.attack.')}} +
    + {{/unless}} + {{#unless @root.isNPC}} + {{#if @root.hasBaseDamage}} +
    + {{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase label="Include Item Damage" name="damage.includeBase" }} +
    + {{/if}} + {{/unless}} + {{#each source.parts as |dmg index|}} + {{#if @root.isNPC}} + {{formField ../fields.value.fields.custom.fields.enabled value=dmg.value.custom.enabled name=(concat ../path "damage.parts." index ".value.custom.enabled") classes="checkbox"}} + + {{#if dmg.value.custom.enabled}} + {{formField ../fields.value.fields.custom.fields.formula value=dmg.value.custom.formula name=(concat ../path "damage.parts." index ".value.custom.formula") localize=true}} {{else}} - {{#with (@root.getRealIndex index) as | realIndex |}} - - {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}} - {{formField ../../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." realIndex ".resultBased") localize=true}} - {{/if}} - {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base) dmg.resultBased)}} -
    - -
    With Hope
    -
    - {{> formula fields=../../fields.value.fields type=../../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex}} -
    -
    - -
    With Fear
    -
    - {{> formula fields=../../fields.valueAlt.fields type=../../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" realIndex=realIndex}} -
    - {{else}} - {{> formula fields=../../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex}} - {{/if}} - {{formField ../../fields.type value=dmg.type name=(concat "damage.parts." realIndex ".type") localize=true}} - - {{#unless dmg.base}}
    {{/unless}} -
    - {{/with}} + {{formField ../fields.value.fields.flatMultiplier value=dmg.value.flatMultiplier name=(concat ../path "damage.parts." index ".value.flatMultiplier") label="Multiplier" classes="inline-child" }} + {{formField ../fields.value.fields.dice value=dmg.value.dice name=(concat ../path "damage.parts." index ".value.dice") classes="inline-child"}} + {{formField ../fields.value.fields.bonus value=dmg.value.bonus name=(concat ../path "damage.parts." index ".value.bonus") localize=true classes="inline-child"}} {{/if}} - {{/each}} -
    + {{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." index ".type") localize=true}} + {{else}} + {{#with (@root.getRealIndex index) as | realIndex |}} + + {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}} + {{formField ../../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." realIndex ".resultBased") localize=true}} + {{/if}} + {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base) dmg.resultBased)}} +
    + +
    With Hope
    +
    + {{> formula fields=../../fields.value.fields type=../../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex}} +
    +
    + +
    With Fear
    +
    + {{> formula fields=../../fields.valueAlt.fields type=../../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" realIndex=realIndex}} +
    + {{else}} + {{> formula fields=../../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex}} + {{/if}} + {{formField ../../fields.type value=dmg.type name=(concat "damage.parts." realIndex ".type") localize=true}} + + {{#unless dmg.base}}
    {{/unless}} +
    + {{/with}} + {{/if}} + {{/each}} {{#*inline "formula"}} diff --git a/templates/views/actionTypes/roll.hbs b/templates/views/actionTypes/roll.hbs index c4a7387c..cfc79670 100644 --- a/templates/views/actionTypes/roll.hbs +++ b/templates/views/actionTypes/roll.hbs @@ -24,5 +24,8 @@
    {{/if}} {{/if}} + {{#unless (eq source.type "diceSet")}} + {{formField fields.advState label= "Advantage State" name="roll.advState" value=source.advState localize=true}} + {{/unless}}
    \ No newline at end of file diff --git a/templates/views/countdowns.hbs b/templates/views/countdowns.hbs index 293b84b9..f06aa4db 100644 --- a/templates/views/countdowns.hbs +++ b/templates/views/countdowns.hbs @@ -1,42 +1,45 @@
    -
    -
    - {{#if canCreate}}{{/if}} - {{#if isGM}}{{/if}} -
    - -
    + {{#if simple}} +
    {{#each source.countdowns}} -
    - - {{this.name}} - {{#if this.canEdit}}{{/if}} - {{#if @root.isGM}}{{/if}} - - - -
    - -
    - {{formGroup @root.countdownFields.name name=(concat @root.base ".countdowns." @key ".name") value=this.name localize=true disabled=(not this.canEdit)}} -
    - {{formGroup @root.countdownFields.progress.fields.current name=(concat @root.base ".countdowns." @key ".progress.current") value=this.progress.current localize=true disabled=(not this.canEdit)}} - {{formGroup @root.countdownFields.progress.fields.max name=(concat @root.base ".countdowns." @key ".progress.max") value=this.progress.max localize=true disabled=(not this.canEdit)}} -
    - {{formGroup @root.countdownFields.progress.fields.type.fields.value name=(concat @root.base ".countdowns." @key ".progress.type.value") value=this.progress.type.value localize=true localize=true disabled=(not this.canEdit)}} -
    -
    -
    + + +
    {{this.name}}
    +
    {{this.progress.current}}/{{this.progress.max}}
    +
    {{/each}}
    -
    - + {{else}} +
    +
    + {{#if canCreate}}{{/if}} + {{#if isGM}}{{/if}} +
    + +
    + {{#each source.countdowns}} +
    + + {{this.name}} + {{#if this.canEdit}}{{/if}} + {{#if @root.isGM}}{{/if}} + + + +
    + +
    + {{formGroup @root.countdownFields.name name=(concat @root.base ".countdowns." @key ".name") value=this.name localize=true disabled=(not this.canEdit)}} +
    + {{formGroup @root.countdownFields.progress.fields.current name=(concat @root.base ".countdowns." @key ".progress.current") value=this.progress.current localize=true disabled=(not this.canEdit)}} + {{formGroup @root.countdownFields.progress.fields.max name=(concat @root.base ".countdowns." @key ".progress.max") value=this.progress.max localize=true disabled=(not this.canEdit)}} +
    + {{formGroup @root.countdownFields.progress.fields.type.fields.value name=(concat @root.base ".countdowns." @key ".progress.type.value") value=this.progress.type.value localize=true localize=true disabled=(not this.canEdit)}} +
    +
    +
    + {{/each}} +
    +
    + {{/if}}
    \ No newline at end of file diff --git a/templates/views/damageSelection.hbs b/templates/views/damageSelection.hbs index b7c61443..988b852e 100644 --- a/templates/views/damageSelection.hbs +++ b/templates/views/damageSelection.hbs @@ -2,7 +2,11 @@
    - + {{!-- --}} +
    {{@root.formula}}
    +
    +
    +
    {{!-- {{#each bonusDamage as |damage index|}} diff --git a/templates/views/levelup/tabs/selections.hbs b/templates/views/levelup/tabs/selections.hbs index 03145347..58f1825b 100644 --- a/templates/views/levelup/tabs/selections.hbs +++ b/templates/views/levelup/tabs/selections.hbs @@ -111,5 +111,16 @@
    {{/if}} + + {{#if this.vicious}} +
    +

    {{localize "DAGGERHEART.Application.LevelUp.summary.vicious"}}

    + {{#each this.vicious}} +
    + {{radioBoxes (concat "levelup." this.path ".data") @root.viciousChoices checked=(lookup this.data 0)}} +
    + {{/each}} +
    + {{/if}} \ No newline at end of file diff --git a/templates/views/levelup/tabs/summary.hbs b/templates/views/levelup/tabs/summary.hbs index c1c1d13f..4ed4438f 100644 --- a/templates/views/levelup/tabs/summary.hbs +++ b/templates/views/levelup/tabs/summary.hbs @@ -4,55 +4,58 @@ data-group='{{tabs.summary.group}}' >
    -
    - {{localize "DAGGERHEART.Application.LevelUp.summary.levelAchievements"}} + {{#if this.achievements}} +
    + {{localize "DAGGERHEART.Application.LevelUp.summary.levelAchievements"}} -
    - {{#if this.achievements.proficiency.shown}} -
    -
    - {{localize "DAGGERHEART.Application.LevelUp.summary.proficiencyIncrease" proficiency=this.achievements.proficiency.old }} - - {{this.achievements.proficiency.new}} +
    + {{#if this.achievements.proficiency.shown}} +
    +
    + {{localize "DAGGERHEART.Application.LevelUp.summary.proficiencyIncrease" proficiency=this.achievements.proficiency.old }} + + {{this.achievements.proficiency.new}} +
    -
    - {{/if}} -
    -
    {{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholds"}}{{#if this.levelAchievements.damageThresholds.unarmored}}({{localize "DAGGERHEART.General.unarmored"}}){{/if}}
    -
    - {{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholdMajorIncrease" threshold=this.achievements.damageThresholds.major.old }} - - {{this.achievements.damageThresholds.major.new}} -
    -
    - {{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholdSevereIncrease" threshold=this.achievements.damageThresholds.severe.old }} - - {{this.achievements.damageThresholds.severe.new}} -
    + {{/if}} + {{#if this.achievements.damageThresholds}} +
    +
    {{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholds"}}{{#if this.levelAchievements.damageThresholds.unarmored}}({{localize "DAGGERHEART.General.unarmored"}}){{/if}}
    +
    + {{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholdMajorIncrease" threshold=this.achievements.damageThresholds.major.old }} + + {{this.achievements.damageThresholds.major.new}} +
    +
    + {{localize "DAGGERHEART.Application.LevelUp.summary.damageThresholdSevereIncrease" threshold=this.achievements.damageThresholds.severe.old }} + + {{this.achievements.damageThresholds.severe.new}} +
    +
    + {{/if}} + {{#if this.achievements.domainCards.shown}} +
    +
    {{localize "DAGGERHEART.Application.LevelUp.summary.domainCards"}}
    +
    + {{#each this.achievements.domainCards.values}} +
    {{this.name}}
    + {{/each}} +
    +
    + {{/if}} + {{#if this.achievements.experiences.shown}} +
    +
    {{localize "DAGGERHEART.Application.LevelUp.summary.newExperiences"}}
    +
    + {{#each this.achievements.experiences.values}} +
    {{this.name}} {{signedNumber this.modifier}}
    + {{/each}} +
    +
    + {{/if}}
    - {{#if this.achievements.domainCards.shown}} -
    -
    {{localize "DAGGERHEART.Application.LevelUp.summary.domainCards"}}
    -
    - {{#each this.achievements.domainCards.values}} -
    {{this.name}}
    - {{/each}} -
    -
    - {{/if}} - {{#if this.achievements.experiences.shown}} -
    -
    {{localize "DAGGERHEART.Application.LevelUp.summary.newExperiences"}}
    -
    - {{#each this.achievements.experiences.values}} -
    {{this.name}} {{signedNumber this.modifier}}
    - {{/each}} -
    -
    - {{/if}} -
    -
    - +
    + {{/if}}
    {{localize "DAGGERHEART.Application.LevelUp.summary.levelAdvancements"}} @@ -151,6 +154,26 @@
    {{/with}} {{/if}} + + {{#if this.advancements.vicious.damage}} +
    + {{localize "DAGGERHEART.Application.LevelUp.summary.damageIncreased" damage=this.advancements.vicious.damage.old }} + + {{this.advancements.vicious.damage.new}} +
    + {{/if}} + {{#if this.advancements.vicious.range}} +
    + {{localize "DAGGERHEART.Application.LevelUp.summary.rangeIncreased" range=this.advancements.vicious.range.old }} + + {{this.advancements.vicious.range.new}} +
    + {{/if}} + {{#each this.advancements.simple}} +
    +
    {{localize "DAGGERHEART.Application.LevelUp.summary.simpleFeature" feature=this}}
    +
    + {{/each}} diff --git a/templates/views/rollSelection.hbs b/templates/views/rollSelection.hbs index c3728ccc..98b6c91b 100644 --- a/templates/views/rollSelection.hbs +++ b/templates/views/rollSelection.hbs @@ -2,25 +2,58 @@ {{#if @root.hasRoll}}
    - {{#unless @root.isLite}} -
    - {{#each experiences}} - {{#if name}} -
    - {{name}} - +{{value}} -
    - {{/if}} - {{/each}} -
    -
    - - -
    - {{/unless}}
    - + {{!-- --}} +
    {{@root.formula}}
    + {{#unless @root.isLite}} +
    + {{#each experiences}} + {{#if name}} +
    + {{name}} + +{{value}} +
    + {{/if}} + {{/each}} +
    +
    + + +
    + {{#if (eq @root.rollType 'D20Roll')}} +
    + +
    + {{/if}} + {{#if (eq @root.rollType 'DualityRoll')}} +
    +
    Hope Dice
    + +
    +
    +
    Fear Dice
    + +
    + {{#if roll.advantage}} +
    +
    Adv/Disadv Dice
    + +
    + {{/if}} + {{/if}} +
    + +
    + {{/unless}} {{!-- {{#if (not isNpc)}} --}} {{!--