diff --git a/lang/en.json b/lang/en.json index 26b12c7c..96185acb 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", @@ -330,7 +343,9 @@ } }, "Combat": { - "takeSpotlight": "Take The Spotlight", + "giveSpotlight": "Give The Spotlight", + "requestSpotlight": "Request The Spotlight", + "requestingSpotlight": "Requesting The Spotlight", "combatStarted": "Active" }, "LevelUp": { diff --git a/module/applications/settings.mjs b/module/applications/settings.mjs index 4a885d17..291b0882 100644 --- a/module/applications/settings.mjs +++ b/module/applications/settings.mjs @@ -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 + }); }; 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 26de2a48..b2e17549 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' } }; @@ -27,5 +31,6 @@ export const gameSettings = { AbilityArray: 'AbilityArray', RangeMeasurement: 'RangeMeasurement' }, - appearance: 'Appearance' + appearance: 'Appearance', + variantRules: 'VariantRules' }; diff --git a/module/data/combatant.mjs b/module/data/combatant.mjs index 05046b5a..955087de 100644 --- a/module/data/combatant.mjs +++ b/module/data/combatant.mjs @@ -2,7 +2,10 @@ 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 }), + active: 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 d72a5a23..25b9ed3c 100644 --- a/module/documents/combat.mjs +++ b/module/documents/combat.mjs @@ -1,6 +1,6 @@ export default class DhpCombat extends Combat { get combatant() { - return this.combatants.contents.find(x => x.system.active) ?? null; + return this.combatants.contents.find(x => x.system.spotlight.active) ?? null; } async startCombat() { diff --git a/module/ui/combatTracker.mjs b/module/ui/combatTracker.mjs index fe249d0e..a5136240 100644 --- a/module/ui/combatTracker.mjs +++ b/module/ui/combatTracker.mjs @@ -1,7 +1,9 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker { static DEFAULT_OPTIONS = { actions: { - takeSpotlight: this.takeSpotlight + requestSpotlight: this.requestSpotlight, + toggleSpotlight: this.toggleSpotlight, + setActionTokens: this.setActionTokens } }; @@ -17,6 +19,27 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C } }; + 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); + + Object.assign(context, { + actionTokens: game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.variantRules).actionTokens + }); + } + + async _prepareTurnContext(combat, combatant, index) { + const turn = await super._prepareTurnContext(combat, combatant, index); + return { ...turn, system: combatant.system.toObject() }; + } + _getCombatContextOptions() { return [ { @@ -34,12 +57,40 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C ]; } - static async takeSpotlight(_, target) { + static async requestSpotlight(_, 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 }); - } + 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 ?? {}; + for (var combatant of this.viewed.combatants) { + const giveSpotlight = combatant.id === combatantId; + + await combatant.update({ + 'system.spotlight': { + requesting: giveSpotlight ? false : combatant.system.spotlight.requesting, + active: giveSpotlight ? !combatant.system.spotlight.active : 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/styles/daggerheart.css b/styles/daggerheart.css index cf6877f7..b22f2a37 100755 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -1322,16 +1322,50 @@ flex: 0 0 var(--input-height); align-self: stretch; display: flex; - flex-direction: column; align-items: center; justify-content: center; + gap: 16px; +} +.combat-sidebar .token-actions .action-tokens { + display: flex; + gap: 4px; +} +.combat-sidebar .token-actions .action-tokens .action-token { + height: 24px; + border: 1px solid; + border-radius: 8px; + 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; } .combat-sidebar .token-actions button { font-size: 22px; } -.combat-sidebar .token-actions button:hover { +.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 .token-actions button.discrete:hover { background: inherit; } +.combat-sidebar .token-actions .combatant-control:focus { + outline: none; + box-shadow: none; +} +.combat-sidebar .token-actions .combatant-control.requesting { + filter: drop-shadow(0 0 3px gold); + color: var(--button-hover-text-color); +} .chat-message.duality { border-color: black; padding: 8px 0 0 0; @@ -3171,6 +3205,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 8ad2c97f..a4bb7c99 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 801b9eaa..da1693a0 100644 --- a/styles/ui.less +++ b/styles/ui.less @@ -103,16 +103,59 @@ flex: 0 0 var(--input-height); align-self: stretch; display: flex; - flex-direction: column; align-items: center; justify-content: center; + gap: 16px; + + .action-tokens { + display: flex; + gap: 4px; + + .action-token { + height: 24px; + border: 1px solid; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + padding: 8px; + --button-size: 0; + + &.used { + opacity: 0.5; + } + } + } button { font-size: 22px; - &:hover { + &.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)); + } + } + + &.discrete:hover { background: inherit; } } + + .combatant-control { + &:focus { + outline: none; + box-shadow: none; + } + + &.requesting { + filter: drop-shadow(0 0 3px gold); + color: var(--button-hover-text-color); + } + } } } 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 index 3587ba65..1d8707b7 100644 --- a/templates/ui/combat/combatTracker.hbs +++ b/templates/ui/combat/combatTracker.hbs @@ -1,8 +1,6 @@
    {{#each turns}}
  1. - {{!-- TODO: Targets --}} - {{!-- Image --}} {{ name }} @@ -38,14 +36,35 @@ {{!-- Resource --}} {{#if resource includeZero=true}} -
    - {{ resource }} -
    +
    + {{ resource }} +
    {{/if}} {{#if ../combat.system.started}}
    - + {{#if isOwner}} + {{#if ../actionTokens.enabled}} +
    + {{#times ../actionTokens.tokens}} + + {{/times}} +
    + {{/if}} + + {{#if @root.user.isGM}} + + {{else}} + + {{/if}} + {{/if}}
    {{/if}}
  2. diff --git a/templates/ui/combat/combatTrackerHeader.hbs b/templates/ui/combat/combatTrackerHeader.hbs index 64d8c73d..9d54058c 100644 --- a/templates/ui/combat/combatTrackerHeader.hbs +++ b/templates/ui/combat/combatTrackerHeader.hbs @@ -55,7 +55,7 @@
    -
    4
    +
    {{fear}}
    {{/if}}