local ____lualib = require("lualib_bundle") local __TS__Class = ____lualib.__TS__Class local Map = ____lualib.Map local __TS__New = ____lualib.__TS__New local __TS__Number = ____lualib.__TS__Number local __TS__ArrayFind = ____lualib.__TS__ArrayFind local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray local __TS__ObjectAssign = ____lualib.__TS__ObjectAssign local Set = ____lualib.Set local __TS__ObjectValues = ____lualib.__TS__ObjectValues local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter local ____exports = {} local ____difficulty_manager = require("difficulty_manager") local Difficulty = ____difficulty_manager.Difficulty local ____player_info = require("player_info") local PlayerInfo = ____player_info.PlayerInfo local ____store_manager = require("store_manager") local StoreManager = ____store_manager.StoreManager local ____modifier_equipment_stats = require("abilities.modifiers.modifier_equipment_stats") local modifier_equipment_stats = ____modifier_equipment_stats.modifier_equipment_stats local ____constants = require("equipment.constants") local EQUIPMENT_MAX_UPGRADE_STAGE = ____constants.EQUIPMENT_MAX_UPGRADE_STAGE local EQUIPMENT_STATE_VERSION = ____constants.EQUIPMENT_STATE_VERSION local createEmptyLoadout = ____constants.createEmptyLoadout local ____balance = require("equipment.balance") local getUpgradeCostByStage = ____balance.getUpgradeCostByStage local ____api = require("equipment.api") local postEquipmentDropToApi = ____api.postEquipmentDropToApi local loadEquipmentStateFromApi = ____api.loadEquipmentStateFromApi local saveEquipmentStateToApi = ____api.saveEquipmentStateToApi local ____roll = require("equipment.roll") local rollAdditionalStatForStageFive = ____roll.rollAdditionalStatForStageFive local rollPostMatchItem = ____roll.rollPostMatchItem local rollRandomUpgradeDeltaPct = ____roll.rollRandomUpgradeDeltaPct local EQUIPMENT_MODIFIER_NAME = modifier_equipment_stats.name ____exports.EquipmentManager = __TS__Class() local EquipmentManager = ____exports.EquipmentManager EquipmentManager.name = "EquipmentManager" EquipmentManager.____file_path = "scripts/vscripts/equipment/manager.lua" function EquipmentManager.prototype.____constructor(self) self.playerState = __TS__New(Map) self.playerOpLock = __TS__New(Map) if not IsServer() then return end self:registerEvents() self:registerLoadHooks() end function EquipmentManager.getInstance(self) if not ____exports.EquipmentManager.instance then ____exports.EquipmentManager.instance = __TS__New(____exports.EquipmentManager) end return ____exports.EquipmentManager.instance end function EquipmentManager.prototype.registerLoadHooks(self) ListenToGameEvent( "player_connect_full", function(event) local playerId = event.PlayerID if playerId == nil or playerId < 0 then return end self:ensureLoaded(playerId) end, nil ) end function EquipmentManager.prototype.registerEvents(self) CustomGameEventManager:RegisterListener( "equipment_request_state", function(_source, event) self:ensureLoaded(event.PlayerID, true) end ) CustomGameEventManager:RegisterListener( "equipment_set_active_loadout", function(_source, event) self:withPlayerLock( event.PlayerID, function() local state = self:getOrCreateState(event.PlayerID) local nextIndex = math.floor(__TS__Number(event.loadout_index or 1)) if nextIndex < 1 or nextIndex > 3 then self:sendActionResult(event.PlayerID, {success = false, message = "Некорректный слот пресета"}) return end state.activeLoadoutIndex = nextIndex self:afterStateChanged(event.PlayerID, state) end ) end ) CustomGameEventManager:RegisterListener( "equipment_equip_item", function(_source, event) self:withPlayerLock( event.PlayerID, function() local state = self:getOrCreateState(event.PlayerID) local instanceId = tostring(event.instance_id or "") local item = __TS__ArrayFind( state.inventory, function(____, x) return x.instanceId == instanceId end ) if not item then self:sendActionResult(event.PlayerID, {success = false, message = "Предмет не найден"}) return end local idxRaw = __TS__Number(event.loadout_index or state.activeLoadoutIndex) local idx = math.floor(idxRaw) local loadoutIndex = idx >= 1 and idx <= 3 and idx or state.activeLoadoutIndex self:removeItemFromAllLoadouts(state, instanceId) state.loadouts[loadoutIndex][item.slotType] = instanceId self:afterStateChanged(event.PlayerID, state) end ) end ) CustomGameEventManager:RegisterListener( "equipment_unequip_item", function(_source, event) self:withPlayerLock( event.PlayerID, function() local state = self:getOrCreateState(event.PlayerID) local instanceId = tostring(event.instance_id or "") local idxRaw = __TS__Number(event.loadout_index or state.activeLoadoutIndex) local idx = math.floor(idxRaw) local loadoutIndex = idx >= 1 and idx <= 3 and idx or state.activeLoadoutIndex for ____, slot in ipairs(__TS__ObjectKeys(state.loadouts[loadoutIndex])) do if state.loadouts[loadoutIndex][slot] == instanceId then state.loadouts[loadoutIndex][slot] = nil end end self:afterStateChanged(event.PlayerID, state) end ) end ) CustomGameEventManager:RegisterListener( "equipment_upgrade_item", function(_source, event) self:withPlayerLock( event.PlayerID, function() local state = self:getOrCreateState(event.PlayerID) local instanceId = tostring(event.instance_id or "") local item = __TS__ArrayFind( state.inventory, function(____, x) return x.instanceId == instanceId end ) if not item then self:sendActionResult(event.PlayerID, {success = false, message = "Предмет не найден"}) return end if item.upgradeStage >= EQUIPMENT_MAX_UPGRADE_STAGE then self:sendActionResult(event.PlayerID, {success = false, message = "Предмет уже максимального уровня"}) return end local store = StoreManager:getInstance() local cost = getUpgradeCostByStage(nil, item.rarity, item.upgradeStage) if not store:removeFreeCurrency(event.PlayerID, cost) then self:sendActionResult(event.PlayerID, {success = false, message = "Недостаточно free currency"}) return end store:saveCurrencyToServer(event.PlayerID) item.upgradeStage = item.upgradeStage + 1 if item.upgradeStage <= 4 then for ____, stat in ipairs(item.stats) do stat.valuePct = stat.valuePct + rollRandomUpgradeDeltaPct(nil) end elseif item.upgradeStage == 5 then local extra = rollAdditionalStatForStageFive(nil, item.stats, item.rarity) if extra then local ____item_stats_0 = item.stats ____item_stats_0[#____item_stats_0 + 1] = extra end end self:afterStateChanged(event.PlayerID, state) end ) end ) end function EquipmentManager.prototype.ensureLoaded(self, playerId, forcePublish) if forcePublish == nil then forcePublish = false end local hadState = self.playerState:has(playerId) local ____local = self:getOrCreateState(playerId) if not hadState or forcePublish then self:publishState(playerId, ____local) end loadEquipmentStateFromApi( nil, playerId, function(____, remote) if not remote then return end local normalized = self:normalizeState(remote) self.playerState:set(playerId, normalized) self:publishState(playerId, normalized) end ) end function EquipmentManager.prototype.rollDropForPlayer(self, playerId, difficultyKey) if not PlayerResource:IsValidPlayerID(playerId) or PlayerResource:IsFakeClient(playerId) then return nil end local state = self:getOrCreateState(playerId) local diff = difficultyKey or Difficulty.leader or "normal" local item = rollPostMatchItem(nil, playerId, diff) local ____state_inventory_1 = state.inventory ____state_inventory_1[#____state_inventory_1 + 1] = item self:afterStateChanged(playerId, state, false) postEquipmentDropToApi(nil, playerId, item) local player = PlayerResource:GetPlayer(playerId) if player then CustomGameEventManager:Send_ServerToPlayer(player, "equipment_drop_received", {item = item}) end return item end function EquipmentManager.prototype.applyEquipmentToHero(self, hero) if not hero or not hero:IsRealHero() or hero:IsIllusion() then return end local playerId = hero:GetPlayerOwnerID() if playerId == nil or playerId < 0 then return end local state = self:getOrCreateState(playerId) local aggregate = self:aggregateStatsForActiveLoadout(state) hero:AddNewModifier( hero, nil, EQUIPMENT_MODIFIER_NAME, { luckPct = tostring(aggregate.luckPct), strengthPct = tostring(aggregate.strengthPct), intellectPct = tostring(aggregate.intellectPct), damagePct = tostring(aggregate.damagePct), damageReductionPct = tostring(aggregate.damageReductionPct), spellAmpPct = tostring(aggregate.spellAmpPct) } ) end function EquipmentManager.prototype.reapplyForPlayer(self, playerId) if not PlayerResource:IsValidPlayerID(playerId) then return end local hero = PlayerResource:GetSelectedHeroEntity(playerId) if not hero then return end self:applyEquipmentToHero(hero) end function EquipmentManager.prototype.getOrCreateState(self, playerId) local existing = self.playerState:get(playerId) if existing then return existing end local info = PlayerInfo:GetPlayerInfo(playerId) local fromInfo = info and info.equipment_state local state = self:normalizeState(fromInfo) self.playerState:set(playerId, state) return state end function EquipmentManager.prototype.normalizeState(self, state) local fallback = { version = EQUIPMENT_STATE_VERSION, inventory = {}, activeLoadoutIndex = 1, loadouts = { [1] = createEmptyLoadout(nil), [2] = createEmptyLoadout(nil), [3] = createEmptyLoadout(nil) } } if not state or type(state) ~= "table" then return fallback end local ____state_activeLoadoutIndex_4 = state.activeLoadoutIndex if ____state_activeLoadoutIndex_4 == nil then ____state_activeLoadoutIndex_4 = 1 end local activeRaw = __TS__Number(____state_activeLoadoutIndex_4) local active = activeRaw >= 1 and activeRaw <= 3 and math.floor(activeRaw) or 1 local ____EQUIPMENT_STATE_VERSION_16 = EQUIPMENT_STATE_VERSION local ____temp_17 = __TS__ArrayIsArray(state.inventory) and state.inventory or ({}) local ____createEmptyLoadout_result_7 = createEmptyLoadout(nil) local ____opt_5 = state.loadouts if ____opt_5 ~= nil then ____opt_5 = ____opt_5[1] end local ____TS__ObjectAssign_result_14 = __TS__ObjectAssign({}, ____createEmptyLoadout_result_7, ____opt_5) local ____createEmptyLoadout_result_10 = createEmptyLoadout(nil) local ____opt_8 = state.loadouts if ____opt_8 ~= nil then ____opt_8 = ____opt_8[2] end local ____TS__ObjectAssign_result_15 = __TS__ObjectAssign({}, ____createEmptyLoadout_result_10, ____opt_8) local ____createEmptyLoadout_result_13 = createEmptyLoadout(nil) local ____opt_11 = state.loadouts if ____opt_11 ~= nil then ____opt_11 = ____opt_11[3] end return { version = ____EQUIPMENT_STATE_VERSION_16, inventory = ____temp_17, activeLoadoutIndex = active, loadouts = { [1] = ____TS__ObjectAssign_result_14, [2] = ____TS__ObjectAssign_result_15, [3] = __TS__ObjectAssign({}, ____createEmptyLoadout_result_13, ____opt_11) } } end function EquipmentManager.prototype.withPlayerLock(self, playerId, fn) if self.playerOpLock:get(playerId) then self:sendActionResult(playerId, {success = false, message = "Операция уже выполняется, подожди"}) return end self.playerOpLock:set(playerId, true) do pcall(function() fn(nil) end) do self.playerOpLock:set(playerId, false) end end end function EquipmentManager.prototype.afterStateChanged(self, playerId, state, includeActionOk) if includeActionOk == nil then includeActionOk = true end self.playerState:set(playerId, state) self:publishState(playerId, state) saveEquipmentStateToApi(nil, playerId, state) if includeActionOk then self:sendActionResult(playerId, {success = true}) end self:reapplyForPlayer(playerId) end function EquipmentManager.prototype.publishState(self, playerId, state) CustomNetTables:SetTableValue( "equipment", tostring(playerId), state ) local playerInfo = PlayerInfo:GetPlayerInfo(playerId) or ({}) playerInfo.equipment_state = state PlayerInfo:UpdatePlayerInfo(playerId, playerInfo) local player = PlayerResource:GetPlayer(playerId) if player then CustomGameEventManager:Send_ServerToPlayer(player, "equipment_state", {state = state}) end end function EquipmentManager.prototype.sendActionResult(self, playerId, payload) local player = PlayerResource:GetPlayer(playerId) if not player then return end CustomGameEventManager:Send_ServerToPlayer(player, "equipment_action_result", payload) end function EquipmentManager.prototype.removeItemFromAllLoadouts(self, state, instanceId) local loadouts = {state.loadouts[1], state.loadouts[2], state.loadouts[3]} for ____, loadout in ipairs(loadouts) do for ____, slot in ipairs(__TS__ObjectKeys(loadout)) do if loadout[slot] == instanceId then loadout[slot] = nil end end end end function EquipmentManager.prototype.aggregateStatsForActiveLoadout(self, state) local loadout = state.loadouts[state.activeLoadoutIndex] local equippedIds = __TS__New(Set) for ____, value in ipairs(__TS__ObjectValues(loadout)) do if value then equippedIds:add(value) end end local equipped = __TS__ArrayFilter( state.inventory, function(____, x) return equippedIds:has(x.instanceId) end ) local result = { luckPct = 0, strengthPct = 0, intellectPct = 0, damagePct = 0, damageReductionPct = 0, spellAmpPct = 0 } for ____, item in ipairs(equipped) do for ____, stat in ipairs(item.stats) do repeat local ____switch74 = stat.statKey local ____cond74 = ____switch74 == "luck_pct" if ____cond74 then result.luckPct = result.luckPct + stat.valuePct break end ____cond74 = ____cond74 or ____switch74 == "strength_pct" if ____cond74 then result.strengthPct = result.strengthPct + stat.valuePct break end ____cond74 = ____cond74 or ____switch74 == "intellect_pct" if ____cond74 then result.intellectPct = result.intellectPct + stat.valuePct break end ____cond74 = ____cond74 or ____switch74 == "damage_pct" if ____cond74 then result.damagePct = result.damagePct + stat.valuePct break end ____cond74 = ____cond74 or ____switch74 == "damage_reduction_pct" if ____cond74 then result.damageReductionPct = result.damageReductionPct + stat.valuePct break end ____cond74 = ____cond74 or ____switch74 == "spell_amp_pct" if ____cond74 then result.spellAmpPct = result.spellAmpPct + stat.valuePct break end until true end end return result end return ____exports