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

1569 lines
57 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
local ____lualib = require("lualib_bundle")
local __TS__Class = ____lualib.__TS__Class
local __TS__New = ____lualib.__TS__New
local __TS__StringTrim = ____lualib.__TS__StringTrim
local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys
local __TS__NumberIsFinite = ____lualib.__TS__NumberIsFinite
local __TS__Delete = ____lualib.__TS__Delete
local __TS__ArraySort = ____lualib.__TS__ArraySort
local Set = ____lualib.Set
local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign
local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray
local __TS__Decorate = ____lualib.__TS__Decorate
local ____exports = {}
local ____tstl_2Dutils = require("lib.tstl-utils")
local reloadable = ____tstl_2Dutils.reloadable
local ____api_helper = require("api_helper")
local setApiHeaders = ____api_helper.setApiHeaders
local ____server_config = require("server_config")
local SERVER_CONFIG = ____server_config.SERVER_CONFIG
local ____ArsenalCatalog = require("arsenal.ArsenalCatalog")
local ARSENAL_ITEMS = ____ArsenalCatalog.ARSENAL_ITEMS
local ARSENAL_ITEMS_MAP = ____ArsenalCatalog.ARSENAL_ITEMS_MAP
local ARSENAL_SLOTS = ____ArsenalCatalog.ARSENAL_SLOTS
local publishArsenalCatalog = ____ArsenalCatalog.publishArsenalCatalog
local ____ArsenalStats = require("arsenal.ArsenalStats")
local createArsenalItemInstance = ____ArsenalStats.createArsenalItemInstance
local getEffectiveItemStats = ____ArsenalStats.getEffectiveItemStats
local ____ArsenalStatRuntime = require("arsenal.ArsenalStatRuntime")
local setArsenalTotalsProvider = ____ArsenalStatRuntime.setArsenalTotalsProvider
local ____store_manager = require("store_manager")
local StoreManager = ____store_manager.StoreManager
local ____real_lobby_player = require("utils.real_lobby_player")
local isRealLobbyPlayer = ____real_lobby_player.isRealLobbyPlayer
require("arsenal.items.weapon")
require("arsenal.items.armor")
require("arsenal.items.helmet")
require("arsenal.items.boots")
require("arsenal.items.necklace")
require("arsenal.items.ring")
require("arsenal.items.dynamic_stats")
local LOG_PREFIX = "[ArsenalManager]"
local ARSENAL_ROLL_VERSION = 2
local ARSENAL_INVENTORY_PUT_DEBOUNCE = 0.12
--- Включить тяжёлый лог пересчёта тоталов (json.encode на каждое изменение) — только для отладки.
local ARSENAL_VERBOSE_TOTALS_LOG = false
--- Максимум экземпляров предметов в инвентаре (синхрон с UI `MIN_CATALOG_CELLS` и PUT API).
local ARSENAL_INVENTORY_MAX_INSTANCES = 207
____exports.ArsenalManagerClass = __TS__Class()
local ArsenalManagerClass = ____exports.ArsenalManagerClass
ArsenalManagerClass.name = "ArsenalManagerClass"
ArsenalManagerClass.____file_path = "scripts/vscripts/arsenal/ArsenalManager.lua"
function ArsenalManagerClass.prototype.____constructor(self)
self.loadouts = {}
self.instances = {}
self.selectedHero = {}
self.appliedFor = {}
self.lastPrintedTotals = {}
self.inventorySaveGeneration = {}
end
function ArsenalManagerClass.getInstance(self)
if not ____exports.ArsenalManagerClass._instance then
____exports.ArsenalManagerClass._instance = __TS__New(____exports.ArsenalManagerClass)
end
return ____exports.ArsenalManagerClass._instance
end
function ArsenalManagerClass.prototype.initialize(self)
publishArsenalCatalog(nil)
setArsenalTotalsProvider(
nil,
function(____, hero) return self:getHeroStatTotals(hero) end
)
self:registerListeners()
print(LOG_PREFIX .. " initialized")
end
function ArsenalManagerClass.prototype.registerListeners(self)
CustomGameEventManager:RegisterListener(
"arsenal_equip_item",
function(_src, data)
local playerId = self:resolvePlayerId(data)
if playerId == nil then
return
end
self:handleEquip(
playerId,
data.hero,
data.slot,
tostring(data.instanceId or "")
)
end
)
CustomGameEventManager:RegisterListener(
"arsenal_unequip_item",
function(_src, data)
local playerId = self:resolvePlayerId(data)
if playerId == nil then
return
end
self:handleUnequip(playerId, data.hero, data.slot)
end
)
CustomGameEventManager:RegisterListener(
"arsenal_select_hero",
function(_src, data)
local playerId = self:resolvePlayerId(data)
if playerId == nil then
return
end
self.selectedHero[playerId] = tostring(data.hero or "")
self:syncToClient(playerId)
if self.selectedHero[playerId] and #self.selectedHero[playerId] > 0 then
self:refreshHeroArsenalStats(playerId, self.selectedHero[playerId])
self:scheduleArsenalStatRefresh(playerId)
end
end
)
CustomGameEventManager:RegisterListener(
"arsenal_request_sync",
function(_src, data)
local playerId = self:resolvePlayerId(data)
if playerId == nil then
return
end
self:loadFromServer(playerId)
end
)
CustomGameEventManager:RegisterListener(
"arsenal_upgrade_item",
function(_src, data)
local playerId = self:resolvePlayerId(data)
if playerId == nil then
return
end
self:handleUpgradeItem(
playerId,
tostring(data.instanceId or "")
)
end
)
CustomGameEventManager:RegisterListener(
"arsenal_toggle_pin",
function(_src, data)
local playerId = self:resolvePlayerId(data)
if playerId == nil then
return
end
self:handleTogglePin(
playerId,
tostring(data.instanceId or "")
)
end
)
CustomGameEventManager:RegisterListener(
"arsenal_toggle_favorite",
function(_src, data)
local playerId = self:resolvePlayerId(data)
if playerId == nil then
return
end
self:handleToggleFavorite(
playerId,
tostring(data.instanceId or "")
)
end
)
CustomGameEventManager:RegisterListener(
"arsenal_disassemble_item",
function(_src, data)
local playerId = self:resolvePlayerId(data)
if playerId == nil then
return
end
self:handleDisassembleItem(
playerId,
tostring(data.instanceId or "")
)
end
)
ListenToGameEvent(
"game_rules_state_change",
function()
if not IsServer() then
return
end
local s = GameRules:State_Get()
if s == DOTA_GAMERULES_STATE_PRE_GAME or s == DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then
do
local pid = 0
while pid < DOTA_MAX_PLAYERS do
do
if not isRealLobbyPlayer(nil, pid) then
goto __continue28
end
self:refreshHeroArsenalStats(pid, "")
self:scheduleArsenalStatRefresh(pid)
end
::__continue28::
pid = pid + 1
end
end
end
end,
nil
)
end
function ArsenalManagerClass.prototype.resolvePlayerId(self, data)
local ____opt_result_2
if data ~= nil then
____opt_result_2 = data.PlayerID
end
local ____opt_result_2_6 = ____opt_result_2
if ____opt_result_2_6 == nil then
local ____opt_result_5
if data ~= nil then
____opt_result_5 = data.playerId
end
____opt_result_2_6 = ____opt_result_5
end
local ____opt_result_2_6_10 = ____opt_result_2_6
if ____opt_result_2_6_10 == nil then
local ____opt_result_9
if data ~= nil then
____opt_result_9 = data.playerID
end
____opt_result_2_6_10 = ____opt_result_9
end
local raw = ____opt_result_2_6_10
if raw == nil or raw == nil then
return nil
end
local n = tonumber(tostring(raw))
if n == nil or n < 0 then
return nil
end
return n
end
function ArsenalManagerClass.prototype.normalizeArsenalStatKey(self, rawKey)
local key = string.lower(tostring(rawKey or ""))
local alias = {
battle_level = "battle_level",
bonus_damage = "bonus_damage",
attack_speed = "attack_speed",
bonus_armor = "bonus_armor",
max_health = "max_health",
max_mana = "max_mana",
health_regen = "health_regen",
mana_regen = "mana_regen",
bonus_health_regen = "health_regen",
bonus_mana_regen = "mana_regen",
hp_regen = "health_regen",
mp_regen = "mana_regen",
move_speed = "move_speed",
magic_resist = "magic_resist",
spell_amp = "spell_amp",
all_stats = "all_stats",
all_stats_pct = "all_stats_pct",
bonus_strength = "bonus_strength",
bonus_agility = "bonus_agility",
bonus_intellect = "bonus_intellect",
strength_pct = "strength_pct",
agility_pct = "agility_pct",
intellect_pct = "intellect_pct",
luck = "luck",
outgoing_damage_pct = "outgoing_damage_pct",
outgoing_damage = "outgoing_damage_pct",
damage_out_pct = "outgoing_damage_pct",
damage_outgoing_pct = "outgoing_damage_pct",
bonus_outgoing_damage_pct = "outgoing_damage_pct",
incoming_damage_reduction_pct = "incoming_damage_reduction_pct",
incoming_reduction_pct = "incoming_damage_reduction_pct",
damage_reduction_pct = "incoming_damage_reduction_pct",
attack_speed_pct = "attack_speed_pct",
attackspeed_pct = "attack_speed_pct",
attack_speed_percent = "attack_speed_pct",
move_speed_pct = "move_speed_pct",
movespeed_pct = "move_speed_pct",
move_speed_percent = "move_speed_pct",
base_damage_pct = "base_damage_pct",
bonus_damage_pct = "base_damage_pct",
attack_damage_pct = "base_damage_pct",
crit_mult = "crit_mult",
crit_multiplier = "crit_mult",
critical_multiplier = "crit_mult",
damage = "bonus_damage",
bonus_attack_damage = "bonus_damage",
attack_damage = "bonus_damage",
attackspeed = "attack_speed",
attack_speed_bonus = "attack_speed",
armor = "bonus_armor",
armor_bonus = "bonus_armor",
physical_armor = "bonus_armor",
health = "max_health",
bonus_health = "max_health",
mana = "max_mana",
bonus_mana = "max_mana",
movespeed = "move_speed",
movespeed_bonus = "move_speed",
move_speed_bonus = "move_speed",
magic_resistance = "magic_resist",
magical_resistance = "magic_resist",
magic_resistance_bonus = "magic_resist",
spell_amplify = "spell_amp",
spell_amplification = "spell_amp",
stats_all = "all_stats",
all_attributes_pct = "all_stats_pct",
attributes_pct = "all_stats_pct",
stats_pct = "all_stats_pct",
strength = "bonus_strength",
bonus_str = "bonus_strength",
str_bonus = "bonus_strength",
agility = "bonus_agility",
bonus_agi = "bonus_agility",
agi_bonus = "bonus_agility",
intellect = "bonus_intellect",
bonus_int = "bonus_intellect",
int_bonus = "bonus_intellect",
str_pct = "strength_pct",
strength_percent = "strength_pct",
bonus_strength_pct = "strength_pct",
agi_pct = "agility_pct",
agility_percent = "agility_pct",
bonus_agility_pct = "agility_pct",
int_pct = "intellect_pct",
intellect_percent = "intellect_pct",
bonus_intellect_pct = "intellect_pct"
}
return alias[key]
end
function ArsenalManagerClass.prototype.refreshHeroArsenalStats(self, playerId, _heroName)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
local assigned = player.GetAssignedHero and player:GetAssignedHero()
local hero = assigned and IsValidEntity(assigned) and assigned:IsRealHero() and assigned or PlayerResource:GetSelectedHeroEntity(playerId)
if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then
return
end
local heroMemo = hero
heroMemo.__arsenalHeroStatTotalsMemoTime = nil
heroMemo.__arsenalHeroStatTotalsMemo = nil
if not hero:HasModifier(____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME) then
hero:AddNewModifier(
hero,
getModifierSourceAbility(nil, hero),
____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME,
{}
)
end
local mod = hero:FindModifierByName(____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME)
if mod then
mod:ForceRefresh()
end
hero:CalculateStatBonus(true)
end
function ArsenalManagerClass.prototype.scheduleArsenalStatRefresh(self, playerId)
if not IsServer() then
return
end
local function run()
self:refreshHeroArsenalStats(playerId, "")
return nil
end
Timers:CreateTimer(0, run)
Timers:CreateTimer(0.15, run)
end
function ArsenalManagerClass.prototype.isArsenalEditPhase(self)
return GameRules:State_Get() == DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP
end
function ArsenalManagerClass.prototype.sendError(self, playerId, token)
local player = PlayerResource:GetPlayer(playerId)
if not player then
return
end
CustomGameEventManager:Send_ServerToPlayer(player, "create_error_message", {message = token})
end
function ArsenalManagerClass.prototype.ensureInstances(self, playerId)
if not self.instances[playerId] then
self.instances[playerId] = {}
end
return self.instances[playerId]
end
function ArsenalManagerClass.prototype.getInstance(self, playerId, instanceId)
local bag = self.instances[playerId]
if not bag then
return nil
end
local direct = bag[instanceId]
if direct ~= nil then
return direct
end
for key in pairs(bag) do
do
local row = bag[key]
if not row then
goto __continue50
end
if row.instanceId == instanceId or row.instance_id == instanceId then
return row
end
end
::__continue50::
end
return nil
end
function ArsenalManagerClass.prototype.countStatLines(self, stats)
if not stats or type(stats) ~= "table" then
return 0
end
local n = 0
local row = stats
for k in pairs(row) do
local idx = tonumber(k)
if idx ~= nil and idx > 0 and row[k] ~= nil then
n = n + 1
end
end
return n
end
function ArsenalManagerClass.prototype.getInstanceByCatalogItem(self, playerId, itemName)
local bag = self.instances[playerId]
if not bag then
return nil
end
for id in pairs(bag) do
local inst = bag[id]
if inst ~= nil and inst.itemName == itemName then
return inst
end
end
return nil
end
function ArsenalManagerClass.prototype.nextSerialForPlayer(self, playerId)
local maxS = 0
local bag = self.instances[playerId]
if bag ~= nil then
for id in pairs(bag) do
local ____opt_11 = bag[id]
local s = ____opt_11 and ____opt_11.serial or 0
if s > maxS then
maxS = s
end
end
end
return maxS + 1
end
function ArsenalManagerClass.prototype.ensureInstanceIdentityMeta(self, _playerId, _inst)
return false
end
function ArsenalManagerClass.prototype.resolveInventoryOwnerDisplayName(self, playerId)
local pid = playerId
if not PlayerResource:IsValidPlayerID(pid) then
return "Unknown"
end
local nm = PlayerResource:GetPlayerName(pid)
local s = __TS__StringTrim(tostring(nm or ""))
return #s > 0 and s or "Unknown"
end
function ArsenalManagerClass.prototype.createNewInstance(self, playerId, itemName, rollQuality, rollContext)
local def = ARSENAL_ITEMS_MAP[itemName]
if not def then
return nil
end
local quality = rollQuality or def.quality
local bag = self:ensureInstances(playerId)
if #__TS__ObjectKeys(bag) >= ARSENAL_INVENTORY_MAX_INSTANCES then
print((((LOG_PREFIX .. " createNewInstance: inventory cap ") .. tostring(ARSENAL_INVENTORY_MAX_INSTANCES)) .. " player=") .. tostring(playerId))
return nil
end
local instanceId = "ars_" .. DoUniqueString("i")
local serial = self:nextSerialForPlayer(playerId)
local created = createArsenalItemInstance(
nil,
instanceId,
serial,
itemName,
quality,
rollContext
)
self:setRollVersion(created, ARSENAL_ROLL_VERSION)
created.ownerName = self:resolveInventoryOwnerDisplayName(playerId)
bag[instanceId] = created
return instanceId
end
function ArsenalManagerClass.prototype.repairBrokenInstance(self, playerId, instanceId)
local ____opt_13 = self.instances[playerId]
local inst = ____opt_13 and ____opt_13[instanceId]
if not inst then
return false
end
if self:countStatLines(inst.stats) >= 7 then
return false
end
local def = ARSENAL_ITEMS_MAP[inst.itemName]
if not def then
return false
end
local oldFlags = inst
local wasPinned = not not oldFlags.pinned
local wasFavorite = not not oldFlags.favorite
local rerolled = createArsenalItemInstance(
nil,
instanceId,
inst.serial,
inst.itemName,
inst.quality or def.quality
)
rerolled.upgradeLevel = math.max(
0,
math.min(
5,
math.floor(inst.upgradeLevel or 0)
)
)
if wasPinned then
rerolled.pinned = true
end
if wasFavorite then
rerolled.favorite = true
end
self:setRollVersion(rerolled, ARSENAL_ROLL_VERSION)
local oldMeta = inst
rerolled.globalSerial = oldMeta.globalSerial
rerolled.ownerName = oldMeta.ownerName
self:ensureInstances(playerId)[instanceId] = rerolled
return true
end
function ArsenalManagerClass.prototype.normalizePinned(self, inst)
local raw = inst.pinned
inst.pinned = raw == true or raw == 1 or raw == "1"
end
function ArsenalManagerClass.prototype.normalizeFavorite(self, inst)
local raw = inst.favorite
inst.favorite = raw == true or raw == 1 or raw == "1"
end
function ArsenalManagerClass.prototype.getRollVersion(self, inst)
local raw = inst.rollVersion
if type(raw) ~= "number" or not __TS__NumberIsFinite(raw) then
return 0
end
return math.floor(raw)
end
function ArsenalManagerClass.prototype.setRollVersion(self, inst, version)
inst.rollVersion = version
end
function ArsenalManagerClass.prototype.rerollInstanceForCurrentVersion(self, playerId, instanceId)
local ____opt_15 = self.instances[playerId]
local inst = ____opt_15 and ____opt_15[instanceId]
if not inst then
return false
end
if self:getRollVersion(inst) >= ARSENAL_ROLL_VERSION then
return false
end
local def = ARSENAL_ITEMS_MAP[inst.itemName]
if not def then
return false
end
local oldFlags = inst
local wasPinned = not not oldFlags.pinned
local wasFavorite = not not oldFlags.favorite
local rerolled = createArsenalItemInstance(
nil,
instanceId,
inst.serial,
inst.itemName,
inst.quality or def.quality
)
rerolled.upgradeLevel = math.max(
0,
math.min(
5,
math.floor(inst.upgradeLevel or 0)
)
)
if wasPinned then
rerolled.pinned = true
end
if wasFavorite then
rerolled.favorite = true
end
self:setRollVersion(rerolled, ARSENAL_ROLL_VERSION)
local oldMeta = inst
rerolled.globalSerial = oldMeta.globalSerial
rerolled.ownerName = oldMeta.ownerName
self:ensureInstances(playerId)[instanceId] = rerolled
return true
end
function ArsenalManagerClass.prototype.normalizeInventoryFlags(self, playerId)
local bag = self.instances[playerId]
if not bag then
return
end
for id in pairs(bag) do
local inst = bag[id]
if inst ~= nil then
self:normalizePinned(inst)
self:normalizeFavorite(inst)
if not not inst.favorite then
inst.pinned = true
end
end
end
end
function ArsenalManagerClass.prototype.debugPrintInventoryOwnerMeta(self, playerId)
local bag = self.instances[playerId]
if not bag then
print(((LOG_PREFIX .. " owner-meta player=") .. tostring(playerId)) .. ": inventory empty")
return
end
for instanceId in pairs(bag) do
local inst = bag[instanceId]
local ownerName = tostring(inst and inst.ownerName or "")
local globalSerial = inst and inst.globalSerial or 0
local markMissing = (not ownerName or #ownerName <= 0 or ownerName == "Unknown") and "MISSING_OWNER" or "OK"
print((((((((((((LOG_PREFIX .. " owner-meta player=") .. tostring(playerId)) .. " instance=") .. instanceId) .. " item=") .. (inst and inst.itemName or "?")) .. " globalSerial=") .. tostring(globalSerial)) .. " ownerName=\"") .. ownerName) .. "\" status=") .. markMissing)
end
end
function ArsenalManagerClass.prototype.getArsenalUpgradeCost(self, quality, currentLevelBeforeUpgrade)
local lv = math.max(
0,
math.min(
4,
math.floor(currentLevelBeforeUpgrade)
)
)
local commonCurve = {
100,
200,
400,
800,
1600
}
local rarityMult = {
common = 1,
rare = 2,
epic = 4,
legendary = 8,
mythic = 16
}
local base = commonCurve[lv + 1] or commonCurve[1]
local mult = rarityMult[quality] or 1
return base * mult
end
function ArsenalManagerClass.prototype.getDisassembleShardReward(self, quality, upgradeLevel)
local base = {
common = 15,
rare = 35,
epic = 70,
legendary = 120,
mythic = 200
}
local b = base[quality] or 15
local lv = math.max(
0,
math.min(
5,
math.floor(upgradeLevel)
)
)
return b + lv * 20
end
function ArsenalManagerClass.prototype.isInstanceEquippedInAnyLoadout(self, playerId, instanceId)
local all = self.loadouts[playerId]
if not all then
return false
end
for heroName in pairs(all) do
do
local hl = all[heroName]
if not hl then
goto __continue106
end
for ____, slot in ipairs(ARSENAL_SLOTS) do
if hl[slot] == instanceId then
return true
end
end
end
::__continue106::
end
return false
end
function ArsenalManagerClass.prototype.removeInstanceFromAllLoadouts(self, playerId, instanceId)
local all = self.loadouts[playerId]
if not all then
return
end
for heroName in pairs(all) do
do
local hl = all[heroName]
if not hl then
goto __continue114
end
for ____, slot in ipairs(ARSENAL_SLOTS) do
if hl[slot] == instanceId then
__TS__Delete(hl, slot)
end
end
end
::__continue114::
end
end
function ArsenalManagerClass.prototype.removeInstanceFromHeroLoadoutExceptSlot(self, playerId, heroName, keepSlot, instanceId)
local ____opt_23 = self.loadouts[playerId]
local hl = ____opt_23 and ____opt_23[heroName]
if not hl then
return
end
for ____, slot in ipairs(ARSENAL_SLOTS) do
do
if slot == keepSlot then
goto __continue122
end
if hl[slot] == instanceId then
__TS__Delete(hl, slot)
end
end
::__continue122::
end
end
function ArsenalManagerClass.prototype.migrateLoadoutsFromLegacyItemNames(self, playerId)
local all = self.loadouts[playerId]
if not all then
return
end
for heroName in pairs(all) do
do
local hl = all[heroName]
if not hl then
goto __continue128
end
for ____, slot in ipairs(ARSENAL_SLOTS) do
do
local v = hl[slot]
if not v then
goto __continue130
end
if self:getInstance(playerId, v) then
goto __continue130
end
if ARSENAL_ITEMS_MAP[v] then
local inst = self:getInstanceByCatalogItem(playerId, v)
if inst then
hl[slot] = inst.instanceId
end
end
end
::__continue130::
end
end
::__continue128::
end
end
function ArsenalManagerClass.prototype.ingestInventoryPayload(self, playerId, payload)
if not payload or type(payload) ~= "table" then
self.instances[playerId] = {}
return
end
if payload.instances and type(payload.instances) == "table" then
local raw = payload.instances
local deduped = {}
for rawKey in pairs(raw) do
do
local row = raw[rawKey]
if not row or type(row) ~= "table" then
goto __continue140
end
local ____tostring_27 = tostring
local ____row_instanceId_25 = row.instanceId
if ____row_instanceId_25 == nil then
____row_instanceId_25 = row.instance_id
end
local ____row_instanceId_25_26 = ____row_instanceId_25
if ____row_instanceId_25_26 == nil then
____row_instanceId_25_26 = rawKey
end
local canonicalId = ____tostring_27(____row_instanceId_25_26)
if not canonicalId or #canonicalId <= 0 then
goto __continue140
end
local normalizedRow = row
normalizedRow.instanceId = canonicalId
deduped[canonicalId] = normalizedRow
end
::__continue140::
end
self:trimInstancesBagToMax(deduped, ARSENAL_INVENTORY_MAX_INSTANCES)
self.instances[playerId] = deduped
return
end
self.instances[playerId] = {}
end
function ArsenalManagerClass.prototype.countCatalogInstances(self, playerId)
local n = 0
local bag = self.instances[playerId]
if not bag then
return 0
end
for id in pairs(bag) do
local it = bag[id]
if it ~= nil and ARSENAL_ITEMS_MAP[it.itemName] ~= nil then
n = n + 1
end
end
return n
end
function ArsenalManagerClass.prototype.trimInstancesBagToMax(self, bag, max)
local keys = __TS__ObjectKeys(bag)
if #keys <= max then
return
end
__TS__ArraySort(
keys,
function(____, a, b)
local ____opt_28 = bag[a]
local sa = ____opt_28 and ____opt_28.serial or 0
local ____opt_30 = bag[b]
local sb = ____opt_30 and ____opt_30.serial or 0
return sb - sa
end
)
do
local i = max
while i < #keys do
local k = keys[i + 1]
if k ~= nil then
__TS__Delete(bag, k)
end
i = i + 1
end
end
print(((((LOG_PREFIX .. " trimInstancesBagToMax: removed ") .. tostring(#keys - max)) .. " oldest instances (cap=") .. tostring(max)) .. ")")
end
function ArsenalManagerClass.prototype.handleEquip(self, playerId, heroName, slot, instanceId)
if not self:isArsenalEditPhase() then
self:sendError(playerId, "#arsenal_locked_in_game")
return
end
if not heroName or not slot or not instanceId then
print((((((((LOG_PREFIX .. " equip: bad args player=") .. tostring(playerId)) .. " hero=") .. heroName) .. " slot=") .. slot) .. " id=") .. instanceId)
return
end
local inst = self:getInstance(playerId, instanceId)
if not inst then
self:sendError(playerId, "#arsenal_item_not_owned")
return
end
local def = ARSENAL_ITEMS_MAP[inst.itemName]
if not def or def.slot ~= slot then
print((((LOG_PREFIX .. " equip: slot mismatch item=") .. inst.itemName) .. " slot=") .. slot)
return
end
self:removeInstanceFromHeroLoadoutExceptSlot(playerId, heroName, slot, instanceId)
if not self.loadouts[playerId] then
self.loadouts[playerId] = {}
end
if not self.loadouts[playerId][heroName] then
self.loadouts[playerId][heroName] = {}
end
self.loadouts[playerId][heroName][slot] = instanceId
self:syncToClient(playerId)
self:saveLoadoutsToServer(playerId)
self:refreshHeroArsenalStats(playerId, heroName)
self:scheduleArsenalStatRefresh(playerId)
print((((((((LOG_PREFIX .. " equip player=") .. tostring(playerId)) .. " hero=") .. heroName) .. " slot=") .. slot) .. " instance=") .. instanceId)
end
function ArsenalManagerClass.prototype.handleUnequip(self, playerId, heroName, slot)
if not self:isArsenalEditPhase() then
self:sendError(playerId, "#arsenal_locked_in_game")
return
end
if not heroName or not slot then
return
end
local ____opt_32 = self.loadouts[playerId]
local heroLoadout = ____opt_32 and ____opt_32[heroName]
if not heroLoadout or heroLoadout[slot] == nil then
return
end
__TS__Delete(heroLoadout, slot)
self:syncToClient(playerId)
self:saveLoadoutsToServer(playerId)
self:refreshHeroArsenalStats(playerId, heroName)
self:scheduleArsenalStatRefresh(playerId)
print((((((LOG_PREFIX .. " unequip player=") .. tostring(playerId)) .. " hero=") .. heroName) .. " slot=") .. slot)
end
function ArsenalManagerClass.prototype.handleUpgradeItem(self, playerId, instanceId)
if not instanceId then
return
end
if not self:isArsenalEditPhase() then
self:sendError(playerId, "#arsenal_locked_in_game")
return
end
local inst = self:getInstance(playerId, instanceId)
if not inst then
self:sendError(playerId, "#arsenal_item_not_owned")
return
end
if inst.upgradeLevel >= 5 then
return
end
local cost = self:getArsenalUpgradeCost(inst.quality, inst.upgradeLevel)
local store = StoreManager:getInstance()
if cost > 0 then
if store:getDustCurrency(playerId) < cost then
self:sendError(playerId, "Недостаточно пыли для улучшения")
return
end
if not store:removeDustCurrency(playerId, cost) then
self:sendError(playerId, "Недостаточно пыли для улучшения")
return
end
store:saveCurrencyToServer(playerId)
end
inst.upgradeLevel = inst.upgradeLevel + 1
self:syncToClient(playerId)
self:saveInventoryToServer(playerId)
local needStats = false
for heroName in pairs(self.loadouts[playerId] or ({})) do
do
local loadout = self.loadouts[playerId][heroName]
if not loadout then
goto __continue173
end
for ____, slot in ipairs(ARSENAL_SLOTS) do
if loadout[slot] == instanceId then
needStats = true
break
end
end
if needStats then
break
end
end
::__continue173::
end
if needStats then
self:refreshHeroArsenalStats(playerId, "")
self:scheduleArsenalStatRefresh(playerId)
end
print((((((((LOG_PREFIX .. " upgrade player=") .. tostring(playerId)) .. " instance=") .. instanceId) .. " level=") .. tostring(inst.upgradeLevel)) .. " cost=") .. tostring(cost))
end
function ArsenalManagerClass.prototype.handleTogglePin(self, playerId, instanceId)
if not instanceId then
return
end
local inst = self:getInstance(playerId, instanceId)
if not inst then
self:sendError(playerId, "#arsenal_item_not_owned")
return
end
local cur = not not inst.pinned
inst.pinned = not cur
self:syncToClient(playerId)
self:saveInventoryToServer(playerId)
print((((((LOG_PREFIX .. " pin=") .. tostring(not cur)) .. " player=") .. tostring(playerId)) .. " instance=") .. instanceId)
end
function ArsenalManagerClass.prototype.handleToggleFavorite(self, playerId, instanceId)
if not instanceId then
return
end
local inst = self:getInstance(playerId, instanceId)
if not inst then
self:sendError(playerId, "#arsenal_item_not_owned")
return
end
local cur = not not inst.favorite
local next = not cur
inst.favorite = next
if next then
inst.pinned = true
end
self:syncToClient(playerId)
self:saveInventoryToServer(playerId)
print((((((((LOG_PREFIX .. " favorite=") .. tostring(next)) .. " pin=") .. tostring(not not inst.pinned)) .. " player=") .. tostring(playerId)) .. " instance=") .. instanceId)
end
function ArsenalManagerClass.prototype.handleDisassembleItem(self, playerId, instanceId)
if not instanceId then
return
end
if not self:isArsenalEditPhase() then
self:sendError(playerId, "#arsenal_locked_in_game")
return
end
local inst = self:getInstance(playerId, instanceId)
if not inst then
self:sendError(playerId, "#arsenal_item_not_owned")
return
end
if not not inst.pinned then
self:sendError(playerId, "#arsenal_disassemble_pinned")
return
end
if self:isInstanceEquippedInAnyLoadout(playerId, instanceId) then
self:sendError(playerId, "#arsenal_disassemble_equipped")
return
end
local shards = self:getDisassembleShardReward(inst.quality, inst.upgradeLevel or 0)
__TS__Delete(
self:ensureInstances(playerId),
instanceId
)
self:removeInstanceFromAllLoadouts(playerId, instanceId)
StoreManager:getInstance():addDustCurrency(playerId, shards)
self:syncToClient(playerId)
self:saveInventoryToServer(playerId)
self:saveLoadoutsToServer(playerId)
self:refreshHeroArsenalStats(playerId, "")
self:scheduleArsenalStatRefresh(playerId)
print((((((LOG_PREFIX .. " disassemble player=") .. tostring(playerId)) .. " instance=") .. instanceId) .. " shards=") .. tostring(shards))
end
function ArsenalManagerClass.prototype.applyLoadout(self, player, hero)
if not player or not hero then
return
end
local playerId = player:GetPlayerID()
if playerId < 0 then
return
end
local heroName = hero:GetUnitName()
local ____opt_34 = self.loadouts[playerId]
local heroLoadout = ____opt_34 and ____opt_34[heroName]
if not heroLoadout then
return
end
for ____, slot in ipairs(ARSENAL_SLOTS) do
do
local instanceId = heroLoadout[slot]
if not instanceId then
goto __continue198
end
if not self:getInstance(playerId, instanceId) then
goto __continue198
end
end
::__continue198::
end
if not hero:HasModifier(____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME) then
hero:AddNewModifier(
hero,
getModifierSourceAbility(nil, hero),
____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME,
{}
)
end
local mod = hero:FindModifierByName(____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME)
if mod then
mod:ForceRefresh()
end
self:scheduleArsenalStatRefresh(playerId)
if not self.appliedFor[playerId] then
self.appliedFor[playerId] = __TS__New(Set)
end
self.appliedFor[playerId]:add(heroName)
print((((LOG_PREFIX .. " applyLoadout player=") .. tostring(playerId)) .. " hero=") .. heroName)
end
function ArsenalManagerClass.prototype.clearLoadout(self, player, hero)
if not player or not hero then
return
end
local playerId = player:GetPlayerID()
if playerId < 0 then
return
end
local heroName = hero:GetUnitName()
local ____opt_36 = self.loadouts[playerId]
local heroLoadout = ____opt_36 and ____opt_36[heroName]
if not heroLoadout then
return
end
hero:RemoveModifierByName(____exports.ArsenalManagerClass.DYNAMIC_MODIFIER_NAME)
local ____opt_38 = self.appliedFor[playerId]
if ____opt_38 ~= nil then
____opt_38:delete(heroName)
end
if self.lastPrintedTotals[playerId] then
self.lastPrintedTotals[playerId][heroName] = ""
end
end
function ArsenalManagerClass.prototype.tryGrantStarterPackIfEmpty(self, playerId)
local changed = false
for ____, def in ipairs(ARSENAL_ITEMS) do
if not self:getInstanceByCatalogItem(playerId, def.itemName) then
changed = self:createNewInstance(playerId, def.itemName) ~= nil or changed
end
end
local bag = self.instances[playerId]
if bag ~= nil then
for instanceId in pairs(bag) do
changed = self:repairBrokenInstance(playerId, instanceId) or changed
end
end
if not changed then
print(((LOG_PREFIX .. " starter/test grant skipped: player=") .. tostring(playerId)) .. ", inventory already valid")
return
end
self:syncToClient(playerId)
self:saveInventoryToServer(playerId)
print((LOG_PREFIX .. " starter/test grant applied player=") .. tostring(playerId))
end
function ArsenalManagerClass.prototype.regenerateAllCatalogItemsForTesting(self, playerId)
self.instances[playerId] = {}
self.loadouts[playerId] = {}
self.appliedFor[playerId] = __TS__New(Set)
for ____, def in ipairs(ARSENAL_ITEMS) do
self:createNewInstance(playerId, def.itemName)
end
self:syncToClient(playerId)
self:saveInventoryToServer(playerId)
self:saveLoadoutsToServer(playerId)
print((LOG_PREFIX .. " regenerated full random arsenal for player=") .. tostring(playerId))
end
function ArsenalManagerClass.prototype.grantArsenalItem(self, playerId, itemName, count)
if count == nil then
count = 1
end
if not ARSENAL_ITEMS_MAP[itemName] then
print((LOG_PREFIX .. " grantArsenalItem: unknown item ") .. itemName)
return false
end
local n = math.max(
1,
math.floor(count)
)
do
local i = 0
while i < n do
self:createNewInstance(playerId, itemName, nil)
i = i + 1
end
end
self:syncToClient(playerId)
self:saveInventoryToServer(playerId)
print((((((LOG_PREFIX .. " grant player=") .. tostring(playerId)) .. " item=") .. itemName) .. " x") .. tostring(n))
return true
end
function ArsenalManagerClass.prototype.grantArsenalItemDetailed(self, playerId, itemName, count, rollQuality, rollContext, options)
if count == nil then
count = 1
end
if not ARSENAL_ITEMS_MAP[itemName] then
print((LOG_PREFIX .. " grantArsenalItemDetailed: unknown item ") .. itemName)
return {}
end
local n = math.max(
1,
math.floor(count)
)
local created = {}
do
local i = 0
while i < n do
do
local instanceId = self:createNewInstance(playerId, itemName, rollQuality, rollContext)
if not instanceId then
goto __continue226
end
local inst = self:getInstance(playerId, instanceId)
local defQ = ARSENAL_ITEMS_MAP[itemName].quality
created[#created + 1] = {instanceId = instanceId, itemName = itemName, quality = inst and inst.quality or rollQuality or defQ}
end
::__continue226::
i = i + 1
end
end
if not (options and options.deferPersistence) then
self:syncToClient(playerId)
self:saveInventoryToServer(playerId)
end
print((((((LOG_PREFIX .. " grant detailed player=") .. tostring(playerId)) .. " item=") .. itemName) .. " x=") .. tostring(#created))
return created
end
function ArsenalManagerClass.prototype.flushArsenalInventoryToClientAndApi(self, playerId)
local pid = playerId
self.inventorySaveGeneration[pid] = (self.inventorySaveGeneration[pid] or 0) + 1
self:syncToClient(pid)
self:sendInventoryPutRequest(pid)
end
function ArsenalManagerClass.prototype.getOwnedCount(self, playerId, itemName)
local bag = self.instances[playerId]
if not bag then
return 0
end
local n = 0
for id in pairs(bag) do
local it = bag[id]
if it ~= nil and it.itemName == itemName then
n = n + 1
end
end
return n
end
function ArsenalManagerClass.prototype.getHeroLoadout(self, playerId, heroName)
local ____opt_44 = self.loadouts[playerId]
return ____opt_44 and ____opt_44[heroName] or ({})
end
function ArsenalManagerClass.prototype.getInventoryInstance(self, playerId, instanceId)
return self:getInstance(playerId, instanceId)
end
function ArsenalManagerClass.prototype.isInstanceTradeLocked(self, playerId, instanceId)
local inst = self:getInstance(playerId, instanceId)
if not inst then
return true
end
local rawPinned = inst.pinned
local rawFavorite = inst.favorite
local pinned = rawPinned == true or rawPinned == 1 or rawPinned == "1"
local favorite = rawFavorite == true or rawFavorite == 1 or rawFavorite == "1"
return pinned or favorite
end
function ArsenalManagerClass.prototype.removeInstanceFromAllLoadoutsForMarket(self, playerId, instanceId)
self:removeInstanceFromAllLoadouts(playerId, instanceId)
self:syncToClient(playerId)
self:saveLoadoutsToServer(playerId)
self:refreshHeroArsenalStats(playerId, "")
self:scheduleArsenalStatRefresh(playerId)
end
function ArsenalManagerClass.prototype.buildInventoryPayloadForMarket(self, playerId)
local source = self.instances[playerId] or ({})
local normalized = {}
for key in pairs(source) do
do
local inst = source[key]
if not inst then
goto __continue241
end
normalized[key] = __TS__ObjectAssign({}, inst, {
instanceId = inst.instanceId,
instance_id = inst.instanceId,
itemName = inst.itemName,
item_name = inst.itemName,
upgradeLevel = inst.upgradeLevel,
upgrade_level = inst.upgradeLevel,
globalSerial = inst.globalSerial,
global_serial = inst.globalSerial,
ownerName = inst.ownerName,
owner_name = inst.ownerName
})
end
::__continue241::
end
return {instances = normalized}
end
function ArsenalManagerClass.prototype.removeInventoryInstanceForMarket(self, playerId, instanceId)
local bag = self:ensureInstances(playerId)
local inst = bag[instanceId]
if not inst then
return nil
end
__TS__Delete(bag, instanceId)
self:removeInstanceFromAllLoadouts(playerId, instanceId)
self:syncToClient(playerId)
self:saveInventoryToServer(playerId)
self:saveLoadoutsToServer(playerId)
self:refreshHeroArsenalStats(playerId, "")
self:scheduleArsenalStatRefresh(playerId)
return inst
end
function ArsenalManagerClass.prototype.upsertInventoryInstanceFromMarket(self, playerId, instance)
if not instance or not instance.instanceId then
return
end
self:ensureInstances(playerId)[instance.instanceId] = instance
self:syncToClient(playerId)
self:saveInventoryToServer(playerId)
self:refreshHeroArsenalStats(playerId, "")
self:scheduleArsenalStatRefresh(playerId)
end
function ArsenalManagerClass.prototype.getHeroStatTotals(self, hero)
local empty = {
battle_level = 0,
bonus_damage = 0,
attack_speed = 0,
bonus_armor = 0,
max_health = 0,
max_mana = 0,
health_regen = 0,
mana_regen = 0,
move_speed = 0,
magic_resist = 0,
spell_amp = 0,
all_stats = 0,
all_stats_pct = 0,
bonus_strength = 0,
bonus_agility = 0,
bonus_intellect = 0,
strength_pct = 0,
agility_pct = 0,
intellect_pct = 0,
luck = 0,
outgoing_damage_pct = 0,
incoming_damage_reduction_pct = 0,
attack_speed_pct = 0,
move_speed_pct = 0,
base_damage_pct = 0,
crit_mult = 0
}
if not hero then
return empty
end
local playerId = hero.GetPlayerID ~= nil and hero:GetPlayerID() or hero:GetPlayerOwnerID()
if playerId < 0 then
return empty
end
local gt = GameRules:GetGameTime()
local heroMemo = hero
local memo = heroMemo.__arsenalHeroStatTotalsMemo
if memo ~= nil and heroMemo.__arsenalHeroStatTotalsMemoTime == gt then
return memo
end
local heroName = hero:GetUnitName()
local ____opt_46 = self.loadouts[playerId]
local heroLoadout = ____opt_46 and ____opt_46[heroName]
if not heroLoadout then
heroMemo.__arsenalHeroStatTotalsMemoTime = gt
heroMemo.__arsenalHeroStatTotalsMemo = empty
return empty
end
for ____, slot in ipairs(ARSENAL_SLOTS) do
do
local instanceId = heroLoadout[slot]
if not instanceId then
goto __continue253
end
local inst = self:getInstance(playerId, instanceId)
if not inst then
goto __continue253
end
local lines = getEffectiveItemStats(nil, inst)
for ____, line in ipairs(lines) do
do
local normalizedKey = self:normalizeArsenalStatKey(tostring(line.key))
if not normalizedKey then
print((((((LOG_PREFIX .. " unknown stat key dropped: \"") .. tostring(line.key)) .. "\" item=") .. inst.itemName) .. " instance=") .. instanceId)
goto __continue256
end
empty[normalizedKey] = (empty[normalizedKey] or 0) + line.value
end
::__continue256::
end
end
::__continue253::
end
if ARSENAL_VERBOSE_TOTALS_LOG then
local totalsHash = json.encode(empty)
if not self.lastPrintedTotals[playerId] then
self.lastPrintedTotals[playerId] = {}
end
local prevHash = self.lastPrintedTotals[playerId][heroName]
if prevHash ~= totalsHash then
self.lastPrintedTotals[playerId][heroName] = totalsHash
print((((((((((LOG_PREFIX .. " totals player=") .. tostring(playerId)) .. " hero=") .. heroName) .. " ") .. ((((((("dmg=" .. tostring(empty.bonus_damage)) .. " as=") .. tostring(empty.attack_speed)) .. " aspct=") .. tostring(empty.attack_speed_pct)) .. " armor=") .. tostring(empty.bonus_armor)) .. " ") .. ((("hp=" .. tostring(empty.max_health)) .. " mp=") .. tostring(empty.max_mana)) .. " ") .. ((((((((("ms=" .. tostring(empty.move_speed)) .. " mspct=") .. tostring(empty.move_speed_pct)) .. " mr=") .. tostring(empty.magic_resist)) .. " amp=") .. tostring(empty.spell_amp)) .. " all=") .. tostring(empty.all_stats)) .. " ") .. ((((((("all%=" .. tostring(empty.all_stats_pct)) .. " str%=") .. tostring(empty.strength_pct)) .. " agi%=") .. tostring(empty.agility_pct)) .. " int%=") .. tostring(empty.intellect_pct)) .. " ") .. (((((((("luck=" .. tostring(empty.luck)) .. " out%=") .. tostring(empty.outgoing_damage_pct)) .. " in_red%=") .. tostring(empty.incoming_damage_reduction_pct)) .. " bdmg%=") .. tostring(empty.base_damage_pct)) .. " crit%=") .. tostring(empty.crit_mult))
end
end
heroMemo.__arsenalHeroStatTotalsMemoTime = gt
heroMemo.__arsenalHeroStatTotalsMemo = empty
return empty
end
function ArsenalManagerClass.prototype.syncToClient(self, playerId)
CustomNetTables:SetTableValue(
"arsenal_loadouts",
tostring(playerId),
{loadouts = self.loadouts[playerId] or ({}), selectedHero = self.selectedHero[playerId] or ""}
)
CustomNetTables:SetTableValue(
"arsenal_inventory",
tostring(playerId),
{instances = self.instances[playerId] or ({})}
)
end
function ArsenalManagerClass.prototype.syncLoadoutsToClientOnly(self, playerId)
CustomNetTables:SetTableValue(
"arsenal_loadouts",
tostring(playerId),
{loadouts = self.loadouts[playerId] or ({}), selectedHero = self.selectedHero[playerId] or ""}
)
end
function ArsenalManagerClass.prototype.saveLoadoutsToServer(self, playerId)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
return
end
local data = self.loadouts[playerId] or ({})
local request = CreateHTTPRequest(
"PUT",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_loadouts"
)
setApiHeaders(nil, request)
request:SetHTTPRequestRawPostBody(
"application/json",
json.encode({arsenal_loadouts = data})
)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
print(((LOG_PREFIX .. " arsenal_loadouts PUT ok (player=") .. tostring(playerId)) .. ")")
else
print((LOG_PREFIX .. " arsenal_loadouts PUT fail: StatusCode=") .. tostring(result.StatusCode))
end
end)
end
function ArsenalManagerClass.prototype.saveInventoryToServer(self, playerId)
local pid = playerId
local g = (self.inventorySaveGeneration[pid] or 0) + 1
self.inventorySaveGeneration[pid] = g
Timers:CreateTimer(
ARSENAL_INVENTORY_PUT_DEBOUNCE,
function()
if self.inventorySaveGeneration[pid] ~= g then
return nil
end
self:sendInventoryPutRequest(pid)
return nil
end
)
end
function ArsenalManagerClass.prototype.sendInventoryPutRequest(self, playerId)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
return
end
local data = {instances = self.instances[playerId] or ({})}
local request = CreateHTTPRequest(
"PUT",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_inventory"
)
setApiHeaders(nil, request)
request:SetHTTPRequestRawPostBody(
"application/json",
json.encode({arsenal_inventory = data})
)
request:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
print(((LOG_PREFIX .. " arsenal_inventory PUT ok (player=") .. tostring(playerId)) .. ")")
else
print((LOG_PREFIX .. " arsenal_inventory PUT fail: StatusCode=") .. tostring(result.StatusCode))
end
end)
end
function ArsenalManagerClass.prototype.loadFromServer(self, playerId)
local steamId = PlayerResource:GetSteamAccountID(playerId)
if not steamId then
return
end
local loadoutsReq = CreateHTTPRequest(
"GET",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_loadouts"
)
setApiHeaders(nil, loadoutsReq)
loadoutsReq:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
do
local function ____catch(e)
print((LOG_PREFIX .. " loadouts decode err: ") .. tostring(e))
end
local ____try, ____hasReturned = pcall(function()
local decoded = {json.decode(result.Body)}
local resp = nil
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
resp = decoded[1]
elseif decoded and type(decoded) == "table" then
resp = decoded
end
if resp and type(resp) == "table" and resp.arsenal_loadouts ~= nil then
self.loadouts[playerId] = resp.arsenal_loadouts or ({})
else
self.loadouts[playerId] = self.loadouts[playerId] or ({})
end
self:syncLoadoutsToClientOnly(playerId)
local selected = self.selectedHero[playerId]
if selected and #selected > 0 then
self:refreshHeroArsenalStats(playerId, selected)
self:scheduleArsenalStatRefresh(playerId)
end
print((LOG_PREFIX .. " loaded arsenal_loadouts for player=") .. tostring(playerId))
end)
if not ____try then
____catch(____hasReturned)
end
end
else
print((LOG_PREFIX .. " arsenal_loadouts GET fail: StatusCode=") .. tostring(result.StatusCode))
end
end)
local invReq = CreateHTTPRequest(
"GET",
((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_inventory"
)
setApiHeaders(nil, invReq)
invReq:Send(function(result)
if result.StatusCode >= 200 and result.StatusCode < 300 then
do
local function ____catch(e)
print((LOG_PREFIX .. " inventory decode err: ") .. tostring(e))
end
local ____try, ____hasReturned = pcall(function()
local decoded = {json.decode(result.Body)}
local resp = nil
if __TS__ArrayIsArray(decoded) and #decoded > 0 then
resp = decoded[1]
elseif decoded and type(decoded) == "table" then
resp = decoded
end
if resp and type(resp) == "table" and resp.arsenal_inventory ~= nil then
self:ingestInventoryPayload(playerId, resp.arsenal_inventory)
else
self.instances[playerId] = self.instances[playerId] or ({})
end
self:normalizeInventoryFlags(playerId)
self:debugPrintInventoryOwnerMeta(playerId)
local repaired = false
local rerolledByVersion = false
local bag = self.instances[playerId]
if bag ~= nil then
for instanceId in pairs(bag) do
repaired = self:repairBrokenInstance(playerId, instanceId) or repaired
rerolledByVersion = self:rerollInstanceForCurrentVersion(playerId, instanceId) or rerolledByVersion
end
end
if repaired or rerolledByVersion then
self.inventorySaveGeneration[playerId] = (self.inventorySaveGeneration[playerId] or 0) + 1
self:sendInventoryPutRequest(playerId)
end
self:migrateLoadoutsFromLegacyItemNames(playerId)
self:syncToClient(playerId)
local selected = self.selectedHero[playerId]
if selected and #selected > 0 then
self:refreshHeroArsenalStats(playerId, selected)
self:scheduleArsenalStatRefresh(playerId)
end
print((((((LOG_PREFIX .. " loaded arsenal_inventory for player=") .. tostring(playerId)) .. " repaired=") .. tostring(repaired)) .. " rerolledByVersion=") .. tostring(rerolledByVersion))
end)
if not ____try then
____catch(____hasReturned)
end
end
else
print((LOG_PREFIX .. " arsenal_inventory GET fail: StatusCode=") .. tostring(result.StatusCode))
end
end)
end
ArsenalManagerClass.DYNAMIC_MODIFIER_NAME = "modifier_arsenal_dynamic_stats"
ArsenalManagerClass = __TS__Decorate(ArsenalManagerClass, ArsenalManagerClass, {reloadable}, {kind = "class", name = "ArsenalManagerClass"})
____exports.ArsenalManagerClass = ArsenalManagerClass
____exports.ArsenalManager = ____exports.ArsenalManagerClass:getInstance()
function ____exports.grantArsenalItem(self, playerId, itemName, count)
if count == nil then
count = 1
end
return ____exports.ArsenalManager:grantArsenalItem(playerId, itemName, count)
end
function ____exports.grantArsenalItemDetailed(self, playerId, itemName, count, rollQuality, rollContext, options)
if count == nil then
count = 1
end
return ____exports.ArsenalManager:grantArsenalItemDetailed(
playerId,
itemName,
count,
rollQuality,
rollContext,
options
)
end
function ____exports.flushArsenalInventoryToClientAndApi(self, playerId)
____exports.ArsenalManager:flushArsenalInventoryToClientAndApi(playerId)
end
return ____exports