Combat and CombatTracker (#108)

* Added Combat and CombatTracker

* Some cleneaup

* Fixing and cleaning up

* Added categories for combatants

* Style improvements

* Layout change
This commit is contained in:
WBHarry 2025-06-07 00:06:54 +02:00 committed by GitHub
parent 32730b3aac
commit aa8fe6a7a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 730 additions and 684 deletions

View file

@ -1,4 +1,3 @@
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
/**
@ -10,101 +9,101 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
*/
export default class Resources 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: Resources.setFear,
increaseFear: Resources.increaseFear
},
position: {
width: 222,
height: 222,
// top: "200px",
// left: "120px"
constructor(options = {}) {
super(options);
}
};
/** @override */
static PARTS = {
resources: {
root: true,
template: "systems/daggerheart/templates/views/resources.hbs"
// template: "templates/ui/players.hbs"
/** @inheritDoc */
static DEFAULT_OPTIONS = {
id: 'resources',
classes: [],
tag: 'div',
window: {
frame: true,
title: 'Fear',
positioned: true,
resizable: true,
minimizable: false
},
actions: {
setFear: Resources.setFear,
increaseFear: Resources.increaseFear
},
position: {
width: 222,
height: 222
// top: "200px",
// left: "120px"
}
};
/** @override */
static PARTS = {
resources: {
root: true,
template: 'systems/daggerheart/templates/views/resources.hbs'
}
};
get currentFear() {
return game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
}
};
get currentFear() {
return game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
}
get maxFear() {
return game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.MaxFear);
}
get maxFear() {
return game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.MaxFear);
}
/* -------------------------------------------- */
/* Rendering */
/* -------------------------------------------- */
/* -------------------------------------------- */
/* Rendering */
/* -------------------------------------------- */
/** @override */
async _prepareContext(_options) {
const display = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.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 _prepareContext(_options) {
const display = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.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(SYSTEM.id, 'app.resources.position') ?? Resources.DEFAULT_OPTIONS.position;
}
/** @override */
async _preFirstRender(context, options) {
options.position = game.user.getFlag(SYSTEM.id, 'app.resources.position') ?? Resources.DEFAULT_OPTIONS.position;
}
/** @override */
async _preRender(context, options) {
if (this.currentFear > this.maxFear)
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear, this.maxFear);
}
/** @override */
async _preRender(context, options) {
if(this.currentFear > this.maxFear) await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear, this.maxFear);
}
_onPosition(position) {
game.user.setFlag(SYSTEM.id, 'app.resources.position', position);
}
_onPosition(position) {
game.user.setFlag(SYSTEM.id, 'app.resources.position', position);
}
async close(options = {}) {
if (!options.allowed) return;
else super.close(options);
}
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 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);
}
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(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear, value);
}
}
async updateFear(value) {
if (!game.user.isGM) return;
value = Math.max(0, Math.min(this.maxFear, value));
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear, value);
}
}

View file

@ -1,5 +1,7 @@
import DhAppearance from '../data/settings/Appearance.mjs';
import DHAppearanceSettings from './settings/appearanceSettings.mjs';
import DhVariantRules from '../data/settings/VariantRules.mjs';
import DHVariantRuleSettings from './settings/variantRuleSettings.mjs';
class DhpAutomationSettings extends FormApplication {
constructor(object = {}, options = {}) {
@ -181,7 +183,8 @@ export const registerDHSettings = () => {
type: Number,
default: 0,
onChange: () => {
if(ui.resources) ui.resources.render({force: true});
if (ui.resources) ui.resources.render({ force: true });
ui.combat.render({ force: true });
}
});
@ -193,7 +196,7 @@ export const registerDHSettings = () => {
type: Number,
default: 12,
onChange: () => {
if(ui.resources) ui.resources.render({force: true});
if (ui.resources) ui.resources.render({ force: true });
}
});
@ -204,15 +207,15 @@ export const registerDHSettings = () => {
config: true,
type: String,
choices: {
'token': 'Tokens',
'bar': 'Bar',
'hide': 'Hide'
token: 'Tokens',
bar: 'Bar',
hide: 'Hide'
},
default: 'token',
onChange: value => {
if(ui.resources) {
if(value === 'hide') ui.resources.close({allowed: true});
else ui.resources.render({force: true});
if (ui.resources) {
if (value === 'hide') ui.resources.close({ allowed: true });
else ui.resources.render({ force: true });
}
}
});
@ -251,6 +254,13 @@ export const registerDHSettings = () => {
}
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.variantRules, {
scope: 'world',
config: false,
type: DhVariantRules,
default: DhVariantRules.defaultSchema
});
game.settings.register(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance, {
scope: 'client',
config: false,
@ -291,4 +301,13 @@ export const registerDHSettings = () => {
type: DHAppearanceSettings,
restricted: false
});
game.settings.registerMenu(SYSTEM.id, SYSTEM.SETTINGS.menu.VariantRules.Name, {
name: game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.title'),
label: game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.label'),
hint: game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.hint'),
icon: SYSTEM.SETTINGS.menu.VariantRules.Icon,
type: DHVariantRuleSettings,
restricted: false
});
};

View file

@ -0,0 +1,59 @@
import DhVariantRules from '../../data/settings/VariantRules.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DHVariantRuleSettings extends HandlebarsApplicationMixin(ApplicationV2) {
constructor() {
super({});
this.settings = new DhVariantRules(
game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.variantRules).toObject()
);
}
get title() {
return game.i18n.localize('DAGGERHEART.Settings.Menu.VariantRules.name');
}
static DEFAULT_OPTIONS = {
tag: 'form',
id: 'daggerheart-appearance-settings',
classes: ['daggerheart', 'setting', 'dh-style'],
position: { width: '600', height: 'auto' },
actions: {
reset: this.reset,
save: this.save
},
form: { handler: this.updateData, submitOnChange: true }
};
static PARTS = {
main: {
template: 'systems/daggerheart/templates/settings/variant-rules.hbs'
}
};
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.settingFields = this.settings;
return context;
}
static async updateData(event, element, formData) {
const updatedSettings = foundry.utils.expandObject(formData.object);
await this.settings.updateSource(updatedSettings);
this.render();
}
static async reset() {
this.settings = new DhVariantRules();
this.render();
}
static async save() {
await game.settings.set(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.variantRules, this.settings.toObject());
this.close();
}
}

View file

@ -10,6 +10,10 @@ export const menu = {
Range: {
Name: 'GameSettingsRange',
Icon: 'fa-solid fa-ruler'
},
VariantRules: {
Name: 'GameSettingsVariantrules',
Icon: 'fa-solid fa-scale-balanced'
}
};
@ -27,5 +31,6 @@ export const gameSettings = {
AbilityArray: 'AbilityArray',
RangeMeasurement: 'RangeMeasurement'
},
appearance: 'Appearance'
appearance: 'Appearance',
variantRules: 'VariantRules'
};

View file

@ -1,8 +1,8 @@
export { default as DhpPC } from './pc.mjs';
export { default as DhpClass } from './class.mjs';
export { default as DhpSubclass } from './subclass.mjs';
export { default as DhpCombat } from './combat.mjs';
export { default as DhpCombatant } from './combatant.mjs';
export { default as DhCombat } from './combat.mjs';
export { default as DhCombatant } from './combatant.mjs';
export { default as DhpAdversary } from './adversary.mjs';
export { default as DhpFeature } from './feature.mjs';
export { default as DhpDomainCard } from './domainCard.mjs';

View file

@ -1,9 +1,6 @@
export default class DhpCombat extends foundry.abstract.TypeDataModel {
export default class DhCombat extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
actions: new fields.NumberField({ initial: 0, integer: true }),
activeCombatant: new fields.StringField({})
};
return {};
}
}

View file

@ -1,8 +1,11 @@
export default class DhpCombatant extends foundry.abstract.TypeDataModel {
export default class DhCombatant extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
active: new fields.BooleanField({ initial: false })
spotlight: new fields.SchemaField({
requesting: new fields.BooleanField({ required: true, initial: false })
}),
actionTokens: new fields.NumberField({ required: true, integer: true, initial: 3 })
};
}
}

View file

@ -0,0 +1,13 @@
export default class DhVariantRules extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
actionTokens: new fields.SchemaField({
enabled: new fields.BooleanField({ required: true, initial: false }),
tokens: new fields.NumberField({ required: true, integer: true, initial: 3 })
})
};
}
static defaultSchema = {};
}

View file

@ -1,44 +1,19 @@
import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs';
export default class DhpCombat extends Combat {
_sortCombatants(a, b) {
if (a.isNPC !== b.isNPC) {
const aVal = a.isNPC ? 0 : 1;
const bVal = b.isNPC ? 0 : 1;
async startCombat() {
this._playCombatSound('startEncounter');
const updateData = { round: 1, turn: null };
Hooks.callAll('combatStart', this, updateData);
await this.update(updateData);
return this;
}
return aVal - bVal;
_sortCombatants(a, b) {
const aNPC = Number(a.isNPC);
const bNPC = Number(b.isNPC);
if (aNPC !== bNPC) {
return aNPC - bNPC;
}
return a.name.localeCompare(b.name);
}
async useActionToken(combatantId) {
const automateActionPoints = await game.settings.get(
SYSTEM.id,
SYSTEM.SETTINGS.gameSettings.Automation.ActionPoints
);
if (game.user.isGM) {
if (this.system.actions < 1) return;
const update = automateActionPoints
? { 'system.activeCombatant': combatantId, 'system.actions': Math.max(this.system.actions - 1, 0) }
: { 'system.activeCombatant': combatantId };
await this.update(update);
} else {
const update = automateActionPoints
? { 'system.activeCombatant': combatantId, 'system.actions': this.system.actions + 1 }
: { 'system.activeCombatant': combatantId };
await game.socket.emit(`system.${SYSTEM.id}`, {
action: socketEvent.GMUpdate,
data: {
action: GMUpdateEvent.UpdateDocument,
uuid: this.uuid,
update: update
}
});
}
}
}

View file

@ -1,199 +1,100 @@
import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs';
export default class DhpCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
constructor(data, context) {
super(data, context);
Hooks.on(socketEvent.DhpFearUpdate, this.onFearUpdate);
}
get template() {
return 'systems/daggerheart/templates/ui/combatTracker.hbs';
}
activateListeners(html) {
super.activateListeners(html);
html.on('click', '.token-action-tokens .use-action-token', this.useActionToken.bind(this));
html.on('click', '.encounter-gm-resources .trade-actions', this.tradeActions.bind(this));
html.on('click', '.encounter-gm-resources .trade-fear', this.tradeFear.bind(this));
html.on('click', '.encounter-gm-resources .icon-button.up', this.increaseResource.bind(this));
html.on('click', '.encounter-gm-resources .icon-button.down', this.decreaseResource.bind(this));
}
async useActionToken(event) {
event.stopPropagation();
const combatant = event.currentTarget.dataset.combatant;
await game.combat.useActionToken(combatant);
}
async tradeActions(event) {
if (event.currentTarget.classList.contains('disabled')) return;
const currentFear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
const value = currentFear + 1;
if (value <= 6) {
Hooks.callAll(socketEvent.GMUpdate, GMUpdateEvent.UpdateFear, null, value);
await game.socket.emit(`system.${SYSTEM.id}`, {
action: socketEvent.GMUpdate,
data: { action: GMUpdateEvent.UpdateFear, update: value }
});
await game.combat.update({ 'system.actions': game.combat.system.actions - 2 });
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
static DEFAULT_OPTIONS = {
actions: {
requestSpotlight: this.requestSpotlight,
toggleSpotlight: this.toggleSpotlight,
setActionTokens: this.setActionTokens
}
}
async tradeFear() {
if (event.currentTarget.classList.contains('disabled')) return;
const currentFear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
const value = currentFear - 1;
if (value >= 0) {
Hooks.callAll(socketEvent.GMUpdate, GMUpdateEvent.UpdateFear, null, value);
await game.socket.emit(`system.${SYSTEM.id}`, {
action: socketEvent.GMUpdate,
data: { action: GMUpdateEvent.UpdateFear, update: value }
});
await game.combat.update({ 'system.actions': game.combat.system.actions + 2 });
}
}
async increaseResource(event) {
if (event.currentTarget.dataset.type === 'action') {
await game.combat.update({ 'system.actions': game.combat.system.actions + 1 });
}
const currentFear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
const value = currentFear + 1;
if (event.currentTarget.dataset.type === 'fear' && value <= 6) {
Hooks.callAll(socketEvent.GMUpdate, GMUpdateEvent.UpdateFear, null, value);
await game.socket.emit(`system.${SYSTEM.id}`, {
action: socketEvent.GMUpdate,
data: { action: GMUpdateEvent.UpdateFear, update: value }
});
}
this.render();
}
async decreaseResource(event) {
if (event.currentTarget.dataset.type === 'action' && game.combat.system.actions - 1 >= 0) {
await game.combat.update({ 'system.actions': game.combat.system.actions - 1 });
}
const currentFear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
const value = currentFear - 1;
if (event.currentTarget.dataset.type === 'fear' && value >= 0) {
Hooks.callAll(socketEvent.GMUpdate, GMUpdateEvent.UpdateFear, null, value);
await game.socket.emit(`system.${SYSTEM.id}`, {
action: socketEvent.GMUpdate,
data: { action: GMUpdateEvent.UpdateFear, update: value }
});
}
this.render();
}
async getData(options = {}) {
let context = await super.getData(options);
// Get the combat encounters possible for the viewed Scene
const combat = this.viewed;
const hasCombat = combat !== null;
const combats = this.combats;
const currentIdx = combats.findIndex(c => c === combat);
const previousId = currentIdx > 0 ? combats[currentIdx - 1].id : null;
const nextId = currentIdx < combats.length - 1 ? combats[currentIdx + 1].id : null;
const settings = game.settings.get('core', Combat.CONFIG_SETTING);
// Prepare rendering data
context = foundry.utils.mergeObject(context, {
combats: combats,
currentIndex: currentIdx + 1,
combatCount: combats.length,
hasCombat: hasCombat,
combat,
turns: [],
previousId,
nextId,
started: this.started,
control: false,
settings,
linked: combat?.scene !== null,
labels: {}
});
context.labels.scope = game.i18n.localize(`COMBAT.${context.linked ? 'Linked' : 'Unlinked'}`);
if (!hasCombat) return context;
// Format information about each combatant in the encounter
let hasDecimals = false;
const turns = [];
for (let [i, combatant] of combat.turns.entries()) {
if (!combatant.visible) continue;
// Prepare turn data
const resource =
combatant.permission >= CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER ? combatant.resource : null;
const turn = {
id: combatant.id,
name: combatant.name,
img: await this._getCombatantThumbnail(combatant),
active: combatant.id === combat.system.activeCombatant,
owner: combatant.isOwner,
defeated: combatant.isDefeated,
hidden: combatant.hidden,
initiative: combatant.initiative,
hasRolled: combatant.initiative !== null,
hasResource: resource !== null,
resource: resource,
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
playerCharacter: game.user?.character?.id === combatant.actor.id,
ownedByPlayer: combatant.hasPlayerOwner
};
if (turn.initiative !== null && !Number.isInteger(turn.initiative)) hasDecimals = true;
turn.css = [turn.active ? 'active' : '', turn.hidden ? 'hidden' : '', turn.defeated ? 'defeated' : '']
.join(' ')
.trim();
// Actor and Token status effects
turn.effects = new Set();
if (combatant.token) {
combatant.token.effects.forEach(e => turn.effects.add(e));
if (combatant.token.overlayEffect) turn.effects.add(combatant.token.overlayEffect);
}
if (combatant.actor) {
for (const effect of combatant.actor.temporaryEffects) {
if (effect.statuses.has(CONFIG.specialStatusEffects.DEFEATED)) turn.defeated = true;
else if (effect.icon) turn.effects.add(effect.icon);
}
}
turns.push(turn);
}
// Format initiative numeric precision
const precision = CONFIG.Combat.initiative.decimals;
turns.forEach(t => {
if (t.initiative !== null) t.initiative = t.initiative.toFixed(hasDecimals ? precision : 0);
});
const fear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
// Merge update data for rendering
return foundry.utils.mergeObject(context, {
round: combat.round,
turn: combat.turn,
turns: turns,
control: combat.combatant?.players?.includes(game.user),
fear: fear
});
}
onFearUpdate = async () => {
this.render(true);
};
async close(options) {
Hooks.off(socketEvent.DhpFearUpdate, this.onFearUpdate);
static PARTS = {
header: {
template: 'systems/daggerheart/templates/ui/combat/combatTrackerHeader.hbs'
},
tracker: {
template: 'systems/daggerheart/templates/ui/combat/combatTracker.hbs'
},
footer: {
template: 'systems/daggerheart/templates/ui/combat/combatTrackerFooter.hbs'
}
};
return super.close(options);
async _prepareCombatContext(context, options) {
await super._prepareCombatContext(context, options);
Object.assign(context, {
fear: game.settings.get(SYSTEM.id, SYSTEM.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(SYSTEM.id, SYSTEM.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);
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();
}
}

View file

@ -1,53 +0,0 @@
import { GMUpdateEvent, socketEvent } from '../helpers/socket.mjs';
export default class DhpPlayers extends foundry.applications.ui.Players {
constructor(data, context) {
super(data, context);
Hooks.on(socketEvent.DhpFearUpdate, this.onFearUpdate);
}
get template() {
return 'systems/daggerheart/templates/ui/players.hbs';
}
async getData(options = {}) {
const context = super.getData(options);
context.fear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
context.user = game.user;
return context;
}
activateListeners(html) {
// Toggle online/offline
html.find('.players-mode').click(this._onToggleOfflinePlayers.bind(this));
html.find('.fear-control.up').click(async event => await this.updateFear(event, 1));
html.find('.fear-control.down').click(async event => await this.updateFear(event, -1));
// Context menu
const contextOptions = this._getUserContextOptions();
Hooks.call('getUserContextOptions', html, contextOptions);
new ContextMenu(html, '.player', contextOptions);
}
async updateFear(_, change) {
const fear = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Resources.Fear);
const value = Math.max(Math.min(fear + change, 6), 0);
Hooks.callAll(socketEvent.GMUpdate, GMUpdateEvent.UpdateFear, null, value);
await game.socket.emit(`system.${SYSTEM.id}`, {
action: socketEvent.GMUpdate,
data: { action: GMUpdateEvent.UpdateFear, update: value }
});
}
onFearUpdate = async () => {
this.render(true);
};
async close(options) {
Hooks.off(socketEvent.DhpFearUpdate, this.onFearUpdate);
return super.close(options);
}
}