Fix conflicts & ContextMenu tweaks

This commit is contained in:
Dapoolp 2025-06-28 18:00:05 +02:00
commit 5f91fa153c
53 changed files with 1195 additions and 818 deletions

View file

@ -1,37 +1,8 @@
# Daggerheart # Daggerheart
#### For Foundry VTT This is a repo for a Foundry VTT implementation of daggerheart. It is not associated with Critical Role or Darrington Press.
This is a repo for a Foundry VTT implementation of daggerheart. It is not associated with critical role ## Setup
or darrington press.
# Table Of Contents
- [Overview](#overview)
- [Developer Guide](#developer-guide)
# Overview
# Developer Guide
#### Coding Practises
##### Style Code
The project is set up for Prettify. Make sure you've run `npm install` since it was added.
There is a pre-commit hook that will automatically run prettify on the files you've changed whenever you do a commit to maintain the formating.
##### Branches And Pull Requests
During pre-release development, we are making use of `main` as the development branch. Once release is getting closer we will instead be making a `dev` branch to base development from to make `main` more stable.
When you work on an issue or feature, start from `main` and make a new branch. Branches should be topically named and with the associated Issue number if it relates to an Issue. EX: `#6/Level-Up-Bugginess`.
---
Once you're finished with an issue or feature, open a Pull Request on Github for that branch.
The Reviewers Team will be approving submissions. This is mainly since we have a wide spread of experience with system building and the system itself, and we do want the system to become something great. As time goes on, more collaborators are likely to be added as reviewers.
#### Setup
- Open a terminal in the directory with the repo `cd <path>/<to>/<repo>` - Open a terminal in the directory with the repo `cd <path>/<to>/<repo>`
- 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: 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
@ -52,4 +23,8 @@ The Reviewers Team will be approving submissions. This is mainly since we have a
Now you should be able to build the app using `npm start` Now you should be able to build the app using `npm start`
[Foundry VTT Website][1] [Foundry VTT Website][1]
[1]: https://foundryvtt.com/ [1]: https://foundryvtt.com/
## 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.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="250" height="250" viewBox="0 0 250 250" fill="currentColor" class="size-9 fill-white"><path d="M0 0 C0.66 0 1.32 0 2 0 C14.95653339 24.10764819 13.9884387 54.46992152 6.36669922 80.13232422 C1.76723518 94.82649212 -5.79578042 108.47370088 -16 120 C-16.80953125 120.95261719 -17.6190625 121.90523438 -18.453125 122.88671875 C-28.60247649 134.60680321 -40.66794415 143.23056663 -54 151 C-55.11955078 151.69996094 -55.11955078 151.69996094 -56.26171875 152.4140625 C-58.125 153.5625 -58.125 153.5625 -61 155 C-61.99 154.67 -62.98 154.34 -64 154 C-63.40251953 152.61941406 -63.40251953 152.61941406 -62.79296875 151.2109375 C-56.7768737 136.97815308 -51.6657355 121.26039587 -57 106 C-60.48120097 98.84702136 -64.6826433 94.79935255 -72.0625 91.75 C-88.3228906 86.28650876 -105.49134889 92.96113448 -120.22265625 100.09326172 C-123.66221434 101.84800331 -126.94286036 103.83239918 -130.1796875 105.93359375 C-132 107 -132 107 -134 107 C-132.55707111 102.98041238 -130.58976472 99.68906021 -128.25 96.125 C-118.67991963 81.01400234 -112.10580278 62.4873375 -115.69140625 44.5390625 C-117.80982846 36.9340154 -120.91326446 29.9255418 -126.68359375 24.3828125 C-128 23 -128 23 -128 21 C-124.05427694 22.26781443 -120.25878223 23.68952262 -116.46484375 25.35546875 C-96.49510929 34.0279337 -76.49404751 42.47157226 -54.625 44.5625 C-53.42875 44.706875 -52.2325 44.85125 -51 45 C-50.67 45.66 -50.34 46.32 -50 47 C-62.21128151 51.78254962 -75.0566624 52.68675429 -88 54 C-65.79558738 58.54120367 -43.30887174 55.01823807 -24 43 C-9.34809271 32.73977845 -3.38234378 16.9117189 0 0 Z " transform="translate(214,14)"></path><path d="M0 0 C0 0.99 0 1.98 0 3 C-1.83305176 4.72038911 -3.65602234 6.26166017 -5.625 7.8125 C-17.58369873 17.60667592 -28.04628692 28.39916079 -37 41 C-37.40669922 41.55703613 -37.81339844 42.11407227 -38.23242188 42.68798828 C-54.64772896 65.26863886 -63.87307523 90.78795784 -64.625 118.6875 C-64.66367188 119.87794922 -64.70234375 121.06839844 -64.7421875 122.29492188 C-64.83540995 125.19647053 -64.92121348 128.09802966 -65 131 C-70.3234693 126.51007392 -75.44004606 121.84637416 -80.5 117.0625 C-81.58087891 116.04639648 -81.58087891 116.04639648 -82.68359375 115.00976562 C-86.09891453 111.78898843 -89.47748759 108.5423865 -92.78515625 105.2109375 C-95.09091176 102.88900936 -97.42581958 100.66411014 -99.9375 98.5625 C-100.948125 97.716875 -101.95875 96.87125 -103 96 C-99.61039817 85.4191987 -87.21339003 78.85910796 -78.28515625 73.12890625 C-75.79288666 71.51382072 -73.37586653 69.7818999 -71 68 C-76.94 69.32 -82.88 70.64 -89 72 C-81.30401881 56.41070477 -70.35848972 48.5065482 -55 41 C-60.0302076 41.52537724 -65.01885239 42.10594786 -70 43 C-58.18395484 23.47783843 -37.52357216 9.15963177 -15.58886719 3.36279297 C-10.41911552 2.10472045 -5.22203715 1.01689154 0 0 Z " transform="translate(127,111)"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.6 KiB

66
coc.md Normal file
View file

@ -0,0 +1,66 @@
# Code of Conduct
## 💚 Our Commitment
As contributors and maintainers of **Foundryborne**, we are dedicated to fostering a safe, inclusive, and welcoming environment for everyone — regardless of background, identity, or experience level.
We want this project to be a space where *everyone* feels comfortable sharing ideas, collaborating, and learning together.
---
## 🤝 Our Standards
We expect all participants in our community (including issues, pull requests, discussions, and other communications) to:
- Be respectful and considerate
- Use inclusive and welcoming language
- Assume good intentions and clarify misunderstandings
- Offer constructive feedback
- Accept feedback graciously
- Avoid demeaning, discriminatory, or harassing behavior or speech
Unacceptable behaviors include:
- Personal attacks
- Trolling, baiting, or inflammatory comments
- Harassment in any form (public or private)
- Publishing private information (doxing) without consent
- Disruptive behavior that derails conversations or community efforts
---
## 📣 Reporting Issues
If you experience or witness unacceptable behavior, please report it by contacting a maintainer privately. You can also open a confidential issue or reach out through our community platform (e.g., Discord or email — TBD).
We take all reports seriously and will treat them with the confidentiality and respect they deserve.
---
## 🛠 Enforcement
Project maintainers are responsible for clarifying standards of acceptable behavior and may take appropriate and fair corrective action in response to any instance of unacceptable behavior.
This may include warnings, temporary or permanent bans, or removing content that violates the Code of Conduct.
---
## 🌍 Scope
This Code of Conduct applies to:
- All project spaces, including GitHub repositories, discussions, issues, and pull requests
- Any official community channels associated with the project (e.g., Discord, forums, etc.)
- Both public and private interactions when representing the project
---
## 🙏 Attribution
This Code of Conduct is based on the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
For questions or feedback, feel free to open a discussion or contact the maintainers.
---
> Let's keep **Foundryborne** a friendly and collaborative space for TTRPG lovers, devs, and curious frogs alike. 🐸

72
contributing.md Normal file
View file

@ -0,0 +1,72 @@
# Contributing to Foundryborne
Welcome! This is a community-driven project to bring [Daggerheart](https://www.daggerheart.com/) to [FoundryVTT](https://foundryvtt.com/) as a full system. We're excited to have you here and appreciate your interest in contributing.
---
## 🤝 How to Contribute
We welcome contributions of all kinds:
- Bug reports
- Feature suggestions
- Code contributions
- UI/UX mockups
- Documentation improvements
- Questions and discussions
Please be respectful and collaborative — were all here to build something great together.
---
## 🧭 General Guidelines
- **Use GitHub Issues** to report bugs or propose features
- **Start a Discussion** for larger ideas or questions
- **Open a Pull Request** once you've confirmed your work aligns with project direction
- **Keep things modular and maintainable** — if you're not sure how to structure something, ask!
- **Orient your code on existing examples**, and feel free to suggest a standard if it makes things clearer
---
## 🗂️ Project Structure
Please try to follow the general logic of the existing code when submitting PRs.
We encourage contributors to leave comments or open Discussions when proposing structural or organizational changes.
---
## 🧾 Issue & PR Best Practices
**For Issues:**
- Use clear, descriptive titles
- Provide a concise explanation of the problem or idea
- Include reproduction steps or example scenarios if it's a bug
- Add screenshots or logs if helpful
**For Pull Requests:**
- Use a clear title summarizing the change
- Provide a brief description of what your code does and why
- Link to any related Issues
- Keep PRs focused — smaller is better
---
## 🔖 Labels and Boards
We use GitHub labels to help organize contributions. If your issue or PR relates to a specific category, feel free to tag it. There is also a GitHub Project Board to help track active work and priorities.
---
## 📣 Communication
Discussions are currently happening on GitHub — in Issues, PRs, and [GitHub Discussions](https://github.com/Foundryborne/daggerheart/discussions). You're welcome to use any of these, though we may consolidate to one in the future.
---
## 🤗 Thank You!
Whether you're fixing a typo or designing entire mechanics — every contribution matters. Thank you for helping bring *Daggerheart* to life in FoundryVTT through **Foundryborne**!
🐸🛠️

View file

@ -122,6 +122,7 @@ Hooks.once('init', () => {
CONFIG.Token.rulerClass = DhpTokenRuler; CONFIG.Token.rulerClass = DhpTokenRuler;
CONFIG.ui.resources = Resources; CONFIG.ui.resources = Resources;
CONFIG.ux.ContextMenu = applications.DhContextMenu;
game.socket.on(`system.${SYSTEM.id}`, handleSocketEvent); game.socket.on(`system.${SYSTEM.id}`, handleSocketEvent);

View file

@ -1106,6 +1106,13 @@
} }
}, },
"Sheets": { "Sheets": {
"TABS": {
"features": "Features",
"effects": "Effects",
"settings": "Settings",
"actions": "Actions",
"description": "Description"
},
"PC": { "PC": {
"Name": "Name", "Name": "Name",
"Pronouns": "Pronouns", "Pronouns": "Pronouns",
@ -1127,6 +1134,17 @@
"biography": "Biography", "biography": "Biography",
"effects": "Effects" "effects": "Effects"
}, },
"ContextMenu": {
"UseItem": "Use",
"Equip": "Equip",
"Unequip": "Unequip",
"Edit": "Edit",
"Delete": "Delete",
"ToLoadout": "Send to Loadout",
"ToVault": "Send to Vault",
"Consume": "Consume Item",
"SendToChat": "Send To Chat"
},
"Armor": { "Armor": {
"Title": "Active Armor" "Title": "Active Armor"
}, },
@ -1323,6 +1341,7 @@
"Severe": "Severe" "Severe": "Severe"
}, },
"Evasion": "Evasion", "Evasion": "Evasion",
"HitPoints": "Hit Points",
"ClassFeatures": "Class Features", "ClassFeatures": "Class Features",
"Subclasses": "Subclasses", "Subclasses": "Subclasses",
"Guide": { "Guide": {

View file

@ -13,3 +13,6 @@ export { default as DhpArmor } from './sheets/items/armor.mjs';
export { default as DhpChatMessage } from './chatMessage.mjs'; export { default as DhpChatMessage } from './chatMessage.mjs';
export { default as DhpEnvironment } from './sheets/environment.mjs'; export { default as DhpEnvironment } from './sheets/environment.mjs';
export { default as DhActiveEffectConfig } from './sheets/activeEffectConfig.mjs'; export { default as DhActiveEffectConfig } from './sheets/activeEffectConfig.mjs';
export { default as DhContextMenu } from './contextMenu.mjs';
export * as api from './sheets/api/_modules.mjs';

View file

@ -0,0 +1,35 @@
export default class DhContextMenu extends ContextMenu {
constructor(container, selector, menuItems, options) {
super(container, selector, menuItems, options);
/** @deprecated since v13 until v15 */
this.#jQuery = options.jQuery;
}
#jQuery;
activateListeners(menu) {
menu.addEventListener('click', this.#onClickItem.bind(this));
}
#onClickItem(event) {
event.preventDefault();
event.stopPropagation();
const element = event.target.closest('.context-item');
if (!element) return;
const item = this.menuItems.find(i => i.element === element);
item?.callback(this.#jQuery ? $(this.target) : this.target, event);
this.close();
}
static triggerContextMenu(event) {
event.preventDefault();
event.stopPropagation();
const { clientX, clientY } = event;
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
}));
}
}

View file

@ -48,8 +48,8 @@ class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
super._attachPartListeners(partId, htmlElement, options); super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelectorAll('.mini-countdown-container').forEach(element => { htmlElement.querySelectorAll('.mini-countdown-container').forEach(element => {
element.addEventListener('click', event => this.updateCountdownValue.bind(this)(event, true)); element.addEventListener('click', event => this.updateCountdownValue.bind(this)(event, false));
element.addEventListener('contextmenu', event => this.updateCountdownValue.bind(this)(event, false)); element.addEventListener('contextmenu', event => this.updateCountdownValue.bind(this)(event, true));
}); });
} }

View file

@ -0,0 +1,3 @@
export { default as DHApplicationMixin } from './application-mixin.mjs';
export { default as DHBaseItemSheet } from './base-item.mjs';
export { default as DHHeritageSheet } from './heritage-sheet.mjs';

View file

@ -0,0 +1,204 @@
const { HandlebarsApplicationMixin } = foundry.applications.api;
import { tagifyElement } from '../../../helpers/utils.mjs';
/**
* @typedef {object} DragDropConfig
* @property {string} [dragSelector] - A CSS selector that identifies draggable elements.
* @property {string} [dropSelector] - A CSS selector that identifies drop targets.
*
* @typedef {Object} TagOption
* @property {string} label
* @property {string} [src]
*
* @typedef {object} TagifyConfig
* @property {String} selector - The CSS selector for get the element to transform into a tag input
* @property {Record<string, TagOption> | (() => Record<string, TagOption>)} options - Available tag options as key-value pairs
* @property {TagChangeCallback} callback - Callback function triggered when tags change
* @property {TagifyOptions} [tagifyOptions={}] - Additional configuration for Tagify
*
* @callback TagChangeCallback
* @param {Array<{value: string, name: string, src?: string}>} selectedOptions - Current selected tags
* @param {{option: string, removed: boolean}} change - What changed (added/removed tag)
* @param {HTMLElement} inputElement - Original input element
*
*
* @typedef {Object} TagifyOptions
* @property {number} [maxTags] - Maximum number of allowed tags
*
* @typedef {import("@client/applications/api/handlebars-application.mjs").HandlebarsRenderOptions} HandlebarsRenderOptions
* @typedef {foundry.applications.types.ApplicationConfiguration & HandlebarsRenderOptions & { dragDrop?: DragDropConfig[], tagifyConfigs?: TagifyConfig[] }} DHSheetV2Configuration
*/
/**
* @template {Constructor<foundry.applications.api.DocumentSheet>} BaseDocumentSheet
* @param {BaseDocumentSheet} Base - The base class to extend.
* @returns {BaseDocumentSheet}
*/
export default function DHApplicationMixin(Base) {
class DHSheetV2 extends HandlebarsApplicationMixin(Base) {
/**
* @param {DHSheetV2Configuration} [options={}]
*/
constructor(options = {}) {
super(options);
/**
* @type {foundry.applications.ux.DragDrop[]}
* @private
*/
this._dragDrop = this._createDragDropHandlers();
}
/**
* The default options for the sheet.
* @type {DHSheetV2Configuration}
*/
static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'sheet', 'dh-style'],
position: {
width: 480,
height: 'auto'
},
actions: {
addEffect: DHSheetV2.#addEffect,
editEffect: DHSheetV2.#editEffect,
removeEffect: DHSheetV2.#removeEffect
},
dragDrop: [],
tagifyConfigs: []
};
/* -------------------------------------------- */
/**@inheritdoc */
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
this._dragDrop.forEach(d => d.bind(htmlElement));
}
/**@inheritdoc */
async _onRender(context, options) {
await super._onRender(context, options);
this._createTagifyElements(this.options.tagifyConfigs);
}
/**
* Creates Tagify elements from configuration objects
* @param {TagifyConfig[]} tagConfigs - Array of Tagify configuration objects
* @throws {TypeError} If tagConfigs is not an array
* @throws {Error} If required properties are missing in config objects
* @param {TagifyConfig[]} tagConfigs
*/
_createTagifyElements(tagConfigs) {
if (!Array.isArray(tagConfigs)) throw new TypeError('tagConfigs must be an array');
tagConfigs.forEach(config => {
try {
const { selector, options, callback, tagifyOptions = {} } = config;
// Validate required fields
if (!selector || !options || !callback) {
throw new Error('Invalid TagifyConfig - missing required properties', config);
}
// Find target element
const element = this.element.querySelector(selector);
if (!element) {
throw new Error(`Element not found with selector: ${selector}`);
}
// Resolve dynamic options if function provided
const resolvedOptions = typeof options === 'function' ? options.call(this) : options;
// Initialize Tagify
tagifyElement(element, resolvedOptions, callback.bind(this), tagifyOptions);
} catch (error) {
console.error('Error initializing Tagify:', error);
}
});
}
/* -------------------------------------------- */
/* Drag and Drop */
/* -------------------------------------------- */
/**
* Creates drag-drop handlers from the configured options.
* @returns {foundry.applications.ux.DragDrop[]}
* @private
*/
_createDragDropHandlers() {
return this.options.dragDrop.map(d => {
d.callbacks = {
drop: this._onDrop.bind(this)
};
return new foundry.applications.ux.DragDrop.implementation(d);
});
}
/**
* Handle drop event.
* @param {DragEvent} event
* @protected
*/
_onDrop(event) { }
/* -------------------------------------------- */
/* Prepare Context */
/* -------------------------------------------- */
/**
* Prepare the template context.
* @param {object} options
* @param {string} [objectPath='document']
* @returns {Promise<object>}
* @inheritdoc
*/
async _prepareContext(options, objectPath = 'document') {
const context = await super._prepareContext(options);
context.config = CONFIG.daggerheart;
context.source = this[objectPath];
context.fields = this[objectPath].schema.fields;
context.systemFields = this[objectPath].system ? this[objectPath].system.schema.fields : {};
return context;
}
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
/**
* Renders an ActiveEffect's sheet sheet.
* @param {PointerEvent} event - The originating click event
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
*/
static async #addEffect() {
const cls = foundry.documents.ActiveEffect;
await cls.create(
{
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize(cls.metadata.label) })
},
{ parent: this.document }
);
}
/**
* Renders an ActiveEffect's sheet sheet.
* @param {PointerEvent} event - The originating click event
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
*/
static async #editEffect(_event, button) {
const effect = this.document.effects.get(button.dataset.effect);
effect.sheet.render({ force: true });
}
/**
* Delete an ActiveEffect from the item.
* @param {PointerEvent} _event - The originating click event
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
*/
static async #removeEffect(_event, button) {
await this.document.effects.get(button.dataset.effect).delete();
}
}
return DHSheetV2;
}

View file

@ -0,0 +1,148 @@
import DHApplicationMixin from './application-mixin.mjs';
import { actionsTypes } from '../../../data/_module.mjs';
import DHActionConfig from '../../config/Action.mjs';
const { ItemSheetV2 } = foundry.applications.sheets;
/**
* A base item sheet extending {@link ItemSheetV2} via {@link DHApplicationMixin}
* @extends ItemSheetV2
* @mixes DHSheetV2
*/
export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
/** @inheritDoc */
static DEFAULT_OPTIONS = {
classes: ['item'],
position: { width: 600 },
form: {
submitOnChange: true
},
actions: {
addAction: DHBaseItemSheet.#addAction,
editAction: DHBaseItemSheet.#editAction,
removeAction: DHBaseItemSheet.#removeAction
}
};
/* -------------------------------------------- */
/** @inheritdoc */
static TABS = {
primary: {
tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'settings' }],
initial: 'description',
labelPrefix: 'DAGGERHEART.Sheets.TABS'
}
};
/* -------------------------------------------- */
/* Prepare Context */
/* -------------------------------------------- */
/**@inheritdoc */
async _preparePartContext(partId, context) {
await super._preparePartContext(partId, context);
const { TextEditor } = foundry.applications.ux;
switch (partId) {
case '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 */
/* -------------------------------------------- */
/**
* Render a dialog prompting the user to select an action type.
*
* @returns {Promise<object>} An object containing the selected action type.
*/
static async selectActionType() {
const content = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/views/actionType.hbs',
{ types: SYSTEM.ACTIONS.actionTypes }
),
title = 'Select Action Type', //useless var
type = 'form',
data = {}; //useless var
//TODO: use DialogV2
return Dialog.prompt({
title,
label: title,
content,
type, //this prop is useless
callback: html => {
const form = html[0].querySelector('form'),
fd = new foundry.applications.ux.FormDataExtended(form);
foundry.utils.mergeObject(data, fd.object, { inplace: true });
// if (!data.name?.trim()) data.name = game.i18n.localize(SYSTEM.ACTIONS.actionTypes[data.type].name);
return data;
},
rejectClose: false
});
}
/**
* Add a new action to the item, prompting the user for its type.
* @param {PointerEvent} _event - The originating click event
* @param {HTMLElement} _button - The capturing HTML element which defines the [data-action="addAction"]
*/
static async #addAction(_event, _button) {
const actionType = await DHBaseItemSheet.selectActionType();
try {
const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack,
action = new cls(
{
_id: foundry.utils.randomID(),
type: actionType.type,
name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name),
...cls.getSourceConfig(this.document)
},
{
parent: this.document
}
);
await this.document.update({ 'system.actions': [...this.document.system.actions, action] });
await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render({
force: true
});
} catch (error) {
console.log(error);
}
}
/**
* Edit an existing action on the item
* @param {PointerEvent} _event - The originating click event
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="editAction"]
*/
static async #editAction(_event, button) {
const action = this.document.system.actions[button.dataset.index];
await new DHActionConfig(action).render({ force: true });
}
/**
* Remove an action from the item.
* @param {PointerEvent} event - The originating click event
* @param {HTMLElement} button - The capturing HTML element which defines the [data-action="removeAction"]
*/
static async #removeAction(event, button) {
event.stopPropagation();
await this.document.update({
'system.actions': this.document.system.actions.filter(
(_, index) => index !== Number.parseInt(button.dataset.index)
)
});
}
}

View file

@ -0,0 +1,31 @@
import DHBaseItemSheet from './base-item.mjs';
export default class DHHeritageSheet extends DHBaseItemSheet {
/**@inheritdoc */
static DEFAULT_OPTIONS = {
position: { width: 450, height: 700 }
};
/**@override */
static PARTS = {
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
actions: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
scrollable: ['.actions']
},
effects: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
scrollable: ['.effects']
}
};
/** @override*/
static TABS = {
primary: {
tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'effects' }],
initial: 'description',
labelPrefix: 'DAGGERHEART.Sheets.TABS'
}
};
}

View file

@ -25,14 +25,13 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
toggleStress: this.toggleStress, toggleStress: this.toggleStress,
toggleHope: this.toggleHope, toggleHope: this.toggleHope,
toggleGold: this.toggleGold, toggleGold: this.toggleGold,
attackRoll: this.attackRoll, toggleLoadoutView: this.toggleLoadoutView,
useDomainCard: this.useDomainCard, useDomainCard: this.useDomainCard,
removeCard: this.removeDomainCard,
selectClass: this.selectClass, selectClass: this.selectClass,
selectSubclass: this.selectSubclass, selectSubclass: this.selectSubclass,
selectAncestry: this.selectAncestry, selectAncestry: this.selectAncestry,
selectCommunity: this.selectCommunity, selectCommunity: this.selectCommunity,
// viewObject: this.viewObject, viewObject: this.viewObject,
useItem: this.useItem, useItem: this.useItem,
useFeature: this.useFeature, useFeature: this.useFeature,
takeShortRest: this.takeShortRest, takeShortRest: this.takeShortRest,
@ -43,12 +42,14 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
makeDeathMove: this.makeDeathMove, makeDeathMove: this.makeDeathMove,
itemQuantityDecrease: (_, button) => this.setItemQuantity(button, -1), itemQuantityDecrease: (_, button) => this.setItemQuantity(button, -1),
itemQuantityIncrease: (_, button) => this.setItemQuantity(button, 1), itemQuantityIncrease: (_, button) => this.setItemQuantity(button, 1),
useAbility: this.useAbility, toChat: this.toChat,
useAdvancementCard: this.useAdvancementCard, useAdvancementCard: this.useAdvancementCard,
useAdvancementAbility: this.useAdvancementAbility, useAdvancementAbility: this.useAdvancementAbility,
toggleEquipItem: this.toggleEquipItem, toggleEquipItem: this.toggleEquipItem,
toggleVault: this.toggleVault,
levelManagement: this.levelManagement, levelManagement: this.levelManagement,
editImage: this._onEditImage editImage: this._onEditImage,
triggerContextMenu: this.triggerContextMenu
}, },
window: { window: {
resizable: true resizable: true
@ -58,11 +59,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
submitOnChange: true, submitOnChange: true,
closeOnSubmit: false closeOnSubmit: false
}, },
dragDrop: [ dragDrop: []
{ dragSelector: null, dropSelector: '.weapon-section' },
{ dragSelector: null, dropSelector: '.armor-section' },
{ dragSelector: '.item-list .item', dropSelector: null }
]
}; };
static PARTS = { static PARTS = {
@ -214,14 +211,96 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
return { primary: primaryTabs, secondary: secondaryTabs }; return { primary: primaryTabs, secondary: secondaryTabs };
} }
async _onFirstRender(context, options) {
await super._onFirstRender(context, options);
this._createContextMenues();
}
_createContextMenues() {
const allOptions = {
useItem: {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.UseItem',
icon: '<i class="fa-solid fa-burst"></i>',
condition: el => {
const item = this.getItem(el);
return !['class', 'subclass'].includes(item.type);
},
callback: (button, event) => this.constructor.useItem.bind(this)(event, button)
},
equip: {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Equip',
icon: '<i class="fa-solid fa-hands"></i>',
condition: el => {
const item = this.getItem(el);
return ['weapon', 'armor'].includes(item.type) && !item.system.equipped;
},
callback: this.constructor.toggleEquipItem.bind(this)
},
unequip: {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Unequip',
icon: '<i class="fa-solid fa-hands"></i>',
condition: el => {
const item = this.getItem(el);
return ['weapon', 'armor'].includes(item.type) && item.system.equipped;
},
callback: this.constructor.toggleEquipItem.bind(this)
},
sendToLoadout: {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToLoadout',
icon: '<i class="fa-solid fa-arrow-up"></i>',
condition: el => {
const item = this.getItem(el);
return ['domainCard'].includes(item.type) && item.system.inVault;
},
callback: this.constructor.toggleVault.bind(this)
},
sendToVault: {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToVault',
icon: '<i class="fa-solid fa-arrow-down"></i>',
condition: el => {
const item = this.getItem(el);
return ['domainCard'].includes(item.type) && !item.system.inVault;
},
callback: this.constructor.toggleVault.bind(this)
},
sendToChat: {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.SendToChat',
icon: '<i class="fa-regular fa-message"></i>',
callback: this.constructor.toChat.bind(this)
},
edit: {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Edit',
icon: '<i class="fa-solid fa-pen-to-square"></i>',
callback: this.constructor.viewObject.bind(this)
},
delete: {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Delete',
icon: '<i class="fa-solid fa-trash"></i>',
callback: this.constructor.deleteItem.bind(this)
}
};
this._createContextMenu(() => Object.values(allOptions), `[data-item-id]`, {
parentClassHooks: false,
fixed: true
});
}
_attachPartListeners(partId, htmlElement, options) { _attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options); super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelector('.level-value')?.addEventListener('change', this.onLevelChange.bind(this)); htmlElement.querySelector('.level-value')?.addEventListener('change', this.onLevelChange.bind(this));
// To Remove when ContextMenu Handler is made }
htmlElement
.querySelectorAll('[data-item-id]') getItem(element) {
.forEach(element => element.addEventListener('contextmenu', this.editItem.bind(this))); const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId,
item = this.document.items.get(itemId);
return item;
}
static triggerContextMenu(event, button) {
return CONFIG.ux.ContextMenu.triggerContextMenu(event);
} }
static _onEditImage() { static _onEditImage() {
@ -408,14 +487,10 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
await this.document.update({ [update]: newValue }); await this.document.update({ [update]: newValue });
} }
static async attackRoll(event, button) { static async toggleLoadoutView(_, button) {
const weapon = await fromUuid(button.dataset.weapon); const newAbilityView = !(button.dataset.value === 'true');
if (!weapon) return; await game.user.setFlag(SYSTEM.id, SYSTEM.FLAGS.displayDomainCardsAsList, newAbilityView);
this.render();
const wasUsed = await weapon.use(event);
if (wasUsed) {
Hooks.callAll(SYSTEM.HOOKS.characterAttack, {});
}
} }
static levelManagement() { static levelManagement() {
@ -439,8 +514,9 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
new DhlevelUp(this.document).render(true); new DhlevelUp(this.document).render(true);
} }
static async useDomainCard(_, button) { static async useDomainCard(event, button) {
const card = this.document.items.find(x => x.uuid === button.dataset.key); const card = this.getItem(event);
if (!card) return;
const cls = getDocumentClass('ChatMessage'); const cls = getDocumentClass('ChatMessage');
const systemData = { const systemData = {
@ -464,13 +540,6 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
cls.create(msg.toObject()); cls.create(msg.toObject());
} }
static async removeDomainCard(_, button) {
if (button.dataset.type === 'domainCard') {
const card = this.document.items.find(x => x.uuid === button.dataset.key);
await card.delete();
}
}
static async selectClass() { static async selectClass() {
(await game.packs.get('daggerheart.classes'))?.render(true); (await game.packs.get('daggerheart.classes'))?.render(true);
} }
@ -505,27 +574,23 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
(await game.packs.get('daggerheart.communities'))?.render(true); (await game.packs.get('daggerheart.communities'))?.render(true);
} }
static useItem(event) { static async useItem(event, button) {
const uuid = event.target.closest('[data-item-id]').dataset.itemId, const item = this.getItem(button);
item = this.document.items.find(i => i.uuid === uuid); if (!item) return;
item.use(event); const wasUsed = await item.use(event);
if (wasUsed && item.type === 'weapon') {
Hooks.callAll(SYSTEM.HOOKS.characterAttack, {});
}
} }
/* static async viewObject(_, button) { static async viewObject(event, button) {
const object = await fromUuid(button.dataset.value); const item = this.getItem(event);
if (!object) return; if (!item) return;
item.sheet.render(true);
const tab = button.dataset.tab; }
if (tab && object.sheet._tabs) object.sheet._tabs[0].active = tab;
if (object.sheet.editMode) object.sheet.editMode = false;
object.sheet.render(true);
} */
editItem(event) { editItem(event) {
const uuid = event.target.closest('[data-item-id]').dataset.itemId, const item = this.getItem(event);
item = this.document.items.find(i => i.uuid === uuid);
if (!item) return; if (!item) return;
if (item.sheet.editMode) item.sheet.editMode = false; if (item.sheet.editMode) item.sheet.editMode = false;
@ -566,33 +631,29 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
static async makeDeathMove() { static async makeDeathMove() {
if (this.document.system.resources.hitPoints.value === this.document.system.resources.hitPoints.max) { if (this.document.system.resources.hitPoints.value === this.document.system.resources.hitPoints.max) {
await new DhpDeathMove(this.document).render(true); await new DhpDeathMove(this.document).render(true);
await this.minimize();
} }
} }
async itemUpdate(event) {
const name = event.currentTarget.dataset.item;
const item = await fromUuid($(event.currentTarget).closest('[data-item-id]')[0].dataset.itemId);
await item.update({ [name]: event.currentTarget.value });
}
async onLevelChange(event) { async onLevelChange(event) {
await this.document.updateLevel(Number(event.currentTarget.value)); await this.document.updateLevel(Number(event.currentTarget.value));
this.render(); this.render();
} }
static async deleteItem(_, button) { static async deleteItem(event, button) {
const item = await fromUuid($(button).closest('[data-item-id]')[0].dataset.itemId); const item = this.getItem(event);
if (!item) return;
await item.delete(); await item.delete();
} }
static async setItemQuantity(button, value) { static async setItemQuantity(button, value) {
const item = await fromUuid($(button).closest('[data-item-id]')[0].dataset.itemId); const item = this.getItem(button);
if (!item) return;
await item.update({ 'system.quantity': Math.max(item.system.quantity + value, 1) }); await item.update({ 'system.quantity': Math.max(item.system.quantity + value, 1) });
} }
static async useFeature(_, button) { static async useFeature(event, button) {
const item = await fromUuid(button.dataset.id); const item = this.getItem(event);
if (!item) return;
const cls = getDocumentClass('ChatMessage'); const cls = getDocumentClass('ChatMessage');
const systemData = { const systemData = {
@ -616,35 +677,30 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
cls.create(msg.toObject()); cls.create(msg.toObject());
} }
static async useAbility(_, button) { static async toChat(event, button) {
const item = await fromUuid(button.dataset.feature); if (button?.dataset?.type === 'experience') {
const type = button.dataset.type; 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.description} ${experience.total < 0 ? experience.total : `+${experience.total}`}`
};
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
)
});
const cls = getDocumentClass('ChatMessage'); cls.create(msg.toObject());
const systemData = { } else {
title: const item = this.getItem(event);
type === 'ancestry' if (!item) return;
? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.AncestryTitle') item.toChat(this.document.id);
: type === 'community' }
? game.i18n.localize('DAGGERHEART.Chat.FoundationCard.CommunityTitle')
: game.i18n.localize('DAGGERHEART.Chat.FoundationCard.SubclassFeatureTitle'),
origin: this.document.id,
img: item.img,
name: item.name,
description: item.system.description,
actions: []
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/ability-use.hbs',
systemData
)
});
cls.create(msg.toObject());
} }
static async useAdvancementCard(_, button) { static async useAdvancementCard(_, button) {
@ -699,8 +755,9 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
cls.create(msg.toObject()); cls.create(msg.toObject());
} }
static async toggleEquipItem(_, button) { static async toggleEquipItem(event, button) {
const item = this.document.items.get(button.id); const item = this.getItem(event);
if (!item) return;
if (item.system.equipped) { if (item.system.equipped) {
await item.update({ 'system.equipped': false }); await item.update({ 'system.equipped': false });
return; return;
@ -724,6 +781,13 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
this.render(); this.render();
} }
static async toggleVault(event, button) {
const itemId = event.target.closest('[data-item-id]').dataset.itemId,
item = this.document.items.get(itemId);
if (!item) return;
await item.update({ 'system.inVault': !item.system.inVault });
}
async _onDragStart(_, event) { async _onDragStart(_, event) {
super._onDragStart(event); super._onDragStart(event);
} }

View file

@ -1,130 +0,0 @@
import DhpApplicationMixin from './daggerheart-sheet.mjs';
import DHActionConfig from '../config/Action.mjs';
import { actionsTypes } from '../../data/_module.mjs';
export default function DHItemMixin(Base) {
return class DHItemSheetV2 extends DhpApplicationMixin(Base) {
constructor(options = {}) {
super(options);
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'sheet', 'item', 'dh-style'],
position: { width: 600 },
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
},
actions: {
addAction: this.addAction,
editAction: this.editAction,
removeAction: this.removeAction
}
};
static TABS = {
description: {
active: true,
cssClass: '',
group: 'primary',
id: 'description',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Description'
},
actions: {
active: false,
cssClass: '',
group: 'primary',
id: 'actions',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Actions'
},
settings: {
active: false,
cssClass: '',
group: 'primary',
id: 'settings',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.config = CONFIG.daggerheart;
context.tabs = super._getTabs(this.constructor.TABS);
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
}
static async selectActionType() {
const content = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/views/actionType.hbs',
{ types: SYSTEM.ACTIONS.actionTypes }
),
title = 'Select Action Type',
type = 'form',
data = {};
return Dialog.prompt({
title,
label: title,
content,
type,
callback: html => {
const form = html[0].querySelector('form'),
fd = new foundry.applications.ux.FormDataExtended(form);
foundry.utils.mergeObject(data, fd.object, { inplace: true });
// if (!data.name?.trim()) data.name = game.i18n.localize(SYSTEM.ACTIONS.actionTypes[data.type].name);
return data;
},
rejectClose: false
});
}
static async addAction() {
const actionType = await DHItemSheetV2.selectActionType();
try {
const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack,
action = new cls(
{
_id: foundry.utils.randomID(),
type: actionType.type,
name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name),
...cls.getSourceConfig(this.document)
},
{
parent: this.document
}
);
await this.document.update({ 'system.actions': [...this.document.system.actions, action] });
await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render(true);
} catch (error) {
console.log(error);
}
}
static async editAction(event, button) {
const index = event.target.closest('[data-index]').dataset.index,
action = this.document.system.actions[index];
await new DHActionConfig(action).render(true);
}
static async removeAction(event, button) {
event.stopPropagation();
const action = event.target.closest('[data-index]').dataset.index;
await this.document.update({
'system.actions': this.document.system.actions.filter(
(_, index) => index !== Number.parseInt(action)
)
});
}
};
}

View file

@ -1,11 +1,12 @@
import DHHeritageSheetV2 from './heritage.mjs'; import DHHeritageSheet from '../api/heritage-sheet.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; export default class AncestrySheet extends DHHeritageSheet {
export default class AncestrySheet extends DHHeritageSheetV2(ItemSheetV2) { /**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['ancestry'] classes: ['ancestry']
}; };
/**@inheritdoc */
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/ancestry/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/ancestry/header.hbs' },
...super.PARTS ...super.PARTS

View file

@ -1,14 +1,20 @@
import { armorFeatures } from '../../../config/itemConfig.mjs'; import DHBaseItemSheet from '../api/base-item.mjs';
import { tagifyElement } from '../../../helpers/utils.mjs';
import DHItemSheetV2 from '../item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; export default class ArmorSheet extends DHBaseItemSheet {
export default class ArmorSheet extends DHItemSheetV2(ItemSheetV2) { /**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['armor'], classes: ['armor'],
dragDrop: [{ dragSelector: null, dropSelector: null }] dragDrop: [{ dragSelector: null, dropSelector: null }],
tagifyConfigs: [
{
selector: '.features-input',
options: () => CONFIG.daggerheart.ITEM.armorFeatures,
callback: ArmorSheet.#onFeatureSelect
}
]
}; };
/**@override */
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/armor/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/armor/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
@ -23,8 +29,9 @@ export default class ArmorSheet extends DHItemSheetV2(ItemSheetV2) {
} }
}; };
/**@inheritdoc */
async _preparePartContext(partId, context) { async _preparePartContext(partId, context) {
super._preparePartContext(partId, context); await super._preparePartContext(partId, context);
switch (partId) { switch (partId) {
case 'settings': case 'settings':
@ -35,15 +42,11 @@ export default class ArmorSheet extends DHItemSheetV2(ItemSheetV2) {
return context; return context;
} }
_attachPartListeners(partId, htmlElement, options) { /**
super._attachPartListeners(partId, htmlElement, options); * Callback function used by `tagifyElement`.
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
const featureInput = htmlElement.querySelector('.features-input'); */
tagifyElement(featureInput, armorFeatures, this.onFeatureSelect.bind(this)); static async #onFeatureSelect(selectedOptions) {
} await this.document.update({ 'system.features': selectedOptions.map(x => ({ value: x.value })) });
async onFeatureSelect(features) {
await this.document.update({ 'system.features': features.map(x => ({ value: x.value })) });
this.render(true);
} }
} }

View file

@ -1,33 +1,29 @@
import DHBaseItemSheet from '../api/base-item.mjs';
import { actionsTypes } from '../../../data/_module.mjs'; import { actionsTypes } from '../../../data/_module.mjs';
import { tagifyElement } from '../../../helpers/utils.mjs';
import DHActionConfig from '../../config/Action.mjs'; import DHActionConfig from '../../config/Action.mjs';
import DaggerheartSheet from '../daggerheart-sheet.mjs';
const { ItemSheetV2 } = foundry.applications.sheets;
const { TextEditor } = foundry.applications.ux; const { TextEditor } = foundry.applications.ux;
export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) { export default class ClassSheet extends DHBaseItemSheet {
/**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['class'],
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'class'],
position: { width: 700 }, position: { width: 700 },
actions: { actions: {
removeSubclass: this.removeSubclass, removeItemFromCollection: ClassSheet.#removeItemFromCollection,
viewSubclass: this.viewSubclass, removeSuggestedItem: ClassSheet.#removeSuggestedItem,
viewDoc: ClassSheet.#viewDoc,
addFeature: this.addFeature, addFeature: this.addFeature,
editFeature: this.editFeature, editFeature: this.editFeature,
deleteFeature: this.deleteFeature, deleteFeature: this.deleteFeature
removeItem: this.removeItem,
viewItem: this.viewItem,
removePrimaryWeapon: this.removePrimaryWeapon,
removeSecondaryWeapon: this.removeSecondaryWeapon,
removeArmor: this.removeArmor
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}, },
tagifyConfigs: [
{
selector: '.domain-input',
options: () => CONFIG.daggerheart.DOMAIN.domains,
callback: ClassSheet.#onDomainSelect
}
],
dragDrop: [ dragDrop: [
{ dragSelector: '.suggested-item', dropSelector: null }, { dragSelector: '.suggested-item', dropSelector: null },
{ dragSelector: null, dropSelector: '.take-section' }, { dragSelector: null, dropSelector: '.take-section' },
@ -40,9 +36,11 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) {
] ]
}; };
/**@override */
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/class/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/class/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
features: { features: {
template: 'systems/daggerheart/templates/sheets/items/class/features.hbs', template: 'systems/daggerheart/templates/sheets/items/class/features.hbs',
scrollable: ['.features'] scrollable: ['.features']
@ -53,170 +51,33 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) {
} }
}; };
/** @inheritdoc */
static TABS = { static TABS = {
features: { primary: {
active: true, tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }],
cssClass: '', initial: 'description',
group: 'primary', labelPrefix: 'DAGGERHEART.Sheets.TABS'
id: 'features',
icon: null,
label: 'DAGGERHEART.Sheets.Class.Tabs.Features'
},
settings: {
active: false,
cssClass: '',
group: 'primary',
id: 'settings',
icon: null,
label: 'DAGGERHEART.Sheets.Class.Tabs.settings'
} }
}; };
_attachPartListeners(partId, htmlElement, options) { /**@inheritdoc */
super._attachPartListeners(partId, htmlElement, options);
const domainInput = htmlElement.querySelector('.domain-input');
tagifyElement(domainInput, SYSTEM.DOMAIN.domains, this.onDomainSelect.bind(this));
}
async _prepareContext(_options) { async _prepareContext(_options) {
const context = await super._prepareContext(_options); const context = await super._prepareContext(_options);
context.document = this.document;
context.tabs = super._getTabs(this.constructor.TABS);
context.domains = this.document.system.domains; context.domains = this.document.system.domains;
return context; return context;
} }
static async updateForm(event, _, formData) { /* -------------------------------------------- */
await this.document.update(formData.object);
this.render(); /**
* Callback function used by `tagifyElement`.
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
*/
static async #onDomainSelect(selectedOptions) {
await this.document.update({ 'system.domains': selectedOptions.map(x => x.value) });
} }
onAddTag(e) { /* -------------------------------------------- */
if (e.detail.index === 2) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.ClassCanOnlyHaveTwoDomains'));
}
}
async onDomainSelect(domains) {
await this.document.update({ 'system.domains': domains.map(x => x.value) });
this.render(true);
}
static async removeSubclass(_, button) {
await this.document.update({
'system.subclasses': this.document.system.subclasses.filter(x => x.uuid !== button.dataset.subclass)
});
}
static async viewSubclass(_, button) {
const subclass = await fromUuid(button.dataset.subclass);
subclass.sheet.render(true);
}
static async deleteFeature(_, button) {
await this.document.update({
'system.features': this.document.system.features.map(x => x.uuid).filter(x => x !== button.dataset.feature)
});
}
static async editFeature(_, button) {
const feature = await fromUuid(button.dataset.feature);
feature.sheet.render(true);
}
static async removeItem(event, button) {
event.stopPropagation();
const type = button.dataset.type;
const path = `system.inventory.${type}`;
await this.document.update({
[path]: this.document.system.inventory[type].filter(x => x.uuid !== button.dataset.item)
});
}
static async viewItem(_, button) {
const item = await fromUuid(button.dataset.item);
item.sheet.render(true);
}
static async removePrimaryWeapon(event) {
event.stopPropagation();
await this.document.update({ 'system.characterGuide.suggestedPrimaryWeapon': null }, { diff: false });
}
static async removeSecondaryWeapon(event) {
event.stopPropagation();
await this.document.update({ 'system.characterGuide.suggestedSecondaryWeapon': null }, { diff: false });
}
static async removeArmor(event) {
event.stopPropagation();
await this.document.update({ 'system.characterGuide.suggestedArmor': null }, { diff: false });
}
async selectActionType() {
const content = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/views/actionType.hbs',
{ types: SYSTEM.ACTIONS.actionTypes }
),
title = 'Select Action Type',
type = 'form',
data = {};
return Dialog.prompt({
title,
label: title,
content,
type,
callback: html => {
const form = html[0].querySelector('form'),
fd = new foundry.applications.ux.FormDataExtended(form);
foundry.utils.mergeObject(data, fd.object, { inplace: true });
return data;
},
rejectClose: false
});
}
getActionPath(type) {
return type === 'hope' ? 'hopeFeatures' : 'classFeatures';
}
static async addFeature(_, target) {
const actionPath = this.getActionPath(target.dataset.type);
const actionType = await this.selectActionType();
const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack,
action = new cls(
{
_id: foundry.utils.randomID(),
systemPath: actionPath,
type: actionType.type,
name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name),
...cls.getSourceConfig(this.document)
},
{
parent: this.document
}
);
await this.document.update({ [`system.${actionPath}`]: [...this.document.system[actionPath], action] });
}
static async editFeature(_, target) {
const action = this.document.system[this.getActionPath(target.dataset.type)].find(
x => x._id === target.dataset.feature
);
await new DHActionConfig(action).render(true);
}
static async deleteFeature(_, target) {
const actionPath = this.getActionPath(target.dataset.type);
await this.document.update({
[`system.${actionPath}`]: this.document.system[actionPath].filter(
action => action._id !== target.dataset.feature
)
});
}
async _onDrop(event) { async _onDrop(event) {
const data = TextEditor.getDragEventData(event); const data = TextEditor.getDragEventData(event);
@ -272,4 +133,107 @@ export default class ClassSheet extends DaggerheartSheet(ItemSheetV2) {
} }
} }
} }
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
/**
* Removes an item from an class collection by UUID.
* @param {PointerEvent} event - The originating click event
* @param {HTMLElement} element - The capturing HTML element which defines the [data-action="removeItemFromCollection"]
*/
static async #removeItemFromCollection(_event, element) {
const { uuid, target } = element.dataset;
const prop = foundry.utils.getProperty(this.document.system, target);
await this.document.update({ [target]: prop.filter(i => i.uuid !== uuid) });
}
/**
* Removes an suggested item from the class.
* @param {PointerEvent} _event - The originating click event
* @param {HTMLElement} element - The capturing HTML element which defines the [data-action="removeSuggestedItem"]
*/
static async #removeSuggestedItem(_event, element) {
const { target } = element.dataset;
await this.document.update({ [`system.characterGuide.${target}`]: null });
}
/**
* Open the sheet of a item by UUID.
* @param {PointerEvent} _event -
* @param {HTMLElement} button
*/
static async #viewDoc(_event, button) {
const doc = await fromUuid(button.dataset.uuid);
doc.sheet.render({ force: true });
}
//TODO: redo this
async selectActionType() {
const content = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/views/actionType.hbs',
{ types: SYSTEM.ACTIONS.actionTypes }
),
title = 'Select Action Type',
type = 'form',
data = {};
return Dialog.prompt({
title,
label: title,
content,
type,
callback: html => {
const form = html[0].querySelector('form'),
fd = new foundry.applications.ux.FormDataExtended(form);
foundry.utils.mergeObject(data, fd.object, { inplace: true });
return data;
},
rejectClose: false
});
}
//TODO: redo this
getActionPath(type) {
return type === 'hope' ? 'hopeFeatures' : 'classFeatures';
}
//TODO: redo this
static async addFeature(_, target) {
const actionPath = this.getActionPath(target.dataset.type);
const actionType = await this.selectActionType();
const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack,
action = new cls(
{
_id: foundry.utils.randomID(),
systemPath: actionPath,
type: actionType.type,
name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name),
...cls.getSourceConfig(this.document)
},
{
parent: this.document
}
);
await this.document.update({ [`system.${actionPath}`]: [...this.document.system[actionPath], action] });
}
//TODO: redo this
static async editFeature(_, target) {
const action = this.document.system[this.getActionPath(target.dataset.type)].find(
x => x._id === target.dataset.feature
);
await new DHActionConfig(action).render(true);
}
//TODO: redo this
static async deleteFeature(_, target) {
const actionPath = this.getActionPath(target.dataset.type);
await this.document.update({
[`system.${actionPath}`]: this.document.system[actionPath].filter(
action => action._id !== target.dataset.feature
)
});
}
} }

View file

@ -1,11 +1,12 @@
import DHHeritageSheetV2 from './heritage.mjs'; import DHHeritageSheet from '../api/heritage-sheet.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; export default class CommunitySheet extends DHHeritageSheet {
export default class CommunitySheet extends DHHeritageSheetV2(ItemSheetV2) { /**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['community'] classes: ['community']
}; };
/**@inheritdoc */
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/community/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/community/header.hbs' },
...super.PARTS ...super.PARTS

View file

@ -1,12 +1,13 @@
import DHItemSheetV2 from '../item.mjs'; import DHBaseItemSheet from '../api/base-item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; export default class ConsumableSheet extends DHBaseItemSheet {
export default class ConsumableSheet extends DHItemSheetV2(ItemSheetV2) { /**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['consumable'], classes: ['consumable'],
position: { width: 550 } position: { width: 550 }
}; };
/**@override */
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/consumable/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/consumable/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },

View file

@ -1,59 +1,37 @@
import DHItemSheetV2 from '../item.mjs'; import DHBaseItemSheet from '../api/base-item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; export default class DomainCardSheet extends DHBaseItemSheet {
export default class DomainCardSheet extends DHItemSheetV2(ItemSheetV2) { /**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['domain-card'], classes: ['domain-card'],
position: { width: 450, height: 700 }, position: { width: 450, height: 700 }
actions: { };
addEffect: this.addEffect,
editEffect: this.editEffect, /** @override */
removeEffect: this.removeEffect static TABS = {
primary: {
tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'settings' }, { id: 'effects' }],
initial: 'description',
labelPrefix: 'DAGGERHEART.Sheets.TABS'
} }
}; };
/**@override */
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/domainCard/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/domainCard/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' }, description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
settings: {
template: 'systems/daggerheart/templates/sheets/items/domainCard/settings.hbs',
scrollable: ['.settings']
},
actions: { actions: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs', template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
scrollable: ['.actions'] scrollable: ['.actions']
}, },
settings: {
template: 'systems/daggerheart/templates/sheets/items/domainCard/settings.hbs',
scrollable: ['.settings']
},
effects: { effects: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs', template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
scrollable: ['.effects'] scrollable: ['.effects']
} }
}; };
static TABS = {
...super.TABS,
effects: {
active: false,
cssClass: '',
group: 'primary',
id: 'effects',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Effects'
}
};
static async addEffect() {
await this.document.createEmbeddedDocuments('ActiveEffect', [
{ name: game.i18n.localize('DAGGERHEART.Feature.NewEffect') }
]);
}
static async editEffect(_, target) {
const effect = this.document.effects.get(target.dataset.effect);
effect.sheet.render(true);
}
static async removeEffect(_, target) {
await this.document.effects.get(target.dataset.effect).delete();
}
} }

View file

@ -1,17 +1,11 @@
import DHItemSheetV2 from '../item.mjs'; import DHBaseItemSheet from '../api/base-item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets;
export default class FeatureSheet extends DHItemSheetV2(ItemSheetV2) {
constructor(options = {}) {
super(options);
this.selectedEffectType = null;
}
export default class FeatureSheet extends DHBaseItemSheet {
/** @inheritDoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
id: 'daggerheart-feature', id: 'daggerheart-feature',
classes: ['feature'], classes: ['feature'],
position: { width: 600, height: 600 }, position: { height: 600 },
window: { resizable: true }, window: { resizable: true },
actions: { actions: {
addEffect: this.addEffect, addEffect: this.addEffect,
@ -19,6 +13,7 @@ export default class FeatureSheet extends DHItemSheetV2(ItemSheetV2) {
} }
}; };
/**@override */
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/feature/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/feature/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
@ -37,58 +32,86 @@ export default class FeatureSheet extends DHItemSheetV2(ItemSheetV2) {
} }
}; };
/**
* Internally tracks the selected effect type from the select.
* @type {String}
* @private
*/
_selectedEffectType;
/**@override */
static TABS = { static TABS = {
...super.TABS, primary: {
effects: { tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'settings' }, { id: 'effects' }],
active: false, initial: 'description',
cssClass: '', labelPrefix: 'DAGGERHEART.Sheets.TABS'
group: 'primary',
id: 'effects',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Effects'
} }
}; };
/* -------------------------------------------- */
/**@inheritdoc*/
_attachPartListeners(partId, htmlElement, options) { _attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options); super._attachPartListeners(partId, htmlElement, options);
$(htmlElement).find('.effect-select').on('change', this.effectSelect.bind(this)); 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) { async _prepareContext(_options) {
const context = await super._prepareContext(_options); const context = await super._prepareContext(_options);
context.document = this.document; context.properties = CONFIG.daggerheart.ACTOR.featureProperties;
context.tabs = super._getTabs(this.constructor.TABS); context.dice = CONFIG.daggerheart.GENERAL.diceTypes;
context.generalConfig = SYSTEM.GENERAL; context.effectConfig = CONFIG.daggerheart.EFFECTS;
context.itemConfig = SYSTEM.ITEM;
context.properties = SYSTEM.ACTOR.featureProperties; context.selectedEffectType = this._selectedEffectType;
context.dice = SYSTEM.GENERAL.diceTypes;
context.selectedEffectType = this.selectedEffectType;
context.effectConfig = SYSTEM.EFFECTS;
return context; return context;
} }
effectSelect(event) { /* -------------------------------------------- */
this.selectedEffectType = event.currentTarget.value; /* Application Clicks Actions */
this.render(true); /* -------------------------------------------- */
}
static async addEffect() { /**
if (!this.selectedEffectType) return; * Adds a new effect to the item, based on the selected effect type.
* @param {PointerEvent} _event - The originating click event
const { id, name, ...rest } = SYSTEM.EFFECTS.effectTypes[this.selectedEffectType]; * @param {HTMLElement} _target - The capturing HTML element which defines the [data-action]
const update = { * @returns
[foundry.utils.randomID()]: { */
type: this.selectedEffectType, 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: '', value: '',
...rest ...rest
} }
}; });
await this.item.update({ 'system.effects': update });
} }
static async removeEffect(_, button) { /**
const path = `system.effects.-=${button.dataset.effect}`; * 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 }); await this.item.update({ [path]: null });
} }
} }

View file

@ -1,147 +0,0 @@
import { actionsTypes } from '../../../data/_module.mjs';
import DHActionConfig from '../../config/Action.mjs';
import DHItemMixin from '../item.mjs';
export default function DHHeritageMixin(Base) {
return class DHHeritageSheetV2 extends DHItemMixin(Base) {
static DEFAULT_OPTIONS = {
tag: 'form',
position: { width: 450, height: 700 },
actions: {
addAction: this.addAction,
editAction: this.editAction,
removeAction: this.removeAction,
addEffect: this.addEffect,
editEffect: this.editEffect,
removeEffect: this.removeEffect
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
}
};
static PARTS = {
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
actions: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
scrollable: ['.actions']
},
effects: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
scrollable: ['.effects']
}
};
static TABS = {
description: {
active: true,
cssClass: '',
group: 'primary',
id: 'description',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Description'
},
actions: {
active: false,
cssClass: '',
group: 'primary',
id: 'actions',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Actions'
},
effects: {
active: false,
cssClass: '',
group: 'primary',
id: 'effects',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Effects'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.document = this.document;
context.tabs = super._getTabs(this.constructor.TABS);
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
}
async selectActionType() {
const content = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/views/actionType.hbs',
{ types: SYSTEM.ACTIONS.actionTypes }
),
title = 'Select Action Type',
type = 'form',
data = {};
return Dialog.prompt({
title,
label: title,
content,
type,
callback: html => {
const form = html[0].querySelector('form'),
fd = new foundry.applications.ux.FormDataExtended(form);
foundry.utils.mergeObject(data, fd.object, { inplace: true });
return data;
},
rejectClose: false
});
}
static async addAction() {
const actionType = await this.selectActionType();
const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack,
action = new cls(
{
_id: foundry.utils.randomID(),
type: actionType.type,
name: game.i18n.localize(SYSTEM.ACTIONS.actionTypes[actionType.type].name),
...cls.getSourceConfig(this.document)
},
{
parent: this.document
}
);
await this.document.update({ 'system.actions': [...this.document.system.actions, action] });
}
static async editAction(_, button) {
const action = this.document.system.actions[button.dataset.index];
await new DHActionConfig(action).render(true);
}
static async removeAction(event, button) {
event.stopPropagation();
await this.document.update({
'system.actions': this.document.system.actions.filter(
(_, index) => index !== Number.parseInt(button.dataset.index)
)
});
}
static async addEffect() {
await this.document.createEmbeddedDocuments('ActiveEffect', [
{ name: game.i18n.localize('DAGGERHEART.Feature.NewEffect') }
]);
}
static async editEffect(_, target) {
const effect = this.document.effects.get(target.dataset.effect);
effect.sheet.render(true);
}
static async removeEffect(_, target) {
await this.document.effects.get(target.dataset.effect).delete();
}
};
}

View file

@ -1,12 +1,13 @@
import DHItemSheetV2 from '../item.mjs'; import DHBaseItemSheet from '../api/base-item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; export default class MiscellaneousSheet extends DHBaseItemSheet {
export default class MiscellaneousSheet extends DHItemSheetV2(ItemSheetV2) { /**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['miscellaneous'], classes: ['miscellaneous'],
position: { width: 550 } position: { width: 550 }
}; };
/**@override */
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/miscellaneous/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/miscellaneous/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },

View file

@ -1,26 +1,21 @@
import DHBaseItemSheet from '../api/base-item.mjs';
import { actionsTypes } from '../../../data/_module.mjs'; import { actionsTypes } from '../../../data/_module.mjs';
import DHActionConfig from '../../config/Action.mjs'; import DHActionConfig from '../../config/Action.mjs';
import DhpApplicationMixin from '../daggerheart-sheet.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; export default class SubclassSheet extends DHBaseItemSheet {
export default class SubclassSheet extends DhpApplicationMixin(ItemSheetV2) { /**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', classes: ['subclass'],
classes: ['daggerheart', 'sheet', 'item', 'dh-style', 'subclass'],
position: { width: 600 }, position: { width: 600 },
window: { resizable: false }, window: { resizable: false },
actions: { actions: {
addFeature: this.addFeature, addFeature: this.addFeature,
editFeature: this.editFeature, editFeature: this.editFeature,
deleteFeature: this.deleteFeature deleteFeature: this.deleteFeature
},
form: {
handler: this.updateForm,
submitOnChange: true,
closeOnSubmit: false
} }
}; };
/**@override */
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/subclass/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/subclass/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
@ -35,46 +30,16 @@ export default class SubclassSheet extends DhpApplicationMixin(ItemSheetV2) {
} }
}; };
/** @inheritdoc */
static TABS = { static TABS = {
description: { primary: {
active: true, tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }],
cssClass: '', initial: 'description',
group: 'primary', labelPrefix: 'DAGGERHEART.Sheets.TABS'
id: 'description',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Description'
},
features: {
active: false,
cssClass: '',
group: 'primary',
id: 'features',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Features'
},
settings: {
active: false,
cssClass: '',
group: 'primary',
id: 'settings',
icon: null,
label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings'
} }
}; };
async _prepareContext(_options) { //TODO redo everything below this message
const context = await super._prepareContext(_options);
context.document = this.document;
context.config = CONFIG.daggerheart;
context.tabs = super._getTabs(this.constructor.TABS);
return context;
}
static async updateForm(event, _, formData) {
await this.document.update(formData.object);
this.render();
}
static addFeature(_, target) { static addFeature(_, target) {
if (target.dataset.type === 'action') this.addAction(target.dataset.level); if (target.dataset.type === 'action') this.addAction(target.dataset.level);

View file

@ -1,13 +1,19 @@
import { weaponFeatures } from '../../../config/itemConfig.mjs'; import DHBaseItemSheet from '../api/base-item.mjs';
import { tagifyElement } from '../../../helpers/utils.mjs';
import DHItemSheetV2 from '../item.mjs';
const { ItemSheetV2 } = foundry.applications.sheets; export default class WeaponSheet extends DHBaseItemSheet {
export default class WeaponSheet extends DHItemSheetV2(ItemSheetV2) { /**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['weapon'] classes: ['weapon'],
tagifyConfigs: [
{
selector: '.features-input',
options: () => CONFIG.daggerheart.ITEM.weaponFeatures,
callback: WeaponSheet.#onFeatureSelect
}
]
}; };
/**@override */
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/weapon/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/weapon/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
@ -22,6 +28,7 @@ export default class WeaponSheet extends DHItemSheetV2(ItemSheetV2) {
} }
}; };
/**@inheritdoc */
async _preparePartContext(partId, context) { async _preparePartContext(partId, context) {
super._preparePartContext(partId, context); super._preparePartContext(partId, context);
@ -34,15 +41,11 @@ export default class WeaponSheet extends DHItemSheetV2(ItemSheetV2) {
return context; return context;
} }
_attachPartListeners(partId, htmlElement, options) { /**
super._attachPartListeners(partId, htmlElement, options); * Callback function used by `tagifyElement`.
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
const featureInput = htmlElement.querySelector('.features-input'); */
tagifyElement(featureInput, weaponFeatures, this.onFeatureSelect.bind(this)); static async #onFeatureSelect(selectedOptions) {
} await this.document.update({ 'system.features': selectedOptions.map(x => ({ value: x.value })) });
async onFeatureSelect(features) {
await this.document.update({ 'system.features': features.map(x => ({ value: x.value })) });
this.render(true);
} }
} }

View file

@ -2,56 +2,55 @@ export const domains = {
arcana: { arcana: {
id: 'arcana', id: 'arcana',
label: 'DAGGERHEART.Domains.arcana.label', label: 'DAGGERHEART.Domains.arcana.label',
src: 'icons/magic/symbols/circled-gem-pink.webp', src: 'systems/daggerheart/assets/icons/domains/arcana.svg',
description: 'DAGGERHEART.Domains.Arcana' description: 'DAGGERHEART.Domains.Arcana'
}, },
blade: { blade: {
id: 'blade', id: 'blade',
label: 'DAGGERHEART.Domains.blade.label', label: 'DAGGERHEART.Domains.blade.label',
src: 'icons/weapons/swords/sword-broad-crystal-paired.webp', src: 'systems/daggerheart/assets/icons/domains/blade.svg',
description: 'DAGGERHEART.Domains.Blade' description: 'DAGGERHEART.Domains.Blade'
}, },
bone: { bone: {
id: 'bone', id: 'bone',
label: 'DAGGERHEART.Domains.bone.label', label: 'DAGGERHEART.Domains.bone.label',
src: 'icons/skills/wounds/bone-broken-marrow-red.webp', src: 'systems/daggerheart/assets/icons/domains/bone.svg',
description: 'DAGGERHEART.Domains.Bone' description: 'DAGGERHEART.Domains.Bone'
}, },
codex: { codex: {
id: 'codex', id: 'codex',
label: 'DAGGERHEART.Domains.codex.label', label: 'DAGGERHEART.Domains.codex.label',
src: 'icons/sundries/books/book-embossed-jewel-gold-purple.webp', src: 'systems/daggerheart/assets/icons/domains/codex.svg',
description: 'DAGGERHEART.Domains.Codex' description: 'DAGGERHEART.Domains.Codex'
}, },
grace: { grace: {
id: 'grace', id: 'grace',
label: 'DAGGERHEART.Domains.grace.label', label: 'DAGGERHEART.Domains.grace.label',
src: 'icons/skills/movement/feet-winged-boots-glowing-yellow.webp', src: 'systems/daggerheart/assets/icons/domains/grace.svg',
description: 'DAGGERHEART.Domains.Grace' description: 'DAGGERHEART.Domains.Grace'
}, },
midnight: { midnight: {
id: 'midnight', id: 'midnight',
label: 'DAGGERHEART.Domains.midnight.label', label: 'DAGGERHEART.Domains.midnight.label',
src: 'icons/environment/settlement/watchtower-castle-night.webp', src: 'systems/daggerheart/assets/icons/domains/midnight.svg',
background: 'systems/daggerheart/assets/backgrounds/MidnightBackground.webp',
description: 'DAGGERHEART.Domains.Midnight' description: 'DAGGERHEART.Domains.Midnight'
}, },
sage: { sage: {
id: 'sage', id: 'sage',
label: 'DAGGERHEART.Domains.sage.label', label: 'DAGGERHEART.Domains.sage.label',
src: 'icons/sundries/misc/pipe-wooden-straight-brown.webp', src: 'systems/daggerheart/assets/icons/domains/sage.svg',
description: 'DAGGERHEART.Domains.Sage' description: 'DAGGERHEART.Domains.Sage'
}, },
splendor: { splendor: {
id: 'splendor', id: 'splendor',
label: 'DAGGERHEART.Domains.splendor.label', label: 'DAGGERHEART.Domains.splendor.label',
src: 'icons/magic/control/control-influence-crown-gold.webp', src: 'systems/daggerheart/assets/icons/domains/splendor.svg',
description: 'DAGGERHEART.Domains.Splendor' description: 'DAGGERHEART.Domains.Splendor'
}, },
valor: { valor: {
id: 'valor', id: 'valor',
label: 'DAGGERHEART.Domains.valor.label', label: 'DAGGERHEART.Domains.valor.label',
src: 'icons/magic/control/control-influence-rally-purple.webp', src: 'systems/daggerheart/assets/icons/domains/valor.svg',
description: 'DAGGERHEART.Domains.Valor' description: 'DAGGERHEART.Domains.Valor'
} }
}; };

View file

@ -36,7 +36,10 @@ export default class DhCharacter extends BaseDataActor {
return { return {
resources: new fields.SchemaField({ resources: new fields.SchemaField({
hitPoints: resourceField(6), hitPoints: new fields.SchemaField({
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true })
}),
stress: resourceField(6), stress: resourceField(6),
hope: resourceField(6), hope: resourceField(6),
tokens: new fields.ObjectField(), tokens: new fields.ObjectField(),
@ -287,7 +290,7 @@ export default class DhCharacter extends BaseDataActor {
this.rules.maxArmorMarked.total = this.rules.maxArmorMarked.value + this.rules.maxArmorMarked.bonus; this.rules.maxArmorMarked.total = this.rules.maxArmorMarked.value + this.rules.maxArmorMarked.bonus;
this.armorScore = this.armor ? this.armor.system.baseScore + (this.bonuses.armorScore ?? 0) : 0; this.armorScore = this.armor ? this.armor.system.baseScore + (this.bonuses.armorScore ?? 0) : 0;
this.resources.hitPoints.maxTotal = this.resources.hitPoints.max + this.resources.hitPoints.bonus; this.resources.hitPoints.maxTotal = (this.class.value?.system?.hitPoints ?? 0) + this.resources.hitPoints.bonus;
this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus; this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus;
this.evasion.total = (this.class?.evasion ?? 0) + this.evasion.bonus; this.evasion.total = (this.class?.evasion ?? 0) + this.evasion.bonus;
this.proficiency.total = this.proficiency.value + this.proficiency.bonus; this.proficiency.total = this.proficiency.value + this.proficiency.bonus;

View file

@ -20,8 +20,14 @@ export default class DHClass extends BaseDataItem {
...super.defineSchema(), ...super.defineSchema(),
domains: new fields.ArrayField(new fields.StringField(), { max: 2 }), domains: new fields.ArrayField(new fields.StringField(), { max: 2 }),
classItems: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), classItems: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
hitPoints: new fields.NumberField({
evasion: new fields.NumberField({ initial: 0, integer: true }), required: true,
integer: true,
min: 1,
initial: 5,
label: 'DAGGERHEART.Sheets.Class.HitPoints'
}),
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.Sheets.Class.Evasion' }),
hopeFeatures: new foundry.data.fields.ArrayField(new ActionField()), hopeFeatures: new foundry.data.fields.ArrayField(new ActionField()),
classFeatures: new foundry.data.fields.ArrayField(new ActionField()), classFeatures: new foundry.data.fields.ArrayField(new ActionField()),
subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),

View file

@ -131,14 +131,35 @@ export default class DhpItem extends Item {
action = await this.selectActionDialog(); action = await this.selectActionDialog();
} }
if (action) response = action.use(event); if (action) response = action.use(event);
// Check Target
// If action.roll => Roll Dialog
// Else If action.cost => Cost Dialog
// Then
// Apply Cost
// Apply Effect
} }
// Display Item Card in chat
return response; return response;
} }
async toChat(origin) {
const cls = getDocumentClass('ChatMessage');
const systemData = {
title:
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.SubclassFeatureTitle'),
origin: origin,
img: this.img,
name: this.name,
description: this.system.description,
actions: []
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/ability-use.hbs',
systemData
)
});
cls.create(msg.toObject());
}
} }

View file

@ -306,6 +306,7 @@ fieldset.daggerheart.chat {
h2 { h2 {
width: 100%; width: 100%;
text-align: center; text-align: center;
margin: 0;
} }
} }

View file

@ -1618,6 +1618,7 @@ fieldset.daggerheart.chat {
.daggerheart.chat.domain-card .domain-card-title h2 { .daggerheart.chat.domain-card .domain-card-title h2 {
width: 100%; width: 100%;
text-align: center; text-align: center;
margin: 0;
} }
.daggerheart.chat.domain-card .ability-card-footer { .daggerheart.chat.domain-card .ability-card-footer {
display: flex; display: flex;
@ -3778,13 +3779,35 @@ div.daggerheart.views.multiclass {
.theme-light .application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet { .theme-light .application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet {
background: transparent; background: transparent;
} }
.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet img { .application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .portrait {
position: relative;
height: 235px; height: 235px;
width: 275px; width: 275px;
border-bottom: 1px solid light-dark(#18162e, #f3c267); border-bottom: 1px solid light-dark(#18162e, #f3c267);
cursor: pointer; cursor: pointer;
}
.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .portrait img {
height: 235px;
width: 275px;
object-fit: cover; object-fit: cover;
} }
.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .portrait .death-roll-btn {
display: none;
}
.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .portrait.death-roll {
filter: grayscale(1);
}
.application.sheet.daggerheart.actor.dh-style.character .character-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.character .character-sidebar-sheet .portrait.death-roll .death-roll-btn:hover {
text-shadow: 0 0 8px #efe6d8;
}
.application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .info-section { .application.sheet.daggerheart.actor.dh-style.character .character-sidebar-sheet .info-section {
position: relative; position: relative;
display: flex; display: flex;

View file

@ -12,12 +12,39 @@
background: transparent; background: transparent;
} }
img { .portrait {
position: relative;
height: 235px; height: 235px;
width: 275px; width: 275px;
border-bottom: 1px solid light-dark(@dark-blue, @golden); border-bottom: 1px solid light-dark(@dark-blue, @golden);
cursor: pointer; cursor: pointer;
object-fit: cover;
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;
}
}
}
} }
.info-section { .info-section {

View file

@ -1,6 +1,5 @@
<div class="daggerheart chat domain-card"> <div class="daggerheart chat domain-card">
<div class="domain-card-title"> <div class="domain-card-title">
<div>{{title}}</div>
<h2>{{name}}</h2> <h2>{{name}}</h2>
</div> </div>
<img src="{{img}}" /> <img src="{{img}}" />

View file

@ -23,6 +23,6 @@
<div class="items-section"> <div class="items-section">
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.Sheets.PC.Tabs.Loadout') type='domainCard' isGlassy=true cardView='list'}} {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.Sheets.PC.Tabs.Loadout') type='domainCard' isGlassy=true cardView='list'}}
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.Sheets.PC.Tabs.Vault') type='domainCard' isGlassy=true cardView='list'}} {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs' title=(localize 'DAGGERHEART.Sheets.PC.Tabs.Vault') type='domainCard' isVault=true isGlassy=true cardView='list'}}
</div> </div>
</section> </section>

View file

@ -1,5 +1,9 @@
<aside class="character-sidebar-sheet"> <aside class="character-sidebar-sheet">
<img class="portrait" src="{{document.img}}" alt="{{document.name}}" data-action='editImage' data-edit="img"> <div class="portrait {{#if (gte document.system.resources.hitPoints.value document.system.resources.hitPoints.maxTotal)}}death-roll{{/if}}">
<img src="{{document.img}}" alt="{{document.name}}" data-action='editImage' data-edit="img">
<a class="death-roll-btn" data-tooltip="{{localize "DAGGERHEART.Sheets.PC.Health.DeathMoveTooltip"}}" data-action="makeDeathMove"><i class="fas fa-skull death-save" ></i></a>
</div>
<div class="info-section"> <div class="info-section">
<div class="resources-section"> <div class="resources-section">
<div class="status-bar"> <div class="status-bar">
@ -111,7 +115,7 @@
</div> </div>
<input name="{{concat "system.experiences." id ".description"}}" data-experience={{id}} value="{{experience.description}}" type="text" /> <input name="{{concat "system.experiences." id ".description"}}" data-experience={{id}} value="{{experience.description}}" type="text" />
<div class="controls"> <div class="controls">
<a><i class="fa-regular fa-message"></i></a> <a data-action="toChat" data-type="experience" data-uuid="{{id}}"><i class="fa-regular fa-message"></i></a>
</div> </div>
</div> </div>
{{/each}} {{/each}}

View file

@ -1,5 +1,5 @@
<li class="card-item"> <li class="card-item" data-item-id="{{item.id}}">
<img src="{{item.img}}" data-action="viewObject" data-value="{{item.uuid}}" class="card-img" /> <img src="{{item.img}}" data-action="useItem" class="card-img" />
<div class="card-label"> <div class="card-label">
<div class="controls"> <div class="controls">
{{#if (eq type 'weapon')}} {{#if (eq type 'weapon')}}
@ -14,18 +14,18 @@
{{/if}} {{/if}}
{{#if (eq type 'domainCard')}} {{#if (eq type 'domainCard')}}
{{#unless item.system.inVault}} {{#unless item.system.inVault}}
<a data-action="sendToVault" data-domain="{{card.uuid}}" id="{{item.id}}" data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToVault'}}"> <a data-action="sendToVault" data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToVault'}}">
<i class="fa-solid fa-arrow-down"></i> <i class="fa-solid fa-arrow-down"></i>
</a> </a>
{{else}} {{else}}
<a data-action="sendToLoadout" data-domain="{{card.uuid}}" id="{{item.id}}" data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToLoadout'}}"> <a data-action="sendToLoadout" data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToLoadout'}}">
<i class="fa-solid fa-arrow-up"></i> <i class="fa-solid fa-arrow-up"></i>
</a> </a>
{{/unless}} {{/unless}}
{{/if}} {{/if}}
<a data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToChat'}}"><i class="fa-regular fa-message"></i></a> <a data-action="toChat" data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToChat'}}"><i class="fa-regular fa-message"></i></a>
<a data-tooltip="{{localize 'DAGGERHEART.Tooltip.moreOptions'}}"><i class="fa-solid fa-ellipsis-vertical"></i></a> <a data-action="triggerContextMenu" data-tooltip="{{localize 'DAGGERHEART.Tooltip.moreOptions'}}"><i class="fa-solid fa-ellipsis-vertical"></i></a>
</div> </div>
<div class="card-name">{{item.name}}</div> <div class="card-name">{{item.name}}</div>
</div> </div>

View file

@ -3,7 +3,7 @@
<ul class="items-list"> <ul class="items-list">
{{#each document.items as |item|}} {{#each document.items as |item|}}
{{#if (eq item.type ../type)}} {{#if (eq item.type ../type)}}
{{#unless (or (eq ../type 'ancestry') (eq item.type 'class') (eq item.type 'subclass'))}} {{#unless (or (eq ../type 'ancestry') (eq item.type 'class') (eq item.type 'subclass') (and (eq ../type 'domainCard') (or (and item.system.inVault (not ../isVault)) (and (not item.system.inVault) ../isVault))))}}
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=item type=../type}} {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=item type=../type}}
{{/unless}} {{/unless}}
{{/if}} {{/if}}

View file

@ -1,5 +1,5 @@
<li class="inventory-item" data-item-id="{{item.uuid}}"> <li class="inventory-item" data-item-id="{{item.id}}">
<img src="{{item.img}}" data-action="useItem" class="item-img" /> <img src="{{item.img}}" class="item-img" data-action="useItem"/>
<div class="item-label"> <div class="item-label">
<div class="item-name">{{item.name}}</div> <div class="item-name">{{item.name}}</div>
{{#if (eq type 'weapon')}} {{#if (eq type 'weapon')}}
@ -103,28 +103,28 @@
</div> </div>
<div class="controls"> <div class="controls">
{{#if (eq type 'weapon')}} {{#if (eq type 'weapon')}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" id="{{item.id}}" data-tooltip="{{#unless item.system.equipped}}{{localize 'DAGGERHEART.Tooltip.equip'}}{{else}}{{localize 'DAGGERHEART.Tooltip.unequip'}}{{/unless}}"> <a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" data-tooltip="{{#unless item.system.equipped}}{{localize 'DAGGERHEART.Tooltip.equip'}}{{else}}{{localize 'DAGGERHEART.Tooltip.unequip'}}{{/unless}}">
<i class="fa-solid fa-hands"></i> <i class="fa-solid fa-hands"></i>
</a> </a>
{{/if}} {{/if}}
{{#if (eq type 'armor')}} {{#if (eq type 'armor')}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" id="{{item.id}}" data-tooltip="{{#unless item.system.equipped}}{{localize 'DAGGERHEART.Tooltip.equip'}}{{else}}{{localize 'DAGGERHEART.Tooltip.unequip'}}{{/unless}}"> <a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" data-tooltip="{{#unless item.system.equipped}}{{localize 'DAGGERHEART.Tooltip.equip'}}{{else}}{{localize 'DAGGERHEART.Tooltip.unequip'}}{{/unless}}">
<i class="fa-solid fa-shield"></i> <i class="fa-solid fa-shield"></i>
</a> </a>
{{/if}} {{/if}}
{{#if (eq type 'domainCard')}} {{#if (eq type 'domainCard')}}
{{#unless item.system.inVault}} {{#unless item.system.inVault}}
<a data-action="sendToVault" data-domain="{{card.uuid}}" id="{{item.id}}" data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToVault'}}"> <a data-action="toggleVault" data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToVault'}}">
<i class="fa-solid fa-arrow-down"></i> <i class="fa-solid fa-arrow-down"></i>
</a> </a>
{{else}} {{else}}
<a data-action="sendToLoadout" data-domain="{{card.uuid}}" id="{{item.id}}" data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToLoadout'}}"> <a data-action="toggleVault" data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToLoadout'}}">
<i class="fa-solid fa-arrow-up"></i> <i class="fa-solid fa-arrow-up"></i>
</a> </a>
{{/unless}} {{/unless}}
{{/if}} {{/if}}
<a data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToChat'}}"><i class="fa-regular fa-message"></i></a> <a data-action="toChat" data-tooltip="{{localize 'DAGGERHEART.Tooltip.sendToChat'}}"><i class="fa-regular fa-message"></i></a>
<a data-tooltip="{{localize 'DAGGERHEART.Tooltip.moreOptions'}}"><i class="fa-solid fa-ellipsis-vertical"></i></a> <a data-action="triggerContextMenu" data-tooltip="{{localize 'DAGGERHEART.Tooltip.moreOptions'}}"><i class="fa-solid fa-ellipsis-vertical"></i></a>
</div> </div>
</li> </li>

View file

@ -5,6 +5,6 @@
> >
<fieldset> <fieldset>
<legend>{{localize "DAGGERHEART.Sheets.Feature.Description"}}</legend> <legend>{{localize "DAGGERHEART.Sheets.Feature.Description"}}</legend>
{{formInput systemFields.description value=source.system.description enriched=source.system.description localize=true toggled=true}} {{formInput systemFields.description value=document.system.description enriched=enrichedDescription toggled=true}}
</fieldset> </fieldset>
</section> </section>

View file

@ -36,16 +36,17 @@
<div class='controls'> <div class='controls'>
<a <a
class='effect-control' class='effect-control'
data-action='viewSubclass' data-action='viewDoc'
data-subclass={{subclass.uuid}} data-uuid={{subclass.uuid}}
data-tooltip='{{localize "DAGGERHEART.Tooltip.openItemWorld"}}' data-tooltip='{{localize "DAGGERHEART.Tooltip.openItemWorld"}}'
> >
<i class="fa-solid fa-globe"></i> <i class="fa-solid fa-globe"></i>
</a> </a>
<a <a
class='effect-control' class='effect-control'
data-action='removeSubclass' data-action='removeItemFromCollection'
data-subclass={{subclass.uuid}} data-target="subclasses"
data-uuid={{subclass.uuid}}
data-tooltip='{{localize "DAGGERHEART.Tooltip.delete"}}' data-tooltip='{{localize "DAGGERHEART.Tooltip.delete"}}'
> >
<i class='fas fa-trash'></i> <i class='fas fa-trash'></i>

View file

@ -3,11 +3,10 @@
data-tab='{{tabs.settings.id}}' data-tab='{{tabs.settings.id}}'
data-group='{{tabs.settings.group}}' data-group='{{tabs.settings.group}}'
> >
<fieldset class="two-columns even">
<fieldset class="two-columns">
<legend>{{localize tabs.settings.label}}</legend> <legend>{{localize tabs.settings.label}}</legend>
<span>{{localize "DAGGERHEART.Sheets.Class.Evasion"}}</span> {{formGroup systemFields.hitPoints value=source.system.hitPoints localize=true}}
{{formField systemFields.evasion value=source.system.evasion}} {{formGroup systemFields.evasion value=source.system.evasion localize=true}}
</fieldset> </fieldset>
<div class="fieldsets-section"> <div class="fieldsets-section">
@ -39,11 +38,11 @@
<legend>{{localize "DAGGERHEART.Sheets.Class.Guide.SuggestedPrimaryWeaponTitle"}}</legend> <legend>{{localize "DAGGERHEART.Sheets.Class.Guide.SuggestedPrimaryWeaponTitle"}}</legend>
<div class="drop-section-body list-items"> <div class="drop-section-body list-items">
{{#if document.system.characterGuide.suggestedPrimaryWeapon}} {{#if document.system.characterGuide.suggestedPrimaryWeapon}}
<div class="suggested-item item-line" data-action="viewItem" data-item="{{document.system.characterGuide.suggestedPrimaryWeapon.uuid}}"> <div class="suggested-item item-line" data-action="viewDoc" data-uuid="{{document.system.characterGuide.suggestedPrimaryWeapon.uuid}}">
<img class="image" src="{{document.system.characterGuide.suggestedPrimaryWeapon.img}}" /> <img class="image" src="{{document.system.characterGuide.suggestedPrimaryWeapon.img}}" />
<span>{{document.system.characterGuide.suggestedPrimaryWeapon.name}}</span> <span>{{document.system.characterGuide.suggestedPrimaryWeapon.name}}</span>
<div class="controls"> <div class="controls">
<i data-action="removePrimaryWeapon" class="fa-solid fa-trash icon-button"></i> <i data-action="removeSuggestedItem" data-target="suggestedPrimaryWeapon" class="fa-solid fa-trash icon-button"></i>
</div> </div>
</div> </div>
{{/if}} {{/if}}
@ -54,11 +53,11 @@
<legend>{{localize "DAGGERHEART.Sheets.Class.Guide.SuggestedSecondaryWeaponTitle"}}</legend> <legend>{{localize "DAGGERHEART.Sheets.Class.Guide.SuggestedSecondaryWeaponTitle"}}</legend>
<div class="drop-section-body list-items"> <div class="drop-section-body list-items">
{{#if document.system.characterGuide.suggestedSecondaryWeapon}} {{#if document.system.characterGuide.suggestedSecondaryWeapon}}
<div class="suggested-item item-line" data-action="viewItem" data-item="{{system.system.characterGuide.suggestedSecondaryWeapon.uuid}}"> <div class="suggested-item item-line" data-action="viewDoc" data-uuid="{{system.system.characterGuide.suggestedSecondaryWeapon.uuid}}">
<img class="image" src="{{document.system.characterGuide.suggestedSecondaryWeapon.img}}" /> <img class="image" src="{{document.system.characterGuide.suggestedSecondaryWeapon.img}}" />
<span>{{document.system.characterGuide.suggestedSecondaryWeapon.name}}</span> <span>{{document.system.characterGuide.suggestedSecondaryWeapon.name}}</span>
<div class="controls"> <div class="controls">
<i data-action="removeSecondaryWeapon" class="fa-solid fa-trash icon-button"></i> <i data-action="removeSuggestedItem" data-target="suggestedSecondaryWeapon" class="fa-solid fa-trash icon-button"></i>
</div> </div>
</div> </div>
{{/if}} {{/if}}
@ -69,11 +68,11 @@
<legend>{{localize "DAGGERHEART.Sheets.Class.Guide.SuggestedArmorTitle"}}</legend> <legend>{{localize "DAGGERHEART.Sheets.Class.Guide.SuggestedArmorTitle"}}</legend>
<div class="drop-section-body list-items"> <div class="drop-section-body list-items">
{{#if document.system.characterGuide.suggestedArmor}} {{#if document.system.characterGuide.suggestedArmor}}
<div class="suggested-item item-line" data-action="viewItem" data-item="{{document.system.characterGuide.suggestedArmor.uuid}}"> <div class="suggested-item item-line" data-action="viewDoc" data-uuid="{{document.system.characterGuide.suggestedArmor.uuid}}">
<img class="image" src="{{document.system.characterGuide.suggestedArmor.img}}" /> <img class="image" src="{{document.system.characterGuide.suggestedArmor.img}}" />
<span>{{document.system.characterGuide.suggestedArmor.name}}</span> <span>{{document.system.characterGuide.suggestedArmor.name}}</span>
<div class="controls"> <div class="controls">
<i data-action="removeArmor" class="fa-solid fa-trash icon-button"></i> <i data-action="removeSuggestedItem" data-target="suggestedArmor" class="fa-solid fa-trash icon-button"></i>
</div> </div>
</div> </div>
{{/if}} {{/if}}
@ -87,11 +86,11 @@
<legend>{{localize "DAGGERHEART.Sheets.Class.Guide.Inventory.Take"}}</legend> <legend>{{localize "DAGGERHEART.Sheets.Class.Guide.Inventory.Take"}}</legend>
<div class="drop-section-body list-items"> <div class="drop-section-body list-items">
{{#each source.system.inventory.take}} {{#each source.system.inventory.take}}
<div class="suggested-item item-line" data-action="viewItem" data-item="{{this.uuid}}"> <div class="suggested-item item-line" data-action="viewDoc" data-uuid="{{this.uuid}}">
<img class="image" src="{{this.img}}" /> <img class="image" src="{{this.img}}" />
<span>{{this.name}}</span> <span>{{this.name}}</span>
<div class="controls"> <div class="controls">
<i data-action="removeItem" data-type="take" data-item="{{this.uuid}}" class="fa-solid fa-trash icon-button"></i> <i data-action="removeItemFromCollection" data-target="invetory.take" data-uuid="{{this.uuid}}" class="fa-solid fa-trash icon-button"></i>
</div> </div>
</div> </div>
{{/each}} {{/each}}
@ -102,11 +101,11 @@
<legend>{{localize "DAGGERHEART.Sheets.Class.Guide.Inventory.ThenChoose"}}</legend> <legend>{{localize "DAGGERHEART.Sheets.Class.Guide.Inventory.ThenChoose"}}</legend>
<div class="drop-section-body list-items"> <div class="drop-section-body list-items">
{{#each source.system.inventory.choiceA}} {{#each source.system.inventory.choiceA}}
<div class="suggested-item item-line" data-action="viewItem" data-item="{{this.uuid}}"> <div class="suggested-item item-line" data-action="viewDoc" data-uuid="{{this.uuid}}">
<img class="image" src="{{this.img}}" /> <img class="image" src="{{this.img}}" />
<span>{{this.name}}</span> <span>{{this.name}}</span>
<div class="controls"> <div class="controls">
<i data-action="removeItem" data-type="choiceA" data-item="{{this.uuid}}" class="fa-solid fa-trash icon-button"></i> <i data-action="removeItemFromCollection" data-target="invetory.choiceA" data-uuid="{{this.uuid}}" class="fa-solid fa-trash icon-button"></i>
</div> </div>
</div> </div>
{{/each}} {{/each}}
@ -117,11 +116,11 @@
<legend>{{localize "DAGGERHEART.Sheets.Class.Guide.Inventory.AndEither"}}</legend> <legend>{{localize "DAGGERHEART.Sheets.Class.Guide.Inventory.AndEither"}}</legend>
<div class="drop-section-body list-items"> <div class="drop-section-body list-items">
{{#each source.system.inventory.choiceB}} {{#each source.system.inventory.choiceB}}
<div class="suggested-item item-line" data-action="viewItem" data-item="{{this.uuid}}"> <div class="suggested-item item-line" data-action="viewDoc" data-uuid="{{this.uuid}}">
<img class="image" src="{{this.img}}" /> <img class="image" src="{{this.img}}" />
<span>{{this.name}}</span> <span>{{this.name}}</span>
<div class="controls"> <div class="controls">
<i data-action="removeItem" data-type="choiceB" data-item="{{this.uuid}}" class="fa-solid fa-trash icon-button"></i> <i data-action="removeItemFromCollection" data-target="invetory.choiceB" data-uuid="{{this.uuid}}" class="fa-solid fa-trash icon-button"></i>
</div> </div>
</div> </div>
{{/each}} {{/each}}

View file

@ -8,15 +8,15 @@
<span>{{localize "DAGGERHEART.Sheets.Feature.effects.addEffect"}}</span> <span>{{localize "DAGGERHEART.Sheets.Feature.effects.addEffect"}}</span>
<div class="nest-inputs"> <div class="nest-inputs">
<select class="effect-select"> <select class="effect-select">
{{selectOptions this.effectConfig.effectTypes selected=this.selectedEffectType labelAttr="name" localize=true blank=""}} {{selectOptions effectConfig.effectTypes selected=selectedEffectType labelAttr="name" localize=true blank=""}}
</select> </select>
<a> <a data-action="addEffect" {{disabled (not selectedEffectType)}}>
<i class="fa-solid fa-plus icon-button {{#if (not this.selectedEffectType)}}disabled{{/if}}" data-action="addEffect"></i> <i class="fa-solid fa-plus icon-button {{disabled (not selectedEffectType)}}"></i>
</a> </a>
</div> </div>
</fieldset> </fieldset>
{{#each this.document.system.effects as |effect key|}} {{#each document.system.effects as |effect key|}}
<fieldset class="two-columns"> <fieldset class="two-columns">
<legend> <legend>
{{localize (concat 'DAGGERHEART.Effects.Types.' effect.type '.Name')}} {{localize (concat 'DAGGERHEART.Effects.Types.' effect.type '.Name')}}
@ -32,8 +32,8 @@
{{selectOptions effect.applyLocationChoices selected=effect.appliesOn localize=true}} {{selectOptions effect.applyLocationChoices selected=effect.appliesOn localize=true}}
</select> </select>
{{/if}} {{/if}}
{{#if (eq effect.valueType ../this.effectConfig.valueTypes.numberString.id)}} {{#if (eq effect.valueType @root.effectConfig.valueTypes.numberString.id)}}
{{#if (eq effect.type ../this.effectConfig.effectTypes.damage.id) }} {{#if (eq effect.type @root.effectConfig.effectTypes.damage.id) }}
<span>{{localize "DAGGERHEART.Sheets.Feature.effects.value"}}</span> <span>{{localize "DAGGERHEART.Sheets.Feature.effects.value"}}</span>
<input type="text" name="system.effects.{{key}}.valueData.value" value="{{effect.valueData.value}}" /> <input type="text" name="system.effects.{{key}}.valueData.value" value="{{effect.valueData.value}}" />
@ -47,7 +47,7 @@
<input type="text" name="system.effects.{{key}}.valueData.value" value="{{effect.valueData.value}}" /> <input type="text" name="system.effects.{{key}}.valueData.value" value="{{effect.valueData.value}}" />
{{/if}} {{/if}}
{{/if}} {{/if}}
{{#if (eq effect.valueType ../this.effectConfig.valueTypes.select.id)}} {{#if (eq effect.valueType @root.effectConfig.valueTypes.select.id)}}
<span> <span>
{{localize effect.valueData.fromValue}} {{localize effect.valueData.fromValue}}
</span> </span>

View file

@ -22,13 +22,13 @@
<legend>{{localize "DAGGERHEART.Sheets.Feature.ValueType.Title"}}</legend> <legend>{{localize "DAGGERHEART.Sheets.Feature.ValueType.Title"}}</legend>
<span>{{localize "DAGGERHEART.Sheets.Feature.ValueType.Title"}}</span> <span>{{localize "DAGGERHEART.Sheets.Feature.ValueType.Title"}}</span>
<select name="system.featureType.type"> <select name="system.featureType.type">
{{selectOptions this.itemConfig.valueTypes selected=document.system.featureType.type labelAttr="name" localize=true}} {{selectOptions itemConfig.valueTypes selected=document.system.featureType.type labelAttr="name" localize=true}}
</select> </select>
{{#if (eq document.system.featureType.type "dice")}} {{#if (eq document.system.featureType.type "dice")}}
<span>{{localize "DAGGERHEART.Sheets.Feature.ValueType.Dice"}}</span> <span>{{localize "DAGGERHEART.Sheets.Feature.ValueType.Dice"}}</span>
<select name="system.featureType.data.value"> <select name="system.featureType.data.value">
{{selectOptions this.dice selected=document.system.featureType.data.value }} {{selectOptions dice selected=document.system.featureType.data.value }}
</select> </select>
<span>{{localize "DAGGERHEART.Feature.Max"}}</span> <span>{{localize "DAGGERHEART.Feature.Max"}}</span>
@ -36,7 +36,7 @@
<span>{{localize "DAGGERHEART.Sheets.Feature.ValueType.Property"}}</span> <span>{{localize "DAGGERHEART.Sheets.Feature.ValueType.Property"}}</span>
<select name="system.featureType.data.property"> <select name="system.featureType.data.property">
{{selectOptions this.properties selected=document.system.featureType.data.property labelAttr="name" localize=true}} {{selectOptions properties selected=document.system.featureType.data.property labelAttr="name" localize=true}}
</select> </select>
{{/if}} {{/if}}
</fieldset> </fieldset>

View file

@ -3,6 +3,7 @@
{{#each types}} {{#each types}}
<li> <li>
<label> <label>
{{! TODO: remove dh-icon}}
<dh-icon class="dh-icon fas {{icon}}"></dh-icon> <dh-icon class="dh-icon fas {{icon}}"></dh-icon>
<span>{{localize name}}</span> <span>{{localize name}}</span>
<input type="radio" name="type" value="{{id}}" {{#if (eq @index 0)}}checked{{/if}}> <input type="radio" name="type" value="{{id}}" {{#if (eq @index 0)}}checked{{/if}}>