New level algorithm, k9 level command, k9 setxp command.
This commit is contained in:
parent
0d6c6240a4
commit
266d17aadb
@ -2,5 +2,6 @@
|
||||
"watch": [ "src" ],
|
||||
"ext": ".ts,.js,.tsx,.json",
|
||||
"ignore": [],
|
||||
"signal": "SIGINT",
|
||||
"exec": "ts-node --project ./tsconfig.json ./src/Main.ts -- --verbose"
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
{
|
||||
"name": "k9",
|
||||
"version": "0.0.1",
|
||||
"version": "0.1.0",
|
||||
"description": "A simple discord bot for tracking user levels.",
|
||||
"main": "build/Main.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon .",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"dev": "nodemon ."
|
||||
},
|
||||
"author": "Auri Collings",
|
||||
"license": "UNLICENSED",
|
||||
|
@ -142,7 +142,7 @@ export default class Bot {
|
||||
if (!msg.content.startsWith(this.config.options.prefix + ' ')) return;
|
||||
const full = msg.content.substr(this.config.options.prefix.length + 1).trim();
|
||||
const command = full.substr(0, full.indexOf(' ') === -1 ? full.length : full.indexOf(' ')).toLowerCase().trimLeft();
|
||||
const args = command.substr(command.length).trimLeft().split(' ');
|
||||
const args = full.substr(command.length).trimLeft().split(' ');
|
||||
const cmd = this.commands[command];
|
||||
if (typeof cmd === 'function') cmd(msg, command, args);
|
||||
else if (typeof cmd === 'object') cmd.trigger(msg, command, args);
|
||||
|
49
src/Plugin/Level/Calc.ts
Normal file
49
src/Plugin/Level/Calc.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { ExperienceConfig } from './LevelPlugin';
|
||||
|
||||
|
||||
/**
|
||||
* Returns the level the provided XP reaches.
|
||||
*
|
||||
* @param {ExperienceConfig} config - The experience config to use for the calculations.
|
||||
* @param {number} experience - The experience to do the calculation for.
|
||||
*/
|
||||
|
||||
export function xpToLevel(config: ExperienceConfig, experience: number) {
|
||||
if (experience < config.offset) return 0;
|
||||
let level = 1;
|
||||
experience -= config.offset;
|
||||
while ((experience -= config.base * Math.sqrt(config.multiplier * level)) >= 0) level++;
|
||||
return level;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the amount of XP required to go from level 0 to this level.
|
||||
*
|
||||
* @param {ExperienceConfig} config - The experience config to use for the calculations.
|
||||
* @param {number} level - The level to do the calculation for.
|
||||
*/
|
||||
|
||||
export function levelToXp(config: ExperienceConfig, level: number) {
|
||||
if (level == 0) return 0;
|
||||
let experience = config.offset;
|
||||
level--;
|
||||
while (level > 0) {
|
||||
experience += config.base * Math.sqrt(config.multiplier * level--);
|
||||
}
|
||||
return experience;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the amount of XP required to go from this level to the level above.
|
||||
*
|
||||
* @param {ExperienceConfig} config - The experience config to use for the calculations.
|
||||
* @param {number} level - The level to do the calculation for.
|
||||
*/
|
||||
|
||||
export function xpInLevel(config: ExperienceConfig, level: number) {
|
||||
if (level < 0) return 0;
|
||||
if (level == 0) return config.offset;
|
||||
return config.base * Math.sqrt(config.multiplier * level);
|
||||
}
|
@ -1,15 +1,16 @@
|
||||
import * as Discord from 'discord.js';
|
||||
import { LevelPluginGuild, LevelPluginUser } from './LevelPlugin';
|
||||
|
||||
import { LevelPluginGuild, ExperienceConfig, LevelRole } from './LevelPlugin';
|
||||
|
||||
export default class LevelCommand {
|
||||
constructor(private roles: { name: string, experience: number, total_experience: number, role: number }[]) {}
|
||||
constructor(_experience: ExperienceConfig, _roles: LevelRole[]) {}
|
||||
|
||||
async trigger(msg: Discord.Message) {
|
||||
if (!msg.guild) return;
|
||||
const guild = await LevelPluginGuild.findOne({ id: msg.guild.id });
|
||||
if (!guild) return;
|
||||
|
||||
const users = (await LevelPluginUser.find({ guild_id: guild._id }).sort({ level: 'desc' }).limit(12)).filter(u => u.id);
|
||||
// const users = (await LevelPluginUser.find({ guild_id: guild._id }).sort({ level: 'desc' }).limit(12)).filter(u => u.id);
|
||||
|
||||
const embed = new Discord.MessageEmbed()
|
||||
.setAuthor("Leaderboard", "https://i.imgur.com/LaPvO6n.png")
|
||||
@ -18,22 +19,22 @@ export default class LevelCommand {
|
||||
.setFooter(`Requested by ${msg.member!.displayName}`, msg.author.avatarURL({ size: 32 })!)
|
||||
.setTimestamp();
|
||||
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
try {
|
||||
let name = (await msg.guild.members.fetch(users[i].id ?? "0")).displayName;
|
||||
let currentRole = null;
|
||||
for (let role of this.roles) {
|
||||
if (role.total_experience < users[i].experience) currentRole = role;
|
||||
else break;
|
||||
}
|
||||
// for (let i = 0; i < users.length; i++) {
|
||||
// try {
|
||||
// let name = (await msg.guild.members.fetch(users[i].id ?? "0")).displayName;
|
||||
// let currentRole = null;
|
||||
// for (let role of this.roles) {
|
||||
// if (role.total_experience < users[i].experience) currentRole = role;
|
||||
// else break;
|
||||
// }
|
||||
|
||||
if (name.length >= 20) name = name.substr(0, 18) + "...";
|
||||
embed.addField(
|
||||
`⠀${i < 3 ? "**" : ""}${i + 1}) ${name}${i < 3 ? "**" : ""}`,
|
||||
`⠀Level ${currentRole?.name ?? 'Potato'} • ${Math.floor(users[i].experience ?? 0)} XP`, true);
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
// if (name.length >= 20) name = name.substr(0, 18) + "...";
|
||||
// embed.addField(
|
||||
// `⠀${i < 3 ? "**" : ""}${i + 1}) ${name}${i < 3 ? "**" : ""}`,
|
||||
// `⠀Level ${currentRole?.name ?? 'Potato'} • ${Math.floor(users[i].experience ?? 0)} XP`, true);
|
||||
// }
|
||||
// catch (e) {}
|
||||
// }
|
||||
|
||||
msg.channel.send({ embed }).catch(_ => { /* Missing send permissions. */ });
|
||||
}
|
||||
|
@ -1,36 +1,50 @@
|
||||
import * as Discord from 'discord.js';
|
||||
|
||||
import { LevelPluginGuild, LevelPluginUser } from './LevelPlugin';
|
||||
import * as Calc from './Calc';
|
||||
import { LevelPluginGuild, LevelPluginUser, ExperienceConfig, LevelRole } from './LevelPlugin';
|
||||
|
||||
const PROGRESS_SEGMENTS = 17;
|
||||
const PROGRESS_FULL = "█";
|
||||
const PROGRESS_EMPTY = "░";
|
||||
|
||||
export default class LevelCommand {
|
||||
constructor(private roles: { name: string, experience: number, total_experience: number, role: number }[]) {}
|
||||
constructor(private experience: ExperienceConfig, private roles: LevelRole[]) {}
|
||||
|
||||
async trigger(msg: Discord.Message) {
|
||||
if (!msg.guild) return;
|
||||
const guild = await LevelPluginGuild.findOne({ id: msg.guild.id });
|
||||
if (!guild) return;
|
||||
const user = (await LevelPluginUser.findOne({ guild_id: guild._id, id: msg.author.id })) ??
|
||||
{ experience: 0, level: 0 };
|
||||
const user = (await LevelPluginUser.findOne({ guild_id: guild._id, id: msg.author.id })) ?? { experience: 0, level: 0 };
|
||||
|
||||
let currentRole = null;
|
||||
let currentLevel = Calc.xpToLevel(this.experience, user.experience);
|
||||
let inLevel = Calc.xpInLevel(this.experience, currentLevel);
|
||||
let levelXp = user.experience - Calc.levelToXp(this.experience, currentLevel);
|
||||
|
||||
let progress = "║ ";
|
||||
let amt = Math.floor(levelXp / inLevel * PROGRESS_SEGMENTS);
|
||||
for (let i = 0; i < amt; i++) progress += PROGRESS_FULL;
|
||||
for (let i = amt; i < PROGRESS_SEGMENTS; i++) progress += PROGRESS_EMPTY;
|
||||
progress += " ║";
|
||||
|
||||
let roleID = "";
|
||||
for (let role of this.roles) {
|
||||
if (role.total_experience < user.experience) currentRole = role;
|
||||
if (role.totalExperience <= user.experience) roleID = role.id;
|
||||
else break;
|
||||
}
|
||||
const nextRole = currentRole ? this.roles[this.roles.indexOf(currentRole) + 1] : this.roles[0];
|
||||
let role = roleID ? (await msg.guild.roles.fetch(roleID))!.name : "Potato";
|
||||
|
||||
msg.channel.send({
|
||||
embed: new Discord.MessageEmbed()
|
||||
.setAuthor("My Level", "https://i.imgur.com/Nqyb94h.png")
|
||||
.setColor("#15B5A6")
|
||||
.setDescription(`Statistics for ${msg.member!.displayName} in ${msg.guild!.name}.`)
|
||||
.setFooter(`Requested by ${msg.member!.displayName}`, msg.author.avatarURL({ size: 16 })!)
|
||||
.setTimestamp()
|
||||
let embed = new Discord.MessageEmbed()
|
||||
.setAuthor("My Level", "https://i.imgur.com/Nqyb94h.png")
|
||||
.setColor("#15B5A6")
|
||||
.setDescription(`Statistics for ${msg.member!.displayName} in ${msg.guild!.name}.`)
|
||||
.setFooter(`Requested by ${msg.member!.displayName}`, msg.author.avatarURL({ size: 16 })!)
|
||||
.setTimestamp()
|
||||
|
||||
.addField(`⠀Level`, `⠀${user.level}`, true)
|
||||
.addField(`⠀Experience`, `⠀${Math.floor(user.experience - (currentRole?.total_experience ?? 0))} ` +
|
||||
(nextRole ? '/ ' + nextRole.experience : ''), true)
|
||||
.addField(`⠀Rank`, `⠀${currentRole ? currentRole.name : 'Potato'}`, true)
|
||||
}).catch(_ => { /* Missing send permissions. */ });
|
||||
.addField(`⠀Level`, `⠀${currentLevel}`, true)
|
||||
.addField(`⠀Experience`, `⠀${Math.round(user.experience)}`, true)
|
||||
.addField(`⠀Rank`, `⠀${role}`, true)
|
||||
.addField(`⠀Level Progress⠀⠀⠀⠀⠀⠀ ${Math.round(levelXp)} / ${Math.round(inLevel)}`, `⠀${progress}`, false);
|
||||
|
||||
msg.channel.send({ embed }).catch(_ => { /* Missing send permissions. */ });
|
||||
}
|
||||
}
|
||||
|
@ -6,24 +6,28 @@ import { Command, CommandFn } from '../../Commands/Command';
|
||||
|
||||
import LevelCommand from './LevelCommand';
|
||||
import LeaderboardCommand from './LeaderboardCommand';
|
||||
import SetExperienceCommand from './SetExperienceCommand';
|
||||
|
||||
// import { experienceInLevel } from './Calculate';
|
||||
|
||||
export interface ExperienceConfig {
|
||||
offset: number,
|
||||
base: number,
|
||||
multiplier: number
|
||||
}
|
||||
|
||||
interface LevelPluginConfig {
|
||||
please_and_thank_you: boolean;
|
||||
experience: ExperienceConfig,
|
||||
message: {
|
||||
cooldown: number;
|
||||
min_length: number;
|
||||
}
|
||||
levels: {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
experience: number;
|
||||
role: number;
|
||||
}
|
||||
}
|
||||
roles: {
|
||||
role: string,
|
||||
level: number
|
||||
}[]
|
||||
}
|
||||
// import {GuildData} from "../GuildData";
|
||||
// import {LevelImageBuilder} from "./LevelImageBuilder"
|
||||
// import {BotLevelRoles, Database, DBServer, DBUser} from "../Database";
|
||||
|
||||
const levelPluginGuildSchema = new Mongoose.Schema({
|
||||
id: String
|
||||
@ -57,33 +61,49 @@ interface ILevelPluginUser extends Mongoose.Document {
|
||||
|
||||
export const LevelPluginUser = Mongoose.model<ILevelPluginUser>('LevelPluginUser', levelPluginUserSchema);
|
||||
|
||||
export interface LevelRole {
|
||||
id: string;
|
||||
level: number;
|
||||
totalExperience: number;
|
||||
}
|
||||
|
||||
export default class LevelPlugin {
|
||||
private roles: { name: string, experience: number, total_experience: number, role: number }[] = [];
|
||||
private roles: LevelRole[] = [];
|
||||
// storage: BotStorage;
|
||||
// imgBuilder: LevelImageBuilder;
|
||||
// checkVAInterval: any;
|
||||
|
||||
constructor(private config: BotConfig & { plugin: { level: LevelPluginConfig } },
|
||||
private client: Discord.Client, commands: { [command: string]: Command | CommandFn }) {
|
||||
// this.storage = storage;
|
||||
// this.imgBuilder = new LevelImageBuilder();
|
||||
|
||||
let total_experience = 0;
|
||||
Object.keys(this.config.plugin.level.levels).map(m => parseInt(m)).sort((a, b) => a - b).forEach(n => {
|
||||
const role = this.config.plugin.level.levels[n.toString()];
|
||||
total_experience += role.experience;
|
||||
this.roles.push({ ...role, total_experience });
|
||||
let experience = this.config.plugin.level.experience;
|
||||
|
||||
let lastLevel = 0;
|
||||
let totalExperience = experience.offset;
|
||||
this.config.plugin.level.roles.map(r => {
|
||||
for (let i = lastLevel; i < r.level; i++)
|
||||
totalExperience += experience.base * Math.sqrt(experience.multiplier * i);
|
||||
lastLevel = r.level;
|
||||
this.roles.push({ id: r.role, level: r.level, totalExperience });
|
||||
});
|
||||
|
||||
// let total_experience = 0;
|
||||
// Object.keys(this.config.plugin.level.levels).map(m => parseInt(m)).sort((a, b) => a - b).forEach(n => {
|
||||
// const role = this.config.plugin.level.levels[n.toString()];
|
||||
// total_experience += role.experience;
|
||||
// this.roles.push({ ...role, total_experience });
|
||||
// });
|
||||
|
||||
client.on('message', this.onMessage);
|
||||
commands.level = new LevelCommand(this.roles);
|
||||
commands.leaderboard = new LeaderboardCommand(this.roles);
|
||||
commands.level = new LevelCommand(experience, this.roles);
|
||||
commands.leaderboard = new LeaderboardCommand(experience, this.roles);
|
||||
commands.setxp = SetExperienceCommand;
|
||||
|
||||
// this.checkVAInterval = setInterval(this.checkVoiceActivity.bind(this), 5*1000*60);
|
||||
}
|
||||
|
||||
private onMessage = async (msg: Discord.Message) => {
|
||||
if (msg.author.id === this.client.user!.id) return;
|
||||
if (msg.author.bot) return;
|
||||
if (msg.content.substr(0, this.config.options.prefix.length + 1).toLowerCase() == this.config.options.prefix + ' ') return;
|
||||
|
||||
// Completely ignore messages that are less than N characters and without a space.
|
||||
|
18
src/Plugin/Level/SetExperienceCommand.ts
Normal file
18
src/Plugin/Level/SetExperienceCommand.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import * as Discord from 'discord.js';
|
||||
|
||||
import { LevelPluginGuild, LevelPluginUser } from './LevelPlugin';
|
||||
|
||||
const MAX_XP = 1000000;
|
||||
|
||||
export default async function SetExperience(msg: Discord.Message, _: string, args: string[]) {
|
||||
let xp = Number.parseInt(args[0]);
|
||||
if (Number.isNaN(xp) || xp < 0 || xp > MAX_XP) return;
|
||||
|
||||
if (!msg.guild) return;
|
||||
const guild = await LevelPluginGuild.findOne({ id: msg.guild.id });
|
||||
if (!guild) return;
|
||||
const user = (await LevelPluginUser.findOne({ guild_id: guild._id, id: msg.author.id }));
|
||||
if (!user) return;
|
||||
user.experience = xp;
|
||||
await user.save();
|
||||
}
|
@ -28,6 +28,15 @@ export default class VoiceChatPlugin {
|
||||
if (config.plugin?.voice_chat?.description?.suffix) this.description_suffix = config.plugin.voice_chat.description.suffix;
|
||||
if (config.plugin?.voice_chat?.channel?.prefix) this.channel_prefix = config.plugin.voice_chat.channel.prefix;
|
||||
if (config.plugin?.voice_chat?.channel?.suffix) this.channel_suffix = config.plugin.voice_chat.channel.suffix;
|
||||
|
||||
this.client.guilds.cache.forEach(guild => {
|
||||
guild.channels.cache.forEach(channel => {
|
||||
if (channel.type != "voice") return;
|
||||
if ((channel as Discord.VoiceChannel).members.size >= 1)
|
||||
this.createChatChannel(channel as Discord.VoiceChannel,
|
||||
(channel as Discord.VoiceChannel).members.entries().next().value[1]);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
onVoiceStateUpdate = (oldState: Discord.VoiceState, newState: Discord.VoiceState) => {
|
||||
@ -44,28 +53,29 @@ export default class VoiceChatPlugin {
|
||||
if (newState.channelID != null) {
|
||||
let channel = newState.guild.channels.resolve(newState.channelID);
|
||||
if (!channel) return;
|
||||
if (!this.channels[channel.guild.id]?.[channel.id] && channel.members.size == 1 && channel.parent) this.createChatChannel(channel as any);
|
||||
if (!this.channels[channel.guild.id]?.[channel.id] && channel.members.size == 1 && channel.parent)
|
||||
this.createChatChannel(channel as any, newState.member!);
|
||||
}
|
||||
}
|
||||
|
||||
createChatChannel(voice: Discord.VoiceChannel) {
|
||||
createChatChannel(voice: Discord.VoiceChannel, member?: Discord.GuildMember) {
|
||||
let channelName = this.channel_prefix + voice.name.replace(/[\W_]+/g,"-").replace(/-+/g, "-") + this.channel_suffix;
|
||||
|
||||
voice.guild.channels.create(channelName, {
|
||||
type: `text`,
|
||||
topic: `${this.description_prefix}<#${voice.id}>${this.description_suffix}`,
|
||||
parent: voice.parent ?? undefined
|
||||
parent: voice.parent ?? undefined,
|
||||
topic: `${this.description_prefix}<#${voice.id}>${this.description_suffix}`
|
||||
}).then(channel => {
|
||||
if (!this.channels[channel.guild.id]) this.channels[channel.guild.id] = {};
|
||||
this.channels[channel.guild.id][voice.id] = channel.id;
|
||||
channel.send({
|
||||
embed: new Discord.MessageEmbed()
|
||||
.setAuthor(channelName, "https://i.imgur.com/vitVUtr.png")
|
||||
.setColor("#EE86ED")
|
||||
.setDescription(
|
||||
`This is a temporary discussion channel for #${voice.name}!\n` +
|
||||
`This channel will be automatically deleted when everybody leaves the voice channel.\n`)
|
||||
.setTimestamp()
|
||||
channel.send({
|
||||
embed: new Discord.MessageEmbed()
|
||||
.setAuthor(channelName, "https://i.imgur.com/vitVUtr.png")
|
||||
.setColor("#EE86ED")
|
||||
.setDescription(
|
||||
`This is a temporary discussion channel for #${voice.name}!\n` +
|
||||
`This channel will be automatically deleted when everybody leaves the voice channel.\n`)
|
||||
.setFooter(member ? `Requested by ${member.displayName}` : '', member?.user.avatarURL({ size: 16 })!)
|
||||
.setTimestamp()
|
||||
}).catch(_ => { /* Missing send permissions. */ });
|
||||
}).catch(_ => { /* Channel was removed. */ });
|
||||
}
|
||||
|
0
src/Tree.ts
Normal file
0
src/Tree.ts
Normal file
Loading…
x
Reference in New Issue
Block a user