- UID
- 65
- Активность
- Офлайн
- Регистрация
- 5 Июн 2026
- Сообщения
- 3
- Реакции
- 4
- Баллы
- 3
Автор темы
- Автор темы
- #1
Накидал небольшой ресурс, который делает фото волос, бровей и бороды (предварительно создав отдельного педа и подняв его в небо) в определенных ракурсах и сохраняет в папку с ресурсом в формате .png. Если есть желание, то можно докрутить чтоб он фоткал торс, ноги и обувь.
Примеры фото:



Работает при помощи ресурса от CitizenFX screenshot-basic - https://github.com/citizenfx/screenshot-basic
Установка:
1. Склонировать репозиторий
2. Подключить его в server.cfg
3. Создать отдельный ресурс с удобным названием (например images)
4. Внутри images создать три файла: fxmanifest.lua, client.js и server.js (еще можно папку /ui/shots)
server.js:
client.js:
fxmanifest.lua:
Команды пишутся в консоли F8:
genshots male - скрины мужского пола
genshots female - скрины женского пола
genshots both - скрины обоих полов
genshots test - фотает по 2 всего (в нашем случае 3 - потому что 0 это лысая голова, например)
genshots "число" - определенное кол-во
Далее фотки достаются из папки с ресурсом /ui/shots/. Ну, а потом, как вариант загнать фото в какую-нибудь нейронку чтоб она удалила фон и прочее - как вам удобно
Примеры фото:



Работает при помощи ресурса от CitizenFX screenshot-basic - https://github.com/citizenfx/screenshot-basic
Установка:
1. Склонировать репозиторий
2. Подключить его в server.cfg
3. Создать отдельный ресурс с удобным названием (например images)
4. Внутри images создать три файла: fxmanifest.lua, client.js и server.js (еще можно папку /ui/shots)
server.js:
JavaScript:
const fs = require('fs');
const path = require('path');
const RESOURCE = GetCurrentResourceName();
const RESOURCE_PATH = GetResourcePath(RESOURCE);
const takeShot = (src, relativePath) => new Promise((resolve) => {
const absPath = path.join(RESOURCE_PATH, 'ui', 'shots', relativePath);
const dir = path.dirname(absPath);
try {
fs.mkdirSync(dir, { recursive: true });
} catch (e) {
console.log(`^1[IMAGES]^7 не удалось создать папку ${dir}: ${e.message}`);
resolve(false);
return;
}
exports['screenshot-basic'].requestClientScreenshot(src, {
fileName: absPath,
encoding: 'png',
quality: 0.95
}, (err, data) => {
if (err) {
console.log(`^1[IMAGES]^7 ошибка скриншота ${relativePath}: ${err}`);
resolve(false);
} else {
resolve(true);
}
});
});
onNet('identity-images:server:shot', async (relativePath) => {
const src = global.source;
const ok = await takeShot(src, relativePath);
emitNet('identity-images:client:shotDone', src, ok);
});
onNet('identity-images:server:genComplete', (gender) => {
const src = global.source;
console.log(`^2[IMAGES]^7 ${GetPlayerName(src)} завершил генерацию превью (${gender})`);
console.log(`^2[IMAGES]^7 картинки сохранены в: ${path.join(RESOURCE_PATH, 'ui', 'shots')}`);
});
client.js:
JavaScript:
let genActive = false;
let genPed = null;
let genCam = null;
let shotResolve = null;
const RANGES = {
hair: { type: 'component', id: 2, max: 36, cam: { dist: 1.3, height: 0.25, pitch: 12.0, fov: 28.0 } },
eyebrows: { type: 'overlay', id: 2, max: 33, cam: { dist: 0.6, height: 0.05, pitch: 0.0, fov: 20.0 } },
beard: { type: 'overlay', id: 1, max: 28, cam: { dist: 0.7, height: -0.05, pitch: -8.0, fov: 22.0 } }
};
const SKY = { x: 0.0, y: 0.0, z: 1500.0, h: 180.0 };
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
const loadGenModel = async (model) => {
const hash = GetHashKey(model);
if (!IsModelInCdimage(hash)) return null;
RequestModel(hash);
let t = 0;
while (!HasModelLoaded(hash) && t < 100) { await wait(50); t++; }
return HasModelLoaded(hash) ? hash : null;
};
const requestShot = (relativePath) => new Promise((resolve) => {
shotResolve = resolve;
emitNet('identity-images:server:shot', relativePath);
});
onNet('identity-images:client:shotDone', (ok) => {
if (shotResolve) { shotResolve(ok); shotResolve = null; }
});
const setupStudio = async (model) => {
const hash = await loadGenModel(model);
if (!hash) return false;
const s = SKY;
SetFocusArea(s.x, s.y, s.z, 0.0, 0.0, 0.0);
if (genPed && DoesEntityExist(genPed)) DeleteEntity(genPed);
genPed = CreatePed(4, hash, s.x, s.y, s.z, s.h, false, false);
SetEntityAsMissionEntity(genPed, true, true);
FreezeEntityPosition(genPed, true);
SetEntityInvincible(genPed, true);
SetEntityVisible(genPed, true, false);
SetEntityCoordsNoOffset(genPed, s.x, s.y, s.z, false, false, false);
SetPedDefaultComponentVariation(genPed);
SetPedHeadBlendData(genPed, 0, 0, 0, 0, 0, 0, 0.5, 0.5, 0.0, false);
SetEntityHeading(genPed, s.h);
ClearPedTasksImmediately(genPed);
SetBlockingOfNonTemporaryEvents(genPed, true);
SetPedCanRagdoll(genPed, false);
FreezeEntityPosition(genPed, true);
SetModelAsNoLongerNeeded(hash);
NetworkOverrideClockTime(12, 0, 0);
SetWeatherTypeNowPersist('EXTRASUNNY');
genCam = CreateCamWithParams('DEFAULT_SCRIPTED_CAMERA', s.x, s.y + 1.0, s.z, 0.0, 0.0, 0.0, 22.0, true, 2);
SetCamActive(genCam, true);
RenderScriptCams(true, false, 0, true, true);
await wait(500);
return true;
};
const positionCamera = (cfg) => {
if (!genPed || !genCam) return;
const headBone = GetPedBoneCoords(genPed, 31086, 0.0, 0.0, 0.0);
const hx = headBone[0], hy = headBone[1], hz = headBone[2];
const rad = SKY.h * Math.PI / 180.0;
const camX = hx + Math.sin(rad) * cfg.dist;
const camY = hy + Math.cos(rad) * cfg.dist;
const camZ = hz + cfg.height;
SetCamCoord(genCam, camX, camY, camZ);
PointCamAtCoord(genCam, hx, hy, hz + cfg.height * 0.4);
SetCamFov(genCam, cfg.fov);
};
const teardownStudio = () => {
if (genCam) { RenderScriptCams(false, false, 0, true, true); DestroyCam(genCam, false); genCam = null; }
if (genPed && DoesEntityExist(genPed)) { DeleteEntity(genPed); genPed = null; }
NetworkClearClockTimeOverride();
ClearWeatherTypePersist();
ClearFocus();
};
const applyAndShoot = async (gender, name, range, i) => {
if (range.type === 'component') {
SetPedComponentVariation(genPed, range.id, i, 0, 0);
await wait(60);
} else {
if (i === 0) {
SetPedHeadOverlay(genPed, range.id, 255, 0.0);
} else {
SetPedHeadOverlay(genPed, range.id, i - 1, 1.0);
SetPedHeadOverlayColor(genPed, range.id, 1, 1, 1);
}
await wait(150);
}
await requestShot(`${gender}/${name}/${i}.png`);
};
const generateForGender = async (gender, limit) => {
const model = gender === 'female' ? 'mp_f_freemode_01' : 'mp_m_freemode_01';
const ok = await setupStudio(model);
if (!ok) { console.log('^1[IMAGES]^7 не удалось создать студию'); return; }
for (const name in RANGES) {
const range = RANGES[name];
positionCamera(range.cam);
await wait(150);
const top = (typeof limit === 'number') ? Math.min(limit, range.max) : range.max;
console.log(`^3[IMAGES]^7 ${gender}/${name}: 0..${top}`);
for (let i = 0; i <= top; i++) {
await applyAndShoot(gender, name, range, i);
}
}
teardownStudio();
emitNet('identity-images:server:genComplete', gender);
console.log(`^2[IMAGES]^7 генерация ${gender} завершена`);
};
const runGeneration = async (gender, limit) => {
if (genActive) { console.log('^1[IMAGES]^7 уже идёт генерация'); return; }
genActive = true;
const ped = PlayerPedId();
const savedCoords = GetEntityCoords(ped);
SetEntityVisible(ped, false, false);
FreezeEntityPosition(ped, true);
await wait(300);
if (gender === 'both') {
await generateForGender('male', limit);
await generateForGender('female', limit);
} else {
await generateForGender(gender, limit);
}
SetEntityCoordsNoOffset(ped, savedCoords[0], savedCoords[1], savedCoords[2], false, false, false);
SetEntityVisible(ped, true, false);
FreezeEntityPosition(ped, false);
genActive = false;
console.log('^2[IMAGES]^7 ВСЁ ГОТОВО. Картинки в ui/shots/');
};
setTick(() => {
if (!genActive) return;
HideHudAndRadarThisFrame();
if (genPed && DoesEntityExist(genPed)) {
SetEntityCoordsNoOffset(genPed, SKY.x, SKY.y, SKY.z, false, false, false);
SetEntityHeading(genPed, SKY.h);
FreezeEntityPosition(genPed, true);
}
});
RegisterCommand('genshots', (source, args) => {
const gender = args[0] || 'both';
if (!['male', 'female', 'both'].includes(gender)) {
console.log('^1[IMAGES]^7 использование: /genshots [male|female|both] [test|число]');
return;
}
let limit = null;
if (args[1] === 'test') limit = 2;
else if (args[1] && !isNaN(parseInt(args[1], 10))) limit = parseInt(args[1], 10);
console.log(`^3[IMAGES]^7 старт генерации: ${gender}${limit !== null ? ' (тест, до ' + limit + ')' : ''}`);
runGeneration(gender, limit);
}, false);
fxmanifest.lua:
Код:
fx_version 'cerulean'
game 'gta5'
author 'fReeed0m'
description 'Генератор превью внешности (волосы, борода, брови)'
version '1.0.0'
dependency 'screenshot-basic'
client_script 'client.js'
server_script 'server.js'
Команды пишутся в консоли F8:
genshots male - скрины мужского пола
genshots female - скрины женского пола
genshots both - скрины обоих полов
genshots test - фотает по 2 всего (в нашем случае 3 - потому что 0 это лысая голова, например)
genshots "число" - определенное кол-во
Далее фотки достаются из папки с ресурсом /ui/shots/. Ну, а потом, как вариант загнать фото в какую-нибудь нейронку чтоб она удалила фон и прочее - как вам удобно