Files
Dota-Zombie-Invasion/scripts/vscripts/cards/cardsystem.lua
T
achmad 599339e225 feat: make all cards use 1 deck slot regardless of rarity
Override getCardDeckSlots to always return 1 so mythic and higher
rarity cards don't take multiple slots in the 30-slot deck.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 04:10:03 +07:00

4198 lines
159 KiB
Lua

local ____lualib = require("lualib_bundle")
local __TS__Number = ____lualib.__TS__Number
local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite
local __TS__Class = ____lualib.__TS__Class
local __TS__New = ____lualib.__TS__New
local __TS__StringReplace = ____lualib.__TS__StringReplace
local __TS__ObjectEntries = ____lualib.__TS__ObjectEntries
local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray
local __TS__StringTrim = ____lualib.__TS__StringTrim
local __TS__Spread = ____lualib.__TS__Spread
local __TS__StringIncludes = ____lualib.__TS__StringIncludes
local __TS__StringSplit = ____lualib.__TS__StringSplit
local __TS__Delete = ____lualib.__TS__Delete
local __TS__ArrayFindIndex = ____lualib.__TS__ArrayFindIndex
local __TS__ArrayFind = ____lualib.__TS__ArrayFind
local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes
local Set = ____lualib.Set
local __TS__Iterator = ____lualib.__TS__Iterator
local __TS__ParseInt = ____lualib.__TS__ParseInt
local __TS__NumberIsNaN = ____lualib.__TS__NumberIsNaN
local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys
local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter
local __TS__ArrayIndexOf = ____lualib.__TS__ArrayIndexOf
local __TS__ArraySplice = ____lualib.__TS__ArraySplice
local __TS__SparseArrayNew = ____lualib.__TS__SparseArrayNew
local __TS__SparseArrayPush = ____lualib.__TS__SparseArrayPush
local __TS__SparseArraySpread = ____lualib.__TS__SparseArraySpread
local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign
local __TS__ArraySort = ____lualib.__TS__ArraySort
local __TS__ArrayMap = ____lualib.__TS__ArrayMap
local __TS__ArraySlice = ____lualib.__TS__ArraySlice
local __TS__ObjectValues = ____lualib.__TS__ObjectValues
local __TS__ArrayFrom = ____lualib.__TS__ArrayFrom
local __TS__TypeOf = ____lualib.__TS__TypeOf
local ____exports = {}
local ____crystal_currency = require("crystal_currency")
local CrystalCurrency = ____crystal_currency.CrystalCurrency
local ____modifier_stats_multiplier = require("modifiers.modifier_stats_multiplier")
local invalidateStatsMultiplierSumCache = ____modifier_stats_multiplier.invalidateStatsMultiplierSumCache
local ____server_config = require("server_config")
local SERVER_CONFIG = ____server_config.SERVER_CONFIG
local ____api_helper = require("api_helper")
local setApiHeaders = ____api_helper.setApiHeaders
local ____card_catalog = require("card_catalog")
local DEFAULT_DECK_CARD_IDS = ____card_catalog.DEFAULT_DECK_CARD_IDS
local DECK_BUILDER_SLOT_CAPACITY = ____card_catalog.DECK_BUILDER_SLOT_CAPACITY
local getDeckBuilderUnlockRequiredMessage = ____card_catalog.getDeckBuilderUnlockRequiredMessage
local ____real_lobby_player = require("utils.real_lobby_player")
local isRealLobbyPlayer = ____real_lobby_player.isRealLobbyPlayer
local ____card_slag = require("cards.card_slag")
local isEmptySelectionCardId = ____card_slag.isEmptySelectionCardId
local isSlagCardId = ____card_slag.isSlagCardId
local SLAG_CARD_ID = ____card_slag.SLAG_CARD_ID
local ____modifier_card_cursed = require("cards.modifier_card_cursed")
local addCursedStack = ____modifier_card_cursed.addCursedStack
local ____modifier_card_greed = require("cards.modifier_card_greed")
local isGreedPoolCardId = ____modifier_card_greed.isGreedPoolCardId
local registerHiddenGreedCard = ____modifier_card_greed.registerHiddenGreedCard
local updateGreedForHero = ____modifier_card_greed.updateGreedForHero
local DEFAULT_DECK_CARD_WEIGHT = 1
local CARD_UPGRADE_MAX_LEVEL = 3
local function isMirrorProtectedCardId(self, cardId)
do
local function ____catch()
return true, false
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
local card80 = require("cards.examples.card_80")
local ____opt_0 = card80.isFrostmourneMirrorProtectedCardId
return true, (____opt_0 and ____opt_0(
card80,
math.floor(__TS__Number(cardId))
)) == true
end)
if not ____try then
____hasReturned, ____returnValue = ____catch()
end
if ____hasReturned then
return ____returnValue
end
end
end
--- Качество карт
____exports.CardQuality = CardQuality or ({})
____exports.CardQuality.COMMON = 1
____exports.CardQuality[____exports.CardQuality.COMMON] = "COMMON"
____exports.CardQuality.RARE = 2
____exports.CardQuality[____exports.CardQuality.RARE] = "RARE"
____exports.CardQuality.EPIC = 3
____exports.CardQuality[____exports.CardQuality.EPIC] = "EPIC"
____exports.CardQuality.LEGENDARY = 4
____exports.CardQuality[____exports.CardQuality.LEGENDARY] = "LEGENDARY"
____exports.CardQuality.MYTHIC = 5
____exports.CardQuality[____exports.CardQuality.MYTHIC] = "MYTHIC"
--- Карта 79 «Эхо выбора»: нельзя дублировать пустые, Фростморн/осколки и мифические карты.
local function isCard79RepeatBlockedCardId(self, cardId)
local id = math.floor(__TS__Number(cardId))
if not __TS__NumberIsFinite(id) or id <= 0 then
return true
end
if isEmptySelectionCardId(nil, id) or isMirrorProtectedCardId(nil, id) then
return true
end
local cardData = ____exports.CardSystem.cardData[id]
return (cardData and cardData.quality) == ____exports.CardQuality.MYTHIC
end
--- Базовый класс для карт
____exports.CardBase = __TS__Class()
local CardBase = ____exports.CardBase
CardBase.name = "CardBase"
CardBase.____file_path = "scripts/vscripts/cards/CardSystem.lua"
function CardBase.prototype.____constructor(self, hero, cardKey)
self.modifiers = nil
self.id = hero:GetPlayerID()
self.cardKey = cardKey
self:OnCreated()
self:ModifierRefresh()
end
function CardBase.prototype.OnCreated(self)
end
function CardBase.prototype.OnDestroy(self)
if self.modifiers and not self.modifiers:IsNull() then
self.modifiers:Destroy()
end
end
function CardBase.prototype.OnTransfer(self)
end
function CardBase.prototype.GetHero(self)
local player = PlayerResource:GetPlayer(self.id)
return player:GetAssignedHero()
end
function CardBase.prototype.GetPlayer(self)
return PlayerResource:GetPlayer(self.id)
end
function CardBase.prototype.GetCardLevelSnapshot(self)
local player = self:GetPlayer()
local cardId = __TS__Number(self.cardKey)
if not player or not __TS__NumberIsFinite(cardId) or cardId <= 0 then
return 1
end
local ____opt_4 = player.cardSystem
local level = ____opt_4 and ____opt_4:GetCardLevel(math.floor(cardId))
return __TS__NumberIsFinite(level) and math.max(
1,
math.floor(level)
) or 1
end
function CardBase.prototype.ModifierRefresh(self)
local hero = self:GetHero()
local modName = self:GetModifierName()
local reusedExistingModifier = false
if self.modifiers and not self.modifiers:IsNull() then
self.modifiers:Destroy()
self.modifiers = nil
end
if modName then
if IsServer() then
local existing = hero:FindModifierByName(modName)
if existing and not existing:IsNull() then
local currentStacks = math.max(
0,
math.floor(existing:GetStackCount() or 0)
)
existing:SetStackCount(currentStacks + 1)
existing:ForceRefresh()
hero:CalculateStatBonus(true)
self.modifiers = nil
reusedExistingModifier = true
end
end
if not reusedExistingModifier then
self.modifiers = hero:AddNewModifier(
hero,
getModifierSourceAbility(nil, hero),
modName,
{card_level = self:GetCardLevelSnapshot()}
)
end
end
if IsServer() then
local cardId = __TS__Number(self.cardKey)
if isGreedPoolCardId(nil, cardId) then
local player = self:GetPlayer()
updateGreedForHero(nil, hero, player and player.cardSystem)
end
end
end
function CardBase.prototype.IsHidden(self)
return false
end
--- Регистрация класса карты
function ____exports.RegisterCard(self, target)
local className = target.name
____exports.CardSystem.cardTypes[className] = target
end
--- Парсинг значений в описании карты
function ____exports.ParseCardDescription(self, description, values)
if not values then
return description
end
local parsedDescription = description
for ____, ____value in ipairs(__TS__ObjectEntries(values)) do
local key = ____value[1]
local value = ____value[2]
local pattern = __TS__New(RegExp, ("%" .. key) .. "%", "g")
parsedDescription = __TS__StringReplace(
parsedDescription,
pattern,
tostring(value)
)
end
return parsedDescription
end
--- Основная система карт
____exports.CardSystem = __TS__Class()
local CardSystem = ____exports.CardSystem
CardSystem.name = "CardSystem"
CardSystem.____file_path = "scripts/vscripts/cards/CardSystem.lua"
function CardSystem.prototype.____constructor(self, playerId)
self.playerItems = {}
self.items = {}
self.itemsId = {}
self.decks = {}
self.activeDeckIndex = 0
self.cardPieces = {}
self.newCardPieces = {}
self.rerollCost = 0
self.cardsTaken = 0
self.cardSelectionQueue = {}
self.isShowingCardSelection = false
self.currentSelectionSource = "unknown"
self.currentSelectionSourceChain = {}
self.currentSelectionToken = 0
self.poolEntriesById = {}
self.poolNextEntrySeq = 1
self.rerollQualityFilter = nil
self.guaranteedQualityRerolls = {}
self.card49FreeRerollCharges = 0
self.card6FreeRerollCharges = 0
self.card88FreeRerollCharges = 0
self.card6FreeRerollLevelGranted = false
self.card51MorningGoldEarned = 0
self.repeatNextSelectedCardOnce = false
self.cardLevelsLoaded = false
self.cardLevelsWaitTimerStarted = false
self.playerId = playerId
self.pool = pool(nil)
self:Init()
Timers:CreateTimer(
1,
function()
self:SyncPool()
return nil
end
)
end
function CardSystem.getCardDeckSlots(self, cardId)
return 1
end
function CardSystem.getDeckUsedSlots(self, cards)
local total = 0
for ____, id in ipairs(cards) do
total = total + ____exports.CardSystem:getCardDeckSlots(id)
end
return total
end
function CardSystem.canAddCardToDeckSlots(self, cards, cardId, capacity)
return ____exports.CardSystem:getDeckUsedSlots(cards) + ____exports.CardSystem:getCardDeckSlots(cardId) <= capacity
end
function CardSystem.prototype.ForceDeckBuilderSync(self)
self:LoadDecks()
self:SyncDecks()
self:SyncCardData()
self:SyncPool()
end
function CardSystem.prototype.Init(self)
local restoredFromRuntimeState = self:RestoreRuntimeStateIfPresent()
if not restoredFromRuntimeState then
Timers:CreateTimer(
1,
function()
self:LoadDecks()
self:loadCardLevelsFromServer()
self:InitCardPool()
end
)
else
self:loadCardLevelsFromServer()
self:SyncDecks()
self:SyncPool()
self:SyncActiveCards()
self:SyncRerollCost()
end
self:SyncDecks()
self:SyncCardData()
self:SyncRerollCost()
CustomGameEventManager:RegisterListener(
"card_selected",
function(_, event)
if self.playerId ~= event.PlayerID then
return
end
self:OnChooseCard(event.PlayerID, event.index, event.selection_token)
end
)
CustomGameEventManager:RegisterListener(
"card_reroll",
function(_, event)
if self.playerId ~= event.PlayerID then
return
end
self:RerollCards(event.PlayerID)
end
)
CustomGameEventManager:RegisterListener(
"deck_new",
function(_, event)
if self.playerId ~= event.PlayerID then
return
end
self:CreateNewDeck(event.name)
end
)
CustomGameEventManager:RegisterListener(
"deck_save",
function(_, event)
if self.playerId ~= event.PlayerID then
return
end
if event.index == nil or event.data == nil and event.cards == nil then
return
end
local cardsData = event.cards or event.data
if not cardsData then
return
end
self:SaveDeck(event.index, cardsData, event.name)
end
)
CustomGameEventManager:RegisterListener(
"deck_delete",
function(_, event)
if self.playerId ~= event.PlayerID then
return
end
self:DeleteDeck(event.index)
end
)
CustomGameEventManager:RegisterListener(
"deck_activate",
function(_, event)
if self.playerId ~= event.PlayerID then
return
end
self:ActivateDeck(event.index)
end
)
CustomGameEventManager:RegisterListener(
"reset_card_pool",
function(_, event)
if self.playerId ~= event.PlayerID then
return
end
self:ResetCardPool()
end
)
CustomGameEventManager:RegisterListener(
"hide_card_selection",
function(_, event)
if self.playerId ~= event.PlayerID then
return
end
self:HideCardSelection()
end
)
CustomGameEventManager:RegisterListener(
"init_card_system",
function(_, event)
if self.playerId ~= event.PlayerID then
return
end
self:SyncDecks()
self:SyncPool()
end
)
CustomGameEventManager:RegisterListener(
"request_pool_sync",
function(_, event)
if self.playerId ~= event.PlayerID then
return
end
self:SyncPool()
end
)
CustomGameEventManager:RegisterListener(
"request_available_cards",
function(_, event)
if self.playerId ~= event.PlayerID then
return
end
self:LoadDecks()
self:loadCardLevelsFromServer()
self:SyncDecks()
self:SyncCardData()
self:SyncPool()
end
)
CustomGameEventManager:RegisterListener(
"card_craft",
function(_, event)
if self.playerId ~= event.PlayerID then
return
end
self:CraftOrUpgradeCard(event.cardId, event.allowDebris)
end
)
ListenToGameEvent(
"game_rules_state_change",
function()
local state = GameRules:State_Get()
if state == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then
self:InitCardPool()
end
end,
nil
)
end
function CardSystem.prototype.RestoreRuntimeStateIfPresent(self)
local key = tostring(self.playerId)
local snapshot = ____exports.CardSystem.runtimeStateByPlayerId[key]
if not snapshot then
return false
end
self.decks = snapshot.decks or ({})
self.activeDeckIndex = snapshot.activeDeckIndex or 0
self.itemsId = __TS__ArrayIsArray(snapshot.itemsId) and ({unpack(snapshot.itemsId)}) or ({})
self.poolEntriesById = {}
self.poolNextEntrySeq = math.max(
1,
math.floor(__TS__Number(snapshot.poolNextEntrySeq or 1))
)
self.cardsTaken = math.max(
0,
math.floor(__TS__Number(snapshot.cardsTaken) or 0)
)
self.rerollCost = math.max(
0,
math.floor(__TS__Number(snapshot.rerollCost) or 0)
)
self.card49FreeRerollCharges = math.max(
0,
math.floor(__TS__Number(snapshot.card49FreeRerollCharges) or 0)
)
self.card6FreeRerollCharges = math.max(
0,
math.floor(__TS__Number(snapshot.card6FreeRerollCharges) or 0)
)
self.card88FreeRerollCharges = math.max(
0,
math.floor(__TS__Number(snapshot.card88FreeRerollCharges) or 0)
)
self.card6FreeRerollLevelGranted = snapshot.card6FreeRerollLevelGranted == true
self.card51MorningGoldEarned = math.max(
0,
math.floor(__TS__Number(snapshot.card51MorningGoldEarned) or 0)
)
self.pool:clear()
if __TS__ArrayIsArray(snapshot.poolData) then
for ____, item in ipairs(snapshot.poolData) do
do
local entryId = __TS__StringTrim(tostring(item.entry_id or ""))
local id = math.floor(__TS__Number(item.index))
local weight = math.floor(__TS__Number(item.weight))
if #entryId <= 0 or not __TS__NumberIsFinite(id) or id <= 0 or not __TS__NumberIsFinite(weight) or weight <= 0 then
goto __continue75
end
self.poolEntriesById[entryId] = {
entryId = entryId,
cardId = id,
weight = weight,
sourceChain = __TS__ArrayIsArray(item.source_chain) and ({__TS__Spread(item.source_chain)}) or ({})
}
end
::__continue75::
end
end
self:RebuildPoolFromEntries()
return true
end
function CardSystem.prototype.CaptureRuntimeState(self)
local poolData = {}
for entryId in pairs(self.poolEntriesById) do
do
local entry = self.poolEntriesById[entryId]
if not entry or entry.weight <= 0 then
goto __continue79
end
poolData[#poolData + 1] = {
entry_id = entry.entryId,
index = entry.cardId,
weight = math.floor(entry.weight),
source_chain = entry.sourceChain and #entry.sourceChain > 0 and ({unpack(entry.sourceChain)}) or nil
}
end
::__continue79::
end
local state = {
poolData = poolData,
poolNextEntrySeq = self.poolNextEntrySeq,
itemsId = {unpack(self.itemsId)},
cardsTaken = self.cardsTaken,
rerollCost = self.rerollCost,
card49FreeRerollCharges = self.card49FreeRerollCharges,
card6FreeRerollCharges = self.card6FreeRerollCharges,
card88FreeRerollCharges = self.card88FreeRerollCharges,
card6FreeRerollLevelGranted = self.card6FreeRerollLevelGranted,
card51MorningGoldEarned = self.card51MorningGoldEarned,
decks = self.decks,
activeDeckIndex = self.activeDeckIndex
}
____exports.CardSystem.runtimeStateByPlayerId[tostring(self.playerId)] = state
end
function CardSystem.prototype.NormalizeSourceToken(self, sourceRaw)
local source = __TS__StringTrim(tostring(sourceRaw or "unknown"))
return #source > 0 and source or "unknown"
end
function CardSystem.prototype.IsNonGameplaySourceToken(self, tokenRaw)
local token = string.lower(self:NormalizeSourceToken(tokenRaw))
return token == "unknown" or token == "deck_initial_pool" or token == "manual_pool_add" or token == "store_purchase_card" or token == "duplicate_current_pool" or token == "duplicate_unselected_current_options" or token == "threshold_empty_card_bonus"
end
function CardSystem.prototype.BuildSourceChain(self, sourceRaw, baseChain)
local chain = {}
local function pushToken(____, tokenRaw)
local token = self:NormalizeSourceToken(tokenRaw)
if self:IsNonGameplaySourceToken(token) then
return
end
local prev = #chain > 0 and chain[#chain] or nil
if prev ~= token then
chain[#chain + 1] = token
end
end
if baseChain and #baseChain > 0 then
for ____, token in ipairs(baseChain) do
pushToken(nil, token)
end
end
local normalizedSource = self:NormalizeSourceToken(sourceRaw)
if __TS__StringIncludes(normalizedSource, "->") then
local tokens = __TS__StringSplit(normalizedSource, "->")
for ____, token in ipairs(tokens) do
pushToken(nil, token)
end
else
pushToken(nil, normalizedSource)
end
return chain
end
function CardSystem.prototype.SerializeSourceChain(self, chain)
if not chain or #chain <= 0 then
return ""
end
return table.concat(chain, " -> ")
end
function CardSystem.prototype.SetCurrentSelectionSource(self, sourceRaw, sourceChain)
local source = self:NormalizeSourceToken(sourceRaw)
self.currentSelectionSource = source
self.currentSelectionSourceChain = sourceChain and #sourceChain > 0 and ({unpack(sourceChain)}) or self:BuildSourceChain(source)
self.currentSelectionToken = self.currentSelectionToken + 1
end
function CardSystem.prototype.BuildPoolEntrySourceChain(self, sourceRaw, sourceChain)
local chainBase = sourceChain and #sourceChain > 0 and sourceChain or self.effectSourceChainContext
return self:BuildSourceChain(sourceRaw, chainBase)
end
function CardSystem.prototype.CreatePoolEntry(self, cardId, weight, sourceRaw, sourceChain)
local normalizedCardId = math.floor(__TS__Number(cardId))
local normalizedWeight = math.floor(__TS__Number(weight))
if not __TS__NumberIsFinite(normalizedCardId) or normalizedCardId <= 0 or not __TS__NumberIsFinite(normalizedWeight) or normalizedWeight <= 0 then
return nil
end
local ____self_10, ____poolNextEntrySeq_11 = self, "poolNextEntrySeq"
local ____self_poolNextEntrySeq_12 = ____self_10[____poolNextEntrySeq_11]
____self_10[____poolNextEntrySeq_11] = ____self_poolNextEntrySeq_12 + 1
local entryId = "e" .. tostring(____self_poolNextEntrySeq_12)
local chain = self:BuildPoolEntrySourceChain(sourceRaw, sourceChain)
self.poolEntriesById[entryId] = {entryId = entryId, cardId = normalizedCardId, weight = normalizedWeight, sourceChain = chain}
return entryId
end
function CardSystem.prototype.RebuildPoolFromEntries(self)
self.pool:clear()
for entryId in pairs(self.poolEntriesById) do
do
local entry = self.poolEntriesById[entryId]
if not entry or entry.weight <= 0 then
goto __continue102
end
self.pool:add(entry.cardId, entry.weight)
end
::__continue102::
end
end
function CardSystem.prototype.GetPoolEntrySourceChain(self, entryId)
if not entryId then
return nil
end
local entry = self.poolEntriesById[entryId]
if not entry or not entry.sourceChain or #entry.sourceChain <= 0 then
return nil
end
return {unpack(entry.sourceChain)}
end
function CardSystem.prototype.PickPoolEntryIdForCardId(self, cardId)
local candidates = {}
local total = 0
for entryId in pairs(self.poolEntriesById) do
do
local entry = self.poolEntriesById[entryId]
if not entry or entry.cardId ~= cardId or entry.weight <= 0 then
goto __continue109
end
candidates[#candidates + 1] = entry
total = total + entry.weight
end
::__continue109::
end
if #candidates <= 0 or total <= 0 then
return nil
end
local roll = RandomInt(1, total)
local cursor = 0
for ____, entry in ipairs(candidates) do
cursor = cursor + entry.weight
if roll <= cursor then
return entry.entryId
end
end
local ____opt_13 = candidates[1]
return ____opt_13 and ____opt_13.entryId
end
function CardSystem.prototype.SelectEntryIdsForCards(self, cardIds)
local remainingByEntryId = {}
for entryId in pairs(self.poolEntriesById) do
local entry = self.poolEntriesById[entryId]
if entry and entry.weight > 0 then
remainingByEntryId[entryId] = entry.weight
end
end
local selected = {}
for ____, cardId in ipairs(cardIds) do
do
if not __TS__NumberIsFinite(cardId) or cardId <= 0 or cardId == 404 then
selected[#selected + 1] = nil
goto __continue120
end
local candidates = {}
local total = 0
for entryId in pairs(remainingByEntryId) do
do
local left = remainingByEntryId[entryId]
local entry = self.poolEntriesById[entryId]
if not entry or entry.cardId ~= cardId or left <= 0 then
goto __continue122
end
candidates[#candidates + 1] = {entryId = entryId, weight = left}
total = total + left
end
::__continue122::
end
if #candidates <= 0 or total <= 0 then
selected[#selected + 1] = nil
goto __continue120
end
local roll = RandomInt(1, total)
local cursor = 0
local picked = candidates[1].entryId
for ____, candidate in ipairs(candidates) do
cursor = cursor + candidate.weight
if roll <= cursor then
picked = candidate.entryId
break
end
end
selected[#selected + 1] = picked
remainingByEntryId[picked] = math.max(0, (remainingByEntryId[picked] or 0) - 1)
end
::__continue120::
end
return selected
end
function CardSystem.prototype.ConsumePoolEntryWeight(self, entryId, amount)
if amount == nil then
amount = 1
end
if not entryId then
return
end
local entry = self.poolEntriesById[entryId]
if not entry then
return
end
entry.weight = math.max(
0,
entry.weight - math.max(
1,
math.floor(amount)
)
)
if entry.weight <= 0 then
__TS__Delete(self.poolEntriesById, entryId)
end
end
function CardSystem.prototype.ReinitCardPool(self)
self:InitCardPool()
end
function CardSystem.prototype.GetCardsTaken(self)
return self.cardsTaken
end
function CardSystem.prototype.GetActiveCardCount(self)
return #self.itemsId
end
function CardSystem.prototype.GetActiveCardCountExcludingInherent(self)
local n = 0
for ____, id in ipairs(self.itemsId) do
do
local cardData = ____exports.CardSystem.cardData[id]
if cardData and cardData.inherent == true then
goto __continue138
end
n = n + 1
end
::__continue138::
end
return n
end
function CardSystem.prototype.GetActiveCardCopies(self, cardId)
local fromItems = 0
for ____, id in ipairs(self.itemsId) do
if id == cardId then
fromItems = fromItems + 1
end
end
local fromModifiers = 0
local ____opt_15 = self:GetPlayer()
local hero = ____opt_15 and ____opt_15:GetAssignedHero()
if hero and IsValidEntity(hero) and hero:IsRealHero() then
local modName = "modifier_card_" .. tostring(cardId)
do
local i = 0
while i < hero:GetModifierCount() do
if hero:GetModifierNameByIndex(i) == modName then
fromModifiers = fromModifiers + 1
end
i = i + 1
end
end
end
return math.max(fromItems, fromModifiers)
end
function CardSystem.prototype.GetCardLevel(self, cardId)
local key = tostring(cardId)
local ____opt_17 = self.cardPieces[key]
local levelRaw = __TS__Number(____opt_17 and ____opt_17.level or 1)
return math.max(
1,
math.min(
CARD_UPGRADE_MAX_LEVEL,
math.floor(levelRaw)
)
)
end
function CardSystem.prototype.ResetCardPool(self)
self.cardsTaken = 0
self:InitCardPool()
end
function CardSystem.prototype.InitCardPool(self)
self.pool:clear()
self.poolEntriesById = {}
self.poolNextEntrySeq = 1
self.items = {}
self.itemsId = {}
self.rerollCost = 0
self.cardsTaken = 0
local activeDeckKey = tostring(self.activeDeckIndex)
local activeDeck = self.decks[activeDeckKey]
local normalizedCards = self:normalizeDeckCards(activeDeck)
if #normalizedCards > 0 then
self.decks[activeDeckKey] = {
name = self:resolveDeckName(activeDeck, self.activeDeckIndex),
cards = {unpack(normalizedCards)}
}
self:SyncDecks()
end
do
local i = 0
while i < #normalizedCards do
local cardId = normalizedCards[i + 1]
local cardData = ____exports.CardSystem.cardData[cardId]
if cardData and not cardData.disabled then
if cardData.inherent then
self:AddCard(
tostring(cardId),
nil,
{skipCurseStack = true}
)
else
self:CreatePoolEntry(cardId, 1, "deck_initial_pool")
end
end
i = i + 1
end
end
self:RebuildPoolFromEntries()
self:SyncPool()
self:SyncActiveCards()
self:SyncRerollCost()
Timers:CreateTimer(
0.5,
function()
self:SyncPool()
return nil
end
)
end
function CardSystem.prototype.HideCardSelection(self)
self.playerItems = {}
self:SyncPlayerItems()
self:SyncRerollCost()
self.isShowingCardSelection = false
self:SyncSelectionQueue()
self:SyncSelectionQueue()
self.cardSelectionQueue = {}
self:SyncSelectionQueue()
end
function CardSystem.prototype.ScheduleCard68CursedPick(self, excludeOptionIds, attempt)
if attempt == nil then
attempt = 0
end
local maxAttempts = 48
local delay = attempt == 0 and 0.12 or 0.05
Timers:CreateTimer(
delay,
function()
if not IsServer() then
return nil
end
if self.isShowingCardSelection or #self.cardSelectionQueue > 0 then
if attempt < maxAttempts then
self:ScheduleCard68CursedPick(excludeOptionIds, attempt + 1)
end
return nil
end
do
local function ____catch(err)
print("[CardSystem] card_68 cursed pick: " .. tostring(err))
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
local card68 = require("cards.examples.card_68")
local slots = card68.CARD_68_OPTION_SLOTS or 3
local poolCards = self:GetPoolCards()
local ____opt_19 = self:GetPlayer()
local hero = ____opt_19 and ____opt_19:GetAssignedHero()
local hasCard69 = hero and IsValidEntity(hero) and hero:IsRealHero() and hero:HasModifier("modifier_card_69")
local ____hasCard69_25
if hasCard69 then
local ____opt_21 = card68.buildAnyOfferIdsFromPool
____hasCard69_25 = ____opt_21 and ____opt_21(card68, poolCards, slots, excludeOptionIds) or ({})
else
local ____opt_23 = card68.buildCursedOfferIdsFromPool
____hasCard69_25 = ____opt_23 and ____opt_23(card68, poolCards, slots, excludeOptionIds) or ({})
end
local offerIds = ____hasCard69_25
if #offerIds <= 0 then
print(((("[CardSystem] card_68: в колоде нет подходящих карт для выбора (игрок " .. tostring(self.playerId)) .. ", card69=") .. (hasCard69 and "yes" or "no")) .. ").")
return true, nil
end
self:ShowCardSelectionExactOptions(offerIds, hasCard69 and "card_68_pool_any_pick" or "card_68_cursed_pick")
end)
if not ____try then
____hasReturned, ____returnValue = ____catch(____hasReturned)
end
if ____hasReturned then
return ____returnValue
end
end
return nil
end
)
end
function CardSystem.prototype.ProcessNextCardSelection(self)
if not self.cardLevelsLoaded then
self:StartWaitingForCardLevelsIfNeeded()
return
end
if #self.cardSelectionQueue == 0 then
return
end
local nextSelection = table.remove(self.cardSelectionQueue, 1)
self:SyncSelectionQueue()
if nextSelection.exactOptionIds ~= nil and #nextSelection.exactOptionIds > 0 then
self:ProcessCardSelectionExactIds(nextSelection.exactOptionIds, nextSelection.source, nextSelection.sourceChain, nextSelection.baseCount)
elseif nextSelection.forcedCardId ~= nil then
self:ProcessCardSelectionWithForcedCard(
nextSelection.count,
nextSelection.source,
nextSelection.forcedCardId,
nextSelection.sourceChain,
nextSelection.baseCount
)
else
self:ProcessCardSelection(
nextSelection.count,
nextSelection.source,
nextSelection.qualityFilter,
nextSelection.sourceChain,
nextSelection.baseCount
)
end
end
function CardSystem.prototype.GetCardSelectionQueueSize(self)
return #self.cardSelectionQueue
end
function CardSystem.prototype.ClearCardSelectionQueue(self)
self.cardSelectionQueue = {}
end
function CardSystem.prototype.IsShowingCardSelection(self)
return self.isShowingCardSelection
end
function CardSystem.prototype.SetRerollQualityFilter(self, qualityFilter)
self.rerollQualityFilter = qualityFilter
if qualityFilter then
else
end
end
function CardSystem.prototype.GetRerollQualityFilter(self)
return self.rerollQualityFilter
end
function CardSystem.prototype.AddGuaranteedQualityRerolls(self, quality, rerollsCount, cardsPerReroll, fallbackBehavior)
if fallbackBehavior == nil then
fallbackBehavior = "skip"
end
print("[CardSystem.AddGuaranteedQualityRerolls] ===== ДОБАВЛЕНИЕ В СИСТЕМУ =====")
print("[CardSystem.AddGuaranteedQualityRerolls] Игрок: " .. tostring(self.playerId))
print("[CardSystem.AddGuaranteedQualityRerolls] Качество: " .. tostring(quality))
print("[CardSystem.AddGuaranteedQualityRerolls] Количество реролов: " .. tostring(rerollsCount))
print("[CardSystem.AddGuaranteedQualityRerolls] Карт за рерол: " .. tostring(cardsPerReroll))
print("[CardSystem.AddGuaranteedQualityRerolls] Поведение при отсутствии: " .. fallbackBehavior)
print("[CardSystem.AddGuaranteedQualityRerolls] Текущее количество гарантированных реролов: " .. tostring(#self.guaranteedQualityRerolls))
local existingIndex = __TS__ArrayFindIndex(
self.guaranteedQualityRerolls,
function(____, item) return item.quality == quality end
)
if existingIndex ~= -1 then
print("[CardSystem.AddGuaranteedQualityRerolls] ✅ Найдены существующие реролы для качества " .. tostring(quality))
print("[CardSystem.AddGuaranteedQualityRerolls] Было реролов: " .. tostring(self.guaranteedQualityRerolls[existingIndex + 1].rerollsLeft))
local ____self_guaranteedQualityRerolls_index_26, ____rerollsLeft_27 = self.guaranteedQualityRerolls[existingIndex + 1], "rerollsLeft"
____self_guaranteedQualityRerolls_index_26[____rerollsLeft_27] = ____self_guaranteedQualityRerolls_index_26[____rerollsLeft_27] + rerollsCount
print("[CardSystem.AddGuaranteedQualityRerolls] Стало реролов: " .. tostring(self.guaranteedQualityRerolls[existingIndex + 1].rerollsLeft))
else
print("[CardSystem.AddGuaranteedQualityRerolls] ✅ Создаем новые реролы для качества " .. tostring(quality))
local ____self_guaranteedQualityRerolls_28 = self.guaranteedQualityRerolls
____self_guaranteedQualityRerolls_28[#____self_guaranteedQualityRerolls_28 + 1] = {quality = quality, rerollsLeft = rerollsCount, cardsPerReroll = cardsPerReroll, fallbackBehavior = fallbackBehavior}
print("[CardSystem.AddGuaranteedQualityRerolls] Добавлен новый элемент в очередь")
end
print("[CardSystem.AddGuaranteedQualityRerolls] Итоговое количество гарантированных реролов: " .. tostring(#self.guaranteedQualityRerolls))
print("[CardSystem.AddGuaranteedQualityRerolls] ==========================================")
end
function CardSystem.prototype.GetGuaranteedQualityRerolls(self, quality)
local item = __TS__ArrayFind(
self.guaranteedQualityRerolls,
function(____, item) return item.quality == quality end
)
return item and item.rerollsLeft or 0
end
function CardSystem.prototype.GetGuaranteedCardsPerReroll(self, quality)
local item = __TS__ArrayFind(
self.guaranteedQualityRerolls,
function(____, item) return item.quality == quality end
)
return item and item.cardsPerReroll or 0
end
function CardSystem.prototype.GetAllGuaranteedQualityRerolls(self)
return {unpack(self.guaranteedQualityRerolls)}
end
function CardSystem.prototype.ClearGuaranteedQualityRerolls(self)
self.guaranteedQualityRerolls = {}
end
function CardSystem.prototype.RefreshCard49DailyFreeReroll(self)
if self:HasActiveCard(49) then
self.card49FreeRerollCharges = math.min(____exports.CardSystem.CARD_49_FREE_STACK_CAP, self.card49FreeRerollCharges + 1)
self:SyncRerollCost()
end
end
function CardSystem.prototype.TryGrantCard6Level2FreeRerolls(self)
if self.card6FreeRerollLevelGranted then
return
end
if self:GetCardLevel(____exports.CardSystem.CARD_6_ID) < 2 then
return
end
local grant = math.max(
0,
math.floor(self:getCardValueByLevel(____exports.CardSystem.CARD_6_ID, "free_reroll_charges", 2))
)
if grant <= 0 then
return
end
self.card6FreeRerollLevelGranted = true
self.card6FreeRerollCharges = math.min(____exports.CardSystem.CARD_6_FREE_STACK_CAP, self.card6FreeRerollCharges + grant)
self:SyncRerollCost()
end
function CardSystem.prototype.GetCard6MinSelectionCount(self)
if not self:HasActiveCard(____exports.CardSystem.CARD_6_ID) then
return 0
end
return math.max(
0,
math.floor(self:getCardValueByLevel(____exports.CardSystem.CARD_6_ID, "min_card_choice", 0))
)
end
function CardSystem.prototype.getCardValueByLevel(self, cardId, key, fallback)
local ____opt_29 = self:GetPlayer()
local hero = ____opt_29 and ____opt_29:GetAssignedHero()
local resolver = require("cards.card_value_resolver")
local ____opt_31 = resolver.getCardValueByLevel
return ____opt_31 and ____opt_31(
resolver,
cardId,
hero,
key,
fallback
) or fallback
end
function CardSystem.prototype.GetCard6FreeRerollChargesForUse(self)
if not self:HasActiveCard(____exports.CardSystem.CARD_6_ID) or self:GetCardLevel(____exports.CardSystem.CARD_6_ID) < 2 then
return 0
end
return math.max(
0,
math.floor(self.card6FreeRerollCharges)
)
end
function CardSystem.prototype.GetCard88FreeRerollChargesForUse(self)
if not self:HasActiveCard(____exports.CardSystem.CARD_88_ID) then
return 0
end
return math.max(
0,
math.floor(self.card88FreeRerollCharges)
)
end
function CardSystem.prototype.TryGrantCard88FreeRerollsFromSlag(self, slagCount)
if slagCount == nil then
slagCount = 1
end
if not self:HasActiveCard(____exports.CardSystem.CARD_88_ID) then
return
end
local safeSlagCount = math.max(
0,
math.floor(slagCount)
)
if safeSlagCount <= 0 then
return
end
local grantPerSlag = math.max(
0,
math.floor(self:getCardValueByLevel(____exports.CardSystem.CARD_88_ID, "free_rerolls_on_slag", 2))
)
local grant = grantPerSlag * safeSlagCount
if grant <= 0 then
return
end
self.card88FreeRerollCharges = math.min(____exports.CardSystem.CARD_88_FREE_STACK_CAP, self.card88FreeRerollCharges + grant)
self:SyncRerollCost()
end
function CardSystem.prototype.ResolveSelectionQualityFilter(self, source, qualityFilter)
if source == "modifier_card_6_bonus_selection" and self:HasActiveCard(____exports.CardSystem.CARD_6_ID) and self:GetCardLevel(____exports.CardSystem.CARD_6_ID) >= 3 then
return {____exports.CardQuality.LEGENDARY, ____exports.CardQuality.MYTHIC}
end
return qualityFilter
end
function CardSystem.prototype.ShowCardSelection(self, count, source, qualityFilter)
if source == nil then
source = "unknown"
end
local resolvedQualityFilter = self:ResolveSelectionQualityFilter(source, qualityFilter)
local baseCount = math.max(
1,
math.floor(count)
)
local resolvedCount = self:ResolveCardSelectionCount(baseCount)
local sourceChain = self:BuildSourceChain(source, self.effectSourceChainContext)
print("[CardSystem.ShowCardSelection] ===== ПОКАЗ ВЫБОРА КАРТ =====")
print("[CardSystem.ShowCardSelection] Игрок: " .. tostring(self.playerId))
print("[CardSystem.ShowCardSelection] Запрошено карт: " .. tostring(count))
print("[CardSystem.ShowCardSelection] Итоговое количество карт: " .. tostring(resolvedCount))
print("[CardSystem.ShowCardSelection] Источник: " .. source)
print("[CardSystem.ShowCardSelection] Фильтр качества: " .. (resolvedQualityFilter and table.concat(resolvedQualityFilter, ", ") or "нет"))
print("[CardSystem.ShowCardSelection] Уже показываем выбор: " .. tostring(self.isShowingCardSelection))
print("[CardSystem.ShowCardSelection] Размер пула карт: " .. tostring(self.pool.len))
if not self.cardLevelsLoaded then
print("[CardSystem.ShowCardSelection] ⏳ Уровни карт ещё не загружены, откладываем показ в очередь")
self:AddToCardSelectionQueue(
resolvedCount,
baseCount,
source,
resolvedQualityFilter,
nil,
nil,
sourceChain
)
self:StartWaitingForCardLevelsIfNeeded()
return
end
if resolvedQualityFilter then
end
if self.isShowingCardSelection then
print(("[CardSystem.ShowCardSelection] ⚠️ Добавляем в очередь выбор из " .. tostring(resolvedCount)) .. " карт")
self:AddToCardSelectionQueue(
resolvedCount,
baseCount,
source,
resolvedQualityFilter,
nil,
nil,
sourceChain
)
return
end
print(("[CardSystem.ShowCardSelection] ✅ Показываем выбор из " .. tostring(resolvedCount)) .. " карт")
self:ProcessCardSelection(
resolvedCount,
source,
resolvedQualityFilter,
sourceChain,
baseCount
)
print("[CardSystem.ShowCardSelection] ======================================")
end
function CardSystem.prototype.ShowCardSelectionWithExtraChoices(self, baseCount, extraCount, source, qualityFilter)
if source == nil then
source = "unknown"
end
local normalizedBase = math.max(
1,
math.floor(baseCount)
)
local normalizedExtra = math.max(
0,
math.floor(extraCount)
)
self:ShowCardSelection(normalizedBase + normalizedExtra, source, qualityFilter)
end
function CardSystem.prototype.GetPassiveCardSelectionExtraChoices(self)
local extra = 0
if self:HasActiveCard(6) then
extra = extra + 1
end
return extra
end
function CardSystem.prototype.BuildOptionSourceChainWithCard6Bonus(self, baseChain, optionIndex, baseCount, source)
local chain = baseChain and ({unpack(baseChain)}) or ({})
local normalizedBase = math.max(
1,
math.floor(baseCount)
)
local isCard6TriggeredSelection = source == "modifier_card_6_bonus_selection"
local shouldAppendCard6 = self:HasActiveCard(6) and (isCard6TriggeredSelection or optionIndex >= normalizedBase)
if shouldAppendCard6 then
return self:BuildSourceChain("modifier_card_6_bonus_selection", chain)
end
return #chain > 0 and chain or nil
end
function CardSystem.prototype.ResolveCardSelectionCount(self, count)
local baseCount = math.max(
1,
math.floor(count)
)
local desiredCount = baseCount + self:GetPassiveCardSelectionExtraChoices()
local minByCard6 = self:GetCard6MinSelectionCount()
if minByCard6 > 0 then
desiredCount = math.max(desiredCount, minByCard6)
end
local maxByPool = self:GetMaxSelectableOptionsByPool()
if maxByPool > 0 then
return math.max(
1,
math.min(desiredCount, maxByPool)
)
end
return desiredCount
end
function CardSystem.prototype.GetMaxSelectableOptionsByPool(self)
if not self.pool then
return 0
end
local total = __TS__Number(self.pool.totalProportion or 0)
if not __TS__NumberIsFinite(total) or total <= 0 then
return 0
end
return math.max(
1,
math.floor(total)
)
end
function CardSystem.prototype.SkipsDawnCardSelection(self)
return self:HasActiveCard(58)
end
function CardSystem.prototype.HasActiveCard(self, cardId)
if __TS__ArrayIncludes(self.itemsId, cardId) then
return true
end
local player = self:GetPlayer()
local hero = player and player:GetAssignedHero()
if not hero then
return false
end
return hero:HasModifier("modifier_card_" .. tostring(cardId))
end
function CardSystem.prototype.DuplicateCurrentPool(self)
local snapshot = {}
if self.pool and self.pool.mappingTable ~= nil then
for key in pairs(self.pool.mappingTable) do
local item = self.pool.mappingTable[key]
local cardId = tonumber(key)
local weight = item and item.proportion or 0
if cardId and weight > 0 and not isMirrorProtectedCardId(nil, cardId) then
snapshot[#snapshot + 1] = {cardId = cardId, weight = weight}
end
end
end
for ____, entry in ipairs(snapshot) do
self:CreatePoolEntry(entry.cardId, entry.weight, "duplicate_current_pool")
end
self:RebuildPoolFromEntries()
self:SyncPool()
end
function CardSystem.prototype.ScheduleRepeatNextSelectedCardOnce(self)
self.repeatNextSelectedCardOnce = true
end
function CardSystem.prototype.HasRepeatNextSelectedCardScheduled(self)
return self.repeatNextSelectedCardOnce
end
function CardSystem.prototype.DuplicateUnselectedCurrentOptions(self, selectedCardId, optionsSnapshot)
local options = optionsSnapshot or self.playerItems
if not options then
return
end
local selected = math.floor(__TS__Number(selectedCardId))
local uniqueOptionIds = __TS__New(Set)
for key in pairs(options) do
do
local ____math_floor_39 = math.floor
local ____opt_37 = options[key]
local optionId = ____math_floor_39(__TS__Number(____opt_37 and ____opt_37.index))
if not __TS__NumberIsFinite(optionId) or optionId <= 0 then
goto __continue238
end
if isEmptySelectionCardId(nil, optionId) or optionId == selected or isMirrorProtectedCardId(nil, optionId) then
goto __continue238
end
uniqueOptionIds:add(optionId)
end
::__continue238::
end
for ____, optionId in __TS__Iterator(uniqueOptionIds) do
local currentWeight = self.pool:getWeightPrize(optionId)
if currentWeight > 0 then
self:CreatePoolEntry(optionId, 1, "duplicate_unselected_current_options")
end
end
self:RebuildPoolFromEntries()
self:SyncPool()
end
function CardSystem.prototype.AddToCardSelectionQueue(self, count, baseCount, source, qualityFilter, forcedCardId, exactOptionIds, sourceChain)
local priority = #self.cardSelectionQueue
local queueItem = {
count = exactOptionIds ~= nil and #exactOptionIds > 0 and #exactOptionIds or count,
baseCount = math.max(
1,
math.floor(baseCount)
),
priority = priority,
source = source,
sourceChain = sourceChain,
qualityFilter = qualityFilter,
forcedCardId = forcedCardId,
exactOptionIds = exactOptionIds
}
local ____self_cardSelectionQueue_40 = self.cardSelectionQueue
____self_cardSelectionQueue_40[#____self_cardSelectionQueue_40 + 1] = queueItem
self:SyncSelectionQueue()
if qualityFilter then
end
end
function CardSystem.prototype.StartWaitingForCardLevelsIfNeeded(self)
if self.cardLevelsWaitTimerStarted then
return
end
self.cardLevelsWaitTimerStarted = true
Timers:CreateTimer(
0.25,
function()
if not self.cardLevelsLoaded then
return 0.25
end
self.cardLevelsWaitTimerStarted = false
self:ProcessNextCardSelection()
return nil
end
)
end
function CardSystem.prototype.ShowCardSelectionWithForcedCard(self, forcedCardId, count, source)
if count == nil then
count = 3
end
if source == nil then
source = "forced_card"
end
local sourceChain = self:BuildSourceChain(source, self.effectSourceChainContext)
local data = ____exports.CardSystem.cardData[forcedCardId]
if data == nil then
print("[CardSystem.ShowCardSelectionWithForcedCard] неизвестный id " .. tostring(forcedCardId))
return
end
if data.disabled == true and forcedCardId ~= 404 then
print(("[CardSystem.ShowCardSelectionWithForcedCard] карта " .. tostring(forcedCardId)) .. " отключена")
return
end
local resolvedCount = self:ResolveCardSelectionCount(count)
if not self.cardLevelsLoaded then
self:AddToCardSelectionQueue(
resolvedCount,
math.max(
1,
math.floor(count)
),
source,
nil,
forcedCardId,
nil,
sourceChain
)
self:StartWaitingForCardLevelsIfNeeded()
return
end
if self.isShowingCardSelection then
self:AddToCardSelectionQueue(
resolvedCount,
math.max(
1,
math.floor(count)
),
source,
nil,
forcedCardId,
nil,
sourceChain
)
return
end
self:ProcessCardSelectionWithForcedCard(
resolvedCount,
source,
forcedCardId,
sourceChain,
math.max(
1,
math.floor(count)
)
)
end
function CardSystem.prototype.BuildRandomNonInherentCatalogIds(self, count, excludeIds, qualityFilter)
local slots = math.max(
1,
math.floor(count)
)
local exclude = __TS__New(Set, excludeIds)
local candidates = {}
for ____, key in ipairs(__TS__ObjectKeys(____exports.CardSystem.cardData)) do
do
local id = __TS__ParseInt(key, 10)
if __TS__NumberIsNaN(id) or id == 404 then
goto __continue257
end
if exclude:has(id) then
goto __continue257
end
local def = ____exports.CardSystem.cardData[id]
if not def or def.disabled == true then
goto __continue257
end
if qualityFilter ~= nil and def.quality ~= qualityFilter then
goto __continue257
end
if def.inherent == true then
goto __continue257
end
candidates[#candidates + 1] = id
end
::__continue257::
end
local shuffled = {unpack(candidates)}
do
local i = #shuffled - 1
while i > 0 do
local j = math.floor(math.random() * (i + 1))
local tmp = shuffled[i + 1]
shuffled[i + 1] = shuffled[j + 1]
shuffled[j + 1] = tmp
i = i - 1
end
end
local result = {}
local uniqueTake = math.min(slots, #shuffled)
do
local i = 0
while i < uniqueTake do
result[#result + 1] = shuffled[i + 1]
i = i + 1
end
end
while #result < slots do
if #candidates == 0 then
result[#result + 1] = 404
else
local r = candidates[math.floor(math.random() * #candidates) + 1]
result[#result + 1] = r
end
end
return result
end
function CardSystem.prototype.BuildRandomLegendaryNonInherentIds(self, count, excludeIds)
return self:BuildRandomNonInherentCatalogIds(count, excludeIds, ____exports.CardQuality.LEGENDARY)
end
function CardSystem.prototype.ProcessCardSelectionExactIds(self, ids, source, sourceChain, baseCount)
if baseCount == nil then
baseCount = #ids
end
self.isShowingCardSelection = true
self:SetCurrentSelectionSource(source, sourceChain)
self:SyncSelectionQueue()
self:SyncPool()
local cardSelection = {}
local optionEntryIds = self:SelectEntryIdsForCards(ids)
do
local i = 0
while i < #ids do
local rawId = ids[i + 1]
local def = ____exports.CardSystem.cardData[rawId]
local safeId = def ~= nil and def.disabled ~= true and rawId or 404
local ____temp_41
if safeId ~= 404 then
____temp_41 = optionEntryIds[i + 1]
else
____temp_41 = nil
end
local entryId = ____temp_41
local optionSourceChain = self:BuildOptionSourceChainWithCard6Bonus(
self:GetPoolEntrySourceChain(entryId),
i,
baseCount,
source
)
cardSelection[tostring(i + 1)] = {
index = safeId,
entry_id = entryId,
source_chain = optionSourceChain and self:SerializeSourceChain(optionSourceChain) or nil,
source_chain_items = optionSourceChain and ({unpack(optionSourceChain)}) or nil,
selection_token = self.currentSelectionToken
}
i = i + 1
end
end
self.playerItems = cardSelection
self:SyncPlayerItems()
self:SyncSelectionSource()
self:SyncRerollCost()
end
function CardSystem.prototype.ShowLegendaryNonInherentCatalogSelection(self, requestedSlots, source, excludeOptionIds)
if requestedSlots == nil then
requestedSlots = 3
end
if source == nil then
source = "legendary_catalog_pick"
end
if excludeOptionIds == nil then
excludeOptionIds = {}
end
local resolved = self:ResolveCardSelectionCount(requestedSlots)
local ids = self:BuildRandomLegendaryNonInherentIds(resolved, excludeOptionIds)
if not self.cardLevelsLoaded then
self:AddToCardSelectionQueue(
resolved,
math.max(
1,
math.floor(requestedSlots)
),
source,
nil,
nil,
ids,
self:BuildSourceChain(source, self.effectSourceChainContext)
)
self:StartWaitingForCardLevelsIfNeeded()
return
end
if self.isShowingCardSelection then
self:AddToCardSelectionQueue(
resolved,
math.max(
1,
math.floor(requestedSlots)
),
source,
nil,
nil,
ids,
self:BuildSourceChain(source, self.effectSourceChainContext)
)
return
end
self:ProcessCardSelectionExactIds(
ids,
source,
self:BuildSourceChain(source, self.effectSourceChainContext),
math.max(
1,
math.floor(requestedSlots)
)
)
end
function CardSystem.prototype.ShowAnyNonInherentCatalogSelection(self, requestedSlots, source, excludeOptionIds)
if requestedSlots == nil then
requestedSlots = 3
end
if source == nil then
source = "any_catalog_pick"
end
if excludeOptionIds == nil then
excludeOptionIds = {}
end
local resolved = self:ResolveCardSelectionCount(requestedSlots)
local ids = self:BuildRandomNonInherentCatalogIds(resolved, excludeOptionIds)
if not self.cardLevelsLoaded then
self:AddToCardSelectionQueue(
resolved,
math.max(
1,
math.floor(requestedSlots)
),
source,
nil,
nil,
ids,
self:BuildSourceChain(source, self.effectSourceChainContext)
)
self:StartWaitingForCardLevelsIfNeeded()
return
end
if self.isShowingCardSelection then
self:AddToCardSelectionQueue(
resolved,
math.max(
1,
math.floor(requestedSlots)
),
source,
nil,
nil,
ids,
self:BuildSourceChain(source, self.effectSourceChainContext)
)
return
end
self:ProcessCardSelectionExactIds(
ids,
source,
self:BuildSourceChain(source, self.effectSourceChainContext),
math.max(
1,
math.floor(requestedSlots)
)
)
end
function CardSystem.prototype.ShowCardSelectionExactOptions(self, optionIds, source)
if source == nil then
source = "exact_options"
end
local normalized = {}
for ____, raw in ipairs(optionIds) do
do
local id = math.floor(__TS__Number(raw))
if not __TS__NumberIsFinite(id) or id <= 0 then
goto __continue279
end
if id == 404 then
normalized[#normalized + 1] = 404
goto __continue279
end
local def = ____exports.CardSystem.cardData[id]
if def == nil or def.disabled == true then
goto __continue279
end
normalized[#normalized + 1] = id
end
::__continue279::
end
if #normalized <= 0 then
return
end
if not self.cardLevelsLoaded then
self:AddToCardSelectionQueue(
#normalized,
#normalized,
source,
nil,
nil,
normalized,
self:BuildSourceChain(source, self.effectSourceChainContext)
)
self:StartWaitingForCardLevelsIfNeeded()
return
end
if self.isShowingCardSelection then
self:AddToCardSelectionQueue(
#normalized,
#normalized,
source,
nil,
nil,
normalized,
self:BuildSourceChain(source, self.effectSourceChainContext)
)
return
end
self:ProcessCardSelectionExactIds(
normalized,
source,
self:BuildSourceChain(source, self.effectSourceChainContext),
#normalized
)
end
function CardSystem.prototype.BuildForcedCardOptionIds(self, forcedCardId, totalSlots)
local slots = math.max(
1,
math.floor(totalSlots)
)
local fromPool = __TS__ArrayFilter(
self:GetPoolCards(),
function(____, id) return id ~= forcedCardId end
)
local result = {forcedCardId}
local needMore = slots - 1
if needMore > 0 then
if #fromPool == 0 then
do
local i = 0
while i < needMore do
result[#result + 1] = 404
i = i + 1
end
end
else
local extra = self:DrawRandomFromSubsetRespectingWeights(fromPool, needMore)
for ____, id in ipairs(extra) do
result[#result + 1] = id
end
end
end
do
local i = #result - 1
while i > 0 do
local j = math.floor(math.random() * (i + 1))
local tmp = result[i + 1]
result[i + 1] = result[j + 1]
result[j + 1] = tmp
i = i - 1
end
end
return result
end
function CardSystem.prototype.ProcessCardSelectionWithForcedCard(self, count, source, forcedCardId, sourceChain, baseCount)
if baseCount == nil then
baseCount = count
end
self.isShowingCardSelection = true
self:SetCurrentSelectionSource(source, sourceChain)
self:SyncSelectionQueue()
self:SyncPool()
local ids = self:BuildForcedCardOptionIds(forcedCardId, count)
local cardSelection = {}
local optionEntryIds = self:SelectEntryIdsForCards(ids)
do
local i = 0
while i < #ids do
local safeId = ids[i + 1]
local ____temp_42
if safeId ~= 404 then
____temp_42 = optionEntryIds[i + 1]
else
____temp_42 = nil
end
local entryId = ____temp_42
local optionSourceChain = self:BuildOptionSourceChainWithCard6Bonus(
self:GetPoolEntrySourceChain(entryId),
i,
baseCount,
source
)
cardSelection[tostring(i + 1)] = {
index = safeId,
entry_id = entryId,
source_chain = optionSourceChain and self:SerializeSourceChain(optionSourceChain) or nil,
source_chain_items = optionSourceChain and ({unpack(optionSourceChain)}) or nil,
selection_token = self.currentSelectionToken
}
i = i + 1
end
end
self.playerItems = cardSelection
self:SyncPlayerItems()
self:SyncSelectionSource()
self:SyncRerollCost()
end
function CardSystem.prototype.ProcessCardSelection(self, count, source, qualityFilter, sourceChain, baseCount)
if baseCount == nil then
baseCount = count
end
print("[CardSystem.ProcessCardSelection] ===== ОБРАБОТКА ВЫБОРА КАРТ =====")
print("[CardSystem.ProcessCardSelection] Игрок: " .. tostring(self.playerId))
print("[CardSystem.ProcessCardSelection] Запрошено карт: " .. tostring(count))
print("[CardSystem.ProcessCardSelection] Источник: " .. source)
print("[CardSystem.ProcessCardSelection] Фильтр качества: " .. (qualityFilter and table.concat(qualityFilter, ", ") or "нет"))
if qualityFilter then
end
self.isShowingCardSelection = true
self:SetCurrentSelectionSource(source, sourceChain)
print("[CardSystem.ProcessCardSelection] Флаг isShowingCardSelection установлен в true")
self:SyncSelectionQueue()
self:SyncPool()
print("[CardSystem.ProcessCardSelection] Размер пула после синхронизации: " .. tostring(self.pool.len))
if self.pool.len == 0 then
print(("[CardSystem.ProcessCardSelection] ⚠️ Пул пустой, показываем " .. tostring(count)) .. " карт 404")
local cardSelection = {}
do
local i = 0
while i < count do
cardSelection[tostring(i + 1)] = {index = 404}
i = i + 1
end
end
self.playerItems = cardSelection
self:SyncPlayerItems()
self:SyncSelectionSource()
self:SyncRerollCost()
self.isShowingCardSelection = false
self:SyncSelectionQueue()
print("[CardSystem.ProcessCardSelection] ==========================================")
return
end
print("[CardSystem.ProcessCardSelection] Получаем карты с фильтром качества...")
local isReroll = source == "reroll"
local ____isReroll_43
if isReroll then
____isReroll_43 = nil
else
____isReroll_43 = qualityFilter
end
local effectiveQualityFilter = ____isReroll_43
local cards = self:GetCardsWithQualityFilter(count, effectiveQualityFilter, isReroll)
print(((("[CardSystem.ProcessCardSelection] Получено карт: " .. tostring(#cards)) .. " (запрошено: ") .. tostring(count)) .. ")")
local cardSelection = {}
local optionEntryIds = self:SelectEntryIdsForCards(cards)
do
local i = 0
while i < #cards do
local cardId = cards[i + 1]
local ____temp_44
if cardId ~= 404 then
____temp_44 = optionEntryIds[i + 1]
else
____temp_44 = nil
end
local entryId = ____temp_44
local optionSourceChain = self:BuildOptionSourceChainWithCard6Bonus(
self:GetPoolEntrySourceChain(entryId),
i,
baseCount,
source
)
cardSelection[tostring(i + 1)] = {
index = cardId,
entry_id = entryId,
source_chain = optionSourceChain and self:SerializeSourceChain(optionSourceChain) or nil,
source_chain_items = optionSourceChain and ({unpack(optionSourceChain)}) or nil,
selection_token = self.currentSelectionToken
}
i = i + 1
end
end
self.playerItems = cardSelection
self:SyncPlayerItems()
self:SyncSelectionSource()
if source ~= "reroll" then
self:SetRerollQualityFilter(qualityFilter)
end
self:SyncRerollCost()
end
function CardSystem.prototype.DrawRandomCardIdsRespectingPoolWeights(self, count, excludedIds, strictExcludeIds)
if strictExcludeIds == nil then
strictExcludeIds = false
end
if count <= 0 then
return {}
end
if self.pool.len == 0 then
local out = {}
do
local i = 0
while i < count do
out[#out + 1] = 404
i = i + 1
end
end
return out
end
local raw = self:DrawBiasedCardIdsFromPool(count, nil, excludedIds, strictExcludeIds)
while #raw < count do
raw[#raw + 1] = 404
end
return raw
end
function CardSystem.prototype.DrawRandomFromSubsetRespectingWeights(self, allowed, count, excludedIds, strictExcludeIds)
if strictExcludeIds == nil then
strictExcludeIds = false
end
if count <= 0 then
return {}
end
local raw = self:DrawBiasedCardIdsFromPool(count, allowed, excludedIds, strictExcludeIds)
if #raw <= 0 then
local out = {}
do
local i = 0
while i < count do
out[#out + 1] = 404
i = i + 1
end
end
return out
end
while #raw < count do
raw[#raw + 1] = 404
end
return raw
end
function CardSystem.prototype.BuildPoolRemainingWeights(self, allowed, excludedIds)
local allowedSet = allowed and #allowed > 0 and __TS__New(Set, allowed) or nil
local remaining = {}
if self.pool and self.pool.mappingTable ~= nil then
for key in pairs(self.pool.mappingTable) do
do
local cardId = __TS__ParseInt(key)
if not __TS__NumberIsFinite(cardId) or cardId <= 0 then
goto __continue316
end
if allowedSet and not allowedSet:has(cardId) then
goto __continue316
end
local weight = self.pool:getWeightPrize(cardId)
if weight > 0 then
remaining[tostring(cardId)] = math.floor(weight)
end
end
::__continue316::
end
end
return remaining
end
function CardSystem.prototype.DrawCardIdsFromRemaining(self, count, remaining, allowed, excludedIds, strictExcludeIds)
if strictExcludeIds == nil then
strictExcludeIds = false
end
if count <= 0 then
return {}
end
local allowedSet = allowed and #allowed > 0 and __TS__New(Set, allowed) or nil
local batchExcluded = {}
if excludedIds then
for ____, raw in ipairs(excludedIds) do
local id = math.floor(__TS__Number(raw))
if __TS__NumberIsFinite(id) and id > 0 then
batchExcluded[id] = true
end
end
end
local result = {}
do
local pick = 0
while pick < count do
local totalWeight = 0
local entries = {}
local hasNonExcludedCandidates = false
for ____, ____value in ipairs(__TS__ObjectEntries(remaining)) do
local cardIdRaw = ____value[1]
local leftRaw = ____value[2]
do
local cardId = __TS__Number(cardIdRaw)
local left = __TS__Number(leftRaw)
if not __TS__NumberIsFinite(cardId) or not __TS__NumberIsFinite(left) or left <= 0 then
goto __continue328
end
if allowedSet and not allowedSet:has(cardId) then
goto __continue328
end
if not batchExcluded[cardId] then
hasNonExcludedCandidates = true
end
end
::__continue328::
end
for ____, ____value in ipairs(__TS__ObjectEntries(remaining)) do
local cardIdRaw = ____value[1]
local leftRaw = ____value[2]
do
local cardId = __TS__Number(cardIdRaw)
local left = __TS__Number(leftRaw)
if not __TS__NumberIsFinite(cardId) or not __TS__NumberIsFinite(left) or left <= 0 then
goto __continue333
end
if allowedSet and not allowedSet:has(cardId) then
goto __continue333
end
if batchExcluded[cardId] and (strictExcludeIds or hasNonExcludedCandidates) then
goto __continue333
end
local pickWeight = math.max(
1,
math.floor(left)
)
entries[#entries + 1] = {cardId = cardId, pickWeight = pickWeight}
totalWeight = totalWeight + pickWeight
end
::__continue333::
end
if totalWeight <= 0 or #entries <= 0 then
break
end
local roll = RandomInt(1, totalWeight)
local cursor = 0
local selectedCardId = entries[1].cardId
for ____, entry in ipairs(entries) do
cursor = cursor + entry.pickWeight
if roll <= cursor then
selectedCardId = entry.cardId
break
end
end
result[#result + 1] = selectedCardId
local key = tostring(selectedCardId)
remaining[key] = math.max(0, (remaining[key] or 0) - 1)
pick = pick + 1
end
end
return result
end
function CardSystem.prototype.DrawBiasedCardIdsFromPool(self, count, allowed, excludedIds, strictExcludeIds)
if strictExcludeIds == nil then
strictExcludeIds = false
end
if count <= 0 then
return {}
end
local remaining = self:BuildPoolRemainingWeights(allowed, excludedIds)
return self:DrawCardIdsFromRemaining(
count,
remaining,
allowed,
excludedIds,
strictExcludeIds
)
end
function CardSystem.prototype.GetCardsWithQualityFilter(self, count, qualityFilter, isReroll, excludedIds, strictExcludeIds)
if isReroll == nil then
isReroll = false
end
if strictExcludeIds == nil then
strictExcludeIds = false
end
print("[CardSystem.GetCardsWithQualityFilter] ===== ПОЛУЧЕНИЕ КАРТ С ФИЛЬТРОМ =====")
print("[CardSystem.GetCardsWithQualityFilter] Игрок: " .. tostring(self.playerId))
print("[CardSystem.GetCardsWithQualityFilter] Запрошено карт: " .. tostring(count))
print("[CardSystem.GetCardsWithQualityFilter] Фильтр качества: " .. (qualityFilter and table.concat(qualityFilter, ", ") or "нет"))
print("[CardSystem.GetCardsWithQualityFilter] Это рерол: " .. tostring(isReroll))
print("[CardSystem.GetCardsWithQualityFilter] Гарантированных реролов: " .. tostring(#self.guaranteedQualityRerolls))
if isReroll and #self.guaranteedQualityRerolls > 0 then
print("[CardSystem.GetCardsWithQualityFilter] ✅ Используем гарантированные карты качества")
local result = self:GetRerollCardsWithGuaranteedQuality(count, qualityFilter, excludedIds, strictExcludeIds)
print(("[CardSystem.GetCardsWithQualityFilter] Возвращаем " .. tostring(#result)) .. " карт из гарантированных")
return result
end
if not qualityFilter or #qualityFilter == 0 then
print("[CardSystem.GetCardsWithQualityFilter] ✅ Случайный выбор с учётом весов в пуле (randomCard)")
local result = self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds)
print(("[CardSystem.GetCardsWithQualityFilter] Возвращаем " .. tostring(#result)) .. " карт")
print(("[CardSystem.GetCardsWithQualityFilter] ID карт: [" .. table.concat(result, ", ")) .. "]")
return result
end
local filteredCards = self:GetPoolCardIdsMatchingQualities(qualityFilter)
if #filteredCards == 0 then
local fallbackQuality = self:GetNextAvailableQuality(self:GetHighestQualityInFilter(qualityFilter))
if fallbackQuality ~= nil then
print((("[CardSystem.GetCardsWithQualityFilter] Нет карт по фильтру [" .. table.concat(qualityFilter, ", ")) .. "] — даунгрейд до ") .. tostring(fallbackQuality))
return self:GetCardsWithQualityFilter(
count,
{fallbackQuality},
isReroll,
excludedIds,
strictExcludeIds
)
end
print("[CardSystem.GetCardsWithQualityFilter] Нет карт по фильтру и даунгрейда — общий пул")
return self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds)
end
local remaining = self:BuildPoolRemainingWeights(nil, excludedIds)
local result = self:DrawCardIdsFromRemaining(
count,
remaining,
filteredCards,
excludedIds,
strictExcludeIds
)
while #result < count do
result[#result + 1] = 404
end
result = self:FillSelectionGapsWithLowerQuality(result, qualityFilter, excludedIds, remaining)
print(("[CardSystem.GetCardsWithQualityFilter] По фильтру качества: [" .. table.concat(result, ", ")) .. "]")
return result
end
function CardSystem.prototype.GetPoolCardIdsMatchingQualities(self, qualities)
local allowed = __TS__New(Set, qualities)
local filteredCards = {}
for ____, cardId in ipairs(self:GetPoolCards()) do
local cardData = ____exports.CardSystem.cardData[cardId]
if cardData and allowed:has(cardData.quality) then
filteredCards[#filteredCards + 1] = cardId
end
end
return filteredCards
end
function CardSystem.prototype.GetHighestQualityInFilter(self, qualityFilter)
local best = qualityFilter[1]
for ____, quality in ipairs(qualityFilter) do
local bestIndex = __TS__ArrayIndexOf(____exports.CardSystem.QUALITY_FALLBACK_ORDER, best)
local qualityIndex = __TS__ArrayIndexOf(____exports.CardSystem.QUALITY_FALLBACK_ORDER, quality)
if qualityIndex >= 0 and (bestIndex < 0 or qualityIndex < bestIndex) then
best = quality
end
end
return best
end
function CardSystem.prototype.GetLowestRarityInFilter(self, qualityFilter)
local lowest = qualityFilter[1]
for ____, quality in ipairs(qualityFilter) do
local lowestIndex = __TS__ArrayIndexOf(____exports.CardSystem.QUALITY_FALLBACK_ORDER, lowest)
local qualityIndex = __TS__ArrayIndexOf(____exports.CardSystem.QUALITY_FALLBACK_ORDER, quality)
if qualityIndex >= 0 and (lowestIndex < 0 or qualityIndex > lowestIndex) then
lowest = quality
end
end
return lowest
end
function CardSystem.prototype.FillSelectionGapsWithLowerQuality(self, selection, qualityFilter, excludedIds, remaining)
local stillMissing = #__TS__ArrayFilter(
selection,
function(____, id) return id == 404 end
)
if stillMissing <= 0 then
return selection
end
local filled = {unpack(selection)}
local poolRemaining = remaining or self:BuildPoolRemainingWeights(nil, excludedIds)
local fallbackQuality = self:GetNextAvailableQuality(self:GetLowestRarityInFilter(qualityFilter))
while stillMissing > 0 and fallbackQuality ~= nil do
do
local fallbackPool = self:GetPoolCardIdsMatchingQualities({fallbackQuality})
if #fallbackPool <= 0 then
fallbackQuality = self:GetNextAvailableQuality(fallbackQuality)
goto __continue365
end
local replacements = self:DrawCardIdsFromRemaining(stillMissing, poolRemaining, fallbackPool, excludedIds)
local replaceIndex = 0
do
local i = 0
while i < #filled do
if filled[i + 1] == 404 and replaceIndex < #replacements and replacements[replaceIndex + 1] ~= 404 then
filled[i + 1] = replacements[replaceIndex + 1]
replaceIndex = replaceIndex + 1
end
i = i + 1
end
end
stillMissing = #__TS__ArrayFilter(
filled,
function(____, id) return id == 404 end
)
if stillMissing <= 0 then
break
end
fallbackQuality = self:GetNextAvailableQuality(fallbackQuality)
end
::__continue365::
end
return filled
end
function CardSystem.prototype.GetNextAvailableQuality(self, targetQuality)
local targetIndex = __TS__ArrayIndexOf(____exports.CardSystem.QUALITY_FALLBACK_ORDER, targetQuality)
if targetIndex == -1 then
return nil
end
do
local i = targetIndex + 1
while i < #____exports.CardSystem.QUALITY_FALLBACK_ORDER do
local quality = ____exports.CardSystem.QUALITY_FALLBACK_ORDER[i + 1]
if #self:GetPoolCardIdsMatchingQualities({quality}) > 0 then
return quality
end
i = i + 1
end
end
return nil
end
function CardSystem.prototype.GetRerollCardsWithGuaranteedQuality(self, count, qualityFilter, excludedIds, strictExcludeIds)
if strictExcludeIds == nil then
strictExcludeIds = false
end
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] ===== ОБРАБОТКА РЕРОЛА =====")
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Игрок: " .. tostring(self.playerId))
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Запрошено карт: " .. tostring(count))
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Гарантированных реролов: " .. tostring(#self.guaranteedQualityRerolls))
do
local i = 0
while i < #self.guaranteedQualityRerolls do
local reroll = self.guaranteedQualityRerolls[i + 1]
print((((((("[CardSystem.GetRerollCardsWithGuaranteedQuality] Рерол " .. tostring(i)) .. ": качество=") .. tostring(reroll.quality)) .. ", осталось=") .. tostring(reroll.rerollsLeft)) .. ", карт=") .. tostring(reroll.cardsPerReroll))
i = i + 1
end
end
local priorityQuality = __TS__ArrayFind(
self.guaranteedQualityRerolls,
function(____, item) return item.rerollsLeft > 0 end
)
if not priorityQuality then
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] ❌ Нет доступных гарантированных реролов")
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем обычные случайные карты (веса пула)")
local result = self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds)
print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем " .. tostring(#result)) .. " случайных карт")
print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] ID карт: [" .. table.concat(result, ", ")) .. "]")
return result
end
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] ✅ Найден приоритетный рерол:")
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] - Качество: " .. tostring(priorityQuality.quality))
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] - Осталось реролов: " .. tostring(priorityQuality.rerollsLeft))
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] - Карт за рерол: " .. tostring(priorityQuality.cardsPerReroll))
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] - Поведение при отсутствии: " .. priorityQuality.fallbackBehavior)
priorityQuality.rerollsLeft = priorityQuality.rerollsLeft - 1
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Счетчик реролов уменьшен до: " .. tostring(priorityQuality.rerollsLeft))
if priorityQuality.rerollsLeft <= 0 then
local index = __TS__ArrayIndexOf(self.guaranteedQualityRerolls, priorityQuality)
if index > -1 then
__TS__ArraySplice(self.guaranteedQualityRerolls, index, 1)
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] ✅ Удален использованный рерол из массива")
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Осталось гарантированных реролов: " .. tostring(#self.guaranteedQualityRerolls))
end
end
local allCards = self:GetPoolCards()
local qualityCards = {}
for ____, cardId in ipairs(allCards) do
local cardData = ____exports.CardSystem.cardData[cardId]
if cardData and cardData.quality == priorityQuality.quality then
qualityCards[#qualityCards + 1] = cardId
end
end
if #qualityCards == 0 then
repeat
local ____switch385 = priorityQuality.fallbackBehavior
local skipResult, delayResult, fallbackQuality, replaceResult, defaultResult
local ____cond385 = ____switch385 == "skip"
if ____cond385 then
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Пропускаем рерол (skip)")
skipResult = self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds)
print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем " .. tostring(#skipResult)) .. " случайных карт (skip)")
return skipResult
end
____cond385 = ____cond385 or ____switch385 == "delay"
if ____cond385 then
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Откладываем рерол (delay)")
delayResult = self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds)
print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем " .. tostring(#delayResult)) .. " случайных карт (delay)")
return delayResult
end
____cond385 = ____cond385 or ____switch385 == "replace"
if ____cond385 then
fallbackQuality = self:GetNextAvailableQuality(priorityQuality.quality)
if fallbackQuality ~= nil then
local fallbackCards = {}
for ____, cardId in ipairs(allCards) do
local cardData = ____exports.CardSystem.cardData[cardId]
if cardData and cardData.quality == fallbackQuality then
fallbackCards[#fallbackCards + 1] = cardId
end
end
if #fallbackCards > 0 then
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Заменяем на качество " .. tostring(fallbackQuality))
local takeFromFallback = math.min(priorityQuality.cardsPerReroll, count)
local selectedCards = self:DrawRandomFromSubsetRespectingWeights(fallbackCards, takeFromFallback, excludedIds, strictExcludeIds)
local remainingCards = self:DrawRandomCardIdsRespectingPoolWeights(count - #selectedCards, excludedIds, strictExcludeIds)
local ____array_45 = __TS__SparseArrayNew(unpack(selectedCards))
__TS__SparseArrayPush(
____array_45,
unpack(remainingCards)
)
local result = {__TS__SparseArraySpread(____array_45)}
priorityQuality.rerollsLeft = priorityQuality.rerollsLeft - 1
print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем " .. tostring(#result)) .. " карт (replace)")
return result
end
end
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Нет доступных карт для замены, используем обычный выбор")
replaceResult = self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds)
print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем " .. tostring(#replaceResult)) .. " случайных карт (replace fallback)")
return replaceResult
end
do
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Неизвестное поведение, используем обычный выбор")
defaultResult = self:DrawRandomCardIdsRespectingPoolWeights(count, excludedIds, strictExcludeIds)
print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] Возвращаем " .. tostring(#defaultResult)) .. " случайных карт (default)")
return defaultResult
end
until true
end
local fromQualityCount = math.min(priorityQuality.cardsPerReroll, count)
local fromQuality = self:DrawRandomFromSubsetRespectingWeights(qualityCards, fromQualityCount, excludedIds, strictExcludeIds)
local restCount = count - #fromQuality
local rest = restCount > 0 and self:DrawRandomCardIdsRespectingPoolWeights(restCount, excludedIds, strictExcludeIds) or ({})
local ____array_46 = __TS__SparseArrayNew(unpack(fromQuality))
__TS__SparseArrayPush(
____array_46,
unpack(rest)
)
local guaranteedCards = {__TS__SparseArraySpread(____array_46)}
do
local i = #guaranteedCards - 1
while i > 0 do
local j = math.floor(math.random() * (i + 1))
local ____temp_47 = {guaranteedCards[j + 1], guaranteedCards[i + 1]}
guaranteedCards[i + 1] = ____temp_47[1]
guaranteedCards[j + 1] = ____temp_47[2]
i = i - 1
end
end
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] Итого возвращаем карт: " .. tostring(#guaranteedCards))
print(("[CardSystem.GetRerollCardsWithGuaranteedQuality] ID карт: [" .. table.concat(guaranteedCards, ", ")) .. "]")
print("[CardSystem.GetRerollCardsWithGuaranteedQuality] ================================")
return guaranteedCards
end
function CardSystem.prototype.OnChooseCard(self, playerId, index, selectionToken)
local player = PlayerResource:GetPlayer(playerId)
local hero = player and player:GetAssignedHero()
if not hero or not IsValidEntity(hero) or not hero:IsAlive() then
return
end
local normalizedToken = math.floor(__TS__Number(selectionToken))
if __TS__NumberIsFinite(normalizedToken) and normalizedToken > 0 and normalizedToken ~= self.currentSelectionToken then
print((((("[CardSystem.OnChooseCard] Игнорируем устаревший выбор: player=" .. tostring(playerId)) .. ", token=") .. tostring(normalizedToken)) .. ", current=") .. tostring(self.currentSelectionToken))
return
end
local selected = self.playerItems[tostring(index)]
if not selected then
return
end
local cardId = selected.index
local selectedEntryId = selected.entry_id
local card68ExcludeOptionIds
if cardId == 68 then
card68ExcludeOptionIds = {}
for key in pairs(self.playerItems) do
local option = self.playerItems[key]
if option and option.index ~= nil and option.index ~= 404 and not isSlagCardId(nil, option.index) then
card68ExcludeOptionIds[#card68ExcludeOptionIds + 1] = option.index
end
end
end
if isSlagCardId(nil, cardId) then
if selectedEntryId then
self:ConsumePoolEntryWeight(selectedEntryId, 1)
else
self:ConsumePoolEntryWeight(
self:PickPoolEntryIdForCardId(cardId),
1
)
end
self:RebuildPoolFromEntries()
self:SyncPool()
self:registerTakenCardChoice()
self.playerItems = {}
self:SyncPlayerItems()
self.isShowingCardSelection = false
self:ProcessNextCardSelection()
return
end
if cardId == 404 then
self:registerTakenCardChoice()
self.playerItems = {}
self:SyncPlayerItems()
self:SyncPool()
self.isShowingCardSelection = false
self:ProcessNextCardSelection()
return
end
if selectedEntryId then
self:ConsumePoolEntryWeight(selectedEntryId, 1)
else
local fallbackEntryId = self:PickPoolEntryIdForCardId(cardId)
self:ConsumePoolEntryWeight(fallbackEntryId, 1)
end
self:RebuildPoolFromEntries()
self:SyncPool()
self:registerTakenCardChoice()
local optionChain = __TS__ArrayIsArray(selected.source_chain_items) and selected.source_chain_items or self.currentSelectionSourceChain
local pickChain = self:BuildSourceChain(
"selected_card_" .. tostring(cardId),
optionChain
)
self:AddCard(
tostring(cardId),
pickChain
)
if self.repeatNextSelectedCardOnce then
self.repeatNextSelectedCardOnce = false
if not isCard79RepeatBlockedCardId(nil, cardId) then
local repeatChain = self:BuildSourceChain(
"repeat_selected_card_" .. tostring(cardId),
pickChain
)
self:AddCard(
tostring(cardId),
repeatChain
)
end
end
self.playerItems = {}
self:SyncPlayerItems()
self:SyncPool()
self.isShowingCardSelection = false
self:ProcessNextCardSelection()
if card68ExcludeOptionIds ~= nil then
self:ScheduleCard68CursedPick(card68ExcludeOptionIds)
end
end
function CardSystem.prototype.registerTakenCardChoice(self)
self.cardsTaken = self.cardsTaken + 1
self:SyncRemainingCards()
local taken = self.cardsTaken
if taken % 10 == 0 or taken % 15 == 0 or taken % 20 == 0 then
self:CreatePoolEntry(404, 1, "threshold_empty_card_bonus")
self:RebuildPoolFromEntries()
self:SyncPool()
self:SyncRemainingCards()
end
end
function CardSystem.prototype.RerollCards(self, playerId)
if not self.playerItems or #__TS__ObjectKeys(self.playerItems) == 0 then
return
end
if self.rerollCost > 0 then
local useCard49Free = self:HasActiveCard(49) and self.card49FreeRerollCharges > 0
local useCard6Free = not useCard49Free and self:GetCard6FreeRerollChargesForUse() > 0
local useCard88Free = not useCard49Free and not useCard6Free and self:GetCard88FreeRerollChargesForUse() > 0
if useCard49Free then
self.card49FreeRerollCharges = self.card49FreeRerollCharges - 1
elseif useCard6Free then
self.card6FreeRerollCharges = self.card6FreeRerollCharges - 1
elseif useCard88Free then
self.card88FreeRerollCharges = self.card88FreeRerollCharges - 1
else
local crystalSystem = CrystalCurrency:getInstance()
local currentCrystals = crystalSystem:getCrystals(self.playerId)
if currentCrystals < self.rerollCost then
return
end
local success = crystalSystem:removeCrystals(self.playerId, self.rerollCost)
if not success then
return
end
end
end
if self.rerollCost < 50 then
self.rerollCost = self.rerollCost + 5
end
self:SyncRerollCost()
self:SyncRemainingCards()
local count = #__TS__ObjectKeys(self.playerItems)
self:ProcessCardSelection(count, "reroll")
end
function CardSystem.prototype.AddCard(self, cardId, sourceChain, options)
local skipCurseStack = (options and options.skipCurseStack) == true
Timers:CreateTimer(
0.1,
function()
local player = self:GetPlayer()
if not player then
return nil
end
local hero = player:GetAssignedHero()
if not hero or not hero:IsAlive() then
return nil
end
local className = "card_" .. cardId
local CardClass = ____exports.CardSystem.cardTypes[className]
if not CardClass then
return nil
end
local parsedId = __TS__ParseInt(cardId)
local previousEffectContext = self.effectSourceChainContext
self.effectSourceChainContext = sourceChain and #sourceChain > 0 and ({unpack(sourceChain)}) or previousEffectContext
if not skipCurseStack and hero:HasModifier("modifier_card_69") then
addCursedStack(nil, hero)
end
local ____self_itemsId_52 = self.itemsId
____self_itemsId_52[#____self_itemsId_52 + 1] = parsedId
invalidateStatsMultiplierSumCache(nil, hero)
do
pcall(function()
local card80 = require("cards.examples.card_80")
local ____opt_53 = card80.notifyCard80PlayerTookCard
if ____opt_53 ~= nil then
____opt_53(card80, hero, parsedId)
end
end)
end
local card = __TS__New(CardClass, hero, cardId)
if isGreedPoolCardId(nil, parsedId) and card:GetModifierName() == nil then
registerHiddenGreedCard(nil, hero, parsedId)
end
if not card:IsHidden() then
local ____self_items_55 = self.items
____self_items_55[#____self_items_55 + 1] = card
self:SyncActiveCards()
else
table.remove(self.itemsId)
invalidateStatsMultiplierSumCache(nil, hero)
end
if isGreedPoolCardId(nil, parsedId) then
updateGreedForHero(nil, hero, self)
end
self.effectSourceChainContext = previousEffectContext
return nil
end
)
end
function CardSystem.prototype.CraftOrUpgradeCard(self, cardId, _allowDebris)
local normalizedCardId = math.floor(__TS__Number(cardId))
if not __TS__NumberIsFinite(normalizedCardId) or normalizedCardId <= 0 then
self:sendCardUpgradeResult(false, "Некорректный id карты")
return
end
local cardData = ____exports.CardSystem.cardData[normalizedCardId]
if not cardData or cardData.disabled == true then
self:sendCardUpgradeResult(false, "Карта недоступна для улучшения")
return
end
if cardData.canupgrade == false then
self:sendCardUpgradeResult(false, "Эту карту нельзя улучшать")
return
end
local key = tostring(normalizedCardId)
local currentState = self.cardPieces[key] or ({num = 0, level = 1})
local currentLevel = math.max(
1,
math.floor(__TS__Number(currentState.level or 1))
)
if currentLevel >= CARD_UPGRADE_MAX_LEVEL then
self:sendCardUpgradeResult(
false,
"Максимальный уровень: " .. tostring(CARD_UPGRADE_MAX_LEVEL)
)
return
end
local nextLevel = currentLevel + 1
local upgradeCost = self:getCardUpgradeCost(cardData.quality, currentLevel)
if upgradeCost <= 0 then
self:sendCardUpgradeResult(false, "Ошибка стоимости улучшения")
return
end
local storeModule = require("store_manager")
local ____opt_58 = storeModule.StoreManager
local ____opt_56 = ____opt_58 and ____opt_58.getInstance
local store = ____opt_56 and ____opt_56(____opt_58)
if not store then
self:sendCardUpgradeResult(false, "Магазин недоступен")
return
end
local isDefaultCard = cardData.default == true
local hasPurchasedCardById = store.hasPurchasedCardById
local ____hasPurchasedCardById_60
if hasPurchasedCardById then
____hasPurchasedCardById_60 = store:hasPurchasedCardById(self.playerId, normalizedCardId) == true
else
____hasPurchasedCardById_60 = false
end
local hasPurchasedCard = ____hasPurchasedCardById_60
local unlockCardId = math.floor(__TS__Number(cardData.deck_builder_unlock_card_id or 0))
if cardData.deck_builder_non_deckable == true and unlockCardId > 0 then
local ____hasPurchasedCardById_61
if hasPurchasedCardById then
____hasPurchasedCardById_61 = store:hasPurchasedCardById(self.playerId, unlockCardId) == true
else
____hasPurchasedCardById_61 = false
end
hasPurchasedCard = ____hasPurchasedCardById_61
end
if not isDefaultCard and not hasPurchasedCard then
self:sendCardUpgradeResult(
false,
unlockCardId > 0 and cardData.deck_builder_non_deckable == true and getDeckBuilderUnlockRequiredMessage(nil, unlockCardId) or "Нельзя улучшить некупленную карту"
)
return
end
local currentDustCurrency = store:getDustCurrency(self.playerId)
if currentDustCurrency < upgradeCost then
self:sendCardUpgradeResult(
false,
"Недостаточно пыли: нужно " .. tostring(upgradeCost)
)
return
end
local removed = store:removeDustCurrency(self.playerId, upgradeCost)
if not removed then
self:sendCardUpgradeResult(false, "Не удалось списать пыль")
return
end
local ____this_63
____this_63 = store
local ____opt_62 = ____this_63.saveCurrencyToServer
if ____opt_62 ~= nil then
____opt_62(____this_63, self.playerId)
end
self.cardPieces[key] = __TS__ObjectAssign({}, currentState, {level = nextLevel})
self:SyncCardPieces()
self:RefreshActiveCardModifiersById(normalizedCardId)
if normalizedCardId == ____exports.CardSystem.CARD_6_ID then
self:TryGrantCard6Level2FreeRerolls()
end
self:saveCardLevelsToServer()
self:sendCardUpgradeResult(
true,
("Карта улучшена до " .. tostring(nextLevel)) .. " уровня",
normalizedCardId,
nextLevel,
upgradeCost
)
end
function CardSystem.prototype.getCardUpgradeCost(self, quality, currentLevel)
return ____exports.CardSystem:getCardUpgradeDustCost(quality, currentLevel)
end
function CardSystem.getCardUpgradeDustCost(self, quality, currentLevel)
local baseCost = ____exports.CardSystem.CARD_UPGRADE_BASE_COST_BY_QUALITY[quality] or 2000
return math.max(
1,
math.floor(baseCost * math.max(1, currentLevel))
)
end
function CardSystem.getDuplicateCardDustCompensation(self, quality, cardLevel)
local maxLevel = CARD_UPGRADE_MAX_LEVEL
local levelForCost = math.max(
1,
math.floor(cardLevel)
)
if levelForCost >= maxLevel then
levelForCost = maxLevel - 1
end
return ____exports.CardSystem:getCardUpgradeDustCost(quality, levelForCost)
end
function CardSystem.prototype.sendCardUpgradeResult(self, success, message, cardId, newLevel, spentFreeCurrency)
local player = self:GetPlayer()
if not player then
return
end
CustomGameEventManager:Send_ServerToPlayer(player, "card_upgrade_result", {
success = success and 1 or 0,
message = message,
card_id = cardId,
new_level = newLevel,
spent_free_currency = spentFreeCurrency,
spent_dust_currency = spentFreeCurrency
})
end
function CardSystem.prototype.RefreshActiveCardModifiersById(self, cardId)
for ____, card in ipairs(self.items) do
do
local cardUnsafe = card
local activeCardId = __TS__Number(cardUnsafe.cardKey or "0")
if not __TS__NumberIsFinite(activeCardId) or activeCardId ~= cardId then
goto __continue451
end
if type(cardUnsafe.ModifierRefresh) == "function" then
cardUnsafe:ModifierRefresh()
end
end
::__continue451::
end
end
function CardSystem.prototype.buildCardLevelsSyncPayload(self)
local payload = {}
for ____, ____value in ipairs(__TS__ObjectEntries(____exports.CardSystem.cardData)) do
local idRaw = ____value[1]
local cardData = ____value[2]
do
local id = __TS__Number(idRaw)
if not __TS__NumberIsFinite(id) or id <= 0 or id == 404 or cardData.disabled == true then
goto __continue456
end
local key = tostring(id)
local ____math_max_67 = math.max
local ____math_floor_66 = math.floor
local ____opt_64 = self.cardPieces[key]
local level = ____math_max_67(
1,
____math_floor_66(__TS__Number(____opt_64 and ____opt_64.level or 1))
)
local canUpgradeByRule = cardData.canupgrade ~= false
local canUpgrade = canUpgradeByRule and level < CARD_UPGRADE_MAX_LEVEL
payload[key] = {
level = level,
max_level = CARD_UPGRADE_MAX_LEVEL,
next_upgrade_cost = canUpgrade and self:getCardUpgradeCost(cardData.quality, level) or 0,
can_upgrade = canUpgradeByRule and 1 or 0
}
end
::__continue456::
end
return payload
end
function CardSystem.prototype.SyncCardPieces(self)
CustomNetTables:SetTableValue(
"cards",
"card_pieces_" .. tostring(self.playerId),
self.cardPieces
)
CustomNetTables:SetTableValue(
"cards",
"card_levels_" .. tostring(self.playerId),
self:buildCardLevelsSyncPayload()
)
end
function CardSystem.prototype.buildPersistedCardLevelsPayload(self)
local payload = {}
for ____, ____value in ipairs(__TS__ObjectEntries(____exports.CardSystem.cardData)) do
local idRaw = ____value[1]
local cardData = ____value[2]
do
local id = __TS__Number(idRaw)
if not __TS__NumberIsFinite(id) or id <= 0 or id == 404 or cardData.disabled == true then
goto __continue461
end
local key = tostring(id)
local ____math_max_71 = math.max
local ____math_floor_70 = math.floor
local ____opt_68 = self.cardPieces[key]
local level = ____math_max_71(
1,
____math_floor_70(__TS__Number(____opt_68 and ____opt_68.level or 1))
)
payload[key] = level
end
::__continue461::
end
return payload
end
function CardSystem.prototype.saveCardLevelsToServer(self)
local steamId = PlayerResource:GetSteamAccountID(self.playerId)
if not steamId then
return
end
local request = CreateHTTPRequestScriptVM(
"PUT",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/card-levels"
)
setApiHeaders(nil, request)
request:SetHTTPRequestRawPostBody(
"application/json",
json.encode({card_levels = self:buildPersistedCardLevelsPayload()})
)
request:Send(function(_result)
end)
end
function CardSystem.prototype.loadCardLevelsFromServer(self)
local steamId = PlayerResource:GetSteamAccountID(self.playerId)
if not steamId then
self.cardLevelsLoaded = true
return
end
local request = CreateHTTPRequestScriptVM(
"GET",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/card-levels"
)
setApiHeaders(nil, request)
request:Send(function(result)
if result.StatusCode < 200 or result.StatusCode >= 300 then
self.cardLevelsLoaded = true
return
end
do
local function ____catch(_error)
self:SyncCardPieces()
self.cardLevelsLoaded = true
end
local ____try, ____hasReturned, ____returnValue = pcall(function()
local decoded = {json.decode(result.Body)}
local ____temp_72
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
____temp_72 = decoded[1]
else
____temp_72 = decoded
end
local responseData = ____temp_72
local ____opt_result_75
if responseData ~= nil then
____opt_result_75 = responseData.card_levels
end
local cardLevels = ____opt_result_75
if not cardLevels or type(cardLevels) ~= "table" then
self:SyncCardPieces()
self.cardLevelsLoaded = true
return true
end
for ____, ____value in ipairs(__TS__ObjectEntries(cardLevels)) do
local cardIdRaw = ____value[1]
local levelRaw = ____value[2]
do
local cardIdNum = __TS__Number(cardIdRaw)
local levelNum = __TS__Number(levelRaw)
if not __TS__NumberIsFinite(cardIdNum) or cardIdNum <= 0 or not __TS__NumberIsFinite(levelNum) then
goto __continue473
end
local cardId = math.floor(cardIdNum)
local level = math.max(
1,
math.min(
CARD_UPGRADE_MAX_LEVEL,
math.floor(levelNum)
)
)
local key = tostring(cardId)
local current = self.cardPieces[key] or ({num = 0, level = 1})
self.cardPieces[key] = __TS__ObjectAssign({}, current, {level = level})
end
::__continue473::
end
self:SyncCardPieces()
self.cardLevelsLoaded = true
end)
if not ____try then
____hasReturned, ____returnValue = ____catch(____hasReturned)
end
if ____hasReturned then
return ____returnValue
end
end
end)
end
function CardSystem.prototype.CreateNewDeck(self, name)
local player = self:GetPlayer()
if not player then
return
end
do
local i = 1
while i <= 10 do
if not self.decks[tostring(i)] then
self.decks[tostring(i)] = {name = name, cards = {}}
self:SyncDecks()
CustomGameEventManager:Send_ServerToPlayer(player, "deck_selected", {index = i})
return
end
i = i + 1
end
end
end
function CardSystem.prototype.SaveDeck(self, index, cards, deckName)
if not cards then
return
end
local cardsArray
if __TS__ArrayIsArray(cards) then
cardsArray = cards
elseif type(cards) == "table" and cards ~= nil then
local keys = __TS__ArraySort(
__TS__ObjectKeys(cards),
function(____, a, b) return __TS__ParseInt(a) - __TS__ParseInt(b) end
)
cardsArray = __TS__ArrayFilter(
__TS__ArrayMap(
keys,
function(____, key) return cards[key] end
),
function(____, card) return type(card) == "number" end
)
else
return
end
local normalizedCards = self:normalizeDeckCards(cardsArray)
local finalDeckName = self:resolveDeckName(deckName, index)
self.decks[tostring(index)] = {name = finalDeckName, cards = normalizedCards}
self:SyncDecks()
self:SaveDeckToServer(index, normalizedCards, finalDeckName)
if index == self.activeDeckIndex then
self:ReinitCardPool()
end
end
function CardSystem.prototype.SaveDeckToServer(self, index, cards, deckName)
local player = self:GetPlayer()
if not player then
return
end
local steamId = PlayerResource:GetSteamAccountID(self.playerId)
if not steamId then
return
end
local finalDeckName
if deckName and __TS__StringTrim(deckName) ~= "" then
finalDeckName = __TS__StringTrim(deckName)
else
local deckData = self.decks[tostring(index)]
if deckData and type(deckData) == "table" and deckData.name ~= nil then
finalDeckName = deckData.name or "Колода " .. tostring(index)
else
finalDeckName = "Колода " .. tostring(index)
end
end
local request = CreateHTTPRequestScriptVM(
"PUT",
(((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/decks/") .. tostring(index)
)
setApiHeaders(nil, request)
local dataToSend = {name = finalDeckName, cards = cards, active = index == self.activeDeckIndex}
request:SetHTTPRequestRawPostBody(
"application/json",
json.encode(dataToSend)
)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
else
end
end)
end
function CardSystem.prototype.DeleteDeck(self, index)
__TS__Delete(
self.decks,
tostring(index)
)
self:SyncDecks()
self:DeleteDeckFromServer(index)
end
function CardSystem.prototype.DeleteDeckFromServer(self, index)
local player = self:GetPlayer()
if not player then
return
end
local steamId = PlayerResource:GetSteamAccountID(self.playerId)
if not steamId then
return
end
local request = CreateHTTPRequestScriptVM(
"DELETE",
(((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/decks/") .. tostring(index)
)
setApiHeaders(nil, request)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
else
end
end)
end
function CardSystem.prototype.ActivateDeck(self, index)
self.activeDeckIndex = index
self:SyncDecks()
local player = self:GetPlayer()
if player then
CustomGameEventManager:Send_ServerToPlayer(player, "deck_selected", {index = index})
end
self:UpdateActiveDeckOnServer()
self:ReinitCardPool()
end
function CardSystem.prototype.UpdateActiveDeckOnServer(self)
local player = self:GetPlayer()
if not player then
return
end
local steamId = PlayerResource:GetSteamAccountID(self.playerId)
if not steamId then
return
end
for ____, ____value in ipairs(__TS__ObjectEntries(self.decks)) do
local deckIndex = ____value[1]
local deckData = ____value[2]
local index = __TS__ParseInt(deckIndex)
local isActive = index == self.activeDeckIndex
local deckName
local deckCards
if __TS__ArrayIsArray(deckData) then
deckName = deckData[1]
deckCards = __TS__ArraySlice(deckData, 1)
elseif deckData and type(deckData) == "table" and deckData.name ~= nil then
deckName = deckData.name or "Колода " .. tostring(index)
deckCards = deckData.cards or ({})
else
local keys = __TS__ArraySort(
__TS__ObjectKeys(deckData),
function(____, a, b) return __TS__ParseInt(a) - __TS__ParseInt(b) end
)
deckName = deckData[keys[1]] or "Колода " .. tostring(index)
deckCards = __TS__ArrayFilter(
__TS__ArrayMap(
__TS__ArraySlice(keys, 1),
function(____, key) return deckData[key] end
),
function(____, card) return type(card) == "number" end
)
end
local request = CreateHTTPRequestScriptVM(
"PUT",
(((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/decks/") .. tostring(index)
)
setApiHeaders(nil, request)
request:SetHTTPRequestHeaderValue("Content-Type", "application/json")
request:SetHTTPRequestNetworkActivityTimeout(10)
request:SetHTTPRequestAbsoluteTimeoutMS(10000)
local dataToSend = {name = deckName, cards = deckCards, active = isActive}
request:SetHTTPRequestRawPostBody(
"application/json",
json.encode(dataToSend)
)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
else
end
end)
end
end
function CardSystem.prototype.LoadDecks(self)
local player = self:GetPlayer()
if not player then
return
end
local steamId = PlayerResource:GetSteamAccountID(self.playerId)
if not steamId then
return
end
local request = CreateHTTPRequestScriptVM(
"GET",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/decks"
)
setApiHeaders(nil, request)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
do
pcall(function()
local data = {json.decode(result.Body)}
if data then
for key in pairs(data) do
end
else
end
local success = true
local decks = data and data[1]
local activeDeckIndex = 0
if data and success and decks then
self.decks = decks.decks or decks
self.activeDeckIndex = decks.activeDeckIndex or activeDeckIndex or 0
self:normalizeAllDecks()
for key in pairs(self.decks) do
end
for ____, ____value in ipairs(__TS__ObjectEntries(self.decks)) do
local deckIndex = ____value[1]
local deckData = ____value[2]
do
if type(deckData) == "boolean" then
goto __continue536
end
if type(deckData) == "number" then
goto __continue536
end
local isLegacy = __TS__ArrayIsArray(deckData)
local deckName = isLegacy and deckData[1] or deckData.name
local cardCount = isLegacy and #deckData - 1 or (deckData.cards ~= nil and deckData.cards ~= nil and #deckData.cards or 0)
end
::__continue536::
end
self:SyncDecks()
else
self:SyncDecks()
end
end)
end
else
end
end)
end
function CardSystem.prototype.GetPlayer(self)
return PlayerResource:GetPlayer(self.playerId)
end
function CardSystem.prototype.resolveDeckName(self, rawName, index)
if type(rawName) == "string" and __TS__StringTrim(rawName) ~= "" then
return __TS__StringTrim(rawName)
end
local existingDeck = self.decks[tostring(index)]
if existingDeck ~= nil and existingDeck ~= nil then
if __TS__ArrayIsArray(existingDeck) then
local legacyName = __TS__StringTrim(tostring(existingDeck[1] or ""))
if legacyName ~= "" then
return legacyName
end
elseif type(existingDeck) == "table" and existingDeck.name ~= nil then
local existingName = __TS__StringTrim(tostring(existingDeck.name or ""))
if existingName ~= "" then
return existingName
end
end
end
return "Колода " .. tostring(index)
end
function CardSystem.prototype.getDeckCardsList(self, deckData)
if not deckData then
return {}
end
if __TS__ArrayIsArray(deckData) then
return __TS__ArrayMap(
__TS__ArrayFilter(
__TS__ArrayMap(
__TS__ArraySlice(deckData, 1),
function(____, card) return __TS__Number(card) end
),
function(____, card) return __TS__NumberIsFinite(card) and card > 0 end
),
function(____, card) return math.floor(card) end
)
end
local cards = deckData.cards
if __TS__ArrayIsArray(cards) then
return __TS__ArrayMap(
__TS__ArrayFilter(
__TS__ArrayMap(
cards,
function(____, card) return __TS__Number(card) end
),
function(____, card) return __TS__NumberIsFinite(card) and card > 0 end
),
function(____, card) return math.floor(card) end
)
end
if cards and type(cards) == "table" then
return __TS__ArrayMap(
__TS__ArrayFilter(
__TS__ArrayMap(
__TS__ObjectValues(cards),
function(____, card) return __TS__Number(card) end
),
function(____, card) return __TS__NumberIsFinite(card) and card > 0 end
),
function(____, card) return math.floor(card) end
)
end
return {}
end
function CardSystem.prototype.getDefaultDeckSeed(self)
local seed = {}
local seen = __TS__New(Set)
local function pushIfValid(____, rawId)
local id = math.floor(__TS__Number(rawId))
if not __TS__NumberIsFinite(id) or id <= 0 or seen:has(id) then
return
end
local cardData = ____exports.CardSystem.cardData[id]
if not cardData or cardData.disabled == true then
return
end
seen:add(id)
seed[#seed + 1] = id
end
for ____, cardId in ipairs(DEFAULT_DECK_CARD_IDS) do
pushIfValid(nil, cardId)
end
if #seed > 0 then
return seed
end
for ____, ____value in ipairs(__TS__ObjectEntries(____exports.CardSystem.cardData)) do
local cardIdRaw = ____value[1]
local cardData = ____value[2]
do
if not cardData or cardData.disabled == true then
goto __continue571
end
pushIfValid(
nil,
__TS__Number(cardIdRaw)
)
end
::__continue571::
end
return seed
end
function CardSystem.prototype.normalizeDeckCards(self, rawDeck)
local requiredSize = DECK_BUILDER_SLOT_CAPACITY
local normalized = {}
local copiesByCardId = {}
local sourceCards = __TS__ArrayIsArray(rawDeck) and rawDeck or self:getDeckCardsList(rawDeck)
local function getCardMaxCopiesForDeck(____, cardData)
local configured = __TS__Number(cardData.max_copies)
if __TS__NumberIsFinite(configured) and configured > 0 then
return math.floor(configured)
end
if cardData.quality >= ____exports.CardQuality.LEGENDARY then
return 1
end
return 2
end
local function getCardMaxCopiesForDeckById(____, cardId)
local cardData = ____exports.CardSystem.cardData[cardId]
if not cardData then
return 2
end
return getCardMaxCopiesForDeck(nil, cardData)
end
for ____, rawId in ipairs(sourceCards) do
do
local id = math.floor(__TS__Number(rawId))
if not __TS__NumberIsFinite(id) or id <= 0 then
goto __continue580
end
local cardData = ____exports.CardSystem.cardData[id]
if not cardData then
if not ____exports.CardSystem:canAddCardToDeckSlots(normalized, id, requiredSize) then
goto __continue580
end
normalized[#normalized + 1] = id
copiesByCardId[id] = (copiesByCardId[id] or 0) + 1
if ____exports.CardSystem:getDeckUsedSlots(normalized) >= requiredSize then
break
end
goto __continue580
end
if cardData.disabled == true then
goto __continue580
end
if cardData.deck_builder_non_deckable == true then
goto __continue580
end
local maxCopies = getCardMaxCopiesForDeck(nil, cardData)
local currentCopies = copiesByCardId[id] or 0
if currentCopies >= maxCopies then
goto __continue580
end
if not ____exports.CardSystem:canAddCardToDeckSlots(normalized, id, requiredSize) then
goto __continue580
end
normalized[#normalized + 1] = id
copiesByCardId[id] = currentCopies + 1
if ____exports.CardSystem:getDeckUsedSlots(normalized) >= requiredSize then
break
end
end
::__continue580::
end
if ____exports.CardSystem:getDeckUsedSlots(normalized) >= requiredSize then
return normalized
end
local seed = self:getDefaultDeckSeed()
if #seed <= 0 then
local emergencySeed = __TS__ArrayFilter(
__TS__ArrayFrom(__TS__New(Set, normalized)),
function(____, id) return __TS__NumberIsFinite(id) and id > 0 end
)
if #emergencySeed > 0 then
seed = emergencySeed
else
local sourceSeed = __TS__ArrayFilter(
__TS__ArrayMap(
__TS__ArrayFrom(__TS__New(Set, sourceCards)),
function(____, id) return math.floor(__TS__Number(id)) end
),
function(____, id) return __TS__NumberIsFinite(id) and id > 0 end
)
if #sourceSeed > 0 then
seed = sourceSeed
else
return normalized
end
end
end
while ____exports.CardSystem:getDeckUsedSlots(normalized) < requiredSize do
local addedInPass = 0
for ____, nextId in ipairs(seed) do
do
local cardData = ____exports.CardSystem.cardData[nextId]
if cardData and cardData.disabled == true then
goto __continue601
end
if cardData and cardData.deck_builder_non_deckable == true then
goto __continue601
end
if (copiesByCardId[nextId] or 0) > 0 then
goto __continue601
end
local maxCopies = getCardMaxCopiesForDeckById(nil, nextId)
local currentCopies = copiesByCardId[nextId] or 0
if currentCopies >= maxCopies then
goto __continue601
end
if not ____exports.CardSystem:canAddCardToDeckSlots(normalized, nextId, requiredSize) then
goto __continue601
end
normalized[#normalized + 1] = nextId
copiesByCardId[nextId] = currentCopies + 1
addedInPass = addedInPass + 1
if ____exports.CardSystem:getDeckUsedSlots(normalized) >= requiredSize then
break
end
end
::__continue601::
end
if addedInPass <= 0 then
break
end
end
if ____exports.CardSystem:getDeckUsedSlots(normalized) < requiredSize and #seed > 0 then
while ____exports.CardSystem:getDeckUsedSlots(normalized) < requiredSize do
local addedInPass = 0
for ____, nextId in ipairs(seed) do
do
local cardData = ____exports.CardSystem.cardData[nextId]
if cardData and cardData.disabled == true then
goto __continue612
end
if cardData and cardData.deck_builder_non_deckable == true then
goto __continue612
end
local maxCopies = getCardMaxCopiesForDeckById(nil, nextId)
local currentCopies = copiesByCardId[nextId] or 0
if currentCopies >= maxCopies then
goto __continue612
end
if not ____exports.CardSystem:canAddCardToDeckSlots(normalized, nextId, requiredSize) then
goto __continue612
end
normalized[#normalized + 1] = nextId
copiesByCardId[nextId] = currentCopies + 1
addedInPass = addedInPass + 1
if ____exports.CardSystem:getDeckUsedSlots(normalized) >= requiredSize then
break
end
end
::__continue612::
end
if addedInPass <= 0 then
break
end
end
end
return normalized
end
function CardSystem.prototype.normalizeAllDecks(self)
local normalizedDecks = {}
for ____, ____value in ipairs(__TS__ObjectEntries(self.decks)) do
local deckIndexRaw = ____value[1]
local deckData = ____value[2]
do
local index = __TS__Number(deckIndexRaw)
if not __TS__NumberIsFinite(index) or index <= 0 then
goto __continue621
end
normalizedDecks[deckIndexRaw] = {
name = self:resolveDeckName(
deckData,
math.floor(index)
),
cards = self:normalizeDeckCards(deckData)
}
end
::__continue621::
end
self.decks = normalizedDecks
end
function CardSystem.prototype.SyncDecks(self)
for ____, ____value in ipairs(__TS__ObjectEntries(self.decks)) do
local deckIndex = ____value[1]
local deckData = ____value[2]
do
if not deckData then
goto __continue625
end
local isLegacy = __TS__ArrayIsArray(deckData)
local deckName
local cardCount
if isLegacy then
deckName = deckData[1]
cardCount = #deckData - 1
else
deckName = deckData.name or "Колода " .. deckIndex
local cards = deckData.cards
cardCount = cards ~= nil and cards ~= nil and __TS__ArrayIsArray(cards) and #cards or 0
end
end
::__continue625::
end
CustomNetTables:SetTableValue(
"cards",
"decks_" .. tostring(self.playerId),
self.decks
)
CustomNetTables:SetTableValue(
"cards",
"active_deck_" .. tostring(self.playerId),
{index = self.activeDeckIndex}
)
self:SyncCardPieces()
self:CaptureRuntimeState()
end
function CardSystem.prototype.SyncPlayerItems(self)
CustomNetTables:SetTableValue(
"cards",
"selection_" .. tostring(self.playerId),
self.playerItems
)
end
function CardSystem.prototype.SyncSelectionQueue(self)
local data = {size = #self.cardSelectionQueue, showing = self.isShowingCardSelection}
CustomNetTables:SetTableValue(
"cards",
"selection_queue_" .. tostring(self.playerId),
data
)
end
function CardSystem.prototype.SyncSelectionSource(self)
CustomNetTables:SetTableValue(
"cards",
"selection_source_" .. tostring(self.playerId),
{
source = self.currentSelectionSource,
chain = self:SerializeSourceChain(self.currentSelectionSourceChain),
chain_items = self.currentSelectionSourceChain,
selection_token = self.currentSelectionToken
}
)
end
function CardSystem.prototype.SyncPoolSourceChains(self)
local data = {}
for entryId in pairs(self.poolEntriesById) do
do
local entry = self.poolEntriesById[entryId]
local chain = entry and entry.sourceChain or ({})
if #chain <= 0 then
goto __continue634
end
data[entryId] = {
index = entry.cardId,
source = chain[#chain] or "unknown",
chain = self:SerializeSourceChain(chain),
chain_items = chain
}
end
::__continue634::
end
CustomNetTables:SetTableValue(
"cards",
"pool_source_" .. tostring(self.playerId),
data
)
end
function CardSystem.prototype.SyncActiveCards(self)
CustomNetTables:SetTableValue(
"cards",
"active_cards_" .. tostring(self.playerId),
self.itemsId
)
self:CaptureRuntimeState()
end
function CardSystem.prototype.SyncPool(self)
local poolData = {}
for entryId in pairs(self.poolEntriesById) do
do
local entry = self.poolEntriesById[entryId]
if not entry or entry.weight <= 0 then
goto __continue639
end
poolData[#poolData + 1] = {entry_id = entry.entryId, index = entry.cardId, weight = entry.weight}
end
::__continue639::
end
CustomNetTables:SetTableValue(
"cards",
"pool_" .. tostring(self.playerId),
poolData
)
self:SyncPoolSourceChains()
self:SyncRemainingCards()
self:CaptureRuntimeState()
end
function CardSystem.prototype.SyncRemainingCards(self)
local remainingCount = self.pool ~= nil and self.pool.totalProportion or 0
CustomNetTables:SetTableValue(
"cards",
"remaining_cards_" .. tostring(self.playerId),
{count = remainingCount}
)
end
function CardSystem.prototype.SyncCardData(self)
if ____exports.CardSystem.clientCardCatalogSynced then
return
end
____exports.CardSystem.clientCardCatalogSynced = true
local function serializeValuesForNetTable(____, values)
if not values then
return nil
end
local out = {}
for ____, ____value in ipairs(__TS__ObjectEntries(values)) do
local key = ____value[1]
local raw = ____value[2]
do
if raw == nil or raw == nil then
goto __continue647
end
if type(raw) == "table" then
local leveled = {}
local hasLevel = false
local tableRaw = raw
do
local i = 1
while i <= 32 do
local ____tableRaw_i_78 = tableRaw[i]
if ____tableRaw_i_78 == nil then
____tableRaw_i_78 = tableRaw[tostring(i)]
end
local v = __TS__Number(____tableRaw_i_78)
if __TS__NumberIsFinite(v) then
leveled[tostring(i)] = v
hasLevel = true
end
i = i + 1
end
end
if hasLevel then
out[key] = leveled
goto __continue647
end
end
local numeric = __TS__Number(raw)
if __TS__NumberIsFinite(numeric) then
out[key] = numeric
end
end
::__continue647::
end
return out
end
local cardDataForClient = {}
for cardId in pairs(____exports.CardSystem.cardData) do
local card = ____exports.CardSystem.cardData[cardId]
cardDataForClient[cardId] = __TS__ObjectAssign(
{},
card,
{
values = serializeValuesForNetTable(nil, card.values),
disabled = card.disabled == true and "true" or "false",
inherent = card.inherent == true and "true" or "false",
default = card.default == true and "true" or "false",
purchasable = card.purchasable == false and "false" or "true",
obtainable = card.obtainable == false and "false" or "true",
deck_builder_non_deckable = card.deck_builder_non_deckable == true and "true" or "false",
deck_builder_unlock_card_id = card.deck_builder_unlock_card_id
}
)
end
CustomNetTables:SetTableValue("cards", "card_data", cardDataForClient)
for cardId in pairs(____exports.CardSystem.cardData) do
local card = ____exports.CardSystem.cardData[cardId]
if card.disabled == true then
end
end
end
function CardSystem.prototype.SyncRerollCost(self)
local key = "reroll_cost_" .. tostring(self.playerId)
local card49Charges = self:HasActiveCard(49) and self.card49FreeRerollCharges or 0
local card6Charges = self:GetCard6FreeRerollChargesForUse()
local card88Charges = self:GetCard88FreeRerollChargesForUse()
local totalFreeCharges = card49Charges + card6Charges + card88Charges
local hasFreeReroll = self.rerollCost > 0 and totalFreeCharges > 0
local data = {
cost = self.rerollCost,
card49_free_reroll = hasFreeReroll and "1" or "0",
card49_free_charges = tostring(card49Charges),
card6_free_charges = tostring(card6Charges),
card88_free_charges = tostring(card88Charges),
free_reroll_charges_total = tostring(totalFreeCharges)
}
CustomNetTables:SetTableValue("cards", key, data)
self:CaptureRuntimeState()
end
function CardSystem.prototype.PushRerollCostToClient(self)
self:SyncRerollCost()
end
function CardSystem.prototype.ResetCard51MorningGold(self)
self.card51MorningGoldEarned = 0
self:CaptureRuntimeState()
end
function CardSystem.prototype.GetCard51MorningGoldEarned(self)
return math.max(
0,
math.floor(self.card51MorningGoldEarned)
)
end
function CardSystem.prototype.TryGrantCard51GoldFromDamage(self, hero, goldGain)
if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then
return 0
end
local baseCap = math.max(
0,
math.floor(self:getCardValueByLevel(51, "morning_gold_cap", 400))
)
local copies = math.max(
1,
self:GetActiveCardCopies(51)
)
local cap = baseCap * copies
if cap <= 0 or goldGain <= 0 then
return 0
end
local earned = self:GetCard51MorningGoldEarned()
local remaining = math.max(0, cap - earned)
local actualGain = math.min(goldGain, remaining)
if actualGain <= 0 then
return 0
end
self.card51MorningGoldEarned = earned + actualGain
PlayerResource:ModifyGold(self.playerId, actualGain, true, DOTA_ModifyGold_Unspecified)
SendOverheadEventMessage(
nil,
OVERHEAD_ALERT_GOLD,
hero,
actualGain,
hero:GetPlayerOwner()
)
self:CaptureRuntimeState()
return actualGain
end
function CardSystem.prototype.AddSlagToPool(self, weight, count, source, sourceChain)
if weight == nil then
weight = 1
end
if count == nil then
count = 1
end
if source == nil then
source = "card_85_fishing_slag"
end
local safeWeight = math.max(
1,
math.floor(weight)
)
local safeCount = math.max(
1,
math.floor(count)
)
if safeCount <= 1 then
self:CreatePoolEntry(SLAG_CARD_ID, safeWeight, source, sourceChain)
else
self:CreatePoolEntry(SLAG_CARD_ID, safeWeight * safeCount, source, sourceChain)
end
self:TryGrantCard88FreeRerollsFromSlag(safeCount)
self:RebuildPoolFromEntries()
self:SyncPool()
end
function CardSystem.prototype.removePoolEntriesForCardWithSourceToken(self, cardId, sourceToken)
local normalizedCardId = math.floor(__TS__Number(cardId))
local normalizedSource = self:NormalizeSourceToken(sourceToken)
if not __TS__NumberIsFinite(normalizedCardId) or normalizedCardId <= 0 or #normalizedSource <= 0 then
return
end
for entryId in pairs(self.poolEntriesById) do
do
local entry = self.poolEntriesById[entryId]
if not entry or entry.cardId ~= normalizedCardId then
goto __continue673
end
local chain = entry.sourceChain or ({})
local matches = false
for ____, token in ipairs(chain) do
if self:NormalizeSourceToken(token) == normalizedSource then
matches = true
break
end
end
if matches then
__TS__Delete(self.poolEntriesById, entryId)
end
end
::__continue673::
end
end
function CardSystem.prototype.SetPoolCardFromSource(self, cardId, weight, source, sourceChain)
if weight == nil then
weight = 1
end
if source == nil then
source = "manual_pool_add"
end
if isSlagCardId(nil, cardId) then
self:AddSlagToPool(weight, 1, source, sourceChain)
return
end
local cardData = ____exports.CardSystem.cardData[cardId]
if cardData and (cardData.disabled == true or cardData.obtainable == false) then
return
end
self:removePoolEntriesForCardWithSourceToken(cardId, source)
self:CreatePoolEntry(
cardId,
math.max(
1,
math.floor(weight)
),
source,
sourceChain
)
self:RebuildPoolFromEntries()
self:SyncPool()
end
function CardSystem.prototype.AddCardToPool(self, cardId, weight, source, sourceChain)
if weight == nil then
weight = 5
end
if source == nil then
source = "manual_pool_add"
end
if isSlagCardId(nil, cardId) then
self:AddSlagToPool(weight, 1, source, sourceChain)
return
end
local cardData = ____exports.CardSystem.cardData[cardId]
if cardData and (cardData.disabled == true or cardData.obtainable == false) then
return
end
self:CreatePoolEntry(cardId, weight, source, sourceChain)
self:RebuildPoolFromEntries()
self:SyncPool()
end
function CardSystem.prototype.AddCardToPoolCount(self, cardId, weight, count, source, sourceChain)
if weight == nil then
weight = 5
end
if count == nil then
count = 1
end
if source == nil then
source = "manual_pool_add"
end
if isSlagCardId(nil, cardId) then
self:AddSlagToPool(weight, count, source, sourceChain)
return
end
local cardData = ____exports.CardSystem.cardData[cardId]
if cardData and (cardData.disabled == true or cardData.obtainable == false) then
return
end
local safeCount = math.max(
0,
math.floor(count)
)
if safeCount <= 1 then
self:CreatePoolEntry(cardId, weight, source, sourceChain)
else
self:CreatePoolEntry(cardId, weight * safeCount, source, sourceChain)
end
self:RebuildPoolFromEntries()
self:SyncPool()
end
function CardSystem.prototype.RemoveCardFromPool(self, cardId)
for entryId in pairs(self.poolEntriesById) do
local entry = self.poolEntriesById[entryId]
if entry and entry.cardId == cardId then
__TS__Delete(self.poolEntriesById, entryId)
end
end
self:RebuildPoolFromEntries()
self:SyncPool()
end
function CardSystem.prototype.HasCardInPool(self, cardId)
for entryId in pairs(self.poolEntriesById) do
local entry = self.poolEntriesById[entryId]
if entry and entry.cardId == cardId and entry.weight > 0 then
return true
end
end
return false
end
function CardSystem.prototype.GetPoolSize(self)
return self.pool.len
end
function CardSystem.prototype.GetPoolCards(self)
local set = __TS__New(Set)
for entryId in pairs(self.poolEntriesById) do
local entry = self.poolEntriesById[entryId]
if entry and entry.weight > 0 then
set:add(entry.cardId)
end
end
return __TS__ArrayFrom(set:values())
end
function CardSystem.prototype.GetPoolCardWeightSum(self, cardId)
local normalizedCardId = math.floor(__TS__Number(cardId))
if not __TS__NumberIsFinite(normalizedCardId) or normalizedCardId <= 0 then
return 0
end
local total = 0
for entryId in pairs(self.poolEntriesById) do
local entry = self.poolEntriesById[entryId]
if entry and entry.cardId == normalizedCardId and entry.weight > 0 then
total = total + entry.weight
end
end
return total
end
function CardSystem.prototype.ClearPool(self)
self.poolEntriesById = {}
self.pool:clear()
self:SyncPool()
end
function CardSystem.prototype.RemoveRandomCardsFromPool(self, count)
local removedCards = {}
local allCards = self:GetPoolCards()
if #allCards == 0 then
return removedCards
end
local cardsToRemove = math.min(count, #allCards)
local shuffledCards = {unpack(allCards)}
do
local i = #shuffledCards - 1
while i > 0 do
local j = math.floor(math.random() * (i + 1))
local ____temp_79 = {shuffledCards[j + 1], shuffledCards[i + 1]}
shuffledCards[i + 1] = ____temp_79[1]
shuffledCards[j + 1] = ____temp_79[2]
i = i - 1
end
end
do
local i = 0
while i < cardsToRemove do
local cardId = shuffledCards[i + 1]
for entryId in pairs(self.poolEntriesById) do
local entry = self.poolEntriesById[entryId]
if entry and entry.cardId == cardId then
__TS__Delete(self.poolEntriesById, entryId)
end
end
removedCards[#removedCards + 1] = cardId
i = i + 1
end
end
self:RebuildPoolFromEntries()
self:SyncPool()
return removedCards
end
CardSystem.CARD_49_FREE_STACK_CAP = 99
CardSystem.CARD_6_ID = 6
CardSystem.CARD_6_FREE_STACK_CAP = 99
CardSystem.CARD_88_ID = 88
CardSystem.CARD_88_FREE_STACK_CAP = 99
CardSystem.CARD_UPGRADE_BASE_COST_BY_QUALITY = {
[____exports.CardQuality.COMMON] = 500,
[____exports.CardQuality.RARE] = 1000,
[____exports.CardQuality.EPIC] = 2000,
[____exports.CardQuality.LEGENDARY] = 7000,
[____exports.CardQuality.MYTHIC] = 15000
}
CardSystem.cardTypes = {}
CardSystem.cardData = {}
CardSystem.clientCardCatalogSynced = false
CardSystem.runtimeStateByPlayerId = {}
CardSystem.QUALITY_FALLBACK_ORDER = {
____exports.CardQuality.MYTHIC,
____exports.CardQuality.LEGENDARY,
____exports.CardQuality.EPIC,
____exports.CardQuality.RARE,
____exports.CardQuality.COMMON
}
--- Инициализация Card System для всех игроков
function ____exports.InitCardSystem(self)
local function initPlayerCardSystem(____, playerId)
local player = PlayerResource:GetPlayer(playerId)
if player and not player.cardSystem then
player.cardSystem = __TS__New(____exports.CardSystem, playerId)
end
end
local function initAllConnectedLobbyPlayers()
do
local i = 0
while i < DOTA_MAX_PLAYERS do
do
local playerId = i
if not isRealLobbyPlayer(nil, playerId) then
goto __continue721
end
initPlayerCardSystem(nil, playerId)
end
::__continue721::
i = i + 1
end
end
end
CustomGameEventManager:RegisterListener(
"init_card_system",
function(source, _event)
local playerId = source
if playerId == nil or playerId < 0 then
return
end
initPlayerCardSystem(nil, playerId)
local player = PlayerResource:GetPlayer(playerId)
local ____opt_80 = player and player.cardSystem
if ____opt_80 ~= nil then
____opt_80:ForceDeckBuilderSync()
end
end
)
CustomGameEventManager:RegisterListener(
"request_available_cards",
function(source, _event)
local playerId = source
if playerId == nil or playerId < 0 then
return
end
initPlayerCardSystem(nil, playerId)
local player = PlayerResource:GetPlayer(playerId)
local ____opt_84 = player and player.cardSystem
if ____opt_84 ~= nil then
____opt_84:ForceDeckBuilderSync()
end
end
)
ListenToGameEvent(
"npc_spawned",
function(event)
local unit = EntIndexToHScript(event.entindex)
if unit and unit:IsRealHero() then
local playerId = unit:GetPlayerOwnerID()
if playerId >= 0 then
Timers:CreateTimer(
0.1,
function()
initPlayerCardSystem(nil, playerId)
local player = PlayerResource:GetPlayer(playerId)
local hero = player and player:GetAssignedHero()
if hero and IsValidEntity(hero) and hero:IsRealHero() then
updateGreedForHero(nil, hero, player and player.cardSystem)
end
return nil
end
)
end
end
end,
nil
)
ListenToGameEvent(
"game_rules_state_change",
function()
local state = GameRules:State_Get()
if state == DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP then
initAllConnectedLobbyPlayers(nil)
end
end,
nil
)
end
--- Утилитарная функция: получить количество взятых карт игрока
function ____exports.GetPlayerCardsTaken(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
return player.cardSystem:GetCardsTaken()
end
return 0
end
--- Сколько карт активно у игрока (в т.ч. стартовая колода) — для масштабирования эффектов вроде карты 5.
function ____exports.GetPlayerActiveCardCount(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
return player.cardSystem:GetActiveCardCount()
end
return 0
end
--- Активные карты игрока без врождённых из колоды.
function ____exports.GetPlayerActiveCardCountExcludingInherent(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
return player.cardSystem:GetActiveCardCountExcludingInherent()
end
return 0
end
--- Добавить карту в пул игрока
function ____exports.AddCardToPlayerPool(self, playerId, cardId, weight, count, source)
if weight == nil then
weight = 5
end
if count == nil then
count = 1
end
if source == nil then
source = "manual_pool_add"
end
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
player.cardSystem:AddCardToPoolCount(cardId, weight, count, source)
else
end
end
--- Замешать шлак (86) в пул игрока.
function ____exports.AddSlagToPlayerPool(self, playerId, weight, count, source)
if weight == nil then
weight = 1
end
if count == nil then
count = 1
end
if source == nil then
source = "card_85_fishing_slag"
end
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
player.cardSystem:AddSlagToPool(weight, count, source)
end
end
--- Удалить карту из пула игрока
function ____exports.RemoveCardFromPlayerPool(self, playerId, cardId)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
player.cardSystem:RemoveCardFromPool(cardId)
else
end
end
--- Проверить, есть ли карта в пуле игрока
function ____exports.HasCardInPlayerPool(self, playerId, cardId)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
return player.cardSystem:HasCardInPool(cardId)
end
return false
end
--- Получить размер пула игрока
function ____exports.GetPlayerPoolSize(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
return player.cardSystem:GetPoolSize()
end
return 0
end
--- Получить все карты в пуле игрока
function ____exports.GetPlayerPoolCards(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
return player.cardSystem:GetPoolCards()
end
return {}
end
--- Очистить пул игрока
function ____exports.ClearPlayerPool(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
player.cardSystem:ClearPool()
else
end
end
--- Удалить случайные карты из пула игрока
function ____exports.RemoveRandomCardsFromPlayerPool(self, playerId, count)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
return player.cardSystem:RemoveRandomCardsFromPool(count)
else
return {}
end
end
--- Добавить гарантированные карты определенного качества в реролы для игрока
function ____exports.AddPlayerGuaranteedQualityRerolls(self, playerId, quality, rerollsCount, cardsPerReroll, fallbackBehavior)
if fallbackBehavior == nil then
fallbackBehavior = "skip"
end
print("[AddPlayerGuaranteedQualityRerolls] ===== ДОБАВЛЕНИЕ ГАРАНТИРОВАННЫХ КАРТ =====")
print("[AddPlayerGuaranteedQualityRerolls] Игрок: " .. tostring(playerId))
print("[AddPlayerGuaranteedQualityRerolls] Качество: " .. tostring(quality))
print("[AddPlayerGuaranteedQualityRerolls] Количество реролов: " .. tostring(rerollsCount))
print("[AddPlayerGuaranteedQualityRerolls] Карт за рерол: " .. tostring(cardsPerReroll))
print("[AddPlayerGuaranteedQualityRerolls] Поведение при отсутствии: " .. fallbackBehavior)
local player = PlayerResource:GetPlayer(playerId)
if not player then
print(("[AddPlayerGuaranteedQualityRerolls] ❌ Игрок " .. tostring(playerId)) .. " не найден")
return
end
local hero = player:GetAssignedHero()
if not hero then
print(("[AddPlayerGuaranteedQualityRerolls] ❌ Герой игрока " .. tostring(playerId)) .. " не найден")
return
end
if not player.cardSystem then
print(("[AddPlayerGuaranteedQualityRerolls] ⚠️ CardSystem не найден для игрока " .. tostring(playerId)) .. ", создаю...")
print("[AddPlayerGuaranteedQualityRerolls] Тип player: " .. __TS__TypeOf(player))
print("[AddPlayerGuaranteedQualityRerolls] player.cardSystem до создания: " .. tostring(player.cardSystem))
player.cardSystem = __TS__New(____exports.CardSystem, playerId)
print(("[AddPlayerGuaranteedQualityRerolls] ✅ CardSystem создан для игрока " .. tostring(playerId)) .. "!")
print("[AddPlayerGuaranteedQualityRerolls] player.cardSystem после создания: " .. tostring(player.cardSystem))
else
print("[AddPlayerGuaranteedQualityRerolls] ✅ CardSystem уже существует для игрока " .. tostring(playerId))
end
print("[AddPlayerGuaranteedQualityRerolls] ✅ Все проверки пройдены, добавляем гарантированные карты")
player.cardSystem:AddGuaranteedQualityRerolls(quality, rerollsCount, cardsPerReroll, fallbackBehavior)
print("[AddPlayerGuaranteedQualityRerolls] ================================================")
end
--- Получить количество оставшихся реролов с гарантированными картами определенного качества для игрока
function ____exports.GetPlayerGuaranteedQualityRerolls(self, playerId, quality)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return 0
end
local hero = player:GetAssignedHero()
if not hero then
return 0
end
if not player.cardSystem then
return 0
end
return player.cardSystem:GetGuaranteedQualityRerolls(quality)
end
--- Получить количество карт в каждом рероле с гарантированными картами определенного качества для игрока
function ____exports.GetPlayerGuaranteedCardsPerReroll(self, playerId, quality)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return 0
end
local hero = player:GetAssignedHero()
if not hero then
return 0
end
if not player.cardSystem then
return 0
end
return player.cardSystem:GetGuaranteedCardsPerReroll(quality)
end
--- Получить все гарантированные карты качества для игрока
function ____exports.GetPlayerAllGuaranteedQualityRerolls(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return {}
end
local hero = player:GetAssignedHero()
if not hero then
return {}
end
if not player.cardSystem then
return {}
end
return player.cardSystem:GetAllGuaranteedQualityRerolls()
end
--- Очистить все гарантированные карты качества для игрока
function ____exports.ClearPlayerGuaranteedQualityRerolls(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
local hero = player:GetAssignedHero()
if not hero then
return
end
if not player.cardSystem then
return
end
player.cardSystem:ClearGuaranteedQualityRerolls()
end
--- Игрок с «Буйным ростом» (58) не получает стандартный выбор карт на рассвете.
function ____exports.shouldPlayerSkipDawnCardSelection(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if not (player and player.cardSystem) then
return false
end
return player.cardSystem:SkipsDawnCardSelection()
end
--- Показать выбор карт игроку с источником
function ____exports.ShowCardSelectionToPlayer(self, playerId, count, source, qualityFilter)
if source == nil then
source = "unknown"
end
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
local hero = player:GetAssignedHero()
if not hero then
return
end
if not player.cardSystem then
player.cardSystem = __TS__New(____exports.CardSystem, playerId)
end
player.cardSystem:ShowCardSelection(count, source, qualityFilter)
end
--- Выбор карт, среди которых гарантированно есть `forcedCardId` (остальные слоты — из пула / 404).
function ____exports.ShowCardSelectionWithForcedCardToPlayer(self, playerId, forcedCardId, count, source)
if count == nil then
count = 3
end
if source == nil then
source = "forced_card"
end
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
local hero = player:GetAssignedHero()
if not hero then
return
end
if not player.cardSystem then
player.cardSystem = __TS__New(____exports.CardSystem, playerId)
end
player.cardSystem:ShowCardSelectionWithForcedCard(forcedCardId, count, source)
end
--- Показ выбора карт из фиксированного списка id.
function ____exports.ShowCardSelectionExactOptionsToPlayer(self, playerId, optionIds, source)
if source == nil then
source = "exact_options"
end
local player = PlayerResource:GetPlayer(playerId)
if not player or not player.cardSystem then
print(("[ShowCardSelectionExactOptionsToPlayer] Игрок " .. tostring(playerId)) .. " не найден или не имеет cardSystem")
return
end
player.cardSystem:ShowCardSelectionExactOptions(optionIds, source)
end
--- Получить размер очереди выборов карт игрока
function ____exports.GetPlayerCardSelectionQueueSize(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
return player.cardSystem:GetCardSelectionQueueSize()
end
return 0
end
--- Очистить очередь выборов карт игрока
function ____exports.ClearPlayerCardSelectionQueue(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
player.cardSystem:ClearCardSelectionQueue()
else
end
end
--- Проверить, показывается ли выбор карт игроку
function ____exports.IsPlayerShowingCardSelection(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
return player.cardSystem:IsShowingCardSelection()
end
return false
end
--- Принудительно выполнить рерол карт для игрока (для тестирования)
function ____exports.ForceRerollCards(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
player.cardSystem:RerollCards(playerId)
else
end
end
--- Установить фильтр качества для рерола игрока
function ____exports.SetPlayerRerollQualityFilter(self, playerId, qualityFilter)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
player.cardSystem:SetRerollQualityFilter(qualityFilter)
else
end
end
--- Получить фильтр качества для рерола игрока
function ____exports.GetPlayerRerollQualityFilter(self, playerId)
local player = PlayerResource:GetPlayer(playerId)
if player and player.cardSystem then
return player.cardSystem:GetRerollQualityFilter()
end
return nil
end
return ____exports