1004 lines
36 KiB
JavaScript
1004 lines
36 KiB
JavaScript
/* -------------------------------------------- */
|
||
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(`<option value="${i}">${i}</option>`)
|
||
}
|
||
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(`<option value="${i}">${i}</option>`)
|
||
}
|
||
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 += `<option value="${i}">${i}</option>`
|
||
}
|
||
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 + "<br>" + 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: "<i class='fas fa-user-plus'></i>",
|
||
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: "<i class='fas fa-user-plus'></i>",
|
||
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: "<i class='fas fa-user-plus'></i>",
|
||
condition: canApply && hasBA2,
|
||
callback: li => WastelandUtility.incDecSante(li, 2, 2)
|
||
}
|
||
)
|
||
options.push(
|
||
{
|
||
name: "Relancer le jet (3 points de Bonne Aventure)",
|
||
icon: "<i class='fas fa-user-plus'></i>",
|
||
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: "<i class='fas fa-user-plus'></i>",
|
||
condition: canApply && hasBA3,
|
||
callback: li => WastelandUtility.applyBonneAventureRoll(li, -3, "newaction")
|
||
}
|
||
)
|
||
options.push(
|
||
{
|
||
name: "Ajouter +10 (1 Point d'Eclat)",
|
||
icon: "<i class='fas fa-user-plus'></i>",
|
||
condition: canApply && hasPE,
|
||
callback: li => WastelandUtility.applyEclatRoll(li, -1, "+10")
|
||
}
|
||
)
|
||
options.push(
|
||
{
|
||
name: "Double le résultat du d20 (1 Point d'Eclat)",
|
||
icon: "<i class='fas fa-user-plus'></i>",
|
||
condition: canApply && hasPE,
|
||
callback: li => WastelandUtility.applyEclatRoll(li, -1, "double20")
|
||
}
|
||
)
|
||
options.push(
|
||
{
|
||
name: "Annuler une blessure (1 Point d'Eclat)",
|
||
icon: "<i class='fas fa-user-plus'></i>",
|
||
condition: canApply && hasPE,
|
||
callback: li => WastelandUtility.cancelBlessure(li)
|
||
}
|
||
)
|
||
options.push(
|
||
{
|
||
name: "Recharger ses points de BA (1 Point d'Eclat)",
|
||
icon: "<i class='fas fa-user-plus'></i>",
|
||
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: `<p>Êtes-vous sûr de vouloir supprimer <strong>${item.name}</strong> ?</p>`,
|
||
rejectClose: false,
|
||
modal: true,
|
||
yes: { label: "Supprimer", icon: "fa-trash" },
|
||
no: { label: "Annuler", icon: "fa-times" }
|
||
})
|
||
|
||
if (confirmed) {
|
||
await actorSheet.document.deleteEmbeddedDocuments("Item", [itemId])
|
||
}
|
||
}
|
||
|
||
} |