local ____lualib = require("lualib_bundle") local __TS__Class = ____lualib.__TS__Class local Map = ____lualib.Map local __TS__New = ____lualib.__TS__New local __TS__ArrayFind = ____lualib.__TS__ArrayFind local __TS__ArrayFindIndex = ____lualib.__TS__ArrayFindIndex local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray local __TS__ArrayMap = ____lualib.__TS__ArrayMap local __TS__NumberToFixed = ____lualib.__TS__NumberToFixed local __TS__Number = ____lualib.__TS__Number local __TS__ObjectValues = ____lualib.__TS__ObjectValues local ____exports = {} local ____api_helper = require("api_helper") local encodeApiBody = ____api_helper.encodeApiBody local setApiHeaders = ____api_helper.setApiHeaders local ____server_config = require("server_config") local SERVER_CONFIG = ____server_config.SERVER_CONFIG local MAX_CONTRACT_SLOTS = 4 local MAX_CONTRACTS_PER_PLAYER = 24 local CONTRACT_DEBUFF_POOL = { "Герои слабее на 20%", "Герои слабее на 35%", "Герои теряют 30% скорости атаки", "Герои получают +20% входящего урона", "Крипы получают +30% скорости передвижения", "Снижение лечения героев на 40%", "Кулдауны способностей героев +20%" } local CONTRACT_TITLE_POOL = { "Смертельный приговор", "Пакт отчаяния", "Кровавый кодекс", "Ночь без надежды", "Предел боли" } local function randomIntInclusive(self, min, max) return RandomInt(min, max) end local function debuffsArrayToMap(self, lines) local result = {} do local i = 0 while i < #lines do result[tostring(i)] = lines[i + 1] i = i + 1 end end return result end ____exports.ContractsManager = __TS__Class() local ContractsManager = ____exports.ContractsManager ContractsManager.name = "ContractsManager" ContractsManager.____file_path = "scripts/vscripts/contracts_manager.lua" function ContractsManager.prototype.____constructor(self) self.contractsByPlayer = __TS__New(Map) self.activeSlots = {} if not IsServer() then return end CustomGameEventManager:RegisterListener( "contracts_request_inventory", function(_source, payload) local playerId = payload.PlayerID if playerId == nil or playerId < 0 then return end self:loadContractsFromServer(playerId, true) end ) ListenToGameEvent( "player_connect_full", function(event) local playerId = event.PlayerID if playerId == nil or playerId < 0 then return end Timers:CreateTimer( 1, function() self:loadContractsFromServer(playerId, true) return nil end ) end, nil ) end function ContractsManager.getInstance(self) if not ____exports.ContractsManager.instance then ____exports.ContractsManager.instance = __TS__New(____exports.ContractsManager) end return ____exports.ContractsManager.instance end function ContractsManager.prototype.getActiveSlots(self) return self.activeSlots end function ContractsManager.prototype.clearActiveSlots(self) self.activeSlots = {} end function ContractsManager.prototype.getContractForPlayer(self, playerId, contractId) local list = self.contractsByPlayer:get(playerId) or ({}) return __TS__ArrayFind( list, function(____, it) return it.id == contractId end ) end function ContractsManager.prototype.proposePlayerContract(self, playerId, contractId) local contract = self:getContractForPlayer(playerId, contractId) if not contract then return false end local sameContractIdx = __TS__ArrayFindIndex( self.activeSlots, function(____, it) return it.contractId == contractId end ) if sameContractIdx >= 0 then self.activeSlots[sameContractIdx + 1] = { contractId = contract.id, ownerPlayerId = playerId, title = contract.title, statMultiplier = contract.statMultiplier, rewardBonusPct = contract.rewardBonusPct, debuffs = {unpack(contract.debuffs)} } return true end local existingByPlayerIdx = __TS__ArrayFindIndex( self.activeSlots, function(____, it) return it.ownerPlayerId == playerId end ) if existingByPlayerIdx >= 0 then self.activeSlots[existingByPlayerIdx + 1] = { contractId = contract.id, ownerPlayerId = playerId, title = contract.title, statMultiplier = contract.statMultiplier, rewardBonusPct = contract.rewardBonusPct, debuffs = {unpack(contract.debuffs)} } return true end if #self.activeSlots >= MAX_CONTRACT_SLOTS then return false end local ____self_activeSlots_0 = self.activeSlots ____self_activeSlots_0[#____self_activeSlots_0 + 1] = { contractId = contract.id, ownerPlayerId = playerId, title = contract.title, statMultiplier = contract.statMultiplier, rewardBonusPct = contract.rewardBonusPct, debuffs = {unpack(contract.debuffs)} } return true end function ContractsManager.prototype.getContractById(self, contractId) return __TS__ArrayFind( self.activeSlots, function(____, it) return it.contractId == contractId end ) end function ContractsManager.prototype.shouldGrantAfterWin(self, modeType, difficultyKey) return modeType == "contract" or difficultyKey == "impossible" end function ContractsManager.prototype.grantProgressiveContractsToWinners(self, nextTierBase) do local playerId = 0 while playerId < DOTA_MAX_TEAM_PLAYERS do do local pid = playerId if not PlayerResource:IsValidPlayerID(pid) or not PlayerResource:IsValidPlayer(pid) or PlayerResource:IsFakeClient(pid) then goto __continue29 end self:grantOneContract(pid, nextTierBase) end ::__continue29:: playerId = playerId + 1 end end end function ContractsManager.prototype.getHighestTierForPlayer(self, playerId) local list = self.contractsByPlayer:get(playerId) or ({}) local maxTier = 0 for ____, contract in ipairs(list) do if contract.tier > maxTier then maxTier = contract.tier end end return maxTier end function ContractsManager.prototype.grantOneContract(self, playerId, minTier) local nextTier = math.max(1, minTier) local generated = self:generateRandomContract(nextTier) local list = self.contractsByPlayer:get(playerId) or ({}) if #list >= MAX_CONTRACTS_PER_PLAYER then table.remove(list, 1) end list[#list + 1] = generated self.contractsByPlayer:set(playerId, list) self:persistContractsToServer(playerId) self:sendInventoryToPlayer(playerId) local steamId = PlayerResource:GetSteamAccountID(playerId) if not steamId then return end local req = CreateHTTPRequestScriptVM( "POST", ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/contracts/grant" ) setApiHeaders(nil, req) req:SetHTTPRequestRawPostBody( "application/json", encodeApiBody( nil, { contract = self:toApiContract(generated), min_tier = minTier } ) ) req:Send(function(_res) return nil end) end function ContractsManager.prototype.loadContractsFromServer(self, playerId, syncToClient) local steamId = PlayerResource:GetSteamAccountID(playerId) if not steamId then if syncToClient then self:sendInventoryToPlayer(playerId) end return end local request = CreateHTTPRequestScriptVM( "GET", ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/contracts" ) setApiHeaders(nil, request) request:Send(function(response) if response.StatusCode < 200 or response.StatusCode >= 300 then if syncToClient then self:sendInventoryToPlayer(playerId) end return end local raw = {json.decode(response.Body or "null")} local arr = __TS__ArrayIsArray(raw) and raw or (__TS__ArrayIsArray(raw and raw.contracts) and (raw and raw.contracts) or ({})) local parsed = {} for ____, item in ipairs(arr) do local contract = self:fromApiContract(item) if contract then parsed[#parsed + 1] = contract end end self.contractsByPlayer:set(playerId, parsed) if syncToClient then self:sendInventoryToPlayer(playerId) end end) end function ContractsManager.prototype.persistContractsToServer(self, playerId) local steamId = PlayerResource:GetSteamAccountID(playerId) if not steamId then return end local list = self.contractsByPlayer:get(playerId) or ({}) local request = CreateHTTPRequestScriptVM( "PUT", ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/contracts" ) setApiHeaders(nil, request) request:SetHTTPRequestRawPostBody( "application/json", encodeApiBody( nil, {contracts = __TS__ArrayMap( list, function(____, it) return self:toApiContract(it) end )} ) ) request:Send(function(_res) return nil end) end function ContractsManager.prototype.sendInventoryToPlayer(self, playerId) local player = PlayerResource:GetPlayer(playerId) if not player then return end local contracts = self.contractsByPlayer:get(playerId) or ({}) local payload = {} for ____, contract in ipairs(contracts) do payload[contract.id] = { id = contract.id, tier = contract.tier, title = contract.title, stat_multiplier = contract.statMultiplier, reward_bonus_pct = contract.rewardBonusPct, debuffs = debuffsArrayToMap(nil, contract.debuffs) } end CustomGameEventManager:Send_ServerToPlayer(player, "contracts_inventory_update", {contracts = payload}) end function ContractsManager.prototype.generateRandomContract(self, tier) local id = "contract_" .. DoUniqueString("cid") local titleBase = CONTRACT_TITLE_POOL[randomIntInclusive(nil, 0, #CONTRACT_TITLE_POOL - 1) + 1] local statMultiplier = __TS__Number(__TS__NumberToFixed( 4 + tier * 0.5 + RandomFloat(0, 1.2), 2 )) local rewardBonusPct = randomIntInclusive(nil, 10 + tier * 2, 30 + tier * 4) local debuffCount = randomIntInclusive(nil, 1, 3) local picked = {} do local i = 0 while i < debuffCount do picked[#picked + 1] = CONTRACT_DEBUFF_POOL[randomIntInclusive(nil, 0, #CONTRACT_DEBUFF_POOL - 1) + 1] i = i + 1 end end return { id = id, tier = tier, title = (titleBase .. " T") .. tostring(tier), statMultiplier = statMultiplier, rewardBonusPct = rewardBonusPct, debuffs = picked } end function ContractsManager.prototype.fromApiContract(self, item) local ____tostring_6 = tostring local ____item_id_5 = item.id if ____item_id_5 == nil then ____item_id_5 = "" end local id = ____tostring_6(____item_id_5) if id == "" then return nil end local ____tonumber_9 = tonumber local ____tostring_8 = tostring local ____item_tier_7 = item.tier if ____item_tier_7 == nil then ____item_tier_7 = "1" end local tier = ____tonumber_9(____tostring_8(____item_tier_7)) or 1 local ____tostring_11 = tostring local ____item_title_10 = item.title if ____item_title_10 == nil then ____item_title_10 = "Смертельный приговор" end local title = ____tostring_11(____item_title_10) local ____tonumber_15 = tonumber local ____tostring_14 = tostring local ____item_stat_multiplier_12 = item.stat_multiplier if ____item_stat_multiplier_12 == nil then ____item_stat_multiplier_12 = item.statMultiplier end local ____item_stat_multiplier_12_13 = ____item_stat_multiplier_12 if ____item_stat_multiplier_12_13 == nil then ____item_stat_multiplier_12_13 = "4" end local statMultiplier = ____tonumber_15(____tostring_14(____item_stat_multiplier_12_13)) or 4 local ____tonumber_19 = tonumber local ____tostring_18 = tostring local ____item_reward_bonus_pct_16 = item.reward_bonus_pct if ____item_reward_bonus_pct_16 == nil then ____item_reward_bonus_pct_16 = item.rewardBonusPct end local ____item_reward_bonus_pct_16_17 = ____item_reward_bonus_pct_16 if ____item_reward_bonus_pct_16_17 == nil then ____item_reward_bonus_pct_16_17 = "15" end local rewardBonusPct = ____tonumber_19(____tostring_18(____item_reward_bonus_pct_16_17)) or 15 local rawDebuffs = item.debuffs local debuffs = {} if __TS__ArrayIsArray(rawDebuffs) then for ____, value in ipairs(rawDebuffs) do debuffs[#debuffs + 1] = tostring(value) end elseif rawDebuffs and type(rawDebuffs) == "table" then for ____, value in ipairs(__TS__ObjectValues(rawDebuffs)) do debuffs[#debuffs + 1] = tostring(value) end end return { id = id, tier = tier, title = title, statMultiplier = statMultiplier, rewardBonusPct = rewardBonusPct, debuffs = debuffs } end function ContractsManager.prototype.toApiContract(self, contract) return { id = contract.id, tier = contract.tier, title = contract.title, stat_multiplier = contract.statMultiplier, reward_bonus_pct = contract.rewardBonusPct, debuffs = contract.debuffs } end ____exports.Contracts = ____exports.ContractsManager:getInstance() return ____exports