← back to browse
goat command

penalty

Interactive Penalty Game with Reply Challenge & Deprecation Fix

0
views
0
likes
0
installs

Aliases: shootout, ps

raw source
const axios = require("axios");
const fs = require("fs-extra");
const path = require("path");
const mongoose = require("mongoose");

// Database Schema
const bankUserSchema = new mongoose.Schema({
  userID: { type: String, required: true, unique: true },
  balance: { type: Number, default: 0 },
  goals: { type: Number, default: 0 },
  saves: { type: Number, default: 0 }
}, { strict: false });

const BankUser = mongoose.models.DiabloBankUser || mongoose.model("DiabloBankUser", bankUserSchema);

// Memory Tracker for Active PvP Challenges & Cooldowns
global.penaltyGames = global.penaltyGames || {
  challenges: new Map(),
  cooldowns: new Map()
};

const VIDEOS = {
  goal: {
    left: "https://files.catbox.moe/dunjkw.mp4",
    center: "https://files.catbox.moe/kxbwc2.mp4",
    right: "https://files.catbox.moe/13ohwi.mp4"
  },
  save: {
    left: "https://files.catbox.moe/kfcjjb.mp4",
    center: "https://files.catbox.moe/3ftsa9.mp4",
    right: "https://files.catbox.moe/kxfa83.mp4"
  }
};

const BOT_USER_ID = "BOT_DIABLO_OFFICIAL";

module.exports = {
  config: {
    name: "penalty",
    aliases: ["shootout", "ps"],
    version: "1.0.0",
    author: "Protik Shah",
    countDown: 5,
    role: 0,
    shortDescription: "Interactive Penalty Game with Reply Challenge & Deprecation Fix",
    category: "game",
    guide: {
      en: "{p}penalty <bet> [1/2/3]\n{p}penalty challenge <bet> [@mention / Reply to message]\n{p}penalty accept <1/2/3>\n{p}penalty top / stats"
    }
  },

  formatMoney: function (num) {
    if (num >= 1000000000) return (num / 1000000000).toFixed(1).replace(/\.0$/, "") + "ʙ";
    if (num >= 1000000) return (num / 1000000).toFixed(1).replace(/\.0$/, "") + "ᴍ";
    if (num >= 1000) return (num / 1000).toFixed(1).replace(/\.0$/, "") + "ᴋ";
    return num.toLocaleString();
  },

  parseBet: function (input, balance) {
    if (!input) return NaN;
    const lower = String(input).toLowerCase().trim();
    if (lower === "all") return Math.min(balance, 69000000000);
    if (lower.endsWith("k")) return parseFloat(lower) * 1000;
    if (lower.endsWith("m")) return parseFloat(lower) * 1000000;
    if (lower.endsWith("b")) return parseFloat(lower) * 1000000000;
    return parseInt(lower);
  },

  checkLimit: function (userID) {
    const now = Date.now();
    const FIVE_HOURS = 5 * 60 * 60 * 1000;
    let userData = global.penaltyGames.cooldowns.get(userID);

    if (!userData || (now - userData.startTime > FIVE_HOURS)) {
      userData = { count: 0, startTime: now };
    }

    if (userData.count >= 10) {
      const remainingTime = Math.ceil((FIVE_HOURS - (now - userData.startTime)) / (60 * 1000));
      return { allowed: false, remainingTime };
    }

    return { allowed: true, userData };
  },

  incrementLimit: function (userID, userData) {
    userData.count += 1;
    global.penaltyGames.cooldowns.set(userID, userData);
  },

  downloadFile: async function (url, dest) {
    const response = await axios({ url, method: "GET", responseType: "stream" });
    const writer = fs.createWriteStream(dest);
    response.data.pipe(writer);
    return new Promise((resolve, reject) => {
      writer.on("finish", resolve);
      writer.on("error", reject);
    });
  },

  getName: async function (usersData, userID) {
    if (userID === BOT_USER_ID) return "🤖 ᴅɪ-ᴀʙʟᴏ ʙᴏᴛ";
    try {
      if (usersData && typeof usersData.getName === "function") {
        const name = await usersData.getName(userID);
        if (name) return name;
      }
      return `User_${userID}`;
    } catch (e) {
      return `User_${userID}`;
    }
  },

  setReaction: function (api, messageID, icon) {
    if (api && typeof api.setMessageReaction === "function") {
      api.setMessageReaction(icon, messageID, (err) => {}, true);
    }
  },

  onStart: async function ({ api, event, args, message, usersData }) {
    const sendMsg = (txt) => message && typeof message.reply === "function" ? message.reply(txt) : api.sendMessage(txt, event.threadID, event.messageID);
    const { senderID, mentions, threadID, messageID, messageReply } = event;
    const BANK_NAME = "🏛️ ᴅɪ-ᴀʙʟᴏ ᴠᴀᴜʟᴛ 🏛️";
    const MAX_BET = 69000000000;

    try {
      // Ensure Bot Account setup
      await BankUser.findOneAndUpdate(
        { userID: BOT_USER_ID },
        { $setOnInsert: { balance: 999999999999, goals: 0, saves: 0 } },
        { upsert: true, returnDocument: 'after' }
      );

      const subCommand = (args[0] || "").toLowerCase();

      // 🏆 Leaderboard Command
      if (["top", "stats", "leaderboard", "topscore", "topsave"].includes(subCommand)) {
        const topScorers = await BankUser.find().sort({ goals: -1 }).limit(5);
        const topKeepers = await BankUser.find().sort({ saves: -1 }).limit(5);

        let leaderboardText = `🏆 ───[ ᴘᴇɴᴀʟᴛʏ ʟᴇᴀᴅᴇʀʙᴏᴀʀᴅ ]─── 🏆\n\n`;
        leaderboardText += `⚽ ᴛᴏᴘ sᴄᴏʀᴇʀs:\n`;
        for (let i = 0; i < topScorers.length; i++) {
          const name = await this.getName(usersData, topScorers[i].userID);
          leaderboardText += `${i + 1}. ${name} ➔ ${topScorers[i].goals || 0} Goals\n`;
        }
        leaderboardText += `\n🧤 ᴛᴏᴘ ɢᴏᴀʟᴋᴇᴇᴘᴇʀs:\n`;
        for (let i = 0; i < topKeepers.length; i++) {
          const name = await this.getName(usersData, topKeepers[i].userID);
          leaderboardText += `${i + 1}. ${name} ➔ ${topKeepers[i].saves || 0} Saves\n`;
        }
        return sendMsg(leaderboardText);
      }

      // Limit Check
      const limitInfo = this.checkLimit(senderID);
      if (!limitInfo.allowed) {
        return sendMsg(`❌ ʟɪᴍɪᴛ ʀᴇᴀᴄʜᴇᴅ!\n⏱️ ʏᴏᴜ ʜᴀᴠᴇ ᴘʟᴀʏᴇᴅ 10/10 ᴛɪᴍᴇs.\n💡 ᴘʟᴇᴀsᴇ ᴡᴀɪᴛ ${limitInfo.remainingTime} ᴍɪɴᴜᴛᴇs.`);
      }

      let user = await BankUser.findOne({ userID: senderID });
      if (!user) {
        user = await BankUser.create({ userID: senderID, balance: 1000, goals: 0, saves: 0 });
      }

      // Handle PvP Accept
      if (subCommand === "accept") {
        const challengeKey = Array.from(global.penaltyGames.challenges.keys()).find(key => key.endsWith(`_${senderID}`));
        if (!challengeKey) return sendMsg("❌ ɴᴏ ᴘᴇɴᴅɪɴɢ ᴄʜᴀʟʟᴇɴɢᴇ ғᴏᴜɴᴅ ғᴏʀ ʏᴏᴜ!");

        const diveInput = (args[1] || "").toLowerCase();
        const validDir = ["left", "center", "right", "1", "2", "3"];
        if (!validDir.includes(diveInput)) {
          return sendMsg("❌ ᴘʟᴇᴀsᴇ sᴘᴇᴄɪғʏ ʏᴏᴜʀ ᴅɪᴠᴇ ᴅɪʀᴇᴄᴛɪᴏɴ!\n👉 Example: {p}penalty accept 1 (1=Left, 2=Center, 3=Right)");
        }

        let keeperDive = diveInput;
        if (diveInput === "1") keeperDive = "left";
        if (diveInput === "2") keeperDive = "center";
        if (diveInput === "3") keeperDive = "right";

        const challenge = global.penaltyGames.challenges.get(challengeKey);
        if (user.balance < challenge.bet) {
          return sendMsg(`❌ ʏᴏᴜ ᴅᴏ ɴᴏᴛ ʜᴀᴠᴇ ᴇɴᴏᴜɢʜ ʙᴀʟᴀɴᴄᴇ ($${this.formatMoney(challenge.bet)}) ᴛᴏ ᴀᴄᴄᴇᴘᴛ!`);
        }

        this.setReaction(api, messageID, "⏳");
        global.penaltyGames.challenges.delete(challengeKey);

        const p1ID = challenge.challengerID;
        const p2ID = senderID;
        const bet = challenge.bet;
        const strikerShot = challenge.secretShot;

        const isGoal = strikerShot !== keeperDive;

        if (isGoal) {
          await BankUser.findOneAndUpdate({ userID: p1ID }, { $inc: { balance: bet, goals: 1 } }, { upsert: true, returnDocument: 'after' });
          await BankUser.findOneAndUpdate({ userID: p2ID }, { $inc: { balance: -bet } }, { upsert: true, returnDocument: 'after' });
        } else {
          await BankUser.findOneAndUpdate({ userID: p1ID }, { $inc: { balance: -bet } }, { upsert: true, returnDocument: 'after' });
          await BankUser.findOneAndUpdate({ userID: p2ID }, { $inc: { balance: bet, saves: 1 } }, { upsert: true, returnDocument: 'after' });
        }

        this.incrementLimit(p1ID, this.checkLimit(p1ID).userData);
        this.incrementLimit(p2ID, limitInfo.userData);

        const p1Name = await this.getName(usersData, p1ID);
        const p2Name = await this.getName(usersData, p2ID);

        const videoUrl = isGoal ? VIDEOS.goal[strikerShot] : VIDEOS.save[strikerShot];
        const videoPath = path.join(__dirname, `pvp_vid_${Date.now()}.mp4`);

        try {
          await this.downloadFile(videoUrl, videoPath);

          const resultMsg = isGoal
            ? `⚽ ───[ ᴘᴠᴘ ɢᴏᴀʟ ]─── ⚽\n\n` +
              `🔥 ${p1Name} (Striker) shot [${strikerShot.toUpperCase()}] & scored against ${p2Name} [${keeperDive.toUpperCase()}]!\n\n` +
              `🏆 ᴡɪɴɴᴇʀ: ${p1Name} (+$${this.formatMoney(bet)})\n` +
              `💀 ʟᴏsᴇʀ : ${p2Name} (-$${this.formatMoney(bet)})\n` +
              `🏦 ʙᴀɴᴋ   : ${BANK_NAME}`
            : `🧱 ───[ ᴘᴠᴘ sᴀᴠᴇᴅ ]─── ⚽\n\n` +
              `🧤 ${p2Name} (Goalkeeper) dived [${keeperDive.toUpperCase()}] & saved ${p1Name}'s shot [${strikerShot.toUpperCase()}]!\n\n` +
              `🏆 ᴡɪɴɴᴇʀ: ${p2Name} (+$${this.formatMoney(bet)})\n` +
              `💀 ʟᴏsᴇʀ : ${p1Name} (-$${this.formatMoney(bet)})\n` +
              `🏦 ʙᴀɴᴋ   : ${BANK_NAME}`;

          this.setReaction(api, messageID, isGoal ? "⚽" : "🧤");

          return api.sendMessage({
            body: resultMsg,
            attachment: fs.createReadStream(videoPath),
            mentions: [
              { tag: p1Name, id: p1ID },
              { tag: p2Name, id: p2ID }
            ]
          }, threadID, () => {
            if (fs.existsSync(videoPath)) fs.unlinkSync(videoPath);
          }, messageID);
        } catch (err) {
          if (fs.existsSync(videoPath)) fs.unlinkSync(videoPath);
          return sendMsg("❌ ᴇʀʀᴏʀ ᴘʀᴏᴄᴇssɪɴɢ ᴘᴠᴘ ᴠɪᴅᴇᴏ!");
        }
      }

      // Handle PvP Challenge Creation (Supports Mention & Reply)
      if (subCommand === "challenge") {
        let targetID = null;

        if (mentions && Object.keys(mentions).length > 0) {
          targetID = Object.keys(mentions)[0];
        } else if (messageReply && messageReply.senderID) {
          targetID = messageReply.senderID;
        }

        if (!targetID) {
          return sendMsg("❌ ᴘʟᴇᴀsᴇ ᴍᴇɴᴛɪᴏɴ ᴏʀ ʀᴇᴘʟʏ ᴛᴏ ᴀ ᴜsᴇʀ's ᴍᴇssᴀɢᴇ ᴛᴏ ᴄʜᴀʟʟᴇɴɢᴇ!\n👉 {p}penalty challenge 10B (by reply or @mention)");
        }

        if (targetID === senderID) return sendMsg("❌ ʏᴏᴜ ᴄᴀɴɴᴏᴛ ᴄʜᴀʟʟᴇɴɢᴇ ʏᴏᴜʀsᴇʟғ!");

        let rawBet = "";
        for (let i = 1; i < args.length; i++) {
          if (!args[i].startsWith("@") && args[i].toLowerCase() !== "challenge") {
            rawBet = args[i];
            break;
          }
        }

        const bet = this.parseBet(rawBet, user.balance);

        if (isNaN(bet) || bet <= 0) {
          return sendMsg(`❌ ɪɴᴠᴀʟɪᴅ ʙᴇᴛ! ᴘʟᴇᴀsᴇ sᴘᴇᴄɪғʏ ᴀ ᴠᴀʟɪᴅ ᴀᴍᴏᴜɴᴛ.\n👉 {p}penalty challenge 10B`);
        }
        if (bet > MAX_BET) {
          return sendMsg(`❌ ᴍᴀxɪᴍᴜᴍ ʙᴇᴛ ʟɪᴍɪᴛ ɪs $${this.formatMoney(MAX_BET)} ($69B)!`);
        }
        if (user.balance < bet) {
          return sendMsg(`❌ ɪɴsᴜғғɪᴄɪᴇɴᴛ ғᴜɴᴅs! ʏᴏᴜ ʜᴀᴠᴇ $${this.formatMoney(user.balance)}`);
        }

        const directions = ["left", "center", "right"];
        const secretShot = directions[Math.floor(Math.random() * directions.length)];

        const senderName = await this.getName(usersData, senderID);
        const targetName = await this.getName(usersData, targetID);

        global.penaltyGames.challenges.set(`${senderID}_${targetID}`, {
          challengerID: senderID,
          targetID,
          bet,
          secretShot
        });

        this.setReaction(api, messageID, "⚔️");

        return api.sendMessage({
          body: `⚔️ ᴘᴇɴᴀʟᴛʏ ᴄʜᴀʟʟᴇɴɢᴇ ⚔️\n\n👤 ${senderName} ᴄʜᴀʟʟᴇɴɢᴇᴅ ${targetName}\n💰 ʙᴇᴛ ᴀᴍᴏᴜɴᴛ: $${this.formatMoney(bet)}\n\n👉 ${targetName} ᴛʏᴘᴇ:\n{p}penalty accept <1/2/3>\n\n[1] Left ⬅️  [2] Center 🎯  [3] Right ➡️`,
          mentions: [
            { tag: senderName, id: senderID },
            { tag: targetName, id: targetID }
          ]
        }, threadID);
      }

      // Single Player PvE
      const bet = this.parseBet(args[0], user.balance);
      const dirInput = (args[1] || "").toLowerCase();
      const validDir = ["left", "center", "right", "1", "2", "3"];

      if (isNaN(bet) || bet <= 0) return sendMsg("❌ ᴜsᴀɢᴇ: {p}penalty <bet/69b> [1/2/3]");
      if (bet > MAX_BET) return sendMsg(`❌ ᴍᴀxɪᴍᴜᴍ ʙᴇᴛ ʟɪᴍɪᴛ ɪs $${this.formatMoney(MAX_BET)} ($69B)!`);
      if (user.balance < bet) return sendMsg(`❌ ɪɴsᴜғғɪᴄɪᴇɴᴛ ғᴜɴᴅs!`);

      if (validDir.includes(dirInput)) {
        let playerDir = dirInput;
        if (dirInput === "1") playerDir = "left";
        if (dirInput === "2") playerDir = "center";
        if (dirInput === "3") playerDir = "right";

        return this.playPvEGame({ api, event, senderID, user, bet, playerDir, limitInfo, BANK_NAME });
      }

      const promptText = `⚽ ───[ ᴘᴇɴᴀʟᴛʏ sʜᴏᴏᴛᴏᴜᴛ ]─── ⚽\n\n` +
        `💰 ʙᴇᴛ ᴀᴍᴏᴜɴᴛ : $${this.formatMoney(bet)}\n\n` +
        `🎯 Choose your shooting direction:\n` +
        `[1] Left ⬅️\n[2] Center 🎯\n[3] Right ➡️\n\n` +
        `💡 ʀᴇᴘʟʏ ᴡɪᴛʜ 1, 2, ᴏʀ 3!`;

      const res = await sendMsg(promptText);

      if (global.GoatBot && global.GoatBot.onReply) {
        global.GoatBot.onReply.set(res.messageID, {
          commandName: this.config.name,
          messageID: res.messageID,
          author: senderID,
          bet: bet,
          user: user,
          limitInfo: limitInfo,
          BANK_NAME: BANK_NAME
        });
      }

    } catch (err) {
      console.error("Penalty Game Error:", err);
      return sendMsg("❌ ᴘᴇɴᴀʟᴛʏ ɢᴀᴍᴇ ᴇʀʀᴏʀ!");
    }
  },

  onReply: async function ({ api, event, message, Reply }) {
    const sendMsg = (txt) => message && typeof message.reply === "function" ? message.reply(txt) : api.sendMessage(txt, event.threadID, event.messageID);
    const { senderID, body } = event;

    if (!Reply) return;
    if (Reply.author !== senderID) return sendMsg("❌ ᴛʜɪs ɪs ɴᴏᴛ ʏᴏᴜʀ ɢᴀᴍᴇ!");

    const choice = (body || "").trim();
    const dirMap = { "1": "left", "2": "center", "3": "right", "left": "left", "center": "center", "right": "right" };

    if (!dirMap[choice.toLowerCase()]) {
      return sendMsg("❌ ɪɴᴠᴀʟɪᴅ ᴄʜᴏɪᴄᴇ! ᴘʟᴇᴀsᴇ ʀᴇᴘʟʏ ᴡɪᴛʜ 1, 2, ᴏʀ 3.");
    }

    if (global.GoatBot && global.GoatBot.onReply) {
      global.GoatBot.onReply.delete(Reply.messageID);
    }

    return this.playPvEGame({
      api,
      event,
      senderID,
      user: Reply.user,
      bet: Reply.bet,
      playerDir: dirMap[choice.toLowerCase()],
      limitInfo: Reply.limitInfo,
      BANK_NAME: Reply.BANK_NAME
    });
  },

  playPvEGame: async function ({ api, event, senderID, user, bet, playerDir, limitInfo, BANK_NAME }) {
    const sendMsg = (txt) => api.sendMessage(txt, event.threadID, event.messageID);
    let videoPath = "";

    try {
      this.setReaction(api, event.messageID, "⏳");

      const directions = ["left", "center", "right"];
      const botDir = directions[Math.floor(Math.random() * directions.length)];
      const isGoal = playerDir !== botDir;

      let updatedUser;

      if (isGoal) {
        updatedUser = await BankUser.findOneAndUpdate(
          { userID: senderID },
          { $inc: { balance: bet, goals: 1 } },
          { upsert: true, returnDocument: 'after' }
        );
      } else {
        updatedUser = await BankUser.findOneAndUpdate(
          { userID: senderID },
          { $inc: { balance: -bet } },
          { upsert: true, returnDocument: 'after' }
        );

        await BankUser.findOneAndUpdate(
          { userID: BOT_USER_ID },
          { $inc: { saves: 1 } },
          { upsert: true, returnDocument: 'after' }
        );
      }

      const currentBalance = updatedUser ? updatedUser.balance : user.balance;

      this.incrementLimit(senderID, limitInfo.userData);

      const videoUrl = isGoal ? VIDEOS.goal[playerDir] : VIDEOS.save[playerDir];
      videoPath = path.join(__dirname, `vid_${senderID}_${Date.now()}.mp4`);
      await this.downloadFile(videoUrl, videoPath);

      const msgText = isGoal
        ? `✨ ───[ ᴘᴇɴᴀʟᴛʏ ɢᴏᴀʟ ]─── ✨\n\n` +
          `⚽ ʏᴏᴜ sᴄᴏʀᴇᴅ  : ${playerDir.toUpperCase()}\n` +
          `🧤 ʙᴏᴛ ᴅɪᴠᴇᴅ   : ${botDir.toUpperCase()}\n` +
          `💰 ᴘʀᴏғɪᴛ      : +$${this.formatMoney(bet)}\n` +
          `💳 ʙᴀʟᴀɴᴄᴇ     : $${currentBalance.toLocaleString()}\n` +
          `🏦 ʙᴀɴᴋ        : ${BANK_NAME}`
        : `✨ ───[ ᴘᴇɴᴀʟᴛʏ sᴀᴠᴇᴅ ]─── ✨\n\n` +
          `⚽ ʏᴏᴜ sʜᴏᴛ    : ${playerDir.toUpperCase()}\n` +
          `🧤 ʙᴏᴛ sᴀᴠᴇᴅ   : ${botDir.toUpperCase()}\n` +
          `💸 ʟᴏss        : -$${this.formatMoney(bet)}\n` +
          `💳 ʙᴀʟᴀɴᴄᴇ     : $${currentBalance.toLocaleString()}\n` +
          `🏦 ʙᴀɴᴋ        : ${BANK_NAME}`;

      this.setReaction(api, event.messageID, isGoal ? "⚽" : "🧤");

      const videoStream = fs.existsSync(videoPath) ? fs.createReadStream(videoPath) : null;
      return api.sendMessage({ body: msgText, attachment: videoStream }, event.threadID, () => {
        if (fs.existsSync(videoPath)) fs.unlinkSync(videoPath);
      }, event.messageID);

    } catch (err) {
      console.error("PvE Game Error:", err);
      if (fs.existsSync(videoPath)) fs.unlinkSync(videoPath);
      return sendMsg("❌ ᴇʀʀᴏʀ ᴘʀᴏᴄᴇssɪɴɢ ʏᴏᴜʀ ɢᴀᴍᴇ!");
    }
  }
};

View raw file