diff --git a/lang/en.json b/lang/en.json index 4a4d073d..b0881c28 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1058,6 +1058,10 @@ "fear": "Fear", "spotlight": "Spotlight" }, + "DaggerheartDiceAnimationEvents": { + "critical": { "name": "Critical" }, + "higher": { "name": "Highest Roll" } + }, "DamageType": { "physical": { "name": "Physical", diff --git a/module/applications/settings/appearanceSettings.mjs b/module/applications/settings/appearanceSettings.mjs index 9eb0cfbf..6266cf39 100644 --- a/module/applications/settings/appearanceSettings.mjs +++ b/module/applications/settings/appearanceSettings.mjs @@ -12,7 +12,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App static DEFAULT_OPTIONS = { tag: 'form', id: 'daggerheart-appearance-settings', - classes: ['daggerheart', 'dialog', 'dh-style', 'setting'], + classes: ['daggerheart', 'dialog', 'dh-style', 'setting', 'appearance-settings'], position: { width: '600', height: 'auto' }, window: { title: 'DAGGERHEART.SETTINGS.Menu.title', @@ -120,6 +120,9 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App * @protected */ async prepareDiceSoNiceContext(context) { + context.animationEvents = CONFIG.DH.GENERAL.daggerheartDiceAnimationEvents; + context.previewAnimation = this.previewAnimation; + context.diceSoNiceTextures = Object.entries(game.dice3d.exports.TEXTURELIST).reduce( (acc, [k, v]) => ({ ...acc, @@ -146,6 +149,13 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App ); context.diceSoNiceFonts = game.dice3d.exports.Utils.prepareFontList(); + const getAnimationsOptions = key => { + const fields = context.fields.diceSoNice.fields[key].fields.sfx.fields; + return { + higher: fields.higher.fields.class.choices + }; + }; + foundry.utils.mergeObject( context.dsnTabs, ['hope', 'fear', 'advantage', 'disadvantage'].reduce( @@ -153,7 +163,8 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App ...acc, [key]: { values: this.setting.diceSoNice[key], - fields: this.setting.schema.getField(`diceSoNice.${key}`).fields + fields: this.setting.schema.getField(`diceSoNice.${key}`).fields, + animations: ['hope', 'fear'].includes(key) ? getAnimationsOptions(key) : {} } }), {} @@ -169,8 +180,9 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App * @param {foundry.applications.ux.FormDataExtended} formData * @returns {Promise} */ - static async #onSubmit(event, form, formData) { + static async #onSubmit(_event, _form, formData) { const data = this.setting.schema.clean(foundry.utils.expandObject(formData.object)); + await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, data); } @@ -183,13 +195,25 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App */ static async #onPreview(_, target) { const formData = new foundry.applications.ux.FormDataExtended(target.closest('form')); - const { diceSoNice } = foundry.utils.expandObject(formData.object); + const { diceSoNice, ...rest } = foundry.utils.expandObject(formData.object); const { key } = target.dataset; const faces = ['advantage', 'disadvantage'].includes(key) ? 'd6' : 'd12'; const preset = await getDiceSoNicePreset(diceSoNice[key], faces); const diceSoNiceRoll = await new foundry.dice.Roll(`1${faces}`).evaluate(); diceSoNiceRoll.dice[0].options.appearance = preset.appearance; diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile; + + const previewAnimation = rest[`${key}PreviewAnimation`]; + const events = CONFIG.DH.GENERAL.daggerheartDiceAnimationEvents; + if (previewAnimation) { + if (previewAnimation === events.critical.id && diceSoNice.sfx.critical.class) { + diceSoNiceRoll.dice[0].options.sfx = { specialEffect: diceSoNice.sfx.critical.class }; + } + if (previewAnimation === events.higher.id && diceSoNice[key].sfx.higher) { + diceSoNiceRoll.dice[0].options.sfx = { specialEffect: diceSoNice[key].sfx.higher.class }; + } + } + await game.dice3d.showForRoll(diceSoNiceRoll, game.user, false); } diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index d46db23a..804defcb 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -630,7 +630,95 @@ export const diceSetNumbers = { flat: 'Flat' }; -export const getDiceSoNicePreset = async (type, faces) => { +export const diceSoNiceSFXClasses = { + PlayAnimationBright: { + id: 'PlayAnimationBright', + label: 'DICESONICE.PlayAnimationBright' + }, + PlayAnimationDark: { + id: 'PlayAnimationDark', + label: 'DICESONICE.PlayAnimationDark' + }, + PlayAnimationOutline: { + id: 'PlayAnimationOutline', + label: 'DICESONICE.PlayAnimationOutline' + }, + PlayAnimationImpact: { + id: 'PlayAnimationImpact', + label: 'DICESONICE.PlayAnimationImpact' + }, + // PlayConfettiStrength1: { + // id: 'PlayConfettiStrength1', + // label: 'DICESONICE.PlayConfettiStrength1' + // }, + // PlayConfettiStrength2: { + // id: 'PlayConfettiStrength2', + // label: 'DICESONICE.PlayConfettiStrength2' + // }, + // PlayConfettiStrength3: { + // id: 'PlayConfettiStrength3', + // label: 'DICESONICE.PlayConfettiStrength3' + // }, + PlayAnimationThormund: { + id: 'PlayAnimationThormund', + label: 'DICESONICE.PlayAnimationThormund' + }, + PlayAnimationParticleSpiral: { + id: 'PlayAnimationParticleSpiral', + label: 'DICESONICE.PlayAnimationParticleSpiral' + }, + PlayAnimationParticleSparkles: { + id: 'PlayAnimationParticleSparkles', + label: 'DICESONICE.PlayAnimationParticleSparkles' + }, + PlayAnimationParticleVortex: { + id: 'PlayAnimationParticleVortex', + label: 'DICESONICE.PlayAnimationParticleVortex' + }, + PlaySoundEpicWin: { + id: 'PlaySoundEpicWin', + label: 'DICESONICE.PlaySoundEpicWin' + }, + PlaySoundEpicFail: { + id: 'PlaySoundEpicFail', + label: 'DICESONICE.PlaySoundEpicFail' + } + // "PlaySoundCustom", + // "PlayMacro" +}; + +export const daggerheartDiceAnimationEvents = { + critical: { + id: 'critical', + label: 'DAGGERHEART.CONFIG.DaggerheartDiceAnimationEvents.critical.name' + }, + higher: { + id: 'higher', + label: 'DAGGERHEART.CONFIG.DaggerheartDiceAnimationEvents.higher.name' + } +}; + +const getDiceSoNiceSFX = sfxOptions => { + const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance); + const criticalAnimationData = diceSoNice.sfx.critical; + if (sfxOptions.critical && criticalAnimationData.class) { + return { + specialEffect: criticalAnimationData.class, + options: {} + }; + } + + if (sfxOptions.higher && sfxOptions.data.higher) { + return { + specialEffect: sfxOptions.data.higher.class, + options: {} + }; + } + + return {}; +}; + +export const getDiceSoNicePreset = async (type, faces, sfxOptions = {}) => { const system = game.dice3d.DiceFactory.systems.get(type.system).dice.get(faces); if (!system) { ui.notifications.error( @@ -653,16 +741,33 @@ export const getDiceSoNicePreset = async (type, faces) => { appearance: { ...system.appearance, ...type - } + }, + sfx: getDiceSoNiceSFX(sfxOptions) }; }; -export const getDiceSoNicePresets = async (hopeFaces, fearFaces, advantageFaces = 'd6', disadvantageFaces = 'd6') => { +export const getDiceSoNicePresets = async ( + result, + hopeFaces, + fearFaces, + advantageFaces = 'd6', + disadvantageFaces = 'd6' +) => { const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance); + const { isCritical, withHope, withFear } = result; + return { - hope: await getDiceSoNicePreset(diceSoNice.hope, hopeFaces), - fear: await getDiceSoNicePreset(diceSoNice.fear, fearFaces), + hope: await getDiceSoNicePreset(diceSoNice.hope, hopeFaces, { + critical: isCritical, + higher: withHope, + data: diceSoNice.hope.sfx + }), + fear: await getDiceSoNicePreset(diceSoNice.fear, fearFaces, { + critical: isCritical, + higher: withFear, + data: diceSoNice.fear.sfx + }), advantage: await getDiceSoNicePreset(diceSoNice.advantage, advantageFaces), disadvantage: await getDiceSoNicePreset(diceSoNice.disadvantage, disadvantageFaces) }; diff --git a/module/data/settings/Appearance.mjs b/module/data/settings/Appearance.mjs index cd98d6f9..30c640b6 100644 --- a/module/data/settings/Appearance.mjs +++ b/module/data/settings/Appearance.mjs @@ -4,6 +4,16 @@ export default class DhAppearance extends foundry.abstract.DataModel { static defineSchema() { const { StringField, ColorField, BooleanField, SchemaField } = foundry.data.fields; + const sfxSchema = () => + new SchemaField({ + class: new StringField({ + nullable: true, + initial: null, + blank: true, + choices: CONFIG.DH.GENERAL.diceSoNiceSFXClasses + }) + }); + // helper to create dice style schema const diceStyle = ({ fg, bg, outline, edge }) => new SchemaField({ @@ -15,7 +25,10 @@ export default class DhAppearance extends foundry.abstract.DataModel { colorset: new StringField({ initial: 'inspired', required: true, blank: false }), material: new StringField({ initial: 'metal', required: true, blank: false }), system: new StringField({ initial: 'standard', required: true, blank: false }), - font: new StringField({ initial: 'auto', required: true, blank: false }) + font: new StringField({ initial: 'auto', required: true, blank: false }), + sfx: new SchemaField({ + higher: sfxSchema() + }) }); return { @@ -30,7 +43,10 @@ export default class DhAppearance extends foundry.abstract.DataModel { hope: diceStyle({ fg: '#ffffff', bg: '#ffe760', outline: '#000000', edge: '#ffffff' }), fear: diceStyle({ fg: '#000000', bg: '#0032b1', outline: '#ffffff', edge: '#000000' }), advantage: diceStyle({ fg: '#ffffff', bg: '#008000', outline: '#000000', edge: '#ffffff' }), - disadvantage: diceStyle({ fg: '#000000', bg: '#b30000', outline: '#ffffff', edge: '#000000' }) + disadvantage: diceStyle({ fg: '#000000', bg: '#b30000', outline: '#ffffff', edge: '#000000' }), + sfx: new SchemaField({ + critical: sfxSchema() + }) }), extendCharacterDescriptions: new BooleanField(), extendAdversaryDescriptions: new BooleanField(), diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 9037250a..75fbdf55 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -378,6 +378,8 @@ export default class DualityRoll extends D20Roll { let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false }); const term = parsedRoll.terms[target.dataset.dieIndex]; await term.reroll(`/r1=${term.total}`); + const result = await parsedRoll.evaluate(); + if (game.modules.get('dice-so-nice')?.active) { const diceSoNiceRoll = { _evaluated: true, @@ -391,7 +393,7 @@ export default class DualityRoll extends D20Roll { options: { appearance: {} } }; - const diceSoNicePresets = await getDiceSoNicePresets(`d${term._faces}`, `d${term._faces}`); + const diceSoNicePresets = await getDiceSoNicePresets(result, `d${term._faces}`, `d${term._faces}`); const type = target.dataset.type; if (diceSoNicePresets[type]) { diceSoNiceRoll.dice[0].options = diceSoNicePresets[type]; @@ -400,8 +402,6 @@ export default class DualityRoll extends D20Roll { await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true); } - await parsedRoll.evaluate(); - const newRoll = game.system.api.dice.DualityRoll.postEvaluate(parsedRoll, { targets: message.system.targets, roll: { diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index c8b62ff6..86514fd1 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -60,7 +60,13 @@ export const getCommandTarget = (options = {}) => { export const setDiceSoNiceForDualityRoll = async (rollResult, advantageState, hopeFaces, fearFaces, advantageFaces) => { if (!game.modules.get('dice-so-nice')?.active) return; - const diceSoNicePresets = await getDiceSoNicePresets(hopeFaces, fearFaces, advantageFaces, advantageFaces); + const diceSoNicePresets = await getDiceSoNicePresets( + rollResult, + hopeFaces, + fearFaces, + advantageFaces, + advantageFaces + ); rollResult.dice[0].options = diceSoNicePresets.hope; rollResult.dice[1].options = diceSoNicePresets.fear; if (rollResult.dice[2] && advantageState) { diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index f5e92e2c..380700fe 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -587,6 +587,13 @@ display: grid; grid-template-columns: 1fr 1fr; gap: 10px; + + > label { + grid-column: span 2; + text-align: center; + font-size: var(--font-size-18); + font-weight: bold; + } } } @@ -612,11 +619,8 @@ .button-container { display: grid; - grid-template-columns: 1fr; - gap: 10px; - text-align: center; - display: flex; - justify-content: center; + align-items: center; + justify-content: space-between; width: 100%; } } diff --git a/styles/less/ui/index.less b/styles/less/ui/index.less index 065e43c5..567f94db 100644 --- a/styles/less/ui/index.less +++ b/styles/less/ui/index.less @@ -27,6 +27,7 @@ @import './settings/settings.less'; @import './settings/homebrew-settings/domains.less'; @import './settings/homebrew-settings/types.less'; +@import './settings/appearance-settings/diceSoNice.less'; @import './sidebar/tabs.less'; @import './sidebar/daggerheartMenu.less'; diff --git a/styles/less/ui/settings/appearance-settings/diceSoNice.less b/styles/less/ui/settings/appearance-settings/diceSoNice.less new file mode 100644 index 00000000..39c8c851 --- /dev/null +++ b/styles/less/ui/settings/appearance-settings/diceSoNice.less @@ -0,0 +1,16 @@ +.daggerheart.dh-style.setting.appearance-settings { + .diceSoNice-footer { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: 8px; + margin-top: 32px; + + label { + text-align: center; + } + + select { + grid-column: span 2; + } + } +} diff --git a/templates/settings/appearance-settings/diceSoNice.hbs b/templates/settings/appearance-settings/diceSoNice.hbs index 6c89fd2f..acb5c0ba 100644 --- a/templates/settings/appearance-settings/diceSoNice.hbs +++ b/templates/settings/appearance-settings/diceSoNice.hbs @@ -2,6 +2,14 @@
{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.hint"}}
+

{{localize "Global Animations"}}

+
+
+ + {{formInput fields.diceSoNice.fields.sfx.fields.critical.fields.class value=setting.diceSoNice.sfx.critical.class blank="" localize=true}} +
+
+
{{#each dsnTabs as |dsnTab|}} -
-
-
- - {{formInput fields.system value=values.system localize=true choices=@root.diceSoNiceSystems}} -
-
-
- - {{formInput fields.foreground value=values.foreground localize=true}} +
+
+

{{localize "Dice Appearance"}}

+ +
+ + {{formInput fields.system value=values.system localize=true choices=@root.diceSoNiceSystems}}
-
- - {{formInput fields.background value=values.background localize=true}} +
+
+ + {{formInput fields.foreground value=values.foreground localize=true}} +
+
+ + {{formInput fields.background value=values.background localize=true}} +
+
+ + {{formInput fields.outline value=values.outline localize=true}} +
+
+ + {{formInput fields.edge value=values.edge localize=true}} +
+
+ + {{formInput fields.colorset value=values.colorset choices=@root.diceSoNiceColorsets localize=true}} +
+
+ + {{formInput fields.texture value=values.texture choices=@root.diceSoNiceTextures localize=true}} +
+
+ + {{formInput fields.material value=values.material choices=@root.diceSoNiceMaterials localize=true}} +
+
+ + {{formInput fields.font value=values.font choices=@root.diceSoNiceFonts localize=true}} +
-
- - {{formInput fields.outline value=values.outline localize=true}} -
-
- - {{formInput fields.edge value=values.edge localize=true}} -
-
- - {{formInput fields.colorset value=values.colorset choices=@root.diceSoNiceColorsets localize=true}} -
-
- - {{formInput fields.texture value=values.texture choices=@root.diceSoNiceTextures localize=true}} -
-
- - {{formInput fields.material value=values.material choices=@root.diceSoNiceMaterials localize=true}} -
-
- - {{formInput fields.font value=values.font choices=@root.diceSoNiceFonts localize=true}} + + {{#if dsnTab.animations}} +

{{localize "Animations"}}

+
+ + {{formInput fields.sfx.fields.higher.fields.class value=values.sfx.higher.class blank="" localize=true}} +
+ {{/if}} + +
-
- -
-
-
+
{{/each}}
\ No newline at end of file