72b73c4dd6
Allows the game client to make HTTP API calls from a listen server (local lobby) instead of requiring a Steam dedicated server. CreateHTTPRequestScriptVM has the exact same API signature but works in both dedicated server and listen server contexts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2952 lines
125 KiB
Lua
2952 lines
125 KiB
Lua
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 = CreateHTTPRequestScriptVM("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 = CreateHTTPRequestScriptVM("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 = CreateHTTPRequestScriptVM(
|
||
"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 = CreateHTTPRequestScriptVM(
|
||
"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 = CreateHTTPRequestScriptVM(
|
||
"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 = CreateHTTPRequestScriptVM(
|
||
"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 = CreateHTTPRequestScriptVM(
|
||
"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 = CreateHTTPRequestScriptVM(
|
||
"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 = CreateHTTPRequestScriptVM(
|
||
"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 = CreateHTTPRequestScriptVM(
|
||
"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 = CreateHTTPRequestScriptVM(
|
||
"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 = CreateHTTPRequestScriptVM(
|
||
"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 = CreateHTTPRequestScriptVM("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 = CreateHTTPRequestScriptVM(
|
||
"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 = CreateHTTPRequestScriptVM(
|
||
"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 = CreateHTTPRequestScriptVM(
|
||
"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
|