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

1575 lines
58 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__Class = ____lualib.__TS__Class
local __TS__New = ____lualib.__TS__New
local __TS__ArrayFindIndex = ____lualib.__TS__ArrayFindIndex
local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign
local Set = ____lualib.Set
local __TS__ArraySort = ____lualib.__TS__ArraySort
local __TS__ArrayFind = ____lualib.__TS__ArrayFind
local __TS__Iterator = ____lualib.__TS__Iterator
local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter
local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray
local __TS__Number = ____lualib.__TS__Number
local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys
local __TS__ArraySplice = ____lualib.__TS__ArraySplice
local __TS__ArrayMap = ____lualib.__TS__ArrayMap
local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries
local __TS__ArraySetLength = ____lualib.__TS__ArraySetLength
local __TS__Delete = ____lualib.__TS__Delete
local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes
local __TS__ArraySome = ____lualib.__TS__ArraySome
local ____exports = {}
local ____contracts_registry = require("death_sentence.contracts_registry")
local getDeathSentenceDismantleShardReward = ____contracts_registry.getDeathSentenceDismantleShardReward
local applyDeathSentenceContractDayDurationAdjustments = ____contracts_registry.applyDeathSentenceContractDayDurationAdjustments
local generateDeathSentenceContractInstanceWithRarity = ____contracts_registry.generateDeathSentenceContractInstanceWithRarity
local normalizeDeathSentenceContractDurability = ____contracts_registry.normalizeDeathSentenceContractDurability
local normalizeDeathSentenceContractDurabilityMax = ____contracts_registry.normalizeDeathSentenceContractDurabilityMax
local DEATH_SENTENCE_CONTRACT_REPAIR_DONATE_COST = ____contracts_registry.DEATH_SENTENCE_CONTRACT_REPAIR_DONATE_COST
local ____contracts_backend = require("death_sentence.contracts_backend")
local loadDeathSentenceContractsFromBackend = ____contracts_backend.loadDeathSentenceContractsFromBackend
local saveDeathSentenceContractsToBackend = ____contracts_backend.saveDeathSentenceContractsToBackend
local ____real_lobby_player = require("utils.real_lobby_player")
local isRealLobbyPlayer = ____real_lobby_player.isRealLobbyPlayer
local countRealLobbyPlayers = ____real_lobby_player.countRealLobbyPlayers
local ____player_connection_state = require("utils.player_connection_state")
local DOTA_CONNECTION_STATE = ____player_connection_state.DOTA_CONNECTION_STATE
local ____store_manager = require("store_manager")
local StoreManager = ____store_manager.StoreManager
local DEATH_SENTENCE_EMPTY_CONTRACT_VOTE = "__empty_death_sentence_contract__"
--- Дебаг: фиктивный «чужой» контракт во 2-й колонке превью при **одном** живом игроке.
-- Не трогает бэкенд. Только для локалки — в репозитории держи `false`.
local DEATH_SENTENCE_DEBUG_INJECT_SOLO_PEER_CONTRACT = false
local DifficultyManager = __TS__Class()
DifficultyManager.name = "DifficultyManager"
DifficultyManager.____file_path = "scripts/vscripts/difficulty_manager.lua"
function DifficultyManager.prototype.____constructor(self)
self.diffs = {
easy = 0,
normal = 0,
hard = 0,
impossible = 0,
death_sentence = 0
}
self.players = {}
self.contractVotes = {}
self.contractColumnOfferByPlayer = {}
self.contractInventoryByPlayer = {}
self.leader = "normal"
self.selectionEnd = false
self.listenersRegistered = false
self.winningContractId = nil
self.winningContractSnapshot = nil
self.sharedContractRoster = {}
self.sharedContractRosterBuilt = false
self.deathSentenceRosterHydrated = false
self.deathSentenceHydrateStarted = false
self.deathSentenceHydrateWaiting = false
self.deathSentenceHydratedByPlayer = {}
self.deathSentenceHydrateWaitingByPlayer = {}
self.deathSentenceRosterMutationEpoch = 0
self.debugSoloPeerContractInstance = nil
end
function DifficultyManager.prototype.areAllRealLobbyPlayersFinishedLoading(self)
local need = countRealLobbyPlayers(nil)
if need <= 0 then
return false
end
local ready = 0
do
local i = 0
while i < DOTA_MAX_PLAYERS do
do
local pid = i
if not isRealLobbyPlayer(nil, pid) then
goto __continue5
end
if PlayerResource:GetConnectionState(pid) == DOTA_CONNECTION_STATE.CONNECTED then
ready = ready + 1
end
end
::__continue5::
i = i + 1
end
end
return ready == need
end
function DifficultyManager.prototype.tickDeathSentenceRosterHydrationWait(self)
if self.deathSentenceHydrateStarted then
return nil
end
local state = GameRules:State_Get()
if state ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then
self:beginDeathSentenceRosterHydration()
return nil
end
if self:areAllRealLobbyPlayersFinishedLoading() then
self:beginDeathSentenceRosterHydration()
return nil
end
return 0.25
end
function DifficultyManager.getInstance(self)
if not DifficultyManager.instance then
DifficultyManager.instance = __TS__New(DifficultyManager)
end
return DifficultyManager.instance
end
function DifficultyManager.prototype.Init(self)
if not self.listenersRegistered then
if type(CustomGameEventManager) == "nil" then
return
end
CustomGameEventManager:RegisterListener(
"invasion_select_difficulty",
function(_, data)
self:Select({PlayerID = data.PlayerID, diff = data.diff})
end
)
CustomGameEventManager:RegisterListener(
"invasion_vote_death_sentence_contract",
function(_, data)
self:SelectContractVote({PlayerID = data.PlayerID, contractId = data.contractId})
end
)
CustomGameEventManager:RegisterListener(
"invasion_set_death_sentence_column_offer",
function(_, data)
self:SetDeathSentenceColumnOffer({PlayerID = data.PlayerID, contractId = data.contractId})
end
)
CustomGameEventManager:RegisterListener(
"invasion_pick_own_death_sentence_contract",
function(_, data)
self:PickOwnDeathSentenceFromInventory({PlayerID = data.PlayerID, contractId = data.contractId})
end
)
CustomGameEventManager:RegisterListener(
"invasion_dismantle_death_sentence_contract",
function(_, data)
self:DismantleDeathSentenceContract({PlayerID = data.PlayerID, instanceId = data.instanceId})
end
)
CustomGameEventManager:RegisterListener(
"invasion_death_sentence_contract_dismantle_batch",
function(_, data)
self:DismantleDeathSentenceContractsBatch({PlayerID = data.PlayerID, instanceIds = data.instanceIds})
end
)
CustomGameEventManager:RegisterListener(
"invasion_death_sentence_contract_toggle_favorite",
function(_, data)
self:ToggleDeathSentenceContractFavorite({PlayerID = data.PlayerID, instanceId = data.instanceId})
end
)
CustomGameEventManager:RegisterListener(
"invasion_death_sentence_contract_toggle_pin",
function(_, data)
self:ToggleDeathSentenceContractPin({PlayerID = data.PlayerID, instanceId = data.instanceId})
end
)
CustomGameEventManager:RegisterListener(
"invasion_request_death_sentence_contracts_sync",
function(_, data)
self:RequestDeathSentenceContractsSync(data.PlayerID)
end
)
CustomGameEventManager:RegisterListener(
"invasion_repair_death_sentence_contract_durability",
function(_, data)
self:RepairDeathSentenceContractDurability({PlayerID = data.PlayerID, instanceId = data.instanceId})
end
)
ListenToGameEvent(
"player_connect_full",
function(event)
local playerId = event.PlayerID
if playerId ~= nil then
self:RequestDeathSentenceContractsSync(playerId)
self:sendUpdateToAllClients()
end
end,
nil
)
self.listenersRegistered = true
end
self:recalculateLeader()
Timers:CreateTimer(
0.25,
function() return self:tickDeathSentenceRosterHydrationWait() end
)
Timers:CreateTimer(
0.75,
function()
self:sendUpdateToAllClients()
return nil
end
)
end
function DifficultyManager.prototype.Select(self, data)
if self.selectionEnd then
return
end
local playerId = data.PlayerID
local newDiff = data.diff
local previousVote = self.players[playerId]
if newDiff ~= "death_sentence" then
self.contractVotes[playerId] = nil
end
if previousVote == newDiff then
if previousVote ~= nil and previousVote ~= nil then
local ____self_diffs_0, ____previousVote_1 = self.diffs, previousVote
____self_diffs_0[____previousVote_1] = ____self_diffs_0[____previousVote_1] - 1
end
self.players[playerId] = nil
else
if previousVote and previousVote ~= nil then
local ____self_diffs_2, ____previousVote_3 = self.diffs, previousVote
____self_diffs_2[____previousVote_3] = ____self_diffs_2[____previousVote_3] - 1
end
self.players[playerId] = newDiff
local ____self_diffs_4, ____newDiff_5 = self.diffs, newDiff
____self_diffs_4[____newDiff_5] = ____self_diffs_4[____newDiff_5] + 1
end
self:recalculateLeader()
self:sendUpdateToAllClients()
end
function DifficultyManager.prototype.SelectContractVote(self, data)
if self.selectionEnd then
return
end
local playerId = data.PlayerID
self:ensurePlayerContractInventory(playerId)
self:sanitizePlayerContractInventory(playerId)
local newContract = data.contractId
if newContract == nil or newContract == "" then
newContract = nil
end
local prev = self.contractVotes[playerId]
if newContract ~= nil and newContract ~= DEATH_SENTENCE_EMPTY_CONTRACT_VOTE and not self:contractInstanceIdInLobbySharedRoster(newContract) then
return
end
if prev ~= nil and prev ~= nil and prev == newContract then
self.contractVotes[playerId] = nil
else
self.contractVotes[playerId] = newContract
end
if self.contractVotes[playerId] ~= nil then
self:forceDifficultyVote(playerId, "death_sentence")
end
self:sendUpdateToAllClients()
end
function DifficultyManager.prototype.PickOwnDeathSentenceFromInventory(self, data)
if self.selectionEnd then
return
end
local playerId = data.PlayerID
self:ensurePlayerContractInventory(playerId)
self:sanitizePlayerContractInventory(playerId)
local id = data.contractId
if id == nil or id == "" then
id = nil
end
if id == nil or id == DEATH_SENTENCE_EMPTY_CONTRACT_VOTE then
self.contractColumnOfferByPlayer[playerId] = nil
self.contractVotes[playerId] = nil
self:sendUpdateToAllClients()
return
end
if not self:contractInstanceInPlayerInventory(playerId, id) then
return
end
self.contractColumnOfferByPlayer[playerId] = id
self.contractVotes[playerId] = id
self:forceDifficultyVote(playerId, "death_sentence")
self:sendUpdateToAllClients()
end
function DifficultyManager.prototype.SetDeathSentenceColumnOffer(self, data)
if self.selectionEnd then
return
end
local playerId = data.PlayerID
if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then
return
end
local nid = data.contractId
if nid == nil or nid == "" then
nid = nil
end
if nid ~= nil and not self:contractInstanceInPlayerInventory(playerId, nid) then
return
end
self.contractColumnOfferByPlayer[playerId] = nid
self:sendUpdateToAllClients()
end
function DifficultyManager.prototype.contractInstanceInPlayerInventory(self, playerId, instanceId)
local inv = self.contractInventoryByPlayer[playerId]
if not inv then
return false
end
for ____, row in ipairs(inv) do
if row.instanceId == instanceId then
return true
end
end
return false
end
function DifficultyManager.prototype.sanitizeColumnOfferAfterInventoryChange(self, playerId)
local sid = self.contractColumnOfferByPlayer[playerId]
if sid == nil or sid == nil then
return
end
if not self:contractInstanceInPlayerInventory(playerId, sid) then
self.contractColumnOfferByPlayer[playerId] = nil
end
end
function DifficultyManager.prototype.sendDeathSentenceDismantleResult(self, playerId, ok, shards, meta)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
CustomGameEventManager:Send_ServerToPlayer(player, "death_sentence_contract_dismantle_result", {ok = ok, shards = shards, batch_count = meta and meta.batchCount or 0, skipped_pinned = meta and meta.skippedPinned or 0})
end
function DifficultyManager.prototype.sendDeathSentenceRepairResult(self, playerId, ok, reason, success)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
local payload = {ok = ok, reason = reason or ""}
if ok and success then
payload.instance_id = success.instanceId
payload.durability = success.durability
payload.durability_max = success.durabilityMax
end
CustomGameEventManager:Send_ServerToPlayer(player, "death_sentence_contract_repair_result", payload)
end
function DifficultyManager.prototype.RepairDeathSentenceContractDurability(self, data)
local playerId = data.PlayerID
local instanceId = data.instanceId
local function fail(____, reason)
return self:sendDeathSentenceRepairResult(playerId, false, reason)
end
if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then
fail(nil, "invalid")
return
end
if self.selectionEnd or type(instanceId) ~= "string" or #instanceId == 0 then
fail(nil, "state")
return
end
if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then
fail(nil, "state")
return
end
if not self.deathSentenceHydratedByPlayer[playerId] then
fail(nil, "state")
return
end
local inv = self.contractInventoryByPlayer[playerId] or ({})
local idx = __TS__ArrayFindIndex(
inv,
function(____, x) return x.instanceId == instanceId end
)
if idx < 0 then
fail(nil, "not_found")
return
end
local inst = inv[idx + 1]
local cur = normalizeDeathSentenceContractDurability(nil, inst.durability, inst.instanceId)
local max = normalizeDeathSentenceContractDurabilityMax(nil, inst.durabilityMax, cur, inst.instanceId)
inst.durability = cur
inst.durabilityMax = max
if cur >= max then
fail(nil, "full")
return
end
local store = StoreManager:getInstance()
local cost = DEATH_SENTENCE_CONTRACT_REPAIR_DONATE_COST
if store:getDonateCurrency(playerId) < cost then
fail(nil, "funds")
return
end
local prevDur = cur
inst.durability = max
if not store:tryConsumeDonateCurrency(playerId, cost) then
inst.durability = prevDur
fail(nil, "funds")
return
end
self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1
self:sortContractRosterForDisplay(inv)
self:rebuildSharedContractRosterFromPlayerInventories()
self:syncContractInventoryToClient(playerId)
self:saveContractRosterForPlayer(
playerId,
function(____, okSave)
if not okSave then
inst.durability = prevDur
store:addDonateCurrency(playerId, cost)
self:syncContractInventoryToClient(playerId)
self:sendUpdateToAllClients()
fail(nil, "save")
return
end
self:sendDeathSentenceRepairResult(
playerId,
true,
"",
{
instanceId = inst.instanceId,
durability = max,
durabilityMax = normalizeDeathSentenceContractDurabilityMax(nil, inst.durabilityMax, max, inst.instanceId)
}
)
self:sendUpdateToAllClients()
end
)
end
function DifficultyManager.prototype.shouldInjectDebugSoloPeerContract(self)
return DEATH_SENTENCE_DEBUG_INJECT_SOLO_PEER_CONTRACT and countRealLobbyPlayers(nil) == 1
end
function DifficultyManager.prototype.getOrCreateDebugSoloPeerContract(self)
if self.debugSoloPeerContractInstance then
return self.debugSoloPeerContractInstance
end
local base = generateDeathSentenceContractInstanceWithRarity(nil, 1, "legendary")
self.debugSoloPeerContractInstance = __TS__ObjectAssign({}, base, {
instanceId = "ds_ci__debug_solo_peer__",
serial = 2,
titleIndex = 42,
pinned = false,
favorite = false
})
return self.debugSoloPeerContractInstance
end
function DifficultyManager.prototype.rebuildSharedContractRosterFromPlayerInventories(self)
local seen = __TS__New(Set)
local next = {}
do
local i = 0
while i < DOTA_MAX_PLAYERS do
do
local pid = i
local inv = self.contractInventoryByPlayer[pid]
if not inv then
goto __continue85
end
for ____, inst in ipairs(inv) do
do
if not inst or seen:has(inst.instanceId) then
goto __continue87
end
seen:add(inst.instanceId)
next[#next + 1] = inst
end
::__continue87::
end
end
::__continue85::
i = i + 1
end
end
if self:shouldInjectDebugSoloPeerContract() then
local dbg = self:getOrCreateDebugSoloPeerContract()
if not seen:has(dbg.instanceId) then
seen:add(dbg.instanceId)
next[#next + 1] = dbg
end
end
self.sharedContractRoster = next
self.sharedContractRosterBuilt = true
self:sortSharedContractRosterForDisplay()
self:invalidateContractVotesNotInRoster()
end
function DifficultyManager.prototype.sortContractRosterForDisplay(self, roster)
__TS__ArraySort(
roster,
function(____, a, b)
local pinA = a.pinned and 1 or 0
local pinB = b.pinned and 1 or 0
if pinA ~= pinB then
return pinB - pinA
end
local favA = a.favorite and 1 or 0
local favB = b.favorite and 1 or 0
if favA ~= favB then
return favB - favA
end
return a.serial - b.serial
end
)
end
function DifficultyManager.prototype.sortSharedContractRosterForDisplay(self)
self:sortContractRosterForDisplay(self.sharedContractRoster)
end
function DifficultyManager.prototype.syncContractInventoryToClient(self, playerId)
if not PlayerResource:IsValidPlayerID(playerId) then
return
end
CustomNetTables:SetTableValue(
"death_sentence_contracts",
tostring(playerId),
{roster = self.contractInventoryByPlayer[playerId] or ({})}
)
end
function DifficultyManager.prototype.ToggleDeathSentenceContractFavorite(self, data)
local playerId = data.PlayerID
local instanceId = data.instanceId
if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then
return
end
if self.selectionEnd or type(instanceId) ~= "string" or #instanceId == 0 then
return
end
if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then
return
end
if not self.deathSentenceHydratedByPlayer[playerId] then
return
end
local inv = self.contractInventoryByPlayer[playerId] or ({})
local inst = __TS__ArrayFind(
inv,
function(____, x) return x.instanceId == instanceId end
)
if not inst then
return
end
inst.favorite = not inst.favorite
self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1
self:sortContractRosterForDisplay(inv)
self:rebuildSharedContractRosterFromPlayerInventories()
self:syncContractInventoryToClient(playerId)
self:saveContractRosterForPlayer(
playerId,
function()
end
)
self:sendUpdateToAllClients()
end
function DifficultyManager.prototype.ToggleDeathSentenceContractPin(self, data)
local playerId = data.PlayerID
local instanceId = data.instanceId
if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then
return
end
if self.selectionEnd or type(instanceId) ~= "string" or #instanceId == 0 then
return
end
if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then
return
end
if not self.deathSentenceHydratedByPlayer[playerId] then
return
end
local inv = self.contractInventoryByPlayer[playerId] or ({})
local inst = __TS__ArrayFind(
inv,
function(____, x) return x.instanceId == instanceId end
)
if not inst then
return
end
inst.pinned = not inst.pinned
self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1
self:sortContractRosterForDisplay(inv)
self:rebuildSharedContractRosterFromPlayerInventories()
self:syncContractInventoryToClient(playerId)
self:saveContractRosterForPlayer(
playerId,
function()
end
)
self:sendUpdateToAllClients()
end
function DifficultyManager.prototype.DismantleDeathSentenceContractsBatch(self, data)
local playerId = data.PlayerID
local rawIds = self:normalizeContractBatchIds(data.instanceIds)
if not PlayerResource:IsValidPlayerID(playerId) then
return
end
if PlayerResource:IsFakeClient(playerId) then
self:sendDeathSentenceDismantleResult(playerId, false, 0)
return
end
if self.selectionEnd or #rawIds == 0 then
self:sendDeathSentenceDismantleResult(playerId, false, 0)
return
end
if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then
self:sendDeathSentenceDismantleResult(playerId, false, 0)
return
end
if not self.deathSentenceHydratedByPlayer[playerId] then
self:sendDeathSentenceDismantleResult(playerId, false, 0)
return
end
local inv = self.contractInventoryByPlayer[playerId] or ({})
local want = __TS__New(Set)
for ____, id in ipairs(rawIds) do
if type(id) == "string" and #id > 0 then
want:add(id)
end
end
if want.size == 0 then
self:sendDeathSentenceDismantleResult(playerId, false, 0)
return
end
local skippedPinned = 0
local totalShards = 0
local removed = 0
local toRemove = {}
for ____, id in __TS__Iterator(want) do
do
local inst = __TS__ArrayFind(
inv,
function(____, x) return x.instanceId == id end
)
if not inst then
goto __continue125
end
if inst.pinned == true then
skippedPinned = skippedPinned + 1
goto __continue125
end
toRemove[#toRemove + 1] = id
totalShards = totalShards + getDeathSentenceDismantleShardReward(nil, inst.rarity)
end
::__continue125::
end
if #toRemove == 0 then
self:sendDeathSentenceDismantleResult(playerId, false, 0, {batchCount = 0, skippedPinned = skippedPinned})
return
end
local removeSet = __TS__New(Set, toRemove)
self.contractInventoryByPlayer[playerId] = __TS__ArrayFilter(
inv,
function(____, x) return not removeSet:has(x.instanceId) end
)
removed = #toRemove
self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1
self:sortContractRosterForDisplay(self.contractInventoryByPlayer[playerId])
self:rebuildSharedContractRosterFromPlayerInventories()
self:sanitizeColumnOfferAfterInventoryChange(playerId)
self:syncContractInventoryToClient(playerId)
self:invalidateContractVotesNotInRoster()
self:saveContractRosterForPlayer(
playerId,
function()
end
)
StoreManager:getInstance():grantDustCurrencyMatchEndReward(playerId, totalShards)
self:sendDeathSentenceDismantleResult(playerId, true, totalShards, {batchCount = removed, skippedPinned = skippedPinned})
self:sendUpdateToAllClients()
end
function DifficultyManager.prototype.normalizeContractBatchIds(self, raw)
if not raw then
return {}
end
if __TS__ArrayIsArray(raw) then
local out = {}
for ____, id in ipairs(raw) do
if type(id) == "string" and #id > 0 then
out[#out + 1] = id
end
end
return out
end
if type(raw) == "table" then
local obj = raw
local out = {}
local keys = __TS__ArraySort(
__TS__ObjectKeys(obj),
function(____, a, b) return __TS__Number(a) - __TS__Number(b) end
)
for ____, key in ipairs(keys) do
local val = obj[key]
if type(val) == "string" and #val > 0 then
out[#out + 1] = val
end
end
return out
end
return {}
end
function DifficultyManager.prototype.DismantleDeathSentenceContract(self, data)
local playerId = data.PlayerID
local instanceId = data.instanceId
if not PlayerResource:IsValidPlayerID(playerId) then
return
end
if PlayerResource:IsFakeClient(playerId) then
self:sendDeathSentenceDismantleResult(playerId, false, 0)
return
end
if self.selectionEnd or type(instanceId) ~= "string" or #instanceId == 0 then
self:sendDeathSentenceDismantleResult(playerId, false, 0)
return
end
if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then
self:sendDeathSentenceDismantleResult(playerId, false, 0)
return
end
if not self.deathSentenceHydratedByPlayer[playerId] then
self:sendDeathSentenceDismantleResult(playerId, false, 0)
return
end
local inv = self.contractInventoryByPlayer[playerId] or ({})
local idx = __TS__ArrayFindIndex(
inv,
function(____, x) return x.instanceId == instanceId end
)
if idx < 0 then
self:sendDeathSentenceDismantleResult(playerId, false, 0)
return
end
local inst = inv[idx + 1]
if inst.pinned == true then
self:sendDeathSentenceDismantleResult(playerId, false, 0)
return
end
local shards = getDeathSentenceDismantleShardReward(nil, inst.rarity)
__TS__ArraySplice(inv, idx, 1)
self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1
self:sortContractRosterForDisplay(inv)
self:rebuildSharedContractRosterFromPlayerInventories()
self:sanitizeColumnOfferAfterInventoryChange(playerId)
self:syncContractInventoryToClient(playerId)
self:invalidateContractVotesNotInRoster()
self:saveContractRosterForPlayer(
playerId,
function()
end
)
StoreManager:getInstance():grantDustCurrencyMatchEndReward(playerId, shards)
self:sendDeathSentenceDismantleResult(playerId, true, shards)
self:sendUpdateToAllClients()
end
function DifficultyManager.prototype.getSteamIdForContracts(self, playerId)
if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then
return nil
end
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId or steamId == 0 then
return nil
end
return tostring(steamId)
end
function DifficultyManager.prototype.invalidateContractVotesNotInRoster(self)
local valid = __TS__New(
Set,
__TS__ArrayMap(
self.sharedContractRoster,
function(____, x) return x.instanceId end
)
)
do
local i = 0
while i < DOTA_MAX_PLAYERS do
local pid = i
local v = self.contractVotes[pid]
if v and not valid:has(v) then
self.contractVotes[pid] = nil
end
i = i + 1
end
end
end
function DifficultyManager.prototype.applyPlayerContractRosterFromBackend(self, playerId, roster)
local next = roster ~= nil and ({unpack(roster)}) or ({})
while #next > DifficultyManager.CONTRACT_INVENTORY_CAP do
table.remove(next)
end
self:sortContractRosterForDisplay(next)
self.contractInventoryByPlayer[playerId] = next
self.deathSentenceHydratedByPlayer[playerId] = true
self:syncContractInventoryToClient(playerId)
self:rebuildSharedContractRosterFromPlayerInventories()
self:sanitizeColumnOfferAfterInventoryChange(playerId)
print(((("[DifficultyManager] Death Sentence: ростер игрока pid=" .. tostring(playerId)) .. " с бэка, шт.=") .. tostring(#next)) .. ".")
end
function DifficultyManager.prototype.loadDeathSentenceRosterForPlayer(self, playerId, done)
if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then
if done ~= nil then
done(nil)
end
return
end
if self.deathSentenceHydratedByPlayer[playerId] then
if done ~= nil then
done(nil)
end
return
end
if self.deathSentenceHydrateWaitingByPlayer[playerId] then
return
end
local steam = self:getSteamIdForContracts(playerId)
if not steam then
print(("[DifficultyManager] Death Sentence: SteamID для pid=" .. tostring(playerId)) .. " пока недоступен, повторим позже.")
if done ~= nil then
done(nil)
end
return
end
self.deathSentenceHydrateWaitingByPlayer[playerId] = true
local mutationEpochAtFetch = self.deathSentenceRosterMutationEpoch
loadDeathSentenceContractsFromBackend(
nil,
steam,
function(____, roster)
self.deathSentenceHydrateWaitingByPlayer[playerId] = false
if self.deathSentenceRosterMutationEpoch ~= mutationEpochAtFetch and self.deathSentenceHydratedByPlayer[playerId] then
print(("[DifficultyManager] Death Sentence: ответ GET игрока pid=" .. tostring(playerId)) .. " проигнорирован (локально уже меняли ростер после старта загрузки).")
if done ~= nil then
done(nil)
end
return
end
self:applyPlayerContractRosterFromBackend(playerId, roster)
if done ~= nil then
done(nil)
end
end
)
end
function DifficultyManager.prototype.beginDeathSentenceRosterHydration(self, preferredPlayerId)
if self.deathSentenceHydrateStarted then
return
end
self.deathSentenceHydrateStarted = true
local players = {}
if preferredPlayerId ~= nil and PlayerResource:IsValidPlayerID(preferredPlayerId) and not PlayerResource:IsFakeClient(preferredPlayerId) then
players[#players + 1] = preferredPlayerId
else
do
local i = 0
while i < DOTA_MAX_PLAYERS do
do
local pid = i
if not isRealLobbyPlayer(nil, pid) then
goto __continue174
end
if PlayerResource:GetConnectionState(pid) ~= DOTA_CONNECTION_STATE.CONNECTED then
goto __continue174
end
players[#players + 1] = pid
end
::__continue174::
i = i + 1
end
end
end
if #players <= 0 then
self.deathSentenceHydrateStarted = false
self.deathSentenceHydrateWaiting = false
print("[DifficultyManager] Death Sentence: нет игроков для загрузки ростеров, повторим позже.")
return
end
self.deathSentenceHydrateWaiting = true
local pending = #players
local function onDone()
pending = pending - 1
if pending > 0 then
return
end
self.deathSentenceHydrateWaiting = false
self:finishDeathSentenceRosterHydration()
end
for ____, playerId in ipairs(players) do
self:loadDeathSentenceRosterForPlayer(playerId, onDone)
end
end
function DifficultyManager.prototype.RequestDeathSentenceContractsSync(self, playerId)
if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then
return
end
if not self.deathSentenceHydratedByPlayer[playerId] then
self:loadDeathSentenceRosterForPlayer(
playerId,
function()
self:finishDeathSentenceRosterHydration()
end
)
return
end
self:sanitizePlayerContractInventory(playerId)
self:syncContractInventoryToClient(playerId)
self:sendUpdateToAllClients()
end
function DifficultyManager.prototype.finishDeathSentenceRosterHydration(self)
self.deathSentenceRosterHydrated = true
self:sendUpdateToAllClients()
end
function DifficultyManager.prototype.ensureSharedContractRoster(self)
if self.sharedContractRosterBuilt then
return
end
if self.deathSentenceHydrateWaiting then
return
end
self:rebuildSharedContractRosterFromPlayerInventories()
end
function DifficultyManager.prototype.ensurePlayerContractInventory(self, playerId)
if not self.deathSentenceHydratedByPlayer[playerId] then
return
end
local inv = self.contractInventoryByPlayer[playerId]
if not inv then
self.contractInventoryByPlayer[playerId] = {}
end
end
function DifficultyManager.prototype.sanitizePlayerContractInventory(self, playerId)
if not self.deathSentenceHydratedByPlayer[playerId] then
return
end
local listRef = self.contractInventoryByPlayer[playerId]
if not listRef then
self.contractInventoryByPlayer[playerId] = {}
return
end
local first = listRef[1]
if type(first) == "string" then
self.contractInventoryByPlayer[playerId] = {}
return
end
local next = {}
for ____, row in ipairs(listRef) do
do
local inst = row
if not inst or type(inst.instanceId) ~= "string" then
goto __continue197
end
inst.durability = normalizeDeathSentenceContractDurability(nil, inst.durability, inst.instanceId)
inst.durabilityMax = normalizeDeathSentenceContractDurabilityMax(nil, inst.durabilityMax, inst.durability, inst.instanceId)
next[#next + 1] = inst
if #next >= DifficultyManager.CONTRACT_INVENTORY_CAP then
break
end
end
::__continue197::
end
self:sortContractRosterForDisplay(next)
self.contractInventoryByPlayer[playerId] = next
end
function DifficultyManager.prototype.contractInstanceIdInLobbySharedRoster(self, contractInstanceId)
self:ensureSharedContractRoster()
for ____, inst in ipairs(self.sharedContractRoster) do
if inst.instanceId == contractInstanceId then
return true
end
end
return false
end
function DifficultyManager.prototype.forceDifficultyVote(self, playerId, newDiff)
local previousVote = self.players[playerId]
if previousVote == newDiff then
return
end
if previousVote ~= nil and previousVote ~= nil then
local ____self_diffs_20, ____previousVote_21 = self.diffs, previousVote
____self_diffs_20[____previousVote_21] = ____self_diffs_20[____previousVote_21] - 1
end
self.players[playerId] = newDiff
local ____self_diffs_22, ____newDiff_23 = self.diffs, newDiff
____self_diffs_22[____newDiff_23] = ____self_diffs_22[____newDiff_23] + 1
self:recalculateLeader()
end
function DifficultyManager.prototype.recalculateLeader(self)
local maxVotes = 0
local newLeader = "normal"
for ____, ____value in ipairs(__TS__ObjectEntries(self.diffs)) do
local diff = ____value[1]
local votes = ____value[2]
if votes > maxVotes then
maxVotes = votes
newLeader = diff
end
end
if maxVotes == 0 then
newLeader = "normal"
end
self.leader = newLeader
end
function DifficultyManager.prototype.resolveWinningContract(self)
local ids = __TS__ArrayMap(
self.sharedContractRoster,
function(____, x) return x.instanceId end
)
if #ids == 0 then
return nil
end
local counts = {}
for ____, id in ipairs(ids) do
counts[id] = 0
end
for ____, ____value in ipairs(__TS__ObjectEntries(self.contractVotes)) do
local pidStr = ____value[1]
local contractId = ____value[2]
do
local pid = tonumber(pidStr)
if self.players[pid] ~= "death_sentence" then
goto __continue218
end
if not contractId or contractId == "" or contractId == DEATH_SENTENCE_EMPTY_CONTRACT_VOTE then
goto __continue218
end
if counts[contractId] == nil then
goto __continue218
end
counts[contractId] = (counts[contractId] or 0) + 1
end
::__continue218::
end
local max = 0
local leaders = {}
for ____, id in ipairs(ids) do
local c = counts[id] or 0
if c > max then
max = c
__TS__ArraySetLength(leaders, 0)
leaders[#leaders + 1] = id
elseif c == max and c > 0 then
leaders[#leaders + 1] = id
end
end
if max == 0 then
return nil
end
return leaders[RandomInt(0, #leaders - 1) + 1]
end
function DifficultyManager.prototype.sendUpdateToAllClients(self)
if type(CustomGameEventManager) == "nil" then
return
end
if self.deathSentenceRosterHydrated then
do
local i = 0
while i < DOTA_MAX_PLAYERS do
do
local playerId = i
if not PlayerResource:IsValidPlayerID(playerId) then
goto __continue231
end
if not self.deathSentenceHydratedByPlayer[playerId] then
goto __continue231
end
self:ensurePlayerContractInventory(playerId)
self:sanitizePlayerContractInventory(playerId)
self:syncContractInventoryToClient(playerId)
end
::__continue231::
i = i + 1
end
end
end
local updateData = {votes = self.diffs, currentLeader = self.leader, playerVotes = self.players}
if self.deathSentenceRosterHydrated then
updateData.contractVotes = self.contractVotes
if self:shouldInjectDebugSoloPeerContract() then
updateData.debugSoloPeerContract = self:getOrCreateDebugSoloPeerContract()
end
local details = {}
do
local i = 0
while i < DOTA_MAX_PLAYERS do
do
local pid = i
local contractId = self.contractVotes[pid]
if not contractId or contractId == DEATH_SENTENCE_EMPTY_CONTRACT_VOTE then
details[pid] = nil
goto __continue236
end
local ____opt_24 = self.contractInventoryByPlayer[pid]
local own = ____opt_24 and __TS__ArrayFind(
self.contractInventoryByPlayer[pid],
function(____, x) return x.instanceId == contractId end
) or nil
local ____own_27 = own
if ____own_27 == nil then
local ____table_sharedContractRosterBuilt_26
if self.sharedContractRosterBuilt then
____table_sharedContractRosterBuilt_26 = __TS__ArrayFind(
self.sharedContractRoster,
function(____, x) return x.instanceId == contractId end
) or nil
else
____table_sharedContractRosterBuilt_26 = nil
end
____own_27 = ____table_sharedContractRosterBuilt_26
end
details[pid] = ____own_27
end
::__continue236::
i = i + 1
end
end
updateData.contractVoteDetails = details
local columnOffer = {}
do
local j = 0
while j < DOTA_MAX_PLAYERS do
do
local pOffer = j
if not PlayerResource:IsValidPlayerID(pOffer) then
goto __continue240
end
local c = self.contractColumnOfferByPlayer[pOffer]
columnOffer[pOffer] = c ~= nil and c ~= nil and c or nil
end
::__continue240::
j = j + 1
end
end
updateData.contractColumnOffer = columnOffer
end
CustomGameEventManager:Send_ServerToAllClients("update_difficulty_selections", updateData)
end
function DifficultyManager.prototype.ensureHeroSelectionResolvedForMatchStart(self)
if self.selectionEnd then
return
end
self:OnHeroSelectionState()
end
function DifficultyManager.prototype.getDeathSentenceContractPayloadForLossPenalty(self, playerId)
if self.leader ~= "death_sentence" then
return nil
end
local active = self:getActiveDeathSentenceContractPayload()
if active then
return active
end
local vote = self.contractVotes[playerId]
if not vote or vote == DEATH_SENTENCE_EMPTY_CONTRACT_VOTE then
return nil
end
local ____opt_28 = self.contractInventoryByPlayer[playerId]
local inst = ____opt_28 and __TS__ArrayFind(
self.contractInventoryByPlayer[playerId],
function(____, x) return x.instanceId == vote end
) or __TS__ArrayFind(
self.sharedContractRoster,
function(____, x) return x.instanceId == vote end
) or nil
if not inst then
return nil
end
local durability = normalizeDeathSentenceContractDurability(nil, inst.durability, inst.instanceId)
return {
instanceId = inst.instanceId,
serial = inst.serial,
titleIndex = inst.titleIndex,
rarity = inst.rarity,
rewardMultiplier = inst.rewardMultiplier,
traitId = inst.traitId,
complicationIds = {unpack(inst.complicationIds)},
durability = durability,
durabilityMax = normalizeDeathSentenceContractDurabilityMax(nil, inst.durabilityMax, durability, inst.instanceId)
}
end
function DifficultyManager.prototype.forceReloadDeathSentenceContractsFromBackend(self, playerId)
if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then
return
end
self.deathSentenceHydratedByPlayer[playerId] = false
__TS__Delete(self.deathSentenceHydrateWaitingByPlayer, playerId)
self:loadDeathSentenceRosterForPlayer(
playerId,
function()
self:finishDeathSentenceRosterHydration()
end
)
end
function DifficultyManager.prototype.OnHeroSelectionState(self)
self.selectionEnd = true
if not self.sharedContractRosterBuilt and not self.deathSentenceHydrateWaiting then
self:rebuildSharedContractRosterFromPlayerInventories()
end
if not self.deathSentenceRosterHydrated then
self.deathSentenceRosterHydrated = true
end
local ____temp_30
if self.leader == "death_sentence" then
____temp_30 = self:resolveWinningContract()
else
____temp_30 = nil
end
self.winningContractId = ____temp_30
local ____temp_31
if self.winningContractId ~= nil then
____temp_31 = __TS__ArrayFind(
self.sharedContractRoster,
function(____, x) return x.instanceId == self.winningContractId end
) or nil
else
____temp_31 = nil
end
self.winningContractSnapshot = ____temp_31
if type(CustomGameEventManager) == "nil" then
return
end
CustomGameEventManager:Send_ServerToAllClients(
"difficulty_selected",
{
difficulty = self.leader,
death_sentence_contract = self.winningContractId,
death_sentence_contract_data = self.winningContractSnapshot and ({
instanceId = self.winningContractSnapshot.instanceId,
serial = self.winningContractSnapshot.serial,
titleIndex = self.winningContractSnapshot.titleIndex,
rarity = self.winningContractSnapshot.rarity,
rewardMultiplier = self.winningContractSnapshot.rewardMultiplier,
traitId = self.winningContractSnapshot.traitId,
complicationIds = {unpack(self.winningContractSnapshot.complicationIds)},
durability = normalizeDeathSentenceContractDurability(nil, self.winningContractSnapshot.durability, self.winningContractSnapshot.instanceId),
durabilityMax = normalizeDeathSentenceContractDurabilityMax(
nil,
self.winningContractSnapshot.durabilityMax,
normalizeDeathSentenceContractDurability(nil, self.winningContractSnapshot.durability, self.winningContractSnapshot.instanceId),
self.winningContractSnapshot.instanceId
)
}) or nil
}
)
if __TS__ArrayIncludes({
"easy",
"normal",
"hard",
"impossible",
"death_sentence"
}, self.leader) then
local units = FindUnitsInRadius(
DOTA_TEAM_GOODGUYS,
Vector(0, 0, 0),
nil,
-1,
DOTA_UNIT_TARGET_TEAM_ENEMY,
DOTA_UNIT_TARGET_ALL,
DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES,
FIND_ANY_ORDER,
false
)
_G.Difficulter = self:getNpcStatScale()
for ____, unit in ipairs(units) do
self:NPC(unit)
end
local dayLength = 0
local NightLength = 0
repeat
local ____switch262 = self.leader
local ____cond262 = ____switch262 == "easy"
if ____cond262 then
dayLength = -60
NightLength = -60
break
end
____cond262 = ____cond262 or ____switch262 == "normal"
if ____cond262 then
dayLength = -120
NightLength = -120
break
end
____cond262 = ____cond262 or ____switch262 == "hard"
if ____cond262 then
dayLength = -60
NightLength = -60
break
end
____cond262 = ____cond262 or (____switch262 == "impossible" or ____switch262 == "death_sentence")
if ____cond262 then
dayLength = -120
NightLength = -120
break
end
until true
do
pcall(function()
local ____require_result_32 = require("DayNightCycleManager")
local DayNightCycleManager = ____require_result_32.DayNightCycleManager
local dayManager = DayNightCycleManager:getInstance()
local oldDayDuration = dayManager:GetDayDuration()
local oldNightDuration = dayManager:GetNightDuration()
dayManager:SetDayDuration(oldDayDuration + dayLength)
dayManager:SetNightDuration(oldNightDuration + NightLength)
if self.leader == "death_sentence" then
applyDeathSentenceContractDayDurationAdjustments(nil, self.winningContractSnapshot)
end
end)
end
end
self:sendUpdateToAllClients()
end
function DifficultyManager.prototype.getNpcStatScale(self)
repeat
local ____switch266 = self.leader
local ____cond266 = ____switch266 == "easy"
if ____cond266 then
return 0.5
end
____cond266 = ____cond266 or ____switch266 == "normal"
if ____cond266 then
return 1
end
____cond266 = ____cond266 or ____switch266 == "hard"
if ____cond266 then
return 2
end
____cond266 = ____cond266 or ____switch266 == "impossible"
if ____cond266 then
return 4
end
____cond266 = ____cond266 or ____switch266 == "death_sentence"
if ____cond266 then
local ____opt_33 = self.winningContractSnapshot
return ____opt_33 and ____opt_33.rewardMultiplier or 6
end
do
return 1
end
until true
end
function DifficultyManager.prototype.getWinningContractSnapshot(self)
return self.winningContractSnapshot
end
function DifficultyManager.prototype.getActiveDeathSentenceContract(self)
if not self.selectionEnd then
return nil
end
return self.winningContractId
end
function DifficultyManager.prototype.getActiveDeathSentenceContractPayload(self)
if not self.selectionEnd or not self.winningContractSnapshot then
return nil
end
local w = self.winningContractSnapshot
local durability = normalizeDeathSentenceContractDurability(nil, w.durability, w.instanceId)
return {
instanceId = w.instanceId,
serial = w.serial,
titleIndex = w.titleIndex,
rarity = w.rarity,
rewardMultiplier = w.rewardMultiplier,
traitId = w.traitId,
complicationIds = {unpack(w.complicationIds)},
durability = durability,
durabilityMax = normalizeDeathSentenceContractDurabilityMax(nil, w.durabilityMax, durability, w.instanceId)
}
end
function DifficultyManager.prototype.getDeathSentenceContractRewardMultiplier(self)
local ____opt_35 = self.winningContractSnapshot
return ____opt_35 and ____opt_35.rewardMultiplier or 3.5
end
function DifficultyManager.prototype.getRarityIndex(self, rarity)
local order = {
"common",
"rare",
"epic",
"legendary",
"mythic"
}
do
local i = 0
while i < #order do
if order[i + 1] == rarity then
return i
end
i = i + 1
end
end
return 0
end
function DifficultyManager.prototype.rarityByIndex(self, index)
local order = {
"common",
"rare",
"epic",
"legendary",
"mythic"
}
local clamped = math.max(
0,
math.min(
#order - 1,
math.floor(index)
)
)
return order[clamped + 1]
end
function DifficultyManager.prototype.roundContractMultiplier(self, value)
return math.floor(value * 100 + 0.5) / 100
end
function DifficultyManager.prototype.rollMatchEndContractRarity(self)
if self.leader ~= "death_sentence" then
local r = RandomInt(1, 100)
if r <= 70 then
return "rare"
end
if r <= 95 then
return "epic"
end
return "legendary"
end
local ____opt_37 = self.winningContractSnapshot
local base = ____opt_37 and ____opt_37.rarity or "epic"
local baseIdx = self:getRarityIndex(base)
local roll = RandomInt(1, 100)
if roll <= 75 then
return base
end
return self:rarityByIndex(baseIdx + 1)
end
function DifficultyManager.prototype.grantMatchEndContractIfEligible(self, playerId)
if self.leader ~= "impossible" and self.leader ~= "death_sentence" then
return nil
end
if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then
return nil
end
if not self.deathSentenceHydratedByPlayer[playerId] then
print(("[DifficultyManager] grantMatchEndContractIfEligible: pid=" .. tostring(playerId)) .. " пропуск — ростер не подтянут с бэка (hyd=false).")
return nil
end
self:ensurePlayerContractInventory(playerId)
self:sanitizePlayerContractInventory(playerId)
local inv = self.contractInventoryByPlayer[playerId] or ({})
if #inv >= DifficultyManager.CONTRACT_INVENTORY_CAP then
print(((((("[DifficultyManager] grantMatchEndContractIfEligible: pid=" .. tostring(playerId)) .. " пропуск — лимит ростера ") .. tostring(DifficultyManager.CONTRACT_INVENTORY_CAP)) .. " (сейчас ") .. tostring(#inv)) .. ").")
return nil
end
local rarity = self:rollMatchEndContractRarity()
local serial = #inv + 1
local created = generateDeathSentenceContractInstanceWithRarity(nil, serial - 1, rarity)
if self.leader == "death_sentence" then
local passed = self.winningContractSnapshot
if passed then
local delta = RandomFloat(0.25, 1)
created.rewardMultiplier = self:roundContractMultiplier(passed.rewardMultiplier + delta)
end
end
inv[#inv + 1] = created
self:sortContractRosterForDisplay(inv)
self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1
self:rebuildSharedContractRosterFromPlayerInventories()
self:syncContractInventoryToClient(playerId)
self:sendUpdateToAllClients()
return created
end
function DifficultyManager.prototype.applyDeathSentenceContractDurabilityOnLoss(self, done)
if self.leader ~= "death_sentence" then
done(nil)
return
end
local cid = self.winningContractId
if not cid or cid == DEATH_SENTENCE_EMPTY_CONTRACT_VOTE then
done(nil)
return
end
local ownerPid
do
local i = 0
while i < DOTA_MAX_PLAYERS do
do
local pid = i
if not PlayerResource:IsValidPlayerID(pid) or PlayerResource:IsFakeClient(pid) then
goto __continue293
end
local inv = self.contractInventoryByPlayer[pid]
if not inv then
goto __continue293
end
if __TS__ArraySome(
inv,
function(____, x) return x.instanceId == cid end
) then
ownerPid = pid
break
end
end
::__continue293::
i = i + 1
end
end
if ownerPid == nil then
print(("[DifficultyManager] DS durability loss: экземпляр " .. cid) .. " не найден ни в одном ростере")
done(nil)
return
end
local inv = self.contractInventoryByPlayer[ownerPid]
local idx = __TS__ArrayFindIndex(
inv,
function(____, x) return x.instanceId == cid end
)
if idx < 0 then
done(nil)
return
end
local inst = inv[idx + 1]
local cur = normalizeDeathSentenceContractDurability(nil, inst.durability, inst.instanceId)
inst.durability = cur
local next = cur - 1
if next <= 0 then
__TS__ArraySplice(inv, idx, 1)
do
local j = 0
while j < DOTA_MAX_PLAYERS do
local pid = j
if self.contractVotes[pid] == cid then
self.contractVotes[pid] = nil
end
if self.contractColumnOfferByPlayer[pid] == cid then
self.contractColumnOfferByPlayer[pid] = nil
end
j = j + 1
end
end
print(((("[DifficultyManager] DS durability: приговор " .. cid) .. " сломан (было ") .. tostring(cur)) .. "→0), удалён из инвентаря")
else
inst.durability = next
print((((("[DifficultyManager] DS durability: приговор " .. cid) .. " ") .. tostring(cur)) .. "") .. tostring(next))
end
self:sortContractRosterForDisplay(inv)
self.deathSentenceRosterMutationEpoch = self.deathSentenceRosterMutationEpoch + 1
self:rebuildSharedContractRosterFromPlayerInventories()
self:saveContractRosterForPlayer(
ownerPid,
function()
self:sendUpdateToAllClients()
done(nil)
end
)
end
function DifficultyManager.prototype.saveContractRosterForPlayer(self, playerId, done)
if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then
done(nil, false)
return
end
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId or steamId == 0 then
done(nil, false)
return
end
saveDeathSentenceContractsToBackend(
nil,
tostring(steamId),
self.contractInventoryByPlayer[playerId] or ({}),
done
)
end
function DifficultyManager.prototype.NPC(self, npc)
if npc:GetTeam() == DOTA_TEAM_GOODGUYS then
return
end
local s = self:getNpcStatScale()
local result = s
npc:SetBaseMaxHealth(npc:GetMaxHealth() * result)
npc:SetMaxHealth(npc:GetMaxHealth() * result)
npc:SetHealth(npc:GetMaxHealth())
npc:SetBaseHealthRegen(npc:GetBaseHealthRegen() * result)
npc:SetBaseDamageMin(npc:GetBaseDamageMin() * result)
npc:SetBaseDamageMax(npc:GetBaseDamageMax() * result)
_G.Difficulter = s
end
DifficultyManager.CONTRACT_INVENTORY_CAP = 30
____exports.Difficulty = DifficultyManager:getInstance()
return ____exports