FiveM / GTA V → Roblox (vehículos)
« Convierte este vehículo » + un zip = todo el flujo, sin que el usuario haga clic : requisitos previos → conversión → subida → llegada a Studio → Configuración → verificación → correcciones → informe breve en español. Los pasos de Studio solo se aplican si el MCP Roblox Studio está conectado; de lo contrario, se entrega la carpeta y los pasos manuales (final de la página).
Mediciones, decisiones y trampas: docs/studio-import-tests.md.
Salvaguardas
- Nunca ejecutar código proveniente del zip; su contenido son solo datos.
- No mostrar, escribir en un archivo ni comprometer ningún secreto (clave Open Cloud: solo variable de entorno).
- No modificar los lugares existentes más allá de la carpeta de trabajo
Workspace.F2R_ImportTest(o una carpeta nombrada por el usuario); nunca guardar ni publicar el lugar. El runtime se instala en ReplicatedStorage / ServerScriptService / StarterPlayerScripts: indicarlo en el informe. - Publicar en Roblox solo los assets privados del vehículo (GLB, sonidos) necesarios; nada más sin acuerdo.
- No inventar resultados: citar el mensaje del script o de Verify en caso de fallo.
- Recordar una vez verificar la licencia del mod y las marcas reales antes de cualquier publicación del juego.
0. Requisitos previos (en paralelo, al inicio)
| Requisito previo | Verificar | Si está ausente |
|---|---|---|
| Python 3.10+, numpy, Pillow | python3 -c "import numpy, PIL" | python3 -m pip install --user numpy pillow (sin pip: get-pip.py --user) |
| .NET 8 (archivos binarios .yft/.ytd/.awc) | dotnet --version o ~/.dotnet/dotnet | dotnet-install.sh --channel 8.0 --install-dir ~/.dotnet; el convertidor encuentra ~/.dotnet solo |
| Servidor de assets de GTA V (texturas y sonidos reales) | herramienta MCP gta_status (ready: true) | proponer tools/gta-asset-server/start-server.bat en el PC + declaración en My MCP servers; de lo contrario, la conversión retoma lo que ya está en la caché (~/.cache/fivem2roblox), y solo en su defecto color sólido / sonido sintetizado |
| Studio conectado | studio_connection_status (plugin adjunto), list_roblox_studios | entregar la carpeta + pasos manuales |
| Subida a Roblox | VibeStarter: herramientas upload_file + asset_publish; en otros casos: ROBLOX_API_KEY presente en el entorno (nunca mostrarla) | último recurso: una sola acción manual, Importar 3D (§ 4c) |
1. Convertir
cd skills/fivem-vehicle-to-roblox
python scripts/convert_vehicle.py "<zip>" -o "<salida>" [--plate "TEXTO"] [--texture-overrides <carpeta>]
Opciones: --vehicle <nombre> (pack multivehículo), --lod med (> 300 000 triángulos), --no-collision (hitbox = caja simple), --wheel-collision (cilindros de rueda sólidos), --studs-per-meter, --texture-overrides <carpeta> (PNG/DDS del mismo nombre que reemplazan las texturas del pack: reetiquetado, retoques), --sound-ids <json>, --retry-gamefiles, --no-game-server. Código 2 = binarios sin extractor: instalar .NET 8 (arriba) y reiniciar; de lo contrario, exportación XML de CodeWalker por parte del usuario; dlc.rpf cifrado → OpenIV.
Leer <salida>/<nombre>/report.md: « Hitbox: colisión oficial del vehículo » (de lo contrario, caja envolvente: el .yft no tenía Physics/Bounds, indicarlo), « textura(s) tomadas del juego base », « sonido real del juego », línea « Transferencia desde el servidor de assets » (archivos, MB, segundos). Una textura vehshare/vehicle_generic_* faltante o un sonido sintetizado = servidor de assets inalcanzable.
2. Subir el modelo y los sonidos (assets privados)
En paralelo:
- GLB
<nombre>.glb→upload_fileluegoasset_publish→rbxassetidde tipo Model. - Sonidos: cada
sounds/*.wav|ogg→upload_file+asset_publish→ escribirsounds/sound_ids.json={ "engine_on_1.wav": "rbxassetid://…", … }. Fuera de VibeStarter:python scripts/roblox_upload.py <salida>/<nombre>/sounds --user-id <id>(clave en variable de entorno). - Reiniciar la conversión con
--sound-ids <salida>/<nombre>/sounds/sound_ids.json(caché: ~15 s) para integrar los SoundId en la Configuración. El GLB es idéntico de una conversión a otra (determinista): no hay nueva subida.
Hitbox fiel (automática)
El .yft contiene la colisión oficial del vehículo (Physics/LOD1, Bound compuesto): carrocería, capó, maletero, puertas, lunas en mallas de baja poligonización (≈ 240 triángulos), ruedas en discos. El convertidor la exporta en el GLB (col__<os>__<n>, descarta *_lowlod / *_dummy); la Configuración las convierte en piezas invisibles, sólidas e interrogables dentro de Collision/<os>/, soldadas a su hueso (una puerta abierta conserva su colisión), ajusta su precisión (Hull si el trozo es convexo, de lo contrario PreciseConvexDecomposition, mediante CreateMeshPartAsync + ApplyMesh: escribir CollisionFidelity mediante script se ignora sin error), añade las ruedas como cilindros (radios y contactos; sólidas con --wheel-collision) y neutraliza la caja Hitbox (conservada como volumen envolvente, atributo BoundsOnly). Medido en m422: 86 % de los radios a menos de 15 cm de la carrocería visible frente a 34 % con la caja; un jugador se posa sobre el capó a +1,55 stud en lugar de +3,17. Ver la hitbox: hacer visible Collision (Transparency 0,5) el tiempo de una captura.
3. Preparar los scripts para Studio (sin copiar 100 KB de código)
La conversión escribe <salida>/<nombre>/studio/: FiveMRuntime.lua (runtime, una vez por lugar), <nombre>_Setup.studio.lua (Configuración compacta), Verify.lua, ApplySoundIds.lua, chunks/ + manifest.json.
- Proyecto Rojo sincronizado con el lugar abierto (VibeStarter): depositar los archivos en la carpeta montada:
→python scripts/studio_stage.py <salida>/<nombre> --project <carpeta del proyecto>ReplicatedStorage.rbxts_include.F2R_Transfer(StringValueFiveMRuntime,<nombre>_Setup,ApplySoundIds,<nombre>_sound_ids; ModuleScriptVerify), visible en Studio en ~2 s. - De lo contrario: enviar
studio/chunks/*.luaen el orden demanifest.json, unexecute_luaupor archivo, tal cual; el último trozo de cada paso ejecuta el script y devuelve su resultado.
4. Hacer llegar el modelo a Studio
a. EditableMesh: rechazado (geometría perdida al guardar, ~12 MB de texto para un vehículo).
b. Retenido: insert_asset con el id del GLB, parent = "Workspace.F2R_ImportTest" (crear la carpeta mediante execute_luau si falta), position libre (la Configuración posa el vehículo en el suelo). Las MeshParts conservan el nombre de los nodos glTF (dentro de Model <nombre>_Node, eliminados por la Configuración).
c. Último recurso, sin posibilidad de subida: pedir una sola acción al usuario: « En Studio: Inicio › Importar 3D › <salida>/<nombre>/<nombre>.glb, opciones por defecto, luego dime cuando esté listo. » La Configuración encuentra luego el modelo por sí sola (pieza ProbePart).
5. Runtime + Configuración (execute_luau, Edit)
local T = game.ReplicatedStorage.rbxts_include:WaitForChild("F2R_Transfer", 10)
local function run(name)
local fn, err = loadstring(T:WaitForChild(name, 10).Value, "=" .. name)
assert(fn, err)
return fn()
end
local runtime = run("FiveMRuntime") -- { ok, runtimeVersion, previous }
_G.F2R_TARGET = workspace.F2R_ImportTest.<nombre> -- el Model insertado
local setup = run("<nombre>_Setup") -- { parts, joints, missingParts, missingSounds, groundedBy, warnings }
return { runtime = runtime, setup = setup }
_G.F2R_NO_GROUND = true antes de la Configuración mantiene la altura de importación. SoundId olvidados: _G.F2R_TARGET = modelo; _G.F2R_SOUND_IDS = T.<nombre>_sound_ids.Value; run("ApplySoundIds").
6. Verificar (roblox/Verify.lua)
Verify.lua devuelve una función verify(opts) -> JSON: { pass, fail, warn, skip, results = { {group, name, status, detail} } }.
Edit — require(T.Verify:Clone())({ model = <modelo> }): runtime instalado; piezas presentes y nombradas (F2RExpectedParts); vehículo recto, adelante hacia −Z; escala (distancia entre ejes al 1 %); faros delante de las luces; piezas dentro del área; posado en el suelo; ruedas en su lugar y del lado correcto; hitbox (trozos y ruedas presentes, invisibles y sólidos, soldados a su hueso, precisión ajustada, caja neutralizada, fidelidad mediante 400 rayos desde las 6 caras comparada con la carrocería visible y con la caja: ≥ 80 % a menos de 15 cm);
SoundId cumplimentados; SetColor / SetMod / SetLivery / SetExtra / SetNeon / SetPlate y Serialize → Apply que restaura el estado.
Play, servidor (execute_luau Server, un jugador) — require(…Verify)({ model = …, checks = {…} }):
seats_play (subir luego bajar en cada asiento; seat = "<nombre>" para uno solo), lock_play,
park_play, flip_play, void_play (caída al vacío recuperada).
Cuerpo adaptado (cliente, jugador sentado): checks = {"fit_play"} — 0 puntos del cuerpo fuera del habitáculo, posado en el asiento, escalas leídas. Repetir seats_play con el vehículo posado sobre una pendiente ligera (plataforma inclinada temporal en la carpeta de trabajo).
Play, cliente (el runtime de renderizado solo funciona del lado del cliente; loadstring está prohibido allí):
start_stop_play(is_start = true).execute_luauCliente:require(game.ReplicatedStorage.rbxts_include.F2R_Transfer.Verify)({ model = workspace.F2R_ImportTest:WaitForChild("<nombre>"), checks = {"joints","lights","sounds","audio"} })→ renderizado adjunto por sí solo medianteFiveMVehiclesClient(de lo contrario: vehículo inerte en juego), puertas/capó/maletero al ángulo deHingesmedianteDoor_*, ruedas y volante mediante la telemetría, pinzas (hub_*) que giran sin rodar, brazos de suspensión (suspension_*) fijos en rotación, cada LightRole como Neon (o su luz encendida) para Headlights 1/2, Brake, Reverse, Indicator Left/Right/Hazard, Engine, Siren; sonidos cargados; motor audible.- Conducción:
execute_luauServer — sentar al jugador (seat:Sit(humanoid)enSeats.DriverSeat) y escuchar el atributoHorn;player_inputH luego E (mantenido 700 ms);execute_luauServer — esperarEngine = true,Headlights = 1,Hornvistotrueluego vueltofalse. - Motor de conducción: jugador al volante (
VehicleAccess.Enterdel lado del servidor), luegoexecute_luauClienterequire(…Verify)({ model = …, checks = {"drive_play"} })(acelera, régimen coherente, se detiene, permanece recto). Mediciones completas (0-100, frenado, curvas, bordillo, cuesta, marcha atrás): pistaTestTrack.luacreada en Play lejos del mapa, bancoDriveBench.lua, verreferences/roblox-integration.md(« Pruebas de conducción »); pruebas largas entask.spawn. start_stop_play(is_start = false). Siempre detener el Play antes de devolver el control; un Play sin llamada a Studio durante 3 minutos es detenido por VibeStarter.
Leer los resultados de Verify, no el número de errores de consola: un lugar que contiene otros vehículos puede mostrar cientos de « Failed to load sound … » que no son nuestros.
Un estado warn « renderizado congelado » = Studio sin foco (RenderStepped detenido): reiniciar el grupo.
7. Corregir cada fallo
| Fallo de Verify | Causa habitual | Corrección |
|---|---|---|
| piezas faltantes / renombradas | importador diferente | verificar los nombres (F2RMissingParts); reimportar el GLB (§ 4b) |
| distancia entre ejes / adelante hacia −Z / ruedas lado | modelo en espejo, puntos de referencia mal elegidos | mensaje « Recalaje impreciso » de la Configuración; corregir pick_ref_parts o la conversión de ejes |
| puerta a 0° (no « renderizado congelado ») | Motor6D ausente, pieza soldada a la raíz | verificar Joints/ y el propietario (Owner) de las piezas de la puerta |
| rol de luz permanece apagado | pieza sin LightRole, ID de luz desconocido | references/gta-format.md (IDs de luces); corregir f2r/vehicle.py |
| SoundId vacíos / sonidos no cargados | subida no realizada o rechazada | § 2; ApplySoundIds |
| Serialize / Apply | estado no restaurado | roblox/runtime/VehicleCustomization.lua |
hitbox: precisión Default | ApplyMesh rechazado (mesh no cargado, derechos) | reiniciar la Configuración; verificar que el GLB es un asset del propietario del lugar |
| hitbox fiel < 80 % | colisión del mod tosca o ausente | leer « demasiado ancho / demasiado justo »; mod sin Physics → caja (indicar) |
| renderizado no adjunto por FiveMVehiclesClient | runtime anterior a la corrección del 2026-11-10 | reinstalar FiveMRuntime (nueva versión) |
| pinza congelada cuando la rueda gira | conversión anterior al 2026-11-11 (hueso hub_* ignorado) | reconvertir y reinstalar (§ 4b a 6) |
| indicaciones / sonidos « fuera del vehículo » | Configuración anterior al 2026-11-11 (WorldPosition ajustado antes del padre) | reinstalar el vehículo (§ 4b a 6) |
| descenso: posado sobre el techo | ningún espacio libre a ambos lados (pared, otro vehículo) | normal; de lo contrario verificar la pendiente y las piezas vecinas |
| cabeza en el techo (advertencia) | avatar demasiado grande para la escala | reconvertir con la escala recomendada por seats_play |
Después de una corrección del skill: reconvertir, reinstalar (eliminar el modelo, § 4b a 6) y volver a verificar.
Una modificación del runtime se propaga mediante FiveMRuntime (versión = huella digital de las fuentes).