[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

@ -46,50 +46,67 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
_getEntryContextOptions() {
const options = super._getEntryContextOptions();
options.push({
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
condition: li => {
const actor = game.actors.get(li.dataset.entryId);
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');
options.push(
{
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
condition: li => {
const actor = game.actors.get(li.dataset.entryId);
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 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)
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`);
}
});
}
},
{
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');
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`);
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty, actor.id);
ui.actors.render();
}
}
});
);
return options;
}

View file

@ -40,7 +40,8 @@ export const gameSettings = {
LastMigrationVersion: 'LastMigrationVersion',
SpotlightRequestQueue: 'SpotlightRequestQueue',
CompendiumBrowserSettings: 'CompendiumBrowserSettings',
SpotlightTracker: 'SpotlightTracker'
SpotlightTracker: 'SpotlightTracker',
ActiveParty: 'ActiveParty',
};
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 */
@ -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) {
super._onDelete(options, userId);
@ -47,5 +61,11 @@ export default class DhParty extends BaseDataActor {
for (const member of this.partyMembers) {
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';
const fields = foundry.data.fields;
@ -36,13 +37,12 @@ export default class DHSummonField extends fields.ArrayField {
const rolls = [];
const summonData = [];
for (const summon of this.summon) {
let count = summon.count;
const roll = new Roll(summon.count);
if (!roll.isDeterministic) {
await roll.evaluate();
if (game.modules.get('dice-so-nice')?.active) rolls.push(roll);
count = roll.total;
}
const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item));
await roll.evaluate();
const 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));
/* 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) {
game.system.registeredTriggers.unregisterItemTriggers(this.items);
} else {
@ -600,6 +602,7 @@ export default class DhpActor extends Actor {
rollData.system = this.system.getRollData();
rollData.prof = this.system.proficiency ?? 1;
rollData.cast = this.system.spellcastModifier ?? 1;
return rollData;
}

View file

@ -1,4 +1,11 @@
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. */
_initialize() {
super._initialize();

View file

@ -189,7 +189,13 @@ export const getDeleteKeys = (property, innerProperty, innerPropertyDefaultValue
// Fix on Foundry native formula replacement for DH
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 => {
return { term: type, default: 1 };
});

View file

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