{"version":3,"file":"api.js","sources":["../module/DiceSystem.js","../module/sfx/DiceSFX.js"],"sourcesContent":["export class DiceSystem {\n\n //setting scoping is currently useless as there's no good way for the user to set a \"local\" setting if they are not using this dice set\n //keeping this for future use\n static SETTING_SCOPE = {\n LOCAL: 0, // User specific settings that is not shared with other players\n SHARED: 1 // User specific settings that is shared with other players\n }\n\n static SETTING_TYPE = {\n BOOLEAN: \"boolean\",\n SELECT: \"select\",\n COLOR: \"color\",\n FILE: \"file\",\n RANGE: \"range\",\n STRING: \"string\"\n }\n\n static SETTING_FORMATING = {\n SEPARATOR: \"separator\",\n HTML: \"html\"\n }\n\n static DICE_EVENT_TYPE = {\n SPAWN: 0,\n CLICK: 1,\n RESULT: 2,\n COLLIDE: 3, //not implemented, risk of performance impact \n DESPAWN: 4 //not implemented. need a use-case. \"result\" seems to be the enough for despawning animations\n }\n\n static generateHash = (str) => {\n let hash = 0,\n i, chr;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n chr = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + chr;\n hash |= 0;\n }\n return hash;\n }\n\n /**\n * Creates a new instance of the DiceSystem class.\n *\n * @param {string} id - The unique identifier for the dice system.\n * @param {string} name - The name of the dice system.\n * @param {Map} [dice=null] - A map of dice for the dice system.\n * @param {string} [mode=\"default\"] - The mode of the dice system.\n * @param {string|null} [group=null] - The group the dice system belongs to.\n */\n constructor(id, name, mode = \"default\", group = null) {\n this._id = id;\n this._name = name;\n this._dice = new DiceMap(this);\n this._mode = mode;\n this._group = group;\n\n this._settings = [];\n this._scopedSettings = new Map();\n\n this._listeners = [];\n\n this._registeredProcessMaterialCallbacks = [];\n this._registeredBeforeShaderCompileCallbacks = [];\n }\n\n get id() {\n return this._id;\n }\n\n get name() {\n return this._name;\n }\n\n get dice() {\n return this._dice;\n }\n\n get mode() {\n return this._mode;\n }\n\n get group() {\n return this._group;\n }\n\n get settings() {\n const objValues = Object.values(DiceSystem.SETTING_TYPE);\n return this._settings.filter((setting) => objValues.includes(setting.type));\n }\n\n on(eventType, listener) {\n if(!Object.values(DiceSystem.DICE_EVENT_TYPE).includes(eventType))\n throw new Error(`[DiceSystem.fire] Invalid dice event type: ${eventType}`);\n\n if (!this._listeners[eventType]) {\n this._listeners[eventType] = [];\n }\n this._listeners[eventType].push(listener);\n }\n\n off(eventType, listener) {\n if (!this._listeners[eventType]) return;\n\n const index = this._listeners[eventType].indexOf(listener);\n if (index > -1) {\n this._listeners[eventType].splice(index, 1);\n }\n }\n\n fire(eventType, event) {\n if(!Object.values(DiceSystem.DICE_EVENT_TYPE).includes(eventType))\n throw new Error(`[DiceSystem.fire] Invalid dice event type: ${eventType}`);\n\n this._dispatchEvent(eventType, event);\n }\n\n _dispatchEvent(eventType, event) {\n if (!this._listeners[eventType]) return;\n\n for (const listener of this._listeners[eventType]) {\n listener(event);\n }\n }\n\n getSettingsByDiceType(diceType) {\n return this._scopedSettings.get(diceType) || this._scopedSettings.get(\"global\");\n }\n\n getCacheString(appearance) {\n return this.id+JSON.stringify(Object.values(appearance));\n }\n\n processMaterial(diceType, material, appearance) {\n if(this.dice.has(diceType)) {\n for(const callback of this._registeredProcessMaterialCallbacks) {\n callback(diceType, material, appearance);\n }\n\n material.userData.diceType = diceType;\n material.userData.system = this.id;\n material.userData.appearance = appearance;\n }\n return material;\n }\n\n beforeShaderCompile(shader, material) {\n let fragmentShader = shader.fragmentShader;\n let vertexShader = shader.vertexShader;\n \n for(const callback of this._registeredBeforeShaderCompileCallbacks) {\n callback(shader, material, material.userData.diceType, material.userData.appearance);\n }\n\n material.userData.shaderCacheKey = DiceSystem.generateHash(shader.fragmentShader+shader.vertexShader);\n\n if(fragmentShader != shader.fragmentShader || vertexShader != shader.vertexShader) {\n material.customProgramCacheKey = () => {\n return material.userData.shaderCacheKey;\n }\n material.needsUpdate = true;\n }\n }\n\n /**\n * Registers a callback to be called when a material is processed for a dice of this system.\n * The callback will be called with the diceType, material and appearance as arguments.\n * @param {function} callback - The callback to be called.\n */\n registerProcessMaterialCallback(callback) {\n this._registeredProcessMaterialCallbacks.push(callback);\n }\n \n /**\n * Registers a callback to be called when a shader is compiled for a dice of this system.\n * The callback will be called with the shader, material and appearance as arguments.\n * @param {function} callback - The callback to be called.\n */\n registerBeforeShaderCompileCallback(callback) {\n this._registeredBeforeShaderCompileCallbacks.push(callback);\n }\n\n updateSettings(diceType = \"global\", settings) {\n this._scopedSettings.set(diceType, {...settings});\n }\n\n loadSettings() {\n this._scopedSettings = new Map();\n //called after the system is added to the dice factory\n //check for saved settings and load them\n const savedSettings = game.user.getFlag(\"dice-so-nice\", \"appearance\");\n\n //set default settings (just key/value pairs)\n const defaultSettings = this.settings.reduce((acc, { id, defaultValue }) => ({ ...acc, [id]: defaultValue }), {});\n this._scopedSettings.set(\"global\", defaultSettings);\n\n if(savedSettings) {\n for(let diceType of Object.keys(savedSettings)) {\n if(savedSettings[diceType].system === this.id) {\n this._scopedSettings.set(diceType, {...savedSettings[diceType].systemSettings});\n }\n }\n }\n }\n\n /**\n * Creates a setting object with the specified type, id, name, scope, value, and additional properties.\n *\n * @param {string} type - The type of the setting.\n * @param {string} id - The unique identifier of the setting.\n * @param {string} name - The name of the setting.\n * @param {string} scope - The scope of the setting.\n * @param {any} value - The value of the setting.\n * @param {Object} [additionalProperties={}] - Additional properties to be added to the setting object.\n * @return {Object} The created setting object.\n */\n _createSetting(type, id, name, scope, defaultValue, additionalProperties = {}) {\n //field checks\n if (!Object.values(DiceSystem.SETTING_TYPE).includes(type) && !Object.values(DiceSystem.SETTING_FORMATING).includes(type)) {\n throw new Error(`[DiceSystem._createSetting] Invalid setting type: ${type}`);\n }\n\n if (!Object.values(DiceSystem.SETTING_SCOPE).includes(scope) && !Object.values(DiceSystem.SETTING_SCOPE).includes(scope)) {\n throw new Error(`[DiceSystem._createSetting] Invalid setting scope: ${scope}`);\n }\n\n if (DiceSystem.SETTING_TYPE.hasOwnProperty(type)) {\n if (!id) {\n throw new Error(`[DiceSystem._createSetting] Invalid setting id: ${id}`);\n }\n\n if (!name) {\n throw new Error(`[DiceSystem._createSetting] Invalid setting name: ${name}`);\n }\n }\n\n this._settings.push({\n type,\n id,\n name,\n defaultValue,\n scope: scope,\n ...additionalProperties\n });\n }\n\n /**\n * Adds a visual separator with an optional title.\n *\n * @param {Object} args - The options for the separator.\n * @param {string} [args.name=\"\"] - The title to display\n * @return {void}\n */\n addSettingSeparator({ name = \"\" } = {}) {\n this._createSetting(\"separator\", null, name, DiceSystem.SETTING_SCOPE.LOCAL, null);\n }\n\n addSettingHTML({ name }) {\n this._createSetting(\"html\", null, name, DiceSystem.SETTING_SCOPE.LOCAL, null);\n }\n\n /**\n * Adds a boolean setting\n *\n * @param {Object} args - The arguments object.\n * @param {string} args.id - The unique identifier for the setting.\n * @param {string} args.name - The name of the setting.\n * @param {string} args.scope - The scope of the setting.\n * @param {boolean} [args.defaultValue=false] - The default value of the setting.\n * @return {void}\n */\n addSettingBoolean({ id, name, scope = DiceSystem.SETTING_SCOPE.SHARED, defaultValue = false }) {\n this._createSetting(\"boolean\", id, name, scope, defaultValue);\n }\n\n /**\n * Adds a color setting\n *\n * @param {Object} args - The arguments object.\n * @param {string} args.id - The unique identifier for the setting.\n * @param {string} args.name - The name of the setting.\n * @param {string} args.scope - The scope of the setting.\n * @param {string} [args.defaultValue=null] - The default value of the setting, in hex format, e.g. \"#ffffff\".\n * @return {void}\n */\n addSettingColor({ id, name, scope = DiceSystem.SETTING_SCOPE.SHARED, defaultValue = \"#ffffff\" }) {\n this._createSetting(\"color\", id, name, scope, defaultValue);\n }\n\n /**\n * Adds a range setting\n *\n * @param {Object} args - The arguments object.\n * @param {string} args.id - The unique identifier for the setting.\n * @param {string} args.name - The name of the setting.\n * @param {string} args.scope - The scope of the setting.\n * @param {number} [args.defaultValue=0] - The default value of the setting.\n * @param {number} [args.min=0] - The minimum value of the setting.\n * @param {number} [args.max=100] - The maximum value of the setting.\n * @param {number} [args.step=1] - The step value of the setting.\n * @return {void}\n */\n addSettingRange({ id, name, scope = DiceSystem.SETTING_SCOPE.SHARED, defaultValue = 0, min = 0, max = 100, step = 1 }) {\n this._createSetting(\"range\", id, name, scope, defaultValue, { min, max, step });\n }\n\n /**\n * Adds a file setting\n *\n * @param {Object} args - The arguments object.\n * @param {string} args.id - The unique identifier for the setting.\n * @param {string} args.name - The name of the setting.\n * @param {string} args.scope - The scope of the setting.\n * @param {string|null} [args.defaultValue=null] - The default value of the setting.\n * @return {void}\n */\n addSettingFile({ id, name, scope = DiceSystem.SETTING_SCOPE.SHARED, defaultValue = \"\" }) {\n // Make sure defaultValue is a string)\n defaultValue = defaultValue || \"\";\n this._createSetting(\"file\", id, name, scope, defaultValue);\n }\n\n /**\n * Adds a select setting\n *\n * @param {Object} args - The arguments object.\n * @param {string} args.id - The unique identifier for the setting.\n * @param {string} args.name - The name of the setting.\n * @param {string} args.scope - The scope of the setting.\n * @param {string|null} [args.defaultValue=null] - The default value of the setting.\n * @param {Object} [args.options={}] - The options for the select setting.\n * @param {string} args.options.id - The unique identifier for the option.\n * @param {string} args.options.label - The label for the option.\n * @param {string} [args.options.group=null] - The group for the option.\n * @return {void}\n */\n addSettingSelect({ id, name, scope = DiceSystem.SETTING_SCOPE.SHARED, defaultValue = null, options = {} }) {\n this._createSetting(\"select\", id, name, scope, defaultValue, { options });\n }\n\n /**\n * Adds a string setting.\n *\n * @param {Object} options - The options for the setting.\n * @param {string} options.id - The unique identifier for the setting.\n * @param {string} options.name - The name of the setting.\n * @param {string} options.scope - The scope of the setting.\n * @param {string|null} [options.defaultValue=null] - The default value of the setting.\n * @return {void}\n */\n addSettingString({ id, name, scope = DiceSystem.SETTING_SCOPE.SHARED, defaultValue = \"\" }) {\n // Make sure defaultValue is a string\n defaultValue = defaultValue || \"\";\n this._createSetting(\"string\", id, name, scope, defaultValue);\n }\n\n /**\n * Retrieves a dice object from the dice array based on the given shape and values.\n *\n * @param {string} shape - The shape of the dice.\n * @param {Array} values - The values of the dice.\n * @return {Object|null} The dice object if found, or null if not found.\n */\n getDiceByShapeAndValues(shape, values) {\n for (let dice of this.dice.values()) {\n if (dice.shape == shape && dice.values.length == values.length) {\n return dice;\n }\n }\n return null;\n }\n\n /**\n * Retrieves the value of a scoped setting for a specific dice type.\n *\n * @param {string} diceType - The type of the dice.\n * @param {string} settingId - The ID of the setting.\n * @return {any} The value of the scoped setting, or the default value if not found.\n */\n getScopedSettingValue(diceType, settingId) {\n return this._scopedSettings.get(diceType)?.[settingId] ?? this._scopedSettings.get(\"global\")?.[settingId];\n }\n\n /**\n * Generates the HTML content and data for a settings dialog line based on the provided setting.\n *\n * @param {Object} setting - The setting object containing the type, id, name, and value.\n * @param {string} diceType - The type of the dice or \"global\"\n * @return {Object} An object containing the HTML content and data for the settings dialog line.\n */\n getSettingsDialogLine(setting, diceType) {\n let line = {\n content: \"\",\n data: {}\n };\n switch (setting.type) {\n case DiceSystem.SETTING_TYPE.BOOLEAN:\n line.content = `\n