export class AcksCombat { static async rollInitiative(combat, data) { // Initialize groups. data.combatants = []; let groups = {}; combat.data.combatants.forEach((cbt) => { groups[cbt.data.flags.acks.group] = {present: true}; data.combatants.push(cbt); }); // Roll initiative for each group. for (const group in groups) { const roll = new Roll("1d6"); await roll.evaluate({async: true}); await roll.toMessage({ flavor: game.i18n.format('ACKS.roll.initiative', { group: CONFIG["ACKS"].colors[group], }), }); groups[group].initiative = roll.total; } // Set the inititative for each group combatant. for (const combatant of data.combatants) { if (!combatant.actor) { return; } let initiative = groups[combatant.data.flags.acks.group].initiative; if (combatant.actor.data.data.isSlow) { initiative -= 1; } await combatant.update({ initiative: initiative, }); } combat.setupTurns(); } static async resetInitiative(combat, data) { const reroll = game.settings.get("acks", "initiativePersistence"); if (!["reset", "reroll"].includes(reroll)) { return; } combat.resetAll(); } static async individualInitiative(combat, data) { const updates = []; const messages = []; let index = 0; for (const [id, combatant] of combat.data.combatants.entries()) { const roll = combatant.getInitiativeRoll(); await roll.evaluate({async: true}); let value = roll.total; if (combat.settings.skipDefeated && combatant.defeated) { value = -790; } updates.push({ _id: id, initiative: value, }); // Determine the roll mode let rollMode = game.settings.get("core", "rollMode"); if ((combatant.token.hidden || combatant.hidden) && (rollMode === "roll")) { rollMode = "gmroll"; } // Construct chat message data const messageData = mergeObject({ speaker: { scene: canvas.scene._id, actor: combatant.actor?.id || null, token: combatant.token.id, alias: combatant.token.name }, flavor: game.i18n.format('ACKS.roll.individualInit', { name: combatant.token.name, }), }, {}); const chatData = await roll.toMessage(messageData, { rollMode, create: false, }); // Only play one sound for the whole set. if (index > 0) { chatData.sound = null; } messages.push(chatData); ++index; } await combat.updateEmbeddedDocuments("Combatant", updates); await CONFIG.ChatMessage.documentClass.create(messages); data.turn = 0; } static format(object, html, user) { html.find(".initiative").each((_, span) => { span.innerHTML = span.innerHTML == "-789.00" ? '' : span.innerHTML; span.innerHTML = span.innerHTML == "-790.00" ? '' : span.innerHTML; }); html.find(".combatant").each((_, ct) => { // Append spellcast and retreat const controls = $(ct).find(".combatant-controls .combatant-control"); const cmbtant = game.combat.combatants.get(ct.dataset.combatantId); const moveActive = cmbtant.data.flags.acks?.moveInCombat ? "active" : ""; controls.eq(1).after( `` ); const spellActive = cmbtant.data.flags.acks?.prepareSpell ? "active" : ""; controls.eq(1).after( `` ); const holdActive = cmbtant.data.flags.acks?.holdTurn ? "active" : ""; controls.eq(1).after( `` ); }); AcksCombat.announceListener(html); let init = game.settings.get("acks", "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); html.find(".combatant").each((_, ct) => { // Can't roll individual inits $(ct).find(".roll").remove(); // Get group color const combatant = object.viewed.combatants.get(ct.dataset.combatantId); let color = combatant.data.flags.acks?.group; // Append colored flag let controls = $(ct).find(".combatant-controls"); controls.prepend( `` ); }); AcksCombat.addListeners(html); } static updateCombatant(combat, combatant, data) { let init = game.settings.get("acks", "initiative"); // Why do you reroll ? // Legacy Slowness code from OSE // if (combatant.actor.data.data.isSlow) { // data.initiative = -789; // return; // } if (data.initiative && init == "group") { let groupInit = data.initiative; // Check if there are any members of the group with init combat.combatants.forEach((ct) => { if ( ct.initiative && ct.initiative != "-789.00" && ct._id != data._id && ct.data.flags.acks.group == combatant.data.flags.acks.group ) { groupInit = ct.initiative; // Set init data.initiative = parseInt(groupInit); } }); } } static announceListener(html) { html.find(".combatant-control.hold-turn").click(async (event) => { event.preventDefault(); // Toggle hold announcement const id = $(event.currentTarget).closest(".combatant")[0].dataset.combatantId; const isActive = event.currentTarget.classList.contains('active'); const combatant = game.combat.combatants.get(id); await combatant.update({ _id: id, flags: { acks: { holdTurn: !isActive, }, }, }); }) html.find(".combatant-control.prepare-spell").click(async (event) => { event.preventDefault(); // Toggle spell announcement const id = $(event.currentTarget).closest(".combatant")[0].dataset.combatantId; const isActive = event.currentTarget.classList.contains('active'); const combatant = game.combat.combatants.get(id); await combatant.update({ _id: id, flags: { acks: { prepareSpell: !isActive, }, }, }); }); html.find(".combatant-control.move-combat").click(async (event) => { event.preventDefault(); // Toggle retreat announcement const id = $(event.currentTarget).closest(".combatant")[0].dataset.combatantId; const isActive = event.currentTarget.classList.contains('active'); const combatant = game.combat.combatants.get(id); await combatant.update({ _id: id, flags: { acks: { moveInCombat: !isActive, }, }, }); }); } static addListeners(html) { // Cycle through colors html.find(".combatant-control.flag").click(async (event) => { event.preventDefault(); if (!game.user.isGM) { return; } const currentColor = event.currentTarget.style.color; const colors = Object.keys(CONFIG.ACKS.colors); let index = colors.indexOf(currentColor); if (index + 1 == colors.length) { index = 0; } else { index++; } const id = $(event.currentTarget).closest(".combatant")[0].dataset.combatantId; const combatant = game.combat.combatants.get(id); await combatant.update({ _id: id, flags: { acks: { group: colors[index], }, }, }); }); html.find('.combat-control[data-control="reroll"]').click(async (event) => { event.preventDefault(); if (!game.combat) { return; } const data = {}; AcksCombat.rollInitiative(game.combat, data); await game.combat.update({ data: data, }) game.combat.setupTurns(); }); } static async addCombatant(combatant, options, userId) { let color = "black"; switch (combatant.token.data.disposition) { case -1: color = "red"; break; case 0: color = "yellow"; break; case 1: color = "green"; break; } await combatant.update({ flags: { acks: { group: color, }, }, }); } static activateCombatant(li) { const turn = game.combat.turns.findIndex(turn => turn._id === li.data('combatant-id')); game.combat.update({turn: turn}) } static addContextEntry(html, options) { options.unshift({ name: "Set Active", icon: '', callback: AcksCombat.activateCombatant }); } static async preUpdateCombat(combat, data, diff, id) { if (!data.round) { return; } if (data.round !== 1) { const reroll = game.settings.get("acks", "initiativePersistence"); if (reroll === "reset") { AcksCombat.resetInitiative(combat, data, diff, id); return; } else if (reroll === "keep") { return; } } const init = game.settings.get("acks", "initiative"); if (init === "group") { AcksCombat.rollInitiative(combat, data, diff, id); } else if (init === "individual") { AcksCombat.individualInitiative(combat, data, diff, id); } } }