Загрузка данных
require('samp.synchronization')
local sampfuncs = require('sampfuncs')
local raknet = require('samp.raknet')
local vector = require('vector3d')
local memory = require('memory')
local ffi = require('ffi')
local inicfg = require('inicfg')
local imgui = require('mimgui')
local encoding = require('encoding')
encoding.default = 'CP1251'
local u8 = encoding.UTF8
ffi.cdef[[
void keybd_event(uint8_t bVk, uint8_t bScan, uint32_t dwFlags, uintptr_t dwExtraInfo);
]]
function pressKeyDown(vk)
ffi.C.keybd_event(vk, 0, 0, 0)
end
function releaseKey(vk)
ffi.C.keybd_event(vk, 0, 2, 0)
end
local directIni = 'r-realtor by @purpurins.ini'
local ini = inicfg.load({
main = {
bar = true,
barX = 50,
barY = 500
},
catch = {
safeX = 0.0,
safeY = 0.0,
safeZ = 0.0,
curdStep = 0.7,
rtpStep = 9,
interiorX = -1297.3,
interiorY = -416.1,
interiorZ = 1001.0,
outsideX = -425.8,
outsideY = -1341.4,
outsideZ = 40.5,
checkRadius = 5.0,
exitTimeout = 10,
routeDelay = 50,
priceLimit = 200000,
autoPayday = false,
manualElevator = false,
apartmentIndex = 1
}
}, directIni)
inicfg.save(ini, directIni)
function SaveCfg()
inicfg.save(ini, directIni)
end
local teleport = {
bar_move = false,
boolean = false,
persent = 0,
packet = 0
}
local catch = {
active = false,
status = ''
}
local apartment = {
hunting = false,
items = {},
target = nil,
waitingElevator = false,
waitingBuy = false
}
local ECONOM_PICKUPS = {
{x = 2321.2, y = -2454.8, z = 1009.4},
{x = 2320.9, y = -2459.2, z = 1009.4},
{x = 2330.1, y = -2454.9, z = 1009.4},
{x = 2330.0, y = -2458.9, z = 1009.4}
}
local KVARTIRA_PICKUPS = {
{x = 3031.0, y = 1704.3, z = 997.5},
{x = 3028.7, y = 1707.1, z = 997.5},
{x = 3026.1, y = 1707.2, z = 997.5},
{x = 3023.8, y = 1704.2, z = 997.5}
}
-- CP1251 encoded strings for packet matching
local CP1251 = {
etazh = string.char(253, 242, 224, 230),
radare = string.char(240, 224, 228, 224, 240, 229),
svoboden = string.char(241, 226, 238, 225, 238, 228, 229, 237),
podyezd = string.char(207, 238, 228, 252, 229, 231, 228),
vzaimodeystvovat = string.char(194, 231, 224, 232, 236, 238, 228, 229, 233, 241, 242, 226, 238, 226, 224, 242, 252)
}
local showMenu = imgui.new.bool(false)
local showSettings = imgui.new.bool(false)
local buf = {
curdStep = imgui.new.float(ini.catch.curdStep),
rtpStep = imgui.new.int(ini.catch.rtpStep),
checkRadius = imgui.new.float(ini.catch.checkRadius),
exitTimeout = imgui.new.int(ini.catch.exitTimeout),
routeDelay = imgui.new.int(ini.catch.routeDelay),
priceLimit = imgui.new.int(ini.catch.priceLimit),
autoPayday = imgui.new.bool(ini.catch.autoPayday),
manualElevator = imgui.new.bool(ini.catch.manualElevator),
apartmentIndex = imgui.new.int(ini.catch.apartmentIndex),
interiorX = imgui.new.float(ini.catch.interiorX),
interiorY = imgui.new.float(ini.catch.interiorY),
interiorZ = imgui.new.float(ini.catch.interiorZ),
outsideX = imgui.new.float(ini.catch.outsideX),
outsideY = imgui.new.float(ini.catch.outsideY),
outsideZ = imgui.new.float(ini.catch.outsideZ),
safeX = imgui.new.float(ini.catch.safeX),
safeY = imgui.new.float(ini.catch.safeY),
safeZ = imgui.new.float(ini.catch.safeZ),
barShow = imgui.new.bool(ini.main.bar)
}
local ROUTE_1 = {
{x=-1295.7156,y=-416.8015,z=1001.0000},
{x=-1295.7213,y=-417.3696,z=1001.0000},
{x=-1295.7269,y=-417.9422,z=1001.0000},
{x=-1295.7336,y=-418.6414,z=1001.0000},
{x=-1295.7404,y=-419.3116,z=1001.0000},
{x=-1295.7472,y=-419.9932,z=1001.0000},
{x=-1295.7537,y=-420.6914,z=1001.0000},
{x=-1295.7604,y=-421.2037,z=1001.0000},
{x=-1296.0499,y=-421.8296,z=1001.0000},
{x=-1296.4739,y=-422.2974,z=1001.0000},
{x=-1297.0659,y=-422.6009,z=1001.0000},
{x=-1297.5638,y=-422.6944,z=1001.0000},
{x=-1298.2347,y=-422.3384,z=1001.0000},
{x=-1298.7526,y=-421.9099,z=1001.0000},
{x=-1299.2393,y=-421.4688,z=1001.0000},
{x=-1299.8873,y=-421.4061,z=1001.0000},
{x=-1300.5593,y=-421.4308,z=1001.0000},
{x=-1301.2802,y=-421.4572,z=1001.0000},
{x=-1301.9220,y=-421.4883,z=1001.0000},
{x=-1302.5580,y=-421.7512,z=1001.0000},
{x=-1302.7153,y=-422.3867,z=1001.0000},
{x=-1302.6377,y=-423.0829,z=1001.0000},
{x=-1302.8138,y=-423.7206,z=1001.0000},
{x=-1303.1964,y=-424.2230,z=1001.0000},
{x=-1303.5144,y=-424.6509,z=1001.0000},
{x=-1303.8748,y=-425.1795,z=1001.0000},
{x=-1304.1715,y=-425.5840,z=1001.0000},
{x=-1304.5883,y=-426.1485,z=1001.0000},
{x=-1304.9713,y=-426.6637,z=1001.0000},
{x=-1305.3905,y=-427.2000,z=1001.0000},
{x=-1305.8579,y=-427.7214,z=1001.0000},
{x=-1306.3307,y=-428.2000,z=1001.0000},
{x=-1306.7999,y=-428.6745,z=1001.0000},
{x=-1307.2588,y=-429.1119,z=1001.0000},
{x=-1307.7524,y=-429.5616,z=1001.0000},
{x=-1308.2727,y=-430.0351,z=1001.0000},
{x=-1308.8119,y=-430.5260,z=1001.0000},
{x=-1309.3169,y=-430.9858,z=1001.0000},
{x=-1309.8079,y=-431.4187,z=1001.0000},
{x=-1310.3145,y=-431.7969,z=1001.0000}
}
local ROUTE_2 = {
{x=-1295.8469,y=-417.0778,z=1005.0547},
{x=-1295.8411,y=-417.6600,z=1005.0547},
{x=-1295.9012,y=-418.2766,z=1005.0547},
{x=-1296.1851,y=-418.9017,z=1005.0547},
{x=-1296.6958,y=-419.3144,z=1005.0547},
{x=-1297.2224,y=-419.7321,z=1005.0547},
{x=-1297.7899,y=-420.1824,z=1005.0547},
{x=-1298.2262,y=-420.5285,z=1005.0547},
{x=-1298.6305,y=-420.8826,z=1005.0547},
{x=-1299.0696,y=-421.3048,z=1005.0547},
{x=-1299.5313,y=-421.7599,z=1005.0547},
{x=-1299.9503,y=-422.1730,z=1005.0547},
{x=-1300.4106,y=-422.6270,z=1005.0547},
{x=-1300.8479,y=-423.0581,z=1005.0547},
{x=-1301.2782,y=-423.4823,z=1005.0547},
{x=-1301.7518,y=-423.9495,z=1005.0547},
{x=-1302.1996,y=-424.3909,z=1005.0547},
{x=-1302.6555,y=-424.8404,z=1005.0547},
{x=-1303.1189,y=-425.2972,z=1005.0547},
{x=-1303.5433,y=-425.7156,z=1005.0547},
{x=-1303.9847,y=-426.1509,z=1005.0547},
{x=-1304.4431,y=-426.6029,z=1005.0547},
{x=-1304.8875,y=-427.0410,z=1005.0547},
{x=-1305.3370,y=-427.4844,z=1005.0547},
{x=-1305.7819,y=-427.9187,z=1005.0547},
{x=-1306.2023,y=-428.3069,z=1005.0547},
{x=-1306.7052,y=-428.7716,z=1005.0547},
{x=-1307.1713,y=-429.2023,z=1005.0547},
{x=-1307.6539,y=-429.6483,z=1005.0547},
{x=-1308.1357,y=-430.0823,z=1005.0547},
{x=-1308.6005,y=-430.4549,z=1005.0547},
{x=-1309.0978,y=-430.7673,z=1005.0547},
{x=-1309.5988,y=-431.0603,z=1005.0547},
{x=-1310.0538,y=-431.3443,z=1005.0547},
{x=-1310.4633,y=-431.6360,z=1005.0547},
{x=-1310.9218,y=-431.9683,z=1005.0547},
{x=-1310.9340,y=-431.9771,z=1003.0547},
{x=-1310.9340,y=-431.9771,z=1002.4662},
{x=-1310.9340,y=-431.9771,z=1001.7351},
{x=-1310.9395,y=-431.9811,z=1001.0000}
}
function selectRoute()
local px, py, pz = getCharCoordinates(PLAYER_PED)
if pz > 1003.0 then
return ROUTE_2, 2
end
return ROUTE_1, 1
end
function parseApartments(text)
for gid, atype, entrance, aptNum, price, flag, px, py in
text:gmatch('%[(%d+),(%d+),"(%d+)","[^#]+#%d+%. [^#]+#(%d+)",(%d+),(%d+),([%d%.%-]+),([%d%.%-]+)%]') do
table.insert(apartment.items, {
globalId = tonumber(gid),
aptType = tonumber(atype),
entrance = entrance,
aptNumber = tonumber(aptNum),
price = tonumber(price),
free = tonumber(flag) == 0,
posX = tonumber(px),
posY = tonumber(py)
})
end
end
function countFreeApartments()
local c = 0
for _, apt in ipairs(apartment.items) do
if apt.free and apt.price <= ini.catch.priceLimit then c = c + 1 end
end
return c
end
function findBestApartment()
local count = 0
for _, apt in ipairs(apartment.items) do
if apt.free and apt.price <= ini.catch.priceLimit then
count = count + 1
if count == ini.catch.apartmentIndex then
return apt
end
end
end
return nil
end
function isEconom(apt)
return apt.price <= 500000
end
function getPickupCoords(apt)
local idx = ((apt.aptNumber - 1) % 4) + 1
if isEconom(apt) then
return ECONOM_PICKUPS[idx]
else
return KVARTIRA_PICKUPS[idx]
end
end
function getFloor(apt)
return math.ceil(apt.aptNumber / 4)
end
local lastAltTime = 0
function sendAltKey()
-- one Alt per 1.5s: the Interactions hook and the elevator phase must not double-press
if os.clock() - lastAltTime < 1.5 then return end
lastAltTime = os.clock()
local bs = raknetNewBitStream()
local bytes = {215, 2, 0, 0, 0, 0, 0, 21, 0, 0, 0, 79, 110, 80, 108, 97, 121, 101, 114, 67, 108, 105, 101, 110, 116, 83, 105, 100, 101, 75, 101, 121, 2, 0, 0, 0, 100, 18, 0, 0, 0}
for _, b in ipairs(bytes) do raknetBitStreamWriteInt8(bs, b) end
raknetSendBitStream(bs)
raknetDeleteBitStream(bs)
end
function sendRealtorClose()
local bs = raknetNewBitStream()
local bytes = {215, 2, 0, 0, 0, 0, 0, 14, 0, 0, 0, 79, 110, 82, 101, 97, 108, 116, 111, 114, 67, 108, 111, 115, 101, 0, 0, 0, 0}
for _, b in ipairs(bytes) do raknetBitStreamWriteInt8(bs, b) end
raknetSendBitStream(bs)
raknetDeleteBitStream(bs)
end
function sendRealtorSelectCategory(cat)
local bs = raknetNewBitStream()
local bytes = {215, 2, 0, 0, 0, 0, 0, 23, 0, 0, 0, 79, 110, 82, 101, 97, 108, 116, 111, 114, 83, 101, 108, 101, 99, 116, 67, 97, 116, 101, 103, 111, 114, 121, 2, 0, 0, 0, 100}
for _, b in ipairs(bytes) do raknetBitStreamWriteInt8(bs, b) end
raknetBitStreamWriteInt8(bs, bit.band(cat, 0xFF))
raknetBitStreamWriteInt8(bs, 0)
raknetBitStreamWriteInt8(bs, 0)
raknetBitStreamWriteInt8(bs, 0)
raknetSendBitStream(bs)
raknetDeleteBitStream(bs)
end
function sendRealtorLoadMore(page)
local bs = raknetNewBitStream()
local bytes = {215, 2, 0, 0, 0, 0, 0, 17, 0, 0, 0, 79, 110, 82, 101, 97, 108, 116, 111, 114, 76, 111, 97, 100, 77, 111, 114, 101, 2, 0, 0, 0, 100}
for _, b in ipairs(bytes) do raknetBitStreamWriteInt8(bs, b) end
raknetBitStreamWriteInt8(bs, bit.band(page, 0xFF))
raknetBitStreamWriteInt8(bs, 0)
raknetBitStreamWriteInt8(bs, 0)
raknetBitStreamWriteInt8(bs, 0)
raknetSendBitStream(bs)
raknetDeleteBitStream(bs)
end
function autoLoadApartments()
lua_thread.create(function()
apartment.items = {}
sampAddChatMessage('{ffff69}[HUNT] Auto loading apartments...', -1)
sendRealtorSelectCategory(1)
wait(500)
for page = 1, 10 do
sendRealtorLoadMore(page)
wait(300)
end
wait(500)
local best = findBestApartment()
if best and apartment.hunting and not apartment.target then
apartment.target = best
sampAddChatMessage('{69ff69}[HUNT] Apt #' .. best.aptNumber .. ' Ent #' .. best.entrance .. ' Price: ' .. best.price, -1)
lua_thread.create(startApartmentCatch)
elseif not best then
sampAddChatMessage('{ff6969}[HUNT] No free apartments under ' .. ini.catch.priceLimit, -1)
end
end)
end
function sendDialogResponseFull(dialogId, button, listIndex, inputText)
local bs = raknetNewBitStream()
-- Header
local header = {215, 2, 0, 0, 0, 0, 0}
for _, b in ipairs(header) do raknetBitStreamWriteInt8(bs, b) end
-- "OnDialogResponse" string
local name = "OnDialogResponse"
raknetBitStreamWriteInt8(bs, #name)
raknetBitStreamWriteInt8(bs, 0)
raknetBitStreamWriteInt8(bs, 0)
raknetBitStreamWriteInt8(bs, 0)
for i = 1, #name do raknetBitStreamWriteInt8(bs, string.byte(name, i)) end
-- Params count
raknetBitStreamWriteInt8(bs, 8)
raknetBitStreamWriteInt8(bs, 0)
raknetBitStreamWriteInt8(bs, 0)
raknetBitStreamWriteInt8(bs, 0)
-- dialogId (dword)
raknetBitStreamWriteInt8(bs, 100)
raknetBitStreamWriteInt8(bs, bit.band(dialogId, 0xFF))
raknetBitStreamWriteInt8(bs, bit.band(bit.rshift(dialogId, 8), 0xFF))
raknetBitStreamWriteInt8(bs, bit.band(bit.rshift(dialogId, 16), 0xFF))
raknetBitStreamWriteInt8(bs, bit.band(bit.rshift(dialogId, 24), 0xFF))
-- button (dword)
raknetBitStreamWriteInt8(bs, 100)
raknetBitStreamWriteInt8(bs, bit.band(button, 0xFF))
raknetBitStreamWriteInt8(bs, bit.band(bit.rshift(button, 8), 0xFF))
raknetBitStreamWriteInt8(bs, bit.band(bit.rshift(button, 16), 0xFF))
raknetBitStreamWriteInt8(bs, bit.band(bit.rshift(button, 24), 0xFF))
-- listIndex (dword)
raknetBitStreamWriteInt8(bs, 100)
local li = listIndex
if li < 0 then li = 0xFFFFFFFF end
raknetBitStreamWriteInt8(bs, bit.band(li, 0xFF))
raknetBitStreamWriteInt8(bs, bit.band(bit.rshift(li, 8), 0xFF))
raknetBitStreamWriteInt8(bs, bit.band(bit.rshift(li, 16), 0xFF))
raknetBitStreamWriteInt8(bs, bit.band(bit.rshift(li, 24), 0xFF))
-- inputText (string with length prefix)
raknetBitStreamWriteInt8(bs, 115)
local str = inputText or ""
raknetBitStreamWriteInt8(bs, bit.band(#str, 0xFF))
raknetBitStreamWriteInt8(bs, bit.band(bit.rshift(#str, 8), 0xFF))
raknetBitStreamWriteInt8(bs, bit.band(bit.rshift(#str, 16), 0xFF))
raknetBitStreamWriteInt8(bs, bit.band(bit.rshift(#str, 24), 0xFF))
for i = 1, #str do raknetBitStreamWriteInt8(bs, string.byte(str, i)) end
raknetSendBitStream(bs)
raknetDeleteBitStream(bs)
end
imgui.OnInitialize(function()
imgui.GetIO().IniFilename = nil
local style = imgui.GetStyle()
style.WindowRounding = 12
style.FrameRounding = 8
style.GrabRounding = 6
style.ScrollbarRounding = 8
style.ChildRounding = 8
style.PopupRounding = 8
style.ItemSpacing = imgui.ImVec2(10, 8)
style.WindowPadding = imgui.ImVec2(14, 14)
style.FramePadding = imgui.ImVec2(10, 6)
style.WindowBorderSize = 1
style.FrameBorderSize = 0
style.ScrollbarSize = 10
local colors = style.Colors
-- Background
colors[imgui.Col.WindowBg] = imgui.ImVec4(0.06, 0.06, 0.09, 0.96)
colors[imgui.Col.ChildBg] = imgui.ImVec4(0.08, 0.08, 0.12, 0.60)
colors[imgui.Col.PopupBg] = imgui.ImVec4(0.07, 0.07, 0.11, 0.96)
-- Borders
colors[imgui.Col.Border] = imgui.ImVec4(0.40, 0.20, 0.80, 0.30)
colors[imgui.Col.BorderShadow] = imgui.ImVec4(0.00, 0.00, 0.00, 0.00)
-- Text
colors[imgui.Col.Text] = imgui.ImVec4(0.93, 0.93, 0.97, 1.00)
colors[imgui.Col.TextDisabled] = imgui.ImVec4(0.45, 0.45, 0.55, 1.00)
-- Buttons
colors[imgui.Col.Button] = imgui.ImVec4(0.35, 0.15, 0.70, 0.65)
colors[imgui.Col.ButtonHovered] = imgui.ImVec4(0.45, 0.25, 0.85, 0.80)
colors[imgui.Col.ButtonActive] = imgui.ImVec4(0.55, 0.30, 0.95, 0.90)
-- Frames
colors[imgui.Col.FrameBg] = imgui.ImVec4(0.12, 0.10, 0.20, 0.80)
colors[imgui.Col.FrameBgHovered] = imgui.ImVec4(0.20, 0.15, 0.35, 0.80)
colors[imgui.Col.FrameBgActive] = imgui.ImVec4(0.25, 0.18, 0.45, 0.80)
-- Headers
colors[imgui.Col.Header] = imgui.ImVec4(0.30, 0.15, 0.60, 0.50)
colors[imgui.Col.HeaderHovered] = imgui.ImVec4(0.40, 0.20, 0.75, 0.60)
colors[imgui.Col.HeaderActive] = imgui.ImVec4(0.45, 0.25, 0.85, 0.70)
-- Separator
colors[imgui.Col.Separator] = imgui.ImVec4(0.40, 0.20, 0.80, 0.25)
colors[imgui.Col.SeparatorHovered] = imgui.ImVec4(0.50, 0.30, 0.90, 0.50)
-- Scrollbar
colors[imgui.Col.ScrollbarBg] = imgui.ImVec4(0.05, 0.05, 0.08, 0.50)
colors[imgui.Col.ScrollbarGrab] = imgui.ImVec4(0.35, 0.15, 0.70, 0.50)
colors[imgui.Col.ScrollbarGrabHovered] = imgui.ImVec4(0.45, 0.25, 0.85, 0.70)
colors[imgui.Col.ScrollbarGrabActive] = imgui.ImVec4(0.55, 0.30, 0.95, 0.90)
-- Slider
colors[imgui.Col.SliderGrab] = imgui.ImVec4(0.50, 0.25, 0.90, 0.80)
colors[imgui.Col.SliderGrabActive] = imgui.ImVec4(0.60, 0.35, 1.00, 1.00)
-- Checkmark
colors[imgui.Col.CheckMark] = imgui.ImVec4(0.60, 0.35, 1.00, 1.00)
-- Title
colors[imgui.Col.TitleBg] = imgui.ImVec4(0.10, 0.05, 0.20, 0.95)
colors[imgui.Col.TitleBgActive] = imgui.ImVec4(0.25, 0.12, 0.50, 0.95)
colors[imgui.Col.TitleBgCollapsed] = imgui.ImVec4(0.08, 0.04, 0.15, 0.70)
end)
local mainFrame = imgui.OnFrame(function() return showMenu[0] and not isPauseMenuActive() and not sampIsScoreboardOpen() end,
function(player)
player.HideCursor = imgui.IsMouseDown(1)
local resX, resY = getScreenResolution()
imgui.SetNextWindowPos(imgui.ImVec2(resX / 2, resY / 2), imgui.Cond.FirstUseEver, imgui.ImVec2(0.5, 0.5))
imgui.SetNextWindowSize(imgui.ImVec2(380, 0), imgui.Cond.FirstUseEver)
imgui.Begin(u8('r-realtor by @purpurins'), showMenu, imgui.WindowFlags.NoCollapse + imgui.WindowFlags.AlwaysAutoResize)
-- Status block
imgui.BeginChild(u8('##status'), imgui.ImVec2(-1, 45), true)
if apartment.hunting then
imgui.TextColored(imgui.ImVec4(0.40, 1.0, 0.40, 1.0), u8(' HUNTING'))
if apartment.target then
imgui.SameLine(150)
imgui.TextColored(imgui.ImVec4(1.0, 0.85, 0.30, 1.0), u8('Apt #' .. apartment.target.aptNumber .. ' | Ent #' .. apartment.target.entrance .. ' | ' .. apartment.target.price))
end
elseif catch.active then
imgui.TextColored(imgui.ImVec4(1.0, 1.0, 0.40, 1.0), u8(' ' .. catch.status))
elseif teleport.boolean then
imgui.TextColored(imgui.ImVec4(1.0, 1.0, 0.40, 1.0), u8(' Teleporting ' .. teleport.persent .. '%'))
else
local vehId = false
if isCharOnFoot(PLAYER_PED) then vehId = getVehicle(200, true, false) end
if vehId then
imgui.TextColored(imgui.ImVec4(0.40, 1.0, 0.40, 1.0), u8(' Ready'))
imgui.SameLine(150)
imgui.TextColored(imgui.ImVec4(0.70, 0.70, 0.80, 1.0), u8('Vehicle: ' .. vehId))
else
imgui.TextColored(imgui.ImVec4(1.0, 0.40, 0.40, 1.0), u8(' No vehicle nearby'))
end
end
imgui.EndChild()
imgui.Spacing()
-- Apartment Catcher
imgui.TextColored(imgui.ImVec4(0.65, 0.45, 1.0, 1.0), u8('APARTMENT CATCHER'))
imgui.Separator()
imgui.Spacing()
imgui.TextColored(imgui.ImVec4(0.70, 0.70, 0.80, 1.0), u8('Limit: ' .. ini.catch.priceLimit .. ' | Free: ' .. countFreeApartments() .. ' | Order: #' .. ini.catch.apartmentIndex))
imgui.Spacing()
if apartment.hunting then
imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.70, 0.15, 0.15, 0.80))
imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.85, 0.20, 0.20, 0.90))
imgui.PushStyleColor(imgui.Col.ButtonActive, imgui.ImVec4(0.95, 0.25, 0.25, 1.00))
if imgui.Button(u8(' Stop Hunting'), imgui.ImVec2(-1, 35)) then
apartment.hunting = false
apartment.target = nil
apartment.waitingElevator = false
apartment.waitingBuy = false
catch.active = false
teleport.boolean = false
catch.status = ''
end
imgui.PopStyleColor(3)
else
imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.15, 0.55, 0.15, 0.80))
imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.20, 0.70, 0.20, 0.90))
imgui.PushStyleColor(imgui.Col.ButtonActive, imgui.ImVec4(0.25, 0.80, 0.25, 1.00))
if imgui.Button(u8(' Start Hunting'), imgui.ImVec2(-1, 35)) then
startHunting()
end
imgui.PopStyleColor(3)
if #apartment.items > 0 and not catch.active and not teleport.boolean then
if imgui.Button(u8(' Test (cached)'), imgui.ImVec2(-1, 28)) then
startTestHunt()
end
end
end
imgui.Spacing()
imgui.Spacing()
-- Teleport
imgui.TextColored(imgui.ImVec4(0.65, 0.45, 1.0, 1.0), u8('TELEPORT'))
imgui.Separator()
imgui.Spacing()
if catch.active or teleport.boolean then
imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.70, 0.15, 0.15, 0.80))
imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.85, 0.20, 0.20, 0.90))
imgui.PushStyleColor(imgui.Col.ButtonActive, imgui.ImVec4(0.95, 0.25, 0.25, 1.00))
if imgui.Button(u8(' Stop'), imgui.ImVec2(-1, 30)) then
catch.active = false
teleport.boolean = false
catch.status = ''
end
imgui.PopStyleColor(3)
else
if imgui.Button(u8(' Catch (route)'), imgui.ImVec2(-1, 28)) then startCatch() end
if imgui.Button(u8(' TP to mark'), imgui.ImVec2(-1, 28)) then startMarkTp() end
imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.15, 0.40, 0.65, 0.80))
imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.20, 0.50, 0.80, 0.90))
imgui.PushStyleColor(imgui.Col.ButtonActive, imgui.ImVec4(0.25, 0.55, 0.90, 1.00))
if imgui.Button(u8(' Bus'), imgui.ImVec2(170, 28)) then
startTpToCoords(-400.3, -1333.9, 41.1)
end
imgui.SameLine()
if imgui.Button(u8(' Arz'), imgui.ImVec2(170, 28)) then
startTpToCoords(648.566, 652.348, 12.0)
end
imgui.PopStyleColor(3)
end
imgui.Spacing()
imgui.Spacing()
-- Info
imgui.TextColored(imgui.ImVec4(0.65, 0.45, 1.0, 1.0), u8('INFO'))
imgui.Separator()
imgui.Spacing()
imgui.BeginChild(u8('##info'), imgui.ImVec2(-1, 75), true)
local px, py, pz = getCharCoordinates(PLAYER_PED)
imgui.TextColored(imgui.ImVec4(0.60, 0.60, 0.70, 1.0), u8(string.format('Pos: %.1f, %.1f, %.1f', px, py, pz)))
imgui.TextColored(imgui.ImVec4(0.60, 0.60, 0.70, 1.0), u8(string.format('Safe: %.1f, %.1f, %.1f', ini.catch.safeX, ini.catch.safeY, ini.catch.safeZ)))
local _, rn = selectRoute()
imgui.TextColored(imgui.ImVec4(0.60, 0.60, 0.70, 1.0), u8('Route: #' .. rn))
imgui.EndChild()
imgui.Spacing()
if imgui.Button(u8(' Save safe pos'), imgui.ImVec2(-1, 25)) then
local sx, sy, sz = getCharCoordinates(PLAYER_PED)
ini.catch.safeX, ini.catch.safeY, ini.catch.safeZ = sx, sy, sz
buf.safeX[0], buf.safeY[0], buf.safeZ[0] = sx, sy, sz
SaveCfg()
sampAddChatMessage(string.format('{69ff69}[r-realtor by @purpurins] Safe: %.1f, %.1f, %.1f', sx, sy, sz), -1)
end
imgui.Spacing()
imgui.PushStyleColor(imgui.Col.Button, imgui.ImVec4(0.20, 0.15, 0.35, 0.80))
imgui.PushStyleColor(imgui.Col.ButtonHovered, imgui.ImVec4(0.30, 0.20, 0.50, 0.90))
imgui.PushStyleColor(imgui.Col.ButtonActive, imgui.ImVec4(0.35, 0.25, 0.60, 1.00))
if imgui.Button(u8(' Settings'), imgui.ImVec2(-1, 25)) then showSettings[0] = not showSettings[0] end
imgui.PopStyleColor(3)
imgui.End()
end)
local settingsFrame = imgui.OnFrame(function() return showSettings[0] and showMenu[0] and not isPauseMenuActive() end,
function(player)
player.HideCursor = imgui.IsMouseDown(1)
local resX, resY = getScreenResolution()
imgui.SetNextWindowPos(imgui.ImVec2(resX / 2 + 190, resY / 2 - 100), imgui.Cond.FirstUseEver)
imgui.SetNextWindowSize(imgui.ImVec2(320, 0), imgui.Cond.FirstUseEver)
imgui.Begin(u8('Settings'), showSettings, imgui.WindowFlags.NoCollapse + imgui.WindowFlags.AlwaysAutoResize)
if imgui.CollapsingHeader(u8('Apartment')) then
if imgui.InputInt(u8('Price limit'), buf.priceLimit, 50000, 100000) then
if buf.priceLimit[0] < 0 then buf.priceLimit[0] = 0 end
ini.catch.priceLimit = buf.priceLimit[0]
SaveCfg()
end
if imgui.Checkbox(u8('Auto payday tabs'), buf.autoPayday) then
ini.catch.autoPayday = buf.autoPayday[0]
SaveCfg()
end
if imgui.Checkbox(u8('Walk to elevator myself'), buf.manualElevator) then
ini.catch.manualElevator = buf.manualElevator[0]
SaveCfg()
end
if imgui.InputInt(u8('Apartment # (order)'), buf.apartmentIndex, 1, 1) then
if buf.apartmentIndex[0] < 1 then buf.apartmentIndex[0] = 1 end
ini.catch.apartmentIndex = buf.apartmentIndex[0]
SaveCfg()
end
end
if imgui.CollapsingHeader(u8('General')) then
if imgui.Checkbox(u8('Show bar'), buf.barShow) then ini.main.bar = buf.barShow[0]; SaveCfg() end
if imgui.SliderFloat(u8('Curd step'), buf.curdStep, 0.1, 2.0, '%.1f') then ini.catch.curdStep = buf.curdStep[0]; SaveCfg() end
if imgui.SliderInt(u8('RTP step'), buf.rtpStep, 1, 20) then ini.catch.rtpStep = buf.rtpStep[0]; SaveCfg() end
if imgui.SliderFloat(u8('Check radius'), buf.checkRadius, 1.0, 20.0, '%.1f') then ini.catch.checkRadius = buf.checkRadius[0]; SaveCfg() end
if imgui.SliderInt(u8('Exit timeout'), buf.exitTimeout, 1, 30) then ini.catch.exitTimeout = buf.exitTimeout[0]; SaveCfg() end
if imgui.SliderInt(u8('Route delay'), buf.routeDelay, 10, 200) then ini.catch.routeDelay = buf.routeDelay[0]; SaveCfg() end
end
if imgui.CollapsingHeader(u8('Coords')) then
if imgui.InputFloat(u8('Int X'), buf.interiorX, 0.1, 1.0, '%.1f') then ini.catch.interiorX = buf.interiorX[0]; SaveCfg() end
if imgui.InputFloat(u8('Int Y'), buf.interiorY, 0.1, 1.0, '%.1f') then ini.catch.interiorY = buf.interiorY[0]; SaveCfg() end
if imgui.InputFloat(u8('Int Z'), buf.interiorZ, 0.1, 1.0, '%.1f') then ini.catch.interiorZ = buf.interiorZ[0]; SaveCfg() end
if imgui.InputFloat(u8('Out X'), buf.outsideX, 0.1, 1.0, '%.1f') then ini.catch.outsideX = buf.outsideX[0]; SaveCfg() end
if imgui.InputFloat(u8('Out Y'), buf.outsideY, 0.1, 1.0, '%.1f') then ini.catch.outsideY = buf.outsideY[0]; SaveCfg() end
if imgui.InputFloat(u8('Out Z'), buf.outsideZ, 0.1, 1.0, '%.1f') then ini.catch.outsideZ = buf.outsideZ[0]; SaveCfg() end
if imgui.InputFloat(u8('Safe X'), buf.safeX, 0.1, 1.0, '%.1f') then ini.catch.safeX = buf.safeX[0]; SaveCfg() end
if imgui.InputFloat(u8('Safe Y'), buf.safeY, 0.1, 1.0, '%.1f') then ini.catch.safeY = buf.safeY[0]; SaveCfg() end
if imgui.InputFloat(u8('Safe Z'), buf.safeZ, 0.1, 1.0, '%.1f') then ini.catch.safeZ = buf.safeZ[0]; SaveCfg() end
end
imgui.End()
end)
function main()
repeat wait(0) until isSampAvailable()
lua_thread.create(infobar)
sampRegisterChatCommand('rtp', function(arg)
if arg == '' then blip, x, y, z = getMark()
elseif arg == 'chp' then blip, x, y, z = getCheckPoint()
elseif arg == 'bar' then ini.main.bar = not ini.main.bar; buf.barShow[0] = ini.main.bar; SaveCfg(); return
elseif arg == 'bar move' then teleport.bar_move = not teleport.bar_move; return
else return end
if blip and isCharOnFoot(PLAYER_PED) and getVehicle(200, true, false) and not teleport.boolean then
local vehicleId = getVehicle(200, true, false)
local vehicleHandle = select(2, sampGetCarHandleBySampVehicleId(vehicleId))
local positionX, positionY, positionZ = getCharCoordinates(PLAYER_PED)
local carX, carY, carZ = getCarCoordinates(vehicleHandle)
teleport.boolean = true
lua_thread.create(function()
slapUnderMap()
if getDistanceBetweenCoords3d(positionX, positionY, positionZ, carX, carY, carZ) > 15 then
teleportToCar(positionX, positionY, positionZ, carX, carY, carZ)
end
wait(500)
teleportToBlip(vehicleId, carX, carY, carZ, x, y, z)
end)
end
end)
sampRegisterChatCommand('rsave', function()
local sx, sy, sz = getCharCoordinates(PLAYER_PED)
ini.catch.safeX, ini.catch.safeY, ini.catch.safeZ = sx, sy, sz
buf.safeX[0], buf.safeY[0], buf.safeZ[0] = sx, sy, sz
SaveCfg()
sampAddChatMessage(string.format('{69ff69}[r-realtor by @purpurins] Safe: %.1f, %.1f, %.1f', sx, sy, sz), -1)
end)
sampRegisterChatCommand('rr', function()
showMenu[0] = not showMenu[0]
end)
sampAddChatMessage('{69ff69}[r-realtor by @purpurins] {FFFFFF}/rr - menu', -1)
wait(-1)
end
function onServerMessage(color, text)
if ini.catch.autoPayday and apartment.hunting and not apartment.target then
local bankCheck = string.char(193, 192, 205, 202, 206, 194, 209, 202, 200, 201) -- БАНКОВСКИЙ in CP1251
if text:find(bankCheck) then
sampAddChatMessage('{ffff69}[HUNT] Payday detected! Auto loading...', -1)
autoLoadApartments()
end
end
end
function onReceivePacket(id, bs)
local len = raknetBitStreamGetNumberOfBytesUsed(bs)
if len < 10 then return end
raknetBitStreamResetReadPointer(bs)
local text = ''
for i = 1, len do
local b = raknetBitStreamReadInt8(bs)
if b < 0 then b = b + 256 end
if b >= 32 then text = text .. string.char(b) end
end
raknetBitStreamResetReadPointer(bs)
-- Parse apartment listings from CEF
if text:find('setPropertyItems') then
apartment.items = {}
parseApartments(text)
elseif text:find('addPropertyItems') then
parseApartments(text)
end
-- Auto-select apartment when hunting
if apartment.hunting and not apartment.target then
if text:find('PropertyItems') then
local best = findBestApartment()
if best then
apartment.target = best
sampAddChatMessage('{69ff69}[HUNT] Apt #' .. best.aptNumber .. ' Ent #' .. best.entrance .. ' Price: ' .. best.price, -1)
lua_thread.create(startApartmentCatch)
end
end
end
-- Auto elevator - simulate arrow keys
if apartment.waitingElevator and text:find('addDialogInQueue') and (text:find(CP1251.etazh) or text:find('FFCD00')) then
local target = apartment.target
if target then
local floor = getFloor(target)
local floorIndex = floor - 1
lua_thread.create(function()
wait(100)
-- Press Down arrow to select floor
for i = 1, floorIndex do
pressKeyDown(0x28) -- VK_DOWN
wait(10)
releaseKey(0x28)
wait(15)
end
wait(30)
-- Press Enter to confirm
pressKeyDown(0x0D) -- VK_RETURN
wait(10)
releaseKey(0x0D)
sampAddChatMessage('{69ff69}[HUNT] Floor ' .. floor, -1)
apartment.waitingElevator = false
wait(2000)
if apartment.hunting and target then
local pickup = getPickupCoords(target)
if pickup then
setCharCoordinates(PLAYER_PED, pickup.x, pickup.y, pickup.z)
apartment.waitingBuy = true
sampAddChatMessage('{69ff69}[HUNT] At pickup, waiting...', -1)
end
end
end)
end
end
-- Interactions [[7,"Взаимодействовать"]] -> answer with the Alt key packet
if apartment.hunting and text:find('Interactions', 1, true) and text:find(CP1251.vzaimodeystvovat, 1, true) then
lua_thread.create(function()
wait(100)
sendAltKey()
sampAddChatMessage('{69ff69}[HUNT] Interaction -> Alt sent', -1)
end)
end
-- Auto close "marked on radar" dialog
if apartment.hunting and text:find('addDialogInQueue') and text:find(CP1251.radare) then
lua_thread.create(function()
wait(300)
sendDialogResponseFull(0, 1, -1, "")
end)
return false
end
-- Auto Alt when at apartment pickup
if apartment.waitingBuy and text:find('Appartam') then
lua_thread.create(function()
wait(100)
pressKeyDown(0xA4) -- VK_LMENU (Left Alt)
wait(30)
releaseKey(0xA4)
sampAddChatMessage('{69ff69}[HUNT] At apartment, pressing Alt...', -1)
end)
end
-- Auto buy
if apartment.waitingBuy and text:find('addDialogInQueue') and (text:find(CP1251.svoboden) or text:find('33CC00')) then
lua_thread.create(function()
wait(500)
sendDialogResponseFull(0, 1, -1, "")
sampAddChatMessage('{69ff69}[HUNT] BOUGHT!', -1)
apartment.waitingBuy = false
apartment.hunting = false
apartment.target = nil
end)
return false
end
end
function startHunting()
if apartment.hunting then return end
apartment.hunting = true
apartment.target = nil
apartment.items = {}
apartment.waitingElevator = false
apartment.waitingBuy = false
sampAddChatMessage('{ffff69}[HUNT] Started! Open CEF > Квартиры > Свободное имущество', -1)
sampAddChatMessage('{ffff69}[HUNT] Price limit: ' .. ini.catch.priceLimit, -1)
end
function startTestHunt()
if #apartment.items == 0 then
sampAddChatMessage('{ff6969}[TEST] No cached data! Open CEF first.', -1)
return
end
local best = findBestApartment()
if not best then
sampAddChatMessage('{ff6969}[TEST] No free apartments in cache under ' .. ini.catch.priceLimit, -1)
return
end
apartment.hunting = true
apartment.target = best
apartment.waitingElevator = false
apartment.waitingBuy = false
sampAddChatMessage('{ffff69}[TEST] Apt #' .. best.aptNumber .. ' Ent #' .. best.entrance .. ' Price: ' .. best.price, -1)
lua_thread.create(startApartmentCatch)
end
function startApartmentCatch()
local target = apartment.target
if not target then return end
-- apartment found: close the realtor tablet right away
sendRealtorClose()
-- Phase 1: Route out of realtor
local routePoints, routeNum = selectRoute()
sampAddChatMessage('{ffff69}[HUNT] Using route ' .. routeNum, -1)
if #routePoints > 0 then
catch.active = true
catch.status = 'Route out...'
for i, point in ipairs(routePoints) do
if not apartment.hunting then break end
catch.status = 'Route ' .. i .. '/' .. #routePoints
setCharCoordinates(PLAYER_PED, point.x, point.y, point.z)
wait(ini.catch.routeDelay)
end
catch.active = false
if not apartment.hunting then return end
catch.status = 'Waiting exit...'
local timeout = os.clock() + ini.catch.exitTimeout
while os.clock() < timeout and apartment.hunting do
wait(100)
local px, py, pz = getCharCoordinates(PLAYER_PED)
if getDistanceBetweenCoords3d(px, py, pz, ini.catch.outsideX, ini.catch.outsideY, ini.catch.outsideZ) <= ini.catch.checkRadius then break end
end
local px, py, pz = getCharCoordinates(PLAYER_PED)
if getDistanceBetweenCoords3d(px, py, pz, ini.catch.outsideX, ini.catch.outsideY, ini.catch.outsideZ) > ini.catch.checkRadius then
sampAddChatMessage('{ff6969}[HUNT] Exit failed!', -1)
apartment.hunting = false
return
end
end
if not apartment.hunting then return end
-- Phase 2: RTP to entrance
sampAddChatMessage('{ffff69}[HUNT] RTP to entrance...', -1)
wait(300)
local vehicleId = nil
for attempt = 1, 10 do
vehicleId = getVehicle(200, true, false)
if not vehicleId then vehicleId = getVehicle(200, false, false) end
if vehicleId then break end
wait(500)
end
if not vehicleId then
sampAddChatMessage('{ff6969}[HUNT] No vehicle after 10 tries!', -1)
apartment.hunting = false
return
end
local vehicleHandle = select(2, sampGetCarHandleBySampVehicleId(vehicleId))
local vx, vy, vz = getCarCoordinates(vehicleHandle)
local px, py, pz = getCharCoordinates(PLAYER_PED)
local entranceZ = getGroundZFor3dCoord(target.posX + 2, target.posY + 2, 100.0)
if entranceZ == 0 then entranceZ = 15.0 end
entranceZ = entranceZ + 1.0
-- slap the player under the map before the teleport
setCharCoordinates(PLAYER_PED, px, py, pz - 3.0)
wait(300)
teleport.boolean = true
if getDistanceBetweenCoords3d(px, py, pz, vx, vy, vz) > 15 then
teleportToCar(px, py, pz, vx, vy, vz)
end
wait(500)
teleportToBlip(vehicleId, vx, vy, vz, target.posX, target.posY, entranceZ)
if not apartment.hunting then return end
-- plain teleport onto the entrance coords (RTP sometimes lands on the canopy)
setCharCoordinates(PLAYER_PED, target.posX, target.posY, entranceZ)
-- Phase 3: Wait 4s after arriving at the house, then teleport to elevator
sampAddChatMessage('{ffff69}[HUNT] Arrived, elevator in 4s...', -1)
wait(4000)
if not apartment.hunting then return end
-- Elevator: either walk there yourself (manualElevator) or auto-move
local elevCoords
if isEconom(target) then
elevCoords = {x = 2325.9, y = -2454.3, z = 997.6}
else
elevCoords = {x = 2769.0, y = 1700.9, z = 997.5}
end
local elevTimeout = 15
if ini.catch.manualElevator then
sampAddChatMessage('{ffff69}[HUNT] Walk to the elevator and press Alt, floor is picked automatically', -1)
apartment.waitingElevator = true
elevTimeout = 60
else
sampAddChatMessage('{ffff69}[HUNT] Moving to elevator...', -1)
safeSetPos(elevCoords.x, elevCoords.y, elevCoords.z)
wait(200)
setCharCoordinates(PLAYER_PED, elevCoords.x, elevCoords.y, elevCoords.z)
wait(300)
-- The Interactions hook normally sends Alt itself; send ours only if it did not (two Alts close the dialog)
local tpAt = os.clock()
apartment.waitingElevator = true
while os.clock() - tpAt < 1.5 and lastAltTime < tpAt and apartment.hunting do wait(50) end
if lastAltTime < tpAt then sendAltKey() end
end
-- Wait for elevator dialog
sampAddChatMessage('{ffff69}[HUNT] Waiting elevator...', -1)
local timeout = os.clock() + elevTimeout
while os.clock() < timeout and apartment.waitingElevator and apartment.hunting do
wait(200)
end
if apartment.waitingElevator then
sampAddChatMessage('{ff6969}[HUNT] Elevator timeout!', -1)
apartment.hunting = false
apartment.waitingElevator = false
end
end
function startCatch()
if catch.active or teleport.boolean then return end
if ini.catch.safeX == 0.0 and ini.catch.safeY == 0.0 and ini.catch.safeZ == 0.0 then
sampAddChatMessage('{ff6969}[r-realtor by @purpurins] No safe pos!', -1)
return
end
local routePoints, routeNum = selectRoute()
if #routePoints == 0 then
sampAddChatMessage('{ff6969}[r-realtor by @purpurins] No route file!', -1)
return
end
catch.active = true
lua_thread.create(function()
catch.status = 'Following route...'
for i, point in ipairs(routePoints) do
if not catch.active then break end
catch.status = 'Route ' .. i .. '/' .. #routePoints
setCharCoordinates(PLAYER_PED, point.x, point.y, point.z)
wait(ini.catch.routeDelay)
end
if not catch.active then return end
catch.status = 'Waiting exit...'
local timeout = os.clock() + ini.catch.exitTimeout
while os.clock() < timeout and catch.active do
wait(100)
local px, py, pz = getCharCoordinates(PLAYER_PED)
if getDistanceBetweenCoords3d(px, py, pz, ini.catch.outsideX, ini.catch.outsideY, ini.catch.outsideZ) <= ini.catch.checkRadius then break end
end
local px, py, pz = getCharCoordinates(PLAYER_PED)
if getDistanceBetweenCoords3d(px, py, pz, ini.catch.outsideX, ini.catch.outsideY, ini.catch.outsideZ) > ini.catch.checkRadius then
catch.status = 'Exit failed!'
catch.active = false
return
end
catch.status = 'RTP to safe...'
wait(300)
local vehicleId = getVehicle(200, true, false)
if not vehicleId then catch.active = false; return end
local vehicleHandle = select(2, sampGetCarHandleBySampVehicleId(vehicleId))
local vx, vy, vz = getCarCoordinates(vehicleHandle)
px, py, pz = getCharCoordinates(PLAYER_PED)
slapUnderMap()
teleport.boolean = true
catch.active = false
catch.status = ''
if getDistanceBetweenCoords3d(px, py, pz, vx, vy, vz) > 15 then teleportToCar(px, py, pz, vx, vy, vz) end
wait(500)
teleportToBlip(vehicleId, vx, vy, vz, ini.catch.safeX, ini.catch.safeY, ini.catch.safeZ)
wait(500)
local fpx, fpy, fpz = getCharCoordinates(PLAYER_PED)
local e2, vh2 = sampGetCarHandleBySampVehicleId(vehicleId)
if e2 then setCarCoordinates(vh2, fpx + 3, fpy, fpz) end
end)
end
-- slap the player under the map so the server sees it before the teleport (packets are let through while slapping)
function slapUnderMap()
local was = teleport.boolean
teleport.boolean = false
local x, y, z = getCharCoordinates(PLAYER_PED)
setCharCoordinates(PLAYER_PED, x, y, z - 3.0)
wait(300)
teleport.boolean = was
end
function startMarkTp()
local blip, mx, my, mz = getMark()
if not blip then sampAddChatMessage('{ff6969}[r-realtor by @purpurins] No mark!', -1); return end
if not isCharOnFoot(PLAYER_PED) then return end
local vehicleId = getVehicle(200, true, false)
if not vehicleId or teleport.boolean then return end
local vehicleHandle = select(2, sampGetCarHandleBySampVehicleId(vehicleId))
local px, py, pz = getCharCoordinates(PLAYER_PED)
local cx, cy, cz = getCarCoordinates(vehicleHandle)
teleport.boolean = true
lua_thread.create(function()
slapUnderMap()
if getDistanceBetweenCoords3d(px, py, pz, cx, cy, cz) > 15 then teleportToCar(px, py, pz, cx, cy, cz) end
wait(500)
teleportToBlip(vehicleId, cx, cy, cz, mx, my, mz)
end)
end
function startTpToCoords(tx, ty, tz)
if not isCharOnFoot(PLAYER_PED) then return end
local vehicleId = getVehicle(200, true, false)
if not vehicleId or teleport.boolean then
sampAddChatMessage('{ff6969}[r-realtor by @purpurins] No vehicle or already running!', -1)
return
end
local vehicleHandle = select(2, sampGetCarHandleBySampVehicleId(vehicleId))
local px, py, pz = getCharCoordinates(PLAYER_PED)
local cx, cy, cz = getCarCoordinates(vehicleHandle)
teleport.boolean = true
lua_thread.create(function()
slapUnderMap()
if getDistanceBetweenCoords3d(px, py, pz, cx, cy, cz) > 15 then teleportToCar(px, py, pz, cx, cy, cz) end
wait(500)
teleportToBlip(vehicleId, cx, cy, cz, tx, ty, tz)
end)
end
function teleportToBlip(vehicleId, mx, my, mz, cx, cy, cz)
local step = ini.catch.rtpStep
local vehicleHandle = select(2, sampGetCarHandleBySampVehicleId(tonumber(vehicleId)))
local start_distance = getDistanceBetweenCoords3d(mx, my, mz, cx, cy, cz)
local time = os.time()
sampSendEnterVehicle(vehicleId, true)
while getDistanceBetweenCoords3d(mx, my, mz, cx, cy, cz) >= step and teleport.boolean do
teleport.persent = math.floor(100 - (getDistanceBetweenCoords3d(mx, my, mz, cx, cy, cz) / start_distance) * 100)
local vec = vector(cx - mx, cy - my, cz - mz)
vec:normalize()
mx = mx + vec.x * step
my = my + vec.y * step
mz = mz + vec.z * step
sendPassenger(mx, my, mz, vehicleId)
wait(0)
end
sendPassenger(cx, cy, cz, vehicleId)
sampAddChatMessage('TP: ' .. os.time() - time .. 's', -1)
requestCollision(cx, cy)
loadScene(cx, cy, cz)
wait(200)
local groundZ = getGroundZFor3dCoord(cx, cy, cz + 5)
if groundZ == 0 then
wait(500)
groundZ = getGroundZFor3dCoord(cx, cy, cz + 5)
end
if groundZ == 0 then groundZ = cz end
setCharCoordinates(PLAYER_PED, cx, cy, groundZ + 1.0)
setCarCoordinates(vehicleHandle, cx + 3, cy, groundZ + 0.5)
teleport.boolean = false
teleport.packet = 0
end
function infobar()
local font = renderCreateFont('Comic Sans MS', 10, 4)
local realtorFont = renderCreateFont('Arial', 9, 5)
while true do wait(0)
local statusText = ''
if apartment.hunting then
statusText = apartment.target and ('{69ff69}HUNT: #' .. apartment.target.aptNumber) or '{ffff69}HUNTING...'
elseif catch.active then
statusText = '{ffff69}' .. catch.status
elseif teleport.boolean then
statusText = '{ffff69}TP ' .. teleport.persent .. '%'
else
local s = '{ff6969}N/A'
if isCharOnFoot(PLAYER_PED) then
local v = getVehicle(200, true, false)
if v then s = '{69ff69}OK (id:' .. v .. ')' end
end
statusText = '{FFFFFF}' .. s
end
if teleport.bar_move then
if sampGetCursorMode() ~= 2 then sampSetCursorMode(2) end
local px, py = getCursorPos()
if isKeyJustPressed(0x01) then
sampSetCursorMode(0); ini.main.barX, ini.main.barY = px, py; teleport.bar_move = false; SaveCfg()
elseif isKeyJustPressed(0x02) then
sampSetCursorMode(0); teleport.bar_move = false
end
renderFontDrawText(font, rainbowText('r-realtor by @purpurins', 1) .. '\n' .. statusText, px, py, -1)
elseif ini.main.bar then
renderFontDrawText(font, rainbowText('r-realtor by @purpurins', 1) .. '\n' .. statusText, ini.main.barX, ini.main.barY, -1)
end
-- r-realtor by @purpurins HUD
if ini.main.bar then
local huntColor = apartment.hunting and '{69FF69}' or '{FF6969}'
local huntText = apartment.hunting and 'ON' or 'OFF'
local paydayColor = ini.catch.autoPayday and '{69FF69}' or '{FF6969}'
local paydayText = ini.catch.autoPayday and 'ON' or 'OFF'
local aptText = 'NONE'
local aptColor = '{FF6969}'
if apartment.target then
aptText = '#' .. apartment.target.aptNumber .. ' Ent#' .. apartment.target.entrance
aptColor = '{69FF69}'
end
local stText = 'IDLE'
local stColor = '{FFFFFF}'
if apartment.waitingBuy then
stText = 'BUYING'
stColor = '{69FF69}'
elseif apartment.waitingElevator then
stText = 'ELEVATOR'
stColor = '{FFFF69}'
elseif apartment.hunting and apartment.target then
stText = 'CATCHING'
stColor = '{FFFF69}'
elseif apartment.hunting then
stText = 'SEARCHING'
stColor = '{FFFF69}'
end
local realtorHud = '{FFFFFF}r-realtor by @purpurins\n'
.. '{FFFFFF}HUNTING: ' .. huntColor .. '[' .. huntText .. ']\n'
.. '{FFFFFF}PAYDAY: ' .. paydayColor .. '[' .. paydayText .. ']\n'
.. '{FFFFFF}APT: ' .. aptColor .. '[' .. aptText .. ']\n'
.. '{FFFFFF}STATUS: ' .. stColor .. '[' .. stText .. ']\n'
.. '{FFFFFF}LIMIT: {FFFF69}[' .. ini.catch.priceLimit .. ']\n'
.. '{FFFFFF}FREE: {FFFF69}[' .. countFreeApartments() .. ']\n'
.. '{FFFFFF}ORDER: {FFFF69}[#' .. ini.catch.apartmentIndex .. ']'
renderFontDrawText(realtorFont, realtorHud, ini.main.barX, ini.main.barY + 40, -1)
end
end
end
function teleportToCar(mx, my, mz, cx, cy, cz)
local h = cz > mz
local step = ini.catch.curdStep
while getDistanceBetweenCoords3d(mx, my, mz, cx, cy, cz) >= step and teleport.boolean do
local vec = vector(cx - mx, cy - my, cz - mz)
vec:normalize()
mx = mx + vec.x * step; my = my + vec.y * step; mz = mz + vec.z * step
sendPlayer(mx, my, mz, h)
wait(0)
end
sendPlayer(cx, cy, cz, h)
end
function sendPassenger(x, y, z, vehicleId)
local vehicleHandle = select(2, sampGetCarHandleBySampVehicleId(tonumber(vehicleId)))
local rx, ry, rz, dx, dy, dz = getCarRealMatrix(vehicleHandle)
local u = createSync('unoccupied')
u.vehicleId = tonumber(vehicleId); u.seatId = 0
u.roll = {rx, ry, rz}; u.direction = {dx, dy, dz}
u.vehicleHealth = getCarHealth(vehicleHandle); u.position = {x, y, z}; u.send()
teleport.packet = teleport.packet + 1
if teleport.packet == 10 then
local p = createSync('passenger')
p.vehicleId = tonumber(vehicleId); p.seatId = 1
p.health = getCharHealth(PLAYER_PED); p.armor = getCharArmour(PLAYER_PED)
p.position = {x, y, z}; p.send()
teleport.packet = 0
end
sendPlayer(x, y, z, true, vehicleId)
end
function sendPlayer(x, y, z, h, surfingVehicleId)
local p = createSync('player')
if tonumber(surfingVehicleId) then
p.surfingOffsets = {1, 1, 1}; p.surfingVehicleId = tonumber(surfingVehicleId)
end
p.moveSpeed = {0.1, 0.1, h and 0.1 or -0.1}; p.position = {x, y, z}; p.send()
end
function onSendPacket(id, bs, priority, reliability, orderingChannel)
if teleport.boolean then return false end
end
function createSync(sync_type, copy_from_player)
copy_from_player = (copy_from_player == nil) and true or copy_from_player
local sync_traits = {
player = {'PlayerSyncData', raknet.PACKET.PLAYER_SYNC, sampStorePlayerOnfootData},
passenger = {'PassengerSyncData', raknet.PACKET.PASSENGER_SYNC, sampStorePlayerPassengerData},
unoccupied = {'UnoccupiedSyncData', raknet.PACKET.UNOCCUPIED_SYNC, nil}
}
local si = sync_traits[sync_type]
local dt = 'struct ' .. si[1]
local data = ffi.new(dt, {})
local ptr = tonumber(ffi.cast('uintptr_t', ffi.new(dt .. '*', data)))
if copy_from_player then
local cf = si[3]
if cf then
local pid = (copy_from_player == true) and select(2, sampGetPlayerIdByCharHandle(PLAYER_PED)) or tonumber(copy_from_player)
cf(pid, ptr)
end
end
local function func_send()
local bs = raknetNewBitStream()
raknetBitStreamWriteInt8(bs, si[2])
raknetBitStreamWriteBuffer(bs, ptr, ffi.sizeof(data))
raknetSendBitStreamEx(bs, sampfuncs.HIGH_PRIORITY, sampfuncs.UNRELIABLE_SEQUENCED, 1)
raknetDeleteBitStream(bs)
end
return setmetatable({send = func_send}, {
__index = function(t, k) return data[k] end,
__newindex = function(t, k, v) data[k] = v end
})
end
-- mark pointer: resolved by signature in clientside.dll, static offset is the fallback
local MARK_SIG = '8B 0D ? ? ? ? 8D 45 F0 50 E8 11' -- mov ecx,[mark_slot] in clientside.dll (from mark_dump)
local MARK_SIG_DISP = 2 -- byte index of the 4-byte absolute address inside the pattern
local MARK_OFFSET_FALLBACK = 0x43C350 -- RVA found by mark_dump (was 0x43F220), used only if the signature is not found
local markPtrAddr = nil
local function findSig(mh, sig)
local bytes, mask = {}, {}
for tok in sig:gmatch('%S+') do
if tok == '?' or tok == '??' then bytes[#bytes + 1] = 0; mask[#mask + 1] = false
else bytes[#bytes + 1] = tonumber(tok, 16); mask[#mask + 1] = true end
end
local n = #bytes
if n == 0 then return nil end
local nt = ffi.cast('uint32_t*', mh + 0x3C)[0]
local codeSize = ffi.cast('uint32_t*', mh + nt + 0x1C)[0] -- SizeOfCode
local codeBase = ffi.cast('uint32_t*', mh + nt + 0x2C)[0] -- BaseOfCode
local base = ffi.cast('uint8_t*', mh + codeBase)
for i = 0, codeSize - n do
local ok = true
for j = 1, n do
if mask[j] and base[i + j - 1] ~= bytes[j] then ok = false; break end
end
if ok then return mh + codeBase + i end
end
return nil
end
local function resolveMarkPtrAddr(mh)
if markPtrAddr then return markPtrAddr end
local hit = findSig(mh, MARK_SIG)
if hit then
markPtrAddr = ffi.cast('uint32_t*', hit + MARK_SIG_DISP)[0]
return markPtrAddr
end
if MARK_SIG ~= '' then
sampAddChatMessage('{ff6969}[r-realtor by @purpurins] mark signature not found, using old offset', -1)
end
markPtrAddr = mh + MARK_OFFSET_FALLBACK
return markPtrAddr
end
function getMark()
local mh = getModuleHandle("clientside.dll")
if mh == 0 then return false end
local ptr = memory.getint32(resolveMarkPtrAddr(mh), true)
if ptr == 0 then return false end
local tx = memory.getfloat(ptr + 0x10, true)
local ty = memory.getfloat(ptr + 0x14, true)
if tx == 0.0 and ty == 0.0 then return false end
local tz = getGroundZFor3dCoord(tx, ty, 1000.0)
return true, tx, ty, (tz == 0 and 15.0 or tz)
end
-- set position only after collision/scene at the target is loaded (interiors: no floor yet = fall into the void)
function safeSetPos(x, y, z, timeoutSec)
requestCollision(x, y)
loadScene(x, y, z)
local deadline = os.clock() + (timeoutSec or 3.0)
while os.clock() < deadline do
local gz = getGroundZFor3dCoord(x, y, z + 1.0)
if gz ~= 0 then break end
requestCollision(x, y)
wait(50)
end
setCharCoordinates(PLAYER_PED, x, y, z)
end
function getCheckPoint()
local rx, ry, rz = 0.0, 0.0, 0.0
local found = false
for id = 0, 31 do
local ms = 0xC7F168 + id * 56
local mx = representIntAsFloat(readMemory(ms + 0, 4, false))
local my = representIntAsFloat(readMemory(ms + 4, 4, false))
local mz = representIntAsFloat(readMemory(ms + 8, 4, false))
if mx ~= 0.0 or my ~= 0.0 or mz ~= 0.0 then rx, ry, rz = mx, my, mz; found = true end
end
return found, rx, ry, rz
end
function getCarRealMatrix(handle)
local entity = getCarPointer(handle)
if entity ~= 0 then
local cm = memory.getuint32(entity + 20, true)
if cm ~= 0 then
return memory.getfloat(cm+0,true), memory.getfloat(cm+4,true), memory.getfloat(cm+8,true),
memory.getfloat(cm+16,true), memory.getfloat(cm+20,true), memory.getfloat(cm+24,true)
end
end
end
function getVehicle(checkDist, passenger, driver)
local dist, id = 9999, -1
local px, py, pz = getCharCoordinates(PLAYER_PED)
for _, veh in pairs(getAllVehicles()) do
local exist, vid = sampGetVehicleIdByCarHandle(veh)
if exist then
local dc = (getDriverOfCar(veh) ~= -1)
if (not driver and not dc) or (driver and dc) then
local cx, cy, cz = getCarCoordinates(veh)
local nd = getDistanceBetweenCoords3d(cx, cy, cz, px, py, pz)
if (passenger and getMaximumNumberOfPassengers(veh) > 0) or not passenger then
if nd < checkDist and dist > nd then id = vid; dist = nd end
end
end
end
end
return id ~= -1 and id or false
end
function rainbowText(text, speed)
local r = {}
local len = #text
for i = 1, len do
local c = text:sub(i, i)
local rv = math.floor(math.sin((os.clock() + (1.00/len)*i) * speed + 0) * 127 + 128)
local gv = math.floor(math.sin((os.clock() + (1.00/len)*i) * speed + 2) * 127 + 128)
local bv = math.floor(math.sin((os.clock() + (1.00/len)*i) * speed + 4) * 127 + 128)
r[#r+1] = string.format('{%02X%02X%02X}%s', rv, gv, bv, c)
end
return table.concat(r)
end