mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
File Structure Rework (#262)
* Restructured all the files * Moved build/daggerheart.js to ./daggerheart.js. Changed rollup to use the css file instead of the less * Restored build/ folder * Mvoed config out form under application * Moved roll.mjs to module/dice and renamed to dhRolls.mjs * Update module/canvas/placeables/_module.mjs Co-authored-by: joaquinpereyra98 <24190917+joaquinpereyra98@users.noreply.github.com> * Le massive export update * Removed unncessary import --------- Co-authored-by: joaquinpereyra98 <24190917+joaquinpereyra98@users.noreply.github.com>
This commit is contained in:
parent
099a4576da
commit
9d76405221
203 changed files with 1556 additions and 2565 deletions
4
module/applications/ui/_module.mjs
Normal file
4
module/applications/ui/_module.mjs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export { default as DhChatLog } from './chatLog.mjs';
|
||||
export { default as DhCombatTracker } from './combatTracker.mjs';
|
||||
export * as DhCountdowns from './countdowns.mjs';
|
||||
export { default as DhFearTracker } from './fearTracker.mjs';
|
||||
270
module/applications/ui/chatLog.mjs
Normal file
270
module/applications/ui/chatLog.mjs
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.targetTemplate = {
|
||||
activeLayer: undefined,
|
||||
document: undefined,
|
||||
object: undefined,
|
||||
minimizedSheets: [],
|
||||
config: undefined,
|
||||
targets: undefined
|
||||
};
|
||||
this.setupHooks();
|
||||
}
|
||||
|
||||
addChatListeners = async (app, html, data) => {
|
||||
html.querySelectorAll('.duality-action-damage').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollDamage(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.duality-action-healing').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollHealing(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.target-save-container').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollSave(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.roll-all-save-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollAllSave(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.duality-action-effect').forEach(element =>
|
||||
element.addEventListener('click', event => this.onApplyEffect(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.target-container').forEach(element => {
|
||||
element.addEventListener('mouseenter', this.hoverTarget);
|
||||
element.addEventListener('mouseleave', this.unhoverTarget);
|
||||
element.addEventListener('click', this.clickTarget);
|
||||
});
|
||||
html.querySelectorAll('.button-target-selection').forEach(element => {
|
||||
element.addEventListener('click', event => this.onTargetSelection(event, data.message));
|
||||
});
|
||||
html.querySelectorAll('.damage-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.onDamage(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.healing-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.onHealing(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.target-indicator').forEach(element =>
|
||||
element.addEventListener('click', this.onToggleTargets)
|
||||
);
|
||||
html.querySelectorAll('.advantage').forEach(element =>
|
||||
element.addEventListener('mouseenter', this.hoverAdvantage)
|
||||
);
|
||||
html.querySelectorAll('.advantage').forEach(element =>
|
||||
element.addEventListener('click', event => this.selectAdvantage.bind(this)(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.ability-use-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.abilityUseButton.bind(this)(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.action-use-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.actionUseButton.bind(this)(event, data.message))
|
||||
);
|
||||
};
|
||||
|
||||
setupHooks() {
|
||||
Hooks.on('renderChatMessageHTML', this.addChatListeners.bind());
|
||||
}
|
||||
|
||||
close(options) {
|
||||
Hooks.off('renderChatMessageHTML', this.addChatListeners);
|
||||
super.close(options);
|
||||
}
|
||||
|
||||
async getActor(id) {
|
||||
// return game.actors.get(id);
|
||||
return await fromUuid(id);
|
||||
}
|
||||
|
||||
getAction(actor, itemId, actionId) {
|
||||
const item = actor.items.get(itemId),
|
||||
action =
|
||||
actor.system.attack?._id === actionId
|
||||
? actor.system.attack
|
||||
: item?.system?.actions?.find(a => a._id === actionId);
|
||||
return action;
|
||||
}
|
||||
|
||||
onRollDamage = async (event, message) => {
|
||||
event.stopPropagation();
|
||||
const actor = await this.getActor(message.system.source.actor);
|
||||
if (!actor || !game.user.isGM) return true;
|
||||
if (message.system.source.item && message.system.source.action) {
|
||||
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
||||
if (!action || !action?.rollDamage) return;
|
||||
await action.rollDamage(event, message);
|
||||
}
|
||||
};
|
||||
|
||||
onRollHealing = async (event, message) => {
|
||||
event.stopPropagation();
|
||||
const actor = await this.getActor(message.system.source.actor);
|
||||
if (!actor || !game.user.isGM) return true;
|
||||
if (message.system.source.item && message.system.source.action) {
|
||||
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
||||
if (!action || !action?.rollHealing) return;
|
||||
await action.rollHealing(event, message);
|
||||
}
|
||||
};
|
||||
|
||||
onRollSave = async (event, message) => {
|
||||
event.stopPropagation();
|
||||
const actor = await this.getActor(message.system.source.actor),
|
||||
tokenId = event.target.closest('[data-token]')?.dataset.token,
|
||||
token = game.canvas.tokens.get(tokenId);
|
||||
if (!token?.actor || !token.isOwner) return true;
|
||||
if (message.system.source.item && message.system.source.action) {
|
||||
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
||||
if (!action || !action?.hasSave) return;
|
||||
action.rollSave(token, event, message);
|
||||
}
|
||||
};
|
||||
|
||||
onRollAllSave = async (event, message) => {
|
||||
event.stopPropagation();
|
||||
const targets = event.target.parentElement.querySelectorAll(
|
||||
'.target-section > [data-token] .target-save-container'
|
||||
);
|
||||
targets.forEach(el => {
|
||||
el.dispatchEvent(new PointerEvent('click', { shiftKey: true }));
|
||||
});
|
||||
};
|
||||
|
||||
onApplyEffect = async (event, message) => {
|
||||
event.stopPropagation();
|
||||
const actor = await this.getActor(message.system.source.actor);
|
||||
if (!actor || !game.user.isGM) return true;
|
||||
if (message.system.source.item && message.system.source.action) {
|
||||
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
||||
if (!action || !action?.applyEffects) return;
|
||||
const { isHit, targets } = this.getTargetList(event, message);
|
||||
if (targets.length === 0)
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected'));
|
||||
await action.applyEffects(event, message, targets);
|
||||
}
|
||||
};
|
||||
|
||||
onTargetSelection = async (event, message) => {
|
||||
event.stopPropagation();
|
||||
const targetSelection = Boolean(event.target.dataset.targetHit),
|
||||
msg = ui.chat.collection.get(message._id);
|
||||
if (msg.system.targetSelection === targetSelection) return;
|
||||
if (targetSelection !== true && !Array.from(game.user.targets).length)
|
||||
return ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected'));
|
||||
msg.system.targetSelection = targetSelection;
|
||||
msg.system.prepareDerivedData();
|
||||
ui.chat.updateMessage(msg);
|
||||
};
|
||||
|
||||
getTargetList = (event, message) => {
|
||||
const targetSelection = event.target
|
||||
.closest('.message-content')
|
||||
.querySelector('.button-target-selection.target-selected'),
|
||||
isHit = Boolean(targetSelection.dataset.targetHit);
|
||||
return {
|
||||
isHit,
|
||||
targets: isHit
|
||||
? message.system.targets.filter(t => t.hit === true).map(target => game.canvas.tokens.get(target.id))
|
||||
: Array.from(game.user.targets)
|
||||
};
|
||||
};
|
||||
|
||||
hoverTarget = event => {
|
||||
event.stopPropagation();
|
||||
const token = canvas.tokens.get(event.currentTarget.dataset.token);
|
||||
if (!token?.controlled) token._onHoverIn(event, { hoverOutOthers: true });
|
||||
};
|
||||
|
||||
unhoverTarget = event => {
|
||||
const token = canvas.tokens.get(event.currentTarget.dataset.token);
|
||||
if (!token?.controlled) token._onHoverOut(event);
|
||||
};
|
||||
|
||||
clickTarget = event => {
|
||||
event.stopPropagation();
|
||||
const token = canvas.tokens.get(event.currentTarget.dataset.token);
|
||||
if (!token) {
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.AttackTargetDoesNotExist'));
|
||||
return;
|
||||
}
|
||||
|
||||
game.canvas.pan(token);
|
||||
};
|
||||
|
||||
onDamage = async (event, message) => {
|
||||
event.stopPropagation();
|
||||
const { isHit, targets } = this.getTargetList(event, message);
|
||||
|
||||
if (message.system.onSave && isHit) {
|
||||
const pendingingSaves = message.system.targets.filter(
|
||||
target => target.hit && target.saved.success === null
|
||||
);
|
||||
if (pendingingSaves.length) {
|
||||
const confirm = await foundry.applications.api.DialogV2.confirm({
|
||||
window: { title: 'Pending Reaction Rolls found' },
|
||||
content: `<p>Some Tokens still need to roll their Reaction Roll.</p><p>Are you sure you want to continue ?</p><p><i>Undone reaction rolls will be considered as failed</i></p>`
|
||||
});
|
||||
if (!confirm) return;
|
||||
}
|
||||
}
|
||||
|
||||
if (targets.length === 0)
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected'));
|
||||
for (let target of targets) {
|
||||
let damage = message.system.roll.total;
|
||||
if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true)
|
||||
damage = Math.ceil(damage * (CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1));
|
||||
|
||||
await target.actor.takeDamage(damage, message.system.roll.type);
|
||||
}
|
||||
};
|
||||
|
||||
onHealing = async (event, message) => {
|
||||
event.stopPropagation();
|
||||
const targets = Array.from(game.user.targets);
|
||||
|
||||
if (targets.length === 0)
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected'));
|
||||
|
||||
for (var target of targets) {
|
||||
await target.actor.takeHealing([{ value: message.system.roll.total, type: message.system.roll.type }]);
|
||||
}
|
||||
};
|
||||
|
||||
onToggleTargets = async event => {
|
||||
event.stopPropagation();
|
||||
$($(event.currentTarget).parent()).find('.target-container').toggleClass('hidden');
|
||||
};
|
||||
|
||||
hoverAdvantage = event => {
|
||||
$(event.currentTarget).siblings('.advantage').toggleClass('unused');
|
||||
};
|
||||
|
||||
selectAdvantage = async (event, message) => {
|
||||
event.stopPropagation();
|
||||
|
||||
const updateMessage = game.messages.get(message._id);
|
||||
await updateMessage.update({ system: { advantageSelected: event.currentTarget.id === 'hope' ? 1 : 2 } });
|
||||
|
||||
$(event.currentTarget).siblings('.advantage').off('click');
|
||||
$(event.currentTarget).off('click');
|
||||
};
|
||||
|
||||
abilityUseButton = async (event, message) => {
|
||||
event.stopPropagation();
|
||||
|
||||
const action = message.system.actions[Number.parseInt(event.currentTarget.dataset.index)];
|
||||
const actor = game.actors.get(message.system.source.actor);
|
||||
await actor.useAction(action);
|
||||
};
|
||||
|
||||
actionUseButton = async (_, message) => {
|
||||
const parent = await foundry.utils.fromUuid(message.system.actor);
|
||||
const actionType = Object.values(message.system.moves)[0].actions[0];
|
||||
const cls = CONFIG.DH.ACTIONS.actionTypes[actionType.type];
|
||||
const action = new cls(
|
||||
{ ...actionType, _id: foundry.utils.randomID(), name: game.i18n.localize(actionType.name) },
|
||||
{ parent: parent }
|
||||
);
|
||||
|
||||
action.use();
|
||||
};
|
||||
}
|
||||
109
module/applications/ui/combatTracker.mjs
Normal file
109
module/applications/ui/combatTracker.mjs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import { EncounterCountdowns } from '../ui/countdowns.mjs';
|
||||
|
||||
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
||||
static DEFAULT_OPTIONS = {
|
||||
actions: {
|
||||
requestSpotlight: this.requestSpotlight,
|
||||
toggleSpotlight: this.toggleSpotlight,
|
||||
setActionTokens: this.setActionTokens,
|
||||
openCountdowns: this.openCountdowns
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
header: {
|
||||
template: 'systems/daggerheart/templates/ui/combatTracker/combatTrackerHeader.hbs'
|
||||
},
|
||||
tracker: {
|
||||
template: 'systems/daggerheart/templates/ui/combatTracker/combatTracker.hbs'
|
||||
},
|
||||
footer: {
|
||||
template: 'systems/daggerheart/templates/ui/combatTracker/combatTrackerFooter.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
async _prepareCombatContext(context, options) {
|
||||
await super._prepareCombatContext(context, options);
|
||||
|
||||
Object.assign(context, {
|
||||
fear: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareTrackerContext(context, options) {
|
||||
await super._prepareTrackerContext(context, options);
|
||||
|
||||
const adversaries = context.turns?.filter(x => x.isNPC) ?? [];
|
||||
const characters = context.turns?.filter(x => !x.isNPC) ?? [];
|
||||
|
||||
Object.assign(context, {
|
||||
actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens,
|
||||
adversaries,
|
||||
characters
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareTurnContext(combat, combatant, index) {
|
||||
const turn = await super._prepareTurnContext(combat, combatant, index);
|
||||
return { ...turn, isNPC: combatant.isNPC, system: combatant.system.toObject() };
|
||||
}
|
||||
|
||||
_getCombatContextOptions() {
|
||||
return [
|
||||
{
|
||||
name: 'COMBAT.ClearMovementHistories',
|
||||
icon: '<i class="fa-solid fa-shoe-prints"></i>',
|
||||
condition: () => game.user.isGM && this.viewed?.combatants.size > 0,
|
||||
callback: () => this.viewed.clearMovementHistories()
|
||||
},
|
||||
{
|
||||
name: 'COMBAT.Delete',
|
||||
icon: '<i class="fa-solid fa-trash"></i>',
|
||||
condition: () => game.user.isGM && !!this.viewed,
|
||||
callback: () => this.viewed.endCombat()
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
static async requestSpotlight(_, target) {
|
||||
const { combatantId } = target.closest('[data-combatant-id]')?.dataset ?? {};
|
||||
const combatant = this.viewed.combatants.get(combatantId);
|
||||
await combatant.update({
|
||||
'system.spotlight': {
|
||||
requesting: !combatant.system.spotlight.requesting
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async toggleSpotlight(_, target) {
|
||||
const { combatantId } = target.closest('[data-combatant-id]')?.dataset ?? {};
|
||||
const combatant = this.viewed.combatants.get(combatantId);
|
||||
|
||||
const toggleTurn = this.viewed.combatants.contents
|
||||
.sort(this.viewed._sortCombatants)
|
||||
.map(x => x.id)
|
||||
.indexOf(combatantId);
|
||||
|
||||
if (this.viewed.turn !== toggleTurn) Hooks.callAll(CONFIG.DH.HOOKS.spotlight, {});
|
||||
|
||||
await this.viewed.update({ turn: this.viewed.turn === toggleTurn ? null : toggleTurn });
|
||||
await combatant.update({ 'system.spotlight.requesting': false });
|
||||
}
|
||||
|
||||
static async setActionTokens(_, target) {
|
||||
const { combatantId, tokenIndex } = target.closest('[data-combatant-id]')?.dataset ?? {};
|
||||
|
||||
const combatant = this.viewed.combatants.get(combatantId);
|
||||
const changeIndex = Number(tokenIndex);
|
||||
const newIndex = combatant.system.actionTokens > changeIndex ? changeIndex : changeIndex + 1;
|
||||
|
||||
await combatant.update({ 'system.actionTokens': newIndex });
|
||||
this.render();
|
||||
}
|
||||
|
||||
static openCountdowns() {
|
||||
new EncounterCountdowns().open();
|
||||
}
|
||||
}
|
||||
370
module/applications/ui/countdowns.mjs
Normal file
370
module/applications/ui/countdowns.mjs
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
import { countdownTypes } from '../../config/generalConfig.mjs';
|
||||
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import constructHTMLButton from '../../helpers/utils.mjs';
|
||||
import OwnershipSelection from '../dialogs/ownershipSelection.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
class Countdowns extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(basePath) {
|
||||
super({});
|
||||
|
||||
this.basePath = basePath;
|
||||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.format('DAGGERHEART.Countdown.Title', {
|
||||
type: game.i18n.localize(`DAGGERHEART.Countdown.Types.${this.basePath}`)
|
||||
});
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['daggerheart', 'dh-style', 'countdown'],
|
||||
tag: 'form',
|
||||
position: { width: 740, height: 700 },
|
||||
window: {
|
||||
frame: true,
|
||||
title: 'Countdowns',
|
||||
resizable: true,
|
||||
minimizable: false
|
||||
},
|
||||
actions: {
|
||||
addCountdown: this.addCountdown,
|
||||
removeCountdown: this.removeCountdown,
|
||||
editImage: this.onEditImage,
|
||||
openOwnership: this.openOwnership,
|
||||
openCountdownOwnership: this.openCountdownOwnership,
|
||||
toggleSimpleView: this.toggleSimpleView
|
||||
},
|
||||
form: { handler: this.updateData, submitOnChange: true }
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
countdowns: {
|
||||
template: 'systems/daggerheart/templates/ui/countdowns.hbs',
|
||||
scrollable: ['.expanded-view']
|
||||
}
|
||||
};
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement.querySelectorAll('.mini-countdown-container').forEach(element => {
|
||||
element.addEventListener('click', event => this.updateCountdownValue.bind(this)(event, false));
|
||||
element.addEventListener('contextmenu', event => this.updateCountdownValue.bind(this)(event, true));
|
||||
});
|
||||
}
|
||||
|
||||
async _preFirstRender(context, options) {
|
||||
options.position =
|
||||
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.basePath}Countdown`].position) ??
|
||||
Countdowns.DEFAULT_OPTIONS.position;
|
||||
|
||||
const viewSetting =
|
||||
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.basePath}Countdown`].simple) ?? !game.user.isGM;
|
||||
this.simpleView =
|
||||
game.user.isGM || !this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER) ? viewSetting : true;
|
||||
context.simple = this.simpleView;
|
||||
}
|
||||
|
||||
_onPosition(position) {
|
||||
game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.basePath}Countdown`].position, position);
|
||||
}
|
||||
|
||||
async _renderFrame(options) {
|
||||
const frame = await super._renderFrame(options);
|
||||
|
||||
if (this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER)) {
|
||||
const button = constructHTMLButton({
|
||||
label: '',
|
||||
classes: ['header-control', 'icon', 'fa-solid', 'fa-wrench'],
|
||||
dataset: { action: 'toggleSimpleView', tooltip: 'DAGGERHEART.Countdown.ToggleSimple' }
|
||||
});
|
||||
this.window.controls.after(button);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
testUserPermission(level, exact, altSettings) {
|
||||
if (game.user.isGM) return true;
|
||||
|
||||
const settings =
|
||||
altSettings ?? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
||||
const defaultAllowed = exact ? settings.ownership.default === level : settings.ownership.default >= level;
|
||||
const userAllowed = exact
|
||||
? settings.playerOwnership[game.user.id]?.value === level
|
||||
: settings.playerOwnership[game.user.id]?.value >= level;
|
||||
return defaultAllowed || userAllowed;
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
const countdownData = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[
|
||||
this.basePath
|
||||
];
|
||||
|
||||
context.isGM = game.user.isGM;
|
||||
context.base = this.basePath;
|
||||
|
||||
context.canCreate = this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER, true);
|
||||
context.source = {
|
||||
...countdownData,
|
||||
countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => {
|
||||
const countdown = countdownData.countdowns[key];
|
||||
|
||||
if (this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.LIMITED, false, countdown)) {
|
||||
acc[key] = {
|
||||
...countdown,
|
||||
canEdit: this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER, true, countdown)
|
||||
};
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
context.systemFields = countdownData.schema.fields;
|
||||
context.countdownFields = context.systemFields.countdowns.element.fields;
|
||||
context.simple = this.simpleView;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async updateData(event, _, formData) {
|
||||
const data = foundry.utils.expandObject(formData.object);
|
||||
const newSetting = foundry.utils.mergeObject(
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns).toObject(),
|
||||
data
|
||||
);
|
||||
|
||||
if (game.user.isGM) {
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, newSetting);
|
||||
this.render();
|
||||
} else {
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateSetting,
|
||||
uuid: CONFIG.DH.SETTINGS.gameSettings.Countdowns,
|
||||
update: newSetting
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async updateSetting(update) {
|
||||
if (game.user.isGM) {
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, update);
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.Countdown,
|
||||
application: `${this.basePath}-countdowns`
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
} else {
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateSetting,
|
||||
uuid: CONFIG.DH.SETTINGS.gameSettings.Countdowns,
|
||||
update: update,
|
||||
refresh: { refreshType: RefreshType.Countdown, application: `${this.basePath}-countdowns` }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static onEditImage(_, target) {
|
||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
||||
const current = setting.countdowns[target.dataset.countdown].img;
|
||||
const fp = new foundry.applications.apps.FilePicker.implementation({
|
||||
current,
|
||||
type: 'image',
|
||||
callback: async path => this.updateImage.bind(this)(path, target.dataset.countdown),
|
||||
top: this.position.top + 40,
|
||||
left: this.position.left + 10
|
||||
});
|
||||
return fp.browse();
|
||||
}
|
||||
|
||||
async updateImage(path, countdown) {
|
||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
await setting.updateSource({
|
||||
[`${this.basePath}.countdowns.${countdown}.img`]: path
|
||||
});
|
||||
|
||||
await this.updateSetting(setting);
|
||||
}
|
||||
|
||||
static openOwnership(_, target) {
|
||||
new Promise((resolve, reject) => {
|
||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath];
|
||||
const ownership = { default: setting.ownership.default, players: setting.playerOwnership };
|
||||
new OwnershipSelection(resolve, reject, this.title, ownership).render(true);
|
||||
}).then(async ownership => {
|
||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
await setting.updateSource({
|
||||
[`${this.basePath}.ownership`]: ownership
|
||||
});
|
||||
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, setting.toObject());
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
static openCountdownOwnership(_, target) {
|
||||
const countdownId = target.dataset.countdown;
|
||||
new Promise((resolve, reject) => {
|
||||
const countdown = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath]
|
||||
.countdowns[countdownId];
|
||||
const ownership = { default: countdown.ownership.default, players: countdown.playerOwnership };
|
||||
new OwnershipSelection(resolve, reject, countdown.name, ownership).render(true);
|
||||
}).then(async ownership => {
|
||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
await setting.updateSource({
|
||||
[`${this.basePath}.countdowns.${countdownId}.ownership`]: ownership
|
||||
});
|
||||
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, setting);
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
static async toggleSimpleView() {
|
||||
this.simpleView = !this.simpleView;
|
||||
await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.basePath}Countdown`].simple, this.simpleView);
|
||||
this.render();
|
||||
}
|
||||
|
||||
async updateCountdownValue(event, increase) {
|
||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
const countdown = countdownSetting[this.basePath].countdowns[event.currentTarget.dataset.countdown];
|
||||
|
||||
if (!this.testUserPermission(CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentValue = countdown.progress.current;
|
||||
|
||||
if (increase && currentValue === countdown.progress.max) return;
|
||||
if (!increase && currentValue === 0) return;
|
||||
|
||||
await countdownSetting.updateSource({
|
||||
[`${this.basePath}.countdowns.${event.currentTarget.dataset.countdown}.progress.current`]: increase
|
||||
? currentValue + 1
|
||||
: currentValue - 1
|
||||
});
|
||||
|
||||
await this.updateSetting(countdownSetting.toObject());
|
||||
}
|
||||
|
||||
static async addCountdown() {
|
||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
await countdownSetting.updateSource({
|
||||
[`${this.basePath}.countdowns.${foundry.utils.randomID()}`]: {
|
||||
name: game.i18n.localize('DAGGERHEART.Countdown.NewCountdown'),
|
||||
ownership: game.user.isGM
|
||||
? {}
|
||||
: {
|
||||
players: {
|
||||
[game.user.id]: { type: CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await this.updateSetting(countdownSetting.toObject());
|
||||
}
|
||||
|
||||
static async removeCountdown(_, target) {
|
||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
const countdownName = countdownSetting[this.basePath].countdowns[target.dataset.countdown].name;
|
||||
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.localize('DAGGERHEART.Countdown.RemoveCountdownTitle')
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.Countdown.RemoveCountdownText', { name: countdownName })
|
||||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
await countdownSetting.updateSource({ [`${this.basePath}.countdowns.-=${target.dataset.countdown}`]: null });
|
||||
|
||||
await this.updateSetting(countdownSetting.toObject());
|
||||
}
|
||||
|
||||
async open() {
|
||||
await this.render(true);
|
||||
if (
|
||||
Object.keys(
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns)[this.basePath].countdowns
|
||||
).length > 0
|
||||
) {
|
||||
this.minimize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class NarrativeCountdowns extends Countdowns {
|
||||
constructor() {
|
||||
super('narrative');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'narrative-countdowns'
|
||||
};
|
||||
}
|
||||
|
||||
export class EncounterCountdowns extends Countdowns {
|
||||
constructor() {
|
||||
super('encounter');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'encounter-countdowns'
|
||||
};
|
||||
}
|
||||
|
||||
export const registerCountdownApplicationHooks = () => {
|
||||
const updateCountdowns = async shouldProgress => {
|
||||
if (game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).countdowns) {
|
||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
for (let countdownCategoryKey in countdownSetting) {
|
||||
const countdownCategory = countdownSetting[countdownCategoryKey];
|
||||
for (let countdownKey in countdownCategory.countdowns) {
|
||||
const countdown = countdownCategory.countdowns[countdownKey];
|
||||
|
||||
if (shouldProgress(countdown)) {
|
||||
await countdownSetting.updateSource({
|
||||
[`${countdownCategoryKey}.countdowns.${countdownKey}.progress.current`]:
|
||||
countdown.progress.current - 1
|
||||
});
|
||||
await game.settings.set(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.Countdowns,
|
||||
countdownSetting
|
||||
);
|
||||
foundry.applications.instances.get(`${countdownCategoryKey}-countdowns`)?.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Hooks.on(CONFIG.DH.HOOKS.characterAttack, async () => {
|
||||
updateCountdowns(countdown => {
|
||||
return (
|
||||
countdown.progress.type.value === countdownTypes.characterAttack.id && countdown.progress.current > 0
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Hooks.on(CONFIG.DH.HOOKS.spotlight, async () => {
|
||||
updateCountdowns(countdown => {
|
||||
return countdown.progress.type.value === countdownTypes.spotlight.id && countdown.progress.current > 0;
|
||||
});
|
||||
});
|
||||
};
|
||||
110
module/applications/ui/fearTracker.mjs
Normal file
110
module/applications/ui/fearTracker.mjs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
/**
|
||||
* A UI element which displays the Users defined for this world.
|
||||
* Currently active users are always displayed, while inactive users can be displayed on toggle.
|
||||
*
|
||||
* @extends ApplicationV2
|
||||
* @mixes HandlebarsApplication
|
||||
*/
|
||||
|
||||
export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'resources',
|
||||
classes: [],
|
||||
tag: 'div',
|
||||
window: {
|
||||
frame: true,
|
||||
title: 'Fear',
|
||||
positioned: true,
|
||||
resizable: true,
|
||||
minimizable: false
|
||||
},
|
||||
actions: {
|
||||
setFear: FearTracker.setFear,
|
||||
increaseFear: FearTracker.increaseFear
|
||||
},
|
||||
position: {
|
||||
width: 222,
|
||||
height: 222
|
||||
// top: "200px",
|
||||
// left: "120px"
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
resources: {
|
||||
root: true,
|
||||
template: 'systems/daggerheart/templates/ui/fearTracker.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
get currentFear() {
|
||||
return game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear);
|
||||
}
|
||||
|
||||
get maxFear() {
|
||||
return game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Rendering */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @override */
|
||||
async _prepareContext(_options) {
|
||||
const display = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).displayFear,
|
||||
current = this.currentFear,
|
||||
max = this.maxFear,
|
||||
percent = (current / max) * 100,
|
||||
isGM = game.user.isGM;
|
||||
// Return the data for rendering
|
||||
return { display, current, max, percent, isGM };
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _preFirstRender(context, options) {
|
||||
options.position =
|
||||
game.user.getFlag(CONFIG.DH.id, 'app.resources.position') ?? FearTracker.DEFAULT_OPTIONS.position;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _preRender(context, options) {
|
||||
if (this.currentFear > this.maxFear)
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, this.maxFear);
|
||||
}
|
||||
|
||||
_onPosition(position) {
|
||||
game.user.setFlag(CONFIG.DH.id, 'app.resources.position', position);
|
||||
}
|
||||
|
||||
async close(options = {}) {
|
||||
if (!options.allowed) return;
|
||||
else super.close(options);
|
||||
}
|
||||
|
||||
static async setFear(event, target) {
|
||||
if (!game.user.isGM) return;
|
||||
const fearCount = Number(target.dataset.index ?? 0);
|
||||
await this.updateFear(this.currentFear === fearCount + 1 ? fearCount : fearCount + 1);
|
||||
}
|
||||
|
||||
static async increaseFear(event, target) {
|
||||
let value = target.dataset.increment ?? 0,
|
||||
operator = value.split('')[0] ?? null;
|
||||
value = Number(value);
|
||||
await this.updateFear(operator ? this.currentFear + value : value);
|
||||
}
|
||||
|
||||
async updateFear(value) {
|
||||
if (!game.user.isGM) return;
|
||||
value = Math.max(0, Math.min(this.maxFear, value));
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue