fvtt-never-stop-blowing-up/module/kidsonbrooms.mjs

332 lines
13 KiB
JavaScript

// Import document classes.
import { KidsOnBroomsActor } from "./documents/actor.mjs";
// Import sheet classes.
import { KidsOnBroomsActorSheet } from "./sheets/actor-sheet.mjs";
// Import helper/utility classes and constants.
import { preloadHandlebarsTemplates } from "./helpers/templates.mjs";
import { KIDSONBROOMS } from "./helpers/config.mjs";
/* -------------------------------------------- */
/* Init Hook */
/* -------------------------------------------- */
Hooks.once('init', async function() {
// Register the helper
Handlebars.registerHelper('capitalizeFirst', function(string) {
if (typeof string === 'string') {
return string.charAt(0).toUpperCase() + string.slice(1);
}
return '';
});
// Add utility classes and functions to the global game object so that they're more easily
// accessible in global contexts.
game.kidsonbrooms = {
KidsOnBroomsActor,
_onTakeAdversityToken: _onTakeAdversityToken, // Add the function to the global object
_onSpendAdversityTokens: _onSpendAdversityTokens // Add the function to the global object
};
// Add custom constants for configuration.
CONFIG.KIDSONBROOMS = KIDSONBROOMS;
/**
* Set an initiative formula for the system
* @type {String}
*/
CONFIG.Combat.initiative = {
formula: "1d20",
decimals: 2
};
// Define custom Document classes
CONFIG.Actor.documentClass = KidsOnBroomsActor;
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("kidsonbrooms", KidsOnBroomsActorSheet, { makeDefault: true });
//If there is a new chat message that is a roll we add the adversity token controls
Hooks.on("renderChatMessage", (message, html, messageData) => {
const adversityControls = html.find('.adversity-controls');
if (adversityControls.length > 0) {
const messageToEdit = adversityControls.data("roll-id");
// Bind event listeners for the controls
adversityControls.find(".take-adversity").off("click").click((event) => {
const actorId = event.currentTarget.dataset.actorId;
const actor = game.actors.get(actorId);
// Check if the current user owns the actor - They can not claim if they are not
if (!actor.testUserPermission(game.user, "owner")) {
ui.notifications.warn("You don't own this character and cannot take adversity tokens.");
return;
}
// Check if the token has already been claimed -- Contigency if the button somehow activates again
if (message.getFlag("kidsonbrooms", "tokenClaimed")) {
ui.notifications.warn("This adversity token has already been claimed.");
return;
}
_onTakeAdversityToken(event, actor);
if (game.user.isGM) {
let tokenControls = game.messages.get(message.id);
console.log(tokenControls);
// Update the chat message content with the button disabled and text changed
const updatedContent = tokenControls.content.replace(
`<button class="take-adversity" data-actor-id="${actor.id}">Take Adversity Token</button>`,
`<button class="take-adversity" data-actor-id="${actor.id}" disabled>Token claimed</button>`
);
console.log("Removing Button");
// Update the message content
tokenControls.update({ content: updatedContent });
// Set the flag on the chat message to indicate that the token has been claimed
tokenControls.setFlag("kidsonbrooms", "tokenClaimed", true);
} else {
// Emit a socket request to update the message to show that the token has been claimed
game.socket.emit('system.kidsonbrooms', {
action: "takeToken",
messageID: message.id,
actorID: actor.id,
});
}
console.log("Send socket message for taking a token");
});
adversityControls.find(".spend-adversity").off("click").click((event) => {
//This entails a lot more, so I offloaded it to a new function
_onSpendAdversityTokens(event, messageToEdit);
});
}
});
// Preload Handlebars templates.
return preloadHandlebarsTemplates();
});
/***
* This handles the incoming socket requests.
* If a player wants to spend tokens on another players roll the gm has to approve first
* if a player wants to claim a token we will update the message since they do not have the permissions
*/
Hooks.once('ready', function() {
game.socket.on('system.kidsonbrooms', async (data) => {
console.log("Socket data received:", data);
if (data.action === "spendTokens") {
console.log(`Request to spend tokens: ${data.tokensToSpend} tokens for ${data.rollActorId} by ${data.spendingActorId}`);
// Only handle the request if the GM is logged in
if (!game.user.isGM) {
console.log("Not GM, ignoring the token spend request.");
return;
}
// The actor who made the roll
const rollActor = game.actors.get(data.rollActorId);
// The actor who is spending tokens
const spendingActor = game.actors.get(data.spendingActorId);
//If these for some reason do not exist
if (!rollActor || !spendingActor) {
console.warn("Actor not found:", data.rollActorId, data.spendingActorId);
return;
}
// Create a confirmation dialog for the GM
new Dialog({
title: "Approve Adversity Token Spending?",
content: `<p>${spendingActor.name} wants to spend ${data.tokenCost} adversity tokens on ${rollActor.name}'s roll to increase it by ${data.tokensToSpend}. Approve?</p>`,
buttons: {
yes: {
label: "Yes",
callback: async () => {
const currentTokens = spendingActor.system.adversityTokens || 0;
// Update the spending actor's adversity token count
await spendingActor.update({ "system.adversityTokens": currentTokens - data.tokenCost });
// Modify the roll message with the new total
await _updateRollMessage(data.rollMessageId, data.tokensToSpend, false);
console.log(`${spendingActor.name} spent ${data.tokensToSpend} tokens, updated roll total to ${roll.cumulativeTotal}`);
ui.notifications.info(`${spendingActor.name} successfully spent ${data.tokensToSpend} tokens.`);
}
},
no: {
label: "No",
callback: () => {
ui.notifications.info(`The GM denied ${spendingActor.name}'s request to spend tokens.`);
}
}
},
default: "yes"
}).render(true);
} else if (data.action === "takeToken") {
// Only handle the request if the GM is logged in
if (!game.user.isGM) {
console.log("Not GM, ignoring the token spend request.");
return;
}
let tokenControls = game.messages.get(data.messageID);
console.log(tokenControls);
// Update the chat message content with the button disabled and text changed
const updatedContent = tokenControls.content.replace(
`<button class="take-adversity" data-actor-id="${data.actorID}">Take Adversity Token</button>`,
`<button class="take-adversity" data-actor-id="${data.actorID}" disabled>Token claimed</button>`
);
console.log("Removing Button");
// Update the message content
tokenControls.update({ content: updatedContent });
// Set the flag on the chat message to indicate that the token has been claimed
tokenControls.setFlag("kidsonbrooms", "tokenClaimed", true);
}
});
});
/***
* This function adds the adversity token to the actor that made the roll and logs it
*
* @param {Event} e - The button click event
* @param {Actor} actor - The actor object that made the roll
*/
async function _onTakeAdversityToken(e, actor) {
e.preventDefault();
// Get the chat message ID (assuming it's stored in the dataset)
const messageId = e.currentTarget.closest('.message').dataset.messageId;
const message = game.messages.get(messageId);
// Add an adversity token to the actor
const currentTokens = actor.system.adversityTokens || 0;
await actor.update({ "system.adversityTokens": currentTokens + 1 });
// Notify the user
ui.notifications.info(`You gained 1 adversity token.`);
console.log(`Gave one adversity token to ${actor.id}`)
}
/***
* This function allows players to spend tokens to change a roll. This will automatically be calculated in their sheet
*
*/
async function _onSpendAdversityTokens(e, rollMessageId) {
e.preventDefault();
// The actor who made the roll
const rollActorId = e.currentTarget.dataset.actorId;
const rollActor = game.actors.get(rollActorId); //technically redundant since it is also done in the main hook, but perfomance is good enuff
// Get the actor of the player who is spending tokens
const spendingPlayerActor = game.actors.get(game.user.character?.id || game.actors.filter(actor => actor.testUserPermission(game.user, "owner"))[0]?.id);
if (!spendingPlayerActor) {
ui.notifications.warn("You don't control any actors.");
return;
}
//Get the tokens to be spend from the input field
const tokenInput = $(e.currentTarget).closest('.adversity-controls').find('.token-input').val();
const tokensToSpend = parseInt(tokenInput, 10);
if (isNaN(tokensToSpend) || tokensToSpend <= 0) {
ui.notifications.warn("Please enter a valid number of tokens.");
return;
}
let tokenCost = tokensToSpend;
// If the player spending tokens is not the owner of the actor who rolled, they spend double
//(note, this is a rule of mine, I have disabled it by default)
if ((!spendingPlayerActor.testUserPermission(game.user, "owner") || spendingPlayerActor.id !== rollActorId) && false) {
tokenCost = tokensToSpend * 2;
}
// Ensure the spending actor has enough adversity tokens
if (spendingPlayerActor.system.adversityTokens < tokenCost) {
ui.notifications.warn(`You do not have enough adversity tokens.`);
return;
}
// Check if the player owns the actor who made the roll
if (spendingPlayerActor.id === rollActorId) {
// The player owns the actor, so they can spend tokens directly without GM approval
const currentTokens = spendingPlayerActor.system.adversityTokens || 0;
// Deduct the tokens from the player
await spendingPlayerActor.update({ "system.adversityTokens": currentTokens - tokenCost });
// Modify the roll message with the new total
await _updateRollMessage(rollMessageId, tokensToSpend, true);
} else {
// The player does not own the actor, so request GM approval to spend the tokens
console.log(`Requesting to spend ${tokensToSpend} tokens for ${rollActor.name} by ${spendingPlayerActor.name} (cost: ${tokenCost})`);
// Emit a socket request to spend tokens
game.socket.emit('system.kidsonbrooms', {
action: "spendTokens",
rollActorId: rollActorId,
spendingActorId: spendingPlayerActor.id, // Send the player's actor who is spending the tokens
tokensToSpend: tokensToSpend,
tokenCost: tokenCost,
rollMessageId: rollMessageId // Pass message ID to update the roll result
});
ui.notifications.info(`Requested to spend ${tokenCost} tokens for ${rollActor.name}`);
}
}
// Helper function to send a new message with the updated roll result
async function _updateRollMessage(rollMessageId, tokensToSpend, isPlayerOfActor) {
const message = game.messages.get(rollMessageId);
if (!message) {
console.error("Message not found with ID:", rollMessageId);
return;
}
// Retrieve current tokens spent from flags, or initialize to 0 if not found
let cumulativeTokensSpent = message.getFlag("kidsonbrooms", "tokensSpent") || 0;
let newTotal = message.getFlag("kidsonbrooms", "newRollTotal") || message.rolls[0].total;
/*if(isPlayerOfActor)
{
// Add the new tokens to the cumulative total
cumulativeTokensSpent += tokensToSpend;
} else {
cumulativeTokensSpent += 2*tokensToSpend;
}*/
cumulativeTokensSpent += tokensToSpend;
newTotal += tokensToSpend;
await message.setFlag("kidsonbrooms", "newRollTotal", newTotal);
// Update the message's flags to store the cumulative tokens spent
await message.setFlag("kidsonbrooms", "tokensSpent", cumulativeTokensSpent);
let newContent = "";
if(cumulativeTokensSpent === 1)
{
newContent = `You have now spent ${cumulativeTokensSpent} token. The new roll total is ${newTotal}.`;
} else {
newContent = `You have now spent ${cumulativeTokensSpent} tokens. The new roll total is ${newTotal}.`;
}
// Create a new chat message to display the updated total
await ChatMessage.create({
speaker: ChatMessage.getSpeaker({ actor: message.speaker.actor }),
content: newContent,
type: CONST.CHAT_MESSAGE_STYLES.OTHER,
});
}