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'; } } }