mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-07-22 18:39:55 +02:00
Merge branch 'main' into feature/enhance-fear-tracker
This commit is contained in:
commit
5e28d34729
76 changed files with 1641 additions and 1058 deletions
|
|
@ -51,7 +51,11 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
const context = await super._prepareContext(_options);
|
||||
context.config = CONFIG.DH;
|
||||
context.title = this.config.title ?? this.title;
|
||||
context.formula = this.roll.constructFormula(this.config);
|
||||
|
||||
const { damageFormula, resourceFormulas } = this.roll.constructFormulas(this.config);
|
||||
context.damageFormula = damageFormula;
|
||||
context.resourceFormulas = resourceFormulas;
|
||||
|
||||
context.hasHealing = this.config.hasHealing;
|
||||
context.directDamage = this.config.directDamage;
|
||||
context.selectedMessageMode = this.config.selectedMessageMode;
|
||||
|
|
@ -73,7 +77,11 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
|||
|
||||
static updateRollConfiguration(_event, _, formData) {
|
||||
const data = foundry.utils.expandObject(formData.object);
|
||||
foundry.utils.mergeObject(this.config.roll, data.roll);
|
||||
|
||||
if (this.config.damageFormula)
|
||||
foundry.utils.mergeObject(this.config.damageFormula, data.damageFormula);
|
||||
|
||||
foundry.utils.mergeObject(this.config.resourceFormulas, data.resourceFormulas);
|
||||
foundry.utils.mergeObject(this.config.modifiers, data.modifiers);
|
||||
this.config.selectedMessageMode = data.selectedMessageMode;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
||||
import { ChatDamageData } from '../../data/chat-message/chatDamageData.mjs';
|
||||
import { MemberData } from '../../data/tagTeamData.mjs';
|
||||
import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
|
@ -140,7 +141,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
const hasRolled = Boolean(data.rollData);
|
||||
if (!hasRolled) return false;
|
||||
|
||||
return !data.rollData.options.hasDamage || Boolean(data.rollData.options.damage);
|
||||
return !data.rollData.options.hasDamage || data.damageRollData.active;
|
||||
});
|
||||
|
||||
return context;
|
||||
|
|
@ -181,7 +182,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
||||
const critSelected = !selectedRoll
|
||||
? undefined
|
||||
: (selectedRoll?.rollData?.options?.roll?.isCritical ?? false);
|
||||
: (selectedRoll?.roll?.isCritical ?? false);
|
||||
|
||||
partContext.hintText = await this.getInfoTexts(this.party.system.tagTeam.members);
|
||||
partContext.joinedRoll = await this.getJoinedRoll({
|
||||
|
|
@ -235,8 +236,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
||||
const critSelected = !selectedRoll ? undefined : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false);
|
||||
const damage = data.rollData?.options?.damage;
|
||||
const critSelected = !selectedRoll ? undefined : (selectedRoll?.roll?.isCritical ?? false);
|
||||
|
||||
return {
|
||||
...data,
|
||||
|
|
@ -247,9 +247,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
hasRolled: Boolean(data.rollData),
|
||||
rollOptions,
|
||||
damageRollOptions,
|
||||
damage: damage,
|
||||
critDamage: await this.getCriticalDamage(damage),
|
||||
useCritDamage: critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical)
|
||||
damage: data.damageRollData,
|
||||
critDamage: await this.getCriticalDamage(data.damageRollData),
|
||||
useCritDamage: critSelected || (critSelected === undefined && data.roll?.isCritical)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -379,7 +379,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
let rollIsSelected = false;
|
||||
for (const member of Object.values(members)) {
|
||||
const rollFinished = Boolean(member.rollData);
|
||||
const damageFinished = member.rollData?.options?.hasDamage ? Boolean(member.rollData.options.damage) : true;
|
||||
const damageFinished = member.rollData?.options?.hasDamage ? member.damageRollData.active : true;
|
||||
|
||||
rollsAreFinished = rollsAreFinished && rollFinished && damageFinished;
|
||||
rollIsSelected = rollIsSelected || member.selected;
|
||||
|
|
@ -540,17 +540,10 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
await action.workflow.get('damage').execute(config, null, true);
|
||||
if (!config.damage) return;
|
||||
|
||||
const current = this.party.system.tagTeam.members[memberKey].rollData;
|
||||
|
||||
await this.updatePartyData(
|
||||
{
|
||||
[`system.tagTeam.members.${memberKey}.rollData`]: {
|
||||
...current,
|
||||
options: {
|
||||
...current.options,
|
||||
damage: config.damage
|
||||
}
|
||||
}
|
||||
[`system.tagTeam.members.${memberKey}.damageRollData`]: config.damage
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
);
|
||||
|
|
@ -558,15 +551,11 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
static async #removeDamageRoll(_, button) {
|
||||
const { memberKey } = button.dataset;
|
||||
const current = this.party.system.tagTeam.members[memberKey].rollData;
|
||||
this.updatePartyData(
|
||||
{
|
||||
[`system.tagTeam.members.${memberKey}.rollData`]: {
|
||||
...current,
|
||||
options: {
|
||||
...current.options,
|
||||
damage: null
|
||||
}
|
||||
[`system.tagTeam.members.${memberKey}.damageRollData`]: {
|
||||
main: null,
|
||||
resources: _replace({})
|
||||
}
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
|
|
@ -574,89 +563,44 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
static async #rerollDamageDice(_, button) {
|
||||
const { memberKey, damageKey, part, dice } = button.dataset;
|
||||
const { isResource, memberKey, damageKey, diceIndex, resultIndex } = button.dataset;
|
||||
const memberData = this.party.system.tagTeam.members[memberKey];
|
||||
const partData = memberData.rollData.options.damage[damageKey].parts[part];
|
||||
const activeDiceResultKey = Object.keys(partData.dice[dice].results).find(
|
||||
index => partData.dice[dice].results[index].active
|
||||
);
|
||||
const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(
|
||||
partData,
|
||||
dice,
|
||||
activeDiceResultKey
|
||||
);
|
||||
|
||||
const rollData = this.party.system.tagTeam.members[memberKey].rollData;
|
||||
rollData.options.damage[damageKey].parts = rollData.options.damage[damageKey].parts.map((damagePart, index) => {
|
||||
if (index !== Number.parseInt(part)) return damagePart;
|
||||
|
||||
return {
|
||||
...damagePart,
|
||||
total: parsedRoll.total,
|
||||
dice: rerolledDice
|
||||
};
|
||||
});
|
||||
rollData.options.damage[damageKey].total = rollData.options.damage[damageKey].parts.reduce((acc, part) => {
|
||||
acc += part.total;
|
||||
return acc;
|
||||
}, 0);
|
||||
await memberData.damageRollData.rerollDamageDie(isResource, damageKey, diceIndex, resultIndex);
|
||||
|
||||
const basePath = `system.tagTeam.members.${memberKey}.damageRollData`;
|
||||
const updatePath = isResource ? `${basePath}.resources.${damageKey}` : `${basePath}.main`;
|
||||
const updateValue = isResource ?
|
||||
memberData.damageRollData.resources[damageKey] : memberData.damageRollData.main;
|
||||
this.updatePartyData(
|
||||
{
|
||||
[`system.tagTeam.members.${memberKey}.rollData`]: rollData
|
||||
[updatePath]: updateValue.toJSON()
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
);
|
||||
}
|
||||
|
||||
async getCriticalDamage(damage) {
|
||||
const newDamage = foundry.utils.deepClone(damage);
|
||||
for (let key in newDamage) {
|
||||
var damage = newDamage[key];
|
||||
damage.formula = '';
|
||||
damage.total = 0;
|
||||
|
||||
for (let part of damage.parts) {
|
||||
const criticalDamage = await getCritDamageBonus(part.formula);
|
||||
if (criticalDamage) {
|
||||
part.modifierTotal += criticalDamage;
|
||||
part.total += criticalDamage;
|
||||
part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`;
|
||||
part.roll = new Roll(part.formula);
|
||||
}
|
||||
|
||||
damage.formula = [damage.formula, part.formula].filter(x => x).join(' + ');
|
||||
damage.total += part.total;
|
||||
async getCriticalDamage(origDamage) {
|
||||
const newDamage = origDamage ? ChatDamageData.fromJSON(JSON.stringify(origDamage)) : null;
|
||||
if (newDamage?.main) {
|
||||
const criticalDamage = await getCritDamageBonus(newDamage.main.formula);
|
||||
if (criticalDamage) {
|
||||
const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true });
|
||||
criticalTerm.evaluate();
|
||||
newDamage.main = await Roll.fromTerms([
|
||||
...origDamage.main.terms,
|
||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||
criticalTerm
|
||||
]);
|
||||
newDamage.main.options = foundry.utils.deepClone(origDamage.main.options);
|
||||
}
|
||||
}
|
||||
|
||||
return newDamage;
|
||||
}
|
||||
|
||||
async getNonCriticalDamage(config) {
|
||||
const newDamage = foundry.utils.deepClone(config.damage);
|
||||
for (let key in newDamage) {
|
||||
var damage = newDamage[key];
|
||||
damage.formula = '';
|
||||
damage.total = 0;
|
||||
|
||||
for (let part of damage.parts) {
|
||||
const critDamageBonus = await getCritDamageBonus(part.formula);
|
||||
part.modifierTotal -= critDamageBonus;
|
||||
part.total -= critDamageBonus;
|
||||
part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`;
|
||||
part.roll = new Roll(part.formula);
|
||||
|
||||
damage.formula = [damage.formula, part.formula].filter(x => x).join(' + ');
|
||||
damage.total += part.total;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newDamage;
|
||||
}
|
||||
|
||||
static async #selectRoll(_, button) {
|
||||
const { memberKey } = button.dataset;
|
||||
|
||||
this.updatePartyData(
|
||||
{
|
||||
[`system.tagTeam.members`]: Object.entries(this.party.system.tagTeam.members).reduce(
|
||||
|
|
@ -667,7 +611,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
{}
|
||||
)
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
/* Selecting a roll must update all member sections hbs to display the correct damage information incase of a critical */
|
||||
[
|
||||
...Object.keys(this.party.system.tagTeam.members),
|
||||
this.constructor.PARTS.rollSelection.id,
|
||||
this.constructor.PARTS.result.id
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -685,29 +634,65 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null;
|
||||
|
||||
const mainRoll = new MemberData(baseMainRoll.toObject());
|
||||
const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData;
|
||||
const systemData = mainRoll.rollData.options;
|
||||
const isCritical = overrideIsCritical ?? systemData.roll.isCritical;
|
||||
if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage);
|
||||
mainRoll.damageRollData = baseMainRoll.damageRollData ?
|
||||
ChatDamageData.fromJSON(JSON.stringify(baseMainRoll.damageRollData)) : null;
|
||||
const secondaryRoll = new MemberData(baseSecondaryRoll.toObject());
|
||||
secondaryRoll.damageRollData = baseSecondaryRoll.damageRollData ?
|
||||
ChatDamageData.fromJSON(JSON.stringify(baseSecondaryRoll.damageRollData)) : null;
|
||||
|
||||
if (secondaryRollData?.options.hasDamage) {
|
||||
const isCritical = overrideIsCritical ?? mainRoll.roll.isCritical;
|
||||
if (isCritical) mainRoll.damageRollData = await this.getCriticalDamage(mainRoll.damageRollData);
|
||||
|
||||
if (secondaryRoll.damageRollData) {
|
||||
const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical)
|
||||
? await this.getCriticalDamage(secondaryRollData.options.damage)
|
||||
: secondaryRollData.options.damage;
|
||||
if (systemData.damage) {
|
||||
for (const [key, damage] of Object.entries(secondaryDamage ?? {})) {
|
||||
if (key in systemData.damage) {
|
||||
systemData.damage[key].formula = [systemData.damage[key]?.formula, damage.formula]
|
||||
.filter(x => x)
|
||||
.join(' + ');
|
||||
systemData.damage[key].total += damage.total;
|
||||
systemData.damage[key].parts.push(...damage.parts);
|
||||
? await this.getCriticalDamage(secondaryRoll.damageRollData)
|
||||
: secondaryRoll.damageRollData;
|
||||
if (mainRoll.damageRollData) {
|
||||
if (secondaryDamage.main) {
|
||||
if (mainRoll.damageRollData.main) {
|
||||
mainRoll.damageRollData.main = Roll.fromTerms([
|
||||
...baseMainRoll.damageRollData.main.terms,
|
||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||
...baseSecondaryRoll.damageRollData.main.terms
|
||||
]);
|
||||
|
||||
/* Joining the roll.options of both rolls */
|
||||
const joinedDamageTypes = new Set([
|
||||
...baseMainRoll.damageRollData.main.options.damageTypes,
|
||||
...baseSecondaryRoll.damageRollData.main.options.damageTypes
|
||||
]);
|
||||
mainRoll.damageRollData.main.options = {
|
||||
...baseMainRoll.damageRollData.main.options,
|
||||
damageTypes: [...joinedDamageTypes]
|
||||
};
|
||||
} else {
|
||||
systemData.damage[key] = damage;
|
||||
mainRoll.damageRollData.main = secondaryDamage.main;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, damage] of Object.entries(secondaryDamage.resources ?? {})) {
|
||||
if (key in mainRoll.damageRollData.resources) {
|
||||
mainRoll.damageRollData.resources[key] = Roll.fromTerms([
|
||||
...baseMainRoll.damageRollData.resources[key].terms,
|
||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||
...baseSecondaryRoll.damageRollData.resources[key].terms
|
||||
]);
|
||||
|
||||
/* Joining the roll.options of both rolls */
|
||||
const joinedDamageTypes = new Set([
|
||||
...baseMainRoll.damageRollData.resources[key].options.damageTypes,
|
||||
...baseSecondaryRoll.damageRollData.resources[key].options.damageTypes
|
||||
]);
|
||||
mainRoll.damageRollData.resources[key].options = {
|
||||
...baseMainRoll.damageRollData.resources[key].options,
|
||||
damageTypes: [...joinedDamageTypes]
|
||||
};
|
||||
} else {
|
||||
mainRoll.damageRollData.resources[key] = damage;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
systemData.damage = secondaryDamage;
|
||||
mainRoll.damageRollData = secondaryDamage;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -762,13 +747,27 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
const mainActor = this.party.system.partyMembers.find(x => x.uuid === mainRoll.options.source.actor);
|
||||
mainRoll.options.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
||||
|
||||
/* This could assumably be done better. For some reason rolls don't get correctly done through rollData.toJSON */
|
||||
const systemData = {
|
||||
...mainRoll.options,
|
||||
damage: joinedRoll.damageRollData?.toJSON()
|
||||
};
|
||||
|
||||
if (joinedRoll.damageRollData.main) {
|
||||
systemData.damage.main = joinedRoll.damageRollData.toJSON();
|
||||
}
|
||||
for (const type of Object.keys(joinedRoll.damageRollData?.resources ?? {})) {
|
||||
systemData.damage.resources[type] = joinedRoll.damageRollData.resources[type].toJSON();
|
||||
}
|
||||
|
||||
const cls = getDocumentClass('ChatMessage'),
|
||||
msgData = {
|
||||
type: 'dualityRoll',
|
||||
user: game.user.id,
|
||||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'),
|
||||
speaker: cls.getSpeaker({ actor: mainActor }),
|
||||
system: mainRoll.options,
|
||||
system: systemData,
|
||||
rolls: [JSON.stringify(joinedRoll.roll)],
|
||||
sound: null,
|
||||
flags: { core: { RollTable: true } }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getUnusedDamageTypes } from '../../helpers/utils.mjs';
|
||||
import { DHDamageData } from '../../data/fields/action/damageField.mjs';
|
||||
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
||||
|
||||
const { ApplicationV2 } = foundry.applications.api;
|
||||
|
|
@ -31,8 +31,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
removeElement: this.removeElement,
|
||||
removeTransformActor: this.removeTransformActor,
|
||||
editEffect: this.editEffect,
|
||||
addDamage: this.addDamage,
|
||||
removeDamage: this.removeDamage,
|
||||
addDamage: this.#onAddDamage,
|
||||
removeDamage: this.#onRemoveDamage,
|
||||
addDamageResource: this.#onAddDamageResource,
|
||||
removeDamageResource: this.#onRemoveDamageResource,
|
||||
editDoc: this.editDoc,
|
||||
addTrigger: this.addTrigger,
|
||||
removeTrigger: this.removeTrigger,
|
||||
|
|
@ -157,9 +159,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
context.tabs = this._getTabs(this.constructor.TABS);
|
||||
context.config = CONFIG.DH;
|
||||
if (this.action.damage) {
|
||||
context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length;
|
||||
|
||||
if (this.action.damage.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
||||
const allKeys = Object.keys(CONFIG.DH.GENERAL.healingTypes);
|
||||
context.allDamageTypesUsed = allKeys.every(k => k in this.action._source.damage.resources);
|
||||
if (this.action.damage?.main?.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
||||
context.hasBaseDamage = !!this.action.parent.attack;
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +255,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
const submitData = this._prepareSubmitData(event, formData);
|
||||
|
||||
const data = foundry.utils.mergeObject(this.action.toObject(), submitData);
|
||||
this.action = await this.action.update(data);
|
||||
this.action = (await this.action.update(data)) ?? this.action;
|
||||
|
||||
this.sheetUpdate?.(this.action);
|
||||
this.render();
|
||||
|
|
@ -299,53 +301,70 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
static addDamage(_event) {
|
||||
if (!this.action.damage.parts) return;
|
||||
/** @this DHActionBaseConfig */
|
||||
static #onAddDamage() {
|
||||
if (!this.action.damage || this.action.damage?.main) return;
|
||||
|
||||
const choices = getUnusedDamageTypes(this.action._source.damage.parts);
|
||||
const data = this.action.toObject();
|
||||
data.damage.main = {
|
||||
...DHDamageData.schema.getInitialValue(),
|
||||
applyTo: 'hitPoints',
|
||||
type: 'physical'
|
||||
};
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
/** @this DHActionBaseConfig */
|
||||
static #onRemoveDamage() {
|
||||
if (!this.action.damage?.main) return;
|
||||
const data = this.action.toObject();
|
||||
data.damage.main = null;
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
/** @this DHActionBaseConfig */
|
||||
static #onAddDamageResource(_event) {
|
||||
if (!this.action.damage) return;
|
||||
|
||||
const allKeys = Object.keys(CONFIG.DH.GENERAL.healingTypes);
|
||||
const unused = allKeys.filter(k => !(k in this.action._source.damage.resources));
|
||||
const choices = unused.map(k => ({ value: k, label: _loc(CONFIG.DH.GENERAL.healingTypes[k].label) }));
|
||||
const content = new foundry.data.fields.StringField({
|
||||
label: game.i18n.localize('Damage Type'),
|
||||
label: _loc('DAGGERHEART.GENERAL.Resource.single'),
|
||||
choices,
|
||||
required: true
|
||||
}).toFormGroup(
|
||||
{},
|
||||
{
|
||||
name: 'type',
|
||||
localize: true,
|
||||
nameAttr: 'value',
|
||||
labelAttr: 'label'
|
||||
}
|
||||
).outerHTML;
|
||||
}).toFormGroup({}, {
|
||||
name: 'type',
|
||||
localize: true,
|
||||
nameAttr: 'value',
|
||||
labelAttr: 'label'
|
||||
}).outerHTML;
|
||||
|
||||
const callback = (_, button) => {
|
||||
const data = this.action.toObject();
|
||||
const type = choices[button.form.elements.type.value].value;
|
||||
const part = this.action.schema.fields.damage.fields.parts.element.getInitialValue();
|
||||
part.applyTo = type;
|
||||
if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
|
||||
part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial;
|
||||
|
||||
data.damage.parts[type] = part;
|
||||
data.damage.resources[type] = {
|
||||
...this.action.schema.fields.damage.fields.resources.element.getInitialValue(),
|
||||
applyTo: type
|
||||
};
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
};
|
||||
|
||||
const typeDialog = new foundry.applications.api.DialogV2({
|
||||
buttons: [
|
||||
foundry.utils.mergeObject(
|
||||
{
|
||||
action: 'ok',
|
||||
label: 'Confirm',
|
||||
icon: 'fas fa-check',
|
||||
default: true
|
||||
},
|
||||
{ callback: callback }
|
||||
)
|
||||
{
|
||||
action: 'ok',
|
||||
label: 'Confirm',
|
||||
icon: 'fas fa-check',
|
||||
default: true,
|
||||
callback
|
||||
}
|
||||
],
|
||||
content: content,
|
||||
rejectClose: false,
|
||||
modal: false,
|
||||
window: {
|
||||
title: game.i18n.localize('Add Damage')
|
||||
title: _loc('DAGGERHEART.ACTIONS.Config.damage.addResource')
|
||||
},
|
||||
position: { width: 300 }
|
||||
});
|
||||
|
|
@ -353,12 +372,12 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
typeDialog.render(true);
|
||||
}
|
||||
|
||||
static removeDamage(_event, button) {
|
||||
if (!this.action.damage.parts) return;
|
||||
/** @this DHActionBaseConfig */
|
||||
static #onRemoveDamageResource(_event, button) {
|
||||
if (!this.action.damage?.resources) return;
|
||||
const data = this.action.toObject();
|
||||
const key = button.dataset.key;
|
||||
delete data.damage.parts[key];
|
||||
data.damage.parts[`${key}`] = _del;
|
||||
data.damage.resources[key] = _del;
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { DHDamageData } from '../../data/fields/action/damageField.mjs';
|
||||
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
|
||||
|
||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||
|
|
@ -8,8 +9,10 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
|||
classes: ['adversary-settings'],
|
||||
position: { width: 455, height: 'auto' },
|
||||
actions: {
|
||||
addExperience: DHAdversarySettings.#addExperience,
|
||||
removeExperience: DHAdversarySettings.#removeExperience
|
||||
addExperience: this.#onAddExperience,
|
||||
removeExperience: this.#onRemoveExperience,
|
||||
addDamage: this.#onAddDamage,
|
||||
removeDamage: this.#onRemoveDamage
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -71,7 +74,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
|||
* Adds a new experience entry to the actor.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #addExperience() {
|
||||
static async #onAddExperience() {
|
||||
const newExperience = {
|
||||
name: 'Experience',
|
||||
modifier: 0
|
||||
|
|
@ -83,7 +86,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
|||
* Removes an experience entry from the actor.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #removeExperience(_, target) {
|
||||
static async #onRemoveExperience(_, target) {
|
||||
const experience = this.actor.system.experiences[target.dataset.experience];
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
|
|
@ -98,4 +101,28 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
|||
|
||||
await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del });
|
||||
}
|
||||
|
||||
/**
|
||||
* @this DHAdversarySettings
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static #onAddDamage() {
|
||||
this.actor.update({
|
||||
'system.attack.damage.main': {
|
||||
...DHDamageData.schema.getInitialValue(),
|
||||
applyTo: 'hitPoints',
|
||||
type: 'physical'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @this DHAdversarySettings
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static #onRemoveDamage() {
|
||||
this.actor.update({
|
||||
'system.attack.damage.main': null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import DhDeathMove from '../../dialogs/deathMove.mjs';
|
|||
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
||||
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||
import { getArmorSources, getDocFromElement, getDocFromElementSync, sortBy } from '../../../helpers/utils.mjs';
|
||||
import { getArmorSources, getDocFromElement, getDocFromElementSync, itemAbleRollParse, sortBy } from '../../../helpers/utils.mjs';
|
||||
|
||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||
|
||||
|
|
@ -29,6 +29,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
||||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||
advanceResourceDie: CharacterSheet.#advanceResourceDie,
|
||||
toggleItemReload: CharacterSheet.#onToggleItemReload,
|
||||
cancelBeastform: CharacterSheet.#cancelBeastform,
|
||||
toggleResourceManagement: CharacterSheet.#toggleResourceManagement,
|
||||
useDowntime: this.useDowntime,
|
||||
|
|
@ -954,11 +955,21 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
});
|
||||
}
|
||||
|
||||
/** */
|
||||
static #advanceResourceDie(_, target) {
|
||||
this.updateResourceDie(target, true);
|
||||
}
|
||||
|
||||
static async #onToggleItemReload(_, target) {
|
||||
const item = await getDocFromElement(target);
|
||||
if (!item || !item.system.resource?.max)
|
||||
return;
|
||||
|
||||
await item.update({
|
||||
'system.resource.value': item.system.needsReload ?
|
||||
itemAbleRollParse(item.system.resource.max, this.document, item) : 0
|
||||
})
|
||||
}
|
||||
|
||||
lowerResourceDie(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
const message = game.messages.get(li.dataset.messageId);
|
||||
return message.system.hasRoll && (game.user.isGM || message.isAuthor);
|
||||
},
|
||||
callback: async li => {
|
||||
onClick: async (_event, li) => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
const reroll = await message.rolls[0].reroll({ liveRoll: true });
|
||||
message.update({ rolls: [reroll] });
|
||||
|
|
@ -127,7 +127,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
: false;
|
||||
return (game.user.isGM || message.isAuthor) && hasRolledDamage;
|
||||
},
|
||||
callback: async li => {
|
||||
onClick: async (_event, li) => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
const update = await message.system.getRerolledDamage();
|
||||
message.update(update);
|
||||
|
|
@ -153,6 +153,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
html.querySelectorAll('.risk-it-all-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
|
||||
);
|
||||
for (const element of html.querySelectorAll('.roll-reload-check')) {
|
||||
element.addEventListener('click', event => this.onRollReloadCheck(event, message));
|
||||
}
|
||||
};
|
||||
|
||||
setupHooks() {
|
||||
|
|
@ -180,28 +183,15 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
}
|
||||
|
||||
async onRollSimple(event, message) {
|
||||
const buttonType = event.target.dataset.type ?? 'damage',
|
||||
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
|
||||
damages = {
|
||||
hitPoints: {
|
||||
parts: [
|
||||
{
|
||||
applyTo: 'hitPoints',
|
||||
damageTypes: [],
|
||||
total
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
targets = Array.from(game.user.targets);
|
||||
|
||||
const buttonType = event.target.dataset.type ?? 'damage';
|
||||
const total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0);
|
||||
const targets = Array.from(game.user.targets);
|
||||
if (targets.length === 0)
|
||||
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
||||
|
||||
targets.forEach(target => {
|
||||
if (buttonType === 'healing') target.actor.takeHealing(damages);
|
||||
else target.actor.takeDamage(damages);
|
||||
});
|
||||
for (const target of targets) {
|
||||
if (buttonType === 'healing') target.actor.takeHealing({ hitPoints: total });
|
||||
else target.actor.takeDamage({ total });
|
||||
}
|
||||
}
|
||||
|
||||
async abilityUseButton(event, message) {
|
||||
|
|
@ -257,26 +247,17 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
}
|
||||
|
||||
const message = game.messages.get(messageData._id);
|
||||
const target = event.target.closest('[data-die-index]');
|
||||
const target = event.target.closest('[data-result]');
|
||||
|
||||
if (target.dataset.type === 'damage') {
|
||||
const { damageType, part, dice, result } = target.dataset;
|
||||
const damagePart = message.system.damage[damageType].parts[part];
|
||||
const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(damagePart, dice, result);
|
||||
const damageParts = message.system.damage[damageType].parts.map((damagePart, index) => {
|
||||
if (index !== Number(part)) return damagePart;
|
||||
return {
|
||||
...damagePart,
|
||||
total: parsedRoll.total,
|
||||
dice: rerolledDice
|
||||
};
|
||||
});
|
||||
const updateMessage = game.messages.get(message._id);
|
||||
await updateMessage.update({
|
||||
[`system.damage.${damageType}`]: {
|
||||
total: parsedRoll.total,
|
||||
parts: damageParts
|
||||
}
|
||||
const { isResource, damageType, dice, result } = target.dataset;
|
||||
await message.system.damage.rerollDamageDie(isResource, damageType, dice, result);
|
||||
|
||||
const updatePath = isResource ? `system.damage.resources.${damageType}` : 'system.damage.main';
|
||||
const updateValue = isResource ?
|
||||
message.system.damage.resources[damageType] : message.system.damage.main;
|
||||
await message.update({
|
||||
[updatePath]: updateValue.toJSON()
|
||||
});
|
||||
} else {
|
||||
const rerollDice = message.system.roll.dice[target.dataset.dieIndex];
|
||||
|
|
@ -303,4 +284,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
super._toggleNotifications(closing)
|
||||
FearTracker.handleOffSet();
|
||||
}
|
||||
|
||||
async onRollReloadCheck(_event, messageData) {
|
||||
const message = game.messages.get(messageData._id);
|
||||
const needsReload = await message.system.action.handleReload?.({ awaitRoll: true });
|
||||
await message.update({ 'system.needsReload': needsReload });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,7 +153,6 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
|||
action: socketEvent.Refresh,
|
||||
data: { refreshType: RefreshType.Countdown }
|
||||
});
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
||||
}
|
||||
|
||||
static #addCountdown() {
|
||||
|
|
|
|||
|
|
@ -12,14 +12,11 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
|||
|
||||
export default class DhCountdowns extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
previousCountdownData = null;
|
||||
changedCountdownsForAnimation = new Set();
|
||||
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
|
||||
this.previousCountdownData =
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns).countdowns;
|
||||
this.setupHooks();
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
|
|
@ -80,7 +77,15 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
return frame;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
async _onFirstRender(context, options) {
|
||||
await super._onFirstRender(context, options);
|
||||
this._createContextMenu(this._getCountdownContextOptions, '.countdown-container[data-countdown]', {
|
||||
parentClassHooks: false, fixed: true
|
||||
});
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
|
||||
|
|
@ -95,7 +100,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
|
||||
/* Handle animations to draw attention to countdown values changing */
|
||||
const typesToAnimate = new Set();
|
||||
for (const countdownKey of this.changedCountdownsForAnimation) {
|
||||
for (const countdownKey of options.animate ?? []) {
|
||||
const shimmerAnimation = [
|
||||
{ backgroundPositionX: '98%' },
|
||||
{ backgroundPositionX: '0%' }
|
||||
|
|
@ -127,8 +132,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
const element = this.element.querySelector(`.header-type-toggles .header-type[data-type="${type}"]`);
|
||||
element?.animate(pulseAnimation, pulseTiming);
|
||||
}
|
||||
|
||||
this.changedCountdownsForAnimation.clear();
|
||||
}
|
||||
|
||||
/** Returns countdown data filtered by ownership */
|
||||
|
|
@ -137,17 +140,15 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
const values = Object.entries(setting.countdowns).map(([key, countdown]) => ({
|
||||
key,
|
||||
countdown,
|
||||
ownership: DhCountdowns.#getPlayerOwnership(game.user, setting, countdown)
|
||||
ownership: countdown.getUserLevel(game.user)
|
||||
}));
|
||||
return values.filter(v => v.ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE);
|
||||
}
|
||||
|
||||
_getCountdownData() {
|
||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
|
||||
return this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => {
|
||||
const playersWithAccess = game.users.reduce((acc, user) => {
|
||||
const ownership = DhCountdowns.#getPlayerOwnership(user, setting, countdown);
|
||||
const ownership = countdown.getUserLevel(user);
|
||||
if (!user.isGM && ownership && ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) {
|
||||
acc.push(user);
|
||||
}
|
||||
|
|
@ -213,19 +214,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
return context;
|
||||
}
|
||||
|
||||
static #getPlayerOwnership(user, setting, countdown) {
|
||||
if (user.isGM) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
|
||||
|
||||
const playerOwnership = countdown.ownership[user.id];
|
||||
return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
||||
? setting.defaultOwnership
|
||||
: playerOwnership;
|
||||
}
|
||||
|
||||
cooldownRefresh = ({ refreshType }) => {
|
||||
if (refreshType === RefreshType.Countdown) this.render();
|
||||
};
|
||||
|
||||
static canPerformEdit() {
|
||||
if (game.user.isGM) return true;
|
||||
|
||||
|
|
@ -323,18 +311,12 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
action: socketEvent.Refresh,
|
||||
data: { refreshType: RefreshType.Countdown }
|
||||
});
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
||||
}
|
||||
|
||||
setupHooks() {
|
||||
Hooks.on(socketEvent.Refresh, this.cooldownRefresh.bind());
|
||||
}
|
||||
|
||||
async close(options) {
|
||||
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
|
||||
if (options.closeKey) return;
|
||||
|
||||
Hooks.off(socketEvent.Refresh, this.cooldownRefresh);
|
||||
return super.close(options);
|
||||
}
|
||||
|
||||
|
|
@ -377,4 +359,30 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]}
|
||||
*/
|
||||
_getCountdownContextOptions() {
|
||||
/** @param {HTMLElement} element */
|
||||
const getCountdownFromElement = element => {
|
||||
const id = element.closest('[data-countdown]').dataset.countdown;
|
||||
if (!id) return null;
|
||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
return setting.countdowns[id ?? ''];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'CONTROLS.CommonDelete',
|
||||
icon: 'fa-solid fa-trash',
|
||||
visible: element => {
|
||||
return getCountdownFromElement(element)?.isOwner;
|
||||
},
|
||||
onClick: (_, target) => {
|
||||
getCountdownFromElement(target)?.delete();
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue