Files
Dota-Zombie-Invasion/scripts/vscripts/death_sentence/contracts_backend.lua
T
achmad 72b73c4dd6 feat: replace CreateHTTPRequest with CreateHTTPRequestScriptVM
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>
2026-05-29 17:36:08 +07:00

213 lines
8.9 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
local ____lualib = require("lualib_bundle")
local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray
local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite
local ____exports = {}
local ____api_helper = require("api_helper")
local setApiHeaders = ____api_helper.setApiHeaders
local encodeApiBody = ____api_helper.encodeApiBody
local ____server_config = require("server_config")
local SERVER_CONFIG = ____server_config.SERVER_CONFIG
local ____contracts_registry = require("death_sentence.contracts_registry")
local normalizeDeathSentenceTitleIndex = ____contracts_registry.normalizeDeathSentenceTitleIndex
local normalizeDeathSentenceContractDurability = ____contracts_registry.normalizeDeathSentenceContractDurability
local normalizeDeathSentenceContractDurabilityMax = ____contracts_registry.normalizeDeathSentenceContractDurabilityMax
local getDeathSentenceComplicationPickCount = ____contracts_registry.getDeathSentenceComplicationPickCount
local LOG_PREFIX = "[DSContractsAPI]"
____exports.DEATH_SENTENCE_CONTRACT_ROSTER_CAP = 90
--- Старые записи (armor/movespeed/full_brutality) приводим к `none` — геймплей только через усложнения-абилки.
local function normalizeDeathSentenceTraitId(self, _v)
return "none"
end
local function isValidRarity(self, v)
return v == "common" or v == "rare" or v == "epic" or v == "legendary" or v == "mythic"
end
--- Разбор ответа GET / тела сохранённого JSON → массив экземпляров (0…CAP) или null при неверной структуре.
function ____exports.parseDeathSentenceContractRosterPayload(self, raw)
if not raw or type(raw) ~= "table" then
return nil
end
local root = raw
local wrap = root.death_sentence_contracts
if not wrap or type(wrap) ~= "table" then
return nil
end
local rosterRaw = wrap.roster
if not __TS__ArrayIsArray(rosterRaw) then
return nil
end
local out = {}
for ____, row in ipairs(rosterRaw) do
do
if not row or type(row) ~= "table" then
goto __continue8
end
local o = row
local instanceId = o.instanceId
local serialNum = type(o.serial) == "number" and o.serial or tonumber(o.serial)
if type(serialNum) ~= "number" or not __TS__NumberIsFinite(serialNum) then
goto __continue8
end
local rarity = o.rarity
local rmNum = type(o.rewardMultiplier) == "number" and o.rewardMultiplier or tonumber(o.rewardMultiplier)
if type(rmNum) ~= "number" or not __TS__NumberIsFinite(rmNum) then
goto __continue8
end
local compRaw = o.complicationIds
if type(instanceId) ~= "string" or #instanceId == 0 then
goto __continue8
end
if not isValidRarity(nil, rarity) then
goto __continue8
end
local complicationIds = {}
if __TS__ArrayIsArray(compRaw) then
for ____, c in ipairs(compRaw) do
if type(c) == "string" and #c > 0 then
complicationIds[#complicationIds + 1] = c
end
end
end
local maxComp = getDeathSentenceComplicationPickCount(nil, rarity)
while #complicationIds > maxComp do
table.remove(complicationIds)
end
local titleIndex = normalizeDeathSentenceTitleIndex(nil, o.titleIndex, instanceId)
local durability = normalizeDeathSentenceContractDurability(nil, o.durability, instanceId)
local ____o_durabilityMax_0 = o.durabilityMax
if ____o_durabilityMax_0 == nil then
____o_durabilityMax_0 = o.durability_max
end
local rawMax = ____o_durabilityMax_0
local durabilityMax = normalizeDeathSentenceContractDurabilityMax(nil, rawMax, durability, instanceId)
local inst = {
instanceId = instanceId,
serial = serialNum,
titleIndex = titleIndex,
rarity = rarity,
rewardMultiplier = rmNum,
traitId = normalizeDeathSentenceTraitId(nil, o.traitId),
complicationIds = complicationIds,
durability = durability,
durabilityMax = durabilityMax
}
local fav = o.favorite
if fav == true or fav == 1 then
inst.favorite = true
elseif fav == false or fav == 0 then
inst.favorite = false
end
local pin = o.pinned
if pin == true or pin == 1 then
inst.pinned = true
elseif pin == false or pin == 0 then
inst.pinned = false
end
out[#out + 1] = inst
end
::__continue8::
end
while #out > ____exports.DEATH_SENTENCE_CONTRACT_ROSTER_CAP do
table.remove(out)
end
return out
end
local function decodeJsonBody(self, body)
do
local function ____catch()
return true, nil
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
return true, {json.decode(body)}
end)
if not ____try then
____hasReturned, ____returnValue = ____catch()
end
if ____hasReturned then
return ____returnValue
end
end
end
--- Извлечь объект из ответа API (массив из одного элемента или объект).
local function unwrapApiJsonObject(self, decoded)
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
return decoded[1]
end
return decoded
end
--- Загрузить ростер с бэка для steam_id. В колбэке roster=null при ошибке / пусто / невалидно.
function ____exports.loadDeathSentenceContractsFromBackend(self, steamId, done)
local url = ((SERVER_CONFIG.API_URL .. "/player/") .. steamId) .. "/death_sentence_contracts"
local request = CreateHTTPRequestScriptVM("GET", url)
setApiHeaders(nil, request)
request:Send(function(result)
if result.StatusCode < 200 or result.StatusCode >= 300 then
print((((LOG_PREFIX .. " GET fail steam=") .. steamId) .. " code=") .. tostring(result.StatusCode))
done(nil, nil)
return
end
local bodyStr = result.Body ~= nil and tostring(result.Body) or ""
if #bodyStr == 0 then
done(nil, nil)
return
end
local decoded = decodeJsonBody(nil, bodyStr)
local obj = unwrapApiJsonObject(nil, decoded)
local roster = ____exports.parseDeathSentenceContractRosterPayload(nil, obj)
if roster == nil then
print((LOG_PREFIX .. " GET parse invalid steam=") .. steamId)
done(nil, nil)
return
end
print((((LOG_PREFIX .. " GET ok steam=") .. steamId) .. " count=") .. tostring(#roster))
done(nil, roster)
end)
end
--- Сохранить ростер на бэк для одного steam_id.
function ____exports.saveDeathSentenceContractsToBackend(self, steamId, roster, done)
local url = ((SERVER_CONFIG.API_URL .. "/player/") .. steamId) .. "/death_sentence_contracts"
local request = CreateHTTPRequestScriptVM("PUT", url)
setApiHeaders(nil, request)
local payload = {death_sentence_contracts = {roster = roster}}
request:SetHTTPRequestRawPostBody(
"application/json",
encodeApiBody(nil, payload)
)
request:Send(function(result)
local ok = result.StatusCode >= 200 and result.StatusCode < 300
if ok then
print((LOG_PREFIX .. " PUT ok steam=") .. steamId)
else
print((((LOG_PREFIX .. " PUT fail steam=") .. steamId) .. " code=") .. tostring(result.StatusCode))
end
if done then
done(nil, ok)
end
end)
end
--- Сохранить один и тот же ростер всем подключённым игрокам (одинаковая сетка лобби).
function ____exports.saveDeathSentenceContractsRosterToAllConnectedPlayers(self, roster)
do
local i = 0
while i < DOTA_MAX_PLAYERS do
do
local pid = i
if not PlayerResource:IsValidPlayerID(pid) then
goto __continue41
end
local steamId = PlayerResource:GetSteamAccountID(pid)
if not steamId or steamId == 0 then
goto __continue41
end
____exports.saveDeathSentenceContractsToBackend(
nil,
tostring(steamId),
roster
)
end
::__continue41::
i = i + 1
end
end
end
return ____exports