[Feature] Active Party (#1803)

This commit is contained in:
WBHarry 2026-04-16 02:26:39 +02:00 committed by GitHub
parent 8808e4646d
commit 7d5cdeb09d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 179 additions and 71 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,6 +3212,8 @@
"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 Active 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

@ -46,7 +46,8 @@ 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 => {
@ -89,7 +90,23 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
ui.notifications.info(`Tier ${tier} ${actor.name} created`); ui.notifications.info(`Tier ${tier} ${actor.name} created`);
} }
} }
}); },
{
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty',
icon: `<i class="fa-regular fa-square"></i>`,
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');
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty, actor.id);
ui.actors.render();
}
}
);
return options; return options;
} }

View file

@ -40,7 +40,8 @@ export const gameSettings = {
LastMigrationVersion: 'LastMigrationVersion', LastMigrationVersion: 'LastMigrationVersion',
SpotlightRequestQueue: 'SpotlightRequestQueue', SpotlightRequestQueue: 'SpotlightRequestQueue',
CompendiumBrowserSettings: 'CompendiumBrowserSettings', CompendiumBrowserSettings: 'CompendiumBrowserSettings',
SpotlightTracker: 'SpotlightTracker' SpotlightTracker: 'SpotlightTracker',
ActiveParty: 'ActiveParty',
}; };
export const actionAutomationChoices = { export const actionAutomationChoices = {

View file

@ -18,6 +18,10 @@ export default class DhParty extends BaseDataActor {
}; };
} }
get active() {
return game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty) === this.parent.id;
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/**@inheritdoc */ /**@inheritdoc */
@ -40,6 +44,16 @@ export default class DhParty extends BaseDataActor {
} }
} }
_onCreate(data, options, userId) {
super._onCreate(data, options, userId);
if (game.user.isActiveGM && !game.actors.party) {
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty, this.parent.id).then(_ => {
ui.actors.render();
});
}
}
_onDelete(options, userId) { _onDelete(options, userId) {
super._onDelete(options, userId); super._onDelete(options, userId);
@ -47,5 +61,11 @@ export default class DhParty extends BaseDataActor {
for (const member of this.partyMembers) { for (const member of this.partyMembers) {
member?.parties?.delete(this.parent); member?.parties?.delete(this.parent);
} }
// If this *was* the active party, delete it. We can't use game.actors.party as this actor was already deleted
const isWorldActor = !this.parent?.parent && !this.parent.compendium;
const activePartyId = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty);
if (isWorldActor && this.id === activePartyId)
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty, null);
} }
} }

View file

@ -1,3 +1,4 @@
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;
@ -36,13 +37,12 @@ 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) {
let count = summon.count; const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item));
const roll = new Roll(summon.count);
if (!roll.isDeterministic) {
await roll.evaluate(); await roll.evaluate();
if (game.modules.get('dice-so-nice')?.active) rolls.push(roll); const count = roll.total;
count = roll.total; if (!roll.isDeterministic && game.modules.get('dice-so-nice')?.active)
} rolls.push(roll);
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

@ -117,7 +117,9 @@ export default class DhpActor extends Actor {
} }
} }
async _preDelete() { async _preDelete(options, user) {
if ((await super._preDelete(options, user)) === false) return false;
if (this.prototypeToken.actorLink) { if (this.prototypeToken.actorLink) {
game.system.registeredTriggers.unregisterItemTriggers(this.items); game.system.registeredTriggers.unregisterItemTriggers(this.items);
} else { } else {
@ -600,6 +602,7 @@ 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

@ -1,4 +1,11 @@
export default class DhActorCollection extends foundry.documents.collections.Actors { export default class DhActorCollection extends foundry.documents.collections.Actors {
/** @returns the active party */
get party() {
const id = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty);
const actor = game.actors.get(id);
return actor?.type === "party" ? actor : null;
}
/** Ensure companions are initialized after all other subtypes. */ /** Ensure companions are initialized after all other subtypes. */
_initialize() { _initialize() {
super._initialize(); super._initialize();

View file

@ -189,7 +189,13 @@ 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, data = {}, { missing, warn = false } = {}) { Roll.replaceFormulaData = function (formula, baseData = {}, { missing, warn = false } = {}) {
/* Inserting global data */
const data = {
...baseData,
partySize: game.actors?.party?.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

@ -189,4 +189,11 @@ const registerNonConfigSettings = () => {
config: false, config: false,
type: SpotlightTracker type: SpotlightTracker
}); });
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty, {
scope: 'world',
config: false,
type: String,
default: null,
});
}; };

View file

@ -174,12 +174,9 @@
"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
}, },
@ -230,7 +227,7 @@
"saturation": 0, "saturation": 0,
"contrast": 0 "contrast": 0
}, },
"detectionModes": [], "detectionModes": {},
"occludable": { "occludable": {
"radius": 0 "radius": 0
}, },
@ -256,7 +253,8 @@
"flags": {}, "flags": {},
"randomImg": false, "randomImg": false,
"appendNumber": false, "appendNumber": false,
"prependAdjective": false "prependAdjective": false,
"depth": 1
}, },
"items": [ "items": [
{ {
@ -496,34 +494,42 @@
"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": {
"hGMzqw00JTlYfHYy": { "SrU7qbh8LcOgfozT": {
"type": "effect", "type": "summon",
"_id": "hGMzqw00JTlYfHYy", "_id": "SrU7qbh8LcOgfozT",
"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,
"step": null "itemId": 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",
"img": "icons/magic/death/undead-skeleton-worn-blue.webp", "range": "far"
"range": ""
} }
}, },
"originItemType": null, "originItemType": null,

View file

@ -52,12 +52,9 @@
"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
}, },
@ -108,7 +105,7 @@
"saturation": 0, "saturation": 0,
"contrast": 0 "contrast": 0
}, },
"detectionModes": [], "detectionModes": {},
"occludable": { "occludable": {
"radius": 0 "radius": 0
}, },
@ -134,7 +131,8 @@
"flags": {}, "flags": {},
"randomImg": false, "randomImg": false,
"appendNumber": false, "appendNumber": false,
"prependAdjective": false "prependAdjective": false,
"depth": 1
}, },
"items": [ "items": [
{ {
@ -323,7 +321,42 @@
"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

@ -14,6 +14,10 @@
{{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>