diff --git a/resources/jraxion_handlingeditor/.fxap b/resources/jraxion_handlingeditor/.fxap new file mode 100644 index 000000000..679a4b381 Binary files /dev/null and b/resources/jraxion_handlingeditor/.fxap differ diff --git a/resources/jraxion_handlingeditor/Readme.md b/resources/jraxion_handlingeditor/Readme.md new file mode 100644 index 000000000..1af297708 --- /dev/null +++ b/resources/jraxion_handlingeditor/Readme.md @@ -0,0 +1 @@ +Make sure the resource name is jraxion_handlingeditor, it will break the resource if you rename it. diff --git a/resources/jraxion_handlingeditor/client/client.lua b/resources/jraxion_handlingeditor/client/client.lua new file mode 100644 index 000000000..18a4b6416 Binary files /dev/null and b/resources/jraxion_handlingeditor/client/client.lua differ diff --git a/resources/jraxion_handlingeditor/config.lua b/resources/jraxion_handlingeditor/config.lua new file mode 100644 index 000000000..f9363e7f1 --- /dev/null +++ b/resources/jraxion_handlingeditor/config.lua @@ -0,0 +1,424 @@ +Config = {} + +-- Make sure the resource name is jraxion_handlingeditor, it will break the resource if you rename it. + +Config.isAllowed = function(source) --server side. most frameworks already have this. + -- if you know what you are doing, edit server/server.lua to change the permission. + if IsPlayerAceAllowed(source, 'admin') or IsPlayerAceAllowed(source, 'command') then + return true + end + return false +end + +Config.Precision = 10000.0 + +Config.Values = { + -- Physical + fMass = { + name = "Vehicle Mass", + type = "float", + change = 100.0, + min = 100.0, + max = 5000.0, + description = "Vehicle weight in kilograms. Affects collision physics." + }, + fInitialDragCoeff = { + name = "Drag Coefficient", + type = "float", + change = 1, + min = 10.0, + max = 120.0, + description = "Aerodynamic drag coefficient. Higher values increase air resistance." + }, + fPopUpLightRotation = { + name = "Pop-up Light Rotation", + type = "float", + change = 1.0, + min = 0.0, + max = 90.0, + description = "Angle of pop-up headlights when deployed" + }, + fPercentSubmerged = { + name = "Submersion Percentage", + type = "float", + change = 5.0, + min = 0.0, + max = 100.0, + description = "Percentage of vehicle that must be submerged before sinking. Default is 85%." + }, + vecCentreOfMassOffset = { + name = "Centre of Mass Offset", + type = "vector", + change = 0.1, + min = -2.0, + max = 2.0, + description = "Center of mass offset from vehicle origin", + components = { + x = {name = "X Offset", description = "Left/Right offset"}, + y = {name = "Y Offset", description = "Front/Back offset"}, + z = {name = "Z Offset", description = "Up/Down offset"} + } + }, + vecInertiaMultiplier = { + name = "Inertia Multiplier", + type = "vector", + change = 0.1, + min = 0.0, + max = 3.0, + description = "Inertia multiplier for each axis", + components = { + x = {name = "X Multiplier", description = "Roll inertia"}, + y = {name = "Y Multiplier", description = "Pitch inertia"}, + z = {name = "Z Multiplier", description = "Yaw inertia"} + } + }, + fSeatOffsetDistX = { + name = "Seat X Offset", + type = "float", + change = 0.1, + min = -2.0, + max = 2.0, + description = "Driver seat left/right offset" + }, + fSeatOffsetDistY = { + name = "Seat Y Offset", + type = "float", + change = 0.1, + min = -2.0, + max = 2.0, + description = "Driver seat front/back offset" + }, + fSeatOffsetDistZ = { + name = "Seat Z Offset", + type = "float", + change = 0.1, + min = -2.0, + max = 2.0, + description = "Driver seat up/down offset" + }, + + -- Transmission + fDriveBiasFront = { + name = "Drive Type", + type = "float", + change = 0.05, + min = 0.0, + max = 1.0, + description = "Drive type: 0.0 = RWD, 1.0 = FWD, 0.5 = balanced 4WD" + }, + nInitialDriveGears = { + name = "Number of Gears", + type = "int", + change = 1, + min = 1, + max = 8, + description = "Number of forward gears in the transmission" + }, + fInitialDriveForce = { + name = "Engine Power", + type = "float", + change = 0.1, + min = 0.01, + max = 2.0, + description = "Engine power multiplier. Higher values increase acceleration." + }, + fDriveInertia = { + name = "Engine Rev Speed", + type = "float", + change = 1, + min = 0.01, + max = 2.0, + description = "Engine rev speed. Higher values make engine reach redline faster." + }, + fClutchChangeRateScaleUpShift = { + name = "Upshift Speed", + type = "float", + change = 0.5, + min = 0.1, + max = 10.0, + description = "Upshift speed multiplier. Higher values = faster shifts." + }, + fClutchChangeRateScaleDownShift = { + name = "Downshift Speed", + type = "float", + change = 0.5, + min = 0.1, + max = 10.0, + description = "Downshift speed multiplier. Higher values = faster shifts." + }, + fInitialDriveMaxFlatVel = { + name = "Top Speed", + type = "float", + change = 5.0, + min = 0.0, + max = 500.0, + description = "Top speed at redline in highest gear (multiply by 0.82 for MPH)" + }, + + -- Brakes + fBrakeForce = { + name = "Brake Power", + type = "float", + change = 0.2, + min = 0.01, + max = 2.0, + description = "Brake power multiplier. Higher values increase braking force." + }, + fBrakeBiasFront = { + name = "Brake Balance", + type = "float", + change = 0.1, + min = 0.0, + max = 1.0, + description = "Brake distribution: 0.0 = rear only, 1.0 = front only, 0.5 = balanced" + }, + fHandBrakeForce = { + name = "Handbrake Power", + type = "float", + change = 0.2, + min = 0.0, + max = 5.0, + description = "Handbrake power multiplier. Higher values increase handbrake strength." + }, + + -- Steering + fSteeringLock = { + name = "Steering Angle", + type = "float", + change = 5.0, + min = 10.0, + max = 70.0, + description = "Maximum steering angle. Higher values increase turn radius." + }, + + -- Traction + fTractionCurveMax = { + name = "Cornering Grip", + type = "float", + change = 0.1, + min = 0.1, + max = 4.0, + description = "Maximum cornering grip multiplier" + }, + fTractionCurveMin = { + name = "Acceleration Grip", + type = "float", + change = 0.1, + min = 0.1, + max = 4.0, + description = "Minimum acceleration/braking grip multiplier" + }, + fTractionCurveLateral = { + name = "Lateral Grip", + type = "float", + change = 1.0, + min = 0.0, + max = 4.0, + description = "Lateral grip curve shape" + }, + fTractionSpringDeltaMax = { + name = "Traction Height Loss", + type = "float", + change = 0.01, + min = 0.0, + max = 0.3, + description = "Height at which vehicle loses traction" + }, + fLowSpeedTractionLossMult = { + name = "Burnout Effect", + type = "float", + change = 0.1, + min = 0.0, + max = 2.0, + description = "Low speed traction loss. Higher values increase burnout effect." + }, + fCamberStiffnesss = { + name = "Drift Control", + type = "float", + change = 0.01, + min = -1.0, + max = 1.0, + description = "Drift grip modifier. Positive values maintain drift angle, negative values cause oversteer." + }, + fTractionBiasFront = { + name = "Traction Balance", + type = "float", + change = 0.01, + min = 0.01, + max = 0.99, + description = "Traction distribution: 0.01 = rear bias, 0.99 = front bias, 0.5 = balanced" + }, + fTractionLossMult = { + name = "Surface Grip Loss", + type = "float", + change = 1.0, + min = 0.0, + max = 2.0, + description = "Surface traction loss multiplier. Higher values reduce grip on all surfaces." + }, + + -- Suspension + fSuspensionForce = { + name = "Suspension Force", + type = "float", + change = 0.1, + min = 0.0, + max = 5.0, + description = "Suspension spring force" + }, + fSuspensionCompDamp = { + name = "Compression Damping", + type = "float", + change = 0.1, + min = 0.0, + max = 5.0, + description = "Suspension compression damping" + }, + fSuspensionReboundDamp = { + name = "Rebound Damping", + type = "float", + change = 0.1, + min = 0.0, + max = 5.0, + description = "Suspension rebound damping" + }, + fSuspensionUpperLimit = { + name = "Upper Limit", + type = "float", + change = 0.01, + min = 0.0, + max = 0.5, + description = "Suspension upper movement limit" + }, + fSuspensionLowerLimit = { + name = "Lower Limit", + type = "float", + change = 0.01, + min = -0.5, + max = 0.0, + description = "Suspension lower movement limit" + }, + fSuspensionRaise = { + name = "Ride Height", + type = "float", + change = 0.01, + min = -0.2, + max = 0.2, + description = "Vehicle ride height adjustment" + }, + fSuspensionBiasFront = { + name = "Suspension Balance", + type = "float", + change = 0.01, + min = 0.0, + max = 1.0, + description = "Suspension force distribution" + }, + fAntiRollBarForce = { + name = "Anti-Roll Force", + type = "float", + change = 0.1, + min = 0.0, + max = 2.0, + description = "Anti-roll bar force" + }, + fAntiRollBarBiasFront = { + name = "Anti-Roll Balance", + type = "float", + change = 0.01, + min = 0.0, + max = 1.0, + description = "Anti-roll bar force distribution" + }, + fRollCentreHeightFront = { + name = "Front Roll Height", + type = "float", + change = 0.01, + min = 0.0, + max = 1.0, + description = "Front roll center height" + }, + fRollCentreHeightRear = { + name = "Rear Roll Height", + type = "float", + change = 0.01, + min = 0.0, + max = 1.0, + description = "Rear roll center height" + }, + + -- Damage + fCollisionDamageMult = { + name = "Collision Damage", + type = "float", + change = 1.0, + min = 0.0, + max = 2.0, + description = "Collision damage multiplier" + }, + fWeaponDamageMult = { + name = "Weapon Damage", + type = "float", + change = 1.0, + min = 0.0, + max = 2.0, + description = "Weapon damage multiplier" + }, + fDeformationDamageMult = { + name = "Visual Damage", + type = "float", + change = 1.0, + min = 0.0, + max = 2.0, + description = "Visual damage deformation multiplier" + }, + fEngineDamageMult = { + name = "Engine Damage", + type = "float", + change = 1.0, + min = 0.0, + max = 2.0, + description = "Engine damage multiplier" + }, + + -- Misc + fPetrolTankVolume = { + name = "Fuel Capacity", + type = "float", + change = 1.0, + min = 0.0, + max = 100.0, + description = "Fuel tank capacity" + }, + fOilVolume = { + name = "Oil Capacity", + type = "float", + change = 1.0, + min = 0.0, + max = 10.0, + description = "Engine oil capacity" + }, + nMonetaryValue = { + name = "Vehicle Value", + type = "int", + change = 1.0, + min = 0, + max = 1000000, + description = "Vehicle monetary value" + }, + strModelFlags = { + name = "Model Flags", + type = "string", + description = "Vehicle model flags" + }, + strHandlingFlags = { + name = "Handling Flags", + type = "string", + description = "Vehicle handling flags" + }, + strDamageFlags = { + name = "Damage Flags", + type = "string", + description = "Vehicle damage flags" + } +} diff --git a/resources/jraxion_handlingeditor/fxmanifest.lua b/resources/jraxion_handlingeditor/fxmanifest.lua new file mode 100644 index 000000000..1696d677f --- /dev/null +++ b/resources/jraxion_handlingeditor/fxmanifest.lua @@ -0,0 +1,39 @@ +fx_version 'cerulean' +game 'gta5' + +lua54 'yes' + +name 'jraxion_handlingeditor' +version '1.0.0' +description 'Handling Editor' +author 'JRaxion' +url 'https://jraxion.tebex.io/' + +client_scripts { + 'config.lua', + 'client/client.lua' +} + +server_scripts { + 'config.lua', + 'server/server.lua' +} + +ui_page 'html/index.html' + +files { + 'html/index.html', + 'html/main.js', + 'html/*.js', + + 'handling.meta', +} + +data_file 'HANDLING_FILE' 'handling.meta' + +escrow_ignore { + 'config.lua', + 'server/server.lua', +} + +dependency '/assetpacks' \ No newline at end of file diff --git a/resources/jraxion_handlingeditor/handling.meta b/resources/jraxion_handlingeditor/handling.meta new file mode 100644 index 000000000..79c8300e9 --- /dev/null +++ b/resources/jraxion_handlingeditor/handling.meta @@ -0,0 +1,62 @@ + + + + + + MONSTER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2224008 + 20568081 + 20 + + + + + + + + \ No newline at end of file diff --git a/resources/jraxion_handlingeditor/html/index.html b/resources/jraxion_handlingeditor/html/index.html new file mode 100644 index 000000000..463687e5d --- /dev/null +++ b/resources/jraxion_handlingeditor/html/index.html @@ -0,0 +1,242 @@ + + + + + jr-handling + + + + + + + + + + + + + + + + diff --git a/resources/jraxion_handlingeditor/html/main.js b/resources/jraxion_handlingeditor/html/main.js new file mode 100644 index 000000000..7a2e43a78 --- /dev/null +++ b/resources/jraxion_handlingeditor/html/main.js @@ -0,0 +1,319 @@ +const modals = { + openhandlingModal: false, +}; + +async function closeNUI() { + openhandlingModal.hide(); + await fetch(`https://jraxion_handlingeditor/close`, { + method: "POST", + headers: { + "Content-Type": "application/json; charset=UTF-8", + }, + }); +} + +function handleUIMessage(data) { + switch (data.type) { + case "openHandlingEditor": + openEditor(data); + break; + default: + console.log("No action received."); + } +} + +function main() { + window.addEventListener("message", (event) => handleUIMessage(event.data)); + openhandlingModal = new bootstrap.Modal(document.getElementById("open-handling-modal")); + + document.querySelectorAll(".modal").forEach((modal) => { + modal.addEventListener("hidden.bs.modal", closeNUI); + }); +} + +main(); + +document.addEventListener('keydown', function(event) { + if (event.key === "Escape") { + closeNUI(); + } +}); + +function updateValue(input, change, key) { + let currentValue; + let newValue; + + if (input.dataset.type === "string") { + currentValue = input.value; + newValue = currentValue; + } else { + currentValue = parseFloat(input.value) || 0; + newValue = currentValue + change; + + const min = parseFloat(input.dataset.min); + const max = parseFloat(input.dataset.max); + + newValue = Math.max(min, Math.min(max, newValue)); + + if (input.dataset.type === "int") { + newValue = Math.round(newValue); + } else if (input.dataset.type === "float") { + newValue = parseFloat(newValue.toFixed(6)); + } + } + + input.value = newValue; + + const slider = input.parentElement.querySelector('input[type="range"]'); + if (slider) { + slider.value = newValue; + } + + $.post('https://jraxion_handlingeditor/updateHandling', JSON.stringify({ + key: key, + value: newValue + })); +} + +function updateFlag(checkbox, flagType) { + const dropdownMenu = checkbox.closest('.dropdown-menu'); + const allFlags = Array.from(dropdownMenu.querySelectorAll('input[type="checkbox"]')); + + const flagValue = allFlags.reduce((acc, flag, index) => { + return acc + (flag.checked ? Math.pow(2, index) : 0); + }, 0).toString(16).toUpperCase(); + + $.post('https://jraxion_handlingeditor/updateHandling', JSON.stringify({ + key: flagType, + value: flagValue + })); + + const input = dropdownMenu.parentElement.previousElementSibling.querySelector('input[type="text"]'); + input.value = flagValue; + + const selectedCount = allFlags.filter(f => f.checked).length; + const dropdownButton = dropdownMenu.previousElementSibling; + dropdownButton.textContent = `Selected Flags (${selectedCount})`; +} + +function updateFromSlider(slider) { + const input = slider.parentElement.querySelector('input[type="text"]'); + const key = input.dataset.handlingKey; + const newValue = parseFloat(slider.value); + + if (input.dataset.type === "int") { + input.value = Math.round(newValue); + } else { + input.value = parseFloat(newValue.toFixed(6)); + } + + $.post('https://jraxion_handlingeditor/updateHandling', JSON.stringify({ + key: key, + value: input.value + })); +} + +function exportHandling() { + const vehicleName = document.querySelector("#handlingLabel").innerHTML; + const rows = document.getElementById('handling-data-accordion').getElementsByTagName('tr'); + + const xmlHeader = '\n\n'; + const xmlStart = '\n \n \n'; + const xmlEnd = ' \n \n'; + + let xmlContent = ` ${vehicleName}\n`; + + const handlingValues = {}; + + Array.from(rows).forEach(row => { + const input = row.querySelector('input[type="text"]'); + if (!input) return; + + const key = input.dataset.handlingKey; + const value = input.value; + + switch(input.dataset.type) { + case "float": + handlingValues[key] = parseFloat(value).toFixed(6); + break; + case "int": + handlingValues[key] = Math.round(value); + break; + case "string": + handlingValues[key] = value; + break; + } + }); + + handlingValues['vecCentreOfMassOffset'] = '0.000000 0.000000 0.000000'; + handlingValues['vecInertiaMultiplier'] = '1.000000 1.000000 1.000000'; + + const propertyOrder = [ + 'fMass', 'fInitialDragCoeff', 'fPercentSubmerged', + 'vecCentreOfMassOffset', 'vecInertiaMultiplier', 'fDriveBiasFront', + 'nInitialDriveGears', 'fInitialDriveForce', 'fDriveInertia', + 'fClutchChangeRateScaleUpShift', 'fClutchChangeRateScaleDownShift', + 'fInitialDriveMaxFlatVel', 'fBrakeForce', 'fBrakeBiasFront', + 'fHandBrakeForce', 'fSteeringLock', 'fTractionCurveMax', + 'fTractionCurveMin', 'fTractionCurveLateral', 'fTractionSpringDeltaMax', + 'fLowSpeedTractionLossMult', 'fCamberStiffnesss', 'fTractionBiasFront', + 'fTractionLossMult', 'fSuspensionForce', 'fSuspensionCompDamp', + 'fSuspensionReboundDamp', 'fSuspensionUpperLimit', 'fSuspensionLowerLimit', + 'fSuspensionRaise', 'fSuspensionBiasFront', 'fAntiRollBarForce', + 'fAntiRollBarBiasFront', 'fRollCentreHeightFront', 'fRollCentreHeightRear', + 'fCollisionDamageMult', 'fWeaponDamageMult', 'fDeformationDamageMult', + 'fEngineDamageMult', 'fPetrolTankVolume', 'fOilVolume', 'fSeatOffsetDistX', + 'fSeatOffsetDistY', 'fSeatOffsetDistZ', 'nMonetaryValue', 'strModelFlags', + 'strHandlingFlags', 'strDamageFlags', 'AIHandling' + ]; + + propertyOrder.forEach(key => { + if (handlingValues[key] !== undefined) { + if (key.startsWith('vec')) { + const [x, y, z] = handlingValues[key].split(' '); + xmlContent += ` <${key} x="${x}" y="${y}" z="${z}" />\n`; + } else if (key === 'strModelFlags' || key === 'strHandlingFlags' || key === 'strDamageFlags') { + xmlContent += ` <${key}>${parseInt(handlingValues[key]).toString(16).toUpperCase()}\n`; + } else { + xmlContent += ` <${key} value="${handlingValues[key]}" />\n`; + } + } + }); + + const subHandlingData = ` + + + +\n`; + + const finalXml = xmlHeader + xmlStart + xmlContent + subHandlingData + xmlEnd; + const exportArea = document.getElementById('exportArea'); + exportArea.value = finalXml; + exportArea.style.display = 'block'; + exportArea.select(); + document.execCommand('copy'); +} + +const flagDefinitions = { + strModelFlags: [ + "IS_VAN", "IS_BUS", "IS_LOW", "IS_BIG", "ABS_STD", "ABS_OPTION", + "ABS_ALT_STD", "ABS_ALT_OPTION", "NO_DOORS", "TANDEM_SEATS", + "SIT_IN_BOAT", "HAS_TRACKS", "NO_EXHAUST", "DOUBLE_EXHAUST", + "NO1FPS_LOOK_BEHIND", "CAN_ENTER_IF_NO_DOOR", "AXLE_F_TORSION", + "AXLE_F_SOLID", "AXLE_F_MCPHERSON", "ATTACH_PED_TO_BODYSHELL", + "AXLE_R_TORSION", "AXLE_R_SOLID", "AXLE_R_MCPHERSON", + "DONT_FORCE_GRND_CLEARANCE", "DONT_RENDER_STEER", "NO_WHEEL_BURST", + "INDESTRUCTIBLE", "DOUBLE_FRONT_WHEELS", "FORCE_FRONT_WHEEL_WIDTH", + "FORCE_REAR_WHEEL_WIDTH" + ], + strHandlingFlags: [ + "SMOOTH_COMPRESN", "REDUCED_MOD_MASS", "DISTRIB_FRONT_MASS", + "DISTRIB_REAR_MASS", "OFFROAD_ABILITY", "OFFROAD_ABILITY2", + "HALOGEN_LIGHTS", "PROC_REARWHEEL_1ST", "USE_MAXSP_LIMIT", + "LOW_RIDER", "STREET_RACER", "SWINGING_CHASSIS", "EXTREME_GRIP", + "INCREASED_GRAVITY", "VERTICAL_FLIGHT_MODE", "DISABLE_GROUND_CLEARANCE", + "RACING_HANDLING", "EMERGENCY_VEHICLE", "NO_HANDBRAKE", + "CVT", "ALT_EXT_WHEEL_BOUNDS_BEH", "DONT_RAISE_BOUNDS_AT_SPEED", + "NARROW_BOUNDS", "MEDIUM_BOUNDS", "WIDE_BOUNDS" + ], + strDamageFlags: [ + "DRIVER_SIDE_FRONT_DOOR", "DRIVER_SIDE_REAR_DOOR", "DRIVER_PASSENGER_SIDE_FRONT_DOOR", + "DRIVER_PASSENGER_SIDE_REAR_DOOR", "BONNET", "BOOT", "DRIVER_SIDE_FRONT_WINDOW", + "DRIVER_SIDE_REAR_WINDOW", "PASSENGER_SIDE_FRONT_WINDOW", "PASSENGER_SIDE_REAR_WINDOW", + "WINDSCREEN", "ENGINE", "ALL_WHEELS", "PETROL_TANK", "DOORS", "TRUNK", "WINDOWS", + "ALL_PANELS", "WHEELS", "EXHAUST", "RADIATOR", "SUSPENSION", "BRAKES", "GEARBOX", + "STEERING", "HEADLIGHTS", "TAILLIGHTS", "INDICATORS", "WIPERS", "MIRRORS" + ] +}; + +function openEditor(data) { + document.querySelector("#handlingLabel").innerHTML = data.vehicleName; + let accordionContent = ""; + + const sortedEntries = Object.entries(data.data).sort((a, b) => a[0].localeCompare(b[0])); + + for (let [i, dd] of sortedEntries) { + if (i.startsWith('vec')) { + continue; + } + + const isNumeric = dd.type === "float" || dd.type === "int"; + const isFlag = i === "strModelFlags" || i === "strHandlingFlags" || i === "strDamageFlags"; + + accordionContent += ` + + ${dd.name} + + ${isNumeric ? `Min: ${dd.min} | Max: ${dd.max}` : ''} + + ${data.originalvalues[i]} + +
+ ${isNumeric ? `` : ''} + + ${isNumeric ? `` : ''} + ${isNumeric && !isFlag ? ` + + ` : ''} +
+ ${isFlag ? ` + + ` : ''} + + + `; + } + + document.querySelector("#handling-data-accordion").innerHTML = accordionContent; + + openhandlingModal.show(); + const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); + const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)); +} + +function searchVehicles() { + const searchInput = document.getElementById('searchBar').value.toLowerCase(); + const rows = document.getElementById('handling-data-accordion').getElementsByTagName('tr'); + + for (let row of rows) { + const vehicleName = row.getElementsByTagName('td')[0].textContent.toLowerCase(); + if (vehicleName.includes(searchInput)) { + row.style.display = ''; + } else { + row.style.display = 'none'; + } + } +} diff --git a/resources/jraxion_handlingeditor/server/server.lua b/resources/jraxion_handlingeditor/server/server.lua new file mode 100644 index 000000000..b00ad67ea --- /dev/null +++ b/resources/jraxion_handlingeditor/server/server.lua @@ -0,0 +1,15 @@ + +while GetCurrentResourceName() ~= "jraxion_handlingeditor" do + Wait(2000) + print("Make sure the resource name is jraxion_handlingeditor, it will break the resource if you rename it.") +end + +RegisterCommand('handlingeditor', function(source, args, rawCommand) + -- Add your permission system here. + + if Config.isAllowed(source) then + TriggerClientEvent('jraxion_handlingeditor:openHandlingEditor', source) + else + print('No permission') + end +end, false)