fvtt-imperium5/modules/imperium5-utility.js

627 lines
22 KiB
JavaScript

/* -------------------------------------------- */
import { Imperium5Combat } from "./imperium5-combat.js";
import { Imperium5Commands } from "./imperium5-commands.js";
/* -------------------------------------------- */
const __level2Dice = ["d0", "d4", "d6", "d8", "d10", "d12"];
const __name2DiceValue = { "0": 0, "d0": 0, "d4": 4, "d6": 6, "d8": 8, "d10": 10, "d12": 12 }
/* -------------------------------------------- */
export class Imperium5Utility {
/* -------------------------------------------- */
static async init() {
Hooks.on('renderChatLog', (log, html, data) => Imperium5Utility.chatListeners(html));
Hooks.on("getChatLogEntryContext", (html, options) => Imperium5Utility.chatRollMenu(html, options))
Hooks.on("getCombatTrackerEntryContext", (html, options) => {
Imperium5Utility.pushInitiativeOptions(html, options);
})
this.rollDataStore = {}
this.defenderStore = {}
this.diceList = []
Imperium5Commands.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);
})
Handlebars.registerHelper('exists', function (val) {
return val != null && val != undefined;
})
Handlebars.registerHelper('for', function (from, to, incr, block) {
var accum = '';
for (var i = from; i < to; i += incr)
accum += block.fn(i);
return accum;
})
Handlebars.registerHelper('times', function (n, block) {
var accum = '';
for (var i = 1; i <= n; ++i)
accum += block.fn(i);
return accum;
})
}
/* -------------------------------------------- */
static registerSettings() {
game.settings.register("fvtt-imperium5", "use-entropie-reussite", {
name: "Utilisation du dé d'Entropie dans les réussites",
hint: "Si coché, ajoute 1 succès au joueur sur un 2, et 1 succés au MJ sur un 7.",
scope: "world",
config: true,
default: false,
type: Boolean
})
game.settings.register("fvtt-imperium5", "use-specialite", {
name: "Utilisation complémentaire des Spécialités",
hint: "Si coché, les Spécialités peuvent permettre de réduire de 1 la valeur d'un dé",
scope: "world",
config: true,
default: false,
type: Boolean
})
}
/* -------------------------------------------- */
static pushInitiativeOptions(html, options) {
}
/* -------------------------------------------- */
static async ready() {
this.registerSettings()
}
/* -------------------------------------------- */
static async addItemDropToActor(actor, item) {
actor.preprocessItem("none", item, false)
let chatData = {
user: game.user.id,
rollMode: game.settings.get("core", "rollMode"),
whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM')),
content: `<div>The item ${item.name} has been dropped on the actor ${actor.name}</div`
}
ChatMessage.create(chatData);
}
/* -------------------------------------------- */
static async loadCompendiumData(compendium) {
const pack = game.packs.get(compendium);
return await pack?.getDocuments() ?? [];
}
/* -------------------------------------------- */
static async loadCompendium(compendium, filter = item => true) {
let compendiumData = await Imperium5Utility.loadCompendiumData(compendium);
return compendiumData.filter(filter);
}
/* -------------------------------------------- */
static getOptionsStatusList() {
return this.optionsStatusList;
}
/* -------------------------------------------- */
static async chatListeners(html) {
html.on("change", '.select-apply-paradigme', event => {
let paraKey = event.currentTarget.value
let rollData = this.getRollDataFromMessage(event)
rollData.previousMessageId = Imperium5Utility.findChatMessageId(event.currentTarget)
this.applyParadigme(rollData, paraKey)
})
html.on("click", '.apply-specialite', event => {
let resultIndex = $(event.currentTarget).data("result-index")
let rollData = this.getRollDataFromMessage(event)
rollData.previousMessageId = Imperium5Utility.findChatMessageId(event.currentTarget)
this.applySpecialite(rollData, resultIndex)
})
html.on("change", '.transfer-success', event => {
let nbSuccess = event.currentTarget.value
let rollData = this.getRollDataFromMessage(event)
rollData.previousMessageId = Imperium5Utility.findChatMessageId(event.currentTarget)
this.applySuccessTransfer(rollData, nbSuccess)
})
html.on("change", '.select-ressource', async event => {
// Filter / remove the ressource
let rollData = this.getRollDataFromMessage(event)
let ressource = rollData.ressources.find(r => r._id == event.currentTarget.value)
rollData.selectedRessources.push(ressource)
rollData.ressources = rollData.ressources.filter(r => r._id != event.currentTarget.value)
rollData.realSuccessPC--;
this.computeIntensite(rollData)
// Update the roll data in the message
rollData.previousMessageId = Imperium5Utility.findChatMessageId(event.currentTarget)
let msg = await this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-imperium5/templates/chat-generic-result.html`, rollData)
})
msg.setFlag("world", "imperium5-roll-data", rollData)
this.removeChatMessageId(rollData.previousMessageId)
})
}
/* -------------------------------------------- */
static async preloadHandlebarsTemplates() {
const templatePaths = [
'systems/fvtt-imperium5/templates/editor-notes-gm.html',
'systems/fvtt-imperium5/templates/actor-partial-ames.html',
'systems/fvtt-imperium5/templates/actor-partial-paradigmes.html',
'systems/fvtt-imperium5/templates/partial-item-description.html',
]
return loadTemplates(templatePaths);
}
/* -------------------------------------------- */
static async getEffectFromCompendium(effectName) {
effectName = effectName.toLowerCase()
let effect = game.items.contents.find(item => item.type == 'effect' && item.name.toLowerCase() == effectName)
if (!effect) {
let effects = await this.loadCompendium('fvtt-imperium5.effect', item => item.name.toLowerCase() == effectName)
let objs = effects.map(i => i.toObject())
effect = objs[0]
} else {
effect = duplicate(effect);
}
console.log("Effect", effect)
return effect
}
/* -------------------------------------------- */
static removeChatMessageId(messageId) {
if (messageId) {
game.messages.get(messageId)?.delete();
}
}
static findChatMessageId(current) {
return Imperium5Utility.getChatMessageId(Imperium5Utility.findChatMessage(current))
}
static getChatMessageId(node) {
return node?.attributes.getNamedItem('data-message-id')?.value
}
static findChatMessage(current) {
return Imperium5Utility.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 Imperium5Utility.findNodeMatching(current.parentElement, predicate)
}
return undefined;
}
/* -------------------------------------------- */
static getRollDataFromMessage(event) {
let messageId = Imperium5Utility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
return message.getFlag("world", "imperium5-roll-data")
}
/* -------------------------------------------- */
static createDirectOptionList(min, max) {
let options = {};
for (let i = min; i <= max; i++) {
options[`${i}`] = `${i}`;
}
return options;
}
/* -------------------------------------------- */
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 chatRollMenu(html, options) {
let canApply = li => canvas.tokens.controlled.length && li.find(".imperium5-roll").length
let canApplyIntensite = function (li) {
let message = game.messages.get(li.attr("data-message-id"))
let rollData = message.getFlag("world", "imperium5-roll-data")
return (rollData.currentIntensite > 0)
}
options.push(
{
name: "Ajouer +3 (1 point de Bonne Aventure)",
icon: "<i class='fas fa-user-plus'></i>",
condition: canApply && canApplyIntensite,
callback: li => Imperium5Utility.applyBonneAventureRoll(li, -1, "+3")
}
)
return options
}
/* -------------------------------------------- */
static async onSocketMesssage(msg) {
console.log("SOCKET MESSAGE", msg.name)
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 loadCompendiumData(compendium) {
const pack = game.packs.get(compendium);
return await pack?.getDocuments() ?? [];
}
/* -------------------------------------------- */
static async loadCompendium(compendium, filter = item => true) {
let compendiumData = await this.loadCompendiumData(compendium);
//console.log("Compendium", compendiumData);
return compendiumData.filter(filter);
}
/* -------------------------------------------- */
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 computeDiceReserve(rollData) {
let capaDice = 0
if (rollData.usedCapacite != "none") {
let capa = rollData.capacites.find(c => c._id == rollData.usedCapacite)
capaDice = capa.system.aide
}
let val = rollData.ame.value + capaDice + ((rollData.useKarma) ? 1 : 0) + ((rollData.useArchetype) ? 1 : 0) + rollData.nbAide + rollData.ameMalus
return Math.max(val, 0)
}
/* -------------------------------------------- */
static computeReussites(rollData) {
let myRoll = rollData.roll
// Calcul des réussites sur les 2 pools
rollData.successPC = rollData.resultsPC.filter(res => res.result <= rollData.seuil).length
rollData.successGM = myRoll.terms[4].results.filter(res => res.result <= 2).length
// Calcul du présage
rollData.bonPresage = myRoll.terms[2].results[0].result == 1
rollData.mauvaisPresage = myRoll.terms[2].results[0].result == 8
// gestion règle optionnelle de l'Entropie
if (rollData.useEntropieReussite && myRoll.terms[2].results[0].result == 2) {
rollData.successPC++
}
if (rollData.useEntropieReussite && myRoll.terms[2].results[0].result == 7) {
rollData.successGM++
}
}
/* -------------------------------------------- */
static computeIntensite(rollData) {
rollData.totalIntensite = 0
let pi = 3
let nbSuccess = rollData.realSuccessPC
while (nbSuccess) {
rollData.totalIntensite += pi
pi = 2 // 3 for the first, 2 after
nbSuccess--
}
for(let r of rollData.selectedRessources) {
rollData.totalIntensite += r.system.ressource
}
}
/* -------------------------------------------- */
static async rollImperium5(rollData) {
// Karma management
let actor = game.actors.get(rollData.actorId)
rollData.nbKarma = 0
if (rollData.useKarma) {
actor.decOneKarma()
rollData.nbKarma++
}
if (rollData.usedCapacite != "none") {
actor.decOneKarma()
rollData.nbKarma++
}
let nbAmeDice = this.computeDiceReserve(rollData)
let diceFormula = `${nbAmeDice}d8[green] + 1d8[blue] + ${rollData.realiteDice}d8[red]`
let humanFormula = `${nbAmeDice}d8, 1d8, ${rollData.realiteDice}d8`
// Performs roll
let myRoll = rollData.roll
if (!myRoll) { // New rolls only of no rerolls
myRoll = new Roll(diceFormula).roll({ async: false })
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
rollData.roll = myRoll
rollData.diceFormula = diceFormula
rollData.humanFormula = humanFormula
}
// local copy of results to allow changes
rollData.resultsPC = duplicate(myRoll.terms[0].results)
// Calcul réussites
this.computeReussites(rollData)
rollData.realSuccessPC = rollData.successPC // To manage source transfer
this.computeIntensite(rollData)
let msg = await this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-imperium5/templates/chat-generic-result.html`, rollData)
})
msg.setFlag("world", "imperium5-roll-data", rollData)
console.log("Final rollData", rollData)
}
/* -------------------------------------------- */
static async applyParadigme(rollData, paraKey) {
let actor = game.actors.get(rollData.actorId)
let para = actor.system.paradigmes[paraKey]
rollData.seuil = para.value
rollData.usedParadigme = para.label
console.log(">>>>>>>>>>NEW SEUIL : ", rollData.seuil)
actor.setParadigmeUsed(paraKey)
this.computeReussites(rollData)
rollData.realSuccessPC = rollData.successPC // To manage source transfer
rollData.paradigmes = []
let msg = await this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-imperium5/templates/chat-generic-result.html`, rollData)
})
msg.setFlag("world", "imperium5-roll-data", rollData)
this.removeChatMessageId(rollData.previousMessageId)
}
/* -------------------------------------------- */
static async applySpecialite(rollData, resultIndex) {
let res = rollData.resultsPC[resultIndex]
res.result = (res.result > 1) ? res.result - 1 : res.result
this.computeReussites(rollData)
rollData.useSpecialite = false
rollData.specialiteApplied = true
let msg = await this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-imperium5/templates/chat-generic-result.html`, rollData)
})
msg.setFlag("world", "imperium5-roll-data", rollData)
this.removeChatMessageId(rollData.previousMessageId)
}
/* ------------------------- ------------------- */
static async applySuccessTransfer(rollData, nbSuccess) {
let actor = game.actors.get(rollData.actorId)
actor.transferToSource(nbSuccess)
rollData.realSuccessPC -= nbSuccess
rollData.sourceTransfer = nbSuccess
let msg = await this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-imperium5/templates/chat-generic-result.html`, rollData)
})
msg.setFlag("world", "imperium5-roll-data", rollData)
this.removeChatMessageId(rollData.previousMessageId)
}
/* ------------------------- ------------------- */
static async rerollDice(actorId, diceIndex = -1) {
let actor = game.actors.get(actorId)
let rollData = actor.getRollData()
}
/* -------------------------------------------- */
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 = duplicate(chatOptions);
chatGM.whisper = this.getUsers(user => user.isGM);
chatGM.content = "Message en aveugle à " + game.user.name + "<br>" + chatOptions.content;
console.log("blindMessageToGM", chatGM);
game.socket.emit("system.fvtt-pegasus-rgp", { 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 createChatMessage(name, rollMode, chatOptions) {
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;
return ChatMessage.create(chatOptions)
}
/* -------------------------------------------- */
static getBasicRollData() {
let rollData = {
rollId: randomID(16),
rollMode: game.settings.get("core", "rollMode"),
realiteDice: 0,
ameMalus: 0,
useArchetype: false,
nbAide: 0,
useKarma: false,
usedCapacite: "none",
seuil: 2,
currentIntensite: -1,
nbSuccessSource: 0,
useSpecialite: game.settings.get("fvtt-imperium5", "use-specialite"),
useEntropieReussite: game.settings.get("fvtt-imperium5", "use-entropie-reussite")
}
Imperium5Utility.updateWithTarget(rollData)
return rollData
}
/* -------------------------------------------- */
static updateWithTarget(rollData) {
let objectDefender
let target = Imperium5Utility.getTarget()
if (target) {
let defenderActor = game.actors.get(target.data.actorId)
objectDefender = defenderActor
objectDefender = mergeObject(objectDefender, target.data.actorData)
rollData.defender = objectDefender
rollData.attackerId = this.id
rollData.defenderId = objectDefender.id
}
}
/* -------------------------------------------- */
static createChatWithRollMode(name, chatOptions) {
return this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions)
}
/* -------------------------------------------- */
static async confirmDelete(actorSheet, li) {
let itemId = li.data("item-id");
let msgTxt = "<p>Etes vous certain de vouloir supprimer cet objet ?";
let buttons = {
delete: {
icon: '<i class="fas fa-check"></i>',
label: "Oui",
callback: () => {
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]);
li.slideUp(200, () => actorSheet.render(false));
}
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler"
}
}
msgTxt += "</p>";
let d = new Dialog({
title: "Confirmer la suppression",
content: msgTxt,
buttons: buttons,
default: "cancel"
});
d.render(true);
}
}