import { VermineUtils } from "../roll.mjs"; /** * Represents a dialog for rolling dice. */ export default class RollDialog extends Dialog { /** * Creates a new RollDialog instance. * @param {Object} data - The data for the dialog. * @param {HTMLElement} html - The HTML content of the dialog. * @param {Object} options - The options for the dialog. * @param {Function} close - The callback function for closing the dialog. */ constructor(data, html, options, close = undefined) { let conf = { title: "jet de dés", content: html, buttons: { roll: { icon: '', label: "Lancer !", callback: () => { this._onRoll() } }, cancel: { icon: '', label: "Annuler", callback: () => { this.close() } } }, close: close, } return super({ ...conf, ...data }, options); }; /** * Creates a new RollDialog instance. * @param {Object} data - The data for the dialog. * @param {HTMLElement} html - The HTML content of the dialog. * @param {Object} options - The options for the dialog. * @param {Function} close - The callback function for closing the dialog. */ static async create(data = { label: null, rolltype: null, NoD: 1, Reroll: false, actorId: game.user.character?.id || canvas.tokens.controlled[0]?.actor.id }) { // Retrieve the actor data based on the actorId data.actor = await game.actors.get(data.actorId); if (!data.actor) { return await ui.notifications.warn("Vous n'avez pas de personnage attitré ou de token selectionné"); } console.log(data.actor) data.availableSpecialties = data.actor.items.filter(it => it.type == "specialty"); console.log(data.availableSpecialties) data.availableItems = data.actor.items.filter(it => it.type == "item"); data.config = CONFIG.VERMINE; // Define options for the dialog let options = { classes: ["vermineDialog"], width: "fit-content", height: 'fit-content', 'z-index': 99999 }; // Render the HTML template for the dialog let html = await renderTemplate('systems/vermine2047/templates/dialogs/roll-dialog.hbs', data); // Return a new RollDialog instance with the provided data, HTML, and options return new RollDialog(data, html, options); } /** * Retrieves the default options for the RollDialog. */ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { focus: true, classes: ["dialog vermine-roll"], }); } /** * Retrieves the data for the dialog. * @returns {Object} The context data for the dialog. */ getData() { // Get the context data from the superclass let context = super.getData(); context.data = this.data; context.config = CONFIG.VERMINE; // Return the context data return context; } prepareItems() { return this.data.actor.items.filter(it => it.type == "item") } prepareSpecialties() { return this.data.actor.items.filter(it => it.type == "specialty") } /** * Activates event listeners for the dialog. * @param {HTMLElement} html - The HTML element of the dialog. */ async activateListeners(html) { // Activate event listeners from the superclass super.activateListeners(html); // Initialize UI elements this._html = html; // Retrieve roll data and set up event listeners await this.getRollData(); // Set up event listeners for all roll-related inputs let rollInputs = html.find('[data-roll]'); for (let inp of rollInputs) { inp.addEventListener('change', this._onRollInputChange.bind(this)); }; this.displaySpecialties(); let selectAbil = html.find('#ability')[0]; // Set the maximum value for self control based on ability value html.find("#self_control")[0].max = selectAbil.value; selectAbil.addEventListener('change', this._onChangeAbility.bind(this)); let selfControl = html.find('#self_control')[0] // Add event listener for self control changes selfControl.addEventListener('change', this._onChangeSelfControl.bind(this)); // Set up difficulty change listener html.find('#difficulty')[0].addEventListener('change', this._onDifficultyChange.bind(this)); // Set up handicap change listener html.find('#handicap')[0].addEventListener('change', this._onHandicapChange.bind(this)); // Set up totem checkbox listeners html.find('#human-totem')[0]?.addEventListener('change', this._onTotemChange.bind(this)); html.find('#adapted-totem')[0]?.addEventListener('change', this._onTotemChange.bind(this)); // Initial update of all UI elements this._updateUI(); }; /** * Retrieves the roll data for the dialog. * @param {Event} ev - The event triggering the roll data retrieval. */ async getRollData(ev) { // Calculate and store the roll data this.rollData = { actor: this.data.actor, NoD: this.getDicePool(), Reroll: this.getReroll(), difficulty: this.getDifficulty(), handicap: this.getHandicap(), rollType: this.getRollType(), rollLabel: this.getLabel(), totems: this.getTotems(), self_control: this.getSelfControl(), max_effort: this.getMaxEffort(), keepTotem: this.getKeepTotem(), skillCategory: this.getSkillCategory() } this.displaySpecialties(); this._updateUI(); }; /** * Gets the selected skill category * @returns {string|null} - The skill category */ getSkillCategory() { const html = this.element[0]; const skillSelect = html.querySelector('#skill'); if (skillSelect && skillSelect.selectedIndex > 0) { const selectedOption = skillSelect.options[skillSelect.selectedIndex]; return selectedOption.dataset.category || null; } return null; } /** * Gets the selected skill level * @returns {number|null} - The skill level */ getSkillLevel() { const html = this.element[0]; const skillSelect = html.querySelector('#skill'); if (skillSelect && skillSelect.selectedIndex > 0) { const selectedOption = skillSelect.options[skillSelect.selectedIndex]; return parseInt(selectedOption.value) || null; } return null; } /** * Checks if a specialty is selected * @returns {boolean} - True if a specialty is selected */ hasSpecialtySelected() { const html = this.element[0]; const specialtyRadio = html.querySelector('input[name="usingSpecialization"]:checked'); return specialtyRadio && specialtyRadio.value !== 'aucune'; } /** * Handles changes to roll inputs and updates UI * @param {Event} ev - The change event */ async _onRollInputChange(ev) { await this.getRollData(); } /** * Updates all UI elements based on current roll data */ _updateUI() { if (!this._html) return; const html = this._html[0]; // Update total dice pool display const totalDice = this.getDicePool(); const totalEl = html.querySelector('#dice-pool-total'); if (totalEl) { totalEl.textContent = `${totalDice}D`; } // Update bonus count const bonusCount = this._calculateBonusCount(); const bonusEl = html.querySelector('#total-bonus'); if (bonusEl) { bonusEl.textContent = bonusCount; } // Update difficulty display const difficultyEl = html.querySelector('#current-difficulty'); const difficultySelect = html.querySelector('#difficulty'); if (difficultyEl && difficultySelect) { const selectedIndex = difficultySelect.selectedIndex; const diffValue = parseInt(difficultySelect.options[selectedIndex].value); const diffLabel = difficultySelect.options[selectedIndex].text.split(' ')[0]; difficultyEl.textContent = `${diffLabel} (${diffValue})`; } // Update handicap display const handicapEl = html.querySelector('#current-handicap'); const handicapSelect = html.querySelector('#handicap'); if (handicapEl && handicapSelect) { const selectedIndex = handicapSelect.selectedIndex; handicapEl.textContent = handicapSelect.options[selectedIndex].text; } // Update ability score display const abilSelect = html.querySelector('#ability'); const abilScoreEl = html.querySelector('#abilityScoreValue'); if (abilSelect && abilScoreEl) { const selectedIndex = abilSelect.selectedIndex; if (selectedIndex > 0) { abilScoreEl.textContent = abilSelect.options[selectedIndex].value; } else { abilScoreEl.textContent = '0'; } } // Update specialty display const specialtyRadios = html.querySelectorAll('input[name="usingSpecialization"]:checked'); const currentSpecEl = html.querySelector('.current-specialty'); if (currentSpecEl && specialtyRadios.length > 0) { const checkedRadio = specialtyRadios[0]; currentSpecEl.textContent = checkedRadio.value === 'aucune' ? game.i18n.localize('VERMINE.none') : checkedRadio.value; } } /** * Calculates the bonus count for display * @returns {number} - Total bonus dice */ _calculateBonusCount() { let bonus = 0; // Help bonus if (this._html.find('#helped')[0]?.checked) { bonus += 1; } // Group bonus const groupValue = parseInt(this._html.find('#group')[0]?.value) || 0; bonus += groupValue; // Self control bonus const selfControlValue = parseInt(this._html.find('#self_control')[0]?.value) || 0; bonus += selfControlValue; // Tools bonus const toolsChecked = this._html.find('input[name="usingTools"]:checked')[0]?.value !== '0'; if (toolsChecked) { bonus += 1; } // Totems bonus if (this._html.find('#human-totem')[0]?.checked) { bonus += parseInt(this.data.actor.system.adaptation.totems.human.value) || 0; } if (this._html.find('#adapted-totem')[0]?.checked) { bonus += parseInt(this.data.actor.system.adaptation.totems.adapted.value) || 0; } // Specialty bonus const specialtyChecked = this._html.find('input[name="usingSpecialization"]:checked')[0]?.value !== 'aucune'; if (specialtyChecked) { bonus += 1; } return bonus; } /** * Handles difficulty change * @param {Event} ev - The change event */ _onDifficultyChange(ev) { this._updateUI(); } /** * Handles handicap change * @param {Event} ev - The change event */ _onHandicapChange(ev) { this._updateUI(); } /** * Handles totem checkbox change * @param {Event} ev - The change event */ _onTotemChange(ev) { this._updateUI(); } /** * Gets the selected totem to keep (for dual totem rolls) * @returns {string|null} - The totem to keep ('human', 'adapted', or null) */ getKeepTotem() { const keepTotemSelect = this._html?.find('#keep-totem-select')[0]; if (keepTotemSelect) { return keepTotemSelect.value; } // Default to null (both totems used) return null; } /** * Handles the change in self control value. * @param {Event} ev - The event triggering the change in self control value. */ _onChangeSelfControl(ev) { let html = this.element[0]; // Update the displayed self control value based on the event html.querySelector('#self_control_value').innerText = ev.currentTarget.value; }; getHandicap() { let html = this.element[0]; // Parse and return the self control value from the HTML element let handicap = parseInt(html.querySelector('#handicap').value) return handicap } getRollType() { let html = this.element[0]; // Update the displayed self control value based on the event if (html.querySelector('select#skill').value) { return "skill" } return "ability" } getLabel() { let html = this.element[0]; if (this.getRollType() == "skill") { return html.querySelector('select#skill').options[html.querySelector('select#skill').selectedIndex].dataset.label } return html.querySelector('select#ability').options[html.querySelector('select#ability').selectedIndex].dataset.label } displaySpecialties() { let specialties = this.element[0].querySelectorAll('[data-spec-skill]'); for (let specEl of specialties) { specEl.style.display = "inline" } } /** * Retrieves the self control value from the HTML element. * @returns {number} The self control value. */ getSelfControl() { let html = this.element[0]; // Parse and return the self control value from the HTML element let selfControl = parseInt(html.querySelector('#self_control').value) return selfControl } /** * Retrieves the maximum effort value from the HTML element. * @returns {number} The maximum effort value. */ getMaxEffort() { let html = this.element[0]; // Retrieve and return the maximum effort value from the HTML element return parseInt(html.querySelector('#ability').value); } /** * Retrieves the selected totems from the HTML element. * @returns {Object} An object containing the selected totems. */ getTotems() { let html = this.element[0]; // Check and store the status of human and adapted totems let totems = { human: html.querySelector('#human-totem')?.checked, adapted: html.querySelector('#adapted-totem')?.checked, } return totems } /** * Handles the change in ability value. * @param {Event} ev - The event triggering the change in ability value. */ _onChangeAbility(ev) { let html = this.element[0]; // Retrieve the selected ability score and update related elements let score = html.querySelector('#ability').options[html.querySelector('#ability').selectedIndex].value; // Check if the score is a number, otherwise set it to 0 if (!typeof score == "number") { score = 0 } html.querySelector('#abilityScore').value = score; html.querySelector('#self_control').max = score; } /** * Retrieves the total dice pool based on various factors. * @returns {number} The total dice pool value. */ getDicePool() { // Retrieve the HTML element let html = this.element[0]; // Get the ability value or set to 0 if not found let abilValue = html.querySelector('#ability').options[html.querySelector('#ability').selectedIndex].value || 0; // Get the skill value or set to 0 if not found let skillValue = html.querySelector('#skill').options[html.querySelector('#skill').selectedIndex].dataset.pool || 0; // Get the self control value let selfControl = html.querySelector('#self_control').value; // Calculate bonuses based on certain conditions console.log(html.querySelector('#usingTools').checked) let bonuses = (html.querySelector('#usingSpecialization')?.checked ? 1 : 0) + (html.querySelector('#helped').checked ? 1 : 0) + (html.querySelector('#usingTools').checked ? 1 : 0); // Calculate the total dice pool let total = parseInt(abilValue) + parseInt(selfControl) + parseInt(skillValue) + bonuses; return total || 0; } /** * Retrieves the reroll value based on selected skill. * @returns {number} The reroll value. */ getReroll() { // Retrieve the HTML element let html = this.element[0]; // Get the selected skill index let selected = html.querySelector('#skill').selectedIndex; // Get the reroll value from the selected skill or set to 0 if not found let reroll = html.querySelector('#skill').options[selected].dataset.reroll || 0; return parseInt(reroll) || 0; } /** * Retrieves the difficulty value based on selected option. * @returns {number} The difficulty value. */ getDifficulty() { // Retrieve the HTML element let html = this.element[0]; // Get the selected index for difficulty let selected = html.querySelector('#difficulty').selectedIndex; // Get the difficulty value from the selected option or set to 0 if not found let diff = html.querySelector('#difficulty').options[selected].value || 0; return parseInt(diff) || 0; } /** * Performs a dice roll based on the roll data and handles self control checks. * @returns {Promise} A promise that resolves with the result of the dice roll. */ async _onRoll() { // Check if self control is required for the roll if (this.rollData.self_control > 0) { // Check if the actor has enough self control if (this.rollData.actor.system.attributes.self_control.value < this.rollData.self_control) { // Display a warning message if self control is insufficient ui.notifications.warn(game.i18n.localize('VERMINE.error_not_enough_self_control')); // Re-render the dialog this.render(true); return false; // Exit the function if self control is insufficient } } let caracName = this.element[0].querySelector('[name="ability"]')?.value if (caracName == "0" || caracName === undefined) { // Display a warning message if no ability selected ui.notifications.warn(game.i18n.localize('VERMINE.error_select_ability')); // Re-render the dialog this.render(true); return false; // Exit the function if no ability } // Deduct self control points if necessary if (this.rollData.self_control > 0) { // Update the actor's self control value await this.rollData.actor.update({ "system.attributes.self_control.value": this.rollData.actor.system.attributes.self_control.value - this.rollData.self_control }); } // Perform the dice roll using VermineUtils return VermineUtils.roll({ ...this.rollData, skillLevel: this.getSkillLevel(), hasSpecialty: this.hasSpecialtySelected() }); } }