I'm making a giveaway discord bot with xp system. Adding xp to the user who has won works but automatically adding him a new level if he has enough xp don't. This code gives user too many levels
ex: User is level 1. He wins 1000xp from giveaway and his level should automatically increased to 10 (You can calculate that with the formula below)
winner is the person who won giveaway
100 * x * (x / 20) + (x * 50) is the formula for calculating new level xp
let prize = args[1]
xp.findOne({
userID: winner.id
}, (err, data) => {
if (err) throw err
if (!data) {
const newData = new xp({
serverID: message.guild.id,
name: winner.tag,
userID: winner.id,
xp: parseInt(prize),
level: 1,
})
let nxtLvl = newData.level + 1
let newLevel = 100 * nxtLvl * (nxtLvl / 20) + (nxtLvl * 50)
for (var i = 0; i < newLevel; i++) {
if (newLevel <= newData.xp) {
newData.level = newData.level + 1
}
}
newData.save().catch(e => console.log(e))
})
Related
I'm currently making a Discord bot using JavaScript, the bot features many commands, but I just came across this flaw, the flaw concerns the Math.Random() object, except it sometimes returns negative numbers, does anyone have a solution to this using one of the methods?
Here's the code::
let db = require(`quick.db`)
module.exports = {
name: "rob",
description: "Rob your friends to get money",
category: "economy",
usage: "rob [member]",
timeout: "1 minute",
run: async (client, message, args) => {
let victim = message.mentions.members.first()
if(!victim) {
return message.channel.send(`:x: Please specify a member to rob!`)
}
let victimW = await db.get(`wallet_${victim.id}`)
if(victimW === null) {
message.channel.send(`:x: That user doesn't have money in his wallet, are you sure you want to rob him?`)
}
let random = Math.floor(Math.random() * 100);
if(random < 30) {
let victimWallet = await db.get(`wallet_${victim.id}`)
let userWallet = await db.get(`wallet_${message.author.id}`)
let amount = Math.floor(Math.random() * victimWallet);
const messages = [`POG! You robbed **${victim.username}** and got **${amount}** :coin:!`, `oo seems like you robbed **${victim.displayName}** and got **${amount}** :coin:!`]
const randomMessage = messages[Math.floor(Math.random() * messages.length)];
message.channel.send(randomMessage)
await db.set(`wallet_${victim.id}`, amount - victimWallet)
await db.set(`wallet_${message.author.id}`, userWallet + amount)
} else if(random > 30) {
let authorWallet = await db.get(`wallet_${message.author.id}`)
let wallet1 = await db.get(`wallet_${victim.id}`)
let amountPaid = Math.floor(Math.random() * authorWallet);
const message1 = [`Pfft noob, you got caught and paid **${victim.displayName} ${amountPaid}** :coin: lol!`, `lel ure such a noob, you paid **${victim.displayName} ${amountPaid}** :coin:!`, `u suck and you paid **${amountPaid}** :coin: to **${victim.displayName}**, such a noob lol!`]
const randomMessage1 = message1[Math.floor(Math.random() * message1.length)];
return message.channel.send(randomMessage1)
await db.set(`wallet_${message.author.id}`, (amountPaid - authorWallet));
await db.set(`wallet_${message.author.id}`, (amountPaid + wallet1));
}
}
}
It all works except sometimes it just sends a negative number, can anyone tell me a math method that make sure the number isn't a negative?
Thanks.
You should verify the amount to steal before stealing it
if (random < 30) {
let victimWallet = await db.get(`wallet_${victim.id}`)
let userWallet = await db.get(`wallet_${message.author.id}`)
let amount = Math.floor(Math.random() * victimWallet);
//Check the amount of victim wallet, return if not enough coins to steal
if (amount > victimwallet) {
console.log("Not enough coins to steal") return;
}
const messages = [`POG! You robbed **${victim.username}** and got **${amount}** :coin:!`, `oo seems like you robbed **${victim.displayName}** and got **${amount}** :coin:!`]
const randomMessage = messages[Math.floor(Math.random() * messages.length)];
message.channel.send(randomMessage)
await db.set(`wallet_${victim.id}`, amount - victimWallet)
await db.set(`wallet_${message.author.id}`, userWallet + amount)
}
If the point is just to make the output to always positive, you can try this "if condition"
var x = -10 //change it with any number
if (x < 0){
x = x * -1
}
console.log(x) //it should will always show positive number
alternatively you can always use Match.abs(x)
var x = -10;
console.log(Math.abs(x));
I'm currently making a command where a random percentage and race or ethnicity pops up at the end, but the issue I'm facing is that I'm rather uncertain on how to grab the names of the ethnicities out of the .json file and make them show up.
This is the current coding:
const dna = require("../dna.json")
module.exports.run = async (bot, message, args) => {
if (message.author.bot) return;
number = 100
percentage = Math.floor (Math.random() * (number - 1 + 1)) +1;
dnaNumber = 5
dnaResults = Math.floor (Math.random() * (dnaNumber - 1 + 1)) +1;
let embed = new Discord.MessageEmbed()
embed.setTitle('DNA Test 🧬')
embed.setColor('RANDOM')
embed.setDescription('You are ' + percentage + '% ' + dnaResults + '!')
embed.setTimestamp()
message.channel.send(embed)
};
...and this is what is shot out by the bot:
DNA Test
The .json file appears like this:
{
"jewish": "Jewish",
"black": "Black",
"hispanic": "Hispanic",
"british": "British",
"german": "German"
}
You can use Object.key() or Object.values() to get the object's key and values in an array. In your case, getting the values would be what you after
const dna = require("../dna.json")
module.exports.run = async (bot, message, args) => {
if (message.author.bot) return;
number = 100
percentage = Math.floor (Math.random() * (number - 1 + 1)) +1;
dnaNumber = 5
dnaResults = Math.floor (Math.random() * (dnaNumber - 1 + 1)) +1;
var dnaEthnicities = Object.values(dna)[dnaResults-1] //add this
let embed = new Discord.MessageEmbed()
embed.setTitle('DNA Test 🧬')
embed.setColor('RANDOM')
embed.setDescription('You are ' + percentage + '% ' + dnaEthnicities + '!') //change to dnaEthnicities
embed.setTimestamp()
message.channel.send(embed)
};
I want to compound interest on a weekly/fortnightly/monthly/annual basis.
I also want an option to have a deposit amount that can be added in.
I have already tried the standard formula of calculating the final amount accrued, as seen here:
(source: gstatic.com)
For example here is my method for calculating the interest compounding weekly:
function calculateWeekly(state: any) {
const { savings, deposit ,interest, timePeriodSelector, timePeriodLength } = state;
let numberOfYears = 0;
if (timePeriodSelector === "weekly") {
numberOfYears = timePeriodLength / weeksInAYear;
} else if (timePeriodSelector === "fortnightly") {
numberOfYears = (timePeriodLength / weeksInAYear) * 2;
} else if (timePeriodSelector === "monthly") {
numberOfYears = (timePeriodLength / weeksInAYear) * weeksInAMonth;
} else if (timePeriodSelector === "annually") {
numberOfYears = (timePeriodLength / weeksInAYear) * weeksInAYear;
}
const weeklyRate = interest / 100 / weeksInAYear;
const lengthOfCompunding = numberOfYears * weeksInAYear;
let startingFigure = parseInt(savings) + parseInt(deposit);
//total gets added on for every time cycle of week
let total =
(startingFigure * (Math.pow(1 + weeklyRate, lengthOfCompunding) - 1)) / weeklyRate;
return roundToTwoDP(total);
}
The issue with the above code is that the deposit gets added into the calculation every time the interest accrues. So a deposit of $10 weekly for 10 weeks will actually get added up to $100.
I attempted a method to accrue the interest using a loop for each week here:
// loops how many times to compound the interest
for(let i = numberOfYears - (1/weeksInAYear); i > 0; i-= (1/weeksInAYear)){
let interestGained = (total * (Math.pow((1 + weeklyRate), lengthOfCompunding))) - total;
total += interestGained + savings;
}
Thanks for any help!
This should do what you want:
const range = (min, max) => {
const size = 1 + max - min
return [...Array(size).keys()].map(n => n + min)
}
const weeksInAYear = 52
const addWeeklyInterest = interestRatePerWeek => (savings, _) => savings + savings * interestRatePerWeek
const calculateTotal = (savings, numberOfYears, interestRatePerWeek) => {
const numberOfWeeks = numberOfYears * weeksInAYear
return range(1, numberOfWeeks).reduce(addWeeklyInterest(interestRatePerWeek), savings)
}
console.log(calculateTotal(1000.00, 1, 0.02))
Output is 2800.328185448178. You might want to round that for display purposes, but also keep in mind that if accuracy is important, you can't use floating-point numbers.
My 'problem' is more of a feature I am looking to add, I used this guide:
https://anidiots.guide/coding-guides/sqlite-based-points-system
I changed the code a little to mainly give you a random amount of XP, I am looking to edit how much XP is needed to level up.
Right now it is a static amount, being 5000 needed to level up. I am trying to make it increase the amount needed to level up by an extra 5000 each time you level up.
Currently, it works like this:
Level 1 to 2 = 5000 total XP needed
Level 2 to 3 = 10000 total xp needed
Currently, the amount needed to level up is always 5000 between each level.
This is how I want it to work:
Level 1 to 2 = 5000 total XP needed
Level 2 to 3 = 15000 total XP needed
Which will be 5000 to level 2 and then 10000 to level 3 and so on (increasing the amount needed by 5000 each time you level up)
I spent the best part of 2 hours trying different things, and mainly looking at the code being completely out of my depth.
I believed that doing something like this would work, but I have no idea if it's correct
if (score.level == '1') {
nextLevel = 5000
}
if (score.level == '2' {
nextLevel = 10000
}
I highly doubt this is correct, otherwise, my message event would be very long, as I plan to have 100 levels
The code in its entirety:
let score;
if (message.guild) {
score = bot.getScore.get(message.author.id, message.guild.id);
if (!score) {
score = {
id: `${message.guild.id}-${message.author.id}`,
user: message.author.id,
guild: message.guild.id,
points: 0,
level: 1,
};
}
const xpAdd = Math.floor(Math.random() * 10) + 50;
const curxp = score.points;
const curlvl = score.level;
const nxtLvl = score.level * 5000;
score.points = curxp + xpAdd;
if (nxtLvl <= score.points) {
score.level = curlvl + 1;
const lvlup = new MessageEmbed()
.setAuthor(
`Congrats ${message.author.username}`,
message.author.displayAvatarURL()
)
.setTitle('You have leveled up!')
.setThumbnail('https://i.imgur.com/lXeBiMs.png')
.setColor(color)
.addField('New Level', curlvl + 1);
message.channel.send(lvlup).then(msg => {
msg.delete({
timeout: 10000,
});
});
}
bot.setScore.run(score);
}
The code as-is works fine and as expected, but as-is is not very good, as there is no reward from going from level 30-31 as it's the same amount of XP needed to get from level 1-2
Here's a little formula which should do the trick (if I understand your problem correctly):
const nxtLvl = 5000 * (Math.pow(2, score.level) - 1);
This gives the following xp requirements to level up:
1->2: 5000
2->3: 15000
3->4: 35000
4->5: 75000
5->6: 155000
Try something like this:
const levels = [0, 5000, 15000, 30000, 50000, 75000];
....
nextLevel = levels[score.level];
Edit
#Dan you mean like this:
nextLevel = 5000 * Math.round( score.level * (score.level + 1) / 2 );
Here Is Code I'm Using
But Problem Is I Can't Add Or Remove XP
Also I Made It With Scratch So I'm Being Mad Understanding This
let Discord;
let Database;
if (typeof window !== "undefined") {
Discord = DiscordJS;
Database = EasyDatabase;
} else {
Discord = require("discord.js");
Database = require("easy-json-database");
}
const delay = (ms) => new Promise((resolve) => setTimeout(() => resolve(), ms));
const s4d = {
Discord,
client: null,
tokenInvalid: false,
reply: null,
joiningMember: null,
database: new Database("./db.json"),
checkMessageExists() {
if (!s4d.client) throw new Error('You cannot perform message operations without a Discord.js client')
if (!s4d.client.readyTimestamp) throw new Error('You cannot perform message operations while the bot is not connected to the Discord API')
}
};
s4d.client = new s4d.Discord.Client({
fetchAllMembers: true
});
s4d.client.on('raw', async (packet) => {
if (['MESSAGE_REACTION_ADD', 'MESSAGE_REACTION_REMOVE'].includes(packet.t)) {
const guild = s4d.client.guilds.cache.get(packet.d.guild_id);
if (!guild) return;
const member = guild.members.cache.get(packet.d.user_id) || guild.members.fetch(d.user_id).catch(() => {});
if (!member) return;
const channel = s4d.client.channels.cache.get(packet.d.channel_id);
if (!channel) return;
const message = channel.messages.cache.get(packet.d.message_id) || await channel.messages.fetch(packet.d.message_id).catch(() => {});
if (!message) return;
s4d.client.emit(packet.t, guild, channel, message, member, packet.d.emoji.name);
}
});
var member_xp, member_level;
s4d.client.login('My Dumb Token').catch((e) => {
s4d.tokenInvalid = true;
s4d.tokenError = e;
});
s4d.client.on('message', async (s4dmessage) => {
if (!((s4dmessage.member).user.bot)) {
member_xp = s4d.database.get(String(('xp-' + String(s4dmessage.author.id))));
member_level = s4d.database.get(String(('level-' + String(s4dmessage.author.id))));
if (!member_xp) {
member_xp = 0;
} else if (!member_level) {
member_level = 0;
}
s4d.database.set(String(('xp-' + String(s4dmessage.author.id))), (member_xp + 1));
member_xp = member_xp + 1;
if (member_xp > 100) {
s4d.database.set(String(('level-' + String(s4dmessage.author.id))), (member_level + 1));
member_level = member_level + 1;
s4dmessage.channel.send(String((['Congratulations, ', s4dmessage.member, 'you jumped to level ', member_level, '!!'].join(''))));
}
if ((s4dmessage.content) == '-level') {
s4dmessage.channel.send(String(([s4dmessage.member, ', you are currently level: ', member_level].join(''))));
} else if ((s4dmessage.content) == '-xp') {
s4dmessage.channel.send(String(([s4dmessage.member, ', you need ', 100 - member_xp, ' to jump to level ', member_level + 1].join(''))));
}
}
});
s4d;
I need to use plain JavaScript to convert an amount of experience points to an amount of hours played by a number of fixed rates.
For example:
A player has 1,129,518 experience points.
The amount of experience points that are gained per hour depends on the amount of xp one already has. They would be arranged something like this:
above 0 xp: 8,000 xp/h
above 2,107 xp: 20,000 xp/h
above 101,333 xp: 45,000 xp/h
above 1,210,421 xp: 68,500 xp/h
above 13,034,431 xp: 75,000 xp/h
I'm struggling to find a way to use these xp rates to convert a given amount of experience points to hours played, using at least somewhat elegant Javascript.
I just end up with a cunfusing mess of if/else statements that ends up failing because of math errors.
Any Math wizards out there that can help me? Thanks.
Code Sample: I would go from here
if(xp === 0){
return 0;
}else if( 2107 >= xp > 0){
const result = (xp/8000).toFixed(1);
return result;
}else if(101333 >= xp > 2107){
const result = ((2107/8000) + ((xp-2107)/20000)).toFixed(1);
return result;
}else if(1210421 >= xp > 101333){
...
}
As you can see it would quickly get out of hand if theres alot of different tiers.
First of all, you should write your if statements like this:
if( 2107 >= xp && xp > 0){
...
}
Next, try thinking about XP as buckets of XP and each bucket having different value/price. Go from most valuable bucket to least valuable, and for each bucket calculate hours and subtract amount of XP that was used to calculate those hours.
You can do this in while loop:
let hours = 0;
while(XP > 0)
{
// figure out bucket you are in, your if statements are fine for that.
let value = 0;
let lowerBoundary = 0;
if( 101333 >= xp && xp > 2107){
value = 20000;
lowerBoundary = 2107;
// you need lower boundary to figure out how many XP you used in this bucket.
}
// else if...
const usedInBucket = XP - lowerBoundary;
hours += usedInBucket / value; // simply calculate hours needed
XP -= usedInBucket;
}
This is what I came up with:
const steps = [{
min: 0,
val: 8000
},
{
min: 2107,
val: 20000
},
{
min: 101333,
val: 45000
},
{
min: 1210421,
val: 68500
},
{
min: 13034431,
val: 75000
},
].sort((a, b) => b.min - a.min);
//using for loop
function xpToHours(xp = 0) {
let h = 0;
steps.forEach(s => {
let amt = Math.max(xp - s.min, 0);
h += amt * s.val;
xp -= amt;
});
return h;
}
//using reduce
function xpToHours2(xp = 0) {
return steps.reduce((h, s) => {
let amt = Math.max(xp - s.min, 0);
xp -= amt;
return h + amt * s.val;
}, 0)
}
[0, 1000, 2000, 3000, 1000000].forEach(xp => console.log(xp, xpToHours(xp)));
[0, 1000, 2000, 3000, 1000000].forEach(xp => console.log(xp, xpToHours2(xp)));
To explain:
steps is just an array containing your different stages. It is sorted by the minimum xp from highest to lowest.
Then we just iterate over this array calculating amt which is the xp used up by the currently highest stage. The needed time is therefore amt * currentstep.val and the xp is reduced by the calculated amount for the next stage.
The easiest way to do this is with a sorted array of ranges and Array.prototype.find
// Make sure this is sorted desc
const expRanges = [{
above: 101333,
xph: 45000
},
{
above: 2107,
xph: 20000
},
{
above: 0,
xph: 8000
}
];
function findExpPerHour(xp) {
return expRanges.find(range => range.above < xp).xph;
}
// TESTS
const playerExpTests = [{
name: "P1",
xp: 12
}, {
name: "P2",
xp: 12000
}, {
name: "P3",
xp: 200000
}, {
name: "P4",
xp: 99999999
}];
playerExpTests.forEach(p => {
console.log(p.name, "Exp per hour:", findExpPerHour(p.xp));
});