local nk = require("nakama") local gacha = {} -- Embedded Gacha Data from gacha_data.json local GACHA_DATA = { banners = { star = { name = "Star Banner", currency = "star", pull_1_cost = 160, pull_10_cost = 1440, pity_at = 90, rates = { common = 0.60, uncommon = 0.25, rare = 0.14, real_prize = 0.01 } }, gold = { name = "Gold Banner", currency = "gold", pull_1_cost = 50, pull_10_cost = 450, pity_at = 90, rates = { common = 0.60, uncommon = 0.25, rare = 0.14, real_prize = 0.01 } } }, fragments = { frag_common = { name = "Common Fragment", rarity = "common", icon = "⬜" }, frag_uncommon = { name = "Uncommon Fragment", rarity = "uncommon", icon = "🟩" }, frag_rare = { name = "Rare Fragment", rarity = "rare", icon = "🟦" } }, pools = { common = {"frag_common"}, uncommon = {"frag_uncommon"}, rare = {"frag_rare"}, real_prize = { "skin_gacha_rainbow_suit", "skin_gacha_dragon_hat", "skin_gacha_phantom_gloves", "skin_gacha_neon_acc" } }, real_prize_catalog = { skin_gacha_rainbow_suit = { name = "Rainbow Suit", category = "costume", rarity = "real_prize", character = "" }, skin_gacha_dragon_hat = { name = "Dragon Hat", category = "head", rarity = "real_prize", character = "" }, skin_gacha_phantom_gloves = { name = "Phantom Gloves", category = "glove", rarity = "real_prize", character = "" }, skin_gacha_neon_acc = { name = "Neon Accessory", category = "accessory", rarity = "real_prize", character = "" } } } local function roll_rarity(rates) local roll = math.random() local cumulative = 0.0 local rarities = {"real_prize", "rare", "uncommon", "common"} for _, rarity in ipairs(rarities) do cumulative = cumulative + (rates[rarity] or 0.0) if roll <= cumulative then return rarity end end return "common" end local function pick_from_pool(rarity) local pool = GACHA_DATA.pools[rarity] if not pool or #pool == 0 then return "" end return pool[math.random(1, #pool)] end function gacha.rpc_perform_gacha_pull(context, payload) if not context.user_id then error("Not authenticated") end local request = nk.json_decode(payload) local banner_id = request.banner_id local count = request.count or 1 if not banner_id or banner_id == "" then error("Banner ID required") end if count < 1 then error("Invalid count") end local banner = GACHA_DATA.banners[banner_id] if not banner then error("Unknown banner: " .. banner_id) end local currency = banner.currency or "star" local cost = banner["pull_" .. count .. "_cost"] if not cost then cost = (banner.pull_1_cost or 999) * count end local pity_at = banner.pity_at or 90 -- Read wallet local status, account = pcall(nk.account_get_id, context.user_id) if not status or not account then error("Could not read account") end local wallet = account.wallet if type(wallet) == "string" then wallet = nk.json_decode(wallet) end local bal = wallet[currency] or 0 if bal < cost then error("Insufficient currency") end -- Read pity and fragments from storage local pity_counters = {} local fragments = {} local read_reqs = { { collection = "profiles", key = "pity_counters", user_id = context.user_id }, { collection = "profiles", key = "fragments", user_id = context.user_id } } local status_read, read_objs = pcall(nk.storage_read, read_reqs) if status_read and read_objs then for _, obj in ipairs(read_objs) do if obj.key == "pity_counters" then pity_counters = obj.value elseif obj.key == "fragments" then fragments = obj.value end end end -- Perform pulls local results = {} local inventory_writes = {} local inventory_items = {} for i = 1, count do local pity = pity_counters[banner_id] or 0 local rarity if pity + 1 >= pity_at then rarity = "real_prize" pity_counters[banner_id] = 0 else rarity = roll_rarity(banner.rates) pity_counters[banner_id] = (rarity == "real_prize") and 0 or (pity + 1) end local item_id = pick_from_pool(rarity) local item_data = {} if rarity == "real_prize" then item_data = GACHA_DATA.real_prize_catalog[item_id] or {} -- Add to inventory_writes table.insert(inventory_writes, { collection = "inventory", key = item_id, user_id = context.user_id, value = { category = item_data.category or "accessory", purchased_at = os.date("!%Y-%m-%dT%H:%M:%S.000Z"), quantity = 1 }, permission_read = 1, permission_write = 0 }) table.insert(inventory_items, item_id) else item_data = GACHA_DATA.fragments[item_id] or {} fragments[item_id] = (fragments[item_id] or 0) + 1 end table.insert(results, { id = item_id, rarity = rarity, name = item_data.name or item_id }) end -- Write wallet local changeset = {} changeset[currency] = -cost local s, err = pcall(function() nk.wallet_update(context.user_id, changeset, {}, true) end) if not s then error("Failed to update wallet") end -- Write storage (pity, fragments, inventory) local write_objs = { { collection = "profiles", key = "pity_counters", user_id = context.user_id, value = pity_counters, permission_read = 1, permission_write = 0 }, { collection = "profiles", key = "fragments", user_id = context.user_id, value = fragments, permission_read = 1, permission_write = 0 } } for _, iw in ipairs(inventory_writes) do table.insert(write_objs, iw) end local s2, err2 = pcall(nk.storage_write, write_objs) if not s2 then error("Failed to write storage: " .. tostring(err2)) end return nk.json_encode({ success = true, results = results, new_pity = pity_counters[banner_id] }) end nk.register_rpc(gacha.rpc_perform_gacha_pull, "perform_gacha_pull") nk.logger_info("LUA TEST: gacha module loaded successfully") return gacha