diff --git a/deploy-commands.js b/deploy-commands.js index 61af8d8..e700363 100644 --- a/deploy-commands.js +++ b/deploy-commands.js @@ -27,6 +27,98 @@ const commands = [ { name: 'Framework', value: 'framework' }, ) ) + ), + new SlashCommandBuilder() + .setName('simulate') + .setDescription('Simulate different type of dice rolls.') + .addSubcommand(subcommand => + subcommand + .setName('simple') + .setDescription('Simulate a simple dice roll.') + ) + .addSubcommand(subcommand => + subcommand + .setName('extended') + .setDescription('Simulate an extended dice roll.') + .addNumberOption(option => + option + .setName('dicepool') + .setDescription('The dicepool of the roll.') + .setRequired(true) + ) + .addNumberOption(option => + option + .setName('threshold') + .setDescription('The threshold of the roll.') + .setRequired(true) + ) + .addNumberOption(option => + option + .setName('interval') + .setDescription('The interval of the roll.') + .setRequired(true) + ) + .addStringOption(option => + option + .setName('interval-unit') + .setDescription('The unit of the interval. Defaults to Hours.') + .setRequired(false) + .addChoices( + { name: 'Seconds', value: 'seconds' }, + { name: 'Minutes', value: 'minutes' }, + { name: 'Hours', value: 'hours' }, + { name: 'Days', value: 'days' }, + { name: 'Weeks', value: 'weeks' }, + { name: 'Months', value: 'months' }, + ) + ) + .addNumberOption(option => + option + .setName('max-edge') + .setDescription('The maximum number of edge to use.') + .setRequired(false) + ) + .addStringOption(option => + option + .setName('edgetype') + .setDescription('What type of edgeing should be used ?') + .addChoices( + { name: 'None', value: 'none' }, + { name: 'Turn Up', value: 'up' }, + { name: 'Reroll', value: 'reroll' }, + { name: 'Smart', value: 'smart' }, + { name: 'All', value: 'all' }, + ) + ) + .addStringOption(option => + option + .setName('edgerefresh') + .setDescription('How often should the edge refresh ?') + .addChoices( + { name: 'Never', value: 'none' }, + { name: 'Every Roll', value: 'fill' }, + { name: '1 Edge per Roll', value: 'once' }, + { name: '2 Edge per Roll', value: 'twice' }, + ) + ) + .addNumberOption(option => + option + .setName('amount') + .setDescription('How many times should the roll be simulated ? WARNING this service is pricey !') + .setRequired(false) + ) + .addBooleanOption(option => + option + .setName('hidden') + .setDescription('Should the result be hidden ?') + .setRequired(false) + ) + .addBooleanOption(option => + option + .setName('true-random') + .setDescription('Should the dice be truly random ?') + .setRequired(false) + ) ) ] .map(command => command.toJSON()); diff --git a/index.js b/index.js index 325c013..9477835 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,7 @@ require('dotenv').config(); +const { EOL } = require('os'); +const { randomInt } = require('node:crypto'); const { Client, GatewayIntentBits } = require('discord.js'); const token = process.env.TOKEN; @@ -12,18 +14,177 @@ client.once('ready', () => { }); client.on('interactionCreate', async interaction => { - if (!interaction.isCommand()) return; + if (!interaction.isCommand()) { + console.log('Not a command'); + return; + } console.log(interaction.commandName); - let commandName = interaction.commandName; + switch (interaction.commandName) { + case "generate": + switch (interaction.options.getSubcommand()) { + case "host": + let rating = interaction.options.getNumber("rating"); + let type = interaction.options.getString("type") ?? "foundation"; + await interaction.reply({files: ["./france-in-pictures-beautiful-places-to-photograph-eiffel-tower.jpg"]}); + break; + + default: + await interaction.reply({ content: 'Invalid subcommand!', ephemeral: true }); + break; + } + break; + + case "simulate": + await interaction.deferReply(); + switch (await interaction.options.getSubcommand()) { + case "simple": + await interaction.reply({ content: 'Simple dice roll!', ephemeral: true }); + break; - if (commandName === "generate") { - if (interaction.options.getSubcommand() === "host") { - let rating = interaction.options.getNumber("rating"); - let type = interaction.options.getString("type") ?? "foundation"; - await interaction.reply({files: ["./france-in-pictures-beautiful-places-to-photograph-eiffel-tower.jpg"]}); - } + case "extended": + // Variables from Discord command + const dicepool = interaction.options.getNumber("dicepool"); + const threshold = interaction.options.getNumber("threshold"); + const interval = interaction.options.getNumber("interval") ?? 1; + const intervalUnit = interaction.options.getString("interval-unit") ?? "Hours"; + const simAmount = interaction.options.getNumber("amount") ?? 10000; + const hidden = interaction.options.getBoolean("hidden") ?? false; + const edgeType = interaction.options.getString("edgetype") ?? "none"; + const edgeRefresh = interaction.options.getString("edgerefresh") ?? "none"; + const maxEdge = interaction.options.getNumber("max-edge") ?? 6; + const useTrueRandom = interaction.options.getBoolean("true-random") ?? false; + + // Check if dicepool is valid + if (dicepool < 1 || dicepool > 100) { + await interaction.editReply({ content: 'Dicepool must be between 1 and 100!', ephemeral: true }); + return; + } + + // Arrays to build the final message + const warnings = []; + const parameters = []; + const calculatedResults = []; + const realResults = []; + let message = ""; + + // Variables for the simulation + let hits = 0; + let glitches = 0; + let criticalGlitches = 0; + let iterations = 0; + let edge = maxEdge; + let failed = false; + + parameters.push(`Dicepool: ${dicepool}`); + parameters.push(`Threshold: ${threshold}`); + parameters.push(`Interval: ${interval} ${intervalUnit}`); + parameters.push(`Amount of simulations: ${simAmount}`); + parameters.push(`Edge: ${edgeType}`); + parameters.push(`Edge refresh: ${edgeRefresh}`); + parameters.push(`Max edge: ${maxEdge}`); + parameters.push(`Use true random: ${useTrueRandom}`); + + message = `Parameters:${EOL}\t${parameters.join(EOL + '\t')}${EOL}${EOL}`; + + calculatedResults.push("Calculated Results (Can't Use Edge): " + EOL); + + + calculatedResults.push(`Calc Rolls : `); + calculatedResults.push(`Hits Bought: `); + + realResults.push("Real Results: " + EOL); + + // Roll dice in dicepool using the fillArrayWithRandomDice Function till we have reached the threshold or above + while (hits < threshold) { + + if (dicepool - iterations === 0) { + failed = true; + break; + } + + let rolls = await fillArrayWithRandomDice(new Array(dicepool), useTrueRandom); + + // TODO: encapsulate into functions + // TODO: Add smart edgetype or warning + // Edge Section: + switch (edgeType) { + + case 'up': + console.log(`Pre Edge Up: ${rolls}`); + rolls.forEach((roll, index) => { + if (roll === 4 && edge >= 2) { + rolls[index] = 5; + edge -= 2; + } + }); + console.log(`Post Edge Up: ${rolls}`); + break; + + + case 'reroll': + console.log(`Pre Reroll: ${rolls.sort().reverse()}`); + if (edge >= 4) { + let rerolls = await fillArrayWithRandomDice(rolls.filter(roll => roll < 5), useTrueRandom); + rolls = rolls.filter(roll => roll >= 5).concat(rerolls); + } + console.log(`Post Reroll: ${rolls.sort().reverse()}`); + break; + } + + let tempHits = rolls.reduce((a, b) => b >= 5 ? a + 1 : a, 0); + let tempGlitches = rolls.reduce((a, b) => b === 1 ? a + 1 : a, 0) > rolls.length / 2 ? 1 : 0; + + if (tempHits === 0 && tempGlitches === 1) { + criticalGlitches++; + tempGlitches--; + } + + hits += tempHits; + glitches += tempGlitches; + + iterations += 1; + + + switch (edgeRefresh) { + case 'fill': + edge = maxEdge; + break; + + case 'once': + if (edge < maxEdge) { + edge++; + } + break; + + case 'twice': + if (edge < maxEdge - 1) { + edge += 2; + } + break; + + default: + break; + } + } + + if (failed) { + await interaction.editReply({ content: 'Failed to reach threshold!', ephemeral: hidden }); + } else { + await interaction.editReply({ content: message, ephemeral: hidden }); + } + break; + } + break; } -}) +}); + +async function fillArrayWithRandomDice(arr, trueRandom = false) { + for (let i = 0; i < arr.length; i++) { + arr[i] = await randomInt(1, 7); + } + return arr; +} + client.login(token); \ No newline at end of file