From d5f7e17339dd0b24490be12064fe06f4fbebdf1c Mon Sep 17 00:00:00 2001 From: WBHarry <89362246+WBHarry@users.noreply.github.com> Date: Sat, 23 Aug 2025 02:18:25 +0200 Subject: [PATCH] Added Custom Adversary Types (#1048) --- lang/en.json | 7 ++- .../settings/homebrewSettings.mjs | 54 +++++++++++++++++-- .../applications/sheets/actors/adversary.mjs | 4 ++ module/config/actorConfig.mjs | 5 ++ module/data/actor/adversary.mjs | 2 +- module/data/settings/Homebrew.mjs | 7 +++ styles/less/ui/index.less | 1 + .../ui/settings/homebrew-settings/types.less | 52 ++++++++++++++++++ .../settings/homebrew-settings/types.hbs | 28 ++++++++++ templates/sheets/actors/adversary/header.hbs | 4 +- templates/ui/tooltip/adversary.hbs | 2 +- 11 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 styles/less/ui/settings/homebrew-settings/types.less create mode 100644 templates/settings/homebrew-settings/types.hbs diff --git a/lang/en.json b/lang/en.json index 1bddbad3..1c614fc2 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1900,7 +1900,8 @@ "tier4": "tier 4", "domains": "Domains", "downtime": "Downtime", - "rules": "Rules" + "rules": "Rules", + "types": "Types" }, "Tiers": { "singular": "Tier", @@ -2230,6 +2231,10 @@ "deleteDomain": "Delete Domain", "deleteDomainText": "Are you sure you want to delete the {name} domain? It will be immediately removed from all Actors in this world where it's currently used. Compendiums are not cleared.", "duplicateDomain": "There is already a domain with this identification." + }, + "adversaryType": { + "title": "Custom Adversary Types", + "newType": "Adversary Type" } }, "Menu": { diff --git a/module/applications/settings/homebrewSettings.mjs b/module/applications/settings/homebrewSettings.mjs index 3fa42afd..c2ac4a89 100644 --- a/module/applications/settings/homebrewSettings.mjs +++ b/module/applications/settings/homebrewSettings.mjs @@ -1,5 +1,6 @@ import { DhHomebrew } from '../../data/settings/_module.mjs'; import { slugify } from '../../helpers/utils.mjs'; + const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; export default class DhHomebrewSettings extends HandlebarsApplicationMixin(ApplicationV2) { @@ -10,11 +11,14 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).toObject() ); - this.selected = { - domain: null - }; + this.selected = this.#getDefaultAdversaryType(); } + #getDefaultAdversaryType = () => ({ + domain: null, + adversaryType: null + }); + get title() { return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title'); } @@ -35,6 +39,9 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli addDomain: this.addDomain, toggleSelectedDomain: this.toggleSelectedDomain, deleteDomain: this.deleteDomain, + addAdversaryType: this.addAdversaryType, + deleteAdversaryType: this.deleteAdversaryType, + selectAdversaryType: this.selectAdversaryType, save: this.save, reset: this.reset }, @@ -45,6 +52,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, settings: { template: 'systems/daggerheart/templates/settings/homebrew-settings/settings.hbs' }, domains: { template: 'systems/daggerheart/templates/settings/homebrew-settings/domains.hbs' }, + types: { template: 'systems/daggerheart/templates/settings/homebrew-settings/types.hbs' }, downtime: { template: 'systems/daggerheart/templates/settings/homebrew-settings/downtime.hbs' }, footer: { template: 'systems/daggerheart/templates/settings/homebrew-settings/footer.hbs' } }; @@ -52,12 +60,19 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli /** @inheritdoc */ static TABS = { main: { - tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'downtime' }], + tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'types' }, { id: 'downtime' }], initial: 'settings', labelPrefix: 'DAGGERHEART.GENERAL.Tabs' } }; + changeTab(tab, group, options) { + super.changeTab(tab, group, options); + this.selected = this.#getDefaultAdversaryType(); + + this.render(); + } + async _prepareContext(_options) { const context = await super._prepareContext(_options); context.settingFields = this.settings; @@ -79,6 +94,11 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli context.configDomains = CONFIG.DH.DOMAIN.domains; context.homebrewDomains = this.settings.domains; break; + case 'types': + context.selectedAdversaryType = this.selected.adversaryType + ? { id: this.selected.adversaryType, ...this.settings.adversaryTypes[this.selected.adversaryType] } + : null; + break; } return context; @@ -301,6 +321,32 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli this.render(); } + static async addAdversaryType(_, target) { + const newId = foundry.utils.randomID(); + await this.settings.updateSource({ + [`adversaryTypes.${newId}`]: { + id: newId, + label: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.adversaryType.newType') + } + }); + + this.selected.adversaryType = newId; + this.render(); + } + + static async deleteAdversaryType(_, target) { + const { key } = target.dataset; + await this.settings.updateSource({ [`adversaryTypes.-=${key}`]: null }); + + this.selected.adversaryType = this.selected.adversaryType === key ? null : this.selected.adversaryType; + this.render(); + } + + static async selectAdversaryType(_, target) { + this.selected.adversaryType = this.selected.adversaryType === target.dataset.type ? null : target.dataset.type; + this.render(); + } + static async save() { await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject()); this.close(); diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index b47cb85a..f575a2f2 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -56,6 +56,7 @@ export default class AdversarySheet extends DHBaseActorSheet { async _prepareContext(options) { const context = await super._prepareContext(options); context.systemFields.attack.fields = this.document.system.attack.schema.fields; + return context; } @@ -65,6 +66,9 @@ export default class AdversarySheet extends DHBaseActorSheet { switch (partId) { case 'header': await this._prepareHeaderContext(context, options); + + const adversaryTypes = CONFIG.DH.ACTOR.allAdversaryTypes(); + context.adversaryType = game.i18n.localize(adversaryTypes[this.document.system.type].label); break; case 'notes': await this._prepareNotesContext(context, options); diff --git a/module/config/actorConfig.mjs b/module/config/actorConfig.mjs index 6453cd78..55f03789 100644 --- a/module/config/actorConfig.mjs +++ b/module/config/actorConfig.mjs @@ -157,6 +157,11 @@ export const adversaryTypes = { } }; +export const allAdversaryTypes = () => ({ + ...adversaryTypes, + ...game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).adversaryTypes +}); + export const environmentTypes = { exploration: { label: 'DAGGERHEART.CONFIG.EnvironmentType.exploration.label', diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index 80bcb43e..ba0693f7 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -27,7 +27,7 @@ export default class DhpAdversary extends BaseDataActor { }), type: new fields.StringField({ required: true, - choices: CONFIG.DH.ACTOR.adversaryTypes, + choices: CONFIG.DH.ACTOR.allAdversaryTypes, initial: CONFIG.DH.ACTOR.adversaryTypes.standard.id }), motivesAndTactics: new fields.StringField(), diff --git a/module/data/settings/Homebrew.mjs b/module/data/settings/Homebrew.mjs index e18fee39..0719b085 100644 --- a/module/data/settings/Homebrew.mjs +++ b/module/data/settings/Homebrew.mjs @@ -108,6 +108,13 @@ export default class DhHomebrew extends foundry.abstract.DataModel { }), description: new fields.HTMLField() }) + ), + adversaryTypes: new fields.TypedObjectField( + new fields.SchemaField({ + id: new fields.StringField({ required: true }), + label: new fields.StringField({ required: true, label: 'DAGGERHEART.GENERAL.label' }), + description: new fields.StringField() + }) ) }; } diff --git a/styles/less/ui/index.less b/styles/less/ui/index.less index 4a93feb6..49d1e009 100644 --- a/styles/less/ui/index.less +++ b/styles/less/ui/index.less @@ -21,3 +21,4 @@ @import './settings/settings.less'; @import './settings/homebrew-settings/domains.less'; +@import './settings/homebrew-settings/types.less'; diff --git a/styles/less/ui/settings/homebrew-settings/types.less b/styles/less/ui/settings/homebrew-settings/types.less new file mode 100644 index 00000000..d09431f7 --- /dev/null +++ b/styles/less/ui/settings/homebrew-settings/types.less @@ -0,0 +1,52 @@ +.theme-light .daggerheart.dh-style.setting.homebrew-settings .types.tab { + .adversary-types-container .adversary-type-container { + background-image: url('../assets/parchments/dh-parchment-light.png'); + } +} + +.daggerheart.dh-style.setting.homebrew-settings { + .types.tab { + .adversary-types-container { + width: 100%; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 4px; + + .adversary-type-container { + height: 2em; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + border: 1px solid; + border-radius: 6px; + padding: 0 8px; + border: 1px solid light-dark(@dark-blue, @golden); + color: light-dark(@dark, @beige); + background-image: url('../assets/parchments/dh-parchment-dark.png'); + cursor: pointer; + opacity: 0.6; + + &:hover { + opacity: 1; + } + + &.active { + opacity: 1; + background: var(--color-warm-2); + } + } + } + + .type-edit-container { + width: 100%; + display: flex; + flex-direction: column; + gap: 8px; + + textarea { + width: 100%; + } + } + } +} diff --git a/templates/settings/homebrew-settings/types.hbs b/templates/settings/homebrew-settings/types.hbs new file mode 100644 index 00000000..f9d3bba3 --- /dev/null +++ b/templates/settings/homebrew-settings/types.hbs @@ -0,0 +1,28 @@ +
+
+ + {{localize "DAGGERHEART.SETTINGS.Homebrew.adversaryType.title"}} + + + +
+ {{#each settingFields.adversaryTypes as |type key|}} +
+ {{type.label}} +
+
+ {{/each}} +
+ + {{#if selectedAdversaryType}} +
+ {{formGroup settingFields.schema.fields.adversaryTypes.element.fields.label name=(concat "adversaryTypes." selectedAdversaryType.id ".label") value=selectedAdversaryType.label localize=true }} + +
+ {{/if}} +
+
\ No newline at end of file diff --git a/templates/sheets/actors/adversary/header.hbs b/templates/sheets/actors/adversary/header.hbs index 8411dd93..e6f829b8 100644 --- a/templates/sheets/actors/adversary/header.hbs +++ b/templates/sheets/actors/adversary/header.hbs @@ -13,9 +13,7 @@
- - {{localize (concat 'DAGGERHEART.CONFIG.AdversaryType.' source.system.type '.label')}} - + {{adversaryType}}
{{#if (eq source.system.type 'horde')}}
diff --git a/templates/ui/tooltip/adversary.hbs b/templates/ui/tooltip/adversary.hbs index b400bd29..86c399c5 100644 --- a/templates/ui/tooltip/adversary.hbs +++ b/templates/ui/tooltip/adversary.hbs @@ -12,7 +12,7 @@
- {{#with (lookup config.ACTOR.adversaryTypes item.system.type) as | type |}} + {{#with (lookup adversaryTypes item.system.type) as | type |}}
{{localize type.label}}
{{/with}}