diff --git a/README.md b/README.md
new file mode 100644
index 0000000..57fc726
--- /dev/null
+++ b/README.md
@@ -0,0 +1,103 @@
+# GTA5 handling.meta interface
+# Handling Flags Overhaul π
+
+## Live demo: https://vivvvi.github.io/Handling-Tools/
+
+## Summary
+This branch introduces a **complete overhaul of the Handling Flags system**. It modularizes core logic, enhances the user experience, and introduces **dynamic XML parsing, sub-array support, and raw XML value editing**. The system is now more maintainable, flexible, and ready for future enhancements.
+
+---
+
+## Table of Contents
+1. [What's New](#whats-new)
+ - [New Files & Structure](#new-files--structure)
+ - [Feature Enhancements](#feature-enhancements)
+ - [User Interface](#user-interface)
+2. [Key Changes](#key-changes)
+3. [Bugs / Issues Addressed](#bugs--issues-addressed)
+4. [TODO / Future Enhancements](#todo--future-enhancements)
+5. [How to Test](#how-to-test)
+6. [Notes for Reviewers](#notes-for-reviewers)
+
+---
+
+## [Whats New]
+
+### New Files & Structure
+- π **flags.js** β Core logic for dynamic flag handling, input validation, and state updates.
+- π **handling.js** β Handles XML parsing, raw input updates, and integration with the UI.
+- π **2 JSON Files** β Metadata files for GTAV flags from Plebs and ikt.
+- π **File Separation** β Extracted and JS logic from `flags.html` and `handling.html` into standalone modules.
+
+### [Feature Enhancements]
+- π±οΈ **Flag System Overhaul** β Unified flag handling logic between `handling.html` and `flags.html`.
+- βοΈ **Editable XML UI** β Users can directly edit raw XML values for:
+ - `strModelFlags`
+ - `strHandlingFlags`
+ - `strDamageFlags`
+ - `handlingName`
+ - `AIHandling`
+- π **Dynamic Sub-Array Support** β Support for `vecCentreOfMassOffset` and `vecInertiaMultiplier` for **X, Y, Z** adjustments.
+- π **Mass Comparer Overhaul** β Streamlined for flattened views and dynamic column selection.
+- π **File Upload Enhancements** β Uploaded file names are now stored and referenced for mass composer and comparison logic.
+
+### [User Interface]
+- π₯οΈ **Interactive Flags UI** β Users can toggle, calculate, and edit flags with ease.
+- π₯ **Live XML Parsing** β View and edit key XML nodes and sub-array data.
+- πΌοΈ **Dynamic Tab Support** β Handling and Flags tabs now share the same modular logic.
+- β‘ **Real-time Calculations** β Flag updates are displayed in real-time, with support for **hexadecimal input validation**.
+
+---
+
+## [Key Changes]
+
+| **File** | **Type** | **Description** |
+|-------------------|-------------|---------------------------------------------------------------------------------|
+| **flags.js** | π New | Handles flag toggling, input listeners, and dynamic state updates. |
+| **handling.js** | π New | Parses and updates key XML nodes and inline elements. |
+| **flags.html** | βοΈ Updated | Modularized to support shared logic with `flags.js`. |
+| **handling.html** | βοΈ Updated | Supports dynamic tabs, raw XML editing, and sub-array parsing. |
+| **flags.json** | π New | Metadata from Plebs and ikt for GTAV flag definitions. |
+| **CSS Updates** | π¨ Improved| Added styles for XML editing, flags toggles, and mass comparer. |
+
+---
+
+## [Bugs / Issues Addressed]
+- π« **Input Restrictions** β Hex input validation logic improved. Supports copy-paste while maintaining constraints.
+- π **UI Consistency** β Refactored logic to support tabs in `flags.html` and `handling.html` using shared components.
+- π₯ **Sub-Array Support** β Improved support for nested items like:
+```xml
+
+ -
+
+
+
+```
+
+# [TODO / Future Enhancements]
+- π not all calculations may still be correct trying to remove miles per hour from calculations.
+- π Complete sub-array support β Handle all sub-handling elements and nested items.
+- βοΈ Remove 'miles' from internal logic β Store data in KM/H and only convert to MPH when required.
+- π Mass Comparer Improvements β Full support for flattening and dynamic column selection.
+- π¦ Testing & QA β Full review of XML parsing logic, flag toggling, and mass comparer values.
+
+# [How to Test]
+Pull the branch and checkout the new files:
+```bash
+git checkout feature/handling-flags-overhaul
+```
+Open handling.html and flags.html.
+
+Test the following:
+- Toggling Flags β Ensure flags toggle on and off properly.
+- Input Validation β Type hexadecimal values into editable fields and ensure input is constrained to 8 characters.
+- File Upload β Test XML file uploads and verify that the file name is referenced correctly.
+- Mass Comparer β Test flattened structure, dynamic columns, and file comparisons.
+- Sub-Array Support β Verify vecCentreOfMassOffset and vecInertiaMultiplier support.
+
+# [Notes for Reviewers]
+This PR introduces significant changes to the structure and logic. Please review the changes to:
+- flags.js, handling.js, and the mass comparer logic.
+- Input Validation β Ensure user input validation logic works properly.
+- Sub-Array Handling β Verify XML sub-array parsing is accurate.
+- Flag Calculation β Review flag calculation logic for consistency.
diff --git a/ace_xml.js b/ace_xml.js
new file mode 100644
index 0000000..0f3b7dd
--- /dev/null
+++ b/ace_xml.js
@@ -0,0 +1,240 @@
+// ** Initialize Ace Editor **
+var rawEditor = ace.edit("rawEditor");
+rawEditor.setTheme("ace/theme/monokai");
+rawEditor.session.setMode("ace/mode/xml");
+rawEditor.setShowPrintMargin(false);
+rawEditor.setFontSize(14);
+rawEditor.setOptions({
+ autoScrollEditorIntoView: true,
+ copyWithEmptySelection: true
+});
+
+// Load XML from the hidden textarea or use default content
+const textarea = document.getElementById('handlingFileDisplay');
+const textareaContent = textarea ? textarea.value.trim() : '';
+const defaultContent = textareaContent || `
+
+
+
+
+
+ 4400110
+ `;
+
+rawEditor.setValue(defaultContent, -1);
+if (textarea) textarea.value = defaultContent;
+
+// ** Regexes to match editable regions **
+const valueAttributeRegex = /\bvalue="([a-zA-Z0-9.-]*)"/g;
+const tagContentRegex = /<([a-zA-Z0-9]+)>([a-zA-Z0-9.-]*)<\/\1>/g;
+const generalAttributeRegex = /\b([a-zA-Z0-9_-]+)="([a-zA-Z0-9.-]*)"/g; // Match any attribute and its value
+
+// ** Track the last known value of the editable area **
+let lastEditableValue = null;
+let lastTag = null;
+let lastAttr = null;
+
+
+
+// ** Track Caret Position & Log Editable Status **
+const debouncedLogCaretInfo = debounce(logCaretInfo, 150);
+rawEditor.on('changeSelection', debouncedLogCaretInfo);
+rawEditor.on('click', debouncedLogCaretInfo);
+
+let isSelecting = false;
+
+rawEditor.container.addEventListener('mousedown', () => isSelecting = true);
+rawEditor.container.addEventListener('mouseup', () => {
+ setTimeout(() => {
+ isSelecting = false;
+ const selectionRange = rawEditor.getSelectionRange();
+ if (!selectionRange.isEmpty()) {
+ console.log('Manual selection detected, skipping programmatic selection.');
+ }
+ }, 50);
+});
+
+
+
+// ** Logs caret information, whether user is in an editable region or not **
+function logCaretInfo() {
+ if (isSelecting) return; // π₯ Do not interfere with user manual selection
+
+ const selectionRange = rawEditor.getSelectionRange();
+ if (!selectionRange.isEmpty()) {
+ // console.log('Manual selection active, skipping programmatic selection.');
+ return; // π« Stop here if a selection is ongoing
+ }
+
+
+ const cursorPosition = rawEditor.getCursorPosition();
+ const docText = rawEditor.getValue();
+ const index = rawEditor.session.doc.positionToIndex(cursorPosition);
+ const charAtCursor = docText[index] || 'EOF';
+ const { isEditable, tagName, attrName, oldValue } = getEditableInfo(index);
+
+ if (isEditable) {
+ const attrDisplay = attrName ? `[${attrName}]` : '';
+ // console.log(`β
Editable position at index: ${index} (character: "${charAtCursor}")`);
+ // console.log(`π’ Editing <${tagName}> ${attrDisplay} Current value: "${oldValue}"`);
+
+ // Store the value to detect changes later
+ lastEditableValue = oldValue;
+ lastTag = tagName;
+ lastAttr = attrName;
+ } else {
+ //console.warn(`π« Not editable at index: ${index} (character: "${charAtCursor}")`);
+ //console.log( getNearestTag(docText, index) );
+ var valAtts = getValuePosition( docText, index );
+
+ if (valAtts){
+
+ // Get row and column corresponding to this character index
+ const startPos = rawEditor.session.doc.indexToPosition(valAtts.start, 0);
+ const endPos = rawEditor.session.doc.indexToPosition(valAtts.end, 0);
+
+ // ** Create the selection range using Ace's Range class **
+ const aceRange = ace.require('ace/range').Range;
+ const range = new aceRange(endPos.row, endPos.column, startPos.row, startPos.column);
+ rawEditor.selection.setSelectionRange(range);
+
+ }
+ // Move the cursor to the calculated (startPos) row and column
+ //rawEditor.moveCursorTo(startPos.row, startPos.column);
+
+ }
+}
+
+// ** Check if a given index position is within an editable range **
+function getEditableInfo(index) {
+ const documentText = rawEditor.getValue();
+ let isEditable = false, tagName = '', attrName = '', oldValue = '';
+
+ // Check if position is inside `value="..."` attributes
+ valueAttributeRegex.lastIndex = 0;
+ while ((match = valueAttributeRegex.exec(documentText)) !== null) {
+ const valueStart = match.index + match[0].indexOf('"') + 1;
+ const valueEnd = valueStart + match[1].length;
+ if (index >= valueStart && index <= valueEnd) {
+ isEditable = true;
+ attrName = 'value';
+ oldValue = match[1];
+ tagName = getNearestTag(documentText, match.index);
+ break;
+ }
+ }
+
+ // Check if position is inside `value ` text content
+ tagContentRegex.lastIndex = 0;
+ while ((match = tagContentRegex.exec(documentText)) !== null) {
+ const valueStart = match.index + match[0].indexOf('>') + 1;
+ const valueEnd = valueStart + match[2].length;
+ if (index >= valueStart && index <= valueEnd) {
+ isEditable = true;
+ attrName = ''; // No attribute for root value
+ oldValue = match[2];
+ tagName = match[1];
+ break;
+ }
+ }
+
+ // Check if position is inside any attribute value
+ generalAttributeRegex.lastIndex = 0;
+ while ((match = generalAttributeRegex.exec(documentText)) !== null) {
+ const valueStart = match.index + match[0].indexOf('"') + 1;
+ const valueEnd = valueStart + match[2].length;
+ if (index >= valueStart && index <= valueEnd) {
+ isEditable = true;
+ attrName = match[1];
+ oldValue = match[2];
+ tagName = getNearestTag(documentText, match.index);
+ break;
+ }
+ }
+
+ return { isEditable, tagName, attrName, oldValue };
+}
+
+// ** Get nearest tag from a position in the document **
+function getNearestTag(documentText, position) {
+ const beforeCursor = documentText.slice(0, position);
+ const tagMatch = beforeCursor.match(/<([a-zA-Z0-9]+)(?=\s|>)/g);
+ if (tagMatch && tagMatch.length > 0) {
+ // console.log( tagMatch[tagMatch.length - 1].replace('<', '') );
+ return tagMatch[tagMatch.length - 1].replace('<', '');
+ }
+ return 'UnknownTag';
+}
+
+function getValuePosition(documentText, position) {
+ const lineStart = documentText.lastIndexOf('\n', position) + 1; // Start of the current line
+ const lineEnd = documentText.indexOf('\n', position);
+ const lineText = documentText.slice(lineStart, lineEnd > -1 ? lineEnd : documentText.length);
+
+ // ** 1. Check for values in attributes like value="1500.000000" **
+ const attributeMatch = lineText.match(/\b([a-zA-Z0-9-]+)="([a-zA-Z0-9.-]*)"/);
+ if (attributeMatch) {
+ const matchStart = lineText.indexOf(attributeMatch[0]) + lineStart;
+ const valueStart = matchStart + attributeMatch[0].indexOf('"') + 1; // Start after the first quote
+ const valueEnd = valueStart + attributeMatch[2].length;
+ return { start: valueStart, end: valueEnd, value: attributeMatch[2], attrName: attributeMatch[1], type: 'attribute', lineText };
+ }
+
+ // ** 2. Check for tag content like 20000 **
+ const tagMatch = lineText.match(/<([a-zA-Z0-9]+)>([a-zA-Z0-9.-]+)<\/\1>/);
+ if (tagMatch) {
+ const matchStart = lineText.indexOf(tagMatch[0]) + lineStart;
+ const valueStart = matchStart + tagMatch[0].indexOf('>') + 1; // Start after the ">" character
+ const valueEnd = valueStart + tagMatch[2].length;
+ return { start: valueStart, end: valueEnd, value: tagMatch[2], tagName: tagMatch[1], type: 'tag', lineText };
+ }
+
+ // ** 3. Check for general attributes, like x="0.000000" y="0.000000" z="0.000000" **
+ const generalAttrMatch = lineText.match(/\b([a-zA-Z0-9-]+)="([a-zA-Z0-9.-]*)"/g);
+ if (generalAttrMatch) {
+ for (let i = 0; i < generalAttrMatch.length; i++) {
+ const match = generalAttrMatch[i].match(/([a-zA-Z0-9-]+)="([a-zA-Z0-9.-]*)"/);
+ const matchStart = lineText.indexOf(match[0], i > 0 ? lineText.indexOf(generalAttrMatch[i - 1]) + generalAttrMatch[i - 1].length : 0) + lineStart;
+ const valueStart = matchStart + match[0].indexOf('"') + 1; // Start after the first quote
+ const valueEnd = valueStart + match[2].length;
+ if (position >= valueStart && position <= valueEnd) {
+ return { start: valueStart, end: valueEnd, value: match[2], attrName: match[1], type: 'attribute', lineText };
+ }
+ }
+ }
+
+ return null; // No match found
+}
+
+
+
+// ** Detect changes and log if a value changes **
+rawEditor.on('change', function() {
+ const { tagName, attrName, newValue } = getCurrentEditableValue();
+
+ if (lastEditableValue !== null && lastEditableValue !== newValue) {
+ const attrDisplay = attrName ? `[${attrName}]` : '';
+ console.log(`π Value changed for <${tagName}> ${attrDisplay} from "${lastEditableValue}" to "${newValue}"`);
+ // console.log( $(`[name="${tagName}${attrDisplay}"].form-control`) );
+ }
+
+ // Update last known value
+ lastEditableValue = newValue;
+});
+
+function getCurrentEditableValue() {
+ const cursorPosition = rawEditor.getCursorPosition();
+ const docText = rawEditor.getValue();
+ const index = rawEditor.session.doc.positionToIndex(cursorPosition);
+ const { isEditable, tagName, attrName, oldValue } = getEditableInfo(index);
+
+ return { tagName, attrName, newValue: oldValue };
+}
+
+function debounce(func, delay) {
+ let timeout;
+ return function(...args) {
+ clearTimeout(timeout);
+ timeout = setTimeout(() => func.apply(this, args), delay);
+ };
+}
diff --git a/flags-ikt.json b/flags-ikt.json
new file mode 100644
index 0000000..d4599e9
--- /dev/null
+++ b/flags-ikt.json
@@ -0,0 +1,521 @@
+{
+ "version":"2.6",
+ "flags": {
+ "strModelFlags": [{
+ "name": "MF_IS_VAN",
+ "description": "Allows double doors for the rear doors animation."
+ },
+ {
+ "name": "MF_IS_BUS",
+ "description": "Uses bus animations for entry/exit."
+ },
+ {
+ "name": "MF_IS_LOW",
+ "description": "Uses animations suitable for cars with a low ride-height."
+ },
+ {
+ "name": "MF_IS_BIG",
+ "description": "Changes the way that the AI drives around corners."
+ },
+ {
+ "name": "MF_ABS_STD",
+ "description": "Arcade Anti-Lock Braking System (ABS) equipped as standard; minimal slip allowed."
+ },
+ {
+ "name": "MF_ABS_OPTION",
+ "description": "Arcade Anti-Lock Braking System (ABS) equipped w/ brakes upgrade."
+ },
+ {
+ "name": "MF_ABS_ALT_STD",
+ "description": "Realistic Anti-Lock Braking System (ABS) equipped as standard; some slip allowed."
+ },
+ {
+ "name": "MF_ABS_ALT_OPTION",
+ "description": "Realistic Anti-Lock Braking System (ABS) equipped w/brakes upgrade."
+ },
+ {
+ "name": "MF_NO_DOORS",
+ "description": "For vehicles that don't have any operable doors."
+ },
+ {
+ "name": "MF_TANDEM_SEATING",
+ "description": "Two people will use the front passenger seat."
+ },
+ {
+ "name": "MF_SIT_IN_BOAT",
+ "description": "Uses seated boat animation instead of standing."
+ },
+ {
+ "name": "MF_HAS_TRACKS",
+ "description": "For vehicles with tracks instead of tires."
+ },
+ {
+ "name": "MF_NO_EXHAUST",
+ "description": "Removes all exhaust particles."
+ },
+ {
+ "name": "MF_DOUBLE_EXHAUST",
+ "description": "Creates a second exhaust by mirroring the model's exhaust over the y-axis."
+ },
+ {
+ "name": "MF_NO_1STPERSON_LOOKBEHIND",
+ "description": "Prevents player using rear view when in first-person mode."
+ },
+ {
+ "name": "MF_CAN_ENTER_IF_NO_DOOR",
+ "description": "Allows entry into the vehicle despite no currently accessible doors."
+ },
+ {
+ "name": "MF_AXLE_F_TORSION",
+ "description": "Front wheels stay vertical to the car."
+ },
+ {
+ "name": "MF_AXLE_F_SOLID",
+ "description": "Front wheels stay parallel to each other."
+ },
+ {
+ "name": "MF_AXLE_F_MCPHERSON",
+ "description": "Front wheels can tilt."
+ },
+ {
+ "name": "MF_ATTACH_PED_TO_BODYSHELL",
+ "description": "???"
+ },
+ {
+ "name": "MF_AXLE_R_TORSION",
+ "description": "Rear wheels stay vertical to the car."
+ },
+ {
+ "name": "MF_AXLE_R_SOLID",
+ "description": "Rear wheels stay parallel to each other."
+ },
+ {
+ "name": "MF_AXLE_R_MCPHERSON",
+ "description": "Rear wheels can tilt."
+ },
+ {
+ "name": "MF_DONT_FORCE_GRND_CLEARANCE",
+ "description": "Chassis COL is taken into account when suspension is compressed while hitting the ground, with sparks rendered."
+ },
+ {
+ "name": "MF_DONT_RENDER_STEER",
+ "description": "Does not render steering animations."
+ },
+ {
+ "name": "MF_NO_WHEEL_BURST",
+ "description": "Has Bulletproof Tires as standard."
+ },
+ {
+ "name": "MF_INDESTRUCTIBLE",
+ "description": "Can't explode or be considered inoperable from damage."
+ },
+ {
+ "name": "MF_DOUBLE_FRONT_WHEELS",
+ "description": "Places a second instance of each front wheel next to the normal one."
+ },
+ {
+ "name": "MF_IS_RC",
+ "description": "For RC vehicles such as the RC Bandito and Invade & Persuade Tank. The player model is hidden upon entering the vehicle."
+ },
+ {
+ "name": "MF_DOUBLE_REAR_WHEELS",
+ "description": "Duplicates the skidmarks of the rear tires."
+ },
+ {
+ "name": "MF_NO_WHEEL_BREAK",
+ "description": "Prevents wheel bones from detaching off the vehicle due to damage."
+ },
+ {
+ "name": "MF_IS_HATCHBACK",
+ "description": "Uses animations suitable for Trunk doors on hatchback-style vehicle bodies."
+ }
+ ],
+ "strHandlingFlags": [{
+ "name": "HF_SMOOTHED_COMPRESSION",
+ "description": "Simulates progressive spring suspension. Makes suspension compression motion smoother."
+ },
+ {
+ "name": "HF_REDUCED_MOD_MASS",
+ "description": "Reduces mass added from upgrades."
+ },
+ {
+ "name": "HF_HAS_KERS",
+ "description": "Partially enables KERS on the vehicle; disables horn and shows the recharge bar below the minimap. KERS boost itself still needs to be enabled by the SET_VEHICLE_KERS_ALLOWED native."
+ },
+ {
+ "name": "HF_HAS_TRACKS",
+ "description": "Inverts the way grip works on the vehicle; with this flag enabled, grip starts at the fTractionCurveMin value and may increase up to the fTractionCurveMax value upon wheel slip. Grip stays at max beyond the vehicle's peak slip angle."
+ },
+ {
+ "name": "HF_NO_HANDBRAKE",
+ "description": "Disables handbrake control for the vehicle."
+ },
+ {
+ "name": "HF_STEER_REARWHEELS",
+ "description": "Steers the rear wheels instead of the front."
+ },
+ {
+ "name": "HF_HANDBRAKE_REARWHEELSTEER",
+ "description": "Handbrake control makes the rear wheels steer as well as the front."
+ },
+ {
+ "name": "HF_STEER_ALL_WHEELS",
+ "description": "Steers all wheels, similar to 4-wheel-steering systems found on real vehicles. The rear wheels will steer at the same lock angle as the front, as defined by fSteeringLock."
+ },
+ {
+ "name": "HF_FREEWHEEL_NO_GAS",
+ "description": "Disables engine-braking when no throttle is applied."
+ },
+ {
+ "name": "HF_NO_REVERSE",
+ "description": "Disables reversing for the vehicle."
+ },
+ {
+ "name": "_HF_UNKNOWN_10",
+ "description": "Unknown. Name hash: 0x4C11C7F9"
+ },
+ {
+ "name": "HF_STEER_NO_WHEELS",
+ "description": "Disables steering on all wheels, used with tracked vehicles."
+ },
+ {
+ "name": "HF_CVT",
+ "description": "Gives the vehicle a fixed-ratio transmission with a gear ratio of 0.90, used for vehicles with nInitialDriveGears=1. If gears amount to more than 1, it will simply force the vehicle into top gear upon acceleration. Recommended for electric vehicles."
+ },
+ {
+ "name": "HF_ALT_EXT_WHEEL_BOUNDS_BEH",
+ "description": "???"
+ },
+ {
+ "name": "HF_DONT_RAISE_BOUNDS_AT_SPEED",
+ "description": "???"
+ },
+ {
+ "name": "HF_EXT_WHEEL_BOUNDS_COL",
+ "description": "???"
+ },
+ {
+ "name": "HF_LESS_SNOW_SINK",
+ "description": "Less grip loss from deep mud/snow, most notably in North Yankton."
+ },
+ {
+ "name": "HF_TYRES_CAN_CLIP",
+ "description": "Tires are allowed to clip into the pavement when under enough pressure, effectiveness depends on tire sidewall. Generally makes the vehicle deal with uneven terrain better. Notes: this is the reason Offroad Tires improve performance on specific vehicles made by R*."
+ },
+ {
+ "name": "_HF_UNKNOWN_19",
+ "description": "Unknown. name hash: 0x2DEA7A05"
+ },
+ {
+ "name": "HF_HEAVY_VEHICLE",
+ "description": "???"
+ },
+ {
+ "name": "HF_OFFROAD_ABILITIES",
+ "description": "Gravity constant increased by 10% to 10.78 m/s^2, resulting in increased grip and faster falling when airborne. Acceleration and braking performance is also increased by 10%."
+ },
+ {
+ "name": "HF_OFFROAD_ABILITIES_X2",
+ "description": "Gravity constant increased by 20% to 11.76 m/s^2, resulting in increased grip and faster falling when airborne. Acceleration and braking performance is also increased by 20%. Vehicle does not react to bushes."
+ },
+ {
+ "name": "HF_TYRES_RAISE_SIDE_IMPACT_THRESHOLD",
+ "description": "Includes the tires in the general side collision hitbox of the vehicle. Recommended for vehicles whose wheels extend beyond the bodywork, like monster-trucks."
+ },
+ {
+ "name": "_HF_INCREASED_GRAVITY",
+ "description": "Gravity constant increased by 20% to 11.76 m/s^2, resulting in increased grip and faster falling when airborne. Acceleration and braking performance is also increased by 20%. Vehicle does not react to bushes. Identical to HF_OFFROAD_ABILITIES_X2."
+ },
+ {
+ "name": "HF_ENABLE_LEAN",
+ "description": "??? Notes: Possibly for motorcycle leaning or boat leaning."
+ },
+ {
+ "name": "_HF_ALLOW_MOTORCYCLE_TRACTION_LOSS",
+ "description": "Allows motorcycles to lose traction."
+ },
+ {
+ "name": "HF_HEAVYARMOUR",
+ "description": "???"
+ },
+ {
+ "name": "HF_ARMOURED",
+ "description": "Prevents vehicle doors (including hood and trunk) from opening in collisions."
+ },
+ {
+ "name": "HF_SELF_RIGHTING_IN_WATER",
+ "description": "???"
+ },
+ {
+ "name": "HF_IMPROVED_RIGHTING_FORCE",
+ "description": "Adds extra force to the vehicle when attempting to flip it back on its wheels."
+ },
+ {
+ "name": "HF_USE_EXTRA_SOFT_SURFACE_SUS",
+ "description": "???"
+ },
+ {
+ "name": "HF_LAST_AVAILABLE_FLAG",
+ "description": "Most likely doesn't do anything."
+ }
+ ],
+ "strDamageFlags": [{
+ "name": "DF_DRIVER_SIDE_FRONT_DOOR",
+ "description": "Marks the driver-side front door (door_dside_f) bone as non-breakable."
+ },
+ {
+ "name": "DF_DRIVER_SIDE_REAR_DOOR",
+ "description": "Marks the driver-side rear door (door_dside_r) bone as non-breakable."
+ },
+ {
+ "name": "DF_DRIVER_PASSENGER_SIDE_FRONT_DOOR",
+ "description": "Marks the passenger-side front door (door_pside_f) bone as non-breakable."
+ },
+ {
+ "name": "DF_DRIVER_PASSENGER_SIDE_REAR_DOOR",
+ "description": "Marks the passenger-side rear door (door_pside_r) bone as non-breakable."
+ },
+ {
+ "name": "DF_BONNET",
+ "description": "Marks the bonnet bone as non-breakable."
+ },
+ {
+ "name": "DF_BOOT",
+ "description": "Marks the boot bone as non-breakable."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ }
+ ],
+ "strAdvancedFlags": [{
+ "name": "CF_DIFF_FRONT",
+ "description": "Unknown. Note from Rockstar: 'If we want torsen diffs they will need to know resistance at the wheel as applying the brakes should apply force back to the wheel on the ground'."
+ },
+ {
+ "name": "CF_DIFF_REAR",
+ "description": "Unknown. Note from Rockstar: 'If we want torsen diffs they will need to know resistance at the wheel as applying the brakes should apply force back to the wheel on the ground'"
+ },
+ {
+ "name": "CF_DIFF_CENTRE",
+ "description": "When enabled, transfers the drive force from the slipping wheels to the less-driven wheels."
+ },
+ {
+ "name": "CF_DIFF_LIMITED_FRONT",
+ "description": "Unknown. Seems to have a similar effect to _AF_SMOOTH_REV_1ST, but with later upshifts."
+ },
+ {
+ "name": "CF_DIFF_LIMITED_REAR",
+ "description": "???"
+ },
+ {
+ "name": "CF_DIFF_LIMITED_CENTRE",
+ "description": "???"
+ },
+ {
+ "name": "CF_DIFF_LOCKING_FRONT",
+ "description": "Unknown. Using this flag causes the vehicle's front wheels to wheelspin if the player is holding down the handbrake and forwards/backwards keys."
+ },
+ {
+ "name": "CF_DIFF_LOCKING_REAR",
+ "description": "Using the handbrake slows the car down more smoothly and, most of the time, without leaving tire marks."
+ },
+ {
+ "name": "CF_DIFF_LOCKING_CENTRE",
+ "description": "???"
+ },
+ {
+ "name": "CF_GEARBOX_FULL_AUTO",
+ "description": "???"
+ },
+ {
+ "name": "CF_GEARBOX_MANUAL",
+ "description": "Unknown. Sets the clutch value to 0.0 when idling."
+ },
+ {
+ "name": "CF_GEARBOX_DIRECT_SHIFT",
+ "description": "???"
+ },
+ {
+ "name": "CF_GEARBOX_ELECTRIC",
+ "description": "Used by the Omnis e-GT."
+ },
+ {
+ "name": "CF_ASSIST_TRACTION_CONTROL",
+ "description": "???"
+ },
+ {
+ "name": "CF_ASSIST_STABILITY_CONTROL",
+ "description": "???"
+ },
+ {
+ "name": "CF_ALLOW_REDUCED_SUSPENSION_FORCE",
+ "description": "Allows the vehicle to be stanced using the SET_REDUCED_SUSPENSION_FORCE native. Requires the CF_FIX_OLD_BUGS flag to be enabled."
+ },
+ {
+ "name": "CF_HARD_REV_LIMIT",
+ "description": "Only working for Tuner cars, this uncaps the RPM in last gear which causes the power to drop off and lower the top speed of the car (effectively creating a speed cap in last gear). Previously this flag changed gear shift behaviour to cause earlier upshifts with a hard rev limit per gear, but this was changed with the Criminal Enterprises update."
+ },
+ {
+ "name": "CF_HOLD_GEAR_WITH_WHEELSPIN",
+ "description": "Later upshifts; usually hits the gear's rev limit before shifting."
+ },
+ {
+ "name": "CF_INCREASE_SUSPENSION_FORCE_WITH_SPEED",
+ "description": "Anti-downforce suspension; increases suspension spring force as vehicle goes faster."
+ },
+ {
+ "name": "CF_BLOCK_INCREASED_ROT_VELOCITY_WITH_DRIVE_FORCE",
+ "description": "Generates fake wheelspin after an instance of real wheelspin; Tyres will stabilize and show 0.0m/s of slip with debugging data, but the traction behaves like it's still spinning."
+ },
+ {
+ "name": "CF_REDUCED_SELF_RIGHTING_SPEED",
+ "description": "Reduces righting force of the vehicle, effectively making it much harder and slower to flip back on its wheels."
+ },
+ {
+ "name": "CF_CLOSE_RATIO_GEARBOX",
+ "description": "Extends the duration of the first gear, giving the vehicle a slower launch with greatly reduced wheelspin."
+ },
+ {
+ "name": "CF_FORCE_SMOOTH_RPM",
+ "description": "Smooth first-gear revving; resistance to hitting the rev-limit."
+ },
+ {
+ "name": "CF_ALLOW_TURN_ON_SPOT",
+ "description": "Allows the vehicle to be rotated left or right while parked on the spot. Intended for tanks/tracked vehicles."
+ },
+ {
+ "name": "CF_CAN_WHEELIE",
+ "description": "Allows the vehicle to perform a handbrake wheelie. The Muscle vehicle class is hardcoded to use this flag."
+ },
+ {
+ "name": "CF_ENABLE_WHEEL_BLOCKER_SIDE_IMPACTS",
+ "description": "Makes the wheels much less likely to clip into the ground when the vehicle is tipped over."
+ },
+ {
+ "name": "CF_FIX_OLD_BUGS",
+ "description": "Forced stock-tyre clipping boundaries, the sidewall gain/loss from a custom tyre will not matter. Refer to strHandlingFlags 00020000 above. Also prevents lowering the vehicle by shooting at its wheels/suspension. This flag is required in addition to CF_ALLOW_REDUCED_SUSPENSION_FORCE to stance the vehicle through script."
+ },
+ {
+ "name": "CF_USE_DOWNFORCE_BIAS",
+ "description": "Changes the way Downforce and spoiler tuning works, uses the setup found on Open-Wheel class vehicles in the vanilla game. Each spoiler/bumper tuning has to be given AdvancedData values to affect downforce. Adjusts initial downforce from fDownforceModifier. 'Curb - boosting' seems to be nullified."
+ },
+ {
+ "name": "CF_REDUCE_BODY_ROLL_WITH_SUSPENSION_MODS",
+ "description": "Reduces body-roll if suspension upgrades are equipped. In addition, the vehicle gains more grip with each suspension option."
+ },
+ {
+ "name": "CF_ALLOWS_EXTENDED_MODS",
+ "description": "Requires AdvancedData to work. Adds Turbo-affecting mods for mod slot 20 (VMT_KNOB) parts, and power-affecting mods for mod slot 22 (VMT_ICE) parts."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ },
+ {
+ "name": "N/A",
+ "description": "Flag does not exist."
+ }
+ ]
+ }
+}
diff --git a/flags-pleb.json b/flags-pleb.json
new file mode 100644
index 0000000..2844ff3
--- /dev/null
+++ b/flags-pleb.json
@@ -0,0 +1,1129 @@
+[
+ {
+ "index": 0,
+ "name": "CF_DIFF_FRONT",
+ "description": "Unknown. Note from Rockstar: 'If we want torsen diffs they will need to know resistance at the wheel as applying the brakes should apply force back to the wheel on the ground'.",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags00"
+ },
+ {
+ "index": 1,
+ "name": "CF_DIFF_REAR",
+ "description": "Unknown. Note from Rockstar: 'If we want torsen diffs they will need to know resistance at the wheel as applying the brakes should apply force back to the wheel on the ground'",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags01"
+ },
+ {
+ "index": 2,
+ "name": "CF_DIFF_CENTRE",
+ "description": "When enabled, transfers the drive force from the slipping wheels to the less-driven wheels. Previously known as _AF_ENABLE_DRIVE_BIAS_TRANSFER",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags02"
+ },
+ {
+ "index": 3,
+ "name": "CF_DIFF_LIMITED_FRONT",
+ "description": "If we want torsen diffs they will need to know resistance at the wheel as applying the brakes should apply force back to the wheel on the ground. Previous comment; Seems to have a similar effect to _AF_SMOOTH_REV_1ST, but with later upshifts.",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags03"
+ },
+ {
+ "index": 4,
+ "name": "CF_DIFF_LIMITED_REAR",
+ "description": "???",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags04"
+ },
+ {
+ "index": 5,
+ "name": "CF_DIFF_LIMITED_CENTRE",
+ "description": "???",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags05"
+ },
+ {
+ "index": 6,
+ "name": "CF_DIFF_LOCKING_FRONT",
+ "description": "Using this flag causes the vehicle's front wheels to wheelspin if the player is holding down the handbrake and forwards/backwards keys.",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags06"
+ },
+ {
+ "index": 7,
+ "name": "CF_DIFF_LOCKING_REAR",
+ "description": "Using the handbrake slows the car down more smoothly and, most of the time, without leaving tire marks. Previously known as _AF_SMOOTH_HANDBRAKE",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags07"
+ },
+ {
+ "index": 8,
+ "name": "CF_DIFF_LOCKING_CENTRE",
+ "description": "???",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags08"
+ },
+ {
+ "index": 9,
+ "name": "CF_GEARBOX_FULL_AUTO",
+ "description": "???",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags09"
+ },
+ {
+ "index": 10,
+ "name": "CF_GEARBOX_MANUAL",
+ "description": "Sets the clutch value to 0.0 when idling.",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags10"
+ },
+ {
+ "index": 11,
+ "name": "CF_GEARBOX_DIRECT_SHIFT",
+ "description": "???",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags11"
+ },
+ {
+ "index": 12,
+ "name": "CF_GEARBOX_ELECTRIC",
+ "description": "Used by the Omnis e-GT.",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags12"
+ },
+ {
+ "index": 13,
+ "name": "CF_ASSIST_TRACTION_CONTROL",
+ "description": "Just reduce throttle.",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags13"
+ },
+ {
+ "index": 14,
+ "name": "CF_ASSIST_STABILITY_CONTROL",
+ "description": "Apply brakes to individual wheels.",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags14"
+ },
+ {
+ "index": 15,
+ "name": "CF_ALLOW_REDUCED_SUSPENSION_FORCE",
+ "description": "Reduce suspension force can be used for 'stancing' cars. Previous comment; Allows the vehicle to be stanced using the SET_REDUCED_SUSPENSION_FORCE native. Requires the CF_FIX_OLD_BUGS flag to be enabled. Previously known as _AF_CAN_BE_STANCED",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags15"
+ },
+ {
+ "index": 16,
+ "name": "CF_HARD_REV_LIMIT",
+ "description": "Only working for Tuner cars, this uncaps the RPM in last gear which causes the power to drop off and lower the top speed of the car (effectively creating a speed cap in last gear). Previously this flag changed gear shift behaviour to cause earlier upshifts with a hard rev limit per gear, but this was changed with the Criminal Enterprises update. Previously known as _AF_TRACTION_CONTROL",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags16"
+ },
+ {
+ "index": 17,
+ "name": "CF_HOLD_GEAR_WITH_WHEELSPIN",
+ "description": "Later upshifts; usually hits the gear's rev limit before shifting. Previously known as _AF_HOLD_GEAR_LONGER",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags17"
+ },
+ {
+ "index": 18,
+ "name": "CF_INCREASE_SUSPENSION_FORCE_WITH_SPEED",
+ "description": "Anti-downforce suspension; increases suspension spring force as vehicle goes faster. Previously known as _AF_STIFFER_SPRING_SPEED",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags18"
+ },
+ {
+ "index": 19,
+ "name": "CF_BLOCK_INCREASED_ROT_VELOCITY_WITH_DRIVE_FORCE",
+ "description": "Generates fake wheelspin after an instance of real wheelspin; Tyres will stabilize and show 0.0m/s of slip with debugging data, but the traction behaves like it's still spinning. Previously known as _AF_FAKE_WHEELSPIN",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags19"
+ },
+ {
+ "index": 20,
+ "name": "CF_REDUCED_SELF_RIGHTING_SPEED",
+ "description": "Reduces righting force of the vehicle, effectively making it much harder and slower to flip back on its wheels. Previously known as _AF_REDUCED_RIGHTING_FORCE",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags20"
+ },
+ {
+ "index": 21,
+ "name": "CF_CLOSE_RATIO_GEARBOX",
+ "description": "Extends the duration of the first gear, giving the vehicle a slower launch with greatly reduced wheelspin. Previously known as _AF_LONGER_1ST",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags21"
+ },
+ {
+ "index": 22,
+ "name": "CF_FORCE_SMOOTH_RPM",
+ "description": "Smooth first-gear revving; resistance to hitting the rev-limit. Previously known as _AF_SMOOTH_REV_1ST",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags22"
+ },
+ {
+ "index": 23,
+ "name": "CF_ALLOW_TURN_ON_SPOT",
+ "description": "Allows the vehicle to be rotated left or right while parked on the spot. Intended for tanks/tracked vehicles. Previously known as _AF_NEUTRAL_STEER",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags23"
+ },
+ {
+ "index": 24,
+ "name": "CF_CAN_WHEELIE",
+ "description": "Allows the vehicle to perform a handbrake wheelie. The Muscle vehicle class is hardcoded to use this flag. Previously known as _AF_HANDBRAKE_WHEELIE",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags24"
+ },
+ {
+ "index": 25,
+ "name": "CF_ENABLE_WHEEL_BLOCKER_SIDE_IMPACTS",
+ "description": "Makes the wheels much less likely to clip into the ground when the vehicle is tipped over. Previously known as _AF_REDUCE_EXT_WHEEL_COL_CLIPPING",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags25"
+ },
+ {
+ "index": 26,
+ "name": "CF_FIX_OLD_BUGS",
+ "description": "Forced stock-tyre clipping boundaries, the sidewall gain/loss from a custom tyre will not matter. Refer to strHandlingFlags 00020000 above. Also prevents lowering the vehicle by shooting at its wheels/suspension. This flag is required in addition to 'CF_ALLOW_REDUCED_SUSPENSION_FORCE' to stance the vehicle through script. Previously known as _AF_IGNORE_TUNED_WHEELS_CLIP",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags26"
+ },
+ {
+ "index": 27,
+ "name": "CF_USE_DOWNFORCE_BIAS",
+ "description": "Changes the way Downforce and spoiler tuning works, uses the setup found on Open-Wheel class vehicles in the vanilla game. Each spoiler/bumper tuning has to be given AdvancedData values to affect downforce. Adjusts initial downforce from fDownforceModifier. 'Curb - boosting' seems to be nullified. Previously known as _AF_DOWNFORCE_KERBFIX",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags27"
+ },
+ {
+ "index": 28,
+ "name": "CF_REDUCE_BODY_ROLL_WITH_SUSPENSION_MODS",
+ "description": "Reduces body-roll if suspension upgrades are equipped. In addition, the vehicle gains more grip with each suspension option. Previously known as _AF_DECREASE_BODYROLL_TUNING",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags28"
+ },
+ {
+ "index": 29,
+ "name": "CF_ALLOWS_EXTENDED_MODS",
+ "description": "Requires AdvancedData to work. Adds Turbo-affecting mods for mod slot 20 (VMT_KNOB) parts, and power-affecting mods for mod slot 22 (VMT_ICE) parts.",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags29"
+ },
+ {
+ "index": 30,
+ "name": "_CF_REMOVED_FLAG_31",
+ "description": "Flag does not exist.",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags30"
+ },
+ {
+ "index": 31,
+ "name": "_CF_REMOVED_FLAG_32",
+ "description": "Flag does not exist.",
+ "category": "AdvancedFlags",
+ "dataIdentifier": "AdvancedFlags31"
+ },
+ {
+ "index": 0,
+ "name": "DF_DRIVER_SIDE_FRONT_DOOR",
+ "description": "Marks the driver-side front door (door_dside_f) bone as non-breakable.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags00"
+ },
+ {
+ "index": 1,
+ "name": "DF_DRIVER_SIDE_REAR_DOOR",
+ "description": "Marks the driver-side rear door (door_dside_r) bone as non-breakable.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags01"
+ },
+ {
+ "index": 2,
+ "name": "DF_DRIVER_PASSENGER_SIDE_FRONT_DOOR",
+ "description": "Marks the passenger-side front door (door_pside_f) bone as non-breakable.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags02"
+ },
+ {
+ "index": 3,
+ "name": "DF_DRIVER_PASSENGER_SIDE_REAR_DOOR",
+ "description": "Marks the passenger-side rear door (door_pside_r) bone as non-breakable.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags03"
+ },
+ {
+ "index": 4,
+ "name": "DF_BONNET",
+ "description": "Marks the bonnet bone as non-breakable.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags04"
+ },
+ {
+ "index": 5,
+ "name": "DF_BOOT",
+ "description": "Marks the boot bone as non-breakable.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags05"
+ },
+ {
+ "index": 6,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags06"
+ },
+ {
+ "index": 7,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags07"
+ },
+ {
+ "index": 8,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags08"
+ },
+ {
+ "index": 9,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags09"
+ },
+ {
+ "index": 10,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags10"
+ },
+ {
+ "index": 11,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags11"
+ },
+ {
+ "index": 12,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags12"
+ },
+ {
+ "index": 13,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags13"
+ },
+ {
+ "index": 14,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags14"
+ },
+ {
+ "index": 15,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags15"
+ },
+ {
+ "index": 16,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags16"
+ },
+ {
+ "index": 17,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags17"
+ },
+ {
+ "index": 18,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags18"
+ },
+ {
+ "index": 19,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags19"
+ },
+ {
+ "index": 20,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags20"
+ },
+ {
+ "index": 21,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags21"
+ },
+ {
+ "index": 22,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags22"
+ },
+ {
+ "index": 23,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags23"
+ },
+ {
+ "index": 24,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags24"
+ },
+ {
+ "index": 25,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags25"
+ },
+ {
+ "index": 26,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags26"
+ },
+ {
+ "index": 27,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags27"
+ },
+ {
+ "index": 28,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags28"
+ },
+ {
+ "index": 29,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags29"
+ },
+ {
+ "index": 30,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags30"
+ },
+ {
+ "index": 31,
+ "name": "N/A",
+ "description": "Flag does not exist.",
+ "category": "DamageFlags",
+ "dataIdentifier": "DamageFlags31"
+ },
+ {
+ "index": 0,
+ "name": "DF_STOP_FOR_CARS",
+ "description": "Stops before vehicles.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags00"
+ },
+ {
+ "index": 1,
+ "name": "DF_STOP_FOR_PEDS",
+ "description": "Stops before peds.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags01"
+ },
+ {
+ "index": 2,
+ "name": "DF_SWERVE_AROUND_ALL_CARS",
+ "description": "Avoids vehicles.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags02"
+ },
+ {
+ "index": 3,
+ "name": "DF_STEER_AROUND_STATIONARY_CARS",
+ "description": "Avoids empty vehicles.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags03"
+ },
+ {
+ "index": 4,
+ "name": "DF_STEER_AROUND_PEDS",
+ "description": "Avoids peds.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags04"
+ },
+ {
+ "index": 5,
+ "name": "DF_STEER_AROUND_OBJECTS",
+ "description": "Avoids objects.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags05"
+ },
+ {
+ "index": 6,
+ "name": "DF_DONT_STEER_AROUND_PLAYER_PED",
+ "description": "Will not avoid drive by player ped",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags06"
+ },
+ {
+ "index": 7,
+ "name": "DF_STOP_AT_LIGHTS",
+ "description": "Stops at traffic lights.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags07"
+ },
+ {
+ "index": 8,
+ "name": "DF_GO_OFF_ROAD_WHEN_AVOIDING",
+ "description": "When avoiding anything, allow for going off road.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags08"
+ },
+ {
+ "index": 9,
+ "name": "DF_DRIVE_INTO_ONCOMING_TRAFFIC",
+ "description": "Allows going wrong way. Only does it if the correct lane is full, will try to reach the correct lane again as soon as possible.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags09"
+ },
+ {
+ "index": 10,
+ "name": "DF_DRIVE_IN_REVERSE",
+ "description": "Drives in reverse gear.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags10"
+ },
+ {
+ "index": 11,
+ "name": "DF_USE_WANDER_FALLBACK_INSTEAD_OF_STRAIGHT_LINE",
+ "description": "If pathfinding fails, cruise randomly instead of going on a straight line.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags11"
+ },
+ {
+ "index": 12,
+ "name": "DF_AVOID_RESTRICTED_AREAS",
+ "description": "Avoid restricted areas.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags12"
+ },
+ {
+ "index": 13,
+ "name": "DF_PREVENT_BACKGROUND_PATHFINDING",
+ "description": "These only work on MISSION_CRUISE.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags13"
+ },
+ {
+ "index": 14,
+ "name": "DF_ADJUST_CRUISE_SPEED_BASED_ON_ROAD_SPEED",
+ "description": "Follow road speed limit.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags14"
+ },
+ {
+ "index": 15,
+ "name": "DF_PREVENT_JOIN_IN_ROAD_DIRECTION_WHEN_MOVING",
+ "description": "Flag seems not to be used in Rockstar's decompiled scripts",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags15"
+ },
+ {
+ "index": 16,
+ "name": "DF_DONT_AVOID_TARGET",
+ "description": "Flag seems not to be used in Rockstar's decompiled scripts",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags16"
+ },
+ {
+ "index": 17,
+ "name": "DF_TARGET_POSITION_OVERRIDES_ENTITY",
+ "description": "Flag seems not to be used in Rockstar's decompiled scripts",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags17"
+ },
+ {
+ "index": 18,
+ "name": "DF_USE_SHORT_CUT_LINKS",
+ "description": "Take the shortest path. Removes most pathing limits, the driver even goes on dirt roads. Use if you're going to be primarily driving off road.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags18"
+ },
+ {
+ "index": 19,
+ "name": "DF_CHANGE_LANES_AROUND_OBSTRUCTIONS",
+ "description": "Will change lanes to drive around obstructions. Previously named: Allow overtaking vehicles if possible.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags19"
+ },
+ {
+ "index": 20,
+ "name": "DF_AVOID_TARGET_COORS",
+ "description": "???",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags20"
+ },
+ {
+ "index": 21,
+ "name": "DF_USE_SWITCHED_OFF_NODES",
+ "description": "Will allow for using switched off street nodes. Cruise tasks ignore this anyway--only used for goto's.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags21"
+ },
+ {
+ "index": 22,
+ "name": "DF_PREFER_NAVMESH_ROUTE",
+ "description": "Take the shortest path. Removes most pathing limits, the driver even goes on dirt roads. Use if you're going to be primarily driving off road.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags22"
+ },
+ {
+ "index": 23,
+ "name": "DF_PLANE_TAXI_MODE",
+ "description": "Only works for planes using MISSION_GOTO, will cause them to drive along the ground instead of fly.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags23"
+ },
+ {
+ "index": 24,
+ "name": "DF_FORCE_STRAIGHT_LINE",
+ "description": "Ignore all pathing, goes straight to destination.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags24"
+ },
+ {
+ "index": 25,
+ "name": "DF_USE_STRING_PULLING_AT_JUNCTIONS",
+ "description": "???",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags25"
+ },
+ {
+ "index": 26,
+ "name": "DF_AVOID_ADVERSE_CONDITIONS",
+ "description": "Flag seems not to be used in Rockstar's decompiled scripts",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags26"
+ },
+ {
+ "index": 27,
+ "name": "DF_AVOID_TURNS",
+ "description": "Flag seems not to be used in Rockstar's decompiled scripts",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags27"
+ },
+ {
+ "index": 28,
+ "name": "DF_EXTEND_ROUTE_WITH_WANDERS_RESULTS",
+ "description": "Flag seems not to be used in Rockstar's decompiled scripts",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags28"
+ },
+ {
+ "index": 29,
+ "name": "DF_AVOID_HIGHWAYS",
+ "description": "Avoid highways when possible, will use the highway if there is no other way to get to the destination.",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags29"
+ },
+ {
+ "index": 30,
+ "name": "DF_FORCE_JOIN_IN_ROAD_DIRECTION",
+ "description": "???",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags30"
+ },
+ {
+ "index": 31,
+ "name": "DF_DONT_TERMINATE_TASK_WHEN_ACHIEVED",
+ "description": "???",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags31"
+ },
+ {
+ "index": 32,
+ "name": "DF_LAST_FLAG",
+ "description": "???",
+ "category": "DrivingStyleFlags",
+ "dataIdentifier": "DrivingStyleFlags32"
+ },
+ {
+ "index": 0,
+ "name": "HF_SMOOTHED_COMPRESSION",
+ "description": "Simulates progressive spring suspension. Makes suspension compression motion smoother.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags00"
+ },
+ {
+ "index": 1,
+ "name": "HF_REDUCED_MOD_MASS",
+ "description": "Reduces mass added from upgrades.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags01"
+ },
+ {
+ "index": 2,
+ "name": "HF_HAS_KERS",
+ "description": "Partially enables KERS on the vehicle; disables horn and shows the recharge bar below the minimap. KERS boost itself still needs to be enabled by the SET_VEHICLE_KERS_ALLOWED native.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags02"
+ },
+ {
+ "index": 3,
+ "name": "HF_HAS_RALLY_TYRES",
+ "description": "Inverts the way grip works on the vehicle; with this flag enabled, grip starts at the fTractionCurveMin value and may increase up to the fTractionCurveMax value upon wheel slip. Grip stays at max beyond the vehicle's peak slip angle.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags03"
+ },
+ {
+ "index": 4,
+ "name": "HF_NO_HANDBRAKE",
+ "description": "Disables handbrake control for the vehicle.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags04"
+ },
+ {
+ "index": 5,
+ "name": "HF_STEER_REARWHEELS",
+ "description": "Steers the rear wheels instead of the front.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags05"
+ },
+ {
+ "index": 6,
+ "name": "HF_HANDBRAKE_REARWHEELSTEER",
+ "description": "Handbrake control makes the rear wheels steer as well as the front.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags06"
+ },
+ {
+ "index": 7,
+ "name": "HF_STEER_ALL_WHEELS",
+ "description": "Steers all wheels, similar to 4-wheel-steering systems found on real vehicles. The rear wheels will steer at the same lock angle as the front, as defined by fSteeringLock.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags07"
+ },
+ {
+ "index": 8,
+ "name": "HF_FREEWHEEL_NO_GAS",
+ "description": "Disables engine-braking when no throttle is applied.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags08"
+ },
+ {
+ "index": 9,
+ "name": "HF_NO_REVERSE",
+ "description": "Disables reversing for the vehicle.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags09"
+ },
+ {
+ "index": 10,
+ "name": "HF_REDUCED_RIGHTING_FORCE",
+ "description": "???",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags10"
+ },
+ {
+ "index": 11,
+ "name": "HF_STEER_NO_WHEELS",
+ "description": "Disables steering on all wheels, used with tracked vehicles.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags11"
+ },
+ {
+ "index": 12,
+ "name": "HF_CVT",
+ "description": "Gives the vehicle a fixed-ratio transmission with a gear ratio of 0.90, used for vehicles with nInitialDriveGears=1. If gears amount to more than 1, it will simply force the vehicle into top gear upon acceleration. Recommended for electric vehicles.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags12"
+ },
+ {
+ "index": 13,
+ "name": "HF_ALT_EXT_WHEEL_BOUNDS_BEH",
+ "description": "Alternative extra wheel bound behavior. Offset extra wheel bounds forward so they act as bumpers and enable all collisions with them.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags13"
+ },
+ {
+ "index": 14,
+ "name": "HF_DONT_RAISE_BOUNDS_AT_SPEED",
+ "description": "Some vehicles bounds dont respond well to be raised up too far, so this turns off the extra bound raising at speed.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags14"
+ },
+ {
+ "index": 15,
+ "name": "HF_EXT_WHEEL_BOUNDS_COL",
+ "description": "Extra wheel bounds collide with other wheels.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags15"
+ },
+ {
+ "index": 16,
+ "name": "HF_LESS_SNOW_SINK",
+ "description": "Less grip loss from deep mud/snow, most notably in North Yankton.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags16"
+ },
+ {
+ "index": 17,
+ "name": "HF_TYRES_CAN_CLIP",
+ "description": "Tires are allowed to clip into the pavement when under enough pressure, effectiveness depends on tire sidewall. Generally makes the vehicle deal with uneven terrain better. Notes: this is the reason Offroad Tires improve performance on specific vehicles made by R*.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags17"
+ },
+ {
+ "index": 18,
+ "name": "HF_REDUCED_DRIVE_OVER_DAMAGE",
+ "description": "Don't explode vehicles when driving over them.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags18"
+ },
+ {
+ "index": 19,
+ "name": "HF_ALT_EXT_WHEEL_BOUNDS_SHRINK",
+ "description": "???",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags19"
+ },
+ {
+ "index": 20,
+ "name": "HF_OFFROAD_ABILITIES",
+ "description": "Gravity constant increased by 10% to 10.78 m/s^2, resulting in increased grip and faster falling when airborne. Acceleration and braking performance is also increased by 10%.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags20"
+ },
+ {
+ "index": 21,
+ "name": "HF_OFFROAD_ABILITIES_X2",
+ "description": "Gravity constant increased by 20% to 11.76 m/s^2, resulting in increased grip and faster falling when airborne. Acceleration and braking performance is also increased by 20%. Vehicle does not react to bushes.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags21"
+ },
+ {
+ "index": 22,
+ "name": "HF_TYRES_RAISE_SIDE_IMPACT_THRESHOLD",
+ "description": "Includes the tires in the general side collision hitbox of the vehicle. Recommended for vehicles whose wheels extend beyond the bodywork, like monster-trucks.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags22"
+ },
+ {
+ "index": 23,
+ "name": "HF_OFFROAD_INCREASED_GRAVITY_NO_FOLIAGE_DRAG",
+ "description": "Gravity constant increased by 20% to 11.76 m/s^2, resulting in increased grip and faster falling when airborne. Acceleration and braking performance is also increased by 20%. Vehicle does not react to bushes. Identical to HF_OFFROAD_ABILITIES_X2.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags23"
+ },
+ {
+ "index": 24,
+ "name": "HF_ENABLE_LEAN",
+ "description": "??? Notes: Possibly for motorcycle leaning or boat leaning.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags24"
+ },
+ {
+ "index": 25,
+ "name": "HF_FORCE_NO_TC_OR_SC",
+ "description": "Allows motorcycles to lose traction.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags25"
+ },
+ {
+ "index": 26,
+ "name": "HF_HEAVYARMOUR",
+ "description": "Vehicle is resistant to explosions.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags26"
+ },
+ {
+ "index": 27,
+ "name": "HF_ARMOURED",
+ "description": "Vehicle is bullet proof. Prevents vehicle doors (including hood and trunk) from opening in collisions.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags27"
+ },
+ {
+ "index": 28,
+ "name": "HF_SELF_RIGHTING_IN_WATER",
+ "description": "???",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags28"
+ },
+ {
+ "index": 29,
+ "name": "HF_IMPROVED_RIGHTING_FORCE",
+ "description": "Adds extra force to the vehicle when attempting to flip it back on its wheels.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags29"
+ },
+ {
+ "index": 30,
+ "name": "HF_LOW_SPEED_WHEELIES",
+ "description": "???",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags30"
+ },
+ {
+ "index": 31,
+ "name": "HF_LAST_AVAILABLE_FLAG",
+ "description": "Most likely doesn't do anything.",
+ "category": "HandlingFlags",
+ "dataIdentifier": "HandlingFlags31"
+ },
+ {
+ "index": 0,
+ "name": "MF_IS_VAN",
+ "description": "Allows double doors for the rear doors animation.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags00"
+ },
+ {
+ "index": 1,
+ "name": "MF_IS_BUS",
+ "description": "Uses bus animations for entry/exit.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags01"
+ },
+ {
+ "index": 2,
+ "name": "MF_IS_LOW",
+ "description": "Uses animations suitable for cars with a low ride-height.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags02"
+ },
+ {
+ "index": 3,
+ "name": "MF_IS_BIG",
+ "description": "Changes the way that the AI drives around corners.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags03"
+ },
+ {
+ "index": 4,
+ "name": "MF_ABS_STD",
+ "description": "Arcade Anti-Lock Braking System (ABS) equipped as standard; minimal slip allowed.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags04"
+ },
+ {
+ "index": 5,
+ "name": "MF_ABS_OPTION",
+ "description": "Arcade Anti-Lock Braking System (ABS) equipped w/ brakes upgrade.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags05"
+ },
+ {
+ "index": 6,
+ "name": "MF_ABS_ALT_STD",
+ "description": "Realistic Anti-Lock Braking System (ABS) equipped as standard; some slip allowed.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags06"
+ },
+ {
+ "index": 7,
+ "name": "MF_ABS_ALT_OPTION",
+ "description": "Realistic Anti-Lock Braking System (ABS) equipped w/brakes upgrade.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags07"
+ },
+ {
+ "index": 8,
+ "name": "MF_NO_DOORS",
+ "description": "For vehicles that don't have any operable doors.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags08"
+ },
+ {
+ "index": 9,
+ "name": "MF_TANDEM_SEATING",
+ "description": "Two people will use the front passenger seat.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags09"
+ },
+ {
+ "index": 10,
+ "name": "MF_SIT_IN_BOAT",
+ "description": "Uses seated boat animation instead of standing.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags10"
+ },
+ {
+ "index": 11,
+ "name": "MF_HAS_TRACKS",
+ "description": "For vehicles with tracks instead of tires.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags11"
+ },
+ {
+ "index": 12,
+ "name": "MF_NO_EXHAUST",
+ "description": "Removes all exhaust particles.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags12"
+ },
+ {
+ "index": 13,
+ "name": "MF_DOUBLE_EXHAUST",
+ "description": "Creates a second exhaust by mirroring the model's exhaust over the y-axis.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags13"
+ },
+ {
+ "index": 14,
+ "name": "MF_NO_1STPERSON_LOOKBEHIND",
+ "description": "Prevents player using rear view when in first-person mode.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags14"
+ },
+ {
+ "index": 15,
+ "name": "MF_CAN_ENTER_IF_NO_DOOR",
+ "description": "Allows entry into the vehicle despite no currently accessible doors.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags15"
+ },
+ {
+ "index": 16,
+ "name": "MF_AXLE_F_TORSION",
+ "description": "Front wheels stay vertical to the car.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags16"
+ },
+ {
+ "index": 17,
+ "name": "MF_AXLE_F_SOLID",
+ "description": "Front wheels stay parallel to each other.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags17"
+ },
+ {
+ "index": 18,
+ "name": "MF_AXLE_F_MCPHERSON",
+ "description": "Front wheels can tilt.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags18"
+ },
+ {
+ "index": 19,
+ "name": "MF_ATTACH_PED_TO_BODYSHELL",
+ "description": "???",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags19"
+ },
+ {
+ "index": 20,
+ "name": "MF_AXLE_R_TORSION",
+ "description": "Rear wheels stay vertical to the car.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags20"
+ },
+ {
+ "index": 21,
+ "name": "MF_AXLE_R_SOLID",
+ "description": "Rear wheels stay parallel to each other.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags21"
+ },
+ {
+ "index": 22,
+ "name": "MF_AXLE_R_MCPHERSON",
+ "description": "Rear wheels can tilt.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags22"
+ },
+ {
+ "index": 23,
+ "name": "MF_DONT_FORCE_GRND_CLEARANCE",
+ "description": "Chassis COL is taken into account when suspension is compressed while hitting the ground, with sparks rendered.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags23"
+ },
+ {
+ "index": 24,
+ "name": "MF_DONT_RENDER_STEER",
+ "description": "Does not render steering animations.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags24"
+ },
+ {
+ "index": 25,
+ "name": "MF_NO_WHEEL_BURST",
+ "description": "Has Bulletproof Tires as standard.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags25"
+ },
+ {
+ "index": 26,
+ "name": "MF_INDESTRUCTIBLE",
+ "description": "Can't explode or be considered inoperable from damage.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags26"
+ },
+ {
+ "index": 27,
+ "name": "MF_DOUBLE_FRONT_WHEELS",
+ "description": "Places a second instance of each front wheel next to the normal one.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags27"
+ },
+ {
+ "index": 28,
+ "name": "MF_IS_RC",
+ "description": "For RC vehicles such as the RC Bandito and Invade & Persuade Tank. The player model is hidden upon entering the vehicle.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags28"
+ },
+ {
+ "index": 29,
+ "name": "MF_DOUBLE_REAR_WHEELS",
+ "description": "Duplicates the skidmarks of the rear tires.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags29"
+ },
+ {
+ "index": 30,
+ "name": "MF_NO_WHEEL_BREAK",
+ "description": "Prevents wheel bones from detaching off the vehicle due to damage.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags30"
+ },
+ {
+ "index": 31,
+ "name": "MF_EXTRA_CAMBER",
+ "description": "The vehicle needs extra wheel camber to stop the wheels clipping the arches. This is only used for the lowrider cars at the moment.",
+ "category": "ModelFlags",
+ "dataIdentifier": "ModelFlags31"
+ }
+]
\ No newline at end of file
diff --git a/flags.html b/flags.html
index aa1235d..a765d2d 100644
--- a/flags.html
+++ b/flags.html
@@ -1,229 +1,87 @@
-
-
-
- Handling Flags
-
-
-
-
-
-
-
- Handling flags and stuff (WIP)
-
-
-
-
-
Model Flags Generator
-
Click on each item of the table to enable or disable a model flag. Below the table, you'll get a number which contains all the flags you have enabled.
-
-
-
-
-
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
-
-
-
RC
-
DOUBLE_RWHEELS
-
MF_NO_WHEEL_BREAK
-
IS_HATCHBACK
-
-
-
-
Result
-
<strModelFlags>00000000 <⁄strModelFlags>
-
Remember that some flags are can override each other. For example, IS_BUS and IS_VAN will not work together.
-
-
+ strong[contenteditable="true"] {
+ background: wheat;
+ padding: 0 0.5rem 0 0.5rem;
+ }
+ span.text-muted {
+ display: ruby;
+ }
-
-
-
Handling Flags Generator
-
Click on each item of the table to enable or disable a handling flag. Below the table, you'll get a number which contains all the flags you have enabled.
-
-
-
-
SMOOTH_COMPRESN
-
REDUCED_MOD_MASS
-
-
-
-
-
NO_HANDBRAKE
-
STEER_REARWHEELS
-
HB_REARWHEEL_STEER
-
STEER_ALL_WHEELS
-
-
-
FREEWHEEL_NO_GAS
-
NO_REVERSE
-
-
-
-
-
CVT
-
ALT_EXT_WHEEL_BOUNDS_BEH
-
DONT_RAISE_BOUNDS_AT_SPEED
-
-
-
-
LESS_SNOW_SINK
-
TYRES_CAN_CLI
-
-
-
-
-
OFFROAD_ABILITY
-
OFFROAD_ABILITY2
-
HF_TYRES_RAISE_SIDE_IMPACT_THRESHOLD
-
-
-
-
ENABLE_LEAN
-
HEAVYARMOUR
-
-
ARMOURED
-
-
-
SELF_RIGHTING_IN_WATER
-
IMPROVED_RIGHTING_FORCE
-
-
-
-
-
-
Result
-
-
<strHandlingFlags>00000000 <⁄strHandlingFlags>
-
-
Remember that some flags are can override each other. For example, STEER_REARWHEELS and STEER_ALL_WHEELS will not work together.
-
-
-
-
-
-
+ .pleb:after {
+ content: 'P';
+ font-size: x-small;
+ font-weight: bold;
+ baseline-shift: super;
+ position: absolute;
+ margin-left: 1rem;
+ }*/
+
+
+
+
+ Handling flags and stuff (WIP)
+
+
+
+
diff --git a/flags.js b/flags.js
new file mode 100644
index 0000000..84f7ed7
--- /dev/null
+++ b/flags.js
@@ -0,0 +1,774 @@
+
+const flagMeta = {
+ modelFlags: {
+ prefix: 'MF'
+ },
+ handlingFlags: {
+ prefix: 'HF'
+ },
+ /*advancedFlags: {
+ prefix: 'CF'
+ },*/
+ damageFlags: {
+ prefix: 'DF'
+ },
+};
+const flagData = {
+ modelFlags: [
+ ['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'],
+ ['IS_RC', 'DOUBLE_RWHEELS', 'NO_WHEEL_BREAK', 'IS_HATCHBACK']
+ ],
+ handlingFlags: [
+ ['SMOOTH_COMPRESN', 'REDUCED_MOD_MASS', '', 'HAS_RALLY_TYRES'],
+ ['NO_HANDBRAKE', 'STEER_REARWHEELS', 'HB_REARWHEEL_STEER', 'STEER_ALL_WHEELS'],
+ ['FREEWHEEL_NO_GAS', 'NO_REVERSE', 'REDUCED_RIGHTING_FORCE', ''],
+ ['CVT', 'ALT_EXT_WHEEL_BOUNDS_BEH', 'DONT_RAISE_BOUNDS_AT_SPEED', ''],
+ ['LESS_SNOW_SINK', 'TYRES_CAN_CLIP', 'REDUCED_DRIVE_OVER_DAMAGE', ''],
+ ['OFFROAD_ABILITY', 'OFFROAD_ABILITY2', 'TYRES_RAISE_SIDE_IMPACT_THRESHOLD', ''],
+ ['ENABLE_LEAN', 'HEAVYARMOUR', '', 'ARMOURED'],
+ ['SELF_RIGHTING_IN_WATER', 'IMPROVED_RIGHTING_FORCE', 'LOW_SPEED_WHEELIES', '']
+ ],
+ /*advancedFlags: [
+ ['', '', '', ''],
+ ['', '', '', ''],
+ ['', '', '', ''],
+ ['', '', '', ''],
+ ['', '', '', ''],
+ ['', '', '', ''],
+ ['', '', '', ''],
+ ['', '', '', '']
+ ],*/
+ damageFlags: [
+ ['', '', '', ''],
+ ['', '', '', ''],
+ ['', '', '', ''],
+ ['', '', '', ''],
+ ['', '', '', ''],
+ ['', '', '', ''],
+ ['', '', '', ''],
+ ['', '', '', '']
+ ]
+
+
+};
+
+
+
+
+
+
+
+
+
+
+
+
+// Store global variables for flag descriptions
+let plebFlags = {};
+let iktFlags = {};
+
+(async function loadFlags() {
+ try {
+ // Run both fetches in parallel
+ const [plebResponse, iktResponse] = await Promise.all([
+ fetch('flags-pleb.json'),
+ fetch('flags-ikt.json')
+ ]);
+
+ if (!plebResponse.ok) throw new Error(`HTTP error! Status: ${plebResponse.status}`);
+ if (!iktResponse.ok) throw new Error(`HTTP error! Status: ${iktResponse.status}`);
+
+ const plebData = await plebResponse.json();
+ const iktData = await iktResponse.json();
+
+ console.log('Loaded flags-pleb.json:', plebData);
+ console.log('Loaded flags-ikt.json:', iktData);
+
+ plebFlags = plebData;
+ iktFlags = iktData.flags || iktData;
+
+ createFlagSections();
+ autoborders();
+ } catch (err) {
+ console.error('Error loading flags files:', err);
+ }
+})();
+
+
+
+function cap1st(str){
+ return String(str).charAt(0).toUpperCase(str) + String(str).slice(1);
+}
+
+function low1st(str){
+ return String(str).charAt(0).toLowerCase(str) + String(str).slice(1);
+}
+
+
+function camelToTitleCase(camelCaseStr) { //ie modelFlags ->Model Flags
+
+ return camelCaseStr
+ .replace(/([A-Z])/g, ' $1') // Insert space before capital letters
+ .replace(/^./, str => str.toUpperCase()); // Capitalize the first letter
+}
+
+function rmFlagsPrefix(flag, key){
+
+
+ var fPrefix = flagMeta[flag].prefix;
+
+ var regex = new RegExp(`^_?(?:${fPrefix}_)`);
+
+ var result = key.replace(regex, '');
+
+
+ //console.log(`flag:${flag}, key:${key}, prefix:${fPrefix}\n\nresult:${result}`);
+
+ return result;
+
+}
+
+
+
+
+function createFlagSections() {
+
+ Object.entries(flagMeta).forEach(([key, value]) => {
+ //console.log(key, value.prefix);
+ createFlagSection(`${key}Section`, camelToTitleCase(key)+` Generator (${flagMeta[key].prefix})`, key, flagData[key], 'str'+cap1st(key) );
+ });
+
+}
+
+function createFlagSection(id, title, resultId, flagRows, handlingMetaKey) {
+
+ //console.log( `createFlagSection( \n\t'id:${id}',\n\ttitle:'${title}',\n\tflag:'${resultId}',\n\tflagRows: [${flagRows}],\n\thandlingMetaKey:'${handlingMetaKey}'\n);` );
+
+ var initFlagVal = "0";
+
+ const container = document.createElement('div');
+ container.id = id;
+ container.classList.add('col-xxl-5', 'text-center', 'bg-whitesmoke', 'mt-5', 'border', 'border-info', 'p-5', 'mx-auto');
+
+ container.innerHTML = `
+
+
${title}
+
+
+
+
${handlingMetaKey} =
${initFlagVal}
+
+ <${handlingMetaKey}>${initFlagVal} <⁄${handlingMetaKey}>
+
+
+
+
+
+ `;
+
+ const flagContainer = container.querySelector('.divTbl');
+ flagRows.forEach((row, rowIndex) => {
+
+
+ const rowDiv = document.createElement('div');
+ rowDiv.classList.add('row');
+
+ row.forEach((flag, colIndex) => {
+
+ var iktVal = ((rowIndex*4)+colIndex);
+
+ var flagInfo = iktFlags[handlingMetaKey][iktVal];
+
+ //var iktName = flagInfo.name.replace(/^_?(?:[A-Z]{2}_)/, '');
+ //var iktName = flagInfo.name.replace(/^_?(?:${flagMeta[resultId].prefix}_)/, '');
+ var iktName = rmFlagsPrefix(resultId, flagInfo.name);
+
+ //console.log('\n iktFlag:', {'index': iktVal, 'flag': resultId, 'rowIndex': rowIndex, 'colIndex': colIndex, 'iktName': iktName} );
+
+ const colDiv = document.createElement('div');
+
+ colDiv.classList.add('col-sm-3', 'py-1', 'align-middle');
+ colDiv.dataset.flag = resultId;
+ colDiv.dataset.rowIndex = rowIndex;
+ colDiv.dataset.colIndex = colIndex;
+
+
+
+
+
+
+
+ var plebFlag = plebFlags.find(item => item.index === iktVal && low1st(item.category) === resultId);
+
+ // error if we find nothing..
+
+ /*var matchCategory = String(plebFlag.category).charAt(0).toLowerCase() + String(plebFlag.category).slice(1);*/
+ //console.log(`${resultId} == ${matchCategory}`);
+ //console.log(`${resultId} == ${matchCategory}`);
+ plebFlag.category = cap1st(plebFlag.category);
+ //console.log(`plebFlag:`, plebFlag);
+
+
+ Object.entries(plebFlag).forEach(([key, value]) => {
+ //console.log(`${key}:\t${value}`);
+ colDiv.classList.add('pleb');
+ colDiv.dataset[`pleb${key}`] = value;
+ });
+
+
+
+
+
+
+
+
+
+
+ colDiv.dataset['iktdescription'] = flagInfo.description;
+ colDiv.dataset['iktname'] = flagInfo.name;
+
+ colDiv.dataset.address = getHexInfo(iktVal);
+
+ colDiv.title = `${colDiv.dataset.address}\n\n`;
+ colDiv.title += `ikt: \t${flagInfo.name}\n${flagInfo.description}`;
+ colDiv.title += `\n\n`;
+ colDiv.title += `pleb:\t${plebFlag.name}\n${plebFlag.description}`;
+ //colDiv.title = JSON.stringify( colDiv.dataset, null, 4); /*`${iktName}\n${flagInfo.description}`*/;
+ if( !flag ){
+ colDiv.classList.add('ikt');
+ colDiv.textContent = rmFlagsPrefix(resultId, iktName) || '';
+ }
+
+ if(flag){
+ colDiv.textContent = flag || '';
+ }
+
+ //console.log( colDiv.dataset );
+
+ rowDiv.appendChild(colDiv);
+ });
+ flagContainer.appendChild(rowDiv);
+ });
+
+ document.querySelector('main .row').appendChild(container);
+
+ makeHexEditable(resultId);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * How We Build a Flag
+ * -------------------
+ * Flags are represented as binary numbers where each bit represents an "ON" or "OFF" state of a specific flag.
+ * Each row contains 4 flags. Each flag corresponds to a bit position in a 4-bit binary number.
+ * The leftmost flag in the row is the most significant bit (MSB), and the rightmost flag is the least significant bit (LSB).
+ *
+ * Example:
+ * ['IS_VAN', 'IS_BUS', 'IS_LOW', 'IS_BIG']
+ * If IS_VAN and IS_LOW are toggled ON, then the binary is: 1010 (binary) = A (hexadecimal)
+*/
+
+function autoborders() {
+ const autoborder = document.querySelectorAll(".col-sm-3");
+ autoborder.forEach((col) => {
+ col.onclick = toggle;
+ col.classList.add('border', /*'text-truncate',*/ 'border-gray');
+ });
+}
+
+function toggle(e) {
+ const target = e.target;
+
+ var flagType = target.dataset.flag;
+ target.classList.toggle('bg-success');
+
+
+
+ //console.log(`flagType: ${flagType}, calcFlags(${flagType}, flagData['${flagType}'])`);
+ calcFlags(flagType, flagData[flagType]);
+
+
+ /*Object.entries(flagMeta).forEach(([key, value]) => {
+ console.log(`flagType: ${flagType}, calcFlags(${key}, flagData['${key}'])`);
+ calcFlags(key, flagData[key]);
+
+ });
+
+ calcFlags('modelFlags', flagData.modelFlags);
+ calcFlags('handlingFlags', flagData.handlingFlags);
+ calcFlags('advancedFlags', flagData.advancedFlags);
+ calcFlags('damageFlags', flagData.strDamageFlags);
+ */
+}
+
+function getHexInfo(flagIndex){
+
+
+ if (flagIndex < 0 || flagIndex > 31) {
+ console.error(`Invalid flagIndex: ${flagIndex}. Must be between 0 and 31.`);
+ return;
+ }
+
+ // Calculate the hexadecimal value of this specific bit
+ const flagValue = (1 << flagIndex) >>> 0; // Force unsigned 32-bit shift
+
+ returnStr=`Flag ${flagIndex} / 0x${flagValue.toString(16).toUpperCase().padStart(8, '0')} \/\/ ${flagValue}`;
+ // Log the flag information
+ //console.log(returnStr);
+
+ return returnStr
+
+}
+
+function calcFlags(flagType, flagArray) {
+ let result = 0 >>> 0; // Force unsigned 32-bit result
+
+ console.log( `calcFlags('${flagType}', flagArray)` );
+
+ var flaginfoLines = '';
+ $(`#${flagType}Section .divTbl div.row`).each((rowIndex, rowElement) => {
+ let rowResult = 0;
+
+
+ var binaryRow = [];
+ $(rowElement).find('div').each((colIndex, flagElement) => {
+
+ //console.log( `index: `, flagElement.dataset.plebindex );
+
+ if (flagElement.classList.contains('bg-success')) {
+
+ // Calculate the flag index using the combined row and column position
+ const flagIndex = (rowIndex * 4) + colIndex;
+
+ // Calculate the bit shift position
+ const shiftAmount = flagIndex;
+
+ // Calculate the hexadecimal value of this specific bit
+ const flagValue = (1 << shiftAmount) >>> 0; // Force unsigned 32-bit shift
+
+ // Log the flag information
+ var flagInfo = `Flag ${flagIndex} / 0x${flagValue.toString(16).toUpperCase().padStart(8, '0')} \/\/ ${flagValue}`;
+
+ flaginfoLines += `${flagInfo}\n`
+
+ // Merge this bit into the overall result
+ result |= flagValue;
+
+ binaryRow.push("1");
+ }else{
+ binaryRow.push("0");
+ }
+
+ });
+ //console.log( `${binaryRow}` );
+
+ });
+
+ // Convert the full 32-bit result to an 8-character hexadecimal string
+ const hexString = (result >>> 0).toString(16).toUpperCase().padStart(8, '0');
+
+ var truncatedHex = hexString.replace(/^0+/, '') || '0';
+
+ document.getElementById(`${flagType}`).innerHTML = truncatedHex;
+
+ //if(flaginfoLines){
+ console.log(`%c${flagType}%c:\n${flaginfoLines}`, 'color:yellow;background:black;font-weigh:bold;', '');
+
+ updHexSpan(flagType, truncatedHex);
+
+ console.log();
+ //}
+}
+
+function updHexSpan(flagType, hexString){
+
+
+ var flagIndex = `${"str"+cap1st(flagType)}`;
+ var messg = `#%c${flagIndex}%c: %c${hexString}`;
+ console.log(`${messg}\n\n`, 'color:yellow;background:black;font-weigh:bold;', '', 'color:yellow;background:blue;font-weigh:bold;');
+
+ $(`#${flagIndex}`).text(`${hexString}`);
+
+ //handlingArray.handling[flagType] = hexString;
+
+ // function replaceHandlingAttr(tagName, attrName, newValue) {
+ // replaceHandlingAttr(fDownforceModifier, value, 62.45){};
+ // tagName:fDownforceModifier, attrName:value, newValue:62.45
+ replaceHandlingAttr(flagIndex, 'string', hexString);
+}
+
+function setFlagState(flagType, hexString) {
+
+ console.log( `%csetFlagState('${flagType}', '${hexString}');`, 'background:blue;color:yellow;font-weigh:bold;' );
+
+ const binaryValue = parseInt(hexString, 16).toString(2).padStart(32, '0');
+ // console.log( 'binaryValue:', binaryValue );
+
+ const flagArray = flagData[flagType];
+
+ flagArray.forEach((row, rowIndex) => {
+ // Reverse the row index to match the binary layout
+ const reverseRowIndex = flagArray.length - 1 - rowIndex;
+ const rowBinary = binaryValue.slice(reverseRowIndex * 4, (reverseRowIndex + 1) * 4).split('').reverse();
+
+ row.forEach((flag, colIndex) => {
+ var flagNo = ((rowIndex * 4) + colIndex);
+ var queryFlagDivs = `[data-flag='${flagType}'][data-plebindex='${flagNo}']`;
+
+ const element = document.querySelector(queryFlagDivs);
+ if (!element) {
+ console.warn(`Element not found for query: ${queryFlagDivs}`);
+ return;
+ }
+
+ if (rowBinary[colIndex] === '1') {
+ element.classList.add('bg-success');
+ } else {
+ element.classList.remove('bg-success');
+ }
+ });
+
+ // console.log(`${rowBinary}`);
+
+ });
+
+ var truncatedHex = hexString.replace(/^0+/, '') || '0';
+
+ updHexSpan(flagType, truncatedHex);
+ hexInputEl = document.getElementById(flagType);
+
+
+ $(hexInputEl).text(truncatedHex);
+
+ var flagIndex = `${"str"+cap1st(flagType)}`;
+ var messg = `${flagIndex} flag updated: "${truncatedHex}"`;
+ $(`#msg_${flagType}`).removeAttr('class').addClass('alert-success').text(messg).show().fadeOut(3600, function(){
+ $(this).html(' ').show();
+ });
+}
+
+
+
+
+
+// Function to restore the caret position
+function restoreCaret(element, position) {
+ const selection = window.getSelection();
+ const range = document.createRange();
+
+ const textNode = element.childNodes[0];
+ const maxPosition = textNode ? textNode.length : 0; // Get the length of the text node safely
+
+ const safePosition = Math.min(position, maxPosition); // Ensure position is within the text length
+ try {
+ range.setStart(textNode, safePosition);
+ } catch (error) {
+ console.warn(`Failed to set caret position: ${error.message}`);
+ return;
+ }
+
+ range.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(range);
+}
+
+
+// Function to replace the current selection with new text
+function replaceSelectionWithText(element, newText) {
+ const selection = window.getSelection();
+ const range = selection.getRangeAt(0);
+ range.deleteContents();
+ range.insertNode(document.createTextNode(newText));
+}
+
+// Function to get the length of the current selection
+function getSelectionLength() {
+ const selection = window.getSelection();
+ return selection.toString().length;
+}
+
+
+function makeHexEditable(elementId) {
+ // Get the element by ID
+ const element = document.getElementById(elementId);
+
+ if (!element) {
+ console.warn(`Element with id "${elementId}" not found.`);
+ return;
+ }
+
+
+ $(element).off('change').on('change', (e) => {
+
+ var messg;
+ const lengthlimit=8;
+ const targetFlagEl = e.currentTarget;
+ var flagStr = targetFlagEl.innerHTML;
+ console.log( flagStr, flagStr.length );
+
+ if( flagStr.length >= lengthlimit ){
+ e.preventDefault();
+ targetFlagEl.innerHTML = flagStr.substring(0, lengthlimit);
+ alert( `max ${lengthlimit} chars`);
+ }
+ })
+
+ // Make it contenteditable
+ element.setAttribute('contenteditable', 'true');
+
+
+
+
+
+
+ $(element).off('keydown input paste').on('keydown input paste', (e) => {
+ const lengthLimit = 8; // Maximum allowed length
+ const targetFlagEl = e.currentTarget;
+
+ // ** Save Original Text and Caret Position **
+ const originalText = targetFlagEl.textContent; // Original text before change
+ const selection = window.getSelection();
+ const range = selection.getRangeAt(0);
+ const caretPosition = range.startOffset;
+
+ // Utility to restore text and caret if something goes wrong
+ const restoreOriginal = () => {
+ targetFlagEl.textContent = originalText;
+ restoreCaret(targetFlagEl, caretPosition);
+ }
+
+ switch (e.type) {
+
+ case 'paste':
+ e.preventDefault(); // Stop default paste action
+
+ navigator.clipboard.readText().then((pastedText) => {
+ const cleanValue = pastedText.toUpperCase().replace(/[^0-9A-F]/g, ''); // Hex only
+ const currentText = targetFlagEl.textContent;
+ const selectionLength = getSelectionLength();
+ const availableChars = lengthLimit - currentText.length + selectionLength;
+
+ const pasteValue = cleanValue.slice(0, availableChars); // Get only what fits
+ const updatedText = replaceSelectionWithText(originalText, caretPosition, selectionLength, pasteValue);
+
+
+ if (cleanValue.length > availableChars) {
+ //console.warn(`Paste rejected - not enough space for the pasted content.`);
+ //restoreOriginal();
+ var messg = `Paste truncated - text would exceed length limit.`;
+ console.warn(messg);
+ $(`#msg_${elementId}`).removeAttr('class').addClass('alert-warning').text(messg).show(); //fadeOut(3600, function(){ $(this).html(' ').show(); });
+
+ targetFlagEl.textContent = updatedText;
+ return;
+ }
+
+
+ if (updatedText.length <= lengthLimit) {
+ targetFlagEl.textContent = updatedText; // Commit change
+ restoreCaret(targetFlagEl, caretPosition + pasteValue.length); // Move caret
+ } else {
+ //console.warn(`Paste rejected - text would exceed length limit.`);
+ //restoreOriginal();
+
+ console.warn(`Paste truncated - text would exceed length limit.`);
+ targetFlagEl.textContent = updatedText;
+
+ }
+ }).catch(err => {
+ console.warn(`Error reading clipboard: ${err}`);
+ restoreOriginal();
+ });
+
+ $(`#msg_${elementId}`).removeAttr('class').text(" ").show();
+
+ break;
+
+ case 'input':
+ const cleanValue = targetFlagEl.textContent.toUpperCase().replace(/[^0-9A-F]/g, '');
+
+ if (cleanValue.length > lengthLimit) {
+ console.warn(`Input rejected - character limit of ${lengthLimit} exceeded.`);
+ restoreOriginal();
+ return;
+ }
+
+ targetFlagEl.textContent = cleanValue; // Commit change
+ restoreCaret(targetFlagEl, caretPosition);
+
+ $(`#msg_${elementId}`).removeAttr('class').text(" ").show();
+
+ break;
+
+ case 'keydown':
+ const validKeys = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
+ const key = e.key.toUpperCase();
+
+ // Handle Enter Key: If 8 chars are present, submit via setFlagState
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ var currentText = targetFlagEl.textContent;
+
+ if (currentText.length <= lengthLimit) {
+
+
+ if( currentText.length < lengthLimit) {
+ console.log( `before: %c${currentText}`, 'color:red;');
+ //currentText = currentText.padEnd( lengthLimit, '0');
+ //currentText = currentText.padStart( lengthLimit, '0');
+ console.log( `after: %c${currentText}`, 'color:red;');
+
+ }
+
+ var messg = `Submitting setFlagState('${targetFlagEl.id}', '${currentText}');`;
+ //console.log(messg);
+ $(`#msg_${elementId}`).removeAttr('class').addClass('alert-warning').text(messg);
+ try {
+ setFlagState(`${targetFlagEl.id}`, `${currentText}`);
+ } catch (error) {
+ console.error('β Error in setFlagState:', error);
+ }
+
+ } else {
+ var messg = `You must enter exactly ${lengthLimit} characters. Current: ${currentText.length}`;
+ console.log(messg);
+ $(`#msg_${elementId}`).removeAttr('class').addClass('alert-danger').text(messg).show(); //fadeOut(3600, function(){ $(this).html(' ').show(); });
+
+ }
+ return;
+ }
+
+ // Allow Ctrl+V, Ctrl+X (paste, cut)
+ if ((key === "A" || key === "V" || key === "Z" || key === "X") && e.ctrlKey) {
+ return;
+ }
+
+ // Allow navigation, backspace, delete, arrow keys
+ if (['Shift', 'Control', 'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) {
+ return;
+ }
+
+ // Reject non-hex characters
+ if (!validKeys.includes(key)) {
+ e.preventDefault();
+
+ var messg = `${elementId} Invalid key "${key}" ignored. Only hex characters (0-9, A-F) allowed. `;
+ console.warn(messg);
+ $(`#msg_${elementId}`).removeAttr('class').addClass('alert-danger').text(messg).show(); //fadeOut(3600, function(){ $(this).html(' ').show(); });
+
+ restoreOriginal();
+ return;
+ }
+
+ // Check if we have space for more characters
+ var currentText = targetFlagEl.textContent;
+ if (currentText.length >= lengthLimit && !getSelectionLength()) {
+ e.preventDefault();
+ var messg = `Max length of ${lengthLimit} reached. Ignoring further input.`;
+ console.warn(messg);
+ $(`#msg_${elementId}`).removeAttr('class').addClass('alert-warning').text(messg).show(); //
+
+ restoreOriginal();
+ return;
+ }
+
+ console.log(`#msg_${elementId}`);
+ $(`#msg_${elementId}`).text(" ");
+ /*
+ var el = $(`#msg_${elementId}`);
+
+ console.log( $(el) );
+ $(el).innerHTML(" ");
+ $(el).removeAttr('class');
+ $(el).show();
+ */
+
+ break;
+
+ }
+
+ /**
+ * Restore the caret position to the previous location.
+ * Ensures the position is valid and doesn't exceed text length.
+ */
+ function restoreCaret(element, position) {
+ const selection = window.getSelection();
+ const range = document.createRange();
+
+ const textNode = element.childNodes[0];
+ const maxPosition = textNode ? textNode.length : 0; // Get the length of the text node safely
+
+ const safePosition = Math.min(position, maxPosition); // Ensure position is within the text length
+ try {
+ range.setStart(textNode, safePosition);
+ } catch (error) {
+ console.warn(`Failed to set caret position: ${error.message}`);
+ return;
+ }
+
+ range.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+
+ /**
+ * Replace the current selection with new text.
+ * Handles the current selection and replaces it with the provided newText.
+ */
+ function replaceSelectionWithText(currentText, startPos, selectionLength, newText) {
+ const before = currentText.slice(0, startPos);
+ const after = currentText.slice(startPos + selectionLength);
+ return before + newText + after;
+ }
+
+ /**
+ * Get the length of the current selection.
+ * Useful for determining how much text is currently highlighted.
+ */
+ function getSelectionLength() {
+ const selection = window.getSelection();
+ return selection.toString().length;
+ }
+
+ });
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ console.log(`Hexadecimal input/keydown enabled for element with id "${elementId}"`);
+
+}
diff --git a/handling.html b/handling.html
index cec0a24..cc62818 100644
--- a/handling.html
+++ b/handling.html
@@ -1,8 +1,10 @@
+
+
@@ -18,6 +20,81 @@
Handling
+
+
+
@@ -96,7 +173,7 @@
Antiroll
- Flags
+ Flags
Raw data
@@ -128,11 +205,11 @@ Downforce
0
-
- 5
+ 500
@@ -140,6 +217,9 @@ Downforce
This car will generate d Gs of additional grip at 60mph.
+
+ This car will generate d Gs of additional grip at 100kph.
Downforce is a way to gain grip at speed, and can be increased by Spoilers.
@@ -257,45 +337,113 @@ Mass
with other entities. Vehicles, Lamp posts, rocks, etc.
-
-
-
Center of Mass
-
vecCentreOfMassOffset
-
- -2
-
- 2
-
-
-
This editor is not able to edit the Center Of Mass offsets yet.
-
-
-
-
-
Rotational Inertia
-
vecInertiaMultiplier
-
- -2
-
- 2
-
-
-
This editor is not able to edit the Rotational Inertia offsets yet.
-
-
+
+
+
+
+
+
+
+
+
+
@@ -391,8 +539,19 @@ Top Speed
- The vehicle's last gear will top at 40 mph.
+
+
+
+ The vehicle's last gear will top at:
+
+ 40 mph.
+
+ 64.37 kph.
+
+
+
Maximum engine top speed. Over this speed, the engine power will degrade greatly.
@@ -938,13 +1097,85 @@ Rollcentre - Back
-
+
+
+
+
+
+
+
+
+
+
FLAGS
+
+
+
+
+
+
+ Handling flags
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Don't edit the values in here. This area serves only
as a preview of the file.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1079,13 +1310,14 @@
Mass comparer
Click on the table headers to sort by that row.
-
-
+
+
@@ -1203,1139 +1435,15 @@ Performance Index
-
+
- var gear = 0;
- while (percent > gearings[gear]) gear++;
+
+
+
- var torque = Math.round((power * gearRatios[handlingArray.handling.nInitialDriveGears][gear]) * 1000) / 1000;
- var dragNow = Math.round(((Math.sqrt(spd / 5) / 100) * drag) * 1000) / 1000;
-
-
- //console.log(spd + " m/s at x" + gear + "ΒΊ - torque: " + torque + " drag " + dragNow + "");
- if (torque < dragNow + 0.01) {
- //var info = "This car peaks at " + spd + "m/s - " + gear + "/" + gearings.length;
- var info = Math.round((spd / 0.44704) * 10) / 10;
-
- var dragExp = document.getElementById("TopSpeedExpl");
- if (dragExp != null) dragExp.innerHTML = info;
- break;
- }
-
-
- //console.log(gearings.findIndex(percent)+"d");
- }
-
- }
- }
- }
- function GetDragCoeffGs(ms, mod) {
- return Math.round(Math.exp(ms * (mod * 0.001)) * 1000) / 1000;
- }
- function GetDownforceGs(ms, mod) {
-
- //Standard
- var speedmod = map(ms, 0, handlingArray.handling.fInitialDriveMaxFlatVel * 0.9, 0, (0.035 * mod));
-
- if (ms > handlingArray.handling.fInitialDriveMaxFlatVel * 0.9) speedmod = (0.035 * mod);
- var r = speedmod;
-
-
- //Minimum
- if (mod <= 1.00) r = 0.035;
-
- //New Downforce
- if (mod > 100) r = 0.0105;
-
- return Math.round(r * 4 * 1000) / 1000;
- }
- function map(x, in_min, in_max, out_min, out_max) {
-
- return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
- }
- function round(number, decimals = 1) {
- if (decimals <= 0) return Math.round(number);
- else return Math.round(number * (10 * decimals)) / (10 * decimals);
- }
- function updateArrayPre() {
- document.getElementById("handlingArray").innerHTML = JSON.stringify(handlingArray, undefined, 2);
- }
- function UpdateCharts() {
-
- //Torque Chart
- var power = handlingArray.handling.fInitialDriveForce;
- var gears = handlingArray.handling.nInitialDriveGears;
- for (var i = 1; i < 10; i++) chartPower.data.datasets[0].data[i] = 0;
- for (var i = 0; i < gears; i++) {
- chartPower.data.datasets[0].data[i] = round(((power * gearRatios[handlingArray.handling.nInitialDriveGears][i])), 2);
- }
- var power = comparisonHandlingArray.handling.fInitialDriveForce;
- var gears = comparisonHandlingArray.handling.nInitialDriveGears;
- for (var i = 1; i < 10; i++) chartPower.data.datasets[1].data[i] = 0;
- for (var i = 0; i < gears; i++) {
- chartPower.data.datasets[1].data[i] = round(((power * gearRatios[comparisonHandlingArray.handling.nInitialDriveGears][i])), 2);
- }
-
-
- //Drag Chart
- var i = 0;
- while (i < chartAirDrag.data.labels.length) {
- chartAirDrag.data.datasets[0].data[i] = GetDragCoeffGs(chartAirDrag.data.labels[i] * 0.447, handlingArray.handling.fInitialDragCoeff);
- i++;
- }
- var i = 0;
- while (i < chartAirDrag.data.labels.length) {
- chartAirDrag.data.datasets[1].data[i] = GetDragCoeffGs(chartAirDrag.data.labels[i] * 0.447, comparisonHandlingArray.handling.fInitialDragCoeff);
- i++;
- }
-
-
- //Downforce
- var i = 0;
- while (i < chartDownforce.data.labels.length) {
- chartDownforce.data.datasets[0].data[i] = GetDownforceGs(chartDownforce.data.labels[i] * 1.609344, handlingArray.handling.fDownforceModifier);
- i++;
- }
- var i = 0;
- while (i < chartDownforce.data.labels.length) {
- chartDownforce.data.datasets[1].data[i] = GetDownforceGs(chartDownforce.data.labels[i] * 1.609344, comparisonHandlingArray.handling.fDownforceModifier);
- i++;
- }
-
-
-
- chartStats.data.datasets[0].data[0] = round(map(handlingArray.handling.fInitialDriveForce, 0, 0.5, 0, 500), 1)/2;
- chartStats.data.datasets[0].data[1] = round(map((handlingArray.handling.fInitialDriveMaxFlatVel / 0.75 * 0.6213712), 0, 200, 0, 200), 1);
- chartStats.data.datasets[0].data[3] = round(map(handlingArray.handling.fTractionCurveMax, 0, 3, 0, 300, 1));
- chartStats.data.datasets[0].data[2] = round(map(handlingArray.handling.fBrakeForce, 0, 1, 0, 100), 1)*2;
- chartStats.data.datasets[0].data[4] = round(map(GetDownforceGs(chartStats.data.datasets[0].data[1] * 1.609344, handlingArray.handling.fDownforceModifier), 0, 0.5, 0, 50), 2)*4;
-
-
- chartStats.data.datasets[1].data[0] = round(map(comparisonHandlingArray.handling.fInitialDriveForce, 0, 0.5, 0, 500), 1)/2;
- chartStats.data.datasets[1].data[1] = round(map((comparisonHandlingArray.handling.fInitialDriveMaxFlatVel / 0.75 * 0.6213712), 0, 200, 0, 200), 1);
- chartStats.data.datasets[1].data[3] = round(map(comparisonHandlingArray.handling.fTractionCurveMax, 0, 3, 0, 300, 1));
- chartStats.data.datasets[1].data[2] = round(map(comparisonHandlingArray.handling.fBrakeForce, 0, 1, 0, 100), 1)*2;
- chartStats.data.datasets[1].data[4] = round(map(GetDownforceGs(chartStats.data.datasets[1].data[1] * 1.609344, comparisonHandlingArray.handling.fDownforceModifier), 0, 0.5, 0, 50), 2)*4;
-
-
- var sum = 0;
- chartStats.data.datasets[0].data.forEach(e => sum += e)
-
- chartPerformanceIndex.data.datasets[0].data[0] = round(sum);
- document.getElementById("currentPrice").innerHTML = round(GetPrice(sum));
-
-
- sum = 0;
- chartStats.data.datasets[1].data.forEach(e => sum += e)
- chartPerformanceIndex.data.datasets[1].data[0] = round(sum);
- document.getElementById("compPrice").innerHTML = round(GetPrice(sum));
-
- //chartStats.data.datasets[2] = chartStats.data.datasets[1];
-
- chartPower.update();
- chartAirDrag.update();
- chartDownforce.update();
- chartStats.update();
- chartPerformanceIndex.update();
- }
-
- function GetPrice(s) {
- if (s > 800) return map(s, 800, 1000, 500000, 1000000);
- if (s > 600) return map(s, 600, 800, 200000, 500000);
- if (s > 400) return map(s, 400, 600, 50000, 200000);
- if (s > 200) return map(s, 200, 400, 10000, 50000);
- if (s > 0) return map(s, 0, 200, 1000, 10000);
- }
-
- var ctxDownforce = document.getElementById('downforceChart').getContext('2d');
- var chartDownforce = new Chart(ctxDownforce, {
- type: 'line',
- data: {
- labels: [0, 50, 100, 150, 200],
- datasets: [{
-
- label: 'Your vehicle',
- fill: false,
- data: [0, 1.5, 3],
- backgroundColor: [
- 'rgba(255, 99, 132, 0.2)',
-
- ],
- borderColor: [
- 'rgba(255, 99, 132, 1)',
-
- ],
- borderWidth: 1,
- lineTension: 0.15,
- },
- {
- label: 'Comparison',
- fill: false,
- data: [0, 1, 2],
- backgroundColor: [
-
- 'rgba(54, 162, 235, 0.2)',
- ],
- borderColor: [
-
- 'rgba(54, 162, 235, 1)',
- ],
- borderWidth: 1,
- lineTension: 0.25,
- }],
- },
- options: {
- scales: {
- yAxes: [{
- ticks: {
-
- beginAtZero: true,
- min: 0
- },
- scaleLabel: {
- display: true,
- labelString: 'Extra Gs'
- }
- },],
- xAxes: [
- {
-
- scaleLabel: {
- display: true,
- labelString: 'Miles Per Hour'
- }
- }
- ]
- },
- },
- });
-
- var ctxAirDrag = document.getElementById('airdragChart').getContext('2d');
- var chartAirDrag = new Chart(ctxAirDrag, {
- type: 'line',
-
- data: {
- labels: [0, 25, 50, 75, 100, 125, 150, 175, 200],
- datasets: [
- {
- label: 'Your Car',
- fill: false,
- data: [0.9, 1, 1.2, 1.3, 1.4, 1.6, 1.8, 2, 2.2],
- backgroundColor: [
- 'rgba(255, 99, 132, 0.2)',
-
- ],
- borderColor: [
- 'rgba(255, 99, 132, 1)',
-
- ],
- borderWidth: 1,
- lineTension: 0.25,
- },
- {
- label: 'Comparison',
- fill: false,
- data: [0.9, 1, 1.2, 1.3, 1.4, 1.6, 1.8, 2, 2.2],
- backgroundColor: [
-
- 'rgba(54, 162, 235, 0.2)',
- ],
- borderColor: [
-
- 'rgba(54, 162, 235, 1)',
- ],
- borderWidth: 1,
- lineTension: 0.25,
- },
- /*
- {
- label: 'x1',
- fill: false,
- data: [0.9, 1, 1.2, 1.3, 1.4, 1.6, 1.8, 2, 2.2],
- backgroundColor: [
- 'rgba(255, 99, 132, 0)',
-
- ],
- borderColor: [
- 'skyblue',
- ],
- borderWidth: 1,
- lineTension: 0.25,
- },
- {
- label: 'x5',
- fill: false,
- data: [0.9, 1.1, 1.2, 1.4, 1.7, 2, 2.26, 2.65, 2.9],
- backgroundColor: [
- 'rgba(255, 99, 132, 0)',
-
- ],
- borderColor: [
- 'skyblue',
-
- ],
- borderWidth: 1,
- lineTension: 0.25,
- },
- {
- label: 'x10',
- fill: false,
- data: [0.9, 1.15, 1.46, 1.78, 2.225, 3.00, 3.7, 4.5, 5.4],
- backgroundColor: [
- 'rgba(255, 99, 132, 0)',
-
- ],
- borderColor: [
- 'skyblue',
-
- ],
- borderWidth: 1,
- lineTension: 0.25,
- },
- {
- label: 'x50',
- fill: false,
- data: [1, 1.1, 1.74, 2.5, 3.4, 4.7, 6.3, 8, 9.9],
- backgroundColor: [
- 'rgba(255, 99, 132, 0)',
-
- ],
- borderColor: [
- 'skyblue',
-
- ],
- borderWidth: 1,
- lineTension: 0.25,
- }
- */],
- },
- options: {
- scales: {
- yAxes: [{
- ticks: {
- beginAtZero: true,
-
- min: 0
- },
-
- scaleLabel: {
- display: true,
- labelString: 'Resistance (Gs)'
- }
- },],
- xAxes: [
- {
-
- scaleLabel: {
- display: true,
- labelString: 'Miles Per Hour'
- }
- }
- ]
- },
- },
- });
-
- var gearRatios =
- {
- 1: [0.9], 2: [3.33, 0.9], 3: [3.33, 1.565, 0.9], 4: [3.33, 1.826, 1.222, 0.9], 5: [3.33, 1.934, 1.358, 1.054, 0.9], 6: [3.333, 1.949, 1.392, 1.095, 0.946, 0.9]
- }
-
- var gearShiftPoints =
- {
- 0: [0], 1: [100], 2: [100], 3: [100], 4: [0, 22, 47, 69], 5: [0, 22, 45, 62, 79], 6: [0, 22, 45, 60, 76, 88], 7: [0, 22, 45, 60, 78, 91, 96], 8: [0, 22, 45, 64, 83, 98, 105, 105]
- }
- var ctxPower = document.getElementById('powerChart').getContext('2d');
- var chartPower = new Chart(ctxPower, {
- type: 'line',
-
- data: {
- labels: ["1ΒΊ", "2ΒΊ", "3ΒΊ", "4ΒΊ", "5ΒΊ", "6ΒΊ", "7ΒΊ", "8ΒΊ", "9ΒΊ", "10ΒΊ"],
-
- datasets: [{
- steppedLine: true,
- label: 'This car',
- fill: false,
- data: [0.2 * gearRatios[1], 0.2 * gearRatios[2], 0.2 * gearRatios[3], 0.2 * gearRatios[4], 0.2 * gearRatios[5], 0.2 * gearRatios[6],],
- backgroundColor: [
- 'rgba(255, 99, 132, 0.2)',
-
- ],
- borderColor: [
- 'rgba(255, 99, 132, 1)',
-
- ],
- borderWidth: 1,
- lineTension: 0,
- },
- {
- steppedLine: true,
- label: 'Comparison',
- fill: false,
- data: [0.2 * gearRatios[1], 0.2 * gearRatios[2], 0.2 * gearRatios[3], 0.2 * gearRatios[4], 0.2 * gearRatios[5], 0.2 * gearRatios[6],],
- backgroundColor: [
- 'rgba(54, 162, 235, 0.2)',
-
- ],
- borderColor: [
- 'rgba(54, 162, 235, 1)',
- ],
- borderWidth: 1,
- lineTension: 0,
- }],
-
- },
- options: {
- scales: {
- yAxes: [{
- ticks: {
- beginAtZero: true,
- min: 0
- },
- scaleLabel: {
- display: true,
- labelString: 'Gs'
- }
- },],
- xAxes: [{
- scaleLabel: {
- display: true,
- labelString: 'Gear'
- }
- }]
- },
- },
- });
-
- var ctxStats = document.getElementById('statsChart').getContext('2d');
- var chartStats = new Chart(ctxStats, {
- type: 'horizontalBar',
-
- data: {
- labels: ["Power", "Speed", "Braking", "Grip", "Aero"],
-
- datasets: [{
- label: 'This car',
- data: [5, 5, 5, 5, 5],
- backgroundColor: [
- 'rgba(255, 99, 132, 0.2)', 'rgba(255, 99, 132, 0.2)', 'rgba(255, 99, 132, 0.2)', 'rgba(255, 99, 132, 0.2)', 'rgba(255, 99, 132, 0.2)',
- ],
- borderColor: [
- 'rgba(255, 99, 132, 1)',
- 'rgba(255, 99, 132, 1)',
- 'rgba(255, 99, 132, 1)',
- 'rgba(255, 99, 132, 1)',
- 'rgba(255, 99, 132, 1)',
- ],
- borderWidth: 1,
-
- }, {
- label: 'Comparison',
- data: [5, 5, 5, 5, 5],
- backgroundColor: [
- 'rgba(54, 162, 235, 0.2)',
- 'rgba(54, 162, 235, 0.2)',
- 'rgba(54, 162, 235, 0.2)',
- 'rgba(54, 162, 235, 0.2)',
- 'rgba(54, 162, 235, 0.2)',
-
- ],
- borderColor: [
- 'rgba(54, 162, 235, 1)',
- 'rgba(54, 162, 235, 1)',
- 'rgba(54, 162, 235, 1)',
- 'rgba(54, 162, 235, 1)',
- 'rgba(54, 162, 235, 1)',
-
- ],
-
- borderWidth: 1,
- }],
-
- },
- options: {
- scales: {
- yAxes: [{
-
- scaleLabel: {
- display: false,
- labelString: ''
- }
- },],
- xAxes: [{
- scaleLabel: {
- display: false,
- labelString: ''
- },
- ticks: {
- max: 250,
- min: 0,
- stepSize: 25
-
- },
- }]
- },
- },
- });
-
-
- var ctxPerformanceIndex = document.getElementById('piChart').getContext('2d');
- var chartPerformanceIndex = new Chart(ctxPerformanceIndex, {
- type: 'horizontalBar',
- data: {
- labels: ["PI"],
-
- datasets: [{
- label: 'This car',
- data: [5],
- backgroundColor: [
- 'rgba(255, 99, 132, 0.2)',
- ],
- borderColor: [
- 'rgba(255, 99, 132, 1)',
- ],
- borderWidth: 1,
-
- }, {
- label: 'Comparison',
- data: [5],
- backgroundColor: [
- 'rgba(54, 162, 235, 0.2)',
- ],
- borderColor: [
- 'rgba(54, 162, 235, 1)',
- ],
- borderWidth: 1,
- }],
-
- },
- options: {
- scales: {
- yAxes: [{
-
- scaleLabel: {
- display: false,
- labelString: ''
- }
- },],
- xAxes: [{
- scaleLabel: {
- display: false,
- labelString: ''
- },
- ticks: {
- max: 1000,
- min: 0,
- stepSize: 50
-
- },
- }]
- },
- },
- });
- /*
- slider.oninput = function() {
- var shiftTime = this.value / 100;
- displayValue.innerHTML = shiftTime;
-
- realValue.innerHTML = Math.round((1 / shiftTime) * 10000) / 10000;
- }
- function map(x, in_min, in_max, out_min, out_max) {
- return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
- }
- */
-
- parser = new DOMParser();
- xmlDoc = parser.parseFromString(rawInput.value, "text/xml");
- xmlDoc.getElementsByTagName("Item")[0].childNodes.forEach(element => {
- if (element.attributes) {
- var d = element.attributes[0];
- if (d != null) {
- var sld = document.getElementById(element.nodeName + "Slider");
- if (sld != null) sld.value = d.value;
- var input = document.getElementById(element.nodeName + "Input");
- if (input != null) {
- input.value = d.value;
- UpdateExplanations(input);
- }
- handlingArray.handling[element.nodeName] = d.value;
- comparisonHandlingArray.handling[element.nodeName] = d.value;
-
- UpdateCharts();
- $("#wEmptyFile").toggleClass("d-none", true);
- }
- }
- });
-
-
-
-
- UpdateCharts();
-
- $(".custom-file-input").on("change", function () {
- var fileName = $(this).val().split("\\").pop();
- $(this).siblings(".custom-file-label").addClass("selected").html(fileName);
- });
-
-
-
- //Mass Comparer
-
- var allowedHandlingValues = ["handlingName", "fTractionCurveMax", "fInitialDriveForce", "fInitialDriveMaxFlatVel", "nInitialDriveGears", "fInitialDragCoeff", "fDownforceModifier", "fBrakeForce"]
-
- //Comparison file load,
- var input = document.getElementById("masscomparerloader");
- var tableArr = [];
-
- var filesLoaded = 0;
- var filesToLoad
- input.addEventListener("change", function () {
- if (this.files && this.files[0]) {
-
-
-
- var line = 0;
- tableArr = []
-
- console.log("Starting process");
-
- for (var i = 0; i < this.files.length; i++) {
-
-
- var myFile = this.files[i];
- var reader = new FileReader();
-
-
-
- reader.addEventListener('load', function (e) {
-
- console.log("Loading file");
-
- //Parse the document. Copy all values in the document on to the sliders/inputs in the page
- parser = new DOMParser();
- xmlDoc = parser.parseFromString(e.target.result, "text/xml");
- var doc = xmlDoc.getElementsByTagName("Item")[0];
-
-
- for (let index = 0; index < xmlDoc.getElementsByTagName("Item").length; index++) {
- var item = xmlDoc.getElementsByTagName("Item")[index];
-
- if (item.getElementsByTagName("handlingName").length > 0) {
- var row = 0;
- tableArr[line] = [];
-
- for (i = 0; i < allowedHandlingValues.length; i++) {
- tableArr[line][i] = "-";
- }
-
- item.childNodes.forEach(element => {
- if (allowedHandlingValues.includes(element.tagName)) {
- if (element.attributes && element.attributes[0]) {
-
- switch (element.tagName) {
-
- case "fInitialDriveMaxFlatVel": {
- tableArr[line][allowedHandlingValues.indexOf(element.tagName)] = parseFloat(element.attributes[0].value / 0.75 * 0.6213).toFixed(1);
- break;
- } case "fInitialDriveForce": {
- tableArr[line][allowedHandlingValues.indexOf(element.tagName)] = parseFloat(element.attributes[0].value*3.33).toFixed(3);
-
- break;
- } case "fTractionCurveMax": {
- tableArr[line][allowedHandlingValues.indexOf(element.tagName)] = parseFloat(element.attributes[0].value).toFixed(3);
-
- break;
- } case "fInitialDriveMaxFlatVel": {
- tableArr[line][allowedHandlingValues.indexOf(element.tagName)] = parseFloat(element.attributes[0].value / 0.75 * 0.6213).toFixed(0);
-
- break;
- }
- default: {
- tableArr[line][allowedHandlingValues.indexOf(element.tagName)] = parseFloat(element.attributes[0].value).toFixed(3);
-
- break;
- }
- }
-
-
- }
- else {
- tableArr[line][allowedHandlingValues.indexOf(element.tagName)] = element.innerHTML;
- }
- }
- });
- console.log("Line nΒΊ" + line)
- line++;
- }
- }
-
-
- $.notify("File loaded.", "success");
- console.log("Loaded file");
- console.log(tableArr)
-
- });
- reader.readAsText(myFile);
- }
- setTimeout(function () { refillTable(); }, 2000);
-
- }
-
- });
-
- function refillTable() {
-
-
-
- //Empty original
- let table = document.getElementById("tbodycompare");
- table.innerHTML = null;
-
- //Pass from array to table
-
- for (let row of tableArr) {
-
-
- var newRow = table.insertRow();
-
- var i = 0;
- for (let cell of row) {
- let newCell = table.rows[table.rows.length - 1].insertCell();
- newCell.textContent = cell;
-
- newCell.setAttribute("value", cell);
- if (i == 1) { newCell.textContent += " Gs" };
- if (i == 2) { newCell.textContent += " Gs" };
- if (i == 3) { newCell.textContent += " MPH" };
- if (i == 4) { newCell.textContent = round(newCell.textContent) };
- if (i == 5) { newCell.textContent += " Gs" };
- if (i == 6) { newCell.textContent += " Gs" };
- i++;
-
-
- newCell.classList.toggle("p-1");
- }
- }
- }
- function filterComparerTable(item) {
-
-
- //refillTable();
-
- // Declare variables
- var input, filter, table, tr, td, i, txtValue, inputValue;
- input = document.getElementById("searchInput");
- filter = input.value.toUpperCase().split(',');
- inputValue = input.value.toUpperCase();
- table = document.getElementById("tbodycompare");
- tr = table.getElementsByTagName("tr");
-
-
-
-
- // Loop through all table rows, and hide those who don't match the search query
- for (i = 0; i < tr.length; i++) {
- td = tr[i].getElementsByTagName("td")[0];
- if (td) {
- txtValue = td.textContent.toUpperCase();
-
- tr[i].classList.remove("d-none")
-
- var done = false;
- filter.forEach(e => {
- if ((e.startsWith('*') && txtValue.includes(e.substr(1)) || txtValue == e)) {
-
- tr[i].classList.remove("d-none")
-
- done = true;
- }
- else if (done == false) {
- tr[i].classList.add("d-none")
-
- }
- });
-
- if (inputValue.length == 0) {
- tr[i].classList.remove("d-none")
-
- }
- }
- }
-
- var moreToDelete = true;
- while (moreToDelete) {
- moreToDelete = false;
- for (i = 0; i < tr.length; i++) {
- if (tr[i].style.display == 'none') {
- table.deleteRow(i);
- moreToDelete = true;
- break;
- }
-
- }
- }
- }
-
- function sortTable(n) {
-
- var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
- table = document.getElementById("tbodycompare");
- switching = true;
-
- var d = new Date();
- var sStarted = d.getMilliseconds();
-
-
- //Set the sorting direction to ascending:
- dir = "asc";
-
-
- $("#tablecompare").toggleClass("text-muted", true);
-
- /*Make a loop that will continue until
- no switching has been done:*/
-
- while (switching) {
-
-
- //start by saying: no switching is done:
- switching = false;
-
- rows = table.rows;
-
-
- /*Loop through all table rows (except the
- first, which contains table headers):*/
- for (i = 0; i < (rows.length - 1); i++) {
- //start by saying there should be no switching:
- shouldSwitch = false;
-
- if (1 == 2 && rows[i].classList.contains("d-none")) {
- //console.log("Skipping row " + i)
-
- }
-
- else {
- //console.log("Doing row " + i)
-
- /*Get the two elements you want to compare,
- one from current row and one from the next:*/
- x = rows[i].getElementsByTagName("TD")[n];
- y = rows[i + 1].getElementsByTagName("TD")[n];
-
- var xValue = x.getAttribute("value");
- var yValue = y.getAttribute("value");
- /*check if the two rows should switch place,
- based on the direction, asc or desc:*/
- if (dir == "asc") {
-
- if (round(xValue, 3) > round(yValue, 3)) {
- //if so, mark as a switch and break the loop:
- shouldSwitch = true;
- break;
- }
-
- } else if (dir == "desc") {
-
-
- if (round(xValue, 3) < round(yValue, 3)) {
- //if so, mark as a switch and break the loop:
- shouldSwitch = true;
- break;
- }
-
- }
- }
-
- }
- if (shouldSwitch) {
- /*If a switch has been marked, make the switch
- and mark that a switch has been done:*/
- rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
-
- switching = true;
-
- //Each time a switch is done, increase this count by 1:
- switchcount++;
-
-
- if (switchcount > 2500) {
- setTimeout(function () { sortTable(n); }, 0);
- return;
- }
- } else if (1 == 2) {
- /*If no switching has been done AND the direction is "asc",
- set the direction to "desc" and run the while loop again.*/
- if (switchcount == 0 && dir == "asc") {
- dir = "desc";
- switching = true;
- }
- }
- }
- $("#tablecompare").toggleClass("text-muted", false);
- }
-
-
\ No newline at end of file
diff --git a/handling.js b/handling.js
new file mode 100644
index 0000000..b563a67
--- /dev/null
+++ b/handling.js
@@ -0,0 +1,1417 @@
+ var baseHandlingV = `
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 440010
+ 20000
+ 0
+ AVERAGE
+
+
+
+
+
+
+
+ `;
+
+
+ // debug log color styles
+ var c = {
+ val: 'color: yellow;background: black; font-weight:bold;',
+ ttext: 'color: yellow;background: blue;',
+ tvalue: 'color: white; background: orange; font-weight: bold;',
+ bgreen: 'color: white; background: green; font-weight: bold;',
+ bpurple:'color: white; background: purple; font-weight: bold;',
+
+
+ };
+
+
+ var kmtomiles = 0.6213712; // multiply with kms to get miles
+ var milestokm = 1.609344; // multiply with miles to get kms
+
+ var handlingFile = null;
+ var rawInput = document.getElementById("handlingFileDisplay");
+
+ rawInput.value = baseHandlingV;
+
+
+ var handlingArray = {
+ handling: {
+
+ }
+ }
+ var comparisonHandlingArray = {
+ handling: {
+
+ }
+ }
+ //File save
+ function UpdateFileObject() {
+ var a = document.getElementById("savefile");
+ var text = rawInput.value; //handlingFile.getElementsByTagName("CHandlingDataMgr")[0];
+ var file = new Blob([text], { type: "text/xml" });
+
+ a.href = URL.createObjectURL(file);
+ a.download = "handling.meta";
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//File load
+var input = document.getElementById("handlingFileLoader");
+input.addEventListener("change", function () {
+ if (this.files && this.files[0]) {
+
+ // we need to zero undefined values
+ handlingArray.handling = {};
+
+
+ var myFile = this.files[0];
+ var reader = new FileReader();
+ reader.addEventListener('load', function (e) {
+ // Parse the document. Copy all values in the document onto the sliders/inputs on the page
+ parser = new DOMParser();
+ xmlDoc = parser.parseFromString(e.target.result, "text/xml");
+
+ xmlDoc.getElementsByTagName("Item")[0].childNodes.forEach(element => {
+ if (element.attributes) {
+ let debugInfo = {
+ elementName: element.nodeName,
+ attributes: {}
+ };
+
+ // Convert attributes to an object
+ const attributesObject = Array.from(element.attributes).reduce((obj, attr) => {
+ obj[attr.name] = parseFloat(attr.value) || attr.value;
+ return obj;
+ }, {});
+
+ debugInfo.attributes = attributesObject;
+
+ // Look for X, Y, Z attributes
+ if ('x' in attributesObject && 'y' in attributesObject && 'z' in attributesObject) {
+ const x = parseFloat(attributesObject['x']) || 0;
+ const y = parseFloat(attributesObject['y']) || 0;
+ const z = parseFloat(attributesObject['z']) || 0;
+
+ // Store X, Y, Z in the handling array
+ handlingArray.handling[element.nodeName] = { X: x, Y: y, Z: z };
+
+ // Populate sliders/inputs for X, Y, Z
+ const xSlider = document.getElementById(`d_${element.nodeName}XSlider`);
+ const xInput = document.getElementById(`d_${element.nodeName}XInput`);
+ if (xSlider) xSlider.value = x;
+ if (xInput) xInput.value = x;
+
+ const ySlider = document.getElementById(`d_${element.nodeName}YSlider`);
+ const yInput = document.getElementById(`d_${element.nodeName}YInput`);
+ if (ySlider) ySlider.value = y;
+ if (yInput) yInput.value = y;
+
+ const zSlider = document.getElementById(`d_${element.nodeName}ZSlider`);
+ const zInput = document.getElementById(`d_${element.nodeName}ZInput`);
+ if (zSlider) zSlider.value = z;
+ if (zInput) zInput.value = z;
+
+ debugInfo.x = x;
+ debugInfo.y = y;
+ debugInfo.z = z;
+
+ console.log(`%c${element.nodeName}%c (%carray%c): %cX:${x}, Y:${y}, Z:${z}`, c.bgreen, '', c.bpurple, '', c.val, debugInfo);
+ }
+
+ else {
+ // Handle single-value elements (with value attribute)
+ if ('value' in attributesObject) {
+ const rawValue = parseFloat(attributesObject['value']) || attributesObject['value'];
+
+ // Update handling array
+ handlingArray.handling[element.nodeName] = rawValue;
+
+ // Populate slider/input
+ const slider = document.getElementById(`${element.nodeName}Slider`);
+ const input = document.getElementById(`${element.nodeName}Input`);
+ if (slider) slider.value = rawValue;
+ if (input) input.value = rawValue;
+
+
+ debugInfo.value = rawValue;
+ console.log(`%c${element.nodeName}%c (%cvalue%c): %c${rawValue}`, c.bgreen, '', c.tvalue, '', c.val );
+
+ }
+
+ // Handle child nodes (e.g., text content)
+ if (typeof element.childNodes[0] !== 'undefined' && element.childNodes[0].nodeValue.trim()) {
+ const textValue = element.childNodes[0].nodeValue.trim();
+
+ const flag = $(`#${element.nodeName}.flagHexval`);
+ if (flag.length) {
+
+ // "strThisIsAFlagName" -> "thisIsAFlagName"
+ var flagName = element.nodeName.replace(/^str(.)(.*)$/, (match, firstChar, rest) => firstChar.toLowerCase() + rest);
+
+ //console.log(`flagName:${flagName} %c#${element.nodeName}.flagHexval`,'color:purple;', flag);
+ //console.log(`%csetFlagState('${flagName}', '${textValue}')`, 'background:blue;color:yellow;font-weigh:bold;');
+
+ //setFlagState('modelFlags', '00000880');
+ setFlagState(flagName, textValue)
+ }
+
+ handlingArray.handling[element.nodeName] = textValue;
+
+ debugInfo.textContent = textValue;
+ console.log(`%c${element.nodeName}%c (%ctext%c): %c${textValue}`, c.bgreen, '', c.ttext, '', c.val );
+
+ }
+ }
+
+ //console.log(`%cDebug Info for ${element.nodeName}`, 'color: yellow; background: black; font-weight: bold;');
+ //console.log(debugInfo);
+ }
+ });
+
+ UpdateCharts();
+ console.log(handlingArray.handling);
+
+ // Copy the file's raw content to the raw input. We will do all our edits there.
+ rawInput.value = e.target.result;
+ applyValueChange();
+ updateArrayPre();
+
+ UpdateExplanations(e);
+
+ });
+
+ reader.readAsText(myFile);
+ $.notify("File loaded.", "success");
+ }
+});
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ //File load
+ var input = document.getElementById("compparisonHandlingFileLoader");
+ input.addEventListener("change", function () {
+ if (this.files && this.files[0]) {
+
+ var myFile = this.files[0];
+ var reader = new FileReader();
+ reader.addEventListener('load', function (e) {
+ //Parse the document. Copy all values in the document on to the sliders/inputs in the page
+ parser = new DOMParser();
+ xmlDoc = parser.parseFromString(e.target.result, "text/xml");
+
+//console.log(xml->, xmlDoc.getElementsByTagName("handlingName")[0].childNodes[0].data );
+
+
+ xmlDoc.getElementsByTagName("Item")[0].childNodes.forEach(element => {
+ if (element.attributes) {
+ var d = element.attributes[0];
+
+ if( typeof element.childNodes[0] !== 'undefined' && $(element.childNodes[0])[0].nodeValue.trim() ){
+
+ console.log(`xml->(+ typeof $(element.childNodes[0])[0].nodeValue + ): `, element.nodeName, $(element.childNodes[0])[0].nodeValue );
+ comparisonHandlingArray.handling[element.nodeName] = $(element.childNodes[0])[0].nodeValue;
+ }
+
+
+ if (d != null) {
+ var sld = document.getElementById(element.nodeName + "Slider");
+ if (sld != null) sld.value = d.value;
+ var input = document.getElementById(element.nodeName + "Input");
+ if (input != null) {
+ input.value = d.value;
+ UpdateExplanations(input);
+ }
+ comparisonHandlingArray.handling[element.nodeName] = d.value;
+ updateArrayPre();
+
+ $("#wEmptyFile").toggleClass("d-none", true);
+ }
+
+ }
+ });
+
+ UpdateCharts();
+
+ console.log(comparisonHandlingArray.handling);
+ //Copy the file's raw content to the raw input. We will do all our edits there.
+ rawInput.value = e.target.result;
+ });
+ reader.readAsText(myFile);
+ $.notify("File loaded.", "success");
+ }
+ });
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ //Other
+
+
+
+
+// Replace the values in the rawInput textarea (the XML file in the textbox).
+// This textarea holds the data we will provide when saving the file.
+function replaceHandlingAttr(tagName, attrName, newValue) {
+
+ if (!rawInput || !rawInput.value) {
+ console.error('rawInput is not defined or empty.');
+ return;
+ }
+
+ var reg;
+
+ // If we're working with x, y, z attributes, they should be updated within the tag.
+ if (['x', 'y', 'z'].includes(attrName)) {
+ // Regex to match tags like:
+ reg = new RegExp(`(<${tagName}[^>]*\\s${attrName}=")([a-zA-Z0-9.-]+)(")`, 'i');
+ }
+ else if (attrName === 'value') {
+ // Regex for attributes like:
+ reg = new RegExp(`(<${tagName}[^>]*\\s${attrName}=")([a-zA-Z0-9.-]+)(")`, 'i');
+ }
+ else if (attrName === 'string') {
+ // New improved regex to match the value inside a tag like value
+ reg = new RegExp(`(<${tagName}\\b[^>]*>)([\\s\\S]*?)(${tagName}>)`, 'i');
+ }
+ else {
+ console.error(`Unsupported attribute name: ${attrName}`);
+ return;
+ }
+
+ const match = rawInput.value.match(reg);
+ if (!match) {
+ console.warn(`No match found for tag: <${tagName}> with attribute: [${attrName}]`);
+ console.log('--- Current rawInput value ---');
+ console.log(rawInput.value);
+ console.log('--- Regex used ---');
+ console.log(reg);
+ return;
+ }
+
+ console.log(`%c[BEFORE] %c${match[0]}`, 'color: orange; font-weight: bold;', '');
+
+ // Replace the matched value with the new value
+ const result = rawInput.value.replace(reg, `$1${newValue}$3`);
+
+ // Check if the replacement was successful
+ if (result === rawInput.value) {
+ console.warn(`No match found for tag: <${tagName}> with attribute: [${attrName}]`);
+ } else {
+ //console.log('%c[AFTER]', 'color: green; font-weight: bold;', result);
+ console.log(`%cUpdated <${tagName}> [${attrName}] to ${newValue}`, 'color: green; font-weight: bold;');
+ rawInput.value = result;
+ }
+}
+
+
+
+
+ // applyValueChange() with no arguments updates all inputs ids, ending in 'Input'.
+ // added support to change ALL INPUT fields IDs matching /Input$/ then running the applyValueChange(input) with input = document.getElementById( matchedEl )
+ // this could be more targetted using class or data- attributes.
+ function applyValueChange(e, type) {
+
+ if (e == null){
+
+ $('input').each(function (i,v){
+
+ var matchesInput = v.id.match(/Input$/);
+ if( matchesInput ){
+ console.log( v.id ); // or matchesInput.input );
+ var input = document.getElementById(v.id);
+ applyValueChange( input );
+ }
+ //console.log(i,v.id);
+
+ });
+
+ }else{
+ AVChange(e, type);
+ }
+ }
+
+
+
+
+ //Applies the change to the array, and looks for its html counterpart (Slider/Input).
+ //Also applies a the change to them, to keep parity.
+ function AVChange(e, type) {
+
+ if (type == null) type = "value";
+ if (rawInput.value.length < 1000) {
+ $("#wEmptyFile").toggleClass("d-none", false);
+ return;
+ } else {
+ $("#wEmptyFile").toggleClass("d-none", true);
+ }
+
+ // Identify the axis (X, Y, Z) for vecCentreOfMassOffset or vecInertiaMultiplier
+ const attributeName = e.name; // e.g. vecCentreOfMassOffsetX, vecCentreOfMassOffsetY, vecCentreOfMassOffsetZ
+ const isXYZ = attributeName.match(/(X|Y|Z)$/); // Check if it's X, Y, or Z attribute
+ if (isXYZ) {
+ // Handle X, Y, Z attributes like vecCentreOfMassOffsetX, vecInertiaMultiplierX
+ const baseName = attributeName.replace(/X|Y|Z/g, ''); // Remove X, Y, Z to get vecCentreOfMassOffset or vecInertiaMultiplier
+ const axis = attributeName.slice(-1).toLowerCase(); // Get 'x', 'y', or 'z'
+
+ // Update handlingArray for x, y, or z under baseName
+ if (!handlingArray.handling[baseName]) handlingArray.handling[baseName] = { x: "0.000000", y: "0.000000", z: "0.000000" };
+ handlingArray.handling[baseName][axis] = e.value;
+
+ // Update the XML (rawInput)
+ replaceHandlingAttr(baseName, axis, e.value);
+
+ // console.log(`%cUpdated ${baseName} [${axis}] to ${e.value}`, 'color: yellow; font-weight: bold;');
+ } else {
+ // Handle normal value attributes like fMass, fBrakeForce, etc.
+ handlingArray.handling[attributeName] = e.value;
+
+ // Update the XML (rawInput)
+ replaceHandlingAttr(attributeName, 'value', e.value);
+
+ console.log(`%cUpdated ${attributeName} [value] to ${e.value}`, 'color: lightblue; font-weight: bold;');
+ }
+
+
+ // Sync range and input fields
+ const input = document.getElementById(e.id.includes('Slider') ? e.id.replace('Slider', 'Input') : e.id.replace('Input', 'Slider'));
+ if (input) input.value = e.value;
+
+ updateArrayPre();
+ UpdateCharts();
+ UpdateExplanations(e);
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ function UpdateExplanations(e) {
+
+
+ var expl = document.getElementById(e.name + "Expl");
+ if (expl != null) {
+ if (e.name == "fClutchChangeRateScaleUpShift") {
+ expl.innerHTML = Math.round((1 / e.value) * 1000) / 1000;
+ }
+ if (e.name == "fDriveInertia") {
+ expl.innerHTML = Math.round((1 / e.value) * 100) / 100;
+ }
+ if (e.name == "fInitialDriveForce") {
+ expl.innerHTML = Math.round((((e.value * gearRatios[1]) * 9.8) / 0.44704) * 100) / 100;
+ }
+ if (e.name == "fBrakeForce") {
+ var decc = (e.value) * 9.8; //ms^2
+ expl.innerHTML = Math.round(((60 * 0.44704) / (decc * 4)) * 100) / 100;
+ }
+ if (e.name == "fBrakeBiasFront") {
+ var balance = (e.value);
+ var result = Math.round((e.value) * 100) + "% Front - " + Math.round((1 - e.value) * 100) + "% Rear";
+
+ expl.innerHTML = result;
+ }
+ if (e.name == "fTractionBiasFront") {
+ var balance = (e.value);
+ var grip = handlingArray.handling.fTractionCurveMax;
+ var gripFront = Math.round((grip * e.value) * 100) / 100;
+ var gripRear = Math.round((grip * (1 - e.value)) * 100) / 100;
+ var result = gripFront + " Gs Front " + gripRear + " Gs Rear";
+
+ expl.innerHTML = result;
+ }
+
+ if (e.name == "fDownforceModifier") {
+ expl.innerHTML = GetDownforceGs(60 * milestokm, e.value);// Math.round((((Math.sqrt(27)) * e.value) * 0.01) * 100) / 100;
+
+ $("#fDownforceModifier2Expl").text( GetDownforceGs(100 , e.value) );
+ }
+ if (e.name == "fInitialDragCoeff") {
+ expl.innerHTML = GetDragCoeffGs(53, e.value);
+ }
+ if (e.name == "fInitialDriveMaxFlatVel") {
+
+ var topSpeedKph = Math.round((e.value / 0.75 ) * 100) / 100;
+ var topSpeedMph = Math.round((topSpeedKph * kmtomiles));
+
+ expl.innerHTML = topSpeedMph;
+
+ $('#fInitialDriveMaxFlatVelExplKm').text(topSpeedKph);
+ //console.log( () + " kph");
+ }
+
+
+ }
+
+
+ if (typeof handlingArray.handling.fInitialDriveMaxFlatVel !== 'undefined' && e.id == 'fBrakeForceInput' ) {
+ if (handlingArray.handling.fInitialDriveForce) {
+ var power = handlingArray.handling.fInitialDriveForce;
+ var topspeed = (handlingArray.handling.fInitialDriveMaxFlatVel / 0.75) / 3.6; //to m/s halved
+
+
+ var topspeedkph=(topspeed*kmtomiles);
+ console.log(`fInitialDriveMaxFlatVel: ${handlingArray.handling.fInitialDriveMaxFlatVel}, %ctopspeed%c: ${topspeed}mph, ${topspeedkph}kph`, 'background:red;color:white;font-weight:bold;', '', e.id, e.value )
+ var drag = handlingArray.handling.fInitialDragCoeff;
+ var gearings = gearShiftPoints[handlingArray.handling.nInitialDriveGears];
+ var spd = 0;
+
+
+ //console.log(topspeed +"m/s")
+ while (spd < topspeed * 2) {
+
+ spd += 2;
+ var percent = (spd * 100) / topspeed;
+
+
+ var gear = 0;
+ while (percent > gearings[gear]) gear++;
+
+
+
+
+ var torque = Math.round((power * gearRatios[handlingArray.handling.nInitialDriveGears][gear]) * 1000) / 1000;
+ var dragNow = Math.round(((Math.sqrt(spd / 5) / 100) * drag) * 1000) / 1000;
+
+
+ //console.log(spd + " m/s at x" + gear + "ΒΊ - torque: " + torque + " drag " + dragNow + "");
+ if (torque < dragNow + 0.01) {
+ //var info = "This car peaks at " + spd + "m/s - " + gear + "/" + gearings.length;
+ var info = Math.round((spd / 0.44704) * 10) / 10;
+
+ var dragExp = document.getElementById("TopSpeedExpl");
+ if (dragExp != null) dragExp.innerHTML = info;
+ break;
+ }
+
+
+ //console.log(gearings.findIndex(percent)+"d");
+ }
+
+ }
+ }
+ }
+ function GetDragCoeffGs(ms, mod) {
+ return Math.round(Math.exp(ms * (mod * 0.001)) * 1000) / 1000;
+ }
+ function GetDownforceGs(ms, mod) {
+
+ //Standard
+ var speedmod = map(ms, 0, handlingArray.handling.fInitialDriveMaxFlatVel * 0.9, 0, (0.035 * mod));
+
+ if (ms > handlingArray.handling.fInitialDriveMaxFlatVel * 0.9) speedmod = (0.035 * mod);
+ var r = speedmod;
+
+
+ //Minimum
+ if (mod <= 1.00) r = 0.035;
+
+ //New Downforce
+ if (mod > 100) r = 0.0105;
+
+ return Math.round(r * 4 * 1000) / 1000;
+ }
+ function map(x, in_min, in_max, out_min, out_max) {
+
+ return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
+ }
+ function round(number, decimals = 1) {
+ if (decimals <= 0) return Math.round(number);
+ else return Math.round(number * (10 * decimals)) / (10 * decimals);
+ }
+ function updateArrayPre() {
+ document.getElementById("handlingArray").innerHTML = JSON.stringify(handlingArray, undefined, 2);
+ }
+ function assignChartLabel(e) {
+
+ //???//
+
+ var ctxChartID = $(e)[0].ctx.canvas.id;
+
+ // Assign label for "Your Car "
+ e.config.data.datasets[0].label = 'Your Car: ' + handlingArray.handling.handlingName;
+ //console.log(`%c${ctxChartID}.config.data.datasets[0].label%c:`, 'color:yellow;background:green;font-weight:bold;', '', e.config.data.datasets[0].label );
+
+ // Assign label for "Comparison "
+ e.config.data.datasets[1].label = 'Comparison: ' + comparisonHandlingArray.handling.handlingName;
+ //console.log(`%c${ctxChartID}.config.data.datasets[1].label%c:`, 'color:yellow;background:green;font-weight:bold;', '', e.config.data.datasets[1].label);
+ }
+
+
+ function UpdateCharts() {
+
+ //Torque Chart
+ var power = handlingArray.handling.fInitialDriveForce;
+ var gears = handlingArray.handling.nInitialDriveGears;
+ for (var i = 1; i < 10; i++) chartPower.data.datasets[0].data[i] = 0;
+ for (var i = 0; i < gears; i++) {
+ chartPower.data.datasets[0].data[i] = round(((power * gearRatios[handlingArray.handling.nInitialDriveGears][i])), 2);
+ }
+ var power = comparisonHandlingArray.handling.fInitialDriveForce;
+ var gears = comparisonHandlingArray.handling.nInitialDriveGears;
+ for (var i = 1; i < 10; i++) chartPower.data.datasets[1].data[i] = 0;
+ for (var i = 0; i < gears; i++) {
+ chartPower.data.datasets[1].data[i] = round(((power * gearRatios[comparisonHandlingArray.handling.nInitialDriveGears][i])), 2);
+ }
+ assignChartLabel(chartPower);
+
+ //Drag Chart
+ var i = 0;
+ while (i < chartAirDrag.data.labels.length) {
+ chartAirDrag.data.datasets[0].data[i] = GetDragCoeffGs(chartAirDrag.data.labels[i] * 0.447, handlingArray.handling.fInitialDragCoeff);
+ i++;
+ }
+ var i = 0;
+ while (i < chartAirDrag.data.labels.length) {
+ chartAirDrag.data.datasets[1].data[i] = GetDragCoeffGs(chartAirDrag.data.labels[i] * 0.447, comparisonHandlingArray.handling.fInitialDragCoeff);
+ i++;
+ }
+ assignChartLabel(chartAirDrag, );
+
+ //Downforce
+ var i = 0;
+ while (i < chartDownforce.data.labels.length) {
+ chartDownforce.data.datasets[0].data[i] = GetDownforceGs(chartDownforce.data.labels[i] * kmtomiles, handlingArray.handling.fDownforceModifier);
+ i++;
+ }
+ var i = 0;
+ while (i < chartDownforce.data.labels.length) {
+ chartDownforce.data.datasets[1].data[i] = GetDownforceGs(chartDownforce.data.labels[i] * kmtomiles, comparisonHandlingArray.handling.fDownforceModifier);
+ i++;
+ }
+ assignChartLabel(chartDownforce);
+
+
+ chartStats.data.datasets[0].data[0] = round(map(handlingArray.handling.fInitialDriveForce, 0, 0.5, 0, 500), 1)/2;
+ chartStats.data.datasets[0].data[1] = round(map((handlingArray.handling.fInitialDriveMaxFlatVel / 0.75 * milestokm), 0, 200, 0, 200), 1);
+ chartStats.data.datasets[0].data[3] = round(map(handlingArray.handling.fTractionCurveMax, 0, 3, 0, 300, 1));
+ chartStats.data.datasets[0].data[2] = round(map(handlingArray.handling.fBrakeForce, 0, 1, 0, 100), 1)*2;
+ chartStats.data.datasets[0].data[4] = round(map(GetDownforceGs(chartStats.data.datasets[0].data[1] * kmtomiles, handlingArray.handling.fDownforceModifier), 0, 0.5, 0, 50), 2)*4;
+
+
+ chartStats.data.datasets[1].data[0] = round(map(comparisonHandlingArray.handling.fInitialDriveForce, 0, 0.5, 0, 500), 1)/2;
+ chartStats.data.datasets[1].data[1] = round(map((comparisonHandlingArray.handling.fInitialDriveMaxFlatVel / 0.75 * milestokm), 0, 200, 0, 200), 1);
+ chartStats.data.datasets[1].data[3] = round(map(comparisonHandlingArray.handling.fTractionCurveMax, 0, 3, 0, 300, 1));
+ chartStats.data.datasets[1].data[2] = round(map(comparisonHandlingArray.handling.fBrakeForce, 0, 1, 0, 100), 1)*2;
+ chartStats.data.datasets[1].data[4] = round(map(GetDownforceGs(chartStats.data.datasets[1].data[1] * kmtomiles, comparisonHandlingArray.handling.fDownforceModifier), 0, 0.5, 0, 50), 2)*4;
+
+
+ var sum = 0;
+ chartStats.data.datasets[0].data.forEach(e => sum += e)
+ chartPerformanceIndex.data.datasets[0].data[0] = round(sum);
+ document.getElementById("currentPrice").innerHTML = round(GetPrice(sum));
+
+ assignChartLabel(chartStats);
+
+
+
+ sum = 0;
+ chartStats.data.datasets[1].data.forEach(e => sum += e)
+ chartPerformanceIndex.data.datasets[1].data[0] = round(sum);
+ document.getElementById("compPrice").innerHTML = round(GetPrice(sum));
+
+ assignChartLabel(chartPerformanceIndex);
+ //chartStats.data.datasets[2] = chartStats.data.datasets[1];
+
+ chartPower.update();
+ chartAirDrag.update();
+ chartDownforce.update();
+ chartStats.update();
+ chartPerformanceIndex.update();
+ }
+
+ function GetPrice(s) {
+ if (s > 800) return map(s, 800, 1000, 500000, 1000000);
+ if (s > 600) return map(s, 600, 800, 200000, 500000);
+ if (s > 400) return map(s, 400, 600, 50000, 200000);
+ if (s > 200) return map(s, 200, 400, 10000, 50000);
+ if (s > 0) return map(s, 0, 200, 1000, 10000);
+ }
+
+ var ctxDownforce = document.getElementById('downforceChart').getContext('2d');
+ var chartDownforce = new Chart(ctxDownforce, {
+ type: 'line',
+ data: {
+ labels: [0, 50, 100, 150, 200],
+ datasets: [{
+
+ label: 'Your vehicle: '+handlingArray.handling.handlingName,
+ fill: false,
+ data: [0, 1.5, 3],
+ backgroundColor: [
+ 'rgba(255, 99, 132, 0.2)',
+
+ ],
+ borderColor: [
+ 'rgba(255, 99, 132, 1)',
+
+ ],
+ borderWidth: 1,
+ lineTension: 0.15,
+ },
+ {
+ label: 'Comparison: ' + comparisonHandlingArray.handling.handlingName,
+ fill: false,
+ data: [0, 1, 2],
+ backgroundColor: [
+
+ 'rgba(54, 162, 235, 0.2)',
+ ],
+ borderColor: [
+
+ 'rgba(54, 162, 235, 1)',
+ ],
+ borderWidth: 1,
+ lineTension: 0.25,
+ }],
+ },
+ options: {
+ scales: {
+ yAxes: [{
+ ticks: {
+
+ beginAtZero: true,
+ min: 0
+ },
+ scaleLabel: {
+ display: true,
+ labelString: 'Extra Gs'
+ }
+ },],
+ xAxes: [
+ {
+
+ scaleLabel: {
+ display: true,
+ labelString: 'Miles Per Hour'
+ }
+ }
+ ]
+ },
+ },
+ });
+
+ var ctxAirDrag = document.getElementById('airdragChart').getContext('2d');
+ var chartAirDrag = new Chart(ctxAirDrag, {
+ type: 'line',
+
+ data: {
+ labels: [0, 25, 50, 75, 100, 125, 150, 175, 200],
+ datasets: [
+ {
+ label: 'Your Car: '+handlingArray.handling.handlingName,
+
+ fill: false,
+ data: [0.9, 1, 1.2, 1.3, 1.4, 1.6, 1.8, 2, 2.2],
+ backgroundColor: [
+ 'rgba(255, 99, 132, 0.2)',
+
+ ],
+ borderColor: [
+ 'rgba(255, 99, 132, 1)',
+
+ ],
+ borderWidth: 1,
+ lineTension: 0.25,
+ },
+ {
+ label: 'Comparison: ' + comparisonHandlingArray.handling.handlingName,
+
+ fill: false,
+ data: [0.9, 1, 1.2, 1.3, 1.4, 1.6, 1.8, 2, 2.2],
+ backgroundColor: [
+
+ 'rgba(54, 162, 235, 0.2)',
+ ],
+ borderColor: [
+
+ 'rgba(54, 162, 235, 1)',
+ ],
+ borderWidth: 1,
+ lineTension: 0.25,
+ },
+ /*
+ {
+ label: 'x1',
+ fill: false,
+ data: [0.9, 1, 1.2, 1.3, 1.4, 1.6, 1.8, 2, 2.2],
+ backgroundColor: [
+ 'rgba(255, 99, 132, 0)',
+
+ ],
+ borderColor: [
+ 'skyblue',
+ ],
+ borderWidth: 1,
+ lineTension: 0.25,
+ },
+ {
+ label: 'x5',
+ fill: false,
+ data: [0.9, 1.1, 1.2, 1.4, 1.7, 2, 2.26, 2.65, 2.9],
+ backgroundColor: [
+ 'rgba(255, 99, 132, 0)',
+
+ ],
+ borderColor: [
+ 'skyblue',
+
+ ],
+ borderWidth: 1,
+ lineTension: 0.25,
+ },
+ {
+ label: 'x10',
+ fill: false,
+ data: [0.9, 1.15, 1.46, 1.78, 2.225, 3.00, 3.7, 4.5, 5.4],
+ backgroundColor: [
+ 'rgba(255, 99, 132, 0)',
+
+ ],
+ borderColor: [
+ 'skyblue',
+
+ ],
+ borderWidth: 1,
+ lineTension: 0.25,
+ },
+ {
+ label: 'x50',
+ fill: false,
+ data: [1, 1.1, 1.74, 2.5, 3.4, 4.7, 6.3, 8, 9.9],
+ backgroundColor: [
+ 'rgba(255, 99, 132, 0)',
+
+ ],
+ borderColor: [
+ 'skyblue',
+
+ ],
+ borderWidth: 1,
+ lineTension: 0.25,
+ }
+ */],
+ },
+ options: {
+ scales: {
+ yAxes: [{
+ ticks: {
+ beginAtZero: true,
+
+ min: 0
+ },
+
+ scaleLabel: {
+ display: true,
+ labelString: 'Resistance (Gs)'
+ }
+ },],
+ xAxes: [
+ {
+
+ scaleLabel: {
+ display: true,
+ labelString: 'Miles Per Hour'
+ }
+ }
+ ]
+ },
+ },
+ });
+
+ var gearRatios =
+ {
+ 1: [0.9], 2: [3.33, 0.9], 3: [3.33, 1.565, 0.9], 4: [3.33, 1.826, 1.222, 0.9], 5: [3.33, 1.934, 1.358, 1.054, 0.9], 6: [3.333, 1.949, 1.392, 1.095, 0.946, 0.9]
+ }
+
+ var gearShiftPoints =
+ {
+ 0: [0], 1: [100], 2: [100], 3: [100], 4: [0, 22, 47, 69], 5: [0, 22, 45, 62, 79], 6: [0, 22, 45, 60, 76, 88], 7: [0, 22, 45, 60, 78, 91, 96], 8: [0, 22, 45, 64, 83, 98, 105, 105]
+ }
+ var ctxPower = document.getElementById('powerChart').getContext('2d');
+ var chartPower = new Chart(ctxPower, {
+ type: 'line',
+
+ data: {
+ labels: ["1ΒΊ", "2ΒΊ", "3ΒΊ", "4ΒΊ", "5ΒΊ", "6ΒΊ", "7ΒΊ", "8ΒΊ", "9ΒΊ", "10ΒΊ"],
+
+ datasets: [{
+ steppedLine: true,
+ label: 'This car: '+handlingArray.handling.handlingName,
+ fill: false,
+ data: [0.2 * gearRatios[1], 0.2 * gearRatios[2], 0.2 * gearRatios[3], 0.2 * gearRatios[4], 0.2 * gearRatios[5], 0.2 * gearRatios[6],],
+ backgroundColor: [
+ 'rgba(255, 99, 132, 0.2)',
+
+ ],
+ borderColor: [
+ 'rgba(255, 99, 132, 1)',
+
+ ],
+ borderWidth: 1,
+ lineTension: 0,
+ },
+ {
+ steppedLine: true,
+ label: 'Comparison: ' + comparisonHandlingArray.handling.handlingName,
+ fill: false,
+ data: [0.2 * gearRatios[1], 0.2 * gearRatios[2], 0.2 * gearRatios[3], 0.2 * gearRatios[4], 0.2 * gearRatios[5], 0.2 * gearRatios[6],],
+ backgroundColor: [
+ 'rgba(54, 162, 235, 0.2)',
+
+ ],
+ borderColor: [
+ 'rgba(54, 162, 235, 1)',
+ ],
+ borderWidth: 1,
+ lineTension: 0,
+ }],
+
+ },
+ options: {
+ scales: {
+ yAxes: [{
+ ticks: {
+ beginAtZero: true,
+ min: 0
+ },
+ scaleLabel: {
+ display: true,
+ labelString: 'Gs'
+ }
+ },],
+ xAxes: [{
+ scaleLabel: {
+ display: true,
+ labelString: 'Gear'
+ }
+ }]
+ },
+ },
+ });
+
+ var ctxStats = document.getElementById('statsChart').getContext('2d');
+ var chartStats = new Chart(ctxStats, {
+ type: 'horizontalBar',
+
+ data: {
+ labels: ["Power", "Speed", "Braking", "Grip", "Aero"],
+
+ datasets: [{
+ label: 'This car: '+handlingArray.handling.handlingName,
+
+ data: [5, 5, 5, 5, 5],
+ backgroundColor: [
+ 'rgba(255, 99, 132, 0.2)', 'rgba(255, 99, 132, 0.2)', 'rgba(255, 99, 132, 0.2)', 'rgba(255, 99, 132, 0.2)', 'rgba(255, 99, 132, 0.2)',
+ ],
+ borderColor: [
+ 'rgba(255, 99, 132, 1)',
+ 'rgba(255, 99, 132, 1)',
+ 'rgba(255, 99, 132, 1)',
+ 'rgba(255, 99, 132, 1)',
+ 'rgba(255, 99, 132, 1)',
+ ],
+ borderWidth: 1,
+
+ }, {
+ label: 'Comparison: ' + comparisonHandlingArray.handling.handlingName,
+ data: [5, 5, 5, 5, 5],
+ backgroundColor: [
+ 'rgba(54, 162, 235, 0.2)',
+ 'rgba(54, 162, 235, 0.2)',
+ 'rgba(54, 162, 235, 0.2)',
+ 'rgba(54, 162, 235, 0.2)',
+ 'rgba(54, 162, 235, 0.2)',
+
+ ],
+ borderColor: [
+ 'rgba(54, 162, 235, 1)',
+ 'rgba(54, 162, 235, 1)',
+ 'rgba(54, 162, 235, 1)',
+ 'rgba(54, 162, 235, 1)',
+ 'rgba(54, 162, 235, 1)',
+
+ ],
+
+ borderWidth: 1,
+ }],
+
+ },
+ options: {
+ scales: {
+ yAxes: [{
+
+ scaleLabel: {
+ display: false,
+ labelString: ''
+ }
+ },],
+ xAxes: [{
+ scaleLabel: {
+ display: false,
+ labelString: ''
+ },
+ ticks: {
+ max: 250,
+ min: 0,
+ stepSize: 25
+
+ },
+ }]
+ },
+ },
+ });
+
+
+ var ctxPerformanceIndex = document.getElementById('piChart').getContext('2d');
+ var chartPerformanceIndex = new Chart(ctxPerformanceIndex, {
+ type: 'horizontalBar',
+ data: {
+ labels: ["PI"],
+
+ datasets: [{
+ label: 'This car: '+handlingArray.handling.handlingName,
+ data: [5],
+ backgroundColor: [
+ 'rgba(255, 99, 132, 0.2)',
+ ],
+ borderColor: [
+ 'rgba(255, 99, 132, 1)',
+ ],
+ borderWidth: 1,
+
+ }, {
+ label: 'Comparison: ' + comparisonHandlingArray.handling.handlingName,
+
+ data: [5],
+ backgroundColor: [
+ 'rgba(54, 162, 235, 0.2)',
+ ],
+ borderColor: [
+ 'rgba(54, 162, 235, 1)',
+ ],
+ borderWidth: 1,
+ }],
+
+ },
+ options: {
+ scales: {
+ yAxes: [{
+
+ scaleLabel: {
+ display: false,
+ labelString: ''
+ }
+ },],
+ xAxes: [{
+ scaleLabel: {
+ display: false,
+ labelString: ''
+ },
+ ticks: {
+ max: 1000,
+ min: 0,
+ stepSize: 50
+
+ },
+ }]
+ },
+ },
+ });
+ /*
+ slider.oninput = function() {
+ var shiftTime = this.value / 100;
+ displayValue.innerHTML = shiftTime;
+
+ realValue.innerHTML = Math.round((1 / shiftTime) * 10000) / 10000;
+ }
+ function map(x, in_min, in_max, out_min, out_max) {
+ return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
+ }
+ */
+
+
+
+ /// DUMMY XML
+ parser = new DOMParser();
+ xmlDoc = parser.parseFromString(rawInput.value, "text/xml");
+
+ xmlDoc.getElementsByTagName("Item")[0].childNodes.forEach(element => {
+
+
+
+
+ if (element.attributes) {
+
+
+
+ if( typeof element.attributes[0] !== 'undefined' ){
+ console.log( `${element.nodeName}: `, element.attributes[0].value );
+ comparisonHandlingArray.handling[ element.nodeName ] = element.attributes[0].value;
+ }else if(element.textContent.trim() ){
+ console.log( `${element.nodeName}:`, element.textContent.trim() );
+ comparisonHandlingArray.handling[ element.nodeName ] = element.textContent.trim();
+ }else{
+ console.log( element.nodeName, element );
+
+ }
+
+ var d = element.attributes[0];
+ if (d != null) {
+ var sld = document.getElementById(element.nodeName + "Slider");
+ if (sld != null) sld.value = d.value;
+ var input = document.getElementById(element.nodeName + "Input");
+ if (input != null) {
+ input.value = d.value;
+ UpdateExplanations(input);
+ }
+ handlingArray.handling[element.nodeName] = d.value;
+ comparisonHandlingArray.handling[element.nodeName] = d.value;
+
+
+ $("#wEmptyFile").toggleClass("d-none", true);
+ }
+
+ }
+ });
+
+
+
+
+ UpdateCharts();
+
+ $(".custom-file-input").on("change", function () {
+ var fileName = $(this).val().split("\\").pop();
+ $(this).siblings(".custom-file-label").addClass("selected").html(fileName);
+ });
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//Mass Comparer
+
+// Define allowed values and their corresponding labels/units
+const columnDefinitions = [
+ { key: "handlingFilePath", name: "filePath", unit: "" },
+ { key: "handlingName", name: "ID", unit: "" },
+ { key: "fTractionCurveMax", name: "Tyre Grip", unit: " Gs" },
+ { key: "fInitialDriveForce", name: "Acceleration", unit: " Gs" },
+ { key: "fInitialDriveMaxFlatVel", name: "Top Speed (MPH)", unit: " MPH" },
+ { key: "fInitialDriveMaxFlatVelKPH", name: "Top Speed (KPH)", unit: " KPH" },
+ { key: "nInitialDriveGears", name: "Gears", unit: "" },
+ { key: "fInitialDragCoeff", name: "Air Drag", unit: " Gs" },
+ { key: "fDownforceModifier", name: "Downforce", unit: " Gs" },
+ { key: "fBrakeForce", name: "Brakes", unit: "" }
+];
+
+var tableArr = [];
+var input = document.getElementById("masscomparerloader");
+
+input.addEventListener("change", function () {
+ tableArr = []; // Reset the table array
+
+ console.log("Starting file processing...");
+
+ Array.from(this.files).forEach(file => {
+ const reader = new FileReader();
+
+ reader.addEventListener('load', (e) => {
+ console.log("Loading file...");
+ const parser = new DOMParser();
+ const xmlDoc = parser.parseFromString(e.target.result, "text/xml");
+
+ Array.from(xmlDoc.getElementsByTagName("Item")).forEach(item => {
+ var itemType = item.getAttribute('type') || 'NULL';
+
+ // Only process CHandlingData and CCarHandlingData types
+ if (itemType !== "CHandlingData" && itemType !== "CCarHandlingData") {
+ console.log(`%cSkipped Item Type: ${itemType}`, 'color: orange; font-weight: bold;');
+ return; // Skip this item
+ }
+
+ const row = new Array(columnDefinitions.length).fill("-");
+
+ // Loop over each child element of the - tag
+ Array.from(item.children).forEach(element => {
+ const colIndex = columnDefinitions.findIndex(col => col.key === element.tagName);
+
+ if (colIndex !== -1 && element.attributes && element.attributes[0]) {
+ const rawValue = parseFloat(element.attributes[0].value) || 0;
+
+ if (element.tagName === "fInitialDriveMaxFlatVel") {
+ const speedmph = (rawValue / 0.75 * milestokm).toFixed(1);
+ const speedkph = (speedmph * kmtomiles).toFixed(1);
+ row[colIndex] = speedmph;
+
+ const kphIndex = columnDefinitions.findIndex(col => col.key === "fInitialDriveMaxFlatVelKPH");
+ if (kphIndex !== -1) row[kphIndex] = speedkph;
+
+ const handlingFilePathIndex = columnDefinitions.findIndex(col => col.key === "handlingFileName");
+
+ const filePathIndex = columnDefinitions.findIndex(col => col.key === "handlingFilePath");
+ if (filePathIndex !== -1) row[filePathIndex] = file.name;
+
+
+ //if (handlingFileNameIndex !== -1) row[handlingFileNameIndex] = filename;
+
+
+
+ } else if (element.tagName === "fInitialDriveForce") {
+ row[colIndex] = (rawValue * 3.33).toFixed(3);
+ } else {
+ row[colIndex] = rawValue.toFixed(3);
+ }
+ } else if (colIndex !== -1) {
+ const textval = element.textContent.trim();
+ row[colIndex] = textval || "-";
+ }
+
+ if( typeof row[colIndex] === 'undefined'){
+
+ if( element.textContent.trim() ){
+
+ console.log( `${colIndex} %c${element.tagName}%c:`, 'color:white;background:purple;font-weight:bold;', '', element.textContent.trim() );
+
+ }else{
+
+
+
+ const attributesArray = Array.from($(element)[0].attributes); // Turn attributes into an array
+ const jsonObject = attributesArray.reduce((obj, attr) => {
+ obj[attr.name] = attr.value;
+ return obj;
+ }, {});
+
+
+ if( typeof jsonObject.value !== 'undefined' && Object.keys(jsonObject).length == 1){
+
+ console.log( `${colIndex} %c${element.tagName} %c:`, 'color:white;background:red;font-weight:bold;', '', jsonObject.value /*Array.from($(element)[0].attributes)*/ );
+
+ }else{
+
+ console.log( `${colIndex} %c${element.tagName} %c:`, 'color:white;background:red;font-weight:bold;', '', jsonObject /*Array.from($(element)[0].attributes)*/ );
+
+ }
+
+ }
+
+ }else{
+
+ console.log( `${colIndex} %c${element.tagName}%c: [${row[colIndex]}]`, 'color:white;background:orange;font-weight:bold;', '' );
+ console.log(`\n`);
+ }
+ });
+
+ // Check if row contains valid data (not all "-" values)
+ const hasValidData = row.some(value => value !== "-");
+
+ // Check if handlingName is set
+ const hasValidHandlingName = row[0] && row[0] !== "-";
+
+ if (hasValidData && hasValidHandlingName) {
+ tableArr.push(row);
+ } else {
+ console.log(`%cSkipped empty or invalid row for type ${itemType}`, 'color: red; font-weight: bold;');
+ }
+ });
+
+ console.log("File loaded and processed.");
+ });
+
+ reader.readAsText(file);
+ });
+
+ setTimeout(() => drawTableFromArray(tableArr), 2000);
+});
+
+function drawTableFromArray(dataArray) {
+ const container = document.getElementById("compareTableDIV");
+ container.innerHTML = ''; // Clear existing content
+
+ const table = document.createElement('table');
+ table.id = "tablecompare";
+ table.classList.add('table', 'mx-auto', 'border');
+
+ // Create table header
+ const thead = document.createElement('thead');
+ const headerRow = document.createElement('tr');
+ columnDefinitions.forEach(col => {
+ const th = document.createElement('th');
+ th.textContent = col.name;
+ th.scope = "col";
+ headerRow.appendChild(th);
+ });
+ thead.appendChild(headerRow);
+ table.appendChild(thead);
+
+ // Create table body
+ const tbody = document.createElement('tbody');
+ dataArray.forEach(row => {
+ const newRow = document.createElement('tr');
+ row.forEach((cell, index) => {
+ const newCell = document.createElement('td');
+ newCell.textContent = cell + (columnDefinitions[index]?.unit || "");
+ newCell.setAttribute("value", cell);
+ newCell.classList.add("p-1");
+ newRow.appendChild(newCell);
+ });
+ tbody.appendChild(newRow);
+ });
+
+ table.appendChild(tbody);
+ container.appendChild(table);
+}
+
+
+
+
+var input = document.getElementById('fDownforceModifier' + "Input");
+applyValueChange( input );
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..cc62818
--- /dev/null
+++ b/index.html
@@ -0,0 +1,1449 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Handling
+
+
+
+
+
+
+
+
+
0.3.5 - Added mass comparer
+
+ Handling Editor
+ Edit handling files easily, and quickly.
+
+
+
+
+
+
+
This tool aims to help beginners visualise and understand handling files,
+ by establishing itself as an intuitive interface between the user and the file.
+
+
+ You can start from scratch, and move sliders around right away. Just press the Save button at the bottom
+ to download the an edited file. Do remember to open it and edit the handling id.
+
+
+ Also, you can load a custom handling file below.
+
+
+
+
+
+
+ Select a handling.meta file...
+
+
+
+
+
+
+
+
+
+
+
+
+ Load a handling
+ file first.
+
+
+
+
+
+
+
+
+
+
+
+
+
Aero
+
This set of values represent the vehicle body shape, and mostly
+ affect top speed behavior.
+
+
+
+
+
Downforce
+
fDownforceModifier
+
+
+ 0
+
+ 500
+
+
+
+
+
This car will generate d Gs of additional grip at 60mph.
+
+
This car will generate d Gs of additional grip at 100kph.
+
+
+
Downforce is a way to gain grip at speed, and can be increased by Spoilers.
+
In V, downforce is represented as two behaviors - an actual down force pressing
+ the car down, and a separate grip gain based on fDownforceModifier and a
+ ruleset. The resulting value is added to the wheel grip, multiply by the
+ four wheels you'd usually have.
+
+
The value of fDownforceModifier can define one of three rulesets for the
+ Downforce calculations:
+
+ Dynamic : Between 1.0 and 100. The vehicle gains grip at a variable
+ rate from 0 when static to 0.035 * fDownforceModifier at 90% of its top
+ speed.
+
+
+ Static (high) : 1.0 or less. The vehicle gains a flat grip increase of
+ 0.035, 0.07 if using a tuning Spoiler.
+
+
+ Static (low) : 100 or more. The vehicle gains a flat grip increase of
+ 0.0105, 0.01313 if using a spoiler.
+
+
+
Active Aero vehicles ignore this value, setting a 0.035 gain when the spoiler is
+ raised, up to 0.07 when it pitches down to aid cornering or braking.
+
+
There is a fourth ruleset, used on the Open Wheel vehicles, which requires
+ advanced flags and defines specific rates per Spoiler/Bumper. Investigation is
+ still ongoing.
+
+
+
+
+
+
Air Resistance
+
fInitialDragCoeff
+
+
+
+ 0
+
+ 20
+
+
+
The vehicle's body air resistance.
+
+
+ Higher resistance means an eariler perceived loss of
+ power at higher speed, resulting in a lower top speed, as the engine cannot
+ overpower this force. Lower air resistance allows the vehicle to travel faster
+ on the same power.
+
+
+
Working as a multiplier, this value defines how much will air drag scale with
+ speed. The higher the multiplier, the more powerful air resistance will be at
+ high speeds.
+
+
+
The tug of war between Torque and Drag is not very strong. Your vehicle can
+ easily go over this speed on downhill or under performance upgrades, though
+ not very far.
+ Drag works against Torque. Both are measured in G-Forces, the speed at which
+ they even out is the vehicle's top speed.
+ Unlike Downforce, drag is omnidirectional.
+
+
+
+
+
+
+
+
+
+
+
+
+
Chassis
+
This set of values represent the vehicle body itself, and will play
+ a big role on how the vehicle behaves overall.
+
+
+
+
+
+
Mass
+
fMass
+
+ 0
+
+ 15000
+
+
+
+
+ Your car will weight 1000 kg.
+
+
+
Measured in Kg, mass is only responsible for the interaction between entities.
+ Let's
+ say its the vehicle's pushing force.
+
Mass is not at all related to physical
+ agility and does not affect the car's physics at all. Only affects how well the
+ car fares when colliding
+ with other entities. Vehicles, Lamp posts, rocks, etc.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Engine
+
The moving force of your vehicle, this set of values govern details
+ about how different bits of the engine will behave.
+ Works in close relationswhip with Transmission.
+
+
+
+
+
+
+
Power / Acceleration
+
fInitialDriveForce
+
+
+
+ 0
+
+ 0.5
+
+
+
+
Also called engine power, it dictates the target acceleration the engine is aiming
+ for, measured in
+ G-Forces. Wheel grip may not be able to cope with it, however.
+
+
Gearing modulates this value in ratios to translate it into torque, which is the
+ final acceleration the vehicle will experience.
+
+
Electric vehicles bypass this entirely, starting with x5 torque whith drains to x1 at
+ the top speed.
+
+
+
+
+
+
+
Drive inertia
+
fDriveInertia
+
+
+
+ 0
+
+ 3
+
+
+
The vehicle will go from idle to redline in s assuming full throttle.
+
+
How responsive the engine revs will be to throttle control. It is measured in Higher
+ values will result
+ in faster RPM acceleration and deceleration, while lower values will result in more
+ sluggish RPMs.
+
+
+ Works similar to how a flywheel acts in real-life vehicles, storing drive momentum
+ from the engine. A lighter flywheel can react faster to engine RPM changes with it's
+ lack of weight, while heavier flywheels have higher momentum force which takes more
+ engine power to influence.
+
+
+
+
+
+
+
+
+
Top Speed
+
fInitialDriveMaxFlatVel
+
+
+ 0
+
+ 200
+
+
+
+
+
+ The vehicle's last gear will top at:
+
+ 40 mph.
+
+ 64.37 kph.
+
+
+
+
+
+
Maximum engine top speed. Over this speed, the engine power will degrade greatly.
+
Keep in mind that gearing will stretch over this 'length'.
+
+
+
+
+
+
+
+
+
Transmission
+
Together with the Engine, the transmission settings define the power
+ output of your vehicle.
+
+
+
+
+
+
NΒΊ of Gears
+
nInitialDriveGears
+
+ 0
+
+ 6
+
+
+
Number of gears at stock.
+
+
As gears modulate the fInitialDriveForce up until
+ fInitialDriveMaxFlatVel , keepin a reasonably number of gears for your top
+ speed is reccomended.
+
+
+
Remember Transmission upgrades add one gear total.
+
+
+
+
+
Shift times
+
fClutchChangeRateScaleUpShift
+
+ 0
+
+ 10
+
+
+
The vehicle will take s to shift from gear to gear.
+
+
+
fClutchChangeRateScaleDownShift
+
+ 0
+
+ 3
+
+
+
+
+
+
+
Power Bias
+
fDriveBiasFront
+
+
+ 0
+
+ 1
+
+
+
Defines how the power from fInitialDriveForce is distributed between the
+ axles.
+
0.0 implies a fully RWD setup, with will only deliver power to the rear wheels.
+
+
+
+
+
+
+
+
+
+
+
Brakes
+
Brakes are one of the main defining factors of the performance of your
+ vehicle. This value should complement both the vehicle's power and traction, so as to keep a
+ coherent balance.
+ Unless you have reasons for it not to.
+
+
+
+
+
+
+
Brake Strength
+
fBrakeForce
+
+ 0
+
+ 1
+
+
+
Target deceleration is 60-0 mph in ~ s.
+
How many Gs of deceleration are applied to each wheel.
+
+
+
+ fTractionCurveMax and fBrakeForce are closely related. Assuming
+ perfect balance, a fourth of brake is enough to make each wheel lockup.
+
+
+
+
+
+
+
Brake Bias
+
fBrakeBiasFront
+
+ 0
+
+ 1
+
+
+
+
+
Distribution of the brake strength between the axles.
+
+ Usually, the best balance is between 0.55 and 0.7 for best braking capabilities, as
+ it accounts for the weight transfer that ensues when braking. Vehicles with
+ more body lean typically need more balance to the front.
+
+
+
+
+
+
Handbrake Strength
+
fHandBrakeForce
+
+
+ 0
+
+ 1
+
+
+
Similar to fBrakeForce , but is only applied to the rear axle(s).
+
+
+
+
+
+
+
+
Traction
+
Traction is the main limiter of power and braking ability, and has to be
+ able to handle both; otherwise your vehicle may suffer wheelspin/wheel lock.
+ However, these can also be treated as features and not defects, being part of the vehicle's
+ personality.
+
+
+
+
+
+
Tire Grip
+
fTractionCurveMax
+
+ 0
+
+ 3
+
+
+
+
fTractionCurveMin
+
+ 0
+
+ 3
+
+
+
Maximum and minimum G's the vehicle's wheels are able to pull.
+
+
+ In V, grip is represented as how much the vehicle's tires are able to accelerate or
+ decelerate the car's body, as a whole.
+
+
+ These values are constantly changed around by context, like suspension pressure,
+ surface the tire is driving on, etcetera.
+
+
+
+
+
+
Traction Curve
+
fTractionCurveLateral
+
+ 10
+
+ 30
+
+
+
Slide angle at which the car will enjoy the best grip available.
+
+
+ The vehicle will tend to stay below half of this value.
+
+
+
+ It is reccomended to keep it at default 22.5 on most vehicles, though Sports and
+ Supercars can have it as low as 18ΒΊ if you so desire, making them stay more
+ straight. Over 24ΒΊ is not reccomended save for the slidiest cars, like old muscles.
+
+
+
+
+
+
Tire Grip Bias
+
fTractionBiasFront
+
+ 0
+
+ 1
+
+
+
Grip balance:
+
+
+ This value perfectly describes situations where the front and rear axles have
+ different kinds of wheels, be it the compounds are different, or the tire width is
+ different. Very useful for dragsters and high performance cars who come with
+ changes like these.
+
+
+
+
+
+
+
+
Offroad Traction Loss
+
fTractionLossMult
+
+ 0
+
+ 1.5
+
+
+
How exaggerated the traction loss is for this vehicle.
+
+ A value of 1.0 makes the car lose grip on each surface as expected by the game.
+ Below 1.0 you lose less grip than normal, over 1.0 you lose more grip than normal.
+ High end cars can have up to 1.5 or so before the traction loss of certain
+ surfaces ends up making the car have negative grip.
+
+
+
+
+
+
Low Speed Burnout Mult
+
fLowSpeedTractionLossMult
+
+ 0
+
+ 2
+
+
+
How exaggerated the fake burnout griploss is for this vehicle.
+
+
+
+
+
Max Steer Angle
+
fSteeringLock
+
+ 0
+
+ 50
+
+
+
Maximum steering angle for the vehicle.
+
+
+
+
+
+
+
+
Suspension
+
Stiffer, softer, loose, tight. This section governs how your car
+ floats above its wheels.
+
+
+
+
+
Spring Strength
+
fSuspensionForce
+
+ 0
+
+ 5
+
+
+
Spring strength.
+
+
+
+
+
Spring Dampen Strength
+
fSuspensionCompDamp
+
+ 0
+
+ 3
+
+
+
+
fSuspensionReboundDamp
+
+ 0
+
+ 3
+
+
+
How strongly the spring strength is dampened when compressing or decompressing.
+
+
+
+
+
Compression/Decompression Limits
+
fSuspensionUpperLimit
+
+ 0
+
+ 1
+
+
+
+
fSuspensionLowerLimit
+
+ -1
+
+ 0
+
+
+
Compression upper and lower limits, in meters. Yeah, use centimeters.
+
+
+
+
+
+
+
Suspension Raise
+
fSuspensionRaise
+
+ 0
+
+ 1
+
+
+
In meters, this raises or lowers the natural stance of the vehicle.
+
+
+
+
+
Strength Bias
+
fSuspensionBiasFront
+
+ 0
+
+ 1
+
+
+
Spring strength distribution between the axles of the vehicle.
+
+
+
+
+
+
+
+
Anti-roll Bars
+
The main goal of the Anti-roll Bars is to prevent the car from leaning
+ too much when taking a corner.
+ Do keep in mind that the wheels the cars leans into, receive more grip from the pressure, while
+ the wheels on the other side lose traction.
+
+
+
+
+
Antiroll Strength
+
fAntiRollBarForce
+
+ 0
+
+ 2
+
+
+
How strongly the antiroll bars try to keep the vehicle from leaning.
+
+
+
+
+
Strength Bias
+
fAntiRollBarBiasFront
+
+ 0
+
+ 1
+
+
+
Distribution of the antiroll bar strength between the car axles.
+
+
+
+
+
+
+
+
Rollcentre - Front
+
fRollCentreHeightFront
+
+ 0
+
+ 1
+
+
+
Relative to the model bottom, defines where the pivot point is. This is used for
+ leaning.
+
+
+
+
+
Rollcentre - Back
+
fRollCentreHeightRear
+
+ 0
+
+ 1
+
+
+
Relative to the model bottom, defines where the pivot point is. This is used for
+ leaning.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
FLAGS
+
+
+
+
+
+
+ Handling flags
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Don't edit the values in here. This area serves only
+ as a preview of the file.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Metrics
+
+
+ This editor makes an effort of translating some handling items to real world
+ measurements, depicted in red on them.
+ For transparency and ease of fact-checking, this section documents and details the math
+ used to get the Ingame metrics.
+
+
+
I then just use standard conversions to get the mph
+ equivalents for the editor section.
+
+ Speed is measured in m/s.
+
+
+
+
+
+
+
+ Handling item
+ File metric
+ Ingame metric
+ Math
+
+
+
+
+ fInitialDriveForce
+ Acceleration (G-Forces)
+ Acceleration (G-Forces)
+ fInitialDriveForce * Ratio of Current Gear
+
+
+ fDownforceModifier
+ Multiplier
+ Grip gain (G-Forces)
+ (0 - 0.035) > (0 - 90% of fInitialDriveMaxFlatVel) *
+ fDownforceModifier * NΒΊ of Wheels
+
+
+ fInitialDragCoeff
+ Multiplier
+ Air Resistance (G-Forces)
+ (Speed * (fInitialDragCoeff * 0.0001))^2
+
+
+ fDriveInertia
+ Multiplier
+ Seconds
+ 1 / fDriveInertia
+
+
+ fInitialDriveMaxFlatVel
+ Speed
+ Kilometers per hour
+ fInitialDriveMaxFlatVel / 0.75
+
+
+ fClutchChangeRateScaleUpShift
+ Multiplier
+ Seconds
+ 1 / fClutchChangeRateScaleUpShift
+
+
+ fBrakeForce
+ Decceleration (G-Forces) per wheel
+ Decceleration (G-Forces) per wheel
+ Speed / (fBrakeForce * Number of wheels)
+
+
+
+ Most of the new math has been figured out using SHVDN to script the math ingame, drawing
+ helpful data like torque per wheel from the game memory itself. All has been
+ confirmed ingame, in Singleplayer - feel free to do your own testing.
+
+
+
+
+
+
+
+
+
+
+
+
+
Mass comparer
+
+
+ This simple tool displays the main performance values of any given handlings you feed
+ it, for ease of comparison. It is not related to the rest of the webpage and will not
+ edit the original loaded file.
+
+
+
+
+
+
Drop the files to compare here.
+
+
+ Select the
+ files You can
+ also drop multiple files from OpenIV.
+
+
+
+
+
+
+
+
+
+
+ Click on the table headers to sort by that row.
+
+
+
+
+
+
+
+
+
+
Comparisons
+
+
+
You can add a comparison item for in the graphs by uploading another handling file here.
+
+
+
+ Select a handling.meta
+ file...
+
+
You can drop handling.meta files from OpenIV directly.
+
+
+
+
+
Simplified Stats
+
The main measures, translated to a 0 - 100 score.
+
+
+
+
+
Performance Index
+
+ Current vehicle: $ - Comparison
+ vehicle:
+ $
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file