Compare commits

...

14 commits

Author SHA1 Message Date
WBHarry
c6335980ba Merge branch 'v14-dev' 2026-04-14 20:55:14 +02:00
WBHarry
1176328f62 Updated deploy.yml 2026-04-14 20:55:10 +02:00
a62d28cd96
updated contributing guidelines (#1800) 2026-04-14 18:51:28 +02:00
WBHarry
8d8dea81fe
[Fix] Compendium Advantage Sources (#1796)
* Duration Description field didn't show initially for type temporary

* Corrected SRD
2026-04-13 20:41:28 +02:00
Nikhil Nagarajan
fb07938e54
container fix (#1795) 2026-04-12 19:10:51 +02:00
WBHarry
c337338c8b Merge branch 'v14-dev' of https://github.com/Foundryborne/daggerheart into v14-dev 2026-04-12 13:58:51 +02:00
WBHarry
f900011510 Fixed trait counting in CharacterCreation. Fixed description layout and labels in CharacterCreation. Fixed drag/drop images from Compendium Browser 2026-04-12 13:58:43 +02:00
WBHarry
56a6613a73
Fixed more missing translations (#1792) 2026-04-12 11:38:15 +02:00
WBHarry
e003db3ec1 Corrected updateActorsRangeDependentEffects when token is null 2026-04-12 11:22:00 +02:00
WBHarry
66c90d69e3 Added saefety to updateActorsRangeDepenedentEffects 2026-04-12 11:10:02 +02:00
WBHarry
a839ca0066 Corrected deploy.yml for new branch 2026-04-12 00:36:24 +02:00
WBHarry
6ed975f5b7 Merge branch 'v14-dev' of https://github.com/Foundryborne/daggerheart into v14-dev 2026-04-12 00:33:04 +02:00
WBHarry
e2c97a7b61 Raised version 2026-04-12 00:32:59 +02:00
WBHarry
3ec013ff50
Reworked summon action and clowncar functionality to work with levels (#1791) 2026-04-12 00:25:43 +02:00
23 changed files with 262 additions and 307 deletions

View file

@ -35,7 +35,7 @@ jobs:
env: env:
version: ${{steps.get_version.outputs.version-without-v}} version: ${{steps.get_version.outputs.version-without-v}}
url: https://github.com/${{github.repository}} url: https://github.com/${{github.repository}}
manifest: https://raw.githubusercontent.com/${{github.repository}}/main/system.json manifest: https://raw.githubusercontent.com/${{github.repository}}/v14/system.json
download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip download: https://github.com/${{github.repository}}/releases/download/${{github.event.release.tag_name}}/system.zip
# Create a zip file with all files required by the module to add to the release # Create a zip file with all files required by the module to add to the release

View file

@ -1,78 +1,9 @@
# Contributing to Foundryborne # Contributing to Daggerheart
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. Thank you for your interest in contributing to the Foundryborne project!
--- To ensure that all contributions align with our project goals and architectural standards, we ask that you **do not submit outside contributions without first receiving feedback from the development team.**
## 🤝 How to Contribute If you have an idea or a fix you'd like to contribute, please start a discussion or open an issue first. We'd love to hear from you and collaborate on the best way to move forward!
We welcome contributions of all kinds: Thank you for your understanding and support.
- 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.
### Community Translations
Please note that we are not accepting community translations in the main project. Instead, community translations should be published as a module.
---
## 🧭 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

@ -355,6 +355,8 @@ Hooks.on(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, async data => {
}); });
const updateActorsRangeDependentEffects = async token => { const updateActorsRangeDependentEffects = async token => {
if (!token) return;
const rangeMeasurement = game.settings.get( const rangeMeasurement = game.settings.get(
CONFIG.DH.id, CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.variantRules CONFIG.DH.SETTINGS.gameSettings.variantRules

View file

@ -74,9 +74,7 @@
"name": "Summon", "name": "Summon",
"tooltip": "Create tokens in the scene.", "tooltip": "Create tokens in the scene.",
"error": "You do not have permission to summon tokens or there is no active scene.", "error": "You do not have permission to summon tokens or there is no active scene.",
"invalidDrop": "You can only drop Actor entities to summon.", "invalidDrop": "You can only drop Actor entities to summon."
"chatMessageTitle": "Test2",
"chatMessageHeaderTitle": "Summoning"
}, },
"transform": { "transform": {
"name": "Transform", "name": "Transform",
@ -2016,6 +2014,10 @@
"hint": "Multiply any damage dealt to you by this number" "hint": "Multiply any damage dealt to you by this number"
} }
}, },
"Battlepoints": {
"full": "Battlepoints",
"short": "BP"
},
"Bonuses": { "Bonuses": {
"rest": { "rest": {
"downtimeAction": "Downtime Action", "downtimeAction": "Downtime Action",
@ -2431,6 +2433,7 @@
"next": "Next", "next": "Next",
"none": "None", "none": "None",
"noTarget": "No current target", "noTarget": "No current target",
"optionalThing": "Optional {thing}",
"partner": "Partner", "partner": "Partner",
"player": { "player": {
"single": "Player", "single": "Player",
@ -2457,6 +2460,7 @@
"rollDamage": "Roll Damage", "rollDamage": "Roll Damage",
"rollWith": "{roll} Roll", "rollWith": "{roll} Roll",
"save": "Save", "save": "Save",
"saveSettings": "Save Settings",
"scalable": "Scalable", "scalable": "Scalable",
"scars": "Scars", "scars": "Scars",
"situationalBonus": "Situational Bonus", "situationalBonus": "Situational Bonus",
@ -2611,8 +2615,14 @@
}, },
"Weapon": { "Weapon": {
"weaponType": "Weapon Type", "weaponType": "Weapon Type",
"primaryWeapon": "Primary Weapon", "primaryWeapon": {
"secondaryWeapon": "Secondary Weapon" "full": "Primary Weapon",
"short": "Primary"
},
"secondaryWeapon": {
"full": "Secondary Weapon",
"short": "Secondary"
}
} }
}, },
"MACROS": { "MACROS": {
@ -3067,6 +3077,7 @@
}, },
"ItemBrowser": { "ItemBrowser": {
"title": "Daggerheart Compendium Browser", "title": "Daggerheart Compendium Browser",
"windowTitle": "Compendium Browser",
"hint": "Select a Folder in sidebar to start browsing through the compendium", "hint": "Select a Folder in sidebar to start browsing through the compendium",
"browserSettings": "Browser Settings", "browserSettings": "Browser Settings",
"columnName": "Name", "columnName": "Name",

View file

@ -11,7 +11,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
this.character = character; this.character = character;
this.setup = { this.setup = {
traits: this.character.system.traits, traits: Object.keys(this.character.system.traits).reduce((acc, key) => {
acc[key] = { value: null };
return acc;
}, {}),
ancestryName: { ancestryName: {
primary: '', primary: '',
secondary: '' secondary: ''
@ -377,8 +380,10 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
]; ];
return Object.values(this.setup.traits).reduce((acc, x) => { return Object.values(this.setup.traits).reduce((acc, x) => {
const index = traitCompareArray.indexOf(x.value); const index = traitCompareArray.indexOf(x.value);
if (index === -1) return acc;
traitCompareArray.splice(index, 1); traitCompareArray.splice(index, 1);
acc += index !== -1; acc += 1;
return acc; return acc;
}, 0); }, 0);
} }

View file

@ -122,15 +122,14 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
async toggleClowncar(actors) { async toggleClowncar(actors) {
const animationDuration = 500; const animationDuration = 500;
const activeTokens = actors.flatMap(member => member.getActiveTokens()); const scene = game.scenes.get(game.user.viewedScene);
/* getDependentTokens returns already removed tokens with id = null. Need to filter that until it's potentially fixed from Foundry */
const activeTokens = actors.flatMap(member => member.getDependentTokens({ scenes: scene }).filter(x => x._id));
const { x: actorX, y: actorY } = this.document; const { x: actorX, y: actorY } = this.document;
if (activeTokens.length > 0) { if (activeTokens.length > 0) {
for (let token of activeTokens) { for (let token of activeTokens) {
await token.document.update( await token.update({ x: actorX, y: actorY, alpha: 0 }, { animation: { duration: animationDuration } });
{ x: actorX, y: actorY, alpha: 0 }, setTimeout(() => token.delete(), animationDuration);
{ animation: { duration: animationDuration } }
);
setTimeout(() => token.document.delete(), animationDuration);
} }
} else { } else {
const activeScene = game.scenes.find(x => x.id === game.user.viewedScene); const activeScene = game.scenes.find(x => x.id === game.user.viewedScene);
@ -140,11 +139,16 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
tokenData.push(data.toObject()); tokenData.push(data.toObject());
} }
const viewedLevel = game.scenes.get(game.user.viewedScene).levels.get(game.user.viewedLevel);
const elevation = this.actor.token?.elevation ?? viewedLevel.elevation.bottom;
const newTokens = await activeScene.createEmbeddedDocuments( const newTokens = await activeScene.createEmbeddedDocuments(
'Token', 'Token',
tokenData.map(tokenData => ({ tokenData.map(tokenData => ({
...tokenData, ...tokenData,
alpha: 0, alpha: 0,
level: viewedLevel,
elevation: elevation,
x: actorX, x: actorX,
y: actorY y: actorY
})) }))

View file

@ -37,7 +37,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
tag: 'div', tag: 'div',
window: { window: {
frame: true, frame: true,
title: 'Compendium Browser', title: 'DAGGERHEART.UI.ItemBrowser.windowTitle',
icon: 'fa-solid fa-book-atlas', icon: 'fa-solid fa-book-atlas',
positioned: true, positioned: true,
resizable: true resizable: true
@ -583,7 +583,9 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
const { itemUuid } = event.target.closest('[data-item-uuid]').dataset, const { itemUuid } = event.target.closest('[data-item-uuid]').dataset,
item = await foundry.utils.fromUuid(itemUuid), item = await foundry.utils.fromUuid(itemUuid),
dragData = item.toDragData(); dragData = item.toDragData();
event.dataTransfer.setData('text/plain', JSON.stringify(dragData)); event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
event.dataTransfer.setDragImage(event.target.querySelector('img'), 0, 0);
} }
_canDragStart() { _canDragStart() {

View file

@ -1,16 +1 @@
export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer { export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {}
async _createPreview(createData, options) {
if (options.actor) {
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
if (options.actor?.system.metadata.usesSize) {
const tokenSize = tokenSizes[options.actor.system.size];
if (tokenSize && options.actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
createData.width = tokenSize;
createData.height = tokenSize;
}
}
}
return super._createPreview(createData, options);
}
}

View file

@ -74,12 +74,13 @@ export const typeConfig = {
columns: [ columns: [
{ {
key: 'type', key: 'type',
label: 'DAGGERHEART.GENERAL.type' label: 'DAGGERHEART.GENERAL.type',
format: type => type ? `TYPES.Item.${type}` : '-'
}, },
{ {
key: 'system.secondary', key: 'system.secondary',
label: 'DAGGERHEART.UI.ItemBrowser.subtype', label: 'DAGGERHEART.UI.ItemBrowser.subtype',
format: isSecondary => (isSecondary ? 'secondary' : isSecondary === false ? 'primary' : '-') format: isSecondary => (isSecondary ? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short' : isSecondary === false ? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short' : '-')
}, },
{ {
key: 'system.tier', key: 'system.tier',
@ -99,8 +100,8 @@ export const typeConfig = {
key: 'system.secondary', key: 'system.secondary',
label: 'DAGGERHEART.UI.ItemBrowser.subtype', label: 'DAGGERHEART.UI.ItemBrowser.subtype',
choices: [ choices: [
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon' }, { value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.full' },
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' } { value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full' }
] ]
}, },
{ {
@ -258,11 +259,13 @@ export const typeConfig = {
columns: [ columns: [
{ {
key: 'system.type', key: 'system.type',
label: 'DAGGERHEART.GENERAL.type' label: 'DAGGERHEART.GENERAL.type',
format: type => type ? `DAGGERHEART.CONFIG.DomainCardTypes.${type}` : '-'
}, },
{ {
key: 'system.domain', key: 'system.domain',
label: 'DAGGERHEART.GENERAL.Domain.single' label: 'DAGGERHEART.GENERAL.Domain.single',
format: domain => domain ? CONFIG.DH.DOMAIN.allDomains()[domain].label : '-'
}, },
{ {
key: 'system.level', key: 'system.level',
@ -374,7 +377,7 @@ export const typeConfig = {
columns: [ columns: [
{ {
key: 'system.linkedClass', key: 'system.linkedClass',
label: 'Class', label: 'TYPES.Item.class',
format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing' format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
}, },
{ {
@ -386,7 +389,7 @@ export const typeConfig = {
filters: [ filters: [
{ {
key: 'system.linkedClass.uuid', key: 'system.linkedClass.uuid',
label: 'Class', label: 'TYPES.Item.class',
choices: items => { choices: items => {
const list = items const list = items
.filter(item => item.system.linkedClass) .filter(item => item.system.linkedClass)
@ -410,7 +413,8 @@ export const typeConfig = {
}, },
{ {
key: 'system.mainTrait', key: 'system.mainTrait',
label: 'DAGGERHEART.GENERAL.Trait.single' label: 'DAGGERHEART.GENERAL.Trait.single',
format: trait => (trait ? `DAGGERHEART.CONFIG.Traits.${trait}.name` : '-')
} }
], ],
filters: [ filters: [

View file

@ -44,12 +44,18 @@ export default class DHSummonField extends fields.ArrayField {
count = roll.total; count = roll.total;
} }
const actor = DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID)); const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
/* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */ /* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */
summon.rolledCount = count;
summon.actor = actor.toObject(); summon.actor = actor.toObject();
summonData.push({ actor, count: count }); const countNumber = Number.parseInt(count);
for (let i = 0; i < countNumber; i++) {
const remaining = countNumber - i;
summonData.push({
actor,
tokenPreviewName: `${actor.prototypeToken.name}${remaining > 1 ? ` (${remaining}x)` : ''}`
});
}
} }
if (rolls.length) await Promise.all(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true))); if (rolls.length) await Promise.all(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true)));
@ -58,32 +64,22 @@ export default class DHSummonField extends fields.ArrayField {
DHSummonField.handleSummon(summonData, this.actor); DHSummonField.handleSummon(summonData, this.actor);
} }
/* Check for any available instances of the actor present in the world if we're missing artwork in the compendium */ /* Check for any available instances of the actor present in the world if we're missing artwork in the compendium. If none exists, create one. */
static getWorldActor(baseActor) { static async getWorldActor(baseActor) {
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`]; const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) { if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) {
const worldActorCopy = game.actors.find(x => x.name === baseActor.name); const worldActorCopy = game.actors.find(x => x.name === baseActor.name);
return worldActorCopy ?? baseActor; if (worldActorCopy) return worldActorCopy;
return await game.system.api.documents.DhpActor.create(baseActor.toObject());
} }
return baseActor; return baseActor;
} }
static async handleSummon(summonData, actionActor, summonIndex = 0) { static async handleSummon(summonData, actionActor) {
const summon = summonData[summonIndex]; await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation });
const result = await CONFIG.ux.TokenManager.createPreviewAsync(summon.actor, {
name: `${summon.actor.prototypeToken.name}${summon.count > 1 ? ` (${summon.count}x)` : ''}`
});
if (!result) return actionActor.sheet?.maximize(); return actionActor.sheet?.maximize();
summon.actor = result.actor;
summon.count--;
if (summon.count <= 0) {
summonIndex++;
if (summonIndex === summonData.length) return actionActor.sheet?.maximize();
}
DHSummonField.handleSummon(summonData, actionActor, summonIndex);
} }
} }

View file

@ -1,104 +1,68 @@
/** /**
* A singleton class that handles preview tokens. * A singleton class that handles creating tokens.
*/ */
export default class DhTokenManager { export default class DhTokenManager {
#activePreview;
#actor;
#resolve;
/** /**
* Create a template preview, deactivating any existing ones. * Create a token previer
* @param {object} data * @param {Actor} actor
* @param {object} tokenData
*/ */
async createPreview(actor, tokenData) { async createPreview(actor, tokenData) {
this.#actor = actor; const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
const token = await canvas.tokens._createPreview( if (actor?.system.metadata.usesSize) {
{ const tokenSize = tokenSizes[actor.system.size];
...actor.prototypeToken, if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
displayName: 50, tokenData.width = tokenSize;
...tokenData tokenData.height = tokenSize;
}, }
{ renderSheet: false, actor } }
return await canvas.tokens.placeTokens(
[
{
...actor.prototypeToken.toObject(),
actorId: actor.id,
displayName: 50,
...tokenData
}
],
{ create: false }
); );
this.#activePreview = {
document: token.document,
object: token,
origin: { x: token.document.x, y: token.document.y }
};
this.#activePreview.events = {
contextmenu: this.#cancelTemplate.bind(this),
mousedown: this.#confirmTemplate.bind(this),
mousemove: this.#onDragMouseMove.bind(this)
};
canvas.stage.on('mousemove', this.#activePreview.events.mousemove);
canvas.stage.on('mousedown', this.#activePreview.events.mousedown);
canvas.app.view.addEventListener('contextmenu', this.#activePreview.events.contextmenu);
}
/* Currently intended for using as a preview of where to create a token. (note the flag) */
async createPreviewAsync(actor, tokenData = {}) {
return new Promise(resolve => {
this.#resolve = resolve;
this.createPreview(actor, { ...tokenData, flags: { daggerheart: { createPlacement: true } } });
});
} }
/** /**
* Handles the movement of the token preview on mousedrag. * Creates new tokens on the canvas by placing previews.
* @param {mousemove Event} event * @param {object} tokenData
* @param {object} options
*/ */
#onDragMouseMove(event) { async createTokensWithPreview(tokensData, { elevation } = {}) {
event.stopPropagation(); const scene = game.scenes.get(game.user.viewedScene);
const { moveTime, object } = this.#activePreview; if (!scene) return;
const update = {};
const now = Date.now(); const level = scene.levels.get(game.user.viewedLevel);
if (now - (moveTime || 0) <= 16) return; if (!level) return;
this.#activePreview.moveTime = now;
let cursor = event.getLocalPosition(canvas.templates); const createElevation = elevation ?? level.elevation.bottom;
for (const tokenData of tokensData) {
const previewTokens = await this.createPreview(tokenData.actor, {
name: tokenData.tokenPreviewName,
level: game.user.viewedLevel,
elevation: createElevation,
flags: { daggerheart: { createPlacement: true } }
});
if (!previewTokens?.length) return null;
Object.assign(update, canvas.grid.getTopLeftPoint(cursor)); await canvas.scene.createEmbeddedDocuments(
'Token',
object.document.updateSource(update); previewTokens.map(x => ({
object.renderFlags.set({ refresh: true }); ...x.toObject(),
} name: tokenData.actor.prototypeToken.name,
displayName: tokenData.actor.prototypeToken.displayName,
/** flags: tokenData.actor.prototypeToken.flags
* Cancels the preview token on right-click. })),
* @param {contextmenu Event} event { controlObject: true, parent: canvas.scene }
*/ );
#cancelTemplate(_event, resolved) { }
const { mousemove, mousedown, contextmenu } = this.#activePreview.events;
this.#activePreview.object.destroy();
canvas.stage.off('mousemove', mousemove);
canvas.stage.off('mousedown', mousedown);
canvas.app.view.removeEventListener('contextmenu', contextmenu);
if (this.#resolve && !resolved) this.#resolve(false);
}
/**
* Creates a real Actor and token at the preview location and cancels the preview.
* @param {click Event} event
*/
async #confirmTemplate(event) {
event.stopPropagation();
this.#cancelTemplate(event, true);
const actor = this.#actor.inCompendium
? await game.system.api.documents.DhpActor.create(this.#actor.toObject())
: this.#actor;
const tokenData = await actor.getTokenDocument();
const result = await canvas.scene.createEmbeddedDocuments('Token', [
{ ...tokenData.toObject(), x: this.#activePreview.document.x, y: this.#activePreview.document.y }
]);
this.#activePreview = undefined;
if (this.#resolve && result.length) this.#resolve(result[0]);
} }
} }

View file

@ -68,31 +68,33 @@
"type": "withinRange", "type": "withinRange",
"target": "hostile", "target": "hostile",
"range": "melee" "range": "melee"
},
"changes": [
{
"key": "system.resistance.physical.resistance",
"type": "override",
"value": 1,
"priority": null,
"phase": "initial"
},
{
"key": "system.disadvantageSources",
"type": "add",
"value": "Action rolls",
"priority": null,
"phase": "initial"
}
],
"duration": {
"type": ""
} }
}, },
"changes": [
{
"key": "system.resistance.physical.resistance",
"mode": 5,
"value": "1",
"priority": null
},
{
"key": "system.disadvantageSources",
"mode": 2,
"value": "Retract",
"priority": null
}
],
"disabled": true, "disabled": true,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null, "units": "seconds",
"seconds": null, "expiry": null,
"rounds": null, "expired": false
"turns": null,
"startRound": null,
"startTurn": null
}, },
"description": "<p>While in your shell, you have resistance to physical damage, you have disadvantage on action rolls, and you cant move.</p>", "description": "<p>While in your shell, you have resistance to physical damage, you have disadvantage on action rolls, and you cant move.</p>",
"tint": "#ffffff", "tint": "#ffffff",
@ -102,6 +104,16 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"start": {
"time": 0,
"combat": null,
"combatant": null,
"initiative": null,
"round": null,
"turn": null
},
"showIcon": 1,
"folder": null,
"_key": "!items.effects!UFR67BUOhNGLFyg9.3V4FPoyjJUnFP9WS" "_key": "!items.effects!UFR67BUOhNGLFyg9.3V4FPoyjJUnFP9WS"
} }
], ],

View file

@ -29,39 +29,28 @@
"type": "withinRange", "type": "withinRange",
"target": "hostile", "target": "hostile",
"range": "melee" "range": "melee"
},
"changes": [
{
"key": "system.advantageSources",
"type": "add",
"value": "Rolls to hide, investigate, or perceive details in low light",
"priority": null,
"phase": "initial"
}
],
"duration": {
"type": ""
} }
}, },
"changes": [
{
"key": "system.advantageSources",
"mode": 2,
"value": "In an area with low light or heavy shadow: hide, investigate, or perceive",
"priority": null
},
{
"key": "system.advantageSources",
"mode": 2,
"value": "",
"priority": null
},
{
"key": "",
"mode": 2,
"value": "",
"priority": null
}
],
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null, "units": "seconds",
"seconds": null, "expiry": null,
"rounds": null, "expired": false
"turns": null,
"startRound": null,
"startTurn": null
}, },
"description": "", "description": "<p>When youre in an area with low light or heavy shadow, you have advantage on rolls to hide, investigate, or perceive details within that area.</p>",
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
"transfer": true, "transfer": true,
@ -71,6 +60,16 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"start": {
"time": 0,
"combat": null,
"combatant": null,
"initiative": null,
"round": null,
"turn": null
},
"showIcon": 1,
"folder": null,
"_key": "!items.effects!aMla3xQuCHEwORGD.pCp32u7UwqxCI4WW" "_key": "!items.effects!aMla3xQuCHEwORGD.pCp32u7UwqxCI4WW"
} }
], ],

View file

@ -132,27 +132,29 @@
"type": "withinRange", "type": "withinRange",
"target": "hostile", "target": "hostile",
"range": "melee" "range": "melee"
},
"changes": [
{
"key": "system.advantageSources",
"type": "add",
"value": "On Attacks",
"priority": null,
"phase": "initial"
}
],
"duration": {
"type": "temporary",
"description": "<p>Until you or an ally rolls a failure with Fear.</p>"
} }
}, },
"changes": [
{
"key": "system.advantageSources",
"mode": 2,
"value": "1",
"priority": null
}
],
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null, "units": "seconds",
"seconds": null, "expiry": null,
"rounds": null, "expired": false
"turns": null,
"startRound": null,
"startTurn": null
}, },
"description": "", "description": "<p>You gain advantage on attack rolls until you or an ally rolls a failure with Fear.</p>",
"tint": "#ffffff", "tint": "#ffffff",
"statuses": [], "statuses": [],
"sort": 0, "sort": 0,
@ -160,6 +162,16 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"start": {
"time": 0,
"combat": null,
"combatant": null,
"initiative": null,
"round": null,
"turn": null
},
"showIcon": 1,
"folder": null,
"_key": "!items.effects!Ef1JsUG50LIoKx2F.s7ma4TNgAvt0ZgEW" "_key": "!items.effects!Ef1JsUG50LIoKx2F.s7ma4TNgAvt0ZgEW"
} }
], ],

View file

@ -175,6 +175,11 @@
opacity: 0.2; opacity: 0.2;
} }
&.no-horizontal-padding {
padding-left: 0;
padding-right: 0;
}
legend { legend {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@ -250,6 +255,11 @@
height: 65px; height: 65px;
background: url(../assets/svg/trait-shield.svg) no-repeat; background: url(../assets/svg/trait-shield.svg) no-repeat;
background-size: 100%; background-size: 100%;
padding-top: 3px;
display: flex;
flex-direction: column;
align-items: center;
row-gap: 3px;
div { div {
filter: drop-shadow(0 0 3px black); filter: drop-shadow(0 0 3px black);
@ -278,6 +288,15 @@
flex-direction: column; flex-direction: column;
gap: 5px; gap: 5px;
&.separated {
border-bottom: 2px solid;
padding-bottom: 8px;
}
.form-group {
padding: 0 0.75rem;
}
.experience-inner-container { .experience-inner-container {
position: relative; position: relative;
display: flex; display: flex;

View file

@ -27,10 +27,11 @@
height: 65px; height: 65px;
background: url(../assets/svg/trait-shield.svg) no-repeat; background: url(../assets/svg/trait-shield.svg) no-repeat;
background-size: 100%; background-size: 100%;
padding-top: 4px; padding-top: 3px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
row-gap: 3px;
span { span {
font-size: var(--font-size-10); font-size: var(--font-size-10);

View file

@ -2,7 +2,7 @@
"id": "daggerheart", "id": "daggerheart",
"title": "Daggerheart", "title": "Daggerheart",
"description": "An unofficial implementation of the Daggerheart system", "description": "An unofficial implementation of the Daggerheart system",
"version": "2.1.1", "version": "2.1.2",
"compatibility": { "compatibility": {
"minimum": "14.359", "minimum": "14.359",
"verified": "14.360", "verified": "14.360",
@ -10,7 +10,7 @@
}, },
"url": "https://github.com/Foundryborne/daggerheart", "url": "https://github.com/Foundryborne/daggerheart",
"manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json",
"download": "https://github.com/Foundryborne/daggerheart/releases/download/2.1.1/system.zip", "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.1.2/system.zip",
"authors": [ "authors": [
{ {
"name": "WBHarry" "name": "WBHarry"

View file

@ -4,17 +4,25 @@
data-group='{{tabs.experience.group}}' data-group='{{tabs.experience.group}}'
> >
<div class="main-selections-container"> <div class="main-selections-container">
<fieldset class="section-container"> <fieldset class="section-container no-horizontal-padding">
<legend>{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.initialExperiences"}} {{experience.nrSelected}}/{{experience.nrTotal}}</legend> <legend>{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.initialExperiences"}} {{experience.nrSelected}}/{{experience.nrTotal}}</legend>
<div class="experiences-inner-container"> <div class="experiences-inner-container">
{{#each experience.values as |experience id|}} {{#each experience.values as |experience id|}}
<div class="experience-container"> <div class="experience-container {{#unless @last}}separated{{/unless}}">
<div class="experience-inner-container"> <div class="form-group">
<input class="experience-description" type="text" name="{{concat "experiences." id ".name" }}" value="{{experience.name}}" placeholder="{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.newExperience"}}" /> <label>{{localize "DAGGERHEART.GENERAL.label"}}</label>
<span class="experience-value">{{numberFormat this.value sign=true}}</span> <div class="experience-inner-container">
<input class="experience-description" type="text" name="{{concat "experiences." id ".name" }}" value="{{experience.name}}" placeholder="{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.newExperience"}}" />
<span class="experience-value">{{numberFormat this.value sign=true}}</span>
</div>
</div> </div>
<textarea name="{{concat "experiences." id ".description"}}">{{experience.description}}</textarea> <div class="form-group">
<label>{{localize "DAGGERHEART.GENERAL.optionalThing" thing=(localize "DAGGERHEART.GENERAL.description")}}</label>
<div class="form-fields">
<textarea name="{{concat "experiences." id ".description"}}">{{experience.description}}</textarea>
</div>
</div>
</div> </div>
{{/each}} {{/each}}
</div> </div>

View file

@ -1,3 +1,3 @@
<footer> <footer>
<button data-action="finish">{{localize "Save Settings"}}</button> <button data-action="finish">{{localize "DAGGERHEART.GENERAL.saveSettings"}}</button>
</footer> </footer>

View file

@ -26,11 +26,11 @@
{{formGroup systemFields.duration.fields.type value=source.system.duration.type localize=true }} {{formGroup systemFields.duration.fields.type value=source.system.duration.type localize=true }}
<div class="form-group slim duration-description"> <div class="form-group slim duration-description {{#if (eq source.system.duration.type 'temporary')}}visible{{/if}}">
<div class="form-fields"> <div class="form-fields">
{{formInput systemFields.duration.fields.description value=source.system.duration.description localize=true }} {{formInput systemFields.duration.fields.description value=source.system.duration.description localize=true }}
</div>
</div> </div>
</div>
<div class="custom-duration-section"> <div class="custom-duration-section">
{{formGroup fields.start.fields.time value=source.start.time localize=true }} {{formGroup fields.start.fields.time value=source.start.time localize=true }}

View file

@ -5,9 +5,9 @@
<h1 class='item-name'><input type='text' name='name' value='{{source.name}}' /></h1> <h1 class='item-name'><input type='text' name='name' value='{{source.name}}' /></h1>
<div class='item-description'> <div class='item-description'>
{{#if source.system.secondary}} {{#if source.system.secondary}}
<h3>{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon"}}</h3> <h3>{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full"}}</h3>
{{else}} {{else}}
<h3>{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon"}}</h3> <h3>{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon.full"}}</h3>
{{/if}} {{/if}}
<h3> <h3>
{{localize (concat 'DAGGERHEART.CONFIG.Traits.' source.system.attack.roll.trait '.short')}} {{localize (concat 'DAGGERHEART.CONFIG.Traits.' source.system.attack.roll.trait '.short')}}

View file

@ -7,9 +7,9 @@
{{#each category as |grouping index|}} {{#each category as |grouping index|}}
<div class="battlepoint-grouping-container"> <div class="battlepoint-grouping-container">
{{#if grouping.nr}} {{#if grouping.nr}}
<label>{{key}} BP: {{concat (localize grouping.description) ' ' '('grouping.nr 'x)'}}</label> <label>{{key}} {{localize "DAGGERHEART.GENERAL.Battlepoints.short"}} - {{concat (localize grouping.description) ' ' '('grouping.nr 'x)'}}</label>
{{else}} {{else}}
<label class="unselected-grouping">{{key}} BP - {{localize grouping.description}}</label> <label class="unselected-grouping">{{key}} {{localize "DAGGERHEART.GENERAL.Battlepoints.short"}} - {{localize grouping.description}}</label>
{{/if}} {{/if}}
</div> </div>
{{/each}} {{/each}}
@ -26,7 +26,7 @@
{{else}} {{else}}
<input type="checkbox" data-combat-id="{{@root.combatId}}" data-category="{{toggle.categoryKey}}" data-grouping="{{toggle.toggleKey}}" {{checked toggle.checked}} /> <input type="checkbox" data-combat-id="{{@root.combatId}}" data-category="{{toggle.categoryKey}}" data-grouping="{{toggle.toggleKey}}" {{checked toggle.checked}} />
{{/if}} {{/if}}
<label class="unselected-grouping">{{toggle.categoryKey}} BP: {{localize toggle.description}}</label> <label class="unselected-grouping">{{toggle.categoryKey}} {{localize "DAGGERHEART.GENERAL.Battlepoints.short"}}: {{localize toggle.description}}</label>
</div> </div>
{{/each}} {{/each}}
</div> </div>

View file

@ -3,7 +3,7 @@
<h2 class="tooltip-title">{{item.name}}</h2> <h2 class="tooltip-title">{{item.name}}</h2>
<div class="tags"> <div class="tags">
<div class="tag"> <div class="tag">
<span>{{#if item.system.secondary}}{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon"}}{{else}}{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon"}}{{/if}}</span> <span>{{#if item.system.secondary}}{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full"}}{{else}}{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon.full"}}{{/if}}</span>
</div> </div>
<div class="tag"> <div class="tag">
{{#with (lookup config.GENERAL.burden item.system.burden) as | burden |}} {{#with (lookup config.GENERAL.burden item.system.burden) as | burden |}}