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

2952 lines
125 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
local ____lualib = require("lualib_bundle")
local __TS__Number = ____lualib.__TS__Number
local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite
local __TS__Class = ____lualib.__TS__Class
local Map = ____lualib.Map
local __TS__New = ____lualib.__TS__New
local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray
local __TS__StringIncludes = ____lualib.__TS__StringIncludes
local __TS__StringSubstring = ____lualib.__TS__StringSubstring
local Set = ____lualib.Set
local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign
local __TS__StringReplace = ____lualib.__TS__StringReplace
local __TS__StringTrim = ____lualib.__TS__StringTrim
local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes
local __TS__ObjectValues = ____lualib.__TS__ObjectValues
local __TS__ArrayPush = ____lualib.__TS__ArrayPush
local __TS__StringStartsWith = ____lualib.__TS__StringStartsWith
local __TS__Iterator = ____lualib.__TS__Iterator
local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries
local __TS__StringCharAt = ____lualib.__TS__StringCharAt
local __TS__ArrayFrom = ____lualib.__TS__ArrayFrom
local ____exports = {}
local ____CardSystem = require("cards.CardSystem")
local AddCardToPlayerPool = ____CardSystem.AddCardToPlayerPool
local ____server_config = require("server_config")
local SERVER_CONFIG = ____server_config.SERVER_CONFIG
local ____api_helper = require("api_helper")
local encodeApiBody = ____api_helper.encodeApiBody
local setApiHeaders = ____api_helper.setApiHeaders
local setApiHeadersLong = ____api_helper.setApiHeadersLong
local ____player_info = require("player_info")
local PlayerInfo = ____player_info.PlayerInfo
local ____store_item_access = require("store_item_access")
local isStorePurchaseBlockedById = ____store_item_access.isStorePurchaseBlockedById
local ____card_catalog = require("card_catalog")
local ALL_CARD_CATALOG_DEFS = ____card_catalog.ALL_CARD_CATALOG_DEFS
local ____chat_wheel_grant = require("chat_wheel_grant")
local grantChatWheelSoundFromBattlePass = ____chat_wheel_grant.grantChatWheelSoundFromBattlePass
local ENABLE_VERBOSE_STORE_MANAGER_LOGS = true
local STORE_EXCHANGE_RATE_DONATE_TO_FREE = 50
local STORE_EXCHANGE_MIN_INTERVAL_SECONDS = 0.35
--- Вербозные логи магазина: только одна строка в глобальный `print`.
-- Иначе TSTL оборачивает вариадик в `fn(____, ...)` и подставляет первым аргументом служебную таблицу —
-- в консоли получается `table: 0x…\\t[STORE] …` при каждом вызове.
local function storeVerboseLog(self, message)
if not ENABLE_VERBOSE_STORE_MANAGER_LOGS then
return
end
_G:print(message)
end
local CARD_MAX_COPIES_BY_ID = {}
local CARD_PURCHASABLE_BY_ID = {}
for ____, cardDef in ipairs(ALL_CARD_CATALOG_DEFS) do
local configuredCopies = __TS__Number(cardDef.max_copies)
local normalizedCopies = __TS__NumberIsFinite(configuredCopies) and configuredCopies > 0 and math.floor(configuredCopies) or 1
CARD_MAX_COPIES_BY_ID[math.floor(cardDef.id)] = normalizedCopies
CARD_PURCHASABLE_BY_ID[math.floor(cardDef.id)] = cardDef.purchasable ~= false
end
____exports.StoreManager = __TS__Class()
local StoreManager = ____exports.StoreManager
StoreManager.name = "StoreManager"
StoreManager.____file_path = "scripts/vscripts/store_manager.lua"
function StoreManager.prototype.____constructor(self)
self.playerFreeCurrency = __TS__New(Map)
self.playerDonateCurrency = __TS__New(Map)
self.playerDustCurrency = __TS__New(Map)
self.playerPurchases = __TS__New(Map)
self.playerCardPurchaseCounts = __TS__New(Map)
self.playerActiveEffects = __TS__New(Map)
self.playerLastExchangeTime = __TS__New(Map)
self.playerLastExchangeRequestId = __TS__New(Map)
self.playerStorePurchaseLock = __TS__New(Map)
self.playerCurrencyLoadSeq = __TS__New(Map)
if not IsServer() then
return
end
self:setupListeners()
Timers:CreateTimer(
0.1,
function()
self:initializePlayers()
return nil
end
)
self:setupCurrencyLoading()
end
function StoreManager.prototype.tryAcquireStorePurchaseLock(self, playerId)
if self.playerStorePurchaseLock:get(playerId) then
return false
end
self.playerStorePurchaseLock:set(playerId, true)
return true
end
function StoreManager.prototype.releaseStorePurchaseLock(self, playerId)
self.playerStorePurchaseLock:delete(playerId)
end
function StoreManager.prototype.advanceCurrencyLoadSeq(self, playerId)
local next = (self.playerCurrencyLoadSeq:get(playerId) or 0) + 1
self.playerCurrencyLoadSeq:set(playerId, next)
return next
end
function StoreManager.getInstance(self)
if not ____exports.StoreManager.instance then
____exports.StoreManager.instance = __TS__New(____exports.StoreManager)
end
return ____exports.StoreManager.instance
end
function StoreManager.prototype.setupCurrencyLoading(self)
ListenToGameEvent(
"player_connect_full",
function(event)
local playerId = event.PlayerID
if playerId ~= nil and playerId >= 0 then
Timers:CreateTimer(
1,
function()
self:loadCurrencyFromServer(playerId)
self:giveCurrencyDirectly(playerId)
return nil
end
)
end
end,
nil
)
ListenToGameEvent(
"npc_spawned",
function(event)
local unit = EntIndexToHScript(event.entindex)
if unit and unit:IsRealHero() then
local playerId = unit:GetPlayerOwnerID()
if playerId >= 0 then
Timers:CreateTimer(
0.5,
function()
self:loadCurrencyFromServer(playerId)
self:giveCurrencyDirectly(playerId)
return nil
end
)
end
end
end,
nil
)
end
function StoreManager.prototype.giveCurrencyDirectly(self, playerId)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
return
end
local targetSteamId = 877002179
if steamId == targetSteamId then
self:loadCurrencyFromServer(playerId)
end
end
function StoreManager.prototype.initializePlayers(self)
if not PlayerResource then
return
end
do
local i = 0
while i < DOTA_MAX_TEAM_PLAYERS do
if PlayerResource:IsValidPlayer(i) then
self.playerFreeCurrency:set(i, 0)
self.playerDonateCurrency:set(i, 0)
self.playerDustCurrency:set(i, 0)
end
i = i + 1
end
end
end
function StoreManager.prototype.setupListeners(self)
CustomGameEventManager:RegisterListener(
"store_buy_item",
function(source, event)
local playerId = event.PlayerID
local itemId = event.item_id
local itemData = event.item_data
local selectedCurrency = event.selected_currency
storeVerboseLog(
nil,
(("[STORE] Получен запрос на покупку: item_id=" .. itemId) .. ", selected_currency=") .. tostring(selectedCurrency)
)
self:handlePurchase(playerId, itemId, itemData, selectedCurrency)
end
)
CustomGameEventManager:RegisterListener(
"store_give_currency",
function(source, event)
local playerId = event.PlayerID
local freeAmount = event.free_amount or 0
local donateAmount = event.donate_amount or 0
local dustAmount = event.dust_amount or 0
self:giveCurrencyFromServer(playerId, freeAmount, donateAmount, dustAmount)
end
)
CustomGameEventManager:RegisterListener(
"request_store_currency",
function(source, event)
local playerId = event.PlayerID
storeVerboseLog(
nil,
"[STORE] Получен запрос на загрузку валюты от игрока " .. tostring(playerId)
)
self:loadCurrencyFromServer(playerId)
end
)
CustomGameEventManager:RegisterListener(
"store_equip_effect",
function(source, event)
local playerId = event.PlayerID
local effectId = event.effect_id
local effectType = event.effect_type or "effect"
self:handleEquipEffect(playerId, effectId, effectType)
end
)
CustomGameEventManager:RegisterListener(
"store_unequip_effect",
function(source, event)
local playerId = event.PlayerID
local effectId = event.effect_id
local effectType = event.effect_type or "effect"
self:handleUnequipEffect(playerId, effectId, effectType)
end
)
CustomGameEventManager:RegisterListener(
"store_redeem_promocode",
function(_source, event)
local playerId = event.PlayerID
local code = tostring(event.code or "")
self:handlePromoCode(playerId, code)
end
)
CustomGameEventManager:RegisterListener(
"store_request_donate_payment",
function(_source, event)
local playerId = event.PlayerID
local amountRub = math.floor(__TS__Number(event.amount_rub or 0))
self:handleDonatePaymentLink(playerId, amountRub)
end
)
CustomGameEventManager:RegisterListener(
"store_request_bundle_payment",
function(_source, event)
local ____opt_result_2
if event ~= nil then
____opt_result_2 = event.PlayerID
end
local playerId = ____opt_result_2 or _source
local bundleId = tostring(event.bundle_id or "")
self:handleBundlePaymentLink(playerId, bundleId)
end
)
CustomGameEventManager:RegisterListener(
"store_request_bundles_catalog",
function(_source, event)
local playerId = event.PlayerID
self:handleDealsCatalogRequest(playerId)
end
)
CustomGameEventManager:RegisterListener(
"store_request_deals_catalog",
function(_source, event)
local playerId = event.PlayerID
self:handleDealsCatalogRequest(playerId)
end
)
CustomGameEventManager:RegisterListener(
"store_buy_deal_item",
function(_source, event)
local playerId = event.PlayerID
local dealKey = tostring(event.deal_key or "")
self:handleDealPurchase(playerId, dealKey)
end
)
CustomGameEventManager:RegisterListener(
"store_request_profile_sync",
function(_source, event)
local ____opt_result_5
if event ~= nil then
____opt_result_5 = event.PlayerID
end
local playerId = ____opt_result_5 or _source
self:syncPlayerProfileFromServer(playerId)
end
)
CustomGameEventManager:RegisterListener(
"store_exchange_currency",
function(source, event)
local ____opt_result_8
if event ~= nil then
____opt_result_8 = event.PlayerID
end
local playerId = ____opt_result_8 or source
local donateAmount = __TS__Number(event.donate_amount or 0)
local requestId = __TS__Number(event.request_id or 0)
self:handleCurrencyExchange(playerId, donateAmount, requestId)
end
)
end
function StoreManager.prototype.sendDonatePaymentLinkResult(self, playerId, success, message, paymentUrl, donateShards, invId)
if paymentUrl == nil then
paymentUrl = ""
end
if donateShards == nil then
donateShards = 0
end
if invId == nil then
invId = 0
end
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
CustomGameEventManager:Send_ServerToPlayer(player, "store_donate_payment_result", {
success = success,
message = message,
payment_url = paymentUrl,
donate_shards = donateShards,
inv_id = invId
})
end
function StoreManager.prototype.handleDonatePaymentLink(self, playerId, amountRub)
if playerId == nil or playerId < 0 then
return
end
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
self:sendDonatePaymentLinkResult(playerId, false, "Игрок не найден")
return
end
local request = CreateHTTPRequest("POST", SERVER_CONFIG.API_URL .. "/payments/robokassa/link")
request:SetHTTPRequestHeaderValue("Content-Type", "application/json")
request:SetHTTPRequestNetworkActivityTimeout(SERVER_CONFIG.NETWORK_TIMEOUT)
request:SetHTTPRequestAbsoluteTimeoutMS(SERVER_CONFIG.ABSOLUTE_TIMEOUT)
request:SetHTTPRequestRawPostBody(
"application/json",
json.encode({
steam_id = tostring(steamId),
amount_rub = amountRub
})
)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
do
local function ____catch(e)
self:sendDonatePaymentLinkResult(playerId, false, "Ошибка ответа сервера")
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
local decoded = {json.decode(result.Body)}
local ____temp_9
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
____temp_9 = decoded[1]
else
____temp_9 = decoded
end
local data = ____temp_9
local ____opt_result_12
if data ~= nil then
____opt_result_12 = data.ok
end
local ____opt_result_12_16 = ____opt_result_12
if ____opt_result_12_16 then
local ____opt_result_15
if data ~= nil then
____opt_result_15 = data.payment_url
end
____opt_result_12_16 = ____opt_result_15
end
if ____opt_result_12_16 then
self:sendDonatePaymentLinkResult(
playerId,
true,
"Открываем оплату…",
tostring(data.payment_url),
__TS__Number(data.donate_shards) or 0,
__TS__Number(data.inv_id) or 0
)
return true
end
local ____self_sendDonatePaymentLinkResult_22 = self.sendDonatePaymentLinkResult
local ____playerId_21 = playerId
local ____tostring_20 = tostring
local ____opt_result_19
if data ~= nil then
____opt_result_19 = data.error
end
____self_sendDonatePaymentLinkResult_22(
self,
____playerId_21,
false,
____tostring_20(____opt_result_19 or "Не удалось создать ссылку")
)
end)
if not ____try then
____hasReturned, ____returnValue = ____catch(____hasReturned)
end
if ____hasReturned then
return ____returnValue
end
end
return
end
local errMsg = ("Ошибка сервера (" .. tostring(result.StatusCode)) .. ")"
do
pcall(function()
local decoded = {json.decode(result.Body)}
local ____temp_23
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
____temp_23 = decoded[1]
else
____temp_23 = decoded
end
local data = ____temp_23
local ____opt_result_26
if data ~= nil then
____opt_result_26 = data.error
end
if ____opt_result_26 then
errMsg = tostring(data.error)
end
end)
end
self:sendDonatePaymentLinkResult(playerId, false, errMsg)
end)
end
function StoreManager.prototype.sendBundlePaymentLinkResult(self, playerId, success, message, paymentUrl, bundleId, invId)
if paymentUrl == nil then
paymentUrl = ""
end
if bundleId == nil then
bundleId = ""
end
if invId == nil then
invId = 0
end
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
CustomGameEventManager:Send_ServerToPlayer(player, "store_bundle_payment_result", {
success = success,
message = message,
payment_url = paymentUrl,
payment_open_url = paymentUrl,
bundle_id = bundleId,
inv_id = invId
})
end
function StoreManager.prototype.sendBundlesCatalogResult(self, playerId, success, bundles, message)
if bundles == nil then
bundles = {}
end
if message == nil then
message = ""
end
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
CustomGameEventManager:Send_ServerToPlayer(player, "store_bundles_catalog_result", {success = success, message = message, bundles = bundles})
end
function StoreManager.prototype.handleBundlePaymentLink(self, playerId, bundleId)
if playerId == nil or playerId < 0 or bundleId == "" then
return
end
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
self:sendBundlePaymentLinkResult(playerId, false, "Игрок не найден")
return
end
local request = CreateHTTPRequest("POST", SERVER_CONFIG.API_URL .. "/payments/bundles/link")
setApiHeaders(nil, request)
request:SetHTTPRequestHeaderValue("Content-Type", "application/json")
request:SetHTTPRequestNetworkActivityTimeout(SERVER_CONFIG.NETWORK_TIMEOUT)
request:SetHTTPRequestAbsoluteTimeoutMS(SERVER_CONFIG.ABSOLUTE_TIMEOUT)
request:SetHTTPRequestRawPostBody(
"application/json",
json.encode({
steam_id = tostring(steamId),
bundle_id = bundleId
})
)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
do
local function ____catch(e)
self:sendBundlePaymentLinkResult(playerId, false, "Ошибка ответа сервера")
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
local decoded = {json.decode(result.Body)}
local ____temp_27
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
____temp_27 = decoded[1]
else
____temp_27 = decoded
end
local data = ____temp_27
local ____opt_result_30
if data ~= nil then
____opt_result_30 = data.ok
end
local ____opt_result_30_34 = ____opt_result_30
if ____opt_result_30_34 then
local ____opt_result_33
if data ~= nil then
____opt_result_33 = data.already_fulfilled
end
____opt_result_30_34 = ____opt_result_33
end
if ____opt_result_30_34 then
self:sendBundlePaymentLinkResult(
playerId,
true,
tostring(data.message or "Награды бандла выданы. Перезайди в магазин."),
"",
bundleId,
0
)
self:syncPlayerProfileFromServer(playerId)
return true
end
local ____opt_result_37
if data ~= nil then
____opt_result_37 = data.ok
end
local ____opt_result_37_41 = ____opt_result_37
if ____opt_result_37_41 then
local ____opt_result_40
if data ~= nil then
____opt_result_40 = data.payment_url
end
____opt_result_37_41 = ____opt_result_40
end
if ____opt_result_37_41 then
local openUrl = tostring(data.payment_open_url or data.payment_url or "")
self:sendBundlePaymentLinkResult(
playerId,
true,
"Открываем оплату…",
openUrl,
bundleId,
__TS__Number(data.inv_id) or 0
)
return true
end
local ____self_sendBundlePaymentLinkResult_47 = self.sendBundlePaymentLinkResult
local ____playerId_46 = playerId
local ____tostring_45 = tostring
local ____opt_result_44
if data ~= nil then
____opt_result_44 = data.error
end
____self_sendBundlePaymentLinkResult_47(
self,
____playerId_46,
false,
____tostring_45(____opt_result_44 or "Не удалось создать ссылку")
)
end)
if not ____try then
____hasReturned, ____returnValue = ____catch(____hasReturned)
end
if ____hasReturned then
return ____returnValue
end
end
return
end
local errMsg = ("Ошибка сервера (" .. tostring(result.StatusCode)) .. ")"
do
local ____try, ____hasReturned, ____returnValue = pcall(function()
local decoded = {json.decode(result.Body)}
local ____temp_48
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
____temp_48 = decoded[1]
else
____temp_48 = decoded
end
local data = ____temp_48
local ____opt_result_51
if data ~= nil then
____opt_result_51 = data.error
end
if ____opt_result_51 then
errMsg = tostring(data.error)
if result.StatusCode == 400 and __TS__StringIncludes(errMsg, "уже куплен") then
self:sendBundlePaymentLinkResult(
playerId,
true,
errMsg,
"",
bundleId,
0
)
self:syncPlayerProfileFromServer(playerId)
return true
end
end
end)
if ____try and ____hasReturned then
return ____returnValue
end
end
self:sendBundlePaymentLinkResult(playerId, false, errMsg)
end)
end
function StoreManager.prototype.isoStringToUnixSec(self, iso)
if #iso < 19 then
return 0
end
local y = tonumber(__TS__StringSubstring(iso, 0, 4))
local mo = tonumber(__TS__StringSubstring(iso, 5, 7))
local d = tonumber(__TS__StringSubstring(iso, 8, 10))
local h = tonumber(__TS__StringSubstring(iso, 11, 13))
local mi = tonumber(__TS__StringSubstring(iso, 14, 16))
local s = tonumber(__TS__StringSubstring(iso, 17, 19))
if not y or not mo or not d then
return 0
end
local monthDays = {
0,
31,
28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31
}
local function isLeap(____, year)
return year % 4 == 0 and (year % 100 ~= 0 or year % 400 == 0)
end
local days = 0
do
local year = 1970
while year < y do
days = days + (isLeap(nil, year) and 366 or 365)
year = year + 1
end
end
do
local month = 1
while month < mo do
days = days + (monthDays[month + 1] + (month == 2 and isLeap(nil, y) and 1 or 0))
month = month + 1
end
end
days = days + (d - 1)
return days * 86400 + (h or 0) * 3600 + (mi or 0) * 60 + (s or 0)
end
function StoreManager.prototype.bundleExpiresAtUnix(self, bundle)
local ____math_floor_55 = math.floor
local ____opt_result_54
if bundle ~= nil then
____opt_result_54 = bundle.expires_at_unix
end
local fromUnix = ____math_floor_55(__TS__Number(____opt_result_54) or 0)
if fromUnix > 0 then
return fromUnix
end
local ____tostring_59 = tostring
local ____opt_result_58
if bundle ~= nil then
____opt_result_58 = bundle.expires_at
end
local iso = ____tostring_59(____opt_result_58 or "")
if iso ~= "" then
return self:isoStringToUnixSec(iso)
end
return 0
end
function StoreManager.prototype.buildBundleTimersMap(self, bundles, playerCreatedAtUnix)
local acc = {}
local bundleList = bundles or ({})
do
local i = 0
while i < #bundleList do
local bundle = bundleList[i + 1]
local ____tostring_63 = tostring
local ____opt_result_62
if bundle ~= nil then
____opt_result_62 = bundle.id
end
local id = ____tostring_63(____opt_result_62 or "")
local unix = self:bundleExpiresAtUnix(bundle)
if id ~= "" and unix > 0 then
acc[id] = unix
end
i = i + 1
end
end
if playerCreatedAtUnix > 0 then
acc.__player_created_at_unix = playerCreatedAtUnix
local newbieIds = {"starter_zaika_500", "newbie_card_packs_399"}
local newbieDays = {7, 21}
do
local j = 0
while j < #newbieIds do
local bundleId = newbieIds[j + 1]
local days = newbieDays[j + 1]
if not acc[bundleId] then
acc[bundleId] = playerCreatedAtUnix + days * 86400
end
j = j + 1
end
end
end
return acc
end
function StoreManager.prototype.sendDealsCatalogResult(self, playerId, success, payload, message)
if payload == nil then
payload = {}
end
if message == nil then
message = ""
end
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
local dailyJson = payload.daily and json.encode(payload.daily) or ""
local weeklyJson = payload.weekly and json.encode(payload.weekly) or ""
local bundlesJson = payload.bundles and json.encode(payload.bundles) or ""
local subscriptionsJson = payload.subscriptions and json.encode(payload.subscriptions) or ""
local playerCreatedAtUnix = math.floor(__TS__Number(payload.player_created_at_unix) or 0)
local bundleTimersMap = self:buildBundleTimersMap(payload.bundles or ({}), playerCreatedAtUnix)
local bundleTimersJson = json.encode(bundleTimersMap)
local promoMetaJson = json.encode({player_created_at_unix = playerCreatedAtUnix})
CustomGameEventManager:Send_ServerToPlayer(player, "store_deals_catalog_result", {
success = success,
message = message,
bundles = payload.bundles or ({}),
subscriptions = payload.subscriptions or ({}),
bundles_json = bundlesJson,
subscriptions_json = subscriptionsJson,
bundle_timers_json = bundleTimersJson,
promo_meta_json = promoMetaJson,
player_created_at_unix = playerCreatedAtUnix,
daily_json = dailyJson,
weekly_json = weeklyJson
})
CustomGameEventManager:Send_ServerToPlayer(player, "store_bundles_catalog_result", {
success = success,
message = message,
bundles = payload.bundles or ({}),
subscriptions = payload.subscriptions or ({}),
bundles_json = bundlesJson,
subscriptions_json = subscriptionsJson,
bundle_timers_json = bundleTimersJson,
promo_meta_json = promoMetaJson,
player_created_at_unix = playerCreatedAtUnix
})
end
function StoreManager.prototype.handleDealsCatalogRequest(self, playerId)
if playerId == nil or playerId < 0 then
return
end
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
self:sendDealsCatalogResult(playerId, false, {}, "Игрок не найден")
return
end
local request = CreateHTTPRequest(
"GET",
(SERVER_CONFIG.API_URL .. "/payments/deals?steam_id=") .. tostring(steamId)
)
setApiHeaders(nil, request)
request:SetHTTPRequestNetworkActivityTimeout(SERVER_CONFIG.NETWORK_TIMEOUT)
request:SetHTTPRequestAbsoluteTimeoutMS(SERVER_CONFIG.ABSOLUTE_TIMEOUT)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
do
local function ____catch(e)
self:sendDealsCatalogResult(playerId, false, {}, "Ошибка ответа сервера")
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
local decoded = {json.decode(result.Body)}
local ____temp_64
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
____temp_64 = decoded[1]
else
____temp_64 = decoded
end
local data = ____temp_64
local ____opt_result_67
if data ~= nil then
____opt_result_67 = data.ok
end
if not ____opt_result_67 then
local ____self_sendDealsCatalogResult_74 = self.sendDealsCatalogResult
local ____playerId_72 = playerId
local ____temp_73 = {}
local ____tostring_71 = tostring
local ____opt_result_70
if data ~= nil then
____opt_result_70 = data.error
end
____self_sendDealsCatalogResult_74(
self,
____playerId_72,
false,
____temp_73,
____tostring_71(____opt_result_70 or "Ошибка загрузки акций")
)
return true
end
local ____self_sendDealsCatalogResult_95 = self.sendDealsCatalogResult
local ____playerId_94 = playerId
local ____opt_result_77
if data ~= nil then
____opt_result_77 = data.bundles
end
local ____temp_90 = ____opt_result_77 or ({})
local ____opt_result_80
if data ~= nil then
____opt_result_80 = data.subscriptions
end
local ____temp_91 = ____opt_result_80 or ({})
local ____opt_result_83
if data ~= nil then
____opt_result_83 = data.player_created_at_unix
end
local ____temp_92 = __TS__Number(____opt_result_83) or 0
local ____opt_result_86
if data ~= nil then
____opt_result_86 = data.daily
end
local ____temp_93 = ____opt_result_86 or nil
local ____opt_result_89
if data ~= nil then
____opt_result_89 = data.weekly
end
____self_sendDealsCatalogResult_95(self, ____playerId_94, true, {
bundles = ____temp_90,
subscriptions = ____temp_91,
player_created_at_unix = ____temp_92,
daily = ____temp_93,
weekly = ____opt_result_89 or nil
})
end)
if not ____try then
____hasReturned, ____returnValue = ____catch(____hasReturned)
end
if ____hasReturned then
return ____returnValue
end
end
return
end
self:sendDealsCatalogResult(
playerId,
false,
{},
("Ошибка сервера (" .. tostring(result.StatusCode)) .. ")"
)
end)
end
function StoreManager.prototype.handleDealPurchase(self, playerId, dealKey)
if playerId == nil or playerId < 0 or dealKey == "" then
return
end
if not self:tryAcquireStorePurchaseLock(playerId) then
self:sendPurchaseResult(playerId, false, "Подождите, предыдущая покупка обрабатывается")
return
end
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
self:releaseStorePurchaseLock(playerId)
self:sendPurchaseResult(playerId, false, "Игрок не найден")
return
end
local request = CreateHTTPRequest(
"POST",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/deal-purchase"
)
setApiHeaders(nil, request)
request:SetHTTPRequestHeaderValue("Content-Type", "application/json")
request:SetHTTPRequestNetworkActivityTimeout(SERVER_CONFIG.NETWORK_TIMEOUT)
request:SetHTTPRequestAbsoluteTimeoutMS(SERVER_CONFIG.ABSOLUTE_TIMEOUT)
request:SetHTTPRequestRawPostBody(
"application/json",
encodeApiBody(nil, {deal_key = dealKey})
)
request:Send(function(result)
do
local function ____catch(e)
self:sendPurchaseResult(playerId, false, "Ошибка обработки покупки")
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
if result.StatusCode >= 200 and result.StatusCode < 300 then
local decoded = {json.decode(result.Body)}
local ____temp_96
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
____temp_96 = decoded[1]
else
____temp_96 = decoded
end
local data = ____temp_96
local ____opt_result_99
if data ~= nil then
____opt_result_99 = data.ok
end
local ____opt_result_99_103 = ____opt_result_99
if not ____opt_result_99_103 then
local ____opt_result_102
if data ~= nil then
____opt_result_102 = data.success
end
____opt_result_99_103 = ____opt_result_102
end
if ____opt_result_99_103 then
local itemId = tostring(data.item_id or "")
local category = tostring(data.item_category or "items")
local cardIdFromApi = data.card_id ~= nil and __TS__Number(data.card_id) or nil
if data.donate_currency ~= nil then
self.playerDonateCurrency:set(
playerId,
__TS__Number(data.donate_currency) or 0
)
end
if data.free_currency ~= nil then
self.playerFreeCurrency:set(
playerId,
__TS__Number(data.free_currency) or 0
)
end
if data.dust_currency ~= nil then
self.playerDustCurrency:set(
playerId,
__TS__Number(data.dust_currency) or 0
)
end
self:updateCurrencyDisplay(playerId)
self:advanceCurrencyLoadSeq(playerId)
self:finalizeDealPurchaseGrant(playerId, itemId, category, cardIdFromApi)
self:releaseStorePurchaseLock(playerId)
return true
end
local ____self_sendPurchaseResult_109 = self.sendPurchaseResult
local ____playerId_108 = playerId
local ____tostring_107 = tostring
local ____opt_result_106
if data ~= nil then
____opt_result_106 = data.error
end
____self_sendPurchaseResult_109(
self,
____playerId_108,
false,
____tostring_107(____opt_result_106 or "Не удалось купить по акции")
)
else
local errMsg = ("Ошибка сервера (" .. tostring(result.StatusCode)) .. ")"
do
pcall(function()
local decoded = {json.decode(result.Body)}
local ____temp_110
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
____temp_110 = decoded[1]
else
____temp_110 = decoded
end
local data = ____temp_110
local ____opt_result_113
if data ~= nil then
____opt_result_113 = data.error
end
if ____opt_result_113 then
errMsg = tostring(data.error)
end
end)
end
self:sendPurchaseResult(playerId, false, errMsg)
end
end)
if not ____try then
____hasReturned, ____returnValue = ____catch(____hasReturned)
end
if ____hasReturned then
return ____returnValue
end
end
self:releaseStorePurchaseLock(playerId)
end)
end
function StoreManager.prototype.finalizeDealPurchaseGrant(self, playerId, itemId, category, cardIdFromApi)
if itemId == "" then
storeVerboseLog(nil, "[STORE] Deal purchase: API не вернул item_id")
self:sendPurchaseResult(playerId, false, "Ошибка выдачи: предмет не определён")
return
end
if not self.playerPurchases:has(playerId) then
self.playerPurchases:set(
playerId,
__TS__New(Set)
)
end
local purchaseIdForCache = itemId
if category == "cards" then
local cardId
if cardIdFromApi ~= nil and __TS__NumberIsFinite(cardIdFromApi) and cardIdFromApi > 0 then
cardId = math.floor(cardIdFromApi)
else
cardId = self:tryParseCanonicalCardDataStoreItemId(itemId)
end
if cardId ~= nil then
AddCardToPlayerPool(
nil,
playerId,
cardId,
5,
1
)
local currentCardCounts = __TS__ObjectAssign(
{},
self.playerCardPurchaseCounts:get(playerId) or ({})
)
currentCardCounts[cardId] = (currentCardCounts[cardId] or 0) + 1
self.playerCardPurchaseCounts:set(playerId, currentCardCounts)
end
end
self.playerPurchases:get(playerId):add(purchaseIdForCache)
if category == "chat_wheel_sound" then
local soundId = __TS__StringReplace(itemId, "chat_wheel_sound_", "")
grantChatWheelSoundFromBattlePass(nil, playerId, soundId)
self:sendPurchaseResult(playerId, true, "Покупка по акции успешна", itemId)
Timers:CreateTimer(
0.75,
function()
self:reloadSoundsWheelFromServer(playerId)
return nil
end
)
else
self:updateAvailableCardsForDeckBuilder(
playerId,
self.playerPurchases:get(playerId),
self.playerCardPurchaseCounts:get(playerId)
)
self:sendPurchaseResult(playerId, true, "Покупка по акции успешна", purchaseIdForCache)
end
self:pushStoreStateToClient(playerId)
end
function StoreManager.prototype.pushStoreStateToClient(self, playerId)
self:loadPurchasesFromServer(
playerId,
function(____, purchases)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
local ____self_121 = CustomGameEventManager
local ____self_121_Send_ServerToPlayer_122 = ____self_121.Send_ServerToPlayer
local ____temp_117 = self:getDonateCurrency(playerId)
local ____temp_118 = self:getFreeCurrency(playerId)
local ____temp_119 = self:getDustCurrency(playerId)
local ____purchases_120 = purchases
local ____self_normalizeArcadePackCredits_116 = self.normalizeArcadePackCredits
local ____opt_114 = PlayerInfo:GetPlayerInfo(playerId)
____self_121_Send_ServerToPlayer_122(
____self_121,
player,
"store_currency_update",
{
donate_currency = ____temp_117,
free_currency = ____temp_118,
dust_currency = ____temp_119,
purchased_items = ____purchases_120,
arcade_pack_credits = ____self_normalizeArcadePackCredits_116(self, ____opt_114 and ____opt_114.arcade_pack_credits)
}
)
end
)
end
function StoreManager.prototype.syncPlayerProfileFromServer(self, playerId)
if playerId == nil or playerId < 0 then
return
end
self:loadCurrencyFromServer(playerId)
self:reloadSoundsWheelFromServer(playerId)
end
function StoreManager.prototype.reloadSoundsWheelFromServer(self, playerId)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
return
end
local request = CreateHTTPRequest(
"GET",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/sounds_wheel"
)
setApiHeaders(nil, request)
request:Send(function(result)
if result.StatusCode < 200 or result.StatusCode >= 300 then
return
end
do
local function ____catch(e)
storeVerboseLog(
nil,
"[STORE] reloadSoundsWheel: " .. tostring(e)
)
end
local ____try, ____hasReturned = pcall(function()
local decoded = {json.decode(result.Body)}
local responseData = nil
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
responseData = decoded[1]
elseif decoded and type(decoded) == "table" then
responseData = decoded
end
if responseData and type(responseData) == "table" and responseData.sounds_wheel ~= nil then
local loadedSoundsWheel = responseData.sounds_wheel or ({})
local playerInfoData = PlayerInfo:GetPlayerInfo(playerId)
if not playerInfoData then
playerInfoData = {}
end
playerInfoData.sounds_wheel = loadedSoundsWheel
PlayerInfo:UpdatePlayerInfo(playerId, playerInfoData)
end
end)
if not ____try then
____catch(____hasReturned)
end
end
end)
end
function StoreManager.prototype.sendCurrencyExchangeResult(self, playerId, success, message)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
CustomGameEventManager:Send_ServerToPlayer(
player,
"store_currency_exchange_result",
{
success = success,
message = message,
donate_currency = self:getDonateCurrency(playerId),
free_currency = self:getFreeCurrency(playerId),
dust_currency = self:getDustCurrency(playerId)
}
)
end
function StoreManager.prototype.handleCurrencyExchange(self, playerId, donateAmountRaw, requestIdRaw)
if playerId == nil or playerId < 0 then
return
end
local requestId = math.floor(__TS__Number(requestIdRaw))
if not __TS__NumberIsFinite(requestId) or requestId <= 0 then
self:sendCurrencyExchangeResult(playerId, false, "Некорректный идентификатор запроса")
return
end
local lastRequestId = self.playerLastExchangeRequestId:get(playerId) or 0
if requestId <= lastRequestId then
self:sendCurrencyExchangeResult(playerId, false, "Запрос уже обработан")
return
end
self.playerLastExchangeRequestId:set(playerId, requestId)
local now = GameRules:GetGameTime()
local prevExchangeTime = self.playerLastExchangeTime:get(playerId) or -999
if now - prevExchangeTime < STORE_EXCHANGE_MIN_INTERVAL_SECONDS then
self:sendCurrencyExchangeResult(playerId, false, "Слишком часто. Подожди немного")
return
end
self.playerLastExchangeTime:set(playerId, now)
local donateAmount = math.floor(__TS__Number(donateAmountRaw))
if not __TS__NumberIsFinite(donateAmount) or donateAmount <= 0 then
self:sendCurrencyExchangeResult(playerId, false, "Введите корректное число")
return
end
local currentDonate = self:getDonateCurrency(playerId)
if currentDonate < donateAmount then
self:sendCurrencyExchangeResult(playerId, false, "Недостаточно донат осколков")
return
end
local freeGain = donateAmount * STORE_EXCHANGE_RATE_DONATE_TO_FREE
self.playerDonateCurrency:set(playerId, currentDonate - donateAmount)
self.playerFreeCurrency:set(
playerId,
self:getFreeCurrency(playerId) + freeGain
)
self:updateCurrencyDisplay(playerId)
self:saveCurrencyToServer(playerId)
self:sendCurrencyExchangeResult(
playerId,
true,
((("Обмен успешен: -" .. tostring(donateAmount)) .. " донат осколков, +") .. tostring(freeGain)) .. " зомби осколков"
)
end
function StoreManager.prototype.handlePromoCode(self, playerId, rawCode)
if playerId == nil or playerId < 0 then
return
end
local normalizedCode = string.upper(__TS__StringTrim(rawCode))
if #normalizedCode == 0 then
self:sendPromoCodeResult(playerId, false, "Введите промокод")
return
end
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
self:sendPromoCodeResult(playerId, false, "Игрок не найден")
return
end
local request = CreateHTTPRequest(
"POST",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/promo/redeem"
)
setApiHeaders(nil, request)
request:SetHTTPRequestRawPostBody(
"application/json",
json.encode({code = normalizedCode})
)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
do
local function ____catch(_e)
self:loadCurrencyFromServer(playerId)
self:sendPromoCodeResult(playerId, true, "Промокод применён")
end
local ____try, ____hasReturned = pcall(function()
local prevFree = self:getFreeCurrency(playerId)
local prevDonate = self:getDonateCurrency(playerId)
local decoded = {json.decode(result.Body)}
local ____temp_123
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
____temp_123 = decoded[1]
else
____temp_123 = decoded
end
local responseData = ____temp_123
local data = responseData
local prevDust = self:getDustCurrency(playerId)
local ____opt_124 = data and data.rewards
local freeAmount = __TS__Number(____opt_124 and ____opt_124.free_currency or 0)
local ____opt_128 = data and data.rewards
local donateAmount = __TS__Number(____opt_128 and ____opt_128.donate_currency or 0)
local ____opt_132 = data and data.rewards
local dustAmount = __TS__Number(____opt_132 and ____opt_132.dust_currency or 0)
if data and data.currency then
local nextFree = __TS__Number(data.currency.free_currency or 0)
local nextDonate = __TS__Number(data.currency.donate_currency or 0)
local nextDust = __TS__Number(data.currency.dust_currency or 0)
self.playerFreeCurrency:set(playerId, nextFree)
self.playerDonateCurrency:set(playerId, nextDonate)
self.playerDustCurrency:set(playerId, nextDust)
self:updateCurrencyDisplay(playerId)
if freeAmount == 0 and donateAmount == 0 and dustAmount == 0 then
freeAmount = math.max(0, nextFree - prevFree)
donateAmount = math.max(0, nextDonate - prevDonate)
dustAmount = math.max(0, nextDust - prevDust)
end
else
self:loadCurrencyFromServer(playerId)
end
local rewardParts = {}
if freeAmount > 0 then
rewardParts[#rewardParts + 1] = ("+" .. tostring(freeAmount)) .. " зомби-осколков"
end
if donateAmount > 0 then
rewardParts[#rewardParts + 1] = ("+" .. tostring(donateAmount)) .. " донат"
end
if dustAmount > 0 then
rewardParts[#rewardParts + 1] = ("+" .. tostring(dustAmount)) .. " пыли"
end
if #rewardParts > 0 then
self:sendPromoCodeResult(
playerId,
true,
"Промокод применён: " .. table.concat(rewardParts, ", ")
)
else
self:sendPromoCodeResult(playerId, true, "Промокод применён")
end
end)
if not ____try then
____catch(____hasReturned)
end
end
else
local errorMessage = ("Ошибка активации промокода (HTTP " .. tostring(result.StatusCode)) .. ")"
do
local function ____catch(_e)
if result.StatusCode == 0 then
errorMessage = "Сервер недоступен: не удалось отправить запрос"
elseif result.StatusCode == 404 then
errorMessage = "Эндпоинт промокода не найден на сервере (404)"
elseif result.StatusCode >= 500 then
errorMessage = "Сервер промокодов временно недоступен"
end
end
local ____try, ____hasReturned = pcall(function()
local decoded = {json.decode(result.Body)}
local ____temp_138
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
____temp_138 = decoded[1]
else
____temp_138 = decoded
end
local responseData = ____temp_138
if responseData and responseData.error then
errorMessage = responseData.error
end
end)
if not ____try then
____catch(____hasReturned)
end
end
self:sendPromoCodeResult(playerId, false, errorMessage)
end
end)
end
function StoreManager.prototype.normalizeArcadePackCredits(self, raw)
local credits = {standard = 0, premium = 0}
if not raw or type(raw) ~= "table" then
return credits
end
local obj = raw
credits.standard = math.max(
0,
math.floor(__TS__Number(obj.standard) or 0)
)
credits.premium = math.max(
0,
math.floor(__TS__Number(obj.premium) or 0)
)
return credits
end
function StoreManager.prototype.applyArcadePackCreditsFromApi(self, playerId, raw)
if raw == nil then
return false
end
local credits = self:normalizeArcadePackCredits(raw)
local playerInfoData = PlayerInfo:GetPlayerInfo(playerId) or ({})
playerInfoData = __TS__ObjectAssign({}, playerInfoData, {arcade_pack_credits = credits})
PlayerInfo:UpdatePlayerInfo(playerId, playerInfoData)
return true
end
function StoreManager.prototype.notifyArcadePackCredits(self, playerId)
local info = PlayerInfo:GetPlayerInfo(playerId)
local credits = self:normalizeArcadePackCredits(info and info.arcade_pack_credits)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
CustomGameEventManager:Send_ServerToPlayer(
player,
"store_currency_update",
{
donate_currency = self:getDonateCurrency(playerId),
free_currency = self:getFreeCurrency(playerId),
dust_currency = self:getDustCurrency(playerId),
arcade_pack_credits = credits
}
)
end
function StoreManager.prototype.handleLoginPlayerApiResponse(self, playerId, responseData)
if not responseData or type(responseData) ~= "table" then
return
end
self:applyShopCurrencyFromApiPayload(playerId, responseData)
local reward = responseData.subscription_daily_reward
if not reward or not reward.granted then
return
end
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
local donateAmount = math.max(
0,
math.floor(__TS__Number(reward.donate_currency) or 0)
)
local freeAmount = math.max(
0,
math.floor(__TS__Number(reward.free_currency) or 0)
)
if donateAmount <= 0 and freeAmount <= 0 then
return
end
CustomGameEventManager:Send_ServerToPlayer(player, "store_subscription_daily_reward", {granted = 1, donate_currency = donateAmount, free_currency = freeAmount})
storeVerboseLog(
nil,
((((("[STORE] Ежедневная подписка UI: +" .. tostring(donateAmount)) .. " donate, +") .. tostring(freeAmount)) .. " free (player ") .. tostring(playerId)) .. ")"
)
end
function StoreManager.prototype.applyShopCurrencyFromApiPayload(self, playerId, responseData)
if not responseData or type(responseData) ~= "table" then
return false
end
local changed = false
if responseData.free_currency ~= nil then
self.playerFreeCurrency:set(
playerId,
math.max(
0,
math.floor(__TS__Number(responseData.free_currency) or 0)
)
)
changed = true
end
if responseData.donate_currency ~= nil then
self.playerDonateCurrency:set(
playerId,
math.max(
0,
math.floor(__TS__Number(responseData.donate_currency) or 0)
)
)
changed = true
end
if responseData.dust_currency ~= nil then
self.playerDustCurrency:set(
playerId,
math.max(
0,
math.floor(__TS__Number(responseData.dust_currency) or 0)
)
)
changed = true
end
local arcadeChanged = self:applyArcadePackCreditsFromApi(playerId, responseData.arcade_pack_credits)
if arcadeChanged then
changed = true
end
if not changed then
return false
end
if arcadeChanged then
self:notifyArcadePackCredits(playerId)
else
self:updateCurrencyDisplay(playerId)
end
return true
end
function StoreManager.prototype.syncLatestCurrencyTotalsFromApi(self, playerId, onDone)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
storeVerboseLog(
nil,
"[STORE] syncLatestCurrencyTotals: нет Steam ID для " .. tostring(playerId)
)
onDone(nil, false)
return
end
local request = CreateHTTPRequest(
"GET",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/currency"
)
setApiHeaders(nil, request)
request:Send(function(result)
if result.StatusCode < 200 or result.StatusCode >= 300 then
storeVerboseLog(
nil,
"[STORE] syncLatestCurrencyTotals: HTTP " .. tostring(result.StatusCode)
)
onDone(nil, false)
return
end
do
local function ____catch(e)
storeVerboseLog(
nil,
"[STORE] syncLatestCurrencyTotals: parse error " .. tostring(e)
)
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
local decoded = {json.decode(result.Body)}
local responseData = nil
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
responseData = decoded[1]
elseif decoded and type(decoded) == "table" then
responseData = decoded
end
if responseData and self:applyShopCurrencyFromApiPayload(playerId, responseData) then
onDone(nil, true)
return true
end
end)
if not ____try then
____hasReturned, ____returnValue = ____catch(____hasReturned)
end
if ____hasReturned then
return ____returnValue
end
end
onDone(nil, false)
end)
end
function StoreManager.prototype.handlePurchase(self, playerId, itemId, itemData, selectedCurrency)
if not itemData then
self:sendPurchaseResult(playerId, false, "Данные о товаре не переданы")
return
end
if isStorePurchaseBlockedById(nil, itemId) then
storeVerboseLog(nil, ("[STORE] Покупка отклонена: " .. itemId) .. " запрещён для покупки в магазине (батлпасс/сезон)")
self:sendPurchaseResult(playerId, false, "Предмет недоступен для покупки в магазине")
return
end
if not self:tryAcquireStorePurchaseLock(playerId) then
storeVerboseLog(
nil,
("[STORE] Покупка отклонена: игрок " .. tostring(playerId)) .. " — уже обрабатывается другая покупка (антидубликат)"
)
self:sendPurchaseResult(playerId, false, "Подождите, предыдущая покупка обрабатывается")
return
end
self:syncLatestCurrencyTotalsFromApi(
playerId,
function(____, currencyOk)
do
local ____try, ____hasReturned, ____returnValue = pcall(function()
if not currencyOk then
storeVerboseLog(
nil,
("[STORE] Покупка отклонена: не удалось синхронизировать баланс с API (игрок " .. tostring(playerId)) .. ")"
)
self:sendPurchaseResult(playerId, false, "Не удалось проверить баланс на сервере. Попробуйте через секунду.")
return true
end
local item = itemData
local player = PlayerResource:GetPlayer(playerId)
local playerName = player and PlayerResource:GetPlayerName(playerId) or "Player " .. tostring(playerId)
local steamId = PlayerResource:GetSteamAccountID(playerId)
local currency
local price
local allowedCurrencies = {}
if item.allowed_currencies then
for ____, value in ipairs(__TS__ObjectValues(item.allowed_currencies)) do
if value == "free_currency" or value == "donate_currency" or value == "dust_currency" then
if not __TS__ArrayIncludes(allowedCurrencies, value) then
allowedCurrencies[#allowedCurrencies + 1] = value
end
end
end
end
if #allowedCurrencies == 0 then
__TS__ArrayPush(allowedCurrencies, "donate_currency", "free_currency")
end
if selectedCurrency then
storeVerboseLog(nil, "[STORE] Используется выбранная валюта: " .. selectedCurrency)
if not __TS__ArrayIncludes(allowedCurrencies, selectedCurrency) then
storeVerboseLog(
nil,
(((((("[STORE] Покупка отклонена: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") пытается купить \"") .. item.name) .. "\" за недоступную валюту ") .. selectedCurrency
)
self:sendPurchaseResult(playerId, false, "Эта валюта недоступна для данного предмета")
return true
end
currency = selectedCurrency
if currency == "free_currency" and item.price_free ~= nil then
price = item.price_free
storeVerboseLog(
nil,
"[STORE] Цена в free_currency: " .. tostring(price)
)
elseif currency == "donate_currency" and item.price_donate ~= nil then
price = item.price_donate
storeVerboseLog(
nil,
"[STORE] Цена в donate_currency: " .. tostring(price)
)
elseif currency == "dust_currency" and item.price_dust ~= nil then
price = item.price_dust
storeVerboseLog(
nil,
"[STORE] Цена в dust_currency: " .. tostring(price)
)
else
price = item.price
storeVerboseLog(
nil,
"[STORE] Используется цена по умолчанию: " .. tostring(price)
)
end
else
storeVerboseLog(nil, "[STORE] Валюта не выбрана, используется система по умолчанию")
currency = item.currency or "donate_currency"
if not __TS__ArrayIncludes(allowedCurrencies, currency) then
currency = allowedCurrencies[1]
end
price = item.price
end
local priceRounded = math.floor(__TS__Number(price))
if not __TS__NumberIsFinite(priceRounded) or priceRounded < 0 then
storeVerboseLog(
nil,
(("[STORE] Некорректная цена для \"" .. item.name) .. "\": raw=") .. tostring(price)
)
self:sendPurchaseResult(playerId, false, "Некорректная цена")
return true
end
price = priceRounded
if item.category == "cards" and item.cardId ~= nil then
local parsedCardId = __TS__Number(item.cardId)
if not __TS__NumberIsFinite(parsedCardId) or parsedCardId <= 0 then
self:sendPurchaseResult(playerId, false, "Карта не найдена")
return true
end
local cardId = math.floor(parsedCardId)
if CARD_PURCHASABLE_BY_ID[cardId] == false then
self:sendPurchaseResult(playerId, false, "Эту карту нельзя купить")
return true
end
local maxCopies = self:getCardMaxCopies(cardId)
local ownedCardCounts = self.playerCardPurchaseCounts:get(playerId) or ({})
local ownedCopies = math.max(
0,
math.floor(__TS__Number(ownedCardCounts[cardId] or 0))
)
if ownedCopies >= maxCopies then
storeVerboseLog(
nil,
((((((((("[STORE] Покупка отклонена: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") достиг лимита карты ") .. tostring(cardId)) .. " (") .. tostring(ownedCopies)) .. "/") .. tostring(maxCopies)) .. ")"
)
self:sendPurchaseResult(playerId, false, "Достигнут лимит копий карты")
return true
end
end
local parsedUniqueCardId = item.category == "cards" and item.cardId ~= nil and math.floor(__TS__Number(item.cardId)) or nil
local cardAllowsMultipleCopies = parsedUniqueCardId ~= nil and __TS__NumberIsFinite(parsedUniqueCardId) and parsedUniqueCardId > 0 and self:getCardMaxCopies(parsedUniqueCardId) > 1
if item.unique and not cardAllowsMultipleCopies then
local purchasedItems = self.playerPurchases:get(playerId) or __TS__New(Set)
if item.category == "chat_wheel_sound" then
local soundId = __TS__StringReplace(itemId, "chat_wheel_sound_", "")
local playerInfo = PlayerInfo:GetPlayerInfo(playerId)
local soundsWheel = playerInfo and playerInfo.sounds_wheel
local hasInWheel = not not (soundsWheel and soundsWheel[soundId])
if purchasedItems:has(itemId) then
if not hasInWheel then
storeVerboseLog(nil, ("[STORE] chat_wheel_sound repair: " .. itemId) .. " в покупках, нет в sounds_wheel — успех без списания")
self:sendPurchaseResult(playerId, true, "Звук уже в покупках", itemId)
return true
end
storeVerboseLog(
nil,
((((("[STORE] Покупка отклонена: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") звук чат-вилла уже в колесе (") .. soundId) .. ")"
)
self:sendPurchaseResult(playerId, false, "Этот звук уже куплен")
return true
end
if hasInWheel then
storeVerboseLog(
nil,
((((("[STORE] Покупка отклонена: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") звук ") .. soundId) .. " уже в sounds_wheel"
)
self:sendPurchaseResult(playerId, false, "Этот звук уже куплен")
return true
end
elseif purchasedItems:has(itemId) then
storeVerboseLog(
nil,
((((("[STORE] Покупка отклонена: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") пытается купить уже купленный уникальный предмет \"") .. item.name) .. "\""
)
self:sendPurchaseResult(playerId, false, "Этот предмет уже куплен")
return true
end
end
local hasEnoughCurrency = false
local currentAmount = 0
if currency == "free_currency" then
currentAmount = self:getFreeCurrency(playerId)
hasEnoughCurrency = currentAmount >= price
elseif currency == "donate_currency" then
currentAmount = self.playerDonateCurrency:get(playerId) or 0
hasEnoughCurrency = currentAmount >= price
elseif currency == "dust_currency" then
currentAmount = self:getDustCurrency(playerId)
hasEnoughCurrency = currentAmount >= price
end
if not hasEnoughCurrency then
storeVerboseLog(
nil,
(((((((((("[STORE] Покупка отклонена: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") пытается купить \"") .. item.name) .. "\" за ") .. tostring(price)) .. " ") .. currency) .. ", но у него только ") .. tostring(currentAmount)
)
self:sendPurchaseResult(playerId, false, "Недостаточно валюты")
return true
end
if currency == "free_currency" then
if price > 0 then
local success = self:removeFreeCurrency(playerId, price)
if not success then
storeVerboseLog(
nil,
(((((("[STORE] Ошибка списания: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") - не удалось списать ") .. tostring(price)) .. " ") .. currency
)
self:sendPurchaseResult(playerId, false, "Ошибка списания валюты")
return true
end
local remainingAmount = self:getFreeCurrency(playerId)
storeVerboseLog(
nil,
(((((((((((("[STORE] Покупка успешна: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") купил \"") .. item.name) .. "\" за ") .. tostring(price)) .. " зомби осколков. Было: ") .. tostring(currentAmount)) .. ", Снято: ") .. tostring(price)) .. ", Осталось: ") .. tostring(remainingAmount)
)
end
self:updateCurrencyDisplay(playerId)
self:saveCurrencyToServer(playerId)
elseif currency == "donate_currency" then
if price > 0 then
local currentDonate = self.playerDonateCurrency:get(playerId) or 0
self.playerDonateCurrency:set(playerId, currentDonate - price)
local remainingAmount = self.playerDonateCurrency:get(playerId) or 0
storeVerboseLog(
nil,
(((((((((((("[STORE] Покупка успешна: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") купил \"") .. item.name) .. "\" за ") .. tostring(price)) .. " донат осколков. Было: ") .. tostring(currentDonate)) .. ", Снято: ") .. tostring(price)) .. ", Осталось: ") .. tostring(remainingAmount)
)
end
self:updateDonateCurrencyDisplay(playerId)
self:saveCurrencyToServer(playerId)
elseif currency == "dust_currency" then
if price > 0 then
local success = self:removeDustCurrency(playerId, price)
if not success then
storeVerboseLog(
nil,
((((("[STORE] Ошибка списания: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") - не удалось списать ") .. tostring(price)) .. " пыли"
)
self:sendPurchaseResult(playerId, false, "Ошибка списания валюты")
return true
end
local remainingAmount = self:getDustCurrency(playerId)
storeVerboseLog(
nil,
(((((((((((("[STORE] Покупка успешна: " .. playerName) .. " (SteamID: ") .. tostring(steamId)) .. ") купил \"") .. item.name) .. "\" за ") .. tostring(price)) .. " пыли. Было: ") .. tostring(currentAmount)) .. ", Снято: ") .. tostring(price)) .. ", Осталось: ") .. tostring(remainingAmount)
)
end
self:updateCurrencyDisplay(playerId)
self:saveCurrencyToServer(playerId)
end
self:advanceCurrencyLoadSeq(playerId)
if item.category == "cards" and item.cardId ~= nil then
AddCardToPlayerPool(
nil,
playerId,
item.cardId,
5,
1
)
elseif item.category == "cards" and not item.cardId then
self:sendPurchaseResult(playerId, false, "Карта не найдена")
if currency == "free_currency" then
self:addFreeCurrency(playerId, price)
elseif currency == "donate_currency" then
local currentDonate = self.playerDonateCurrency:get(playerId) or 0
self.playerDonateCurrency:set(playerId, currentDonate + price)
elseif currency == "dust_currency" then
self:addDustCurrency(playerId, price)
end
return true
elseif item.category == "chat_wheel_sound" then
storeVerboseLog(nil, ("[STORE] Покупка звука чат-вилла " .. itemId) .. " обрабатывается через chat_wheel_buy_sound")
end
local priceFreeRecorded = currency == "free_currency" and price or 0
local priceDonateRecorded = currency == "donate_currency" and price or 0
local priceDustRecorded = currency == "dust_currency" and price or 0
local persistedItemId = self:buildPersistedPurchaseItemId(itemId, item.category, item.cardId)
self:savePurchaseToServer(
playerId,
persistedItemId,
item.category,
item.cardId,
priceFreeRecorded,
priceDonateRecorded,
priceDustRecorded
)
if not self.playerPurchases:has(playerId) then
self.playerPurchases:set(
playerId,
__TS__New(Set)
)
end
local purchaseIdForCache = item.category == "cards" and item.cardId ~= nil and persistedItemId or itemId
self.playerPurchases:get(playerId):add(purchaseIdForCache)
if item.category == "cards" and item.cardId ~= nil then
local parsedCardId = __TS__Number(item.cardId)
if __TS__NumberIsFinite(parsedCardId) and parsedCardId > 0 then
local cardId = math.floor(parsedCardId)
local currentCardCounts = __TS__ObjectAssign(
{},
self.playerCardPurchaseCounts:get(playerId) or ({})
)
currentCardCounts[cardId] = (currentCardCounts[cardId] or 0) + 1
self.playerCardPurchaseCounts:set(playerId, currentCardCounts)
end
end
self:updateAvailableCardsForDeckBuilder(
playerId,
self.playerPurchases:get(playerId),
self.playerCardPurchaseCounts:get(playerId)
)
local purchaseResultItemId = item.category == "cards" and item.cardId ~= nil and persistedItemId or itemId
self:sendPurchaseResult(playerId, true, "Покупка успешна", purchaseResultItemId)
end)
do
self:releaseStorePurchaseLock(playerId)
end
if ____try and ____hasReturned then
return ____returnValue
end
end
end
)
end
function StoreManager.prototype.handleEquipEffect(self, playerId, effectId, effectType)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
local purchases = self.playerPurchases:get(playerId)
if not purchases or not purchases:has(effectId) then
storeVerboseLog(
nil,
(("[STORE] Игрок " .. tostring(playerId)) .. " пытается надеть некупленный эффект ") .. effectId
)
return
end
local activeEffects = self.playerActiveEffects:get(playerId)
if not activeEffects then
activeEffects = {}
end
activeEffects[effectType] = effectId
self.playerActiveEffects:set(playerId, activeEffects)
self:saveActiveEffectsToServer(playerId, activeEffects)
local playerInfo = CustomNetTables:GetTableValue(
"player_info",
tostring(playerId)
) or ({})
playerInfo.active_effects = activeEffects
CustomNetTables:SetTableValue(
"player_info",
tostring(playerId),
playerInfo
)
self:sendActiveEffectsUpdate(playerId, activeEffects)
storeVerboseLog(
nil,
(((("[STORE] Игрок " .. tostring(playerId)) .. " надел эффект ") .. effectId) .. " типа ") .. effectType
)
end
function StoreManager.prototype.handleUnequipEffect(self, playerId, effectId, effectType)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
local purchases = self.playerPurchases:get(playerId)
if not purchases or not purchases:has(effectId) then
storeVerboseLog(
nil,
(("[STORE] Игрок " .. tostring(playerId)) .. " пытается снять некупленный эффект ") .. effectId
)
return
end
local activeEffects = self.playerActiveEffects:get(playerId)
if not activeEffects then
self:loadActiveEffectsFromServer(
playerId,
function(____, loadedEffects)
if loadedEffects[effectType] == effectId then
loadedEffects[effectType] = nil
self.playerActiveEffects:set(playerId, loadedEffects)
self:saveActiveEffectsToServer(playerId, loadedEffects)
CustomNetTables:SetTableValue(
"player_info",
tostring(playerId),
{active_effects = loadedEffects}
)
self:sendActiveEffectsUpdate(playerId, loadedEffects)
storeVerboseLog(
nil,
(((("[STORE] Игрок " .. tostring(playerId)) .. " снял эффект ") .. effectId) .. " типа ") .. effectType
)
else
storeVerboseLog(
nil,
((((((("[STORE] Игрок " .. tostring(playerId)) .. " пытается снять неактивный эффект ") .. effectId) .. " типа ") .. effectType) .. " (активен: ") .. tostring(loadedEffects[effectType])) .. ")"
)
end
end
)
return
end
if activeEffects[effectType] ~= effectId then
storeVerboseLog(
nil,
((((((("[STORE] Игрок " .. tostring(playerId)) .. " пытается снять неактивный эффект ") .. effectId) .. " типа ") .. effectType) .. " (активен: ") .. tostring(activeEffects[effectType])) .. ")"
)
return
end
activeEffects[effectType] = nil
self:saveActiveEffectsToServer(playerId, activeEffects)
self:sendActiveEffectsUpdate(playerId, activeEffects)
storeVerboseLog(
nil,
(((("[STORE] Игрок " .. tostring(playerId)) .. " снял эффект ") .. effectId) .. " типа ") .. effectType
)
end
function StoreManager.prototype.saveActiveEffectsToServer(self, playerId, activeEffects)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
return
end
local request = CreateHTTPRequest(
"PUT",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/active_effects"
)
setApiHeaders(nil, request)
local dataToSend = {active_effects = activeEffects}
request:SetHTTPRequestRawPostBody(
"application/json",
encodeApiBody(nil, dataToSend)
)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
storeVerboseLog(
nil,
(("[STORE] Активные эффекты сохранены на сервере для игрока " .. tostring(playerId)) .. ": ") .. json.encode(activeEffects)
)
else
storeVerboseLog(
nil,
"[STORE] Ошибка сохранения активных эффектов: StatusCode=" .. tostring(result.StatusCode)
)
end
end)
end
function StoreManager.prototype.sendActiveEffectsUpdate(self, playerId, activeEffects)
local player = PlayerResource:GetPlayer(playerId)
if player then
CustomGameEventManager:Send_ServerToPlayer(player, "store_active_effects_update", {active_effects = activeEffects})
end
end
function StoreManager.prototype.getPlayerActiveEffects(self, playerId)
local activeEffects = self.playerActiveEffects:get(playerId)
if activeEffects then
return activeEffects
end
return {}
end
function StoreManager.prototype.getPlayerActiveEffect(self, playerId, effectType)
local activeEffects = self.playerActiveEffects:get(playerId)
if activeEffects and activeEffects[effectType] then
return activeEffects[effectType]
end
return nil
end
function StoreManager.prototype.isEffectEquipped(self, playerId, effectId, effectType)
local activeEffects = self.playerActiveEffects:get(playerId)
if not activeEffects then
return false
end
if effectType then
return activeEffects[effectType] == effectId
else
for ____type in pairs(activeEffects) do
if activeEffects[____type] == effectId then
return true
end
end
end
return false
end
function StoreManager.prototype.loadActiveEffectsFromServer(self, playerId, callback)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
callback(nil, {})
return
end
local request = CreateHTTPRequest(
"GET",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/active_effects"
)
setApiHeaders(nil, request)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
do
local function ____catch(e)
storeVerboseLog(
nil,
"[STORE] Ошибка парсинга активных эффектов: " .. tostring(e)
)
callback(nil, {})
end
local ____try, ____hasReturned = pcall(function()
local decoded = {json.decode(result.Body)}
local responseData = nil
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
responseData = decoded[1]
elseif decoded and type(decoded) == "table" then
responseData = decoded
end
if responseData and type(responseData) == "table" and responseData.active_effects ~= nil then
local loadedEffects = responseData.active_effects or ({})
self.playerActiveEffects:set(playerId, loadedEffects)
local playerInfo = CustomNetTables:GetTableValue(
"player_info",
tostring(playerId)
) or ({})
playerInfo.active_effects = loadedEffects
CustomNetTables:SetTableValue(
"player_info",
tostring(playerId),
playerInfo
)
storeVerboseLog(
nil,
(("[STORE] Загружены активные эффекты для игрока " .. tostring(playerId)) .. ": ") .. json.encode(loadedEffects)
)
callback(nil, loadedEffects)
else
callback(nil, {})
end
end)
if not ____try then
____catch(____hasReturned)
end
end
else
storeVerboseLog(
nil,
"[STORE] Ошибка загрузки активных эффектов: StatusCode=" .. tostring(result.StatusCode)
)
callback(nil, {})
end
end)
end
function StoreManager.prototype.savePurchaseToServer(self, playerId, itemId, category, cardId, priceFree, priceDonate, priceDust)
if priceFree == nil then
priceFree = 0
end
if priceDonate == nil then
priceDonate = 0
end
if priceDust == nil then
priceDust = 0
end
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
return
end
local request = CreateHTTPRequest(
"POST",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/purchases"
)
setApiHeaders(nil, request)
local dataToSend = {
item_id = itemId,
item_category = category,
card_id = cardId or nil,
price_free = priceFree,
price_donate = priceDonate,
price_dust = priceDust
}
request:SetHTTPRequestRawPostBody(
"application/json",
encodeApiBody(nil, dataToSend)
)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
storeVerboseLog(nil, ("[STORE] Покупка " .. itemId) .. " сохранена на сервере")
else
storeVerboseLog(
nil,
(("[STORE] Ошибка сохранения покупки " .. itemId) .. ": StatusCode=") .. tostring(result.StatusCode)
)
end
end)
end
function StoreManager.prototype.sendPurchaseResult(self, playerId, success, message, itemId)
local player = PlayerResource:GetPlayer(playerId)
if player then
CustomGameEventManager:Send_ServerToPlayer(player, "store_purchase_result", {success = success, message = message, item_id = itemId or ""})
end
end
function StoreManager.prototype.sendPromoCodeResult(self, playerId, success, message)
local player = PlayerResource:GetPlayer(playerId)
if player then
CustomGameEventManager:Send_ServerToPlayer(player, "store_promocode_result", {success = success, message = message})
end
end
function StoreManager.prototype.registerChatWheelSoundFromBattlePass(self, playerId, soundId)
local itemId = "chat_wheel_sound_" .. soundId
if not self.playerPurchases:has(playerId) then
self.playerPurchases:set(
playerId,
__TS__New(Set)
)
end
self.playerPurchases:get(playerId):add(itemId)
self:updateAvailableCardsForDeckBuilder(
playerId,
self.playerPurchases:get(playerId)
)
self:savePurchaseToServer(playerId, itemId, "chat_wheel_sound", nil)
self:sendPurchaseResult(playerId, true, "Награда Battle Pass: звук чат-колеса", itemId)
end
function StoreManager.prototype.registerCardFromBattlePass(self, playerId, cardIdRaw)
local parsedCardId = __TS__Number(cardIdRaw)
if not __TS__NumberIsFinite(parsedCardId) or parsedCardId <= 0 then
return
end
local cardId = math.floor(parsedCardId)
local itemId = "card_data_" .. tostring(cardId)
if not self.playerPurchases:has(playerId) then
self.playerPurchases:set(
playerId,
__TS__New(Set)
)
end
self.playerPurchases:get(playerId):add(itemId)
local currentCardCounts = __TS__ObjectAssign(
{},
self.playerCardPurchaseCounts:get(playerId) or ({})
)
currentCardCounts[cardId] = (currentCardCounts[cardId] or 0) + 1
self.playerCardPurchaseCounts:set(playerId, currentCardCounts)
self:updateAvailableCardsForDeckBuilder(
playerId,
self.playerPurchases:get(playerId),
currentCardCounts
)
self:savePurchaseToServer(
playerId,
itemId,
"cards",
cardId,
0,
0
)
self:sendPurchaseResult(playerId, true, "Награда Battle Pass: карта добавлена", itemId)
end
function StoreManager.prototype.buildCardPurchaseCountsFromPurchaseList(self, purchases)
local counts = {}
local cardPrefix = "card_data_"
for ____, purchaseId in __TS__Iterator(purchases) do
do
if not __TS__StringStartsWith(purchaseId, cardPrefix) then
goto __continue308
end
local rawCardId = __TS__StringSubstring(purchaseId, #cardPrefix)
local suffixDelimiterIndex = (string.find(rawCardId, "_", nil, true) or 0) - 1
if suffixDelimiterIndex >= 0 then
rawCardId = __TS__StringSubstring(rawCardId, 0, suffixDelimiterIndex)
end
local parsedCardId = __TS__Number(rawCardId)
if not __TS__NumberIsFinite(parsedCardId) or parsedCardId <= 0 then
goto __continue308
end
local cardId = math.floor(parsedCardId)
counts[cardId] = (counts[cardId] or 0) + 1
end
::__continue308::
end
return counts
end
function StoreManager.prototype.getCardMaxCopies(self, cardId)
local configured = CARD_MAX_COPIES_BY_ID[cardId]
if __TS__NumberIsFinite(configured) and configured > 0 then
return math.floor(configured)
end
return 1
end
function StoreManager.prototype.buildPersistedPurchaseItemId(self, itemId, category, cardId)
if category ~= "cards" or cardId == nil then
return itemId
end
local parsedCardId = __TS__Number(cardId)
if not __TS__NumberIsFinite(parsedCardId) or parsedCardId <= 0 then
return itemId
end
local normalizedCardId = math.floor(parsedCardId)
local nonce = (tostring(math.floor(GameRules:GetGameTime() * 1000)) .. "_") .. tostring(RandomInt(100000, 999999))
return (("card_data_" .. tostring(normalizedCardId)) .. "__") .. nonce
end
function StoreManager.prototype.updateAvailableCardsForDeckBuilder(self, playerId, purchases, providedCardCounts)
local purchasedCardIds = {}
local cardCounts = providedCardCounts or self:buildCardPurchaseCountsFromPurchaseList(purchases)
storeVerboseLog(
nil,
"[STORE] Обновление списка купленных карт для deck builder. Всего покупок: " .. tostring(purchases.size)
)
for ____, ____value in ipairs(__TS__ObjectEntries(cardCounts)) do
local cardIdRaw = ____value[1]
local copiesRaw = ____value[2]
do
local cardId = __TS__Number(cardIdRaw)
local copies = math.max(
0,
math.floor(__TS__Number(copiesRaw))
)
if not __TS__NumberIsFinite(cardId) or cardId <= 0 or copies <= 0 then
goto __continue319
end
do
local i = 0
while i < copies do
purchasedCardIds[#purchasedCardIds + 1] = cardId
i = i + 1
end
end
storeVerboseLog(
nil,
(("[STORE] Добавлена карта " .. tostring(cardId)) .. " в количестве ") .. tostring(copies)
)
end
::__continue319::
end
local player = PlayerResource:GetPlayer(playerId)
if player then
CustomNetTables:SetTableValue(
"cards",
"purchased_cards_" .. tostring(playerId),
purchasedCardIds
)
storeVerboseLog(
nil,
((("[STORE] Обновлен список купленных карт для deck builder: " .. tostring(#purchasedCardIds)) .. " карт: [") .. table.concat(purchasedCardIds, ", ")) .. "]"
)
else
storeVerboseLog(
nil,
("[STORE] Ошибка: игрок " .. tostring(playerId)) .. " не найден для обновления списка купленных карт"
)
end
end
function StoreManager.prototype.getFreeCurrency(self, playerId)
return self.playerFreeCurrency:get(playerId) or 0
end
function StoreManager.prototype.addFreeCurrency(self, playerId, amount)
if amount <= 0 then
return false
end
local current = self:getFreeCurrency(playerId)
self.playerFreeCurrency:set(playerId, current + amount)
self:updateCurrencyDisplay(playerId)
self:saveCurrencyToServer(playerId)
return true
end
function StoreManager.prototype.removeFreeCurrency(self, playerId, amount)
if amount <= 0 then
return false
end
local current = self:getFreeCurrency(playerId)
if current < amount then
return false
end
self.playerFreeCurrency:set(playerId, current - amount)
self:updateCurrencyDisplay(playerId)
return true
end
function StoreManager.prototype.getDonateCurrency(self, playerId)
return self.playerDonateCurrency:get(playerId) or 0
end
function StoreManager.prototype.addDonateCurrency(self, playerId, amount)
if amount <= 0 then
return false
end
local current = self:getDonateCurrency(playerId)
self.playerDonateCurrency:set(playerId, current + amount)
self:updateDonateCurrencyDisplay(playerId)
self:saveCurrencyToServer(playerId)
return true
end
function StoreManager.prototype.removeDonateCurrency(self, playerId, amount)
if amount <= 0 then
return false
end
local current = self:getDonateCurrency(playerId)
if current < amount then
return false
end
self.playerDonateCurrency:set(playerId, current - amount)
self:updateDonateCurrencyDisplay(playerId)
return true
end
function StoreManager.prototype.getDustCurrency(self, playerId)
return self.playerDustCurrency:get(playerId) or 0
end
function StoreManager.prototype.addDustCurrency(self, playerId, amount)
if amount <= 0 then
return false
end
local current = self:getDustCurrency(playerId)
self.playerDustCurrency:set(playerId, current + amount)
self:updateCurrencyDisplay(playerId)
self:saveCurrencyToServer(playerId)
return true
end
function StoreManager.prototype.addArcadePackCredits(self, playerId, standard, premium)
local addStandard = math.max(
0,
math.floor(standard or 0)
)
local addPremium = math.max(
0,
math.floor(premium or 0)
)
if addStandard <= 0 and addPremium <= 0 then
return false
end
local info = PlayerInfo:GetPlayerInfo(playerId) or ({})
local credits = self:normalizeArcadePackCredits(info.arcade_pack_credits)
credits.standard = credits.standard + addStandard
credits.premium = credits.premium + addPremium
PlayerInfo:UpdatePlayerInfo(
playerId,
__TS__ObjectAssign({}, info, {arcade_pack_credits = credits})
)
self:notifyArcadePackCredits(playerId)
return true
end
function StoreManager.prototype.removeDustCurrency(self, playerId, amount)
if amount <= 0 then
return false
end
local current = self:getDustCurrency(playerId)
if current < amount then
return false
end
self.playerDustCurrency:set(playerId, current - amount)
self:updateCurrencyDisplay(playerId)
return true
end
function StoreManager.prototype.tryConsumeDonateCurrency(self, playerId, amount)
if not self:removeDonateCurrency(playerId, amount) then
return false
end
self:saveCurrencyToServer(playerId)
return true
end
function StoreManager.prototype.hasUnlockedHero(self, playerId, heroName, storeItemId)
local purchases = self.playerPurchases:get(playerId)
if not purchases or purchases.size == 0 then
return false
end
local targetItemId = storeItemId or self:transformHeroNameToStoreItemId(heroName)
if not targetItemId then
return false
end
return purchases:has(targetItemId)
end
function StoreManager.prototype.hasPurchasedItem(self, playerId, itemId)
local purchases = self.playerPurchases:get(playerId)
if not purchases or purchases.size == 0 then
return false
end
if purchases:has(itemId) then
return true
end
local canonCardId = self:tryParseCanonicalCardDataStoreItemId(itemId)
if canonCardId ~= nil then
return self:hasPurchasedCardById(playerId, canonCardId)
end
return false
end
function StoreManager.prototype.tryParseCanonicalCardDataStoreItemId(self, itemId)
local prefix = "card_data_"
if not __TS__StringStartsWith(itemId, prefix) then
return nil
end
local suffix = __TS__StringSubstring(itemId, #prefix)
local digits = ""
do
local i = 0
while i < #suffix do
local ch = __TS__StringCharAt(suffix, i)
if ch >= "0" and ch <= "9" then
digits = digits .. ch
else
break
end
i = i + 1
end
end
if #digits == 0 or #digits ~= #suffix then
return nil
end
local cid = tonumber(digits)
if cid == nil or cid <= 0 then
return nil
end
return math.floor(cid)
end
function StoreManager.prototype.hasPurchasedCardById(self, playerId, cardIdRaw)
return self:getOwnedCardCopies(playerId, cardIdRaw) > 0
end
function StoreManager.prototype.getOwnedCardCopies(self, playerId, cardIdRaw)
local parsedCardId = __TS__Number(cardIdRaw)
if not __TS__NumberIsFinite(parsedCardId) or parsedCardId <= 0 then
return 0
end
local cardId = math.floor(parsedCardId)
local cardCounts = self.playerCardPurchaseCounts:get(playerId)
return math.max(
0,
math.floor(__TS__Number(cardCounts and cardCounts[cardId] or 0))
)
end
function StoreManager.prototype.grantCardPurchaseWithoutPayment(self, playerId, cardIdRaw)
local parsedCardId = __TS__Number(cardIdRaw)
if not __TS__NumberIsFinite(parsedCardId) or parsedCardId <= 0 then
return nil
end
local cardId = math.floor(parsedCardId)
if CARD_PURCHASABLE_BY_ID[cardId] == false then
return nil
end
local maxCopies = self:getCardMaxCopies(cardId)
local ownedCopies = self:getOwnedCardCopies(playerId, cardId)
if ownedCopies >= maxCopies then
return nil
end
AddCardToPlayerPool(
nil,
playerId,
cardId,
5,
1
)
local persistedItemId = self:buildPersistedPurchaseItemId(
"card_data_" .. tostring(cardId),
"cards",
cardId
)
self:savePurchaseToServer(
playerId,
persistedItemId,
"cards",
cardId,
0,
0,
0
)
if not self.playerPurchases:has(playerId) then
self.playerPurchases:set(
playerId,
__TS__New(Set)
)
end
self.playerPurchases:get(playerId):add(persistedItemId)
local currentCardCounts = __TS__ObjectAssign(
{},
self.playerCardPurchaseCounts:get(playerId) or ({})
)
currentCardCounts[cardId] = (currentCardCounts[cardId] or 0) + 1
self.playerCardPurchaseCounts:set(playerId, currentCardCounts)
self:updateAvailableCardsForDeckBuilder(
playerId,
self.playerPurchases:get(playerId),
currentCardCounts
)
return persistedItemId
end
function StoreManager.prototype.transformHeroNameToStoreItemId(self, heroName)
if __TS__StringStartsWith(heroName, ____exports.StoreManager.HERO_STORE_PREFIX) then
return "hero_" .. __TS__StringSubstring(heroName, #____exports.StoreManager.HERO_STORE_PREFIX)
end
if #heroName > 0 then
return heroName
end
return nil
end
function StoreManager.prototype.updateDonateCurrencyDisplay(self, playerId)
self:updateCurrencyDisplay(playerId)
end
function StoreManager.prototype.updateCurrencyDisplay(self, playerId)
local donateAmount = self:getDonateCurrency(playerId)
local freeAmount = self:getFreeCurrency(playerId)
local dustAmount = self:getDustCurrency(playerId)
local info = PlayerInfo:GetPlayerInfo(playerId)
local arcadePackCredits = self:normalizeArcadePackCredits(info and info.arcade_pack_credits)
local player = PlayerResource:GetPlayer(playerId)
if player then
CustomGameEventManager:Send_ServerToPlayer(player, "store_currency_update", {donate_currency = donateAmount, free_currency = freeAmount, dust_currency = dustAmount, arcade_pack_credits = arcadePackCredits})
end
end
function StoreManager.prototype.grantShopExtrasViaApi(self, playerId, extras, logSuccess)
self:sendShopExtrasGiveRequest(
playerId,
{
free = math.max(
0,
math.floor(extras.freeAmount or 0)
),
donate = math.max(
0,
math.floor(extras.donateAmount or 0)
),
dust = math.max(
0,
math.floor(extras.dustAmount or 0)
),
arcadeStandard = math.max(
0,
math.floor(extras.arcadePackStandard or 0)
),
arcadePremium = math.max(
0,
math.floor(extras.arcadePackPremium or 0)
)
},
logSuccess,
false
)
end
function StoreManager.prototype.sendShopExtrasGiveRequest(self, playerId, amounts, logSuccess, alreadyRetriedAfterEnsure)
local ____amounts_149 = amounts
local free = ____amounts_149.free
local donate = ____amounts_149.donate
local dust = ____amounts_149.dust
local arcadeStandard = ____amounts_149.arcadeStandard
local arcadePremium = ____amounts_149.arcadePremium
if free <= 0 and donate <= 0 and dust <= 0 and arcadeStandard <= 0 and arcadePremium <= 0 then
return
end
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
storeVerboseLog(
nil,
"[STORE] grantShopExtrasViaApi: нет Steam ID для " .. tostring(playerId)
)
return
end
local body = {free_currency = free, donate_currency = donate, dust_currency = dust}
if arcadeStandard > 0 or arcadePremium > 0 then
body.arcade_pack_credits = {standard = arcadeStandard, premium = arcadePremium}
end
local request = CreateHTTPRequest(
"POST",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/currency/give"
)
setApiHeadersLong(nil, request)
request:SetHTTPRequestRawPostBody(
"application/json",
encodeApiBody(nil, body)
)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
storeVerboseLog(nil, logSuccess)
do
local function ____catch(e)
storeVerboseLog(
nil,
"[STORE] grantShopExtrasViaApi: ошибка парсинга: " .. tostring(e)
)
end
local ____try, ____hasReturned = pcall(function()
local decoded = {json.decode(result.Body)}
local responseData = nil
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
responseData = decoded[1]
elseif decoded and type(decoded) == "table" then
responseData = decoded
end
if responseData then
self:applyShopCurrencyFromApiPayload(playerId, responseData)
end
end)
if not ____try then
____catch(____hasReturned)
end
end
return
end
if result.StatusCode == 404 and not alreadyRetriedAfterEnsure then
storeVerboseLog(nil, "[STORE] grantShopExtrasViaApi 404 — создаём player и повторяем")
self:ensurePlayerRowOnApi(
playerId,
function(____, ok)
if ok then
self:sendShopExtrasGiveRequest(playerId, amounts, logSuccess, true)
else
storeVerboseLog(nil, "[STORE] grantShopExtrasViaApi: не удалось создать игрока на API")
end
end
)
return
end
local bodyStr = result.Body ~= nil and tostring(result.Body) or ""
storeVerboseLog(
nil,
(("[STORE] grantShopExtrasViaApi: HTTP " .. tostring(result.StatusCode)) .. " body=") .. bodyStr
)
end)
end
function StoreManager.prototype.saveCurrencyToServer(self, playerId)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
return
end
local freeCurrency = self:getFreeCurrency(playerId)
local donateCurrency = self:getDonateCurrency(playerId)
local dustCurrency = self:getDustCurrency(playerId)
local request = CreateHTTPRequest(
"PUT",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/currency"
)
setApiHeaders(nil, request)
local dataToSend = {free_currency = freeCurrency, donate_currency = donateCurrency, dust_currency = dustCurrency}
request:SetHTTPRequestRawPostBody(
"application/json",
encodeApiBody(nil, dataToSend)
)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
else
end
end)
end
function StoreManager.prototype.ensurePlayerRowOnApi(self, playerId, done)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
storeVerboseLog(
nil,
"[STORE] ensurePlayerRowOnApi: нет Steam ID для " .. tostring(playerId)
)
done(nil, false)
return
end
local request = CreateHTTPRequest("POST", SERVER_CONFIG.API_URL .. "/player")
setApiHeadersLong(nil, request)
request:SetHTTPRequestRawPostBody(
"application/json",
encodeApiBody(
nil,
{
steam_id = steamId,
player_name = PlayerResource:GetPlayerName(playerId) or ""
}
)
)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
done(nil, true)
return
end
local bodyStr = result.Body ~= nil and tostring(result.Body) or ""
storeVerboseLog(
nil,
(("[STORE] ensurePlayerRowOnApi: HTTP " .. tostring(result.StatusCode)) .. " body=") .. bodyStr
)
done(nil, false)
end)
end
function StoreManager.prototype.giveCurrencyFromServer(self, playerId, freeAmount, donateAmount, dustAmount)
if dustAmount == nil then
dustAmount = 0
end
self:sendGiveCurrencyRequest(
playerId,
freeAmount,
donateAmount,
dustAmount,
false
)
end
function StoreManager.prototype.sendGiveCurrencyRequest(self, playerId, freeAmount, donateAmount, dustAmount, alreadyRetriedAfterEnsure)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
storeVerboseLog(
nil,
"[STORE] giveCurrency: нет Steam ID для " .. tostring(playerId)
)
return
end
if freeAmount <= 0 and donateAmount <= 0 and dustAmount <= 0 then
return
end
local request = CreateHTTPRequest(
"POST",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/currency/give"
)
setApiHeadersLong(nil, request)
request:SetHTTPRequestRawPostBody(
"application/json",
encodeApiBody(nil, {free_currency = freeAmount, donate_currency = donateAmount, dust_currency = dustAmount})
)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
do
local function ____catch(e)
storeVerboseLog(
nil,
"[STORE] currency/give: ошибка парсинга: " .. tostring(e)
)
end
local ____try, ____hasReturned = pcall(function()
local decoded = {json.decode(result.Body)}
local responseData = nil
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
responseData = decoded[1]
elseif decoded and type(decoded) == "table" then
responseData = decoded
end
if responseData and self:applyShopCurrencyFromApiPayload(playerId, responseData) then
storeVerboseLog(
nil,
(("[STORE] currency/give OK: free=" .. tostring(responseData.free_currency)) .. " donate=") .. tostring(responseData.donate_currency)
)
else
storeVerboseLog(
nil,
"[STORE] currency/give: неожиданный JSON: " .. tostring(result.Body)
)
end
end)
if not ____try then
____catch(____hasReturned)
end
end
elseif result.StatusCode == 404 and not alreadyRetriedAfterEnsure then
storeVerboseLog(nil, "[STORE] currency/give 404 (нет строки player) — POST /player и повтор")
self:ensurePlayerRowOnApi(
playerId,
function(____, ok)
if ok then
self:sendGiveCurrencyRequest(
playerId,
freeAmount,
donateAmount,
dustAmount,
true
)
else
storeVerboseLog(nil, "[STORE] Не удалось создать игрока на API — осколки не начислены")
end
end
)
else
local bodyStr = result.Body ~= nil and tostring(result.Body) or ""
storeVerboseLog(
nil,
(("[STORE] Ошибка currency/give: HTTP " .. tostring(result.StatusCode)) .. " body=") .. bodyStr
)
end
end)
end
function StoreManager.prototype.grantDustCurrencyMatchEndReward(self, playerId, amount)
local n = math.floor(amount)
if n <= 0 then
return
end
self:giveCurrencyFromServer(playerId, 0, 0, n)
end
function StoreManager.prototype.grantFreeCurrencyMatchEndReward(self, playerId, amount)
local n = math.floor(amount)
if n <= 0 then
return
end
self:giveCurrencyFromServer(playerId, n, 0)
end
function StoreManager.prototype.loadCurrencyFromServer(self, playerId)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
storeVerboseLog(
nil,
"[STORE] Ошибка: Steam ID не найден для игрока " .. tostring(playerId)
)
return
end
storeVerboseLog(
nil,
((("[STORE] Загрузка валюты для игрока " .. tostring(playerId)) .. " (SteamID: ") .. tostring(steamId)) .. ")"
)
local loadSeq = self:advanceCurrencyLoadSeq(playerId)
local request = CreateHTTPRequest(
"GET",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/currency"
)
setApiHeaders(nil, request)
request:Send(function(result)
if (self.playerCurrencyLoadSeq:get(playerId) or 0) ~= loadSeq then
storeVerboseLog(
nil,
((("[STORE] Пропуск устаревшего ответа валюты для игрока " .. tostring(playerId)) .. " (seq ") .. tostring(loadSeq)) .. ")"
)
return
end
storeVerboseLog(
nil,
(("[STORE] Ответ сервера: StatusCode=" .. tostring(result.StatusCode)) .. ", Body=") .. tostring(result.Body)
)
if result.StatusCode >= 200 and result.StatusCode < 300 then
do
local function ____catch(e)
storeVerboseLog(
nil,
"[STORE] Ошибка парсинга ответа: " .. tostring(e)
)
end
local ____try, ____hasReturned = pcall(function()
local decoded = {json.decode(result.Body)}
storeVerboseLog(
nil,
"[STORE] Декодированный ответ: " .. json.encode(decoded)
)
local responseData = nil
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
responseData = decoded[1]
elseif decoded and type(decoded) == "table" then
responseData = decoded
end
if responseData and (responseData.free_currency ~= nil or responseData.donate_currency ~= nil or responseData.dust_currency ~= nil) then
storeVerboseLog(
nil,
(((("[STORE] Валюта с сервера: free=" .. tostring(responseData.free_currency)) .. ", donate=") .. tostring(responseData.donate_currency)) .. ", dust=") .. tostring(responseData.dust_currency)
)
if responseData.free_currency ~= nil then
self.playerFreeCurrency:set(playerId, responseData.free_currency)
storeVerboseLog(
nil,
"[STORE] Установлены зомби осколки: " .. tostring(responseData.free_currency)
)
end
if responseData.donate_currency ~= nil then
self.playerDonateCurrency:set(playerId, responseData.donate_currency)
storeVerboseLog(
nil,
"[STORE] Установлена донат валюта: " .. tostring(responseData.donate_currency)
)
end
if responseData.dust_currency ~= nil then
self.playerDustCurrency:set(playerId, responseData.dust_currency)
storeVerboseLog(
nil,
"[STORE] Установлена пыль: " .. tostring(responseData.dust_currency)
)
end
self:loadPurchasesFromServer(
playerId,
function(____, purchases)
self:loadActiveEffectsFromServer(
playerId,
function(____, loadedEffects)
local player = PlayerResource:GetPlayer(playerId)
if player then
local eventData = {
donate_currency = responseData.donate_currency or 0,
free_currency = responseData.free_currency or 0,
dust_currency = responseData.dust_currency or 0,
purchased_items = purchases,
arcade_pack_credits = self:normalizeArcadePackCredits(responseData.arcade_pack_credits)
}
storeVerboseLog(
nil,
"[STORE] Отправка события клиенту: " .. json.encode(eventData)
)
CustomGameEventManager:Send_ServerToPlayer(player, "store_currency_update", eventData)
self:sendActiveEffectsUpdate(playerId, loadedEffects)
else
storeVerboseLog(
nil,
("[STORE] Ошибка: Игрок " .. tostring(playerId)) .. " не найден для отправки события"
)
end
end
)
end
)
else
storeVerboseLog(
nil,
"[STORE] Ошибка: Неверный формат ответа от сервера. responseData: " .. json.encode(responseData)
)
end
end)
if not ____try then
____catch(____hasReturned)
end
end
else
storeVerboseLog(
nil,
"[STORE] Ошибка HTTP: StatusCode=" .. tostring(result.StatusCode)
)
end
end)
end
function StoreManager.prototype.loadPurchasesFromServer(self, playerId, callback)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
callback(nil, {})
return
end
local request = CreateHTTPRequest(
"GET",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/purchases"
)
setApiHeaders(nil, request)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
do
local function ____catch(e)
storeVerboseLog(
nil,
"[STORE] Ошибка парсинга списка покупок: " .. tostring(e)
)
callback(nil, {})
end
local ____try, ____hasReturned = pcall(function()
local decoded = {json.decode(result.Body)}
local purchases = {}
local responseData = nil
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
responseData = decoded[1]
elseif decoded and type(decoded) == "table" then
responseData = decoded
end
if responseData and type(responseData) == "table" and responseData.purchases ~= nil then
local purchasesData = responseData.purchases
if __TS__ArrayIsArray(purchasesData) then
purchases = purchasesData
end
end
local previousPurchases = self.playerPurchases:get(playerId) or __TS__New(Set)
local purchasesSet = __TS__New(Set, purchases)
for ____, p in __TS__Iterator(previousPurchases) do
purchasesSet:add(p)
end
local cardCounts = self:buildCardPurchaseCountsFromPurchaseList(purchasesSet)
self.playerPurchases:set(playerId, purchasesSet)
self.playerCardPurchaseCounts:set(playerId, cardCounts)
self:updateAvailableCardsForDeckBuilder(playerId, purchasesSet, cardCounts)
local mergedPurchasesArray = __TS__ArrayFrom(purchasesSet)
storeVerboseLog(
nil,
(((("[STORE] Загружено с API " .. tostring(#purchases)) .. " покупок, после слияния с сессией ") .. tostring(#mergedPurchasesArray)) .. " для игрока ") .. tostring(playerId)
)
callback(nil, mergedPurchasesArray)
end)
if not ____try then
____catch(____hasReturned)
end
end
else
storeVerboseLog(
nil,
"[STORE] Ошибка загрузки покупок: StatusCode=" .. tostring(result.StatusCode)
)
callback(nil, {})
end
end)
end
StoreManager.HERO_STORE_PREFIX = "npc_dota_hero_"
____exports.StoreManager:getInstance()
return ____exports