Загрузка данных


script_name('Sufferd Market Helper')
script_author('OpenAI')
script_version('1.0.0')

local imgui = require 'mimgui'
local sampev = require 'samp.events'
local encoding = require 'encoding'

encoding.default = 'CP1251'
u8 = encoding.UTF8

market_selling = {}
market_buying = {}

local window = imgui.new.bool(false)
local scanning = imgui.new.bool(false)
local cfg_commission = imgui.new.int(4)
local results = {}
local result_signature = ''
local market_revision = 0
local status_text = 'Сканирование выключено'
local last_dialog_signature = ''

local function safe_string(value)
    if value == nil then
        return ''
    end
    return tostring(value)
end

local function trim(value)
    value = safe_string(value)
    return value:gsub('^%s+', ''):gsub('%s+$', '')
end

local function strip_colors(value)
    return safe_string(value):gsub('{%x%x%x%x%x%x%x%x}', ''):gsub('{%x%x%x%x%x%x}', '')
end

local function contains_any(value, variants)
    value = safe_string(value)
    for _, part in ipairs(variants) do
        if value:find(part, 1, true) then
            return true
        end
    end
    return false
end

local function split_tabs(line)
    local fields = {}
    for field in (safe_string(line) .. '\t'):gmatch('(.-)\t') do
        fields[#fields + 1] = field
    end
    return fields
end

local function parse_price(value)
    local price = strip_colors(trim(value)):gsub('[,%$%.]', '')
    price = price:gsub('%s+', '')
    return tonumber(price)
end

local function normalize_item(value)
    return trim(strip_colors(value)):gsub('%s+', ' ')
end

local function remove_stall_records(storage, stall)
    for item, records in pairs(storage) do
        local filtered = {}
        for _, record in ipairs(records) do
            if record.stall ~= stall then
                filtered[#filtered + 1] = record
            end
        end
        if #filtered > 0 then
            storage[item] = filtered
        else
            storage[item] = nil
        end
    end
end

local function add_record(storage, item, stall, price, amount)
    if item == '' or stall == '' or not price or price < 0 then
        return
    end
    storage[item] = storage[item] or {}
    storage[item][#storage[item] + 1] = {
        stall = stall,
        price = price,
        amount = amount
    }
end

local function parse_market_dialog(title, text)
    title = trim(strip_colors(title))
    text = safe_string(text)
    local sale = contains_any(title, {'Продаж', 'продаж', 'ПРОДАЖ'})
    local buy = contains_any(title, {'Скуп', 'скуп', 'СКУП'})
    if not sale and not buy then
        return false
    end
    if title == '' or not text:find('\t', 1, true) then
        return false
    end
    local stall = title
    local target = sale and market_selling or market_buying
    remove_stall_records(target, stall)
    local count = 0
    for line in safe_string(text):gmatch('[^\r\n]+') do
        local fields = split_tabs(line)
        if #fields >= 3 then
            local item = normalize_item(fields[1])
            local amount = trim(fields[2])
            local price = parse_price(fields[3])
            if item ~= '' and price then
                local is_header = contains_any(item, {'Название', 'название', 'Предмет', 'предмет'}) or contains_any(fields[3], {'Цена', 'цена'})
                if not is_header then
                    add_record(target, item, stall, price, amount)
                    count = count + 1
                end
            end
        end
    end
    status_text = (sale and 'Продажа: ' or 'Скупка: ') .. stall .. ' [' .. tostring(count) .. ']'
    return true
end

local function get_best_record(records, mode)
    local best
    for _, record in ipairs(records or {}) do
        if type(record) == 'table' and type(record.price) == 'number' then
            if not best or (mode == 'min' and record.price < best.price) or (mode == 'max' and record.price > best.price) then
                best = record
            end
        end
    end
    return best
end

local function rebuild_results()
    local calculated = {}
    local commission = tonumber(cfg_commission[0]) or 4
    for item, selling_records in pairs(market_selling) do
        local buying_records = market_buying[item]
        if buying_records then
            local seller = get_best_record(selling_records, 'min')
            local buyer = get_best_record(buying_records, 'max')
            if seller and buyer then
                local money_received = buyer.price * (1 - commission / 100)
                local profit = money_received - seller.price
                if profit > 0 then
                    calculated[#calculated + 1] = {
                        item = item,
                        seller = seller,
                        buyer = buyer,
                        profit = profit,
                        received = money_received
                    }
                end
            end
        end
    end
    table.sort(calculated, function(a, b)
        return a.profit > b.profit
    end)
    results = calculated
    result_signature = tostring(market_revision) .. ':' .. tostring(commission)
end

local function format_money(value)
    local number = math.floor(tonumber(value) or 0)
    local text = tostring(number)
    local sign = ''
    if text:sub(1, 1) == '-' then
        sign = '-'
        text = text:sub(2)
    end
    local formatted = text
    while true do
        local replaced, count = formatted:gsub('^(%d+)(%d%d%d)', '%1,%2')
        formatted = replaced
        if count == 0 then
            break
        end
    end
    return sign .. formatted .. '$'
end

local function clear_market()
    market_selling = {}
    market_buying = {}
    results = {}
    result_signature = ''
    last_dialog_signature = ''
    market_revision = market_revision + 1
    status_text = 'База рынка очищена'
end

local function toggle_scanning()
    scanning[0] = not scanning[0]
    if not scanning[0] then
        clear_market()
        status_text = 'Сканирование выключено'
    else
        market_revision = market_revision + 1
        result_signature = ''
        status_text = 'Сканирование включено'
    end
end

local function draw_results()
    imgui.Separator()
    imgui.Text(u8('Выгодные сделки: ' .. tostring(#results)))
    imgui.BeginChild('market_results', imgui.ImVec2(0, 0), true)
    if #results == 0 then
        imgui.TextColored(imgui.ImVec4(0.65, 0.65, 0.65, 1.0), u8('Подходящих сделок пока нет'))
    else
        for index, deal in ipairs(results) do
            local line = string.format('%s | Купить у: %s (%s) -> Продать: %s (%s) | Профит: +%s', deal.item, deal.seller.stall, format_money(deal.seller.price), deal.buyer.stall, format_money(deal.buyer.price), format_money(deal.profit))
            imgui.PushStyleColor(imgui.Col.Text, imgui.ImVec4(0.35, 1.0, 0.45, 1.0))
            imgui.TextWrapped(u8(line))
            imgui.PopStyleColor()
            if index < #results then
                imgui.Separator()
            end
        end
    end
    imgui.EndChild()
end

imgui.OnFrame(function()
    return window[0]
end, function()
    imgui.SetNextWindowSize(imgui.ImVec2(980, 560), imgui.Cond.FirstUseEver)
    imgui.SetNextWindowSizeConstraints(imgui.ImVec2(520, 300), imgui.ImVec2(1800, 1200))
    if imgui.Begin('Sufferd Market Helper', window, imgui.WindowFlags.NoCollapse) then
        local scan_label = scanning[0] and u8('Сканирование рынка [ВКЛ]') or u8('Сканирование рынка [ВЫКЛ]')
        if imgui.Button(scan_label, imgui.ImVec2(-1, 42)) then
            toggle_scanning()
        end
        imgui.PushItemWidth(260)
        imgui.SliderInt(u8('Комиссия сервера'), cfg_commission, 1, 5, '%d%%')
        imgui.PopItemWidth()
        imgui.SameLine()
        imgui.Text(u8(status_text))
        local new_signature = tostring(market_revision) .. ':' .. tostring(cfg_commission[0])
        if new_signature ~= result_signature then
            rebuild_results()
        end
        draw_results()
    end
    imgui.End()
end)

function sampev.onShowDialog(dialogId, style, title, button1, button2, text)
    if not scanning[0] or tonumber(style) ~= 5 then
        return
    end
    title = u8(safe_string(title))
    text = u8(safe_string(text))
    local current_signature = safe_string(dialogId) .. '|' .. title .. '|' .. text
    if current_signature == last_dialog_signature then
        return
    end
    last_dialog_signature = current_signature
    if parse_market_dialog(title, text) then
        market_revision = market_revision + 1
        rebuild_results()
    end
end

function main()
    repeat
        wait(100)
    until isSampAvailable()
    sampRegisterChatCommand('sufferd', function()
        window[0] = not window[0]
    end)
    while true do
        wait(500)
        if scanning[0] then
            rebuild_results()
        end
    end
end