script_name("LazyCamHack by waxwing") script_version("1") require "lib.moonloader" local imgui = require "mimgui" local encoding = require "encoding" -- ===================================== -- КОДИРОВКА -- ===================================== encoding.default = "CP1251" local u8 = encoding.UTF8 local function ui(text) return text end -- ===================================== -- НАСТРОЙКИ -- ===================================== local cfg = { ped_dist = imgui.new.float(1.0), veh_dist = imgui.new.float(1.5), pair_dist = imgui.new.float(1.0), cam_height = imgui.new.float(0.7), top_height = imgui.new.float(4.0), shot_delay = imgui.new.int(500) } local win_state = imgui.new.bool(false) local is_working = false -- ===================================== -- СООБЩЕНИЯ -- ===================================== local function msg(text) sampAddChatMessage( u8:decode("{00FF00}[LazyCamHack] {FFFFFF}" .. text), -1 ) end -- ===================================== -- IMGUI -- ===================================== imgui.OnInitialize(function() imgui.GetIO().IniFilename = nil local style = imgui.GetStyle() style.WindowRounding = 8.0 style.FrameRounding = 4.0 style.WindowPadding = imgui.ImVec2(14, 14) style.ItemSpacing = imgui.ImVec2(8, 9) end) imgui.OnFrame( function() return win_state[0] end, function() local sw, sh = getScreenResolution() imgui.SetNextWindowPos( imgui.ImVec2(sw / 2, sh / 2), imgui.Cond.FirstUseEver, imgui.ImVec2(0.5, 0.5) ) -- Увеличенное окно imgui.SetNextWindowSize( imgui.ImVec2(490, 470), imgui.Cond.FirstUseEver ) imgui.Begin( ui("LazyCamHack от waxwing"), win_state, imgui.WindowFlags.NoResize ) imgui.Text(ui("НАСТРОЙКИ КАМЕРЫ")) imgui.Separator() imgui.SliderFloat( ui("Дистанция пешком"), cfg.ped_dist, 0.1, 10.0, "%.2f" ) imgui.SliderFloat( ui("Дистанция автомобиля"), cfg.veh_dist, 0.1, 15.0, "%.2f" ) imgui.SliderFloat( ui("Дистанция общего кадра"), cfg.pair_dist, 0.1, 15.0, "%.2f" ) imgui.SliderFloat( ui("Высота обычных кадров"), cfg.cam_height, -1.0, 3.0, "%.1f" ) imgui.SliderFloat( ui("Высота верхних кадров"), cfg.top_height, 1.5, 8.0, "%.1f" ) imgui.Spacing() imgui.Separator() imgui.Text(ui("ДОПОЛНИТЕЛЬНО")) imgui.SliderInt( ui("Задержка скриншота"), cfg.shot_delay, 100, 1500 ) imgui.Spacing() if imgui.Button( ui("Сфотографировать себя"), imgui.ImVec2(-1, 36) ) then win_state[0] = false start_camhack("me") end imgui.Spacing() imgui.Separator() imgui.TextDisabled( ui("Команды:") ) imgui.TextDisabled( ui("/lch [ID] - фотографировать игрока") ) imgui.TextDisabled( ui("/lch me - фотографировать себя") ) imgui.End() end ) -- ===================================== -- КООРДИНАТЫ -- ===================================== local function get_entity_coords(ped) if not doesCharExist(ped) then return nil end if isCharInAnyCar(ped) then local veh = storeCarCharIsInNoSave(ped) if doesVehicleExist(veh) then return getCarCoordinates(veh) end end return getCharCoordinates(ped) end -- ===================================== -- СМЕЩЕНИЕ КАМЕРЫ -- ===================================== local function get_camera_offset(ped, x, y, z) if isCharInAnyCar(ped) then local veh = storeCarCharIsInNoSave(ped) if doesVehicleExist(veh) then return getOffsetFromCarInWorldCoords( veh, x, y, z ) end end return getOffsetFromCharInWorldCoords( ped, x, y, z ) end -- ===================================== -- СКРИНШОТ -- ===================================== local function take_shot(cx, cy, cz, tx, ty, tz) setFixedCameraPosition( cx, cy, cz, 0.0, 0.0, 0.0 ) pointCameraAtPoint( tx, ty, tz, 2 ) wait(cfg.shot_delay[0]) setVirtualKeyDown(0x77, true) wait(80) setVirtualKeyDown(0x77, false) wait(250) end -- ===================================== -- КАДР 1: EXTREME LOW ANGLE -- КАМЕРА У ЗЕМЛИ, ОБА ПЕРСОНАЖА -- ===================================== -- Первый кадр: камера почти касается земли, -- снимает обоих персонажей снизу вверх. local function take_low_angle(target) if not doesCharExist(PLAYER_PED) then return false end local mx, my, mz = getCharCoordinates(PLAYER_PED) if not mx or not my or not mz then return false end -- Отдельная камера для съёмки себя if target == PLAYER_PED then local cam_x, cam_y, cam_z = getOffsetFromCharInWorldCoords( PLAYER_PED, 0.0, 0.65, -0.75 ) if not cam_x or not cam_y or not cam_z then return false end take_shot( cam_x, cam_y, cam_z, mx, my, mz + 1.45 ) return true end if not doesCharExist(target) then return false end local tx, ty, tz = getCharCoordinates(target) if not tx or not ty or not tz then return false end local dx = tx - mx local dy = ty - my local len = math.sqrt(dx * dx + dy * dy) local cam_x, cam_y if len > 0.1 then local perp_x = -dy / len local perp_y = dx / len local cam_dist = math.max( 0.08, cfg.pair_dist[0] * 0.08 ) cam_x = (mx + tx) / 2 + perp_x * cam_dist cam_y = (my + ty) / 2 + perp_y * cam_dist else cam_x, cam_y = getOffsetFromCharInWorldCoords( PLAYER_PED, 0.0, 0.65, -0.75 ) end local cam_z = math.min(mz, tz) - 0.85 local look_x = (mx + tx) / 2 local look_y = (my + ty) / 2 local look_z = math.max(mz, tz) + 1.45 take_shot( cam_x, cam_y, cam_z, look_x, look_y, look_z ) return true end -- Второй кадр: такой же ракурс, как второй кадр /lch me local function take_target_portrait(target) if not doesCharExist(target) then return false end local tx, ty, tz = get_entity_coords(target) if not tx or not ty or not tz then return false end -- Используем то же смещение, что и при съёмке себя local cx, cy, cz = get_camera_offset( target, 0.0, 2.5, 0.7 ) if not cx or not cy or not cz then return false end -- Наводим камеру на лицо Target take_shot( cx, cy, cz, tx, ty, tz + 1.0 ) return true end -- ===================================== -- РАКУРСЫ ДВУХ ПЕРСОНАЖЕЙ -- ===================================== local cinematic_angles = { -- 3. HIGH ANGLE { side = -1.0, forward = 0.0, height = 4.5, distance = 1.0, look_height = 0.8 }, -- 4. LEFT CINEMATIC { side = 0.8, forward = 0.65, height = 1.2, distance = 1.0, look_height = 1.0 }, -- 5. RIGHT CINEMATIC { side = -0.8, forward = 0.65, height = 1.2, distance = 1.0, look_height = 1.0 }, -- 6. OVER SHOULDER { side = 0.65, forward = -0.8, height = 1.0, distance = 1.0, look_height = 1.1 }, -- 7. EPIC WIDE SHOT { side = 1.0, forward = 0.0, height = 3.8, distance = 1.6, look_height = 0.9 } } -- ===================================== -- ОБЩИЕ РАКУРСЫ -- ===================================== local function take_pair_shots(target) local shots = 0 for _, angle in ipairs(cinematic_angles) do if not doesCharExist(target) or not doesCharExist(PLAYER_PED) then break end local mx, my, mz = get_entity_coords(PLAYER_PED) local tx, ty, tz = get_entity_coords(target) if not mx or not tx then break end local mid_x = (mx + tx) / 2 local mid_y = (my + ty) / 2 local mid_z = (mz + tz) / 2 local dx = tx - mx local dy = ty - my local len = math.sqrt(dx * dx + dy * dy) if len < 0.1 then len = 0.1 end local dir_x = dx / len local dir_y = dy / len local perp_x = -dir_y local perp_y = dir_x -- Учитываем дистанцию между игроками local dist = ( cfg.pair_dist[0] + len * 0.15 ) * angle.distance local cx = mid_x + perp_x * dist * angle.side + dir_x * dist * angle.forward local cy = mid_y + perp_y * dist * angle.side + dir_y * dist * angle.forward local cz = mid_z + angle.height take_shot( cx, cy, cz, mid_x, mid_y, mid_z + angle.look_height ) shots = shots + 1 end return shots end -- ===================================== -- СЪЁМКА СЕБЯ: 7 КАДРОВ -- ===================================== local function take_self_shots() local shots = 0 if not doesCharExist(PLAYER_PED) then return 0 end -- Первый кадр: камера у земли if take_low_angle(PLAYER_PED) then shots = shots + 1 end -- Остальные шесть ракурсов local offsets = { {0.0, 2.5, 0.7}, {0.0, -3.0, 4.5}, {3.5, 2.0, 1.2}, {-3.5, 2.0, 1.2}, {3.5, -2.0, 1.0}, {-4.5, 0.0, 3.5} } for _, offset in ipairs(offsets) do if not doesCharExist(PLAYER_PED) then break end local px, py, pz = get_entity_coords(PLAYER_PED) if not px or not py or not pz then break end local cx, cy, cz = get_camera_offset( PLAYER_PED, offset[1], offset[2], offset[3] ) if not cx or not cy or not cz then break end take_shot( cx, cy, cz, px, py, pz + 1.0 ) shots = shots + 1 end return shots end -- ===================================== -- ФОТОСЕССИЯ: 7 КАДРОВ -- ===================================== local function take_cinematic_shots(target) if target == PLAYER_PED then return take_self_shots() end local shots = 0 -- 1. Камера у земли, оба персонажа take_low_angle(target) shots = shots + 1 -- 2. Портрет Target if doesCharExist(target) then take_target_portrait(target) shots = shots + 1 end -- 3-7. Остальные общие ракурсы if doesCharExist(target) then take_pair_shots(target) shots = shots + 5 end return shots end -- ===================================== -- ВОССТАНОВЛЕНИЕ ИНТЕРФЕЙСА -- ===================================== local function restore_game() pcall(function() setVirtualKeyDown(0x77, false) end) pcall(function() restoreCameraJumpcut() end) wait(300) pcall(function() displayHud(true) displayRadar(true) sampSetChatDisplayMode(2) end) end -- ===================================== -- ЗАПУСК ФОТОСЕССИИ -- ===================================== function start_camhack(arg) if is_working then msg("Съёмка уже выполняется!") return end local target -- /lch me if arg and arg:lower():match("^%s*me%s*$") then target = PLAYER_PED else local target_id = tonumber(arg) if not target_id or target_id < 0 or target_id > 1004 or target_id % 1 ~= 0 then msg("Использование: /lch [ID] или /lch me") return end -- Проверяем собственный ID local my_result, my_id = sampGetPlayerIdByCharHandle(PLAYER_PED) if my_result and target_id == my_id then target = PLAYER_PED else local result result, target = sampGetCharHandleBySampPlayerId(target_id) if not result then msg("Игрок не найден или не застримлен!") return end end end if not target or not doesCharExist(target) then msg("Персонаж недоступен!") return end lua_thread.create(function() is_working = true local shots_count = 0 displayHud(false) displayRadar(false) sampSetChatDisplayMode(0) wait(500) local success, err = pcall(function() if doesCharExist(target) then shots_count = take_cinematic_shots(target) end end) restore_game() is_working = false if not success then msg( "Ошибка фотосессии: " .. tostring(err) ) return end msg( string.format( "Съёмка завершена! Скриншотов: %d", shots_count ) ) end) end -- ===================================== -- MAIN -- ===================================== function main() repeat wait(100) until isSampAvailable() sampRegisterChatCommand("lch", function(arg) if arg == nil or arg:match("^%s*$") then win_state[0] = not win_state[0] else start_camhack(arg) end end) sampRegisterChatCommand( "lazycamhack", start_camhack ) msg("LazyCamHack v1 от waxwing загружен!") msg("Команды: /lch [ID], /lch me") while true do wait(0) end end