Compare commits

..

No commits in common. "c6335980bad7ea5c9de61b55c6268b140e772acd" and "94f1fbdd9b9ebb6705a70e7694699aec1cb61bbf" have entirely different histories.

23 changed files with 307 additions and 262 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}}/v14/system.json manifest: https://raw.githubusercontent.com/${{github.repository}}/main/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,9 +1,78 @@
# Contributing to Daggerheart # Contributing to Foundryborne
Thank you for your interest in contributing to the Foundryborne project! 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.
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.** ---
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! ## 🤝 How to Contribute
Thank you for your understanding and support. 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.
### 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,8 +355,6 @@ 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,7 +74,9 @@
"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",
@ -2014,10 +2016,6 @@
"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",
@ -2433,7 +2431,6 @@
"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",
@ -2460,7 +2457,6 @@
"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",
@ -2615,14 +2611,8 @@
}, },
"Weapon": { "Weapon": {
"weaponType": "Weapon Type", "weaponType": "Weapon Type",
"primaryWeapon": { "primaryWeapon": "Primary Weapon",
"full": "Primary Weapon", "secondaryWeapon": "Secondary Weapon"
"short": "Primary"
},
"secondaryWeapon": {
"full": "Secondary Weapon",
"short": "Secondary"
}
} }
}, },
"MACROS": { "MACROS": {
@ -3077,7 +3067,6 @@
}, },
"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,10 +11,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
this.character = character; this.character = character;
this.setup = { this.setup = {
traits: Object.keys(this.character.system.traits).reduce((acc, key) => { traits: this.character.system.traits,
acc[key] = { value: null };
return acc;
}, {}),
ancestryName: { ancestryName: {
primary: '', primary: '',
secondary: '' secondary: ''
@ -380,10 +377,8 @@ 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 += 1; acc += index !== -1;
return acc; return acc;
}, 0); }, 0);
} }

View file

@ -122,14 +122,15 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
async toggleClowncar(actors) { async toggleClowncar(actors) {
const animationDuration = 500; const animationDuration = 500;
const scene = game.scenes.get(game.user.viewedScene); const activeTokens = actors.flatMap(member => member.getActiveTokens());
/* 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.update({ x: actorX, y: actorY, alpha: 0 }, { animation: { duration: animationDuration } }); await token.document.update(
setTimeout(() => token.delete(), animationDuration); { x: actorX, y: actorY, alpha: 0 },
{ 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);
@ -139,16 +140,11 @@ 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: 'DAGGERHEART.UI.ItemBrowser.windowTitle', title: 'Compendium Browser',
icon: 'fa-solid fa-book-atlas', icon: 'fa-solid fa-book-atlas',
positioned: true, positioned: true,
resizable: true resizable: true
@ -583,9 +583,7 @@ 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 +1,16 @@
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,13 +74,12 @@ 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 ? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short' : isSecondary === false ? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short' : '-') format: isSecondary => (isSecondary ? 'secondary' : isSecondary === false ? 'primary' : '-')
}, },
{ {
key: 'system.tier', key: 'system.tier',
@ -100,8 +99,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.full' }, { value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon' },
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full' } { value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' }
] ]
}, },
{ {
@ -259,13 +258,11 @@ 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',
@ -377,7 +374,7 @@ export const typeConfig = {
columns: [ columns: [
{ {
key: 'system.linkedClass', key: 'system.linkedClass',
label: 'TYPES.Item.class', label: 'Class',
format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing' format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
}, },
{ {
@ -389,7 +386,7 @@ export const typeConfig = {
filters: [ filters: [
{ {
key: 'system.linkedClass.uuid', key: 'system.linkedClass.uuid',
label: 'TYPES.Item.class', label: 'Class',
choices: items => { choices: items => {
const list = items const list = items
.filter(item => item.system.linkedClass) .filter(item => item.system.linkedClass)
@ -413,8 +410,7 @@ 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,18 +44,12 @@ export default class DHSummonField extends fields.ArrayField {
count = roll.total; count = roll.total;
} }
const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID)); const actor = 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();
const countNumber = Number.parseInt(count); summonData.push({ actor, count: 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)));
@ -64,22 +58,32 @@ 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. If none exists, create one. */ /* Check for any available instances of the actor present in the world if we're missing artwork in the compendium */
static async getWorldActor(baseActor) { static 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);
if (worldActorCopy) return worldActorCopy; return worldActorCopy ?? baseActor;
return await game.system.api.documents.DhpActor.create(baseActor.toObject());
} }
return baseActor; return baseActor;
} }
static async handleSummon(summonData, actionActor) { static async handleSummon(summonData, actionActor, summonIndex = 0) {
await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation }); const summon = summonData[summonIndex];
const result = await CONFIG.ux.TokenManager.createPreviewAsync(summon.actor, {
name: `${summon.actor.prototypeToken.name}${summon.count > 1 ? ` (${summon.count}x)` : ''}`
});
return actionActor.sheet?.maximize(); if (!result) 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,68 +1,104 @@
/** /**
* A singleton class that handles creating tokens. * A singleton class that handles preview tokens.
*/ */
export default class DhTokenManager { export default class DhTokenManager {
#activePreview;
#actor;
#resolve;
/** /**
* Create a token previer * Create a template preview, deactivating any existing ones.
* @param {Actor} actor * @param {object} data
* @param {object} tokenData
*/ */
async createPreview(actor, tokenData) { async createPreview(actor, tokenData) {
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes; this.#actor = actor;
if (actor?.system.metadata.usesSize) { const token = await canvas.tokens._createPreview(
const tokenSize = tokenSizes[actor.system.size]; {
if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) { ...actor.prototypeToken,
tokenData.width = tokenSize; displayName: 50,
tokenData.height = tokenSize; ...tokenData
} },
} { 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 } } });
});
} }
/** /**
* Creates new tokens on the canvas by placing previews. * Handles the movement of the token preview on mousedrag.
* @param {object} tokenData * @param {mousemove Event} event
* @param {object} options
*/ */
async createTokensWithPreview(tokensData, { elevation } = {}) { #onDragMouseMove(event) {
const scene = game.scenes.get(game.user.viewedScene); event.stopPropagation();
if (!scene) return; const { moveTime, object } = this.#activePreview;
const update = {};
const level = scene.levels.get(game.user.viewedLevel); const now = Date.now();
if (!level) return; if (now - (moveTime || 0) <= 16) return;
this.#activePreview.moveTime = now;
const createElevation = elevation ?? level.elevation.bottom; let cursor = event.getLocalPosition(canvas.templates);
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;
await canvas.scene.createEmbeddedDocuments( Object.assign(update, canvas.grid.getTopLeftPoint(cursor));
'Token',
previewTokens.map(x => ({ object.document.updateSource(update);
...x.toObject(), object.renderFlags.set({ refresh: true });
name: tokenData.actor.prototypeToken.name, }
displayName: tokenData.actor.prototypeToken.displayName,
flags: tokenData.actor.prototypeToken.flags /**
})), * Cancels the preview token on right-click.
{ controlObject: true, parent: canvas.scene } * @param {contextmenu Event} event
); */
} #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,33 +68,31 @@
"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": {
"value": null, "startTime": null,
"units": "seconds", "combat": null,
"expiry": null, "seconds": null,
"expired": false "rounds": null,
"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",
@ -104,16 +102,6 @@
"_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,28 +29,39 @@
"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": {
"value": null, "startTime": null,
"units": "seconds", "combat": null,
"expiry": null, "seconds": null,
"expired": false "rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
}, },
"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>", "description": "",
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
"transfer": true, "transfer": true,
@ -60,16 +71,6 @@
"_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,29 +132,27 @@
"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": {
"value": null, "startTime": null,
"units": "seconds", "combat": null,
"expiry": null, "seconds": null,
"expired": false "rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
}, },
"description": "<p>You gain advantage on attack rolls until you or an ally rolls a failure with Fear.</p>", "description": "",
"tint": "#ffffff", "tint": "#ffffff",
"statuses": [], "statuses": [],
"sort": 0, "sort": 0,
@ -162,16 +160,6 @@
"_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,11 +175,6 @@
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;
@ -255,11 +250,6 @@
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);
@ -288,15 +278,6 @@
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,11 +27,10 @@
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; padding-top: 4px;
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.2", "version": "2.1.1",
"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.2/system.zip", "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.1.1/system.zip",
"authors": [ "authors": [
{ {
"name": "WBHarry" "name": "WBHarry"

View file

@ -4,25 +4,17 @@
data-group='{{tabs.experience.group}}' data-group='{{tabs.experience.group}}'
> >
<div class="main-selections-container"> <div class="main-selections-container">
<fieldset class="section-container no-horizontal-padding"> <fieldset class="section-container">
<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 {{#unless @last}}separated{{/unless}}"> <div class="experience-container">
<div class="form-group"> <div class="experience-inner-container">
<label>{{localize "DAGGERHEART.GENERAL.label"}}</label> <input class="experience-description" type="text" name="{{concat "experiences." id ".name" }}" value="{{experience.name}}" placeholder="{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.newExperience"}}" />
<div class="experience-inner-container"> <span class="experience-value">{{numberFormat this.value sign=true}}</span>
<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>
<div class="form-group"> <textarea name="{{concat "experiences." id ".description"}}">{{experience.description}}</textarea>
<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 "DAGGERHEART.GENERAL.saveSettings"}}</button> <button data-action="finish">{{localize "Save Settings"}}</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 {{#if (eq source.system.duration.type 'temporary')}}visible{{/if}}"> <div class="form-group slim duration-description">
<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.full"}}</h3> <h3>{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon"}}</h3>
{{else}} {{else}}
<h3>{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon.full"}}</h3> <h3>{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon"}}</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}} {{localize "DAGGERHEART.GENERAL.Battlepoints.short"}} - {{concat (localize grouping.description) ' ' '('grouping.nr 'x)'}}</label> <label>{{key}} BP: {{concat (localize grouping.description) ' ' '('grouping.nr 'x)'}}</label>
{{else}} {{else}}
<label class="unselected-grouping">{{key}} {{localize "DAGGERHEART.GENERAL.Battlepoints.short"}} - {{localize grouping.description}}</label> <label class="unselected-grouping">{{key}} BP - {{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}} {{localize "DAGGERHEART.GENERAL.Battlepoints.short"}}: {{localize toggle.description}}</label> <label class="unselected-grouping">{{toggle.categoryKey}} BP: {{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.full"}}{{else}}{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon.full"}}{{/if}}</span> <span>{{#if item.system.secondary}}{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon"}}{{else}}{{localize "DAGGERHEART.ITEMS.Weapon.primaryWeapon"}}{{/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 |}}