163 lines
7.0 KiB
JavaScript
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.');
|
|
}
|
|
},
|
|
}; |