diff --git a/dist/module/actor/entity.js b/dist/module/actor/entity.js index 8a87cce4..28d83f90 100644 --- a/dist/module/actor/entity.js +++ b/dist/module/actor/entity.js @@ -480,15 +480,16 @@ export class OseActor extends Actor { const rollData = { actor: this.data, item: attData.item, + itemId: attData.item._id, roll: { type: options.type, thac0: thac0, dmg: dmgParts, save: attData.roll.save, - target: attData.roll.target, + target: attData.roll.target + }, }; - // Roll and return return OseDice.Roll({ event: options.event, @@ -497,6 +498,7 @@ export class OseActor extends Actor { skipDialog: options.skipDialog, speaker: ChatMessage.getSpeaker({ actor: this }), flavor: label, + flags: {ose: {roll: 'attack', itemId: attData.item._id}}, title: label, }); } diff --git a/dist/module/combat.js b/dist/module/combat.js index 7da4572c..7bac9428 100644 --- a/dist/module/combat.js +++ b/dist/module/combat.js @@ -2,43 +2,55 @@ export class OseCombat { static STATUS_SLOW = -789; static STATUS_DIZZY = -790; - static rollInitiative(combat, data) { + static debounce(callback, wait) { + let timeoutId = null; + return (...args) => { + window.clearTimeout(timeoutId); + timeoutId = window.setTimeout(() => { + callback.apply(null, args); + }, wait); + }; + } + static async rollInitiative(combat, data) { // Check groups data.combatants = []; let groups = {}; combat.data.combatants.forEach((cbt) => { - const group = cbt.getFlag("ose", "group"); + const group = cbt.getFlag('ose', 'group'); groups[group] = { present: true }; data.combatants.push(cbt); }); - // Roll init - Object.keys(groups).forEach((group) => { - let roll = new Roll("1d6").evaluate({async: false}); - roll.toMessage({ - flavor: game.i18n.format('OSE.roll.initiative', { group: CONFIG["OSE"].colors[group] }), + for(let group in groups){ + // Object.keys(groups).forEach((group) => { + let roll = new Roll('1d6').evaluate({ async: false }); + await roll.toMessage({ + flavor: game.i18n.format('OSE.roll.initiative', { group: CONFIG['OSE'].colors[group] }) }); groups[group].initiative = roll.total; - }); - + // }); + }; // Set init for (let i = 0; i < data.combatants.length; ++i) { - if (!data.combatants[i].actor) { - return; - } - if (data.combatants[i].actor.data.data.isSlow) { - data.combatants[i].update({initiative: OseCombat.STATUS_SLOW}); - } else { - const group = data.combatants[i].getFlag("ose", "group"); - data.combatants[i].update({initiative: groups[group].initiative}); + if (game.user.isGM) { + if (!data.combatants[i].actor) { + return; + } + if (data.combatants[i].actor.data.data.isSlow) { + await data.combatants[i].update({ initiative: OseCombat.STATUS_SLOW }); + } else { + const group = data.combatants[i].getFlag('ose', 'group'); + this.debounce(data.combatants[i].update({ initiative: groups[group].initiative }), 500); + } } } - combat.setupTurns(); + + await combat.setupTurns(); } static async resetInitiative(combat, data) { - let reroll = game.settings.get("ose", "rerollInitiative"); - if (!["reset", "reroll"].includes(reroll)) { + let reroll = game.settings.get('ose', 'rerollInitiative'); + if (!['reset', 'reroll'].includes(reroll)) { return; } combat.resetAll(); @@ -47,98 +59,98 @@ export class OseCombat { static async individualInitiative(combat, data) { let updates = []; let messages = []; - combat.data.combatants.forEach((c, i) => { + + for (let i = 0; i < combat.data.combatants.size; i++) { + let c = combat.data.combatants.contents[i]; + // This comes from foundry.js, had to remove the update turns thing // Roll initiative - const cf = c._getInitiativeFormula(c); - const roll = c.getInitiativeRoll(cf); + const cf = await c._getInitiativeFormula(c); + const roll = await c.getInitiativeRoll(cf).evaluate({ async: true }); + let value = roll.total; if (combat.settings.skipDefeated && c.defeated) { value = OseCombat.STATUS_DIZZY; } - updates.push({ _id: c.id, initiative: value }); + const data = { _id: c.id, initiative: value }; + + updates.push(data); // Determine the roll mode - let rollMode = game.settings.get("core", "rollMode");; - if ((c.token.hidden || c.hidden) && (rollMode === "roll")) rollMode = "gmroll"; + let rollMode = game.settings.get('core', 'rollMode'); + if ((c.token.hidden || c.hidden) && rollMode === 'roll') rollMode = 'gmroll'; // Construct chat message data - // Construct chat message data - let messageData = foundry.utils.mergeObject({ - speaker: { - scene: combat.scene.id, - actor: c.actor?.id, - token: c.token?.id, - alias: c.name + let messageData = foundry.utils.mergeObject( + { + speaker: { + scene: combat.scene.id, + actor: c.actor?.id, + token: c.token?.id, + alias: c.name + }, + flavor: game.i18n.format('OSE.roll.individualInit', { name: c.token.name }), + flags: { 'ose.initiativeRoll': true } }, - flavor: game.i18n.format('OSE.roll.individualInit', { name: c.token.name }), - flags: {"ose.initiativeRoll": true} - }, {}); - const chatData = roll.toMessage(messageData, { rollMode: c.hidden && (rollMode === "roll") ? "gmroll" : rollMode, create: false }); - - if (i > 0) chatData.sound = null; // Only play 1 sound for the whole set + {} + ); + const chatData = await roll.toMessage(messageData, { + rollMode: c.hidden && rollMode === 'roll' ? 'gmroll' : rollMode, + create: false + }); + if (i > 0) chatData.sound = null; // Only play 1 sound for the whole set messages.push(chatData); - }); - - await combat.updateEmbeddedDocuments("Combatant", updates); + } + if (game.user.isGM) { + await combat.updateEmbeddedDocuments('Combatant', updates); + } await ChatMessage.implementation.create(messages); data.turn = 0; } static format(object, html, user) { - html.find(".initiative").each((_, span) => { + html.find('.initiative').each((_, span) => { span.innerHTML = - span.innerHTML == `${OseCombat.STATUS_SLOW}` - ? '' - : span.innerHTML; - span.innerHTML = - span.innerHTML == `${OseCombat.STATUS_DIZZY}` - ? '' - : span.innerHTML; + span.innerHTML == `${OseCombat.STATUS_SLOW}` ? '' : span.innerHTML; + span.innerHTML = span.innerHTML == `${OseCombat.STATUS_DIZZY}` ? '' : span.innerHTML; }); - html.find(".combatant").each((_, ct) => { + html.find('.combatant').each((_, ct) => { // Append spellcast and retreat - const controls = $(ct).find(".combatant-controls .combatant-control"); + const controls = $(ct).find('.combatant-controls .combatant-control'); const cmbtant = object.viewed.combatants.get(ct.dataset.combatantId); - const moveInCombat = cmbtant.getFlag("ose", "moveInCombat"); - const preparingSpell = cmbtant.getFlag("ose", "prepareSpell"); - const moveActive = moveInCombat ? "active" : ""; - controls.eq(1).after( - `` - ); - const spellActive = preparingSpell ? "active" : ""; - controls.eq(1).after( - `` - ); + const moveInCombat = cmbtant.getFlag('ose', 'moveInCombat'); + const preparingSpell = cmbtant.getFlag('ose', 'prepareSpell'); + const moveActive = moveInCombat ? 'active' : ''; + controls.eq(1).after(``); + const spellActive = preparingSpell ? 'active' : ''; + controls + .eq(1) + .after(``); }); OseCombat.announceListener(html); - let init = game.settings.get("ose", "initiative") === "group"; + let init = game.settings.get('ose', 'initiative') === 'group'; if (!init) { return; } html.find('.combat-control[data-control="rollNPC"]').remove(); html.find('.combat-control[data-control="rollAll"]').remove(); - let trash = html.find( - '.encounters .combat-control[data-control="endCombat"]' - ); - $( - '' - ).insertBefore(trash); + let trash = html.find('.encounters .combat-control[data-control="endCombat"]'); + $('').insertBefore(trash); - html.find(".combatant").each((_, ct) => { + html.find('.combatant').each((_, ct) => { // Can't roll individual inits - $(ct).find(".roll").remove(); + $(ct).find('.roll').remove(); // Get group color const cmbtant = object.viewed.combatants.get(ct.dataset.combatantId); - let color = cmbtant.getFlag("ose", "group"); + let color = cmbtant.getFlag('ose', 'group'); // Append colored flag - let controls = $(ct).find(".combatant-controls"); + let controls = $(ct).find('.combatant-controls'); controls.prepend( `` ); @@ -147,53 +159,52 @@ export class OseCombat { } static updateCombatant(combatant, data) { - let init = game.settings.get("ose", "initiative"); + let init = game.settings.get('ose', 'initiative'); // Why do you reroll ? if (combatant.actor.data.data.isSlow) { data.initiative = -789; return; } - if (data.initiative && init == "group") { + if (data.initiative && init == 'group') { let groupInit = data.initiative; - const cmbtGroup = combatant.getFlag("ose", "group"); + const cmbtGroup = combatant.getFlag('ose', 'group'); // Check if there are any members of the group with init game.combats.viewed.combatants.forEach((ct) => { - const group = ct.getFlag("ose", "group"); - if ( - ct.initiative && - ct.initiative != "-789.00" && - ct.id != data.id && - group == cmbtGroup - ) { + const group = ct.getFlag('ose', 'group'); + if (ct.initiative && ct.initiative != '-789.00' && ct.id != data.id && group == cmbtGroup) { // Set init - combatant.update({initiative: parseInt(ct.initiative)}); + if (game.user.isGM) { + combatant.update({ initiative: parseInt(groupInit) }); + } } }); } } static announceListener(html) { - html.find(".combatant-control.prepare-spell").click((ev) => { + html.find('.combatant-control.prepare-spell').click((ev) => { ev.preventDefault(); // Toggle spell announcement - let id = $(ev.currentTarget).closest(".combatant")[0].dataset.combatantId; + let id = $(ev.currentTarget).closest('.combatant')[0].dataset.combatantId; let isActive = ev.currentTarget.classList.contains('active'); const combatant = game.combat.combatants.get(id); - combatant.setFlag("ose", "prepareSpell", !isActive); + combatant.setFlag('ose', 'prepareSpell', !isActive); }); - html.find(".combatant-control.move-combat").click((ev) => { + html.find('.combatant-control.move-combat').click((ev) => { ev.preventDefault(); // Toggle spell announcement - let id = $(ev.currentTarget).closest(".combatant")[0].dataset.combatantId; + let id = $(ev.currentTarget).closest('.combatant')[0].dataset.combatantId; let isActive = ev.currentTarget.classList.contains('active'); const combatant = game.combat.combatants.get(id); - combatant.setFlag("ose", "moveInCombat", !isActive); - }) + if (game.user.isGM) { + combatant.setFlag('ose', 'moveInCombat', !isActive); + } + }); } static addListeners(html) { // Cycle through colors - html.find(".combatant-control.flag").click((ev) => { + html.find('.combatant-control.flag').click((ev) => { if (!game.user.isGM) { return; } @@ -205,9 +216,11 @@ export class OseCombat { } else { index++; } - let id = $(ev.currentTarget).closest(".combatant")[0].dataset.combatantId; + let id = $(ev.currentTarget).closest('.combatant')[0].dataset.combatantId; const combatant = game.combat.combatants.get(id); - combatant.setFlag("ose", "group", colors[index]); + if (game.user.isGM) { + combatant.setFlag('ose', 'group', colors[index]); + } }); html.find('.combat-control[data-control="reroll"]').click((ev) => { @@ -216,63 +229,67 @@ export class OseCombat { } let data = {}; OseCombat.rollInitiative(game.combat, data); - game.combat.update({ data: data }).then(() => { - game.combat.setupTurns(); - }); + if (game.user.isGM) { + game.combat.update({ data: data }).then(() => { + game.combat.setupTurns(); + }); + } }); } static addCombatant(combat, data, options, id) { let token = canvas.tokens.get(data.tokenId); - let color = "black"; + let color = 'black'; switch (token.data.disposition) { case -1: - color = "red"; + color = 'red'; break; case 0: - color = "yellow"; + color = 'yellow'; break; case 1: - color = "green"; + color = 'green'; break; } data.flags = { ose: { - group: color, - }, + group: color + } }; } static activateCombatant(li) { - const turn = game.combat.turns.findIndex(turn => turn.id === li.data('combatant-id')); - game.combat.update({ turn: turn }) + const turn = game.combat.turns.findIndex((turn) => turn.id === li.data('combatant-id')); + if (game.user.isGM) { + game.combat.update({ turn: turn }); + } } static addContextEntry(html, options) { options.unshift({ - name: "Set Active", + name: 'Set Active', icon: '', callback: OseCombat.activateCombatant }); } static async preUpdateCombat(combat, data, diff, id) { - let init = game.settings.get("ose", "initiative"); - let reroll = game.settings.get("ose", "rerollInitiative"); + let init = game.settings.get('ose', 'initiative'); + let reroll = game.settings.get('ose', 'rerollInitiative'); if (!data.round) { return; } if (data.round !== 1) { - if (reroll === "reset") { + if (reroll === 'reset') { OseCombat.resetInitiative(combat, data, diff, id); return; - } else if (reroll === "keep") { + } else if (reroll === 'keep') { return; } } - if (init === "group") { + if (init === 'group') { OseCombat.rollInitiative(combat, data, diff, id); - } else if (init === "individual") { + } else if (init === 'individual') { OseCombat.individualInitiative(combat, data, diff, id); } } diff --git a/dist/module/dice.js b/dist/module/dice.js index 466adcd4..4dbf1b6d 100644 --- a/dist/module/dice.js +++ b/dist/module/dice.js @@ -188,23 +188,24 @@ export class OseDice { static async sendAttackRoll({ parts = [], data = {}, + flags= {}, title = null, flavor = null, speaker = null, form = null, } = {}) { const template = "systems/ose/dist/templates/chat/roll-attack.html"; - let chatData = { user: game.user.id, speaker: speaker, + flags: flags }; let templateData = { title: title, flavor: flavor, data: data, - config: CONFIG.OSE, + config: CONFIG.OSE }; // Optionally include a situational bonus @@ -367,6 +368,8 @@ export class OseDice { flavor = null, title = null, chatMessage = true, + flags = {} + } = {}) { let rolled = false; const template = "systems/ose/dist/templates/chat/roll-dialog.html"; @@ -378,7 +381,6 @@ export class OseDice { : game.settings.get("core", "rollMode"), rollModes: CONFIG.Dice.rollModes, }; - let rollData = { parts: parts, data: data, @@ -386,6 +388,7 @@ export class OseDice { flavor: flavor, speaker: speaker, chatMessage: chatMessage, + flags: flags }; if (skipDialog) { return ["melee", "missile", "attack"].includes(data.roll.type) diff --git a/dist/ose.js b/dist/ose.js index d1e7d1ad..02ff0ca1 100644 --- a/dist/ose.js +++ b/dist/ose.js @@ -1,38 +1,39 @@ // Import Modules -import { OseItemSheet } from "./module/item/item-sheet.js"; -import { OseActorSheetCharacter } from "./module/actor/character-sheet.js"; -import { OseActorSheetMonster } from "./module/actor/monster-sheet.js"; -import { preloadHandlebarsTemplates } from "./module/preloadTemplates.js"; -import { OseActor } from "./module/actor/entity.js"; -import { OseItem } from "./module/item/entity.js"; -import { OSE } from "./module/config.js"; -import { registerSettings } from "./module/settings.js"; -import { registerHelpers } from "./module/helpers.js"; -import * as chat from "./module/chat.js"; -import * as treasure from "./module/treasure.js"; -import * as macros from "./module/macros.js"; -import * as party from "./module/party.js"; -import { OseCombat } from "./module/combat.js"; -import * as renderList from "./module/renderList.js"; +import { OseItemSheet } from './module/item/item-sheet.js'; +import { OseActorSheetCharacter } from './module/actor/character-sheet.js'; +import { OseActorSheetMonster } from './module/actor/monster-sheet.js'; +import { preloadHandlebarsTemplates } from './module/preloadTemplates.js'; +import { OseActor } from './module/actor/entity.js'; +import { OseItem } from './module/item/entity.js'; +import { OSE } from './module/config.js'; +import { registerSettings } from './module/settings.js'; +import { registerHelpers } from './module/helpers.js'; +import * as chat from './module/chat.js'; +import * as treasure from './module/treasure.js'; +import * as macros from './module/macros.js'; +import * as party from './module/party.js'; +import { OseCombat } from './module/combat.js'; +import * as renderList from './module/renderList.js'; /* -------------------------------------------- */ /* Foundry VTT Initialization */ /* -------------------------------------------- */ -Hooks.once("init", async function () { +Hooks.once('init', async function () { /** * Set an initiative formula for the system * @type {String} */ CONFIG.Combat.initiative = { - formula: "1d6 + @initiative.value", - decimals: 2, + formula: '1d6 + @initiative.value', + decimals: 2 }; CONFIG.OSE = OSE; game.ose = { rollItemMacro: macros.rollItemMacro, + oseCombat: OseCombat }; // Custom Handlebars helpers @@ -45,22 +46,22 @@ Hooks.once("init", async function () { CONFIG.Item.documentClass = OseItem; // Register sheet application classes - Actors.unregisterSheet("core", ActorSheet); - Actors.registerSheet("ose", OseActorSheetCharacter, { - types: ["character"], + Actors.unregisterSheet('core', ActorSheet); + Actors.registerSheet('ose', OseActorSheetCharacter, { + types: ['character'], makeDefault: true, - label: "OSE.SheetClassCharacter", + label: 'OSE.SheetClassCharacter' }); - Actors.registerSheet("ose", OseActorSheetMonster, { - types: ["monster"], + Actors.registerSheet('ose', OseActorSheetMonster, { + types: ['monster'], makeDefault: true, - label: "OSE.SheetClassMonster", + label: 'OSE.SheetClassMonster' }); - Items.unregisterSheet("core", ItemSheet); - Items.registerSheet("ose", OseItemSheet, { + Items.unregisterSheet('core', ItemSheet); + Items.registerSheet('ose', OseItemSheet, { makeDefault: true, - label: "OSE.SheetClassItem", + label: 'OSE.SheetClassItem' }); await preloadHandlebarsTemplates(); @@ -69,16 +70,9 @@ Hooks.once("init", async function () { /** * This function runs after game data has been requested and loaded from the servers, so entities exist */ -Hooks.once("setup", function () { +Hooks.once('setup', function () { // Localize CONFIG objects once up-front - const toLocalize = [ - "saves_short", - "saves_long", - "scores", - "armor", - "colors", - "tags", - ]; + const toLocalize = ['saves_short', 'saves_long', 'scores', 'armor', 'colors', 'tags']; for (let o of toLocalize) { CONFIG.OSE[o] = Object.entries(CONFIG.OSE[o]).reduce((obj, e) => { obj[e[0]] = game.i18n.localize(e[1]); @@ -87,70 +81,65 @@ Hooks.once("setup", function () { } // Custom languages - const languages = game.settings.get("ose", "languages"); - if (languages != "") { - const langArray = languages.split(","); + const languages = game.settings.get('ose', 'languages'); + if (languages != '') { + const langArray = languages.split(','); langArray.forEach((l, i) => (langArray[i] = l.trim())); CONFIG.OSE.languages = langArray; } }); -Hooks.once("ready", async () => { - Hooks.on("hotbarDrop", (bar, data, slot) => - macros.createOseMacro(data, slot) - ); +Hooks.once('ready', async () => { + Hooks.on('hotbarDrop', (bar, data, slot) => macros.createOseMacro(data, slot)); }); // License and KOFI infos -Hooks.on("renderSidebarTab", async (object, html) => { +Hooks.on('renderSidebarTab', async (object, html) => { if (object instanceof ActorDirectory) { party.addControl(object, html); } if (object instanceof Settings) { - let gamesystem = html.find("#game-details"); + let gamesystem = html.find('#game-details'); // SRD Link - let ose = gamesystem.find("h4").last(); - ose.append( - ` SRD` - ); + let ose = gamesystem.find('h4').last(); + ose.append(` SRD`); // License text - const template = "systems/ose/dist/templates/chat/license.html"; + const template = 'systems/ose/dist/templates/chat/license.html'; const rendered = await renderTemplate(template); - gamesystem.find(".system").append(rendered); + gamesystem.find('.system').append(rendered); // User guide let docs = html.find("button[data-action='docs']"); - const styling = - "border:none;margin-right:2px;vertical-align:middle;margin-bottom:5px"; + const styling = 'border:none;margin-right:2px;vertical-align:middle;margin-bottom:5px'; $( `` ).insertAfter(docs); html.find('button[data-action="userguide"]').click((ev) => { - new FrameViewer("https://mesfoliesludiques.gitlab.io/foundryvtt-ose", { - resizable: true, + new FrameViewer('https://mesfoliesludiques.gitlab.io/foundryvtt-ose', { + resizable: true }).render(true); }); } }); -Hooks.on("preCreateCombatant", (combat, data, options, id) => { - let init = game.settings.get("ose", "initiative"); - if (init == "group") { +Hooks.on('preCreateCombatant', (combat, data, options, id) => { + let init = game.settings.get('ose', 'initiative'); + if (init == 'group') { OseCombat.addCombatant(combat, data, options, id); } }); -Hooks.on("updateCombatant", OseCombat.updateCombatant); -Hooks.on("renderCombatTracker", OseCombat.format); -Hooks.on("preUpdateCombat", OseCombat.preUpdateCombat); -Hooks.on("getCombatTrackerEntryContext", OseCombat.addContextEntry); +Hooks.on('updateCombatant', OseCombat.debounce(OseCombat.updateCombatant), 100); +Hooks.on('renderCombatTracker', OseCombat.debounce(OseCombat.format, 100)); +Hooks.on('preUpdateCombat', OseCombat.preUpdateCombat); +Hooks.on('getCombatTrackerEntryContext', OseCombat.debounce(OseCombat.addContextEntry, 100)); -Hooks.on("renderChatLog", (app, html, data) => OseItem.chatListeners(html)); -Hooks.on("getChatLogEntryContext", chat.addChatMessageContextOptions); -Hooks.on("renderChatMessage", chat.addChatMessageButtons); -Hooks.on("renderRollTableConfig", treasure.augmentTable); -Hooks.on("updateActor", party.update); +Hooks.on('renderChatLog', (app, html, data) => OseItem.chatListeners(html)); +Hooks.on('getChatLogEntryContext', chat.addChatMessageContextOptions); +Hooks.on('renderChatMessage', chat.addChatMessageButtons); +Hooks.on('renderRollTableConfig', treasure.augmentTable); +Hooks.on('updateActor', party.update); -Hooks.on("renderCompendium", renderList.RenderCompendium); -Hooks.on("renderSidebarDirectory", renderList.RenderDirectory); +Hooks.on('renderCompendium', renderList.RenderCompendium); +Hooks.on('renderSidebarDirectory', renderList.RenderDirectory);