Logo OmniDevvKANA Web
Masuk
Kembali ke katalog plugin

rpg

gacha

Putar gacha 100 koin untuk dapat item acak

Spin the 100 coin gacha for a random item

plugins/rpg/gacha.js

Lokasi file di repo kana-md

1

Perintah termasuk alias

5.0 KB

Ukuran file sumber

4/11/2026

Update terakhir, commit 44f4010

Daftar perintah

Ketuk tombol salin untuk menyalin perintah, lalu tempel di chat WhatsApp bot KANA.

.gacha

Cara pakai

.gacha

Contoh

.gacha

Kode sumber plugin

Unduh .js

Cara pasang: simpan file ini ke folder plugins/rpg/ di bot KANA kamu, lalu restart bot. Pemilik source code lain bisa mengambil update plugin terbaru dari halaman ini kapan saja.

import { sendButtons } from '../../src/ui/buttons.js'
import { getProfile, updateProfile } from '../../src/services/user-service.js'

export const config = {
  name: 'gacha',
  alias: [],
  category: 'rpg',
  description: { id: 'Putar gacha 100 koin untuk dapat item acak', en: 'Spin the 100 coin gacha for a random item' },
  usage: '.gacha',
  example: '.gacha',
  cooldown: 3,
}

export const GACHA_COST = 100

// Pool bawaan dipakai bila src/rpg/data.js belum tersedia atau impornya
// gagal. Nama item yang masuk inventory adalah id item.
export const FALLBACK_POOL = [
  { id: 'potion-kecil', name: 'Potion Kecil', emoji: '๐Ÿงช', rarity: 'common' },
  { id: 'roti-tawar', name: 'Roti Tawar', emoji: '๐Ÿž', rarity: 'common' },
  { id: 'batu-sungai', name: 'Batu Sungai', emoji: '๐Ÿชจ', rarity: 'common' },
  { id: 'perisai-kayu', name: 'Perisai Kayu', emoji: '๐Ÿ›ก๏ธ', rarity: 'uncommon' },
  { id: 'pedang-latihan', name: 'Pedang Latihan', emoji: '๐Ÿ—ก๏ธ', rarity: 'uncommon' },
  { id: 'jubah-penjelajah', name: 'Jubah Penjelajah', emoji: '๐Ÿงฅ', rarity: 'rare' },
  { id: 'kalung-bulan', name: 'Kalung Bulan', emoji: '๐Ÿ“ฟ', rarity: 'rare' },
  { id: 'sayap-pegasus', name: 'Sayap Pegasus', emoji: '๐Ÿชฝ', rarity: 'epic' },
  { id: 'mahkota-bintang', name: 'Mahkota Bintang', emoji: '๐Ÿ‘‘', rarity: 'epic' },
  { id: 'telur-naga', name: 'Telur Naga', emoji: '๐Ÿฅš', rarity: 'legendary' },
]

const RARITY_WEIGHTS = { common: 50, uncommon: 25, rare: 15, epic: 7, legendary: 3 }
export const RARITY_EMOJI = { common: 'โšช', uncommon: '๐ŸŸข', rare: '๐Ÿ”ต', epic: '๐ŸŸฃ', legendary: '๐ŸŸก' }

export function pickWeighted(pool, rng = Math.random) {
  const entries = (pool ?? []).filter((item) => item && (item.id || item.name))
  if (!entries.length) return null
  const weights = entries.map((item) => RARITY_WEIGHTS[String(item.rarity || 'common').toLowerCase()] ?? 10)
  const total = weights.reduce((a, b) => a + b, 0)
  let roll = rng() * total
  for (let i = 0; i < entries.length; i++) {
    roll -= weights[i]
    if (roll <= 0) return entries[i]
  }
  return entries[entries.length - 1]
}

async function loadPool() {
  try {
    const mod = await import('../../src/rpg/data.js')
    const raw = mod.ITEMS ?? mod.items ?? mod.default?.ITEMS
    const list = Array.isArray(raw) ? raw : raw && typeof raw === 'object' ? Object.values(raw) : null
    if (list && list.length) return list
  } catch {
    // data RPG belum ada: pakai pool bawaan.
  }
  return FALLBACK_POOL
}

export const i18n = {
  id: {
    gacha: {
      title: 'Gacha KANA ๐ŸŽ',
      noBalance: 'Mouu, gacha butuh {cost} koin, saldo kamu cuma {balance} koin (โ•ฅ๏นโ•ฅ) Ambil jatah harianmu dulu yaa~',
      result: 'Putar putar putar... ๐ŸŽ\nKamu dapat: {emoji} {name}\nRarity: {rarityEmoji} {rarity}\nBiaya {cost} koin, saldo sekarang {balance} koin.\n\n{mood}',
      btnAgain: 'Gacha Lagi',
      btnInventory: 'Inventory',
      btnShop: 'Shop',
      btnDaily: 'Ambil Daily',
    },
  },
  en: {
    gacha: {
      title: 'KANA Gacha ๐ŸŽ',
      noBalance: 'Mouu, gacha costs {cost} coins, your balance is only {balance} coins (โ•ฅ๏นโ•ฅ) Claim your daily first~',
      result: 'Spinning spinning... ๐ŸŽ\nYou got: {emoji} {name}\nRarity: {rarityEmoji} {rarity}\nCost {cost} coins, balance now {balance} coins.\n\n{mood}',
      btnAgain: 'Spin Again',
      btnInventory: 'Inventory',
      btnShop: 'Shop',
      btnDaily: 'Claim Daily',
    },
  },
}

export const test = { input: '.gacha', expect: /gacha/i }

export async function handler(ctx) {
  const profile = await getProfile(ctx.storage, ctx.senderDigits)
  const balance = profile.balance || 0
  if (balance < GACHA_COST) {
    const body = ctx.t('gacha.noBalance', { cost: GACHA_COST, balance })
    return sendButtons(ctx.sock, ctx.chat, {
      title: ctx.t('gacha.title'),
      body,
      footer: ctx.t('menu.footer'),
      replies: [{ text: ctx.t('gacha.btnDaily'), id: '.daily' }],
      textFallback: body,
    })
  }
  const pool = await loadPool()
  const item = pickWeighted(pool)
  const itemId = String(item.id ?? item.name)
  const inventory = profile.inventory && typeof profile.inventory === 'object' ? { ...profile.inventory } : {}
  if (Array.isArray(inventory)) inventory.push(itemId)
  else inventory[itemId] = (Number(inventory[itemId]) || 0) + 1
  const updated = await updateProfile(ctx.storage, ctx.senderDigits, {
    balance: balance - GACHA_COST,
    inventory,
  })
  const rarity = String(item.rarity || 'common')
  const body = ctx.t('gacha.result', {
    emoji: item.emoji || RARITY_EMOJI[rarity.toLowerCase()] || '๐ŸŽ',
    name: item.name || itemId,
    rarityEmoji: RARITY_EMOJI[rarity.toLowerCase()] || 'โšช',
    rarity,
    cost: GACHA_COST,
    balance: updated.balance,
    mood: ctx.say('senang'),
  })
  return sendButtons(ctx.sock, ctx.chat, {
    title: ctx.t('gacha.title'),
    body,
    footer: ctx.t('menu.footer'),
    replies: [
      { text: ctx.t('gacha.btnAgain'), id: '.gacha' },
      { text: ctx.t('gacha.btnInventory'), id: '.inventory' },
      { text: ctx.t('gacha.btnShop'), id: '.shop' },
    ],
    textFallback: body,
  })
}