initial commit
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
local ____lualib = require("lualib_bundle")
|
||||
local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray
|
||||
local ____exports = {}
|
||||
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 function API()
|
||||
return SERVER_CONFIG.API_URL
|
||||
end
|
||||
--- MVP: верхняя граница множителя с бэка (п.19 плана).
|
||||
____exports.CONTRACT_MULTIPLIER_MAX_MVP = 10
|
||||
function ____exports.requestIdDrop(self, sessionOrMatchKey, steamId, rollIndex)
|
||||
return (((("drop:" .. sessionOrMatchKey) .. ":") .. steamId) .. ":") .. tostring(rollIndex)
|
||||
end
|
||||
function ____exports.requestIdNominate(self, sessionId, steamId, contractInstanceId)
|
||||
return (((("nominate:" .. sessionId) .. ":") .. steamId) .. ":") .. contractInstanceId
|
||||
end
|
||||
function ____exports.requestIdVote(self, sessionId, voterSteamId, contractInstanceId)
|
||||
return (((("vote:" .. sessionId) .. ":") .. voterSteamId) .. ":") .. contractInstanceId
|
||||
end
|
||||
function ____exports.requestIdFinalize(self, sessionId)
|
||||
return "finalize:" .. sessionId
|
||||
end
|
||||
function ____exports.requestIdLinkMatch(self, sessionId, matchId)
|
||||
return (("link-match:" .. sessionId) .. ":") .. matchId
|
||||
end
|
||||
local function decodeJsonBody(self, result)
|
||||
if result.Body == nil or result.Body == nil or result.Body == "" then
|
||||
return nil
|
||||
end
|
||||
do
|
||||
local function ____catch()
|
||||
return true, nil
|
||||
end
|
||||
local ____try, ____hasReturned, ____returnValue = pcall(function()
|
||||
return true, {json.decode(result.Body)}
|
||||
end)
|
||||
if not ____try then
|
||||
____hasReturned, ____returnValue = ____catch()
|
||||
end
|
||||
if ____hasReturned then
|
||||
return ____returnValue
|
||||
end
|
||||
end
|
||||
end
|
||||
--- П.19: валидный множитель для применения в игре, иначе undefined.
|
||||
function ____exports.clampContractMultiplierFromBackend(self, raw)
|
||||
if type(raw) ~= "number" then
|
||||
return nil
|
||||
end
|
||||
local n = raw
|
||||
if n ~= n then
|
||||
return nil
|
||||
end
|
||||
if n == math.huge or n == -math.huge then
|
||||
return nil
|
||||
end
|
||||
if n < 1 or n > ____exports.CONTRACT_MULTIPLIER_MAX_MVP then
|
||||
return nil
|
||||
end
|
||||
return n
|
||||
end
|
||||
local function extractContractsArray(self, data)
|
||||
if not data then
|
||||
return {}
|
||||
end
|
||||
if __TS__ArrayIsArray(data) then
|
||||
return data
|
||||
end
|
||||
if data.contracts and __TS__ArrayIsArray(data.contracts) then
|
||||
return data.contracts
|
||||
end
|
||||
return {}
|
||||
end
|
||||
function ____exports.contractAdapterGetPlayerContracts(self, steamId, callback)
|
||||
local url = (API(nil) .. "/contracts/player/") .. steamId
|
||||
local req = CreateHTTPRequest("GET", url)
|
||||
setApiHeaders(nil, req)
|
||||
req:Send(function(result)
|
||||
if result.StatusCode >= 200 and result.StatusCode < 300 then
|
||||
local data = decodeJsonBody(nil, result)
|
||||
callback(
|
||||
nil,
|
||||
extractContractsArray(nil, data),
|
||||
false
|
||||
)
|
||||
return
|
||||
end
|
||||
print((("[contract_backend_adapter] GET player contracts HTTP " .. tostring(result.StatusCode)) .. " steam=") .. steamId)
|
||||
callback(nil, nil, true)
|
||||
end)
|
||||
end
|
||||
function ____exports.contractAdapterSaveDroppedContract(self, steamId, requestId, draft, callback)
|
||||
local url = API(nil) .. "/contracts/drop"
|
||||
local req = CreateHTTPRequest("POST", url)
|
||||
setApiHeadersLong(nil, req)
|
||||
req:SetHTTPRequestRawPostBody(
|
||||
"application/json",
|
||||
encodeApiBody(nil, {request_id = requestId, steam_id = steamId, contract = draft})
|
||||
)
|
||||
req:Send(function(result)
|
||||
if result.StatusCode < 200 or result.StatusCode >= 300 then
|
||||
print((((("[contract_backend_adapter] drop HTTP " .. tostring(result.StatusCode)) .. " steam=") .. steamId) .. " body=") .. tostring(result.Body))
|
||||
callback(nil, nil, false)
|
||||
return
|
||||
end
|
||||
local data = decodeJsonBody(nil, result)
|
||||
if data and data.ok == false then
|
||||
print("[contract_backend_adapter] drop ok=false steam=" .. steamId)
|
||||
callback(nil, nil, false)
|
||||
return
|
||||
end
|
||||
local c = data and data.contract
|
||||
if c and c.contract_instance_id then
|
||||
callback(nil, c, true)
|
||||
return
|
||||
end
|
||||
print("[contract_backend_adapter] drop missing contract in body steam=" .. steamId)
|
||||
callback(nil, nil, false)
|
||||
end)
|
||||
end
|
||||
function ____exports.contractAdapterNominate(self, sessionId, requestId, steamId, contractInstanceId, callback)
|
||||
local url = ((API(nil) .. "/contracts/session/") .. sessionId) .. "/nominate"
|
||||
local req = CreateHTTPRequest("POST", url)
|
||||
setApiHeaders(nil, req)
|
||||
req:SetHTTPRequestRawPostBody(
|
||||
"application/json",
|
||||
encodeApiBody(nil, {request_id = requestId, steam_id = steamId, contract_instance_id = contractInstanceId})
|
||||
)
|
||||
req:Send(function(result)
|
||||
callback(nil, result.StatusCode >= 200 and result.StatusCode < 300)
|
||||
end)
|
||||
end
|
||||
function ____exports.contractAdapterVote(self, sessionId, requestId, voterSteamId, contractInstanceId, callback)
|
||||
local url = ((API(nil) .. "/contracts/session/") .. sessionId) .. "/vote"
|
||||
local req = CreateHTTPRequest("POST", url)
|
||||
setApiHeaders(nil, req)
|
||||
req:SetHTTPRequestRawPostBody(
|
||||
"application/json",
|
||||
encodeApiBody(nil, {request_id = requestId, voter_steam_id = voterSteamId, contract_instance_id = contractInstanceId})
|
||||
)
|
||||
req:Send(function(result)
|
||||
callback(nil, result.StatusCode >= 200 and result.StatusCode < 300)
|
||||
end)
|
||||
end
|
||||
function ____exports.contractAdapterFinalizeContractVoting(self, sessionId, requestId, matchId, localWinnerContractInstanceId, candidatesSnapshot, votesSnapshot, callback)
|
||||
local url = ((API(nil) .. "/contracts/session/") .. sessionId) .. "/finalize"
|
||||
local req = CreateHTTPRequest("POST", url)
|
||||
setApiHeadersLong(nil, req)
|
||||
local body = {
|
||||
request_id = requestId,
|
||||
session_id = sessionId,
|
||||
match_id = matchId,
|
||||
local_winner_contract_instance_id = localWinnerContractInstanceId,
|
||||
candidates_snapshot = candidatesSnapshot,
|
||||
votes_snapshot = votesSnapshot
|
||||
}
|
||||
req:SetHTTPRequestRawPostBody(
|
||||
"application/json",
|
||||
encodeApiBody(nil, body)
|
||||
)
|
||||
req:Send(function(result)
|
||||
if result.StatusCode < 200 or result.StatusCode >= 300 then
|
||||
print((("[contract_backend_adapter] finalize HTTP " .. tostring(result.StatusCode)) .. " session=") .. sessionId)
|
||||
callback(nil, nil, false)
|
||||
return
|
||||
end
|
||||
local data = decodeJsonBody(nil, result)
|
||||
if not data or type(data.ok) ~= "boolean" then
|
||||
print("[contract_backend_adapter] finalize bad body session=" .. sessionId)
|
||||
callback(nil, nil, false)
|
||||
return
|
||||
end
|
||||
callback(nil, data, true)
|
||||
end)
|
||||
end
|
||||
function ____exports.contractAdapterLinkSessionToMatch(self, sessionId, requestId, matchId, callback)
|
||||
local url = ((API(nil) .. "/contracts/session/") .. sessionId) .. "/link-match"
|
||||
local req = CreateHTTPRequest("POST", url)
|
||||
setApiHeaders(nil, req)
|
||||
req:SetHTTPRequestRawPostBody(
|
||||
"application/json",
|
||||
encodeApiBody(nil, {request_id = requestId, match_id = matchId})
|
||||
)
|
||||
req:Send(function(result)
|
||||
callback(nil, result.StatusCode >= 200 and result.StatusCode < 300)
|
||||
end)
|
||||
end
|
||||
return ____exports
|
||||
@@ -0,0 +1,29 @@
|
||||
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
|
||||
local ____exports = {}
|
||||
--- Веса тира после победы на Impossible. Tier 0 = без дропа.
|
||||
____exports.CONTRACT_DROP_TIERS = {
|
||||
{0, 15},
|
||||
{1, 55},
|
||||
{2, 20},
|
||||
{3, 7},
|
||||
{4, 3}
|
||||
}
|
||||
function ____exports.rollContractDropTier(self)
|
||||
local sum = 0
|
||||
for ____, ____value in ipairs(____exports.CONTRACT_DROP_TIERS) do
|
||||
local w = ____value[2]
|
||||
sum = sum + w
|
||||
end
|
||||
local r = RandomInt(1, sum)
|
||||
local acc = 0
|
||||
for ____, ____value in ipairs(____exports.CONTRACT_DROP_TIERS) do
|
||||
local tier = ____value[1]
|
||||
local w = ____value[2]
|
||||
acc = acc + w
|
||||
if r <= acc then
|
||||
return tier
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
return ____exports
|
||||
@@ -0,0 +1,34 @@
|
||||
local ____lualib = require("lualib_bundle")
|
||||
local __TS__NumberToFixed = ____lualib.__TS__NumberToFixed
|
||||
local ____exports = {}
|
||||
local function isoLikeTimestamp(self)
|
||||
local ms = math.floor(GameRules:GetGameTime() * 1000)
|
||||
return (("game_" .. tostring(ms)) .. "_") .. tostring(RandomInt(10000, 99999))
|
||||
end
|
||||
local function buildModifiersForTier(self, tier)
|
||||
local mult = 1 + tier * 0.08
|
||||
return {{modifier_id = "contract_strain", title = "Напряжение", description = "Дополнительный множатель сложности от контракта.", value = mult}}
|
||||
end
|
||||
--- Черновик до SaveDroppedContract; финальный id — с бэкенда.
|
||||
function ____exports.generateContractDraft(self, ownerSteamId, tier, draftInstanceId)
|
||||
local seed = (draftInstanceId .. "_") .. tostring(RandomInt(1, 2147483647))
|
||||
local difficulty_multiplier = 1 + tier * 0.1
|
||||
local name = tier <= 1 and "Пакт выжившего" or (tier == 2 and "Условие крови" or (tier == 3 and "Клятва бездны" or "Смертельный завет"))
|
||||
local description = ((("Контракт " .. tostring(tier)) .. " тира. Дополнительно ×") .. __TS__NumberToFixed(difficulty_multiplier, 2)) .. " к масштабу врагов (поверх сложности)."
|
||||
return {
|
||||
contract_instance_id = draftInstanceId,
|
||||
owner_steam_id = ownerSteamId,
|
||||
tier = tier,
|
||||
name = name,
|
||||
description = description,
|
||||
seed = seed,
|
||||
difficulty_multiplier = difficulty_multiplier,
|
||||
modifiers = buildModifiersForTier(nil, tier),
|
||||
durability = 3,
|
||||
is_broken = false,
|
||||
source = "impossible_win_drop",
|
||||
created_at = isoLikeTimestamp(nil),
|
||||
application_conditions = "Активен только при подтверждённом контракте матча и сложности Impossible."
|
||||
}
|
||||
end
|
||||
return ____exports
|
||||
@@ -0,0 +1,541 @@
|
||||
local ____lualib = require("lualib_bundle")
|
||||
local __TS__Class = ____lualib.__TS__Class
|
||||
local Map = ____lualib.Map
|
||||
local __TS__New = ____lualib.__TS__New
|
||||
local __TS__ArrayMap = ____lualib.__TS__ArrayMap
|
||||
local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys
|
||||
local __TS__ArrayFind = ____lualib.__TS__ArrayFind
|
||||
local __TS__ArraySome = ____lualib.__TS__ArraySome
|
||||
local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter
|
||||
local ____exports = {}
|
||||
local ____contract_steam_compare = require("contracts.contract_steam_compare")
|
||||
local compareSteamIdDecimalString = ____contract_steam_compare.compareSteamIdDecimalString
|
||||
local ____contract_backend_adapter = require("contracts.contract_backend_adapter")
|
||||
local clampContractMultiplierFromBackend = ____contract_backend_adapter.clampContractMultiplierFromBackend
|
||||
local contractAdapterFinalizeContractVoting = ____contract_backend_adapter.contractAdapterFinalizeContractVoting
|
||||
local contractAdapterGetPlayerContracts = ____contract_backend_adapter.contractAdapterGetPlayerContracts
|
||||
local contractAdapterLinkSessionToMatch = ____contract_backend_adapter.contractAdapterLinkSessionToMatch
|
||||
local contractAdapterNominate = ____contract_backend_adapter.contractAdapterNominate
|
||||
local contractAdapterSaveDroppedContract = ____contract_backend_adapter.contractAdapterSaveDroppedContract
|
||||
local contractAdapterVote = ____contract_backend_adapter.contractAdapterVote
|
||||
local requestIdDrop = ____contract_backend_adapter.requestIdDrop
|
||||
local requestIdFinalize = ____contract_backend_adapter.requestIdFinalize
|
||||
local requestIdLinkMatch = ____contract_backend_adapter.requestIdLinkMatch
|
||||
local requestIdNominate = ____contract_backend_adapter.requestIdNominate
|
||||
local requestIdVote = ____contract_backend_adapter.requestIdVote
|
||||
local ____contract_drop_config = require("contracts.contract_drop_config")
|
||||
local rollContractDropTier = ____contract_drop_config.rollContractDropTier
|
||||
local ____contract_generator = require("contracts.contract_generator")
|
||||
local generateContractDraft = ____contract_generator.generateContractDraft
|
||||
local NET_TABLE = "contract_match"
|
||||
local NET_KEY = "state"
|
||||
local FINALIZE_TIMEOUT_SEC = 10
|
||||
____exports.ContractMatchManager = __TS__Class()
|
||||
local ContractMatchManager = ____exports.ContractMatchManager
|
||||
ContractMatchManager.name = "ContractMatchManager"
|
||||
ContractMatchManager.____file_path = "scripts/vscripts/contracts/contract_match_manager.lua"
|
||||
function ContractMatchManager.prototype.____constructor(self)
|
||||
self.listenersRegistered = false
|
||||
self.inventoryBySteam = __TS__New(Map)
|
||||
self.candidates = {}
|
||||
self.votesByVoterSteam = {}
|
||||
self.nominateOrderSeq = 0
|
||||
self.publicState = {
|
||||
contract_session_id = "",
|
||||
phase = "voting",
|
||||
candidates = {},
|
||||
vote_counts = {},
|
||||
active_contract = nil,
|
||||
contract_multiplier = 1
|
||||
}
|
||||
self.confirmedActiveContract = nil
|
||||
self.confirmedMultiplier = 1
|
||||
self.finalizeResolved = false
|
||||
self.finalizeAttemptSerial = 0
|
||||
self.activeFinalizeAttempt = 0
|
||||
self.linkMatchDone = false
|
||||
self.dropRollIndex = 0
|
||||
end
|
||||
function ContractMatchManager.getInstance(self)
|
||||
if not ____exports.ContractMatchManager.instance then
|
||||
____exports.ContractMatchManager.instance = __TS__New(____exports.ContractMatchManager)
|
||||
end
|
||||
return ____exports.ContractMatchManager.instance
|
||||
end
|
||||
function ContractMatchManager.prototype.init(self)
|
||||
if self.listenersRegistered or type(CustomGameEventManager) == "nil" then
|
||||
return
|
||||
end
|
||||
CustomGameEventManager:RegisterListener(
|
||||
"invasion_contracts_request",
|
||||
function(_src, data)
|
||||
local pid = data.PlayerID
|
||||
self:onContractsRequest(pid)
|
||||
end
|
||||
)
|
||||
CustomGameEventManager:RegisterListener(
|
||||
"invasion_contract_nominate",
|
||||
function(_src, data)
|
||||
local pid = data.PlayerID
|
||||
local ____tostring_1 = tostring
|
||||
local ____data_contract_instance_id_0 = data.contract_instance_id
|
||||
if ____data_contract_instance_id_0 == nil then
|
||||
____data_contract_instance_id_0 = ""
|
||||
end
|
||||
local cid = ____tostring_1(____data_contract_instance_id_0)
|
||||
self:onNominate(pid, cid)
|
||||
end
|
||||
)
|
||||
CustomGameEventManager:RegisterListener(
|
||||
"invasion_contract_vote",
|
||||
function(_src, data)
|
||||
local pid = data.PlayerID
|
||||
local ____tostring_3 = tostring
|
||||
local ____data_contract_instance_id_2 = data.contract_instance_id
|
||||
if ____data_contract_instance_id_2 == nil then
|
||||
____data_contract_instance_id_2 = ""
|
||||
end
|
||||
local cid = ____tostring_3(____data_contract_instance_id_2)
|
||||
self:onVote(pid, cid)
|
||||
end
|
||||
)
|
||||
self.listenersRegistered = true
|
||||
self:syncNetTable()
|
||||
end
|
||||
function ContractMatchManager.prototype.getConfirmedContractMultiplier(self)
|
||||
return self.confirmedMultiplier
|
||||
end
|
||||
function ContractMatchManager.prototype.getConfirmedActiveContract(self)
|
||||
return self.confirmedActiveContract
|
||||
end
|
||||
function ContractMatchManager.prototype.tryLinkSessionToBackendMatch(self, backendMatchId)
|
||||
local sid = self:getOrCreateSessionId()
|
||||
local mid = backendMatchId
|
||||
if not mid or self.linkMatchDone then
|
||||
return
|
||||
end
|
||||
local rid = requestIdLinkMatch(nil, sid, mid)
|
||||
contractAdapterLinkSessionToMatch(
|
||||
nil,
|
||||
sid,
|
||||
rid,
|
||||
mid,
|
||||
function(____, ok)
|
||||
if ok then
|
||||
self.linkMatchDone = true
|
||||
print((("[ContractMatchManager] link-match ok session=" .. sid) .. " match=") .. mid)
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
function ContractMatchManager.prototype.beginFinalizeForDifficulty(self, leader, backendMatchId, onWorldApplyReady)
|
||||
if leader ~= "impossible" then
|
||||
self.confirmedActiveContract = nil
|
||||
self.confirmedMultiplier = 1
|
||||
self.publicState.phase = "locked_without_contract"
|
||||
self.publicState.active_contract = nil
|
||||
self.publicState.contract_multiplier = 1
|
||||
self.publicState.public_error_code = nil
|
||||
self:syncNetTable()
|
||||
onWorldApplyReady(nil)
|
||||
return
|
||||
end
|
||||
self.finalizeAttemptSerial = self.finalizeAttemptSerial + 1
|
||||
local attempt = self.finalizeAttemptSerial
|
||||
self.activeFinalizeAttempt = attempt
|
||||
self.finalizeResolved = false
|
||||
self.publicState.phase = "finalizing"
|
||||
self.publicState.public_error_code = nil
|
||||
self:syncNetTable()
|
||||
Timers:CreateTimer(
|
||||
FINALIZE_TIMEOUT_SEC,
|
||||
function()
|
||||
if self.finalizeResolved then
|
||||
return nil
|
||||
end
|
||||
if self.activeFinalizeAttempt ~= attempt then
|
||||
return nil
|
||||
end
|
||||
if self.publicState.phase ~= "finalizing" then
|
||||
return nil
|
||||
end
|
||||
print("[ContractMatchManager] finalize timeout attempt=" .. tostring(attempt))
|
||||
self:applyFinalizeFailure(attempt, "contracts_finalize_failed", onWorldApplyReady)
|
||||
return nil
|
||||
end
|
||||
)
|
||||
local sid = self:getOrCreateSessionId()
|
||||
local matchId = backendMatchId or nil
|
||||
local localWinner = self:computeLocalWinnerContractId()
|
||||
local candSnap = __TS__ArrayMap(
|
||||
self.candidates,
|
||||
function(____, c) return {contract_instance_id = c.contract_instance_id, owner_steam_id = c.owner_steam_id, nominated_at = c.nominated_at, nominated_order = c.nominated_order} end
|
||||
)
|
||||
local voteSnap = {}
|
||||
for ____, voter in ipairs(__TS__ObjectKeys(self.votesByVoterSteam)) do
|
||||
local cid = self.votesByVoterSteam[voter]
|
||||
if cid ~= nil and cid ~= "" then
|
||||
voteSnap[#voteSnap + 1] = {voter_steam_id = voter, contract_instance_id = cid}
|
||||
end
|
||||
end
|
||||
local rid = requestIdFinalize(nil, sid)
|
||||
contractAdapterFinalizeContractVoting(
|
||||
nil,
|
||||
sid,
|
||||
rid,
|
||||
matchId,
|
||||
localWinner,
|
||||
candSnap,
|
||||
voteSnap,
|
||||
function(____, resp, httpOk)
|
||||
if self.finalizeResolved then
|
||||
print("[ContractMatchManager] finalize HTTP late ignored attempt=" .. tostring(attempt))
|
||||
return
|
||||
end
|
||||
if self.activeFinalizeAttempt ~= attempt then
|
||||
print("[ContractMatchManager] finalize HTTP stale attempt=" .. tostring(attempt))
|
||||
return
|
||||
end
|
||||
if self.publicState.phase ~= "finalizing" then
|
||||
print("[ContractMatchManager] finalize HTTP wrong phase")
|
||||
return
|
||||
end
|
||||
if not httpOk or not resp or not resp.ok then
|
||||
print("[ContractMatchManager] finalize HTTP/body fail attempt=" .. tostring(attempt))
|
||||
self:applyFinalizeFailure(attempt, "contracts_finalize_failed", onWorldApplyReady)
|
||||
return
|
||||
end
|
||||
local mult = clampContractMultiplierFromBackend(nil, resp.contract_multiplier)
|
||||
if mult == nil then
|
||||
print("[ContractMatchManager] finalize invalid multiplier raw=" .. tostring(resp.contract_multiplier))
|
||||
self:applyFinalizeFailure(attempt, "contracts_finalize_failed", onWorldApplyReady)
|
||||
return
|
||||
end
|
||||
self.finalizeResolved = true
|
||||
self.confirmedMultiplier = mult
|
||||
self.confirmedActiveContract = resp.active_contract or nil
|
||||
self.publicState.active_contract = self.confirmedActiveContract
|
||||
self.publicState.contract_multiplier = mult
|
||||
self.publicState.phase = self.confirmedActiveContract and "locked" or "locked_without_contract"
|
||||
self.publicState.public_error_code = nil
|
||||
self:syncNetTable()
|
||||
onWorldApplyReady(nil)
|
||||
end
|
||||
)
|
||||
end
|
||||
function ContractMatchManager.prototype.processImpossibleVictoryDrops(self, allowPersistedDrop)
|
||||
if not allowPersistedDrop then
|
||||
print("[ContractMatchManager] drop skipped: match end rewards / stats blocked")
|
||||
return
|
||||
end
|
||||
do
|
||||
local i = 0
|
||||
while i < DOTA_MAX_PLAYERS do
|
||||
do
|
||||
local pid = i
|
||||
if not PlayerResource:IsValidPlayerID(pid) or not PlayerResource:IsValidPlayer(pid) or PlayerResource:IsFakeClient(pid) then
|
||||
goto __continue34
|
||||
end
|
||||
local steam = tostring(PlayerResource:GetSteamAccountID(pid))
|
||||
local tier = rollContractDropTier(nil)
|
||||
if tier <= 0 then
|
||||
goto __continue34
|
||||
end
|
||||
self.dropRollIndex = self.dropRollIndex + 1
|
||||
local draftId = (((("draft_" .. self:getOrCreateSessionId()) .. "_") .. steam) .. "_") .. tostring(self.dropRollIndex)
|
||||
local draft = generateContractDraft(nil, steam, tier, draftId)
|
||||
local rid = requestIdDrop(
|
||||
nil,
|
||||
self:getOrCreateSessionId(),
|
||||
steam,
|
||||
self.dropRollIndex
|
||||
)
|
||||
contractAdapterSaveDroppedContract(
|
||||
nil,
|
||||
steam,
|
||||
rid,
|
||||
draft,
|
||||
function(____, canonical, ok)
|
||||
if not ok or not canonical then
|
||||
print("[ContractMatchManager] drop save failed steam=" .. steam)
|
||||
return
|
||||
end
|
||||
if type(CustomGameEventManager) ~= "nil" then
|
||||
local pl = PlayerResource:GetPlayer(pid)
|
||||
if pl then
|
||||
CustomGameEventManager:Send_ServerToPlayer(pl, "invasion_contract_drop_result", {ok = true, contract = canonical})
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
::__continue34::
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
function ContractMatchManager.prototype.getOrCreateSessionId(self)
|
||||
if not self.sessionId then
|
||||
local t = math.floor(GameRules:GetGameTime() * 1000)
|
||||
self.sessionId = (("cs_" .. tostring(t)) .. "_") .. tostring(RandomInt(100000, 999999))
|
||||
self.publicState.contract_session_id = self.sessionId
|
||||
end
|
||||
return self.sessionId
|
||||
end
|
||||
function ContractMatchManager.prototype.onContractsRequest(self, playerId)
|
||||
if not PlayerResource:IsValidPlayerID(playerId) or not PlayerResource:IsValidPlayer(playerId) then
|
||||
return
|
||||
end
|
||||
local steam = tostring(PlayerResource:GetSteamAccountID(playerId))
|
||||
contractAdapterGetPlayerContracts(
|
||||
nil,
|
||||
steam,
|
||||
function(____, list, err)
|
||||
if list == nil then
|
||||
self:sendInventory(playerId, nil, "contracts_load_failed", true)
|
||||
return
|
||||
end
|
||||
self.inventoryBySteam:set(steam, list)
|
||||
self:sendInventory(playerId, list, nil, true)
|
||||
end
|
||||
)
|
||||
end
|
||||
function ContractMatchManager.prototype.sendInventory(self, playerId, contracts, errorCode, done)
|
||||
if type(CustomGameEventManager) == "nil" then
|
||||
return
|
||||
end
|
||||
local payload = {contracts = contracts, loading_done = done}
|
||||
if errorCode then
|
||||
payload.error_code = errorCode
|
||||
end
|
||||
local pl = PlayerResource:GetPlayer(playerId)
|
||||
if pl then
|
||||
CustomGameEventManager:Send_ServerToPlayer(pl, "invasion_contracts_inventory", payload)
|
||||
end
|
||||
end
|
||||
function ContractMatchManager.prototype.onNominate(self, playerId, contractInstanceId)
|
||||
if self.publicState.phase ~= "voting" then
|
||||
print("[ContractMatchManager] nominate rejected phase=" .. self.publicState.phase)
|
||||
return
|
||||
end
|
||||
if not contractInstanceId or #contractInstanceId == 0 then
|
||||
return
|
||||
end
|
||||
if not PlayerResource:IsValidPlayerID(playerId) or not PlayerResource:IsValidPlayer(playerId) then
|
||||
return
|
||||
end
|
||||
local steam = tostring(PlayerResource:GetSteamAccountID(playerId))
|
||||
local inv = self.inventoryBySteam:get(steam)
|
||||
local ____opt_4 = inv
|
||||
local found = ____opt_4 and __TS__ArrayFind(
|
||||
inv,
|
||||
function(____, c) return c.contract_instance_id == contractInstanceId end
|
||||
)
|
||||
if not found or found.is_broken or found.owner_steam_id ~= steam then
|
||||
self:syncNetTableErrorFlash("contracts_invalid_contract")
|
||||
return
|
||||
end
|
||||
local sid = self:getOrCreateSessionId()
|
||||
local rid = requestIdNominate(nil, sid, steam, contractInstanceId)
|
||||
contractAdapterNominate(
|
||||
nil,
|
||||
sid,
|
||||
rid,
|
||||
steam,
|
||||
contractInstanceId,
|
||||
function(____, ok)
|
||||
if not ok then
|
||||
print((("[ContractMatchManager] nominate HTTP fail steam=" .. steam) .. " id=") .. contractInstanceId)
|
||||
self:syncNetTableErrorFlash("contracts_invalid_contract")
|
||||
return
|
||||
end
|
||||
self:removeCandidateByOwner(steam)
|
||||
self.nominateOrderSeq = self.nominateOrderSeq + 1
|
||||
local pub = {
|
||||
contract_instance_id = contractInstanceId,
|
||||
owner_steam_id = steam,
|
||||
nominated_at = GameRules:GetGameTime(),
|
||||
nominated_order = self.nominateOrderSeq,
|
||||
name = found.name,
|
||||
tier = found.tier
|
||||
}
|
||||
local ____self_candidates_6 = self.candidates
|
||||
____self_candidates_6[#____self_candidates_6 + 1] = pub
|
||||
self.votesByVoterSteam[steam] = contractInstanceId
|
||||
self:rebuildVoteCounts()
|
||||
self:syncNetTable()
|
||||
end
|
||||
)
|
||||
end
|
||||
function ContractMatchManager.prototype.onVote(self, playerId, contractInstanceId)
|
||||
if self.publicState.phase ~= "voting" then
|
||||
print("[ContractMatchManager] vote rejected phase=" .. self.publicState.phase)
|
||||
return
|
||||
end
|
||||
if not contractInstanceId then
|
||||
return
|
||||
end
|
||||
if not PlayerResource:IsValidPlayerID(playerId) or not PlayerResource:IsValidPlayer(playerId) then
|
||||
return
|
||||
end
|
||||
local voter = tostring(PlayerResource:GetSteamAccountID(playerId))
|
||||
local exists = __TS__ArraySome(
|
||||
self.candidates,
|
||||
function(____, c) return c.contract_instance_id == contractInstanceId end
|
||||
)
|
||||
if not exists then
|
||||
self:syncNetTableErrorFlash("contracts_vote_rejected")
|
||||
return
|
||||
end
|
||||
local sid = self:getOrCreateSessionId()
|
||||
local rid = requestIdVote(nil, sid, voter, contractInstanceId)
|
||||
contractAdapterVote(
|
||||
nil,
|
||||
sid,
|
||||
rid,
|
||||
voter,
|
||||
contractInstanceId,
|
||||
function(____, ok)
|
||||
if not ok then
|
||||
print("[ContractMatchManager] vote HTTP fail voter=" .. voter)
|
||||
self:syncNetTableErrorFlash("contracts_vote_rejected")
|
||||
return
|
||||
end
|
||||
self.votesByVoterSteam[voter] = contractInstanceId
|
||||
self:rebuildVoteCounts()
|
||||
self:syncNetTable()
|
||||
end
|
||||
)
|
||||
end
|
||||
function ContractMatchManager.prototype.removeCandidateByOwner(self, steam)
|
||||
self.candidates = __TS__ArrayFilter(
|
||||
self.candidates,
|
||||
function(____, c) return c.owner_steam_id ~= steam end
|
||||
)
|
||||
end
|
||||
function ContractMatchManager.prototype.rebuildVoteCounts(self)
|
||||
local counts = {}
|
||||
for ____, voter in ipairs(__TS__ObjectKeys(self.votesByVoterSteam)) do
|
||||
local cid = self.votesByVoterSteam[voter]
|
||||
if cid ~= nil and cid ~= "" then
|
||||
counts[cid] = (counts[cid] or 0) + 1
|
||||
end
|
||||
end
|
||||
self.publicState.vote_counts = counts
|
||||
self.publicState.candidates = self.candidates
|
||||
end
|
||||
function ContractMatchManager.prototype.computeLocalWinnerContractId(self)
|
||||
if #self.candidates == 0 then
|
||||
return nil
|
||||
end
|
||||
local counts = {}
|
||||
for ____, voter in ipairs(__TS__ObjectKeys(self.votesByVoterSteam)) do
|
||||
local cid = self.votesByVoterSteam[voter]
|
||||
if cid ~= nil and cid ~= "" then
|
||||
counts[cid] = (counts[cid] or 0) + 1
|
||||
end
|
||||
end
|
||||
local totalVotes = 0
|
||||
for ____, k in ipairs(__TS__ObjectKeys(counts)) do
|
||||
totalVotes = totalVotes + (counts[k] or 0)
|
||||
end
|
||||
if totalVotes == 0 then
|
||||
return nil
|
||||
end
|
||||
local bestIds = {}
|
||||
local bestVotes = -1
|
||||
for ____, cid in ipairs(__TS__ObjectKeys(counts)) do
|
||||
local n = counts[cid] or 0
|
||||
if n > bestVotes then
|
||||
bestVotes = n
|
||||
bestIds = {cid}
|
||||
elseif n == bestVotes then
|
||||
bestIds[#bestIds + 1] = cid
|
||||
end
|
||||
end
|
||||
if #bestIds == 1 then
|
||||
return bestIds[1]
|
||||
end
|
||||
return self:pickTieBreakContractId(bestIds)
|
||||
end
|
||||
function ContractMatchManager.prototype.pickTieBreakContractId(self, ids)
|
||||
local byId = {}
|
||||
for ____, c in ipairs(self.candidates) do
|
||||
byId[c.contract_instance_id] = c
|
||||
end
|
||||
local best = ids[1]
|
||||
do
|
||||
local i = 1
|
||||
while i < #ids do
|
||||
local cur = ids[i + 1]
|
||||
if self:compareCandidatesForTie(byId[cur], byId[best], cur, best) < 0 then
|
||||
best = cur
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
return best
|
||||
end
|
||||
function ContractMatchManager.prototype.compareCandidatesForTie(self, ca, cb, idA, idB)
|
||||
if not ca or not cb then
|
||||
return 0
|
||||
end
|
||||
if ca.nominated_order ~= cb.nominated_order then
|
||||
return ca.nominated_order < cb.nominated_order and -1 or 1
|
||||
end
|
||||
local steamCmp = compareSteamIdDecimalString(nil, ca.owner_steam_id, cb.owner_steam_id)
|
||||
if steamCmp ~= 0 then
|
||||
return steamCmp
|
||||
end
|
||||
if idA < idB then
|
||||
return -1
|
||||
end
|
||||
if idA > idB then
|
||||
return 1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
function ContractMatchManager.prototype.applyFinalizeFailure(self, attempt, code, onWorldApplyReady)
|
||||
if self.finalizeResolved then
|
||||
return
|
||||
end
|
||||
if self.activeFinalizeAttempt ~= attempt then
|
||||
return
|
||||
end
|
||||
if self.activeFinalizeAttempt ~= attempt then
|
||||
return
|
||||
end
|
||||
self.finalizeResolved = true
|
||||
self.confirmedActiveContract = nil
|
||||
self.confirmedMultiplier = 1
|
||||
self.publicState.active_contract = nil
|
||||
self.publicState.contract_multiplier = 1
|
||||
self.publicState.phase = "finalize_error"
|
||||
self.publicState.public_error_code = code
|
||||
self:syncNetTable()
|
||||
onWorldApplyReady(nil)
|
||||
end
|
||||
function ContractMatchManager.prototype.syncNetTableErrorFlash(self, code)
|
||||
self.publicState.public_error_code = code
|
||||
self:syncNetTable()
|
||||
Timers:CreateTimer(
|
||||
0.1,
|
||||
function()
|
||||
self.publicState.public_error_code = nil
|
||||
self:syncNetTable()
|
||||
return nil
|
||||
end
|
||||
)
|
||||
end
|
||||
function ContractMatchManager.prototype.syncNetTable(self)
|
||||
if type(CustomNetTables) == "nil" then
|
||||
return
|
||||
end
|
||||
self.publicState.candidates = self.candidates
|
||||
self:rebuildVoteCounts()
|
||||
if not self.publicState.contract_session_id and self.sessionId then
|
||||
self.publicState.contract_session_id = self.sessionId
|
||||
end
|
||||
CustomNetTables:SetTableValue(NET_TABLE, NET_KEY, self.publicState)
|
||||
end
|
||||
return ____exports
|
||||
@@ -0,0 +1,22 @@
|
||||
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
|
||||
local ____exports = {}
|
||||
--- Сравнение SteamID64 как decimal string (п.15 плана): без tonumber/Number на id.
|
||||
-- Возвращает -1 если a < b, 0 если равны, 1 если a > b.
|
||||
function ____exports.compareSteamIdDecimalString(self, a, b)
|
||||
local la = #a
|
||||
local lb = #b
|
||||
if la < lb then
|
||||
return -1
|
||||
end
|
||||
if la > lb then
|
||||
return 1
|
||||
end
|
||||
if a < b then
|
||||
return -1
|
||||
end
|
||||
if a > b then
|
||||
return 1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
return ____exports
|
||||
@@ -0,0 +1,3 @@
|
||||
--[[ Generated with https://github.com/TypeScriptToLua/TypeScriptToLua ]]
|
||||
local ____exports = {}
|
||||
return ____exports
|
||||
Reference in New Issue
Block a user