From 598c179c8a6961544477d010c5d60c9b69460d6d Mon Sep 17 00:00:00 2001 From: Brian Le Date: Tue, 12 Jan 2021 12:55:49 -0500 Subject: [PATCH 1/8] Add EXP gain calculation --- src/structures/elimination_scoreboard.ts | 4 +++- src/structures/game_session.ts | 16 +++++++++++++++- src/structures/player.ts | 18 ++++++++++++++++++ src/structures/scoreboard.ts | 4 +++- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/structures/elimination_scoreboard.ts b/src/structures/elimination_scoreboard.ts index a64a8c54c..c00969b93 100644 --- a/src/structures/elimination_scoreboard.ts +++ b/src/structures/elimination_scoreboard.ts @@ -47,11 +47,13 @@ export default class EliminationScoreboard extends Scoreboard { * @param _avatarURL - Unused * @param _pointsEarned - Unused */ - updateScoreboard(_winnerTag: string, winnerID: string, _avatarURL: string, _pointsEarned: number) { + updateScoreboard(_winnerTag: string, winnerID: string, _avatarURL: string, _pointsEarned: number, expGain: number) { let maxLives = -1; for (const player of Object.values(this.players)) { if (player.getId() !== winnerID) { player.decrementLives(); + } else { + player.incrementExp(expGain); } if (player.getLives() === maxLives) { this.firstPlace.push(player); diff --git a/src/structures/game_session.ts b/src/structures/game_session.ts index 1e4752541..771b6a483 100644 --- a/src/structures/game_session.ts +++ b/src/structures/game_session.ts @@ -199,7 +199,8 @@ export default class GameSession { // update scoreboard const userTag = getUserTag(message.author); - this.scoreboard.updateScoreboard(userTag, message.author.id, message.author.avatarURL, pointsEarned); + const expGain = await this.calculateExpGain(guildPreference); + this.scoreboard.updateScoreboard(userTag, message.author.id, message.author.avatarURL, pointsEarned, expGain); // misc. game round cleanup this.stopGuessTimeout(); @@ -479,4 +480,17 @@ export default class GameSession { if (!this.gameRound) return "No active game round"; return `${this.gameRound.songName}:${this.gameRound.artist}:${this.gameRound.videoID}`; } + + /** + * https://www.desmos.com/calculator/y8kweubkqs + * @param guildPreference - The guild preference + * @returns The amount of EXP gained based on the current game options + */ + private async calculateExpGain(guildPreference: GuildPreference): Promise { + const songCount = Math.min(await getSongCount(guildPreference), guildPreference.getLimit()); + const expBase = 1000 / (1 + (Math.exp(2 - (0.00125 * songCount)))); + let expJitter = expBase * (0.05 * Math.random()); + expJitter *= Math.round(Math.random()) ? 1 : -1; + return Math.floor(expBase + expJitter); + } } diff --git a/src/structures/player.ts b/src/structures/player.ts index 5530a6bac..4ded36cf0 100644 --- a/src/structures/player.ts +++ b/src/structures/player.ts @@ -11,11 +11,15 @@ export default class Player { /** The player's avatar URL */ private avatarURL: string; + /** The player's EXP gain */ + private expGain: number; + constructor(tag: string, id: string, avatarURL: string, points: number) { this.tag = tag; this.id = id; this.score = points; this.avatarURL = avatarURL; + this.expGain = 0; } /** @returns the player's Discord tag */ @@ -28,6 +32,11 @@ export default class Player { return this.score; } + /** @returns the player's EXP gain */ + getExpGain(): number { + return this.expGain; + } + /** @returns the player's Discord ID */ getId(): string { return this.id; @@ -45,4 +54,13 @@ export default class Player { incrementScore(pointsEarned: number) { this.score += pointsEarned; } + + /** + * Increment the player's EXP gain by the specified amount + * @param expGain - The amount of EXP that was gained + */ + + incrementExp(expGain: number) { + this.expGain += expGain; + } } diff --git a/src/structures/scoreboard.ts b/src/structures/scoreboard.ts index 7c8578772..199cc3d1c 100644 --- a/src/structures/scoreboard.ts +++ b/src/structures/scoreboard.ts @@ -77,13 +77,15 @@ export default class Scoreboard { * @param winnerID - The Discord ID of the correct guesser * @param avatarURL - The avatar URL of the correct guesser * @param pointsEarned - The amount of points awarded + * @param expGain - The amount of EXP gained */ - updateScoreboard(winnerTag: string, winnerID: string, avatarURL: string, pointsEarned: number) { + updateScoreboard(winnerTag: string, winnerID: string, avatarURL: string, pointsEarned: number, expGain: number) { if (!this.players[winnerID]) { this.players[winnerID] = new Player(winnerTag, winnerID, avatarURL, pointsEarned); } else { this.players[winnerID].incrementScore(pointsEarned); } + this.players[winnerID].incrementExp(expGain); if (this.players[winnerID].getScore() === this.highestScore) { // If user is tied for first, add them to the first place array From 387272057914809b0774c7f1be7205b2bba6488f Mon Sep 17 00:00:00 2001 From: Brian Le Date: Tue, 12 Jan 2021 13:29:05 -0500 Subject: [PATCH 2/8] No EXP for song counts < 10 --- src/structures/game_session.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/structures/game_session.ts b/src/structures/game_session.ts index 771b6a483..c3898e34e 100644 --- a/src/structures/game_session.ts +++ b/src/structures/game_session.ts @@ -488,6 +488,7 @@ export default class GameSession { */ private async calculateExpGain(guildPreference: GuildPreference): Promise { const songCount = Math.min(await getSongCount(guildPreference), guildPreference.getLimit()); + if (songCount < 10) return 0; const expBase = 1000 / (1 + (Math.exp(2 - (0.00125 * songCount)))); let expJitter = expBase * (0.05 * Math.random()); expJitter *= Math.round(Math.random()) ? 1 : -1; From 9e179c62dfe3a330a4d469dd29d00c844494c6ba Mon Sep 17 00:00:00 2001 From: Brian Le Date: Tue, 12 Jan 2021 14:56:01 -0500 Subject: [PATCH 3/8] Add EXP table --- src/structures/game_session.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/structures/game_session.ts b/src/structures/game_session.ts index c3898e34e..656604b16 100644 --- a/src/structures/game_session.ts +++ b/src/structures/game_session.ts @@ -23,6 +23,9 @@ import { ModeType } from "../commands/game_options/mode"; const logger = _logger("game_session"); const LAST_PLAYED_SONG_QUEUE_SIZE = 10; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const EXP_TABLE = [...Array(100).keys()].map((level) => 10 * (level ** 2) + 200 * level - 200); + export default class GameSession { /** The GameType that the GameSession started in */ public readonly gameType: GameType; @@ -489,7 +492,7 @@ export default class GameSession { private async calculateExpGain(guildPreference: GuildPreference): Promise { const songCount = Math.min(await getSongCount(guildPreference), guildPreference.getLimit()); if (songCount < 10) return 0; - const expBase = 1000 / (1 + (Math.exp(2 - (0.00125 * songCount)))); + const expBase = 1000 / (1 + (Math.exp(1 - (0.00125 * songCount)))); let expJitter = expBase * (0.05 * Math.random()); expJitter *= Math.round(Math.random()) ? 1 : -1; return Math.floor(expBase + expJitter); From 148be3d2359c3ef3592b175f501fb06d92fcd5eb Mon Sep 17 00:00:00 2001 From: Brian Le Date: Tue, 12 Jan 2021 16:39:59 -0500 Subject: [PATCH 4/8] Integrate leveling system --- .../20210112130147_player-exp-system.js | 14 ++++ src/helpers/discord_utils.ts | 21 +++-- src/structures/game_session.ts | 81 +++++++++++++++++-- src/structures/scoreboard.ts | 19 +++++ 4 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 migrations/20210112130147_player-exp-system.js diff --git a/migrations/20210112130147_player-exp-system.js b/migrations/20210112130147_player-exp-system.js new file mode 100644 index 000000000..8d24f7bbb --- /dev/null +++ b/migrations/20210112130147_player-exp-system.js @@ -0,0 +1,14 @@ + +exports.up = function (knex) { + return knex.schema.table("player_stats", function (table) { + table.integer("exp").defaultTo(0); + table.integer("level").defaultTo(1); + }); +}; + +exports.down = function (knex) { + return knex.schema.table("player_stats", function (table) { + table.dropColumn("exp"); + table.dropColumn("level"); + }); +}; diff --git a/src/helpers/discord_utils.ts b/src/helpers/discord_utils.ts index a54bb6d5c..6804e8b6c 100644 --- a/src/helpers/discord_utils.ts +++ b/src/helpers/discord_utils.ts @@ -114,13 +114,15 @@ export async function sendEndOfRoundMessage(messageContext: MessageContext, scor * @param description - The description of the embed */ export async function sendErrorMessage(messageContext: MessageContext, title: string, description: string) { + const author = messageContext.user ? { + name: messageContext.user.username, + icon_url: messageContext.user.avatarURL, + } : null; + await sendMessage(messageContext.channel, { embed: { color: EMBED_ERROR_COLOR, - author: { - name: messageContext.user.username, - icon_url: messageContext.user.avatarURL, - }, + author, title: bold(title), description, }, @@ -145,12 +147,15 @@ export async function sendInfoMessage(messageContext: MessageContext, title: str text: footerText, }; } + + const author = messageContext.user ? { + name: messageContext.user.username, + icon_url: messageContext.user.avatarURL, + } : null; + const embed = { color: EMBED_INFO_COLOR, - author: { - name: messageContext.user.username, - icon_url: messageContext.user.avatarURL, - }, + author, title: bold(title), description, footer, diff --git a/src/structures/game_session.ts b/src/structures/game_session.ts index 656604b16..8a233bd84 100644 --- a/src/structures/game_session.ts +++ b/src/structures/game_session.ts @@ -5,7 +5,7 @@ import { ShuffleType } from "../commands/game_options/shuffle"; import dbContext from "../database_context"; import { isDebugMode, skipSongPlay } from "../helpers/debug_utils"; import { - getDebugLogHeader, getSqlDateString, getUserTag, getVoiceChannel, sendErrorMessage, sendEndOfRoundMessage, getMessageContext, + getDebugLogHeader, getSqlDateString, getUserTag, getVoiceChannel, sendErrorMessage, sendEndOfRoundMessage, getMessageContext, sendInfoMessage, } from "../helpers/discord_utils"; import { ensureVoiceConnection, getGuildPreference, selectRandomSong, getSongCount, endSession } from "../helpers/game_utils"; import { delay, getAudioDurationInSeconds } from "../helpers/utils"; @@ -23,8 +23,19 @@ import { ModeType } from "../commands/game_options/mode"; const logger = _logger("game_session"); const LAST_PLAYED_SONG_QUEUE_SIZE = 10; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const EXP_TABLE = [...Array(100).keys()].map((level) => 10 * (level ** 2) + 200 * level - 200); +const EXP_TABLE = [...Array(100).keys()].map((level) => { + if (level === 0 || level === 1) return 0; + return 10 * (level ** 2) + 200 * level - 200; +}); + +// eslint-disable-next-line no-return-assign +const CUM_EXP_TABLE = EXP_TABLE.map(((sum) => (value) => sum += value)(0)); + +interface LevelUpResult { + userId: string; + startLevel: number; + endLevel: number; +} export default class GameSession { /** The GameType that the GameSession started in */ @@ -139,6 +150,7 @@ export default class GameSession { } } + const leveledUpPlayers: Array = []; // commit player stats for (const participant of this.participants) { await this.ensurePlayerStat(participant); @@ -147,6 +159,20 @@ export default class GameSession { if (playerScore > 0) { await this.incrementPlayerSongsGuessed(participant, playerScore); } + const playerExpGain = this.scoreboard.getPlayerExpGain(participant); + if (playerExpGain > 0) { + const levelUpResult = await this.incrementPlayerExp(participant, playerExpGain); + if (levelUpResult) { + leveledUpPlayers.push(levelUpResult); + } + } + } + + // send level up message + if (leveledUpPlayers.length > 0) { + const message = leveledUpPlayers.map((leveledUpPlayer) => `${this.scoreboard.getPlayerName(leveledUpPlayer.userId)} has leveled from \`${leveledUpPlayer.startLevel}\` to \`${leveledUpPlayer.endLevel}\``) + .join("\n"); + sendInfoMessage({ channel: this.textChannel }, "Power up!", message); } // commit guild stats @@ -194,8 +220,6 @@ export default class GameSession { const pointsEarned = this.checkGuess(message, guildPreference.getModeType()); if (pointsEarned > 0) { - logger.info(`${getDebugLogHeader(message)} | Song correctly guessed. song = ${this.gameRound.songName}`); - // update game session's lastActive const gameSession = state.gameSessions[this.guildID]; gameSession.lastActiveNow(); @@ -203,6 +227,7 @@ export default class GameSession { // update scoreboard const userTag = getUserTag(message.author); const expGain = await this.calculateExpGain(guildPreference); + logger.info(`${getDebugLogHeader(message)} | Song correctly guessed. song = ${this.gameRound.songName}. Gained ${expGain} EXP`); this.scoreboard.updateScoreboard(userTag, message.author.id, message.author.avatarURL, pointsEarned, expGain); // misc. game round cleanup @@ -476,6 +501,40 @@ export default class GameSession { .increment("games_played", 1); } + /** + * @param userId - The Discord ID of the user to exp gain + * @param expGain - The amount of EXP gained + */ + private async incrementPlayerExp(userId: string, expGain: number): Promise { + const { exp: currentExp, level } = (await dbContext.kmq("player_stats") + .select(["exp", "level"]) + .where("player_id", "=", userId) + .first()); + const newExp = currentExp + expGain; + let newLevel = level; + + // check for level up + while (newExp > CUM_EXP_TABLE[newLevel + 1]) { + newLevel++; + } + + // persist exp and level to data store + await dbContext.kmq("player_stats") + .update({ exp: newExp, level: newLevel }) + .where("player_id", "=", userId); + + if (level !== newLevel) { + logger.info(`${userId} has leveled from ${level} to ${newLevel}`); + return { + userId, + startLevel: level, + endLevel: newLevel, + }; + } + + return null; + } + /** * @returns Debug string containing basic information about the GameRound */ @@ -490,11 +549,21 @@ export default class GameSession { * @returns The amount of EXP gained based on the current game options */ private async calculateExpGain(guildPreference: GuildPreference): Promise { + let expModifier = 1; const songCount = Math.min(await getSongCount(guildPreference), guildPreference.getLimit()); + + // minimum amount of songs for exp gain if (songCount < 10) return 0; + + // penalize for using artist guess modes + if (guildPreference.getModeType() === ModeType.ARTIST || guildPreference.getModeType() === ModeType.BOTH) { + if (guildPreference.isGroupsMode()) return 0; + expModifier = 0.3; + } + const expBase = 1000 / (1 + (Math.exp(1 - (0.00125 * songCount)))); let expJitter = expBase * (0.05 * Math.random()); expJitter *= Math.round(Math.random()) ? 1 : -1; - return Math.floor(expBase + expJitter); + return Math.floor(expModifier * (expBase + expJitter)); } } diff --git a/src/structures/scoreboard.ts b/src/structures/scoreboard.ts index 199cc3d1c..cbe302629 100644 --- a/src/structures/scoreboard.ts +++ b/src/structures/scoreboard.ts @@ -118,6 +118,17 @@ export default class Scoreboard { return 0; } + /** + * @param userId - The Discord user ID of the player to check + * @returns the exp gained by the player + */ + getPlayerExpGain(userId: string): number { + if (userId in this.players) { + return this.players[userId].getExpGain(); + } + return 0; + } + /** @returns whether the game has completed */ async gameFinished(): Promise { const guildPreference = await getGuildPreference(this.guildID); @@ -128,4 +139,12 @@ export default class Scoreboard { getPlayerNames(): Array { return Object.values(this.players).map((player) => player.getTag()); } + + /** + * @param userId - The Discord user ID of the Player + * @returns a Player object for the corresponding user ID + * */ + getPlayerName(userId: string): string { + return this.players[userId].getTag(); + } } From 7eaf3f10afaf71cb7978e91ec19e5cbb81706677 Mon Sep 17 00:00:00 2001 From: Brian Le Date: Tue, 12 Jan 2021 17:18:05 -0500 Subject: [PATCH 5/8] Add level/experience data to profile --- src/commands/game_commands/profile.ts | 49 ++++++++++++++++++--------- src/structures/game_session.ts | 2 +- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/commands/game_commands/profile.ts b/src/commands/game_commands/profile.ts index 297dbee0e..98b9d86af 100644 --- a/src/commands/game_commands/profile.ts +++ b/src/commands/game_commands/profile.ts @@ -5,6 +5,7 @@ import { getDebugLogHeader, getMessageContext, getUserTag, sendEmbed, sendInfoMe import BaseCommand, { CommandArgs } from "../base_command"; import _logger from "../../logger"; import { bold, friendlyFormattedDate } from "../../helpers/utils"; +import { CUM_EXP_TABLE } from "../../structures/game_session"; const logger = _logger("profile"); @@ -60,22 +61,38 @@ export default class ProfileCommand implements BaseCommand { .where("games_played", ">", gamesPlayed) .first())["count"] as number) + 1; - const fields: Array = [{ - name: "Songs Guessed", - value: `${songsGuessed} | #${relativeSongRank}/${totalPlayers} `, - }, - { - name: "Games Played", - value: `${gamesPlayed} | #${relativeGamesPlayedRank}/${totalPlayers} `, - }, - { - name: "First Played", - value: firstPlayDateString, - }, - { - name: "Last Active", - value: lastActiveDateString, - }]; + const { exp, level } = (await dbContext.kmq("player_stats") + .select(["exp", "level"]) + .where("player_id", "=", requestedPlayer.id) + .first()); + + const fields: Array = [ + { + name: "Level", + value: `${level}`, + inline: true, + }, + { + name: "Experience", + value: `${exp}/${CUM_EXP_TABLE[level + 1]}`, + inline: true, + }, + { + name: "Songs Guessed", + value: `${songsGuessed} | #${relativeSongRank}/${totalPlayers} `, + }, + { + name: "Games Played", + value: `${gamesPlayed} | #${relativeGamesPlayedRank}/${totalPlayers} `, + }, + { + name: "First Played", + value: firstPlayDateString, + }, + { + name: "Last Active", + value: lastActiveDateString, + }]; sendEmbed(message.channel, { title: bold(`${getUserTag(requestedPlayer)}`), diff --git a/src/structures/game_session.ts b/src/structures/game_session.ts index 8a233bd84..14de838bd 100644 --- a/src/structures/game_session.ts +++ b/src/structures/game_session.ts @@ -29,7 +29,7 @@ const EXP_TABLE = [...Array(100).keys()].map((level) => { }); // eslint-disable-next-line no-return-assign -const CUM_EXP_TABLE = EXP_TABLE.map(((sum) => (value) => sum += value)(0)); +export const CUM_EXP_TABLE = EXP_TABLE.map(((sum) => (value) => sum += value)(0)); interface LevelUpResult { userId: string; From 73d0c2a4ab9e35620620cf25e3cf5ea6a44f34aa Mon Sep 17 00:00:00 2001 From: Brian Le Date: Tue, 12 Jan 2021 18:10:27 -0500 Subject: [PATCH 6/8] Add level rank names --- src/commands/game_commands/profile.ts | 25 +++++++++++++++++++++++-- src/structures/game_session.ts | 5 +++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/commands/game_commands/profile.ts b/src/commands/game_commands/profile.ts index 98b9d86af..f39c0e2a0 100644 --- a/src/commands/game_commands/profile.ts +++ b/src/commands/game_commands/profile.ts @@ -9,6 +9,28 @@ import { CUM_EXP_TABLE } from "../../structures/game_session"; const logger = _logger("profile"); +const RANK_TITLES = [ + { title: "Novice", req: 0 }, + { title: "Trainee", req: 10 }, + { title: "Pre-debut", req: 20 }, + { title: "Nugu", req: 30 }, + { title: "New Artist Of The Year", req: 40 }, + { title: "Artist Of The Year", req: 50 }, + { title: "Bonsang Award Winner", req: 60 }, + { title: "Daesang Award Winner", req: 70 }, + { title: "CEO of KMQ Entertainment", req: 80 }, + { title: "President of South Korea", req: 90 }, + { title: "Reuniter of the Two Koreas", req: 100 }, +]; + +export function getRankNameByLevel(level: number): string { + for (let i = RANK_TITLES.length - 1; i >= 0; i--) { + const rankTitle = RANK_TITLES[i]; + if (level >= rankTitle.req) return rankTitle.title; + } + return RANK_TITLES[0].title; +} + export default class ProfileCommand implements BaseCommand { help = { name: "profile", @@ -65,11 +87,10 @@ export default class ProfileCommand implements BaseCommand { .select(["exp", "level"]) .where("player_id", "=", requestedPlayer.id) .first()); - const fields: Array = [ { name: "Level", - value: `${level}`, + value: `${level} (${getRankNameByLevel(level)})`, inline: true, }, { diff --git a/src/structures/game_session.ts b/src/structures/game_session.ts index 14de838bd..ca486fa8d 100644 --- a/src/structures/game_session.ts +++ b/src/structures/game_session.ts @@ -19,11 +19,12 @@ import EliminationScoreboard from "./elimination_scoreboard"; import { deleteGameSession } from "../helpers/management_utils"; import { GameType } from "../commands/game_commands/play"; import { ModeType } from "../commands/game_options/mode"; +import { getRankNameByLevel } from "../commands/game_commands/profile"; const logger = _logger("game_session"); const LAST_PLAYED_SONG_QUEUE_SIZE = 10; -const EXP_TABLE = [...Array(100).keys()].map((level) => { +const EXP_TABLE = [...Array(200).keys()].map((level) => { if (level === 0 || level === 1) return 0; return 10 * (level ** 2) + 200 * level - 200; }); @@ -170,7 +171,7 @@ export default class GameSession { // send level up message if (leveledUpPlayers.length > 0) { - const message = leveledUpPlayers.map((leveledUpPlayer) => `${this.scoreboard.getPlayerName(leveledUpPlayer.userId)} has leveled from \`${leveledUpPlayer.startLevel}\` to \`${leveledUpPlayer.endLevel}\``) + const message = leveledUpPlayers.map((leveledUpPlayer) => `${this.scoreboard.getPlayerName(leveledUpPlayer.userId)} has leveled from \`${leveledUpPlayer.startLevel}\` to \`${leveledUpPlayer.endLevel} (${getRankNameByLevel(leveledUpPlayer.endLevel)})\``) .join("\n"); sendInfoMessage({ channel: this.textChannel }, "Power up!", message); } From c1922207b50962af480fbe0f0c8280e1f32df9da Mon Sep 17 00:00:00 2001 From: Brian Le Date: Tue, 12 Jan 2021 18:18:45 -0500 Subject: [PATCH 7/8] Truncate level up messages if over 10 levelers --- src/structures/game_session.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/structures/game_session.ts b/src/structures/game_session.ts index ca486fa8d..24f414e12 100644 --- a/src/structures/game_session.ts +++ b/src/structures/game_session.ts @@ -171,9 +171,12 @@ export default class GameSession { // send level up message if (leveledUpPlayers.length > 0) { - const message = leveledUpPlayers.map((leveledUpPlayer) => `${this.scoreboard.getPlayerName(leveledUpPlayer.userId)} has leveled from \`${leveledUpPlayer.startLevel}\` to \`${leveledUpPlayer.endLevel} (${getRankNameByLevel(leveledUpPlayer.endLevel)})\``) - .join("\n"); - sendInfoMessage({ channel: this.textChannel }, "Power up!", message); + let levelUpMessages = leveledUpPlayers.map((leveledUpPlayer) => `\`${this.scoreboard.getPlayerName(leveledUpPlayer.userId)}\` has leveled from \`${leveledUpPlayer.startLevel}\` to \`${leveledUpPlayer.endLevel} (${getRankNameByLevel(leveledUpPlayer.endLevel)})\``); + if (levelUpMessages.length > 10) { + levelUpMessages = levelUpMessages.slice(0, 10); + levelUpMessages.push("and many others..."); + } + sendInfoMessage({ channel: this.textChannel }, "🚀 Power up!", levelUpMessages.join("\n")); } // commit guild stats @@ -545,7 +548,7 @@ export default class GameSession { } /** - * https://www.desmos.com/calculator/y8kweubkqs + * https://www.desmos.com/calculator/zxvbuq0bch * @param guildPreference - The guild preference * @returns The amount of EXP gained based on the current game options */ From 4a869db2fbb8c5ea348c9c8293e5897044859ba9 Mon Sep 17 00:00:00 2001 From: Brian Le Date: Tue, 12 Jan 2021 21:33:50 -0500 Subject: [PATCH 8/8] Update GAMEPLAY.md to include EXP info --- GAMEPLAY.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/GAMEPLAY.md b/GAMEPLAY.md index 437234e1d..9fd54a9f8 100644 --- a/GAMEPLAY.md +++ b/GAMEPLAY.md @@ -8,9 +8,21 @@ You can see the current scoreboard by typing `,score`. It is also shown after th See the latest updates to KMQ with `,news`. -## Game Modes +# Game Modes See who can survive the longest into a KMQ game with elimination mode. Using `,play elimination x`, everyone starts with `x` lives and the last one alive wins. Guessing correctly will save your life, while everyone else loses one. Use elimination mode in conjunction with `,timer` to raise the pressure! `,goal` cannot be used in elimination mode. +# EXP System +Think you have what it takes to be a KMQ master? Rise through the ranks, gaining EXP and leveling up by playing KMQ. Every correct guess will net you some EXP, increasing based on your game options. The higher the number of songs selected by your game options, the more EXP you will get! + +You start off as a `Novice`, and work your way up through the works of `Trainee` (Level 10), `Pre-debut` (Level 20), `Nugu` (Level 30), and many more to discover. Check out `,profile` to see where you stand. + +You will only gain EXP if: +- There is a minimum of 10 songs selected +- You are using `,mode song` (full EXP) +- You are using `,mode artist` or `,mode both` and you are not using `,groups` (30% EXP) + + + # Game Options KMQ offers different game options to narrow down the selection of random songs based on your preferences. The current game options can be viewed by using `,options` or simply tagging KMQ Bot.