Compare commits

..

2 commits

Author SHA1 Message Date
WBHarry
e3673c50c6 Revert "Made the active PartySize available as a parameter for formulas"
This reverts commit ca097c21a3.
2026-04-15 00:37:30 +02:00
WBHarry
e4b3f5d5bd Finished 2026-04-15 00:35:10 +02:00
14 changed files with 129 additions and 167 deletions

View file

@ -236,6 +236,8 @@
}, },
"defaultHopeDice": "Default Hope Dice", "defaultHopeDice": "Default Hope Dice",
"defaultFearDice": "Default Fear Dice", "defaultFearDice": "Default Fear Dice",
"defaultAdvantageDice": "Default Advantage Dice",
"defaultDisadvantageDice": "Default Disadvantage Dice",
"disadvantageSources": { "disadvantageSources": {
"label": "Disadvantage Sources", "label": "Disadvantage Sources",
"hint": "Add single words or short text as reminders and hints of what a character has disadvantage on." "hint": "Add single words or short text as reminders and hints of what a character has disadvantage on."
@ -3210,8 +3212,6 @@
"companion": "Level {level} - {partner}", "companion": "Level {level} - {partner}",
"companionNoPartner": "No Partner", "companionNoPartner": "No Partner",
"duplicateToNewTier": "Duplicate to New Tier", "duplicateToNewTier": "Duplicate to New Tier",
"activateParty": "Make Activate Party",
"partyIsActive": "Active",
"createAdversary": "Create Adversary", "createAdversary": "Create Adversary",
"pickTierTitle": "Pick a new tier for this adversary" "pickTierTitle": "Pick a new tier for this adversary"
}, },

View file

@ -123,6 +123,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.advantage = this.config.roll?.advantage; context.advantage = this.config.roll?.advantage;
context.disadvantage = this.config.roll?.disadvantage; context.disadvantage = this.config.roll?.disadvantage;
context.diceOptions = CONFIG.DH.GENERAL.diceTypes; context.diceOptions = CONFIG.DH.GENERAL.diceTypes;
context.diceFaces = CONFIG.DH.GENERAL.dieFaces.reduce((acc, face) => {
acc[face] = `d${face}`;
return acc;
}, {});
context.isLite = this.config.roll?.lite; context.isLite = this.config.roll?.lite;
context.extraFormula = this.config.extraFormula; context.extraFormula = this.config.extraFormula;
context.formula = this.roll.constructFormula(this.config); context.formula = this.roll.constructFormula(this.config);
@ -153,7 +157,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
if (this.config.uses) this.config.uses = foundry.utils.mergeObject(this.config.uses, rest.uses); if (this.config.uses) this.config.uses = foundry.utils.mergeObject(this.config.uses, rest.uses);
if (rest.roll?.dice) { if (rest.roll?.dice) {
Object.entries(rest.roll.dice).forEach(([key, value]) => { Object.entries(rest.roll.dice).forEach(([key, value]) => {
this.roll[key] = value; if(key === 'advantageFaces')
this.roll[key] = Number.parseInt(value);
else
this.roll[key] = value;
}); });
} }
if (rest.hasOwnProperty('trait')) { if (rest.hasOwnProperty('trait')) {
@ -173,6 +180,16 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.disadvantage = advantage === -1; this.disadvantage = advantage === -1;
this.config.roll.advantage = this.config.roll.advantage === advantage ? 0 : advantage; this.config.roll.advantage = this.config.roll.advantage === advantage ? 0 : advantage;
if(this.config.roll.advantage === 1 && this.config.data.rules.roll.defaultAdvantageDice) {
const faces = Number.parseInt(this.config.data.rules.roll.defaultAdvantageDice);
this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces;
}
else if(this.config.roll.advantage === -1 && this.config.data.rules.roll.defaultDisadvantageDice) {
const faces = Number.parseInt(this.config.data.rules.roll.defaultDisadvantageDice);
this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces;
}
this.render(); this.render();
} }

View file

@ -46,71 +46,50 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
_getEntryContextOptions() { _getEntryContextOptions() {
const options = super._getEntryContextOptions(); const options = super._getEntryContextOptions();
options.push( options.push({
{ name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier', icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`, condition: li => {
condition: li => { const actor = game.actors.get(li.dataset.entryId);
const actor = game.actors.get(li.dataset.entryId); return actor?.type === 'adversary' && actor.system.type !== 'social';
return actor?.type === 'adversary' && actor.system.type !== 'social';
},
callback: async li => {
const actor = game.actors.get(li.dataset.entryId);
if (!actor) throw new Error('Unexpected missing actor');
const tiers = [1, 2, 3, 4].filter(t => t !== actor.system.tier);
const content = document.createElement('div');
const select = document.createElement('select');
select.name = 'tier';
select.append(
...tiers.map(t => {
const option = document.createElement('option');
option.value = t;
option.textContent = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${t}`);
return option;
})
);
content.append(select);
const tier = await foundry.applications.api.Dialog.input({
classes: ['dh-style', 'dialog'],
window: { title: 'DAGGERHEART.UI.Sidebar.actorDirectory.pickTierTitle' },
content,
ok: {
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.createAdversary',
callback: (event, button, dialog) => Number(button.form.elements.tier.value)
}
});
if (tier === actor.system.tier) {
ui.notifications.warn('This actor is already at this tier');
} else if (tier) {
const source = actor.system.adjustForTier(tier);
await Actor.create(source);
ui.notifications.info(`Tier ${tier} ${actor.name} created`);
}
}
}, },
{ callback: async li => {
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty', const actor = game.actors.get(li.dataset.entryId);
icon: `<i class="fa-regular fa-square"></i>`, if (!actor) throw new Error('Unexpected missing actor');
condition: li => {
const actor = game.actors.get(li.dataset.entryId);
return actor && actor.type === 'party' && !actor.system.active;
},
callback: async li => {
const actor = game.actors.get(li.dataset.entryId);
if (!actor) throw new Error('Unexpected missing actor');
const currentActiveParty = game.actors.find(x => x.type === 'party' && x.system.active); const tiers = [1, 2, 3, 4].filter(t => t !== actor.system.tier);
if (currentActiveParty) const content = document.createElement('div');
await currentActiveParty.update({ 'system.active': false }); const select = document.createElement('select');
select.name = 'tier';
select.append(
...tiers.map(t => {
const option = document.createElement('option');
option.value = t;
option.textContent = game.i18n.localize(`DAGGERHEART.GENERAL.Tiers.${t}`);
return option;
})
);
content.append(select);
await actor.update({ 'system.active': true }); const tier = await foundry.applications.api.Dialog.input({
ui.actors.render(); classes: ['dh-style', 'dialog'],
window: { title: 'DAGGERHEART.UI.Sidebar.actorDirectory.pickTierTitle' },
content,
ok: {
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.createAdversary',
callback: (event, button, dialog) => Number(button.form.elements.tier.value)
}
});
if (tier === actor.system.tier) {
ui.notifications.warn('This actor is already at this tier');
} else if (tier) {
const source = actor.system.adjustForTier(tier);
await Actor.create(source);
ui.notifications.info(`Tier ${tier} ${actor.name} created`);
} }
} }
); });
return options; return options;
} }

View file

@ -289,7 +289,23 @@ export default class DhCharacter extends DhCreature {
guaranteedCritical: new fields.BooleanField({ guaranteedCritical: new fields.BooleanField({
label: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.label', label: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.label',
hint: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.hint' hint: 'DAGGERHEART.ACTORS.Character.roll.guaranteedCritical.hint'
}) }),
defaultAdvantageDice: new fields.NumberField({
nullable: true,
required: true,
integer: true,
choices: CONFIG.DH.GENERAL.dieFaces,
initial: null,
label: 'DAGGERHEART.ACTORS.Character.defaultAdvantageDice'
}),
defaultDisadvantageDice: new fields.NumberField({
nullable: true,
required: true,
integer: true,
choices: CONFIG.DH.GENERAL.dieFaces,
initial: null,
label: 'DAGGERHEART.ACTORS.Character.defaultDisadvantageDice'
}),
}) })
}) })
}; };

View file

@ -61,6 +61,24 @@ export default class DhCompanion extends DhCreature {
initial: false, initial: false,
label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.vulnerable' label: 'DAGGERHEART.GENERAL.Rules.conditionImmunities.vulnerable'
}) })
}),
roll: new fields.SchemaField({
defaultAdvantageDice: new fields.NumberField({
nullable: true,
required: true,
integer: true,
choices: CONFIG.DH.GENERAL.dieFaces,
initial: null,
label: 'DAGGERHEART.ACTORS.Character.defaultAdvantageDice'
}),
defaultDisadvantageDice: new fields.NumberField({
nullable: true,
required: true,
integer: true,
choices: CONFIG.DH.GENERAL.dieFaces,
initial: null,
label: 'DAGGERHEART.ACTORS.Character.defaultDisadvantageDice'
}),
}) })
}), }),
attack: new ActionField({ attack: new ActionField({

View file

@ -9,7 +9,6 @@ export default class DhParty extends BaseDataActor {
const fields = foundry.data.fields; const fields = foundry.data.fields;
return { return {
...super.defineSchema(), ...super.defineSchema(),
active: new fields.BooleanField(),
partyMembers: new ForeignDocumentUUIDArrayField({ type: 'Actor' }, { prune: true }), partyMembers: new ForeignDocumentUUIDArrayField({ type: 'Actor' }, { prune: true }),
notes: new fields.HTMLField(), notes: new fields.HTMLField(),
gold: new fields.SchemaField({ gold: new fields.SchemaField({
@ -45,15 +44,6 @@ export default class DhParty extends BaseDataActor {
} }
} }
/**@inheritdoc */
async _preCreate(data, options, user) {
const allowed = await super._preCreate(data, options, user);
if (allowed === false) return;
if (!game.actors.some(x => x.type === 'party' && x.active))
await this.updateSource({ active: true });
}
_onDelete(options, userId) { _onDelete(options, userId) {
super._onDelete(options, userId); super._onDelete(options, userId);

View file

@ -1,4 +1,3 @@
import { itemAbleRollParse } from '../../../helpers/utils.mjs';
import FormulaField from '../formulaField.mjs'; import FormulaField from '../formulaField.mjs';
const fields = foundry.data.fields; const fields = foundry.data.fields;
@ -37,12 +36,13 @@ export default class DHSummonField extends fields.ArrayField {
const rolls = []; const rolls = [];
const summonData = []; const summonData = [];
for (const summon of this.summon) { for (const summon of this.summon) {
const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item)); let count = summon.count;
await roll.evaluate(); const roll = new Roll(summon.count);
const count = roll.total; if (!roll.isDeterministic) {
if (!roll.isDeterministic && game.modules.get('dice-so-nice')?.active) await roll.evaluate();
rolls.push(roll); if (game.modules.get('dice-so-nice')?.active) rolls.push(roll);
count = roll.total;
}
const actor = await 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. */

View file

@ -3,7 +3,6 @@ import D20Roll from './d20Roll.mjs';
import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
export default class DualityRoll extends D20Roll { export default class DualityRoll extends D20Roll {
_advantageFaces = 6;
_advantageNumber = 1; _advantageNumber = 1;
_rallyIndex; _rallyIndex;
@ -11,6 +10,9 @@ export default class DualityRoll extends D20Roll {
super(formula, data, options); super(formula, data, options);
this.rallyChoices = this.setRallyChoices(); this.rallyChoices = this.setRallyChoices();
this.guaranteedCritical = options.guaranteedCritical; this.guaranteedCritical = options.guaranteedCritical;
const advantageFaces = data.rules?.roll?.defaultAdvantageDice ? Number.parseInt(data.rules.roll.defaultAdvantageDice) : 6
this.advantageFaces = Number.isNaN(advantageFaces) ? 6 : advantageFaces;
} }
static messageType = 'dualityRoll'; static messageType = 'dualityRoll';
@ -51,14 +53,6 @@ export default class DualityRoll extends D20Roll {
return this.dice[2] instanceof game.system.api.dice.diceTypes.DisadvantageDie ? this.dice[2] : null; return this.dice[2] instanceof game.system.api.dice.diceTypes.DisadvantageDie ? this.dice[2] : null;
} }
get advantageFaces() {
return this._advantageFaces;
}
set advantageFaces(faces) {
this._advantageFaces = this.getFaces(faces);
}
get advantageNumber() { get advantageNumber() {
return this._advantageNumber; return this._advantageNumber;
} }

View file

@ -600,7 +600,6 @@ export default class DhpActor extends Actor {
rollData.system = this.system.getRollData(); rollData.system = this.system.getRollData();
rollData.prof = this.system.proficiency ?? 1; rollData.prof = this.system.proficiency ?? 1;
rollData.cast = this.system.spellcastModifier ?? 1; rollData.cast = this.system.spellcastModifier ?? 1;
return rollData; return rollData;
} }

View file

@ -189,15 +189,7 @@ export const getDeleteKeys = (property, innerProperty, innerPropertyDefaultValue
// Fix on Foundry native formula replacement for DH // Fix on Foundry native formula replacement for DH
const nativeReplaceFormulaData = Roll.replaceFormulaData; const nativeReplaceFormulaData = Roll.replaceFormulaData;
Roll.replaceFormulaData = function (formula, baseData = {}, { missing, warn = false } = {}) { Roll.replaceFormulaData = function (formula, data = {}, { missing, warn = false } = {}) {
/* Inserting global data */
const data = {
...baseData,
partySize:
!game.actors ? 0 :
game.actors.find(x => x.type === 'party' && x.system.active)?.system.partyMembers.length ?? 0,
};
const terms = Object.keys(CONFIG.DH.GENERAL.multiplierTypes).map(type => { const terms = Object.keys(CONFIG.DH.GENERAL.multiplierTypes).map(type => {
return { term: type, default: 1 }; return { term: type, default: 1 };
}); });

View file

@ -174,9 +174,12 @@
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
"anchorX": 0.5, "anchorX": 0.5,
"anchorY": 0.5, "anchorY": 0.5,
"offsetX": 0,
"offsetY": 0,
"fit": "contain", "fit": "contain",
"scaleX": 1, "scaleX": 1,
"scaleY": 1, "scaleY": 1,
"rotation": 0,
"tint": "#ffffff", "tint": "#ffffff",
"alphaThreshold": 0.75 "alphaThreshold": 0.75
}, },
@ -227,7 +230,7 @@
"saturation": 0, "saturation": 0,
"contrast": 0 "contrast": 0
}, },
"detectionModes": {}, "detectionModes": [],
"occludable": { "occludable": {
"radius": 0 "radius": 0
}, },
@ -253,8 +256,7 @@
"flags": {}, "flags": {},
"randomImg": false, "randomImg": false,
"appendNumber": false, "appendNumber": false,
"prependAdjective": false, "prependAdjective": false
"depth": 1
}, },
"items": [ "items": [
{ {
@ -494,42 +496,34 @@
"description": "<p><strong>Spend a Fear</strong> to summon a number of @UUID[Compendium.daggerheart.adversaries.Actor.OsLG2BjaEdTZUJU9]{Fallen Shock Troops} equal to twice the number of PCs. The Shock Troops appear at Far range.</p>", "description": "<p><strong>Spend a Fear</strong> to summon a number of @UUID[Compendium.daggerheart.adversaries.Actor.OsLG2BjaEdTZUJU9]{Fallen Shock Troops} equal to twice the number of PCs. The Shock Troops appear at Far range.</p>",
"resource": null, "resource": null,
"actions": { "actions": {
"SrU7qbh8LcOgfozT": { "hGMzqw00JTlYfHYy": {
"type": "summon", "type": "effect",
"_id": "SrU7qbh8LcOgfozT", "_id": "hGMzqw00JTlYfHYy",
"systemPath": "actions", "systemPath": "actions",
"baseAction": false,
"description": "", "description": "",
"chatDisplay": true, "chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action", "actionType": "action",
"triggers": [],
"cost": [ "cost": [
{ {
"scalable": false, "scalable": false,
"key": "fear", "key": "fear",
"value": 1, "value": 1,
"itemId": null, "step": null
"step": null,
"consumeOnSuccess": false
} }
], ],
"uses": { "uses": {
"value": null, "value": null,
"max": "", "max": "",
"recovery": null, "recovery": null
"consumeOnSuccess": false },
"effects": [],
"target": {
"type": "self",
"amount": null
}, },
"summon": [
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.OsLG2BjaEdTZUJU9",
"count": "@partySize*2"
}
],
"name": "Spend Fear", "name": "Spend Fear",
"range": "far" "img": "icons/magic/death/undead-skeleton-worn-blue.webp",
"range": ""
} }
}, },
"originItemType": null, "originItemType": null,

View file

@ -52,9 +52,12 @@
"src": "systems/daggerheart/assets/icons/documents/actors/forest.svg", "src": "systems/daggerheart/assets/icons/documents/actors/forest.svg",
"anchorX": 0.5, "anchorX": 0.5,
"anchorY": 0.5, "anchorY": 0.5,
"offsetX": 0,
"offsetY": 0,
"fit": "contain", "fit": "contain",
"scaleX": 1, "scaleX": 1,
"scaleY": 1, "scaleY": 1,
"rotation": 0,
"tint": "#ffffff", "tint": "#ffffff",
"alphaThreshold": 0.75 "alphaThreshold": 0.75
}, },
@ -105,7 +108,7 @@
"saturation": 0, "saturation": 0,
"contrast": 0 "contrast": 0
}, },
"detectionModes": {}, "detectionModes": [],
"occludable": { "occludable": {
"radius": 0 "radius": 0
}, },
@ -131,8 +134,7 @@
"flags": {}, "flags": {},
"randomImg": false, "randomImg": false,
"appendNumber": false, "appendNumber": false,
"prependAdjective": false, "prependAdjective": false
"depth": 1
}, },
"items": [ "items": [
{ {
@ -321,42 +323,7 @@
"system": { "system": {
"description": "<p>A @UUID[Compendium.daggerheart.adversaries.Actor.8yUj2Mzvnifhxegm]{Young Dryad}, two @UUID[Compendium.daggerheart.adversaries.Actor.VtFBt9XBE0WrGGxP]{Sylvan Soldiers}, and a number of @UUID[Compendium.daggerheart.adversaries.Actor.G62k4oSkhkoXEs2D]{Minor Treants} equal to the number of PCs appear to confront the party for their intrusion.</p><section id=\"secret-01hkXuVJc5Kei4QW\" class=\"secret\"><p><em>What are the grove guardians concealing? What threat to the forest could the PCs confront to appease the Dryad?</em></p></section>", "description": "<p>A @UUID[Compendium.daggerheart.adversaries.Actor.8yUj2Mzvnifhxegm]{Young Dryad}, two @UUID[Compendium.daggerheart.adversaries.Actor.VtFBt9XBE0WrGGxP]{Sylvan Soldiers}, and a number of @UUID[Compendium.daggerheart.adversaries.Actor.G62k4oSkhkoXEs2D]{Minor Treants} equal to the number of PCs appear to confront the party for their intrusion.</p><section id=\"secret-01hkXuVJc5Kei4QW\" class=\"secret\"><p><em>What are the grove guardians concealing? What threat to the forest could the PCs confront to appease the Dryad?</em></p></section>",
"resource": null, "resource": null,
"actions": { "actions": {},
"TPm6rpKA4mbili82": {
"type": "summon",
"_id": "TPm6rpKA4mbili82",
"systemPath": "actions",
"baseAction": false,
"description": "",
"chatDisplay": true,
"originItem": {
"type": "itemCollection"
},
"actionType": "action",
"triggers": [],
"cost": [],
"uses": {
"value": null,
"max": null,
"recovery": null,
"consumeOnSuccess": false
},
"summon": [
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.8yUj2Mzvnifhxegm",
"count": "1"
},
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.VtFBt9XBE0WrGGxP",
"count": "2"
},
{
"actorUUID": "Compendium.daggerheart.adversaries.Actor.G62k4oSkhkoXEs2D",
"count": "@partySize"
}
]
}
},
"originItemType": null, "originItemType": null,
"originId": null, "originId": null,
"featureForm": "action" "featureForm": "action"

View file

@ -158,7 +158,7 @@
{{/times}} {{/times}}
</select> </select>
<select name="roll.dice.advantageFaces"{{#unless advantage}} disabled{{/unless}}> <select name="roll.dice.advantageFaces"{{#unless advantage}} disabled{{/unless}}>
{{selectOptions diceOptions selected=(concat 'd' @root.roll.advantageFaces)}} {{selectOptions diceFaces selected=@root.roll.advantageFaces}}
</select> </select>
</div> </div>
{{#if abilities}} {{#if abilities}}

View file

@ -14,10 +14,6 @@
{{else}} {{else}}
<span class="entry-subtitle">{{localize "DAGGERHEART.UI.Sidebar.actorDirectory.companionNoPartner"}}</span> <span class="entry-subtitle">{{localize "DAGGERHEART.UI.Sidebar.actorDirectory.companionNoPartner"}}</span>
{{/if}} {{/if}}
{{else if (eq type "party")}}
{{#if system.active}}
<span class="entry-subtitle">{{localize "DAGGERHEART.UI.Sidebar.actorDirectory.partyIsActive"}}</span>
{{/if}}
{{/if}} {{/if}}
</a> </a>
</li> </li>