diff --git a/daggerheart.mjs b/daggerheart.mjs index b4bd4455..13b81761 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'; @@ -74,11 +73,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 = { @@ -91,7 +90,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; @@ -297,6 +296,7 @@ const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/components/card-preview.hbs', 'systems/daggerheart/templates/views/levelup/parts/selectable-card-preview.hbs', 'systems/daggerheart/templates/views/levelup/parts/multiclass-preview-card.hbs', - 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' + 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs', + 'systems/daggerheart/templates/ui/combat/combatTrackerSection.hbs' ]); }; diff --git a/lang/en.json b/lang/en.json index e5d9f478..39d5225c 100755 --- a/lang/en.json +++ b/lang/en.json @@ -61,6 +61,13 @@ "outline": "Outline", "edge": "Edge" } + }, + "VariantRules": { + "title": "Variant Rules", + "label": "Variant Rules", + "hint": "Apply variant rules from the Daggerheart system", + "name": "Variant Rules", + "actionTokens": "Action Tokens" } }, "Automation": { @@ -101,6 +108,12 @@ "Hint": "Enable measuring of ranges with the ruler according to set distances." } }, + "VariantRules": { + "ActionTokens": { + "Name": "Action Tokens", + "Hint": "Give each player action tokens to use in combat" + } + }, "DualityRollColor": { "Name": "Duality Roll Colour Scheme", "Hint": "The display type for Duality Rolls", @@ -156,6 +169,14 @@ "Single": "Experience", "plural": "Experiences" }, + "Adversary": { + "Singular": "Adversary", + "Plural": "Adversaries" + }, + "Character": { + "Singular": "Character", + "Plural": "Characters" + }, "RefreshType": { "Session": "Session", "Shortrest": "Short Rest", @@ -335,6 +356,12 @@ "grimoire": "Grimoire" } }, + "Combat": { + "giveSpotlight": "Give The Spotlight", + "requestSpotlight": "Request The Spotlight", + "requestingSpotlight": "Requesting The Spotlight", + "combatStarted": "Active" + }, "LevelUp": { "Options": { "trait": "Gain a +1 bonus to two unmarked character traits and mark them.", diff --git a/module/applications/resources.mjs b/module/applications/resources.mjs index 6956573e..bbd47fc5 100644 --- a/module/applications/resources.mjs +++ b/module/applications/resources.mjs @@ -42,7 +42,6 @@ export default class Resources extends HandlebarsApplicationMixin(ApplicationV2) resources: { root: true, template: 'systems/daggerheart/templates/views/resources.hbs' - // template: "templates/ui/players.hbs" } }; diff --git a/module/applications/settings.mjs b/module/applications/settings.mjs index a1769c68..a7eed1b9 100644 --- a/module/applications/settings.mjs +++ b/module/applications/settings.mjs @@ -2,6 +2,8 @@ import { DualityRollColor } from '../config/settingsConfig.mjs'; import { defaultLevelTiers, DhLevelTiers } from '../data/levelTier.mjs'; 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 = {}) { @@ -184,6 +186,7 @@ export const registerDHSettings = () => { default: 0, onChange: () => { if (ui.resources) ui.resources.render({ force: true }); + ui.combat.render({ force: true }); } }); @@ -253,6 +256,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, @@ -310,4 +320,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 + }); }; diff --git a/module/applications/settings/variantRuleSettings.mjs b/module/applications/settings/variantRuleSettings.mjs new file mode 100644 index 00000000..101d3e42 --- /dev/null +++ b/module/applications/settings/variantRuleSettings.mjs @@ -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(); + } +} diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index a0702a80..3a33e61b 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -10,6 +10,10 @@ export const menu = { Range: { Name: 'GameSettingsRange', Icon: 'fa-solid fa-ruler' + }, + VariantRules: { + Name: 'GameSettingsVariantrules', + Icon: 'fa-solid fa-scale-balanced' } }; @@ -29,7 +33,8 @@ export const gameSettings = { }, DualityRollColor: 'DualityRollColor', LevelTiers: 'LevelTiers', - appearance: 'Appearance' + appearance: 'Appearance', + variantRules: 'VariantRules' }; export const DualityRollColor = { diff --git a/module/data/_module.mjs b/module/data/_module.mjs index 4822229f..dfd69ad2 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 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'; diff --git a/module/data/combat.mjs b/module/data/combat.mjs index 3ad52b8b..e0490286 100644 --- a/module/data/combat.mjs +++ b/module/data/combat.mjs @@ -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 {}; } } diff --git a/module/data/combatant.mjs b/module/data/combatant.mjs index 60c32db6..cae5d08f 100644 --- a/module/data/combatant.mjs +++ b/module/data/combatant.mjs @@ -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 }) }; } } diff --git a/module/data/settings/VariantRules.mjs b/module/data/settings/VariantRules.mjs new file mode 100644 index 00000000..2a1f948d --- /dev/null +++ b/module/data/settings/VariantRules.mjs @@ -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 = {}; +} diff --git a/module/documents/combat.mjs b/module/documents/combat.mjs index c7905605..3ad3189e 100644 --- a/module/documents/combat.mjs +++ b/module/documents/combat.mjs @@ -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 - } - }); - } - } } diff --git a/module/ui/combatTracker.mjs b/module/ui/combatTracker.mjs index 86002cb6..46f7f318 100644 --- a/module/ui/combatTracker.mjs +++ b/module/ui/combatTracker.mjs @@ -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: '', + 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 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(); } } diff --git a/module/ui/players.mjs b/module/ui/players.mjs deleted file mode 100644 index cbce702b..00000000 --- a/module/ui/players.mjs +++ /dev/null @@ -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); - } -} diff --git a/styles/daggerheart.css b/styles/daggerheart.css index 0f398e0d..fb1bbbd5 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -1293,60 +1293,92 @@ .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; - flex-direction: column; + position: relative; align-items: center; - padding: 0 4px; justify-content: center; + color: black; } -.combat-sidebar .encounter-gm-resources .gm-resource-tools { - display: flex; - flex-direction: column; - justify-content: center; - padding: 0 5px 0 4px; +.combat-sidebar .encounter-controls.combat .encounter-control-fear-container .dice { + height: 24px; } -.combat-sidebar .encounter-gm-resources .gm-resource-tools i { - margin: 0 2px; +.combat-sidebar .encounter-controls.combat .encounter-control-fear-container .encounter-control-fear { + position: absolute; font-size: 16px; } -.combat-sidebar .encounter-gm-resources .gm-resource-tools i.disabled { - opacity: 0.6; +.combat-sidebar .encounter-controls.combat .encounter-control-fear-container .encounter-control-counter { + position: absolute; + right: -10px; + color: var(--color-text-secondary); } -.combat-sidebar .encounter-gm-resources .gm-resource-tools i:hover:not(.disabled) { - cursor: pointer; - filter: drop-shadow(0 0 3px red); +.combat-sidebar .encounter-controls.combat .control-buttons { + width: min-content; } -.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 .combatant-controls { + flex: 0; } -.combat-sidebar .token-action-tokens { - flex: 0 0 48px; +.combat-sidebar .token-actions { + align-self: stretch; + display: flex; + align-items: top; + justify-content: center; + gap: 16px; +} +.combat-sidebar .token-actions .action-tokens { + display: flex; + gap: 4px; +} +.combat-sidebar .token-actions .action-tokens .action-token { + height: 22px; + width: 22px; + border: 1px solid; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + padding: 8px; + --button-size: 0; +} +.combat-sidebar .token-actions .action-tokens .action-token.used { + opacity: 0.5; + background: transparent; +} +.combat-sidebar .token-actions button { + font-size: 22px; + height: 24px; + width: 24px; +} +.combat-sidebar .token-actions button.main { + background: var(--button-hover-background-color); + color: var(--button-hover-text-color); + border-color: var(--button-hover-border-color); +} +.combat-sidebar .token-actions button.main:hover { + filter: drop-shadow(0 0 3px var(--button-hover-text-color)); +} +.combat-sidebar .spotlight-control { + font-size: 26px; +} +.combat-sidebar .spotlight-control:focus { + outline: none; + box-shadow: none; +} +.combat-sidebar .spotlight-control.discrete:hover { + background: inherit; +} +.combat-sidebar .spotlight-control.requesting { + filter: drop-shadow(0 0 3px gold); + color: var(--button-hover-text-color); +} +.combat-sidebar h4 { + margin: 0; 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); -} .chat-message.duality { border-color: black; padding: 8px 0 0 0; @@ -3406,6 +3438,19 @@ div.daggerheart.views.multiclass { .application.setting.dh-style footer button { flex: 1; } +.application.setting.dh-style .form-group { + display: flex; + justify-content: space-between; + align-items: center; +} +.application.setting.dh-style .form-group label { + font-size: 16px; +} +.application.setting.dh-style .form-group .form-fields { + display: flex; + gap: 4px; + align-items: center; +} .system-daggerheart .tagify { background: light-dark(transparent, transparent); border: 1px solid light-dark(#222, #efe6d8); diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index c4a15cbb..a9018996 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -212,6 +212,22 @@ flex: 1; } } + + .form-group { + display: flex; + justify-content: space-between; + align-items: center; + + label { + font-size: 16px; + } + + .form-fields { + display: flex; + gap: 4px; + align-items: center; + } + } } .system-daggerheart { diff --git a/styles/ui.less b/styles/ui.less index 7d1ff690..c54b0b3b 100644 --- a/styles/ui.less +++ b/styles/ui.less @@ -1,71 +1,106 @@ .combat-sidebar { - .encounter-gm-resources { - flex: 0; - display: flex; - justify-content: center; - padding: @largePadding 0; + .encounter-controls.combat { + justify-content: space-between; - .gm-resource-controls { + .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; - } + .encounter-control-counter { + position: absolute; + right: -10px; + color: var(--color-text-secondary); + } + } - &:hover:not(.disabled) { - cursor: pointer; - filter: drop-shadow(0 0 3px @mainShadow); + .control-buttons { + width: min-content; + } + } + + .combatant-controls { + flex: 0; + } + + .token-actions { + align-self: stretch; + display: flex; + align-items: top; + justify-content: center; + gap: 16px; + + .action-tokens { + display: flex; + gap: 4px; + + .action-token { + height: 22px; + width: 22px; + border: 1px solid; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + padding: 8px; + --button-size: 0; + + &.used { + opacity: 0.5; + background: transparent; } } } - .gm-resource { - background: rgba(255, 255, 255, 0.1); - padding: @fullPadding; - border-radius: 8px; - border: @normalBorder solid black; - font-size: 20px; + button { + font-size: 22px; + height: 24px; + width: 24px; + + &.main { + background: var(--button-hover-background-color); + color: var(--button-hover-text-color); + border-color: var(--button-hover-border-color); + + &:hover { + filter: drop-shadow(0 0 3px var(--button-hover-text-color)); + } + } } } - .token-action-tokens { - flex: 0 0 48px; + .spotlight-control { + font-size: 26px; + + &:focus { + outline: none; + box-shadow: none; + } + + &.discrete:hover { + background: inherit; + } + + &.requesting { + filter: drop-shadow(0 0 3px gold); + color: var(--button-hover-text-color); + } + } + + h4 { + margin: 0; 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); - } } } diff --git a/templates/settings/variant-rules.hbs b/templates/settings/variant-rules.hbs new file mode 100644 index 00000000..f39cb2a9 --- /dev/null +++ b/templates/settings/variant-rules.hbs @@ -0,0 +1,22 @@ +
+
+ + +
+ {{formInput settingFields.schema.fields.actionTokens.fields.enabled value=settingFields._source.actionTokens.enabled}} + {{formInput settingFields.schema.fields.actionTokens.fields.tokens value=settingFields._source.actionTokens.tokens disabled=(not settingFields._source.actionTokens.enabled)}} +
+
+ + +
+ \ No newline at end of file diff --git a/templates/ui/combat/combatTracker.hbs b/templates/ui/combat/combatTracker.hbs new file mode 100644 index 00000000..8de5640a --- /dev/null +++ b/templates/ui/combat/combatTracker.hbs @@ -0,0 +1,4 @@ +
+ {{> 'systems/daggerheart/templates/ui/combat/combatTrackerSection.hbs' this title=(localize "DAGGERHEART.General.Character.Plural") turns=this.characters}} + {{> 'systems/daggerheart/templates/ui/combat/combatTrackerSection.hbs' this title=(localize "DAGGERHEART.General.Adversary.Plural") turns=this.adversaries}} +
\ No newline at end of file diff --git a/templates/ui/combat/combatTrackerFooter.hbs b/templates/ui/combat/combatTrackerFooter.hbs new file mode 100644 index 00000000..4bb9bb10 --- /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..a2f5d557 --- /dev/null +++ b/templates/ui/combat/combatTrackerHeader.hbs @@ -0,0 +1,86 @@ +
+ + {{!-- Encounter Controls --}} + {{#if user.isGM}} + + {{/if}} + +
+ {{#if hasCombat}} +
+ + +
{{fear}}
+
+ {{/if}} + + {{!-- Combat Status --}} + + {{#if combats.length}} + {{#if combat.round}} + {{ localize "DAGGERHEART.Combat.combatStarted" }} + {{else}} + {{ localize "COMBAT.NotStarted" }} + {{/if}} + {{else}} + {{ localize "COMBAT.None" }} + {{/if}} + + + {{!-- Combat Controls --}} + {{#if hasCombat}} +
+
+ +
+ {{/if}} + +
+ +
diff --git a/templates/ui/combat/combatTrackerSection.hbs b/templates/ui/combat/combatTrackerSection.hbs new file mode 100644 index 00000000..688b4efc --- /dev/null +++ b/templates/ui/combat/combatTrackerSection.hbs @@ -0,0 +1,64 @@ +
+

{{title}}

+
    + {{#each turns}} +
  1. + {{!-- Image --}} + {{ name }} + + {{!-- Name & Controls --}} +
    + {{ name }} +
    +
    + {{#if @root.user.isGM}} + + + {{/if}} + {{#if canPing}} + + {{/if}} + {{#unless @root.user.isGM}} + + {{/unless}} +
    + + {{#if ../combat.round}} +
    + {{#if isOwner}} + {{#if (and (not isNPC) ../actionTokens.enabled)}} +
    + {{#times ../actionTokens.tokens}} + + {{/times}} +
    + {{/if}} + {{/if}} +
    + {{/if}} +
    +
    + + {{#if @root.user.isGM}} + + {{else}} + + {{/if}} +
  2. + {{/each}} +
+
\ No newline at end of file diff --git a/templates/ui/combatTracker.hbs b/templates/ui/combatTracker.hbs deleted file mode 100644 index 04d1a91b..00000000 --- a/templates/ui/combatTracker.hbs +++ /dev/null @@ -1,160 +0,0 @@ -
-
- {{#if user.isGM}} - - {{/if}} - -
- {{#if user.isGM}} - - - - - - - {{/if}} - - {{#if combatCount}} - {{#if combat.round}} -

{{localize 'COMBAT.Round'}} {{combat.round}}

- {{else}} -

{{localize 'COMBAT.NotStarted'}}

- {{/if}} - {{else}} -

{{localize "COMBAT.None"}}

- {{/if}} - - {{#if user.isGM}} - - - - - - - {{/if}} - - - -
-
- -
- {{#if combat.system}} -
- - -
-
- - {{combat.system.actions}} -
-
- - -
-
- - {{fear}} -
-
- - -
- {{/if}} -
- -
    - {{#each turns}} -
  1. - {{this.name}} -
    -

    {{this.name}}

    -
    - {{#if ../user.isGM}} - - - - - - - {{/if}} - {{#if this.canPing}} - - - - {{/if}} - {{#unless ../user.isGM}} - - - - {{/unless}} -
    - {{#each this.effects}} - - {{/each}} -
    -
    -
    - - {{#if this.hasResource}} -
    - {{this.resource}} -
    - {{/if}} - -
    - {{#if this.playerCharacter}} - - {{else if (and (not this.ownedByPlayer) ../user.isGM)}} - - {{/if}} - {{!-- {{#if this.hasRolled}} - {{this.initiative}} - {{else if this.owner}} - - {{/if}} --}} -
    -
  2. - {{/each}} -
- - -
diff --git a/templates/ui/players.hbs b/templates/ui/players.hbs deleted file mode 100644 index 3912e955..00000000 --- a/templates/ui/players.hbs +++ /dev/null @@ -1,35 +0,0 @@ -