diff --git a/daggerheart.mjs b/daggerheart.mjs index b65bf1f3..27c01dd6 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -286,6 +286,7 @@ 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', diff --git a/lang/en.json b/lang/en.json index eb738315..f6f56668 100755 --- a/lang/en.json +++ b/lang/en.json @@ -501,6 +501,20 @@ "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", "InfoLabel": "At Level 2, gain an additional Experience at +2 and gain a +1 bonus to your Proficiency.", @@ -1146,6 +1160,8 @@ "CharacterSetup": "Character setup isn't done yet", "Level": "Level", "LevelUp": "You can level up", + "Actions": "Actions", + "CompanionActions": "Companion Actions", "Tabs": { "Features": "Features", "Inventory": "Inventory", 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/countdowns.mjs b/module/applications/countdowns.mjs index 0eac145f..9fcb0a2b 100644 --- a/module/applications/countdowns.mjs +++ b/module/applications/countdowns.mjs @@ -160,7 +160,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), 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/character.mjs b/module/applications/sheets/character.mjs index 44ded6f2..a601f99b 100644 --- a/module/applications/sheets/character.mjs +++ b/module/applications/sheets/character.mjs @@ -6,6 +6,8 @@ import DaggerheartSheet from './daggerheart-sheet.mjs'; import { abilities } from '../../config/actorConfig.mjs'; import DhCharacterlevelUp from '../levelup/characterLevelup.mjs'; import DhCharacterCreation from '../characterCreation.mjs'; +import DHActionConfig from '../config/Action.mjs'; +import { DHBaseAction } from '../../data/action/action.mjs'; const { ActorSheetV2 } = foundry.applications.sheets; const { TextEditor } = foundry.applications.ux; @@ -51,6 +53,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { levelManagement: this.levelManagement, editImage: this._onEditImage, triggerContextMenu: this.triggerContextMenu + // editAction: this.editAction, }, window: { resizable: true @@ -303,17 +306,27 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } getItem(element) { - const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId, + const listElement = (element.target ?? element).closest('[data-item-id]'); + if (listElement.dataset.isAction) return this.getAction(listElement); + + const itemId = listElement.dataset.itemId, item = this.document.items.get(itemId); return item; } + getAction(listElement) { + const target = listElement.dataset.partner === 'true' ? this.document.system.companion : this.document; + if (!target) return null; + + return target.system.actions.find(x => x.id === listElement.dataset.itemId); + } + static triggerContextMenu(event, button) { return CONFIG.ux.ContextMenu.triggerContextMenu(event); } 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'], @@ -616,10 +629,15 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } } - static async viewObject(event, button) { + static async viewObject(event) { const item = this.getItem(event); if (!item) return; - item.sheet.render(true); + + if (item instanceof DHBaseAction) { + new DHActionConfig(item).render({ force: true }); + } else { + item.sheet.render(true); + } } editItem(event) { @@ -672,10 +690,16 @@ 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(); + + if (item instanceof DHBaseAction) { + const newActions = item.parent.actions.filter(x => x.id !== item.id); + await item.parent.parent.update({ ['system.actions']: newActions }); + } else { + await item.delete(); + } } static async setItemQuantity(button, value) { @@ -711,7 +735,30 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) { } static async toChat(event, button) { - if (button?.dataset?.type === 'experience') { + const item = event.dataset ? event : button.closest(['[data-item-id']); + if (item?.dataset.isAction) { + const action = this.getAction(item); + const cls = getDocumentClass('ChatMessage'); + const systemData = { + title: action.name, + origin: this, + img: action.img, + name: action.name, + description: action.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()); + } else if (button?.dataset?.type === 'experience') { const experience = this.document.system.experiences[button.dataset.uuid]; const cls = getDocumentClass('ChatMessage'); const systemData = { diff --git a/module/applications/sheets/daggerheart-sheet.mjs b/module/applications/sheets/daggerheart-sheet.mjs index 4810b0a7..821972d9 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] : [], diff --git a/module/data/action/action.mjs b/module/data/action/action.mjs index aef15844..da776df8 100644 --- a/module/data/action/action.mjs +++ b/module/data/action/action.mjs @@ -130,7 +130,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() { diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index ec94ae1a..6b5a0877 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -1,4 +1,5 @@ import { burden } from '../../config/generalConfig.mjs'; +import ActionField from '../fields/actionField.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import DhLevelData from '../levelData.mjs'; import BaseDataActor from './base.mjs'; @@ -96,6 +97,7 @@ export default class DhCharacter extends BaseDataActor { value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }), subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }) }), + actions: new fields.ArrayField(new ActionField()), levelData: new fields.EmbeddedDataField(DhLevelData), bonuses: new fields.SchemaField({ armorScore: new fields.NumberField({ integer: true, initial: 0 }), @@ -155,6 +157,17 @@ export default class DhCharacter extends BaseDataActor { return this.parent.items.find(x => x.type === 'community') ?? null; } + // get actions() { + // const generalActions = []; // Add in things like Sprint etc + // const levelupActions = this.levelData.actions.filter(x => !x.partner).map(x => x.value); + + // return [...generalActions, ...levelupActions]; + // } + + get companionActions() { + return this.companion ? this.companion.system.actions : []; + } + get needsCharacterSetup() { return !this.class.value || !this.class.subclass; } diff --git a/module/data/actor/companion.mjs b/module/data/actor/companion.mjs index 55b740c8..fc4b8283 100644 --- a/module/data/actor/companion.mjs +++ b/module/data/actor/companion.mjs @@ -72,6 +72,7 @@ export default class DhCompanion extends BaseDataActor { } } }), + actions: new fields.ArrayField(new ActionField()), levelData: new fields.EmbeddedDataField(DhLevelData) }; } diff --git a/module/data/levelData.mjs b/module/data/levelData.mjs index 989ba973..afdfba78 100644 --- a/module/data/levelData.mjs +++ b/module/data/levelData.mjs @@ -42,7 +42,8 @@ export default class DhLevelData extends foundry.abstract.DataModel { 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 }) + itemUuid: new fields.StringField({ required: true }), + actionIds: new fields.ArrayField(new fields.StringField()) }) ) }) @@ -50,6 +51,10 @@ export default class DhLevelData extends foundry.abstract.DataModel { }; } + 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 919d25a1..04012852 100644 --- a/module/data/levelTier.mjs +++ b/module/data/levelTier.mjs @@ -65,14 +65,29 @@ export const CompanionLevelOptionType = { }, creatureComfort: { id: 'creatureComfort', - label: 'Creature Comfort' - // actions: [ - - // ], + label: 'Creature Comfort', + actions: [ + { + name: 'DAGGERHEART.LevelUp.Actions.CreatureComfort.Name', + img: 'icons/magic/life/heart-cross-purple-orange.webp', + type: 'attack', + actionType: 'passive', + description: 'DAGGERHEART.LevelUp.Actions.CreatureComfort.Description' + } + ] }, armored: { id: 'armored', - label: 'Armored' + label: 'Armored', + actions: [ + { + name: 'DAGGERHEART.LevelUp.Actions.Armored.Name', + img: 'icons/equipment/shield/kite-wooden-oak-glow.webp', + type: 'attack', + actionType: 'passive', + description: 'DAGGERHEART.LevelUp.Actions.Armored.Description' + } + ] }, vicious: { id: 'vicious', @@ -84,7 +99,16 @@ export const CompanionLevelOptionType = { }, bonded: { id: 'bonded', - label: 'Bonded' + label: 'Bonded', + actions: [ + { + name: 'DAGGERHEART.LevelUp.Actions.Bonded.Name', + img: 'icons/magic/life/heart-red-blue.webp', + type: 'attack', + actionType: 'passive', + description: 'DAGGERHEART.LevelUp.Actions.Bonded.Description' + } + ] }, aware: { id: 'aware', diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index ef7e47c9..e4e76015 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 { actionsTypes } from '../data/_module.mjs'; +import { LevelOptionType } from '../data/levelTier.mjs'; export default class DhpActor extends Actor { async _preCreate(data, options, user) { @@ -20,10 +22,6 @@ export default class DhpActor extends Actor { async updateLevel(newLevel) { if (!['character', 'companion'].includes(this.type) || newLevel === this.system.levelData.level.changed) return; - if (this.system.companion) { - this.system.companion.updateLevel(newLevel); - } - if (newLevel > this.system.levelData.level.current) { const maxLevel = Object.values( game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers).tiers @@ -122,10 +120,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]; @@ -150,6 +153,7 @@ export default class DhpActor extends Actor { } let multiclass = null; + const actionIds = []; const domainCards = []; const subclassFeatureState = { class: null, multiclass: null }; const selections = []; @@ -158,6 +162,27 @@ export default class DhpActor extends Actor { for (var checkboxNr of Object.keys(selection)) { const checkbox = selection[checkboxNr]; + const tierOption = LevelOptionType[checkbox.type]; + for (var actionData of tierOption.actions ?? []) { + const cls = actionsTypes[actionData.type]; + const actionId = foundry.utils.randomID(); + actionIds.push(actionId); + actions.push( + new cls( + { + // ...cls.getSourceConfig(target), + ...actionData, + _id: actionId, + name: game.i18n.localize(actionData.name), + description: game.i18n.localize(actionData.description) + }, + { + parent: this + } + ) + ); + } + if (checkbox.type === 'multiclass') { multiclass = { ...checkbox, @@ -255,6 +280,7 @@ export default class DhpActor extends Actor { await this.update({ system: { + actions: [...this.system.actions, ...actions], levelData: { level: { current: this.system.levelData.level.changed @@ -263,6 +289,10 @@ export default class DhpActor extends Actor { } } }); + + if (this.system.companion) { + this.system.companion.updateLevel(this.system.levelData.level.changed); + } } /** diff --git a/templates/sheets/actors/character/features.hbs b/templates/sheets/actors/character/features.hbs index b2851193..2cab4714 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.actions}} + {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize "DAGGERHEART.Sheets.PC.Actions") type='actions'}} + {{/if}} + {{#if document.system.companionActions}} + {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize "DAGGERHEART.Sheets.PC.CompanionActions") type='companionActions'}} + {{/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/global/partials/action-item.hbs b/templates/sheets/global/partials/action-item.hbs new file mode 100644 index 00000000..5259f31a --- /dev/null +++ b/templates/sheets/global/partials/action-item.hbs @@ -0,0 +1,16 @@ +