[Feature] DualityDice DiceSoNice Animations (#1722)

* .

* Fixed translation

* Remove fieldset

* DiceSoNice Animation Overrides (#1726)

* .

* .

* Fixed hope/fear animation input

* Visual tweaks to general tab

* Move general tab to top

* Fixed localizaiton

---------

Co-authored-by: Carlos Fernandez <cfern1990@gmail.com>
This commit is contained in:
WBHarry 2026-03-13 00:15:52 +01:00 committed by GitHub
parent 6c7937a9ff
commit 8172e8baa7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 492 additions and 150 deletions

View file

@ -1058,6 +1058,10 @@
"fear": "Fear", "fear": "Fear",
"spotlight": "Spotlight" "spotlight": "Spotlight"
}, },
"DaggerheartDiceAnimationEvents": {
"critical": { "name": "Critical" },
"higher": { "name": "Highest Roll" }
},
"DamageType": { "DamageType": {
"physical": { "physical": {
"name": "Physical", "name": "Physical",
@ -2309,6 +2313,7 @@
"plurial": "Players" "plurial": "Players"
}, },
"portrait": "Portrait", "portrait": "Portrait",
"preview": "Preview",
"proficiency": "Proficiency", "proficiency": "Proficiency",
"quantity": "Quantity", "quantity": "Quantity",
"range": "Range", "range": "Range",
@ -2758,7 +2763,12 @@
"colorset": "Theme", "colorset": "Theme",
"material": "Material", "material": "Material",
"system": "Dice Preset", "system": "Dice Preset",
"font": "Font" "font": "Font",
"critical": "Duality Critical Animation",
"diceAppearance": "Dice Appearance",
"animations": "Animations",
"defaultAnimations": "Set Animations As Player Defaults",
"previewAnimation": "Preview Animation"
} }
}, },
"variantRules": { "variantRules": {

View file

@ -12,7 +12,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', tag: 'form',
id: 'daggerheart-appearance-settings', id: 'daggerheart-appearance-settings',
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'], classes: ['daggerheart', 'dialog', 'dh-style', 'setting', 'appearance-settings'],
position: { width: '600', height: 'auto' }, position: { width: '600', height: 'auto' },
window: { window: {
title: 'DAGGERHEART.SETTINGS.Menu.title', title: 'DAGGERHEART.SETTINGS.Menu.title',
@ -70,6 +70,14 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
} }
} }
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
htmlElement
.querySelector('.default-animations-input')
?.addEventListener('change', this.toggleSFXOverride.bind(this));
}
/** @inheritdoc */ /** @inheritdoc */
_configureRenderParts(options) { _configureRenderParts(options) {
const parts = super._configureRenderParts(options); const parts = super._configureRenderParts(options);
@ -83,15 +91,20 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
/**@inheritdoc */ /**@inheritdoc */
async _prepareContext(options) { async _prepareContext(options) {
const context = await super._prepareContext(options); const context = await super._prepareContext(options);
if (options.isFirstRender) if (options.isFirstRender) {
this.setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance); this.setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
this.globalOverrides = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.GlobalOverrides);
}
context.setting = this.setting; context.setting = this.setting;
context.globalOverrides = this.globalOverrides;
context.fields = this.setting.schema.fields; context.fields = this.setting.schema.fields;
context.tabs = this._prepareTabs('general'); context.tabs = this._prepareTabs('general');
context.dsnTabs = this._prepareTabs('diceSoNice'); context.dsnTabs = this._prepareTabs('diceSoNice');
context.isGM = game.user.isGM;
return context; return context;
} }
@ -120,6 +133,9 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
* @protected * @protected
*/ */
async prepareDiceSoNiceContext(context) { async prepareDiceSoNiceContext(context) {
context.animationEvents = CONFIG.DH.GENERAL.daggerheartDiceAnimationEvents;
context.previewAnimation = this.previewAnimation;
context.diceSoNiceTextures = Object.entries(game.dice3d.exports.TEXTURELIST).reduce( context.diceSoNiceTextures = Object.entries(game.dice3d.exports.TEXTURELIST).reduce(
(acc, [k, v]) => ({ (acc, [k, v]) => ({
...acc, ...acc,
@ -146,6 +162,13 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
); );
context.diceSoNiceFonts = game.dice3d.exports.Utils.prepareFontList(); 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( foundry.utils.mergeObject(
context.dsnTabs, context.dsnTabs,
['hope', 'fear', 'advantage', 'disadvantage'].reduce( ['hope', 'fear', 'advantage', 'disadvantage'].reduce(
@ -153,7 +176,8 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
...acc, ...acc,
[key]: { [key]: {
values: this.setting.diceSoNice[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,13 +193,20 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
* @param {foundry.applications.ux.FormDataExtended} formData * @param {foundry.applications.ux.FormDataExtended} formData
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
static async #onSubmit(event, form, formData) { static async #onSubmit(_event, _form, formData) {
const data = this.setting.schema.clean(foundry.utils.expandObject(formData.object)); const data = this.setting.schema.clean(foundry.utils.expandObject(formData.object));
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, data); await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, data);
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
async toggleSFXOverride(event) {
await this.globalOverrides.diceSoNiceSFXUpdate(this.setting, event.target.checked);
this.globalOverrides = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.GlobalOverrides);
this.render();
}
/** /**
* Submit the configuration form. * Submit the configuration form.
* @this {DHAppearanceSettings} * @this {DHAppearanceSettings}
@ -183,13 +214,25 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
*/ */
static async #onPreview(_, target) { static async #onPreview(_, target) {
const formData = new foundry.applications.ux.FormDataExtended(target.closest('form')); 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 { key } = target.dataset;
const faces = ['advantage', 'disadvantage'].includes(key) ? 'd6' : 'd12'; const faces = ['advantage', 'disadvantage'].includes(key) ? 'd6' : 'd12';
const preset = await getDiceSoNicePreset(diceSoNice[key], faces); const preset = await getDiceSoNicePreset(diceSoNice[key], faces);
const diceSoNiceRoll = await new foundry.dice.Roll(`1${faces}`).evaluate(); const diceSoNiceRoll = await new foundry.dice.Roll(`1${faces}`).evaluate();
diceSoNiceRoll.dice[0].options.appearance = preset.appearance; diceSoNiceRoll.dice[0].options.appearance = preset.appearance;
diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile; 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); await game.dice3d.showForRoll(diceSoNiceRoll, game.user, false);
} }

View file

@ -630,7 +630,95 @@ export const diceSetNumbers = {
flat: 'Flat' 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).diceSoNiceData;
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); const system = game.dice3d.DiceFactory.systems.get(type.system).dice.get(faces);
if (!system) { if (!system) {
ui.notifications.error( ui.notifications.error(
@ -653,16 +741,33 @@ export const getDiceSoNicePreset = async (type, faces) => {
appearance: { appearance: {
...system.appearance, ...system.appearance,
...type ...type
} },
sfx: getDiceSoNiceSFX(sfxOptions)
}; };
}; };
export const getDiceSoNicePresets = async (hopeFaces, fearFaces, advantageFaces = 'd6', disadvantageFaces = 'd6') => { export const getDiceSoNicePresets = async (
const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance); result,
hopeFaces,
fearFaces,
advantageFaces = 'd6',
disadvantageFaces = 'd6'
) => {
const diceSoNice = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).diceSoNiceData;
const { isCritical, withHope, withFear } = result;
return { return {
hope: await getDiceSoNicePreset(diceSoNice.hope, hopeFaces), hope: await getDiceSoNicePreset(diceSoNice.hope, hopeFaces, {
fear: await getDiceSoNicePreset(diceSoNice.fear, fearFaces), 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), advantage: await getDiceSoNicePreset(diceSoNice.advantage, advantageFaces),
disadvantage: await getDiceSoNicePreset(diceSoNice.disadvantage, disadvantageFaces) disadvantage: await getDiceSoNicePreset(diceSoNice.disadvantage, disadvantageFaces)
}; };

View file

@ -72,17 +72,17 @@ const companionBaseResources = Object.freeze({
export const character = { export const character = {
base: characterBaseResources, base: characterBaseResources,
custom: {}, // module stuff goes here custom: {}, // module stuff goes here
all: { ...characterBaseResources }, all: { ...characterBaseResources }
}; };
export const adversary = { export const adversary = {
base: adversaryBaseResources, base: adversaryBaseResources,
custom: {}, // module stuff goes here custom: {}, // module stuff goes here
all: { ...adversaryBaseResources }, all: { ...adversaryBaseResources }
}; };
export const companion = { export const companion = {
base: companionBaseResources, base: companionBaseResources,
custom: {}, // module stuff goes here custom: {}, // module stuff goes here
all: { ...companionBaseResources }, all: { ...companionBaseResources }
}; };

View file

@ -26,6 +26,7 @@ export const gameSettings = {
Metagaming: 'Metagaming', Metagaming: 'Metagaming',
Homebrew: 'Homebrew', Homebrew: 'Homebrew',
appearance: 'Appearance', appearance: 'Appearance',
GlobalOverrides: 'GlobalOverrides',
variantRules: 'VariantRules', variantRules: 'VariantRules',
Resources: { Resources: {
Fear: 'ResourcesFear' Fear: 'ResourcesFear'

View file

@ -78,7 +78,7 @@ class ResourcesField extends fields.TypedObjectField {
const resource = resources[key]; const resource = resources[key];
value.label = resource.label; value.label = resource.label;
value.isReversed = resources[key].reverse; value.isReversed = resources[key].reverse;
value.max = typeof resource.max === 'number' ? value.max ?? resource.max : null; value.max = typeof resource.max === 'number' ? (value.max ?? resource.max) : null;
} }
return data; return data;
} }

View file

@ -1,6 +1,16 @@
export default class DhAppearance extends foundry.abstract.DataModel { export default class DhAppearance extends foundry.abstract.DataModel {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.SETTINGS.Appearance']; static LOCALIZATION_PREFIXES = ['DAGGERHEART.SETTINGS.Appearance'];
static sfxSchema = () =>
new foundry.data.fields.SchemaField({
class: new foundry.data.fields.StringField({
nullable: true,
initial: null,
blank: true,
choices: CONFIG.DH.GENERAL.diceSoNiceSFXClasses
})
});
static defineSchema() { static defineSchema() {
const { StringField, ColorField, BooleanField, SchemaField } = foundry.data.fields; const { StringField, ColorField, BooleanField, SchemaField } = foundry.data.fields;
@ -15,7 +25,10 @@ export default class DhAppearance extends foundry.abstract.DataModel {
colorset: new StringField({ initial: 'inspired', required: true, blank: false }), colorset: new StringField({ initial: 'inspired', required: true, blank: false }),
material: new StringField({ initial: 'metal', required: true, blank: false }), material: new StringField({ initial: 'metal', required: true, blank: false }),
system: new StringField({ initial: 'standard', 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: DhAppearance.sfxSchema()
})
}); });
return { return {
@ -30,7 +43,10 @@ export default class DhAppearance extends foundry.abstract.DataModel {
hope: diceStyle({ fg: '#ffffff', bg: '#ffe760', outline: '#000000', edge: '#ffffff' }), hope: diceStyle({ fg: '#ffffff', bg: '#ffe760', outline: '#000000', edge: '#ffffff' }),
fear: diceStyle({ fg: '#000000', bg: '#0032b1', outline: '#ffffff', edge: '#000000' }), fear: diceStyle({ fg: '#000000', bg: '#0032b1', outline: '#ffffff', edge: '#000000' }),
advantage: diceStyle({ fg: '#ffffff', bg: '#008000', outline: '#000000', edge: '#ffffff' }), 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: DhAppearance.sfxSchema()
})
}), }),
extendCharacterDescriptions: new BooleanField(), extendCharacterDescriptions: new BooleanField(),
extendAdversaryDescriptions: new BooleanField(), extendAdversaryDescriptions: new BooleanField(),
@ -65,4 +81,48 @@ export default class DhAppearance extends foundry.abstract.DataModel {
showGenericStatusEffects: new BooleanField({ initial: true }) showGenericStatusEffects: new BooleanField({ initial: true })
}; };
} }
get diceSoNiceData() {
const globalOverrides = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.GlobalOverrides);
const getSFX = (baseClientData, overrideKey) => {
if (!globalOverrides.diceSoNice.sfx.overrideEnabled) return baseClientData;
const overrideData = globalOverrides.diceSoNice.sfx[overrideKey];
const clientData = foundry.utils.deepClone(baseClientData);
return Object.keys(clientData).reduce((acc, key) => {
const data = clientData[key];
acc[key] = Object.keys(data).reduce((acc, dataKey) => {
const value = data[dataKey];
acc[dataKey] = value ? value : overrideData[key][dataKey];
return acc;
}, {});
return acc;
}, {});
};
return {
...this.diceSoNice,
sfx: getSFX(this.diceSoNice.sfx, 'global'),
hope: {
...this.diceSoNice.hope,
sfx: getSFX(this.diceSoNice.hope.sfx, 'hope')
},
fear: {
...this.diceSoNice.fear,
sfx: getSFX(this.diceSoNice.fear.sfx, 'fear')
}
};
}
/** Invoked by the setting when data changes */
handleChange() {
if (this.displayFear) {
if (ui.resources) {
if (this.displayFear === 'hide') ui.resources.close({ allowed: true });
else ui.resources.render({ force: true });
}
}
const globalOverrides = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.GlobalOverrides);
globalOverrides.diceSoNiceSFXUpdate(this);
}
} }

View file

@ -0,0 +1,55 @@
import DhAppearance from './Appearance.mjs';
/**
* A setting to handle cases where we want to allow the GM to set a global default for client settings.
*/
export default class DhGlobalOverrides extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
diceSoNice: new fields.SchemaField({
sfx: new fields.SchemaField({
overrideEnabled: new fields.BooleanField(),
global: new fields.SchemaField({
critical: DhAppearance.sfxSchema()
}),
hope: new fields.SchemaField({
higher: DhAppearance.sfxSchema()
}),
fear: new fields.SchemaField({
higher: DhAppearance.sfxSchema()
})
})
})
};
}
async diceSoNiceSFXUpdate(appearanceSettings, enabled) {
if (!game.user.isGM) return;
const newEnabled = enabled !== undefined ? enabled : this.diceSoNice.sfx.overrideEnabled;
if (newEnabled) {
const newOverrides = foundry.utils.mergeObject(this.toObject(), {
diceSoNice: {
sfx: {
overrideEnabled: true,
global: appearanceSettings.diceSoNice.sfx,
hope: appearanceSettings.diceSoNice.hope.sfx,
fear: appearanceSettings.diceSoNice.fear.sfx
}
}
});
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.GlobalOverrides, newOverrides);
} else {
const newOverrides = {
...this.toObject(),
diceSoNice: {
sfx: {
overrideEnabled: false
}
}
};
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.GlobalOverrides, newOverrides);
}
}
}

View file

@ -220,7 +220,7 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
return result; return result;
}, {}), }, {}),
...config.custom, ...config.custom,
...config.base, ...config.base
}); });
} }
} }

View file

@ -3,3 +3,4 @@ export { default as DhAutomation } from './Automation.mjs';
export { default as DhHomebrew } from './Homebrew.mjs'; export { default as DhHomebrew } from './Homebrew.mjs';
export { default as DhMetagaming } from './Metagaming.mjs'; export { default as DhMetagaming } from './Metagaming.mjs';
export { default as DhVariantRules } from './VariantRules.mjs'; export { default as DhVariantRules } from './VariantRules.mjs';
export { default as DhGlobalOverrides } from './GlobalOverrides.mjs';

View file

@ -378,6 +378,8 @@ export default class DualityRoll extends D20Roll {
let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false }); let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false });
const term = parsedRoll.terms[target.dataset.dieIndex]; const term = parsedRoll.terms[target.dataset.dieIndex];
await term.reroll(`/r1=${term.total}`); await term.reroll(`/r1=${term.total}`);
const result = await parsedRoll.evaluate();
if (game.modules.get('dice-so-nice')?.active) { if (game.modules.get('dice-so-nice')?.active) {
const diceSoNiceRoll = { const diceSoNiceRoll = {
_evaluated: true, _evaluated: true,
@ -391,7 +393,7 @@ export default class DualityRoll extends D20Roll {
options: { appearance: {} } 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; const type = target.dataset.type;
if (diceSoNicePresets[type]) { if (diceSoNicePresets[type]) {
diceSoNiceRoll.dice[0].options = 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 game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
} }
await parsedRoll.evaluate();
const newRoll = game.system.api.dice.DualityRoll.postEvaluate(parsedRoll, { const newRoll = game.system.api.dice.DualityRoll.postEvaluate(parsedRoll, {
targets: message.system.targets, targets: message.system.targets,
roll: { roll: {

View file

@ -60,7 +60,13 @@ export const getCommandTarget = (options = {}) => {
export const setDiceSoNiceForDualityRoll = async (rollResult, advantageState, hopeFaces, fearFaces, advantageFaces) => { export const setDiceSoNiceForDualityRoll = async (rollResult, advantageState, hopeFaces, fearFaces, advantageFaces) => {
if (!game.modules.get('dice-so-nice')?.active) return; 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[0].options = diceSoNicePresets.hope;
rollResult.dice[1].options = diceSoNicePresets.fear; rollResult.dice[1].options = diceSoNicePresets.fear;
if (rollResult.dice[2] && advantageState) { if (rollResult.dice[2] && advantageState) {

View file

@ -46,6 +46,7 @@ export const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/ui/chat/parts/target-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/target-part.hbs',
'systems/daggerheart/templates/ui/chat/parts/button-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/button-part.hbs',
'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs', 'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs',
'systems/daggerheart/templates/scene/dh-config.hbs' 'systems/daggerheart/templates/scene/dh-config.hbs',
'systems/daggerheart/templates/settings/appearance-settings/diceSoNiceTab.hbs'
]); ]);
}; };

View file

@ -1,6 +1,13 @@
import { defaultLevelTiers, DhLevelTiers } from '../data/levelTier.mjs'; import { defaultLevelTiers, DhLevelTiers } from '../data/levelTier.mjs';
import DhCountdowns from '../data/countdowns.mjs'; import DhCountdowns from '../data/countdowns.mjs';
import { DhAppearance, DhAutomation, DhHomebrew, DhMetagaming, DhVariantRules } from '../data/settings/_module.mjs'; import {
DhAppearance,
DhAutomation,
DhGlobalOverrides,
DhHomebrew,
DhMetagaming,
DhVariantRules
} from '../data/settings/_module.mjs';
import { import {
DhAppearanceSettings, DhAppearanceSettings,
DhAutomationSettings, DhAutomationSettings,
@ -54,17 +61,18 @@ const registerMenuSettings = () => {
} }
}); });
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.GlobalOverrides, {
scope: 'world',
config: false,
type: DhGlobalOverrides
});
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, { game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, {
scope: 'client', scope: 'client',
config: false, config: false,
type: DhAppearance, type: DhAppearance,
onChange: value => { onChange: value => {
if (value.displayFear) { value.handleChange();
if (ui.resources) {
if (value.displayFear === 'hide') ui.resources.close({ allowed: true });
else ui.resources.render({ force: true });
}
}
} }
}); });
}; };

View file

@ -565,61 +565,12 @@
} }
.application.setting.dh-style { .application.setting.dh-style {
fieldset {
h2, h2,
h3, h3,
h4 { h4 {
margin: 8px 0 4px; margin: 8px 0 4px;
text-align: center; text-align: center;
} }
.title-hint {
font-size: var(--font-size-12);
font-variant: small-caps;
text-align: center;
}
.field-section {
display: flex;
flex-direction: column;
gap: 10px;
.split-section {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
}
.label-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
&.full-width {
grid-template-columns: 1fr 1fr 1fr 1fr;
select {
grid-column: span 3;
}
}
label {
align-self: center;
text-align: center;
white-space: nowrap;
}
}
.button-container {
display: grid;
grid-template-columns: 1fr;
gap: 10px;
text-align: center;
display: flex;
justify-content: center;
width: 100%;
}
}
footer { footer {
margin-top: 8px; margin-top: 8px;

View file

@ -28,6 +28,7 @@
@import './settings/homebrew-settings/domains.less'; @import './settings/homebrew-settings/domains.less';
@import './settings/homebrew-settings/types.less'; @import './settings/homebrew-settings/types.less';
@import './settings/homebrew-settings/resources.less'; @import './settings/homebrew-settings/resources.less';
@import './settings/appearance-settings/diceSoNice.less';
@import './sidebar/tabs.less'; @import './sidebar/tabs.less';
@import './sidebar/daggerheartMenu.less'; @import './sidebar/daggerheartMenu.less';

View file

@ -0,0 +1,72 @@
.daggerheart.dh-style.setting.appearance-settings {
.tab.active[data-tab='diceSoNice'] {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.diceSoNice-footer {
display: grid;
align-items: center;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 8px;
margin-top: 32px;
label {
text-align: center;
}
select {
grid-column: span 2;
}
}
.title-hint {
font-size: var(--font-size-14);
font-variant: small-caps;
text-align: center;
}
.split-section {
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;
}
}
.field-section {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 4px;
width: 100%;
padding: 0 8px;
}
.label-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
&.full-width {
grid-template-columns: 1fr 1fr 1fr 1fr;
select {
grid-column: span 3;
}
}
label {
align-self: center;
text-align: center;
white-space: nowrap;
}
}
}

View file

@ -1,7 +1,19 @@
<section class="tab {{#if tab.active}} active{{/if}}" data-group="{{tab.group}}" data-tab="{{tab.id}}"> <section class="tab {{#if tab.active}} active{{/if}}" data-group="{{tab.group}}" data-tab="{{tab.id}}">
<fieldset>
<div class="title-hint">{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.hint"}}</div> <div class="title-hint">{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.hint"}}</div>
<div class="field-section form-fields">
{{#if isGM}}
<div class="form-group">
<label for="dsn-overrideEnabled">{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.defaultAnimations"}}</label>
<input id="dsn-overrideEnabled" type="checkbox" class="default-animations-input" {{checked globalOverrides.diceSoNice.sfx.overrideEnabled}} />
</div>
{{/if}}
<div class="form-group">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.critical"}}</label>
<div class="form-fields">
{{formInput fields.diceSoNice.fields.sfx.fields.critical.fields.class value=setting.diceSoNice.sfx.critical.class blank="" localize=true}}
</div>
</div>
</div>
<section class='tab-navigation'> <section class='tab-navigation'>
<div class='navigation-container'> <div class='navigation-container'>
<div class="navigation-inner-container"> <div class="navigation-inner-container">
@ -19,53 +31,7 @@
</section> </section>
{{#each dsnTabs as |dsnTab|}} {{#each dsnTabs as |dsnTab|}}
<section class="tab {{#if dsnTab.active}} active{{/if}}" data-group="{{dsnTab.group}}" data-tab="{{dsnTab.id}}"> <section class="tab {{#if dsnTab.active}} active{{/if}}" data-group="{{dsnTab.group}}" data-tab="{{dsnTab.id}}">
<div class="field-section"> {{> "systems/daggerheart/templates/settings/appearance-settings/diceSoNiceTab.hbs" dsnTab }}
<div class="label-container full-width">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.system"}}</label>
{{formInput fields.system value=values.system localize=true choices=@root.diceSoNiceSystems}}
</div>
<div class="split-section">
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.foreground"}}</label>
{{formInput fields.foreground value=values.foreground localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.background"}}</label>
{{formInput fields.background value=values.background localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.outline"}}</label>
{{formInput fields.outline value=values.outline localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.edge"}}</label>
{{formInput fields.edge value=values.edge localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.colorset"}}</label>
{{formInput fields.colorset value=values.colorset choices=@root.diceSoNiceColorsets localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.texture"}}</label>
{{formInput fields.texture value=values.texture choices=@root.diceSoNiceTextures localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.material"}}</label>
{{formInput fields.material value=values.material choices=@root.diceSoNiceMaterials localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.font"}}</label>
{{formInput fields.font value=values.font choices=@root.diceSoNiceFonts localize=true}}
</div>
</div>
<div class="button-container">
<button type="button" data-action="preview" data-key="{{dsnTab.id}}">
<i class="fa-solid fa-dice"></i>
<span>{{localize "Preview"}}</span>
</button>
</div>
</div>
</section> </section>
{{/each}} {{/each}}
</fieldset>
</section> </section>

View file

@ -0,0 +1,62 @@
<div class="field-section">
<h3>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.diceAppearance"}}</h3>
<div class="label-container full-width">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.system"}}</label>
{{formInput fields.system value=values.system localize=true choices=@root.diceSoNiceSystems}}
</div>
<div class="split-section">
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.foreground"}}</label>
{{formInput fields.foreground value=values.foreground localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.background"}}</label>
{{formInput fields.background value=values.background localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.outline"}}</label>
{{formInput fields.outline value=values.outline localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.edge"}}</label>
{{formInput fields.edge value=values.edge localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.colorset"}}</label>
{{formInput fields.colorset value=values.colorset choices=@root.diceSoNiceColorsets localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.texture"}}</label>
{{formInput fields.texture value=values.texture choices=@root.diceSoNiceTextures localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.material"}}</label>
{{formInput fields.material value=values.material choices=@root.diceSoNiceMaterials localize=true}}
</div>
<div class="label-container">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.font"}}</label>
{{formInput fields.font value=values.font choices=@root.diceSoNiceFonts localize=true}}
</div>
</div>
{{#if animations}}
<h3>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.animations"}}</h3>
<div class="label-container full-width">
<label>{{localize "DAGGERHEART.CONFIG.DaggerheartDiceAnimationEvents.higher.name"}}</label>
{{formInput fields.sfx.fields.higher.fields.class value=values.sfx.higher.class blank="" localize=true}}
</div>
{{/if}}
<div class="diceSoNice-footer">
<label>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.previewAnimation"}}</label>
<select name="{{concat id "PreviewAnimation"}}">
{{selectOptions @root.animationEvents selected=@root.previewAnimation blank="" localize=true }}
</select>
<button type="button" data-action="preview" data-key="{{id}}">
<i class="fa-solid fa-dice"></i>
<span>{{localize "DAGGERHEART.GENERAL.preview"}}</span>
</button>
</div>
</div>