local ____lualib = require("lualib_bundle") local __TS__Class = ____lualib.__TS__Class local __TS__New = ____lualib.__TS__New local __TS__ArrayIsArray = ____lualib.__TS__ArrayIsArray local __TS__ArrayMap = ____lualib.__TS__ArrayMap local __TS__StringTrim = ____lualib.__TS__StringTrim local __TS__StringSplit = ____lualib.__TS__StringSplit local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter local __TS__ObjectValues = ____lualib.__TS__ObjectValues local __TS__StringSubstring = ____lualib.__TS__StringSubstring local __TS__ObjectKeys = ____lualib.__TS__ObjectKeys local __TS__TypeOf = ____lualib.__TS__TypeOf local __TS__Number = ____lualib.__TS__Number local Set = ____lualib.Set local __TS__ArraySome = ____lualib.__TS__ArraySome 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 ____ArsenalManager = require("arsenal.ArsenalManager") local ArsenalManager = ____ArsenalManager.ArsenalManager local ____store_manager = require("store_manager") local StoreManager = ____store_manager.StoreManager local LOG_PREFIX = "[MarketplaceManager]" --- Вкл. шумные print (API, рефреш, слоты). По умолчанию выкл. local MARKETPLACE_VERBOSE_LOG = false --- Отдельно: разбор истории продаж / NetTable (вкл. при отладке пустой истории). local MARKETPLACE_SALES_DEBUG_LOG = false local function marketplaceLog(self, message) if MARKETPLACE_VERBOSE_LOG then print(message) end end local function salesHistoryDebugLog(self, message) if MARKETPLACE_SALES_DEBUG_LOG then print((LOG_PREFIX .. "[sales] ") .. message) end end local MARKET_MIN_PRICE_FREE = 5000 --- Слоты активных лотов на игрока (без донат-расширения). local MARKET_BASE_SLOTS_LIMIT = 8 local MARKET_MAX_SLOTS_LIMIT = 8 ____exports.MarketplaceManagerClass = __TS__Class() local MarketplaceManagerClass = ____exports.MarketplaceManagerClass MarketplaceManagerClass.name = "MarketplaceManagerClass" MarketplaceManagerClass.____file_path = "scripts/vscripts/arsenal/MarketplaceManager.lua" function MarketplaceManagerClass.prototype.____constructor(self) self.marketBuyInFlightByPlayer = {} self.marketCreateInFlightByPlayer = {} end function MarketplaceManagerClass.getInstance(self) if not ____exports.MarketplaceManagerClass._instance then ____exports.MarketplaceManagerClass._instance = __TS__New(____exports.MarketplaceManagerClass) end return ____exports.MarketplaceManagerClass._instance end function MarketplaceManagerClass.prototype.initialize(self) if not IsServer() then return end self:registerListeners() marketplaceLog(nil, LOG_PREFIX .. " initialized") end function MarketplaceManagerClass.prototype.registerListeners(self) CustomGameEventManager:RegisterListener( "arsenal_market_refresh", function(_src, data) local playerId = self:resolvePlayerId(data) if playerId == nil then return end local stats = self:parseStatKeys(data.stats) self:refreshForPlayer(playerId, stats, false) end ) CustomGameEventManager:RegisterListener( "arsenal_market_create", function(_src, data) local playerId = self:resolvePlayerId(data) if playerId == nil then return end local instanceId = tostring(data.instance_id or "") local priceFree = math.max( 1, math.floor(tonumber(data.price_free) or 0) ) self:createListing(playerId, instanceId, priceFree) end ) CustomGameEventManager:RegisterListener( "arsenal_market_buy", function(_src, data) local playerId = self:resolvePlayerId(data) if playerId == nil then return end local listingId = tostring(data.listing_id or "") self:buyListing(playerId, listingId) end ) CustomGameEventManager:RegisterListener( "arsenal_market_cancel", function(_src, data) local playerId = self:resolvePlayerId(data) if playerId == nil then return end local listingId = tostring(data.listing_id or "") self:cancelListing(playerId, listingId) end ) end function MarketplaceManagerClass.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 MarketplaceManagerClass.prototype.parseStatKeys(self, raw) if not raw then return {} end if __TS__ArrayIsArray(raw) then return __TS__ArrayMap( raw, function(____, x) return tostring(x) end ) end if type(raw) == "string" then return __TS__ArrayFilter( __TS__ArrayMap( __TS__StringSplit(raw, ","), function(____, x) return __TS__StringTrim(tostring(x)) end ), function(____, x) return #x > 0 end ) end if type(raw) == "table" then return __TS__ArrayMap( __TS__ObjectValues(raw), function(____, x) return tostring(x) end ) end return {} end function MarketplaceManagerClass.prototype.getSteamId(self, playerId) local steamId = PlayerResource:GetSteamAccountID(playerId) if not steamId or steamId <= 0 then return nil end return steamId end function MarketplaceManagerClass.prototype.sendResult(self, playerId, success, messageToken, op) marketplaceLog( nil, (((((((LOG_PREFIX .. " result player=") .. tostring(playerId)) .. " ok=") .. tostring(success)) .. " msg=") .. messageToken) .. " op=") .. (op or "none") ) local player = PlayerResource:GetPlayer(playerId) if not player then return end local payload = {success = success, message = messageToken} if op ~= nil then payload.op = op end CustomGameEventManager:Send_ServerToPlayer(player, "arsenal_market_result", payload) end function MarketplaceManagerClass.prototype.callApi(self, method, url, body, cb) marketplaceLog( nil, (((((LOG_PREFIX .. " api -> ") .. method) .. " ") .. url) .. " body=") .. (body and json.encode(body) or "{}") ) local req = CreateHTTPRequestScriptVM(method, url) setApiHeaders(nil, req) if method == "POST" or method == "PUT" then req:SetHTTPRequestRawPostBody( "application/json", json.encode(body or ({})) ) end req:Send(function(result) local ok = result.StatusCode >= 200 and result.StatusCode < 300 local payload = nil if result.Body and #result.Body > 0 then local decoded = {json.decode(result.Body)} if decoded and type(decoded) == "table" then payload = decoded end end local bodySize = type(result.Body) == "string" and #result.Body or 0 marketplaceLog( nil, (((((((((LOG_PREFIX .. " api <- ") .. method) .. " ") .. url) .. " code=") .. tostring(result.StatusCode)) .. " ok=") .. tostring(ok)) .. " bodySize=") .. tostring(bodySize) ) if result.Body and #result.Body > 0 then local preview = #result.Body > 220 and __TS__StringSubstring(result.Body, 0, 220) .. "..." or result.Body marketplaceLog(nil, (LOG_PREFIX .. " api body preview=") .. preview) end if payload and type(payload) == "table" then local sampleItems = payload.items if sampleItems and __TS__ArrayIsArray(sampleItems) then marketplaceLog( nil, (LOG_PREFIX .. " api payload items=") .. tostring(#sampleItems) ) else local keys = table.concat( __TS__ObjectKeys(payload), "," ) marketplaceLog(nil, ((LOG_PREFIX .. " api payload keys=[") .. keys) .. "]") end end if MARKETPLACE_SALES_DEBUG_LOG and (string.find(url, "arsenal_market/sales", nil, true) or 0) - 1 >= 0 then local rawBody = type(result.Body) == "string" and result.Body or "" local preview = #rawBody <= 0 and "(empty body)" or (#rawBody > 380 and __TS__StringSubstring(rawBody, 0, 380) .. "..." or rawBody) salesHistoryDebugLog( nil, (((((("HTTP GET sales code=" .. tostring(result.StatusCode)) .. " ok=") .. tostring(ok)) .. " bodyLen=") .. tostring(bodySize)) .. " preview=") .. preview ) end if cb ~= nil then cb(nil, ok, payload) end end) end function MarketplaceManagerClass.prototype.unwrapPayload(self, payload) if not payload or type(payload) ~= "table" then return payload end if __TS__ArrayIsArray(payload) then for ____, part in ipairs(payload) do do if not part or type(part) ~= "table" then goto __continue50 end if __TS__ArrayIsArray(part) then return part end if part.items ~= nil or part.listings ~= nil or part.data ~= nil then return part end end ::__continue50:: end end return payload end function MarketplaceManagerClass.prototype.extractListingsFromPayload(self, payload) local p = self:unwrapPayload(payload) if not p or type(p) ~= "table" then return {} end if __TS__ArrayIsArray(p) then return p end local direct = p.items if __TS__ArrayIsArray(direct) then return direct end local listings = p.listings if __TS__ArrayIsArray(listings) then return listings end local data = p.data if __TS__ArrayIsArray(data) then return data end if data and type(data) == "table" then local di = data.items if __TS__ArrayIsArray(di) then return di end local dl = data.listings if __TS__ArrayIsArray(dl) then return dl end end return {} end function MarketplaceManagerClass.prototype.extractSalesHistoryFromPayload(self, payload) local function pickRowsArray(____, node) if not node or type(node) ~= "table" then return {rows = {}, source = "empty_node"} end if __TS__ArrayIsArray(node) then return {rows = node, source = "nested_array"} end local o = node for ____, k in ipairs({ "items", "sales", "sales_history", "history", "rows" }) do local v = o[k] if __TS__ArrayIsArray(v) then return {rows = v, source = "object." .. k} end end local data = o.data if __TS__ArrayIsArray(data) then return {rows = data, source = "object.data_array"} end if data and type(data) == "table" then local ____data_items_13 = data.items if ____data_items_13 == nil then ____data_items_13 = data.sales end local di = ____data_items_13 if __TS__ArrayIsArray(di) then return {rows = di, source = "object.data.items_or_sales"} end end return {rows = {}, source = "object_no_array"} end local rawType = (payload == nil or payload == nil) and "null" or __TS__TypeOf(payload) local p = self:unwrapPayload(payload) local unwrappedType = (p == nil or p == nil) and "null" or __TS__TypeOf(p) local shape = "none" local rows = {} if not p then salesHistoryDebugLog(nil, ("extract: rawType=" .. rawType) .. " unwrapped=null → 0 rows") return {} end if __TS__ArrayIsArray(p) then rows = p shape = "unwrap_array" elseif type(p) == "table" then local picked = pickRowsArray(nil, p) rows = picked.rows shape = picked.source else salesHistoryDebugLog(nil, ((("extract: rawType=" .. rawType) .. " unwrapped=") .. unwrappedType) .. " → 0 rows (not object/array)") return {} end local out = {} for ____, row in ipairs(rows) do do if not row or type(row) ~= "table" then goto __continue78 end local ____math_max_17 = math.max local ____math_floor_16 = math.floor local ____row_price_free_14 = row.price_free if ____row_price_free_14 == nil then ____row_price_free_14 = row.priceFree end local ____row_price_free_14_15 = ____row_price_free_14 if ____row_price_free_14_15 == nil then ____row_price_free_14_15 = 0 end local price = ____math_max_17( 0, ____math_floor_16(__TS__Number(____row_price_free_14_15)) ) local ____math_max_21 = math.max local ____math_floor_20 = math.floor local ____row_commission_free_18 = row.commission_free if ____row_commission_free_18 == nil then ____row_commission_free_18 = row.commissionFree end local ____row_commission_free_18_19 = ____row_commission_free_18 if ____row_commission_free_18_19 == nil then ____row_commission_free_18_19 = 0 end local commission = ____math_max_21( 0, ____math_floor_20(__TS__Number(____row_commission_free_18_19)) ) local ____math_max_25 = math.max local ____math_floor_24 = math.floor local ____row_seller_received_free_22 = row.seller_received_free if ____row_seller_received_free_22 == nil then ____row_seller_received_free_22 = row.sellerReceivedFree end local ____row_seller_received_free_22_23 = ____row_seller_received_free_22 if ____row_seller_received_free_22_23 == nil then ____row_seller_received_free_22_23 = 0 end local received = ____math_max_25( 0, ____math_floor_24(__TS__Number(____row_seller_received_free_22_23)) ) local ____tostring_28 = tostring local ____row_listing_id_26 = row.listing_id if ____row_listing_id_26 == nil then ____row_listing_id_26 = row.listingId end local ____row_listing_id_26_27 = ____row_listing_id_26 if ____row_listing_id_26_27 == nil then ____row_listing_id_26_27 = "" end local ____tostring_28_result_42 = ____tostring_28(____row_listing_id_26_27) local ____tostring_31 = tostring local ____row_item_name_29 = row.item_name if ____row_item_name_29 == nil then ____row_item_name_29 = row.itemName end local ____row_item_name_29_30 = ____row_item_name_29 if ____row_item_name_29_30 == nil then ____row_item_name_29_30 = "" end local ____tostring_31_result_43 = ____tostring_31(____row_item_name_29_30) local ____tostring_33 = tostring local ____row_quality_32 = row.quality if ____row_quality_32 == nil then ____row_quality_32 = "common" end local ____tostring_33_result_44 = ____tostring_33(____row_quality_32) local ____math_floor_36 = math.floor local ____row_buyer_steam_id_34 = row.buyer_steam_id if ____row_buyer_steam_id_34 == nil then ____row_buyer_steam_id_34 = row.buyerSteamId end local ____row_buyer_steam_id_34_35 = ____row_buyer_steam_id_34 if ____row_buyer_steam_id_34_35 == nil then ____row_buyer_steam_id_34_35 = 0 end local ____math_floor_36_result_45 = ____math_floor_36(__TS__Number(____row_buyer_steam_id_34_35)) local ____tostring_39 = tostring local ____row_buyer_name_37 = row.buyer_name if ____row_buyer_name_37 == nil then ____row_buyer_name_37 = row.buyerName end local ____row_buyer_name_37_38 = ____row_buyer_name_37 if ____row_buyer_name_37_38 == nil then ____row_buyer_name_37_38 = "" end local ____tostring_39_result_46 = ____tostring_39(____row_buyer_name_37_38) local ____temp_47 = received > 0 and received or math.max(0, price - commission) local ____row_sold_at_40 = row.sold_at if ____row_sold_at_40 == nil then ____row_sold_at_40 = row.soldAt end local ____row_sold_at_40_41 = ____row_sold_at_40 if ____row_sold_at_40_41 == nil then ____row_sold_at_40_41 = row.updated_at end out[#out + 1] = { listing_id = ____tostring_28_result_42, item_name = ____tostring_31_result_43, quality = ____tostring_33_result_44, price_free = price, buyer_steam_id = ____math_floor_36_result_45, buyer_name = ____tostring_39_result_46, commission_free = commission, seller_received_free = ____temp_47, sold_at = ____row_sold_at_40_41 } end ::__continue78:: end local first = #out > 0 and out[1] or nil salesHistoryDebugLog( nil, (((((((((("extract: rawType=" .. rawType) .. " unwrapped=") .. unwrappedType) .. " shape=") .. shape) .. " rawRows=") .. tostring(#rows)) .. " normalized=") .. tostring(#out)) .. " firstItem=") .. (first and tostring(first.item_name) or "-") ) return out end function MarketplaceManagerClass.prototype.extractInventoryInstancesFromPayload(self, payload) local visited = __TS__New(Set) local walk walk = function(____, node, depth) if not node or type(node) ~= "table" then return nil end if visited:has(node) then return nil end visited:add(node) if depth > 6 then return nil end local directInstances = node.instances if directInstances and type(directInstances) == "table" then return directInstances end local ai = node.arsenal_inventory if ai and type(ai) == "table" and ai.instances and type(ai.instances) == "table" then return ai.instances end local d = node.data if d and type(d) == "table" then local fromData = walk(nil, d, depth + 1) if fromData then return fromData end end if __TS__ArrayIsArray(node) then for ____, part in ipairs(node) do local found = walk(nil, part, depth + 1) if found then return found end end return nil end for k in pairs(node) do do local v = node[k] if not v or type(v) ~= "table" then goto __continue94 end local found = walk(nil, v, depth + 1) if found then return found end end ::__continue94:: end return nil end return walk(nil, payload, 0) or ({}) end function MarketplaceManagerClass.prototype.collectCreateCandidateIds(self, localInstanceId, owned, localInstances, apiInstances) local out = {} local seen = {} local function push(____, v) if v == nil or v == nil then return end local s = tostring(v) if not s or #s <= 0 then return end if seen[s] then return end seen[s] = true out[#out + 1] = type(v) == "number" and v or s end push( nil, __TS__Number(owned.globalSerial or 0) > 0 and __TS__Number(owned.globalSerial or 0) or nil ) push( nil, __TS__Number(owned.serial or 0) > 0 and __TS__Number(owned.serial or 0) or nil ) push(nil, localInstanceId) push(nil, owned.instanceId) push(nil, owned.instance_id) local function scanBag(____, bag) for key in pairs(bag) do local row = bag[key] local ____tostring_56 = tostring local ____opt_result_50 if row ~= nil then ____opt_result_50 = row.instanceId end local ____opt_result_50_54 = ____opt_result_50 if ____opt_result_50_54 == nil then local ____opt_result_53 if row ~= nil then ____opt_result_53 = row.instance_id end ____opt_result_50_54 = ____opt_result_53 end local ____opt_result_50_54_55 = ____opt_result_50_54 if ____opt_result_50_54_55 == nil then ____opt_result_50_54_55 = "" end local rowInstance = ____tostring_56(____opt_result_50_54_55) local ____opt_result_59 if row ~= nil then ____opt_result_59 = row.globalSerial end local ____opt_result_59_63 = ____opt_result_59 if ____opt_result_59_63 == nil then local ____opt_result_62 if row ~= nil then ____opt_result_62 = row.global_serial end ____opt_result_59_63 = ____opt_result_62 end local ____opt_result_59_63_64 = ____opt_result_59_63 if ____opt_result_59_63_64 == nil then ____opt_result_59_63_64 = 0 end local rowGlobal = __TS__Number(____opt_result_59_63_64) local ____opt_result_67 if row ~= nil then ____opt_result_67 = row.serial end local ____opt_result_67_68 = ____opt_result_67 if ____opt_result_67_68 == nil then ____opt_result_67_68 = 0 end local rowSerial = __TS__Number(____opt_result_67_68) local sameInstance = #rowInstance > 0 and rowInstance == localInstanceId local sameGlobal = __TS__Number(owned.globalSerial or 0) > 0 and rowGlobal > 0 and rowGlobal == __TS__Number(owned.globalSerial or 0) local sameSerial = __TS__Number(owned.serial or 0) > 0 and rowSerial > 0 and rowSerial == __TS__Number(owned.serial or 0) if sameInstance or sameGlobal or sameSerial then push(nil, key) push(nil, rowInstance) if rowGlobal > 0 then push(nil, rowGlobal) end if rowSerial > 0 then push(nil, rowSerial) end end end end scanBag(nil, localInstances) scanBag(nil, apiInstances) return out end function MarketplaceManagerClass.prototype.extractSlotsFromPayload(self, payload, myListingsCount) local p = self:unwrapPayload(payload) if not p or type(p) ~= "table" then return {slots_limit = MARKET_BASE_SLOTS_LIMIT, slots_used = myListingsCount} end local ____temp_69 if p.data and type(p.data) == "table" then ____temp_69 = p.data else ____temp_69 = p end local data = ____temp_69 local ____data_slots_limit_70 = data.slots_limit if ____data_slots_limit_70 == nil then ____data_slots_limit_70 = data.market_slots_limit end local ____data_slots_limit_70_71 = ____data_slots_limit_70 if ____data_slots_limit_70_71 == nil then ____data_slots_limit_70_71 = MARKET_BASE_SLOTS_LIMIT end local rawLimit = __TS__Number(____data_slots_limit_70_71) local ____data_slots_used_72 = data.slots_used if ____data_slots_used_72 == nil then ____data_slots_used_72 = data.market_slots_used end local ____data_slots_used_72_73 = ____data_slots_used_72 if ____data_slots_used_72_73 == nil then ____data_slots_used_72_73 = myListingsCount end local rawUsed = __TS__Number(____data_slots_used_72_73) return { slots_limit = math.max( MARKET_BASE_SLOTS_LIMIT, math.min( MARKET_MAX_SLOTS_LIMIT, math.floor(rawLimit or MARKET_BASE_SLOTS_LIMIT) ) ), slots_used = math.max( 0, math.floor(rawUsed or myListingsCount) ) } end function MarketplaceManagerClass.prototype.setMarketTables(self, playerId, publicListings, myListings, slots, selectedStats, salesHistory) marketplaceLog( nil, ((((((((((((((LOG_PREFIX .. " set tables player=") .. tostring(playerId)) .. " public=") .. tostring(#publicListings)) .. " my=") .. tostring(#myListings)) .. " sales=") .. tostring(#salesHistory)) .. " slots=") .. tostring(slots.slots_used or #myListings)) .. "/") .. tostring(slots.slots_limit or MARKET_BASE_SLOTS_LIMIT)) .. " stats=[") .. table.concat(selectedStats, ",")) .. "]" ) local statKeysSet = {} for ____, row in ipairs(publicListings) do for ____, k in ipairs(row.stat_keys or ({})) do if k and #k > 0 then statKeysSet[k] = true end end for ____, p in ipairs(row.stats_snapshot or ({})) do local k = tostring(p.key or "") if #k > 0 then statKeysSet[k] = true end end end CustomNetTables:SetTableValue( "arsenal_market", "listings_public", { listings = publicListings, stat_keys = __TS__ObjectKeys(statKeysSet), selected_stats = selectedStats, ts = GameRules:GetGameTime() } ) local salesJsonEncoded = json.encode(salesHistory) salesHistoryDebugLog( nil, (((((("setMarketTables pid=" .. tostring(playerId)) .. " sales=") .. tostring(#salesHistory)) .. " sales_json_chars=") .. tostring(#salesJsonEncoded)) .. " my=") .. tostring(#myListings) ) CustomNetTables:SetTableValue( "arsenal_market", tostring(playerId), { my_listings = myListings, sales_history_json = salesJsonEncoded, slots_limit = slots.slots_limit or MARKET_BASE_SLOTS_LIMIT, slots_used = slots.slots_used or #myListings, ts = GameRules:GetGameTime() } ) end function MarketplaceManagerClass.prototype.refreshForAllConnectedPlayers(self) do local pid = 0 while pid < DOTA_MAX_PLAYERS do do if not PlayerResource:IsValidPlayerID(pid) then goto __continue121 end local playerId = pid local player = PlayerResource:GetPlayer(playerId) if not player then goto __continue121 end self:refreshForPlayer(playerId, {}, true) end ::__continue121:: pid = pid + 1 end end end function MarketplaceManagerClass.prototype.refreshForPlayer(self, playerId, selectedStats, silentOnError) local steamId = self:getSteamId(playerId) if not steamId then return end marketplaceLog( nil, (((((((LOG_PREFIX .. " refresh player=") .. tostring(playerId)) .. " steam=") .. tostring(steamId)) .. " stats=[") .. table.concat(selectedStats, ",")) .. "] silent=") .. tostring(silentOnError) ) local statsQuery = #selectedStats > 0 and "?stats=" .. table.concat(selectedStats, ",") or "" self:callApi( "GET", (SERVER_CONFIG.API_URL .. "/arsenal_market/listings") .. statsQuery, nil, function(____, okPub, pubPayload) self:callApi( "GET", ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/my_listings", nil, function(____, okMine, minePayload) self:callApi( "GET", ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/slots", nil, function(____, okSlots, slotsPayload) self:callApi( "GET", ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/sales", nil, function(____, okSales, salesPayload) local publicListings = okPub and self:extractListingsFromPayload(pubPayload) or ({}) local myListings = okMine and self:extractListingsFromPayload(minePayload) or ({}) local slots = okSlots and self:extractSlotsFromPayload(slotsPayload, #myListings) or ({slots_limit = MARKET_BASE_SLOTS_LIMIT, slots_used = #myListings}) salesHistoryDebugLog( nil, (((((("GET sales player=" .. tostring(playerId)) .. " steam=") .. tostring(steamId)) .. " ok=") .. tostring(okSales)) .. " payloadType=") .. ((salesPayload == nil or salesPayload == nil) and "null" or __TS__TypeOf(salesPayload)) ) local salesHistory = okSales and self:extractSalesHistoryFromPayload(salesPayload) or ({}) salesHistoryDebugLog( nil, "GET sales → normalized=" .. tostring(#salesHistory) ) self:setMarketTables( playerId, publicListings, myListings, slots, selectedStats, salesHistory ) if (not okPub or not okMine or not okSlots) and not silentOnError then self:sendResult(playerId, false, "#store_marketplace_error_fetch", "refresh") end end ) end ) end ) end ) end function MarketplaceManagerClass.prototype.createListing(self, playerId, instanceId, priceFree) marketplaceLog( nil, (((((LOG_PREFIX .. " create req player=") .. tostring(playerId)) .. " instance=") .. instanceId) .. " price=") .. tostring(priceFree) ) if not instanceId or priceFree < MARKET_MIN_PRICE_FREE then self:sendResult(playerId, false, "#store_marketplace_error_min_price", "create") return end local steamId = self:getSteamId(playerId) if not steamId then return end local owned = ArsenalManager:getInventoryInstance(playerId, instanceId) if not owned then self:sendResult(playerId, false, "#arsenal_item_not_owned", "create") return end marketplaceLog( nil, (((((((((((LOG_PREFIX .. " create owned local instanceId=") .. tostring(owned.instanceId or "")) .. " serial=") .. tostring(owned.serial or 0)) .. " globalSerial=") .. tostring(owned.globalSerial or 0)) .. " item=") .. tostring(owned.itemName or "")) .. " quality=") .. tostring(owned.quality or "")) .. " lvl=") .. tostring(owned.upgradeLevel or 0) ) if ArsenalManager:isInstanceTradeLocked(playerId, instanceId) then self:sendResult(playerId, false, "#store_marketplace_error_locked_item", "create") return end local itemLevel = math.floor(__TS__Number(owned.upgradeLevel or 0)) if itemLevel < 1 then self:sendResult(playerId, false, "#store_marketplace_error_min_level_1", "create") return end local pidKey = playerId if self.marketCreateInFlightByPlayer[pidKey] then self:sendResult(playerId, false, "#store_marketplace_error_create_in_progress", "create") return end self.marketCreateInFlightByPlayer[pidKey] = true local function clearCreateFlight() self.marketCreateInFlightByPlayer[pidKey] = false end local inventoryPayload = ArsenalManager:buildInventoryPayloadForMarket(playerId) local payloadInstances = inventoryPayload.instances or ({}) marketplaceLog( nil, (LOG_PREFIX .. " create local payload instancesCount=") .. tostring(#__TS__ObjectKeys(payloadInstances)) ) local localRow = payloadInstances[instanceId] if localRow then marketplaceLog( nil, (((((((((LOG_PREFIX .. " create local payload hit key=") .. instanceId) .. " row.instanceId=") .. tostring(localRow.instanceId or "")) .. " row.instance_id=") .. tostring(localRow.instance_id or "")) .. " row.global_serial=") .. tostring(localRow.global_serial or 0)) .. " row.serial=") .. tostring(localRow.serial or 0) ) else marketplaceLog(nil, (LOG_PREFIX .. " create local payload miss key=") .. instanceId) end self:callApi( "PUT", ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_inventory", {arsenal_inventory = inventoryPayload}, function(____, _okSync) self:callApi( "GET", ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_inventory", nil, function(____, _okInv, invPayload) local apiInstances = self:extractInventoryInstancesFromPayload(invPayload) marketplaceLog( nil, (LOG_PREFIX .. " create api inventory instancesCount=") .. tostring(#__TS__ObjectKeys(apiInstances)) ) local byLocalKey = apiInstances[instanceId] if byLocalKey then marketplaceLog( nil, (((((((((LOG_PREFIX .. " create api hit localKey=") .. instanceId) .. " row.instanceId=") .. tostring(byLocalKey.instanceId or "")) .. " row.instance_id=") .. tostring(byLocalKey.instance_id or "")) .. " row.global_serial=") .. tostring(byLocalKey.global_serial or 0)) .. " row.serial=") .. tostring(byLocalKey.serial or 0) ) else marketplaceLog(nil, (LOG_PREFIX .. " create api miss localKey=") .. instanceId) end local candidateIds = self:collectCreateCandidateIds(instanceId, owned, payloadInstances, apiInstances) marketplaceLog( nil, ((LOG_PREFIX .. " create candidates=[") .. table.concat( __TS__ArrayMap( candidateIds, function(____, x) return tostring(x) end ), "," )) .. "]" ) self:callApi( "GET", ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/slots", nil, function(____, okSlots, slotsPayload) if not okSlots then clearCreateFlight(nil) self:sendResult(playerId, false, "#store_marketplace_error_fetch", "create") return end local ____math_max_79 = math.max local ____math_min_78 = math.min local ____math_floor_77 = math.floor local ____opt_result_76 if slotsPayload ~= nil then ____opt_result_76 = slotsPayload.slots_limit end local slotsLimit = ____math_max_79( MARKET_BASE_SLOTS_LIMIT, ____math_min_78( MARKET_MAX_SLOTS_LIMIT, ____math_floor_77(__TS__Number(____opt_result_76 or MARKET_BASE_SLOTS_LIMIT)) ) ) local ____math_max_84 = math.max local ____math_floor_83 = math.floor local ____opt_result_82 if slotsPayload ~= nil then ____opt_result_82 = slotsPayload.slots_used end local slotsUsed = ____math_max_84( 0, ____math_floor_83(__TS__Number(____opt_result_82 or 0)) ) if slotsUsed >= slotsLimit then clearCreateFlight(nil) self:sendResult(playerId, false, "#store_marketplace_error_slots_full", "create") return end local createUrl = ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/create" local function buildCreateBody(____, primaryInstanceId) return { instance_id = primaryInstanceId, instanceId = instanceId, item_instance_id = primaryInstanceId, itemInstanceId = instanceId, serial = __TS__Number(owned.serial or 0), global_serial = __TS__Number(owned.globalSerial or 0), globalSerial = __TS__Number(owned.globalSerial or 0), item_name = tostring(owned.itemName or ""), itemName = tostring(owned.itemName or ""), quality = tostring(owned.quality or ""), upgrade_level = __TS__Number(owned.upgradeLevel or 0), upgradeLevel = __TS__Number(owned.upgradeLevel or 0), price_free = priceFree, priceFree = priceFree, request_id = tostring(DoUniqueString("market_create")), requestId = tostring(DoUniqueString("market_create_alt")) } end local function onCreateSuccess() ArsenalManager:removeInventoryInstanceForMarket(playerId, instanceId) clearCreateFlight(nil) ArsenalManager:loadFromServer(playerId) self:refreshForPlayer(playerId, {}, true) self:sendResult(playerId, true, "#store_marketplace_success_create", "create") end local tryCreateByIndex tryCreateByIndex = function(____, idx) if idx >= #candidateIds then clearCreateFlight(nil) self:sendResult(playerId, false, "#store_marketplace_error_create", "create") return end local currentId = candidateIds[idx + 1] marketplaceLog( nil, (((LOG_PREFIX .. " create try idx=") .. tostring(idx)) .. " candidate=") .. tostring(currentId) ) self:callApi( "POST", createUrl, buildCreateBody(nil, currentId), function(____, okTry, payloadTry) if not okTry then local ____tostring_93 = tostring local ____opt_result_87 if payloadTry ~= nil then ____opt_result_87 = payloadTry.error end local ____opt_result_87_91 = ____opt_result_87 if ____opt_result_87_91 == nil then local ____opt_result_90 if payloadTry ~= nil then ____opt_result_90 = payloadTry.message end ____opt_result_87_91 = ____opt_result_90 end local ____opt_result_87_91_92 = ____opt_result_87_91 if ____opt_result_87_91_92 == nil then ____opt_result_87_91_92 = "" end local err = ____tostring_93(____opt_result_87_91_92) marketplaceLog( nil, ((((((LOG_PREFIX .. " create try fail idx=") .. tostring(idx)) .. " candidate=") .. tostring(currentId)) .. " err=\"") .. err) .. "\"" ) if idx + 1 < #candidateIds then tryCreateByIndex(nil, idx + 1) return end clearCreateFlight(nil) self:sendResult(playerId, false, "#store_marketplace_error_create", "create") return end marketplaceLog( nil, (((LOG_PREFIX .. " create try success idx=") .. tostring(idx)) .. " candidate=") .. tostring(currentId) ) onCreateSuccess(nil) end ) end tryCreateByIndex(nil, 0) end ) end ) end ) end function MarketplaceManagerClass.prototype.buyListing(self, playerId, listingId) marketplaceLog( nil, (((LOG_PREFIX .. " buy req player=") .. tostring(playerId)) .. " listing=") .. listingId ) if not listingId then self:sendResult(playerId, false, "#store_marketplace_error_invalid_data", "buy") return end local steamId = self:getSteamId(playerId) if not steamId then return end local pidKey = playerId if self.marketBuyInFlightByPlayer[pidKey] then self:sendResult(playerId, false, "#store_marketplace_error_buy_in_progress", "buy") return end self.marketBuyInFlightByPlayer[pidKey] = true local function clearBuyFlight() self.marketBuyInFlightByPlayer[pidKey] = false end self:callApi( "GET", ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/my_listings", nil, function(____, okMine, minePayload) if okMine then local ownListings = self:extractListingsFromPayload(minePayload) local isOwn = __TS__ArraySome( ownListings, function(____, x) return tostring(x.listing_id or "") == listingId end ) if isOwn then clearBuyFlight(nil) self:sendResult(playerId, false, "#store_marketplace_error_buy", "buy") return end end self:callApi( "POST", ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/buy", { listing_id = listingId, request_id = tostring(DoUniqueString("market_buy")) }, function(____, ok, payload) clearBuyFlight(nil) if not ok then self:sendResult(playerId, false, "#store_marketplace_error_buy", "buy") return end ArsenalManager:loadFromServer(playerId) StoreManager:getInstance():loadCurrencyFromServer(playerId) self:refreshForPlayer(playerId, {}, true) local ____opt_result_96 if payload ~= nil then ____opt_result_96 = payload.seller_steam_id end local sellerSteamId = __TS__Number(____opt_result_96 or 0) if sellerSteamId > 0 then local sellerPid = self:findConnectedPlayerBySteamId(sellerSteamId) if sellerPid ~= nil then ArsenalManager:loadFromServer(sellerPid) StoreManager:getInstance():loadCurrencyFromServer(sellerPid) self:refreshForPlayer(sellerPid, {}, true) end end self:refreshForAllConnectedPlayers() self:sendResult(playerId, true, "#store_marketplace_success_buy", "buy") end ) end ) end function MarketplaceManagerClass.prototype.cancelListing(self, playerId, listingId) marketplaceLog( nil, (((LOG_PREFIX .. " cancel req player=") .. tostring(playerId)) .. " listing=") .. listingId ) if not listingId then self:sendResult(playerId, false, "#store_marketplace_error_invalid_data", "cancel") return end local steamId = self:getSteamId(playerId) if not steamId then return end self:callApi( "POST", ((SERVER_CONFIG.API_URL .. "/player/") .. tostring(steamId)) .. "/arsenal_market/cancel", { listing_id = listingId, request_id = tostring(DoUniqueString("market_cancel")) }, function(____, ok) if not ok then self:sendResult(playerId, false, "#store_marketplace_error_cancel", "cancel") return end ArsenalManager:loadFromServer(playerId) self:refreshForPlayer(playerId, {}, true) self:sendResult(playerId, true, "#store_marketplace_success_cancel", "cancel") end ) end function MarketplaceManagerClass.prototype.findConnectedPlayerBySteamId(self, steamId) do local pid = 0 while pid < DOTA_MAX_PLAYERS do do if not PlayerResource:IsValidPlayerID(pid) then goto __continue175 end if PlayerResource:GetSteamAccountID(pid) == steamId then return pid end end ::__continue175:: pid = pid + 1 end end return nil end MarketplaceManagerClass = __TS__Decorate(MarketplaceManagerClass, MarketplaceManagerClass, {reloadable}, {kind = "class", name = "MarketplaceManagerClass"}) ____exports.MarketplaceManagerClass = MarketplaceManagerClass ____exports.MarketplaceManager = ____exports.MarketplaceManagerClass:getInstance() return ____exports