Reworked Action storage

This commit is contained in:
WBHarry 2025-06-30 23:01:50 +02:00
parent 3333a9e00a
commit ce593d02f3
16 changed files with 196 additions and 23 deletions

View file

@ -286,6 +286,7 @@ const preloadHandlebarsTemplates = async function () {
return foundry.applications.handlebars.loadTemplates([ return foundry.applications.handlebars.loadTemplates([
'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs', 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs',
'systems/daggerheart/templates/sheets/global/partials/inventory-item.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/domain-card-item.hbs',
'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs', 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs',

View file

@ -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.", "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." "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": { "Tier2": {
"Label": "Levels 2-4", "Label": "Levels 2-4",
"InfoLabel": "At Level 2, gain an additional Experience at +2 and gain a +1 bonus to your Proficiency.", "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", "CharacterSetup": "Character setup isn't done yet",
"Level": "Level", "Level": "Level",
"LevelUp": "You can level up", "LevelUp": "You can level up",
"Actions": "Actions",
"CompanionActions": "Companion Actions",
"Tabs": { "Tabs": {
"Features": "Features", "Features": "Features",
"Inventory": "Inventory", "Inventory": "Inventory",

View file

@ -143,7 +143,7 @@ export default class AncestrySelectionDialog extends HandlebarsApplicationMixin(
} }
static _onEditImage() { static _onEditImage() {
const fp = new FilePicker({ const fp = new foundry.applications.apps.FilePicker.implementation({
current: this.data.ancestryInfo.img, current: this.data.ancestryInfo.img,
type: 'image', type: 'image',
redirectToRoot: ['icons/svg/mystery-man.svg'], redirectToRoot: ['icons/svg/mystery-man.svg'],

View file

@ -160,7 +160,7 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
static onEditImage(_, target) { static onEditImage(_, target) {
const setting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath]; const setting = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Countdowns)[this.basePath];
const current = setting.countdowns[target.dataset.countdown].img; const current = setting.countdowns[target.dataset.countdown].img;
const fp = new FilePicker({ const fp = new foundry.applications.apps.FilePicker.implementation({
current, current,
type: 'image', type: 'image',
callback: async path => this.updateImage.bind(this)(path, target.dataset.countdown), callback: async path => this.updateImage.bind(this)(path, target.dataset.countdown),

View file

@ -81,7 +81,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
} }
static onEditImage() { static onEditImage() {
const fp = new FilePicker({ const fp = new foundry.applications.apps.FilePicker.implementation({
current: this.img, current: this.img,
type: 'image', type: 'image',
callback: async path => { callback: async path => {

View file

@ -6,6 +6,8 @@ import DaggerheartSheet from './daggerheart-sheet.mjs';
import { abilities } from '../../config/actorConfig.mjs'; import { abilities } from '../../config/actorConfig.mjs';
import DhCharacterlevelUp from '../levelup/characterLevelup.mjs'; import DhCharacterlevelUp from '../levelup/characterLevelup.mjs';
import DhCharacterCreation from '../characterCreation.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 { ActorSheetV2 } = foundry.applications.sheets;
const { TextEditor } = foundry.applications.ux; const { TextEditor } = foundry.applications.ux;
@ -51,6 +53,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
levelManagement: this.levelManagement, levelManagement: this.levelManagement,
editImage: this._onEditImage, editImage: this._onEditImage,
triggerContextMenu: this.triggerContextMenu triggerContextMenu: this.triggerContextMenu
// editAction: this.editAction,
}, },
window: { window: {
resizable: true resizable: true
@ -303,17 +306,27 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
} }
getItem(element) { 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); item = this.document.items.get(itemId);
return item; 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) { static triggerContextMenu(event, button) {
return CONFIG.ux.ContextMenu.triggerContextMenu(event); return CONFIG.ux.ContextMenu.triggerContextMenu(event);
} }
static _onEditImage() { static _onEditImage() {
const fp = new FilePicker({ const fp = new foundry.applications.apps.FilePicker.implementation({
current: this.document.img, current: this.document.img,
type: 'image', type: 'image',
redirectToRoot: ['icons/svg/mystery-man.svg'], 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); const item = this.getItem(event);
if (!item) return; if (!item) return;
item.sheet.render(true);
if (item instanceof DHBaseAction) {
new DHActionConfig(item).render({ force: true });
} else {
item.sheet.render(true);
}
} }
editItem(event) { editItem(event) {
@ -672,10 +690,16 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
this.render(); this.render();
} }
static async deleteItem(event, button) { static async deleteItem(event) {
const item = this.getItem(event); const item = this.getItem(event);
if (!item) return; 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) { static async setItemQuantity(button, value) {
@ -711,7 +735,30 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
} }
static async toChat(event, button) { 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 experience = this.document.system.experiences[button.dataset.uuid];
const cls = getDocumentClass('ChatMessage'); const cls = getDocumentClass('ChatMessage');
const systemData = { const systemData = {

View file

@ -38,7 +38,7 @@ export default function DhpApplicationMixin(Base) {
const attr = target.dataset.edit; const attr = target.dataset.edit;
const current = foundry.utils.getProperty(this.document, attr); const current = foundry.utils.getProperty(this.document, attr);
const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}; const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {};
const fp = new FilePicker({ const fp = new foundry.applications.apps.FilePicker.implementation({
current, current,
type: 'image', type: 'image',
redirectToRoot: img ? [img] : [], redirectToRoot: img ? [img] : [],

View file

@ -130,7 +130,11 @@ export class DHBaseAction extends foundry.abstract.DataModel {
} }
get actor() { 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() { get chatTemplate() {

View file

@ -1,4 +1,5 @@
import { burden } from '../../config/generalConfig.mjs'; import { burden } from '../../config/generalConfig.mjs';
import ActionField from '../fields/actionField.mjs';
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
import DhLevelData from '../levelData.mjs'; import DhLevelData from '../levelData.mjs';
import BaseDataActor from './base.mjs'; import BaseDataActor from './base.mjs';
@ -96,6 +97,7 @@ export default class DhCharacter extends BaseDataActor {
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }), value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }) subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
}), }),
actions: new fields.ArrayField(new ActionField()),
levelData: new fields.EmbeddedDataField(DhLevelData), levelData: new fields.EmbeddedDataField(DhLevelData),
bonuses: new fields.SchemaField({ bonuses: new fields.SchemaField({
armorScore: new fields.NumberField({ integer: true, initial: 0 }), 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; 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() { get needsCharacterSetup() {
return !this.class.value || !this.class.subclass; return !this.class.value || !this.class.subclass;
} }

View file

@ -72,6 +72,7 @@ export default class DhCompanion extends BaseDataActor {
} }
} }
}), }),
actions: new fields.ArrayField(new ActionField()),
levelData: new fields.EmbeddedDataField(DhLevelData) levelData: new fields.EmbeddedDataField(DhLevelData)
}; };
} }

View file

@ -42,7 +42,8 @@ export default class DhLevelData extends foundry.abstract.DataModel {
amount: new fields.NumberField({ integer: true }), amount: new fields.NumberField({ integer: true }),
data: new fields.ArrayField(new fields.StringField({ required: true })), data: new fields.ArrayField(new fields.StringField({ required: true })),
secondaryData: new fields.TypedObjectField(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() { get canLevelUp() {
return this.level.current < this.level.changed; return this.level.current < this.level.changed;
} }

View file

@ -65,14 +65,29 @@ export const CompanionLevelOptionType = {
}, },
creatureComfort: { creatureComfort: {
id: 'creatureComfort', id: 'creatureComfort',
label: 'Creature Comfort' label: 'Creature Comfort',
// actions: [ 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: { armored: {
id: '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: { vicious: {
id: 'vicious', id: 'vicious',
@ -84,7 +99,16 @@ export const CompanionLevelOptionType = {
}, },
bonded: { bonded: {
id: '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: { aware: {
id: 'aware', id: 'aware',

View file

@ -1,6 +1,8 @@
import DamageSelectionDialog from '../applications/damageSelectionDialog.mjs'; import DamageSelectionDialog from '../applications/damageSelectionDialog.mjs';
import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs'; import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs';
import DamageReductionDialog from '../applications/damageReductionDialog.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 { export default class DhpActor extends Actor {
async _preCreate(data, options, user) { async _preCreate(data, options, user) {
@ -20,10 +22,6 @@ export default class DhpActor extends Actor {
async updateLevel(newLevel) { async updateLevel(newLevel) {
if (!['character', 'companion'].includes(this.type) || newLevel === this.system.levelData.level.changed) return; 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) { if (newLevel > this.system.levelData.level.current) {
const maxLevel = Object.values( const maxLevel = Object.values(
game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.LevelTiers).tiers 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) { async levelUp(levelupData) {
const actions = [];
const levelups = {}; const levelups = {};
for (var levelKey of Object.keys(levelupData)) { for (var levelKey of Object.keys(levelupData)) {
const level = levelupData[levelKey]; const level = levelupData[levelKey];
@ -150,6 +153,7 @@ export default class DhpActor extends Actor {
} }
let multiclass = null; let multiclass = null;
const actionIds = [];
const domainCards = []; const domainCards = [];
const subclassFeatureState = { class: null, multiclass: null }; const subclassFeatureState = { class: null, multiclass: null };
const selections = []; const selections = [];
@ -158,6 +162,27 @@ export default class DhpActor extends Actor {
for (var checkboxNr of Object.keys(selection)) { for (var checkboxNr of Object.keys(selection)) {
const checkbox = selection[checkboxNr]; 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') { if (checkbox.type === 'multiclass') {
multiclass = { multiclass = {
...checkbox, ...checkbox,
@ -255,6 +280,7 @@ export default class DhpActor extends Actor {
await this.update({ await this.update({
system: { system: {
actions: [...this.system.actions, ...actions],
levelData: { levelData: {
level: { level: {
current: this.system.levelData.level.changed 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);
}
} }
/** /**

View file

@ -10,6 +10,12 @@
{{#if document.system.class.subclass}} {{#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'}} {{> '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}}
{{#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}} {{#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'}} {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(concat (localize 'TYPES.Item.community') ' - ' document.system.community.name) type='community'}}
{{/if}} {{/if}}

View file

@ -0,0 +1,16 @@
<li class="inventory-item" data-item-id="{{item.id}}" data-is-action="true" data-partner="{{partner}}">
<img src="{{item.img}}" class="item-img" data-action="useItem" />
<div class="item-label">
<div class="item-name">{{item.name}}</div>
{{!-- <div class="item-tags">
<div class="tag">
</div>
<div class="tag">
</div>
</div> --}}
</div>
<div class="controls">
<a data-action="toChat" data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToChat'}}"><i class="fa-regular fa-message"></i></a>
<a data-action="triggerContextMenu" data-tooltip="{{localize 'DAGGERHEART.Tooltip.moreOptions'}}"><i class="fa-solid fa-ellipsis-vertical"></i></a>
</div>
</li>

View file

@ -34,6 +34,16 @@
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=effect type=../type}} {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=effect type=../type}}
{{/if}} {{/if}}
{{/each}} {{/each}}
{{#each document.system.actions as |action|}}
{{#if (or (eq ../type 'actions'))}}
{{> 'systems/daggerheart/templates/sheets/global/partials/action-item.hbs' item=action}}
{{/if}}
{{/each}}
{{#each document.system.companionActions as |action|}}
{{#if (or (eq ../type 'companionActions'))}}
{{> 'systems/daggerheart/templates/sheets/global/partials/action-item.hbs' item=action partner=true}}
{{/if}}
{{/each}}
{{/unless}} {{/unless}}
</ul> </ul>