/* -------------------------------------------- */
import { WastelandCombat } from "./wasteland-combat.js";
import { WastelandCommands } from "./wasteland-commands.js";
/* -------------------------------------------- */
const __contrecouptCharme = {
1 : {name: "Effet chromatique", description: "le corps du kobold prend des teintes aussi étranges que voyantes. L'effet s’estompe progressivement et 24 heures plus tard, le kobold retrouve ses couleurs d’origine." },
3 : {name: "Enivrement Kobold", description: "très excité par son premier tour, le kobold doit immédiatement faire un autre tour, pour lequel il emploiera un dé plus gros." },
5 : {name: "Mutisme superstitieux", description: "le kobold ne doit plus parler» pendant les prochaines 24 heures. S'il le fait malgré tout, les effets de son tour s’arrêtent." },
7 : {name: "Agité!", description: "le kobold ne tient plus en place. Il ne peut se reposer pendant les prochaines 12 heures. Dès. que 12 heures se sont écoulées, il s'effondre comme une masse et dort 12 heures d'affilée d’un sommeil enchanté dont rien ne pourra le réveiller." },
9 : {name: "Somnolence", description: "le kobold devient somnolent. Il n’arrive pas à se concentrer même sur une tâche simple, bäille sans arrêt, traîne les pieds et n’agit plus que de mauvaise grâce. Cela dure jusqu’à ce qu'il ait dormi au moins 12 heures." },
11 : {name: "Manie incontrôlable", description: "le kobold est pris d’une manie incontrôlable. Dès qu'il voit un chapeau rouge, il doit suivre son porteur. Il ne mangera que si son voisin de gauche mange aussi, etc. Cela dure pendant une jour- née puis l’effet s’inverse pendant une heure : il ne suivra jamais un chapeau rouge, ne mangera jamais si son voi- sin de gauche mange, etc. Le contrecoup prend alors fin." },
13 : {name: "Malédiction des Ternes", description: "le kobold perd cette qualité mystérieuse qui fait que les kobolds sont des kobolds et devient tout. Terne. Il perd 1d20 point(s) de Bonne Aventure (s’il doit en perdre plus qu'il n’en a, il tombe simplement à 0). Ces points perdus pourront cependant être regagnés normalement." },
15 : {name: "La petite Mort", description: "le kobold s'endort pour 1420 heures. Rien ni personne ne pourra le tirer de ce sommeil enchanté avant que ce contrecoup ne prenne fin." },
17 : {name: "Angoisse cauchemardesque", description: "le kobold a une brève vision de pure horreur. Il perd 1420 points de Psyché {s'il doit en perdre plus qu'il n’en a, il tombe à 0)." },
19 : {name: "Anémie Kobold", description: "le kobold se met à saigner du nez, des oreilles et même d’autres endroits. Il perd 1420 point(s) de Santé." }
}
/* -------------------------------------------- */
export class WastelandUtility {
/* -------------------------------------------- */
static async init() {
Hooks.on('renderChatLog', (log, html, data) => WastelandUtility.chatListeners(html))
Hooks.on('renderChatMessageHTML', (message, html, data) => WastelandUtility.chatListeners(html))
Hooks.on("getChatMessageContextOptions", (html, options) => WastelandUtility.chatRollMenu(html, options))
Hooks.on("getCombatTrackerEntryContext", (html, options) => {
WastelandUtility.pushInitiativeOptions(html, options);
})
this.rollDataStore = {}
this.defenderStore = {}
WastelandCommands.init();
Handlebars.registerHelper('count', function (list) {
return list.length;
})
Handlebars.registerHelper('includes', function (array, val) {
return array.includes(val);
})
Handlebars.registerHelper('upper', function (text) {
return text.toUpperCase();
})
Handlebars.registerHelper('lower', function (text) {
return text.toLowerCase()
})
Handlebars.registerHelper('upperFirst', function (text) {
if (typeof text !== 'string') return text
return text.charAt(0).toUpperCase() + text.slice(1)
})
Handlebars.registerHelper('notEmpty', function (list) {
return list.length > 0;
})
Handlebars.registerHelper('mul', function (a, b) {
return parseInt(a) * parseInt(b);
})
}
/* -------------------------------------------- */
static getActorFromRollData(rollData) {
let actor = game.actors.get(rollData.actorId)
if (rollData.tokenId) {
let token = canvas.tokens.placeables.find(t => t.id == rollData.tokenId)
if (token) {
actor = token.actor
}
}
return actor
}
/* -------------------------------------------- */
static getModificateurOptions() {
let opt = []
for (let i = -15; i <= 15; i++) {
opt.push(``)
}
return opt.concat("\n")
}
/* -------------------------------------------- */
static sortArrayObjectsByName(myArray) {
myArray.sort((a, b) => {
return a.name.localeCompare(b.name);
})
}
/* -------------------------------------------- */
static getPointAmeOptions() {
let opt = []
for (let i = 1; i <= 20; i++) {
opt.push(``)
}
return opt.concat("\n")
}
/* -------------------------------------------- */
static getAttributs() {
return { adr: "Adresse", pui: "Puissance", cla: "Clairvoyance", pre: "Présence", tre: "Trempe" }
}
/* -------------------------------------------- */
static pushInitiativeOptions(html, options) {
}
/* -------------------------------------------- */
static getSkills() {
return this.skills
}
/* -------------------------------------------- */
static async ready() {
const skills = await WastelandUtility.loadCompendium("fvtt-wasteland.skills")
this.skills = skills.map(i => i.toObject())
// Note: listeNiveauSkill, listeNiveauCreature, etc. are now created in init hook
// to be available immediately when sheets are opened
}
/* -------------------------------------------- */
static createDirectOptionList(min, max) {
let options = {};
for (let i = min; i <= max; i++) {
options[`${i}`] = `${i}`;
}
return options;
}
static createArrayOptionList(min, max) {
let options = [];
for (let i = min; i <= max; i++) {
options.push({key:`${i}`, label:`${i}`});
}
return options;
}
/* -------------------------------------------- */
static async loadCompendiumData(compendium) {
const pack = game.packs.get(compendium);
return await pack?.getDocuments() ?? [];
}
/* -------------------------------------------- */
static async loadCompendium(compendium, filter = item => true) {
let compendiumData = await WastelandUtility.loadCompendiumData(compendium);
return compendiumData.filter(filter);
}
/* -------------------------------------------- */
static getOptionsStatusList() {
return this.optionsStatusList;
}
/* -------------------------------------------- */
static async chatListeners(html) {
$(html).on("click", '.predilection-reroll', async event => {
let predIdx = $(event.currentTarget).data("predilection-index")
let messageId = WastelandUtility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData)
// Sauvegarder le résultat précédent
let previousResult = {
diceResult: rollData.diceResult,
finalResult: rollData.finalResult,
diceFormula: rollData.diceFormula
}
// Récupérer la prédilection utilisée
let predilectionUsed = foundry.utils.duplicate(rollData.competence.system.predilections[predIdx])
// Marquer la prédilection comme utilisée
await actor.setPredilectionUsed(rollData.competence._id, predIdx)
// Mettre à jour la compétence dans rollData
rollData.competence = foundry.utils.duplicate(actor.getCompetence(rollData.competence._id))
// Lancer le reroll avec prédilection
await WastelandUtility.rollWastelandPredilection(rollData, predilectionUsed, previousResult)
})
$(html).on("click", '.arme-roll-degats', async event => {
let messageId = WastelandUtility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "wasteland-roll")
if (rollData && rollData.arme) {
let actor = WastelandUtility.getActorFromRollData(rollData)
await actor.rollArmeDegats(rollData.arme._id)
}
})
}
/* -------------------------------------------- */
static async preloadHandlebarsTemplates() {
const templatePaths = [
'systems/fvtt-wasteland/templates/editor-notes-gm.hbs',
'systems/fvtt-wasteland/templates/partial-item-description.hbs',
'systems/fvtt-wasteland/templates/partial-item-header.hbs',
'systems/fvtt-wasteland/templates/partial-item-nav.hbs'
]
return foundry.applications.handlebars.loadTemplates(templatePaths);
}
/* -------------------------------------------- */
static removeChatMessageId(messageId) {
if (messageId) {
game.messages.get(messageId)?.delete();
}
}
static findChatMessageId(current) {
return WastelandUtility.getChatMessageId(WastelandUtility.findChatMessage(current));
}
static getChatMessageId(node) {
return node?.attributes.getNamedItem('data-message-id')?.value;
}
static findChatMessage(current) {
return WastelandUtility.findNodeMatching(current, it => it.classList.contains('chat-message') && it.attributes.getNamedItem('data-message-id'))
}
static findNodeMatching(current, predicate) {
if (current) {
if (predicate(current)) {
return current;
}
return WastelandUtility.findNodeMatching(current.parentElement, predicate);
}
return undefined;
}
/* -------------------------------------------- */
static buildListOptions(min, max) {
let options = ""
for (let i = min; i <= max; i++) {
options += ``
}
return options;
}
/* -------------------------------------------- */
static getTarget() {
if (game.user.targets && game.user.targets.size == 1) {
for (let target of game.user.targets) {
return target;
}
}
return undefined;
}
/* -------------------------------------------- */
static updateRollData(rollData) {
let id = rollData.rollId;
let oldRollData = this.rollDataStore[id] || {};
let newRollData = foundry.utils.mergeObject(oldRollData, rollData);
this.rollDataStore[id] = newRollData;
}
/* -------------------------------------------- */
static saveRollData(rollData) {
game.socket.emit("system.fvtt-wasteland", {
name: "msg_update_roll", data: rollData
}); // Notify all other clients of the roll
this.updateRollData(rollData);
}
/* -------------------------------------------- */
static getRollData(id) {
return this.rollDataStore[id];
}
/* -------------------------------------------- */
static onSocketMesssage(msg) {
if (msg.name == "msg_update_defense_state") {
this.updateDefenseState(msg.data.defenderId, msg.data.rollId);
}
if (msg.name == "msg_update_roll") {
this.updateRollData(msg.data);
}
}
/* -------------------------------------------- */
static chatDataSetup(content, modeOverride, isRoll = false, forceWhisper) {
let chatData = {
user: game.user.id,
rollMode: modeOverride || game.settings.get("core", "rollMode"),
content: content
};
if (["gmroll", "blindroll"].includes(chatData.rollMode)) chatData["whisper"] = ChatMessage.getWhisperRecipients("GM").map(u => u.id);
if (chatData.rollMode === "blindroll") chatData["blind"] = true;
else if (chatData.rollMode === "selfroll") chatData["whisper"] = [game.user];
if (forceWhisper) { // Final force !
chatData["speaker"] = ChatMessage.getSpeaker();
chatData["whisper"] = ChatMessage.getWhisperRecipients(forceWhisper);
}
return chatData;
}
/* -------------------------------------------- */
static async showDiceSoNice(roll, rollMode) {
if (game.modules.get("dice-so-nice")?.active) {
if (game.dice3d) {
let whisper = null;
let blind = false;
rollMode = rollMode ?? game.settings.get("core", "rollMode");
switch (rollMode) {
case "blindroll": //GM only
blind = true;
case "gmroll": //GM + rolling player
whisper = this.getUsers(user => user.isGM);
break;
case "roll": //everybody
whisper = this.getUsers(user => user.active);
break;
case "selfroll":
whisper = [game.user.id];
break;
}
await game.dice3d.showForRoll(roll, game.user, true, whisper, blind);
}
}
}
/* -------------------------------------------- */
static async computeResult(rollData, actor) {
if (rollData.charme) {
let resultIndex = false
let resTab = foundry.utils.duplicate(rollData.charme.system.resultats)
for(let id in resTab) {
let res = resTab[id]
if (!resultIndex && rollData.finalResult >= res.value) {
resultIndex = id;
}
}
if (resultIndex) {
rollData.charmeDuree = rollData.charme.system.resultats[resultIndex].description
}
let effectRoll = await new Roll(rollData.charmeDice).roll()
if (rollData.charme.system.charmetype == "tour") {
rollData.contrecoupResult = effectRoll.total
if (rollData.contrecoupResult % 2 == 1) {
rollData.contrecoup = __contrecouptCharme[rollData.contrecoupResult]
}
}
if (rollData.charme.system.charmetype == "charme") {
rollData.charmeSante = effectRoll.total
actor.incDecSante(rollData.charmeSante)
}
} else {
if (rollData.mainDice == "1d20") {
let diceValue = rollData.roll.terms[0].results[0].result
diceValue *= (rollData.doubleD20) ? 2 : 1
//console.log("PAIR/IMP", diceValue)
if (diceValue % 2 == 1) {
//console.log("PAIR/IMP2", diceValue)
rollData.finalResult -= rollData.roll.terms[0].results[0].result // Substract value
if (diceValue == 1 || diceValue == 11) {
rollData.isDramatique = true
rollData.isSuccess = false
}
}
}
//console.log("Result : ", rollData)
if (rollData.difficulte > 0 && !rollData.isDramatique) {
rollData.isSuccess = (rollData.finalResult >= rollData.difficulte)
rollData.isHeroique = ((rollData.finalResult - rollData.difficulte) >= 10)
rollData.isDramatique = ((rollData.finalResult - rollData.difficulte) <= -10)
}
}
}
/* -------------------------------------------- */
static async rollWasteland(rollData) {
let actor = WastelandUtility.getActorFromRollData(rollData)
if (rollData.attrKey == "tochoose") { // No attr selected, force address
rollData.attrKey = "adr"
}
if (!rollData.attr && actor && actor.system.attributs && actor.system.attributs[rollData.attrKey]) {
rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey])
if (rollData.attr.labelnorm) {
rollData.actionImg = "systems/fvtt-wasteland/assets/icons/" + rollData.attr.labelnorm + ".webp"
}
}
if (rollData.charme) {
rollData.diceFormula = rollData.charmeDice
} else {
rollData.diceFormula = rollData.mainDice
// Double D20 ne s'applique que si on lance un D20, pas un D10
if (rollData.doubleD20 && rollData.mainDice === "1d20") {
rollData.diceFormula += "*2"
if (!rollData.isReroll) {
actor.changeEclat(-1)
}
}
}
//console.log("BEFORE COMP", rollData)
if (rollData.competence) {
rollData.predilections = foundry.utils.duplicate(rollData.competence.system.predilections.filter(pred => !pred.used) || [])
let compmod = (rollData.competence.system.niveau == 0) ? -3 : 0
let attrValue = rollData.attr?.value || 0
rollData.diceFormula += `+${attrValue}+${rollData.competence.system.niveau}+${rollData.modificateur}+${compmod}`
} else {
let attrValue = rollData.attr?.value || 0
rollData.diceFormula += `+${attrValue}*2+${rollData.modificateur}`
}
if (rollData.arme && rollData.arme.type == "arme") {
rollData.diceFormula += `+${rollData.arme.system.bonusmaniementoff}`
}
// Apply desavantages (tactical advantages)
let desavantagesBonus = 0
if (rollData.desavantages) {
for (let desavantage in rollData.desavantages) {
if (rollData.desavantages[desavantage]) {
desavantagesBonus += 5
}
}
desavantagesBonus = Math.min(15, desavantagesBonus)
if (desavantagesBonus > 0) {
rollData.diceFormula += `+${desavantagesBonus}`
}
}
// Monté ?
if (rollData.isMonte) {
rollData.diceFormula += "+5"
}
// Specific modifiers for ranged combat
if (rollData.arme?.system?.isDistance) {
if (rollData.visee) {
rollData.diceFormula += "+5"
}
if (rollData.cibleconsciente && rollData.defender) {
rollData.diceFormula += `-${rollData.defender.system.attributs.adr.value}`
}
if (rollData.ciblecourt) {
if (rollData.difficulte <= 15) { // Portée courte ou moins
rollData.diceFormula += `-5`
} else {
rollData.diceFormula += `-10`
}
}
if (rollData.typeCouvert && rollData.typeCouvert != "aucun" && rollData.config.couverts[rollData.typeCouvert]) {
rollData.diceFormula += `+${rollData.config.couverts[rollData.typeCouvert].value}`
}
}
let myRoll = await new Roll(rollData.diceFormula).roll()
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
rollData.roll = foundry.utils.duplicate(myRoll)
rollData.diceResult = myRoll.terms[0].results[0].result
console.log(">>>> ", myRoll)
rollData.finalResult = myRoll.total
await this.computeResult(rollData, actor)
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-wasteland/templates/chat-generic-result-v2.hbs`, rollData)
}, rollData)
}
/* -------------------------------------------- */
static async bonusRollWasteland(rollData) {
rollData.bonusFormula = rollData.addedBonus
let bonusRoll = await new Roll(rollData.bonusFormula).roll()
await this.showDiceSoNice(bonusRoll, game.settings.get("core", "rollMode"));
rollData.bonusRoll = foundry.utils.duplicate(bonusRoll)
rollData.finalResult += rollData.bonusRoll.total
await this.computeResult(rollData)
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-wasteland/templates/chat-generic-result-v2.hbs`, rollData)
}, rollData)
}
/* -------------------------------------------- */
static async rollWastelandPredilection(rollData, predilectionUsed, previousResult) {
// Sauvegarder le premier résultat
rollData.firstRoll = {
diceResult: previousResult.diceResult,
finalResult: previousResult.finalResult,
diceFormula: previousResult.diceFormula
}
rollData.predilectionUsed = predilectionUsed
rollData.isPredilectionReroll = true
let actor = WastelandUtility.getActorFromRollData(rollData)
if (rollData.attrKey == "tochoose") {
rollData.attrKey = "adr"
}
if (!rollData.attr && actor && actor.system.attributs && actor.system.attributs[rollData.attrKey]) {
rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey])
if (rollData.attr.labelnorm) {
rollData.actionImg = "systems/fvtt-wasteland/assets/icons/" + rollData.attr.labelnorm + ".webp"
}
}
if (rollData.charme) {
rollData.diceFormula = rollData.charmeDice
} else {
rollData.diceFormula = rollData.mainDice
// Double D20 ne s'applique que si on lance un D20, pas un D10
if (rollData.doubleD20 && rollData.mainDice === "1d20") {
rollData.diceFormula += "*2"
}
}
if (rollData.competence) {
rollData.predilections = foundry.utils.duplicate(rollData.competence.system.predilections.filter(pred => !pred.used) || [])
let compmod = (rollData.competence.system.niveau == 0) ? -3 : 0
let attrValue = rollData.attr?.value || 0
rollData.diceFormula += `+${attrValue}+${rollData.competence.system.niveau}+${rollData.modificateur}+${compmod}`
} else {
let attrValue = rollData.attr?.value || 0
rollData.diceFormula += `+${attrValue}*2+${rollData.modificateur}`
}
if (rollData.arme && rollData.arme.type == "arme") {
rollData.diceFormula += `+${rollData.arme.system.bonusmaniementoff}`
}
// Apply desavantages (tactical advantages)
if (rollData.desavantages) {
let totalDesavantage = 0
for (let key in rollData.desavantages) {
if (rollData.desavantages[key]) {
totalDesavantage += 5
}
}
totalDesavantage = Math.min(totalDesavantage, 15)
if (totalDesavantage > 0) {
rollData.diceFormula += `+${totalDesavantage}`
}
}
// Apply mounted bonus
if (rollData.isMonte) {
rollData.diceFormula += `+5`
}
// Apply ranged combat modifiers
if (rollData.arme && rollData.arme.system && rollData.arme.system.isDistance) {
if (rollData.visee) {
rollData.diceFormula += `+5`
}
if (rollData.cibleconsciente && rollData.defender) {
let adrMalus = -(rollData.defender.system?.attributs?.adr?.value || 0)
rollData.diceFormula += `${adrMalus}`
}
if (rollData.ciblecourt) {
if (rollData.portee === "portee-courte" || rollData.portee === "portee-moyenne") {
rollData.diceFormula += `-5`
} else {
rollData.diceFormula += `-10`
}
}
if (rollData.couvert && rollData.typeCouvertValue) {
rollData.diceFormula += `${rollData.typeCouvertValue}`
}
}
// Nouveau jet de dé
let myRoll = await new Roll(rollData.diceFormula).roll()
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
rollData.roll = foundry.utils.duplicate(myRoll)
rollData.diceResult = myRoll.total
rollData.bonusFormula = "0"
if (rollData.charme && rollData.charme.system.bonusmajeur > 0) {
rollData.bonusFormula = `${rollData.charme.system.bonusmajeur}d6`
rollData.textBonus = "Bonus de Charme"
}
rollData.finalResult = rollData.diceResult
let bonusRoll = await new Roll(rollData.bonusFormula).roll()
await this.showDiceSoNice(bonusRoll, game.settings.get("core", "rollMode"))
rollData.bonusRoll = foundry.utils.duplicate(bonusRoll)
rollData.finalResult += rollData.bonusRoll.total
// Comparer avec le premier jet et garder le meilleur
rollData.secondRoll = {
diceResult: rollData.diceResult,
finalResult: rollData.finalResult
}
if (rollData.firstRoll.finalResult > rollData.finalResult) {
// Garder le premier résultat
rollData.diceResult = rollData.firstRoll.diceResult
rollData.finalResult = rollData.firstRoll.finalResult
rollData.keptRoll = "first"
} else {
// Garder le second résultat
rollData.keptRoll = "second"
}
await this.computeResult(rollData)
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-wasteland/templates/chat-generic-result-v2.hbs`, rollData)
}, rollData)
}
/* -------------------------------------------- */
static getUsers(filter) {
return game.users.filter(filter).map(user => user.data._id);
}
/* -------------------------------------------- */
static getWhisperRecipients(rollMode, name) {
switch (rollMode) {
case "blindroll": return this.getUsers(user => user.isGM);
case "gmroll": return this.getWhisperRecipientsAndGMs(name);
case "selfroll": return [game.user.id];
}
return undefined;
}
/* -------------------------------------------- */
static getWhisperRecipientsAndGMs(name) {
let recep1 = ChatMessage.getWhisperRecipients(name) || [];
return recep1.concat(ChatMessage.getWhisperRecipients('GM'));
}
/* -------------------------------------------- */
static blindMessageToGM(chatOptions) {
let chatGM = foundry.utils.duplicate(chatOptions);
chatGM.whisper = this.getUsers(user => user.isGM);
chatGM.content = "Blinde message of " + game.user.name + "
" + chatOptions.content;
console.log("blindMessageToGM", chatGM);
game.socket.emit("system.fvtt-weapons-of-the-gods", { msg: "msg_gm_chat_message", data: chatGM });
}
/* -------------------------------------------- */
static async searchItem(dataItem) {
let item;
if (dataItem.pack) {
item = await fromUuid("Compendium." + dataItem.pack + "." + dataItem.id);
} else {
item = game.items.get(dataItem.id)
}
return item
}
/* -------------------------------------------- */
static split3Columns(data) {
let array = [[], [], []];
if (data == undefined) return array;
let col = 0;
for (let key in data) {
let keyword = data[key];
keyword.key = key; // Self-reference
array[col].push(keyword);
col++;
if (col == 3) col = 0;
}
return array;
}
/* -------------------------------------------- */
static async createChatMessage(name, rollMode, chatOptions, rollData = undefined) {
switch (rollMode) {
case "blindroll": // GM only
if (!game.user.isGM) {
this.blindMessageToGM(chatOptions);
chatOptions.whisper = [game.user.id];
chatOptions.content = "Message only to the GM";
}
else {
chatOptions.whisper = this.getUsers(user => user.isGM);
}
break;
default:
chatOptions.whisper = this.getWhisperRecipients(rollMode, name);
break;
}
chatOptions.alias = chatOptions.alias || name
let msg = await ChatMessage.create(chatOptions)
console.log("=======>", rollData)
msg.setFlag("world", "wasteland-roll", rollData)
}
/* -------------------------------------------- */
static getBasicRollData() {
let rollData = {
rollId: foundry.utils.randomID(16),
rollMode: game.settings.get("core", "rollMode"),
modificateursOptions: this.getModificateurOptions(),
pointAmeOptions: this.getPointAmeOptions(),
difficulte: 0,
modificateur: 0,
config: game.system.wasteland.config,
}
WastelandUtility.updateWithTarget(rollData)
return rollData
}
/* -------------------------------------------- */
static updateWithTarget(rollData) {
let target = WastelandUtility.getTarget()
if (target) {
rollData.defenderTokenId = target.id
let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor
rollData.defender = defender.toObject() // For template access
rollData.armeDefense = defender.getBestDefenseValue()
if (rollData.armeDefense) {
rollData.difficulte = rollData.armeDefense.system.totalDefensif
} else {
ui.notifications.warn("Aucune arme de défense équipée, difficulté manuelle à positionner.")
}
}
}
/* -------------------------------------------- */
static createChatWithRollMode(name, chatOptions, rollData = undefined) {
this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions, rollData)
}
/* -------------------------------------------- */
static applyBonneAventureRoll(li, changed, addedBonus) {
let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData)
actor.changeBonneAventure(changed)
rollData.isReroll = true
rollData.textBonus = "Bonus de Points d'Aventure"
if (addedBonus == "reroll") {
WastelandUtility.rollWasteland(rollData)
} else {
rollData.addedBonus = addedBonus
WastelandUtility.bonusRollWasteland(rollData)
}
}
}
/* -------------------------------------------- */
static applyEclatRoll(li, changed, addedBonus) {
let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData)
actor.changeEclat(changed)
rollData.isReroll = true
rollData.textBonus = "Bonus d'Eclat"
rollData.addedBonus = addedBonus
WastelandUtility.bonusRollWasteland(rollData)
}
}
/* -------------------------------------------- */
static cancelBlessure(li) {
let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData)
// Vérifier que l'acteur a au moins 1 point d'éclat
if (actor.getEclat() < 1) {
ui.notifications.warn("Pas assez de points d'Éclat")
return
}
// Déduire 1 point d'éclat
actor.changeEclat(-1)
// Annuler la dernière blessure (incrémenter la santé)
let currentSante = actor.system.sante.value
let maxSante = actor.system.sante.max
if (currentSante < maxSante) {
actor.update({ 'system.sante.value': currentSante + 1 })
ui.notifications.info("Blessure annulée - 1 Point d'Éclat dépensé")
} else {
ui.notifications.warn("Vous êtes déjà en pleine santé")
// Rembourser le point d'éclat
actor.changeEclat(1)
}
}
}
/* -------------------------------------------- */
static reloadBA(li) {
let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData)
// Vérifier que l'acteur a au moins 1 point d'éclat
if (actor.getEclat() < 1) {
ui.notifications.warn("Pas assez de points d'Éclat")
return
}
// Déduire 1 point d'éclat
actor.changeEclat(-1)
// Recharger les points de Bonne Aventure au maximum
let maxBA = actor.system.bonneaventure.base
actor.update({ 'system.bonneaventure.actuelle': maxBA })
ui.notifications.info(`Points de Bonne Aventure rechargés à ${maxBA} - 1 Point d'Éclat dépensé`)
}
}
/* -------------------------------------------- */
static incDecSante(li, value, costBA) {
let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "wasteland-roll")
let actor = WastelandUtility.getActorFromRollData(rollData)
// Vérifier que l'acteur a assez de points de Bonne Aventure
if (actor.getBonneAventure() < costBA) {
ui.notifications.warn("Pas assez de points de Bonne Aventure")
return
}
// Déduire les points de Bonne Aventure
actor.changeBonneAventure(-costBA)
// Incrémenter la santé
actor.incDecSante(value)
ui.notifications.info(`+${value} Point(s) de Santé - ${costBA} Point(s) de Bonne Aventure dépensé(s)`)
}
}
/* -------------------------------------------- */
static chatRollMenu(html, options) {
let canApply = li => canvas.tokens.controlled.length && li.find(".wasteland-roll").length
let hasBA = function (li) {
let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll")
if (rollData?.actorId) {
let actor = game.actors.get(rollData.actorId)
return actor.getBonneAventure() > 0
}
return false
}
let hasBA2 = function (li) {
let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll")
if (rollData?.actorId) {
let actor = game.actors.get(rollData.actorId)
return actor.getBonneAventure() >= 2
}
return false
}
let hasBA3 = function (li) {
let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll")
if (rollData?.actorId) {
let actor = game.actors.get(rollData.actorId)
return actor.getBonneAventure() >= 3
}
return false
}
let hasPE = function (li) {
let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll")
if (rollData?.actorId) {
let actor = game.actors.get(rollData.actorId)
return actor.getEclat() >= 1
}
return false
}
let hasPredilection = function (li) {
let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll")
if (rollData.competence) {
let nbPred = rollData.competence.data.predilections.filter(pred => !pred.used).length
return (!rollData.isReroll && rollData.competence && nbPred > 0)
}
return false
}
let canCompetenceDouble = function (li) {
let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "wasteland-roll")
if (rollData.competence) {
return rollData.competence.data.doublebonus
}
return false
}
options.push(
{
name: "Ajouter +3 (1 point de Bonne Aventure)",
icon: "",
condition: canApply && hasBA,
callback: li => WastelandUtility.applyBonneAventureRoll(li, -1, "+3")
}
)
options.push(
{
name: "Gain de 1 Point de Santé / 24 heure (1 point de Bonne Aventure)",
icon: "",
condition: canApply && hasBA,
callback: li => WastelandUtility.incDecSante(li, 1, 1)
}
)
options.push(
{
name: "Gain de 2 Points de Santé / 24 heure (2 points de Bonne Aventure)",
icon: "",
condition: canApply && hasBA2,
callback: li => WastelandUtility.incDecSante(li, 2, 2)
}
)
options.push(
{
name: "Relancer le jet (3 points de Bonne Aventure)",
icon: "",
condition: canApply && hasBA3,
callback: li => WastelandUtility.applyBonneAventureRoll(li, -3, "reroll")
}
)
options.push(
{
name: "Bénéficier d'1 action supplémentaire (3 points de Bonne Aventure)",
icon: "",
condition: canApply && hasBA3,
callback: li => WastelandUtility.applyBonneAventureRoll(li, -3, "newaction")
}
)
options.push(
{
name: "Ajouter +10 (1 Point d'Eclat)",
icon: "",
condition: canApply && hasPE,
callback: li => WastelandUtility.applyEclatRoll(li, -1, "+10")
}
)
options.push(
{
name: "Double le résultat du d20 (1 Point d'Eclat)",
icon: "",
condition: canApply && hasPE,
callback: li => WastelandUtility.applyEclatRoll(li, -1, "double20")
}
)
options.push(
{
name: "Annuler une blessure (1 Point d'Eclat)",
icon: "",
condition: canApply && hasPE,
callback: li => WastelandUtility.cancelBlessure(li)
}
)
options.push(
{
name: "Recharger ses points de BA (1 Point d'Eclat)",
icon: "",
condition: canApply && hasPE,
callback: li => WastelandUtility.reloadBA(li)
}
)
return options
}
/* -------------------------------------------- */
static async confirmDelete(actorSheet, li) {
const itemId = li.dataset.itemId
if (!itemId) return
const item = actorSheet.document.items.get(itemId)
if (!item) return
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { title: "Confirmer la suppression" },
content: `
Êtes-vous sûr de vouloir supprimer ${item.name} ?
`, rejectClose: false, modal: true, yes: { label: "Supprimer", icon: "fa-trash" }, no: { label: "Annuler", icon: "fa-times" } }) if (confirmed) { await actorSheet.document.deleteEmbeddedDocuments("Item", [itemId]) } } }