438 lines
18 KiB
Lua
438 lines
18 KiB
Lua
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
|