-- [FIX 5.2.8] Enchant (+N) preserved from scan/cloud merge. script_name("Market Helper") script_description("Arizona RP Market Scanner & Auto Trade") script_author("Shinik_Pupckin") script_version("5.2.8") _MH_VER = "5.2.8" MH_TOAST_DURATION = nil function _mh_toast_type(text, color) local t = tostring(text or '') if t:find('[MH ARB]', 1, true) then return 'arbitrage' 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' elseif t:find('[MH TIMER]', 1, true) then return 'reminder' else return 'info' end end function _mh_toast_clean(text) local t = tostring(text or '') t = t:gsub('%[MH[^%]]-%]%s*', '') t = t:gsub('{%x%x%x%x%x%x}', '') 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 function _mh_notify_category(text) local t = tostring(text or '') if t:find('%[MH \xc0\xe2\xf2\xee%]') and t:find(' @ %$') then return 'sell_item' end if t:find('%[MH \xc0\xe2\xf2\xee%]') and (t:find('\xd1\xea\xf3\xef:') or t:find('\xd1\xea\xf3\xef \xf8\xf2')) then return 'buy_item' end if t:find('%[MH \xc0\xe2\xf2\xee%]') or t:find('%[FH \xc0\xe2\xf2\xee%]') then if t:find(' @ %$') then return 'sell_item' end if t:find('\xd1\xea\xf3\xef') then return 'buy_item' end if t:find('\xc2\xfb\xea\xeb\xe0\xe4\xea') or t:find('\xf1\xf2\xe0\xea\xee\xe2') or t:find('\xdf\xf7\xe5\xe5\xea') then return 'sell_status' end if t:find('\xef\xee\xea\xf3\xef') or t:find('\xef\xee\xe7\xe8\xf6\xe8') then return 'buy_status' end return 'sell_status' end return 'other' end _G.mh_notify_log = _G.mh_notify_log or {} local function _mh_notify_should_toast(category) local nf = settings and settings.notify_filters if not nf then return true end if category == 'sell_item' then return nf.sell_item ~= false end if category == 'buy_item' then return nf.buy_item ~= false end if category == 'sell_status' then return nf.sell_status ~= false end if category == 'buy_status' then return nf.buy_status ~= false end if category == 'watch' then return nf.watch ~= false end if category == 'fav' then return nf.fav ~= false end if category == 'arb' then return nf.arb ~= false end if category == 'reminder' then return nf.reminder ~= false end return nf.other ~= false end function mh_notify(text, color, forced_category) sampAddChatMessage(text, color) local _cat = forced_category or _mh_notify_category(text) local _clean = _mh_toast_clean(text) table.insert(_G.mh_notify_log, { time = os.date('%H:%M:%S'), ts = os.time(), text = _clean, raw = tostring(text or ''), color = color, category = _cat, }) local _max_hist = (settings and settings.notify_history_max) or 500 while #_G.mh_notify_log > _max_hist do table.remove(_G.mh_notify_log, 1) end if MH_Notify and _mh_notify_should_toast(_cat) then pcall(function() MH_Notify.add(_clean, _mh_toast_type(text, color), MH_TOAST_DURATION) end) _mh_play_alert_sound() end end function mh_notify_local(text, color, forced_category) local _cat = forced_category or _mh_notify_category(text) local _clean = _mh_toast_clean(text) table.insert(_G.mh_notify_log, { time = os.date('%H:%M:%S'), ts = os.time(), text = _clean, raw = tostring(text or ''), color = color, category = _cat, }) local _max_hist = (settings and settings.notify_history_max) or 500 while #_G.mh_notify_log > _max_hist do table.remove(_G.mh_notify_log, 1) end if MH_Notify and _mh_notify_should_toast(_cat) then pcall(function() MH_Notify.add(_clean, _mh_toast_type(text, color), MH_TOAST_DURATION) end) _mh_play_alert_sound() 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 .. '/FH_daily_prices') end local effil = require('effil') _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)() _G.MH_SOUND_ENABLED = (_G.MH_SOUND_ENABLED ~= false) _G._mh_sound_path = getWorkingDirectory():gsub('\\','/'):gsub('/$','') .. '/MarketHelper/sound.mp3' _G._mh_sound_handle = nil _G._mh_sound_warned = false _G._mh_sound_dl_busy = false function _G._mh_sound_download_async() if _G._mh_sound_dl_busy then return end _G._mh_sound_dl_busy = true 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 < 100 then return false, 'too small: '..tostring(#data)..'b' end return true, data end)(_vbr7n .. '/sound.mp3') local _elapsed = 0 while _elapsed < 30000 do wait(300); _elapsed = _elapsed + 300 local _st, _er = _thr:status() if not _st or _er then _G._mh_sound_dl_busy = false; return end if _st == 'completed' or _st == 'canceled' then local _ok2, _result = _thr:get() if _ok2 and _result then _mh_ensure_dir(getWorkingDirectory():gsub('\\','/'):gsub('/$','') .. '/MarketHelper') local _f = io.open(_G._mh_sound_path, 'wb') if _f then _f:write(_result); _f:close() end end _G._mh_sound_dl_busy = false return end end _G._mh_sound_dl_busy = false end) end function _G._mh_load_sound() local _f = io.open(_G._mh_sound_path, 'rb') if not _f then _G._mh_sound_download_async() return false end _f:close() local ok, handle = pcall(loadAudioStream, _G._mh_sound_path) if ok and handle ~= nil and handle ~= false and handle ~= 0 then _G._mh_sound_handle = handle return true end _G._mh_sound_handle = nil return false end function _mh_play_alert_sound() if not _G.MH_SOUND_ENABLED then return end if not _G._mh_sound_handle then if not _G._mh_load_sound() then return end end pcall(setAudioStreamState, _G._mh_sound_handle, 1) end do local _f0 = io.open(_G._mh_sound_path, 'rb') if _f0 then _f0:close() else _G._mh_sound_download_async() end end _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 do 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 end do 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 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 function _mh_is_fake_nick(nick) if not nick or nick == '' then return true end return tostring(nick):lower():match('^player_[%x%d]+$') ~= nil end function _mh_get_live_nick() local nick = '' pcall(function() local ok, pid = sampGetPlayerIdByCharHandle(PLAYER_PED) if ok then nick = sampGetPlayerNickname(pid) or '' end end) if not _mh_is_fake_nick(nick) then return nick end local nick2 = '' pcall(function() nick2 = sampGetCurrentPlayerName() or '' end) if not _mh_is_fake_nick(nick2) then return nick2 end local nick3 = '' pcall(function() nick3 = sampGetPlayerNickname(0) or '' end) if not _mh_is_fake_nick(nick3) then return nick3 end local nick4 = '' pcall(function() nick4 = (settings.premium and settings.premium.nick) or '' end) if not _mh_is_fake_nick(nick4) then return nick4 end local nick5 = _G._mh_nick_v_cached or '' if not _mh_is_fake_nick(nick5) then return nick5 end return (nick ~= '' and nick) or (nick2 ~= '' and nick2) or (nick3 ~= '' and nick3) or (nick4 ~= '' and nick4) or nick5 end 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") or tostring(title):find("\xf1\xf2\xe0\xf2\xe8\xf1\xf2\xe8\xea\xe0") 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) _G._mh_nick_v_cached = _mh_nick_v lua_thread.create(function() wait(3000) if _G._challenge_verify then local _ch_token, _ch_outdated, _ch_tampered = _G._challenge_verify() if _ch_token and _ch_token ~= '' then _G._mh_session_tok = _ch_token _G._mh_session_tok_ts = os.time() if _ch_outdated then _G._mh_upd_state = 'outdated' else _G._mh_upd_state = 'ok' end elseif _ch_tampered then _G._mh_upd_state = 'tampered' else _G._mh_upd_state = 'error' end else _G._mh_upd_state = 'error' 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('lib.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 _c1, _c2 = settings.catch_loot_log, settings.catch_open_log settings.catch_loot_log, settings.catch_open_log = nil, nil local ok, j = pcall(encodeJson, settings) settings.catch_loot_log, settings.catch_open_log = _c1, _c2 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 -- The game can reveal a new number after relisting the same shop. -- Keep the runtime value, relog value and own-shop snapshots in sync immediately. function _mh_sync_current_own_shop_num(_num) local _n = tonumber(_num) if not _n or _n < 1 then return nil end mh_own_shop_num = _n if settings.general then settings.general.own_shop_num_saved = _n end if type(settings.my_shop_snapshot) == 'table' then settings.my_shop_snapshot.shop_num = _n end if type(_G._mh_own_shop_live_snapshot) == 'table' then _G._mh_own_shop_live_snapshot.shop_num = _n end _G._mh_own_shop_capture_num = _n pcall(_wfn7p) return _n end -- Read the number shown by the game for the shop currently next to the player. -- This is deliberately checked before the relog snapshot, because after relisting -- the saved number may belong to the previous shop. function _mh_detect_current_shop_num_nearby() local _best_d, _best_n = 12.0, nil local _px, _py = nil, nil pcall(function() _px, _py = getCharCoordinates(PLAYER_PED) end) if not _px or not _py then return nil end for _tid = 0, 2047 do local _ok, _tt, _, _tpx, _tpy = pcall(sampGet3dTextInfoById, _tid) if _ok and _tt and _tpx and _tpy then local _txt = tostring(_tt) local _n = _txt:match('Лавка%s*[#№Nn]?%s*(%d+)') if not _n then _n = _txt:match('лавка%s*[#№Nn]?%s*(%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 < _best_d then _best_d, _best_n = _d, _num end end end end end return _best_n 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 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 return true, resp end function _mh_ads_delete_req(_url, _nick, _sess) local requests = require('requests') local hdrs = {} if _nick and _nick ~= '' then hdrs['X-MH-Nick'] = _nick end _sess = (_sess and _sess ~= '') and _sess or '' if _sess ~= '' then hdrs['X-MH-Session'] = _sess end local ok, resp = pcall(requests.request, 'DELETE', _url, { headers = hdrs }) if not ok then return false, 'connection failed' end if not resp then return false, 'connection failed' end resp.json = nil; resp.xml = nil 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 = 'Home', id = 33 }, { 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 if _mh_price_dbg then local _cnt = 0 for _ in pairs(mh_arz_items_db) do _cnt = _cnt + 1 end _mh_price_dbg('PRICE INJECT: каталог предметов с API догружен, записей=' .. tostring(_cnt)) end 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 '' 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 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_dlg_cp(s) if type(s) ~= 'string' or s == '' then return s or '' end if not s:find('[\128-\255]') then return s end local rest = s:gsub('[\194-\223][\128-\191]', '') :gsub('[\224-\239][\128-\191][\128-\191]', '') :gsub('[\240-\244][\128-\191][\128-\191][\128-\191]', '') if rest:find('[\128-\255]') then return s end return (s:gsub('[\194-\244][\128-\191]*', function(c) local n = #c if n == 2 then local a, b = c:byte(1, 2) if a == 208 or a == 209 then local cp = (a - 192) * 64 + (b - 128) if cp >= 1040 and cp <= 1103 then return string.char(cp - 1040 + 192) end if cp == 1025 then return '\168' end if cp == 1105 then return '\184' end elseif c == '\194\160' then return ' ' elseif c == '\194\171' then return '\171' elseif c == '\194\187' then return '\187' end elseif c == '\226\132\150' then return '\185' elseif c == '\226\128\147' or c == '\226\128\148' then return '-' end return '?' 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 {} do 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 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 = _mh_get_live_nick() 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) local _mh_tg_worker = nil local _mh_push_ch = effil.channel(4) do 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) end 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() 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 _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 'Неизв.' 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 _G._mh_payday_buf = nil _G._mh_payday_collecting = false function _mh_payday_flush() local p = _G._mh_payday_buf _G._mh_payday_buf = nil _G._mh_payday_collecting = false if not p then return end if not settings.telegram or not settings.telegram.notify_payday then return end local now_s = os.date('%H:%M %d.%m.%Y') local function money(v) return '$' .. _kcr3y(v or 0) end local function gain(v) v = v or 0 if v == 0 then return '' end return ' ({aaffaa}+' .. _kcr3y(v) .. '{ffffff})' end local function gain_utf(v) v = v or 0 if v == 0 then return '' end return ' (+' .. _kcr3y(v) .. ')' end local _az_part = '' if p.donate_total then local function gain_az(v) v = v or 0 if v == 0 then return '' end return ' ({aaffaa}+' .. _kcr3y(v) .. '{ffffff})' end _az_part = ' {aaaaaa}AZ: {ffffff}' .. _kcr3y(p.donate_total) .. gain_az(p.donate_gain) end mh_notify('[MH] {ffcc00}PayDay {ffffff}Банк: ' .. money(p.bank_total) .. gain(p.bank_gain) .. ' {aaaaaa}Депозит: {ffffff}' .. money(p.dep_total) .. gain(p.dep_gain) .. _az_part, 0xFFCC44) local msg = '[PAYDAY] Зарплата\n' .. '--------------------\n' .. 'Банк: ' .. money(p.bank_total) .. gain_utf(p.bank_gain) .. '\n' .. 'Депозит: ' .. money(p.dep_total) .. gain_utf(p.dep_gain) if p.family_fee and p.family_fee > 0 then msg = msg .. '\n -> в бюджет семьи: ' .. money(p.family_fee) end msg = msg .. '\n' .. 'Зарплата: ' .. money(p.salary) .. '\n' if p.bonus_sum and p.bonus_sum > 0 then msg = msg .. 'Доп. доход (' .. (p.bonus_name or 'бонус') .. '): ' .. money(p.bonus_sum) .. '\n' end msg = msg .. '--------------------\n' if p.level then msg = msg .. 'Уровень: ' .. p.level if p.resp_cur and p.resp_max then msg = msg .. ' (' .. p.resp_cur .. '/' .. p.resp_max .. ' респектов)' end if p.exp_gain and p.exp_gain > 0 then msg = msg .. gain_utf(p.exp_gain) .. ' EXP' end msg = msg .. '\n' end if p.donate_total then msg = msg .. 'Донат-счёт: ' .. p.donate_total .. ' AZ' .. gain_utf(p.donate_gain) .. '\n' end local total_income = (p.bank_gain or 0) + (p.dep_gain or 0) + (p.bonus_sum or 0) if total_income > 0 then msg = msg .. '--------------------\n' msg = msg .. 'Итого за PayDay: ' .. money(total_income) .. '\n' end msg = msg .. '--------------------\n' .. now_s mh_tg_send(msg, true) end function _mh_payday_on_line(clean) if clean:find('БАНКОВСКИЙ ЧЕК') then _G._mh_payday_buf = {} _G._mh_payday_collecting = true return true end if not _G._mh_payday_collecting then return false end local p = _G._mh_payday_buf local bt, bg = clean:match('[Тт]екущая сумма в банке:%s*%$?([%d%s,%.]+)%s*%(%+%$?([%d%s,%.]+)%)') if bt then p.bank_total = tonumber((bt:gsub('[%s,%.]', ''))) p.bank_gain = tonumber((bg:gsub('[%s,%.]', ''))) return true end local lvl, rc, rm = clean:match('(%d+)%-й уровень и (%d+)/(%d+) респект') if lvl then p.level = tonumber(lvl); p.resp_cur = tonumber(rc); p.resp_max = tonumber(rm) local eg = clean:match('%(%+(%d+)%s*EXP%)') if eg then p.exp_gain = tonumber(eg) end return true end local dt, dg = clean:match('[Тт]екущая сумма на депозите:%s*%$?([%d%s,%.]+)%s*%(%+%$?([%d%s,%.]+)%)') if dt then p.dep_total = tonumber((dt:gsub('[%s,%.]', ''))) p.dep_gain = tonumber((dg:gsub('[%s,%.]', ''))) local ff = clean:match('бюджет семьи:%s*%$?([%d%s,%.]+)') if ff then p.family_fee = tonumber((ff:gsub('[%s,%.]', ''))) end return true end local sal = clean:match('[Оо]бщая заработная плата:%s*%$?([%d%s,%.]+)') if sal then p.salary = tonumber((sal:gsub('[%s,%.]', ''))) return true end local don, dgn = clean:match('[Бб]аланс на донат%-сче[тё]т:%s*([%d%s,%.]+)%s*AZ%s*%(%+([%d%s,%.]+)%s*AZ%)') if don then p.donate_total = tonumber((don:gsub('[%s,%.]', ''))) p.donate_gain = tonumber((dgn:gsub('[%s,%.]', ''))) return true end if not don then local don2 = clean:match('[Бб]аланс на донат%-сче[тё]т:%s*([%d%s,%.]+)%s*AZ') if don2 then p.donate_total = tonumber((don2:gsub('[%s,%.]', ''))); return true end end local bsum, bname = clean:match('[Вв]ы получили%s*%+%$?([%d%s,%.]+)%s*за%s*"?([^"\n]+)"?') if bsum then p.bonus_sum = (p.bonus_sum or 0) + (tonumber((bsum:gsub('[%s,%.]', ''))) or 0) p.bonus_name = bname:match('^%s*(.-)%s*$') return true end return false 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 if 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 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 mh_notify('[MH] {6acfff}' .. tostring(it.nm or it.base_nm) .. ' {ffffff}$' .. _kcr3y(it.price) .. ' {aaaaaa}#' .. tostring(it.uid) .. ' ' .. tostring(it.owner or '?'), 0x6ACFFF, 'watch') if settings.telegram and settings.telegram.notify_watch then 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 end ::_wbg_skip:: wait(300000) end end) lua_thread.create(function() wait(90000) while true do if 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_notify('[MH] {ffaa00}' .. tostring(nm_full or base_nm) .. ' {ffffff}$' .. _kcr3y(price) .. ' {aaaaaa}(-' .. _pct .. '% рынка)', 0xFFAA00, 'fav') if settings.telegram and settings.telegram.notify_fav then 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 end ::_fav_lv_next:: end end ::_fav_skip:: wait(300000) end end) lua_thread.create(function() wait(120000) while true do 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 _lavka_status = 'Не проверено' if not (fh_lv_autosell_running or fh_lv_autobuy_running) then _G._mh_lavka_check_pending = true _G._mh_lavka_check_result = nil local _ok_cmd = pcall(sampSendChat, '/lavka') if _ok_cmd then local _waited = 0 while _waited < 3000 and not _G._mh_lavka_check_result do wait(100); _waited = _waited + 100 end end if _G._mh_lavka_check_result == 'ok' then _lavka_status = 'Установлена' elseif _G._mh_lavka_check_result == 'gone' then _lavka_status = 'Не установлена' end _G._mh_lavka_check_pending = false end local _msg = ('[MH] Статус: в игре '..os.date('%H:%M')..'\n' ..'--------------------\n' ..'Игрок: '.._pnm..'\n' ..'Сервер: '.._srv..'\n' ..'HP: '..tostring(_hp)..'\n' ..'Лавка: '.._lavka_status..'\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 if _bcn4w() and settings.notify_filters and settings.notify_filters.arb ~= false 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 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 = '' _prem_check_seq = 0 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 local _b_ok = _bcn4w() if not (_qtp7v and _G._pf2 and _G._pf3 and _G._pf4) then return false end return _b_ok 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) 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 _wfn7p() if _G._mh_collection_push then pcall(_G._mh_collection_push) end if _G._mh_market_tags_push then pcall(_G._mh_market_tags_push) end if _G._mh_shops_tags_push then pcall(_G._mh_shops_tags_push) end end function mh_tag_key(bid) return _rgn9z(bid) end local function _hns5r(url, cb) local thread = effil.thread(_hfn2t)(url) lua_thread.create(function() local deadline = os.clock() + 20 while true do wait(30) if os.clock() >= deadline then pcall(function() thread:cancel() end) return cb(nil, nil, 'timeout') end 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 = _mh_get_live_nick() 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 if parsed.outdated then _G._mh_upd_latest = parsed.latest or _G._mh_upd_latest return parsed.session_token, true, false end return parsed.session_token, false, false end if ok2 and parsed and parsed.valid == false then return nil, false, true end end return nil, false, false end _G._challenge_verify = _challenge_verify end _prem_fail_streak = 0 _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 _prem_fail_streak = 0 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 _prem_fail_streak = _prem_fail_streak + 1 if _prem_fail_streak >= 3 then _mh_reset_prem() _sess_tok = '' _prem_fail_streak = 0 else lua_thread.create(function() wait(10000) _lkg8m() end) end end end end) end function _mh_prem_stats_recheck(on_done, timeout_sec) local done = false local found_nick = nil local prev_hook = sampev and sampev.onShowDialog or nil local function _finish(nick_result) if done then return end done = true if sampev then pcall(function() sampev.onShowDialog = prev_hook end) end if on_done then pcall(on_done, nick_result) end end if sampev then sampev.onShowDialog = function(id, style, title, btn1, btn2, text) if not done then local tl = tostring(title or ''):lower() local _is_stats_dialog = tl:find('stat') or tostring(title or ''):find('\xf1\xf2\xe0\xf2\xe8\xf1\xf2\xe8\xea\xe0') if _is_stats_dialog then local raw = tostring(text or '') local nm = raw:match('[%s|]?[Ии]мя:%s*{?%x*}?%s*([A-Za-z0-9_]+)') if nm and not nm:find('^Player_%x+$') then found_nick = nm if prev_hook then pcall(prev_hook, id, style, title, btn1, btn2, text) end _finish(found_nick) return false elseif mh_debug_enabled then mh_notify('[MH DEBUG] /stats recheck: нет совпадения ника, текст: ' .. raw:sub(1, 200), 0xFFAA00) end end end if prev_hook then return prev_hook(id, style, title, btn1, btn2, text) end end end pcall(sampSendChat, '/stats') lua_thread.create(function() local deadline = os.clock() + (timeout_sec or 8) while os.clock() < deadline and not done do wait(200) end _finish(nil) end) end local function _fpc2t(key, callback) if _prem_checking then return end _prem_checking = true _prem_check_status = 'Проверка...' local _fpc2t_guard_id = (_prem_check_seq or 0) + 1 _prem_check_seq = _fpc2t_guard_id lua_thread.create(function() wait(25000) if _prem_checking and _prem_check_seq == _fpc2t_guard_id then _prem_checking = false _prem_check_status = 'Нет ответа от сервера, попробуйте ещё раз' if callback then callback(false, nil) end end end) 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 '') local function _accept(parsed) settings.premium.activated = true settings.premium.key = key local _my_disp = _mh_get_live_nick() 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 '' if (parsed.nick or '') ~= '' then _G._mh_nick_v_cached = parsed.nick end 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() _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) end _hns5r(url, function(code, text, err) _prem_checking = false _prem_check_seq = (_prem_check_seq or 0) + 1 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 pcall(function() local ok_pid, pid = pcall(sampGetPlayerIdByCharHandle, PLAYER_PED) if ok_pid and pid then local fresh = sampGetPlayerNickname(pid) or '' if not _mh_is_fake_nick(fresh) then _G._mh_nick_v_cached = fresh end end end) local my_nick = _mh_get_live_nick():lower() 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 _prem_check_status = 'Проверка ника через /stats...' _mh_prem_stats_recheck(function(stats_nick) local sn = (stats_nick or ''):lower():gsub('[_%-]',' ') if stats_nick and sn == srv_nick_n then _accept(parsed) else settings.premium.activated = false _prem_check_status = 'Ключ зарегистрирован на другого игрока' mh_notify('[MH] {ff4444}Premium: ключ зарегистрирован на ' .. (parsed.nick or '?'), 0xFFFFFF) if callback then callback(false, nil) end end end, 8) return end end _accept(parsed) 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 function _mh_deep_copy_list(src) local out = {} if type(src) ~= 'table' then return out end for i, v in ipairs(src) do if type(v) == 'table' then local nv = {} for k2, v2 in pairs(v) do nv[k2] = v2 end out[i] = nv else out[i] = v end end return out end _mh_translit_map = { ['\xe0']='a', ['\xe1']='b', ['\xe2']='v', ['\xe3']='g', ['\xe4']='d', ['\xe5']='e', ['\xa3']='e', ['\xe6']='zh', ['\xe7']='z', ['\xe8']='i', ['\xe9']='y', ['\xea']='k', ['\xeb']='l', ['\xec']='m', ['\xed']='n', ['\xee']='o', ['\xef']='p', ['\xf0']='r', ['\xf1']='s', ['\xf2']='t', ['\xf3']='u', ['\xf4']='f', ['\xf5']='h', ['\xf6']='ts', ['\xf7']='ch', ['\xf8']='sh', ['\xf9']='sch', ['\xfa']='', ['\xfb']='y', ['\xfc']='', ['\xfd']='e', ['\xfe']='yu', ['\xff']='ya', ['\xc0']='A', ['\xc1']='B', ['\xc2']='V', ['\xc3']='G', ['\xc4']='D', ['\xc5']='E', ['\xa1']='E', ['\xc6']='Zh', ['\xc7']='Z', ['\xc8']='I', ['\xc9']='Y', ['\xca']='K', ['\xcb']='L', ['\xcc']='M', ['\xcd']='N', ['\xce']='O', ['\xcf']='P', ['\xd0']='R', ['\xd1']='S', ['\xd2']='T', ['\xd3']='U', ['\xd4']='F', ['\xd5']='H', ['\xd6']='Ts', ['\xd7']='Ch', ['\xd8']='Sh', ['\xd9']='Sch', ['\xda']='', ['\xdb']='Y', ['\xdc']='', ['\xdd']='E', ['\xde']='Yu', ['\xdf']='Ya', } function _mh_transliterate(s) s = tostring(s or '') local out = {} for i = 1, #s do local ch = s:sub(i, i) out[#out+1] = _mh_translit_map[ch] or ch end return table.concat(out) end function _mh_safe_filename(s) s = _mh_transliterate(tostring(s or '')) s = s:gsub('[^%w%-_%.]', '_') s = s:gsub('%s+', '_') s = s:gsub('_+', '_') s = s:gsub('^_+', ''):gsub('_+$', '') if s == '' then s = 'preset' end if #s > 60 then s = s:sub(1, 60) end return s end function _mh_preset_dir() local dir = getWorkingDirectory():gsub('\\', '/'):gsub('/$', '') .. '/MarketHelper/Preset' _mh_ensure_dir(getWorkingDirectory():gsub('\\', '/'):gsub('/$', '') .. '/MarketHelper') _mh_ensure_dir(dir) return dir end function _mh_export_preset(kind, preset_name, items) local prefix = (kind == 'buy') and 'buy' or 'sell' local date_part = os.date('%d_%m') local fname = prefix .. '_' .. date_part .. '_' .. _mh_safe_filename(preset_name) .. '.json' local dir = _mh_preset_dir() local path = dir .. '/' .. fname local payload = { kind = prefix, name = preset_name, created = os.date('%d.%m.%Y %H:%M'), items = items, } local ok, j = pcall(encodeJson, payload) if not ok or not j then return false, 'encode_failed' end local f = io.open(path, 'w') if not f then return false, 'open_failed: '..tostring(path) end f:write(j) f:close() return true, path end function _mh_list_presets(kind) local prefix = (kind == 'buy') and 'buy_' or 'sell_' local dir = _mh_preset_dir() local out = {} local lfs_ok, lfs = pcall(require, 'lfs') if not lfs_ok or type(lfs) ~= 'table' or not lfs.dir then mh_notify('[MH] {ff6666}Импорт: модуль lfs недоступен ('..tostring(lfs)..'). Папка: '..tostring(dir), 0xFFFFFF) return out end local iter_ok, iter_err = pcall(function() local total_files, matched = 0, 0 for fname in lfs.dir(dir) do if fname ~= '.' and fname ~= '..' then total_files = total_files + 1 if fname:sub(1, #prefix) == prefix and fname:match('%.json$') then matched = matched + 1 local path = dir .. '/' .. fname local f = io.open(path, 'r') if f then local raw = f:read('*a') f:close() local ok, data = pcall(decodeJson, raw) if ok and type(data) == 'table' then table.insert(out, { fname = fname, path = path, name = data.name or fname, created = data.created or '', count = (type(data.items) == 'table') and #data.items or 0, items = data.items, }) else mh_notify('[MH] {ff6666}Импорт: не смог разобрать JSON в файле '..fname, 0xFFFFFF) end else mh_notify('[MH] {ff6666}Импорт: не смог открыть файл '..fname, 0xFFFFFF) end end end end if #out == 0 then mh_notify('[MH] {ffaa55}Импорт: в папке '..tostring(dir)..' всего файлов: '..tostring(total_files)..', подходящих под "'..prefix..'*.json": '..tostring(matched), 0xFFFFFF) end end) if not iter_ok then mh_notify('[MH] {ff6666}Импорт: ошибка чтения папки '..tostring(dir)..' — '..tostring(iter_err), 0xFFFFFF) end table.sort(out, function(a, b) return a.fname > b.fname end) return out end function _mh_import_preset_file(path) local f = io.open(path, 'r') if not f then return false, 'open_failed' end local raw = f:read('*a') f:close() local ok, data = pcall(decodeJson, raw) if not ok or type(data) ~= 'table' or type(data.items) ~= 'table' then return false, 'decode_failed' end return true, (data.name or 'Импорт'), data.items end function _mh_url_encode(s) s = tostring(s or '') s = s:gsub('([^%w%-%.%_%~])', function(c) return string.format('%%%02X', string.byte(c)) end) return s end _G._mh_cloud_open = false _G._mh_cloud_loaded_once = false _G._mh_cloud_list = {} _G._mh_cloud_total = 0 _G._mh_cloud_page = 1 _G._mh_cloud_loading = false _G._mh_cloud_error = nil _G._mh_cloud_sort = 'downloads' _G._mh_cloud_budget_max = 0 _G._mh_cloud_search = '' _G._mh_cloud_mine_only = false _G._mh_cloud_my_votes = {} _G._mh_buy_cloud_raw = {} _G._mh_cloud_view_open = false _G._mh_cloud_view_preset = nil function _mh_cloud_open_preset_view(cp) if not cp then return end _G._mh_cloud_view_preset = cp _G._mh_cloud_view_open = true if cp.id then cp.views = (cp.views or 0) + 1 local ok_j, j = pcall(encodeJson, {preset_id = cp.id}) if ok_j then _jmx9s(_vbr7n .. '/cloud-presets/view', j, function() end) end end end function _mh_cloud_current_server_id() 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 _idx if _sel > 1 then _idx = _sel elseif _live > 0 then _idx = _live + 1 end return _idx and (ARZ_SERVERS[_idx] or {}).id or -1 end function _mh_cloud_push_buy_preset() if not fh_lv_autobuy_preset or #fh_lv_autobuy_preset == 0 then mh_notify('[MH Cloud] {ffaa44}Пресет скупки пуст — нечего загружать.', 0xFFFFFF) return end local _pname = (settings.buy_presets and settings.buy_presets[fh_ab_preset_idx] and settings.buy_presets[fh_ab_preset_idx].name) or ('Скупка '..tostring(fh_ab_preset_idx)) local _nick = _mh_get_live_nick() if _mh_is_fake_nick(_nick) then mh_notify('[MH Cloud] {ff6666}Не удалось определить ник — публикация отменена.', 0xFFFFFF) return end local _sid = _mh_cloud_current_server_id() local _budget = _G._abp_budget_total or 0 local _items = {} for _, it in ipairs(fh_lv_autobuy_preset) do if it.name and it.name ~= '' and (it.max_price or 0) > 0 then table.insert(_items, { name = _to_utf8(it.name), price = it.max_price, qty = it.qty or 1, }) end end if #_items == 0 then mh_notify('[MH Cloud] {ffaa44}В пресете нет товаров с указанной ценой — нечего загружать.', 0xFFFFFF) return end local payload = { nick = _to_utf8(_nick), server_id = _sid, title = _to_utf8(_pname), items = _items, budget = _budget, } local ok_j, j = pcall(encodeJson, payload) if not ok_j then mh_notify('[MH Cloud] {ff6644}Ошибка кодирования пресета.', 0xFFFFFF) return end mh_notify('[MH Cloud] {aaddff}Публикую пресет "'.._pname..'" ('..#_items..' тов.)...', 0xFFFFFF) local function _do_push(is_retry) _jmx9s(_vbr7n .. '/cloud-presets/create', j, function(code, body, err) if code == 200 and body then local ok2, parsed = pcall(decodeJson, body) if ok2 and type(parsed) == 'table' and parsed.ok then if parsed.updated then mh_notify('[MH Cloud] {aaffaa}Пресет "'.._pname..'" обновлён в облаке.', 0xFFFFFF) else mh_notify('[MH Cloud] {aaffaa}Пресет "'.._pname..'" опубликован в облаке.', 0xFFFFFF) end _G._mh_cloud_loaded_once = false return end end if not is_retry then lua_thread.create(function() wait(1200); _do_push(true) end) return end mh_notify('[MH Cloud] {ff6666}Не удалось опубликовать пресет ('..tostring(err or code)..').', 0xFFFFFF) end) end _do_push(false) end function _mh_cloud_fetch_list(page) page = page or _G._mh_cloud_page or 1 if _G._mh_cloud_loading then return end _G._mh_cloud_loading = true _G._mh_cloud_error = nil local _sid = _mh_cloud_current_server_id() local _url = _vbr7n .. '/cloud-presets/list' .. '?server_id=' .. tostring(_sid) .. '&budget_max=' .. tostring(_G._mh_cloud_budget_max or 0) .. '&sort=' .. tostring(_G._mh_cloud_sort or 'downloads') .. '&page=' .. tostring(page) local _srch = _G._mh_cloud_search or '' if _srch ~= '' then local _ok_enc, _enc = pcall(function() return require('encoding').UTF8:decode(_srch) end) _url = _url .. '&search=' .. _mh_url_encode(_ok_enc and _enc or _srch) end if _G._mh_cloud_mine_only then local _mnick = _mh_get_live_nick() if not _mh_is_fake_nick(_mnick) then _url = _url .. '&mine_nick=' .. _mh_url_encode(_to_utf8(_mnick)) end end _fwm2c(_url, '', _mh_get_live_nick(), function(code, body, err) _G._mh_cloud_loading = false if err or code ~= 200 or not body then _G._mh_cloud_error = tostring(err or code or 'connection failed') return end local ok2, parsed = pcall(decodeJson, body) if not ok2 or type(parsed) ~= 'table' or not parsed.ok then _G._mh_cloud_error = 'bad_response' return end _G._mh_cloud_list = parsed.presets or {} _G._mh_cloud_total = parsed.total or 0 _G._mh_cloud_page = page end) end function _mh_cloud_add_preset_to_mine(cp) if not cp or type(cp.items) ~= 'table' or #cp.items == 0 then return end if not settings.buy_presets then settings.buy_presets = {} end local _items = {} for _, it in ipairs(cp.items) do table.insert(_items, { name = it.name, max_price = it.price or 0, qty = it.qty or 1, enabled = true, }) end local _name = (cp.title ~= '' and cp.title or ('Облако '..tostring(cp.id))) .. ' [' .. (cp.nick or '?') .. ']' table.insert(settings.buy_presets, {name = _name, items = _items}) fh_ab_preset_idx = #settings.buy_presets settings.active_buy_preset = fh_ab_preset_idx fh_lv_autobuy_preset = settings.buy_presets[fh_ab_preset_idx].items settings.autobuy_preset = fh_lv_autobuy_preset _G.ab_max_buf = nil; _G.ab_qty_buf = nil; _G.ab_en_buf = nil settings._ab_undo_snap = nil lua_thread.create(function() wait(0); pcall(_wfn7p) end) mh_notify('[MH Cloud] {aaffaa}Пресет "'.._name..'" добавлен в вашу скупку ('..#_items..' тов.).', 0xFFFFFF) if cp.id then local ok_j, j = pcall(encodeJson, {preset_id = cp.id}) if ok_j then _jmx9s(_vbr7n .. '/cloud-presets/download', j, function() end) end end end function _mh_cloud_vote_preset(cp, vote, req_id) if not cp or not cp.id then return end local _vnick = _mh_get_live_nick() if _mh_is_fake_nick(_vnick) then mh_notify('[MH Cloud] {ff6666}Не удалось определить ник для голосования.', 0xFFFFFF) return end local ok_j, j = pcall(encodeJson, {preset_id = cp.id, nick = _to_utf8(_vnick), vote = vote}) if not ok_j then return end _jmx9s(_vbr7n .. '/cloud-presets/vote', j, function(code, body, err) local _ok = false if code == 200 and body then local ok2, parsed = pcall(decodeJson, body) if ok2 and type(parsed) == 'table' and parsed.ok then _ok = true _G._mh_cloud_my_votes[cp.id] = parsed.my_vote cp.likes = parsed.likes cp.dislikes = parsed.dislikes cp.rating = parsed.likes - parsed.dislikes end end if req_id ~= nil then local resp = {kind = 'cloud_vote_result', req_id = tonumber(req_id) or 0, id = cp.id} if _ok then resp.my_vote = _G._mh_cloud_my_votes[cp.id] resp.likes = cp.likes resp.dislikes = cp.dislikes else resp.error = tostring(err or code or 'bad_response') end local ok_j2, j2 = pcall(encodeJson, resp) if ok_j2 then local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j2) end) if ok_b64 and b64 then pcall(function() if _mh_web_jar and _mh_buy.state.created and _mh_buy.state.loaded then _mh_web_jar.executeJS(_mh_buy.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end end) end end end end) end function _mh_cloud_delete_preset(cp) if not cp or not cp.id then return end local _dnick = _mh_get_live_nick() if _mh_is_fake_nick(_dnick) then mh_notify('[MH Cloud] {ff6666}Не удалось определить ник для удаления.', 0xFFFFFF) return end local ok_j, j = pcall(encodeJson, {preset_id = cp.id, nick = _to_utf8(_dnick)}) if not ok_j then return end _jmx9s(_vbr7n .. '/cloud-presets/delete', j, function(code, body, err) if code == 200 and body then local ok2, parsed = pcall(decodeJson, body) if ok2 and type(parsed) == 'table' and parsed.ok then mh_notify('[MH Cloud] {aaffaa}Пресет "'..tostring(cp.title or '?')..'" удалён из облака.', 0xFFFFFF) for i, it in ipairs(_G._mh_cloud_list or {}) do if it.id == cp.id then table.remove(_G._mh_cloud_list, i); break end end _G._mh_cloud_total = math.max(0, (_G._mh_cloud_total or 1) - 1) return end if ok2 and parsed.error == 'not_owner' then mh_notify('[MH Cloud] {ff6666}Можно удалять только свои пресеты.', 0xFFFFFF) return end end mh_notify('[MH Cloud] {ff6666}Не удалось удалить пресет ('..tostring(err or code)..').', 0xFFFFFF) end) end function _mh_mkt_log_index_by_name() local _ver = _G._mh_mkt_log_ver or 0 if _G._mh_mkt_log_idx and _G._mh_mkt_log_idx_ver == _ver then return _G._mh_mkt_log_idx end local idx = {} for i = 1, #fh_mkt_log do local le = fh_mkt_log[i] if le and le.item then local bucket = idx[le.item] if not bucket then bucket = {}; idx[le.item] = bucket end bucket[#bucket + 1] = le end end _G._mh_mkt_log_idx = idx _G._mh_mkt_log_idx_ver = _ver return idx end function _mh_other_shops_sell_index() if _G._mh_other_shops_idx_built then return _G._mh_other_shops_idx or {} end local idx = {} 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_lo = (_si.name or ''):lower() if _sn_lo ~= '' and (_si.price or 0) > 0 then local bucket = idx[_sn_lo] if not bucket then bucket = {}; idx[_sn_lo] = bucket end bucket[#bucket + 1] = _si.price end end end end _G._mh_other_shops_idx = idx _G._mh_other_shops_idx_built = true return idx end function _mh_shop_idx_remove(shop) if not shop or type(shop) ~= 'table' then return end local idx = _mh_other_shops_sell_index() for _, _si in ipairs(shop.sell_items or {}) do local _sn_lo = (_si.name or ''):lower() local _pr = _si.price or 0 if _sn_lo ~= '' and _pr > 0 then local bucket = idx[_sn_lo] if bucket then for i = #bucket, 1, -1 do if bucket[i] == _pr then table.remove(bucket, i) break end end if #bucket == 0 then idx[_sn_lo] = nil end end end end end function _mh_shop_idx_add(shop) if not shop or type(shop) ~= 'table' then return end local idx = _mh_other_shops_sell_index() for _, _si in ipairs(shop.sell_items or {}) do local _sn_lo = (_si.name or ''):lower() local _pr = _si.price or 0 if _sn_lo ~= '' and _pr > 0 then local bucket = idx[_sn_lo] if not bucket then bucket = {}; idx[_sn_lo] = bucket end bucket[#bucket + 1] = _pr end end 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 _mh_cp_hist_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 = {} local _nm_bucket = _mh_mkt_log_index_by_name()[nm] if _nm_bucket then for i = #_nm_bucket, 1, -1 do local le = _nm_bucket[i] if le 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 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 = {} local _cp_bucket = _mh_other_shops_sell_index()[nm_lo] if _cp_bucket then for _, _price in ipairs(_cp_bucket) do table.insert(_cp_live, _price) 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 _dtl_owner = _G._dtl_cache_nm local _dtl_owner_nm = _dtl_owner and _dtl_owner:match('^(.-)%|[^|]*$') if _dtl_owner_nm == nm 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 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') _mh_cp_hist_dates[h.dt] = true 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) local _d = se.date or '' local _has_cp = _mh_cp_hist_dates and _mh_cp_hist_dates[_d] if _sv and _d >= _cutoff30 and not _has_cp then _add_px(_d, _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 = {} local _shop_bucket = _mh_other_shops_sell_index()[nm_lo] if _shop_bucket then for _, _price in ipairs(_shop_bucket) do if not _shop_pts_seen[_price] then _shop_pts_seen[_price] = true table.insert(_shop_pts, _price) 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 _dtl_owner = _G._dtl_cache_nm local _dtl_owner_nm = _dtl_owner and _dtl_owner:match('^(.-)%|[^|]*$') if _dtl_owner_nm == nm 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 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 _ARB_AVG_TOP_N = 5 local _ARB_AVG_MIN_N = 3 local function _calc_avg(days_n) local cutoff = os.date('%Y-%m-%d', os.time() - days_n*86400) local pts = {} for _d, _prices in pairs(_px_by_date) do if _d >= cutoff then for _, _p in ipairs(_prices) do table.insert(pts, _p) end end end if #pts < _ARB_AVG_MIN_N then return nil end table.sort(pts) local _n = math.min(_ARB_AVG_TOP_N, #pts) local _s = 0 for i = 1, _n do _s = _s + pts[i] end return math.floor(_s / _n) end local today = _calc_avg(7) or _calc_avg(30) if not today or today <= 0 then if #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 today = _today_hist 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 = (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 local _res = {today=today, avg7=avg7, avg30=avg30} _G._mkt_price_gcache[nm] = {avg7=avg7, avg30=avg30} _G._mkt_today_gcache[nm] = today 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, false end local cached = _mh_name_canon_cache[nm] if cached ~= nil then return cached[1], cached[2] 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 _found = canon ~= nil local r = canon or (nm:match('^%s*(.-)%s*$') or nm) _mh_name_canon_cache[nm] = {r, _found} return r, _found end function _mh_norm_nm_reset() _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('\\\\|\\\\','/')..'/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 local _en_sig = {} for _ei, _abp in ipairs(fh_lv_autobuy_preset) do _qty_sum = _qty_sum + (_abp.qty or 0) _en_sig[_ei] = (_abp.enabled ~= false) and '1' or '0' end local _key = _pver..'|'.._dver..'|'..tostring(_cur_srv_abp)..'|'..tostring(_qty_sum)..'|'..table.concat(_en_sig) if _G._abp_price_cache_key ~= _key then _G._abp_price_cache_key = _key local _nc = {} local _total = 0 local _en_cnt = 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 if _abp.enabled ~= false then _total = _total + ((_abp.max_price or 0) * (_abp.qty or 1)) _en_cnt = _en_cnt + 1 end if _ai % 20 == 0 then wait(0) end end _G._abp_price_cache = _nc _G._abp_budget_total = _total _G._abp_en_cnt = _en_cnt _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 = {} local MAX_MARGIN_PCT = 1000 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 mkt_price = nil local nm_lo = nm:lower() local hist = e.cp_hist do local _mp = _mh_get_mkt_price(nm) if _mp then local _v7 = (_mp.avg7 and _mp.avg7 > 100) and _mp.avg7 or nil local _v30 = (_mp.avg30 and _mp.avg30 > 100) and _mp.avg30 or nil if _v7 and _v30 then mkt_price = math.min(_v7, _v30) elseif _v7 then mkt_price = _v7 elseif _v30 then mkt_price = _v30 elseif _mp.today and _mp.today > 100 then mkt_price = _mp.today end end if not mkt_price or mkt_price <= 100 then if e.cp_sp and e.cp_sp > 100 then mkt_price = e.cp_sp end end if not mkt_price or mkt_price <= 100 then if e.s_avg and e.s_avg > 100 then mkt_price = e.s_avg end end end if mkt_price and mkt_price > 100 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 and shop_price <= 100 then shop_price = nil end if shop_price then local margin = mkt_price - shop_price local margin_pct = shop_price>0 and margin/shop_price*100 or 0 local ok = margin > 0 and margin_pct <= MAX_MARGIN_PCT 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=margin_pct, 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 and buy_price <= 100 then buy_price = nil end if buy_price then local margin = buy_price - mkt_price local margin_pct = mkt_price>0 and margin/mkt_price*100 or 0 local ok = margin > 0 and margin_pct <= MAX_MARGIN_PCT 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=margin_pct, 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 > 100 and buy_e.price > 100 and sell_e.price < buy_e.price then local margin = buy_e.price - sell_e.price local margin_pct = sell_e.price>0 and margin/sell_e.price*100 or 0 local ok = margin > 0 and margin_pct <= MAX_MARGIN_PCT 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=margin_pct, 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 type(settings.qty1_items) ~= 'table' then settings.qty1_items = {} end function _mh_is_qty1_item(name) if not name then return false end return settings.qty1_items[name:lower()] == true end function _mh_mark_qty1_item(name) if not name then return end local key = name:lower() if settings.qty1_items[key] ~= true then settings.qty1_items[key] = true _wfn7p() end end function _mh_clear_qty1_item(name) if not name then return false end local key = name:lower() local changed = false if settings.qty1_items[key] == true then settings.qty1_items[key] = nil changed = true end if type(fh_lv_autobuy_preset) == 'table' then for _, it in ipairs(fh_lv_autobuy_preset) do if it and it.name and it.name:lower() == key then if it.qty_limit_1 then it.qty_limit_1 = nil changed = true end local tq = tonumber(it.target_qty) or 0 local q = tonumber(it.qty) or 0 if tq > 1 and q < tq then it.qty = tq changed = true end end end end if changed then _wfn7p() end return changed end do local _q1_changed = false if type(fh_lv_autobuy_preset) == 'table' then for _, _it in ipairs(fh_lv_autobuy_preset) do if _it and _it.name and (tonumber(_it.target_qty) or 0) > 1 then local _key = _it.name:lower() if settings.qty1_items[_key] == true then settings.qty1_items[_key] = nil _q1_changed = true end if _it.qty_limit_1 then _it.qty_limit_1 = nil _q1_changed = true end end end end if _q1_changed then _wfn7p() end end function _mh_dialog_has_qty_price(text) text = tostring(text or ''):lower() if text == '' then return false end local has_qty_word = text:find('колич', 1, true) ~= nil or text:find('кол-во', 1, true) ~= nil or text:find('кол во', 1, true) ~= nil or text:find('quantity', 1, true) ~= nil local has_price_word = text:find('цен', 1, true) ~= nil or text:find('price', 1, true) ~= nil if has_qty_word and has_price_word then return true end if text:find('%d+%s*,%s*%d+') then return true end return false end 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 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 settings.telegram.notify_payday == nil then settings.telegram.notify_payday = 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.notify_filters then settings.notify_filters = {} end if settings.notify_filters.sell_item == nil then settings.notify_filters.sell_item = true end if settings.notify_filters.buy_item == nil then settings.notify_filters.buy_item = true end if settings.notify_filters.sell_status == nil then settings.notify_filters.sell_status = true end if settings.notify_filters.buy_status == nil then settings.notify_filters.buy_status = true end if settings.notify_filters.watch == nil then settings.notify_filters.watch = true end if settings.notify_filters.fav == nil then settings.notify_filters.fav = true end if settings.notify_filters.other == nil then settings.notify_filters.other = true end if settings.notify_filters.arb == nil then settings.notify_filters.arb = true end if settings.notify_filters.reminder == nil then settings.notify_filters.reminder = true end if settings.notify_history_max == nil then settings.notify_history_max = 500 end if type(settings.notify_timers) ~= 'table' then settings.notify_timers = {} end if type(settings.catch_loot_log) ~= 'table' then settings.catch_loot_log = {} end if type(settings.catch_open_log) ~= 'table' then settings.catch_open_log = {} end if _G._mh_catch_ready == nil then _G._mh_catch_ready = false end if settings.sound_enabled == nil then settings.sound_enabled = true end _G.MH_SOUND_ENABLED = settings.sound_enabled ~= false 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.rounding then settings.overlay.rounding = 4 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, 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 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') do local imgui = imgui assert(imgui ~= nil, '[NotificationToastSystem] Глобальная переменная imgui не найдена. Модуль должен подключаться из MoonLoader-скрипта с доступом к imgui.') 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 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 local DEFAULT_CONFIG = { 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, anim_speed = 10.0, max_visible = 5, position = 'bottom_right', 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, border_enabled = false, border_color = {1.00, 1.00, 1.00, 0.25}, border_size = 1.0, } 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('Ошибка'), }, arbitrage = { color = {0.35, 0.75, 0.95}, icon = fa.SCALE_BALANCED, label = u8('Арбитраж'), }, 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', } 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 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 self.alpha = 0 self.y = 0 self.target_y = 0 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 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 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 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 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 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 local NotificationManager = {} NotificationManager.__index = NotificationManager NotificationManager.config = {} for k, v in pairs(DEFAULT_CONFIG) do NotificationManager.config[k] = v end NotificationManager.active = {} NotificationManager.pending = {} NotificationManager.enabled = true NotificationManager._registered = false 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 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 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 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 x = (sw - cfg.width) / 2 end if x < 0 then x = 0 end if x + cfg.width > sw then x = sw - cfg.width end 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 else return x, sh - mgy, true 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 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 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.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) 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 _hovered = safe_call(imgui.IsWindowHovered, imgui.HoveredFlags and imgui.HoveredFlags.AllowWhenBlockedByPopup or nil) if _hovered and safe_call(imgui.IsMouseClicked, 0) then n:dismiss() end 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) 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() local _measured_y = safe_call(imgui.GetCursorPosY) if _measured_y then local _real_h = _measured_y + cfg.padding_y 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() 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 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() local function _mh_toast_cfg_from_settings() local s = settings.toast or {} 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 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 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), sb_icon_scale = imgui.new.float(tonumber(settings.interface.sidebar_icon_scale) or 1.0), } 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 _G._mh_fMain, _G._mh_fTitle = nil, nil imgui.OnInitialize(function() imgui.GetIO().IniFilename = nil _fwb3h() do local io = imgui.GetIO() local icon_ranges = imgui.new.ImWchar[3](fa.min_range, fa.max_range, 0) local icon_cfg = imgui.ImFontConfig() icon_cfg.MergeMode = true icon_cfg.PixelSnapH = true io.Fonts:AddFontFromMemoryCompressedBase85TTF( require('lib.fAwesome6').get_font_data_base85('solid'), 16.0, icon_cfg, icon_ranges ) end do local io = imgui.GetIO() local icon_ranges2 = imgui.new.ImWchar[3](fa.min_range, fa.max_range, 0) local ok_sb, f_sb = pcall(io.Fonts.AddFontFromMemoryCompressedBase85TTF, io.Fonts, require('lib.fAwesome6').get_font_data_base85('solid'), 26.0, nil, icon_ranges2 ) if ok_sb then _G._mh_fSidebarIcon = f_sb end end do local io = imgui.GetIO() local base = getWorkingDirectory():gsub('\\', '/'):gsub('/$', '') local candidates = { base .. '/lib/mimgui/trebucbd.ttf', base .. '/trebucbd.ttf', base .. '/MarketHelper/trebucbd.ttf', } local ttf = nil for _, p in ipairs(candidates) do if doesFileExist(p) then ttf = p; break end end if ttf then local ranges = io.Fonts:GetGlyphRangesCyrillic() local ok1, f1 = pcall(io.Fonts.AddFontFromFileTTF, io.Fonts, ttf, 16.0, nil, ranges) if ok1 then _G._mh_fMain = f1 end local ok2, f2 = pcall(io.Fonts.AddFontFromFileTTF, io.Fonts, ttf, 22.0, nil, ranges) if ok2 then _G._mh_fTitle = f2 end end end imgui.GetIO().FontGlobalScale = tonumber(settings.interface.font_scale) or 0.70 end) local message_color = 0xFFAA00 do local message_color_hex = "{FFAA00}" fh_last_dlg_title = "" fh_last_dlg_title_raw = "" fh_last_dlg_text = "" fh_last_dlg_id = -1 fh_other_dlg_signal = nil if not sampGetDialogTitle then function sampGetDialogTitle() return fh_last_dlg_title_raw end end end if not sampGetDialogText then function sampGetDialogText() return fh_last_dlg_text end end function mh_dlg_text() return mh_dlg_cp(sampGetDialogText() or '') 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() return nil 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 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(), }) 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 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() wait(0) local ok, j = pcall(encodeJson, _snap) if ok then _mh_ensure_dir(getWorkingDirectory():gsub('\\', '/'):gsub('/$', '') .. '/MarketHelper') wait(0) 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() fh_session_chat_ts = {} for _i = 1, #fh_session_chat do fh_session_chat_ts[_i] = os.time() end 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 {} if not settings.buy_presets or type(settings.buy_presets) ~= "table" or #settings.buy_presets == 0 then local _old_ab = settings.autobuy_preset settings.buy_presets = {{name = 'Пресет 1', items = (_old_ab and type(_old_ab) == "table") and _old_ab or {}}} settings.active_buy_preset = 1 _wfn7p() end if not settings.active_buy_preset or settings.active_buy_preset < 1 or settings.active_buy_preset > #settings.buy_presets then settings.active_buy_preset = 1 end fh_ab_preset_idx = settings.active_buy_preset local _abp0 = settings.buy_presets[fh_ab_preset_idx] fh_lv_autobuy_preset = (_abp0 and _abp0.items) or {} settings.autobuy_preset = fh_lv_autobuy_preset 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_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 fh_other_shop_dialog_num = nil fh_other_shop_dialog_num_ts = 0 mh_own_shop_num = nil pcall(function() local _saved_shop_num = settings.general and tonumber(settings.general.own_shop_num_saved) if not _saved_shop_num or _saved_shop_num < 1 then local _snap_num = settings.my_shop_snapshot and tonumber(settings.my_shop_snapshot.shop_num) if _snap_num and _snap_num > 0 then _saved_shop_num = _snap_num end end if _saved_shop_num and _saved_shop_num > 0 then mh_own_shop_num = _saved_shop_num end end) mh_pending_lavka_buf = {} mh_own_shop_capture_buf = {} _G._mh_passive_cooldown = 300 _G._mh_passive_buf_ts = 0 _G._mh_passive_buf_mono = 0 _G._mh_passive_buf_gen = 0 _G._mh_own_shop_live_snapshot = nil _G._mh_own_shop_live_snapshot_mono = 0 _G._mh_own_shop_live_snapshot_gen = 0 _G._mh_own_shop_capture_pending = false _G._mh_own_shop_capture_num = nil _G._mh_own_shop_capture_id = 0 _G._mh_own_shop_capture_slot0_seen = false _G._mh_own_shop_live_snapshot_fresh = false _G._mh_sell_run_finish_mono = 0 _G._mh_buy_run_finish_mono = 0 _G._mh_sell_run_finish_ts = 0 _G._mh_buy_run_finish_ts = 0 function _mh_capture_own_shop_from_pending(owner, shop_num) if not owner or owner == '' or not shop_num then return false end local my_nick = '' pcall(function() local _ok_id, _pid = sampGetPlayerIdByCharHandle(PLAYER_PED) if _ok_id then my_nick = sampGetPlayerNickname(_pid) or '' end end) if my_nick == '' or owner:lower() ~= my_nick:lower() then return false end local snap = {owner=my_nick, shop_num=tonumber(shop_num) or shop_num, server_id=(type(_mh_cloud_current_server_id)=='function' and _mh_cloud_current_server_id()) or -1, ts=os.time(), sell_active=false, buy_active=false, sell_items={}, buy_items={}} local by_side = { [13] = {}, [28] = {} } local side_order = { [13] = {}, [28] = {} } local no_slot_groups = { [13] = {}, [28] = {} } local no_slot_order = { [13] = {}, [28] = {} } local saw_sell, saw_buy = false, false local capture_buf = mh_own_shop_capture_buf or {} for _, batch in ipairs(capture_buf) do local pkt = tonumber(batch.pkt_type) if pkt == 13 or pkt == 28 then if pkt == 13 then saw_sell = true else saw_buy = true end if type(batch.items) == 'table' then for _, it in ipairs(batch.items) do local iid = tonumber(it.item_id) local price = tonumber(it.price) or 0 local slot = tonumber(it.slot_idx) or 0 if iid and price > 0 then local nm = _G._rgn9z(iid) nm = tostring(nm or ''):gsub('{[^}]+}',''):match('^%s*(.-)%s*$') or '' if nm ~= '' then local rec = {name=nm, price=price, qty=1, item_id=iid, slot_idx=slot, ench=tonumber(it.ench) or 0} if slot > 0 then local key = tostring(slot) if by_side[pkt][key] then by_side[pkt][key] = rec else by_side[pkt][key] = rec side_order[pkt][#side_order[pkt] + 1] = key end else local key = tostring(iid)..'|'..tostring(price)..'|'..tostring(tonumber(it.ench) or 0) if no_slot_groups[pkt][key] then no_slot_groups[pkt][key].qty = no_slot_groups[pkt][key].qty + 1 else no_slot_groups[pkt][key] = rec no_slot_order[pkt][#no_slot_order[pkt] + 1] = key end end end end end end end end local function append_side(pkt, dst) if pkt ~= 13 and pkt ~= 28 then return end local keys = side_order[pkt] table.sort(keys, function(a,b) return (tonumber(a) or 0) < (tonumber(b) or 0) end) for _, key in ipairs(keys) do dst[#dst + 1] = by_side[pkt][key] end for _, key in ipairs(no_slot_order[pkt]) do dst[#dst + 1] = no_slot_groups[pkt][key] end end append_side(13, snap.sell_items) append_side(28, snap.buy_items) snap.sell_active=saw_sell; snap.buy_active=saw_buy _G._mh_own_shop_live_snapshot=snap _G._mh_own_shop_live_snapshot_mono=os.clock() _G._mh_own_shop_live_snapshot_gen=(_G._mh_own_shop_live_snapshot_gen or 0) + 1 _G._mh_own_shop_live_snapshot_fresh=true settings.my_shop_snapshot=snap _mh_sync_current_own_shop_num(snap.shop_num) if mh_debug_enabled then mh_notify('[MH] own live shop: sell='..#snap.sell_items..' buy='..#snap.buy_items..' num='..tostring(snap.shop_num),0x88FF88) end return true end cm_radius_enabled = false cm_render_enabled = false cm_radius_draw_dist = 20 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 if p[2] ~= nil then 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 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 _G._ryb5t = _ryb5t 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 function _G._mh_save_catch_now() if not _G._mh_catch_ready then return end if type(_G._mh_catch_write) ~= 'function' then return end _G._mh_catch_write('catch_loot_log.json', settings.catch_loot_log) _G._mh_catch_write('catch_open_log.json', settings.catch_open_log) end local function _gfr3j(date_str) return getWorkingDirectory():gsub('\\\\','/') .. '/FH_daily_prices/' .. date_str .. '.json' end local _fhd_migrated = false local function _fhd_migrate_stray(real_dir) if _fhd_migrated then return end _fhd_migrated = true local lfs_ok, lfs = pcall(require, 'lfs') if not lfs_ok then return end local stray_dir = getWorkingDirectory():gsub('\\\\','/') .. '/MarketHelper/FH_daily_prices' if not lfs.attributes(stray_dir) then return end for fname in lfs.dir(stray_dir) do if fname:match('^%d%d%d%d%-%d%d%-%d%d%.json$') then local src = stray_dir .. '/' .. fname local dst = real_dir .. '/' .. fname local src_attr = lfs.attributes(src) local dst_attr = lfs.attributes(dst) local should_move = true if dst_attr and src_attr and dst_attr.size >= src_attr.size then should_move = false end if should_move then local f = io.open(src, 'r') if f then local data = f:read('*a'); f:close() local fw = io.open(dst, 'w') if fw then fw:write(data); fw:close() end end end os.remove(src) end end pcall(lfs.rmdir, stray_dir) end local function _nvw9k() local lfs = require('lfs') local dir = getWorkingDirectory():gsub('\\\\','/') .. '/FH_daily_prices' if not lfs.attributes(dir) then lfs.mkdir(dir) end _fhd_migrate_stray(dir) end lua_thread.create(function() wait(10000) local ok, lfs = pcall(require, 'lfs') if ok then local dir = getWorkingDirectory():gsub('\\\\','/') .. '/FH_daily_prices' if lfs.attributes(dir) then _fhd_migrate_stray(dir) end 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) if rec.s_min and rec.s_min > 0 then m.s_min = m.s_min and math.min(m.s_min, rec.s_min) or rec.s_min end if rec.s_max and rec.s_max > 0 then m.s_max = m.s_max and math.max(m.s_max, rec.s_max) or rec.s_max end if rec.b_min and rec.b_min > 0 then m.b_min = m.b_min and math.min(m.b_min, rec.b_min) or rec.b_min end end end local _snap_path = _gfr3j(today .. '.sent') if not _G._mh_daily_sent_snap or _G._mh_daily_sent_date ~= today then local _snap_loaded = {} local _sf = io.open(_snap_path, 'r') if _sf then local _ok_s, _d_s = pcall(decodeJson, _sf:read('*a')); _sf:close() if _ok_s and type(_d_s) == 'table' then _snap_loaded = _d_s end end _G._mh_daily_sent_snap = _snap_loaded _G._mh_daily_sent_date = today end local _snap = _G._mh_daily_sent_snap local function _save_snap() local _ok_j, _j = pcall(encodeJson, _snap) if _ok_j then local _sf2 = io.open(_snap_path, 'w') if _sf2 then _sf2:write(_j); _sf2:close() end end end local items = {} local _new_snap = {} 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 local prev = _snap[nm] local s_cnt = rec.s_totalC or 0 local b_cnt = rec.b_totalC or 0 local _changed = (not prev) or (s_cnt ~= (prev.s_cnt or 0)) or (b_cnt ~= (prev.b_cnt or 0)) if _changed then table.insert(items, { name = nm, s_avg = s_avg or 0, s_cnt = s_cnt, b_avg = b_avg or 0, b_cnt = b_cnt, b_max = rec.b_max or 0, s_min = rec.s_min or 0, s_max = rec.s_max or 0, b_min = rec.b_min or 0, }) end _new_snap[nm] = {s_cnt = s_cnt, b_cnt = b_cnt} end end end if #items == 0 then _G._mh_daily_push_last = now 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 _save_snap() 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 for _, _bit in ipairs(batches[idx]) do _snap[_bit.name] = _new_snap[_bit.name] or {s_cnt = _bit.s_cnt, b_cnt = _bit.b_cnt} end 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() 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('\\\\','/') .. '/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('\\\\','/') .. '/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' 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 _G._mh_mkt_log_ver = (_G._mh_mkt_log_ver or 0) + 1 if mh_tg_on_trade then mh_tg_on_trade(_new_le) end if fh_mkt_prices[item] == nil then _G._fh_mkt_prices_ver = (_G._fh_mkt_prices_ver or 0) + 1 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) text = mh_dlg_cp(text or '') 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 _is_new = fh_mkt_prices[name] == nil 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 if _is_new then _G._fh_mkt_prices_ver = (_G._fh_mkt_prices_ver or 0) + 1 end 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 _is_new = fh_mkt_prices[item_name] == nil 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 if _is_new then _G._fh_mkt_prices_ver = (_G._fh_mkt_prices_ver or 0) + 1 end end end local function _nqh8s(text, style) local items = {} if not text or text == "" then return items end text = mh_dlg_cp(text) 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("^Сканировать все цены") 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; e._loc_ts = os.time(); 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 text = mh_dlg_cp(text) 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 e._loc_ts = os.time() fh_mkt_auto[item_name] = e; fh_mkt_auto_last_upd = dt end function _mh_deep_engine(cfg) local tag = cfg.tag or '[MH]' local stats = { done = 0, skipped = 0, resets = 0, aborted = nil } local scanned = {} local rtt_ms, rtt_n = cfg.rtt0 or 1000, 0 local assumed = cfg.txt0 local TRIES = cfg.item_tries or 3 local PTRIES = cfg.page_tries or 3 local LOST = cfg.lost_ms or 25000 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(mul) return math.max(cfg.to_min, math.min(rtt_ms * (mul or 1) * cfg.to_k + cfg.to_add, cfg.to_max)) end local function running() return cfg.is_active() end local function nap(ms) local t = 0 while t < ms and running() do wait(50); t = t + 50 end end local function dbg(msg, scr) if scr then printStyledString(scr, 1500, 6) end if mh_debug_enabled then mh_notify(tag .. ' {aaaaaa}' .. msg, 0xFFFFFF) end end local function list_open() return sampIsDialogActive() and sampGetCurrentDialogId() == cfg.list_id end local function wait_list(old_txt, ms) local t0 = os.clock(); local t = 0 while t < ms and running() do wait(16); t = t + 16 if list_open() then local ntxt = mh_dlg_text() 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_detail(ms) local t0 = os.clock(); local t = 0 local other = nil while t < ms and running() do wait(16); t = t + 16 if sampIsDialogActive() then local cid = sampGetCurrentDialogId() if cid ~= cfg.list_id then local title = fh_last_dlg_title or '' if cfg.is_detail(title) then upd_rtt(math.floor((os.clock() - t0) * 1000)) return cid, (mh_dlg_text() or ''), title, nil end other = cid end end end return nil, nil, nil, other end local function close_stray() if sampIsDialogActive() then local cid = sampGetCurrentDialogId() if cid ~= cfg.list_id then local btn = 0 if cid ~= 26048 and cfg.is_detail(fh_last_dlg_title or '') then btn = 1 end sampSendDialogResponse(cid, btn, 0, '') return true end end return false end local function ensure_list(patience_ms) if assumed then return cfg.list_id, assumed end local t0 = os.clock() local told = false while running() do if list_open() then return cfg.list_id, (mh_dlg_text() or '') end close_stray() local d, tx = wait_list(nil, math.min(srv_to(1), 4000)) if d then return d, tx end if (os.clock() - t0) * 1000 > patience_ms then return nil, nil end if not told then told = true dbg('Список закрыт - жду возврата (можно нажать "Назад")', '~w~MH wait list') end end return nil, nil end local function find_idx(txt, name) for _, it in ipairs(cfg.parse_list(txt)) do if it.name == name then return it.idx end end return nil end local function count_item() stats.done = stats.done + 1 cfg.on_progress(stats.done) end local function process_item(item) for try = 1, TRIES do if not running() then return 'stop' end local ld, lt = ensure_list(LOST) if not ld then return 'lost' end local idx = find_idx(lt, item.name) if not idx then return 'moved' end sampSendDialogResponse(ld, 1, idx - cfg.idx_offset(lt), 0) assumed = nil local dd, dtxt, dtitle, other = wait_detail(math.floor(srv_to(1) * (1 + 0.5 * (try - 1)))) if dd then local detail = cfg.parse_detail(dtxt, dtitle) sampSendDialogResponse(dd, 1, 0, '') if detail and detail.name ~= '' and #detail.history > 0 then cfg.on_detail(detail) end scanned[item.name] = true count_item() wait_list(nil, srv_to(1)) return 'ok' end if other then close_stray() scanned[item.name] = true count_item() wait_list(nil, srv_to(1)) return 'ok' end if try < TRIES then dbg('Нет ответа по товару, повтор ' .. try .. '/' .. TRIES, '~w~MH retry ~y~' .. try .. '/' .. TRIES) nap(500 * try) end end scanned[item.name] = true stats.skipped = stats.skipped + 1 count_item() close_stray() return 'skip' end local function press_back_recover() if list_open() then sampSendDialogResponse(cfg.list_id, 0, 0, '') assumed = nil end return ensure_list(LOST) end local pages, consec, back_used, loops = 0, 0, 0, 0 while running() do loops = loops + 1 if loops > 1500 then stats.aborted = 'loop_cap'; break end local ld, lt = ensure_list(LOST) if not ld then if running() then stats.aborted = 'list_lost' end break end local has_prev = fh_find_listitem(lt, 'Предыдущая страница') ~= nil if pages > 0 and not has_prev then stats.resets = stats.resets + 1 pages = 0 dbg('Список вернулся на 1-ю страницу, пропускаю снятое', '~w~MH page 1') if stats.resets > 8 then stats.aborted = 'too_many_resets'; break end end local restart = false for _, item in ipairs(cfg.parse_list(lt)) do if not running() then break end if not scanned[item.name] then local res = process_item(item) if res == 'ok' then consec = 0 elseif res == 'skip' then consec = consec + 1 if consec >= 3 then consec = 0 if back_used < 2 and running() then back_used = back_used + 1 dbg('3 пропуска подряд - "Назад" и продолжаю', '~w~MH back') local rd = press_back_recover() if not rd then if running() then stats.aborted = 'no_response' end end restart = true else stats.aborted = 'no_response' end break end elseif res == 'moved' or res == 'lost' then restart = true break else break end end end if stats.aborted or not running() then break end if not restart then local ld2, lt2 = ensure_list(LOST) if not ld2 then if running() then stats.aborted = 'list_lost' end break end if not fh_find_listitem(lt2, 'Следующая страница') then break end local moved = false for try = 1, PTRIES do if not running() then break end ld2, lt2 = ensure_list(LOST) if not ld2 then break end local nxt = fh_find_listitem(lt2, 'Следующая страница') if not nxt then moved = 'end'; break end sampSendDialogResponse(ld2, 1, nxt - cfg.idx_offset(lt2), 0) assumed = nil local nd = wait_list(lt2, srv_to(2)) if nd then pages = pages + 1; moved = true; wait(80); break end dbg('Страница не открылась, повтор ' .. try .. '/' .. PTRIES, '~w~MH page retry ~y~' .. try .. '/' .. PTRIES) nap(800 * try) end if moved == 'end' then break end if not moved and running() then if back_used < 2 then back_used = back_used + 1 dbg('Страница не листается - "Назад" и продолжаю', '~w~MH back') if not press_back_recover() and running() then stats.aborted = 'page_stuck'; break end else stats.aborted = 'page_stuck' break end end end end return stats end function _mh_deep_abort_text(reason) if reason == 'list_lost' then return 'окно списка закрылось и не вернулось' end if reason == 'no_response' then return 'сервер не отвечает на выбор товара' end if reason == 'page_stuck' then return 'не удаётся перейти на следующую страницу' end if reason == 'too_many_resets' then return 'список слишком часто сбрасывался на 1-ю страницу' end return 'слишком много циклов' 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 _saved_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() pcall(function() if _mh_piar and _mh_piar.push_state then _mh_piar.push_state() end end) end) end do 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 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 do 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 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 -- [FIX] Прямой скан чужой лавки уже получает enchant из -- server packet. Не теряем его при переносе в mh_arz_data. local _ench = tonumber(it.ench) or 0 local _item_ref = (_ench > 0) and (tostring(_wyk7z) .. '(' .. tostring(_ench) .. ')') or _wyk7z table.insert(_sell_ids, _item_ref) 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 local _ench = tonumber(it.ench) or 0 local _item_ref = (_ench > 0) and (tostring(_wyk7z) .. '(' .. tostring(_ench) .. ')') or _wyk7z table.insert(_buy_ids, _item_ref) 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 '?') _mh_shop_idx_remove(fh_other_shops[_key]) 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, } _mh_shop_idx_add(fh_other_shops[_key]) _mh_shop_bump() 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 if not existing then _G._fh_mkt_prices_ver = (_G._fh_mkt_prices_ver or 0) + 1 end 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 _mh_cloud_srv_id() 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 function _mh_auto_prices_push(silent) if _G._mh_apr_pushing then return end local srv_id = _mh_cloud_srv_id() if srv_id == -1 then if not silent then mh_notify('[MH Cloud] {ffaa00}Сервер не определён - цены авто не отправлены', 0xFFFFFF) end return end local items = {} for nm, e in pairs(fh_mkt_auto) do if type(nm) == 'string' and type(e) == 'table' then local hist = (type(e.cp_hist) == 'table' and #e.cp_hist > 0) and e.cp_hist or nil if (e.s_avg or 0) > 0 or (e.cp_sp or 0) > 0 or hist then local entry = { name = _to_utf8(nm) } if hist then entry.cp_hist = hist entry.hist_asc = (#hist > 1 and tostring(hist[1].dt or '') < tostring(hist[#hist].dt or '')) and true or false end if (e.cp_sp or 0) > 0 then entry.cp_sp = e.cp_sp end if (e.s_avg or 0) > 0 then entry.s_avg = e.s_avg end if (e.s_min or 0) > 0 then entry.s_min = e.s_min end if (e.s_max or 0) > 0 then entry.s_max = e.s_max end if type(e.date) == 'string' then entry.date = e.date end table.insert(items, entry) end end end if #items == 0 then if not silent then mh_notify('[MH Cloud] {ffaa00}Нет данных авто для отправки', 0xFFFFFF) end return end _G._mh_apr_pushing = true lua_thread.create(function() local CHUNK_SIZE = 100 local chunks, 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, total_items = #chunks, #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 '' for ci = 1, total_chunks do local ok_j, j = pcall(encodeJson, { server_id = srv_id, items = chunks[ci] }) if not ok_j then had_error = true; break end local th = effil.thread(_twd4k)(_vbr7n .. '/autoprices/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) had_error = true; break end local st, err = th:status() if not st or err then 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 had_error = true; break end if ci < total_chunks then wait(300) end end _G._mh_apr_pushing = false if had_error then if not silent then mh_notify('[MH Cloud] {ff4444}Не удалось отправить цены авто', 0xFFFFFF) end elseif not silent then mh_notify('[MH Cloud] {00cc00}Цены авто отправлены (' .. total_items .. ')', 0xFFFFFF) end end) end function _mh_auto_prices_pull(silent) if _G._mh_apr_pulling then return end local srv_id = _mh_cloud_srv_id() 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 _G._mh_apr_pulling = true _fwm2c(_vbr7n .. '/autoprices/pull?server=' .. tostring(srv_id) .. '&nick=' .. _ppn .. '&ver=' .. _MH_VER, (settings.premium and settings.premium.tok) or '', _ppn, function(code, body, _e) _G._mh_apr_pulling = false local function retry_later() _G._mh_auto_pull_ts = os.time() - 1740 end if _e or not body or #body == 0 then retry_later() 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 type(parsed.items) ~= 'table' then retry_later() if not silent then mh_notify('[MH Cloud] {ff4444}Ошибка парсинга цен авто', 0xFFFFFF) end return end if tonumber(parsed.server_id) == -1 then retry_later() end local merged = 0 for _, item in ipairs(parsed.items) do local nm = item.name if type(nm) == 'string' 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_auto[nm] local srv_upd = tonumber(item.updated_at) or 0 local our_upd = existing and existing._upd_ts or 0 local our_loc = existing and existing._loc_ts or 0 if not existing or (srv_upd > our_upd and srv_upd >= our_loc) then local e = existing or {} if type(item.cp_hist) == 'table' and #item.cp_hist > 0 then e.cp_hist = item.cp_hist end if (item.cp_sp or 0) > 0 then e.cp_sp = item.cp_sp end if (item.s_avg or 0) > 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 if type(item.date) == 'string' and item.date ~= '' then e.date = item.date elseif not e.date then e.date = os.date('%d.%m %H:%M', srv_upd > 0 and srv_upd or os.time()) end e._upd_ts = srv_upd e._is_auto = true fh_mkt_auto[nm] = e merged = merged + 1 end end end if merged > 0 then if _mh_auto_invalidate_cache then pcall(_mh_auto_invalidate_cache) end if _mh_auto_refresh_web then pcall(_mh_auto_refresh_web) end _ryb5t() end if not silent then mh_notify('[MH Cloud] {00cc00}Цены авто получены: ' .. merged .. ' обновлено из ' .. #parsed.items, 0xFFFFFF) end end) end _G._mh_auto_prices_push = _mh_auto_prices_push _G._mh_auto_prices_pull = _mh_auto_prices_pull function _mh_positive_shop_items(items) local out = {} for _, it in ipairs(items or {}) do if type(it) == "table" then local name = tostring(it.name or "") local price = tonumber(it.price) or 0 local qty = tonumber(it.qty) or 0 if name ~= "" and name ~= "?" and price > 0 and qty > 0 then local copy = {} for k, v in pairs(it) do copy[k] = v end copy.price = price copy.qty = qty table.insert(out, copy) end end end return out end function _crf5h(s, _on_result) 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 _clean_sell = _mh_positive_shop_items(s.sell_items) local _clean_buy = _mh_positive_shop_items(s.buy_items) local _sell_u8, _buy_u8 = {}, {} for _, it in ipairs(_clean_sell) do table.insert(_sell_u8, { name = _to_utf8(it.name), price = it.price, qty = it.qty, item_id = it.item_id or 0, ench = ((tonumber(it.ench) or 0) > 0) and tonumber(it.ench) or nil }) end for _, it in ipairs(_clean_buy) do table.insert(_buy_u8, { name = _to_utf8(it.name), price = it.price, qty = it.qty, item_id = it.item_id or 0, ench = ((tonumber(it.ench) or 0) > 0) and tonumber(it.ench) or nil }) 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 if _on_result then _on_result(true, nil) 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) if _on_result then _on_result(false, 'no_connection') end elseif code == 0 then mh_notify('[MH Cloud] {ff4444}Лавка НЕ отправлена: запрос не дошёл до сервера (внутренняя ошибка сети). Попробуй ещё раз.', 0xFFFFFF) if _on_result then _on_result(false, 'code_0') end elseif code ~= 200 and code ~= nil then mh_notify('[MH Cloud] {ff6644}Push: сервер вернул ' .. tostring(code), 0xFFFFFF) if _on_result then _on_result(false, 'http_' .. tostring(code)) end else _szb8v = true if _on_result then _on_result(true, nil) end end end) end local function mh_push_own_preset_shop(merge_existing, from_presets) local my_nick = '' pcall(function() my_nick = sampGetPlayerNickname(select(2, sampGetPlayerIdByCharHandle(PLAYER_PED))) or '' end) if my_nick == '' then return end local _previous_snapshot_num = settings.my_shop_snapshot and tonumber(settings.my_shop_snapshot.shop_num) local _previous_saved_num = settings.general and tonumber(settings.general.own_shop_num_saved) local my_shop_num = _G._mh_own_shop_capture_num or mh_own_shop_num -- When the player is standing by the newly listed shop, the visible 3D label -- is the freshest source. Do not let a relog value from the previous shop win. -- A number that came from the server packet (fresh live capture) is authoritative. -- The 3D label scan can pick up a NEIGHBOUR's stall (any 'Лавка N' within 12 m), -- so it is only a fallback and is never used for the manual upload button. local _live_fresh_num = nil do local _ls = _G._mh_own_shop_live_snapshot if type(_ls) == 'table' and _G._mh_own_shop_live_snapshot_fresh == true and tostring(_ls.owner or ''):lower() == my_nick:lower() and (os.time() - (tonumber(_ls.ts) or 0)) <= 15 then local _n = tonumber(_ls.shop_num) if _n and _n > 0 then _live_fresh_num = _n end end end if _live_fresh_num then my_shop_num = _live_fresh_num elseif from_presets ~= true or not my_shop_num then local _nearby_shop_num = _mh_detect_current_shop_num_nearby() if _nearby_shop_num then my_shop_num = _nearby_shop_num _mh_sync_current_own_shop_num(_nearby_shop_num) end end if not my_shop_num then local _saved_own = settings.general and tonumber(settings.general.own_shop_num_saved) if _saved_own and _saved_own > 0 then my_shop_num = _saved_own end end if not my_shop_num then local _snap_own = settings.my_shop_snapshot and tonumber(settings.my_shop_snapshot.shop_num) if _snap_own and _snap_own > 0 then my_shop_num = _snap_own end end local _shop_number_changed = false if my_shop_num then local _mn = tonumber(my_shop_num) if _mn and _mn > 0 then if _previous_snapshot_num and _previous_snapshot_num > 0 and _mn ~= _previous_snapshot_num then _shop_number_changed = true elseif _previous_saved_num and _previous_saved_num > 0 and _mn ~= _previous_saved_num then _shop_number_changed = true end end end if _shop_number_changed and mh_debug_enabled then mh_notify('[MH] {ffaa00}Новая лавка: #'..tostring(_previous_snapshot_num)..' -> #'..tostring(my_shop_num)..'. Старый snapshot не используется.', 0xFFFFFF) end local _merge_existing = (merge_existing == true) local _from_presets = (from_presets == true) local _sell_session_ran = (_G._mh_sell_ran_session == true) local _buy_session_ran = (_G._mh_buy_ran_session == true) local _sell_run_new_only = (_G._mh_sell_run_new_only == true) local _buy_run_new_only = (_G._mh_buy_run_new_only == true) local _prev_sell_active = true local _prev_buy_active = false if type(settings.my_shop_snapshot) == 'table' then local _snap0 = settings.my_shop_snapshot if _snap0.sell_active ~= nil then _prev_sell_active = (_snap0.sell_active == true) else _prev_sell_active = (#(_snap0.sell_items or {}) > 0) end if _snap0.buy_active ~= nil then _prev_buy_active = (_snap0.buy_active == true) else _prev_buy_active = false end end -- A different shop number means this is a newly listed shop, not a relog -- continuation of the previous one. Never carry the old side (especially buy) over. if _shop_number_changed then _prev_sell_active = false _prev_buy_active = false end local _live_snapshot = _G._mh_own_shop_live_snapshot local _live_ts = type(_live_snapshot) == 'table' and tonumber(_live_snapshot.ts or 0) or 0 local _live_age = os.time() - _live_ts local _live_num = _live_snapshot and tostring(_live_snapshot.shop_num or '?') or '' local _live_owner = _live_snapshot and tostring(_live_snapshot.owner or ''):lower() or '' local _live_matches = type(_live_snapshot) == 'table' and _live_age >= 0 and _live_age <= 5 and _live_owner == my_nick:lower() and _live_num == tostring(my_shop_num or '?') local _live_after_sell = _live_matches and _sell_session_ran and _live_ts >= ((tonumber(_G._mh_sell_run_finish_ts) or 0) - 5) local _live_after_buy = _live_matches and _buy_session_ran and _live_ts >= ((tonumber(_G._mh_buy_run_finish_ts) or 0) - 5) local _live_authoritative = _live_matches and (_G._mh_own_shop_live_snapshot_fresh == true) and (((_sell_session_ran and _live_after_sell) or (_buy_session_ran and _live_after_buy)) or _from_presets) local sell_items = {} if _from_presets then local _snap = (_live_authoritative and _live_snapshot) or settings.my_shop_snapshot if type(_snap) == 'table' then if _live_authoritative or _prev_sell_active then for _, p in ipairs(_snap.sell_items or {}) do if p.name and p.name ~= '' and (tonumber(p.price) or 0) > 0 and (tonumber(p.qty) or 0) > 0 then table.insert(sell_items, {name=p.name, price=tonumber(p.price) or 0, qty=tonumber(p.qty) or 0, item_id=tonumber(p.item_id) or 0, slot_idx=tonumber(p.slot_idx) or 0, ench=tonumber(p.ench) or 0}) end end end else for _, p in ipairs(fh_lv_autosell_preset or {}) do if p.name and p.name ~= '' and p.enabled ~= false and (tonumber(p.price) or 0) > 0 and (tonumber(p.qty) or 0) > 0 then table.insert(sell_items, {name=p.name, price=tonumber(p.price) or 0, qty=tonumber(p.qty) or 0, item_id=tonumber(p.item_id) or 0, slot_idx=tonumber(p.slot_idx) or 0, ench=tonumber(p.ench) or 0}) end end end elseif _sell_session_ran then if _live_authoritative and type(_live_snapshot) == 'table' and type(_live_snapshot.sell_items) == 'table' then for _, p in ipairs(_live_snapshot.sell_items) do if p.name and p.name ~= '' and (tonumber(p.price) or 0) > 0 and (tonumber(p.qty) or 0) > 0 then table.insert(sell_items, {name=p.name, price=tonumber(p.price) or 0, qty=tonumber(p.qty) or 0, item_id=tonumber(p.item_id) or 0, slot_idx=tonumber(p.slot_idx) or 0, ench=tonumber(p.ench) or 0}) end end elseif _G._mh_sell_confirmed_items and #_G._mh_sell_confirmed_items > 0 then for _, p in ipairs(_G._mh_sell_confirmed_items) do if p.name and p.name ~= '' and (p.price or 0) > 0 then table.insert(sell_items, {name=p.name, price=tonumber(p.price) or 0, qty=tonumber(p.qty) or 0, item_id=tonumber(p.item_id) or 0, slot_idx=tonumber(p.slot_idx) or 0, ench=tonumber(p.ench) or 0}) end end end end local buy_items = {} if _from_presets then local _snap = (_live_authoritative and _live_snapshot) or settings.my_shop_snapshot if type(_snap) == 'table' and (_live_authoritative or _prev_buy_active) then for _, p in ipairs(_snap.buy_items or {}) do if p.name and p.name ~= '' and (tonumber(p.price) or 0) > 0 and (tonumber(p.qty) or 0) > 0 then table.insert(buy_items, {name=p.name, price=tonumber(p.price) or 0, qty=tonumber(p.qty) or 0, item_id=tonumber(p.item_id) or 0, slot_idx=tonumber(p.slot_idx) or 0, ench=tonumber(p.ench) or 0}) end end elseif not _snap then for _, p in ipairs(fh_lv_autobuy_preset or {}) do if p.name and p.name ~= '' and p.enabled ~= false and (tonumber(p.max_price) or 0) > 0 and (tonumber(p.qty) or 0) > 0 then table.insert(buy_items, {name=p.name, price=tonumber(p.max_price) or 0, qty=tonumber(p.qty) or 0}) end end end elseif _buy_session_ran then if _G._mh_buy_confirmed_items and #_G._mh_buy_confirmed_items > 0 then for _, p in ipairs(_G._mh_buy_confirmed_items) do if p.name and p.name ~= '' and (p.price or 0) > 0 then table.insert(buy_items, {name=p.name, price=tonumber(p.price) or 0, qty=tonumber(p.qty) or 0, item_id=tonumber(p.item_id) or 0, slot_idx=tonumber(p.slot_idx) or 0, ench=tonumber(p.ench) or 0}) end end end end sell_items = _mh_positive_shop_items(sell_items) buy_items = _mh_positive_shop_items(buy_items) if #sell_items == 0 and #buy_items == 0 then if _from_presets then mh_notify('[MH Cloud] {ffaa00}Не удалось считать товары своей лавки (0/0) -- отправка отменена, чтобы не затереть данные. Открой свою лавку и повтори.', 0xFFFFFF) end return end local _self_sid = (type(_mh_cloud_current_server_id) == 'function' and _mh_cloud_current_server_id()) or -1 if _self_sid == -1 then local _boot = _G.mh_boot_srv_idx and (_G.mh_boot_srv_idx > 0) and (_G.mh_boot_srv_idx + 1) or 0 if _boot > 0 then _self_sid = (ARZ_SERVERS[_boot] or {}).id or -1 end end if _live_authoritative and type(_live_snapshot) == 'table' then local _live_sid2 = tonumber(_live_snapshot.server_id or -1) or -1 if not (_live_sid2 == -1 or _self_sid == -1 or _live_sid2 == _self_sid) then _live_authoritative = false end end local new_key = my_nick .. '_' .. tostring(my_shop_num or '?') local _base_shop = nil if _merge_existing then local function _mh_reconcile_base_side(_base_list, _preset_list) if type(_base_list) ~= 'table' then return {} end if type(_preset_list) ~= 'table' or #_preset_list == 0 then local _copy = {} for _, _it in ipairs(_base_list) do _copy[#_copy + 1] = _it end return _copy end local _pm = {} for _, _p in ipairs(_preset_list) do if type(_p) == 'table' and _p.name and _p.name ~= '' then local _key = tostring(_p.name):gsub('^%s+',''):gsub('%s+$',''):lower() local _q = tonumber(_p.qty) or 0 local _old = _pm[_key] if _old == nil or _q < _old then _pm[_key] = _q end end end local _out = {} for _, _it in ipairs(_base_list) do local _key = tostring(_it.name or ''):gsub('^%s+',''):gsub('%s+$',''):lower() local _pq = _pm[_key] if _pq == nil then _out[#_out + 1] = _it elseif _pq > 0 then local _copy = {} for _k,_v in pairs(_it) do _copy[_k] = _v end local _bq = tonumber(_copy.qty) or 0 if _bq > _pq then _copy.qty = _pq end if (tonumber(_copy.qty) or 0) > 0 then _out[#_out + 1] = _copy end end end return _out end if not _shop_number_changed then if type(settings.my_shop_snapshot) == 'table' then local _snap = settings.my_shop_snapshot local _snap_owner = tostring(_snap.owner or ''):lower() local _snap_num = tostring(_snap.shop_num or '?') local _snap_sid = tonumber(_snap.server_id or -1) or -1 if _snap_owner == my_nick:lower() and _snap_num == tostring(my_shop_num or '?') and (_snap_sid == -1 or _self_sid == -1 or _snap_sid == _self_sid) then _base_shop = _snap end end if not _base_shop then _base_shop = fh_other_shops[new_key] end if not _base_shop then local _nick_lo = my_nick:lower() for _, _sh in pairs(fh_other_shops or {}) do if _sh and tostring(_sh.owner or ''):lower() == _nick_lo and tostring(_sh.shop_num or '?') == tostring(my_shop_num or '?') then _base_shop = _sh break end end end end if not _base_shop and not _shop_number_changed then local _nick_lo = my_nick:lower() for _, _lv in ipairs(mh_arz_data or {}) do if _lv.username and _lv.username:lower() == _nick_lo and (_self_sid == -1 or not _lv.serverId or _lv.serverId == -1 or _lv.serverId == _self_sid) then local _ss, _bb = {}, {} for _i, _iid in ipairs(_lv.items_sell or {}) do local _bid, _ench = _G._bqs3v(_iid) local _nm = _G._rgn9z(_bid) if _ench and _ench ~= '' then _nm = _nm .. ' (+' .. _ench .. ')' end local _qp = (_lv.count_sell or {})[_i] local _pp = (_lv.price_sell or {})[_i] if _nm and _nm ~= '' and (tonumber(_qp) or 0) > 0 and (tonumber(_pp) or 0) > 0 then table.insert(_ss, {name=_nm, price=tonumber(_pp) or 0, qty=tonumber(_qp) or 0}) end end for _i, _iid in ipairs(_lv.items_buy or {}) do local _bid, _ench = _G._bqs3v(_iid) local _nm = _G._rgn9z(_bid) if _ench and _ench ~= '' then _nm = _nm .. ' (+' .. _ench .. ')' end local _qp = (_lv.count_buy or {})[_i] local _pp = (_lv.price_buy or {})[_i] if _nm and _nm ~= '' and (tonumber(_qp) or 0) > 0 and (tonumber(_pp) or 0) > 0 then table.insert(_bb, {name=_nm, price=tonumber(_pp) or 0, qty=tonumber(_qp) or 0}) end end _base_shop = {owner=my_nick, shop_num=my_shop_num or _lv.LavkaUid or '?', sell_items=_ss, buy_items=_bb, server_id=_self_sid} break end end end if _live_authoritative and type(_live_snapshot) == 'table' then sell_items = _mh_positive_shop_items(_live_snapshot.sell_items or {}) buy_items = _mh_positive_shop_items(_live_snapshot.buy_items or {}) _base_shop = _live_snapshot elseif _base_shop then local function _mh_item_key(_it) if type(_it) ~= 'table' then return nil end local _name = tostring(_it.name or '') _name = _name:gsub('^%s+', ''):gsub('%s+$', ''):lower() if _name == '' then return nil end return 'name:' .. _name end local function _mh_merge_item_list(_base, _delta) local _out, _pos = {}, {} local _base_clean = _mh_positive_shop_items(_base) local _delta_clean = _mh_positive_shop_items(_delta) for _, _it in ipairs(_base_clean) do local _copy = {name=_it.name, price=_it.price, qty=_it.qty or 1, item_id=_it.item_id, ench=_it.ench} local _key = _mh_item_key(_copy) if _key and not _pos[_key] then table.insert(_out, _copy) _pos[_key] = #_out elseif not _key then table.insert(_out, _copy) end end for _, _it in ipairs(_delta_clean) do local _copy = {name=_it.name, price=_it.price, qty=_it.qty or 1, item_id=_it.item_id, ench=_it.ench} local _key = _mh_item_key(_copy) if _key and _pos[_key] then _out[_pos[_key]] = _copy else table.insert(_out, _copy) if _key then _pos[_key] = #_out end end end return _out end local _sell_full_sync = _sell_session_ran and not _sell_run_new_only local _base_sell = _sell_full_sync and {} or _mh_reconcile_base_side(_base_shop.sell_items or {}, fh_lv_autosell_preset) local _base_buy = _mh_reconcile_base_side(_base_shop.buy_items or {}, fh_lv_autobuy_preset) if _sell_session_ran and not _buy_session_ran and not _prev_buy_active then _base_buy = {} end if _buy_session_ran and not _sell_session_ran and not _prev_sell_active then _base_sell = {} end if not _sell_full_sync then sell_items = _mh_merge_item_list(_base_sell, sell_items) end buy_items = _mh_merge_item_list(_base_buy, buy_items) if mh_debug_enabled then mh_notify('[MH REBUY] merge: base=' .. tostring(#(_base_shop.sell_items or {})) .. '/' .. tostring(#(_base_shop.buy_items or {})) .. ' +delta=' .. tostring(#sell_items - #(_base_shop.sell_items or {})) .. '/' .. tostring(#buy_items - #(_base_shop.buy_items or {})) .. ' -> total=' .. tostring(#sell_items) .. '/' .. tostring(#buy_items), 0x88FF88) end end 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 = _self_sid, } local _final_sell_active = _live_authoritative and type(_live_snapshot) == 'table' and (_live_snapshot.sell_active == true) or ((_from_presets and _prev_sell_active) or (_sell_session_ran and #sell_items > 0) or (not _sell_session_ran and _prev_sell_active and #sell_items > 0)) local _final_buy_active = _live_authoritative and type(_live_snapshot) == 'table' and (_live_snapshot.buy_active == true) or ((_from_presets and _prev_buy_active) or (_buy_session_ran and #buy_items > 0) or (not _buy_session_ran and _prev_buy_active and #buy_items > 0)) settings.my_shop_snapshot = { owner = shop_obj.owner, shop_num = shop_obj.shop_num, server_id = shop_obj.server_id, ts = shop_obj.ts, sell_active = (_final_sell_active == true), buy_active = (_final_buy_active == true), sell_items = {}, buy_items = {}, } for _, _it in ipairs(shop_obj.sell_items or {}) do table.insert(settings.my_shop_snapshot.sell_items, {name=_it.name, price=_it.price, qty=_it.qty or 1, item_id=_it.item_id, slot_idx=_it.slot_idx, ench=tonumber(_it.ench) or 0}) end for _, _it in ipairs(shop_obj.buy_items or {}) do table.insert(settings.my_shop_snapshot.buy_items, {name=_it.name, price=_it.price, qty=_it.qty or 1, item_id=_it.item_id, slot_idx=_it.slot_idx, ench=tonumber(_it.ench) or 0}) end local _own_num_final = tonumber(shop_obj.shop_num) if _own_num_final and _own_num_final > 0 then _mh_sync_current_own_shop_num(_own_num_final) end _G._mh_sell_ran_session = false _G._mh_buy_ran_session = false _G._mh_sell_run_new_only = false _G._mh_buy_run_new_only = false _G._mh_sell_confirmed_items = {} _G._mh_buy_confirmed_items = {} _mh_shop_idx_remove(fh_other_shops[new_key]) fh_other_shops[new_key] = shop_obj _mh_shop_idx_add(fh_other_shops[new_key]) _mh_shop_bump() settings.other_shops = fh_other_shops _wfn7p() 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 _lv.items_sell = _sell_ids2; _lv.price_sell = _sell_pr2; _lv.count_sell = _sell_cnt2 _lv.items_buy = _buy_ids2; _lv.price_buy = _buy_pr2; _lv.count_buy = _buy_cnt2 _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 local _retried = false local function _on_push_result(ok, err) if ok then mh_notify('[MH Cloud] {aaffaa}Своя лавка отправлена: ' ..my_nick..' #'..tostring(my_shop_num or '?') ..' ('..#sell_items..' прод / '..#buy_items..' скуп)', 0xFFFFFF) return end if not _retried and (err == 'code_0' or err == 'no_connection') then _retried = true lua_thread.create(function() wait(1500) _crf5h(shop_obj, _on_push_result) end) return end mh_notify('[MH Cloud] {ff4444}Не удалось отправить лавку на сервер (' ..my_nick..' #'..tostring(my_shop_num or '?') ..') -- показанные в интерфейсе товары могут не совпадать с тем, что видят другие игроки. Попробуй ещё раз позже.', 0xFFFFFF) end _crf5h(shop_obj, _on_push_result) end _G._mh_own_shop_refresh_running = false _G._mh_refresh_own_shop_and_push = function(_merge_existing, _from_presets) if _G._mh_own_shop_refresh_running then return end if fh_other_shop_scanning then mh_push_own_preset_shop(_merge_existing, _from_presets) return end _G._mh_own_shop_refresh_running = true lua_thread.create(function() local _before_gen = _G._mh_own_shop_live_snapshot_gen or 0 _G._mh_own_shop_live_snapshot_fresh = false _G._mh_own_shop_capture_id = (_G._mh_own_shop_capture_id or 0) + 1 _G._mh_own_shop_capture_pending = false _G._mh_own_shop_capture_num = nil _G._mh_own_shop_capture_slot0_seen = false mh_pending_lavka_buf = {} mh_own_shop_capture_buf = {} _G._mh_passive_buf_ts = 0 _G._mh_passive_buf_mono = 0 pcall(function() if sampIsDialogActive() then sampSendDialogResponse(sampGetCurrentDialogId(), 0, 0, '') wait(120) end _yzr1t(8, 7, -1, '') end) local _got_fresh = false for _wait_i = 1, 50 do wait(50) local _snap = _G._mh_own_shop_live_snapshot local _gen = _G._mh_own_shop_live_snapshot_gen or 0 if _gen > _before_gen and type(_snap) == 'table' and _G._mh_own_shop_live_snapshot_fresh == true and tonumber(_snap.ts or 0) >= os.time() - 5 and tostring(_snap.owner or ''):lower() == tostring(sampGetPlayerNickname(select(2, sampGetPlayerIdByCharHandle(PLAYER_PED))) or ''):lower() then _got_fresh = true break end end wait(120) if sampIsDialogActive() then local _did = sampGetCurrentDialogId() if _did == 3040 or _got_fresh then pcall(function() sampSendDialogResponse(_did, 0, 0, '') end) wait(100) end end _G._mh_own_shop_refresh_running = false if mh_debug_enabled then mh_notify('[MH] own shop refresh: '..(_got_fresh and 'OK' or 'fallback'), _got_fresh and 0x88FF88 or 0xFFAA44) end mh_push_own_preset_shop(_merge_existing, _from_presets) end) 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 = {}, {}, {} -- [FIX] If the cloud record is from an older client / legacy source -- and has no `ench`, do not erase an enchant that we already -- captured locally for the same owner/item/price. local _old_cloud_entry = found_idx and mh_arz_data[found_idx] or nil local function _mh_pull_slot_ench(_slot, _old_items, _old_prices) local _incoming = tonumber(_slot and _slot.ench) or 0 if _incoming > 0 then return _incoming end if not _old_cloud_entry then return 0 end local _nm = tostring((_slot and _slot.name) or '') local _pr = tonumber((_slot and _slot.price) or 0) or 0 for _oi, _oid in ipairs(_old_items or {}) do local _obid, _oench = _G._bqs3v(_oid) if _oench ~= '' then local _old_nm = tostring(mh_arz_items_db[_obid] or '') local _old_pr = tonumber((_old_prices or {})[_oi]) or 0 if _old_nm == _nm and _old_pr == _pr then return tonumber(_oench) or 0 end end end return 0 end 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 -- [FIX] shops/pull уже содержит enchant в каждой записи, -- но раньше при преобразовании cloud -> mh_arz_data это -- поле терялось. _bqs3v() умеет разбирать формат -- "internal_id(enchant)", поэтому сохраняем заточку -- прямо в ссылке на item, не меняя существующие массивы. local _ench = _mh_pull_slot_ench(slot, _old_cloud_entry and _old_cloud_entry.items_sell, _old_cloud_entry and _old_cloud_entry.price_sell) local _item_ref = (_ench > 0) and (tostring(_wyk7z) .. '(' .. tostring(_ench) .. ')') or _wyk7z table.insert(sell_ids, _item_ref) 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 local _ench = _mh_pull_slot_ench(slot, _old_cloud_entry and _old_cloud_entry.items_buy, _old_cloud_entry and _old_cloud_entry.price_buy) local _item_ref = (_ench > 0) and (tostring(_wyk7z) .. '(' .. tostring(_ench) .. ')') or _wyk7z table.insert(buy_ids, _item_ref) 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 local _srv_ts = entry._mh_updated_at or 0 local _local_ts = _ex._mh_updated_at or 0 local _newer_or_equal = (_srv_ts >= _local_ts) or _local_ts == 0 if _newer_or_equal then _ex._mh_updated_at = _srv_ts 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 elseif mh_debug_enabled then mh_notify('[MH DBG] pull: пропущен устаревший merge для '..tostring(sh.owner) ..' (srv_ts='..tostring(_srv_ts)..' < local_ts='..tostring(_local_ts)..')', 0xFFAA00) 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 do 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 end local function _rcw6d(key) _mh_shop_idx_remove(fh_other_shops[key]) fh_other_shops[key] = nil settings.other_shops = fh_other_shops _mh_shop_bump() _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 = {} _G._as_inv_lbl_ver = nil mh_notify('[MH Авто] {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('[MH Авто] {ff4444}Инвентарь не получен (таймаут).', 0xFFFFFF) if _mh_sell and _mh_sell.push_state then pcall(_mh_sell.push_state) end if _mh_catch and _mh_catch.push_state then pcall(_mh_catch.push_state) end end end) end local _fh_allitems_state = {cache=nil, lower=nil, ver=-1} local function fh_get_allitems_list() local ver = _G._fh_mkt_prices_ver or 0 local st = _fh_allitems_state if st.cache and ver == st.ver then return st.cache, st.lower end local list = {} for nm, _ in pairs(fh_mkt_prices) do if type(nm) == 'string' then table.insert(list, nm) end end table.sort(list) local lower_list = {} for i, nm in ipairs(list) do lower_list[i] = fh_lower(nm) end st.cache = list st.lower = lower_list st.ver = ver return list, lower_list end local function fh_is_slot_dialog(cid) if cid == 25663 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(new_only) 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 _new_keys = nil if new_only then _new_keys = {} local _snap = settings.my_shop_snapshot for _, _it in ipairs((_snap and _snap.sell_items) or {}) do if (tonumber(_it.qty) or 0) > 0 then local _k = tostring(_it.name or ''):lower() _new_keys[_k] = true end end end local queue = {} for _, preset in ipairs(fh_lv_autosell_preset) do local _allow_preset = true if new_only then local _pk = tostring(preset.name or ''):lower() _allow_preset = not _new_keys[_pk] end if _allow_preset and 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 if new_only then mh_notify('[MH Авто] {ff8800}Нет новых товаров для выставления.', 0xFFFFFF) return end mh_notify('[MH Авто] {ff8800}Нет товаров для выставления.', 0xFFFFFF) return end fh_lv_autosell_running = true _G._mh_sell_run_finish_mono = 0 fh_lv_autosell_done = 0 fh_lv_autosell_status = 'Запуск...' _G._mh_sell_confirmed_items = {} _G._mh_sell_run_new_only = (new_only == true) local total = #queue mh_notify('[MH Авто] {ffaa00}Выставляю ' .. total .. ' стаков...', 0xFFFFFF) lua_thread.create(function() if _mh_sell and _mh_sell.push_state then pcall(_mh_sell.push_state) end 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 '' local _name_check = item.name:gsub('%(%+%d+%)', ''):match('^%s*(.-)%s*$') or item.name local dlg_text = '' local got_dialog = false local max_attempts = 4 for _attempt = 1, max_attempts do if not fh_lv_autosell_running then break end _G._mh_dlg26544_text = nil _G._mh_dlg26544_time = nil if sampIsDialogActive() and sampGetCurrentDialogId() ~= 26547 then sampSendDialogResponse(sampGetCurrentDialogId(), 0, 0, '') wait(80) end _mh_flog('SELL_CLICK item=' .. item.name .. ' slot=' .. item.slot .. ' qty=' .. item.qty .. ' price=' .. item.price .. ' attempt=' .. _attempt .. ' json=' .. json_str) _yzr1t(60, -1, 2, json_str) local open_t = os.clock() + 1.8 wait(80) dlg_text = '' while os.clock() < open_t do wait(40) if _G._mh_dlg26544_text and _G._mh_dlg26544_text ~= '' and (_G._mh_dlg26544_time or 0) > (os.clock() - 2) then dlg_text = mh_dlg_cp(_G._mh_dlg26544_text) _G._mh_dlg26544_text = nil break end if sampIsDialogActive() and sampGetCurrentDialogId() == 26547 then local cur_text = mh_dlg_cp(sampGetDialogText() or '') if cur_text ~= '' then dlg_text = cur_text break end end end if dlg_text ~= '' and sampIsDialogActive() and sampGetCurrentDialogId() == 26547 then for _settle = 1, 4 do wait(35) if not sampIsDialogActive() or sampGetCurrentDialogId() ~= 26547 then break end local _re_text = mh_dlg_cp(sampGetDialogText() or '') if _re_text ~= '' and _re_text == dlg_text then break end if _re_text ~= '' then dlg_text = _re_text end end end if dlg_text ~= '' and sampIsDialogActive() and sampGetCurrentDialogId() == 26547 then if _name_check ~= '' and not dlg_text:find(_name_check, 1, true) then for _wcheck = 1, 8 do wait(35) if not sampIsDialogActive() or sampGetCurrentDialogId() ~= 26547 then dlg_text = ''; break end local _rt = mh_dlg_cp(sampGetDialogText() or '') if _rt ~= '' then dlg_text = _rt end if dlg_text:find(_name_check, 1, true) then break end end _mh_flog('SELL_WAIT namecheck retry item=' .. item.name .. ' text=[' .. dlg_text:sub(1,80) .. ']') end end _mh_flog('SELL_WAIT result: attempt=' .. _attempt .. ' active=' .. tostring(sampIsDialogActive()) .. ' dlg_id=' .. tostring(sampIsDialogActive() and sampGetCurrentDialogId() or -1) .. ' text=[' .. dlg_text:sub(1,80) .. ']') if sampIsDialogActive() and sampGetCurrentDialogId() == 26547 and dlg_text ~= '' and (_name_check == '' or dlg_text:find(_name_check, 1, true)) then got_dialog = true break end if _attempt < max_attempts then mh_notify('[MH] {ff8800}Диалог не открылся: ' .. item.name .. ' (попытка ' .. (_attempt+1) .. '/' .. max_attempts .. ')', 0xFFFFFF) wait(250) end end if got_dialog then wait(180) if not (sampIsDialogActive() and sampGetCurrentDialogId() == 26547) then got_dialog = false end end if got_dialog 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(26547, 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(26547, 0, 0, '') if fh_lv_sell_confirmed and not fh_lv_sell_forbidden and not fh_lv_sell_no_slots then table.insert(_G._mh_sell_confirmed_items, {name=item.name, price=item.price, qty=item.qty, item_id=tonumber(item.item_id) or 0, slot_idx=tonumber(item.slot) or 0, ench=tonumber(item.ench) or 0}) end 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 .. ' (диалог не открылся за ' .. max_attempts .. ' попытки)', 0xFFFFFF) if sampIsDialogActive() then sampSendDialogResponse(sampGetCurrentDialogId(), 0, 0, '') wait(100) end wait(200) 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() if _mh_sell and _mh_sell.push_state then pcall(_mh_sell.push_state) end _G._mh_sell_ran_session = true _G._mh_sell_run_new_only = (new_only == true) _G._mh_sell_run_finish_mono = os.clock() _G._mh_sell_run_finish_ts = os.time() lua_thread.create(function() wait(300); _G._mh_refresh_own_shop_and_push(true, false) end) end) end do 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('[MH Авто] {ff4444}Список авто-покупки пуст. Добавьте товары.', 0xFFFFFF) return end fh_lv_autobuy_running = true _G._mh_buy_run_finish_mono = 0 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('[MH Авто] {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('[MH Авто] {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('[MH Авто] {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('[MH Авто] {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('[MH Авто] {00cc00}Авто-покупка завершена. Успешно: '..done..'/'..total, 0xFFFFFF) end) end end local function _xpk6g(rebuy_only) 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 local _run_list if rebuy_only then _run_list = {} for _, _abp in ipairs(fh_lv_autobuy_preset) do if _abp.needs_rebuy == true then table.insert(_run_list, _abp) end end if #_run_list == 0 then mh_notify('[MH \xc0\xe2\xf2\xee] {ff8844}\xcd\xe5\xf7\xe5\xe3\xee \xe4\xee\xea\xf3\xef\xe0\xf2\xfc.', 0xFFFFFF) return end else _run_list = {} for _, _abp in ipairs(fh_lv_autobuy_preset) do if _abp.enabled ~= false then table.insert(_run_list, _abp) end end if #_run_list == 0 then mh_notify('[MH \xc0\xe2\xf2\xee] {ff8844}\xc2\xf1\xe5 \xef\xee\xe7\xe8\xf6\xe8\xe8 \xef\xf0\xe5\xf1\xe5\xf2\xe0 \xee\xf2\xea\xeb\xfe\xf7\xe5\xed\xfb.', 0xFFFFFF) return end end fh_lv_autobuy_running = true fh_ab_search_idx = 1 fh_lv_autobuy_status = '\xc7\xe0\xef\xf3\xf1\xea...' _G._mh_buy_confirmed_items = {} if not rebuy_only then _G._mh_ab_has_real_sale = false for _, _abp in ipairs(fh_lv_autobuy_preset) do _abp.needs_rebuy = false end end local _rebuy_qty_override = nil if rebuy_only then _rebuy_qty_override = {} for _, _abp in ipairs(_run_list) do local _tgt = _abp.target_qty or _abp.qty or 1 _rebuy_qty_override[_abp] = (_tgt > 0) and _tgt or (_abp.qty or 1) end end local total = #_run_list 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, '') local _el = 0 while _el < 200 and sampIsDialogActive() do wait(25); _el = _el + 25 end end end local function wait_text_settle(dlg_id, max_ms) local el = 0 local prev = '' local stable_confirms = 0 while el < max_ms do wait(25); el = el + 25 if not sampIsDialogActive() or sampGetCurrentDialogId() ~= dlg_id then return sampGetDialogText() or '' end local cur = sampGetDialogText() or '' if cur ~= '' then if cur == prev then stable_confirms = stable_confirms + 1 if stable_confirms >= 2 then return cur end else stable_confirms = 0 end prev = cur end end return prev 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 _mh_buy and _mh_buy.push_state then pcall(_mh_buy.push_state) end 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 if _mh_buy and _mh_buy.push_state then pcall(_mh_buy.push_state) end return end for pi = 1, total do if not fh_lv_autobuy_running then break end local buy_item = _run_list[pi] if not buy_item then break end local _orig_abp = buy_item fh_ab_search_idx = pi fh_lv_autobuy_status = buy_item.name .. ' (' .. pi .. '/' .. total .. ')' local _override_qty = _rebuy_qty_override and _rebuy_qty_override[buy_item] if _override_qty then buy_item = setmetatable({qty = _override_qty}, {__index = buy_item}) end local search_str = buy_item.search or buy_item.name:sub(1, 24) local dlg = nil if (buy_item.qty or 1) <= 0 or (not rebuy_only and buy_item.enabled == false) then _dsf3y(buy_item.name, 0, buy_item.qty or 0, 'buy', 'skip') goto next_item end 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_text_settle(3040, 400) _G.mh_ab_caught_dlg = nil sampSendDialogResponse(3040, 1, 2, '') dlg = wait_dlg({25663}, 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_text_settle(3040, 400) _G.mh_ab_caught_dlg = nil sampSendDialogResponse(3040, 1, 2, '') dlg = wait_dlg({25663}, 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_text_settle(25663, 1100) _G.mh_ab_caught_dlg = nil sampSendDialogResponse(25663, 1, 0, search_str) dlg = wait_dlg({25664}, 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 = mh_dlg_cp(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(25664, 1, found_idx, '') else mh_notify('[MH] {ff8800}\xcd\xe5 \xed\xe0\xf8\xb8\xeb: ' .. buy_item.name, 0xFFFFFF) sampSendDialogResponse(25664, 0, 0, '') goto next_item end end dlg = wait_dlg({26560, 26563}, 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 == 26560 then _G.mh_ab_caught_dlg = nil sampSendDialogResponse(dlg, 1, 0, '') dlg = wait_dlg({26563}, 2000) _mh_flog('AB s4b dlg=' .. tostring(dlg)) end if dlg == 26563 then if not sampIsDialogActive() then goto next_item end local txt = mh_dlg_cp(sampGetDialogText() or '') for _ab_settle = 1, 4 do wait(35) if not sampIsDialogActive() or sampGetCurrentDialogId() ~= dlg then break end local _ab_re = mh_dlg_cp(sampGetDialogText() or '') if _ab_re ~= '' and _ab_re == txt then break end if _ab_re ~= '' then txt = _ab_re end end local _ab_name_check = buy_item.name:gsub('%(%+%d+%)', ''):match('^%s*(.-)%s*$') or buy_item.name if _ab_name_check ~= '' and not txt:find(_ab_name_check, 1, true) then local _ab_fw = _ab_name_check:lower():match('^(%S+)') for _ab_wcheck = 1, 8 do wait(35) if not sampIsDialogActive() or sampGetCurrentDialogId() ~= dlg then txt = ''; break end local _ab_rt = mh_dlg_cp(sampGetDialogText() or '') if _ab_rt ~= '' then txt = _ab_rt end if txt:find(_ab_name_check, 1, true) then break end if _ab_fw and #_ab_fw >= 4 and txt:lower():find(_ab_fw, 1, true) then break end end _mh_flog('AB s4 namecheck retry item=' .. buy_item.name .. ' text=[' .. txt:sub(1,80) .. ']') end if not sampIsDialogActive() or sampGetCurrentDialogId() ~= dlg then goto next_item end local has_qty = _mh_dialog_has_qty_price(txt) _mh_flog('AB s4 qty=' .. tostring(has_qty) .. ' text=[' .. txt:sub(1,180) .. ']') if has_qty then _mh_clear_qty1_item(buy_item.name) buy_item.qty_limit_1 = nil else _mh_mark_qty1_item(buy_item.name) buy_item.qty_limit_1 = true end local _ab_qty = tonumber(buy_item.qty) or 1 local _ab_target_qty = tonumber(buy_item.target_qty) or 0 if has_qty and _ab_target_qty > _ab_qty and _ab_target_qty > 1 then _ab_qty = _ab_target_qty end if _ab_qty < 1 then _ab_qty = 1 end local _ab_price = tonumber(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') table.insert(_G._mh_buy_confirmed_items, { name = buy_item.name, price = _ab_price, qty = _ab_qty, item_id = tonumber(buy_item.item_id) or 0, slot_idx = tonumber(buy_item.slot) or 0, ench = tonumber(buy_item.ench) or 0 }) if rebuy_only then _orig_abp.needs_rebuy = false end do local _el = 0 while _el < 500 and sampIsDialogActive() and sampGetCurrentDialogId() == dlg do wait(25); _el = _el + 25 end end else if rebuy_only then _orig_abp.needs_rebuy = false end for _ab_i = 1, _ab_qty do if not fh_lv_autobuy_running then break end if _ab_i > 1 then do local _el = 0 while _el < 800 and sampIsDialogActive() and sampGetCurrentDialogId() == dlg do wait(25); _el = _el + 25 end end close_dlg() if not open_shop_menu() then break end wait_text_settle(3040, 400) _G.mh_ab_caught_dlg = nil sampSendDialogResponse(3040, 1, 2, '') dlg = wait_dlg({25663}, 4000) if not dlg then break end wait_text_settle(25663, 1100) _G.mh_ab_caught_dlg = nil sampSendDialogResponse(25663, 1, 0, search_str) dlg = wait_dlg({25664}, 4000) if not dlg then break end wait_text_settle(25664, 400) do local _lt = mh_dlg_cp(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(25664, 0, 0, '') break end _G.mh_ab_caught_dlg = nil sampSendDialogResponse(25664, 1, _fidx, '') end dlg = wait_dlg({26560, 26563}, 4000) if not dlg then break end if dlg == 26560 then _G.mh_ab_caught_dlg = nil sampSendDialogResponse(dlg, 1, 0, '') dlg = wait_dlg({26563}, 3000) if not dlg then break end end if not sampIsDialogActive() then break end end wait_text_settle(dlg, 180) if not sampIsDialogActive() or sampGetCurrentDialogId() ~= dlg then mh_notify('[MH \xc0\xe2\xf2\xee] {ff4444}\xd0\xe8\xf1\xea \xf1\xea\xe8\xef\xe0: ' .. buy_item.name .. ' (\xe4\xe8\xe0\xeb\xee\xe3 \xe7\xe0\xea\xf0\xfb\xeb\xf1\xff)', 0xFFFFFF) break 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') table.insert(_G._mh_buy_confirmed_items, {name=buy_item.name, price=_ab_price, qty=1, item_id=tonumber(buy_item.item_id) or 0, slot_idx=tonumber(buy_item.slot) or 0, ench=tonumber(buy_item.ench) or 0}) end end end ::next_item:: close_dlg() if pi < total and fh_lv_autobuy_running then open_shop_menu() 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) if _mh_buy and _mh_buy.push_state then pcall(_mh_buy.push_state) end _G._mh_buy_ran_session = true _G._mh_buy_run_new_only = false _G._mh_buy_run_finish_mono = os.clock() _G._mh_buy_run_finish_ts = os.time() lua_thread.create(function() wait(300); _G._mh_refresh_own_shop_and_push(true, false) end) end) end do 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('[MH 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('[MH 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('[MH 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('[MH Market] {00cc00}Скан лавки завершён. Товаров: '..tot, 0xFFFFFF) end) end end if not _G.mkt_detail_open then _G.mkt_detail_open = false end do 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 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 = {'.', ',', '_', '-', '=', '~', '+', '#'} do 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 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) 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 _G._dtl_last_build_ts = 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 local _dtl_min_interval = 60 local _dtl_since_build = os.time() - (_G._dtl_last_build_ts or 0) local _dtl_first_build = not _G._dtl_stats if _G._dtl_last_data_ver == _cur_data_ver then _G._dtl_dirty = false if _G._dtl_stats then _G._dtl_ready = true end elseif not _dtl_first_build and _dtl_since_build < _dtl_min_interval then else _G._dtl_last_data_ver = _cur_data_ver _G._dtl_last_build_ts = os.time() _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 = {} 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 local _sh_live_srv = {} for _, sh in pairs(fh_other_shops) do if type(sh) ~= 'table' then goto _dtl_sh_next end local _sh_in_srv = (_dtl_cur_srv_id == -1 or not sh.server_id or sh.server_id == -1 or sh.server_id == _dtl_cur_srv_id) if _sh_in_srv then 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 table.insert(_sh_live_srv, si.price) 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 end 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 ::_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 lv.items_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 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 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 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 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') 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 = _sh_live_srv 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, _ic_tag .. ' ' .. _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), _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.TextColored(imgui.ImVec4(0.6,0.6,0.6,1), _ic_boxes..' '..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 _DTL_HIST_MAX_DAYS = 30 if #_all_dates > _DTL_HIST_MAX_DAYS then for _i = #_all_dates, _DTL_HIST_MAX_DAYS + 1, -1 do table.remove(_all_dates, _i) end end _total_hist_rows = #_all_dates 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), _ic_chrtl .. ' ' .. _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) local _dtl_hd_cws do local _cwhd = imgui.GetWindowContentRegionWidth() local _dt_col = 120*d local _rest3 = (_cwhd - _dt_col) / 3 _dtl_hd_cws = {_dt_col, math.floor(_rest3), math.floor(_rest3), math.floor(_rest3)} imgui.Columns(4, '##dtl_hd', false) imgui.SetColumnWidth(0, _dtl_hd_cws[1]) imgui.SetColumnWidth(1, _dtl_hd_cws[2]) imgui.SetColumnWidth(2, _dtl_hd_cws[3]) imgui.SetColumnWidth(3, _dtl_hd_cws[4]) imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1), _ic_cald..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.Columns(1) imgui.Separator() end if imgui.BeginChild('##dtl_hist_days', imgui.ImVec2(-1, hist_h), true) then _dpn1w() imgui.Columns(4, '##dtl_hd', false) imgui.SetColumnWidth(0, _dtl_hd_cws[1]) imgui.SetColumnWidth(1, _dtl_hd_cws[2]) imgui.SetColumnWidth(2, _dtl_hd_cws[3]) imgui.SetColumnWidth(3, _dtl_hd_cws[4]) 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), _ic_store..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), _ic_clk .. ' ' .. _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 do local function _uwj4x() return os.date('%d.%m.%Y') end end local function _onv2g() return os.date('%H:%M:%S') end do local function fh_datetime_now() return os.date('%d.%m.%Y [%H:%M:%S]') end end do 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 end do 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 end function fh_lower(s) if not s then return '' end s = tostring(s):lower() return (s:gsub('[\192-\223]', function(c) return string.char(c:byte() + 32) end)) end local function fh_prepare_text(s) return fh_lower(tostring(s or '')) end do 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 end do 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 end fh_sort_mode = 1 fh_sort_asc = true do 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 end do 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 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 do 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 end do 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 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_][%w_]*) купил у вас (.+) %((%d+) шт%.%), вы получили .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)' }, { own=true, op='SELL', pat='([%a_][%w_]*) купил у вас (.+) %((%d+) шт%.%), вы получили .-([%d][%d%.%,]*)' }, { own=true, op='SELL', pat='([%a_][%w_]*) купил у вас (.+), вы получили .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)' }, { own=true, op='SELL', pat='([%a_][%w_]*) купил у вас (.+), вы получили .-([%d][%d%.%,]*)' }, { own=true, op='BUY', pat='Вы купили (.+) %((%d+) шт%.%) у игрока ([%a_][%w_]*) за .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)' }, { own=true, op='BUY', pat='Вы купили (.+) %((%d+) шт%.%) у игрока ([%a_][%w_]*) за .-([%d][%d%.%,]*)' }, { own=true, op='BUY', pat='Вы купили (.+) у игрока ([%a_][%w_]*) за .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)' }, { own=true, op='BUY', pat='Вы купили (.+) у игрока ([%a_][%w_]*) за .-([%d][%d%.%,]*)' }, { own=false, op='BUY', pat='Вы успешно продали (.+) %((%d+) шт%.%) торговцу ([%a_][%w_]*), с продажи получили .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)%s*[^%d]' }, { own=false, op='BUY', pat='Вы успешно продали (.+) %((%d+) шт%.%) торговцу ([%a_][%w_]*), с продажи получили [^%d]*([%d][%d%.%,]*)%s*[%(%)%s]' }, { own=false, op='BUY', pat='Вы успешно продали (.+) %((%d+) шт%.%) торговцу ([%a_][%w_]*), с продажи получили .-([%d][%d%.%,]*)%s*$' }, { own=false, op='BUY', pat='Вы успешно продали (.+) торговцу ([%a_][%w_]*), с продажи получили [^%d]*([%d][%d%.%,]*)%s*[%(%)%s]' }, { own=false, op='BUY', pat='Вы успешно продали (.+) торговцу ([%a_][%w_]*), с продажи получили .-([%d][%d%.%,]*)%s*$' }, { own=false, op='SELL', pat='Вы успешно купили (.+) %((%d+) шт%.%) у ([%a_][%w_]*) за .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)' }, { own=false, op='SELL', pat='Вы успешно купили (.+) %((%d+) шт%.%) у ([%a_][%w_]*) за .-([%d][%d%.%,]*)' }, { own=false, op='SELL', pat='Вы успешно купили (.+) у ([%a_][%w_]*) за .-([КМ][К]?%s*[%d][%d%.%,]*%s*[КМ]?[К]?%s*[%d]?[%d%.%,]*)' }, { own=false, op='SELL', pat='Вы успешно купили (.+) у ([%a_][%w_]*) за .-([%d][%d%.%,]*)' }, } local function _wsn4d(text) if not text then return nil end local _clean_for_chat_check = text:gsub('{%x+}', '') local _head40 = _clean_for_chat_check:sub(1, 40) if _head40:match('^%s*[%a_][%w_]*%[%d+%]') or _head40:match('^%s*%b[]%s*[%a_][%w_]*%[%d+%]') 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 _G._mh_sync_own_shop_after_sale = function(_item, _qty, _price) local _q = tonumber(_qty) or 0 if _q <= 0 or not _item or tostring(_item) == '' then return end local _nick = '' pcall(function() local _ok, _id = sampGetPlayerIdByCharHandle(PLAYER_PED) if _ok then _nick = sampGetPlayerNickname(_id) or '' end end) if _nick == '' then return end local _own_num = tonumber(mh_own_shop_num) if not _own_num and settings.general then _own_num = tonumber(settings.general.own_shop_num_saved) end if not _own_num and type(settings.my_shop_snapshot) == 'table' then _own_num = tonumber(settings.my_shop_snapshot.shop_num) end if not _own_num then return end local _name = tostring(_item):gsub('^%s+', ''):gsub('%s+$', ''):lower() local _sale_price = tonumber(_price) or 0 local _changed = false local function _reduce_list(_list) if type(_list) ~= 'table' then return false end local _fallback = nil for _, _it in ipairs(_list) do if type(_it) == 'table' then local _nm = tostring(_it.name or ''):gsub('^%s+', ''):gsub('%s+$', ''):lower() if _nm == _name then if _sale_price > 0 and tonumber(_it.price) == _sale_price then local _old = tonumber(_it.qty) or 0 _it.qty = math.max(0, _old - _q) return true end if not _fallback then _fallback = _it end end end end if _fallback then local _old = tonumber(_fallback.qty) or 0 _fallback.qty = math.max(0, _old - _q) return true end return false end local _snap = settings.my_shop_snapshot if type(_snap) == 'table' and tostring(_snap.owner or ''):lower() == _nick:lower() and tostring(_snap.shop_num or '?') == tostring(_own_num) then if _reduce_list(_snap.sell_items) then _snap.ts = os.time() _changed = true end end for _, _sh in pairs(fh_other_shops or {}) do if type(_sh) == 'table' and tostring(_sh.owner or ''):lower() == _nick:lower() and tostring(_sh.shop_num or '?') == tostring(_own_num) then if _reduce_list(_sh.sell_items) then _sh.ts = os.time() _changed = true end end end for _, _lv in ipairs(mh_arz_data or {}) do if type(_lv) == 'table' and tostring(_lv.username or ''):lower() == _nick:lower() and tostring(_lv.LavkaUid or '?') == tostring(_own_num) then local _updated = false for _i, _iid in ipairs(_lv.items_sell or {}) do local _bid, _ench = _G._bqs3v(_iid) local _nm = _G._rgn9z(_bid) if _ench and _ench ~= '' then _nm = _nm .. ' (+' .. _ench .. ')' end if tostring(_nm or ''):gsub('^%s+', ''):gsub('%s+$', ''):lower() == _name then local _p = tonumber((_lv.price_sell or {})[_i]) or 0 if _sale_price <= 0 or _p == _sale_price then local _old = tonumber((_lv.count_sell or {})[_i]) or 0 _lv.count_sell[_i] = math.max(0, _old - _q) _updated = true break end end end if _updated then _lv._mh_updated_at = os.time() _changed = true end end end if _changed then settings.other_shops = fh_other_shops _wfn7p() end end do 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 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 if _G.as_en_buf then _G.as_en_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 _is_q1 = _mh_is_qty1_item(name) local _tq = _is_q1 and 1 or (tonumber(qty) or 1) table.insert(fh_lv_autobuy_preset, { name = name, qty = _tq, target_qty = _tq, max_price = tonumber(max_price) or 0, qty_limit_1 = _is_q1 or nil, }) _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 local _MH_WEB_STATIC_VER = 19 local function _MH_WEB_URL_FN() local _z = (settings.mh_web and settings.mh_web.zoom) or 0.9 local _nick = '' pcall(function() _nick = (settings.premium and settings.premium.nick) or '' end) if _nick == '' then _nick = _mh_get_live_nick() end local _sess = _G._mh_session_tok or '' return "https://va-ta.com/market/?v=" .. tostring(_MH_WEB_STATIC_VER) .. "&nick=" .. _mh_url_encode(_nick) .. "&sess=" .. _mh_url_encode(_sess) .. "&zoom=" .. tostring(_z) .. "&theme=" .. _mh_theme.query_param() end local _MH_WEB_BID = 9001 local _mh_web_jar_ok, _mh_web_jar = pcall(require, "webviews") if not _mh_web_jar_ok then _mh_web_jar = nil end local function _mh_web_center_text(text) local width = imgui.GetWindowWidth() local calc = imgui.CalcTextSize(text) imgui.SetCursorPosX(width / 2 - calc.x / 2) imgui.Text(text) end local _mh_web_state = { created = false, loaded = false, crashed = false, progress = 0, pos = {x = 0, y = 0, w = 0, h = 0}, } if not settings.mh_web then settings.mh_web = {} end if not settings.mh_theme then settings.mh_theme = {} end _mh_theme = _mh_theme or {} do local _mh_theme_def = { preset = 'emerald', accent = '#39e88f', accent2 = '#2fe6d9', bg = '#050810', panel = '#0c1120', panel2 = '#111729', text = '#e8ecf7', text_dim = '#8891ac', line = '#1c2440', font_scale = 1.0, panel_alpha = 1.0, font_family = 'sans', } for k, v in pairs(_mh_theme_def) do if settings.mh_theme[k] == nil then settings.mh_theme[k] = v end end end _mh_theme_presets = { emerald = {accent='#39e88f', accent2='#2fe6d9', bg='#050810', panel='#0c1120', panel2='#111729', text='#e8ecf7', text_dim='#8891ac', line='#1c2440'}, amber = {accent='#ffb648', accent2='#ff8a3d', bg='#0e0904', panel='#181008', panel2='#20150c', text='#f5ecdf', text_dim='#a89478', line='#3a2814'}, nebula = {accent='#a78bfa', accent2='#f472b6', bg='#0a0714', panel='#140f24', panel2='#1c1533', text='#ede9fe', text_dim='#9a8fc2', line='#2d2350'}, aurora = {accent='#4ade80', accent2='#22d3ee', bg='#040d0c', panel='#081815', panel2='#0d221d', text='#e3fcf3', text_dim='#7fada0', line='#123830'}, deepspace = {accent='#60a5fa', accent2='#818cf8', bg='#03050f', panel='#080c1e', panel2='#0d1329', text='#e2e8ff', text_dim='#7c88b8', line='#1a2350'}, supernova = {accent='#fb7185', accent2='#fbbf24', bg='#0f0508', panel='#1a0a10', panel2='#240e17', text='#fde8ec', text_dim='#b28590', line='#3d1523'}, voidgold = {accent='#eab308', accent2='#f59e0b', bg='#08070a', panel='#100e14', panel2='#17141d', text='#f3f0e8', text_dim='#9d9587', line='#2a2620'}, martian = {accent='#f97316', accent2='#ef4444', bg='#0c0705', panel='#180e09', panel2='#22140c', text='#f7ece4', text_dim='#ad8b76', line='#3d2415'}, cyan = {accent='#2fe6d9', accent2='#39e88f', bg='#020c0e', panel='#061719', panel2='#0b2124', text='#e0fbfa', text_dim='#7cadab', line='#123638'}, monochrome = {accent='#e2e8f0', accent2='#94a3b8', bg='#08090b', panel='#0f1114', panel2='#15181c', text='#f1f5f9', text_dim='#94a0ae', line='#242a31'}, sakura = {accent='#ff8fb3', accent2='#ffc2d9', bg='#0f0810', panel='#1a0f1c', panel2='#241526', text='#fce8f0', text_dim='#b98ca3', line='#3a1f34'}, forest = {accent='#84cc6a', accent2='#c9a24a', bg='#080a06', panel='#10140c', panel2='#161d11', text='#e9f0e0', text_dim='#8fa07d', line='#25311a'}, ocean = {accent='#22d3ee', accent2='#0ea5e9', bg='#02090c', panel='#061419', panel2='#0a1e26', text='#dff6fb', text_dim='#71a3b0', line='#123444'}, sunset = {accent='#fb923c', accent2='#f43f5e', bg='#0f0704', panel='#1b0d07', panel2='#26130a', text='#fbeae0', text_dim='#c08d73', line='#452414'}, crimson = {accent='#ff2d55', accent2='#ff6b6b', bg='#0a0304', panel='#150608', panel2='#1e0a0c', text='#f9e4e6', text_dim='#b87d84', line='#3d1218'}, } function _mh_theme.hex_to_float3(hex) hex = (hex or ''):gsub('#', '') if #hex ~= 6 then return 0, 0, 0 end local r = tonumber(hex:sub(1, 2), 16) or 0 local g = tonumber(hex:sub(3, 4), 16) or 0 local b = tonumber(hex:sub(5, 6), 16) or 0 return r / 255, g / 255, b / 255 end function _mh_theme.sync_imgui_accent() local t = settings.mh_theme local ar, ag, ab = _mh_theme.hex_to_float3(t.accent) settings.interface.accent_r, settings.interface.accent_g, settings.interface.accent_b = ar, ag, ab if _G.accent_color then accent_color[0], accent_color[1], accent_color[2] = ar, ag, ab end local tr, tg, tb = _mh_theme.hex_to_float3(t.text) settings.interface.text_r, settings.interface.text_g, settings.interface.text_b = tr, tg, tb if _G.vid_text_col then _G.vid_text_col[0], _G.vid_text_col[1], _G.vid_text_col[2] = tr, tg, tb end if type(_fwb3h) == 'function' then pcall(_fwb3h) end end function _mh_theme.query_param() local t = settings.mh_theme local payload = { accent = t.accent, accent2 = t.accent2, bg = t.bg, panel = t.panel, panel2 = t.panel2, text = t.text, text_dim = t.text_dim, line = t.line, font_scale = t.font_scale, panel_alpha = t.panel_alpha, font_family = t.font_family, } local ok_j, j = pcall(encodeJson, payload) if not ok_j then return '' end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return '' end return b64 end if not settings.mh_web._zoom_4_9_8_migrated then local _z_keys = { 'zoom', 'shops_zoom', 'auto_zoom', 'sell_zoom', 'piar_zoom', 'buy_zoom', 'arb_zoom', 'pick_zoom', } for _, _zk in ipairs(_z_keys) do if settings.mh_web[_zk] == 0.8 then settings.mh_web[_zk] = 0.9 end end settings.mh_web._zoom_4_9_8_migrated = true end if not settings.mh_web.zoom then settings.mh_web.zoom = 0.9 end local function _mh_web_apply_zoom() if not (_mh_web_jar and _mh_web_state.created) then return end local z = settings.mh_web.zoom pcall(function() _mh_web_jar.executeJS(_MH_WEB_BID, "document.documentElement.style.zoom='" .. tostring(z) .. "';") end) end local function _mh_web_set_zoom(z) if z < 0.3 then z = 0.3 end if z > 1.5 then z = 1.5 end settings.mh_web.zoom = z pcall(_wfn7p) _mh_web_apply_zoom() end if _mh_web_jar then function _mh_web_jar.onAction(data) if not data then return end if data.browserid ~= _MH_WEB_BID then return end if data.type == "WV_STARTED" then _mh_web_state.crashed = false _mh_web_state.loaded = false elseif data.type == "WV_LOADED" then _mh_web_state.loaded = true _mh_web_apply_zoom() if _G._mh_market_tags_push then pcall(_G._mh_market_tags_push) end elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_web_state.progress then _mh_web_state.progress = p end elseif data.type == "WV_DIE" then _mh_web_state.crashed = true _mh_web_state.created = false pcall(function() _mh_web_jar.deleteBrowser(_MH_WEB_BID) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then pcall(_mh_shops_handle_bridge_payload, _js_obj) end end end end end function _mh_web_hide() if _mh_web_jar and _mh_web_state.created then pcall(function() _mh_web_jar.setClickable(_MH_WEB_BID, false) _mh_web_jar.setVisible(_MH_WEB_BID, false) _mh_web_jar.deleteBrowser(_MH_WEB_BID) end) _mh_web_state.created = false _mh_web_state.loaded = false _mh_web_state.crashed = false _mh_web_state.progress = 0 _mh_web_state.visible_applied = false _mh_web_state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_web_soft_hide() if _mh_web_jar and _mh_web_state.created then pcall(function() _mh_web_jar.setClickable(_MH_WEB_BID, false) _mh_web_jar.setVisible(_MH_WEB_BID, false) end) _mh_web_state.visible_applied = false end end function _mh_web_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0 webviews \xed\xe5 \xed\xe0\xe9\xe4\xe5\xed\xe0.') imgui.TextWrapped(u8'\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xe5 \xe1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xf3 webviews (\xf2\xe0 \xe6\xe5, \xf7\xf2\xee \xe8\xf1\xef\xee\xeb\xfc\xe7\xf3\xe5\xf2\xf1\xff \xe2 WebMobile), \xf7\xf2\xee\xe1\xfb \xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xf1\xe0\xe9\xf2\xe0 \xf0\xe0\xe1\xee\xf2\xe0\xeb\xe0.') return end if not _mh_web_state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_MH_WEB_BID, _MH_WEB_URL_FN()) end) if ok then _mh_web_state.created = true _mh_web_state.loaded = false _mh_web_state.crashed = false _mh_web_state.progress = 0 _mh_web_state.visible_applied = false end end if _mh_web_state.created then if not _mh_web_state.visible_applied then pcall(function() _mh_web_jar.setClickable(_MH_WEB_BID, true) _mh_web_jar.setVisible(_MH_WEB_BID, true) end) _mh_web_state.visible_applied = true end end imgui.Text(fa.GLOBE .. ' ' .. u8'\xd0\xdb\xcd\xce\xca') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_web_zoom_out', imgui.ImVec2(30*d, 0)) then _mh_web_set_zoom(settings.mh_web.zoom - 0.1) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_web_zoom_in', imgui.ImVec2(30*d, 0)) then _mh_web_set_zoom(settings.mh_web.zoom + 0.1) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_web_reload') and _mh_web_jar then _mh_web_state.loaded = false _mh_web_state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_MH_WEB_BID, _MH_WEB_URL_FN()) end) end imgui.Separator() local _cp = imgui.GetCursorPos() local _screen_pos = imgui.GetCursorScreenPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _screen_pos.x, _screen_pos.y local _pw, _ph = _avail.x, _avail.y if _mh_web_state.created and _mh_web_jar then local _pos = _mh_web_state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_MH_WEB_BID, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_MH_WEB_BID, _pw, _ph) end) end _mh_web_state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_web_state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff \xef\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc (crash) :(') imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc##mh_web_retry', imgui.ImVec2(160*d, 0)) then _mh_web_state.created = false end elseif not _mh_web_state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) local _pct = _mh_web_state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0... ' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0...') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end local _MH_SHOPS_STATIC_VER = 13 local function _mh_shops_build_url() local _nick = '' pcall(function() _nick = (settings.premium and settings.premium.nick) or '' end) if _nick == '' then _nick = _mh_get_live_nick() end local _sess = _G._mh_session_tok or '' local _z = (settings.mh_web and settings.mh_web.shops_zoom) or 0.9 return "https://va-ta.com/shops/?v=" .. tostring(_MH_SHOPS_STATIC_VER) .. "&nick=" .. _mh_url_encode(_nick) .. "&sess=" .. _mh_url_encode(_sess) .. "&zoom=" .. tostring(_z) .. "&theme=" .. _mh_theme.query_param() end local _MH_SHOPS_BID = 9002 local _mh_shops_state = { created = false, loaded = false, crashed = false, progress = 0, pos = {x = 0, y = 0, w = 0, h = 0}, } if not settings.mh_web.shops_zoom then settings.mh_web.shops_zoom = 0.9 end local function _mh_shops_apply_zoom() if not (_mh_web_jar and _mh_shops_state.created) then return end local z = settings.mh_web.shops_zoom pcall(function() _mh_web_jar.executeJS(_MH_SHOPS_BID, "document.documentElement.style.zoom='" .. tostring(z) .. "';") end) end local function _mh_shops_set_zoom(z) if z < 0.3 then z = 0.3 end if z > 1.5 then z = 1.5 end settings.mh_web.shops_zoom = z pcall(_wfn7p) _mh_shops_apply_zoom() end if _mh_web_jar then local _mh_web_onAction_prev = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _MH_SHOPS_BID then if data.type == "WV_STARTED" then _mh_shops_state.crashed = false _mh_shops_state.loaded = false elseif data.type == "WV_LOADED" then _mh_shops_state.loaded = true _mh_shops_apply_zoom() if _G._mh_shops_tags_push then pcall(_G._mh_shops_tags_push) end elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_shops_state.progress then _mh_shops_state.progress = p end elseif data.type == "WV_DIE" then _mh_shops_state.crashed = true _mh_shops_state.created = false pcall(function() _mh_web_jar.deleteBrowser(_MH_SHOPS_BID) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then pcall(_mh_shops_handle_bridge_payload, _js_obj) end end elseif data.browserid == _MH_SHOPS_BID and data.type ~= "WV_PROGRESS" then return end return end if _mh_web_onAction_prev then _mh_web_onAction_prev(data) end end end function _mh_shops_hide() if _mh_web_jar and _mh_shops_state.created then pcall(function() _mh_web_jar.setClickable(_MH_SHOPS_BID, false) _mh_web_jar.setVisible(_MH_SHOPS_BID, false) _mh_web_jar.deleteBrowser(_MH_SHOPS_BID) end) _mh_shops_state.created = false _mh_shops_state.loaded = false _mh_shops_state.crashed = false _mh_shops_state.progress = 0 _mh_shops_state.visible_applied = false _mh_shops_state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_shops_soft_hide() if _mh_web_jar and _mh_shops_state.created then pcall(function() _mh_web_jar.setClickable(_MH_SHOPS_BID, false) _mh_web_jar.setVisible(_MH_SHOPS_BID, false) end) _mh_shops_state.visible_applied = false end end function _mh_bridge_u8_to_cp(s) if type(s) ~= 'string' or s == '' then return s end local ok, r = pcall(function() return require('encoding').CP1251:encode(require('encoding').UTF8:decode(s)) end) return (ok and r) and r or s end function _mh_shops_handle_bridge_payload(data) if type(data) ~= 'table' or not data.kind then return end if data.kind == 'preset' and data.items and #data.items > 0 then local _preset_name = _mh_bridge_u8_to_cp(data.name) or 'Скупка (сайт)' local _items_cp = {} for i = 1, #data.items do local it = data.items[i] if type(it) == 'table' then local _it_cp = {} for k, v in pairs(it) do _it_cp[k] = (k == 'name') and _mh_bridge_u8_to_cp(v) or v end _items_cp[#_items_cp + 1] = _it_cp end end local exp_ok, path_or_err = _mh_export_preset('buy', _preset_name, _items_cp) if exp_ok then mh_notify('Пресет скупки сохранён: ' .. _preset_name, 0x66FF99) else mh_notify('Не удалось сохранить пресет скупки: ' .. tostring(path_or_err), 0xFF4F6D) end elseif data.kind == 'call' and data.owner and data.owner ~= '' then local _owner_nick = data.owner 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 elseif data.kind == 'upload_my_shop' then if type(mh_push_own_preset_shop) == 'function' then _G._mh_refresh_own_shop_and_push(false, true) end elseif data.kind == 'gps' and data.shop_num ~= nil and tostring(data.shop_num) ~= '' then local _shop_num = tostring(data.shop_num) sampSendChat('/findilavka ' .. _shop_num) elseif data.kind == 'set_tag' and data.item and data.item ~= '' then local _item_name = _mh_bridge_u8_to_cp(data.item) local _tag = data.tag if _tag == 'none' or _tag == '' or _tag == nil then _tag = nil end if _tag == 'watch' or _tag == 'fav' or _tag == 'skip' or _tag == nil then mh_set_item_tag(_item_name, _tag) end end end do local _MH_WISH_STATIC_VER = 4 local function _mh_wish_build_url() local _z = (settings.mh_web and settings.mh_web.wish_zoom) or 0.8 return "https://va-ta.com/collection/?v=" .. tostring(_MH_WISH_STATIC_VER) .. "&zoom=" .. tostring(_z) .. "&theme=" .. _mh_theme.query_param() end local _MH_WISH_BID = 9004 local _mh_wish_state = { created = false, loaded = false, crashed = false, progress = 0, pos = {x = 0, y = 0, w = 0, h = 0}, } if not settings.mh_web.wish_zoom then settings.mh_web.wish_zoom = 0.8 end local function _mh_wish_apply_zoom() if not (_mh_web_jar and _mh_wish_state.created) then return end local z = settings.mh_web.wish_zoom pcall(function() _mh_web_jar.executeJS(_MH_WISH_BID, "document.documentElement.style.zoom='" .. tostring(z) .. "';") end) end local function _mh_wish_set_zoom(z) if z < 0.3 then z = 0.3 end if z > 1.5 then z = 1.5 end settings.mh_web.wish_zoom = z pcall(_wfn7p) _mh_wish_apply_zoom() end function _mh_bridge_cp_to_u8(s) if type(s) ~= 'string' or s == '' then return s end local ok, r = pcall(function() return require('encoding').UTF8:encode(require('encoding').CP1251:decode(s)) end) return (ok and r) and r or s end local function _mh_wish_collect_json() local items = {} if settings.item_tags then for nm, tg in pairs(settings.item_tags) do if nm and nm ~= '' and (tg == 'watch' or tg == 'fav' or tg == 'skip') then table.insert(items, { name = _mh_bridge_cp_to_u8(nm), tag = tg }) end end end local ok_j, j = pcall(encodeJson, { items = items }) if not ok_j then return nil end return j end function _mh_collection_push() if not (_mh_web_jar and _mh_wish_state.created and _mh_wish_state.loaded) then return end local j = _mh_wish_collect_json() if not j then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_MH_WISH_BID, "window.mhReceiveCollection && window.mhReceiveCollection('" .. b64 .. "');") end) end _G._mh_collection_push = _mh_collection_push function _mh_market_tags_push() if not (_mh_web_jar and _mh_web_state.created and _mh_web_state.loaded) then return end local j = _mh_wish_collect_json() if not j then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_MH_WEB_BID, "window.mhReceiveTags && window.mhReceiveTags('" .. b64 .. "');") end) end _G._mh_market_tags_push = _mh_market_tags_push function _mh_shops_tags_push() if not (_mh_web_jar and _mh_shops_state.created and _mh_shops_state.loaded) then return end local j = _mh_wish_collect_json() if not j then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_MH_SHOPS_BID, "window.mhReceiveTags && window.mhReceiveTags('" .. b64 .. "');") end) end _G._mh_shops_tags_push = _mh_shops_tags_push if _mh_web_jar then local _mh_web_onAction_prev2 = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _MH_WISH_BID then if data.type == "WV_STARTED" then _mh_wish_state.crashed = false _mh_wish_state.loaded = false elseif data.type == "WV_LOADED" then _mh_wish_state.loaded = true _mh_wish_apply_zoom() _mh_collection_push() elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_wish_state.progress then _mh_wish_state.progress = p end elseif data.type == "WV_DIE" then _mh_wish_state.crashed = true _mh_wish_state.created = false pcall(function() _mh_web_jar.deleteBrowser(_MH_WISH_BID) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then pcall(_mh_shops_handle_bridge_payload, _js_obj) end end end return end if _mh_web_onAction_prev2 then _mh_web_onAction_prev2(data) end end end function _mh_wish_hide() if _mh_web_jar and _mh_wish_state.created then pcall(function() _mh_web_jar.setClickable(_MH_WISH_BID, false) _mh_web_jar.setVisible(_MH_WISH_BID, false) _mh_web_jar.deleteBrowser(_MH_WISH_BID) end) _mh_wish_state.created = false _mh_wish_state.loaded = false _mh_wish_state.crashed = false _mh_wish_state.progress = 0 _mh_wish_state.visible_applied = false _mh_wish_state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_wish_soft_hide() if _mh_web_jar and _mh_wish_state.created then pcall(function() _mh_web_jar.setClickable(_MH_WISH_BID, false) _mh_web_jar.setVisible(_MH_WISH_BID, false) end) _mh_wish_state.visible_applied = false end end function _mh_wish_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0 webviews \xed\xe5 \xed\xe0\xe9\xe4\xe5\xed\xe0.') imgui.TextWrapped(u8'\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xe5 \xe1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xf3 webviews (\xf2\xe0 \xe6\xe5, \xf7\xf2\xee \xe8\xf1\xef\xee\xeb\xfc\xe7\xf3\xe5\xf2\xf1\xff \xe2 WebMobile), \xf7\xf2\xee\xe1\xfb \xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xf1\xe0\xe9\xf2\xe0 \xf0\xe0\xe1\xee\xf2\xe0\xeb\xe0.') return end if not _mh_wish_state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_MH_WISH_BID, _mh_wish_build_url()) end) if ok then _mh_wish_state.created = true _mh_wish_state.loaded = false _mh_wish_state.crashed = false _mh_wish_state.progress = 0 _mh_wish_state.visible_applied = false end end if _mh_wish_state.created then if not _mh_wish_state.visible_applied then pcall(function() _mh_web_jar.setClickable(_MH_WISH_BID, true) _mh_web_jar.setVisible(_MH_WISH_BID, true) end) _mh_wish_state.visible_applied = true end end imgui.Text(fa.STAR .. ' ' .. u8'\xcf\xce\xc4\xc1\xce\xd0\xca\xc0') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_wish_zoom_out', imgui.ImVec2(30*d, 0)) then _mh_wish_set_zoom(settings.mh_web.wish_zoom - 0.1) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.wish_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_wish_zoom_in', imgui.ImVec2(30*d, 0)) then _mh_wish_set_zoom(settings.mh_web.wish_zoom + 0.1) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_wish_reload') and _mh_web_jar then _mh_wish_state.loaded = false _mh_wish_state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_MH_WISH_BID, _mh_wish_build_url()) end) end imgui.Separator() local _cp = imgui.GetCursorPos() local _screen_pos = imgui.GetCursorScreenPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _screen_pos.x, _screen_pos.y local _pw, _ph = _avail.x, _avail.y if _mh_wish_state.created and _mh_web_jar then local _pos = _mh_wish_state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_MH_WISH_BID, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_MH_WISH_BID, _pw, _ph) end) end _mh_wish_state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_wish_state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff \xef\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc (crash) :(') imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc##mh_wish_retry', imgui.ImVec2(160*d, 0)) then _mh_wish_state.created = false end elseif not _mh_wish_state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) local _pct = _mh_wish_state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0... ' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0...') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end end _mh_auto = _mh_auto or {} _mh_auto.static_ver = 4 _mh_auto.bid = 9005 _mh_auto.state = _mh_auto.state or { created = false, loaded = false, crashed = false, progress = 0, pos = {x = 0, y = 0, w = 0, h = 0}, } _mh_auto.list_cache_k = nil _mh_auto.list_cache_l = {} _mh_auto.SPARK_MAX = 30 _mh_auto.PAGE_SIZE = 30 function _mh_auto.build_url() local _z = (settings.mh_web and settings.mh_web.auto_zoom) or 0.9 return "https://va-ta.com/autos/?v=" .. tostring(_mh_auto.static_ver) .. "&zoom=" .. tostring(_z) .. "&theme=" .. _mh_theme.query_param() end if not settings.mh_web.auto_zoom then settings.mh_web.auto_zoom = 0.9 end function _mh_auto.apply_zoom() if not (_mh_web_jar and _mh_auto.state.created) then return end local z = settings.mh_web.auto_zoom pcall(function() _mh_web_jar.executeJS(_mh_auto.bid, "document.documentElement.style.zoom='" .. tostring(z) .. "';") end) end function _mh_auto.set_zoom(z) if z < 0.3 then z = 0.3 end if z > 1.5 then z = 1.5 end settings.mh_web.auto_zoom = z pcall(_wfn7p) _mh_auto.apply_zoom() end function _mh_auto.u8_to_cp(s) if type(s) ~= 'string' or s == '' then return s end local ok, r = pcall(function() return require('encoding').CP1251:encode(require('encoding').UTF8:decode(s)) end) return (ok and r) and r or s end function _mh_auto.cp_to_u8(s) if type(s) ~= 'string' or s == '' then return s end local ok, r = pcall(function() return require('encoding').UTF8:encode(require('encoding').CP1251:decode(s)) end) return (ok and r) and r or s end function _mh_auto.build_spark(e) if not e or type(e.cp_hist) ~= 'table' or #e.cp_hist == 0 then return nil end local hist = e.cp_hist local from = math.max(1, #hist - _mh_auto.SPARK_MAX + 1) local spark = {} for i = from, #hist do local h = hist[i] if type(h) == 'table' and h.price then spark[#spark + 1] = h.price end end if #spark < 2 then return nil end return spark end function _mh_auto.get_filtered_sorted(search_u8, sort_mode) local search_cp = '' if search_u8 and search_u8 ~= '' then search_cp = (_mh_auto.u8_to_cp(search_u8) or ''):lower() end local ck = search_cp .. '|' .. tostring(sort_mode or 0) if _mh_auto.list_cache_k == ck then return _mh_auto.list_cache_l end local al = {} for nm, e in pairs(fh_mkt_auto) do if type(nm) == 'string' and type(e) == 'table' then if search_cp == '' or nm:lower():find(search_cp, 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 = sort_mode 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) < tostring(b.nm) end) elseif sm == 1 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) < tostring(b.nm) end) else table.sort(al, function(a, b) return tostring(a.nm) < tostring(b.nm) end) end _mh_auto.list_cache_k = ck _mh_auto.list_cache_l = al return al end function _mh_auto_invalidate_cache() _mh_auto.list_cache_k = nil end function _mh_auto_handle_query(data) if not (_mh_web_jar and _mh_auto.state.created and _mh_auto.state.loaded) then return end local req_id = tonumber(data.req_id) or 0 local page = tonumber(data.page) or 1 if page < 1 then page = 1 end local sort_mode = tonumber(data.sort) or 0 local search_u8 = type(data.search) == 'string' and data.search or '' _mh_auto.last_q = { req_id = req_id, page = page, sort = sort_mode, search = search_u8 } local al = _mh_auto.get_filtered_sorted(search_u8, sort_mode) local total_count = #al local total_pages = math.max(1, math.ceil(total_count / _mh_auto.PAGE_SIZE)) if page > total_pages then page = total_pages end local from = (page - 1) * _mh_auto.PAGE_SIZE + 1 local to = math.min(page * _mh_auto.PAGE_SIZE, total_count) local items = {} for i = from, to do local row = al[i] if row then local e = row.e items[#items + 1] = { name = _mh_auto.cp_to_u8(row.nm), price = e.s_avg or e.cp_sp, min = e.s_min, max = e.s_max, date = e.date and _mh_auto.cp_to_u8(e.date) or nil, spark = _mh_auto.build_spark(e), } end end local resp = { req_id = req_id, page = page, total_pages = total_pages, total_count = total_count, items = items, } local ok_j, j = pcall(encodeJson, resp) if not ok_j then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_mh_auto.bid, "window.mhReceiveAutoPage && window.mhReceiveAutoPage('" .. b64 .. "');") end) end function _mh_auto_respond(req_id, search_u8, sort_mode, page) pcall(_mh_auto_handle_query, {req_id = req_id, search = search_u8, sort = sort_mode, page = page}) end function _mh_auto_refresh_web() local q = _mh_auto.last_q if not q then return end pcall(_mh_auto_handle_query, {req_id = q.req_id, search = q.search, sort = q.sort, page = q.page}) end if _mh_web_jar then local _mh_web_onAction_prev4 = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _mh_auto.bid then if data.type == "WV_STARTED" then _mh_auto.state.crashed = false _mh_auto.state.loaded = false elseif data.type == "WV_LOADED" then _mh_auto.state.loaded = true _mh_auto.apply_zoom() _mh_auto_respond(0, '', 0, 1) elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_auto.state.progress then _mh_auto.state.progress = p end elseif data.type == "WV_DIE" then _mh_auto.state.crashed = true _mh_auto.state.created = false pcall(function() _mh_web_jar.deleteBrowser(_mh_auto.bid) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then if _js_obj.kind == 'auto_query' then pcall(_mh_auto_handle_query, _js_obj) else pcall(_mh_shops_handle_bridge_payload, _js_obj) end end end end return end if _mh_web_onAction_prev4 then _mh_web_onAction_prev4(data) end end end function _mh_auto_hide() if _mh_web_jar and _mh_auto.state.created then pcall(function() _mh_web_jar.setClickable(_mh_auto.bid, false) _mh_web_jar.setVisible(_mh_auto.bid, false) _mh_web_jar.deleteBrowser(_mh_auto.bid) end) _mh_auto.state.created = false _mh_auto.state.loaded = false _mh_auto.state.crashed = false _mh_auto.state.progress = 0 _mh_auto.state.visible_applied = false _mh_auto.state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_auto_soft_hide() if _mh_web_jar and _mh_auto.state.created then pcall(function() _mh_web_jar.setClickable(_mh_auto.bid, false) _mh_web_jar.setVisible(_mh_auto.bid, false) end) _mh_auto.state.visible_applied = false end end function _mh_auto_tab_render() local d = settings.general.custom_dpi or 1 if not _G._mh_auto_pull_ts or (os.time() - _G._mh_auto_pull_ts) > 1800 then _G._mh_auto_pull_ts = os.time() lua_thread.create(function() wait(300); _G._mh_auto_prices_pull(true) end) end if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0 webviews \xed\xe5 \xed\xe0\xe9\xe4\xe5\xed\xe0.') imgui.TextWrapped(u8'\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xe5 \xe1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xf3 webviews (\xf2\xe0 \xe6\xe5, \xf7\xf2\xee \xe8\xf1\xef\xee\xeb\xfc\xe7\xf3\xe5\xf2\xf1\xff \xe2 WebMobile), \xf7\xf2\xee\xe1\xfb \xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xf1\xe0\xe9\xf2\xe0 \xf0\xe0\xe1\xee\xf2\xe0\xeb\xe0.') return end if not _mh_auto.state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_mh_auto.bid, _mh_auto.build_url()) end) if ok then _mh_auto.state.created = true _mh_auto.state.loaded = false _mh_auto.state.crashed = false _mh_auto.state.progress = 0 _mh_auto.state.visible_applied = false end end if _mh_auto.state.created then if not _mh_auto.state.visible_applied then pcall(function() _mh_web_jar.setClickable(_mh_auto.bid, true) _mh_web_jar.setVisible(_mh_auto.bid, true) end) _mh_auto.state.visible_applied = true end end imgui.Text(fa.CAR .. ' ' .. u8'\xc0\xc2\xd2\xce\xcc\xce\xc1\xc8\xcb\xc8') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_auto_zoom_out', imgui.ImVec2(30*d, 0)) then _mh_auto.set_zoom(settings.mh_web.auto_zoom - 0.1) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.auto_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_auto_zoom_in', imgui.ImVec2(30*d, 0)) then _mh_auto.set_zoom(settings.mh_web.auto_zoom + 0.1) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_auto_reload') and _mh_web_jar then _mh_auto.state.loaded = false _mh_auto.state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_mh_auto.bid, _mh_auto.build_url()) end) end imgui.Separator() local _cp = imgui.GetCursorPos() local _screen_pos = imgui.GetCursorScreenPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _screen_pos.x, _screen_pos.y local _pw, _ph = _avail.x, _avail.y if _mh_auto.state.created and _mh_web_jar then local _pos = _mh_auto.state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_mh_auto.bid, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_mh_auto.bid, _pw, _ph) end) end _mh_auto.state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_auto.state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff \xef\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc (crash) :(') imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc##mh_auto_retry', imgui.ImVec2(160*d, 0)) then _mh_auto.state.created = false end elseif not _mh_auto.state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) local _pct = _mh_auto.state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0... ' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0...') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end _mh_listings = _mh_listings or {} function _mh_listings.my_nick() return _mh_get_live_nick() end function _mh_listings.post(path, body_tbl, callback) local _nck = _mh_listings.my_nick() local _sess = _G._mh_session_tok or '' local ok_j, j = pcall(encodeJson, body_tbl or {}) if not ok_j then if callback then callback(nil, 'json encode error') end return end if path:sub(1, 5) == '/api/' then path = path:sub(5) end local thread = effil.thread(_twd4k)(_vbr7n .. path, j, _nck, _nck, _sess) lua_thread.create(function() local _deadline = os.clock() + 20 while true do wait(30) if os.clock() > _deadline then pcall(function() thread:cancel() end) if callback then callback(nil, 'timeout') end return end local status, err = thread:status() if not status or err then if callback then callback(nil, tostring(err or 'thread error')) end return end if status == 'completed' or status == 'canceled' then local ok2, resp = thread:get() if not ok2 or not resp then if callback then callback(nil, tostring(resp or 'connection failed')) end return end local code = resp.status_code local ok_d, parsed = pcall(decodeJson, resp.text or '') if code == 200 and ok_d and type(parsed) == 'table' then if callback then callback(parsed, nil) end else local _err if ok_d and type(parsed) == 'table' and parsed.error then _err = parsed.error elseif code == 0 or code == nil then _err = 'connection_error' else _err = 'http_' .. tostring(code) end if callback then callback(nil, tostring(_err)) end end return end end end) end function _mh_listings.error_message(err) if err == 'limit_reached' then return '\xc4\xee\xf1\xf2\xe8\xe3\xed\xf3\xf2\x20\xeb\xe8\xec\xe8\xf2\x20\xe0\xea\xf2\xe8\xe2\xed\xfb\xf5\x20\xee\xe1\xfa\xff\xe2\xeb\xe5\xed\xe8\xe9\x20(5).\x20\xd3\xe4\xe0\xeb\xe8\xf2\xe5\x20\xf1\xf2\xe0\xf0\xee\xe5\x20\xed\xe0\x20\xf1\xe0\xe9\xf2\xe5,\x20\xf7\xf2\xee\xe1\xfb\x20\xf0\xe0\xe7\xec\xe5\xf1\xf2\xe8\xf2\xfc\x20\xed\xee\xe2\xee\xe5.' elseif err == 'cooldown' then return '\xcf\xee\xe4\xed\xff\xf2\xfc\x20\xec\xee\xe6\xed\xee\x20\xf0\xe0\xe7\x20\xe2\x206\x20\xf7\xe0\xf1\xee\xe2\x2e\x20\xcf\xee\xef\xf0\xee\xe1\xf3\xe9\xf2\xe5\x20\xef\xee\xe7\xe6\xe5.' elseif err == 'premium_required' then return '\xcf\xee\xe4\xed\xff\xf2\xe8\xe5\x20\xee\xe1\xfa\xff\xe2\xeb\xe5\xed\xe8\xe9\x20\xe4\xee\xf1\xf2\xf3\xef\xed\xee\x20\xf2\xee\xeb\xfc\xea\xee\x20premium.' elseif err == 'unauthorized' then return '\xcd\xe5\x20\xf3\xe4\xe0\xeb\xee\xf1\xfc\x20\xef\xee\xe4\xf2\xe2\xe5\xf0\xe4\xe8\xf2\xfc\x20\xed\xe8\xea\x20(\xef\xee\xef\xf0\xee\xe1\xf3\xe9\xf2\xe5\x20\xef\xee\xe7\xe6\xe5).' elseif err == 'contact required' then return '\xd3\xea\xe0\xe6\xe8\xf2\xe5\x20\xea\xee\xed\xf2\xe0\xea\xf2\x20\xe4\xeb\xff\x20\xf1\xe2\xff\xe7\xe8\x2e' elseif err == 'empty preset' then return '\xcf\xf0\xe5\xf1\xe5\xf2\x20\xef\xf3\xf1\xf2\x20\xe8\xeb\xe8\x20\xf1\xee\xe4\xe5\xf0\xe6\xe8\xf2\x20\xf2\xee\xeb\xfc\xea\xee\x20\xe2\xfb\xea\xeb\xfe\xf7\xe5\xed\xed\xfb\xe5\x20\xef\xee\xe7\xe8\xf6\xe8\xe8\x2e' elseif err == 'timeout' or (err and tostring(err):find('connect')) then return '\xcd\xe5\xf2\x20\xf1\xe2\xff\xe7\xe8\x20\xf1\x20\xf1\xe5\xf0\xe2\xe5\xf0\xee\xec\x2e' elseif err == 'not found' then return '\xce\xe1\xfa\xff\xe2\xeb\xe5\xed\xe8\xe5\x20\xed\xe5\x20\xed\xe0\xe9\xe4\xe5\xed\xee\x20\x28\xe2\xee\xe7\xec\xee\xe6\xed\xee\x2c\x20\xf3\xe6\xe5\x20\xf3\xe4\xe0\xeb\xe5\xed\xee\x29\x2e' end return '\xce\xf8\xe8\xe1\xea\xe0:\x20' .. tostring(err or '?') end _mh_sell = _mh_sell or {} _mh_sell.static_ver = 16 _mh_sell.bid = 9006 _mh_sell.state = _mh_sell.state or { created = false, loaded = false, crashed = false, progress = 0, pos = {x = -99999, y = -99999, w = 0, h = 0}, push_seq = 0, } function _mh_sell.build_url() local _z = (settings.mh_web and settings.mh_web.sell_zoom) or 0.9 local _srv = _mh_cloud_current_server_id() return "https://va-ta.com/sell/?v=" .. tostring(_mh_sell.static_ver) .. "&zoom=" .. tostring(_z) .. "&server=" .. tostring(_srv) .. "&theme=" .. _mh_theme.query_param() end if not settings.mh_web.sell_zoom then settings.mh_web.sell_zoom = 0.9 end function _mh_sell.apply_zoom() if not (_mh_web_jar and _mh_sell.state.created) then return end local z = settings.mh_web.sell_zoom pcall(function() _mh_web_jar.executeJS(_mh_sell.bid, "document.documentElement.style.zoom='" .. tostring(z) .. "';") end) end function _mh_sell.set_zoom(z) if z < 0.3 then z = 0.3 end if z > 1.5 then z = 1.5 end settings.mh_web.sell_zoom = z pcall(_wfn7p) _mh_sell.apply_zoom() end function _mh_sell.u8_to_cp(s) if type(s) ~= 'string' or s == '' then return s end local ok, r = pcall(function() return require('encoding').CP1251:encode(require('encoding').UTF8:decode(s)) end) return (ok and r) and r or s end function _mh_sell.cp_to_u8(s) if type(s) ~= 'string' or s == '' then return s end local ok, r = pcall(function() return require('encoding').UTF8:encode(require('encoding').CP1251:decode(s)) end) return (ok and r) and r or s end function _mh_sell.item_prices(nm) local _cur_ver = 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) if not _G._mh_sell_ip_cache or _G._mh_sell_ip_cache_ver ~= _cur_ver then _G._mh_sell_ip_cache = {} _G._mh_sell_ip_cache_ver = _cur_ver end local _hit = _G._mh_sell_ip_cache[nm] if _hit then return _hit end local _mp = _mh_get_mkt_price(nm) local _avg7, _avg30, _today 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 local _today_cp = (_mp and _mp.today and _mp.today > 0) and _mp.today or _today local _lvc = _G._lv_shops_cache or {} local _lve = _lvc[nm:lower()] local _sell_lv = _lve and _lve.sell or nil local _res = {today_cp = _today_cp, avg_cp = _today, sell_lv = _sell_lv} _G._mh_sell_ip_cache[nm] = _res return _res end function _mh_sell.snapshot_items(items) local out = {} for i, it in ipairs(items or {}) do local pr = _mh_sell.item_prices(it.name or '') out[i] = { name = _mh_sell.cp_to_u8(it.name), qty = it.qty or 1, price = it.price or 0, enabled = (it.enabled ~= false), today_cp = pr.today_cp, avg_cp = pr.avg_cp, sell_lv = pr.sell_lv, } end return out end function _mh_sell.snapshot_items_light(items) local out = {} for i, it in ipairs(items or {}) do out[i] = { name = _mh_sell.cp_to_u8(it.name), qty = it.qty or 1, price = it.price or 0, enabled = (it.enabled ~= false), } end return out end function _mh_sell.build_state() local _presets = {} for pi, pr in ipairs(settings.presets or {}) do _presets[pi] = { name = _mh_sell.cp_to_u8(pr.name) or ('Preset ' .. tostring(pi)), items = (pi == fh_active_preset_idx) and _mh_sell.snapshot_items(pr.items or {}) or _mh_sell.snapshot_items_light(pr.items or {}), } end local _inv = {} for i, iv in ipairs(fh_lv_inventory or {}) do _inv[i] = {name = _mh_sell.cp_to_u8(iv.name), count = iv.count or 0} end return { kind = 'state', presets = _presets, active_preset = fh_active_preset_idx or 1, inventory = _inv, running = fh_lv_autosell_running and true or false, status = _mh_sell.cp_to_u8(fh_lv_autosell_status or ''), } end function _mh_sell.push_state() if not (_mh_web_jar and _mh_sell.state.created) then return end _mh_sell.state.push_seq = (_mh_sell.state.push_seq or 0) + 1 local _my_seq = _mh_sell.state.push_seq lua_thread.create(function() wait(0) local ok_j, j = pcall(encodeJson, _mh_sell.build_state()) if not ok_j or type(j) ~= 'string' then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end if _mh_sell.state.push_seq ~= _my_seq then return end pcall(function() _mh_web_jar.executeJS(_mh_sell.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end) end function _mh_sell.push_import_list() if not (_mh_web_jar and _mh_sell.state.created and _mh_sell.state.loaded) then return end local _list = _mh_list_presets('sell') local _out = {} for i, e in ipairs(_list) do _out[i] = {fname = e.fname, name = _mh_sell.cp_to_u8(e.name), created = e.created, count = e.count} end local ok_j, j = pcall(encodeJson, {kind = 'import_list', list = _out}) if not ok_j then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_mh_sell.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end function _mh_sell.push_listing_status() if not (_mh_web_jar and _mh_sell.state.created) then return end local _pid = 'sell_' .. tostring(fh_active_preset_idx or 1) _mh_listings.post('/api/mh/listings/mine', {}, function(res, err) if not res or not res.ok then return end local _mine = nil for _, l in ipairs(res.listings or {}) do if l.preset_id == _pid then _mine = l; break end end local ok_j, j = pcall(encodeJson, { kind = 'listing_status', published = _mine ~= nil, listing_id = _mine and _mine.id or nil, is_premium = res.is_premium and true or false, bump_cooldown = res.bump_cooldown, last_bump_at = _mine and _mine.last_bump_at or 0, active_count = #(res.listings or {}), active_limit = res.limit, }) if not ok_j then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_mh_sell.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end) end function _mh_sell.handle_bridge_payload(data) if type(data) ~= 'table' or not data.kind then return end if data.kind == 'ready' then pcall(_mh_sell.push_state) pcall(_mh_sell.push_listing_status) return end local _changed = true if data.kind == 'sell_add' and data.name and data.name ~= '' then local _nm = _mh_sell.u8_to_cp(data.name) local _already = false for _, p in ipairs(fh_lv_autosell_preset) do if fh_lower(p.name) == fh_lower(_nm) then _already = true; break end end if _already then _changed = false else local _pr = _mh_sell.item_prices(_nm) local _best = _pr.today_cp or _pr.avg_cp or _pr.sell_lv or 0 local _qty = 1 for _, iv in ipairs(fh_lv_inventory) do if fh_lower(iv.name) == fh_lower(_nm) then _qty = iv.count or 1; break end end table.insert(fh_lv_autosell_preset, {name = _nm, price = _best, qty = _qty}) end elseif data.kind == 'sell_remove' and data.index then local _idx = tonumber(data.index) if _idx and fh_lv_autosell_preset[_idx] then table.remove(fh_lv_autosell_preset, _idx) else _changed = false end elseif data.kind == 'sell_update' and data.index then local _idx = tonumber(data.index) local _it = _idx and fh_lv_autosell_preset[_idx] if _it then if data.price ~= nil then _it.price = tonumber(data.price) or _it.price end if data.qty ~= nil then _it.qty = tonumber(data.qty) or _it.qty end if data.enabled ~= nil then _it.enabled = data.enabled and true or false end else _changed = false end elseif data.kind == 'sell_reorder' and data.index and data.target then local _idx, _tgt = tonumber(data.index), tonumber(data.target) local _n = #fh_lv_autosell_preset if _idx and _tgt and fh_lv_autosell_preset[_idx] then if _tgt < 1 then _tgt = 1 end if _tgt > _n then _tgt = _n end if _tgt ~= _idx then local _moved = table.remove(fh_lv_autosell_preset, _idx) table.insert(fh_lv_autosell_preset, _tgt, _moved) else _changed = false end else _changed = false end elseif data.kind == 'sell_sort' and data.mode then if data.mode == 'price' then table.sort(fh_lv_autosell_preset, function(a,b) return (a.price or 0) > (b.price or 0) end) elseif data.mode == 'demand' then local _qty_cache = {} for _i = 1, #fh_lv_autosell_preset do local _it2 = fh_lv_autosell_preset[_i] local _e = _it2 and fh_mkt_prices[_it2.name] local _s = _e and _e.cp_hist and _mjg5t(_e.cp_hist, 30) or nil _qty_cache[_it2] = (_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 elseif data.kind == 'sell_preset_switch' and data.index then local _pi = tonumber(data.index) if _pi and settings.presets and settings.presets[_pi] then fh_active_preset_idx = _pi settings.active_preset = _pi fh_lv_autosell_preset = settings.presets[_pi].items or {} else _changed = false end elseif data.kind == 'sell_preset_create' then if not settings.presets then settings.presets = {} end local _nm = (data.name and data.name ~= '') and _mh_sell.u8_to_cp(data.name) or ('\xcf\xf0\xe5\xf1\xe5\xf2\x20' .. tostring(#settings.presets + 1)) table.insert(settings.presets, {name = _nm, items = {}}) fh_active_preset_idx = #settings.presets settings.active_preset = fh_active_preset_idx fh_lv_autosell_preset = settings.presets[fh_active_preset_idx].items elseif data.kind == 'sell_preset_rename' and data.name and data.name ~= '' then local _p = settings.presets and settings.presets[fh_active_preset_idx] if _p then _p.name = _mh_sell.u8_to_cp(data.name) else _changed = false end elseif data.kind == 'sell_preset_delete' then if settings.presets and #settings.presets > 1 then 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 {} else mh_notify('[MH] {ff4444}' .. '\xcd\xe5\xeb\xfc\xe7\xff\x20\xf3\xe4\xe0\xeb\xe8\xf2\xfc\x20\xe5\xe4\xe8\xed\xf1\xf2\xe2\xe5\xed\xed\xfb\xe9\x20\xef\xf0\xe5\xf1\xe5\xf2\x2e', 0xFFFFFF) _changed = false end elseif data.kind == 'sell_preset_import_list' then _mh_sell.push_import_list() _changed = false elseif data.kind == 'sell_preset_import_pick' and data.fname and data.fname ~= '' then local _dir = _mh_preset_dir() local _path = _dir .. '/' .. data.fname local _ok3, _nm3, _items3 = _mh_import_preset_file(_path) if _ok3 then if not settings.presets then settings.presets = {} end table.insert(settings.presets, {name = _nm3, items = _items3 or {}}) fh_active_preset_idx = #settings.presets settings.active_preset = fh_active_preset_idx fh_lv_autosell_preset = settings.presets[fh_active_preset_idx].items else mh_notify('[MH] {ff6666}' .. '\xcd\xe5\x20\xf3\xe4\xe0\xeb\xee\xf1\xfc\x20\xe8\xec\xef\xee\xf0\xf2\xe8\xf0\xee\xe2\xe0\xf2\xfc\x20\xef\xf0\xe5\xf1\xe5\xf2\x2e', 0xFFFFFF) _changed = false end elseif data.kind == 'sell_rescan_inventory' then if not fh_lv_inv_scanning then _vcz9h() lua_thread.create(function() local _w = 0 while fh_lv_inv_scanning and _w < 1200 do wait(10); _w = _w + 1 end if _mh_sell and _mh_sell.push_state then pcall(_mh_sell.push_state) end end) else mh_notify('[MH] {ffaa00}\xd1\xea\xe0\xed\x20\xe8\xed\xe2\xe5\xed\xf2\xe0\xf0\xff\x20\xf3\xe6\xe5\x20\xe8\xe4\xb8\xf2\x2e\x2e\x2e', 0xFFFFFF) end _changed = false elseif data.kind == 'sell_run' or data.kind == 'sell_run_new' then if #fh_lv_autosell_preset > 0 and #fh_lv_inventory > 0 then lua_thread.create(function() wait(0) mh_lavka_inv_ready = false mh_lavka_inv = {} mh_notify('[MH] {ffaa00}' .. '\xce\xf2\xea\xf0\xfb\xe2\xe0\xfe\x20\xeb\xe0\xe2\xea\xf3\x2e\x2e\x2e', 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}' .. '\xcb\xe0\xe2\xea\xe0\x20\xed\xe5\x20\xee\xf2\xea\xf0\xfb\xeb\xe0\xf1\xfc\x2e\x20\xcf\xee\xe4\xee\xe9\xe4\xe8\xf2\xe5\x20\xe1\xeb\xe8\xe6\xe5\x2e', 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}' .. '\xc8\xed\xe2\xe5\xed\xf2\xe0\xf0\xfc\x20\xeb\xe0\xe2\xea\xe8\x20\xed\xe5\x20\xe7\xe0\xe3\xf0\xf3\xe7\xe8\xeb\xf1\xff\x2e', 0xFFFFFF) return end wait(100) fh_lv_autostart_enabled = true _wmc7r(data.kind == 'sell_run_new') if _mh_sell and _mh_sell.push_state then pcall(_mh_sell.push_state) end end) else mh_notify('[MH] {ff4444}' .. '\xcf\xf0\xe5\xf1\xe5\xf2\x20\xef\xf3\xf1\xf2\x20\xe8\xeb\xe8\x20\xe8\xed\xe2\xe5\xed\xf2\xe0\xf0\xfc\x20\xed\xe5\x20\xee\xf2\xf1\xea\xe0\xed\xe8\xf0\xee\xe2\xe0\xed\x2e', 0xFFFFFF) end _changed = false elseif data.kind == 'sell_stop' then fh_lv_autosell_running = false _changed = false elseif data.kind == 'sell_snapshot_save' then settings._as_undo_snap = _mh_deep_copy_list(fh_lv_autosell_preset) mh_notify('[MH] {aaddff}' .. '\xcf\xf0\xe5\xf1\xe5\xf2\x20\xf1\xee\xf5\xf0\xe0\xed\xb8\xed\x20\x28' .. tostring(#fh_lv_autosell_preset) .. '\x20\xf2\xee\xe2\x2e\x29\x20\x2d\x2d\x20\xe2\x20\xf4\xe0\xe9\xeb\x20\xed\xe0\xf1\xf2\xf0\xee\xe5\xea\x20\xe8\x20\xe2\x20\xef\xe0\xec\xff\xf2\xfc\x20\xe4\xeb\xff\x20\xe2\xee\xf1\xf1\xf2\xe0\xed\xee\xe2\xeb\xe5\xed\xe8\xff\x2e', 0xFFFFFF) elseif data.kind == 'sell_snapshot_restore' then if settings._as_undo_snap then fh_lv_autosell_preset = _mh_deep_copy_list(settings._as_undo_snap) mh_notify('[MH] {aaffaa}' .. '\xcf\xf0\xe5\xf1\xe5\xf2\x20\xe2\xee\xf1\xf1\xf2\xe0\xed\xee\xe2\xeb\xe5\xed\x20\xe8\xe7\x20\xf1\xed\xe8\xec\xea\xe0\x20\x28' .. tostring(#fh_lv_autosell_preset) .. '\x20\xf2\xee\xe2\x2e\x29\x2e', 0xFFFFFF) else mh_notify('[MH] {ffaa55}' .. '\xd1\xed\xe8\xec\xee\xea\x20\xe5\xf9\xb8\x20\xed\xe5\x20\xf1\xee\xf5\xf0\xe0\xed\xb8\xed\x2e', 0xFFFFFF) _changed = false end elseif data.kind == 'sell_export_file' then local _pname = (settings.presets and settings.presets[fh_active_preset_idx] and settings.presets[fh_active_preset_idx].name) or ('\xcf\xf0\xe5\xf1\xe5\xf2\x20' .. tostring(fh_active_preset_idx)) local _ok5, _res5 = _mh_export_preset('sell', _pname, fh_lv_autosell_preset) if _ok5 then mh_notify('[MH] {aaffaa}' .. '\xcf\xf0\xe5\xf1\xe5\xf2\x20\xf1\xee\xf5\xf0\xe0\xed\xb8\xed\x20\xe2\x20\xf4\xe0\xe9\xeb\x3a\x20' .. tostring(_res5), 0xFFFFFF) else mh_notify('[MH] {ff6666}' .. '\xcd\xe5\x20\xf3\xe4\xe0\xeb\xee\xf1\xfc\x20\xf1\xee\xf5\xf0\xe0\xed\xe8\xf2\xfc\x20\xef\xf0\xe5\xf1\xe5\xf2\x20\xe2\x20\xf4\xe0\xe9\xeb\x20\x28' .. tostring(_res5) .. ').', 0xFFFFFF) end _changed = false elseif data.kind == 'sell_max_all' then local function _do_sell_max_all() local _updated, _restored = 0, 0 for _mi, _mp2 in ipairs(fh_lv_autosell_preset) do for _, _inv in ipairs(fh_lv_inventory) do if fh_lower(_inv.name) == fh_lower(_mp2.name) and _inv.count > 0 then local _was_zero = (_mp2.qty or 0) <= 0 _mp2.qty = _inv.count if _was_zero and _mp2.qty > 0 and _mp2.enabled == false then _mp2.enabled = true _restored = _restored + 1 end _updated = _updated + 1 break end end end local _msg5 = '[MH] {00cc00}' .. '\xcc\xe0\xea\xf1\x20\xe2\xf1\xe5\x3a\x20\xee\xe1\xed\xee\xe2\xeb\xe5\xed\xee\x20' .. _updated .. '\x20\xf2\xee\xe2\xe0\xf0\xee\xe2\x2e' if _restored > 0 then _msg5 = _msg5 .. '\x20\xc2\xea\xeb\xfe\xf7\xe5\xed\xee\x20\xee\xe1\xf0\xe0\xf2\xed\xee\x3a\x20' .. _restored .. '.' end if _updated == 0 and #fh_lv_autosell_preset > 0 then _msg5 = '[MH] {ffaa44}' .. '\xcc\xe0\xea\xf1\x20\xe2\xf1\xe5\x3a\x20\xed\xe8\x20\xee\xe4\xed\xee\xe9\x20\xef\xee\xe7\xe8\xf6\xe8\xe8\x20\xef\xf0\xe5\xf1\xe5\xf2\xe0\x20\xed\xe5\x20\xed\xe0\xe9\xe4\xe5\xed\xee\x20\xe2\x20\xe8\xed\xe2\xe5\xed\xf2\xe0\xf0\xe5\x2e' end mh_notify(_msg5, 0xFFFFFF) settings.autosell_preset = fh_lv_autosell_preset local _ap5 = settings.presets and settings.presets[fh_active_preset_idx] if _ap5 then _ap5.items = fh_lv_autosell_preset end pcall(_wfn7p) if _mh_sell and _mh_sell.push_state then pcall(_mh_sell.push_state) end end if #fh_lv_inventory == 0 then if fh_lv_inv_scanning then mh_notify('[MH] {ffaa00}' .. '\xd1\xea\xe0\xed\x20\xe8\xed\xe2\xe5\xed\xf2\xe0\xf0\xff\x20\xf3\xe6\xe5\x20\xe8\xe4\xb8\xf2\x2e\x2e\x2e', 0xFFFFFF) else mh_notify('[MH] {ffaa00}' .. '\xc8\xed\xe2\xe5\xed\xf2\xe0\xf0\xfc\x20\xed\xe5\x20\xf1\xea\xe0\xed\xe8\xf0\xee\xe2\xe0\xed\x20\x2d\x20\xe7\xe0\xef\xf3\xf1\xea\xe0\xfe\x20\xf1\xea\xe0\xed\x2e\x2e\x2e', 0xFFFFFF) if type(_vcz9h) == 'function' then _vcz9h() end end lua_thread.create(function() local _w = 0 while fh_lv_inv_scanning and _w < 1200 do wait(10); _w = _w + 1 end _do_sell_max_all() end) else _do_sell_max_all() end _changed = false elseif data.kind == 'sell_clean_preset' then local _inv_names = {} for _, _iv2 in ipairs(fh_lv_inventory) do _inv_names[_iv2.name] = (_iv2.count or 0) end 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 elseif data.kind == 'sell_publish_listing' then local _contact = data.contact or '' local _description = data.description or '' local _pid = 'sell_' .. tostring(fh_active_preset_idx or 1) local _sell_out = {} for _, it in ipairs(fh_lv_autosell_preset or {}) do if it.enabled ~= false then table.insert(_sell_out, { name = _mh_sell.cp_to_u8(it.name), price = it.price or 0, qty = it.qty or 0, }) end end local _srv = _mh_cloud_current_server_id() _mh_listings.post('/api/mh/listings/publish', { preset_id = _pid, deal_type = 'sale', server_id = _srv, sell_items = _sell_out, buy_items = {}, contact = _contact, description = _description, }, function(res, err) if res and res.ok then mh_notify('[MH] {00cc00}' .. (res.action == 'updated' and '\xce\xe1\xfa\xff\xe2\xeb\xe5\xed\xe8\xe5\x20\xee\xe1\xed\xee\xe2\xeb\xe5\xed\xee\x2e' or '\xce\xe1\xfa\xff\xe2\xeb\xe5\xed\xe8\xe5\x20\xf0\xe0\xe7\xec\xe5\xf9\xe5\xed\xee\x2e'), 0xFFFFFF) else mh_notify('[MH] {ff6666}' .. _mh_listings.error_message(err), 0xFFFFFF) end if _mh_sell and _mh_sell.push_listing_status then _mh_sell.push_listing_status() end end) _changed = false elseif data.kind == 'sell_listing_status_req' then if _mh_sell.push_listing_status then _mh_sell.push_listing_status() end _changed = false elseif data.kind == 'set_tag' then pcall(_mh_shops_handle_bridge_payload, data) _changed = false else _changed = false end if _changed then local _ap = settings.presets and settings.presets[fh_active_preset_idx] if _ap then _ap.items = fh_lv_autosell_preset end settings.autosell_preset = fh_lv_autosell_preset lua_thread.create(function() wait(0) pcall(_wfn7p) end) pcall(_mh_sell.push_state) if data.kind == 'sell_preset_switch' or data.kind == 'sell_preset_create' or data.kind == 'sell_preset_delete' or data.kind == 'sell_preset_import_pick' then pcall(_mh_sell.push_listing_status) end end end if _mh_web_jar then local _mh_sell_onAction_prev = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _mh_sell.bid then if data.type == "WV_STARTED" then _mh_sell.state.crashed = false _mh_sell.state.loaded = false elseif data.type == "WV_LOADED" then _mh_sell.state.loaded = true _mh_sell.apply_zoom() _mh_sell.push_state() elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_sell.state.progress then _mh_sell.state.progress = p end elseif data.type == "WV_DIE" then _mh_sell.state.crashed = true _mh_sell.state.created = false pcall(function() _mh_web_jar.deleteBrowser(_mh_sell.bid) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then pcall(_mh_sell.handle_bridge_payload, _js_obj) end end end return end if _mh_sell_onAction_prev then _mh_sell_onAction_prev(data) end end end function _mh_sell_hide() if _mh_web_jar and _mh_sell.state.created then pcall(function() _mh_web_jar.setClickable(_mh_sell.bid, false) _mh_web_jar.setVisible(_mh_sell.bid, false) _mh_web_jar.deleteBrowser(_mh_sell.bid) end) _mh_sell.state.created = false _mh_sell.state.loaded = false _mh_sell.state.crashed = false _mh_sell.state.progress = 0 _mh_sell.state.visible_applied = false _mh_sell.state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_sell_soft_hide() if _mh_web_jar and _mh_sell.state.created then pcall(function() _mh_web_jar.setClickable(_mh_sell.bid, false) _mh_web_jar.setVisible(_mh_sell.bid, false) end) _mh_sell.state.visible_applied = false end end function _mh_sell_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0\x20\x77\x65\x62\x76\x69\x65\x77\x73\x20\xed\xe5\x20\xed\xe0\xe9\xe4\xe5\xed\xe0\x2e') imgui.TextWrapped(u8'\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xe5\x20\xe1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xf3\x20\x77\x65\x62\x76\x69\x65\x77\x73\x20\x28\xf2\xe0\x20\xe6\xe5\x2c\x20\xf7\xf2\xee\x20\xe8\xf1\xef\xee\xeb\xfc\xe7\xf3\xe5\xf2\xf1\xff\x20\xe2\x20\x57\x65\x62\x4d\x6f\x62\x69\x6c\x65\x29\x2c\x20\xf7\xf2\xee\xe1\xfb\x20\xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0\x20\xf1\xe0\xe9\xf2\xe0\x20\xf0\xe0\xe1\xee\xf2\xe0\xeb\xe0\x2e') return end if not _mh_sell.state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_mh_sell.bid, _mh_sell.build_url()) end) if ok then _mh_sell.state.created = true _mh_sell.state.loaded = false _mh_sell.state.crashed = false _mh_sell.state.progress = 0 _mh_sell.state.visible_applied = false end end if _mh_sell.state.created then if not _mh_sell.state.visible_applied then pcall(function() _mh_web_jar.setClickable(_mh_sell.bid, true) _mh_web_jar.setVisible(_mh_sell.bid, true) end) _mh_sell.state.visible_applied = true end end imgui.Text(fa.CART_SHOPPING .. ' ' .. u8'\xcf\xd0\xce\xc4\xc0\xc6\xc0') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_sell_zoom_out', imgui.ImVec2(30*d, 0)) then _mh_sell.set_zoom(settings.mh_web.sell_zoom - 0.1) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.sell_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_sell_zoom_in', imgui.ImVec2(30*d, 0)) then _mh_sell.set_zoom(settings.mh_web.sell_zoom + 0.1) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_sell_reload') and _mh_web_jar then _mh_sell.state.loaded = false _mh_sell.state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_mh_sell.bid, _mh_sell.build_url()) end) end imgui.Separator() local _wp, _cp2 = imgui.GetWindowPos(), imgui.GetCursorPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _wp.x + _cp2.x, _wp.y + _cp2.y local _pw, _ph = _avail.x, _avail.y if _mh_sell.state.created and _mh_web_jar then local _pos = _mh_sell.state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_mh_sell.bid, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_mh_sell.bid, _pw, _ph) end) end _mh_sell.state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_sell.state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp2.x, _cp2.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0\x20\xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff\x20\xef\xe5\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc\x20\x28\x63\x72\x61\x73\x68\x29\x20\x3a\x28') imgui.SetCursorPos(imgui.ImVec2(_cp2.x, _cp2.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc\x23\x23\x6d\x68\x5f\x73\x65\x6c\x6c\x5f\x72\x65\x74\x72\x79', imgui.ImVec2(160*d, 0)) then _mh_sell.state.created = false end elseif not _mh_sell.state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp2.x, _cp2.y + _ph/2 - 10)) local _pct = _mh_sell.state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e\x20' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end _mh_catch = _mh_catch or {} _mh_catch.static_ver = 8 _mh_catch.bid = 9011 _mh_catch.state = _mh_catch.state or { created = false, loaded = false, crashed = false, progress = 0, pos = {x = -99999, y = -99999, w = 0, h = 0}, } function _mh_catch.build_url() local _z = (settings.mh_web and settings.mh_web.catch_zoom) or 0.9 return "https://va-ta.com/catch/?v=" .. tostring(_mh_catch.static_ver) .. "&_cb=" .. tostring(os.time()) .. "&zoom=" .. tostring(_z) .. "&theme=" .. _mh_theme.query_param() end if not settings.mh_web.catch_zoom then settings.mh_web.catch_zoom = 0.9 end function _mh_catch.apply_zoom() if not (_mh_web_jar and _mh_catch.state.created) then return end local z = settings.mh_web.catch_zoom pcall(function() _mh_web_jar.executeJS(_mh_catch.bid, "document.documentElement.style.zoom='" .. tostring(z) .. "';") end) end function _mh_catch.set_zoom(z) if z < 0.3 then z = 0.3 end if z > 1.5 then z = 1.5 end settings.mh_web.catch_zoom = z pcall(_wfn7p) _mh_catch.apply_zoom() end local function _mh_catch_is_box_name(nm) local low = fh_lower(nm or '') if low:find('оскол', 1, true) then return false end return low:find('лар', 1, true) or low:find('кейс', 1, true) or low:find('сундук', 1, true) or low:find('ящик', 1, true) or low:find('тайник', 1, true) or low:find('шкатулк', 1, true) or low:find('box', 1, true) end function _mh_catch_open_record(box_name) if not _G._mh_catch_ready then return end local day = os.date('%Y-%m-%d') local days = settings.catch_open_log if type(days[day]) ~= 'table' then days[day] = {total = 0, boxes = {}} end days[day].total = (tonumber(days[day].total) or 0) + 1 if type(days[day].boxes) ~= 'table' then days[day].boxes = {} end local nm = tostring(box_name or '') if nm ~= '' then days[day].boxes[nm] = (tonumber(days[day].boxes[nm]) or 0) + 1 end pcall(_wfn7p) pcall(_G._mh_save_catch_now) end function _mh_catch_loot_record(text) if type(text) ~= 'string' or text == '' then return false end if not _G._mh_catch_ready then return false end local clean = text:gsub('{%x+}', '') local item = clean:match("Вам был добавлен предмет%s*:%s*'(.-)'%s*[%.!]?") or clean:match("Вам был добавлен предмет%s*'(.-)'%s*[%.!]?") if not item or item == '' then item = clean:match('Вам был добавлен предмет%s*:%s*"(.-)"%s*[%.!]?') or clean:match('Вам был добавлен предмет%s*"(.-)"%s*[%.!]?') or clean:match('Вам был добавлен предмет%s*:%s*[«](.-)[»]%s*[%.!]?') or clean:match('Вам был добавлен предмет%s*[«](.-)[»]%s*[%.!]?') end if not item or item == '' then return false end item = item:gsub('^%s+', ''):gsub('%s+$', '') if item == '' then return false end local qty = tonumber(clean:match("['\"][^'\"]+['\"]%s*[xх]%s*(%d+)")) or tonumber(clean:match('[xх]%s*(%d+)')) or tonumber(clean:match('%((%d+)%s*шт')) or 1 if qty < 1 then qty = 1 end qty = math.floor(qty) local is_auto_capture = _G._ao_session_open or ((_G._mh_catch_loot_capture_until or 0) >= os.time()) if is_auto_capture then local seq = tonumber(_G._mh_catch_open_seq) or 0 _G._mh_catch_loot_seen = _G._mh_catch_loot_seen or {} _G._mh_catch_loot_seen[seq] = _G._mh_catch_loot_seen[seq] or {} local sig = item .. '\31' .. tostring(qty) if _G._mh_catch_loot_seen[seq][sig] then return false end _G._mh_catch_loot_seen[seq][sig] = true end if not is_auto_capture then _G._mh_catch_recent_loot = _G._mh_catch_recent_loot or {} local sig = item .. '\31' .. tostring(qty) local now = os.clock() local prev = _G._mh_catch_recent_loot[sig] if prev and (now - prev) < 0.8 then return false end _G._mh_catch_recent_loot[sig] = now end local day = os.date('%Y-%m-%d') local days = settings.catch_loot_log if type(days[day]) ~= 'table' then days[day] = {} end days[day][item] = (tonumber(days[day][item]) or 0) + qty if not is_auto_capture then local opens = settings.catch_open_log if type(opens[day]) ~= 'table' then opens[day] = {total = 0, boxes = {}} end opens[day].total = (tonumber(opens[day].total) or 0) + 1 end pcall(_wfn7p) pcall(_G._mh_save_catch_now) pcall(_G._mh_catch_push) return true end _G._mh_catch_last_err = '' _G._mh_catch_saves = 0 function _G._mh_catch_dir() return getWorkingDirectory():gsub('\\', '/'):gsub('/$', '') .. '/MarketHelper' end function _G._mh_catch_path(name) return _G._mh_catch_dir() .. '/FH_' .. name end function _G._mh_catch_conv(t, fn, depth) if type(t) ~= 'table' or type(fn) ~= 'function' then return t end depth = depth or 0 if depth > 6 then return t end local r = {} for k, v in pairs(t) do if type(k) == 'string' then k = fn(k) end if type(v) == 'table' then v = _G._mh_catch_conv(v, fn, depth + 1) end r[k] = v end return r end function _G._mh_catch_merge(a, b, depth) if type(a) ~= 'table' then a = {} end if type(b) ~= 'table' then return a end depth = depth or 0 if depth > 6 then return a end for k, v in pairs(b) do local av = a[k] if av == nil then a[k] = v elseif type(av) == 'table' and type(v) == 'table' then _G._mh_catch_merge(av, v, depth + 1) elseif type(av) == 'number' and type(v) == 'number' and v > av then a[k] = v end end return a end function _G._mh_catch_pack(name, tbl) local is_open = name:find('open', 1, true) ~= nil local out = {__v = 2} for day, rec in pairs(tbl) do if type(day) == 'string' and day:match('^%d%d%d%d%-%d%d%-%d%d$') and type(rec) == 'table' then local o = {} if is_open then o.total = tonumber(rec.total) or 0 o.boxes = {} if type(rec.boxes) == 'table' then for bn, c in pairs(rec.boxes) do o.boxes['N' .. tostring(bn)] = tonumber(c) or 0 end end else for it, q in pairs(rec) do o['N' .. tostring(it)] = tonumber(q) or 0 end end out['D' .. day] = o end end return out end function _G._mh_catch_unpack(name, d, drop_num_keys) local is_open = name:find('open', 1, true) ~= nil local v2 = (d.__v == 2) local today = os.date('%Y-%m-%d') local out = {} local function nm(k) if type(k) == 'string' then if v2 then return (k:gsub('^N', '', 1)) end return k end return tostring(k) end for k, v in pairs(d) do local day if type(k) == 'string' then if v2 then day = k:match('^D(%d%d%d%d%-%d%d%-%d%d)$') else day = k:match('^(%d%d%d%d%-%d%d%-%d%d)$') end elseif type(k) == 'number' and not drop_num_keys and k >= 2000 and k < 2200 then day = today end if day and type(v) == 'table' then local cur = out[day] or (is_open and {total = 0, boxes = {}} or {}) if is_open then local t = tonumber(v.total) or 0 if t > cur.total then cur.total = t end if type(v.boxes) == 'table' then for bk, c in pairs(v.boxes) do local n = nm(bk); c = tonumber(c) or 0 if c > (cur.boxes[n] or 0) then cur.boxes[n] = c end end end else for ik, q in pairs(v) do local n = nm(ik); q = tonumber(q) or 0 if q > (cur[n] or 0) then cur[n] = q end end end out[day] = cur end end return out end function _G._mh_catch_read(name) local path = _G._mh_catch_path(name) local f = io.open(path, 'rb') if not f then return nil, 'nofile' end local raw = f:read('*a'); f:close() if type(raw) ~= 'string' or raw:match('^%s*$') then return nil, 'empty' end local ok, d = pcall(decodeJson, raw) if ok and type(d) == 'table' then if d.__enc == 'u8' then d.__enc = nil d = _G._mh_catch_conv(d, _G._mh_bridge_u8_to_cp or _mh_bridge_u8_to_cp) end local ok2, r = pcall(_G._mh_catch_unpack, name, d) if ok2 and type(r) == 'table' then return r end _G._mh_catch_last_err = 'unpack: ' .. tostring(r) end local bf = io.open(path .. '.bad', 'wb') if bf then bf:write(raw); bf:close() end return nil, 'decode' end function _G._mh_catch_write(name, tbl) if type(tbl) ~= 'table' then return false end local path = _G._mh_catch_path(name) pcall(_mh_ensure_dir, _G._mh_catch_dir()) local packed = _G._mh_catch_pack(name, tbl) local ok, j = pcall(encodeJson, packed) if not ok or type(j) ~= 'string' or j == '' then local first_err = tostring(j) local u = _G._mh_catch_conv(packed, _mh_bridge_cp_to_u8) if type(u) == 'table' then u.__enc = 'u8' local ok2, j2 = pcall(encodeJson, u) if ok2 and type(j2) == 'string' and j2 ~= '' then ok, j = true, j2 end end if not ok then _G._mh_catch_last_err = 'encode: ' .. first_err return false end end local f, ferr = io.open(path, 'wb') if not f then _G._mh_catch_last_err = 'open: ' .. tostring(ferr) .. ' | ' .. path return false end local wok, werr = f:write(j) f:close() if not wok then _G._mh_catch_last_err = 'write: ' .. tostring(werr) return false end _G._mh_catch_saves = (_G._mh_catch_saves or 0) + 1 return true end function _G._mh_catch_init() local st = {} for _, p in ipairs({ {'catch_loot_log.json', 'catch_loot_log'}, {'catch_open_log.json', 'catch_open_log'}, }) do local d, why = _G._mh_catch_read(p[1]) local legacy = {} if type(settings[p[2]]) == 'table' then local okl, rl = pcall(_G._mh_catch_unpack, p[1], settings[p[2]], true) if okl and type(rl) == 'table' then legacy = rl end end if d then settings[p[2]] = _G._mh_catch_merge(d, legacy) else settings[p[2]] = legacy if why == 'decode' then _G._mh_catch_last_err = 'decode ' .. p[1] .. ' (kopiya: .bad)' end end st[#st + 1] = p[1] .. '=' .. tostring(why or 'ok') end _G._mh_catch_ready = true _G._mh_catch_init_state = table.concat(st, ', ') pcall(_G._mh_save_catch_now) end function _G._mh_catch_status() local nd, ni, no = 0, 0, 0 if type(settings.catch_loot_log) == 'table' then for _, items in pairs(settings.catch_loot_log) do nd = nd + 1 if type(items) == 'table' then for _ in pairs(items) do ni = ni + 1 end end end end if type(settings.catch_open_log) == 'table' then for _, rec in pairs(settings.catch_open_log) do if type(rec) == 'table' then no = no + (tonumber(rec.total) or 0) end end end local function fsize(name) local f = io.open(_G._mh_catch_path(name), 'rb') if not f then return -1 end local sz = f:seek('end') or 0; f:close(); return sz end local _extra = string.format(' | soobsh=%d zapisano=%d | tekst: %s', _G._mh_catch_seen_n or 0, _G._mh_catch_rec_n or 0, tostring(_G._mh_catch_last_text or '-')) return _extra .. string.format(' || ready=%s | dney=%d predm.=%d otkryto=%d | fajly: loot=%db open=%db | zapisej=%d | start: %s | oshibka: %s', tostring(_G._mh_catch_ready), nd, ni, no, fsize('catch_loot_log.json'), fsize('catch_open_log.json'), _G._mh_catch_saves or 0, tostring(_G._mh_catch_init_state or '?'), (_G._mh_catch_last_err ~= '' and _G._mh_catch_last_err or 'net')) end _G._mh_catch_init() function _mh_catch_loot_query(day_filter) local days = type(settings.catch_loot_log) == 'table' and settings.catch_loot_log or {} local open_days = type(settings.catch_open_log) == 'table' and settings.catch_open_log or {} local now = os.time() local out_days = {} local total_qty, total_types, total_opened = 0, 0, 0 local seen_types = {} local day_keys = {} local merged = {} for day, items in pairs(days) do if type(day) == 'string' and day:match('^%d%d%d%d%-%d%d%-%d%d$') and type(items) == 'table' then merged[day] = merged[day] or {} for name, qty in pairs(items) do if type(name) == 'string' then merged[day][name] = tonumber(qty) or 0 end end end end for day, rec in pairs(open_days) do if type(day) == 'string' and day:match('^%d%d%d%d%-%d%d%-%d%d$') and type(rec) == 'table' then merged[day] = merged[day] or {} end end for day in pairs(merged) do if type(day) == 'string' then table.insert(day_keys, day) end end table.sort(day_keys, function(a,b) return a > b end) local function include_day(day) local y,m,d = day:match('^(%d%d%d%d)%-(%d%d)%-(%d%d)$') if not y then return false, nil end local ts = os.time({year=tonumber(y),month=tonumber(m),day=tonumber(d),hour=12,min=0,sec=0}) local age = math.floor((os.time({year=tonumber(os.date('%Y', now)),month=tonumber(os.date('%m', now)),day=tonumber(os.date('%d', now)),hour=12,min=0,sec=0}) - ts) / 86400 + 0.5) if day_filter == 1 then return day == os.date('%Y-%m-%d', now), {y=y,m=m,d=d} end if day_filter == 2 then return age >= 0 and age < 7, {y=y,m=m,d=d} end if day_filter == 3 then return age >= 0 and age < 30, {y=y,m=m,d=d} end return true, {y=y,m=m,d=d} end for _, day in ipairs(day_keys) do local include, parts = include_day(day) if include and parts then local items = days[day] or {} local arr, day_qty = {}, 0 for name, qty in pairs(items) do qty = tonumber(qty) or 0 if qty > 0 then table.insert(arr, {name=_mh_bridge_cp_to_u8(name), qty=qty}) day_qty = day_qty + qty total_qty = total_qty + qty if not seen_types[name] then seen_types[name] = true total_types = total_types + 1 end end end table.sort(arr, function(a,b) return a.qty > b.qty or (a.qty == b.qty and a.name < b.name) end) local op = open_days[day] local opened = type(op) == 'table' and (tonumber(op.total) or 0) or 0 total_opened = total_opened + opened local boxes = {} if type(op) == 'table' and type(op.boxes) == 'table' then for box, count in pairs(op.boxes) do count = tonumber(count) or 0 if count > 0 then table.insert(boxes, {name=_mh_bridge_cp_to_u8(box), qty=count}) end end end table.sort(boxes, function(a,b) return a.qty > b.qty or (a.qty == b.qty and a.name < b.name) end) if opened > 0 or #arr > 0 then table.insert(out_days, { date=day, label=string.format('%02d.%02d.%04d', tonumber(parts.d), tonumber(parts.m), tonumber(parts.y)), opened=opened, total=day_qty, boxes=boxes, items=arr, }) end end end return out_days, total_qty, total_types, total_opened end function _mh_catch.build_state() local _is_prem = _mh_is_premium() _mh_flog('[catch] build_state: premium=' .. tostring(_is_prem)) local _boxes, _box_count = {}, 0 for _, it in ipairs(fh_lv_inventory or {}) do if _mh_catch_is_box_name(it.name) then _box_count = _box_count + 1 local _slot_idx = -1 if it.stacks and it.stacks[1] then _slot_idx = it.stacks[1].slot end table.insert(_boxes, { name = _mh_catch.cp_to_u8(it.name), count = it.count or 0, slot = _slot_idx, }) end end local _slot_out = nil if _G._ao_slots and _G._ao_slots[1] then local _s = _G._ao_slots[1] _slot_out = { name = _mh_catch.cp_to_u8(_s.name or ''), max_count = _s.max_count or 0, interval = _s.interval or 1.0, done = _s.done or 0, running = _s.running and true or false, status = _mh_catch.cp_to_u8(_s.status or ''), delete_on_sold = _s.delete_on_sold and true or false, } end local _tap_out = nil if _is_prem then local _tap = settings.trade_autoprice or {} _tap_out = { enabled = _tap.enabled and true or false, pct = _tap.pct or 65 } end return { kind = 'state', premium = _is_prem, radius_enabled = cm_radius_enabled and true or false, radius_draw_dist = cm_radius_draw_dist or 20, render_enabled = cm_render_enabled and true or false, storage_enabled = (settings.general and settings.general.auto_storage_collect) and true or false, inv_scanning = fh_lv_inv_scanning and true or false, boxes = _boxes, box_count = _box_count, slot = _slot_out, autoprice = _tap_out, } end function _mh_catch.u8_to_cp(s) if type(s) ~= 'string' or s == '' then return s end local ok, r = pcall(function() return require('encoding').CP1251:encode(require('encoding').UTF8:decode(s)) end) return (ok and r) and r or s end function _mh_catch.cp_to_u8(s) if type(s) ~= 'string' or s == '' then return s end local ok, r = pcall(function() return require('encoding').UTF8:encode(require('encoding').CP1251:decode(s)) end) return (ok and r) and r or s end function _mh_catch.push_state() if not (_mh_web_jar and _mh_catch.state.created) then _mh_flog('[catch] push_state: guard fail, jar=' .. tostring(_mh_web_jar ~= nil) .. ' created=' .. tostring(_mh_catch.state.created)) return end _mh_catch.state.push_seq = (_mh_catch.state.push_seq or 0) + 1 local _my_seq = _mh_catch.state.push_seq _mh_flog('[catch] push_state: queued seq=' .. tostring(_my_seq)) lua_thread.create(function() wait(0) local ok_j, j = pcall(encodeJson, _mh_catch.build_state()) if not ok_j or type(j) ~= 'string' then _mh_flog('[catch] push_state seq=' .. tostring(_my_seq) .. ': encodeJson FAILED, ok=' .. tostring(ok_j) .. ' err=' .. tostring(j)) return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then _mh_flog('[catch] push_state seq=' .. tostring(_my_seq) .. ': base64 FAILED, ok=' .. tostring(ok_b64)) return end if _mh_catch.state.push_seq ~= _my_seq then _mh_flog('[catch] push_state seq=' .. tostring(_my_seq) .. ': stale, current=' .. tostring(_mh_catch.state.push_seq) .. ' -- dropped') return end local ok_exec, exec_err = pcall(function() _mh_web_jar.executeJS(_mh_catch.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) _mh_flog('[catch] push_state seq=' .. tostring(_my_seq) .. ': executeJS ok=' .. tostring(ok_exec) .. ' err=' .. tostring(exec_err) .. ' json_len=' .. tostring(#j)) end) end function _mh_catch.handle_bridge_payload(data) if type(data) ~= 'table' or not data.kind then return end _mh_flog('[catch] handle_bridge_payload: kind=' .. tostring(data.kind)) if data.kind == 'ready' then pcall(_mh_catch.push_state) return end if data.kind == 'catch_toggle_radius' then cm_radius_enabled = data.value and true or false elseif data.kind == 'catch_set_radius_dist' then local v = tonumber(data.value) or 20 if v < 10 then v = 10 end if v > 20 then v = 20 end cm_radius_draw_dist = v elseif data.kind == 'catch_toggle_render' then cm_render_enabled = data.value and true or false elseif data.kind == 'catch_toggle_storage' then if not settings.general then settings.general = {} end settings.general.auto_storage_collect = data.value and true or false elseif data.kind == 'catch_scan_inventory' then if not fh_lv_inv_scanning then _vcz9h() pcall(_mh_catch.push_state) lua_thread.create(function() wait(200) _yzr1t(52, -1, 2, '{"slot":0,"type":1}') end) end elseif data.kind == 'catch_open_start' then if not _mh_is_premium() then return end local _nm = _mh_catch.u8_to_cp(data.name or '') if _nm == '' then return end local _slot_idx = -1 local _item_id = nil for _, it in ipairs(fh_lv_inventory or {}) do if it.name == _nm then if it.stacks and it.stacks[1] then _slot_idx = it.stacks[1].slot end if mh_arz_items_db then for _iid, _inm in pairs(mh_arz_items_db) do if _inm == it.name then _item_id = _iid; break end end end break end end if _slot_idx < 0 then _mh_catch.push_state() return end if not _G._ao_slots then _G._ao_slots = {} end local _max = tonumber(data.max_count) or 0 if _max < 0 then _max = 0 end local _interval = tonumber(data.interval) or 1.0 if _interval < 0.1 then _interval = 0.1 end if _interval > 300 then _interval = 300 end _G._ao_slots[1] = { name = _nm, slot_idx = _slot_idx, item_id = _item_id, interval = _interval, max_count = _max, done = 0, running = true, status = 'Запуск...', delete_on_sold = data.delete_on_sold and true or false, } mh_notify('[MH] {00cc00}Открытие (сайт): ' .. _nm .. ' | каждые ' .. _interval .. 'с' .. (_max > 0 and (' | макс ' .. _max) or ''), 0xFFFFFF) lua_thread.create(function() local _s = _G._ao_slots[1] 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}Открытие готово: ' .. _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 end _mh_catch.push_state() 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 = 'Нет в инвентаре!' _mh_catch.push_state() wait(2000) else if not _G._ao_session_open then _yzr1t(52, -1, 0, '""') wait(300) _G._ao_session_open = true end _G._mh_catch_open_seq = (tonumber(_G._mh_catch_open_seq) or 0) + 1 _mh_catch_open_record(_s.name) _G._mh_catch_loot_capture_until = os.time() + 15 _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 '') _mh_catch.push_state() end local _ms, _el, _last_rem = math.floor(_s.interval * 1000), 0, nil while _el < _ms and _s.running do wait(100); _el = _el + 100 local _rem = math.ceil((_ms - _el) / 1000) if _rem > 0 and _rem ~= _last_rem then _last_rem = _rem _s.status = 'След. через ' .. _rem .. 'с | открыто: ' .. _s.done _mh_catch.push_state() end end end end) elseif data.kind == 'catch_open_stop' then if _G._ao_slots and _G._ao_slots[1] then local _s = _G._ao_slots[1] _s.running = false _s.status = 'Стоп (' .. (_s.done or 0) .. ')' _G._ao_session_open = false mh_notify('[MH] {ff8800}Открытие остановлено (сайт). Открыто: ' .. (_s.done or 0), 0xFFFFFF) end elseif data.kind == 'catch_autoprice_set' then if not _mh_is_premium() then _mh_catch.push_state() return end if not settings.trade_autoprice then settings.trade_autoprice = {} end if data.key == 'enabled' then settings.trade_autoprice.enabled = data.value and true or false elseif data.key == 'pct' then local v = tonumber(data.value) or 65 if v < 10 then v = 10 end if v > 100 then v = 100 end settings.trade_autoprice.pct = v end elseif data.kind == 'set_tag' then pcall(_mh_shops_handle_bridge_payload, data) return else return end pcall(_wfn7p) pcall(_mh_catch.push_state) end if _mh_web_jar then local _mh_catch_onAction_prev = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _mh_catch.bid then if data.type == "WV_STARTED" then _mh_catch.state.crashed = false _mh_catch.state.loaded = false elseif data.type == "WV_LOADED" then _mh_flog('[catch] onAction: WV_LOADED') _mh_catch.state.loaded = true _mh_catch.apply_zoom() _mh_catch.push_state() lua_thread.create(function() local _deadline = os.clock() + 12 while _mh_catch.state.loaded and _mh_catch.state.created do wait(500) if not (_mh_catch.state.loaded and _mh_catch.state.created) then break end pcall(_mh_catch.push_state) if _mh_is_premium() then break end if os.clock() > _deadline then break end end end) elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_catch.state.progress then _mh_catch.state.progress = p end elseif data.type == "WV_DIE" then _mh_flog('[catch] onAction: WV_DIE (crash)') _mh_catch.state.crashed = true _mh_catch.state.created = false pcall(function() _mh_web_jar.deleteBrowser(_mh_catch.bid) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then pcall(_mh_catch.handle_bridge_payload, _js_obj) else _mh_flog('[catch] onAction: WV_ANSWER decodeJson FAILED ok=' .. tostring(_ok_js) .. ' raw=' .. _json_part:sub(1, 200)) end else _mh_flog('[catch] onAction: WV_ANSWER (no MH_BRIDGE prefix) raw=' .. _raw_msg:sub(1, 200)) end end return end if _mh_catch_onAction_prev then _mh_catch_onAction_prev(data) end end end function _mh_catch_hide() if _mh_web_jar and _mh_catch.state.created then pcall(function() _mh_web_jar.setClickable(_mh_catch.bid, false) _mh_web_jar.setVisible(_mh_catch.bid, false) _mh_web_jar.deleteBrowser(_mh_catch.bid) end) _mh_catch.state.created = false _mh_catch.state.loaded = false _mh_catch.state.crashed = false _mh_catch.state.progress = 0 _mh_catch.state.visible_applied = false _mh_catch.state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_catch_soft_hide() if _mh_web_jar and _mh_catch.state.created then pcall(function() _mh_web_jar.setClickable(_mh_catch.bid, false) _mh_web_jar.setVisible(_mh_catch.bid, false) end) _mh_catch.state.visible_applied = false end end function _mh_catch_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0\x20\x77\x65\x62\x76\x69\x65\x77\x73\x20\xed\xe5\x20\xed\xe0\xe9\xe4\xe5\xed\xe0\x2e') imgui.TextWrapped(u8'\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xe5\x20\xe1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xf3\x20\x77\x65\x62\x76\x69\x65\x77\x73\x20\x28\xf2\xe0\x20\xe6\xe5\x2c\x20\xf7\xf2\xee\x20\xe8\xf1\xef\xee\xeb\xfc\xe7\xf3\xe5\xf2\xf1\xff\x20\xe2\x20\x57\x65\x62\x4d\x6f\x62\x69\x6c\x65\x29\x2c\x20\xf7\xf2\xee\xe1\xfb\x20\xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0\x20\xf1\xe0\xe9\xf2\xe0\x20\xf0\xe0\xe1\xee\xf2\xe0\xeb\xe0\x2e') return end if not _mh_catch.state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_mh_catch.bid, _mh_catch.build_url()) end) if ok then _mh_catch.state.created = true _mh_catch.state.loaded = false _mh_catch.state.crashed = false _mh_catch.state.progress = 0 _mh_catch.state.visible_applied = false end end if _mh_catch.state.created then local _now = os.clock() if not _mh_catch.state.last_state_push or (_now - _mh_catch.state.last_state_push) >= 0.75 then _mh_catch.state.last_state_push = _now pcall(_mh_catch.push_state) end end if _mh_catch.state.created then if not _mh_catch.state.visible_applied then pcall(function() _mh_web_jar.setClickable(_mh_catch.bid, true) _mh_web_jar.setVisible(_mh_catch.bid, true) end) _mh_catch.state.visible_applied = true end end imgui.Text(fa.LOCATION_CROSSHAIRS .. ' ' .. u8'\xcb\xce\xc2\xcb\xdf') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_catch_zoom_out', imgui.ImVec2(30*d, 0)) then _mh_catch.set_zoom(settings.mh_web.catch_zoom - 0.1) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.catch_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_catch_zoom_in', imgui.ImVec2(30*d, 0)) then _mh_catch.set_zoom(settings.mh_web.catch_zoom + 0.1) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_catch_reload') and _mh_web_jar then _mh_catch.state.loaded = false _mh_catch.state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_mh_catch.bid, _mh_catch.build_url()) end) end imgui.Separator() local _wp, _cp2 = imgui.GetWindowPos(), imgui.GetCursorPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _wp.x + _cp2.x, _wp.y + _cp2.y local _pw, _ph = _avail.x, _avail.y if _mh_catch.state.created and _mh_web_jar then local _pos = _mh_catch.state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_mh_catch.bid, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_mh_catch.bid, _pw, _ph) end) end _mh_catch.state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_catch.state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp2.x, _cp2.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0\x20\xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff\x20\xef\xe5\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc\x20\x28\x63\x72\x61\x73\x68\x29\x20\x3a\x28') imgui.SetCursorPos(imgui.ImVec2(_cp2.x, _cp2.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc\x23\x23\x6d\x68\x5f\x63\x61\x74\x63\x68\x5f\x72\x65\x74\x72\x79', imgui.ImVec2(160*d, 0)) then _mh_catch.state.created = false end elseif not _mh_catch.state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp2.x, _cp2.y + _ph/2 - 10)) local _pct = _mh_catch.state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e\x20' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end _mh_piar = _mh_piar or {} _mh_piar.static_ver = 12 _mh_piar.bid = 9010 _mh_piar.state = _mh_piar.state or { created = false, loaded = false, crashed = false, progress = 0, pos = {x = -99999, y = -99999, w = 0, h = 0}, push_seq = 0, } function _mh_piar.build_url() local _z = (settings.mh_web and settings.mh_web.piar_zoom) or 0.9 return "https://va-ta.com/pr/?v=" .. tostring(_mh_piar.static_ver) .. "&zoom=" .. tostring(_z) .. "&theme=" .. _mh_theme.query_param() end if not settings.mh_web.piar_zoom then settings.mh_web.piar_zoom = 0.9 end function _mh_piar.apply_zoom() if not (_mh_web_jar and _mh_piar.state.created) then return end local z = settings.mh_web.piar_zoom pcall(function() _mh_web_jar.executeJS(_mh_piar.bid, "document.documentElement.style.zoom='" .. tostring(z) .. "';") end) end function _mh_piar.set_zoom(z) if z < 0.3 then z = 0.3 end if z > 1.5 then z = 1.5 end settings.mh_web.piar_zoom = z pcall(_wfn7p) _mh_piar.apply_zoom() end function _mh_piar.u8_to_cp(s) if type(s) ~= 'string' or s == '' then return s end local ok, r = pcall(function() return require('encoding').CP1251:encode(require('encoding').UTF8:decode(s)) end) return (ok and r) and r or s end function _mh_piar.cp_to_u8(s) if type(s) ~= 'string' or s == '' then return s end local ok, r = pcall(function() return require('encoding').UTF8:encode(require('encoding').CP1251:decode(s)) end) return (ok and r) and r or s end function _mh_piar.build_state() local _templates = {} for i, t in ipairs(settings.piar_templates or {}) do local _lines = {} for li, ln in ipairs(t.lines or {}) do _lines[li] = _mh_piar.cp_to_u8(ln) end local _queue_left, _queue_total = nil, nil if t.auto then local _iv = t.auto_interval or 300 if (t.auto_interval_max or 0) > _iv then _iv = t._next_interval or _iv end _queue_total = _iv _queue_left = math.max(0, _iv - (os.time() - (t.last_time or 0))) end _templates[i] = { name = _mh_piar.cp_to_u8(t.name) or '', enable = (t.enable ~= false), auto = t.auto and true or false, waiting = t.waiting or 1.5, auto_interval = t.auto_interval or 300, auto_interval_max = t.auto_interval_max or 0, auto_limit = t.auto_limit or 0, auto_count = t.auto_count or 0, random_line = t.random_line and true or false, lines = _lines, queue_left = _queue_left, queue_total = _queue_total, } end return { kind = 'state', templates = _templates, own_shop_num = mh_own_shop_num or (settings.general and settings.general.own_shop_num_saved), auto_ad_confirm = settings.general and settings.general.auto_ad_confirm and true or false, auto_ad_station = settings.general and settings.general.auto_ad_station_idx or 2, auto_ad_type = settings.general and settings.general.auto_ad_type or 0, } end function _mh_piar.push_state() if not (_mh_web_jar and _mh_piar.state.created) then return end _mh_piar.state.push_seq = (_mh_piar.state.push_seq or 0) + 1 local _my_seq = _mh_piar.state.push_seq lua_thread.create(function() wait(0) local ok_j, j = pcall(encodeJson, _mh_piar.build_state()) if not ok_j or type(j) ~= 'string' then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end if _mh_piar.state.push_seq ~= _my_seq then return end pcall(function() _mh_web_jar.executeJS(_mh_piar.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end) end function _mh_piar.handle_bridge_payload(data) if type(data) ~= 'table' or not data.kind then return end if data.kind == 'ready' then pcall(_mh_piar.push_state) return end local _changed = true if data.kind == 'piar_run' and data.index then local _idx = tonumber(data.index) if _idx and settings.piar_templates[_idx] then _xjg7y(_idx) end _changed = false elseif data.kind == 'piar_auto_toggle' and data.index then local _idx = tonumber(data.index) local _t = _idx and settings.piar_templates[_idx] if _t then _t.auto = data.enable and true or false if _t.auto then _t.auto_count = 0 _t.last_time = os.time() _t._next_interval = nil end else _changed = false end elseif data.kind == 'piar_enable_toggle' and data.index then local _idx = tonumber(data.index) local _t = _idx and settings.piar_templates[_idx] if _t then _t.enable = data.enable and true or false else _changed = false end elseif data.kind == 'piar_create' then if not settings.piar_templates then settings.piar_templates = {} end table.insert(settings.piar_templates, { name = '\xcd\xee\xe2\xfb\xe9', 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 \xd2\xe5\xea\xf1\xf2'}, }) elseif data.kind == 'piar_delete' and data.index then local _idx = tonumber(data.index) if _idx and settings.piar_templates[_idx] then table.remove(settings.piar_templates, _idx) else _changed = false end elseif data.kind == 'piar_update' and data.index then local _idx = tonumber(data.index) local _t = _idx and settings.piar_templates[_idx] if _t then if data.name ~= nil and data.name ~= '' then _t.name = _mh_piar.u8_to_cp(data.name) end if data.waiting ~= nil then local _w = tonumber(data.waiting) if _w then _t.waiting = math.max(0.5, math.min(10, _w)) end end if data.auto_interval ~= nil then local _v = tonumber(data.auto_interval) if _v then _t.auto_interval = math.max(30, math.min(3600, _v)) if (_t.auto_interval_max or 0) < _t.auto_interval then _t.auto_interval_max = _t.auto_interval end _t._next_interval = nil end end if data.auto_interval_max ~= nil then local _v = tonumber(data.auto_interval_max) if _v then _t.auto_interval_max = math.max(0, math.min(3600, _v)); _t._next_interval = nil end end if data.auto_limit ~= nil then local _v = tonumber(data.auto_limit) if _v then _t.auto_limit = math.max(0, math.min(100, _v)); _t.auto_count = 0 end end if data.random_line ~= nil then _t.random_line = data.random_line and true or false end else _changed = false end elseif data.kind == 'piar_line_add' and data.index then local _idx = tonumber(data.index) local _t = _idx and settings.piar_templates[_idx] if _t then if not _t.lines then _t.lines = {} end table.insert(_t.lines, '/s \xd2\xe5\xea\xf1\xf2') else _changed = false end elseif data.kind == 'piar_line_update' and data.index and data.line_index and data.text ~= nil then local _idx, _li = tonumber(data.index), tonumber(data.line_index) local _t = _idx and settings.piar_templates[_idx] if _t and _t.lines and _t.lines[_li] then _t.lines[_li] = _mh_piar.u8_to_cp(data.text) else _changed = false end elseif data.kind == 'piar_line_remove' and data.index and data.line_index then local _idx, _li = tonumber(data.index), tonumber(data.line_index) local _t = _idx and settings.piar_templates[_idx] if _t and _t.lines and _t.lines[_li] then table.remove(_t.lines, _li) else _changed = false end elseif data.kind == 'piar_line_add_lavka' and data.index then local _idx = tonumber(data.index) local _t = _idx and settings.piar_templates[_idx] if _t then if not _t.lines then _t.lines = {} end if #_t.lines == 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 .. ' ' end end else _changed = false end elseif data.kind == 'piar_ad_confirm_toggle' then if settings.general then settings.general.auto_ad_confirm = data.enable and true or false else _changed = false end elseif data.kind == 'piar_ad_station' and data.idx ~= nil then local _v = tonumber(data.idx) if settings.general and _v and _v >= 0 and _v <= 2 then settings.general.auto_ad_station_idx = _v else _changed = false end elseif data.kind == 'piar_ad_type' and data.idx ~= nil then local _v = tonumber(data.idx) if settings.general and _v and (_v == 0 or _v == 1) then settings.general.auto_ad_type = _v else _changed = false end elseif data.kind == 'piar_editor_open' then _G._mh_piar_editor_expand = false _G._mh_piar_editor_restore = false _changed = false elseif data.kind == 'piar_editor_close' then _G._mh_piar_editor_expand = false _G._mh_piar_editor_restore = false _changed = false else _changed = false end if _changed then lua_thread.create(function() wait(0) pcall(_wfn7p) end) pcall(_mh_piar.push_state) lua_thread.create(function() wait(250) pcall(_mh_piar.push_state) end) end end if _mh_web_jar then local _mh_piar_onAction_prev = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _mh_piar.bid then if data.type == "WV_STARTED" then _mh_piar.state.crashed = false _mh_piar.state.loaded = false elseif data.type == "WV_LOADED" then _mh_piar.state.loaded = true _mh_piar.apply_zoom() _mh_piar.push_state() elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_piar.state.progress then _mh_piar.state.progress = p end elseif data.type == "WV_DIE" then _mh_piar.state.crashed = true _mh_piar.state.created = false pcall(function() _mh_web_jar.deleteBrowser(_mh_piar.bid) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then pcall(_mh_piar.handle_bridge_payload, _js_obj) end end end return end if _mh_piar_onAction_prev then _mh_piar_onAction_prev(data) end end end function _mh_piar_hide() if _mh_web_jar and _mh_piar.state.created then pcall(function() _mh_web_jar.setClickable(_mh_piar.bid, false) _mh_web_jar.setVisible(_mh_piar.bid, false) _mh_web_jar.deleteBrowser(_mh_piar.bid) end) _mh_piar.state.created = false _mh_piar.state.loaded = false _mh_piar.state.crashed = false _mh_piar.state.progress = 0 _mh_piar.state.visible_applied = false _mh_piar.state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_piar_soft_hide() if _mh_web_jar and _mh_piar.state.created then pcall(function() _mh_web_jar.setClickable(_mh_piar.bid, false) _mh_web_jar.setVisible(_mh_piar.bid, false) end) _mh_piar.state.visible_applied = false end end function _mh_piar_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0\x20\x77\x65\x62\x76\x69\x65\x77\x73\x20\xed\xe5\x20\xed\xe0\xe9\xe4\xe5\xed\xe0\x2e') imgui.TextWrapped(u8'\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xe5\x20\xe1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xf3\x20\x77\x65\x62\x76\x69\x65\x77\x73\x20\x28\xf2\xe0\x20\xe6\xe5\x2c\x20\xf7\xf2\xee\x20\xe8\xf1\xef\xee\xeb\xfc\xe7\xf3\xe5\xf2\xf1\xff\x20\xe2\x20\x57\x65\x62\x4d\x6f\x62\x69\x6c\x65\x29\x2c\x20\xf7\xf2\xee\xe1\xfb\x20\xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0\x20\xf1\xe0\xe9\xf2\xe0\x20\xf0\xe0\xe1\xee\xf2\xe0\xeb\xe0\x2e') return end if not _mh_piar.state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_mh_piar.bid, _mh_piar.build_url()) end) if ok then _mh_piar.state.created = true _mh_piar.state.loaded = false _mh_piar.state.crashed = false _mh_piar.state.progress = 0 _mh_piar.state.visible_applied = false end end if _mh_piar.state.created then if not _mh_piar.state.visible_applied then pcall(function() _mh_web_jar.setClickable(_mh_piar.bid, true) _mh_web_jar.setVisible(_mh_piar.bid, true) end) _mh_piar.state.visible_applied = true end end imgui.Text(fa.TOWER_BROADCAST .. ' ' .. u8'\xcf\xc8\xc0\xd0') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_piar_zoom_out', imgui.ImVec2(30*d, 0)) then _mh_piar.set_zoom(settings.mh_web.piar_zoom - 0.1) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.piar_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_piar_zoom_in', imgui.ImVec2(30*d, 0)) then _mh_piar.set_zoom(settings.mh_web.piar_zoom + 0.1) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_piar_reload') and _mh_web_jar then _mh_piar.state.loaded = false _mh_piar.state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_mh_piar.bid, _mh_piar.build_url()) end) end imgui.Separator() local _wp, _cp2 = imgui.GetWindowPos(), imgui.GetCursorPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _wp.x + _cp2.x, _wp.y + _cp2.y local _pw, _ph = _avail.x, _avail.y if _mh_piar.state.created and _mh_web_jar then local _pos = _mh_piar.state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_mh_piar.bid, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_mh_piar.bid, _pw, _ph) end) end _mh_piar.state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_piar.state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp2.x, _cp2.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0\x20\xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff\x20\xef\xe5\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc\x20\x28\x63\x72\x61\x73\x68\x29\x20\x3a\x28') imgui.SetCursorPos(imgui.ImVec2(_cp2.x, _cp2.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc\x23\x23\x6d\x68\x5f\x70\x69\x61\x72\x5f\x72\x65\x74\x72\x79', imgui.ImVec2(160*d, 0)) then _mh_piar.state.created = false end elseif not _mh_piar.state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp2.x, _cp2.y + _ph/2 - 10)) local _pct = _mh_piar.state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e\x20' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end _mh_buy = _mh_buy or {} _mh_buy.static_ver = 11 _mh_buy.bid = 9007 _mh_buy.state = _mh_buy.state or { created = false, loaded = false, crashed = false, progress = 0, pos = {x = -99999, y = -99999, w = 0, h = 0}, push_seq = 0, } function _mh_buy.build_url() local _z = (settings.mh_web and settings.mh_web.buy_zoom) or 0.9 local _srv = _mh_cloud_current_server_id() return "https://va-ta.com/buy/?v=" .. tostring(_mh_buy.static_ver) .. "&zoom=" .. tostring(_z) .. "&server=" .. tostring(_srv) .. "&theme=" .. _mh_theme.query_param() end if not settings.mh_web.buy_zoom then settings.mh_web.buy_zoom = 0.9 end function _mh_buy.apply_zoom() if not (_mh_web_jar and _mh_buy.state.created) then return end local z = settings.mh_web.buy_zoom pcall(function() _mh_web_jar.executeJS(_mh_buy.bid, "document.documentElement.style.zoom='" .. tostring(z) .. "';") end) end function _mh_buy.set_zoom(z) if z < 0.3 then z = 0.3 end if z > 1.5 then z = 1.5 end settings.mh_web.buy_zoom = z pcall(_wfn7p) _mh_buy.apply_zoom() end function _mh_buy.u8_to_cp(s) if type(s) ~= 'string' or s == '' then return s end local ok, r = pcall(function() return require('encoding').CP1251:encode(require('encoding').UTF8:decode(s)) end) return (ok and r) and r or s end function _mh_buy.cp_to_u8(s) if type(s) ~= 'string' or s == '' then return s end local ok, r = pcall(function() return require('encoding').UTF8:encode(require('encoding').CP1251:decode(s)) end) return (ok and r) and r or s end function _mh_buy.item_prices(nm) local _cur_ver = 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) if not _G._mh_buy_ip_cache or _G._mh_buy_ip_cache_ver ~= _cur_ver then _G._mh_buy_ip_cache = {} _G._mh_buy_ip_cache_ver = _cur_ver end local _hit = _G._mh_buy_ip_cache[nm] if _hit then return _hit end local _mp = _mh_get_mkt_price(nm) local _avg7, _avg30, _today 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 local _today_cp = (_mp and _mp.today and _mp.today > 0) and _mp.today or _today local _lvc = _G._lv_shops_cache or {} local _lve = _lvc[nm:lower()] local _sell_lv = _lve and _lve.sell or nil local _buy_lv = _lve and _lve.buy or nil local _res = {today_cp = _today_cp, avg_cp = _today, sell_lv = _sell_lv, buy_lv = _buy_lv} _G._mh_buy_ip_cache[nm] = _res return _res end function _mh_buy.snapshot_items(items) local out = {} for i, it in ipairs(items or {}) do local pr = _mh_buy.item_prices(it.name or '') local _q1 = it.qty_limit_1 or _mh_is_qty1_item(it.name) out[i] = { name = _mh_buy.cp_to_u8(it.name), qty = it.qty or 1, target_qty = it.target_qty or it.qty or 1, max_price = it.max_price or 0, enabled = (it.enabled ~= false), qty_limit_1 = _q1 and true or false, needs_rebuy = it.needs_rebuy == true, today_cp = pr.today_cp, avg_cp = pr.avg_cp, sell_lv = pr.sell_lv, buy_lv = pr.buy_lv, } end return out end function _mh_buy.snapshot_items_light(items) local out = {} for i, it in ipairs(items or {}) do local _q1 = it.qty_limit_1 or _mh_is_qty1_item(it.name) out[i] = { name = _mh_buy.cp_to_u8(it.name), qty = it.qty or 1, target_qty = it.target_qty or it.qty or 1, max_price = it.max_price or 0, enabled = (it.enabled ~= false), qty_limit_1 = _q1 and true or false, needs_rebuy = it.needs_rebuy == true, } end return out end function _mh_buy.build_state() local _presets = {} for pi, pr in ipairs(settings.buy_presets or {}) do _presets[pi] = { name = _mh_buy.cp_to_u8(pr.name) or ('Preset ' .. tostring(pi)), items = (pi == fh_ab_preset_idx) and _mh_buy.snapshot_items(pr.items or {}) or _mh_buy.snapshot_items_light(pr.items or {}), } end local _bal = 0 pcall(function() _bal = getPlayerMoney(PLAYER_PED) end) local _servers = {} for i, s in ipairs(ARZ_SERVERS) do _servers[i] = {id = s.id, name = _mh_buy.cp_to_u8(s.name)} end return { kind = 'state', presets = _presets, active_preset = fh_ab_preset_idx or 1, running = fh_lv_autobuy_running and true or false, status = _mh_buy.cp_to_u8(fh_lv_autobuy_status or ''), balance = _bal, has_rebuy = (function() if _G._mh_ab_has_real_sale == true then return true end for _, _rb in ipairs(fh_lv_autobuy_preset or {}) do if _rb.needs_rebuy == true then return true end end return false end)(), cloud_servers = _servers, } end function _mh_buy.push_state() if not (_mh_web_jar and _mh_buy.state.created) then return end _mh_buy.state.push_seq = (_mh_buy.state.push_seq or 0) + 1 local _my_seq = _mh_buy.state.push_seq lua_thread.create(function() wait(0) local ok_j, j = pcall(encodeJson, _mh_buy.build_state()) if not ok_j or type(j) ~= 'string' then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end if _mh_buy.state.push_seq ~= _my_seq then return end pcall(function() _mh_web_jar.executeJS(_mh_buy.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end) end function _mh_buy.push_import_list() if not (_mh_web_jar and _mh_buy.state.created and _mh_buy.state.loaded) then return end local _list = _mh_list_presets('buy') local _out = {} for i, e in ipairs(_list) do _out[i] = {fname = e.fname, name = _mh_buy.cp_to_u8(e.name), created = e.created, count = e.count} end local ok_j, j = pcall(encodeJson, {kind = 'import_list', list = _out}) if not ok_j then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_mh_buy.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end function _mh_buy.push_listing_status() if not (_mh_web_jar and _mh_buy.state.created) then return end local _pid = 'buy_' .. tostring(fh_ab_preset_idx or 1) _mh_listings.post('/api/mh/listings/mine', {}, function(res, err) if not res or not res.ok then return end local _mine = nil for _, l in ipairs(res.listings or {}) do if l.preset_id == _pid then _mine = l; break end end local ok_j, j = pcall(encodeJson, { kind = 'listing_status', published = _mine ~= nil, listing_id = _mine and _mine.id or nil, is_premium = res.is_premium and true or false, bump_cooldown = res.bump_cooldown, last_bump_at = _mine and _mine.last_bump_at or 0, active_count = #(res.listings or {}), active_limit = res.limit, }) if not ok_j then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_mh_buy.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end) end function _mh_buy.push_search_results(q_u8, req_id) if not (_mh_web_jar and _mh_buy.state.created and _mh_buy.state.loaded) then return end local _q = fh_lower(_mh_buy.u8_to_cp(q_u8 or '')):match('^%s*(.-)%s*$') or '' local _items = {} if #_q >= 2 then local _all, _all_lower = fh_get_allitems_list() local _already = {} for _, p in ipairs(fh_lv_autobuy_preset) do _already[fh_lower(p.name)] = true end local _shown = 0 for i, nm in ipairs(_all) do if _shown >= 25 then break end local nm_lower = _all_lower[i] if nm_lower:find(_q, 1, true) then local e = fh_mkt_prices[nm] local ph = e and (e.s_avg or e.b_avg) or 0 _items[#_items + 1] = { name = _mh_buy.cp_to_u8(nm), price = ph, already = _already[nm_lower] == true, } _shown = _shown + 1 end end end local resp = {kind = 'search_results', req_id = tonumber(req_id) or 0, items = _items} local ok_j, j = pcall(encodeJson, resp) if not ok_j then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_mh_buy.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end function _mh_buy.cloud_fetch_list(filters, req_id) local page = tonumber(filters and filters.page) or 1 if page < 1 then page = 1 end local _sid = tonumber(filters and filters.server_id) or -1 local _budget_max = tonumber(filters and filters.budget_max) or 0 local _sort = (filters and filters.sort) or 'downloads' local _url = _vbr7n .. '/cloud-presets/list' .. '?server_id=' .. tostring(_sid) .. '&budget_max=' .. tostring(_budget_max) .. '&sort=' .. tostring(_sort) .. '&page=' .. tostring(page) local _srch_u8 = (filters and filters.search) or '' if _srch_u8 ~= '' then _url = _url .. '&search=' .. _mh_url_encode(_srch_u8) end if filters and filters.mine_only then local _mnick = _mh_get_live_nick() if not _mh_is_fake_nick(_mnick) then _url = _url .. '&mine_nick=' .. _mh_url_encode(_to_utf8(_mnick)) end end _fwm2c(_url, '', _mh_get_live_nick(), function(code, body, err) local resp = {kind = 'cloud_list', req_id = tonumber(req_id) or 0, page = page} if err or code ~= 200 or not body then resp.error = tostring(err or code or 'connection failed') else local ok2, parsed = pcall(decodeJson, body) if not ok2 or type(parsed) ~= 'table' or not parsed.ok then resp.error = 'bad_response' else _G._mh_buy_cloud_raw = parsed.presets or {} local _list = {} for i, cp in ipairs(parsed.presets or {}) do _list[i] = { id = cp.id, title = _mh_buy.cp_to_u8(cp.title or ''), nick = _mh_buy.cp_to_u8(cp.nick or ''), server_id = cp.server_id, budget = cp.budget or 0, item_count = cp.item_count or #(cp.items or {}), downloads = cp.downloads or 0, views = cp.views or 0, likes = cp.likes or 0, dislikes = cp.dislikes or 0, my_vote = _G._mh_cloud_my_votes[cp.id], is_owner = (function() local _mn = _mh_get_live_nick() if _mh_is_fake_nick(_mn) then return false end return tostring(cp.nick or ''):lower() == tostring(_mn):lower() end)(), } end resp.presets = _list resp.total = parsed.total or 0 end end local ok_j, j = pcall(encodeJson, resp) if not ok_j then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() if _mh_web_jar and _mh_buy.state.created and _mh_buy.state.loaded then _mh_web_jar.executeJS(_mh_buy.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end end) end) end function _mh_buy.cloud_push_detail(preset_id, req_id) local _pid = tonumber(preset_id) local cp = nil for _, e in ipairs(_G._mh_buy_cloud_raw or {}) do if e.id == _pid then cp = e; break end end if not cp then local resp = {kind = 'cloud_detail', req_id = tonumber(req_id) or 0, error = 'not_found'} local ok_j, j = pcall(encodeJson, resp) if ok_j then local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if ok_b64 and b64 then pcall(function() _mh_web_jar.executeJS(_mh_buy.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end end return end cp.views = (cp.views or 0) + 1 if cp.id then local ok_j0, j0 = pcall(encodeJson, {preset_id = cp.id}) if ok_j0 then _jmx9s(_vbr7n .. '/cloud-presets/view', j0, function() end) end end local _items = {} for i, it in ipairs(cp.items or {}) do _items[i] = {name = _mh_buy.cp_to_u8(it.name or ''), price = it.price or 0, qty = it.qty or 1} end local resp = { kind = 'cloud_detail', req_id = tonumber(req_id) or 0, id = cp.id, title = _mh_buy.cp_to_u8(cp.title or ''), nick = _mh_buy.cp_to_u8(cp.nick or ''), server_id = cp.server_id, downloads = cp.downloads or 0, views = cp.views or 0, likes = cp.likes or 0, dislikes = cp.dislikes or 0, items = _items, } local ok_j, j = pcall(encodeJson, resp) if not ok_j then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_mh_buy.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end function _mh_buy.handle_bridge_payload(data) if type(data) ~= 'table' or not data.kind then return end if data.kind == 'ready' then pcall(_mh_buy.push_state) pcall(_mh_buy.push_listing_status) return end local _changed = true if data.kind == 'buy_add' and data.name and data.name ~= '' then local _nm = _mh_buy.u8_to_cp(data.name) local _already = false for _, p in ipairs(fh_lv_autobuy_preset) do if fh_lower(p.name) == fh_lower(_nm) then _already = true; break end end if _already then _changed = false else local _is_q1 = _mh_is_qty1_item(_nm) local _pr = _mh_buy.item_prices(_nm) local _best = _pr.buy_lv or _pr.today_cp or _pr.avg_cp or 0 table.insert(fh_lv_autobuy_preset, { name = _nm, max_price = _best, qty = 1, target_qty = 1, qty_limit_1 = _is_q1 or nil, }) end elseif data.kind == 'buy_remove' and data.index then local _idx = tonumber(data.index) if _idx and fh_lv_autobuy_preset[_idx] then table.remove(fh_lv_autobuy_preset, _idx) else _changed = false end elseif data.kind == 'buy_update' and data.index then local _idx = tonumber(data.index) local _it = _idx and fh_lv_autobuy_preset[_idx] if _it then if data.max_price ~= nil then _it.max_price = tonumber(data.max_price) or _it.max_price end if data.qty ~= nil then local _q1 = _it.qty_limit_1 or _mh_is_qty1_item(_it.name) _it.qty = _q1 and 1 or (tonumber(data.qty) or _it.qty) if not _it.target_qty or _it.target_qty == 0 then _it.target_qty = _it.qty end end if data.enabled ~= nil then _it.enabled = data.enabled and true or false if _it.enabled and (_it.qty or 0) <= 0 then _it.qty = 1 if not _it.target_qty or _it.target_qty == 0 then _it.target_qty = 1 end end end if (_it.qty or 0) <= 0 then _it.enabled = false end else _changed = false end elseif data.kind == 'buy_reorder' and data.index and data.target then local _idx, _tgt = tonumber(data.index), tonumber(data.target) local _n = #fh_lv_autobuy_preset if _idx and _tgt and fh_lv_autobuy_preset[_idx] then if _tgt < 1 then _tgt = 1 end if _tgt > _n then _tgt = _n end if _tgt ~= _idx then local _moved = table.remove(fh_lv_autobuy_preset, _idx) table.insert(fh_lv_autobuy_preset, _tgt, _moved) else _changed = false end else _changed = false end elseif data.kind == 'buy_sort' and data.mode then if data.mode == 'price_asc' then table.sort(fh_lv_autobuy_preset, function(a,b) return (a.max_price or 0) < (b.max_price or 0) end) elseif data.mode == 'price_desc' then table.sort(fh_lv_autobuy_preset, function(a,b) return (a.max_price or 0) > (b.max_price or 0) end) elseif data.mode == 'sum_desc' then table.sort(fh_lv_autobuy_preset, function(a,b) return ((a.max_price or 0)*(a.qty or 1)) > ((b.max_price or 0)*(b.qty or 1)) end) else table.sort(fh_lv_autobuy_preset, function(a,b) return (a.name or '') < (b.name or '') end) end elseif data.kind == 'buy_mark_all' then local _marked = 0 for _, _abp in ipairs(fh_lv_autobuy_preset) do if (_abp.qty or 0) > 0 and _abp.enabled ~= true then _abp.enabled = true _marked = _marked + 1 end end mh_notify('[MH] {00cc00}' .. '\xce\xf2\xec\xe5\xf2\xe8\xf2\xfc\x20\xe2\xf1\xe5\x3a\x20\xe2\xea\xeb\xfe\xf7\xe5\xed\xee\x20' .. _marked .. '\x20\xf2\xee\xe2\xe0\xf0\xee\xe2\x2e', 0xFFFFFF) elseif data.kind == 'buy_preset_switch' and data.index then local _pi = tonumber(data.index) if _pi and settings.buy_presets and settings.buy_presets[_pi] then fh_ab_preset_idx = _pi settings.active_buy_preset = _pi fh_lv_autobuy_preset = settings.buy_presets[_pi].items or {} settings.autobuy_preset = fh_lv_autobuy_preset settings._ab_undo_snap = nil else _changed = false end elseif data.kind == 'buy_preset_create' then if not settings.buy_presets then settings.buy_presets = {} end local _nm = (data.name and data.name ~= '') and _mh_buy.u8_to_cp(data.name) or ('\xcf\xf0\xe5\xf1\xe5\xf2\x20' .. tostring(#settings.buy_presets + 1)) table.insert(settings.buy_presets, {name = _nm, items = {}}) fh_ab_preset_idx = #settings.buy_presets settings.active_buy_preset = fh_ab_preset_idx fh_lv_autobuy_preset = settings.buy_presets[fh_ab_preset_idx].items settings.autobuy_preset = fh_lv_autobuy_preset elseif data.kind == 'buy_preset_rename' and data.name and data.name ~= '' then local _p = settings.buy_presets and settings.buy_presets[fh_ab_preset_idx] if _p then _p.name = _mh_buy.u8_to_cp(data.name) else _changed = false end elseif data.kind == 'buy_preset_delete' then if settings.buy_presets and #settings.buy_presets > 1 then table.remove(settings.buy_presets, fh_ab_preset_idx) fh_ab_preset_idx = math.max(1, fh_ab_preset_idx - 1) settings.active_buy_preset = fh_ab_preset_idx fh_lv_autobuy_preset = settings.buy_presets[fh_ab_preset_idx].items or {} settings.autobuy_preset = fh_lv_autobuy_preset else mh_notify('[MH] {ff4444}' .. '\xcd\xe5\xeb\xfc\xe7\xff\x20\xf3\xe4\xe0\xeb\xe8\xf2\xfc\x20\xe5\xe4\xe8\xed\xf1\xf2\xe2\xe5\xed\xed\xfb\xe9\x20\xef\xf0\xe5\xf1\xe5\xf2\x2e', 0xFFFFFF) _changed = false end elseif data.kind == 'buy_search' then pcall(_mh_buy.push_search_results, data.query, data.req_id) _changed = false elseif data.kind == 'cloud_list' then pcall(_mh_buy.cloud_fetch_list, data.filters, data.req_id) _changed = false elseif data.kind == 'cloud_detail' then pcall(_mh_buy.cloud_push_detail, data.id, data.req_id) _changed = false elseif data.kind == 'cloud_vote' and data.id then local _cp = nil for _, e in ipairs(_G._mh_buy_cloud_raw or {}) do if e.id == tonumber(data.id) then _cp = e; break end end if _cp then pcall(_mh_cloud_vote_preset, _cp, tonumber(data.vote) == -1 and -1 or 1, data.req_id) end _changed = false elseif data.kind == 'cloud_add' and data.id then local _cp = nil for _, e in ipairs(_G._mh_buy_cloud_raw or {}) do if e.id == tonumber(data.id) then _cp = e; break end end if _cp then pcall(_mh_cloud_add_preset_to_mine, _cp) pcall(_mh_buy.push_state) end _changed = false elseif data.kind == 'cloud_delete' and data.id then local _cp = nil for _, e in ipairs(_G._mh_buy_cloud_raw or {}) do if e.id == tonumber(data.id) then _cp = e; break end end if _cp then pcall(_mh_cloud_delete_preset, _cp) end _changed = false elseif data.kind == 'buy_preset_import_list' then _mh_buy.push_import_list() _changed = false elseif data.kind == 'buy_preset_import_pick' and data.fname and data.fname ~= '' then local _dir = _mh_preset_dir() local _path = _dir .. '/' .. data.fname local _ok3, _nm3, _items3 = _mh_import_preset_file(_path) if _ok3 then if not settings.buy_presets then settings.buy_presets = {} end table.insert(settings.buy_presets, {name = _nm3, items = _items3 or {}}) fh_ab_preset_idx = #settings.buy_presets settings.active_buy_preset = fh_ab_preset_idx fh_lv_autobuy_preset = settings.buy_presets[fh_ab_preset_idx].items settings.autobuy_preset = fh_lv_autobuy_preset else mh_notify('[MH] {ff6666}' .. '\xcd\xe5\x20\xf3\xe4\xe0\xeb\xee\xf1\xfc\x20\xe8\xec\xef\xee\xf0\xf2\xe8\xf0\xee\xe2\xe0\xf2\xfc\x20\xef\xf0\xe5\xf1\xe5\xf2\x2e', 0xFFFFFF) _changed = false end elseif data.kind == 'buy_export_file' then local _pname = (settings.buy_presets and settings.buy_presets[fh_ab_preset_idx] and settings.buy_presets[fh_ab_preset_idx].name) or ('\xd1\xea\xf3\xef\xea\xe0\x20' .. tostring(fh_ab_preset_idx)) local _ok5, _res5 = _mh_export_preset('buy', _pname, fh_lv_autobuy_preset) if _ok5 then mh_notify('[MH] {aaffaa}' .. '\xcf\xf0\xe5\xf1\xe5\xf2\x20\xf1\xee\xf5\xf0\xe0\xed\xb8\xed\x20\xe2\x20\xf4\xe0\xe9\xeb\x3a\x20' .. tostring(_res5), 0xFFFFFF) else mh_notify('[MH] {ff6666}' .. '\xcd\xe5\x20\xf3\xe4\xe0\xeb\xee\xf1\xfc\x20\xf1\xee\xf5\xf0\xe0\xed\xe8\xf2\xfc\x20\xef\xf0\xe5\xf1\xe5\xf2\x20\xe2\x20\xf4\xe0\xe9\xeb\x20\x28' .. tostring(_res5) .. ').', 0xFFFFFF) end _changed = false elseif data.kind == 'buy_export_cloud' then pcall(_mh_cloud_push_buy_preset) _changed = false elseif data.kind == 'buy_snapshot_save' then settings._ab_undo_snap = _mh_deep_copy_list(fh_lv_autobuy_preset) mh_notify('[MH] {aaddff}' .. '\xcf\xf0\xe5\xf1\xe5\xf2\x20\xf1\xee\xf5\xf0\xe0\xed\xb8\xed\x20\x28' .. tostring(#fh_lv_autobuy_preset) .. '\x20\xf2\xee\xe2\x2e\x29\x20\x2d\x2d\x20\xe2\x20\xf4\xe0\xe9\xeb\x20\xed\xe0\xf1\xf2\xf0\xee\xe5\xea\x20\xe8\x20\xe2\x20\xef\xe0\xec\xff\xf2\xfc\x20\xe4\xeb\xff\x20\xe2\xee\xf1\xf1\xf2\xe0\xed\xee\xe2\xeb\xe5\xed\xe8\xff\x2e', 0xFFFFFF) elseif data.kind == 'buy_snapshot_restore' then if settings._ab_undo_snap then fh_lv_autobuy_preset = _mh_deep_copy_list(settings._ab_undo_snap) 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 mh_notify('[MH] {aaffaa}' .. '\xcf\xf0\xe5\xf1\xe5\xf2\x20\xe2\xee\xf1\xf1\xf2\xe0\xed\xee\xe2\xeb\xe5\xed\x20\xe8\xe7\x20\xf1\xed\xe8\xec\xea\xe0\x20\x28' .. tostring(#fh_lv_autobuy_preset) .. '\x20\xf2\xee\xe2\x2e\x29\x2e', 0xFFFFFF) else mh_notify('[MH] {ffaa55}' .. '\xd1\xed\xe8\xec\xee\xea\x20\xe5\xf9\xb8\x20\xed\xe5\x20\xf1\xee\xf5\xf0\xe0\xed\xb8\xed\x2e', 0xFFFFFF) _changed = false end elseif data.kind == 'buy_clean_preset' then local _removed = 0 for i = #fh_lv_autobuy_preset, 1, -1 do table.remove(fh_lv_autobuy_preset, i) _removed = _removed + 1 end mh_notify('[MH] {ffaa55}' .. '\xce\xf7\xe8\xf9\xe5\xed\x20\xef\xf0\xe5\xf1\xe5\xf2\x20\x28\xf3\xe4\xe0\xeb\xe5\xed\xee\x20' .. _removed .. '\x29\x2e', 0xFFFFFF) elseif data.kind == 'buy_distribute_budget' and data.budget then local _budget = tonumber(data.budget) or 0 local _n = #fh_lv_autobuy_preset if _budget <= 0 or _n == 0 then mh_notify('[MH] {ff4444}' .. '\xc2\xe2\xe5\xe4\xe8\xf2\xe5\x20\xf1\xf3\xec\xec\xf3\x20\xe1\xee\xeb\xfc\xf8\xe5\x20\x30', 0xFFFFFF) _changed = false else local _price = {} for _abi = 1, _n do _price[_abi] = math.max(1, fh_lv_autobuy_preset[_abi].max_price or 1) end local _MAX_ITEM_SHARE = 0.05 local _max_qty = {} for _abi = 1, _n do if fh_lv_autobuy_preset[_abi].enabled == false then _max_qty[_abi] = 0 elseif fh_lv_autobuy_preset[_abi].qty_limit_1 or _mh_is_qty1_item(fh_lv_autobuy_preset[_abi].name) then _max_qty[_abi] = 1 else local _cap_value = math.max(_price[_abi], _budget * _MAX_ITEM_SHARE) _max_qty[_abi] = math.max(1, math.floor(_cap_value / _price[_abi])) end end local _assigned = {} for _abi = 1, _n do _assigned[_abi] = 0 end local _remaining = _budget local _expensive_idx = {} for _abi = 1, _n do table.insert(_expensive_idx, _abi) end table.sort(_expensive_idx, function(a, b) return _price[a] > _price[b] end) local _cheap_idx = {} for _abi = 1, _n do table.insert(_cheap_idx, _abi) end table.sort(_cheap_idx, function(a, b) return _price[a] < _price[b] end) do local _phase1_limit = _budget * 0.4 local _spent1 = 0 local _progressed = true while _progressed do _progressed = false for _, _abi in ipairs(_expensive_idx) do if _assigned[_abi] < _max_qty[_abi] and (_spent1 + _price[_abi]) <= _phase1_limit and _remaining >= _price[_abi] then _assigned[_abi] = _assigned[_abi] + 1 _remaining = _remaining - _price[_abi] _spent1 = _spent1 + _price[_abi] _progressed = true end end end end do local _phase2_limit = math.min(_remaining, _budget * 0.4) local _share2 = _phase2_limit / _n for _abi = 1, _n do local _qty2 = math.floor(_share2 / _price[_abi]) _qty2 = math.min(_qty2, _max_qty[_abi] - _assigned[_abi]) if _qty2 > 0 then _assigned[_abi] = _assigned[_abi] + _qty2 _remaining = _remaining - _qty2 * _price[_abi] end end end do local _active = {} for _, _abi in ipairs(_cheap_idx) do if _assigned[_abi] < _max_qty[_abi] then table.insert(_active, _abi) end end while #_active > 0 do if _remaining <= 0 then break end local _min_p = nil for _, _abi in ipairs(_active) do if not _min_p or _price[_abi] < _min_p then _min_p = _price[_abi] end end if _remaining < _min_p then break end local _got_one = false for _, _abi in ipairs(_active) do if _assigned[_abi] < _max_qty[_abi] and _remaining >= _price[_abi] then _assigned[_abi] = _assigned[_abi] + 1 _remaining = _remaining - _price[_abi] _got_one = true end end local _next_active = {} for _, _abi in ipairs(_active) do if _assigned[_abi] < _max_qty[_abi] and _price[_abi] <= _remaining then table.insert(_next_active, _abi) end end if not _got_one then break end _active = _next_active end end local _total_qty, _total_spent = 0, 0 for _abi, _abp in ipairs(fh_lv_autobuy_preset) do local _wasEnabled = _abp.enabled ~= false local _new_qty = _assigned[_abi] or 0 _abp.qty = _new_qty _abp.target_qty = _new_qty if _wasEnabled then _abp.enabled = _new_qty > 0 end _total_qty = _total_qty + _new_qty _total_spent = _total_spent + _new_qty * _price[_abi] end mh_notify('[MH] {aaffaa}' .. '\xc1\xfe\xe4\xe6\xe5\xf2\x20\x24' .. _kcr3y(_budget) .. '\x20\xf0\xe0\xf1\xef\xf0\xe5\xe4\xe5\xeb\xb8\xed\x20\xef\xee\x20' .. _n .. '\x20\xf2\xee\xe2\xe0\xf0\xe0\xec\x20\x28\xe8\xf1\xef\xee\xeb\xfc\xe7\xee\xe2\xe0\xed\xee\x20\x24' .. _kcr3y(_total_spent) .. '\x2c\x20\xe8\xf2\xee\xe3\xee\x20' .. tostring(_total_qty) .. '\x20\xf8\xf2\x29', 0xFFFFFF) end elseif data.kind == 'buy_rebuy' then if #fh_lv_autobuy_preset == 0 then mh_notify('[MH] {ffaa44}' .. '\xcf\xf0\xe5\xf1\xe5\xf2\x20\xf1\xea\xf3\xef\xea\xe8\x20\xef\xf3\xf1\xf2', 0xFFFFFF) elseif type(_xpk6g) == 'function' then _xpk6g(true) end _changed = false elseif data.kind == 'buy_run' then if #fh_lv_autobuy_preset > 0 and type(_xpk6g) == 'function' then _xpk6g(false) else mh_notify('[MH] {ff4444}' .. '\xcf\xf0\xe5\xf1\xe5\xf2\x20\xf1\xea\xf3\xef\xea\xe8\x20\xef\xf3\xf1\xf2\x2e', 0xFFFFFF) end _changed = false elseif data.kind == 'buy_stop' then fh_lv_autobuy_running = false if _mh_buy and _mh_buy.push_state then pcall(_mh_buy.push_state) end _changed = false elseif data.kind == 'buy_publish_listing' then local _contact = data.contact or '' local _description = data.description or '' local _pid = 'buy_' .. tostring(fh_ab_preset_idx or 1) local _buy_out = {} for _, it in ipairs(fh_lv_autobuy_preset or {}) do if it.enabled ~= false then table.insert(_buy_out, { name = _mh_buy.cp_to_u8(it.name), price = it.max_price or 0, qty = it.target_qty or it.qty or 0, }) end end local _srv = _mh_cloud_current_server_id() _mh_listings.post('/api/mh/listings/publish', { preset_id = _pid, deal_type = 'buy', server_id = _srv, sell_items = {}, buy_items = _buy_out, contact = _contact, description = _description, }, function(res, err) if res and res.ok then mh_notify('[MH] {00cc00}' .. (res.action == 'updated' and '\xce\xe1\xfa\xff\xe2\xeb\xe5\xed\xe8\xe5\x20\xee\xe1\xed\xee\xe2\xeb\xe5\xed\xee\x2e' or '\xce\xe1\xfa\xff\xe2\xeb\xe5\xed\xe8\xe5\x20\xf0\xe0\xe7\xec\xe5\xf9\xe5\xed\xee\x2e'), 0xFFFFFF) else mh_notify('[MH] {ff6666}' .. _mh_listings.error_message(err), 0xFFFFFF) end if _mh_buy and _mh_buy.push_listing_status then _mh_buy.push_listing_status() end end) _changed = false elseif data.kind == 'buy_listing_status_req' then if _mh_buy.push_listing_status then _mh_buy.push_listing_status() end _changed = false elseif data.kind == 'set_tag' then pcall(_mh_shops_handle_bridge_payload, data) _changed = false else _changed = false end if _changed then local _ap = settings.buy_presets and settings.buy_presets[fh_ab_preset_idx] if _ap then _ap.items = fh_lv_autobuy_preset end settings.autobuy_preset = fh_lv_autobuy_preset lua_thread.create(function() wait(0) pcall(_wfn7p) end) pcall(_mh_buy.push_state) if data.kind == 'buy_preset_switch' or data.kind == 'buy_preset_create' or data.kind == 'buy_preset_delete' or data.kind == 'buy_preset_import_pick' then pcall(_mh_buy.push_listing_status) end end end if _mh_web_jar then local _mh_buy_onAction_prev = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _mh_buy.bid then if data.type == "WV_STARTED" then _mh_buy.state.crashed = false _mh_buy.state.loaded = false elseif data.type == "WV_LOADED" then _mh_buy.state.loaded = true _mh_buy.apply_zoom() _mh_buy.push_state() elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_buy.state.progress then _mh_buy.state.progress = p end elseif data.type == "WV_DIE" then _mh_buy.state.crashed = true _mh_buy.state.created = false pcall(function() _mh_web_jar.deleteBrowser(_mh_buy.bid) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then pcall(_mh_buy.handle_bridge_payload, _js_obj) end end end return end if _mh_buy_onAction_prev then _mh_buy_onAction_prev(data) end end end function _mh_buy_hide() if _mh_web_jar and _mh_buy.state.created then pcall(function() _mh_web_jar.setClickable(_mh_buy.bid, false) _mh_web_jar.setVisible(_mh_buy.bid, false) _mh_web_jar.deleteBrowser(_mh_buy.bid) end) _mh_buy.state.created = false _mh_buy.state.loaded = false _mh_buy.state.crashed = false _mh_buy.state.progress = 0 _mh_buy.state.visible_applied = false _mh_buy.state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_buy_soft_hide() if _mh_web_jar and _mh_buy.state.created then pcall(function() _mh_web_jar.setClickable(_mh_buy.bid, false) _mh_web_jar.setVisible(_mh_buy.bid, false) end) _mh_buy.state.visible_applied = false end end function _mh_buy_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0\x20\x77\x65\x62\x76\x69\x65\x77\x73\x20\xed\xe5\x20\xed\xe0\xe9\xe4\xe5\xed\xe0\x2e') imgui.TextWrapped(u8'\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xe5\x20\xe1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xf3\x20\x77\x65\x62\x76\x69\x65\x77\x73\x20\x28\xf2\xe0\x20\xe6\xe5\x2c\x20\xf7\xf2\xee\x20\xe8\xf1\xef\xee\xeb\xfc\xe7\xf3\xe5\xf2\xf1\xff\x20\xe2\x20\x57\x65\x62\x4d\x6f\x62\x69\x6c\x65\x29\x2c\x20\xf7\xf2\xee\xe1\xfb\x20\xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0\x20\xf1\xe0\xe9\xf2\xe0\x20\xf0\xe0\xe1\xee\xf2\xe0\xeb\xe0\x2e') return end if not _mh_buy.state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_mh_buy.bid, _mh_buy.build_url()) end) if ok then _mh_buy.state.created = true _mh_buy.state.loaded = false _mh_buy.state.crashed = false _mh_buy.state.progress = 0 _mh_buy.state.visible_applied = false end end if _mh_buy.state.created then if not _mh_buy.state.visible_applied then pcall(function() _mh_web_jar.setClickable(_mh_buy.bid, true) _mh_web_jar.setVisible(_mh_buy.bid, true) end) _mh_buy.state.visible_applied = true end end imgui.Text(fa.CART_SHOPPING .. ' ' .. u8'\xd1\xca\xd3\xcf\xca\xc0') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_buy_zoom_out', imgui.ImVec2(30*d, 0)) then _mh_buy.set_zoom(settings.mh_web.buy_zoom - 0.1) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.buy_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_buy_zoom_in', imgui.ImVec2(30*d, 0)) then _mh_buy.set_zoom(settings.mh_web.buy_zoom + 0.1) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_buy_reload') and _mh_web_jar then _mh_buy.state.loaded = false _mh_buy.state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_mh_buy.bid, _mh_buy.build_url()) end) end imgui.Separator() local _wp, _cp2 = imgui.GetWindowPos(), imgui.GetCursorPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _wp.x + _cp2.x, _wp.y + _cp2.y local _pw, _ph = _avail.x, _avail.y if _mh_buy.state.created and _mh_web_jar then local _pos = _mh_buy.state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_mh_buy.bid, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_mh_buy.bid, _pw, _ph) end) end _mh_buy.state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_buy.state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp2.x, _cp2.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0\x20\xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff\x20\xef\xe5\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc\x20\x28\x63\x72\x61\x73\x68\x29\x20\x3a\x28') imgui.SetCursorPos(imgui.ImVec2(_cp2.x, _cp2.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc\x23\x23\x6d\x68\x5f\x62\x75\x79\x5f\x72\x65\x74\x72\x79', imgui.ImVec2(160*d, 0)) then _mh_buy.state.created = false end elseif not _mh_buy.state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp2.x, _cp2.y + _ph/2 - 10)) local _pct = _mh_buy.state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e\x20' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end _mh_ads = _mh_ads or {} _mh_ads.bid = 9016 _mh_ads.state = _mh_ads.state or { created = false, loaded = false, crashed = false, progress = 0, pos = {x = -99999, y = -99999, w = 0, h = 0}, } if not settings.mh_web.ads_zoom then settings.mh_web.ads_zoom = 0.9 end _mh_ads.static_ver = 10 function _mh_ads.build_url() local _z = settings.mh_web.ads_zoom or 0.9 local _srv = _mh_cloud_current_server_id() local _nck = _mh_listings.my_nick() return "https://va-ta.com/ads/?v=" .. tostring(_mh_ads.static_ver) .. "&zoom=" .. tostring(_z) .. "&server=" .. tostring(_srv) .. "&theme=" .. _mh_theme.query_param() .. "&nick=" .. _mh_url_encode(_nck) end function _mh_ads.apply_zoom() if not (_mh_web_jar and _mh_ads.state.created) then return end local z = settings.mh_web.ads_zoom pcall(function() _mh_web_jar.executeJS(_mh_ads.bid, "document.documentElement.style.zoom='" .. tostring(z) .. "';") end) end function _mh_ads.set_zoom(z) if z < 0.3 then z = 0.3 end if z > 1.5 then z = 1.5 end settings.mh_web.ads_zoom = z pcall(_wfn7p) _mh_ads.apply_zoom() end function _mh_ads.handle_bridge_payload(data) if type(data) ~= 'table' or not data.kind then return end if data.kind == 'call' and data.owner and tostring(data.owner) ~= '' then local _owner_raw = tostring(data.owner) local _owner_cp = _mh_bridge_u8_to_cp(_owner_raw) or _owner_raw local _fid = nil local _matched_nick = nil for _pid = 0, 999 do local _ok, _pn = pcall(sampGetPlayerNickname, _pid) if _ok and _pn and _pn ~= '' then local _pn_s = tostring(_pn) if _pn_s:lower() == _owner_raw:lower() or _pn_s:lower() == _owner_cp:lower() or tostring(_mh_bridge_u8_to_cp(_pn_s) or _pn_s):lower() == _owner_raw:lower() then _fid = _pid _matched_nick = _pn_s break end end end if _fid then _G._mh_call_pending_nick = (_matched_nick or _owner_raw):lower() sampSendChat('/number ' .. _fid) mh_notify('[MH] {aaffaa}Звонок: ' .. (_matched_nick or _owner_raw), 0xFFFFFF) else mh_notify('[MH] {ffaa00}' .. _owner_raw .. ' не в сети', 0xFFFFFF) end return end if data.kind == 'ads_my_listings_req' then _mh_listings.post('/api/mh/listings/mine', {}, function(res, err) local ok_j, j = pcall(encodeJson, { kind = 'my_listings', listings = (res and res.listings) or {}, is_premium = (res and res.is_premium) and true or false, bump_cooldown = res and res.bump_cooldown, limit = res and res.limit, error = (not res or not res.ok) and tostring(err or 'unknown') or nil, }) if ok_j then local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if ok_b64 and b64 then pcall(function() _mh_web_jar.executeJS(_mh_ads.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end end end) return end if data.kind == 'ads_bump' and data.id then _mh_listings.post('/api/mh/listings/bump', {id = tonumber(data.id)}, function(res, err) if res and res.ok then mh_notify('[MH] {00cc00}' .. '\xce\xe1\xfa\xff\xe2\xeb\xe5\xed\xe8\xe5\x20\xef\xee\xe4\xed\xff\xf2\xee\x2e', 0xFFFFFF) else mh_notify('[MH] {ff6666}' .. _mh_listings.error_message(err), 0xFFFFFF) end local ok_j, j = pcall(encodeJson, {kind = 'bump_result', ok = (res and res.ok) and true or false, error = err}) if ok_j then local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if ok_b64 and b64 then pcall(function() _mh_web_jar.executeJS(_mh_ads.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end end end) elseif data.kind == 'ads_edit' and data.id then local _description = data.description or '' local _contact = data.contact or '' local _prices = {} for _, p in ipairs(data.prices or {}) do table.insert(_prices, tonumber(p) or 0) end _mh_listings.post('/api/mh/listings/edit', { id = tonumber(data.id), description = _description, contact = _contact, prices = _prices, }, function(res, err) if res and res.ok then mh_notify('[MH] {00cc00}' .. '\xce\xe1\xfa\xff\xe2\xeb\xe5\xed\xe8\xe5\x20\xee\xe1\xed\xee\xe2\xeb\xe5\xed\xee\x2e', 0xFFFFFF) else mh_notify('[MH] {ff6666}' .. _mh_listings.error_message(err), 0xFFFFFF) end local ok_j, j = pcall(encodeJson, {kind = 'edit_result', ok = (res and res.ok) and true or false, error = err}) if ok_j then local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if ok_b64 and b64 then pcall(function() _mh_web_jar.executeJS(_mh_ads.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end end end) elseif data.kind == 'ads_delete' and data.id then local _nck = _mh_listings.my_nick() local _sess = _G._mh_session_tok or '' local thread = effil.thread(_mh_ads_delete_req)(_vbr7n .. '/mh/listings/' .. tostring(tonumber(data.id) or 0), _nck, _sess) lua_thread.create(function() local _w = 0 while _w < 200 do wait(30); _w = _w + 1 local status, err = thread:status() if not status or err then return end if status == 'completed' or status == 'canceled' then local ok2, resp = thread:get() local _ok = ok2 and resp and resp.status_code == 200 if _ok then mh_notify('[MH] {aaffaa}' .. '\xce\xe1\xfa\xff\xe2\xeb\xe5\xed\xe8\xe5\x20\xf3\xe4\xe0\xeb\xe5\xed\xee\x2e', 0xFFFFFF) else mh_notify('[MH] {ff6666}' .. '\xcd\xe5\x20\xf3\xe4\xe0\xeb\xee\xf1\xfc\x20\xf3\xe4\xe0\xeb\xe8\xf2\xfc\x20\xee\xe1\xfa\xff\xe2\xeb\xe5\xed\xe8\xe5\x2e', 0xFFFFFF) end local ok_j, j = pcall(encodeJson, {kind = 'delete_result', ok = _ok and true or false, id = data.id}) if ok_j then local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if ok_b64 and b64 then pcall(function() _mh_web_jar.executeJS(_mh_ads.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end end return end end end) elseif data.kind == 'ads_create_manual' then local _srv = _mh_cloud_current_server_id() if data.server_id and tonumber(data.server_id) then _srv = tonumber(data.server_id) end local _items = {} for _, it in ipairs(data.items or {}) do table.insert(_items, { name = it.name, price = tonumber(it.price) or 0, qty = tonumber(it.qty) or 0, }) end _mh_listings.post('/api/mh/listings/publish', { preset_id = '', deal_type = (data.deal_type == 'buy') and 'buy' or 'sale', server_id = _srv, sell_items = (data.deal_type == 'buy') and {} or _items, buy_items = (data.deal_type == 'buy') and _items or {}, contact = data.contact or '', description = data.description or '', }, function(res, err) if res and res.ok then mh_notify('[MH] {00cc00}' .. '\xce\xe1\xfa\xff\xe2\xeb\xe5\xed\xe8\xe5\x20\xf0\xe0\xe7\xec\xe5\xf9\xe5\xed\xee\x2e', 0xFFFFFF) else mh_notify('[MH] {ff6666}' .. _mh_listings.error_message(err), 0xFFFFFF) end local ok_j, j = pcall(encodeJson, {kind = 'create_result', ok = (res and res.ok) and true or false, error = err}) if ok_j then local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if ok_b64 and b64 then pcall(function() _mh_web_jar.executeJS(_mh_ads.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end end end) elseif data.kind == 'ads_search' and data.query then if type(_mh_buy) == 'table' and _mh_buy.push_search_results then local _q_cp = _mh_buy.u8_to_cp(data.query) local _req_id = tonumber(data.req_id) or 0 local _results = {} local _q_lo = fh_lower(_q_cp) local _count = 0 for nm, _ in pairs(fh_mkt_prices or {}) do if _count >= 30 then break end if _q_lo == '' or fh_lower(nm):find(_q_lo, 1, true) then local _pr = fh_mkt_prices[nm] table.insert(_results, { name = _mh_buy.cp_to_u8(nm), price = (_pr and _pr.cp_sp) or 0, }) _count = _count + 1 end end local ok_j, j = pcall(encodeJson, {kind = 'search_results', req_id = _req_id, items = _results}) if ok_j then local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if ok_b64 and b64 then pcall(function() _mh_web_jar.executeJS(_mh_ads.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end end end end end if _mh_web_jar then local _mh_ads_onAction_prev = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _mh_ads.bid then if data.type == "WV_STARTED" then _mh_ads.state.crashed = false _mh_ads.state.loaded = false elseif data.type == "WV_LOADED" then _mh_ads.state.loaded = true _mh_ads.apply_zoom() elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_ads.state.progress then _mh_ads.state.progress = p end elseif data.type == "WV_DIE" then _mh_ads.state.crashed = true _mh_ads.state.created = false pcall(function() _mh_web_jar.deleteBrowser(_mh_ads.bid) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then pcall(_mh_ads.handle_bridge_payload, _js_obj) end end end return end if _mh_ads_onAction_prev then _mh_ads_onAction_prev(data) end end end function _mh_ads_hide() if _mh_web_jar and _mh_ads.state.created then pcall(function() _mh_web_jar.setClickable(_mh_ads.bid, false) _mh_web_jar.setVisible(_mh_ads.bid, false) _mh_web_jar.deleteBrowser(_mh_ads.bid) end) _mh_ads.state.created = false _mh_ads.state.loaded = false _mh_ads.state.crashed = false _mh_ads.state.progress = 0 _mh_ads.state.visible_applied = false _mh_ads.state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_ads_soft_hide() if _mh_web_jar and _mh_ads.state.created then pcall(function() _mh_web_jar.setClickable(_mh_ads.bid, false) _mh_web_jar.setVisible(_mh_ads.bid, false) end) _mh_ads.state.visible_applied = false end end function _mh_ads_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0\x20\x77\x65\x62\x76\x69\x65\x77\x73\x20\xed\xe5\x20\xed\xe0\xe9\xe4\xe5\xed\xe0\x2e') imgui.TextWrapped(u8'\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xe5\x20\xe1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xf3\x20\x77\x65\x62\x76\x69\x65\x77\x73\x20\x28\xf2\xe0\x20\xe6\xe5\x2c\x20\xf7\xf2\xee\x20\xe8\xf1\xef\xee\xeb\xfc\xe7\xf3\xe5\xf2\xf1\xff\x20\xe2\x20\x57\x65\x62\x4d\x6f\x62\x69\x6c\x65\x29\x2c\x20\xf7\xf2\xee\xe1\xfb\x20\xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0\x20\xf1\xe0\xe9\xf2\xe0\x20\xf0\xe0\xe1\xee\xf2\xe0\xeb\xe0\x2e') return end if not _mh_ads.state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_mh_ads.bid, _mh_ads.build_url()) end) if ok then _mh_ads.state.created = true _mh_ads.state.loaded = false _mh_ads.state.crashed = false _mh_ads.state.progress = 0 _mh_ads.state.visible_applied = false end end if _mh_ads.state.created then if not _mh_ads.state.visible_applied then pcall(function() _mh_web_jar.setClickable(_mh_ads.bid, true) _mh_web_jar.setVisible(_mh_ads.bid, true) end) _mh_ads.state.visible_applied = true end end imgui.Text((fa.BULLHORN or fa.TOWER_BROADCAST) .. ' ' .. u8'\xce\xc1\xda\xdf\xc2\xcb\xc5\xcd\xc8\xdf') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_ads_zoom_out', imgui.ImVec2(30*d, 0)) then _mh_ads.set_zoom(settings.mh_web.ads_zoom - 0.1) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.ads_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_ads_zoom_in', imgui.ImVec2(30*d, 0)) then _mh_ads.set_zoom(settings.mh_web.ads_zoom + 0.1) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_ads_reload') and _mh_web_jar then _mh_ads.state.loaded = false _mh_ads.state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_mh_ads.bid, _mh_ads.build_url()) end) end imgui.Separator() local _wp, _cp3 = imgui.GetWindowPos(), imgui.GetCursorPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _wp.x + _cp3.x, _wp.y + _cp3.y local _pw, _ph = _avail.x, _avail.y if _mh_ads.state.created and _mh_web_jar then local _pos = _mh_ads.state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_mh_ads.bid, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_mh_ads.bid, _pw, _ph) end) end _mh_ads.state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_ads.state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp3.x, _cp3.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0\x20\xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff\x20\xef\xe5\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc\x20\x28\x63\x72\x61\x73\x68\x29\x20\x3a\x28') imgui.SetCursorPos(imgui.ImVec2(_cp3.x, _cp3.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc\x23\x23\x6d\x68\x5f\x61\x64\x73\x5f\x72\x65\x74\x72\x79', imgui.ImVec2(160*d, 0)) then _mh_ads.state.created = false end elseif not _mh_ads.state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp3.x, _cp3.y + _ph/2 - 10)) local _pct = _mh_ads.state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e\x20' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end function _mh_shops_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0 webviews \xed\xe5 \xed\xe0\xe9\xe4\xe5\xed\xe0.') imgui.TextWrapped(u8'\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xe5 \xe1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xf3 webviews (\xf2\xe0 \xe6\xe5, \xf7\xf2\xee \xe8\xf1\xef\xee\xeb\xfc\xe7\xf3\xe5\xf2\xf1\xff \xe2 WebMobile), \xf7\xf2\xee\xe1\xfb \xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xf1\xe0\xe9\xf2\xe0 \xf0\xe0\xe1\xee\xf2\xe0\xeb\xe0.') return end if not _mh_shops_state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_MH_SHOPS_BID, _mh_shops_build_url()) end) if ok then _mh_shops_state.created = true _mh_shops_state.loaded = false _mh_shops_state.crashed = false _mh_shops_state.progress = 0 _mh_shops_state.visible_applied = false _mh_shops_state.built_sess = _G._mh_session_tok or '' end end if _mh_shops_state.created then local _cur_sess = _G._mh_session_tok or '' if _cur_sess ~= '' and _cur_sess ~= (_mh_shops_state.built_sess or '') then pcall(function() _mh_web_jar.changeUrl(_MH_SHOPS_BID, _mh_shops_build_url()) end) _mh_shops_state.built_sess = _cur_sess _mh_shops_state.loaded = false _mh_shops_state.progress = 0 end end if _mh_shops_state.created then if not _mh_shops_state.visible_applied then pcall(function() _mh_web_jar.setClickable(_MH_SHOPS_BID, true) _mh_web_jar.setVisible(_MH_SHOPS_BID, true) end) _mh_shops_state.visible_applied = true end end imgui.Text(fa.GLOBE .. ' ' .. u8'\xcb\xc0\xc2\xca\xc8') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_shops_zoom_out', imgui.ImVec2(30*d, 0)) then _mh_shops_set_zoom(settings.mh_web.shops_zoom - 0.1) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.shops_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_shops_zoom_in', imgui.ImVec2(30*d, 0)) then _mh_shops_set_zoom(settings.mh_web.shops_zoom + 0.1) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_shops_reload') and _mh_web_jar then _mh_shops_state.loaded = false _mh_shops_state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_MH_SHOPS_BID, _MH_SHOPS_URL) end) end imgui.Separator() local _cp = imgui.GetCursorPos() local _screen_pos = imgui.GetCursorScreenPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _screen_pos.x, _screen_pos.y local _pw, _ph = _avail.x, _avail.y if _mh_shops_state.created and _mh_web_jar then local _pos = _mh_shops_state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_MH_SHOPS_BID, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_MH_SHOPS_BID, _pw, _ph) end) end _mh_shops_state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_shops_state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff \xef\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc (crash) :(') imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc##mh_shops_retry', imgui.ImVec2(160*d, 0)) then _mh_shops_state.created = false end elseif not _mh_shops_state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) local _pct = _mh_shops_state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0... ' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0...') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end local _MH_ARB_STATIC_VER = 5 local function _mh_arb_build_url() local _nick = '' pcall(function() _nick = (settings.premium and settings.premium.nick) or '' end) if _nick == '' then _nick = _mh_get_live_nick() end local _sess = _G._mh_session_tok or '' local _z = (settings.mh_web and settings.mh_web.arb_zoom) or 0.9 return "https://va-ta.com/arb/?v=" .. tostring(_MH_ARB_STATIC_VER) .. "&nick=" .. _mh_url_encode(_nick) .. "&sess=" .. _mh_url_encode(_sess) .. "&zoom=" .. tostring(_z) .. "&theme=" .. _mh_theme.query_param() end local _MH_ARB_BID = 9003 local _mh_arb_state = { created = false, loaded = false, crashed = false, progress = 0, pos = {x = 0, y = 0, w = 0, h = 0}, } if not settings.mh_web.arb_zoom then settings.mh_web.arb_zoom = 0.9 end local function _mh_arb_apply_zoom() if not (_mh_web_jar and _mh_arb_state.created) then return end local z = settings.mh_web.arb_zoom pcall(function() _mh_web_jar.executeJS(_MH_ARB_BID, "document.documentElement.style.zoom='" .. tostring(z) .. "';") end) end local function _mh_arb_set_zoom(z) if z < 0.3 then z = 0.3 end if z > 1.5 then z = 1.5 end settings.mh_web.arb_zoom = z pcall(_wfn7p) _mh_arb_apply_zoom() end if _mh_web_jar then local _mh_shops_onAction_prev = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _MH_ARB_BID then if data.type == "WV_STARTED" then _mh_arb_state.crashed = false _mh_arb_state.loaded = false elseif data.type == "WV_LOADED" then _mh_arb_state.loaded = true _mh_arb_apply_zoom() elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_arb_state.progress then _mh_arb_state.progress = p end elseif data.type == "WV_DIE" then _mh_arb_state.crashed = true _mh_arb_state.created = false pcall(function() _mh_web_jar.deleteBrowser(_MH_ARB_BID) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then pcall(_mh_shops_handle_bridge_payload, _js_obj) end end end return end if _mh_shops_onAction_prev then _mh_shops_onAction_prev(data) end end end function _mh_arb_hide() if _mh_web_jar and _mh_arb_state.created then pcall(function() _mh_web_jar.setClickable(_MH_ARB_BID, false) _mh_web_jar.setVisible(_MH_ARB_BID, false) _mh_web_jar.deleteBrowser(_MH_ARB_BID) end) _mh_arb_state.created = false _mh_arb_state.loaded = false _mh_arb_state.crashed = false _mh_arb_state.progress = 0 _mh_arb_state.visible_applied = false _mh_arb_state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_arb_soft_hide() if _mh_web_jar and _mh_arb_state.created then pcall(function() _mh_web_jar.setClickable(_MH_ARB_BID, false) _mh_web_jar.setVisible(_MH_ARB_BID, false) end) _mh_arb_state.visible_applied = false end end function _mh_arb_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0 webviews \xed\xe5 \xed\xe0\xe9\xe4\xe5\xed\xe0.') imgui.TextWrapped(u8'\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xe5 \xe1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xf3 webviews (\xf2\xe0 \xe6\xe5, \xf7\xf2\xee \xe8\xf1\xef\xee\xeb\xfc\xe7\xf3\xe5\xf2\xf1\xff \xe2 WebMobile), \xf7\xf2\xee\xe1\xfb \xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xf1\xe0\xe9\xf2\xe0 \xf0\xe0\xe1\xee\xf2\xe0\xeb\xe0.') return end if not _mh_arb_state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_MH_ARB_BID, _mh_arb_build_url()) end) if ok then _mh_arb_state.created = true _mh_arb_state.loaded = false _mh_arb_state.crashed = false _mh_arb_state.progress = 0 _mh_arb_state.visible_applied = false _mh_arb_state.built_sess = _G._mh_session_tok or '' end end if _mh_arb_state.created then local _cur_sess = _G._mh_session_tok or '' if _cur_sess ~= '' and _cur_sess ~= (_mh_arb_state.built_sess or '') then pcall(function() _mh_web_jar.changeUrl(_MH_ARB_BID, _mh_arb_build_url()) end) _mh_arb_state.built_sess = _cur_sess _mh_arb_state.loaded = false _mh_arb_state.progress = 0 end end if _mh_arb_state.created then if not _mh_arb_state.visible_applied then pcall(function() _mh_web_jar.setClickable(_MH_ARB_BID, true) _mh_web_jar.setVisible(_MH_ARB_BID, true) end) _mh_arb_state.visible_applied = true end end imgui.Text(fa.GLOBE .. ' ' .. u8'\xc0\xd0\xc1\xc8\xd2\xd0\xc0\xc6') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_arb_zoom_out', imgui.ImVec2(30*d, 0)) then _mh_arb_set_zoom(settings.mh_web.arb_zoom - 0.1) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.arb_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_arb_zoom_in', imgui.ImVec2(30*d, 0)) then _mh_arb_set_zoom(settings.mh_web.arb_zoom + 0.1) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_arb_reload') and _mh_web_jar then _mh_arb_state.loaded = false _mh_arb_state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_MH_ARB_BID, _mh_arb_build_url()) end) end imgui.Separator() local _cp = imgui.GetCursorPos() local _screen_pos = imgui.GetCursorScreenPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _screen_pos.x, _screen_pos.y local _pw, _ph = _avail.x, _avail.y if _mh_arb_state.created and _mh_web_jar then local _pos = _mh_arb_state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_MH_ARB_BID, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_MH_ARB_BID, _pw, _ph) end) end _mh_arb_state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_arb_state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff \xef\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc (crash) :(') imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc##mh_arb_retry', imgui.ImVec2(160*d, 0)) then _mh_arb_state.created = false end elseif not _mh_arb_state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) local _pct = _mh_arb_state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0... ' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0...') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end _mh_pick = _mh_pick or {} _mh_pick.static_ver = 6 _mh_pick.bid = 9008 _mh_pick.state = _mh_pick.state or { created = false, loaded = false, crashed = false, progress = 0, pos = {x = 0, y = 0, w = 0, h = 0}, } function _mh_pick.build_url() local _srv_id = -1 pcall(function() local _s = ARZ_SERVERS[_G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 1] _srv_id = _s and _s.id or -1 end) local _url = "https://va-ta.com/pick/?v=" .. tostring(_mh_pick.static_ver) if _srv_id and _srv_id ~= -1 then _url = _url .. "&server=" .. tostring(_srv_id) end local _z = (settings.mh_web and settings.mh_web.pick_zoom) or 0.9 _url = _url .. "&zoom=" .. tostring(_z) .. "&theme=" .. _mh_theme.query_param() return _url end if not settings.mh_web.pick_zoom then settings.mh_web.pick_zoom = 0.9 end function _mh_pick.apply_zoom() if not (_mh_web_jar and _mh_pick.state.created) then return end local z = settings.mh_web.pick_zoom pcall(function() _mh_web_jar.executeJS(_mh_pick.bid, "document.documentElement.style.zoom='" .. tostring(z) .. "';") end) end function _mh_pick.set_zoom(z) if z < 0.3 then z = 0.3 end if z > 1.5 then z = 1.5 end settings.mh_web.pick_zoom = z pcall(_wfn7p) _mh_pick.apply_zoom() end if _mh_web_jar then _mh_pick._onAction_prev = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _mh_pick.bid then if data.type == "WV_STARTED" then _mh_pick.state.crashed = false _mh_pick.state.loaded = false elseif data.type == "WV_LOADED" then _mh_pick.state.loaded = true _mh_pick.apply_zoom() elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_pick.state.progress then _mh_pick.state.progress = p end elseif data.type == "WV_DIE" then _mh_pick.state.crashed = true _mh_pick.state.created = false pcall(function() _mh_web_jar.deleteBrowser(_mh_pick.bid) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then pcall(_mh_shops_handle_bridge_payload, _js_obj) end end end return end if _mh_pick._onAction_prev then _mh_pick._onAction_prev(data) end end end function _mh_pick_hide() if _mh_web_jar and _mh_pick.state.created then pcall(function() _mh_web_jar.setClickable(_mh_pick.bid, false) _mh_web_jar.setVisible(_mh_pick.bid, false) _mh_web_jar.deleteBrowser(_mh_pick.bid) end) _mh_pick.state.created = false _mh_pick.state.loaded = false _mh_pick.state.crashed = false _mh_pick.state.progress = 0 _mh_pick.state.visible_applied = false _mh_pick.state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_pick_soft_hide() if _mh_web_jar and _mh_pick.state.created then pcall(function() _mh_web_jar.setClickable(_mh_pick.bid, false) _mh_web_jar.setVisible(_mh_pick.bid, false) end) _mh_pick.state.visible_applied = false end end function _mh_pick_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0 webviews \xed\xe5 \xed\xe0\xe9\xe4\xe5\xed\xe0.') imgui.TextWrapped(u8'\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xe5 \xe1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xf3 webviews (\xf2\xe0 \xe6\xe5, \xf7\xf2\xee \xe8\xf1\xef\xee\xeb\xfc\xe7\xf3\xe5\xf2\xf1\xff \xe2 WebMobile), \xf7\xf2\xee\xe1\xfb \xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xf1\xe0\xe9\xf2\xe0 \xf0\xe0\xe1\xee\xf2\xe0\xeb\xe0.') return end if not _mh_pick.state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_mh_pick.bid, _mh_pick.build_url()) end) if ok then _mh_pick.state.created = true _mh_pick.state.loaded = false _mh_pick.state.crashed = false _mh_pick.state.progress = 0 _mh_pick.state.visible_applied = false end end if _mh_pick.state.created then if not _mh_pick.state.visible_applied then pcall(function() _mh_web_jar.setClickable(_mh_pick.bid, true) _mh_web_jar.setVisible(_mh_pick.bid, true) end) _mh_pick.state.visible_applied = true end end imgui.Text(fa.GLOBE .. ' ' .. u8'\xcf\xce\xc4\xc1\xce\xd0') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_pick_zoom_out', imgui.ImVec2(30*d, 0)) then _mh_pick.set_zoom(settings.mh_web.pick_zoom - 0.1) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.pick_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_pick_zoom_in', imgui.ImVec2(30*d, 0)) then _mh_pick.set_zoom(settings.mh_web.pick_zoom + 0.1) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_pick_reload') and _mh_web_jar then _mh_pick.state.loaded = false _mh_pick.state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_mh_pick.bid, _mh_pick.build_url()) end) end imgui.Separator() local _cp = imgui.GetCursorPos() local _screen_pos = imgui.GetCursorScreenPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _screen_pos.x, _screen_pos.y local _pw, _ph = _avail.x, _avail.y if _mh_pick.state.created and _mh_web_jar then local _pos = _mh_pick.state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_mh_pick.bid, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_mh_pick.bid, _pw, _ph) end) end _mh_pick.state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_pick.state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff \xef\xe5\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc (crash) :(') imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc##mh_pick_retry', imgui.ImVec2(160*d, 0)) then _mh_pick.state.created = false end elseif not _mh_pick.state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) local _pct = _mh_pick.state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0... ' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0...') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end local _MH_LOG = { static_ver = 2, bid = 9009, page_size = 60, trade_page_size = 20, chat_page_size = 80, hist_page_size = 100, state = { created = false, loaded = false, crashed = false, progress = 0, pos = {x = 0, y = 0, w = 0, h = 0}, }, } _MH_LOG.static_ver = 19 if not settings.mh_web.log_zoom then settings.mh_web.log_zoom = 0.8 end function _MH_LOG.build_url() local _srv = _mh_cloud_current_server_id() if _srv == -1 then local _boot = tonumber(_G.mh_boot_srv_idx or 0) or 0 if _boot > 0 then _srv = (ARZ_SERVERS[_boot + 1] or {}).id or -1 end end local _z = (settings.mh_web and settings.mh_web.log_zoom) or 0.8 local _ov = (settings.overlay and settings.overlay.enabled ~= false) and "1" or "0" return "https://va-ta.com/log/?v=" .. tostring(_MH_LOG.static_ver) .. "&server=" .. tostring(_srv) .. "&zoom=" .. tostring(_z) .. "&theme=" .. _mh_theme.query_param() .. "&ov=" .. _ov end function _MH_LOG.apply_zoom() if not (_mh_web_jar and _MH_LOG.state.created) then return end local z = settings.mh_web.log_zoom pcall(function() _mh_web_jar.executeJS(_MH_LOG.bid, "document.documentElement.style.zoom='" .. tostring(z) .. "';") end) end function _MH_LOG.set_zoom(z) if z < 0.3 then z = 0.3 end if z > 1.5 then z = 1.5 end settings.mh_web.log_zoom = z pcall(_wfn7p) _MH_LOG.apply_zoom() end function _MH_LOG.send(payload) if mh_debug_enabled then mh_notify('[MH Дебаг] {aaaaaa}_MH_LOG.send kind='..tostring(payload and payload.kind).. ' created='..tostring(_MH_LOG.state.created)..' loaded='..tostring(_MH_LOG.state.loaded), 0xFFFFFF) end if not (_mh_web_jar and _MH_LOG.state.created) then return end local ok_j, j = pcall(encodeJson, payload) if not ok_j then if mh_debug_enabled then mh_notify('[MH Дебаг] {ff5555}_MH_LOG.send: encodeJson FAILED', 0xFF5555) end return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then if mh_debug_enabled then mh_notify('[MH Дебаг] {ff5555}_MH_LOG.send: base64 FAILED', 0xFF5555) end return end local ok_exec, err_exec = pcall(function() _mh_web_jar.executeJS(_MH_LOG.bid, "window.mhReceiveLogData && window.mhReceiveLogData('" .. b64 .. "');") end) if mh_debug_enabled then if ok_exec then mh_notify('[MH Дебаг] {55ff55}_MH_LOG.send: executeJS OK, len='..tostring(#b64), 0x55FF55) else mh_notify('[MH Дебаг] {ff5555}_MH_LOG.send: executeJS THREW '..tostring(err_exec), 0xFF5555) end end end function _MH_LOG.day_ok(le_dt, day_filter, sel_date) if day_filter == 0 or not day_filter 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 day_filter == 1 then return e_day == d_day elseif day_filter == 2 or 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 local span = (day_filter == 2) and (7 * 86400) or (30 * 86400) return (now - e_ts) <= span elseif day_filter == 4 then return e_day == (sel_date or '') end return true end function _MH_LOG.query_deals(req_id, page, search, day_filter, sel_date) if mh_debug_enabled then mh_notify('[MH Дебаг] {aaaaaa}query_deals: fh_mkt_log total='..tostring(#fh_mkt_log).. ' day_filter='..tostring(day_filter)..' first_dt='..tostring(fh_mkt_log[1] and fh_mkt_log[1].dt), 0xFFFFFF) end local srch = (search or ''):lower() local filt = {} for i = #fh_mkt_log, 1, -1 do local le = fh_mkt_log[i] if le and le.item and _MH_LOG.day_ok(le.dt, day_filter, sel_date) then local item_lo = le.item:lower() if srch == '' or item_lo:find(srch, 1, true) then table.insert(filt, le) end end end if mh_debug_enabled then mh_notify('[MH Дебаг] {aaaaaa}query_deals: filt count='..tostring(#filt), 0xFFFFFF) end local total_sell_sum, total_buy_sum, total_sell_cnt, total_buy_cnt = 0, 0, 0, 0 for _, le2 in ipairs(filt) do local is_s = fh_is_my_sell(le2) local q = le2.qty or 1 if is_s then total_sell_sum = total_sell_sum + (le2.price or 0) * q; total_sell_cnt = total_sell_cnt + q else total_buy_sum = total_buy_sum + (le2.price or 0) * q; total_buy_cnt = total_buy_cnt + q end end local total_count = #filt local total_pages = math.max(1, math.ceil(total_count / _MH_LOG.page_size)) if page < 1 then page = 1 end if page > total_pages then page = total_pages end local from_i = (page - 1) * _MH_LOG.page_size + 1 local to_i = math.min(page * _MH_LOG.page_size, total_count) local items = {} for i = from_i, to_i do local le = filt[i] table.insert(items, { dt = le.dt, item = _mh_bridge_cp_to_u8(le.item), price = le.price, qty = le.qty or 1, op = fh_is_my_sell(le) and 'sell' or 'buy', status = le.status, }) end _MH_LOG.send({ kind = 'deals_page', req_id = req_id, page = page, total_pages = total_pages, total_count = total_count, items = items, totals = { sell_sum = total_sell_sum, sell_cnt = total_sell_cnt, buy_sum = total_buy_sum, buy_cnt = total_buy_cnt }, }) end function _MH_LOG.parse_chat_colors(raw) local segments = {} local cur_r, cur_g, cur_b = 0.85, 0.85, 0.85 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(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 return segments end local _MH_LOG_TRADE_WORDS = { '\xcf\xf0\xee\xe4\xe0\xec', '\xcf\xf0\xee\xe4\xe0\xfd\xf8\xfc', '\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', } function _MH_LOG.query_chat(req_id, page, filter, search) local srch = (search or ''):lower() local out = {} local out_ts = {} for i = 1, #fh_session_chat do local raw = fh_session_chat[i] local plain = raw:gsub('{%x%x%x%x%x%x%x?%x?}', ''):lower() local srch_ok = srch == '' or plain:find(srch, 1, true) local filt_ok = false if not filter or filter == 0 then filt_ok = true elseif filter == 1 then filt_ok = plain:find('vip adv', 1, true) ~= nil elseif filter == 2 then for _, w in ipairs(_MH_LOG_TRADE_WORDS) do if plain:find(w:lower(), 1, true) then filt_ok = true; break end end elseif filter == 3 then filt_ok = raw:find('[\xc0\xeb\xfc\xff\xed\xf1]', 1, true) ~= nil elseif filter == 4 then filt_ok = raw:find('[\xd1\xe5\xec\xfc\xff]', 1, true) ~= nil end if filt_ok and srch_ok then table.insert(out, raw) table.insert(out_ts, fh_session_chat_ts and fh_session_chat_ts[i] or nil) end end local total_count = #out local total_pages = math.max(1, math.ceil(total_count / _MH_LOG.chat_page_size)) if page < 1 then page = 1 end if page > total_pages then page = total_pages end local from_i = (page - 1) * _MH_LOG.chat_page_size + 1 local to_i = math.min(page * _MH_LOG.chat_page_size, total_count) local items = {} for i = from_i, to_i do local segs = _MH_LOG.parse_chat_colors(out[i]) for _, s in ipairs(segs) do s.t = _mh_bridge_cp_to_u8(s.t) end local ts = out_ts[i] table.insert(items, { segs = segs, dt = ts and os.date('%H:%M:%S', ts) or '' }) end _MH_LOG.send({ kind = 'chat_page', req_id = req_id, page = page, total_pages = total_pages, total_count = total_count, items = items, last_count = #fh_session_chat, }) end function _MH_LOG.query_chat_dates(req_id) local ok, dates = pcall(MH_util.cby5m) local out = {} if ok and dates then for _, d in ipairs(dates) do table.insert(out, { label = _mh_bridge_cp_to_u8(d.label), day = d.day, month = d.month, year = d.year }) end end _MH_LOG.send({ kind = 'chat_dates', req_id = req_id, dates = out }) end function _MH_LOG.query_chat_history(req_id, day, month, year, page, search) local ok, raw = pcall(_rqh9z, day, month, year) local lines = {} if ok and raw then for i = #raw, 1, -1 do table.insert(lines, raw[i]) end end local srch = (search or ''):lower() local filt = {} for _, hline in ipairs(lines) do if hline ~= '' then local plain = hline:gsub('{%x%x%x%x%x%x%x?%x?}', ''):lower() if srch == '' or plain:find(srch, 1, true) then table.insert(filt, hline) end end end local total_count = #filt local total_pages = math.max(1, math.ceil(total_count / _MH_LOG.hist_page_size)) if page < 1 then page = 1 end if page > total_pages then page = total_pages end local from_i = (page - 1) * _MH_LOG.hist_page_size + 1 local to_i = math.min(page * _MH_LOG.hist_page_size, total_count) local items = {} for i = from_i, to_i do local segs = _MH_LOG.parse_chat_colors(filt[i]) for _, s in ipairs(segs) do s.t = _mh_bridge_cp_to_u8(s.t) end table.insert(items, segs) end _MH_LOG.send({ kind = 'chat_history_page', req_id = req_id, page = page, total_pages = total_pages, total_count = total_count, items = items, }) end function _MH_LOG.query_catch_loot(req_id, day_filter) local ok, a, b, c, d = pcall(_mh_catch_loot_query, tonumber(day_filter) or 0) local days, total_qty, total_types, total_opened if ok then days, total_qty, total_types, total_opened = a, b, c, d else days, total_qty, total_types, total_opened = {}, 0, 0, 0 if mh_debug_enabled then mh_notify('[MH DEBUG] catch_loot_query ERROR: '..tostring(a), 0xFF5555) end end _MH_LOG.send({ kind = 'catch_loot_page', req_id = req_id, days = type(days) == 'table' and days or {}, total_qty = tonumber(total_qty) or 0, total_types = tonumber(total_types) or 0, total_opened = tonumber(total_opened) or 0, }) end function _G._mh_catch_push() if not (_mh_web_jar and _MH_LOG.state.created and _MH_LOG.state.loaded) then return end local q = _MH_LOG.last_catch_q or {req_id = 0, day_filter = 0} pcall(_MH_LOG.query_catch_loot, q.req_id, q.day_filter) end function _MH_LOG.clear_catch_loot() settings.catch_loot_log = {} settings.catch_open_log = {} pcall(_wfn7p) pcall(_G._mh_save_catch_now) pcall(_G._mh_catch_push) end function _MH_LOG.query_trades(req_id, page, day_filter, sel_date) local filt = {} for i = 1, #fh_trade_log do local fe = fh_trade_log[i] if fe and _MH_LOG.day_ok(fe.dt, day_filter, sel_date) then table.insert(filt, fe) end end local sgm, sget = 0, 0 for _, tr in ipairs(filt) do sgm = sgm + (tr.give_money or 0) sget = sget + (tr.get_money or 0) end local total_count = #filt local total_pages = math.max(1, math.ceil(total_count / _MH_LOG.trade_page_size)) if page < 1 then page = 1 end if page > total_pages then page = total_pages end local from_i = (page - 1) * _MH_LOG.trade_page_size + 1 local to_i = math.min(page * _MH_LOG.trade_page_size, total_count) local items = {} local function conv_items(list) local out = {} for _, gi in ipairs(list or {}) do table.insert(out, { name = _mh_bridge_cp_to_u8(gi.name), qty = gi.qty }) end return out end for i = from_i, to_i do local tr = filt[i] table.insert(items, { dt = tr.dt, partner = _mh_bridge_cp_to_u8(tr.partner or ''), give_money = tr.give_money, get_money = tr.get_money, give_items = conv_items(tr.give_items), get_items = conv_items(tr.get_items), }) end _MH_LOG.send({ kind = 'trades_page', req_id = req_id, page = page, total_pages = total_pages, total_count = total_count, items = items, totals = { given = sgm, received = sget }, }) end function _MH_LOG.query_tg_settings(req_id) local tg = settings.telegram or {} local token = tg.bot_token or '' _MH_LOG.send({ kind = 'tg_settings', req_id = req_id, has_token = token ~= '', token_tail = #token >= 4 and token:sub(-4) or '', chat_id = tg.chat_id or '', enabled = tg.enabled or false, notify_trades = tg.notify_trades or false, notify_watch = tg.notify_watch or false, notify_payday = tg.notify_payday or false, notify_heartbeat = tg.notify_heartbeat or false, use_proxy = tg.use_proxy or false, notify_arb = tg.notify_arb or false, notify_fav = tg.notify_fav or false, arb_threshold = tg.arb_threshold or 0, is_premium = _mh_is_premium(), }) end function _MH_LOG.set_tg_setting(key, value) if not settings.telegram then settings.telegram = {} end local tg = settings.telegram local bool_keys = { enabled=true, notify_trades=true, notify_watch=true, notify_payday=true, notify_heartbeat=true, use_proxy=true, notify_arb=true, notify_fav=true } local str_keys = { bot_token=true, chat_id=true } if bool_keys[key] then tg[key] = value == true pcall(_wfn7p) elseif str_keys[key] then local ok, r = pcall(function() return require('encoding').CP1251:encode(value) end) tg[key] = ok and r or value pcall(_wfn7p) elseif key == 'arb_threshold' then tg.arb_threshold = tonumber(value) or 0 pcall(_wfn7p) end end function _mh_notify_timer_next_id() local mx = 0 for _, t in ipairs(settings.notify_timers or {}) do local id = tonumber(t.id) or 0 if id > mx then mx = id end end return mx + 1 end function _mh_notify_timer_normalize(t) if type(t) ~= 'table' then return nil end t.id = tonumber(t.id) or _mh_notify_timer_next_id() t.title = tostring(t.title or '') t.interval_sec = math.max(60, math.floor(tonumber(t.interval_sec) or 1800)) t.repeats = math.max(0, math.floor(tonumber(t.repeats) or 0)) t.fired = math.max(0, math.floor(tonumber(t.fired) or 0)) t.enabled = t.enabled ~= false t.warn_enabled = t.warn_enabled == true t.warn_sec = math.max(60, math.floor(tonumber(t.warn_sec) or 300)) t.next_at = tonumber(t.next_at) or 0 t.warned = t.warned == true if t.enabled and t.next_at <= 0 then t.next_at = os.time() + t.interval_sec t.warned = false end return t end function _mh_notify_timer_emit(t, prefix) local title = tostring(t.title or 'Напоминание') local text = '[MH TIMER] {ffffff}' .. title if prefix and prefix ~= '' then text = '[MH TIMER] {ffaa00}' .. prefix .. ': {ffffff}' .. title end mh_notify_local(text, 0xFFCC66, 'reminder') end function _mh_notify_timer_tick() local now = os.time() for _, t in ipairs(settings.notify_timers or {}) do _mh_notify_timer_normalize(t) if t.enabled and t.next_at > 0 then if t.warn_enabled and not t.warned and t.interval_sec > t.warn_sec and now >= (t.next_at - t.warn_sec) and now < t.next_at then local mins = math.max(1, math.ceil(t.warn_sec / 60)) _mh_notify_timer_emit(t, 'через ' .. tostring(mins) .. ' мин.') t.warned = true end if now >= t.next_at then _mh_notify_timer_emit(t, 'Время') t.fired = t.fired + 1 if t.repeats > 0 and t.fired >= t.repeats then t.enabled = false t.next_at = 0 t.warned = false else repeat t.next_at = t.next_at + t.interval_sec until t.next_at > now t.warned = false end pcall(_wfn7p) end end end end lua_thread.create(function() wait(3000) while true do pcall(_mh_notify_timer_tick) wait(1000) end end) function _MH_LOG.query_notify_settings(req_id) local nf = settings.notify_filters or {} _MH_LOG.send({ kind = 'notify_settings', req_id = req_id, sell_item = nf.sell_item ~= false, buy_item = nf.buy_item ~= false, sell_status = nf.sell_status ~= false, buy_status = nf.buy_status ~= false, watch = nf.watch ~= false, fav = nf.fav ~= false, other = nf.other ~= false, arb = nf.arb ~= false, reminder = nf.reminder ~= false, sound_enabled = _G.MH_SOUND_ENABLED ~= false, history_max = settings.notify_history_max or 500, }) end function _MH_LOG.set_notify_setting(key, value) local bool_keys = { sell_item=true, buy_item=true, sell_status=true, buy_status=true, watch=true, fav=true, other=true, arb=true, reminder=true } if bool_keys[key] then if not settings.notify_filters then settings.notify_filters = {} end settings.notify_filters[key] = value == true pcall(_wfn7p) elseif key == 'sound_enabled' then _G.MH_SOUND_ENABLED = value == true settings.sound_enabled = value == true pcall(_wfn7p) end end function _MH_LOG.query_notify_timers(req_id) local out = {} for _, t in ipairs(settings.notify_timers or {}) do _mh_notify_timer_normalize(t) table.insert(out, { id=t.id, title=_mh_bridge_cp_to_u8(t.title or ''), interval_sec=t.interval_sec, repeats=t.repeats, fired=t.fired or 0, enabled=t.enabled ~= false, warn_enabled=t.warn_enabled == true, warn_sec=t.warn_sec or 300, next_at=t.next_at or 0, }) end _MH_LOG.send({kind='notify_timers', req_id=req_id, items=out}) end function _MH_LOG.upsert_notify_timer(data) if type(settings.notify_timers) ~= 'table' then settings.notify_timers = {} end local id = tonumber(data.id) or 0 local found for _, t in ipairs(settings.notify_timers) do if tonumber(t.id) == id and id > 0 then found=t break end end if not found then found={id=_mh_notify_timer_next_id(), fired=0} table.insert(settings.notify_timers, found) end local title=tostring(data.title or ''):gsub('^%s+',''):gsub('%s+$','') if title=='' then title='Напоминание' end local is_new = (found.fired == 0 and found.interval_sec == nil) found.title=_mh_bridge_u8_to_cp(title) found.interval_sec=math.max(60, math.floor(tonumber(data.interval_sec) or 1800)) found.repeats=math.max(0, math.floor(tonumber(data.repeats) or 0)) if is_new then found.enabled=true end if found.enabled == nil then found.enabled=true end found.warn_enabled=data.warn_enabled == true found.warn_sec=math.max(60, math.floor(tonumber(data.warn_sec) or 300)) found.fired=tonumber(data.reset_count)==1 and 0 or math.max(0, math.floor(tonumber(found.fired) or 0)) if found.enabled then found.next_at=os.time()+found.interval_sec found.warned=false else found.next_at=0 found.warned=false end pcall(_wfn7p) _MH_LOG.query_notify_timers(tonumber(data.req_id) or 0) end function _MH_LOG.toggle_notify_timer(id, enabled) id=tonumber(id) or 0 for _, t in ipairs(settings.notify_timers or {}) do if tonumber(t.id)==id then t.enabled=enabled==true if t.enabled then t.fired=0 t.next_at=os.time()+(tonumber(t.interval_sec) or 1800) t.warned=false else t.next_at=0 t.warned=false end pcall(_wfn7p) return end end end function _MH_LOG.delete_notify_timer(id) id=tonumber(id) or 0 for i=#settings.notify_timers,1,-1 do if tonumber(settings.notify_timers[i].id)==id then table.remove(settings.notify_timers,i) pcall(_wfn7p) return end end end function _MH_LOG.query_notify_history(req_id, page) local hist = _G.mh_notify_log or {} local total_count = #hist local total_pages = math.max(1, math.ceil(total_count / _MH_LOG.page_size)) if page < 1 then page = 1 end if page > total_pages then page = total_pages end local from_i = total_count - (page - 1) * _MH_LOG.page_size local to_i = math.max(1, total_count - page * _MH_LOG.page_size + 1) local items = {} for i = from_i, to_i, -1 do local he = hist[i] if he then table.insert(items, { time = he.time, category = he.category, text = he.text or '', }) end end _MH_LOG.send({ kind = 'notify_history_page', req_id = req_id, page = page, total_pages = total_pages, total_count = total_count, items = items, }) end function _MH_LOG.query_chat_log_setting(req_id) _MH_LOG.send({ kind = 'chat_log_setting', req_id = req_id, enabled = settings.chat_log_enabled ~= false, }) end function _MH_LOG.set_chat_log_setting(value) settings.chat_log_enabled = value == true pcall(_wfn7p) end function _MH_LOG.query_overlay_settings(req_id) local ov = settings.overlay or {} _MH_LOG.send({ kind = 'overlay_settings', req_id = req_id, enabled = ov.enabled ~= false, alpha = ov.alpha or 0.6, lines = ov.lines or 8, day_filter = ov.day_filter or 0, sell_r = ov.sell_r or 0.3, sell_g = ov.sell_g or 0.9, sell_b = ov.sell_b or 0.3, buy_r = ov.buy_r or 0.3, buy_g = ov.buy_g or 0.6, buy_b = ov.buy_b or 1.0, rounding = ov.rounding or 4, }) end function _MH_LOG.set_overlay_setting(key, value) if not settings.overlay then settings.overlay = {} end local ov = settings.overlay local bool_keys = { enabled = true } local num_keys = { alpha=true, lines=true, day_filter=true, sell_r=true, sell_g=true, sell_b=true, buy_r=true, buy_g=true, buy_b=true, rounding=true } if bool_keys[key] then ov[key] = value == true pcall(_wfn7p) elseif num_keys[key] then ov[key] = tonumber(value) pcall(_wfn7p) end end function _MH_LOG.clear_deals() fh_mkt_log = {} _G._mh_mkt_log_ver = (_G._mh_mkt_log_ver or 0) + 1 pcall(_ryb5t) end function _MH_LOG.clear_trades() fh_trade_log = {} pcall(_ryb5t) end function _MH_LOG.clear_chat() fh_session_chat = {} fh_session_chat_ts = {} pcall(_lvn7s) end function _MH_LOG.clear_notify_history() _G.mh_notify_log = {} end function _MH_LOG.tg_test() pcall(mh_tg_send, '[MH] Тест Telegram!', false) end function _MH_LOG.on_query(data) if not _MH_LOG.last_query_by_kind then _MH_LOG.last_query_by_kind = {} end do local _rk = tostring(data.kind) if not (_rk:find('^clear_') or _rk:find('clear_history', 1, true) or _rk == 'tg_test') then _MH_LOG.last_query_by_kind[_rk] = data end end _MH_LOG.last_query = data local kind = data.kind local req_id = tonumber(data.req_id) or 0 if kind == 'deals_query' then local search = data.search if type(search) == 'string' and search ~= '' then search = _mh_bridge_u8_to_cp(search) else search = '' end _MH_LOG.query_deals(req_id, tonumber(data.page) or 1, search, tonumber(data.day_filter) or 0, data.date and _mh_bridge_u8_to_cp(data.date) or '') elseif kind == 'chat_query' then local search = data.search if type(search) == 'string' and search ~= '' then search = _mh_bridge_u8_to_cp(search) else search = '' end _MH_LOG.query_chat(req_id, tonumber(data.page) or 1, tonumber(data.filter) or 0, search) elseif kind == 'chat_dates_query' then _MH_LOG.query_chat_dates(req_id) elseif kind == 'chat_history_query' then local search = data.search if type(search) == 'string' and search ~= '' then search = _mh_bridge_u8_to_cp(search) else search = '' end _MH_LOG.query_chat_history(req_id, tonumber(data.day), tonumber(data.month), tonumber(data.year), tonumber(data.page) or 1, search) elseif kind == 'trades_query' then _MH_LOG.query_trades(req_id, tonumber(data.page) or 1, tonumber(data.day_filter) or 0, data.date and _mh_bridge_u8_to_cp(data.date) or '') elseif kind == 'catch_loot_query' then _MH_LOG.last_catch_q = {req_id = req_id, day_filter = tonumber(data.day_filter) or 0} _MH_LOG.query_catch_loot(req_id, tonumber(data.day_filter) or 0) elseif kind == 'clear_catch_loot' then _MH_LOG.clear_catch_loot() elseif kind == 'tg_get_settings' then _MH_LOG.query_tg_settings(req_id) elseif kind == 'tg_set_setting' then local v = data.value if type(v) == 'string' and v ~= '' then v = _mh_bridge_u8_to_cp(v) end _MH_LOG.set_tg_setting(data.key, v) elseif kind == 'tg_test' then _MH_LOG.tg_test() elseif kind == 'notify_get_settings' then _MH_LOG.query_notify_settings(req_id) elseif kind == 'notify_set_setting' then _MH_LOG.set_notify_setting(data.key, data.value) elseif kind == 'notify_timers_query' then _MH_LOG.query_notify_timers(req_id) elseif kind == 'notify_timer_upsert' then _MH_LOG.upsert_notify_timer(data) elseif kind == 'notify_timer_toggle' then _MH_LOG.toggle_notify_timer(data.id, data.enabled) _MH_LOG.query_notify_timers(req_id) elseif kind == 'notify_timer_delete' then _MH_LOG.delete_notify_timer(data.id) _MH_LOG.query_notify_timers(req_id) elseif kind == 'notify_history_query' then _MH_LOG.query_notify_history(req_id, tonumber(data.page) or 1) elseif kind == 'notify_clear_history' then _MH_LOG.clear_notify_history() elseif kind == 'overlay_get_settings' then _MH_LOG.query_overlay_settings(req_id) elseif kind == 'overlay_set_setting' then _MH_LOG.set_overlay_setting(data.key, data.value) elseif kind == 'chat_log_get_setting' then _MH_LOG.query_chat_log_setting(req_id) elseif kind == 'chat_log_set_setting' then _MH_LOG.set_chat_log_setting(data.value) elseif kind == 'clear_deals' then _MH_LOG.clear_deals() elseif kind == 'clear_trades' then _MH_LOG.clear_trades() elseif kind == 'clear_chat' then _MH_LOG.clear_chat() elseif kind == 'set_tag' then pcall(_mh_shops_handle_bridge_payload, data) end end if _mh_web_jar then _MH_LOG.onaction_prev = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _MH_LOG.bid then if data.type == "WV_STARTED" then _MH_LOG.state.crashed = false _MH_LOG.state.loaded = false _MH_LOG.last_query = nil _MH_LOG.last_query_by_kind = {} elseif data.type == "WV_LOADED" then _MH_LOG.state.loaded = true _MH_LOG.apply_zoom() if _MH_LOG.last_query_by_kind then for _replay_kind, _replay_data in pairs(_MH_LOG.last_query_by_kind) do local ok_lq, err_lq = pcall(_MH_LOG.on_query, _replay_data) if not ok_lq and mh_debug_enabled then mh_notify('[MH Дебаг] {ff5555}_MH_LOG replay ERROR ('.._replay_kind..'): '..tostring(err_lq), 0xFF5555) end end elseif _MH_LOG.last_query then local ok_lq, err_lq = pcall(_MH_LOG.on_query, _MH_LOG.last_query) if not ok_lq and mh_debug_enabled then mh_notify('[MH Дебаг] {ff5555}_MH_LOG last_query replay ERROR: '..tostring(err_lq), 0xFF5555) end end elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _MH_LOG.state.progress then _MH_LOG.state.progress = p end elseif data.type == "WV_DIE" then _MH_LOG.state.crashed = true _MH_LOG.state.created = false pcall(function() _mh_web_jar.deleteBrowser(_MH_LOG.bid) end) elseif data.type == "WV_ANSWER" then local raw_msg = tostring(data.msg or '') local prefix = 'MH_BRIDGE:' local idx = raw_msg:find(prefix, 1, true) if mh_debug_enabled then mh_notify('[MH Дебаг] {aaaaaa}_MH_LOG WV_ANSWER raw='..raw_msg:sub(1,80), 0xFFFFFF) end if idx then local json_part = raw_msg:sub(idx + #prefix) local ok_js, js_obj = pcall(decodeJson, json_part) if ok_js and type(js_obj) == 'table' and js_obj.kind then if mh_debug_enabled then mh_notify('[MH Дебаг] {55ff55}_MH_LOG on_query kind='..tostring(js_obj.kind), 0x55FF55) end local ok_oq, err_oq = pcall(_MH_LOG.on_query, js_obj) if not ok_oq and mh_debug_enabled then mh_notify('[MH Дебаг] {ff5555}_MH_LOG on_query ERROR: '..tostring(err_oq), 0xFF5555) end elseif mh_debug_enabled then mh_notify('[MH Дебаг] {ff5555}_MH_LOG decodeJson FAILED ok='..tostring(ok_js), 0xFF5555) end end end return end if _MH_LOG.onaction_prev then _MH_LOG.onaction_prev(data) end end end function _mh_log_hide() if _mh_web_jar and _MH_LOG.state.created then pcall(function() _mh_web_jar.setClickable(_MH_LOG.bid, false) _mh_web_jar.setVisible(_MH_LOG.bid, false) _mh_web_jar.deleteBrowser(_MH_LOG.bid) end) _MH_LOG.state.created = false _MH_LOG.state.loaded = false _MH_LOG.state.crashed = false _MH_LOG.state.progress = 0 _MH_LOG.state.visible_applied = false _MH_LOG.state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_log_soft_hide() if _mh_web_jar and _MH_LOG.state.created then pcall(function() _mh_web_jar.setClickable(_MH_LOG.bid, false) _mh_web_jar.setVisible(_MH_LOG.bid, false) end) _MH_LOG.state.visible_applied = false end end function _mh_log_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0 webviews \xed\xe5 \xed\xe0\xe9\xe4\xe5\xed\xe0.') imgui.TextWrapped(u8'\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xe5 \xe1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xf3 webviews (\xf2\xe0 \xe6\xe5, \xf7\xf2\xee \xe8\xf1\xef\xee\xeb\xfc\xe7\xf3\xe5\xf2\xf1\xff \xe2 WebMobile), \xf7\xf2\xee\xe1\xfb \xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xf1\xe0\xe9\xf2\xe0 \xf0\xe0\xe1\xee\xf2\xe0\xeb\xe0.') return end if not _MH_LOG.state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_MH_LOG.bid, _MH_LOG.build_url()) end) if ok then _MH_LOG.state.created = true _MH_LOG.state.loaded = false _MH_LOG.state.crashed = false _MH_LOG.state.progress = 0 _MH_LOG.state.visible_applied = false end end if _MH_LOG.state.created then if not _MH_LOG.state.visible_applied then pcall(function() _mh_web_jar.setClickable(_MH_LOG.bid, true) _mh_web_jar.setVisible(_MH_LOG.bid, true) end) _MH_LOG.state.visible_applied = true end end if _MH_LOG.state.loaded and (os.clock() - (_G._mh_catch_push_t or 0)) > 3 then _G._mh_catch_push_t = os.clock() pcall(_G._mh_catch_push) end imgui.Text(fa.CLOCK .. ' ' .. u8'\xcb\xce\xc3') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_log_zoom_out', imgui.ImVec2(30*d, 0)) then _MH_LOG.set_zoom(settings.mh_web.log_zoom - 0.1) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.log_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_log_zoom_in', imgui.ImVec2(30*d, 0)) then _MH_LOG.set_zoom(settings.mh_web.log_zoom + 0.1) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_log_reload') and _mh_web_jar then _MH_LOG.state.loaded = false _MH_LOG.state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_MH_LOG.bid, _MH_LOG.build_url()) end) end imgui.Separator() local cp = imgui.GetCursorPos() local screen_pos = imgui.GetCursorScreenPos() local avail = imgui.GetContentRegionAvail() local px, py = screen_pos.x, screen_pos.y local pw, ph = avail.x, avail.y if _MH_LOG.state.created and _mh_web_jar then local pos = _MH_LOG.state.pos if pos.x ~= px or pos.y ~= py then pcall(function() _mh_web_jar.setPos(_MH_LOG.bid, px, py) end) end if pos.w ~= pw or pos.h ~= ph then pcall(function() _mh_web_jar.setSize(_MH_LOG.bid, pw, ph) end) end _MH_LOG.state.pos = {x = px, y = py, w = pw, h = ph} end if _MH_LOG.state.crashed then imgui.SetCursorPos(imgui.ImVec2(cp.x, cp.y + ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff \xef\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc (crash) :(') imgui.SetCursorPos(imgui.ImVec2(cp.x, cp.y + ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc##mh_log_retry', imgui.ImVec2(160*d, 0)) then _MH_LOG.state.created = false end elseif not _MH_LOG.state.loaded then imgui.SetCursorPos(imgui.ImVec2(cp.x, cp.y + ph/2 - 10)) local pct = _MH_LOG.state.progress if type(pct) == "number" and pct > 0 and pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0... ' .. pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0...') end end imgui.Dummy(imgui.ImVec2(pw, ph)) end _mh_rating = _mh_rating or {} _mh_rating.static_ver = 4 _mh_rating.bid = 9012 _mh_rating.state = _mh_rating.state or { created = false, loaded = false, crashed = false, progress = 0, pos = {x = 0, y = 0, w = 0, h = 0}, } function _mh_rating.build_url() local _srv_id = -1 pcall(function() local _s = ARZ_SERVERS[_G.arz_srv_sel and (_G.arz_srv_sel[0] + 1) or 1] _srv_id = _s and _s.id or -1 end) local _url = "https://va-ta.com/rating/?v=" .. tostring(_mh_rating.static_ver) if _srv_id and _srv_id ~= -1 then _url = _url .. "&server=" .. tostring(_srv_id) end local _z = (settings.mh_web and settings.mh_web.rating_zoom) or 0.8 _url = _url .. "&zoom=" .. tostring(_z) .. "&theme=" .. _mh_theme.query_param() return _url end if not settings.mh_web.rating_zoom then settings.mh_web.rating_zoom = 0.8 end function _mh_rating.push_state() if not (_mh_web_jar and _mh_rating.state.created and _mh_rating.state.loaded) then return end local _nick = _mh_get_live_nick() if not _nick or _nick == '' then return end local ok_j, j = pcall(encodeJson, {my_nick = _to_utf8(_nick)}) if not ok_j or type(j) ~= 'string' then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_mh_rating.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end function _mh_rating.apply_zoom() if not (_mh_web_jar and _mh_rating.state.created) then return end local z = settings.mh_web.rating_zoom pcall(function() _mh_web_jar.executeJS(_mh_rating.bid, "document.documentElement.style.zoom='" .. tostring(z) .. "';") end) _mh_rating.push_state() end function _mh_rating.set_zoom(z) if z < 0.3 then z = 0.3 end if z > 1.5 then z = 1.5 end settings.mh_web.rating_zoom = z pcall(_wfn7p) _mh_rating.apply_zoom() end if _mh_web_jar then _mh_rating._onAction_prev = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _mh_rating.bid then if data.type == "WV_STARTED" then _mh_rating.state.crashed = false _mh_rating.state.loaded = false elseif data.type == "WV_LOADED" then _mh_rating.state.loaded = true _mh_rating.apply_zoom() elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_rating.state.progress then _mh_rating.state.progress = p end elseif data.type == "WV_DIE" then _mh_rating.state.crashed = true _mh_rating.state.created = false pcall(function() _mh_web_jar.deleteBrowser(_mh_rating.bid) end) end return end if _mh_rating._onAction_prev then _mh_rating._onAction_prev(data) end end end function _mh_rating_hide() if _mh_web_jar and _mh_rating.state.created then pcall(function() _mh_web_jar.setClickable(_mh_rating.bid, false) _mh_web_jar.setVisible(_mh_rating.bid, false) _mh_web_jar.deleteBrowser(_mh_rating.bid) end) _mh_rating.state.created = false _mh_rating.state.loaded = false _mh_rating.state.crashed = false _mh_rating.state.progress = 0 _mh_rating.state.visible_applied = false _mh_rating.state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_rating_soft_hide() if _mh_web_jar and _mh_rating.state.created then pcall(function() _mh_web_jar.setClickable(_mh_rating.bid, false) _mh_web_jar.setVisible(_mh_rating.bid, false) end) _mh_rating.state.visible_applied = false end end function _mh_rating_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0 webviews \xed\xe5 \xed\xe0\xe9\xe4\xe5\xed\xe0.') imgui.TextWrapped(u8'\xd3\xf1\xf2\xe0\xed\xee\xe2\xe8\xf2\xe5 \xe1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xf3 webviews (\xf2\xe0 \xe6\xe5, \xf7\xf2\xee \xe8\xf1\xef\xee\xeb\xfc\xe7\xf3\xe5\xf2\xf1\xff \xe2 WebMobile), \xf7\xf2\xee\xe1\xfb \xf1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xf1\xe0\xe9\xf2\xe0 \xf0\xe0\xe1\xee\xf2\xe0\xeb\xe0.') return end if not _G._mh_rating_pull_attempted then _G._mh_rating_pull_attempted = true pcall(_xp_push_self) end if not _mh_rating.state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_mh_rating.bid, _mh_rating.build_url()) end) if ok then _mh_rating.state.created = true _mh_rating.state.loaded = false _mh_rating.state.crashed = false _mh_rating.state.progress = 0 _mh_rating.state.visible_applied = false end end if _mh_rating.state.created then if not _mh_rating.state.visible_applied then pcall(function() _mh_web_jar.setClickable(_mh_rating.bid, true) _mh_web_jar.setVisible(_mh_rating.bid, true) end) _mh_rating.state.visible_applied = true end end imgui.Text(fa.GLOBE .. ' ' .. _cyr5f('\xd0\xc5\xc9\xd2\xc8\xcd\xc3')) imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_rating_zoom_out', imgui.ImVec2(30*d, 0)) then _mh_rating.set_zoom(settings.mh_web.rating_zoom - 0.1) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.rating_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_rating_zoom_in', imgui.ImVec2(30*d, 0)) then _mh_rating.set_zoom(settings.mh_web.rating_zoom + 0.1) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_rating_reload') and _mh_web_jar then _mh_rating.state.loaded = false _mh_rating.state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_mh_rating.bid, _mh_rating.build_url()) end) end imgui.Separator() local _cp = imgui.GetCursorPos() local _screen_pos = imgui.GetCursorScreenPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _screen_pos.x, _screen_pos.y local _pw, _ph = _avail.x, _avail.y if _mh_rating.state.created and _mh_web_jar then local _pos = _mh_rating.state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_mh_rating.bid, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_mh_rating.bid, _pw, _ph) end) end _mh_rating.state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_rating.state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0 \xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff \xef\xe5\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc (crash) :(') imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc##mh_rating_retry', imgui.ImVec2(160*d, 0)) then _mh_rating.state.created = false end elseif not _mh_rating.state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) local _pct = _mh_rating.state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0... ' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0 \xf1\xe0\xe9\xf2\xe0...') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end _mh_theme = _mh_theme or {} _mh_theme.static_ver = 7 _mh_theme.bid = 9013 _mh_theme.state = _mh_theme.state or { created = false, loaded = false, crashed = false, progress = 0, pos = {x = 0, y = 0, w = 0, h = 0}, } function _mh_theme.build_url() local _z = (settings.mh_web and settings.mh_web.theme_zoom) or 0.9 return "https://va-ta.com/theme/?v=" .. tostring(_mh_theme.static_ver) .. "&zoom=" .. tostring(_z) .. "&theme=" .. _mh_theme.query_param() end if not settings.mh_web.theme_zoom then settings.mh_web.theme_zoom = 0.9 end function _mh_theme.build_toast_state() local s = settings.toast or {} local nf = settings.notify_filters or {} return { enabled = s.enabled ~= false, pos_h = s.pos_h or 'right', pos_v = s.pos_v or 'bottom', width = s.width or 320, corner_radius = s.corner_radius or 8, accent_width = s.accent_width or 4, padding_x = s.padding_x or 14, padding_y = s.padding_y or 10, spacing = s.spacing or 8, margin_x = s.margin_x or 18, margin_y = s.margin_y or 18, duration = s.duration or 6.0, anim_speed = s.anim_speed or 10.0, max_visible = s.max_visible or 5, font_scale = s.font_scale or 1.0, bg_r = s.bg_r or 0.08, bg_g = s.bg_g or 0.08, bg_b = s.bg_b or 0.10, bg_a = s.bg_a or 0.90, text_r = s.text_r or 0.94, text_g = s.text_g or 0.94, text_b = s.text_b or 0.96, border_enabled = s.border_enabled == true, border_r = s.border_r or 1.0, border_g = s.border_g or 1.0, border_b = s.border_b or 1.0, border_a = s.border_a or 0.25, border_size = s.border_size or 1.0, filters = { sell_item = nf.sell_item ~= false, buy_item = nf.buy_item ~= false, sell_status = nf.sell_status ~= false, buy_status = nf.buy_status ~= false, watch = nf.watch ~= false, fav = nf.fav ~= false, arb = nf.arb ~= false, reminder = nf.reminder ~= false, other = nf.other ~= false, }, history_max = settings.notify_history_max or 500, } end function _mh_theme.build_window_state() local d = tonumber(settings.general and settings.general.custom_dpi) or 1 local sw = settings.mh_win local w = (sw and sw.w and sw.w / d) or 900 local h = (sw and sw.h and sw.h / d) or 520 if w < 760 then w = 900 end if h < 480 then h = 520 end return { w = w, h = h } end function _mh_theme.build_state() return { kind = 'state', theme = { preset = settings.mh_theme.preset, accent = settings.mh_theme.accent, accent2 = settings.mh_theme.accent2, bg = settings.mh_theme.bg, panel = settings.mh_theme.panel, panel2 = settings.mh_theme.panel2, text = settings.mh_theme.text, text_dim = settings.mh_theme.text_dim, line = settings.mh_theme.line, font_scale = settings.mh_theme.font_scale, panel_alpha = settings.mh_theme.panel_alpha, font_family = settings.mh_theme.font_family, }, toast = _mh_theme.build_toast_state(), window = _mh_theme.build_window_state(), } end function _mh_theme.push_state() if not (_mh_web_jar and _mh_theme.state.created) then return end lua_thread.create(function() wait(0) local ok_j, j = pcall(encodeJson, _mh_theme.build_state()) if not ok_j or type(j) ~= 'string' then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_mh_theme.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end) end function _mh_theme.apply_all() _mh_theme.sync_imgui_accent() _mh_apply_toast_settings() _mh_theme.push_state() end function _mh_theme.handle_bridge_payload(data) if type(data) ~= 'table' or not data.kind then return end if data.kind == 'ready' then pcall(_mh_theme.push_state) return end if data.kind == 'theme_update' and type(data.theme) == 'table' then for k, v in pairs(data.theme) do if settings.mh_theme[k] ~= nil then settings.mh_theme[k] = v end end pcall(_wfn7p) _mh_theme.apply_all() elseif data.kind == 'theme_preset' and data.name and _mh_theme_presets[data.name] then local p = _mh_theme_presets[data.name] settings.mh_theme.preset = data.name settings.mh_theme.accent = p.accent settings.mh_theme.accent2 = p.accent2 settings.mh_theme.bg = p.bg settings.mh_theme.panel = p.panel settings.mh_theme.panel2 = p.panel2 settings.mh_theme.text = p.text settings.mh_theme.text_dim = p.text_dim settings.mh_theme.line = p.line pcall(_wfn7p) _mh_theme.apply_all() elseif data.kind == 'toast_update' and type(data.toast) == 'table' then if not settings.toast then settings.toast = {} end for k, v in pairs(data.toast) do settings.toast[k] = v end pcall(_wfn7p) _mh_apply_toast_settings() _mh_theme.push_state() elseif data.kind == 'toast_filter_update' and data.key then if not settings.notify_filters then settings.notify_filters = {} end settings.notify_filters[data.key] = data.value == true pcall(_wfn7p) _mh_theme.push_state() elseif data.kind == 'toast_history_max' and data.value then settings.notify_history_max = math.floor(tonumber(data.value) or 500) pcall(_wfn7p) elseif data.kind == 'toast_test' then mh_notify('[MH] {aaffaa}Тестовое уведомление — так будут выглядеть плашки', 0xFFFFFF) elseif data.kind == 'window_size_update' then local d = tonumber(settings.general and settings.general.custom_dpi) or 1 local sw = settings.mh_win or {} if data.w then local w = math.max(760, math.min(1600, tonumber(data.w) or 900)) sw.w = w * d end if data.h then local h = math.max(480, math.min(1200, tonumber(data.h) or 520)) sw.h = h * d end if not sw.w then sw.w = 900 * d end 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 pcall(_wfn7p) _mh_theme.push_state() end end if _mh_web_jar then _mh_theme._onAction_prev = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _mh_theme.bid then if data.type == "WV_STARTED" then _mh_theme.state.crashed = false _mh_theme.state.loaded = false elseif data.type == "WV_LOADED" then _mh_theme.state.loaded = true _mh_theme.push_state() elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_theme.state.progress then _mh_theme.state.progress = p end elseif data.type == "WV_DIE" then _mh_theme.state.crashed = true _mh_theme.state.created = false pcall(function() _mh_web_jar.deleteBrowser(_mh_theme.bid) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then pcall(_mh_theme.handle_bridge_payload, _js_obj) end end end return end if _mh_theme._onAction_prev then _mh_theme._onAction_prev(data) end end end function _mh_theme_hide() if _mh_web_jar and _mh_theme.state.created then pcall(function() _mh_web_jar.setClickable(_mh_theme.bid, false) _mh_web_jar.setVisible(_mh_theme.bid, false) _mh_web_jar.deleteBrowser(_mh_theme.bid) end) _mh_theme.state.created = false _mh_theme.state.loaded = false _mh_theme.state.crashed = false _mh_theme.state.progress = 0 _mh_theme.state.visible_applied = false _mh_theme.state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_theme_soft_hide() if _mh_web_jar and _mh_theme.state.created then pcall(function() _mh_web_jar.setClickable(_mh_theme.bid, false) _mh_web_jar.setVisible(_mh_theme.bid, false) end) _mh_theme.state.visible_applied = false end end function _mh_theme_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0\x20\x77\x65\x62\x76\x69\x65\x77\x73\x20\xed\xe5\x20\xed\xe0\xe9\xe4\xe5\xed\xe0\x2e') return end if not _mh_theme.state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_mh_theme.bid, _mh_theme.build_url()) end) if ok then _mh_theme.state.created = true _mh_theme.state.loaded = false _mh_theme.state.crashed = false _mh_theme.state.progress = 0 _mh_theme.state.visible_applied = false end end if _mh_theme.state.created and not _mh_theme.state.visible_applied then pcall(function() _mh_web_jar.setClickable(_mh_theme.bid, true) _mh_web_jar.setVisible(_mh_theme.bid, true) end) _mh_theme.state.visible_applied = true end imgui.Text(fa.SWATCHBOOK .. ' ' .. u8'\xc2\xc8\xc4') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_theme_zoom_out', imgui.ImVec2(30*d, 0)) then settings.mh_web.theme_zoom = math.max(0.3, settings.mh_web.theme_zoom - 0.1) pcall(_wfn7p) pcall(function() _mh_web_jar.executeJS(_mh_theme.bid, "document.documentElement.style.zoom='" .. tostring(settings.mh_web.theme_zoom) .. "';") end) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.theme_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_theme_zoom_in', imgui.ImVec2(30*d, 0)) then settings.mh_web.theme_zoom = math.min(1.5, settings.mh_web.theme_zoom + 0.1) pcall(_wfn7p) pcall(function() _mh_web_jar.executeJS(_mh_theme.bid, "document.documentElement.style.zoom='" .. tostring(settings.mh_web.theme_zoom) .. "';") end) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_theme_reload') and _mh_web_jar then _mh_theme.state.loaded = false _mh_theme.state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_mh_theme.bid, _mh_theme.build_url()) end) end imgui.Separator() local _cp = imgui.GetCursorPos() local _screen_pos = imgui.GetCursorScreenPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _screen_pos.x, _screen_pos.y local _pw, _ph = _avail.x, _avail.y if _mh_theme.state.created and _mh_web_jar then local _pos = _mh_theme.state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_mh_theme.bid, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_mh_theme.bid, _pw, _ph) end) end _mh_theme.state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_theme.state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0\x20\xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff\x20\xef\xe5\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc\x20\x28\x63\x72\x61\x73\x68\x29\x20\x3a\x28') imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc##mh_theme_retry', imgui.ImVec2(160*d, 0)) then _mh_theme.state.created = false end elseif not _mh_theme.state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) local _pct = _mh_theme.state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e\x20' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end _mh_about = _mh_about or {} _mh_about.static_ver = 2 _mh_about.bid = 9014 _mh_about.state = _mh_about.state or { created = false, loaded = false, crashed = false, progress = 0, pos = {x = 0, y = 0, w = 0, h = 0}, } function _mh_about.build_url() local _z = (settings.mh_web and settings.mh_web.about_zoom) or 0.9 return "https://va-ta.com/about/?v=" .. tostring(_mh_about.static_ver) .. "&zoom=" .. tostring(_z) .. "&theme=" .. _mh_theme.query_param() end if not settings.mh_web.about_zoom then settings.mh_web.about_zoom = 0.9 end function _mh_about.build_state() return { kind = 'state', version = thisScript().version or '?', author = 'Shinik_Pupckin', tg_channel = 'https://t.me/shinikmod', tg_author = 'https://t.me/shinik_1', upd_state = _G._mh_upd_state, upd_latest = _G._mh_upd_latest, dl_state = _G._mh_dl_state, dl_progress = _G._mh_dl_progress or 0, dl_err = _G._mh_dl_err or '', } end function _mh_about.push_state() if not (_mh_web_jar and _mh_about.state.created and _mh_about.state.loaded) then return end lua_thread.create(function() wait(0) local ok_j, j = pcall(encodeJson, _mh_about.build_state()) if not ok_j or type(j) ~= 'string' then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_mh_about.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end) end local _mh_about_ticker_active = false local function _mh_about_start_ticker() if _mh_about_ticker_active then return end _mh_about_ticker_active = true lua_thread.create(function() while _mh_about.state.created do wait(1000) _mh_about.push_state() local _s, _d = _G._mh_upd_state, _G._mh_dl_state if _s ~= 'checking' and _d ~= 'downloading' then break end end _mh_about_ticker_active = false end) end function _mh_about.handle_bridge_payload(data) if type(data) ~= 'table' or not data.kind then return end if data.kind == 'about_check_update' then pcall(_G._mh_check_update, true) _mh_about.push_state() _mh_about_start_ticker() elseif data.kind == 'about_download_start' then _G._mh_dl_state = 'confirm' _mh_about.push_state() elseif data.kind == 'about_download_confirm' then pcall(_G._mh_do_download) _mh_about.push_state() _mh_about_start_ticker() elseif data.kind == 'about_download_cancel' then _G._mh_dl_state = nil _mh_about.push_state() elseif data.kind == 'about_download_retry' then _G._mh_dl_state = nil _mh_about.push_state() elseif data.kind == 'about_open_link' and data.url then local _known = { tg_channel = 'https://t.me/shinikmod', tg_author = 'https://t.me/shinik_1', } local _url = _known[data.url] if _url then if _cvh6z() then pcall(gta._Z12AND_OpenLinkPKc, _url) else os.execute('start ' .. _url) end end end end if _mh_web_jar then _mh_about._onAction_prev = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _mh_about.bid then if data.type == "WV_STARTED" then _mh_about.state.crashed = false _mh_about.state.loaded = false elseif data.type == "WV_LOADED" then _mh_about.state.loaded = true pcall(_G._mh_check_update, false) _mh_about.push_state() _mh_about_start_ticker() elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_about.state.progress then _mh_about.state.progress = p end elseif data.type == "WV_DIE" then _mh_about.state.crashed = true _mh_about.state.created = false pcall(function() _mh_web_jar.deleteBrowser(_mh_about.bid) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then pcall(_mh_about.handle_bridge_payload, _js_obj) end end end return end if _mh_about._onAction_prev then _mh_about._onAction_prev(data) end end end function _mh_about_hide() if _mh_web_jar and _mh_about.state.created then pcall(function() _mh_web_jar.setClickable(_mh_about.bid, false) _mh_web_jar.setVisible(_mh_about.bid, false) _mh_web_jar.deleteBrowser(_mh_about.bid) end) _mh_about.state.created = false _mh_about.state.loaded = false _mh_about.state.crashed = false _mh_about.state.progress = 0 _mh_about.state.visible_applied = false _mh_about.state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_about_soft_hide() if _mh_web_jar and _mh_about.state.created then pcall(function() _mh_web_jar.setClickable(_mh_about.bid, false) _mh_web_jar.setVisible(_mh_about.bid, false) end) _mh_about.state.visible_applied = false end end function _mh_about_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0\x20\x77\x65\x62\x76\x69\x65\x77\x73\x20\xed\xe5\x20\xed\xe0\xe9\xe4\xe5\xed\xe0\x2e') return end if not _mh_about.state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_mh_about.bid, _mh_about.build_url()) end) if ok then _mh_about.state.created = true _mh_about.state.loaded = false _mh_about.state.crashed = false _mh_about.state.progress = 0 _mh_about.state.visible_applied = false end end if _mh_about.state.created and not _mh_about.state.visible_applied then pcall(function() _mh_web_jar.setClickable(_mh_about.bid, true) _mh_web_jar.setVisible(_mh_about.bid, true) end) _mh_about.state.visible_applied = true end imgui.Text(fa.CIRCLE_INFO .. ' ' .. u8'\xce\x20\xd1\xca\xd0\xc8\xcf\xd2\xc5') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_about_zoom_out', imgui.ImVec2(30*d, 0)) then settings.mh_web.about_zoom = math.max(0.3, settings.mh_web.about_zoom - 0.1) pcall(_wfn7p) pcall(function() _mh_web_jar.executeJS(_mh_about.bid, "document.documentElement.style.zoom='" .. tostring(settings.mh_web.about_zoom) .. "';") end) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.about_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_about_zoom_in', imgui.ImVec2(30*d, 0)) then settings.mh_web.about_zoom = math.min(1.5, settings.mh_web.about_zoom + 0.1) pcall(_wfn7p) pcall(function() _mh_web_jar.executeJS(_mh_about.bid, "document.documentElement.style.zoom='" .. tostring(settings.mh_web.about_zoom) .. "';") end) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_about_reload') and _mh_web_jar then _mh_about.state.loaded = false _mh_about.state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_mh_about.bid, _mh_about.build_url()) end) end imgui.Separator() local _cp = imgui.GetCursorPos() local _screen_pos = imgui.GetCursorScreenPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _screen_pos.x, _screen_pos.y local _pw, _ph = _avail.x, _avail.y if _mh_about.state.created and _mh_web_jar then local _pos = _mh_about.state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_mh_about.bid, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_mh_about.bid, _pw, _ph) end) end _mh_about.state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_about.state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0\x20\xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff\x20\xef\xe5\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc\x20\x28\x63\x72\x61\x73\x68\x29\x20\x3a\x28') imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc##mh_about_retry', imgui.ImVec2(160*d, 0)) then _mh_about.state.created = false end elseif not _mh_about.state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) local _pct = _mh_about.state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e\x20' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) end _mh_premium = _mh_premium or {} _mh_premium.static_ver = 2 _mh_premium.bid = 9015 _mh_premium.state = _mh_premium.state or { created = false, loaded = false, crashed = false, progress = 0, pos = {x = 0, y = 0, w = 0, h = 0}, } function _mh_premium.build_url() local _z = (settings.mh_web and settings.mh_web.premium_zoom) or 0.9 return "https://va-ta.com/premium/?v=" .. tostring(_mh_premium.static_ver) .. "&zoom=" .. tostring(_z) .. "&theme=" .. _mh_theme.query_param() end if not settings.mh_web.premium_zoom then settings.mh_web.premium_zoom = 0.9 end function _mh_premium_current_server_name() local ok, idx = pcall(_mpf7d) if ok and tonumber(idx or 0) and idx > 0 then local row = ARZ_SERVERS[idx + 1] if row and row.name then return row.name end end local boot = tonumber(_G.mh_boot_srv_idx or 0) or 0 if boot > 0 then local row = ARZ_SERVERS[boot + 1] if row and row.name then return row.name end end if _G.arz_srv_sel then local sel = tonumber(_G.arz_srv_sel[0] or 0) or 0 local row = ARZ_SERVERS[sel + 1] if row and row.id and row.id ~= -1 then return row.name end end return '' end function _mh_premium.build_state() return { kind = 'state', activated = settings.premium and settings.premium.activated == true, user = settings.premium and settings.premium.user or '', nick = settings.premium and settings.premium.nick or '', key = settings.premium and settings.premium.key or '', expires = settings.premium and settings.premium.expires or '', server = _mh_premium_current_server_name(), server_id = (function() local ok, idx = pcall(_mpf7d) if ok and tonumber(idx or 0) and idx > 0 then return (ARZ_SERVERS[idx + 1] or {}).id or -1 end local boot = tonumber(_G.mh_boot_srv_idx or 0) or 0 if boot > 0 then return (ARZ_SERVERS[boot + 1] or {}).id or -1 end if _G.arz_srv_sel then local sel = tonumber(_G.arz_srv_sel[0] or 0) or 0 return (ARZ_SERVERS[sel + 1] or {}).id or -1 end return -1 end)(), checking = _prem_checking == true, check_status = _prem_check_status or '', } end function _mh_premium.push_state() if not (_mh_web_jar and _mh_premium.state.created and _mh_premium.state.loaded) then return end lua_thread.create(function() wait(0) local ok_j, j = pcall(encodeJson, _mh_premium.build_state()) if not ok_j or type(j) ~= 'string' then return end local ok_b64, b64 = pcall(function() return require('basexx').to_base64(j) end) if not ok_b64 or not b64 then return end pcall(function() _mh_web_jar.executeJS(_mh_premium.bid, "window.mhReceiveState && window.mhReceiveState('" .. b64 .. "');") end) end) end _mh_premium_ticker_active = false function _mh_premium_start_ticker() if _mh_premium_ticker_active then return end _mh_premium_ticker_active = true lua_thread.create(function() while _mh_premium.state.created do wait(500) _mh_premium.push_state() if not _prem_checking then break end end _mh_premium_ticker_active = false end) end function _mh_premium.handle_bridge_payload(data) if type(data) ~= 'table' or not data.kind then return end if data.kind == 'premium_activate' and data.key and tostring(data.key) ~= '' then local key_entered = tostring(data.key):gsub('^%s+',''):gsub('%s+$','') _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) end _mh_premium.push_state() end) _mh_premium_start_ticker() _mh_premium.push_state() elseif data.kind == 'premium_deactivate' then settings.premium.activated = false settings.premium.key = '' settings.premium.user = '' _mh_reset_prem() _wfn7p() _mh_premium.push_state() elseif data.kind == 'premium_open_link' and data.url then local _known = { tg_buy = 'https://t.me/MHARIZONAbot' } local _url = _known[data.url] if _url then if _cvh6z() then pcall(gta._Z12AND_OpenLinkPKc, _url) else os.execute('start ' .. _url) end end end end if _mh_web_jar then _mh_premium._onAction_prev = _mh_web_jar.onAction function _mh_web_jar.onAction(data) if not data then return end if data.browserid == _mh_premium.bid then if data.type == "WV_STARTED" then _mh_premium.state.crashed = false _mh_premium.state.loaded = false elseif data.type == "WV_LOADED" then _mh_premium.state.loaded = true _mh_premium.push_state() elseif data.type == "WV_PROGRESS" then local p = tonumber(data.msg) if p and p > _mh_premium.state.progress then _mh_premium.state.progress = p end elseif data.type == "WV_DIE" then _mh_premium.state.crashed = true _mh_premium.state.created = false pcall(function() _mh_web_jar.deleteBrowser(_mh_premium.bid) end) elseif data.type == "WV_ANSWER" then local _raw_msg = tostring(data.msg or '') local _prefix = 'MH_BRIDGE:' local _idx = _raw_msg:find(_prefix, 1, true) if _idx then local _json_part = _raw_msg:sub(_idx + #_prefix) local _ok_js, _js_obj = pcall(decodeJson, _json_part) if _ok_js and type(_js_obj) == 'table' and _js_obj.kind then pcall(_mh_premium.handle_bridge_payload, _js_obj) end end end return end if _mh_premium._onAction_prev then _mh_premium._onAction_prev(data) end end end function _mh_premium_hide() if _mh_web_jar and _mh_premium.state.created then pcall(function() _mh_web_jar.setClickable(_mh_premium.bid, false) _mh_web_jar.setVisible(_mh_premium.bid, false) _mh_web_jar.deleteBrowser(_mh_premium.bid) end) _mh_premium.state.created = false _mh_premium.state.loaded = false _mh_premium.state.crashed = false _mh_premium.state.progress = 0 _mh_premium.state.visible_applied = false _mh_premium.state.pos = {x = -99999, y = -99999, w = 0, h = 0} end end function _mh_premium_soft_hide() if _mh_web_jar and _mh_premium.state.created then pcall(function() _mh_web_jar.setClickable(_mh_premium.bid, false) _mh_web_jar.setVisible(_mh_premium.bid, false) end) _mh_premium.state.visible_applied = false end end function _mh_premium_tab_render() local d = settings.general.custom_dpi or 1 if not _mh_web_jar then imgui.Spacing() imgui.TextColored(imgui.ImVec4(1, 0.35, 0.35, 1), u8'\xc1\xe8\xe1\xeb\xe8\xee\xf2\xe5\xea\xe0\x20\x77\x65\x62\x76\x69\x65\x77\x73\x20\xed\xe5\x20\xed\xe0\xe9\xe4\xe5\xed\xe0\x2e') return end if not _mh_premium.state.created then local ok = pcall(function() _mh_web_jar.createBrowser(_mh_premium.bid, _mh_premium.build_url()) end) if ok then _mh_premium.state.created = true _mh_premium.state.loaded = false _mh_premium.state.crashed = false _mh_premium.state.progress = 0 _mh_premium.state.visible_applied = false end end if _mh_premium.state.created and not _mh_premium.state.visible_applied then pcall(function() _mh_web_jar.setClickable(_mh_premium.bid, true) _mh_web_jar.setVisible(_mh_premium.bid, true) end) _mh_premium.state.visible_applied = true end imgui.Text(_ic_star .. ' ' .. u8'\xcf\xd0\xc5\xcc\xc8\xd3\xcc') imgui.SameLine(imgui.GetWindowContentRegionWidth() - 154*d) if imgui.Button(fa.MAGNIFYING_GLASS_MINUS .. '##mh_premium_zoom_out', imgui.ImVec2(30*d, 0)) then settings.mh_web.premium_zoom = math.max(0.3, settings.mh_web.premium_zoom - 0.1) pcall(_wfn7p) pcall(function() _mh_web_jar.executeJS(_mh_premium.bid, "document.documentElement.style.zoom='" .. tostring(settings.mh_web.premium_zoom) .. "';") end) end imgui.SameLine() imgui.Text(math.floor(settings.mh_web.premium_zoom * 100) .. '%%') imgui.SameLine() if imgui.Button(fa.MAGNIFYING_GLASS_PLUS .. '##mh_premium_zoom_in', imgui.ImVec2(30*d, 0)) then settings.mh_web.premium_zoom = math.min(1.5, settings.mh_web.premium_zoom + 0.1) pcall(_wfn7p) pcall(function() _mh_web_jar.executeJS(_mh_premium.bid, "document.documentElement.style.zoom='" .. tostring(settings.mh_web.premium_zoom) .. "';") end) end imgui.SameLine() if imgui.Button(fa.ROTATE_RIGHT .. '##mh_premium_reload') and _mh_web_jar then _mh_premium.state.loaded = false _mh_premium.state.progress = 0 pcall(function() _mh_web_jar.changeUrl(_mh_premium.bid, _mh_premium.build_url()) end) end imgui.Separator() local _cp = imgui.GetCursorPos() local _screen_pos = imgui.GetCursorScreenPos() local _avail = imgui.GetContentRegionAvail() local _px, _py = _screen_pos.x, _screen_pos.y local _pw, _ph = _avail.x, _avail.y if _mh_premium.state.created and _mh_web_jar then local _pos = _mh_premium.state.pos if _pos.x ~= _px or _pos.y ~= _py then pcall(function() _mh_web_jar.setPos(_mh_premium.bid, _px, _py) end) end if _pos.w ~= _pw or _pos.h ~= _ph then pcall(function() _mh_web_jar.setSize(_mh_premium.bid, _pw, _ph) end) end _mh_premium.state.pos = {x = _px, y = _py, w = _pw, h = _ph} end if _mh_premium.state.crashed then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) _mh_web_center_text(u8'\xd1\xf2\xf0\xe0\xed\xe8\xf6\xe0\x20\xef\xf0\xe8\xeb\xee\xe6\xe5\xed\xe8\xff\x20\xef\xe5\xf0\xe5\xf0\xe2\xe0\xeb\xe0\xf1\xfc\x20\x28\x63\x72\x61\x73\x68\x29\x20\x3a\x28') imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 + 12)) if imgui.Button(fa.ROTATE_RIGHT .. ' ' .. u8'\xcf\xe5\xf0\xe5\xe7\xe0\xef\xf3\xf1\xf2\xe8\xf2\xfc##mh_premium_retry', imgui.ImVec2(160*d, 0)) then _mh_premium.state.created = false end elseif not _mh_premium.state.loaded then imgui.SetCursorPos(imgui.ImVec2(_cp.x, _cp.y + _ph/2 - 10)) local _pct = _mh_premium.state.progress if type(_pct) == "number" and _pct > 0 and _pct < 100 then _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e\x20' .. _pct .. '%%') else _mh_web_center_text(u8'\xc7\xe0\xe3\xf0\xf3\xe7\xea\xe0\x20\xf1\xe0\xe9\xf2\xe0\x2e\x2e\x2e') end end imgui.Dummy(imgui.ImVec2(_pw, _ph)) 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 local _market_tabbar_flags = 0 pcall(function() _market_tabbar_flags = imgui.TabBarFlags.FittingPolicyScroll end) if imgui.BeginTabBar('##market_subtabs', _market_tabbar_flags) then if imgui.BeginTabItem(IC.chrtl..' '..u8'Рынок##sub_market_web') then _G._mh_on_market_subtab = true _mh_web_tab_render() imgui.EndTabItem() end if false and imgui.BeginTabItem(IC.chrtl..' '..u8'Рынок(старое)##sub_market') then if true 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(IC.circs..' '..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 = { IC.cald..' '..u8'\xcf\xce\xd1\xcb\xc5\xc4\xcd\xc8\xc5##s0', IC.calw..' '..u8'30 \xc4\xcd\xc5\xc9##s1', IC.tag..' '..u8'\xd6\xc5\xcd\xc0##s2', fa.ARROW_DOWN_A_Z..' '..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(IC.flt..' '.._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 do local _cw_total = imgui.GetWindowContentRegionWidth() local _cw_nm = math.floor(_cw_total * 0.30) local _cw_avg = math.floor(_cw_total * 0.15) local _cw_min = math.floor(_cw_total * 0.15) local _cw_max = math.floor(_cw_total * 0.15) local _cw_qty = math.floor(_cw_total * 0.11) local _cw_trend = math.floor(_cw_total) - _cw_nm - _cw_avg - _cw_min - _cw_max - _cw_qty _G._cp_hdr_cws = {_cw_nm, _cw_avg, _cw_min, _cw_max, _cw_qty, _cw_trend} imgui.Columns(6, '##cphdr', false) 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, IC.tag..u8' \xd2\xce\xc2\xc0\xd0'); imgui.NextColumn() imgui.TextColored(hca, IC.chrtl..u8' \xd1\xd0\xc5\xc4\xcd\xdf\xdf'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.35,0.90,0.40,1), IC.dn..u8' \xcc\xc8\xcd'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.9,0.4,0.3,1), IC.up..u8' \xcc\xc0\xca\xd1'); imgui.NextColumn() imgui.TextColored(hc, IC.boxes..u8' \xd8\xd2.'); imgui.NextColumn() imgui.TextColored(hc, IC.chrts..' '.._cyr5f(' \xd2\xd0\xc5\xcd\xc4')); imgui.NextColumn() imgui.Columns(1) imgui.Separator() end if imgui.BeginChild('##cp_list', imgui.ImVec2(-1, list_h), true) then _dpn1w() local _hcw = _G._cp_hdr_cws or {} imgui.Columns(6, '##cphdr', false) imgui.SetColumnWidth(0, _hcw[1] or 0) imgui.SetColumnWidth(1, _hcw[2] or 0) imgui.SetColumnWidth(2, _hcw[3] or 0) imgui.SetColumnWidth(3, _hcw[4] or 0) imgui.SetColumnWidth(4, _hcw[5] or 0) imgui.SetColumnWidth(5, _hcw[6] or 0) 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 = (_G._cp_hdr_cws and _G._cp_hdr_cws[1] or 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 = { IC.dn..' '..u8'Цена(уб.)##as0', IC.up..' '..u8'Цена(воз.)##as1', fa.ARROW_DOWN_A_Z..' '..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 do 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, IC.car..u8' Автомобиль'); imgui.NextColumn() imgui.TextColored(hc, IC.tag..u8' Цена $'); imgui.NextColumn() imgui.TextColored(hc, IC.dn..u8' Мин'); imgui.NextColumn() imgui.TextColored(hc, IC.up..u8' Макс'); imgui.NextColumn() imgui.TextColored(hc, IC.rot..u8' Обнов.'); imgui.NextColumn() imgui.Columns(1) imgui.Separator() end 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) 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(fa.SERVER..' '.._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) local _arb_s2s_cws, _arb_reg_cws do 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 _arb_s2s_cws = {_cw_nm,_cw_s,_cw_r30,_cw_b,_cw_m,_cw_pct} 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, IC.tag..' '.._cyr5f(' Товар')); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.4,0.95,0.4,0.9), IC.cart..' '.._cyr5f('Продаёт')); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.9,0.8,0.4,0.9), IC.chrtl..' '.._cyr5f('Рынок 30д')); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.4,0.7,1,0.9), IC.store..' '.._cyr5f('Скупает')); imgui.NextColumn() imgui.TextColored(hc, IC.chrts..' '.._cyr5f('Маржа')); imgui.NextColumn() imgui.TextColored(hc, _cyr5f('%%')); imgui.NextColumn() 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} _arb_reg_cws = cws for ci,cw2 in ipairs(cws) do imgui.SetColumnWidth(ci-1,cw2) end imgui.TextColored(hc,IC.tag..' '.._cyr5f(' Товар')); imgui.NextColumn() imgui.TextColored(hc,IC.lyr..' '.._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),IC.cart..' '.._cyr5f(' Скуп. $')); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.6,0.85,1,0.9),IC.chrtl..' '.._cyr5f(' Рынок $')); imgui.NextColumn() else imgui.TextColored(imgui.ImVec4(0.6,0.85,1,0.9),IC.store..' '.._cyr5f(' Лавка $')); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.4,0.95,0.4,0.9),IC.chrtl..' '.._cyr5f(' Рынок $')); imgui.NextColumn() end end imgui.TextColored(hc,IC.chrts..' '.._cyr5f(' Маржа $')); imgui.NextColumn() imgui.TextColored(hc,_cyr5f(' %%')); imgui.NextColumn() imgui.TextColored(hc,fa.USER..' '.._cyr5f(' Овнер')); imgui.NextColumn() imgui.TextColored(hc,IC.phone); imgui.NextColumn() imgui.TextColored(hc,IC.gps); imgui.NextColumn() imgui.Columns(1) end imgui.Separator() end if imgui.BeginChild('##arb_list', imgui.ImVec2(-1,list_h), true) then _dpn1w() if _is_s2s then imgui.Columns(6,'##s2s_hdr',false) local w = _arb_s2s_cws or {} for ci=1,6 do imgui.SetColumnWidth(ci-1, w[ci] or 0) end 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) do local w2 = _arb_reg_cws or {} for ci=1,9 do imgui.SetColumnWidth(ci-1, w2[ci] or 0) end end 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), IC.cld..' '..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 = { IC.chrtl..' '..u8'По умолч.##as0', IC.cart..' '..u8'Продаёт##as1', IC.store..' '..u8'Скупает##as2', fa.ARROW_DOWN_A_Z..' '..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.18, cw_arz*0.07, cw_arz*0.24, cw_arz*0.07, cw_arz*0.24, 0}; cwI[6] = cw_arz - cwI[1] - cwI[2] - cwI[3] - cwI[4] - cwI[5] for ci, v in ipairs(cwI) do imgui.SetColumnWidth(ci-1, v) end imgui.TextColored(hc2, IC.tag..u8' Товар'); imgui.NextColumn() imgui.TextColored(hc2, IC.lyr..u8' Тип'); imgui.NextColumn() imgui.TextColored(hc2, IC.chrtl..u8' Цена'); imgui.NextColumn() imgui.TextColored(hc2, IC.boxes..u8' Кол-во'); imgui.NextColumn() imgui.TextColored(hc2, IC.store..u8' Лавка'); imgui.NextColumn() imgui.TextColored(hc2, IC.gps..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, 0}; cw6[6] = cw_arz - cw6[1] - cw6[2] - cw6[3] - cw6[4] - cw6[5] for ci, v in ipairs(cw6) do imgui.SetColumnWidth(ci-1, v) end imgui.TextColored(hc2, fa.USER..u8' Владелец'); imgui.NextColumn() imgui.TextColored(hc2, fa.SERVER..u8' Сервер'); imgui.NextColumn() imgui.TextColored(hc2, fa.ID_BADGE..u8' ID'); imgui.NextColumn() imgui.TextColored(hc2, IC.cart..u8' Продаёт'); imgui.NextColumn() imgui.TextColored(hc2, IC.store..u8' Скупает'); imgui.NextColumn() imgui.TextColored(hc2, fa.PLAY..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.18, cw_arz*0.07, cw_arz*0.24, cw_arz*0.07, cw_arz*0.24, 0}; cwI[6] = cw_arz - cwI[1] - cwI[2] - cwI[3] - cwI[4] - cwI[5] 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, 0}; cw6[6] = cw_arz - cw6[1] - cw6[2] - cw6[3] - cw6[4] - cw6[5] 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 false and 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 settings.active_buy_preset = fh_ab_preset_idx 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 = {} _G._mh_other_shops_idx = nil _G._mh_other_shops_idx_built = false _mh_shop_bump() _wfn7p() _G.os_selected_key = nil mh_notify('[MH] {ff4444}Чужие лавки очищены.', 0xFFFFFF) end imgui.PopStyleColor() imgui.EndTabItem() end if false and imgui.BeginTabItem(IC.star .. ' ' .. _cyr5f('Подборка') .. '##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 if imgui.BeginTabItem(IC.coin .. ' ' .. _cyr5f('Подбор') .. '##sub_pick_web') then _G._mh_on_pick_subtab = true _mh_pick_tab_render() imgui.EndTabItem() end if false and imgui.BeginTabItem(IC.coin .. ' ' .. _cyr5f('Подбор') .. '##sub_pick') then 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 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 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 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 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 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 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) 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 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 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 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] 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 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')) 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) 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 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)) 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(IC.coin .. ' ' .. _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(IC.chrts .. ' ' .. _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' )) 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() local _c0 = 0.28 local _c1 = 0.16 local _c2 = 0.15 local _c3 = 0.15 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(IC.tag..' '.._cyr5f('\xcd\xe0\xe7\xe2\xe0\xed\xe8\xe5')); imgui.NextColumn() imgui.Text(IC.store..' '.._cyr5f('\xcb\xe0\xe2\xea\xe0')); imgui.NextColumn() imgui.Text(IC.cart..' '.._cyr5f('\xd6\xe5\xed\xe0\x20\xe2\x20\xeb\xe0\xe2\xea\xe5')); imgui.NextColumn() imgui.Text(IC.chrtl..' '.._cyr5f('\xd1\xf0\xe5\xe4\xed\xff\xff\x20\xf6\xe5\xed\xe0')); imgui.NextColumn() imgui.Text(IC.chrts..' '.._cyr5f('\xd2\xf0\xe5\xed\xe4') .. ' / %'); imgui.NextColumn() imgui.PopStyleColor() imgui.Separator() 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 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() 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 false and _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 bw2 = left_w local bw2r = right_w 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(bw2,0)) then _vcz9h() end imgui.PopStyleColor(2) 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(bw2r,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(bw2r,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 local _as_en_cnt = 0 for _, asp in ipairs(fh_lv_autosell_preset) do if asp.enabled ~= false then total_sell = total_sell + (asp.price or 0) * (asp.qty or 1) _as_en_cnt = _as_en_cnt + 1 end end imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), _cyr5f(' Итого выкладки: $' .. _kcr3y(total_sell) .. ' ')) imgui.SameLine(0,0) imgui.TextColored(imgui.ImVec4(0.55,0.75,0.55,1), '('.._as_en_cnt..'/'..#fh_lv_autosell_preset..')') 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), IC.boxes..' '.._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 local _as_inv_ver = tostring(#fh_lv_inventory) .. '|' .. tostring(#fh_lv_autosell_preset) if _G._as_inv_lbl_ver ~= _as_inv_ver then _G._as_inv_lbl_ver = _as_inv_ver local _in_preset = {} for _, p in ipairs(fh_lv_autosell_preset) do _in_preset[p.name] = true end _G._as_inv_in_preset = _in_preset local _lbls = {} for _, inv in ipairs(fh_lv_inventory) do _lbls[inv.name] = _cyr5f(inv.name..' '..inv.count..' шт.') end _G._as_inv_lbls = _lbls end local _in_preset = _G._as_inv_in_preset or {} local _lbls = _G._as_inv_lbls or {} 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 = _in_preset[inv.name] or false local lbl = _lbls[inv.name] or _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 settings._as_undo_snap = 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() imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.10,0.22,0.32,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.16,0.34,0.48,_G._mh_wa or 1)) if imgui.Button(IC.rot..' '.._cyr5f('Импортировать из файла##impp'), imgui.ImVec2(-1,0)) then _G._import_list_sell = _mh_list_presets('sell') _G._show_import_sell = not _G._show_import_sell end imgui.PopStyleColor(2) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Импортировать пресет из файла (MarketHelper/Preset)')) end if _G._show_import_sell then imgui.Separator() local _il = _G._import_list_sell or {} if #_il == 0 then imgui.TextDisabled(_cyr5f(' Файлов пресетов продажи не найдено.')) else imgui.TextColored(imgui.ImVec4(0.6,0.85,1,1), IC.fimp..' '.._cyr5f('Выберите файл для импорта:')) for _ii, _ie in ipairs(_il) do local _lbl = u8(_ie.name)..' ('..tostring(_ie.count)..') '.._ie.created if imgui.Selectable(_lbl..'##impsel'.._ii) then local _ok3, _nm3, _items3 = _mh_import_preset_file(_ie.path) if _ok3 then if not settings.presets then settings.presets = {} end table.insert(settings.presets, {name = _nm3, items = _items3 or {}}) fh_active_preset_idx = #settings.presets settings.active_preset = fh_active_preset_idx fh_lv_autosell_preset = settings.presets[fh_active_preset_idx].items _G.as_price_buf = nil; _G.as_qty_buf = nil settings._as_undo_snap = nil _wfn7p() mh_notify('[MH] {aaffaa}Пресет "'.._nm3..'" импортирован ('..tostring(#(_items3 or {}))..' тов.).', 0xFFFFFF) else mh_notify('[MH] {ff6666}Не удалось импортировать пресет ('..tostring(_nm3)..').', 0xFFFFFF) end _G._show_import_sell = false end end end imgui.Separator() end 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 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(cw_p * 0.55) 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 imgui.SameLine(0, 4*d) imgui.PushStyleColor(imgui.Col.Button, _G.as_sort_mode > 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(sort_labels[_G.as_sort_mode+1]..'##assort', imgui.ImVec2(-1, 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.PopStyleColor() do local _bw3s = (cw_p - 8*d - 8*d) / 3 local _as_snap = settings._as_undo_snap local _as_has_diff = _as_snap ~= nil 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('Сохранить##astgt_all'), imgui.ImVec2(_bw3s, 0)) then settings._as_undo_snap = _mh_deep_copy_list(fh_lv_autosell_preset) settings.autosell_preset = fh_lv_autosell_preset if settings.presets and settings.presets[fh_active_preset_idx] then settings.presets[fh_active_preset_idx].items = fh_lv_autosell_preset end _wfn7p() mh_notify('[MH] {aaddff}Пресет сохранён (' ..tostring(#fh_lv_autosell_preset)..' тов.) — в файл настроек и в память для восстановления.', 0xFFFFFF) end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Сохранить снимок всего пресета целиком (кол-во, цены, состав) для восстановления')) end imgui.SameLine(0, 4*d) if _as_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('Восстановить##asrst_all'), imgui.ImVec2(_bw3s, 0)) then if _as_has_diff then fh_lv_autosell_preset = _mh_deep_copy_list(_as_snap) local _ap2 = settings.presets and settings.presets[fh_active_preset_idx] if _ap2 then _ap2.items = fh_lv_autosell_preset end settings.autosell_preset = fh_lv_autosell_preset _G.as_qty_buf = nil _G.as_price_buf = nil _G.as_pos_buf = nil _G.as_en_buf = nil _G._as_diff_ver = nil _wfn7p() _tcv8f() mh_notify('[MH] {aaffaa}Пресет восстановлен из снимка (' ..tostring(#fh_lv_autosell_preset)..' тов.).', 0xFFFFFF) end end imgui.PopStyleColor(3) if imgui.IsItemHovered() then if _as_has_diff then imgui.SetTooltip(_cyr5f('Восстановить весь пресет из сохранённого снимка (' ..tostring(#_as_snap)..' тов.)')) else imgui.SetTooltip(_cyr5f('Снимок ещё не сохранён')) end end imgui.SameLine(0, 4*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.10,0.10,0.10,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.20,0.20,0.20,_G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.7,0.7,0.7,1)) if imgui.Button(IC.save..' '.._cyr5f('В файл (поделиться)##asexport'), imgui.ImVec2(_bw3s, 0)) then local _pname = (settings.presets and settings.presets[fh_active_preset_idx] and settings.presets[fh_active_preset_idx].name) or ('Пресет '..tostring(fh_active_preset_idx)) local _ok, _res = _mh_export_preset('sell', _pname, fh_lv_autosell_preset) if _ok then mh_notify('[MH] {aaffaa}Пресет сохранён в файл: '.._res, 0xFFFFFF) else mh_notify('[MH] {ff6666}Не удалось сохранить пресет в файл ('..tostring(_res)..').', 0xFFFFFF) end end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Сохранить пресет как файл в MarketHelper/Preset — можно передать другому игроку')) end if _as_has_diff then imgui.TextColored(imgui.ImVec4(1,0.65,0.15,1), '('..tostring(#_as_snap)..')') end end local _bw2s = (cw_p - 8*d) / 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('Макс все##asmaxall'), imgui.ImVec2(_bw2s, 0)) then if #fh_lv_inventory == 0 then if fh_lv_inv_scanning then mh_notify('[MH] {ffaa00}Скан инвентаря уже идёт...', 0xFFFFFF) else mh_notify('[MH] {ffaa00}Инвентарь не сканирован - нажмите "Скан инвентаря" сначала.', 0xFFFFFF) end else local updated = 0 local restored = 0 for _maxasi, _maxasp in ipairs(fh_lv_autosell_preset) do for _, _inv in ipairs(fh_lv_inventory) do if fh_lower(_inv.name) == fh_lower(_maxasp.name) and _inv.count > 0 then local _was_zero = (_maxasp.qty or 0) <= 0 _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 if _was_zero and _maxasp.qty > 0 and _maxasp.enabled == false then _maxasp.enabled = true restored = restored + 1 if _G.as_en_buf and _G.as_en_buf[_maxasi] then _G.as_en_buf[_maxasi] = nil end end updated = updated + 1 break end end end _G.as_qty_buf = nil _tcv8f() local _msg = '[MH] {00cc00}Макс все: обновлено ' .. updated .. ' товаров.' if restored > 0 then _msg = _msg .. ' Включено обратно: ' .. restored .. '.' end if updated == 0 and #fh_lv_autosell_preset > 0 then _msg = '[MH] {ffaa44}Макс все: ни одной позиции пресета не найдено в инвентаре.' end mh_notify(_msg, 0xFFFFFF) end end imgui.PopStyleColor(2) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Установить кол-во из инвентаря\nдля всех товаров пресета сразу')) end imgui.SameLine(0, 4*d) local _as_inv_ver_key2 = tostring(#fh_lv_inventory) .. '|' .. tostring(#fh_lv_autosell_preset) if _G._as_inv_names_ver2 ~= _as_inv_ver_key2 then _G._as_inv_names_ver2 = _as_inv_ver_key2 _G._as_inv_names2 = {} for _, _iv in ipairs(fh_lv_inventory) do _G._as_inv_names2[_iv.name] = (_iv.count or 0) end _G._as_can_clean2 = false for _, _pp in ipairs(fh_lv_autosell_preset) do if (_pp.qty or 0) == 0 or (_G._as_inv_names2[_pp.name] == nil and #fh_lv_inventory > 0) then _G._as_can_clean2 = true; break end end end local _as_can_clean2 = _G._as_can_clean2 or false if _as_can_clean2 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(_bw2s, 0)) then local _new_preset2 = {} local _inv_names2 = _G._as_inv_names2 or {} for _, _pp in ipairs(fh_lv_autosell_preset) do local _inv_qty2 = _inv_names2[_pp.name] local _keep2 = (_pp.qty or 0) > 0 if _keep2 and #fh_lv_inventory > 0 and _inv_qty2 == nil then _keep2 = false end if _keep2 then table.insert(_new_preset2, _pp) end end fh_lv_autosell_preset = _new_preset2 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(_bw2s, 0)) imgui.PopStyleColor(3) end imgui.Separator() 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 if asp.enabled == nil then asp.enabled = true end if (asp.qty or 0) <= 0 and asp.enabled then asp.enabled = false _tcv8f() end if not _G.as_en_buf then _G.as_en_buf = {} end if not _G.as_en_buf[asi] then _G.as_en_buf[asi] = imgui.new.bool(asp.enabled) end local _as_en_cb = _G.as_en_buf[asi] _as_en_cb[0] = asp.enabled if imgui.Checkbox('##asp_en'..asi, _as_en_cb) then asp.enabled = _as_en_cb[0] _tcv8f() end if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Выставлять этот товар при автопродаже')) end imgui.SameLine(0,4*d) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1,1,1,1)) local _asp_nm_str = _cyr5f(asp.name) local _asp_nm_w = imgui.CalcTextSize(_asp_nm_str).x if imgui.Selectable(_asp_nm_str..'##asp_nm'..asi, false, 0, imgui.ImVec2(_asp_nm_w,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 _lbl_price_w = imgui.CalcTextSize(_cyr5f('Цена:')).x + 4*d local _lbl_qty_w = imgui.CalcTextSize(_cyr5f('Шт.')).x + 4*d local _qty_extra = imgui.CalcTextSize('9999').x local qty_w2 = 50*d + _qty_extra local price_w2 = fw - qty_w2 - x_w2 - _lbl_price_w - _lbl_qty_w - 6*d if price_w2 < 40*d then price_w2 = 40*d end imgui.TextDisabled(_cyr5f('Цена:')) imgui.SameLine(0,4*d) 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,8*d) imgui.TextDisabled(_cyr5f('Шт.')) imgui.SameLine(0,4*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() if not _G.as_pos_buf then _G.as_pos_buf = {} end if not _G.as_pos_buf[asi] then _G.as_pos_buf[asi] = imgui.new.char[8](tostring(asi)) end local _as_pos_w = imgui.CalcTextSize('9999').x + 16*d local _as_lbl_w = imgui.CalcTextSize(_cyr5f('Позиция:')).x imgui.TextDisabled(_cyr5f('Позиция:')) imgui.SameLine(0,4*d) imgui.PushItemWidth(_as_pos_w) if imgui.InputText('##aspos'..asi, _G.as_pos_buf[asi], 8, imgui.InputTextFlags.EnterReturnsTrue) then local _target = tonumber(ffi.string(_G.as_pos_buf[asi])) local _n_tot = #fh_lv_autosell_preset if _target then _target = math.floor(_target) if _target < 1 then _target = 1 end if _target > _n_tot then _target = _n_tot end if _target ~= asi then local _moved = table.remove(fh_lv_autosell_preset, asi) table.insert(fh_lv_autosell_preset, _target, _moved) _G.as_price_buf=nil; _G.as_qty_buf=nil; _G.as_pos_buf=nil; _G.as_en_buf=nil local _ap2 = settings.presets and settings.presets[fh_active_preset_idx] if _ap2 then _ap2.items = fh_lv_autosell_preset end _wfn7p() mh_notify('[MH] '.._moved.name..': позиция -> '..tostring(_target), 0xAAFFAA) end end end imgui.PopItemWidth() if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Введи номер позиции - товар встанет на это место')) end imgui.SameLine(0,2*d) local _has_sg = _asp_today_cp ~= nil local _has_rn = _asp_avg_cp ~= nil local _has_pr = _sell_lv ~= nil local _btn_cnt = (_has_sg and 1 or 0) + (_has_rn and 1 or 0) + (_has_pr and 1 or 0) if _btn_cnt < 1 then _btn_cnt = 1 end local _as_btns_w = fw - _as_pos_w - _as_lbl_w - 8*d - 2*d local _bw4 = (_as_btns_w - math.max(0,_btn_cnt-1)*3*d) / _btn_cnt 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_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)) if imgui.Button(_cyr5f('Рынок##asrn'..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_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; _G.as_pos_buf=nil; _G.as_en_buf=nil _tcv8f() end imgui.EndChild() end end if _G.mh_tab == 3 then _mh_sell_tab_render() end if _G.mh_tab == 4 then _mh_buy_tab_render() end if _G.mh_tab == 7 then _mh_piar_tab_render() end if false and _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 _price = {} for _abi = 1, _n do _price[_abi] = math.max(1, fh_lv_autobuy_preset[_abi].max_price or 1) end local _MAX_ITEM_SHARE = 0.05 local _max_qty = {} for _abi = 1, _n do if fh_lv_autobuy_preset[_abi].qty_limit_1 or _mh_is_qty1_item(fh_lv_autobuy_preset[_abi].name) then _max_qty[_abi] = 1 else local _cap_value = math.max(_price[_abi], _budget * _MAX_ITEM_SHARE) _max_qty[_abi] = math.max(1, math.floor(_cap_value / _price[_abi])) end end local _assigned = {} for _abi = 1, _n do _assigned[_abi] = 0 end local _remaining = _budget local _expensive_idx = {} for _abi = 1, _n do table.insert(_expensive_idx, _abi) end table.sort(_expensive_idx, function(a, b) return _price[a] > _price[b] end) local _cheap_idx = {} for _abi = 1, _n do table.insert(_cheap_idx, _abi) end table.sort(_cheap_idx, function(a, b) return _price[a] < _price[b] end) do local _phase1_limit = _budget * 0.4 local _spent1 = 0 local _progressed = true while _progressed do _progressed = false for _, _abi in ipairs(_expensive_idx) do if _assigned[_abi] < _max_qty[_abi] and (_spent1 + _price[_abi]) <= _phase1_limit and _remaining >= _price[_abi] then _assigned[_abi] = _assigned[_abi] + 1 _remaining = _remaining - _price[_abi] _spent1 = _spent1 + _price[_abi] _progressed = true end end end end do local _phase2_limit = math.min(_remaining, _budget * 0.4) local _share2 = _phase2_limit / _n for _abi = 1, _n do local _qty2 = math.floor(_share2 / _price[_abi]) _qty2 = math.min(_qty2, _max_qty[_abi] - _assigned[_abi]) if _qty2 > 0 then _assigned[_abi] = _assigned[_abi] + _qty2 _remaining = _remaining - _qty2 * _price[_abi] end end end do local _active = {} for _, _abi in ipairs(_cheap_idx) do if _assigned[_abi] < _max_qty[_abi] then table.insert(_active, _abi) end end while #_active > 0 do if _remaining <= 0 then break end local _min_p = nil for _, _abi in ipairs(_active) do if not _min_p or _price[_abi] < _min_p then _min_p = _price[_abi] end end if _remaining < _min_p then break end local _got_one = false for _, _abi in ipairs(_active) do if _assigned[_abi] < _max_qty[_abi] and _remaining >= _price[_abi] then _assigned[_abi] = _assigned[_abi] + 1 _remaining = _remaining - _price[_abi] _got_one = true end end local _next_active = {} for _, _abi in ipairs(_active) do if _assigned[_abi] < _max_qty[_abi] and _price[_abi] <= _remaining then table.insert(_next_active, _abi) end end if not _got_one then break end _active = _next_active end end if not _G.ab_qty_buf then _G.ab_qty_buf = {} end local _total_qty = 0 local _total_spent = 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 _total_spent = _total_spent + _new_qty * _price[_abi] _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(_total_spent)..', итого '..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 do local _bw_run = (right_w2 - 6*d) / 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.35, _G._mh_wa or 1)) if imgui.Button(IC.bolt..' '.._cyr5f('Запустить авто-скуп##abrun'), imgui.ImVec2(_bw_run,0)) then _xpk6g() end imgui.PopStyleColor(2) imgui.SameLine(0, 6*d) local _rebuy_has_new = _G._mh_ab_has_real_sale == true if not _rebuy_has_new then for _, _rb in ipairs(fh_lv_autobuy_preset or {}) do if _rb.needs_rebuy == true then _rebuy_has_new = true break end end end if fh_lv_autosell_running or fh_lv_autobuy_running or not _rebuy_has_new then imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.25,0.25,0.25,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.25,0.25,0.25,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.5,0.5,0.5,1)) imgui.Button(IC.rot..' '.._cyr5f('Докупить##abrebuy_dis'), imgui.ImVec2(_bw_run,0)) imgui.PopStyleColor(3) if imgui.IsItemHovered() and not (fh_lv_autosell_running or fh_lv_autobuy_running) then imgui.SetTooltip(_cyr5f('Станет активна, когда после выкладки скупки вам что-то реально продадут')) end else imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.55,0.35,0.08,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.75,0.48,0.10, _G._mh_wa or 1)) if imgui.Button(IC.rot..' '.._cyr5f('Докупить##abrebuy'), imgui.ImVec2(_bw_run,0)) then if #fh_lv_autobuy_preset == 0 then mh_notify('[MH] {ffaa44}Пресет скупки пуст', 0xFFFFFF) else _xpk6g(true) end end imgui.PopStyleColor(2) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Ставит галочки только на товары, которые реально у вас купили с момента последней выкладки, и сразу запускает скупку')) end end end end if #fh_lv_autobuy_preset > 0 then local total_buy = _G._abp_budget_total or 0 local _ab_en_cnt = _G._abp_en_cnt or 0 imgui.TextColored(imgui.ImVec4(0.4,0.9,0.4,1), _cyr5f(' Итого скуп: $' .. _kcr3y(total_buy) .. ' ')) imgui.SameLine(0,0) imgui.TextColored(imgui.ImVec4(0.55,0.75,0.55,1), '('.._ab_en_cnt..'/'..#fh_lv_autobuy_preset..')') 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), IC.tag..' '.._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; _G.ab_en_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 settings.active_buy_preset = 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(); _G.ab_en_buf=nil settings._ab_undo_snap = nil 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 settings.active_buy_preset = fh_ab_preset_idx fh_lv_autobuy_preset = {} settings.autobuy_preset = fh_lv_autobuy_preset _G.ab_max_buf=nil; _G.ab_qty_buf=nil; _wfn7p(); _G.ab_en_buf=nil 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() imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.10,0.22,0.32,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.16,0.34,0.48,_G._mh_wa or 1)) if imgui.Button(IC.rot..' '.._cyr5f('Импортировать из файла##impabp'), imgui.ImVec2(-1,0)) then _G._import_list_buy = _mh_list_presets('buy') _G._show_import_buy = not _G._show_import_buy end imgui.PopStyleColor(2) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Импортировать пресет скупки из файла (MarketHelper/Preset)')) end if _G._show_import_buy then imgui.Separator() local _ilb = _G._import_list_buy or {} if #_ilb == 0 then imgui.TextDisabled(_cyr5f(' Файлов пресетов скупки не найдено.')) else imgui.TextColored(imgui.ImVec4(0.6,0.85,1,1), IC.fimp..' '.._cyr5f('Выберите файл для импорта:')) for _ii2, _ie2 in ipairs(_ilb) do local _lbl2 = u8(_ie2.name)..' ('..tostring(_ie2.count)..') '.._ie2.created if imgui.Selectable(_lbl2..'##impabsel'.._ii2) then local _ok4, _nm4, _items4 = _mh_import_preset_file(_ie2.path) if _ok4 then if not settings.buy_presets then settings.buy_presets = {} end table.insert(settings.buy_presets, {name = _nm4, items = _items4 or {}}) fh_ab_preset_idx = #settings.buy_presets settings.active_buy_preset = fh_ab_preset_idx fh_lv_autobuy_preset = settings.buy_presets[fh_ab_preset_idx].items settings.autobuy_preset = fh_lv_autobuy_preset _G.ab_max_buf = nil; _G.ab_qty_buf = nil; _G.ab_en_buf = nil settings._ab_undo_snap = nil _wfn7p() mh_notify('[MH] {aaffaa}Пресет скупки "'.._nm4..'" импортирован ('..tostring(#(_items4 or {}))..' тов.).', 0xFFFFFF) else mh_notify('[MH] {ff6666}Не удалось импортировать пресет ('..tostring(_nm4)..').', 0xFFFFFF) end _G._show_import_buy = false end end end imgui.Separator() end 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) settings.active_buy_preset = fh_ab_preset_idx 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; _G.ab_en_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 if _G.ab_preset_sort == 1 then table.sort(fh_lv_autobuy_preset, function(a,b) return (a.max_price or 0) < (b.max_price or 0) end) elseif _G.ab_preset_sort == 2 then table.sort(fh_lv_autobuy_preset, function(a,b) return (a.max_price or 0) > (b.max_price or 0) end) elseif _G.ab_preset_sort == 3 then table.sort(fh_lv_autobuy_preset, function(a,b) return ((a.max_price or 0)*(a.qty or 1)) > ((b.max_price or 0)*(b.qty or 1)) end) else table.sort(fh_lv_autobuy_preset, function(a,b) return (a.name or '') < (b.name or '') end) end _G.ab_max_buf = nil; _G.ab_qty_buf = nil; _G.ab_en_buf = nil; _G._abp_price_cache_key = nil 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 settings.autobuy_preset = fh_lv_autobuy_preset _wfn7p() end imgui.PopStyleColor() 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 do local _bw3g = (right_w2 - 14*d - 8*d) / 3 local _ab_snap = settings._ab_undo_snap local _has_diff = _ab_snap ~= nil 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(_bw3g, 0)) then settings._ab_undo_snap = _mh_deep_copy_list(fh_lv_autobuy_preset) 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() mh_notify('[MH] {aaddff}Пресет скупки сохранён (' ..tostring(#fh_lv_autobuy_preset)..' тов.) — в файл настроек и в память для восстановления.', 0xFFFFFF) end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Сохранить снимок всего пресета целиком (кол-во, цены, состав) для восстановления')) end imgui.SameLine(0, 4*d) 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(_bw3g, 0)) then if _has_diff then fh_lv_autobuy_preset = _mh_deep_copy_list(_ab_snap) 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 _G.ab_qty_buf = nil _G.ab_max_buf = nil _G.ab_pos_buf = nil _G.ab_en_buf = nil _G._ab_diff_ver = nil _G._abp_price_cache_key = nil settings.autobuy_preset = fh_lv_autobuy_preset; _wfn7p() mh_notify('[MH] {aaffaa}Пресет скупки восстановлен из снимка (' ..tostring(#fh_lv_autobuy_preset)..' тов.).', 0xFFFFFF) end end imgui.PopStyleColor(3) if imgui.IsItemHovered() then if _has_diff then imgui.SetTooltip(_cyr5f('Восстановить весь пресет из сохранённого снимка (' ..tostring(#_ab_snap)..' тов.)')) else imgui.SetTooltip(_cyr5f('Снимок ещё не сохранён')) end end imgui.SameLine(0, 4*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.10,0.10,0.10,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.20,0.20,0.20,_G._mh_wa or 1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.7,0.7,0.7,1)) if imgui.Button(IC.save..' '.._cyr5f('В файл (поделиться)##abexport'), imgui.ImVec2(_bw3g, 0)) then local _pname = (settings.buy_presets and settings.buy_presets[fh_ab_preset_idx] and settings.buy_presets[fh_ab_preset_idx].name) or ('Скупка '..tostring(fh_ab_preset_idx)) local _ok, _res = _mh_export_preset('buy', _pname, fh_lv_autobuy_preset) if _ok then mh_notify('[MH] {aaffaa}Пресет скупки сохранён в файл: '.._res, 0xFFFFFF) else mh_notify('[MH] {ff6666}Не удалось сохранить пресет в файл ('..tostring(_res)..').', 0xFFFFFF) end end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Сохранить пресет как файл в MarketHelper/Preset — можно передать другому игроку')) end if _has_diff then imgui.TextColored(imgui.ImVec4(1,0.65,0.15,1), '('..tostring(#_ab_snap)..')') end end do local _bw_cloud = (right_w2 - 4*d) / 2 imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.08,0.30,0.46,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.12,0.46,0.68,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.65,0.9,1,1)) if imgui.Button(IC.cld..' '.._cyr5f('Загрузить в облако##abp_cloud_push'), imgui.ImVec2(_bw_cloud, 0)) then _mh_cloud_push_buy_preset() end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Мгновенно опубликовать текущий пресет скупки в облако — другие игроки смогут его скопировать себе')) end imgui.SameLine(0, 4*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.28,0.22,0.02,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.42,0.34,0.04,1)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1,0.85,0.5,1)) if imgui.Button(IC.cld..' '.._cyr5f('Облако пресетов##abp_cloud_open'), imgui.ImVec2(_bw_cloud, 0)) then _G._mh_cloud_open = true if not _G._mh_cloud_loaded_once then _mh_cloud_fetch_list() _G._mh_cloud_loaded_once = true end end imgui.PopStyleColor(3) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Открыть облако пресетов скупки — искать и копировать себе чужие пресеты')) end end imgui.Separator() if #fh_lv_autobuy_preset > 0 then imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.10,0.34,0.10,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.16,0.50,0.16, _G._mh_wa or 1)) if imgui.Button(IC.chk..' '.._cyr5f('Отметить все##abmarkall'), imgui.ImVec2(-1, 0)) then local _marked = 0 for _abi, _abp in ipairs(fh_lv_autobuy_preset) do if (_abp.qty or 0) > 0 and _abp.enabled ~= true then _abp.enabled = true _marked = _marked + 1 end if _G.ab_en_buf and _G.ab_en_buf[_abi] then _G.ab_en_buf[_abi] = nil end end settings.autobuy_preset = fh_lv_autobuy_preset _wfn7p() mh_notify('[MH] {00cc00}Отметить все: включено ' .. _marked .. ' товаров.', 0xFFFFFF) end imgui.PopStyleColor(2) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Включить галочки у всех товаров пресета (кроме тех, где количество 0)')) end end 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 if abp.enabled == nil then abp.enabled = true end if (abp.qty or 0) <= 0 and abp.enabled then abp.enabled = false settings.autobuy_preset = fh_lv_autobuy_preset; _wfn7p() end if not _G.ab_en_buf then _G.ab_en_buf = {} end if not _G.ab_en_buf[abi] then _G.ab_en_buf[abi] = imgui.new.bool(abp.enabled) end local _ab_en_cb = _G.ab_en_buf[abi] _ab_en_cb[0] = abp.enabled if imgui.Checkbox('##abp_en'..abi, _ab_en_cb) then abp.enabled = _ab_en_cb[0] settings.autobuy_preset = fh_lv_autobuy_preset; _wfn7p() end if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Выставлять этот товар при автоскупке')) end imgui.SameLine(0,4*d) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(1,1,1,1)) local _abp_nm_str = _cyr5f(abp.name) local _abp_nm_w = imgui.CalcTextSize(_abp_nm_str).x if imgui.Selectable(_abp_nm_str..'##abp_nm'..abi, false, 0, imgui.ImVec2(_abp_nm_w,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_extra2 = imgui.CalcTextSize('9999').x local qty_w3 = 52*d + _qty_extra2 local _lbl_price_w2 = imgui.CalcTextSize(_cyr5f('Цена:')).x + 4*d local _lbl_qty_w2 = imgui.CalcTextSize(_cyr5f('Шт.')).x + 4*d local price_w3 = fw2 - qty_w3 - x_w3 - btn_w3*3 - 5*3*d - 6*d - _lbl_price_w2 - _lbl_qty_w2 if price_w3 < 40*d then price_w3 = 40*d end imgui.TextDisabled(_cyr5f('Цена:')) imgui.SameLine(0,4*d) 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,8*d) imgui.TextDisabled(_cyr5f('Шт.')) imgui.SameLine(0,4*d) local _abp_q1 = abp.qty_limit_1 or _mh_is_qty1_item(abp.name) if _abp_q1 and (abp.qty or 1) ~= 1 then abp.qty = 1 _G.ab_qty_buf[abi] = imgui.new.char[16]('1') settings.autobuy_preset=fh_lv_autobuy_preset; _wfn7p() end imgui.PushItemWidth(qty_w3) if _abp_q1 then imgui.PushStyleColor(imgui.Col.FrameBg, imgui.ImVec4(0.12,0.12,0.12,0.6)) imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.5,0.5,0.5,1)) end if imgui.InputText('##abq'..abi, _G.ab_qty_buf[abi], 16) then if _abp_q1 then _G.ab_qty_buf[abi] = imgui.new.char[16]('1') else local _nq = tonumber(ffi.string(_G.ab_qty_buf[abi])) or abp.qty abp.qty = _nq if not abp.target_qty then abp.target_qty = _nq end _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 end if _abp_q1 then imgui.PopStyleColor(2) 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 if not _abp_q1 then local _step = math.max(1, abp.qty_step or 1) abp.qty = math.max(1, (abp.qty or 1) - _step) if not abp.target_qty then abp.target_qty = abp.qty end _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 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 if not _abp_q1 then local _step = math.max(1, abp.qty_step or 1) abp.qty = (abp.qty or 1) + _step if not abp.target_qty then abp.target_qty = abp.qty end _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 end imgui.PopStyleColor() if _abp_q1 then imgui.SameLine(0,2*d) imgui.TextColored(imgui.ImVec4(1,0.65,0.15,1), _cyr5f('(max 1шт)')) end 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 if not _G.ab_pos_buf then _G.ab_pos_buf = {} end if not _G.ab_pos_buf[abi] then _G.ab_pos_buf[abi] = imgui.new.char[8](tostring(abi)) end local _ab_pos_w = imgui.CalcTextSize('9999').x + 16*d local _ab_lbl_w = imgui.CalcTextSize(_cyr5f('Позиция:')).x imgui.TextDisabled(_cyr5f('Позиция:')) imgui.SameLine(0,4*d) imgui.PushItemWidth(_ab_pos_w) if imgui.InputText('##abpos'..abi, _G.ab_pos_buf[abi], 8, imgui.InputTextFlags.EnterReturnsTrue) then local _target = tonumber(ffi.string(_G.ab_pos_buf[abi])) local _n_tot = #fh_lv_autobuy_preset if _target then _target = math.floor(_target) if _target < 1 then _target = 1 end if _target > _n_tot then _target = _n_tot end if _target ~= abi then local _moved = table.remove(fh_lv_autobuy_preset, abi) table.insert(fh_lv_autobuy_preset, _target, _moved) _G.ab_preset_sort = -1 _G.ab_max_buf=nil; _G.ab_qty_buf=nil; _G.ab_pos_buf=nil; _G.ab_en_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() mh_notify('[MH] '.._moved.name..': позиция -> '..tostring(_target), 0xAAFFAA) end end end imgui.PopItemWidth() if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Введи номер позиции - товар встанет на это место')) end imgui.SameLine(0,2*d) local _has_sk2 = _buy_lv2 ~= nil local _has_rn2 = _abp_avg_cp ~= nil local _btn_cnt2 = (_has_rn2 and 1 or 0) + (_has_sk2 and 1 or 0) if _btn_cnt2 < 1 then _btn_cnt2 = 1 end local _ab_btns_w = fw2 - _ab_pos_w - _ab_lbl_w - 8*d - 2*d local _bw4b = (_ab_btns_w - math.max(0,_btn_cnt2-1)*3*d) / _btn_cnt2 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(_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_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 end end imgui.Separator() end end if del_ab_i then table.remove(fh_lv_autobuy_preset,del_ab_i) _G.ab_pos_buf = nil 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 if _G.ab_en_buf then local nb={} for i,v in pairs(_G.ab_en_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_en_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 _mh_log_tab_render() end if false and _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 - 16*d) / 5 local function tab_btn(label, mode, icon) 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 local _lbl_tb = icon and (icon..' '.._cyr5f(label)) or _cyr5f(label) if imgui.Button(_lbl_tb..'##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, IC.calds) imgui.SameLine(0, 4*d) tab_btn('\xd7\xe0\xf2', 2, IC.chrts) imgui.SameLine(0, 4*d) tab_btn('\xd2\xf0\xe5\xe9\xe4', 4, IC.chrtl) imgui.SameLine(0, 4*d) tab_btn('Telegram', 3, IC.phone) imgui.SameLine(0, 4*d) tab_btn('\xd3\xe2\xe5\xe4\xee\xec\xeb\xe5\xed\xe8\xff', 5, IC.bolt) 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,IC.clk..' '.._cyr5f('\xc2\xf0.')); imgui.NextColumn() imgui.TextColored(hc,IC.tag..' '.._cyr5f('\xd2\xee\xe2\xe0\xf0')); imgui.NextColumn() imgui.TextColored(hc,IC.coin..' '.._cyr5f('\xd6\xe5\xed\xe0 $')); imgui.NextColumn() imgui.TextColored(hc,IC.boxes..' '.._cyr5f('\xd8\xf2.')); imgui.NextColumn() imgui.TextColored(hc,IC.alr..' '.._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={}; _G._mh_mkt_log_ver = (_G._mh_mkt_log_ver or 0) + 1; _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),IC.cald..u8' \xc4\xe0\xf2\xe0'); imgui.NextColumn() imgui.TextColored(ac3,IC.tag..u8' \xd2\xee\xe2\xe0\xf0'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1),IC.boxes..u8' \xca\xee\xeb.'); imgui.NextColumn() imgui.TextColored(ac3,IC.coin..u8' \xd6\xe5\xed\xe0 $'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1),IC.alr..u8' \xd2\xe8\xef'); imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.5,0.5,0.5,1),IC.eye..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.Dummy(imgui.ImVec2(col1_lg2 - 4, _ilh)) if imgui.IsItemHovered() and imgui.IsMouseReleased(0) and not _G._sw.active 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('PayDay (зарплата каждые 30 мин)','notify_payday') 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() 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() elseif _G.log_tab_mode == 5 then local nf = settings.notify_filters imgui.TextDisabled(_cyr5f(' Показывать тосты для:')) do local function _nf_chk(label, key) local cb = imgui.new.bool(nf[key] ~= false) if imgui.Checkbox(_cyr5f(label)..'##nf_'..key, cb) then nf[key] = cb[0]; _wfn7p() end end _nf_chk('Автовыкладка (каждый товар)', 'sell_item') imgui.SameLine(0, 16*d) _nf_chk('Автоскупка (каждый товар)', 'buy_item') _nf_chk('Статус выкладки', 'sell_status') imgui.SameLine(0, 16*d) _nf_chk('Статус скупки', 'buy_status') _nf_chk('Остальные уведомления', 'other') end do local _snd_cb = imgui.new.bool(_G.MH_SOUND_ENABLED ~= false) if imgui.Checkbox(_cyr5f('Звук уведомлений')..'##nf_sound', _snd_cb) then _G.MH_SOUND_ENABLED = _snd_cb[0] settings.sound_enabled = _snd_cb[0]; _wfn7p() end end imgui.TextDisabled(_cyr5f(' История (последние '..tostring(#(_G.mh_notify_log or {}))..' из '..tostring(settings.notify_history_max or 500)..'):')) imgui.SameLine(0, 10*d) imgui.PushStyleColor(imgui.Col.Button,imgui.ImVec4(0.4,0.08,0.08,0.8)) if imgui.Button(fa.TRASH_CAN..' '.._cyr5f('Очистить##nflogcl'),imgui.ImVec2(0,0)) then _G.mh_notify_log = {} end imgui.PopStyleColor() imgui.Separator() local _nlog_h = imgui.GetWindowHeight() - 260*d if imgui.BeginChild('##nf_hist', imgui.ImVec2(-1, math.max(120*d, _nlog_h)), true) then _dpn1w() local _hist = _G.mh_notify_log or {} if #_hist == 0 then imgui.TextDisabled(_cyr5f(' Уведомлений пока нет.')) else for _hi = #_hist, math.max(1, #_hist - 300), -1 do local _he = _hist[_hi] if _he then local _hc = imgui.ImVec4(0.75,0.75,0.75,1) if _he.category == 'sell_item' or _he.category == 'sell_status' then _hc = imgui.ImVec4(sb_r*1.6+0.2, sb_g*1.6+0.2, sb_b*1.6+0.2, 1) elseif _he.category == 'buy_item' or _he.category == 'buy_status' then _hc = imgui.ImVec4(bb_r*1.6+0.2, bb_g*1.6+0.2, bb_b*1.6+0.2, 1) end imgui.TextDisabled(_he.time or '') imgui.SameLine(0, 8*d) imgui.TextColored(_hc, _he.text or '') end end end imgui.EndChild() end end end if false and _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() 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_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() 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() if not _G.ao_del_buf then _G.ao_del_buf = {} end if not _G.ao_del_buf[_aoi] then _G.ao_del_buf[_aoi] = imgui.new.bool(_sl.delete_on_sold or false) end local _del_cb = _G.ao_del_buf[_aoi] _del_cb[0] = _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 if _G.mh_tab == 8 then _mh_catch_tab_render() 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 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 lua_thread.create(function() _lkg8m() 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("mrkcatch", function() local function say(t) mh_notify("[MH] {aaaaaa}" .. tostring(t), message_color) end local nd, ni, no = 0, 0, 0 for _, items in pairs(type(settings.catch_loot_log) == 'table' and settings.catch_loot_log or {}) do nd = nd + 1 if type(items) == 'table' then for _ in pairs(items) do ni = ni + 1 end end end for _, rec in pairs(type(settings.catch_open_log) == 'table' and settings.catch_open_log or {}) do if type(rec) == 'table' then no = no + (tonumber(rec.total) or 0) end end say('1) v ozu: dney=' .. nd .. ' predm=' .. ni .. ' otkryto=' .. no .. ' ready=' .. tostring(_G._mh_catch_ready)) say('2) soobsh=' .. (_G._mh_catch_seen_n or 0) .. ' zapisano=' .. (_G._mh_catch_rec_n or 0) .. ' saves=' .. (_G._mh_catch_saves or 0)) for _, nm in ipairs({'catch_loot_log.json', 'catch_open_log.json'}) do local f = io.open(_G._mh_catch_path(nm), 'rb') if f then local raw = f:read('*a') or ''; f:close() say('3) ' .. nm .. ' ' .. #raw .. 'b: ' .. raw:sub(1, 70):gsub('{', '('):gsub('}', ')')) else say('3) ' .. nm .. ' NET FAJLA') end end say('4) start: ' .. tostring(_G._mh_catch_init_state)) say('5) oshibka: ' .. tostring(_G._mh_catch_last_err ~= '' and _G._mh_catch_last_err or 'net')) say('6) papka: ' .. tostring(_G._mh_catch_dir())) 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 _G._mh_rawpkt_freq = {} 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 if _G._mh_rawpkt_freq then local f2 = io.open(path, 'a') if f2 then f2:write(string.rep('=',60) .. '\n') f2:write('RAWPKT SUMMARY (packet_id != 220, за сессию дебага)\n') local _ids = {} for pid, cnt in pairs(_G._mh_rawpkt_freq) do table.insert(_ids, {pid=pid, cnt=cnt}) end table.sort(_ids, function(a,b) return a.cnt > b.cnt end) for _, e in ipairs(_ids) do f2:write(string.format(' id=%-4d count=%d\n', e.pid, e.cnt)) end f2:write(string.rep('=',60) .. '\n') f2:close() end _G._mh_rawpkt_freq = nil end 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 local pX, pY = getCharCoordinates(PLAYER_PED) 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 dist = math.sqrt((posX-pX)^2 + (posY-pY)^2) if dist <= cm_radius_draw_dist then local col = dist > 5 and 0xFFFFFFFF or 0xFFFF2222 cm_drawCircle3d(posX, posY, posZ - 1.3, 5, col) end 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) return false end function _mh_price_dbg(msg) if not (mh_debug_enabled or mh_pktdbg_enabled) then return end pcall(function() local _pth = getWorkingDirectory():gsub('\\','/') .. '/mrkfdbg.log' local _fh = io.open(_pth, 'a') if _fh then _fh:write('[' .. os.date('%H:%M:%S') .. '] ' .. tostring(msg) .. '\n') _fh:close() end end) end function _mh_price_line_for_text(text) local _price_line = nil local _ok_price, _err_price = pcall(function() if not text or text == '' then return end local _clean = text:gsub('{%x%x%x%x%x%x}', '') local _first_line = nil for _line in (_clean .. '\n'):gmatch('([^\n]*)\n') do local _trimmed = _line:match('^%s*(.-)%s*$') if _trimmed and _trimmed ~= '' then _first_line = _trimmed break end end if not _first_line then return end local _after_colon = _first_line:match('^[^:]+:%s*(.+)$') local _candidates = {} if _after_colon and _after_colon ~= '' then table.insert(_candidates, _after_colon) end table.insert(_candidates, _first_line) local _resolved_nm = nil for _, _cand in ipairs(_candidates) do local _canon, _found = _mh_norm_nm and _mh_norm_nm(_cand) if _found then _resolved_nm = _canon break end end if not _resolved_nm then for _, _cand in ipairs(_candidates) do if (fh_mkt_prices and fh_mkt_prices[_cand]) or (fh_mkt_lavka and fh_mkt_lavka[_cand]) then _resolved_nm = _cand break end end end if not _resolved_nm then local _cand_lo = {} for _, _cand in ipairs(_candidates) do _cand_lo[#_cand_lo+1] = _cand:lower() end if fh_mkt_prices then for _k in pairs(fh_mkt_prices) do local _k_lo = _k:lower() for _, _cl in ipairs(_cand_lo) do if _k_lo == _cl then _resolved_nm = _k; break end end if _resolved_nm then break end end end if not _resolved_nm and fh_mkt_lavka then for _k in pairs(fh_mkt_lavka) do local _k_lo = _k:lower() for _, _cl in ipairs(_cand_lo) do if _k_lo == _cl then _resolved_nm = _k; break end end if _resolved_nm then break end end end end if not _resolved_nm then local _idx_size = 0 if _mh_items_rev_idx then for _ in pairs(_mh_items_rev_idx) do _idx_size = _idx_size + 1 end end _mh_price_dbg('PRICE INJECT: имя НЕ распознано, candidates=[' .. table.concat(_candidates, ' | ') .. '] | items_loaded=' .. tostring(mh_arz_items_loaded) .. ' items_loading=' .. tostring(mh_arz_items_loading) .. ' rev_idx_size=' .. tostring(_idx_size)) return end local function _mh_fmt_dots(n) if not n then return nil end n = math.floor(n) if n <= 0 then return nil end local _s = tostring(n) local _out = '' local _cnt = 0 for i = #_s, 1, -1 do _out = _s:sub(i,i) .. _out _cnt = _cnt + 1 if _cnt % 3 == 0 and i > 1 then _out = '.' .. _out end end return _out end local _mp = _mh_get_mkt_price and _mh_get_mkt_price(_resolved_nm) local _avg_val = _mp and (_mp.avg7 or _mp.today or _mp.avg30) or nil local _sell_val, _buy_val = nil, nil local _dtl_srv_for_key = (type(_mh_cloud_current_server_id) == 'function') and _mh_cloud_current_server_id() or -1 local _dtl_key_now = _resolved_nm .. '|' .. tostring(_dtl_srv_for_key) if _G._dtl_cache_nm == _dtl_key_now then local _sr_live = _G._dtl_sell_rows local _br_live = _G._dtl_buy_rows if _sr_live and #_sr_live > 0 and _sr_live[1].price and _sr_live[1].price > 0 then _sell_val = _sr_live[1].price end if _br_live and #_br_live > 0 and _br_live[1].price and _br_live[1].price > 0 then _buy_val = _br_live[1].price end end local _fe = fh_mkt_prices and fh_mkt_prices[_resolved_nm] if not _sell_val and _fe and _fe.s_min and _fe.s_min > 0 then _sell_val = _fe.s_min end if not _buy_val and _fe and _fe.b_max and _fe.b_max > 0 then _buy_val = _fe.b_max end local _le = fh_mkt_lavka and fh_mkt_lavka[_resolved_nm] if not _sell_val and _le and _le.s_min and _le.s_min > 0 then _sell_val = _le.s_min end if not _buy_val and _le and _le.b_max and _le.b_max > 0 then _buy_val = _le.b_max end if not _avg_val and not _sell_val and not _buy_val then _mh_price_dbg('PRICE INJECT: имя распознано как "' .. _resolved_nm .. '", НО данных о цене нет (avg=' .. tostring(_avg_val) .. ' sell=' .. tostring(_sell_val) .. ' buy=' .. tostring(_buy_val) .. ' fh_mkt_prices_has_entry=' .. tostring(_fe ~= nil) .. ' fh_mkt_lavka_has_entry=' .. tostring(_le ~= nil) .. ')') return end local _parts = {} if _avg_val then table.insert(_parts, '\xd1\xf0\xe5\xe4\xed\xff\xff: $' .. (_mh_fmt_dots(_avg_val) or '0')) end if _sell_val then table.insert(_parts, '\xcf\xf0\xee\xe4\xe0\xfe\xf2: $' .. (_mh_fmt_dots(_sell_val) or '0')) end if _buy_val then table.insert(_parts, '\xd1\xea\xf3\xef\xe0\xfe\xf2: $' .. (_mh_fmt_dots(_buy_val) or '0')) end if #_parts == 0 then return end _price_line = '\n\n{66CCFF}[MH] ' .. table.concat(_parts, '{FFFFFF}, {66CCFF}') .. '{FFFFFF}' _mh_price_dbg('PRICE INJECT: успех, имя="' .. _resolved_nm .. '" line=' .. _price_line) end) if not _ok_price then pcall(function() local _pth = getWorkingDirectory():gsub('\\','/') .. '/mrkfdbg.log' local _fh = io.open(_pth, 'a') if _fh then _fh:write('PRICE INJECT ERROR: ' .. tostring(_err_price) .. '\n') _fh:close() end end) end return _price_line end function _mh_inject_price_line(text, style, _price_line) local _real_text = (text or ''):gsub('%z+$', '') if style == 1 then local _trimmed_end = _real_text:gsub('%s+$', '') local _last_line = _trimmed_end:match('([^\n]*)$') or '' if _last_line:match(':%s*$') then local _head, _tail = _trimmed_end:match('^(.-)\n([^\n]*)$') if _head and _tail then return _head .. _price_line .. '\n\n' .. _tail end end end return _real_text .. _price_line end function sampev.onShowDialog(dialogId, style, title, button1, button2, text) if (dialogId == 28261 or dialogId == 3082) and _mh_is_premium() then _mh_price_dbg('PRICE INJECT: onShowDialog вызван для id=' .. tostring(dialogId) .. ' style=' .. tostring(style)) local _price_line = _mh_price_line_for_text(text) if not _price_line then _mh_price_dbg('PRICE INJECT: _price_line = nil, инъекция пропущена (см. причину строкой выше)') else local _new_text = _mh_inject_price_line(text, style, _price_line) _mh_price_dbg('PRICE INJECT: успех, имя резолвлено, подменяю текст диалога id=' .. tostring(dialogId)) return { dialogId, style, title, button1, button2, _new_text } end end 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=='')) 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 fh_other_shop_dialog_num = num fh_other_shop_dialog_num_ts = os.clock() 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 '' 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 mh_debug_enabled and (dialogId == 26547 or dialogId == 25493) then local t = title and title:gsub("{%x+}",""):sub(1,30) or "" mh_notify("[MH DEBUG] Dialog ID: " .. dialogId .. " style: " .. style, 0xFF8888) mh_notify("[MH DEBUG] Title: " .. t, 0xFF8888) if text and text ~= "" then local first = text:gsub("{%x+}",""):match("^%s*(.-)%s*$") or "" mh_notify("[MH DEBUG] [1] " .. first:sub(1,60), 0xFF8888) local cnt = 0; for _ in text:gmatch('[^\n]+') do cnt=cnt+1 end mh_notify("[MH 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 == 26547 and text then local has_qty = text:find("запятую") ~= nil fh_mkt_shop_price_qty = has_qty _G._mh_dlg26544_text = text _G._mh_dlg26544_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('[MH Дебаг] {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('[MH Авто] {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('[MH Авто] {00cc00}Инвентарь: ' .. #fh_lv_inventory .. ' позиций', 0xFFFFFF) if _mh_sell and _mh_sell.push_state then pcall(_mh_sell.push_state) end if _mh_catch and _mh_catch.push_state then pcall(_mh_catch.push_state) end 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 = 'Проанализировать все цены [MH]' 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('[MH Market] {00cc00}Анализ завершён! Товаров: ' .. tot, 0xFFFFFF) printStyledString('~w~MH 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~MH: ~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('[MH 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 = 'Углублённый скан [MH]' 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(1500); _G._mh_auto_prices_push(true) end) 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(1500); _G._mh_auto_prices_push(true) end) 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 mh_pktdbg_enabled then local _path = getWorkingDirectory():gsub('\\','/') .. '/mrkfdbg.log' local _f = io.open(_path, 'a') if _f then local _tp = td_data and td_data.position _f:write(string.format('TD id=%-6d x=%-8s y=%-8s text=%s\n', td_id, tostring(_tp and _tp.x or '?'), tostring(_tp and _tp.y or '?'), tostring(td_data and td_data.text or ''):sub(1,200))) _f:close() end end 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 mh_pktdbg_enabled then 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('DLGRESP id=%-6d button=%d listItem=%d inputText=%s\n', dialogId, button or -1, listItem or -1, tostring(inputText or ''):sub(1,100))) _f:write(' last_dlg_title: ' .. tostring(fh_last_dlg_title or ''):sub(1,80) .. '\n') _f:close() end end 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 or ''):find('Средняя цена товаров при продаже') ~= nil end if is_mkt then end if is_mkt and fh_mkt_cp_deep_scanning then return { dialogId, button, listItem, inputText } 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('[MH Market] {ffaa00}Углублённый скан запущен!', 0xFFFFFF) local _was_main_open_outer = MainWindow[0] MainWindow[0] = false local txt0 = mh_dlg_text() or '' local dlg0 = dialogId lua_thread.create(function() local _was_main_open = _was_main_open_outer local function deep_idx_offset(text) if text and ( text:find('\xd1\xea\xe0\xed\xe8\xf0\xee\xe2\xe0\xf2\xfc \xe2\xf1\xe5 \xf6\xe5\xed\xfb [MH]', 1, true) or text:find('\xd3\xe3\xeb\xf3\xe1\xeb\xb8\xed\xed\xfb\xe9 \xf1\xea\xe0\xed [MH]', 1, true) ) then return 2 end return 0 end local stats = _mh_deep_engine({ tag = '[MH Market]', list_id = 15073, dlg0 = dlg0, txt0 = txt0, rtt0 = 1200, to_min = 3000, to_k = 5, to_add = 1500, to_max = 20000, lost_ms = 25000, is_active = function() return fh_mkt_cp_deep_scanning end, parse_list = function(tx) return _G._nqh8s(tx, 5) end, idx_offset = deep_idx_offset, is_detail = function(title) return (title or ''):find('Продажа товара') ~= nil end, parse_detail = function(tx, title) return MH_util.jfw5v(tx, title) end, on_detail = function(detail) fh_mkt_save_cp_detail(detail.name, detail.history) lua_thread.create(function() wait(200); _G._mh_upload_deals() end) end, on_progress = function(n) fh_mkt_cp_deep_done = n printStyledString('~w~MH deep: ~y~'..n, 1000, 6) end, }) fh_mkt_cp_deep_scanning = false _ryb5t() if sampIsDialogActive() then sampSendDialogResponse(sampGetCurrentDialogId(), 0, 0, '') end MainWindow[0] = _was_main_open local _skip_s = (stats.skipped > 0) and (' (пропущено: ' .. stats.skipped .. ')') or '' if stats.aborted then mh_notify('[MH Market] {ffaa00}Скан остановлен: ' .. _mh_deep_abort_text(stats.aborted) .. '. Снято: ' .. fh_mkt_cp_deep_done .. _skip_s, 0xFFFFFF) else mh_notify('[MH Market] {00cc00}Скан завершён! Товаров: '..fh_mkt_cp_deep_done .. _skip_s, 0xFFFFFF) end lua_thread.create(function() wait(1000); _G._mh_prices_push() end) wait(400) printStyledString('~w~MH 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 = mh_dlg_text() or '' local nxt_raw = fh_mkt_cp_prev_text or txt local nxt = fh_find_listitem(nxt_raw, 'Следующая страница') if nxt == nil then mh_notify('[MH 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('[MH Market] {ffaa00}Запущен анализ цен. Не открывайте другие диалоги!', 0xFFFFFF) _hbr6z(txt) local tot = 0; for _ in pairs(fh_mkt_prices) do tot = tot + 1 end printStyledString('~w~MH 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 { dialogId, button, listItem, inputText } 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 = mh_dlg_text() or '' local dlg0 = dialogId lua_thread.create(function() local function auto_idx_offset(text) if text and ( text:find('Сканировать все авто [MH]', 1, true) or text:find('Углублённый скан авто [MH]', 1, true) ) then return 2 end return 0 end local stats = _mh_deep_engine({ tag = '[MH Auto]', list_id = 15376, dlg0 = dlg0, txt0 = txt0, rtt0 = 500, to_min = 800, to_k = 4, to_add = 500, to_max = 15000, lost_ms = 25000, is_active = function() return fh_mkt_auto_deep_scanning end, parse_list = function(tx) return fh_mkt_parse_auto_list(tx) end, idx_offset = auto_idx_offset, is_detail = function(title) return true end, parse_detail = function(tx, title) return fh_mkt_parse_auto_detail(tx, title) end, on_detail = function(detail) fh_mkt_save_auto_detail(detail.name, detail.history) if _mh_auto_invalidate_cache then pcall(_mh_auto_invalidate_cache) end end, on_progress = function(n) fh_mkt_auto_deep_done = n printStyledString('~w~MH Auto deep: ~y~'..n, 1000, 6) end, }) fh_mkt_auto_deep_scanning = false _ryb5t() lua_thread.create(function() wait(1000); _G._mh_auto_prices_push() end) if sampIsDialogActive() then sampSendDialogResponse(sampGetCurrentDialogId(), 0, 0, '') end local _skip_s = (stats.skipped > 0) and (' (пропущено: ' .. stats.skipped .. ')') or '' if stats.aborted then mh_notify('[MH Auto] {ffaa00}Скан остановлен: ' .. _mh_deep_abort_text(stats.aborted) .. '. Снято: ' .. fh_mkt_auto_deep_done .. _skip_s, 0xFFFFFF) else mh_notify('[MH Auto] {00cc00}Углублённый скан завершён! Авто: '..fh_mkt_auto_deep_done .. _skip_s, 0xFFFFFF) end 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 = mh_dlg_text() 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_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 end function sampev.onServerMessage(color, text) if not text then return end if mh_pktdbg_enabled then local _path = getWorkingDirectory():gsub('\\','/') .. '/mrkfdbg.log' local _f = io.open(_path, 'a') if _f then _f:write(string.format('MSG color=%06X text=%s\n', color or 0, tostring(text):sub(1,300))) _f:close() end 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 do if text:find('добавлен предмет', 1, true) then _G._mh_catch_seen_n = (_G._mh_catch_seen_n or 0) + 1 _G._mh_catch_last_text = text:sub(1, 120) end local _cok, _cres = pcall(_mh_catch_loot_record, text) if not _cok then _G._mh_catch_last_err = 'record: ' .. tostring(_cres) elseif _cres == true then _G._mh_catch_rec_n = (_G._mh_catch_rec_n or 0) + 1 end 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) table.insert(fh_session_chat_ts, 1, os.time()) while #fh_session_chat > 2000 do table.remove(fh_session_chat) table.remove(fh_session_chat_ts) end _G._lvn7s() end do local _clean_pd = text:gsub('{%x+}', '') if _mh_payday_on_line(_clean_pd) then _G._mh_payday_last_line_t = os.clock() lua_thread.create(function() local _my_t = _G._mh_payday_last_line_t wait(600) if _G._mh_payday_last_line_t == _my_t then _mh_payday_flush() end end) end 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 if _G._mh_sync_own_shop_after_sale then pcall(_G._mh_sync_own_shop_after_sale, trade.item, trade.qty, trade.price) end 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) abp.needs_rebuy = true _G._mh_ab_has_real_sale = true 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 == 25663 or dialogId == 25664 or dialogId == 26560 or dialogId == 26563 ) 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 if settings.chat_log_enabled ~= false then table.insert(fh_session_chat, 1, msg) table.insert(fh_session_chat_ts, 1, os.time()) while #fh_session_chat > 2000 do table.remove(fh_session_chat) table.remove(fh_session_chat_ts) end _G._lvn7s() end end addEventHandler("onShowDialog", function(dialogId, style, title, button1, button2, text) if not mh_pktdbg_enabled then return end local _ok_dbg, _err_dbg = pcall(function() local _title = tostring(title or '') local _text = tostring(text or '') 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=%-6s style=%s\n', tostring(dialogId), tostring(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,1200) .. '\n'):gmatch('([^\n]*)\n') do _f:write(' ' .. _line .. '\n') _ln = _ln + 1; if _ln >= 24 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) if not _ok_dbg then pcall(function() local _path2 = getWorkingDirectory():gsub('\\','/') .. '/mrkfdbg.log' local _f2 = io.open(_path2, 'a') if _f2 then _f2:write(string.rep('-',60) .. '\n') _f2:write('DLG HANDLER ERROR id=' .. tostring(dialogId) .. ' err=' .. tostring(_err_dbg) .. '\n\n') _f2:close() end end) end end) local _mh_dbg_orig_onshowdialog = sampev.onShowDialog sampev.onShowDialog = function(dialogId, style, title, button1, button2, text) if mh_pktdbg_enabled then pcall(function() local _path3 = getWorkingDirectory():gsub('\\','/') .. '/mrkfdbg.log' local _f3 = io.open(_path3, 'a') if _f3 then local _t3 = tostring(title or '') local _x3 = tostring(text or '') _f3:write(string.rep('=',60) .. '\n') _f3:write(string.format('DLG[RAW sampev] id=%-6s style=%s\n', tostring(dialogId), tostring(style))) _f3:write(' title : ' .. _t3:sub(1,120) .. '\n') _f3:write(string.format(' text : (%d chars)\n', #_x3)) local _ln3 = 0 for _line3 in (_x3 .. '\n'):gmatch('([^\n]*)\n') do _f3:write(' ' .. _line3 .. '\n') _ln3 = _ln3 + 1 end _f3:write('\n') _f3:close() end end) end if _mh_dbg_orig_onshowdialog then return _mh_dbg_orig_onshowdialog(dialogId, style, title, button1, button2, text) 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 mh_pktdbg_enabled and packet_id ~= 220 then _G._mh_rawpkt_freq = _G._mh_rawpkt_freq or {} _G._mh_rawpkt_freq[packet_id] = (_G._mh_rawpkt_freq[packet_id] or 0) + 1 end 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) end _dbl(' --- RAW JSON (full) ---') for _jl in (json_s .. '\n'):gmatch('([^\n]*)\n') do if _jl ~= '' then _dbl(' ' .. _jl) end 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) and not _G._mh_own_shop_capture_pending 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, ench=(tonumber(item_obj:match('"enchant"%s*:%s*(%d+)')) or 0)}) 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_dialog_num or 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)..'|'..tostring(tonumber(it.ench) or 0) 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,ench=tonumber(it.ench) 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 if shopnum_j then _mh_sync_current_own_shop_num(shopnum_j) end _G._mh_own_shop_capture_id = (_G._mh_own_shop_capture_id or 0) + 1 local _capture_id = _G._mh_own_shop_capture_id mh_pending_lavka_buf = {} mh_own_shop_capture_buf = {} _G._mh_passive_buf_ts = 0 _G._mh_passive_buf_mono = 0 _G._mh_passive_buf_gen = (_G._mh_passive_buf_gen or 0) + 1 _G._mh_own_shop_capture_slot0_seen = false _G._mh_own_shop_capture_pending = true _G._mh_own_shop_capture_num = shopnum_j lua_thread.create(function() wait(1200) if _G._mh_own_shop_capture_id ~= _capture_id then return end local _num = _G._mh_own_shop_capture_num or shopnum_j local _fresh = (_G._mh_passive_buf_ts or 0) > 0 and (os.time() - (_G._mh_passive_buf_ts or 0)) <= 4 and (os.clock() - (_G._mh_passive_buf_mono or 0)) <= 4 if _G._mh_own_shop_capture_pending and _G._mh_own_shop_capture_id == _capture_id and _num and _fresh and not fh_other_shop_scanning then _mh_capture_own_shop_from_pending(owner_j, _num) mh_own_shop_capture_buf = {} end if _G._mh_own_shop_capture_id == _capture_id then _G._mh_own_shop_capture_pending = false _G._mh_own_shop_capture_num = nil _G._mh_own_shop_capture_slot0_seen = false mh_own_shop_capture_buf = {} end end) 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 _dlg_snum = nil if fh_other_shop_dialog_num and (os.clock() - (fh_other_shop_dialog_num_ts or 0)) <= 5.0 then _dlg_snum = fh_other_shop_dialog_num end local _snum = shopnum_j or _dlg_snum 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 fh_other_shop_dialog_num = nil fh_other_shop_dialog_num_ts = 0 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) .. '|' .. tostring(tonumber(it.ench) or 0) 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, ench = tonumber(it.ench) 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 _lench = tonumber(_li.ench) or 0 local _lk = tostring(_li.item_id)..'|'..tostring(_li.price)..'|'..tostring(_lench) 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,ench=_lench}; 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 if _G._mh_own_shop_capture_pending then _G._mh_own_shop_capture_slot0_seen = true elseif not _G._mh_own_shop_capture_slot0_seen then mh_pending_lavka_buf = {} _G._mh_passive_buf_ts = 0 _G._mh_passive_buf_mono = 0 _G._mh_own_shop_capture_slot0_seen = true end 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 _G._mh_passive_buf_mono = os.clock() _G._mh_passive_buf_gen = (_G._mh_passive_buf_gen or 0) + 1 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, ench=(tonumber(item_obj:match('"enchant"%s*:%s*(%d+)')) or 0)}) end end if #buf_items > 0 then local target_buf = _G._mh_own_shop_capture_pending and mh_own_shop_capture_buf or mh_pending_lavka_buf table.insert(target_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='..#target_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 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() _mh_rating_tab_render() if false then 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, _ic_eye..u8' ' .._cyr5f('Ник')); imgui.NextColumn() imgui.TextColored(hc, _ic_gps..u8' ' .._cyr5f('Сервер')); imgui.NextColumn() imgui.TextColored(hc, _ic_star..u8' ' .._cyr5f('Уровень')); imgui.NextColumn() imgui.TextColored(hc, _ic_chrts..u8' ' .._cyr5f('Опыт / след.')); imgui.NextColumn() imgui.TextColored(hc, _ic_up..u8' ' .._cyr5f('До ур.')); imgui.NextColumn() imgui.TextColored(hc, _ic_alr..u8' ' .._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 _G._arz_shop_win_open = true _G._shop_win_init = nil 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 end imgui.OnFrame(function() return MainWindow[0] end, function() local _mh_was_on_market_subtab = _G._mh_on_market_subtab _G._mh_on_market_subtab = false if not _mh_was_on_market_subtab then _mh_web_hide() end local _mh_was_on_shops_subtab = _G._mh_on_shops_subtab _G._mh_on_shops_subtab = false if not _mh_was_on_shops_subtab then _mh_shops_hide() end local _mh_was_on_wish_subtab = _G._mh_on_wish_subtab _G._mh_on_wish_subtab = false if not _mh_was_on_wish_subtab then _mh_wish_hide() end local _mh_was_on_arb_subtab = _G._mh_on_arb_subtab _G._mh_on_arb_subtab = false if not _mh_was_on_arb_subtab then _mh_arb_hide() end local _mh_was_on_pick_subtab = _G._mh_on_pick_subtab _G._mh_on_pick_subtab = false if not _mh_was_on_pick_subtab then _mh_pick_hide() end local _mh_was_on_auto_subtab = _G._mh_on_auto_subtab _G._mh_on_auto_subtab = false if not _mh_was_on_auto_subtab then _mh_auto_hide() end local d = settings.general.custom_dpi if _G.mh_tab ~= 3 then _mh_sell_hide() end if _G.mh_tab ~= 4 then _mh_buy_hide() end if _G.mh_tab ~= 5 then _mh_log_hide() end if _G.mh_tab ~= 7 then _mh_piar_hide() end if _G.mh_tab ~= 8 then _mh_catch_hide() end if _G.mh_tab ~= 12 then _mh_rating_hide() end if _G.mh_tab ~= 6 then _mh_theme_hide() end if _G.mh_tab ~= 9 then _mh_about_hide() end if _G.mh_tab ~= 10 then _mh_premium_hide() end if _G.mh_tab ~= 13 then _mh_ads_hide() end 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 _mh_min_w, _mh_min_h = 760, 480 local _use_saved = _mwc and _mwc.w and _mwc.w >= _mh_min_w and _mwc.h and _mwc.h >= _mh_min_h local _mw = _use_saved and _mwc.w or 1387 local _mh = _use_saved and _mwc.h or 832 pcall(function() imgui.SetNextWindowSizeConstraints(imgui.ImVec2(_mh_min_w, _mh_min_h), imgui.ImVec2(100000, 100000)) end) local _mh_piar_edit_w, _mh_piar_edit_h = 1080, 760 local _mh_piar_size_forced = false if false and _G._mh_piar_editor_expand then if not _G._mh_piar_pre_edit_w then _G._mh_piar_pre_edit_w, _G._mh_piar_pre_edit_h = _mw, _mh end local _ew = math.max(_mw, _mh_piar_edit_w) local _eh = math.max(_mh, _mh_piar_edit_h) if _ew ~= _mw or _eh ~= _mh then pcall(function() imgui.SetNextWindowSize(imgui.ImVec2(_ew, _eh), imgui.Cond.Once) end) end _mh_piar_size_forced = true elseif false and _G._mh_piar_editor_restore then _G._mh_piar_editor_restore = false local _rw = _G._mh_piar_pre_edit_w or _mw local _rh = _G._mh_piar_pre_edit_h or _mh _G._mh_piar_pre_edit_w, _G._mh_piar_pre_edit_h = nil, nil pcall(function() imgui.SetNextWindowSize(imgui.ImVec2(_rw, _rh), imgui.Cond.Always) end) _G._mh_skip_winsave_frame = true _mh_piar_size_forced = true end if _mh_piar_size_forced then if _G._mh_win_reset then imgui.SetNextWindowPos(imgui.ImVec2(sizeX/2, sizeY/2), imgui.Cond.Always, imgui.ImVec2(0.5, 0.5)) _G._mh_win_reset = false else imgui.SetNextWindowPos(imgui.ImVec2(sizeX/2, sizeY/2), imgui.Cond.Once, imgui.ImVec2(0.5, 0.5)) end elseif _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 _G._mh_skip_winsave_frame then _G._mh_skip_winsave_frame = false elseif false and _G._mh_piar_editor_expand then elseif _rws.x > 760 and _rws.y > 480 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() do local _gh = 34 * d local _c1 = imgui.ColorConvertFloat4ToU32(imgui.ImVec4(ar*0.35, ag*0.35, ab*0.35, 0.55)) local _c2 = imgui.ColorConvertFloat4ToU32(imgui.ImVec4(ar*0.10, ag*0.10, ab*0.10, 0.00)) dl:AddRectFilledMultiColor( imgui.ImVec2(wp.x - 8*d, wp.y - 4*d), imgui.ImVec2(wp.x + hw - 8*d, wp.y - 4*d + _gh), _c1, _c1, _c2, _c2) end if _G._mh_fTitle then imgui.PushFont(_G._mh_fTitle) end imgui.TextColored(imgui.ImVec4(ar, ag, ab, 1), u8' MARKET HELPER') if _G._mh_fTitle then imgui.PopFont() end 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 = imgui.GetContentRegionAvail().x * 0.13 local mh_sidebar_items = { { icon = fa.SHOP, label = u8'\xd0\xdb\xcd\xce\xca', id = 1, accent = nil }, { icon = fa.WAREHOUSE, label = u8'\xcb\xc0\xc2\xca\xc8', id = 2, accent = nil }, { icon = fa.TAG, label = u8'\xcf\xd0\xce\xc4\xc0\xc6\xc0', id = 3, accent = nil }, { icon = fa.BASKET_SHOPPING, label = u8'\xd1\xca\xd3\xcf\xca\xc0', id = 4, accent = nil }, { icon = fa.BULLHORN or fa.TOWER_BROADCAST, label = u8'\xce\xc1\xda\xdf\xc2\xcb.', id = 13, accent = nil }, { icon = fa.CLOCK_ROTATE_LEFT, label = u8'\xcb\xce\xc3', id = 5, accent = nil }, { icon = fa.SQUARE_PLUS, label = u8'\xca\xc0\xcb\xdc\xca.', id = 11, accent = nil }, { icon = fa.TOWER_BROADCAST, label = u8'\xcf\xc8\xc0\xd0', id = 7, accent = nil }, { icon = fa.SWATCHBOOK, label = u8'\xc2\xc8\xc4', id = 6, accent = nil }, { icon = fa.LOCATION_CROSSHAIRS, label = u8'\xcb\xce\xc2\xcb\xdf', id = 8, accent = nil }, { icon = fa.AWARD, label = u8'\xd0\xc5\xc9\xd2\xc8\xcd\xc3', id = 12, accent = imgui.ImVec4(1.0, 0.72, 0.15, 1) }, { icon = fa.CIRCLE_INFO, label = u8'\xce \xd1\xca\xd0\xc8\xcf\xd2\xc5', id = 9, accent = nil }, { icon = fa.CROWN, label = u8'Premium', id = 10, accent = imgui.ImVec4(1.0, 0.82, 0.10, 1) }, } 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)) local _mh_sb_visible_n = 0 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 _mh_sb_visible_n = _mh_sb_visible_n + 1 end end if imgui.BeginChild('##mh_sidebar', imgui.ImVec2(sidebar_w, imgui.GetContentRegionAvail().y), true) then _dpn1w() imgui.Spacing() local _mh_sb_spacing_y = imgui.GetStyle().ItemSpacing.y local _mh_sb_avail_h = imgui.GetContentRegionAvail().y - _mh_sb_spacing_y * math.max(0, _mh_sb_visible_n - 1) local _mh_sb_btn_h = _mh_sb_avail_h / math.max(1, _mh_sb_visible_n) local _mh_sb_dl = imgui.GetWindowDrawList() 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 local _btn_r, _btn_g, _btn_b = ar, ag, ab if stab.accent then _btn_r, _btn_g, _btn_b = stab.accent.x, stab.accent.y, stab.accent.z end local _cell_pos = imgui.GetCursorScreenPos() local _cell_w = imgui.GetContentRegionAvail().x local clicked = imgui.InvisibleButton('##sbhit' .. stab.id, imgui.ImVec2(_cell_w, _mh_sb_btn_h)) local hovered = imgui.IsItemHovered() local _p_min = imgui.ImVec2(_cell_pos.x, _cell_pos.y) local _p_max = imgui.ImVec2(_cell_pos.x + _cell_w, _cell_pos.y + _mh_sb_btn_h) if is_active then local _bg_col = _mh_bc(_btn_r, _btn_g, _btn_b, 1) _mh_sb_dl:AddRectFilled(_p_min, _p_max, imgui.GetColorU32Vec4(_bg_col), 6*d) local _bar_col = imgui.GetColorU32Vec4(imgui.ImVec4(_btn_r, _btn_g, _btn_b, 1)) _mh_sb_dl:AddRectFilled(imgui.ImVec2(_p_min.x, _p_min.y), imgui.ImVec2(_p_min.x + 3*d, _p_max.y), _bar_col, 3*d) elseif hovered then local _bg_col = imgui.ImVec4(_btn_r*0.22, _btn_g*0.22, _btn_b*0.22, _G._mh_wa or 1) _mh_sb_dl:AddRectFilled(_p_min, _p_max, imgui.GetColorU32Vec4(_bg_col), 6*d) end local _icon_col, _label_col if is_active then local _lum = 0.299*_btn_r + 0.587*_btn_g + 0.114*_btn_b if _lum > 0.6 then _icon_col = imgui.ImVec4(0.08, 0.08, 0.08, 1) _label_col = imgui.ImVec4(0.08, 0.08, 0.08, 1) else _icon_col = imgui.ImVec4(1, 1, 1, 1) _label_col = imgui.ImVec4(1, 1, 1, 1) end elseif hovered then _icon_col = imgui.ImVec4(_btn_r, _btn_g, _btn_b, 0.9) _label_col = imgui.ImVec4(0.85, 0.85, 0.85, 1) else _icon_col = imgui.ImVec4(0.62, 0.62, 0.62, 1) _label_col = imgui.ImVec4(0.55, 0.55, 0.55, 1) end local _has_big_icon = _G._mh_fSidebarIcon ~= nil local _sb_ic_scale = tonumber(settings.interface.sidebar_icon_scale) or 1.0 if _has_big_icon then imgui.PushFont(_G._mh_fSidebarIcon); imgui.SetWindowFontScale(_sb_ic_scale) end local _icon_sz = imgui.CalcTextSize(stab.icon) if _has_big_icon then imgui.SetWindowFontScale(1.0); imgui.PopFont() end local _label_sz = imgui.CalcTextSize(stab.label) local _gap = 6*d local _block_w = _icon_sz.x + _gap + _label_sz.x local _row_x = _cell_pos.x + (_cell_w - _block_w) * 0.5 local _icon_x = _row_x local _icon_y = _cell_pos.y + (_mh_sb_btn_h - _icon_sz.y) * 0.5 local _label_x = _row_x + _icon_sz.x + _gap local _label_y = _cell_pos.y + (_mh_sb_btn_h - _label_sz.y) * 0.5 if _has_big_icon then imgui.PushFont(_G._mh_fSidebarIcon); imgui.SetWindowFontScale(_sb_ic_scale) end _mh_sb_dl:AddText(imgui.ImVec2(_icon_x, _icon_y), imgui.GetColorU32Vec4(_icon_col), stab.icon) if _has_big_icon then imgui.SetWindowFontScale(1.0); imgui.PopFont() end _mh_sb_dl:AddText(imgui.ImVec2(_label_x, _label_y), imgui.GetColorU32Vec4(_label_col), stab.label) if clicked then _G.mh_tab = stab.id _G._sw.inertia = 0; _G._sw.vel = 0; _G._sw.drag_y = 0 end ::_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 _mh_premium_tab_render() 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 == 13 then _mh_ads_tab_render() end if _G.mh_tab == 6 then _mh_theme_tab_render() end if _G.mh_tab == 9 then _mh_about_tab_render() end if false and _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), _ic_gear..' '.._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), _ic_star..' '.._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), _ic_gear..' '.._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), _ic_chrts..' '.._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), _ic_pen..' '.._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.TextColored(imgui.ImVec4(ar, ag, ab, 1), _ic_boxes..' '.._cyr5f(' РАЗМЕР ИКОНОК МЕНЮ')); imgui.Separator(); imgui.Spacing() imgui.TextDisabled(_cyr5f(' Текущий: ' .. string.format('%.2f', settings.interface.sidebar_icon_scale or 1.0) .. 'x')) imgui.PushItemWidth(-1) if imgui.SliderFloat('##sb_icon_scale_sl', sl.sb_icon_scale, 0.5, 2.0, '%.2f') then settings.interface.sidebar_icon_scale = sl.sb_icon_scale[0] _wfn7p() end imgui.PopItemWidth() 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)##sbicon_reset'), imgui.ImVec2(-1, 26*d)) then settings.interface.sidebar_icon_scale = 1.0 sl.sb_icon_scale[0] = 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), _ic_warn..' '.._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), _ic_lyr..' '.._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 > 760) and _sw.w/d or 900) end imgui.TextDisabled(_cyr5f(' Ширина:')); imgui.SameLine(); imgui.PushItemWidth(-1) if imgui.SliderFloat('##win_w', _G.sl_win_w, 760, 1600, '%.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 > 480) and _sw.h/d or 520) end imgui.TextDisabled(_cyr5f(' Высота:')); imgui.SameLine(); imgui.PushItemWidth(-1) if imgui.SliderFloat('##win_h', _G.sl_win_h, 480, 1200, '%.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), _ic_clk..' '.._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='РЕЙТИНГ'}, {id=10, label='Premium'}, } 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), _ic_alr..' '.._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), _ic_circc..' '.._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 false and _G.mh_tab == 7 then do imgui.Spacing() imgui.TextColored(imgui.ImVec4(ar,ag,ab,1), _ic_bolt..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), _ic_pen..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(_ic_tag..u8' Шаблон'); imgui.NextColumn() imgui.TextDisabled(_ic_gear..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 false and _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() if not MainWindow[0] then _mh_web_soft_hide() end if not MainWindow[0] then _mh_shops_soft_hide() end if not MainWindow[0] then _mh_wish_soft_hide() end if not MainWindow[0] then _mh_arb_soft_hide() end if not MainWindow[0] then _mh_pick_soft_hide() end if not MainWindow[0] then _mh_auto_soft_hide() end if not MainWindow[0] then _mh_sell_soft_hide() end if not MainWindow[0] then _mh_buy_soft_hide() end if not MainWindow[0] then _mh_log_soft_hide() end if not MainWindow[0] then _mh_piar_soft_hide() end if not MainWindow[0] then _mh_catch_soft_hide() end if not MainWindow[0] then _mh_rating_soft_hide() end if not MainWindow[0] then _mh_theme_soft_hide() end if not MainWindow[0] then _mh_about_soft_hide() end if not MainWindow[0] then _mh_premium_soft_hide() end if not MainWindow[0] then _mh_ads_soft_hide() end 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) local old_rounding = s.WindowRounding s.WindowRounding = (settings.overlay.rounding or 4) * d imgui.Begin('##mh_overlay_log', imgui.new.bool(true), imgui.WindowFlags.NoTitleBar + imgui.WindowFlags.NoCollapse + imgui.WindowFlags.NoScrollbar) _G._ltz8m() _G.ov_day_filter = (settings.overlay and settings.overlay.day_filter) or 1 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() local sum_sell_r = settings.overlay.sell_r or 0.3 local sum_sell_g = settings.overlay.sell_g or 0.9 local sum_sell_b = settings.overlay.sell_b or 0.3 local sum_buy_r = settings.overlay.buy_r or 0.3 local sum_buy_g = settings.overlay.buy_g or 0.6 local sum_buy_b = settings.overlay.buy_b or 1.0 imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(sum_sell_r, sum_sell_g, sum_sell_b, 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(sum_buy_r, sum_buy_g, sum_buy_b, 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 s.WindowRounding = old_rounding 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) imgui.OnFrame(function() return _G._mh_cloud_open == true end, function() local d = tonumber(settings.general and settings.general.custom_dpi) or 1 local sizeX3, sizeY3 = imgui.GetIO().DisplaySize.x, imgui.GetIO().DisplaySize.y if not _G._mh_cloud_win_pos then _G._mh_cloud_win_pos = {x = sizeX3/2, y = sizeY3/2} end local CloudWindowMH = imgui.new.bool(true) imgui.SetNextWindowPos(imgui.ImVec2(_G._mh_cloud_win_pos.x, _G._mh_cloud_win_pos.y), imgui.Cond.Once, imgui.ImVec2(0.5, 0.5)) imgui.SetNextWindowSize(imgui.ImVec2(720*d, 620*d), imgui.Cond.FirstUseEver) imgui.Begin(fa.CLOUD..' '.._cyr5f('Облако пресетов скупки##mh_cloud_win'), CloudWindowMH, imgui.WindowFlags.NoCollapse) _ltz8m() if not CloudWindowMH[0] then _G._mh_cloud_open = false end local cw3 = imgui.GetWindowContentRegionWidth() if not _G._mh_cloud_srch_buf then _G._mh_cloud_srch_buf = imgui.new.char[64]('') end if not _G._mh_cloud_budget_buf then _G._mh_cloud_budget_buf = imgui.new.char[24]('') end imgui.PushItemWidth(cw3 * 0.32) if imgui.InputTextWithHint('##mh_cloud_srch', _cyr5f('Поиск: название / ник...'), _G._mh_cloud_srch_buf, 64) then local _raw = ffi.string(_G._mh_cloud_srch_buf) local _ok_dec, _dec = pcall(function() return require('encoding').UTF8:decode(_raw) end) _G._mh_cloud_search = (_ok_dec and _dec or _raw):match('^%s*(.-)%s*$') end imgui.PopItemWidth() imgui.SameLine(0, 6*d) imgui.PushItemWidth(cw3 * 0.16) if imgui.InputTextWithHint('##mh_cloud_budget', _cyr5f('Бюджет $'), _G._mh_cloud_budget_buf, 24) then local _raw_b = ffi.string(_G._mh_cloud_budget_buf):gsub('[^%d]', '') _G._mh_cloud_budget_max = tonumber(_raw_b) or 0 end imgui.PopItemWidth() if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Показывать только пресеты, укладывающиеся в этот бюджет')) end imgui.SameLine(0, 6*d) local _srv_names = {_cyr5f('Все сервера')} for i = 2, #ARZ_SERVERS do _srv_names[i] = ARZ_SERVERS[i].name end if not _G._mh_cloud_srv_sel then _G._mh_cloud_srv_sel = imgui.new.int(0) end imgui.PushItemWidth(cw3 * 0.2) if imgui.BeginCombo('##mh_cloud_srv', _srv_names[_G._mh_cloud_srv_sel[0]+1] or _srv_names[1]) then for i, nm in ipairs(_srv_names) do if imgui.Selectable(nm..'##mhcs'..i, (i-1)==_G._mh_cloud_srv_sel[0]) then _G._mh_cloud_srv_sel[0] = i - 1 end end imgui.EndCombo() end imgui.PopItemWidth() imgui.SameLine(0, 6*d) local _sort_lbls = {downloads = _cyr5f('Скачивания'), budget = _cyr5f('Бюджет'), rating = _cyr5f('Рейтинг'), new = _cyr5f('Новые')} local _sort_order = {'downloads', 'budget', 'rating', 'new'} imgui.PushItemWidth(cw3 * 0.16) if imgui.BeginCombo('##mh_cloud_sort', _sort_lbls[_G._mh_cloud_sort] or _sort_lbls.downloads) then for _, sk in ipairs(_sort_order) do if imgui.Selectable(_sort_lbls[sk]..'##mhcsort'..sk, sk==_G._mh_cloud_sort) then _G._mh_cloud_sort = sk end end imgui.EndCombo() end imgui.PopItemWidth() imgui.SameLine(0, 6*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.08,0.30,0.46,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.12,0.46,0.68,1)) if imgui.Button(fa.MAGNIFYING_GLASS..'##mh_cloud_apply', imgui.ImVec2(30*d, 0)) then local _sel_i = _G._mh_cloud_srv_sel[0] + 1 _G.arz_srv_sel = _G.arz_srv_sel or imgui.new.int(0) _mh_cloud_fetch_list(1) end imgui.PopStyleColor(2) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Применить фильтры')) end imgui.SameLine(0, 6*d) local _mine_on = _G._mh_cloud_mine_only == true imgui.PushStyleColor(imgui.Col.Button, _mine_on and _mh_bc(0.12,0.42,0.62,1) or _mh_bc(0.16,0.16,0.16,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, _mine_on and imgui.ImVec4(0.18,0.56,0.78,1) or imgui.ImVec4(0.24,0.24,0.24,1)) if imgui.Button(fa.USER..' '.._cyr5f('Мои##mh_cloud_mine'), imgui.ImVec2(0, 0)) then _G._mh_cloud_mine_only = not _mine_on _mh_cloud_fetch_list(1) end imgui.PopStyleColor(2) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Показать только пресеты, опубликованные вами')) end imgui.Separator() if _G._mh_cloud_loading then imgui.TextColored(imgui.ImVec4(1,0.8,0.2,1), _cyr5f('Загрузка...')) elseif _G._mh_cloud_error then imgui.TextColored(imgui.ImVec4(1,0.4,0.4,1), _cyr5f('Ошибка: ')..tostring(_G._mh_cloud_error)) elseif #(_G._mh_cloud_list or {}) == 0 then imgui.TextDisabled(_cyr5f(' Пресетов не найдено. Измените фильтры или загрузите свой первым!')) else imgui.TextDisabled(_cyr5f('Найдено: ')..tostring(_G._mh_cloud_total or 0)) imgui.Spacing() local _card_w = cw3 - 4*d if imgui.BeginChild('##mh_cloud_list_scroll', imgui.ImVec2(-1, -34*d), false) then _dpn1w() for _, cp in ipairs(_G._mh_cloud_list) do imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.10,0.10,0.12,1)) imgui.PushStyleColor(imgui.Col.Border, imgui.ImVec4(0.28,0.28,0.32,0.8)) if imgui.BeginChild('##mh_cp_card'..tostring(cp.id), imgui.ImVec2(_card_w, 88*d), true) then local _srv_nm = _cyr5f('Все сервера') for i = 2, #ARZ_SERVERS do if ARZ_SERVERS[i].id == cp.server_id then _srv_nm = ARZ_SERVERS[i].name; break end end local _title_str = u8(cp.title or '?') local _title_sp = imgui.GetCursorScreenPos() local _title_lh = imgui.GetTextLineHeight() local _title_tw = imgui.CalcTextSize(_title_str).x imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0,0,0,0)) if imgui.Selectable('##mhcptitle'..tostring(cp.id), false, 0, imgui.ImVec2(_title_tw + 6*d, _title_lh)) then _mh_cloud_open_preset_view(cp) end imgui.PopStyleColor() if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Нажмите, чтобы посмотреть состав пресета')) end imgui.GetWindowDrawList():AddText(_title_sp, 0xFFFFDDD9, _title_str) imgui.SameLine(0, 8*d) imgui.TextColored(imgui.ImVec4(0.5,0.7,1,0.9), fa.STORE..' '..u8(_srv_nm)) imgui.TextColored(imgui.ImVec4(0.7,0.7,0.7,1), fa.USER..' '..u8(cp.nick or '?')) imgui.SameLine(0, 14*d) imgui.TextColored(imgui.ImVec4(1,0.85,0.4,1), fa.COINS..' '.._vnh1j(cp.budget or 0)) imgui.SameLine(0, 14*d) imgui.TextDisabled(_cyr5f('Товаров: ')..tostring(cp.item_count or #(cp.items or {}))) imgui.TextColored(imgui.ImVec4(0.6,0.85,1,1), fa.DOWNLOAD..' '..tostring(cp.downloads or 0)) imgui.SameLine(0, 14*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.16,0.16,0.20,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.24,0.24,0.30,1)) if imgui.Button(_ic_eye..' '..tostring(cp.views or 0)..'##mhcpview'..tostring(cp.id), imgui.ImVec2(60*d, 0)) then _mh_cloud_open_preset_view(cp) end imgui.PopStyleColor(2) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Посмотреть состав пресета')) end imgui.SameLine(0, 8*d) local _my_vote = _G._mh_cloud_my_votes[cp.id] imgui.PushStyleColor(imgui.Col.Button, _my_vote==1 and _mh_bc(0.10,0.42,0.14,1) or _mh_bc(0.14,0.14,0.14,1)) if imgui.Button(fa.THUMBS_UP..' '..tostring(cp.likes or 0)..'##mhcpvup'..tostring(cp.id), imgui.ImVec2(64*d, 0)) then _mh_cloud_vote_preset(cp, 1) end imgui.PopStyleColor() imgui.SameLine(0, 6*d) imgui.PushStyleColor(imgui.Col.Button, _my_vote==-1 and _mh_bc(0.42,0.10,0.10,1) or _mh_bc(0.14,0.14,0.14,1)) if imgui.Button(fa.THUMBS_DOWN..' '..tostring(cp.dislikes or 0)..'##mhcpvdn'..tostring(cp.id), imgui.ImVec2(64*d, 0)) then _mh_cloud_vote_preset(cp, -1) end imgui.PopStyleColor() local _is_owner = (function() local _mn = _mh_get_live_nick() if _mh_is_fake_nick(_mn) then return false end return tostring(cp.nick or ''):lower() == tostring(_mn):lower() end)() local _add_w = _is_owner and (cw3 - 230*d) or (cw3 - 190*d) imgui.SameLine(_add_w) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.10,0.42,0.14,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.16,0.60,0.20,1)) if imgui.Button(fa.CIRCLE_PLUS..' '.._cyr5f('Добавить себе##mhcpadd'..tostring(cp.id)), imgui.ImVec2(180*d, 0)) then _mh_cloud_add_preset_to_mine(cp) end imgui.PopStyleColor(2) if _is_owner then imgui.SameLine(0, 6*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,1)) if imgui.Button(fa.TRASH_CAN..'##mhcpdel'..tostring(cp.id), imgui.ImVec2(30*d, 0)) then _mh_cloud_delete_preset(cp) end imgui.PopStyleColor(2) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Удалить свой пресет из облака')) end end end imgui.EndChild() imgui.PopStyleColor(2) imgui.Spacing() end imgui.EndChild() end local _pages = math.max(1, math.ceil((_G._mh_cloud_total or 0) / 30)) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.15,0.15,0.15,1)) if imgui.Button(fa.ANGLE_LEFT..'##mh_cloud_prev', imgui.ImVec2(30*d,0)) and (_G._mh_cloud_page or 1) > 1 then _mh_cloud_fetch_list((_G._mh_cloud_page or 1) - 1) end imgui.SameLine(0,4*d) imgui.TextDisabled(tostring(_G._mh_cloud_page or 1)..' / '..tostring(_pages)) imgui.SameLine(0,4*d) if imgui.Button(fa.ANGLE_RIGHT..'##mh_cloud_next', imgui.ImVec2(30*d,0)) and (_G._mh_cloud_page or 1) < _pages then _mh_cloud_fetch_list((_G._mh_cloud_page or 1) + 1) end imgui.PopStyleColor() end imgui.End() end) imgui.OnFrame(function() return _G._mh_cloud_view_open == true and _G._mh_cloud_view_preset ~= nil end, function() local d = tonumber(settings.general and settings.general.custom_dpi) or 1 local sizeX4, sizeY4 = imgui.GetIO().DisplaySize.x, imgui.GetIO().DisplaySize.y local pop_w4 = math.min(sizeX4 - 16, 620 * d) local pop_h4 = math.min(sizeY4 - 20, sizeY4 * 0.85) imgui.SetNextWindowSize(imgui.ImVec2(pop_w4, pop_h4), imgui.Cond.FirstUseEver) imgui.SetNextWindowPos(imgui.ImVec2(sizeX4/2, sizeY4/2), imgui.Cond.FirstUseEver, imgui.ImVec2(0.5, 0.5)) local cp = _G._mh_cloud_view_preset local ViewWinMH = imgui.new.bool(true) imgui.Begin(fa.LAYER_GROUP..' '.._cyr5f('Состав пресета##mh_cp_view_win'), ViewWinMH, imgui.WindowFlags.NoCollapse) _ltz8m() if not ViewWinMH[0] then _G._mh_cloud_view_open = false end local cwv = imgui.GetWindowContentRegionWidth() imgui.TextColored(imgui.ImVec4(0.9,0.94,1,1), u8(cp.title or '?')) local _srv_nm_v = _cyr5f('Все сервера') for i = 2, #ARZ_SERVERS do if ARZ_SERVERS[i].id == cp.server_id then _srv_nm_v = ARZ_SERVERS[i].name; break end end imgui.TextColored(imgui.ImVec4(0.7,0.7,0.7,1), fa.USER..' '..u8(cp.nick or '?')) imgui.SameLine(0, 14*d) imgui.TextColored(imgui.ImVec4(0.5,0.7,1,0.9), fa.STORE..' '..u8(_srv_nm_v)) imgui.Separator() local _items_v = cp.items or {} local _sum_calc = 0 for _, it in ipairs(_items_v) do _sum_calc = _sum_calc + (tonumber(it.price) or 0) * (tonumber(it.qty) or 1) end imgui.TextColored(imgui.ImVec4(1,0.85,0.4,1), fa.COINS..' '.._cyr5f('Сумма скупки: $').._kcr3y(_sum_calc)) imgui.SameLine(0, 18*d) imgui.TextDisabled(_cyr5f('Товаров: ')..tostring(#_items_v)) imgui.Spacing() imgui.TextColored(imgui.ImVec4(0.6,0.85,1,1), fa.DOWNLOAD..' '..tostring(cp.downloads or 0)) imgui.SameLine(0, 14*d) imgui.TextColored(imgui.ImVec4(0.7,0.85,0.7,1), _ic_eye..' '..tostring(cp.views or 0)) imgui.SameLine(0, 14*d) imgui.TextColored(imgui.ImVec4(0.5,0.85,0.5,1), fa.THUMBS_UP..' '..tostring(cp.likes or 0)) imgui.SameLine(0, 6*d) imgui.TextColored(imgui.ImVec4(0.85,0.5,0.5,1), fa.THUMBS_DOWN..' '..tostring(cp.dislikes or 0)) imgui.Separator() imgui.Spacing() local _total_h_v = imgui.GetWindowHeight() local _title_h_v = imgui.GetCursorPosY() local _footer_h_v = 40 * d + 8 * d local _content_h_v = _total_h_v - _title_h_v - _footer_h_v - 6 * d imgui.PushStyleColor(imgui.Col.ChildBg, imgui.ImVec4(0.07,0.07,0.085,1)) if imgui.BeginChild('##mh_cp_view_items', imgui.ImVec2(-1, _content_h_v), true) then _dpn1w() if #_items_v == 0 then imgui.Spacing(); imgui.TextDisabled(_cyr5f(' Пресет пуст.')) else local col_w_v = {cwv*0.46, cwv*0.20, cwv*0.14, cwv*0.20} imgui.Columns(4, '##mh_cpv_hdr', false) for ci, cwv_v in ipairs(col_w_v) do imgui.SetColumnWidth(ci - 1, cwv_v) end local hc_v = imgui.ImVec4(0.55,0.55,0.62,1) imgui.TextColored(hc_v, _cyr5f(' Товар')); imgui.NextColumn() imgui.TextColored(hc_v, _cyr5f('Цена')); imgui.NextColumn() imgui.TextColored(hc_v, _cyr5f('Кол-во')); imgui.NextColumn() imgui.TextColored(hc_v, _cyr5f('Сумма')); imgui.NextColumn() imgui.Columns(1) imgui.Separator() local _sorted_v = {} for i, it in ipairs(_items_v) do _sorted_v[i] = it end table.sort(_sorted_v, function(a, b) local sa = (tonumber(a.price) or 0) * (tonumber(a.qty) or 1) local sb = (tonumber(b.price) or 0) * (tonumber(b.qty) or 1) return sa > sb end) for ii, it in ipairs(_sorted_v) do local _pr_v = tonumber(it.price) or 0 local _qt_v = tonumber(it.qty) or 1 local _sum_v = _pr_v * _qt_v local row_bg_v = (ii % 2 == 0) and imgui.ImVec4(0.12,0.12,0.14,0.6) or imgui.ImVec4(0,0,0,0) imgui.PushStyleColor(imgui.Col.ChildBg, row_bg_v) imgui.Columns(4, '##mh_cpv_row'..ii, false) for ci, cwv_v in ipairs(col_w_v) do imgui.SetColumnWidth(ci - 1, cwv_v) end imgui.Text(u8(' ' .. tostring(it.name or '?'))) imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.7,0.85,1,1), '$' .. _kcr3y(_pr_v)) imgui.NextColumn() imgui.Text(tostring(_qt_v) .. ' ' .. _cyr5f('шт.')) imgui.NextColumn() imgui.TextColored(imgui.ImVec4(0.5,0.9,0.5,1), '$' .. _kcr3y(_sum_v)) imgui.NextColumn() imgui.Columns(1) imgui.PopStyleColor() end end end imgui.EndChild() imgui.PopStyleColor() imgui.Spacing() local _is_owner_v = (function() local _mn = _mh_get_live_nick() if _mh_is_fake_nick(_mn) then return false end return tostring(cp.nick or ''):lower() == tostring(_mn):lower() end)() local _btn_h_v = 32 * d local _close_w_v = 90 * d local _add_w_v = _is_owner_v and (cwv - _close_w_v - 40*d - 12*d) or (cwv - _close_w_v - 8*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.10,0.42,0.14,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.16,0.60,0.20,1)) if imgui.Button(fa.CIRCLE_PLUS..' '.._cyr5f('Добавить себе##mhcpvadd'), imgui.ImVec2(_add_w_v, _btn_h_v)) then _mh_cloud_add_preset_to_mine(cp) _G._mh_cloud_view_open = false end imgui.PopStyleColor(2) if _is_owner_v then imgui.SameLine(0, 6*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,1)) if imgui.Button(fa.TRASH_CAN..'##mhcpvdel', imgui.ImVec2(40*d, _btn_h_v)) then _mh_cloud_delete_preset(cp) _G._mh_cloud_view_open = false end imgui.PopStyleColor(2) if imgui.IsItemHovered() then imgui.SetTooltip(_cyr5f('Удалить свой пресет из облака')) end end imgui.SameLine(0, 6*d) imgui.PushStyleColor(imgui.Col.Button, _mh_bc(0.16,0.16,0.16,1)) imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.24,0.24,0.24,1)) if imgui.Button(_cyr5f('Закрыть##mhcpvclose'), imgui.ImVec2(_close_w_v, _btn_h_v)) then _G._mh_cloud_view_open = false end imgui.PopStyleColor(2) imgui.End() end) addEventHandler('onScriptTerminate', function(sc, res) if sc ~= thisScript() then return end if _mh_web_jar then for _, _bid in ipairs({9001,9002,9003,9004,9005,9006,9007,9008,9009,9010,9011,9012,9013,9014,9015,9016}) do pcall(function() _mh_web_jar.deleteBrowser(_bid) end) end 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) local _mh_mainwin_was_open = true imgui.OnFrame(function() return true end, function() local _now_open = MainWindow[0] == true if _mh_mainwin_was_open and not _now_open then pcall(_mh_web_soft_hide) pcall(_mh_shops_soft_hide) pcall(_mh_wish_soft_hide) pcall(_mh_arb_soft_hide) pcall(_mh_pick_soft_hide) pcall(_mh_auto_soft_hide) pcall(_mh_sell_soft_hide) pcall(_mh_buy_soft_hide) pcall(_mh_log_soft_hide) pcall(_mh_piar_soft_hide) pcall(_mh_catch_soft_hide) pcall(_mh_rating_soft_hide) pcall(_mh_theme_soft_hide) pcall(_mh_about_soft_hide) pcall(_mh_premium_soft_hide) pcall(_mh_ads_soft_hide) end _mh_mainwin_was_open = _now_open end) imgui.OnFrame( function() return fh_mkt_cp_deep_scanning == true or fh_mkt_auto_deep_scanning == true end, function() local d = tonumber(settings.general and settings.general.custom_dpi) or 1 local is_auto = (fh_mkt_auto_deep_scanning == true) local done = is_auto and (fh_mkt_auto_deep_done or 0) or (fh_mkt_cp_deep_done or 0) local now = os.clock() if not _G._mh_hud_t or now - _G._mh_hud_t > 1.0 then _G._mh_hud_t = now local n = 0 for _ in pairs(is_auto and fh_mkt_auto or fh_mkt_prices) do n = n + 1 end _G._mh_hud_tot = n end local tot = _G._mh_hud_tot or 0 local sw = imgui.GetIO().DisplaySize.x imgui.SetNextWindowPos(imgui.ImVec2(sw / 2, 12 * d), imgui.Cond.Always, imgui.ImVec2(0.5, 0)) imgui.SetNextWindowSize(imgui.ImVec2(300 * d, 0), imgui.Cond.Always) imgui.SetNextWindowBgAlpha(0.85) local _open = imgui.new.bool(true) if imgui.Begin('##mh_deep_hud', _open, imgui.WindowFlags.NoTitleBar + imgui.WindowFlags.NoResize + imgui.WindowFlags.NoMove + imgui.WindowFlags.NoScrollbar + imgui.WindowFlags.NoCollapse + imgui.WindowFlags.NoSavedSettings + imgui.WindowFlags.NoFocusOnAppearing + imgui.WindowFlags.NoNav) then local title = is_auto and u8'Углублённый скан авто: ' or u8'Углублённый скан ЦР: ' imgui.TextColored(imgui.ImVec4(1.0, 0.70, 0.10, 1), title .. done .. ((tot > 0) and ('/' .. tot) or '')) if imgui.Button(u8'СТОП##mhdeephudstop', imgui.ImVec2(-1, 0)) then fh_mkt_cp_deep_scanning = false fh_mkt_auto_deep_scanning = false end end imgui.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 settings.active_buy_preset = fh_ab_preset_idx 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 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 )