script_name("Market Helper") script_description("Arizona RP Market Scanner & Auto Trade") script_author("Shinik_Pupckin") script_version("4.8.4") _MH_VER = "4.8.4" --====================================================================== -- [MH] Toast notifications wrapper (module embedded further below, right -- after imgui is loaded - see "[MH] Embedded NotificationToastSystem"). -- Declared as GLOBALS on purpose: the main chunk is close to Lua's -- 200-local-variable limit, so we avoid adding new top-level locals here. --====================================================================== -- Раньше здесь передавалась захардкоженная MH_TOAST_DURATION (4 сек) ТРЕТЬИМ -- аргументом в MH_Notify.add() - Notification.new берёт duration именно из -- этого аргумента, если он не nil (см. "self.duration = tonumber(duration) -- or cfg.duration"), поэтому пользовательская настройка длительности тоста -- в панели настроек (settings.toast.duration, слайдер там же) ВСЕГДА -- игнорировалась - тост показывался 4 сек независимо от того, что выставил -- пользователь. Теперь передаём nil - длительность берётся из cfg.duration -- (то есть из настроек, actually применённых через _mh_apply_toast_settings). MH_TOAST_DURATION = nil -- Guess a toast type ('info'/'success'/'warning'/'error'/'arbitrage') from -- the cSAMP color tags already used inside MH's chat strings, so existing -- call sites do not need to be rewritten by hand. function _mh_toast_type(text, color) local t = tostring(text or '') -- Проверяем префикс "[MH ARB]" РАНЬШЕ общей success-ветки ниже - иначе -- тег {aaffaa}, который также стоит в арбитражных сообщениях, увёл бы -- их в обычный 'success' с заголовком "Успех" вместо "Арбитраж". if t:find('[MH ARB]', 1, true) then return 'arbitrage' -- [MH FIX] По аналогии с "[MH ARB]" выше - отдельные типы тостов -- для Вотчлиста ("Смотреть", иконка глаза), Избранного (звезда) и -- покупок/продаж (стрелка вверх, синяя/зелёная) - раньше эти -- mh_notify() падали в общий 'info' с заголовком "Информация", хотя у них -- уже был свой уникальный маркер в тексте/цвете. Проверяются -- ДО общих success/warning веток ниже по той же причине, что и с арбитражем -- выше - иначе их бы перехватили более общие цветовые условия -- (например {ffaa00} у избранного). elseif t:find('{6acfff}', 1, true) then return 'watch' elseif t:find('% рынка)', 1, true) then return 'favorite' elseif t:find('[MH] [+] Продажа', 1, true) then return 'sale' elseif t:find('[MH] [-] Покупка', 1, true) then return 'purchase' elseif t:find('{ff4444}', 1, true) or t:find('{ff6666}', 1, true) or t:find('{ff6644}', 1, true) or color == 0xFF4444 then return 'error' elseif t:find('{aaffaa}', 1, true) or t:find('{00cc00}', 1, true) or t:find('{00cc88}', 1, true) or color == 0x88FF88 then return 'success' elseif t:find('{ffaa00}', 1, true) or t:find('{ff8800}', 1, true) or t:find('{ff9966}', 1, true) or t:find('{FFD700}', 1, true) then return 'warning' else return 'info' end end -- Strip the "[MH ...]" prefix and cSAMP "{rrggbb}" color tags so the toast -- shows clean text instead of raw chat markup. function _mh_toast_clean(text) local t = tostring(text or '') t = t:gsub('%[MH[^%]]-%]%s*', '') t = t:gsub('{%x%x%x%x%x%x}', '') -- многие вызовы mh_notify() строят строку динамически (числа/суммы), смешивая её с CP1251-литералами прямо в исходнике - в чат (sampAddChatMessage) это идёт как есть, но imgui/тост ждёт UTF-8, иначе рисует "??" вместо кириллицы -- _to_utf8 объявлена ниже по файлу как глобальная функция, но т.к. этот код исполняется только во время вызова (после полной загрузки скрипта), она уже будет существовать - pcall на случай, если её ещё нет if _to_utf8 then local ok, u = pcall(_to_utf8, t) if ok and u and u ~= '' then t = u end end return t end -- Drop-in replacement for sampAddChatMessage used across this script: -- keeps the original chat message AND shows a toast popup for it. -- MH_Notify is defined later in the file (right after imgui loads), so at -- the moment this function is *called* (not defined) it will already exist. function mh_notify(text, color) sampAddChatMessage(text, color) if MH_Notify then pcall(function() MH_Notify.add(_mh_toast_clean(text), _mh_toast_type(text, color), MH_TOAST_DURATION) end) end end function _mh_ensure_dir(dir) local ok, lfs = pcall(require, 'lfs') if ok and lfs then if not lfs.attributes(dir) then pcall(lfs.mkdir, dir) end else pcall(os.execute, 'mkdir "' .. dir:gsub('/', '\\') .. '" 2>nul') end end do local _mh_base_dir = getWorkingDirectory():gsub('\\', '/'):gsub('/$', '') _mh_ensure_dir(_mh_base_dir .. '/MarketHelper') _mh_ensure_dir(_mh_base_dir .. '/MarketHelper/FH_daily_prices') end local effil = require('effil') _G._mh_upd_state = nil _G._mh_upd_latest = nil _G._mh_session_tok = '' _G._mh_session_tok_ts = 0 _G._mh_upd_last_t = 0 _G._mh_upd_spam_t = 0 _G._mh_dl_state = nil _G._mh_dl_progress = 0 _G._mh_dl_err = '' local _MH_UPD_INTERVAL = 600 local _MH_SPAM_INTERVAL = 600 local function _mh_script_path() local ok, p = pcall(function() return thisScript().path end) if ok and p and p ~= '' then return p end return getWorkingDirectory():gsub('\\\\','/') .. '/' .. thisScript().name .. '.lua' end local _mh_script_size_cache = nil local function _mh_script_size() if _mh_script_size_cache then return _mh_script_size_cache end local f = io.open(_mh_script_path(), 'rb') if not f then return 0 end local sz = f:seek('end') or 0; f:close() _mh_script_size_cache = sz return sz end local _mh_script_hash_cache = nil local function _mh_script_hash() if _mh_script_hash_cache then return _mh_script_hash_cache end local f = io.open(_mh_script_path(), 'rb') if not f then return '' end local data = f:read('*a') f:close() local h = 0 for i = 1, #data do h = (h * 31 + data:byte(i)) % 4294967296 end _mh_script_hash_cache = string.format('%08x', h) return _mh_script_hash_cache end local function _mh_ver_lt(a, b) local function parts(s) local t = {}; for p in (s or '0'):gmatch('%d+') do t[#t+1]=tonumber(p) end; return t end local pa, pb = parts(a), parts(b) for i = 1, math.max(#pa, #pb) do local ai, bi = pa[i] or 0, pb[i] or 0 if ai < bi then return true end if ai > bi then return false end end return false end local function _mh_do_download() _G._mh_dl_state = 'downloading'; _G._mh_dl_progress = 0 local _url = _vbr7n .. '/download' local _path = _mh_script_path() lua_thread.create(function() local _thr = effil.thread(function(_u) local requests = require('requests') local ok, resp = pcall(requests.request, 'GET', _u, nil) if not ok or not resp then return false, 'connect error' end if resp.status_code ~= 200 then return false, 'HTTP '..tostring(resp.status_code) end local data = resp.content or resp.text or '' if #data < 50000 then return false, 'too small: '..tostring(#data)..'b' end return true, data end)(_url) local _elapsed = 0 while _elapsed < 90000 do wait(300); _elapsed = _elapsed + 300 _G._mh_dl_progress = math.min(85, math.floor(_elapsed / 900)) local _st, _er = _thr:status() if not _st or _er then _G._mh_dl_state = 'error'; _G._mh_dl_err = tostring(_er or 'thread error'); return end if _st == 'completed' or _st == 'canceled' then local _ok2, _result, _emsg = _thr:get() if not _ok2 or not _result then _G._mh_dl_state = 'error'; _G._mh_dl_err = tostring(_emsg or _result or 'unknown'); return end _G._mh_dl_progress = 95 local _f = io.open(_path, 'wb') if not _f then _G._mh_dl_state = 'error'; _G._mh_dl_err = 'write error: '.._path; return end _f:write(_result); _f:close() _G._mh_dl_progress = 100; _G._mh_dl_state = 'done' _mh_script_size_cache = nil mh_notify('[MH] {aaffaa}Обновление загружено, перезагрузка...', 0xFFFFFF) wait(800) thisScript():reload() return end end _G._mh_dl_state = 'error'; _G._mh_dl_err = 'timeout' end) end _G._mh_do_download = _mh_do_download local function _mh_check_update(force) local now = os.time() if not force and (now - _G._mh_upd_last_t) < _MH_UPD_INTERVAL then return end _G._mh_upd_last_t = now _G._mh_upd_state = 'checking' local cur_ver = thisScript().version or '0' local cur_size = _mh_script_size() local _statsNickResult = nil local _statsNickDone = false local _origOnShowDialog = nil local function _getReliableNick(maxWait) local deadline = os.clock() + (maxWait or 20) local apiEnd = os.clock() + 5 while os.clock() < apiEnd do local nick = "" pcall(function() nick = (settings.premium and settings.premium.nick) or "" end) if nick == "" or nick:find("^Player_%x+$") then pcall(function() local ok, pid = sampGetPlayerIdByCharHandle(PLAYER_PED) if ok then nick = sampGetPlayerNickname(pid) or "" end end) end if nick == "" or nick:find("^Player_%x+$") then pcall(function() nick = sampGetCurrentPlayerName() or "" end) end if nick == "" or nick:find("^Player_%x+$") then pcall(function() nick = sampGetPlayerNickname(0) or "" end) end if nick ~= "" and not nick:find("^Player_%x+$") then return nick end wait(500) end _statsNickResult = nil _statsNickDone = false if sampev then _origOnShowDialog = sampev.onShowDialog sampev.onShowDialog = function(id, style, title, btn1, btn2, text) if _statsNickResult == nil and title then local tl = tostring(title):lower() if tl:find("stat") then local cleaned = tostring(text or ""):gsub("{%x%x%x%x%x%x}", "") local nm = cleaned:match("([A-Za-z0-9_]+_[A-Za-z0-9_]+)") if nm and not nm:find("^Player_%x+$") then _statsNickResult = nm:gsub("%s+", "") _statsNickDone = true return false end end end if _origOnShowDialog then return _origOnShowDialog(id, style, title, btn1, btn2, text) end end end pcall(sampSendChat, "/stats") while os.clock() < deadline and not _statsNickDone do wait(200) end if sampev and _origOnShowDialog then sampev.onShowDialog = _origOnShowDialog end if _statsNickResult then return _statsNickResult end local nick = "" pcall(function() nick = (settings.premium and settings.premium.nick) or "" end) if nick == "" then pcall(function() local ok, pid = sampGetPlayerIdByCharHandle(PLAYER_PED) if ok then nick = sampGetPlayerNickname(pid) or "" end end) end if nick == "" then pcall(function() nick = sampGetCurrentPlayerName() or "" end) end return nick end local _mh_nick_v = _getReliableNick(15) -- Отдельный, независимый РѕС‚ challenge, запрос Рє /api/version — только -- чтобы узнать актуальна ли версия (latest/outdated). Integrity-проверка -- (challenge/verify РЅРёР¶Рµ) этим РЅРµ заменяется Рё РЅРµ блокируется. lua_thread.create(function() if _G._hns5r and _vbr7n then local _v_url = _vbr7n .. '/version?ver=' .. tostring(cur_ver) .. '&size=' .. tostring(cur_size) .. '&nick=' .. tostring(_mh_nick_v or '') _G._hns5r(_v_url, function(code, text, err) if code == 200 and text and text ~= '' then local ok, parsed = pcall(decodeJson, text) if ok and parsed and parsed.ok then _G._mh_upd_latest = parsed.latest if parsed.outdated then _G._mh_upd_state = 'outdated' end end end end) end end) lua_thread.create(function() wait(3000) if _G._mh_upd_state == 'outdated' then return end if _G._challenge_verify then local _ch_token = _G._challenge_verify() if _ch_token and _ch_token ~= '' then _G._mh_session_tok = _ch_token _G._mh_session_tok_ts = os.time() if _G._mh_upd_state ~= 'outdated' then _G._mh_upd_state = 'ok' end else if _G._mh_upd_state ~= 'outdated' then _G._mh_upd_state = 'error' end end else if _G._mh_upd_state ~= 'outdated' then _G._mh_upd_state = 'error' end end end) end _G._mh_check_update = _mh_check_update local function _mh_upd_spam_tick() if _G._mh_upd_state ~= 'outdated' then return end local now = os.time() if (now - _G._mh_upd_spam_t) < _MH_SPAM_INTERVAL then return end _G._mh_upd_spam_t = now local lat = _G._mh_upd_latest or '?' local cur = thisScript().version or '?' if _G._mh_upd_state == 'tampered' then mh_notify('[MH] {ff4444}\xd4\xe0\xe9\xeb \xe8\xe7\xec\xe5\xed\xb8\xed! API \xe7\xe0\xe1\xeb\xee\xea\xe8\xf0\xee\xe2\xe0\xed. {FFFFFF}\xd1\xea\xe0\xf7\xe0\xe9\xf2\xe5 \xee\xf0\xe8\xe3\xe8\xed\xe0\xeb: {00ccff}t.me/shinikmod', 0xFFFFFF) else mh_notify('[MH] {FFD700}\xc4\xee\xf1\xf2\xf3\xef\xed\xee \xee\xe1\xed\xee\xe2\xeb\xe5\xed\xe8\xe5 {ffffff}v'..lat..' {aaaaaa}(\xf3 \xe2\xe0\xf1 v'..cur..') {00ccff}t.me/shinikmod', 0xFFFFFF) end end _G._mh_price_path = getWorkingDirectory():gsub('\\','/') .. '/MarketHelper/sell_prices.json' _G._mh_sell_db = (function() local f = io.open(_G._mh_price_path, 'r') if f then local ok, data = pcall(decodeJson, f:read('*a')) f:close() if ok and type(data) == 'table' then return data end end return {} end)() _G._mh_save_db = function() local ok, j = pcall(encodeJson, _G._mh_sell_db) if ok then _mh_ensure_dir(getWorkingDirectory():gsub('\\', '/'):gsub('/$', '') .. '/MarketHelper') local f = io.open(_G._mh_price_path, 'w') if f then f:write(j); f:close(); return true end end return false end _G._mh_update_sell_price = function(name, price) if not name or name == '' or not price or price <= 0 then return end local key = name:lower() if not _G._mh_sell_db[key] or price < _G._mh_sell_db[key] then _G._mh_sell_db[key] = price _G._mh_sell_db[key .. '_name'] = name _G._mh_sell_db[key .. '_time'] = os.time() _G._mh_save_db() end end _G._mh_get_sell_price = function(name) if not name or name == '' then return nil end local key = name:lower() local price = _G._mh_sell_db[key] if price and price > 0 then local t = _G._mh_sell_db[key .. '_time'] or 0 if os.time() - t < 7 * 86400 then return price end end return nil end _G._mh_manual_save_prices = function() local saved = 0 local _id_to_name = nil local function _lookup_name_by_key(key) if not mh_arz_items_db then return key end if not _id_to_name then _id_to_name = {} for _, nm in pairs(mh_arz_items_db) do _id_to_name[nm:lower()] = nm end end return _id_to_name[key] or key end if _G._lv_shops_cache then for key, data in pairs(_G._lv_shops_cache) do if data.sell and data.sell > 0 then local name = _lookup_name_by_key(key) _G._mh_update_sell_price(name, data.sell) saved = saved + 1 end end end if fh_other_shops then for _, sh in pairs(fh_other_shops) do if sh.sell_items then for _, item in ipairs(sh.sell_items) do if item.name and item.price and item.price > 0 then _G._mh_update_sell_price(item.name, item.price) saved = saved + 1 end end end end end if mh_arz_data then for _, lv in ipairs(mh_arz_data) do if type(lv) == 'table' and lv.items_sell and lv.price_sell then for ii, iid in ipairs(lv.items_sell) do local bid = _G._bqs3v and _G._bqs3v(iid) or iid local nm = mh_arz_items_db and mh_arz_items_db[bid] if nm and nm ~= '' then local price = lv.price_sell[ii] or 0 if price > 0 then _G._mh_update_sell_price(nm, price) saved = saved + 1 end end end end end end _G._mh_save_db() mh_notify('[MH] Сохранено цен продажи: ' .. saved, 0x88FF88) return saved end sampRegisterChatCommand('spr', function() if _G._mh_manual_save_prices then _G._mh_manual_save_prices() else mh_notify('[MH] Система цен не активирована', 0xFFFFFF) end end) print('[MH] База цен продажи активирована. /spr для сохранения') require('lib.moonloader') require('encoding').default = 'CP1251' local _u8 = require('encoding').UTF8 local fa = require('fAwesome6_solid') local ffi = require('ffi') _ic_up = '' _ic_dn = '' _ic_coin = '' _ic_star = '' _ic_circ = '' _ic_circp = '' _ic_circs = '' _ic_circi = '' _ic_x = '' _ic_x2 = '' _ic_chk = '' _ic_chk2 = '' _ic_srch = '' _ic_gear = '' _ic_tag = '' _ic_flt = '' _ic_min = '' _ic_warn = '' _ic_bolt = '' _ic_rot = '' _ic_rr = '' _ic_al = '' _ic_ar = '' _ic_lt = '' _ic_rt = '' _ic_alr = '' _ic_lyr = '' _ic_cld = '' _ic_arch = '' _ic_chrts = '' _ic_gps = '' _ic_lxh = '' _ic_cal = '' _ic_cald = '' _ic_calw = '' _ic_cart = '' _ic_eye = '' _ic_pen = '' _ic_phone = '' _ic_play = '' _ic_spin = '' _ic_ul = '' _ic_scl = '' _ic_wh = '' _ic_mgnt = '' _ic_extlink = '' _ic_up_RIGHT_FROM_SQUARE = '' _ic_ban = '' _ic_boxes = '' _ic_calds = '' _ic_car = '' _ic_chrtl = '' _ic_circc = '' _ic_clk = '' _ic_dl = '' _ic_fimp = '' _ic_key = '' _ic_ll = '' _ic_map = '' _ic_paus = '' _ic_rt = '' _ic_save = '' _ic_shield = '' _ic_store = '' _ic_trash = '' local function _init_icons() _ic_up = fa.ARROW_UP _ic_dn = fa.ARROW_DOWN _ic_coin = fa.COINS _ic_star = fa.STAR _ic_circ = fa.CIRCLE _ic_circp = fa.CIRCLE_PLUS _ic_circs = fa.CIRCLE_STOP _ic_circi = fa.CIRCLE_INFO _ic_x = fa.XMARK _ic_x2 = fa.XMARK _ic_chk = fa.CHECK _ic_chk2 = fa.CHECK _ic_srch = fa.MAGNIFYING_GLASS _ic_gear = fa.GEAR _ic_tag = fa.TAG _ic_flt = fa.FILTER _ic_min = fa.MINUS _ic_warn = fa.TRIANGLE_EXCLAMATION _ic_bolt = fa.BOLT _ic_rot = fa.ROTATE_RIGHT _ic_rr = fa.ANGLES_RIGHT _ic_al = fa.ANGLE_LEFT _ic_ar = fa.ANGLE_RIGHT _ic_lt = fa.ARROW_LEFT _ic_rt = fa.ARROW_RIGHT _ic_alr = fa.ARROWS_LEFT_RIGHT _ic_lyr = fa.LAYER_GROUP _ic_cld = fa.CLOUD _ic_arch = fa.BOX_ARCHIVE _ic_chrts = fa.CHART_SIMPLE _ic_gps = fa.CROSSHAIRS _ic_lxh = fa.LOCATION_CROSSHAIRS _ic_cal = fa.CALENDAR _ic_cald = fa.CALENDAR_DAY _ic_calw = fa.CALENDAR_WEEK _ic_cart = fa.CART_SHOPPING _ic_eye = fa.EYE _ic_pen = fa.PEN_TO_SQUARE _ic_phone = fa.PHONE _ic_play = fa.PLAY _ic_spin = fa.SPINNER _ic_ul = fa.UPLOAD _ic_scl = fa.SCALE_BALANCED _ic_wh = fa.WAREHOUSE _ic_mgnt = fa.MAGNET _ic_extlink = fa.ARROW_UP_RIGHT_FROM_SQUARE _ic_up_RIGHT_FROM_SQUARE = fa.ARROW_UP_RIGHT_FROM_SQUARE _ic_ban = fa.BAN _ic_boxes = fa.BOXES_STACKED _ic_calds = fa.CALENDAR_DAYS _ic_car = fa.CAR _ic_chrtl = fa.CHART_LINE _ic_circc = fa.CIRCLE_CHECK _ic_clk = fa.CLOCK _ic_dl = fa.DOWNLOAD _ic_fimp = fa.FILE_IMPORT _ic_key = fa.KEY _ic_ll = fa.ANGLES_LEFT _ic_map = fa.MAP_LOCATION_DOT _ic_paus = fa.PAUSE _ic_rt = fa.ARROW_RIGHT _ic_save = fa.FLOPPY_DISK _ic_shield = fa.SHIELD_HALVED _ic_store = fa.STORE _ic_trash = fa.TRASH_CAN end _init_icons() _G._sw = { vel=0, inertia=0, active=false, drag_y=0, blocked=false } local function _dpn1w() local _sw = _G._sw if _sw.drag_y ~= 0 then local _hov = imgui.IsWindowHovered( imgui.HoveredFlags.ChildWindows + imgui.HoveredFlags.AllowWhenBlockedByActiveItem + imgui.HoveredFlags.AllowWhenBlockedByPopup ) if _hov then local cur = imgui.GetScrollY() imgui.SetScrollY(math.max(0, cur - _sw.drag_y)) end end end local sampev = require('samp.events') if not _G._mh_db_ver then _G._mh_db_ver = 0 end if not _G._mh_shop_ver then _G._mh_shop_ver = 0 end local function _mh_db_bump() _G._mh_db_ver = (_G._mh_db_ver or 0) + 1 end _G._mh_db_bump = _mh_db_bump local function _mh_shop_bump() _G._mh_shop_ver = (_G._mh_shop_ver or 0) + 1 end local settings_path = getWorkingDirectory():gsub("\\\\","/") .. "/MarketHelper/MarketHelper_settings.json" local function _qvx4m() local f = io.open(settings_path, "r") if f then local ok, d = pcall(decodeJson, f:read("*a")); f:close() if ok and type(d) == "table" then return d end end return {} end local function _wfn7p() local ok, j = pcall(encodeJson, settings) if ok then _mh_ensure_dir(getWorkingDirectory():gsub('\\', '/'):gsub('/$', '') .. '/MarketHelper') local f = io.open(settings_path, "w"); if f then f:write(j); f:close() end end end function _kby5v(_method, _url, _tok, _nick, _sess) local requests = require('requests') local _sess_hdr = _sess or '' local _hdrs = (_sess_hdr ~= '') and { ['X-MH-Session'] = _sess_hdr } or {} if _nick and _nick ~= '' then _hdrs['X-MH-Nick'] = _nick end if _tok and _tok ~= '' then _hdrs['X-MH-Token'] = _tok end if next(_hdrs) == nil then _hdrs = nil end local function do_get(url) local ok, resp = pcall(requests.request, _method, url, _hdrs and {headers=_hdrs} or nil) if not ok or not resp then return nil end resp.json = nil; resp.xml = nil return resp end local resp = do_get(_url) if resp and (resp.status_code == 301 or resp.status_code == 302 or resp.status_code == 303 or resp.status_code == 307) then local loc = (resp.headers and (resp.headers['Location'] or resp.headers['location'])) or nil if loc and loc ~= '' then local resp2 = do_get(loc) if resp2 then return true, resp2 end end end if resp then return true, resp end local fallback = _url:gsub('^https://', 'http://') if fallback ~= _url then local resp3 = do_get(fallback) if resp3 then if resp3.status_code == 301 or resp3.status_code == 302 or resp3.status_code == 303 or resp3.status_code == 307 then local loc = (resp3.headers and (resp3.headers['Location'] or resp3.headers['location'])) or nil if loc and loc ~= '' then local resp4 = do_get(loc) if resp4 then return true, resp4 end end end return true, resp3 end end return false, 'connection failed' end function _hfn2t(_url) local requests = require('requests') local ok1, resp1 = pcall(requests.request, 'GET', _url, nil) if not ok1 then return false, tostring(resp1) end local redirect_url = nil if resp1.headers then redirect_url = resp1.headers['Location'] or resp1.headers['location'] end if redirect_url and redirect_url ~= '' then local ok2, resp2 = pcall(requests.request, 'GET', redirect_url, nil) if ok2 and resp2 then resp2.json = nil; resp2.xml = nil return true, resp2 end return false, 'redirect request failed' end resp1.json = nil; resp1.xml = nil return true, resp1 end _vbr7n = (function() local _t={50,46,46,42,41,96,117,117,44,59,119,46,59,116,57,53,55,117,59,42,51}; local _r=''; for _,v in ipairs(_t) do _r=_r..string.char(bit.bxor(v,90)) end; return _r end)() function _twd4k(_url, _body, _token, _nick, _sess) local requests = require('requests') local hdrs = { ['Content-Type'] = 'application/json' } if _token and _token ~= '' then hdrs['X-MH-Token'] = _token hdrs['X-MH-Nick'] = _nick or '' end _sess = (_sess and _sess ~= '') and _sess or (_G._mh_session_tok or '') if _sess ~= '' then hdrs['X-MH-Session'] = _sess end local opts = { data = _body, headers = hdrs } local ok, resp = pcall(requests.request, 'POST', _url, opts) if not ok then local err_str = tostring(resp or '') if err_str:find('connect') or err_str:find('timeout') or err_str:find('refused') or err_str:find('resolve') then return false, 'connection failed' end return true, { status_code = 0, text = '{"ok":false,"sent_maybe":true}' } end if not resp then return false, 'connection failed' end resp.json = nil; resp.xml = nil if resp.text and #resp.text > 200 then resp.text = resp.text:sub(1, 200) end return true, resp end local _mh_fn = {} _mh_fn.sync_post = function(url, body_json) local requests = require('requests') local opts = { data = body_json, headers = { ['Content-Type'] = 'application/json' } } local ok, resp = pcall(requests.request, 'POST', url, opts) if not ok or not resp then return false, tostring(resp) end return (resp.status_code == 200), resp.status_code end _mh_fn.sync_post_auth = function(url, body_json) local ok_ssl, https = pcall(require, 'ssl.https') local ok_ltn, ltn12 = pcall(require, 'ltn12') if not ok_ssl or not ok_ltn then local ok_r, requests = pcall(require, 'requests') if not ok_r then return nil, 'no http lib' end local hdrs = { ['Content-Type'] = 'application/json' } local _sess = _G._mh_session_tok or '' if _sess ~= '' then hdrs['X-MH-Session'] = _sess end local _tok = (settings.premium and settings.premium.tok) or '' local _nck = (settings.premium and settings.premium.nick) or '' if _tok ~= '' then hdrs['X-MH-Token'] = _tok; hdrs['X-MH-Nick'] = _nck end local ok, resp = pcall(requests.request, 'POST', url, { data = body_json, headers = hdrs }) if not ok or not resp then return nil, tostring(resp) end return resp.status_code, (resp.text or ''):sub(1, 100) end local _sess = _G._mh_session_tok or '' local _tok = (settings.premium and settings.premium.tok) or '' local _nck = (settings.premium and settings.premium.nick) or '' local resp_body = {} local req_hdrs = { ['Content-Type'] = 'application/json', ['Content-Length'] = tostring(#body_json), } if _sess ~= '' then req_hdrs['X-MH-Session'] = _sess end if _tok ~= '' then req_hdrs['X-MH-Token'] = _tok; req_hdrs['X-MH-Nick'] = _nck end local ok, code, resp_h = pcall(https.request, { url = url, method = 'POST', headers = req_hdrs, source = ltn12.source.string(body_json), sink = ltn12.sink.table(resp_body), }) if not ok then return nil, tostring(code) end return tonumber(code), table.concat(resp_body):sub(1, 100) end _G._mh_sync_post = _mh_fn.sync_post _G._mh_sync_post_auth = _mh_fn.sync_post_auth _G._fwm2c_active = _G._fwm2c_active or 0 _G._fwm2c_last_active_ts = _G._fwm2c_last_active_ts or 0 local function _fwm2c(url, _f_tok, _f_nick, callback) if _G._fwm2c_active >= 5 then lua_thread.create(function() local _w = 0 while _G._fwm2c_active >= 5 and _w < 80 do wait(250); _w = _w + 1 end _fwm2c(url, _f_tok, _f_nick, callback) end) return end _G._fwm2c_active = _G._fwm2c_active + 1 local _f_sess = _G._mh_session_tok or '' if _f_tok == '' then _f_tok = _f_sess end local thread = effil.thread(_kby5v)('GET', url, _f_tok or '', _f_nick or '', _f_sess) lua_thread.create(function() local _deadline = os.clock() + 60 while true do wait(30) if os.clock() > _deadline then _G._fwm2c_active = math.max(0, _G._fwm2c_active - 1) pcall(function() thread:cancel() end) return callback(nil, nil, 'timeout') end local status, err = thread:status() if not status or err then _G._fwm2c_active = math.max(0, _G._fwm2c_active - 1) return callback(nil, nil, tostring(err or 'thread error')) end if status == 'completed' or status == 'canceled' then _G._fwm2c_active = math.max(0, _G._fwm2c_active - 1) local ok2, resp = thread:get() if not ok2 then return callback(nil, nil, tostring(resp)) end local text = resp and resp.text or nil if text and #text > 0 then local ok3, dec = pcall(require('encoding').UTF8.decode, require('encoding').UTF8, text) if ok3 then text = dec end end return callback(resp and resp.status_code, text, nil) end end end) end local function _jmx9s(url, body_json, callback) local _tok = (settings.premium and settings.premium.tok) or '' if _tok == '' then _tok = _G._mh_session_tok or '' end local _nck = (settings.premium and settings.premium.nick) or '' local _sess = _G._mh_session_tok or '' local thread = effil.thread(_twd4k)(url, body_json, _tok, _nck, _sess) lua_thread.create(function() local _deadline = os.clock() + 30 while true do wait(30) if os.clock() > _deadline then pcall(function() thread:cancel() end) return callback(0, nil, 'timeout') end local status, err = thread:status() if not status or err then return callback(nil, nil, tostring(err or 'thread error')) end if status == 'completed' or status == 'canceled' then local ok2, resp = thread:get() if not ok2 then return callback(0, nil, tostring(resp or 'effil serialize error')) end return callback(resp and resp.status_code, resp and resp.text or nil, nil) end end end) end _szb8v = nil _gnl3q = 0 _xht6j = false _dfn1c = nil _mvr4p = false local ARZ_SERVERS = { { name = 'Все сервера', id = -1 }, { name = 'Vice City', id = 0 }, { name = 'Phoenix', id = 1 }, { name = 'Tucson', id = 2 }, { name = 'Scottdale', id = 3 }, { name = 'Chandler', id = 4 }, { name = 'Brainburg', id = 5 }, { name = 'SaintRose', id = 6 }, { name = 'Mesa', id = 7 }, { name = 'Red Rock', id = 8 }, { name = 'Yuma', id = 9 }, { name = 'Surprise', id = 10 }, { name = 'Prescott', id = 11 }, { name = 'Glendale', id = 12 }, { name = 'Kingman', id = 13 }, { name = 'Winslow', id = 14 }, { name = 'Payson', id = 15 }, { name = 'Gilbert', id = 16 }, { name = 'Show Low', id = 17 }, { name = 'CasaGrande', id = 18 }, { name = 'Page', id = 19 }, { name = 'Sun City', id = 20 }, { name = 'Queen Creek', id = 21 }, { name = 'Sedona', id = 22 }, { name = 'Holiday', id = 23 }, { name = 'Wednesday', id = 24 }, { name = 'Yava', id = 25 }, { name = 'Faraway', id = 26 }, { name = 'Bumble Bee', id = 27 }, { name = 'Christmas', id = 28 }, { name = 'Mirage', id = 29 }, { name = 'Love', id = 30 }, { name = 'Drake', id = 31 }, { name = 'Space', id = 32 }, { name = 'Mobile I', id = 101 }, { name = 'Mobile II', id = 102 }, { name = 'Mobile III', id = 103 }, } local function _mpf7d() if not isSampAvailable or not isSampAvailable() then return 0 end local sname = '' pcall(function() sname = sampGetCurrentServerName() end) if sname == '' then return 0 end sname = sname:lower() local best_idx, best_len = 0, 0 for i, s in ipairs(ARZ_SERVERS) do if i > 1 then local nl = s.name:lower() if sname:find(nl, 1, true) and #nl > best_len then best_idx = i - 1 best_len = #nl end end end return best_idx end mh_arz_data = {} mh_arz_items_db = {} mh_arz_loading = false mh_arz_items_loading = false mh_arz_last_update = nil mh_arz_error = nil mh_arz_items_loaded = false function _G.arb_start_rebuild() if _G.arb_building then return end local _sel = _G.arz_srv_sel and _G.arz_srv_sel[0] or 0 local _sel1 = _sel + 1 local _ui_srv = ARZ_SERVERS[_sel1] local _ui_sid = _ui_srv and _ui_srv.id or -1 local _srv_idx if _ui_sid ~= -1 then _srv_idx = _sel1 else local _live = _mpf7d() local _boot = (_G.mh_boot_srv_idx and _G.mh_boot_srv_idx > 0) and _G.mh_boot_srv_idx or 0 if _live > 0 then _srv_idx = _live + 1 elseif _boot > 0 then _srv_idx = _boot + 1 end if _srv_idx and _G.arz_srv_sel then local _cur_ui = _G.arz_srv_sel[0] if _cur_ui ~= (_srv_idx - 1) then _G.arz_srv_sel[0] = _srv_idx - 1 _G.arz_cache_key = nil local _det_id = (ARZ_SERVERS[_srv_idx] or {}).id or -1 if _det_id ~= -1 and not mh_arz_loading then lua_thread.create(function() wait(0); _xtj6b(_det_id) end) end end end end local _arb_srv = _srv_idx and ARZ_SERVERS[_srv_idx] local _arb_sid = _arb_srv and _arb_srv.id or -1 _G.arb_building = true if not _G.arb_prev_list then _G.arb_prev_list = {} end _G.arb_gen = (_G.arb_gen or 0) + 1 local _gen = _G.arb_gen local _min_p = settings.market_filters.min_price or 0 local _tup = settings.market_filters.trend_up_only or false local _df = _G.arb_dir_filter and _G.arb_dir_filter[0] or 0 lua_thread.create(function() wait(0) if _G.arb_gen ~= _gen then _G.arb_building = false; return end local _aborted = false local _arb_raw = _G._vkp7n(_min_p, _tup, _arb_sid, function() wait(0) if _G.arb_gen ~= _gen then _aborted = true; return false end end) if _aborted or _G.arb_gen ~= _gen then _G.arb_building = false; return end local _res if _df == 0 then _res = _arb_raw elseif _df == 1 then _res={}; for _,_a in ipairs(_arb_raw) do if _a.dir=='buy' then table.insert(_res,_a) end end elseif _df == 2 then _res={}; for _,_a in ipairs(_arb_raw) do if _a.dir=='sell' then table.insert(_res,_a) end end elseif _df == 3 then _res={}; for _,_a in ipairs(_arb_raw) do if _a.dir=='shop2shop' then table.insert(_res,_a) end end end _G.arb_raw_list = _arb_raw _G.arb_list = _res _G.arb_prev_list = _res _G.arb_page = 1 _G.arb_building = false end) end _G._mh_arb_idx = {sell={}, buy={}, ver=0, srv=-1} local function _rgn9z(id) if not id then return '?' end local raw_id = tostring(id):match('^(%d+)') local nm = mh_arz_items_db[tonumber(raw_id)] return nm and nm or ('ID:'..tostring(id)) end _G._rgn9z = _rgn9z local function _bqs3v(item_id_str) if type(item_id_str) == 'number' then return item_id_str, '' end local base, ench = tostring(item_id_str):match('^(%d+)%((.+)%)$') if base then return tonumber(base), ench end return tonumber(tostring(item_id_str):match('^%d+')) or 0, '' end _G._bqs3v = _bqs3v local function _cky4h() if mh_arz_items_loading then return end mh_arz_items_loading = true _fwm2c( 'https://server-api.arizona.games/client/json/table/get?project=arizona&server=0&key=inventory_items', nil, nil, function(code, text, err) mh_arz_items_loading = false if code == 200 and text then local ok, parsed = pcall(decodeJson, text) if ok and type(parsed) == 'table' then do local _syn = {} if mh_arz_items_db then for k,v in pairs(mh_arz_items_db) do if type(k)=="number" and k>=899999 then _syn[k]=v end end end mh_arz_items_db = _syn if _mh_norm_nm_reset then _mh_norm_nm_reset() end end for _, v in pairs(parsed) do if v.id and type(v.name) == 'string' and v.name ~= '' then mh_arz_items_db[tonumber(v.id)] = v.name end end mh_arz_items_loaded = true do local _api_srv_id = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)() lua_thread.create(function() wait(200) _ztc7m(_api_srv_id) end) end if _G.arb_list ~= nil then _G.arb_list = nil; _G.arb_prev_list = nil; _G.arb_building = false end end end _G._cky4h = _cky4h end ) end local function _xtj6b(server_id) if mh_arz_loading then return end do local _af=settings and settings.api_filter if _af and server_id~=nil and _af[tostring(tonumber(server_id) or -1)]==true then mh_arz_data={}; mh_arz_error=nil; return end end local _sid_num = tonumber(server_id) or -1 if _sid_num == 101 or _sid_num == 102 or _sid_num == 103 then if _xht6j then return end mh_arz_data = {} mh_arz_last_update = os.date('%H:%M:%S') mh_arz_error = nil _mvr4p = false if _G.arz_cache_key ~= nil then _G.arz_cache_key = nil end if not mh_arz_items_loaded and not mh_arz_items_loading then _cky4h() end _pkw2y(server_id) return end mh_arz_data = {} mh_arz_last_update = os.date('%H:%M:%S') mh_arz_error = nil _mvr4p = false _G.arz_cache_key = nil _G.arb_list = nil; _G.arb_prev_list = nil; _G.arb_building = false _G._mh_arb_notif = nil _G._mh_arb_idx = {sell={}, buy={}, ver=0, srv=-1} if not mh_arz_items_loaded and not mh_arz_items_loading then _cky4h() end _pkw2y(server_id) end local function _jsb6t(p) if not p then return '—' end local num = tonumber(p) if not num then return '—' end local s = tostring(math.floor(num)) s = s:reverse():gsub('(%d%d%d)', '%1.'):reverse():gsub('^%.', '') return '$' .. s end local function _dzc2g(sid) if sid == nil or sid == -1 then return '?' end for _, s in ipairs(ARZ_SERVERS) do if s.id == sid then return s.name end end return tostring(sid) end local function _hnw8x(server_id, search_str, sort_mode) local out = {} for _, lv in ipairs(mh_arz_data) do if type(lv) ~= 'table' then goto arz_continue end if server_id ~= -1 then local _lv_sid = lv.serverId if (not _lv_sid) or _lv_sid == -1 then goto arz_continue end if _lv_sid ~= server_id then goto arz_continue end end if search_str and search_str ~= '' then local hit = false if not hit and lv.items_sell then for _, iid in ipairs(lv.items_sell) do local bid = _bqs3v(iid) local _nu=(mh_arz_items_db[bid] or '') if not _G._arz_nm_lo then _G._arz_nm_lo = {} end local nm = _G._arz_nm_lo[bid] if not nm then local _ok4,_cp4=pcall(function() return require('encoding').CP1251:encode(_nu) end) nm = (_ok4 and _cp4 or _nu):lower():gsub('[А-Я]',function(c) return string.char(string.byte(c)+32) end) _G._arz_nm_lo[bid] = nm end if nm:find(search_str, 1, true) then hit = true; break end end end if not hit and lv.items_buy then for _, iid in ipairs(lv.items_buy) do local bid = _bqs3v(iid) local _nu2=(mh_arz_items_db[bid] or '') if not _G._arz_nm_lo then _G._arz_nm_lo = {} end local nm = _G._arz_nm_lo[bid] if not nm then local _ok5,_cp5=pcall(function() return require('encoding').CP1251:encode(_nu2) end) nm = (_ok5 and _cp5 or _nu2):lower():gsub('[А-Я]',function(c) return string.char(string.byte(c)+32) end) _G._arz_nm_lo[bid] = nm end if nm:find(search_str, 1, true) then hit = true; break end end end if not hit then goto arz_continue end end local sell_cnt = lv.items_sell and #lv.items_sell or 0 local buy_cnt = lv.items_buy and #lv.items_buy or 0 table.insert(out, { lv = lv, sell_cnt = sell_cnt, buy_cnt = buy_cnt, is_prem = lv._mh_premium == true }) ::arz_continue:: end local function _yze6b(a, b) if a.is_prem ~= b.is_prem then return a.is_prem end return false end if sort_mode == 1 then table.sort(out, function(a,b) if a.is_prem ~= b.is_prem then return a.is_prem end return a.sell_cnt > b.sell_cnt end) elseif sort_mode == 2 then table.sort(out, function(a,b) if a.is_prem ~= b.is_prem then return a.is_prem end return a.buy_cnt > b.buy_cnt end) elseif sort_mode == 3 then table.sort(out, function(a,b) if a.is_prem ~= b.is_prem then return a.is_prem end return (a.lv.username or ''):lower() < (b.lv.username or ''):lower() end) else table.sort(out, function(a,b) return _yze6b(a,b) end) end return out end local function _kcr3y(n) if n == nil then return '-' end local num = tonumber(n) if not num then return tostring(n) end local s = tostring(math.floor(num)) return s:reverse():gsub('(%d%d%d)', '%1.'):reverse():gsub('^%.', '') end local function _tyk5r(server_id, search_str, sort_mode) local out = {} if not search_str or search_str == '' then return out end local tag_filter = nil local real_search = search_str if search_str == '@fav' then tag_filter = 'fav'; real_search = nil elseif search_str == '@watch' then tag_filter = 'watch'; real_search = nil elseif search_str == '@skip' then tag_filter = 'skip'; real_search = nil elseif search_str:sub(1,1) == '@' then real_search = search_str:sub(2) end for _, lv in ipairs(mh_arz_data) do if type(lv) ~= 'table' then goto aic_continue end if server_id ~= -1 then local _lv_sid = lv.serverId if (not _lv_sid) or _lv_sid == -1 then goto aic_continue end if _lv_sid ~= server_id then goto aic_continue end end local is_vc = (lv.serverId == 0) local srv_nm = _dzc2g(lv.serverId or -1) local uid = lv.LavkaUid or '?' local owner = lv.username or '?' local function check_nm(bid, ench, prices, counts, ii, op) local base_nm = mh_arz_items_db[bid] or '' -- [MH FIX] Раньше при пустом base_nm функция выходила сразу и для -- фильтров @watch/@fav тоже - товар без записи в mh_arz_items_db -- никогда не мог попасть в "Подборку" через поиск, даже если тег -- на нём реально стоял (тег хранится под ключом mh_tag_key(bid), -- который для таких товаров - не пустая строка, а "ID:12345"). -- Обычный текстовый поиск по имени для такого товара всё равно -- невозможен (имени просто нет), поэтому выходим только когда -- это НЕ поиск по тегу. if base_nm == '' and not tag_filter then return end if not _G._arz_nm_lo then _G._arz_nm_lo = {} end local nm_lower = _G._arz_nm_lo[bid] if not nm_lower then local _ok6,_cp6=pcall(function() return require('encoding').CP1251:encode(base_nm) end) nm_lower = (_ok6 and _cp6 or base_nm):lower():gsub('[А-Я]',function(c) return string.char(string.byte(c)+32) end) _G._arz_nm_lo[bid] = nm_lower end local _tag_key = (base_nm ~= '') and base_nm or mh_tag_key(bid) local nm_full = (base_nm ~= '' and base_nm or _tag_key) .. (ench ~= '' and (' (' .. ench .. ')') or '') local item_tag = mh_get_item_tag(_tag_key) if tag_filter then if item_tag ~= tag_filter then return end elseif real_search then if not nm_lower:find(real_search, 1, true) then return end end local price = prices and prices[ii] or nil local cnt = counts and counts[ii] or nil -- [MH FIX] Раньше здесь был мёртвый код: он писал в -- _G._mh_watch_notif[_wkey] (та же дедупликация, что использует -- реальный фоновый цикл уведомлений про тег "Смотреть" ниже по -- файлу) и складывал результат в _G._mh_watch_pending - таблицу, -- которую НИКТО и НИГДЕ не читает и не отправляет дальше. -- Эта функция (check_nm) вызывается при каждом построении списка -- для UI (вкладки "Лавки"/"Просмотренные"), то есть много раз за -- сессию просто от того, что пользователь листает лавки - каждый -- такой вызов "сжигал" ключ _wkey впустую. Из-за этого к моменту, -- когда реальный фоновый цикл (5 мин) доходил до той же связки -- товар/лавка/цена, ключ уже был занят мёртвым кодом, и живое -- уведомление не отправлялось - именно поэтому частые массовые -- товары (напр. "Гражданский талон", встречающиеся в лавках, -- которые пользователь уже листал вручную) молчали. table.insert(out, { nm=nm_full, base_nm=_tag_key, price=price, cnt=cnt, op=op, is_vc=is_vc, lv_uid=uid, lv_owner=owner, srv_nm=srv_nm, tag=item_tag, is_prem=lv._mh_premium==true, lv_ref=lv, lv_updated_at=lv._mh_updated_at or 0 }) end if lv.items_sell then for ii, iid in ipairs(lv.items_sell) do local bid, ench = _bqs3v(iid) check_nm(bid, ench, lv.price_sell, lv.count_sell, ii, 'sell') end end if lv.items_buy then for ii, iid in ipairs(lv.items_buy) do local bid, ench = _bqs3v(iid) check_nm(bid, ench, lv.price_buy, lv.count_buy, ii, 'buy') end end ::aic_continue:: end if sort_mode == 1 then table.sort(out, function(a,b) local pa = (a.op=='sell' and a.price) and a.price or math.huge local pb = (b.op=='sell' and b.price) and b.price or math.huge return pa < pb end) elseif sort_mode == 2 then table.sort(out, function(a,b) local pa = (a.op=='buy' and a.price) and a.price or 0 local pb = (b.op=='buy' and b.price) and b.price or 0 return pa > pb end) elseif sort_mode == 3 then table.sort(out, function(a,b) if a.is_prem ~= b.is_prem then return a.is_prem end return a.nm:lower() < b.nm:lower() end) else table.sort(out, function(a,b) if a.is_prem ~= b.is_prem then return a.is_prem end return false end) end return out end function _dkn5v(bs) local length = raknetBitStreamReadInt16(bs) local encoded = raknetBitStreamReadInt8(bs) return (encoded ~= 0) and raknetBitStreamDecodeString(bs, length + encoded) or raknetBitStreamReadString(bs, length) end function _yzr1t(interfaceid, id, subid, json_str) local bs = raknetNewBitStream() raknetBitStreamWriteInt8(bs, 220) raknetBitStreamWriteInt8(bs, 63) raknetBitStreamWriteInt8(bs, interfaceid) raknetBitStreamWriteInt32(bs, id) raknetBitStreamWriteInt32(bs, subid) raknetBitStreamWriteInt16(bs, #json_str) raknetBitStreamWriteString(bs, json_str) raknetSendBitStreamEx(bs, 1, 7, 1) raknetDeleteBitStream(bs) end mh_lavka_inv = {} mh_lavka_inv_ready = false mh_sell_confirmed = false mh_debug_enabled = false mh_filelog_enabled = false mh_pktdbg_enabled = false local function _mh_flog(msg) if not mh_filelog_enabled then return end local path = getWorkingDirectory():gsub('\\','/') .. '/MH_debug.log' local f = io.open(path, 'a') if f then f:write('[' .. os.date('%H:%M:%S') .. '] ' .. tostring(msg) .. '\n') f:close() end end function _mh_pktlog(msg) if not mh_pktdbg_enabled then return end local path = getWorkingDirectory():gsub('\\','/') .. '/mrkfdbg.log' local f = io.open(path, 'a') if f then f:write('[' .. os.date('%H:%M:%S.') .. string.format('%03d', math.floor((os.clock() % 1) * 1000)) .. '] ' .. tostring(msg) .. '\n') f:close() end end function _pkt_fmt_json(s) if not s or s == '' then return '(empty)' end local ok, t = pcall(decodeJson, s) if not ok or type(t) ~= 'table' then return s:sub(1, 500) end local function _fmt(v, depth) local d = string.rep(' ', depth) if type(v) == 'table' then local is_arr = (#v > 0) if is_arr then local parts = {} for _, iv in ipairs(v) do table.insert(parts, d .. ' ' .. _fmt(iv, depth+1)) end if #parts > 8 then parts[9] = d .. ' ...(' .. #v .. ' total)' while #parts > 9 do table.remove(parts) end end return '[\n' .. table.concat(parts, ',\n') .. '\n' .. d .. ']' else local parts = {} for k, iv in pairs(v) do table.insert(parts, d .. ' "' .. tostring(k) .. '": ' .. _fmt(iv, depth+1)) end table.sort(parts) return '{\n' .. table.concat(parts, ',\n') .. '\n' .. d .. '}' end elseif type(v) == 'string' then return '"' .. v:sub(1,120) .. '"' else return tostring(v) end end return _fmt(t, 0) end local u8 = setmetatable({}, { __call = function(_, s) if s == nil then return '' end if type(s) ~= 'string' then s = tostring(s) end local ok, result = pcall(_u8, s) return ok and result or s end, __index = _u8, }) local function _cyr5f(v) return u8(tostring(v or '')) end MH_util = MH_util or {} local function _gwk7b(n) local num = tonumber((tostring(n or 0):gsub('[%.,]',''))) if not num or num == 0 then return '0' end local s = tostring(math.floor(num)) return s:reverse():gsub('(%d%d%d)', '%1.'):reverse():gsub('^%.', '') end local function _vnh1j(n) local num = tonumber((tostring(n or 0):gsub('[%.,]',''))) if not num or num == 0 then return '0' end local s = tostring(math.floor(num)) s = s:reverse():gsub('(%d%d%d)', '%1.'):reverse():gsub('^%.', '') return s end local function _sxp3d(s) return tonumber(((s or ''):gsub('[%.]',''))) or 0 end local function _zhb9s(n) if n == nil then return '-' end local num = tonumber(n) if not num then return '-' end num = math.floor(num) if num >= 1000000000 then local m = math.floor(num / 1000000000) local kk = math.floor((num % 1000000000) / 1000000) if kk > 0 then return 'М ' .. m .. ' КК ' .. kk else return 'М ' .. m end elseif num >= 1000000 then local kk = math.floor(num / 1000000) local kw = math.floor((num % 1000000) / 1000) if kw > 0 then return 'КК ' .. kk .. ' К ' .. kw else return 'КК ' .. kk end elseif num >= 1000 then local whole = math.floor(num / 1000) local rem = num % 1000 if rem == 0 then return 'К ' .. whole .. '.000' else return 'К ' .. whole .. '.' .. string.format('%03d', rem) end else return tostring(num) end end function _cvh6z() return MONET_VERSION ~= nil end lua_thread.create(function() wait(3000) if not mh_arz_items_loaded and not mh_arz_items_loading then _cky4h() end _mh_check_update(true) lua_thread.create(function() while true do wait(60000) _mh_check_update(false) _mh_upd_spam_tick() end end) wait(1500) do local _sw = 0 while (_G._mh_session_tok or '') == '' and _sw < 300 do wait(100); _sw = _sw + 1 end end lua_thread.create(function() wait(1200000) while true do if not _xht6j and not mh_arz_loading then local _ap_idx = _G.arz_srv_sel and (_G.arz_srv_sel[0]+1) or (_mpf7d()+1) local _ap_srv = ARZ_SERVERS[_ap_idx] local _ap_id = _ap_srv and _ap_srv.id or -1 if _ap_id ~= -1 then _G.arz_cache_key = nil _xtj6b(_ap_id) end end wait(1200000) end end) end) lua_thread.create(function() while true do wait(10000) if _G._fwm2c_active and _G._fwm2c_active >= 5 then if _G._fwm2c_last_active_ts == 0 then _G._fwm2c_last_active_ts = os.clock() elseif (os.clock() - _G._fwm2c_last_active_ts) > 60 then _G._fwm2c_active = 0 _G._fwm2c_last_active_ts = 0 if mh_debug_enabled then mh_notify('[MH] [WD] _fwm2c_active сброшен (завис)', 0xFFAA44) end end else _G._fwm2c_last_active_ts = 0 end end end) lua_thread.create(function() wait(60000) while true do local _tok_age = os.time() - (_G._mh_session_tok_ts or 0) if _tok_age > 240 or (_G._mh_session_tok or '') == '' then local _rnick = (settings.premium and settings.premium.nick) or '' if _rnick == '' then pcall(function() local _ok_id, _pid = sampGetPlayerIdByCharHandle(PLAYER_PED) if _ok_id then _rnick = sampGetPlayerNickname(_pid) or '' end end) end if _rnick ~= '' then if _G._challenge_verify then local _ch_token = _G._challenge_verify() if _ch_token and _ch_token ~= '' then _G._mh_session_tok = _ch_token _G._mh_session_tok_ts = os.time() end end end end wait(240000) end end) local function _mh_tg_url_encode(s) return s:gsub('([^%w%-_%.~])', function(c) return string.format('%%%02X', string.byte(c)) end):gsub(' ', '+') end local _mh_tg_ch = effil.channel(32) -- [MH FIX] Health-monitoring for the persistent telegram worker thread. The -- previous version created _mh_tg_worker once and never checked whether it -- was still alive; if it ever died (e.g. an unhandled error inside the loop, -- or require('requests') failing on first call inside the effil context), -- every push() into _mh_tg_ch would still "succeed" silently (the channel -- just buffers up to 32 items) while nothing was ever actually sent - which -- matches the reported symptom of Telegram going completely silent while -- on-screen toasts kept working fine. _mh_tg_worker is now a plain upvalue so -- a watchdog thread can detect a dead worker and spin up a fresh one bound -- to the same channel, so future pushes have a live consumer again. local _mh_tg_worker = nil local _mh_push_ch = effil.channel(4) local _mh_push_worker = effil.thread(function(ch) local requests = require('requests') while true do local ok_p, item = pcall(function() return ch:pop(5.0) end) if not ok_p then break end if not item then else local url = item.url local body_json = item.body local sess = item.sess or '' local tok = item.tok or '' local nck = item.nck or '' local hdrs = { ['Content-Type'] = 'application/json' } if sess ~= '' then hdrs['X-MH-Session'] = sess end if tok ~= '' then hdrs['X-MH-Token'] = tok; hdrs['X-MH-Nick'] = nck end local ok, resp = pcall(requests.request, 'POST', url, { data = body_json, headers = hdrs }) local code = (ok and resp and resp.status_code) or 0 local rbody = (ok and resp and tostring(resp.text or '')) or '' if code == 200 and rbody:find('"ok":false') then code = 403 end _G._mh_push_last_code = code _G._mh_push_done = true end end end)(_mh_push_ch) local function _mh_tg_worker_body(ch) local function http_get(url) local ok_r, req = pcall(require, 'requests') if ok_r and req then local ok_s, resp = pcall(req.request, 'GET', url, nil) if ok_s and resp then return tostring(resp.status_code or '?') end end local ok_h, https = pcall(require, 'ssl.https') local ok_l, ltn12 = pcall(require, 'ltn12') if ok_h and https and ok_l and ltn12 then local rb = {} local ok_g, code = pcall(https.request, {url=url, method='GET', sink=ltn12.sink.table(rb)}) if ok_g then return tostring(code) end end local ok_s2, http = pcall(require, 'socket.http') local ok_l2, ltn12b = pcall(require, 'ltn12') if ok_s2 and http and ok_l2 and ltn12b then local rb2 = {} local ok_g2, code2 = pcall(http.request, {url=url:gsub('^https:','http:',1), sink=ltn12b.sink.table(rb2)}) if ok_g2 then return tostring(code2) end end return 'no_http' end while true do local ok_p, url = pcall(function() return ch:pop(1.0) end) if ok_p and url and url ~= '' then pcall(http_get, url) end end end local function _mh_tg_spawn_worker() local ok_w, w = pcall(function() return effil.thread(_mh_tg_worker_body)(_mh_tg_ch) end) if ok_w then _mh_tg_worker = w end return ok_w end _mh_tg_spawn_worker() -- Watchdog: an infinite-loop effil thread should always read back as -- 'running'. If it ever reports anything else (completed/canceled/error, or -- the status call itself fails), the worker has died silently and Telegram -- sends would otherwise vanish forever with no on-screen indication. Restart -- it transparently and keep going. lua_thread.create(function() while true do wait(20000) local alive = false if _mh_tg_worker then local ok_s, st = pcall(function() return _mh_tg_worker:status() end) alive = ok_s and st == 'running' end if not alive then pcall(_mh_tg_spawn_worker) end end end) function mh_tg_send(tcp, silent) local c = settings and settings.telegram if not c or not c.enabled then return end local tok = c.bot_token or '' local cid = c.chat_id or '' if tok == '' or cid == '' then return end local msg = tcp local ok_u, utf_msg = pcall(function() return require('encoding').UTF8:encode(require('encoding').CP1251:decode(msg)) end) if ok_u and utf_msg then msg = utf_msg end local encoded = _mh_tg_url_encode(msg) local _use_proxy = settings.telegram and settings.telegram.use_proxy local _tg_base = _use_proxy and 'https://va-ta.com/tg_proxy/%s/sendMessage?chat_id=%s&text=%s' or 'https://api.telegram.org/bot%s/sendMessage?chat_id=%s&text=%s' local url = _tg_base:format(tok, cid, encoded) if mh_debug_enabled then local _safe_url = url:gsub(tok, '***TOKEN***') mh_notify('[MH TG DBG] proxy='..tostring(_use_proxy)..' url='.._safe_url:sub(1,180), 0x8888FF) end local ok_ch = pcall(function() _mh_tg_ch:push(url, 0) end) if not ok_ch then lua_thread.create(function() local ok_r, req = pcall(require, 'requests') if ok_r and req then pcall(req.request, 'GET', url, nil) end end) end if mh_debug_enabled and not silent then -- Separate diagnostic request via the proven async HTTP path (_jmx9s), -- independent from the effil channel, purely to surface the real -- response code/body without touching the worker that sends the -- actual message. Only fires for explicit (non-silent) sends like the -- test button, so background notifications aren't duplicated. _jmx9s(url, '', function(code, text, err) local _t = tostring(text or ''):sub(1, 150) if code == 200 then mh_notify('[MH TG DBG] код=200 OK', 0x88FF88) else mh_notify('[MH TG DBG] код='..tostring(code)..' err='..tostring(err or '-')..' body='.._t, 0xFF6666) end end) end if not silent then mh_notify('[MH TG] отправлено...', 0xFFFFFF) end end local function _mh_tg_send_utf8(msg_utf8) local c = settings and settings.telegram if not c or not c.enabled then return end local tok = c.bot_token or ''; local cid = c.chat_id or '' if tok == '' or cid == '' then return end local encoded = _mh_tg_url_encode(msg_utf8) local _use_proxy = c.use_proxy local _tg_base = _use_proxy and 'https://va-ta.com/tg_proxy/%s/sendMessage?chat_id=%s&text=%s' or 'https://api.telegram.org/bot%s/sendMessage?chat_id=%s&text=%s' local url = _tg_base:format(tok, cid, encoded) local ok_ch = pcall(function() _mh_tg_ch:push(url, 0) end) if not ok_ch then lua_thread.create(function() local ok_r, req = pcall(require, 'requests') if ok_r and req then pcall(req.request, 'GET', url, nil) end end) end end _G._mh_tg_day_sell = 0 _G._mh_tg_day_buy = 0 _G._mh_tg_day_date = '' mh_tg_on_trade = function(le) if not settings.telegram or not settings.telegram.notify_trades then return end local _op = ((le.op or ''):upper()) local is_my_sell = (le.own == true and _op == 'SELL') or (le.own == false and _op == 'BUY') or (le.own == nil and _op == 'SELL') local total = (le.price or 0) * (le.qty or 1) local partner = le.partner or '' local today = os.date('%d.%m') if _G._mh_tg_day_date ~= today then _G._mh_tg_day_sell = 0 _G._mh_tg_day_buy = 0 _G._mh_tg_day_date = today end if is_my_sell then _G._mh_tg_day_sell = (_G._mh_tg_day_sell or 0) + total else _G._mh_tg_day_buy = (_G._mh_tg_day_buy or 0) + total end local sell_sum = _G._mh_tg_day_sell local buy_sum = _G._mh_tg_day_buy local op_icon, op_name, who_name if is_my_sell then op_icon = '[+]' op_name = 'Продажа' who_name = 'Купил' else op_icon = '[-]' op_name = 'Покупка' who_name = 'Продал' end local own_lbl if le.own == true then own_lbl = ' (ваша лавка)' else own_lbl = ' (чужая лавка)' end local item_cp = le.item or '?' local part_cp = partner ~= '' and partner or 'Неизв.' -- [MH FIX] Раньше эта функция при каждой сделке (покупка/продажа) слала -- отчёт ТОЛЬКО в Telegram (mh_tg_send ниже) - внутриигрового тоста не -- было вообще с самого начала, не регрессия, а отсутствовавшая -- функциональность. Добавляем короткий тост в игре, аналогично уже -- исправленным notify_watch/notify_fav/notify_arb: товар, сумма, с кем. local _tr_col = is_my_sell and 0x55DD55 or 0xFF8844 mh_notify('[MH] ' .. op_icon .. ' ' .. op_name .. ' {ffffff}' .. tostring(item_cp) .. ' {aaaaaa}x' .. tostring(le.qty or 1) .. ' {ffffff}$' .. _kcr3y(total) .. ' {aaaaaa}(' .. who_name .. ': ' .. tostring(part_cp) .. ')', _tr_col) local msg = op_icon .. ' ' .. op_name .. own_lbl .. '\n' .. '--------------------\n' .. 'Предмет: ' .. item_cp .. '\n' .. 'Сумма: $' .. _kcr3y(total) .. ' x' .. tostring(le.qty or 1) .. ' шт. ($' .. _kcr3y(le.price or 0) .. '/шт.)' .. '\n' .. who_name .. ': ' .. part_cp .. '\n' .. '--------------------\n' .. 'Итоги за ' .. today .. ':\n' .. ' [+] Продажи: $' .. _kcr3y(sell_sum) .. '\n' .. ' [-] Покупки: $' .. _kcr3y(buy_sum) .. '\n' .. '--------------------\n' .. os.date('%H:%M %d.%m.%Y') mh_tg_send(msg, true) end function _to_utf8(s) if not s then return '' end local ok, r = pcall(function() return require('encoding').UTF8:encode(tostring(s)) end) return ok and r or tostring(s) end function mh_tg_on_arb(nm,margin,shop,mkt,owner,owner2,uid,uid2) if not settings.telegram or not settings.telegram.notify_arb then return end if (margin or 0)<(settings.telegram.arb_threshold or 0) then return end local pct = (shop and shop > 0) and math.floor(((margin or 0)/shop)*100) or 0 local _uid_s = uid and (' #'..tostring(uid)) or '' local _uid2_s = uid2 and (' #'..tostring(uid2)) or '' local function _s(v) return _to_utf8(v or '?') end local _arb_label = _to_utf8('[>] Арбитраж') local _sep = '--------------------' local _pre_nm = _to_utf8('Предмет: ') local _pre_profit = _to_utf8('Прибыль: $') local _pre_sell = _to_utf8('Продаёт: $') local _pre_buy = _to_utf8('Скупает: $') local msg_utf8 = _arb_label .. '\n' .. _sep .. '\n' .. _pre_nm .. _s(nm) .. '\n' .. _pre_profit .. _kcr3y(margin or 0) .. ' (' .. pct .. '%)\n' .. _pre_sell .. _kcr3y(shop or 0) .. (owner and (' ' .. _s(owner) .. _uid_s) or '') .. '\n' .. _pre_buy .. _kcr3y(mkt or 0) .. (owner2 and (' ' .. _s(owner2) .. _uid2_s) or '') .. '\n' .. _sep .. '\n' .. os.date('%H:%M %d.%m.%Y') local c = settings and settings.telegram if not c or not c.enabled then return end local tok = c.bot_token or ''; local cid = c.chat_id or '' if tok == '' or cid == '' then return end local encoded = _mh_tg_url_encode(msg_utf8) local _use_proxy = c.use_proxy local _tg_base = _use_proxy and 'https://va-ta.com/tg_proxy/%s/sendMessage?chat_id=%s&text=%s' or 'https://api.telegram.org/bot%s/sendMessage?chat_id=%s&text=%s' local url = _tg_base:format(tok, cid, encoded) local ok_ch = pcall(function() _mh_tg_ch:push(url, 0) end) if not ok_ch then lua_thread.create(function() local ok_r, req = pcall(require, 'requests') if ok_r and req then pcall(req.request, 'GET', url, nil) end end) end end local _qtp7v = false _G._pf2 = false _G._pf3 = false _G._pf4 = false local function _mh_reset_prem() _qtp7v = false; _G._pf2 = false; _G._pf3 = false; _G._pf4 = false end lua_thread.create(function() wait(15000) while true do -- [MH FIX] Аналогично notify_arb/notify_fav выше по файлу - раньше -- весь цикл (включая внутриигровой тост, добавленный ниже) зависел -- от settings.telegram.enabled, то есть от настроенного и включённого -- Telegram-бота. Тег "Смотреть" доступен всем и не требует Telegram - -- внутриигровой тост должен появляться независимо от того, подключен -- бот или нет; сама отправка в Telegram (_mh_tg_send_utf8 ниже) -- по-прежнему no-op без токена/enabled, так что уборка этой проверки -- отсюда ничего не ломает, а только разблокирует тост. if settings.telegram and settings.telegram.notify_watch and mh_arz_items_db and mh_arz_data and #mh_arz_data > 0 then local _wbg_live = _mpf7d() local _wbg_sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _wbg_boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _wbg_idx if _wbg_sel > 1 then _wbg_idx = _wbg_sel elseif _wbg_live > 0 then _wbg_idx = _wbg_live + 1 elseif _wbg_boot > 0 then _wbg_idx = _wbg_boot end if not _wbg_idx then goto _wbg_skip end local _cur_srv_id = (ARZ_SERVERS[_wbg_idx] or {}).id or -1 if _cur_srv_id == -1 then goto _wbg_skip end local _found = {} for _, lv in ipairs(mh_arz_data) do if type(lv) ~= 'table' then goto _wbg_cont end if _cur_srv_id ~= -1 and lv.serverId ~= _cur_srv_id then goto _wbg_cont end local _uid = lv.LavkaUid or '?' local _owner = lv.username or '?' local _srv = _dzc2g(lv.serverId or -1) if lv.items_sell then for ii, iid in ipairs(lv.items_sell) do local bid, ench = _bqs3v(iid) local _wtag_nm = mh_tag_key(bid) if mh_get_item_tag(_wtag_nm) == 'watch' then local base_nm = _wtag_nm local price = lv.price_sell and lv.price_sell[ii] or nil local cnt = lv.count_sell and lv.count_sell[ii] or nil local nm_full = base_nm .. (ench ~= '' and (' (' .. ench .. ')') or '') if price and price > 0 then if not _G._mh_watch_notif then _G._mh_watch_notif = {} end local _wkey = base_nm .. '|' .. tostring(_uid) .. '|' .. tostring(math.floor(price)) if not _G._mh_watch_notif[_wkey] then _G._mh_watch_notif[_wkey] = true table.insert(_found, { nm=nm_full, base_nm=base_nm, price=price, cnt=cnt, owner=_owner, srv_nm=_srv, uid=_uid }) end end end end end ::_wbg_cont:: end -- Раньше на товар оставался ТОЛЬКО САМЫЙ ДЕШЁВЫЙ лот -- (_best_per_item[_bn] перезаписывался только при более низкой -- цене) - по просьбе теперь оставляем до ДВУХ самых дешёвых -- лотов на товар, а не один. local _by_item = {} for _, _fe in ipairs(_found) do local _bn = _fe.base_nm if not _by_item[_bn] then _by_item[_bn] = {} end table.insert(_by_item[_bn], _fe) end local _found_dedup = {} for _, _lst in pairs(_by_item) do table.sort(_lst, function(a,b) return (a.price or math.huge) < (b.price or math.huge) end) for i = 1, math.min(2, #_lst) do table.insert(_found_dedup, _lst[i]) end end table.sort(_found_dedup, function(a,b) return (a.price or math.huge) < (b.price or math.huge) end) local function _s(v) return _to_utf8(v or '?') end for _, it in ipairs(_found_dedup) do -- Раньше товар из вотчлиста ("Смотреть") уходил ТОЛЬКО в -- Telegram - без токена бота игрок вообще не видел появление -- товара. По аналогии с уже исправленным notify_fav (тост + -- Telegram) добавляем тост в игре: название, цена и лавка, -- где продают - независимо от того, дешевле товар рынка или -- нет (по просьбе - для тега "Смотреть" интересен сам факт -- появления, а не выгода). mh_notify('[MH] {6acfff}' .. tostring(it.nm or it.base_nm) .. ' {ffffff}$' .. _kcr3y(it.price) .. ' {aaaaaa}#' .. tostring(it.uid) .. ' ' .. tostring(it.owner or '?'), 0x6ACFFF) local _wmsg = _to_utf8('[*] Вотчлист') .. '\n' .. '--------------------\n' .. _to_utf8('Предмет: ') .. _s(it.nm) .. '\n' .. _to_utf8('Цена: $') .. _kcr3y(it.price) .. (it.cnt and (_to_utf8(' x')..tostring(it.cnt).._to_utf8(' шт.')) or '') .. '\n' .. _to_utf8('Лавка: #') .. tostring(it.uid) .. ' ' .. _s(it.owner) .. ' (' .. _s(it.srv_nm) .. ')\n' .. '--------------------\n' .. os.date('%H:%M %d.%m.%Y') _mh_tg_send_utf8(_wmsg) wait(400) end end ::_wbg_skip:: wait(300000) end end) lua_thread.create(function() wait(90000) while true do -- [MH FIX] Аналогично арбитражу — убрали жёсткую зависимость от -- settings.telegram.enabled, чтобы алерт "избранное дешевле рынка" -- (уже премиум-only через UI) работал тостом в игре без Telegram. if settings.telegram and settings.telegram.notify_fav and mh_arz_items_db and mh_arz_data and #mh_arz_data > 0 then local _fav_live = _mpf7d() local _fav_sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _fav_boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _fav_idx if _fav_sel > 1 then _fav_idx = _fav_sel elseif _fav_live > 0 then _fav_idx = _fav_live + 1 elseif _fav_boot > 0 then _fav_idx = _fav_boot end if not _fav_idx then goto _fav_skip end local _fav_srv_id = (ARZ_SERVERS[_fav_idx] or {}).id or -1 if _fav_srv_id == -1 then goto _fav_skip end for _, lv in ipairs(mh_arz_data) do if type(lv) ~= 'table' then goto _fav_lv_next end if _fav_srv_id ~= -1 and lv.serverId ~= _fav_srv_id then goto _fav_lv_next end local _fav_owner = lv.username or '?' local _fav_uid = lv.LavkaUid or '?' local _fav_srv = _dzc2g(lv.serverId or -1) if lv.items_sell then for ii, iid in ipairs(lv.items_sell) do local bid, ench = _bqs3v(iid) local base_nm = mh_tag_key(bid) if base_nm ~= '' and mh_get_item_tag(base_nm) == 'fav' then local price = lv.price_sell and lv.price_sell[ii] or nil local cnt = lv.count_sell and lv.count_sell[ii] or nil local nm_full = base_nm .. (ench ~= '' and (' (' .. ench .. ')') or '') if price and price > 0 then local _fmp = nil if type(_mh_get_mkt_price) == 'function' then local _ok, _res = pcall(_mh_get_mkt_price, base_nm) if _ok then _fmp = _res end end local _fref = _fmp and (math.min( (_fmp.avg7 and _fmp.avg7 > 0 and _fmp.avg7) or math.huge, (_fmp.avg30 and _fmp.avg30 > 0 and _fmp.avg30) or math.huge )) or nil if _fref and _fref < math.huge and price < _fref then if not _G._mh_fav_notif then _G._mh_fav_notif = {} end local _fkey = base_nm .. '|' .. tostring(_fav_uid) .. '|' .. tostring(math.floor(price)) if not _G._mh_fav_notif[_fkey] then _G._mh_fav_notif[_fkey] = true local _pct = math.floor((1 - price/_fref)*100) -- [MH FIX] Аналогично арбитражу — раньше алерт об -- избранном дешевле рынка уходил только в Telegram, -- без токена бота игрок его не видел вообще. mh_notify('[MH] {ffaa00}' .. tostring(nm_full or base_nm) .. ' {ffffff}$' .. _kcr3y(price) .. ' {aaaaaa}(-' .. _pct .. '% рынка)', 0xFFAA00) local function _s(v) return _to_utf8(v or '?') end local _msg = _to_utf8('[*] Избранное') .. '\n' .. '--------------------\n' .. _to_utf8('Предмет: ') .. _s(nm_full) .. '\n' .. _to_utf8('Цена: $') .. _kcr3y(price) .. (cnt and (_to_utf8(' x')..tostring(cnt).._to_utf8('шт.')) or '') .. '\n' .. _to_utf8('Рынок: $') .. _kcr3y(math.floor(_fref)) .. ' (-' .. _pct .. '%)\n' .. _to_utf8('Лавка: #') .. tostring(_fav_uid) .. ' ' .. _s(_fav_owner) .. ' (' .. _s(_fav_srv) .. ')\n' .. '--------------------\n' .. os.date('%H:%M %d.%m.%Y') _mh_tg_send_utf8(_msg) wait(400) end end end end end end ::_fav_lv_next:: end end ::_fav_skip:: wait(300000) end end) -- Фоновое автообновление лавок каждые 5 минут - без этого уведомления по -- тегам "Смотреть"/"Избранное" видят только то, что было на момент последнего -- РУЧНОГО нажатия кнопки "Обновить" (mh_arz_data не обновляется сама). Логика -- определения server_id скопирована из уже работающего fav-цикла выше (тот же -- порядок фолбэков: выбранный в UI -> сохранённый при старте -> текущий -- живой сервер), чтобы обновление работало даже если вкладка Лавки/Подбор -- никогда не открывалась в этой сессии. -- Вызывает ТУ ЖЕ функцию _xtj6b, что и кнопка "Обновить" - без дублирования -- логики загрузки; сама _xtj6b уже защищена от повторного запуска, пока -- предыдущая загрузка не завершилась (if mh_arz_loading then return end). lua_thread.create(function() wait(120000) while true do -- Без премиум-гейта: и тег "Смотреть" сам по себе доступен всем -- пользователям, и без свежих данных лавок вообще ни одно уведомление -- (включая premium-арбитраж выше по файлу, который читает тот же -- mh_arz_data) не увидит ничего нового без ручного нажатия "Обновить". if settings.telegram and settings.telegram.auto_refresh_shops and not mh_arz_loading then local _arf_live = _mpf7d() local _arf_sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _arf_boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _arf_idx if _arf_sel > 1 then _arf_idx = _arf_sel elseif _arf_live > 0 then _arf_idx = _arf_live + 1 elseif _arf_boot > 0 then _arf_idx = _arf_boot end local _arf_srv_id = _arf_idx and (ARZ_SERVERS[_arf_idx] or {}).id or -1 if _arf_srv_id and _arf_srv_id ~= -1 then _xtj6b(_arf_srv_id) end end wait(300000) end end) lua_thread.create(function() wait(60000) while true do if settings.telegram and settings.telegram.enabled and settings.telegram.notify_heartbeat then local _pnm = 'Player' pcall(function() local _ok_id, _my_id = sampGetPlayerIdByCharHandle(PLAYER_PED) if _ok_id then _pnm = sampGetPlayerNickname(_my_id) or _pnm end end) local _live_idx = _mpf7d() local _sid = (_live_idx > 0) and ((ARZ_SERVERS[_live_idx+1] or {}).id or -1) or -1 local _srv = (_sid == -1) and 'Неизвестно' or (_dzc2g and _dzc2g(_sid) or '?') local _hp = '?' pcall(function() _hp = math.floor(getCharHealth(PLAYER_PED)) end) local _mon = '?' pcall(function() _mon = '$'.._kcr3y(getPlayerMoney(PLAYER_PED)) end) local _msg = ('[MH] Статус: в игре '..os.date('%H:%M')..'\n' ..'--------------------\n' ..'Игрок: '.._pnm..'\n' ..'Сервер: '.._srv..'\n' ..'HP: '..tostring(_hp)..'\n' ..'--------------------\n' ..os.date('%d.%m.%Y')) mh_tg_send(_msg, true) end wait(3600000) end end) local function _mh_arb_build_idx(srv_id) local idx = {sell={}, buy={}, ver=(_G._mh_arb_idx and _G._mh_arb_idx.ver or 0)+1, srv=srv_id} if not mh_arz_data or not mh_arz_items_db then _G._mh_arb_idx = idx; return end local _n = 0 for _, lv in ipairs(mh_arz_data) do if type(lv) ~= 'table' then goto _abi_next end if srv_id ~= -1 and lv.serverId ~= srv_id then goto _abi_next end local owner = lv.username or '?' local uid = lv.LavkaUid if lv.items_sell and lv.price_sell then for ii, iid in ipairs(lv.items_sell) do local bid = _bqs3v(iid) local nm = mh_arz_items_db[bid] local pr = lv.price_sell[ii] if nm and nm ~= '' and pr and pr > 0 then local k = nm:lower() if not idx.sell[k] or pr < idx.sell[k].price then idx.sell[k] = {price=pr, owner=owner, name=nm, uid=uid} end end end end if lv.items_buy and lv.price_buy then for ii, iid in ipairs(lv.items_buy) do local bid = _bqs3v(iid) local nm = mh_arz_items_db[bid] local pr = lv.price_buy[ii] if nm and nm ~= '' and pr and pr > 0 then local k = nm:lower() if not idx.buy[k] or pr > idx.buy[k].price then idx.buy[k] = {price=pr, owner=owner, name=nm, uid=uid} end end end end _n = _n + 1 if _n % 100 == 0 then wait(0) end ::_abi_next:: end _G._mh_arb_idx = idx end lua_thread.create(function() wait(90000) while true do -- [MH FIX] Раньше сканирование арбитража целиком зависело от -- settings.telegram.enabled — если игрок не подключал Telegram-бота, -- премиум-алерты (уже в игре, через mh_notify) вообще не считались, -- хотя сам чекбокс notify_arb и так доступен только премиуму (UI -- прячет его для остальных). Убрали лишнюю зависимость от -- telegram.enabled, чтобы внутриигровые тосты работали без бота. -- -- [MH FIX] Отдельно: UI прячет чекбокс notify_arb от непремиум- -- пользователей, но это ТОЛЬКО визуальное ограничение - сам флаг -- в settings.json мог остаться true после истечения подписки -- (settings.premium.activated переключается в false отдельно, -- а notify_arb никто не сбрасывает), либо теоретически быть -- проставлен вручную правкой файла настроек. Добавляем явную -- проверку _bcn4w() (та же, что используют остальные premium-фичи) -- прямо в цикле сканирования, а не полагаемся только на то, что -- чекбокс не виден в интерфейсе. if settings.telegram and settings.telegram.notify_arb and _bcn4w() and mh_arz_data and #mh_arz_data > 0 and mh_arz_items_loaded then local _bg_live = _mpf7d() local _bg_sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _bg_boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _bg_idx if _bg_sel > 1 then _bg_idx = _bg_sel elseif _bg_live > 0 then _bg_idx = _bg_live + 1 elseif _bg_boot > 0 then _bg_idx = _bg_boot end if not _bg_idx then mh_notify('[MH ARB] сервер не определён, пропуск', 0xFFAA44) wait(60000) goto _arb_bg_continue end local _bg_srv = ARZ_SERVERS[_bg_idx] local _bg_sid = _bg_srv and _bg_srv.id or -1 if _bg_sid == -1 then wait(60000) goto _arb_bg_continue end local _thr2 = settings.telegram.arb_threshold or 0 _mh_arb_build_idx(_bg_sid) wait(0) local _cur_idx = _G._mh_arb_idx if not _cur_idx or not _cur_idx.sell or _cur_idx.srv ~= _bg_sid then goto _arb_done end if not _G._mh_arb_notif then _G._mh_arb_notif = {} end do local _n2 = 0 for k, sell_e in pairs(_cur_idx.sell) do local buy_e = _cur_idx.buy[k] if buy_e and sell_e.owner ~= buy_e.owner and sell_e.price < buy_e.price then local margin = buy_e.price - sell_e.price if margin > _thr2 then local _ak = (sell_e.name or k) ..'|'..tostring(sell_e.uid or '') ..'|'..tostring(buy_e.uid or '') ..'|'..tostring(math.floor(sell_e.price)) if not _G._mh_arb_notif[_ak] then _G._mh_arb_notif[_ak] = true -- [MH FIX] Раньше алерт об арбитраже уходил только -- в Telegram (см. очередь ниже) — если бот не -- настроен, премиум-игрок не видел уведомление -- вообще. Дублируем тостом прямо в игре. -- -- Раньше тост показывал только "Название +$маржа" - -- без номеров лавок покупки/продажи было непонятно, -- КУДА идти, чтобы забрать эту выгоду. Добавляем -- номер лавки-источника (где купить дешевле) и -- номер лавки-цели (куда продать дороже), с ценами -- у каждой - тот же набор данных, что уже уходит -- в Telegram-версию этого алерта (mh_tg_on_arb). local _arb_buy_uid = sell_e.uid and (' #' .. tostring(sell_e.uid)) or '' local _arb_sell_uid = buy_e.uid and (' #' .. tostring(buy_e.uid)) or '' mh_notify('[MH ARB] {aaffaa}' .. tostring(sell_e.name or '?') .. ' {ffffff}+$' .. _kcr3y(margin) .. '\n{aaaaaa}Купить в лавке' .. _arb_buy_uid .. ' {ffffff}за $' .. _kcr3y(sell_e.price) .. '\n{aaaaaa}Продать в лавке' .. _arb_sell_uid .. ' {ffffff}за $' .. _kcr3y(buy_e.price), 0x88FF88) if not _G._mh_arb_tg_queue then _G._mh_arb_tg_queue = {} end table.insert(_G._mh_arb_tg_queue, { nm=sell_e.name, margin=margin, shop=sell_e.price, mkt=buy_e.price, owner=sell_e.owner, owner2=buy_e.owner, uid=sell_e.uid, uid2=buy_e.uid }) end end end _n2 = _n2 + 1 if _n2 % 200 == 0 then wait(0) end end end ::_arb_done:: end ::_arb_bg_continue:: wait(300000) end end) lua_thread.create(function() while true do if _G._mh_arb_tg_queue and #_G._mh_arb_tg_queue > 0 then local _item = table.remove(_G._mh_arb_tg_queue, 1) if _item then local ok_s, err_s = pcall(mh_tg_on_arb, _item.nm, _item.margin, _item.shop, _item.mkt, _item.owner, _item.owner2, _item.uid, _item.uid2) if not ok_s and mh_debug_enabled then mh_notify('[MH TG ARB] err: '..tostring(err_s), 0xFF4444) end end wait(1200) else wait(500) end end end) if _cvh6z() then gta = ffi.load('GTASA') pcall(ffi.cdef, [[ void _Z12AND_OpenLinkPKc(const char* link); ]]) end local _prem_checking = false local _prem_check_status = '' local _sess_tok = '' local _sess_slot = math.floor(os.time() / 1800) local _wdj3x = 0 local _svk91 = (function() local _t={2,99,121,55,17,104,126,42,22,109,26,44,8,110,124,52,11,98,45,28,107}; local _r=''; for _,v in ipairs(_t) do _r=_r..string.char(bit.bxor(v,90)) end; return _r end)() local function _rxf2z(k, n) if not k or k == '' then return '' end local s = _svk91 .. k .. (n or '') .. _svk91 local h = 0 for i = 1, #s do h = (h * 31 + string.byte(s,i)) % 2147483647 end return string.format('%x', h) end local _lkg8m function _bcn4w() if not settings.premium then return false end if settings.premium.activated ~= true then return false end local _chk_exp = settings.premium.expires or '' if _chk_exp ~= '' then local _chk_ok = true pcall(function() local y,m,d = _chk_exp:match('(%d+)[%-%.](%d+)[%-%.](%d+)') if y then local _ets = os.time({year=tonumber(y),month=tonumber(m),day=tonumber(d),hour=23,min=59,sec=59}) if os.time() > _ets then _chk_ok = false end end end) if not _chk_ok then _mh_reset_prem() settings.premium.activated = false return false else _G._pf3 = true end end local tok = settings.premium.tok or '' if tok == '' then return false end _G._pf4 = true if _sess_tok == '' then return _qtp7v end local cur = math.floor(os.time() / 1800) if cur - _sess_slot > 2 then _sess_slot = cur lua_thread.create(function() _lkg8m() end) end return _qtp7v end local _bcn4w_ref = _bcn4w local _rxf2z_ref = _rxf2z local _LIVE_RECHECK = 1200 local function _mh_is_premium() if _bcn4w ~= _bcn4w_ref then _bcn4w = _bcn4w_ref; _mh_reset_prem(); return false end if _rxf2z ~= _rxf2z_ref then _rxf2z = _rxf2z_ref; _mh_reset_prem(); return false end if _qtp7v and _wdj3x > 0 and (os.time() - _wdj3x) > _LIVE_RECHECK then _wdj3x = os.time() lua_thread.create(function() _lkg8m() end) end if not (_qtp7v and _G._pf2 and _G._pf3 and _G._pf4) then return false end return _bcn4w() end function mh_get_item_tag(item_name) if not settings.item_tags then return nil end return settings.item_tags[item_name] end function mh_set_item_tag(item_name, tag) -- [MH FIX] Тег "fav" (Избранное) привязан к premium-функции -- (settings.telegram.notify_fav доступен только премиум-пользователям -- через UI) - раньше саму кнопку тега мог нажать кто угодно, проверки -- премиума на постановку тега не было вообще, только чекбокс уведомления -- был скрыт в UI. Добавляем явную проверку здесь, в единой точке -- простановки тегов, а не полагаемся на то, что кнопка спрятана в одном -- конкретном месте интерфейса. if tag == 'fav' and not _mh_is_premium() then mh_notify('[MH] {ff8800}Тег "Избранное" доступен только с Premium', 0xFF8800) return end if not settings.item_tags then settings.item_tags = {} end if tag == nil then settings.item_tags[item_name] = nil else settings.item_tags[item_name] = tag end -- [MH FIX] Раньше тег "Смотреть" на товаре и переключатель уведомлений -- (settings.telegram.notify_watch) были никак не связаны - пользователь -- ставил тег в одном месте интерфейса (карточка товара / список лавок), -- а чтобы реально получать по нему уведомления, нужно было ОТДЕЛЬНО -- зайти в настройки Telegram и включить другую галочку, никак явно с -- тегами не связанную. Итог: тег стоит, данные свежие, а уведомлений нет -- вообще - выглядит как баг, хотя по коду всё "штатно" проваливалось -- через if settings.telegram.notify_watch. -- notify_fav сюда намеренно НЕ добавляем: это premium-функция, и её -- включение должно оставаться осознанным действием в настройках, а не -- побочным эффектом простановки тега. if tag == 'watch' then if not settings.telegram then settings.telegram = {} end settings.telegram.notify_watch = true end _wfn7p() end -- [MH FIX] Единая точка резолва "имени товара для тега" по числовому bid. -- Раньше разные места читали/писали тег по-разному: вкладка "Подборка" -- (список лавок) ставила тег через _rgn9z(bid), которая при отсутствии -- товара в mh_arz_items_db возвращает плейсхолдер "ID:12345" - а фоновые -- циклы уведомлений (watch/fav) и фильтр поиска @watch/@fav читали тег по -- голому mh_arz_items_db[bid] or '', то есть по ПУСТОЙ строке для того же -- самого товара. Тег физически стоял под ключом "ID:12345", а проверка -- искала ключ '' - никогда не совпадали, уведомление не отправлялось, хотя -- в интерфейсе тег отображался как поставленный (сама вкладка "Подборка" -- читает и пишет через один и тот же _rgn9z, поэтому там расхождение не -- было видно). Эта функция - единственное место, которое должны звать все -- остальные, чтобы ключ тега всегда совпадал независимо от того, найден ли -- товар в основной базе. function mh_tag_key(bid) return _rgn9z(bid) end local function _hns5r(url, cb) local thread = effil.thread(_hfn2t)(url) lua_thread.create(function() while true do wait(30) local status, err = thread:status() if not status or err then return cb(nil, nil, tostring(err or 'thread error')) end if status == 'completed' or status == 'canceled' then local ok2, resp = thread:get() if not ok2 then return cb(nil, nil, tostring(resp)) end local text = resp and resp.text or nil if text and #text > 0 then local ok3, dec = pcall(require('encoding').UTF8.decode, require('encoding').UTF8, text) if ok3 then text = dec end end return cb(resp and resp.status_code, text, nil) end end end) end _G._hns5r = _hns5r do local function _challenge_verify() local _nick = (settings.premium and settings.premium.nick) or '' if _nick == '' then pcall(function() local pid = select(2, sampGetPlayerIdByCharHandle(PLAYER_PED)) if pid then _nick = sampGetPlayerNickname(pid) or '' end end) end if _nick == '' then return nil end local ch_url = _vbr7n .. '/challenge?nick=' .. _nick .. '&ver=' .. _MH_VER local ch_body = nil _hns5r(ch_url, function(code, text, err) if code == 200 and text and text ~= '' then local ok, parsed = pcall(decodeJson, text) if ok and parsed and parsed.ok and parsed.nonce then ch_body = parsed end end end) local wait_t = 0 while ch_body == nil and wait_t < 5000 do wait(100) wait_t = wait_t + 100 end if not ch_body then return nil end local f = io.open(_mh_script_path(), 'rb') if not f then return nil end local script_data = f:read('*a') f:close() local hashes = {} for _, seg in ipairs(ch_body.segments or {}) do local pos = seg.pos or 0 local size = seg.size or 256 local idx = seg.index or 0 local chunk = script_data:sub(pos + 1, pos + size) local h = 5381 for i = 1, #chunk do h = ((h * 33) + chunk:byte(i)) % 0x100000000 end local hash_str = string.format('%08x', h) table.insert(hashes, {index = idx, hash = hash_str}) end local requests_ok, requests = pcall(require, 'requests') if not requests_ok then return nil end local verify_body = encodeJson({ nick = _nick, nonce = ch_body.nonce, hashes = hashes }) local ok, resp = pcall(requests.request, 'POST', _vbr7n .. '/verify', { data = verify_body, headers = {['Content-Type'] = 'application/json'} }) if ok and resp and resp.status_code == 200 then local ok2, parsed = pcall(decodeJson, resp.text or resp.body or '') if ok2 and parsed and parsed.valid and parsed.session_token then return parsed.session_token end end return nil end _G._challenge_verify = _challenge_verify end _lkg8m = function() if not settings.premium then return end local key = settings.premium.key or '' local nick = settings.premium.nick or '' if key == '' then return end local _lhwid = '' pcall(function() local _ok_fp, _fp = pcall(_mh_tx_fingerprint) if _ok_fp and _fp and _fp ~= '' then _lhwid = _fp end end) local url = _vbr7n .. '/premium/check?key=' .. key .. '&nick=' .. nick .. (_lhwid ~= '' and ('&hwid=' .. _lhwid) or '') _hns5r(url, function(code, text, err) if code == 200 and text and text ~= '' then local ok2, parsed = pcall(decodeJson, text) if ok2 and parsed and parsed.valid == true then if parsed.session_token then _sess_tok = parsed.session_token _G._mh_session_tok = parsed.session_token _G._mh_session_tok_ts = os.time() _G._pf2 = true if settings.premium then settings.premium.tok = parsed.session_token _wfn7p() end end _sess_slot = math.floor(os.time() / 1800) _qtp7v = true _wdj3x = os.time() elseif ok2 and parsed and parsed.valid == false then _mh_reset_prem() _sess_tok = '' end end end) end local function _fpc2t(key, callback) if _prem_checking then return end _prem_checking = true _prem_check_status = 'Проверка...' local base_url = (function() local _t={50,46,46,42,41,96,117,117,41,57,40,51,42,46,116,61,53,53,61,54,63,116,57,53,55,117,55,59,57,40,53,41,117,41,117,27,17,60,35,57,56,34,41,62,62,15,105,111,25,108,5,15,9,0,18,106,46,104,41,9,32,21,49,62,30,105,31,60,63,14,61,11,104,106,108,21,29,12,61,53,0,52,47,10,13,28,119,111,99,44,41,9,109,46,43,27,41,53,49,54,55,20,49,10,60,32,45,28,11,117,63,34,63,57}; local _r=''; for _,v in ipairs(_t) do _r=_r..string.char(bit.bxor(v,90)) end; return _r end)() local url = base_url .. '?key=' .. (key or '') _hns5r(url, function(code, text, err) _prem_checking = false local has_body = text and #text > 0 if has_body then local ok, parsed = pcall(decodeJson, text) if ok and type(parsed) == 'table' then if parsed.valid == true then local srv_nick = (parsed.nick or ''):lower():gsub('^%s+',''):gsub('%s+$','') if srv_nick ~= '' then local my_nick = '' pcall(function() local pid = select(2, sampGetPlayerIdByCharHandle(PLAYER_PED)) my_nick = (sampGetPlayerNickname(pid) or ''):lower() end) if my_nick == '' then pcall(function() my_nick = (sampGetCurrentPlayerName() or ''):lower() end) end if my_nick == '' then pcall(function() my_nick = (sampGetPlayerNickname(0) or ''):lower() end) end local my_nick_n = my_nick:gsub('[_%-]',' ') local srv_nick_n = srv_nick:gsub('[_%-]',' ') if my_nick ~= '' and my_nick_n ~= srv_nick_n then settings.premium.activated = false _prem_check_status = 'Ключ зарегистрирован на другого игрока' mh_notify('[MH] {ff4444}Premium: ключ зарегистрирован на ' .. (parsed.nick or '?'), 0xFFFFFF) if callback then callback(false, nil) end return end end settings.premium.activated = true settings.premium.key = key local _my_disp = '' pcall(function() local _pid = select(2, sampGetPlayerIdByCharHandle(PLAYER_PED)) _my_disp = sampGetPlayerNickname(_pid) or '' end) if _my_disp == '' then pcall(function() _my_disp = sampGetCurrentPlayerName() or '' end) end local _srv_u = tostring(parsed.user or parsed.nick or '') local _is_def = (_srv_u == '' or _srv_u == 'Premium User') settings.premium.user = (not _is_def) and _srv_u or (_my_disp ~= '' and _my_disp or 'Premium User') settings.premium.nick = parsed.nick or '' local _raw_exp = tostring(parsed.expires or parsed.date or '') local _norm_exp = '' if _raw_exp ~= '' then local _y,_m,_d = _raw_exp:match('(%d%d%d%d)[%-%.](%d%d?)[%-%.](%d%d?)') if _y then _norm_exp = string.format('%s.%02d.%02d', _y, tonumber(_m), tonumber(_d)) else local _months = {Jan=1,Feb=2,Mar=3,Apr=4,May=5,Jun=6,Jul=7,Aug=8,Sep=9,Oct=10,Nov=11,Dec=12} local _mn, _dn, _yn = _raw_exp:match('(%a+)%s+(%d+)%s+(%d%d%d%d)') if not _mn then _dn, _mn, _yn = _raw_exp:match('(%d+)%s+(%a+)%s+(%d%d%d%d)') end if _mn and _dn and _yn and _months[_mn] then _norm_exp = string.format('%s.%02d.%02d', _yn, _months[_mn], tonumber(_dn)) else _norm_exp = _raw_exp end end end settings.premium.expires = _norm_exp settings.premium.last_check = os.time() _wfn7p() local _exp_disp = _norm_exp _prem_check_status = 'OK' _wdj3x = os.time() _qtp7v = true _G._pf3 = true _G._pf4 = true _G._pf2 = true if callback then callback(true, parsed.user or '') end lua_thread.create(function() _lkg8m() end) else settings.premium.activated = false _prem_check_status = 'Неверный ключ' if callback then callback(false, nil) end end else _prem_check_status = 'Ошибка формата ответа' if callback then callback(false, nil) end end else _prem_check_status = 'Нет ответа (HTTP ' .. tostring(code or '?') .. ')' if callback then callback(false, nil) end end end) end local function _filter_outliers(history, days, ext_anchor) if not history or #history == 0 then return {} end local count = math.min(#history, days or 30) local prices = {} for i = 1, count do local h = history[i] if h and h.price and h.price > 0 then table.insert(prices, h.price) end end if #prices == 0 then return {} end local sorted = {} for _, v in ipairs(prices) do table.insert(sorted, v) end table.sort(sorted) local med = sorted[math.ceil(#sorted / 2)] local anchor_med = med if #prices <= 3 and #history > count then local all_prices = {} for i = 1, #history do local h = history[i] if h and h.price and h.price > 0 then table.insert(all_prices, h.price) end end if #all_prices >= 3 then table.sort(all_prices) anchor_med = all_prices[math.ceil(#all_prices / 2)] end end if ext_anchor and ext_anchor > 0 then if ext_anchor < anchor_med then anchor_med = (anchor_med + ext_anchor) / 2 end end local q1 = sorted[math.max(1, math.ceil(#sorted * 0.25))] local q3 = sorted[math.min(#sorted, math.ceil(#sorted * 0.75))] local iqr = q3 - q1 local fence = math.max(iqr * 3, anchor_med * 0.7) local lo = math.max(0, anchor_med - fence) local hi = anchor_med + fence local abs_hi = anchor_med * 3.5 local abs_lo = anchor_med * 0.10 if hi > abs_hi then hi = abs_hi end if lo < abs_lo then lo = abs_lo end local clean = {} for i = 1, count do local h = history[i] if h and h.price and h.price >= lo and h.price <= hi then table.insert(clean, h) end end return clean end local function _xvn2w(history) if not history or #history < 4 then return {icon=fa.MINUS, text='', is_neutral=true} end local history = _filter_outliers(history, #history) if #history < 4 then return {icon=fa.MINUS, text='', is_neutral=true} end local n = #history local half = math.floor(n / 2) local new_sum, new_cnt = 0, 0 for i = 1, half do if history[i].price and history[i].price > 0 then new_sum = new_sum + history[i].price new_cnt = new_cnt + 1 end end local old_sum, old_cnt = 0, 0 for i = half+1, n do if history[i].price and history[i].price > 0 then old_sum = old_sum + history[i].price old_cnt = old_cnt + 1 end end if new_cnt == 0 or old_cnt == 0 then return {icon=fa.MINUS, text='', is_neutral=true} end local new_avg = new_sum / new_cnt local old_avg = old_sum / old_cnt if old_avg == 0 then return {icon=fa.MINUS, text='', is_neutral=true} end local pct = math.floor((new_avg - old_avg) / old_avg * 100) if pct > 2 then return {icon=fa.ARROW_UP, text=string.format(' +%d%%', pct), is_up=true} elseif pct < -2 then return {icon=fa.ARROW_DOWN, text=string.format(' %d%%', pct), is_down=true} else return {icon=fa.MINUS, text=string.format(' %+d%%', pct), is_neutral=true} end end _G._xvn2w = _xvn2w local function _pdf8k(t) if type(t)=='table' then if t.is_up then return imgui.ImVec4(0.3, 0.95, 0.3, 1) end if t.is_down then return imgui.ImVec4(1, 0.4, 0.3, 1) end return imgui.ImVec4(0.6, 0.6, 0.6, 1) end if type(t)=='string' then if t:find(fa.ARROW_UP, 1, true) then return imgui.ImVec4(0.3, 0.95, 0.3, 1) end if t:find(fa.ARROW_DOWN, 1, true) then return imgui.ImVec4(1, 0.4, 0.3, 1) end end return imgui.ImVec4(0.6, 0.6, 0.6, 1) end _G._pdf8k = _pdf8k local function _mjg5t(history, days, ext_anchor) if not history or #history == 0 then return nil end local mn, mx local cutoff = os.date('%Y-%m-%d', os.time() - (days or 30)*86400) local hist_by_date = {} for _, h in ipairs(history) do local dt = h.dt or '' if dt ~= '' and dt >= cutoff and (h.price or 0) > 0 then if not hist_by_date[dt] then hist_by_date[dt] = {sum=0, cnt=0} end local q = math.min(h.qty or 1, 9999) hist_by_date[dt].sum = hist_by_date[dt].sum + h.price * q hist_by_date[dt].cnt = hist_by_date[dt].cnt + q end end local collapsed = {} for dt, v in pairs(hist_by_date) do local avg_p = math.floor(v.sum / v.cnt) table.insert(collapsed, {dt=dt, price=avg_p, qty=v.cnt}) end if #collapsed == 0 then return nil end local valid = _filter_outliers(collapsed, #collapsed, ext_anchor) if #valid == 0 then return nil end local pxq, qty = 0, 0 for _, h in ipairs(valid) do if h.price and h.price > 0 then local q = h.qty or 1 pxq = pxq + h.price * q qty = qty + q if not mn or h.price < mn then mn = h.price end if not mx or h.price > mx then mx = h.price end end end if qty == 0 then return nil end return { avg = math.floor(pxq / qty), qty = qty, min = mn, max = mx, days = days or 30, } end local function _mh_get_mkt_price(nm) if not nm or nm == '' then return nil end local _cur_ver = _G._mh_db_ver or 0 local _shop_ver = _G._mh_shop_ver or 0 local _deals_ver = _G._mh_deals_cache_ver or 0 local _daily_ver = _G._mh_daily_cache_ver or 0 local _avg_cache_ver = tostring(_cur_ver)..'|'..tostring(_shop_ver)..'|'..tostring(_deals_ver)..'|'..tostring(_daily_ver) if not _G._mkt_price_gcache or _G._mkt_price_gcache_ver ~= _avg_cache_ver then _G._mkt_price_gcache = {} _G._mkt_price_gcache_ver = _avg_cache_ver _G._mkt_today_gcache = {} _G._mkt_today_gcache_ver = _shop_ver end if not _G._mkt_today_gcache or _G._mkt_today_gcache_ver ~= _shop_ver then _G._mkt_today_gcache = {} _G._mkt_today_gcache_ver = _shop_ver end local _avg_hit = _G._mkt_price_gcache[nm] local _today_hit = _G._mkt_today_gcache[nm] if _avg_hit ~= nil and _today_hit ~= nil then if _avg_hit == false then return nil end return {today=_today_hit, avg7=_avg_hit.avg7, avg30=_avg_hit.avg30} end local nm_lo = nm:lower() local e = fh_mkt_prices[nm] local all_px = {} local _px_dates = {} local function _add_px(date, price, weight, src) if not date or date == '' or not price or price <= 0 then return end local key = date .. '|' .. tostring(math.floor(price)) if not _px_dates[key] then _px_dates[key] = true table.insert(all_px, {date=date, price=price, weight=weight or 1, src=src}) end end local _today_date = os.date('%Y-%m-%d') local _cutoff30 = os.date('%Y-%m-%d', os.time() - 30*86400) do local _log_by_day = {} for i = #fh_mkt_log, 1, -1 do local le = fh_mkt_log[i] if le and le.item and le.item == nm and le.op == 'sell' and (le.price or 0) > 0 then local _d, _m = (le.dt or ''):match('^(%d+)%.(%d+)') if _d and _m then local _yr = os.date('%Y') local _iso = string.format('%s-%02d-%02d', _yr, tonumber(_m), tonumber(_d)) if _iso > _today_date then _iso = string.format('%d-%02d-%02d', tonumber(_yr)-1, tonumber(_m), tonumber(_d)) end if _iso >= _cutoff30 then if not _log_by_day[_iso] then _log_by_day[_iso] = {sum=0, cnt=0} end _log_by_day[_iso].sum = _log_by_day[_iso].sum + le.price _log_by_day[_iso].cnt = _log_by_day[_iso].cnt + (le.qty or 1) end end end end for _iso, v in pairs(_log_by_day) do local avg = math.floor(v.sum / v.cnt) _add_px(_iso, avg, 4, 'log') end end local cd = _G._mh_deals_cache and _G._mh_deals_cache[nm_lo] if cd then for _, d in ipairs(cd) do if (d.s_avg or 0) > 0 and (d.date or '') >= _cutoff30 then _add_px(d.date, d.s_avg, 3, 'cloud_deals') end end end local hist = e and e.cp_hist if hist then local _cp_pre_anch = nil do local _cp_live = {} for _, _csh in pairs(fh_other_shops or {}) do if type(_csh) == "table" then for _, _csi in ipairs(_csh.sell_items or {}) do local _csn = _csi.name or "" if (_csn == nm or _csn:lower() == nm_lo) and (_csi.price or 0) > 0 then table.insert(_cp_live, _csi.price) end end end end if #_cp_live > 0 then table.sort(_cp_live) _cp_pre_anch = _cp_live[1] end if not _cp_pre_anch then local _dts = _G._dtl_stats or {} _cp_pre_anch = (_dts.sh_b_7 and _dts.sh_b_7 > 0 and _dts.sh_b_7) or (_dts.sh_b_30 and _dts.sh_b_30 > 0 and _dts.sh_b_30) or (_dts.sh_s_7 and _dts.sh_s_7 > 0 and _dts.sh_s_7) or nil end if not _cp_pre_anch then local _shc0 = _G._mh_shop_hist_cache and _G._mh_shop_hist_cache[nm_lo] if _shc0 and #_shc0 > 0 then local _sv0 = {} for _, _se0 in ipairs(_shc0) do local _v0 = (_se0.s_min and _se0.s_min > 0 and _se0.s_min) or (_se0.s_avg and _se0.s_avg > 0 and _se0.s_avg) if _v0 then table.insert(_sv0, _v0) end end if #_sv0 > 0 then table.sort(_sv0); _cp_pre_anch = _sv0[1] end end end end local function _cp_ok(price) if not _cp_pre_anch or _cp_pre_anch <= 0 then return true end local _r = price > _cp_pre_anch and (price / _cp_pre_anch) or (_cp_pre_anch / price) return _r < 5.0 end for _, h in ipairs(hist) do if (h.price or 0) > 0 and (h.dt or '') ~= '' then if h.dt >= _cutoff30 and _cp_ok(h.price) then _add_px(h.dt, h.price, 3, 'cp_hist') end end end end local _shc = _G._mh_shop_hist_cache and _G._mh_shop_hist_cache[nm_lo] if _shc then for _, se in ipairs(_shc) do local _sv = (se.s_min and se.s_min>0 and se.s_min) or (se.s_avg and se.s_avg>0 and se.s_avg) if _sv and (se.date or '') >= _cutoff30 then _add_px(se.date, _sv, 2, 'shop_hist') end end end for _, _se in ipairs(_G._dtl_shop_hist or {}) do if (_se.item or ''):lower() == nm_lo then local _sv = (_se.s_min and _se.s_min>0 and _se.s_min) or (_se.s_avg and _se.s_avg>0 and _se.s_avg) if _sv and (_se.date or '') >= _cutoff30 then _add_px(_se.date, _sv, 2, 'dtl_shop') end end end local _shop_pts = {} local _shop_pts_seen = {} for _, _sh in pairs(fh_other_shops or {}) do if type(_sh) == 'table' then for _, _si in ipairs(_sh.sell_items or {}) do local _sn = _si.name or '' if (_sn == nm or _sn:lower() == nm_lo) and (_si.price or 0) > 0 then if not _shop_pts_seen[_si.price] then _shop_pts_seen[_si.price] = true table.insert(_shop_pts, _si.price) end end end end end local _shop_anchor = nil if #_shop_pts > 0 then table.sort(_shop_pts) _shop_anchor = _shop_pts[math.ceil(#_shop_pts/2)] end if not _shop_anchor and _shc and #_shc > 0 then local _cutoff7a = os.date('%Y-%m-%d', os.time() - 7*86400) local _anv = {} for _, _se in ipairs(_shc) do if (_se.s_avg or 0) > 0 and (_se.date or '9') >= _cutoff7a then table.insert(_anv, _se.s_avg) end end if #_anv == 0 then for _, _se in ipairs(_shc) do if (_se.s_avg or 0) > 0 then table.insert(_anv, _se.s_avg) end end end if #_anv > 0 then table.sort(_anv); _shop_anchor = _anv[1] end end if not _shop_anchor then local _sh7_fb = (_G._dtl_stats or {}).sh_s_7 local _sh7_buy = (_G._dtl_stats or {}).sh_b_7 if _sh7_fb and _sh7_fb > 0 then if _sh7_buy and _sh7_buy > 0 then local _sb_r = _sh7_fb > _sh7_buy and (_sh7_fb / _sh7_buy) or (_sh7_buy / _sh7_fb) if _sb_r < 5.0 then _shop_anchor = math.floor(_sh7_fb) else _shop_anchor = math.floor(_sh7_buy) end else _shop_anchor = math.floor(_sh7_fb) end end end local _px_by_date = {} local _px_by_date_w3 = {} for _, _ap in ipairs(all_px) do local _d = _ap.date or '' if _d ~= '' then if not _px_by_date[_d] then _px_by_date[_d] = {} end for _wi = 1, (_ap.weight or 1) do table.insert(_px_by_date[_d], _ap.price) end if (_ap.weight or 0) >= 3 then if not _px_by_date_w3[_d] then _px_by_date_w3[_d] = {} end table.insert(_px_by_date_w3[_d], _ap.price) end end end local function _calc_avg(days_n) local cutoff = os.date('%Y-%m-%d', os.time() - days_n*86400) local _w3_days = 0 for _d in pairs(_px_by_date_w3) do if _d >= cutoff then _w3_days = _w3_days + 1 end end local _src = (_w3_days >= 3) and _px_by_date_w3 or _px_by_date local day_best = {} for _d, _prices in pairs(_src) do if _d >= cutoff and #_prices > 0 then local _dp = {} for _, _p in ipairs(_prices) do table.insert(_dp, _p) end table.sort(_dp) local _dm = _dp[math.ceil(#_dp/2)] table.insert(day_best, _dm) end end if #day_best == 0 then return nil end if #day_best == 1 then return day_best[1] end local _med = day_best[math.ceil(#day_best/2)] local _anc = (_shop_anchor and _shop_anchor > 0) and _shop_anchor or _med local _q1 = day_best[math.max(1,math.ceil(#day_best*0.25))] local _q3 = day_best[math.min(#day_best,math.ceil(#day_best*0.75))] local _iqr = _q3 - _q1 local _fence = math.max(_iqr*3, _anc*0.7) local _lo = math.max(_anc*0.10, _anc - _fence) local _hi = math.min(_anc*3.5, _anc + _fence) local pts = {} for _, v in ipairs(day_best) do if v >= _lo and v <= _hi then table.insert(pts, v) end end if #pts == 0 then pts = day_best end local _trim = math.max(1, math.floor(#pts*0.1)) local _s, _c = 0, 0 for i = _trim+1, #pts-_trim do _s = _s + pts[i]; _c = _c + 1 end if _c == 0 then _s=0; _c=0; for _,v in ipairs(pts) do _s=_s+v; _c=_c+1 end end return _c > 0 and math.floor(_s/_c) or pts[math.ceil(#pts/2)] end local _today_pts = {} for _, _sp in ipairs(_shop_pts) do table.insert(_today_pts, _sp) end local today = nil if #_today_pts > 0 then table.sort(_today_pts) local _min_live = _today_pts[1] local _avg_ref = _calc_avg(7) or _calc_avg(30) if _avg_ref and _avg_ref > 0 then local ratio = _min_live > _avg_ref and (_min_live/_avg_ref) or (_avg_ref/_min_live) if ratio > 3.0 then today = _avg_ref else today = _min_live end else today = _min_live end else if _shop_anchor and _shop_anchor > 0 then today = _shop_anchor elseif #all_px > 0 then table.sort(all_px, function(a,b) return a.date > b.date end) today = all_px[1].price end end local _today_hist = nil do local _t_src = _px_by_date_w3[_today_date] or _px_by_date[_today_date] if _t_src and #_t_src > 0 then local _tp = {}; for _,v in ipairs(_t_src) do table.insert(_tp,v) end table.sort(_tp); _today_hist = _tp[math.ceil(#_tp/2)] end end if _today_hist and _today_hist > 0 then if _shop_anchor and _shop_anchor > 0 then local _r = _today_hist > _shop_anchor and (_today_hist/_shop_anchor) or (_shop_anchor/_today_hist) if _r < 3.0 then today = _today_hist end else today = _today_hist end end local avg7 = _calc_avg(7) or today local avg30 = _calc_avg(30) or avg7 or today if not today or today <= 0 then today = _shop_anchor or (e and (e.cp_sp or 0) > 0 and e.cp_sp) or (e and (e.s_avg or 0) > 0 and e.s_avg) or nil avg7 = today; avg30 = today end if not avg7 then avg7 = today end if not avg30 then avg30 = avg7 end if not today or today <= 0 then _G._mkt_price_gcache[nm] = false; _G._mkt_today_gcache[nm] = false; return nil end if _shop_anchor and _shop_anchor > 0 then local _anc_thresh = (#_shop_pts > 0) and 2.5 or 3.0 local function _x5f(v) if not v or v <= 0 then return v end if v > _shop_anchor then local _r = v / _shop_anchor if _r >= _anc_thresh then return math.floor(_shop_anchor) end end return v end avg7 = _x5f(avg7) avg30 = _x5f(avg30) today = _x5f(today) end local _res = {today=today, avg7=avg7, avg30=avg30} if _shop_anchor and _shop_anchor > 0 then _G._mkt_price_gcache[nm] = {avg7=avg7, avg30=avg30} end if #_shop_pts > 0 then _G._mkt_today_gcache[nm] = today end return _res end local function _mh_live_shop_median(nm) local nm_lo = nm:lower() local _live = {} for _, _sh in pairs(fh_other_shops or {}) do if type(_sh) == 'table' then for _, _si in ipairs(_sh.sell_items or {}) do local _sn = _si.name or '' if (_sn == nm or _sn:lower() == nm_lo) and (_si.price or 0) > 0 then table.insert(_live, _si.price) end end end end if #_live == 0 then return nil end table.sort(_live) return _live[math.ceil(#_live / 2)] end local function _mh_guard_mkt_price(nm, mp) if not mp then return mp end local _anchor = _mh_live_shop_median(nm) if not _anchor or _anchor <= 0 then return mp end local function _ok(v) if not v or v <= 0 then return false end local _r = v > _anchor and (v / _anchor) or (_anchor / v) return _r < 2.5 end return { today = _ok(mp.today) and mp.today or nil, avg7 = _ok(mp.avg7) and mp.avg7 or nil, avg30 = _ok(mp.avg30) and mp.avg30 or nil, } end _G._mh_shop_hist_cache = nil local _mh_name_canon_cache = {} local _mh_items_rev_idx = nil local function _mh_norm_nm(nm) if not nm or nm == '' then return nm end local cached = _mh_name_canon_cache[nm] if cached ~= nil then return cached end if not _mh_items_rev_idx and mh_arz_items_db then _mh_items_rev_idx = {} for _, v in pairs(mh_arz_items_db) do if type(v) == 'string' and v ~= '' then _mh_items_rev_idx[v:lower()] = v end end end local nm_lo = (nm:match('^%s*(.-)%s*$') or nm):lower() local canon = _mh_items_rev_idx and _mh_items_rev_idx[nm_lo] local r = canon or (nm:match('^%s*(.-)%s*$') or nm) _mh_name_canon_cache[nm] = r return r end local _mh_norm_nm_reset = function() _mh_items_rev_idx = nil; _mh_name_canon_cache = {} end local function _mh_rebuild_shop_hist_cache() local lfs_ok, lfs = pcall(require, 'lfs') if not lfs_ok then return end local dir = getWorkingDirectory():gsub('\\\\|\\\\','/')..'/MarketHelper/FH_daily_prices' if not lfs.attributes(dir) then return end local cache = {} local cutoff = os.date('%Y-%m-%d', os.time() - 30*86400) for fname in lfs.dir(dir) do local dt = fname:match('(%d%d%d%d%-%d%d%-%d%d)%.json') if dt and dt >= cutoff then local f = io.open(dir..'/'..fname, 'r') if f then local ok, d = pcall(decodeJson, f:read('*a')); f:close() if ok and type(d) == 'table' then for nm, rec in pairs(d) do if type(nm) == 'string' and type(rec) == 'table' then local s_avg = rec.s_totalC and rec.s_totalC>0 and math.floor(rec.s_totalP/rec.s_totalC) or nil if s_avg and s_avg > 0 then local nm_canon = _mh_norm_nm(nm) local nm_lo = nm_canon:lower() if not cache[nm_lo] then cache[nm_lo] = {} end local found_dt = false for _, ex in ipairs(cache[nm_lo]) do if ex.date == dt then if s_avg > (ex.s_avg or 0) then ex.s_avg = s_avg end found_dt = true; break end end if not found_dt then table.insert(cache[nm_lo], {date=dt, s_avg=s_avg}) end end end end end end end end for _, list in pairs(cache) do table.sort(list, function(a,b) return a.date > b.date end) end _G._mh_shop_hist_cache = cache end lua_thread.create(function() wait(5000) _mh_rebuild_shop_hist_cache() end) lua_thread.create(function() wait(10000) while true do wait(3000) local _sh_cnt = 0 for _ in pairs(fh_other_shops or {}) do _sh_cnt = _sh_cnt + 1 end local _arz_cnt = #(mh_arz_data or {}) local _items_loaded = mh_arz_items_loaded and 1 or 0 local _cur_srv_for_key = (_G.arz_srv_sel and _G.arz_srv_sel[0]) or _mpf7d() or 0 local _cache_key = _sh_cnt * 100000 + _arz_cnt * 10 + _items_loaded + _cur_srv_for_key * 1000000 if _G._lv_shops_cache_v ~= _cache_key then _G._lv_shops_cache_v = _cache_key local _c = {} for _, _sh in pairs(fh_other_shops or {}) do for _, _si in ipairs(_sh.sell_items or {}) do if type(_si.name)=='string' and _si.price and _si.price>0 then local _k = _si.name:lower() if not _c[_k] then _c[_k]={} end if not _c[_k].sell or _si.price < _c[_k].sell then _c[_k].sell=_si.price end _c[_k].sell_live = true end end for _, _bi in ipairs(_sh.buy_items or {}) do if type(_bi.name)=='string' and _bi.price and _bi.price>0 then local _k = _bi.name:lower() if not _c[_k] then _c[_k]={} end if not _c[_k].buy or _bi.price > _c[_k].buy then _c[_k].buy=_bi.price end _c[_k].buy_live = true end end wait(0) end if mh_arz_data and mh_arz_items_db then local _lvc_live_idx = _mpf7d() local _lvc_sel_idx = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _lvc_srv_idx = (_lvc_sel_idx > 0 and _lvc_sel_idx) or (_lvc_live_idx > 0 and (_lvc_live_idx + 1)) or 0 local _lvc_srv_id = (_lvc_srv_idx > 0 and ARZ_SERVERS[_lvc_srv_idx] and ARZ_SERVERS[_lvc_srv_idx].id) or -1 local _batch = 0 for _, _lv in ipairs(mh_arz_data) do if type(_lv)=='table' then local _lv_sid = _lv.serverId if _lvc_srv_id ~= -1 then if _lv_sid and _lv_sid ~= -1 and _lv_sid ~= _lvc_srv_id then goto lv_cache_skip end end for _ii, _iid in ipairs(_lv.items_sell or {}) do local _nm = mh_arz_items_db[_bqs3v(_iid)] local _pr = (_lv.price_sell or {})[_ii] if type(_nm)=='string' and _nm~='' and _pr and _pr>0 then local _k = _nm:lower() if not _c[_k] then _c[_k]={} end if not _c[_k].sell_live then if not _c[_k].sell or _pr<_c[_k].sell then _c[_k].sell=_pr end end end end for _ii, _iid in ipairs(_lv.items_buy or {}) do local _nm = mh_arz_items_db[_bqs3v(_iid)] local _pr = (_lv.price_buy or {})[_ii] if type(_nm)=='string' and _nm~='' and _pr and _pr>0 then local _k = _nm:lower() if not _c[_k] then _c[_k]={} end if not _c[_k].buy_live then if not _c[_k].buy or _pr>_c[_k].buy then _c[_k].buy=_pr end end end end ::lv_cache_skip:: end _batch = _batch + 1 if _batch >= 50 then _batch=0; wait(0) end end end _G._lv_shops_cache = _c end end end) lua_thread.create(function() wait(5000) while true do wait(1000) if _G.mh_tab == 4 and fh_lv_autobuy_preset and #fh_lv_autobuy_preset > 0 then local _pver = #fh_lv_autobuy_preset local _dver = tostring(_G._mh_db_ver or 0)..'|'..tostring(_G._mh_shop_ver or 0).. '|'..tostring(_G._mh_deals_cache_ver or 0)..'|'..tostring(_G._mh_daily_cache_ver or 0) local _cur_srv_abp = (_G.arz_srv_sel and _G.arz_srv_sel[0]) or _mpf7d() or 0 local _qty_sum = 0 for _, _abp in ipairs(fh_lv_autobuy_preset) do _qty_sum = _qty_sum + (_abp.qty or 0) end local _key = _pver..'|'.._dver..'|'..tostring(_cur_srv_abp)..'|'..tostring(_qty_sum) if _G._abp_price_cache_key ~= _key then _G._abp_price_cache_key = _key local _nc = {} local _total = 0 for _ai, _abp in ipairs(fh_lv_autobuy_preset) do local _mp = _mh_get_mkt_price(_abp.name) local _avg_cp = nil if _mp then local _v7 = (_mp.avg7 and _mp.avg7 > 0) and _mp.avg7 or nil local _v30 = (_mp.avg30 and _mp.avg30 > 0) and _mp.avg30 or nil if _v7 and _v30 then _avg_cp = math.min(_v7, _v30) elseif _v7 then _avg_cp = _v7 elseif _v30 then _avg_cp = _v30 end end _nc[_ai] = _avg_cp _total = _total + ((_abp.max_price or 0) * (_abp.qty or 1)) if _ai % 20 == 0 then wait(0) end end _G._abp_price_cache = _nc _G._abp_budget_total = _total _G._abp_price_cache_key = _key end end end end) local function _vkp7n(min_price, trend_up_only, api_server_id, yield_fn) local result = {} -- [MH FIX] Batch-анчор цен по всем лавкам сервера (mh_arz_data), -- тот же принцип, что и вкладка "Подбор" (_mh_pick_price_idx). -- Раньше mkt_price считался через _mh_get_mkt_price(), у которой -- anchor берётся только из fh_other_shops (свои сканированные -- лавки), и при отсутствии анчора x5-защита просто отключалась, -- как и сравнение с avg7/avg30 по нескольким точкам истории, из-за -- чего cp_hist даже с anchor=nil мог скользнуть к старому/аномальному -- значению. Результат: разные товары показывали одинаковый -- (крошечный) рынок, хотя лавочные цены были разными. -- -- Исправление: берём anchor из того же batch-индекса, что используется -- во вкладке "Подбор" (_mh_pick_price_idx), построен из mh_arz_data (все лавки -- сервера, не только свои), и для расчёта статистики по cp_hist -- используем _mjg5t() (ту же функцию, что и "Подбор") вместо асинхронного -- расчёта внутри карточки товара. Если _G._mh_pick_price_idx ещё не построен -- (пользователь не открывал вкладку "Подбор") - строим его здесь по тому же -- принципу. local _vkp_shop_ver = _G._mh_shop_ver or 0 if not _G._mh_pick_cache or _G._mh_pick_cache_ver ~= _vkp_shop_ver then local built = {} local price_idx = {} for _, lv in ipairs(mh_arz_data or {}) do if type(lv) == 'table' and lv.items_sell and lv.price_sell then for ii, iid in ipairs(lv.items_sell) do local raw_id = tostring(iid or ''):match('^(%d+)') local item_nm = raw_id and mh_arz_items_db and mh_arz_items_db[tonumber(raw_id)] local shop_price = tonumber(lv.price_sell[ii]) if item_nm and item_nm ~= '' and shop_price and shop_price > 0 then table.insert(built, { name = item_nm, shop_price = shop_price, uid = lv.LavkaUid or 0, owner = lv.username or '', }) if not price_idx[item_nm] then price_idx[item_nm] = {} end table.insert(price_idx[item_nm], shop_price) end end end end for _, arr in pairs(price_idx) do table.sort(arr) end _G._mh_pick_cache = built _G._mh_pick_cache_ver = _vkp_shop_ver _G._mh_pick_price_idx = price_idx end local _vkp_price_idx = _G._mh_pick_price_idx or {} local function _vkp_shop_anchor(item_name) local arr = _vkp_price_idx[item_name] if not arr or #arr == 0 then return nil end if #arr <= 2 then return arr[math.ceil(#arr / 2)] end local q1 = arr[math.max(1, math.ceil(#arr * 0.25))] local q3 = arr[math.min(#arr, math.ceil(#arr * 0.75))] local iqr = q3 - q1 local med = arr[math.ceil(#arr / 2)] local fence = math.max(iqr * 3, med * 0.7) local lo, hi = med - fence, med + fence local clean = {} for _, v in ipairs(arr) do if v >= lo and v <= hi then table.insert(clean, v) end end if #clean == 0 then return med end return clean[math.ceil(#clean / 2)] end -- Рассчитывает рыночную цену товара тем же способом, что и "Подбор" -- (_mh_pick_avg_price): берёт кандидатов из _mh_get_mkt_price() и cp_hist-статистики через -- _mjg5t(), отбрасывает все, что выбивается из диапазона anchor x2.5, берёт -- минимальный из оставшихся. local function _vkp_mkt_price(item_name, hist) local anchor = _vkp_shop_anchor(item_name) local function _within(v) if not v or v <= 0 then return false end if not anchor or anchor <= 0 then return true end local r = v > anchor and (v / anchor) or (anchor / v) return r < 2.5 end local best = nil local function _consider(v) if _within(v) and (not best or v < best) then best = v end end local ok, mp = pcall(_mh_get_mkt_price, item_name) if ok and mp then _consider(mp.today) _consider(mp.avg7) _consider(mp.avg30) end if hist and #hist > 0 then local _today_anchor = anchor or (ok and mp and (mp.today or mp.avg7)) local ok3, s1 = pcall(_mjg5t, hist, 1, _today_anchor) local ok4, s7 = pcall(_mjg5t, hist, 7, _today_anchor) local ok5, s30 = pcall(_mjg5t, hist, 30, _today_anchor) if ok3 and s1 and s1.avg then _consider(s1.avg) end if ok4 and s7 and s7.avg then _consider(s7.avg) end if ok5 and s30 and s30.avg then _consider(s30.avg) end end if not best and anchor and anchor > 0 then best = anchor end return best end local _osh_sell_idx = {} local _osh_buy_idx = {} for _, _sh in pairs(fh_other_shops or {}) do if type(_sh) == 'table' then for _, _si in ipairs(_sh.sell_items or {}) do if type(_si.name)=='string' and (_si.price or 0) > 0 then local _k = _si.name:lower() if not _osh_sell_idx[_k] then _osh_sell_idx[_k] = {} end table.insert(_osh_sell_idx[_k], _si.price) end end for _, _bi in ipairs(_sh.buy_items or {}) do if type(_bi.name)=='string' and (_bi.price or 0) > 0 then local _k = _bi.name:lower() if not _osh_buy_idx[_k] then _osh_buy_idx[_k] = {} end table.insert(_osh_buy_idx[_k], _bi.price) end end end end local _now = os.time() local _date_today = os.date('%Y-%m-%d', _now) local _date_3d = os.date('%Y-%m-%d', _now - 3*86400) local _date_7d = os.date('%Y-%m-%d', _now - 7*86400) local _date_14d = os.date('%Y-%m-%d', _now - 14*86400) local _date_30d = os.date('%Y-%m-%d', _now - 30*86400) local api_sell = {} local api_buy = {} if mh_arz_data and type(mh_arz_data) == 'table' and mh_arz_items_db then for _, lv in ipairs(mh_arz_data) do if type(lv) ~= 'table' then goto api_lv_next end if api_server_id and api_server_id ~= -1 then local _lv_sid = lv.serverId if (not _lv_sid) or _lv_sid == -1 then goto api_lv_next end if _lv_sid ~= api_server_id then goto api_lv_next end end local owner = lv.username or '?' if lv.items_sell and lv.price_sell then for ii, iid in ipairs(lv.items_sell) do local bid = _bqs3v(iid) local nm = mh_arz_items_db[bid] local pr = lv.price_sell[ii] if nm and nm ~= '' and pr and pr > 0 then local k = nm:lower() if not api_sell[k] or pr < api_sell[k].price then api_sell[k] = {price=pr, owner=owner, name=nm, uid=lv.LavkaUid} end end end end if lv.items_buy and lv.price_buy then for ii, iid in ipairs(lv.items_buy) do local bid = _bqs3v(iid) local nm = mh_arz_items_db[bid] local pr = lv.price_buy[ii] if nm and nm ~= '' and pr and pr > 0 then local k = nm:lower() if not api_buy[k] or pr > api_buy[k].price then api_buy[k] = {price=pr, owner=owner, name=nm, uid=lv.LavkaUid} end end end end ::api_lv_next:: end end local shops_sell_map = {} local shops_buy_map = {} local function _inline_trend_up(hist) if not hist or #hist < 4 then return false end local _n = #hist; local _half = math.floor(_n/2) local _ns,_nc,_os,_oc = 0,0,0,0 for _i=1,_half do if hist[_i].price and hist[_i].price>0 then _ns=_ns+hist[_i].price;_nc=_nc+1 end end for _i=_half+1,_n do if hist[_i].price and hist[_i].price>0 then _os=_os+hist[_i].price;_oc=_oc+1 end end if _nc>0 and _oc>0 then return (_ns/_nc - _os/_oc) / (_os/_oc) * 100 > 2 end return false end local _arb_extra = {} if _G._mh_deals_cache then for _nm_cd, _ in pairs(_G._mh_deals_cache) do if not fh_mkt_prices[_nm_cd] then local _found_nm = nil for _snm, _ in pairs(fh_other_shops) do break end _arb_extra[_nm_cd] = true end end end local _arb_names = {} for nm in pairs(fh_mkt_prices) do _arb_names[nm] = fh_mkt_prices[nm] end if _G._mh_deals_cache then for _nm_cd, _cd_hist in pairs(_G._mh_deals_cache) do if not _arb_names[_nm_cd] then local _real_nm = _nm_cd for _, _sh in pairs(fh_other_shops) do if type(_sh)=='table' then for _, _si in ipairs(_sh.sell_items or {}) do if type(_si.name)=='string' and _si.name:lower()==_nm_cd then _real_nm = _si.name; break end end end end _arb_names[_real_nm] = _arb_names[_real_nm] or {} end end end local _arb_list = {} for nm, e in pairs(_arb_names) do if type(nm) == 'string' and type(e) == 'table' then table.insert(_arb_list, {nm=nm, e=e}) end end if yield_fn then yield_fn() end local CHUNK = 200 local _vkp_aborted = false for ci = 1, #_arb_list do if _vkp_aborted then break end local _item = _arb_list[ci] local nm = _item.nm local e = _item.e local nm_lo = nm:lower() local hist = e.cp_hist -- [MH FIX] рыночная цена теперь считается батч-анчором по всем лавкам -- (см. _vkp_mkt_price выше), а не слепо доверяется _mh_get_mkt_price(). local mkt_price = _vkp_mkt_price(nm, hist) if not mkt_price or mkt_price <= 0 then if e.cp_sp and e.cp_sp > 0 then mkt_price = e.cp_sp end end if mkt_price and mkt_price > 0 then do local ss = shops_sell_map[nm_lo] local as = api_sell[nm_lo] local shop_price, shop_owner, shop_qty, shop_uid if ss then shop_price=ss.price; shop_owner=ss.owner; shop_qty=ss.qty; shop_uid=ss.uid end if as and (not shop_price or as.price < shop_price) then shop_price=as.price; shop_owner=as.owner; shop_qty=nil; shop_uid=as.uid end if shop_price then local margin = mkt_price - shop_price local ok = margin > 0 and (not min_price or min_price == 0 or shop_price >= min_price) and (not trend_up_only or _inline_trend_up(hist)) if ok then table.insert(result, { nm=nm, mkt=mkt_price, shop=shop_price, margin=margin, margin_pct=shop_price>0 and margin/shop_price*100 or 0, owner=shop_owner or '?', shop_qty=shop_qty, uid=shop_uid, dir='buy' }) end end end do local sb = shops_buy_map[nm_lo] local ab = api_buy[nm_lo] local buy_price, buy_owner, buy_qty, buy_uid if sb then buy_price=sb.price; buy_owner=sb.owner; buy_qty=sb.qty; buy_uid=sb.uid end if ab and (not buy_price or ab.price > buy_price) then buy_price=ab.price; buy_owner=ab.owner; buy_qty=nil; buy_uid=ab.uid end if buy_price then local margin = buy_price - mkt_price local ok = margin > 0 and (not min_price or min_price == 0 or mkt_price >= min_price) if ok then table.insert(result, { nm=nm, mkt=mkt_price, shop=buy_price, margin=margin, margin_pct=mkt_price>0 and margin/mkt_price*100 or 0, owner=buy_owner or '?', shop_qty=buy_qty, uid=buy_uid, dir='sell' }) end end end end if yield_fn and ci % CHUNK == 0 then if yield_fn() == false then _vkp_aborted = true end end end local combined_sell = {} for k, v in pairs(api_sell) do combined_sell[k] = {price=v.price, owner=v.owner, qty=nil, name=v.name, uid=v.uid} end local combined_buy = {} for k, v in pairs(api_buy) do combined_buy[k] = {price=v.price, owner=v.owner, qty=nil, name=v.name, uid=v.uid} end local _s2s_n = 0 for k, sell_e in pairs(combined_sell) do _s2s_n = _s2s_n + 1; if _s2s_n % 100 == 0 and yield_fn then yield_fn() end local buy_e = combined_buy[k] if buy_e and sell_e.owner ~= buy_e.owner and sell_e.price < buy_e.price then local margin = buy_e.price - sell_e.price local ok = margin > 0 and (not min_price or min_price == 0 or sell_e.price >= min_price) if ok then local _s2s_mp = _mh_get_mkt_price(sell_e.name) table.insert(result, { nm=sell_e.name, mkt=buy_e.price, shop=sell_e.price, margin=margin, margin_pct=sell_e.price>0 and margin/sell_e.price*100 or 0, owner=sell_e.owner, owner2=buy_e.owner, uid=sell_e.uid, uid2=buy_e.uid, shop_qty=sell_e.qty, dir='shop2shop', mkt30=_s2s_mp and _s2s_mp.avg30 or nil }) end end end table.sort(result, function(a,b) return a.margin > b.margin end) return result end _G._vkp7n = _vkp7n local function _tcv8f() if not settings.presets then settings.presets = {} end if not settings.presets[fh_active_preset_idx] then settings.presets[fh_active_preset_idx] = {name="Пресет "..fh_active_preset_idx, items={}} end settings.presets[fh_active_preset_idx].items = fh_lv_autosell_preset settings.active_preset = fh_active_preset_idx _wfn7p() end settings = _qvx4m() if not settings.general then settings.general = {} end if not settings.api_filter then settings.api_filter = {} end if not settings.api_sources then settings.api_sources = {mh=true} end -- [MH FIX] Три уведомления, НЕ требующие premium (в отличие от notify_arb/ -- notify_fav), включаем по умолчанию для новых пользователей - раньше все -- переключатели Telegram-уведомлений были выключены по умолчанию (nil), -- включая те, что не premium-only, из-за чего тег "Смотреть" мог стоять на -- товаре, автообновление лавок могло даже работать, а уведомление всё равно -- не приходило - потому что отдельный, никак явно не связанный с тегами -- переключатель notify_watch оставался выключен, и человек об этом даже не -- подозревал (то же для auto_refresh_shops и notify_trades). -- Каждый ключ проверяется отдельно ("== nil", а не false) - если у -- пользователя уже ЕСТЬ settings.telegram (например, только с bot_token), -- но конкретно этих трёх ключей там ещё не было (старая версия скрипта до -- этого фикса), они всё равно включатся один раз при следующем запуске. -- Если пользователь ранее ОСОЗНАННО выключил что-то из них через UI (то -- есть ключ уже существует и равен false, а не nil) - его выбор не -- перезаписывается, потому что false ~= nil. if settings.telegram == nil then settings.telegram = {} end if settings.telegram.auto_refresh_shops == nil then settings.telegram.auto_refresh_shops = true end if settings.telegram.notify_watch == nil then settings.telegram.notify_watch = true end if settings.telegram.notify_trades == nil then settings.telegram.notify_trades = true end if not settings.premium then settings.premium = {} end if settings.premium.key == nil then settings.premium.key = '' end if settings.premium.activated == true and settings.premium.key ~= '' then local _boot_tok = settings.premium.tok or '' if _boot_tok ~= '' then local _boot_expires = settings.premium.expires or '' local _boot_ok = true if _boot_expires ~= '' then pcall(function() local y,m,d = _boot_expires:match('(%d+)[%-%.](%d+)[%-%.](%d+)') if y then local _exp_ts = os.time({year=tonumber(y),month=tonumber(m),day=tonumber(d),hour=23,min=59,sec=59}) if os.time() > _exp_ts then _boot_ok = false settings.premium.activated = false settings.premium.key = '' settings.premium.user = '' settings.premium.nick = '' settings.premium.expires = '' settings.premium.last_check = 0 _wfn7p() mh_notify('[MH] {ff4444}Premium истёк (' .. _boot_expires .. '). Деактивирован.', 0xFFFFFF) end end end) end if _boot_ok then _qtp7v = true _G._pf2 = true _G._pf3 = true _G._pf4 = true end end lua_thread.create(_lkg8m) end if settings.premium.activated == nil then settings.premium.activated = false end if settings.premium.activated == true and (settings.premium.tok == nil or settings.premium.tok == '') then settings.premium.activated = false _wfn7p() end if settings.premium.expires == nil then settings.premium.expires = '' end if not settings.item_tags then settings.item_tags = {} end if not settings.market_filters then settings.market_filters = {} end if settings.market_filters.min_price == nil then settings.market_filters.min_price = 0 end if settings.market_filters.trend_up_only == nil then settings.market_filters.trend_up_only = false end if not settings.interface then settings.interface = {} end if not settings.trade_autoprice then settings.trade_autoprice = {} end if settings.trade_autoprice.enabled == nil then settings.trade_autoprice.enabled = false end if settings.trade_autoprice.pct == nil then settings.trade_autoprice.pct = 65 end if not settings.piar_templates then settings.piar_templates = { { name = 'Пиар /vr', enable = true, auto = false, auto_interval = 600, auto_interval_max = 0, waiting = 1.5, last_time = 0, lines = { '/vr Хочешь в орг? Пиши в /pm!', }}, { name = 'Пиар /s', enable = true, auto = false, auto_interval = 300, auto_interval_max = 0, waiting = 1.5, last_time = 0, lines = { '/s Приглашаем всех активных игроков!', }}, } end for _, t in ipairs(settings.piar_templates or {}) do t.name=t.name or''; t.lines=t.lines or{}; t.waiting=t.waiting or 1.5 t.auto_interval=t.auto_interval or 300; t.last_time=t.last_time or 0 if t.enable==nil then t.enable=true end if t.auto==nil then t.auto=false end end if settings.general.auto_vr_confirm == nil then settings.general.auto_vr_confirm = true end if settings.general.auto_ad_confirm == nil then settings.general.auto_ad_confirm = false end if settings.general.auto_storage_collect == nil then settings.general.auto_storage_collect = false end if settings.general.auto_ad_station_idx == nil then settings.general.auto_ad_station_idx = 2 end if settings.general.auto_ad_type == nil then settings.general.auto_ad_type = 0 end if not settings.overlay then settings.overlay = {} end if settings.overlay.enabled == nil then settings.overlay.enabled = true end if not settings.overlay.pos_x then settings.overlay.pos_x = 10 end if not settings.overlay.pos_y then settings.overlay.pos_y = 200 end if not settings.overlay.width then settings.overlay.width = 420 end if not settings.overlay.height then settings.overlay.height = 180 end if not settings.overlay.alpha then settings.overlay.alpha = 0.6 end if not settings.overlay.lines then settings.overlay.lines = 8 end if not settings.overlay.sell_r then settings.overlay.sell_r = 0.3 end if not settings.overlay.sell_g then settings.overlay.sell_g = 0.9 end if not settings.overlay.sell_b then settings.overlay.sell_b = 0.3 end if not settings.overlay.buy_r then settings.overlay.buy_r = 0.3 end if not settings.overlay.buy_g then settings.overlay.buy_g = 0.6 end if not settings.overlay.buy_b then settings.overlay.buy_b = 1.0 end if not settings.overlay.log_price_r then settings.overlay.log_price_r = 1.0 end if not settings.overlay.log_price_g then settings.overlay.log_price_g = 0.85 end if not settings.overlay.log_price_b then settings.overlay.log_price_b = 0.2 end if not settings.overlay then settings.overlay = {} end if settings.overlay.enabled == nil then settings.overlay.enabled = true end if not settings.overlay.pos_x then settings.overlay.pos_x = 10 end if not settings.overlay.pos_y then settings.overlay.pos_y = 200 end if not settings.overlay.width then settings.overlay.width = 420 end if not settings.overlay.height then settings.overlay.height = 180 end if not settings.overlay.alpha then settings.overlay.alpha = 0.6 end if not settings.overlay.lines then settings.overlay.lines = 8 end if not settings.overlay.sell_r then settings.overlay.sell_r = 0.3 end if not settings.overlay.sell_g then settings.overlay.sell_g = 0.9 end if not settings.overlay.sell_b then settings.overlay.sell_b = 0.3 end if not settings.overlay.buy_r then settings.overlay.buy_r = 0.3 end if not settings.overlay.buy_g then settings.overlay.buy_g = 0.6 end if not settings.overlay.buy_b then settings.overlay.buy_b = 1.0 end if not settings.interface.accent_r then settings.interface.accent_r = 0.056 end if not settings.interface.accent_g then settings.interface.accent_g = 0.484 end if not settings.interface.accent_b then settings.interface.accent_b = 0.657 end if not settings.interface.sell_btn_r then settings.interface.sell_btn_r = 0.085 end if not settings.interface.sell_btn_g then settings.interface.sell_btn_g = 0.115 end if not settings.interface.sell_btn_b then settings.interface.sell_btn_b = 0.222 end if not settings.interface.buy_btn_r then settings.interface.buy_btn_r = 0.211 end if not settings.interface.buy_btn_g then settings.interface.buy_btn_g = 0.225 end if not settings.interface.buy_btn_b then settings.interface.buy_btn_b = 0.393 end if not settings.general.autofind_dpi then settings.general.custom_dpi = 1.25 settings.general.autofind_dpi = true; _wfn7p() end if not settings.toast then settings.toast = {} end do local _mh_toast_def = { enabled = true, width = 320, padding_x = 14, padding_y = 10, accent_width = 4, corner_radius = 8, spacing = 8, margin_x = 18, margin_y = 34, duration = 6.0, -- было 4.0 - тосты пропадали слишком быстро anim_speed = 10.0, max_visible = 5, pos_h = 'right', pos_v = 'bottom', bg_r = 0.08, bg_g = 0.08, bg_b = 0.10, bg_a = 0.90, text_r = 0.94, text_g = 0.94, text_b = 0.96, text_a = 1.00, title_r = 0.72, title_g = 0.72, title_b = 0.78, title_a = 1.00, border_enabled = false, border_r = 1.00, border_g = 1.00, border_b = 1.00, border_a = 0.25, border_size = 1.0, font_scale = 1.0, } for k, v in pairs(_mh_toast_def) do if settings.toast[k] == nil then settings.toast[k] = v end end -- [MH FIX] Одноразовая миграция: у пользователей с уже существующим -- settings.json поле toast.duration могло быть сохранено ещё старой -- версией скрипта (там дефолт был 4.0, а фактически на экране держалось -- ~2 сек из-за отдельного бага в mh_notify() с хардкодом длительности - -- см. правку MH_TOAST_DURATION выше). Общий цикл прямо над этой строкой -- применяет новый дефолт (6.0) ТОЛЬКО если ключа нет вообще - а он уже -- есть у таких пользователей со старым значением, поэтому обновление -- дефолта их не касалось. Поднимаем принудительно один раз, если -- сохранённое значение меньше нового дефолта, и не трогаем тех, кто уже -- сам осознанно поставил себе больше через слайдер настроек. if not settings.toast._dur_migrated_v2 then if (settings.toast.duration or 0) < 6.0 then settings.toast.duration = 6.0 end settings.toast._dur_migrated_v2 = true _wfn7p() end end imgui = require('mimgui') --====================================================================== -- [MH] Embedded NotificationToastSystem (originally a separate module, -- merged directly into this script - no external require()/file needed). --====================================================================== do local imgui = imgui assert(imgui ~= nil, '[NotificationToastSystem] Глобальная переменная imgui не найдена. Модуль должен подключаться из MoonLoader-скрипта с доступом к imgui.') --====================================================================== -- 0. Мелкие безопасные обёртки над API imgui (разные сборки биндингов -- могут иметь неполный набор функций — на каждый рискованный вызов -- предусмотрен pcall + запасной вариант). --====================================================================== local function safe_call(fn, ...) if type(fn) ~= 'function' then return nil end local ok, a, b = pcall(fn, ...) if ok then return a, b end return nil end -- Упаковка ImVec4(r,g,b,a) в ImU32, с фоллбэком на ручную упаковку ABGR. local function pack_color(r, g, b, a) a = a == nil and 1 or a if imgui.ColorConvertFloat4ToU32 then local ok, packed = pcall(imgui.ColorConvertFloat4ToU32, imgui.ImVec4(r, g, b, a)) if ok and packed then return packed end end local function to_byte(v) return math.floor((v < 0 and 0 or (v > 1 and 1 or v)) * 255 + 0.5) end local rr, gg, bb, aa = to_byte(r), to_byte(g), to_byte(b), to_byte(a) return (aa * 0x1000000) + (bb * 0x10000) + (gg * 0x100) + rr end --====================================================================== -- 1. Глобальная конфигурация модуля (может быть переопределена через -- NotificationManager.configure({...})) --====================================================================== local DEFAULT_CONFIG = { width = 320, -- фиксированная ширина плашки, px padding_x = 14, -- внутренние отступы по X padding_y = 10, -- внутренние отступы по Y accent_width = 4, -- ширина цветной полосы-индикатора corner_radius = 8, -- скругление фона плашки spacing = 8, -- расстояние между плашками в стеке margin_x = 18, -- отступ стека от края экрана по X margin_y = 34, -- отступ стека от края экрана по Y (увеличен с 18, чтобы нижняя плашка не упиралась в край экрана/safe area на мобилках) duration = 6.0, -- время показа по умолчанию, сек (было 4.0) anim_speed = 10.0, -- скорость lerp-анимации (чем больше — тем резче) max_visible = 5, -- сколько плашек показывается одновременно position = 'bottom_right', -- bottom_right | bottom_left | bottom_center -- | top_right | top_left | top_center bg_color = {0.08, 0.08, 0.10, 0.90}, text_color = {0.94, 0.94, 0.96, 1.00}, title_color = {0.72, 0.72, 0.78, 1.00}, font_scale = 1.0, -- множитель масштаба (совместимо с custom_dpi проекта) border_enabled = false, -- рисовать ли рамку по краю плашки border_color = {1.00, 1.00, 1.00, 0.25}, border_size = 1.0, -- толщина рамки, px } local NOTIFICATION_TYPES = { info = { color = {0.25, 0.55, 0.95}, icon = fa.CIRCLE_INFO, label = u8('Информация'), }, success = { color = {0.22, 0.78, 0.40}, icon = fa.CIRCLE_CHECK, label = u8('Успех'), }, warning = { color = {0.95, 0.66, 0.15}, icon = fa.TRIANGLE_EXCLAMATION, label = u8('Внимание'), }, error = { color = {0.92, 0.26, 0.26}, icon = fa.XMARK, label = u8('Ошибка'), }, -- Отдельный тип для алертов об арбитраже - раньше эти уведомления шли -- как 'success' (зелёные) и показывали заголовок "Успех", что не -- объясняло, ПРО ЧТО уведомление. Собственный тип с заголовком -- "Арбитраж" и иконкой весов (та же, что на вкладке "Арбитраж"). arbitrage = { color = {0.35, 0.75, 0.95}, icon = fa.SCALE_BALANCED, label = u8('Арбитраж'), }, -- [MH FIX] По аналогии с arbitrage выше - товар из вотчлиста -- ("Смотреть") раньше показывался как обычный 'info' с заголовком -- "Информация". Своя иконка (глаз, та же, что и тег "Смотреть" -- в списке товаров) и заголовок "Смотреть". watch = { color = {0.42, 0.81, 1.00}, icon = fa.EYE, label = u8('Смотреть'), }, -- Избранное дешевле рынка - своя иконка (звезда, та же, что и тег -- "Избранное") и заголовок "Избранное". favorite = { color = {1.00, 0.67, 0.00}, icon = fa.STAR, label = u8('Избранное'), }, -- Продажа (ваш товар купили) - зелёная стрелка вверх. sale = { color = {0.33, 0.87, 0.33}, icon = fa.ARROW_UP, label = u8('Продажа'), }, -- Покупка (вы купили у другого игрока) - синяя стрелка вверх. purchase = { color = {0.30, 0.55, 1.00}, icon = fa.ARROW_UP, label = u8('Покупка'), }, } local STATE = { APPEARING = 'appearing', SHOWING = 'showing', DISAPPEARING = 'disappearing', DEAD = 'dead', } --====================================================================== -- 2. Математика анимации: FPS-independent Lerp -- t = 1 - e^(-speed*dt) — экспоненциальное сглаживание, -- даёт одинаковую по ощущениям скорость на 30 FPS и 240 FPS, -- в отличие от наивного "pos += speed*dt". --====================================================================== local function clamp01(v) if v < 0 then return 0 elseif v > 1 then return 1 else return v end end local function lerp(a, b, t) return a + (b - a) * t end local function lerp_dt(current, target, speed, dt) local t = 1 - math.exp(-speed * math.max(dt, 0)) return lerp(current, target, clamp01(t)) end --====================================================================== -- 3. КЛАСС Notification — одно уведомление: своё состояние, таймер, -- позиция и альфа-канал, независимые от остальных элементов очереди. --====================================================================== local Notification = {} Notification.__index = Notification local _id_seq = 0 local function next_id() _id_seq = _id_seq + 1 return _id_seq end function Notification.new(text, ntype, duration, cfg) local self = setmetatable({}, Notification) self.id = next_id() self.text = tostring(text or '') self.ntype = NOTIFICATION_TYPES[ntype] and ntype or 'info' self.duration = tonumber(duration) or cfg.duration self.cfg = cfg -- ссылка на конфиг менеджера на момент создания self.state = STATE.APPEARING self.timer = 0 -- счётчик времени в состоянии showing/disappearing self.alpha = 0 -- текущая прозрачность [0..1] self.y = 0 -- текущая позиция окна по Y (px) self.target_y = 0 -- целевая позиция по Y (куда лерпим) self.offset = 0 -- смещение от края стека (сумма высот более "новых" плашек) self.height = nil -- высота плашки, вычисляется один раз по тексту self.spawned = false -- была ли выставлена стартовая (закадровая) позиция return self end -- Вычисляет высоту плашки под конкретный текст с переносом строк. -- Считается один раз (текст неизменен на всём жизненном цикле уведомления). function Notification:calc_height() if self.height then return self.height end local cfg = self.cfg local fscale = cfg.font_scale or 1.0 -- CalcTextSize вызывается ВНЕ окна тоста (нет активного SetWindowFontScale), поэтому меряет по дефолтному масштабу, а рендерится текст потом с cfg.font_scale - если он != 1.0, высота окна не совпадает с реальным текстом и низ обрезается -- домножаем результат замера на font_scale - CalcTextSize линейно масштабируется вместе с размером шрифта, так расчёт совпадёт с тем, что реально нарисуется внутри Begin/SetWindowFontScale local wrap_w = (cfg.width - cfg.padding_x * 2 - cfg.accent_width - 8) / fscale local text_h = 16 local sz = safe_call(imgui.CalcTextSize, self.text, false, wrap_w) if sz then text_h = sz.y * fscale end -- Variable title height: the "[icon] label" header now wraps too, so a -- long icon+label combo (or a wide font_scale) doesn't get clipped - -- measure it the same way as the body instead of assuming one line. local def = NOTIFICATION_TYPES[self.ntype] local title_h = 18 * fscale if def then local title_str = string.format('%s %s', def.icon, def.label) local tsz = safe_call(imgui.CalcTextSize, title_str, false, wrap_w) if tsz then title_h = tsz.y * fscale end end -- запас прочности: CalcTextSize и реальный TextWrapped/layout ImGui иногда -- расходятся (разные пути измерения текста), и на практике расхождение -- растёт вместе с числом строк - не фиксированные 1-2px, а накапливается -- на каждом переносе. Оцениваем число строк грубо через (text_h / line_h) -- и добавляем запас пропорционально ему, плюс фиксированная база. -- Дополнительно: +6px чисто визуального "воздуха" снизу (breathing room) - -- без него текст ровно впритык к нижнему краю плашки, что выглядит -- зажато, даже когда физически уже ничего не обрезается. local line_h = 16 * fscale local approx_lines = math.max(1, math.ceil((text_h + title_h) / line_h)) local safety_margin = 8 + approx_lines * 3 local breathing_room = 6 self.height = cfg.padding_y * 2 + title_h + text_h + 12 + safety_margin + breathing_room return self.height end -- Переводит уведомление в фазу исчезновения (можно вызывать вручную извне, -- например для программного закрытия по клику). function Notification:dismiss() if self.state ~= STATE.DISAPPEARING and self.state ~= STATE.DEAD then self.state = STATE.DISAPPEARING self.timer = 0 end end function Notification:is_dead() return self.state == STATE.DEAD end -- off_screen_y — координата Y "за кадром" (откуда плашка появляется/куда исчезает) -- target_y — текущая целевая позиция в стеке (пересчитывается менеджером каждый кадр, -- т.к. стек может "уехать" при добавлении/удалении соседних уведомлений) function Notification:update(dt, target_y, off_screen_y) self.target_y = target_y local cfg = self.cfg if self.state == STATE.APPEARING then if not self.spawned then self.y = off_screen_y self.spawned = true end self.y = lerp_dt(self.y, self.target_y, cfg.anim_speed, dt) self.alpha = lerp_dt(self.alpha, 1, cfg.anim_speed, dt) if math.abs(self.y - self.target_y) < 0.75 and self.alpha > 0.97 then self.state = STATE.SHOWING self.timer = 0 end elseif self.state == STATE.SHOWING then -- плашка продолжает подтягиваться к target_y, т.к. стек может сдвигаться, -- когда добавляются/уходят соседние уведомления self.y = lerp_dt(self.y, self.target_y, cfg.anim_speed, dt) self.alpha = lerp_dt(self.alpha, 1, cfg.anim_speed, dt) self.timer = self.timer + dt if self.timer >= self.duration then self:dismiss() end elseif self.state == STATE.DISAPPEARING then self.y = lerp_dt(self.y, off_screen_y, cfg.anim_speed, dt) self.alpha = lerp_dt(self.alpha, 0, cfg.anim_speed, dt) self.timer = self.timer + dt if self.alpha < 0.02 or self.timer > 2.5 then self.state = STATE.DEAD end end end --====================================================================== -- 4. КЛАСС / СИНГЛТОН NotificationManager — менеджер очереди и рендер. --====================================================================== local NotificationManager = {} NotificationManager.__index = NotificationManager NotificationManager.config = {} for k, v in pairs(DEFAULT_CONFIG) do NotificationManager.config[k] = v end NotificationManager.active = {} -- активные (видимые/анимируемые) уведомления, по возрастанию времени добавления NotificationManager.pending = {} -- ждут своей очереди, если превышен max_visible NotificationManager.enabled = true NotificationManager._registered = false -------------------------------------------------------------------- -- 4.1 Конфигурация -------------------------------------------------------------------- -- Позволяет переопределить любые поля конфигурации: -- NotificationManager.configure({ position = 'top_right', duration = 3, width = 300 }) function NotificationManager.configure(opts) if type(opts) ~= 'table' then return NotificationManager end for k, v in pairs(opts) do NotificationManager.config[k] = v end return NotificationManager end function NotificationManager.set_position(pos) NotificationManager.config.position = pos return NotificationManager end function NotificationManager.set_enabled(flag) NotificationManager.enabled = flag and true or false return NotificationManager end -------------------------------------------------------------------- -- 4.2 Публичное API добавления уведомлений -------------------------------------------------------------------- -- Основной метод API: NotificationManager.add(text, type, duration) -- text — строка сообщения (обязательно) -- type — 'info' | 'success' | 'warning' | 'error' (по умолчанию 'info') -- duration — время показа в секундах (по умолчанию config.duration, т.е. 4) -- Возвращает id созданного уведомления. function NotificationManager.add(text, ntype, duration) if not text or text == '' then return nil end local notif = Notification.new(text, ntype, duration, NotificationManager.config) if #NotificationManager.active < NotificationManager.config.max_visible then table.insert(NotificationManager.active, notif) else table.insert(NotificationManager.pending, notif) end NotificationManager:_ensure_registered() return notif.id end -- Короткие алиасы под каждый тип уведомления function NotificationManager.info(text, duration) return NotificationManager.add(text, 'info', duration) end function NotificationManager.success(text, duration) return NotificationManager.add(text, 'success', duration) end function NotificationManager.warning(text, duration) return NotificationManager.add(text, 'warning', duration) end function NotificationManager.error(text, duration) return NotificationManager.add(text, 'error', duration) end -- Мгновенно закрыть уведомление по id (запускает анимацию исчезновения) function NotificationManager.dismiss(id) for _, n in ipairs(NotificationManager.active) do if n.id == id then n:dismiss(); return true end end for i, n in ipairs(NotificationManager.pending) do if n.id == id then table.remove(NotificationManager.pending, i); return true end end return false end -- Полностью очистить очередь (активные + ожидающие), без анимации function NotificationManager.clear() NotificationManager.active = {} NotificationManager.pending = {} end function NotificationManager.count() return #NotificationManager.active + #NotificationManager.pending end -------------------------------------------------------------------- -- 4.3 Внутренняя логика: обновление очереди/стека -------------------------------------------------------------------- -- Возвращает (anchor_x, anchor_bottom_or_top_y, grows_up, off_screen_y_fn) -- в зависимости от выбранного угла экрана. function NotificationManager:_layout_params(sw, sh) local cfg = self.config local pos = cfg.position or 'bottom_right' local x if pos:find('right') then x = sw - cfg.margin_x - cfg.width elseif pos:find('left') then x = cfg.margin_x else -- *_center x = (sw - cfg.width) / 2 end if x < 0 then x = 0 end if x + cfg.width > sw then x = sw - cfg.width end -- страховка: не даём плашке уехать за левый/правый край экрана, если width+margin (с учётом DPI) окажется больше ширины экрана -- страховка независимо от настроек пользователя: нижний/верхний отступ никогда не меньше 28px (с учётом DPI) - защищает от обрезания текста снизу на устройствах с жестовой навигацией/safe area, даже если у пользователя уже сохранён старый margin_y=18 в settings.toast local _d = tonumber(settings.general and settings.general.custom_dpi) or 1 local min_margin_y = 28 * _d local mgy = cfg.margin_y if mgy < min_margin_y then mgy = min_margin_y end local from_top = pos:find('^top') if from_top then return x, mgy, false -- false = стек растёт вниз от anchor_y else return x, sh - mgy, true -- true = стек растёт вверх от anchor_y (anchor = нижний край) end end function NotificationManager:_update(dt) local cfg = self.config -- переносим уведомления из очереди ожидания, если освободилось место while #self.active < cfg.max_visible and #self.pending > 0 do table.insert(self.active, table.remove(self.pending, 1)) end if #self.active == 0 then return end local io = imgui.GetIO() local sw, sh = io.DisplaySize.x, io.DisplaySize.y local anchor_x, anchor_y, grows_up = self:_layout_params(sw, sh) local off_screen_y if grows_up then off_screen_y = sh + 40 else off_screen_y = -40 end -- считаем накопленное смещение в стеке: самое новое уведомление стоит -- ближе к anchor_y, более старые "уезжают" от него (эффект сдвига вверх/вниз) local acc = 0 for i = #self.active, 1, -1 do local n = self.active[i] n:calc_height() n.offset = acc acc = acc + n.height + cfg.spacing local target_y if grows_up then target_y = anchor_y - n.height - n.offset else target_y = anchor_y + n.offset end n:update(dt, target_y, grows_up and (off_screen_y) or (off_screen_y - n.height)) end self._anchor_x = anchor_x -- убираем "умершие" уведомления из активного списка for i = #self.active, 1, -1 do if self.active[i]:is_dead() then table.remove(self.active, i) end end end -------------------------------------------------------------------- -- 4.4 Отрисовка -------------------------------------------------------------------- local TOAST_WINDOW_FLAGS = imgui.WindowFlags.NoTitleBar + imgui.WindowFlags.NoResize + imgui.WindowFlags.NoMove + imgui.WindowFlags.NoScrollbar + imgui.WindowFlags.NoScrollWithMouse + imgui.WindowFlags.NoCollapse + imgui.WindowFlags.NoSavedSettings + imgui.WindowFlags.NoFocusOnAppearing + imgui.WindowFlags.NoNav + imgui.WindowFlags.NoInputs + imgui.WindowFlags.NoBackground function NotificationManager:_draw_toast(n) local cfg = self.config local def = NOTIFICATION_TYPES[n.ntype] local a = clamp01(n.alpha) if a <= 0.01 then return end imgui.SetNextWindowPos(imgui.ImVec2(self._anchor_x, n.y), imgui.Cond.Always) imgui.SetNextWindowSize(imgui.ImVec2(cfg.width, n.height), imgui.Cond.Always) imgui.SetNextWindowBgAlpha(0) -- Примечание: на некоторых мобильных сборках mimgui (MonetLoader) нет -- imgui.PushStyleVar — поэтому фейд и убранная рамка окна делаются без -- него: альфа зашивается прямо в цвета через PushStyleColor (которая -- точно поддерживается — используется по всему остальному скрипту), -- а рамка скрывается прозрачным Col.Border вместо WindowBorderSize=0. imgui.PushStyleColor(imgui.Col.Border, imgui.ImVec4(0, 0, 0, 0)) local win_name = '##mh_toast_' .. tostring(n.id) local dummy_open = imgui.new.bool(true) if imgui.Begin(win_name, dummy_open, TOAST_WINDOW_FLAGS) then safe_call(imgui.SetWindowFontScale, cfg.font_scale or 1.0) local dl = imgui.GetWindowDrawList() local p_min = imgui.GetWindowPos() local p_max = imgui.ImVec2(p_min.x + cfg.width, p_min.y + n.height) -- фон плашки: тёмный, полупрозрачный, скруглённые углы local bg = cfg.bg_color dl:AddRectFilled(p_min, p_max, pack_color(bg[1], bg[2], bg[3], bg[4] * a), cfg.corner_radius) -- настраиваемая рамка по краю плашки (опционально, включается в "Вид") if cfg.border_enabled and (cfg.border_size or 0) > 0 then local bc = cfg.border_color or {1, 1, 1, 0.25} safe_call(dl.AddRect, dl, p_min, p_max, pack_color(bc[1], bc[2], bc[3], (bc[4] or 1) * a), cfg.corner_radius, 0, cfg.border_size) end -- вертикальная цветная полоса-индикатор слева (тип уведомления) local c = def.color local stripe_min = imgui.ImVec2(p_min.x + 3, p_min.y + 4) local stripe_max = imgui.ImVec2(p_min.x + 3 + cfg.accent_width, p_max.y - 4) dl:AddRectFilled(stripe_min, stripe_max, pack_color(c[1], c[2], c[3], a), cfg.accent_width / 2) -- отступ контента от полосы-индикатора imgui.SetCursorPosX(cfg.padding_x + cfg.accent_width + 6) imgui.SetCursorPosY(cfg.padding_y) -- заголовок: иконка + текстовый маркер типа (альфа зашита в цвет) -- перенос по ширине, как и тело сообщения, чтобы длинный label не уносило -- заголовок не выходил за правый край плашки/экрана imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(c[1], c[2], c[3], a)) imgui.PushTextWrapPos(cfg.width - cfg.padding_x) imgui.TextWrapped(string.format('%s %s', def.icon, def.label)) imgui.PopTextWrapPos() imgui.PopStyleColor() -- сообщение с автопереносом по ширине плашки (альфа зашита в цвет) imgui.SetCursorPosX(cfg.padding_x + cfg.accent_width + 6) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(cfg.text_color[1], cfg.text_color[2], cfg.text_color[3], cfg.text_color[4] * a)) imgui.PushTextWrapPos(cfg.width - cfg.padding_x) imgui.TextWrapped(n.text) imgui.PopTextWrapPos() imgui.PopStyleColor() -- не гадаем высоту заранее: после отрисовки всего контента (заголовок + сообщение) смотрим реальную позицию курсора - это и есть настоящая занятая высота, безо всяких приближений через CalcTextSize -- если реальная высота контента больше текущей n.height - окно было мало и текст обрезался; подгоняем высоту под контент, на следующем кадре плашка вырастет и обрезания больше не будет -- если контента оказалось меньше расчётного (например текст короче ожидаемого) - тоже подстраиваемся, чтобы не было лишнего пустого места снизу local _measured_y = safe_call(imgui.GetCursorPosY) if _measured_y then local _real_h = _measured_y + cfg.padding_y -- если реального контента оказалось БОЛЬШЕ, чем текущая высота окна - -- значит именно в этом кадре текст обрезался; растим высоту сразу, -- без порога в 1px (раньше проверка была симметричной и слишком -- строгой, из-за чего рост высоты после клипа мог задержаться на -- лишний кадр). Уменьшение высоты (контента стало меньше) не -- срочное - для него оставляем прежний порог, чтобы не дёргать -- box из-за микроскопических колебаний измерения. if _real_h > n.height then n.height = _real_h elseif math.abs(_real_h - n.height) > 1 then n.height = _real_h end end end imgui.End() imgui.PopStyleColor() -- Border end function NotificationManager:_render() if not self.enabled then return end self:_update(safe_call(function() return imgui.GetIO().DeltaTime end) or 0) if #self.active == 0 then return end for _, n in ipairs(self.active) do self:_draw_toast(n) end end -------------------------------------------------------------------- -- 4.5 Саморегистрация в цикле рендера imgui (однократно) -------------------------------------------------------------------- function NotificationManager:_ensure_registered() if self._registered then return end self._registered = true imgui.OnFrame( function() return NotificationManager.enabled end, function() NotificationManager:_render() end ) end -- регистрируем цикл сразу при подключении модуля, чтобы уведомления, -- добавленные из любого места, гарантированно отрисовывались NotificationManager:_ensure_registered() --====================================================================== -- 4.6 Применение стиля/позиции плашек из settings.toast (вкладка "Вид") --====================================================================== -- Собирает таблицу для NotificationManager.configure() из settings.toast. local function _mh_toast_cfg_from_settings() local s = settings.toast or {} -- применяем DPI-масштаб проекта (settings.general.custom_dpi) к геометрии плашки, как и весь остальной интерфейс, иначе плашка "уезжает" за экран на масштабах, отличных от 1.0 local d = tonumber(settings.general and settings.general.custom_dpi) or 1 return { width = (s.width or 320) * d, padding_x = (s.padding_x or 14) * d, padding_y = (s.padding_y or 10) * d, accent_width = (s.accent_width or 4) * d, corner_radius = (s.corner_radius or 8) * d, spacing = (s.spacing or 8) * d, margin_x = (s.margin_x or 18) * d, margin_y = (s.margin_y or 18) * d, duration = s.duration, anim_speed = s.anim_speed, max_visible = s.max_visible, position = (s.pos_v or 'bottom') .. '_' .. (s.pos_h or 'right'), bg_color = {s.bg_r, s.bg_g, s.bg_b, s.bg_a}, text_color = {s.text_r, s.text_g, s.text_b, s.text_a}, title_color = {s.title_r, s.title_g, s.title_b, s.title_a}, border_enabled= s.border_enabled, border_color = {s.border_r, s.border_g, s.border_b, s.border_a}, border_size = s.border_size, font_scale = s.font_scale, } end -- Перечитывает settings.toast и применяет к менеджеру плашек. -- Вызывается один раз при загрузке и каждый раз, когда пользователь -- меняет что-то во вкладке "Вид" -> "Уведомления (плашки)". function _mh_apply_toast_settings() NotificationManager.configure(_mh_toast_cfg_from_settings()) NotificationManager.set_enabled(settings.toast == nil or settings.toast.enabled ~= false) end _mh_apply_toast_settings() -- Тест-функция для плашек: показывает одно или все уведомления сразу -- с текущими настройками стиля/позиции. Используется кнопками -- "Тест" во вкладке "Вид". function _mh_toast_test(kind) local samples = { info = u8('Пример информационного уведомления MH.'), success = u8('Операция прошла успешно.'), warning = u8('Внимание: проверьте настройки.'), error = u8('Пример ошибки — что-то пошло не так.'), } if kind == 'all' then for _, k in ipairs({'info', 'success', 'warning', 'error'}) do NotificationManager.add(samples[k], k, NotificationManager.config.duration) end return end if not samples[kind] then kind = 'info' end NotificationManager.add(samples[kind], kind, NotificationManager.config.duration) end --====================================================================== -- 5. Экспорт модуля --====================================================================== MH_Notify = NotificationManager end imgui = require('mimgui') local sizeX, sizeY = getScreenResolution() local MainWindow = imgui.new.bool() local sl = { dpi = imgui.new.float(tonumber(settings.general.custom_dpi) or 1), window_alpha = imgui.new.float(settings.interface.window_alpha or 0.98), bg_bright = imgui.new.float(settings.interface.bg_brightness or 0.03), font_scale = imgui.new.float(tonumber(settings.interface.font_scale) or 0.70), } local accent_color = imgui.new.float[3]( settings.interface.accent_r or 0.056, settings.interface.accent_g or 0.484, settings.interface.accent_b or 0.657 ) local function _ltz8m() if not _cvh6z() then imgui.SetWindowFontScale(settings.general.custom_dpi) end end _G._ltz8m = _ltz8m function imgui.CenterColumnSmallButton(t) local d=(t or''):match('(.+)##') or t or''; imgui.SetCursorPosX((imgui.GetColumnOffset()+(imgui.GetColumnWidth()/2))-imgui.CalcTextSize(d).x/2); return imgui.SmallButton(t or'') end function imgui.GetMiddleButtonX(c) local w=imgui.GetWindowContentRegionWidth(); local s=imgui.GetStyle().ItemSpacing.x; return c==1 and w or w/c-((s*(c-1))/c) end function _mh_bc(r,g,b,a) local _bri = _G._mh_btn_bright or 1.0 local _sat = _G._mh_btn_sat or 1.0 local _gray = r*0.299 + g*0.587 + b*0.114 local nr = (_gray + (r-_gray)*_sat)*_bri local ng = (_gray + (g-_gray)*_sat)*_bri local nb = (_gray + (b-_gray)*_sat)*_bri local _wa = _G._mh_wa or 1.0 return imgui.ImVec4(math.min(nr,1), math.min(ng,1), math.min(nb,1), (a or 1)*_wa) end function _mh_bca(r,g,b,a) local _bria = _G._mh_btn_active_bright or 1.0 local _sat = _G._mh_btn_sat or 1.0 local _gray = r*0.299 + g*0.587 + b*0.114 local nr = (_gray + (r-_gray)*_sat)*_bria local ng = (_gray + (g-_gray)*_sat)*_bria local nb = (_gray + (b-_gray)*_sat)*_bria local _wa = _G._mh_wa or 1.0 return imgui.ImVec4(math.min(nr,1), math.min(ng,1), math.min(nb,1), (a or 1)*_wa) end _mh_themes = { { name='Classic', accent={1, 0.65, 0}, bg={0.06, 0.057, 0.048}, text={0.93, 0.88, 0.78}, border_mul=0.70, rounding=4, border_size=1, btn=nil, sell_btn={0.10,0.45,0.10}, buy_btn={0.00,0.28,0.50}, }, { name='Cyber Neon', accent={0.00, 0.92, 1.00}, bg={0.03, 0.035, 0.05}, text={0.75, 1.00, 0.95}, border_mul=0.80, rounding=2, border_size=1.5, btn={0.04,0.07,0.10}, sell_btn={0.05,0.55,0.45}, buy_btn={0.00,0.20,0.55}, }, { name='Matrix', accent={0.10, 0.95, 0.30}, bg={0.02, 0.04, 0.02}, text={0.55, 1.00, 0.55}, border_mul=0.65, rounding=0, border_size=1, btn={0.03,0.07,0.03}, sell_btn={0.05,0.40,0.08}, buy_btn={0.02,0.22,0.08}, }, { name='Vaporwave', accent={0.92, 0.25, 0.90}, bg={0.06, 0.03, 0.08}, text={1.00, 0.80, 1.00}, border_mul=0.75, rounding=8, border_size=1, btn={0.10,0.04,0.12}, sell_btn={0.55,0.10,0.45}, buy_btn={0.25,0.05,0.55}, }, { name='Deep Space', accent={0.35, 0.55, 1.00}, bg={0.04, 0.04, 0.07}, text={0.82, 0.88, 1.00}, border_mul=0.60, rounding=6, border_size=1, btn={0.06,0.06,0.12}, sell_btn={0.08,0.22,0.55}, buy_btn={0.04,0.12,0.40}, }, { name='Blood', accent={0.95, 0.10, 0.12}, bg={0.06, 0.03, 0.03}, text={1.00, 0.80, 0.78}, border_mul=0.70, rounding=3, border_size=1.5, btn={0.12,0.04,0.04}, sell_btn={0.55,0.05,0.05}, buy_btn={0.30,0.05,0.08}, }, } _G._mh_themes = _mh_themes function _mh_apply_theme(t) settings.interface.accent_r = t.accent[1] settings.interface.accent_g = t.accent[2] settings.interface.accent_b = t.accent[3] settings.interface.bg_r = t.bg[1] settings.interface.bg_g = t.bg[2] settings.interface.bg_b = t.bg[3] settings.interface.bg_brightness = t.bg[1] settings.interface.text_r = t.text[1] settings.interface.text_g = t.text[2] settings.interface.text_b = t.text[3] local ar,ag,ab = t.accent[1],t.accent[2],t.accent[3] local bm = t.border_mul or 0.70 settings.interface.border_r = ar * bm settings.interface.border_g = ag * bm settings.interface.border_b = ab * bm settings.interface.rounding = t.rounding settings.interface.border_size = t.border_size if t.btn then settings.interface.btn_r = t.btn[1] settings.interface.btn_g = t.btn[2] settings.interface.btn_b = t.btn[3] else settings.interface.btn_r = nil settings.interface.btn_g = nil settings.interface.btn_b = nil end if t.sell_btn then settings.interface.sell_btn_r = t.sell_btn[1] settings.interface.sell_btn_g = t.sell_btn[2] settings.interface.sell_btn_b = t.sell_btn[3] end if t.buy_btn then settings.interface.buy_btn_r = t.buy_btn[1] settings.interface.buy_btn_g = t.buy_btn[2] settings.interface.buy_btn_b = t.buy_btn[3] end accent_color[0] = ar; accent_color[1] = ag; accent_color[2] = ab _G.sl_rounding = nil; _G.sl_border = nil _G.vid_text_col = nil; _G.vid_bg_col = nil; _G.vid_border_col = nil _G.vid_sell_col = nil; _G.vid_buy_col = nil _G.vid_sell_btn_col = nil; _G.vid_buy_btn_col = nil _G.vid_btn_col = nil; _G.sl_btn_bri = nil _wfn7p(); _fwb3h() end _G._mh_apply_theme = _mh_apply_theme function _fwb3h() imgui.SwitchContext() local s=imgui.GetStyle(); local d=settings.general.custom_dpi local bg=settings.interface.bg_brightness or 0.06 local wa=settings.interface.window_alpha or 0.98 local ar=settings.interface.accent_r or 1; local ag=settings.interface.accent_g or .55; local ab=settings.interface.accent_b or 0 local sb_r=settings.interface.sell_btn_r or 0.10; local sb_g=settings.interface.sell_btn_g or 0.45; local sb_b=settings.interface.sell_btn_b or 0.10 _G._mh_sb_r=sb_r; _G._mh_sb_g=sb_g; _G._mh_sb_b=sb_b local bb_r=settings.interface.buy_btn_r or 0.00; local bb_g=settings.interface.buy_btn_g or 0.28; local bb_b=settings.interface.buy_btn_b or 0.50 s.WindowPadding=imgui.ImVec2(8*d,8*d); s.FramePadding=imgui.ImVec2(6*d,5*d) s.ItemSpacing=imgui.ImVec2(6*d,5*d); s.ItemInnerSpacing=imgui.ImVec2(3*d,3*d) s.ScrollbarSize=(settings.interface.scrollbar_w or 12)*d; s.GrabMinSize=(settings.interface.grab_w or 12)*d s.WindowBorderSize=2*d; s.ChildBorderSize=1*d; s.PopupBorderSize=1*d; s.FrameBorderSize=0*d; s.TabBorderSize=0*d s.WindowRounding=4*d; s.ChildRounding=4*d; s.FrameRounding=4*d; s.PopupRounding=4*d s.ScrollbarRounding=3*d; s.GrabRounding=3*d; s.TabRounding=4*d s.WindowTitleAlign=imgui.ImVec2(.5,.5); s.ButtonTextAlign=imgui.ImVec2(.5,.5); s.SelectableTextAlign=imgui.ImVec2(.5,.5) local tr=settings.interface.text_r or .93; local tg=settings.interface.text_g or .88; local tb_=settings.interface.text_b or .78 local bgr=settings.interface.bg_r or bg; local bgg=settings.interface.bg_g or (bg*0.95); local bgb_=settings.interface.bg_b or (bg*0.80) local brr=settings.interface.border_r or (ar*.70); local brg=settings.interface.border_g or (ag*.70); local brb=settings.interface.border_b or (ab*.70) local rnd=settings.interface.rounding or 4; local bsz=settings.interface.border_size or 1 s.WindowRounding=rnd*d; s.ChildRounding=rnd*d; s.FrameRounding=rnd*d; s.PopupRounding=rnd*d; s.TabRounding=rnd*d s.WindowBorderSize=bsz*d; s.ChildBorderSize=bsz*d s.Colors[imgui.Col.Text] =imgui.ImVec4(tr,tg,tb_,1) s.Colors[imgui.Col.TextDisabled] =imgui.ImVec4(tr*.45,tg*.45,tb_*.45,1) s.Colors[imgui.Col.WindowBg] =imgui.ImVec4(bgr,bgg,bgb_,wa) s.Colors[imgui.Col.ChildBg] =imgui.ImVec4(bgr+.03,bgg+.028,bgb_+.015,wa) s.Colors[imgui.Col.PopupBg] =imgui.ImVec4(bgr+.02,bgg+.018,bgb_+.01,wa) s.Colors[imgui.Col.Border] =imgui.ImVec4(brr,brg,brb,.90) s.Colors[imgui.Col.BorderShadow] =imgui.ImVec4(0,0,0,0) s.Colors[imgui.Col.FrameBg] =imgui.ImVec4(bg+.06,bg+.055,bg+.03,wa) s.Colors[imgui.Col.FrameBgHovered] =imgui.ImVec4(bg+.10,bg+.09,bg+.05,wa) s.Colors[imgui.Col.FrameBgActive] =imgui.ImVec4(bg+.14,bg+.12,bg+.07,wa) s.Colors[imgui.Col.TitleBg] =imgui.ImVec4(bg*.8,bg*.8,bg*.8,wa) s.Colors[imgui.Col.TitleBgActive] =imgui.ImVec4(ar*.18,ag*.18,ab*.18,wa) s.Colors[imgui.Col.TitleBgCollapsed] =imgui.ImVec4(bg*.7,bg*.7,bg*.7,wa) s.Colors[imgui.Col.MenuBarBg] =imgui.ImVec4(bg+.04,bg+.035,bg+.02,wa) s.Colors[imgui.Col.ScrollbarBg] =imgui.ImVec4(bg+.01,bg+.01,bg+.005,wa) s.Colors[imgui.Col.ScrollbarGrab] =imgui.ImVec4(ar*.30, ag*.30, ab*.30, 1) s.Colors[imgui.Col.ScrollbarGrabHovered]=imgui.ImVec4(ar*.50, ag*.50, ab*.50, 1) s.Colors[imgui.Col.ScrollbarGrabActive]=imgui.ImVec4(ar*.70, ag*.70, ab*.70, 1) s.Colors[imgui.Col.CheckMark] =imgui.ImVec4(ar, ag, ab, 1) s.Colors[imgui.Col.SliderGrab] =imgui.ImVec4(ar*.5, ag*.5, ab*.5, 1) s.Colors[imgui.Col.SliderGrabActive] =imgui.ImVec4(ar*.75, ag*.75, ab*.75, 1) local btn_r=settings.interface.btn_r; local btn_g=settings.interface.btn_g; local btn_b=settings.interface.btn_b local bta_r=settings.interface.bta_r; local bta_g=settings.interface.bta_g; local bta_b=settings.interface.bta_b _G._mh_btn_bright = settings.interface.btn_bright or 1.0 _G._mh_btn_active_bright= settings.interface.btn_active_bright or 1.0 _G._mh_btn_sat = settings.interface.btn_sat or 1.0 local def_btn_r = btn_r or (bg+.08); local def_btn_g = btn_g or (bg+.07); local def_btn_b = btn_b or (bg+.04) s.Colors[imgui.Col.Button] =imgui.ImVec4(def_btn_r, def_btn_g, def_btn_b, wa) s.Colors[imgui.Col.ButtonHovered] =imgui.ImVec4(math.min(1,def_btn_r+ar*.25), math.min(1,def_btn_g+ag*.25), math.min(1,def_btn_b+ab*.25),wa) s.Colors[imgui.Col.ButtonActive] =imgui.ImVec4(math.min(1,def_btn_r+ar*.40), math.min(1,def_btn_g+ag*.40), math.min(1,def_btn_b+ab*.40),wa) s.Colors[imgui.Col.Header] =imgui.ImVec4(ar*.20, ag*.20, ab*.20, wa) s.Colors[imgui.Col.HeaderHovered] =imgui.ImVec4(ar*.35, ag*.35, ab*.35, wa) s.Colors[imgui.Col.HeaderActive] =imgui.ImVec4(ar*.50, ag*.50, ab*.50, wa) s.Colors[imgui.Col.Separator] =imgui.ImVec4(ar*.25, ag*.25, ab*.25, .60) s.Colors[imgui.Col.Tab] =imgui.ImVec4(bg+.04,bg+.035,bg+.02,wa) s.Colors[imgui.Col.TabHovered] =imgui.ImVec4(ar*.40, ag*.40, ab*.40, wa) s.Colors[imgui.Col.TabActive] =imgui.ImVec4(ar*.30, ag*.30, ab*.30, wa) s.Colors[imgui.Col.ModalWindowDimBg] =imgui.ImVec4(.04,.04,.04,.90) end imgui.OnInitialize(function() imgui.GetIO().IniFilename = nil _fwb3h() fa.Init(16.0) imgui.GetIO().FontGlobalScale = tonumber(settings.interface.font_scale) or 0.70 end) local message_color = 0xFFAA00 local message_color_hex = "{FFAA00}" local fh_last_dlg_title = "" local fh_last_dlg_title_raw = "" local fh_last_dlg_text = "" local fh_last_dlg_id = -1 fh_other_dlg_signal = nil if not sampGetDialogTitle then function sampGetDialogTitle() return fh_last_dlg_title_raw end end if not sampGetDialogText then function sampGetDialogText() return fh_last_dlg_text end end fh_mkt_prices = {} fh_mkt_lavka = {} fh_mkt_log = {} fh_mkt_lavka_log = {} fh_trade_log = {} _XP_SELL_DIV = 1000000 _XP_BUY_DIV = 2000000 _XP_LV_BASE = 100 _G._xp_db = {} _G._xp_db_path = nil function _xp_level(xp) return math.floor(math.sqrt((xp or 0) / _XP_LV_BASE)) end function _xp_for_level(lv) return lv * lv * _XP_LV_BASE end function _xp_from_virtu(sv, bv, tsv, tbv) return math.floor((sv or 0) / _XP_SELL_DIV) + math.floor((bv or 0) / _XP_BUY_DIV) + math.floor((tsv or 0) / _XP_SELL_DIV) + math.floor((tbv or 0) / _XP_BUY_DIV) end function _xp_load() if not _G._xp_db_path then _G._xp_db_path = _zdb1r('player_xp.json') end local f = io.open(_G._xp_db_path, 'r') if f then local ok, d = pcall(decodeJson, f:read('*a')); f:close() if ok and type(d) == 'table' then _G._xp_db = d end end end function _xp_save() if not _G._xp_db_path then _G._xp_db_path = _zdb1r('player_xp.json') end local ok, j = pcall(encodeJson, _G._xp_db) if ok then local f = io.open(_G._xp_db_path, 'w'); if f then f:write(j); f:close() end end end function _xp_add(nick, amount, item_name, op) if not nick or nick == '' or amount <= 0 then return end nick = nick:lower(); op = (op or 'sell'):lower() if not _G._xp_db[nick] then _G._xp_db[nick]={xp=0,level=0,sales_count=0,last_sale='',display_nick=nick, sales_virtu=0,buy_virtu=0,buy_count=0,trade_sell_virtu=0,trade_buy_virtu=0} end local p = _G._xp_db[nick] p.last_sale = os.date('%d.%m %H:%M') if op == 'buy' then p.buy_virtu = (p.buy_virtu or 0) + amount p.buy_count = (p.buy_count or 0) + 1 else p.sales_virtu = (p.sales_virtu or 0) + amount p.sales_count = (p.sales_count or 0) + 1 end p.xp = _xp_from_virtu(p.sales_virtu, p.buy_virtu, p.trade_sell_virtu, p.trade_buy_virtu) p.level = _xp_level(p.xp) end function _xp_add_trade(my_nick, get_money, give_money) if not my_nick or my_nick == '' then return end my_nick = my_nick:lower() if not _G._xp_db[my_nick] then _G._xp_db[my_nick]={xp=0,level=0,sales_count=0,last_sale='',display_nick=my_nick, sales_virtu=0,buy_virtu=0,buy_count=0,trade_sell_virtu=0,trade_buy_virtu=0} end local p = _G._xp_db[my_nick] if (get_money or 0) > 0 then p.trade_sell_virtu = (p.trade_sell_virtu or 0) + get_money end if (give_money or 0) > 0 then p.trade_buy_virtu = (p.trade_buy_virtu or 0) + give_money end p.xp = _xp_from_virtu(p.sales_virtu, p.buy_virtu, p.trade_sell_virtu, p.trade_buy_virtu) p.level = _xp_level(p.xp) _G._xp_rank_cache = nil end function _xp_recalc_from_log() lua_thread.create(function() local nick = '' pcall(function() local _pid = select(2, sampGetPlayerIdByCharHandle(PLAYER_PED)) nick = sampGetPlayerNickname(_pid) or '' end) if nick == '' then pcall(function() nick = sampGetCurrentPlayerName() or '' end) end if nick == '' then nick = (settings.premium and settings.premium.nick) or '' end local nl = nick:lower() if nl == '' then mh_notify('[MH] {ff8800}XP: ник не определён (войдите на сервер)', 0xFFFFFF); return end local tv_sell, cnt_sell, tv_buy, cnt_buy, last = 0, 0, 0, 0, '' local lfs_ok2, lfs2 = pcall(require, 'lfs') local _wd = getWorkingDirectory():gsub('\\', '/') local _seen_deals = {} if lfs_ok2 and lfs2 then local _deals_files = {} local _ok_ls, _err_ls = pcall(function() for _fn in lfs2.dir(_wd) do _deals_files[#_deals_files+1] = _fn end end) if _ok_ls then for _, _fn in ipairs(_deals_files) do if _fn:match('^deals_srv%d+%.json$') then local _fp = _wd .. '/' .. _fn local _ff = io.open(_fp, 'r') if _ff then local _ok_d, _dd = pcall(decodeJson, _ff:read('*a')); _ff:close() if _ok_d and type(_dd) == 'table' then for _date, _day_arr in pairs(_dd) do if type(_day_arr) == 'table' then for _, _de in ipairs(_day_arr) do local _dop = (_de.op or ''):lower() local _damt = (_de.price or 0) * (_de.qty or 1) local _dkey = tostring(_date)..(_de.item or '')..(_de.partner or '')..tostring(_de.qty or 0)..tostring(_de.price or 0) if not _seen_deals[_dkey] then _seen_deals[_dkey] = true if _dop == 'sell' then tv_sell = tv_sell + _damt; cnt_sell = cnt_sell + 1 if _de.t and _de.t ~= '' then last = _de.t end elseif _dop == 'buy' then tv_buy = tv_buy + _damt; cnt_buy = cnt_buy + 1 if _de.t and _de.t ~= '' then last = _de.t end end end end end end end wait(0) end end end end end local batch = 0 for _, le in ipairs(fh_mkt_log) do local _lop = (le.op or ''):upper() local _amt = (le.price or 0) * (le.qty or 1) local _is_sell = (le.own == true and _lop == 'SELL') or (le.own == false and _lop == 'BUY') or (le.own == nil and _lop == 'SELL') local _is_buy = (le.own == true and _lop == 'BUY') or (le.own == false and _lop == 'SELL') or (le.own == nil and _lop == 'BUY') local _lkey = (le.dt and le.dt:sub(1,10) or '') ..(le.item or '')..(le.partner or '') ..tostring(le.qty or 0)..tostring(le.price or 0) if not _seen_deals[_lkey] then if _is_sell then tv_sell = tv_sell + _amt; cnt_sell = cnt_sell + 1 if le.dt and le.dt ~= '' then last = le.dt end elseif _is_buy then tv_buy = tv_buy + _amt; cnt_buy = cnt_buy + 1 if le.dt and le.dt ~= '' then last = le.dt end end end batch=batch+1; if batch%200==0 then wait(0) end end local tv_trade_sell, tv_trade_buy = 0, 0 for _, _trd in ipairs(fh_trade_log or {}) do tv_trade_sell = tv_trade_sell + (_trd.get_money or 0) tv_trade_buy = tv_trade_buy + (_trd.give_money or 0) wait(0) end local xp_from_log = _xp_from_virtu(tv_sell, tv_buy, tv_trade_sell, tv_trade_buy) if not _G._xp_db[nl] then _G._xp_db[nl]={display_nick=nick} end local p = _G._xp_db[nl] p.display_nick = nick p.sales_virtu = tv_sell p.buy_virtu = tv_buy p.trade_sell_virtu = tv_trade_sell p.trade_buy_virtu = tv_trade_buy local xp = xp_from_log p.xp=xp; p.level=_xp_level(xp) if tv_sell > 0 then p.sales_count=cnt_sell end if tv_buy > 0 then p.buy_count=cnt_buy end if last ~= '' then p.last_sale=last end p.is_premium=_mh_is_premium() _G._xp_rank_cache=nil _xp_save(); _xp_push_self() mh_notify('[MH] {aaffaa}XP: Lv.'..p.level..' | '..math.floor(xp)..' XP' ..' | прод: $'..math.floor((tv_sell+tv_trade_sell)/1e6)..'M' ..' | пок: $'..math.floor(((p.buy_virtu or 0)+tv_trade_buy)/1e6)..'M' ..' | трейд+: $'..math.floor(tv_trade_sell/1e6)..'M' ..' | трейд-: $'..math.floor(tv_trade_buy/1e6)..'M', 0xFFFFFF) end) end function _mh_tx_fingerprint() if settings.tx_fingerprint and settings.tx_fingerprint ~= '' then return settings.tx_fingerprint end local log = fh_lv_trade_log or {} if #log < 5 then return nil end local n = #log local res = {} local si = (n > 19) and (n - 19) or 1 for i = n, si, -1 do local e = log[i] if e and e.item and e.price and e.qty and e.op then res[#res+1] = tostring(e.item)..'|'..tostring(e.price)..'|'..tostring(e.qty)..'|'..tostring(e.op) end end if #res < 5 then return nil end local s = table.concat(res, ';') local h, h2 = 5381, 0 for i = 1, #s do local b = string.byte(s, i) h = (h * 33 + b) % 2147483647 h2 = (h2 * 31 + b) % 2147483647 end local fp = string.format('%08x', h) .. string.format('%08x', h2) settings.tx_fingerprint = fp _wfn7p() return fp end function _xp_push_self() local nick = '' pcall(function() local _pid = select(2, sampGetPlayerIdByCharHandle(PLAYER_PED)) nick = sampGetPlayerNickname(_pid) or '' end) if nick == '' then pcall(function() nick = sampGetCurrentPlayerName() or '' end) end if nick == '' then nick = (settings.premium and settings.premium.nick) or '' end if nick == '' then return end if nick:lower():match('^player_[%x%d]+$') then return end local _now_ps = os.time() if _G._xp_push_last_ts and (_now_ps - _G._xp_push_last_ts) < 60 then return end local nick_lo = nick:lower() if not _G._xp_db[nick_lo] then _G._xp_db[nick_lo] = {} end _G._xp_db[nick_lo].display_nick = nick local p = _G._xp_db[nick_lo] or {} local _live_idx = _mpf7d() local _sel_idx = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) local _boot_idx = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) local _srv_idx if _live_idx > 0 then _srv_idx = _live_idx + 1 elseif _boot_idx then _srv_idx = _boot_idx elseif _sel_idx and _sel_idx > 1 then _srv_idx = _sel_idx end local srv = (_srv_idx and (ARZ_SERVERS[_srv_idx] or {}).id) or -1 if srv == -1 then local _cached_srv = _G._xp_db[nick_lo] and _G._xp_db[nick_lo].server if _cached_srv and _cached_srv ~= -1 then srv = _cached_srv end end if srv ~= -1 then _G._xp_db[nick_lo].server = srv end local _tx_fp = _mh_tx_fingerprint() or '' local body = encodeJson({ nick = _to_utf8(nick), server = srv, xp = math.floor(p.xp or 0), level = math.floor(p.level or 0), sales_virtu = math.floor(p.sales_virtu or 0), sales_count = math.floor(p.sales_count or 0), buy_virtu = math.floor(p.buy_virtu or 0), buy_count = math.floor(p.buy_count or 0), trade_sell_virtu = math.floor(p.trade_sell_virtu or 0), trade_buy_virtu = math.floor(p.trade_buy_virtu or 0), premium = _mh_is_premium(), tx_fingerprint = _tx_fp, }) local _hash_key = tostring(nick) .. '|' .. tostring(math.floor(p.xp or 0)) .. '|' .. tostring(math.floor(p.sales_virtu or 0)) .. '|' .. tostring(math.floor(p.sales_count or 0)) .. '|' .. tostring(math.floor(p.buy_virtu or 0)) .. '|' .. tostring(math.floor(p.trade_sell_virtu or 0)) .. '|' .. tostring(math.floor(p.trade_buy_virtu or 0)) .. '|' .. tostring(srv) if _G._xp_push_last_hash == _hash_key then return end _G._xp_push_last_hash = _hash_key _G._xp_push_last_ts = _now_ps _jmx9s(_vbr7n..'/rating/push', body, function() end) end _G._xp_srv_data={}; _G._xp_srv_loaded=false; _G._xp_srv_loading=false function _xp_pull_srv(srv_override) if _G._xp_srv_loading then return end _G._xp_srv_loading=true local _rtg_filter_idx = _G._rtg_srv_filter and _G._rtg_srv_filter[0] or 0 local srv = srv_override if srv == nil then if _rtg_filter_idx == 0 then local _live = _mpf7d() if _live > 0 then srv = (ARZ_SERVERS[_live + 1] or {}).id or -1 elseif _G.arz_srv_sel and _G.arz_srv_sel[0] > 0 then srv = (ARZ_SERVERS[_G.arz_srv_sel[0] + 1] or {}).id or -1 elseif _G.mh_boot_srv_idx and _G.mh_boot_srv_idx > 0 then srv = (ARZ_SERVERS[_G.mh_boot_srv_idx + 1] or {}).id or -1 else srv = -1 end else srv = (ARZ_SERVERS[_rtg_filter_idx] or {}).id or -1 end end _G._xp_srv_filter_id = srv _fwm2c(_vbr7n..'/rating/pull?server='..tostring(srv)..'&ver='.._MH_VER, (settings.premium and settings.premium.tok) or _G._mh_session_tok or '', (settings.premium and settings.premium.nick) or '', function(code,body,_e) _G._xp_srv_loading=false if code==200 and body and body~='' then local ok,parsed=pcall(decodeJson,body) if ok and type(parsed)=='table' then _G._xp_srv_data=parsed; _G._xp_srv_loaded=true local _my_nl = '' pcall(function() local _pid = select(2, sampGetPlayerIdByCharHandle(PLAYER_PED)) _my_nl = (sampGetPlayerNickname(_pid) or ''):lower() end) if _my_nl == '' then pcall(function() _my_nl = (sampGetCurrentPlayerName() or ''):lower() end) end if _my_nl ~= '' then for _,e in ipairs(parsed) do if type(e.nick)=='string' and e.nick:lower()==_my_nl then local _loc = _G._xp_db[_my_nl] if not _loc then _loc={}; _G._xp_db[_my_nl]=_loc end local srv_xp = e.xp or 0 if srv_xp > (_loc.xp or 0) then _loc.xp = srv_xp _loc.level = _xp_level(srv_xp) _loc.sales_virtu = math.max(e.sales_virtu or 0, _loc.sales_virtu or 0) _loc.sales_count = math.max(e.sales_count or 0, _loc.sales_count or 0) _loc.trade_sell_virtu = math.max(e.trade_sell_virtu or 0, _loc.trade_sell_virtu or 0) _loc.trade_buy_virtu = math.max(e.trade_buy_virtu or 0, _loc.trade_buy_virtu or 0) _xp_save() end break end end end _G._xp_rank_cache=nil end end end) end _G._xp_rank_cache=nil; _G._xp_rank_cache_ver=-1 local function _mh_is_fake_nick(nick) if not nick or nick == '' then return true end return nick:lower():match('^player_[%x%d]+$') ~= nil end function _xp_get_rank() if _G._xp_srv_loaded and #(_G._xp_srv_data or {})>0 then if _G._xp_rank_cache and _G._xp_rank_cache_ver==#_G._xp_srv_data then return _G._xp_rank_cache end local list={}; local _seen_nicks={} for _,e in ipairs(_G._xp_srv_data) do local _nl=(e.nick or ''):lower() if _mh_is_fake_nick(_nl) then goto _rtg_skip end if not _seen_nicks[_nl] then _seen_nicks[_nl]=true local _loc_prem = _G._xp_db and _G._xp_db[_nl] and _G._xp_db[_nl].is_premium local _is_prem = (e.premium == true) or (e.premium == 1) or (_loc_prem == true) local _e_tsv = e.trade_sell_virtu or 0 local _e_tbv = e.trade_buy_virtu or 0 local _e_xp = _xp_from_virtu(e.sales_virtu or 0, e.buy_virtu or 0, _e_tsv, _e_tbv) local _e_lv = _xp_level(_e_xp) table.insert(list,{nick=_nl,display_nick=e.display_nick or e.nick or '?', xp=_e_xp,level=_e_lv, sales_virtu=e.sales_virtu or 0,sales_count=e.sales_count or 0, buy_virtu=e.buy_virtu or 0,buy_count=e.buy_count or 0, trade_sell_virtu=_e_tsv, trade_buy_virtu=_e_tbv, is_premium=_is_prem, server=e.server or -1}) end ::_rtg_skip:: end do local _my_nl_m = "" pcall(function() local _pid = select(2, sampGetPlayerIdByCharHandle(PLAYER_PED)) _my_nl_m = (sampGetPlayerNickname(_pid) or ""):lower() end) if _my_nl_m == "" then pcall(function() _my_nl_m = (sampGetCurrentPlayerName() or ""):lower() end) end if _my_nl_m ~= "" and not _seen_nicks[_my_nl_m] then local _loc_me = _G._xp_db and _G._xp_db[_my_nl_m] if _loc_me and (_loc_me.xp or 0) > 0 then local _is_prem_me = _mh_is_premium() or (_loc_me.is_premium == true) table.insert(list, { nick = _my_nl_m, display_nick = _loc_me.display_nick or _my_nl_m, xp = _loc_me.xp or 0, level = _loc_me.level or 0, sales_virtu = _loc_me.sales_virtu or 0, sales_count = _loc_me.sales_count or 0, buy_virtu = _loc_me.buy_virtu or 0, buy_count = _loc_me.buy_count or 0, trade_sell_virtu = _loc_me.trade_sell_virtu or 0, trade_buy_virtu = _loc_me.trade_buy_virtu or 0, is_premium = _is_prem_me, server = _loc_me.server or -1, }) end end end table.sort(list,function(a,b) if a.level~=b.level then return a.level>b.level end; return a.xp>b.xp end) _G._xp_rank_cache=list; _G._xp_rank_cache_ver=#_G._xp_srv_data return list end local cur=0; for _ in pairs(_G._xp_db) do cur=cur+1 end if _G._xp_rank_cache and _G._xp_rank_cache_ver==cur then return _G._xp_rank_cache end local list={} for nick,p in pairs(_G._xp_db) do table.insert(list,{nick=nick,display_nick=p.display_nick or nick, xp=p.xp or 0,level=p.level or 0, sales_virtu=p.sales_virtu or 0,sales_count=p.sales_count or 0, buy_virtu=p.buy_virtu or 0,buy_count=p.buy_count or 0, trade_sell_virtu=p.trade_sell_virtu or 0, trade_buy_virtu=p.trade_buy_virtu or 0, is_premium=p.is_premium, server=p.server or -1}) end table.sort(list,function(a,b) if a.level~=b.level then return a.level>b.level end; return a.xp>b.xp end) _G._xp_rank_cache=list; _G._xp_rank_cache_ver=cur; return list end fh_mkt_last_update = nil fh_mkt_lavka_ids = {} fh_mkt_lavka_sep = {} fh_mkt_lavka_page_id = -1 fh_mkt_cp_scanning = false fh_mkt_cp_page = 0 fh_mkt_cp_prev_text = nil fh_mkt_cp_go_idx = nil fh_mkt_lv_scanning = false fh_mkt_lv_done = 0 fh_mkt_lv_total = 0 fh_mkt_lv_cur_dialog = 3082 fh_lv_autosell_running = false fh_lv_autostart_enabled = false fh_lv_autosell_done = 0 fh_lv_autobuy_running = false fh_ab_search_idx = 0 fh_lv_autosell_status = '' fh_lv_autobuy_status = '' fh_lv_trade_log = {} local _mh_chat_persist_path = getWorkingDirectory():gsub("\\\\","/") .. "/MarketHelper/MarketHelper_chat.json" local _lvn7s_pending = false local _lvn7s_last = 0 local function _lvn7s() local _now = os.time() if _lvn7s_pending then return end if (_now - _lvn7s_last) < 30 then return end _lvn7s_pending = true local _snap = fh_session_chat lua_thread.create(function() local ok, j = pcall(encodeJson, _snap) if ok then _mh_ensure_dir(getWorkingDirectory():gsub('\\', '/'):gsub('/$', '') .. '/MarketHelper') local f = io.open(_mh_chat_persist_path, 'w') if f then f:write(j); f:close() end end _lvn7s_pending = false _lvn7s_last = os.time() end) end _G._lvn7s = _lvn7s lua_thread.create(function() wait(60000) while true do _lvn7s_pending = false _lvn7s_last = 0 _lvn7s() wait(60000) end end) function MH_util.gzp1k() local f = io.open(_mh_chat_persist_path,"r") if f then local ok, d = pcall(decodeJson, f:read("*a")); f:close() if ok and type(d) == "table" then return d end end return {} end fh_session_chat = MH_util.gzp1k() if settings.chat_log_enabled == nil then settings.chat_log_enabled = true end fh_session_start_dt = os.date('%d.%m %H:%M') fh_session_log_start = nil fh_overlay_log = {} fh_lv_autosell_preset = {} fh_active_preset_idx = 1 if not settings.presets or type(settings.presets) ~= "table" or #settings.presets == 0 then local old = settings.autosell_preset settings.presets = {{name="Пресет 1", items=(old and type(old)=="table") and old or {}}} settings.active_preset = 1 _wfn7p() end if not settings.active_preset then settings.active_preset = 1 end fh_active_preset_idx = settings.active_preset local _ap = settings.presets[fh_active_preset_idx] fh_lv_autosell_preset = (_ap and _ap.items) or {} fh_lv_autobuy_preset = {} fh_ab_preset_idx = 1 if settings and settings.autobuy_preset and type(settings.autobuy_preset) == "table" then fh_lv_autobuy_preset = settings.autobuy_preset end fh_lv_sell_confirmed = false fh_lv_sell_forbidden = false fh_lv_sell_no_slots = false fh_mkt_lavka_all_tds = {} fh_mkt_lavka_slot_w = nil fh_mkt_lavka_slot_h = nil fh_mkt_lavka_page_ready = false fh_mkt_shop_dlg_id = -1 fh_mkt_shop_inv_tds = {} fh_mkt_shop_ui_open = false fh_mkt_shop_price_dlg = -1 fh_mkt_shop_price_item = '' fh_mkt_put_td_id = -1 fh_mkt_shop_price_qty = true fh_lv_autobuy_preset = {} fh_lv_inventory = {} fh_lv_inv_scanning = false fh_lv_allitems_srch = '' fh_mkt_cp_deep_scanning = false fh_mkt_cp_deep_page_idx = 0 fh_mkt_cp_deep_cur_page_items = {} fh_mkt_cp_deep_items = {} fh_mkt_cp_deep_idx = 0 fh_mkt_cp_deep_total = 0 fh_mkt_cp_deep_done = 0 fh_mkt_cp_deep_dlg_id = nil fh_mkt_cp_deep_state = 'idle' fh_mkt_cp_deep_item_dlg = nil fh_mkt_lavka_slot_w = nil fh_mkt_lavka_slot_h = nil local function _bmj2p(e, price, qty, side) qty = qty or 1 if side == 'buy' then e.b_totalP=(e.b_totalP or 0)+price*qty; e.b_totalC=(e.b_totalC or 0)+qty e.b_avg=math.floor(e.b_totalP/e.b_totalC) e.b_min=e.b_min and math.min(e.b_min,price) or price e.b_max=e.b_max and math.max(e.b_max,price) or price e.b_last=price; e.b_scans=(e.b_scans or 0)+1 else e.s_totalP=(e.s_totalP or 0)+price*qty; e.s_totalC=(e.s_totalC or 0)+qty e.s_avg=math.floor(e.s_totalP/e.s_totalC) e.s_min=e.s_min and math.min(e.s_min,price) or price e.s_max=e.s_max and math.max(e.s_max,price) or price e.s_last=price; e.s_scans=(e.s_scans or 0)+1 end e.date=os.date("%d.%m.%Y %H:%M"); return e end fh_other_shops = {} if false and settings and settings.other_shops and type(settings.other_shops) == 'table' then fh_other_shops = settings.other_shops for k, sh in pairs(fh_other_shops) do if sh.owner then local clean = sh.owner:match('%-%s*([A-Za-z_][A-Za-z0-9_]+)') or sh.owner:match('^([A-Za-z_][A-Za-z0-9_]+)') if clean and clean ~= sh.owner then sh.owner = clean end end end for _, sh in pairs(fh_other_shops) do for _, it in ipairs(sh.sell_items or {}) do if it.name and it.name ~= '' and it.price and it.price > 0 then fh_mkt_lavka[it.name] = _bmj2p(fh_mkt_lavka[it.name] or {}, it.price, it.qty or 1, 'sell') end end for _, it in ipairs(sh.buy_items or {}) do if it.name and it.name ~= '' and it.price and it.price > 0 then fh_mkt_lavka[it.name] = _bmj2p(fh_mkt_lavka[it.name] or {}, it.price, it.qty or 1, 'buy') end end end end fh_other_shop_cur = nil fh_other_shop_scanning = false fh_player_dlg_open = false fh_other_scan_done = 0 fh_other_scan_total = 0 fh_other_shop_owner = '' fh_other_shop_price_tds = {} fh_other_shop_pending_num = nil mh_own_shop_num = nil mh_pending_lavka_buf = {} _G._mh_passive_cooldown = 300 _G._mh_passive_buf_ts = 0 cm_radius_enabled = false cm_catch_enabled = false cm_render_enabled = false cm_catch_status = '' fh_mkt_auto = {} fh_mkt_auto_last_upd = nil fh_mkt_auto_scanning = false fh_mkt_auto_page = 0 fh_mkt_auto_prev_text = nil fh_mkt_auto_go_idx = nil fh_mkt_auto_deep_scanning = false fh_mkt_auto_deep_go_idx = nil fh_mkt_auto_deep_done = 0 fh_storage_running = false fh_storage_idx = 0 local function _zdb1r(file) return getWorkingDirectory():gsub('\\\\','/') .. '/MarketHelper/FH_' .. file end local _ryb5t_pending = false local function _ryb5t() if _ryb5t_pending then return end _ryb5t_pending = true lua_thread.create(function() wait(400) _ryb5t_pending = false for _,p in ipairs({ {'mkt_prices.json', fh_mkt_prices}, {'mkt_log.json', fh_mkt_log}, {'mkt_lavka.json', fh_mkt_lavka}, {'mkt_lavka_log.json', fh_mkt_lavka_log}, {'mkt_auto.json', fh_mkt_auto}, {'trade_log.json', fh_trade_log}, }) do local ok,j = pcall(encodeJson, p[2]) if ok then local f=io.open(_zdb1r(p[1]),'w'); if f then f:write(j); f:close() end end wait(0) end if fh_mkt_last_update then local f=io.open(_zdb1r('mkt_last_update.txt'),'w') if f then f:write(fh_mkt_last_update); f:close() end end end) end local function _lkz7q() for _,p in ipairs({ {'mkt_prices.json', 'fh_mkt_prices'}, {'mkt_log.json', 'fh_mkt_log'}, {'mkt_lavka.json', 'fh_mkt_lavka'}, {'mkt_lavka_log.json', 'fh_mkt_lavka_log'}, {'mkt_auto.json', 'fh_mkt_auto'}, {'trade_log.json', 'fh_trade_log'}, }) do local f=io.open(_zdb1r(p[1]),'r') if f then local ok,d=pcall(decodeJson,f:read('*a')); f:close() if ok and type(d)=='table' then _G[p[2]]=d end end wait(0) end local fu=io.open(_zdb1r('mkt_last_update.txt'),'r') if fu then fh_mkt_last_update=fu:read('*a'); fu:close() end local clean_auto = {} for k,v in pairs(fh_mkt_auto) do if type(k) == 'string' and type(v) == 'table' then local ck = k:gsub('##%a%d+$','') if ck ~= '' then if clean_auto[ck] then local ex = clean_auto[ck] ex.s_avg = v.s_avg or ex.s_avg ex.s_min = (ex.s_min and v.s_min) and math.min(ex.s_min,v.s_min) or ex.s_min or v.s_min ex.s_max = (ex.s_max and v.s_max) and math.max(ex.s_max,v.s_max) or ex.s_max or v.s_max ex.date = ex.date or v.date else clean_auto[ck] = v end end end end fh_mkt_auto = clean_auto _mh_db_bump() end local function _gfr3j(date_str) return getWorkingDirectory():gsub('\\\\','/') .. '/FH_daily_prices/' .. date_str .. '.json' end local function _nvw9k() local lfs = require('lfs') local dir = getWorkingDirectory():gsub('\\\\','/') .. '/MarketHelper/FH_daily_prices' if not lfs.attributes(dir) then lfs.mkdir(dir) end end function MH_util.kyb5x() _nvw9k() local today = os.date('%Y-%m-%d') local path = _gfr3j(today) local existing = {} local f = io.open(path, 'r') if f then local ok, d = pcall(decodeJson, f:read('*a')); f:close() if ok and type(d) == 'table' then existing = d end end local _save_srv_id = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)() local _sp_idx = {} local _bp_idx = {} for _, sh in pairs(fh_other_shops) do if type(sh) ~= 'table' then goto _save_sh_next end if _save_srv_id ~= -1 and sh.server_id and sh.server_id ~= -1 and sh.server_id ~= _save_srv_id then goto _save_sh_next end for _, si in ipairs(sh.sell_items or {}) do if si.name and si.name ~= '' and si.price and si.price > 0 then if not existing[si.name] then existing[si.name] = {} end local rec = existing[si.name] local q = si.qty or 1 rec.s_totalP = (rec.s_totalP or 0) + si.price * q rec.s_totalC = (rec.s_totalC or 0) + q if sh.server_id then rec.server_id = sh.server_id end if not _sp_idx[si.name] then _sp_idx[si.name] = {} end local _seen = false for _, _pp in ipairs(_sp_idx[si.name]) do if _pp == si.price then _seen=true; break end end if not _seen and #_sp_idx[si.name] < 10 then table.insert(_sp_idx[si.name], si.price) end end end for _, bi in ipairs(sh.buy_items or {}) do if bi.name and bi.name ~= '' and bi.price and bi.price > 0 then if not existing[bi.name] then existing[bi.name] = {} end local rec = existing[bi.name] local q = bi.qty or 1 rec.b_totalP = (rec.b_totalP or 0) + bi.price * q rec.b_totalC = (rec.b_totalC or 0) + q rec.b_max = rec.b_max and math.max(rec.b_max, bi.price) or bi.price if not _bp_idx[bi.name] then _bp_idx[bi.name] = {} end local _seen = false for _, _pp in ipairs(_bp_idx[bi.name]) do if _pp == bi.price then _seen=true; break end end if not _seen and #_bp_idx[bi.name] < 10 then table.insert(_bp_idx[bi.name], bi.price) end end end ::_save_sh_next:: end for nm, prices in pairs(_sp_idx) do if existing[nm] then table.sort(prices); existing[nm].s_prices = prices end end for nm, prices in pairs(_bp_idx) do if existing[nm] then table.sort(prices, function(a,b) return a>b end); existing[nm].b_prices = prices end end local ok, j = pcall(encodeJson, existing) if ok then local fw = io.open(path, 'w') if fw then fw:write(j); fw:close() end end end _G._mh_daily_push_last = 0 local function _mh_upload_daily_prices() local now = os.time() if now - (_G._mh_daily_push_last or 0) < 3600 then return end local srv_id = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)() if srv_id == -1 then return end local today = os.date('%Y-%m-%d') local path = _gfr3j(today) local f = io.open(path, 'r') if not f then return end local ok, data = pcall(decodeJson, f:read('*a')); f:close() if not ok or type(data) ~= 'table' then return end local _merged = {} for nm, rec in pairs(data) do if type(nm) == 'string' and type(rec) == 'table' then local canon = _mh_norm_nm(nm) if not _merged[canon] then _merged[canon] = {s_totalP=0,s_totalC=0,b_totalP=0,b_totalC=0,b_max=0} end local m = _merged[canon] m.s_totalP = m.s_totalP + (rec.s_totalP or 0) m.s_totalC = m.s_totalC + (rec.s_totalC or 0) m.b_totalP = m.b_totalP + (rec.b_totalP or 0) m.b_totalC = m.b_totalC + (rec.b_totalC or 0) m.b_max = math.max(m.b_max, rec.b_max or 0) end end local items = {} for nm, rec in pairs(_merged) do if type(nm) == 'string' and type(rec) == 'table' then local s_avg = rec.s_totalC > 0 and math.floor(rec.s_totalP / rec.s_totalC) or nil local b_avg = rec.b_totalC > 0 and math.floor(rec.b_totalP / rec.b_totalC) or nil if s_avg or b_avg then table.insert(items, { name = nm, s_avg = s_avg or 0, s_cnt = rec.s_totalC or 0, b_avg = b_avg or 0, b_cnt = rec.b_totalC or 0, b_max = rec.b_max or 0, }) end end end if #items == 0 then return end _G._mh_daily_push_last = now if mh_debug_enabled then local ok_full, full_body = pcall(encodeJson, {server_id=srv_id, date=today, items=items}) mh_notify('[MH Cloud] {8888ff}Daily: '..#items..' items, полный размер: '.. (ok_full and (#full_body..' байт') or 'ошибка encode'), 0xFFFFFF) end local BATCH_SIZE = 25 local batches = {} for i = 1, #items, BATCH_SIZE do local chunk = {} for j = i, math.min(i + BATCH_SIZE - 1, #items) do chunk[#chunk + 1] = items[j] end batches[#batches + 1] = chunk end local total_ok, total_err = 0, 0 local function send_batch(idx) if idx > #batches then if mh_debug_enabled then if total_err == 0 then mh_notify('[MH Cloud] {00cc88}Суточные: '..#items..' товаров ('..#batches..' парт.)', 0xFFFFFF) else mh_notify('[MH Cloud] {ff6666}Суточные: '..total_ok..'/'..#batches..' парт. отправлено, ошибок: '..total_err, 0xFFFFFF) end end return end local ok_j, body = pcall(encodeJson, {server_id=srv_id, date=today, items=batches[idx]}) if not ok_j then total_err = total_err + 1 send_batch(idx + 1) return end if mh_debug_enabled then mh_notify('[MH Cloud] {8888ff}Парт. '..idx..'/'..#batches..': '..#batches[idx]..' шт., '..#body..' байт, url='.._vbr7n..'/daily/push', 0xFFFFFF) end _jmx9s(_vbr7n .. '/daily/push', body, function(code, text, err) if code == 200 then total_ok = total_ok + 1 else total_err = total_err + 1 if mh_debug_enabled then local _txt_s = tostring(text or ''):sub(1, 150) mh_notify('[MH Cloud] {ff6666}Парт. '..idx..' код='..tostring(code)..' err='..tostring(err or '-')..' body='.._txt_s, 0xFFFFFF) end end send_batch(idx + 1) end) end send_batch(1) end _G._mh_upload_daily_prices = _mh_upload_daily_prices lua_thread.create(function() wait(60000) while true do _mh_upload_daily_prices() wait(3600000) end end) _G._mh_daily_cache = {} _G._mh_daily_cache_srv = -1 _G._mh_daily_pull_last = 0 local _mh_daily_pulling = false local function _mh_daily_pull(silent) if _mh_daily_pulling then return end local now = os.time() if now - (_G._mh_daily_pull_last or 0) < 1800 then return end local sid = _mh_get_srv_id and _mh_get_srv_id() or -1 if sid == -1 then return end _mh_daily_pulling = true local _dpn='' pcall(function() _dpn=sampGetPlayerNickname(select(2,sampGetPlayerIdByCharHandle(PLAYER_PED))) or '' end) if _dpn=='' then pcall(function() _dpn=sampGetCurrentPlayerName() or '' end) end if _dpn=='' then _dpn=(settings.premium and settings.premium.nick) or '' end _fwm2c(_vbr7n..'/daily/pull?server='..sid..'&days=30&nick='.._dpn..'&ver='.._MH_VER, (settings.premium and settings.premium.tok) or '', _dpn, function(code, body, _e) _mh_daily_pulling = false _G._mh_daily_pull_last = os.time() if not body or #body == 0 then return end local ok, resp = pcall(decodeJson, body) if not ok or type(resp) ~= 'table' or not resp.ok then return end local cache = {} for _, itm in ipairs(resp.items or {}) do local nm = itm.name or '' if nm ~= '' then cache[nm:lower()] = itm.history or {} end end _G._mh_daily_cache = cache _G._mh_daily_cache_srv = sid _G._mh_daily_cache_ver = (_G._mh_daily_cache_ver or 0) + 1 _G._mkt_price_gcache = {} _G._dtl_cache_nm = nil; _G._dtl_dirty = true if not silent and mh_debug_enabled then mh_notify('[MH Cloud] {00cc88}Daily: '..tostring(#(resp.items or {}))..' товаров', 0xFFFFFF) end end) end _G._mh_daily_pull = _mh_daily_pull lua_thread.create(function() wait(90000) while true do _mh_daily_pull(true) wait(1800000) end end) _G._mh_deals_push_last = 0 _G._mh_deals_cache = {} _G._mh_deals_cache_srv = -1 local _utf8 = function(s) if not s then return '' end; local ok,r=pcall(function() return require('encoding').UTF8:encode(tostring(s)) end); return ok and r or tostring(s) end local function _mh_get_srv_id() -- [MH FIX] РўРѕС‚ Р¶Рµ баг, что Рё РІ watch/fav/arb-циклах: старое выражение -- (_G.arz_srv_sel and (_G.arz_srv_sel[0]+1)) or (_G.mh_boot_srv_idx and ...) -- считало arz_srv_sel[0]==0 ("Р’СЃРµ сервера") РРЎРўРННЫМ значением (0+1=1), -- поэтому "or" РЅРёРєРѕРіРґР° РЅРµ РґРѕС…РѕРґРёР» РґРѕ fallback РЅР° текущий Р¶РёРІРѕР№ сервер. -- Р’ итоге _mh_get_srv_id всегда возвращал id=-1 Сѓ всех, кто РЅРµ выбирал -- сервер вручную РІ выпадающем СЃРїРёСЃРєРµ - Р° _mh_upload_deals() РїСЂРё sid==-1 -- молча выходит Рё РќРКОГДА РЅРµ отправляет сделки. Приоритет теперь как Сѓ -- рабочего arb-цикла: явный выбор РІ UI > текущий Р¶РёРІРѕР№ сервер > boot. local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end _G._mh_get_srv_id = _mh_get_srv_id local function _mh_deals_collect_log(sid) local path = _zdb1r('deals_srv'..tostring(sid)..'.json') local f = io.open(path, 'r') if not f then return {} end local ok, deals = pcall(decodeJson, f:read('*a')); f:close() if not ok or type(deals) ~= 'table' then return {} end local cutoff = os.date('%Y-%m-%d', os.time() - 30*86400) local agg = {} for date, day_list in pairs(deals) do if type(date)=='string' and date>=cutoff and type(day_list)=='table' then for _, d in ipairs(day_list) do local nm = d.item or ''; local op = d.op or 'sell' if nm~='' and (d.price or 0)>0 then local k = date..'|'..nm:lower()..'|'..op agg[k] = agg[k] or {sum=0,qty=0,date=date,name=nm,op=op} local q = d.qty or 1 agg[k].sum = agg[k].sum + d.price*q agg[k].qty = agg[k].qty + q end end end end local items = {} for _, v in pairs(agg) do if v.qty>0 then table.insert(items,{date=v.date,name=_utf8(v.name),op=v.op, src='log',avg_price=math.floor(v.sum/v.qty),total_qty=v.qty}) end end return items end local function _mh_deals_collect_deep() local items = {} local cutoff = os.date('%Y-%m-%d', os.time() - 30*86400) for nm, e in pairs(fh_mkt_prices) do if e and e.cp_hist then for _, h in ipairs(e.cp_hist) do local date = h.dt or '' if date>=cutoff and (h.price or 0)>0 then table.insert(items,{date=date,name=_utf8(nm),op='sell', src='deep',avg_price=h.price,total_qty=(h.qty or 1)}) end end end end return items end local _mh_deals_pushing = false local function _mh_upload_deals() local now = os.time() if now-(_G._mh_deals_push_last or 0)<3600 then return end if _mh_deals_pushing then return end local sid = _mh_get_srv_id() if sid==-1 then return end local all = {} for _,v in ipairs(_mh_deals_collect_log(sid)) do table.insert(all,v) end for _,v in ipairs(_mh_deals_collect_deep()) do table.insert(all,v) end if #all==0 then return end _G._mh_deals_push_last = now _mh_deals_pushing = true local ok_j, body = pcall(encodeJson,{server_id=sid,items=all}) if not ok_j then _mh_deals_pushing=false; return end _jmx9s(_vbr7n..'/deals/push', body, function(code,_r,_e) _mh_deals_pushing = false if mh_debug_enabled and code==200 then mh_notify('[MH Cloud] {00cc88}Deals push: '..#all..' records', 0xFFFFFF) end end) end _G._mh_upload_deals = _mh_upload_deals local _mh_deals_pulling = false local function _mh_deals_pull(silent) if _mh_deals_pulling then return end local sid = _mh_get_srv_id() if sid==-1 then return end _mh_deals_pulling = true _fwm2c(_vbr7n..'/deals/pull?server='..sid..'&days=30&ver='.._MH_VER, (settings.premium and settings.premium.tok) or '', (settings.premium and settings.premium.nick) or '', function(code, body, _e) _mh_deals_pulling = false if not body or #body==0 then return end local ok, resp = pcall(decodeJson, body) if not ok or type(resp)~='table' or not resp.ok then return end local cache = {} for _, itm in ipairs(resp.items or {}) do local nm = itm.name or '' if nm~='' then cache[nm:lower()] = itm.history or {} end end _G._mh_deals_cache = cache _G._mh_deals_cache_srv = sid _G._mh_deals_cache_ver = (_G._mh_deals_cache_ver or 0) + 1 _G._mkt_price_gcache = {} _G._dtl_cache_nm = nil; _G._dtl_dirty = true lua_thread.create(function() wait(100); _mh_rebuild_shop_hist_cache() end) if not silent then mh_notify('[MH Cloud] {00cc88}Deals: '..tostring(#(resp.items or {}))..' items', 0xFFFFFF) end end) end _G._mh_deals_pull = _mh_deals_pull lua_thread.create(function() wait(90000) _mh_deals_pull(true) _mh_upload_deals() while true do wait(3600000) _mh_deals_pull(true) _mh_upload_deals() end end) function _ztc7m(server_id) if not mh_arz_data or #mh_arz_data == 0 then return end if not mh_arz_items_db or not mh_arz_items_loaded then return end _nvw9k() local today = os.date('%Y-%m-%d') local path = _gfr3j(today) local existing = {} local old_f = io.open(path, 'r') if old_f then local ok, d = pcall(decodeJson, old_f:read('*a')); old_f:close() if ok and type(d) == 'table' then for nm, e in pairs(d) do if type(nm) == 'string' and type(e) == 'table' and e._from_log then existing[nm] = e end end end end local fresh = {} local _ztc_batch = 0 for _, lv in ipairs(mh_arz_data) do _ztc_batch = _ztc_batch + 1; if _ztc_batch % 100 == 0 then wait(0) end if type(lv) ~= 'table' then goto _api_daily_next end if server_id and server_id ~= -1 and lv.serverId ~= server_id then goto _api_daily_next end if lv.items_sell and lv.price_sell then for ii, iid in ipairs(lv.items_sell) do local bid = _bqs3v(iid) local nm = mh_arz_items_db[bid] local pr = lv.price_sell[ii] local q = (lv.count_sell and lv.count_sell[ii]) or 1 if nm and nm ~= '' and pr and pr > 0 then if not fresh[nm] then fresh[nm] = {s_totalP=0,s_totalC=0,b_totalP=0,b_totalC=0} end fresh[nm].s_totalP = fresh[nm].s_totalP + pr * q fresh[nm].s_totalC = fresh[nm].s_totalC + q end end end if lv.items_buy and lv.price_buy then for ii, iid in ipairs(lv.items_buy) do local bid = _bqs3v(iid) local nm = mh_arz_items_db[bid] local pr = lv.price_buy[ii] local q = (lv.count_buy and lv.count_buy[ii]) or 1 if nm and nm ~= '' and pr and pr > 0 then if not fresh[nm] then fresh[nm] = {s_totalP=0,s_totalC=0,b_totalP=0,b_totalC=0} end fresh[nm].b_totalP = fresh[nm].b_totalP + pr * q fresh[nm].b_totalC = fresh[nm].b_totalC + q end end end ::_api_daily_next:: end for nm, v in pairs(fresh) do if not existing[nm] then existing[nm] = {} end existing[nm].s_totalP = v.s_totalP existing[nm].s_totalC = v.s_totalC existing[nm].b_totalP = v.b_totalP existing[nm].b_totalC = v.b_totalC existing[nm]._scan_ts = os.time() end local ok2, j2 = pcall(encodeJson, existing) if ok2 then local fw = io.open(path, 'w') if fw then fw:write(j2); fw:close() end end _G._dtl_cache_nm = nil; _G._dtl_dirty = true lua_thread.create(function() wait(200); _mh_rebuild_shop_hist_cache() end) end local function fh_get_daily_avg_price(item_name) local lfs = require('lfs') local dir = getWorkingDirectory():gsub('\\\\','/') .. '/MarketHelper/FH_daily_prices' if not lfs.attributes(dir) then return nil end local total_p, total_c = 0, 0 for fname in lfs.dir(dir) do if fname:match('^%d%d%d%d%-%d%d%-%d%d%.json$') then local f = io.open(dir .. '/' .. fname, 'r') if f then local ok, data = pcall(decodeJson, f:read('*a')); f:close() if ok and type(data) == 'table' and data[item_name] then local e = data[item_name] if e.s_totalP and e.s_totalC and e.s_totalC > 0 then total_p = total_p + e.s_totalP total_c = total_c + e.s_totalC end end end end end if total_c > 0 then return math.floor(total_p / total_c) end return nil end local _dly_file_cache = {} local _dly_item_cache = {} local _dly_dir_sig = nil local _dly_dir_sig_ts = 0 local function fh_get_daily_shop_history(item_name) if not item_name or item_name == '' then return {} end local lfs_ok, lfs = pcall(require, 'lfs') if not lfs_ok then return {} end local dir = getWorkingDirectory():gsub('\\\\','/') .. '/MarketHelper/FH_daily_prices' if not lfs.attributes(dir) then return {} end local now = os.time() if not _dly_dir_sig or (now - _dly_dir_sig_ts) > 30 then local sig_parts = {} for fname in lfs.dir(dir) do if fname:match('^%d%d%d%d%-%d%d%-%d%d%.json$') then local attr = lfs.attributes(dir .. '/' .. fname) local mt = (attr and attr.modification) or 0 table.insert(sig_parts, fname..':'..tostring(mt)) end end table.sort(sig_parts) local new_sig = table.concat(sig_parts, '|') if new_sig ~= _dly_dir_sig then _dly_dir_sig = new_sig _dly_item_cache = {} end _dly_dir_sig_ts = now end local hit = _dly_item_cache[item_name] if hit and hit.sig == _dly_dir_sig then return hit.days end local days = {} for fname in lfs.dir(dir) do local y,m,d2 = fname:match('^(%d%d%d%d)-(%d%d)-(%d%d)%.json$') if y then local path = dir .. '/' .. fname local attr = lfs.attributes(path) local mt = (attr and attr.modification) or 0 local fc = _dly_file_cache[path] if not fc or fc.mtime ~= mt then local f = io.open(path, 'r') if f then local raw = f:read('*a'); f:close() local ok, dat = pcall(decodeJson, raw) if ok and type(dat) == 'table' then fc = {mtime=mt, data=dat} _dly_file_cache[path] = fc else fc = nil end end end if fc and fc.data and fc.data[item_name] then local e = fc.data[item_name] local s_avg = (e.s_totalC and e.s_totalC>0) and math.floor(e.s_totalP/e.s_totalC) or nil local b_avg = (e.b_totalC and e.b_totalC>0) and math.floor(e.b_totalP/e.b_totalC) or nil local b_max = e.b_max or nil local s_prices = e.s_prices or nil local b_prices = e.b_prices or nil local s_min = e.s_min or nil local s_prices_avg = nil if s_prices and #s_prices > 0 then local _sp = {} for _,v in ipairs(s_prices) do if (v or 0) > 0 then table.insert(_sp, v) end end if #_sp > 0 then table.sort(_sp) if not s_min then s_min = _sp[1] end local _n = #_sp local _q1 = _sp[math.max(1, math.floor(_n*0.25+0.5))] local _q3 = _sp[math.min(_n, math.floor(_n*0.75+0.5))] local _iqr = _q3 - _q1 local _lo = _q1 - _iqr*1.5; local _hi = _q3 + _iqr*1.5 local _ss, _sc = 0, 0 for _,v in ipairs(_sp) do if _iqr==0 or (v>=_lo and v<=_hi) then _ss=_ss+v; _sc=_sc+1 end end s_prices_avg = _sc>0 and math.floor(_ss/_sc) or _sp[math.ceil(_n/2)] end end if s_avg or b_avg then table.insert(days, {date=y..'-'..m..'-'..d2, s_avg=s_avg, s_min=s_min, s_prices_avg=s_prices_avg, b_avg=b_avg, b_max=b_max, s_prices=s_prices, b_prices=b_prices}) end end end end table.sort(days, function(a,b) return a.date > b.date end) _dly_item_cache[item_name] = {sig=_dly_dir_sig, days=days} return days end local function _ngw1x(item, qty, price, op, partner, is_vc, own) if not item or not price or price<=0 then return end local dt=os.date("%d.%m %H:%M"); local dt_day=os.date("%Y-%m-%d"); qty=qty or 1 local side=(op=='buy') and 'buy' or 'sell' -- [MH FIX] КОРНЕВАЯ РџР РР§РРќРђ пропажи сделок: старое выражение считало -- arz_srv_sel[0]==0 ("Р’СЃРµ сервера") истинным (0+1=1 -> id=-1), поэтому -- РЅРёР¶Рµ "if _cur_srv_id ~= -1" РЅРёРєРѕРіРґР° РЅРµ выполнялось Сѓ тех, кто РЅРµ -- выбрал сервер вручную РІ выпадающем СЃРїРёСЃРєРµ - сделки вообще РЅРµ -- записывались РІ deals_srv{sid}.json, Рё отправлять было нечего. local _cur_srv_id = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)() local _new_le={dt=dt,item=item,qty=qty,price=price,op=op,own=own,partner=partner or "",vc=is_vc,srv=_cur_srv_id} if partner and partner ~= '' and price > 0 then local _xp_op = (op or ''):upper() local _my_nick_xp = '' pcall(function() local _pid2 = select(2, sampGetPlayerIdByCharHandle(PLAYER_PED)) _my_nick_xp = (sampGetPlayerNickname(_pid2) or ''):lower() end) if _my_nick_xp == '' then pcall(function() _my_nick_xp = (sampGetCurrentPlayerName() or ''):lower() end) end if _xp_op == 'SELL' then _xp_add(partner, price * qty, item, 'buy') if _my_nick_xp ~= '' then _xp_add(_my_nick_xp, price * qty, item, 'sell') end elseif _xp_op == 'BUY' then _xp_add(partner, price * qty, item, 'sell') if _my_nick_xp ~= '' then _xp_add(_my_nick_xp, price * qty, item, 'buy') end end _G._xp_rank_cache = nil end table.insert(fh_mkt_log,_new_le) while #fh_mkt_log>2000 do table.remove(fh_mkt_log,1) end if mh_tg_on_trade then mh_tg_on_trade(_new_le) end fh_mkt_prices[item]=_bmj2p(fh_mkt_prices[item] or {},price,qty,side) fh_mkt_last_update=dt if _cur_srv_id ~= -1 then local _srv_deals_path = _zdb1r('deals_srv'..tostring(_cur_srv_id)..'.json') local _srv_deals = {} local _fd = io.open(_srv_deals_path, 'r') if _fd then local _ok,_dd=pcall(decodeJson,_fd:read('*a')); _fd:close(); if _ok and type(_dd)=='table' then _srv_deals=_dd end end if not _srv_deals[dt_day] then _srv_deals[dt_day]={} end table.insert(_srv_deals[dt_day], {t=dt,item=item,qty=qty,price=price,op=op,partner=partner or ""}) local _days_sorted = {} for k in pairs(_srv_deals) do local _ks = tostring(k) if _ks:match('^%d%d%d%d%-%d%d%-%d%d$') then table.insert(_days_sorted, _ks) end end table.sort(_days_sorted) while #_days_sorted > 30 do local _old = _days_sorted[1] _srv_deals[_old] = nil table.remove(_days_sorted, 1) end local _ok2,_j2=pcall(encodeJson,_srv_deals) if _ok2 then local _fw=io.open(_srv_deals_path,'w'); if _fw then _fw:write(_j2); _fw:close() end end end if op == 'sell' and partner and partner ~= '' then _xp_save() end end local function fh_find_listitem(text, needle) local iline = 0 for ln in text:gmatch('[^\n]+') do iline = iline + 1 if iline > 1 and ln:find(needle, 1, true) then return iline - 2 end end return nil end local _parse_mkt_price function MH_util.jfw5v(text, title_text) if not text or text == "" then return nil end local item_name = "" if title_text then local tn = title_text:gsub("{%x+}",""):match("^%s*(.-)%s*$") or "" local nm = tn:match("'(.+)'") or tn:match('"(.+)"') if nm and nm ~= "" then item_name = nm elseif not tn:find("Продажа") and not tn:find("последние") and tn ~= "" then item_name = tn end end if item_name == "" then return nil end local history = {} local function _parse_segment(seg) seg = seg:gsub("{%x%x%x%x%x%x}",""):match("^%s*(.-)%s*$") or "" if seg == "" then return end local dt_s, qty_s, price_s dt_s, qty_s, price_s = seg:match("^(%d%d%d%d%-%d%d%-%d%d)%s*\t%s*(%d+)%s*\t%s*(.+)") if not dt_s then dt_s, qty_s, price_s = seg:match("^(%d%d%d%d%-%d%d%-%d%d)%s+(%d+)%s+(.+)") end if not dt_s then local p2; dt_s, p2 = seg:match("^(%d%d%d%d%-%d%d%-%d%d)%s*\t%s*(.+)") if dt_s then qty_s = "1"; price_s = p2 end end if dt_s and price_s then local ps = price_s:match("^:CASH:(.+)$") or price_s ps = ps:match("^([^|]+)") or ps ps = ps:match("^%s*(.-)%s*$") or ps local price = _parse_mkt_price(ps) local qty = tonumber(qty_s) or 1 if price and price > 0 then table.insert(history, {dt=dt_s, qty=qty, price=price}) end end end for line in text:gmatch("[^\n]+") do local has_pipe = line:find(" | ", 1, true) if has_pipe then for seg in (line .. " | "):gmatch("(.-)%s+|%s+") do _parse_segment(seg) end else _parse_segment(line) end end return { name = item_name, history = history } end _parse_mkt_price = function(s) if not s or s == '' then return nil end if s:sub(1,6) == ':CASH:' then s = s:sub(7) end local c = s:gsub('{%x%x%x%x%x%x}', ' '):match('^%s*(.-)%s*$') or '' if c == '' then return nil end c = c:gsub('[Kk][Kk]', 'КК') c = c:gsub('([^%a])([Kk])([^%a])', function(a,k,b) return a..'К'..b end) c = c:gsub('^([Kk])([^%a])', function(k,b) return 'К'..b end) c = c:gsub('([^%a])([Mm])([^%a])', function(a,m,b) return a..'М'..b end) c = c:gsub('^([Mm])([^%a])', function(m,b) return 'М'..b end) local kk_i,frac_i = c:match('КК%D*(%d+)%D+(%d[%d%.%,]*)') if kk_i and frac_i then return math.floor(tonumber(kk_i)*1e6)+(tonumber((frac_i:gsub('[.,]',''))) or 0) end local m_i,frac_m = c:match('М%D*(%d+)%D+(%d[%d%.%,]*)') if m_i and frac_m then return math.floor(tonumber(m_i)*1e9)+(tonumber((frac_m:gsub('[.,]',''))) or 0) end local kk_s,k_s = c:match('КК%D*([%d%.%,]+)%D+К%D*([%d%.%,]+)') if kk_s and k_s then return math.floor((tonumber((kk_s:gsub(',','.'))or 0))*1e6)+(tonumber((k_s:gsub('[.,]',''))) or 0) end local m_s,k2_s = c:match('М%D*([%d%.%,]+)%D+К%D*([%d%.%,]+)') if m_s and k2_s then return math.floor((tonumber((m_s:gsub(',','.'))or 0))*1e9)+(tonumber((k2_s:gsub('[.,]',''))) or 0) end local kk = c:match('КК%D*([%d%.%,]+)') if kk then return math.floor((tonumber((kk:gsub(',','.'))) or 0)*1e6) end local m = c:match('М%D*([%d%.%,]+)') if m then return math.floor((tonumber((m:gsub(',','.'))) or 0)*1e9) end local k = c:match('К%s*([%d%.%,]+)') if k then if k:find('[.,]') then return tonumber((k:gsub('[.,]',''))) or 0 else return (tonumber(k) or 0)*1000 end end local d = c:gsub('^%$',''):gsub('%s','') local nd=0; for _ in d:gmatch('%.') do nd=nd+1 end if nd>1 then d=d:gsub('[.,]','') elseif nd==1 then local a=d:match('%.(%d+)$'); if a and #a==3 then d=d:gsub('%.','') end end d=d:gsub(',','') return tonumber(d) end local function _hbr6z(text) if not text or text=="" then return 0 end local count=0; local iline=0 for raw in text:gmatch("[^\n]+") do iline=iline+1 if iline>3 then local clean=raw:gsub("{%x+}","") local is_vc=clean:find("[Vv][Cc]%$") ~= nil local name,price_s local price_raw if is_vc then name, price_raw = clean:match('^(.-)%s+[Vv][Cc]%s*%$?(.+)$') if not name then name,price_raw=clean:match('^(.-) [Vv][Cc]%$?(.+)$') end else name, price_raw = clean:match('^(.-)%s* (.+)$') if not name then name, price_raw = clean:match('^(.-)%s%s+(.+)$') end end name = name and name:match('^%s*(.-)%s*$') or '' local price = price_raw and _parse_mkt_price(price_raw) or nil if name~="" and price and price>10 then local e=fh_mkt_prices[name] or {} if is_vc then if not e.vc_st or (os.time()-e.vc_st)>60 then e.vc_st=os.time(); e.vc_sp=nil end if e.vc_sp then e.vc_min=e.vc_min and math.min(e.vc_min,price) or math.min(e.vc_sp,price) e.vc_max=e.vc_max and math.max(e.vc_max,price) or math.max(e.vc_sp,price) else e.vc_sp=price end e.vc_totalP=(e.vc_totalP or 0)+price; e.vc_totalC=(e.vc_totalC or 0)+1 e.vc_avg=math.floor(e.vc_totalP/e.vc_totalC) e.vc_min=e.vc_min or price; e.vc_max=e.vc_max or price else if not e.cp_st or (os.time()-e.cp_st)>60 then e.cp_st=os.time(); e.cp_sp=nil e.s_totalP=nil; e.s_totalC=nil e.s_min=nil; e.s_max=nil end if e.cp_sp then e.s_min=e.s_min and math.min(e.s_min,price) or math.min(e.cp_sp,price) e.s_max=e.s_max and math.max(e.s_max,price) or math.max(e.cp_sp,price) else e.cp_sp=price end e.s_totalP=(e.s_totalP or 0)+price; e.s_totalC=(e.s_totalC or 0)+1 e.s_avg=math.floor(e.s_totalP/e.s_totalC) e.s_min=e.s_min or price; e.s_max=e.s_max or price end e.date=os.date("%d.%m.%Y"); fh_mkt_prices[name]=e; count=count+1 end end end if count>0 then fh_mkt_last_update=os.date("%d.%m %H:%M") end return count end local function fh_mkt_save_cp_detail(item_name, history) if not item_name or item_name == "" or not history then return end local dt = os.date("%d.%m %H:%M") local e = fh_mkt_prices[item_name] or {} e.cp_hist = history local total_pxq, total_q = 0, 0 local s_min, s_max for _, h in ipairs(history) do if h.price and h.price > 0 then local q = h.qty or 1 total_pxq = total_pxq + h.price * q; total_q = total_q + q if not s_min or h.price < s_min then s_min = h.price end if not s_max or h.price > s_max then s_max = h.price end end end if total_q > 0 then e.s_avg=math.floor(total_pxq/total_q); e.s_min=s_min; e.s_max=s_max e.s_totalC=total_q; e.date=dt fh_mkt_prices[item_name]=e; fh_mkt_last_update=dt end end local function _nqh8s(text, style) local items = {} if not text or text == "" then return items end local iline = 0; local list_idx = -1 for raw in text:gmatch("[^\n]+") do iline = iline + 1 if iline == 1 then else list_idx = list_idx + 1 local clean = raw:gsub("{%x+}",""):match("^%s*(.-)%s*$") or "" local skip = clean=="" or clean:find("^Поиск по") or clean:find("^Следующая страница") or clean:find("^Предыдущая страница") or clean:find("^>>") or clean:find("^<<") or clean:find("Проанализировать все цены") or clean:find("Углублённый скан") if not skip and clean ~= "" then local nm = clean:match("^(.-)%s*\t") or clean:match("^(.-)%s*%$") or clean nm = nm:match("^%s*(.-)%s*$") or "" if nm ~= "" and #nm > 1 then table.insert(items, {name=nm, idx=list_idx}) end end end end return items end _G._nqh8s = _nqh8s local function _gyc9t(text) if not text or text == "" then return 0 end local count = 0; local iline = 0 for raw in text:gmatch("[^\n]+") do iline = iline + 1 if iline > 1 then local clean = raw:gsub("{%x+}", "") local price_raw2 name, price_raw2 = clean:match('^(.-)%s* (.+)$') if not name then name, price_raw2 = clean:match('^(.-)%s%s+(.+)$') end name = name and name:match("^%s*(.-)%s*$") or "" name = name:gsub('##%a%d+$','') local price = _parse_mkt_price(price_raw2 or '') if name ~= "" and price and price > 1000 then name = _mh_norm_nm(name) local e = fh_mkt_auto[name] or {} if not e.cp_st or (os.time() - (e.cp_st or 0)) > 60 then e.cp_st = os.time(); e.cp_sp = nil e.s_totalP = nil; e.s_totalC = nil e.s_min = nil; e.s_max = nil end if e.cp_sp then e.s_min = e.s_min and math.min(e.s_min, price) or math.min(e.cp_sp, price) e.s_max = e.s_max and math.max(e.s_max, price) or math.max(e.cp_sp, price) else e.cp_sp = price end e.s_totalP = (e.s_totalP or 0) + price e.s_totalC = (e.s_totalC or 0) + 1 e.s_avg = math.floor(e.s_totalP / e.s_totalC) e.s_min = e.s_min or price; e.s_max = e.s_max or price if not e.hist then e.hist = {} end local dt_now = os.date("%d.%m") if not e.hist[1] or e.hist[1].dt ~= dt_now then table.insert(e.hist, 1, {dt = dt_now, price = price}) while #e.hist > 30 do table.remove(e.hist) end else e.hist[1].price = price end e.date = os.date("%d.%m.%Y"); e._is_auto = true; fh_mkt_auto[name] = e; count = count + 1 end end end if count > 0 then fh_mkt_auto_last_upd = os.date("%d.%m %H:%M") end return count end local function fh_mkt_parse_auto_list(text) local items = {} if not text or text == '' then return items end local iline = 0; local list_idx = -1 for raw in text:gmatch('[^\n]+') do iline = iline + 1 if iline == 1 then else list_idx = list_idx + 1 local clean = raw:gsub('{%x+}',''):match('^%s*(.-)%s*$') or '' local skip = clean == '' or clean:find('^Поиск по') or clean:find('^Следующая страница') or clean:find('^Предыдущая страница') or clean:find('Сканировать все авто') or clean:find('Углублённый скан') if not skip and clean ~= '' then local nm = clean:match('^(.-)%s*\t') or clean:match('^(.-)%s*%$') or clean nm = nm:gsub('##%a%d+$',''):match('^%s*(.-)%s*$') or '' if nm ~= '' and #nm > 1 then table.insert(items, {name=nm, idx=list_idx}) end end end end return items end local function fh_mkt_parse_auto_detail(text, title_text) if not text or text == '' then return nil end local item_name = '' if title_text then local tn = title_text:gsub('{%x+}',''):match('^%s*(.-)%s*$') or '' local nm = tn:match("'(.+)'") or tn:match('"(.+)"') if nm and nm ~= '' then item_name = nm elseif tn ~= '' and not tn:find('Продажа') and not tn:find('последние') then item_name = tn end end if item_name == '' then return nil end item_name = item_name:gsub('##%a%d+$','') local history = {} for line in text:gmatch('[^\n]+') do local clean = line:gsub('{%x%x%x%x%x%x}',''):match('^%s*(.-)%s*$') or '' if clean ~= '' then local dt_s, qty_s, price_s dt_s, qty_s, price_s = clean:match('^(%d%d%d%d%-%d%d%-%d%d)%s*\t%s*(%d+)%s*\t%s*(.+)') if not dt_s then dt_s, qty_s, price_s = clean:match('^(%d%d%d%d%-%d%d%-%d%d)%s+(%d+)%s+(.+)') end if not dt_s then dt_s, qty_s, price_s = clean:match('^(%d%d%d%d%-%d%d%-%d%d)%s*|%s*(%d+)%s*|%s*(.+)') end if not dt_s then local p2; dt_s, p2 = clean:match('^(%d%d%d%d%-%d%d%-%d%d)%s*\t%s*(.+)') if dt_s then qty_s = '1'; price_s = p2 end end if dt_s and price_s then local price = _parse_mkt_price(price_s) local qty = tonumber(qty_s) or 1 if price and price > 0 then table.insert(history, {dt=dt_s, qty=qty, price=price}) end end end end return { name=item_name, history=history } end local function fh_mkt_save_auto_detail(item_name, history) if not item_name or item_name == '' or not history or #history == 0 then return end local dt = os.date('%d.%m %H:%M') local e = fh_mkt_auto[item_name] or {} e.cp_hist = history local total_pxq, total_q = 0, 0; local s_min, s_max for _, h in ipairs(history) do if h.price and h.price > 0 then local q = h.qty or 1 total_pxq = total_pxq + h.price * q; total_q = total_q + q if not s_min or h.price < s_min then s_min = h.price end if not s_max or h.price > s_max then s_max = h.price end end end if total_q > 0 then e.s_avg = math.floor(total_pxq/total_q); e.s_min = s_min; e.s_max = s_max e.s_totalC = total_q; e.date = dt end fh_mkt_auto[item_name] = e; fh_mkt_auto_last_upd = dt end local mh_isActiveCommand = false local _mh_piar_vr_active = false local function _tcz2r(text, waiting, callback) if mh_isActiveCommand then return end lua_thread.create(function() mh_isActiveCommand = true local lines = {} for line in text:gmatch("[^&]+") do table.insert(lines, line) end for i, line in ipairs(lines) do if i > 1 then wait((waiting or 1.5) * 1000) end _mh_piar_vr_active = true sampSendChat(line) wait(1500) _mh_piar_vr_active = false end mh_isActiveCommand = false if callback then callback() end end) end local function _xjg7y(index) local t = settings.piar_templates and settings.piar_templates[index] if not t or not t.enable then return end local _saved_num = settings.general and settings.general.own_shop_num_saved local _cur_num = mh_own_shop_num or fh_other_shop_pending_num if not _cur_num then local _my_nick = settings.general and settings.general.nick if _my_nick and _my_nick ~= '' and fh_other_shops then local _best_ts = 0 for _, _sh in pairs(fh_other_shops) do local _sown = (_sh.owner or ''):lower() if _sown == _my_nick:lower() and (_sh.shop_num or 0) > 0 then local _ts = _sh.ts or 0 if _ts > _best_ts then _best_ts = _ts _cur_num = _sh.shop_num end end end end end if _cur_num and settings.general then settings.general.own_shop_num_saved = _cur_num _ryb5t() end local _lavka_num = tostring(_cur_num or _saved_num or '') local _lines_resolved = {} for _, ln in ipairs(t.lines or {}) do table.insert(_lines_resolved, (ln:gsub('', _lavka_num))) end local _to_send if t.random_line and #_lines_resolved > 1 then _to_send = _lines_resolved[math.random(1, #_lines_resolved)] else _to_send = table.concat(_lines_resolved, '&') end _tcz2r(_to_send, t.waiting, function() t.last_time = os.time() if (t.auto_limit or 0) > 0 then t.auto_count = (t.auto_count or 0) + 1 if t.auto_count >= t.auto_limit then t.auto = false t.auto_count = 0 mh_notify('[MH] {ffaa00}Пиар «' .. (t.name or '?') .. '» {ffffff}— лимит ' .. t.auto_limit .. ' отправок достигнут, авто выключен.', 0xFFFFFF) end end _wfn7p() end) end local function _pxm3k(text, title_clean) if not text or text=="" then return nil end local name="" for line in text:gmatch("[^\n]+") do local n=line:match(":%s*{[^}]+}(.-)%s*{[^}]+}") if not n or n=="" then n=line:match("{[^}]+}(.-)%s*{[^}]+}") end if n and n~="" then name=n; break end end local price_s="" for line in text:gmatch("[^\n]+") do local p=line:match("Стоимость:.-$([%d,%.]+)") if not p then p=line:match("Стоимость:[^\n]-(%d[%d,%.]+)") end if p then price_s=p; break end end local qty_s="1" for line in text:gmatch("[^\n]+") do local q=line:match("В%s*наличии:%s*(.-)%s*шт") if not q then q=line:match("Игрок%s*покупает:%s*(.-)%s*шт") end if q then qty_s=q; break end end local price=tonumber((price_s:gsub("[,.]",""))); local qty=tonumber(qty_s) or 1 if name=="" or not price or price<=0 then return nil end local op=(title_clean and title_clean:find("Покупка")) and "sell" or "buy" return {name=name,price=price,qty=qty,op=op} end local function _jnw7r(name,price,qty,op) if not name or name=="" or not price or price<=0 then return end local dt=os.date("%d.%m %H:%M") fh_mkt_lavka[name]=_bmj2p(fh_mkt_lavka[name] or {},price,qty or 1,op) table.insert(fh_mkt_lavka_log,{dt=dt,item=name,price=price,qty=qty or 1,op=op}) while #fh_mkt_lavka_log>1000 do table.remove(fh_mkt_lavka_log,1) end end local function _xgf3s(dtext) if not dtext or dtext == '' then return nil end for line in dtext:gmatch('[^\n]+') do local n = line:match('[Кк]упить%s+предмет%s+{[^}]+}(.-)%s*{[^}]+}%s*%(ID:') if not n then n = line:match('[Кк]упить%s+предмет%s+(.-)%s+%(ID:') end if not n then n = line:match('[Пп]родать%s+предмет%s+{[^}]+}(.-)%s*{[^}]+}') end if not n then n = line:match('[Пп]родать%s+предмет%s+(.-)%s+%(ID:') end if n and n ~= '' then return n:match('^%s*(.-)%s*$') end end for line in dtext:gmatch('[^\n]+') do local n = line:match('{[^}]+}([^{]+){[^}]+}') if n then n = n:match('^%s*(.-)%s*$') if n and #n > 1 and not n:find('^%d') and not n:find('^%[') then return n end end end return nil end local function _qrm8t(text, title_clean) if not text or text == "" then return nil end local name = _xgf3s(text) or "" if name == "" then return nil end local slot_type if title_clean and title_clean:find("Продажа") and not title_clean:find("предмета") then slot_type = "buy_items" else slot_type = "sell_items" end local price = 0 local clean = text:gsub('{%x+}', '') for line in clean:gmatch("[^\n]+") do local p = line:match("[Сс]тоимость%s*:%s*%$?([%d%s,]+)") or line:match("[Цц]ена%s*:%s*%$?([%d%s,]+)") if p then price = tonumber((p:gsub('[%s,]',''))) or 0; break end end if price == 0 then local best = 0 for p_s in clean:gmatch("%$([%d,]+)") do local pn = tonumber((p_s:gsub(',',''))) or 0 if pn > best then best = pn end end price = best end local qty = tonumber(clean:match("[Вв]%s*наличии%s*:%s*(%d+)")) or 1 return {name=name, price=price, qty=qty, slot_type=slot_type} end local function _qbh9f() if not fh_other_shop_cur then if mh_debug_enabled then mh_notify('[MH DBG] _qbh9f: fh_other_shop_cur=nil', 0xFF6600) end return end local s = fh_other_shop_cur if not s.owner or s.owner == '' then if mh_debug_enabled then mh_notify('[MH DBG] _qbh9f: owner пустой', 0xFF6600) end return end if mh_debug_enabled then mh_notify('[MH DBG] _qbh9f: '..s.owner..' sell='..#(s.sell_items or {})..' buy='..#(s.buy_items or {})..' srv='..tostring(s.server_id), 0x66FF66) end if #(s.sell_items or {}) == 0 and #(s.buy_items or {}) == 0 then _crf5h(s) return end do local _olo = (s.owner or ''):lower() local _now_ts = s.ts or os.time() local _sell_ids, _sell_pr, _sell_cnt = {}, {}, {} local _buy_ids, _buy_pr, _buy_cnt = {}, {}, {} for _, it in ipairs(s.sell_items or {}) do if it.name and it.name ~= '' then _wyk7z = (_wyk7z or 899999) + 1 mh_arz_items_db[_wyk7z] = it.name table.insert(_sell_ids, _wyk7z) table.insert(_sell_pr, it.price or 0) table.insert(_sell_cnt, it.qty or 0) end end for _, it in ipairs(s.buy_items or {}) do if it.name and it.name ~= '' then _wyk7z = (_wyk7z or 899999) + 1 mh_arz_items_db[_wyk7z] = it.name table.insert(_buy_ids, _wyk7z) table.insert(_buy_pr, it.price or 0) table.insert(_buy_cnt, it.qty or 0) end end local _found_arz = false for _, _dv in ipairs(mh_arz_data) do if _dv.username and _dv.username:lower() == _olo then _dv._mh_updated_at = _now_ts _dv._mh_cloud = true if #_sell_ids > 0 then _dv.items_sell = _sell_ids; _dv.price_sell = _sell_pr; _dv.count_sell = _sell_cnt end if #_buy_ids > 0 then _dv.items_buy = _buy_ids; _dv.price_buy = _buy_pr; _dv.count_buy = _buy_cnt end if not _dv.LavkaUid or _dv.LavkaUid == 0 then _dv.LavkaUid = tonumber(s.shop_num) or _dv.LavkaUid end _found_arz = true; break end end if not _found_arz and (s.server_id or -1) ~= -1 then table.insert(mh_arz_data, { serverId = s.server_id, username = s.owner, LavkaUid = tonumber(s.shop_num) or 0, items_sell = _sell_ids, price_sell = _sell_pr, count_sell = _sell_cnt, items_buy = _buy_ids, price_buy = _buy_pr, count_buy = _buy_cnt, _mh_cloud = true, _mh_updated_at = _now_ts, }) end _G.arz_cache_key = nil _mh_shop_bump() end for _, it in ipairs(s.sell_items or {}) do if it.name and it.name ~= '' and it.price and it.price > 0 then _jnw7r(it.name, it.price, it.qty or 1, 'sell') end end for _, it in ipairs(s.buy_items or {}) do if it.name and it.name ~= '' and it.price and it.price > 0 then _jnw7r(it.name, it.price, it.qty or 1, 'buy') end end do local _key = (s.owner or '?') .. '_' .. tostring(s.shop_num or '?') fh_other_shops[_key] = { owner = s.owner, shop_num = s.shop_num, dt = s.dt or os.date('%d.%m %H:%M'), ts = s.ts or os.time(), sell_items = s.sell_items or {}, buy_items = s.buy_items or {}, server_id = s.server_id, } settings.other_shops = fh_other_shops _wfn7p() end _crf5h(s) end local _mh_pprices_pushing = false local _mh_pprices_pulling = false local function _mh_prices_push() if _mh_pprices_pushing then return end local srv_id = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)() local items = {} for nm, e in pairs(fh_mkt_prices) do if e and (e.s_avg and e.s_avg > 0 or e.cp_sp and e.cp_sp > 0 or e.cp_hist and #e.cp_hist > 0) then local entry = { name = _to_utf8(nm) } if e.cp_hist and #e.cp_hist > 0 then entry.cp_hist = e.cp_hist end if e.cp_sp and e.cp_sp > 0 then entry.cp_sp = e.cp_sp end if e.s_avg and e.s_avg > 0 then entry.s_avg = e.s_avg end if e.s_min and e.s_min > 0 then entry.s_min = e.s_min end if e.s_max and e.s_max > 0 then entry.s_max = e.s_max end table.insert(items, entry) end end if #items == 0 then mh_notify('[MH Cloud] {ffaa00}Нет данных ЦР для отправки', 0xFFFFFF) return end _mh_pprices_pushing = true lua_thread.create(function() local CHUNK_SIZE = 100 local chunks = {} local cur = {} for i = 1, #items do table.insert(cur, items[i]) if #cur >= CHUNK_SIZE then table.insert(chunks, cur); cur = {} end end if #cur > 0 then table.insert(chunks, cur) end local total_chunks = #chunks local total_items = #items local had_error = false local _tok = (settings.premium and settings.premium.tok) or '' local _nck = (settings.premium and settings.premium.nick) or '' local _sess = _G._mh_session_tok or '' mh_notify('[MH Cloud] {aaffaa}Отправляю '..total_items ..' товаров ('..total_chunks..' чанк'..(total_chunks>1 and 'ов' or '')..')...', 0xFFFFFF) for ci = 1, total_chunks do local chunk = chunks[ci] local ok_j, j = pcall(encodeJson, { server_id = srv_id, items = chunk }) if not ok_j then mh_notify('[MH Cloud] {ff4444}Ошибка JSON чанк '..ci, 0xFFFFFF) had_error = true; break end local th = effil.thread(_twd4k)(_vbr7n..'/prices/push', j, _tok, _nck, _sess) local _deadline = os.clock() + 30 local code = 0 while true do wait(30) if os.clock() > _deadline then pcall(function() th:cancel() end) mh_notify('[MH Cloud] {ff4444}Чанк '..ci..'/'..total_chunks..': таймаут', 0xFFFFFF) had_error = true; break end local st, err = th:status() if not st or err then mh_notify('[MH Cloud] {ff4444}Чанк '..ci..'/'..total_chunks..': ошибка потока', 0xFFFFFF) had_error = true; break end if st == 'completed' or st == 'canceled' then local ok2, resp = th:get() if ok2 and resp then code = resp.status_code or 0 local rbody = resp.text or '' if code == 200 and rbody:find('"ok":false') then code = 403 end end break end end if had_error then break end if code ~= 200 then mh_notify('[MH Cloud] {ff4444}Чанк '..ci..'/'..total_chunks..' err:'..code, 0xFFFFFF) had_error = true; break end if ci < total_chunks then wait(300) end end _mh_pprices_pushing = false if not had_error then mh_notify('[MH Cloud] {00cc00}ЦР отправлены ('..total_items..' товаров, '..total_chunks..' чанк'..(total_chunks>1 and 'ов' or '')..')', 0xFFFFFF) end end) end _G._mh_prices_push = _mh_prices_push local function _mh_prices_pull(silent) if _mh_pprices_pulling then return end local srv_id = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)() _mh_pprices_pulling = true if not silent then mh_notify('[MH Cloud] {aaaaff}Загружаю цены ЦР...', 0xFFFFFF) end local _ppn='' pcall(function() _ppn=sampGetPlayerNickname(select(2,sampGetPlayerIdByCharHandle(PLAYER_PED))) or '' end) if _ppn=='' then pcall(function() _ppn=sampGetCurrentPlayerName() or '' end) end if _ppn=='' then _ppn=(settings.premium and settings.premium.nick) or '' end _fwm2c(_vbr7n .. '/prices/pull?server=' .. tostring(srv_id) .. '&nick=' .. _ppn .. '&ver=' .. _MH_VER, (settings.premium and settings.premium.tok) or '', _ppn, function(code, body, _e) _mh_pprices_pulling = false if _e or not body or #body == 0 then if not silent then mh_notify('[MH Cloud] {ff4444}Ошибка загрузки цен', 0xFFFFFF) end return end local ok, parsed = pcall(decodeJson, body) if not ok or type(parsed) ~= 'table' or not parsed.items then if not silent then mh_notify('[MH Cloud] {ff4444}Ошибка парсинга цен', 0xFFFFFF) end return end local merged = 0 for _, item in ipairs(parsed.items) do local nm = item.name if nm and nm ~= '' then local ok_d, nm_cp = pcall(function() return require('encoding').CP1251:encode(nm) end) if ok_d and nm_cp then nm = nm_cp end local existing = fh_mkt_prices[nm] local srv_upd = item.updated_at or 0 local our_upd = existing and existing._upd_ts or 0 if not existing or srv_upd > our_upd then local e = existing or {} if item.cp_hist and #item.cp_hist > 0 then e.cp_hist = item.cp_hist end if item.cp_sp and item.cp_sp > 0 then e.cp_sp = item.cp_sp end if item.s_avg and item.s_avg > 0 then e.s_avg = item.s_avg e.s_min = item.s_min or e.s_min e.s_max = item.s_max or e.s_max end e._upd_ts = srv_upd fh_mkt_prices[nm] = e merged = merged + 1 end end end _mh_db_bump() if not silent then mh_notify('[MH Cloud] {00cc00}Цены ЦР получены: '..merged..' обновлено из '..#parsed.items, 0xFFFFFF) end end) end _G._mh_prices_pull = _mh_prices_pull function _crf5h(s) if not s or not s.owner or s.owner == '' then return end if mh_debug_enabled then mh_notify('[MH DBG] _crf5h: '..tostring(s.owner)..' srv='..tostring(s.server_id or -1)..' sell='..#(s.sell_items or {})..' buy='..#(s.buy_items or {}), 0x66AAFF) end local _owner_u8 = _to_utf8(s.owner) local _sell_u8, _buy_u8 = {}, {} for _, it in ipairs(s.sell_items or {}) do table.insert(_sell_u8, { name = _to_utf8(it.name), price = it.price, qty = it.qty, item_id = it.item_id or 0 }) end for _, it in ipairs(s.buy_items or {}) do table.insert(_buy_u8, { name = _to_utf8(it.name), price = it.price, qty = it.qty, item_id = it.item_id or 0 }) end local _sid = s.server_id or -1 if _sid == -1 then local _auto = _mpf7d() if _auto and _auto > 0 then _sid = (ARZ_SERVERS[_auto + 1] or {}).id or -1 end end if mh_debug_enabled and _sid == -1 then mh_notify('[MH Cloud] {ffaa00}Push с server_id=-1 (' .. (s.owner or '?') .. ') — сервер примет', 0xFFFFFF) end local payload = { server_id = _sid, owner = _owner_u8, shop_num = tostring(s.shop_num or '?'), sell_slots = _sell_u8, buy_slots = _buy_u8, scanned_at = os.time(), } local ok_j, j = pcall(encodeJson, payload) if not ok_j then mh_notify('[MH Cloud] {ff6644}JSON encode err', 0xFFFFFF) return end _jmx9s(_vbr7n .. '/shops/push', j, function(code, body, _e) _szb8v = (code == 200) _gnl3q = os.time() if code == 200 then if mh_debug_enabled then mh_notify('[MH Cloud] {aaffaa}Push OK: ' .. _owner_u8, 0xFFFFFF) end elseif code == nil and _e and (tostring(_e):find('connect') or tostring(_e):find('timeout') or tostring(_e):find('refused') or tostring(_e):find('resolve')) then mh_notify('[MH Cloud] {ff4444}Нет связи с сервером: ' .. tostring(_e), 0xFFFFFF) elseif code == 0 or (code ~= 200 and code ~= nil) then if code == 0 then _szb8v = true else mh_notify('[MH Cloud] {ff6644}Push: сервер вернул ' .. tostring(code), 0xFFFFFF) end else _szb8v = true end end) end local function mh_push_own_preset_shop() local my_nick = '' pcall(function() my_nick = sampGetPlayerNickname(select(2, sampGetPlayerIdByCharHandle(PLAYER_PED))) or '' end) if my_nick == '' then return end local my_shop_num = mh_own_shop_num or fh_other_shop_pending_num if not my_shop_num then pcall(function() local _px, _py = getCharCoordinates(PLAYER_PED) local _bd, _bn = 8.0, nil for _tid = 0, 2047 do local _ok, _tt, _, _tpx, _tpy = pcall(sampGet3dTextInfoById, _tid) if _ok and _tt and _tpx then local _n = tostring(_tt):match('[#\xe2\x84\x96]%s*(%d+)') if not _n then _n = tostring(_tt):match('(%d+)$') end if _n then local _num = tonumber(_n) if _num and _num >= 1 and _num <= 9999 then local _d = math.sqrt((_tpx-_px)^2+(_tpy-_py)^2) if _d < _bd then _bd=_d; _bn=_num end end end end end if _bn then my_shop_num = _bn end end) end local sell_items = {} if _G._mh_sell_ran_session then for _, p in ipairs(fh_lv_autosell_preset or {}) do if p.name and p.name ~= '' and (p.price or 0) > 0 then table.insert(sell_items, {name=p.name, price=p.price, qty=p.qty or 1}) end end end local buy_items = {} if _G._mh_buy_ran_session then for _, p in ipairs(fh_lv_autobuy_preset or {}) do if p.name and p.name ~= '' and (p.max_price or 0) > 0 then table.insert(buy_items, {name=p.name, price=p.max_price, qty=p.qty or 1}) end end end if #sell_items == 0 and #buy_items == 0 then return end local shop_obj = { owner = my_nick, shop_num = my_shop_num or '?', dt = os.date('%d.%m %H:%M'), ts = os.time(), sell_items = sell_items, buy_items = buy_items, server_id = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)(), } local new_key = my_nick .. '_' .. tostring(my_shop_num or '?') fh_other_shops[new_key] = shop_obj _mh_shop_bump() settings.other_shops = fh_other_shops _wfn7p() _crf5h(shop_obj) do local _self_sid = shop_obj.server_id local _self_key = my_nick:lower() local _sell_ids2, _sell_pr2, _sell_cnt2 = {}, {}, {} for _, it in ipairs(sell_items) do if it.name and it.name ~= '' then _wyk7z = _wyk7z + 1 mh_arz_items_db[_wyk7z] = it.name table.insert(_sell_ids2, _wyk7z) table.insert(_sell_pr2, it.price) table.insert(_sell_cnt2, it.qty or 1) end end local _buy_ids2, _buy_pr2, _buy_cnt2 = {}, {}, {} for _, it in ipairs(buy_items) do if it.name and it.name ~= '' then _wyk7z = _wyk7z + 1 mh_arz_items_db[_wyk7z] = it.name table.insert(_buy_ids2, _wyk7z) table.insert(_buy_pr2, it.price) table.insert(_buy_cnt2, it.qty or 1) end end local _found = false for _, _lv in ipairs(mh_arz_data) do if _lv.username and _lv.username:lower() == _self_key then if _self_sid == -1 or not _lv.serverId or _lv.serverId == -1 or _lv.serverId == _self_sid then if #_sell_ids2 > 0 then _lv.items_sell = _sell_ids2; _lv.price_sell = _sell_pr2; _lv.count_sell = _sell_cnt2 end if #_buy_ids2 > 0 then _lv.items_buy = _buy_ids2; _lv.price_buy = _buy_pr2; _lv.count_buy = _buy_cnt2 end _lv._mh_cloud = true _lv._mh_updated_at = os.time() if my_shop_num then _lv.LavkaUid = my_shop_num end _found = true break end end end if not _found and (#_sell_ids2 > 0 or #_buy_ids2 > 0) then table.insert(mh_arz_data, { serverId = _self_sid, username = my_nick, LavkaUid = my_shop_num or '?', items_sell = _sell_ids2, price_sell = _sell_pr2, count_sell = _sell_cnt2, items_buy = _buy_ids2, price_buy = _buy_pr2, count_buy = _buy_cnt2, _mh_cloud = true, _mh_updated_at = os.time(), }) end _G.arz_cache_key = nil _G._dtl_cache_nm = nil; _G._dtl_dirty = true _mh_shop_bump() end mh_notify('[MH Cloud] {aaffaa}Своя лавка отправлена: ' ..my_nick..' #'..tostring(my_shop_num or '?') ..' ('..#sell_items..' прод / '..#buy_items..' скуп)', 0xFFFFFF) end _wyk7z = 899999 function _pkw2y(server_id) local sid = tostring(server_id ~= nil and server_id or -1) if _xht6j then return end _xht6j = true mh_arz_loading = true _dfn1c = nil local _spn = '' pcall(function() local _ok_s, _my_id = sampGetPlayerIdByCharHandle(PLAYER_PED) if _ok_s then _spn = sampGetPlayerNickname(_my_id) or '' end end) if _spn == '' then pcall(function() _spn = sampGetCurrentPlayerName() or '' end) end if _spn == '' then _spn = (settings.premium and settings.premium.nick) or '' end if (_G._mh_session_tok or '') == '' then _G._xht6j_gen = (_G._xht6j_gen or 0) + 1 local _my_gen = _G._xht6j_gen lua_thread.create(function() local _sw2 = 0 while (_G._mh_session_tok or '') == '' and _sw2 < 300 do wait(100); _sw2 = _sw2 + 1 end if _my_gen == _G._xht6j_gen then _xht6j = false mh_arz_loading = false _pkw2y(tonumber(sid) or -1) end end) lua_thread.create(function() wait(35000) if _xht6j and _my_gen == _G._xht6j_gen then _xht6j = false; mh_arz_loading = false end end) return end local _tok_for_pull = (settings.premium and settings.premium.tok) or '' if _tok_for_pull == '' then _tok_for_pull = _G._mh_session_tok or '' end if _tok_for_pull == '' then if _G._mh_check_update then _G._mh_check_update(true) end local _tw = 0 while _tok_for_pull == '' and _tw < 100 do wait(100); _tw = _tw + 1 _tok_for_pull = (settings.premium and settings.premium.tok) or '' if _tok_for_pull == '' then _tok_for_pull = _G._mh_session_tok or '' end end if _tok_for_pull == '' then mh_notify('[MH] {ff4444}Не удалось получить токен. Проверьте соединение.', 0xFFFFFF) _xht6j = false; mh_arz_loading = false return end end _fwm2c(_vbr7n .. '/shops/pull?server=' .. sid .. '&nick=' .. _spn .. '&ver=' .. _MH_VER, _tok_for_pull, _spn, function(c2, t2, e2) _xht6j = false mh_arz_loading = false if e2 then local _e2s = tostring(e2) if (_e2s:find('connect') or _e2s:find('refused') or _e2s:find('failed')) and not _G._pkw2y_retried then _G._pkw2y_retried = true lua_thread.create(function() wait(5000) _G._pkw2y_retried = false _pkw2y(tonumber(sid) or -1) end) return end _G._pkw2y_retried = false _dfn1c = _e2s; return end if not t2 or #t2 == 0 then _dfn1c = 'Пустой ответ'; return end local ok2, parsed = pcall(decodeJson, t2) if not ok2 or type(parsed) ~= 'table' or not parsed.shops then _dfn1c = (c2 == nil and 'connection failed' or 'JSON err: ' .. tostring(c2)) return end if type(parsed.session_token) == 'string' and parsed.session_token ~= '' then _G._mh_session_tok = parsed.session_token _G._mh_session_tok_ts = os.time() if settings.premium then settings.premium.tok = parsed.session_token end end if parsed.error == 'rate_limited' then local _wait_sec = type(parsed.retry_after) == 'number' and parsed.retry_after or 30 _dfn1c = nil mh_arz_loading = false if not _G._pkw2y_retried then _G._pkw2y_retried = true lua_thread.create(function() wait(_wait_sec * 1000) _G._pkw2y_retried = false _pkw2y(tonumber(sid) or -1) end) end return end if not mh_arz_items_db then mh_arz_items_db = {} end if not mh_arz_items_loaded and not mh_arz_items_loading then _G._cky4h() end local added = 0 for _, sh in ipairs(parsed.shops) do if sh.owner and sh.owner ~= '' then do local _co = sh.owner:match('%-%s*([A-Za-z_][A-Za-z0-9_]+)') or sh.owner:match('^([A-Za-z_][A-Za-z0-9_]+)') if _co and _co ~= '' then sh.owner = _co end end local found_idx = nil local _sh_uid_s = tostring(sh.shop_num or '') local _sh_uid_valid = _sh_uid_s ~= '' and _sh_uid_s ~= '0' and _sh_uid_s ~= '?' local _sh_srv = sh.server_id for idx, lv in ipairs(mh_arz_data) do if _sh_srv and _sh_srv ~= -1 and lv.serverId and lv.serverId ~= _sh_srv then goto _merge_next end local nick_match = lv.username and lv.username:lower() == sh.owner:lower() local uid_match = _sh_uid_valid and lv.LavkaUid and tostring(lv.LavkaUid) == _sh_uid_s if nick_match or uid_match then found_idx = idx; break end ::_merge_next:: end local sell_ids, sell_pr, sell_cnt = {}, {}, {} for _, slot in ipairs(sh.sell_slots or {}) do local _nm = slot.name if _nm and _nm ~= '' and _nm ~= '?' then _wyk7z = _wyk7z + 1 mh_arz_items_db[_wyk7z] = _nm table.insert(sell_ids, _wyk7z) table.insert(sell_pr, slot.price or 0) table.insert(sell_cnt, slot.qty or 0) end end local buy_ids, buy_pr, buy_cnt = {}, {}, {} for _, slot in ipairs(sh.buy_slots or {}) do local _nm = slot.name if _nm and _nm ~= '' and _nm ~= '?' then _wyk7z = _wyk7z + 1 mh_arz_items_db[_wyk7z] = _nm table.insert(buy_ids, _wyk7z) table.insert(buy_pr, slot.price or 0) table.insert(buy_cnt, slot.qty or 0) end end local entry = { serverId = (sh.server_id and sh.server_id ~= -1) and sh.server_id or (tonumber(sid) or -1), username = sh.owner, LavkaUid = sh.shop_num or 0, items_sell = sell_ids, price_sell = sell_pr, count_sell = sell_cnt, items_buy = buy_ids, price_buy = buy_pr, count_buy = buy_cnt, _mh_cloud = true, _mh_premium = sh.is_premium == true, _mh_updated_at = sh.updated_at or sh.scanned_at or 0, } if found_idx then local _ex = mh_arz_data[found_idx] _ex._mh_cloud = true if entry._mh_premium == true then _ex._mh_premium = true end _ex._mh_updated_at = entry._mh_updated_at or _ex._mh_updated_at if #sell_ids > 0 then _ex.items_sell = sell_ids; _ex.price_sell = sell_pr; _ex.count_sell = sell_cnt end if #buy_ids > 0 then _ex.items_buy = buy_ids; _ex.price_buy = buy_pr; _ex.count_buy = buy_cnt end if not _ex.LavkaUid or _ex.LavkaUid == 0 then _ex.LavkaUid = entry.LavkaUid end added = added + 1 else table.insert(mh_arz_data, entry) added = added + 1 end end end _mvr4p = true _G._dtl_cache_nm = nil; _G._dtl_dirty = true _G.arz_cache_key = nil if mh_debug_enabled then mh_notify('[MH DBG] pull done: added='..added..' total_cloud='..#parsed.shops, 0x66FF99) end if mh_arz_items_loaded then lua_thread.create(function() wait(100) _ztc7m(tonumber(sid) or -1) end) end end) end local function _bky4d(txt) if not txt then return nil end local s = txt:gsub('{%x%x%x%x%x%x}',' '):gsub(',','.'):match('^%s*(.-)%s*$') or '' s = s:gsub('[Kk][Kk]','КК') s = s:gsub('([^%a])([Kk])([^%a])',function(a,k,b) return a..'К'..b end) s = s:gsub('^([Kk])([^%a])',function(k,b) return 'К'..b end) s = s:gsub('([^%a])([Mm])([^%a])',function(a,m,b) return a..'М'..b end) s = s:gsub('^([Mm])([^%a])',function(m,b) return 'М'..b end) local kk_i,frac_i = s:match('КК%D*(%d+)%D+(%d[%d%.]*)') if kk_i and frac_i then return math.floor(tonumber(kk_i)*1e6)+(tonumber((frac_i:gsub('%.',''))) or 0) end local m_i,frac_m = s:match('М%D*(%d+)%D+(%d[%d%.]*)') if m_i and frac_m then return math.floor(tonumber(m_i)*1e9)+(tonumber((frac_m:gsub('%.',''))) or 0) end local kk_s,k_s = s:match('КК%D*([%d%.]+)%D+К%D*([%d%.]+)') if kk_s and k_s then return math.floor((tonumber(kk_s) or 0)*1e6)+(tonumber((k_s:gsub('%.',''))) or 0) end local kk2 = s:match('КК%D*([%d%.]+)') if kk2 then local p=math.floor((tonumber(kk2) or 0)*1e6); if p>=500 then return p end end local ms = s:match('М%D*([%d%.]+)') if ms then local p=math.floor((tonumber(ms) or 0)*1e9); if p>=500 then return p end end local ks = s:match('К%s*([%d][%d%.]*)') if ks then local p=tonumber((ks:gsub('%.',''))) or 0 if p==0 then p=math.floor((tonumber(ks) or 0)*1000) end if p>=500 then return p end end local plain=s:gsub('[%s]','') local p=tonumber(plain:match('^%$([%d%.]+)$')) if not p then p=tonumber(plain:match('^([%d%.]+)$')) end if p and p<500 then local s2=plain:gsub('[%$]',''):gsub('%.',''); local p2=tonumber(s2) if p2 and p2>=500 then p=p2 end end if p and p>=500 then return p end return nil end local function _ntx3c(owner, shop_num) fh_other_shop_price_tds = {} fh_other_shop_cur = { owner = owner or fh_other_shop_owner or "?", shop_num = shop_num or (function() _G.mh_shop_scan_cnt = (_G.mh_shop_scan_cnt or 0) + 1; return tostring(_G.mh_shop_scan_cnt) end)(), dt = os.date('%d.%m %H:%M'), ts = os.time(), sell_items = {}, buy_items = {}, server_id = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)(), } fh_other_shop_scanning = true mh_notify('[MH] {aaaaff}Лавка ' .. (owner or '?') .. ' #' .. tostring(shop_num or '?') .. ' — нажми "Меню товаров" для скана', 0xFFFFFF) lua_thread.create(function() local wait_ms = 0 while wait_ms < 15000 do wait(200); wait_ms = wait_ms + 200 if not fh_other_shop_scanning or not fh_other_shop_cur then return end if next(fh_other_shop_price_tds) ~= nil then break end end if not fh_other_shop_scanning or not fh_other_shop_cur then return end for td_id, td_data in pairs(fh_mkt_lavka_all_tds) do if td_data.text and td_data.position then local p = _bky4d(td_data.text) if p and not fh_other_shop_price_tds[td_id] then fh_other_shop_price_tds[td_id] = {price=p, x=td_data.position.x, y=td_data.position.y} end end end local sorted_prices = {} for td_id, info in pairs(fh_other_shop_price_tds) do table.insert(sorted_prices, {td_id=td_id, price=info.price, x=info.x, y=info.y}) end table.sort(sorted_prices, function(a, b) if math.abs(a.y - b.y) < 8 then return a.x < b.x end return a.y < b.y end) local deduped = {} local seen_price_row = {} for _, ptd2 in ipairs(sorted_prices) do local row_key = tostring(ptd2.price) .. '_' .. tostring(math.floor(ptd2.y / 40)) if not seen_price_row[row_key] then seen_price_row[row_key] = true table.insert(deduped, ptd2) end end sorted_prices = deduped if mh_debug_enabled then mh_notify('[MH Скан] Ценовых TD: ' .. #sorted_prices, 0xFFAAFF) for i, ptd in ipairs(sorted_prices) do if i <= 10 then mh_notify(' [Цена ' .. i .. '] td=' .. ptd.td_id .. ' $' .. ptd.price .. ' x=' .. math.floor(ptd.x) .. ' y=' .. math.floor(ptd.y), 0xaaaaff) end end end wait(400) local _dlg_send_done = false local MAX_WAIT_MS = 2000 local function click_and_wait_dlg(td_id) fh_other_dlg_signal = nil sampSendClickTextdraw(td_id) local elapsed = 0 while elapsed < MAX_WAIT_MS do wait(10); elapsed = elapsed + 10 if fh_other_dlg_signal then break end if not fh_other_shop_scanning then return nil end if not fh_mkt_shop_ui_open then return nil end end local sig = fh_other_dlg_signal fh_other_dlg_signal = nil return sig end local function close_and_wait(dlg_id) _dlg_send_done = false fh_other_dlg_signal = nil sampSendDialogResponse(dlg_id, 0, 0, '') local elapsed = 0 while elapsed < 1500 do wait(10); elapsed = elapsed + 10 if _dlg_send_done or fh_other_dlg_signal then break end if not fh_other_shop_scanning then break end end end local function insert_item(sig, price_override) local tc = sig.title or '' local dtext = sig.text or '' local item_id = tonumber(dtext:match('%(?ID:%s*(%d+)%)')) or 0 local nm if item_id > 0 then nm = _G._rgn9z(item_id) if nm == ('ID:'..tostring(item_id)) then nm = nil end end if not nm or nm == '' then nm = _xgf3s(dtext) end if not nm or nm == '' then return false end if not fh_other_shop_cur then return false end local st = (tc:find('\xcf\xf0\xee\xe4\xe0\xe6\xe0') and not tc:find('\xef\xf0\xe5\xe4\xec\xe5\xf2\xe0')) and 'buy_items' or 'sell_items' local lst = fh_other_shop_cur[st] local qty = tonumber(dtext:match('[\xc2\xe2]%s*\xed\xe0\xeb\xe8\xf7\xe8\xe8%s*:%s*(%d+)')) or 1 local price_val = price_override or 0 local ex_item = nil for _, it in ipairs(lst) do if it.name:lower() == nm:lower() and it.price == price_val then ex_item = it; break end end if ex_item then ex_item.qty = (ex_item.qty or 1) + qty if item_id > 0 and (not ex_item.item_id or ex_item.item_id == 0) then ex_item.item_id = item_id end else table.insert(lst, {name=nm, price=price_val, qty=qty, item_id=item_id}) if mh_debug_enabled then mh_notify('[MH] '..st..': '..nm:sub(1,26)..' $'..tostring(price_val)..' ID:'..tostring(item_id), 0x88CCFF) end end return true end if #sorted_prices == 0 then mh_notify('[MH Скан] {ffaa00}Ценовые TD не найдены. Пробуем все слоты...', 0xFFFFFF) for _, td_id in ipairs(fh_mkt_shop_inv_tds) do if not fh_other_shop_scanning or not fh_other_shop_cur then break end local sig2 = click_and_wait_dlg(td_id) if sig2 and (sig2.title:find('Покупка') or sig2.title:find('Продажа')) then insert_item(sig2, 0) close_and_wait(sig2.id) elseif sig2 then close_and_wait(sig2.id) end end else for _, ptd in ipairs(sorted_prices) do if not fh_other_shop_scanning or not fh_other_shop_cur then break end local candidates = {} local has_self = false for other_id, other_data in pairs(fh_mkt_lavka_all_tds) do if other_data.position then local dx = math.abs(other_data.position.x - ptd.x) local dy = math.abs(other_data.position.y - ptd.y) if dx <= 25 and dy <= 35 then table.insert(candidates, other_id) if other_id == ptd.td_id then has_self = true end end end end if not has_self then table.insert(candidates, ptd.td_id) end local got_name = false for _, cand_id in ipairs(candidates) do if got_name then break end if not fh_other_shop_scanning then break end local sig = click_and_wait_dlg(cand_id) if sig then if sig.title:find('Покупка') or sig.title:find('Продажа') then got_name = insert_item(sig, ptd.price) close_and_wait(sig.id) else close_and_wait(sig.id) end end end end end if fh_other_shop_scanning and fh_other_shop_cur then _qbh9f() fh_other_shop_scanning = false end end) end local function _rcw6d(key) fh_other_shops[key] = nil settings.other_shops = fh_other_shops _wfn7p() end local function _dsf3y(item, price, qty, op, status) local entry = { dt = os.date('%d.%m %H:%M'), item = item, price = price, qty = qty, op = op, status = status } table.insert(fh_lv_trade_log, 1, entry) end local function fh_parse_inventory_dialog(dlg_text) local raw_items = {} for line in dlg_text:gmatch('[^\r\n]+') do local clean = line:gsub('{' .. '%x+}', '') local slot_s, name, cnt_s slot_s, name, cnt_s = clean:match('%[(%d+)%]%s+(.-)%s+%{[^}]*%}%[(%d+)%s+') if not slot_s then slot_s, name, cnt_s = clean:match('%[(%d+)%]%s+(.-)%s+%[(%d+)%s+') end if not slot_s then slot_s, name, cnt_s = clean:match('%[(%d+)%]%s+(.-)%s+%[(%d+)%]') end if slot_s and name and cnt_s then name = name:match('^%s*(.-)%s*$') or name if name ~= '' and name ~= 'Название' then local _slot = tonumber(slot_s) local _cnt = tonumber(cnt_s) or 1 if mh_debug_enabled then mh_notify('[INV] slot='..tostring(_slot)..' x'..tostring(_cnt)..' '..name:sub(1,20), 0x888888) end table.insert(raw_items, {slot=_slot, name=name, count=_cnt}) end end end local grouped = {} local name_map = {} for _, item in ipairs(raw_items) do if name_map[item.name] then local g = name_map[item.name] g.count = g.count + item.count g.slots = g.slots .. ';' .. tostring(item.slot) table.insert(g.stacks, {slot=item.slot, count=item.count}) else local g = {name=item.name, count=item.count, slots=tostring(item.slot), stacks={{slot=item.slot, count=item.count}}} table.insert(grouped, g) name_map[item.name] = g end end for _, g in ipairs(grouped) do local found = false for _, v in ipairs(fh_lv_inventory) do if v.name == g.name then v.count = v.count + g.count v.slots = v.slots .. ';' .. g.slots for _, st in ipairs(g.stacks) do table.insert(v.stacks, st) end found = true; break end end if not found then table.insert(fh_lv_inventory, g) end end end fh_lv_inv_dialog_step = 0 local function _vcz9h() if fh_lv_inv_scanning then return end fh_lv_inv_scanning = true fh_lv_inv_dialog_step = 0 fh_lv_inventory = {} mh_notify('[FH Авто] {ffaa00}Сканирую инвентарь...', 0xFFFFFF) sampSendChat('/mm') lua_thread.create(function() local w = 0 while fh_lv_inv_scanning and w < 1000 do wait(10); w=w+1 end if fh_lv_inv_scanning then fh_lv_inv_scanning = false fh_lv_inv_dialog_step = 0 mh_notify('[FH Авто] {ff4444}Инвентарь не получен (таймаут).', 0xFFFFFF) end end) end local function fh_get_allitems_list() local list = {} for nm, _ in pairs(fh_mkt_prices) do if type(nm) == 'string' then table.insert(list, nm) end end table.sort(list) return list end local function fh_is_slot_dialog(cid) if cid == 25666 then return true end local title = fh_last_dlg_title or '' return title:find('Продажа') ~= nil or title:find('Slot') ~= nil or title:find('Слот') ~= nil or title:find('Товар') ~= nil end local function fh_get_slot_item_name(dlg_text, dlg_title) local name = '' if dlg_title and dlg_title ~= '' then local t = dlg_title:gsub('{' .. '%x+}',''):match('^%s*(.-)%s*$') or '' local n = t:match(':(.+)$') or '' n = n:match('^%s*(.-)%s*$') or '' if n ~= '' and not n:find('Продажа') and not n:find('Slot') then name = n end end if name == '' then name = dlg_text:match('{' .. '57FF6B}(.-){%x+}') or '' end if name == '' then for line in dlg_text:gmatch('[^\n]+') do local n = line:gsub('{' .. '%x+}',''):match('^%s*(.-)%s*$') if n and n ~= '' and not n:find(':') and #n > 1 then name = n; break end end end return name:match('^%s*(.-)%s*$') or '' end local function _wmc7r() if fh_lv_autosell_running or fh_lv_autobuy_running then return end if #fh_lv_autosell_preset == 0 then mh_notify('[MH Авто] {ff4444}Пресет пуст.', 0xFFFFFF); return end if #fh_lv_inventory == 0 then mh_notify('[MH Авто] {ff4444}Сначала нажмите "Скан инвентаря"!', 0xFFFFFF); return end if not mh_lavka_inv_ready then mh_notify('[MH Авто] {ff4444}Откройте UI: ВЗАИМОДЕЙСТВИЕ -> [1]', 0xFFFFFF) return end local queue = {} for _, preset in ipairs(fh_lv_autosell_preset) do if preset.enabled ~= false and (preset.price or 0) >= 10 then for _, inv in ipairs(fh_lv_inventory) do if inv.name:lower() == preset.name:lower() then local stacks = inv.stacks local _preset_left = math.min(preset.qty or inv.count, inv.count) if stacks and #stacks > 0 then for _, st in ipairs(stacks) do if _preset_left <= 0 then break end local _take = math.min(st.count, _preset_left) if _take > 0 then table.insert(queue, { name = preset.name, price = preset.price, qty = _take, slot = st.slot, item_id = 0, }) _preset_left = _preset_left - _take end end else local slot = tonumber((inv.slots or ''):match('(%d+)')) if slot and _preset_left > 0 then table.insert(queue, { name = preset.name, price = preset.price, qty = _preset_left, slot = slot, item_id = 0, }) end end break end end end end if #queue == 0 then mh_notify('[MH Авто] {ff8800}Нет товаров для выставления.', 0xFFFFFF) return end fh_lv_autosell_running = true fh_lv_autosell_done = 0 fh_lv_autosell_status = 'Запуск...' local total = #queue mh_notify('[MH Авто] {ffaa00}Выставляю ' .. total .. ' стаков...', 0xFFFFFF) lua_thread.create(function() for _, item in ipairs(queue) do if not fh_lv_autosell_running then break end fh_lv_autosell_status = item.name .. ' x' .. item.qty .. ' (' .. fh_lv_autosell_done .. '/' .. total .. ')' local json_str = '{"amount":' .. item.qty .. ',"id":' .. item.item_id .. ',"slot":' .. item.slot .. ',"type":1}' local prev_dlg_text = sampIsDialogActive() and sampGetDialogText() or '' _mh_flog('SELL_CLICK item=' .. item.name .. ' slot=' .. item.slot .. ' qty=' .. item.qty .. ' price=' .. item.price .. ' json=' .. json_str) _yzr1t(60, -1, 2, json_str) local dlg_text = '' local open_t = os.clock() + 1.5 wait(80) while os.clock() < open_t do wait(40) if _G._mh_dlg26546_text and _G._mh_dlg26546_text ~= '' and (_G._mh_dlg26546_time or 0) > (os.clock() - 2) then dlg_text = _G._mh_dlg26546_text _G._mh_dlg26546_text = nil break end if sampIsDialogActive() and sampGetCurrentDialogId() == 26546 then local cur_text = sampGetDialogText() or '' if cur_text ~= '' then dlg_text = cur_text break end end end _mh_flog('SELL_WAIT result: active=' .. tostring(sampIsDialogActive()) .. ' dlg_id=' .. tostring(sampIsDialogActive() and sampGetCurrentDialogId() or -1) .. ' text=[' .. dlg_text:sub(1,80) .. ']') if sampIsDialogActive() and sampGetCurrentDialogId() == 26546 and dlg_text ~= '' then local has_qty = dlg_text:find("запятую") ~= nil local resp = has_qty and (tostring(item.qty) .. ',' .. tostring(item.price)) or tostring(item.price) mh_notify('[MH Авто] {00cc00}' .. item.name .. ' x' .. item.qty .. ' @ $' .. tostring(item.price) .. ' slot=' .. item.slot, 0xFFFFFF) fh_lv_sell_confirmed = false fh_lv_sell_forbidden = false fh_lv_sell_no_slots = false mh_sell_confirmed = false sampSendDialogResponse(26546, 1, 0, resp) fh_lv_autosell_done = fh_lv_autosell_done + 1 _dsf3y(item.name, item.price, item.qty, 'sell', 'ok') local confirm_t = os.clock() + 4 while not mh_sell_confirmed and os.clock() < confirm_t do wait(40) end if not fh_lv_sell_confirmed then local extra_t = os.clock() + 2.0 while not fh_lv_sell_confirmed and os.clock() < extra_t do wait(30) end end sampSendDialogResponse(26546, 0, 0, '') if fh_lv_sell_no_slots then mh_notify('[MH Авто] {ff4444}Нет свободных ячеек, останавливаю.', 0xFFFFFF) fh_lv_autosell_running = false; break end wait(300) else mh_notify('[MH] {ff8800}Диалог не открылся: ' .. item.name .. ' (попытка...)', 0xFFFFFF) wait(350) if sampIsDialogActive() then sampSendDialogResponse(sampGetCurrentDialogId(), 0, 0, '') wait(100) end _yzr1t(60, -1, 2, json_str) local retry_t = os.clock() + 2.5 local retry_text = '' wait(80) while os.clock() < retry_t do wait(30) if sampIsDialogActive() and sampGetCurrentDialogId() == 26546 then retry_text = sampGetDialogText() or '' if retry_text ~= '' then break end end end if sampIsDialogActive() and sampGetCurrentDialogId() == 26546 and retry_text ~= '' then local has_qty2 = retry_text:find('запятую') ~= nil local resp2 = has_qty2 and (tostring(item.qty) .. ',' .. tostring(item.price)) or tostring(item.price) mh_notify('[MH Авто] {00ff88}(retry ok) ' .. item.name .. ' x' .. item.qty .. ' @ $' .. tostring(item.price), 0xFFFFFF) fh_lv_sell_confirmed = false fh_lv_sell_forbidden = false fh_lv_sell_no_slots = false mh_sell_confirmed = false sampSendDialogResponse(26546, 1, 0, resp2) fh_lv_autosell_done = fh_lv_autosell_done + 1 _dsf3y(item.name, item.price, item.qty, 'sell', 'retry') local ct2 = os.clock() + 3 while not mh_sell_confirmed and os.clock() < ct2 do wait(40) end sampSendDialogResponse(26546, 0, 0, '') if fh_lv_sell_no_slots then mh_notify('[MH Авто] {ff4444}Нет свободных ячеек, останавливаю.', 0xFFFFFF) fh_lv_autosell_running = false; break end wait(300) else mh_notify('[MH] {ff4444}Скип: ' .. item.name .. ' slot=' .. item.slot, 0xFFFFFF) wait(200) end end end fh_lv_autosell_running = false fh_lv_autosell_status = 'Готово: ' .. fh_lv_autosell_done .. '/' .. total mh_notify('[MH Авто] {00cc00}Выкладка: ' .. fh_lv_autosell_done .. ' стаков выложено.', 0xFFFFFF) fh_lv_autostart_enabled = false _wfn7p() _G._mh_sell_ran_session = true lua_thread.create(function() wait(800); mh_push_own_preset_shop() end) end) end local function _jtb4n() if fh_lv_autosell_running or fh_lv_autobuy_running then return end if #fh_lv_autobuy_preset == 0 then mh_notify('[FH Авто] {ff4444}Список авто-покупки пуст. Добавьте товары.', 0xFFFFFF) return end fh_lv_autobuy_running = true fh_lv_autobuy_status = 'Запуск...' lua_thread.create(function() if #fh_mkt_lavka_ids == 0 then fh_lv_autobuy_status = 'Открываю лавку...' sampSendChat('/mm') local w=0 while #fh_mkt_lavka_ids==0 and w<600 do wait(10); w=w+1 end if #fh_mkt_lavka_ids==0 then mh_notify('[FH Авто] {ff4444}Лавка не открылась!', 0xFFFFFF) fh_lv_autobuy_running=false fh_lv_autobuy_status='Лавка не доступна' return end wait(400) end local done, total = 0, #fh_lv_autobuy_preset mh_notify('[FH Авто] {ffaa00}Авто-покупка стартует: '..total..' позиций', 0xFFFFFF) for pi, buy_item in ipairs(fh_lv_autobuy_preset) do if not fh_lv_autobuy_running then break end fh_lv_autobuy_status = buy_item.name..' ('..pi..'/'..total..')' local found = false for _, td_id in ipairs(fh_mkt_lavka_ids) do if not fh_lv_autobuy_running then break end if sampIsDialogActive() then sampSendDialogResponse(sampGetCurrentDialogId(),0,0,'') wait(100) end sampSendClickTextdraw(td_id) wait(150) do local _w=0 while _w<800 and not sampIsDialogActive() do wait(40);_w=_w+40 end end local wd, dlg_id, dlg_price = 0, nil, nil while wd<3000 do wait(50); wd=wd+50 if sampIsDialogActive() then local cid = sampGetCurrentDialogId() local ctxt = sampGetDialogText() or '' if fh_is_slot_dialog(cid) then local ctitle_ab = sampGetDialogTitle() or '' ctitle_ab = ctitle_ab:gsub('{%x+}',''):match('^%s*(.-)%s*$') or '' local slot_name = fh_get_slot_item_name(ctxt, ctitle_ab) if slot_name:lower()==buy_item.name:lower() then for line in ctxt:gmatch('[^\n]+') do local p=line:match('Стоимость:%s*%$([%d,%.]+)') or line:match('[Цц]ена[^%d]*([%d]+)') or line:match('%$([%d]+)') if p then dlg_price=tonumber((p:gsub('[,.]',''))); break end end dlg_id=cid; break else sampSendDialogResponse(cid,0,0,'') wait(300) end elseif cid~=fh_mkt_lv_cur_dialog then sampSendDialogResponse(cid,0,0,'') wait(300) end end end if dlg_id then if buy_item.max_price and buy_item.max_price > 0 and dlg_price and dlg_price > buy_item.max_price then mh_notify('[FH Авто] {ffaa00}Пропуск: '..buy_item.name..' цена $'.._kcr3y(dlg_price)..' > макс $'.._kcr3y(buy_item.max_price), 0xFFFFFF) _dsf3y(buy_item.name, dlg_price or 0, buy_item.qty or 1, 'buy', 'skip') sampSendDialogResponse(dlg_id,0,0,'') wait(100) found=true; break end sampSendDialogResponse(dlg_id,1,2,'') wait(700) if sampIsDialogActive() then local inp = sampGetCurrentDialogId() sampSendDialogResponse(inp,1,0,tostring(buy_item.qty or 1)) wait(600) end _dsf3y(buy_item.name, dlg_price or 0, buy_item.qty or 1, 'buy', 'ok') done=done+1 found=true wait(500) break end end if not found then _dsf3y(buy_item.name, 0, buy_item.qty or 1, 'buy', 'skip') mh_notify('[FH Авто] {ffaa00}Пропуск покупки: '..buy_item.name, 0xFFFFFF) end end if sampIsDialogActive() then sampSendDialogResponse(sampGetCurrentDialogId(),0,0,'') wait(100) end sampSendClickTextdraw(65535) fh_lv_autobuy_running=false fh_lv_autobuy_status='Завершµно: '..done..'/'..total mh_notify('[FH Авто] {00cc00}Авто-покупка завершена. Успешно: '..done..'/'..total, 0xFFFFFF) end) end local function _xpk6g() if fh_lv_autosell_running or fh_lv_autobuy_running then return end if #fh_lv_autobuy_preset == 0 then mh_notify('[MH \xc0\xe2\xf2\xee] {ff4444}\xcf\xf0\xe5\xf1\xe5\xf2 \xf1\xea\xf3\xef\xe0 \xef\xf3\xf1\xf2.', 0xFFFFFF); return end fh_lv_autobuy_running = true fh_ab_search_idx = 1 fh_lv_autobuy_status = '\xc7\xe0\xef\xf3\xf1\xea...' local total = #fh_lv_autobuy_preset mh_notify('[MH \xc0\xe2\xf2\xee] {ffaa00}\xc0\xe2\xf2\xee-\xf1\xea\xf3\xef\xea\xe0: ' .. total .. ' \xf2\xee\xe2\xe0\xf0\xee\xe2...', 0xFFFFFF) local function wait_dlg(ids, ms) local el = 0 while el < ms do wait(30); el = el + 30 if _G.mh_ab_caught_dlg then local c = _G.mh_ab_caught_dlg if ids == nil then _G.mh_ab_caught_dlg = nil; return c end for _, id in ipairs(ids) do if c == id then _G.mh_ab_caught_dlg = nil; return c end end end if sampIsDialogActive() then local c = sampGetCurrentDialogId() if ids == nil then return c end for _, id in ipairs(ids) do if c == id then return c end end end end return nil end local function close_dlg() _G.mh_ab_caught_dlg = nil if sampIsDialogActive() then sampSendDialogResponse(sampGetCurrentDialogId(), 0, 0, '') wait(200) end end local function open_shop_menu() close_dlg() _yzr1t(8, 7, -1, '') local d = wait_dlg({3040}, 2500) _mh_flog('AB open_menu dlg=' .. tostring(d)) return d end lua_thread.create(function() if not open_shop_menu() then mh_notify('[MH] {ff4444}\xcb\xe0\xe2\xea\xe0 \xed\xe5 \xee\xf2\xea\xf0\xfb\xeb\xe0\xf1\xfc!', 0xFFFFFF) fh_lv_autobuy_running = false; return end for pi = 1, total do if not fh_lv_autobuy_running then break end local buy_item = fh_lv_autobuy_preset[pi] if not buy_item then break end fh_ab_search_idx = pi fh_lv_autobuy_status = buy_item.name .. ' (' .. pi .. '/' .. total .. ')' local search_str = buy_item.search or buy_item.name:sub(1, 24) local dlg = nil if not sampIsDialogActive() or sampGetCurrentDialogId() ~= 3040 then if not open_shop_menu() then mh_notify('[MH] {ff8800}\xd0\xe5\xec\xee\xed\xf2\xed\xee \xed\xe0\xf8\xb8\xeb ' .. buy_item.name, 0xFFFFFF) goto next_item end end wait(100) _G.mh_ab_caught_dlg = nil sampSendDialogResponse(3040, 1, 2, '') dlg = wait_dlg({25666}, 3000) _mh_flog('AB s2 dlg=' .. tostring(dlg)) if not dlg then _mh_flog('AB s2 retry') if not open_shop_menu() then goto next_item end wait(150) _G.mh_ab_caught_dlg = nil sampSendDialogResponse(3040, 1, 2, '') dlg = wait_dlg({25666}, 4000) _mh_flog('AB s2 retry dlg=' .. tostring(dlg)) if not dlg then mh_notify('[MH] {ff4444}\xd1\xf2\xf0\xee\xea\xe0 \xef\xee\xe8\xf1\xea\xe0 \xed\xe5 \xee\xf2\xea\xf0\xfb\xeb\xe0\xf1\xfc: ' .. buy_item.name, 0xFFFFFF) goto next_item end end wait(1100) _G.mh_ab_caught_dlg = nil sampSendDialogResponse(25666, 1, 0, search_str) dlg = wait_dlg({25667}, 4000) _mh_flog('AB s3 dlg=' .. tostring(dlg)) if not dlg then mh_notify('[MH] {ff8800}\xd0\xe5\xe7\xf3\xeb\xfc\xf2\xe0\xf2\xee\xe2 \xed\xe5\xf2: ' .. buy_item.name, 0xFFFFFF) goto next_item end do local list_text = sampGetDialogText() or '' local bnl = buy_item.name:lower() local found_idx = nil local fi = 0 for line in list_text:gmatch('[^\n]+') do local nm = line:gsub('{[^}]+}',''):match('^%d+[%.%)%s]+(.-)%s*$') or line:gsub('{[^}]+}',''):match('^%s*(.-)%s*$') if nm and nm:lower():find(bnl, 1, true) then found_idx = fi; break end fi = fi + 1 end if found_idx == nil then local fw = bnl:match('^(%S+)') if fw and #fw >= 4 then fi = 0 for line in list_text:gmatch('[^\n]+') do if line:gsub('{[^}]+}',''):lower():find(fw, 1, true) then found_idx = fi; break end fi = fi + 1 end end end _mh_flog('AB s3 found=' .. tostring(found_idx) .. ' item=' .. buy_item.name) if found_idx ~= nil then _G.mh_ab_caught_dlg = nil sampSendDialogResponse(25667, 1, found_idx, '') else mh_notify('[MH] {ff8800}\xcd\xe5 \xed\xe0\xf8\xb8\xeb: ' .. buy_item.name, 0xFFFFFF) sampSendDialogResponse(25667, 0, 0, '') goto next_item end end dlg = wait_dlg({26559, 26562, 3060}, 3000) _mh_flog('AB s4 dlg=' .. tostring(dlg)) if not dlg then mh_notify('[MH] {ff4444}\xc4\xe8\xe0\xeb\xee\xe3 \xf6\xe5\xed\xfb \xed\xe5 \xef\xee\xff\xe2\xe8\xeb\xf1\xff: ' .. buy_item.name, 0xFFFFFF) goto next_item end if dlg == 26559 then _G.mh_ab_caught_dlg = nil sampSendDialogResponse(dlg, 1, 0, '') dlg = wait_dlg({26562, 3060}, 2000) _mh_flog('AB s4b dlg=' .. tostring(dlg)) end if dlg == 26562 or dlg == 3060 then if not sampIsDialogActive() then goto next_item end local txt = sampGetDialogText() or '' local has_qty = txt:find('\xcf\xf0\xe8\xec\xe5\xf0:') ~= nil local _ab_qty = buy_item.qty or 1 local _ab_price = buy_item.max_price or 0 if has_qty then local resp = tostring(_ab_qty) .. ',' .. tostring(_ab_price) mh_notify('[MH \xc0\xe2\xf2\xee] {00cc00}\xd1\xea\xf3\xef: ' .. buy_item.name .. ' x' .. _ab_qty .. ' -> ' .. resp, 0xFFFFFF) _G.mh_ab_caught_dlg = nil sampSendDialogResponse(dlg, 1, 0, resp) _dsf3y(buy_item.name, _ab_price, _ab_qty, 'buy', 'ok') wait(500) else for _ab_i = 1, _ab_qty do if not fh_lv_autobuy_running then break end if _ab_i > 1 then wait(800) close_dlg() wait(500) if not open_shop_menu() then break end wait(200) _G.mh_ab_caught_dlg = nil sampSendDialogResponse(3040, 1, 2, '') dlg = wait_dlg({25666}, 4000) if not dlg then break end wait(1100) _G.mh_ab_caught_dlg = nil sampSendDialogResponse(25666, 1, 0, search_str) dlg = wait_dlg({25667}, 4000) if not dlg then break end wait(300) do local _lt = sampGetDialogText() or '' local _bnl = buy_item.name:lower() local _fidx = nil local _fi = 0 for _line in _lt:gmatch('[^\n]+') do local _nm = _line:gsub('{[^}]+}',''):match('^%d+[%.%)%s]+(.-)%s*$') or _line:gsub('{[^}]+}',''):match('^%s*(.-)%s*$') if _nm and _nm:lower():find(_bnl, 1, true) then _fidx = _fi; break end _fi = _fi + 1 end if _fidx == nil then sampSendDialogResponse(25667, 0, 0, '') break end _G.mh_ab_caught_dlg = nil sampSendDialogResponse(25667, 1, _fidx, '') end dlg = wait_dlg({26559, 26562, 3060}, 4000) if not dlg then break end if dlg == 26559 then _G.mh_ab_caught_dlg = nil sampSendDialogResponse(dlg, 1, 0, '') dlg = wait_dlg({26562, 3060}, 3000) if not dlg then break end end if not sampIsDialogActive() then break end end mh_notify('[MH \xc0\xe2\xf2\xee] {00cc00}\xd1\xea\xf3\xef \xf8\xf2 ' .. _ab_i .. '/' .. _ab_qty .. ': ' .. buy_item.name .. ' -> ' .. tostring(_ab_price), 0xFFFFFF) _G.mh_ab_caught_dlg = nil sampSendDialogResponse(dlg, 1, 0, tostring(_ab_price)) _dsf3y(buy_item.name, _ab_price, 1, 'buy', 'ok') wait(600) end end end ::next_item:: close_dlg() if pi < total and fh_lv_autobuy_running then open_shop_menu() wait(100) end end close_dlg() fh_lv_autobuy_running = false fh_lv_autobuy_status = '\xc3\xee\xf2\xee\xe2\xee' mh_notify('[MH \xc0\xe2\xf2\xee] {00cc00}\xc0\xe2\xf2\xee-\xf1\xea\xf3\xef\xea\xe0 \xe7\xe0\xe2\xe5\xf0\xf8\xe5\xed\xe0.', 0xFFFFFF) _G._mh_buy_ran_session = true lua_thread.create(function() wait(800); mh_push_own_preset_shop() end) end) end local function fh_mkt_run_lavka_scan() if fh_mkt_lv_scanning then return end fh_mkt_lv_scanning = true lua_thread.create(function() if #fh_mkt_lavka_ids == 0 then mh_notify('[FH Market] {ffaa00}Открываю лавку...', 0xFFFFFF) fh_mkt_lavka_ids = {}; fh_mkt_lavka_sep = {} fh_mkt_lavka_slot_w = nil; fh_mkt_lavka_slot_h = nil sampSendChat('/mm') local waited = 0 while #fh_mkt_lavka_ids == 0 and waited < 500 do wait(10); waited = waited + 1 end if #fh_mkt_lavka_ids == 0 then mh_notify('[FH Market] {ff4444}Лавка не открылась. Подойдите к прилавке и повторите.', 0xFFFFFF) fh_mkt_lv_scanning = false; return end wait(500) end local ids_snap = {} for _,v in ipairs(fh_mkt_lavka_ids) do table.insert(ids_snap, v) end fh_mkt_lv_done = 0; fh_mkt_lv_total = #ids_snap mh_notify('[FH Market] {ffaa00}Сканирую лавку... Слотов: ' .. #ids_snap, 0xFFFFFF) for _, td_id in ipairs(ids_snap) do if not fh_mkt_lv_scanning then break end local snap = fh_mkt_lv_done if sampIsDialogActive() then sampSendDialogResponse(sampGetCurrentDialogId(), 0, 0, '') wait(80) end sampSendClickTextdraw(td_id) local w = 0 while fh_mkt_lv_done == snap and w < 400 do wait(10); w = w + 1 end wait(50) end if sampIsDialogActive() then sampSendDialogResponse(sampGetCurrentDialogId(), 0, 0, '') end sampSendClickTextdraw(65535) wait(100) fh_mkt_lv_scanning = false; _ryb5t(); MH_util.kyb5x() _G._mkt_price_gcache = {} _G._mh_daily_push_last = 0 lua_thread.create(function() wait(500); _G._mh_upload_daily_prices() end) local tot = 0; for _ in pairs(fh_mkt_lavka) do tot = tot + 1 end mh_notify('[FH Market] {00cc00}Скан лавки завершён. Товаров: '..tot, 0xFFFFFF) end) end if not _G.mkt_detail_open then _G.mkt_detail_open = false end local function _mh_dtl_prefetch(nm) if not nm or nm == '' then return end local _cur_srv = _G.arz_srv_sel and _G.arz_srv_sel[0] or -1 local key = nm .. '|' .. tostring(_cur_srv) if _G._dtl_cache_nm == key and not _G._dtl_dirty then return end if _G._dtl_building then return end _G.mkt_detail_item = nm _G._dtl_cache_nm = nil _G._dtl_dirty = true _G._dtl_sell_rows = nil _G._dtl_buy_rows = nil _G._dtl_shop_hist = nil _G._dtl_stats = nil _G._dtl_ready = false _G._dtl_ready = false end if not _G.mkt_detail_item then _G.mkt_detail_item = '' end if not _G.mkt_detail_src then _G.mkt_detail_src = 'cp' end if not _G.mkt_cp_page then _G.mkt_cp_page = 1 end if not _G.mkt_lv_page then _G.mkt_lv_page = 1 end local MKT_PAGE_SIZE = 50 local SPARK_CHARS = {'.', ',', '_', '-', '=', '~', '+', '#'} local function fh_spark(history, max_days) max_days = max_days or 10 if not history or #history == 0 then return '—' end local slice = {} for i = math.min(#history, max_days), 1, -1 do table.insert(slice, history[i].price or 0) end if #slice == 0 then return '—' end local mn, mx = slice[1], slice[1] for _, v in ipairs(slice) do if v < mn then mn = v end if v > mx then mx = v end end local range = mx - mn local result = '' for _, v in ipairs(slice) do local idx = 1 if range > 0 then idx = math.floor((v - mn) / range * 7) + 1 else idx = 4 end result = result .. SPARK_CHARS[idx] end return result end local _stats_cache = setmetatable({}, {__mode='k'}) function _qty_all_sources(nm, days) if not nm or nm == '' then return 0 end local cutoff = os.date('%Y-%m-%d', os.time() - days*86400) local today = os.date('%Y-%m-%d') local nm_lo = nm:lower() local by_date = {} local e = fh_mkt_prices[nm] if e and e.cp_hist then for _, h in ipairs(e.cp_hist) do local dt = h.dt or '' if dt ~= '' and dt >= cutoff then local q = h.qty or 1 if q > (by_date[dt] or 0) then by_date[dt] = q end end end end for i = #fh_mkt_log, 1, -1 do local le = fh_mkt_log[i] if le and le.item == nm and le.op == 'sell' and (le.price or 0) > 0 then local _d, _m = (le.dt or ''):match('^(%d+)%.(%d+)') if _d and _m then local _yr = os.date('%Y') local _iso = string.format('%s-%02d-%02d', _yr, tonumber(_m), tonumber(_d)) if _iso > today then _iso = string.format('%d-%02d-%02d', tonumber(_yr)-1, tonumber(_m), tonumber(_d)) end if _iso >= cutoff then local q = le.qty or 1 if q > (by_date[_iso] or 0) then by_date[_iso] = q end end end end end local _dc = _G._mh_daily_cache and _G._mh_daily_cache[nm_lo] if _dc then for _, d in ipairs(_dc) do local dt = d.date or '' if dt ~= '' and dt >= cutoff and (d.s_cnt or 0) > 0 then if d.s_cnt > (by_date[dt] or 0) then by_date[dt] = d.s_cnt end end end end local cd = _G._mh_deals_cache and _G._mh_deals_cache[nm_lo] if cd then for _, d in ipairs(cd) do local dt = d.date or '' if dt ~= '' and dt >= cutoff then local sq = (d.s_qty or d.cnt or d.qty or 0) if sq > (by_date[dt] or 0) then by_date[dt] = sq end end end end local total = 0 for _, q in pairs(by_date) do total = total + q end return total > 0 and total or nil end local function _cached_stats(hist, anchor) if not hist or #hist == 0 then return nil, nil, nil, nil end local n = #hist local h1 = hist[1] local c = _stats_cache[hist] if c and c.n == n and c.h1 == h1 and c.anchor == anchor then return c.s7, c.s30, c.s1, c.trend end local s7 = _mjg5t(hist, 7, anchor) local s30 = _mjg5t(hist, 30, anchor) local s1 = _mjg5t(hist, 1, anchor) local trend = _G._xvn2w(hist) _stats_cache[hist] = {n=n, h1=h1, anchor=anchor, s7=s7, s30=s30, s1=s1, trend=trend} return s7, s30, s1, trend end _G._mkt_row_cache = {} _G._mkt_row_cache_key = '' _G._mkt_row_cache_ready = false _G._mkt_row_building = false local function _mkt_build_row_cache(mf, cp_from, cp_to, d_scale) if _G._mkt_row_building then return end local _rv = tostring(_G._mh_db_ver or 0)..'|'..tostring(_G._mh_shop_ver or 0) ..'|'..tostring(_G._mh_deals_cache_ver or 0)..'|'..tostring(_G._mh_daily_cache_ver or 0) local _rk = tostring(cp_from)..'-'..tostring(cp_to)..'|'..tostring(mf)..'|'.._rv if _G._mkt_row_cache_key == _rk then return end _G._mkt_row_building = true _G._mkt_row_cache_ready = false local _b_mf = mf local _b_from = cp_from local _b_to = cp_to local _b_d = d_scale or 1 local _b_rk = _rk _G._mkt_row_cache = {} _G._mkt_row_cache_key = _b_rk _G._mkt_row_cache_ready = false _G._mkt_row_building = true _G._mkt_row_build_next = _b_from _G._mkt_row_build_to = _b_to _G._mkt_row_build_mf = _b_mf _G._mkt_row_build_d = _b_d end local _MKT_ROW_BATCH = 8 local function _mkt_row_cache_step() if not _G._mkt_row_building then return end local mf = _G._mkt_row_build_mf local d = _G._mkt_row_build_d or 1 local _rcw_fixed = 180 * d - 6 local from = _G._mkt_row_build_next local stop = math.min(from + _MKT_ROW_BATCH - 1, _G._mkt_row_build_to) local cache = _G._mkt_row_cache for ri = from, stop do local r = mf[ri]; if not r then break end local e = r.e local nm = r.nm or '' local hist = e.cp_hist local has_deep = hist and #hist > 0 local today_price, s7, s30, _today_s, _trd local _mp = _mh_get_mkt_price(nm) _mp = _mh_guard_mkt_price(nm, _mp) if _mp then today_price = (_mp.today and _mp.today > 0) and _mp.today or (_mp.avg7 and _mp.avg7 > 0) and _mp.avg7 or nil s7 = _mp.avg7 and {avg=_mp.avg7} or nil s30 = _mp.avg30 and {avg=_mp.avg30} or nil end if has_deep then local _s7c, _s30c, _s1c, _trdc = _cached_stats(hist, today_price) _today_s = _s1c; _trd = _trdc local _q1a = _qty_all_sources(nm, 1) local _q7a = _qty_all_sources(nm, 7) local _q30a = _qty_all_sources(nm, 30) local _s_total = (e.s_totalC or 0) if not _q30a and _s_total > 0 then _q30a = _s_total if not _q7a then _q7a = math.max(1, math.floor(_s_total / 4)) end end if _mp then if _mp.avg7 then s7 = {avg=_mp.avg7, qty=_q7a or (_s7c and _s7c.qty) or nil} end if _mp.avg30 then s30 = {avg=_mp.avg30, qty=_q30a or (_s30c and _s30c.qty) or nil} end end if _today_s then _today_s = {avg=_today_s.avg, qty=_q1a or (_today_s and _today_s.qty) or nil} end end if not today_price then if e.cp_sp and e.cp_sp > 0 then today_price = e.cp_sp elseif e.s_avg and e.s_avg > 0 then today_price = e.s_avg end end if not s7 or not s30 then local _fb = _mp and ((_mp.avg7 and _mp.avg7>0 and _mp.avg7) or (_mp.avg30 and _mp.avg30>0 and _mp.avg30) or (_mp.today and _mp.today>0 and _mp.today)) if not _fb and e.s_avg and e.s_avg > 0 then _fb = e.s_avg end if _fb then if not s7 then s7 = {avg=_fb} end if not s30 then s30 = {avg=_fb, min=e.s_min, max=e.s_max, qty=e.s_totalC or 0} end end end if s30 and (not s30.qty or s30.qty == 0) and (e.s_totalC or 0) > 0 then s30 = {avg=s30.avg, min=s30.min, max=s30.max, qty=e.s_totalC} end if not _trd then _trd = {icon=_ic_min, text='', is_neutral=true} end local tc_r, tc_g, tc_b if _trd.is_up then tc_r,tc_g,tc_b = 0.3, 0.95, 0.3 elseif _trd.is_down then tc_r,tc_g,tc_b = 1.0, 0.40, 0.3 else tc_r,tc_g,tc_b = 0.6, 0.60, 0.6 end local today_qty = _today_s and _today_s.qty or nil local _q30fb = s30 and s30.qty and s30.qty > 0 and s30.qty or (e.s_totalC or 0) if (not today_qty or today_qty == 0) and _q30fb > 0 then today_qty = math.max(1, math.floor(_q30fb / 30)) end if s7 and _q30fb > 0 then local _q7fb = math.max(1, math.floor(_q30fb / 4)) local _q7cur = s7.qty or 0 s7 = {avg=s7.avg, qty=math.max(_q7cur, _q7fb)} end cache[ri] = { tc_r=tc_r, tc_g=tc_g, tc_b=tc_b, fmt_avg = e and e.s_avg and e.s_avg > 0 and (' $'.._kcr3y(e.s_avg)) or nil, fmt_min = e and e.s_min and e.s_min > 0 and (' $'.._kcr3y(e.s_min)) or nil, fmt_max = e and e.s_max and e.s_max > 0 and (' $'.._kcr3y(e.s_max)) or nil, fmt_qty = (function() local q = _qty_all_sources(nm, 30) or (e and e.s_totalC) or 0; return q > 0 and (' '.._kcr3y(q)) or nil end)(), fmt_trd = _trd.icon..' '.._cyr5f(_trd.text), } end _G._mkt_row_build_next = stop + 1 if stop >= _G._mkt_row_build_to then _G._mkt_row_building = false _G._mkt_row_cache_ready = true end end local function _fmt_price_arz(n) if not n or n <= 0 then return '$0' end n = math.floor(n) if n >= 1000000000 then local m = math.floor(n / 1000000000) local rem = n % 1000000000 local kk = math.floor(rem / 1000000) local k = rem % 1000000 local s = 'М ' .. tostring(m) if kk > 0 then s = s .. ' КК ' .. tostring(kk) end if k > 0 then local kw = math.floor(k/1000); local kr = k%1000 s = s .. ' К ' .. tostring(kw) .. '.' .. string.format('%03d',kr) end return s elseif n >= 1000000 then local kk = math.floor(n / 1000000) local rem = n % 1000000 local s = 'КК ' .. tostring(kk) if rem > 0 then local kw = math.floor(rem/1000); local kr = rem%1000 s = s .. ' К ' .. tostring(kw) .. '.' .. string.format('%03d',kr) end return s elseif n >= 1000 then local whole = math.floor(n / 1000) local rem = n % 1000 if rem == 0 then return 'К ' .. tostring(whole) .. '.000' else return 'К ' .. tostring(whole) .. '.' .. string.format('%03d', rem) end else return '$' .. tostring(n) end end function MH_util.hmc6p(item_name, src) local d = settings.general.custom_dpi local ar = settings.interface.accent_r or 1 local ag = settings.interface.accent_g or .65 local ab = settings.interface.accent_b or 0.0 local sb_r = settings.interface.sell_btn_r or 0.10 local sb_g = settings.interface.sell_btn_g or 0.45 local sb_b = settings.interface.sell_btn_b or 0.10 local bb_r = settings.interface.buy_btn_r or 0.00 local bb_g = settings.interface.buy_btn_g or 0.28 local bb_b = settings.interface.buy_btn_b or 0.50 local lp_r = settings.overlay and settings.overlay.log_price_r or 1.0 local lp_g = settings.overlay and settings.overlay.log_price_g or 0.85 local lp_b = settings.overlay and settings.overlay.log_price_b or 0.2 local ac = imgui.ImVec4(ar, ag, ab, 1) local cp_e = fh_mkt_prices[item_name] local lv_e = fh_mkt_lavka[item_name] local cp_hist = (cp_e and cp_e.cp_hist) or {} if not _G.dtl_tab then _G.dtl_tab = imgui.new.int(0) end local _dtl_cur_srv = _G.arz_srv_sel and _G.arz_srv_sel[0] or -1 local _dtl_cache_key = item_name .. '|' .. tostring(_dtl_cur_srv) if _G._dtl_cache_nm ~= _dtl_cache_key then local _prev_nm = _G._dtl_cache_nm _G._dtl_cache_nm = _dtl_cache_key _G._dtl_dirty = true _G._dtl_frame_ctr = 0 local _item_changed = _prev_nm ~= nil if _item_changed then _G._dtl_sell_rows = nil _G._dtl_buy_rows = nil _G._dtl_shop_hist = nil _G._dtl_stats = nil _G._dtl_ready = false _G._dtl_last_data_ver = nil end end _G._dtl_frame_ctr = ((_G._dtl_frame_ctr or 0) + 1) % 6 local _dtl_heavy_frame = (_G._dtl_frame_ctr == 0) if _G._dtl_dirty and not _G._dtl_building then local _cur_data_ver = tostring(_G._mh_db_ver or 0) .. '|' .. tostring(_G._mh_shop_ver or 0) .. '|' .. tostring(_G._mh_daily_cache_ver or 0) .. '|' .. _dtl_cache_key if _G._dtl_last_data_ver == _cur_data_ver then _G._dtl_dirty = false if _G._dtl_stats then _G._dtl_ready = true end else _G._dtl_last_data_ver = _cur_data_ver _G._dtl_building = true local _build_nm = item_name local _build_key = _dtl_cache_key if not _G._mh_deals_pull_ts or (os.time() - _G._mh_deals_pull_ts) > 600 then _G._mh_deals_pull_ts = os.time() lua_thread.create(function() _G._mh_deals_pull(true) _G._mh_upload_deals() end) end if not _G._mh_prices_pull_ts or (os.time() - (_G._mh_prices_pull_ts or 0)) > 1800 then _G._mh_prices_pull_ts = os.time() lua_thread.create(function() wait(800); _G._mh_prices_pull(true) end) end lua_thread.create(function() do local _nm_lo = _build_nm:lower() local _sr, _br = {}, {} local _sh_local = fh_get_daily_shop_history(_build_nm) do local _nm_lo_log = item_name:lower() local _log_days = {} for _, _le in ipairs(fh_mkt_log) do if _le and _le.item and _le.item:lower() == _nm_lo_log and _le.price and _le.price > 0 then local _dm = _le.dt and _le.dt:match('^(%d%d)%.(%d%d)') local _day_d, _day_m if _le.dt then _day_d, _day_m = _le.dt:match('^(%d%d)%.(%d%d)') end local _day_key if _day_d and _day_m then local _yr = os.date('%Y') _day_key = _yr..'-'.._day_m..'-'.._day_d end if _day_key then if not _log_days[_day_key] then _log_days[_day_key] = {sP=0,sC=0,bP=0,bC=0,bMax=0,sMin=math.huge} end local _q = _le.qty or 1 local _is_sell = (_le.op == 'SELL') or (_le.op == 'sell') if _is_sell then _log_days[_day_key].sP = _log_days[_day_key].sP + _le.price * _q _log_days[_day_key].sC = _log_days[_day_key].sC + _q if _le.price < _log_days[_day_key].sMin then _log_days[_day_key].sMin = _le.price end else _log_days[_day_key].bP = _log_days[_day_key].bP + _le.price * _q _log_days[_day_key].bC = _log_days[_day_key].bC + _q if _le.price > _log_days[_day_key].bMax then _log_days[_day_key].bMax = _le.price end end end end end local _sh_idx = {} for _i, _e in ipairs(_sh_local) do _sh_idx[_e.date] = _i end for _dk, _dv in pairs(_log_days) do local _log_s = _dv.sC > 0 and math.floor(_dv.sP / _dv.sC) or nil local _log_sMin = _dv.sMin and _dv.sMin < math.huge and _dv.sMin or nil local _log_b = _dv.bC > 0 and math.floor(_dv.bP / _dv.bC) or nil local _log_bMax = _dv.bMax and _dv.bMax > 0 and _dv.bMax or nil local _ei = _sh_idx[_dk] if _ei then local _e = _sh_local[_ei] if _log_s and not _e.s_avg then _e.s_avg = _log_s; _e.s_src = 'log' end if _log_sMin and (not _e.s_min or _log_sMin < _e.s_min) then _e.s_min = _log_sMin end if _log_b and not _e.b_avg then _e.b_avg = _log_b; _e.b_src = 'log' end if _log_bMax and (not _e.b_max or _log_bMax > _e.b_max) then _e.b_max = _log_bMax end else if _log_s or _log_b then table.insert(_sh_local, { date=_dk, s_avg=_log_s, s_min=_log_sMin, b_avg=_log_b, b_max=_log_bMax, s_src='log', b_src='log' }) end end end table.sort(_sh_local, function(a,b) return a.date > b.date end) end do local _cloud_h = (_G._mh_deals_cache or {})[item_name:lower()] or {} local _sh_idx2 = {} for _i,_e in ipairs(_sh_local) do _sh_idx2[_e.date]=_i end for _, cd in ipairs(_cloud_h) do local dt = cd.date or '' local src = cd.src or 'log' if dt == '' then goto _cloud_merge_next end local ei = _sh_idx2[dt] local sh if ei then sh = _sh_local[ei] else sh = {date=dt} table.insert(_sh_local, sh) _sh_idx2[dt] = #_sh_local end if src == 'deep' then if (cd.s_avg or 0)>0 and not sh.deep_price then sh.deep_price=cd.s_avg end else if (cd.s_avg or 0)>0 and not sh.deal_s then sh.deal_s=cd.s_avg; sh.deal_s_qty=(cd.s_qty or cd.total_qty or 0) end if (cd.b_avg or 0)>0 and not sh.deal_b then sh.deal_b=cd.b_avg; sh.deal_b_qty=(cd.b_qty or cd.total_qty or 0) end end ::_cloud_merge_next:: end if #_cloud_h > 0 then table.sort(_sh_local, function(a,b) return a.date > b.date end) end end do local _cd_srv = _G._mh_daily_cache_srv or -1 local _cur_sid = _G._mh_get_srv_id and _G._mh_get_srv_id() or -1 local _srv_ok = (_cd_srv == _cur_sid) or (_cur_sid == -1) or (_cd_srv == -1) local _daily_h = _srv_ok and ((_G._mh_daily_cache or {})[item_name:lower()] or {}) or {} if #_daily_h > 0 then local _sh_idx3 = {} for _i, _e in ipairs(_sh_local) do _sh_idx3[_e.date] = _i end for _, dh in ipairs(_daily_h) do local dt = dh.date or '' if dt == '' then goto _daily_merge_next end local ei = _sh_idx3[dt] local sh if ei then sh = _sh_local[ei] else sh = {date=dt} table.insert(_sh_local, sh) _sh_idx3[dt] = #_sh_local end local cloud_s = (dh.s_avg or 0) > 0 and dh.s_avg or nil local cloud_b = (dh.b_avg or 0) > 0 and dh.b_avg or nil local cloud_contrib = dh.contrib or 1 if cloud_s then if not sh.s_avg then sh.s_avg = cloud_s sh.s_cnt = dh.s_cnt or 0 sh.s_src = cloud_contrib > 1 and 'cloud+'..cloud_contrib or 'cloud' elseif cloud_contrib > 1 then local loc_cnt = sh.s_cnt or 0 local cld_cnt = dh.s_cnt or 0 if loc_cnt > 0 and cld_cnt > 0 then sh.s_avg = math.floor((sh.s_avg * loc_cnt + cloud_s * cld_cnt) / (loc_cnt + cld_cnt)) sh.s_cnt = loc_cnt + cld_cnt end sh.s_src = 'cloud+'..cloud_contrib end end if cloud_b then if not sh.b_avg then sh.b_avg = cloud_b sh.b_cnt = dh.b_cnt or 0 sh.b_src = cloud_contrib > 1 and 'cloud+'..cloud_contrib or 'cloud' elseif cloud_contrib > 1 then local loc_cnt = sh.b_cnt or 0 local cld_cnt = dh.b_cnt or 0 if loc_cnt > 0 and cld_cnt > 0 then sh.b_avg = math.floor((sh.b_avg * loc_cnt + cloud_b * cld_cnt) / (loc_cnt + cld_cnt)) sh.b_cnt = loc_cnt + cld_cnt end sh.b_src = 'cloud+'..cloud_contrib end end ::_daily_merge_next:: end table.sort(_sh_local, function(a,b) return a.date > b.date end) end if not _G._mh_daily_pull_last or (os.time() - (_G._mh_daily_pull_last or 0)) > 1800 then lua_thread.create(function() wait(200); _G._mh_daily_pull(true) end) end end local _dtl_cur_srv_id = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)() local _osh_sell_seen = {} local _osh_buy_seen = {} for _, sh in pairs(fh_other_shops) do if type(sh) ~= 'table' then goto _dtl_sh_next end if _dtl_cur_srv_id ~= -1 and sh.server_id and sh.server_id ~= -1 and sh.server_id ~= _dtl_cur_srv_id then goto _dtl_sh_next end local _own_lo = (sh.owner or '?'):lower() for _, si in ipairs(sh.sell_items or {}) do if type(si.name)=='string' and si.name:lower()==_nm_lo and si.price and si.price>0 then if not _osh_sell_seen[_own_lo] or si.price < _osh_sell_seen[_own_lo].price then _osh_sell_seen[_own_lo] = {price=si.price, owner=sh.owner or '?', qty=si.qty, src=''} end end end for _, bi in ipairs(sh.buy_items or {}) do if type(bi.name)=='string' and bi.name:lower()==_nm_lo and bi.price and bi.price>0 then if not _osh_buy_seen[_own_lo] or bi.price > _osh_buy_seen[_own_lo].price then _osh_buy_seen[_own_lo] = {price=bi.price, owner=sh.owner or '?', qty=bi.qty, src=''} end end end ::_dtl_sh_next:: end for _, v in pairs(_osh_sell_seen) do table.insert(_sr, v) end for _, v in pairs(_osh_buy_seen) do table.insert(_br, v) end if mh_arz_data and mh_arz_items_db then local _ui_srv = ARZ_SERVERS[_G.arz_srv_sel and (_G.arz_srv_sel[0]+1) or 1] local _ui_srv_id = _ui_srv and _ui_srv.id or -1 local _dtl_srv_id = _ui_srv_id if _dtl_srv_id == -1 then local _auto_idx = _mpf7d() local _auto_srv = ARZ_SERVERS[_auto_idx + 1] _dtl_srv_id = _auto_srv and _auto_srv.id or -1 end local _dtl_owners_seen = {} for _, _r in ipairs(_sr) do if _r.owner then _dtl_owners_seen[_r.owner:lower()] = true end end for _, lv in ipairs(mh_arz_data) do if type(lv)=='table' then if _dtl_srv_id ~= -1 and lv.serverId ~= _dtl_srv_id then goto _dtl_api_next end local _lv_own_lo = (lv.username or '?'):lower() if _dtl_owners_seen[_lv_own_lo] then goto _dtl_api_next end local owner = lv.username or '?' if lv.items_sell and lv.price_sell then for ii, iid in ipairs(lv.items_sell) do local nm2 = mh_arz_items_db[_G._bqs3v(iid)] if nm2 and tostring(nm2):lower()==_nm_lo then local pr = lv.price_sell[ii] if pr and pr>0 then table.insert(_sr, {price=pr, owner=owner, qty=lv.count_sell and lv.count_sell[ii], src='[API]'}) end end end end if lv.items_buy and lv.price_buy then for ii, iid in ipairs(lv.items_buy) do local nm2 = mh_arz_items_db[_G._bqs3v(iid)] if nm2 and tostring(nm2):lower()==_nm_lo then local pr = lv.price_buy[ii] if pr and pr>0 then table.insert(_br, {price=pr, owner=owner, qty=lv.count_buy and lv.count_buy[ii], src='[API]'}) end end end end ::_dtl_api_next:: end end end table.sort(_sr, function(a,b) return a.price < b.price end) table.sort(_br, function(a,b) return a.price > b.price end) if _G._dtl_cache_nm == _build_key then _G._dtl_sell_rows = _sr _G._dtl_buy_rows = _br _G._dtl_shop_hist = _sh_local do local _today_str = os.date('%Y-%m-%d') local _live_s_min, _live_s_avg_sum, _live_s_avg_cnt = nil, 0, 0 local _live_b_max, _live_b_avg_sum, _live_b_avg_cnt = nil, 0, 0 for _, sh in pairs(fh_other_shops or {}) do if type(sh) == 'table' then for _, si in ipairs(sh.sell_items or {}) do if type(si.name)=='string' and si.name:lower()==_nm_lo and (si.price or 0)>0 then if not _live_s_min or si.price < _live_s_min then _live_s_min = si.price end _live_s_avg_sum = _live_s_avg_sum + si.price _live_s_avg_cnt = _live_s_avg_cnt + 1 end end for _, bi in ipairs(sh.buy_items or {}) do if type(bi.name)=='string' and bi.name:lower()==_nm_lo and (bi.price or 0)>0 then if not _live_b_max or bi.price > _live_b_max then _live_b_max = bi.price end _live_b_avg_sum = _live_b_avg_sum + bi.price _live_b_avg_cnt = _live_b_avg_cnt + 1 end end end end if mh_arz_data and mh_arz_items_db then for _, lv in ipairs(mh_arz_data) do if type(lv)=='table' then if lv.items_sell then for ii,iid in ipairs(lv.items_sell) do local bid = _G._bqs3v(iid) local nm2 = mh_arz_items_db[bid] if nm2 and tostring(nm2):lower()==_nm_lo then local pr = lv.price_sell and lv.price_sell[ii] if (pr or 0)>0 then if not _live_s_min or pr<_live_s_min then _live_s_min=pr end _live_s_avg_sum=_live_s_avg_sum+pr; _live_s_avg_cnt=_live_s_avg_cnt+1 end end end end if lv.items_buy then for ii,iid in ipairs(lv.items_buy) do local bid = _G._bqs3v(iid) local nm2 = mh_arz_items_db[bid] if nm2 and tostring(nm2):lower()==_nm_lo then local pr = lv.price_buy and lv.price_buy[ii] if (pr or 0)>0 then if not _live_b_max or pr>_live_b_max then _live_b_max=pr end _live_b_avg_sum=_live_b_avg_sum+pr; _live_b_avg_cnt=_live_b_avg_cnt+1 end end end end end end end if _live_s_min or _live_b_max then local _te = nil for _, e in ipairs(_sh_local) do if e.date == _today_str then _te = e; break end end if not _te then _te={date=_today_str}; table.insert(_sh_local,1,_te) end if _live_s_min then if not _te.s_min or _live_s_min < _te.s_min then _te.s_min=_live_s_min end end if _live_s_avg_cnt > 0 and not _te.s_avg then _te.s_avg = math.floor(_live_s_avg_sum / _live_s_avg_cnt) end if _live_b_max then if not _te.b_max or _live_b_max > _te.b_max then _te.b_max=_live_b_max end end if _live_b_avg_cnt > 0 and not _te.b_avg then _te.b_avg = math.floor(_live_b_avg_sum / _live_b_avg_cnt) end end end local _sh_fin = _sh_local or {} do local _nm_lo_sh = _build_nm:lower() local _cloud_hist = _G._mh_daily_cache and _G._mh_daily_cache[_nm_lo_sh] if _cloud_hist and #_cloud_hist > 0 then local _sh_fin_dates = {} for _, _e in ipairs(_sh_fin) do if _e.date and _e.date ~= '' then _sh_fin_dates[_e.date] = true end end local _sh_fin_ext = {} for _, _e in ipairs(_sh_fin) do _sh_fin_ext[#_sh_fin_ext+1] = _e end for _, _ce in ipairs(_cloud_hist) do local _cd = _ce.date or '' if _cd ~= '' and not _sh_fin_dates[_cd] then _sh_fin_ext[#_sh_fin_ext+1] = { date = _cd, s_avg = _ce.s_avg or 0, b_avg = _ce.b_avg or 0, } end end _sh_fin = _sh_fin_ext end end local function _sh_sell_val(e) return (e.s_min and e.s_min > 0 and e.s_min) or (e.s_avg and e.s_avg > 0 and e.s_avg) or (e.deal_s and e.deal_s > 0 and e.deal_s) or (e.deep_price and e.deep_price > 0 and e.deep_price) or nil end local function _sh_buy_val(e) return (e.b_max and e.b_max > 0 and e.b_max) or (e.b_avg and e.b_avg > 0 and e.b_avg) or (e.deal_b and e.deal_b > 0 and e.deal_b) or nil end local function _sh_avg_fin(days_n, field) local cutoff = os.date('%Y-%m-%d', os.time() - days_n*86400) local vals = {} for _, e in ipairs(_sh_fin) do if (e.date or '') >= cutoff then local v if field == 's_avg' or field == 'sell' then v = _sh_sell_val(e) elseif field == 'b_avg' or field == 'b_max' or field == 'buy' then v = _sh_buy_val(e) else v = e[field] end if v and v > 0 then table.insert(vals, v) end end end if #vals == 0 then return nil end if #vals == 1 then return vals[1] end table.sort(vals) local n = #vals local q1 = vals[math.max(1, math.floor(n*0.25 + 0.5))] local q3 = vals[math.min(n, math.floor(n*0.75 + 0.5))] local iqr = q3 - q1 local lo, hi = q1 - iqr*1.5, q3 + iqr*1.5 local s, c = 0, 0 for _, v in ipairs(vals) do if iqr == 0 or (v >= lo and v <= hi) then s = s + v; c = c + 1 end end return c > 0 and math.floor(s/c) or math.floor(vals[math.ceil(n/2)]) end local _nm_lo_thr = _build_nm:lower() local _cutoff30_thr = os.date('%Y-%m-%d', os.time() - 30*86400) local _today_thr = os.date('%Y-%m-%d') local _all_px_thr = {} local _dates_seen_thr = {} local function _thr_add(date, price, weight, qty) if not date or date=='' or not price or price<=0 then return end if date < _cutoff30_thr then return end if not _dates_seen_thr[date] then _dates_seen_thr[date]={} end local k = tostring(math.floor(price)) if not _dates_seen_thr[date][k] then _dates_seen_thr[date][k]=true table.insert(_all_px_thr,{date=date,price=price,weight=weight or 1,qty=qty or 1}) else for _,_ap in ipairs(_all_px_thr) do if _ap.date==date and math.floor(_ap.price)==math.floor(price) then _ap.qty = (_ap.qty or 1) + (qty or 0); break end end end end local _log_day_thr={} for i=#fh_mkt_log,1,-1 do local le=fh_mkt_log[i] if le and le.item and le.item:lower()==_nm_lo_thr and le.op=='sell' and (le.price or 0)>0 then local _d,_m=(le.dt or ''):match('^(%d+)%.(%d+)') if _d and _m then local _yr=os.date('%Y') local _iso=string.format('%s-%02d-%02d',_yr,tonumber(_m),tonumber(_d)) if _iso>_today_thr then _iso=string.format('%d-%02d-%02d',tonumber(_yr)-1,tonumber(_m),tonumber(_d)) end if _iso>=_cutoff30_thr then if not _log_day_thr[_iso] then _log_day_thr[_iso]={sum=0,cnt=0} end _log_day_thr[_iso].sum=_log_day_thr[_iso].sum+le.price*(le.qty or 1) _log_day_thr[_iso].cnt=_log_day_thr[_iso].cnt+(le.qty or 1) end end end end for _iso,v in pairs(_log_day_thr) do _thr_add(_iso,math.floor(v.sum/v.cnt),4,v.cnt) end local _cd=_G._mh_deals_cache and _G._mh_deals_cache[_nm_lo_thr] if _cd then for _,e in ipairs(_cd) do if (e.s_avg or 0)>0 then _thr_add(e.date,e.s_avg,3,e.cnt or e.qty or 1) end end end local _shc=_G._mh_shop_hist_cache and _G._mh_shop_hist_cache[_nm_lo_thr] if _shc then for _,e in ipairs(_shc) do local _sv = (e.s_min and e.s_min>0 and e.s_min) or (e.s_avg and e.s_avg>0 and e.s_avg) if _sv then _thr_add(e.date, _sv, 2) end end end for _,e in ipairs(_sh_local or {}) do local _sv = (e.s_min and e.s_min>0 and e.s_min) or (e.s_avg and e.s_avg>0 and e.s_avg) if _sv then _thr_add(e.date, _sv, 2) end end local _px_by_date_thr={} for _,_ap in ipairs(_all_px_thr) do local _d=_ap.date or '' if _d~='' and (_ap.price or 0)>0 then if not _px_by_date_thr[_d] then _px_by_date_thr[_d]={} end for _w=1,(_ap.weight or 1) do table.insert(_px_by_date_thr[_d],_ap.price) end end end local function _thr_qty(days_n) local cutoff=os.date('%Y-%m-%d',os.time()-days_n*86400) local t=0 for _,_ap in ipairs(_all_px_thr) do if _ap.date>=cutoff and (_ap.weight or 0)>=3 then t = t + (_ap.qty or 1) end end return t>0 and t or nil end local _anch_thr={} for _,e in ipairs(_sh_local or {}) do if (e.s_avg or 0)>0 then table.insert(_anch_thr,e.s_avg) elseif (e.b_avg or 0)>0 then table.insert(_anch_thr,e.b_avg) end end local _anch_val=nil if #_anch_thr>0 then table.sort(_anch_thr); _anch_val=_anch_thr[math.ceil(#_anch_thr/2)] end local _mp_thr=_mh_get_mkt_price(_build_nm) local _today_p=_mp_thr and _mp_thr.today or nil local _cp_hist_thr = (fh_mkt_prices[_build_nm] or {}).cp_hist if _cp_hist_thr then local function _cp_thr_ok(price) if not _anch_val or _anch_val <= 0 then return true end local _r = price > _anch_val and (price / _anch_val) or (_anch_val / price) return _r < 5.0 end for _, h in ipairs(_cp_hist_thr) do if (h.price or 0) > 0 and (h.dt or '') ~= '' then if _cp_thr_ok(h.price) then _thr_add(h.dt, h.price, 5) end end end end local _log_anch_7, _log_anch_30 local _mkt_avg_7, _mkt_avg_30 do local _cut7 = os.date('%Y-%m-%d', os.time() - 7*86400) local _cut30 = os.date('%Y-%m-%d', os.time() - 30*86400) local function _fill_days(min_w) local d7={}; local d30={} for _, _ap in ipairs(_all_px_thr) do if (_ap.weight or 0) >= min_w then if _ap.date >= _cut30 then if not d30[_ap.date] then d30[_ap.date]={} end table.insert(d30[_ap.date], _ap.price) end if _ap.date >= _cut7 then if not d7[_ap.date] then d7[_ap.date]={} end table.insert(d7[_ap.date], _ap.price) end end end return d7, d30 end local _day7, _day30 = _fill_days(3) local _used_w2 = false local _days3_count = 0 for _ in pairs(_day7) do _days3_count = _days3_count + 1 end if not next(_day7) and not next(_day30) then _day7, _day30 = _fill_days(2) _used_w2 = true elseif _days3_count < 3 then local _day7b, _day30b = _fill_days(2) _used_w2 = true for _d, _dp in pairs(_day7b) do if not _day7[_d] then _day7[_d] = _dp end end for _d, _dp in pairs(_day30b) do if not _day30[_d] then _day30[_d] = _dp end end end local function _day_median(prices) if #prices == 0 then return nil end table.sort(prices) return prices[math.ceil(#prices/2)] end local _m7={}; local _m30={} local _s7, _c7, _s30, _c30 = 0, 0, 0, 0 for _, _dp in pairs(_day30) do local _med = _day_median(_dp); if _med then table.insert(_m30, _med); _s30=_s30+_med; _c30=_c30+1 end end for _, _dp in pairs(_day7) do local _med = _day_median(_dp); if _med then table.insert(_m7, _med); _s7=_s7+_med; _c7=_c7+1 end end local function _iqr_avg(pts) if #pts == 0 then return nil end if #pts == 1 then return pts[1] end table.sort(pts) local n = #pts local q1 = pts[math.max(1, math.floor(n*0.25+0.5))] local q3 = pts[math.min(n, math.floor(n*0.75+0.5))] local iqr = q3 - q1 local lo = q1 - iqr*1.5; local hi = q3 + iqr*1.5 local s, c = 0, 0 for _, v in ipairs(pts) do if iqr == 0 or (v >= lo and v <= hi) then s=s+v; c=c+1 end end return c > 0 and math.floor(s/c) or pts[math.ceil(n/2)] end _mkt_avg_7 = _iqr_avg(_m7) _mkt_avg_30 = _iqr_avg(_m30) do local _x5_ref = nil local _sh_live = {} local _x5_srv_id = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)() for _, _sh in pairs(fh_other_shops) do if _x5_srv_id == -1 or not _sh.server_id or _sh.server_id == -1 or _sh.server_id == _x5_srv_id then for _, _si in ipairs(_sh.sell_items or {}) do if type(_si.name)=='string' and _si.name:lower()==_nm_lo_thr then local _sp = tonumber(_si.price) or 0 if _sp > 0 then table.insert(_sh_live, _sp) end end end end end if #_sh_live > 0 then table.sort(_sh_live) local _take = math.min(3, #_sh_live) local _min_s, _min_c = 0, 0 for _mi = 1, _take do _min_s = _min_s + _sh_live[_mi]; _min_c = _min_c + 1 end _x5_ref = math.floor(_min_s / _min_c) end if not _x5_ref then if _anch_val and _anch_val > 0 then _x5_ref = _anch_val else local _shc_x5 = _G._mh_shop_hist_cache and _G._mh_shop_hist_cache[_nm_lo_thr] if _shc_x5 and #_shc_x5 > 0 then local _sv = {} for _, _se in ipairs(_shc_x5) do if (_se.s_avg or 0) > 0 then table.insert(_sv, _se.s_avg) end end if #_sv > 0 then table.sort(_sv) _x5_ref = _sv[math.ceil(#_sv/2)] end end end end if _x5_ref and _x5_ref > 0 then local _thr_live = (#_sh_live > 0) and 2.5 or 3.0 local function _x5_fix(v) if not v then return v end if v > _x5_ref then local ratio = v / _x5_ref if ratio >= _thr_live then return math.floor(_x5_ref) end end return v end _mkt_avg_7 = _x5_fix(_mkt_avg_7) _mkt_avg_30 = _x5_fix(_mkt_avg_30) end end if _c7 > 0 then _log_anch_7 = math.floor(_s7 / _c7) end if _c30 > 0 then _log_anch_30 = math.floor(_s30 / _c30) end end _G._dtl_stats = { sh_s_7 = _sh_avg_fin(7, 'sell') or _sh_avg_fin(30, 'sell'), sh_s_30 = _sh_avg_fin(30, 'sell') or _sh_avg_fin(7, 'sell'), sh_b_7 = _sh_avg_fin(7, 'buy') or _sh_avg_fin(30, 'buy'), sh_b_30 = _sh_avg_fin(30, 'buy') or _sh_avg_fin(7, 'buy'), mkt_7 = _mkt_avg_7, mkt_w2 = _used_w2, mkt_30 = _mkt_avg_30, today = _today_p, qty1 = _qty_all_sources(_build_nm, 1), qty7 = (function() local _q = (_qty_all_sources(_build_nm, 7) or _thr_qty(7) or 0) return _q > 0 and _q or nil end)(), qty30 = _qty_all_sources(_build_nm, 30) or _thr_qty(30) or (function() local _fe = (fh_mkt_prices[_build_nm] or fh_mkt_auto[_build_nm]) return _fe and (_fe.s_totalC or 0) > 0 and _fe.s_totalC or nil end)(), all_px = _all_px_thr, px_by_date = _px_by_date_thr, shop_anchor = _anch_val, log_anch_7 = _log_anch_7, log_anch_30 = _log_anch_30, } _G._dtl_ready = true _G._dtl_dirty = false end _G._dtl_building = false end end) end end local sell_rows = _G._dtl_sell_rows or {} local buy_rows = _G._dtl_buy_rows or {} imgui.TextColored(ac, _cyr5f('Товар: ' .. item_name)) local _hdr_date = (cp_e and cp_e.date) or (lv_e and lv_e.date) if not _hdr_date then local _nm_lo_hdr = item_name:lower() for _, _sh_hdr in pairs(fh_other_shops) do local _has = false for _, _si in ipairs(_sh_hdr.sell_items or {}) do if type(_si.name)=='string' and _si.name:lower()==_nm_lo_hdr then _has=true; break end end if not _has then for _, _bi in ipairs(_sh_hdr.buy_items or {}) do if type(_bi.name)=='string' and _bi.name:lower()==_nm_lo_hdr then _has=true; break end end end if _has and _sh_hdr.dt then _hdr_date = 'скан лавки ' .. _sh_hdr.dt; break end end end if _hdr_date then imgui.SameLine(); imgui.TextDisabled(_cyr5f(' обновлено ' .. _hdr_date)) end imgui.Spacing() if not _G.dtl_tab then _G.dtl_tab = imgui.new.int(0) end _G.dtl_tab[0] = 1 imgui.Spacing() do local _has_any_stats = (cp_hist and #cp_hist > 0) or (fh_mkt_prices[item_name] and (fh_mkt_prices[item_name].s_avg or fh_mkt_prices[item_name].b_avg)) or (#(_G._dtl_shop_hist or {}) > 0) if _has_any_stats then local _sh_pre = _G._dtl_shop_hist or {} local _shop_anchor = nil if #_sh_pre > 0 then local _anch_vals = {} for _, _ae in ipairs(_sh_pre) do if _ae.s_avg and _ae.s_avg > 0 then table.insert(_anch_vals, _ae.s_avg) elseif _ae.b_avg and _ae.b_avg > 0 then table.insert(_anch_vals, _ae.b_avg) end end if #_anch_vals > 0 then table.sort(_anch_vals) _shop_anchor = _anch_vals[math.ceil(#_anch_vals/2)] end end if (not _shop_anchor or _shop_anchor <= 0) and lv_e then _shop_anchor = lv_e.s_avg or lv_e.b_avg end if (not _shop_anchor or _shop_anchor <= 0) and cp_e then _shop_anchor = cp_e.s_avg or cp_e.b_avg end local trend = cp_hist and #cp_hist>0 and _G._xvn2w(cp_hist) or nil local tc = trend and _G._pdf8k(trend) or imgui.ImVec4(0.6,0.6,0.6,1) if not _G._dtl_ready then imgui.Spacing() imgui.TextDisabled(_cyr5f(' Расчёт...')) return end local _dstats_dtl = _G._dtl_stats or {} local _s1_avg = _dstats_dtl.today local function _blend_avg(mkt, sh, qty) if not mkt then return sh end if not sh then return mkt end if (qty or 99) <= 2 then return math.floor((mkt + sh) / 2) end return mkt end local _s7_avg = _blend_avg(_dstats_dtl.mkt_7, _dstats_dtl.sh_s_7, _dstats_dtl.qty7) local _s30_avg = _blend_avg(_dstats_dtl.mkt_30, _dstats_dtl.sh_s_30, _dstats_dtl.qty30) do local _q1 = _dstats_dtl.qty1 local _q7 = _dstats_dtl.qty7 or 0 local _q30 = _dstats_dtl.qty30 or 0 local _fe2 = fh_mkt_prices[_build_nm] if _fe2 and (_fe2.s_totalC or 0) > 0 then if _q30 == 0 then _q30 = _fe2.s_totalC end _q7 = _q7 + math.floor(_fe2.s_totalC / 4) end _G._dtl_qty1 = _q1; _G._dtl_qty7 = _q7; _G._dtl_qty30 = _q30 end local _all_px_dtl = _dstats_dtl.all_px or {} local _px_by_date = _dstats_dtl.px_by_date or {} local _shop_anchor = _dstats_dtl.shop_anchor local function _dtl_mkt_avg(days_n) return days_n <= 7 and _s7_avg or _s30_avg end local _fe_c = fh_mkt_prices[_build_nm] or fh_mkt_auto[_build_nm] local _tc30 = _fe_c and (_fe_c.s_totalC or 0) or 0 local _q7c = (_G._dtl_qty7 or 0) + (_tc30 > 0 and math.floor(_tc30/4) or 0) local _q30c = (_G._dtl_qty30 or 0) > 0 and _G._dtl_qty30 or _tc30 local s7 = (_s7_avg or _q7c > 0) and {avg=_s7_avg, qty=_q7c > 0 and _q7c or nil} or nil local s30 = (_s30_avg or _q30c > 0) and {avg=_s30_avg, qty=_q30c > 0 and _q30c or nil} or nil imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), u8' Статистика рынка (история по дням)') imgui.Spacing() local _sh = _G._dtl_shop_hist or {} local function _sh_avg(days_n, field) local cutoff = os.date('%Y-%m-%d', os.time() - days_n*86400) local vals = {} for _, e in ipairs(_sh) do if e.date >= cutoff and e[field] and e[field] > 0 then table.insert(vals, e[field]) end end if #vals == 0 then return nil end if #vals == 1 then return vals[1] end table.sort(vals) local n = #vals local q1 = vals[math.max(1, math.floor(n*0.25 + 0.5))] local q3 = vals[math.min(n, math.floor(n*0.75 + 0.5))] local iqr = q3 - q1 local lo = q1 - iqr * 1.5 local hi = q3 + iqr * 1.5 local s, c = 0, 0 for _, v in ipairs(vals) do if iqr == 0 or (v >= lo and v <= hi) then s = s + v; c = c + 1 end end return c > 0 and math.floor(s / c) or math.floor(vals[math.ceil(n/2)]) end local sh_s_today = #_sh>0 and (_sh[1].s_min or _sh[1].s_avg) or nil local sh_s_today_avg = #_sh>0 and _sh[1].s_avg or nil local _sh_s_today_min = nil local sh_b_today = #_sh>0 and (_sh[1].b_max or _sh[1].b_avg) or nil local _dstats = _G._dtl_stats or {} local sh_s_7 = _dstats.sh_s_7 local sh_b_7 = _dstats.sh_b_7 local sh_s_30 = _dstats.sh_s_30 local sh_b_30 = _dstats.sh_b_30 imgui.Columns(5, '##dtl_stat2', false) local _cw6 = imgui.GetWindowContentRegionWidth() local _c0w = 58*d local _crest = _cw6 - _c0w imgui.SetColumnWidth(0, _c0w) imgui.SetColumnWidth(1, math.floor(_crest*0.28)) imgui.SetColumnWidth(2, math.floor(_crest*0.27)) imgui.SetColumnWidth(3, math.floor(_crest*0.27)) imgui.SetColumnWidth(4, math.floor(_crest*0.18)) imgui.TextColored(imgui.ImVec4(0.6,0.6,0.6,1), u8''); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.6,0.6,0.6,1), u8'Рынок $'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), u8'Продажа $'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(1,0.5,0.2,1), u8'Скупка $'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.6,0.6,0.6,1), u8'Продано'); imgui.NextColumn() local today_h = cp_hist and cp_hist[1] or nil local _mkt_today = nil local _mkt_today_est = false do local _today_pts = {} local _nm_lo_today = item_name:lower() local _dtl_cur_srv2 = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)() for _, _osh in pairs(fh_other_shops or {}) do if type(_osh) ~= 'table' then goto _mkt_today_sh_next end if _dtl_cur_srv2 ~= -1 and _osh.server_id and _osh.server_id ~= -1 and _osh.server_id ~= _dtl_cur_srv2 then goto _mkt_today_sh_next end for _, _si in ipairs(_osh.sell_items or {}) do if type(_si.name)=='string' and _si.name:lower()==_nm_lo_today and (_si.price or 0)>0 then table.insert(_today_pts, _si.price) end end ::_mkt_today_sh_next:: end if #_today_pts > 0 then table.sort(_today_pts) _sh_s_today_min = _today_pts[1] end local _today_live_ref = (_sh_s_today_min and _sh_s_today_min > 0) and _sh_s_today_min or (_dstats_dtl.shop_anchor and _dstats_dtl.shop_anchor > 0 and _dstats_dtl.shop_anchor) or nil local function _today_anom(v) if not v or v <= 0 or not _today_live_ref or _today_live_ref <= 0 then return false end return (v > _today_live_ref) and (v / _today_live_ref) >= 2.5 end if cp_hist and cp_hist[1] and (cp_hist[1].price or 0) > 0 then local _cp_v = cp_hist[1].price if _today_anom(_cp_v) then _mkt_today = _today_live_ref _mkt_today_est = true else _mkt_today = _cp_v _mkt_today_est = false end else local _cd_today = _G._mh_deals_cache and _G._mh_deals_cache[item_name:lower()] local _today_d = os.date('%Y-%m-%d') local _yest_d = os.date('%Y-%m-%d', os.time() - 86400) if _cd_today then for _, _cde in ipairs(_cd_today) do if (_cde.date == _today_d or _cde.date == _yest_d) and (_cde.s_avg or 0) > 0 and not _today_anom(_cde.s_avg) then _mkt_today = _cde.s_avg _mkt_today_est = false break end end end if not _mkt_today or _mkt_today <= 0 then if _today_live_ref and _today_live_ref > 0 then _mkt_today = _today_live_ref _mkt_today_est = true elseif _s7_avg and _s7_avg > 0 then _mkt_today = _s7_avg _mkt_today_est = true end end end end local _mkt_7 = _s7_avg local _mkt_30 = _s30_avg local _dstats_la = _G._dtl_stats or {} local _log_anchor = _dstats_la.log_anch_7 or _dstats_la.log_anch_30 local _prev_day_anchor = nil do local _sh2 = _G._dtl_shop_hist or {} local _today_d = os.date('%Y-%m-%d') for _, e in ipairs(_sh2) do if e.date and e.date < _today_d and (e.s_avg or 0) > 0 then _prev_day_anchor = e.s_avg break end end end local _ind_anchor = _log_anchor or _prev_day_anchor if not _log_anchor and _ind_anchor and _ind_anchor > 0 then local _ia_nm_lo = item_name:lower() local _ia_live = {} for _, _sh in pairs(fh_other_shops or {}) do if type(_sh) == 'table' then for _, _si in ipairs(_sh.sell_items or {}) do local _sn = _si.name or '' if (_sn == item_name or _sn:lower() == _ia_nm_lo) and (_si.price or 0) > 0 then table.insert(_ia_live, _si.price) end end end end if #_ia_live > 0 then table.sort(_ia_live) local _ia_sa = _ia_live[math.ceil(#_ia_live / 2)] if _ia_sa and _ia_sa > 0 then local _ia_r = _ind_anchor > _ia_sa and (_ind_anchor / _ia_sa) or (_ia_sa / _ind_anchor) if _ia_r >= 2.5 then _ind_anchor = nil end end end end local _sh2_hist = _G._dtl_shop_hist or {} local _all_dates = {} local _date_seen = {} local _total_hist_rows = (cp_hist and #cp_hist or 0) local _tmp_dt_seen = {} if cp_hist then for _,_hh in ipairs(cp_hist) do _tmp_dt_seen[_hh.dt or '']=true end end for _, _ese in ipairs(_sh2_hist) do if _ese.date and not _tmp_dt_seen[_ese.date] then _total_hist_rows=_total_hist_rows+1; _tmp_dt_seen[_ese.date]=true end end for _, h in ipairs(cp_hist or {}) do local dt = h.dt or '' if dt ~= '' and not _date_seen[dt] then _date_seen[dt] = #_all_dates + 1 table.insert(_all_dates, {dt=dt, mkt=h, sh=nil}) end end for _, e in ipairs(_sh2_hist) do local dt = e.date or '' if dt == '' then goto _ad_sh_next end local ei = _date_seen[dt] if ei then _all_dates[ei].sh = e else _date_seen[dt] = #_all_dates + 1 table.insert(_all_dates, {dt=dt, mkt=nil, sh=e}) end ::_ad_sh_next:: end table.sort(_all_dates, function(a,b) return a.dt > b.dt end) local _grd_sa = nil do local _gmp = _mh_get_mkt_price and _mh_get_mkt_price(item_name) if _gmp then local _nm_lo_grd = item_name:lower() local _grd_live = {} local _dtl_cur_srv3 = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)() for _, _sh in pairs(fh_other_shops or {}) do if type(_sh) == 'table' then if _dtl_cur_srv3 ~= -1 and _sh.server_id and _sh.server_id ~= -1 and _sh.server_id ~= _dtl_cur_srv3 then else for _, _si in ipairs(_sh.sell_items or {}) do local _sn = _si.name or '' if (_sn == item_name or _sn:lower() == _nm_lo_grd) and (_si.price or 0) > 0 then table.insert(_grd_live, _si.price) end end end end end if #_grd_live > 0 then table.sort(_grd_live) _grd_sa = _grd_live[math.ceil(#_grd_live / 2)] end local function _grd_ok(v) if not v or v <= 0 then return false end if not _grd_sa or _grd_sa <= 0 then return false end local _r = v > _grd_sa and (v / _grd_sa) or (_grd_sa / v) return _r < 2.5 end if _grd_ok(_gmp.avg7) then _mkt_7 = _gmp.avg7; s7 = {avg=_gmp.avg7, qty=(s7 and s7.qty or _qty7 or 0)} end if _grd_ok(_gmp.avg30) then _mkt_30 = _gmp.avg30; s30 = {avg=_gmp.avg30, qty=(s30 and s30.qty or _qty30 or 0)} end end end local _live_shop_ref = (_grd_sa and _grd_sa > 0) and _grd_sa or nil local _buy_anchor = (sh_b_7 and sh_b_7 > 0) and sh_b_7 or ((_G._dtl_stats or {}).sh_b_7 and (_G._dtl_stats or {}).sh_b_7 > 0 and (_G._dtl_stats or {}).sh_b_7) or nil local function _mkt_sane(mkt_val, shop_ref, ind_ref) local _eff_anchor = _live_shop_ref or (ind_ref and ind_ref > 0 and ind_ref) or (_buy_anchor and _buy_anchor > 0 and _buy_anchor) or nil if not mkt_val or mkt_val <= 0 then return _eff_anchor or shop_ref, true end if _live_shop_ref and _live_shop_ref > 0 then local ratio0 = mkt_val > _live_shop_ref and (mkt_val/_live_shop_ref) or (_live_shop_ref/mkt_val) if ratio0 > 2.5 then return _live_shop_ref, true end end if ind_ref and ind_ref > 0 then local ratio = mkt_val > ind_ref and (mkt_val/ind_ref) or (ind_ref/mkt_val) if ratio > 3.0 then return _live_shop_ref or ind_ref, true end end if not _live_shop_ref and not (ind_ref and ind_ref > 0) then if _buy_anchor and _buy_anchor > 0 then local _ba_r = mkt_val > _buy_anchor and (mkt_val/_buy_anchor) or (_buy_anchor/mkt_val) if _ba_r > 4.0 then return _buy_anchor, true end end end if shop_ref and shop_ref > 0 then local ratio2 = mkt_val > shop_ref and (mkt_val/shop_ref) or (shop_ref/mkt_val) if ratio2 > 2.5 then return _live_shop_ref or _buy_anchor or shop_ref, true end end return mkt_val, false end local _anom_t, _anom_7, _anom_30 _mkt_today, _anom_t = _mkt_sane(_mkt_today, sh_s_7, _ind_anchor) _mkt_7, _anom_7 = _mkt_sane(_mkt_7, sh_s_7, _ind_anchor) _mkt_30, _anom_30 = _mkt_sane(_mkt_30, sh_s_30, _ind_anchor) if _anom_t then _mkt_today_est = true end imgui.TextColored(imgui.ImVec4(0.4,0.95,1,1), u8' День'); imgui.NextColumn() if _mkt_today and _mkt_today > 0 then local _mkt_pfx = '$' local _mkt_col = _mkt_today_est and imgui.ImVec4(0.65,0.85,0.65,1) or imgui.ImVec4(0.4,0.95,1,1) imgui.TextColored(_mkt_col, _cyr5f(' '.._mkt_pfx.._kcr3y(_mkt_today))); imgui.NextColumn() else imgui.TextDisabled(u8' —'); imgui.NextColumn() end do local _dmin_today = #_sh>0 and (_sh[1].s_min or 0)>0 and _sh[1].s_min or nil local _s_disp if _sh_s_today_min and _sh_s_today_min>0 and _dmin_today then _s_disp = math.min(_sh_s_today_min, _dmin_today) elseif _sh_s_today_min and _sh_s_today_min>0 then _s_disp = _sh_s_today_min elseif _dmin_today then _s_disp = _dmin_today else _s_disp = sh_s_today end if _s_disp then imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), _cyr5f(' $'.._kcr3y(_s_disp))) else imgui.TextDisabled(u8' —') end end; imgui.NextColumn() if sh_b_today then imgui.TextColored(imgui.ImVec4(0.4,0.7,1,1), _cyr5f(' $'.._kcr3y(sh_b_today))) else imgui.TextDisabled(u8' —') end; imgui.NextColumn() do local _cd_qty_now = nil local _cd_q_arr = _G._mh_deals_cache and _G._mh_deals_cache[item_name:lower()] if _cd_q_arr then local _td_q = os.date('%Y-%m-%d') for _, _cde in ipairs(_cd_q_arr) do if _cde.date == _td_q and (_cde.s_qty or _cde.total_qty or 0) > 0 then _cd_qty_now = (_cde.s_qty or _cde.total_qty); break end end end local _dq = (_G._dtl_qty1 and _G._dtl_qty1 > 0) and _G._dtl_qty1 or _cd_qty_now or (today_h and (today_h.qty or 0) > 0 and today_h.qty) or (_sh[1] and (_sh[1].s_qty or _sh[1].s_cnt or 0) > 0 and (_sh[1].s_qty or _sh[1].s_cnt)) if _dq then imgui.TextColored(imgui.ImVec4(1,1,1,0.8), _cyr5f(' '..tostring(_dq))); imgui.NextColumn() else imgui.TextDisabled(u8' '); imgui.NextColumn() end end imgui.TextColored(imgui.ImVec4(lp_r,lp_g,lp_b,1), u8' 7дн.'); imgui.NextColumn() if _mkt_7 and _mkt_7>0 then local _m7pfx = '$' local _mkt_w2 = (_G._dtl_stats or {}).mkt_w2 local _m7col = (_mkt_7_est or _mkt_w2) and imgui.ImVec4(lp_r*0.75,lp_g*0.75,lp_b*0.5,1) or imgui.ImVec4(lp_r,lp_g,lp_b,1) imgui.TextColored(_m7col, _cyr5f(' '.._m7pfx.._kcr3y(_mkt_7))); imgui.NextColumn() else imgui.TextDisabled(u8' —'); imgui.NextColumn() end if sh_s_7 then imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), _cyr5f(' $'.._kcr3y(sh_s_7))) else imgui.TextDisabled(u8' —') end; imgui.NextColumn() if sh_b_7 then imgui.TextColored(imgui.ImVec4(0.4,0.7,1,1), _cyr5f(' $'.._kcr3y(sh_b_7))) else imgui.TextDisabled(u8' —') end; imgui.NextColumn() if s7 and (s7.qty or 0) > 0 then imgui.TextColored(imgui.ImVec4(1,1,1,0.8), _cyr5f(' '.._kcr3y(s7.qty))); imgui.NextColumn() else imgui.TextDisabled(u8' —'); imgui.NextColumn() end imgui.TextColored(imgui.ImVec4(0.4,0.95,0.4,1), u8' 30дн.'); imgui.NextColumn() if _mkt_30 and _mkt_30>0 then local _m30pfx = '$' local _m30col = _mkt_30_est and imgui.ImVec4(0.35,0.75,0.35,1) or imgui.ImVec4(0.4,0.95,0.4,1) imgui.TextColored(_m30col, _cyr5f(' '.._m30pfx.._kcr3y(_mkt_30))); imgui.NextColumn() else imgui.TextDisabled(u8' —'); imgui.NextColumn() end if sh_s_30 then imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), _cyr5f(' $'.._kcr3y(sh_s_30))) else imgui.TextDisabled(u8' —') end; imgui.NextColumn() if sh_b_30 then imgui.TextColored(imgui.ImVec4(0.4,0.7,1,1), _cyr5f(' $'.._kcr3y(sh_b_30))) else imgui.TextDisabled(u8' —') end; imgui.NextColumn() if s30 and (s30.qty or 0) > 0 then imgui.TextColored(imgui.ImVec4(1,1,1,0.8), _cyr5f(' '.._kcr3y(s30.qty))); imgui.NextColumn() else imgui.TextDisabled(u8' —'); imgui.NextColumn() end imgui.Columns(1) imgui.Spacing() imgui.Spacing() local _sh2 = _G._dtl_shop_hist or {} local _sh_date_idx = {} for _, e in ipairs(_sh2) do if e.date and e.date ~= '' then _sh_date_idx[e.date] = e end end do local _td = os.date('%Y-%m-%d') local _s_min_live = sell_rows and #sell_rows > 0 and sell_rows[1].price or nil local _s_avg_live = nil if sell_rows and #sell_rows > 0 then local _ss, _sc = 0, 0 for _, _sr2 in ipairs(sell_rows) do if (_sr2.price or 0) > 0 then _ss = _ss + _sr2.price; _sc = _sc + 1 end end if _sc > 0 then _s_avg_live = math.floor(_ss / _sc) end end local _b_live = buy_rows and #buy_rows > 0 and buy_rows[1].price or nil if _s_min_live or _b_live then if not _sh_date_idx[_td] then local _new = {date=_td, s_avg=nil, s_min=nil, b_avg=nil, b_max=nil} _sh_date_idx[_td] = _new table.insert(_sh2, 1, _new) end local _e = _sh_date_idx[_td] if _s_min_live then _e.s_min = _s_min_live end if _s_avg_live then _e.s_avg = _s_avg_live end if _b_live then _e.b_max = _b_live; if not _e.b_avg then _e.b_avg = _b_live end end local _dsh = _G._dtl_shop_hist if _dsh then local _found_today = false for _, _de in ipairs(_dsh) do if _de.date == _td then if _s_min_live then _de.s_min = _s_min_live end if _s_avg_live then _de.s_avg = _s_avg_live end if _b_live then _de.b_max = _b_live; if not _de.b_avg then _de.b_avg = _b_live end end _found_today = true; break end end if not _found_today then local _ne = {date=_td, s_avg=_s_avg_live, s_min=_s_min_live, b_avg=_b_live, b_max=_b_live} table.insert(_dsh, 1, _ne) end end end end local _all_chart_dates = {} local _acd_set = {} local _chart_cutoff = os.date('%Y-%m-%d', os.time() - 30*86400) for _, h in ipairs(cp_hist or {}) do local dt = h.dt or '' if dt ~= '' and dt >= _chart_cutoff and not _acd_set[dt] then table.insert(_all_chart_dates, dt); _acd_set[dt] = true end end for _, e in ipairs(_sh2) do local dt = e.date or '' if dt ~= '' and dt >= _chart_cutoff and not _acd_set[dt] then table.insert(_all_chart_dates, dt); _acd_set[dt] = true end end local _cd_chart = _G._mh_deals_cache and _G._mh_deals_cache[(_detail_item or ''):lower()] if _cd_chart then for _, e in ipairs(_cd_chart) do local dt = e.date or '' if dt ~= '' and dt >= _chart_cutoff and not _acd_set[dt] then table.insert(_all_chart_dates, dt); _acd_set[dt] = true end end end table.sort(_all_chart_dates, function(a,b) return a > b end) if #_all_chart_dates > 30 then local _tmp = {} for i=1,30 do _tmp[i]=_all_chart_dates[i] end _all_chart_dates = _tmp end local _chart_dates = {} for i = #_all_chart_dates, 1, -1 do table.insert(_chart_dates, _all_chart_dates[i]) end local plot_n = math.max(#_chart_dates, 1) local _cp_by_date = {} do for _, h in ipairs(cp_hist or {}) do if h.dt and h.dt ~= '' and (h.price or 0) > 0 then _cp_by_date[h.dt] = h end end local _all_px_src = (_dstats_dtl or {}).all_px or {} local _px_hi_weight = {} for _, _ap in ipairs(_all_px_src) do if (_ap.weight or 0) >= 3 and _ap.date and _ap.date ~= '' and (_ap.price or 0) > 0 then if not _px_hi_weight[_ap.date] then _px_hi_weight[_ap.date] = {} end table.insert(_px_hi_weight[_ap.date], _ap.price) end end for _cd, _prices in pairs(_px_hi_weight) do if not _cp_by_date[_cd] and #_prices > 0 then table.sort(_prices) _cp_by_date[_cd] = {dt=_cd, price=_prices[math.ceil(#_prices/2)], qty=1} end end for _sd, _se in pairs(_sh_date_idx) do if not _cp_by_date[_sd] then local _sa = (_se.s_min and _se.s_min > 0 and _se.s_min) or (_se.s_avg and _se.s_avg > 0 and _se.s_avg) or 0 if _sa > 0 then _cp_by_date[_sd] = {dt=_sd, price=_sa, qty=1, _src='shop_hist'} end end end end local _chart_lo, _chart_hi = 0, math.huge do local _apc = {} for _, dt in ipairs(_chart_dates) do local mh = _cp_by_date[dt] if mh and mh.price and mh.price > 0 then table.insert(_apc, mh.price) end end if #_apc == 0 then for _, dt in ipairs(_chart_dates) do local se = _sh_date_idx[dt] if se and se.s_avg and se.s_avg > 0 then table.insert(_apc, se.s_avg) end if se and se.b_avg and se.b_avg > 0 then table.insert(_apc, se.b_avg) end end end if #_apc > 0 then table.sort(_apc) local _med = _apc[math.ceil(#_apc/2)] local _q1 = _apc[math.max(1, math.ceil(#_apc*0.25))] local _q3 = _apc[math.min(#_apc, math.ceil(#_apc*0.75))] local _fenc = math.max((_q3-_q1)*6, _med*1.5) _chart_lo = math.max(0, _med - _fenc) _chart_hi = _med + _fenc end end local p_min, p_max = math.huge, -math.huge local _ptk = _dtl_cache_key .. '|' .. tostring(#_chart_dates) if not _G._cached_pt_key or _G._cached_pt_key ~= _ptk then _G._cached_pt_key = _ptk local cpt, cps, cpb, chsd = {}, {}, {}, false for _, dt in ipairs(_chart_dates) do local mh = _cp_by_date[dt] local v = (mh and mh.price) or 0 if v > 0 and (v < _chart_lo or v > _chart_hi) then v = 0 end local se = _sh_date_idx[dt] local _sv_raw = (se and (se.s_min and se.s_min>0 and se.s_min or se.s_avg)) or 0 local _bv_raw = (se and (se.b_max or se.b_avg)) or 0 local _hi_guard = _chart_hi * 3 local sv = (_sv_raw > 0 and _sv_raw <= _hi_guard) and _sv_raw or 0 local bv = (_bv_raw > 0 and _bv_raw <= _hi_guard) and _bv_raw or 0 table.insert(cpt, v) table.insert(cps, sv) table.insert(cpb, bv) if sv > 0 or bv > 0 then chsd = true end end _G._cached_pt = cpt; _G._cached_ps = cps; _G._cached_pb = cpb; _G._cached_hsd = chsd end local plot_tbl = _G._cached_pt or {} local _plot_s = _G._cached_ps or {} local _plot_b = _G._cached_pb or {} local _has_shop_data = _G._cached_hsd or false for _, v_plot in ipairs(plot_tbl) do if v_plot > 0 then if v_plotp_max then p_max=v_plot end end end for _, sv in ipairs(_plot_s) do if sv > 0 then if svp_max then p_max=sv end end end for _, bv in ipairs(_plot_b) do if bv > 0 then if bvp_max then p_max=bv end end end local plot_scale, overlay_s local p_real_max = (p_max ~= -math.huge) and p_max or 0 if p_real_max >= 1000000000 then plot_scale = 1000000; overlay_s = _cyr5f('млн $') elseif p_real_max >= 1000000 then plot_scale = 1000; overlay_s = _cyr5f('тыс $') else plot_scale = 1; overlay_s = _cyr5f('$') end local p_min_sc2 = (p_min ~= math.huge) and (p_min / plot_scale * 0.92) or 0 local p_max_sc2 = (p_max ~= -math.huge) and (p_max / plot_scale * 1.08) or 1 if p_max_sc2 <= p_min_sc2 then p_max_sc2 = p_min_sc2 + 1 end local _plot_filled = {} for i = 1, plot_n do _plot_filled[i] = plot_tbl[i] end local _last_v = nil for i = 1, plot_n do if _plot_filled[i] and _plot_filled[i] > 0 then _last_v = _plot_filled[i] elseif _last_v then _plot_filled[i] = _last_v end end local _first_v = nil for i = 1, plot_n do if _plot_filled[i] and _plot_filled[i] > 0 then _first_v = _plot_filled[i]; break end end if _first_v then for i = 1, plot_n do if not _plot_filled[i] or _plot_filled[i] == 0 then _plot_filled[i] = _first_v else break end end end local function _fill_arr(arr) local _fl = nil for i = 1, plot_n do if arr[i] and arr[i] > 0 then _fl = arr[i] elseif _fl then arr[i] = _fl end end local _fr = nil for i = 1, plot_n do if arr[i] and arr[i] > 0 then _fr = arr[i]; break end end if _fr then for i = 1, plot_n do if not arr[i] or arr[i] == 0 then arr[i] = _fr else break end end end end _fill_arr(_plot_s) _fill_arr(_plot_b) local plot_vals = ffi.new('float[?]', plot_n) for i = 0, plot_n - 1 do plot_vals[i] = (_plot_filled[i + 1] or 0) / plot_scale end imgui.TextColored(imgui.ImVec4(0.5, 0.8, 1, 1), _cyr5f(' График цен (' .. plot_n .. ' дн):')) local _has_mkt_data = false for _, _ptv in ipairs(plot_tbl) do if _ptv > 0 then _has_mkt_data = true; break end end if _has_mkt_data or _has_shop_data then imgui.SameLine(0,8*d) if _has_mkt_data then imgui.TextColored(imgui.ImVec4(0.3,0.8,1,1), u8'— Рынок') imgui.SameLine(0,8*d) end if _has_shop_data then imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), u8'— Продажа') imgui.SameLine(0,8*d) imgui.TextColored(imgui.ImVec4(1,0.5,0.2,1), u8'— Скупка') end end imgui.PushStyleColor(imgui.Col.PlotLines, imgui.ImVec4(0.3, 0.8, 1, 1)) imgui.PushStyleColor(imgui.Col.PlotLinesHovered, imgui.ImVec4(lp_r,lp_g,lp_b,1)) imgui.PushStyleColor(imgui.Col.FrameBg, imgui.ImVec4(0.06, 0.06, 0.10, _G._mh_wa or 1)) local _chart_w = imgui.GetWindowContentRegionWidth() - 16*d local _chart_h = 80*d local _cp_chart = imgui.GetCursorScreenPos() imgui.PlotLines('##fhspark', plot_vals, plot_n, 0, overlay_s, p_min_sc2, p_max_sc2, imgui.ImVec2(_chart_w, _chart_h)) imgui.PopStyleColor(3) if (_has_mkt_data or _has_shop_data) and plot_n >= 1 then local _dl = imgui.GetWindowDrawList() local _rng = (p_max_sc2 - p_min_sc2) if _rng <= 0 then _rng = 1 end local function _chart_pt(xi, val_raw) local v = (val_raw / plot_scale) local nx = (plot_n > 1) and ((xi - 1) / (plot_n - 1)) or 0.5 local ny = 1 - (v - p_min_sc2) / _rng ny = math.max(0, math.min(1, ny)) return _cp_chart.x + nx * _chart_w, _cp_chart.y + ny * _chart_h end local function _draw_line_or_dot(pts_tbl, col, thick) if plot_n == 1 then local v = pts_tbl[1] if v and v > 0 then local x, y = _chart_pt(1, v) _dl:AddCircleFilled(imgui.ImVec2(x, y), thick*2, col) end else for xi = 1, plot_n - 1 do local v1, v2 = pts_tbl[xi], pts_tbl[xi+1] if v1 and v1>0 and v2 and v2>0 then local x1,y1 = _chart_pt(xi,v1); local x2,y2 = _chart_pt(xi+1,v2) _dl:AddLine(imgui.ImVec2(x1,y1), imgui.ImVec2(x2,y2), col, thick) end end end end if _has_shop_data then _draw_line_or_dot(_plot_s, 0xFF44EE66, 1.5) end if _has_shop_data then _draw_line_or_dot(_plot_b, 0xFF3380FF, 2.0) end end imgui.Spacing() imgui.Separator() imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f(' История по дням (' .. _total_hist_rows .. '):')) local _today_str = os.date('%Y-%m-%d') local hist_h = math.min(#_all_dates * 18*d + 30*d, 155*d) if imgui.BeginChild('##dtl_hist_days', imgui.ImVec2(-1, hist_h), true) then _dpn1w() imgui.Columns(4, '##dtl_hd', false) local _cwhd = imgui.GetWindowContentRegionWidth() local _dt_col = 120*d local _rest3 = (_cwhd - _dt_col) / 3 imgui.SetColumnWidth(0, _dt_col) imgui.SetColumnWidth(1, math.floor(_rest3)) imgui.SetColumnWidth(2, math.floor(_rest3)) imgui.SetColumnWidth(3, math.floor(_rest3)) imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1), u8' Дата'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.8,0.8,0.5,1), _ic_store..' '..u8'Рынок $'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), _ic_tag..' '..u8'Продажа'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(1,0.5,0.2,1), _ic_cart..' '..u8'Скупка'); imgui.NextColumn() imgui.Separator() local function _prices_med3(prices) if not prices or #prices == 0 then return nil end local _s = {}; for _, v in ipairs(prices) do table.insert(_s, v) end table.sort(_s) local _cnt = math.min(3, #_s) local _sum = 0 for i = 1, _cnt do _sum = _sum + _s[i] end return math.floor(_sum / _cnt) end local _osh_now_prices = {} for _, _osp in pairs(fh_other_shops or {}) do if type(_osp) == 'table' then for _, _osi in ipairs(_osp.sell_items or {}) do if type(_osi.name)=='string' and _osi.name==item_name and (_osi.price or 0) > 0 then local _seen = false for _, _ep in ipairs(_osh_now_prices) do if _ep==_osi.price then _seen=true; break end end if not _seen then table.insert(_osh_now_prices, _osi.price) end end end end end local _osh_now_med = _prices_med3(_osh_now_prices) for ri, row in ipairs(_all_dates) do local is_today = (row.dt == _today_str) local is_fresh = (ri == 1) local dt_col = is_today and imgui.ImVec4(0.4,0.95,1,1) or (is_fresh and imgui.ImVec4(lp_r,lp_g,lp_b,1) or imgui.ImVec4(0.65,0.65,0.65,1)) local dt_lbl = row.dt imgui.TextColored(dt_col, _cyr5f(' '..dt_lbl)); imgui.NextColumn() local _mkt_price_anomaly = false if row.mkt and row.mkt.price and row.mkt.price > 0 and cp_hist and #cp_hist >= 3 then local _all_p = {} for _, _hh in ipairs(cp_hist) do if _hh.price and _hh.price > 0 then table.insert(_all_p, _hh.price) end end table.sort(_all_p) local _take60 = math.max(1, math.ceil(#_all_p * 0.6)) local _s60, _c60 = 0, 0 for _i = 1, _take60 do _s60 = _s60 + _all_p[_i]; _c60 = _c60 + 1 end local _med2 = _c60 > 0 and math.floor(_s60 / _c60) or _all_p[math.ceil(#_all_p/2)] if _med2 > 0 then local _ratio = row.mkt.price / _med2 if _ratio > 2.0 or _ratio < 0.35 then local _prev = _all_dates[ri - 1] local _next = _all_dates[ri + 1] local _p = row.mkt.price local _same_prev = _prev and _prev.mkt and _prev.mkt.price == _p local _same_next = _next and _next.mkt and _next.mkt.price == _p if not _same_prev and not _same_next then _mkt_price_anomaly = true end end end end do local _row_anchor = (_sh_s_today_min and _sh_s_today_min>0 and _sh_s_today_min) or (_mkt_7 and _mkt_7>0 and _mkt_7) or (_mkt_30 and _mkt_30>0 and _mkt_30) or (row.sh and (row.sh.s_avg or 0)>0 and row.sh.s_avg) or nil local function _is_anom(v) if not v or v <= 0 then return false end if not _row_anchor or _row_anchor <= 0 then return false end local r = v > _row_anchor and (v/_row_anchor) or (_row_anchor/v) return r > 2.5 end local _rp=nil; local _rp_dim=false local _rp_src='' if is_today and _mkt_today and _mkt_today > 0 then _rp = _mkt_today _rp_dim = _mkt_today_est _rp_src = 'day_stat' else local _candidates = { {v=row.mkt and not _mkt_price_anomaly and (row.mkt.price or 0)>0 and row.mkt.price, dim=false, src='scan'}, {v=row.sh and row.sh.deep_price, dim=false, src='deep'}, {v=row.sh and row.sh.deal_s, dim=false, src='log'}, {v=row.sh and (row.sh.s_prices and _prices_med3(row.sh.s_prices)), dim=true, src='s_prices'}, {v=row.sh and row.sh.s_avg, dim=true, src='s_avg'}, } for _, _cand in ipairs(_candidates) do local _cv = type(_cand.v)=='number' and _cand.v or 0 if _cv > 0 and not _is_anom(_cv) then _rp = _cv; _rp_dim = _cand.dim; _rp_src = _cand.src break end end if not _rp and _row_anchor and _row_anchor > 0 then _rp = _row_anchor; _rp_dim = true; _rp_src = 'anchor' end end if _rp then local rc = _rp_dim and imgui.ImVec4(0.55,0.75,0.55,1) or (is_fresh and imgui.ImVec4(lp_r,lp_g,lp_b,1) or imgui.ImVec4(0.85,0.85,0.85,1)) imgui.TextColored(rc, _cyr5f(' $'.._kcr3y(_rp))) if imgui.IsItemHovered() then if _rp_src=='scan' then imgui.SetTooltip(_cyr5f('Цена центрального рынка (скан)')) elseif _rp_src=='log' then imgui.SetTooltip(_cyr5f('Средняя цена реальных сделок (лог игроков)')) elseif _rp_src=='deep' then imgui.SetTooltip(_cyr5f('Данные углублённого скана другого игрока')) elseif _rp_src=='s_prices' then imgui.SetTooltip(_cyr5f('~Мед. 3 деш. цен лавок (цР недоступен)')) elseif _rp_src=='s_avg' then imgui.SetTooltip(_cyr5f('~Ср. цена лавок (цР недоступен)')) elseif _rp_src=='anchor' then imgui.SetTooltip(_cyr5f('~Оценка по истории (все цены аномальны)')) end end else imgui.TextDisabled(u8' —') end end; imgui.NextColumn() do local _sp_best, _is_best, _sp_tip if is_today then local _live = (_sh_s_today_min and _sh_s_today_min>0) and _sh_s_today_min or nil local _dmin = row.sh and (row.sh.s_min or 0)>0 and row.sh.s_min or nil if _live and _dmin then _sp_best = math.min(_live, _dmin); _is_best=true _sp_tip = _cyr5f('Ср. цена продажи (лавки + история)') elseif _live then _sp_best = _live; _is_best=true _sp_tip = _cyr5f('Ср. цена продажи в лавках') elseif _dmin then _sp_best = _dmin; _is_best=true _sp_tip = _cyr5f('Ср. цена продажи (история)') elseif _osh_now_med and _osh_now_med>0 then _sp_best = _osh_now_med; _is_best=false _sp_tip = _cyr5f('Ср. цена продажи') end else _sp_best = row.sh and (row.sh.s_min or row.sh.s_avg) _is_best = row.sh and (row.sh.s_min or nil) ~= nil _sp_tip = _is_best and _cyr5f('Ср. цена продажи в лавках') or _cyr5f('Ср. цена продажи в лавках') end if _sp_best then local lc=is_today and imgui.ImVec4(0.5,1,0.5,1) or imgui.ImVec4(0.4,0.9,0.4,1) imgui.TextColored(lc, _cyr5f(' $'.._kcr3y(_sp_best))) if imgui.IsItemHovered() and _sp_tip then imgui.SetTooltip(_sp_tip) end else imgui.TextDisabled(u8' —') end end; imgui.NextColumn() do local _bmax = row.sh and row.sh.b_max local _bavg = row.sh and row.sh.b_avg local _bp = (_bmax and _bmax > 0) and _bmax or _bavg local _is_max = _bmax and _bmax > 0 if _bp then local bc=is_today and imgui.ImVec4(0.5,0.8,1,1) or imgui.ImVec4(0.4,0.7,1,1) imgui.TextColored(bc, _cyr5f(' $'.._kcr3y(_bp))) if imgui.IsItemHovered() then if _is_max then imgui.SetTooltip(_cyr5f('Лучшая цена скупки в лавках за день')) else imgui.SetTooltip(_cyr5f('Ср. цена скупки в лавках')) end end else imgui.TextDisabled(u8' —') end end; imgui.NextColumn() end imgui.Columns(1); imgui.EndChild() end elseif cp_e then imgui.TextColored(imgui.ImVec4(1,0.75,0,1), u8' Только поверхностный скан (нет истории по дням)') imgui.TextDisabled(u8' Запустите Углублённый скан для получения истории') imgui.Spacing() if cp_e.s_avg then imgui.Text(_cyr5f(' Ср. цена (shallow): $' .. _kcr3y(cp_e.s_avg))) if cp_e.s_min then imgui.Text(_cyr5f(' Мин: $'.._kcr3y(cp_e.s_min)..' Макс: $'.._kcr3y(cp_e.s_max))) end end imgui.Separator() else imgui.TextDisabled(u8' Нет данных с чекпоинта.') imgui.Separator() end if #sell_rows > 0 or #buy_rows > 0 then imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar,ag,ab,1), u8' Цены в лавках игроков:') imgui.Spacing() _G._goto_owner_lavka = function(owner_name) local _found_lv = nil local _own_lo = (owner_name or ''):lower() for _, _lv in ipairs(mh_arz_data or {}) do if type(_lv)=='table' and _lv.username and _lv.username:lower()==_own_lo then _found_lv = _lv; break end end if not _found_lv then for _, _sh in pairs(fh_other_shops or {}) do if type(_sh)=='table' and _sh.owner and _sh.owner:lower()==_own_lo then _found_lv = {username=_sh.owner, LavkaUid=_sh.shop_num or 0, serverId=_sh.server_id or -1, items_sell={}, items_buy={}, price_sell={}, price_buy={}, count_sell={}, count_buy={}, _mh_cloud=true} if mh_arz_items_db then local _fi = 910000 for _,si in ipairs(_sh.sell_items or {}) do _fi=_fi+1; mh_arz_items_db[_fi]=si.name or '?' table.insert(_found_lv.items_sell,_fi) table.insert(_found_lv.price_sell,si.price or 0) table.insert(_found_lv.count_sell,si.qty or 1) end for _,bi in ipairs(_sh.buy_items or {}) do _fi=_fi+1; mh_arz_items_db[_fi]=bi.name or '?' table.insert(_found_lv.items_buy,_fi) table.insert(_found_lv.price_buy,bi.price or 0) table.insert(_found_lv.count_buy,bi.qty or 1) end end break end end end if _found_lv then _G.mh_tab=2; _G.arz_detail=_found_lv; _G.arz_detail_tab=0 _G.arz_detail_back=true; _G.arz_back_item=item_name; _G.arz_back_src=src _G.arz_page=1; _G.arz_cache_key=nil; _G.mkt_detail_open=false else _G.mh_tab=2; _G.arz_detail=nil; _G.arz_page=1; _G.arz_cache_key=nil local _ok_e,_cp_e = pcall(function() return require('encoding').CP1251:encode(require('encoding').UTF8:decode(item_name)) end) if _G.arz_srch then ffi.copy(_G.arz_srch,(_ok_e and _cp_e) and _cp_e or item_name) end _G.arz_srch_s=item_name:lower(); _G.mkt_detail_open=false end end _G._goto_arz = function() _G.mh_tab = 2 local _ok_e,_cp_e = pcall(function() return require('encoding').CP1251:encode(require('encoding').UTF8:decode(item_name)) end) if _G.arz_srch then ffi.copy(_G.arz_srch,(_ok_e and _cp_e) and _cp_e or item_name) end _G.arz_srch_s = item_name:lower() _G.arz_detail=nil; _G.arz_page=1; _G.arz_cache_key=nil; _G.mkt_detail_open=false end end if _G.dtl_tab[0] == 1 then local col_w2 = (imgui.GetWindowContentRegionWidth() - 8*d) / 2 local rows_h = math.min(math.max(#sell_rows,#buy_rows),3)*16*d + 42*d + 20 if imgui.BeginChild('##lv_sell', imgui.ImVec2(col_w2, rows_h), true) then _dpn1w() imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), _ic_tag..' '..u8'Продаёт') imgui.Separator() if #sell_rows == 0 then imgui.TextDisabled(u8' Нет данных') else for ri, r in ipairs(sell_rows) do imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), _cyr5f('$'.._kcr3y(r.price))) imgui.SameLine(0,6*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0,0,0,0)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.1,sb_g*1.1,sb_b*1.1,0.4)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.65,0.65,0.65,1)) local _is_online_sr = (r.src == '[API]') local _dot_col_sr = _is_online_sr and imgui.ImVec4(0.2,0.9,0.2,1) or imgui.ImVec4(0.9,0.2,0.2,1) imgui.TextColored(_dot_col_sr, _ic_circ) imgui.SameLine(0,3*d) local _row_lbl = _cyr5f(r.owner..(r.qty and ' x'..r.qty or '')) if imgui.Button(_row_lbl..'##sr'..ri, imgui.ImVec2(0,0)) then if _G._goto_owner_lavka then _G._goto_owner_lavka(r.owner) end end imgui.PopStyleColor(3) end end imgui.EndChild() end imgui.SameLine(0, 8*d) if imgui.BeginChild('##lv_buy', imgui.ImVec2(col_w2, rows_h), true) then _dpn1w() imgui.TextColored(imgui.ImVec4(0.4,0.7,1,1), _ic_store..' '..u8'Скупает') imgui.Separator() if #buy_rows == 0 then imgui.TextDisabled(u8' Нет данных') else for ri, r in ipairs(buy_rows) do imgui.TextColored(imgui.ImVec4(0.4,0.7,1,1), _cyr5f('$'.._kcr3y(r.price))) imgui.SameLine(0,6*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0,0,0,0)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.1,0.2,0.5,0.4)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.65,0.65,0.65,1)) local _is_online_br = (r.src == '[API]') local _dot_col_br = _is_online_br and imgui.ImVec4(0.2,0.9,0.2,1) or imgui.ImVec4(0.9,0.2,0.2,1) imgui.TextColored(_dot_col_br, _ic_circ) imgui.SameLine(0,3*d) local _row_lbl2 = _cyr5f(r.owner..(r.qty and ' x'..r.qty or '')) if imgui.Button(_row_lbl2..'##br'..ri, imgui.ImVec2(0,0)) then if _G._goto_owner_lavka then _G._goto_owner_lavka(r.owner) end end imgui.PopStyleColor(3) end end imgui.EndChild() end imgui.Spacing() end end imgui.Spacing() local _mh_ml_key = item_name .. '|' .. tostring(#fh_mkt_log) if _G._mh_ml_cache_key ~= _mh_ml_key then _G._mh_ml_cache_key = _mh_ml_key local _cache = {} local nm_low = item_name:lower() for i = #fh_mkt_log, 1, -1 do local le = fh_mkt_log[i] if le and le.item and le.item:lower() == nm_low then table.insert(_cache, le) if #_cache >= 50 then break end end end _G._mh_ml_cache = _cache end local my_hist = _G._mh_ml_cache or {} imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f(' Мои сделки (' .. #my_hist .. '):')) local log_h = math.min(#my_hist, 4) * 18*d + 50*d if imgui.BeginChild('##dtl_mylog', imgui.ImVec2(-1, log_h), true) then _dpn1w() if #my_hist == 0 then imgui.TextDisabled(u8' Сделок по этому товару нет') else local _ml_avail = imgui.GetContentRegionAvail().x local _c0 = 110*d local _c1 = 54*d local _c2 = _ml_avail - _c0 - _c1 - 80*d local _c3 = 80*d if _c2 < 80*d then _c2 = 80*d end imgui.Columns(4, '##dtl_ml', false) imgui.SetColumnWidth(0,_c0); imgui.SetColumnWidth(1,_c1) imgui.SetColumnWidth(2,_c2); imgui.SetColumnWidth(3,_c3) local _hc = imgui.ImVec4(0.5,0.5,0.5,1) imgui.TextColored(_hc,u8'Дата'); imgui.NextColumn() imgui.TextColored(_hc,u8'Кол.'); imgui.NextColumn() imgui.TextColored(_hc,u8'Цена $'); imgui.NextColumn() imgui.TextColored(_hc,u8'Тип'); imgui.NextColumn() imgui.Separator() for _, le in ipairs(my_hist) do local _op = (le.op or ''):lower() local is_sell if le.own == nil then is_sell = (_op == 'sell') else is_sell = (le.own == true and _op == 'sell') or (le.own == false and _op == 'buy') end local tc2 = is_sell and imgui.ImVec4(0.4,0.95,0.4,1) or imgui.ImVec4(0.4,0.7,1,1) local _gc = imgui.ImVec4(0.6,0.6,0.6,1) imgui.TextColored(_gc, _cyr5f(' '..(le.dt or ''))); imgui.NextColumn() imgui.TextColored(_gc, tostring(le.qty or 1)); imgui.NextColumn() imgui.TextColored(tc2, _cyr5f(_fmt_price_arz(le.price))); imgui.NextColumn() imgui.TextColored(tc2, is_sell and u8'Продажа' or u8'Покупка'); imgui.NextColumn() end imgui.Columns(1) end imgui.EndChild() end end local function _uwj4x() return os.date('%d.%m.%Y') end local function _onv2g() return os.date('%H:%M:%S') end local function fh_datetime_now() return os.date('%d.%m.%Y [%H:%M:%S]') end local function _hxn2b(text) if not text or text == '' then return {''} end text = text:gsub('\r\n', '\n'):gsub('\r', '\n') local lines = {} for line in (text .. '\n'):gmatch('(.-)\n') do table.insert(lines, line) end return lines end local function fh_clean_log_line(s) s = s:gsub('%[%d%d:%d%d:%d%d%]', '') s = s:gsub('%[Color:%-?%d+%]', '') s = s:gsub('{%x%x%x%x%x%x}', '') return s:match('^%s*(.-)%s*$') or '' end local function fh_lower(s) if not s then return '' end s = tostring(s):lower() local result = '' for i = 1, #s do local b = s:byte(i) if b >= 192 and b <= 223 then result = result .. string.char(b + 32) else result = result .. string.char(b) end end return result end local function fh_prepare_text(s) return fh_lower(tostring(s or '')) end local function fh_get_files(dir) local lfs = require('lfs') local files = {} if not lfs.attributes(dir) then return files end for f in lfs.dir(dir) do if f ~= '.' and f ~= '..' then table.insert(files, f) end end return files end local function fh_wait_for(timeout_ms, condition, on_timeout) local t = timeout_ms while not condition() do wait(1) t = t - 1 if t <= 0 then if on_timeout then on_timeout() end return false end end return true end fh_sort_mode = 1 fh_sort_asc = true local function fh_sort_items(items, mode, asc) local comparators = { [1] = function(a, b) return fh_lower(a.name or '') < fh_lower(b.name or '') end, [2] = function(a, b) return (a.price or 0) < (b.price or 0) end, [3] = function(a, b) return ((a.price or 0) * (a.qty or 0)) < ((b.price or 0) * (b.qty or 0)) end, [4] = function(a, b) return (a.qty or 0) < (b.qty or 0) end, } local cmp = comparators[mode] or comparators[1] if not asc then local orig = cmp cmp = function(a, b) return orig(b, a) end end table.sort(items, cmp) end local function fh_sort_lavka(mode, asc) local list = {} for name, e in pairs(fh_mkt_lavka) do table.insert(list, {name = name, e = e}) end local comparators = { [1] = function(a, b) return fh_lower(a.name) < fh_lower(b.name) end, [2] = function(a, b) return (a.e.s_avg or 0) < (b.e.s_avg or 0) end, [3] = function(a, b) return (a.e.b_avg or 0) < (b.e.b_avg or 0) end, [4] = function(a, b) return (a.e.s_scans or 0) < (b.e.s_scans or 0) end, } local cmp = comparators[mode] or comparators[1] if not asc then local orig = cmp cmp = function(a, b) return orig(b, a) end end table.sort(list, cmp) return list end local function fh_search_items(query, items) local result = {} if not query or query == '' then return items end local q = fh_prepare_text(query) for _, item in ipairs(items) do local n = fh_prepare_text(item.name or '') if n:find(q, 1, true) then table.insert(result, item) end end return result end local function fh_search_lavka(query) local list = {} for name, e in pairs(fh_mkt_lavka) do table.insert(list, {name = name, e = e}) end return fh_search_items(query, list) end local function fh_search_log(query, log_table) local result = {} log_table = log_table or fh_mkt_log if not query or query == '' then return log_table end local q = fh_prepare_text(query) for _, entry in ipairs(log_table) do local s = fh_prepare_text((entry.item or '') .. ' ' .. (entry.partner or '')) if s:find(q, 1, true) then table.insert(result, entry) end end return result end local function _npb4c() local wd = getWorkingDirectory() wd = wd:gsub('\\', '/'):gsub('/$', '') return wd .. '/MH_chatlogs/' end local function _ekf6d(day, month, year) return string.format('%s%02d.%02d.%04d.json', _npb4c(), day, month, year) end local function _wjx3t() local dir = _npb4c() local ok, lfs = pcall(require, 'lfs') if ok then if not lfs.attributes(dir) then pcall(lfs.mkdir, dir) end else os.execute('mkdir "' .. dir:gsub('/', '\\') .. '" 2>nul') end end local function _vsm8w(line) pcall(function() _wjx3t() local t = os.date('*t') local path = _ekf6d(t.day, t.month, t.year) local enc = line:gsub('\\', '\\\\'):gsub('"', '\\"') local f = io.open(path, 'a') if f then f:write('"[' .. _onv2g() .. '] ' .. enc .. '"\n'); f:close() end end) end local function _rqh9z(day, month, year) local path = _ekf6d(day, month, year) local f = io.open(path, 'r') if not f then return {} end local lines = {} for raw in f:lines() do local s = raw:match('^"(.*)"$') if s then s = s:gsub('\\"', '"'):gsub('\\\\', '\\') table.insert(lines, s) end end f:close() return lines end function MH_util.cby5m() local ok, lfs = pcall(require, 'lfs') if not ok then return {} end local dir = _npb4c() local dates = {} if not lfs.attributes(dir) then return dates end for entry in lfs.dir(dir) do local d, m, y = entry:match('^(%d%d)%.(%d%d)%.(%d%d%d%d)%.json$') if d then table.insert(dates, {label=d..'.'..m..'.'..y, day=tonumber(d), month=tonumber(m), year=tonumber(y)}) end end table.sort(dates, function(a, b) if a.year ~= b.year then return a.year > b.year end if a.month ~= b.month then return a.month > b.month end return a.day > b.day end) return dates end fh_log_view = { dates = {}, sel_date = nil, lines = {}, } local function _wdk4v() fh_log_view.dates = MH_util.cby5m() end local function _tjr8f() local v = fh_log_view if not v.sel_date then v.lines = {}; return end local raw = _rqh9z(v.sel_date.day, v.sel_date.month, v.sel_date.year) local lines = {} for i = #raw, 1, -1 do table.insert(lines, raw[i]) end v.lines = lines end local function _parse_trade_sum(s) if not s then return 0 end local c = tostring(s):match('^%s*(.-)%s*$') or '' c = c:match('^(.-)%s*%([^%)]*%)%s*$') or c c = c:match('^%s*(.-)%s*$') or c if c == '' then return 0 end c = c:gsub('РљРљ', 'KK') c = c:gsub('Рљ', 'K') c = c:gsub('Рњ', 'M') local _ckk, _ck = c:match(':KK:%s*([%d%.%,]+)%s+:K:%s*([%d%.%,]+)') if _ckk and _ck then return math.floor((tonumber((_ckk:gsub('[.,]',''))) or 0)*1000000) + math.floor((tonumber((_ck:gsub('[.,]',''))) or 0)*1000) end local _cm, _ck2 = c:match(':M:%s*([%d%.%,]+)%s+:K:%s*([%d%.%,]+)') if _cm and _ck2 then return math.floor((tonumber((_cm:gsub('[.,]',''))) or 0)*1000000000) + math.floor((tonumber((_ck2:gsub('[.,]',''))) or 0)*1000) end local _skk = c:match(':KK:%s*([%d%.%,]+)'); if _skk then return math.floor((tonumber((_skk:gsub('[.,]',''))) or 0)*1000000) end local _sk = c:match(':K:%s*([%d%.%,]+)'); if _sk then return math.floor((tonumber((_sk:gsub('[.,]',''))) or 0)*1000) end local _sm = c:match(':M:%s*([%d%.%,]+)'); if _sm then return math.floor((tonumber((_sm:gsub('[.,]',''))) or 0)*1000000000) end c = c:gsub('[Mm][Mm]', 'МК') c = c:gsub('[Kk][Kk]', 'КК') c = c:gsub('([^%a])([Mm])([^%a])', function(a,m,b) return a..'М'..b end) c = c:gsub('^([Mm])([^%a])', function(m,b) return 'М'..b end) c = c:gsub('([^%a])([Mm])$', function(a,m) return a..'М' end) if c:match('^[Mm]$') then c = 'М' end c = c:gsub('([^%a])([Kk])([^%a])', function(a,k,b) return a..'К'..b end) c = c:gsub('^([Kk])([^%a])', function(k,b) return 'К'..b end) c = c:gsub('([^%a])([Kk])$', function(a,k) return a..'К' end) if c:match('^[Kk]$') then c = 'К' end do local kk_s, k_s = c:match('КК%s*([%d%.%,]+)%s+К%s*([%d%.%,]+)') if kk_s and k_s then local kk_num = tonumber((kk_s:gsub(',','.'))) or 0 local k_num = tonumber((k_s:gsub('[.,]',''))) or 0 return math.floor(kk_num * 1000000) + k_num end local m_s, k2_s = c:match('М%s*([%d%.%,]+)%s+К%s*([%d%.%,]+)') if m_s and k2_s then local m_num = tonumber((m_s:gsub(',','.'))) or 0 local k2_num = tonumber((k2_s:gsub('[.,]',''))) or 0 return math.floor(m_num * 1000000000) + k2_num end local m2_s, kk2_s = c:match('М%s*([%d%.%,]+)%s+КК%s*([%d%.%,]+)') if m2_s and kk2_s then local m2_num = tonumber((m2_s:gsub(',','.'))) or 0 local kk2_num = tonumber((kk2_s:gsub(',','.'))) or 0 return math.floor(m2_num * 1000000000) + math.floor(kk2_num * 1000000) end end local tail = c:match('КК%s*([%d][%d%.%,]*)%s*$') if tail then tail = tail:gsub(',','.') local nd=0; for _ in tail:gmatch('%.') do nd=nd+1 end if nd > 1 then tail = tail:gsub('%.','') end return math.floor((tonumber(tail) or 0) * 1000000) end tail = c:match('М%s*([%d][%d%.%,]*)%s*$') if tail then tail = tail:gsub(',','.') local nd=0; for _ in tail:gmatch('%.') do nd=nd+1 end if nd > 1 then tail = tail:gsub('%.','') end return math.floor((tonumber(tail) or 0) * 1000000000) end tail = c:match('К%s*([%d][%d%.%,]*)%s*$') if tail then if tail:find('[.,]') then return tonumber((tail:gsub('[.,]',''))) or 0 else return (tonumber(tail) or 0) * 1000 end end c = c:match('([%d][%d%.%,]*)%s*$') or c c = c:gsub('^[%$]+', '') local ndots = 0 for _ in c:gmatch('%.') do ndots=ndots+1 end if ndots > 1 then c = c:gsub('[.,]', '') elseif ndots == 1 then local after = c:match('%.(%d+)$') if after and #after == 3 then c = c:gsub('%.', '') end end c = c:gsub(',', '') return tonumber(c) or 0 end FH_TRADE_PATTERNS = { { own=true, op='SELL', pat='([%a_]+) купил у вас (.+) %((%d+) шт%.%), вы получили .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)' }, { own=true, op='SELL', pat='([%a_]+) купил у вас (.+) %((%d+) шт%.%), вы получили .-([%d][%d%.%,]*)' }, { own=true, op='SELL', pat='([%a_]+) купил у вас (.+), вы получили .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)' }, { own=true, op='SELL', pat='([%a_]+) купил у вас (.+), вы получили .-([%d][%d%.%,]*)' }, { own=true, op='BUY', pat='Вы купили (.+) %((%d+) шт%.%) у игрока ([%a_]+) за .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)' }, { own=true, op='BUY', pat='Вы купили (.+) %((%d+) шт%.%) у игрока ([%a_]+) за .-([%d][%d%.%,]*)' }, { own=true, op='BUY', pat='Вы купили (.+) у игрока ([%a_]+) за .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)' }, { own=true, op='BUY', pat='Вы купили (.+) у игрока ([%a_]+) за .-([%d][%d%.%,]*)' }, { own=false, op='BUY', pat='Вы успешно продали (.+) %((%d+) шт%.%) торговцу ([%a_]+), с продажи получили .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)%s*[^%d]' }, { own=false, op='BUY', pat='Вы успешно продали (.+) %((%d+) шт%.%) торговцу ([%a_]+), с продажи получили [^%d]*([%d][%d%.%,]*)%s*[%(%)%s]' }, { own=false, op='BUY', pat='Вы успешно продали (.+) %((%d+) шт%.%) торговцу ([%a_]+), с продажи получили .-([%d][%d%.%,]*)%s*$' }, { own=false, op='BUY', pat='Вы успешно продали (.+) торговцу ([%a_]+), с продажи получили [^%d]*([%d][%d%.%,]*)%s*[%(%)%s]' }, { own=false, op='BUY', pat='Вы успешно продали (.+) торговцу ([%a_]+), с продажи получили .-([%d][%d%.%,]*)%s*$' }, { own=false, op='SELL', pat='Вы успешно купили (.+) %((%d+) шт%.%) у ([%a_]+) за .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)' }, { own=false, op='SELL', pat='Вы успешно купили (.+) %((%d+) шт%.%) у ([%a_]+) за .-([%d][%d%.%,]*)' }, { own=false, op='SELL', pat='Вы успешно купили (.+) у ([%a_]+) за .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)' }, { own=false, op='SELL', pat='Вы успешно купили (.+) у ([%a_]+) за .-([%d][%d%.%,]*)' }, } local function _wsn4d(text) if not text then return nil end local is_vc = text:find('VC$') ~= nil text = text:gsub('[Kk][Kk]', '\xca\xca') text = text:gsub('[Kk](%s+[%d%.%,])', '\xca%1') text = text:gsub('[Kk]([%d%.%,])', '\xca %1') text = text:gsub('[Mm](%s+[%d%.%,])', '\xcc%1') text = text:gsub('[Mm]([%d%.%,])', '\xcc %1') for _, p in ipairs(FH_TRADE_PATTERNS) do local a, b, c, d = text:match(p.pat) if a then local item, qty, partner, total if d then if p.pat:find('купил у вас') then partner, item, qty, total = a, b, tonumber(c) or 1, d else item, qty, partner, total = a, tonumber(b) or 1, c, d end else if p.pat:find('купил у вас') then partner, item, total = a, b, c else item, partner, total = a, b, c end qty = 1 end local sum = _parse_trade_sum(total) local price = qty > 0 and math.ceil(sum / qty) or 0 if item and price > 0 then return { item = item:match('^%s*(.-)%s*$'), qty = qty, price = price, partner = partner or '', op = p.op, own = p.own, is_vc = is_vc, } end end end return nil end local function fh_merge_by_date(sell_arr, buy_arr) local map = {} for _, entry in ipairs(sell_arr or {}) do local d = entry.dateCreate__date or entry.date or '' map[d] = map[d] or {} map[d].sell = entry end for _, entry in ipairs(buy_arr or {}) do local d = entry.dateCreate__date or entry.date or '' map[d] = map[d] or {} map[d].buy = entry end local result = {} for date, v in pairs(map) do table.insert(result, {date=date, sell=v.sell, buy=v.buy}) end table.sort(result, function(a, b) return a.date > b.date end) return result end local _pst = (function() local function _find(name, preset) for i, item in ipairs(preset) do if fh_lower(item.name or '') == fh_lower(name or '') then return item, i end end return nil, nil end return { find = _find, add_sell = function(name, qty, price) if _find(name, fh_lv_autosell_preset) then return false end table.insert(fh_lv_autosell_preset, { name = name, qty = tonumber(qty) or 1, price = tonumber(price) or 0, }) local new_idx = #fh_lv_autosell_preset if _G.as_price_buf then _G.as_price_buf[new_idx] = nil end if _G.as_qty_buf then _G.as_qty_buf[new_idx] = nil end return true end, add_buy = function(name, qty, max_price) if _find(name, fh_lv_autobuy_preset) then return false end local _tq = tonumber(qty) or 1 table.insert(fh_lv_autobuy_preset, { name = name, qty = _tq, target_qty = _tq, max_price = tonumber(max_price) or 0, }) _G.ab_price_buf = nil; _G.ab_qty_buf = nil return true end, remove = function(preset, idx) table.remove(preset, idx) if _G.as_price_buf then local nb = {} for i, v in pairs(_G.as_price_buf) do if type(i)=='number' then if i < idx then nb[i]=v elseif i > idx then nb[i-1]=v end end end _G.as_price_buf = nb end if _G.as_qty_buf then local nb = {} for i, v in pairs(_G.as_qty_buf) do if type(i)=='number' then if i < idx then nb[i]=v elseif i > idx then nb[i-1]=v end end end _G.as_qty_buf = nb end _G.ab_price_buf = nil; _G.ab_qty_buf = nil end, clear = function(preset) for i = #preset, 1, -1 do table.remove(preset, i) end _G.as_price_buf = nil; _G.as_qty_buf = nil _G.ab_price_buf = nil; _G.ab_qty_buf = nil end, ref_price = function(name) local lv = fh_mkt_lavka[name] if lv and (lv.s_avg or lv.b_avg) then return lv.s_avg or lv.b_avg end local cp = fh_mkt_prices[name] if cp then if cp.cp_hist and #cp.cp_hist > 0 then local s7 = _mjg5t(cp.cp_hist, 7) local s30 = _mjg5t(cp.cp_hist, 30) local v7 = s7 and s7.avg and s7.avg > 0 and s7.avg or nil local v30 = s30 and s30.avg and s30.avg > 0 and s30.avg or nil if v7 and v30 then return math.min(v7, v30) elseif v7 then return v7 elseif v30 then return v30 end end if cp.s_avg or cp.b_avg then return cp.s_avg or cp.b_avg end end return fh_get_daily_avg_price(name) end, } end)() function fh_is_my_sell(e) local op = (e.op or ''):upper() local own = e.own if own == true then return op == 'SELL' end if own == false then return op == 'BUY' end return op == 'SELL' end function _qbs9k() local d = settings.general.custom_dpi local bg = settings.interface.bg_brightness or 0.06 local ar = settings.interface.accent_r or 1 local ag = settings.interface.accent_g or 0.55 local ab = settings.interface.accent_b or 0.0 local sb_r = settings.interface.sell_btn_r or 0.10 local sb_g = settings.interface.sell_btn_g or 0.45 local sb_b = settings.interface.sell_btn_b or 0.10 local bb_r = settings.interface.buy_btn_r or 0.00 local bb_g = settings.interface.buy_btn_g or 0.28 local bb_b = settings.interface.buy_btn_b or 0.50 local lp_r = settings.overlay and settings.overlay.log_price_r or 1.0 local lp_g = settings.overlay and settings.overlay.log_price_g or 0.85 local lp_b = settings.overlay and settings.overlay.log_price_b or 0.2 local IC = { up = fa.ARROW_UP, dn = fa.ARROW_DOWN, lt = fa.ARROW_LEFT, rt = fa.ARROW_RIGHT, ll = fa.ANGLES_LEFT, rr = fa.ANGLES_RIGHT, al = fa.ANGLE_LEFT, ar = fa.ANGLE_RIGHT, ban = fa.BAN, bolt = fa.BOLT, boxes = fa.BOXES_STACKED, arch = fa.BOX_ARCHIVE, cal = fa.CALENDAR, cald = fa.CALENDAR_DAY, calds = fa.CALENDAR_DAYS, calw = fa.CALENDAR_WEEK, car = fa.CAR, cart = fa.CART_SHOPPING, chrtl = fa.CHART_LINE, chrts = fa.CHART_SIMPLE, chk = fa.CHECK, circ = fa.CIRCLE, circc = fa.CIRCLE_CHECK, circi = fa.CIRCLE_INFO, circp = fa.CIRCLE_PLUS, circs = fa.CIRCLE_STOP, clk = fa.CLOCK, cld = fa.CLOUD, coin = fa.COINS, gps = fa.CROSSHAIRS, dl = fa.DOWNLOAD, eye = fa.EYE, fimp = fa.FILE_IMPORT, flt = fa.FILTER, save = fa.FLOPPY_DISK, gear = fa.GEAR, lyr = fa.LAYER_GROUP, lxh = fa.LOCATION_CROSSHAIRS, mgnt = fa.MAGNET, srch = fa.MAGNIFYING_GLASS, map = fa.MAP_LOCATION_DOT, min = fa.MINUS, paus = fa.PAUSE, pen = fa.PEN_TO_SQUARE, phone = fa.PHONE, rot = fa.ROTATE_RIGHT, scl = fa.SCALE_BALANCED, star = fa.STAR, store = fa.STORE, tag = fa.TAG, trash = fa.TRASH_CAN, warn = fa.TRIANGLE_EXCLAMATION, ul = fa.UPLOAD, wh = fa.WAREHOUSE, x = fa.XMARK, alr = fa.ARROWS_LEFT_RIGHT, } if not _G.mkt_srch then _G.mkt_srch = imgui.new.char[256]('') end if not _G.mkt_srch_s then _G.mkt_srch_s = '' end if not _G.mkt_lv_srch then _G.mkt_lv_srch = imgui.new.char[256]('') end if not _G.mkt_lv_ss then _G.mkt_lv_ss = '' end if not _G.mkt_log_f then _G.mkt_log_f = imgui.new.char[128](''); _G.mkt_log_fs = '' end if not _G.mkt_cp_page then _G.mkt_cp_page = 1 end if not _G.mkt_lv_page then _G.mkt_lv_page = 1 end if not _G.mkt_log_f2 then _G.mkt_log_f2 = imgui.new.char[128](''); _G.mkt_log_fs2 = '' end if not _G.mkt_log_page then _G.mkt_log_page = 1 end if not _G.mkt_cp_filter then _G.mkt_cp_filter = 0 end if not _G.mkt_cp_sort then _G.mkt_cp_sort = 0 end local cw = imgui.GetWindowContentRegionWidth() if _G.mh_tab == 1 then if not _G._mh_prices_pull_ts or (os.time() - (_G._mh_prices_pull_ts or 0)) > 1800 then _G._mh_prices_pull_ts = os.time() lua_thread.create(function() wait(500); _G._mh_prices_pull(true) end) end if imgui.BeginTabBar('##market_subtabs') then if imgui.BeginTabItem(IC.chrtl..' '..u8'Рынок##sub_market') then local _mkt_data_ver = _G._mh_db_ver or 0 if _G._mkt_cp_tot_ver ~= _mkt_data_ver then _G._mkt_cp_tot_ver = _mkt_data_ver _G._mkt_cp_tot_c = 0 _G._mkt_cp_deep_c = 0 for _, e in pairs(fh_mkt_prices) do _G._mkt_cp_tot_c = _G._mkt_cp_tot_c + 1 if e.cp_hist and #e.cp_hist > 0 then _G._mkt_cp_deep_c = _G._mkt_cp_deep_c + 1 end end _G.mkt_cp_cache_srch = nil end local cp_tot = _G._mkt_cp_tot_c or 0 local deep_tot = _G._mkt_cp_deep_c or 0 local scan_active = fh_mkt_cp_scanning or fh_mkt_cp_deep_scanning local autosell_on = settings.autosell and settings.autosell.enabled local autobuy_off = not (settings.autobuy and settings.autobuy.enabled) local dot_g = imgui.ImVec4(0.20, 0.90, 0.35, 1) local dot_y = imgui.ImVec4(0.95, 0.75, 0.10, 1) local dot_d = imgui.ImVec4(0.30, 0.28, 0.22, 1) if fh_mkt_cp_deep_scanning then imgui.Spacing() local done = fh_mkt_cp_deep_done or 0 imgui.TextColored(dot_g, _cyr5f('\xd1\xca\xc0\xcd\xc8\xd0\xce\xc2\xc0\xcd\xc8\xc5: ' .. done .. '/' .. cp_tot)) local _stop_w = 65*d imgui.SameLine(0,10*d) imgui.ProgressBar(-1*os.clock(), imgui.ImVec2(imgui.GetContentRegionAvail().x - _stop_w - 8*d, 8*d)) imgui.SameLine(0,8*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.65,0.15,0.10,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.80,0.20,0.15, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(0.90,0.25,0.20,1)) if imgui.Button(u8'СТОП##cpdeepstop', imgui.ImVec2(_stop_w, 0)) then fh_mkt_cp_deep_scanning = false end imgui.PopStyleColor(3) imgui.Spacing() elseif fh_mkt_cp_scanning then imgui.Spacing() imgui.TextColored(dot_g, _cyr5f('\xd1\xca\xc0\xcd\xc8\xd0\xce\xc2\xc0\xcd\xc8\xc5 (' .. cp_tot .. ')')) imgui.SameLine(0,10*d); imgui.ProgressBar(-1*os.clock(), imgui.ImVec2(imgui.GetContentRegionAvail().x, 8*d)) imgui.Spacing() end imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar*0.7, ag*0.7, ab*0.7, 1), IC.srch) imgui.SameLine(0, 6*d) local _cp_srch_w = cw - 140*d imgui.PushItemWidth(_cp_srch_w) imgui.PushStyleColor(imgui.Col.FrameBg, imgui.ImVec4(bg+.08,bg+.07,bg+.04, _G._mh_wa or 1)) if imgui.InputTextWithHint(u8'##cp_srch', _cyr5f('Поиск товара...'), _G.mkt_srch, 256) then do local _r = u8:decode(ffi.string(_G.mkt_srch)) local _ok,_cp = pcall(function() return require('encoding').CP1251:encode(_r) end) local _s = (_ok and _cp or _r):lower() _G.mkt_srch_s = _s:gsub('[А-Я]',function(c) return string.char(string.byte(c)+32) end):match('^%s*(.-)%s*$') end _G.mkt_cp_page = 1 _G.mkt_cp_cache_srch = nil; _G._mkt_trend_cache = nil end imgui.PopStyleColor(); imgui.PopItemWidth() imgui.SameLine(0, 3*d) local _has_cp_srch = ffi.string(_G.mkt_srch) ~= '' imgui.PushStyleColor(imgui.Col.Button, _has_cp_srch and imgui.ImVec4(0.38,0.08,0.08,1) or imgui.ImVec4(0.12,0.12,0.12,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.55,0.12,0.12, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, _has_cp_srch and imgui.ImVec4(1,0.4,0.4,1) or imgui.ImVec4(0.3,0.3,0.3,1)) if imgui.Button(IC.x..'##cpsrchclr', imgui.ImVec2(28*d, 0)) and _has_cp_srch then ffi.fill(_G.mkt_srch, 256, 0) _G.mkt_srch_s = ''; _G.mkt_cp_page = 1; _G.mkt_cp_cache_srch = nil; _G._mkt_trend_cache = nil end imgui.PopStyleColor(3) local filter_active = _G.mkt_cp_filter == 1 if imgui.IsItemHovered() then imgui.BeginTooltip() if filter_active then imgui.TextColored(imgui.ImVec4(lp_r,lp_g,lp_b,1), u8'\xd4\xc8\xcb\xd2\xd0: \xd2\xce\xc2\xc0\xd0\xdb \xd1 \xc8\xd1\xd2\xce\xd0\xc8\xc5\xc9 \xd6\xc5\xcd') imgui.TextDisabled(u8'\xcf\xee\xea\xe0\xe7\xfb\xe2\xe0\xfe\xf2\xf1\xff \xf2\xee\xeb\xfc\xea\xee \xf2\xee\xe2\xe0\xf0\xfb \xf1 \xe8\xf1\xf2\xee\xf0\xe8\xe5\xe9') else imgui.TextColored(imgui.ImVec4(0.65,0.65,0.65,1), u8'\xd4\xc8\xcb\xd2\xd0: \xc2\xd1\xc5 \xd2\xce\xc2\xc0\xd0\xdb') imgui.TextDisabled(u8'\xcf\xee\xea\xe0\xe7\xfb\xe2\xe0\xfe\xf2\xf1\xff \xe2\xf1\xe5 \xf2\xee\xe2\xe0\xf0\xfb') end imgui.TextDisabled(u8'\xcd\xe0\xe6\xec\xe8\xf2\xe5 \xe4\xeb\xff \xef\xe5\xf0\xe5\xea\xeb\xfe\xf7\xe5\xed\xe8\xff') imgui.EndTooltip() end if filter_active then imgui.PopStyleColor(3) end imgui.Spacing() local sort_labels = { u8'\xcf\xce\xd1\xcb\xc5\xc4\xcd\xc8\xc5##s0', u8'30 \xc4\xcd\xc5\xc9##s1', u8'\xd6\xc5\xcd\xc0##s2', u8'\xc0-\xdf##s3', } local sw4 = (cw - 18*d) / 4 for si = 0, 3 do if si > 0 then imgui.SameLine(0, 6*d) end local is_active = (_G.mkt_cp_sort == si) if is_active then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(ar*0.6, ag*0.6, ab*0.6, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(ar*0.8, ag*0.8, ab*0.8, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(ar, ag, ab, 1)) end if imgui.Button(sort_labels[si+1], imgui.ImVec2(sw4, 0)) then _G.mkt_cp_sort = si _G.mkt_cp_cache_srch = nil; _G._mkt_trend_cache = nil _G.mkt_cp_page = 1 end if is_active then imgui.PopStyleColor(3) end end imgui.Separator() if not _G.mkt_cp_cache_srch then _G.mkt_cp_cache_srch = nil; _G._mkt_trend_cache = nil end if not _G.mkt_cp_cache_list then _G.mkt_cp_cache_list = {} end local srch = _G.mkt_srch_s or '' local _cur_data_ver = _G._mh_db_ver or 0 local cache_key = srch .. '|' .. (_G.mkt_cp_filter or 0) .. '|' .. (_G.mkt_cp_sort or 0) local cache_key_full = cache_key .. '|' .. _cur_data_ver local _page_reset_needed = (_G.mkt_cp_cache_srch ~= cache_key) if _G._mkt_cp_cache_full_key ~= cache_key_full and not _G._mkt_build_running then _G._mkt_cp_cache_full_key = cache_key_full _G._mkt_build_running = true _G._mkt_build_pending_key = cache_key _G._mkt_build_page_reset = _page_reset_needed local _b_srch = srch local _b_filt = _G.mkt_cp_filter or 0 local _b_sort = _G.mkt_cp_sort or 0 local _b_min_p = (_bcn4w() and settings.market_filters and settings.market_filters.min_price) or 0 local _b_tup = _bcn4w() and settings.market_filters and settings.market_filters.trend_up_only local _b_dbver = _G._mh_db_ver or 0 local _b_prices = {} for _bk, _bv in pairs(fh_mkt_prices) do _b_prices[_bk] = _bv end if not _G._mkt_nm_lo_cache or _G._mkt_nm_lo_cache_ver ~= _b_dbver then _G._mkt_nm_lo_cache_ver = _b_dbver _G._mkt_nm_lo_cache = {} for nm2, _ in pairs(_b_prices) do if type(nm2) == 'string' then _G._mkt_nm_lo_cache[nm2] = nm2:lower():gsub('[А-Я]', function(c) return string.char(string.byte(c)+32) end) end end end local _b_nm_lo = _G._mkt_nm_lo_cache if not _G._mkt_trend_cache then _G._mkt_trend_cache = {} end local _b_trd_cache = _G._mkt_trend_cache lua_thread.create(function() local mf_new = {} local _cnt = 0 for nm, e in pairs(_b_prices) do if type(nm)=='string' and type(e)=='table' then local has_deep = e.cp_hist and #e.cp_hist > 0 if not (_b_filt == 1 and not has_deep) then local _pass = true if _b_srch ~= '' then local nm_lo = _b_nm_lo[nm] or nm:lower() if not nm_lo:find(_b_srch, 1, true) then _pass = false end end if _pass and not has_deep and not e.s_avg and not e.b_avg then _pass = false end if _pass and _b_min_p > 0 then local _chk_p = (has_deep and e.cp_hist[1].price) or e.cp_sp or e.s_avg or 0 if _chk_p < _b_min_p then _pass = false end end if _pass and _b_tup then if not (e.cp_hist and #e.cp_hist >= 4) then _pass = false else if not _b_trd_cache[nm] then _b_trd_cache[nm] = _G._xvn2w(e.cp_hist) end local _trd = _b_trd_cache[nm] if not (type(_trd)=='table' and _trd.is_up) then _pass = false end end end if _pass then local _q30 = 0 if e.cp_hist and #e.cp_hist > 0 then local _cut = os.date('%Y-%m-%d', os.time() - 30*86400) for _, _h in ipairs(e.cp_hist) do if (_h.dt or '') >= _cut then _q30 = _q30 + (_h.qty or 1) end end end if _q30 == 0 then _q30 = e.s_totalC or 0 end table.insert(mf_new, {nm=nm, e=e, q30=_q30}) end end end _cnt = _cnt + 1 if _cnt % 300 == 0 then wait(0) end end if _b_sort == 0 then table.sort(mf_new, function(a, b) local da = (a.e.cp_hist and a.e.cp_hist[1] and a.e.cp_hist[1].dt) or '' local db = (b.e.cp_hist and b.e.cp_hist[1] and b.e.cp_hist[1].dt) or '' if da ~= db then return da > db end return a.nm < b.nm end) elseif _b_sort == 1 then table.sort(mf_new, function(a, b) local qa = a.q30 or a.e.s_totalC or 0 local qb = b.q30 or b.e.s_totalC or 0 if qa ~= qb then return qa > qb end; return a.nm < b.nm end) elseif _b_sort == 2 then table.sort(mf_new, function(a, b) local pa = a.e.s_avg or 0; local pb = b.e.s_avg or 0 if pa ~= pb then return pa > pb end; return a.nm < b.nm end) else table.sort(mf_new, function(a, b) return a.nm < b.nm end) end wait(0) _G.mkt_cp_cache_list = mf_new _G.mkt_cp_cache_srch = _G._mkt_build_pending_key if _G._mkt_build_page_reset then _G.mkt_cp_page = 1 end _G._mkt_build_running = false end) end local _mkt_ver2 = tostring(_G._mh_db_ver or 0) if _G._mkt_trend_cache_ver ~= _mkt_ver2 then _G._mkt_trend_cache_ver = _mkt_ver2 _G._mkt_trend_cache = {} end if _G._mkt_build_running then imgui.SameLine(0, 8*d) imgui.TextColored(imgui.ImVec4(1, 0.75, 0.2, 0.85), _ic_spin .. ' ' .. _cyr5f('обновление...')) end local mf = _G.mkt_cp_cache_list or {} local MKT_PAGE_SIZE = 50 local cp_pages = math.max(1, math.ceil(#mf / MKT_PAGE_SIZE)) if _G.mkt_cp_page > cp_pages then _G.mkt_cp_page = cp_pages end local cp_from = (_G.mkt_cp_page-1)*MKT_PAGE_SIZE+1 local cp_to = math.min(_G.mkt_cp_page*MKT_PAGE_SIZE, #mf) do if not _G.mkt_minp then _G.mkt_minp = imgui.new.int(settings.market_filters and settings.market_filters.min_price or 0) end if not _G.mkt_tup then _G.mkt_tup = imgui.new.bool(settings.market_filters and settings.market_filters.trend_up_only or false) end imgui.TextDisabled(_cyr5f('Мин. цена $:')) imgui.SameLine(0,6*d) imgui.PushItemWidth(110*d) if imgui.InputInt('##mkt_minp', _G.mkt_minp, 0, 0) then if _G.mkt_minp[0] < 0 then _G.mkt_minp[0] = 0 end settings.market_filters.min_price = _G.mkt_minp[0]; _wfn7p() end imgui.PopItemWidth() imgui.SameLine(0,12*d) if imgui.Checkbox(IC.up..' '.._cyr5f('Тренд вверх##mkt_tup'), _G.mkt_tup) then settings.market_filters.trend_up_only = _G.mkt_tup[0]; _wfn7p() end imgui.SameLine(0, 14*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.05,0.22,0.48,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.08,0.35,0.72, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.55,0.85,1,1)) local _pull_lbl = fa.DOWNLOAD..' '.._cyr5f('\xd6\xe5\xed\xfb ##mkt_pricepull') if imgui.Button(_pull_lbl, imgui.ImVec2(0, 0)) then _G._mh_prices_pull(false) end if imgui.IsItemHovered() then local _srv_nm = (ARZ_SERVERS[(_G.arz_srv_sel and (_G.arz_srv_sel[0]+1)) or 1] or {}).name or '?' local _srv_id = (ARZ_SERVERS[(_G.arz_srv_sel and (_G.arz_srv_sel[0]+1)) or 1] or {}).id or -1 imgui.SetTooltip(_cyr5f('Загрузить цены ЦР с облака\n'.._srv_nm..' (id='.._srv_id..')')) end imgui.SameLine(0, 6*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10,_G._mh_sb_g or 0.45,_G._mh_sb_b or 0.10,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.1,sb_g*1.1,sb_b*1.1, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.55,1,0.60,1)) local _push_lbl = fa.UPLOAD..' '.._cyr5f('\xd6\xe5\xed\xfb ##mkt_pricepush') if imgui.Button(_push_lbl, imgui.ImVec2(0, 0)) then _G._mh_prices_push() end if imgui.IsItemHovered() then local _srv_nm2 = (ARZ_SERVERS[(_G.arz_srv_sel and (_G.arz_srv_sel[0]+1)) or 1] or {}).name or '?' local _srv_id2 = (ARZ_SERVERS[(_G.arz_srv_sel and (_G.arz_srv_sel[0]+1)) or 1] or {}).id or -1 imgui.SetTooltip(_cyr5f('Выгрузить цены ЦР на облако\n'.._srv_nm2..' (id='.._srv_id2..')')) end imgui.Spacing() end local list_h = imgui.GetWindowHeight() - imgui.GetCursorPosY() - 85*d if imgui.BeginChild('##cp_list', imgui.ImVec2(-1, list_h), true) then _dpn1w() imgui.Columns(6, '##cphdr', false) local _cw_total = imgui.GetWindowContentRegionWidth() local _cw_nm = math.floor(_cw_total * 0.28) local _cw_avg = math.floor(_cw_total * 0.14) local _cw_min = math.floor(_cw_total * 0.14) local _cw_max = math.floor(_cw_total * 0.14) local _cw_qty = math.floor(_cw_total * 0.10) local _cw_trend = math.floor(_cw_total * 0.12) imgui.SetColumnWidth(0, _cw_nm) imgui.SetColumnWidth(1, _cw_avg) imgui.SetColumnWidth(2, _cw_min) imgui.SetColumnWidth(3, _cw_max) imgui.SetColumnWidth(4, _cw_qty) imgui.SetColumnWidth(5, _cw_trend) local hc = imgui.ImVec4(0.42, 0.40, 0.32, 1) local hca = imgui.ImVec4(ar*0.85, ag*0.85, ab*0.85, 1) imgui.TextColored(hca, u8' \xd2\xce\xc2\xc0\xd0'); imgui.NextColumn() imgui.TextColored(hca, u8' \xd1\xd0\xc5\xc4\xcd\xdf\xdf'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.35,0.90,0.40,1), u8' \xcc\xc8\xcd'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.9,0.4,0.3,1), u8' \xcc\xc0\xca\xd1'); imgui.NextColumn() imgui.TextColored(hc, u8' \xd8\xd2.'); imgui.NextColumn() imgui.TextColored(hc, IC.chrts..' '.._cyr5f(' \xd2\xd0\xc5\xcd\xc4')); imgui.NextColumn() imgui.Separator() if #mf == 0 then imgui.TextDisabled(u8' База пуста. Запустите скан на чекпоинте.') for _=1,7 do imgui.NextColumn() end end _mkt_build_row_cache(mf, cp_from, cp_to, d) _mkt_row_cache_step() local _rc = _G._mkt_row_cache local _lp_c = imgui.ImVec4(lp_r,lp_g,lp_b,1) local _grey = imgui.ImVec4(0.7,0.7,0.7,1) local _grn = imgui.ImVec4(0.4,0.95,0.4,1) local _zero = imgui.ImVec4(0,0,0,0) local _rlh = imgui.GetTextLineHeight() for ri = cp_from, cp_to do local r = mf[ri]; if not r then break end local row = _rc[ri] do local _rsp = imgui.GetCursorScreenPos() local nm = r.nm or '' local tag = mh_get_item_tag(nm) local tag_px = '' if tag == 'watch' then tag_px = fa.EYE..' ' elseif tag == 'skip' then tag_px = fa.BAN..' ' elseif tag == 'fav' then tag_px = fa.STAR..' ' end local nm_c32 = (r.e and r.e.cp_hist and #r.e.cp_hist>0) and 0xFFFFFFFF or 0xFFBFBFBF if tag == 'fav' then nm_c32 = 0xFFD9C41A elseif tag == 'watch' then nm_c32 = 0xFFD9BE6A elseif tag == 'skip' then nm_c32 = 0xFF7A7A7A end local label = tag_px .. _cyr5f(' ' .. nm) local textw = imgui.CalcTextSize(label).x local _rcw = 180*d - 6 imgui.PushStyleColor(imgui.Col.Text, _zero) if imgui.Selectable('##cp'..ri, false, imgui.SelectableFlags.SpanAllColumns + imgui.SelectableFlags.AllowDoubleClick, imgui.ImVec2(0, 0)) then _G.mkt_detail_item = r.nm _G.mkt_detail_src = 'cp' _G.mkt_detail_open = true end imgui.PopStyleColor() local _rdl = imgui.GetWindowDrawList() _rdl:PushClipRect(_rsp, imgui.ImVec2(_rsp.x + _rcw, _rsp.y + _rlh + 2), true) local _roff = 0 if textw > _rcw then local _rsd = textw - _rcw + 8 local _rspd = 1.5 local _rspt = _rsd / 40 + 2 * _rspd local _rph = math.fmod(imgui.GetTime() + ri * 0.53, _rspt) if _rph > _rspd then _roff = math.min((_rph - _rspd) * 40, _rsd) end if _rph >= _rspt - _rspd then _roff = _rsd end end _rdl:AddText(imgui.ImVec2(_rsp.x - _roff, _rsp.y), nm_c32, label) _rdl:PopClipRect() end imgui.NextColumn() if row then if row.fmt_avg then imgui.TextColored(_lp_c, row.fmt_avg) else imgui.TextDisabled(u8' —') end imgui.NextColumn() if row.fmt_min then imgui.TextColored(_grn, row.fmt_min) else imgui.TextDisabled(u8' —') end imgui.NextColumn() if row.fmt_max then imgui.TextColored(imgui.ImVec4(0.9,0.4,0.3,1), row.fmt_max) else imgui.TextDisabled(u8' —') end imgui.NextColumn() if row.fmt_qty then imgui.TextColored(_grey, row.fmt_qty) else imgui.TextDisabled(u8' —') end imgui.NextColumn() imgui.TextColored(imgui.ImVec4(row.tc_r, row.tc_g, row.tc_b, 1), row.fmt_trd) imgui.NextColumn() else for _=1,7 do imgui.TextDisabled(u8' ...'); imgui.NextColumn() end end end imgui.Columns(1) imgui.EndChild() end imgui.Spacing() imgui.Separator() imgui.Spacing() local pw = 38*d if imgui.Button(u8'\xab##cpp', imgui.ImVec2(pw,0)) then _G.mkt_cp_page=1 end imgui.SameLine(0,4*d) if imgui.Button(u8'\xab##cppr', imgui.ImVec2(pw,0)) then if _G.mkt_cp_page>1 then _G.mkt_cp_page=_G.mkt_cp_page-1 end end imgui.SameLine(0,10*d) imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f(_G.mkt_cp_page..'/'..cp_pages)) imgui.SameLine(0,4*d) imgui.TextColored(imgui.ImVec4(0.40,0.38,0.30,1), _cyr5f('('..#mf..' \xf2\xee\xe2\xe0\xf0\xee\xe2)')) imgui.SameLine(0,10*d) if imgui.Button(u8'\xbb##cpnx', imgui.ImVec2(pw,0)) then if _G.mkt_cp_page Поиск > Авторынок — откройте список авто') end imgui.Spacing() imgui.PushItemWidth(cw_a - 6*d) if imgui.InputText(u8'##auto_srch', _G.mkt_auto_srch, 256) then _G.mkt_auto_srch_s = u8:decode(ffi.string(_G.mkt_auto_srch)):lower() _G.mkt_auto_page = 1; _G.mkt_auto_cache_k = nil end imgui.PopItemWidth() imgui.Spacing() local auto_sort_labels = { u8'Цена(уб.)##as0', u8'Цена(воз.)##as1', u8'А-Я##as2' } local sw3 = (cw_a - 12*d) / 3 for si = 0, 2 do if si > 0 then imgui.SameLine(0, 6*d) end local is_act = (_G.mkt_auto_sort == si) if is_act then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(ar*0.6, ag*0.6, ab*0.6, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(ar*0.8, ag*0.8, ab*0.8, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(ar, ag, ab, 1)) end if imgui.Button(auto_sort_labels[si+1], imgui.ImVec2(sw3, 0)) then _G.mkt_auto_sort = si; _G.mkt_auto_cache_k = nil; _G.mkt_auto_page = 1 end if is_act then imgui.PopStyleColor(3) end end imgui.Separator() local auto_srch = _G.mkt_auto_srch_s or '' local auto_ck = auto_srch .. '|' .. (_G.mkt_auto_sort or 0) if _G.mkt_auto_cache_k ~= auto_ck then _G.mkt_auto_cache_k = auto_ck local al = {} for nm, e in pairs(fh_mkt_auto) do if type(nm)=='string' and type(e)=='table' then if auto_srch=='' or nm:lower():find(auto_srch,1,true) then if e.s_avg or e.cp_sp then table.insert(al, {nm=nm,e=e}) end end end end local sm = _G.mkt_auto_sort or 0 if sm == 0 then table.sort(al, function(a,b) local pa=(a.e.s_avg or a.e.cp_sp or 0); local pb=(b.e.s_avg or b.e.cp_sp or 0) if pa~=pb then return pa > pb end; return tostring(a.nm) auto_pages then _G.mkt_auto_page = auto_pages end local a_from = (_G.mkt_auto_page-1)*AUTO_PAGE+1 local a_to = math.min(_G.mkt_auto_page*AUTO_PAGE, #amf) local list_h_a = imgui.GetWindowHeight() - imgui.GetCursorPosY() - 45*d if imgui.BeginChild('##auto_list', imgui.ImVec2(-1, list_h_a), true) then _dpn1w() imgui.Columns(5,'##autohdr',false) imgui.SetColumnWidth(0, cw_a*0.35 - 30*d); imgui.SetColumnWidth(1, cw_a*0.20) imgui.SetColumnWidth(2, cw_a*0.15 + 10*d); imgui.SetColumnWidth(3, cw_a*0.15 + 10*d); imgui.SetColumnWidth(4, cw_a*0.15 + 10*d) local hc = imgui.ImVec4(0.6,0.6,0.6,1) imgui.TextColored(hc, u8' Автомобиль'); imgui.NextColumn() imgui.TextColored(hc, u8' Цена $'); imgui.NextColumn() imgui.TextColored(hc, u8' Мин'); imgui.NextColumn() imgui.TextColored(hc, u8' Макс'); imgui.NextColumn() imgui.TextColored(hc, u8' Обнов.'); imgui.NextColumn() imgui.Separator() if #amf == 0 then imgui.TextDisabled(u8' Список пуст. Откройте Авторынок.') for _=1,4 do imgui.NextColumn() end end for ri = a_from, a_to do local r = amf[ri]; if not r then break end local e = r.e local price = e.s_avg or e.cp_sp local p_min = e.s_min; local p_max = e.s_max do local _asp = imgui.GetCursorScreenPos() local _alh = imgui.GetTextLineHeight() local _astr = _cyr5f(' ' .. (r.nm or '')) local _atw = imgui.CalcTextSize(_astr).x local _acw = cw_a * 0.35 - 30*d - 6 imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0,0,0,0)) if imgui.Selectable('##asel'..ri, false, imgui.SelectableFlags.SpanAllColumns) then _G.mkt_auto_detail_item = r.nm _G.mkt_auto_detail_open = true end imgui.PopStyleColor() local _adl = imgui.GetWindowDrawList() _adl:PushClipRect(_asp, imgui.ImVec2(_asp.x + _acw, _asp.y + _alh + 2), true) local _aoff = 0 if _atw > _acw then local _asd = _atw - _acw + 8 local _aspd = 1.5 local _aspt = _asd / 40 + 2 * _aspd local _aph = math.fmod(imgui.GetTime() + ri * 0.53, _aspt) if _aph > _aspd then _aoff = math.min((_aph - _aspd) * 40, _asd) end if _aph >= _aspt - _aspd then _aoff = _asd end end _adl:AddText(imgui.ImVec2(_asp.x - _aoff, _asp.y), 0xFFFFFFFF, _astr) _adl:PopClipRect() end imgui.NextColumn() if price then imgui.TextColored(imgui.ImVec4(lp_r,lp_g,lp_b,1), _cyr5f(' $'.._kcr3y(price))) else imgui.TextDisabled(u8' —') end imgui.NextColumn() if p_min then imgui.TextColored(imgui.ImVec4(0.4,0.95,0.4,1), _cyr5f(' $'.._zhb9s(p_min))) else imgui.TextDisabled(u8' —') end imgui.NextColumn() if p_max then imgui.TextColored(imgui.ImVec4(1,0.5,0.5,1), _cyr5f(' $'.._zhb9s(p_max))) else imgui.TextDisabled(u8' —') end imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1), _cyr5f(' '..(e.date or '—'))) imgui.NextColumn() end imgui.Columns(1); imgui.EndChild() end local pw_a = 42*d imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f('Стр. '.._G.mkt_auto_page..'/'..auto_pages..' ('..#amf..' авто)')) imgui.SameLine(0,8*d) if imgui.Button(IC.ll..'##alp',imgui.ImVec2(pw_a,0)) then _G.mkt_auto_page=1 end imgui.SameLine(0,4*d) if imgui.Button(IC.al..'##alpr',imgui.ImVec2(pw_a,0)) then if _G.mkt_auto_page>1 then _G.mkt_auto_page=_G.mkt_auto_page-1 end end imgui.SameLine(0,4*d) if imgui.Button(IC.ar..'##alnx',imgui.ImVec2(pw_a,0)) then if _G.mkt_auto_page 0 then _init_idx = _G.mh_boot_srv_idx end _G.arz_srv_sel = imgui.new.int(_init_idx) end if not _G.arz_srv_ptr then local _names = {} for _, s in ipairs(ARZ_SERVERS) do table.insert(_names, u8(s.name)) end _G.arz_srv_ptr = imgui.new['const char*'][#_names](_names) _G.arz_srv_count = #_names end if not _G.arb_first_load_done then _G.arb_first_load_done = true if not mh_arz_loading then local _al_srv = ARZ_SERVERS[_G.arz_srv_sel[0] + 1] local _al_id = _al_srv and _al_srv.id or -1 lua_thread.create(function() wait(0); _xtj6b(_al_id) end) end end imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar,ag,ab,1), IC.flt..' ') imgui.SameLine(0,4*d2) imgui.TextDisabled(_cyr5f('Мин. цена лавки $:')) imgui.SameLine(0,6*d2) imgui.PushItemWidth(120*d2) if imgui.InputInt('##arb_minp', _G.arb_min_price, 0, 0) then if _G.arb_min_price[0] < 0 then _G.arb_min_price[0] = 0 end settings.market_filters.min_price = _G.arb_min_price[0]; _wfn7p() _G.arb_list = nil end imgui.PopItemWidth() imgui.SameLine(0, 8*d2) if imgui.Checkbox('##arb_tup', _G.arb_trend_up) then settings.market_filters.trend_up_only = _G.arb_trend_up[0]; _wfn7p() _G.arb_list = nil end if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Только тренд вверх')) end imgui.Spacing() if not _G.arb_dir_filter then _G.arb_dir_filter = imgui.new.int(0) end do local dds = { {i=IC.lyr, t=_cyr5f('Все'), v=0, r=0.55,g=0.55,b=0.55}, {i=IC.store..IC.rt..IC.chrtl, t=_cyr5f('Покупка в лавке'), v=1, r=0.35,g=0.70,b=1.00}, {i=IC.chrtl..IC.rt..IC.store, t=_cyr5f('Скуп дороже рынка'), v=2, r=1.00,g=0.70,b=0.25}, {i=IC.store..IC.alr..IC.store, t=_cyr5f('Лавка-Лавка'), v=3, r=0.75,g=0.45,b=1.00}, } for _di,_dd in ipairs(dds) do if _di > 1 then imgui.SameLine(0, 4*d2) end local _da = (_G.arb_dir_filter[0] == _dd.v) local r,g,b = _dd.r,_dd.g,_dd.b if _da then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(r*.30,g*.30,b*.30,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(r*.45,g*.45,b*.45, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(r*.60,g*.60,b*.60,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(r,g,b,1)) else imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.09,0.09,0.11,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(r*.16,g*.16,b*.16, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(r*.28,g*.28,b*.28,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(r*.60,g*.60,b*.60,1)) end if imgui.Button(_dd.i..'##ddf'..tostring(_dd.v), imgui.ImVec2(0, 20*d2)) then _G.arb_dir_filter[0] = _dd.v _G.arb_page = 1 if _G.arb_raw_list then local _df = _dd.v if _df == 0 then _G.arb_list = _G.arb_raw_list elseif _df == 1 then _G.arb_list = {} for _,_a in ipairs(_G.arb_raw_list) do if _a.dir=='buy' then table.insert(_G.arb_list,_a) end end elseif _df == 2 then _G.arb_list = {} for _,_a in ipairs(_G.arb_raw_list) do if _a.dir=='sell' then table.insert(_G.arb_list,_a) end end elseif _df == 3 then _G.arb_list = {} for _,_a in ipairs(_G.arb_raw_list) do if _a.dir=='shop2shop' then table.insert(_G.arb_list,_a) end end end else _G.arb_list = nil; _G.arb_prev_list = nil; _G.arb_building = false end end imgui.PopStyleColor(4) if imgui.IsItemHovered() then imgui.SetTooltip(_dd.t) end end end imgui.Spacing() imgui.TextDisabled(_cyr5f('Сервер:')) imgui.SameLine(0, 6*d2) imgui.PushItemWidth(140*d2) imgui.PushStyleColor(imgui.Col.FrameBg, imgui.ImVec4(0.12, 0.12, 0.15, _G._mh_wa or 1)) if imgui.Combo(u8'##arb_srv', _G.arz_srv_sel, _G.arz_srv_ptr, _G.arz_srv_count) then _G.arb_list = nil; _G.arb_raw_list = nil; _G.arb_prev_list = nil; _G.arb_building = false; _G.arb_page = 1 _G._mh_arb_notif = nil _G._mh_arb_idx = {sell={}, buy={}, ver=0, srv=-1} if not mh_arz_loading then local _chg_id = ARZ_SERVERS[_G.arz_srv_sel[0] + 1] and ARZ_SERVERS[_G.arz_srv_sel[0] + 1].id or -1 lua_thread.create(function() wait(0); _xtj6b(_chg_id) end) end end imgui.PopStyleColor() imgui.PopItemWidth() imgui.SameLine(0, 6*d2) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.08, 0.18, 0.32, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.12, 0.28, 0.50, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(0.16, 0.38, 0.65, 1)) if imgui.Button(IC.gps .. ' ' .. u8'Авто##arb_auto', imgui.ImVec2(80*d2, 0)) then local _aidx = _mpf7d() _G.arz_srv_sel[0] = _aidx _G.arb_list = nil; _G.arb_raw_list = nil; _G.arb_prev_list = nil; _G.arb_building = false; _G.arb_page = 1 _G._mh_arb_notif = nil _G._mh_arb_idx = {sell={}, buy={}, ver=0, srv=-1} if not mh_arz_loading then local _auto_id = ARZ_SERVERS[_aidx + 1] and ARZ_SERVERS[_aidx + 1].id or -1 lua_thread.create(function() wait(0); _xtj6b(_auto_id) end) end end imgui.PopStyleColor(3) imgui.Separator(); imgui.Spacing() do local _api_n = mh_arz_data and #mh_arz_data or 0 if mh_arz_loading then imgui.TextColored(imgui.ImVec4(ar,ag,0.2,1), IC.rot..' '.._cyr5f('Загрузка лавок с API...')) elseif mh_arz_items_loading then imgui.TextColored(imgui.ImVec4(ar*0.7,ag*0.7,0.2,1), IC.rot..' '.._cyr5f('Загрузка базы предметов...')) elseif _api_n == 0 then imgui.TextColored(imgui.ImVec4(1,0.4,0.4,1), IC.warn..' '.._cyr5f('Нет данных API — нажмите Авто или смените сервер')) else local _idb = mh_arz_items_loaded and _cyr5f('Предметы: ОК') or _cyr5f('Предметы: загрузка...') imgui.TextDisabled(IC.store..' '.._cyr5f('API: '.._api_n..' лавок | ').._idb) end imgui.Spacing() end local _need_rebuild = not _G.arb_building and ( not _G.arb_prev_list or (#(_G.arb_list or {}) == 0 and #(_G.arb_prev_list or {}) == 0 and mh_arz_data and #mh_arz_data > 0 and mh_arz_items_loaded) ) if _need_rebuild then _G.arb_building = true if not _G.arb_prev_list then _G.arb_prev_list = {} end _G.arb_gen = (_G.arb_gen or 0) + 1 local _gen = _G.arb_gen local _arb_srv = ARZ_SERVERS[_G.arz_srv_sel and (_G.arz_srv_sel[0]+1) or 1] local _arb_sid = _arb_srv and _arb_srv.id or -1 local _min_p = settings.market_filters.min_price or 0 local _tup = settings.market_filters.trend_up_only or false local _df = _G.arb_dir_filter and _G.arb_dir_filter[0] or 0 lua_thread.create(function() wait(0) if _G.arb_gen ~= _gen then _G.arb_building = false; return end local _aborted = false local _arb_raw = _G._vkp7n(_min_p, _tup, _arb_sid, function() wait(0) if _G.arb_gen ~= _gen then _aborted = true; return false end end) if _aborted or _G.arb_gen ~= _gen then _G.arb_building = false; return end local _res if _df == 0 then _res = _arb_raw elseif _df == 1 then _res={}; for _,_a in ipairs(_arb_raw) do if _a.dir=='buy' then table.insert(_res,_a) end end elseif _df == 2 then _res={}; for _,_a in ipairs(_arb_raw) do if _a.dir=='sell' then table.insert(_res,_a) end end elseif _df == 3 then _res={}; for _,_a in ipairs(_arb_raw) do if _a.dir=='shop2shop' then table.insert(_res,_a) end end end _G.arb_raw_list = _arb_raw _G.arb_list = _res _G.arb_prev_list = _res _G.arb_page = 1 _G.arb_building = false end) end if _G.arb_building then imgui.SameLine(0,8*d) imgui.TextColored(imgui.ImVec4(ar,ag,ab,0.7), IC.rot..' ') if not _G.arb_list and _G.arb_prev_list then _G.arb_list = _G.arb_prev_list end imgui.SameLine(0,4*d2) imgui.TextDisabled(_cyr5f('Строю список...')) imgui.Spacing() elseif #(_G.arb_list or {}) == 0 then local _api_n2 = mh_arz_data and #mh_arz_data or 0 local _mkt_n = 0; for _ in pairs(fh_mkt_prices) do _mkt_n=_mkt_n+1 end imgui.TextDisabled(_cyr5f('Нет результатов. API лавок: '.._api_n2..', товаров в базе: '.._mkt_n)) end local arb = _G.arb_list or {} local ARB_PG = 50 local arb_pages = math.max(1, math.ceil(#arb / ARB_PG)) if _G.arb_page > arb_pages then _G.arb_page = arb_pages end local af = (_G.arb_page-1)*ARB_PG+1 local at = math.min(_G.arb_page*ARB_PG, #arb) local list_h = imgui.GetWindowHeight() - imgui.GetCursorPosY() - 45*d2 local _is_s2s = (_G.arb_dir_filter and _G.arb_dir_filter[0]==3) if imgui.BeginChild('##arb_list', imgui.ImVec2(-1,list_h), true) then _dpn1w() local hc = imgui.ImVec4(ar,ag,ab,0.8) if _is_s2s then imgui.Columns(6,'##s2s_hdr',false) local _cw_nm = cw_a*0.20 - 4*d2 local _cw_s = cw_a*0.22 local _cw_r30 = cw_a*0.18 local _cw_b = cw_a*0.22 local _cw_m = cw_a*0.10 local _cw_pct = cw_a - _cw_nm - _cw_s - _cw_r30 - _cw_b - _cw_m imgui.SetColumnWidth(0, _cw_nm) imgui.SetColumnWidth(1, _cw_s) imgui.SetColumnWidth(2, _cw_r30) imgui.SetColumnWidth(3, _cw_b) imgui.SetColumnWidth(4, _cw_m) imgui.SetColumnWidth(5, _cw_pct) imgui.TextColored(hc, _cyr5f(' Товар')); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.4,0.95,0.4,0.9), _cyr5f('Продаёт')); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.9,0.8,0.4,0.9), _cyr5f('Рынок 30д')); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.4,0.7,1,0.9), _cyr5f('Скупает')); imgui.NextColumn() imgui.TextColored(hc, _cyr5f('Маржа')); imgui.NextColumn() imgui.TextColored(hc, _cyr5f('%%')); imgui.NextColumn() imgui.Separator() if #arb==0 then imgui.TextDisabled(_cyr5f(' Лавки не найдены. Сканируйте Обзор -> Просмотренные лавки.')) for _=1,5 do imgui.NextColumn() end end for i=af,at do local a=arb[i]; if not a then break end local mc=a.margin>=0 and imgui.ImVec4(0.3,0.95,0.3,1) or imgui.ImVec4(1,0.4,0.3,1) local tag=mh_get_item_tag(a.nm) local nm_c=imgui.ImVec4(1,1,1,0.9) if tag=='watch' then nm_c=imgui.ImVec4(0.4,0.85,1,1) elseif tag=='skip' then nm_c=imgui.ImVec4(0.5,0.5,0.5,0.6) elseif tag=='fav' then nm_c=imgui.ImVec4(1,0.85,0.1,1) end local tpfx='' if tag=='watch' then tpfx=IC.eye..' ' elseif tag=='skip' then tpfx=IC.ban..' ' elseif tag=='fav' then tpfx=IC.star..' ' end imgui.PushStyleColor(imgui.Col.Text,nm_c) local _cw2=imgui.GetColumnWidth()-4*d2 local _ft=tpfx.._cyr5f(' '..a.nm) local _cx,_cy=imgui.GetCursorPosX(),imgui.GetCursorPosY() if imgui.Selectable('##s2s'..i,false,imgui.SelectableFlags.AllowDoubleClick,imgui.ImVec2(_cw2,0)) then _G.mkt_detail_item=a.nm; _G.mkt_detail_src='cp'; _G.mkt_detail_open=true; _G._dtl_win_init=nil end imgui.SetCursorPos(imgui.ImVec2(_cx,_cy)) imgui.TextUnformatted(_ft) imgui.PopStyleColor() imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.4,0.95,0.4,1), _cyr5f('$'.._kcr3y(a.shop))) imgui.SameLine(0,5*d2) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10*0.78,_G._mh_sb_g or 0.45*0.78,_G._mh_sb_b or 0.10*0.78,0.85)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.22,sb_g*1.22,sb_b*1.22, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.4,1,0.4,1)) if imgui.SmallButton(IC.phone..'##p1_'..i) then local _o=a.owner or '' if _o~='' then lua_thread.create(function() for _p=0,999 do local _ok,_pn=pcall(sampGetPlayerNickname,_p) if _ok and _pn and _pn:lower()==_o:lower() then _G._mh_call_pending_nick=_o:lower(); sampSendChat('/number '.._p); break end end end) end end imgui.PopStyleColor(3) imgui.SameLine(0,4*d2) imgui.PushStyleColor(imgui.Col.Button, _mh_bc((_G._mh_sb_r or 0.10)*0.55,(_G._mh_sb_g or 0.45)*0.55,(_G._mh_sb_b or 0.10)*0.55,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*0.85,sb_g*0.85,sb_b*0.85, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.4,1,0.5,1)) if imgui.SmallButton(IC.gps..'##g1_'..i) then if a.uid then sampSendChat('/findilavka '..a.uid) end end imgui.PopStyleColor(3) local _uid1 = a.uid and _cyr5f(' #'..tostring(a.uid)) or '' imgui.TextColored(imgui.ImVec4(0.55,0.55,0.55,1), _cyr5f(' '..a.owner).._uid1) imgui.NextColumn() if a.mkt30 and a.mkt30 > 0 then local _r30c = a.shop < a.mkt30 and imgui.ImVec4(0.4,0.95,0.4,1) or imgui.ImVec4(1,0.6,0.3,1) imgui.TextColored(_r30c, _cyr5f('$'.._kcr3y(a.mkt30))) else imgui.TextDisabled(u8' вЂ"') end imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.4,0.7,1,1), _cyr5f('$'.._kcr3y(a.mkt))) imgui.SameLine(0,5*d2) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10*0.78,_G._mh_sb_g or 0.45*0.78,_G._mh_sb_b or 0.10*0.78,0.85)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.22,sb_g*1.22,sb_b*1.22, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.4,1,0.4,1)) if imgui.SmallButton(IC.phone..'##p2_'..i) then local _o2=a.owner2 or '' if _o2~='' then lua_thread.create(function() for _p=0,999 do local _ok,_pn=pcall(sampGetPlayerNickname,_p) if _ok and _pn and _pn:lower()==_o2:lower() then _G._mh_call_pending_nick=_o2:lower(); sampSendChat('/number '.._p); break end end end) end end imgui.PopStyleColor(3) imgui.SameLine(0,4*d2) imgui.PushStyleColor(imgui.Col.Button, _mh_bc((_G._mh_sb_r or 0.10)*0.55,(_G._mh_sb_g or 0.45)*0.55,(_G._mh_sb_b or 0.10)*0.55,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*0.85,sb_g*0.85,sb_b*0.85, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.4,1,0.5,1)) if imgui.SmallButton(IC.gps..'##g2_'..i) then if a.uid2 then sampSendChat('/findilavka '..a.uid2) end end imgui.PopStyleColor(3) local _uid2 = a.uid2 and _cyr5f(' #'..tostring(a.uid2)) or '' imgui.TextColored(imgui.ImVec4(0.55,0.55,0.55,1), _cyr5f(' '..(a.owner2 or '?')).._uid2) imgui.NextColumn() imgui.TextColored(mc, _cyr5f('$'.._kcr3y(a.margin))); imgui.NextColumn() imgui.TextColored(mc, _cyr5f(string.format('%.1f%%',a.margin_pct))); imgui.NextColumn() end imgui.Columns(1) else imgui.Columns(9,'##arb_hdr',false) local _cw_inner = imgui.GetWindowContentRegionWidth() local _arb_nm_w = math.floor(_cw_inner * 0.190) local _arb_typ_w = math.floor(_cw_inner * 0.038) local _arb_prc_w = 0 local _arb_mrg_w = 0 local _arb_pct_w = math.floor(_cw_inner * 0.058) local _arb_own_w = math.floor(_cw_inner * 0.120) local _arb_icon_w = 28*d2 local _arb_price_pool = _cw_inner - _arb_nm_w - _arb_typ_w - _arb_pct_w - _arb_own_w - _arb_icon_w*2 _arb_prc_w = math.floor(_arb_price_pool / 3) _arb_mrg_w = _arb_price_pool - _arb_prc_w*2 local cws={_arb_nm_w,_arb_typ_w,_arb_prc_w,_arb_prc_w,_arb_mrg_w,_arb_pct_w,_arb_own_w,_arb_icon_w,_arb_icon_w} for ci,cw2 in ipairs(cws) do imgui.SetColumnWidth(ci-1,cw2) end imgui.TextColored(hc,_cyr5f(' Товар')); imgui.NextColumn() imgui.TextColored(hc,_cyr5f(' Тип')); imgui.NextColumn() do local _df2 = _G.arb_dir_filter and _G.arb_dir_filter[0] or 0 if _df2 == 2 then imgui.TextColored(imgui.ImVec4(0.4,0.95,0.4,0.9),_cyr5f(' Скуп. $')); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.6,0.85,1,0.9),_cyr5f(' Рынок $')); imgui.NextColumn() else imgui.TextColored(imgui.ImVec4(0.6,0.85,1,0.9),_cyr5f(' Лавка $')); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.4,0.95,0.4,0.9),_cyr5f(' Рынок $')); imgui.NextColumn() end end imgui.TextColored(hc,_cyr5f(' Маржа $')); imgui.NextColumn() imgui.TextColored(hc,_cyr5f(' %%')); imgui.NextColumn() imgui.TextColored(hc,_cyr5f(' Овнер')); imgui.NextColumn() imgui.TextColored(hc,IC.phone); imgui.NextColumn() imgui.TextColored(hc,IC.gps); imgui.NextColumn() imgui.Separator() if #arb==0 then imgui.TextDisabled(_cyr5f(' Данных нет. Сначала сканируйте лавки (ВЛАВПОРОБ -> Чужие лавки).')) for _=1,8 do imgui.NextColumn() end end for i=af,at do local a=arb[i]; if not a then break end local mc=a.margin>=0 and imgui.ImVec4(0.3,0.95,0.3,1) or imgui.ImVec4(1,0.4,0.3,1) local tag=mh_get_item_tag(a.nm) local nm_c = imgui.ImVec4(1,1,1,0.9) if tag == 'watch' then nm_c = imgui.ImVec4(0.4,0.85,1,1) elseif tag == 'skip' then nm_c = imgui.ImVec4(0.5,0.5,0.5,0.6) elseif tag == 'fav' then nm_c = imgui.ImVec4(1,0.85,0.1,1) end local tag_prefix = '' if tag == 'watch' then tag_prefix = fa.EYE..' ' elseif tag == 'skip' then tag_prefix = fa.BAN..' ' elseif tag == 'fav' then tag_prefix = fa.STAR..' ' end imgui.PushStyleColor(imgui.Col.Text, nm_c) local _col_w = imgui.GetColumnWidth() - 8*d2 local _full_txt = tag_prefix.._cyr5f(' '..a.nm) local _txt_w = imgui.CalcTextSize(_full_txt).x local _cx = imgui.GetCursorPosX() local _cy = imgui.GetCursorPosY() if imgui.Selectable('##arb'..i, false, imgui.SelectableFlags.AllowDoubleClick, imgui.ImVec2(_col_w, 0)) then _G.mkt_detail_item = a.nm; _G.mkt_detail_src = 'cp'; _G.mkt_detail_open = true end imgui.SetCursorPos(imgui.ImVec2(_cx, _cy)) if _txt_w > _col_w then local _t = os.clock() % (4 + 1) local _scroll = 0 if _t > 1 then _scroll = math.min((_t - 1) / 3 * (_txt_w - _col_w + 8*d2), _txt_w - _col_w + 8*d2) end local _sp = imgui.GetCursorScreenPos() imgui.PushClipRect(imgui.ImVec2(_sp.x, _sp.y - 2), imgui.ImVec2(_sp.x + _col_w, _sp.y + 20*d2), true) imgui.SetCursorPos(imgui.ImVec2(_cx - _scroll, _cy)) imgui.TextUnformatted(_full_txt) imgui.PopClipRect() else imgui.TextUnformatted(_full_txt) end imgui.PopStyleColor() imgui.NextColumn() if a.dir == 'buy' then imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.35,0.70,1,1)) imgui.TextUnformatted(' '..IC.store..IC.rt..IC.chrtl) imgui.PopStyleColor() elseif a.dir == 'sell' then imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1,0.70,0.25,1)) imgui.TextUnformatted(' '..IC.chrtl..IC.rt..IC.store) imgui.PopStyleColor() else imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.75,0.45,1,1)) imgui.TextUnformatted(' '..IC.store..IC.alr..IC.store) imgui.PopStyleColor() end if imgui.IsItemHovered() then local _itip if a.dir == 'buy' then _itip = _cyr5f('Покупаешь в лавке, продаёшь на рынке') elseif a.dir == 'sell' then _itip = _cyr5f('Лавка скупает дороже рынка: купи на рынке, сдай в лавку') else _itip = _cyr5f('Покупаешь у '..a.owner..', продаёшь в '..(a.owner2 or '?')) end imgui.SetTooltip(_itip) end imgui.NextColumn() local shop_c = a.dir == 'sell' and imgui.ImVec4(0.4,0.95,0.4,1) or imgui.ImVec4(0.5,0.8,1,1) local mkt_c = a.dir == 'sell' and imgui.ImVec4(0.5,0.8,1,1) or imgui.ImVec4(0.4,0.95,0.4,1) imgui.TextColored(shop_c, _cyr5f(' $'.._kcr3y(a.shop))); imgui.NextColumn() imgui.TextColored(mkt_c, _cyr5f(' $'.._kcr3y(a.mkt))); imgui.NextColumn() imgui.TextColored(mc, _cyr5f(' $'.._kcr3y(a.margin))); imgui.NextColumn() imgui.TextColored(mc, _cyr5f(string.format(' %.1f%%', a.margin_pct))); imgui.NextColumn() local _uid_s = a.uid and _cyr5f(' #'..tostring(a.uid)) or '' local _uid_s2 = a.uid2 and _cyr5f(' #'..tostring(a.uid2)) or '' if a.dir == 'shop2shop' and a.owner2 then imgui.TextColored(imgui.ImVec4(0.65,0.65,0.65,1), _cyr5f(' '..a.owner).._uid_s) imgui.SameLine(0,2*d2) imgui.TextColored(imgui.ImVec4(0.4,0.4,0.4,1), '>') imgui.SameLine(0,2*d2) imgui.TextColored(imgui.ImVec4(0.75,0.45,1,1), _cyr5f(a.owner2).._uid_s2) else imgui.TextColored(imgui.ImVec4(0.7,0.7,0.7,1), _cyr5f(' '..a.owner).._uid_s) end imgui.NextColumn() imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10*0.78,_G._mh_sb_g or 0.45*0.78,_G._mh_sb_b or 0.10*0.78,0.85)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.22,sb_g*1.22,sb_b*1.22, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.4,1,0.4,1)) if imgui.SmallButton(IC.phone..'##call'..i) then local _owner_nick = a.owner or '' if _owner_nick ~= '' and _owner_nick ~= '?' then lua_thread.create(function() local _found_id = nil for _pid = 0, 999 do local _ok, _pnick = pcall(sampGetPlayerNickname, _pid) if _ok and _pnick and _pnick:lower() == _owner_nick:lower() then _found_id = _pid; break end end if _found_id then _G._mh_call_pending_nick = _owner_nick:lower() sampSendChat('/number ' .. _found_id) mh_notify('[MH] {aaffaa}Звонок: ' .. _owner_nick, 0xFFFFFF) else mh_notify('[MH] {ffaa00}' .. _owner_nick .. ' не в сети (офлайн)', 0xFFFFFF) end end) end end imgui.PopStyleColor(3) imgui.NextColumn() imgui.PushStyleColor(imgui.Col.Button, _mh_bc((_G._mh_sb_r or 0.10)*0.55,(_G._mh_sb_g or 0.45)*0.55,(_G._mh_sb_b or 0.10)*0.55,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*0.85,sb_g*0.85,sb_b*0.85, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.4,1,0.5,1)) if imgui.SmallButton(IC.gps..'##arbgps'..i) then local _uid = a.uid if _uid then sampSendChat('/findilavka '.._uid) end end imgui.PopStyleColor(3) imgui.NextColumn() end end imgui.Columns(1) imgui.EndChild() end imgui.Spacing() local pw5 = 36*d2 if imgui.Button(IC.ll..'##arbpp', imgui.ImVec2(pw5,0)) then _G.arb_page=1 end imgui.SameLine(0,3*d2) if imgui.Button(IC.al..'##arbpr', imgui.ImVec2(pw5,0)) then if _G.arb_page>1 then _G.arb_page=_G.arb_page-1 end end imgui.SameLine(0,5*d2) imgui.TextColored(imgui.ImVec4(ar,ag,ab,1), _cyr5f('Стр. '.._G.arb_page..'/'..arb_pages..' ('..#arb..' товаров)')) imgui.SameLine(0,5*d2) if imgui.Button(IC.ar..'##arbnx', imgui.ImVec2(pw5,0)) then if _G.arb_page= 15 if imgui.SmallButton(u8'Повтор##cloud_retry') then if _cd_oke then _G._mh_cloud_cd = _now_cde _dfn1c = nil local _re_srv = ARZ_SERVERS[_G.arz_srv_sel and (_G.arz_srv_sel[0]+1) or 1] local _re_id = _re_srv and _re_srv.id or -1 lua_thread.create(function() wait(0); _pkw2y(_re_id) end) end end elseif _mvr4p then if not _G._cloud_cnt_cache or _G._cloud_cnt_sz ~= #mh_arz_data then _G._cloud_cnt_sz = #mh_arz_data local _cc = 0 for _, lv in ipairs(mh_arz_data) do if lv._mh_cloud then _cc = _cc + 1 end end _G._cloud_cnt_cache = _cc end imgui.TextColored(imgui.ImVec4(0.5,0.9,0.5,1), IC.cld .. ' ' .. _cyr5f('MH Cloud: ' .. _G._cloud_cnt_cache .. ' лавок')) if imgui.SmallButton(IC.rot .. '##cloud_refresh') then local _now_cd2 = os.clock() if not _G._mh_cloud_cd or (_now_cd2 - _G._mh_cloud_cd) >= 15 then _G._mh_cloud_cd = _now_cd2 local _new_arz = {} for _, lv in ipairs(mh_arz_data) do if not lv._mh_cloud then table.insert(_new_arz, lv) end end mh_arz_data = _new_arz _mvr4p = false local _rf_srv = ARZ_SERVERS[_G.arz_srv_sel and (_G.arz_srv_sel[0]+1) or 1] local _rf_id = _rf_srv and _rf_srv.id or -1 lua_thread.create(function() wait(0); _pkw2y(_rf_id) end) end end else imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1), IC.cld .. ' ' .. u8'MH Cloud: не загружен') imgui.SameLine(0, 8*d) local _now_cd3 = os.clock() local _cd_ok3 = not _G._mh_cloud_cd or (_now_cd3 - _G._mh_cloud_cd) >= 15 local _btn_lbl3 = _cd_ok3 and u8'Загрузить##cloud_load' or u8'Загрузить (кд)##cloud_load' if imgui.SmallButton(_btn_lbl3) then if _cd_ok3 then _G._mh_cloud_cd = _now_cd3 _dfn1c = nil local _ld_srv = ARZ_SERVERS[_G.arz_srv_sel and (_G.arz_srv_sel[0]+1) or 1] local _ld_id = _ld_srv and _ld_srv.id or -1 lua_thread.create(function() wait(0); _pkw2y(_ld_id) end) end end end end imgui.Spacing() imgui.Separator() imgui.Spacing() local combo_w = cw_arz * 0.42 local btn_ref_w = 120 * d imgui.PushItemWidth(combo_w) imgui.PushStyleColor(imgui.Col.FrameBg, imgui.ImVec4(bg + .07, bg + .065, bg + .035, _G._mh_wa or 1)) if imgui.Combo(u8'##arz_srv', _G.arz_srv_sel, _G.arz_srv_ptr, _G.arz_srv_count) then _G.arz_page = 1 _G.arz_cache_key = nil _G.arz_detail = nil if not mh_arz_loading then local new_srv_id = ARZ_SERVERS[_G.arz_srv_sel[0] + 1] and ARZ_SERVERS[_G.arz_srv_sel[0] + 1].id or -1 lua_thread.create(function() wait(0); _xtj6b(new_srv_id) end) end end imgui.PopStyleColor() imgui.PopItemWidth() imgui.SameLine(0, 6 * d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(ar * 0.22, ag * 0.22, ab * 0.12, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(ar * 0.45, ag * 0.45, ab * 0.25, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(ar * 0.65, ag * 0.65, ab * 0.35, 1)) local loading_any = mh_arz_loading or mh_arz_items_loading if loading_any then imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.45, 0.45, 0.45, 1)) end if imgui.Button(IC.rot .. ' ' .. u8'Обновить##arz_refresh', imgui.ImVec2(btn_ref_w, 0)) then local _now_cd = os.clock() local _cd_ok = not _G._mh_refresh_cd or (_now_cd - _G._mh_refresh_cd) >= 15 if not loading_any and _cd_ok then _G._mh_refresh_cd = _now_cd _G._mh_session_tok = '' lua_thread.create(function() _mh_check_update(true) local _sw = 0 while (_G._mh_session_tok or '') == '' and _sw < 50 do wait(100); _sw = _sw + 1 end end) local sel_srv_id = ARZ_SERVERS[_G.arz_srv_sel[0] + 1] and ARZ_SERVERS[_G.arz_srv_sel[0] + 1].id or -1 _G.arz_cache_key = nil _G.arz_page = 1 _G.arz_detail = nil _xtj6b(sel_srv_id) end end if loading_any then imgui.PopStyleColor() end if imgui.IsItemHovered() and _G._mh_refresh_cd then local _cd_left = math.ceil(15 - (os.clock() - _G._mh_refresh_cd)) if _cd_left > 0 then imgui.SetTooltip(_cyr5f('Кулдаун: ' .. _cd_left .. ' сек.')) end end imgui.PopStyleColor(3) imgui.SameLine(0, 6 * d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.08, 0.18, 0.32, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.12, 0.28, 0.50, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(0.16, 0.38, 0.65, 1)) if imgui.Button(IC.gps .. ' ' .. u8'Авто##arz_auto', imgui.ImVec2(80 * d, 0)) then local idx = _mpf7d() _G.arz_srv_sel[0] = idx _G.arz_cache_key = nil _G.arz_page = 1 _G.arz_detail = nil end imgui.PopStyleColor(3) imgui.SameLine(0, 6 * d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.08,0.16,0.28,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.14,0.26,0.46,1)) if imgui.Button(IC.gear .. u8'##api_filter_btn', imgui.ImVec2(32*d, 0)) then _G._api_filter_open = not (_G._api_filter_open or false) end imgui.PopStyleColor(2) if imgui.IsItemHovered() then imgui.SetTooltip(u8'Фильтр API-серверов') end if _G._api_filter_open then imgui.Separator() imgui.TextColored(imgui.ImVec4(0.5,0.8,1,1), u8'Источники данных:') imgui.Spacing() if not settings.api_sources then settings.api_sources={mh=true,mcr=true} end local _as = settings.api_sources local _en_mh = (_as.mh ~= false) local _cb_mh = imgui.new.bool(_en_mh) if imgui.Checkbox(u8'MH Cloud##apisrc_mh', _cb_mh) then _as.mh = _cb_mh[0] or false _wfn7p() end imgui.Spacing() if imgui.SmallButton(u8'Скрыть##apif_close') then _G._api_filter_open = false end imgui.Separator() end if #mh_arz_data == 0 and not mh_arz_loading then imgui.SameLine(0, 10 * d) imgui.TextColored(imgui.ImVec4(1, 0.75, 0.2, 0.8), u8'<- нажмите Обновить') end imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar * 0.65, ag * 0.65, ab * 0.65, 1), IC.srch) imgui.SameLine(0, 5 * d) imgui.PushItemWidth(cw_arz * 0.38 - 32*d) imgui.PushStyleColor(imgui.Col.FrameBg, imgui.ImVec4(bg + .08, bg + .075, bg + .04, _G._mh_wa or 1)) if imgui.InputTextWithHint(u8'##arz_srch', u8'Поиск товара...', _G.arz_srch, 256) then do local _r3 = u8:decode(ffi.string(_G.arz_srch)) local _ok3,_cp3 = pcall(function() return require('encoding').CP1251:encode(_r3) end) local _s3 = (_ok3 and _cp3 or _r3):lower() _G.arz_srch_s = _s3:gsub('[А-Я]',function(c) return string.char(string.byte(c)+32) end):match('^%s*(.-)%s*$') end _G.arz_page = 1; _G.arz_cache_key = nil; _G.arz_detail = nil end imgui.PopStyleColor(); imgui.PopItemWidth() imgui.SameLine(0, 3*d) local _has_arz_srch = ffi.string(_G.arz_srch) ~= '' imgui.PushStyleColor(imgui.Col.Button, _has_arz_srch and imgui.ImVec4(0.38,0.08,0.08,1) or imgui.ImVec4(0.12,0.12,0.12,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.55,0.12,0.12, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, _has_arz_srch and imgui.ImVec4(1,0.4,0.4,1) or imgui.ImVec4(0.3,0.3,0.3,1)) if imgui.Button(IC.x..'##arzsrchclr', imgui.ImVec2(28*d, 0)) and _has_arz_srch then ffi.fill(_G.arz_srch, 256, 0) _G.arz_srch_s = ''; _G.arz_page = 1; _G.arz_cache_key = nil; _G.arz_detail = nil end imgui.PopStyleColor(3) local sort_labels = { u8'По умолч.##as0', u8'Продаёт##as1', u8'Скупает##as2', u8'А-Я##as3' } local sw = (cw_arz - cw_arz * 0.38 - 30 * d - 10 * d) / 4 for si = 0, 3 do if si > 0 then imgui.SameLine(0, 4 * d) end local is_act = (_G.arz_sort == si) if is_act then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(ar * 0.55, ag * 0.55, ab * 0.3, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(ar * 0.75, ag * 0.75, ab * 0.4, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(ar, ag, ab, 1)) end if imgui.Button(sort_labels[si + 1], imgui.ImVec2(sw, 0)) then _G.arz_sort = si _G.arz_cache_key = nil _G.arz_page = 1 end if is_act then imgui.PopStyleColor(3) end end imgui.Spacing() imgui.Separator() imgui.Spacing() if _G.arz_detail then _G._arz_shop_win_open = true; _G._shop_win_init = nil else local cur_srv_id = ARZ_SERVERS[_G.arz_srv_sel[0] + 1] and ARZ_SERVERS[_G.arz_srv_sel[0] + 1].id or -1 local srch_active = (_G.arz_srch_s or '') ~= '' local cache_key = tostring(cur_srv_id) .. '|' .. (_G.arz_srch_s or '') .. '|' .. tostring(_G.arz_sort) .. '|' .. tostring(srch_active) if _G.arz_cache_key ~= cache_key then _G.arz_cache_key = cache_key if srch_active then _G.arz_cache_list = _tyk5r(cur_srv_id, _G.arz_srch_s or '', _G.arz_sort) else _G.arz_cache_list = _hnw8x(cur_srv_id, _G.arz_srch_s or '', _G.arz_sort) end end local all_lavki = _G.arz_cache_list local ARZ_PER_P = 30 local total_p = math.max(1, math.ceil(#all_lavki / ARZ_PER_P)) if _G.arz_page > total_p then _G.arz_page = total_p end local from_i = (_G.arz_page - 1) * ARZ_PER_P + 1 local to_i = math.min(_G.arz_page * ARZ_PER_P, #all_lavki) local hc2 = imgui.ImVec4(ar * 0.5, ag * 0.5, ab * 0.3, 1) if srch_active then imgui.Columns(6, '##arz_items_hdr', false) local cwI = {cw_arz*0.22, cw_arz*0.08, cw_arz*0.15, cw_arz*0.07, cw_arz*0.25, cw_arz*0.21} for ci, v in ipairs(cwI) do imgui.SetColumnWidth(ci-1, v) end imgui.TextColored(hc2, u8' Товар'); imgui.NextColumn() imgui.TextColored(hc2, u8' Тип'); imgui.NextColumn() imgui.TextColored(hc2, u8' Цена'); imgui.NextColumn() imgui.TextColored(hc2, u8' Кол-во'); imgui.NextColumn() imgui.TextColored(hc2, u8' Лавка'); imgui.NextColumn() imgui.TextColored(hc2, u8' GPS'); imgui.NextColumn() imgui.Columns(1) else imgui.Columns(6, '##arz_list_hdr', false) local cw6 = {cw_arz*0.26, cw_arz*0.14, cw_arz*0.12, cw_arz*0.14, cw_arz*0.14, cw_arz*0.18} for ci, v in ipairs(cw6) do imgui.SetColumnWidth(ci-1, v) end imgui.TextColored(hc2, u8' Владелец'); imgui.NextColumn() imgui.TextColored(hc2, u8' Сервер'); imgui.NextColumn() imgui.TextColored(hc2, u8' ID'); imgui.NextColumn() imgui.TextColored(hc2, u8' Продаёт'); imgui.NextColumn() imgui.TextColored(hc2, u8' Скупает'); imgui.NextColumn() imgui.TextColored(hc2, u8' Действие'); imgui.NextColumn() imgui.Columns(1) end imgui.Separator() local list_h_main = imgui.GetWindowHeight() - imgui.GetCursorPosY() - 42 * d imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(bg + .025, bg + .022, bg + .012, _G._mh_wa or 1)) if imgui.BeginChild('##arz_main_list', imgui.ImVec2(-1, list_h_main), false) then _dpn1w() if #all_lavki == 0 then imgui.Spacing() if #mh_arz_data == 0 then imgui.TextDisabled(u8' Нет данных — нажмите «Обновить» для загрузки') elseif srch_active then imgui.TextDisabled(u8' Товары не найдены. Попробуйте другой запрос.') else imgui.TextDisabled(u8' Лавки не найдены по заданным фильтрам.') end end if srch_active then local cwI = {cw_arz*0.22, cw_arz*0.08, cw_arz*0.15, cw_arz*0.07, cw_arz*0.25, cw_arz*0.21} for ri = from_i, to_i do local it = all_lavki[ri] if not it then break end local row_col = (ri % 2 == 0) and imgui.ImVec4(bg+.05, bg+.045, bg+.025, 0.6) or imgui.ImVec4(0, 0, 0, 0) imgui.PushStyleColor(imgui.Col.ChildBg, row_col) imgui.Columns(6, '##arzir'..ri, false) for ci, v in ipairs(cwI) do imgui.SetColumnWidth(ci-1, v) end local rsp = imgui.GetCursorScreenPos() local rlh = imgui.GetTextLineHeight() local nm_str = u8(' ' .. it.nm) local ntw = imgui.CalcTextSize(nm_str).x local ncw = cwI[1] - 6 imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0,0,0,0)) if imgui.Selectable('##arzis'..ri, false, imgui.SelectableFlags.AllowDoubleClick, imgui.ImVec2(0, rlh+2)) then local _snm = it.nm:match('^(.-)%s*%(') or it.nm if _snm and _snm ~= '' then _G.mkt_detail_item = _snm:match('^%s*(.-)%s*$') if not fh_mkt_prices[_G.mkt_detail_item] then _G.mkt_detail_item = it.nm end _G.mkt_detail_src = 'cp' _G.mkt_detail_open = true end end imgui.PopStyleColor() local dlI = imgui.GetWindowDrawList() dlI:PushClipRect(rsp, imgui.ImVec2(rsp.x+ncw, rsp.y+rlh+2), true) local n_off = 0 if ntw > ncw then local nsd = ntw - ncw + 8 local nspd = 1.8 local nspt = nsd / 38 + 2 * nspd local nsph = math.fmod(imgui.GetTime() + ri * 0.5, nspt) if nsph > nspd then n_off = math.min((nsph - nspd) * 38, nsd) end if nsph >= nspt - nspd then n_off = nsd end end local _itag_col = 0xFFFFFFFF local _itag = it.tag or mh_get_item_tag(it.base_nm or it.nm) if _itag == 'fav' then _itag_col = 0xFFFFD700 elseif _itag == 'skip' then _itag_col = 0xFF888888 elseif _itag == 'watch' then _itag_col = 0xFF6ACFFF end local _tpfx = '' if _itag == 'fav' then _tpfx = fa.STAR..' ' elseif _itag == 'skip' then _tpfx = fa.BAN..' ' elseif _itag == 'watch' then _tpfx = fa.EYE..' ' end local nm_str_tagged = _tpfx ~= '' and (_tpfx..nm_str:match('^%s*(.*)')) or nm_str dlI:AddText(imgui.ImVec2(rsp.x - n_off, rsp.y), _itag_col, nm_str_tagged) dlI:PopClipRect() imgui.NextColumn() if it.op == 'sell' then imgui.TextColored(imgui.ImVec4(lp_r, lp_g, lp_b, 1), IC.tag .. u8(' Прод.')) else imgui.TextColored(imgui.ImVec4(0.4, 0.8, 1.0, 1), IC.store .. u8(' Скуп.')) end imgui.NextColumn() local currency = it.is_vc and 'VC$' or 'SA$' if it.price then imgui.TextColored(imgui.ImVec4(lp_r, lp_g, lp_b, 1), u8(' ' .. _jsb6t(it.price) .. ' ' .. currency)) else imgui.TextDisabled(u8' —') end imgui.NextColumn() if it.cnt then imgui.Text(u8(' ' .. tostring(it.cnt) .. ' шт.')) else imgui.TextDisabled(u8' —') end imgui.NextColumn() do local _oc = it.is_prem and imgui.ImVec4(1,0.84,0,1) or imgui.ImVec4(0.55,0.75,0.55,1) local _olbl = u8(' #'..tostring(it.lv_uid)..' '..it.lv_owner) imgui.PushStyleColor(imgui.Col.Text, _oc) if imgui.Selectable(_olbl..'##arzown'..ri, false, imgui.SelectableFlags.AllowDoubleClick, imgui.ImVec2(cwI[5]-4, 0)) then if it.lv_ref then _G.arz_detail = it.lv_ref _G.arz_detail_tab = 0 end end imgui.PopStyleColor() if it.is_prem then imgui.SameLine(0,2*d); imgui.TextColored(imgui.ImVec4(1,0.84,0,1), IC.star) end if (it.lv_updated_at or 0) > 0 then local _age = os.time() - it.lv_updated_at local _tl = _age < 86400 and os.date('%H:%M', it.lv_updated_at) or os.date('%d.%m', it.lv_updated_at) imgui.SameLine(0, 3*d) imgui.TextColored(imgui.ImVec4(0.45,0.45,0.45,1), IC.clk..' '.._tl) end end imgui.NextColumn() if (it.lv_updated_at or 0) > 0 then local _age2 = os.time() - it.lv_updated_at local _tl2 = _age2 < 86400 and os.date('%H:%M', it.lv_updated_at) or os.date('%d.%m', it.lv_updated_at) imgui.TextColored(imgui.ImVec4(0.45,0.45,0.45,1), IC.clk..' '.._tl2) imgui.SameLine(0, 4*d) end imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10*0.78,_G._mh_sb_g or 0.45*0.78,_G._mh_sb_b or 0.10*0.78,0.85)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.22,sb_g*1.22,sb_b*1.22, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.4, 1.0, 0.4, 1)) if imgui.SmallButton(IC.phone .. '##igtel' .. ri) then local _on = it.lv_owner or '' if _on ~= '' and _on ~= '?' then lua_thread.create(function() for _p = 0, 999 do local _ok, _pn = pcall(sampGetPlayerNickname, _p) if _ok and _pn and _pn:lower() == _on:lower() then _G._mh_call_pending_nick = _on:lower() sampSendChat('/number ' .. _p) break end end end) end end imgui.PopStyleColor(3) imgui.SameLine(0, 4 * d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc((_G._mh_sb_r or 0.10)*0.50,(_G._mh_sb_g or 0.45)*0.50,(_G._mh_sb_b or 0.10)*0.50,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*0.72,sb_g*0.72,sb_b*0.72, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(0.14, 0.48, 0.20, 1)) if imgui.SmallButton(IC.gps .. '##igps' .. ri) then sampSendChat('/findilavka ' .. tostring(it.lv_uid)) end imgui.PopStyleColor(3) imgui.NextColumn() imgui.Columns(1) imgui.PopStyleColor() end else local cw6 = {cw_arz*0.26, cw_arz*0.14, cw_arz*0.12, cw_arz*0.14, cw_arz*0.14, cw_arz*0.18} for ri = from_i, to_i do local row = all_lavki[ri] if not row then break end local lv = row.lv local srv_nm = _dzc2g(lv.serverId or -1) local row_col = (ri % 2 == 0) and imgui.ImVec4(bg+.05, bg+.045, bg+.025, 0.6) or imgui.ImVec4(0, 0, 0, 0) imgui.PushStyleColor(imgui.Col.ChildBg, row_col) imgui.Columns(6, '##arz_row' .. ri, false) for ci, v in ipairs(cw6) do imgui.SetColumnWidth(ci-1, v) end local _is_prow = row.is_prem local rsp = imgui.GetCursorScreenPos() local rlh = imgui.GetTextLineHeight() local owner_str = u8(' ' .. (lv.username or '?')) local otw = imgui.CalcTextSize(owner_str).x local ocw = cw6[1] - (_is_prow and 22*d or 6) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0,0,0,0)) if imgui.Selectable('##arzsel'..ri, false, imgui.SelectableFlags.AllowDoubleClick, imgui.ImVec2(ocw, rlh+2)) then _G.arz_detail = lv _G.arz_detail_tab = 0 end imgui.PopStyleColor() local dl2 = imgui.GetWindowDrawList() dl2:PushClipRect(rsp, imgui.ImVec2(rsp.x + ocw, rsp.y + rlh + 2), true) local o_off = 0 if otw > ocw then local osd = otw - ocw + 8 local ospd = 1.8 local ospt = osd / 38 + 2 * ospd local osph = math.fmod(imgui.GetTime() + ri * 0.7, ospt) if osph > ospd then o_off = math.min((osph - ospd) * 38, osd) end if osph >= ospt - ospd then o_off = osd end end local _oc = _is_prow and 0xFFD700FF or 0xFFFFFFFF dl2:AddText(imgui.ImVec2(rsp.x - o_off, rsp.y), _oc, owner_str) if _is_prow then dl2:AddRect( imgui.ImVec2(rsp.x - 2, rsp.y - 1), imgui.ImVec2(rsp.x + cw6[1] - 4, rsp.y + rlh + 2), 0xFFD700CC, 3.0 ) end dl2:PopClipRect() if _is_prow then imgui.SameLine(0, 3*d) imgui.TextColored(imgui.ImVec4(1, 0.84, 0, 1), IC.star) end imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.55, 0.75, 0.55, 1), u8(' ' .. srv_nm)) imgui.NextColumn() imgui.TextDisabled(u8(' #' .. tostring(lv.LavkaUid or '?'))) imgui.NextColumn() if row.sell_cnt > 0 then imgui.TextColored(imgui.ImVec4(lp_r, lp_g, lp_b, 1), u8(' ' .. row.sell_cnt .. ' поз.')) else imgui.TextDisabled(u8' —') end imgui.NextColumn() if row.buy_cnt > 0 then imgui.TextColored(imgui.ImVec4(0.4, 0.8, 1.0, 1), u8(' ' .. row.buy_cnt .. ' поз.')) else imgui.TextDisabled(u8' —') end imgui.NextColumn() do local _rts = lv._mh_updated_at or 0 if _rts > 0 then local _rage = os.time() - _rts local _rtl = _rage < 86400 and os.date('%H:%M', _rts) or os.date('%d.%m', _rts) imgui.TextColored(imgui.ImVec4(0.45,0.45,0.45,1), IC.clk..' '.._rtl) imgui.SameLine(0, 4*d) end end imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10*0.78,_G._mh_sb_g or 0.45*0.78,_G._mh_sb_b or 0.10*0.78,0.85)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.22,sb_g*1.22,sb_b*1.22, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.4, 1.0, 0.4, 1)) if imgui.SmallButton(IC.phone .. '##arztel' .. ri) then local _on = lv.username or '' if _on ~= '' and _on ~= '?' then lua_thread.create(function() for _p = 0, 999 do local _ok, _pn = pcall(sampGetPlayerNickname, _p) if _ok and _pn and _pn:lower() == _on:lower() then _G._mh_call_pending_nick = _on:lower() sampSendChat('/number ' .. _p) break end end end) end end imgui.PopStyleColor(3) imgui.SameLine(0, 4 * d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc((_G._mh_sb_r or 0.10)*0.50,(_G._mh_sb_g or 0.45)*0.50,(_G._mh_sb_b or 0.10)*0.50,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*0.72,sb_g*0.72,sb_b*0.72, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(0.14, 0.48, 0.20, 1)) if imgui.SmallButton(IC.gps .. '##arzgps' .. ri) then sampSendChat('/findilavka ' .. tostring(lv.LavkaUid or 1)) end imgui.PopStyleColor(3) imgui.NextColumn() imgui.Columns(1) imgui.PopStyleColor() end end imgui.EndChild() end imgui.PopStyleColor() imgui.Spacing() local pw_arz = 38 * d local count_label = srch_active and u8('Стр. ' .. _G.arz_page .. '/' .. total_p .. ' (' .. #all_lavki .. ' товаров)') or u8('Стр. ' .. _G.arz_page .. '/' .. total_p .. ' (' .. #all_lavki .. ' лавок)') imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), count_label) imgui.SameLine(0, 8 * d) if imgui.Button(IC.ll .. '##arz_fp', imgui.ImVec2(pw_arz, 0)) then _G.arz_page = 1 end imgui.SameLine(0, 3 * d) if imgui.Button(IC.al .. '##arz_pp', imgui.ImVec2(pw_arz, 0)) then if _G.arz_page > 1 then _G.arz_page = _G.arz_page - 1 end end imgui.SameLine(0, 3 * d) if imgui.Button(IC.ar .. '##arz_np', imgui.ImVec2(pw_arz, 0)) then if _G.arz_page < total_p then _G.arz_page = _G.arz_page + 1 end end imgui.SameLine(0, 3 * d) if imgui.Button(IC.rr .. '##arz_lp', imgui.ImVec2(pw_arz, 0)) then _G.arz_page = total_p end end imgui.EndTabItem() end if imgui.BeginTabItem(IC.wh .. ' ' .. u8'Просмотренные##sub_other') then local cw_os = imgui.GetWindowContentRegionWidth() local shops_cnt = 0; for _ in pairs(fh_other_shops) do shops_cnt = shops_cnt + 1 end do local _now = os.time() if not _G._os_clean_t then _G._os_clean_t = 0 end if (_now - _G._os_clean_t) >= 60 then _G._os_clean_t = _now local _stale = 2 * 3600 local _cleaned = 0 for k, v in pairs(fh_other_shops) do local _ts = v.ts or 0 if _ts > 0 and (_now - _ts) > _stale then fh_other_shops[k] = nil _cleaned = _cleaned + 1 end end if _cleaned > 0 then settings.other_shops = fh_other_shops _wfn7p() end end end if fh_other_shop_scanning and not fh_other_shop_cur then fh_other_shop_scanning = false fh_other_shop_price_tds = {} fh_other_dlg_signal = nil end if fh_other_shop_scanning and fh_other_shop_cur then local _paused = fh_player_dlg_open local _sc = fh_other_scan_done local _st = fh_other_scan_total local _pause_str = _paused and (IC.paus .. ' {ПАУЗА} ') or '' local _prog_str = _st > 0 and (' [' .. _sc .. '/' .. _st .. ']') or '' local _col = _paused and imgui.ImVec4(0.4,0.7,1,1) or imgui.ImVec4(1,0.75,0,1) imgui.TextColored(_col, IC.rot .. ' ' .. _cyr5f( (_paused and 'ПАУЗА — закройте окно ' or 'Скан: ') .. (fh_other_shop_cur.owner or '?') .. ' #' .. tostring(fh_other_shop_cur.shop_num or '?') .. _prog_str)) if _st > 0 then local _frac = _paused and -1 * os.clock() or (_sc / _st) imgui.ProgressBar(_frac, imgui.ImVec2(cw_os * 0.55, 5*d), '') imgui.SameLine(0, 8*d) end imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.50,0.10,0.10,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.75,0.15,0.15, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(0.95,0.20,0.20,1)) if imgui.Button(IC.circs..' '.._cyr5f('Стоп##os_stop'), imgui.ImVec2(0,0)) then fh_other_shop_scanning = false fh_other_dlg_signal = nil fh_other_shop_cur = nil fh_other_shop_price_tds = {} mh_notify('[MH] {ff4444}Скан остановлен.', 0xFFFFFF) end imgui.PopStyleColor(3) else imgui.TextDisabled(_cyr5f(' Сохранёно лавок: ' .. shops_cnt)) end imgui.TextDisabled(u8' Работает автомат: подойдите к чужой лавке и откройте меню товаров') imgui.Spacing() if not _G.os_srch_buf then _G.os_srch_buf = imgui.new.char[128]('') end if not _G.os_srch then _G.os_srch = '' end imgui.PushItemWidth(-1) if imgui.InputTextWithHint('##os_srch', _cyr5f('Поиск по товару, нику, лавке...'), _G.os_srch_buf, 128) then _G.os_srch = u8:decode(ffi.string(_G.os_srch_buf)):lower() end imgui.PopItemWidth() imgui.Separator() local os_list = {} for key, shop in pairs(fh_other_shops) do if type(key) ~= 'string' or type(shop) ~= 'table' then goto os_next end local match = false if _G.os_srch == '' or #_G.os_srch < 3 then match = true else if key:lower():find(_G.os_srch,1,true) or (shop.owner or ''):lower():find(_G.os_srch,1,true) then match = true end if not match then for _, it in ipairs(shop.sell_items or {}) do if it.name:lower():find(_G.os_srch,1,true) then match=true; break end end end if not match then for _, it in ipairs(shop.buy_items or {}) do if it.name:lower():find(_G.os_srch,1,true) then match=true; break end end end end if match then table.insert(os_list, {key=key, shop=shop}) end ::os_next:: end table.sort(os_list, function(a,b) return (a.shop.dt or '') > (b.shop.dt or '') end) local panel_h_os = imgui.GetWindowHeight() - 150*d local left_w_os = math.floor(cw_os * 0.38) local right_w_os = cw_os - left_w_os - 8*d if not _G.os_selected_key then _G.os_selected_key = nil end if not _G.os_selected_tab then _G.os_selected_tab = 'sell' end if imgui.BeginChild('##os_left', imgui.ImVec2(left_w_os, panel_h_os), true) then _dpn1w() if #os_list == 0 then imgui.TextDisabled(_cyr5f(' Пусто.')) imgui.TextDisabled(_cyr5f(' Подойдите к чужой лавке')) imgui.TextDisabled(_cyr5f(' и откройте меню товаров')) end for _, os_e in ipairs(os_list) do local shop = os_e.shop local is_sel = (_G.os_selected_key == os_e.key) local lbl = _cyr5f((shop.owner or '?') .. ' #' .. tostring(shop.shop_num or '?')) local sub = _cyr5f(' ' .. (shop.dt or '') .. ' ' .. #shop.sell_items .. ' Прод. / ' .. #shop.buy_items .. ' Скуп.') if is_sel then imgui.PushStyleColor(imgui.Col.Header, imgui.ImVec4(ar*0.3, ag*0.3, ab*0.3, 1)) imgui.PushStyleColor(imgui.Col.HeaderHovered, imgui.ImVec4(ar*0.5, ag*0.5, ab*0.5, 1)) end if imgui.Selectable(lbl..'##osshop_'..os_e.key, is_sel, 0, imgui.ImVec2(0,0)) then _G.os_selected_key = os_e.key end if is_sel then imgui.PopStyleColor(2) end imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.5,0.5,0.5,1)) imgui.TextUnformatted(sub) imgui.PopStyleColor() imgui.Separator() end imgui.EndChild() end imgui.SameLine(0, 8*d) if imgui.BeginChild('##os_right', imgui.ImVec2(right_w_os, panel_h_os), true) then _dpn1w() local sel_shop = _G.os_selected_key and fh_other_shops[_G.os_selected_key] if not sel_shop then imgui.TextDisabled(_cyr5f(' Тут будут товары')) imgui.TextDisabled(_cyr5f(' Выберите лавку слева')) else imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f((sel_shop.owner or '?') .. ' Лавка #' .. tostring(sel_shop.shop_num or '?') .. ' ' .. (sel_shop.dt or ''))) imgui.SameLine(0, 6*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc((_G._mh_sb_r or 0.10)*0.55,(_G._mh_sb_g or 0.45)*0.55,(_G._mh_sb_b or 0.10)*0.55,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*0.85,sb_g*0.85,sb_b*0.85, _G._mh_wa or 1)) if imgui.SmallButton(IC.gps..' GPS##osgps') then local _snum = sel_shop.shop_num if _snum then sampSendChat('/findilavka '.._snum) end end imgui.PopStyleColor(2) imgui.SameLine(0, 4*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10*0.78,_G._mh_sb_g or 0.45*0.78,_G._mh_sb_b or 0.10*0.78,0.85)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.22,sb_g*1.22,sb_b*1.22, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.4,1,0.4,1)) if imgui.SmallButton(IC.phone..'##oscall') then local _owner_nick = sel_shop.owner or '' if _owner_nick ~= '' then lua_thread.create(function() local _fid = nil for _pid = 0, 999 do local _ok, _pn = pcall(sampGetPlayerNickname, _pid) if _ok and _pn and _pn:lower()==_owner_nick:lower() then _fid=_pid; break end end if _fid then _G._mh_call_pending_nick = _owner_nick:lower() sampSendChat('/number '.._fid) mh_notify('[MH] {aaffaa}Звонок: '.._owner_nick, 0xFFFFFF) else mh_notify('[MH] {ffaa00}'.._owner_nick..' не в сети', 0xFFFFFF) end end) end end imgui.PopStyleColor(3) imgui.SameLine(0, 6*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.45,0.08,0.08,1)) if imgui.SmallButton(IC.trash..'##osdel') then _rcw6d(_G.os_selected_key) _G.os_selected_key = nil end imgui.PopStyleColor() imgui.Separator() local tw = (right_w_os - 20*d) / 2 imgui.PushStyleColor(imgui.Col.Button, _G.os_selected_tab=='sell' and imgui.ImVec4(sb_r, sb_g, sb_b, 1) or imgui.ImVec4(0.1,0.1,0.1,1)) if imgui.Button(IC.tag..' '.._cyr5f('Продаёт (' .. #sel_shop.sell_items .. ')##ostabs'), imgui.ImVec2(tw,0)) then _G.os_selected_tab = 'sell' end imgui.PopStyleColor() imgui.SameLine(0, 4*d) imgui.PushStyleColor(imgui.Col.Button, _G.os_selected_tab=='buy' and imgui.ImVec4(0,0.25,0.5,1) or imgui.ImVec4(0.1,0.1,0.1,1)) if imgui.Button(IC.cart..' '.._cyr5f('Скупает (' .. #sel_shop.buy_items .. ')##ostabb'), imgui.ImVec2(tw,0)) then _G.os_selected_tab = 'buy' end imgui.PopStyleColor() imgui.Separator() local item_list = (_G.os_selected_tab=='sell') and sel_shop.sell_items or sel_shop.buy_items local tab_color = (_G.os_selected_tab=='sell') and imgui.ImVec4(0.4,0.9,0.4,1) or imgui.ImVec4(0.4,0.7,1,1) if #item_list > 0 then if _G.os_selected_tab == 'sell' then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10*0.72,_G._mh_sb_g or 0.45*0.72,_G._mh_sb_b or 0.10*0.72,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r,sb_g,sb_b, _G._mh_wa or 1)) if imgui.Button(IC.fimp..' '..u8('Импорт -> НОВЫЙ пресет ПРОДАЖИ##osimp_s'), imgui.ImVec2(-1, 0)) then local _new_preset_name = sel_shop.owner and (sel_shop.owner..' Продажа') or ('Пресет '..tostring(#settings.presets+1)) local _new_preset = {name=_new_preset_name, items={}} local added, skipped = 0, 0 for _, it in ipairs(item_list) do if not _pst.find(it.name, _new_preset.items) then table.insert(_new_preset.items, {name=it.name, qty=it.qty or 1, price=it.price or 0}) added = added + 1 else skipped = skipped + 1 end end table.insert(settings.presets, _new_preset) fh_active_preset_idx = #settings.presets settings.active_preset = fh_active_preset_idx fh_lv_autosell_preset = _new_preset.items _G.as_price_buf = nil; _G.as_qty_buf = nil _wfn7p() mh_notify('[MH] {00cc00}Продажи -> новый пресет #'..tostring(fh_active_preset_idx)..': +'..added ..(skipped>0 and ' ({aaaaaa}'..skipped..' уже есть{ffffff})' or ''), 0xFFFFFF) end imgui.PopStyleColor(2) else imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.00,0.22,0.42,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.00,0.30,0.56, _G._mh_wa or 1)) if imgui.Button(IC.fimp..' '..u8('Импорт -> НОВЫЙ пресет СКУПКИ##osimp_b'), imgui.ImVec2(-1, 0)) then if not settings.buy_presets then settings.buy_presets = {} end local _bp_name = sel_shop.owner and (sel_shop.owner..' Скупка') or ('Скупка '..tostring(#settings.buy_presets+1)) local _new_bp = {name=_bp_name, items={}} local added, skipped = 0, 0 for _, it in ipairs(item_list) do if not _pst.find(it.name, _new_bp.items) then table.insert(_new_bp.items, {name=it.name, qty=it.qty or 1, max_price=it.price or 0}) added = added + 1 else skipped = skipped + 1 end end table.insert(settings.buy_presets, _new_bp) fh_ab_preset_idx = #settings.buy_presets fh_lv_autobuy_preset = _new_bp.items settings.autobuy_preset = fh_lv_autobuy_preset _G.ab_price_buf = nil; _G.ab_qty_buf = nil _wfn7p() mh_notify('[MH] {4488ff}Скупка -> новый пресет #'..tostring(fh_ab_preset_idx)..': +'..added ..(skipped>0 and ' ({aaaaaa}'..skipped..' уже есть{ffffff})' or ''), 0xFFFFFF) end imgui.PopStyleColor(2) end imgui.Spacing() end if #item_list == 0 then imgui.TextDisabled(_cyr5f(' Пусто')) end for _, it in ipairs(item_list) do local cp_e = fh_mkt_prices[it.name] local mkt_p = nil do local _mp2 = _mh_get_mkt_price(it.name) if _mp2 then local _v7 = (_mp2.avg7 and _mp2.avg7 > 0) and _mp2.avg7 or nil local _v30 = (_mp2.avg30 and _mp2.avg30 > 0) and _mp2.avg30 or nil if _v7 and _v30 then mkt_p = math.min(_v7, _v30) elseif _v7 then mkt_p = _v7 elseif _v30 then mkt_p = _v30 elseif _mp2.today and _mp2.today > 0 then mkt_p = _mp2.today end end end local diff_str = '' local diff_col = imgui.ImVec4(0.6,0.6,0.6,1) if mkt_p and mkt_p > 0 and it.price > 0 then local diff = mkt_p - it.price local pct = math.floor(math.abs(diff) / mkt_p * 100) if _G.os_selected_tab == 'sell' then if diff > 0 then diff_str='-'..pct..'%'; diff_col=imgui.ImVec4(0.3,0.95,0.3,1) elseif diff < 0 then diff_str='+'..pct..'%'; diff_col=imgui.ImVec4(1,0.45,0.3,1) else diff_str='=' end else if diff < 0 then diff_str='+'..pct..'%'; diff_col=imgui.ImVec4(0.3,0.95,0.3,1) elseif diff > 0 then diff_str='-'..pct..'%'; diff_col=imgui.ImVec4(1,0.45,0.3,1) else diff_str='=' end end end local _os_tag = mh_get_item_tag(it.name) local _os_tpfx = '' if _os_tag == 'watch' then _os_tpfx = fa.EYE..' ' elseif _os_tag == 'skip' then _os_tpfx = fa.BAN..' ' elseif _os_tag == 'fav' then _os_tpfx = fa.STAR..' ' end local _os_tc = imgui.ImVec4(1,1,1,0.9) if _os_tag=='fav' then _os_tc = imgui.ImVec4(1,0.85,0.1,1) elseif _os_tag=='skip' then _os_tc = imgui.ImVec4(0.5,0.5,0.5,0.6) elseif _os_tag=='watch' then _os_tc = imgui.ImVec4(0.4,0.85,1,1) end imgui.PushStyleColor(imgui.Col.Text, _os_tc) if imgui.Selectable(_os_tpfx.._cyr5f(it.name .. '##osit_'.._ ), false, 0, imgui.ImVec2(0,0)) then _G.mkt_detail_item = it.name _G.mkt_detail_src = fh_mkt_prices[it.name] and 'cp' or 'tags' _G.mkt_detail_pos = nil _G.mkt_detail_open = true end imgui.PopStyleColor() imgui.SameLine(0,8*d) imgui.TextColored(tab_color, _cyr5f('$'.._kcr3y(it.price))) if mkt_p then imgui.TextColored(imgui.ImVec4(0.38,0.38,0.38,1), _cyr5f(' Рын: $'.._kcr3y(mkt_p))) imgui.SameLine(0,6*d) imgui.TextColored(diff_col, _cyr5f(diff_str)) if cp_e and cp_e.cp_hist and #cp_e.cp_hist >= 4 then local _trd = _G._xvn2w(cp_e.cp_hist) local _tc = _G._pdf8k(_trd) imgui.SameLine(0,6*d) imgui.TextColored(_tc, _trd.icon .. _cyr5f(_trd.text)) end else imgui.TextColored(imgui.ImVec4(0.28,0.28,0.28,1), _cyr5f(' Рын: нет данных')) end end end imgui.EndChild() end imgui.Spacing() imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.45,0.08,0.08,1)) if imgui.Button(IC.trash..' '.._cyr5f('Очистить всё##os_clearall'), imgui.ImVec2(0,0)) then fh_other_shops = {} settings.other_shops = {} _wfn7p() _G.os_selected_key = nil mh_notify('[MH] {ff4444}Чужие лавки очищены.', 0xFFFFFF) end imgui.PopStyleColor() imgui.EndTabItem() end if imgui.BeginTabItem(IC.star .. ' ' .. _cyr5f('\xcf\xee\xe4\xe1\xee\xf0\xea\xe0') .. '##sub_wish') then if not _G.mh_wish_filter then _G.mh_wish_filter = 0 end if not _G.mh_wish_srch then _G.mh_wish_srch = imgui.new.char[128](''); _G.mh_wish_srch_s = '' end if not _G.mh_wish_sort then _G.mh_wish_sort = 0 end local cw_w = imgui.GetWindowContentRegionWidth() local tagged = {} if settings.item_tags then for nm, tg in pairs(settings.item_tags) do if nm and nm ~= '' and tg then table.insert(tagged, {name=nm, tag=tg}) end end end local srch_lo = _G.mh_wish_srch_s or '' local filtered = {} for _, it in ipairs(tagged) do local tag_ok = (_G.mh_wish_filter == 0) or (_G.mh_wish_filter == 1 and it.tag == 'watch') or (_G.mh_wish_filter == 2 and it.tag == 'fav') or (_G.mh_wish_filter == 3 and it.tag == 'skip') local srch_ok = srch_lo == '' or it.name:lower():find(srch_lo, 1, true) if tag_ok and srch_ok then table.insert(filtered, it) end end if _G.mh_wish_sort == 0 then table.sort(filtered, function(a,b) return a.name < b.name end) else local order = {watch=1,fav=2,skip=3} table.sort(filtered, function(a,b) local oa=order[a.tag] or 9; local ob=order[b.tag] or 9 if oa ~= ob then return oa < ob end; return a.name < b.name end) end local tag_colors = { watch = imgui.ImVec4(0.20, 0.55, 0.95, 1), fav = imgui.ImVec4(0.92, 0.70, 0.05, 1), skip = imgui.ImVec4(0.75, 0.20, 0.20, 1), } local tag_bg = { watch = imgui.ImVec4(0.07, 0.15, 0.30, 1), fav = imgui.ImVec4(0.22, 0.16, 0.03, 1), skip = imgui.ImVec4(0.25, 0.07, 0.07, 1), } local tag_icons = { watch=IC.eye, fav=IC.star, skip=IC.ban } local tag_labels = { watch = _cyr5f('\xd1\xeb\xe5\xe6\xea\xe0'), fav = _cyr5f('\xc8\xe7\xe1\xf0\xe0\xed'), skip = _cyr5f('\xce\xf2\xea\xeb\xfe\xf7'), } local function wfbtn(lbl, mode) local act = _G.mh_wish_filter == mode local bc = act and imgui.ImVec4(ar*0.28,ag*0.28,ab*0.28,1) or imgui.ImVec4(0.12,0.12,0.15,1) local bh = act and imgui.ImVec4(ar*0.40,ag*0.40,ab*0.40,1) or imgui.ImVec4(0.18,0.18,0.22,1) local tc2 = act and imgui.ImVec4(ar,ag,ab,1) or imgui.ImVec4(0.65,0.65,0.70,1) imgui.PushStyleColor(imgui.Col.Button, bc) imgui.PushStyleColor(imgui.Col.ButtonHovered, bh) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(ar*0.5,ag*0.5,ab*0.5,1)) imgui.PushStyleColor(imgui.Col.Text, tc2) if imgui.Button(lbl..'##wf'..mode, imgui.ImVec2(0, 22*d)) then _G.mh_wish_filter = mode end imgui.PopStyleColor(4) end wfbtn(_cyr5f('\xc2\xf1\xe5 ')..#tagged, 0) imgui.SameLine(0, 3*d) wfbtn(IC.eye..' '.._cyr5f('\xd1\xeb\xe5\xe6\xea\xe0'), 1) imgui.SameLine(0, 3*d) wfbtn(IC.star..' '.._cyr5f('\xc8\xe7\xe1\xf0\xe0\xed'), 2) imgui.SameLine(0, 3*d) wfbtn(IC.ban..' '.._cyr5f('\xce\xf2\xea\xeb\xfe\xf7'), 3) imgui.SameLine(0, 6*d) local sort_ic = _G.mh_wish_sort == 0 and (IC.dn..' A-Z') or (IC.lyr..' '.._cyr5f('\xd2\xe5\xe3')) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.12,0.12,0.15,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.20,0.20,0.25, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.60,0.60,0.65,1)) if imgui.Button(sort_ic..'##wsort', imgui.ImVec2(0, 22*d)) then _G.mh_wish_sort = (_G.mh_wish_sort == 0) and 1 or 0 end imgui.PopStyleColor(3) local tg_ok = settings.telegram and settings.telegram.enabled and settings.telegram.notify_watch and mh_arz_data and #mh_arz_data > 0 local tg_w = 68*d imgui.SameLine(cw_w - tg_w + 4*d) imgui.PushStyleColor(imgui.Col.Button, tg_ok and imgui.ImVec4(0.08,0.36,0.60,1) or imgui.ImVec4(0.12,0.12,0.15,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, tg_ok and imgui.ImVec4(0.12,0.52,0.82,1) or imgui.ImVec4(0.16,0.16,0.20,1)) imgui.PushStyleColor(imgui.Col.ButtonActive, tg_ok and imgui.ImVec4(0.06,0.28,0.50,1) or imgui.ImVec4(0.10,0.10,0.13,1)) imgui.PushStyleColor(imgui.Col.Text, tg_ok and imgui.ImVec4(1,1,1,1) or imgui.ImVec4(0.38,0.38,0.38,1)) local tg_clicked = imgui.Button(fa.PAPER_PLANE..' '.._cyr5f('\xd2\xc3')..'##wl_tg_now', imgui.ImVec2(tg_w, 22*d)) imgui.PopStyleColor(4) if tg_clicked then if tg_ok then lua_thread.create(function() local _wbg_live = _mpf7d() local _wbg_sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _wbg_boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _wbg_idx if _wbg_sel > 1 then _wbg_idx = _wbg_sel elseif _wbg_live > 0 then _wbg_idx = _wbg_live + 1 elseif _wbg_boot > 0 then _wbg_idx = _wbg_boot end local _cur_srv_id = _wbg_idx and (ARZ_SERVERS[_wbg_idx] or {}).id or -1 if not _wbg_idx or _cur_srv_id == -1 then mh_notify('[MH] {ffaa00}\xd1\xe5\xf0\xe2\xe5\xf0 \xed\xe5 \xee\xef\xf0\xe5\xe4\xe5\xeb\xb8\xed', 0xFFFFFF) return end local _found = {} for _, lv in ipairs(mh_arz_data) do if type(lv) ~= 'table' then goto _tgbtn_cont end if lv.serverId ~= _cur_srv_id then goto _tgbtn_cont end local _uid = lv.LavkaUid or '?' local _owner = lv.username or '?' local _srv = _dzc2g(lv.serverId or -1) if lv.items_sell then for ii, iid in ipairs(lv.items_sell) do local bid, ench = _G._bqs3v(iid) local _wtag_nm2 = mh_tag_key(bid) if mh_get_item_tag(_wtag_nm2) == 'watch' then local base_nm = _wtag_nm2 local price = lv.price_sell and lv.price_sell[ii] or nil local cnt = lv.count_sell and lv.count_sell[ii] or nil local nm_full = base_nm .. (ench ~= '' and (' (' .. ench .. ')') or '') if price and price > 0 then table.insert(_found, { nm=nm_full, base_nm=base_nm, price=price, cnt=cnt, owner=_owner, srv_nm=_srv, uid=_uid }) end end end end ::_tgbtn_cont:: end local _best = {} for _, fe in ipairs(_found) do local bn = fe.base_nm if not _best[bn] or (fe.price or math.huge) < (_best[bn].price or math.huge) then _best[bn] = fe end end local _dedup = {} for _, bv in pairs(_best) do table.insert(_dedup, bv) end table.sort(_dedup, function(a,b) return (a.price or math.huge) < (b.price or math.huge) end) if #_dedup == 0 then mh_notify('[MH] {aaffaa}\xd2\xee\xe2\xe0\xf0\xfb \xed\xe5 \xed\xe0\xe9\xe4\xe5\xed\xfb', 0xFFFFFF) return end for _, it in ipairs(_dedup) do local function _s(v) return _to_utf8(v or '?') end local _wmsg = _to_utf8('[*] Вотчлист') .. '\n' .. '--------------------\n' .. _to_utf8('Предмет: ') .. _s(it.nm) .. '\n' .. _to_utf8('Цена: $') .. _kcr3y(it.price) .. (it.cnt and (_to_utf8(' x')..tostring(it.cnt).._to_utf8(' шт.')) or '') .. '\n' .. _to_utf8('Лавка: #') .. tostring(it.uid) .. ' ' .. _s(it.owner) .. ' (' .. _s(it.srv_nm) .. ')\n' .. '--------------------\n' .. os.date('%H:%M %d.%m.%Y') _mh_tg_send_utf8(_wmsg) wait(400) end mh_notify('[MH] {aaffaa}\xce\xf2\xef\xf0\xe0\xe2\xeb\xe5\xed\xee: ' .. #_dedup .. ' \xf2\xee\xe2\xe0\xf0\xe0', 0xFFFFFF) end) else mh_notify('[MH] {ffaa00}\xd2\xe5\xeb\xe5\xe3\xf0\xe0\xec \xed\xe5 \xed\xe0\xf1\xf2\xf0\xee\xe5\xed', 0xFFFFFF) end end imgui.Spacing() imgui.PushStyleColor(imgui.Col.FrameBg, imgui.ImVec4(0.10,0.10,0.13, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.FrameBgHovered, imgui.ImVec4(0.14,0.14,0.18, _G._mh_wa or 1)) imgui.PushItemWidth(-1) if imgui.InputTextWithHint('##wish_srch', IC.srch..' '.._cyr5f('\xcf\xee\xe8\xf1\xea \xef\xee \xed\xe0\xe7\xe2\xe0\xed\xe8\xfe...'), _G.mh_wish_srch, 128) then _G.mh_wish_srch_s = u8:decode(ffi.string(_G.mh_wish_srch)):lower() end imgui.PopItemWidth() imgui.PopStyleColor(2) imgui.Spacing() imgui.TextDisabled(_cyr5f('\xd0\xe5\xe7\xf3\xeb\xfc\xf2\xe0\xf2\xee\xe2: ') .. #filtered) imgui.Spacing() local list_h = imgui.GetWindowHeight() - 200*d if imgui.BeginChild('##wish_list', imgui.ImVec2(-1, list_h), false) then _dpn1w() if #filtered == 0 then imgui.Spacing(); imgui.Spacing() imgui.TextDisabled(_cyr5f('\xcf\xf3\xf1\xf2\xee')) imgui.TextDisabled(_cyr5f('\xc4\xee\xe1\xe0\xe2\xfc\xf2\xe5 \xf2\xee\xe2\xe0\xf0\xfb \xf7\xe5\xf0\xe5\xe7 \xea\xe0\xf0\xf2\xee\xf7\xea\xf3 \xf2\xee\xe2\xe0\xf0\xe0')) end local card_h = 32*d local del_w = 26*d local bar_w = 4*d for idx, it in ipairs(filtered) do local tc = tag_colors[it.tag] or imgui.ImVec4(0.5,0.5,0.5,1) local tbg = tag_bg[it.tag] or imgui.ImVec4(0.12,0.12,0.14,1) local ico = tag_icons[it.tag] or IC.circ local tlbl = tag_labels[it.tag] or '' local card_w = cw_w - del_w - 6*d imgui.PushStyleColor(imgui.Col.ChildBg, tbg) if imgui.BeginChild('##wcard'..idx, imgui.ImVec2(card_w, card_h), false) then local wp = imgui.GetWindowPos() local cx = imgui.GetCursorPosX() local cy = imgui.GetCursorPosY() local mid_y = cy + (card_h - imgui.GetTextLineHeight()) * 0.5 - 1*d imgui.GetWindowDrawList():AddRectFilled( imgui.ImVec2(wp.x, wp.y), imgui.ImVec2(wp.x + bar_w, wp.y + card_h), imgui.ColorConvertFloat4ToU32(tc) ) imgui.SetCursorPos(imgui.ImVec2(cx, cy)) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0,0,0,0)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(ar*0.12,ag*0.12,ab*0.12,0.6)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(ar*0.22,ag*0.22,ab*0.22,0.8)) local card_clicked = imgui.Button('##wcard_btn'..idx, imgui.ImVec2(card_w, card_h)) imgui.PopStyleColor(3) if card_clicked then _G.mkt_detail_item = it.name _G.mkt_detail_src = 'tags' _G.mkt_detail_pos = nil _G.mkt_detail_open = true end imgui.SetCursorPos(imgui.ImVec2(cx + bar_w + 7*d, mid_y)) imgui.PushStyleColor(imgui.Col.Text, tc) imgui.Text(ico) imgui.PopStyleColor() imgui.SameLine(0, 6*d) imgui.SetCursorPosY(mid_y) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.92,0.92,0.95,1)) imgui.Text(_cyr5f(it.name)) imgui.PopStyleColor() imgui.EndChild() end imgui.PopStyleColor() imgui.SameLine(0, 4*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.25,0.07,0.07,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.55,0.10,0.10, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(0.70,0.12,0.12,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1,0.4,0.4,1)) if imgui.Button(IC.x..'##wdel'..idx, imgui.ImVec2(del_w, card_h)) then mh_set_item_tag(it.name, nil) end imgui.PopStyleColor(4) imgui.Spacing() end imgui.EndChild() end imgui.EndTabItem() end -- ================================================================== -- [MH] Вкладка "Подбор" — подбор товаров в лавках под бюджет игрока. -- -- Логика: -- 1) Берём уже отсканированные лавки из mh_arz_data (наполняется -- вкладкой "Лавки" через парсинг диалогов/3D-текстов рынка). -- 2) Для каждого товара, который лавка ПРОДАЁТ игроку (items_sell/ -- price_sell), сравниваем цену лавки со средней рыночной ценой: -- сначала пробуем реальную статистику _mh_get_mkt_price(), -- и только если её нет — берём приблизительную цену из локальной -- таблицы mh_pick_basedb (см. ниже). -- 3) Отфильтровываем по бюджету игрока и сортируем по выгоде. -- 4) Рисуем таблицу с индикатором тренда (^ выгодно / v дорого). -- -- Все обращения к чужим таблицам обёрнуты проверками на nil, чтобы -- отсутствие данных по конкретному товару/лавке не роняло скрипт. -- ================================================================== if imgui.BeginTabItem(IC.coin .. ' ' .. _cyr5f('\xcf\xee\xe4\xe1\xee\xf0') .. '##sub_pick') then -- 1. Локальная база примерных средних цен (подстраховка). -- Заполнена для примера ходовыми товарами ЦР Arizona RP: -- аксессуары, рулетки (инструмент) и стройресурсы. -- Ключ — точное название предмета, значение — средняя цена. if not _G.mh_pick_basedb then _G.mh_pick_basedb = { [_cyr5f('\xd0\xf3\xeb\xe5\xf2\xea\xe0')] = 3500, -- Рулетка [_cyr5f('\xd7\xe0\xf1\xfb')] = 4200, -- Часы [_cyr5f('\xd6\xe5\xef\xee\xf7\xea\xe0\x20\xe7\xee\xeb\xee\xf2\xe0\xff')] = 8600, -- Цепочка золотая [_cyr5f('\xc4\xee\xf1\xea\xe8')] = 120, -- Доски [_cyr5f('\xcc\xe5\xf2\xe0\xeb\xeb')] = 340, -- Металл [_cyr5f('\xd1\xf2\xe5\xea\xeb\xee')] = 210, -- Стекло [_cyr5f('\xd6\xe5\xec\xe5\xed\xf2')] = 95, -- Цемент [_cyr5f('\xcf\xf0\xee\xe2\xee\xe4\xe0')] = 150, -- Провода } end -- 2. Состояние UI вкладки (создаётся один раз за сессию), со -- значениями, восстановленными из settings.general - тот же -- JSON-файл (MarketHelper_settings.json), в который скрипт -- уже сохраняет остальные настройки (см. _qvx4m()/_wfn7p() -- выше). Раньше бюджет/мин.выгода/фильтр/сортировка вообще -- не сохранялись - при каждом переоткрытии вкладки (в том -- числе после перезахода в игру) они слетали на хардкод -- (50000 / 5 / выкл / "по выгоде"). if not settings.general.mh_pick then settings.general.mh_pick = {} end local _pk_saved = settings.general.mh_pick if not _G.mh_pick_budget then _G.mh_pick_budget = imgui.new.int(tonumber(_pk_saved.budget) or 50000) end if not _G.mh_pick_minpct then _G.mh_pick_minpct = imgui.new.int(tonumber(_pk_saved.minpct) or 5) end if _G.mh_pick_only_good == nil then _G.mh_pick_only_good = imgui.new.bool(_pk_saved.only_good == true) end if not _G.mh_pick_sort then _G.mh_pick_sort = tonumber(_pk_saved.sort) or 0 end -- 0=выгода 1=название 2=цена -- Сохраняем изменения фильтров на диск сразу же, не только в -- _G на время сессии - иначе после закрытия/перезапуска игры -- всё равно вернёмся к 50000. local function _mh_pick_save_filters() settings.general.mh_pick = { budget = _G.mh_pick_budget[0], minpct = _G.mh_pick_minpct[0], only_good = _G.mh_pick_only_good[0] == true, sort = _G.mh_pick_sort, } _wfn7p() end -- 3. Сборка списка позиций из отсканированных лавок. -- Кэшируем результат по версии данных лавок (_mh_shop_ver), -- чтобы не пересчитывать всё на каждом кадре интерфейса. -- -- Заодно строим индекс "название товара -> отсортированный список -- цен во ВСЕХ лавках сервера" (_mh_pick_price_idx). Он нужен ниже -- для живого якоря: _mh_guard_mkt_price() (используется в -- карточке товара) берёт якорь из fh_other_shops - лавок, которые -- игрок ЛИЧНО открывал/сканировал. Но в "Подбор" рассматриваются -- 1000+ позиций из ОБЛАЧНОЙ базы лавок (mh_arz_data), большинство -- которых игрок никогда не открывал лично - для них fh_other_shops -- пуст, и такой guard возвращает цену БЕЗ всякой фильтрации - -- отсюда и брались цифры вроде "$4.000.140.020" при живых ценах -- в лавках $10-15М. Используя цены из уже загруженного списка -- лавок (mh_arz_data) как якорь, получаем рабочую защиту даже -- для товаров, которые игрок не смотрел вживую - именно они и -- составляют почти весь список "Подбор". local _shop_ver = _G._mh_shop_ver or 0 if not _G._mh_pick_cache or _G._mh_pick_cache_ver ~= _shop_ver then local built = {} local price_idx = {} for _, lv in ipairs(mh_arz_data or {}) do if type(lv) == 'table' and lv.items_sell and lv.price_sell then for ii, iid in ipairs(lv.items_sell) do -- Защита: id предмета может прийти как строка/число/nil local raw_id = tostring(iid or ''):match('^(%d+)') local item_nm = raw_id and mh_arz_items_db and mh_arz_items_db[tonumber(raw_id)] local shop_price = tonumber(lv.price_sell[ii]) if item_nm and item_nm ~= '' and shop_price and shop_price > 0 then table.insert(built, { name = item_nm, shop_price = shop_price, uid = lv.LavkaUid or 0, owner = lv.username or '', }) if not price_idx[item_nm] then price_idx[item_nm] = {} end table.insert(price_idx[item_nm], shop_price) end end end end for _, arr in pairs(price_idx) do table.sort(arr) end _G._mh_pick_cache = built _G._mh_pick_cache_ver = _shop_ver _G._mh_pick_price_idx = price_idx end -- якорь брал сырую медиану ВСЕХ цен лавок, включая явных спекулянтов - например "Дельтаплан на спину" продавался в разных лавках по $211М/$1.5 млрд/$2.9 млрд, и при малой выборке (2-3 лавки) медиана из них сама оказывалась завышенной в разы. Раз anchor используется как фильтр (_within_anchor) для настоящих рыночных значений (today/avg7/avg30/s1/s7/s30), завышенный anchor отсеивал ВСЕ нормальные (низкие) значения как "слишком далёкие от якоря" - и тогда срабатывал fallback на сам испорченный anchor, отсюда и рекомендация купить по цене рынка $1.5 млрд. Добавлена простая IQR-отсечка выбросов перед взятием медианы, как и в остальных местах проекта (_filter_outliers/_iqr_avg) - несколько аномально дорогих лотов теперь не тянут якорь вверх local function _mh_pick_shop_anchor(item_name) local arr = _G._mh_pick_price_idx and _G._mh_pick_price_idx[item_name] if not arr or #arr == 0 then return nil end if #arr <= 2 then return arr[math.ceil(#arr / 2)] end local q1 = arr[math.max(1, math.ceil(#arr * 0.25))] local q3 = arr[math.min(#arr, math.ceil(#arr * 0.75))] local iqr = q3 - q1 local med = arr[math.ceil(#arr / 2)] local fence = math.max(iqr * 3, med * 0.7) local lo, hi = med - fence, med + fence local clean = {} for _, v in ipairs(arr) do if v >= lo and v <= hi then table.insert(clean, v) end end if #clean == 0 then return med end return clean[math.ceil(#clean / 2)] end -- 4. Безопасное получение средней цены предмета: сначала реальная -- рыночная статистика, при её отсутствии — локальная база. -- -- Раньше брали только avg7 (или avg30/today как фолбэк), без -- какой-либо защиты от завышенных цифр, и без учёта углублённой -- истории (cp_hist). Карточка товара ("Статистика товара") для -- товаров, у которых есть cp_hist, пересчитывает День/7дн/30дн -- через _mjg5t()/_cached_stats() - более точный расчёт по -- реальным датам сделок, а не общий _mh_get_mkt_price(). Из-за -- этого "Подбор" (раньше смотревший только на _mh_get_mkt_price) -- мог показывать заметно другую (и более старую/завышенную) -- цифру, чем карточка того же товара - например авиа $152М в -- "Подбор" при $77.5М/117.9М/122.2М в карточке. -- -- Теперь считаем ОБА набора значений (общий mp.today/avg7/avg30 -- И, если есть cp_hist, s1/s7/s30 через тот же _mjg5t, что и -- карточка) и берём минимальное положительное из всех них, -- прошедшее проверку на якорь _mh_pick_shop_anchor() (медиана -- живых цен этого товара по всем лавкам вкладки "Подбор"). local function _mh_pick_avg_price(item_name) if not item_name or item_name == '' then return nil end local anchor = _mh_pick_shop_anchor(item_name) -- стратегия выбора переписана ЕЩЁ РАЗ по прямой просьбе: приоритет 30дн->7дн->Сегодня всё ещё расходился с карточкой, потому что карточка вообще не берёт "Рынок $" через _mjg5t(cp_hist) напрямую - там сложный отдельный расчёт через _G._dtl_stats (медиана по дням + IQR-фильтр + анти-аномальная защита по живым ценам в лавках), который считается только для ОДНОГО открытого в данный момент товара (асинхронный воркер) и физически недоступен для всех 1200+ позиций списка "Подбор" одновременно. Вместо попытки один-в-один повторить эту тяжёлую логику карточки, теперь берём простое и предсказуемое правило по прямой просьбе: МИНИМУМ среди всех доступных чисел (today/avg7/avg30 из общего _mh_get_mkt_price и s1/s7/s30 из _mjg5t(cp_hist)) - какая бы то ни было причина расхождений с картой, минимум гарантированно не завысит рекомендацию цены рынка, а только либо совпадёт, либо будет ниже - то есть в худшем случае недосоветует часть выгодных лотов, но никогда не порекомендует товар по цене выше, чем он стоит на самом деле local function _within_anchor(v) if not v or v <= 0 then return false end if not anchor or anchor <= 0 then return true end local r = v > anchor and (v / anchor) or (anchor / v) return r < 2.5 end local best = nil local function _consider(v) if _within_anchor(v) and (not best or v < best) then best = v end end local ok, mp = pcall(_mh_get_mkt_price, item_name) if ok and mp then _consider(mp.today) _consider(mp.avg7) _consider(mp.avg30) end do local ok2, cp_e = pcall(function() return fh_mkt_prices[item_name] end) local hist = ok2 and cp_e and cp_e.cp_hist if hist and #hist > 0 then -- РАНЬШЕ: якорем для _mjg5t (фильтр выбросов истории по -- 1/7/30 дням) служил mp.today/mp.avg7 из _mh_get_mkt_price - -- то есть ТОТ ЖЕ САМЫЙ источник, который сам может быть -- завышен (см. пример "Наследник Дома": Подбор показывал -- $152.561.561, тогда как карточка товара - $77.500.000). -- _filter_outliers умеет тянуть якорь ТОЛЬКО ВНИЗ (если -- ext_anchor < внутренней медианы истории) - если сам якорь -- завышен, некорректно высокая планка "hi" остаётся, и -- фильтр не отсекает большие цифры, из-за чего заниженная -- (правильная) историческая цена может быть отброшена, а -- завышенная - пройти. -- ТЕПЕРЬ: якорем служит anchor - независимая от -- _mh_get_mkt_price медиана РЕАЛЬНЫХ цен по лавкам игроков -- (_mh_pick_shop_anchor, с собственным IQR). Она не зависит -- от того же потенциально аномального источника и поэтому -- надёжнее тянет фильтр вниз, когда история завышена. local _today_anchor = anchor or (ok and mp and (mp.today or mp.avg7)) local ok3, s1 = pcall(_mjg5t, hist, 1, _today_anchor) local ok4, s7 = pcall(_mjg5t, hist, 7, _today_anchor) local ok5, s30 = pcall(_mjg5t, hist, 30, _today_anchor) if ok3 and s1 and s1.avg then _consider(s1.avg) end if ok4 and s7 and s7.avg then _consider(s7.avg) end if ok5 and s30 and s30.avg then _consider(s30.avg) end end end -- Сам якорь (медиана реальных цен по лавкам игроков) тоже -- участвует в выборе минимума, а не только как фильтр-порог - -- раньше он использовался лишь для отсечения (_within_anchor) и -- как последний резерв, если ничего не прошло фильтр; теперь он -- напрямую конкурирует с остальными источниками за минимум, что -- ближе к запрошенной логике "после всех вычислений - минимум". if anchor and anchor > 0 then _consider(anchor) end -- Если ВСЕ источники отсеклись якорем (например данные -- полностью устарели), лучше показать сам якорь (живую -- цену по лавкам), чем оставить товар без всякой оценки. if not best and anchor and anchor > 0 then best = anchor end if best then return best end local base = _G.mh_pick_basedb[item_name] if base and base > 0 then return base end return nil end -- 5. Фильтр по бюджету и расчёт выгоды/тренда для каждой позиции. local budget = math.max(0, _G.mh_pick_budget[0] or 0) local min_pct = _G.mh_pick_minpct[0] or 0 local only_good = _G.mh_pick_only_good[0] -- Раньше фильтрация+сортировка всего списка (сотни-тысячи позиций, -- на скрине - 1184) пересчитывались КАЖДЫЙ КАДР безусловно, даже -- если игрок ничего не менял в фильтрах - каждый кадр заново шли -- вызовы _mh_pick_avg_price() на каждую позицию и полный table.sort(). -- Кэшируем результат по составному ключу: пока бюджет/мин.выгода/ -- чекбокс/сортировка/версия данных лавок не изменились - отдаём -- уже посчитанный rows без пересчёта. local _pick_key = table.concat({ _shop_ver, budget, min_pct, only_good and 1 or 0, _G.mh_pick_sort }, '|') local rows if _G._mh_pick_rows_cache and _G._mh_pick_rows_key == _pick_key then rows = _G._mh_pick_rows_cache else rows = {} for _, e in ipairs(_G._mh_pick_cache or {}) do if e.shop_price <= budget or budget == 0 then local avg = _mh_pick_avg_price(e.name) local pct = nil if avg and avg > 0 then -- Положительный % — цена в лавке НИЖЕ рынка (выгодно купить) pct = (avg - e.shop_price) / avg * 100 end local pass = (not only_good) or (pct and pct >= min_pct) if pass then table.insert(rows, { name = e.name, shop_price = e.shop_price, avg = avg, pct = pct, uid = e.uid, owner = e.owner, }) end end end if _G.mh_pick_sort == 0 then table.sort(rows, function(a, b) local pa = a.pct or -math.huge local pb = b.pct or -math.huge return pa > pb end) elseif _G.mh_pick_sort == 1 then table.sort(rows, function(a, b) return a.name < b.name end) else table.sort(rows, function(a, b) return a.shop_price < b.shop_price end) end _G._mh_pick_rows_cache = rows _G._mh_pick_rows_key = _pick_key end -- 5.5. Блок обновления данных: кнопка "Обновить" (та же цепочка -- загрузки и тот же кулдаун _G._mh_refresh_cd, что и во -- вкладке "Лавки" - без дублирования логики запроса) плюс -- статус последнего обновления и индикатор загрузки, чтобы -- было явно видно, откуда брать данные и что они не "зависли". local cw_pk = imgui.GetWindowContentRegionWidth() do local ar = settings.interface.accent_r or 1 local ag = settings.interface.accent_g or 0.55 local ab = settings.interface.accent_b or 0.0 local loading_any_pk = mh_arz_loading or mh_arz_items_loading imgui.PushStyleColor(imgui.Col.Button, _mh_bc(ar * 0.22, ag * 0.22, ab * 0.12, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(ar * 0.45, ag * 0.45, ab * 0.25, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(ar * 0.65, ag * 0.65, ab * 0.35, 1)) if loading_any_pk then imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.45, 0.45, 0.45, 1)) end local _pk_btn_lbl = loading_any_pk and (IC.rot .. ' ' .. _cyr5f('\xce\xe1\xed\xee\xe2\xeb\xe5\xed\xe8\xe5\x2e\x2e\x2e')) -- "Обновление..." or (IC.rot .. ' ' .. _cyr5f('\xce\xe1\xed\xee\xe2\xe8\xf2\xfc')) -- "Обновить" if imgui.Button(_pk_btn_lbl .. '##mh_pick_refresh', imgui.ImVec2(140 * d, 0)) then local _now_cd_pk = os.clock() local _cd_ok_pk = not _G._mh_refresh_cd or (_now_cd_pk - _G._mh_refresh_cd) >= 15 if not loading_any_pk and _cd_ok_pk then _G._mh_refresh_cd = _now_cd_pk _G._mh_session_tok = '' lua_thread.create(function() _mh_check_update(true) local _sw = 0 while (_G._mh_session_tok or '') == '' and _sw < 50 do wait(100); _sw = _sw + 1 end end) local sel_srv_id_pk = ARZ_SERVERS[_G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 1] sel_srv_id_pk = sel_srv_id_pk and sel_srv_id_pk.id or -1 _G.arz_cache_key = nil _G.arz_page = 1 _G.arz_detail = nil _xtj6b(sel_srv_id_pk) end end if loading_any_pk then imgui.PopStyleColor() end if imgui.IsItemHovered() and _G._mh_refresh_cd then local _cd_left_pk = math.ceil(15 - (os.clock() - _G._mh_refresh_cd)) if _cd_left_pk > 0 then imgui.SetTooltip(_cyr5f('\xca\xf3\xeb\xe4\xe0\xf3\xed\x3a\x20' .. _cd_left_pk .. '\x20\xf1\xe5\xea\x2e')) -- "Кулдаун: N сек." end end imgui.PopStyleColor(3) imgui.SameLine(0, 10 * d) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.55, 0.55, 0.60, 1)) if mh_arz_last_update and mh_arz_last_update ~= '' then imgui.Text(_cyr5f('\xce\xe1\xed\xee\xe2\xeb\xe5\xed\xee\x3a\x20') .. mh_arz_last_update) -- "Обновлено: HH:MM:SS" else imgui.Text(_cyr5f('\xc4\xe0\xed\xed\xfb\xe5\x20\xed\xe5\x20\xe7\xe0\xe3\xf0\xf3\xe6\xe0\xeb\xe8\xf1\xfc')) -- "Данные не загружались" end imgui.PopStyleColor() if mh_arz_error and mh_arz_error ~= '' then -- mh_arz_error уже приходит как UTF-8 (см. как он выводится -- во вкладке "Лавки" - там используется u8(), а не _cyr5f()); -- _cyr5f предназначена для CP1251-литералов в исходнике, -- применение её к уже-UTF8 строке дало бы кракозябры imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.90, 0.35, 0.35, 1)) imgui.TextWrapped(_cyr5f('\xce\xf8\xe8\xe1\xea\xe0\x3a\x20') .. u8(mh_arz_error)) -- "Ошибка: ..." imgui.PopStyleColor() end end imgui.Dummy(imgui.ImVec2(0, 14 * d)) -- 6. Панель фильтров: бюджет, порог выгоды, сортировка. -- -- Раскладка в две РАВНЫЕ по ширине колонки на всю высоту блока: -- - слева, друг под другом (как в самой первой версии) - поля -- "Бюджет ($)" и "Мин. выгода, %", каждое на всю ширину -- своей колонки; -- - справа - дисклеймер про источник цен, той же высоты, что -- и левая колонка целиком (а не тонкая полоска-строка) - -- занимает ровно то пустое место, которое раньше пустовало -- справа от полей ввода на телефонном экране. -- Высота правой рамки берётся строго по факту отрисованной -- левой колонки (замеряется курсором ДО/ПОСЛЕ), поэтому обе -- половины всегда совпадают по высоте, даже если высота полей -- изменится из-за другого шрифта/DPI. imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.65, 0.65, 0.70, 1)) imgui.Text(IC.flt .. ' ' .. _cyr5f('\xd4\xe8\xeb\xfc\xf2\xf0\x20\xef\xee\x20\xe1\xfe\xe4\xe6\xe5\xf2\xf3\x20\xe8\x20\xe2\xfb\xe3\xee\xe4\xe5')) imgui.PopStyleColor() imgui.Spacing() local _col_gap = 12 * d local _col_w = (cw_pk - _col_gap) / 2 local _flt_y0 = imgui.GetCursorScreenPos().y imgui.BeginGroup() imgui.PushStyleColor(imgui.Col.FrameBg, imgui.ImVec4(0.10, 0.10, 0.13, _G._mh_wa or 1)) imgui.PushItemWidth(_col_w) imgui.Text(_cyr5f('\xc1\xfe\xe4\xe6\xe5\xf2\x20\x28\x24\x29')) if imgui.InputInt('##mh_pick_budget_in', _G.mh_pick_budget, 1000, 10000) then if _G.mh_pick_budget[0] < 0 then _G.mh_pick_budget[0] = 0 end _mh_pick_save_filters() end imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.50, 0.50, 0.55, 1)) imgui.Text('= ' .. _jsb6t(_G.mh_pick_budget[0])) imgui.PopStyleColor() imgui.Spacing() imgui.Text(_cyr5f('\xcc\xe8\xed\xe8\xec\xe0\xeb\xfc\xed\xe0\xff\x20\xe2\xfb\xe3\xee\xe4\xe0\x2c\x20' .. '\xef\xf0\xee\xf6\xe5\xed\xf2\xee\xe2\x20\xee\xf2\x20\xf1\xf0\xe5\xe4\xed\xe5\xe9\x20\xf6\xe5\xed\xfb')) -- "Минимальная выгода, процентов от средней цены" if imgui.InputInt('##mh_pick_minpct_in', _G.mh_pick_minpct, 1, 5) then if _G.mh_pick_minpct[0] < 0 then _G.mh_pick_minpct[0] = 0 end if _G.mh_pick_minpct[0] > 100 then _G.mh_pick_minpct[0] = 100 end _mh_pick_save_filters() end imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.50, 0.50, 0.55, 1)) imgui.Text(_cyr5f('\xcf\xee\xf0\xee\xe3\x3a\x20\xee\xf2\x20') .. tostring(_G.mh_pick_minpct[0]) .. ' ' .. _cyr5f( '\xef\xf0\xee\xf6\xe5\xed\xf2\xee\xe2\x20\xed\xe8\xe6\xe5\x20\xf1\xf0\xe5\xe4\xed\xe5\xe9\x20\xf6\xe5\xed\xfb\x20\xf0\xfb\xed\xea\xe0' )) -- "Порог: от N процентов ниже средней цены рынка" imgui.PopStyleColor() imgui.PopItemWidth() imgui.PopStyleColor() imgui.EndGroup() local _flt_h = imgui.GetCursorScreenPos().y - _flt_y0 imgui.SameLine(0, _col_gap) -- Дисклеймер: рамка справа, высотой ровно как левая колонка. do local _wtxt = _cyr5f( '\xd6\xe5\xed\xfb\x20\xee\xf1\xed\xee\xe2\xe0\xed\xfb\x20\xed\xe0\x20\xe4\xe0\xed\xed\xfb\xf5\x20' .. '\xe8\xe3\xf0\xee\xea\xee\xe2\x20\xe8\x20\xec\xee\xe3\xf3\xf2\x20\xe1\xfb\xf2\xfc\x20' .. '\xed\xe5\xf2\xee\xf7\xed\xfb\xec\xe8\x2e\x20\xcf\xf0\xe8\xed\xe8\xec\xe0\xff\x20' .. '\xf0\xe5\xf8\xe5\xed\xe8\xe5\x20\xee\x20\xef\xee\xea\xf3\xef\xea\xe5\x2c\x20' .. '\xf0\xf3\xea\xee\xe2\xee\xe4\xf1\xf2\xe2\xf3\xe9\xf2\xe5\xf1\xfc\x20\xf1\xee\xe1\xf1\xf2\xe2\xe5\xed\xed\xfb\xec\x20' .. '\xee\xef\xfb\xf2\xee\xec\x2c\x20\xf7\xf2\xee\xe1\xfb\x20\xed\xe5\x20\xef\xee\xf2\xe5\xf0\xff\xf2\xfc\x20\xf1\xf0\xe5\xe4\xf1\xf2\xe2\xe0' ) -- "Цены основаны на данных игроков и могут быть неточными. -- Принимая решение о покупке, руководствуйтесь собственным -- опытом, чтобы не потерять средства" imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.20, 0.15, 0.05, 0.35)) imgui.PushStyleColor(imgui.Col.Border, imgui.ImVec4(0.75, 0.60, 0.30, 0.5)) local _wbox_w = _col_w local _wbox_h = _flt_h local _wpad = 10 * d local _icon_scale = 1.4 local _icon_h = imgui.CalcTextSize(IC.warn).y * _icon_scale local _wtxt_w = _wbox_w - _wpad * 2 if imgui.BeginChild('##mh_pick_disclaimer', imgui.ImVec2(_wbox_w, _wbox_h), true, imgui.WindowFlags.NoScrollbar + imgui.WindowFlags.NoScrollWithMouse) then imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.90, 0.72, 0.35, 1)) imgui.SetCursorPos(imgui.ImVec2(_wpad, _wpad)) imgui.SetWindowFontScale(_icon_scale) imgui.Text(IC.warn) imgui.SetWindowFontScale(1.0) imgui.PopStyleColor() imgui.SetCursorPos(imgui.ImVec2(_wpad, _wpad + _icon_h + 6 * d)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.85, 0.68, 0.35, 1)) imgui.PushTextWrapPos(_wpad + _wtxt_w) imgui.TextWrapped(_wtxt) imgui.PopTextWrapPos() imgui.PopStyleColor() end imgui.EndChild() imgui.PopStyleColor(2) end imgui.Spacing() if imgui.Checkbox(_cyr5f('\xd2\xee\xeb\xfc\xea\xee\x20\xe2\xfb\xe3\xee\xe4\xed\xfb\xe5') .. '##mh_pick_og', _G.mh_pick_only_good) then _mh_pick_save_filters() end imgui.SameLine(0, 10 * d) local sort_labels = { [0] = IC.chrtl .. ' ' .. _cyr5f('\xcf\xee\x20\xe2\xfb\xe3\xee\xe4\xe5'), [1] = IC.tag .. ' ' .. _cyr5f('\xcf\xee\x20\xed\xe0\xe7\xe2\xe0\xed\xe8\xfe'), [2] = IC.coin .. ' ' .. _cyr5f('\xcf\xee\x20\xf6\xe5\xed\xe5'), } imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.12, 0.12, 0.15, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.20, 0.20, 0.25, _G._mh_wa or 1)) if imgui.Button(sort_labels[_G.mh_pick_sort] .. '##mh_pick_sort_btn', imgui.ImVec2(0, 24 * d)) then _G.mh_pick_sort = (_G.mh_pick_sort + 1) % 3 _mh_pick_save_filters() end imgui.PopStyleColor(2) imgui.Separator() -- 7. Таблица результатов. -- -- 5 колонок, каждая строка - в одну линию (без второй строки -- под названием, чтобы не удваивать высоту таблицы): -- 1) Название предмета; -- 2) Лавка - ник владельца + номер (#uid), отдельная своя -- колонка, а не довесок к названию или к тренду; -- 3) Цена в лавке; -- 4) Средняя цена; -- 5) Тренд/% и компактная иконка-кнопка GPS рядом на той же -- строке (SmallButton только с иконкой, без слова "GPS"). -- Ширины колонок пересчитаны под контент, чтобы всё влезало: -- название и ник/номер обычно самые длинные строки, поэтому -- им отдано больше места, а цены/тренд короче по своей природе -- (например "$50.000" или "99.9%") и умещаются в узкие колонки. local _c0 = 0.28 -- Название предмета local _c1 = 0.16 -- Лавка (ник + №) local _c2 = 0.15 -- Цена в лавке local _c3 = 0.15 -- Средняя цена -- Тренд/% + GPS-кнопка донабирает остаток ширины (0.26) -- автоматически - последнюю колонку imgui.Columns не задаём -- явно, она получает то, что осталось от cw_pk. if not mh_arz_data or #mh_arz_data == 0 then imgui.Spacing() imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.55, 0.55, 0.60, 1)) imgui.TextWrapped(_cyr5f( '\xd1\xed\xe0\xf7\xe0\xeb\xe0\x20\xee\xf2\xea\xf0\xee\xe9\xf2\xe5\x20\xe2\xea\xeb\xe0\xe4\xea\xf3\x20' .. '\xcb\xe0\xe2\xea\xe8\x20\xf7\xf2\xee\xe1\xfb\x20\xf1\xee\xe1\xf0\xe0\xf2\xfc\x20\xe4\xe0\xed\xed\xfb\xe5' )) imgui.PopStyleColor() elseif #rows == 0 then imgui.Spacing() imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.55, 0.55, 0.60, 1)) imgui.TextWrapped(_cyr5f('\xcd\xe5\xf2\x20\xef\xee\xe4\xf5\xee\xe4\xff\xf9\xe8\xf5\x20\xf2\xee\xe2\xe0\xf0\xee\xe2\x20\xef\xee\xe4\x20\xe1\xfe\xe4\xe6\xe5\xf2')) imgui.PopStyleColor() else imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.55, 0.55, 0.60, 1)) imgui.Text(_cyr5f('\xcd\xe0\xe9\xe4\xe5\xed\xee\x20\xef\xee\xe7\xe8\xf6\xe8\xe9') .. ': ' .. #rows) imgui.PopStyleColor() if imgui.BeginChild('##mh_pick_scroll', imgui.ImVec2(-1, 0), false) then _dpn1w() local _pick_row_x0 = imgui.GetCursorScreenPos().x imgui.Columns(5, '##mh_pick_cols', false) imgui.SetColumnWidth(0, cw_pk * _c0) imgui.SetColumnWidth(1, cw_pk * _c1) imgui.SetColumnWidth(2, cw_pk * _c2) imgui.SetColumnWidth(3, cw_pk * _c3) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.60, 0.60, 0.65, 1)) imgui.Text(_cyr5f('\xcd\xe0\xe7\xe2\xe0\xed\xe8\xe5')); imgui.NextColumn() imgui.Text(_cyr5f('\xcb\xe0\xe2\xea\xe0')); imgui.NextColumn() -- "Лавка" imgui.Text(_cyr5f('\xd6\xe5\xed\xe0\x20\xe2\x20\xeb\xe0\xe2\xea\xe5')); imgui.NextColumn() imgui.Text(_cyr5f('\xd1\xf0\xe5\xe4\xed\xff\xff\x20\xf6\xe5\xed\xe0')); imgui.NextColumn() imgui.Text(_cyr5f('\xd2\xf0\xe5\xed\xe4') .. ' / %'); imgui.NextColumn() imgui.PopStyleColor() imgui.Separator() -- РУЧНОЙ CLIPPING (аналог ImGuiListClipper). -- -- Раньше здесь было "for ri, r in ipairs(rows) do ... end" -- без всяких условий - то есть ВСЕ строки (в примере на -- скрине - 566 позиций) целиком проходили через ImGui -- КАЖДЫЙ КАДР: Text/PushStyleColor/SmallButton/ -- IsItemHovered/GetCursorScreenPos/AddRectFilled на -- каждую строку, даже те, что визуально скрыты за -- скроллом сверху/снизу. Именно это и грузило кадр, -- а не пересчёт данных (тот уже был закэширован). -- -- Раз высота строки теперь фиксированная и одинаковая -- для всех строк (row_h_fixed, одна линия текста - см. -- ниже, где "Лавка" обрезается в одну строку вместо -- TextWrapped на 1-2 строки), можно посчитать видимый -- диапазон индексов по прокрутке БЕЗ прохода по всем -- строкам, и отрисовать только его: -- 1) пустой Dummy() высотой "сколько строк над экраном" -- (даёт скроллбару верную общую высоту, без реальной -- отрисовки того, что не видно); -- 2) сами видимые строки (+ небольшой запас сверху/ -- снизу, чтобы при быстром скролле не мигало); -- 3) пустой Dummy() высотой "сколько строк под экраном". local row_h_fixed = imgui.GetTextLineHeightWithSpacing() + 4 * d local _scroll_y = imgui.GetScrollY() local _view_h = imgui.GetWindowHeight() local _pad_rows = 4 -- запас строк сверху/снизу от видимой области local first_vis = math.max(1, math.floor(_scroll_y / row_h_fixed) - _pad_rows + 1) local last_vis = math.min(#rows, math.ceil((_scroll_y + _view_h) / row_h_fixed) + _pad_rows) if first_vis > 1 then imgui.Dummy(imgui.ImVec2(1, (first_vis - 1) * row_h_fixed)) imgui.NextColumn(); imgui.NextColumn(); imgui.NextColumn(); imgui.NextColumn(); imgui.NextColumn() end for ri = first_vis, last_vis do local r = rows[ri] local row_bg = nil if r.pct then if r.pct >= min_pct and min_pct > 0 then row_bg = imgui.ImVec4(0.10, 0.30, 0.10, 0.35) elseif r.pct >= 0 then row_bg = imgui.ImVec4(0.10, 0.22, 0.10, 0.20) elseif r.pct <= -15 then row_bg = imgui.ImVec4(0.30, 0.10, 0.10, 0.25) else row_bg = imgui.ImVec4(0.30, 0.10, 0.10, 0.12) end end local wp_y_top = imgui.GetCursorScreenPos().y -- Название товара кликабельно - открывает ту же -- карточку статистики товара, что и во вкладках -- "Лавки"/"Просмотренные" (см. _G.mkt_detail_*). if imgui.Selectable(_cyr5f(r.name .. '##mh_pick_nm' .. ri), false, imgui.SelectableFlags.None, imgui.ImVec2(0, row_h_fixed - 4 * d)) then _G.mkt_detail_item = r.name _G.mkt_detail_src = 'cp' _G.mkt_detail_open = true _G._dtl_win_init = nil end imgui.NextColumn() -- Лавка: ник владельца + номер, отдельная колонка, -- мельче и тусклее основного текста строки. Одна -- строка (без TextWrapped) - высота строки должна -- оставаться постоянной для корректного клиппинга. if (r.owner and r.owner ~= '') or (r.uid and r.uid ~= 0) then imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.55, 0.55, 0.62, 1)) imgui.SetWindowFontScale(0.85) local _own_txt = (r.owner and r.owner ~= '') and u8(r.owner) or '?' local _uid_txt = (r.uid and r.uid ~= 0) and ('#' .. tostring(r.uid)) or '' local _meta = _own_txt .. ((_uid_txt ~= '') and (' ' .. _uid_txt) or '') imgui.Text(_meta) imgui.SetWindowFontScale(1.0) imgui.PopStyleColor() else imgui.Text('—') end imgui.NextColumn() imgui.Text(_jsb6t(r.shop_price)); imgui.NextColumn() imgui.Text(r.avg and _jsb6t(r.avg) or '—'); imgui.NextColumn() if r.pct then local trend_up = r.pct >= 0 local tc = trend_up and imgui.ImVec4(0.35, 0.85, 0.35, 1) or imgui.ImVec4(0.90, 0.35, 0.35, 1) imgui.PushStyleColor(imgui.Col.Text, tc) imgui.Text((trend_up and (IC.up .. ' ') or (IC.dn .. ' ')) .. string.format('%.1f', r.pct) .. '%') imgui.PopStyleColor() else imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.5, 0.5, 0.5, 1)) imgui.Text('—') imgui.PopStyleColor() end imgui.SameLine(0, 8 * d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.10, 0.30, 0.10, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.14, 0.45, 0.16, _G._mh_wa or 1)) if imgui.SmallButton(IC.gps .. '##mh_pick_go' .. ri) then if r.uid and r.uid ~= 0 then sampSendChat('/findilavka ' .. tostring(r.uid)) else mh_notify('[MH] ' .. _cyr5f('\xd1\xf1\xfb\xeb\xea\xe0\x20\xed\xe0\x20\xeb\xe0\xe2\xea\xf3\x20\xed\xe5\x20\xed\xe0\xe9\xe4\xe5\xed\xe0'), 0xFFFFFF) end end if imgui.IsItemHovered() then imgui.SetTooltip('GPS') end imgui.PopStyleColor(2) imgui.NextColumn() if row_bg then local wp_y_bottom = wp_y_top + row_h_fixed imgui.GetWindowDrawList():AddRectFilled( imgui.ImVec2(_pick_row_x0, wp_y_top), imgui.ImVec2(_pick_row_x0 + cw_pk, wp_y_bottom), imgui.ColorConvertFloat4ToU32(row_bg) ) end end if last_vis < #rows then imgui.Dummy(imgui.ImVec2(1, (#rows - last_vis) * row_h_fixed)) end imgui.Columns(1) imgui.EndChild() end end imgui.EndTabItem() end imgui.EndTabBar() end end if _G.mh_tab == 3 then local cw_lv = imgui.GetWindowContentRegionWidth() local left_w = math.floor(cw_lv * 0.42) local right_w = cw_lv - left_w - 8*d if fh_lv_autosell_running then imgui.TextColored(imgui.ImVec4(1,0.75,0,1), _cyr5f(' Авто-выкладка: '..fh_lv_autosell_status)) imgui.ProgressBar(-1*os.clock(), imgui.ImVec2(-1, 5*d)) if imgui.Button(IC.circs..' '.._cyr5f('Стоп##asstop'), imgui.ImVec2(-1,0)) then fh_lv_autosell_running=false end else if fh_lv_autosell_status ~= '' then imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), _cyr5f(' '..fh_lv_autosell_status)) else imgui.TextDisabled(_cyr5f(' Лево: инвентарь лавки | Право: пресет')) end local bw3 = math.floor((cw_lv - 16*d) / 3) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(bb_r, bb_g, bb_b, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(bb_r*1.35, bb_g*1.35, bb_b*1.3, _G._mh_wa or 1)) if imgui.Button(IC.boxes..' '.._cyr5f('Скан инвентаря##asinv'), imgui.ImVec2(bw3,0)) then _vcz9h() end imgui.PopStyleColor(2) imgui.SameLine(0,8*d) local _inv_ver_key = tostring(#fh_lv_inventory) .. '|' .. tostring(#fh_lv_autosell_preset) if _G._as_inv_names_ver ~= _inv_ver_key then _G._as_inv_names_ver = _inv_ver_key _G._as_inv_names = {} for _, _iv in ipairs(fh_lv_inventory) do _G._as_inv_names[_iv.name] = (_iv.count or 0) end _G._as_can_clean = false for _, _pp in ipairs(fh_lv_autosell_preset) do if (_pp.qty or 0) == 0 or (_G._as_inv_names[_pp.name] == nil and #fh_lv_inventory > 0) then _G._as_can_clean = true; break end end end local _inv_names = _G._as_inv_names or {} local _can_clean = _G._as_can_clean or false if _can_clean then imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.8, 0.3, 0.2, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(1.0, 0.4, 0.3, 1)) if imgui.Button(_cyr5f('Очистить пресет##clean_preset'), imgui.ImVec2(bw3, 0)) then local _new_preset = {} for _, _pp in ipairs(fh_lv_autosell_preset) do local _inv_qty = _inv_names[_pp.name] local _keep = (_pp.qty or 0) > 0 if _keep and #fh_lv_inventory > 0 and _inv_qty == nil then _keep = false end if _keep then table.insert(_new_preset, _pp) end end fh_lv_autosell_preset = _new_preset settings.sell_preset = fh_lv_autosell_preset _wfn7p() end imgui.PopStyleColor(2) else imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.3,0.3,0.3,0.5)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.3,0.3,0.3,0.5)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.5,0.5,0.5,0.7)) imgui.Button(_cyr5f('Очистить пресет##clean_preset_dis'), imgui.ImVec2(bw3, 0)) imgui.PopStyleColor(3) end imgui.SameLine(0,8*d) local _has_preset = #fh_lv_autosell_preset > 0 local _has_inv = #fh_lv_inventory > 0 local _sell_can = _has_preset and _has_inv if _sell_can then local _as_glow = true local _br = 0.05 local _bg = 0.55 local _bb = 0.10 imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_br, _bg, _bb, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(_br+0.05, _bg+0.18, _bb+0.05, _G._mh_wa or 1)) if imgui.Button(IC.bolt..' '.._cyr5f('Выставить продажу##asrun'), imgui.ImVec2(bw3,0)) then lua_thread.create(function() wait(0) mh_lavka_inv_ready = false mh_lavka_inv = {} if true then mh_notify('[MH] {ffaa00}Открываю лавку...', 0xFFFFFF) if sampIsDialogActive() then sampSendDialogResponse(sampGetCurrentDialogId(), 0, 0, '') wait(150) end _yzr1t(8, 7, -1, '') local _tw = 0 while _tw < 60 do wait(50); _tw = _tw + 1 if sampIsDialogActive() and sampGetCurrentDialogId() == 3040 then break end end if not (sampIsDialogActive() and sampGetCurrentDialogId() == 3040) then mh_notify('[MH] {ff4444}Лавка не открылась. Подойдите ближе.', 0xFFFFFF) return end wait(80) sampSendDialogResponse(3040, 1, 0, '') local _tw2 = 0 while not mh_lavka_inv_ready and _tw2 < 100 do wait(50); _tw2 = _tw2 + 1 end if not mh_lavka_inv_ready then mh_notify('[MH] {ff4444}Инвентарь лавки не загрузился.', 0xFFFFFF) return end wait(100) fh_lv_autostart_enabled = true _wmc7r() end end) end imgui.PopStyleColor(2) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Открою лавку и выставлю товары по пресету')) end else imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.3,0.3,0.3,0.5)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.3,0.3,0.3,0.5)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.5,0.5,0.5,0.7)) imgui.Button(IC.bolt..' '.._cyr5f('Выставить продажу##asrun_dis'), imgui.ImVec2(bw3,0)) imgui.PopStyleColor(3) if imgui.IsItemHovered() then local _why = not _has_preset and _cyr5f('Пресет пуст') or _cyr5f('Сначала нажмите Скан инвентаря') imgui.SetTooltip(_why) end end end imgui.Separator() if #fh_lv_autosell_preset > 0 then local total_sell = 0 for _, asp in ipairs(fh_lv_autosell_preset) do total_sell = total_sell + (asp.price or 0) * (asp.qty or 1) end imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), _cyr5f(' Итого выкладки: $' .. _kcr3y(total_sell))) end local panel_h = imgui.GetWindowHeight() - 130*d if imgui.BeginChild('##inv_panel', imgui.ImVec2(left_w, panel_h), true) then _dpn1w() imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f('Инвентарь ('..#fh_lv_inventory..'):')) imgui.Separator() if not _G.as_inv_srch_buf then _G.as_inv_srch_buf = imgui.new.char[64]('') end if not _G.as_inv_srch then _G.as_inv_srch = '' end imgui.PushItemWidth(imgui.GetContentRegionAvail().x - 32*d) if imgui.InputTextWithHint('##asinvsrch', _cyr5f('Поиск...'), _G.as_inv_srch_buf, 64) then _G.as_inv_srch = fh_lower(u8:decode(ffi.string(_G.as_inv_srch_buf))):match('^%s*(.-)%s*$') end imgui.PopItemWidth() imgui.SameLine(0, 3*d) do local _hsi = ffi.string(_G.as_inv_srch_buf) ~= '' imgui.PushStyleColor(imgui.Col.Button, _hsi and imgui.ImVec4(0.38,0.08,0.08,1) or imgui.ImVec4(0.12,0.12,0.12,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.55,0.12,0.12,_G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, _hsi and imgui.ImVec4(1,0.4,0.4,1) or imgui.ImVec4(0.3,0.3,0.3,1)) if imgui.Button(IC.x..'##asinvsrchclr', imgui.ImVec2(28*d, 0)) and _hsi then ffi.fill(_G.as_inv_srch_buf, 64, 0); _G.as_inv_srch = '' end imgui.PopStyleColor(3) end if fh_lv_inv_scanning then imgui.TextColored(imgui.ImVec4(1,0.7,0,1), _cyr5f(' Скан...')) imgui.ProgressBar(-1*os.clock(), imgui.ImVec2(-1,5*d)) elseif #fh_lv_inventory == 0 then imgui.TextDisabled(_cyr5f(' Пусто.')) imgui.TextDisabled(_cyr5f(' Нажмите "Скан лавки"')) imgui.TextDisabled(_cyr5f(' или /mm у прилавка')) else for _, inv in ipairs(fh_lv_inventory) do if _G.as_inv_srch and _G.as_inv_srch ~= '' and not fh_lower(inv.name):find(_G.as_inv_srch, 1, true) then else local already = false for _, p in ipairs(fh_lv_autosell_preset) do if p.name == inv.name then already=true; break end end local lbl = _cyr5f(inv.name..' '..inv.count..' шт.') imgui.Spacing() if not already then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(sb_r, sb_g, sb_b, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.5, sb_g*1.5, sb_b*1.5, _G._mh_wa or 1)) if imgui.Button(lbl..'##ainv_'..inv.name, imgui.ImVec2(-1,0)) then local _add_cp_e = fh_mkt_prices[inv.name] local _add_best = 0 if _add_cp_e then if _add_cp_e.cp_hist and #_add_cp_e.cp_hist > 0 then local _add_s7 = _mjg5t(_add_cp_e.cp_hist, 7) if _add_s7 then _add_best = _add_s7.avg end end if _add_best == 0 then _add_best = _add_cp_e.s_avg or _add_cp_e.b_avg or 0 end end if _add_best == 0 then local _lv = fh_mkt_lavka[inv.name] if _lv then _add_best = _lv.b_avg or _lv.s_avg or 0 end end table.insert(fh_lv_autosell_preset,{name=inv.name,price=_add_best,qty=inv.count}) if not _G.as_price_buf then _G.as_price_buf={} end if not _G.as_qty_buf then _G.as_qty_buf={} end local _new_idx = #fh_lv_autosell_preset _G.as_price_buf[_new_idx]=imgui.new.char[32](_vnh1j(_add_best)) _G.as_qty_buf[_new_idx]=imgui.new.char[16](tostring(inv.count)) _tcv8f() end imgui.PopStyleColor(2) else imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.12,0.12,0.12,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.45,0.45,0.45,1)) imgui.Button(lbl..'##ainv_'..inv.name, imgui.ImVec2(-1,0)) imgui.PopStyleColor(2) end end end end imgui.EndChild() end imgui.SameLine(0,8*d) if imgui.BeginChild('##as_preset', imgui.ImVec2(right_w, panel_h), true) then _dpn1w() local _sbw_p = (settings.interface.scrollbar_w or 12)*d local cw_p = right_w - 12*d - _sbw_p imgui.PushItemWidth(cw_p * 0.50) local preset_names = {} for pi, pr in ipairs(settings.presets or {}) do local pnm = pr.name or ('Пресет '..pi) preset_names[pi] = u8(pnm) end local cur_name = preset_names[fh_active_preset_idx] or u8('Пресет 1') if imgui.BeginCombo('##preset_sel', cur_name) then for pi, pname in ipairs(preset_names) do if imgui.Selectable(pname..'##ps'..pi, pi==fh_active_preset_idx) then fh_active_preset_idx = pi settings.active_preset = pi fh_lv_autosell_preset = settings.presets[pi].items or {} _G.as_price_buf = nil; _G.as_qty_buf = nil _wfn7p() end end imgui.EndCombo() end imgui.PopItemWidth() imgui.SameLine(0,3*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(bb_r, bb_g, bb_b, 1)) if imgui.Button(IC.circp..'##addp', imgui.ImVec2(cw_p*0.18,0)) then local np = {name='Preset '..((#(settings.presets or {}))+1), items={}} if not settings.presets then settings.presets={} end table.insert(settings.presets, np) fh_active_preset_idx = #settings.presets settings.active_preset = fh_active_preset_idx fh_lv_autosell_preset = {} _G.as_price_buf=nil; _G.as_qty_buf=nil _wfn7p() end imgui.PopStyleColor() imgui.SameLine(0,2*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.5,0.08,0.08,1)) if imgui.Button(IC.trash..'##delp', imgui.ImVec2(cw_p*0.15,0)) then if #(settings.presets or {}) > 1 then _G._del_preset_confirm = (_G._del_preset_confirm == 'sell') and nil or 'sell' else mh_notify('[MH] {ff4444}Нельзя удалить единственный пресет.', 0xFFFFFF) end end imgui.PopStyleColor() imgui.SameLine(0,2*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.25,0.15,0,1)) if imgui.Button(IC.pen..' '.._cyr5f('Имя##renp'), imgui.ImVec2(-1,0)) then _G._renaming_preset = not _G._renaming_preset local cur = settings.presets and settings.presets[fh_active_preset_idx] _G._new_preset_name_buf = imgui.new.char[32](cur and cur.name or '') end imgui.PopStyleColor() if _G._del_preset_confirm == 'sell' then local _hw2 = imgui.GetContentRegionAvail().x imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.7,0.1,0.1,1)) if imgui.Button(_cyr5f('Удалить пресет? Да##delpyes'), imgui.ImVec2(_hw2*0.6,0)) then _G._del_preset_confirm = nil table.remove(settings.presets, fh_active_preset_idx) fh_active_preset_idx = math.max(1, fh_active_preset_idx - 1) settings.active_preset = fh_active_preset_idx fh_lv_autosell_preset = settings.presets[fh_active_preset_idx].items or {} _G.as_price_buf=nil; _G.as_qty_buf=nil _wfn7p() end imgui.PopStyleColor() imgui.SameLine(0,4*d) if imgui.Button(_cyr5f('Отмена##delpno'), imgui.ImVec2(-1,0)) then _G._del_preset_confirm = nil end end if _G._renaming_preset then if not _G._new_preset_name_buf then _G._new_preset_name_buf=imgui.new.char[32]('') end imgui.PushItemWidth(-1) if imgui.InputText('##pren', _G._new_preset_name_buf, 32, imgui.InputTextFlags.EnterReturnsTrue) then local nm = ffi.string(_G._new_preset_name_buf):match('^%s*(.-)%s*$') local _ok_nm, nm_dec = pcall(function() return require('encoding').UTF8:decode(nm) end) if _ok_nm and nm_dec and #nm_dec > 0 then nm = nm_dec end if nm~='' and settings.presets and settings.presets[fh_active_preset_idx] then settings.presets[fh_active_preset_idx].name = nm _wfn7p() end _G._renaming_preset = false end imgui.PopItemWidth() end if not _G.as_sort_mode then _G.as_sort_mode = 0 end local sort_labels = {_cyr5f('Сорт: А-Я'), _cyr5f('Сорт: Цена'), _cyr5f('Сорт: Рынок')} if imgui.Button(sort_labels[_G.as_sort_mode+1]..'##assort', imgui.ImVec2(0,0)) then _G.as_sort_mode = (_G.as_sort_mode + 1) % 3 if _G.as_sort_mode == 1 then table.sort(fh_lv_autosell_preset, function(a,b) return (a.price or 0) > (b.price or 0) end) elseif _G.as_sort_mode == 2 then local _qty_cache = {} for _i_qc = 1, #fh_lv_autosell_preset do local _it = fh_lv_autosell_preset[_i_qc] local _e = _it and fh_mkt_prices[_it.name] local _s = _e and _e.cp_hist and _mjg5t(_e.cp_hist, 30) or nil _qty_cache[_it] = (_s and _s.qty) or 0 end table.sort(fh_lv_autosell_preset, function(a,b) return (_qty_cache[a] or 0) > (_qty_cache[b] or 0) end) else table.sort(fh_lv_autosell_preset, function(a,b) return (a.name or '') < (b.name or '') end) end _G.as_price_buf=nil; _G.as_qty_buf=nil local active_p = settings.presets and settings.presets[fh_active_preset_idx] if active_p then active_p.items = fh_lv_autosell_preset end settings.autosell_preset = fh_lv_autosell_preset _wfn7p() end imgui.SameLine(0,6*d) imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), _cyr5f('Пресет ('..#fh_lv_autosell_preset..')')) imgui.SameLine(0,6*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(sb_r, sb_g, sb_b, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.4, sb_g*1.4, sb_b*1.4, _G._mh_wa or 1)) if imgui.Button(IC.save..' '.._cyr5f('\xd1\xee\xf5\xf0.\xcf\xf0\xe5\xf1\xe5\xf2##assave'), imgui.ImVec2(0,0)) then _tcv8f() mh_notify('[MH] {00cc00}\xcf\xf0\xe5\xf1\xe5\xf2 \xf1\xee\xf5\xf0\xe0\xed\xb8\xed.', 0xFFFFFF) end imgui.PopStyleColor(2) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(sb_r, sb_g, sb_b, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.4, sb_g*1.4, sb_b*1.4, _G._mh_wa or 1)) if imgui.Button(IC.warn..' '.._cyr5f('\xcc\xe0\xea\xf1 \xe2\xf1\xe5##asmaxall'), imgui.ImVec2(-1, 0)) then local updated = 0 for _maxasi, _maxasp in ipairs(fh_lv_autosell_preset) do for _, _inv in ipairs(fh_lv_inventory) do if _inv.name:lower() == _maxasp.name:lower() and _inv.count > 0 then _maxasp.qty = _inv.count if _G.as_qty_buf and _G.as_qty_buf[_maxasi] then _G.as_qty_buf[_maxasi] = imgui.new.char[16](tostring(_inv.count)) end updated = updated + 1 break end end end _G.as_qty_buf = nil _tcv8f() mh_notify('[MH] {00cc00}\xcc\xe0\xea\xf1 \xe2\xf1\xe5: \xee\xe1\xed\xee\xe2\xeb\xe5\xed\xee ' .. updated .. ' \xf2\xee\xe2\xe0\xf0\xee\xe2.', 0xFFFFFF) end imgui.PopStyleColor(2) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xfc \xea\xee\xeb-\xe2\xee \xe8\xe7 \xe8\xed\xe2\xe5\xed\xf2\xe0\xf0\xff\n\xe4\xeb\xff \xe2\xf1\xe5\xf5 \xf2\xee\xe2\xe0\xf0\xee\xe2 \xef\xf0\xe5\xf1\xe5\xf2\xe0 \xf1\xf0\xe0\xe7\xf3')) end imgui.Separator() if not _G.as_preset_srch_buf then _G.as_preset_srch_buf = imgui.new.char[64]('') end if not _G.as_preset_srch then _G.as_preset_srch = '' end imgui.PushItemWidth(imgui.GetContentRegionAvail().x - 32*d) if imgui.InputTextWithHint('##aspresetsrch', _cyr5f('Поиск...'), _G.as_preset_srch_buf, 64) then _G.as_preset_srch = fh_lower(u8:decode(ffi.string(_G.as_preset_srch_buf))):match('^%s*(.-)%s*$') end imgui.PopItemWidth() imgui.SameLine(0, 3*d) do local _hsp = ffi.string(_G.as_preset_srch_buf) ~= '' imgui.PushStyleColor(imgui.Col.Button, _hsp and imgui.ImVec4(0.38,0.08,0.08,1) or imgui.ImVec4(0.12,0.12,0.12,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.55,0.12,0.12,_G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, _hsp and imgui.ImVec4(1,0.4,0.4,1) or imgui.ImVec4(0.3,0.3,0.3,1)) if imgui.Button(IC.x..'##aspresetsrchclr', imgui.ImVec2(28*d, 0)) and _hsp then ffi.fill(_G.as_preset_srch_buf, 64, 0); _G.as_preset_srch = '' end imgui.PopStyleColor(3) end if #fh_lv_autosell_preset == 0 then imgui.TextDisabled(_cyr5f(' Пусто. Кликните')) imgui.TextDisabled(_cyr5f(' товар слева.')) end local _cur_srv_asp = (_G.arz_srv_sel and _G.arz_srv_sel[0]) or _mpf7d() or 0 local _asp_price_ver = tostring(_G._mh_db_ver or 0)..'|' ..tostring(_G._mh_shop_ver or 0)..'|' ..tostring(_G._mh_deals_cache_ver or 0)..'|' ..tostring(#fh_lv_autosell_preset)..'|' ..tostring(_cur_srv_asp) if _G._asp_frame_cache_ver ~= _asp_price_ver then _G._asp_frame_cache_ver = _asp_price_ver _G._asp_frame_cache = {} local _lvc = _G._lv_shops_cache or {} for _fci, _fca in ipairs(fh_lv_autosell_preset) do local _mp = _mh_get_mkt_price(_fca.name) local _nlo = _fca.name:lower() local _lve = _lvc[_nlo] local _avg7, _avg30, _today, _sell_lv, _buy_lv if _mp then _avg7 = (_mp.avg7 and _mp.avg7 > 0) and _mp.avg7 or nil _avg30 = (_mp.avg30 and _mp.avg30 > 0) and _mp.avg30 or nil if _avg7 and _avg30 then _today = math.min(_avg7, _avg30) else _today = _avg7 or _avg30 end end _sell_lv = _lve and _lve.sell or nil _buy_lv = _lve and _lve.buy or nil _G._asp_frame_cache[_fci] = { avg_cp = _today, today_cp = (_mp and _mp.today and _mp.today > 0) and _mp.today or _today, sell_lv = _sell_lv, buy_lv = _buy_lv, } end end local del_as_i = nil for asi, asp in ipairs(fh_lv_autosell_preset) do if _G.as_preset_srch and _G.as_preset_srch ~= '' and not fh_lower(asp.name):find(_G.as_preset_srch, 1, true) then else if not _G.as_price_buf then _G.as_price_buf={} end if not _G.as_qty_buf then _G.as_qty_buf={} end if not _G.as_price_buf[asi] then _G.as_price_buf[asi]=imgui.new.char[32](_vnh1j(asp.price or 0)) end if not _G.as_qty_buf[asi] then _G.as_qty_buf[asi]=imgui.new.char[16](tostring(asp.qty or 1)) end local _fc = _G._asp_frame_cache and _G._asp_frame_cache[asi] or {} local _asp_avg_cp = _fc.avg_cp local _asp_today_cp = _fc.today_cp local _sell_lv = _fc.sell_lv local _buy_lv = _fc.buy_lv local _asp_avg_lv = _sell_lv or _buy_lv imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1,1,1,1)) if imgui.Selectable(_cyr5f(asp.name..'##asp_nm'..asi), false, 0, imgui.ImVec2(0,0)) then _G.mkt_detail_item = asp.name _G.mkt_detail_src = fh_mkt_prices[asp.name] and 'cp' or 'tags' _G.mkt_detail_pos = nil _G.mkt_detail_open = true end imgui.PopStyleColor() if _asp_avg_cp then imgui.SameLine(0,8*d) imgui.TextColored(imgui.ImVec4(1,0.75,0.2,1), _cyr5f('Рынок:$'.._kcr3y(_asp_avg_cp))) end if _asp_avg_lv then if _buy_lv then imgui.TextColored(imgui.ImVec4(0.5,0.7,1,1), _cyr5f('Скупают:$'.._kcr3y(_buy_lv))) imgui.SameLine(0,6*d) end if _sell_lv then imgui.TextColored(imgui.ImVec4(0.4,0.95,0.4,1), _cyr5f('Продают:$'.._kcr3y(_sell_lv))) end end do local fw = right_w - 14*d - _sbw_p local x_w2 = 26*d local qty_w2 = 50*d local price_w2 = fw - qty_w2 - x_w2 - 6*d if price_w2 < 40*d then price_w2 = 40*d end imgui.PushItemWidth(price_w2) if imgui.InputText('##asp'..asi, _G.as_price_buf[asi], 32) then local _raw = ffi.string(_G.as_price_buf[asi]) local _val = _sxp3d(_raw) if _val > 0 then asp.price = _val end _G.as_price_buf[asi] = imgui.new.char[32](_vnh1j(asp.price or 0)) _tcv8f() end imgui.PopItemWidth() imgui.SameLine(0,3*d) imgui.PushItemWidth(qty_w2) if imgui.InputText('##asq'..asi, _G.as_qty_buf[asi], 16) then asp.qty = tonumber(ffi.string(_G.as_qty_buf[asi])) or asp.qty _tcv8f() end imgui.PopItemWidth() imgui.SameLine(0,3*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.45,0.08,0.08,1)) if imgui.Button(IC.x..'##asd'..asi, imgui.ImVec2(x_w2, 0)) then del_as_i=asi end imgui.PopStyleColor() imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.08,0.25,0.45,1)) if imgui.Button(IC.up..'##asmu'..asi, imgui.ImVec2((fw-2*d)/2, 0)) then if asi > 1 then _G._as_swap = fh_lv_autosell_preset[asi] fh_lv_autosell_preset[asi] = fh_lv_autosell_preset[asi-1] fh_lv_autosell_preset[asi-1] = _G._as_swap _G.as_price_buf=nil; _G.as_qty_buf=nil local _ap2 = settings.presets and settings.presets[fh_active_preset_idx] if _ap2 then _ap2.items = fh_lv_autosell_preset end _wfn7p() end end imgui.PopStyleColor() imgui.SameLine(0,2*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.08,0.25,0.45,1)) if imgui.Button(IC.dn..'##asmd'..asi, imgui.ImVec2((fw-2*d)/2, 0)) then if asi < #fh_lv_autosell_preset then _G._as_swap = fh_lv_autosell_preset[asi] fh_lv_autosell_preset[asi] = fh_lv_autosell_preset[asi+1] fh_lv_autosell_preset[asi+1] = _G._as_swap _G.as_price_buf=nil; _G.as_qty_buf=nil local _ap2 = settings.presets and settings.presets[fh_active_preset_idx] if _ap2 then _ap2.items = fh_lv_autosell_preset end _wfn7p() end end imgui.PopStyleColor() end do local _has_nw = _asp_avg_cp ~= nil local _has_rn = _asp_avg_cp ~= nil local _has_sg = _asp_today_cp ~= nil local _has_lv = _asp_avg_lv ~= nil local _has_sk = _buy_lv ~= nil local _has_pr = _sell_lv ~= nil local _btn_cnt = (_has_nw and 1 or 0) + (_has_rn and 1 or 0) + (_has_sg and 1 or 0) + (_has_sk and 1 or 0) + (_has_pr and 1 or 0) if _btn_cnt < 1 then _btn_cnt = 1 end local _bw4 = (right_w - 14*d - math.max(0,_btn_cnt-1)*3*d) / _btn_cnt if _has_nw then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.40,0.25,0.02,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.55,0.35,0.04, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1,0.85,0.3,1)) if imgui.Button(_cyr5f('Неделя##ascp'..asi), imgui.ImVec2(_bw4, 0)) then asp.price = _asp_avg_cp _G.as_price_buf[asi] = imgui.new.char[32](_vnh1j(_asp_avg_cp)) _tcv8f() end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Ср. 7 дн: $'.._kcr3y(_asp_avg_cp))) end imgui.SameLine(0,3*d) end if _has_rn then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10*0.78,_G._mh_sb_g or 0.45*0.78,_G._mh_sb_b or 0.10*0.78,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.1,sb_g*1.1,sb_b*1.1, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.4,1,0.6,1)) local _n_shown = _has_nw and 1 or 0 local _rn_w = (_n_shown > 0) and _bw4 or (right_w - 14*d) if imgui.Button(_cyr5f('Рынок##asrn'..asi), imgui.ImVec2(_rn_w, 0)) then asp.price = _asp_avg_cp _G.as_price_buf[asi] = imgui.new.char[32](_vnh1j(_asp_avg_cp)) _tcv8f() end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Рынок (7дн): $'.._kcr3y(_asp_avg_cp))) end imgui.SameLine(0,3*d) end if _has_sg then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.0,0.28,0.50,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.0,0.40,0.70, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.5,0.85,1,1)) if imgui.Button(_cyr5f('Сегодня##astoday'..asi), imgui.ImVec2(_bw4, 0)) then local _tcp = tonumber(_asp_today_cp) or 0 asp.price = math.floor(_tcp) _G.as_price_buf[asi] = imgui.new.char[32](_vnh1j(math.floor(_tcp))) _tcv8f() end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Цена сегодня: $'.._kcr3y(_asp_today_cp))) end imgui.SameLine(0,3*d) end if _has_sk then local _sk_price = _buy_lv imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.30,0.08,0.42,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.42,0.12,0.58, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.85,0.55,1,1)) if imgui.Button(_cyr5f('Скупка##assk'..asi), imgui.ImVec2(_bw4, 0)) then asp.price = _sk_price _G.as_price_buf[asi] = imgui.new.char[32](_vnh1j(_sk_price)) _tcv8f() end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Ср. цена скупки: $'.._kcr3y(_sk_price))) end imgui.SameLine(0,3*d) end if _has_pr then local _pr_price = _sell_lv imgui.PushStyleColor(imgui.Col.Button, _mh_bc(sb_r*0.8, sb_g*0.9, sb_b*0.8, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.2, sb_g*1.2, sb_b*1.2, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.5,1,0.5,1)) if imgui.Button(_cyr5f('Продажа##aspr'..asi), imgui.ImVec2(_bw4, 0)) then asp.price = _pr_price _G.as_price_buf[asi] = imgui.new.char[32](_vnh1j(_pr_price)) _tcv8f() end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Ср. цена продажи: $'.._kcr3y(_pr_price))) end end end imgui.Separator() end end if del_as_i then table.remove(fh_lv_autosell_preset,del_as_i); _G.as_price_buf=nil; _G.as_qty_buf=nil _tcv8f() end imgui.EndChild() end end if _G.mh_tab == 4 then if _G._ab_tab_prev ~= 4 then _G._abp_price_cache_key = nil _G._ab_diff_ver = nil end _G._ab_tab_prev = 4 local cw_lv2 = imgui.GetWindowContentRegionWidth() local left_w2 = math.floor(cw_lv2 * 0.42) local right_w2 = cw_lv2 - left_w2 - 8*d if fh_lv_autobuy_running then imgui.TextColored(imgui.ImVec4(1,0.75,0,1), _cyr5f(' Авто-скуп: '..fh_lv_autobuy_status)) imgui.ProgressBar(-1*os.clock(), imgui.ImVec2(-1, 5*d)) if imgui.Button(IC.circs..' '.._cyr5f('Стоп##abstop'), imgui.ImVec2(-1,0)) then fh_lv_autobuy_running=false end else if fh_lv_autobuy_status ~= '' then imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), _cyr5f(' '..fh_lv_autobuy_status)) else imgui.TextDisabled(_cyr5f(' Лево: база цен | Право: пресет скупа')) end do local _bal = 0 pcall(function() _bal = getPlayerMoney(PLAYER_PED) end) local _preset_cnt = #fh_lv_autobuy_preset local _has_preset = _preset_cnt > 0 if not _G.ab_budget_open then _G.ab_budget_open = false end if not _G.ab_budget_buf then _G.ab_budget_buf = imgui.new.char[32](_vnh1j(_bal)) end local _bw_dist = imgui.GetWindowContentRegionWidth() * 0.42 imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.25,0.15,0.42,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.38,0.22,0.62, _G._mh_wa or 1)) if imgui.Button(IC.coin..' '.._cyr5f('Распределить бюджет##abdist'), imgui.ImVec2(_bw_dist, 0)) then if not _has_preset then mh_notify('[MH] {ffaa44}Пресет скупки пуст', 0xFFFFFF) else _G.ab_budget_open = not _G.ab_budget_open if _G.ab_budget_open then _G.ab_budget_buf = imgui.new.char[32](_vnh1j(_bal)) end end end imgui.PopStyleColor(2) imgui.SameLine(0, 6*d) if _G.ab_budget_open then imgui.Spacing() imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.12,0.08,0.20, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Border, imgui.ImVec4(0.55,0.30,0.90,0.8)) if imgui.BeginChild('##ab_budget_pop', imgui.ImVec2(-1, 60*d), true) then imgui.Spacing() imgui.TextColored(imgui.ImVec4(0.75,0.55,1,1), IC.coin..' ') imgui.SameLine(0,4*d) imgui.TextDisabled(_cyr5f('Бюджет на скупку $:')) imgui.SameLine(0,6*d) imgui.PushItemWidth(130*d) imgui.PushStyleColor(imgui.Col.FrameBg, imgui.ImVec4(0.18,0.10,0.30, _G._mh_wa or 1)) if imgui.InputText('##ab_budget_inp', _G.ab_budget_buf, 32) then local _raw_v = ffi.string(_G.ab_budget_buf) local _num_v = tonumber((_raw_v:gsub('[%.]',''))) if _num_v and _num_v > 0 then local _fmt_v = _vnh1j(_num_v) if _fmt_v ~= _raw_v and not _raw_v:match('%.$') then _G.ab_budget_buf = imgui.new.char[32](_fmt_v) end end end imgui.PopStyleColor() imgui.PopItemWidth() imgui.SameLine(0,6*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10,_G._mh_sb_g or 0.45,_G._mh_sb_b or 0.10,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.44,sb_g*1.44,sb_b*1.44, _G._mh_wa or 1)) if imgui.Button(IC.chk..' '.._cyr5f('Применить##abdoapply'), imgui.ImVec2(0,0)) then local _inp_raw = ffi.string(_G.ab_budget_buf) local _budget = _sxp3d(_inp_raw) if _budget > 0 and _has_preset then local _n = #fh_lv_autobuy_preset local _assigned = {} for _abi = 1, _n do _assigned[_abi] = 0 end local _sorted_idx = {} for _abi = 1, _n do table.insert(_sorted_idx, _abi) end table.sort(_sorted_idx, function(a, b) return (fh_lv_autobuy_preset[a].max_price or 1) > (fh_lv_autobuy_preset[b].max_price or 1) end) local _remaining = _budget local _items_left = _n for _, _abi in ipairs(_sorted_idx) do local _price = math.max(1, fh_lv_autobuy_preset[_abi].max_price or 1) local _share = math.floor(_remaining / _items_left) local _qty = math.floor(_share / _price) if _qty == 0 and _remaining >= _price then _qty = 1 end _assigned[_abi] = _qty _remaining = _remaining - _qty * _price _items_left = _items_left - 1 end if not _G.ab_qty_buf then _G.ab_qty_buf = {} end local _total_qty = 0 for _abi, _abp in ipairs(fh_lv_autobuy_preset) do local _new_qty = _assigned[_abi] or 0 _abp.qty = _new_qty _abp.target_qty = _new_qty _total_qty = _total_qty + _new_qty _G.ab_qty_buf[_abi] = imgui.new.char[16](tostring(_new_qty)) end settings.autobuy_preset = fh_lv_autobuy_preset _wfn7p() mh_notify('[MH] {aaffaa}Бюджет $'.._kcr3y(_budget)..' распределён по '.._n..' товарам (по $'.._kcr3y(_share_each)..' на товар, итого '..tostring(_total_qty)..' шт)', 0xFFFFFF) _G.ab_budget_open = false else mh_notify('[MH] {ff4444}Введите сумму больше 0', 0xFFFFFF) end end imgui.PopStyleColor(2) imgui.SameLine(0,4*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.35,0.08,0.08,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.55,0.12,0.12, _G._mh_wa or 1)) if imgui.Button(IC.x..'##abbudgetclose', imgui.ImVec2(0,0)) then _G.ab_budget_open = false end imgui.PopStyleColor(2) imgui.EndChild() end imgui.PopStyleColor(2) imgui.Spacing() end end imgui.PushStyleColor(imgui.Col.Button, _mh_bc(bb_r, bb_g, bb_b, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(bb_r*1.35, bb_g*1.35, bb_b*1.35, _G._mh_wa or 1)) if imgui.Button(IC.bolt..' '.._cyr5f('Запустить авто-скуп##abrun'), imgui.ImVec2(-1,0)) then _xpk6g() end imgui.PopStyleColor(2) end if #fh_lv_autobuy_preset > 0 then local total_buy = _G._abp_budget_total or 0 imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), _cyr5f(' Итого скуп: $' .. _kcr3y(total_buy))) end imgui.Separator() local panel_h2 = imgui.GetWindowHeight() - 105*d if imgui.BeginChild('##ab_all', imgui.ImVec2(left_w2, panel_h2), true) then _dpn1w() imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f('База цен:')) imgui.Separator() if not _G.ab_srch_buf then _G.ab_srch_buf = imgui.new.char[64]('') end imgui.PushItemWidth(imgui.GetContentRegionAvail().x - 32*d) if imgui.InputTextWithHint('##absrch', _cyr5f('Поиск...'), _G.ab_srch_buf, 64) then fh_lv_allitems_srch = fh_lower(u8:decode(ffi.string(_G.ab_srch_buf))):match('^%s*(.-)%s*$') end imgui.PopItemWidth() imgui.SameLine(0, 3*d) do local _hab = ffi.string(_G.ab_srch_buf) ~= '' imgui.PushStyleColor(imgui.Col.Button, _hab and imgui.ImVec4(0.38,0.08,0.08,1) or imgui.ImVec4(0.12,0.12,0.12,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.55,0.12,0.12,_G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, _hab and imgui.ImVec4(1,0.4,0.4,1) or imgui.ImVec4(0.3,0.3,0.3,1)) if imgui.Button(IC.x..'##absrchclr', imgui.ImVec2(28*d, 0)) and _hab then ffi.fill(_G.ab_srch_buf, 64, 0); fh_lv_allitems_srch = '' end imgui.PopStyleColor(3) end local srch_ab = fh_lv_allitems_srch or '' if not _G.ab_sort_popular then _G.ab_sort_popular = false end local sort_color = _G.ab_sort_popular and imgui.ImVec4(sb_r*1.4, sb_g*1.4, sb_b*1.4, 1) or imgui.ImVec4(0.2, 0.2, 0.2, 1) imgui.PushStyleColor(imgui.Col.Button, sort_color) local sort_lbl = _G.ab_sort_popular and _cyr5f('в Рейтинг: по популярности') or _cyr5f('Рейтинг: по названию') if imgui.Button(sort_lbl..'##absort', imgui.ImVec2(-1, 0)) then _G.ab_sort_popular = not _G.ab_sort_popular _G.ab_sorted_cache = nil end imgui.PopStyleColor() if #srch_ab < 2 and not _G.ab_sort_popular then imgui.Spacing() imgui.TextDisabled(_cyr5f(' Введите минимум 2 символа')) imgui.TextDisabled(_cyr5f(' для поиска товара')) else if not _G._ab_prices_ver_t or (os.clock() - _G._ab_prices_ver_t) > 3 then local _pv = 0 for _ in pairs(fh_mkt_prices) do _pv = _pv + 1 end _G._ab_prices_ver = _pv _G._ab_prices_ver_t = os.clock() end local _prices_ver = _G._ab_prices_ver or 0 local all_items if _G.ab_sort_popular then if not _G.ab_sorted_cache or _G._ab_sorted_ver ~= _prices_ver then _G._ab_sorted_ver = _prices_ver local raw = fh_get_allitems_list() local scored = {} for _, nm in ipairs(raw) do local e = fh_mkt_prices[nm] local qty30 = 0 if e and e.cp_hist then local s = _mjg5t(e.cp_hist, 30) qty30 = s and s.qty or 0 end table.insert(scored, {name=nm, qty=qty30}) end table.sort(scored, function(a,b) return a.qty > b.qty end) _G.ab_sorted_cache = scored end all_items = {} for _, v in ipairs(_G.ab_sorted_cache) do table.insert(all_items, v.name) end else if not _G._ab_plain_cache or _G._ab_plain_ver ~= _prices_ver then _G._ab_plain_cache = fh_get_allitems_list() _G._ab_plain_ver = _prices_ver end all_items = _G._ab_plain_cache end local shown = 0 for _, nm in ipairs(all_items) do if fh_lower(nm):find(srch_ab, 1, true) then local already2 = false for _, p2 in ipairs(fh_lv_autobuy_preset) do if p2.name == nm then already2=true; break end end local e2 = fh_mkt_prices[nm] local ph = e2 and (e2.s_avg or e2.b_avg) or 0 local lbl2 = _cyr5f(nm..(ph>0 and (' $'.._kcr3y(ph)) or '')) imgui.Spacing() if not already2 then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(bb_r, bb_g*0.85, bb_b*0.85, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(bb_r*1.35, bb_g*1.2, bb_b*1.1, _G._mh_wa or 1)) if imgui.Button(lbl2..'##aball_'..nm, imgui.ImVec2(-1,0)) then table.insert(fh_lv_autobuy_preset,{name=nm,max_price=0,qty=1,target_qty=1}) _G.ab_max_buf=nil; _G.ab_qty_buf=nil; _G.ab_srch_item_buf=nil settings.autobuy_preset=fh_lv_autobuy_preset if settings.buy_presets and settings.buy_presets[fh_ab_preset_idx] then settings.buy_presets[fh_ab_preset_idx].items=fh_lv_autobuy_preset end _wfn7p() end imgui.PopStyleColor(2) else imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.12,0.12,0.12,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.45,0.45,0.45,1)) imgui.Button(lbl2..'##aball_'..nm, imgui.ImVec2(-1,0)) imgui.PopStyleColor(2) end shown = shown + 1 if shown >= 50 then imgui.Spacing() imgui.TextDisabled(_cyr5f(' ... уточните поиск')) break end end end if shown == 0 then imgui.Spacing() imgui.TextDisabled(_cyr5f(' Ничего не найдено')) end end imgui.EndChild() end imgui.SameLine(0,8*d) if imgui.BeginChild('##ab_preset', imgui.ImVec2(right_w2, panel_h2), true) then _dpn1w() local _sbw_p2 = (settings.interface.scrollbar_w or 12)*d if not settings.buy_presets then settings.buy_presets = {{name='\xcf\xf0\xe5\xf1\xe5\xf2 1', items={}}} end if not _G.ab_preset_synced then local cur = settings.buy_presets[fh_ab_preset_idx] if cur then fh_lv_autobuy_preset = cur.items or {} end _G.ab_preset_synced = true end local ab_names = {} for pi, pr in ipairs(settings.buy_presets) do ab_names[pi] = _cyr5f(pr.name or ('\xcf\xf0\xe5\xf1\xe5\xf2 '..pi)) end imgui.PushItemWidth(right_w2 * 0.50) if imgui.BeginCombo('##abpreset_sel', ab_names[fh_ab_preset_idx] or _cyr5f('\xcf\xf0\xe5\xf1\xe5\xf2 1')) then for pi, pname in ipairs(ab_names) do if imgui.Selectable(pname..'##abps'..pi, pi==fh_ab_preset_idx) then fh_ab_preset_idx = pi fh_lv_autobuy_preset = settings.buy_presets[pi].items or {} settings.autobuy_preset = fh_lv_autobuy_preset _G.ab_max_buf=nil; _G.ab_qty_buf=nil; _wfn7p() end end imgui.EndCombo() end imgui.PopItemWidth() imgui.SameLine(0,3*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(bb_r, bb_g, bb_b, 1)) if imgui.Button(IC.circp..'##addabp', imgui.ImVec2(right_w2*0.18,0)) then table.insert(settings.buy_presets, {name='\xcf\xf0\xe5\xf1\xe5\xf2 '..((#settings.buy_presets)+1), items={}}) fh_ab_preset_idx = #settings.buy_presets fh_lv_autobuy_preset = {} settings.autobuy_preset = fh_lv_autobuy_preset _G.ab_max_buf=nil; _G.ab_qty_buf=nil; _wfn7p() end imgui.PopStyleColor() imgui.SameLine(0,2*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.5,0.08,0.08,1)) if imgui.Button(IC.trash..'##delabp', imgui.ImVec2(right_w2*0.15,0)) then if #(settings.buy_presets or {}) > 1 then _G._del_preset_confirm = (_G._del_preset_confirm == 'buy') and nil or 'buy' else mh_notify('[MH] {ff4444}Нельзя удалить единственный пресет.', 0xFFFFFF) end end imgui.PopStyleColor() imgui.SameLine(0,2*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.25,0.15,0,1)) if imgui.Button(IC.pen..' '.._cyr5f('\xc8\xec\xff##renabp'), imgui.ImVec2(-1,0)) then _G._ren_abp = not _G._ren_abp local cur = settings.buy_presets[fh_ab_preset_idx] _G._abp_name_buf = imgui.new.char[32](cur and cur.name or '') end imgui.PopStyleColor() if _G._del_preset_confirm == 'buy' then local _hw2b = imgui.GetContentRegionAvail().x imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.7,0.1,0.1,1)) if imgui.Button(_cyr5f('Удалить пресет? Да##delabpyes'), imgui.ImVec2(_hw2b*0.6,0)) then _G._del_preset_confirm = nil table.remove(settings.buy_presets, fh_ab_preset_idx) fh_ab_preset_idx = math.max(1, fh_ab_preset_idx - 1) fh_lv_autobuy_preset = settings.buy_presets[fh_ab_preset_idx].items or {} settings.autobuy_preset = fh_lv_autobuy_preset _G.ab_max_buf=nil; _G.ab_qty_buf=nil _wfn7p() end imgui.PopStyleColor() imgui.SameLine(0,4*d) if imgui.Button(_cyr5f('Отмена##delabpno'), imgui.ImVec2(-1,0)) then _G._del_preset_confirm = nil end end if _G._ren_abp then if not _G._abp_name_buf then _G._abp_name_buf=imgui.new.char[32]('') end imgui.PushItemWidth(-1) if imgui.InputText('##abpren', _G._abp_name_buf, 32, imgui.InputTextFlags.EnterReturnsTrue) then local nm = ffi.string(_G._abp_name_buf):match('^%s*(.-)%s*$') local _ok_nm, nm_dec = pcall(function() return require('encoding').UTF8:decode(nm) end) if _ok_nm and nm_dec and #nm_dec > 0 then nm = nm_dec end if nm~='' and settings.buy_presets[fh_ab_preset_idx] then settings.buy_presets[fh_ab_preset_idx].name = nm; _wfn7p() end _G._ren_abp = false end imgui.PopItemWidth() end if #fh_lv_autobuy_preset==0 then imgui.TextDisabled(_cyr5f(' \xcf\xf3\xf1\xf2\xee. \xca\xeb\xe8\xea\xed\xe8\xf2\xe5')) imgui.TextDisabled(_cyr5f(' \xf2\xee\xe2\xe0\xf0 \xf1\xeb\xe5\xe2\xe0.')) end if not _G.ab_preset_srch_buf2 then _G.ab_preset_srch_buf2 = imgui.new.char[64]('') end if not _G.ab_preset_srch2 then _G.ab_preset_srch2 = '' end if not _G.ab_preset_sort then _G.ab_preset_sort = 0 end imgui.PushItemWidth(right_w2 * 0.55) if imgui.InputTextWithHint('##abpresetsrch', _cyr5f('Поиск...'), _G.ab_preset_srch_buf2, 64) then _G.ab_preset_srch2 = fh_lower(u8:decode(ffi.string(_G.ab_preset_srch_buf2))):match('^%s*(.-)%s*$') end imgui.PopItemWidth() imgui.SameLine(0, 3*d) do local _habp = ffi.string(_G.ab_preset_srch_buf2) ~= '' imgui.PushStyleColor(imgui.Col.Button, _habp and imgui.ImVec4(0.38,0.08,0.08,1) or imgui.ImVec4(0.12,0.12,0.12,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.55,0.12,0.12,_G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, _habp and imgui.ImVec4(1,0.4,0.4,1) or imgui.ImVec4(0.3,0.3,0.3,1)) if imgui.Button(IC.x..'##abpresetsrchclr', imgui.ImVec2(28*d, 0)) and _habp then ffi.fill(_G.ab_preset_srch_buf2, 64, 0); _G.ab_preset_srch2 = '' end imgui.PopStyleColor(3) end imgui.SameLine(0, 4*d) local _ab_sort_lbls = {_cyr5f('А-Я'), _cyr5f('Цена+'), _cyr5f('Цена-'), _cyr5f('Сумма-')} local _ab_srt_disp = _G.ab_preset_sort == -1 and 0 or _G.ab_preset_sort imgui.PushStyleColor(imgui.Col.Button, _ab_srt_disp > 0 and imgui.ImVec4(bb_r*0.6, bb_g*0.6, bb_b*0.6, 1) or imgui.ImVec4(0.18, 0.18, 0.18, 1)) if imgui.Button(_ab_sort_lbls[_ab_srt_disp+1]..'##abpsort', imgui.ImVec2(-1, 0)) then _G.ab_preset_sort = (_ab_srt_disp + 1) % 4 end imgui.PopStyleColor() if not _G.ab_flt_min_buf then _G.ab_flt_min_buf = imgui.new.char[20]('') end if not _G.ab_flt_max_buf then _G.ab_flt_max_buf = imgui.new.char[20]('') end if not _G.ab_flt_min then _G.ab_flt_min = 0 end if not _G.ab_flt_max then _G.ab_flt_max = 0 end local _flt_hw = (right_w2 - 10*d) / 2 imgui.PushItemWidth(_flt_hw) imgui.PushStyleColor(imgui.Col.FrameBg, imgui.ImVec4(0.10, 0.08, 0.06, _G._mh_wa or 1)) if imgui.InputTextWithHint('##abfltmin', _cyr5f('Мин цена...'), _G.ab_flt_min_buf, 20) then _G.ab_flt_min = tonumber(ffi.string(_G.ab_flt_min_buf):match('%d+') or '0') or 0 end imgui.PopStyleColor(); imgui.PopItemWidth() imgui.SameLine(0, 4*d) imgui.PushItemWidth(_flt_hw) imgui.PushStyleColor(imgui.Col.FrameBg, imgui.ImVec4(0.10, 0.08, 0.06, _G._mh_wa or 1)) if imgui.InputTextWithHint('##abfltmax', _cyr5f('Макс цена...'), _G.ab_flt_max_buf, 20) then _G.ab_flt_max = tonumber(ffi.string(_G.ab_flt_max_buf):match('%d+') or '0') or 0 end imgui.PopStyleColor(); imgui.PopItemWidth() do local _bw2g = (right_w2 - 14*d - 4*d) / 2 local _preset_ver = #fh_lv_autobuy_preset if _G._ab_diff_ver ~= _preset_ver then local _dc = 0 for _, _ap in ipairs(fh_lv_autobuy_preset) do local _tq = _ap.target_qty or _ap.qty or 1 if (_ap.qty or 1) ~= _tq then _dc = _dc + 1 end end _G._ab_diff_cnt = _dc _G._ab_diff_ver = _preset_ver end local _diff_cnt = _G._ab_diff_cnt or 0 imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.08,0.28,0.52,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.12,0.42,0.75,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.6,0.88,1,1)) if imgui.Button(IC.save..' '.._cyr5f('Сохранить##abtgt_all'), imgui.ImVec2(_bw2g, 0)) then for _, _ap in ipairs(fh_lv_autobuy_preset) do _ap.target_qty = _ap.qty or 1 end _G.ab_qty_buf = nil _G._ab_diff_ver = nil settings.autobuy_preset = fh_lv_autobuy_preset; _wfn7p() mh_notify('[MH] {aaddff}Цели скупки сохранены для всех товаров.', 0xFFFFFF) end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Запомнить текущее qty каждого товара как целевое количество')) end imgui.SameLine(0, 4*d) local _has_diff = _diff_cnt > 0 if _has_diff then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10*0.85,_G._mh_sb_g or 0.45*0.85,_G._mh_sb_b or 0.10*0.85,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.38,sb_g*1.38,sb_b*1.38,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.5,1,0.5,1)) else imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.13,0.13,0.13,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, _mh_bc(0.13,0.13,0.13,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.3,0.3,0.3,1)) end if imgui.Button(IC.rot..' '.._cyr5f('Восстановить##abrst_all'), imgui.ImVec2(-1, 0)) then if _has_diff then local _restored = 0 for _, _ap in ipairs(fh_lv_autobuy_preset) do local _tq = _ap.target_qty or _ap.qty or 1 if (_ap.qty or 1) ~= _tq then _ap.qty = _tq; _restored = _restored + 1 end end _G.ab_qty_buf = nil _G._ab_diff_ver = nil settings.autobuy_preset = fh_lv_autobuy_preset; _wfn7p() mh_notify('[MH] {aaffaa}Восстановлено '..tostring(_restored)..' товаров до целевого кол-ва.', 0xFFFFFF) end end imgui.PopStyleColor(3) if imgui.IsItemHovered() then if _has_diff then imgui.SetTooltip(_cyr5f('Восстановить qty до цели: ')..tostring(_diff_cnt).._cyr5f(' товаров изменились')) else imgui.SetTooltip(_cyr5f('Все товары уже на целевом количестве')) end end if _has_diff then imgui.SameLine(0, 6*d) imgui.TextColored(imgui.ImVec4(1,0.65,0.15,1), '('..tostring(_diff_cnt)..')') end end imgui.Separator() local _ab_total_budget_cache = _G._abp_budget_total or 0 local del_ab_i = nil local _ab_srt = _G.ab_preset_sort or 0 if _ab_srt == -1 then _ab_srt = 0 end local _ab_render_ids = {} for _i2 = 1, #fh_lv_autobuy_preset do local _p2 = fh_lv_autobuy_preset[_i2] local _srch2 = _G.ab_preset_srch2 or '' if _srch2 ~= '' and not fh_lower(_p2.name or ''):find(_srch2, 1, true) then elseif (_G.ab_flt_min or 0) > 0 and (_p2.max_price or 0) < (_G.ab_flt_min or 0) then elseif (_G.ab_flt_max or 0) > 0 and (_p2.max_price or 0) > (_G.ab_flt_max or 0) then else table.insert(_ab_render_ids, _i2) end end if _ab_srt == 1 then table.sort(_ab_render_ids, function(a,b) return (fh_lv_autobuy_preset[a].max_price or 0) < (fh_lv_autobuy_preset[b].max_price or 0) end) elseif _ab_srt == 2 then table.sort(_ab_render_ids, function(a,b) return (fh_lv_autobuy_preset[a].max_price or 0) > (fh_lv_autobuy_preset[b].max_price or 0) end) elseif _ab_srt == 3 then table.sort(_ab_render_ids, function(a,b) return ((fh_lv_autobuy_preset[a].max_price or 0)*(fh_lv_autobuy_preset[a].qty or 1)) > ((fh_lv_autobuy_preset[b].max_price or 0)*(fh_lv_autobuy_preset[b].qty or 1)) end) end for _, abi in ipairs(_ab_render_ids) do local abp = fh_lv_autobuy_preset[abi] if true then if not _G.ab_max_buf then _G.ab_max_buf={} end if not _G.ab_qty_buf then _G.ab_qty_buf={} end if not abp.target_qty or abp.target_qty == 0 then abp.target_qty = abp.qty or 1 end if not _G.ab_max_buf[abi] then _G.ab_max_buf[abi]=imgui.new.char[32](_vnh1j(abp.max_price or 0)) end if not _G.ab_qty_buf[abi] then _G.ab_qty_buf[abi]=imgui.new.char[16](tostring(abp.qty or 1)) end local _abp_avg_cp = _G._abp_price_cache and _G._abp_price_cache[abi] or nil local _nm_lo_abp = abp.name:lower() local _lv_entry2 = _G._lv_shops_cache and _G._lv_shops_cache[_nm_lo_abp] local _sell_lv2 = _lv_entry2 and _lv_entry2.sell local _buy_lv2 = _lv_entry2 and _lv_entry2.buy local _abp_avg_lv = _sell_lv2 or _buy_lv2 imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1,1,1,1)) if imgui.Selectable(_cyr5f(abp.name..'##abp_nm'..abi), false, 0, imgui.ImVec2(0,0)) then _G.mkt_detail_item = abp.name _G.mkt_detail_src = fh_mkt_prices[abp.name] and 'cp' or 'tags' _G.mkt_detail_pos = nil _G.mkt_detail_open = true end imgui.PopStyleColor() if _abp_avg_cp then imgui.SameLine(0,8*d) imgui.TextColored(imgui.ImVec4(1,0.75,0.2,1), _cyr5f('Рынок:$'.._kcr3y(_abp_avg_cp))) end if _abp_avg_lv then if _sell_lv2 then imgui.TextColored(imgui.ImVec4(0.4,0.95,0.4,1), _cyr5f('Продают:$'.._kcr3y(_sell_lv2))) imgui.SameLine(0,6*d) end if _buy_lv2 then imgui.TextColored(imgui.ImVec4(0.5,0.7,1,1), _cyr5f('Скупают:$'.._kcr3y(_buy_lv2))) end end do local _item_total = (abp.max_price or 0) * (abp.qty or 1) imgui.TextColored(imgui.ImVec4(0.6,0.6,0.6,1), _cyr5f('Сумма: $'.._kcr3y(_item_total))) end do local fw2 = right_w2 - 14*d - _sbw_p2 local x_w3 = 30*d local btn_w3 = 28*d local qty_w3 = 52*d local price_w3 = fw2 - qty_w3 - x_w3 - btn_w3*3 - 5*3*d - 6*d if price_w3 < 40*d then price_w3 = 40*d end imgui.PushItemWidth(price_w3) if imgui.InputText('##abm'..abi, _G.ab_max_buf[abi], 32) then local _raw2 = ffi.string(_G.ab_max_buf[abi]) local _val2 = _sxp3d(_raw2) if _val2 > 0 then abp.max_price = _val2 end _G.ab_max_buf[abi] = imgui.new.char[32](_vnh1j(abp.max_price or 0)) _G._ab_rid_key = nil; _G._abp_price_cache_key = nil settings.autobuy_preset=fh_lv_autobuy_preset; _wfn7p() end imgui.PopItemWidth() imgui.SameLine(0,3*d) imgui.PushItemWidth(qty_w3) if imgui.InputText('##abq'..abi, _G.ab_qty_buf[abi], 16) then local _nq = tonumber(ffi.string(_G.ab_qty_buf[abi])) or abp.qty abp.qty = _nq abp.target_qty = _nq _G._ab_rid_key = nil; _G._abp_price_cache_key = nil; _G._ab_diff_ver = nil settings.autobuy_preset=fh_lv_autobuy_preset; _wfn7p() end imgui.PopItemWidth() imgui.SameLine(0,3*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.30,0.10,0.10,1)) if imgui.Button(IC.min..'##abqm'..abi, imgui.ImVec2(btn_w3, 0)) then local _step = math.max(1, abp.qty_step or 1) abp.qty = math.max(1, (abp.qty or 1) - _step) abp.target_qty = abp.qty _G.ab_qty_buf[abi] = imgui.new.char[16](tostring(abp.qty)) _G._ab_rid_key=nil; _G._abp_price_cache_key=nil; _G._ab_diff_ver=nil settings.autobuy_preset=fh_lv_autobuy_preset; _wfn7p() end imgui.PopStyleColor() imgui.SameLine(0,2*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.10,0.30,0.10,1)) if imgui.Button(IC.circp..'##abqp'..abi, imgui.ImVec2(btn_w3, 0)) then local _step = math.max(1, abp.qty_step or 1) abp.qty = (abp.qty or 1) + _step abp.target_qty = abp.qty _G.ab_qty_buf[abi] = imgui.new.char[16](tostring(abp.qty)) _G._ab_rid_key=nil; _G._abp_price_cache_key=nil; _G._ab_diff_ver=nil settings.autobuy_preset=fh_lv_autobuy_preset; _wfn7p() end imgui.PopStyleColor() imgui.SameLine(0,2*d) local _gear_active = (_G._ab_step_popup_id == abi) if _gear_active then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.20,0.20,0.40,1)) else imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.15,0.15,0.25,1)) end if imgui.Button(IC.gear..'##abgs'..abi, imgui.ImVec2(btn_w3, 0)) then if _G._ab_step_popup_id == abi then _G._ab_step_popup_id = nil else _G._ab_step_popup_id = abi _G._ab_step_buf = imgui.new.int(math.max(1, abp.qty_step or 1)) end end imgui.PopStyleColor() imgui.SameLine(0,2*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.45,0.08,0.08,1)) if imgui.Button(IC.x..'##abd'..abi, imgui.ImVec2(x_w3, 0)) then del_ab_i=abi; _G._ab_step_popup_id=nil end imgui.PopStyleColor() if _G._ab_step_popup_id == abi then imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.10,0.10,0.22,0.98)) if imgui.BeginChild('##abstep'..abi, imgui.ImVec2(fw2, 80*d), false, imgui.WindowFlags.NoScrollWithMouse + imgui.WindowFlags.NoScrollbar) then imgui.SetCursorPosY(imgui.GetCursorPosY()+5*d) local _lbl_w = 38*d local _cls_w = 30*d local _sl_w = fw2 - _lbl_w - _cls_w - 14*d imgui.TextDisabled(_cyr5f('Шаг:')) imgui.SameLine(0,4*d) imgui.PushItemWidth(_sl_w) if imgui.SliderInt('##abstepsl'..abi, _G._ab_step_buf, 1, 999, '%d шт.') then abp.qty_step = _G._ab_step_buf[0] settings.autobuy_preset=fh_lv_autobuy_preset; _wfn7p() end imgui.PopItemWidth() imgui.SameLine(0,6*d) if imgui.Button(IC.x..'##abstepcl'..abi, imgui.ImVec2(_cls_w,0)) then _G._ab_step_popup_id = nil end imgui.Separator() do local _tq2 = abp.target_qty or abp.qty or 1 local _cq2 = abp.qty or 1 local _hw2 = (fw2 - 4*d) / 2 if _cq2 == _tq2 then imgui.TextColored(imgui.ImVec4(0.45,0.45,0.45,1), _cyr5f('Цель: ')..tostring(_tq2).._cyr5f(' шт.')) else imgui.TextColored(imgui.ImVec4(1,0.65,0.15,1), tostring(_cq2).._cyr5f(' / ')..tostring(_tq2).._cyr5f(' шт. (цель)')) end imgui.SameLine(0,8*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.08,0.30,0.55,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.12,0.45,0.80,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.6,0.88,1,1)) if imgui.Button(_cyr5f('Сохр.##abtgt2'..abi), imgui.ImVec2(_hw2, 0)) then abp.target_qty = _cq2 settings.autobuy_preset=fh_lv_autobuy_preset; _wfn7p() end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Запомнить ')..tostring(_cq2).._cyr5f(' шт. как цель')) end imgui.SameLine(0,4*d) local _can2 = (_cq2 ~= _tq2) if not _can2 then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.12,0.12,0.12,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, _mh_bc(0.12,0.12,0.12,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.3,0.3,0.3,1)) else imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.085,_G._mh_sb_g or 0.38,_G._mh_sb_b or 0.085,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.38,sb_g*1.38,sb_b*1.38,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.5,1,0.5,1)) end if imgui.Button(_cyr5f('Восст.##abrst2'..abi), imgui.ImVec2(-1, 0)) then if _can2 then abp.qty = _tq2 _G.ab_qty_buf[abi] = imgui.new.char[16](tostring(_tq2)) settings.autobuy_preset=fh_lv_autobuy_preset; _wfn7p() mh_notify('[MH] '..abp.name..': qty -> '..tostring(_tq2), 0xAAFFAA) end end imgui.PopStyleColor(3) if imgui.IsItemHovered() and _can2 then imgui.SetTooltip(_cyr5f('Восстановить до цели: ')..tostring(_tq2).._cyr5f(' шт.')) end end imgui.EndChild() end imgui.PopStyleColor() end imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.08,0.25,0.45,1)) if imgui.Button(IC.up..'##abmu'..abi, imgui.ImVec2((fw2-2*d)/2, 0)) then local _pos_u = nil for _ri_u, _rv_u in ipairs(_ab_render_ids) do if _rv_u == abi then _pos_u = _ri_u; break end end if _pos_u and _pos_u > 1 then local _prev = _ab_render_ids[_pos_u - 1] _G._ab_swap = fh_lv_autobuy_preset[abi] fh_lv_autobuy_preset[abi] = fh_lv_autobuy_preset[_prev] fh_lv_autobuy_preset[_prev] = _G._ab_swap _G.ab_preset_sort = -1 _G.ab_max_buf=nil; _G.ab_qty_buf=nil _G._abp_price_cache_key=nil settings.autobuy_preset=fh_lv_autobuy_preset if settings.buy_presets and settings.buy_presets[fh_ab_preset_idx] then settings.buy_presets[fh_ab_preset_idx].items=fh_lv_autobuy_preset end _wfn7p() end end imgui.PopStyleColor() imgui.SameLine(0,2*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.08,0.25,0.45,1)) if imgui.Button(IC.dn..'##abmd'..abi, imgui.ImVec2((fw2-2*d)/2, 0)) then local _pos_d = nil for _ri_d, _rv_d in ipairs(_ab_render_ids) do if _rv_d == abi then _pos_d = _ri_d; break end end if _pos_d and _pos_d < #_ab_render_ids then local _next = _ab_render_ids[_pos_d + 1] _G._ab_swap = fh_lv_autobuy_preset[abi] fh_lv_autobuy_preset[abi] = fh_lv_autobuy_preset[_next] fh_lv_autobuy_preset[_next] = _G._ab_swap _G.ab_preset_sort = -1 _G.ab_max_buf=nil; _G.ab_qty_buf=nil _G._abp_price_cache_key=nil settings.autobuy_preset=fh_lv_autobuy_preset if settings.buy_presets and settings.buy_presets[fh_ab_preset_idx] then settings.buy_presets[fh_ab_preset_idx].items=fh_lv_autobuy_preset end _wfn7p() end end imgui.PopStyleColor() end do local _bw4b = (right_w2 - 14*d - 3*3*d) / 4 local _has_pr2 = _sell_lv2 ~= nil local _has_sk2 = _buy_lv2 ~= nil local _has_nw2 = _abp_avg_cp ~= nil local _has_rn2 = _abp_avg_cp ~= nil if _has_pr2 then local _pr2 = _sell_lv2 imgui.PushStyleColor(imgui.Col.Button, _mh_bc(sb_r*0.8, sb_g*0.9, sb_b*0.8, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.2, sb_g*1.2, sb_b*1.2, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.5,1,0.5,1)) if imgui.Button(_cyr5f('Продажа##abpr'..abi), imgui.ImVec2(_bw4b, 0)) then abp.max_price = _pr2 _G.ab_max_buf[abi] = imgui.new.char[32](_vnh1j(_pr2)) settings.autobuy_preset=fh_lv_autobuy_preset; _wfn7p() end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Ср. цена продажи: $'.._kcr3y(_pr2))) end imgui.SameLine(0,3*d) end if _has_sk2 then local _sk2 = _buy_lv2 imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.30,0.08,0.42,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.42,0.12,0.58, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.85,0.55,1,1)) if imgui.Button(_cyr5f('Скупка##absk'..abi), imgui.ImVec2(_bw4b, 0)) then abp.max_price = _sk2 _G.ab_max_buf[abi] = imgui.new.char[32](_vnh1j(_sk2)) settings.autobuy_preset=fh_lv_autobuy_preset; _wfn7p() end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Ср. цена скупки: $'.._kcr3y(_sk2))) end imgui.SameLine(0,3*d) end if _has_nw2 then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.40,0.25,0.02,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.55,0.35,0.04, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1,0.85,0.3,1)) if imgui.Button(_cyr5f('Неделя##abcp'..abi), imgui.ImVec2(_bw4b, 0)) then abp.max_price = _abp_avg_cp _G.ab_max_buf[abi] = imgui.new.char[32](_vnh1j(_abp_avg_cp)) settings.autobuy_preset=fh_lv_autobuy_preset; _wfn7p() end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Ср. 7 дн: $'.._kcr3y(_abp_avg_cp))) end imgui.SameLine(0,3*d) end if _has_rn2 then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10*0.78,_G._mh_sb_g or 0.45*0.78,_G._mh_sb_b or 0.10*0.78,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.1,sb_g*1.1,sb_b*1.1, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.4,1,0.6,1)) if imgui.Button(_cyr5f('Рынок##abrn'..abi), imgui.ImVec2(-1, 0)) then abp.max_price = _abp_avg_cp _G.ab_max_buf[abi] = imgui.new.char[32](_vnh1j(_abp_avg_cp)) settings.autobuy_preset=fh_lv_autobuy_preset; _wfn7p() end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Рынок (7дн): $'.._kcr3y(_abp_avg_cp))) end end end imgui.Separator() end end if del_ab_i then table.remove(fh_lv_autobuy_preset,del_ab_i) if _G.ab_qty_buf then local nb={} for i,v in pairs(_G.ab_qty_buf) do if type(i)=='number' then if i < del_ab_i then nb[i]=v elseif i > del_ab_i then nb[i-1]=v end end end _G.ab_qty_buf=nb end if _G.ab_max_buf then local nb={} for i,v in pairs(_G.ab_max_buf) do if type(i)=='number' then if i < del_ab_i then nb[i]=v elseif i > del_ab_i then nb[i-1]=v end end end _G.ab_max_buf=nb end _G.ab_srch_item_buf=nil settings.autobuy_preset=fh_lv_autobuy_preset if settings.buy_presets and settings.buy_presets[fh_ab_preset_idx] then settings.buy_presets[fh_ab_preset_idx].items=fh_lv_autobuy_preset end _wfn7p() end imgui.EndChild() end end if _G.mh_tab == 5 then if not _G.log_overlay_open then _G.log_overlay_open = false end if not _G.ov_alpha_buf then _G.ov_alpha_buf = imgui.new.float(settings.overlay.alpha) end if not _G.ov_lines_buf then _G.ov_lines_buf = imgui.new.int(settings.overlay.lines) end if not _G.ov_sell_col then _G.ov_sell_col = imgui.new.float[3](settings.overlay.sell_r, settings.overlay.sell_g, settings.overlay.sell_b) end if not _G.ov_buy_col then _G.ov_buy_col = imgui.new.float[3](settings.overlay.buy_r, settings.overlay.buy_g, settings.overlay.buy_b) end local ov_en = imgui.new.bool(settings.overlay.enabled) if imgui.Checkbox(_cyr5f('Плавающий лог##ovcheck'), ov_en) then settings.overlay.enabled = ov_en[0]; _wfn7p() end if settings.overlay.enabled then imgui.SameLine(0,10*d) if imgui.Button(IC.gear..' '.._cyr5f('Настройки##ovset'), imgui.ImVec2(0,0)) then _G.log_overlay_open = not _G.log_overlay_open end if _G.log_overlay_open then imgui.Separator() imgui.TextDisabled(_cyr5f(' Прозрачность:')) imgui.PushItemWidth(-1) if imgui.SliderFloat('##ovalpha', _G.ov_alpha_buf, 0.05, 1.0) then settings.overlay.alpha=_G.ov_alpha_buf[0]; _wfn7p() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' \xd1\xf2\xf0\xee\xea (\xec\xe0\xea\xf1):')) imgui.PushItemWidth(-1) if imgui.SliderInt('##ovlines', _G.ov_lines_buf, 3, 20) then settings.overlay.lines=_G.ov_lines_buf[0]; _wfn7p() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' \xd4\xe8\xeb\xfc\xf2\xf0 \xef\xe5\xf0\xe8\xee\xe4\xe0:')) do local _ovfl = { [0] = _cyr5f('\xc2\xf1\xe5'), [1] = _cyr5f('\xd1\xe5\xe3\xee\xe4\xed\xff'), [2] = _cyr5f('\xcd\xe5\xe4\xe5\xeb\xff'), [3] = _cyr5f('\xcc\xe5\xf1\xff\xf6'), } for _ofi2 = 0, 3 do if _ofi2 > 0 then imgui.SameLine(0, 3*d) end local _ofa2 = _G.ov_day_filter == _ofi2 if _ofa2 then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(sb_r,sb_g,sb_b,0.9)) end if imgui.Button(_ovfl[_ofi2]..'##ovdfs'.._ofi2, imgui.ImVec2(0,0)) then _G.ov_day_filter = _ofi2 if not settings.overlay then settings.overlay = {} end settings.overlay.day_filter = _ofi2; _wfn7p() end if _ofa2 then imgui.PopStyleColor() end end end imgui.TextDisabled(_cyr5f(' \xc6\xe2\xe5\xf2 \xef\xf0\xee\xe4\xe0\xe6:')) imgui.PushItemWidth(-1) if imgui.ColorEdit3('##ovsell', _G.ov_sell_col) then settings.overlay.sell_r=_G.ov_sell_col[0]; settings.overlay.sell_g=_G.ov_sell_col[1]; settings.overlay.sell_b=_G.ov_sell_col[2]; _wfn7p() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Цвет покупки:')) imgui.PushItemWidth(-1) if imgui.ColorEdit3('##ovbuy', _G.ov_buy_col) then settings.overlay.buy_r=_G.ov_buy_col[0]; settings.overlay.buy_g=_G.ov_buy_col[1]; settings.overlay.buy_b=_G.ov_buy_col[2]; _wfn7p() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Расположение/размер - тащите оверлей в игре')) imgui.Separator() end end if not _G.log_tab_mode then _G.log_tab_mode = 1 end if not _G.mkt_log_f2 then _G.mkt_log_f2 = imgui.new.char[128](''); _G.mkt_log_fs2='' end if not _G.mkt_log_page then _G.mkt_log_page = 1 end local ar3 = settings.interface.accent_r or 1 local ag3 = settings.interface.accent_g or .65 local ab3 = settings.interface.accent_b or 0.0 local sb_r = settings.interface.sell_btn_r or 0.10 local sb_g = settings.interface.sell_btn_g or 0.45 local sb_b = settings.interface.sell_btn_b or 0.10 local bb_r = settings.interface.buy_btn_r or 0.00 local bb_g = settings.interface.buy_btn_g or 0.28 local bb_b = settings.interface.buy_btn_b or 0.50 local _ltab_w = (imgui.GetContentRegionAvail().x - 12*d) / 4 local function tab_btn(label, mode) local active = _G.log_tab_mode == mode if active then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(ar3*.5, ag3*.5, ab3*.5, .9)) end if imgui.Button(_cyr5f(label)..'##lmode'..mode, imgui.ImVec2(_ltab_w, 0)) then _G.log_tab_mode=mode end if active then imgui.PopStyleColor() end end do tab_btn('\xd1\xe4\xe5\xeb\xea\xe8', 1) imgui.SameLine(0, 4*d) tab_btn('\xd7\xe0\xf2', 2) imgui.SameLine(0, 4*d) tab_btn('\xd2\xf0\xe5\xe9\xe4', 4) imgui.SameLine(0, 4*d) tab_btn('Telegram', 3) end imgui.Separator() if _G.log_tab_mode == 0 then local log_h = imgui.GetWindowHeight() - 120*d if imgui.BeginChild('##lv_tlog', imgui.ImVec2(-1, log_h), true) then _dpn1w() local cw_sl = imgui.GetWindowContentRegionWidth() local col1_sl = math.max(185*d, cw_sl - (105 + 130 + 48 + 95)*d - 8*d) imgui.Columns(5,'##tlhdr',false) imgui.SetColumnWidth(0,105*d); imgui.SetColumnWidth(1,col1_sl) imgui.SetColumnWidth(2,130*d); imgui.SetColumnWidth(3,48*d); imgui.SetColumnWidth(4,95*d) local hc=imgui.ImVec4(0.6,0.6,0.6,1) imgui.TextColored(hc,_cyr5f(' \xc2\xf0.')); imgui.NextColumn() imgui.TextColored(hc,_cyr5f(' \xd2\xee\xe2\xe0\xf0')); imgui.NextColumn() imgui.TextColored(hc,_cyr5f(' \xd6\xe5\xed\xe0 $')); imgui.NextColumn() imgui.TextColored(hc,_cyr5f(' \xd8\xf2.')); imgui.NextColumn() imgui.TextColored(hc,_cyr5f(' \xd2\xe8\xef')); imgui.NextColumn() imgui.Separator() if #fh_lv_trade_log==0 then imgui.TextDisabled(_cyr5f(' \xcb\xee\xe3 \xef\xf3\xf1\xf2.')) for _=1,4 do imgui.NextColumn() end end for _,tl in ipairs(fh_lv_trade_log) do imgui.TextColored(imgui.ImVec4(0.6,0.6,0.6,1),_cyr5f(' '..tl.dt)); imgui.NextColumn() imgui.Text(_cyr5f(' '..tl.item)); imgui.NextColumn() local _price_str = (tl.price and tl.price > 0) and ('$'.._kcr3y(tl.price)) or '...' imgui.TextColored(imgui.ImVec4(lp_r,lp_g,lp_b,1),_cyr5f(' '.._price_str)); imgui.NextColumn() imgui.Text(_cyr5f(' '..tl.qty)); imgui.NextColumn() local tc_l = tl.op=='sell' and imgui.ImVec4(0.3,0.92,0.3,1) or imgui.ImVec4(0.35,0.65,1,1) local op_icon = tl.op=='sell' and (IC.up..' ') or (IC.dn..' ') local op_l = tl.op=='sell' and _cyr5f('Продажа') or _cyr5f('Покупка') if tl.status=='skip' then tc_l=imgui.ImVec4(0.55,0.55,0.55,1); op_icon=IC.min..' '; op_l=_cyr5f('Прп.') end imgui.TextColored(tc_l, op_icon.._cyr5f(op_l)); imgui.NextColumn() end imgui.Columns(1); imgui.EndChild() end elseif _G.log_tab_mode == 1 then local tot_log = #fh_mkt_log if not _G.log_day_filter then _G.log_day_filter = 1 end if not _G.log_day_sel_date then _G.log_day_sel_date = '' end local _all_dates = {} local _dates_seen = {} for _di = #fh_mkt_log, 1, -1 do local _de = fh_mkt_log[_di] if _de and _de.dt then local _dk = _de.dt:sub(1,5) if _dk ~= '' and not _dates_seen[_dk] then _dates_seen[_dk] = true table.insert(_all_dates, _dk) end end end local _ldf_w = (imgui.GetContentRegionAvail().x - 20*d) / 5 local _ldf_labels = { [0] = _cyr5f('\xc2\xf1\xe5'), [1] = fa.CALENDAR_DAY..' '.._cyr5f('\xd1\xe5\xe3\xee\xe4\xed\xff'), [2] = fa.CALENDAR_WEEK..' '.._cyr5f('\xcd\xe5\xe4\xe5\xeb\xff'), [3] = IC.calds..' '.._cyr5f('\xcc\xe5\xf1\xff\xf6'), } for _fi = 0, 3 do if _fi > 0 then imgui.SameLine(0, 5*d) end local _fa2 = _G.log_day_filter == _fi if _fa2 then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(ar3*.5, ag3*.5, ab3*.5, .9)) end if imgui.Button(_ldf_labels[_fi]..'##ldf'.._fi, imgui.ImVec2(_ldf_w, 0)) then _G.log_day_filter = _fi; _G.mkt_log_page = 1 end if _fa2 then imgui.PopStyleColor() end end imgui.SameLine(0, 5*d) do local _fd4 = _G.log_day_filter == 4 if _fd4 then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(ar3*.5, ag3*.5, ab3*.5, .9)) end if imgui.Button(fa.CALENDAR..' '.._cyr5f('\xc4\xe0\xf2\xe0##ldf4'), imgui.ImVec2(_ldf_w, 0)) then _G.log_day_filter = 4; _G.mkt_log_page = 1 _G.log_day_popup_open = true end if _fd4 then imgui.PopStyleColor() end if _G.log_day_filter == 4 and _G.log_day_sel_date ~= '' then imgui.SameLine(0, 4*d) imgui.TextDisabled(_G.log_day_sel_date) end end if _G.log_day_popup_open and #_all_dates > 0 then imgui.OpenPopup('##ldpick') _G.log_day_popup_open = false end if imgui.BeginPopup('##ldpick') then imgui.TextDisabled(_cyr5f('\xc2\xfb\xe1\xe5\xf0\xe8\xf2\xe5 \xe4\xe0\xf2\xf3:')) imgui.Separator() for _, _dd in ipairs(_all_dates) do local _sel = _G.log_day_sel_date == _dd if _sel then imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(ar3, ag3, ab3, 1)) end if imgui.Selectable(_dd..'##dsel', _sel, 0, imgui.ImVec2(80*d, 0)) then _G.log_day_sel_date = _dd; _G.log_day_filter = 4; _G.mkt_log_page = 1 imgui.CloseCurrentPopup() end if _sel then imgui.PopStyleColor() end end imgui.EndPopup() end local _cra = imgui.GetContentRegionAvail().x local _clr_w = 90*d local _srch_w = _cra - _clr_w - 58*d imgui.PushItemWidth(_srch_w) if imgui.InputTextWithHint('##log_srch2', _cyr5f('Поиск...'), _G.mkt_log_f2, 128) then _G.mkt_log_fs2=fh_lower(u8:decode(ffi.string(_G.mkt_log_f2))):match('^%s*(.-)%s*$'); _G.mkt_log_page=1 end imgui.PopItemWidth() if _G.mkt_log_fs2 and _G.mkt_log_fs2 ~= '' then imgui.SameLine(0,2*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0,0,0,0)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.4,0.1,0.1,0.5)) if imgui.Button(fa.XMARK..'##srch2clr', imgui.ImVec2(22*d,0)) then _G.mkt_log_f2 = imgui.new.char[128]('') _G.mkt_log_fs2 = ''; _G.mkt_log_page=1 end imgui.PopStyleColor(2) imgui.SameLine(0,2*d) end imgui.SameLine(0, 4*d) imgui.TextDisabled(_cyr5f(tot_log..' зап.')) imgui.SameLine(0, 6*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.35,0.08,0.08,0.8)) if imgui.Button(IC.trash..' '.._cyr5f('Очистить##lgclr'), imgui.ImVec2(0,0)) then fh_mkt_log={}; _ryb5t() end imgui.PopStyleColor() local function _mh_log_day_ok(le_dt) if _G.log_day_filter == 0 then return true end if not le_dt or le_dt == '' then return false end local now = os.time() local t = os.date('*t', now) local d_day = string.format('%02d.%02d', t.day, t.month) local e_day = le_dt:sub(1,5) if _G.log_day_filter == 1 then return e_day == d_day elseif _G.log_day_filter == 2 then local e_d = tonumber(le_dt:sub(1,2)) or 0 local e_m = tonumber(le_dt:sub(4,5)) or 0 local e_ts = os.time({year=t.year, month=e_m, day=e_d, hour=0, min=0, sec=0}) if e_ts > now then e_ts = os.time({year=t.year-1, month=e_m, day=e_d, hour=0, min=0, sec=0}) end return (now - e_ts) <= (7 * 86400) elseif _G.log_day_filter == 3 then local e_d = tonumber(le_dt:sub(1,2)) or 0 local e_m = tonumber(le_dt:sub(4,5)) or 0 local e_ts = os.time({year=t.year, month=e_m, day=e_d, hour=0, min=0, sec=0}) if e_ts > now then e_ts = os.time({year=t.year-1, month=e_m, day=e_d, hour=0, min=0, sec=0}) end return (now - e_ts) <= (30 * 86400) elseif _G.log_day_filter == 4 then return e_day == (_G.log_day_sel_date or '') end return true end local log_filt={} local lfs=_G.mkt_log_fs2 or '' for i=#fh_mkt_log,1,-1 do local le=fh_mkt_log[i] if le and le.item and (lfs=='' or fh_lower(le.item):find(lfs,1,true)) and _mh_log_day_ok(le.dt) then table.insert(log_filt,le) end end local LOG_PAGE=60 local log_pages=math.max(1,math.ceil(#log_filt/LOG_PAGE)) if _G.mkt_log_page>log_pages then _G.mkt_log_page=log_pages end local lf_from=(_G.mkt_log_page-1)*LOG_PAGE+1 local lf_to=math.min(_G.mkt_log_page*LOG_PAGE,#log_filt) local total_sell_sum, total_buy_sum, total_sell_cnt, total_buy_cnt = 0, 0, 0, 0 for _, le2 in ipairs(log_filt) do local is_s = fh_is_my_sell(le2) if is_s then total_sell_sum=total_sell_sum+(le2.price or 0)*(le2.qty or 1); total_sell_cnt=total_sell_cnt+(le2.qty or 1) else total_buy_sum=total_buy_sum+(le2.price or 0)*(le2.qty or 1); total_buy_cnt=total_buy_cnt+(le2.qty or 1) end end imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.0, 0.18, 0.05, 0.55)) if imgui.BeginChild('##log_stat_bar', imgui.ImVec2(-1, 36*d), false) then imgui.SetCursorPosY(imgui.GetCursorPosY() + 4*d) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.3,0.92,0.3,1)) imgui.Text(IC.up..' '.._cyr5f('\xcf\xf0\xee\xe4\xe0\xe6\xe8: ').._cyr5f(total_sell_cnt..' \xf8\xf2.')..' '..IC.coin..' '.._cyr5f(_kcr3y(total_sell_sum))) imgui.PopStyleColor() imgui.SameLine(0, 20*d) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.35,0.65,1.0,1)) imgui.Text(IC.dn..' '.._cyr5f('\xcf\xee\xea\xf3\xef\xea\xe8: ').._cyr5f(total_buy_cnt..' \xf8\xf2.')..' '..IC.coin..' '.._cyr5f(_kcr3y(total_buy_sum))) imgui.PopStyleColor() imgui.EndChild() end imgui.PopStyleColor() local list_h3=imgui.GetWindowHeight()-imgui.GetCursorPosY()-48*d if imgui.BeginChild('##log_list2',imgui.ImVec2(-1,list_h3), true) then _dpn1w() local cw_lg2 = imgui.GetWindowContentRegionWidth() local fixed_lg2 = (120 + 40 + 125 + 98) * d local flex_lg2 = math.max(cw_lg2 - fixed_lg2 - 8*d, (140 + 130) * d) local col1_lg2 = math.floor(flex_lg2 * 0.52) local col5_lg2 = flex_lg2 - col1_lg2 imgui.Columns(6,'##loghdr2',false) imgui.SetColumnWidth(0,120*d); imgui.SetColumnWidth(1,col1_lg2) imgui.SetColumnWidth(2,40*d); imgui.SetColumnWidth(3,125*d) imgui.SetColumnWidth(4,98*d); imgui.SetColumnWidth(5,col5_lg2) local ac3=imgui.ImVec4(ar3, ag3, ab3, 1) imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1),u8'\xc4\xe0\xf2\xe0'); imgui.NextColumn() imgui.TextColored(ac3,u8'\xd2\xee\xe2\xe0\xf0'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1),u8'\xca\xee\xeb.'); imgui.NextColumn() imgui.TextColored(ac3,u8'\xd6\xe5\xed\xe0 $'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1),u8'\xd2\xe8\xef'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1),u8'\xcd\xe8\xea'); imgui.NextColumn() imgui.Separator() if #log_filt==0 then imgui.TextDisabled(u8' \xcb\xee\xe3 \xef\xf3\xf1\xf2.') for _=1,5 do imgui.NextColumn() end end for ri=lf_from,lf_to do local le=log_filt[ri]; if not le then break end local is_sell = fh_is_my_sell(le) local tc3=is_sell and imgui.ImVec4(0.4,0.95,0.4,1) or imgui.ImVec4(0.4,0.7,1,1) imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1),_cyr5f(' '..(le.dt or ''))); imgui.NextColumn() do local _istr = _cyr5f(' '..(le.item or '')) local _itw = imgui.CalcTextSize(_istr).x local _isp = imgui.GetCursorScreenPos() local _ilh = imgui.GetTextLineHeight() imgui.InvisibleButton('##lgib'..ri, imgui.ImVec2(col1_lg2 - 4, _ilh)) if imgui.IsItemClicked(0) then _G.mkt_detail_item = le.item _G.mkt_detail_src = 'cp' _G.mkt_detail_open = true end local _idl = imgui.GetWindowDrawList() _idl:PushClipRect(_isp, imgui.ImVec2(_isp.x + col1_lg2 - 5, _isp.y + _ilh + 2), true) local _ioff = 0 if _itw > col1_lg2 - 8 then local _isd = _itw - col1_lg2 + 10 local _ispd = 1.5 local _ispt = _isd / 40 + 2 * _ispd local _iph = math.fmod(imgui.GetTime() + ri * 0.47, _ispt) if _iph > _ispd then _ioff = math.min((_iph - _ispd) * 40, _isd) end if _iph >= _ispt - _ispd then _ioff = _isd end end _idl:AddText(imgui.ImVec2(_isp.x - _ioff, _isp.y), 0xE6FFFFFF, _istr) _idl:PopClipRect() end imgui.NextColumn() imgui.TextColored(imgui.ImVec4(1,1,1,0.7),_cyr5f(' '..(le.qty or 1))); imgui.NextColumn() local _total_price = (le.price or 0) * (le.qty or 1) imgui.TextColored(tc3,_cyr5f(' $'.._kcr3y(_total_price))); imgui.NextColumn() local _tc4 = is_sell and imgui.ImVec4(0.3,0.92,0.3,1) or imgui.ImVec4(0.35,0.65,1,1) local _op4 = is_sell and (IC.up..' '.._cyr5f('Продажа')) or (IC.dn..' '.._cyr5f('Покупка')) imgui.TextColored(_tc4, _op4); imgui.NextColumn() local _partner = le.partner or '' do local _nstr = _partner ~= '' and _cyr5f(' '.._partner) or ' —' local _ncol = _partner ~= '' and 0xFFDADADA or 0xFF808080 local _ntw = imgui.CalcTextSize(_nstr).x local _nsp = imgui.GetCursorScreenPos() local _nlh = imgui.GetTextLineHeight() imgui.Dummy(imgui.ImVec2(col5_lg2 - 4, _nlh)) local _ndl = imgui.GetWindowDrawList() _ndl:PushClipRect(_nsp, imgui.ImVec2(_nsp.x + col5_lg2 - 5, _nsp.y + _nlh + 2), true) local _noff = 0 if _ntw > col5_lg2 - 8 then local _nsd = _ntw - col5_lg2 + 10 local _nspd = 1.5 local _nspt = _nsd / 40 + 2 * _nspd local _nph = math.fmod(imgui.GetTime() + ri * 0.47 + 0.3, _nspt) if _nph > _nspd then _noff = math.min((_nph - _nspd) * 40, _nsd) end if _nph >= _nspt - _nspd then _noff = _nsd end end _ndl:AddText(imgui.ImVec2(_nsp.x - _noff, _nsp.y), _ncol, _nstr) _ndl:PopClipRect() end imgui.NextColumn() end imgui.Columns(1); imgui.EndChild() end local pw3=42*d if imgui.Button(IC.ll..'##lgpp',imgui.ImVec2(pw3,0)) then _G.mkt_log_page=1 end imgui.SameLine(0,4*d) if imgui.Button(IC.al..'##lgpr',imgui.ImVec2(pw3,0)) then if _G.mkt_log_page>1 then _G.mkt_log_page=_G.mkt_log_page-1 end end imgui.SameLine(0,6*d) imgui.TextColored(imgui.ImVec4(ar3, ag3, ab3, 1),_cyr5f('\xd1\xf2\xf0. '.._G.mkt_log_page..'/'..log_pages..' ('..#log_filt..')')) imgui.SameLine(0,6*d) if imgui.Button(IC.ar..'##lgnx',imgui.ImVec2(pw3,0)) then if _G.mkt_log_page pos then table.insert(segments, {r=cur_r,g=cur_g,b=cur_b, t=raw:sub(pos,ts-1)}) end cur_r = tonumber(rh,16)/255 cur_g = tonumber(gh,16)/255 cur_b = tonumber(bh,16)/255 pos = te + 1 else table.insert(segments, {r=cur_r,g=cur_g,b=cur_b, t=raw:sub(pos)}) break end end if #segments == 0 then return end for si, seg in ipairs(segments) do if seg.t ~= '' then imgui.TextColored(imgui.ImVec4(seg.r,seg.g,seg.b,1), _cyr5f(seg.t)) if si < #segments then imgui.SameLine(0,0) end end end end local trade_words = { '\xcf\xf0\xee\xe4\xe0\xec', '\xcf\xf0\xee\xe4\xe0\xfd\xf8\xfc', '\xca\xf3\xef\xeb\xfe', '\xca\xf3\xef\xeb\xfe', '\xd1\xe4\xe0\xec', '\xd1\xe4\xe0\xb8\xf8\xfc', '\xce\xe1\xec\xe5\xed', '\xee\xe1\xec\xe5\xed\xff\xfe', '\xd1\xe5\xeb\xeb', 'sell', 'buy', '/sell', '/buy', '/findilavka', '/findibiz', '\xef\xf0\xee\xe4\xe0\xfc', '\xef\xf0\xee\xe4\xe0\xeb', '\xea\xf3\xef\xeb\xfe', '\xea\xf3\xef\xe8\xec', '\xf2\xee\xf0\xe3', 'trade', '\xf1\xe4\xe5\xeb\xea\xe0', '\xc2\xfb\xe3\xee\xe4\xed\xee' } if not _G.fh_chat_page then _G.fh_chat_page = 1 end local CHAT_PAGE = 80 local _chat_cache_key = tostring(_G.fh_chat_filter) ..'|'..(_G.fh_chat_srch_s or '') ..'|'..tostring(#fh_session_chat) if _G._fh_chat_cache_key ~= _chat_cache_key and not _G._fh_chat_building then _G._fh_chat_cache_key = _chat_cache_key _G._fh_chat_building = true local _snap = fh_session_chat local _filt = _G.fh_chat_filter local _srch = _G.fh_chat_srch_s or '' local _tw = trade_words lua_thread.create(function() local _res = {} for _i, _cmsg in ipairs(_snap) do local _raw = _cmsg:gsub('{%x%x%x%x%x%x%x?%x?}', '') local _lo = _raw :gsub('[A-Z]', function(c) return string.char(string.byte(c)+32) end) :gsub('[А-Я]', function(c) return string.char(string.byte(c)+32) end) local srch_ok = _srch == '' or _lo:find(_srch, 1, true) local filt_ok = false if _filt == 0 then filt_ok = true elseif _filt == 1 then filt_ok = _lo:find('vip adv', 1, true) ~= nil elseif _filt == 2 then for _, _w in ipairs(_tw) do local _wl = _w:gsub('[A-Z]',function(c) return string.char(string.byte(c)+32) end) :gsub('[А-Я]',function(c) return string.char(string.byte(c)+32) end) if _lo:find(_wl, 1, true) then filt_ok=true; break end end elseif _filt == 3 then filt_ok = _cmsg:find('[Альянс]', 1, true) ~= nil elseif _filt == 4 then filt_ok = _cmsg:find('[Семья]', 1, true) ~= nil end if filt_ok and srch_ok then table.insert(_res, _cmsg) end if _i % 300 == 0 then wait(0) end end _G._fh_chat_filtered = _res _G._fh_chat_building = false _G.fh_chat_page = 1 end) end local filtered = _G._fh_chat_filtered or {} local chat_h = imgui.GetWindowHeight() - 270*d if imgui.BeginChild('##fh_chat_list', imgui.ImVec2(-1, chat_h), true) then _dpn1w() local chat_pages = math.max(1, math.ceil(#filtered / CHAT_PAGE)) if _G.fh_chat_page > chat_pages then _G.fh_chat_page = chat_pages end local cf_from = (_G.fh_chat_page - 1) * CHAT_PAGE + 1 local cf_to = math.min(_G.fh_chat_page * CHAT_PAGE, #filtered) local shown = 0 for ri = cf_from, cf_to do local _cmsg = filtered[ri] if _cmsg then _msc6g(_cmsg) shown = shown + 1 end end if shown == 0 then imgui.TextDisabled(_cyr5f(' \xd1\xee\xee\xe1\xf9\xe5\xed\xe8\xe9 \xed\xe5\xf2.')) end imgui.EndChild() end local _chat_pages_now = math.max(1, math.ceil(#fh_session_chat / CHAT_PAGE)) local pw4 = 36*d if imgui.Button(IC.ll..'##chpp', imgui.ImVec2(pw4,0)) then _G.fh_chat_page=1 end imgui.SameLine(0,3*d) if imgui.Button(IC.al..'##chpr', imgui.ImVec2(pw4,0)) then if _G.fh_chat_page>1 then _G.fh_chat_page=_G.fh_chat_page-1 end end imgui.SameLine(0,5*d) imgui.TextColored(imgui.ImVec4(ar3,ag3,ab3,1), _cyr5f('\xd1\xf2\xf0. '.._G.fh_chat_page..'/'.._chat_pages_now)) imgui.SameLine(0,5*d) if imgui.Button(IC.ar..'##chnx', imgui.ImVec2(pw4,0)) then if _G.fh_chat_page<_chat_pages_now then _G.fh_chat_page=_G.fh_chat_page+1 end end imgui.SameLine(0,3*d) if imgui.Button(IC.rr..'##chls', imgui.ImVec2(pw4,0)) then _G.fh_chat_page=_chat_pages_now end else if not _G.fh_hist_init then _G.fh_hist_init = true _wdk4v() _G.fh_hist_srch = imgui.new.char[256]('') _G.fh_hist_srch_s = '' end local function _ptz9q(raw) local segs = {} local cr,cg,cb = 0.75,0.75,0.75 local pos = 1 while pos <= #raw do local ts8,te8,rh8,gh8,bh8 = raw:find('{(%x%x)(%x%x)(%x%x)%x%x}', pos) local ts6,te6,rh6,gh6,bh6 = raw:find('{(%x%x)(%x%x)(%x%x)}', pos) local ts,te,rh,gh,bh if ts8 and ts6 then if ts8<=ts6 then ts,te,rh,gh,bh=ts8,te8,rh8,gh8,bh8 else ts,te,rh,gh,bh=ts6,te6,rh6,gh6,bh6 end elseif ts8 then ts,te,rh,gh,bh=ts8,te8,rh8,gh8,bh8 elseif ts6 then ts,te,rh,gh,bh=ts6,te6,rh6,gh6,bh6 end if ts then if ts>pos then table.insert(segs,{r=cr,g=cg,b=cb,t=raw:sub(pos,ts-1)}) end cr=tonumber(rh,16)/255; cg=tonumber(gh,16)/255; cb=tonumber(bh,16)/255 pos=te+1 else table.insert(segs,{r=cr,g=cg,b=cb,t=raw:sub(pos)}); break end end if #segs==0 then return end for si,seg in ipairs(segs) do if seg.t~='' then imgui.TextColored(imgui.ImVec4(seg.r,seg.g,seg.b,1), _cyr5f(seg.t)) if si<#segs then imgui.SameLine(0,0) end end end end if imgui.Button(IC.rot..'##hrefr', imgui.ImVec2(0,0)) then _wdk4v() fh_log_view.sel_date = nil fh_log_view.lines = {} end imgui.SameLine(0, 6*d) imgui.TextDisabled(_cyr5f('Доступно: '..#fh_log_view.dates..' дн.')) imgui.SameLine(0, 8*d) local _hs_srch_w = imgui.GetContentRegionAvail().x - 32*d imgui.PushItemWidth(_hs_srch_w) imgui.PushStyleColor(imgui.Col.FrameBg, imgui.ImVec4(bg+.08,bg+.07,bg+.04, _G._mh_wa or 1)) if imgui.InputTextWithHint(u8'##hist_srch', _cyr5f('Поиск...'), _G.fh_hist_srch, 256) then local _rh = u8:decode(ffi.string(_G.fh_hist_srch)) local _okh,_cph = pcall(function() return require('encoding').CP1251:encode(_rh) end) local _sh = (_okh and _cph or _rh):lower() _G.fh_hist_srch_s = _sh:gsub('[А-Я]',function(c) return string.char(string.byte(c)+32) end) _G._fh_hist_cache_key = nil end imgui.PopStyleColor(); imgui.PopItemWidth() imgui.SameLine(0, 3*d) local _has_hs_srch = ffi.string(_G.fh_hist_srch) ~= '' imgui.PushStyleColor(imgui.Col.Button, _has_hs_srch and imgui.ImVec4(0.38,0.08,0.08,1) or imgui.ImVec4(0.12,0.12,0.12,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.55,0.12,0.12, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, _has_hs_srch and imgui.ImVec4(1,0.4,0.4,1) or imgui.ImVec4(0.3,0.3,0.3,1)) if imgui.Button(IC.x..'##hssrchclr', imgui.ImVec2(28*d, 0)) and _has_hs_srch then ffi.fill(_G.fh_hist_srch, 256, 0) _G.fh_hist_srch_s = ''; _G._fh_hist_cache_key = nil end imgui.PopStyleColor(3) imgui.Separator() local hist_h = imgui.GetWindowHeight() - imgui.GetCursorPosY() - 52*d local date_list_w = 110*d if imgui.BeginChild('##hist_dates', imgui.ImVec2(date_list_w, hist_h), true) then _dpn1w() if #fh_log_view.dates == 0 then imgui.TextDisabled(_cyr5f(' Нет логов')) end for _, dt in ipairs(fh_log_view.dates) do local sel = fh_log_view.sel_date and fh_log_view.sel_date.label == dt.label if imgui.Selectable(u8(dt.label)..'##histdt'..dt.label, sel) then fh_log_view.sel_date = dt fh_log_view.lines = {} fh_log_view.loading = true lua_thread.create(function() _tjr8f() fh_log_view.loading = false end) end if sel then imgui.SetItemDefaultFocus() end end imgui.EndChild() end imgui.SameLine(0, 4*d) if imgui.BeginChild('##fh_hist_list', imgui.ImVec2(-1, hist_h), true) then _dpn1w() if not fh_log_view.sel_date then imgui.TextDisabled(_cyr5f(' Выберите дату слева')) elseif #fh_log_view.lines == 0 then imgui.TextDisabled(_cyr5f(' Сообщений нет.')) else local _hckey = tostring(#fh_log_view.lines) ..'|'..(_G.fh_hist_srch_s or '') ..'|'..(fh_log_view.sel_date and fh_log_view.sel_date.label or '') if _G._fh_hist_cache_key ~= _hckey then _G._fh_hist_cache_key = _hckey _G._fh_hist_filtered = {} local srch = _G.fh_hist_srch_s or '' for _, hline in ipairs(fh_log_view.lines) do if hline ~= '' then local _pl_r = hline:gsub('{%x%x%x%x%x%x%x?%x?}', '') local pl = _pl_r:lower():gsub('[\192-\223]',function(c) return string.char(string.byte(c)+32) end) if srch == '' or pl:find(srch, 1, true) then table.insert(_G._fh_hist_filtered, hline) end end end _G.fh_hist_page = 1 end local hfilt = _G._fh_hist_filtered or {} local HIST_PAGE = 100 if not _G.fh_hist_page then _G.fh_hist_page = 1 end local h_pages = math.max(1, math.ceil(#hfilt / HIST_PAGE)) if _G.fh_hist_page > h_pages then _G.fh_hist_page = h_pages end local hf_from = (_G.fh_hist_page - 1) * HIST_PAGE + 1 local hf_to = math.min(_G.fh_hist_page * HIST_PAGE, #hfilt) for ri = hf_from, hf_to do local hline = hfilt[ri] if hline then _ptz9q(hline) end end if #hfilt == 0 then imgui.TextDisabled(_cyr5f(' Ничего не найдено.')) end end imgui.EndChild() end if fh_log_view.sel_date then local _hf2 = _G._fh_hist_filtered or {} local _hp2 = _G.fh_hist_page or 1 local _hpg = math.max(1, math.ceil(#_hf2 / 100)) local pw2 = (imgui.GetWindowContentRegionWidth() - 120*d) / 4 if imgui.Button(IC.ll..'##hppp', imgui.ImVec2(pw2,0)) then _G.fh_hist_page=1 end imgui.SameLine(0,2*d) if imgui.Button(IC.al..'##hppr', imgui.ImVec2(pw2,0)) then if _hp2 > 1 then _G.fh_hist_page = _hp2-1 end end imgui.SameLine(0,2*d) imgui.TextColored(imgui.ImVec4(ar3,ag3,ab3,1), _cyr5f('Стр. '.._hp2..'/'.._hpg..' ('..#_hf2..')')) imgui.SameLine(0,2*d) if imgui.Button(IC.ar..'##hpnx', imgui.ImVec2(pw2,0)) then if _hp2 < _hpg then _G.fh_hist_page = _hp2+1 end end imgui.SameLine(0,2*d) if imgui.Button(IC.rr..'##hpls', imgui.ImVec2(pw2,0)) then _G.fh_hist_page=_hpg end end end elseif _G.log_tab_mode == 3 then if not settings.telegram then settings.telegram={} end local tg=settings.telegram if not _G.tg_bot_buf then _G.tg_bot_buf =imgui.new.char[128](tg.bot_token or '') end if not _G.tg_chat_buf then _G.tg_chat_buf=imgui.new.char[64](tg.chat_id or '') end local _tg_lh = imgui.GetWindowHeight() - 90*d if imgui.BeginChild('##tg_scroll', imgui.ImVec2(-1, _tg_lh), false) then _dpn1w() imgui.Spacing() imgui.TextColored(imgui.ImVec4(0.4,0.7,1,1), u8' Telegram Bot') imgui.Separator(); imgui.Spacing() imgui.TextDisabled(u8' Bot Token:'); imgui.SetNextItemWidth(-1) if imgui.InputText('##tgbot',_G.tg_bot_buf,128) then tg.bot_token=ffi.string(_G.tg_bot_buf);_wfn7p() end imgui.TextDisabled(u8' Chat ID:'); imgui.SetNextItemWidth(-1) if imgui.InputText('##tgchat',_G.tg_chat_buf,64) then tg.chat_id=ffi.string(_G.tg_chat_buf);_wfn7p() end imgui.Spacing() imgui.TextDisabled(u8' @BotFather->/newbot->token | Chat ID: @userinfobot') imgui.Spacing(); imgui.Separator(); imgui.Spacing() local en=tg.enabled or false; local enb=imgui.new.bool(en) if imgui.Checkbox(u8'Включить##tgen',enb) then tg.enabled=enb[0];_wfn7p() end imgui.Spacing() local function tgck(lbl,key) local v=tg[key] or false; local vb=imgui.new.bool(v) if imgui.Checkbox(u8(' '..lbl)..'##tgc_'..key,vb) then tg[key]=vb[0];_wfn7p() end end tgck('Мои продажи / покупки','notify_trades') tgck('Товар из вотчлиста в лавке','notify_watch') tgck('Я в игре (пинг каждый час)','notify_heartbeat') imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextColored(imgui.ImVec4(0.5,0.8,1,1), _cyr5f(' TG Прокси (для России)')) imgui.Separator(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Через сервер, если TG заблокирован')) imgui.Spacing() tgck('Прокси через MH-сервер','use_proxy') imgui.Spacing(); imgui.Separator(); imgui.Spacing() -- Автообновление лавок НЕ гейтится премиумом (в отличие от -- notify_arb/notify_fav ниже): и тег "Смотреть" (доступен -- всем), и вотчлист/арбитраж-уведомления читают один и тот же -- mh_arz_data, который без автообновления не меняется сама -- по себе - без него уведомления по тегам не приходят вообще -- ни у кого, премиум тут ни при чём, это вопрос базовой -- работоспособности функции, а не привилегия. tgck('Автообновление лавок каждые 5 мин','auto_refresh_shops') local _is_p = _mh_is_premium() if _is_p then tgck('Арбитраж в Telegram','notify_arb') tgck('Избранное дешевле рынка','notify_fav') else imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.4,0.4,0.4,1)) imgui.Text(IC.circs..' '.._cyr5f('Арбитраж в Telegram (Premium)')) imgui.Text(IC.circs..' '.._cyr5f('Избранное дешевле рынка (Premium)')) imgui.PopStyleColor() end imgui.Spacing() imgui.TextDisabled(u8'Порог арб. $:'); imgui.SameLine(0,4*d) if not _G.tg_thr_buf then _G.tg_thr_buf=imgui.new.float(tg.arb_threshold or 0) end imgui.SetNextItemWidth(120*d) if imgui.InputFloat('##tgthr',_G.tg_thr_buf,0,0,'%.0f') then tg.arb_threshold=_G.tg_thr_buf[0];_wfn7p() end imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.PushStyleColor(imgui.Col.Button,_mh_bc(0.1,0.4,0.8,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered,imgui.ImVec4(0.15,0.55,1, _G._mh_wa or 1)) if imgui.Button(u8' Тест TG ##tgtest',imgui.ImVec2(-1,0)) then mh_tg_send('[MH] Тест Telegram!',false) end imgui.PopStyleColor(2) imgui.EndChild() end elseif _G.log_tab_mode == 4 then if _mh_is_premium() then local _tap = settings.trade_autoprice imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.0, 0.15, 0.1, 0.6)) if imgui.BeginChild('##trd_ap', imgui.ImVec2(-1, 58*d), false) then imgui.Spacing() if not _G._tap_en_cb then _G._tap_en_cb = imgui.new.bool(_tap.enabled) end _G._tap_en_cb[0] = _tap.enabled imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1, 0.82, 0.1, 1)) if imgui.Checkbox(fa.STAR .. ' ' .. _cyr5f('Авто-цена (Premium)##tapchk'), _G._tap_en_cb) then _tap.enabled = _G._tap_en_cb[0]; _wfn7p() end imgui.PopStyleColor() imgui.SameLine(0, 12*d) imgui.TextDisabled(_cyr5f('% от рынка:')) imgui.SameLine(0, 4*d) if not _G._tap_pct_sl then _G._tap_pct_sl = imgui.new.int(_tap.pct or 65) end imgui.PushItemWidth(100*d) if imgui.SliderInt('##tappct', _G._tap_pct_sl, 10, 100, '%d%%') then _tap.pct = _G._tap_pct_sl[0]; _wfn7p() end imgui.PopItemWidth() imgui.EndChild() end imgui.PopStyleColor() imgui.Spacing() end if not _G.trd_day_filter then _G.trd_day_filter = 1 end if not _G.trd_day_sel_date then _G.trd_day_sel_date = '' end if not _G.trade_log_page then _G.trade_log_page = 1 end local _PAGE = 20 local _trd_dates, _trd_dates_seen = {}, {} for _di=#fh_trade_log,1,-1 do local _de=fh_trade_log[_di] if _de and _de.dt then local _dk=_de.dt:sub(1,5) if _dk~='' and not _trd_dates_seen[_dk] then _trd_dates_seen[_dk]=true; table.insert(_trd_dates,_dk) end end end local function _trd_day_ok(le_dt) if _G.trd_day_filter==0 then return true end if not le_dt or le_dt=='' then return false end local now=os.time(); local t=os.date('*t',now) local d_day=string.format('%02d.%02d',t.day,t.month) local e_day=le_dt:sub(1,5) if _G.trd_day_filter==1 then return e_day==d_day elseif _G.trd_day_filter==2 then local e_d=tonumber(le_dt:sub(1,2)) or 0; local e_m=tonumber(le_dt:sub(4,5)) or 0 local e_ts=os.time({year=t.year,month=e_m,day=e_d,hour=0,min=0,sec=0}) if e_ts>now then e_ts=os.time({year=t.year-1,month=e_m,day=e_d,hour=0,min=0,sec=0}) end return (now-e_ts)<=(7*86400) elseif _G.trd_day_filter==3 then local e_d=tonumber(le_dt:sub(1,2)) or 0; local e_m=tonumber(le_dt:sub(4,5)) or 0 local e_ts=os.time({year=t.year,month=e_m,day=e_d,hour=0,min=0,sec=0}) if e_ts>now then e_ts=os.time({year=t.year-1,month=e_m,day=e_d,hour=0,min=0,sec=0}) end return (now-e_ts)<=(30*86400) elseif _G.trd_day_filter==4 then return e_day==(_G.trd_day_sel_date or '') end return true end local _tl_f={} for _fi=1,#fh_trade_log do local _fe=fh_trade_log[_fi] if _fe and _trd_day_ok(_fe.dt) then table.insert(_tl_f,_fe) end end local _tl=_tl_f local _tlpg=math.max(1,math.ceil(#_tl/_PAGE)) if _G.trade_log_page>_tlpg then _G.trade_log_page=_tlpg end local _tf=(_G.trade_log_page-1)*_PAGE+1 local _tt=math.min(_G.trade_log_page*_PAGE,#_tl) imgui.Spacing() local _tdf_w=(imgui.GetContentRegionAvail().x-20*d)/5 local _tdf_labels={ [0]=_cyr5f('Все'), [1]=fa.CALENDAR_DAY..' '.._cyr5f('Сегодня'), [2]=fa.CALENDAR_WEEK..' '.._cyr5f('Неделя'), [3]=IC.calds..' '.._cyr5f('Месяц'), } for _fi2=0,3 do if _fi2>0 then imgui.SameLine(0,5*d) end local _fa2=_G.trd_day_filter==_fi2 if _fa2 then imgui.PushStyleColor(imgui.Col.Button,_mh_bc(ar3*.5,ag3*.5,ab3*.5,.9)) end if imgui.Button(_tdf_labels[_fi2]..'##tdf'.._fi2,imgui.ImVec2(_tdf_w,0)) then _G.trd_day_filter=_fi2; _G.trade_log_page=1 end if _fa2 then imgui.PopStyleColor() end end imgui.SameLine(0,5*d) do local _fd4=_G.trd_day_filter==4 if _fd4 then imgui.PushStyleColor(imgui.Col.Button,_mh_bc(ar3*.5,ag3*.5,ab3*.5,.9)) end if imgui.Button(fa.CALENDAR..' '.._cyr5f('Дата##tdf4'),imgui.ImVec2(_tdf_w,0)) then _G.trd_day_filter=4; _G.trade_log_page=1; _G.trd_day_popup_open=true end if _fd4 then imgui.PopStyleColor() end if _G.trd_day_filter==4 and _G.trd_day_sel_date~='' then imgui.SameLine(0,4*d); imgui.TextDisabled(_G.trd_day_sel_date) end end if _G.trd_day_popup_open and #_trd_dates>0 then imgui.OpenPopup('##trdpick'); _G.trd_day_popup_open=false end if imgui.BeginPopup('##trdpick') then imgui.TextDisabled(_cyr5f('Выберите дату:')) imgui.Separator() for _,_dd in ipairs(_trd_dates) do local _sel=_G.trd_day_sel_date==_dd if _sel then imgui.PushStyleColor(imgui.Col.Text,imgui.ImVec4(ar3,ag3,ab3,1)) end if imgui.Selectable(_dd..'##trdsel',_sel,0,imgui.ImVec2(80*d,0)) then _G.trd_day_sel_date=_dd; _G.trd_day_filter=4; _G.trade_log_page=1 imgui.CloseCurrentPopup() end if _sel then imgui.PopStyleColor() end end imgui.EndPopup() end imgui.Spacing() local _sgm,_sget=0,0 for _,_tr in ipairs(_tl) do _sgm=_sgm+(_tr.give_money or 0); _sget=_sget+(_tr.get_money or 0) end imgui.PushStyleColor(imgui.Col.ChildBg,imgui.ImVec4(0.0,0.18,0.05,0.55)) if imgui.BeginChild('##tlst',imgui.ImVec2(-1,36*d),false) then imgui.SetCursorPosY(imgui.GetCursorPosY()+4*d) imgui.PushStyleColor(imgui.Col.Text,imgui.ImVec4(0.4,0.9,0.4,1)) imgui.Text(fa.ARROWS_LEFT_RIGHT..' '.._cyr5f('Трейдов: '..(#_tl)..' Отдал: $'.._kcr3y(_sgm)..' Получил: $'.._kcr3y(_sget))) imgui.PopStyleColor(); imgui.EndChild() end imgui.PopStyleColor(); imgui.Spacing() local _avail_h = imgui.GetContentRegionAvail().y local _btns_h = 32*d + 32*d + 8*d local _lh = math.max(60*d, _avail_h - _btns_h) if imgui.BeginChild('##tlls',imgui.ImVec2(-1,_lh), false) then _dpn1w() if #_tl==0 then imgui.Spacing() imgui.TextDisabled(_cyr5f(' Сделок нет.')) end local hc4=imgui.ImVec4(ar3*.6,ag3*.6,ab3*.4,1) for _ti=_tf,_tt do local _tr=_tl[_ti]; if not _tr then break end imgui.Spacing() imgui.TextColored(hc4,_cyr5f(' '.._tr.dt..' ')) imgui.SameLine(0,2*d) imgui.TextColored(imgui.ImVec4(ar3,ag3,ab3,1),fa.ARROWS_LEFT_RIGHT..' '.._cyr5f(_tr.partner)) if #(_tr.give_items or {})>0 or (_tr.give_money or 0)>0 then imgui.TextColored(imgui.ImVec4(1,0.45,0.3,1),_cyr5f(' Отдал:')) if (_tr.give_money or 0)>0 then imgui.SameLine(0,4*d) imgui.TextColored(imgui.ImVec4(1,0.7,0.2,1),fa.COINS..' '.._cyr5f('$'.._kcr3y(_tr.give_money))) end for _,gi in ipairs(_tr.give_items or {}) do imgui.TextDisabled(_cyr5f(' '..gi.name..' x'..gi.qty)) end end if #(_tr.get_items or {})>0 or (_tr.get_money or 0)>0 then imgui.TextColored(imgui.ImVec4(0.35,0.9,0.35,1),_cyr5f(' Получил:')) if (_tr.get_money or 0)>0 then imgui.SameLine(0,4*d) imgui.TextColored(imgui.ImVec4(0.4,1,0.4,1),fa.COINS..' '.._cyr5f('$'.._kcr3y(_tr.get_money))) end for _,gi in ipairs(_tr.get_items or {}) do imgui.TextDisabled(_cyr5f(' '..gi.name..' x'..gi.qty)) end end imgui.Separator() end imgui.EndChild() end if _tlpg>1 then imgui.Spacing() local _pw=(imgui.GetContentRegionAvail().x-8*d)/4 if imgui.Button(fa.ANGLES_LEFT..'##tpp', imgui.ImVec2(_pw,0)) then _G.trade_log_page=1 end imgui.SameLine(0,2*d) if imgui.Button(fa.ANGLE_LEFT..'##tpb', imgui.ImVec2(_pw,0)) then _G.trade_log_page=math.max(1,_G.trade_log_page-1) end imgui.SameLine(0,2*d) if imgui.Button(fa.ANGLE_RIGHT..'##tpn', imgui.ImVec2(_pw,0)) then _G.trade_log_page=math.min(_tlpg,_G.trade_log_page+1) end imgui.SameLine(0,2*d) if imgui.Button(fa.ANGLES_RIGHT..'##tppp',imgui.ImVec2(_pw,0)) then _G.trade_log_page=_tlpg end imgui.SameLine(0,6*d) imgui.TextDisabled(_cyr5f('Стр. '.._G.trade_log_page..'/'.. _tlpg)) end imgui.Spacing() imgui.PushStyleColor(imgui.Col.Button,imgui.ImVec4(0.4,0.08,0.08,0.8)) if imgui.Button(fa.TRASH_CAN..' '.._cyr5f('Очистить##tlcl'),imgui.ImVec2(0,0)) then fh_trade_log={}; _G.trade_log_page=1; _ryb5t() end imgui.PopStyleColor() end end if _G.mh_tab == 8 then local d = settings.general.custom_dpi local cw_cm = imgui.GetWindowContentRegionWidth() local ar_cm = settings.interface.accent_r or 1 local ag_cm = settings.interface.accent_g or 0.65 local ab_cm = settings.interface.accent_b or 0.0 local sb_r = settings.interface.sell_btn_r or 0.10 local sb_g = settings.interface.sell_btn_g or 0.45 local sb_b = settings.interface.sell_btn_b or 0.10 local bb_r = settings.interface.buy_btn_r or 0.00 local bb_g = settings.interface.buy_btn_g or 0.28 local bb_b = settings.interface.buy_btn_b or 0.50 local ac_cm = imgui.ImVec4(ar_cm, ag_cm, ab_cm, 1) local _cm_visible_h = imgui.GetContentRegionAvail().y if imgui.BeginChild('##cm_scroll', imgui.ImVec2(-1, _cm_visible_h), false) then _dpn1w() imgui.Spacing() if cm_catch_status ~= '' then imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.08, 0.18, 0.08, _G._mh_wa or 1)) if imgui.BeginChild('##cm_status', imgui.ImVec2(-1, 32*d), false, imgui.WindowFlags.NoScrollWithMouse + imgui.WindowFlags.NoScrollbar) then imgui.SetCursorPosY(imgui.GetCursorPosY() + 5*d) imgui.TextColored(imgui.ImVec4(0.3, 0.95, 0.3, 1), IC.circc..' '.._cyr5f(cm_catch_status)) imgui.EndChild() end imgui.PopStyleColor() imgui.Spacing() end imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.14, 0.14, 0.17, _G._mh_wa or 1)) if imgui.BeginChild('##cm_block1', imgui.ImVec2(-1, 68*d), true, imgui.WindowFlags.NoScrollWithMouse + imgui.WindowFlags.NoScrollbar) then imgui.Spacing() imgui.TextColored(imgui.ImVec4(0.5, 0.75, 1, 1), IC.lxh..' '..u8'\xd0\xe0\xe4\xe8\xf3\xf1 \xeb\xe0\xe2\xee\xea') imgui.SameLine(cw_cm - 90*d) if cm_radius_enabled then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(sb_r, sb_g, sb_b, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.4, sb_g*1.4, sb_b*1.4, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(sb_r*0.85, sb_g*0.85, sb_b*0.85, 1)) if imgui.Button(IC.circc..' '..u8'\xc2\xea\xeb##cm_r', imgui.ImVec2(85*d, 0)) then cm_radius_enabled = false end imgui.PopStyleColor(3) else if imgui.Button(IC.circ..' '..u8'\xc2\xea\xeb##cm_r', imgui.ImVec2(85*d, 0)) then cm_radius_enabled = true end end imgui.TextDisabled(u8' \xce\xf2\xee\xe1\xf0\xe0\xe6\xe0\xe5\xf2 \xea\xf0\xf3\xe3 \xf0\xe0\xe4\xe8\xf3\xf1\xe0 5\xec \xe2\xee\xea\xf0\xf3\xe3 \xea\xe0\xe6\xe4\xee\xe9 \xeb\xe0\xe2\xea\xe8 \xd6\xd0') imgui.EndChild() end imgui.PopStyleColor() imgui.Spacing() imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.14, 0.14, 0.17, _G._mh_wa or 1)) if imgui.BeginChild('##cm_block2', imgui.ImVec2(-1, 68*d), true, imgui.WindowFlags.NoScrollWithMouse + imgui.WindowFlags.NoScrollbar) then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.65, 0.1, 1), IC.mgnt..' '..u8'\xc0\xe2\xf2\xee-\xeb\xee\xe2\xeb\xff') imgui.SameLine(cw_cm - 90*d) if cm_catch_enabled then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.5, 0.25, 0.0, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.65, 0.35, 0.0, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(0.4, 0.2, 0.0, 1)) if imgui.Button(IC.circc..' '..u8'\xc2\xea\xeb##cm_c', imgui.ImVec2(85*d, 0)) then cm_catch_enabled = false cm_catch_status = '' mh_notify('[MH] {ff8800}\xc0\xe2\xf2\xee-\xeb\xee\xe2\xeb\xff \xe2\xfb\xea\xeb\xfe\xf7\xe5\xed\xe0', 0xFFFFFF) end imgui.PopStyleColor(3) else if imgui.Button(IC.circ..' '..u8'\xc2\xea\xeb##cm_c', imgui.ImVec2(85*d, 0)) then cm_catch_enabled = true mh_notify('[MH] {00cc00}\xc0\xe2\xf2\xee-\xeb\xee\xe2\xeb\xff \xe2\xea\xeb\xfe\xf7\xe5\xed\xe0', 0xFFFFFF) end end imgui.TextDisabled(u8' \xc0\xe2\xf2\xee\xec\xe0\xf2\xe8\xf7\xe5\xf1\xea\xe8 \xe7\xe0\xed\xe8\xec\xe0\xe5\xf2 \xeb\xe0\xe2\xea\xf3 \xef\xf0\xe8 \xef\xee\xff\xe2\xeb\xe5\xed\xe8\xe8 \xe4\xe8\xe0\xeb\xee\xe3\xe0 3010') imgui.EndChild() end imgui.PopStyleColor() imgui.Spacing() imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.14, 0.14, 0.17, _G._mh_wa or 1)) if imgui.BeginChild('##cm_block3', imgui.ImVec2(-1, 68*d), true, imgui.WindowFlags.NoScrollWithMouse + imgui.WindowFlags.NoScrollbar) then imgui.Spacing() imgui.TextColored(imgui.ImVec4(0.3, 0.85, 1, 1), IC.map..' '..u8'\xd0\xe5\xed\xe4\xe5\xf0 \xeb\xe0\xe2\xee\xea') imgui.SameLine(cw_cm - 90*d) if cm_render_enabled then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(bb_r, bb_g, bb_b, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(bb_r*1.35, bb_g*1.35, bb_b*1.3, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(bb_r*0.85, bb_g*0.85, bb_b*0.85, 1)) if imgui.Button(IC.circc..' '..u8'\xc2\xea\xeb##cm_rn', imgui.ImVec2(85*d, 0)) then cm_render_enabled = false end imgui.PopStyleColor(3) else if imgui.Button(IC.circ..' '..u8'\xc2\xea\xeb##cm_rn', imgui.ImVec2(85*d, 0)) then cm_render_enabled = true end end imgui.TextDisabled(u8' \xd0\xe8\xf1\xf3\xe5\xf2 \xeb\xe8\xed\xe8\xe8 \xea \xf1\xe2\xee\xe1\xee\xe4\xed\xfb\xec \xeb\xe0\xe2\xea\xe0\xec \xed\xe0 \xd6\xd0') imgui.EndChild() end imgui.PopStyleColor() imgui.Spacing() imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.14, 0.14, 0.17, _G._mh_wa or 1)) if imgui.BeginChild('##cm_block4', imgui.ImVec2(-1, 68*d), true, imgui.WindowFlags.NoScrollWithMouse + imgui.WindowFlags.NoScrollbar) then imgui.Spacing() imgui.TextColored(imgui.ImVec4(0.85, 0.6, 0.1, 1), IC.arch..' '..u8'\xc0\xe2\xf2\xee /storage \xf1\xe1\xee\xf0') imgui.SameLine(cw_cm - 90*d) local st_on = settings.general and settings.general.auto_storage_collect if st_on then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(sb_r, sb_g, sb_b, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.4, sb_g*1.4, sb_b*1.4, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(sb_r*0.85, sb_g*0.85, sb_b*0.85, 1)) if imgui.Button(IC.circc..' '..u8'\xc2\xea\xeb##cm_st', imgui.ImVec2(85*d, 0)) then settings.general.auto_storage_collect = false; _wfn7p() end imgui.PopStyleColor(3) else if imgui.Button(IC.circ..' '..u8'\xc2\xea\xeb##cm_st', imgui.ImVec2(85*d, 0)) then settings.general.auto_storage_collect = true; _wfn7p() end end imgui.TextDisabled(u8' \xc0\xe2\xf2\xee-\xf1\xe1\xee\xf0 \xef\xf0\xe5\xe4\xec\xe5\xf2\xee\xe2 \xe8\xe7 /storage \xf5\xf0\xe0\xed\xe8\xeb\xe8\xf9\xe0') imgui.EndChild() end imgui.PopStyleColor() imgui.Spacing() imgui.TextDisabled(' ' .. IC.circi .. ' ' .. _cyr5f('\xcf\xee\xe4\xee\xe9\xe4\xe8\xf2\xe5 \xea \xeb\xe0\xe2\xea\xe5 \xef\xf0\xe8 \xe2\xea\xeb\xfe\xf7\xb8\xed\xed\xee\xe9 \xe0\xe2\xf2\xee-\xeb\xee\xe2\xeb\xe5')) if _mh_is_premium() then imgui.Spacing() imgui.Separator() imgui.Spacing() imgui.TextColored(imgui.ImVec4(1.0, 0.82, 0.10, 1), IC.star .. ' ' .. _cyr5f('Premium Open')) imgui.Spacing() imgui.PushStyleColor(imgui.Col.Tab, imgui.ImVec4(0.10, 0.10, 0.13, 1)) imgui.PushStyleColor(imgui.Col.TabHovered, imgui.ImVec4(0.20, 0.18, 0.10, 1)) imgui.PushStyleColor(imgui.Col.TabActive, imgui.ImVec4(0.30, 0.25, 0.08, 1)) if imgui.BeginTabBar('##prem_open_tabs') then if imgui.BeginTabItem(IC.arch .. ' ' .. _cyr5f('Ларцы##prem_open_boxes')) then imgui.Spacing() if not _G._ao_slots then _G._ao_slots = {} for _aoi = 1, 1 do _G._ao_slots[_aoi] = { name = '', slot_idx = -1, interval = 1.0, max_count = 0, done = 0, running = false, status = '', } end end if not _G._ao_ctx_slot then _G._ao_ctx_slot = nil end if not _G._ao_buf_init then _G._ao_buf_init = true _G._ao_buf_int = {} _G._ao_buf_max = {} for _aoi = 1, 1 do _G._ao_buf_int[_aoi] = imgui.new.float(_G._ao_slots[_aoi].interval) _G._ao_buf_max[_aoi] = imgui.new.int(_G._ao_slots[_aoi].max_count) end end if fh_lv_inv_scanning then imgui.TextColored(imgui.ImVec4(1, 0.7, 0, 1), _cyr5f(' Скан...')) imgui.ProgressBar(-1 * os.clock(), imgui.ImVec2(-1, 5 * d)) else local _ao_scan_lbl = (#fh_lv_inventory > 0) and (IC.boxes .. ' ' .. _cyr5f('Инвентарь (' .. #fh_lv_inventory .. ' поз.)##ao_sc')) or (IC.boxes .. ' ' .. _cyr5f('Скан инвентаря##ao_sc')) if imgui.Button(_ao_scan_lbl, imgui.ImVec2(cw_cm * 0.60, 0)) then _vcz9h() lua_thread.create(function() wait(200) _yzr1t(52, -1, 2, '{"slot":0,"type":1}') end) end end imgui.Spacing() for _aoi = 1, 1 do local _sl = _G._ao_slots[_aoi] local _bg = _sl.running and imgui.ImVec4(0.08, 0.28, 0.08, _G._mh_wa or 1) or imgui.ImVec4(0.12, 0.12, 0.15, _G._mh_wa or 1) imgui.PushStyleColor(imgui.Col.ChildBg, _bg) if imgui.BeginChild('##ao_sl' .. _aoi, imgui.ImVec2(-1, 122 * d), true, imgui.WindowFlags.NoScrollWithMouse + imgui.WindowFlags.NoScrollbar) then imgui.Spacing() local _hdr_col = _sl.running and imgui.ImVec4(0.3, 1.0, 0.3, 1) or imgui.ImVec4(1.0, 0.82, 0.10, 0.9) imgui.TextColored(_hdr_col, IC.arch .. ' ' .. _cyr5f('Открытие')) if _sl.status ~= '' then imgui.SameLine() imgui.TextDisabled(' ' .. _cyr5f(_sl.status)) end local _ao_box_cnt = 0 for _, _bci in ipairs(fh_lv_inventory) do local _bcn = fh_lower(_bci.name) if not _bcn:find('оскол', 1, true) and ( _bcn:find('лар', 1, true) or _bcn:find('кейс', 1, true) or _bcn:find('сундук', 1, true) or _bcn:find('ящик', 1, true) or _bcn:find('тайник', 1, true) or _bcn:find('шкатулк', 1, true) or _bcn:find('box', 1, true) ) then _ao_box_cnt = _ao_box_cnt + 1 end end imgui.SameLine() local _ao_cnt_col = (_ao_box_cnt > 0) and imgui.ImVec4(0.35, 0.85, 0.35, 1) or imgui.ImVec4(0.85, 0.35, 0.35, 1) imgui.TextColored(_ao_cnt_col, ' ' .. IC.star .. ' ' .. _cyr5f('найдено: ' .. _ao_box_cnt)) local _combo_lbl = (_sl.name ~= '') and _cyr5f(_sl.name) or _cyr5f('--- выбрать предмет ---') imgui.PushItemWidth(cw_cm * 0.50) if imgui.BeginCombo('##ao_cmb' .. _aoi, _combo_lbl) then for _, _ie in ipairs(fh_lv_inventory) do local _ao_nm_low = fh_lower(_ie.name) local _ao_is_shard = _ao_nm_low:find('оскол', 1, true) local _ao_is_box = (not _ao_is_shard) and ( _ao_nm_low:find('лар', 1, true) or _ao_nm_low:find('кейс', 1, true) or _ao_nm_low:find('сундук', 1, true) or _ao_nm_low:find('ящик', 1, true) or _ao_nm_low:find('тайник', 1, true) or _ao_nm_low:find('шкатулк', 1, true) or _ao_nm_low:find('box', 1, true) ) if _ao_is_box then local _ssel = (_sl.name == _ie.name) local _fsl = (_ie.stacks and _ie.stacks[1]) and _ie.stacks[1].slot or -1 local _ilbl = IC.arch .. ' ' .. _cyr5f(_ie.name .. ' x' .. (_ie.count or 0)) if imgui.Selectable(_ilbl .. '##s' .. _aoi .. 'x' .. tostring(_fsl), _ssel) then _sl.name = _ie.name _sl.slot_idx = _fsl _sl.item_id = nil if mh_arz_items_db then for _iid, _inm in pairs(mh_arz_items_db) do if _inm == _ie.name then _sl.item_id = _iid; break end end end _sl.done = 0 _sl.status = '' end if _ssel then imgui.SetItemDefaultFocus() end end end imgui.EndCombo() end imgui.PopItemWidth() imgui.SameLine() imgui.TextDisabled(_cyr5f('Макс:')) imgui.SameLine() imgui.PushItemWidth(52 * d) if imgui.InputInt('##ao_mx' .. _aoi, _G._ao_buf_max[_aoi], 0, 0) then local _mv = _G._ao_buf_max[_aoi][0] if _mv < 0 then _mv = 0 end _sl.max_count = _mv _G._ao_buf_max[_aoi][0] = _mv end imgui.PopItemWidth() imgui.SameLine() if imgui.Button(_cyr5f('Макс##ao_mxb' .. _aoi), imgui.ImVec2(44*d, 0)) then if _sl.name ~= '' then for _, _mxi in ipairs(fh_lv_inventory) do if _mxi.name == _sl.name then _sl.max_count = _mxi.count or 0 _G._ao_buf_max[_aoi][0] = _sl.max_count break end end end end imgui.SameLine() local _del_cb = imgui.new.bool(_sl.delete_on_sold or false) if imgui.Checkbox(_cyr5f('Уд.##ao_del' .. _aoi), _del_cb) then _sl.delete_on_sold = _del_cb[0] end if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Удалить слот после продажи всех')) end imgui.TextDisabled(_cyr5f('Интервал(с):')) imgui.SameLine() imgui.PushItemWidth(68 * d) if imgui.InputFloat('##ao_iv' .. _aoi, _G._ao_buf_int[_aoi], 0.0, 0.0, '%.1f') then local _fv = _G._ao_buf_int[_aoi][0] if _fv < 0.1 then _fv = 0.1 end if _fv > 300 then _fv = 300 end _sl.interval = _fv _G._ao_buf_int[_aoi][0] = _fv end imgui.PopItemWidth() local _cw_sl = imgui.GetWindowContentRegionWidth() imgui.SameLine(_cw_sl - 88 * d) if _sl.running then imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.50, 0.10, 0.10, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.70, 0.16, 0.16, 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, imgui.ImVec4(0.38, 0.06, 0.06, 1)) if imgui.Button(IC.circc .. ' ' .. _cyr5f('Стоп') .. '##ao_b' .. _aoi, imgui.ImVec2(85 * d, 0)) then _sl.running = false _sl.status = 'Стоп (' .. _sl.done .. ')' _G._ao_session_open = false mh_notify('[MH] {ff8800}Открытие слот ' .. _aoi .. ' остановлен. Открыто: ' .. _sl.done, 0xFFFFFF) end imgui.PopStyleColor(3) else if imgui.Button(IC.circ .. ' ' .. _cyr5f('Старт') .. '##ao_b' .. _aoi, imgui.ImVec2(85 * d, 0)) then if _sl.name == '' or _sl.slot_idx < 0 then mh_notify('[MH] {ff4444}Слот ' .. _aoi .. ': выберите предмет!', 0xFFFFFF) else _sl.running = true _sl.done = 0 _sl.status = 'Запуск...' mh_notify('[MH] {00cc00}Слот ' .. _aoi .. ': ' .. _sl.name .. ' | каждые ' .. _sl.interval .. 'с' .. (_sl.max_count > 0 and (' | макс ' .. _sl.max_count) or ''), 0xFFFFFF) local _ci = _aoi lua_thread.create(function() local _s = _G._ao_slots[_ci] while _s.running do if _s.max_count > 0 and _s.done >= _s.max_count then _s.running = false _G._ao_session_open = false _s.status = 'Готово! ' .. _s.done .. '/' .. _s.max_count mh_notify('[MH] {00cc00}Слот ' .. _ci .. ' готов: ' .. _s.done .. ' шт', 0xFFFFFF) if _s.delete_on_sold then _s.name = ''; _s.slot_idx = -1 _s.max_count = 0; _s.done = 0 _s.status = ''; _s.delete_on_sold = false if _G._ao_buf_max and _G._ao_buf_max[_ci] then _G._ao_buf_max[_ci][0] = 0 end mh_notify('[MH] {ffaa00}Слот ' .. _ci .. ' очищен (продан)', 0xFFFFFF) end break end local _real_slot = nil if _s.item_id and _G._ao_item_to_slot then _real_slot = _G._ao_item_to_slot[_s.item_id] end if not _real_slot then for _, _ie2 in ipairs(fh_lv_inventory) do if _ie2.name == _s.name then if _ie2.stacks and _ie2.stacks[1] then _s.slot_idx = _ie2.stacks[1].slot _real_slot = _s.slot_idx end break end end end if not _real_slot or _real_slot < 0 then _s.status = 'Нет в инвентаре!' wait(2000) else if not _G._ao_session_open then _yzr1t(52, -1, 0, '""') wait(300) _G._ao_session_open = true end _yzr1t(52, -1, 2, '{"slot":' .. _real_slot .. ',"type":1}') wait(150) _yzr1t(52, -1, 3, '{"action":1,"id":0,"slot":' .. _real_slot .. ',"type":1}') wait(400) _s.done = _s.done + 1 _s.status = 'Открыто: ' .. _s.done .. (_s.max_count > 0 and ('/' .. _s.max_count) or '') end local _ms = math.floor(_s.interval * 1000) local _el = 0 while _el < _ms and _s.running do wait(100); _el = _el + 100 local _rem = math.ceil((_ms - _el) / 1000) if _rem > 0 then _s.status = 'След. через ' .. _rem .. 'с | открыто: ' .. _s.done end end end end) end end end imgui.EndChild() end imgui.PopStyleColor() imgui.Spacing() end do local _ao_any_box = false for _, _tci in ipairs(fh_lv_inventory) do local _tcn = fh_lower(_tci.name) if not _tcn:find('оскол', 1, true) and ( _tcn:find('лар', 1, true) or _tcn:find('кейс', 1, true) or _tcn:find('сундук', 1, true) or _tcn:find('ящик', 1, true) or _tcn:find('тайник', 1, true) or _tcn:find('шкатулк', 1, true) or _tcn:find('box', 1, true) ) then _ao_any_box = true; break end end if #fh_lv_inventory == 0 then imgui.TextColored(imgui.ImVec4(1.0, 0.7, 0.2, 1), IC.circi .. ' ' .. _cyr5f(' Нажмите "Скан инвентаря" чтобы увидеть список ларцов')) elseif not _ao_any_box then imgui.TextColored(imgui.ImVec4(1.0, 0.45, 0.35, 1), IC.circi .. ' ' .. _cyr5f(' Ларцы не найдены в инвентаре')) else imgui.TextDisabled(_cyr5f(' Сначала выберите предмет, затем нажмите Старт')) end end imgui.Spacing() imgui.EndTabItem() end imgui.EndTabBar() end imgui.PopStyleColor(3) end imgui.EndChild() end end end function cm_isCentralMarket(x, y) return (x > 1090 and x < 1180 and y > -1550 and y < -1429) end function cm_drawCircle3d(x, y, z, radius, color) local step = 10 local sX_old, sY_old for angle = 0, 360, step do local lX = radius * math.cos(math.rad(angle)) + x local lY = radius * math.sin(math.rad(angle)) + y local _, sX, sY, sZ = convert3DCoordsToScreenEx(lX, lY, z) if sZ and sZ > 1 then if sX_old and sY_old then renderDrawLine(sX, sY, sX_old, sY_old, 1.5, color) end sX_old, sY_old = sX, sY end end end function cm_get_distance_to(posX, posY, posZ) local pX, pY, pZ = getCharCoordinates(PLAYER_PED) return math.sqrt((posX-pX)^2 + (posY-pY)^2) end function main() if not isSampLoaded() or not isSampfuncsLoaded() then return end collectgarbage('setpause', 105) collectgarbage('setstepmul', 250) fh_session_log_start = #fh_mkt_log + 1 while not isSampAvailable() do wait(0) end while not sampIsLocalPlayerSpawned() do wait(0) end mh_notify("[Market Helper] {ffffff}v" .. thisScript().version .. " | {FFAA00}/mrk | {aaaaaa}debug: /mrkdbg", message_color) _G._mh_loading = true lua_thread.create(function() wait(0) _lkz7q() _G._mh_loading = false _G._xp_db_path = _zdb1r('player_xp.json') _xp_load() wait(3000) _xp_pull_srv() wait(3000) _xp_recalc_from_log() do local _srv_wait = 0 while _mpf7d() == 0 and _srv_wait < 20 do wait(500); _srv_wait = _srv_wait + 1 end if _mpf7d() > 0 then _G.mh_boot_srv_idx = _mpf7d() end end _xp_push_self() wait(2000) _xp_pull_srv() lua_thread.create(function() while true do wait(300000) _xp_push_self() wait(2000) _xp_pull_srv() end end) end) lua_thread.create(function() wait(6000) _G._mh_prices_pull(true) end) lua_thread.create(function() wait(3000) local key = settings.premium and settings.premium.key or '' if key == '' then return end if settings.premium.activated and (settings.premium.nick or '') ~= '' then local ok_n, my_id = pcall(sampGetPlayerIdByCharHandle, PLAYER_PED) if ok_n and my_id then local ok_n2, my_nick = pcall(sampGetPlayerNickname, my_id) if ok_n2 and my_nick then local saved_nick = (settings.premium.nick or ''):lower():gsub('^%s+',''):gsub('%s+$','') local cur_nick = my_nick:lower():gsub('^%s+',''):gsub('%s+$','') if saved_nick ~= '' and cur_nick ~= '' and cur_nick ~= saved_nick then settings.premium.activated = false settings.premium.key = '' settings.premium.user = '' settings.premium.nick = '' settings.premium.expires = '' _mh_reset_prem() _wfn7p() mh_notify('[MH] {ff4444}Premium: ключ недействителен (не ваш ник).', 0xFFFFFF) return end end end end local last = settings.premium.last_check or 0 local exp_str = settings.premium.expires or '' if exp_str ~= '' then pcall(function() local y,m,d = exp_str:match('(%d+)[%-%.](%d+)[%-%.](%d+)') if y then local exp_ts = os.time({year=tonumber(y),month=tonumber(m),day=tonumber(d),hour=23,min=59,sec=59}) local days_left = math.ceil((exp_ts - os.time()) / 86400) if days_left < 0 then settings.premium.activated = false settings.premium.key = '' settings.premium.user = '' settings.premium.nick = '' settings.premium.expires = '' settings.premium.last_check = 0 _mh_reset_prem() _wfn7p() mh_notify('[MH] {ff4444}Premium истёк (' .. exp_str .. '). Деактивирован.', 0xFFFFFF) return elseif days_left <= 3 then local msg = days_left == 0 and '[MH] {ff4444}Premium истекает сегодня!' or '[MH] {ffaa00}Premium истекает через ' .. days_left .. ' дн. (' .. exp_str .. ')' mh_notify(msg, 0xFFFFFF) end end end) end if not settings.premium.activated then return end if (os.time() - last) < 86400 then return end _fpc2t(key, function(valid, user) if valid then if mh_debug_enabled then mh_notify('[MH] {aaaaff}Premium OK: ' .. (user or ''), 0xFFFFFF) end local exp = settings.premium.expires or '' if exp ~= '' then pcall(function() local y,m,d = exp:match('(%d+)[%-%.](%d+)[%-%.](%d+)') if y then local exp_ts = os.time({year=tonumber(y),month=tonumber(m),day=tonumber(d),hour=23,min=59,sec=59}) local days_left = math.ceil((exp_ts - os.time()) / 86400) if days_left <= 3 and days_left >= 0 then local msg = days_left == 0 and '[MH] {ff4444}Premium истекает сегодня!' or '[MH] {ffaa00}Premium истекает через ' .. days_left .. ' дн. (' .. exp .. ')' mh_notify(msg, 0xFFFFFF) end end end) end else settings.premium.activated = false settings.premium.key = '' settings.premium.user = '' settings.premium.nick = '' settings.premium.expires = '' settings.premium.last_check = 0 _mh_reset_prem() _wfn7p() mh_notify('[MH] {ff4444}Premium деактивирован: ключ недействителен.', 0xFFFFFF) end end) end) sampRegisterChatCommand("mrk", function() MainWindow[0] = not MainWindow[0] end) sampRegisterChatCommand("mrkdbg", function() mh_debug_enabled = not mh_debug_enabled if mh_debug_enabled then mh_notify("[Market Helper] {00cc00}Debug ON", message_color) else mh_notify("[Market Helper] {ff4444}Debug OFF", message_color) end end) sampRegisterChatCommand("mrkflog", function() mh_filelog_enabled = not mh_filelog_enabled local path = getWorkingDirectory():gsub('\\\\','/') .. '/MH_debug.log' if mh_filelog_enabled then local f = io.open(path, 'w'); if f then f:write('[' .. os.date('%H:%M:%S') .. '] === MH FILE LOG START ===\n'); f:close() end mh_notify("[Market Helper] {00cc00}File log ON -> MH_debug.log", message_color) else mh_notify("[Market Helper] {ff4444}File log OFF", message_color) end end) sampRegisterChatCommand("mrkfdbg", function() mh_pktdbg_enabled = not mh_pktdbg_enabled local path = getWorkingDirectory():gsub('\\\\','/') .. '/mrkfdbg.log' if mh_pktdbg_enabled then local f = io.open(path, 'w') if f then f:write('========================================\n') f:write(' MarketHelper Packet Debug Log\n') f:write(' Started: ' .. os.date('%Y-%m-%d %H:%M:%S') .. '\n') f:write(' Capture: PKT220 (iface/sub), onShowDialog, onSendPacket\n') f:write('========================================\n\n') f:write('LEGEND:\n') f:write(' RECV <<< = входящий пакет от сервера игры\n') f:write(' SEND >>> = исходящий пакет от клиента\n') f:write(' DLG = диалог (onShowDialog)\n') f:write(' SRV_ID = server_id в данных лавки\n') f:write(' OWN = owner (ник владельца лавки)\n') f:write('\n') f:close() end mh_notify('[MH] {00cc00}Packet debug ON -> mrkfdbg.log', 0xFFFFFF) mh_notify('[MH] {aaaaaa}Подойди к лавке/рынку чтобы захватить пакеты', 0xFFFFFF) else mh_notify('[MH] {ff4444}Packet debug OFF', 0xFFFFFF) mh_notify('[MH] {aaaaaa}Лог сохранён: ' .. path, 0xFFFFFF) end end) sampRegisterChatCommand("mrkpush", function() local count = 0 for _, s in pairs(fh_other_shops) do if s and s.owner and s.owner ~= '' then count = count + 1 end end if count == 0 then mh_notify('[MH Cloud] {ff6644}Нет лавок для отправки.', 0xFFFFFF) return end mh_notify('[MH Cloud] {aaaaff}Отправляю ' .. count .. ' лавок на сервер...', 0xFFFFFF) lua_thread.create(function() local sent = 0 local srv_id = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)() for _, s in pairs(fh_other_shops) do if s and s.owner and s.owner ~= '' then if not s.server_id then s.server_id = srv_id end local payload = { server_id = s.server_id or -1, owner = s.owner, shop_num = s.shop_num, sell_slots = s.sell_items or {}, buy_slots = s.buy_items or {}, } local ok_j, j = pcall(encodeJson, payload) if ok_j then local ok, code = _G._mh_sync_post(_vbr7n .. '/shops/push', j) if ok then sent = sent + 1 end end wait(150) end end wait(500) mh_notify('[MH Cloud] {00cc00}Готово! Отправлено: ' .. sent .. ' из ' .. count .. '. Нажми "Обновить" во вкладке Лавки.', 0xFFFFFF) end) end) while true do wait(0) if cm_radius_enabled then for IDTEXT = 0, 2048 do if sampIs3dTextDefined(IDTEXT) then local text3d, _, posX, posY, posZ = sampGet3dTextInfoById(IDTEXT) if text3d == "\xd3\xef\xf0\xe0\xe2\xeb\xe5\xed\xe8\xff \xf2\xee\xe2\xe0\xf0\xe0\xec\xe8." and not cm_isCentralMarket(posX, posY) then local pX, pY = getCharCoordinates(PLAYER_PED) local dist = math.sqrt((posX-pX)^2 + (posY-pY)^2) local col = dist > 5 and 0xFFFFFFFF or 0xFFFF2222 cm_drawCircle3d(posX, posY, posZ - 1.3, 5, col) end end end end if cm_render_enabled then local _px, _py, _pz = getCharCoordinates(PLAYER_PED) for id = 0, 2304 do if sampIs3dTextDefined(id) then local text3d, _, posX, posY, posZ = sampGet3dTextInfoById(id) if math.floor(posZ) == 17 and text3d == '' then local _dist = math.sqrt((_px-posX)^2 + (_py-posY)^2) if _dist <= 100 and isPointOnScreen(posX, posY, posZ, 100) then local pX2, pY2 = convert3DCoordsToScreen(_px, _py, _pz) local lX2, lY2 = convert3DCoordsToScreen(posX, posY, posZ) renderDrawLine(pX2, pY2, lX2, lY2, 1, 0xFF52FF4D) renderDrawPolygon(lX2, lY2, 10, 10, 10, 0, 0xFFFFFFFF) end end end end end if not mh_isActiveCommand then for idx, t in ipairs(settings.piar_templates or {}) do local _piar_iv = t.auto_interval or 300 if (t.auto_interval_max or 0) > _piar_iv then local _rng = math.max(0, t.auto_interval_max - _piar_iv) if not t._next_interval then t._next_interval = _piar_iv + (_rng > 0 and math.random(0, _rng) or 0) end _piar_iv = t._next_interval end if t.enable and t.auto and os.time() - (t.last_time or 0) >= _piar_iv then t._next_interval = nil _xjg7y(idx); break end end end end end function _mh_qpop_try_open(nm_hint, max_age) max_age = max_age or 5.0 local _age = _G._mh_qpop_pending_time and (os.clock() - _G._mh_qpop_pending_time) or 99 if _age > max_age then return false end local _nm = (nm_hint and nm_hint ~= '') and nm_hint or (_G._mh_qpop_pending_nm or '') if (_nm == '' or _nm:match('^ID:')) and _G._mh_qpop_pending_id then local _r = mh_arz_items_db and mh_arz_items_db[_G._mh_qpop_pending_id] if _r and _r ~= '' then _nm = _r end end local _open_nm = _nm if (_open_nm == '' or _open_nm:match('^ID:')) and _G._mh_qpop_pending_id then _open_nm = 'ID:' .. tostring(_G._mh_qpop_pending_id) end if _open_nm == '' then return false end local _nm_is_id = _open_nm:match('^ID:') _G.mh_qpop_item = _open_nm _G.mh_qpop_item_id = _G._mh_qpop_pending_id _G.mh_qpop_item_price = _G._mh_qpop_pending_price or 0 _G.mh_qpop_item_type = _G._mh_qpop_pending_type or 13 _G.mh_qpop_cache_nm = '' _G.mh_qpop_open = true _G._qpop_win_init = nil if not _nm_is_id then _G._mh_qpop_pending_id = nil _G._mh_qpop_pending_nm = nil _G._mh_qpop_pending_time = nil _G._mh_qpop_pending_price = nil _G._mh_qpop_pending_type = nil end return true end function sampev.onShowDialog(dialogId, style, title, button1, button2, text) if dialogId == 8248 and _mh_is_premium() and settings.trade_autoprice and settings.trade_autoprice.enabled then lua_thread.create(function() local _wait = 0 while not (_G._mh_trade_auto_offer and _G._mh_trade_auto_offer > 0 and _G._mh_trade_auto_offer_ts and (os.time() - _G._mh_trade_auto_offer_ts) < 60) and _wait < 30 do wait(100); _wait = _wait + 1 end if _G._mh_trade_auto_offer and _G._mh_trade_auto_offer > 0 and _G._mh_trade_auto_offer_ts and (os.time() - _G._mh_trade_auto_offer_ts) < 60 and sampIsDialogActive() and sampGetCurrentDialogId() == 8248 then local _offer_copy = _G._mh_trade_auto_offer _G._mh_trade_auto_offer = nil sampSendDialogResponse(8248, 1, 0, tostring(_offer_copy)) mh_notify('[MH] Авто-цена применена: ' .. _fmt_price_arz(_offer_copy), 0xAAFFAA) end end) end fh_last_dlg_title_raw = title or "" fh_last_dlg_title = title and title:gsub("{%x+}",""):match("^%s*(.-)%s*$") or "" fh_last_dlg_text = text or "" fh_last_dlg_id = dialogId or -1 local _t = (title or ''):gsub('{%x+}',''):sub(1,60) local _tx = (text or ''):gsub('{%x+}',''):sub(1,120):gsub('\n',' | ') _mh_flog('DLG id=' .. tostring(dialogId) .. ' style=' .. tostring(style) .. ' title=[' .. _t .. '] text=[' .. _tx .. ']') if dialogId == 28148 and title and title ~= '' then local _dp = title:gsub('{%x+}','') :match('Результат сделки с (.-)%s*$') or (_G._mh_trade_partner or '') _dp = (_dp:match('^%s*(.-)%s*$') or _dp) local _raw = (text or ''):gsub('{%x+}','') local _parts = {} local _pos = 1 while true do local _s, _e = _raw:find('\n', _pos, true) local _chunk if _s then _chunk = _raw:sub(_pos, _s - 1) _pos = _e + 1 else _chunk = _raw:sub(_pos) end _chunk = _chunk:gsub('[%c]', ''):match('^%s*(.-)%s*$') or '' if #_chunk > 0 then table.insert(_parts, _chunk) end if not _s then break end end _mh_flog('TRADE parts=' .. #_parts .. ' first=[' .. (_parts[1] or '') .. '] second=[' .. (_parts[2] or '') .. ']') local _parts2 = {} for _, pp in ipairs(_parts) do if pp ~= '' and pp ~= '-' and pp:find('%S') then table.insert(_parts2, pp) end end _parts = _parts2 local _sec=''; local _gi={}; local _gei={}; local _gm=0; local _gem=0 for _, pp in ipairs(_parts) do if pp:find('Перед', 1, true) then _sec='give' elseif pp:find('Получ', 1, true) then _sec='get' elseif pp:find('Комис', 1, true) then _sec='' elseif _sec=='give' or _sec=='get' then local mn = _parse_trade_sum(pp) local ism = (pp:find('КК') or pp:find('К%s*%d') or pp:find('%$') or pp:find(':K') or pp:find(':KK')) and mn>0 if ism then if _sec=='give' then _gm=mn else _gem=mn end else local nm,qt = pp:match('^(.-)%s*[xXС...](%d+)%s*$') if not nm then nm=pp; qt='1' end nm = nm:match('^%s*(.-)%s*$') or nm if nm~='' and nm~='-' then local e={name=nm,qty=tonumber(qt) or 1} if _sec=='give' then table.insert(_gi,e) else table.insert(_gei,e) end end end end end _mh_flog('TRADE dp=' .._dp ..' gi=' ..#_gi..' gei='..#_gei ..' gm=' ..tostring(_gm)..' gem='..tostring(_gem) ..' parts='..tostring(#_parts)) if _gm == 0 and _G._mh_trade_money_give and _G._mh_trade_money_give > 0 then _gm = _G._mh_trade_money_give end if _gem == 0 and _G._mh_trade_money_get and _G._mh_trade_money_get > 0 then _gem = _G._mh_trade_money_get end if _dp~='' and (#_gi>0 or #_gei>0 or _gm>0 or _gem>0) then local _dk2 = _dp .. '|' .. tostring(math.floor(os.time()/4)) if _G._mh_trade_last_key ~= _dk2 then _G._mh_trade_last_key = _dk2 table.insert(fh_trade_log,1,{dt=os.date('%d.%m %H:%M'),partner=_dp, give_items=_gi,get_items=_gei,give_money=_gm,get_money=_gem}) while #fh_trade_log>500 do table.remove(fh_trade_log) end do local _my_trd_nick = '' pcall(function() local _pid3 = select(2, sampGetPlayerIdByCharHandle(PLAYER_PED)) _my_trd_nick = (sampGetPlayerNickname(_pid3) or ''):lower() end) if _my_trd_nick == '' then pcall(function() _my_trd_nick = (sampGetCurrentPlayerName() or ''):lower() end) end if _my_trd_nick ~= '' then _xp_add_trade(_my_trd_nick, _gem, _gm) _xp_save() end end _ryb5t() _G._mh_trade_partner=nil _G._mh_trade_saved_ts = os.time() _mh_flog('TRADE SAVED (dlg): ' .._dp..' gi='..#_gi..' gm='..tostring(_gm)) mh_notify('[MH] Трейд с '.._dp..' сохранён', 0xAAFFAA) else _mh_flog('TRADE SKIP (dlg): already saved by pkt') end else _mh_flog('TRADE SKIP: dp=[' .._dp..'] empty='..tostring(_dp=='')) mh_notify('[MH DBG] trade skip dp=[' .._dp..'] gi='..#_gi..' gm='..tostring(_gm), 0xFF8800) end end if dialogId == 3040 and title and title ~= '' then local t_clean = title:gsub('{%x+}', ''):match('^%s*(.-)%s*$') or '' local n = t_clean:match('[\xe2\x84\x96#]%s*(%d+)') or t_clean:match('(%d+)%s*$') if n then local num = tonumber(n) if num and num >= 1 and num <= 99999 then fh_other_shop_pending_num = num mh_own_shop_num = num if fh_other_shop_cur then fh_other_shop_cur.shop_num = num end for k, s in pairs(fh_other_shops) do if s.owner and fh_other_shop_owner ~= '' and s.owner:lower() == fh_other_shop_owner:lower() then local old_num = tostring(s.shop_num or '') if old_num ~= tostring(num) then s.shop_num = num local new_key = s.owner .. '_' .. num fh_other_shops[new_key] = s fh_other_shops[k] = nil settings.other_shops = fh_other_shops _wfn7p() end break end end if mh_debug_enabled then mh_notify('[MH DLG3040] shopNum='..num..' title='..t_clean:sub(1,30), 0xAAFFAA) end end end end if dialogId == 3082 then local _raw = (text or ''):gsub('{%x+}', '') local _line1 = _raw:match('^%s*(.-)%s*[\n|]') or _raw:match('^%s*(.-)%s*$') or '' local _nm = _line1:match('^[^:]+:%s*(.+)$') or _line1 _nm = (_nm:match('^%s*(.-)%s*$') or ''):gsub('%(%a+%)%s*$', ''):match('^%s*(.-)%s*$') or '' if _nm ~= '' then local _nm_lo = _nm:lower() local _in_mkt = fh_mkt_prices and (fh_mkt_prices[_nm] ~= nil or fh_mkt_prices[_nm_lo] ~= nil) local _in_lv = _G._lv_shops_cache and (_G._lv_shops_cache[_nm_lo] ~= nil) if _in_mkt or _in_lv then _G.mh_qpop_item = _nm _G.mh_qpop_cache_nm = '' _G.mh_qpop_open = true else _G.mh_qpop_item = _nm _G.mh_qpop_cache_nm = '' _G.mh_qpop_open = true end end end if fh_other_shop_scanning then fh_other_dlg_signal = {id=dialogId, title=fh_last_dlg_title, title_raw=fh_last_dlg_title_raw, text=text or ""} local _tc = fh_last_dlg_title or '' local _is_slot = _tc:find('Продажа%s+товара') ~= nil or _tc:find('Продажа%s+предмета') ~= nil or _tc:find('Покупка') ~= nil or _tc:find('[Тт]овар') ~= nil or dialogId == 3082 if _is_slot then return false end fh_player_dlg_open = true end if fh_mkt_lv_scanning and dialogId == fh_mkt_lv_cur_dialog then fh_mkt_lv_done = fh_mkt_lv_done + 1 lua_thread.create(function() wait(80); sampSendDialogResponse(dialogId, 0, 0, '') end) return false end if dialogId == 3010 and cm_catch_enabled then lua_thread.create(function() wait(100) sampSendDialogResponse(dialogId, 1, 0, "") cm_catch_status = os.date("%H:%M") .. " \xeb\xe0\xe2\xea\xe0 \xef\xee\xe9\xec\xe0\xed\xe0!" mh_notify('[MH] {00cc00}\xc2\xfb \xef\xee\xe9\xec\xe0\xeb\xe8 \xeb\xe0\xe2\xea\xf3!', 0xFFFFFF) end) return false end if mh_debug_enabled and (dialogId == 26546 or dialogId == 25493) then local t = title and title:gsub("{%x+}",""):sub(1,30) or "" mh_notify("[FH DEBUG] Dialog ID: " .. dialogId .. " style: " .. style, 0xFF8888) mh_notify("[FH DEBUG] Title: " .. t, 0xFF8888) if text and text ~= "" then local first = text:gsub("{%x+}",""):match("^%s*(.-)%s*$") or "" mh_notify("[FH DEBUG] [1] " .. first:sub(1,60), 0xFF8888) local cnt = 0; for _ in text:gmatch('[^\n]+') do cnt=cnt+1 end mh_notify("[FH DEBUG] ... Всего строк: " .. cnt, 0xFF8888) end end if fh_lv_autosell_running and dialogId == 25493 and text then local found_name, found_idx = nil, nil local li = 0 for ln in text:gmatch("[^\n]+") do local nm = ln:gsub("{%x+}", ""):match("%[%d+%]%s+(.-)%s+%[") if nm then nm = nm:match("^%s*(.-)%s*$") or "" for _, p in ipairs(fh_lv_autosell_preset) do if p.name:lower() == nm:lower() and p.enabled ~= false and (p.price or 0) >= 10 then found_name = p.name; found_idx = li; break end end end if found_name then break end li = li + 1 end if found_name then mh_notify("[MH] 25493 -> выбираю: " .. found_name, 0xFFFFFF) lua_thread.create(function() wait(300); sampSendDialogResponse(dialogId, 1, found_idx, "") end) else lua_thread.create(function() wait(100); sampSendDialogResponse(dialogId, 0, 0, "") end) end return false end if dialogId == 26546 and text then local has_qty = text:find("запятую") ~= nil fh_mkt_shop_price_qty = has_qty _G._mh_dlg26546_text = text _G._mh_dlg26546_time = os.clock() if fh_mkt_shop_price_dlg == -2 then fh_mkt_shop_price_dlg = dialogId end end if fh_lv_inv_scanning then if mh_debug_enabled then mh_notify('[FH Дебаг] {aaaaaa}dlg='..tostring(dialogId)..' step='..tostring(fh_lv_inv_dialog_step)..' title='..tostring(title):sub(1,30), 0xFFFFFF) end if dialogId == 722 or dialogId == 600 or dialogId == 235 then fh_lv_inv_dialog_step = fh_lv_inv_dialog_step + 1 lua_thread.create(function() wait(150); sampSendDialogResponse(dialogId, 1, 0, '') end) return false end local _is_inv_dlg = dialogId == 25494 or dialogId == 25493 or (text and ( text:find('%[%d+%]') and text:find('%[%d+ шт%]') or text:find('\xc8\xed\xe2\xe5\xed\xf2\xe0\xf0\xfc:') or text:find('\xd1\xeb\xe5\xe4\xf3\xfe\xf9\xe0\xff \xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0') or text:find('\xcf\xf0\xe5\xe4\xfb\xe4\xf3\xf9\xe0\xff \xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0') )) if _is_inv_dlg then fh_parse_inventory_dialog(text or '') local next_idx = nil if text then local iline = 0 for ln in text:gmatch('[^\n]+') do iline = iline + 1 if iline > 1 and ln:find('\xd1\xeb\xe5\xe4\xf3\xfe\xf9\xe0\xff \xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0', 1, true) then next_idx = iline - 2 break end end end if next_idx then mh_notify('[FH Авто] {aaaaaa}Стр. ' .. #fh_lv_inventory .. ' позиций, листаю...', 0xFFFFFF) lua_thread.create(function() wait(150); sampSendDialogResponse(dialogId, 1, next_idx, '') end) else fh_lv_inv_scanning = false fh_lv_inv_dialog_step = 0 mh_notify('[FH Авто] {00cc00}Инвентарь: ' .. #fh_lv_inventory .. ' позиций', 0xFFFFFF) lua_thread.create(function() wait(80) sampSendDialogResponse(dialogId, 0, 0, '') wait(150) _yzr1t(52, -1, 4, '""') end) end return false end end if fh_mkt_cp_scanning and title and text then local ct2 = title:gsub('{%x+}',''):match('^%s*(.-)%s*$') or '' local is_our = (dialogId == 15073) or ct2:find('\xf0\xe5\xe4\xed\xff\xff') ~= nil if not is_our then fh_mkt_cp_prev_text = nil lua_thread.create(function() wait(150) sampSendDialogResponse(dialogId, 0, 0, '') wait(300) end) return false end end if text ~= nil and _mh_piar_vr_active then if string.find(text, "Ваше сообщение является рекламой?") then sampSendDialogResponse(dialogId, 1, "", "") return false end end if title and settings.general.auto_ad_confirm then local ct_ad = title:gsub('{%x+}',''):match('^%s*(.-)%s*$') or '' if ct_ad:find('Подача') and not ct_ad:find('Подтв') then lua_thread.create(function() wait(400) sampSendDialogResponse(dialogId, 1, 0, text and text:match('^%s*(.-)%s*$') or '') end) return false end if ct_ad:find('радио') or ct_ad:find('Радио') then lua_thread.create(function() wait(400) sampSendDialogResponse(dialogId, 1, settings.general.auto_ad_station_idx or 2, '') end) return false end if ct_ad:find('тип') or ct_ad:find('Тип') then lua_thread.create(function() wait(400) sampSendDialogResponse(dialogId, 1, settings.general.auto_ad_type or 0, '') end) return false end if ct_ad:find('Подтв') then lua_thread.create(function() wait(400) sampSendDialogResponse(dialogId, 1, -1, '') end) return false end end if settings.general.auto_storage_collect then local ct_st = title and title:gsub('{%x+}',''):match('^%s*(.-)%s*$') or '' if text and text:find('Основное хранилище') then local pick_st, idx_st = nil, 0 for line_st in text:gmatch('[^\n]+') do local cl_st = line_st:gsub('{%x+}',''):match('^%s*(.-)%s*$') or '' if cl_st:find('Основное хранилище') then pick_st = idx_st; break end idx_st = idx_st + 1 end fh_storage_running = true lua_thread.create(function() wait(300) sampSendDialogResponse(dialogId, 1, pick_st or 0, '') end) return false end if ct_st == 'Хранилище предметов' then local total_st = 0 if text then for _ in text:gmatch('[^\n]+') do total_st = total_st + 1 end end if total_st > 0 then fh_storage_running = true sampSendDialogResponse(dialogId, 1, 0, '') return false else fh_storage_running = false mh_notify('[MH] {aaaaaa}Хранилище пусто!', 0xFFFFFF) sampSendDialogResponse(dialogId, 0, 0, '') return false end end if ct_st == 'Хранилище' and (style == 4 or style == 5) then local pick_all_st, pick_one_st = nil, nil local idx_st2 = 0 if text then for line_st in text:gmatch('[^\n]+') do local cl_st = line_st:gsub('{%x+}',''):gsub('%[%d+%]%s*',''):match('^%s*(.-)%s*$') or '' if cl_st:find('Забрать все') then pick_all_st = idx_st2 elseif cl_st:find('Забрать') then if pick_one_st == nil then pick_one_st = idx_st2 end end idx_st2 = idx_st2 + 1 end end local pick_st2 = pick_all_st or pick_one_st if pick_st2 ~= nil then lua_thread.create(function() wait(200) sampSendDialogResponse(dialogId, 1, pick_st2, '') end) else lua_thread.create(function() wait(200) sampSendDialogResponse(dialogId, 0, 0, '') end) end return false end if ct_st:find('Хранилище') and style == 0 and text then local cl_st = text:gsub('{%x+}',''):lower() if cl_st:find('забрать') or cl_st:find('забира') then lua_thread.create(function() wait(200) sampSendDialogResponse(dialogId, 1, 0, '') end) return false end end end local ct_mkt = title and title:gsub('{%x+}',''):match('^%s*(.-)%s*$') or '' local is_mkt_list = (dialogId == 15073) or (dialogId ~= 15376 and ct_mkt:find('Средняя цена') ~= nil) if is_mkt_list and text then local scan_label = 'Проанализировать все цены [FH]' if fh_mkt_cp_scanning then if fh_mkt_cp_prev_text == text then local tot = 0; for _ in pairs(fh_mkt_prices) do tot = tot + 1 end mh_notify('[FH Market] {00cc00}Анализ завершён! Товаров: ' .. tot, 0xFFFFFF) printStyledString('~w~FH Market: ~g~' .. tot .. ' ~w~items OK', 2500, 6) fh_mkt_cp_prev_text = nil; fh_mkt_cp_scanning = false _ryb5t() lua_thread.create(function() wait(100); sampSendDialogResponse(dialogId, 0, 0, '') end) return false end _hbr6z(text) local tot2 = 0; for _ in pairs(fh_mkt_prices) do tot2 = tot2 + 1 end fh_mkt_cp_page = (fh_mkt_cp_page or 0) + 1 printStyledString('~w~FH: ~g~' .. tot2 .. ' ~w~items | p.~r~' .. fh_mkt_cp_page, 1800, 6) fh_mkt_cp_prev_text = text local next_idx = fh_find_listitem(text, 'Следующая страница') if next_idx then lua_thread.create(function() wait(100); sampSendDialogResponse(dialogId, 1, next_idx, 0) end) else local tot3 = 0; for _ in pairs(fh_mkt_prices) do tot3 = tot3 + 1 end mh_notify('[FH Market] {00cc00}Скан завершён. Товаров: ' .. tot3, 0xFFFFFF) fh_mkt_cp_scanning = false; _ryb5t() lua_thread.create(function() wait(100); sampSendDialogResponse(dialogId, 0, 0, 0) end) end return false else local already = false for ln2 in text:gmatch('[^\n]+') do if ln2:find(scan_label, 1, true) then already = true; break end end if not already then local deep_label = 'Углублённый скан [FH]' local new_text = string.gsub(text, '(\xcf\xee\xe8\xf1\xea \xef\xee \xed\xe0\xe7\xe2\xe0\xed\xe8\xfe\t%s)\n', '%1\n{00FF00}' .. scan_label .. '\t \n{FFAA00}' .. deep_label .. '\t \n', 1) if new_text == text then local fl = text:match('^([^\n]*)') new_text = fl .. '\n{00FF00}' .. scan_label .. '\t \n{FFAA00}' .. deep_label .. '\t \n' .. text:sub(#fl + 1) end fh_mkt_cp_go_idx = fh_find_listitem(new_text, scan_label) fh_mkt_cp_deep_go_idx = fh_find_listitem(new_text, deep_label) return { dialogId, style, title, button1, button2, new_text } end end end local is_auto_dlg = (dialogId == 15376) if not is_auto_dlg and title then local ct_a = title:gsub('{%x+}',''):match('^%s*(.-)%s*$') or '' is_auto_dlg = ct_a:find('Средняя цена автомобилей') ~= nil end if is_auto_dlg and text then local auto_scan_label = 'Сканировать все авто [MH]' if fh_mkt_auto_scanning then if fh_mkt_auto_prev_text == text then local tot = 0; for _ in pairs(fh_mkt_auto) do tot = tot + 1 end mh_notify('[MH Auto] {00cc00}Скан завершён! Авто: ' .. tot, 0xFFFFFF) printStyledString('~w~MH Auto: ~g~' .. tot .. ' ~w~OK', 2500, 6) fh_mkt_auto_prev_text = nil; fh_mkt_auto_scanning = false _ryb5t() lua_thread.create(function() wait(100); sampSendDialogResponse(dialogId, 0, 0, '') end) return false end _gyc9t(text) local atot = 0; for _ in pairs(fh_mkt_auto) do atot = atot + 1 end fh_mkt_auto_page = (fh_mkt_auto_page or 0) + 1 printStyledString('~w~MH Auto: ~g~' .. atot .. ' ~w~| p.~r~' .. fh_mkt_auto_page, 1800, 6) fh_mkt_auto_prev_text = text local nxt_a = fh_find_listitem(text, 'Следующая страница') if nxt_a then lua_thread.create(function() wait(100); sampSendDialogResponse(dialogId, 1, nxt_a, 0) end) else local atot3 = 0; for _ in pairs(fh_mkt_auto) do atot3 = atot3 + 1 end mh_notify('[MH Auto] {00cc00}Скан завершён. Авто: ' .. atot3, 0xFFFFFF) fh_mkt_auto_scanning = false; _ryb5t() lua_thread.create(function() wait(100); sampSendDialogResponse(dialogId, 0, 0, 0) end) end return false else local auto_deep_label = 'Углублённый скан авто [MH]' local already_a = false for ln_a in text:gmatch('[^\n]+') do if ln_a:find(auto_scan_label, 1, true) then already_a = true; break end end if not already_a then local new_text_a = string.gsub(text, '(\xcf\xee\xe8\xf1\xea \xef\xee \xed\xe0\xe7\xe2\xe0\xed\xe8\xfe\t[^\n]*)\n', '%1\n{00FF00}' .. auto_scan_label .. '\t \n{FFAA00}' .. auto_deep_label .. '\t \n', 1) if new_text_a == text then local fl_a = text:match('^([^\n]*)') new_text_a = fl_a .. '\n{00FF00}' .. auto_scan_label .. '\t \n{FFAA00}' .. auto_deep_label .. '\t \n' .. text:sub(#fl_a+1) end fh_mkt_auto_go_idx = fh_find_listitem(new_text_a, auto_scan_label) fh_mkt_auto_deep_go_idx = fh_find_listitem(new_text_a, auto_deep_label) return { dialogId, style, title, button1, button2, new_text_a } end end end end function sampev.onShowTextDraw(td_id, td_data) if not td_data or not td_data.position then return end local px = td_data.position.x or 0 local py = td_data.position.y or 0 local py_s = tostring(py) if td_data.text then local n = tostring(td_data.text):match('#(%d+)') if n then local num = tonumber(n) if num and num >= 1 and num <= 9999 then fh_other_shop_pending_num = num if mh_debug_enabled then mh_notify('[MH TD#] id='..td_id..' num='..num..' txt='..tostring(td_data.text):sub(1,30), 0xAAFFAA) end end end end if td_data.text == 'ON_SALE' then fh_mkt_lavka_ids = {} fh_mkt_lavka_slot_w = nil fh_mkt_lavka_slot_h = nil fh_mkt_lavka_page_id = -1 fh_mkt_lavka_page_ready = false end if td_data.text == 'LD_BEAT:chit' and px > 320 and py > 240 then if fh_mkt_lavka_page_id < 0 then fh_mkt_lavka_page_id = td_id if mh_debug_enabled then mh_notify('[MH] {aaaaaa}TD страниц: ' .. td_id, 0xFFFFFF) end end fh_mkt_lavka_page_ready = true fh_mkt_shop_ui_open = true end if px == 325 and (py_s:find('164%.') or py_s:find('169%.') or py_s:find('165%.') or py_s:find('168%.')) then local exists = false for _, v in ipairs(fh_mkt_lavka_ids) do if v == td_id then exists = true; break end end if not exists then table.insert(fh_mkt_lavka_ids, td_id) if not fh_mkt_lavka_slot_w then fh_mkt_lavka_slot_w = td_data.lineWidth fh_mkt_lavka_slot_h = td_data.lineHeight end end end if fh_mkt_shop_ui_open then local is_service = (td_data.text == 'ON_SALE' or td_data.text == 'LD_BEAT:chit' or td_data.text == 'LD_SPAC:white') if not is_service then local exists2 = false for _, v in ipairs(fh_mkt_shop_inv_tds) do if v == td_id then exists2 = true; break end end if not exists2 then table.insert(fh_mkt_shop_inv_tds, td_id) if #fh_mkt_shop_inv_tds <= 35 then local txt_s = tostring(td_data.text or ''):sub(1,20) if mh_debug_enabled then mh_notify('[TD#'..#fh_mkt_shop_inv_tds..'] id='..td_id..' x='..math.floor(px)..' y='..math.floor(py)..' txt='..txt_s, 0xFFFFFF) end end end end end if fh_other_shop_scanning and td_data.text then local p = _bky4d(td_data.text) if p then fh_other_shop_price_tds[td_id] = {price=p, x=px, y=py} if mh_debug_enabled then mh_notify('[MH TD$] id=' .. td_id .. ' $' .. p .. ' x=' .. math.floor(px) .. ' y=' .. math.floor(py), 0x88ff88) end end end fh_mkt_lavka_all_tds[td_id] = td_data end function sampev.onTextDrawHide(td_id) for _,v in ipairs(fh_mkt_lavka_ids) do if v == td_id then fh_mkt_lavka_ids={} fh_mkt_lavka_sep={} fh_mkt_lavka_page_id=-1 break end end if td_id == fh_mkt_lavka_page_id then fh_mkt_shop_ui_open = false fh_mkt_shop_inv_tds = {} fh_mkt_lavka_page_id = -1 if fh_other_shop_scanning and fh_other_shop_cur and not _G._mh_qbh9f_scheduled then _qbh9f() fh_other_shop_scanning = false fh_other_shop_price_tds = {} end end local hid_td_data = fh_mkt_lavka_all_tds[td_id] if hid_td_data and hid_td_data.text == 'ON_SALE' then fh_mkt_lavka_all_tds[td_id] = nil if fh_other_shop_scanning and fh_other_shop_cur and not _G._mh_qbh9f_scheduled then _qbh9f() fh_other_shop_scanning = false fh_other_shop_price_tds = {} end end if td_id == fh_mkt_put_td_id then fh_mkt_put_td_id = -1 end end mh_last_3dtext_num = nil mh_last_3dtext_time = 0 function sampev.onCreate3DText(id, color, position, distance, testLOS, attachedPlayerId, attachedVehicleId, text) if not text or text == '' then return end local n = text:match('#(%d+)') if not n then return end local num = tonumber(n) if not num or num < 1 or num > 9999 then return end local pX, pY = getCharCoordinates(PLAYER_PED) local dx = (position and position.x or 0) - pX local dy = (position and position.y or 0) - pY local dist = math.sqrt(dx*dx + dy*dy) if dist > 15.0 then return end mh_last_3dtext_num = num mh_last_3dtext_time = os.clock() fh_other_shop_pending_num = num if mh_debug_enabled then mh_notify('[MH 3DT] #'..num..' dist='..string.format('%.1f', dist)..' id='..id, 0xAAFFAA) end end function sampev.onSendDialogResponse(dialogId, button, listItem, inputText) if fh_other_shop_scanning then _dlg_send_done = true end fh_player_dlg_open = false if dialogId == 26547 or dialogId == 3082 then _G.mh_qpop_open = false end if fh_other_shop_scanning and fh_other_shop_cur and button == 0 then local title_now = fh_last_dlg_title or '' local is_shop_dlg = title_now:find('Торговая лавка') ~= nil or title_now:find('Лавка') ~= nil or title_now:find('Покупка') ~= nil or title_now:find('Продажа') ~= nil if is_shop_dlg then lua_thread.create(function() wait(300) if fh_other_shop_scanning and fh_other_shop_cur then _qbh9f() fh_other_shop_scanning = false end end) end end local is_mkt = (dialogId == 15073) if not is_mkt then is_mkt = fh_last_dlg_title:find('Средняя цена товаров при продаже') ~= nil end if is_mkt then end if is_mkt and fh_mkt_cp_deep_scanning then return false end if is_mkt and not fh_mkt_cp_deep_scanning and fh_mkt_cp_deep_go_idx ~= nil then if button == 1 and listItem == fh_mkt_cp_deep_go_idx then fh_mkt_cp_deep_go_idx = nil fh_mkt_cp_deep_scanning = true fh_mkt_cp_deep_done = 0 mh_notify('[FH Market] {ffaa00}Углублённый скан запущен!', 0xFFFFFF) local _was_main_open_outer = MainWindow[0] MainWindow[0] = false local txt0 = sampGetDialogText() or '' local dlg0 = dialogId lua_thread.create(function() local rtt_ms = 1200; local rtt_n = 0 local function upd_rtt(ms) rtt_n=rtt_n+1; rtt_ms=math.floor((rtt_ms*math.min(rtt_n-1,9)+ms)/math.min(rtt_n,10)) end local function srv_to() return math.max(3000, math.min(rtt_ms*5+1500, 20000)) end local function wait_for_list(old_txt) local timeout_ms = srv_to(); local t0 = os.clock(); local t = 0 while t < timeout_ms do wait(16); t = t + 16 if sampIsDialogActive() and sampGetCurrentDialogId() == 15073 then local ntxt = sampGetDialogText() or '' if not old_txt or ntxt ~= old_txt then upd_rtt(math.floor((os.clock()-t0)*1000)) return sampGetCurrentDialogId(), ntxt end end end return nil, nil end local function wait_for_detail() local timeout_ms = srv_to(); local t0 = os.clock(); local t = 0 while t < timeout_ms do wait(16); t = t + 16 if sampIsDialogActive() then local tt = fh_last_dlg_title or '' if tt:find('Продажа товара') then upd_rtt(math.floor((os.clock()-t0)*1000)) return sampGetCurrentDialogId(), sampGetDialogText() or '', tt end end end return nil, nil, nil end local function wait_back_to_list() local timeout_ms = srv_to(); local t = 0 while t < timeout_ms do wait(16); t = t + 16 if sampIsDialogActive() and sampGetCurrentDialogId() == 15073 then return sampGetCurrentDialogId(), sampGetDialogText() or '' end end return nil, nil end local cur_dlg = dlg0 local cur_txt = txt0 local _was_main_open = _was_main_open_outer local idx_offset = 2 while fh_mkt_cp_deep_scanning do local page_items = _G._nqh8s(cur_txt, 5) if #page_items == 0 then goto continue_deep end for _, item in ipairs(page_items) do if not fh_mkt_cp_deep_scanning then break end sampSendDialogResponse(cur_dlg, 1, item.idx - idx_offset, 0) local det_dlg, det_txt, det_title = wait_for_detail() if det_dlg then local detail = MH_util.jfw5v(det_txt, det_title) if detail and detail.name ~= '' and #detail.history > 0 then fh_mkt_save_cp_detail(detail.name, detail.history) lua_thread.create(function() wait(200); _G._mh_upload_deals() end) end fh_mkt_cp_deep_done = fh_mkt_cp_deep_done + 1 printStyledString('~w~FH deep: ~y~'..fh_mkt_cp_deep_done, 1000, 6) sampSendDialogResponse(det_dlg, 1, 0, '') else fh_mkt_cp_deep_done = fh_mkt_cp_deep_done + 1 if sampIsDialogActive() then local cid2 = sampGetCurrentDialogId() if cid2 ~= 15073 then sampSendDialogResponse(cid2, 1, 0, '') end end end local nld, ntxt = wait_back_to_list() if nld then cur_dlg = nld; cur_txt = ntxt end end if not fh_mkt_cp_deep_scanning then break end ::continue_deep:: local cur_txt2 = sampIsDialogActive() and (sampGetDialogText() or '') or cur_txt local next_i = fh_find_listitem(cur_txt2, 'Следующая страница') if not next_i then break end local cid = sampIsDialogActive() and sampGetCurrentDialogId() or cur_dlg local old_page_txt = sampIsDialogActive() and sampGetDialogText() or cur_txt local nld2, ntxt2 = nil, nil for _retry = 1, 3 do sampSendDialogResponse(cid, 1, next_i - idx_offset, 0) local _page_to_save = rtt_ms rtt_ms = math.floor(rtt_ms * 2) nld2, ntxt2 = wait_for_list(old_page_txt) rtt_ms = _page_to_save if nld2 then break end mh_notify('[FH Market] {ffaa00}Попытка перехода страницы: ' .. _retry .. '/3', 0xFFFFFF) wait(1000) end if nld2 then wait(80) cur_dlg = nld2; cur_txt = ntxt2 else break end end fh_mkt_cp_deep_scanning = false _ryb5t() if sampIsDialogActive() then sampSendDialogResponse(sampGetCurrentDialogId(), 0, 0, '') end MainWindow[0] = _was_main_open mh_notify('[FH Market] {00cc00}Скан завершён! Товаров: '..fh_mkt_cp_deep_done, 0xFFFFFF) lua_thread.create(function() wait(1000); _G._mh_prices_push() end) printStyledString('~w~FH DONE: ~g~'..fh_mkt_cp_deep_done, 3000, 6) end) return false end if button == 1 and listItem > fh_mkt_cp_deep_go_idx then fh_mkt_cp_deep_go_idx = nil return { dialogId, button, listItem - 2, inputText } end fh_mkt_cp_deep_go_idx = nil end if is_mkt and not fh_mkt_cp_scanning and fh_mkt_cp_go_idx ~= nil then if button == 1 and listItem == fh_mkt_cp_go_idx then local txt = sampGetDialogText() or '' local nxt_raw = fh_mkt_cp_prev_text or txt local nxt = fh_find_listitem(nxt_raw, 'Следующая страница') if nxt == nil then mh_notify('[FH Market] {ff4444}Ошибка: не найдена след. страница!', 0xFFFFFF) fh_mkt_cp_go_idx = nil return { dialogId, 0, listItem, inputText } end fh_mkt_cp_scanning = true fh_mkt_cp_prev_text = txt mh_notify('[FH Market] {ffaa00}Запущен анализ цен. Не открывайте другие диалоги!', 0xFFFFFF) _hbr6z(txt) local tot = 0; for _ in pairs(fh_mkt_prices) do tot = tot + 1 end printStyledString('~w~FH scan p.1: ~r~' .. tot, 2000, 6) local fixed = nxt - 2 fh_mkt_cp_go_idx = nil return { dialogId, 1, fixed, inputText } end local fixed2 = listItem - 2 fh_mkt_cp_go_idx = nil return { dialogId, button, fixed2, inputText } end local is_auto_resp = (dialogId == 15376) if is_auto_resp and fh_mkt_auto_deep_scanning then return false end if is_auto_resp and not fh_mkt_auto_deep_scanning and fh_mkt_auto_deep_go_idx ~= nil then if button == 1 and listItem == fh_mkt_auto_deep_go_idx then fh_mkt_auto_deep_go_idx = nil fh_mkt_auto_deep_scanning = true fh_mkt_auto_deep_done = 0 mh_notify('[MH Auto] {ffaa00}Углублённый скан авторынка запущен!', 0xFFFFFF) local txt0 = sampGetDialogText() or '' local dlg0 = dialogId lua_thread.create(function() local rtt_ms = 500; local rtt_n = 0 local function upd_rtt(ms) rtt_n=rtt_n+1; rtt_ms=math.floor((rtt_ms*math.min(rtt_n-1,9)+ms)/math.min(rtt_n,10)) end local function srv_to() return math.max(800, math.min(rtt_ms*4+500, 15000)) end local function wait_for_auto_list(old_txt) local timeout_ms = srv_to(); local t0 = os.clock(); local t = 0 while t < timeout_ms do wait(16); t = t + 16 if sampIsDialogActive() and sampGetCurrentDialogId() == 15376 then local ntxt = sampGetDialogText() or '' if not old_txt or ntxt ~= old_txt then upd_rtt(math.floor((os.clock()-t0)*1000)) return sampGetCurrentDialogId(), ntxt end end end return nil, nil end local function wait_for_auto_detail() local timeout_ms = srv_to(); local t0 = os.clock(); local t = 0 while t < timeout_ms do wait(16); t = t + 16 if sampIsDialogActive() then if sampGetCurrentDialogId() ~= 15376 then upd_rtt(math.floor((os.clock()-t0)*1000)) return sampGetCurrentDialogId(), sampGetDialogText() or '', fh_last_dlg_title or '' end end end return nil, nil, nil end local cur_dlg = dlg0; local cur_txt = txt0 while fh_mkt_auto_deep_scanning do local page_items = fh_mkt_parse_auto_list(cur_txt) if #page_items == 0 then goto continue_auto_deep end for _, item in ipairs(page_items) do if not fh_mkt_auto_deep_scanning then break end sampSendDialogResponse(cur_dlg, 1, item.idx - 2, 0) local det_dlg, det_txt, det_title = wait_for_auto_detail() if det_dlg and det_txt then local detail = fh_mkt_parse_auto_detail(det_txt, det_title) if detail and detail.name ~= '' and #detail.history > 0 then fh_mkt_save_auto_detail(detail.name, detail.history) lua_thread.create(function() wait(200); _G._mh_upload_deals() end) end fh_mkt_auto_deep_done = fh_mkt_auto_deep_done + 1 printStyledString('~w~MH Auto deep: ~y~'..fh_mkt_auto_deep_done, 1000, 6) sampSendDialogResponse(det_dlg, 1, 0, '') else fh_mkt_auto_deep_done = fh_mkt_auto_deep_done + 1 if sampIsDialogActive() and sampGetCurrentDialogId() ~= 15376 then sampSendDialogResponse(sampGetCurrentDialogId(), 1, 0, '') end end local nld, ntxt = wait_for_auto_list(nil) if nld then cur_dlg = nld; cur_txt = ntxt end end if not fh_mkt_auto_deep_scanning then break end ::continue_auto_deep:: local cur_txt2 = sampIsDialogActive() and (sampGetDialogText() or '') or cur_txt local next_i = fh_find_listitem(cur_txt2, 'Следующая страница') if not next_i then break end local cid = sampIsDialogActive() and sampGetCurrentDialogId() or cur_dlg local old_txt2 = sampIsDialogActive() and sampGetDialogText() or cur_txt sampSendDialogResponse(cid, 1, next_i - 2, 0) local nld2, ntxt2 = wait_for_auto_list(old_txt2) if nld2 then cur_dlg = nld2; cur_txt = ntxt2 else break end end fh_mkt_auto_deep_scanning = false _ryb5t() lua_thread.create(function() wait(1000); _G._mh_prices_push() end) if sampIsDialogActive() then sampSendDialogResponse(sampGetCurrentDialogId(), 0, 0, '') end mh_notify('[MH Auto] {00cc00}Углублённый скан завершён! Авто: '..fh_mkt_auto_deep_done, 0xFFFFFF) printStyledString('~w~MH Auto DONE: ~g~'..fh_mkt_auto_deep_done, 3000, 6) end) return false end if button == 1 and fh_mkt_auto_deep_go_idx and listItem > fh_mkt_auto_deep_go_idx then fh_mkt_auto_deep_go_idx = nil return { dialogId, button, listItem - 2, inputText } end fh_mkt_auto_deep_go_idx = nil end if is_auto_resp and not fh_mkt_auto_scanning and fh_mkt_auto_go_idx ~= nil then if button == 1 and listItem == fh_mkt_auto_go_idx then local txt = sampGetDialogText() or '' local nxt = fh_find_listitem(txt, 'Следующая страница') if nxt == nil then mh_notify('[MH Auto] {ff4444}Ошибка: нет след. страницы!', 0xFFFFFF) fh_mkt_auto_go_idx = nil return { dialogId, 0, listItem, inputText } end fh_mkt_auto_scanning = true fh_mkt_auto_prev_text = txt fh_mkt_auto_page = 1 mh_notify('[MH Auto] {ffaa00}Скан авторынка запущен. Не открывайте другие диалоги!', 0xFFFFFF) _gyc9t(txt) local tot = 0; for _ in pairs(fh_mkt_auto) do tot = tot + 1 end printStyledString('~w~MH Auto p.1: ~r~' .. tot, 2000, 6) local fixed_a = nxt - 2 fh_mkt_auto_go_idx = nil return { dialogId, 1, fixed_a, inputText } end fh_mkt_auto_go_idx = nil return { dialogId, button, listItem - 2, inputText } end end function sampev.onSetObjectMaterialText(ev, data) if not (cm_catch_enabled or cm_render_enabled) then return end local Object = sampGetObjectHandleBySampId(ev) if not (doesObjectExist(Object) and getObjectModel(Object) == 18663) then return end if not (data and data.text and data.text:find("\xd1\xe2\xee\xe1\xee\xe4\xed\xe0!")) then return end local posX, posY, posZ = getObjectCoordinates(Object) local dist = cm_get_distance_to(posX, posY, posZ) if dist <= 2.0 and cm_catch_enabled then mh_notify('[MH] {ffaa00}\xcd\xe0\xf8\xb8\xeb \xeb\xe0\xe2\xea\xf3, \xe6\xec\xf3 \xe0\xeb\xfc\xf2...', 0xFFFFFF) lua_thread.create(function() setGameKeyState(19, 255) wait(100) setGameKeyState(19, 0) _yzr1t(8, 7, -1, {}) end) end end function sampev.onServerMessage(color, text) if not text then return end if fh_lv_autosell_running or fh_lv_autobuy_running then _mh_flog('SRV_MSG color=' .. string.format('%06X', color) .. ' text=' .. text:gsub('{%x+}',''):sub(1,100)) end if _G._mh_lavka_check_pending then local _raw = text:gsub('{%x+}', '') if _raw:lower():find('уже') and _raw:lower():find('лавк') then _G._mh_lavka_check_result = 'ok' elseif _raw:lower():find('лавк') and ( _raw:lower():find('удал') or _raw:lower():find('снят') or _raw:lower():find('нет') or _raw:lower():find('не уст')) then _G._mh_lavka_check_result = 'gone' elseif _raw:lower():find('нет лавк') or _raw:lower():find('не.*лавк') then _G._mh_lavka_check_result = 'gone' end end if _G._mh_call_pending_nick and _G._mh_call_pending_nick ~= '' then local raw = text:gsub('{' .. '%x+}', '') local nick_part, num_part = raw:match('^([%a_][%w_]+%[%d+%])%s*:%s*(%d+)%s*$') if num_part and #num_part >= 5 then local nick_in_msg = (nick_part or ''):match('^([%a_][%w_]+)') if nick_in_msg and nick_in_msg:lower() == _G._mh_call_pending_nick then _G._mh_call_pending_nick = nil lua_thread.create(function() wait(400) sampSendChat('/call ' .. num_part) end) return false end end end local _hex = string.format('%06X', bit.band(color, 0xFFFFFF)) local _colored = '{' .. _hex .. '}' .. text _vsm8w(_colored) if settings.chat_log_enabled ~= false then table.insert(fh_session_chat, 1, _colored) _G._lvn7s() end local trade = _wsn4d(text) if trade then _ngw1x(trade.item, trade.qty, trade.price, trade.op, trade.partner, trade.is_vc, trade.own) if trade.price and trade.price > 0 then for _, tl in ipairs(fh_lv_trade_log) do if tl.item and tl.item:lower() == trade.item:lower() and tl.op == (trade.op == 'SELL' and 'sell' or 'buy') and (tl.price == 0 or tl.price ~= trade.price) then tl.price = trade.price break end end end lua_thread.create(function() wait(300); _ryb5t() end) if trade.own == true and trade.op == 'SELL' then local active_p = settings.presets and settings.presets[fh_active_preset_idx] local sell_list = active_p and active_p.items or fh_lv_autosell_preset for _asi, asp in ipairs(sell_list) do if asp.name:lower() == trade.item:lower() then asp.qty = math.max(0, (asp.qty or 0) - trade.qty) if active_p then active_p.items = sell_list end settings.autosell_preset = fh_lv_autosell_preset if _G.as_qty_buf and _G.as_qty_buf[_asi] then _G.as_qty_buf[_asi] = imgui.new.char[16](tostring(asp.qty)) end _wfn7p(); break end end end if trade.own == true and trade.op == 'BUY' then local buy_preset = fh_lv_autobuy_preset local cur_buy_p = settings.buy_presets and settings.buy_presets[fh_ab_preset_idx] if cur_buy_p and cur_buy_p.items then buy_preset = cur_buy_p.items end for _abi, abp in ipairs(buy_preset) do if abp.name:lower() == trade.item:lower() then abp.qty = math.max(0, (abp.qty or 1) - trade.qty) if cur_buy_p then cur_buy_p.items = buy_preset end settings.autobuy_preset = fh_lv_autobuy_preset if _G.ab_qty_buf and _G.ab_qty_buf[_abi] then _G.ab_qty_buf[_abi] = imgui.new.char[16](tostring(abp.qty)) end _wfn7p(); break end end end end do local clean_os = text:gsub('{%x+}', '') local owner_m = clean_os:match('^([%a_][%w_]+)%s+продаёт%s+товар') if owner_m then fh_other_shop_owner = owner_m end local chat_num = clean_os:match('#(%d+)') if chat_num then local num = tonumber(chat_num) if num and num >= 1 and num <= 9999 then fh_other_shop_pending_num = num if mh_debug_enabled then mh_notify('[MH CHAT#] num='..num..' msg='..clean_os:sub(1,40), 0xAAFFFF) end end end end if fh_lv_autobuy_running and ( dialogId == 25666 or dialogId == 25667 or dialogId == 26559 or dialogId == 26562 or dialogId == 3060 ) then _G.mh_ab_caught_dlg = dialogId end if _G.mh_ab_wait_confirm and _G.mh_ab_confirm_data then local clean_srv = text:gsub('{%x+}', '') if clean_srv:find('выставлен на скупку') or clean_srv:find('скупк') or clean_srv:find('успешно') or clean_srv:find('установлен') then _dsf3y( _G.mh_ab_confirm_data.name, _G.mh_ab_confirm_data.price, _G.mh_ab_confirm_data.qty, 'buy', 'ok' ) _G.mh_ab_wait_confirm = false _G.mh_ab_confirm_data = nil end end if fh_lv_autosell_running then local clean = text:gsub('{%x+}', '') if clean:find('успешно выставлен на продажу') then fh_lv_sell_confirmed = true end if clean:find('Данные товары запрещено продавать') then fh_lv_sell_forbidden = true fh_lv_sell_confirmed = true end if clean:find('нет доступных ячеек') then fh_lv_sell_no_slots = true fh_lv_sell_confirmed = true end if clean:find('Продажа деактивирована') or clean:find('закрыли лавку') then fh_mkt_lavka_ids = {} fh_mkt_lavka_all_tds = {} fh_mkt_lavka_page_id = -1 end end end function sampev.onChatMessage(playerId, text) if not text then return end local ok, name = pcall(sampGetPlayerNickname, playerId) if not ok or not name then name = tostring(playerId) end local msg = '{FFFFFF}' .. name .. ': ' .. text table.insert(fh_session_chat, 1, msg) _G._lvn7s() end addEventHandler("onShowDialog", function(dialogId, style, title, button1, button2, text) if not mh_pktdbg_enabled then return end local _title = tostring(title or '') local _text = tostring(text or '') local _relevant_id = {[3082]=true,[26546]=true,[25493]=true,[26547]=true,[30001]=true} local _relevant_kw = _title:find('Лавк') or _title:find('Рынок') or _title:find('Маркет') or _title:find('Куп') or _title:find('Прод') or _title:find('Предмет') or _title:find('Menu') or _title:find('Item') if not _relevant_id[dialogId] and not _relevant_kw then return end local _path = getWorkingDirectory():gsub('\\','/') .. '/mrkfdbg.log' local _f = io.open(_path, 'a') if _f then _f:write(string.rep('-',60) .. '\n') _f:write(string.format('DLG id=%-6d style=%d\n', dialogId, style)) _f:write(' title : ' .. _title:sub(1,80) .. '\n') _f:write(' btn1 : ' .. tostring(button1 or ''):sub(1,40) .. '\n') _f:write(' btn2 : ' .. tostring(button2 or ''):sub(1,40) .. '\n') _f:write(string.format(' text : (%d chars)\n', #_text)) local _ln = 0 for _line in (_text:sub(1,600) .. '\n'):gmatch('([^\n]*)\n') do _f:write(' ' .. _line .. '\n') _ln = _ln + 1; if _ln >= 12 then _f:write(' ...(truncated)\n'); break end end local _sid = _text:match('server_id[=:%s]+(%d+)') or _text:match('serverId[=:%s]+(%d+)') if _sid then _f:write(' [server_id found in text]: ' .. _sid .. '\n') end _f:write('\n') _f:close() end end) addEventHandler("onSendPacket", function(packet_id, bs) if packet_id ~= 220 then return end if not mh_filelog_enabled then return end local ok, res = pcall(function() local pos = raknetBitStreamGetNumberOfBitsUsed(bs) raknetBitStreamIgnoreBits(bs, 8) local marker = raknetBitStreamReadInt8(bs) local iface = raknetBitStreamReadInt8(bs) local id = raknetBitStreamReadInt32(bs) local subid = raknetBitStreamReadInt32(bs) local len = raknetBitStreamReadInt16(bs) local json_s = (len and len > 0) and raknetBitStreamReadString(bs, len) or '' _mh_flog('SEND PKT220 iface='..tostring(iface)..' id='..tostring(id)..' sub='..tostring(subid)..' json='..json_s:sub(1,300)) if mh_pktdbg_enabled then local _ok_sj, _sjt = pcall(decodeJson, json_s) local _send_iface_names = { [4]='MARKET_ACTION',[6]='SHOP_BROWSE',[7]='SHOP_ITEM_REQ', [8]='SHOP_ACTION',[57]='TRADE',[60]='SLOT_CLICK',[255]='CLOSE_MENU', } local _path = getWorkingDirectory():gsub('\\','/') .. '/mrkfdbg.log' local _f2 = io.open(_path, 'a') if _f2 then _f2:write(string.rep('-',60) .. '\n') _f2:write(string.format('SEND >>> PKT220 iface=%-3d sub=%-4d id=%s\n', iface, subid, tostring(id))) _f2:write(' iface_name: ' .. (_send_iface_names[iface] or '?') .. '\n') if iface == 60 and subid == 2 and _ok_sj and type(_sjt)=='table' then _f2:write(' [SLOT CLICK — запрос предмета]\n') _f2:write(string.format(' slot : %s\n', tostring(_sjt.slot or '?'))) _f2:write(string.format(' id : %s\n', tostring(_sjt.id or '?'))) _f2:write(string.format(' type : %s (13=продажа, 28=скупка)\n', tostring(_sjt.type or '?'))) _f2:write(string.format(' amount : %s (цена в лавке)\n', tostring(_sjt.amount or '?'))) local _bid = _sjt.id and _G._bqs3v and _G._bqs3v(tonumber(_sjt.id)) local _bnm = _bid and mh_arz_items_db and mh_arz_items_db[_bid] if _bnm then _f2:write(' item : "' .. _bnm .. '"\n') end elseif json_s ~= '' then _f2:write(' json: ' .. json_s:sub(1,400) .. '\n') end _f2:write('\n') _f2:close() end end if iface == 255 then local _age = _G._mh_qpop_pending_time and (os.clock() - _G._mh_qpop_pending_time) or 99 if _age > 3.0 then _G._mh_qpop_pending_id = nil _G._mh_qpop_pending_nm = nil _G._mh_qpop_pending_time = nil _G._mh_qpop_pending_price = nil end if _G._ao_session_open then _G._ao_session_open = false end end if iface == 60 and subid == 2 and json_s ~= '' then local _click_id = tonumber(json_s:match('"id"%s*:%s*(%d+)')) local _click_type = tonumber(json_s:match('"type"%s*:%s*(%d+)')) if _click_id and (_click_type == 13 or _click_type == 28) then local _click_nm = mh_arz_items_db and mh_arz_items_db[_click_id] or nil if not _click_nm or _click_nm == '' then local _click_slot = tonumber(json_s:match('"slot"%s*:%s*(%d+)')) if fh_other_shop_cur and _click_slot then local _lst = _click_type == 13 and fh_other_shop_cur.sell_items or fh_other_shop_cur.buy_items for _, _si in ipairs(_lst or {}) do if _si.slot == _click_slot and type(_si.name) == 'string' and _si.name ~= '' then _click_nm = _si.name; break end end end end _G._mh_qpop_pending_id = _click_id _G._mh_qpop_pending_nm = _click_nm or '' _G._mh_qpop_pending_time = os.clock() end end raknetBitStreamResetReadPointer(bs) end) if not ok then _mh_flog('SEND PKT220 err: '..tostring(res)) end end) addEventHandler("onReceivePacket", function(packet_id, bs) if packet_id ~= 220 then return end raknetBitStreamIgnoreBits(bs, 8) local marker = raknetBitStreamReadInt8(bs) if marker ~= 84 then return end local iface = raknetBitStreamReadInt8(bs) local subid = raknetBitStreamReadInt8(bs) local json_s = _dkn5v(bs) if not json_s then return end _mh_flog('PKT220 iface=' .. iface .. ' sub=' .. subid .. ' json=' .. json_s:sub(1,200)) if mh_pktdbg_enabled then local _dbg_lines = {} function _dbl(s) table.insert(_dbg_lines, s) end _dbl(string.rep('-', 60)) _dbl(string.format('RECV <<< PKT220 iface=%-3d sub=%-3d', iface, subid)) local _iface_names = { [4] = 'MARKET (ЦР)', [6] = 'SHOP_LIST (список лавок)', [7] = 'SHOP_ITEMS (товары лавки)', [8] = 'SHOP_ACTION (действие)', [57] = 'TRADE (трейд)', [60] = 'SLOT_CLICK (клик слота)', } local _iname = _iface_names[iface] or '?' _dbl(' iface_name : ' .. _iname) local _sub_names = { [0] = 'open/reset', [1] = 'update/info', [2] = 'items/click', [3] = 'confirm', [15] = 'menu_overlay', } _dbl(' sub_name : ' .. (_sub_names[subid] or '?')) local _ok_j, _jt = pcall(decodeJson, json_s) if _ok_j and type(_jt) == 'table' then local _fields = { 'id','type','slot','amount','count','price', 'name','owner','username','server_id','serverId', 'LavkaUid','shop_num','title','text','items', 'sell','buy','server','server_name', } _dbl(' --- Ключевые поля ---') for _, _fk in ipairs(_fields) do if _jt[_fk] ~= nil then local _fv = _jt[_fk] if type(_fv) == 'table' then _dbl(string.format(' %-14s = [table, %d entries]', _fk, #_fv > 0 and #_fv or 0)) else _dbl(string.format(' %-14s = %s', _fk, tostring(_fv):sub(1,80))) end end end if iface == 6 then local _arr = _jt.shops or _jt.list or _jt if type(_arr) == 'table' and #_arr > 0 then _dbl(string.format(' --- Лавки в пакете: %d шт ---', #_arr)) for _si = 1, math.min(3, #_arr) do local _se = _arr[_si] if type(_se) == 'table' then _dbl(string.format(' [%d] owner="%s" server_id=%s LavkaUid=%s sell_cnt=%s', _si, tostring(_se.owner or _se.username or '?'):sub(1,20), tostring(_se.server_id or _se.serverId or 'nil'), tostring(_se.LavkaUid or _se.id or 'nil'), tostring(_se.items_sell and #_se.items_sell or _se.sell_count or 'nil') )) end end if #_arr > 3 then _dbl(' ... и ещё ' .. (#_arr-3)) end end end if iface == 7 then local _sitems = _jt.items or _jt.sell or {} local _bitems = _jt.buy or {} _dbl(string.format(' --- Товары: sell=%d buy=%d ---', type(_sitems)=='table' and #_sitems or 0, type(_bitems)=='table' and #_bitems or 0)) for _ii = 1, math.min(4, type(_sitems)=='table' and #_sitems or 0) do local _it = _sitems[_ii] if type(_it) == 'table' then local _nm = mh_arz_items_db and mh_arz_items_db[tonumber(_it.id or _it.item)] or tostring(_it.id or _it.item or '?') _dbl(string.format(' sell[%d] id=%s name="%s" price=%s count=%s', _ii, tostring(_it.id or _it.item or '?'), _nm:sub(1,30), tostring(_it.price or '?'), tostring(_it.count or _it.amount or '?'))) end end end if iface == 4 then _dbl(' --- ЦР пакет sub=' .. subid .. ' ---') local _lots = _jt.lots or _jt.items or {} if type(_lots) == 'table' and #_lots > 0 then _dbl(string.format(' lots_count=%d', #_lots)) for _li = 1, math.min(3, #_lots) do local _lt = _lots[_li] if type(_lt) == 'table' then _dbl(string.format(' lot[%d] item_id=%s price=%s count=%s seller=%s', _li, tostring(_lt.item_id or _lt.id or '?'), tostring(_lt.price or '?'), tostring(_lt.count or _lt.amount or '?'), tostring(_lt.seller or _lt.owner or '?'):sub(1,20))) end end end end if iface == 8 then _dbl(string.format(' type=%s title="%s"', tostring(_jt.type or '?'), tostring(_jt.title or '?'):sub(1,40))) end else _dbl(' [raw, not JSON]: ' .. json_s:sub(1, 300)) end _dbl(' --- RAW JSON (800c) ---') local _js_short = json_s:sub(1, 800) for _jl in (_js_short .. '\n'):gmatch('([^\n]*)\n') do if _jl ~= '' then _dbl(' ' .. _jl) end end if #json_s > 800 then _dbl(' ...(truncated, total=' .. #json_s .. ' bytes)') end local _path = getWorkingDirectory():gsub('\\','/') .. '/mrkfdbg.log' local _f = io.open(_path, 'a') if _f then _f:write(table.concat(_dbg_lines, '\n') .. '\n') _f:close() end end if iface == 57 then if subid == 0 then local _pn = json_s:match('"name"%s*:%s*"([^"]+)"') if _pn then _G._mh_trade_partner = _pn:match('^(.-)%s*%(%d+%)%s*$') or _pn _G._mh_trade_money_give = 0 _G._mh_trade_money_get = 0 _G._mh_trade_our_items = {} _G._mh_trade_their_items = {} _G._mh_trade_calc_token = (_G._mh_trade_calc_token or 0) + 1 _G._mh_trade_auto_offer = nil end end if subid == 2 then local _tp = tonumber(json_s:match('"type"%s*:%s*(%d+)')) or 0 local _items = {} for item_obj in json_s:gmatch('{"[^}]+}') do local iid = tonumber(item_obj:match('"item"%s*:%s*(%d+)')) local amt = tonumber(item_obj:match('"amount"%s*:%s*(%d+)')) or 1 local avail = tonumber(item_obj:match('"available"%s*:%s*(%d+)')) or 0 if iid then local nm = (mh_arz_items_db and mh_arz_items_db[iid]) or tostring(iid) table.insert(_items, {name=nm, qty=amt}) end end if _tp == 3 then _G._mh_trade_our_items = _items elseif _tp == 4 then if not _G._mh_trade_their_items then _G._mh_trade_their_items = {} end for _, it in ipairs(_items) do local iid_raw = tonumber(it.name) if iid_raw then local bid, ench = _G._bqs3v(iid_raw) local base_nm = (mh_arz_items_db and mh_arz_items_db[bid]) or '' if base_nm ~= '' then it.name = base_nm .. (ench ~= '' and (' (' .. ench .. ')') or '') it.base_nm = base_nm else it.name = tostring(iid_raw) end it._resolved = true end local found_idx = nil for i, ex in ipairs(_G._mh_trade_their_items) do if ex.name == it.name then found_idx = i; break end end if found_idx then _G._mh_trade_their_items[found_idx].qty = it.qty else table.insert(_G._mh_trade_their_items, it) end end local _cur_items = _G._mh_trade_their_items if _mh_is_premium() and settings.trade_autoprice and settings.trade_autoprice.enabled and #_cur_items > 0 then _G._mh_trade_calc_token = (_G._mh_trade_calc_token or 0) + 1 local _my_token = _G._mh_trade_calc_token lua_thread.create(function() wait(600) if _G._mh_trade_calc_token ~= _my_token then return end local _snap = _G._mh_trade_their_items local _total = 0 local _found = 0 local _pct = settings.trade_autoprice.pct or 65 for _, _it in ipairs(_snap) do local _qty = _it.qty or 1 local _nm = _it.base_nm or _it.name local _avg = nil local _nm_lo = _nm:lower() if _G._lv_shops_cache and _G._lv_shops_cache[_nm_lo] then _avg = _G._lv_shops_cache[_nm_lo].sell end if not _avg or _avg <= 0 then local _min_price = math.huge for _, _sh in pairs(fh_other_shops or {}) do if type(_sh) == 'table' then for _, _si in ipairs(_sh.sell_items or {}) do if type(_si.name) == 'string' and _si.name:lower() == _nm_lo then local _p = tonumber(_si.price) or 0 if _p > 0 and _p < _min_price then _min_price = _p end end end end end if _min_price < math.huge then _avg = _min_price end end if not _avg or _avg <= 0 then local _min_price = math.huge for _, _lv in ipairs(mh_arz_data or {}) do if type(_lv) == 'table' and _lv.items_sell and _lv.price_sell then for _ii, _iid in ipairs(_lv.items_sell) do local _bid = _G._bqs3v and _G._bqs3v(_iid) or _iid local _nm2 = mh_arz_items_db and mh_arz_items_db[_bid] if _nm2 and _nm2:lower() == _nm_lo then local _p = _lv.price_sell[_ii] or 0 if _p > 0 and _p < _min_price then _min_price = _p end end end end end if _min_price < math.huge then _avg = _min_price end end if not _avg or _avg <= 0 then if type(_G._mh_get_sell_price) == 'function' then _avg = _G._mh_get_sell_price(_nm) end end if not _avg or _avg <= 0 then local _mp = _mh_get_mkt_price(_nm) if _mp then _avg = _mp.avg7 or _mp.avg30 or _mp.today end end if _avg and _avg > 0 then _total = _total + (_avg * _qty) _found = _found + 1 mh_notify('[MH] ' .. _nm .. ' x' .. _qty .. ' = $' .. _kcr3y(_avg * _qty), 0xCCCCCC) else mh_notify('[MH] {ff8800}Нет цены: ' .. _nm, 0xFFFFFF) end end if _G._mh_trade_calc_token ~= _my_token then return end if _total > 0 then local _offer = math.floor(_total * _pct / 100) _G._mh_trade_auto_offer = _offer _G._mh_trade_auto_offer_ts = os.time() mh_notify('[MH] {aaffaa}Авто-цена: ' .. _fmt_price_arz(_offer) .. ' (' .. _pct .. '% от ' .. _fmt_price_arz(_total) .. ', ' .. _found .. '/' .. #_snap .. ' тов.)', 0xFFFFFF) wait(300) if _G._mh_trade_calc_token == _my_token then local _json = '{"type":0,"money":"' .. tostring(_offer) .. '"}' _yzr1t(57, 10, 10, _json) mh_notify('[MH] {00ff88}Сумма вписана: ' .. _fmt_price_arz(_offer), 0xFFFFFF) mh_notify('[MH] {ffdd00}>> Сумма вписана автоматически, подтвердите сделку вручную', 0xFFFFFF) end else mh_notify('[MH] {ff4444}Авто-цена: цены не найдены', 0xFFFFFF) end end) end end end if subid == 4 then local sc = tonumber(json_s:match('"self"%s*:%s*{[^}]*"confirm"%s*:%s*(%d+)')) or 0 local tc = tonumber(json_s:match('"target"%s*:%s*{[^}]*"confirm"%s*:%s*(%d+)')) or 0 local sa = tonumber(json_s:match('"self"%s*:%s*{[^}]*"accept"%s*:%s*(%d+)')) or 0 local ta = tonumber(json_s:match('"target"%s*:%s*{[^}]*"accept"%s*:%s*(%d+)')) or 0 if sa == 1 and ta == 1 and sc == 1 and tc == 1 then local _dp = _G._mh_trade_partner or "" local _gi = _G._mh_trade_our_items or {} local _gei = _G._mh_trade_their_items or {} local _gm = _G._mh_trade_money_give or 0 local _gem = _G._mh_trade_money_get or 0 if _dp ~= "" and (#_gi > 0 or #_gei > 0 or _gm > 0 or _gem > 0) then if not fh_trade_log then fh_trade_log = {} end local _dk = _dp .. '|' .. tostring(math.floor(os.time()/4)) if _G._mh_trade_last_key ~= _dk then _G._mh_trade_last_key = _dk table.insert(fh_trade_log, 1, { dt=os.date('%d.%m %H:%M'), partner=_dp, give_items=_gi, get_items=_gei, give_money=_gm, get_money=_gem }) while #fh_trade_log > 500 do table.remove(fh_trade_log) end do local _my_trd_nick2 = '' pcall(function() local _pid4 = select(2, sampGetPlayerIdByCharHandle(PLAYER_PED)) _my_trd_nick2 = (sampGetPlayerNickname(_pid4) or ''):lower() end) if _my_trd_nick2 == '' then pcall(function() _my_trd_nick2 = (sampGetCurrentPlayerName() or ''):lower() end) end if _my_trd_nick2 ~= '' then _xp_add_trade(_my_trd_nick2, _gem, _gm) _xp_save() end end _ryb5t() _G._mh_trade_saved_ts = os.time() _G._mh_trade_partner = nil _mh_flog("TRADE SAVED (pkt): " .. _dp .. " gi=" .. #_gi .. " gei=" .. #_gei .. " gm=" .. _gm .. " gem=" .. _gem) mh_notify("[MH] Трейд с " .. _dp .. " сохранён", 0xAAFFAA) end end end end if subid == 6 then local sv = tonumber(json_s:match('"self"%s*:%s*{[^}]*"value"%s*:%s*(%d+)')) local tv = tonumber(json_s:match('"target"%s*:%s*{[^}]*"value"%s*:%s*(%d+)')) if sv then _G._mh_trade_money_give = sv end if tv then _G._mh_trade_money_get = tv end end end if iface == 52 then if mh_debug_enabled then mh_notify("[52] sub="..subid.." "..json_s:sub(1,80), 0x888888) end if subid == 1 then _G._ao_inv_ready = true end if subid == 3 then local _ao_sl = tonumber(json_s:match('"slot"%s*:%s*(%d+)')) _G._ao_ctx_slot = _ao_sl or true end end if iface == 52 and subid == 2 then local pkt_type_52 = tonumber(json_s:match('"type"%s*:%s*(%d+)')) or 0 _mh_flog('PKT52 type=' .. pkt_type_52 .. ' json_start=' .. json_s:sub(1,150)) if pkt_type_52 == 1 then local slots_list = {} for item_obj in json_s:gmatch('{"[^}]+}') do local slot = tonumber(item_obj:match('"slot"%s*:%s*(%d+)')) local item = tonumber(item_obj:match('"item"%s*:%s*(%d+)')) if slot and item then table.insert(slots_list, {slot=slot, item=item}) end end if #slots_list > 0 then table.sort(slots_list, function(a,b) return a.slot < b.slot end) mh_lavka_inv = {} for idx, entry in ipairs(slots_list) do mh_lavka_inv[idx-1] = entry end if not _G._ao_item_to_slot then _G._ao_item_to_slot = {} end for _, _e in ipairs(slots_list) do if _e.item and _e.slot then _G._ao_item_to_slot[_e.item] = _e.slot end end mh_lavka_inv_ready = true mh_sell_confirmed = true if mh_debug_enabled then mh_notify("[MH PKT52/1] Инвентарь: " .. #slots_list .. " предметов", 0x88CCFF) end _mh_flog("PKT52/type1 lavka_inv=" .. #slots_list .. " slots") end end if pkt_type_52 == 13 or pkt_type_52 == 28 then local buf_items = {} for item_obj in json_s:gmatch('{([^}]+)}') do local item_id = tonumber(item_obj:match('"item"%s*:%s*(%d+)')) local amount_r = tonumber(item_obj:match('"amount"%s*:%s*(%d+)')) or 0 local price_txt = item_obj:match('"text"%s*:%s*"([^"]*)"') local price_t = (price_txt and #price_txt > 0) and _parse_trade_sum(price_txt) or 0 local price = (price_t > amount_r) and price_t or amount_r local _sn = tonumber(item_obj:match('"slot"%s*:%s*(%d+)')) or 0 if item_id and price and price > 0 then table.insert(buf_items, {item_id=item_id, price=price, avail=1, slot_idx=_sn}) end end if #buf_items > 0 then local was_empty = (#mh_pending_lavka_buf == 0) table.insert(mh_pending_lavka_buf, {pkt_type=pkt_type_52, items=buf_items}) if mh_debug_enabled then local lbl = pkt_type_52==13 and 'SELL' or 'BUY' mh_notify('[MH buf/52] '..lbl..'+'..(#buf_items)..' pending='..(#mh_pending_lavka_buf), 0x888888) end if was_empty then lua_thread.create(function() wait(2500) if #mh_pending_lavka_buf == 0 then return end if fh_other_shop_cur and (#fh_other_shop_cur.sell_items > 0 or #fh_other_shop_cur.buy_items > 0) then return end local fallback_owner = (fh_other_shop_owner and fh_other_shop_owner ~= '') and fh_other_shop_owner or nil if not fallback_owner then mh_notify('[MH] {ff9900}Не удалось определить владельца лавки.', 0xFFFFFF) mh_pending_lavka_buf = {}; return end local my_nick = '' pcall(function() my_nick = sampGetPlayerNickname(select(2, sampGetPlayerIdByCharHandle(PLAYER_PED))) or '' end) if fallback_owner:lower() == my_nick:lower() then mh_pending_lavka_buf = {}; return end if mh_debug_enabled then mh_notify('[MH fb] Форс-обработка: '..fallback_owner, 0xFF9900) end fh_other_shop_cur = { owner = fallback_owner, shop_num = fh_other_shop_pending_num or '?', dt = os.date('%d.%m %H:%M'), ts = os.time(), sell_items = {}, buy_items = {}, server_id = (function() local _sel = _G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 0 local _live = (type(_mpf7d) == 'function') and _mpf7d() or 0 local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 local _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 elseif _boot > 0 then _idx = _boot end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end)(), } fh_other_shop_scanning = true for _, batch in ipairs(mh_pending_lavka_buf) do local cur_list if batch.pkt_type == 13 then cur_list = fh_other_shop_cur.sell_items elseif batch.pkt_type == 28 then cur_list = fh_other_shop_cur.buy_items end if cur_list then local groups, order = {}, {} for _, it in ipairs(batch.items) do local nm = _G._rgn9z(it.item_id) local clean_nm = nm:gsub('{[^}]+}',''):match('^%s*(.-)%s*$') or nm local key = tostring(it.item_id)..'|'..tostring(it.price) if groups[key] then groups[key].qty = groups[key].qty + 1 else groups[key]={name=clean_nm,price=it.price,qty=1,item_id=it.item_id,slot_idx=it.slot_idx or 0}; table.insert(order,key) end end for _, key in ipairs(order) do table.insert(cur_list, groups[key]) end end end mh_pending_lavka_buf = {} local sc = #fh_other_shop_cur.sell_items local bc = #fh_other_shop_cur.buy_items mh_notify('[MH] {aaaaff}Лавка '..fallback_owner..': продажа='..sc..', скупка='..bc, 0xFFFFFF) if sc > 0 or bc > 0 then _qbh9f() end fh_other_shop_scanning = false end) end end else local slots_list = {} for item_obj in json_s:gmatch('{([^}]+)}') do local slot = tonumber(item_obj:match('"slot"%s*:%s*(%d+)')) local item = tonumber(item_obj:match('"item"%s*:%s*(%d+)')) if slot and item then table.insert(slots_list, {slot=slot, item=item}) end end table.sort(slots_list, function(a,b) return a.slot < b.slot end) mh_sell_confirmed = true mh_lavka_inv = {} for idx, entry in ipairs(slots_list) do mh_lavka_inv[idx-1] = entry end if mh_debug_enabled then mh_notify("[MH PKT] Инвентарь: " .. #slots_list .. " предметов", 0x888888) end for i=0, math.min(4, #slots_list-1) do local e = mh_lavka_inv[i] if mh_debug_enabled and e then mh_notify(" ["..i.."] slot="..e.slot.." item="..e.item, 0x888888) end end end end if iface == 60 and subid == 1 then _mh_flog('PKT60_1 full json=' .. json_s:sub(1,500)) if mh_debug_enabled then mh_notify('[MH] {88CCFF}iface=60/sub=1 пришёл', 0x88CCFF) end if fh_other_shop_scanning then _G._mh_is_manual_scan = true end mh_lavka_inv = {} mh_lavka_inv_ready = true local _enc = require('encoding') local _ok_enc, json_s_u8 = pcall(function() return _enc.UTF8:decode(json_s) end) if _ok_enc and json_s_u8 and #json_s_u8 > 0 then json_s = json_s_u8 end local owner_j = json_s:match('"name"%s*:%s*"([^"]+)"') if not owner_j or owner_j == '' then owner_j = json_s:match('"owner"%s*:%s*"([^"]+)"') or json_s:match('"nick"%s*:%s*"([^"]+)"') end if owner_j then owner_j = owner_j:match('%-%s*([A-Za-z_][A-Za-z0-9_]+)') or owner_j:match('^([A-Za-z_][A-Za-z0-9_]+)') or owner_j end local shop_title = owner_j or '' local shopnum_j = json_s:match('"shopNum"%s*:%s*(%d+)') or json_s:match('"shopId"%s*:%s*(%d+)') or json_s:match('"lavkaId"%s*:%s*(%d+)') if owner_j and owner_j ~= '' then fh_other_shop_owner = owner_j end if mh_debug_enabled then mh_notify('[MH 60/1] title='..shop_title..' owner='..(owner_j or '?'), 0x88CCFF) end local my_nick = sampGetPlayerNickname(select(2, sampGetPlayerIdByCharHandle(PLAYER_PED))) or '' local is_my_shop = (owner_j and owner_j:lower() == my_nick:lower()) if is_my_shop then mh_pending_lavka_buf = {} end if not is_my_shop then local eff_owner = owner_j or fh_other_shop_owner or '' if eff_owner ~= '' then fh_other_shop_price_tds = {} local _snum = shopnum_j or fh_other_shop_pending_num if not _snum then local _px, _py = getCharCoordinates(PLAYER_PED) local _bd, _bn = 6.0, nil for _tid = 0, 2047 do local _ok, _tt, _tc, _tpx, _tpy = pcall(sampGet3dTextInfoById, _tid) if _ok and _tt and _tpx then local _n = tostring(_tt):match('[#№](%d+)') if _n then local _num = tonumber(_n) if _num and _num >= 1 and _num <= 9999 then local _d = math.sqrt((_tpx-_px)^2 + (_tpy-_py)^2) if _d < _bd then _bd = _d; _bn = _num end end end end end _snum = _bn end fh_other_shop_cur = { owner = eff_owner, shop_num = _snum or '?', dt = os.date('%d.%m %H:%M'), ts = os.time(), sell_items = {}, buy_items = {}, server_id = (function() local _sel = (_G.arz_srv_sel and (_G.arz_srv_sel[0]+1)) or (_G.mh_boot_srv_idx and (_G.mh_boot_srv_idx+1)) local _sid2 = (_sel and (ARZ_SERVERS[_sel] or {}).id) or -1 if _sid2 == -1 then local _au = _mpf7d() if _au and _au > 0 then _sid2 = (ARZ_SERVERS[_au+1] or {}).id or -1 end end return _sid2 end)(), } fh_other_shop_scanning = true _G._mh_qbh9f_scheduled = true fh_other_shop_pending_num = nil for _, batch in ipairs(mh_pending_lavka_buf) do local cur_list if batch.pkt_type == 13 then cur_list = fh_other_shop_cur.sell_items elseif batch.pkt_type == 28 then cur_list = fh_other_shop_cur.buy_items end if cur_list then local groups = {} local order = {} for _, it in ipairs(batch.items) do local nm = _G._rgn9z(it.item_id) local clean_nm = nm:gsub('{[^}]+}', ''):match('^%s*(.-)%s*$') or nm local key = tostring(it.item_id) .. '|' .. tostring(it.price) if groups[key] then groups[key].qty = groups[key].qty + 1 else groups[key] = { name = clean_nm, price = it.price, qty = 1, item_id = it.item_id, slot_idx = it.slot_idx or 0, } table.insert(order, key) end end for _, key in ipairs(order) do table.insert(cur_list, groups[key]) end end end mh_pending_lavka_buf = {} if mh_debug_enabled then mh_notify('[MH 60/1] '..eff_owner..' sell='..#fh_other_shop_cur.sell_items..' buy='..#fh_other_shop_cur.buy_items..' srv='..tostring(fh_other_shop_cur.server_id), 0x88CCFF) end lua_thread.create(function() wait(800) if not fh_other_shop_cur then return end if #(fh_other_shop_cur.sell_items or {}) == 0 and #(fh_other_shop_cur.buy_items or {}) == 0 and #mh_pending_lavka_buf > 0 then for _, _late_batch in ipairs(mh_pending_lavka_buf) do local _late_list if _late_batch.pkt_type == 13 then _late_list = fh_other_shop_cur.sell_items elseif _late_batch.pkt_type == 28 then _late_list = fh_other_shop_cur.buy_items end if _late_list then local _lg, _lo = {}, {} for _, _li in ipairs(_late_batch.items) do local _lnm = _G._rgn9z(_li.item_id) _lnm = _lnm:gsub('{[^}]+}',''):match('^%s*(.-)%s*$') or _lnm local _lk = tostring(_li.item_id)..'|'..tostring(_li.price) if _lg[_lk] then _lg[_lk].qty = _lg[_lk].qty + 1 else _lg[_lk]={name=_lnm,price=_li.price,qty=1,item_id=_li.item_id}; table.insert(_lo,_lk) end end for _, _lk2 in ipairs(_lo) do table.insert(_late_list, _lg[_lk2]) end end end mh_pending_lavka_buf = {} if mh_debug_enabled then mh_notify('[MH race] Обработаны запоздавшие пакеты: sell='..#fh_other_shop_cur.sell_items..' buy='..#fh_other_shop_cur.buy_items, 0xFFAA44) end end local sell_c = #fh_other_shop_cur.sell_items local buy_c = #fh_other_shop_cur.buy_items _G._mh_is_manual_scan = false _G._mh_qbh9f_scheduled = false _qbh9f() if mh_debug_enabled then mh_notify('[MH] push: '..(fh_other_shop_cur.owner or '?')..' sell='..sell_c..' buy='..buy_c, 0x88FF88) end fh_other_scan_done = sell_c + buy_c fh_other_scan_total = sell_c + buy_c fh_other_shop_scanning = false end) end end end if iface == 60 and subid == 0 then local pkt_type = tonumber(json_s:match('"type"%s*:%s*(%d+)')) or 0 if pkt_type == 13 or pkt_type == 28 then local _first_slot = tonumber(json_s:match('"slot"%s*:%s*(%d+)')) or 999 if _first_slot == 0 then local _now_ts = os.time() local _buf_age = _now_ts - (_G._mh_passive_buf_ts or 0) local _buf_fresh = (_buf_age < 10) and (#mh_pending_lavka_buf > 0) if not _buf_fresh then mh_pending_lavka_buf = {} if mh_debug_enabled then mh_notify('[MH pass] slot=0: буфер сброшен (новая лавка)', 0x88FF88) end else if mh_debug_enabled then mh_notify('[MH pass] slot=0: буфер НЕ сброшен (свежий, age='..tostring(_buf_age)..'s)', 0xFF8800) end end _G._mh_passive_buf_ts = _now_ts end local buf_items = {} for item_obj in json_s:gmatch('{([^}]+)}') do local item_id = tonumber(item_obj:match('"item"%s*:%s*(%d+)')) local amount_r = tonumber(item_obj:match('"amount"%s*:%s*(%d+)')) or 0 local price_txt = item_obj:match('"text"%s*:%s*"([^"]*)') local price_t = (price_txt and #price_txt > 0) and _parse_trade_sum(price_txt) or 0 local price = (price_t > amount_r) and price_t or amount_r local _sn = tonumber(item_obj:match('"slot"%s*:%s*(%d+)')) or 0 if item_id and price and price > 0 then table.insert(buf_items, {item_id=item_id, price=price, avail=1, slot_idx=_sn}) end end if #buf_items > 0 then table.insert(mh_pending_lavka_buf, {pkt_type=pkt_type, items=buf_items}) if mh_debug_enabled then local lbl = pkt_type==13 and 'SELL' or 'BUY' mh_notify('[MH buf] '..lbl..'+'..#buf_items..' pending='..#mh_pending_lavka_buf, 0x888888) end else if mh_debug_enabled then mh_notify('[MH buf] 60/0 type='..pkt_type..' buf_items=0', 0xFF8800) local _first = json_s:match('{([^}]+)}') if _first then mh_notify('[MH buf raw] '.._first:sub(1,100), 0xFF8800) end end end end end if iface == 52 and fh_other_shop_scanning and fh_other_shop_cur then if mh_debug_enabled then mh_notify('[MH 52] инв. получен, скан лавки идёт...', 0x888888) end end if iface == 52 and subid == 2 and mh_lavka_inv_ready then if fh_lv_autostart_enabled and #fh_lv_autosell_preset > 0 and not fh_lv_autosell_running and next(mh_lavka_inv) then fh_lv_sell_confirmed = false fh_lv_sell_forbidden = false fh_lv_sell_no_slots = false mh_notify("[MH] {00cc00}Автозапуск выкладки!", 0xFFFFFF) lua_thread.create(function() wait(300); _wmc7r() end) end end if iface == 8 and subid == 15 then local _menu_type = tonumber(json_s:match('"type"%s*:%s*(%d+)')) local _menu_title = json_s:match('"title"%s*:%s*"([^"]*)"') or '' if _menu_type == 3 and _menu_title:find('Мен', 1, true) then _mh_qpop_try_open('', 3.0) _G._mh_qpop_opened_at = os.clock() if not _G._mh_passive_scan_busy then _G._mh_passive_scan_busy = true _G._mh_is_manual_scan = true lua_thread.create(function() wait(300) if fh_other_shop_cur and ((#(fh_other_shop_cur.sell_items or {}) > 0) or (#(fh_other_shop_cur.buy_items or {}) > 0)) then _G._mh_passive_scan_busy = false return end wait(700) if fh_other_shop_cur and ((#(fh_other_shop_cur.sell_items or {}) > 0) or (#(fh_other_shop_cur.buy_items or {}) > 0)) then _G._mh_passive_scan_busy = false return end if #mh_pending_lavka_buf > 0 then local _dl = os.clock() + 3.0 while os.clock() < _dl do wait(100) if fh_other_shop_cur and ((#(fh_other_shop_cur.sell_items or {}) > 0) or (#(fh_other_shop_cur.buy_items or {}) > 0)) then break end end _G._mh_passive_scan_busy = false return end mh_pending_lavka_buf = {} _yzr1t(60, 113, 113, '') local _dl2 = os.clock() + 4.0 local _prev = fh_other_shop_owner while os.clock() < _dl2 do wait(100) if fh_other_shop_cur and ((#(fh_other_shop_cur.sell_items or {}) > 0) or (#(fh_other_shop_cur.buy_items or {}) > 0)) then wait(400); break end if #mh_pending_lavka_buf > 0 and fh_other_shop_owner ~= _prev then wait(400); break end end _G._mh_passive_scan_busy = false end) end elseif _menu_type == 0 then local _age_open = _G._mh_qpop_opened_at and (os.clock() - _G._mh_qpop_opened_at) or 99 local _has_title = (_menu_title ~= '') if _has_title and _age_open > 1.5 then _G.mh_qpop_open = false end end end end) function _mh_draw_tab_rating() if not _G._xp_pull_attempted and not _G._xp_srv_loading then _G._xp_pull_attempted = true _xp_push_self() lua_thread.create(function() wait(1500); _xp_pull_srv() end) end if not _G._rtg_srv_filter then _G._rtg_srv_filter = imgui.new.int(0) end local d = settings.general.custom_dpi local cw = imgui.GetWindowContentRegionWidth() local ar_r = settings.interface.accent_r or 1 local ag_r = settings.interface.accent_g or .65 local ab_r = settings.interface.accent_b or 0.0 local bg = settings.interface.bg or 0.10 local _rh = imgui.GetContentRegionAvail().y if imgui.BeginChild('##rating_wrap', imgui.ImVec2(-1, _rh), false) then do local _sw2 = _G._sw if _sw2 and _sw2.drag_y ~= 0 then imgui.SetScrollY(math.max(0, imgui.GetScrollY() - _sw2.drag_y)) end end imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar_r, ag_r, ab_r, 1), _ic_chrts .. ' ' .. _cyr5f('Рейтинг торговцев')) imgui.Spacing() local _rtg_srv_labels = {} _rtg_srv_labels[0] = _cyr5f('Текущий сервер') for _si, _ss in ipairs(ARZ_SERVERS) do _rtg_srv_labels[_si] = _cyr5f(_ss.name) end imgui.PushItemWidth(cw * 0.55) if imgui.BeginCombo('##rtg_srv_combo', _rtg_srv_labels[_G._rtg_srv_filter[0]]) then for _ci = 0, #ARZ_SERVERS do local _sel = (_G._rtg_srv_filter[0] == _ci) if imgui.Selectable(_rtg_srv_labels[_ci]..'##rtgsrv'.._ci, _sel) then _G._rtg_srv_filter[0] = _ci _G._xp_srv_loaded = false _G._xp_pull_attempted = false _G._xp_srv_data = {} _G._xp_rank_cache = nil _xp_push_self() lua_thread.create(function() wait(1500); _xp_pull_srv() end) end if _sel then imgui.SetItemDefaultFocus() end end imgui.EndCombo() end imgui.PopItemWidth() imgui.SameLine(0, 4*d) if imgui.Button(_ic_rot..' '.._cyr5f('Мой##xp_recalc'), imgui.ImVec2(cw*0.20, 0)) then _xp_recalc_from_log() end imgui.SameLine(0,4*d) if _G._xp_srv_loading then imgui.TextDisabled(_cyr5f('Загрузка...')) else if imgui.Button(_ic_cld..'##xp_pull_btn', imgui.ImVec2(cw*0.14,0)) then _xp_push_self(); lua_thread.create(function() wait(1500); _xp_pull_srv() end) end end imgui.Spacing() imgui.Separator() imgui.Spacing() local ncols = 7 local col_w = {28*d, cw*0.20, cw*0.12, cw*0.14, cw*0.16, cw*0.14, cw*0.17} imgui.Columns(ncols, '##rtg_hdr', false) for i, w in ipairs(col_w) do imgui.SetColumnWidth(i-1, w) end local hc = imgui.ImVec4(ar_r*0.6, ag_r*0.6, ab_r*0.4, 1) imgui.TextColored(hc, '#'); imgui.NextColumn() imgui.TextColored(hc, _cyr5f(' Ник')); imgui.NextColumn() imgui.TextColored(hc, _cyr5f(' Сервер')); imgui.NextColumn() imgui.TextColored(hc, _cyr5f(' Уровень')); imgui.NextColumn() imgui.TextColored(hc, _cyr5f(' Опыт / след.')); imgui.NextColumn() imgui.TextColored(hc, _cyr5f(' До ур.')); imgui.NextColumn() imgui.TextColored(hc, _cyr5f(' Получил/Отдал')); imgui.NextColumn() imgui.Columns(1) imgui.Separator() if not _G._xp_rank_page then _G._xp_rank_page = 1 end local _RPAGE = 30 local rank_list = _xp_get_rank() local total_r = #rank_list local total_pages = math.max(1, math.ceil(total_r / _RPAGE)) if _G._xp_rank_page > total_pages then _G._xp_rank_page = 1 end local my_nick = '' pcall(function() local _my_pid = select(2, sampGetPlayerIdByCharHandle(PLAYER_PED)) my_nick = (sampGetPlayerNickname(_my_pid) or ''):lower() end) if my_nick == '' then pcall(function() my_nick = (sampGetCurrentPlayerName() or ''):lower() end) end local rs = (_G._xp_rank_page - 1) * _RPAGE + 1 local re = math.min(_G._xp_rank_page * _RPAGE, total_r) if total_r == 0 then imgui.Spacing() imgui.TextDisabled(_cyr5f(' Нет данных. Нажмите Пересчитать.')) end for ri = rs, re do local p = rank_list[ri] local is_me = p.nick == my_nick local row_bg = (ri % 2 == 0) and imgui.ImVec4(bg+.05, bg+.045, bg+.02, 0.5) or imgui.ImVec4(0,0,0,0) imgui.PushStyleColor(imgui.Col.ChildBg, row_bg) imgui.Columns(ncols, '##rtg_row'..ri, false) for i, w in ipairs(col_w) do imgui.SetColumnWidth(i-1, w) end local place_col if ri == 1 then place_col = imgui.ImVec4(1.0, 0.84, 0.0, 1) elseif ri == 2 then place_col = imgui.ImVec4(0.75, 0.75, 0.75, 1) elseif ri == 3 then place_col = imgui.ImVec4(0.80, 0.50, 0.20, 1) else place_col = imgui.ImVec4(0.5, 0.5, 0.5, 0.8) end imgui.TextColored(place_col, tostring(ri)) imgui.NextColumn() local nick_str = p.display_nick or p.nick local nick_col = is_me and imgui.ImVec4(ar_r, ag_r, ab_r, 1) or imgui.ImVec4(0.55, 0.85, 1.0, 1) local _nsp = imgui.GetCursorScreenPos() local _nlh = imgui.GetTextLineHeight() local _ncw = col_w[2] - 4*d local _nlbl = _cyr5f(' ' .. nick_str) local _ntw = imgui.CalcTextSize(_nlbl).x local _nc32 = is_me and 0xFFFFFFFF or imgui.ColorConvertFloat4ToU32(nick_col) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0,0,0,0)) if imgui.Selectable('##rtgnick'..ri, false, 0, imgui.ImVec2(_ncw, 0)) then local _found_lv = nil local _p_nick_lo = p.nick:lower() for _, _lv in ipairs(mh_arz_data or {}) do if type(_lv) == 'table' and (_lv.username or ''):lower() == _p_nick_lo then _found_lv = _lv; break end end if not _found_lv then for _, _sh in pairs(fh_other_shops or {}) do if type(_sh) == 'table' and (_sh.owner or ''):lower() == _p_nick_lo then _found_lv = {username=_sh.owner, LavkaUid=_sh.shop_num or 0, serverId=_sh.server_id or -1, items_sell={}, items_buy={}, price_sell={}, price_buy={}, count_sell={}, count_buy={}, _mh_cloud=true} if mh_arz_items_db then local _fi = 920000 for _, si in ipairs(_sh.sell_items or {}) do _fi=_fi+1; mh_arz_items_db[_fi]=si.name or '?' table.insert(_found_lv.items_sell, _fi) table.insert(_found_lv.price_sell, si.price or 0) table.insert(_found_lv.count_sell, si.qty or 1) end for _, bi in ipairs(_sh.buy_items or {}) do _fi=_fi+1; mh_arz_items_db[_fi]=bi.name or '?' table.insert(_found_lv.items_buy, _fi) table.insert(_found_lv.price_buy, bi.price or 0) table.insert(_found_lv.count_buy, bi.qty or 1) end end break end end end if _found_lv then _G.mh_tab = 2 _G.arz_detail = _found_lv _G.arz_detail_tab = 0 _G.arz_page = 1 _G.arz_cache_key = nil _G._rtg_open_lavka = true else mh_notify('[MH] {ffaa44}Лавка ' .. nick_str .. ' не найдена. Обновите лавки (/mrk).', 0xFFFFFF) end end imgui.PopStyleColor() do local _ndl = imgui.GetWindowDrawList() local _nc32b = imgui.ColorConvertFloat4ToU32(nick_col) _ndl:PushClipRect(_nsp, imgui.ImVec2(_nsp.x + _ncw, _nsp.y + _nlh + 2), true) local _noff = 0 if _ntw > _ncw then local _nsd = _ntw - _ncw + 8 local _nspd = 1.5 local _nspt = _nsd / 40 + 2 * _nspd local _nph = math.fmod(imgui.GetTime() + ri * 0.37, _nspt) if _nph > _nspd then _noff = math.min((_nph - _nspd) * 40, _nsd) end if _nph >= _nspt - _nspd then _noff = _nsd end end _ndl:AddText(imgui.ImVec2(_nsp.x - _noff, _nsp.y), _nc32b, _nlbl) _ndl:PopClipRect() end local _p_is_prem = p.is_premium or (_G._xp_db and _G._xp_db[p.nick] and _G._xp_db[p.nick].is_premium == true) if _p_is_prem then imgui.SameLine(0, 3*d) imgui.TextColored(imgui.ImVec4(1.0, 0.82, 0.10, 1), _ic_star) end imgui.NextColumn() local _srv_name = '?' if p.server and p.server ~= -1 then for _, _s in ipairs(ARZ_SERVERS or {}) do if _s.id == p.server then _srv_name = _s.name or '?' break end end end imgui.TextDisabled(' ' .. _srv_name) imgui.NextColumn() local lv_pct = (p.level > 0) and (p.xp - _xp_for_level(p.level)) / (_xp_for_level(p.level+1) - _xp_for_level(p.level)) or 0 local lv_col = imgui.ImVec4( 0.2 + 0.8*(p.level/math.max(p.level,20)), 0.8 - 0.4*(p.level/math.max(p.level,20)), 0.2, 1) imgui.TextColored(lv_col, ' LVL.' .. tostring(p.level)) imgui.SameLine() imgui.PushStyleColor(imgui.Col.PlotHistogram, lv_col) imgui.ProgressBar(math.min(lv_pct, 1.0), imgui.ImVec2(36*d, 8*d), '') imgui.PopStyleColor() imgui.NextColumn() function _fmt_xpv(n) if n >= 1e9 then return string.format('%.1fM', n/1e9) elseif n >= 1e6 then return string.format('%.1fKK', n/1e6) elseif n >= 1e3 then return string.format('%.1fK', n/1e3) else return tostring(math.floor(n)) end end local _xp_next_v = _xp_for_level((p.level or 0) + 1) local xp_str = _fmt_xpv(p.xp) .. ' / ' .. _fmt_xpv(_xp_next_v) imgui.Text(' ' .. xp_str) imgui.NextColumn() local _lv_cur = p.level or 0 local _xp_next = _xp_for_level(_lv_cur + 1) local _xp_need = math.max(0, _xp_next - (p.xp or 0)) local _nls if _xp_need >= 1e9 then _nls = string.format('%.1fM', _xp_need/1e9) elseif _xp_need >= 1e6 then _nls = string.format('%.1fKK', _xp_need/1e6) elseif _xp_need >= 1e3 then _nls = string.format('%.1fK', _xp_need/1e3) else _nls = tostring(math.floor(_xp_need)) end if _xp_need == 0 then imgui.TextDisabled(' --') else imgui.TextDisabled(' -' .. _nls) end imgui.NextColumn() local _all_sell = (p.sales_virtu or 0) + (p.trade_sell_virtu or 0) local _all_buy = (p.buy_virtu or 0) + (p.trade_buy_virtu or 0) local function _fmt_tv(n) if n >= 1e9 then return string.format('%.1fM', n/1e9) elseif n >= 1e6 then return string.format('%.0fKK', n/1e6) elseif n >= 1e3 then return string.format('%.0fK', n/1e3) else return tostring(math.floor(n)) end end if _all_sell > 0 or _all_buy > 0 then imgui.TextColored(imgui.ImVec4(0.35,0.9,0.35,1), ' '.._fmt_tv(_all_sell)) imgui.SameLine(0,2*d) imgui.TextDisabled('/') imgui.SameLine(0,2*d) imgui.TextColored(imgui.ImVec4(1,0.55,0.3,1), _fmt_tv(_all_buy)) else imgui.TextDisabled(' --') end imgui.NextColumn() imgui.Columns(1) imgui.PopStyleColor() end if total_pages > 1 then imgui.Separator() imgui.Spacing() local pw = (cw - 8*d) / 3 if imgui.Button(_ic_al..'##rp', imgui.ImVec2(pw,0)) then _G._xp_rank_page=1 end imgui.SameLine(0,4*d) if imgui.Button(_ic_lt..'##rpp', imgui.ImVec2(pw,0)) then _G._xp_rank_page = math.max(1, _G._xp_rank_page-1) end imgui.SameLine(0,4*d) if imgui.Button(_ic_ar..'##rpn', imgui.ImVec2(pw,0)) then _G._xp_rank_page = math.min(total_pages, _G._xp_rank_page+1) end imgui.Spacing() imgui.TextDisabled(_cyr5f('Стр. '.. _G._xp_rank_page ..'/'..total_pages.. ' ('..total_r..' игроков)')) end imgui.EndChild() end end imgui.OnFrame(function() return MainWindow[0] end, function() local d = settings.general.custom_dpi local ar = settings.interface.accent_r or 1.0 local ag = settings.interface.accent_g or 0.55 local ab = settings.interface.accent_b or 0.0 local sb_r = settings.interface.sell_btn_r or 0.10 local sb_g = settings.interface.sell_btn_g or 0.45 local sb_b = settings.interface.sell_btn_b or 0.10 local bb_r = settings.interface.buy_btn_r or 0.00 local bb_g = settings.interface.buy_btn_g or 0.28 local bb_b = settings.interface.buy_btn_b or 0.50 local bg = settings.interface.bg_brightness or 0.06 local lp_r = settings.overlay and settings.overlay.log_price_r or 1.0 local lp_g = settings.overlay and settings.overlay.log_price_g or 0.85 local lp_b = settings.overlay and settings.overlay.log_price_b or 0.2 do local _mwc = settings.mh_win local _use_saved = _mwc and _mwc.w and _mwc.w > 200 and _mwc.h and _mwc.h > 100 local _mw = _use_saved and _mwc.w or 1387 local _mh = _use_saved and _mwc.h or 832 if _G._mh_win_reset then imgui.SetNextWindowPos(imgui.ImVec2(sizeX/2, sizeY/2), imgui.Cond.Always, imgui.ImVec2(0.5, 0.5)) imgui.SetNextWindowSize(imgui.ImVec2(_mw, _mh), imgui.Cond.Always) _G._mh_win_reset = false else imgui.SetNextWindowPos(imgui.ImVec2(sizeX/2, sizeY/2), imgui.Cond.Once, imgui.ImVec2(0.5, 0.5)) imgui.SetNextWindowSize(imgui.ImVec2(_mw, _mh), imgui.Cond.Once) end end imgui.GetIO().ConfigWindowsMoveFromTitleBarOnly = not (settings.interface and settings.interface.free_move == true) _G._mh_wa = settings.interface and settings.interface.window_alpha or 0.98 local _mh_wa = _G._mh_wa imgui.SetNextWindowBgAlpha(_mh_wa) imgui.PushStyleColor(imgui.Col.Border, imgui.ImVec4(ar*0.80, ag*0.80, ab*0.80, 1.0)) imgui.PushStyleColor(imgui.Col.TitleBg, imgui.ImVec4(bg*0.8, bg*0.8, bg*0.8, _mh_wa)) imgui.PushStyleColor(imgui.Col.TitleBgActive, imgui.ImVec4(ar*0.18, ag*0.18, ab*0.18, _mh_wa)) local _sw_flags = imgui.WindowFlags.NoCollapse if _G._sw and (_G._sw.active or math.abs(_G._sw.inertia or 0) > 0.5) then _sw_flags = _sw_flags + imgui.WindowFlags.NoMove end imgui.Begin(u8(" MARKET HELPER v" .. thisScript().version .. " "), MainWindow, _sw_flags) imgui.PopStyleColor(3) _G._ltz8m() do local _rws = imgui.GetWindowSize() if _rws.x > 200 and _rws.y > 100 then if not settings.mh_win then settings.mh_win = {} end local _rsw, _rsh = settings.mh_win.w, settings.mh_win.h if math.abs((_rsw or 0) - _rws.x) > 2 or math.abs((_rsh or 0) - _rws.y) > 2 then settings.mh_win.w = _rws.x; settings.mh_win.h = _rws.y local _now = os.clock() if not _G._mh_winsave_t or _now - _G._mh_winsave_t > 1.0 then _wfn7p(); _G._mh_winsave_t = _now end end if _G.sl_win_w then _G.sl_win_w[0] = _rws.x / d end if _G.sl_win_h then _G.sl_win_h[0] = _rws.y / d end end end do local _io = imgui.GetIO() local _sw = _G._sw local _dy = _io.MouseDelta.y local _dn = _io.MouseDown[0] if _dn then if _io.MouseClicked[0] then _sw.blocked = imgui.IsAnyItemActive() if not _sw.blocked then local _wpos = imgui.GetWindowPos() local _wsz = imgui.GetWindowSize() local _sbw = (settings.interface and settings.interface.scrollbar_w or 12) * d local _mx = _io.MousePos.x if _mx > (_wpos.x + _wsz.x - _sbw * 2) then _sw.blocked = true end end _sw.active = false _sw.vel = 0 _sw.drag_y = 0 _sw.inertia = 0 end if not _sw.blocked and math.abs(_dy) > 0.3 then _sw.active = true _sw.vel = _sw.vel * 0.55 + _dy * 0.45 _sw.drag_y = _dy else _sw.drag_y = 0 end else if _sw.active then _sw.inertia = _sw.vel * 1.5 _sw.active = false _sw.vel = 0 end _sw.blocked = false _sw.drag_y = 0 if not _sw.blocked then if math.abs(_sw.inertia) > 0.5 then _sw.drag_y = _sw.inertia _sw.inertia = _sw.inertia * 0.72 else _sw.inertia = 0 end else _sw.inertia = 0 end end end local hw = imgui.GetWindowWidth() local dl = imgui.GetWindowDrawList() local wp = imgui.GetCursorScreenPos() imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), u8' MARKET HELPER') imgui.SameLine() imgui.TextColored(imgui.ImVec4(ar*0.45,ag*0.45,ab*0.45,1), u8(' · ARIZONA RP · v' .. (thisScript().version or '?'))) if _bcn4w() then imgui.SameLine(0, 10*d) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1.0, 0.85, 0.10, 1)) imgui.Text(_ic_star .. ' PREMIUM') imgui.PopStyleColor() end imgui.SameLine(hw - 72*d) imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _ic_circ) imgui.SameLine(0,4*d) imgui.TextColored(imgui.ImVec4(ar*0.60, ag*0.60, ab*0.60, 1), _ic_circ) imgui.SameLine(0,4*d) imgui.TextColored(imgui.ImVec4(ar*0.25, ag*0.25, ab*0.25, 1), _ic_circ) imgui.Separator() imgui.Spacing() if not _G.mh_tab then _G.mh_tab = 2 end local sidebar_w = 130*d local mh_sidebar_items = { { icon = fa.STORE, label = u8'\xd0\xdb\xcd\xce\xca', id = 1 }, { icon = fa.WAREHOUSE, label = u8'\xcb\xc0\xc2\xca\xc8', id = 2 }, { icon = fa.TAG, label = u8'\xcf\xd0\xce\xc4\xc0\xc6\xc0', id = 3 }, { icon = fa.CART_PLUS, label = u8'\xd1\xca\xd3\xcf\xca\xc0', id = 4 }, { icon = fa.CLOCK_ROTATE_LEFT, label = u8'\xcb\xce\xc3', id = 5 }, { icon = fa.CIRCLE_PLUS, label = u8'\xca\xc0\xcb\xdc\xca.', id = 11 }, { icon = fa.BULLHORN, label = u8'\xcf\xc8\xc0\xd0', id = 7 }, { icon = fa.PALETTE, label = u8'\xc2\xc8\xc4', id = 6 }, { icon = fa.CROSSHAIRS, label = u8'\xcb\xce\xc2\xcb\xdf', id = 8 }, { icon = fa.TROPHY, label = u8'\xd0\xc5\xc9\xd2\xc8\xcd\xc3', id = 12 }, { icon = fa.CIRCLE_INFO, label = u8'\xce \xd1\xca\xd0\xc8\xcf\xd2\xc5', id = 9 }, { icon = fa.STAR, label = u8'Premium', id = 10 }, } imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(bg*0.7, bg*0.7, bg*0.7, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Border, imgui.ImVec4(ar*0.25, ag*0.25, ab*0.25, 0.5)) if imgui.BeginChild('##mh_sidebar', imgui.ImVec2(sidebar_w, imgui.GetContentRegionAvail().y), true) then _dpn1w() imgui.Spacing() for _, stab in ipairs(mh_sidebar_items) do local _stab_tv = settings.tabs_visible and settings.tabs_visible['tab_'..tostring(stab.id)] if _stab_tv == false then goto _sidebar_skip end local is_active = _G.mh_tab == stab.id if is_active then local _btn_r, _btn_g, _btn_b = ar, ag, ab if stab.id == 10 then _btn_r, _btn_g, _btn_b = 1.0, 0.82, 0.10 end imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_btn_r, _btn_g, _btn_b, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(_btn_r*0.85, _btn_g*0.85, _btn_b*0.85, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(_btn_r*0.95, _btn_g*0.95, _btn_b*0.95, 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.04, 0.04, 0.03, 1)) else local _idle_text = stab.id==10 and imgui.ImVec4(1.0,0.82,0.10,0.9) or imgui.ImVec4(0.65,0.65,0.65,1) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(bg+.05, bg+.045, bg+.025,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(ar*0.20, ag*0.20, ab*0.20, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(ar*0.35, ag*0.35, ab*0.35, 1)) imgui.PushStyleColor(imgui.Col.Text, _idle_text) end local btn_lbl = stab.icon .. ' ' .. stab.label .. '##sb' .. stab.id if imgui.Button(btn_lbl, imgui.ImVec2(sidebar_w - 10*d, 40*d)) then _G.mh_tab = stab.id _G._sw.inertia = 0; _G._sw.vel = 0; _G._sw.drag_y = 0 end imgui.PopStyleColor(4) imgui.Spacing() ::_sidebar_skip:: end imgui.EndChild() end imgui.PopStyleColor(2) imgui.SameLine(0, 6*d) imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(bg+.025, bg+.022, bg+.012, _G._mh_wa or 1)) if imgui.BeginChild('##mh_content', imgui.ImVec2(-1, imgui.GetContentRegionAvail().y), false) then _qbs9k() if _G.mh_tab == 10 then do local d_pm = settings.general.custom_dpi local pm_h = imgui.GetWindowHeight() - 55*d_pm if imgui.BeginChild('##prem_wrap', imgui.ImVec2(-1, pm_h), false) then _dpn1w() imgui.Spacing() local gold = imgui.ImVec4(1.0, 0.82, 0.10, 1) local gold_dim = imgui.ImVec4(0.75, 0.62, 0.08, 1) local _pm_lbl = _cyr5f('PREMIUM') local _pm_lbl_w = imgui.CalcTextSize(_pm_lbl).x + 60*d_pm imgui.SetCursorPosX(math.max(0,(imgui.GetWindowContentRegionWidth()-_pm_lbl_w)*0.5)) imgui.PushStyleColor(imgui.Col.Text, gold) imgui.Text(_ic_star..' ') imgui.SameLine(0,4*d_pm) imgui.Text(_pm_lbl) imgui.SameLine(0,4*d_pm) imgui.Text(' '.._ic_star) imgui.PopStyleColor() imgui.Spacing(); imgui.Separator(); imgui.Spacing() local is_prem = _bcn4w() if is_prem then imgui.SetCursorPosX((imgui.GetWindowContentRegionWidth()-280*d_pm)*0.5) imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.12,0.10,0.02, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Border, imgui.ImVec4(1.0,0.82,0.10,0.8)) if imgui.BeginChild('##prem_active_box', imgui.ImVec2(310*d_pm, 120*d_pm), true) then imgui.Spacing() imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.3,0.95,0.3,1)) imgui.SetCursorPosX((imgui.GetColumnWidth()-imgui.CalcTextSize(_ic_chk).x)*0.5) imgui.Text(_ic_chk..' '.._cyr5f('Активирован')) imgui.PopStyleColor() local prem_user = settings.premium.user or '' if prem_user ~= '' then imgui.PushStyleColor(imgui.Col.Text, gold_dim) imgui.SetCursorPosX((imgui.GetColumnWidth()-imgui.CalcTextSize(_cyr5f(prem_user)).x)*0.5) imgui.Text(_cyr5f(prem_user)) imgui.PopStyleColor() end local prem_exp = settings.premium.expires or '' if prem_exp ~= '' then local exp_col = imgui.ImVec4(0.7,0.7,0.7,1) local days_left = nil pcall(function() local y,m,d = prem_exp:match('(%d+)[%-%.](%d+)[%-%.](%d+)') if y then local exp_ts = os.time({year=tonumber(y),month=tonumber(m),day=tonumber(d),hour=23,min=59,sec=59}) local now_ts = os.time() days_left = math.ceil((exp_ts - now_ts) / 86400) if days_left <= 3 then exp_col = imgui.ImVec4(1.0,0.4,0.2,1) elseif days_left <= 7 then exp_col = imgui.ImVec4(1.0,0.75,0.1,1) end end end) local exp_suffix = '' if days_left then if days_left <= 0 then exp_suffix = ' (истекла)' elseif days_left == 1 then exp_suffix = ' (1 день)' elseif days_left <= 4 then exp_suffix = ' (' .. days_left .. ' дня)' elseif days_left <= 7 then exp_suffix = ' (' .. days_left .. ' дней)' end end local exp_lbl = _cyr5f('До: ' .. prem_exp .. exp_suffix) imgui.PushStyleColor(imgui.Col.Text, exp_col) imgui.SetCursorPosX((imgui.GetColumnWidth()-imgui.CalcTextSize(exp_lbl).x)*0.5) imgui.Text(exp_lbl) imgui.PopStyleColor() end imgui.Spacing(); imgui.EndChild() end imgui.PopStyleColor(2); imgui.Spacing() imgui.SetCursorPosX((imgui.GetWindowContentRegionWidth()-200*d_pm)*0.5) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.35,0.12,0.12,1)) if imgui.Button(_ic_x..' '.._cyr5f('Деактивировать##prem_deact'), imgui.ImVec2(200*d_pm,0)) then settings.premium.activated=false; settings.premium.key=''; settings.premium.user=''; _mh_reset_prem() _wfn7p() end imgui.PopStyleColor(); imgui.Spacing(); imgui.Separator(); imgui.Spacing() end if not _G.prem_key_buf then _G.prem_key_buf = imgui.new.char[128](_cyr5f(settings.premium.key or '')) end imgui.TextColored(gold_dim, _ic_key..' ') imgui.SameLine(0,6*d_pm) imgui.TextDisabled(_cyr5f('Лицензионный ключ:')) imgui.PushItemWidth(-1) imgui.PushStyleColor(imgui.Col.FrameBg, imgui.ImVec4(0.10,0.08,0.02, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Border, imgui.ImVec4(0.75,0.62,0.08,0.7)) imgui.InputText('##prem_key_inp', _G.prem_key_buf, 128) imgui.PopStyleColor(2); imgui.PopItemWidth(); imgui.Spacing() imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.45,0.35,0.04,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.65,0.52,0.06, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1.0,0.95,0.7,1)) local _btn_lbl = _prem_checking and (_ic_spin..' '.._cyr5f('Проверка...')) or (_ic_bolt..' '.._cyr5f('Активировать##prem_act')) if imgui.Button(_btn_lbl, imgui.ImVec2(-1, 36*d_pm)) and not _prem_checking then local key_entered = u8:decode(ffi.string(_G.prem_key_buf)):gsub('^%s+',''):gsub('%s+$','') if key_entered ~= '' then _fpc2t(key_entered, function(ok2, user2) if ok2 then local _exp2 = settings.premium.expires or '' local _nick2 = settings.premium.nick or '' local _msg = '[MH] {FFD700}Premium Активирован!' if _nick2 ~= '' then _msg = _msg .. ' {ffffff}Ник: {FFD700}' .. _nick2 end if _exp2 ~= '' then _msg = _msg .. ' {aaaaaa}| {ffffff}До: ' .. _exp2 else _msg = _msg .. ' {aaaaaa}| {ff8800}Срок: не задан' end mh_notify(_msg, 0xFFFFFF) else mh_notify('[MH] {FF4444}Premium: Неверный ключ', 0xFFFFFF) end end) end end imgui.PopStyleColor(3) imgui.Spacing() imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.45,0.35,0.04,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.65,0.52,0.06, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1.0,0.95,0.7,1)) if imgui.Button(_ic_star..' '.._cyr5f('Купить Premium')..' '.._ic_star..'##prem_buy_btn', imgui.ImVec2(-1, 36*d_pm)) then if _cvh6z() then pcall(gta._Z12AND_OpenLinkPKc, 'https://t.me/MHARIZONAbot') else os.execute('start https://t.me/MHARIZONAbot') end end imgui.PopStyleColor(3) if _prem_check_status ~= '' then imgui.Spacing() local _psc_ok = (_prem_check_status == 'OK') local _psc = _psc_ok and imgui.ImVec4(0.3,0.95,0.3,1) or imgui.ImVec4(1,0.45,0.25,1) local _psc_txt = _prem_check_status if _psc_ok then local _exp_s = settings.premium.expires or '' local _usr_s = settings.premium.user or '' _psc_txt = 'Ключ принят' if _usr_s ~= '' and _usr_s ~= 'Premium User' then _psc_txt = _psc_txt .. ' · ' .. _usr_s end if _exp_s ~= '' then _psc_txt = _psc_txt .. ' · До: ' .. _exp_s end end imgui.TextColored(_psc, _cyr5f(_psc_txt)) end imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextColored(gold_dim, _ic_star..' '.._cyr5f('Преимущества Premium:')) imgui.Spacing() local feats = { {_ic_scl, 'Арбитраж — находи выгодные сделки между лавками'}, {_ic_warn, 'Уведомления Telegram — Избранное в лавке дешевле рынка'}, {_ic_rot, 'АвтоЦена при трейде — цена товара по рынку. Если 50% — скупаешь вдвое дешевле рынка'}, {_ic_star, 'Позиция первым + Звёздочка + Цвет ника в вкладке Лавки'}, {_ic_flt, 'Фильтры по цене и тренду в таблице Рынка'}, {_ic_tag, 'Теги на товары: Смотреть / Не брать / Избранное'}, {_ic_eye, 'Теги видны в Чужих лавках и АРЗ Базе'}, } imgui.Spacing() for _, fv in ipairs(feats) do imgui.TextColored(gold, fv[1]..' ') imgui.SameLine(0,4*d_pm) imgui.TextColored(imgui.ImVec4(0.88,0.88,0.88,1), _cyr5f(fv[2])) imgui.Spacing() end imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextColored(imgui.ImVec4(0.4,0.4,0.4,1), _ic_shield..' ') imgui.SameLine(0,5*d_pm) imgui.TextColored(imgui.ImVec4(0.45,0.45,0.45,1), _cyr5f('Авторизация работает на инфраструктуре Google Cloud')) imgui.EndChild() end end end if _G.mh_tab == 11 then do if not _G.mh_calc then _G.mh_calc = { expr = '', result = nil, err = false, fresh = false, } end local calc = _G.mh_calc function calc_append(v) if calc.fresh then if v:match('[%+%-%*/]') then calc.expr = tostring(calc.result or '0') .. v else calc.expr = v end calc.fresh = false; calc.err = false else if calc.err then calc.expr = v; calc.err = false else calc.expr = calc.expr .. v end end end function calc_eval() if calc.expr == '' then return end local s = calc.expr:gsub(',', '.') local fn = loadstring('return ' .. s) if fn then local ok, res = pcall(fn) if ok and type(res) == 'number' then calc.result = res calc.err = false calc.fresh = true else calc.err = true; calc.fresh = false end else calc.err = true; calc.fresh = false end end function calc_back() if calc.fresh then calc.expr = tostring(calc.result or ''); calc.fresh = false end if calc.err then calc.expr = ''; calc.err = false; return end calc.expr = calc.expr:sub(1, -2) end function calc_clear() calc.expr = ''; calc.result = nil; calc.err = false; calc.fresh = false end function _fmt_calc_disp(expr) return (expr:gsub('%d+', function(n) if #n <= 3 then return n end return n:reverse():gsub('(%d%d%d)', '%1.'):reverse():gsub('^%.', '') end)) end local display_line1 = calc.expr ~= '' and _cyr5f(_fmt_calc_disp(calc.expr)) or ' ' local display_line2 if calc.err then display_line2 = _cyr5f('\xce\xf8\xe8\xe1\xea\xe0') elseif calc.result ~= nil then local r = calc.result if r == math.floor(r) then local _rs = tostring(math.floor(r)):reverse():gsub('(%d%d%d)', '%1.'):reverse():gsub('^%.', '') display_line2 = _cyr5f('= ' .. _rs) else display_line2 = _cyr5f('= ' .. string.format('%.4f', r):gsub('0+$',''):gsub('%.$','')) end else display_line2 = ' ' end local calc_h = imgui.GetWindowHeight() - 55*d if imgui.BeginChild('##calc_wrap', imgui.ImVec2(-1, calc_h), false) then _dpn1w() imgui.Spacing() local cw = imgui.GetWindowContentRegionWidth() imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.05, 0.05, 0.08, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Border, imgui.ImVec4(ar*0.6, ag*0.6, ab*0.6, 0.8)) if imgui.BeginChild('##calc_display', imgui.ImVec2(-1, 70*d), true) then imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.55, 0.55, 0.6, 1)) imgui.SetCursorPosY(imgui.GetCursorPosY() + 4*d) imgui.SetCursorPosX(8*d) imgui.Text(display_line1) imgui.PopStyleColor() local col2 = calc.err and imgui.ImVec4(1,0.35,0.35,1) or (calc.fresh and imgui.ImVec4(0.3,0.95,0.5,1) or imgui.ImVec4(ar,ag,ab,1)) imgui.PushStyleColor(imgui.Col.Text, col2) imgui.SetCursorPosX(8*d) imgui.Text(display_line2) imgui.PopStyleColor() imgui.EndChild() end imgui.PopStyleColor(2) imgui.Spacing(); imgui.Separator(); imgui.Spacing() local gap = 6*d local cols = 4 local btn_w = (cw - gap*(cols-1)) / cols local btn_h = 52*d local ac = imgui.ImVec4(ar, ag, ab, 1) local ac_dim = imgui.ImVec4(ar*0.6, ag*0.6, ab*0.6, 1) local red = imgui.ImVec4(0.75, 0.18, 0.18, 1) local dark = imgui.ImVec4(0.12, 0.12, 0.16, 1) local function cbtn(label, on_click, col_btn, col_txt) col_btn = col_btn or dark col_txt = col_txt or imgui.ImVec4(0.9,0.9,0.9,1) imgui.PushStyleColor(imgui.Col.Button, col_btn) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(col_btn.x+0.1, col_btn.y+0.1, col_btn.z+0.1, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(col_btn.x+0.2, col_btn.y+0.2, col_btn.z+0.2, 1)) imgui.PushStyleColor(imgui.Col.Text, col_txt) if imgui.Button(label .. '##cb', imgui.ImVec2(btn_w, btn_h)) then on_click() end imgui.PopStyleColor(4) end cbtn(_ic_trash, calc_clear, red) imgui.SameLine(0, gap) cbtn(_ic_al .. ' del', calc_back, imgui.ImVec4(0.3,0.15,0.1,1)) imgui.SameLine(0, gap) cbtn(' / ', function() calc_append('/') end, ac_dim, ac) imgui.SameLine(0, gap) cbtn(_ic_x, function() calc_append('*') end, ac_dim, ac) imgui.Spacing() cbtn('7', function() calc_append('7') end) imgui.SameLine(0, gap) cbtn('8', function() calc_append('8') end) imgui.SameLine(0, gap) cbtn('9', function() calc_append('9') end) imgui.SameLine(0, gap) cbtn(_ic_min, function() calc_append('-') end, ac_dim, ac) imgui.Spacing() cbtn('4', function() calc_append('4') end) imgui.SameLine(0, gap) cbtn('5', function() calc_append('5') end) imgui.SameLine(0, gap) cbtn('6', function() calc_append('6') end) imgui.SameLine(0, gap) cbtn(' + ', function() calc_append('+') end, ac_dim, ac) imgui.Spacing() cbtn('1', function() calc_append('1') end) imgui.SameLine(0, gap) cbtn('2', function() calc_append('2') end) imgui.SameLine(0, gap) cbtn('3', function() calc_append('3') end) imgui.SameLine(0, gap) local eq_c = imgui.ImVec4(ar*0.9, ag*0.9, ab*0.9, 1) cbtn(' = ', calc_eval, eq_c, imgui.ImVec4(0.04,0.04,0.03,1)) imgui.Spacing() cbtn('0', function() calc_append('0') end) imgui.SameLine(0, gap) cbtn(',', function() calc_append('.') end) imgui.SameLine(0, gap) cbtn('( )', function() local e = calc.fresh and '' or calc.expr local opens = 0; for _ in e:gmatch('%(') do opens=opens+1 end local closes = 0; for _ in e:gmatch('%)') do closes=closes+1 end if opens <= closes then calc_append('(') else calc_append(')') end end) imgui.SameLine(0, gap) cbtn(' % ', function() local e = calc.fresh and tostring(calc.result or '') or calc.expr if e == '' then return end local last_op_pos, last_op = nil, nil local depth = 0 for i = #e, 1, -1 do local c = e:sub(i,i) if c == ')' then depth = depth + 1 elseif c == '(' then depth = depth - 1 elseif depth == 0 and (c == '+' or c == '-') and i > 1 then last_op_pos = i; last_op = c; break end end if last_op_pos then local base = e:sub(1, last_op_pos - 1) local pct_str = e:sub(last_op_pos + 1) local new_e = base .. last_op .. '(' .. base .. '*' .. pct_str .. '/100)' calc.expr = new_e; calc.fresh = false; calc.err = false else calc.expr = '(' .. e .. '/100)'; calc.fresh = false; calc.err = false end calc_eval() end, ac_dim, ac) imgui.Spacing(); imgui.Separator(); imgui.Spacing() if calc.result ~= nil and not calc.err then imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.55, 0.55, 0.6, 1)) imgui.Text(_cyr5f('\xd0\xe5\xe7\xf3\xeb\xfc\xf2\xe0\xf2: ')) imgui.SameLine(0, 4*d) imgui.PopStyleColor() local _rv = calc.result local res_str if _rv == math.floor(_rv) then res_str = tostring(math.floor(_rv)):reverse():gsub('(%d%d%d)', '%1.'):reverse():gsub('^%.', '') else res_str = tostring(_rv) end imgui.TextColored(imgui.ImVec4(ar,ag,ab,1), res_str) imgui.SameLine(0, 8*d) if imgui.Button(_ic_save .. '##calc_copy', imgui.ImVec2(0,0)) then setClipboardText(res_str) end end imgui.EndChild() end end end if _G.mh_tab == 6 then do local vid_h = imgui.GetWindowHeight() - 55*d if imgui.BeginChild('##vid_wrap', imgui.ImVec2(-1, vid_h), false) then _dpn1w() local cw = imgui.GetWindowContentRegionWidth() local half = (cw - 8*d) * 0.5 imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f(' ЦВЕТА')); imgui.Separator(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Акцентный цвет:')) imgui.PushItemWidth(-1) if imgui.ColorEdit3('##acc', accent_color) then settings.interface.accent_r=accent_color[0]; settings.interface.accent_g=accent_color[1] settings.interface.accent_b=accent_color[2]; _wfn7p(); _fwb3h() end imgui.PopItemWidth(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Цвет текста:')) if not _G.vid_text_col then _G.vid_text_col = imgui.new.float[3]( settings.interface.text_r or 0.93, settings.interface.text_g or 0.88, settings.interface.text_b or 0.78 ) end imgui.PushItemWidth(-1) if imgui.ColorEdit3('##textcol', _G.vid_text_col) then settings.interface.text_r=_G.vid_text_col[0] settings.interface.text_g=_G.vid_text_col[1] settings.interface.text_b=_G.vid_text_col[2] _wfn7p(); _fwb3h() end imgui.PopItemWidth(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Цвет фона окна:')) if not _G.vid_bg_col then local bg = settings.interface.bg_brightness or 0.06 _G.vid_bg_col = imgui.new.float[3](bg, bg*0.95, bg*0.80) end imgui.PushItemWidth(-1) if imgui.ColorEdit3('##bgcol', _G.vid_bg_col) then settings.interface.bg_r = _G.vid_bg_col[0] settings.interface.bg_g = _G.vid_bg_col[1] settings.interface.bg_b = _G.vid_bg_col[2] settings.interface.bg_brightness = _G.vid_bg_col[0] _wfn7p(); _fwb3h() end imgui.PopItemWidth(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Цвет границы:')) if not _G.vid_border_col then _G.vid_border_col = imgui.new.float[3]( settings.interface.border_r or (ar*0.70), settings.interface.border_g or (ag*0.70), settings.interface.border_b or 0 ) end imgui.PushItemWidth(-1) if imgui.ColorEdit3('##bordercol', _G.vid_border_col) then settings.interface.border_r=_G.vid_border_col[0] settings.interface.border_g=_G.vid_border_col[1] settings.interface.border_b=_G.vid_border_col[2] _wfn7p(); _fwb3h() end imgui.PopItemWidth(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Цвет продаж (оверлей):')) if not _G.vid_sell_col then _G.vid_sell_col = imgui.new.float[3]( settings.overlay.sell_r or 0.3, settings.overlay.sell_g or 0.9, settings.overlay.sell_b or 0.3 ) end imgui.PushItemWidth(-1) if imgui.ColorEdit3('##sellcol', _G.vid_sell_col) then settings.overlay.sell_r=_G.vid_sell_col[0] settings.overlay.sell_g=_G.vid_sell_col[1] settings.overlay.sell_b=_G.vid_sell_col[2] _wfn7p() end imgui.PopItemWidth(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Цвет покупок (оверлей):')) if not _G.vid_buy_col then _G.vid_buy_col = imgui.new.float[3]( settings.overlay.buy_r or 0.3, settings.overlay.buy_g or 0.6, settings.overlay.buy_b or 1.0 ) end imgui.PushItemWidth(-1) if imgui.ColorEdit3('##buycol', _G.vid_buy_col) then settings.overlay.buy_r=_G.vid_buy_col[0] settings.overlay.buy_g=_G.vid_buy_col[1] settings.overlay.buy_b=_G.vid_buy_col[2] _wfn7p() end imgui.PopItemWidth(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Цвет цен / акцента в таблицах:')) if not _G.vid_log_price_col then _G.vid_log_price_col = imgui.new.float[3]( settings.overlay.log_price_r or 1.0, settings.overlay.log_price_g or 0.85, settings.overlay.log_price_b or 0.2 ) end imgui.PushItemWidth(-1) if imgui.ColorEdit3('##logpricecol', _G.vid_log_price_col) then settings.overlay.log_price_r = _G.vid_log_price_col[0] settings.overlay.log_price_g = _G.vid_log_price_col[1] settings.overlay.log_price_b = _G.vid_log_price_col[2] _G.vid_log_price_col = nil _wfn7p() end imgui.PopItemWidth(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Цвет кнопок продажи:')) if not _G.vid_sell_btn_col then _G.vid_sell_btn_col = imgui.new.float[3]( settings.interface.sell_btn_r or 0.10, settings.interface.sell_btn_g or 0.45, settings.interface.sell_btn_b or 0.10 ) end imgui.PushItemWidth(-1) if imgui.ColorEdit3('##sellbtncol', _G.vid_sell_btn_col) then settings.interface.sell_btn_r = _G.vid_sell_btn_col[0] settings.interface.sell_btn_g = _G.vid_sell_btn_col[1] settings.interface.sell_btn_b = _G.vid_sell_btn_col[2] _wfn7p(); _fwb3h() end imgui.PopItemWidth(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Цвет кнопок скупки:')) if not _G.vid_buy_btn_col then _G.vid_buy_btn_col = imgui.new.float[3]( settings.interface.buy_btn_r or 0.00, settings.interface.buy_btn_g or 0.28, settings.interface.buy_btn_b or 0.50 ) end imgui.PushItemWidth(-1) if imgui.ColorEdit3('##buybtncol', _G.vid_buy_btn_col) then settings.interface.buy_btn_r = _G.vid_buy_btn_col[0] settings.interface.buy_btn_g = _G.vid_buy_btn_col[1] settings.interface.buy_btn_b = _G.vid_buy_btn_col[2] _wfn7p(); _fwb3h() end imgui.PopItemWidth(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Цвет обычных кнопок:')) if not _G.vid_btn_col then local _bg = settings.interface.bg_brightness or 0.06 _G.vid_btn_col = imgui.new.float[3]( settings.interface.btn_r or (_bg+0.08), settings.interface.btn_g or (_bg+0.07), settings.interface.btn_b or (_bg+0.04) ) end local _half_btn = (cw - 8*d) * 0.75 imgui.PushItemWidth(_half_btn) if imgui.ColorEdit3('##btncol', _G.vid_btn_col) then settings.interface.btn_r = _G.vid_btn_col[0] settings.interface.btn_g = _G.vid_btn_col[1] settings.interface.btn_b = _G.vid_btn_col[2] _wfn7p(); _fwb3h() end imgui.PopItemWidth() imgui.SameLine(0, 8*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(ar*0.3,ag*0.3,ab*0.3,0.9)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(ar*0.5,ag*0.5,ab*0.5,1)) if imgui.Button(_cyr5f(' Сброс ##btnreset'), imgui.ImVec2(-1, 0)) then settings.interface.btn_r = nil settings.interface.btn_g = nil settings.interface.btn_b = nil _G.vid_btn_col = nil _wfn7p(); _fwb3h() end imgui.PopStyleColor(2) imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f(' ТЕМЫ')); imgui.Separator(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Выберите готовую тему или настройте вручную ниже:')) imgui.Spacing() local _themes = _G._mh_themes or {} local _tw = (cw - (#_themes - 1) * 5 * d) / #_themes for _ti, _th in ipairs(_themes) do if _ti > 1 then imgui.SameLine(0, 5*d) end local _tar = _th.accent[1]; local _tag = _th.accent[2]; local _tab = _th.accent[3] local _tbg = _th.bg[1] local _cur_r = settings.interface.accent_r or 1 local _cur_g = settings.interface.accent_g or 0.65 local _cur_b = settings.interface.accent_b or 0 local _is_cur = math.abs(_cur_r - _tar) < 0.05 and math.abs(_cur_g - _tag) < 0.05 and math.abs(_cur_b - _tab) < 0.05 local _bclr = _is_cur and imgui.ImVec4(_tbg + _tar*0.28, _tbg + _tag*0.28, _tbg + _tab*0.28, 1) or imgui.ImVec4(_tbg + 0.04, _tbg + 0.04, _tbg + 0.04, 1) imgui.PushStyleColor(imgui.Col.Button, _bclr) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(_tbg + _tar*0.40, _tbg + _tag*0.40, _tbg + _tab*0.40, 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, imgui.ImVec4(_tbg + _tar*0.60, _tbg + _tag*0.60, _tbg + _tab*0.60, 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(_tar, _tag, _tab, 1)) local _tlbl = (_is_cur and _ic_chk..' ' or '') .. _cyr5f(_th.name) .. '##theme' .. _ti if imgui.Button(_tlbl, imgui.ImVec2(_tw, 32*d)) then if _G._mh_apply_theme then _G._mh_apply_theme(_th) end end imgui.PopStyleColor(4) end imgui.Spacing() imgui.TextDisabled(_cyr5f(' Цветовые акценты:')) local color_presets = { {_cyr5f('Оранж'), 1,.65,0}, {_cyr5f('Синий'), .2,.5,1}, {_cyr5f('Зелён'), .2,.8,.3}, {_cyr5f('Красн'), .9,.2,.2}, {_cyr5f('Фиол'), .6,.3,.9}, {_cyr5f('Белый'), .85,.85,.85}, } local pw = (cw - 5*5*d) / #color_presets for pi, pr in ipairs(color_presets) do if pi > 1 then imgui.SameLine(0,5*d) end imgui.PushStyleColor(imgui.Col.Button, _mh_bc(pr[2]*.5,pr[3]*.5,pr[4]*.5,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(pr[2]*.7,pr[3]*.7,pr[4]*.7, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(pr[2],pr[3],pr[4],1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(pr[2],pr[3],pr[4],1)) if imgui.Button(pr[1]..'##cpr'..pi, imgui.ImVec2(pw,24*d)) then settings.interface.accent_r=pr[2]; settings.interface.accent_g=pr[3]; settings.interface.accent_b=pr[4] accent_color[0]=pr[2]; accent_color[1]=pr[3]; accent_color[2]=pr[4] _G.vid_border_col=nil; _G.vid_text_col=nil _wfn7p(); _fwb3h() end imgui.PopStyleColor(4) end imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f(' ПАРАМЕТРЫ')); imgui.Separator(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Яркость фона:')); imgui.SameLine(); imgui.PushItemWidth(-1) if imgui.SliderFloat('##bg_v', sl.bg_bright, 0.01, 0.35, '%.2f') then settings.interface.bg_brightness=sl.bg_bright[0]; _G.vid_bg_col=nil; _wfn7p(); _fwb3h() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Прозрачность окна:')); imgui.SameLine(); imgui.PushItemWidth(-1) if imgui.SliderFloat('##alpha_v', sl.window_alpha, 0.3, 1.0, '%.2f') then settings.interface.window_alpha=sl.window_alpha[0]; _wfn7p(); _fwb3h() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Рруглость углов:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G.sl_rounding then _G.sl_rounding = imgui.new.float[1](settings.interface.rounding or 4.0) end if imgui.SliderFloat('##rounding', _G.sl_rounding, 0.0, 12.0, '%.1f') then settings.interface.rounding = _G.sl_rounding[0]; _wfn7p(); _fwb3h() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Толщина рамки:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G.sl_border then _G.sl_border = imgui.new.float[1](settings.interface.border_size or 1.0) end if imgui.SliderFloat('##bordersize', _G.sl_border, 0.0, 3.0, '%.1f') then settings.interface.border_size = _G.sl_border[0]; _wfn7p(); _fwb3h() end imgui.PopItemWidth() imgui.Spacing(); imgui.Separator(); imgui.Spacing() if not _G._vid_free_move_cb then _G._vid_free_move_cb = imgui.new.bool(settings.interface.free_move or false) end _G._vid_free_move_cb[0] = (settings.interface.free_move == true) if imgui.Checkbox(_cyr5f('Перемещать окно за любую точку##free_move'), _G._vid_free_move_cb) then settings.interface.free_move = _G._vid_free_move_cb[0] _wfn7p() end if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Вкл: окно перемещается за любую точку\nВыкл: только за заголовок (по умолчанию)')) end imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f(' МАШТАБ (DPI)')); imgui.Separator(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Текущий: ' .. string.format('%.2f', settings.general.custom_dpi or 1.0))) imgui.PushItemWidth(-1) if imgui.SliderFloat('##dpi_sl', sl.dpi, 0.5, 3.0, '%.2f') then settings.general.custom_dpi=sl.dpi[0]; _wfn7p(); _fwb3h() if _mh_apply_toast_settings then _mh_apply_toast_settings() end end imgui.PopItemWidth() imgui.Spacing() imgui.TextDisabled(_cyr5f(' Быстрый масштаб:')) local _scale_btns = { {_cyr5f('XS (0.6)'), 0.60}, {_cyr5f('S (0.8)'), 0.80}, {_cyr5f('M (1.0)'), 1.00}, {_cyr5f('L (1.2)'), 1.20}, {_cyr5f('XL (1.5)'), 1.50}, } local _sbw = (imgui.GetWindowContentRegionWidth() - 4*d*4) / #_scale_btns for _si, _sb in ipairs(_scale_btns) do if _si > 1 then imgui.SameLine(0, 4*d) end local _cur_dpi = settings.general.custom_dpi or 1.0 local _is_cur = math.abs(_cur_dpi - _sb[2]) < 0.05 local _bc = _is_cur and imgui.ImVec4(ar*0.6, ag*0.6, ab*0.2, 1) or imgui.ImVec4(0.12, 0.12, 0.12, 1) imgui.PushStyleColor(imgui.Col.Button, _bc) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(ar*0.3, ag*0.3, ab*0.1, _G._mh_wa or 1)) if imgui.Button(_sb[1]..'##scl'..tostring(_sb[2]), imgui.ImVec2(_sbw, 26*d)) then settings.general.custom_dpi = _sb[2] sl.dpi[0] = _sb[2] _G._mh_win_reset = true if _mh_apply_toast_settings then _mh_apply_toast_settings() end _wfn7p(); _fwb3h() mh_notify('[MH] {aaffaa}Масштаб: '.._sb[1], 0xffffff) end imgui.PopStyleColor(2) end imgui.Spacing() imgui.PushStyleColor(imgui.Col.Button, _mh_bc((_G._mh_sb_r or 0.10)*0.60,(_G._mh_sb_g or 0.45)*0.60,(_G._mh_sb_b or 0.10)*0.60,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*0.78,sb_g*0.78,sb_b*0.78, _G._mh_wa or 1)) if imgui.Button(_cyr5f('Сбросить масштаб по умолчанию (1.0)##win_reset'), imgui.ImVec2(-1, 26*d)) then settings.general.custom_dpi = 1.0 sl.dpi[0] = 1.0 _G._mh_win_reset = true if _mh_apply_toast_settings then _mh_apply_toast_settings() end _wfn7p(); _fwb3h() mh_notify('[MH] {aaffaa}Масштаб сброшен до 1.0', 0xffffff) end imgui.PopStyleColor(2) imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f(' РАЗМЕР ШРИФТА')); imgui.Separator(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Текущий: ' .. string.format('%.2f', settings.interface.font_scale or 1.0) .. 'x')) imgui.PushItemWidth(-1) if imgui.SliderFloat('##font_scale_sl', sl.font_scale, 0.5, 2.0, '%.2f') then settings.interface.font_scale = sl.font_scale[0] imgui.GetIO().FontGlobalScale = sl.font_scale[0] _wfn7p() end imgui.PopItemWidth() imgui.Spacing() imgui.TextDisabled(_cyr5f(' Быстрый размер:')) local _fsc_btns = { {_cyr5f('XS (0.7)'), 0.70}, {_cyr5f('S (0.85)'), 0.85}, {_cyr5f('M (1.0)'), 1.00}, {_cyr5f('L (1.2)'), 1.20}, {_cyr5f('XL (1.5)'), 1.50}, } local _fscw = (imgui.GetWindowContentRegionWidth() - 4*d*4) / #_fsc_btns for _fi, _fb in ipairs(_fsc_btns) do if _fi > 1 then imgui.SameLine(0, 4*d) end local _cur_fs = settings.interface.font_scale or 1.0 local _is_fs_cur = math.abs(_cur_fs - _fb[2]) < 0.05 imgui.PushStyleColor(imgui.Col.Button, _is_fs_cur and imgui.ImVec4(ar*0.6, ag*0.6, ab*0.2, 1) or imgui.ImVec4(0.12, 0.12, 0.12, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(ar*0.3, ag*0.3, ab*0.1, _G._mh_wa or 1)) if imgui.Button(_fb[1]..'##fsc'..tostring(_fb[2]), imgui.ImVec2(_fscw, 26*d)) then settings.interface.font_scale = _fb[2] sl.font_scale[0] = _fb[2] imgui.GetIO().FontGlobalScale = _fb[2] _wfn7p() mh_notify('[MH] {aaffaa}Шрифт: '.._fb[1], 0xffffff) end imgui.PopStyleColor(2) end imgui.Spacing() imgui.PushStyleColor(imgui.Col.Button, _mh_bc((_G._mh_sb_r or 0.10)*0.60,(_G._mh_sb_g or 0.45)*0.60,(_G._mh_sb_b or 0.10)*0.60,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*0.78,sb_g*0.78,sb_b*0.78, _G._mh_wa or 1)) if imgui.Button(_cyr5f('Сбросить шрифт по умолчанию (1.0)##font_reset'), imgui.ImVec2(-1, 26*d)) then settings.interface.font_scale = 1.0 sl.font_scale[0] = 1.0 imgui.GetIO().FontGlobalScale = 1.0 _wfn7p() mh_notify('[MH] {aaffaa}Шрифт сброшен до 1.0', 0xffffff) end imgui.PopStyleColor(2) imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f(' УВЕДОМЛЕНИЯ (ПЛАШКИ)')); imgui.Separator(); imgui.Spacing() if not settings.toast then settings.toast = {} end if not _G._mh_tc_en then _G._mh_tc_en = imgui.new.bool(settings.toast.enabled ~= false) end _G._mh_tc_en[0] = (settings.toast.enabled ~= false) if imgui.Checkbox(_cyr5f('Показывать всплывающие уведомления##toast_en'), _G._mh_tc_en) then settings.toast.enabled = _G._mh_tc_en[0] _wfn7p(); _mh_apply_toast_settings() end imgui.Spacing() imgui.TextDisabled(_cyr5f(' Расположение на экране:')) do local _pos_bw = (cw - 4*3*d) / 4 local function _pos_btn(lbl, is_cur, on_click) imgui.PushStyleColor(imgui.Col.Button, is_cur and imgui.ImVec4(ar*0.55, ag*0.55, ab*0.55, 1) or imgui.ImVec4(0.14, 0.14, 0.14, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(ar*0.40, ag*0.40, ab*0.40, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, is_cur and imgui.ImVec4(0.04,0.04,0.03,1) or imgui.ImVec4(0.75,0.75,0.75,1)) if imgui.Button(lbl, imgui.ImVec2(_pos_bw, 26*d)) then on_click() end imgui.PopStyleColor(3) end _pos_btn(_cyr5f('Слева##toast_left'), settings.toast.pos_h == 'left', function() settings.toast.pos_h = 'left'; _wfn7p(); _mh_apply_toast_settings() end) imgui.SameLine(0, 4*d) _pos_btn(_cyr5f('Справа##toast_right'), settings.toast.pos_h ~= 'left', function() settings.toast.pos_h = 'right'; _wfn7p(); _mh_apply_toast_settings() end) imgui.SameLine(0, 4*d) _pos_btn(_cyr5f('Сверху##toast_top'), settings.toast.pos_v == 'top', function() settings.toast.pos_v = 'top'; _wfn7p(); _mh_apply_toast_settings() end) imgui.SameLine(0, 4*d) _pos_btn(_cyr5f('Снизу##toast_bottom'), settings.toast.pos_v ~= 'top', function() settings.toast.pos_v = 'bottom'; _wfn7p(); _mh_apply_toast_settings() end) end imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Ширина плашки:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_width then _G._mh_tc_width = imgui.new.float[1](settings.toast.width or 320) end if imgui.SliderFloat('##toast_width', _G._mh_tc_width, 200, 520, '%.0f px') then settings.toast.width = _G._mh_tc_width[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Скругление углов:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_round then _G._mh_tc_round = imgui.new.float[1](settings.toast.corner_radius or 8) end if imgui.SliderFloat('##toast_round', _G._mh_tc_round, 0, 20, '%.0f') then settings.toast.corner_radius = _G._mh_tc_round[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Толщина полосы-индикатора:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_accent then _G._mh_tc_accent = imgui.new.float[1](settings.toast.accent_width or 4) end if imgui.SliderFloat('##toast_accent', _G._mh_tc_accent, 0, 10, '%.0f') then settings.toast.accent_width = _G._mh_tc_accent[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Внутренний отступ (X):')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_padx then _G._mh_tc_padx = imgui.new.float[1](settings.toast.padding_x or 14) end if imgui.SliderFloat('##toast_padx', _G._mh_tc_padx, 4, 30, '%.0f') then settings.toast.padding_x = _G._mh_tc_padx[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Внутренний отступ (Y):')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_pady then _G._mh_tc_pady = imgui.new.float[1](settings.toast.padding_y or 10) end if imgui.SliderFloat('##toast_pady', _G._mh_tc_pady, 4, 30, '%.0f') then settings.toast.padding_y = _G._mh_tc_pady[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Расстояние между плашками:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_spacing then _G._mh_tc_spacing = imgui.new.float[1](settings.toast.spacing or 8) end if imgui.SliderFloat('##toast_spacing', _G._mh_tc_spacing, 0, 24, '%.0f') then settings.toast.spacing = _G._mh_tc_spacing[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Отступ от края экрана (X):')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_mx then _G._mh_tc_mx = imgui.new.float[1](settings.toast.margin_x or 18) end if imgui.SliderFloat('##toast_mx', _G._mh_tc_mx, 0, 60, '%.0f') then settings.toast.margin_x = _G._mh_tc_mx[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Отступ от края экрана (Y):')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_my then _G._mh_tc_my = imgui.new.float[1](settings.toast.margin_y or 18) end if imgui.SliderFloat('##toast_my', _G._mh_tc_my, 0, 60, '%.0f') then settings.toast.margin_y = _G._mh_tc_my[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Длительность показа (сек):')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_dur then _G._mh_tc_dur = imgui.new.float[1](settings.toast.duration or 4.0) end if imgui.SliderFloat('##toast_dur', _G._mh_tc_dur, 1.0, 15.0, '%.1f') then settings.toast.duration = _G._mh_tc_dur[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Скорость анимации:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_anim then _G._mh_tc_anim = imgui.new.float[1](settings.toast.anim_speed or 10.0) end if imgui.SliderFloat('##toast_anim', _G._mh_tc_anim, 2.0, 30.0, '%.1f') then settings.toast.anim_speed = _G._mh_tc_anim[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Макс. плашек одновременно:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_maxv then _G._mh_tc_maxv = imgui.new.float[1](settings.toast.max_visible or 5) end if imgui.SliderFloat('##toast_maxv', _G._mh_tc_maxv, 1, 10, '%.0f') then settings.toast.max_visible = math.floor(_G._mh_tc_maxv[0]); _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Масштаб шрифта плашек:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_fscale then _G._mh_tc_fscale = imgui.new.float[1](settings.toast.font_scale or 1.0) end if imgui.SliderFloat('##toast_fscale', _G._mh_tc_fscale, 0.5, 2.0, '%.2f') then settings.toast.font_scale = _G._mh_tc_fscale[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Цвет фона плашки:')) if not _G._mh_tc_bg then _G._mh_tc_bg = imgui.new.float[3](settings.toast.bg_r or 0.08, settings.toast.bg_g or 0.08, settings.toast.bg_b or 0.10) end imgui.PushItemWidth(-1) if imgui.ColorEdit3('##toast_bgcol', _G._mh_tc_bg) then settings.toast.bg_r = _G._mh_tc_bg[0]; settings.toast.bg_g = _G._mh_tc_bg[1]; settings.toast.bg_b = _G._mh_tc_bg[2] _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Прозрачность фона:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_bga then _G._mh_tc_bga = imgui.new.float[1](settings.toast.bg_a or 0.90) end if imgui.SliderFloat('##toast_bga', _G._mh_tc_bga, 0.10, 1.00, '%.2f') then settings.toast.bg_a = _G._mh_tc_bga[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Цвет текста:')) if not _G._mh_tc_text then _G._mh_tc_text = imgui.new.float[3](settings.toast.text_r or 0.94, settings.toast.text_g or 0.94, settings.toast.text_b or 0.96) end imgui.PushItemWidth(-1) if imgui.ColorEdit3('##toast_textcol', _G._mh_tc_text) then settings.toast.text_r = _G._mh_tc_text[0]; settings.toast.text_g = _G._mh_tc_text[1]; settings.toast.text_b = _G._mh_tc_text[2] _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth(); imgui.Spacing() if not _G._mh_tc_border_en then _G._mh_tc_border_en = imgui.new.bool(settings.toast.border_enabled == true) end _G._mh_tc_border_en[0] = (settings.toast.border_enabled == true) if imgui.Checkbox(_cyr5f('Показывать рамку по краю плашки##toast_border_en'), _G._mh_tc_border_en) then settings.toast.border_enabled = _G._mh_tc_border_en[0] _wfn7p(); _mh_apply_toast_settings() end if settings.toast.border_enabled then imgui.TextDisabled(_cyr5f(' Цвет рамки:')) if not _G._mh_tc_bcol then _G._mh_tc_bcol = imgui.new.float[3](settings.toast.border_r or 1, settings.toast.border_g or 1, settings.toast.border_b or 1) end imgui.PushItemWidth(-1) if imgui.ColorEdit3('##toast_bordercol', _G._mh_tc_bcol) then settings.toast.border_r = _G._mh_tc_bcol[0]; settings.toast.border_g = _G._mh_tc_bcol[1]; settings.toast.border_b = _G._mh_tc_bcol[2] _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Прозрачность рамки:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_ba then _G._mh_tc_ba = imgui.new.float[1](settings.toast.border_a or 0.25) end if imgui.SliderFloat('##toast_ba', _G._mh_tc_ba, 0.05, 1.0, '%.2f') then settings.toast.border_a = _G._mh_tc_ba[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Толщина рамки:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G._mh_tc_bsize then _G._mh_tc_bsize = imgui.new.float[1](settings.toast.border_size or 1.0) end if imgui.SliderFloat('##toast_bsize', _G._mh_tc_bsize, 0.5, 4.0, '%.1f') then settings.toast.border_size = _G._mh_tc_bsize[0]; _wfn7p(); _mh_apply_toast_settings() end imgui.PopItemWidth() end imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Тест плашек:')) do local _tb_w = (cw - 4*3*d) / 4 imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.20, 0.45, 0.85, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.30, 0.55, 0.95, 1)) if imgui.Button(_cyr5f('Инфо##toast_test_info'), imgui.ImVec2(_tb_w, 26*d)) then _mh_toast_test('info') end imgui.PopStyleColor(2) imgui.SameLine(0, 4*d) imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.18, 0.65, 0.32, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.25, 0.75, 0.40, 1)) if imgui.Button(_cyr5f('Успех##toast_test_success'), imgui.ImVec2(_tb_w, 26*d)) then _mh_toast_test('success') end imgui.PopStyleColor(2) imgui.SameLine(0, 4*d) imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.80, 0.55, 0.10, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.90, 0.65, 0.15, 1)) if imgui.Button(_cyr5f('Внимание##toast_test_warn'), imgui.ImVec2(_tb_w, 26*d)) then _mh_toast_test('warning') end imgui.PopStyleColor(2) imgui.SameLine(0, 4*d) imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.75, 0.20, 0.20, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.85, 0.28, 0.28, 1)) if imgui.Button(_cyr5f('Ошибка##toast_test_error'), imgui.ImVec2(_tb_w, 26*d)) then _mh_toast_test('error') end imgui.PopStyleColor(2) end imgui.Spacing() imgui.PushStyleColor(imgui.Col.Button, _mh_bc((_G._mh_sb_r or 0.10)*0.60,(_G._mh_sb_g or 0.45)*0.60,(_G._mh_sb_b or 0.10)*0.60,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*0.78,sb_g*0.78,sb_b*0.78, _G._mh_wa or 1)) if imgui.Button(_cyr5f('Показать все типы сразу##toast_test_all'), imgui.ImVec2(-1, 26*d)) then _mh_toast_test('all') end imgui.PopStyleColor(2) imgui.Spacing() imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.4, 0.1, 0.05, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.6, 0.15, 0.08, _G._mh_wa or 1)) if imgui.Button(_cyr5f('Сбросить стиль плашек по умолчанию##toast_reset'), imgui.ImVec2(-1, 26*d)) then settings.toast = { enabled = true, width = 320, padding_x = 14, padding_y = 10, accent_width = 4, corner_radius = 8, spacing = 8, margin_x = 18, margin_y = 18, duration = 4.0, anim_speed = 10.0, max_visible = 5, pos_h = 'right', pos_v = 'bottom', bg_r = 0.08, bg_g = 0.08, bg_b = 0.10, bg_a = 0.90, text_r = 0.94, text_g = 0.94, text_b = 0.96, text_a = 1.00, title_r = 0.72, title_g = 0.72, title_b = 0.78, title_a = 1.00, border_enabled = false, border_r = 1.0, border_g = 1.0, border_b = 1.0, border_a = 0.25, border_size = 1.0, font_scale = 1.0, } _G._mh_tc_en=nil; _G._mh_tc_width=nil; _G._mh_tc_round=nil; _G._mh_tc_accent=nil _G._mh_tc_padx=nil; _G._mh_tc_pady=nil; _G._mh_tc_spacing=nil _G._mh_tc_mx=nil; _G._mh_tc_my=nil; _G._mh_tc_dur=nil; _G._mh_tc_anim=nil _G._mh_tc_maxv=nil; _G._mh_tc_fscale=nil _G._mh_tc_bg=nil; _G._mh_tc_bga=nil; _G._mh_tc_text=nil _G._mh_tc_border_en=nil; _G._mh_tc_bcol=nil; _G._mh_tc_ba=nil; _G._mh_tc_bsize=nil _wfn7p(); _mh_apply_toast_settings() mh_notify('[MH] {aaffaa}Стиль плашек сброшен', 0xffffff) end imgui.PopStyleColor(2) imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f(' РАЗМЕР ОКНА')); imgui.Separator(); imgui.Spacing() if not _G.sl_win_w then local _sw = settings.mh_win _G.sl_win_w = imgui.new.float[1]((_sw and _sw.w and _sw.w/d > 200) and _sw.w/d or 900) end imgui.TextDisabled(_cyr5f(' Ширина:')); imgui.SameLine(); imgui.PushItemWidth(-1) if imgui.SliderFloat('##win_w', _G.sl_win_w, 400, 1200, '%.0f') then local _sw = settings.mh_win or {} _sw.w = _G.sl_win_w[0] * d if not _sw.h then _sw.h = 520*d end if not _sw.x then _sw.x = sizeX/2 - _sw.w/2 end if not _sw.y then _sw.y = sizeY/2 - _sw.h/2 end settings.mh_win = _sw _G._mh_win_reset = true; _wfn7p() end imgui.PopItemWidth() if not _G.sl_win_h then local _sw = settings.mh_win _G.sl_win_h = imgui.new.float[1]((_sw and _sw.h and _sw.h/d > 150) and _sw.h/d or 520) end imgui.TextDisabled(_cyr5f(' Высота:')); imgui.SameLine(); imgui.PushItemWidth(-1) if imgui.SliderFloat('##win_h', _G.sl_win_h, 300, 900, '%.0f') then local _sw = settings.mh_win or {} _sw.h = _G.sl_win_h[0] * d if not _sw.w then _sw.w = 900*d end if not _sw.x then _sw.x = sizeX/2 - _sw.w/2 end if not _sw.y then _sw.y = sizeY/2 - _sw.h/2 end settings.mh_win = _sw _G._mh_win_reset = true; _wfn7p() end imgui.PopItemWidth() imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f(' ПЛАВАЮЩИЙ ЛОГ')); imgui.Separator(); imgui.Spacing() local ov_en = imgui.new.bool(settings.overlay and settings.overlay.enabled or false) if imgui.Checkbox(_cyr5f('Чуть лог включён##ovtog'), ov_en) then if not settings.overlay then settings.overlay = {} end settings.overlay.enabled = ov_en[0]; _wfn7p() end imgui.Spacing() imgui.TextDisabled(_cyr5f(' Строк в оверлее:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G.sl_ovlines then _G.sl_ovlines = imgui.new.float[1](settings.overlay and settings.overlay.lines or 8) end if imgui.SliderFloat('##ovlines', _G.sl_ovlines, 3, 20, '%.0f') then if not settings.overlay then settings.overlay = {} end settings.overlay.lines = math.floor(_G.sl_ovlines[0]); _wfn7p() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f(' Прозрачность оверлея:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G.sl_ovalpha then _G.sl_ovalpha = imgui.new.float[1](settings.overlay and settings.overlay.alpha or 0.6) end if imgui.SliderFloat('##ovalpha', _G.sl_ovalpha, 0.1, 1.0, '%.2f') then if not settings.overlay then settings.overlay = {} end settings.overlay.alpha = _G.sl_ovalpha[0]; _wfn7p() end imgui.PopItemWidth() imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), _cyr5f(' ВИДИМОСТЬ ВКЛАДОК')) imgui.Separator(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Отключите ненужные вкладки:')) imgui.Spacing() if not settings.tabs_visible then settings.tabs_visible = {} end local _tv = settings.tabs_visible local _tab_vis_list = { {id=1, label='РЫНОК'}, {id=2, label='ЛАВКИ'}, {id=3, label='ПРОДАЖА'}, {id=4, label='СКУПКА'}, {id=5, label='ЛОГ'}, {id=11, label='КАЛЬК.'}, {id=7, label='ПИАР'}, {id=8, label='ЛОВЛЯ'}, {id=12, label='РЕЙТИНГ'}, } local _tv_cw = imgui.GetContentRegionAvail().x local _tv_col_w = (_tv_cw - 8*d) / 2 imgui.Columns(2,'##tabvis',false) imgui.SetColumnWidth(0,_tv_col_w); imgui.SetColumnWidth(1,_tv_col_w) for _tvi, _titem in ipairs(_tab_vis_list) do local _tv_key = 'tab_'..tostring(_titem.id) local _tv_cur = _tv[_tv_key]; if _tv_cur == nil then _tv_cur = true end if not _G['_tvcb'.._tv_key] then _G['_tvcb'.._tv_key] = imgui.new.bool(_tv_cur) end _G['_tvcb'.._tv_key][0] = _tv_cur if imgui.Checkbox(_cyr5f(_titem.label)..'##tvis'.._titem.id, _G['_tvcb'.._tv_key]) then _tv[_tv_key] = _G['_tvcb'.._tv_key][0]; _wfn7p() end imgui.NextColumn() end imgui.Columns(1) imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar,ag,ab,1), _cyr5f('ПОЛЗУНКИ')); imgui.Separator(); imgui.Spacing() imgui.TextDisabled(_cyr5f('Ширина маркера:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G.sl_grab_w then _G.sl_grab_w = imgui.new.float[1](settings.interface.grab_w or 12) end if imgui.SliderFloat('##grab_w', _G.sl_grab_w, 4, 40, '%.0f px') then settings.interface.grab_w = _G.sl_grab_w[0]; _wfn7p(); _fwb3h() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f('Ширина скроллбара:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G.sl_scrollbar_w then _G.sl_scrollbar_w = imgui.new.float[1](settings.interface.scrollbar_w or 12) end if imgui.SliderFloat('##scrollbar_w', _G.sl_scrollbar_w, 4, 40, '%.0f px') then settings.interface.scrollbar_w = _G.sl_scrollbar_w[0]; _wfn7p(); _fwb3h() end imgui.PopItemWidth() imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar,ag,ab,1), _cyr5f('КНОПКИ')); imgui.Separator(); imgui.Spacing() if not _G.vid_btn_col then local br = settings.interface.btn_r or (bg+.08) local bg_ = settings.interface.btn_g or (bg+.07) local bb_ = settings.interface.btn_b or (bg+.04) _G.vid_btn_col = imgui.new.float[3](br, bg_, bb_) end imgui.TextDisabled(_cyr5f('Цвет кнопок:')); imgui.SameLine() local _bc = imgui.ImVec4(_G.vid_btn_col[0], _G.vid_btn_col[1], _G.vid_btn_col[2], 1) imgui.ColorButton('##btncolprev', _bc, 0, imgui.ImVec2(20*d, 20*d)) imgui.SameLine(0, 4*d) if imgui.Button(_cyr5f('Сброс##btncolreset'), imgui.ImVec2(60*d, 0)) then local _bg = settings.interface.bg_brightness or 0.06 _G.vid_btn_col[0] = _bg+.08; _G.vid_btn_col[1] = _bg+.07; _G.vid_btn_col[2] = _bg+.04 settings.interface.btn_r = nil; settings.interface.btn_g = nil; settings.interface.btn_b = nil _wfn7p(); _fwb3h() end imgui.PushItemWidth(-1) if imgui.SliderFloat(_cyr5f('R##btnr'), _G.vid_btn_col, 0.0, 1.0, '%.2f') then settings.interface.btn_r = _G.vid_btn_col[0]; _wfn7p(); _fwb3h() end if imgui.SliderFloat(_cyr5f('G##btng'), _G.vid_btn_col + 1, 0.0, 1.0, '%.2f') then settings.interface.btn_g = _G.vid_btn_col[1]; _wfn7p(); _fwb3h() end if imgui.SliderFloat(_cyr5f('B##btnb'), _G.vid_btn_col + 2, 0.0, 1.0, '%.2f') then settings.interface.btn_b = _G.vid_btn_col[2]; _wfn7p(); _fwb3h() end imgui.PopItemWidth() imgui.Spacing() imgui.TextDisabled(_cyr5f('Яркость (обычная):')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G.sl_btn_bri then _G.sl_btn_bri = imgui.new.float[1](settings.interface.btn_bright or 1.0) end if imgui.SliderFloat('##btn_bri', _G.sl_btn_bri, 0.2, 2.0, '%.2f') then settings.interface.btn_bright = _G.sl_btn_bri[0]; _wfn7p(); _fwb3h() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f('Яркость (нажатая):')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G.sl_bta_bri then _G.sl_bta_bri = imgui.new.float[1](settings.interface.btn_active_bright or 1.0) end if imgui.SliderFloat('##bta_bri', _G.sl_bta_bri, 0.2, 2.0, '%.2f') then settings.interface.btn_active_bright = _G.sl_bta_bri[0]; _wfn7p(); _fwb3h() end imgui.PopItemWidth() imgui.TextDisabled(_cyr5f('Насыщенность цвета:')); imgui.SameLine(); imgui.PushItemWidth(-1) if not _G.sl_btn_sat then _G.sl_btn_sat = imgui.new.float[1](settings.interface.btn_sat or 1.0) end if imgui.SliderFloat('##btn_sat', _G.sl_btn_sat, 0.0, 1.0, '%.2f') then settings.interface.btn_sat = _G.sl_btn_sat[0]; _wfn7p(); _fwb3h() end imgui.PopItemWidth(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' 1.0 = ориг, <1 = темнее, >1 = светлее. Нас: 0=б/ч, 1=полный')); imgui.Spacing() imgui.Spacing(); imgui.Separator(); imgui.Spacing() imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.4,0.1,0.05,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.6,0.15,0.08, _G._mh_wa or 1)) if imgui.Button(_cyr5f('Сбросить дизайн##resettheme'), imgui.ImVec2(-1, 28*d)) then settings.interface.accent_r=1; settings.interface.accent_g=0.65; settings.interface.accent_b=0 settings.interface.bg_brightness=0.06; settings.interface.window_alpha=0.98 settings.interface.rounding=4; settings.interface.border_size=1 settings.interface.text_r=nil; settings.interface.text_g=nil; settings.interface.text_b=nil settings.interface.border_r=nil; settings.interface.border_g=nil; settings.interface.border_b=nil accent_color[0]=1; accent_color[1]=0.65; accent_color[2]=0 _G.vid_text_col=nil; _G.vid_bg_col=nil; _G.vid_border_col=nil _G.sl_rounding=nil; _G.sl_border=nil settings.interface.grab_w=nil; settings.interface.scrollbar_w=nil settings.interface.btn_r=nil; settings.interface.btn_g=nil; settings.interface.btn_b=nil settings.interface.btn_bright=nil; settings.interface.btn_active_bright=nil; settings.interface.btn_sat=nil _G.sl_grab_w=nil; _G.sl_scrollbar_w=nil; _G.vid_btn_col=nil _G.sl_btn_bri=nil; _G.sl_bta_bri=nil; _G.sl_btn_sat=nil _wfn7p(); _fwb3h() end imgui.PopStyleColor(2) imgui.EndChild() end end end if _G.mh_tab == 7 then do imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar,ag,ab,1), u8' Авто-функции'); imgui.Separator() local funcs_mh = { {u8'Авто /ad подтв.', 'auto_ad_confirm'}, } local cw_p = imgui.GetWindowContentRegionWidth() local col_w_p = (cw_p - 8*d) / 2 imgui.Columns(2, '##mhfuncs', false) imgui.SetColumnWidth(0, col_w_p); imgui.SetColumnWidth(1, col_w_p) for fi, f in ipairs(funcs_mh) do if fi > 1 then imgui.NextColumn() end if not _G['_mhfncb'..fi] then _G['_mhfncb'..fi] = imgui.new.bool(false) end _G['_mhfncb'..fi][0] = settings.general and settings.general[f[2]] or false if imgui.Checkbox(f[1]..'##mhfn'..fi, _G['_mhfncb'..fi]) then if settings.general then settings.general[f[2]] = (_G['_mhfncb'..fi][0] == true) _wfn7p() end end end imgui.Columns(1) if settings.general and settings.general.auto_ad_confirm then imgui.Spacing() imgui.TextDisabled(u8' Станция /ad:'); imgui.SameLine() local st_names = {u8'Los Santos##adst', u8'Las Venturas##adst', u8'San Fierro##adst'} local st_idx = settings.general.auto_ad_station_idx or 2 imgui.PushStyleColor(imgui.Col.Button, _mh_bc(bb_r, bb_g, bb_b, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(bb_r*1.35, bb_g*1.35, bb_b*1.3, _G._mh_wa or 1)) if imgui.SmallButton(st_names[st_idx + 1]) then settings.general.auto_ad_station_idx = (st_idx + 1) % 3; _wfn7p() end imgui.SameLine(0, 12*d) local ad_type_lbl = (settings.general.auto_ad_type or 0) == 0 and u8'Обычное##adtype' or u8'VIP##adtype' if imgui.SmallButton(ad_type_lbl) then settings.general.auto_ad_type = (settings.general.auto_ad_type or 0) == 0 and 1 or 0; _wfn7p() end imgui.PopStyleColor(2) end imgui.Separator(); imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar,ag,ab,1), u8' Шаблоны автопиара') imgui.SameLine(); imgui.TextDisabled(u8' /mhp [номер] — ручной запуск') imgui.Separator() local piar_h = imgui.GetWindowHeight() - 200*d if imgui.BeginChild('##mhpiars', imgui.ImVec2(-1, piar_h - 34*d), true) then _dpn1w() local col_dot = 18*d local col_info = cw_p - col_dot - 340*d if col_info < 80*d then col_info = 80*d end local col_btns = 340*d imgui.Columns(3, '##mhphdr', false) imgui.SetColumnWidth(0, col_dot); imgui.SetColumnWidth(1, col_info); imgui.SetColumnWidth(2, col_btns) imgui.NextColumn() imgui.TextDisabled(u8'Шаблон'); imgui.NextColumn() imgui.TextDisabled(u8'Действия'); imgui.NextColumn() imgui.Columns(1); imgui.Separator() for i, t in ipairs(settings.piar_templates or {}) do imgui.Columns(3, '##mhpr'..i, false) imgui.SetColumnWidth(0, col_dot); imgui.SetColumnWidth(1, col_info); imgui.SetColumnWidth(2, col_btns) if t.auto then imgui.TextColored(imgui.ImVec4(0.2,0.85,0.2,1), u8'•') else imgui.TextColored(imgui.ImVec4(0.85,0.2,0.2,1), u8'•') end imgui.NextColumn() imgui.Text(_cyr5f(' ' .. (t.name or ''))) imgui.TextDisabled(_cyr5f(' ' .. #(t.lines or {}) .. ' стр | ' .. (t.waiting or 1.5) .. 'с')) if t.auto then local elapsed = os.time() - (t.last_time or 0) local interval = t._next_interval or t.auto_interval or 300 local left = math.max(0, interval - elapsed) local rng_str = (t.auto_interval_max or 0) > (t.auto_interval or 300) and (' [ранд ' .. (t.auto_interval or 300) .. '-' .. t.auto_interval_max .. 'с]') or '' local lim_str = (t.auto_limit or 0) > 0 and (' [' .. (t.auto_count or 0) .. '/' .. t.auto_limit .. ']') or '' imgui.TextDisabled(_cyr5f(' Очередь: ' .. left .. 'с / ' .. interval .. 'с' .. rng_str .. lim_str)) end imgui.NextColumn() local bw3 = (col_btns - 12*d) / 3 if imgui.Button(_ic_play..' '..u8'Пуск##mhp'..i, imgui.ImVec2(bw3, 0)) then _xjg7y(i) end imgui.SameLine(0,4) if t.auto then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(sb_r, sb_g, sb_b, 1)) if imgui.Button(_ic_circs..' '..u8'Авто:ВКЛ##mha'..i, imgui.ImVec2(bw3, 0)) then t.auto=false; _wfn7p() end imgui.PopStyleColor() else imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.4,0.15,0.15,1)) if imgui.Button(_ic_play..' '..u8'Авто:ВЫКЛ##mha'..i, imgui.ImVec2(bw3, 0)) then t.auto=true; t.auto_count=0; t.last_time=os.time(); t._next_interval=nil; _wfn7p() end imgui.PopStyleColor() end imgui.SameLine(0,4) if imgui.Button(_ic_pen..'##mhe'..i, imgui.ImVec2(bw3, 0)) then _G.mh_piar_edit_index = i; _G.mh_piar_edit_open = true end imgui.NextColumn(); imgui.Columns(1); imgui.Separator() end imgui.EndChild() end if imgui.Button(_ic_circp..' '..u8'Шаблон##mhpadd', imgui.ImVec2(-1, 0)) then table.insert(settings.piar_templates, {name='Новый', enable=true, auto=false, auto_interval=300, auto_interval_max=0, auto_limit=0, auto_count=0, waiting=1.5, last_time=0, lines={'/s Текст'}}) _wfn7p() _G.mh_piar_edit_index = #settings.piar_templates _G.mh_piar_edit_open = true end end end if _G.mh_tab == 12 then _mh_draw_tab_rating() end if _G.mh_tab == 9 then do local about_h = imgui.GetWindowHeight() - 80*d if imgui.BeginChild('##about_wrap', imgui.ImVec2(-1, about_h), false) then _G._ltz8m() imgui.Spacing() imgui.SetCursorPosX((imgui.GetWindowContentRegionWidth() - imgui.CalcTextSize(u8'Market Helper').x) / 2) imgui.TextColored(imgui.ImVec4(ar,ag,ab,1), u8'Market Helper') imgui.SetCursorPosX((imgui.GetWindowContentRegionWidth() - imgui.CalcTextSize('v' .. thisScript().version).x) / 2) imgui.TextDisabled('v' .. thisScript().version) imgui.Spacing() do local _us = _G._mh_upd_state local _ul = _G._mh_upd_latest imgui.SetCursorPosX(16*d) if _us == 'ok' then imgui.TextColored(imgui.ImVec4(0.3,0.95,0.3,1), _ic_chk..' ') imgui.SameLine(0,2*d) imgui.TextColored(imgui.ImVec4(0.3,0.95,0.3,1), _cyr5f('\xc0\xea\xf2\xf3\xe0\xeb\xfc\xed\xe0\xff \xe2\xe5\xf0\xf1\xe8\xff')) elseif _us == 'outdated' then imgui.TextColored(imgui.ImVec4(1,0.75,0,1), _ic_up..' ') imgui.SameLine(0,2*d) imgui.TextColored(imgui.ImVec4(1,0.75,0,1), _cyr5f('\xc4\xee\xf1\xf2\xf3\xef\xed\xee v'..((_ul) or '?'))) imgui.SameLine(0,8*d) local _dls = _G._mh_dl_state if _dls == 'downloading' then imgui.TextColored(imgui.ImVec4(0.4,0.85,1,1), _cyr5f('\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0... ')..tostring(_G._mh_dl_progress or 0)..'%') elseif _dls == 'done' then imgui.TextColored(imgui.ImVec4(0.3,0.95,0.3,1), _cyr5f('\xcf\xe5\xf0\xe5\xe7\xe0\xe3\xf0\xf3\xe7\xea\xe0...')) elseif _dls == 'error' then imgui.TextColored(imgui.ImVec4(1,0.35,0.35,1), _cyr5f('\xce\xf8\xe8\xe1\xea\xe0: ')..(_G._mh_dl_err or '')) imgui.SameLine(0,6*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(sb_r,sb_g,sb_b,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.8,1,0.5,1)) if imgui.SmallButton(_cyr5f('\xcf\xee\xe2\xf2\xee\xf0##mh_dl_retry')) then _G._mh_dl_state = nil end imgui.PopStyleColor(2) elseif _dls == 'confirm' then imgui.Spacing() imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.08,0.12,0.06,0.97)) if imgui.BeginChild('##mh_confirm_box', imgui.ImVec2(-1, 68*d), true) then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1,0.9,0.3,1), _cyr5f('\xd1\xea\xe0\xf7\xe0\xf2\xfc v')..((_ul) or '?').. _cyr5f('? (\xf1\xee\xe3\xeb\xe0\xf1\xe8\xe5 \xf1 \xef\xee\xeb\xe8\xf2\xe8\xea\xee\xe9 @shinikmod)')) imgui.Spacing() local _bw2 = (imgui.GetWindowContentRegionWidth()-6*d)/2 imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10*0.9,_G._mh_sb_g or 0.45*0.9,_G._mh_sb_b or 0.10*0.9,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.44,sb_g*1.44,sb_b*1.44,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.8,1,0.5,1)) if imgui.Button(_cyr5f('\xd1\xea\xe0\xf7\xe0\xf2\xfc##mh_dl_yes'), imgui.ImVec2(_bw2, 0)) then _G._mh_do_download() end imgui.PopStyleColor(3) imgui.SameLine(0,6*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.40,0.08,0.08,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1,0.6,0.6,1)) if imgui.Button(_cyr5f('\xce\xf2\xec\xe5\xed\xe0##mh_dl_no'), imgui.ImVec2(_bw2, 0)) then _G._mh_dl_state = nil end imgui.PopStyleColor(2) end imgui.EndChild() imgui.PopStyleColor() else imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10,_G._mh_sb_g or 0.45,_G._mh_sb_b or 0.10,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(sb_r*1.55,sb_g*1.55,sb_b*1.55, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.8,1,0.5,1)) if imgui.SmallButton(_ic_up_RIGHT_FROM_SQUARE..' '.._cyr5f('\xd1\xea\xe0\xf7\xe0\xf2\xfc##mh_dl_btn')) then _G._mh_dl_state = 'confirm' end imgui.PopStyleColor(3) end elseif _us == 'tampered' then imgui.TextColored(imgui.ImVec4(1,0.3,0.3,1), _ic_shield..' ') imgui.SameLine(0,2*d) imgui.TextColored(imgui.ImVec4(1,0.3,0.3,1), _cyr5f('\xd4\xe0\xe9\xeb \xe8\xe7\xec\xe5\xed\xb8\xed!')) imgui.SameLine(0,6*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0,0.40,0.75,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0,0.55,1.0, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1,1,1,1)) if imgui.SmallButton(_ic_up_RIGHT_FROM_SQUARE..' t.me/shinikmod##mh_tam_btn') then if _cvh6z() then pcall(gta._Z12AND_OpenLinkPKc, 'https://t.me/shinikmod') else os.execute('start https://t.me/shinikmod') end end imgui.PopStyleColor(3) elseif _us == 'checking' then imgui.TextColored(imgui.ImVec4(0.6,0.6,0.6,1), _cyr5f('\xcf\xf0\xee\xe2\xe5\xf0\xea\xe0...')) else imgui.TextDisabled(_cyr5f('v' .. (thisScript().version or '?'))) end imgui.SameLine(0,10*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.10,0.10,0.14,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.18,0.18,0.26, _G._mh_wa or 1)) if imgui.SmallButton(_ic_rot..'##mh_upd_btn') then _G._mh_check_update(true) end if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('\xcf\xf0\xee\xe2\xe5\xf0\xe8\xf2\xfc \xee\xe1\xed\xee\xe2\xeb\xe5\xed\xe8\xe5')) end imgui.PopStyleColor(2) end imgui.Separator() imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar,ag,ab,1), u8' \xc0\xe2\xf2\xee\xf0') imgui.Separator() imgui.Spacing() imgui.SetCursorPosX(16*d) imgui.Text(u8'Shinik_Pupckin') imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar,ag,ab,1), u8' \xd1\xf1\xfb\xeb\xea\xe8') imgui.Separator() imgui.Spacing() imgui.SetCursorPosX(16*d) imgui.TextDisabled(u8'\xd2\xe5\xeb\xe5\xe3\xf0\xe0\xec \xea\xe0\xed\xe0\xeb:') imgui.SameLine() imgui.TextColored(imgui.ImVec4(0.3,0.7,1.0,1), 'https://t.me/shinikmod') if imgui.IsItemClicked() then if _cvh6z() then pcall(gta._Z12AND_OpenLinkPKc, 'https://t.me/shinikmod') else os.execute('start https://t.me/shinikmod') end end if imgui.IsItemHovered() then imgui.SetMouseCursor(imgui.MouseCursor.Hand) end imgui.Spacing() imgui.SetCursorPosX(16*d) imgui.TextDisabled(u8'\xc0\xe2\xf2\xee\xf0 (\xeb\xe8\xf7\xea\xe0):') imgui.SameLine() imgui.TextColored(imgui.ImVec4(0.3,0.7,1.0,1), 'https://t.me/shinik_1') if imgui.IsItemClicked() then if _cvh6z() then pcall(gta._Z12AND_OpenLinkPKc, 'https://t.me/shinik_1') else os.execute('start https://t.me/shinik_1') end end if imgui.IsItemHovered() then imgui.SetMouseCursor(imgui.MouseCursor.Hand) end imgui.Spacing() imgui.Separator() imgui.Spacing() imgui.SetCursorPosX(16*d) imgui.TextDisabled(u8'\xc1\xe0\xe3\xe8 \xe8 \xef\xf0\xe5\xe4\xeb\xee\xe6\xe5\xed\xe8\xff \x97 \xe2 \xf2\xe5\xeb\xe5\xe3\xf0\xe0\xec \xea\xe0\xed\xe0\xeb') imgui.EndChild() end end imgui.EndChild() imgui.EndChild() end end imgui.PopStyleColor() imgui.End() end) imgui.OnFrame( function() return _G.mh_qpop_open == true end, function() local d = settings.general.custom_dpi local ar = settings.interface.accent_r or 1.0 local ag = settings.interface.accent_g or 0.55 local ab = settings.interface.accent_b or 0.0 local bg = settings.interface.bg_brightness or 0.06 local wa = settings.interface and settings.interface.window_alpha or 0.98 local sb_r = settings.interface.sell_btn_r or 0.10 local sb_g = settings.interface.sell_btn_g or 0.45 local sb_b = settings.interface.sell_btn_b or 0.10 local bb_r = settings.interface.buy_btn_r or 0.00 local bb_g = settings.interface.buy_btn_g or 0.28 local bb_b = settings.interface.buy_btn_b or 0.50 local sw, sh = imgui.GetIO().DisplaySize.x, imgui.GetIO().DisplaySize.y local pw = math.min(sw * 0.30, 340*d) local ph = math.min(sh * 0.52, 330*d) local px = sw - pw - 50 local py = sh - ph - 50 if not _G._qpop_win_init then imgui.SetNextWindowPos(imgui.ImVec2(px, py), imgui.Cond.Always) imgui.SetNextWindowSize(imgui.ImVec2(pw, ph), imgui.Cond.Always) _G._qpop_win_init = true else imgui.SetNextWindowSize(imgui.ImVec2(pw, ph), imgui.Cond.Once) end imgui.SetNextWindowBgAlpha(wa) local _qs = imgui.GetStyle() local _save_rnd = _qs.WindowRounding local _save_pad = _qs.WindowPadding _qs.WindowRounding = 8*d _qs.WindowPadding = imgui.ImVec2(10*d, 8*d) imgui.PushStyleColor(imgui.Col.WindowBg, imgui.ImVec4(bg+0.02, bg+0.015, bg+0.005, wa)) imgui.PushStyleColor(imgui.Col.Border, imgui.ImVec4(ar*0.6, ag*0.6, ab*0.3, 0.7)) imgui.PushStyleColor(imgui.Col.TitleBgActive, imgui.ImVec4(ar*0.18, ag*0.12, ab*0.06, wa)) local closed = imgui.new.bool(true) local nm = _G.mh_qpop_item or '' local _wt = u8' Индекс цен##qpop' if imgui.Begin(_wt, closed, bit.bor(imgui.WindowFlags.NoCollapse, imgui.WindowFlags.NoResize, imgui.WindowFlags.NoMove, imgui.WindowFlags.NoFocusOnAppearing)) then if _G.mh_qpop_cache_nm ~= nm then _G.mh_qpop_cache = _mh_qpop_prices(nm) _G.mh_qpop_cache_nm = nm end local p = _G.mh_qpop_cache or {} local ac = imgui.ImVec4(ar, ag, ab, 1) local sc = imgui.ImVec4(sb_r*2.0+0.1, sb_g*2.0+0.2, sb_b*2.0+0.1, 1) local bc = imgui.ImVec4(bb_r*2.0+0.15, bb_g*2.0+0.3, bb_b*2.0+0.5, 1) local dc = imgui.ImVec4(0.5, 0.5, 0.5, 1) local wc = imgui.ImVec4(1.0, 0.82, 0.15, 1) local _qp_nm_show = nm if _qp_nm_show:match('^ID:') then local _qp_id = _G.mh_qpop_item_id if _qp_id and mh_arz_items_db and mh_arz_items_db[_qp_id] then _qp_nm_show = mh_arz_items_db[_qp_id] _G.mh_qpop_item = _qp_nm_show end end imgui.PushStyleColor(imgui.Col.Text, ac) imgui.TextWrapped(_cyr5f(_qp_nm_show)) imgui.PopStyleColor() local _qp_price = _G.mh_qpop_item_price or 0 local _qp_type = _G.mh_qpop_item_type or 13 if _qp_price > 0 then local _type_lbl = _qp_type == 28 and _cyr5f('Скупка: ') or _cyr5f('Продаёт: ') local _type_col = _qp_type == 28 and imgui.ImVec4(bb_r*2+0.15, bb_g*2+0.3, bb_b*2+0.5, 1) or imgui.ImVec4(sb_r*2+0.1, sb_g*2+0.2, sb_b*2+0.1, 1) local _price_s if _qp_price >= 1e6 then _price_s = string.format('%.2fM', _qp_price/1e6) elseif _qp_price >= 1e3 then _price_s = string.format('%.0fK', _qp_price/1e3) else _price_s = tostring(_qp_price) end imgui.TextColored(_type_col, _type_lbl .. _price_s) end imgui.Separator() imgui.Spacing() local cw2 = imgui.GetWindowContentRegionWidth() local c0 = cw2 * 0.32 local c1 = (cw2 - c0) / 3 imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(ar*0.65, ag*0.65, ab*0.4, 1)) local _base_x = imgui.GetCursorPosX() imgui.SetCursorPosX(_base_x + c0) imgui.Text(_cyr5f('День')) imgui.SameLine(0, 0); imgui.SetCursorPosX(_base_x + c0 + c1) imgui.Text(_cyr5f('7 дн')) imgui.SameLine(0, 0); imgui.SetCursorPosX(_base_x + c0 + c1*2) imgui.Text(_cyr5f('30 дн')) imgui.PopStyleColor() imgui.Separator() imgui.Spacing() function _row(label, col, v_day, v7, v30) local _rx = _base_x local function _fmtv(v) if not v or v <= 0 then return _cyr5f('—') end if v >= 1e6 then return string.format('%.1fM', v/1e6) elseif v >= 1e3 then return string.format('%.0fK', v/1e3) else return tostring(math.floor(v)) end end imgui.TextColored(col, _cyr5f(label)) imgui.SameLine(0, 0); imgui.SetCursorPosX(_rx + c0) imgui.TextColored(v_day and v_day>0 and col or dc, _fmtv(v_day)) imgui.SameLine(0, 0); imgui.SetCursorPosX(_rx + c0 + c1) imgui.TextColored(v7 and v7>0 and col or dc, _fmtv(v7)) imgui.SameLine(0, 0); imgui.SetCursorPosX(_rx + c0 + c1*2) imgui.TextColored(v30 and v30>0 and col or dc, _fmtv(v30)) end _row('Рынок', wc, p.mkt_today, p.sh_s_7 or p.mkt_7, p.sh_s_30 or p.mkt_30) imgui.Spacing() _row('Продают', sc, p.lv_sell, p.sh_s_7, p.sh_s_30) imgui.Spacing() _row('Скупают', bc, p.lv_buy, p.sh_b_7, p.sh_b_30) imgui.Spacing() local _qs_cnt = p.cnt_sell or 0 local _qb_cnt = p.cnt_buy or 0 if _qs_cnt > 0 or _qb_cnt > 0 then imgui.Separator() imgui.Spacing() local _dc2 = imgui.ImVec4(0.55, 0.55, 0.55, 1) if _qs_cnt > 0 then imgui.TextColored(sc, _cyr5f('Продают: ')) imgui.SameLine(0,2*d) imgui.TextColored(_dc2, tostring(_qs_cnt) .. _cyr5f(' лавок')) end if _qb_cnt > 0 then imgui.TextColored(bc, _cyr5f('Скупают: ')) imgui.SameLine(0,2*d) imgui.TextColored(_dc2, tostring(_qb_cnt) .. _cyr5f(' лавок')) end end imgui.Spacing() imgui.Separator() imgui.Spacing() imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(ar*0.20, ag*0.14, ab*0.07, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(ar*0.35, ag*0.24, ab*0.12, 1)) if imgui.Button(_cyr5f('Подробнее ->##qpopfull'), imgui.ImVec2(cw2 * 0.60, 0)) then _G.mkt_detail_item = nm _G.mkt_detail_src = fh_mkt_prices and fh_mkt_prices[nm] and 'cp' or 'tags' _G.mkt_detail_pos = nil _G.mkt_detail_open = true _G.mh_qpop_open = false end imgui.PopStyleColor(2) imgui.SameLine(0, 6*d) imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.32, 0.06, 0.06, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.48, 0.10, 0.10, 1)) if imgui.Button(_cyr5f('Закрыть##qpopclose'), imgui.ImVec2(-1, 0)) then _G.mh_qpop_open = false end imgui.PopStyleColor(2) end if not closed[0] then _G.mh_qpop_open = false end imgui.PopStyleColor(3) _qs.WindowRounding = _save_rnd _qs.WindowPadding = _save_pad imgui.End() end ) imgui.OnFrame( function() return settings.overlay and settings.overlay.enabled == true end, function() local d = settings.general.custom_dpi or 1 local sw, sh = imgui.GetIO().DisplaySize.x, imgui.GetIO().DisplaySize.y local px = settings.overlay.pos_x or 10 local py = settings.overlay.pos_y or 200 local pw = settings.overlay.width or 420 local ph = settings.overlay.height or 180 if not _G.ov_initialized then imgui.SetNextWindowPos(imgui.ImVec2(px, py), imgui.Cond.Always) imgui.SetNextWindowSize(imgui.ImVec2(pw, ph), imgui.Cond.Always) _G.ov_initialized = true else imgui.SetNextWindowPos(imgui.ImVec2(px, py), imgui.Cond.Once) imgui.SetNextWindowSize(imgui.ImVec2(pw, ph), imgui.Cond.Once) end imgui.SetNextWindowBgAlpha(settings.overlay.alpha or 0.6) local s = imgui.GetStyle() local old_pad = s.WindowPadding s.WindowPadding = imgui.ImVec2(4*d, 4*d) imgui.Begin('##mh_overlay_log', imgui.new.bool(true), imgui.WindowFlags.NoTitleBar + imgui.WindowFlags.NoCollapse + imgui.WindowFlags.NoScrollbar) _G._ltz8m() if not _G.ov_day_filter then _G.ov_day_filter = (settings.overlay and settings.overlay.day_filter) or 1 end function _ov_day_ok(le_dt) if _G.ov_day_filter == 0 then return true end if not le_dt or le_dt == '' then return false end local now = os.time() local t = os.date('*t', now) local d_day = string.format('%02d.%02d', t.day, t.month) local e_day = le_dt:sub(1,5) if _G.ov_day_filter == 1 then return e_day == d_day elseif _G.ov_day_filter == 2 then local e_d = tonumber(le_dt:sub(1,2)) or 0 local e_m = tonumber(le_dt:sub(4,5)) or 0 local e_ts = os.time({year=t.year, month=e_m, day=e_d, hour=0, min=0, sec=0}) if e_ts > now then e_ts = os.time({year=t.year-1, month=e_m, day=e_d, hour=0, min=0, sec=0}) end return (now - e_ts) <= (7 * 86400) elseif _G.ov_day_filter == 3 then local e_d = tonumber(le_dt:sub(1,2)) or 0 local e_m = tonumber(le_dt:sub(4,5)) or 0 local e_ts = os.time({year=t.year, month=e_m, day=e_d, hour=0, min=0, sec=0}) if e_ts > now then e_ts = os.time({year=t.year-1, month=e_m, day=e_d, hour=0, min=0, sec=0}) end return (now - e_ts) <= (30 * 86400) end return true end local lines = settings.overlay.lines or 8 local ov_entries = {} for i = #fh_mkt_log, 1, -1 do local e = fh_mkt_log[i] if e and e.vc ~= true and _ov_day_ok(e.dt) then table.insert(ov_entries, e) end if #ov_entries >= lines then break end end local _ov_log_ver = tostring(#fh_mkt_log) .. '|' .. tostring(_G.ov_day_filter or 1) if _G._ov_sum_ver ~= _ov_log_ver then _G._ov_sum_ver = _ov_log_ver local _ss, _bs, _sc, _bc = 0, 0, 0, 0 for i = 1, #fh_mkt_log do local e_sum = fh_mkt_log[i] if e_sum and e_sum.vc ~= true and _ov_day_ok(e_sum.dt) then if fh_is_my_sell(e_sum) then _ss = _ss + (e_sum.price or 0) * (e_sum.qty or 1) _sc = _sc + (e_sum.qty or 1) else _bs = _bs + (e_sum.price or 0) * (e_sum.qty or 1) _bc = _bc + (e_sum.qty or 1) end end end _G._ov_sum_cache = {ss=_ss, bs=_bs, sc=_sc, bc=_bc} end local _ovc = _G._ov_sum_cache or {ss=0,bs=0,sc=0,bc=0} local sell_sum, buy_sum, sell_cnt, buy_cnt = _ovc.ss, _ovc.bs, _ovc.sc, _ovc.bc local total = #ov_entries local start = 1 for i = start, total do local e = ov_entries[i] if e then local r, g, b_c if fh_is_my_sell(e) then r = settings.overlay.sell_r or 0.3 g = settings.overlay.sell_g or 0.9 b_c = settings.overlay.sell_b or 0.3 else r = settings.overlay.buy_r or 0.3 g = settings.overlay.buy_g or 0.6 b_c = settings.overlay.buy_b or 1.0 end local line = (e.dt or '') .. ' ' .. (e.item or '') .. ' x' .. (e.qty or 1) .. ' $' .. _kcr3y((e.price or 0) * (e.qty or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(r, g, b_c, 1)) imgui.TextWrapped(_cyr5f(line)) imgui.PopStyleColor() end end imgui.Separator() imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.3,0.9,0.3,1)) imgui.TextWrapped(_ic_up..' '.._cyr5f(sell_cnt..' \xf8\xf2.')..' '.._ic_coin..' '.._cyr5f(_kcr3y(sell_sum))) imgui.PopStyleColor() imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.3,0.6,1.0,1)) imgui.TextWrapped(_ic_dn..' '.._cyr5f(buy_cnt..' \xf8\xf2.')..' '.._ic_coin..' '.._cyr5f(_kcr3y(buy_sum))) imgui.PopStyleColor() local np = imgui.GetWindowPos() local ns = imgui.GetWindowSize() local pos_changed = np.x ~= settings.overlay.pos_x or np.y ~= settings.overlay.pos_y local size_changed = ns.x ~= settings.overlay.width or ns.y ~= settings.overlay.height if (pos_changed or size_changed) and not imgui.IsWindowFocused() then settings.overlay.pos_x = np.x; settings.overlay.pos_y = np.y settings.overlay.width = ns.x; settings.overlay.height = ns.y _wfn7p() end s.WindowPadding = old_pad imgui.End() end ) imgui.OnFrame( function() return _G.mkt_auto_detail_open == true end, function() local d = settings.general.custom_dpi local ar2 = settings.interface.accent_r or 1 local ag2 = settings.interface.accent_g or .65 local ab2 = settings.interface.accent_b or 0.0 local sb_r = settings.interface.sell_btn_r or 0.10 local sb_g = settings.interface.sell_btn_g or 0.45 local sb_b = settings.interface.sell_btn_b or 0.10 local bb_r = settings.interface.buy_btn_r or 0.00 local bb_g = settings.interface.buy_btn_g or 0.28 local bb_b = settings.interface.buy_btn_b or 0.50 local ac2 = imgui.ImVec4(ar2, ag2, ab2, 1) local lp_r = settings.overlay and settings.overlay.log_price_r or 1.0 local lp_g = settings.overlay and settings.overlay.log_price_g or 0.85 local lp_b = settings.overlay and settings.overlay.log_price_b or 0.2 local sw, sh = imgui.GetIO().DisplaySize.x, imgui.GetIO().DisplaySize.y local pop_w = math.min(sw - 20*d, 510*d) local pop_h = math.min(sh - 40*d, 560*d) imgui.SetNextWindowSize(imgui.ImVec2(pop_w, pop_h), imgui.Cond.Always) imgui.SetNextWindowPos(imgui.ImVec2(sw/2, sh/2), imgui.Cond.Always, imgui.ImVec2(0.5, 0.5)) local closed2 = imgui.new.bool(true) if imgui.Begin(u8'Статистика авто##autopop', closed2, imgui.WindowFlags.NoCollapse + imgui.WindowFlags.NoResize) then _G._ltz8m() local nm = _G.mkt_auto_detail_item or '' local e = fh_mkt_auto[nm] local cp_hist = e and e.cp_hist imgui.TextColored(ac2, _cyr5f('Авто: ' .. nm)) if e and e.date then imgui.SameLine() imgui.TextDisabled(_cyr5f(' обновлено ' .. e.date)) end imgui.Separator(); imgui.Spacing() if e then if cp_hist and #cp_hist > 0 then local s7 = _mjg5t(cp_hist, 7) local s30 = _mjg5t(cp_hist, 30) local trend = _G._xvn2w(cp_hist) local tc_tr = _G._pdf8k(trend) local _trend_icon = type(trend)=='table' and trend.icon or _ic_min local _trend_text = type(trend)=='table' and trend.text or '' imgui.TextColored(ac2, u8'Статистика (мировой рынок)') imgui.Spacing() imgui.Columns(3, '##adtl2', false) local _cw_a2 = imgui.GetWindowContentRegionWidth() imgui.SetColumnWidth(0, 62*d) imgui.SetColumnWidth(1, math.floor((_cw_a2-62*d)*0.58)) imgui.SetColumnWidth(2, math.floor((_cw_a2-62*d)*0.42)) local hc2 = imgui.ImVec4(0.6,0.6,0.6,1) imgui.TextColored(hc2, u8''); imgui.NextColumn() imgui.TextColored(hc2, u8'Ср. цена $'); imgui.NextColumn() imgui.TextColored(hc2, u8'Сделок'); imgui.NextColumn() local today_h = cp_hist[1] imgui.TextColored(imgui.ImVec4(0.4,0.95,1,1), u8'Сегодня'); imgui.NextColumn() if today_h and today_h.price and today_h.price > 0 then imgui.TextColored(imgui.ImVec4(0.4,0.95,1,1), _cyr5f('$'.._kcr3y(today_h.price))); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(1,1,1,0.8), _cyr5f(tostring(today_h.qty or 0))); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(1,1,1,0.4), _cyr5f(today_h.dt or '')); imgui.NextColumn() else for _=1,3 do imgui.TextDisabled(u8'—'); imgui.NextColumn() end end imgui.TextColored(imgui.ImVec4(lp_r,lp_g,lp_b,1), u8'7дн.'); imgui.NextColumn() if s7 then imgui.TextColored(imgui.ImVec4(lp_r,lp_g,lp_b,1), _cyr5f('$'.._kcr3y(s7.avg))); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(1,1,1,0.8), _cyr5f(_kcr3y(s7.qty))); imgui.NextColumn() else for _=1,2 do imgui.TextDisabled(u8'—'); imgui.NextColumn() end end imgui.TextColored(imgui.ImVec4(0.4,0.95,0.4,1), u8'30дн.'); imgui.NextColumn() if s30 then imgui.TextColored(imgui.ImVec4(0.4,0.95,0.4,1), _cyr5f('$'.._kcr3y(s30.avg))); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(1,1,1,0.8), _cyr5f(_kcr3y(s30.qty))); imgui.NextColumn() else for _=1,2 do imgui.TextDisabled(u8'—'); imgui.NextColumn() end end imgui.Columns(1); imgui.Spacing() imgui.TextColored(imgui.ImVec4(0.6,0.6,0.6,1), u8'Тренд: ') imgui.SameLine(); imgui.TextColored(tc_tr, _trend_icon..' '.._cyr5f(_trend_text)) imgui.Spacing() local plot_n = math.min(#cp_hist, 30) local p_min_v, p_max_v = math.huge, -math.huge local plot_tbl = {} for i = plot_n, 1, -1 do local pv = cp_hist[i].price or 0 table.insert(plot_tbl, pv) if pv < p_min_v then p_min_v = pv end if pv > p_max_v then p_max_v = pv end end if #plot_tbl > 1 then local plot_arr = imgui.new.float[#plot_tbl]() for i2, v in ipairs(plot_tbl) do plot_arr[i2-1] = v end local lbl = _cyr5f('Мин: $'.._zhb9s(p_min_v)..' Макс: $'.._zhb9s(p_max_v)) imgui.PlotLines('##autoplot', plot_arr, #plot_tbl, 0, lbl, p_min_v*0.98, p_max_v*1.02, imgui.ImVec2(imgui.GetContentRegionAvail().x, 70*d)) end imgui.Spacing(); imgui.Separator(); imgui.Spacing() elseif e.s_avg or e.cp_sp then imgui.TextColored(ac2, u8'Цены (пов. скан)') imgui.Spacing() local price2 = e.s_avg or e.cp_sp imgui.TextColored(imgui.ImVec4(lp_r,lp_g,lp_b,1), _cyr5f('$'.._kcr3y(price2))) if e.s_min then imgui.TextColored(imgui.ImVec4(0.4,0.95,0.4,1), _cyr5f('Мин: $'.._kcr3y(e.s_min))) end if e.s_max then imgui.TextColored(imgui.ImVec4(1,0.5,0.5,1), _cyr5f('Макс: $'.._kcr3y(e.s_max))) end imgui.Spacing() imgui.TextDisabled(u8'Для графика — Углублённый скан авто [MH]') imgui.Spacing(); imgui.Separator(); imgui.Spacing() end if cp_hist and #cp_hist > 0 then imgui.TextColored(imgui.ImVec4(lp_r,lp_g,lp_b,0.9), _cyr5f(' История по дням (' .. #cp_hist .. '):')) local hist_h = math.min(#cp_hist * 18*d + 30*d, 220*d) if imgui.BeginChild('##autohistch', imgui.ImVec2(-1, hist_h), true) then _dpn1w() imgui.Columns(3, '##auto_hd', false) imgui.SetColumnWidth(0, 120*d); imgui.SetColumnWidth(1, 70*d); imgui.SetColumnWidth(2, 110*d) imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1), u8' Дата'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1), u8' Сделок'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1), u8' Ср. цена'); imgui.NextColumn() imgui.Separator() for i3, h in ipairs(cp_hist) do local rc = (i3==1) and imgui.ImVec4(lp_r,lp_g,lp_b,1) or imgui.ImVec4(0.85,0.85,0.85,1) imgui.TextColored(imgui.ImVec4(0.65,0.65,0.65,1), _cyr5f(' '..(h.dt or ''))); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(1,1,1,0.75), _cyr5f(' '..(h.qty or 0))); imgui.NextColumn() imgui.TextColored(rc, _cyr5f(' $'.._kcr3y(h.price or 0))); imgui.NextColumn() end imgui.Columns(1); imgui.EndChild() end elseif e.hist and #e.hist > 0 then local hist = e.hist imgui.TextColored(imgui.ImVec4(0.6,0.6,0.6,1), u8'История сканов:') if imgui.BeginChild('##autohistch2', imgui.ImVec2(-1, 100*d), true) then _dpn1w() for hi = 1, #hist do local h = hist[hi] imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1), _cyr5f(h.dt or '')) imgui.SameLine(70*d) imgui.TextColored(imgui.ImVec4(lp_r,lp_g,lp_b,1), _cyr5f('$'.._kcr3y(h.price or 0))) end imgui.EndChild() end end else imgui.TextDisabled(u8'Данные не найдены') end imgui.Spacing() if imgui.Button(_ic_x..' '..u8'Закрыть##autodtlclose', imgui.ImVec2(-1, 0)) then _G.mkt_auto_detail_open = false end end if not closed2[0] then _G.mkt_auto_detail_open = false end imgui.End() end ) imgui.OnFrame(function() return _G.mh_piar_edit_open == true and _G.mh_piar_edit_index ~= nil end, function() if not _G.mh_piar_edit_index or not settings.piar_templates[_G.mh_piar_edit_index] then _G.mh_piar_edit_open = false; _G.mh_piar_edit_index = nil; return end local t = settings.piar_templates[_G.mh_piar_edit_index] local d = settings.general.custom_dpi local sizeX2, sizeY2 = imgui.GetIO().DisplaySize.x, imgui.GetIO().DisplaySize.y if not _G.mh_piar_edit_pos then _G.mh_piar_edit_pos = {x = sizeX2/2, y = sizeY2/2} end local PiarEditWindowMH = imgui.new.bool(true) imgui.SetNextWindowPos(imgui.ImVec2(_G.mh_piar_edit_pos.x, _G.mh_piar_edit_pos.y), imgui.Cond.Once, imgui.ImVec2(0.5, 0.5)) imgui.SetNextWindowSize(imgui.ImVec2(620*d, 560*d), imgui.Cond.Always) imgui.GetIO().ConfigWindowsMoveFromTitleBarOnly = false imgui.Begin(_cyr5f(' \xcf\xe8\xe0\xf0 / \xd0\xe5\xe4\xe0\xea\xf2\xee\xf0'), PiarEditWindowMH, imgui.WindowFlags.NoCollapse) _G._ltz8m() imgui.Text(u8'Название:') local _pn_key = 'mh_piar_namebuf_' .. tostring(_G.mh_piar_edit_index) if not _G[_pn_key] or _G._piar_namebuf_for ~= tostring(_G.mh_piar_edit_index) then _G[_pn_key] = imgui.new.char[256](_cyr5f(t.name or '')) _G._piar_namebuf_for = tostring(_G.mh_piar_edit_index) end imgui.PushItemWidth(-1) if imgui.InputText('##mhpn', _G[_pn_key], 256) then t.name = u8:decode(ffi.string(_G[_pn_key])); _wfn7p() end imgui.PopItemWidth() imgui.Text(u8'Задержка (с):') local pw2 = imgui.new.float(t.waiting or 1.5); imgui.PushItemWidth(-1) if imgui.SliderFloat('##mhpw', pw2, 0.5, 10) then t.waiting=pw2[0]; _wfn7p() end imgui.PopItemWidth() imgui.Text(u8'Авто-интервал мин (с):') local pi2 = imgui.new.int(t.auto_interval or 300); imgui.PushItemWidth(-1) if imgui.SliderInt('##mhpi', pi2, 30, 3600) then t.auto_interval=pi2[0] if (t.auto_interval_max or 0) < pi2[0] then t.auto_interval_max=pi2[0] end t._next_interval = nil _wfn7p() end imgui.PopItemWidth() imgui.Text(u8'Авто-интервал макс (0=нет рандома):') local pm2 = imgui.new.int(t.auto_interval_max or 0); imgui.PushItemWidth(-1) if imgui.SliderInt('##mhpm', pm2, 0, 3600) then t.auto_interval_max=pm2[0]; t._next_interval=nil; _wfn7p() end imgui.PopItemWidth() imgui.Text(u8'Лимит авто-отправок (0 = без лимита):') local pl2 = imgui.new.int(t.auto_limit or 0); imgui.PushItemWidth(-1) if imgui.SliderInt('##mhpl2', pl2, 0, 100) then t.auto_limit=pl2[0]; t.auto_count=0; _wfn7p() end imgui.PopItemWidth() local _rl2 = imgui.new.bool(t.random_line or false) if imgui.Checkbox(u8'Случайная строка##mhrl', _rl2) then t.random_line = _rl2[0]; _wfn7p() end imgui.Separator() if t.random_line then imgui.Text(u8'Строки (одна выбирается рандомно):') else imgui.Text(u8'Строки (используй & для разделения):') end if imgui.BeginChild('##mhpl', imgui.ImVec2(-1, 160*d), true) then local rm = nil for li, line in ipairs(t.lines or {}) do local _lbk = 'mh_piar_linebuf_'..tostring(_G.mh_piar_edit_index)..'_'..li if not _G[_lbk] then _G[_lbk] = imgui.new.char[512](_cyr5f(line)) end imgui.PushItemWidth(390*d) if imgui.InputText('##mhpli'..li, _G[_lbk], 512) then t.lines[li]=u8:decode(ffi.string(_G[_lbk])); _wfn7p() end imgui.SameLine() if imgui.SmallButton(_ic_x..'##mhpld'..li) then rm=li end end if rm then table.remove(t.lines, rm) for _ci = rm, #t.lines + 1 do _G['mh_piar_linebuf_'..tostring(_G.mh_piar_edit_index)..'_'.._ci] = nil end _wfn7p() end imgui.EndChild() end local hw_p3 = (imgui.GetWindowContentRegionWidth() - 6*d) / 3 if imgui.Button(_ic_circp..' '..u8'Строка##mhpaline', imgui.ImVec2(hw_p3, 0)) then table.insert(t.lines, '/s Текст'); _wfn7p() end imgui.SameLine(0, 3*d) local _cur_lavka = mh_own_shop_num or fh_other_shop_pending_num local _lbtn_lbl = _cur_lavka and (_cyr5f('Лавка #')..tostring(_cur_lavka)) or _cyr5f('Лавка') imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.08, 0.30, 0.50, 1)) if imgui.Button(_lbtn_lbl..'##mhplavka', imgui.ImVec2(hw_p3, 0)) then if #(t.lines or {}) == 0 then table.insert(t.lines, '/s ') else local _last = t.lines[#t.lines] if not _last:find('', 1, true) then t.lines[#t.lines] = _last .. ' ' local _lbk2 = 'mh_piar_linebuf_'..tostring(_G.mh_piar_edit_index)..'_'..#t.lines _G[_lbk2] = imgui.new.char[512](_cyr5f(t.lines[#t.lines])) end end _wfn7p() end imgui.PopStyleColor() if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f(' подставит номер лавки при отправке')) end imgui.SameLine(0, 3*d) if imgui.Button(_ic_trash..' '..u8'Удалить шаблон##mhpldel', imgui.ImVec2(hw_p3, 0)) then table.remove(settings.piar_templates, _G.mh_piar_edit_index) _wfn7p(); _G.mh_piar_edit_open = false; _G.mh_piar_edit_index = nil end if not PiarEditWindowMH[0] then _G.mh_piar_edit_open = false end imgui.End() end) addEventHandler('onScriptTerminate', function(sc, res) if sc ~= thisScript() then return end if settings and settings.piar_templates then local _changed = false for _, t in ipairs(settings.piar_templates) do if t.auto then t.auto = false _changed = true end end if _changed then pcall(_wfn7p) end end end) imgui.OnFrame( function() return _G._arz_shop_win_open == true and _G.arz_detail ~= nil end, function() local d = settings.general.custom_dpi local sw, sh = imgui.GetIO().DisplaySize.x, imgui.GetIO().DisplaySize.y local pop_w = math.min(sw - 16, 820 * d) local pop_h = math.min(sh - 20, sh * 0.85) if not _G._shop_win_init then imgui.SetNextWindowSize(imgui.ImVec2(pop_w, pop_h), imgui.Cond.Always) imgui.SetNextWindowPos(imgui.ImVec2(sw / 2, sh / 2), imgui.Cond.Always, imgui.ImVec2(0.5, 0.5)) if not _G.mkt_detail_open then imgui.SetNextWindowFocus() end _G._shop_win_init = true else imgui.SetNextWindowSize(imgui.ImVec2(pop_w, pop_h), imgui.Cond.Once) end imgui.GetIO().ConfigWindowsMoveFromTitleBarOnly = true local _sw_closed = imgui.new.bool(true) local bg = settings.interface.bg_brightness or 0.06 local ar = settings.interface.accent_r or 1 local ag = settings.interface.accent_g or 0.65 local ab = settings.interface.accent_b or 0.0 local lp_r = settings.interface.sell_btn_r or 0.10 local lp_g = settings.interface.sell_btn_g or 0.45 local lp_b = settings.interface.sell_btn_b or 0.10 local cw_arz = pop_w - imgui.GetStyle().WindowPadding.x * 2 function _mh_bc(r,g,b,a) return imgui.ImVec4(r,g,b,a) end function _mh_bca(r,g,b,a) return imgui.ImVec4(r*1.2,g*1.2,b*1.2,a) end local lv = _G.arz_detail if not _G._xp_pull_attempted and not _G._xp_srv_loading then _G._xp_pull_attempted = true lua_thread.create(function() wait(500); _xp_pull_srv() end) end local _win_title = u8((lv and lv.username or '?') .. ' Лавка #' .. tostring(lv and lv.LavkaUid or '?') .. '##arz_shop_popup') local _pop_bg = settings.interface.bg_brightness or 0.06 local _pop_wa = settings.interface.window_alpha or 0.98 local _pop_ar = settings.interface.accent_r or 1.0 local _pop_ag = settings.interface.accent_g or 0.55 local _pop_ab = settings.interface.accent_b or 0.0 imgui.SetNextWindowBgAlpha(_pop_wa) imgui.PushStyleColor(imgui.Col.WindowBg, imgui.ImVec4(_pop_bg, _pop_bg, _pop_bg, _pop_wa)) imgui.PushStyleColor(imgui.Col.Border, imgui.ImVec4(_pop_ar * 0.80, _pop_ag * 0.80, _pop_ab * 0.80, 1.0)) imgui.PushStyleColor(imgui.Col.TitleBg, imgui.ImVec4(_pop_bg * 0.8, _pop_bg * 0.8, _pop_bg * 0.8, _pop_wa)) imgui.PushStyleColor(imgui.Col.TitleBgActive, imgui.ImVec4(_pop_ar * 0.18, _pop_ag * 0.18, _pop_ab * 0.18, _pop_wa)) imgui.PushStyleColor(imgui.Col.ScrollbarBg, imgui.ImVec4(_pop_bg, _pop_bg, _pop_bg, 0.6)) imgui.PushStyleColor(imgui.Col.ScrollbarGrab, imgui.ImVec4(_pop_ar * 0.4, _pop_ag * 0.4, _pop_ab * 0.4, 1.0)) if imgui.Begin(_win_title, _sw_closed, imgui.WindowFlags.NoCollapse) then local _total_h = imgui.GetWindowHeight() local _title_h = imgui.GetCursorPosY() local _footer_h = 36 * d + 8 * d local _content_h = _total_h - _title_h - _footer_h - 10 * d if imgui.BeginChild('##arz_sw_scroll', imgui.ImVec2(-1, _content_h), false) then do local _sw2 = _G._sw if _sw2.drag_y ~= 0 then imgui.SetScrollY(math.max(0, imgui.GetScrollY() - _sw2.drag_y)) end end do local srv_nm = _dzc2g(lv.serverId or -1) local is_vc = (lv.serverId == 0) local _upd = lv._mh_updated_at or 0 local _base = u8('Сервер: ' .. srv_nm .. ' ' .. (is_vc and 'VC$' or 'SA$')) local _lv_is_prem = lv._mh_premium or (_G._xp_db and _G._xp_db[(lv.username or ''):lower()] and _G._xp_db[(lv.username or ''):lower()].is_premium) or false if _upd > 0 then local _age = os.time() - _upd local _time_lbl = _age < 86400 and os.date('%H:%M', _upd) or os.date('%d.%m %H:%M', _upd) imgui.TextDisabled(_base .. ' ') imgui.SameLine(0, 0) imgui.TextDisabled(_ic_clk .. ' ' .. _time_lbl) else imgui.TextDisabled(_base) end if _lv_is_prem then imgui.SameLine(0, 8) imgui.TextColored(imgui.ImVec4(1, 0.84, 0, 1), _ic_star .. ' Premium') end end do local _owner_lc = (lv.username or ''):lower() local _pdata = _G._xp_db and _G._xp_db[_owner_lc] if (not _pdata or (_pdata.xp or 0) == 0) and _G._xp_srv_data then for _, _se in ipairs(_G._xp_srv_data) do if type(_se.nick) == 'string' and _se.nick:lower() == _owner_lc then if (_se.xp or 0) > 0 then _pdata = { xp = _se.xp or 0, level = _se.level or 0, is_premium = (_se.premium == true), } end break end end end if _pdata and (_pdata.level or 0) > 0 then local _lv = _pdata.level or 0 local _xp = _pdata.xp or 0 local _xp_next = _xp_for_level(_lv + 1) local _xp_cur = _xp_for_level(_lv) local _pct = (_xp_next > _xp_cur) and ((_xp - _xp_cur) / (_xp_next - _xp_cur)) or 1 local _lv_col = imgui.ImVec4( 0.2 + 0.8*(_lv/math.max(_lv,20)), 0.8 - 0.4*(_lv/math.max(_lv,20)), 0.2, 1) imgui.TextColored(_lv_col, _cyr5f('Lv.' .. _lv)) imgui.SameLine(0, 6) imgui.PushStyleColor(imgui.Col.PlotHistogram, _lv_col) imgui.ProgressBar(math.min(_pct, 1.0), imgui.ImVec2(80 * d, 10 * d), '') imgui.PopStyleColor() imgui.SameLine(0, 8) function _fmt_xp(n) if n >= 1e9 then return string.format('%.2fB', n/1e9) elseif n >= 1e6 then return string.format('%.2fM', n/1e6) elseif n >= 1e3 then return string.format('%.1fK', n/1e3) else return tostring(math.floor(n)) end end local _xp_need = _xp_next - _xp local _xp_s = _fmt_xp(_xp) .. ' / ' .. _fmt_xp(_xp_next) .. ' XP' imgui.TextDisabled(_cyr5f(_xp_s)) if _xp_need > 0 then imgui.SameLine(0, 6) imgui.TextDisabled(_cyr5f('(' .. _fmt_xp(_xp_need) .. ' до апа)')) end if _pdata.is_premium then imgui.SameLine(0, 8) imgui.TextColored(imgui.ImVec4(1, 0.84, 0, 1), _ic_star .. ' Premium') end else if lv._mh_premium or (_pdata and _pdata.is_premium) then imgui.TextColored(imgui.ImVec4(1, 0.84, 0, 1), _ic_star .. ' Premium') end end end do local _sum_s, _sum_b = 0, 0 if lv.items_sell and lv.price_sell then for _si2, _ in ipairs(lv.items_sell) do _sum_s = _sum_s + (lv.price_sell[_si2] or 0) * ((lv.count_sell and lv.count_sell[_si2]) or 1) end end if lv.items_buy and lv.price_buy then for _bi2, _ in ipairs(lv.items_buy) do _sum_b = _sum_b + (lv.price_buy[_bi2] or 0) * ((lv.count_buy and lv.count_buy[_bi2]) or 1) end end function _fmt_sum(n) local s = tostring(math.floor(n)) return '$' .. s:reverse():gsub('(%d%d%d)', '%1.'):reverse():gsub('^%.', '') end imgui.TextDisabled(u8('Продаёт: ')) imgui.SameLine(0, 2 * d) imgui.TextColored(imgui.ImVec4(0.3, 0.9, 0.3, 1), _fmt_sum(_sum_s)) imgui.SameLine(0, 12 * d) imgui.TextDisabled(u8('Скупает: ')) imgui.SameLine(0, 2 * d) imgui.TextColored(imgui.ImVec4(0.3, 0.6, 1.0, 1), _fmt_sum(_sum_b)) end imgui.Spacing() local tab_sell_lbl = fa.TAG .. ' ' .. u8('Продаёт (' .. tostring(lv.items_sell and #lv.items_sell or 0) .. ')##arz_sw_tab0') local tab_buy_lbl = fa.STORE .. ' ' .. u8('Скупает (' .. tostring(lv.items_buy and #lv.items_buy or 0) .. ')##arz_sw_tab1') local tw2 = (cw_arz - 6 * d) / 2 for ti = 0, 1 do if ti > 0 then imgui.SameLine(0, 6 * d) end local is_a = (_G.arz_detail_tab == ti) if is_a then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(ar * 0.5, ag * 0.5, ab * 0.28, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(ar * 0.7, ag * 0.7, ab * 0.38, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(ar, ag, ab, 1)) end local lbl = ti == 0 and tab_sell_lbl or tab_buy_lbl if imgui.Button(lbl, imgui.ImVec2(tw2, 28 * d)) then _G.arz_detail_tab = ti end if is_a then imgui.PopStyleColor(3) end end do local _is_sell_tab = (_G.arz_detail_tab == 0) local _cp_items = _is_sell_tab and (lv.items_sell or {}) or (lv.items_buy or {}) local _cp_prices = _is_sell_tab and (lv.price_sell or {}) or (lv.price_buy or {}) local _cp_counts = _is_sell_tab and (lv.count_sell or {}) or (lv.count_buy or {}) local _cp_lbl = _is_sell_tab and (_ic_fimp .. ' ' .. u8'НОВ. пресет ПРОДАЖИ##arz_sw_cp_s') or (_ic_fimp .. ' ' .. u8'НОВ. пресет СКУПКИ##arz_sw_cp_b') local _cp_col = _is_sell_tab and imgui.ImVec4(0.10, 0.32, 0.10, 1) or imgui.ImVec4(0.00, 0.22, 0.42, 1) local _cp_colh = _is_sell_tab and imgui.ImVec4(0.16, 0.46, 0.16, 1) or imgui.ImVec4(0.00, 0.30, 0.56, 1) if #_cp_items > 0 then imgui.PushStyleColor(imgui.Col.Button, _cp_col) imgui.PushStyleColor(imgui.Col.ButtonHovered, _cp_colh) if imgui.Button(_cp_lbl, imgui.ImVec2(-1, 0)) then local _owner_nm = lv.username or '?' local _added, _skipped = 0, 0 if _is_sell_tab then local _np = {name = _owner_nm .. ' Продажа', items = {}} for _ci, _iid in ipairs(_cp_items) do local _bid, _ench = _G._bqs3v(_iid) local _nm = _G._rgn9z(_bid) if _ench ~= '' then _nm = _nm .. ' (' .. _ench .. ')' end local _pr, _qt = _cp_prices[_ci] or 0, _cp_counts[_ci] or 1 if not _pst.find(_nm, _np.items) then table.insert(_np.items, {name=_nm, qty=_qt, price=_pr}); _added = _added + 1 else _skipped = _skipped + 1 end end table.insert(settings.presets, _np) fh_active_preset_idx = #settings.presets settings.active_preset = fh_active_preset_idx fh_lv_autosell_preset = _np.items _G.as_price_buf = nil; _G.as_qty_buf = nil; _wfn7p() mh_notify('[MH] {00cc00}Продажи -> пресет #' .. tostring(fh_active_preset_idx) .. ': +' .. _added .. ((_skipped > 0) and ' ({aaaaaa}' .. _skipped .. ' уже есть{ffffff})' or ''), 0xFFFFFF) else if not settings.buy_presets then settings.buy_presets = {} end local _nbp = {name = _owner_nm .. ' Скупка', items = {}} for _ci, _iid in ipairs(_cp_items) do local _bid, _ench = _G._bqs3v(_iid) local _nm = _G._rgn9z(_bid) if _ench ~= '' then _nm = _nm .. ' (' .. _ench .. ')' end local _pr, _qt = _cp_prices[_ci] or 0, _cp_counts[_ci] or 1 if not _pst.find(_nm, _nbp.items) then table.insert(_nbp.items, {name=_nm, qty=_qt, max_price=_pr}); _added = _added + 1 else _skipped = _skipped + 1 end end table.insert(settings.buy_presets, _nbp) fh_ab_preset_idx = #settings.buy_presets fh_lv_autobuy_preset = _nbp.items settings.autobuy_preset = fh_lv_autobuy_preset _G.ab_price_buf = nil; _G.ab_qty_buf = nil; _wfn7p() mh_notify('[MH] {4488ff}Скупка -> пресет #' .. tostring(fh_ab_preset_idx) .. ': +' .. _added .. ((_skipped > 0) and ' ({aaaaaa}' .. _skipped .. ' уже есть{ffffff})' or ''), 0xFFFFFF) end end imgui.PopStyleColor(2) end end imgui.Spacing() local is_vc2 = (lv.serverId == 0) local currency = is_vc2 and 'VC$' or 'SA$' local _raw_items = _G.arz_detail_tab == 0 and (lv.items_sell or {}) or (lv.items_buy or {}) local _raw_prices = _G.arz_detail_tab == 0 and (lv.price_sell or {}) or (lv.price_buy or {}) local _raw_counts = _G.arz_detail_tab == 0 and (lv.count_sell or {}) or (lv.count_buy or {}) local items_arr, prices_arr, counts_arr = {}, {}, {} local _preset_names = {} for _, p in ipairs(fh_lv_autosell_preset or {}) do if p.name then _preset_names[p.name:lower()] = true end end for _, p in ipairs(fh_lv_autobuy_preset or {}) do if p.name then _preset_names[p.name:lower()] = true end end for i, iid in ipairs(_raw_items) do local _bid, _ench = _G._bqs3v(iid) local _nm = _G._rgn9z(_bid) if _ench ~= '' then _nm = _nm .. ' (' .. _ench .. ')' end local _tag = mh_arz_items_db[_bid] or _nm if not _preset_names[(_tag or ''):lower()] then table.insert(items_arr, iid) table.insert(prices_arr, _raw_prices[i]) table.insert(counts_arr, _raw_counts[i]) end end imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(bg + .04, bg + .037, bg + .02, _G._mh_wa or 1)) local list_h_sw = imgui.GetContentRegionAvail().y if imgui.BeginChild('##arz_sw_items', imgui.ImVec2(-1, list_h_sw), false) then do local _sw2 = _G._sw if _sw2.drag_y ~= 0 then imgui.SetScrollY(math.max(0, imgui.GetScrollY() - _sw2.drag_y)) end end local show_tags = true local col_w = show_tags and {cw_arz*0.36, cw_arz*0.17, cw_arz*0.12, cw_arz*0.09, cw_arz*0.24} or {cw_arz*0.46, cw_arz*0.20, cw_arz*0.18, cw_arz*0.14} local ncols = show_tags and 5 or 4 imgui.Columns(ncols, '##arz_sw_hdr', false) for ci, cw_v in ipairs(col_w) do imgui.SetColumnWidth(ci - 1, cw_v) end local hc = imgui.ImVec4(ar * 0.55, ag * 0.55, ab * 0.35, 1) imgui.TextColored(hc, u8' Предмет'); imgui.NextColumn() imgui.TextColored(hc, u8' Цена'); imgui.NextColumn() imgui.TextColored(hc, u8' Кол-во'); imgui.NextColumn() imgui.TextColored(hc, u8' Валюта'); imgui.NextColumn() if show_tags then imgui.TextColored(hc, u8' Тег'); imgui.NextColumn() end imgui.Columns(1) imgui.Separator() if #items_arr == 0 then imgui.Spacing(); imgui.TextDisabled(u8' Список пуст.') end if not _G._cached_item_lookups then _G._cached_item_lookups = {} end for ii, iid in ipairs(items_arr) do local _lk = tostring(iid) if not _G._cached_item_lookups[_lk] then local _bid, _ench = _G._bqs3v(iid) local _nm = _G._rgn9z(_bid) local _full = _nm .. (_ench ~= '' and (' (' .. _ench .. ')') or '') local _tag = mh_arz_items_db[_bid] or _nm _G._cached_item_lookups[_lk] = {bid=_bid, full=_full, tag=_tag} end local _cl = _G._cached_item_lookups[_lk] local bid = _cl.bid local nm_full = _cl.full local _tag_nm = _cl.tag local price = prices_arr[ii] local cnt = counts_arr[ii] local row_bg = (ii % 2 == 0) and imgui.ImVec4(bg + .06, bg + .055, bg + .03, 0.5) or imgui.ImVec4(0, 0, 0, 0) imgui.PushStyleColor(imgui.Col.ChildBg, row_bg) imgui.Columns(ncols, '##arz_sw_row' .. ii, false) local cur_sp = imgui.GetCursorScreenPos() local cur_lh = imgui.GetTextLineHeight() local _arz_tag = mh_get_item_tag(_tag_nm) local _arz_tpfx = '' if _arz_tag == 'watch' then _arz_tpfx = fa.EYE .. ' ' elseif _arz_tag == 'skip' then _arz_tpfx = fa.BAN .. ' ' elseif _arz_tag == 'fav' then _arz_tpfx = fa.STAR .. ' ' end local nm_str = _arz_tpfx .. u8(' ' .. nm_full) local nm_tw = imgui.CalcTextSize(nm_str).x local nm_cw = col_w[1] - 6 imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0, 0, 0, 0)) if imgui.Selectable('##arzswt' .. ii, false, imgui.SelectableFlags.AllowDoubleClick, imgui.ImVec2(nm_cw, cur_lh + 2)) then if _tag_nm and _tag_nm ~= '' and _tag_nm ~= '?' then _G.mkt_detail_item = _tag_nm _G.mkt_detail_src = 'cp' _G.mkt_detail_open = true end end imgui.PopStyleColor() local dl_sw = imgui.GetWindowDrawList() dl_sw:PushClipRect(cur_sp, imgui.ImVec2(cur_sp.x + nm_cw, cur_sp.y + cur_lh + 2), true) local txt_off = 0 if nm_tw > nm_cw then local sd = nm_tw - nm_cw + 8 local spd = 1.8 local spt = sd / 38 + 2 * spd local sph = math.fmod(imgui.GetTime() + ii * 0.6, spt) if sph > spd then txt_off = math.min((sph - spd) * 38, sd) end if sph >= spt - spd then txt_off = sd end end local _arz_col32 = 0xFFFFFFFF if _arz_tag == 'fav' then _arz_col32 = 0xFFFFD700 elseif _arz_tag == 'skip' then _arz_col32 = 0xFF888888 elseif _arz_tag == 'watch' then _arz_col32 = 0xFF6ACFFF end dl_sw:AddText(imgui.ImVec2(cur_sp.x - txt_off, cur_sp.y), _arz_col32, nm_str) dl_sw:PopClipRect() imgui.NextColumn() imgui.TextColored(imgui.ImVec4(lp_r, lp_g, lp_b, 1), u8(' ' .. _jsb6t(price))) imgui.NextColumn() imgui.Text(u8(' ' .. tostring(cnt or '?') .. ' шт.')) imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.55, 0.55, 0.55, 1), u8(' ' .. currency)) imgui.NextColumn() if show_tags then local _tbw = (col_w[5] - 8 * d) / 3 local _wc = _arz_tag == 'watch' and imgui.ImVec4(0.15, 0.50, 0.85, 1) or imgui.ImVec4(0.08, 0.16, 0.30, 1) imgui.PushStyleColor(imgui.Col.Button, _wc) if imgui.SmallButton(_ic_eye .. '##tw' .. ii) then mh_set_item_tag(_tag_nm, _arz_tag == 'watch' and nil or 'watch'); _G.arz_cache_key = nil end imgui.PopStyleColor(); imgui.SameLine(0, 3 * d) local _sc = _arz_tag == 'skip' and imgui.ImVec4(0.50, 0.12, 0.12, 1) or imgui.ImVec4(0.22, 0.06, 0.06, 1) imgui.PushStyleColor(imgui.Col.Button, _sc) if imgui.SmallButton(_ic_ban .. '##ts' .. ii) then mh_set_item_tag(_tag_nm, _arz_tag == 'skip' and nil or 'skip'); _G.arz_cache_key = nil end imgui.PopStyleColor(); imgui.SameLine(0, 3 * d) local _fc = _arz_tag == 'fav' and imgui.ImVec4(0.55, 0.42, 0.04, 1) or imgui.ImVec4(0.22, 0.17, 0.02, 1) imgui.PushStyleColor(imgui.Col.Button, _fc) if imgui.SmallButton(_ic_star .. '##tf' .. ii) then mh_set_item_tag(_tag_nm, _arz_tag == 'fav' and nil or 'fav'); _G.arz_cache_key = nil end imgui.PopStyleColor() imgui.NextColumn() end imgui.Columns(1) imgui.PopStyleColor() end imgui.EndChild() end imgui.PopStyleColor() imgui.EndChild() end imgui.Separator() local _btn_h = 34 * d local _bw3 = (cw_arz - 8 * d) / 3 imgui.PushStyleColor(imgui.Col.Button, _mh_bc((_G._mh_sb_r or 0.10)*0.55,(_G._mh_sb_g or 0.45)*0.55,(_G._mh_sb_b or 0.10)*0.55,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4((_G._mh_sb_r or 0.10)*0.85,(_G._mh_sb_g or 0.45)*0.85,(_G._mh_sb_b or 0.10)*0.85, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.ButtonActive, _mh_bca(0.14, 0.55, 0.20, 1)) if imgui.Button(_ic_gps .. ' ' .. u8'GPS##arz_sw_gps', imgui.ImVec2(_bw3, _btn_h)) then sampSendChat('/findilavka ' .. tostring(lv.LavkaUid or 1)) end imgui.PopStyleColor(3) imgui.SameLine(0, 4 * d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(_G._mh_sb_r or 0.10*0.78,_G._mh_sb_g or 0.45*0.78,_G._mh_sb_b or 0.10*0.78,0.85)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4((_G._mh_sb_r or 0.10)*1.22,(_G._mh_sb_g or 0.45)*1.22,(_G._mh_sb_b or 0.10)*1.22, _G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.4, 1, 0.4, 1)) if imgui.Button(_ic_phone .. ' ' .. u8'Позвонить##arz_sw_call', imgui.ImVec2(_bw3, _btn_h)) then local _owner_nick = lv.username or '' if _owner_nick ~= '' then lua_thread.create(function() local _fid = nil for _pid = 0, 999 do local _ok, _pn = pcall(sampGetPlayerNickname, _pid) if _ok and _pn and _pn:lower() == _owner_nick:lower() then _fid = _pid; break end end if _fid then sampSendChat('/call ' .. _fid) else mh_notify('[MH] {ff9966}Игрок ' .. _owner_nick .. ' не онлайн', 0xFFFFFF) end end) end end imgui.PopStyleColor(3) imgui.SameLine(0, 4 * d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.32, 0.08, 0.08, 1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.50, 0.12, 0.12, _G._mh_wa or 1)) if imgui.Button(_ic_x .. ' ' .. u8'Закрыть##arz_sw_close', imgui.ImVec2(-1, _btn_h)) then _G._arz_shop_win_open = false _G.arz_detail = nil end imgui.PopStyleColor(2) end if not _sw_closed[0] then _G._arz_shop_win_open = false _G.arz_detail = nil end imgui.End() imgui.PopStyleColor(6) end ) _G.mh_qpop_open = false _G.mh_qpop_item = '' _G.mh_qpop_cache = nil _G.mh_qpop_cache_nm = '' _G._mh_qpop_opened_at = nil function _mh_qpop_prices(nm) if not nm or nm == '' then return nil end local mp = _mh_get_mkt_price(nm) local e = fh_mkt_prices and fh_mkt_prices[nm] local lv = _G._lv_shops_cache and _G._lv_shops_cache[nm:lower()] local out = {} out.mkt_today = mp and mp.today or (e and (e.cp_sp or e.s_avg) or nil) out.mkt_7 = mp and mp.avg7 or nil out.mkt_30 = mp and mp.avg30 or nil out.lv_sell = lv and lv.sell or (e and e.s_avg or nil) out.lv_buy = lv and lv.buy or (e and e.b_avg or nil) local st = _G._dtl_stats if st and _G.mkt_detail_item == nm then out.sh_s_7 = st.sh_s_7 or out.mkt_7 out.sh_s_30 = st.sh_s_30 or out.mkt_30 out.sh_b_7 = st.sh_b_7 out.sh_b_30 = st.sh_b_30 else out.sh_s_7 = out.mkt_7 out.sh_s_30 = out.mkt_30 end local nm_lo = nm:lower() local cnt_s, cnt_b = 0, 0 for _, _lv in ipairs(mh_arz_data or {}) do for i2, iid in ipairs(_lv.items_sell or {}) do local _raw_id = tostring(iid):match('^(%d+)') local _lv_nm = mh_arz_items_db and mh_arz_items_db[tonumber(_raw_id)] if _lv_nm and _lv_nm:lower() == nm_lo then cnt_s = cnt_s + 1; break end end for i2, iid in ipairs(_lv.items_buy or {}) do local _raw_id = tostring(iid):match('^(%d+)') local _lv_nm = mh_arz_items_db and mh_arz_items_db[tonumber(_raw_id)] if _lv_nm and _lv_nm:lower() == nm_lo then cnt_b = cnt_b + 1; break end end end out.cnt_sell = cnt_s out.cnt_buy = cnt_b return out end imgui.OnFrame( function() return _G.mkt_detail_open == true end, function() local d = settings.general.custom_dpi * 0.73 local sw, sh = imgui.GetIO().DisplaySize.x, imgui.GetIO().DisplaySize.y local pop_w = math.min(sw - 16, 853*d) local pop_h = math.min(sh - 20, sh * 0.88) if not _G._dtl_win_init then imgui.SetNextWindowSize(imgui.ImVec2(pop_w, pop_h), imgui.Cond.Always) imgui.SetNextWindowPos(imgui.ImVec2(sw/2, sh/2), imgui.Cond.Always, imgui.ImVec2(0.5, 0.5)) imgui.SetNextWindowFocus() _G._dtl_win_init = true else imgui.SetNextWindowSize(imgui.ImVec2(pop_w, pop_h), imgui.Cond.Once) end imgui.GetIO().ConfigWindowsMoveFromTitleBarOnly = true local closed = imgui.new.bool(true) local _wflags = imgui.WindowFlags.NoCollapse if imgui.Begin(u8'\xd1\xf2\xe0\xf2\xe8\xf1\xf2\xe8\xea\xe0 \xf2\xee\xe2\xe0\xf0\xe0##dtlpop', closed, _wflags) then local _btn_h = 34*d local _btn_gap = 3*d local _sep_h = 6*d local _total_h = imgui.GetWindowHeight() local _title_h = imgui.GetCursorPosY() local _content_h = _total_h - _title_h - _btn_h - _sep_h - 20*d if imgui.BeginChild('##dtl_scroll', imgui.ImVec2(-1, _content_h), false) then _dpn1w() if _G.mkt_detail_item then MH_util.hmc6p(_G.mkt_detail_item, _G.mkt_detail_src) end imgui.EndChild() end imgui.Separator() local _cw4 = (imgui.GetWindowContentRegionWidth() - _btn_gap * 3) / 4 if _G.mkt_detail_item then local _dtag = mh_get_item_tag(_G.mkt_detail_item) local _wc = _dtag=='watch' and imgui.ImVec4(0.15,0.5,0.85,1) or imgui.ImVec4(0.12,0.28,0.48,1) imgui.PushStyleColor(imgui.Col.Button, _wc) if imgui.Button(_ic_eye..' '.._cyr5f('\xd1\xec\xee\xf2\xf0\xe5\xf2\xfc##dtw'), imgui.ImVec2(_cw4, _btn_h)) then mh_set_item_tag(_G.mkt_detail_item, _dtag=='watch' and nil or 'watch') end imgui.PopStyleColor() imgui.SameLine(0, _btn_gap) local _sc = _dtag=='skip' and imgui.ImVec4(0.45,0.12,0.12,1) or imgui.ImVec4(0.28,0.08,0.08,1) imgui.PushStyleColor(imgui.Col.Button, _sc) if imgui.Button(_ic_ban..' '.._cyr5f('\xcd\xe5 \xe1\xf0\xe0\xf2\xfc##dts'), imgui.ImVec2(_cw4, _btn_h)) then mh_set_item_tag(_G.mkt_detail_item, _dtag=='skip' and nil or 'skip') end imgui.PopStyleColor() imgui.SameLine(0, _btn_gap) local _fc = _dtag=='fav' and imgui.ImVec4(0.55,0.42,0.04,1) or imgui.ImVec4(0.30,0.22,0.04,1) imgui.PushStyleColor(imgui.Col.Button, _fc) if imgui.Button(_ic_star..' '.._cyr5f('\xc8\xe7\xe1\xf0\xe0\xed\xed\xee\xe5##dtf'), imgui.ImVec2(_cw4, _btn_h)) then mh_set_item_tag(_G.mkt_detail_item, _dtag=='fav' and nil or 'fav') end imgui.PopStyleColor() imgui.SameLine(0, _btn_gap) end imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.32,0.08,0.08,1)) if imgui.Button(_ic_x..' '..u8'\xc7\xe0\xea\xf0\xfb\xf2\xfc##dtlclose', imgui.ImVec2(-1, _btn_h)) then _G.mkt_detail_open = false end imgui.PopStyleColor() end if not closed[0] then _G.mkt_detail_open = false end imgui.End() end )