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.
.gachaCara pakai
.gacha
Contoh
.gacha
Kode sumber plugin
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,
})
}
