I need some help, I was using DeepSeek to help me write a Python script to help me mass-edit a bunch of .jbeam files, but I'm having some trouble. Python on its own doesn't support .jbeam format, and I couldn't find anything to help it support it. Python tries to convert to .json and vice versa and has issues that way. Also tried having DeepSeek help write a custom parser for the script that's specifically for .jbeam and it still has a lot of trouble. Various elements are added to new lines instead of sharing lines, spacing and indentation ends up wrong, etc. Here's what I'm trying to do: I'm creating new vehicle parts in the vehicle customizer for doors, hoods, and trunks. These doors when selected will not have break open and will stay closed. The script will search a specified directory's .jbeam files and check for part elements that are openable (autocoupler and couplernodes and stuff). If an element in the list of parts in the jbeam file does not contain them, delete the element from the list of parts in the jbeam. If the entire .jbeam file does not contain any elements with coupling, delete the file. If it does, append "_latched" to the part internal name, "(Latched)" to the part display name, and change the "autoCouplingStrength" to "999999999". For example: Code: "bastion_hood": { "information":{ "authors":"BeamNG", "name":"Hood", "value":320, }, "slotType" : "bastion_hood", "flexbodies": [ ["mesh", "[group]:", "nonFlexMaterials"], ["bastion_hood", ["bastion_hood"]], ["bastion_hood_shield", ["bastion_hood"]], ], "controller": [ ["fileName"], ["advancedCouplerControl", {"name":"hoodLatchCoupler"}], ["advancedCouplerControl", {"name":"hoodCatchCoupler"}], ] "hoodLatchCoupler":{ "groupType": "autoCoupling", "couplerNodes":[ ["cid1", "cid2", "autoCouplingStrength", "autoCouplingRadius", "autoCouplingLockRadius", "autoCouplingSpeed", "couplingStartRadius", "breakGroup"] ["f15", "h8", 30000, 0.01, 0.005, 0.2, 0.1, "hood_latch"] ] "attachSoundVolume":1, "detachSoundVolume":1, "soundNode:":["f15"] "attachSoundEvent": "event:>Vehicle>Latches>Hood>90s_02_close", "detachSoundEvent": "event:>Vehicle>Latches>Hood>90s_03_open_lever", "breakSoundEvent":"" "openForceMagnitude": 0, "openForceDuration": 0.0, "closeForceMagnitude": 0, "closeForceDuration": 0.0 } } Becomes: Code: "bastion_hood_latched": { "information":{ "authors":"BeamNG", "name":"Hood (Latched)", "value":320, }, "slotType" : "bastion_hood", "flexbodies": [ ["mesh", "[group]:", "nonFlexMaterials"], ["bastion_hood", ["bastion_hood"]], ["bastion_hood_shield", ["bastion_hood"]], ], "controller": [ ["fileName"], ["advancedCouplerControl", {"name":"hoodLatchCoupler"}], ["advancedCouplerControl", {"name":"hoodCatchCoupler"}], ] "hoodLatchCoupler":{ "groupType": "autoCoupling", "couplerNodes":[ ["cid1", "cid2", "autoCouplingStrength", "autoCouplingRadius", "autoCouplingLockRadius", "autoCouplingSpeed", "couplingStartRadius", "breakGroup"] ["f15", "h8", 999999999, 0.01, 0.005, 0.2, 0.1, "hood_latch"] ] "attachSoundVolume":1, "detachSoundVolume":1, "soundNode:":["f15"] "attachSoundEvent": "event:>Vehicle>Latches>Hood>90s_02_close", "detachSoundEvent": "event:>Vehicle>Latches>Hood>90s_03_open_lever", "breakSoundEvent":"" "openForceMagnitude": 0, "openForceDuration": 0.0, "closeForceMagnitude": 0, "closeForceDuration": 0.0 } } But like I said, attempts to make a script for this has caused there to be incorrect spacing, indentation, and lines, causing errors. Other times I try to make something it puts EVERYTHING on just one very long line like a paragraph. Is there anything I can use for Python to have a script that reads/writes .jbeam properly?
For parsing, you can try hjson: https://github.com/hjson implementation for c++ seems to work, not sure how it will work with python
You could do this entire thing through lua without having to deal with parsing issues from a python json library. The game has util functions for loading and saving json format that work perfectly with jbeam files: jsonReadFile and jsonWriteFile. I use these for editing config files to save custom data like mirror offsets. The write function has a parameter to have the proper indentations. The read function will return a table you can iterate through to find the fields you're looking for, remove or edit them and then save the edited table with the write function. You can loop over all jbeam files with FS:findFiles and then FS:removeFile to delete those you don't want. But because of the way the BeamNG virtual filesystem works, only the edited jbeam files you save would end up in the userfolder so you don't need to delete anything. If you plan on releasing a mod, starting from a brand new userfolder you can basically just grab the userfolder/vehicles folder that will only contain your edited files and throw it in a zip.
Throw this code in a lua script (ex: userfolder/myScript.lua) and run it from console (extensions.myScript.myFunction()) Code: local M = {} local function myFunction() local jbeamFiles = FS:findFiles("/vehicles/", "*.jbeam", -1) local cjbeam = {} local saveCurrent = false local cpartedited = {} local cjbeamedited = {} for _,path in pairs(jbeamFiles) do saveCurrent = false cjbeam = jsonReadFile(path) cjbeamedited = deepcopy(cjbeam) for pname,part in pairs(cjbeam) do cpartedited = deepcopy(part) for k,v in pairs(cpartedited) do if string.find(k, "Coupler") then -- found coupler if v["couplerNodes"] then saveCurrent = true -- mark file for saving for field_index,field_name in ipairs(v["couplerNodes"][1]) do -- loop over couplerNodes headers if field_name == "autoCouplingStrength" then for index,array in ipairs(v["couplerNodes"]) do if index ~= 1 then array[field_index] = 999999999 end end end end end end end if saveCurrent then cpartedited["information"]["name"] = cpartedited["information"]["name"] .. " Latched" cjbeamedited[pname .. "_latched"] = cpartedited end end -- parts loop if saveCurrent then jsonWriteFile(path, cjbeamedited, true) end end -- files loop end M.myFunction = myFunction return M I think it does what you want, at least as far as the jbeam files are concerned. The formatting isn't the same as the original files which are most likely hand made for the most part, but the jbeam file still works fine in game. In game the latched part doesn't release no matter how hard you pull on it. Also attached the output from that script in a zip file. Put it in your mods folder and it should add the latched parts.
Thanks a lot! Will this still delete the entries for non-coupler parts? Because that way I don't override the other misc parts. Like if you look at, say, a door, it'll have the door itself in the list, as well as any extra doors (like custom ones or for different body types), but then they also contain door cards and stuff in the list too, and so deleting the door cards and any misc parts in those lists means I'm not overriding anything else.
It only saves jbeam files that have edited coupler parts in them but those files could indeed contain non coupler parts, although they aren't edited. If you don't want any non coupler parts in the edited jbeam files you need to create a new unique jbeam file that only contains the edited coupler parts. Edit: I previously posted changes to make but realized it needed a few extra lines so I just edited the code. Still haven't tested but I believe this should work. First edit is using an empty table instead of copying the existing jbeam data so only edited parts are stored, second edit is using a bool to only add the edited parts to that jbeam table and final edit is to append "_latched" to the filename so it creates a unique jbeam file instead of overwriting the original. Code: local M = {} local function myFunction() local jbeamFiles = FS:findFiles("/vehicles/", "*.jbeam", -1) local cjbeam = {} local saveCurrent = false local addPart = false local cpartedited = {} local cjbeamedited = {} for _,path in pairs(jbeamFiles) do saveCurrent = false cjbeam = jsonReadFile(path) cjbeamedited = {} for pname,part in pairs(cjbeam) do addPart = false cpartedited = deepcopy(part) for k,v in pairs(cpartedited) do if string.find(k, "Coupler") then -- found coupler if v["couplerNodes"] then saveCurrent = true -- mark file for saving addPart = true -- mark part to add to edited jbeam for field_index,field_name in ipairs(v["couplerNodes"][1]) do -- loop over couplerNodes headers if field_name == "autoCouplingStrength" then for index,array in ipairs(v["couplerNodes"]) do if index ~= 1 then array[field_index] = 999999999 end end end end end end end if addPart then cpartedited["information"]["name"] = cpartedited["information"]["name"] .. " Latched" cjbeamedited[pname .. "_latched"] = cpartedited end end -- parts loop if saveCurrent then jsonWriteFile(string.gsub(path, ".jbeam", "_latched.jbeam"), cjbeamedited, true) end end -- files loop end M.myFunction = myFunction return M
Tried running extensions.myScript.myFunction() and this happened. The lua is just saved in the root userfolder.
Works fine for me. If you had the game running while you added the script you need to hit CTRL+L to reload lua scripts. If you make edits you need to do it as well to load changes. I just tested it and the only issue I noticed is that the string matching to replace ".jbeam" wasn't detecting dot character literally which could change some folder names, I don't think it would cause any problems but the fix is just adding a percent sign before the dot so: Replace this line Code: jsonWriteFile(string.gsub(path, ".jbeam", "_latched.jbeam"), cjbeamedited, true) With this line Code: jsonWriteFile(string.gsub(path, "%.jbeam", "_latched.jbeam"), cjbeamedited, true) I've once again attached the new output, you can see it worked as expected and created jbeam files that only contain latched variants of parts that have couplers.
The script should be inside the 0.34 subfolder, the game doesn't load anything before that into the virtual filesystem so that version specific subfolder is the actual root of the userfolder as far as the game is concerned. This post explains in detail how this system works, you should take some time to read through it. Even if you manually migrate the userfolder to a different location, the game will still create version specific subfolders inside the folder you picked. That version specific subfolder is where all your configs, settings and mods are saved. That's the folder that opens up if you use the launcher to go to the userfolder. I think all the old data might have contributed to the confusion, prior to 0.22 the game didn't have version specific userfolder so that folder was the root of the userfolder but now the actual root is a version specific subfolder. You can remove pretty much everything except the version specific userfolders (all of which are optional except the current version) and the file version.txt which the game uses to pick the correct userfolder to load. Should look something like this: Just move the extra stuff to another folder so you don't lose anything in there, but keep in mind the game hasn't been using any of these files since 0.22 dropped so most likely it's all useless.
The code stops after the scintilla because it can't decode scintilla_engine_v8_ece.jbeam or something like that. No idea what that file is or where it came from. I already checked my mods for it and it DID exist in the main game's scintilla.zip but I deleted the whole zip and just verified and it's not there anymore, but the issue persists. I'll have to check again. I can't use safe mode because then I can't use the lua. Edit: Figured out what mod is causing the issue, but then it gets hung up on another mod jbeam. It's a little annoying because the biggest thing is not only having it do vanilla cars but also modded parts and vehicles, and I don't know why it keeps getting hung up on various modded jbeams.
Modded jbeam files very often have issues because of typos, missing fields, etc. Best you can do is avoid files that cause problems. Show me console output if there are any errors as well as the contents of a jbeam file that doesn't work.