local ____lualib = require("lualib_bundle") local __TS__Class = ____lualib.__TS__Class local Map = ____lualib.Map local __TS__New = ____lualib.__TS__New local Set = ____lualib.Set local __TS__Iterator = ____lualib.__TS__Iterator local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach local __TS__ArrayFind = ____lualib.__TS__ArrayFind local __TS__Number = ____lualib.__TS__Number local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys local __TS__ArrayMap = ____lualib.__TS__ArrayMap local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter local ____exports = {} local ____match_end_combat_stats = require("match_end_combat_stats") local MatchEndCombatStats = ____match_end_combat_stats.MatchEndCombatStats 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 ____chat_wheel_grant = require("chat_wheel_grant") local grantChatWheelSoundFromBattlePass = ____chat_wheel_grant.grantChatWheelSoundFromBattlePass local ____store_manager = require("store_manager") local StoreManager = ____store_manager.StoreManager local ____player_info = require("player_info") local PlayerInfo = ____player_info.PlayerInfo local ____battle_pass_duplicate_compensation = require("battle_pass_duplicate_compensation") local getBpDuplicateCompensationByStoreItemId = ____battle_pass_duplicate_compensation.getBpDuplicateCompensationByStoreItemId local ____WaveManager = require("WaveManager") local WaveManager = ____WaveManager.WaveManager local ____QuestSystem = require("quests.QuestSystem") local QuestEvents = ____QuestSystem.QuestEvents local ENABLE_VERBOSE_BATTLE_PASS_SERVER_LOGS = true local function rawPrintFn(____, ...) _G:print(...) end local ____print = ENABLE_VERBOSE_BATTLE_PASS_SERVER_LOGS and rawPrintFn or (function(____, ...) return nil end) --- Число из ответа API / тела события (VScript). local function parseApiNumber(self, v) if v == nil or v == nil then return 0 end local n = tonumber(tostring(v)) return n ~= nil and n ~= nil and n or 0 end --- Булевы поля из API (1 / "true" и т.д.) local function apiJsonBool(self, v) return v == true or v == 1 or v == "1" or v == "true" end ____exports.BattlePassServer = __TS__Class() local BattlePassServer = ____exports.BattlePassServer BattlePassServer.name = "BattlePassServer" BattlePassServer.____file_path = "scripts/vscripts/battle_pass_server.lua" function BattlePassServer.prototype.____constructor(self) self.serverUrl = SERVER_CONFIG.API_URL self.playerQuestState = __TS__New(Map) self.bpServerSnapshotByPlayer = __TS__New(Map) self:setupEventListeners() self:setupQuestTracking() self:setupPlayerConnectionHandlers() ____print(nil, "[BattlePassServer] Инициализирован") end function BattlePassServer.getInstance(self) if not ____exports.BattlePassServer.instance then ____exports.BattlePassServer.instance = __TS__New(____exports.BattlePassServer) end return ____exports.BattlePassServer.instance end function BattlePassServer.prototype.rememberBattlePassSnapshot(self, playerId, level, experience, hasPremium) local prev = self.bpServerSnapshotByPlayer:get(playerId) local ____temp_3 if hasPremium ~= nil then ____temp_3 = hasPremium else local ____temp_2 = prev and prev.has_premium if ____temp_2 == nil then ____temp_2 = false end ____temp_3 = ____temp_2 end local hp = ____temp_3 self.bpServerSnapshotByPlayer:set(playerId, {level = level, experience = experience, has_premium = hp}) end function BattlePassServer.prototype.getBattlePassSnapshotForMatchEnd(self, playerId) local s = self.bpServerSnapshotByPlayer:get(playerId) if s then return {level = s.level, experience = s.experience, has_premium = s.has_premium} end return {level = 1, experience = 0, has_premium = false} end function BattlePassServer.prototype.getNpcQuestsCompletedForPlayer(self, playerId) local state = self.playerQuestState:get(playerId) return state and state.npcQuestsCompleted or 0 end function BattlePassServer.prototype.getTotalHealToAlliesForPlayer(self, playerId) return MatchEndCombatStats:getInstance():getHealToAlliesSum(playerId) end function BattlePassServer.prototype.pickDecodedJsonRoot(self, decoded, rootKeys) if not decoded or type(decoded) ~= "table" then return decoded end for ____, key in ipairs(rootKeys) do local v = decoded[key] if v ~= nil and v ~= nil then return decoded end end for ____, idx in ipairs({1, 0}) do local inner = decoded[idx] if inner and type(inner) == "table" then for ____, key in ipairs(rootKeys) do local v = inner[key] if v ~= nil and v ~= nil then return inner end end end end return decoded end function BattlePassServer.prototype.setupPlayerConnectionHandlers(self) ListenToGameEvent( "player_connect_full", function(event) local playerId = event.PlayerID if playerId ~= nil and playerId >= 0 then Timers:CreateTimer( 2, function() if PlayerResource:IsValidPlayer(playerId) and not PlayerResource:IsFakeClient(playerId) then local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) ____print( nil, ((("[BattlePassServer] Автоматическая загрузка квестов для игрока " .. tostring(playerId)) .. " (Steam ID: ") .. steamId) .. ")" ) self:loadQuests(playerId, steamId) ____print( nil, "[BattlePassServer] Предзагрузка данных Battle Pass для игрока " .. tostring(playerId) ) self:loadBattlePassData(playerId, steamId) end return nil end ) end end, nil ) ListenToGameEvent( "npc_spawned", function(event) local unitIndex = event.entindex if unitIndex == nil or unitIndex == nil then return end local unit = EntIndexToHScript(unitIndex) if unit and unit:IsRealHero() then local playerId = unit:GetPlayerOwnerID() if playerId >= 0 and not PlayerResource:IsFakeClient(playerId) then self:onHeroSelected( playerId, unit:GetUnitName() ) Timers:CreateTimer( 1, function() local state = self.playerQuestState:get(playerId) local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) if not state or not state.questsLoaded then ____print( nil, ((("[BattlePassServer] Автоматическая загрузка квестов при спавне героя для игрока " .. tostring(playerId)) .. " (Steam ID: ") .. steamId) .. ")" ) self:loadQuests(playerId, steamId) end if not self.bpServerSnapshotByPlayer:has(playerId) then ____print( nil, "[BattlePassServer] Догрузка Battle Pass при спавне героя для игрока " .. tostring(playerId) ) self:loadBattlePassData(playerId, steamId) end return nil end ) end end end, nil ) end function BattlePassServer.prototype.setupQuestTracking(self) ListenToGameEvent( "entity_killed", function(event) local killedIndex = event.entindex_killed if killedIndex == nil or killedIndex == nil then return end local killedUnit = EntIndexToHScript(killedIndex) if not killedUnit then return end local killerIndex = event.entindex_attacker local ____temp_6 if killerIndex == nil or killerIndex == nil then ____temp_6 = nil else ____temp_6 = EntIndexToHScript(killerIndex) end local killerEnt = ____temp_6 if not killerEnt then return end local hero local killerHasIsRealHero = killerEnt.IsRealHero ~= nil if killerHasIsRealHero and killerEnt:IsRealHero() then hero = killerEnt else local ____opt_7 = killerEnt.GetOwner local owner = ____opt_7 and ____opt_7(killerEnt) local ____opt_result_11 if owner ~= nil then ____opt_result_11 = owner.IsRealHero end local ownerHasIsRealHero = ____opt_result_11 ~= nil if owner and ownerHasIsRealHero and owner:IsRealHero() then hero = owner end end if not hero then return end local playerId = hero:GetPlayerOwnerID() if playerId < 0 then return end local team = killedUnit:GetTeamNumber() if team == DOTA_TEAM_NEUTRALS or team == DOTA_TEAM_BADGUYS then local state = self:getOrCreateQuestState(playerId) state.killCount = state.killCount + 1 self:syncPlayerQuestProgress(playerId) end end, nil ) Timers:CreateTimer( 5, function() do local function ____catch(e) ____print( nil, "[BattlePassServer] Не удалось подписаться на OnWaveEnd: " .. tostring(e) ) end local ____try, ____hasReturned = pcall(function() WaveManager:getInstance():OnWaveEnd(function(____, info) do local i = 0 while i < PlayerResource:GetPlayerCount() do do if not PlayerResource:IsValidPlayer(i) or PlayerResource:IsFakeClient(i) then goto __continue52 end local state = self:getOrCreateQuestState(i) state.wavesCompleted = info.waveIndex self:syncPlayerQuestProgress(i) end ::__continue52:: i = i + 1 end end end) ____print(nil, "[BattlePassServer] Зарегистрирован OnWaveEnd для квестов") end) if not ____try then ____catch(____hasReturned) end end return nil end ) QuestEvents:OnQuestCompleted(function(____, data) local questId = data.questId or "" ____print( nil, "[BattlePassServer] OnQuestCompleted: questId=" .. tostring(questId) ) do local i = 0 while i < PlayerResource:GetPlayerCount() do do if not PlayerResource:IsValidPlayer(i) or PlayerResource:IsFakeClient(i) then goto __continue56 end local state = self:getOrCreateQuestState(i) state.npcQuestsCompleted = state.npcQuestsCompleted + 1 local canSync = state.questsLoaded and #state.quests > 0 if not canSync then ____print( nil, ((("[BattlePassServer] Квесты ещё не загружены для игрока " .. tostring(i)) .. ", прогресс сохранён (npcQuestsCompleted=") .. tostring(state.npcQuestsCompleted)) .. ")" ) Timers:CreateTimer( 3, function() self:syncPlayerQuestProgress(i) return nil end ) end self:syncPlayerQuestProgress(i) end ::__continue56:: i = i + 1 end end end) ListenToGameEvent( "entity_killed", function(event) local killedIndex = event.entindex_killed if killedIndex == nil or killedIndex == nil then return end local killed = EntIndexToHScript(killedIndex) local ____temp_14 = not killed if not ____temp_14 then local ____this_13 ____this_13 = killed local ____opt_12 = ____this_13.IsRealHero if ____opt_12 ~= nil then ____opt_12 = ____opt_12(____this_13) end ____temp_14 = not ____opt_12 end if ____temp_14 then return end local playerId = killed:GetPlayerOwnerID() if playerId < 0 then return end local state = self:getOrCreateQuestState(playerId) state.lastDeathTime = GameRules:GetGameTime() self:syncPlayerQuestProgress(playerId) end, nil ) ListenToGameEvent( "dota_item_picked_up", function(event) local heroIndex = event.HeroEntityIndex local itemIndex = event.ItemEntityIndex if heroIndex == nil or heroIndex == nil then return end if itemIndex == nil or itemIndex == nil then return end local hero = EntIndexToHScript(heroIndex) local item = EntIndexToHScript(itemIndex) if not hero or not item or not hero:IsRealHero() then return end local playerId = hero:GetPlayerOwnerID() if playerId < 0 then return end local itemName = item:GetAbilityName() local state = self:getOrCreateQuestState(playerId) state.collectedItems[itemName] = (state.collectedItems[itemName] or 0) + 1 self:syncPlayerQuestProgress(playerId) end, nil ) CustomGameEventManager:RegisterListener( "cooking_claim_dish", function(_, event) local ____event_playerID_15 = event.playerID if ____event_playerID_15 == nil then ____event_playerID_15 = event.PlayerID end local playerId = ____event_playerID_15 if playerId == nil or playerId < 0 then return end local result = event.result local state = self:getOrCreateQuestState(playerId) state.cookingCompleted = state.cookingCompleted + 1 if result == "item_grilled_meat" then state.cookGrilledMeat = 1 end self:syncPlayerQuestProgress(playerId) end ) CustomGameEventManager:RegisterListener( "cooking_start", function(_, event) local ____event_PlayerID_16 = event.PlayerID if ____event_PlayerID_16 == nil then ____event_PlayerID_16 = event.playerID end local playerId = ____event_PlayerID_16 if playerId == nil or playerId < 0 then return end local state = self:getOrCreateQuestState(playerId) state.campfireUses = state.campfireUses + 1 self:syncPlayerQuestProgress(playerId) end ) Timers:CreateTimer( 15, function() self:syncAllQuestProgress() return 30 end ) ____print(nil, "[BattlePassServer] Квест-трекинг инициализирован") end function BattlePassServer.prototype.getOrCreateQuestState(self, playerId) local state = self.playerQuestState:get(playerId) if not state then state = { quests = {}, killCount = 0, blackShopBuys = 0, blackShopBuysByQuality = {}, npcQuestsCompleted = 0, npcQuestsByNpc = {}, wavesCompleted = 0, gameStartTime = GameRules:GetGameTime(), lastSyncTime = 0, questsLoaded = false, cookingCompleted = 0, cookGrilledMeat = 0, campfireUses = 0, tipsGiven = 0, lastDeathTime = 0, collectedItems = {}, heroesPlayed = __TS__New(Set) } self.playerQuestState:set(playerId, state) end return state end function BattlePassServer.prototype.onTipped(self, playerId) local state = self:getOrCreateQuestState(playerId) state.tipsGiven = state.tipsGiven + 1 self:syncPlayerQuestProgress(playerId) end function BattlePassServer.prototype.onHealAlly(self, healerPlayerId, amount) MatchEndCombatStats:getInstance():addHealToAllies(healerPlayerId, amount) self:syncPlayerQuestProgress(healerPlayerId) end function BattlePassServer.prototype.onHeroSelected(self, playerId, heroName) local state = self:getOrCreateQuestState(playerId) state.heroesPlayed:add(heroName) local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) local request = CreateHTTPRequestScriptVM("POST", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/hero-played") setApiHeaders(nil, request) request:SetHTTPRequestRawPostBody( "application/json", json.encode({hero_name = heroName}) ) request:Send(function() end) self:syncPlayerQuestProgress(playerId) end function BattlePassServer.prototype.onBlackShopPurchase(self, playerId, quality) local state = self:getOrCreateQuestState(playerId) state.blackShopBuys = state.blackShopBuys + 1 if quality then local q = string.lower(quality) state.blackShopBuysByQuality[q] = (state.blackShopBuysByQuality[q] or 0) + 1 end self:syncPlayerQuestProgress(playerId) end function BattlePassServer.prototype.getQuestProgress(self, playerId, quest) local state = self.playerQuestState:get(playerId) if not state then return 0 end repeat local ____switch85 = quest.type local ____cond85 = ____switch85 == "kill_zombies" if ____cond85 then return state.killCount end ____cond85 = ____cond85 or ____switch85 == "survive_time" if ____cond85 then return math.floor(GameRules:GetGameTime() - state.gameStartTime) end ____cond85 = ____cond85 or ____switch85 == "buy_black_shop" if ____cond85 then return state.blackShopBuys end ____cond85 = ____cond85 or ____switch85 == "buy_black_shop_quality" if ____cond85 then do local q = string.lower(quest.quality or "") return q ~= "" and (state.blackShopBuysByQuality[q] or 0) or 0 end end ____cond85 = ____cond85 or ____switch85 == "complete_npc_quest" if ____cond85 then return state.npcQuestsCompleted end ____cond85 = ____cond85 or ____switch85 == "complete_npc_quest_npc" if ____cond85 then do local npc = quest.npc or "" return npc ~= "" and (state.npcQuestsByNpc[npc] or 0) or 0 end end ____cond85 = ____cond85 or ____switch85 == "earn_gold" if ____cond85 then do do local function ____catch(e) return true, 0 end local ____try, ____hasReturned, ____returnValue = pcall(function() return true, PlayerResource:GetTotalEarnedGold(playerId) end) if not ____try then ____hasReturned, ____returnValue = ____catch(____hasReturned) end if ____hasReturned then return ____returnValue end end end end ____cond85 = ____cond85 or ____switch85 == "hero_level" if ____cond85 then do local hero = PlayerResource:GetSelectedHeroEntity(playerId) if hero then return hero:GetLevel() end return 0 end end ____cond85 = ____cond85 or ____switch85 == "survive_waves" if ____cond85 then return state.wavesCompleted end ____cond85 = ____cond85 or ____switch85 == "complete_cooking" if ____cond85 then return state.cookingCompleted end ____cond85 = ____cond85 or ____switch85 == "cook_grilled_meat" if ____cond85 then return state.cookGrilledMeat end ____cond85 = ____cond85 or ____switch85 == "use_campfire" if ____cond85 then return state.campfireUses end ____cond85 = ____cond85 or ____switch85 == "tip_teammate" if ____cond85 then return state.tipsGiven end ____cond85 = ____cond85 or ____switch85 == "no_death" if ____cond85 then do if state.lastDeathTime > 0 then return 0 end return math.floor(GameRules:GetGameTime() - state.gameStartTime) end end ____cond85 = ____cond85 or ____switch85 == "heal_ally" if ____cond85 then return MatchEndCombatStats:getInstance():getHealToAlliesSum(playerId) end ____cond85 = ____cond85 or ____switch85 == "deal_damage" if ____cond85 then return MatchEndCombatStats:getInstance():getOutgoingDamageSum(playerId) end ____cond85 = ____cond85 or ____switch85 == "collect_item" if ____cond85 then do local item = quest.target_item or "" return item ~= "" and (state.collectedItems[item] or 0) or 0 end end ____cond85 = ____cond85 or ____switch85 == "play_different_hero" if ____cond85 then return math.max(state.heroesPlayed.size, quest.progress or 0) end do return 0 end until true end function BattlePassServer.prototype.syncPlayerQuestProgress(self, playerId) local state = self.playerQuestState:get(playerId) if not state or not state.questsLoaded or #state.quests == 0 then return end if not PlayerResource:IsValidPlayer(playerId) or PlayerResource:IsFakeClient(playerId) then return end local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) local anyUpdated = false for ____, quest in ipairs(state.quests) do do if quest.claimed then goto __continue99 end local currentProgress = self:getQuestProgress(playerId, quest) local newProgress = math.min(currentProgress, quest.target) local newCompleted = newProgress >= quest.target local wasCompleted = quest.completed if newProgress ~= quest.progress or newCompleted ~= quest.completed then quest.progress = newProgress quest.completed = newCompleted anyUpdated = true self:syncQuestProgressToApi(steamId, quest.quest_id, newProgress) if quest.completed and not wasCompleted then ____print(nil, ("[BattlePassServer] Квест " .. quest.quest_id) .. " выполнен, автоматически забираем...") self:claimQuest(playerId, steamId, quest.quest_id) end end end ::__continue99:: end if anyUpdated then self:sendQuestsToClient(playerId, state.quests) end end function BattlePassServer.prototype.syncAllQuestProgress(self) for ____, ____value in __TS__Iterator(self.playerQuestState) do local playerId = ____value[1] local state = ____value[2] do if not state.questsLoaded or #state.quests == 0 then goto __continue106 end if not PlayerResource:IsValidPlayer(playerId) or PlayerResource:IsFakeClient(playerId) then goto __continue106 end local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) local anyUpdated = false for ____, quest in ipairs(state.quests) do do if quest.claimed then goto __continue109 end local currentProgress = self:getQuestProgress(playerId, quest) local newProgress = math.min(currentProgress, quest.target) local newCompleted = newProgress >= quest.target local wasCompleted = quest.completed if newProgress ~= quest.progress or newCompleted ~= quest.completed then quest.progress = newProgress quest.completed = newCompleted anyUpdated = true self:syncQuestProgressToApi(steamId, quest.quest_id, newProgress) if quest.completed and not wasCompleted then ____print(nil, ("[BattlePassServer] Квест " .. quest.quest_id) .. " выполнен, автоматически забираем...") self:claimQuest(playerId, steamId, quest.quest_id) end end end ::__continue109:: end if anyUpdated then self:sendQuestsToClient(playerId, state.quests) end end ::__continue106:: end end function BattlePassServer.prototype.syncQuestProgressToApi(self, steamId, questId, progress) local request = CreateHTTPRequestScriptVM("POST", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/quests/progress") setApiHeaders(nil, request) request:SetHTTPRequestRawPostBody( "application/json", json.encode({quest_id = questId, progress = progress}) ) request:Send(function(result) if result.StatusCode >= 200 and result.StatusCode < 300 then do pcall(function() local decoded = {json.decode(result.Body)} local data = self:pickDecodedJsonRoot(decoded, {"success", "completed", "progress"}) if data and apiJsonBool(nil, data.completed) then ____print( nil, ((("[BattlePassServer] Квест " .. questId) .. " стал выполненным (прогресс: ") .. tostring(progress)) .. ")" ) end end) end else ____print( nil, (("[BattlePassServer] Ошибка синхронизации квеста " .. questId) .. ": ") .. tostring(result.StatusCode) ) end end) end function BattlePassServer.prototype.sendQuestsToClient(self, playerId, quests) local player = PlayerResource:GetPlayer(playerId) if not player then return end local questsObject = {} __TS__ArrayForEach( quests, function(____, q, idx) questsObject[tostring(idx)] = { quest_id = q.quest_id, type = q.type, name = q.name, description = q.description, progress = q.progress, target = q.target, completed = q.completed and 1 or 0, claimed = q.claimed and 1 or 0, reward_exp = q.reward_exp, reward_free_currency = q.reward_free_currency or 0 } end ) CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_quests_data", {quests = questsObject}) end function BattlePassServer.prototype.loadQuests(self, playerId, steamId) ____print( nil, ((("[BattlePassServer] Запрос квестов для " .. steamId) .. " (playerId: ") .. tostring(playerId)) .. ")" ) local request = CreateHTTPRequestScriptVM("GET", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/quests") setApiHeaders(nil, request) request:Send(function(result) local player = PlayerResource:GetPlayer(playerId) if not player then ____print( nil, ("[BattlePassServer] Игрок " .. tostring(playerId)) .. " не найден при получении квестов" ) return end if result.StatusCode == 0 then ____print(nil, "[BattlePassServer] Сервер недоступен (StatusCode=0) для квестов") return end if result.StatusCode >= 200 and result.StatusCode < 300 then do local function ____catch(e) ____print( nil, "[BattlePassServer] Ошибка парсинга квестов: " .. tostring(e) ) ____print( nil, "[BattlePassServer] Тело ответа: " .. tostring(result.Body) ) end local ____try, ____hasReturned = pcall(function() local decoded = {json.decode(result.Body)} ____print( nil, "[BattlePassServer] Ответ API квестов (raw): " .. tostring(result.Body) ) local data = self:pickDecodedJsonRoot(decoded, {"quests"}) ____print( nil, "[BattlePassServer] Финальные данные: " .. json.encode(data) ) local questsData = nil if data and type(data) == "table" then if data.quests ~= nil and data.quests ~= nil then questsData = data.quests ____print(nil, "[BattlePassServer] ✓ Найдено через .quests") elseif data.quests ~= nil and data.quests ~= nil then questsData = data.quests ____print(nil, "[BattlePassServer] ✓ Найдено через [\"quests\"]") end end if questsData == nil or questsData == nil then ____print(nil, "[BattlePassServer] ⚠️ quests не найдено в data") end local questsArray = {} if questsData ~= nil and questsData ~= nil then local rawQuests = {} local index = 1 while true do do local item = questsData[index] if item == nil or item == nil then if index == 1 then index = 0 goto __continue136 end break end rawQuests[#rawQuests + 1] = item index = index + 1 end ::__continue136:: end if #rawQuests == 0 and type(questsData) == "table" then for key in pairs(questsData) do local item = questsData[key] if item and type(item) == "table" then rawQuests[#rawQuests + 1] = item end end end ____print( nil, ("[BattlePassServer] Получено " .. tostring(#rawQuests)) .. " квестов из API" ) for ____, q in ipairs(rawQuests) do if q and type(q) == "table" then local questObj = { quest_id = q.quest_id or "", type = q.type or "unknown", name = q.name or q.quest_id or "", description = q.description or "", progress = q.progress or 0, target = q.target or 1, completed = apiJsonBool(nil, q.completed), claimed = apiJsonBool(nil, q.claimed), reward_exp = q.reward_exp or 0, reward_free_currency = q.reward_free_currency or 0 } if q.quality then questObj.quality = q.quality end if q.npc then questObj.npc = q.npc end if q.target_item then questObj.target_item = q.target_item end questsArray[#questsArray + 1] = questObj end end else ____print(nil, "[BattlePassServer] ⚠️ questsData пусто или null после всех попыток") end local state = self:getOrCreateQuestState(playerId) state.quests = questsArray state.questsLoaded = true for ____, quest in ipairs(state.quests) do do if quest.claimed then goto __continue150 end local currentProgress = self:getQuestProgress(playerId, quest) local newProgress = math.min(currentProgress, quest.target) local newCompleted = newProgress >= quest.target local wasCompleted = quest.completed if newProgress > quest.progress or newCompleted ~= quest.completed then quest.progress = newProgress quest.completed = newCompleted self:syncQuestProgressToApi(steamId, quest.quest_id, newProgress) end if quest.completed and not quest.claimed then ____print(nil, ("[BattlePassServer] Квест " .. quest.quest_id) .. " уже выполнен, автоматически забираем...") self:claimQuest(playerId, steamId, quest.quest_id) end end ::__continue150:: end ____print( nil, (("[BattlePassServer] Загружено " .. tostring(#questsArray)) .. " квестов для ") .. steamId ) self:sendQuestsToClient(playerId, state.quests) Timers:CreateTimer( 0.5, function() self:syncPlayerQuestProgress(playerId) return nil end ) end) if not ____try then ____catch(____hasReturned) end end else ____print( nil, "[BattlePassServer] Ошибка загрузки квестов: " .. tostring(result.StatusCode) ) ____print( nil, "[BattlePassServer] Тело ответа: " .. tostring(result.Body) ) end end) end function BattlePassServer.prototype.claimQuest(self, playerId, steamId, questId) ____print( nil, (((("[BattlePassServer] claimQuest: playerId=" .. tostring(playerId)) .. ", steamId=") .. steamId) .. ", questId=") .. questId ) local state = self.playerQuestState:get(playerId) if not state then ____print( nil, "[BattlePassServer] ⚠️ Состояние квестов не найдено для playerId=" .. tostring(playerId) ) return end local quest = __TS__ArrayFind( state.quests, function(____, q) return q.quest_id == questId end ) if not quest then ____print(nil, ("[BattlePassServer] ⚠️ Квест " .. questId) .. " не найден в локальном состоянии") local player = PlayerResource:GetPlayer(playerId) if player then CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_quest_claim_result", {success = false, error = "Quest not found"}) end return end ____print( nil, (((((("[BattlePassServer] Квест найден: completed=" .. tostring(quest.completed)) .. ", claimed=") .. tostring(quest.claimed)) .. ", progress=") .. tostring(quest.progress)) .. "/") .. tostring(quest.target) ) if not quest.completed or quest.claimed then ____print( nil, (("[BattlePassServer] ⚠️ Квест не может быть забран: completed=" .. tostring(quest.completed)) .. ", claimed=") .. tostring(quest.claimed) ) local player = PlayerResource:GetPlayer(playerId) if player then CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_quest_claim_result", {success = false, error = "Quest not completed or already claimed"}) end return end ____print(nil, "[BattlePassServer] Отправляем запрос на клейм квеста " .. questId) local request = CreateHTTPRequestScriptVM("POST", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/quests/claim") setApiHeadersLong(nil, request) request:SetHTTPRequestRawPostBody( "application/json", json.encode({quest_id = questId}) ) request:Send(function(result) local player = PlayerResource:GetPlayer(playerId) if not player then return end if result.StatusCode >= 200 and result.StatusCode < 300 then do local function ____catch(e) ____print( nil, "[BattlePassServer] Ошибка парсинга claim квеста: " .. tostring(e) ) ____print( nil, "[BattlePassServer] Тело ответа: " .. tostring(result.Body) ) end local ____try, ____hasReturned = pcall(function() local decoded = {json.decode(result.Body)} ____print( nil, "[BattlePassServer] Ответ claim квеста (raw): " .. tostring(result.Body) ) local data = self:pickDecodedJsonRoot(decoded, {"success", "reward_exp", "quest_id", "new_level"}) ____print( nil, "[BattlePassServer] Данные claim: " .. json.encode(data) ) quest.claimed = true local rewardExpNum = __TS__Number(data.reward_exp) local rewardExp = __TS__NumberIsFinite(rewardExpNum) and rewardExpNum or 0 local rewardFreeNum = __TS__Number(data.reward_free_currency) local rewardFreeCurrency = __TS__NumberIsFinite(rewardFreeNum) and rewardFreeNum or 0 local newLevelNum = __TS__Number(data.new_level) local newLevel = __TS__NumberIsFinite(newLevelNum) and newLevelNum or 0 local newExpNum = __TS__Number(data.new_experience) local newExp = __TS__NumberIsFinite(newExpNum) and newExpNum or 0 ____print( nil, (((((((("[BattlePassServer] Квест " .. questId) .. " забран для ") .. steamId) .. ", +") .. tostring(rewardExp)) .. " BP XP, новый уровень: ") .. tostring(newLevel)) .. ", новый опыт: ") .. tostring(newExp) ) CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_quest_claim_result", { success = true, quest_id = questId, reward_exp = rewardExp, reward_free_currency = rewardFreeCurrency, new_level = newLevel, new_experience = newExp }) self:sendQuestsToClient(playerId, state.quests) ____print(nil, "[BattlePassServer] Перезагружаем данные BP после клейма квеста...") self:loadBattlePassData(playerId, steamId) end) if not ____try then ____catch(____hasReturned) end end else local errorMsg = "Error: " .. tostring(result.StatusCode) ____print( nil, "[BattlePassServer] Ошибка клейма квеста: StatusCode=" .. tostring(result.StatusCode) ) ____print( nil, "[BattlePassServer] Тело ответа: " .. tostring(result.Body) ) do local function ____catch(e) ____print( nil, "[BattlePassServer] Ошибка парсинга ответа об ошибке: " .. tostring(e) ) end local ____try, ____hasReturned = pcall(function() local body = {json.decode(result.Body)} local errorBody = body if body and type(body) == "table" and body[1] ~= nil then errorBody = body[1] end if errorBody and errorBody.error then errorMsg = errorBody.error ____print(nil, "[BattlePassServer] Сообщение об ошибке: " .. errorMsg) end end) if not ____try then ____catch(____hasReturned) end end CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_quest_claim_result", {success = false, error = errorMsg}) end end) end function BattlePassServer.prototype.setupEventListeners(self) CustomGameEventManager:RegisterListener( "request_battle_pass", function(source, event) local playerId = event.PlayerID local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) ____print( nil, (("[BattlePassServer] Запрос данных Battle Pass для игрока " .. tostring(playerId)) .. ", Steam ID: ") .. steamId ) self:loadBattlePassData(playerId, steamId) end ) CustomGameEventManager:RegisterListener( "battle_pass_claim_reward", function(source, event) local playerId = event.PlayerID local level = event.level local rawPrem = event.is_premium local isPremium = rawPrem == 1 or rawPrem == true or rawPrem == "1" local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) ____print( nil, (((("[BattlePassServer] Запрос на забор награды уровня " .. tostring(level)) .. " (premium: ") .. tostring(isPremium)) .. ") для игрока ") .. tostring(playerId) ) self:claimReward(playerId, steamId, level, isPremium) end ) CustomGameEventManager:RegisterListener( "buy_battle_pass_premium", function(source, event) local playerId = event.PlayerID local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) ____print( nil, "[BattlePassServer] Запрос на покупку Battle Pass Premium для игрока " .. tostring(playerId) ) self:buyPremium(playerId, steamId) end ) CustomGameEventManager:RegisterListener( "claim_all_battle_pass_rewards", function(source, event) local playerId = event.PlayerID local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) ____print( nil, "[BattlePassServer] Запрос на забор ВСЕХ наград для игрока " .. tostring(playerId) ) self:claimAllRewards(playerId, steamId) end ) CustomGameEventManager:RegisterListener( "request_battle_pass_quests", function(source, event) local playerId = event.PlayerID local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) ____print( nil, "[BattlePassServer] Запрос квестов для игрока " .. tostring(playerId) ) self:loadQuests(playerId, steamId) end ) CustomGameEventManager:RegisterListener( "claim_battle_pass_quest", function(source, event) local playerId = event.PlayerID local questId = event.quest_id local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) ____print( nil, (("[BattlePassServer] Клейм квеста " .. questId) .. " для игрока ") .. tostring(playerId) ) self:claimQuest(playerId, steamId, questId) end ) end function BattlePassServer.prototype.loadBattlePassData(self, playerId, steamId) local request = CreateHTTPRequestScriptVM("GET", (self.serverUrl .. "/battlepass/") .. steamId) setApiHeaders(nil, request) request:Send(function(result) do local function ____catch(____error) ____print( nil, "[BattlePassServer] Ошибка обработки ответа: " .. tostring(____error) ) local player = PlayerResource:GetPlayer(playerId) if player then CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_data", {error = "Data processing error"}) end end local ____try, ____hasReturned, ____returnValue = pcall(function() local player = PlayerResource:GetPlayer(playerId) if not player then return true end if result.StatusCode == 0 then ____print(nil, "[BattlePassServer] Сервер недоступен (StatusCode=0)") CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_data", {error = "Server unavailable"}) return true end if result.StatusCode >= 200 and result.StatusCode < 300 then do local function ____catch(e) ____print( nil, "[BattlePassServer] Ошибка парсинга JSON: " .. tostring(e) ) CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_data", {error = "Data parsing error"}) end local ____try, ____hasReturned = pcall(function() local responseData = {json.decode(result.Body)} local data = nil if __TS__ArrayIsArray(responseData) and #responseData > 0 then data = responseData[1] elseif responseData and type(responseData) == "table" then data = responseData end if data then local claimedRewards = data.claimed_rewards or ({}) local claimedObject = {} if __TS__ArrayIsArray(claimedRewards) then __TS__ArrayForEach( claimedRewards, function(____, level, index) claimedObject[tostring(index)] = level end ) elseif type(claimedRewards) == "table" then for key in pairs(claimedRewards) do if rawget(claimedRewards, key) ~= nil then claimedObject[key] = claimedRewards[key] end end end local claimedPremiumRewards = data.claimed_premium_rewards or ({}) local claimedPremiumObject = {} if __TS__ArrayIsArray(claimedPremiumRewards) then __TS__ArrayForEach( claimedPremiumRewards, function(____, level, index) claimedPremiumObject[tostring(index)] = level end ) elseif type(claimedPremiumRewards) == "table" then for key in pairs(claimedPremiumRewards) do if rawget(claimedPremiumRewards, key) ~= nil then claimedPremiumObject[key] = claimedPremiumRewards[key] end end end ____print( nil, (((((((("[BattlePassServer] Данные загружены: level=" .. tostring(data.level)) .. ", exp=") .. tostring(data.experience)) .. ", claimed=") .. tostring(#__TS__ObjectKeys(claimedObject))) .. ", premium=") .. tostring(data.has_premium)) .. ", claimed_premium=") .. tostring(#__TS__ObjectKeys(claimedPremiumObject)) ) self:rememberBattlePassSnapshot( playerId, parseApiNumber(nil, data.level), parseApiNumber(nil, data.experience), apiJsonBool(nil, data.has_premium) ) CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_data", { level = data.level or 0, experience = data.experience or 0, claimed_rewards = claimedObject, has_premium = data.has_premium and 1 or 0, claimed_premium_rewards = claimedPremiumObject }) else ____print(nil, "[BattlePassServer] Данные не найдены, создаем новый Battle Pass для " .. steamId) self:createBattlePass(playerId, steamId) end end) if not ____try then ____catch(____hasReturned) end end elseif result.StatusCode == 404 then ____print(nil, "[BattlePassServer] 404 - создаем новый Battle Pass для " .. steamId) self:createBattlePass(playerId, steamId) else ____print( nil, "[BattlePassServer] Ошибка сервера: " .. tostring(result.StatusCode) ) CustomGameEventManager:Send_ServerToPlayer( player, "battle_pass_data", {error = "Server error: " .. tostring(result.StatusCode)} ) end end) if not ____try then ____hasReturned, ____returnValue = ____catch(____hasReturned) end if ____hasReturned then return ____returnValue end end end) end function BattlePassServer.prototype.createBattlePass(self, playerId, steamId, afterCreate) local request = CreateHTTPRequestScriptVM("POST", self.serverUrl .. "/battlepass") setApiHeadersLong(nil, request) request:SetHTTPRequestRawPostBody( "application/json", encodeApiBody(nil, { steam_id = steamId, level = 0, experience = 0, claimed_rewards = {}, has_premium = false, claimed_premium_rewards = {} }) ) request:Send(function(result) local player = PlayerResource:GetPlayer(playerId) if not player then if afterCreate then afterCreate(nil, false) end return end if result.StatusCode >= 200 and result.StatusCode < 300 then ____print(nil, "[BattlePassServer] Battle Pass создан на сервере для " .. steamId) self:rememberBattlePassSnapshot(playerId, 0, 0, false) CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_data", { level = 0, experience = 0, claimed_rewards = {}, has_premium = 0, claimed_premium_rewards = {} }) if afterCreate then afterCreate(nil, true) end else ____print( nil, "[BattlePassServer] Ошибка создания Battle Pass: " .. tostring(result.StatusCode) ) CustomGameEventManager:Send_ServerToPlayer( player, "battle_pass_data", {error = "Failed to create Battle Pass: " .. tostring(result.StatusCode)} ) if afterCreate then afterCreate(nil, false) end end end) end function BattlePassServer.prototype.claimReward(self, playerId, steamId, level, isPremium) if isPremium == nil then isPremium = false end local endpoint = isPremium and "claim-premium" or "claim" local request = CreateHTTPRequestScriptVM("POST", (((self.serverUrl .. "/battlepass/") .. steamId) .. "/") .. endpoint) setApiHeadersLong(nil, request) request:SetHTTPRequestRawPostBody( "application/json", json.encode({steam_id = steamId, level = level, is_premium = isPremium}) ) request:Send(function(result) local player = PlayerResource:GetPlayer(playerId) if not player then return end if result.StatusCode >= 200 and result.StatusCode < 300 then ____print( nil, (((("[BattlePassServer] Награда уровня " .. tostring(level)) .. " (premium: ") .. tostring(isPremium)) .. ") забрана для ") .. steamId ) local rCfg = self:getRewardForLevel(level, isPremium) local expF = 0 local expD = 0 if (rCfg and rCfg.type) == "free_currency" then expF = rCfg.amount or 0 end if (rCfg and rCfg.type) == "donate_currency" then expD = rCfg.amount or 0 end local ____temp_21 = self:parseCurrencyGrantedFromClaimResponse(tostring(result.Body)) local apiFree = ____temp_21.apiFree local apiDonate = ____temp_21.apiDonate local hasField = ____temp_21.hasField if hasField and (expF > 0 or expD > 0) then self:syncBattlePassShopCurrencyIfUnderGranted( playerId, apiFree, apiDonate, expF, expD, (("одиночный claim ур." .. tostring(level)) .. " prem=") .. tostring(isPremium) ) elseif not hasField and (expF > 0 or expD > 0) then rawPrintFn( nil, ((((("[BattlePassServer] В ответе claim нет currency_granted — начисляю осколки через /currency/give: free=" .. tostring(expF)) .. " donate=") .. tostring(expD)) .. " (ур.") .. tostring(level)) .. ")" ) self:grantShopCurrencyViaApi( playerId, expF, expD, ((("[BattlePassServer] Валюта БП (нет поля API): +" .. tostring(expF)) .. " free, +") .. tostring(expD)) .. " donate" ) end self:grantRewardInGame(playerId, level, isPremium) CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_claim_result", {success = true, level = level, is_premium = isPremium and 1 or 0}) else local errorMsg = "Error: " .. tostring(result.StatusCode) do pcall(function() local body = {json.decode(result.Body)} if body and body.error then errorMsg = body.error end end) end ____print(nil, "[BattlePassServer] Ошибка забора награды: " .. errorMsg) CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_claim_result", {success = false, level = level, error = errorMsg}) end end) end function BattlePassServer.battlePassFreeShardsPerLevel(self, level) return level * 250 end function BattlePassServer.battlePassDustForLevel(self, level) return level * 200 end function BattlePassServer.isDustLevel(self, level, isPremium) local list = isPremium and ____exports.BattlePassServer.BP_PREMIUM_DUST_LEVELS or ____exports.BattlePassServer.BP_FREE_DUST_LEVELS for ____, l in ipairs(list) do if l == level then return true end end return false end function BattlePassServer.isPackLevel(self, level, isPremium) local list = isPremium and ____exports.BattlePassServer.BP_PREMIUM_PACK_LEVELS or ____exports.BattlePassServer.BP_FREE_PACK_LEVELS for ____, l in ipairs(list) do if l == level then return true end end return false end function BattlePassServer.buildFreeRewardsConfig(self) local cfg = {} do local lvl = 1 while lvl <= 50 do if ____exports.BattlePassServer:isDustLevel(lvl, false) then cfg[lvl] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(lvl) } elseif ____exports.BattlePassServer:isPackLevel(lvl, false) then cfg[lvl] = {type = "arcade_pack_standard", amount = 1} else cfg[lvl] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(lvl) } end lvl = lvl + 1 end end return cfg end function BattlePassServer.prototype.getRewardForLevel(self, lvl, isPremium) if isPremium == nil then isPremium = false end local config = isPremium and ____exports.BattlePassServer.PREMIUM_REWARDS_CONFIG or ____exports.BattlePassServer.REWARDS_CONFIG local reward = config[lvl] if reward == nil or reward == nil then return nil end return reward end function BattlePassServer.prototype.findCurrencyGrantedInDecoded(self, decoded) if not decoded or type(decoded) ~= "table" then return nil end local candidates = {decoded} local z = decoded if z[0] and type(z[0]) == "table" then candidates[#candidates + 1] = z[0] end if z[1] and type(z[1]) == "table" then candidates[#candidates + 1] = z[1] end for ____, obj in ipairs(candidates) do local cg = obj.currency_granted if cg ~= nil and cg ~= nil and type(cg) == "table" then return { free = parseApiNumber(nil, cg.free_currency), donate = parseApiNumber(nil, cg.donate_currency) } end end return nil end function BattlePassServer.prototype.parseCurrencyGrantedFromClaimResponse(self, body) do local ____try, ____hasReturned, ____returnValue = pcall(function() local decoded = {json.decode(body)} local found = self:findCurrencyGrantedInDecoded(decoded) if found then return true, {apiFree = found.free, apiDonate = found.donate, hasField = true} end end) if ____try and ____hasReturned then return ____returnValue end end return {apiFree = 0, apiDonate = 0, hasField = false} end function BattlePassServer.prototype.sumExpectedShopCurrencyForLevels(self, freeLevels, premiumLevels) local free = 0 local donate = 0 for ____, lvl in ipairs(freeLevels) do local r = self:getRewardForLevel(lvl, false) if (r and r.type) == "free_currency" then free = free + (r.amount or 0) end end for ____, lvl in ipairs(premiumLevels) do local r = self:getRewardForLevel(lvl, true) if (r and r.type) == "free_currency" then free = free + (r.amount or 0) end if (r and r.type) == "donate_currency" then donate = donate + (r.amount or 0) end end return {free = free, donate = donate} end function BattlePassServer.prototype.syncBattlePassShopCurrencyIfUnderGranted(self, playerId, apiFree, apiDonate, expectedFree, expectedDonate, logCtx) local needFree = math.max(0, expectedFree - (apiFree or 0)) local needDonate = math.max(0, expectedDonate - (apiDonate or 0)) if needFree <= 0 and needDonate <= 0 then return false end rawPrintFn( nil, (((((((((((("[BattlePassServer] " .. logCtx) .. ": API free=") .. tostring(apiFree)) .. " donate=") .. tostring(apiDonate)) .. ", ожидалось free=") .. tostring(expectedFree)) .. " donate=") .. tostring(expectedDonate)) .. " → доначисление give +") .. tostring(needFree)) .. "/+") .. tostring(needDonate) ) self:grantShopCurrencyViaApi( playerId, needFree, needDonate, ((((("[BattlePassServer] Доначисление валюты БП (" .. logCtx) .. "): +") .. tostring(needFree)) .. " free, +") .. tostring(needDonate)) .. " donate" ) return true end function BattlePassServer.prototype.grantShopCurrencyViaApi(self, playerId, freeAmount, donateAmount, logSuccess, dustAmount, arcadePackStandard, arcadePackPremium) if dustAmount == nil then dustAmount = 0 end if arcadePackStandard == nil then arcadePackStandard = 0 end if arcadePackPremium == nil then arcadePackPremium = 0 end StoreManager:getInstance():grantShopExtrasViaApi(playerId, { freeAmount = freeAmount, donateAmount = donateAmount, dustAmount = dustAmount, arcadePackStandard = arcadePackStandard, arcadePackPremium = arcadePackPremium }, logSuccess) end function BattlePassServer.prototype.playerAlreadyOwnsChatWheelSound(self, playerId, soundId) local info = PlayerInfo:GetPlayerInfo(playerId) local sw = info and info.sounds_wheel if sw and sw[soundId] then return true end return StoreManager:getInstance():hasPurchasedItem(playerId, "chat_wheel_sound_" .. soundId) end function BattlePassServer.prototype.grantRewardInGame(self, playerId, level, isPremium) if isPremium == nil then isPremium = false end local reward = self:getRewardForLevel(level, isPremium) if not reward then ____print( nil, ((("[BattlePassServer] Уровень " .. tostring(level)) .. " (premium: ") .. tostring(isPremium)) .. ") не имеет награды" ) return end if not PlayerResource:IsValidPlayer(playerId) or not PlayerResource:GetPlayer(playerId) then ____print( nil, (("[BattlePassServer] Слот " .. tostring(playerId)) .. " не валиден — пропуск выдачи награды уровня ") .. tostring(level) ) return end local storeMgr = StoreManager:getInstance() repeat local ____switch272 = reward.type local cardId, cardStoreId, itemName, itemSteamId, purchaseRequest, effectId local ____cond272 = ____switch272 == "free_currency" or ____switch272 == "donate_currency" or ____switch272 == "dust_currency" or ____switch272 == "arcade_pack_standard" or ____switch272 == "arcade_pack_premium" if ____cond272 then ____print( nil, ((((("[BattlePassServer] Пропуск " .. reward.type) .. " уровня ") .. tostring(level)) .. " (premium=") .. tostring(isPremium)) .. ") — уже начислено на сервере при клейме" ) break end ____cond272 = ____cond272 or ____switch272 == "card" if ____cond272 then cardId = reward.card_id if cardId == nil or cardId == nil then ____print( nil, "[BattlePassServer] Ошибка: card_id не указан для карты на уровне " .. tostring(level) ) break end cardStoreId = "card_data_" .. tostring(cardId) if storeMgr:hasPurchasedCardById(playerId, cardId) then local comp = getBpDuplicateCompensationByStoreItemId(nil, cardStoreId) if comp.free_currency > 0 or comp.donate_currency > 0 then self:grantShopCurrencyViaApi( playerId, comp.free_currency, comp.donate_currency, ((((((("[BattlePassServer] Дубликат карты " .. cardStoreId) .. " (ур. ") .. tostring(level)) .. "): компенсация +") .. tostring(comp.free_currency)) .. " free, +") .. tostring(comp.donate_currency)) .. " donate" ) else ____print(nil, ("[BattlePassServer] Дубликат " .. cardStoreId) .. ", нет компенсации в battle_pass_duplicate_compensation.ts") end break end storeMgr:registerCardFromBattlePass(playerId, cardId) ____print( nil, (("[BattlePassServer] Карта " .. tostring(cardId)) .. " добавлена через StoreManager для игрока ") .. tostring(playerId) ) break end ____cond272 = ____cond272 or ____switch272 == "item" if ____cond272 then itemName = reward.item_name if not itemName then ____print( nil, "[BattlePassServer] Ошибка: item_name не указан для предмета на уровне " .. tostring(level) ) break end if storeMgr:hasPurchasedItem(playerId, itemName) then local comp = getBpDuplicateCompensationByStoreItemId(nil, itemName) if comp.free_currency > 0 or comp.donate_currency > 0 then self:grantShopCurrencyViaApi( playerId, comp.free_currency, comp.donate_currency, ((((((("[BattlePassServer] Дубликат предмета " .. itemName) .. " (ур. ") .. tostring(level)) .. "): компенсация +") .. tostring(comp.free_currency)) .. " free, +") .. tostring(comp.donate_currency)) .. " donate" ) else ____print(nil, ("[BattlePassServer] Дубликат " .. itemName) .. ", нет компенсации в battle_pass_duplicate_compensation.ts") end break end itemSteamId = tostring(PlayerResource:GetSteamAccountID(playerId)) purchaseRequest = CreateHTTPRequestScriptVM("POST", ((self.serverUrl .. "/player/") .. itemSteamId) .. "/purchases") setApiHeaders(nil, purchaseRequest) purchaseRequest:SetHTTPRequestRawPostBody( "application/json", json.encode({item_id = itemName, item_category = "items", card_id = nil}) ) purchaseRequest:Send(function(purchaseResult) if purchaseResult.StatusCode >= 200 and purchaseResult.StatusCode < 300 then ____print( nil, ((((("[BattlePassServer] Предмет " .. itemName) .. " добавлен в покупки игрока ") .. tostring(playerId)) .. " (Steam ID: ") .. itemSteamId) .. ")" ) else ____print( nil, "[BattlePassServer] Ошибка добавления предмета в покупки: " .. tostring(purchaseResult.StatusCode) ) ____print( nil, "[BattlePassServer] Тело ответа: " .. tostring(purchaseResult.Body) ) end end) break end ____cond272 = ____cond272 or ____switch272 == "effect" if ____cond272 then effectId = reward.effect_id if effectId then if storeMgr:hasPurchasedItem(playerId, effectId) then local comp = getBpDuplicateCompensationByStoreItemId(nil, effectId) if comp.free_currency > 0 or comp.donate_currency > 0 then self:grantShopCurrencyViaApi( playerId, comp.free_currency, comp.donate_currency, ((((((("[BattlePassServer] Дубликат эффекта " .. effectId) .. " (ур. ") .. tostring(level)) .. "): компенсация +") .. tostring(comp.free_currency)) .. " free, +") .. tostring(comp.donate_currency)) .. " donate" ) else ____print(nil, ("[BattlePassServer] Дубликат " .. effectId) .. ", нет компенсации в battle_pass_duplicate_compensation.ts") end break end local effectSteamId = tostring(PlayerResource:GetSteamAccountID(playerId)) local effectPurchaseRequest = CreateHTTPRequestScriptVM("POST", ((self.serverUrl .. "/player/") .. effectSteamId) .. "/purchases") setApiHeaders(nil, effectPurchaseRequest) effectPurchaseRequest:SetHTTPRequestRawPostBody( "application/json", json.encode({item_id = effectId, item_category = "effects", card_id = nil}) ) effectPurchaseRequest:Send(function(effectPurchaseResult) if effectPurchaseResult.StatusCode >= 200 and effectPurchaseResult.StatusCode < 300 then ____print( nil, ((((("[BattlePassServer] Эффект " .. effectId) .. " добавлен в покупки игрока ") .. tostring(playerId)) .. " (Steam ID: ") .. effectSteamId) .. ")" ) else ____print( nil, "[BattlePassServer] Ошибка добавления эффекта в покупки: " .. tostring(effectPurchaseResult.StatusCode) ) ____print( nil, "[BattlePassServer] Тело ответа: " .. tostring(effectPurchaseResult.Body) ) end end) end break end ____cond272 = ____cond272 or ____switch272 == "chat_wheel_sound" if ____cond272 then do local sid = reward.sound_id if not sid then ____print( nil, "[BattlePassServer] Ошибка: sound_id не указан для chat_wheel_sound на уровне " .. tostring(level) ) break end if self:playerAlreadyOwnsChatWheelSound(playerId, sid) then local cwItemId = "chat_wheel_sound_" .. sid local comp = getBpDuplicateCompensationByStoreItemId(nil, cwItemId) if comp.free_currency > 0 or comp.donate_currency > 0 then self:grantShopCurrencyViaApi( playerId, comp.free_currency, comp.donate_currency, ((((((("[BattlePassServer] Дубликат звука " .. cwItemId) .. " (ур. ") .. tostring(level)) .. "): компенсация +") .. tostring(comp.free_currency)) .. " free, +") .. tostring(comp.donate_currency)) .. " donate" ) else ____print(nil, ("[BattlePassServer] Дубликат " .. cwItemId) .. ", нет компенсации в battle_pass_duplicate_compensation.ts") end break end grantChatWheelSoundFromBattlePass(nil, playerId, sid) break end end until true end function BattlePassServer.prototype.claimAllRewards(self, playerId, steamId) local request = CreateHTTPRequestScriptVM("POST", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/claim-all") setApiHeadersLong(nil, request) request:SetHTTPRequestRawPostBody( "application/json", json.encode({steam_id = steamId}) ) request:Send(function(result) local player = PlayerResource:GetPlayer(playerId) if not player then return end if result.StatusCode >= 200 and result.StatusCode < 300 then do local function ____catch(e) ____print( nil, "[BattlePassServer] Ошибка парсинга claim-all: " .. tostring(e) ) CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_claim_result", {success = false, error = "Data processing error"}) end local ____try, ____hasReturned = pcall(function() local decoded = {json.decode(result.Body)} local responseData = self:pickDecodedJsonRoot(decoded, {"success", "free_levels", "premium_levels"}) local ____opt_result_30 if responseData ~= nil then ____opt_result_30 = responseData.free_levels end local flRaw = ____opt_result_30 local ____opt_result_33 if responseData ~= nil then ____opt_result_33 = responseData.premium_levels end local plRaw = ____opt_result_33 local freeLevels = __TS__ArrayIsArray(flRaw) and __TS__ArrayFilter( __TS__ArrayMap( flRaw, function(____, v) return parseApiNumber(nil, v) end ), function(____, n) return n >= 1 end ) or ({}) local premiumLevels = __TS__ArrayIsArray(plRaw) and __TS__ArrayFilter( __TS__ArrayMap( plRaw, function(____, v) return parseApiNumber(nil, v) end ), function(____, n) return n >= 1 end ) or ({}) ____print( nil, (((("[BattlePassServer] Забрано " .. tostring(#freeLevels)) .. " бесплатных + ") .. tostring(#premiumLevels)) .. " премиум наград для ") .. steamId ) local ____temp_34 = self:sumExpectedShopCurrencyForLevels(freeLevels, premiumLevels) local expF = ____temp_34.free local expD = ____temp_34.donate local ____temp_35 = self:parseCurrencyGrantedFromClaimResponse(tostring(result.Body)) local apiFree = ____temp_35.apiFree local apiDonate = ____temp_35.apiDonate local hasField = ____temp_35.hasField if hasField and (expF > 0 or expD > 0) then self:syncBattlePassShopCurrencyIfUnderGranted( playerId, apiFree, apiDonate, expF, expD, ((("claim-all (" .. tostring(#freeLevels)) .. "+") .. tostring(#premiumLevels)) .. " ур.)" ) elseif not hasField and (expF > 0 or expD > 0) then rawPrintFn( nil, (("[BattlePassServer] claim-all: нет currency_granted в ответе — give free=" .. tostring(expF)) .. " donate=") .. tostring(expD) ) self:grantShopCurrencyViaApi( playerId, expF, expD, ((("[BattlePassServer] Валюта БП claim-all (нет поля API): +" .. tostring(expF)) .. " free, +") .. tostring(expD)) .. " donate" ) end for ____, lvl in ipairs(freeLevels) do self:grantRewardInGame(playerId, lvl, false) end for ____, lvl in ipairs(premiumLevels) do self:grantRewardInGame(playerId, lvl, true) end local freeObject = {} __TS__ArrayForEach( freeLevels, function(____, lvl, idx) freeObject[tostring(idx)] = lvl end ) local premiumObject = {} __TS__ArrayForEach( premiumLevels, function(____, lvl, idx) premiumObject[tostring(idx)] = lvl end ) CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_claim_result", {success = true, claimed_levels = freeObject, claimed_premium_levels = premiumObject}) end) if not ____try then ____catch(____hasReturned) end end else local errorMsg = "Error: " .. tostring(result.StatusCode) do pcall(function() local body = {json.decode(result.Body)} if body and body.error then errorMsg = body.error end end) end ____print(nil, "[BattlePassServer] Ошибка забора всех наград: " .. errorMsg) CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_claim_result", {success = false, error = errorMsg}) end end) end function BattlePassServer.prototype.buyPremium(self, playerId, steamId) local request = CreateHTTPRequestScriptVM("POST", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/buy-premium") setApiHeadersLong(nil, request) request:SetHTTPRequestRawPostBody( "application/json", json.encode({steam_id = steamId, cost = 1000}) ) request:Send(function(result) local player = PlayerResource:GetPlayer(playerId) if not player then return end if result.StatusCode >= 200 and result.StatusCode < 300 then ____print(nil, "[BattlePassServer] Battle Pass Premium куплен для " .. steamId) CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_buy_result", {success = true}) self:loadBattlePassData(playerId, steamId) else local code = result.StatusCode local errorMsg = "Error: " .. tostring(code) local ____tostring_37 = tostring local ____result_Body_36 = result.Body if ____result_Body_36 == nil then ____result_Body_36 = "" end local rawBody = ____tostring_37(____result_Body_36) if rawBody ~= "" then do pcall(function() local decoded = {json.decode(rawBody)} local body = self:pickDecodedJsonRoot(decoded, {"error"}) if body and body.error ~= nil and body.error ~= nil and tostring(body.error) ~= "" then errorMsg = tostring(body.error) end end) end end ____print( nil, ((("[BattlePassServer] Ошибка покупки Premium: " .. errorMsg) .. " (HTTP ") .. tostring(code)) .. ")" ) CustomGameEventManager:Send_ServerToPlayer(player, "battle_pass_buy_result", {success = false, error = errorMsg}) end end) end function BattlePassServer.prototype.addExperience(self, playerId, amount) self:sendAddExpRequest(playerId, amount, false) end function BattlePassServer.prototype.sendAddExpRequest(self, playerId, amount, alreadyRetriedAfterCreate) local expAdd = math.max( 0, math.floor(amount) ) if expAdd <= 0 then return end local steamId = tostring(PlayerResource:GetSteamAccountID(playerId)) ____print( nil, ((((("[BattlePassServer] Добавляем " .. tostring(expAdd)) .. " опыта Battle Pass игроку ") .. tostring(playerId)) .. " (Steam ID: ") .. steamId) .. ")" ) local request = CreateHTTPRequestScriptVM("POST", ((self.serverUrl .. "/battlepass/") .. steamId) .. "/addexp") setApiHeadersLong(nil, request) request:SetHTTPRequestRawPostBody( "application/json", encodeApiBody(nil, {steam_id = steamId, experience = expAdd}) ) request:Send(function(result) if result.StatusCode >= 200 and result.StatusCode < 300 then do local function ____catch(e) ____print(nil, "[BattlePassServer] Опыт добавлен для " .. steamId) end local ____try, ____hasReturned = pcall(function() local responseData = {json.decode(result.Body)} local levelUp = responseData.level_up == true ____print( nil, (((((("[BattlePassServer] Опыт добавлен для " .. steamId) .. ". Новый уровень: ") .. tostring(responseData.level)) .. ", опыт: ") .. tostring(responseData.experience)) .. ", level_up: ") .. tostring(levelUp) ) if responseData.level ~= nil and responseData.experience ~= nil then self:rememberBattlePassSnapshot( playerId, parseApiNumber(nil, responseData.level), parseApiNumber(nil, responseData.experience) ) end end) if not ____try then ____catch(____hasReturned) end end self:loadBattlePassData(playerId, steamId) elseif result.StatusCode == 404 and not alreadyRetriedAfterCreate then ____print(nil, "[BattlePassServer] addexp 404 (нет battle_pass) — создаём запись и повторяем начисление") self:createBattlePass( playerId, steamId, function(____, ok) if ok then self:sendAddExpRequest(playerId, expAdd, true) else ____print(nil, "[BattlePassServer] Не удалось создать Battle Pass — опыт за матч не начислен") end end ) else local bodyStr = result.Body ~= nil and tostring(result.Body) or "" ____print( nil, (("[BattlePassServer] Ошибка добавления опыта: HTTP " .. tostring(result.StatusCode)) .. " body=") .. bodyStr ) end end) end BattlePassServer.REWARD_AMOUNTS = {DONATE_CURRENCY = { LEVEL_1 = 100, LEVEL_11 = 100, LEVEL_21 = 100, LEVEL_31 = 100, LEVEL_41 = 100 }, ITEM_AMOUNT = 1, EFFECT_AMOUNT = 1, CARD_AMOUNT = 1} BattlePassServer.BP_FREE_DUST_LEVELS = { 3, 8, 13, 18, 23, 28, 33, 38, 43, 48 } BattlePassServer.BP_FREE_PACK_LEVELS = { 10, 20, 30, 40, 50 } BattlePassServer.BP_PREMIUM_DUST_LEVELS = { 2, 6, 13, 16, 22, 26, 32, 36, 42, 47 } BattlePassServer.BP_PREMIUM_PACK_LEVELS = { 3, 9, 23, 33, 48 } BattlePassServer.REWARDS_CONFIG = ____exports.BattlePassServer:buildFreeRewardsConfig() BattlePassServer.PREMIUM_REWARDS_CONFIG = { [1] = {type = "donate_currency", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.DONATE_CURRENCY.LEVEL_1}, [2] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(2) }, [3] = {type = "arcade_pack_premium", amount = 1}, [4] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(4) }, [5] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(5) }, [6] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(6) }, [7] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(7) }, [8] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(8) }, [9] = {type = "arcade_pack_premium", amount = 1}, [10] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(10) }, [11] = {type = "donate_currency", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.DONATE_CURRENCY.LEVEL_11}, [12] = {type = "effect", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.EFFECT_AMOUNT, effect_id = "skin_effect_bp_diretide_emblem"}, [13] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(13) }, [14] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(14) }, [15] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(15) }, [16] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(16) }, [17] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(17) }, [18] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(18) }, [19] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(19) }, [20] = {type = "effect", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.EFFECT_AMOUNT, effect_id = "skin_effect_bp_diretide_emblem_v1"}, [21] = {type = "donate_currency", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.DONATE_CURRENCY.LEVEL_21}, [22] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(22) }, [23] = {type = "arcade_pack_premium", amount = 1}, [24] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(24) }, [25] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(25) }, [26] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(26) }, [27] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(27) }, [28] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(28) }, [29] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(29) }, [30] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(30) }, [31] = {type = "donate_currency", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.DONATE_CURRENCY.LEVEL_31}, [32] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(32) }, [33] = {type = "arcade_pack_premium", amount = 1}, [34] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(34) }, [35] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(35) }, [36] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(36) }, [37] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(37) }, [38] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(38) }, [39] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(39) }, [40] = {type = "item", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.ITEM_AMOUNT, item_name = "hero_spectre"}, [41] = {type = "donate_currency", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.DONATE_CURRENCY.LEVEL_41}, [42] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(42) }, [43] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(43) }, [44] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(44) }, [45] = {type = "effect", amount = ____exports.BattlePassServer.REWARD_AMOUNTS.EFFECT_AMOUNT, effect_id = "skin_effect_bp_diretide_emblem_v3"}, [46] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(46) }, [47] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(47) }, [48] = {type = "arcade_pack_premium", amount = 1}, [49] = { type = "dust_currency", amount = ____exports.BattlePassServer:battlePassDustForLevel(49) }, [50] = { type = "free_currency", amount = ____exports.BattlePassServer:battlePassFreeShardsPerLevel(50) } } if IsServer() then (function() Timers:CreateTimer( 2, function() do local function ____catch(____error) ____print( nil, "[BattlePassServer] Ошибка инициализации: " .. tostring(____error) ) return true, nil end local ____try, ____hasReturned, ____returnValue = pcall(function() local battlePassServer = ____exports.BattlePassServer:getInstance() ____print(nil, "[BattlePassServer] Успешно инициализирован") return true, nil end) if not ____try then ____hasReturned, ____returnValue = ____catch(____hasReturned) end if ____hasReturned then return ____returnValue end end end ) end)(nil) end return ____exports