local ____lualib = require("lualib_bundle") local Set = ____lualib.Set local __TS__New = ____lualib.__TS__New local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite local ____exports = {} local grantContractCreepAbility function grantContractCreepAbility(self, unit, abilityName) if not IsValidEntity(unit) or not unit:IsAlive() then return end if unit:FindAbilityByName(abilityName) then return end do pcall(function() local ab = unit:AddAbility(abilityName) if ab ~= nil then local maxLevel = ab:GetMaxLevel() ab:SetLevel(maxLevel > 0 and maxLevel or 1) end end) end end --- Сколько вариантов названия «приговор» в локализации (#ds_sentence_title_001 …). ____exports.DEATH_SENTENCE_TITLE_INDEX_MAX = 100 --- Восстановление прочности контракта до максимума за донат-осколки (магазин). ____exports.DEATH_SENTENCE_CONTRACT_REPAIR_DONATE_COST = 25 --- Только усложнения, дающие крипу способность из wave_creep_abilities (без чистых статов / глобальных модификаторов). ____exports.DEATH_SENTENCE_COMPLICATION_POOL = { "ds_complication_explode", "ds_complication_zombie_virus", "ds_complication_ghost_evasive", "ds_complication_phasing_march", "ds_complication_zombie_armor_decress", "ds_complication_toxin", "ds_complication_full_brutality", "ds_complication_desperate_vampirism", "ds_complication_head_leap", "ds_complication_bone_armor" } --- Сколько случайных «дебаффов» волн (усложнений) по редкости приговора. function ____exports.getDeathSentenceComplicationPickCount(self, rarity) if rarity == "common" then return 0 end if rarity == "rare" then return 1 end if rarity == "epic" then return 2 end if rarity == "legendary" then return 3 end return 4 end --- Шанс (1…100), что при появлении врага на него повесится одна строка усложнения из приговора. -- Броски по строкам независимы — одному юниту может достаться сразу несколько абилок. ____exports.DEATH_SENTENCE_CONTRACT_CREEP_ABILITY_SPAWN_CHANCE_PCT = 25 --- Юниты ивентов (виспы, рыбалка, бомбы) — без пассивок усложнения приговора при спавне. local DEATH_SENTENCE_CONTRACT_BUFF_BLOCKED_UNITS = __TS__New(Set, {"npc_wisps", "npc_fish_1", "npc_fish_2", "npc_bomb"}) --- Усложнение приговора → пассивка крипа из wave_creep_abilities (только то, что вешается при спавне). local DEATH_SENTENCE_COMPLICATION_CREEP_ABILITY = { ds_complication_explode = "zombie_death_explosion", ds_complication_zombie_virus = "zombie_virus", ds_complication_ghost_evasive = "ghost_evasive", ds_complication_phasing_march = "wave_phasing_march", ds_complication_zombie_armor_decress = "zombie_armor_decress", ds_complication_toxin = "toxin", ds_complication_full_brutality = "wave_full_brutality", ds_complication_desperate_vampirism = "wave_desperate_vampirism", ds_complication_head_leap = "contract_head_leap", ds_complication_bone_armor = "bone_armor" } local function roundMult2(self, x) return math.floor(x * 100 + 0.5) / 100 end local function rollRarity(self) local r = RandomInt(1, 100) if r <= 40 then return "common" end if r <= 70 then return "rare" end if r <= 90 then return "epic" end if r <= 98 then return "legendary" end return "mythic" end local function rollRewardMultiplierForRarity(self, r) if r == "common" then return roundMult2( nil, RandomFloat(3, 4) ) end if r == "rare" then return roundMult2( nil, RandomFloat(4, 5) ) end if r == "epic" then return roundMult2( nil, RandomFloat(5, 6) ) end if r == "legendary" then return roundMult2( nil, RandomFloat(6, 7) ) end return roundMult2( nil, RandomFloat(7, 9) ) end --- Детерминированный индекс названия для старых записей без titleIndex. function ____exports.inferDeathSentenceTitleIndexFromInstanceId(self, instanceId) local h = 0 do local i = 0 while i < #instanceId do local ch = string.byte(instanceId, i + 1) or 0 h = (h * 31 + ch) % ____exports.DEATH_SENTENCE_TITLE_INDEX_MAX i = i + 1 end end return h + 1 end --- Детерминированная прочность 1…5 для старых записей без поля (без RNG при каждом GET). function ____exports.inferDeathSentenceDurabilityFromInstanceId(self, instanceId) local h = 0 do local i = 0 while i < #instanceId do local ch = string.byte(instanceId, i + 1) or 0 h = (h * 31 + ch) % 1000000 i = i + 1 end end return h % 5 + 1 end function ____exports.rollDeathSentenceContractDurability(self) return RandomInt(1, 5) end function ____exports.normalizeDeathSentenceContractDurability(self, raw, instanceId) local n = type(raw) == "number" and raw or tonumber(raw) if type(n) == "number" and __TS__NumberIsFinite(n) then local f = math.floor(n) if f >= 1 and f <= 5 then return f end end return ____exports.inferDeathSentenceDurabilityFromInstanceId(nil, instanceId) end --- Макс. прочность из JSON; если поля нет или битое — не ниже текущей `currentDurability`. function ____exports.normalizeDeathSentenceContractDurabilityMax(self, rawMax, currentDurability, instanceId) local n = type(rawMax) == "number" and rawMax or tonumber(rawMax) local m if type(n) == "number" and __TS__NumberIsFinite(n) then local f = math.floor(n) if f >= 1 and f <= 5 then m = f else m = currentDurability end else m = currentDurability end return math.max(m, currentDurability) end function ____exports.normalizeDeathSentenceTitleIndex(self, raw, instanceId) local n = type(raw) == "number" and raw or tonumber(raw) if type(n) ~= "number" or not __TS__NumberIsFinite(n) then return ____exports.inferDeathSentenceTitleIndexFromInstanceId(nil, instanceId) end local f = math.floor(n) if f < 1 or f > ____exports.DEATH_SENTENCE_TITLE_INDEX_MAX then return ____exports.inferDeathSentenceTitleIndexFromInstanceId(nil, instanceId) end return f end --- Пыль (dust_currency) за разбор приговора. function ____exports.getDeathSentenceDismantleShardReward(self, rarity) local base = { common = 15, rare = 35, epic = 70, legendary = 120, mythic = 200 } return base[rarity] or 15 end --- Сколько секунд вычитается из дня за один слот усложнения «короткий день». ____exports.DEATH_SENTENCE_SHORT_DAY_SEC_PER_STACK = 60 local SCARCE_LOOT_DROP_FACTOR_PER_STACK = 0.5 --- Множитель шанса выпадения предметов с юнитов (см. ItemDropSystem): каждый слот `ds_complication_scarce_loot` даёт ×0.5. function ____exports.getDeathSentenceItemDropChanceMultiplier(self, instance) if not instance then return 1 end local stacks = 0 for ____, cid in ipairs(instance.complicationIds) do if cid == "ds_complication_scarce_loot" then stacks = stacks + 1 end end if stacks <= 0 then return 1 end return math.pow(SCARCE_LOOT_DROP_FACTOR_PER_STACK, stacks) end local function rollComplicationIdsForRarity(self, rarity) local need = ____exports.getDeathSentenceComplicationPickCount(nil, rarity) if need <= 0 then return {} end local out = {} while #out < need do local pool = {unpack(____exports.DEATH_SENTENCE_COMPLICATION_POOL)} do local i = #pool - 1 while i > 0 do local j = RandomInt(0, i) local tmp = pool[i + 1] pool[i + 1] = pool[j + 1] pool[j + 1] = tmp i = i - 1 end end do local k = 0 while k < #pool and #out < need do out[#out + 1] = pool[k + 1] k = k + 1 end end end return out end --- Один экземпляр в общем ростере (слот в инвентаре). function ____exports.generateDeathSentenceContractInstance(self, slotIndex) local rarity = rollRarity(nil) local d = ____exports.rollDeathSentenceContractDurability(nil) return { instanceId = (("ds_ci_" .. tostring(slotIndex)) .. "_") .. tostring(RandomInt(10000, 99999999)), serial = slotIndex + 1, titleIndex = RandomInt(1, ____exports.DEATH_SENTENCE_TITLE_INDEX_MAX), rarity = rarity, rewardMultiplier = rollRewardMultiplierForRarity(nil, rarity), traitId = "none", complicationIds = rollComplicationIdsForRarity(nil, rarity), durability = d, durabilityMax = d } end --- Создание экземпляра контракта с принудительной редкостью (для наград конца матча). function ____exports.generateDeathSentenceContractInstanceWithRarity(self, slotIndex, rarity) local d = ____exports.rollDeathSentenceContractDurability(nil) return { instanceId = (("ds_ci_" .. tostring(slotIndex)) .. "_") .. tostring(RandomInt(10000, 99999999)), serial = slotIndex + 1, titleIndex = RandomInt(1, ____exports.DEATH_SENTENCE_TITLE_INDEX_MAX), rarity = rarity, rewardMultiplier = rollRewardMultiplierForRarity(nil, rarity), traitId = "none", complicationIds = rollComplicationIdsForRarity(nil, rarity), durability = d, durabilityMax = d } end --- После настройки длины дня в лобби: каждый слот `ds_complication_short_day` у выигравшего приговора -- уменьшает длительность следующего дня на `DEATH_SENTENCE_SHORT_DAY_SEC_PER_STACK` секунд (не ниже порога менеджера). function ____exports.applyDeathSentenceContractDayDurationAdjustments(self, instance) if not IsServer() or not instance then return end local stacks = 0 for ____, cid in ipairs(instance.complicationIds) do if cid == "ds_complication_short_day" then stacks = stacks + 1 end end if stacks <= 0 then return end do pcall(function() local ____require_result_0 = require("DayNightCycleManager") local DayNightCycleManager = ____require_result_0.DayNightCycleManager DayNightCycleManager:getInstance():adjustNextDayDurationBy(-____exports.DEATH_SENTENCE_SHORT_DAY_SEC_PER_STACK * stacks) end) end end --- После базового скейла сложности в GameMode (враги). function ____exports.applyDeathSentenceContractOnEnemySpawn(self, unit, instance) if not IsServer() or not IsValidEntity(unit) then return end if unit:GetTeam() == DOTA_TEAM_GOODGUYS then return end if not instance then return end local unitName = unit:GetUnitName() if unitName and DEATH_SENTENCE_CONTRACT_BUFF_BLOCKED_UNITS:has(unitName) then return end local chance = ____exports.DEATH_SENTENCE_CONTRACT_CREEP_ABILITY_SPAWN_CHANCE_PCT for ____, cid in ipairs(instance.complicationIds) do do local abilityName = DEATH_SENTENCE_COMPLICATION_CREEP_ABILITY[cid] if not abilityName then goto __continue60 end if RandomInt(1, 100) > chance then goto __continue60 end grantContractCreepAbility(nil, unit, abilityName) end ::__continue60:: end end return ____exports