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

403 lines
14 KiB
Lua

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 = CreateHTTPRequest(
"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 = CreateHTTPRequest(
"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 = CreateHTTPRequest(
"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