Files
Dota-Zombie-Invasion/scripts/vscripts/store_arcade_packs.lua
T
2026-05-29 15:11:31 +07:00

821 lines
33 KiB
Lua

local ____lualib = require("lualib_bundle")
local Map = ____lualib.Map
local __TS__New = ____lualib.__TS__New
local Set = ____lualib.Set
local __TS__Number = ____lualib.__TS__Number
local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray
local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign
local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite
local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries
local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter
local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes
local __TS__ArrayEvery = ____lualib.__TS__ArrayEvery
local ____exports = {}
local getPurchasableCardPool, getPurchasablePoolByQuality, pickCardExactQuality, purchasableCardPoolCache, purchasablePoolByQualityCache, ARCADE_PACK_QUALITY_PICK_ATTEMPTS
local ____card_catalog = require("card_catalog")
local ALL_CARD_CATALOG_DEFS = ____card_catalog.ALL_CARD_CATALOG_DEFS
local ____CardSystem = require("cards.CardSystem")
local CardQuality = ____CardSystem.CardQuality
local CardSystem = ____CardSystem.CardSystem
local ____custom_game_events = require("custom_game_events")
local ensurePlayerCardSystem = ____custom_game_events.ensurePlayerCardSystem
local ____api_helper = require("api_helper")
local encodeApiBody = ____api_helper.encodeApiBody
local setApiHeaders = ____api_helper.setApiHeaders
local ____player_info = require("player_info")
local PlayerInfo = ____player_info.PlayerInfo
local ____server_config = require("server_config")
local SERVER_CONFIG = ____server_config.SERVER_CONFIG
local ____store_manager = require("store_manager")
local StoreManager = ____store_manager.StoreManager
function getPurchasableCardPool(self)
if purchasableCardPoolCache and #purchasableCardPoolCache > 0 then
return purchasableCardPoolCache
end
local pool = {}
for ____, def in ipairs(ALL_CARD_CATALOG_DEFS) do
do
local id = math.floor(__TS__Number(def.id))
if not __TS__NumberIsFinite(id) or id <= 0 or id == 404 then
goto __continue46
end
if def.defaultCard == true or def.purchasable == false then
goto __continue46
end
local runtimeData = CardSystem.cardData[id]
if (runtimeData and runtimeData.disabled) == true then
goto __continue46
end
pool[#pool + 1] = id
end
::__continue46::
end
if #pool == 0 then
for ____, ____value in ipairs(__TS__ObjectEntries(CardSystem.cardData)) do
local idKey = ____value[1]
local data = ____value[2]
do
local id = math.floor(__TS__Number(idKey))
if not __TS__NumberIsFinite(id) or id <= 0 or data.disabled == true or data.default == true or data.purchasable == false then
goto __continue52
end
pool[#pool + 1] = id
end
::__continue52::
end
end
purchasableCardPoolCache = pool
purchasablePoolByQualityCache = nil
return pool
end
function getPurchasablePoolByQuality(self)
if purchasablePoolByQualityCache then
return purchasablePoolByQualityCache
end
local byQuality = {}
for ____, cardId in ipairs(getPurchasableCardPool(nil)) do
local cardData = CardSystem.cardData[cardId]
local quality = math.max(
CardQuality.COMMON,
math.min(
CardQuality.MYTHIC,
math.floor(__TS__Number(cardData and cardData.quality or CardQuality.COMMON))
)
)
if not byQuality[quality] then
byQuality[quality] = {}
end
local ____byQuality_quality_11 = byQuality[quality]
____byQuality_quality_11[#____byQuality_quality_11 + 1] = cardId
end
purchasablePoolByQualityCache = byQuality
return byQuality
end
function pickCardExactQuality(self, quality, excludeIds)
local pool = getPurchasablePoolByQuality(nil)[quality]
if not pool or #pool == 0 then
return nil
end
local exclude = __TS__New(Set, excludeIds)
local available = __TS__ArrayFilter(
pool,
function(____, id) return not exclude:has(id) end
)
local pickFrom = #available > 0 and available or pool
return pickFrom[RandomInt(0, #pickFrom - 1) + 1]
end
____exports.ARCADE_PACK_DEFINITIONS = {arcade_pack_standard = {id = "arcade_pack_standard", cardsCount = 3, price_free = 25000, allowed_currencies = {"free_currency"}}, arcade_pack_premium = {id = "arcade_pack_premium", cardsCount = 3, price_donate = 80, allowed_currencies = {"donate_currency"}}}
--- Пороги pity: паков подряд без редкости → гарантия в следующем паке.
local ARCADE_PITY_STANDARD_MYTHIC_PACKS = 100
local ARCADE_PITY_STANDARD_LEGENDARY_PACKS = 50
local ARCADE_PITY_PREMIUM_MYTHIC_PACKS = 10
local ARCADE_SESSION_TTL_SECONDS = 600
local sessionsByPlayerId = __TS__New(Map)
local arcadePityByPlayerId = __TS__New(Map)
local arcadePityLoadedFromBackend = __TS__New(Set)
local function defaultArcadePityState(self)
return {standardPacksWithoutMythic = 0, standardPacksWithoutLegendary = 0, premiumPacksWithoutMythic = 0}
end
local function normalizeArcadePityState(self, raw)
local state = defaultArcadePityState(nil)
if not raw or type(raw) ~= "table" then
return state
end
local obj = raw
state.standardPacksWithoutMythic = math.max(
0,
math.floor(__TS__Number(obj.standardPacksWithoutMythic) or 0)
)
local ____math_max_2 = math.max
local ____math_floor_1 = math.floor
local ____obj_standardPacksWithoutLegendary_0 = obj.standardPacksWithoutLegendary
if ____obj_standardPacksWithoutLegendary_0 == nil then
____obj_standardPacksWithoutLegendary_0 = obj.standardPacksWithoutEpic
end
state.standardPacksWithoutLegendary = ____math_max_2(
0,
____math_floor_1(__TS__Number(____obj_standardPacksWithoutLegendary_0) or 0)
)
state.premiumPacksWithoutMythic = math.max(
0,
math.floor(__TS__Number(obj.premiumPacksWithoutMythic) or 0)
)
return state
end
local function decodeJsonBody(self, body)
do
local function ____catch()
return true, nil
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
return true, {json.decode(body)}
end)
if not ____try then
____hasReturned, ____returnValue = ____catch()
end
if ____hasReturned then
return ____returnValue
end
end
end
local function unwrapApiJsonObject(self, decoded)
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
return decoded[1]
end
return decoded
end
local function parseArcadePityPayload(self, raw)
if not raw or type(raw) ~= "table" then
return nil
end
local root = raw
local pityRaw = root.arcade_pity
if pityRaw == nil or pityRaw == nil then
return normalizeArcadePityState(nil, nil)
end
return normalizeArcadePityState(nil, pityRaw)
end
local function applyArcadePityState(self, playerId, state)
arcadePityByPlayerId:set(playerId, state)
local info = PlayerInfo:GetPlayerInfo(playerId) or ({})
PlayerInfo:UpdatePlayerInfo(
playerId,
__TS__ObjectAssign({}, info, {arcade_pity = state})
)
end
local function saveArcadePityToBackend(self, playerId, state)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
return
end
local request = CreateHTTPRequest(
"PUT",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arcade_pity"
)
setApiHeaders(nil, request)
request:SetHTTPRequestRawPostBody(
"application/json",
encodeApiBody(nil, {arcade_pity = state})
)
request:Send(function(result)
if result.StatusCode < 200 or result.StatusCode >= 300 then
print((((("[ARCADE_PITY] PUT fail player=" .. tostring(playerId)) .. " steam=") .. tostring(steamId)) .. " code=") .. tostring(result.StatusCode))
end
end)
end
--- Загрузить pity с бэка при входе в игру / реконнект.
function ____exports.loadArcadePityForPlayer(self, playerId)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
return
end
local request = CreateHTTPRequest(
"GET",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arcade_pity"
)
setApiHeaders(nil, request)
request:Send(function(result)
if result.StatusCode < 200 or result.StatusCode >= 300 then
print((((("[ARCADE_PITY] GET fail player=" .. tostring(playerId)) .. " steam=") .. tostring(steamId)) .. " code=") .. tostring(result.StatusCode))
return
end
local bodyStr = result.Body ~= nil and tostring(result.Body) or ""
if #bodyStr == 0 then
return
end
local decoded = decodeJsonBody(nil, bodyStr)
local obj = unwrapApiJsonObject(nil, decoded)
local state = parseArcadePityPayload(nil, obj)
if state == nil then
print((("[ARCADE_PITY] GET parse invalid player=" .. tostring(playerId)) .. " steam=") .. tostring(steamId))
return
end
arcadePityLoadedFromBackend:add(playerId)
applyArcadePityState(nil, playerId, state)
print((((((("[ARCADE_PITY] GET ok player=" .. tostring(playerId)) .. " stdM=") .. tostring(state.standardPacksWithoutMythic)) .. " stdL=") .. tostring(state.standardPacksWithoutLegendary)) .. " premM=") .. tostring(state.premiumPacksWithoutMythic))
end)
end
local function getArcadePityState(self, playerId)
local state = arcadePityByPlayerId:get(playerId)
if not state then
local info = PlayerInfo:GetPlayerInfo(playerId)
state = normalizeArcadePityState(nil, info and info.arcade_pity)
arcadePityByPlayerId:set(playerId, state)
end
return state
end
local function saveArcadePityState(self, playerId, state)
applyArcadePityState(nil, playerId, state)
saveArcadePityToBackend(nil, playerId, state)
end
local function getCardQualityForPity(self, cardId)
local cardData = CardSystem.cardData[cardId]
return math.max(
CardQuality.COMMON,
math.min(
CardQuality.MYTHIC,
math.floor(__TS__Number(cardData and cardData.quality or CardQuality.COMMON))
)
)
end
local function packHasMythic(self, cardIds)
for ____, cardId in ipairs(cardIds) do
if getCardQualityForPity(nil, cardId) == CardQuality.MYTHIC then
return true
end
end
return false
end
local function packHasLegendaryOrBetter(self, cardIds)
for ____, cardId in ipairs(cardIds) do
if getCardQualityForPity(nil, cardId) >= CardQuality.LEGENDARY then
return true
end
end
return false
end
local function buildArcadePityPlan(self, packId, state)
local plan = {}
local slot = 0
if packId == "arcade_pack_premium" then
if state.premiumPacksWithoutMythic >= ARCADE_PITY_PREMIUM_MYTHIC_PACKS then
plan.mythicSlot = slot
slot = slot + 1
end
return plan
end
if state.standardPacksWithoutMythic >= ARCADE_PITY_STANDARD_MYTHIC_PACKS then
plan.mythicSlot = slot
slot = slot + 1
end
if state.standardPacksWithoutLegendary >= ARCADE_PITY_STANDARD_LEGENDARY_PACKS then
plan.legendarySlot = slot
slot = slot + 1
end
return plan
end
local function pickCardForcedQuality(self, quality, excludeIds)
do
local attempt = 0
while attempt < ARCADE_PACK_QUALITY_PICK_ATTEMPTS do
local cardId = pickCardExactQuality(nil, quality, excludeIds)
if cardId ~= nil then
return cardId
end
attempt = attempt + 1
end
end
return nil
end
--- Шансы редкости карты в паке аркады (сумма = 100%, шкала 1..10000).
local ARCADE_PACK_QUALITY_ROLL_MAX = 10000
local ARCADE_PACK_STANDARD_QUALITY_THRESHOLDS = {
{quality = CardQuality.COMMON, cumulative = 5500},
{quality = CardQuality.RARE, cumulative = 8000},
{quality = CardQuality.EPIC, cumulative = 9500},
{quality = CardQuality.LEGENDARY, cumulative = 9980},
{quality = CardQuality.MYTHIC, cumulative = 10000}
}
--- Премиум-пак: только эпик / легендарка / мифик.
local ARCADE_PACK_PREMIUM_QUALITY_THRESHOLDS = {{quality = CardQuality.EPIC, cumulative = 6000}, {quality = CardQuality.LEGENDARY, cumulative = 9800}, {quality = CardQuality.MYTHIC, cumulative = 10000}}
ARCADE_PACK_QUALITY_PICK_ATTEMPTS = 64
local function getArcadePackQualityWeights(self, packId)
local thresholds = packId == "arcade_pack_premium" and ARCADE_PACK_PREMIUM_QUALITY_THRESHOLDS or ARCADE_PACK_STANDARD_QUALITY_THRESHOLDS
local weights = {}
local prevCumulative = 0
for ____, entry in ipairs(thresholds) do
weights[#weights + 1] = {quality = entry.quality, weight = entry.cumulative - prevCumulative}
prevCumulative = entry.cumulative
end
return weights
end
local function rollArcadePackQualityFromWeights(self, weights)
local total = 0
for ____, w in ipairs(weights) do
total = total + w.weight
end
if total <= 0 then
local ____opt_12 = weights[#weights]
return ____opt_12 and ____opt_12.quality or CardQuality.COMMON
end
local roll = RandomInt(1, total)
for ____, w in ipairs(weights) do
if roll <= w.weight then
return w.quality
end
roll = roll - w.weight
end
return weights[#weights].quality
end
--- Редкости, для которых в каталоге есть хотя бы одна карта.
local function getStockedQualityWeights(self, packId)
local minQuality = packId == "arcade_pack_premium" and CardQuality.EPIC or CardQuality.COMMON
local byQuality = getPurchasablePoolByQuality(nil)
return __TS__ArrayFilter(
getArcadePackQualityWeights(nil, packId),
function(____, entry)
if entry.quality < minQuality then
return false
end
local pool = byQuality[entry.quality]
return pool ~= nil and #pool > 0
end
)
end
--- Сначала бросок по таблице шансов → карта строго этой редкости.
-- Если пул пуст — повторный бросок (без понижения/повышения тира).
local function pickRandomCardForPack(self, packId, excludeIds)
local tableWeights = getArcadePackQualityWeights(nil, packId)
do
local attempt = 0
while attempt < ARCADE_PACK_QUALITY_PICK_ATTEMPTS do
local rolledQuality = rollArcadePackQualityFromWeights(nil, tableWeights)
local cardId = pickCardExactQuality(nil, rolledQuality, excludeIds)
if cardId ~= nil then
return cardId
end
attempt = attempt + 1
end
end
local stockedWeights = getStockedQualityWeights(nil, packId)
if #stockedWeights == 0 then
return nil
end
do
local attempt = 0
while attempt < ARCADE_PACK_QUALITY_PICK_ATTEMPTS do
local rolledQuality = rollArcadePackQualityFromWeights(nil, stockedWeights)
local cardId = pickCardExactQuality(nil, rolledQuality, excludeIds)
if cardId ~= nil then
return cardId
end
attempt = attempt + 1
end
end
return nil
end
local function rollRandomCardIds(self, count, packId, pityPlan)
if #getPurchasableCardPool(nil) == 0 then
return {}
end
local forcedBySlot = __TS__New(Map)
if (pityPlan and pityPlan.mythicSlot) ~= nil and pityPlan.mythicSlot < count then
forcedBySlot:set(pityPlan.mythicSlot, CardQuality.MYTHIC)
end
if (pityPlan and pityPlan.legendarySlot) ~= nil and pityPlan.legendarySlot < count and not forcedBySlot:has(pityPlan.legendarySlot) then
forcedBySlot:set(pityPlan.legendarySlot, CardQuality.LEGENDARY)
end
local result = {}
do
local slot = 0
while slot < count do
local forcedQuality = forcedBySlot:get(slot)
local cardId
if forcedQuality ~= nil then
cardId = pickCardForcedQuality(nil, forcedQuality, result)
end
if cardId == nil then
cardId = pickRandomCardForPack(nil, packId, result)
end
if cardId ~= nil then
result[#result + 1] = cardId
end
slot = slot + 1
end
end
return result
end
local function clearExpiredSession(self, playerId)
local session = sessionsByPlayerId:get(playerId)
if not session then
return
end
if GameRules:GetGameTime() - session.createdAt > ARCADE_SESSION_TTL_SECONDS then
sessionsByPlayerId:delete(playerId)
end
end
local function updateArcadePityAfterPack(self, playerId, packId, cardIds)
local state = getArcadePityState(nil, playerId)
local gotMythic = packHasMythic(nil, cardIds)
local gotLegendaryPlus = packHasLegendaryOrBetter(nil, cardIds)
if packId == "arcade_pack_premium" then
state.premiumPacksWithoutMythic = gotMythic and 0 or state.premiumPacksWithoutMythic + 1
else
state.standardPacksWithoutMythic = gotMythic and 0 or state.standardPacksWithoutMythic + 1
state.standardPacksWithoutLegendary = gotLegendaryPlus and 0 or state.standardPacksWithoutLegendary + 1
end
saveArcadePityState(nil, playerId, state)
end
local function sendArcadeEvent(self, playerId, eventName, payload)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
CustomGameEventManager:Send_ServerToPlayer(player, eventName, payload)
end
local function resolvePackPrice(self, pack, currency)
if currency == "dust_currency" and pack.price_dust ~= nil then
return math.floor(pack.price_dust)
end
if currency == "donate_currency" and pack.price_donate ~= nil then
return math.floor(pack.price_donate)
end
if currency == "free_currency" and pack.price_free ~= nil then
return math.floor(pack.price_free)
end
return -1
end
local function deductCurrency(self, store, playerId, currency, price)
if price <= 0 then
return true
end
if currency == "free_currency" then
return store:removeFreeCurrency(playerId, price)
end
if currency == "donate_currency" then
return store:removeDonateCurrency(playerId, price)
end
if currency == "dust_currency" then
return store:removeDustCurrency(playerId, price)
end
return false
end
local function refundCurrency(self, store, playerId, currency, price)
if price <= 0 then
return
end
if currency == "free_currency" then
store:addFreeCurrency(playerId, price)
elseif currency == "donate_currency" then
store:addDonateCurrency(playerId, price)
elseif currency == "dust_currency" then
store:addDustCurrency(playerId, price)
end
end
local function defaultArcadePackCredits(self)
return {standard = 0, premium = 0}
end
local function normalizeArcadePackCredits(self, raw)
local state = defaultArcadePackCredits(nil)
if not raw or type(raw) ~= "table" then
return state
end
local obj = raw
state.standard = math.max(
0,
math.floor(__TS__Number(obj.standard) or 0)
)
state.premium = math.max(
0,
math.floor(__TS__Number(obj.premium) or 0)
)
return state
end
local function applyArcadePackCredits(self, playerId, credits)
local info = PlayerInfo:GetPlayerInfo(playerId) or ({})
PlayerInfo:UpdatePlayerInfo(
playerId,
__TS__ObjectAssign({}, info, {arcade_pack_credits = credits})
)
end
local function getLocalArcadePackCredits(self, playerId)
local info = PlayerInfo:GetPlayerInfo(playerId)
return normalizeArcadePackCredits(nil, info and info.arcade_pack_credits)
end
local function getArcadePackCreditKey(self, packId)
if packId == "arcade_pack_standard" then
return "standard"
end
if packId == "arcade_pack_premium" then
return "premium"
end
return nil
end
local function tryConsumeArcadePackCredit(self, playerId, packId, onDone)
local creditKey = getArcadePackCreditKey(nil, packId)
if not creditKey then
onDone(nil, false)
return
end
local ____local = getLocalArcadePackCredits(nil, playerId)
if ____local[creditKey] <= 0 then
onDone(nil, false)
return
end
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
onDone(nil, false)
return
end
local request = CreateHTTPRequest(
"POST",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arcade_pack_credits/consume"
)
setApiHeaders(nil, request)
request:SetHTTPRequestRawPostBody(
"application/json",
encodeApiBody(nil, {pack_id = packId})
)
request:Send(function(result)
if result.StatusCode < 200 or result.StatusCode >= 300 then
onDone(nil, false)
return
end
do
local ____try, ____hasReturned, ____returnValue = pcall(function()
local decoded = decodeJsonBody(nil, result.Body)
local data = unwrapApiJsonObject(nil, decoded)
if (data and data.consumed) == true and data.arcade_pack_credits then
local credits = normalizeArcadePackCredits(nil, data.arcade_pack_credits)
applyArcadePackCredits(nil, playerId, credits)
onDone(nil, true)
return true
end
end)
if ____try and ____hasReturned then
return ____returnValue
end
end
onDone(nil, false)
end)
end
local function getCardLevelForPlayer(self, playerId, cardId)
local cardSystem = ensurePlayerCardSystem(nil, playerId)
if not cardSystem then
return 1
end
local level = cardSystem:GetCardLevel(cardId)
return __TS__NumberIsFinite(level) and math.max(
1,
math.floor(level)
) or 1
end
local function buildFlipPayload(self, playerId, cardId, slotIndex)
local cardData = CardSystem.cardData[cardId]
local quality = math.max(
1,
math.floor(__TS__Number(cardData and cardData.quality or CardQuality.COMMON))
)
local cardLevel = getCardLevelForPlayer(nil, playerId, cardId)
local store = StoreManager:getInstance()
local ownedBefore = store:getOwnedCardCopies(playerId, cardId)
local maxCopies = store:getCardMaxCopies(cardId)
local isDuplicate = ownedBefore >= maxCopies
local dustGranted = 0
if isDuplicate then
dustGranted = CardSystem:getDuplicateCardDustCompensation(quality, cardLevel)
if dustGranted > 0 then
store:addDustCurrency(playerId, dustGranted)
store:saveCurrencyToServer(playerId)
end
else
store:grantCardPurchaseWithoutPayment(playerId, cardId)
end
local rawIcon = cardData and cardData.icon
local icon = rawIcon ~= nil and #tostring(rawIcon) > 0 and tostring(rawIcon) or ("file://{images}/custom_game/cards/card_" .. tostring(cardId)) .. ".png"
local rawCardName = cardData and cardData.name
local cardName = rawCardName ~= nil and #tostring(rawCardName) > 0 and tostring(rawCardName) or ("card_" .. tostring(cardId)) .. "_name"
return {
slot_index = slotIndex,
card_id = cardId,
duplicate = isDuplicate and 1 or 0,
dust_granted = dustGranted,
card_level = cardLevel,
quality = quality,
icon = icon,
card_name = cardName
}
end
--- Выдать все карты пака сразу (порядок слотов важен для дубликатов).
local function grantArcadePackCards(self, playerId, cardIds)
local payloads = {}
do
local i = 0
while i < #cardIds do
payloads[#payloads + 1] = buildFlipPayload(nil, playerId, cardIds[i + 1], i)
i = i + 1
end
end
return payloads
end
local function handleBuyArcadePack(self, playerId, packId, selectedCurrency)
local pack = ____exports.ARCADE_PACK_DEFINITIONS[packId]
local store = StoreManager:getInstance()
if not pack then
store:sendPurchaseResult(playerId, false, "Неизвестный пак")
return
end
if not store:tryAcquireStorePurchaseLock(playerId) then
store:sendPurchaseResult(playerId, false, "Подождите, предыдущая покупка обрабатывается")
return
end
store:syncLatestCurrencyTotalsFromApi(
playerId,
function(____, currencyOk)
do
local function ____catch()
store:releaseStorePurchaseLock(playerId)
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
if not currencyOk then
store:sendPurchaseResult(playerId, false, "Не удалось проверить баланс")
store:releaseStorePurchaseLock(playerId)
return true
end
if not arcadePityLoadedFromBackend:has(playerId) then
____exports.loadArcadePityForPlayer(nil, playerId)
end
clearExpiredSession(nil, playerId)
if sessionsByPlayerId:has(playerId) then
store:sendPurchaseResult(playerId, false, "Сначала откройте карты из текущего пака")
store:releaseStorePurchaseLock(playerId)
return true
end
tryConsumeArcadePackCredit(
nil,
playerId,
packId,
function(____, usedCredit)
do
local ____try, ____hasReturned, ____returnValue = pcall(function()
local currency = selectedCurrency or pack.allowed_currencies[1] or "dust_currency"
if not usedCredit and not __TS__ArrayIncludes(pack.allowed_currencies, currency) then
store:sendPurchaseResult(playerId, false, "Эта валюта недоступна для пака")
return true
end
local price = usedCredit and 0 or resolvePackPrice(nil, pack, currency)
if price < 0 then
store:sendPurchaseResult(playerId, false, "Некорректная цена пака")
return true
end
local pityState = getArcadePityState(nil, playerId)
local pityPlan = buildArcadePityPlan(nil, pack.id, pityState)
local cardIds = rollRandomCardIds(nil, pack.cardsCount, pack.id, pityPlan)
if #cardIds < pack.cardsCount then
store:sendPurchaseResult(playerId, false, "Нет доступных карт для пака")
return true
end
if not usedCredit and not deductCurrency(
nil,
store,
playerId,
currency,
price
) then
store:sendPurchaseResult(playerId, false, "Недостаточно валюты")
return true
end
if not usedCredit then
store:advanceCurrencyLoadSeq(playerId)
store:saveCurrencyToServer(playerId)
else
store:notifyArcadePackCredits(playerId)
end
store:updateCurrencyDisplay(playerId)
local flipPayloads = grantArcadePackCards(nil, playerId, cardIds)
updateArcadePityAfterPack(nil, playerId, pack.id, cardIds)
store:updateCurrencyDisplay(playerId)
local session = {
packId = pack.id,
flipPayloads = flipPayloads,
revealed = {},
createdAt = GameRules:GetGameTime()
}
do
local i = 0
while i < pack.cardsCount do
local ____session_revealed_28 = session.revealed
____session_revealed_28[#____session_revealed_28 + 1] = false
i = i + 1
end
end
sessionsByPlayerId:set(playerId, session)
local successMessage = usedCredit and "Открыт бесплатный пак из бандла" or "Пак куплен"
store:sendPurchaseResult(playerId, true, successMessage, pack.id)
sendArcadeEvent(nil, playerId, "store_arcade_pack_opened", {pack_id = pack.id, slot_count = pack.cardsCount, cards = flipPayloads, used_bundle_credit = usedCredit and 1 or 0})
end)
do
store:releaseStorePurchaseLock(playerId)
end
if ____try and ____hasReturned then
return ____returnValue
end
end
end
)
end)
if not ____try then
____hasReturned, ____returnValue = ____catch()
end
if ____hasReturned then
return ____returnValue
end
end
end
)
end
local function handleFlipArcadeCard(self, playerId, slotIndexRaw)
clearExpiredSession(nil, playerId)
local session = sessionsByPlayerId:get(playerId)
if not session then
sendArcadeEvent(nil, playerId, "store_arcade_flip_result", {success = 0, message = "Нет активного пака"})
return
end
local slotIndex = math.floor(__TS__Number(slotIndexRaw))
if not __TS__NumberIsFinite(slotIndex) or slotIndex < 0 or slotIndex >= #session.flipPayloads then
sendArcadeEvent(nil, playerId, "store_arcade_flip_result", {success = 0, message = "Некорректная карта"})
return
end
if session.revealed[slotIndex + 1] then
sendArcadeEvent(nil, playerId, "store_arcade_flip_result", {success = 0, message = "Карта уже открыта"})
return
end
session.revealed[slotIndex + 1] = true
local flipPayload = session.flipPayloads[slotIndex + 1]
sendArcadeEvent(
nil,
playerId,
"store_arcade_flip_result",
__TS__ObjectAssign({success = 1}, flipPayload)
)
local allRevealed = __TS__ArrayEvery(
session.revealed,
function(____, v) return v == true end
)
if allRevealed then
sessionsByPlayerId:delete(playerId)
sendArcadeEvent(nil, playerId, "store_arcade_pack_complete", {pack_id = session.packId})
end
end
local function setupStoreArcadePackListeners(self)
CustomGameEventManager:RegisterListener(
"store_buy_arcade_pack",
function(_source, event)
local playerId = event.PlayerID
local packId = tostring(event.pack_id or event.item_id or "")
local selectedCurrency = event.selected_currency
handleBuyArcadePack(nil, playerId, packId, selectedCurrency)
end
)
CustomGameEventManager:RegisterListener(
"store_arcade_flip_card",
function(_source, event)
local playerId = event.PlayerID
local ____event_slot_index_29 = event.slot_index
if ____event_slot_index_29 == nil then
____event_slot_index_29 = event.slotIndex
end
local ____event_slot_index_29_30 = ____event_slot_index_29
if ____event_slot_index_29_30 == nil then
____event_slot_index_29_30 = -1
end
local slotIndex = __TS__Number(____event_slot_index_29_30)
handleFlipArcadeCard(nil, playerId, slotIndex)
end
)
end
if IsServer() then
setupStoreArcadePackListeners(nil)
end
return ____exports