Files
saddbot/commands/utility/calcpayout.js

163 lines
7.0 KiB
JavaScript

const { SlashCommandBuilder } = require('discord.js');
const torn = require('../../torn.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('calcpayout')
.setDescription('Calculate war payout based on participation')
.addIntegerOption(option =>
option.setName('total')
.setDescription('Full war earnings total before cuts (Optional)')
.setRequired(false))
.addIntegerOption(option =>
option.setName('percentage')
.setDescription('Percentage of leader cut (default 10)'))
.addStringOption(option =>
option.setName('method')
.setDescription('Calculation method')
.addChoices(
{ name: 'Participation Based', value: 'flat' },
{ name: 'Score Based', value: 'weighted' },
{ name: 'Attack Based', value: 'attacks' },
)),
async execute(interaction) {
let total = interaction.options.getInteger('total');
const percentage = interaction.options.getInteger('percentage') ?? 10;
const method = interaction.options.getString('method') ?? 'flat';
await interaction.deferReply();
try {
const myFaction = await torn.faction.basic();
const lastWarRaw = await torn.faction.rankedWars({ offset: 0, limit: 1, sort: 'DESC' });
const lastWarID = lastWarRaw[0].id
const lastWar = await torn.faction.rankedWarReport(lastWarID);
const ourFactionId = myFaction.ID || myFaction.id; // API v1 vs v2 fallback checks
const ourFaction = lastWar.factions.find(faction => faction.id === ourFactionId);
const enemyFaction = lastWar.factions.find(faction => faction.id !== ourFactionId);
if (!ourFaction) {
return interaction.editReply('Could not find our faction in the last war report.');
}
// Auto-calculate total if not provided
if (!total) {
let calculatedTotal = 0;
const rewards = ourFaction.rewards;
if (rewards && rewards.items) {
const itemIds = Array.isArray(rewards.items)
? rewards.items.map(i => i.id || i.ID)
: Object.keys(rewards.items);
for (const itemId of itemIds) {
const qt = Array.isArray(rewards.items)
? rewards.items.find(i => (i.id == itemId || i.ID == itemId)).quantity
: rewards.items[itemId];
const itemData = await torn.item(itemId);
if (itemData && itemData.value && itemData.value.market_price) {
calculatedTotal += itemData.value.market_price * qt;
}
}
}
if (calculatedTotal > 0) {
total = calculatedTotal;
} else {
return interaction.editReply('No total provided and could not calculate rewards from the war report.');
}
}
// Calculate cuts
const leaderCut = Math.ceil(total * (percentage / 100));
const pool = total - leaderCut;
const members = ourFaction.members;
const participants = [];
const nonParticipants = [];
let totalScore = 0;
let totalAttacks = 0;
// Filter members
for (const memberId in members) {
const member = members[memberId];
if (member.id == myFaction.leader_id) {
console.log(`User ${member.name} skipped (Leader exclusion).`);
continue;
}
if (member.attacks > 0) {
participants.push(member);
totalScore += member.score;
totalAttacks += member.attacks;
} else {
nonParticipants.push(member);
}
}
// Sort logic
if (method === 'attacks') {
participants.sort((a, b) => b.attacks - a.attacks);
} else {
participants.sort((a, b) => b.score - a.score);
}
let message = `# War Payout: ${ourFaction.name} vs ${enemyFaction.name}\n`;
message += `**Total Earnings:** $${total.toLocaleString()}${!interaction.options.getInteger('total') ? ' (Auto-Calculated)' : ''}\n`;
message += `**Leader Cut (${percentage}%):** $${leaderCut.toLocaleString()} (Yameii)\n`;
message += `**Distributable Pool:** $${pool.toLocaleString()}\n`;
let methodText = 'Participation Based';
if (method === 'weighted') methodText = 'Score Based';
if (method === 'attacks') methodText = 'Attack Based';
message += `**Calculation Method:** ${methodText}\n`;
message += `**Participants:** ${participants.length}\n\n`;
message += `## Payouts\n`;
if (method === 'weighted') {
participants.forEach(member => {
const share = (member.score / totalScore);
const payout = Math.floor(pool * share);
message += `- **${member.name}**: $${payout.toLocaleString()} (${(share * 100).toFixed(2)}% of pool | Score: ${member.score})\n`;
});
} else if (method === 'attacks') {
participants.forEach(member => {
const share = (member.attacks / totalAttacks);
const payout = Math.floor(pool * share);
message += `- **${member.name}**: $${payout.toLocaleString()} (${(share * 100).toFixed(2)}% of pool | Attacks: ${member.attacks})\n`;
});
} else {
const payout = Math.floor(pool / participants.length);
participants.forEach(member => {
message += `- **${member.name}**: $${payout.toLocaleString()}\n`;
});
}
if (nonParticipants.length > 0) {
message += `\n## Non-Participants\n`;
message += nonParticipants.map(m => m.name).join(', ');
}
// Discord message limit is 2000 chars. If we have many members, it might split.
// For now, assuming it fits or valid first chunk.
if (message.length > 2000) {
const chunks = message.match(/[\s\S]{1,1900}/g) || [];
for (const chunk of chunks) {
if (chunk === chunks[0]) await interaction.editReply(chunk);
else await interaction.followUp(chunk);
}
} else {
await interaction.editReply(message);
}
} catch (error) {
console.error(error);
await interaction.editReply('An error occurred while calculating payouts.');
}
},
};