From d5532fda48c9ae90c73bd6a18e2cf244ad371445 Mon Sep 17 00:00:00 2001 From: Milo Date: Fri, 26 Dec 2025 04:26:29 +0100 Subject: [PATCH] feat: skins upgrade --- src/bot/commands/inventory.js | 7 ++ src/bot/handlers/messageCreate.js | 25 ++++++- src/server/routes/api.js | 116 ++++++++++++++++++++++++++++-- src/utils/caseOpening.js | 16 +++++ 4 files changed, 158 insertions(+), 6 deletions(-) diff --git a/src/bot/commands/inventory.js b/src/bot/commands/inventory.js index 9fd272f..4f37f99 100644 --- a/src/bot/commands/inventory.js +++ b/src/bot/commands/inventory.js @@ -17,6 +17,13 @@ import { getUserInventory } from "../../database/index.js"; * @param {string} interactionId - The unique ID of the interaction. */ export async function handleInventoryCommand(req, res, client, interactionId) { + return res.send({ + type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: `La commande /inventory est désactivée. Tu peux consulter ton inventaire sur FlopoSite.`, + flags: InteractionResponseFlags.EPHEMERAL, + }, + }); const { member, guild_id, token, data } = req.body; const commandUserId = member.user.id; // User can specify another member, otherwise it defaults to themself diff --git a/src/bot/handlers/messageCreate.js b/src/bot/handlers/messageCreate.js index cbe3231..969e2cc 100644 --- a/src/bot/handlers/messageCreate.js +++ b/src/bot/handlers/messageCreate.js @@ -25,7 +25,7 @@ import { updateUserCoins, } from "../../database/index.js"; import { client } from "../client.js"; -import { drawCaseContent, drawCaseSkin } from "../../utils/caseOpening.js"; +import { drawCaseContent, drawCaseSkin, getDummySkinUpgradeProbs } from "../../utils/caseOpening.js"; // Constants for the AI rate limiter const MAX_REQUESTS_PER_INTERVAL = parseInt(process.env.MAX_REQUESTS || "5"); @@ -196,6 +196,28 @@ async function handleAdminCommands(message) { const [command, ...args] = message.content.split(" "); switch (command) { + case "?sp": + let msgText = "" + for (let skinTierRank = 1; skinTierRank <= 4; skinTierRank++) { + msgText += `\n--- Tier Rank: ${skinTierRank} ---\n`; + let skinMaxLevels = 4; + let skinMaxChromas = 4; + for (let skinLevel = 1; skinLevel < skinMaxLevels; skinLevel++) { + msgText += (`Levels: ${skinLevel}/${skinMaxLevels}, MaxChromas: ${1}/${skinMaxChromas} - `); + msgText += (`${getDummySkinUpgradeProbs(skinLevel, 1, skinTierRank, skinMaxLevels, skinMaxChromas, 15).successProb.toFixed(4)}, `); + msgText += (`${getDummySkinUpgradeProbs(skinLevel, 1, skinTierRank, skinMaxLevels, skinMaxChromas, 15).destructionProb.toFixed(4)}, `); + msgText += (`${getDummySkinUpgradeProbs(skinLevel, 1, skinTierRank, skinMaxLevels, skinMaxChromas, 15).upgradePrice}\n`); + } + for (let skinChroma = 1; skinChroma < skinMaxChromas; skinChroma++) { + msgText += (`Levels: ${skinMaxLevels}/${skinMaxLevels}, MaxChromas: ${skinChroma}/${skinMaxChromas} - `); + msgText += (`${getDummySkinUpgradeProbs(skinMaxLevels, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, 15).successProb.toFixed(4)}, `); + msgText += (`${getDummySkinUpgradeProbs(skinMaxLevels, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, 15).destructionProb.toFixed(4)}, `); + msgText += (`${getDummySkinUpgradeProbs(skinMaxLevels, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, 15).upgradePrice}\n`); + } + message.reply(msgText); + msgText = ""; + } + break; case "?v": console.log("Active Polls:", activePolls); break; @@ -331,6 +353,7 @@ async function handleAdminCommands(message) { console.log(e); message.reply(`Error during case test: ${e.message}`); } + break; case `${prefix}:refund-skins`: try { const DBskins = getAllSkins.all(); diff --git a/src/server/routes/api.js b/src/server/routes/api.js index 97d4e7e..8833658 100644 --- a/src/server/routes/api.js +++ b/src/server/routes/api.js @@ -34,7 +34,7 @@ import { DiscordRequest } from "../../api/discord.js"; import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js"; import { emitDataUpdated, socketEmit } from "../socket.js"; import { handleCaseOpening } from "../../utils/marketNotifs.js"; -import { drawCaseContent, drawCaseSkin } from "../../utils/caseOpening.js"; +import { drawCaseContent, drawCaseSkin, getSkinUpgradeProbs } from "../../utils/caseOpening.js"; // Create a new router instance const router = express.Router(); @@ -123,13 +123,13 @@ export function apiRoutes(client, io) { let caseTypeVal; switch (caseType) { case "standard": - caseTypeVal = 500; + caseTypeVal = 1000; break; case "premium": - caseTypeVal = 750; + caseTypeVal = 2000; break; case "ultra": - caseTypeVal = 1000; + caseTypeVal = 4000; break; default: return res.status(400).json({ error: "Invalid case type." }); @@ -166,7 +166,7 @@ export function apiRoutes(client, io) { }); console.log( - `[${Date.now()}] ${userId} opened a ${caseType} Valorant case and received skin ${result.randomSelectedSkinUuid}`, + `${commandUser.username} opened a ${caseType} Valorant case and received skin ${result.randomSelectedSkinUuid}`, ); const updatedSkin = getSkin.get(result.randomSkinData.uuid); await handleCaseOpening(caseType, userId, result.randomSelectedSkinUuid, client); @@ -214,6 +214,112 @@ export function apiRoutes(client, io) { } }); + router.get("/skin-upgrade/:uuid/fetch", (req, res) => { + try { + const skin = getSkin.get(req.params.uuid); + const skinData = skins.find((s) => s.uuid === skin.uuid); + const { successProb, destructionProb, upgradePrice } = getSkinUpgradeProbs(skin, skinData); + + const segments = [ + { id: 'SUCCEEDED', color: '5865f2', percent: successProb, label: 'Réussie' }, + { id: 'DESTRUCTED', color: 'f26558', percent: destructionProb, label: 'Détruit' }, + { id: 'NONE', color: '18181818', percent: 1 - successProb - destructionProb, label: 'Échec' }, + ] + + res.json({ segments, upgradePrice }); + } catch (error) { + console.log(error) + res.status(500).json({ error: "Failed to fetch skin upgrade." }); + } + }); + + router.post("/skin-upgrade/:uuid", async (req, res) => { + const { userId } = req.body; + try { + const skin = getSkin.get(req.params.uuid); + const skinData = skins.find((s) => s.uuid === skin.uuid); + if ( + !skinData || + (skin.currentLvl >= skinData.levels.length && skin.currentChroma >= skinData.chromas.length) + ) { + return res.status(403).json({ error: "Skin is already maxed out or invalid skin." }); + } + if (skin.user_id !== userId) { + return res.status(403).json({ error: "User does not own this skin." }); + } + const upgradePrice = Math.floor(parseFloat(skin.maxPrice) / 10); + + const commandUser = getUser.get(userId); + if (!commandUser) { + return res.status(404).json({ error: "User not found." }); + } + if (commandUser.coins < upgradePrice) { + return res.status(403).json({ error: `Pas assez de FlopoCoins (${upgradePrice} requis).` }); + } + + insertLog.run({ + id: `${userId}-${Date.now()}`, + user_id: userId, + action: "VALO_SKIN_UPGRADE", + target_user_id: null, + coins_amount: -upgradePrice, + user_new_amount: commandUser.coins - upgradePrice, + }); + updateUserCoins.run({ + id: userId, + coins: commandUser.coins - upgradePrice, + }); + + let succeeded = false; + let destructed = false; + const { successProb, destructionProb } = getSkinUpgradeProbs(skin, skinData); + const roll = Math.random(); + if (roll < destructionProb) { + destructed = true; + } else if (roll < successProb + destructionProb) { + succeeded = true; + } + + if (succeeded) { + const isLevelUpgrade = skin.currentLvl < skinData.levels.length; + if (isLevelUpgrade) { + skin.currentLvl++; + } else { + skin.currentChroma++; + } + const calculatePrice = () => { + let result = parseFloat(skin.basePrice); + result *= 1 + skin.currentLvl / Math.max(skinData.levels.length, 2); + result *= 1 + skin.currentChroma / 4; + return parseFloat(result.toFixed(0)); + }; + skin.currentPrice = calculatePrice(); + + updateSkin.run({ + uuid: skin.uuid, + user_id: skin.user_id, + currentLvl: skin.currentLvl, + currentChroma: skin.currentChroma, + currentPrice: skin.currentPrice, + }); + } else if (destructed) { + updateSkin.run({ + uuid: skin.uuid, + user_id: null, + currentLvl: null, + currentChroma: null, + currentPrice: null, + }); + } + + console.log(`${commandUser.username} attempted to upgrade skin ${skin.uuid} - ${succeeded ? "SUCCEEDED" : destructed ? "DESTRUCTED" : "FAILED"}`); + res.json({ wonId: succeeded ? "SUCCEEDED" : destructed ? "DESTRUCTED" : "NONE" }); + } catch (error) { + console.error("Error fetching skin upgrade:", error); + res.status(500).json({ error: "Failed to fetch skin upgrade." }); + } + }); + router.get("/users/by-elo", (req, res) => { try { const users = getUsersByElo.all(); diff --git a/src/utils/caseOpening.js b/src/utils/caseOpening.js index fcb238a..4ddf9c9 100644 --- a/src/utils/caseOpening.js +++ b/src/utils/caseOpening.js @@ -127,3 +127,19 @@ export function drawCaseSkin(caseContent) { randomSelectedSkinIndex, }; } + +export function getSkinUpgradeProbs(skin, skinData) { + const successProb = + (1 - (((skin.currentChroma + skin.currentLvl + skinData.chromas.length + skinData.levels.length) / 18) * (parseInt(skin.tierRank) / 4)))/2; + const destructionProb = ((skin.currentChroma + skinData.levels.length) / (skinData.chromas.length + skinData.levels.length)) * (parseInt(skin.tierRank) / 5) * 0.075; + const upgradePrice = Math.max(Math.floor(parseFloat(skin.currentPrice) * (1 - successProb)), 1); + return { successProb, destructionProb, upgradePrice }; +} + +export function getDummySkinUpgradeProbs(skinLevel, skinChroma, skinTierRank, skinMaxLevels, skinMaxChromas, skinMaxPrice) { + const successProb = + 1 - (((skinChroma + skinLevel + (skinMaxChromas + skinMaxLevels)) / 18) * (parseInt(skinTierRank) / 4)); + const destructionProb = ((skinChroma + skinMaxLevels) / (skinMaxChromas + skinMaxLevels)) * (parseInt(skinTierRank) / 5) * 0.1; + const upgradePrice = Math.max(Math.floor((parseFloat(skinMaxPrice) * (1 - successProb))), 1); + return { successProb, destructionProb, upgradePrice }; +}