mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-22 15:33:37 +02:00
Merged with v14-Dev
This commit is contained in:
commit
8d84b8da48
70 changed files with 1076 additions and 936 deletions
|
|
@ -7,3 +7,4 @@ export * as documents from './documents/_module.mjs';
|
|||
export * as enrichers from './enrichers/_module.mjs';
|
||||
export * as helpers from './helpers/_module.mjs';
|
||||
export * as systemRegistration from './systemRegistration/_module.mjs';
|
||||
export * as macros from './macros/_modules.mjs';
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
this.selected = null;
|
||||
this.evolved = { form: null };
|
||||
this.hybrid = { forms: {}, advantages: {}, features: {} };
|
||||
this.modifications = {
|
||||
traitBonuses: configData.modifications.traitBonuses.map(x => ({
|
||||
trait: null,
|
||||
bonus: x.bonus
|
||||
}))
|
||||
};
|
||||
|
||||
this._dragDrop = this._createDragDropHandlers();
|
||||
}
|
||||
|
|
@ -28,6 +34,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
selectBeastform: this.selectBeastform,
|
||||
toggleHybridFeature: this.toggleHybridFeature,
|
||||
toggleHybridAdvantage: this.toggleHybridAdvantage,
|
||||
toggleTraitBonus: this.toggleTraitBonus,
|
||||
submitBeastform: this.submitBeastform
|
||||
},
|
||||
form: {
|
||||
|
|
@ -48,6 +55,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
tabs: { template: 'systems/daggerheart/templates/dialogs/beastform/tabs.hbs' },
|
||||
beastformTier: { template: 'systems/daggerheart/templates/dialogs/beastform/beastformTier.hbs' },
|
||||
advanced: { template: 'systems/daggerheart/templates/dialogs/beastform/advanced.hbs' },
|
||||
modifications: { template: 'systems/daggerheart/templates/dialogs/beastform/modifications.hbs' },
|
||||
footer: { template: 'systems/daggerheart/templates/dialogs/beastform/footer.hbs' }
|
||||
};
|
||||
|
||||
|
|
@ -146,6 +154,9 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
{}
|
||||
);
|
||||
|
||||
context.modifications = this.modifications;
|
||||
context.traits = CONFIG.DH.ACTOR.abilities;
|
||||
|
||||
context.tier = beastformTiers[this.tabGroups.primary];
|
||||
context.tierKey = this.tabGroups.primary;
|
||||
|
||||
|
|
@ -155,6 +166,9 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
}
|
||||
|
||||
canSubmit() {
|
||||
const modificationsFinished = this.modifications.traitBonuses.every(x => x.trait);
|
||||
if (!modificationsFinished) return false;
|
||||
|
||||
if (this.selected) {
|
||||
switch (this.selected.system.beastformType) {
|
||||
case 'normal':
|
||||
|
|
@ -261,6 +275,13 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
this.render();
|
||||
}
|
||||
|
||||
static toggleTraitBonus(_, button) {
|
||||
const { index, trait } = button.dataset;
|
||||
this.modifications.traitBonuses[index].trait =
|
||||
this.modifications.traitBonuses[index].trait === trait ? null : trait;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async submitBeastform() {
|
||||
await this.close({ submitted: true });
|
||||
}
|
||||
|
|
@ -292,6 +313,23 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
}
|
||||
}
|
||||
|
||||
const beastformEffect = selected.effects.find(x => x.type === 'beastform');
|
||||
for (const traitBonus of app.modifications.traitBonuses) {
|
||||
const existingChange = beastformEffect.changes.find(
|
||||
x => x.key === `system.traits.${traitBonus.trait}.value`
|
||||
);
|
||||
if (existingChange) {
|
||||
existingChange.value = Number.parseInt(existingChange.value) + traitBonus.bonus;
|
||||
} else {
|
||||
beastformEffect.changes.push({
|
||||
key: `system.traits.${traitBonus.trait}.value`,
|
||||
mode: 2,
|
||||
priority: null,
|
||||
value: traitBonus.bonus
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
resolve({
|
||||
selected: selected,
|
||||
evolved: { ...app.evolved, form: evolved },
|
||||
|
|
|
|||
|
|
@ -200,6 +200,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
partContext.members[partId] = {
|
||||
...data,
|
||||
roll: data.roll,
|
||||
isEditable: actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER),
|
||||
key: partId,
|
||||
readyToRoll: Boolean(data.rollChoice),
|
||||
|
|
@ -448,23 +449,19 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
const { member, diceType } = button.dataset;
|
||||
const memberData = this.party.system.tagTeam.members[member];
|
||||
|
||||
const dieIndex = diceType === 'hope' ? 0 : diceType === 'fear' ? 2 : 4;
|
||||
|
||||
const { parsedRoll, newRoll } = await game.system.api.dice.DualityRoll.reroll(
|
||||
memberData.rollData,
|
||||
dieIndex,
|
||||
diceType
|
||||
);
|
||||
const rollData = parsedRoll.toJSON();
|
||||
const dieIndex = diceType === 'hope' ? 0 : diceType === 'fear' ? 1 : 2;
|
||||
const newRoll = game.system.api.dice.DualityRoll.fromData(memberData.rollData);
|
||||
const dice = newRoll.dice[dieIndex];
|
||||
await dice.reroll(`/r1=${dice.total}`, {
|
||||
liveRoll: {
|
||||
roll: newRoll,
|
||||
isReaction: true
|
||||
}
|
||||
});
|
||||
const rollData = newRoll.toJSON();
|
||||
this.updatePartyData(
|
||||
{
|
||||
[`system.tagTeam.members.${member}.rollData`]: {
|
||||
...rollData,
|
||||
options: {
|
||||
...rollData.options,
|
||||
roll: newRoll
|
||||
}
|
||||
}
|
||||
[`system.tagTeam.members.${member}.rollData`]: rollData
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
);
|
||||
|
|
@ -699,7 +696,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
const error = this.checkInitiatorHopeError(this.party.system.tagTeam.initiator);
|
||||
if (error) return error;
|
||||
|
||||
const mainRoll = (await this.getJoinedRoll()).rollData;
|
||||
const joinedRoll = await this.getJoinedRoll();
|
||||
const mainRoll = joinedRoll.rollData;
|
||||
const finalRoll = foundry.utils.deepClone(joinedRoll.roll);
|
||||
|
||||
const mainActor = this.party.system.partyMembers.find(x => x.uuid === mainRoll.options.source.actor);
|
||||
mainRoll.options.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
||||
|
|
@ -710,7 +709,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'),
|
||||
speaker: cls.getSpeaker({ actor: mainActor }),
|
||||
system: mainRoll.options,
|
||||
rolls: [mainRoll],
|
||||
rolls: [JSON.stringify(joinedRoll.roll)],
|
||||
sound: null,
|
||||
flags: { core: { RollTable: true } }
|
||||
};
|
||||
|
|
@ -722,7 +721,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
const fearUpdate = { key: 'fear', value: null, total: null, enabled: true };
|
||||
for (let memberId in tagTeamData.members) {
|
||||
const resourceUpdates = [];
|
||||
const rollGivesHope = mainRoll.options.roll.isCritical || mainRoll.options.roll.result.duality === 1;
|
||||
const rollGivesHope = finalRoll.isCritical || finalRoll.withHope;
|
||||
if (memberId === tagTeamData.initiator.memberId) {
|
||||
const value = tagTeamData.initiator.cost
|
||||
? rollGivesHope
|
||||
|
|
@ -733,9 +732,8 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
} else if (rollGivesHope) {
|
||||
resourceUpdates.push({ key: 'hope', value: 1, total: -1, enabled: true });
|
||||
}
|
||||
if (mainRoll.options.roll.isCritical)
|
||||
resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true });
|
||||
if (mainRoll.options.roll.result.duality === -1) {
|
||||
if (finalRoll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true });
|
||||
if (finalRoll.withFear) {
|
||||
fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1;
|
||||
fearUpdate.total = fearUpdate.total === null ? -1 : fearUpdate.total - 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
editDoc: this.editDoc,
|
||||
addTrigger: this.addTrigger,
|
||||
removeTrigger: this.removeTrigger,
|
||||
expandTrigger: this.expandTrigger
|
||||
expandTrigger: this.expandTrigger,
|
||||
addBeastformTraitBonus: this.addBeastformTraitBonus,
|
||||
removeBeastformTraitBonus: this.removeBeastformTraitBonus
|
||||
},
|
||||
form: {
|
||||
handler: this.updateForm,
|
||||
|
|
@ -412,6 +414,21 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
}
|
||||
}
|
||||
|
||||
static async addBeastformTraitBonus() {
|
||||
const data = this.action.toObject();
|
||||
data.beastform.modifications.traitBonuses = [
|
||||
...data.beastform.modifications.traitBonuses,
|
||||
this.action.schema.fields.beastform.fields.modifications.fields.traitBonuses.element.getInitialValue()
|
||||
];
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
static async removeBeastformTraitBonus(_event, button) {
|
||||
const data = this.action.toObject();
|
||||
data.beastform.modifications.traitBonuses.splice(button.dataset.index, 1);
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
updateSummonCount(event) {
|
||||
event.stopPropagation();
|
||||
const wrapper = event.target.closest('.summon-count-wrapper');
|
||||
|
|
|
|||
|
|
@ -79,8 +79,6 @@ export default function DHApplicationMixin(Base) {
|
|||
*/
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
|
||||
this._setupDragDrop();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -175,9 +173,6 @@ export default function DHApplicationMixin(Base) {
|
|||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
/* Core dragDrop from ActorDocument is always only 1. Possible we could refactor our own */
|
||||
if (Array.isArray(this._dragDrop)) this._dragDrop.forEach(d => d.bind(htmlElement));
|
||||
|
||||
// Handle delta inputs
|
||||
for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) {
|
||||
deltaInput.dataset.numValue = deltaInput.value;
|
||||
|
|
@ -289,6 +284,16 @@ export default function DHApplicationMixin(Base) {
|
|||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
this._createTagifyElements(this.options.tagifyConfigs);
|
||||
|
||||
for (const d of this.options.dragDrop) {
|
||||
new foundry.applications.ux.DragDrop.implementation({
|
||||
...d,
|
||||
callbacks: {
|
||||
dragstart: this._onDragStart.bind(this),
|
||||
drop: this._onDrop.bind(this)
|
||||
}
|
||||
}).bind(this.element);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -349,26 +354,6 @@ export default function DHApplicationMixin(Base) {
|
|||
/* Drag and Drop */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Creates drag-drop handlers from the configured options.
|
||||
* @returns {foundry.applications.ux.DragDrop[]}
|
||||
* @private
|
||||
*/
|
||||
_setupDragDrop() {
|
||||
if (this._dragDrop) {
|
||||
this._dragDrop.callbacks.dragStart = this._onDragStart;
|
||||
this._dragDrop.callback.drop = this._onDrop;
|
||||
} else {
|
||||
this._dragDrop = this.options.dragDrop.map(d => {
|
||||
d.callbacks = {
|
||||
dragstart: this._onDragStart.bind(this),
|
||||
drop: this._onDrop.bind(this)
|
||||
};
|
||||
return new foundry.applications.ux.DragDrop.implementation(d);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dragStart event.
|
||||
* @param {DragEvent} event
|
||||
|
|
|
|||
|
|
@ -228,7 +228,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
'systems/daggerheart/templates/ui/chat/action.hbs',
|
||||
systemData
|
||||
),
|
||||
title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.displayInChat'),
|
||||
speaker: cls.getSpeaker(),
|
||||
flags: {
|
||||
daggerheart: {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
},
|
||||
actions: {
|
||||
selectRefreshable: DaggerheartMenu.#selectRefreshable,
|
||||
refreshActors: DaggerheartMenu.#refreshActors
|
||||
refreshActors: DaggerheartMenu.#refreshActors,
|
||||
createFallCollisionDamage: DaggerheartMenu.#createFallCollisionDamage
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -50,6 +51,7 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
const context = await super._prepareContext(options);
|
||||
context.refreshables = this.refreshSelections;
|
||||
context.disableRefresh = Object.values(this.refreshSelections).every(x => !x.selected);
|
||||
context.fallAndCollision = CONFIG.DH.GENERAL.fallAndCollisionDamage;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
|
@ -71,4 +73,22 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #createFallCollisionDamage(_event, button) {
|
||||
const data = CONFIG.DH.GENERAL.fallAndCollisionDamage[button.dataset.key];
|
||||
const roll = new Roll(data.damageFormula);
|
||||
await roll.evaluate();
|
||||
|
||||
/* class BaseRoll needed to get rendered by foundryRoll.hbs */
|
||||
const rollJSON = roll.toJSON();
|
||||
rollJSON.class = 'BaseRoll';
|
||||
|
||||
foundry.documents.ChatMessage.implementation.create({
|
||||
title: game.i18n.localize(data.chatTitle),
|
||||
author: game.user.id,
|
||||
speaker: foundry.documents.ChatMessage.implementation.getSpeaker(),
|
||||
rolls: [rollJSON],
|
||||
sound: CONFIG.sounds.dice
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import { abilities } from '../../config/actorConfig.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
|
||||
import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs';
|
||||
import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.mjs';
|
||||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
||||
constructor(options) {
|
||||
|
|
@ -21,6 +24,84 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
classes: ['daggerheart']
|
||||
};
|
||||
|
||||
static CHAT_COMMANDS = {
|
||||
...super.CHAT_COMMANDS,
|
||||
dr: {
|
||||
rgx: /^(?:\/dr)((?:\s)[^]*)?/,
|
||||
fn: (_, match) => {
|
||||
const argString = match[1]?.trim();
|
||||
const result = argString ? rollCommandToJSON(argString) : { result: {} };
|
||||
if (!result) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.dualityParsing'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const { result: rollCommand, flavor } = result;
|
||||
|
||||
const reaction = rollCommand.reaction;
|
||||
const traitValue = rollCommand.trait?.toLowerCase();
|
||||
const advantage = rollCommand.advantage
|
||||
? CONFIG.DH.ACTIONS.advantageState.advantage.value
|
||||
: rollCommand.disadvantage
|
||||
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
|
||||
: undefined;
|
||||
const difficulty = rollCommand.difficulty;
|
||||
const grantResources = rollCommand.grantResources;
|
||||
|
||||
const target = getCommandTarget({ allowNull: true });
|
||||
const title =
|
||||
(flavor ?? traitValue)
|
||||
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label)
|
||||
})
|
||||
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
||||
|
||||
enrichedDualityRoll({
|
||||
reaction,
|
||||
traitValue,
|
||||
target,
|
||||
difficulty,
|
||||
title,
|
||||
label: game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll'),
|
||||
actionType: null,
|
||||
advantage,
|
||||
grantResources
|
||||
});
|
||||
return false;
|
||||
}
|
||||
},
|
||||
fr: {
|
||||
rgx: /^(?:\/fr)((?:\s)[^]*)?/,
|
||||
fn: (_, match) => {
|
||||
const argString = match[1]?.trim();
|
||||
const result = argString ? rollCommandToJSON(argString) : { result: {} };
|
||||
|
||||
if (!result) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateParsing'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const { result: rollCommand, flavor } = result;
|
||||
const fateTypeData = getFateTypeData(rollCommand?.type);
|
||||
|
||||
if (!fateTypeData)
|
||||
return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.fateTypeParsing'));
|
||||
|
||||
const { value: fateType, label: fateTypeLabel } = fateTypeData;
|
||||
const target = getCommandTarget({ allowNull: true });
|
||||
const title = flavor ?? game.i18n.localize('DAGGERHEART.GENERAL.fateRoll');
|
||||
|
||||
enrichedFateRoll({
|
||||
target,
|
||||
title,
|
||||
label: fateTypeLabel,
|
||||
fateType
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_getEntryContextOptions() {
|
||||
return [
|
||||
...super._getEntryContextOptions(),
|
||||
|
|
@ -175,7 +256,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
action.use(event);
|
||||
}
|
||||
|
||||
async rerollEvent(event, message) {
|
||||
async rerollEvent(event, messageData) {
|
||||
event.stopPropagation();
|
||||
if (!event.shiftKey) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
|
|
@ -187,6 +268,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
const message = game.messages.get(messageData._id);
|
||||
const target = event.target.closest('[data-die-index]');
|
||||
|
||||
if (target.dataset.type === 'damage') {
|
||||
|
|
@ -209,27 +291,16 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
}
|
||||
});
|
||||
} else {
|
||||
let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0];
|
||||
const rollClass =
|
||||
game.system.api.dice[
|
||||
message.type === 'dualityRoll'
|
||||
? 'DualityRoll'
|
||||
: target.dataset.type === 'damage'
|
||||
? 'DHRoll'
|
||||
: 'D20Roll'
|
||||
];
|
||||
|
||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
|
||||
const { newRoll, parsedRoll } = await rollClass.reroll(
|
||||
originalRoll_parsed,
|
||||
target.dataset.dieIndex,
|
||||
target.dataset.type
|
||||
);
|
||||
|
||||
await game.messages.get(message._id).update({
|
||||
'system.roll': newRoll,
|
||||
'rolls': [parsedRoll]
|
||||
const rerollDice = message.system.roll.dice[target.dataset.dieIndex];
|
||||
await rerollDice.reroll(`/r1=${rerollDice.total}`, {
|
||||
liveRoll: {
|
||||
roll: message.system.roll,
|
||||
actor: message.system.actionActor,
|
||||
isReaction: message.system.roll.options.actionType === 'reaction'
|
||||
}
|
||||
});
|
||||
await message.update({
|
||||
rolls: [message.system.roll.toJSON()]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs';
|
||||
import { expireActiveEffects } from '../../helpers/utils.mjs';
|
||||
import { clearPreviousSpotlight } from '../../macros/spotlightCombatant.mjs';
|
||||
|
||||
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
||||
static DEFAULT_OPTIONS = {
|
||||
|
|
@ -150,13 +151,13 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
}
|
||||
|
||||
async setCombatantSpotlight(combatantId) {
|
||||
const combatant = this.viewed.combatants.get(combatantId);
|
||||
const update = {
|
||||
system: {
|
||||
'spotlight.requesting': false,
|
||||
'spotlight.requestOrderIndex': 0
|
||||
}
|
||||
};
|
||||
const combatant = this.viewed.combatants.get(combatantId);
|
||||
|
||||
const toggleTurn = this.viewed.combatants.contents
|
||||
.sort(this.viewed._sortCombatants)
|
||||
|
|
@ -187,6 +188,14 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
round: this.viewed.round + 1
|
||||
});
|
||||
await combatant.update(update);
|
||||
if (combatant.token) clearPreviousSpotlight();
|
||||
}
|
||||
|
||||
async clearTurn() {
|
||||
await this.viewed.update({
|
||||
turn: null,
|
||||
round: this.viewed.round + 1
|
||||
});
|
||||
}
|
||||
|
||||
static async requestSpotlight(_, target) {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,36 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
|||
this.previewHelp ||= this.addChild(this.#drawPreviewHelp());
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
_refreshTurnMarker() {
|
||||
// Should a Turn Marker be active?
|
||||
const { turnMarker } = this.document;
|
||||
const markersEnabled =
|
||||
CONFIG.Combat.settings.turnMarker.enabled && turnMarker.mode !== CONST.TOKEN_TURN_MARKER_MODES.DISABLED;
|
||||
const spotlighted = game.settings
|
||||
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker)
|
||||
.spotlightedTokens.has(this.document.uuid);
|
||||
|
||||
const turnIsSet = typeof game.combat?.turn === 'number';
|
||||
const isTurn = game.combat?.combatant?.tokenId === this.id;
|
||||
const markerActive = markersEnabled && turnIsSet ? isTurn : spotlighted;
|
||||
|
||||
// Activate a Turn Marker
|
||||
if (markerActive) {
|
||||
if (!this.turnMarker)
|
||||
this.turnMarker = this.addChildAt(new foundry.canvas.placeables.tokens.TokenTurnMarker(this), 0);
|
||||
canvas.tokens.turnMarkers.add(this);
|
||||
this.turnMarker.draw();
|
||||
}
|
||||
|
||||
// Remove a Turn Marker
|
||||
else if (this.turnMarker) {
|
||||
canvas.tokens.turnMarkers.delete(this);
|
||||
this.turnMarker.destroy();
|
||||
this.turnMarker = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
async _drawEffects() {
|
||||
this.effects.renderable = false;
|
||||
|
|
|
|||
|
|
@ -1102,3 +1102,29 @@ export const comparator = {
|
|||
label: 'DAGGERHEART.CONFIG.Comparator.lte'
|
||||
}
|
||||
};
|
||||
export const fallAndCollisionDamage = {
|
||||
veryClose: {
|
||||
id: 'veryClose',
|
||||
label: 'DAGGERHEART.CONFIG.fallAndCollision.veryClose.label',
|
||||
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.veryClose.chatTitle',
|
||||
damageFormula: '1d10 + 3'
|
||||
},
|
||||
close: {
|
||||
id: 'veryClose',
|
||||
label: 'DAGGERHEART.CONFIG.fallAndCollision.close.label',
|
||||
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.close.chatTitle',
|
||||
damageFormula: '1d20 + 5'
|
||||
},
|
||||
far: {
|
||||
id: 'veryClose',
|
||||
label: 'DAGGERHEART.CONFIG.fallAndCollision.far.label',
|
||||
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.far.chatTitle',
|
||||
damageFormula: '1d100 + 15'
|
||||
},
|
||||
collision: {
|
||||
id: 'veryClose',
|
||||
label: 'DAGGERHEART.CONFIG.fallAndCollision.collision.label',
|
||||
chatTitle: 'DAGGERHEART.CONFIG.fallAndCollision.collision.chatTitle',
|
||||
damageFormula: '1d20 + 5'
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
export const keybindings = {
|
||||
spotlight: 'DHSpotlight'
|
||||
};
|
||||
|
||||
export const menu = {
|
||||
Automation: {
|
||||
Name: 'GameSettingsAutomation',
|
||||
|
|
@ -35,7 +39,8 @@ export const gameSettings = {
|
|||
Countdowns: 'Countdowns',
|
||||
LastMigrationVersion: 'LastMigrationVersion',
|
||||
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
||||
CompendiumBrowserSettings: 'CompendiumBrowserSettings'
|
||||
CompendiumBrowserSettings: 'CompendiumBrowserSettings',
|
||||
SpotlightTracker: 'SpotlightTracker'
|
||||
};
|
||||
|
||||
export const actionAutomationChoices = {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ export { default as DhRollTable } from './rollTable.mjs';
|
|||
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
||||
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
||||
export { default as TagTeamData } from './tagTeamData.mjs';
|
||||
export { default as SpotlightTracker } from './spotlightTracker.mjs';
|
||||
|
||||
export * as countdowns from './countdowns.mjs';
|
||||
export * as actions from './action/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -166,12 +166,11 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
|||
this.parent.actor?.type === 'character' &&
|
||||
this.parent.actor.system.resources.armor
|
||||
) {
|
||||
const newArmorTotal = (changed.system?.changes ?? []).reduce((acc, change) => {
|
||||
if (change.type === 'armor') acc += change.value.current;
|
||||
return acc;
|
||||
}, this.parent.actor.system.armor?.system?.armor?.current ?? 0);
|
||||
const armorEffect = changed.system?.changes?.find(x => x.type === 'armor');
|
||||
const newArmorTotal =
|
||||
armorEffect?.value?.current + (this.parent.actor.system.armor?.system?.armor?.current ?? 0);
|
||||
|
||||
if (newArmorTotal !== this.parent.actor.system.armorScore.value) {
|
||||
if (armorEffect && newArmorTotal !== this.parent.actor.system.armorScore.value) {
|
||||
const armorData = getScrollTextData(this.parent.actor, { value: newArmorTotal }, 'armor');
|
||||
options.scrollingTextData = [armorData];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export default class BeastformEffect extends BaseEffect {
|
|||
width: new fields.NumberField({ integer: false, nullable: true })
|
||||
})
|
||||
}),
|
||||
advantageOn: new fields.ArrayField(new fields.StringField()),
|
||||
advantageOn: new fields.TypedObjectField(new fields.SchemaField({ value: new fields.StringField() })),
|
||||
featureIds: new fields.ArrayField(new fields.StringField()),
|
||||
effectIds: new fields.ArrayField(new fields.StringField())
|
||||
};
|
||||
|
|
|
|||
|
|
@ -757,7 +757,6 @@ export default class DhCharacter extends DhCreature {
|
|||
|
||||
prepareDerivedData() {
|
||||
super.prepareDerivedData();
|
||||
let baseHope = this.resources.hope.value;
|
||||
if (this.companion) {
|
||||
for (let levelKey in this.companion.system.levelData.levelups) {
|
||||
const level = this.companion.system.levelData.levelups[levelKey];
|
||||
|
|
@ -772,7 +771,6 @@ export default class DhCharacter extends DhCreature {
|
|||
}
|
||||
|
||||
this.resources.hope.max -= this.scars;
|
||||
this.resources.hope.value = Math.min(baseHope, this.resources.hope.max);
|
||||
this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait;
|
||||
|
||||
this.resources.armor = {
|
||||
|
|
|
|||
|
|
@ -60,4 +60,14 @@ export default class DhCreature extends BaseDataActor {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
const minLimitResource = resource => {
|
||||
if (resource) resource.value = Math.min(resource.value, resource.max);
|
||||
};
|
||||
|
||||
minLimitResource(this.resources.stress);
|
||||
minLimitResource(this.resources.hitPoints);
|
||||
minLimitResource(this.resources.hope);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
return {
|
||||
title: new fields.StringField(),
|
||||
actionDescription: new fields.HTMLField(),
|
||||
roll: new fields.ObjectField(),
|
||||
targets: targetsField(),
|
||||
hasRoll: new fields.BooleanField({ initial: false }),
|
||||
hasDamage: new fields.BooleanField({ initial: false }),
|
||||
|
|
@ -55,6 +54,16 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
};
|
||||
}
|
||||
|
||||
get roll() {
|
||||
if (this.parent.type === 'dualityRoll')
|
||||
return this.parent.rolls.find(x => x instanceof game.system.api.dice.DualityRoll);
|
||||
|
||||
if (this.parent.type === 'fateRoll')
|
||||
return this.parent.rolls.find(x => x instanceof game.system.api.dice.FateRoll);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
get actionActor() {
|
||||
if (!this.source.actor) return null;
|
||||
return fromUuidSync(this.source.actor);
|
||||
|
|
|
|||
|
|
@ -28,8 +28,21 @@ export default class BeastformField extends fields.SchemaField {
|
|||
{ 1: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') }
|
||||
);
|
||||
},
|
||||
hint: 'DAGGERHEART.ACTIONS.Config.beastform.exactHint'
|
||||
label: 'DAGGERHEART.ACTIONS.Config.beastform.exact.label',
|
||||
hint: 'DAGGERHEART.ACTIONS.Config.beastform.exact.hint'
|
||||
})
|
||||
}),
|
||||
modifications: new fields.SchemaField({
|
||||
traitBonuses: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
bonus: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: 1,
|
||||
min: 1,
|
||||
label: 'DAGGERHEART.ACTIONS.Config.beastform.modifications.traitBonuses.bonus'
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
};
|
||||
super(beastformFields, options, context);
|
||||
|
|
@ -66,15 +79,9 @@ export default class BeastformField extends fields.SchemaField {
|
|||
) ?? 1;
|
||||
|
||||
config.tierLimit = this.beastform.tierAccess.exact ?? actorTier;
|
||||
config.modifications = this.beastform.modifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO by Harry
|
||||
* @param {*} selectedForm
|
||||
* @param {*} evolvedData
|
||||
* @param {*} hybridData
|
||||
* @returns
|
||||
*/
|
||||
static async transform(selectedForm, evolvedData, hybridData) {
|
||||
const formData = evolvedData?.form ?? selectedForm;
|
||||
const beastformEffect = formData.effects.find(x => x.type === 'beastform');
|
||||
|
|
|
|||
|
|
@ -99,10 +99,14 @@ export default class DHBeastform extends BaseDataItem {
|
|||
|
||||
get beastformAttackData() {
|
||||
const effect = this.parent.effects.find(x => x.type === 'beastform');
|
||||
return DHBeastform.getBeastformAttackData(effect);
|
||||
}
|
||||
|
||||
static getBeastformAttackData(effect) {
|
||||
if (!effect) return null;
|
||||
|
||||
const traitBonus =
|
||||
effect.system.changes.find(x => x.key === `system.traits.${this.mainTrait}.value`)?.value ?? 0;
|
||||
const mainTrait = effect.system.changes.find(x => x.key === 'system.rules.attack.roll.trait')?.value;
|
||||
const traitBonus = effect.system.changes.find(x => x.key === `system.traits.${mainTrait}.value`)?.value ?? 0;
|
||||
const evasionBonus = effect.system.changes.find(x => x.key === 'system.evasion')?.value ?? 0;
|
||||
|
||||
const damageDiceIndex = effect.system.changes.find(x => x.key === 'system.rules.attack.damage.diceIndex');
|
||||
|
|
@ -110,7 +114,7 @@ export default class DHBeastform extends BaseDataItem {
|
|||
const damageBonus = effect.system.changes.find(x => x.key === 'system.rules.attack.damage.bonus')?.value ?? 0;
|
||||
|
||||
return {
|
||||
trait: game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.mainTrait].label),
|
||||
trait: game.i18n.localize(CONFIG.DH.ACTOR.abilities[mainTrait]?.label),
|
||||
traitBonus: traitBonus ? Number(traitBonus).signedString() : '',
|
||||
evasionBonus: evasionBonus ? Number(evasionBonus).signedString() : '',
|
||||
damageDice: damageDice,
|
||||
|
|
|
|||
9
module/data/spotlightTracker.mjs
Normal file
9
module/data/spotlightTracker.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export default class SpotlightTracker extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
spotlightedTokens: new fields.SetField(new fields.DocumentUUIDField())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -4,3 +4,4 @@ export { default as DamageRoll } from './damageRoll.mjs';
|
|||
export { default as DHRoll } from './dhRoll.mjs';
|
||||
export { default as DualityRoll } from './dualityRoll.mjs';
|
||||
export { default as FateRoll } from './fateRoll.mjs';
|
||||
export { diceTypes } from './die/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -217,49 +217,11 @@ export default class D20Roll extends DHRoll {
|
|||
results: d.results
|
||||
};
|
||||
});
|
||||
data.modifierTotal = this.calculateTotalModifiers(roll);
|
||||
data.modifierTotal = roll.modifierTotal;
|
||||
return data;
|
||||
}
|
||||
|
||||
resetFormula() {
|
||||
return (this._formula = this.constructor.getFormula(this.terms));
|
||||
}
|
||||
|
||||
static async reroll(rollString, _target, message) {
|
||||
let parsedRoll = game.system.api.dice.D20Roll.fromData(rollString);
|
||||
parsedRoll = await parsedRoll.reroll();
|
||||
const newRoll = game.system.api.dice.D20Roll.postEvaluate(parsedRoll, {
|
||||
targets: message.system.targets,
|
||||
roll: {
|
||||
advantage: message.system.roll.advantage?.type,
|
||||
difficulty: message.system.roll.difficulty ? Number(message.system.roll.difficulty) : null
|
||||
}
|
||||
});
|
||||
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
await game.dice3d.showForRoll(parsedRoll, game.user, true);
|
||||
}
|
||||
|
||||
const rerolled = {
|
||||
any: true,
|
||||
rerolls: [
|
||||
...(message.system.roll.dice[0].rerolled?.rerolls?.length > 0
|
||||
? [message.system.roll.dice[0].rerolled?.rerolls]
|
||||
: []),
|
||||
rollString.terms[0].results
|
||||
]
|
||||
};
|
||||
return {
|
||||
newRoll: {
|
||||
...newRoll,
|
||||
dice: [
|
||||
{
|
||||
...newRoll.dice[0],
|
||||
rerolled: rerolled
|
||||
}
|
||||
]
|
||||
},
|
||||
parsedRoll
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ export default class DHRoll extends Roll {
|
|||
return game.i18n.localize('DAGGERHEART.GENERAL.Roll.basic');
|
||||
}
|
||||
|
||||
get modifierTotal() {
|
||||
return this.constructor.calculateTotalModifiers(this);
|
||||
}
|
||||
|
||||
static messageType = 'adversaryRoll';
|
||||
|
||||
static CHAT_TEMPLATE = 'systems/daggerheart/templates/ui/chat/roll.hbs';
|
||||
|
|
@ -122,10 +126,6 @@ export default class DHRoll extends Roll {
|
|||
if (roll._evaluated) {
|
||||
const message = await cls.create(msgData, { messageMode: config.selectedMessageMode });
|
||||
|
||||
if (config.tagTeamSelected) {
|
||||
game.system.api.applications.dialogs.TagTeamDialog.assignRoll(message.speakerActor, message);
|
||||
}
|
||||
|
||||
if (roll.formula !== '' && game.modules.get('dice-so-nice')?.active) {
|
||||
await game.dice3d.waitFor3DAnimationByMessageID(message.id);
|
||||
}
|
||||
|
|
@ -142,6 +142,7 @@ export default class DHRoll extends Roll {
|
|||
const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options });
|
||||
return foundry.applications.handlebars.renderTemplate(template, {
|
||||
...chatData,
|
||||
roll: this,
|
||||
parent: chatData.parent,
|
||||
targetMode: chatData.targetMode,
|
||||
metagamingSettings
|
||||
|
|
@ -245,16 +246,21 @@ export default class DHRoll extends Roll {
|
|||
return (this._formula = this.constructor.getFormula(this.terms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate total modifiers of any rolls, including non-dh rolls.
|
||||
* This exists because damage rolls still may receive base roll classes
|
||||
*/
|
||||
static calculateTotalModifiers(roll) {
|
||||
let modifierTotal = 0;
|
||||
for (let i = 0; i < roll.terms.length; i++) {
|
||||
if (
|
||||
roll.terms[i] instanceof foundry.dice.terms.NumericTerm &&
|
||||
!!roll.terms[i - 1] &&
|
||||
roll.terms[i - 1] instanceof foundry.dice.terms.OperatorTerm
|
||||
)
|
||||
modifierTotal += Number(`${roll.terms[i - 1].operator}${roll.terms[i].total}`);
|
||||
if (!roll.terms[i].isDeterministic) continue;
|
||||
const termTotal = roll.terms[i].total;
|
||||
if (typeof termTotal === 'number') {
|
||||
const multiplier = roll.terms[i - 1]?.operator === ' - ' ? -1 : 1;
|
||||
modifierTotal += multiplier * termTotal;
|
||||
}
|
||||
}
|
||||
|
||||
return modifierTotal;
|
||||
}
|
||||
|
||||
|
|
|
|||
9
module/dice/die/_module.mjs
Normal file
9
module/dice/die/_module.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import DualityDie from './dualityDie.mjs';
|
||||
import AdvantageDie from './advantageDie.mjs';
|
||||
import DisadvantageDie from './disadvantageDie.mjs';
|
||||
|
||||
export const diceTypes = {
|
||||
DualityDie,
|
||||
AdvantageDie,
|
||||
DisadvantageDie
|
||||
};
|
||||
7
module/dice/die/advantageDie.mjs
Normal file
7
module/dice/die/advantageDie.mjs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export default class AdvantageDie extends foundry.dice.terms.Die {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.modifiers = [];
|
||||
}
|
||||
}
|
||||
7
module/dice/die/disadvantageDie.mjs
Normal file
7
module/dice/die/disadvantageDie.mjs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export default class DisadvantageDie extends foundry.dice.terms.Die {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.modifiers = [];
|
||||
}
|
||||
}
|
||||
62
module/dice/die/dualityDie.mjs
Normal file
62
module/dice/die/dualityDie.mjs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
||||
|
||||
export default class DualityDie extends foundry.dice.terms.Die {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.modifiers = [];
|
||||
}
|
||||
|
||||
#getDualityState(roll) {
|
||||
if (!roll) return null;
|
||||
return roll.withHope ? 1 : roll.withFear ? -1 : 0;
|
||||
}
|
||||
|
||||
#updateResources(oldDuality, newDuality, actor) {
|
||||
const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||
if (game.user.isGM ? !hopeFear.gm : !hopeFear.players) return;
|
||||
|
||||
const updates = [];
|
||||
const hope = (newDuality >= 0 ? 1 : 0) - (oldDuality >= 0 ? 1 : 0);
|
||||
const stress = (newDuality === 0 ? 1 : 0) - (oldDuality === 0 ? 1 : 0);
|
||||
const fear = (newDuality === -1 ? 1 : 0) - (oldDuality === -1 ? 1 : 0);
|
||||
|
||||
if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true });
|
||||
if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true });
|
||||
if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true });
|
||||
|
||||
const resourceUpdates = new ResourceUpdateMap(actor);
|
||||
resourceUpdates.addResources(updates);
|
||||
resourceUpdates.updateResources();
|
||||
}
|
||||
|
||||
async reroll(modifier, options) {
|
||||
const oldDuality = this.#getDualityState(options.liveRoll.roll);
|
||||
await super.reroll(modifier, options);
|
||||
|
||||
if (options?.liveRoll) {
|
||||
/* Can't currently test since DiceSoNice is not v14. Might need to set the appearance earlier if a roll is triggered by super.reroll */
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const diceSoNiceRoll = {
|
||||
_evaluated: true,
|
||||
dice: [this],
|
||||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
const preset = await getDiceSoNicePreset(diceSoNice[key], faces);
|
||||
diceSoNiceRoll.dice[0].options.appearance = preset.appearance;
|
||||
diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile;
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
} else {
|
||||
foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
}
|
||||
|
||||
await options.liveRoll.roll._evaluate();
|
||||
if (options.liveRoll.isReaction) return;
|
||||
|
||||
const newDuality = this.#getDualityState(options.liveRoll.roll);
|
||||
this.#updateResources(oldDuality, newDuality, options.liveRoll.actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||
import D20Roll from './d20Roll.mjs';
|
||||
import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
||||
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
||||
|
||||
export default class DualityRoll extends D20Roll {
|
||||
_advantageFaces = 6;
|
||||
|
|
@ -26,27 +24,31 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
get dHope() {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
if (!(this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice();
|
||||
return this.dice[0];
|
||||
}
|
||||
|
||||
set dHope(faces) {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[0].faces = this.getFaces(faces);
|
||||
// TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dHope.faces
|
||||
this.dHope.faces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get dFear() {
|
||||
if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
if (!(this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice();
|
||||
return this.dice[1];
|
||||
}
|
||||
|
||||
set dFear(faces) {
|
||||
if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[1].faces = this.getFaces(faces);
|
||||
// TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dFear.faces
|
||||
this.dFear.faces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get dAdvantage() {
|
||||
return this.dice[2];
|
||||
return this.dice[2] instanceof game.system.api.dice.diceTypes.AdvantageDie ? this.dice[2] : null;
|
||||
}
|
||||
|
||||
get dDisadvantage() {
|
||||
return this.dice[2] instanceof game.system.api.dice.diceTypes.DisadvantageDie ? this.dice[2] : null;
|
||||
}
|
||||
|
||||
get advantageFaces() {
|
||||
|
|
@ -65,6 +67,11 @@ export default class DualityRoll extends D20Roll {
|
|||
this._advantageNumber = Number(value);
|
||||
}
|
||||
|
||||
get extraDice() {
|
||||
const { DualityDie, AdvantageDie, DisadvantageDie } = game.system.api.dice.diceTypes;
|
||||
return this.dice.filter(x => ![DualityDie, AdvantageDie, DisadvantageDie].some(die => x instanceof die));
|
||||
}
|
||||
|
||||
setRallyChoices() {
|
||||
return this.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||
const change = c.system.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||
|
|
@ -118,22 +125,28 @@ export default class DualityRoll extends D20Roll {
|
|||
|
||||
/** @inheritDoc */
|
||||
static fromData(data) {
|
||||
data.terms[0].class = foundry.dice.terms.Die.name;
|
||||
data.terms[2].class = foundry.dice.terms.Die.name;
|
||||
data.terms[0].class = 'DualityDie';
|
||||
data.terms[2].class = 'DualityDie';
|
||||
if (data.options.roll.advantage?.type && data.terms[4]?.faces) {
|
||||
data.terms[4].class = data.options.roll.advantage.type === 1 ? 'AdvantageDie' : 'DisadvantageDie';
|
||||
}
|
||||
return super.fromData(data);
|
||||
}
|
||||
|
||||
createBaseDice() {
|
||||
if (this.dice[0] instanceof foundry.dice.terms.Die && this.dice[1] instanceof foundry.dice.terms.Die) {
|
||||
if (
|
||||
this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie &&
|
||||
this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie
|
||||
) {
|
||||
this.terms = [this.terms[0], this.terms[1], this.terms[2]];
|
||||
return;
|
||||
}
|
||||
|
||||
this.terms[0] = new foundry.dice.terms.Die({
|
||||
this.terms[0] = new game.system.api.dice.diceTypes.DualityDie({
|
||||
faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12
|
||||
});
|
||||
this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' });
|
||||
this.terms[2] = new foundry.dice.terms.Die({
|
||||
this.terms[2] = new game.system.api.dice.diceTypes.DualityDie({
|
||||
faces: this.data.rules.dualityRoll?.defaultFearDice ?? 12
|
||||
});
|
||||
}
|
||||
|
|
@ -305,7 +318,6 @@ export default class DualityRoll extends D20Roll {
|
|||
!config.source?.actor ||
|
||||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
|
||||
config.actionType === 'reaction' ||
|
||||
config.tagTeamSelected ||
|
||||
config.skips?.resources
|
||||
)
|
||||
return;
|
||||
|
|
@ -346,7 +358,6 @@ export default class DualityRoll extends D20Roll {
|
|||
if (
|
||||
automationSettings.countdownAutomation &&
|
||||
config.actionType !== 'reaction' &&
|
||||
!config.tagTeamSelected &&
|
||||
!config.skips?.updateCountdowns
|
||||
) {
|
||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||
|
|
@ -373,61 +384,4 @@ export default class DualityRoll extends D20Roll {
|
|||
if (currentCombatant?.actorId == config.data.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
|
||||
}
|
||||
}
|
||||
|
||||
static async reroll(rollBase, dieIndex, diceType) {
|
||||
let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollBase, evaluated: false });
|
||||
const term = parsedRoll.terms[dieIndex];
|
||||
await term.reroll(`/r1=${term.total}`);
|
||||
const result = await parsedRoll.evaluate();
|
||||
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const diceSoNiceRoll = {
|
||||
_evaluated: true,
|
||||
dice: [
|
||||
new foundry.dice.terms.Die({
|
||||
...term,
|
||||
faces: term._faces,
|
||||
results: term.results.filter(x => !x.rerolled)
|
||||
})
|
||||
],
|
||||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
const diceSoNicePresets = await getDiceSoNicePresets(`d${term._faces}`, `d${term._faces}`);
|
||||
if (diceSoNicePresets[diceType]) {
|
||||
diceSoNiceRoll.dice[0].options = diceSoNicePresets[diceType];
|
||||
}
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
} else {
|
||||
foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
}
|
||||
|
||||
const newRoll = game.system.api.dice.DualityRoll.postEvaluate(parsedRoll, {
|
||||
targets: parsedRoll.options.targets ?? [],
|
||||
roll: {
|
||||
advantage: parsedRoll.options.roll.advantage?.type,
|
||||
difficulty: parsedRoll.options.roll.difficulty ? Number(parsedRoll.options.roll.difficulty) : null
|
||||
}
|
||||
});
|
||||
|
||||
const extraIndex = newRoll.advantage ? 3 : 2;
|
||||
newRoll.extra = newRoll.extra.slice(extraIndex);
|
||||
|
||||
const actor = parsedRoll.options.source.actor
|
||||
? await foundry.utils.fromUuid(parsedRoll.options.source.actor)
|
||||
: null;
|
||||
const config = {
|
||||
source: { actor: parsedRoll.options.source.actor ?? '' },
|
||||
targets: parsedRoll.targets,
|
||||
roll: newRoll,
|
||||
rerolledRoll: parsedRoll.roll,
|
||||
resourceUpdates: new ResourceUpdateMap(actor)
|
||||
};
|
||||
|
||||
await DualityRoll.addDualityResourceUpdates(config);
|
||||
await config.resourceUpdates.updateResources();
|
||||
|
||||
return { newRoll, parsedRoll };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ export default class FateRoll extends D20Roll {
|
|||
}
|
||||
|
||||
set dHope(faces) {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[0].faces = this.getFaces(faces);
|
||||
// TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dHope.faces
|
||||
this.dHope.faces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get dFear() {
|
||||
|
|
@ -31,8 +31,8 @@ export default class FateRoll extends D20Roll {
|
|||
}
|
||||
|
||||
set dFear(faces) {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[0].faces = this.getFaces(faces);
|
||||
// TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dFear.faces
|
||||
this.dFear.faces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get isCritical() {
|
||||
|
|
@ -43,6 +43,20 @@ export default class FateRoll extends D20Roll {
|
|||
return this.data.fateType;
|
||||
}
|
||||
|
||||
get withHope() {
|
||||
return this.data.fateType === 'Hope';
|
||||
}
|
||||
|
||||
get withFear() {
|
||||
return this.data.fateType === 'Fear';
|
||||
}
|
||||
|
||||
get totalLabel() {
|
||||
const label = this.withHope ? 'DAGGERHEART.GENERAL.hope' : 'DAGGERHEART.GENERAL.fear';
|
||||
|
||||
return game.i18n.localize(label);
|
||||
}
|
||||
|
||||
static getHooks(hooks) {
|
||||
return [...(hooks ?? []), 'Fate'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { itemAbleRollParse } from '../helpers/utils.mjs';
|
||||
import { RefreshType } from '../systemRegistration/socket.mjs';
|
||||
|
||||
export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -111,37 +110,41 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
|||
update.img = 'icons/magic/life/heart-cross-blue.webp';
|
||||
}
|
||||
|
||||
const existingEffect = this.actor.effects.find(x => x.origin === data.origin);
|
||||
const stacks = Boolean(data.system?.stacking);
|
||||
if (existingEffect && !stacks) return false;
|
||||
if (this.actor && data.origin) {
|
||||
const existingEffect = this.actor.effects.find(x => x.origin === data.origin);
|
||||
const stacks = Boolean(data.system?.stacking);
|
||||
if (existingEffect && !stacks) return false;
|
||||
|
||||
if (existingEffect && stacks) {
|
||||
const incrementedValue = existingEffect.system.stacking.value + 1;
|
||||
await existingEffect.update({
|
||||
'system.stacking.value': Math.min(incrementedValue, existingEffect.system.stacking.max ?? Infinity)
|
||||
});
|
||||
return false;
|
||||
if (existingEffect && stacks) {
|
||||
const incrementedValue = existingEffect.system.stacking.value + 1;
|
||||
await existingEffect.update({
|
||||
'system.stacking.value': Math.min(incrementedValue, existingEffect.system.stacking.max ?? Infinity)
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const statuses = Object.keys(data.statuses ?? {});
|
||||
const immuneStatuses =
|
||||
statuses.filter(
|
||||
status =>
|
||||
this.parent.system.rules?.conditionImmunities &&
|
||||
this.parent.system.rules.conditionImmunities[status]
|
||||
) ?? [];
|
||||
if (immuneStatuses.length > 0) {
|
||||
update.statuses = statuses.filter(x => !immuneStatuses.includes(x));
|
||||
const conditions = CONFIG.DH.GENERAL.conditions();
|
||||
const scrollingTexts = immuneStatuses.map(status => ({
|
||||
text: game.i18n.format('DAGGERHEART.ACTIVEEFFECT.immuneStatusText', {
|
||||
status: game.i18n.localize(conditions[status].name)
|
||||
})
|
||||
}));
|
||||
if (update.statuses.length > 0) {
|
||||
setTimeout(() => scrollingTexts, 500);
|
||||
} else {
|
||||
this.parent.queueScrollText(scrollingTexts);
|
||||
if (this.parent) {
|
||||
const statuses = Object.keys(data.statuses ?? {});
|
||||
const immuneStatuses =
|
||||
statuses.filter(
|
||||
status =>
|
||||
this.parent.system.rules?.conditionImmunities &&
|
||||
this.parent.system.rules.conditionImmunities[status]
|
||||
) ?? [];
|
||||
if (immuneStatuses.length > 0) {
|
||||
update.statuses = statuses.filter(x => !immuneStatuses.includes(x));
|
||||
const conditions = CONFIG.DH.GENERAL.conditions();
|
||||
const scrollingTexts = immuneStatuses.map(status => ({
|
||||
text: game.i18n.format('DAGGERHEART.ACTIVEEFFECT.immuneStatusText', {
|
||||
status: game.i18n.localize(conditions[status].name)
|
||||
})
|
||||
}));
|
||||
if (update.statuses.length > 0) {
|
||||
setTimeout(() => scrollingTexts, 500);
|
||||
} else {
|
||||
this.parent.queueScrollText(scrollingTexts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -152,20 +155,6 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
|||
await super._preCreate(data, options, user);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
_onCreate(data, options, userId) {
|
||||
super._onCreate(data, options, userId);
|
||||
|
||||
Hooks.callAll(RefreshType.EffectsDisplay);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
_onDelete(data, options, userId) {
|
||||
super._onDelete(data, options, userId);
|
||||
|
||||
Hooks.callAll(RefreshType.EffectsDisplay);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Methods */
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -30,6 +30,18 @@ export default class DhpActor extends Actor {
|
|||
return this.system.metadata.isNPC;
|
||||
}
|
||||
|
||||
prepareData() {
|
||||
super.prepareData();
|
||||
|
||||
// Update effects if it is the user's character or is controlled
|
||||
if (canvas.ready) {
|
||||
const controlled = canvas.tokens.controlled.some(t => t.actor === this);
|
||||
if (game.user.character === this || controlled) {
|
||||
ui.effectsDisplay.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritDoc */
|
||||
|
|
@ -122,14 +134,6 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
}
|
||||
|
||||
_onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId) {
|
||||
if (collection === 'effects') {
|
||||
ui.effectsDisplay.render();
|
||||
}
|
||||
|
||||
super._onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId);
|
||||
}
|
||||
|
||||
async updateLevel(newLevel) {
|
||||
if (!['character', 'companion'].includes(this.type) || newLevel === this.system.levelData.level.changed) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
|
||||
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
|
||||
|
||||
export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||
targetHook = null;
|
||||
|
|
@ -78,25 +78,14 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
if (this.isContentVisible) {
|
||||
if (this.type === 'dualityRoll') {
|
||||
html.classList.add('duality');
|
||||
switch (this.system.roll?.result?.duality) {
|
||||
case 1:
|
||||
html.classList.add('hope');
|
||||
break;
|
||||
case -1:
|
||||
html.classList.add('fear');
|
||||
break;
|
||||
default:
|
||||
html.classList.add('critical');
|
||||
break;
|
||||
}
|
||||
if (this.system.roll.withHope) html.classList.add('hope');
|
||||
else if (this.system.roll.withFear) html.classList.add('fear');
|
||||
else html.classList.add('critical');
|
||||
}
|
||||
if (this.type === 'fateRoll') {
|
||||
html.classList.add('fate');
|
||||
if (this.system.roll?.fate.fateDie == 'Hope') {
|
||||
html.classList.add('hope');
|
||||
}
|
||||
if (this.system.roll?.fate.fateDie == 'Fear') {
|
||||
html.classList.add('fear');
|
||||
if (this.system.roll?.fateDie) {
|
||||
html.classList.add(this.system.roll.fateDie.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -197,7 +197,6 @@ export default class DHItem extends foundry.documents.Item {
|
|||
actor: item.parent,
|
||||
speaker: cls.getSpeaker(),
|
||||
system: systemData,
|
||||
title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.displayInChat'),
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/ability-use.hbs',
|
||||
systemData
|
||||
|
|
|
|||
|
|
@ -494,62 +494,4 @@ export default class DHToken extends CONFIG.Token.documentClass {
|
|||
game.system.registeredTriggers.unregisterItemTriggers(this.actor.items);
|
||||
}
|
||||
}
|
||||
|
||||
/* V14 TEMP until foundry fixes: https://discord.com/channels/170995199584108546/1421197211194228907/1467296028700049566 */
|
||||
_onRelatedUpdate(update = {}, operation = {}) {
|
||||
this.#refreshOverrides(operation);
|
||||
this._prepareBars();
|
||||
|
||||
// Update tracked Combat resource
|
||||
const combatant = this.combatant;
|
||||
if (combatant) {
|
||||
const isActorUpdate = [this, null, undefined].includes(operation.parent);
|
||||
const resource = game.combat.settings.resource;
|
||||
const updates = Array.isArray(update) ? update : [update];
|
||||
if (isActorUpdate && resource && updates.some(u => foundry.utils.hasProperty(u.system ?? {}, resource))) {
|
||||
combatant.updateResource();
|
||||
}
|
||||
ui.combat.render();
|
||||
}
|
||||
|
||||
// Trigger redraws on the token
|
||||
if (this.parent.isView) {
|
||||
if (this.object?.hasActiveHUD) canvas.tokens.hud.render();
|
||||
this.object?.renderFlags.set({ redrawEffects: true });
|
||||
for (const key of ['bar1', 'bar2']) {
|
||||
const name = `${this.object?.objectId}.animate${key.capitalize()}`;
|
||||
const easing = foundry.canvas.animation.CanvasAnimation.easeInOutCosine;
|
||||
this.object?.animate({ [key]: this[key] }, { name, easing });
|
||||
}
|
||||
for (const app of foundry.applications.sheets.TokenConfig.instances()) {
|
||||
app._preview?.updateSource({ delta: this.toObject().delta }, { diff: false, recursive: false });
|
||||
app._preview?.object?.renderFlags.set({ refreshBars: true, redrawEffects: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* V14 TEMP until foundry fixes: https://discord.com/channels/170995199584108546/1421197211194228907/1467296028700049566 */
|
||||
#refreshOverrides(operation) {
|
||||
if (!this.actor) return;
|
||||
|
||||
const { deepClone, mergeObject, equals, isEmpty } = foundry.utils;
|
||||
const oldOverrides = deepClone(this._overrides) ?? {};
|
||||
const newOverrides = deepClone(this.actor?.tokenOverrides ?? {}, { prune: true });
|
||||
if (!equals(oldOverrides, newOverrides)) {
|
||||
this._overrides = newOverrides;
|
||||
this.reset();
|
||||
|
||||
// Send emulated update data to the PlaceableObject
|
||||
if (!canvas.ready || canvas.scene !== this.scene) return;
|
||||
const { width, height, depth, ...changes } = mergeObject(
|
||||
mergeObject(oldOverrides, this, { insertKeys: false, insertValues: false }),
|
||||
this._overrides
|
||||
);
|
||||
this.object?._onUpdate(changes, {}, game.user.id);
|
||||
|
||||
// Hand off size changes to a secondary handler requiring downstream implementation.
|
||||
const sizeChanges = deepClone({ width, height, depth }, { prune: true });
|
||||
if (!isEmpty(sizeChanges)) this._onOverrideSize(sizeChanges, operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,12 +31,39 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
|||
this.#bordered = true;
|
||||
let effect = {};
|
||||
if (element.dataset.uuid) {
|
||||
const effectData = (await foundry.utils.fromUuid(element.dataset.uuid)).toObject();
|
||||
const effectItem = await foundry.utils.fromUuid(element.dataset.uuid);
|
||||
const effectData = effectItem.toObject();
|
||||
|
||||
effect = {
|
||||
...effectData,
|
||||
name: game.i18n.localize(effectData.name),
|
||||
description: game.i18n.localize(effectData.description ?? effectData.parent.system.description)
|
||||
name: game.i18n.localize(effectData.name)
|
||||
};
|
||||
|
||||
if (effectData.type === 'beastform') {
|
||||
const beastformData = {
|
||||
features: [],
|
||||
advantageOn: effectData.system.advantageOn,
|
||||
beastformAttackData: game.system.api.data.items.DHBeastform.getBeastformAttackData(effectItem)
|
||||
};
|
||||
|
||||
const features = effectItem.parent.items.filter(x => effectItem.system.featureIds.includes(x.id));
|
||||
for (const feature of features) {
|
||||
const featureData = feature.toObject();
|
||||
featureData.enrichedDescription = await feature.system.getEnrichedDescription();
|
||||
beastformData.features.push(featureData);
|
||||
}
|
||||
|
||||
effect.description = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/tooltip/parts/beastformData.hbs',
|
||||
{
|
||||
item: { system: beastformData }
|
||||
}
|
||||
);
|
||||
} else {
|
||||
effect.description = game.i18n.localize(
|
||||
effectData.description ?? effectData.parent.system.description
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const conditions = CONFIG.DH.GENERAL.conditions();
|
||||
const condition = conditions[element.dataset.condition];
|
||||
|
|
|
|||
1
module/macros/_modules.mjs
Normal file
1
module/macros/_modules.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as spotlightCombatant } from './spotlightCombatant.mjs';
|
||||
50
module/macros/spotlightCombatant.mjs
Normal file
50
module/macros/spotlightCombatant.mjs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Spotlight a token on the canvas. If it is a combatant, run it through combatTracker's spotlight logic.
|
||||
* @param {TokenDocument} token - The token to spotlight
|
||||
* @returns {void}
|
||||
*/
|
||||
const spotlightCombatantMacro = async token => {
|
||||
if (!token)
|
||||
return ui.notifications.error(game.i18n.localize('DAGGERHEART.MACROS.Spotlight.errors.noTokenSelected'));
|
||||
|
||||
const combatantCombat = token.combatant
|
||||
? game.combat
|
||||
: game.combats.find(combat => combat.combatants.some(x => x.token && x.token.id === token.document.id));
|
||||
if (combatantCombat) {
|
||||
const combatant = combatantCombat.combatants.find(x => x.token.id === token.document.id);
|
||||
if (!combatantCombat.active) {
|
||||
await combatantCombat.activate();
|
||||
if (combatantCombat.combatant?.id !== combatant.id) ui.combat.setCombatantSpotlight(combatant.id);
|
||||
} else {
|
||||
ui.combat.setCombatantSpotlight(combatant.id);
|
||||
}
|
||||
} else {
|
||||
if (game.combat) await ui.combat.clearTurn();
|
||||
|
||||
const spotlightTracker = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker);
|
||||
const isSpotlighted = spotlightTracker.spotlightedTokens.has(token.document.uuid);
|
||||
if (!isSpotlighted) await clearPreviousSpotlight();
|
||||
|
||||
spotlightTracker.updateSource({
|
||||
spotlightedTokens: isSpotlighted ? [] : [token.document.uuid]
|
||||
});
|
||||
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker, spotlightTracker);
|
||||
token.renderFlags.set({ refreshTurnMarker: true });
|
||||
}
|
||||
};
|
||||
|
||||
export const clearPreviousSpotlight = async () => {
|
||||
const spotlightTracker = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker);
|
||||
const previouslySpotlightedUuid =
|
||||
spotlightTracker.spotlightedTokens.size > 0 ? spotlightTracker.spotlightedTokens.first() : null;
|
||||
if (!previouslySpotlightedUuid) return;
|
||||
|
||||
spotlightTracker.updateSource({ spotlightedTokens: [] });
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker, spotlightTracker);
|
||||
|
||||
const previousToken = await foundry.utils.fromUuid(previouslySpotlightedUuid);
|
||||
previousToken.object.renderFlags.set({ refreshTurnMarker: true });
|
||||
};
|
||||
|
||||
export default spotlightCombatantMacro;
|
||||
|
|
@ -36,6 +36,7 @@ export const preloadHandlebarsTemplates = async function () {
|
|||
'systems/daggerheart/templates/actionTypes/summon.hbs',
|
||||
'systems/daggerheart/templates/actionTypes/transform.hbs',
|
||||
'systems/daggerheart/templates/settings/components/settings-item-line.hbs',
|
||||
'systems/daggerheart/templates/ui/tooltip/parts/beastformData.hbs',
|
||||
'systems/daggerheart/templates/ui/tooltip/parts/tooltipChips.hbs',
|
||||
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs',
|
||||
'systems/daggerheart/templates/dialogs/downtime/activities.hbs',
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@ import {
|
|||
DhVariantRuleSettings
|
||||
} from '../applications/settings/_module.mjs';
|
||||
import { CompendiumBrowserSettings } from '../data/_module.mjs';
|
||||
import SpotlightTracker from '../data/spotlightTracker.mjs';
|
||||
|
||||
export const registerDHSettings = () => {
|
||||
registerKeyBindings();
|
||||
registerMenuSettings();
|
||||
registerMenus();
|
||||
registerNonConfigSettings();
|
||||
|
|
@ -33,6 +35,25 @@ export const registerDHSettings = () => {
|
|||
});
|
||||
};
|
||||
|
||||
export const registerKeyBindings = () => {
|
||||
game.keybindings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.keybindings.spotlight, {
|
||||
name: game.i18n.localize('DAGGERHEART.SETTINGS.Keybindings.spotlight.name'),
|
||||
hint: game.i18n.localize('DAGGERHEART.SETTINGS.Keybindings.spotlight.hint'),
|
||||
uneditable: [],
|
||||
editable: [],
|
||||
onDown: () => {
|
||||
const selectedTokens = canvas.tokens.controlled.length > 0 ? canvas.tokens.controlled[0] : null;
|
||||
const hoveredTokens = game.canvas.tokens.hover ? game.canvas.tokens.hover : null;
|
||||
const tokens = selectedTokens ?? hoveredTokens;
|
||||
game.system.api.macros.spotlightCombatant(tokens);
|
||||
},
|
||||
onUp: () => {},
|
||||
restricted: true,
|
||||
reservedModifiers: [],
|
||||
precedence: CONST.KEYBINDING_PRECEDENCE.NORMAL
|
||||
});
|
||||
};
|
||||
|
||||
const registerMenuSettings = () => {
|
||||
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules, {
|
||||
scope: 'world',
|
||||
|
|
@ -162,4 +183,10 @@ const registerNonConfigSettings = () => {
|
|||
config: false,
|
||||
type: CompendiumBrowserSettings
|
||||
});
|
||||
|
||||
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker, {
|
||||
scope: 'world',
|
||||
config: false,
|
||||
type: SpotlightTracker
|
||||
});
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue