72b73c4dd6
Allows the game client to make HTTP API calls from a listen server (local lobby) instead of requiring a Steam dedicated server. CreateHTTPRequestScriptVM has the exact same API signature but works in both dedicated server and listen server contexts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4203 lines
159 KiB
Lua
4203 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)
|
|
local cardData = ____exports.CardSystem.cardData[cardId]
|
|
local configured = __TS__Number(cardData and cardData.deck_slots)
|
|
if __TS__NumberIsFinite(configured) and configured > 0 then
|
|
return math.floor(configured)
|
|
end
|
|
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
|