From 0f77697614cfe81d90ac90ed4e571d04e0a426cd Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 6 Jun 2025 16:39:17 +0200 Subject: [PATCH] Temp --- daggerheart.mjs | 23 +- lang/en.json | 4 + module/data/_module.mjs | 8 +- module/data/combat.mjs | 4 +- module/data/combatant.mjs | 5 +- module/documents/combat.mjs | 73 +++--- module/ui/combatTracker.mjs | 232 +++--------------- module/ui/combatTrackerOld.mjs | 199 +++++++++++++++ styles/daggerheart.css | 79 +++--- styles/ui.less | 149 +++++++---- templates/ui/combat/combatTracker.hbs | 53 ++++ templates/ui/combat/combatTrackerFooter.hbs | 17 ++ templates/ui/combat/combatTrackerHeader.hbs | 86 +++++++ ...combatTracker.hbs => combatTrackerOld.hbs} | 0 14 files changed, 589 insertions(+), 343 deletions(-) create mode 100644 module/ui/combatTrackerOld.mjs create mode 100644 templates/ui/combat/combatTracker.hbs create mode 100644 templates/ui/combat/combatTrackerFooter.hbs create mode 100644 templates/ui/combat/combatTrackerHeader.hbs rename templates/ui/{combatTracker.hbs => combatTrackerOld.hbs} (100%) diff --git a/daggerheart.mjs b/daggerheart.mjs index bcb058cb..bc342899 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -3,11 +3,10 @@ import * as applications from './module/applications/_module.mjs'; import * as models from './module/data/_module.mjs'; import * as documents from './module/documents/_module.mjs'; import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs'; -import DhpCombatTracker from './module/ui/combatTracker.mjs'; +import DhCombatTracker from './module/ui/combatTracker.mjs'; import { GMUpdateEvent, handleSocketEvent, socketEvent } from './module/helpers/socket.mjs'; import { registerDHSettings } from './module/applications/settings.mjs'; import DhpChatLog from './module/ui/chatLog.mjs'; -import DhpPlayers from './module/ui/players.mjs'; import DhpRuler from './module/ui/ruler.mjs'; import DhpTokenRuler from './module/ui/tokenRuler.mjs'; import { dualityRollEnricher } from './module/enrichers/DualityRollEnricher.mjs'; @@ -65,11 +64,11 @@ Hooks.once('init', () => { Actors.registerSheet(SYSTEM.id, applications.DhpEnvironment, { types: ['environment'], makeDefault: true }); CONFIG.Combat.dataModels = { - base: models.DhpCombat + base: models.DhCombat }; CONFIG.Combatant.dataModels = { - base: models.DhpCombatant + base: models.DhCombatant }; CONFIG.ChatMessage.dataModels = models.messages.config; @@ -77,7 +76,7 @@ Hooks.once('init', () => { CONFIG.Canvas.rulerClass = DhpRuler; CONFIG.Combat.documentClass = documents.DhpCombat; - CONFIG.ui.combat = DhpCombatTracker; + CONFIG.ui.combat = DhCombatTracker; CONFIG.ui.chat = DhpChatLog; // CONFIG.ui.players = DhpPlayers; CONFIG.Token.rulerClass = DhpTokenRuler; @@ -97,8 +96,8 @@ Hooks.once('init', () => { Hooks.on('ready', () => { ui.resources = new CONFIG.ui.resources(); - ui.resources.render({force: true}); -}) + ui.resources.render({ force: true }); +}); Hooks.once('dicesoniceready', () => {}); @@ -201,8 +200,8 @@ Hooks.on('chatMessage', (_, message) => { const title = attributeValue ? game.i18n.format('DAGGERHEART.Chat.DualityRoll.AbilityCheckTitle', { - ability: game.i18n.localize(abilities[attributeValue].label) - }) + ability: game.i18n.localize(abilities[attributeValue].label) + }) : game.i18n.localize('DAGGERHEART.General.Duality'); const hopeAndFearRoll = `1${rollCommand.hope ?? 'd12'}+1${rollCommand.fear ?? 'd12'}`; @@ -221,9 +220,9 @@ Hooks.on('chatMessage', (_, message) => { roll, attribute: attribute ? { - value: attribute.data.value, - label: `${game.i18n.localize(abilities[attributeValue].label)} ${attribute.data.value >= 0 ? `+` : ``}${attribute.data.value}` - } + value: attribute.data.value, + label: `${game.i18n.localize(abilities[attributeValue].label)} ${attribute.data.value >= 0 ? `+` : ``}${attribute.data.value}` + } : undefined, title }); diff --git a/lang/en.json b/lang/en.json index 75a803e1..26b12c7c 100755 --- a/lang/en.json +++ b/lang/en.json @@ -329,6 +329,10 @@ "grimoire": "Grimoire" } }, + "Combat": { + "takeSpotlight": "Take The Spotlight", + "combatStarted": "Active" + }, "LevelUp": { "Tier1": { "Label": "Level 2-4", diff --git a/module/data/_module.mjs b/module/data/_module.mjs index 198f6547..3a830885 100644 --- a/module/data/_module.mjs +++ b/module/data/_module.mjs @@ -1,8 +1,8 @@ export { default as DhpPC } from './pc.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 DhpEnvironment } from './environment.mjs'; -export * as items from "./item/_module.mjs"; -export * as messages from "./chat-message/_modules.mjs"; \ No newline at end of file +export * as items from './item/_module.mjs'; +export * as messages from './chat-message/_modules.mjs'; diff --git a/module/data/combat.mjs b/module/data/combat.mjs index 3ad52b8b..60936003 100644 --- a/module/data/combat.mjs +++ b/module/data/combat.mjs @@ -1,9 +1,9 @@ -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({}) + started: new fields.BooleanField({ required: true, initial: false }) }; } } diff --git a/module/data/combatant.mjs b/module/data/combatant.mjs index 60c32db6..05046b5a 100644 --- a/module/data/combatant.mjs +++ b/module/data/combatant.mjs @@ -1,8 +1,9 @@ -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 }) + active: new fields.BooleanField({ initial: false }), + actionTokens: new fields.NumberField({ required: true, integer: true, initial: 3 }) }; } } diff --git a/module/documents/combat.mjs b/module/documents/combat.mjs index c7905605..d72a5a23 100644 --- a/module/documents/combat.mjs +++ b/module/documents/combat.mjs @@ -1,44 +1,53 @@ -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; + get combatant() { + return this.combatants.contents.find(x => x.system.active) ?? null; + } - return aVal - bVal; + async startCombat() { + this._playCombatSound('startEncounter'); + const updateData = { 'system.started': true }; + Hooks.callAll('combatStart', this, updateData); + await this.update(updateData); + return this; + } + + _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 - ); + // 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; + // 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 }; + // 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 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 - } - }); - } - } + // await game.socket.emit(`system.${SYSTEM.id}`, { + // action: socketEvent.GMUpdate, + // data: { + // action: GMUpdateEvent.UpdateDocument, + // uuid: this.uuid, + // update: update + // } + // }); + // } + // } } diff --git a/module/ui/combatTracker.mjs b/module/ui/combatTracker.mjs index 86002cb6..fe249d0e 100644 --- a/module/ui/combatTracker.mjs +++ b/module/ui/combatTracker.mjs @@ -1,199 +1,45 @@ -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: { + takeSpotlight: this.takeSpotlight } - } - - 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); + _getCombatContextOptions() { + return [ + { + name: 'COMBAT.ClearMovementHistories', + icon: '', + condition: () => game.user.isGM && this.viewed?.combatants.size > 0, + callback: () => this.viewed.clearMovementHistories() + }, + { + name: 'COMBAT.Delete', + icon: '', + condition: () => game.user.isGM && !!this.viewed, + callback: () => this.viewed.endCombat() + } + ]; + } + + static async takeSpotlight(_, target) { + const { combatantId } = target.closest('[data-combatant-id]')?.dataset ?? {}; + for (var combatant of this.viewed.combatants) { + await combatant.update({ 'system.active': combatantId === combatant.id ? true : false }); + } + + this.render(); } } diff --git a/module/ui/combatTrackerOld.mjs b/module/ui/combatTrackerOld.mjs new file mode 100644 index 00000000..86002cb6 --- /dev/null +++ b/module/ui/combatTrackerOld.mjs @@ -0,0 +1,199 @@ +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 }); + } + } + + 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); + + return super.close(options); + } +} diff --git a/styles/daggerheart.css b/styles/daggerheart.css index a99ffbd7..cf6877f7 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -1293,59 +1293,44 @@ .daggerheart.sheet.pc div[data-application-part] .sheet-body .inventory-container .inventory-item-list .inventory-row img { width: 32px; } -.combat-sidebar .encounter-gm-resources { - flex: 0; - display: flex; - justify-content: center; - padding: 8px 0; +.combat-sidebar .encounter-controls.combat { + justify-content: space-between; } -.combat-sidebar .encounter-gm-resources .gm-resource-controls { +.combat-sidebar .encounter-controls.combat .encounter-control-fear-container { + display: flex; + position: relative; + align-items: center; + justify-content: center; + color: black; +} +.combat-sidebar .encounter-controls.combat .encounter-control-fear-container .dice { + height: 24px; +} +.combat-sidebar .encounter-controls.combat .encounter-control-fear-container .encounter-control-fear { + position: absolute; + font-size: 16px; +} +.combat-sidebar .encounter-controls.combat .encounter-control-fear-container .encounter-control-counter { + position: absolute; + right: -10px; + color: var(--color-text-secondary); +} +.combat-sidebar .encounter-controls.combat .control-buttons { + width: min-content; +} +.combat-sidebar .token-actions { + flex: 0 0 var(--input-height); + align-self: stretch; display: flex; flex-direction: column; align-items: center; - padding: 0 4px; justify-content: center; } -.combat-sidebar .encounter-gm-resources .gm-resource-tools { - display: flex; - flex-direction: column; - justify-content: center; - padding: 0 5px 0 4px; +.combat-sidebar .token-actions button { + font-size: 22px; } -.combat-sidebar .encounter-gm-resources .gm-resource-tools i { - margin: 0 2px; - font-size: 16px; -} -.combat-sidebar .encounter-gm-resources .gm-resource-tools i.disabled { - opacity: 0.6; -} -.combat-sidebar .encounter-gm-resources .gm-resource-tools i:hover:not(.disabled) { - cursor: pointer; - filter: drop-shadow(0 0 3px red); -} -.combat-sidebar .encounter-gm-resources .gm-resource { - background: rgba(255, 255, 255, 0.1); - padding: 4px; - border-radius: 8px; - border: 2px solid black; - font-size: 20px; -} -.combat-sidebar .token-action-tokens { - flex: 0 0 48px; - text-align: center; -} -.combat-sidebar .token-action-tokens .use-action-token.disabled { - opacity: 0.6; -} -.combat-sidebar .icon-button.spaced { - margin-left: 4px; -} -.combat-sidebar .icon-button.disabled { - opacity: 0.6; -} -.combat-sidebar .icon-button:hover:not(.disabled) { - cursor: pointer; - filter: drop-shadow(0 0 3px red); +.combat-sidebar .token-actions button:hover { + background: inherit; } .chat-message.duality { border-color: black; @@ -2903,7 +2888,7 @@ div.daggerheart.views.multiclass { font-style: normal; font-weight: 700; font-display: swap; - src: url(https://fonts.gstatic.com/s/cinzeldecorative/v17/daaHSScvJGqLYhG8nNt8KPPswUAPniZoaelD.ttf) format('truetype'); + src: url(https://fonts.gstatic.com/s/cinzeldecorative/v18/daaHSScvJGqLYhG8nNt8KPPswUAPniZoaelD.ttf) format('truetype'); } @font-face { font-family: 'Montserrat'; diff --git a/styles/ui.less b/styles/ui.less index 7d1ff690..801b9eaa 100644 --- a/styles/ui.less +++ b/styles/ui.less @@ -1,71 +1,118 @@ .combat-sidebar { - .encounter-gm-resources { - flex: 0; - display: flex; - justify-content: center; - padding: @largePadding 0; + // .encounter-gm-resources { + // flex: 0; + // display: flex; + // justify-content: center; + // padding: @largePadding 0; - .gm-resource-controls { + // .gm-resource-controls { + // display: flex; + // flex-direction: column; + // align-items: center; + // padding: 0 4px; + // justify-content: center; + // } + + // .gm-resource-tools { + // display: flex; + // flex-direction: column; + // justify-content: center; + // padding: 0 5px 0 @fullPadding; + + // i { + // margin: 0 @tinyMargin; + // font-size: 16px; + + // &.disabled { + // opacity: 0.6; + // } + + // &:hover:not(.disabled) { + // cursor: pointer; + // filter: drop-shadow(0 0 3px @mainShadow); + // } + // } + // } + + // .gm-resource { + // background: rgba(255, 255, 255, 0.1); + // padding: @fullPadding; + // border-radius: 8px; + // border: @normalBorder solid black; + // font-size: 20px; + // } + // } + + // .token-action-tokens { + // flex: 0 0 48px; + // text-align: center; + + // .use-action-token { + // &.disabled { + // opacity: 0.6; + // } + // } + // } + + // .icon-button { + // &.spaced { + // margin-left: @halfMargin; + // } + + // &.disabled { + // opacity: 0.6; + // } + + // &:hover:not(.disabled) { + // cursor: pointer; + // filter: drop-shadow(0 0 3px @mainShadow); + // } + // } + .encounter-controls.combat { + justify-content: space-between; + + .encounter-control-fear-container { display: flex; - flex-direction: column; + position: relative; align-items: center; - padding: 0 4px; justify-content: center; - } + color: black; - .gm-resource-tools { - display: flex; - flex-direction: column; - justify-content: center; - padding: 0 5px 0 @fullPadding; + .dice { + height: 24px; + } - i { - margin: 0 @tinyMargin; + .encounter-control-fear { + position: absolute; font-size: 16px; + } - &.disabled { - opacity: 0.6; - } - - &:hover:not(.disabled) { - cursor: pointer; - filter: drop-shadow(0 0 3px @mainShadow); - } + .encounter-control-counter { + position: absolute; + right: -10px; + color: var(--color-text-secondary); } } - .gm-resource { - background: rgba(255, 255, 255, 0.1); - padding: @fullPadding; - border-radius: 8px; - border: @normalBorder solid black; - font-size: 20px; + .control-buttons { + width: min-content; } } - .token-action-tokens { - flex: 0 0 48px; - text-align: center; + .token-actions { + flex: 0 0 var(--input-height); + align-self: stretch; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; - .use-action-token { - &.disabled { - opacity: 0.6; + button { + font-size: 22px; + + &:hover { + background: inherit; } } } - - .icon-button { - &.spaced { - margin-left: @halfMargin; - } - - &.disabled { - opacity: 0.6; - } - - &:hover:not(.disabled) { - cursor: pointer; - filter: drop-shadow(0 0 3px @mainShadow); - } - } } diff --git a/templates/ui/combat/combatTracker.hbs b/templates/ui/combat/combatTracker.hbs new file mode 100644 index 00000000..3587ba65 --- /dev/null +++ b/templates/ui/combat/combatTracker.hbs @@ -0,0 +1,53 @@ +
    + {{#each turns}} +
  1. + {{!-- TODO: Targets --}} + + {{!-- Image --}} + {{ name }} + + {{!-- Name & Controls --}} +
    + {{ name }} +
    + {{#if @root.user.isGM}} + + + {{/if}} + {{#if canPing}} + + {{/if}} + {{#unless @root.user.isGM}} + + {{/unless}} + {{!-- TODO: Target Control --}} +
    + {{#each effects.icons}} + {{ name }} + {{/each}} +
    +
    +
    + + {{!-- Resource --}} + {{#if resource includeZero=true}} +
    + {{ resource }} +
    + {{/if}} + + {{#if ../combat.system.started}} +
    + +
    + {{/if}} +
  2. + {{/each}} +
diff --git a/templates/ui/combat/combatTrackerFooter.hbs b/templates/ui/combat/combatTrackerFooter.hbs new file mode 100644 index 00000000..72680508 --- /dev/null +++ b/templates/ui/combat/combatTrackerFooter.hbs @@ -0,0 +1,17 @@ + diff --git a/templates/ui/combat/combatTrackerHeader.hbs b/templates/ui/combat/combatTrackerHeader.hbs new file mode 100644 index 00000000..64d8c73d --- /dev/null +++ b/templates/ui/combat/combatTrackerHeader.hbs @@ -0,0 +1,86 @@ +
+ + {{!-- Encounter Controls --}} + {{#if user.isGM}} + + {{/if}} + +
+ {{#if hasCombat}} +
+ + +
4
+
+ {{/if}} + + {{!-- Combat Status --}} + + {{#if combats.length}} + {{#if combat.system.started}} + {{ localize "DAGGERHEART.Combat.combatStarted" }} + {{else}} + {{ localize "COMBAT.NotStarted" }} + {{/if}} + {{else}} + {{ localize "COMBAT.None" }} + {{/if}} + + + {{!-- Combat Controls --}} + {{#if hasCombat}} +
+
+ +
+ {{/if}} + +
+ +
diff --git a/templates/ui/combatTracker.hbs b/templates/ui/combatTrackerOld.hbs similarity index 100% rename from templates/ui/combatTracker.hbs rename to templates/ui/combatTrackerOld.hbs