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()
});
}
}