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 = CreateHTTPRequestScriptVM( "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 = CreateHTTPRequestScriptVM( "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 = CreateHTTPRequestScriptVM( "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 = CreateHTTPRequestScriptVM( "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