mirror of
https://github.com/iDisaster/GTAConnected.git
synced 2026-03-08 01:15:23 +00:00
Replaced all skin hash values with verified correct decimal hashes from the List of models hashes reference. This includes GTA IV, TLAD, and TBOGT characters, law enforcement, emergency services, gangs, and civilians.
5452 lines
234 KiB
JavaScript
5452 lines
234 KiB
JavaScript
|
|
// ============================================================================
|
|
// MD REVOLUTION MOD MENU - Client Side
|
|
// Press F5 to open/close menu, Arrow keys to navigate, Enter to select
|
|
// ============================================================================
|
|
|
|
// Menu state
|
|
let menuOpen = false;
|
|
let currentMenu = "main";
|
|
let selectedIndex = 0;
|
|
let menuStack = [];
|
|
let scrollOffset = 0;
|
|
|
|
// ============================================================================
|
|
// CLEAN MODERN UI - Dark Glassmorphism Theme
|
|
// ============================================================================
|
|
const UI = {
|
|
// Background colors
|
|
bgDark: { r: 13, g: 17, b: 23 }, // Deep dark background
|
|
bgPanel: { r: 22, g: 27, b: 34 }, // Panel background
|
|
bgHover: { r: 33, g: 38, b: 45 }, // Hover state
|
|
bgSelected: { r: 45, g: 50, b: 58 }, // Selected item
|
|
|
|
// Accent colors - clean cyan
|
|
accent: { r: 0, g: 212, b: 255 }, // Primary accent (cyan)
|
|
accentDim: { r: 0, g: 150, b: 180 }, // Dimmed accent
|
|
accentGlow: { r: 0, g: 180, b: 220 }, // Glow effect
|
|
|
|
// Text colors
|
|
textPrimary: { r: 230, g: 237, b: 243 }, // Primary text (almost white)
|
|
textSecondary: { r: 139, g: 148, b: 158 }, // Secondary text (gray)
|
|
textMuted: { r: 88, g: 96, b: 105 }, // Muted text
|
|
|
|
// Status colors
|
|
success: { r: 63, g: 185, b: 80 }, // Green for ON
|
|
error: { r: 248, g: 81, b: 73 }, // Red for errors
|
|
warning: { r: 210, g: 153, b: 34 }, // Yellow/gold for warnings
|
|
|
|
// Border colors
|
|
border: { r: 48, g: 54, b: 61 }, // Subtle border
|
|
borderFocus: { r: 0, g: 212, b: 255 }, // Focused border (accent)
|
|
|
|
// Section header
|
|
sectionText: { r: 88, g: 166, b: 255 } // Blue for section headers
|
|
};
|
|
|
|
// ============================================================================
|
|
// CLEAN ANIMATION STATE - Smooth, minimal effects only
|
|
// ============================================================================
|
|
let animTime = 0;
|
|
let smoothScrollY = 0;
|
|
let targetScrollY = 0;
|
|
let menuOpenAnim = 0;
|
|
let screenShake = 0;
|
|
let flashAlpha = 0;
|
|
let currentDescription = "";
|
|
|
|
// Clean animation variables
|
|
let hoverAlpha = 0; // Smooth hover transition
|
|
let selectionAlpha = 0; // Selection highlight alpha
|
|
let smoothSelectedIndex = 0; // Smooth selection position
|
|
|
|
// Glowing scrollbar animation
|
|
let scrollbarGlow = 0; // Pulsing glow intensity for scrollbar
|
|
let scrollbarPulse = 0; // Pulse timer
|
|
|
|
// Selection glow animation
|
|
let selectionGlow = 0; // Pulsing glow for selected item
|
|
let selectionPulseDir = 1; // Pulse direction
|
|
|
|
// Accent line animation
|
|
let accentLineGlow = 0; // Top accent line glow effect
|
|
|
|
// Game feature variables (rainbow car, matrix mode)
|
|
let rainbowHue = 0; // For rainbow car feature
|
|
let matrixEffect = 0; // For matrix mode feature
|
|
|
|
// ============================================================================
|
|
// GTA IV VEHICLE COLOR DATABASE (from carcols.dat)
|
|
// Color ID 0-133 with names and RGB values
|
|
// ============================================================================
|
|
const GTA_IV_COLORS = [
|
|
{ id: 0, name: "Black", r: 0, g: 0, b: 0 },
|
|
{ id: 1, name: "Black 2", r: 1, g: 1, b: 1 },
|
|
{ id: 2, name: "Graphite", r: 15, g: 15, b: 15 },
|
|
{ id: 3, name: "Anthracite", r: 28, g: 28, b: 28 },
|
|
{ id: 4, name: "Dark Gray", r: 38, g: 38, b: 38 },
|
|
{ id: 5, name: "Charcoal", r: 45, g: 45, b: 45 },
|
|
{ id: 6, name: "Stone Gray", r: 54, g: 54, b: 54 },
|
|
{ id: 7, name: "Dolphin Gray", r: 71, g: 71, b: 71 },
|
|
{ id: 8, name: "Light Gray", r: 90, g: 90, b: 90 },
|
|
{ id: 9, name: "Aluminum", r: 100, g: 100, b: 100 },
|
|
{ id: 10, name: "Smoke Gray", r: 116, g: 116, b: 116 },
|
|
{ id: 11, name: "Ash Gray", r: 128, g: 128, b: 128 },
|
|
{ id: 12, name: "Silver", r: 140, g: 140, b: 140 },
|
|
{ id: 13, name: "Pearl Silver", r: 155, g: 155, b: 155 },
|
|
{ id: 14, name: "Light Silver", r: 170, g: 170, b: 170 },
|
|
{ id: 15, name: "Platinum", r: 185, g: 185, b: 185 },
|
|
{ id: 16, name: "Bright Silver", r: 200, g: 200, b: 200 },
|
|
{ id: 17, name: "Off White", r: 220, g: 220, b: 220 },
|
|
{ id: 18, name: "White", r: 255, g: 255, b: 255 },
|
|
{ id: 19, name: "Frost White", r: 245, g: 245, b: 245 },
|
|
{ id: 20, name: "Cream", r: 255, g: 253, b: 245 },
|
|
{ id: 21, name: "Ivory", r: 255, g: 255, b: 240 },
|
|
{ id: 22, name: "Pearl White", r: 240, g: 240, b: 235 },
|
|
{ id: 23, name: "Champagne", r: 230, g: 220, b: 200 },
|
|
{ id: 24, name: "Desert Sand", r: 210, g: 195, b: 170 },
|
|
{ id: 25, name: "Tan", r: 190, g: 170, b: 140 },
|
|
{ id: 26, name: "Dark Tan", r: 160, g: 140, b: 110 },
|
|
{ id: 27, name: "Red", r: 255, g: 0, b: 0 },
|
|
{ id: 28, name: "Dark Red", r: 180, g: 0, b: 0 },
|
|
{ id: 29, name: "Wine Red", r: 140, g: 20, b: 35 },
|
|
{ id: 30, name: "Candy Red", r: 255, g: 50, b: 50 },
|
|
{ id: 31, name: "Torch Red", r: 230, g: 25, b: 25 },
|
|
{ id: 32, name: "Garnet Red", r: 165, g: 30, b: 45 },
|
|
{ id: 33, name: "Maroon", r: 100, g: 20, b: 30 },
|
|
{ id: 34, name: "Burgundy", r: 90, g: 10, b: 25 },
|
|
{ id: 35, name: "Cherry Red", r: 200, g: 10, b: 35 },
|
|
{ id: 36, name: "Orange", r: 255, g: 130, b: 0 },
|
|
{ id: 37, name: "Gold", r: 255, g: 190, b: 0 },
|
|
{ id: 38, name: "Bronze", r: 180, g: 130, b: 70 },
|
|
{ id: 39, name: "Copper", r: 200, g: 120, b: 80 },
|
|
{ id: 40, name: "Sunset Orange", r: 255, g: 100, b: 50 },
|
|
{ id: 41, name: "Tangerine", r: 255, g: 150, b: 50 },
|
|
{ id: 42, name: "Peach", r: 255, g: 200, b: 150 },
|
|
{ id: 43, name: "Light Tan", r: 220, g: 190, b: 160 },
|
|
{ id: 44, name: "Beige", r: 195, g: 175, b: 145 },
|
|
{ id: 45, name: "Brown", r: 110, g: 70, b: 40 },
|
|
{ id: 46, name: "Dark Brown", r: 75, g: 45, b: 25 },
|
|
{ id: 47, name: "Chocolate", r: 65, g: 35, b: 20 },
|
|
{ id: 48, name: "Saddle Brown", r: 130, g: 85, b: 50 },
|
|
{ id: 49, name: "Mocha", r: 95, g: 60, b: 35 },
|
|
{ id: 50, name: "Green", r: 0, g: 180, b: 0 },
|
|
{ id: 51, name: "Racing Green", r: 0, g: 100, b: 50 },
|
|
{ id: 52, name: "Dark Green", r: 0, g: 80, b: 40 },
|
|
{ id: 53, name: "Forest Green", r: 35, g: 90, b: 45 },
|
|
{ id: 54, name: "Hunter Green", r: 25, g: 70, b: 35 },
|
|
{ id: 55, name: "Lime Green", r: 130, g: 255, b: 0 },
|
|
{ id: 56, name: "Seafoam", r: 100, g: 200, b: 150 },
|
|
{ id: 57, name: "Teal", r: 0, g: 130, b: 130 },
|
|
{ id: 58, name: "Turquoise", r: 70, g: 200, b: 180 },
|
|
{ id: 59, name: "Aqua", r: 0, g: 255, b: 200 },
|
|
{ id: 60, name: "Mint Green", r: 150, g: 255, b: 180 },
|
|
{ id: 61, name: "Army Green", r: 80, g: 95, b: 50 },
|
|
{ id: 62, name: "Blue", r: 0, g: 100, b: 255 },
|
|
{ id: 63, name: "Dark Navy", r: 0, g: 20, b: 60 },
|
|
{ id: 64, name: "Dark Blue", r: 0, g: 40, b: 100 },
|
|
{ id: 65, name: "Navy Blue", r: 20, g: 50, b: 120 },
|
|
{ id: 66, name: "Midnight Blue", r: 25, g: 35, b: 80 },
|
|
{ id: 67, name: "Cobalt Blue", r: 0, g: 70, b: 170 },
|
|
{ id: 68, name: "Royal Blue", r: 30, g: 80, b: 180 },
|
|
{ id: 69, name: "Sapphire Blue", r: 15, g: 80, b: 200 },
|
|
{ id: 70, name: "Ocean Blue", r: 50, g: 120, b: 190 },
|
|
{ id: 71, name: "Steel Blue", r: 70, g: 130, b: 180 },
|
|
{ id: 72, name: "Slate Blue", r: 90, g: 110, b: 160 },
|
|
{ id: 73, name: "Light Blue", r: 100, g: 180, b: 255 },
|
|
{ id: 74, name: "Sky Blue", r: 135, g: 200, b: 250 },
|
|
{ id: 75, name: "Baby Blue", r: 175, g: 220, b: 255 },
|
|
{ id: 76, name: "Ice Blue", r: 200, g: 230, b: 255 },
|
|
{ id: 77, name: "Powder Blue", r: 180, g: 210, b: 240 },
|
|
{ id: 78, name: "Cyan", r: 0, g: 255, b: 255 },
|
|
{ id: 79, name: "Electric Blue", r: 0, g: 150, b: 255 },
|
|
{ id: 80, name: "Surf Blue", r: 50, g: 180, b: 220 },
|
|
{ id: 81, name: "Marine Blue", r: 30, g: 90, b: 150 },
|
|
{ id: 82, name: "Bright Blue", r: 0, g: 130, b: 255 },
|
|
{ id: 83, name: "Ultra Blue", r: 30, g: 60, b: 200 },
|
|
{ id: 84, name: "Racing Blue", r: 0, g: 90, b: 200 },
|
|
{ id: 85, name: "Harbor Blue", r: 60, g: 100, b: 140 },
|
|
{ id: 86, name: "Denim Blue", r: 80, g: 100, b: 130 },
|
|
{ id: 87, name: "Petrol Blue", r: 40, g: 70, b: 90 },
|
|
{ id: 88, name: "Galaxy Blue", r: 10, g: 30, b: 80 },
|
|
{ id: 89, name: "Yellow", r: 255, g: 255, b: 0 },
|
|
{ id: 90, name: "Taxi Yellow", r: 250, g: 210, b: 0 },
|
|
{ id: 91, name: "Bright Yellow", r: 255, g: 240, b: 0 },
|
|
{ id: 92, name: "Canary Yellow", r: 255, g: 230, b: 100 },
|
|
{ id: 93, name: "Lemon Yellow", r: 255, g: 250, b: 140 },
|
|
{ id: 94, name: "Flax Yellow", r: 230, g: 220, b: 100 },
|
|
{ id: 95, name: "Mustard", r: 200, g: 170, b: 50 },
|
|
{ id: 96, name: "Dark Yellow", r: 180, g: 160, b: 30 },
|
|
{ id: 97, name: "Sand Yellow", r: 210, g: 200, b: 140 },
|
|
{ id: 98, name: "Olive", r: 120, g: 120, b: 50 },
|
|
{ id: 99, name: "Purple", r: 150, g: 0, b: 200 },
|
|
{ id: 100, name: "Pink", r: 255, g: 150, b: 200 },
|
|
{ id: 101, name: "Hot Pink", r: 255, g: 50, b: 150 },
|
|
{ id: 102, name: "Magenta", r: 255, g: 0, b: 150 },
|
|
{ id: 103, name: "Fuchsia", r: 230, g: 50, b: 130 },
|
|
{ id: 104, name: "Violet", r: 130, g: 50, b: 180 },
|
|
{ id: 105, name: "Plum", r: 100, g: 30, b: 100 },
|
|
{ id: 106, name: "Dark Purple", r: 70, g: 20, b: 90 },
|
|
{ id: 107, name: "Grape", r: 90, g: 40, b: 120 },
|
|
{ id: 108, name: "Lavender", r: 200, g: 180, b: 220 },
|
|
{ id: 109, name: "Lilac", r: 210, g: 175, b: 210 },
|
|
{ id: 110, name: "Orchid", r: 180, g: 130, b: 200 },
|
|
{ id: 111, name: "Mauve", r: 160, g: 130, b: 160 },
|
|
{ id: 112, name: "Salmon", r: 255, g: 150, b: 130 },
|
|
{ id: 113, name: "Coral", r: 255, g: 130, b: 100 },
|
|
{ id: 114, name: "Rose", r: 255, g: 180, b: 180 },
|
|
{ id: 115, name: "Blush", r: 255, g: 200, b: 200 },
|
|
{ id: 116, name: "Light Pink", r: 255, g: 220, b: 230 },
|
|
{ id: 117, name: "Crimson", r: 200, g: 20, b: 60 },
|
|
{ id: 118, name: "Scarlet", r: 230, g: 40, b: 40 },
|
|
{ id: 119, name: "Fire Red", r: 255, g: 65, b: 0 },
|
|
{ id: 120, name: "Vermillion", r: 230, g: 75, b: 50 },
|
|
{ id: 121, name: "Rust", r: 150, g: 70, b: 40 },
|
|
{ id: 122, name: "Terracotta", r: 180, g: 90, b: 60 },
|
|
{ id: 123, name: "Auburn", r: 130, g: 60, b: 50 },
|
|
{ id: 124, name: "Sienna", r: 140, g: 80, b: 55 },
|
|
{ id: 125, name: "Metallic Black", r: 20, g: 25, b: 30 },
|
|
{ id: 126, name: "Metallic Silver", r: 170, g: 175, b: 180 },
|
|
{ id: 127, name: "Metallic Red", r: 200, g: 50, b: 60 },
|
|
{ id: 128, name: "Metallic Blue", r: 50, g: 100, b: 200 },
|
|
{ id: 129, name: "Metallic Green", r: 50, g: 150, b: 80 },
|
|
{ id: 130, name: "Metallic Gold", r: 200, g: 170, b: 80 },
|
|
{ id: 131, name: "Metallic Orange", r: 230, g: 130, b: 50 },
|
|
{ id: 132, name: "Metallic Purple", r: 130, g: 70, b: 170 },
|
|
{ id: 133, name: "Police Blue", r: 40, g: 60, b: 120 }
|
|
];
|
|
|
|
// Vehicle Color Editor State
|
|
let colorEditorActive = false;
|
|
let colorEditorSlot = 0; // 0-3 for Color 1-4
|
|
let colorEditorValues = [0, 0, 0, 0]; // Current color ID for each slot
|
|
|
|
// Get color info by ID
|
|
function getColorById(id) {
|
|
if (id >= 0 && id < GTA_IV_COLORS.length) {
|
|
return GTA_IV_COLORS[id];
|
|
}
|
|
return GTA_IV_COLORS[0]; // Default to black
|
|
}
|
|
|
|
// ============================================================================
|
|
// THEME SYSTEM - Clean Modern Themes
|
|
// ============================================================================
|
|
let currentTheme = "midnight"; // Default theme - Clean dark
|
|
|
|
// Clean modern themes
|
|
const themes = {
|
|
// Midnight - Default clean dark theme with cyan accent
|
|
midnight: {
|
|
primary: { r: 22, g: 27, b: 34 },
|
|
accent: { r: 0, g: 212, b: 255 },
|
|
highlight: { r: 0, g: 212, b: 255 },
|
|
name: "Midnight"
|
|
},
|
|
ocean: { primary: { r: 20, g: 30, b: 48 }, accent: { r: 59, g: 130, b: 246 }, highlight: { r: 96, g: 165, b: 250 }, name: "Ocean" },
|
|
emerald: { primary: { r: 20, g: 30, b: 25 }, accent: { r: 16, g: 185, b: 129 }, highlight: { r: 52, g: 211, b: 153 }, name: "Emerald" },
|
|
rose: { primary: { r: 30, g: 20, b: 25 }, accent: { r: 244, g: 63, b: 94 }, highlight: { r: 251, g: 113, b: 133 }, name: "Rose" },
|
|
amber: { primary: { r: 30, g: 25, b: 18 }, accent: { r: 245, g: 158, b: 11 }, highlight: { r: 251, g: 191, b: 36 }, name: "Amber" },
|
|
violet: { primary: { r: 25, g: 20, b: 35 }, accent: { r: 139, g: 92, b: 246 }, highlight: { r: 167, g: 139, b: 250 }, name: "Violet" },
|
|
slate: { primary: { r: 30, g: 32, b: 36 }, accent: { r: 148, g: 163, b: 184 }, highlight: { r: 203, g: 213, b: 225 }, name: "Slate" },
|
|
crimson: { primary: { r: 28, g: 18, b: 18 }, accent: { r: 239, g: 68, b: 68 }, highlight: { r: 248, g: 113, b: 113 }, name: "Crimson" }
|
|
};
|
|
|
|
function getTheme() {
|
|
return themes[currentTheme] || themes.midnight;
|
|
}
|
|
|
|
// Info bar configuration
|
|
let infoBar = {
|
|
height: 50
|
|
};
|
|
|
|
// Item descriptions
|
|
const itemDescriptions = {
|
|
// Player Options
|
|
"player_god": "⚡ Makes you invincible - you won't take any damage from bullets, explosions, or falls!",
|
|
"player_noragdoll": "🛡️ Prevents your character from falling down when hit - stay on your feet always!",
|
|
"player_superrun": "🏃 Gives you super-speed running abilities - run faster than normal!",
|
|
"player_neverwanted": "🚫 Removes wanted levels permanently - police won't chase you!",
|
|
"player_addhealth": "💊 Restores your health to maximum - heals all injuries!",
|
|
"player_addarmor": "🛡️ Adds full armor protection - bulletproof vest equipped!",
|
|
"player_wanted": "⭐ Set your wanted level from 0-6 stars - more stars = more police!",
|
|
"player_teleport": "📍 Instantly teleport to any location on the map!",
|
|
|
|
// Vehicle Options
|
|
"spawn_vehicle": "🚗 Spawn any vehicle instantly - appears right next to you!",
|
|
"veh_repair": "🔧 Fully repairs your current vehicle to 100% condition!",
|
|
"veh_flip": "🔄 Flips your vehicle upright if it's upside down!",
|
|
"veh_delete": "💥 Removes all vehicles you spawned - clean up the streets!",
|
|
"veh_color": "🎨 Changes your vehicle's primary and secondary colors!",
|
|
"veh_god": "🛡️ Makes your current vehicle indestructible - no damage!",
|
|
"veh_neverdirty": "🧼 Keeps your vehicle permanently clean and shiny!",
|
|
"veh_nitro": "🚀 Gives your vehicle instant speed boost - hold accelerator!",
|
|
"veh_rainbow": "🌈 Makes your vehicle cycle through rainbow colors!",
|
|
"veh_neon": "💡 Adds neon underglow effects to your vehicle!",
|
|
"veh_rpg": "💥 Shoots RPG rockets from your vehicle - explosive ammo!",
|
|
"veh_ghost": "👻 Vehicle becomes ghost - pass through everything!",
|
|
"veh_flip": "🔄 Quick vehicle flip - gets you back on track instantly!",
|
|
"veh_repair": "🔧 Instant full repair - no more engine damage!",
|
|
"veh_siren": "🚔 Activates police sirens and emergency lights!",
|
|
|
|
// Weapon Options
|
|
"weapon_all": "🔫 Gives you every weapon in the game - full arsenal!",
|
|
"weapon_remove": "🚫 Removes all weapons from your inventory - go peaceful!",
|
|
"weapon_rpg": "💥 Gives RPG with unlimited explosive rockets!",
|
|
"weapon_sniper": "🔭 Gives you a powerful sniper rifle for long-range kills!",
|
|
"weapon_shotgun": "🔫 Gives you a shotgun - devastating close range damage!",
|
|
"weapon_machine": "🔫 Gives you a machine gun - rapid fire automatic weapon!",
|
|
"weapon_pistol": "🔫 Gives you a pistol - reliable sidearm weapon!",
|
|
"weapon_explosive": "💣 Makes all your weapons explosive - bullets explode on impact!",
|
|
|
|
// World Options
|
|
"world_time": "🕐 Changes time of day - set any hour from midnight to midnight!",
|
|
"world_weather": "🌤 Changes weather conditions - control rain, sun, storms!",
|
|
"world_clear_area": "🧹 Clears all objects, vehicles, and peds around you!",
|
|
"world_clear_vehicles": "🚗 Removes all vehicles in the area - clean streets!",
|
|
"world_clear_peds": "🚶 Removes all pedestrians in the area - empty streets!",
|
|
"world_clear_objects": "📦 Removes all objects in the area - clean environment!",
|
|
"world_traffic": "🚦 Enables/disables traffic - control city vehicle flow!",
|
|
"world_traffic_density": "📊 Controls how many vehicles spawn - from empty to heavy traffic!",
|
|
"world_timecycle": "🎨 Applies visual time effects - day/night/cinematic filters!",
|
|
"sky_color": "🌈 Changes sky color to different atmospheric effects!",
|
|
|
|
// Fun Options
|
|
"fun_launch": "🚀 Launches you high into the sky - super jump effect!",
|
|
"fun_superjump": "🦘 Gives you massive jumping ability - reach rooftops easily!",
|
|
"fun_ragdoll": "🤸 Makes your character go limp and fall - ragdoll physics!",
|
|
"fun_explode": "💥 Creates explosion at your position - destructive fun!",
|
|
"fun_random_explosion": "💣 Spawns random explosions nearby - chaos mode!",
|
|
"fun_car_rain": "🚗 Makes vehicles rain from the sky - vehicle shower!",
|
|
"fun_ped_invasion": "👥 Spawns many pedestrians around you - crowd control!",
|
|
"fun_gang": "👨👩👧👦 Creates a gang of armed NPCs - backup crew!",
|
|
"fun_army": "⚔️ Deploys military forces around you - army protection!",
|
|
"fun_money_rain": "💰 Makes money pickups fall from the sky - get rich!",
|
|
"fun_weapon_shower": "🔫 Drops weapons from above - weapon rain effect!",
|
|
"fun_screen_shake": "📳 Shakes the screen violently - earthquake effect!",
|
|
"fun_flash": "💡 Creates bright camera flash - temporary blindness!",
|
|
"fun_drunk": "🍺 Makes screen wobble - drunk vision effect!",
|
|
"fun_matrix": "💻 Matrix digital rain effect - sci-fi visual!",
|
|
"fun_thermal": "🌡️ Thermal vision mode - see heat signatures!",
|
|
"fun_night": "🌙 Night vision mode - see clearly in darkness!",
|
|
|
|
// Toggle States
|
|
"godMode": "🛡️ GOD MODE - Complete invincibility and unlimited health!",
|
|
"invincible": "⚡ INVINCIBLE - Cannot be killed or damaged!",
|
|
"superRun": "🏃 SUPER SPEED - Run 3x faster than normal!",
|
|
"noRagdoll": "🚶 NO RAGDOLL - Never fall down, always stay standing!",
|
|
"neverWanted": "🚫 NEVER WANTED - Police ignore you completely!",
|
|
"vehGodMode": "🛡️ VEHICLE GOD MODE - Your car can't be destroyed!",
|
|
"driveOnWater": "🌊 DRIVE ON WATER - Cars float and work on water!",
|
|
"rainbowCar": "🌈 RAINBOW CAR - Vehicle cycles through all colors!",
|
|
"neonLights": "💡 NEON LIGHTS - Glowing underglow effects!",
|
|
"flyMode": "✈️ FLY MODE - Move freely through the air!",
|
|
"vehShootRPG": "💥 VEHICLE ROCKETS - Your car shoots explosive ammo!",
|
|
"rainbowSky": "🌈 RAINBOW SKY - Sky cycles through rainbow colors!",
|
|
"explosiveAmmo": "💣 EXPLOSIVE AMMO - All bullets create explosions!",
|
|
"moonGravity": "🌙 MOON GRAVITY - Low gravity physics, super jumps!",
|
|
"drunkMode": "🍺 DRUNK MODE - Screen wobbles and controls reversed!",
|
|
"matrixMode": "💻 MATRIX MODE - Digital rain effect overlay!",
|
|
"thermalVision": "🌡️ THERMAL VISION - See heat signatures in dark!",
|
|
"nightVision": "🌙 NIGHT VISION - Enhanced vision in darkness!",
|
|
|
|
// Vehicle Color descriptions
|
|
"veh_color_slot": "🎨 Set individual color slot - customize each color independently!",
|
|
"veh_color_1": "🎨 PRIMARY COLOR - Main body color of the vehicle!",
|
|
"veh_color_2": "🎨 SECONDARY COLOR - Accent/trim color of the vehicle!",
|
|
"veh_color_3": "🎨 TERTIARY COLOR - Additional color detail!",
|
|
"veh_color_4": "🎨 QUATERNARY COLOR - Extra color option!",
|
|
"color_slot_edit": "🎨 Use LEFT/RIGHT arrows to change color (0-133 from GTA IV carcols.dat)",
|
|
"color_preset": "🎨 Apply preset colors to all 4 vehicle color slots instantly!",
|
|
|
|
// Vehicle Upgrade descriptions
|
|
"veh_upgrade_add": "🔧 Add upgrade - Enhance your vehicle with this mod!",
|
|
"veh_upgrade_remove": "❌ Remove upgrade - Take off this modification!",
|
|
"veh_upgrade_remove_all": "🗑️ REMOVE ALL - Strip all upgrades from vehicle!",
|
|
"veh_upgrades": "🔧 UPGRADES - Add NOS, hydraulics, wheels, spoilers and more!",
|
|
|
|
// Handling Editor descriptions
|
|
"handling_reset": "🔄 RESET - Restore all handling values to vehicle defaults!",
|
|
"handling_preset": "⚡ PRESET - Apply a pre-configured handling setup instantly!",
|
|
"handling_set": "🎛️ ADJUST - Modify this handling parameter value!",
|
|
"handling_traction_max": "🏎️ TRACTION MAX - Maximum grip your tyres can achieve!",
|
|
"handling_traction_min": "🏎️ TRACTION MIN - Minimum grip when sliding or braking!",
|
|
"handling_traction_bias": "⚖️ TRACTION BIAS - Front vs rear grip distribution!",
|
|
"handling_traction_loss": "💨 TRACTION LOSS - How easily tyres lose grip!",
|
|
"handling_susp_force": "🔧 SUSPENSION FORCE - Spring stiffness for bumps!",
|
|
"handling_susp_height": "📏 SUSPENSION HEIGHT - Ride height of the vehicle!",
|
|
"handling_susp_damp": "🎯 DAMPING - Suspension bounce control!",
|
|
"handling_power": "⚡ ENGINE POWER - Acceleration and torque!",
|
|
"handling_speed": "🚀 TOP SPEED - Maximum velocity limit!",
|
|
"handling_brake": "🛑 BRAKE FORCE - Stopping power of brakes!",
|
|
"handling_mass": "⚖️ MASS - Vehicle weight affects grip and momentum!",
|
|
"handling_com": "🎯 CENTER OF MASS - Affects roll and flip tendency!",
|
|
"handling_steering": "🎮 STEERING - How far wheels can turn!"
|
|
};
|
|
|
|
// Font for text rendering (will be loaded on resource start)
|
|
let menuFont = null;
|
|
let titleFont = null;
|
|
|
|
// Menu dimensions in pixels - positioned on the right side
|
|
const menu = {
|
|
x: 920,
|
|
y: 80,
|
|
width: 420,
|
|
headerHeight: 85,
|
|
itemHeight: 48,
|
|
footerHeight: 45,
|
|
maxVisibleItems: 12
|
|
};
|
|
|
|
// Player list cache
|
|
let playerList = [];
|
|
|
|
// Vehicle handling values
|
|
let handlingMods = {
|
|
grip: 1.0,
|
|
acceleration: 1.0,
|
|
topSpeed: 1.0,
|
|
braking: 1.0
|
|
};
|
|
|
|
// ============================================================================
|
|
// LAYER B: PHYSICS EMULATION SYSTEM
|
|
// All handling effects are achieved through velocity/turnVelocity manipulation
|
|
// This bypasses the need for real handling.dat editing which is not supported
|
|
// ============================================================================
|
|
let physicsEmulation = {
|
|
// Grip/Traction Assist State
|
|
gripAssistEnabled: true,
|
|
gripAssistStrength: 1.0, // 0-2, higher = more correction
|
|
|
|
// Acceleration Boost State
|
|
accelBoostEnabled: false,
|
|
accelBoostMultiplier: 1.0, // 1.0 = normal, 2.0 = double
|
|
maxSpeedLimit: 60.0, // m/s (~216 km/h)
|
|
|
|
// Braking State
|
|
brakeAssistEnabled: true,
|
|
brakeMultiplier: 1.0, // 1.0 = normal, 2.0 = stronger
|
|
|
|
// Stability/Anti-Roll State
|
|
stabilityEnabled: true,
|
|
stabilityStrength: 1.0, // How much to dampen rotation
|
|
antiRollStrength: 0.5, // How much to resist flipping
|
|
|
|
// Previous frame values for calculations
|
|
lastVelocity: null,
|
|
lastHeading: 0,
|
|
lastSpeed: 0,
|
|
lastSteerInput: 0,
|
|
|
|
// Input detection (estimated from velocity changes)
|
|
isAccelerating: false,
|
|
isBraking: false,
|
|
isSteering: false,
|
|
steerDirection: 0 // -1 left, 0 center, 1 right
|
|
};
|
|
|
|
// ============================================================================
|
|
// HANDLING EDITOR SYSTEM
|
|
// ============================================================================
|
|
let handlingEditorActive = false;
|
|
let handlingEditorAnim = 0;
|
|
let handlingValues = {
|
|
// Traction & Grip
|
|
tractionCurveMax: 2.0,
|
|
tractionCurveMin: 1.8,
|
|
tractionBias: 0.5,
|
|
// Suspension
|
|
suspensionForce: 2.0,
|
|
suspensionCompDamp: 1.0,
|
|
suspensionReboundDamp: 1.2,
|
|
suspensionUpperLimit: 0.12,
|
|
suspensionLowerLimit: -0.10,
|
|
suspensionRaise: 0.0,
|
|
// Engine & Speed
|
|
driveForce: 0.25,
|
|
driveInertia: 1.0,
|
|
initialDriveMaxVel: 200.0,
|
|
brakeForce: 0.8,
|
|
// Mass & Weight
|
|
mass: 1500.0,
|
|
centreOfMassZ: 0.0,
|
|
// Handling feel
|
|
steeringLock: 35.0,
|
|
tractionLoss: 0.8
|
|
};
|
|
|
|
// Store original values for reset
|
|
let originalHandlingValues = null;
|
|
|
|
// Visual feedback colors based on values
|
|
let tyreGlowColors = { fr: {r:0,g:255,b:100}, fl: {r:0,g:255,b:100}, rr: {r:0,g:255,b:100}, rl: {r:0,g:255,b:100} };
|
|
let engineGlow = { r: 255, g: 100, b: 0, intensity: 0 };
|
|
let brakeGlow = { r: 255, g: 50, b: 0, intensity: 0 };
|
|
let suspensionOffset = 0;
|
|
let carRotationAngle = 0;
|
|
|
|
// Currently selected handling parameter for highlighting
|
|
let selectedHandlingParam = "";
|
|
|
|
// ============================================================================
|
|
// MENU DATA STRUCTURE
|
|
// ============================================================================
|
|
|
|
const menuData = {
|
|
main: {
|
|
title: "MD REVOLUTION",
|
|
items: [
|
|
{ label: "Self Options", action: "submenu", target: "self" },
|
|
{ label: "Vehicle Spawner", action: "submenu", target: "vehicles" },
|
|
{ label: "Vehicle Options", action: "submenu", target: "vehicleOptions" },
|
|
{ label: "Network Options", action: "submenu", target: "network" },
|
|
{ label: "Teleport Locations", action: "submenu", target: "teleport" },
|
|
{ label: "World Options", action: "submenu", target: "world" },
|
|
{ label: "Weapons", action: "submenu", target: "weapons" },
|
|
{ label: "Fun Options", action: "submenu", target: "fun" },
|
|
{ label: "--- Settings ---", action: "none" },
|
|
{ label: "Mod Menu Theme", action: "submenu", target: "themes" }
|
|
]
|
|
},
|
|
|
|
themes: {
|
|
title: "MENU THEME",
|
|
items: [
|
|
{ label: "Midnight (Default)", action: "theme", value: "midnight" },
|
|
{ label: "Ocean", action: "theme", value: "ocean" },
|
|
{ label: "Emerald", action: "theme", value: "emerald" },
|
|
{ label: "Rose", action: "theme", value: "rose" },
|
|
{ label: "Amber", action: "theme", value: "amber" },
|
|
{ label: "Violet", action: "theme", value: "violet" },
|
|
{ label: "Slate", action: "theme", value: "slate" },
|
|
{ label: "Crimson", action: "theme", value: "crimson" }
|
|
]
|
|
},
|
|
|
|
self: {
|
|
title: "SELF OPTIONS",
|
|
items: [
|
|
{ label: "Restore Health", action: "self_health" },
|
|
{ label: "Restore Armor", action: "self_armor" },
|
|
{ label: "Max Health & Armor", action: "self_max" },
|
|
{ label: "Give All Weapons", action: "self_weapons" },
|
|
{ label: "Clear Wanted Level", action: "self_wanted" },
|
|
{ label: "--- Toggles ---", action: "none" },
|
|
{ label: "God Mode", action: "toggle", target: "godMode", state: false },
|
|
{ label: "Invincible", action: "toggle", target: "invincible", state: false },
|
|
{ label: "Super Run", action: "toggle", target: "superRun", state: false },
|
|
{ label: "No Ragdoll", action: "toggle", target: "noRagdoll", state: false },
|
|
{ label: "Never Wanted", action: "toggle", target: "neverWanted", state: false },
|
|
{ label: "Invisible", action: "toggle", target: "invisible", state: false },
|
|
{ label: "Infinite Sprint", action: "toggle", target: "infiniteSprint", state: false },
|
|
{ label: "Freeze Position", action: "toggle", target: "freezePlayer", state: false },
|
|
{ label: "--- Actions ---", action: "none" },
|
|
{ label: "Set On Fire", action: "self_fire" },
|
|
{ label: "Clear Tasks", action: "self_cleartasks" },
|
|
{ label: "Respawn", action: "self_respawn" },
|
|
{ label: "Suicide", action: "self_suicide" },
|
|
{ label: "Change Skin", action: "submenu", target: "skins" }
|
|
]
|
|
},
|
|
|
|
skins: {
|
|
title: "PLAYER SKINS",
|
|
items: [
|
|
{ label: "Story Characters", action: "submenu", target: "skins_story" },
|
|
{ label: "TLAD Characters", action: "submenu", target: "skins_tlad" },
|
|
{ label: "TBOGT Characters", action: "submenu", target: "skins_tbogt" },
|
|
{ label: "Law Enforcement", action: "submenu", target: "skins_cops" },
|
|
{ label: "Emergency Services", action: "submenu", target: "skins_emergency" },
|
|
{ label: "Gang Members", action: "submenu", target: "skins_gangs" },
|
|
{ label: "Male Civilians", action: "submenu", target: "skins_male" },
|
|
{ label: "Female Civilians", action: "submenu", target: "skins_female" },
|
|
{ label: "Workers & Staff", action: "submenu", target: "skins_workers" },
|
|
{ label: "Special NPCs", action: "submenu", target: "skins_special" },
|
|
{ label: "--- Random Skins ---", action: "none" },
|
|
{ label: "Random Skin", action: "skin_random" },
|
|
{ label: "Random Male", action: "skin_random_male" },
|
|
{ label: "Random Female", action: "skin_random_female" }
|
|
]
|
|
},
|
|
|
|
skins_story: {
|
|
title: "STORY CHARACTERS",
|
|
items: [
|
|
{ label: "--- Family & Friends ---", action: "none" },
|
|
{ label: "Roman Bellic", action: "skin", value: 2302238665 },
|
|
{ label: "Mallorie Bardas", action: "skin", value: 3254679890 },
|
|
{ label: "Little Jacob", action: "skin", value: 1487004273 },
|
|
{ label: "Brucie Kibbutz", action: "skin", value: 2564987168 },
|
|
{ label: "Packie McReary", action: "skin", value: 1690783035 },
|
|
{ label: "Dwayne Forge", action: "skin", value: 3677703193 },
|
|
{ label: "--- Crime Bosses ---", action: "none" },
|
|
{ label: "Playboy X", action: "skin", value: 1794146792 },
|
|
{ label: "Dimitri Rascalov", action: "skin", value: 237497537 },
|
|
{ label: "Mikhail Faustin", action: "skin", value: 57218969 },
|
|
{ label: "Ray Bulgarin", action: "skin", value: 237511807 },
|
|
{ label: "Jimmy Pegorino", action: "skin", value: 1884668464 },
|
|
{ label: "Phil Bell", action: "skin", value: 2468508362 },
|
|
{ label: "Ray Boccino", action: "skin", value: 954215094 },
|
|
{ label: "--- McReary Family ---", action: "none" },
|
|
{ label: "Derrick McReary", action: "skin", value: 1169442297 },
|
|
{ label: "Francis McReary", action: "skin", value: 1710545037 },
|
|
{ label: "Gerald McReary", action: "skin", value: 652098186 },
|
|
{ label: "Kate McReary", action: "skin", value: 3521216458 },
|
|
{ label: "--- Other Characters ---", action: "none" },
|
|
{ label: "Bernie Crane", action: "skin", value: 1500493064 },
|
|
{ label: "Manny Escuela", action: "skin", value: 1445589009 },
|
|
{ label: "Elizabeta Torres", action: "skin", value: 2933135023 },
|
|
{ label: "Michelle", action: "skin", value: 3214308084 },
|
|
{ label: "Vlad Glebov", action: "skin", value: 896408642 },
|
|
{ label: "Ilyena Faustin", action: "skin", value: 3459742170 },
|
|
{ label: "Hossan Ramzy", action: "skin", value: 980768434 }
|
|
]
|
|
},
|
|
|
|
skins_tlad: {
|
|
title: "TLAD CHARACTERS",
|
|
items: [
|
|
{ label: "--- Main Characters ---", action: "none" },
|
|
{ label: "Johnny Klebitz", action: "skin", value: 8206123 },
|
|
{ label: "Jim Fitzgerald", action: "skin", value: 870892404 },
|
|
{ label: "Terry Thorpe", action: "skin", value: 1728056212 },
|
|
{ label: "Clay Simons", action: "skin", value: 1825562762 },
|
|
{ label: "Brian Jeremy", action: "skin", value: 349841464 },
|
|
{ label: "Billy Grey", action: "skin", value: 3843248439 },
|
|
{ label: "--- Lost MC Members ---", action: "none" },
|
|
{ label: "Jason Michaels", action: "skin", value: 3346030785 },
|
|
{ label: "Angus Martin", action: "skin", value: 1917871822 },
|
|
{ label: "Dave Grossman", action: "skin", value: 3056906300 },
|
|
{ label: "Lost MC Biker 1", action: "skin", value: 1914397972 },
|
|
{ label: "Lost MC Biker 2", action: "skin", value: 2156528113 },
|
|
{ label: "--- Other TLAD ---", action: "none" },
|
|
{ label: "Ashley Butler", action: "skin", value: 3567004438 },
|
|
{ label: "Thomas Stubbs", action: "skin", value: 2513523815 },
|
|
{ label: "Ray Boccino (TLAD)", action: "skin", value: 954215094 }
|
|
]
|
|
},
|
|
|
|
skins_tbogt: {
|
|
title: "TBOGT CHARACTERS",
|
|
items: [
|
|
{ label: "--- Main Characters ---", action: "none" },
|
|
{ label: "Luis Lopez", action: "skin", value: 3802496606 },
|
|
{ label: "Tony Prince", action: "skin", value: 4020398429 },
|
|
{ label: "Yusuf Amir", action: "skin", value: 3846796161 },
|
|
{ label: "Armando Torres", action: "skin", value: 1370299619 },
|
|
{ label: "Henrique Bardas", action: "skin", value: 1905515841 },
|
|
{ label: "--- Antagonists ---", action: "none" },
|
|
{ label: "Ray Bulgarin (TBOGT)", action: "skin", value: 243666427 },
|
|
{ label: "Rocco Pelosi", action: "skin", value: 3381042378 },
|
|
{ label: "Timur", action: "skin", value: 2345614827 },
|
|
{ label: "Mori Kibbutz", action: "skin", value: 1662225612 },
|
|
{ label: "--- Club Characters ---", action: "none" },
|
|
{ label: "Dessie", action: "skin", value: 2848083183 },
|
|
{ label: "Joni", action: "skin", value: 3412908435 },
|
|
{ label: "Troy", action: "skin", value: 1662473323 },
|
|
{ label: "Maisonette Bouncer", action: "skin", value: 2514268405 },
|
|
{ label: "--- Celebrities ---", action: "none" },
|
|
{ label: "Evan Moss", action: "skin", value: 3497746837 },
|
|
{ label: "Kerry McIntosh", action: "skin", value: 763838720 },
|
|
{ label: "Poppy Mitchell", action: "skin", value: 3450748540 },
|
|
{ label: "Cloe Parker", action: "skin", value: 2802928488 }
|
|
]
|
|
},
|
|
|
|
skins_cops: {
|
|
title: "LAW ENFORCEMENT",
|
|
items: [
|
|
{ label: "--- LCPD ---", action: "none" },
|
|
{ label: "LCPD Officer", action: "skin", value: 4111764146 },
|
|
{ label: "LCPD Officer 2", action: "skin", value: 4111764146 },
|
|
{ label: "LCPD Fat Cop", action: "skin", value: 3924571768 },
|
|
{ label: "LCPD Traffic Cop", action: "skin", value: 2776029317 },
|
|
{ label: "LCPD Detective", action: "skin", value: 3295460374 },
|
|
{ label: "--- NOOSE ---", action: "none" },
|
|
{ label: "NOOSE Officer", action: "skin", value: 3290204350 },
|
|
{ label: "NOOSE Tactical", action: "skin", value: 3290204350 },
|
|
{ label: "NOOSE Commander", action: "skin", value: 3290204350 },
|
|
{ label: "--- FIB ---", action: "none" },
|
|
{ label: "FIB Agent", action: "skin", value: 3295460374 },
|
|
{ label: "FIB Agent 2", action: "skin", value: 3295460374 },
|
|
{ label: "--- Prison ---", action: "none" },
|
|
{ label: "Prison Guard", action: "skin", value: 2378673688 },
|
|
{ label: "Prison Guard 2", action: "skin", value: 2378673688 },
|
|
{ label: "--- Security ---", action: "none" },
|
|
{ label: "Security Guard", action: "skin", value: 2423978125 },
|
|
{ label: "Bank Security", action: "skin", value: 2423978125 },
|
|
{ label: "Club Bouncer", action: "skin", value: 2514268405 }
|
|
]
|
|
},
|
|
|
|
skins_emergency: {
|
|
title: "EMERGENCY SERVICES",
|
|
items: [
|
|
{ label: "--- Fire Department ---", action: "none" },
|
|
{ label: "Firefighter", action: "skin", value: 3684742681 },
|
|
{ label: "Firefighter 2", action: "skin", value: 3684742681 },
|
|
{ label: "Fire Chief", action: "skin", value: 610888851 },
|
|
{ label: "--- Medical ---", action: "none" },
|
|
{ label: "Paramedic", action: "skin", value: 3119890080 },
|
|
{ label: "Paramedic 2", action: "skin", value: 3119890080 },
|
|
{ label: "Doctor", action: "skin", value: 3108026518 },
|
|
{ label: "Nurse", action: "skin", value: 346338575 },
|
|
{ label: "Nurse 2", action: "skin", value: 3101188907 },
|
|
{ label: "Surgeon", action: "skin", value: 219393781 },
|
|
{ label: "--- Coast Guard ---", action: "none" },
|
|
{ label: "Coast Guard", action: "skin", value: 4111764146 },
|
|
{ label: "Lifeguard", action: "skin", value: 62496225 }
|
|
]
|
|
},
|
|
|
|
skins_gangs: {
|
|
title: "GANG MEMBERS",
|
|
items: [
|
|
{ label: "--- Russian Mafia ---", action: "none" },
|
|
{ label: "Russian Thug 1", action: "skin", value: 2206803240 },
|
|
{ label: "Russian Thug 2", action: "skin", value: 1976502708 },
|
|
{ label: "Russian Thug 3", action: "skin", value: 1543404628 },
|
|
{ label: "--- Italian Mafia ---", action: "none" },
|
|
{ label: "Italian Mobster 1", action: "skin", value: 2892525257 },
|
|
{ label: "Italian Mobster 2", action: "skin", value: 3381042378 },
|
|
{ label: "Mafia Suit", action: "skin", value: 2666550233 },
|
|
{ label: "--- Irish Mob ---", action: "none" },
|
|
{ label: "Irish Thug 1", action: "skin", value: 1690783035 },
|
|
{ label: "Irish Thug 2", action: "skin", value: 62496225 },
|
|
{ label: "--- Albanian Mob ---", action: "none" },
|
|
{ label: "Albanian 1", action: "skin", value: 3791037286 },
|
|
{ label: "Albanian 2", action: "skin", value: 4059382627 },
|
|
{ label: "--- Jamaican Gang ---", action: "none" },
|
|
{ label: "Jamaican 1", action: "skin", value: 1487004273 },
|
|
{ label: "Jamaican 2", action: "skin", value: 2794569427 },
|
|
{ label: "Jamaican 3", action: "skin", value: 3413608606 },
|
|
{ label: "--- Street Gangs ---", action: "none" },
|
|
{ label: "Spanish Lord 1", action: "skin", value: 492147228 },
|
|
{ label: "Spanish Lord 2", action: "skin", value: 2368926169 },
|
|
{ label: "Biker Gang", action: "skin", value: 2156528113 },
|
|
{ label: "Lost MC Member", action: "skin", value: 1914397972 },
|
|
{ label: "Angels of Death", action: "skin", value: 226415164 },
|
|
{ label: "--- Triads ---", action: "none" },
|
|
{ label: "Triad 1", action: "skin", value: 3210959519 },
|
|
{ label: "Triad 2", action: "skin", value: 4130031670 }
|
|
]
|
|
},
|
|
|
|
skins_male: {
|
|
title: "MALE CIVILIANS",
|
|
items: [
|
|
{ label: "--- Business ---", action: "none" },
|
|
{ label: "Businessman 1", action: "skin", value: 1530937394 },
|
|
{ label: "Businessman 2", action: "skin", value: 690281432 },
|
|
{ label: "Lawyer", action: "skin", value: 2441990607 },
|
|
{ label: "Executive", action: "skin", value: 3658388177 },
|
|
{ label: "Banker", action: "skin", value: 465237040 },
|
|
{ label: "--- Casual ---", action: "none" },
|
|
{ label: "Casual Guy 1", action: "skin", value: 62496225 },
|
|
{ label: "Casual Guy 2", action: "skin", value: 523785438 },
|
|
{ label: "Hipster", action: "skin", value: 2085884255 },
|
|
{ label: "College Student", action: "skin", value: 1207402441 },
|
|
{ label: "Jogger", action: "skin", value: 2392208684 },
|
|
{ label: "--- Street ---", action: "none" },
|
|
{ label: "Street Guy 1", action: "skin", value: 62496225 },
|
|
{ label: "Street Guy 2", action: "skin", value: 523785438 },
|
|
{ label: "Hobo 1", action: "skin", value: 3214294247 },
|
|
{ label: "Hobo 2", action: "skin", value: 4152580634 },
|
|
{ label: "Bum", action: "skin", value: 3214294247 },
|
|
{ label: "--- Ethnic ---", action: "none" },
|
|
{ label: "African American 1", action: "skin", value: 2794569427 },
|
|
{ label: "African American 2", action: "skin", value: 3677703193 },
|
|
{ label: "Hispanic 1", action: "skin", value: 1370299619 },
|
|
{ label: "Hispanic 2", action: "skin", value: 1905515841 },
|
|
{ label: "Asian 1", action: "skin", value: 757349871 },
|
|
{ label: "Asian 2", action: "skin", value: 3145223654 },
|
|
{ label: "--- Senior ---", action: "none" },
|
|
{ label: "Old Man 1", action: "skin", value: 3862373481 },
|
|
{ label: "Old Man 2", action: "skin", value: 2034185905 },
|
|
{ label: "Retired", action: "skin", value: 243672348 }
|
|
]
|
|
},
|
|
|
|
skins_female: {
|
|
title: "FEMALE CIVILIANS",
|
|
items: [
|
|
{ label: "--- Business ---", action: "none" },
|
|
{ label: "Business Woman 1", action: "skin", value: 453889158 },
|
|
{ label: "Business Woman 2", action: "skin", value: 155063868 },
|
|
{ label: "Secretary", action: "skin", value: 394310337 },
|
|
{ label: "Executive Woman", action: "skin", value: 4010737771 },
|
|
{ label: "--- Casual ---", action: "none" },
|
|
{ label: "Casual Woman 1", action: "skin", value: 3394344139 },
|
|
{ label: "Casual Woman 2", action: "skin", value: 286007875 },
|
|
{ label: "Hipster Girl", action: "skin", value: 761763258 },
|
|
{ label: "College Girl", action: "skin", value: 1473654742 },
|
|
{ label: "Jogger Woman", action: "skin", value: 1350216795 },
|
|
{ label: "--- Street ---", action: "none" },
|
|
{ label: "Street Woman 1", action: "skin", value: 3394344139 },
|
|
{ label: "Street Woman 2", action: "skin", value: 1290755317 },
|
|
{ label: "Homeless Woman", action: "skin", value: 3214294247 },
|
|
{ label: "--- Fashion ---", action: "none" },
|
|
{ label: "Rich Woman", action: "skin", value: 2514581497 },
|
|
{ label: "Shop Girl", action: "skin", value: 1586287288 },
|
|
{ label: "Model", action: "skin", value: 3450748540 },
|
|
{ label: "Socialite", action: "skin", value: 1182843182 },
|
|
{ label: "Tourist Woman", action: "skin", value: 1754440500 },
|
|
{ label: "--- Party ---", action: "none" },
|
|
{ label: "Club Girl 1", action: "skin", value: 3412908435 },
|
|
{ label: "Club Girl 2", action: "skin", value: 3450748540 },
|
|
{ label: "Party Girl", action: "skin", value: 2802928488 },
|
|
{ label: "--- Senior ---", action: "none" },
|
|
{ label: "Old Woman 1", action: "skin", value: 4091134002 },
|
|
{ label: "Old Woman 2", action: "skin", value: 100706569 }
|
|
]
|
|
},
|
|
|
|
skins_workers: {
|
|
title: "WORKERS & STAFF",
|
|
items: [
|
|
{ label: "--- Construction ---", action: "none" },
|
|
{ label: "Construction Worker 1", action: "skin", value: 3572947498 },
|
|
{ label: "Construction Worker 2", action: "skin", value: 3279009568 },
|
|
{ label: "Hard Hat Worker", action: "skin", value: 3580746516 },
|
|
{ label: "--- Service ---", action: "none" },
|
|
{ label: "Janitor", action: "skin", value: 3010919736 },
|
|
{ label: "Mechanic", action: "skin", value: 3938062777 },
|
|
{ label: "Gas Station Attendant", action: "skin", value: 420915580 },
|
|
{ label: "--- Food Service ---", action: "none" },
|
|
{ label: "Fast Food Worker", action: "skin", value: 989485 },
|
|
{ label: "Cluckin Bell Worker", action: "skin", value: 3283436873 },
|
|
{ label: "Burger Shot Worker", action: "skin", value: 989485 },
|
|
{ label: "Chef", action: "skin", value: 1878085135 },
|
|
{ label: "--- Retail ---", action: "none" },
|
|
{ label: "Store Clerk", action: "skin", value: 357919731 },
|
|
{ label: "Vendor", action: "skin", value: 4108853339 },
|
|
{ label: "--- Transport ---", action: "none" },
|
|
{ label: "Taxi Driver", action: "skin", value: 8772846 },
|
|
{ label: "Bus Driver", action: "skin", value: 134077503 },
|
|
{ label: "Pilot", action: "skin", value: 2349798414 },
|
|
{ label: "--- Delivery ---", action: "none" },
|
|
{ label: "Delivery Guy", action: "skin", value: 2923833437 },
|
|
{ label: "Mail Carrier", action: "skin", value: 4010604433 }
|
|
]
|
|
},
|
|
|
|
skins_special: {
|
|
title: "SPECIAL NPCS",
|
|
items: [
|
|
{ label: "--- Entertainers ---", action: "none" },
|
|
{ label: "Street Performer", action: "skin", value: 3106721027 },
|
|
{ label: "Mime", action: "skin", value: 4230734264 },
|
|
{ label: "Statue of Happiness Performer", action: "skin", value: 62496225 },
|
|
{ label: "--- Sports ---", action: "none" },
|
|
{ label: "Baseball Player", action: "skin", value: 3495737411 },
|
|
{ label: "Golfer", action: "skin", value: 3915732450 },
|
|
{ label: "--- Beach ---", action: "none" },
|
|
{ label: "Beach Bum", action: "skin", value: 3214294247 },
|
|
{ label: "Swimmer", action: "skin", value: 2392208684 },
|
|
{ label: "--- Military ---", action: "none" },
|
|
{ label: "Army Soldier", action: "skin", value: 3290204350 },
|
|
{ label: "Military Officer", action: "skin", value: 3290204350 },
|
|
{ label: "--- Unique ---", action: "none" },
|
|
{ label: "Clown", action: "skin", value: 4230734264 },
|
|
{ label: "Mascot", action: "skin", value: 62496225 },
|
|
{ label: "Santa Claus", action: "skin", value: 3862373481 },
|
|
{ label: "--- Multiplayer ---", action: "none" },
|
|
{ label: "MP Female", action: "skin", value: 3653091386 }
|
|
]
|
|
},
|
|
|
|
vehicles: {
|
|
title: "VEHICLE SPAWNER",
|
|
items: [
|
|
{ label: "Sports Cars", action: "submenu", target: "veh_sports" },
|
|
{ label: "Muscle Cars", action: "submenu", target: "veh_muscle" },
|
|
{ label: "Sedans", action: "submenu", target: "veh_sedans" },
|
|
{ label: "SUVs & Trucks", action: "submenu", target: "veh_suv" },
|
|
{ label: "Compacts", action: "submenu", target: "veh_compacts" },
|
|
{ label: "Motorcycles", action: "submenu", target: "veh_bikes" },
|
|
{ label: "Emergency", action: "submenu", target: "veh_emergency" },
|
|
{ label: "Aircraft", action: "submenu", target: "veh_aircraft" },
|
|
{ label: "Boats", action: "submenu", target: "veh_boats" },
|
|
{ label: "Commercial", action: "submenu", target: "veh_commercial" },
|
|
{ label: "Special", action: "submenu", target: "veh_special" },
|
|
{ label: "Taxis", action: "submenu", target: "veh_taxis" },
|
|
{ label: "--- Vehicle Options ---", action: "none" },
|
|
{ label: "Delete My Vehicles", action: "vehicle_delete" },
|
|
{ label: "Repair Current", action: "veh_repair" },
|
|
{ label: "Flip Current", action: "veh_flip" }
|
|
]
|
|
},
|
|
|
|
veh_sports: {
|
|
title: "SPORTS CARS",
|
|
items: [
|
|
{ label: "Infernus", action: "spawn_vehicle", value: "infernus" },
|
|
{ label: "Turismo", action: "spawn_vehicle", value: "turismo" },
|
|
{ label: "Comet", action: "spawn_vehicle", value: "comet" },
|
|
{ label: "Banshee", action: "spawn_vehicle", value: "banshee" },
|
|
{ label: "Sultan", action: "spawn_vehicle", value: "sultan" },
|
|
{ label: "Coquette", action: "spawn_vehicle", value: "coquette" },
|
|
{ label: "Super GT", action: "spawn_vehicle", value: "super gt" },
|
|
{ label: "Sultan RS", action: "spawn_vehicle", value: "sultan rs" }
|
|
]
|
|
},
|
|
|
|
veh_muscle: {
|
|
title: "MUSCLE CARS",
|
|
items: [
|
|
{ label: "Buccaneer", action: "spawn_vehicle", value: "buccaneer" },
|
|
{ label: "Dukes", action: "spawn_vehicle", value: "dukes" },
|
|
{ label: "Faction", action: "spawn_vehicle", value: "faction" },
|
|
{ label: "Ruiner", action: "spawn_vehicle", value: "ruiner" },
|
|
{ label: "Sabre", action: "spawn_vehicle", value: "sabre" },
|
|
{ label: "Sabre GT", action: "spawn_vehicle", value: "sabregt" },
|
|
{ label: "Vigero", action: "spawn_vehicle", value: "vigero" },
|
|
{ label: "Phoenix", action: "spawn_vehicle", value: "phoenix" }
|
|
]
|
|
},
|
|
|
|
veh_suv: {
|
|
title: "SUVs & TRUCKS",
|
|
items: [
|
|
{ label: "Patriot", action: "spawn_vehicle", value: "patriot" },
|
|
{ label: "Cavalcade", action: "spawn_vehicle", value: "cavalcade" },
|
|
{ label: "Huntley", action: "spawn_vehicle", value: "huntley" },
|
|
{ label: "Landstalker", action: "spawn_vehicle", value: "landstalker" },
|
|
{ label: "Rancher", action: "spawn_vehicle", value: "rancher" },
|
|
{ label: "Bobcat", action: "spawn_vehicle", value: "bobcat" }
|
|
]
|
|
},
|
|
|
|
veh_bikes: {
|
|
title: "MOTORCYCLES",
|
|
items: [
|
|
{ label: "NRG 900", action: "spawn_vehicle", value: "nrg900" },
|
|
{ label: "PCJ 600", action: "spawn_vehicle", value: "pcj" },
|
|
{ label: "Sanchez", action: "spawn_vehicle", value: "sanchez" },
|
|
{ label: "Faggio", action: "spawn_vehicle", value: "faggio" },
|
|
{ label: "Freeway", action: "spawn_vehicle", value: "freeway" }
|
|
]
|
|
},
|
|
|
|
veh_emergency: {
|
|
title: "EMERGENCY",
|
|
items: [
|
|
{ label: "Police Cruiser", action: "spawn_vehicle", value: "police" },
|
|
{ label: "Police Cruiser 2", action: "spawn_vehicle", value: "police2" },
|
|
{ label: "FBI Car", action: "spawn_vehicle", value: "fbi" },
|
|
{ label: "NOOSE Cruiser", action: "spawn_vehicle", value: "noose" },
|
|
{ label: "Ambulance", action: "spawn_vehicle", value: "ambulance" },
|
|
{ label: "Fire Truck", action: "spawn_vehicle", value: "firetruk" },
|
|
{ label: "Taxi", action: "spawn_vehicle", value: "taxi" },
|
|
{ label: "Stretch Limo", action: "spawn_vehicle", value: "stretch" }
|
|
]
|
|
},
|
|
|
|
veh_aircraft: {
|
|
title: "AIRCRAFT",
|
|
items: [
|
|
{ label: "Annihilator", action: "spawn_vehicle", value: "annihilator" },
|
|
{ label: "Maverick", action: "spawn_vehicle", value: "maverick" },
|
|
{ label: "Police Maverick", action: "spawn_vehicle", value: "polmav" },
|
|
{ label: "Tour Maverick", action: "spawn_vehicle", value: "tourmav" }
|
|
]
|
|
},
|
|
|
|
veh_boats: {
|
|
title: "BOATS",
|
|
items: [
|
|
{ label: "Dinghy", action: "spawn_vehicle", value: "dinghy" },
|
|
{ label: "Jetmax", action: "spawn_vehicle", value: "jetmax" },
|
|
{ label: "Marquis", action: "spawn_vehicle", value: "marquis" },
|
|
{ label: "Predator", action: "spawn_vehicle", value: "predator" },
|
|
{ label: "Reefer", action: "spawn_vehicle", value: "reefer" },
|
|
{ label: "Squalo", action: "spawn_vehicle", value: "squalo" },
|
|
{ label: "Tropic", action: "spawn_vehicle", value: "tropic" },
|
|
{ label: "Tuga", action: "spawn_vehicle", value: "tuga" }
|
|
]
|
|
},
|
|
|
|
veh_sedans: {
|
|
title: "SEDANS",
|
|
items: [
|
|
{ label: "Admiral", action: "spawn_vehicle", value: "admiral" },
|
|
{ label: "Cavalcade", action: "spawn_vehicle", value: "cavalcade" },
|
|
{ label: "Cognoscenti", action: "spawn_vehicle", value: "cognoscenti" },
|
|
{ label: "Emperor", action: "spawn_vehicle", value: "emperor" },
|
|
{ label: "Esperanto", action: "spawn_vehicle", value: "esperanto" },
|
|
{ label: "Feroci", action: "spawn_vehicle", value: "feroci" },
|
|
{ label: "Feltzer", action: "spawn_vehicle", value: "feltzer" },
|
|
{ label: "Intruder", action: "spawn_vehicle", value: "intruder" },
|
|
{ label: "Landstalker", action: "spawn_vehicle", value: "landstalker" },
|
|
{ label: "Lokus", action: "spawn_vehicle", value: "lokus" },
|
|
{ label: "Marbella", action: "spawn_vehicle", value: "marbella" },
|
|
{ label: "Merit", action: "spawn_vehicle", value: "merit" },
|
|
{ label: "Oracle", action: "spawn_vehicle", value: "oracle" },
|
|
{ label: "Pinnacle", action: "spawn_vehicle", value: "pinnacle" },
|
|
{ label: "Premiere", action: "spawn_vehicle", value: "premiere" },
|
|
{ label: "Presidente", action: "spawn_vehicle", value: "presidente" },
|
|
{ label: "Primo", action: "spawn_vehicle", value: "primo" },
|
|
{ label: "Rebla", action: "spawn_vehicle", value: "rebla" },
|
|
{ label: "Romero", action: "spawn_vehicle", value: "romero" },
|
|
{ label: "Schafter", action: "spawn_vehicle", value: "schafter" },
|
|
{ label: "Sentinel", action: "spawn_vehicle", value: "sentinel" },
|
|
{ label: "Solair", action: "spawn_vehicle", value: "solair" },
|
|
{ label: "Stratum", action: "spawn_vehicle", value: "stratum" },
|
|
{ label: "Stretch", action: "spawn_vehicle", value: "stretch" },
|
|
{ label: "Vincent", action: "spawn_vehicle", value: "vincent" },
|
|
{ label: "Virgo", action: "spawn_vehicle", value: "virgo" },
|
|
{ label: "Willard", action: "spawn_vehicle", value: "willard" },
|
|
{ label: "Washington", action: "spawn_vehicle", value: "washington" }
|
|
]
|
|
},
|
|
|
|
veh_compacts: {
|
|
title: "COMPACTS",
|
|
items: [
|
|
{ label: "Blista", action: "spawn_vehicle", value: "blista" },
|
|
{ label: "Futo", action: "spawn_vehicle", value: "futo" },
|
|
{ label: "Ingot", action: "spawn_vehicle", value: "ingot" },
|
|
{ label: "PMP 600", action: "spawn_vehicle", value: "pmp 600" },
|
|
{ label: "Sultan RS", action: "spawn_vehicle", value: "sultan rs" }
|
|
]
|
|
},
|
|
|
|
veh_commercial: {
|
|
title: "COMMERCIAL",
|
|
items: [
|
|
{ label: "Airtug", action: "spawn_vehicle", value: "airtug" },
|
|
{ label: "Benson", action: "spawn_vehicle", value: "benson" },
|
|
{ label: "Biff", action: "spawn_vehicle", value: "biff" },
|
|
{ label: "Boxville", action: "spawn_vehicle", value: "boxville" },
|
|
{ label: "Burrito", action: "spawn_vehicle", value: "burrito" },
|
|
{ label: "Burrito 2", action: "spawn_vehicle", value: "burrito2" },
|
|
{ label: "Cabby", action: "spawn_vehicle", value: "cabby" },
|
|
{ label: "Flatbed", action: "spawn_vehicle", value: "flatbed" },
|
|
{ label: "Forklift", action: "spawn_vehicle", value: "forklift" },
|
|
{ label: "Mule", action: "spawn_vehicle", value: "mule" },
|
|
{ label: "Packer", action: "spawn_vehicle", value: "packer" },
|
|
{ label: "Pony", action: "spawn_vehicle", value: "pony" },
|
|
{ label: "Speedo", action: "spawn_vehicle", value: "speedo" },
|
|
{ label: "Stockade", action: "spawn_vehicle", value: "stockade" },
|
|
{ label: "Trashmaster", action: "spawn_vehicle", value: "trashmaster" },
|
|
{ label: "Yankee", action: "spawn_vehicle", value: "yankee" }
|
|
]
|
|
},
|
|
|
|
veh_special: {
|
|
title: "SPECIAL VEHICLES",
|
|
items: [
|
|
{ label: "Mr Tasty", action: "spawn_vehicle", value: "mr tasty" },
|
|
{ label: "Cablecar", action: "spawn_vehicle", value: "cablecar" },
|
|
{ label: "Subway", action: "spawn_vehicle", value: "subway" },
|
|
{ label: "El Train", action: "spawn_vehicle", value: "el train" }
|
|
]
|
|
},
|
|
|
|
veh_taxis: {
|
|
title: "TAXIS",
|
|
items: [
|
|
{ label: "Taxi", action: "spawn_vehicle", value: "taxi" },
|
|
{ label: "Taxi 2", action: "spawn_vehicle", value: "taxi2" },
|
|
{ label: "Roman's Taxi", action: "spawn_vehicle", value: "roman's taxi" }
|
|
]
|
|
},
|
|
|
|
vehicleOptions: {
|
|
title: "VEHICLE OPTIONS",
|
|
items: [
|
|
{ label: "Repair Vehicle", action: "veh_repair" },
|
|
{ label: "Flip Vehicle", action: "veh_flip" },
|
|
{ label: "Vehicle Colors", action: "submenu", target: "veh_colors" },
|
|
{ label: "Vehicle Upgrades", action: "submenu", target: "veh_upgrades" },
|
|
{ label: "Handling Editor", action: "submenu", target: "handlingEditor" },
|
|
{ label: "--- Toggles ---", action: "none" },
|
|
{ label: "God Mode", action: "toggle", target: "vehGodMode", state: false },
|
|
{ label: "Nitro Boost", action: "veh_nitro" },
|
|
{ label: "Drive On Water", action: "toggle", target: "driveOnWater", state: false },
|
|
{ label: "Rainbow Color", action: "toggle", target: "rainbowCar", state: false },
|
|
{ label: "Neon Lights", action: "submenu", target: "veh_neons" },
|
|
{ label: "Fly Mode", action: "toggle", target: "flyMode", state: false },
|
|
{ label: "Shoot RPG", action: "toggle", target: "vehShootRPG", state: false },
|
|
{ label: "--- Quick Actions ---", action: "none" },
|
|
{ label: "Lock Doors", action: "veh_lock" },
|
|
{ label: "Unlock Doors", action: "veh_unlock" },
|
|
{ label: "Toggle Engine", action: "veh_engine" },
|
|
{ label: "Pop Tires", action: "veh_poptires" },
|
|
{ label: "Fix Tires", action: "veh_fixtires" }
|
|
]
|
|
},
|
|
|
|
veh_upgrades: {
|
|
title: "VEHICLE UPGRADES",
|
|
items: [
|
|
{ label: "--- Performance ---", action: "none" },
|
|
{ label: "Add Nitro", action: "veh_upgrade_add", value: 1010 },
|
|
{ label: "Add Hydraulics", action: "veh_upgrade_add", value: 1087 },
|
|
{ label: "--- Wheels ---", action: "none" },
|
|
{ label: "Offroad Wheels", action: "veh_upgrade_add", value: 1025 },
|
|
{ label: "Shadow Wheels", action: "veh_upgrade_add", value: 1073 },
|
|
{ label: "Mega Wheels", action: "veh_upgrade_add", value: 1074 },
|
|
{ label: "Rimshine Wheels", action: "veh_upgrade_add", value: 1075 },
|
|
{ label: "Wires Wheels", action: "veh_upgrade_add", value: 1076 },
|
|
{ label: "Classic Wheels", action: "veh_upgrade_add", value: 1077 },
|
|
{ label: "Twist Wheels", action: "veh_upgrade_add", value: 1078 },
|
|
{ label: "Cutter Wheels", action: "veh_upgrade_add", value: 1079 },
|
|
{ label: "Switch Wheels", action: "veh_upgrade_add", value: 1080 },
|
|
{ label: "Grove Wheels", action: "veh_upgrade_add", value: 1081 },
|
|
{ label: "Import Wheels", action: "veh_upgrade_add", value: 1082 },
|
|
{ label: "Dollar Wheels", action: "veh_upgrade_add", value: 1083 },
|
|
{ label: "--- Exhausts ---", action: "none" },
|
|
{ label: "Upswept Exhaust", action: "veh_upgrade_add", value: 1018 },
|
|
{ label: "Twin Exhaust", action: "veh_upgrade_add", value: 1019 },
|
|
{ label: "Large Exhaust", action: "veh_upgrade_add", value: 1020 },
|
|
{ label: "Medium Exhaust", action: "veh_upgrade_add", value: 1021 },
|
|
{ label: "Small Exhaust", action: "veh_upgrade_add", value: 1022 },
|
|
{ label: "--- Spoilers ---", action: "none" },
|
|
{ label: "Pro Spoiler", action: "veh_upgrade_add", value: 1000 },
|
|
{ label: "Win Spoiler", action: "veh_upgrade_add", value: 1001 },
|
|
{ label: "Drag Spoiler", action: "veh_upgrade_add", value: 1002 },
|
|
{ label: "Alpha Spoiler", action: "veh_upgrade_add", value: 1003 },
|
|
{ label: "Champ Spoiler", action: "veh_upgrade_add", value: 1014 },
|
|
{ label: "Fury Spoiler", action: "veh_upgrade_add", value: 1015 },
|
|
{ label: "--- Roof ---", action: "none" },
|
|
{ label: "Roof Scoop", action: "veh_upgrade_add", value: 1006 },
|
|
{ label: "Roof Vent", action: "veh_upgrade_add", value: 1032 },
|
|
{ label: "Alien Roof Vent", action: "veh_upgrade_add", value: 1033 },
|
|
{ label: "--- Side Skirts ---", action: "none" },
|
|
{ label: "Right Alien Skirt", action: "veh_upgrade_add", value: 1007 },
|
|
{ label: "Left Alien Skirt", action: "veh_upgrade_add", value: 1017 },
|
|
{ label: "Right Chrome Skirt", action: "veh_upgrade_add", value: 1027 },
|
|
{ label: "Left Chrome Skirt", action: "veh_upgrade_add", value: 1031 },
|
|
{ label: "--- Remove Upgrades ---", action: "none" },
|
|
{ label: "Remove Nitro", action: "veh_upgrade_remove", value: 1010 },
|
|
{ label: "Remove Hydraulics", action: "veh_upgrade_remove", value: 1087 },
|
|
{ label: "Remove All Upgrades", action: "veh_upgrade_remove_all" }
|
|
]
|
|
},
|
|
|
|
handlingEditor: {
|
|
title: "HANDLING EDITOR",
|
|
items: [
|
|
{ label: "--- Presets ---", action: "none" },
|
|
{ label: "Reset to Default", action: "handling_reset" },
|
|
{ label: "Race Setup", action: "handling_preset", value: "race" },
|
|
{ label: "Off-Road Setup", action: "handling_preset", value: "offroad" },
|
|
{ label: "Low Rider", action: "handling_preset", value: "lowrider" },
|
|
{ label: "--- Traction & Grip ---", action: "none" },
|
|
{ label: "Traction Max", action: "submenu", target: "handling_traction_max" },
|
|
{ label: "Traction Min", action: "submenu", target: "handling_traction_min" },
|
|
{ label: "Traction Bias (F/R)", action: "submenu", target: "handling_traction_bias" },
|
|
{ label: "Traction Loss", action: "submenu", target: "handling_traction_loss" },
|
|
{ label: "--- Suspension ---", action: "none" },
|
|
{ label: "Suspension Force", action: "submenu", target: "handling_susp_force" },
|
|
{ label: "Suspension Height", action: "submenu", target: "handling_susp_height" },
|
|
{ label: "Suspension Damping", action: "submenu", target: "handling_susp_damp" },
|
|
{ label: "--- Engine & Speed ---", action: "none" },
|
|
{ label: "Engine Power", action: "submenu", target: "handling_power" },
|
|
{ label: "Top Speed", action: "submenu", target: "handling_speed" },
|
|
{ label: "Brake Force", action: "submenu", target: "handling_brake" },
|
|
{ label: "--- Weight & Balance ---", action: "none" },
|
|
{ label: "Vehicle Mass", action: "submenu", target: "handling_mass" },
|
|
{ label: "Center of Mass", action: "submenu", target: "handling_com" },
|
|
{ label: "--- Steering ---", action: "none" },
|
|
{ label: "Steering Lock", action: "submenu", target: "handling_steering" }
|
|
]
|
|
},
|
|
|
|
handling_traction_max: {
|
|
title: "TRACTION MAX",
|
|
items: [
|
|
{ label: "Ice (0.5)", action: "handling_set", param: "tractionCurveMax", value: 0.5 },
|
|
{ label: "Very Low (1.0)", action: "handling_set", param: "tractionCurveMax", value: 1.0 },
|
|
{ label: "Low (1.5)", action: "handling_set", param: "tractionCurveMax", value: 1.5 },
|
|
{ label: "Default (2.0)", action: "handling_set", param: "tractionCurveMax", value: 2.0 },
|
|
{ label: "Medium (2.5)", action: "handling_set", param: "tractionCurveMax", value: 2.5 },
|
|
{ label: "High (3.0)", action: "handling_set", param: "tractionCurveMax", value: 3.0 },
|
|
{ label: "Very High (3.5)", action: "handling_set", param: "tractionCurveMax", value: 3.5 },
|
|
{ label: "Racing Slicks (4.0)", action: "handling_set", param: "tractionCurveMax", value: 4.0 },
|
|
{ label: "Glue (5.0)", action: "handling_set", param: "tractionCurveMax", value: 5.0 }
|
|
]
|
|
},
|
|
|
|
handling_traction_min: {
|
|
title: "TRACTION MIN",
|
|
items: [
|
|
{ label: "Ice (0.3)", action: "handling_set", param: "tractionCurveMin", value: 0.3 },
|
|
{ label: "Very Low (0.8)", action: "handling_set", param: "tractionCurveMin", value: 0.8 },
|
|
{ label: "Low (1.2)", action: "handling_set", param: "tractionCurveMin", value: 1.2 },
|
|
{ label: "Default (1.8)", action: "handling_set", param: "tractionCurveMin", value: 1.8 },
|
|
{ label: "Medium (2.2)", action: "handling_set", param: "tractionCurveMin", value: 2.2 },
|
|
{ label: "High (2.8)", action: "handling_set", param: "tractionCurveMin", value: 2.8 },
|
|
{ label: "Very High (3.5)", action: "handling_set", param: "tractionCurveMin", value: 3.5 }
|
|
]
|
|
},
|
|
|
|
handling_traction_bias: {
|
|
title: "TRACTION BIAS",
|
|
items: [
|
|
{ label: "Full Front (0.0)", action: "handling_set", param: "tractionBias", value: 0.0 },
|
|
{ label: "Front Heavy (0.3)", action: "handling_set", param: "tractionBias", value: 0.3 },
|
|
{ label: "Balanced (0.5)", action: "handling_set", param: "tractionBias", value: 0.5 },
|
|
{ label: "Rear Heavy (0.7)", action: "handling_set", param: "tractionBias", value: 0.7 },
|
|
{ label: "Full Rear (1.0)", action: "handling_set", param: "tractionBias", value: 1.0 }
|
|
]
|
|
},
|
|
|
|
handling_traction_loss: {
|
|
title: "TRACTION LOSS",
|
|
items: [
|
|
{ label: "No Loss (0.0)", action: "handling_set", param: "tractionLoss", value: 0.0 },
|
|
{ label: "Minimal (0.4)", action: "handling_set", param: "tractionLoss", value: 0.4 },
|
|
{ label: "Default (0.8)", action: "handling_set", param: "tractionLoss", value: 0.8 },
|
|
{ label: "Loose (1.2)", action: "handling_set", param: "tractionLoss", value: 1.2 },
|
|
{ label: "Ice Mode (2.0)", action: "handling_set", param: "tractionLoss", value: 2.0 }
|
|
]
|
|
},
|
|
|
|
handling_susp_force: {
|
|
title: "SUSPENSION FORCE",
|
|
items: [
|
|
{ label: "Soft (0.5)", action: "handling_set", param: "suspensionForce", value: 0.5 },
|
|
{ label: "Comfort (1.0)", action: "handling_set", param: "suspensionForce", value: 1.0 },
|
|
{ label: "Default (2.0)", action: "handling_set", param: "suspensionForce", value: 2.0 },
|
|
{ label: "Firm (3.0)", action: "handling_set", param: "suspensionForce", value: 3.0 },
|
|
{ label: "Sport (4.0)", action: "handling_set", param: "suspensionForce", value: 4.0 },
|
|
{ label: "Race (5.0)", action: "handling_set", param: "suspensionForce", value: 5.0 },
|
|
{ label: "Rock Solid (8.0)", action: "handling_set", param: "suspensionForce", value: 8.0 }
|
|
]
|
|
},
|
|
|
|
handling_susp_height: {
|
|
title: "SUSPENSION HEIGHT",
|
|
items: [
|
|
{ label: "Slammed (-0.15)", action: "handling_set", param: "suspensionRaise", value: -0.15 },
|
|
{ label: "Low (-0.10)", action: "handling_set", param: "suspensionRaise", value: -0.10 },
|
|
{ label: "Lowered (-0.05)", action: "handling_set", param: "suspensionRaise", value: -0.05 },
|
|
{ label: "Default (0.0)", action: "handling_set", param: "suspensionRaise", value: 0.0 },
|
|
{ label: "Raised (0.05)", action: "handling_set", param: "suspensionRaise", value: 0.05 },
|
|
{ label: "High (0.10)", action: "handling_set", param: "suspensionRaise", value: 0.10 },
|
|
{ label: "Off-Road (0.15)", action: "handling_set", param: "suspensionRaise", value: 0.15 },
|
|
{ label: "Monster (0.25)", action: "handling_set", param: "suspensionRaise", value: 0.25 }
|
|
]
|
|
},
|
|
|
|
handling_susp_damp: {
|
|
title: "SUSPENSION DAMPING",
|
|
items: [
|
|
{ label: "Bouncy (0.3)", action: "handling_set", param: "suspensionCompDamp", value: 0.3 },
|
|
{ label: "Soft (0.6)", action: "handling_set", param: "suspensionCompDamp", value: 0.6 },
|
|
{ label: "Default (1.0)", action: "handling_set", param: "suspensionCompDamp", value: 1.0 },
|
|
{ label: "Sport (1.5)", action: "handling_set", param: "suspensionCompDamp", value: 1.5 },
|
|
{ label: "Race (2.0)", action: "handling_set", param: "suspensionCompDamp", value: 2.0 },
|
|
{ label: "Stiff (3.0)", action: "handling_set", param: "suspensionCompDamp", value: 3.0 }
|
|
]
|
|
},
|
|
|
|
handling_power: {
|
|
title: "ENGINE POWER",
|
|
items: [
|
|
{ label: "Weak (0.10)", action: "handling_set", param: "driveForce", value: 0.10 },
|
|
{ label: "Low (0.18)", action: "handling_set", param: "driveForce", value: 0.18 },
|
|
{ label: "Default (0.25)", action: "handling_set", param: "driveForce", value: 0.25 },
|
|
{ label: "Tuned (0.35)", action: "handling_set", param: "driveForce", value: 0.35 },
|
|
{ label: "Sport (0.45)", action: "handling_set", param: "driveForce", value: 0.45 },
|
|
{ label: "Super (0.60)", action: "handling_set", param: "driveForce", value: 0.60 },
|
|
{ label: "Hyper (0.80)", action: "handling_set", param: "driveForce", value: 0.80 },
|
|
{ label: "Rocket (1.20)", action: "handling_set", param: "driveForce", value: 1.20 }
|
|
]
|
|
},
|
|
|
|
handling_speed: {
|
|
title: "TOP SPEED",
|
|
items: [
|
|
{ label: "Slow (100)", action: "handling_set", param: "initialDriveMaxVel", value: 100.0 },
|
|
{ label: "City (140)", action: "handling_set", param: "initialDriveMaxVel", value: 140.0 },
|
|
{ label: "Default (200)", action: "handling_set", param: "initialDriveMaxVel", value: 200.0 },
|
|
{ label: "Fast (250)", action: "handling_set", param: "initialDriveMaxVel", value: 250.0 },
|
|
{ label: "Super (300)", action: "handling_set", param: "initialDriveMaxVel", value: 300.0 },
|
|
{ label: "Hyper (400)", action: "handling_set", param: "initialDriveMaxVel", value: 400.0 },
|
|
{ label: "Insane (500)", action: "handling_set", param: "initialDriveMaxVel", value: 500.0 }
|
|
]
|
|
},
|
|
|
|
handling_brake: {
|
|
title: "BRAKE FORCE",
|
|
items: [
|
|
{ label: "Weak (0.3)", action: "handling_set", param: "brakeForce", value: 0.3 },
|
|
{ label: "Low (0.5)", action: "handling_set", param: "brakeForce", value: 0.5 },
|
|
{ label: "Default (0.8)", action: "handling_set", param: "brakeForce", value: 0.8 },
|
|
{ label: "Strong (1.2)", action: "handling_set", param: "brakeForce", value: 1.2 },
|
|
{ label: "Race (1.6)", action: "handling_set", param: "brakeForce", value: 1.6 },
|
|
{ label: "Ceramic (2.0)", action: "handling_set", param: "brakeForce", value: 2.0 },
|
|
{ label: "Instant Stop (3.0)", action: "handling_set", param: "brakeForce", value: 3.0 }
|
|
]
|
|
},
|
|
|
|
handling_mass: {
|
|
title: "VEHICLE MASS",
|
|
items: [
|
|
{ label: "Feather (500)", action: "handling_set", param: "mass", value: 500.0 },
|
|
{ label: "Light (1000)", action: "handling_set", param: "mass", value: 1000.0 },
|
|
{ label: "Default (1500)", action: "handling_set", param: "mass", value: 1500.0 },
|
|
{ label: "Heavy (2000)", action: "handling_set", param: "mass", value: 2000.0 },
|
|
{ label: "Truck (3000)", action: "handling_set", param: "mass", value: 3000.0 },
|
|
{ label: "Tank (5000)", action: "handling_set", param: "mass", value: 5000.0 }
|
|
]
|
|
},
|
|
|
|
handling_com: {
|
|
title: "CENTER OF MASS",
|
|
items: [
|
|
{ label: "Very Low (-0.3)", action: "handling_set", param: "centreOfMassZ", value: -0.3 },
|
|
{ label: "Low (-0.15)", action: "handling_set", param: "centreOfMassZ", value: -0.15 },
|
|
{ label: "Default (0.0)", action: "handling_set", param: "centreOfMassZ", value: 0.0 },
|
|
{ label: "High (0.15)", action: "handling_set", param: "centreOfMassZ", value: 0.15 },
|
|
{ label: "Very High (0.3)", action: "handling_set", param: "centreOfMassZ", value: 0.3 }
|
|
]
|
|
},
|
|
|
|
handling_steering: {
|
|
title: "STEERING LOCK",
|
|
items: [
|
|
{ label: "Tight (25)", action: "handling_set", param: "steeringLock", value: 25.0 },
|
|
{ label: "Default (35)", action: "handling_set", param: "steeringLock", value: 35.0 },
|
|
{ label: "Loose (45)", action: "handling_set", param: "steeringLock", value: 45.0 },
|
|
{ label: "Wide (55)", action: "handling_set", param: "steeringLock", value: 55.0 },
|
|
{ label: "Extreme (70)", action: "handling_set", param: "steeringLock", value: 70.0 }
|
|
]
|
|
},
|
|
|
|
veh_neons: {
|
|
title: "NEON LIGHTS",
|
|
items: [
|
|
{ label: "Toggle Neons", action: "toggle", target: "neonLights", state: false },
|
|
{ label: "Red Neons", action: "neon_color", value: { r: 255, g: 0, b: 0 } },
|
|
{ label: "Blue Neons", action: "neon_color", value: { r: 0, g: 100, b: 255 } },
|
|
{ label: "Green Neons", action: "neon_color", value: { r: 0, g: 255, b: 0 } },
|
|
{ label: "Purple Neons", action: "neon_color", value: { r: 255, g: 0, b: 255 } },
|
|
{ label: "Pink Neons", action: "neon_color", value: { r: 255, g: 100, b: 200 } },
|
|
{ label: "Yellow Neons", action: "neon_color", value: { r: 255, g: 255, b: 0 } },
|
|
{ label: "White Neons", action: "neon_color", value: { r: 255, g: 255, b: 255 } },
|
|
{ label: "Cyan Neons", action: "neon_color", value: { r: 0, g: 255, b: 255 } },
|
|
{ label: "Orange Neons", action: "neon_color", value: { r: 255, g: 150, b: 0 } }
|
|
]
|
|
},
|
|
|
|
veh_colors: {
|
|
title: "VEHICLE COLORS",
|
|
items: [
|
|
{ label: "Open Color Editor", action: "submenu", target: "vehColorEditor" },
|
|
{ label: "Random Colors", action: "veh_color_random" }
|
|
]
|
|
},
|
|
|
|
// Vehicle Color Editor - special menu with horizontal scrolling
|
|
// Use LEFT/RIGHT arrows to change color ID, UP/DOWN to select slot
|
|
vehColorEditor: {
|
|
title: "VEHICLE COLOR EDITOR",
|
|
isColorEditor: true,
|
|
items: [
|
|
{ label: "Color 1", action: "color_slot_edit", slot: 0 },
|
|
{ label: "Color 2", action: "color_slot_edit", slot: 1 },
|
|
{ label: "Color 3", action: "color_slot_edit", slot: 2 },
|
|
{ label: "Color 4", action: "color_slot_edit", slot: 3 }
|
|
]
|
|
},
|
|
|
|
network: {
|
|
title: "NETWORK OPTIONS",
|
|
items: [
|
|
{ label: "Refresh Player List", action: "refresh_players" },
|
|
{ label: "--- Players ---", action: "none" }
|
|
]
|
|
},
|
|
|
|
// MD Essentials - Teleport System
|
|
// MD Series ; Version 12, March 2016
|
|
// Copyright (C) 2016 DEVILSDESIGN
|
|
teleport: {
|
|
title: "TELEPORT LOCATIONS",
|
|
items: [
|
|
{ label: "Helipads", action: "submenu", target: "teleport_helipads" },
|
|
{ label: "Airport", action: "submenu", target: "teleport_airport" },
|
|
{ label: "Broker", action: "submenu", target: "teleport_broker" },
|
|
{ label: "Dukes", action: "submenu", target: "teleport_dukes" },
|
|
{ label: "Algonquin", action: "submenu", target: "teleport_algonquin" },
|
|
{ label: "Alderney", action: "submenu", target: "teleport_alderney" },
|
|
{ label: "Bohan", action: "submenu", target: "teleport_bohan" },
|
|
{ label: "Happiness Island", action: "submenu", target: "teleport_happiness" },
|
|
{ label: "Special Places", action: "submenu", target: "teleport_special" },
|
|
{ label: "Police Stations", action: "submenu", target: "teleport_police" }
|
|
]
|
|
},
|
|
|
|
teleport_helipads: {
|
|
title: "HELIPADS",
|
|
items: [
|
|
{ label: "Airport Helipad", action: "teleport", value: { x: 2219.8132, y: 745.6130, z: 5.830 } },
|
|
{ label: "Sheriffs Helipad", action: "teleport", value: { x: 2132.2297, y: 441.9621, z: 23.4956 } },
|
|
{ label: "City Helipad", action: "teleport", value: { x: -707.6221, y: 365.6933, z: 3.8330 } },
|
|
{ label: "Heli Tours", action: "teleport", value: { x: 380.0672, y: -714.4636, z: 4.4611 } },
|
|
{ label: "Fire Department Helipad", action: "teleport", value: { x: -2125.2920, y: 142.8950, z: 18.4270 } },
|
|
{ label: "City 2 Helipad", action: "teleport", value: { x: -826.3179, y: 781.8586, z: 6.3370 } }
|
|
]
|
|
},
|
|
|
|
teleport_airport: {
|
|
title: "AIRPORT",
|
|
items: [
|
|
{ label: "Helipads", action: "teleport", value: { x: 2219.8132, y: 745.6130, z: 5.830 } },
|
|
{ label: "Hangar 1 (Top)", action: "teleport", value: { x: 2168.1106, y: 765.7915, z: 28.9740 } },
|
|
{ label: "Hangar 1 (Inside)", action: "teleport", value: { x: 2168.1106, y: 765.7915, z: 5.5784 } },
|
|
{ label: "Hangar 2 (Top)", action: "teleport", value: { x: 2255.2063, y: 643.0560, z: 28.8072 } },
|
|
{ label: "Hangar 2 (Inside)", action: "teleport", value: { x: 2255.2063, y: 643.0560, z: 5.5784 } },
|
|
{ label: "Tower (Top)", action: "teleport", value: { x: 2622.3501, y: 408.0522, z: 79.2688 } },
|
|
{ label: "Tower (Level 2)", action: "teleport", value: { x: 2624.6602, y: 405.3479, z: 41.5520 } },
|
|
{ label: "Tower (Level 1)", action: "teleport", value: { x: 2631.9465, y: 403.1338, z: 17.7790 } },
|
|
{ label: "Tower (Inside)", action: "teleport", value: { x: 2633.4407, y: 415.1447, z: 5.3555 } },
|
|
{ label: "Radar Tower (Top)", action: "teleport", value: { x: 2311.6799, y: 26.2974, z: 82.4870 } },
|
|
{ label: "Radar Tower (Inside)", action: "teleport", value: { x: 2320.0469, y: 30.7563, z: 5.4215 } },
|
|
{ label: "Sheriffs Building (Top)", action: "teleport", value: { x: 2132.2297, y: 441.9621, z: 23.4956 } },
|
|
{ label: "Sheriffs Building (Inside)", action: "teleport", value: { x: 2138.7852, y: 433.7174, z: 5.8495 } },
|
|
{ label: "Sniper Lookout (Top)", action: "teleport", value: { x: 2494.9207, y: 267.1155, z: 20.1800 } },
|
|
{ label: "Sniper Lookout (Inside)", action: "teleport", value: { x: 2502.8150, y: 276.0000, z: 5.5750 } },
|
|
{ label: "Bazooka Lookout (Top)", action: "teleport", value: { x: 2496.3533, y: 515.8263, z: 20.1800 } },
|
|
{ label: "Bazooka Lookout (Inside)", action: "teleport", value: { x: 2496.9800, y: 503.9500, z: 5.5750 } },
|
|
{ label: "Big Building (Inside)", action: "teleport", value: { x: 2425.5264, y: 389.2336, z: 5.8487 } }
|
|
]
|
|
},
|
|
|
|
teleport_broker: {
|
|
title: "BROKER",
|
|
items: [
|
|
{ label: "Crackhouse", action: "teleport", value: { x: 1375.8765, y: 197.4544, z: 47.8063 } }
|
|
]
|
|
},
|
|
|
|
teleport_dukes: {
|
|
title: "DUKES",
|
|
items: [
|
|
{ label: "Barge Basement", action: "teleport", value: { x: 704.5024, y: -270.7895, z: 5.1121 } },
|
|
{ label: "Hove Beach", action: "teleport", value: { x: 1100.5000, y: -747.0000, z: 7.3972 } },
|
|
{ label: "Brucies Garage", action: "teleport", value: { x: 875.9251, y: -119.5862, z: 6.0054 } },
|
|
{ label: "Porn Shop", action: "teleport", value: { x: 796.0092, y: -540.5947, z: 7.5266 } }
|
|
]
|
|
},
|
|
|
|
teleport_algonquin: {
|
|
title: "ALGONQUIN",
|
|
items: [
|
|
{ label: "Hockey Team Office", action: "teleport", value: { x: -245.9398, y: 222.9828, z: 205.9805 } },
|
|
{ label: "Majestic Hotel", action: "teleport", value: { x: -178.2, y: 582.6, z: 127.8500 } },
|
|
{ label: "Playboy X's Pad", action: "teleport", value: { x: -416.3491, y: 1461.9805, z: 38.9715 } },
|
|
{ label: "Rotterdam Tower", action: "teleport", value: { x: -279.5515, y: -101.2410, z: 386.7909 } },
|
|
{ label: "Westminster Towers", action: "teleport", value: { x: -532.6810, y: 1273.3307, z: 106.6500 } },
|
|
{ label: "Underground Parking", action: "teleport", value: { x: 55.3537, y: 1125.3387, z: 3.4527 } },
|
|
{ label: "Safe House", action: "teleport", value: { x: 103.5343, y: 857.4968, z: 43.6211 } },
|
|
{ label: "Scrapyard", action: "teleport", value: { x: -473.0454, y: 1746.8669, z: 8.3762 } },
|
|
{ label: "Construction Site", action: "teleport", value: { x: 237.5457, y: -805.6555, z: 14.7000 } },
|
|
{ label: "Subway", action: "teleport", value: { x: -7.6952, y: 356.7396, z: -2.9570 } },
|
|
{ label: "Skydive", action: "teleport", value: { x: -2476.0000, y: 942.7000, z: 1101.0000 } },
|
|
{ label: "Fight Club (TBOGT ONLY)", action: "teleport", value: { x: -385.3183, y: 1493.0056, z: 11.7148 } },
|
|
{ label: "Lawyer's Office", action: "teleport", value: { x: 123.6929, y: -671.5339, z: 15.8061 } },
|
|
{ label: "Projects Tower", action: "teleport", value: { x: -120.6184, y: 1502.8611, z: 98.7829 } }
|
|
]
|
|
},
|
|
|
|
teleport_alderney: {
|
|
title: "ALDERNEY",
|
|
items: [
|
|
{ label: "Sultan House", action: "teleport", value: { x: -992.8975, y: 1870.2732, z: 23.3234 } },
|
|
{ label: "Sultan Spawn", action: "teleport", value: { x: -968.4757, y: 1908.7188, z: 22.3870 } },
|
|
{ label: "Cognoscenti Garage", action: "teleport", value: { x: -1409.1864, y: 1461.8617, z: 25.5280 } },
|
|
{ label: "Sprunk Factory", action: "teleport", value: { x: -1539.8414, y: 163.2967, z: 10.9000 } },
|
|
{ label: "Strip Club", action: "teleport", value: { x: -1577.2926, y: 18.9291, z: 11.0153 } },
|
|
{ label: "Prison Cage", action: "teleport", value: { x: -1079.8000, y: -469.7000, z: 3.6200 } },
|
|
{ label: "Building Cant Jump Off", action: "teleport", value: { x: -2072.8728, y: 25.4543, z: 96.2373 } }
|
|
]
|
|
},
|
|
|
|
teleport_bohan: {
|
|
title: "BOHAN",
|
|
items: [
|
|
{ label: "Strip Club", action: "teleport", value: { x: 1186.0593, y: 1697.5045, z: 17.7532 } },
|
|
{ label: "Bohan Safe House", action: "teleport", value: { x: 603.3540, y: 1409.7708, z: 18.4847 } },
|
|
{ label: "Baseball Park", action: "teleport", value: { x: 711.0583, y: 1911.1498, z: 27.1642 } },
|
|
{ label: "Near the Bridge 1", action: "teleport", value: { x: 1462.0757, y: 1563.3900, z: 4.0576 } },
|
|
{ label: "Near the Bridge 2", action: "teleport", value: { x: 549.9749, y: 1278.2107, z: 21.8234 } }
|
|
]
|
|
},
|
|
|
|
teleport_happiness: {
|
|
title: "HAPPINESS ISLAND",
|
|
items: [
|
|
{ label: "Building (Top)", action: "teleport", value: { x: -607.6907, y: -767.8975, z: 20.6426 } },
|
|
{ label: "Building (Level 1)", action: "teleport", value: { x: -608.9211, y: -779.1715, z: 17.5085 } },
|
|
{ label: "Building (Inside)", action: "teleport", value: { x: -608.0413, y: -768.1970, z: 9.8789 } },
|
|
{ label: "Statue Head", action: "teleport", value: { x: -609.5771, y: -753.4142, z: 85.7500 } },
|
|
{ label: "Statue Cup", action: "teleport", value: { x: -605.3948, y: -749.7060, z: 94.9000 } },
|
|
{ label: "Statue Heart", action: "teleport", value: { x: -608.8611, y: -755.9594, z: 65.9950 } },
|
|
{ label: "Plaza", action: "teleport", value: { x: -600.1514, y: -961.0953, z: 4.8429 } },
|
|
{ label: "Pier", action: "teleport", value: { x: -409.8473, y: -976.8823, z: 3.6314 } }
|
|
]
|
|
},
|
|
|
|
teleport_special: {
|
|
title: "SPECIAL PLACES",
|
|
items: [
|
|
{ label: "Underwater Hideout", action: "teleport", value: { x: 1626.7833, y: 1319.8804, z: -47.1266 } },
|
|
{ label: "Secret Prison (Lockup)", action: "teleport", value: { x: -1079.8553, y: -362.9944, z: 7.4039 } },
|
|
{ label: "Building Cant Jump Off", action: "teleport", value: { x: -2072.8728, y: 25.4543, z: 96.2373 } },
|
|
{ label: "Prison Cage", action: "teleport", value: { x: -1079.8000, y: -469.7000, z: 3.6200 } }
|
|
]
|
|
},
|
|
|
|
teleport_police: {
|
|
title: "POLICE STATIONS",
|
|
items: [
|
|
{ label: "Mohawk Ave", action: "teleport", value: { x: 903.4626, y: -363.2110, z: 16.9067 } },
|
|
{ label: "Bunker Hill Ave", action: "teleport", value: { x: 1245.5483, y: 583.3440, z: 38.0665 } },
|
|
{ label: "Kunzite St", action: "teleport", value: { x: -421.5369, y: 286.9949, z: 10.8285 } },
|
|
{ label: "San Juan Rd", action: "teleport", value: { x: 88.1274, y: 1224.2035, z: 15.5327 } },
|
|
{ label: "Bridger St", action: "teleport", value: { x: -921.2032, y: 1316.4619, z: 24.0243 } },
|
|
{ label: "Albany Ave", action: "teleport", value: { x: 158.4689, y: -203.2467, z: 14.3076 } }
|
|
]
|
|
},
|
|
|
|
world: {
|
|
title: "WORLD OPTIONS",
|
|
items: [
|
|
{ label: "--- Time Control ---", action: "none" },
|
|
{ label: "Dawn (5:00)", action: "world_time", value: 5 },
|
|
{ label: "Morning (8:00)", action: "world_time", value: 8 },
|
|
{ label: "Noon (12:00)", action: "world_time", value: 12 },
|
|
{ label: "Afternoon (15:00)", action: "world_time", value: 15 },
|
|
{ label: "Evening (18:00)", action: "world_time", value: 18 },
|
|
{ label: "Dusk (20:00)", action: "world_time", value: 20 },
|
|
{ label: "Night (0:00)", action: "world_time", value: 0 },
|
|
{ label: "Midnight (23:00)", action: "world_time", value: 23 },
|
|
{ label: "--- Weather Control ---", action: "none" },
|
|
{ label: "Sunny/Clear", action: "world_weather", value: 1 },
|
|
{ label: "Cloudy", action: "world_weather", value: 3 },
|
|
{ label: "Overcast", action: "world_weather", value: 2 },
|
|
{ label: "Light Rain", action: "world_weather", value: 4 },
|
|
{ label: "Heavy Rain", action: "world_weather", value: 5 },
|
|
{ label: "Thunderstorm", action: "world_weather", value: 7 },
|
|
{ label: "Foggy", action: "world_weather", value: 6 },
|
|
{ label: "Snowy", action: "world_weather", value: 8 },
|
|
{ label: "--- Environment ---(WIP)", action: "none" },
|
|
{ label: "Clear Area", action: "world_clear_area" },
|
|
{ label: "Clear Vehicles", action: "world_clear_vehicles" },
|
|
{ label: "Clear Peds", action: "world_clear_peds" },
|
|
{ label: "Clear Objects", action: "world_clear_objects" },
|
|
{ label: "Traffic ON", action: "world_traffic", value: true },
|
|
{ label: "Traffic OFF", action: "world_traffic", value: false },
|
|
{ label: "Set Traffic Density", action: "submenu", target: "world_traffic_density" },
|
|
{ label: "--- Visual Effects ---(WIP)", action: "none" },
|
|
{ label: "Rainbow Sky", action: "toggle", target: "rainbowSky", state: false },
|
|
{ label: "Sky Colors", action: "submenu", target: "sky_colors" },
|
|
{ label: "Timecycle Modifier", action: "submenu", target: "world_timecycles" }
|
|
]
|
|
},
|
|
|
|
sky_colors: {
|
|
title: "SKY COLORS (WIP)",
|
|
items: [
|
|
{ label: "Default Sky", action: "sky_color", value: 0 },
|
|
{ label: "Red Sky", action: "sky_color", value: 1 },
|
|
{ label: "Blue Sky", action: "sky_color", value: 2 },
|
|
{ label: "Green Sky", action: "sky_color", value: 3 },
|
|
{ label: "Purple Sky", action: "sky_color", value: 4 },
|
|
{ label: "Orange Sky", action: "sky_color", value: 5 },
|
|
{ label: "Pink Sky", action: "sky_color", value: 6 },
|
|
{ label: "Yellow Sky", action: "sky_color", value: 7 },
|
|
{ label: "Cyan Sky", action: "sky_color", value: 8 },
|
|
{ label: "Black Sky", action: "sky_color", value: 9 },
|
|
{ label: "White Sky", action: "sky_color", value: 10 }
|
|
]
|
|
},
|
|
|
|
world_traffic_density: {
|
|
title: "TRAFFIC DENSITY(WIP)",
|
|
items: [
|
|
{ label: "No Traffic", action: "world_traffic_density", value: 0.0 },
|
|
{ label: "Very Light", action: "world_traffic_density", value: 0.2 },
|
|
{ label: "Light", action: "world_traffic_density", value: 0.4 },
|
|
{ label: "Normal", action: "world_traffic_density", value: 0.6 },
|
|
{ label: "Heavy", action: "world_traffic_density", value: 0.8 },
|
|
{ label: "Very Heavy", action: "world_traffic_density", value: 1.0 },
|
|
{ label: "Maximum", action: "world_traffic_density", value: 1.5 }
|
|
]
|
|
},
|
|
|
|
world_timecycles: {
|
|
title: "TIMECYCLE MODIFIERS",
|
|
items: [
|
|
{ label: "Default", action: "world_timecycle", value: "default" },
|
|
{ label: "Bright", action: "world_timecycle", value: "bright" },
|
|
{ label: "Dark", action: "world_timecycle", value: "dark" },
|
|
{ label: "Foggy", action: "world_timecycle", value: "foggy" },
|
|
{ label: "Hazy", action: "world_timecycle", value: "hazy" },
|
|
{ label: "Night", action: "world_timecycle", value: "night" },
|
|
{ label: "Sunset", action: "world_timecycle", value: "sunset" },
|
|
{ label: "Sunrise", action: "world_timecycle", value: "sunrise" },
|
|
{ label: "Clear Modifier", action: "world_timecycle_clear" }
|
|
]
|
|
},
|
|
|
|
weapons: {
|
|
title: "WEAPONS",
|
|
items: [
|
|
{ label: "Get All Weapons", action: "weapon_all" },
|
|
{ label: "Remove All Weapons", action: "weapon_remove" },
|
|
{ label: "--- Weapon Toggles ---", action: "none" },
|
|
{ label: "Unlimited Ammo", action: "toggle", target: "unlimitedAmmo", state: false },
|
|
{ label: "No Reload", action: "toggle", target: "noReload", state: false },
|
|
{ label: "Explosive Ammo", action: "toggle", target: "explosiveAmmo", state: false },
|
|
{ label: "Fire Bullets", action: "toggle", target: "fireBullets", state: false },
|
|
{ label: "--- Give Weapon ---", action: "none" },
|
|
{ label: "Baseball Bat", action: "weapon", value: 2 },
|
|
{ label: "Knife", action: "weapon", value: 3 },
|
|
{ label: "Grenades", action: "weapon", value: 4 },
|
|
{ label: "Pistol", action: "weapon", value: 5 },
|
|
{ label: "Desert Eagle", action: "weapon", value: 6 },
|
|
{ label: "Shotgun", action: "weapon", value: 9 },
|
|
{ label: "SMG", action: "weapon", value: 12 },
|
|
{ label: "Assault Rifle", action: "weapon", value: 14 },
|
|
{ label: "Sniper Rifle", action: "weapon", value: 16 },
|
|
{ label: "RPG", action: "weapon", value: 18 }
|
|
]
|
|
},
|
|
|
|
fun: {
|
|
title: "FUN OPTIONS",
|
|
items: [
|
|
{ label: "--- Physics Fun ---", action: "none" },
|
|
{ label: "Launch Me Up", action: "fun_launch" },
|
|
{ label: "Super Jump", action: "fun_superjump" },
|
|
{ label: "Moon Gravity", action: "toggle", target: "moonGravity", state: false },
|
|
{ label: "Drunk Mode", action: "toggle", target: "drunkMode", state: false },
|
|
{ label: "Spinbot", action: "toggle", target: "spinbot", state: false },
|
|
{ label: "Ragdoll", action: "fun_ragdoll" },
|
|
{ label: "--- Chaos Mode ---(WIP)", action: "none" },
|
|
{ label: "Explode Me", action: "fun_explode" },
|
|
{ label: "Random Explosion", action: "fun_random_explosion" },
|
|
{ label: "Car Rain", action: "fun_car_rain" },
|
|
{ label: "Ped Invasion", action: "fun_ped_invasion" },
|
|
{ label: "Weapon Shower", action: "fun_weapon_shower" },
|
|
{ label: "--- Spawning ---(WIP)", action: "none" },
|
|
{ label: "Spawn Random Ped", action: "fun_ped" },
|
|
{ label: "Spawn Gang", action: "fun_gang" },
|
|
{ label: "Spawn Army", action: "fun_army" },
|
|
{ label: "Spawn Money Rain", action: "fun_money_rain" },
|
|
{ label: "--- Visual Effects ---(WIP)", action: "none" },
|
|
{ label: "Screen Shake", action: "fun_screen_shake" },
|
|
{ label: "Flash Screen", action: "fun_flash" },
|
|
{ label: "Matrix Mode", action: "toggle", target: "matrixMode", state: false },
|
|
{ label: "Thermal Vision", action: "toggle", target: "thermalVision", state: false },
|
|
{ label: "Night Vision", action: "toggle", target: "nightVision", state: false }
|
|
]
|
|
}
|
|
};
|
|
|
|
// Enhanced toggle states
|
|
let toggleStates = {
|
|
godMode: false,
|
|
invincible: false,
|
|
superRun: false,
|
|
noRagdoll: false,
|
|
neverWanted: false,
|
|
invisible: false,
|
|
infiniteSprint: false,
|
|
freezePlayer: false,
|
|
vehGodMode: false,
|
|
driveOnWater: false,
|
|
rainbowCar: false,
|
|
neonLights: false,
|
|
flyMode: false,
|
|
vehShootRPG: false,
|
|
rainbowSky: false,
|
|
explosiveAmmo: false,
|
|
unlimitedAmmo: false,
|
|
noReload: false,
|
|
fireBullets: false,
|
|
moonGravity: false,
|
|
drunkMode: false,
|
|
spinbot: false,
|
|
matrixMode: false,
|
|
thermalVision: false,
|
|
nightVision: false
|
|
};
|
|
|
|
// Neon objects storage
|
|
let neonObjects = [];
|
|
let neonColor = { r: 255, g: 0, b: 255 }; // Default purple
|
|
|
|
// Sky color cycling
|
|
let skyColorIndex = 0;
|
|
|
|
// Sky colors for selection
|
|
const skyColors = [
|
|
{ name: "Default", r: -1, g: -1, b: -1 },
|
|
{ name: "Red", r: 255, g: 50, b: 50 },
|
|
{ name: "Blue", r: 50, g: 100, b: 255 },
|
|
{ name: "Green", r: 50, g: 255, b: 100 },
|
|
{ name: "Purple", r: 180, g: 50, b: 255 },
|
|
{ name: "Orange", r: 255, g: 150, b: 50 },
|
|
{ name: "Pink", r: 255, g: 100, b: 200 },
|
|
{ name: "Yellow", r: 255, g: 255, b: 100 },
|
|
{ name: "Cyan", r: 100, g: 255, b: 255 }
|
|
];
|
|
|
|
// Last shot time for RPG vehicle
|
|
let lastVehShot = 0;
|
|
|
|
// ============================================================================
|
|
// FONT LOADING
|
|
// ============================================================================
|
|
|
|
addEventHandler("OnResourceReady", function(event, resource) {
|
|
// Use built-in default font (no external TTF file needed)
|
|
// Signature: lucasFont.createDefaultFont(float size, string family, [string style = "Regular"])
|
|
try {
|
|
menuFont = lucasFont.createDefaultFont(16.0, "Arial", "Regular");
|
|
if (menuFont != null) {
|
|
console.log("[ModMenu] Default font created successfully");
|
|
}
|
|
} catch(e) {
|
|
console.log("[ModMenu] Could not create default font: " + e);
|
|
// Try alternative font
|
|
try {
|
|
menuFont = lucasFont.createDefaultFont(16.0, "Tahoma");
|
|
console.log("[ModMenu] Fallback font created");
|
|
} catch(e2) {
|
|
console.log("[ModMenu] Fallback font also failed: " + e2);
|
|
}
|
|
}
|
|
|
|
if (menuFont == null) {
|
|
console.log("[ModMenu] Font creation failed - text won't render");
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// INPUT HANDLING
|
|
// ============================================================================
|
|
|
|
addEventHandler("OnKeyUp", function(event, key, scanCode, mods) {
|
|
// F5 to toggle menu (NOT F6)
|
|
if (key === SDLK_F5) {
|
|
menuOpen = !menuOpen;
|
|
if (menuOpen) {
|
|
currentMenu = "main";
|
|
selectedIndex = 0;
|
|
scrollOffset = 0;
|
|
menuStack = [];
|
|
// Show cursor only - NO second param to avoid pause menu
|
|
gui.showCursor(true);
|
|
// Block phone when menu opens
|
|
try {
|
|
natives.destroyMobilePhone();
|
|
} catch(e) {}
|
|
} else {
|
|
// Hide cursor only
|
|
gui.showCursor(false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Block phone on ANY key press while menu is open
|
|
if (menuOpen) {
|
|
try {
|
|
natives.destroyMobilePhone();
|
|
} catch(e) {}
|
|
}
|
|
|
|
if (!menuOpen) return;
|
|
|
|
// Navigation - simple key handling
|
|
if (key === SDLK_UP) {
|
|
navigateUp();
|
|
} else if (key === SDLK_DOWN) {
|
|
navigateDown();
|
|
} else if (key === SDLK_LEFT) {
|
|
// Left arrow - decrease color value in color editor
|
|
handleColorEditorLeft();
|
|
} else if (key === SDLK_RIGHT) {
|
|
// Right arrow - increase color value in color editor
|
|
handleColorEditorRight();
|
|
} else if (key === SDLK_RETURN || key === SDLK_KP_ENTER) {
|
|
selectItem();
|
|
} else if (key === SDLK_BACKSPACE || key === SDLK_ESCAPE) {
|
|
goBack();
|
|
}
|
|
});
|
|
|
|
// Handle left arrow in color editor
|
|
function handleColorEditorLeft() {
|
|
if (currentMenu !== "vehColorEditor") return;
|
|
|
|
let items = getCurrentMenuItems();
|
|
let item = items[selectedIndex];
|
|
|
|
if (item && item.action === "color_slot_edit") {
|
|
let slot = item.slot;
|
|
colorEditorValues[slot]--;
|
|
if (colorEditorValues[slot] < 0) {
|
|
colorEditorValues[slot] = GTA_IV_COLORS.length - 1;
|
|
}
|
|
applyColorToVehicle(slot, colorEditorValues[slot]);
|
|
}
|
|
}
|
|
|
|
// Handle right arrow in color editor
|
|
function handleColorEditorRight() {
|
|
if (currentMenu !== "vehColorEditor") return;
|
|
|
|
let items = getCurrentMenuItems();
|
|
let item = items[selectedIndex];
|
|
|
|
if (item && item.action === "color_slot_edit") {
|
|
let slot = item.slot;
|
|
colorEditorValues[slot]++;
|
|
if (colorEditorValues[slot] >= GTA_IV_COLORS.length) {
|
|
colorEditorValues[slot] = 0;
|
|
}
|
|
applyColorToVehicle(slot, colorEditorValues[slot]);
|
|
}
|
|
}
|
|
|
|
// Apply color to vehicle slot
|
|
function applyColorToVehicle(slot, colorId) {
|
|
if (!localPlayer || !localPlayer.vehicle) {
|
|
showNotification("Not in vehicle!");
|
|
return;
|
|
}
|
|
|
|
let veh = localPlayer.vehicle;
|
|
let colorInfo = getColorById(colorId);
|
|
|
|
try {
|
|
switch(slot) {
|
|
case 0:
|
|
veh.colour1 = colorId;
|
|
break;
|
|
case 1:
|
|
veh.colour2 = colorId;
|
|
break;
|
|
case 2:
|
|
veh.colour3 = colorId;
|
|
break;
|
|
case 3:
|
|
veh.colour4 = colorId;
|
|
break;
|
|
}
|
|
showNotification("Color " + (slot + 1) + ": " + colorId);
|
|
} catch(e) {
|
|
// Fallback for older API
|
|
try {
|
|
if (slot === 0 || slot === 1) {
|
|
let col1 = slot === 0 ? colorId : colorEditorValues[0];
|
|
let col2 = slot === 1 ? colorId : colorEditorValues[1];
|
|
natives.changeCarColour(veh, col1, col2);
|
|
showNotification("Color " + (slot + 1) + ": " + colorInfo.name);
|
|
}
|
|
} catch(e2) {
|
|
console.log("[ColorEditor] Failed: " + e2);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load current vehicle colors into editor
|
|
function loadVehicleColorsToEditor() {
|
|
if (!localPlayer || !localPlayer.vehicle) return;
|
|
|
|
let veh = localPlayer.vehicle;
|
|
try {
|
|
colorEditorValues[0] = veh.colour1 || 0;
|
|
colorEditorValues[1] = veh.colour2 || 0;
|
|
colorEditorValues[2] = veh.colour3 || 0;
|
|
colorEditorValues[3] = veh.colour4 || 0;
|
|
} catch(e) {
|
|
// Default to black if can't read
|
|
colorEditorValues = [0, 0, 0, 0];
|
|
}
|
|
}
|
|
|
|
function navigateUp() {
|
|
let items = getCurrentMenuItems();
|
|
do {
|
|
selectedIndex--;
|
|
if (selectedIndex < 0) {
|
|
selectedIndex = items.length - 1;
|
|
}
|
|
} while (items[selectedIndex] && items[selectedIndex].action === "none");
|
|
updateScroll(items);
|
|
}
|
|
|
|
function navigateDown() {
|
|
let items = getCurrentMenuItems();
|
|
do {
|
|
selectedIndex++;
|
|
if (selectedIndex >= items.length) {
|
|
selectedIndex = 0;
|
|
}
|
|
} while (items[selectedIndex] && items[selectedIndex].action === "none");
|
|
updateScroll(items);
|
|
}
|
|
|
|
function updateScroll(items) {
|
|
if (selectedIndex < scrollOffset) {
|
|
scrollOffset = selectedIndex;
|
|
} else if (selectedIndex >= scrollOffset + menu.maxVisibleItems) {
|
|
scrollOffset = selectedIndex - menu.maxVisibleItems + 1;
|
|
}
|
|
}
|
|
|
|
function goBack() {
|
|
// Deactivate color editor when leaving
|
|
if (currentMenu === "vehColorEditor") {
|
|
colorEditorActive = false;
|
|
}
|
|
|
|
// Deactivate handling editor when leaving handling menus
|
|
if (currentMenu === "handlingEditor" || currentMenu.startsWith("handling_")) {
|
|
handlingEditorActive = false;
|
|
selectedHandlingParam = "";
|
|
}
|
|
|
|
if (menuStack.length > 0) {
|
|
let prev = menuStack.pop();
|
|
currentMenu = prev.menu;
|
|
selectedIndex = prev.index;
|
|
scrollOffset = prev.scroll;
|
|
} else {
|
|
menuOpen = false;
|
|
// IMPORTANT: Enable controls when closing menu
|
|
gui.showCursor(false);
|
|
}
|
|
}
|
|
|
|
function getCurrentMenuItems() {
|
|
if (currentMenu === "network") {
|
|
return getNetworkMenuItems();
|
|
}
|
|
return menuData[currentMenu] ? menuData[currentMenu].items : [];
|
|
}
|
|
|
|
function getNetworkMenuItems() {
|
|
let items = [
|
|
{ label: "Refresh Player List", action: "refresh_players" },
|
|
{ label: "--- Players (" + playerList.length + ") ---", action: "none" }
|
|
];
|
|
for (let i = 0; i < playerList.length; i++) {
|
|
items.push({
|
|
label: playerList[i].name + " [ID: " + playerList[i].id + "]",
|
|
action: "teleport_to_player_direct",
|
|
playerData: playerList[i]
|
|
});
|
|
}
|
|
if (playerList.length === 0) {
|
|
items.push({ label: "(No players found)", action: "none" });
|
|
items.push({ label: "(Click Refresh above)", action: "none" });
|
|
}
|
|
return items;
|
|
}
|
|
|
|
// Refresh player list function
|
|
function refreshPlayerList() {
|
|
// Request player list from server
|
|
triggerNetworkEvent("ModMenu:GetPlayers");
|
|
showNotification("Refreshing players...");
|
|
}
|
|
|
|
// ============================================================================
|
|
// ACTION HANDLING
|
|
// ============================================================================
|
|
|
|
let selectedPlayer = null;
|
|
|
|
// Update current description
|
|
function updateDescription() {
|
|
let items = getCurrentMenuItems();
|
|
let item = items[selectedIndex];
|
|
|
|
if (!item) {
|
|
currentDescription = "";
|
|
return;
|
|
}
|
|
|
|
// Get description based on action or target
|
|
if (item.action === "toggle") {
|
|
currentDescription = itemDescriptions[item.target] || "";
|
|
} else if (itemDescriptions[item.action]) {
|
|
currentDescription = itemDescriptions[item.action] || "";
|
|
} else if (itemDescriptions[item.value]) {
|
|
currentDescription = itemDescriptions[item.value] || "";
|
|
} else {
|
|
currentDescription = "";
|
|
}
|
|
}
|
|
|
|
function selectItem() {
|
|
let items = getCurrentMenuItems();
|
|
let item = items[selectedIndex];
|
|
if (!item || item.action === "none") return;
|
|
|
|
// Update description before handling action
|
|
updateDescription();
|
|
|
|
switch (item.action) {
|
|
case "submenu":
|
|
if (item.target === "player_options" && item.playerData) {
|
|
selectedPlayer = item.playerData;
|
|
openPlayerMenu(item.playerData);
|
|
} else {
|
|
menuStack.push({ menu: currentMenu, index: selectedIndex, scroll: scrollOffset });
|
|
currentMenu = item.target;
|
|
selectedIndex = 0;
|
|
scrollOffset = 0;
|
|
|
|
// Auto-refresh player list when entering network menu
|
|
if (item.target === "network") {
|
|
refreshPlayerList();
|
|
}
|
|
|
|
// Load vehicle colors when entering color editor
|
|
if (item.target === "vehColorEditor") {
|
|
loadVehicleColorsToEditor();
|
|
colorEditorActive = true;
|
|
}
|
|
|
|
// Activate handling editor visualization when entering handling menus
|
|
if (item.target === "handlingEditor" || item.target.startsWith("handling_")) {
|
|
handlingEditorActive = true;
|
|
// Highlight the relevant parameter in the visualization
|
|
if (item.target.includes("traction")) {
|
|
selectedHandlingParam = "traction";
|
|
} else if (item.target.includes("susp")) {
|
|
selectedHandlingParam = "suspension";
|
|
} else if (item.target.includes("power") || item.target.includes("speed")) {
|
|
selectedHandlingParam = "engine";
|
|
} else if (item.target.includes("brake")) {
|
|
selectedHandlingParam = "brakes";
|
|
} else if (item.target.includes("mass") || item.target.includes("com")) {
|
|
selectedHandlingParam = "weight";
|
|
} else if (item.target.includes("steering")) {
|
|
selectedHandlingParam = "steering";
|
|
} else {
|
|
selectedHandlingParam = "";
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "toggle":
|
|
toggleStates[item.target] = !toggleStates[item.target];
|
|
item.state = toggleStates[item.target];
|
|
triggerNetworkEvent("ModMenu:Toggle", item.target, toggleStates[item.target]);
|
|
break;
|
|
|
|
case "spawn_vehicle":
|
|
triggerNetworkEvent("ModMenu:SpawnVehicle", item.value);
|
|
showNotification("Spawning: " + item.label);
|
|
break;
|
|
|
|
case "teleport":
|
|
triggerNetworkEvent("ModMenu:Teleport", item.value.x, item.value.y, item.value.z);
|
|
showNotification("Teleporting...");
|
|
break;
|
|
|
|
case "self_health":
|
|
triggerNetworkEvent("ModMenu:SelfOption", "health");
|
|
showNotification("Health restored!");
|
|
break;
|
|
|
|
case "self_armor":
|
|
triggerNetworkEvent("ModMenu:SelfOption", "armor");
|
|
showNotification("Armor restored!");
|
|
break;
|
|
|
|
case "self_max":
|
|
triggerNetworkEvent("ModMenu:SelfOption", "max");
|
|
showNotification("Max health & armor!");
|
|
break;
|
|
|
|
case "self_weapons":
|
|
triggerNetworkEvent("ModMenu:SelfOption", "weapons");
|
|
showNotification("All weapons given!");
|
|
break;
|
|
|
|
case "self_wanted":
|
|
triggerNetworkEvent("ModMenu:SelfOption", "wanted");
|
|
showNotification("Wanted cleared!");
|
|
break;
|
|
|
|
case "self_respawn":
|
|
triggerNetworkEvent("ModMenu:SelfOption", "respawn");
|
|
showNotification("Respawning...");
|
|
break;
|
|
|
|
case "self_suicide":
|
|
triggerNetworkEvent("ModMenu:SelfOption", "suicide");
|
|
break;
|
|
|
|
case "self_fire":
|
|
try {
|
|
natives.startCharFire(localPlayer);
|
|
showNotification("You're on fire!");
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "self_cleartasks":
|
|
try {
|
|
natives.clearCharTasks(localPlayer);
|
|
showNotification("Tasks cleared!");
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "skin":
|
|
triggerNetworkEvent("ModMenu:ChangeSkin", item.value);
|
|
showNotification("Skin changed!");
|
|
break;
|
|
|
|
case "skin_random":
|
|
triggerNetworkEvent("ModMenu:ChangeSkin", "random");
|
|
showNotification("Random skin!");
|
|
break;
|
|
|
|
case "skin_random_male":
|
|
case "skin_random_mp_boy":
|
|
triggerNetworkEvent("ModMenu:ChangeSkin", "random_male");
|
|
showNotification("Random Male Skin!");
|
|
break;
|
|
|
|
case "skin_random_female":
|
|
case "skin_random_mp_girl":
|
|
triggerNetworkEvent("ModMenu:ChangeSkin", "random_female");
|
|
showNotification("Random Female Skin!");
|
|
break;
|
|
|
|
case "veh_repair":
|
|
triggerNetworkEvent("ModMenu:VehicleOption", "repair");
|
|
showNotification("Repaired!");
|
|
break;
|
|
|
|
case "veh_flip":
|
|
triggerNetworkEvent("ModMenu:VehicleOption", "flip");
|
|
showNotification("Flipped!");
|
|
break;
|
|
|
|
case "veh_nitro":
|
|
triggerNetworkEvent("ModMenu:VehicleOption", "nitro");
|
|
showNotification("NITRO!");
|
|
break;
|
|
|
|
case "veh_lock":
|
|
if (localPlayer.vehicle) {
|
|
try {
|
|
natives.lockCarDoors(localPlayer.vehicle, 2);
|
|
showNotification("Doors locked!");
|
|
} catch(e) {}
|
|
}
|
|
break;
|
|
|
|
case "veh_unlock":
|
|
if (localPlayer.vehicle) {
|
|
try {
|
|
natives.lockCarDoors(localPlayer.vehicle, 1);
|
|
showNotification("Doors unlocked!");
|
|
} catch(e) {}
|
|
}
|
|
break;
|
|
|
|
case "veh_engine":
|
|
if (localPlayer.vehicle) {
|
|
try {
|
|
let isOn = natives.isCarEngineOn(localPlayer.vehicle);
|
|
natives.switchCarEngine(localPlayer.vehicle, !isOn);
|
|
showNotification(isOn ? "Engine off!" : "Engine on!");
|
|
} catch(e) {}
|
|
}
|
|
break;
|
|
|
|
case "veh_poptires":
|
|
if (localPlayer.vehicle) {
|
|
try {
|
|
natives.burstCarTyre(localPlayer.vehicle, 0);
|
|
natives.burstCarTyre(localPlayer.vehicle, 1);
|
|
natives.burstCarTyre(localPlayer.vehicle, 2);
|
|
natives.burstCarTyre(localPlayer.vehicle, 3);
|
|
showNotification("Tires popped!");
|
|
} catch(e) {}
|
|
}
|
|
break;
|
|
|
|
case "veh_fixtires":
|
|
if (localPlayer.vehicle) {
|
|
try {
|
|
natives.fixCarTyre(localPlayer.vehicle, 0);
|
|
natives.fixCarTyre(localPlayer.vehicle, 1);
|
|
natives.fixCarTyre(localPlayer.vehicle, 2);
|
|
natives.fixCarTyre(localPlayer.vehicle, 3);
|
|
showNotification("Tires fixed!");
|
|
} catch(e) {}
|
|
}
|
|
break;
|
|
|
|
case "veh_color":
|
|
triggerNetworkEvent("ModMenu:VehicleColor", item.value[0], item.value[1]);
|
|
showNotification("Color changed!");
|
|
break;
|
|
|
|
case "veh_color_random":
|
|
let c1 = Math.floor(Math.random() * 132);
|
|
let c2 = Math.floor(Math.random() * 132);
|
|
triggerNetworkEvent("ModMenu:VehicleColor", c1, c2);
|
|
showNotification("Random color!");
|
|
break;
|
|
|
|
case "veh_color_slot":
|
|
// Set individual color slot using vehicle.colour1/2/3/4
|
|
if (localPlayer && localPlayer.vehicle) {
|
|
let veh = localPlayer.vehicle;
|
|
try {
|
|
switch(item.slot) {
|
|
case 1:
|
|
veh.colour1 = item.value;
|
|
showNotification("Color 1 set to " + item.value);
|
|
break;
|
|
case 2:
|
|
veh.colour2 = item.value;
|
|
showNotification("Color 2 set to " + item.value);
|
|
break;
|
|
case 3:
|
|
veh.colour3 = item.value;
|
|
showNotification("Color 3 set to " + item.value);
|
|
break;
|
|
case 4:
|
|
veh.colour4 = item.value;
|
|
showNotification("Color 4 set to " + item.value);
|
|
break;
|
|
}
|
|
} catch(e) {
|
|
// Fallback using natives.changeCarColour for slots 1-2
|
|
try {
|
|
if (item.slot === 1 || item.slot === 2) {
|
|
let col1 = item.slot === 1 ? item.value : veh.colour1;
|
|
let col2 = item.slot === 2 ? item.value : veh.colour2;
|
|
natives.changeCarColour(veh, col1, col2);
|
|
showNotification("Color " + item.slot + " set!");
|
|
}
|
|
} catch(e2) {
|
|
console.log("[Color] Failed to set color slot: " + e2);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "color_slot_edit":
|
|
// Color editor slot - pressing Enter does nothing, use LEFT/RIGHT
|
|
showNotification("Use LEFT/RIGHT arrows to change color");
|
|
break;
|
|
|
|
case "color_preset":
|
|
// Apply preset colors to all 4 slots
|
|
if (localPlayer && localPlayer.vehicle) {
|
|
let veh = localPlayer.vehicle;
|
|
let vals = item.values;
|
|
try {
|
|
veh.colour1 = vals[0];
|
|
veh.colour2 = vals[1];
|
|
veh.colour3 = vals[2];
|
|
veh.colour4 = vals[3];
|
|
colorEditorValues = [vals[0], vals[1], vals[2], vals[3]];
|
|
showNotification("Preset applied: " + item.label);
|
|
} catch(e) {
|
|
try {
|
|
natives.changeCarColour(veh, vals[0], vals[1]);
|
|
colorEditorValues = [vals[0], vals[1], 0, 0];
|
|
showNotification("Preset applied (2 colors)");
|
|
} catch(e2) {
|
|
console.log("[ColorEditor] Preset failed: " + e2);
|
|
}
|
|
}
|
|
} else {
|
|
showNotification("Not in vehicle!");
|
|
}
|
|
break;
|
|
|
|
case "veh_upgrade_add":
|
|
// Add upgrade using vehicle.addUpgrade(upgradeId)
|
|
if (localPlayer && localPlayer.vehicle) {
|
|
try {
|
|
localPlayer.vehicle.addUpgrade(item.value);
|
|
showNotification("Upgrade added!");
|
|
} catch(e) {
|
|
console.log("[Upgrades] addUpgrade failed: " + e);
|
|
showNotification("Upgrade not available for this vehicle");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "veh_upgrade_remove":
|
|
// Remove upgrade using vehicle.removeUpgrade(upgradeId)
|
|
if (localPlayer && localPlayer.vehicle) {
|
|
try {
|
|
localPlayer.vehicle.removeUpgrade(item.value);
|
|
showNotification("Upgrade removed!");
|
|
} catch(e) {
|
|
console.log("[Upgrades] removeUpgrade failed: " + e);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "veh_upgrade_remove_all":
|
|
// Remove all common upgrades
|
|
if (localPlayer && localPlayer.vehicle) {
|
|
let veh = localPlayer.vehicle;
|
|
let upgradeIds = [1010, 1087, 1025, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083,
|
|
1018, 1019, 1020, 1021, 1022, 1000, 1001, 1002, 1003, 1014, 1015, 1006, 1032, 1033,
|
|
1007, 1017, 1027, 1031];
|
|
for (let i = 0; i < upgradeIds.length; i++) {
|
|
try {
|
|
veh.removeUpgrade(upgradeIds[i]);
|
|
} catch(e) {}
|
|
}
|
|
showNotification("All upgrades removed!");
|
|
}
|
|
break;
|
|
|
|
case "vehicle_delete":
|
|
triggerNetworkEvent("ModMenu:DeleteVehicles");
|
|
showNotification("Deleted!");
|
|
break;
|
|
|
|
case "world_time":
|
|
triggerNetworkEvent("ModMenu:WorldTime", item.value);
|
|
showNotification("Time: " + item.value + ":00");
|
|
break;
|
|
|
|
case "world_weather":
|
|
triggerNetworkEvent("ModMenu:WorldWeather", item.value);
|
|
showNotification("Weather changed!");
|
|
break;
|
|
|
|
case "weapon":
|
|
triggerNetworkEvent("ModMenu:GiveWeapon", item.value);
|
|
showNotification("Weapon given!");
|
|
break;
|
|
|
|
case "weapon_all":
|
|
triggerNetworkEvent("ModMenu:SelfOption", "weapons");
|
|
showNotification("All weapons!");
|
|
break;
|
|
|
|
case "weapon_remove":
|
|
try {
|
|
natives.removeAllCharWeapons(localPlayer);
|
|
showNotification("Weapons removed!");
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "refresh_players":
|
|
refreshPlayerList();
|
|
break;
|
|
|
|
case "teleport_to_player":
|
|
if (selectedPlayer) {
|
|
triggerNetworkEvent("ModMenu:TeleportToPlayer", selectedPlayer.id);
|
|
showNotification("Teleporting to " + selectedPlayer.name);
|
|
}
|
|
break;
|
|
|
|
case "teleport_to_player_direct":
|
|
if (item.playerData) {
|
|
triggerNetworkEvent("ModMenu:TeleportToPlayer", item.playerData.id);
|
|
showNotification("Teleporting to " + item.playerData.name);
|
|
}
|
|
break;
|
|
|
|
case "fun_launch":
|
|
triggerNetworkEvent("ModMenu:Fun", "launch");
|
|
showNotification("LAUNCH!");
|
|
break;
|
|
|
|
case "fun_explode":
|
|
triggerNetworkEvent("ModMenu:Fun", "explode");
|
|
break;
|
|
|
|
case "fun_ped":
|
|
triggerNetworkEvent("ModMenu:Fun", "ped");
|
|
showNotification("Ped spawned!");
|
|
break;
|
|
|
|
case "fun_ragdoll":
|
|
triggerNetworkEvent("ModMenu:Fun", "ragdoll");
|
|
break;
|
|
|
|
case "neon_color":
|
|
neonColor = item.value;
|
|
showNotification("Neon color: " + item.label.replace(" Neons", ""));
|
|
break;
|
|
|
|
case "sky_color":
|
|
skyColorIndex = item.value;
|
|
if (skyColorIndex === 0) {
|
|
// Reset to default
|
|
try {
|
|
natives.releaseSkybox();
|
|
} catch(e) {}
|
|
showNotification("Default sky");
|
|
} else {
|
|
showNotification("Sky: " + item.value);
|
|
}
|
|
break;
|
|
|
|
case "world_clear_area":
|
|
if (localPlayer) {
|
|
try {
|
|
let pos = localPlayer.position;
|
|
natives.clearArea(pos.x, pos.y, pos.z, 50.0, true);
|
|
showNotification("Area cleared!");
|
|
} catch(e) {}
|
|
}
|
|
break;
|
|
|
|
case "world_clear_vehicles":
|
|
try {
|
|
let pos = localPlayer ? localPlayer.position : new Vec3(0, 0, 0);
|
|
natives.clearAreaOfCars(pos.x, pos.y, pos.z, 100.0);
|
|
showNotification("Vehicles cleared!");
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "world_clear_peds":
|
|
try {
|
|
let pos = localPlayer ? localPlayer.position : new Vec3(0, 0, 0);
|
|
natives.clearAreaOfChars(pos.x, pos.y, pos.z, 100.0);
|
|
showNotification("Peds cleared!");
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "world_clear_objects":
|
|
try {
|
|
let pos = localPlayer ? localPlayer.position : new Vec3(0, 0, 0);
|
|
natives.clearAreaOfObjects(pos.x, pos.y, pos.z, 100.0);
|
|
showNotification("Objects cleared!");
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "world_traffic":
|
|
try {
|
|
natives.setTrafficEnabled(item.value);
|
|
showNotification("Traffic: " + (item.value ? "ENABLED" : "DISABLED"));
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "world_traffic_density":
|
|
try {
|
|
natives.trafficDensity(item.value);
|
|
showNotification("Traffic density: " + item.value);
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "world_timecycle":
|
|
try {
|
|
if (item.value === "clear") {
|
|
natives.clearTimecycleModifier();
|
|
showNotification("Timecycle cleared!");
|
|
} else {
|
|
natives.setTimecycleModifier(item.value);
|
|
showNotification("Timecycle: " + item.value);
|
|
}
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "world_timecycle_clear":
|
|
try {
|
|
natives.clearTimecycleModifier();
|
|
showNotification("Timecycle cleared!");
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "fun_superjump":
|
|
if (localPlayer) {
|
|
try {
|
|
let vel = localPlayer.velocity;
|
|
vel.z = 15.0;
|
|
localPlayer.velocity = vel;
|
|
showNotification("SUPER JUMP!");
|
|
} catch(e) {}
|
|
}
|
|
break;
|
|
|
|
case "fun_random_explosion":
|
|
if (localPlayer) {
|
|
try {
|
|
let pos = localPlayer.position;
|
|
let offsetX = (Math.random() - 0.5) * 20;
|
|
let offsetY = (Math.random() - 0.5) * 20;
|
|
natives.addExplosion(pos.x + offsetX, pos.y + offsetY, pos.z, Math.floor(Math.random() * 26), 5.0, true, false, 1.0);
|
|
showNotification("Random explosion!");
|
|
} catch(e) {}
|
|
}
|
|
break;
|
|
|
|
case "fun_car_rain":
|
|
try {
|
|
let pos = localPlayer ? localPlayer.position : new Vec3(0, 0, 0);
|
|
let carModels = ["infernus", "banshee", "sultan"];
|
|
for (let i = 0; i < 10; i++) {
|
|
let randomModel = carModels[Math.floor(Math.random() * carModels.length)];
|
|
let spawnX = pos.x + (Math.random() - 0.5) * 30;
|
|
let spawnY = pos.y + (Math.random() - 0.5) * 30;
|
|
let spawnZ = pos.z + 10 + Math.random() * 20;
|
|
|
|
let modelHash = vehicleHashes[randomModel];
|
|
if (modelHash) {
|
|
natives.requestModel(modelHash);
|
|
setTimeout(() => {
|
|
if (natives.hasModelLoaded(modelHash)) {
|
|
let car = natives.createCar(modelHash, new Vec3(spawnX, spawnY, spawnZ), true);
|
|
if (car) {
|
|
let vel = new Vec3((Math.random() - 0.5) * 5, (Math.random() - 0.5) * 5, -10);
|
|
car.velocity = vel;
|
|
}
|
|
natives.markModelAsNoLongerNeeded(modelHash);
|
|
}
|
|
}, 500);
|
|
}
|
|
}
|
|
showNotification("CAR RAIN!");
|
|
screenShake = 1.0;
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "fun_ped_invasion":
|
|
try {
|
|
let pos = localPlayer ? localPlayer.position : new Vec3(0, 0, 0);
|
|
for (let i = 0; i < 20; i++) {
|
|
let spawnX = pos.x + (Math.random() - 0.5) * 50;
|
|
let spawnY = pos.y + (Math.random() - 0.5) * 50;
|
|
let spawnZ = pos.z;
|
|
|
|
let ped = natives.createChar(4, -1, new Vec3(spawnX, spawnY, spawnZ), true);
|
|
if (ped) {
|
|
natives.warpCharIntoCar(ped, localPlayer.vehicle);
|
|
}
|
|
}
|
|
showNotification("PED INVASION!");
|
|
screenShake = 0.5;
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "fun_gang":
|
|
try {
|
|
let pos = localPlayer ? localPlayer.position : new Vec3(0, 0, 0);
|
|
for (let i = 0; i < 8; i++) {
|
|
let spawnX = pos.x + (Math.random() - 0.5) * 20;
|
|
let spawnY = pos.y + (Math.random() - 0.5) * 20;
|
|
let spawnZ = pos.z;
|
|
let ped = natives.createChar(4, -1, new Vec3(spawnX, spawnY, spawnZ), true);
|
|
if (ped) {
|
|
natives.giveWeaponToChar(ped, 14, 500, false); // AK47
|
|
natives.giveWeaponToChar(ped, 18, 10, false); // RPG
|
|
}
|
|
}
|
|
showNotification("GANG SPAWNED!");
|
|
screenShake = 0.3;
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "fun_army":
|
|
try {
|
|
let pos = localPlayer ? localPlayer.position : new Vec3(0, 0, 0);
|
|
let armyModels = ["fbi", "police", "nstockade"];
|
|
for (let i = 0; i < 15; i++) {
|
|
let spawnX = pos.x + (Math.random() - 0.5) * 40;
|
|
let spawnY = pos.y + (Math.random() - 0.5) * 40;
|
|
let spawnZ = pos.z;
|
|
|
|
let ped = natives.createChar(4, -1, new Vec3(spawnX, spawnY, spawnZ), true);
|
|
if (ped) {
|
|
natives.giveWeaponToChar(ped, 14, 1000, false); // AK47
|
|
natives.giveWeaponToChar(ped, 16, 100, false); // Sniper
|
|
natives.giveWeaponToChar(ped, 18, 50, false); // RPG
|
|
}
|
|
}
|
|
showNotification("ARMY DEPLOYED!");
|
|
screenShake = 0.8;
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "fun_money_rain":
|
|
try {
|
|
let pos = localPlayer ? localPlayer.position : new Vec3(0, 0, 0);
|
|
for (let i = 0; i < 50; i++) {
|
|
let spawnX = pos.x + (Math.random() - 0.5) * 30;
|
|
let spawnY = pos.y + (Math.random() - 0.5) * 30;
|
|
let spawnZ = pos.z + 10 + Math.random() * 30;
|
|
natives.createMoneyPickup(new Vec3(spawnX, spawnY, spawnZ), 100, true);
|
|
}
|
|
showNotification("MONEY RAIN! $$$");
|
|
flashAlpha = 0.5;
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "fun_weapon_shower":
|
|
try {
|
|
let pos = localPlayer ? localPlayer.position : new Vec3(0, 0, 0);
|
|
let weapons = [5, 6, 9, 12, 14, 16, 18]; // Pistol, Deagle, Shotgun, SMG, AK, Sniper, RPG
|
|
for (let i = 0; i < 30; i++) {
|
|
let spawnX = pos.x + (Math.random() - 0.5) * 25;
|
|
let spawnY = pos.y + (Math.random() - 0.5) * 25;
|
|
let spawnZ = pos.z + 10 + Math.random() * 25;
|
|
let weaponId = weapons[Math.floor(Math.random() * weapons.length)];
|
|
natives.createPickup(weaponId, 2, new Vec3(spawnX, spawnY, spawnZ), true);
|
|
}
|
|
showNotification("WEAPON SHOWER!");
|
|
screenShake = 0.4;
|
|
} catch(e) {}
|
|
break;
|
|
|
|
case "fun_screen_shake":
|
|
screenShake = 1.5;
|
|
showNotification("SCREEN SHAKE!");
|
|
break;
|
|
|
|
case "fun_flash":
|
|
flashAlpha = 1.0;
|
|
showNotification("FLASH!");
|
|
break;
|
|
|
|
case "theme":
|
|
currentTheme = item.value;
|
|
showNotification("Theme: " + themes[item.value].name);
|
|
break;
|
|
|
|
// ============================================================================
|
|
// HANDLING EDITOR ACTIONS
|
|
// ============================================================================
|
|
case "handling_set":
|
|
if (localPlayer && localPlayer.vehicle) {
|
|
handlingValues[item.param] = item.value;
|
|
selectedHandlingParam = item.param;
|
|
applyHandlingValue(item.param, item.value);
|
|
showNotification(item.param + ": " + item.value);
|
|
// Trigger visual feedback
|
|
updateHandlingVisuals(item.param, item.value);
|
|
} else {
|
|
showNotification("Get in a vehicle first!");
|
|
}
|
|
break;
|
|
|
|
case "handling_reset":
|
|
if (localPlayer && localPlayer.vehicle) {
|
|
resetHandlingToDefault();
|
|
showNotification("Handling reset to default!");
|
|
} else {
|
|
showNotification("Get in a vehicle first!");
|
|
}
|
|
break;
|
|
|
|
case "handling_preset":
|
|
if (localPlayer && localPlayer.vehicle) {
|
|
applyHandlingPreset(item.value);
|
|
showNotification("Applied: " + item.label);
|
|
} else {
|
|
showNotification("Get in a vehicle first!");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
function openPlayerMenu(playerData) {
|
|
menuData.player_options = {
|
|
title: playerData.name,
|
|
items: [
|
|
{ label: "Teleport to Player", action: "teleport_to_player" }
|
|
]
|
|
};
|
|
menuStack.push({ menu: currentMenu, index: selectedIndex, scroll: scrollOffset });
|
|
currentMenu = "player_options";
|
|
selectedIndex = 0;
|
|
scrollOffset = 0;
|
|
}
|
|
|
|
// ============================================================================
|
|
// NETWORK HANDLERS
|
|
// ============================================================================
|
|
|
|
addNetworkHandler("ModMenu:PlayerList", function(playerNames, playerIds) {
|
|
playerList = [];
|
|
|
|
// Parse pipe-separated strings
|
|
if (playerNames && playerNames.length > 0) {
|
|
let names = playerNames.split("|");
|
|
let ids = playerIds.split("|");
|
|
|
|
for (let i = 0; i < names.length; i++) {
|
|
playerList.push({
|
|
name: names[i],
|
|
id: parseInt(ids[i])
|
|
});
|
|
}
|
|
}
|
|
|
|
showNotification("Found " + playerList.length + " players");
|
|
console.log("[ModMenu] Player list updated: " + playerList.length + " players");
|
|
});
|
|
|
|
addNetworkHandler("ModMenu:Notification", function(msg) {
|
|
showNotification(msg);
|
|
});
|
|
|
|
// ============================================================================
|
|
// EXECUTE HANDLERS - Client-side natives execution
|
|
// ============================================================================
|
|
|
|
// Execute weather change using GTA IV native
|
|
addNetworkHandler("ModMenu:ExecuteWeather", function(weatherId) {
|
|
try {
|
|
natives.forceWeatherNow(weatherId);
|
|
showNotification("Weather changed!");
|
|
} catch(e) {
|
|
console.log("[ModMenu] Weather error: " + e);
|
|
}
|
|
});
|
|
|
|
// Execute time change using GTA IV native
|
|
addNetworkHandler("ModMenu:ExecuteTime", function(hour) {
|
|
try {
|
|
natives.forceTimeOfDay(hour, 0);
|
|
showNotification("Time: " + hour + ":00");
|
|
} catch(e) {
|
|
console.log("[ModMenu] Time error: " + e);
|
|
}
|
|
});
|
|
|
|
// Execute self options using natives
|
|
addNetworkHandler("ModMenu:ExecuteSelfOption", function(option) {
|
|
if (!localPlayer) {
|
|
showNotification("Player not ready");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
switch(option) {
|
|
case "health":
|
|
localPlayer.health = 200;
|
|
showNotification("Health restored!");
|
|
break;
|
|
case "armor":
|
|
localPlayer.armour = 100;
|
|
showNotification("Armor restored!");
|
|
break;
|
|
case "max":
|
|
localPlayer.health = 200;
|
|
localPlayer.armour = 100;
|
|
showNotification("Max health & armor!");
|
|
break;
|
|
case "weapons":
|
|
// Give weapons using natives
|
|
natives.giveWeaponToChar(localPlayer, 5, 500, false); // Pistol
|
|
natives.giveWeaponToChar(localPlayer, 6, 500, false); // Deagle
|
|
natives.giveWeaponToChar(localPlayer, 9, 100, false); // Shotgun
|
|
natives.giveWeaponToChar(localPlayer, 12, 500, false); // SMG
|
|
natives.giveWeaponToChar(localPlayer, 14, 500, false); // AK
|
|
natives.giveWeaponToChar(localPlayer, 16, 50, false); // Sniper
|
|
natives.giveWeaponToChar(localPlayer, 18, 10, false); // RPG
|
|
showNotification("All weapons given!");
|
|
break;
|
|
case "wanted":
|
|
natives.alterWantedLevel(0, 0);
|
|
natives.applyWantedLevelChangeNow(0);
|
|
showNotification("Wanted cleared!");
|
|
break;
|
|
case "suicide":
|
|
// Kill the player properly using explode head native
|
|
natives.explodeCharHead(localPlayer);
|
|
showNotification("Goodbye!");
|
|
break;
|
|
}
|
|
} catch(e) {
|
|
console.log("[ModMenu] Self option error: " + e);
|
|
showNotification("Action failed");
|
|
}
|
|
});
|
|
|
|
// Execute teleport - with multiple fallback methods
|
|
addNetworkHandler("ModMenu:ExecuteTeleport", function(x, y, z) {
|
|
if (!localPlayer) {
|
|
showNotification("Player not ready!");
|
|
return;
|
|
}
|
|
|
|
console.log("[ModMenu] Executing teleport to: " + x + ", " + y + ", " + z);
|
|
|
|
try {
|
|
// Method 1: Direct position set
|
|
let pos = new Vec3(x, y, z);
|
|
|
|
// Check if player is in vehicle
|
|
if (localPlayer.vehicle) {
|
|
// Teleport the vehicle instead
|
|
try {
|
|
localPlayer.vehicle.position = pos;
|
|
showNotification("Teleported (in vehicle)!");
|
|
console.log("[ModMenu] Teleported vehicle successfully");
|
|
return;
|
|
} catch(e) {
|
|
console.log("[ModMenu] Vehicle teleport failed, trying player: " + e);
|
|
}
|
|
}
|
|
|
|
// Try direct position assignment
|
|
localPlayer.position = pos;
|
|
showNotification("Teleported!");
|
|
console.log("[ModMenu] Teleported player successfully");
|
|
} catch(e) {
|
|
console.log("[ModMenu] Teleport error: " + e);
|
|
|
|
// Method 2: Try using native
|
|
try {
|
|
natives.setCharCoordinates(localPlayer, x, y, z);
|
|
showNotification("Teleported!");
|
|
console.log("[ModMenu] Teleported using native successfully");
|
|
} catch(e2) {
|
|
console.log("[ModMenu] Native teleport also failed: " + e2);
|
|
showNotification("Teleport failed!");
|
|
}
|
|
}
|
|
});
|
|
|
|
// Execute teleport to player - get target player position and teleport
|
|
addNetworkHandler("ModMenu:ExecuteTeleportToPlayer", function(targetId) {
|
|
if (!localPlayer) return;
|
|
|
|
try {
|
|
// Find the target player in the player list
|
|
let clients = getClients();
|
|
for (let i = 0; i < clients.length; i++) {
|
|
if (clients[i].index == targetId && clients[i].player) {
|
|
let targetPos = clients[i].player.position;
|
|
let pos = new Vec3(targetPos.x + 2, targetPos.y, targetPos.z);
|
|
localPlayer.position = pos;
|
|
showNotification("Teleported to player!");
|
|
return;
|
|
}
|
|
}
|
|
showNotification("Player not found");
|
|
} catch(e) {
|
|
console.log("[ModMenu] Teleport to player error: " + e);
|
|
showNotification("Teleport failed");
|
|
}
|
|
});
|
|
|
|
// Vehicle model names (cleaned up for proper spawning)
|
|
const vehicleModels = [
|
|
// Sports Cars
|
|
"infernus", "turismo", "comet", "banshee", "sultan", "coquette",
|
|
// Muscle Cars
|
|
"buccaneer", "dukes", "faction", "ruiner", "sabre", "sabregt", "vigero",
|
|
// Sedans
|
|
"admiral", "cavalcade", "cognoscenti", "emperor", "esperanto", "feroci",
|
|
"feltzer", "intruder", "landstalker", "lokus", "marbella", "merit",
|
|
"oracle", "pinnacle", "premiere", "presidente", "primo", "rebla",
|
|
"romero", "schafter", "sentinel", "solair", "stratum", "stretch",
|
|
"vincent", "virgo", "willard", "washington",
|
|
// SUVs & Trucks
|
|
"bobcat", "boxville", "biff", "burrito", "chavos", "dilettante",
|
|
"flatbed", "forklift", "habanero", "huntley", "moonbeam", "mule",
|
|
"packer", "patriot", "perennial", "pony", "rancher", "speedo",
|
|
"stockade", "trashmaster", "yankee",
|
|
// Compacts
|
|
"blista", "futo", "ingot", "pmp600", "sultanrs",
|
|
// Motorcycles
|
|
"faggio", "hellfury", "nrg900", "pcj600", "sanchez", "zombie",
|
|
// Emergency
|
|
"ambulance", "firetruk", "police", "police2", "police3", "police4",
|
|
"nstockade", "pstockade", "fbi", "noose",
|
|
// Helicopters
|
|
"annihilator", "maverick", "polmav", "tourmav",
|
|
// Boats
|
|
"dinghy", "jetmax", "marquis", "predator", "reefer", "squalo", "tropic", "tuga",
|
|
// Commercial
|
|
"airtug", "benson", "biff", "boxville", "burrito", "burrito2", "cabby",
|
|
"flatbed", "forklift", "mule", "packer", "pony", "speedo", "stockade",
|
|
"trashmaster", "yankee",
|
|
// Special
|
|
"mrtasty", "cablecar", "subway", "eltrain",
|
|
// Taxis
|
|
"taxi", "taxi2", "romantaxi"
|
|
];
|
|
|
|
// Vehicle model hashes - SIGNED 32-bit integers from GTAConnected wiki
|
|
// DO NOT use 0x prefix with decimal numbers! Use actual values.
|
|
const vehicleHashes = {
|
|
// Sports Cars
|
|
"infernus": 418536135,
|
|
"turismo": -1896659641,
|
|
"comet": 1063483177,
|
|
"banshee": -1041692462,
|
|
"sultan": 970598228,
|
|
"coquette": 108773431,
|
|
"supergt": 1821991593,
|
|
"futo": 2016857647,
|
|
// Muscle Cars
|
|
"buccaneer": -682211828,
|
|
"dukes": 723973206,
|
|
"faction": -2119578145,
|
|
"fortune": 627033353,
|
|
"ruiner": -227741703,
|
|
"sabre": -449022887,
|
|
"sabregt": -1685021548,
|
|
"stallion": 1923400478,
|
|
"vigero": -825837129,
|
|
// Sedans
|
|
"admiral": 1264341792,
|
|
"cognoscenti": -2030171296,
|
|
"emperor": -685276541,
|
|
"esperanto": -276900515,
|
|
"feroci": 974744810,
|
|
"feltzer": -1097828879,
|
|
"intruder": 886934177,
|
|
"lokus": -37030056,
|
|
"marbella": 1304597482,
|
|
"merit": -1260881538,
|
|
"oracle": 1348744438,
|
|
"pinnacle": 131140572,
|
|
"premier": -1883869285,
|
|
"presidente": -1962071130,
|
|
"primo": -1150599089,
|
|
"rebla": 83136452,
|
|
"romero": 627094268,
|
|
"schafter": -322343873,
|
|
"sentinel": 1349725314,
|
|
"solair": 1344573448,
|
|
"stratum": 1723137093,
|
|
"stretch": -1961627517,
|
|
"vincent": -583281407,
|
|
"virgo": -498054846,
|
|
"willard": 1937616578,
|
|
"washington": 1777363799,
|
|
// SUVs & Trucks
|
|
"bobcat": 1075851868,
|
|
"cavalcade": 2006918058,
|
|
"habanero": 884422927,
|
|
"huntley": 486987393,
|
|
"landstalker": 1269098716,
|
|
"patriot": -808457413,
|
|
"rancher": 1390084576,
|
|
// Vans & Commercial
|
|
"benson": 2053223216,
|
|
"biff": 850991848,
|
|
"boxville": -1987130134,
|
|
"burrito": -1346687836,
|
|
"burrito2": -907477130,
|
|
"cabby": 1884962369,
|
|
"flatbed": 1353720154,
|
|
"forklift": 1491375716,
|
|
"moonbeam": 525509695,
|
|
"mule": 904750859,
|
|
"packer": 569305213,
|
|
"perennial": -2077743597,
|
|
"pony": -119658072,
|
|
"speedo": -810318068,
|
|
"stockade": 1747439474,
|
|
"trashmaster": 1917016601,
|
|
"yankee": -1099960214,
|
|
// Compacts
|
|
"blista": -344943009,
|
|
"chavos": -67282078,
|
|
"dilettante": -1130810103,
|
|
"ingot": -1289722222,
|
|
"pmp600": 1376298265,
|
|
"sultanrs": -295689028,
|
|
// Motorcycles
|
|
"bobber": -1830458836,
|
|
"faggio": -1842748181,
|
|
"freeway": 1534326199,
|
|
"hellfury": 584879743,
|
|
"nrg900": 1203311498,
|
|
"pcj600": -909201658,
|
|
"sanchez": 788045382,
|
|
"zombie": -570033273,
|
|
// Emergency
|
|
"ambulance": 1171614426,
|
|
"firetruk": 1938952078,
|
|
"police": 2046537925,
|
|
"police2": -1627000575,
|
|
"policepatriot": -350085182,
|
|
"fbi": 1127131465,
|
|
"noose": 148777611,
|
|
"nstockade": -1900572838,
|
|
"pstockade": 1911513875,
|
|
// Helicopters
|
|
"annihilator": 837858166,
|
|
"maverick": -1660661558,
|
|
"polmav": 353883353,
|
|
"tourmav": 2027357303,
|
|
// Boats
|
|
"dinghy": 1033245328,
|
|
"jetmax": 861409633,
|
|
"marquis": -1043459709,
|
|
"predator": -488123221,
|
|
"reefer": 1759673526,
|
|
"squalo": 400514754,
|
|
"tropic": 290013743,
|
|
"tuga": 1064455782,
|
|
// Commercial & Special
|
|
"airtug": 1560980623,
|
|
"bus": -713569950,
|
|
"mrtasty": 583100975,
|
|
// Trains (may not spawn)
|
|
"cablecar": -960289747,
|
|
"subway": 800869680,
|
|
"eltrain": -1953988645,
|
|
// Taxis
|
|
"taxi": -956048545,
|
|
"taxi2": 1208856469,
|
|
"romantaxi": -1932515764
|
|
};
|
|
|
|
// Execute vehicle spawn using natives
|
|
addNetworkHandler("ModMenu:ExecuteSpawnVehicle", function(vehicleName) {
|
|
try {
|
|
if (!localPlayer) {
|
|
showNotification("Not ready");
|
|
return;
|
|
}
|
|
|
|
// Clean up vehicle name and map to correct hash
|
|
let cleanName = vehicleName.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
|
|
let modelHash = vehicleHashes[cleanName];
|
|
if (!modelHash) {
|
|
showNotification("Unknown vehicle: " + cleanName);
|
|
return;
|
|
}
|
|
|
|
console.log("[ModMenu] Spawning vehicle: " + vehicleName + " (hash: " + modelHash + ")");
|
|
|
|
// Request the model first
|
|
natives.requestModel(modelHash);
|
|
|
|
// Wait for model to load then spawn
|
|
let attempts = 0;
|
|
let spawnInterval = setInterval(function() {
|
|
attempts++;
|
|
if (natives.hasModelLoaded(modelHash)) {
|
|
clearInterval(spawnInterval);
|
|
|
|
let pos = localPlayer.position;
|
|
let heading = localPlayer.heading || 0;
|
|
|
|
// Spawn position in front of player
|
|
let spawnX = pos.x + Math.sin(-heading) * 5;
|
|
let spawnY = pos.y + Math.cos(-heading) * 5;
|
|
let spawnZ = pos.z + 0.5;
|
|
|
|
// Create spawn position as Vec3 (GTAConnected requires Vec3)
|
|
let spawnPos = new Vec3(spawnX, spawnY, spawnZ);
|
|
|
|
// Create the car - GTAConnected: createCar(hash, Vec3, bool)
|
|
let vehicle = natives.createCar(modelHash, spawnPos, true);
|
|
|
|
if (vehicle) {
|
|
// Set vehicle heading to match player
|
|
natives.setCarHeading(vehicle, heading);
|
|
// Warp player into the vehicle
|
|
natives.warpCharIntoCar(localPlayer, vehicle);
|
|
showNotification("Spawned: " + vehicleName);
|
|
console.log("[ModMenu] Vehicle spawned successfully");
|
|
} else {
|
|
showNotification("Failed to create vehicle");
|
|
}
|
|
|
|
// Mark model as no longer needed
|
|
natives.markModelAsNoLongerNeeded(modelHash);
|
|
} else if (attempts > 100) {
|
|
clearInterval(spawnInterval);
|
|
showNotification("Model load timeout for: " + vehicleName);
|
|
}
|
|
}, 50); // Check every 50ms instead of 100ms
|
|
} catch(e) {
|
|
console.log("[ModMenu] Vehicle spawn error: " + e);
|
|
showNotification("Error: " + e);
|
|
}
|
|
});
|
|
|
|
// Execute vehicle options
|
|
addNetworkHandler("ModMenu:ExecuteVehicleOption", function(option) {
|
|
if (!localPlayer || !localPlayer.vehicle) {
|
|
showNotification("Get in a vehicle first!");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
let veh = localPlayer.vehicle;
|
|
switch(option) {
|
|
case "repair":
|
|
natives.fixCar(veh);
|
|
showNotification("Vehicle repaired!");
|
|
break;
|
|
case "flip":
|
|
let rot = veh.rotation;
|
|
veh.rotation = new Vec3(0, 0, rot.z);
|
|
showNotification("Vehicle flipped!");
|
|
break;
|
|
case "nitro":
|
|
// Boost vehicle forward using velocity
|
|
let heading = veh.heading || 0;
|
|
let speed = 50.0;
|
|
let vx = Math.sin(heading) * speed * -1;
|
|
let vy = Math.cos(heading) * speed;
|
|
let vel = new Vec3(vx, vy, 5);
|
|
veh.velocity = vel;
|
|
showNotification("NITRO!");
|
|
break;
|
|
}
|
|
} catch(e) {
|
|
console.log("[ModMenu] Vehicle option error: " + e);
|
|
}
|
|
});
|
|
|
|
// Execute vehicle color change
|
|
addNetworkHandler("ModMenu:ExecuteVehicleColor", function(color1, color2) {
|
|
if (!localPlayer || !localPlayer.vehicle) {
|
|
showNotification("Get in a vehicle first!");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
natives.changeCarColour(localPlayer.vehicle, color1, color2);
|
|
showNotification("Color changed!");
|
|
} catch(e) {
|
|
console.log("[ModMenu] Color change error: " + e);
|
|
}
|
|
});
|
|
|
|
// Execute weapon give
|
|
addNetworkHandler("ModMenu:ExecuteGiveWeapon", function(weaponId) {
|
|
if (!localPlayer) return;
|
|
|
|
try {
|
|
natives.giveWeaponToChar(localPlayer, weaponId, 500, false);
|
|
showNotification("Weapon given!");
|
|
} catch(e) {
|
|
console.log("[ModMenu] Weapon error: " + e);
|
|
}
|
|
});
|
|
|
|
// Execute fun options
|
|
addNetworkHandler("ModMenu:ExecuteFun", function(option) {
|
|
if (!localPlayer) return;
|
|
|
|
try {
|
|
let pos = localPlayer.position;
|
|
switch(option) {
|
|
case "launch":
|
|
let launchPos = new Vec3(pos.x, pos.y, pos.z + 50);
|
|
localPlayer.position = launchPos;
|
|
showNotification("LAUNCH!");
|
|
break;
|
|
case "explode":
|
|
natives.addExplosion(pos.x, pos.y, pos.z, 0, 5.0, true, false, 1.0);
|
|
break;
|
|
case "ragdoll":
|
|
natives.switchPedToRagdoll(localPlayer, 1000, 1000, 0, true, true, false);
|
|
break;
|
|
}
|
|
} catch(e) {
|
|
console.log("[ModMenu] Fun option error: " + e);
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// GTA IV CHARACTER MODEL HASHES - Verified Correct Values
|
|
// ============================================================================
|
|
|
|
// Story Characters - Main (using correct IG_ model hashes)
|
|
const SKIN_ROMAN = 2302238665; // IG_ROMAN
|
|
const SKIN_MALLORIE = 3254679890; // IG_MALLORIE
|
|
const SKIN_JACOB = 1487004273; // IG_LILJACOB
|
|
const SKIN_BRUCIE = 2564987168; // IG_BRUCIE
|
|
const SKIN_PACKIE = 1690783035; // IG_PACKIE_MC
|
|
const SKIN_DWAYNE = 3677703193; // IG_DWAYNE
|
|
const SKIN_PLAYBOY = 1794146792; // IG_PLAYBOY_X
|
|
const SKIN_DIMITRI = 237497537; // IG_DMITRI
|
|
const SKIN_FAUSTIN = 57218969; // IG_FAUSTIN
|
|
const SKIN_BULGARIN = 237511807; // IG_BULGARIN
|
|
const SKIN_PEGORINO = 1884668464; // CS_JIMMY_PEGORINO
|
|
const SKIN_PHIL_BELL = 2468508362; // IG_PHIL_BELL
|
|
const SKIN_RAY_BOCCINO = 954215094; // IG_RAY_BOCCINO
|
|
const SKIN_DERRICK = 1169442297; // IG_DERRICK_MC
|
|
const SKIN_FRANCIS = 1710545037; // IG_FRANCIS_MC
|
|
const SKIN_GERALD = 652098186; // CS_GERRYMC
|
|
const SKIN_KATE = 3521216458; // IG_KATEMC
|
|
const SKIN_BERNIE = 1500493064; // IG_BERNIE_CRANE
|
|
const SKIN_MANNY = 1445589009; // IG_MANNY
|
|
const SKIN_ELIZABETA = 2933135023; // CS_ELIZABETA
|
|
const SKIN_MICHELLE = 3214308084; // IG_MICHELLE
|
|
const SKIN_VLAD = 896408642; // IG_VLAD
|
|
const SKIN_ILYENA = 3459742170; // IG_ILYENA
|
|
const SKIN_HOSSAN = 980768434; // IG_HOSSAN
|
|
|
|
// TLAD Characters
|
|
const SKIN_JOHNNY = 8206123; // IG_JOHNNY2
|
|
const SKIN_JIM = 870892404; // IG_JIM_FITZ
|
|
const SKIN_TERRY = 1728056212; // IG_TERRY
|
|
const SKIN_CLAY = 1825562762; // IG_CLAY
|
|
const SKIN_BRIAN = 349841464; // IG_BRIANJ
|
|
const SKIN_BILLY = 3843248439; // IG_BILLY
|
|
const SKIN_JASON = 3346030785; // IG_JASON_M
|
|
const SKIN_ANGUS = 1917871822; // LOSTBUDDY_13
|
|
const SKIN_DAVE = 3056906300; // IG_DAVE_GROSSMAN
|
|
const SKIN_LOST_BIKER1 = 1914397972; // LOSTBUDDY_01
|
|
const SKIN_LOST_BIKER2 = 2156528113; // LOSTBUDDY_02
|
|
const SKIN_ASHLEY = 3567004438; // IG_ASHLEYA
|
|
const SKIN_STUBBS = 2513523815; // IG_STROOPER
|
|
|
|
// TBOGT Characters
|
|
const SKIN_LUIS = 3802496606; // IG_LUIS
|
|
const SKIN_TONY = 4020398429; // IG_TONY
|
|
const SKIN_YUSUF = 3846796161; // IG_YUSEF
|
|
const SKIN_ARMANDO = 1370299619; // IG_ARMANDO
|
|
const SKIN_HENRIQUE = 1905515841; // IG_HENRIQUE
|
|
const SKIN_ROCCO = 3381042378; // IG_ROCCO
|
|
const SKIN_TIMUR = 2345614827; // IG_TIMUR
|
|
const SKIN_MORI = 1662225612; // IG_MORI_K
|
|
const SKIN_DESSIE = 2848083183; // IG_DESSIE
|
|
const SKIN_JONI = 3412908435; // F_Y_JONI
|
|
const SKIN_TROY = 1662473323; // IG_TROY
|
|
const SKIN_BOUNCER = 2514268405; // M_Y_BOUNCER_01
|
|
const SKIN_EVAN = 3497746837; // IG_EVAN
|
|
const SKIN_KERRY = 763838720; // F_Y_TRENDY_01
|
|
const SKIN_POPPY = 3450748540; // F_Y_PGIRL_01
|
|
const SKIN_CLOE = 2802928488; // F_Y_CLOEPARKER
|
|
|
|
// Law Enforcement
|
|
const SKIN_COP = 4111764146; // M_Y_COP
|
|
const SKIN_COP2 = 4111764146; // M_Y_COP
|
|
const SKIN_COP_FAT = 3924571768; // M_M_FATCOP_01
|
|
const SKIN_COP_TRAFFIC = 2776029317; // M_Y_COP_TRAFFIC
|
|
const SKIN_DETECTIVE = 3295460374; // M_M_FBI
|
|
const SKIN_NOOSE = 3290204350; // M_Y_SWAT
|
|
const SKIN_NOOSE_TACTICAL = 3290204350; // M_Y_SWAT
|
|
const SKIN_NOOSE_COMMANDER = 3290204350; // M_Y_SWAT
|
|
const SKIN_FIB = 3295460374; // M_M_FBI
|
|
const SKIN_PRISON_GUARD = 2378673688; // M_Y_PRISONGUARD
|
|
const SKIN_PRISON_GUARD2 = 2378673688; // M_Y_PRISONGUARD
|
|
const SKIN_SECURITY = 2423978125; // M_M_SECURITYMAN
|
|
|
|
// Emergency Services
|
|
const SKIN_FIREMAN = 3684742681; // M_Y_FIREMAN
|
|
const SKIN_FIREMAN2 = 3684742681; // M_Y_FIREMAN
|
|
const SKIN_PARAMEDIC = 3119890080; // M_Y_PMEDIC
|
|
const SKIN_DOCTOR = 3108026518; // M_M_DOCTOR_01
|
|
const SKIN_NURSE = 346338575; // F_Y_DOCTOR_01
|
|
const SKIN_NURSE2 = 3101188907; // F_Y_NURSE
|
|
|
|
// Gang Members
|
|
const SKIN_RUSSIAN1 = 2206803240; // M_Y_GRUS_LO_01
|
|
const SKIN_RUSSIAN2 = 1976502708; // M_Y_GRUS_LO_02
|
|
const SKIN_RUSSIAN3 = 1543404628; // M_Y_GRUS_HI_02
|
|
const SKIN_ITALIAN1 = 2892525257; // M_M_PITALIAN_01
|
|
const SKIN_ITALIAN2 = 3381042378; // IG_ROCCO
|
|
const SKIN_ALBANIAN1 = 3791037286; // M_Y_GALB_LO_01
|
|
const SKIN_ALBANIAN2 = 4059382627; // M_Y_GALB_LO_02
|
|
const SKIN_JAMAICAN1 = 1487004273; // IG_LILJACOB
|
|
const SKIN_JAMAICAN2 = 2794569427; // M_Y_GJAM_LO_01
|
|
const SKIN_JAMAICAN3 = 3413608606; // M_Y_GJAM_LO_02
|
|
const SKIN_TRIAD1 = 3210959519; // M_Y_GTRI_LO_01
|
|
const SKIN_TRIAD2 = 4130031670; // M_Y_GTRI_LO_02
|
|
|
|
// Male Civilians
|
|
const SKIN_BUSINESS = 1530937394; // M_Y_BUSINESS_01
|
|
const SKIN_STREET = 62496225; // M_Y_STREET_01
|
|
const SKIN_HOBO = 3214294247; // M_M_GENBUM_01
|
|
const SKIN_BIKER = 2156528113; // LOSTBUDDY_02
|
|
const SKIN_HIPSTER = 2085884255; // M_Y_BOHO_01
|
|
const SKIN_WORKER = 3572947498; // M_Y_CONSTRUCT_01
|
|
|
|
// Female Civilians
|
|
const SKIN_F_BUSINESS = 453889158; // F_Y_BUSINESS_01
|
|
const SKIN_F_STREET = 3394344139; // F_Y_STREET_02
|
|
const SKIN_F_RICH = 2514581497; // F_Y_PRICH_01
|
|
const SKIN_F_SHOP = 1586287288; // F_Y_SHOP_03
|
|
const SKIN_F_TOURIST = 1754440500; // F_Y_TOURIST_01
|
|
const SKIN_F_JOGGER = 1350216795; // F_Y_GYMGAL_01
|
|
const SKIN_F_CLUB = 3412908435; // F_Y_JONI
|
|
const SKIN_F_MODEL = 3450748540; // F_Y_PGIRL_01
|
|
const SKIN_F_PARTY = 2802928488; // F_Y_CLOEPARKER
|
|
|
|
// ============================================================================
|
|
// RANDOM SKIN SELECTION ARRAYS
|
|
// ============================================================================
|
|
|
|
// All male character skins for random selection
|
|
const maleSkins = [
|
|
// Story Characters
|
|
SKIN_ROMAN, SKIN_JACOB, SKIN_BRUCIE, SKIN_PACKIE,
|
|
SKIN_DWAYNE, SKIN_PLAYBOY, SKIN_DIMITRI, SKIN_FAUSTIN, SKIN_BULGARIN,
|
|
SKIN_PEGORINO, SKIN_PHIL_BELL, SKIN_RAY_BOCCINO, SKIN_DERRICK,
|
|
SKIN_FRANCIS, SKIN_GERALD, SKIN_BERNIE, SKIN_MANNY, SKIN_VLAD, SKIN_HOSSAN,
|
|
// TLAD
|
|
SKIN_JOHNNY, SKIN_JIM, SKIN_TERRY, SKIN_CLAY, SKIN_BRIAN, SKIN_BILLY,
|
|
SKIN_JASON, SKIN_ANGUS, SKIN_DAVE, SKIN_LOST_BIKER1, SKIN_LOST_BIKER2,
|
|
// TBOGT
|
|
SKIN_LUIS, SKIN_TONY, SKIN_YUSUF, SKIN_ARMANDO, SKIN_HENRIQUE,
|
|
SKIN_ROCCO, SKIN_TIMUR, SKIN_MORI, SKIN_TROY, SKIN_BOUNCER, SKIN_EVAN,
|
|
// Law & Emergency
|
|
SKIN_COP, SKIN_COP2, SKIN_NOOSE, SKIN_FIREMAN, SKIN_PARAMEDIC,
|
|
// Civilians & Gangs
|
|
SKIN_BUSINESS, SKIN_STREET, SKIN_HOBO, SKIN_BIKER, SKIN_HIPSTER,
|
|
SKIN_WORKER, SKIN_RUSSIAN1, SKIN_ITALIAN1, SKIN_JAMAICAN1
|
|
];
|
|
|
|
// All female character skins for random selection
|
|
const femaleSkins = [
|
|
// Story Characters
|
|
SKIN_MALLORIE, SKIN_KATE, SKIN_ELIZABETA, SKIN_MICHELLE, SKIN_ILYENA,
|
|
// TLAD
|
|
SKIN_ASHLEY,
|
|
// TBOGT
|
|
SKIN_DESSIE, SKIN_JONI, SKIN_KERRY, SKIN_POPPY, SKIN_CLOE,
|
|
// Civilians
|
|
SKIN_F_BUSINESS, SKIN_F_STREET, SKIN_F_RICH, SKIN_F_SHOP, SKIN_NURSE,
|
|
SKIN_F_TOURIST, SKIN_F_JOGGER, SKIN_F_CLUB, SKIN_F_MODEL, SKIN_F_PARTY
|
|
];
|
|
|
|
// Legacy compatibility aliases
|
|
const mpBoySkins = maleSkins;
|
|
const mpGirlSkins = femaleSkins;
|
|
|
|
// Execute skin change
|
|
addNetworkHandler("ModMenu:ExecuteSkinChange", function(skinId) {
|
|
if (!localPlayer) return;
|
|
|
|
try {
|
|
if (skinId === "random") {
|
|
// Random from all skins
|
|
let allSkins = maleSkins.concat(femaleSkins);
|
|
skinId = allSkins[Math.floor(Math.random() * allSkins.length)];
|
|
} else if (skinId === "random_male" || skinId === "random_mp_boy") {
|
|
// Random male skin
|
|
skinId = maleSkins[Math.floor(Math.random() * maleSkins.length)];
|
|
} else if (skinId === "random_female" || skinId === "random_mp_girl") {
|
|
// Random female skin
|
|
skinId = femaleSkins[Math.floor(Math.random() * femaleSkins.length)];
|
|
}
|
|
|
|
// Ensure skinId is a valid number
|
|
skinId = parseInt(skinId);
|
|
if (isNaN(skinId)) {
|
|
console.log("[ModMenu] Invalid skin ID");
|
|
showNotification("Invalid skin ID");
|
|
return;
|
|
}
|
|
|
|
console.log("[ModMenu] Changing skin to: " + skinId);
|
|
|
|
// Request the model to be loaded
|
|
natives.requestModel(skinId);
|
|
|
|
// Use interval to check if model is actually loaded
|
|
let attempts = 0;
|
|
let maxAttempts = 100; // 5 seconds max wait
|
|
let loadInterval = setInterval(function() {
|
|
attempts++;
|
|
|
|
try {
|
|
// Force streaming to load the model
|
|
natives.loadAllObjectsNow();
|
|
|
|
// Check if model has loaded using hasModelLoaded
|
|
let isLoaded = false;
|
|
try {
|
|
isLoaded = natives.hasModelLoaded(skinId);
|
|
} catch(e) {
|
|
// If hasModelLoaded fails, try after some attempts
|
|
if (attempts > 10) {
|
|
isLoaded = true; // Assume loaded after enough time
|
|
}
|
|
}
|
|
|
|
if (isLoaded) {
|
|
clearInterval(loadInterval);
|
|
|
|
// Get player index
|
|
let playerIndex = natives.getPlayerId();
|
|
|
|
// Change the player model
|
|
natives.changePlayerModel(playerIndex, skinId);
|
|
|
|
// Refresh player ped reference
|
|
try {
|
|
natives.loadAllObjectsNow();
|
|
} catch(e) {}
|
|
|
|
console.log("[ModMenu] Skin changed successfully to: " + skinId);
|
|
showNotification("Skin changed!");
|
|
return;
|
|
}
|
|
|
|
// Re-request if not loaded yet
|
|
if (attempts % 10 === 0) {
|
|
natives.requestModel(skinId);
|
|
}
|
|
|
|
} catch(e) {
|
|
console.log("[ModMenu] Skin load attempt " + attempts + ": " + e);
|
|
}
|
|
|
|
if (attempts >= maxAttempts) {
|
|
clearInterval(loadInterval);
|
|
console.log("[ModMenu] Skin load timeout for: " + skinId);
|
|
showNotification("Skin load failed - model may not exist");
|
|
}
|
|
}, 50);
|
|
|
|
} catch(e) {
|
|
console.log("[ModMenu] Skin change error: " + e);
|
|
showNotification("Skin change failed");
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// RENDERING - MD REVOLUTION MOD MENU
|
|
// Clean Modern Dark Theme with customizable accent colors
|
|
// ============================================================================
|
|
|
|
// Animation Effects Update - Smooth transitions only
|
|
addEventHandler("OnProcess", function(event) {
|
|
// Update animation time
|
|
animTime += 0.016;
|
|
|
|
// Smooth selection interpolation
|
|
smoothSelectedIndex += (selectedIndex - smoothSelectedIndex) * 0.15;
|
|
|
|
// Selection alpha pulse (subtle)
|
|
selectionAlpha = 0.5 + Math.sin(animTime * 2) * 0.1;
|
|
|
|
// Scrollbar glow pulse animation
|
|
scrollbarPulse += 0.04;
|
|
scrollbarGlow = 0.5 + Math.sin(scrollbarPulse) * 0.5;
|
|
|
|
// Selection glow pulse (breathing effect)
|
|
selectionGlow += 0.025 * selectionPulseDir;
|
|
if (selectionGlow >= 1) selectionPulseDir = -1;
|
|
if (selectionGlow <= 0.3) selectionPulseDir = 1;
|
|
|
|
// Accent line glow animation
|
|
accentLineGlow = 0.7 + Math.sin(animTime * 1.5) * 0.3;
|
|
|
|
// Screen shake decay
|
|
if (screenShake > 0) {
|
|
screenShake -= 0.1;
|
|
if (screenShake < 0) screenShake = 0;
|
|
}
|
|
|
|
// Flash effect decay
|
|
if (flashAlpha > 0) {
|
|
flashAlpha -= 0.05;
|
|
if (flashAlpha < 0) flashAlpha = 0;
|
|
}
|
|
|
|
// Apply continuous effects when menu is closed
|
|
if (!menuOpen && localPlayer) {
|
|
try {
|
|
// Moon gravity
|
|
if (toggleStates.moonGravity) {
|
|
localPlayer.gravity = 0.1;
|
|
} else if (localPlayer.gravity !== 9.8) {
|
|
localPlayer.gravity = 9.8;
|
|
}
|
|
|
|
// Drunk mode - wobble effect
|
|
if (toggleStates.drunkMode) {
|
|
try {
|
|
natives.setGameCamShake(true, 2);
|
|
natives.shakeCam(1, 100);
|
|
} catch(e) {
|
|
try {
|
|
natives.setGameCamHeading(Math.sin(animTime * 3) * 5);
|
|
} catch(e2) {}
|
|
}
|
|
}
|
|
|
|
// Night Vision
|
|
if (toggleStates.nightVision) {
|
|
try {
|
|
natives.setNightvision(true);
|
|
} catch(e) {
|
|
try {
|
|
natives.setTimecycleModifier("nightvision");
|
|
} catch(e2) {}
|
|
}
|
|
}
|
|
|
|
// Thermal Vision
|
|
if (toggleStates.thermalVision) {
|
|
try {
|
|
natives.setInfaredvision(true);
|
|
} catch(e) {
|
|
try {
|
|
natives.setTimecycleModifier("thermal");
|
|
} catch(e2) {}
|
|
}
|
|
}
|
|
|
|
// Clear vision effects when both disabled
|
|
if (!toggleStates.nightVision && !toggleStates.thermalVision) {
|
|
try {
|
|
natives.setNightvision(false);
|
|
natives.setInfaredvision(false);
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Screen shake effect
|
|
if (screenShake > 0.01) {
|
|
try {
|
|
let shakeAmount = screenShake * 50;
|
|
natives.shakeCam(1, Math.floor(shakeAmount));
|
|
} catch(e) {
|
|
try {
|
|
natives.setGameCamShake(true, Math.floor(screenShake * 3));
|
|
} catch(e2) {}
|
|
}
|
|
}
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Matrix effect update (for matrix mode toggle)
|
|
if (toggleStates.matrixMode) {
|
|
matrixEffect += 0.1;
|
|
} else {
|
|
matrixEffect *= 0.9;
|
|
}
|
|
|
|
// Smooth scroll interpolation
|
|
smoothScrollY += (targetScrollY - smoothScrollY) * 0.12;
|
|
|
|
// Menu open animation - smooth ease out
|
|
if (menuOpen && menuOpenAnim < 1) {
|
|
menuOpenAnim += (1 - menuOpenAnim) * 0.12;
|
|
if (menuOpenAnim > 0.99) menuOpenAnim = 1;
|
|
} else if (!menuOpen && menuOpenAnim > 0) {
|
|
menuOpenAnim -= menuOpenAnim * 0.15;
|
|
if (menuOpenAnim < 0.01) menuOpenAnim = 0;
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// CLEAN MODERN MENU RENDERING - Dark Glassmorphism Style
|
|
// ============================================================================
|
|
addEventHandler("OnDrawnHUD", function(event) {
|
|
if (menuOpenAnim <= 0) return;
|
|
|
|
let currentData = menuData[currentMenu];
|
|
let items = getCurrentMenuItems();
|
|
let title = currentData ? currentData.title : currentMenu.toUpperCase();
|
|
let theme = getTheme();
|
|
|
|
let visibleCount = Math.min(items.length, menu.maxVisibleItems);
|
|
let totalHeight = menu.headerHeight + (visibleCount * menu.itemHeight) + menu.footerHeight;
|
|
|
|
let animAlpha = Math.floor(255 * menuOpenAnim);
|
|
|
|
// Smooth slide animation (no bounce, no elastic)
|
|
let slideOffset = (1 - menuOpenAnim) * 80;
|
|
let baseX = menu.x + slideOffset;
|
|
let baseY = menu.y;
|
|
|
|
// ===== SUBTLE DROP SHADOW =====
|
|
let shadowAlpha = Math.floor(80 * menuOpenAnim);
|
|
drawRect(baseX + 4, baseY + 4, menu.width, totalHeight + 10, toColour(0, 0, 0, shadowAlpha));
|
|
|
|
// ===== MAIN PANEL BACKGROUND - Clean dark =====
|
|
let panelBg = toColour(UI.bgDark.r, UI.bgDark.g, UI.bgDark.b, Math.floor(245 * menuOpenAnim));
|
|
drawRect(baseX, baseY, menu.width, totalHeight + 10, panelBg);
|
|
|
|
// ===== SUBTLE BORDER =====
|
|
let borderAlpha = Math.floor(100 * menuOpenAnim);
|
|
let borderCol = toColour(UI.border.r, UI.border.g, UI.border.b, borderAlpha);
|
|
drawRect(baseX, baseY, menu.width, 1, borderCol);
|
|
drawRect(baseX, baseY + totalHeight + 9, menu.width, 1, borderCol);
|
|
drawRect(baseX, baseY, 1, totalHeight + 10, borderCol);
|
|
drawRect(baseX + menu.width - 1, baseY, 1, totalHeight + 10, borderCol);
|
|
|
|
// ===== HEADER =====
|
|
let headerY = baseY;
|
|
let headerH = menu.headerHeight;
|
|
|
|
// Header background - slightly lighter
|
|
let headerBg = toColour(UI.bgPanel.r, UI.bgPanel.g, UI.bgPanel.b, Math.floor(255 * menuOpenAnim));
|
|
drawRect(baseX + 1, headerY + 1, menu.width - 2, headerH - 1, headerBg);
|
|
|
|
// Accent line at top of header with glow animation
|
|
let glowLineAlpha = Math.floor(60 * menuOpenAnim * accentLineGlow);
|
|
let accentGlowCol = toColour(theme.accent.r, theme.accent.g, theme.accent.b, glowLineAlpha);
|
|
drawRect(baseX - 2, headerY - 3, menu.width + 4, 8, accentGlowCol);
|
|
|
|
let accentCol = toColour(theme.accent.r, theme.accent.g, theme.accent.b, animAlpha);
|
|
drawRect(baseX, headerY, menu.width, 2, accentCol);
|
|
|
|
// Bright center highlight
|
|
let brightAccent = toColour(
|
|
Math.min(255, theme.accent.r + 80),
|
|
Math.min(255, theme.accent.g + 80),
|
|
Math.min(255, theme.accent.b + 80),
|
|
Math.floor(animAlpha * accentLineGlow)
|
|
);
|
|
drawRect(baseX + menu.width * 0.25, headerY, menu.width * 0.5, 2, brightAccent);
|
|
|
|
// Title text
|
|
let logoX = baseX + 16;
|
|
let logoY = headerY + 18;
|
|
|
|
// "MD REVOLUTION" - Clean white text
|
|
let titleCol = toColour(UI.textPrimary.r, UI.textPrimary.g, UI.textPrimary.b, animAlpha);
|
|
drawText("MD", logoX, logoY, titleCol, 26);
|
|
|
|
let revCol = toColour(theme.accent.r, theme.accent.g, theme.accent.b, animAlpha);
|
|
drawText("REVOLUTION", logoX + 58, logoY + 4, revCol, 18);
|
|
|
|
// Submenu title
|
|
let subTitleY = headerY + headerH - 26;
|
|
let subText = currentMenu === "main" ? "MAIN MENU" : title;
|
|
let subCol = toColour(UI.textSecondary.r, UI.textSecondary.g, UI.textSecondary.b, animAlpha);
|
|
drawText(subText, baseX + 16, subTitleY, subCol, 12);
|
|
|
|
// Theme indicator badge
|
|
let themeName = theme.name.toUpperCase();
|
|
let badgeX = baseX + menu.width - 16 - (themeName.length * 6);
|
|
let badgeBg = toColour(UI.bgHover.r, UI.bgHover.g, UI.bgHover.b, Math.floor(200 * menuOpenAnim));
|
|
drawRect(badgeX - 8, headerY + 12, themeName.length * 6 + 16, 18, badgeBg);
|
|
let badgeTextCol = toColour(theme.accent.r, theme.accent.g, theme.accent.b, animAlpha);
|
|
drawText(themeName, badgeX, headerY + 15, badgeTextCol, 10);
|
|
|
|
// ===== SEPARATOR LINE =====
|
|
let sepY = baseY + menu.headerHeight;
|
|
let sepCol = toColour(UI.border.r, UI.border.g, UI.border.b, Math.floor(150 * menuOpenAnim));
|
|
drawRect(baseX + 1, sepY, menu.width - 2, 1, sepCol);
|
|
|
|
// ===== MENU ITEMS =====
|
|
let yPos = baseY + menu.headerHeight + 1;
|
|
targetScrollY = scrollOffset * menu.itemHeight;
|
|
|
|
for (let i = scrollOffset; i < scrollOffset + visibleCount && i < items.length; i++) {
|
|
let item = items[i];
|
|
let isSelected = (i === selectedIndex);
|
|
let itemY = yPos + (i - scrollOffset) * menu.itemHeight;
|
|
|
|
if (isSelected) {
|
|
// Selected item - glowing accent background
|
|
let glowIntensity = 0.15 + selectionGlow * 0.15;
|
|
|
|
// Outer glow layer
|
|
let outerGlowAlpha = Math.floor(40 * menuOpenAnim * selectionGlow);
|
|
let outerGlow = toColour(theme.accent.r, theme.accent.g, theme.accent.b, outerGlowAlpha);
|
|
drawRect(baseX, itemY - 2, menu.width, menu.itemHeight + 2, outerGlow);
|
|
|
|
// Main selection background
|
|
let selBgAlpha = Math.floor(255 * menuOpenAnim);
|
|
let selBg = toColour(UI.bgSelected.r, UI.bgSelected.g, UI.bgSelected.b, selBgAlpha);
|
|
drawRect(baseX + 1, itemY, menu.width - 2, menu.itemHeight - 2, selBg);
|
|
|
|
// Inner accent overlay (subtle)
|
|
let innerGlowAlpha = Math.floor(25 * menuOpenAnim * selectionGlow);
|
|
let innerGlow = toColour(theme.accent.r, theme.accent.g, theme.accent.b, innerGlowAlpha);
|
|
drawRect(baseX + 1, itemY, menu.width - 2, menu.itemHeight - 2, innerGlow);
|
|
|
|
// Left accent bar with glow
|
|
let barGlowAlpha = Math.floor(100 * menuOpenAnim * selectionGlow);
|
|
let barGlow = toColour(theme.accent.r, theme.accent.g, theme.accent.b, barGlowAlpha);
|
|
drawRect(baseX - 2, itemY, 8, menu.itemHeight - 2, barGlow);
|
|
|
|
let accentBarCol = toColour(theme.accent.r, theme.accent.g, theme.accent.b, animAlpha);
|
|
drawRect(baseX + 1, itemY, 3, menu.itemHeight - 2, accentBarCol);
|
|
|
|
} else if (item.action === "none") {
|
|
// Section header - no background, just spacing
|
|
} else {
|
|
// Non-selected item - subtle hover effect on every other row
|
|
if ((i - scrollOffset) % 2 === 1) {
|
|
let altBg = toColour(UI.bgPanel.r, UI.bgPanel.g, UI.bgPanel.b, Math.floor(60 * menuOpenAnim));
|
|
drawRect(baseX + 1, itemY, menu.width - 2, menu.itemHeight - 2, altBg);
|
|
}
|
|
}
|
|
|
|
// Item text
|
|
let textX = baseX + 20;
|
|
let textY = itemY + 14;
|
|
let textSize = 13;
|
|
|
|
if (item.action === "none") {
|
|
// Section header - muted blue text
|
|
let secCol = toColour(UI.sectionText.r, UI.sectionText.g, UI.sectionText.b, Math.floor(200 * menuOpenAnim));
|
|
drawText(item.label, textX, textY, secCol, 11);
|
|
|
|
// Subtle line under section header
|
|
let lineCol = toColour(UI.border.r, UI.border.g, UI.border.b, Math.floor(80 * menuOpenAnim));
|
|
drawRect(baseX + 16, itemY + menu.itemHeight - 6, menu.width - 32, 1, lineCol);
|
|
|
|
} else if (item.action === "toggle" && toggleStates[item.target]) {
|
|
// Toggle ON - accent color text
|
|
let toggleCol = toColour(theme.accent.r, theme.accent.g, theme.accent.b, animAlpha);
|
|
drawText(item.label, textX, textY, toggleCol, textSize);
|
|
|
|
} else if (isSelected) {
|
|
// Selected item - white text
|
|
let selTextCol = toColour(UI.textPrimary.r, UI.textPrimary.g, UI.textPrimary.b, animAlpha);
|
|
drawText(item.label, textX, textY, selTextCol, textSize);
|
|
|
|
} else {
|
|
// Normal item - secondary text color
|
|
let normCol = toColour(UI.textSecondary.r, UI.textSecondary.g, UI.textSecondary.b, animAlpha);
|
|
drawText(item.label, textX, textY, normCol, textSize);
|
|
}
|
|
|
|
// Toggle status indicator with glow effects
|
|
if (item.action === "toggle") {
|
|
let isOn = toggleStates[item.target];
|
|
let stateX = baseX + menu.width - 55;
|
|
let boxY = itemY + 10;
|
|
let boxW = 40;
|
|
let boxH = 20;
|
|
|
|
if (isOn) {
|
|
// ON state - green pill with pulsing glow
|
|
let glowIntensity = 0.5 + selectionGlow * 0.5;
|
|
|
|
// Outer glow
|
|
let glowAlpha = Math.floor(50 * menuOpenAnim * glowIntensity);
|
|
let glowCol = toColour(UI.success.r, UI.success.g, UI.success.b, glowAlpha);
|
|
drawRect(stateX - 3, boxY - 3, boxW + 6, boxH + 6, glowCol);
|
|
|
|
// Main pill
|
|
let onBg = toColour(UI.success.r, UI.success.g, UI.success.b, Math.floor(220 * menuOpenAnim));
|
|
drawRect(stateX, boxY, boxW, boxH, onBg);
|
|
|
|
// Inner highlight
|
|
let highlightAlpha = Math.floor(80 * menuOpenAnim * glowIntensity);
|
|
let highlightCol = toColour(
|
|
Math.min(255, UI.success.r + 60),
|
|
Math.min(255, UI.success.g + 60),
|
|
Math.min(255, UI.success.b + 60),
|
|
highlightAlpha
|
|
);
|
|
drawRect(stateX + 2, boxY + 2, boxW - 4, 8, highlightCol);
|
|
|
|
let onText = toColour(255, 255, 255, animAlpha);
|
|
drawText("ON", stateX + 12, boxY + 4, onText, 10);
|
|
} else {
|
|
// OFF state - muted with subtle border
|
|
let offBg = toColour(UI.bgHover.r, UI.bgHover.g, UI.bgHover.b, Math.floor(180 * menuOpenAnim));
|
|
drawRect(stateX, boxY, boxW, boxH, offBg);
|
|
|
|
// Subtle inner shadow
|
|
let shadowCol = toColour(0, 0, 0, Math.floor(30 * menuOpenAnim));
|
|
drawRect(stateX, boxY, boxW, 2, shadowCol);
|
|
|
|
let offText = toColour(UI.textMuted.r, UI.textMuted.g, UI.textMuted.b, animAlpha);
|
|
drawText("OFF", stateX + 9, boxY + 4, offText, 10);
|
|
}
|
|
}
|
|
|
|
// Submenu arrow
|
|
if (item.action === "submenu") {
|
|
let arrowX = baseX + menu.width - 25;
|
|
let arrowCol = isSelected ?
|
|
toColour(theme.accent.r, theme.accent.g, theme.accent.b, animAlpha) :
|
|
toColour(UI.textMuted.r, UI.textMuted.g, UI.textMuted.b, animAlpha);
|
|
drawText(">", arrowX, textY, arrowCol, textSize);
|
|
}
|
|
|
|
// Color Editor Slot - show left/right arrows with color ID only (no name)
|
|
if (item.action === "color_slot_edit") {
|
|
let slot = item.slot;
|
|
let colorId = colorEditorValues[slot];
|
|
let colorInfo = getColorById(colorId);
|
|
|
|
// Position for color info display (on the right side)
|
|
let displayX = baseX + menu.width - 95;
|
|
let displayY = itemY + 14;
|
|
|
|
// Left arrow "<"
|
|
let arrowCol = isSelected ?
|
|
toColour(theme.accent.r, theme.accent.g, theme.accent.b, animAlpha) :
|
|
toColour(UI.textMuted.r, UI.textMuted.g, UI.textMuted.b, Math.floor(animAlpha * 0.5));
|
|
drawText("<", displayX, displayY, arrowCol, 14);
|
|
|
|
// Color ID only (no name) - with color preview
|
|
let idCol = isSelected ?
|
|
toColour(UI.textPrimary.r, UI.textPrimary.g, UI.textPrimary.b, animAlpha) :
|
|
toColour(UI.textSecondary.r, UI.textSecondary.g, UI.textSecondary.b, animAlpha);
|
|
drawText(colorId.toString(), displayX + 22, displayY, idCol, 12);
|
|
|
|
// Color preview box
|
|
let previewCol = toColour(colorInfo.r, colorInfo.g, colorInfo.b, animAlpha);
|
|
drawRect(displayX + 45, displayY + 2, 16, 12, previewCol);
|
|
|
|
// Right arrow ">"
|
|
let rightArrowX = displayX + 68;
|
|
drawText(">", rightArrowX, displayY, arrowCol, 14);
|
|
}
|
|
}
|
|
|
|
// ===== FOOTER =====
|
|
let footerY = yPos + visibleCount * menu.itemHeight;
|
|
|
|
// Footer separator
|
|
drawRect(baseX + 1, footerY, menu.width - 2, 1, sepCol);
|
|
|
|
// Footer background
|
|
let footerBg = toColour(UI.bgPanel.r, UI.bgPanel.g, UI.bgPanel.b, Math.floor(200 * menuOpenAnim));
|
|
drawRect(baseX + 1, footerY + 1, menu.width - 2, menu.footerHeight - 2, footerBg);
|
|
|
|
// Navigation hints
|
|
let hintY = footerY + 14;
|
|
let hintCol = toColour(UI.textMuted.r, UI.textMuted.g, UI.textMuted.b, animAlpha);
|
|
let keyCol = toColour(UI.textSecondary.r, UI.textSecondary.g, UI.textSecondary.b, animAlpha);
|
|
|
|
// Show different hints for color editor
|
|
if (currentMenu === "vehColorEditor") {
|
|
drawText("[", baseX + 15, hintY, hintCol, 10);
|
|
drawText("UP/DN", baseX + 22, hintY, keyCol, 10);
|
|
drawText("]", baseX + 57, hintY, hintCol, 10);
|
|
drawText("Slot", baseX + 67, hintY, hintCol, 10);
|
|
|
|
drawText("[", baseX + 110, hintY, hintCol, 10);
|
|
drawText("L/R", baseX + 117, hintY, keyCol, 10);
|
|
drawText("]", baseX + 140, hintY, hintCol, 10);
|
|
drawText("Color", baseX + 150, hintY, hintCol, 10);
|
|
|
|
drawText("[", baseX + 200, hintY, hintCol, 10);
|
|
drawText("BACK", baseX + 207, hintY, keyCol, 10);
|
|
drawText("]", baseX + 240, hintY, hintCol, 10);
|
|
drawText("Exit", baseX + 250, hintY, hintCol, 10);
|
|
} else {
|
|
drawText("[", baseX + 15, hintY, hintCol, 10);
|
|
drawText("UP/DOWN", baseX + 22, hintY, keyCol, 10);
|
|
drawText("]", baseX + 72, hintY, hintCol, 10);
|
|
drawText("Navigate", baseX + 82, hintY, hintCol, 10);
|
|
|
|
drawText("[", baseX + 145, hintY, hintCol, 10);
|
|
drawText("ENTER", baseX + 152, hintY, keyCol, 10);
|
|
drawText("]", baseX + 190, hintY, hintCol, 10);
|
|
drawText("Select", baseX + 200, hintY, hintCol, 10);
|
|
|
|
drawText("[", baseX + 255, hintY, hintCol, 10);
|
|
drawText("BACK", baseX + 262, hintY, keyCol, 10);
|
|
drawText("]", baseX + 295, hintY, hintCol, 10);
|
|
drawText("Return", baseX + 305, hintY, hintCol, 10);
|
|
}
|
|
|
|
// ===== GLOWING SCROLLBAR =====
|
|
if (items.length > menu.maxVisibleItems) {
|
|
let scrollbarX = baseX + menu.width - 10;
|
|
let scrollbarY = baseY + menu.headerHeight + 8;
|
|
let scrollbarH = visibleCount * menu.itemHeight - 16;
|
|
let maxScroll = items.length - visibleCount;
|
|
let scrollProgress = scrollOffset / maxScroll;
|
|
let thumbH = Math.max(35, scrollbarH * (visibleCount / items.length));
|
|
let thumbY = scrollbarY + scrollProgress * (scrollbarH - thumbH);
|
|
|
|
// Outer glow effect for track
|
|
let glowAlpha = Math.floor(30 * menuOpenAnim * scrollbarGlow);
|
|
let trackGlow = toColour(theme.accent.r, theme.accent.g, theme.accent.b, glowAlpha);
|
|
drawRect(scrollbarX - 3, scrollbarY - 2, 12, scrollbarH + 4, trackGlow);
|
|
|
|
// Track background with subtle gradient look
|
|
let trackBg = toColour(UI.bgHover.r, UI.bgHover.g, UI.bgHover.b, Math.floor(180 * menuOpenAnim));
|
|
drawRect(scrollbarX, scrollbarY, 6, scrollbarH, trackBg);
|
|
|
|
// Inner track line
|
|
let trackInner = toColour(UI.bgDark.r, UI.bgDark.g, UI.bgDark.b, Math.floor(150 * menuOpenAnim));
|
|
drawRect(scrollbarX + 1, scrollbarY + 1, 4, scrollbarH - 2, trackInner);
|
|
|
|
// Thumb outer glow (pulsing)
|
|
let thumbGlowAlpha = Math.floor(80 * menuOpenAnim * scrollbarGlow);
|
|
let thumbGlow = toColour(theme.accent.r, theme.accent.g, theme.accent.b, thumbGlowAlpha);
|
|
drawRect(scrollbarX - 4, thumbY - 3, 14, thumbH + 6, thumbGlow);
|
|
|
|
// Thumb mid glow
|
|
let thumbMidGlow = toColour(theme.accent.r, theme.accent.g, theme.accent.b, Math.floor(120 * menuOpenAnim * scrollbarGlow));
|
|
drawRect(scrollbarX - 2, thumbY - 1, 10, thumbH + 2, thumbMidGlow);
|
|
|
|
// Thumb main body
|
|
let thumbCol = toColour(theme.accent.r, theme.accent.g, theme.accent.b, Math.floor(255 * menuOpenAnim));
|
|
drawRect(scrollbarX, thumbY, 6, thumbH, thumbCol);
|
|
|
|
// Thumb highlight (brighter center)
|
|
let highlightAlpha = Math.floor(255 * menuOpenAnim * (0.5 + scrollbarGlow * 0.5));
|
|
let thumbHighlight = toColour(
|
|
Math.min(255, theme.accent.r + 50),
|
|
Math.min(255, theme.accent.g + 50),
|
|
Math.min(255, theme.accent.b + 50),
|
|
highlightAlpha
|
|
);
|
|
drawRect(scrollbarX + 1, thumbY + 2, 4, thumbH - 4, thumbHighlight);
|
|
|
|
// Page indicator with subtle styling
|
|
let pageText = (scrollOffset + 1) + "/" + (maxScroll + 1);
|
|
let pageCol = toColour(UI.textMuted.r, UI.textMuted.g, UI.textMuted.b, animAlpha);
|
|
drawText(pageText, baseX + menu.width - 45, footerY + 26, pageCol, 9);
|
|
}
|
|
|
|
// ===== INFO BAR =====
|
|
if (currentDescription && currentDescription.length > 0) {
|
|
let infoY = baseY + totalHeight + 16;
|
|
let infoBarHeight = 50;
|
|
|
|
// Shadow
|
|
drawRect(baseX + 4, infoY + 4, menu.width, infoBarHeight, toColour(0, 0, 0, Math.floor(60 * menuOpenAnim)));
|
|
|
|
// Background
|
|
let infoBg = toColour(UI.bgDark.r, UI.bgDark.g, UI.bgDark.b, Math.floor(240 * menuOpenAnim));
|
|
drawRect(baseX, infoY, menu.width, infoBarHeight, infoBg);
|
|
|
|
// Border
|
|
drawRect(baseX, infoY, menu.width, 1, borderCol);
|
|
drawRect(baseX, infoY + infoBarHeight - 1, menu.width, 1, borderCol);
|
|
drawRect(baseX, infoY, 1, infoBarHeight, borderCol);
|
|
drawRect(baseX + menu.width - 1, infoY, 1, infoBarHeight, borderCol);
|
|
|
|
// Accent line
|
|
drawRect(baseX, infoY, 3, infoBarHeight, accentCol);
|
|
|
|
// Description text
|
|
let descCol = toColour(UI.textSecondary.r, UI.textSecondary.g, UI.textSecondary.b, animAlpha);
|
|
drawText(currentDescription, baseX + 14, infoY + 18, descCol, 11);
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// VISUAL EFFECTS OVERLAY - Flash, Matrix, Screen Effects
|
|
// ============================================================================
|
|
addEventHandler("OnDrawnHUD", function(event) {
|
|
let screenWidth = 1920;
|
|
let screenHeight = 1080;
|
|
try {
|
|
screenWidth = game.width || 1920;
|
|
screenHeight = game.height || 1080;
|
|
} catch(e) {}
|
|
|
|
// Flash Screen Effect - white overlay that fades out
|
|
if (flashAlpha > 0.01) {
|
|
let flashColor = toColour(255, 255, 255, Math.floor(flashAlpha * 255));
|
|
drawRect(0, 0, screenWidth, screenHeight, flashColor);
|
|
}
|
|
|
|
// Screen Shake Visual Effect - draw offset overlay
|
|
if (screenShake > 0.01) {
|
|
// Apply visual shake by drawing slightly offset
|
|
let shakeX = (Math.random() - 0.5) * screenShake * 20;
|
|
let shakeY = (Math.random() - 0.5) * screenShake * 20;
|
|
// Draw subtle edge distortion
|
|
let edgeColor = toColour(0, 0, 0, Math.floor(screenShake * 50));
|
|
drawRect(shakeX, shakeY, 10, screenHeight, edgeColor);
|
|
drawRect(screenWidth - 10 + shakeX, shakeY, 10, screenHeight, edgeColor);
|
|
}
|
|
|
|
// Matrix Mode - Digital rain effect
|
|
if (toggleStates.matrixMode) {
|
|
// Draw matrix-style falling characters
|
|
let matrixColor = toColour(0, 255, 65, 150);
|
|
let matrixBgColor = toColour(0, 30, 10, 100);
|
|
|
|
// Draw dark green overlay
|
|
drawRect(0, 0, screenWidth, screenHeight, matrixBgColor);
|
|
|
|
// Draw vertical lines simulating matrix rain
|
|
for (let col = 0; col < 40; col++) {
|
|
let x = col * 48;
|
|
let offset = (matrixEffect * 50 + col * 73) % screenHeight;
|
|
|
|
// Draw fading trail
|
|
for (let i = 0; i < 15; i++) {
|
|
let y = (offset + i * 30) % screenHeight;
|
|
let alpha = Math.max(0, 150 - i * 10);
|
|
let charColor = toColour(0, 255, 65, alpha);
|
|
// Draw small rectangles as "characters"
|
|
drawRect(x, y, 8, 12, charColor);
|
|
}
|
|
}
|
|
|
|
// Add scanline effect
|
|
for (let y = 0; y < screenHeight; y += 4) {
|
|
let scanlineColor = toColour(0, 0, 0, 30);
|
|
drawRect(0, y, screenWidth, 2, scanlineColor);
|
|
}
|
|
}
|
|
|
|
// Night Vision Overlay - green tint effect if native doesn't work
|
|
if (toggleStates.nightVision) {
|
|
let nvColor = toColour(0, 80, 0, 60);
|
|
drawRect(0, 0, screenWidth, screenHeight, nvColor);
|
|
// Add grain effect
|
|
for (let i = 0; i < 50; i++) {
|
|
let grainX = Math.random() * screenWidth;
|
|
let grainY = Math.random() * screenHeight;
|
|
let grainColor = toColour(100, 255, 100, Math.floor(Math.random() * 30));
|
|
drawRect(grainX, grainY, 2, 2, grainColor);
|
|
}
|
|
}
|
|
|
|
// Thermal Vision Overlay - color bands if native doesn't work
|
|
if (toggleStates.thermalVision) {
|
|
let thermalColor = toColour(100, 50, 150, 40);
|
|
drawRect(0, 0, screenWidth, screenHeight, thermalColor);
|
|
// Add heat shimmer effect
|
|
for (let i = 0; i < 30; i++) {
|
|
let heatX = Math.random() * screenWidth;
|
|
let heatY = Math.random() * screenHeight;
|
|
let heatW = 20 + Math.random() * 40;
|
|
let heatH = 10 + Math.random() * 20;
|
|
let r = 200 + Math.floor(Math.random() * 55);
|
|
let g = 100 + Math.floor(Math.random() * 100);
|
|
let b = Math.floor(Math.random() * 50);
|
|
let heatColor = toColour(r, g, b, 20);
|
|
drawRect(heatX, heatY, heatW, heatH, heatColor);
|
|
}
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// PLAYER STATS PANEL - Clean Modern Design
|
|
// ============================================================================
|
|
addEventHandler("OnDrawnHUD", function(event) {
|
|
if (menuOpenAnim <= 0) return;
|
|
if (!localPlayer) return;
|
|
|
|
let alpha = Math.floor(255 * menuOpenAnim);
|
|
let theme = getTheme();
|
|
|
|
// Panel position (top-right of screen, next to menu)
|
|
let panelX = 700;
|
|
let panelY = 100;
|
|
let panelW = 200;
|
|
let panelH = 220;
|
|
|
|
// Slide in animation from right
|
|
let slideOffset = (1 - menuOpenAnim) * 60;
|
|
panelX += slideOffset;
|
|
|
|
// Shadow
|
|
drawRect(panelX + 3, panelY + 3, panelW, panelH, toColour(0, 0, 0, Math.floor(60 * menuOpenAnim)));
|
|
|
|
// Panel background
|
|
let bgColor = toColour(UI.bgDark.r, UI.bgDark.g, UI.bgDark.b, Math.floor(240 * menuOpenAnim));
|
|
drawRect(panelX, panelY, panelW, panelH, bgColor);
|
|
|
|
// Border
|
|
let borderCol = toColour(UI.border.r, UI.border.g, UI.border.b, Math.floor(100 * menuOpenAnim));
|
|
drawRect(panelX, panelY, panelW, 1, borderCol);
|
|
drawRect(panelX, panelY + panelH - 1, panelW, 1, borderCol);
|
|
drawRect(panelX, panelY, 1, panelH, borderCol);
|
|
drawRect(panelX + panelW - 1, panelY, 1, panelH, borderCol);
|
|
|
|
// Accent line at top
|
|
let accentCol = toColour(theme.accent.r, theme.accent.g, theme.accent.b, alpha);
|
|
drawRect(panelX, panelY, panelW, 2, accentCol);
|
|
|
|
// Header
|
|
let headerBg = toColour(UI.bgPanel.r, UI.bgPanel.g, UI.bgPanel.b, Math.floor(200 * menuOpenAnim));
|
|
drawRect(panelX + 1, panelY + 2, panelW - 2, 28, headerBg);
|
|
|
|
let titleCol = toColour(UI.textPrimary.r, UI.textPrimary.g, UI.textPrimary.b, alpha);
|
|
drawText("PLAYER STATUS", panelX + 12, panelY + 10, titleCol, 11);
|
|
|
|
// Content
|
|
let contentY = panelY + 38;
|
|
let labelCol = toColour(UI.textSecondary.r, UI.textSecondary.g, UI.textSecondary.b, alpha);
|
|
let valueCol = toColour(UI.textPrimary.r, UI.textPrimary.g, UI.textPrimary.b, alpha);
|
|
let rowH = 24;
|
|
|
|
// Health bar
|
|
drawText("Health", panelX + 12, contentY, labelCol, 10);
|
|
let health = localPlayer.health || 0;
|
|
let maxHealth = 200;
|
|
let healthPct = Math.min(1, health / maxHealth);
|
|
let barBg = toColour(UI.bgHover.r, UI.bgHover.g, UI.bgHover.b, alpha);
|
|
drawRect(panelX + 12, contentY + 14, panelW - 24, 6, barBg);
|
|
let healthCol = healthPct > 0.5 ? toColour(UI.success.r, UI.success.g, UI.success.b, alpha) :
|
|
healthPct > 0.25 ? toColour(UI.warning.r, UI.warning.g, UI.warning.b, alpha) :
|
|
toColour(UI.error.r, UI.error.g, UI.error.b, alpha);
|
|
drawRect(panelX + 12, contentY + 14, (panelW - 24) * healthPct, 6, healthCol);
|
|
drawText(Math.floor(health).toString(), panelX + panelW - 35, contentY, valueCol, 10);
|
|
contentY += rowH + 2;
|
|
|
|
// Armor bar
|
|
drawText("Armor", panelX + 12, contentY, labelCol, 10);
|
|
let armor = localPlayer.armour || 0;
|
|
let armorPct = Math.min(1, armor / 100);
|
|
drawRect(panelX + 12, contentY + 14, panelW - 24, 6, barBg);
|
|
let armorCol = toColour(59, 130, 246, alpha);
|
|
drawRect(panelX + 12, contentY + 14, (panelW - 24) * armorPct, 6, armorCol);
|
|
drawText(Math.floor(armor).toString(), panelX + panelW - 35, contentY, valueCol, 10);
|
|
contentY += rowH + 6;
|
|
|
|
// Separator
|
|
let sepCol = toColour(UI.border.r, UI.border.g, UI.border.b, Math.floor(80 * menuOpenAnim));
|
|
drawRect(panelX + 12, contentY, panelW - 24, 1, sepCol);
|
|
contentY += 10;
|
|
|
|
// Wanted Level
|
|
drawText("Wanted", panelX + 12, contentY, labelCol, 10);
|
|
let wanted = 0;
|
|
try { wanted = localPlayer.wantedLevel || 0; } catch(e) {}
|
|
for (let i = 0; i < 6; i++) {
|
|
let starCol = i < wanted ? toColour(UI.warning.r, UI.warning.g, UI.warning.b, alpha) : toColour(UI.bgHover.r, UI.bgHover.g, UI.bgHover.b, alpha);
|
|
drawRect(panelX + 80 + i * 16, contentY + 2, 10, 10, starCol);
|
|
}
|
|
contentY += rowH;
|
|
|
|
// Vehicle Status
|
|
drawText("Vehicle", panelX + 12, contentY, labelCol, 10);
|
|
let vehStatus = "On Foot";
|
|
let vehStatusCol = toColour(UI.textMuted.r, UI.textMuted.g, UI.textMuted.b, alpha);
|
|
if (localPlayer.vehicle) {
|
|
vehStatus = "In Vehicle";
|
|
vehStatusCol = toColour(UI.success.r, UI.success.g, UI.success.b, alpha);
|
|
}
|
|
drawText(vehStatus, panelX + 80, contentY, vehStatusCol, 10);
|
|
contentY += rowH;
|
|
|
|
// Active Mods
|
|
drawText("Active Mods", panelX + 12, contentY, labelCol, 10);
|
|
let activeCount = 0;
|
|
for (let key in toggleStates) {
|
|
if (toggleStates[key]) activeCount++;
|
|
}
|
|
let modCountCol = activeCount > 0 ? toColour(theme.accent.r, theme.accent.g, theme.accent.b, alpha) : valueCol;
|
|
drawText(activeCount.toString(), panelX + 80, contentY, modCountCol, 10);
|
|
contentY += rowH + 4;
|
|
|
|
// Time display
|
|
drawRect(panelX + 12, contentY, panelW - 24, 1, sepCol);
|
|
contentY += 8;
|
|
|
|
let now = new Date();
|
|
let timeStr = now.getHours().toString().padStart(2, '0') + ":" +
|
|
now.getMinutes().toString().padStart(2, '0') + ":" +
|
|
now.getSeconds().toString().padStart(2, '0');
|
|
let timeCol = toColour(theme.accent.r, theme.accent.g, theme.accent.b, alpha);
|
|
drawText(timeStr, panelX + panelW / 2 - 25, contentY, timeCol, 12);
|
|
});
|
|
|
|
// ============================================================================
|
|
// ACTIVE TOGGLES INDICATOR - Clean minimal design
|
|
// ============================================================================
|
|
addEventHandler("OnDrawnHUD", function(event) {
|
|
if (menuOpen) return;
|
|
if (!localPlayer) return;
|
|
|
|
let theme = getTheme();
|
|
|
|
// Count active toggles
|
|
let activeToggles = [];
|
|
if (toggleStates.godMode) activeToggles.push("GOD");
|
|
if (toggleStates.invincible) activeToggles.push("INV");
|
|
if (toggleStates.superRun) activeToggles.push("SPD");
|
|
if (toggleStates.neverWanted) activeToggles.push("NW");
|
|
if (toggleStates.invisible) activeToggles.push("INV");
|
|
if (toggleStates.vehGodMode) activeToggles.push("VGOD");
|
|
if (toggleStates.flyMode) activeToggles.push("FLY");
|
|
|
|
if (activeToggles.length === 0) return;
|
|
|
|
// Position in top-left corner (menu is now on right side)
|
|
let indicatorX = 30;
|
|
let indicatorY = 20;
|
|
let bgWidth = 70;
|
|
let bgHeight = 18 + activeToggles.length * 14;
|
|
|
|
// Background
|
|
let bgColor = toColour(UI.bgDark.r, UI.bgDark.g, UI.bgDark.b, 220);
|
|
drawRect(indicatorX, indicatorY, bgWidth, bgHeight, bgColor);
|
|
|
|
// Border
|
|
let borderColor = toColour(UI.border.r, UI.border.g, UI.border.b, 100);
|
|
drawRect(indicatorX, indicatorY, bgWidth, 1, borderColor);
|
|
drawRect(indicatorX, indicatorY + bgHeight - 1, bgWidth, 1, borderColor);
|
|
drawRect(indicatorX, indicatorY, 1, bgHeight, borderColor);
|
|
drawRect(indicatorX + bgWidth - 1, indicatorY, 1, bgHeight, borderColor);
|
|
|
|
// Accent line
|
|
let accentCol = toColour(theme.accent.r, theme.accent.g, theme.accent.b, 255);
|
|
drawRect(indicatorX, indicatorY, 2, bgHeight, accentCol);
|
|
|
|
// Title
|
|
let titleColor = toColour(UI.textSecondary.r, UI.textSecondary.g, UI.textSecondary.b, 200);
|
|
drawText("ACTIVE", indicatorX + 12, indicatorY + 4, titleColor, 8);
|
|
|
|
// List active toggles
|
|
let listY = indicatorY + 16;
|
|
for (let i = 0; i < activeToggles.length; i++) {
|
|
let toggleColor = toColour(theme.accent.r, theme.accent.g, theme.accent.b, 255);
|
|
drawText(activeToggles[i], indicatorX + 12, listY + i * 14, toggleColor, 9);
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// BOTTOM SCREEN BANNER - Clean minimal design
|
|
// ============================================================================
|
|
addEventHandler("OnDrawnHUD", function(event) {
|
|
if (menuOpenAnim <= 0) return;
|
|
|
|
let theme = getTheme();
|
|
let screenWidth = 1920;
|
|
let screenHeight = 1080;
|
|
try {
|
|
screenWidth = game.width || 1920;
|
|
screenHeight = game.height || 1080;
|
|
} catch(e) {}
|
|
|
|
let alpha = Math.floor(255 * menuOpenAnim);
|
|
let bannerH = 35;
|
|
let bannerY = screenHeight - bannerH - 10;
|
|
|
|
// Slide up animation
|
|
let slideOffset = (1 - menuOpenAnim) * 40;
|
|
bannerY += slideOffset;
|
|
|
|
// Banner background
|
|
let bannerBg = toColour(UI.bgDark.r, UI.bgDark.g, UI.bgDark.b, Math.floor(220 * menuOpenAnim));
|
|
drawRect(0, bannerY, screenWidth, bannerH, bannerBg);
|
|
|
|
// Top accent line
|
|
let accentCol = toColour(theme.accent.r, theme.accent.g, theme.accent.b, alpha);
|
|
drawRect(0, bannerY, screenWidth, 1, accentCol);
|
|
|
|
// Left logo
|
|
let titleCol = toColour(UI.textPrimary.r, UI.textPrimary.g, UI.textPrimary.b, alpha);
|
|
let revCol = toColour(theme.accent.r, theme.accent.g, theme.accent.b, alpha);
|
|
drawText("MD", 20, bannerY + 10, titleCol, 14);
|
|
drawText("REVOLUTION", 50, bannerY + 12, revCol, 11);
|
|
|
|
// Center text
|
|
let centerText = "GTA CONNECTED";
|
|
let centerX = screenWidth / 2 - centerText.length * 3;
|
|
let centerCol = toColour(UI.textSecondary.r, UI.textSecondary.g, UI.textSecondary.b, alpha);
|
|
drawText(centerText, centerX, bannerY + 12, centerCol, 10);
|
|
|
|
// Right side - F5 hint
|
|
let hintCol = toColour(UI.textMuted.r, UI.textMuted.g, UI.textMuted.b, alpha);
|
|
let keyCol = toColour(theme.accent.r, theme.accent.g, theme.accent.b, alpha);
|
|
drawText("Press", screenWidth - 140, bannerY + 12, hintCol, 9);
|
|
drawText("[F5]", screenWidth - 105, bannerY + 12, keyCol, 9);
|
|
drawText("to close", screenWidth - 70, bannerY + 12, hintCol, 9);
|
|
});
|
|
|
|
// Draw rectangle using graphics API
|
|
function drawRect(x, y, w, h, colour) {
|
|
try {
|
|
let pos = new Vec2(x, y);
|
|
let size = new Vec2(w, h);
|
|
graphics.drawRectangle(null, pos, size, colour, colour, colour, colour);
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Draw gradient rectangle (left color to right color)
|
|
function drawGradientRect(x, y, w, h, colourLeft, colourRight) {
|
|
try {
|
|
let pos = new Vec2(x, y);
|
|
let size = new Vec2(w, h);
|
|
graphics.drawRectangle(null, pos, size, colourLeft, colourRight, colourLeft, colourRight);
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Draw text using loaded font or fallback
|
|
function drawText(text, x, y, colour, size) {
|
|
if (menuFont != null) {
|
|
try {
|
|
let pos = new Vec2(x, y);
|
|
menuFont.render(text, pos, menu.width, 0.0, 0.0, size, colour, false, false, false, true);
|
|
} catch(e) {}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// NOTIFICATIONS (stub - notifications removed, function kept for compatibility)
|
|
// ============================================================================
|
|
|
|
function showNotification(text) {
|
|
// Notifications removed - toggle states now shown in menu labels
|
|
}
|
|
|
|
// ============================================================================
|
|
// TOGGLE EFFECTS
|
|
// ============================================================================
|
|
|
|
// Track last toggle states to only call native when changed
|
|
let lastGodMode = false;
|
|
let lastInvincible = false;
|
|
let lastSuperRun = false;
|
|
let lastNoRagdoll = false;
|
|
let lastVehGodMode = false;
|
|
let lastInvisible = false;
|
|
let processCounter = 0;
|
|
|
|
// Spinbot speed control (lower = slower)
|
|
let spinbotSpeed = 2;
|
|
|
|
addEventHandler("OnProcess", function(event) {
|
|
if (!localPlayer) return;
|
|
processCounter++;
|
|
|
|
// Player god mode - use invincibility native + health
|
|
if (toggleStates.godMode !== lastGodMode) {
|
|
try {
|
|
natives.setCharInvincible(localPlayer, toggleStates.godMode);
|
|
} catch(e) {}
|
|
lastGodMode = toggleStates.godMode;
|
|
}
|
|
|
|
// Keep health topped up in god mode as backup
|
|
if (toggleStates.godMode) {
|
|
if (localPlayer.health < 200) localPlayer.health = 200;
|
|
if (localPlayer.armour < 100) localPlayer.armour = 100;
|
|
}
|
|
|
|
// Invincible toggle - separate from god mode, just invincibility
|
|
if (toggleStates.invincible !== lastInvincible) {
|
|
try {
|
|
natives.setCharInvincible(localPlayer, toggleStates.invincible);
|
|
natives.setCharProofs(localPlayer, toggleStates.invincible, toggleStates.invincible, toggleStates.invincible, toggleStates.invincible, toggleStates.invincible);
|
|
} catch(e) {}
|
|
lastInvincible = toggleStates.invincible;
|
|
}
|
|
|
|
// Super Run - increase movement speed
|
|
if (toggleStates.superRun !== lastSuperRun) {
|
|
try {
|
|
if (toggleStates.superRun) {
|
|
natives.setCharMoveAnimSpeedMultiplier(localPlayer, 3.0);
|
|
} else {
|
|
natives.setCharMoveAnimSpeedMultiplier(localPlayer, 1.0);
|
|
}
|
|
} catch(e) {}
|
|
lastSuperRun = toggleStates.superRun;
|
|
}
|
|
|
|
// No Ragdoll - prevent ragdoll using multiple methods
|
|
if (toggleStates.noRagdoll !== lastNoRagdoll) {
|
|
try {
|
|
// Use GTA IV specific natives
|
|
natives.setCharCanBeKnockedOffBike(localPlayer, !toggleStates.noRagdoll);
|
|
natives.setCharWillFlyThroughWindscreen(localPlayer, !toggleStates.noRagdoll);
|
|
} catch(e) {}
|
|
lastNoRagdoll = toggleStates.noRagdoll;
|
|
}
|
|
// Keep preventing ragdoll every frame
|
|
if (toggleStates.noRagdoll) {
|
|
try {
|
|
// Multiple approaches for GTA IV ragdoll prevention
|
|
natives.setCharCanBeKnockedOffBike(localPlayer, false);
|
|
natives.setCharWillFlyThroughWindscreen(localPlayer, false);
|
|
// Unlock ragdoll to reset it, then prevent
|
|
natives.unlockRagdoll(localPlayer, true);
|
|
// Make character not fall from hits
|
|
natives.setCharNeverTargetted(localPlayer, false);
|
|
// If currently in ragdoll, recover
|
|
try {
|
|
if (natives.isPedRagdoll(localPlayer)) {
|
|
natives.switchPedToAnimated(localPlayer, true);
|
|
}
|
|
} catch(e2) {}
|
|
// Alternative: task to stand up
|
|
try {
|
|
natives.taskSetCharDecisionMaker(localPlayer, 0);
|
|
} catch(e3) {}
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Never wanted - clear wanted level
|
|
if (toggleStates.neverWanted) {
|
|
try {
|
|
natives.clearWantedLevel(0);
|
|
} catch(e) {
|
|
localPlayer.wantedLevel = 0;
|
|
}
|
|
}
|
|
|
|
// Invisible - make player invisible
|
|
if (toggleStates.invisible) {
|
|
try {
|
|
natives.setCharVisible(localPlayer, false);
|
|
} catch(e) {}
|
|
} else {
|
|
try {
|
|
natives.setCharVisible(localPlayer, true);
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Infinite Sprint - never get tired
|
|
if (toggleStates.infiniteSprint) {
|
|
try {
|
|
natives.setCharNeverTired(localPlayer, true);
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Freeze Position - lock player in place
|
|
if (toggleStates.freezePlayer) {
|
|
try {
|
|
natives.freezeCharPosition(localPlayer, true);
|
|
} catch(e) {}
|
|
} else {
|
|
try {
|
|
natives.freezeCharPosition(localPlayer, false);
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Fire Bullets - set bullets on fire
|
|
if (toggleStates.fireBullets) {
|
|
try {
|
|
natives.setCharShootRate(localPlayer, 100);
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Unlimited Ammo - add ammo for all weapon types
|
|
if (toggleStates.unlimitedAmmo && processCounter % 30 === 0) {
|
|
try {
|
|
// Add ammo for common weapon IDs (pistol, shotgun, smg, rifle, sniper, rpg)
|
|
let weaponIds = [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18];
|
|
for (let i = 0; i < weaponIds.length; i++) {
|
|
natives.addAmmoToChar(localPlayer, weaponIds[i], 999);
|
|
}
|
|
} catch(e) {}
|
|
}
|
|
|
|
// No Reload - keep ammo in clip full
|
|
if (toggleStates.noReload && processCounter % 5 === 0) {
|
|
try {
|
|
natives.setCharAmmoInClip(localPlayer, 999);
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Spinbot - rotate character heading
|
|
if (toggleStates.spinbot) {
|
|
try {
|
|
let currentHeading = localPlayer.heading || 0;
|
|
localPlayer.heading = (currentHeading + 10) % 360;
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Vehicle-specific toggles
|
|
if (localPlayer.vehicle) {
|
|
let veh = localPlayer.vehicle;
|
|
|
|
// Vehicle god mode
|
|
if (toggleStates.vehGodMode !== lastVehGodMode) {
|
|
try {
|
|
natives.setCarCanBeDamaged(veh, !toggleStates.vehGodMode);
|
|
} catch(e) {}
|
|
lastVehGodMode = toggleStates.vehGodMode;
|
|
}
|
|
if (toggleStates.vehGodMode) {
|
|
try { natives.fixCar(veh); } catch(e) {}
|
|
}
|
|
|
|
// Drive on water - keep vehicle above water level
|
|
if (toggleStates.driveOnWater) {
|
|
try {
|
|
let pos = veh.position;
|
|
let waterZ = 0; // Sea level in GTA IV
|
|
if (pos.z < waterZ + 1) {
|
|
// Keep car floating on water
|
|
let vel = veh.velocity;
|
|
veh.position = new Vec3(pos.x, pos.y, waterZ + 0.8);
|
|
// Maintain forward momentum but cancel downward
|
|
if (vel.z < 0) {
|
|
veh.velocity = new Vec3(vel.x, vel.y, 0);
|
|
}
|
|
}
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Rainbow car color - change all 4 colors to random values
|
|
if (toggleStates.rainbowCar && processCounter % 5 === 0) {
|
|
try {
|
|
// Generate 4 random colors (0-133)
|
|
let c1 = Math.floor(Math.random() * 134);
|
|
let c2 = Math.floor(Math.random() * 134);
|
|
let c3 = Math.floor(Math.random() * 134);
|
|
let c4 = Math.floor(Math.random() * 134);
|
|
|
|
// Apply all 4 colors
|
|
try {
|
|
veh.colour1 = c1;
|
|
veh.colour2 = c2;
|
|
veh.colour3 = c3;
|
|
veh.colour4 = c4;
|
|
} catch(e1) {
|
|
// Fallback: use natives for color 1 and 2
|
|
natives.changeCarColour(veh, c1, c2);
|
|
}
|
|
} catch(e) {}
|
|
}
|
|
|
|
// ============================================================================
|
|
// LAYER B: PHYSICS EMULATION PROCESSING
|
|
// All handling effects achieved via velocity manipulation
|
|
// ============================================================================
|
|
try {
|
|
let vel = veh.velocity;
|
|
let turnVel = veh.turnVelocity;
|
|
let heading = veh.heading || 0;
|
|
let speed = Math.sqrt(vel.x * vel.x + vel.y * vel.y);
|
|
let speedKmh = speed * 3.6;
|
|
|
|
// Calculate forward direction vector from heading
|
|
let forwardX = -Math.sin(heading);
|
|
let forwardY = Math.cos(heading);
|
|
|
|
// Calculate right direction vector (perpendicular to forward)
|
|
let rightX = Math.cos(heading);
|
|
let rightY = Math.sin(heading);
|
|
|
|
// Decompose velocity into forward and sideways components
|
|
let forwardSpeed = vel.x * forwardX + vel.y * forwardY;
|
|
let sidewaysSpeed = vel.x * rightX + vel.y * rightY;
|
|
|
|
// Detect steering input from turn velocity
|
|
let steerInput = turnVel.z || 0;
|
|
let isSteeringLeft = steerInput > 0.01;
|
|
let isSteeringRight = steerInput < -0.01;
|
|
physicsEmulation.isSteering = isSteeringLeft || isSteeringRight;
|
|
physicsEmulation.steerDirection = isSteeringLeft ? -1 : (isSteeringRight ? 1 : 0);
|
|
|
|
// Detect acceleration/braking from speed changes
|
|
if (physicsEmulation.lastSpeed !== null) {
|
|
let speedDelta = speed - physicsEmulation.lastSpeed;
|
|
physicsEmulation.isAccelerating = speedDelta > 0.1 && forwardSpeed > 0;
|
|
physicsEmulation.isBraking = speedDelta < -0.2 || forwardSpeed < -1;
|
|
}
|
|
|
|
// Save for next frame
|
|
physicsEmulation.lastSpeed = speed;
|
|
physicsEmulation.lastHeading = heading;
|
|
physicsEmulation.lastVelocity = vel;
|
|
|
|
// ===== GRIP / TRACTION ASSIST =====
|
|
if (physicsEmulation.gripAssistEnabled && speed > 2) {
|
|
// Calculate grip based on handling values
|
|
let gripFactor = (handlingValues.tractionCurveMax / 2.0) * physicsEmulation.gripAssistStrength;
|
|
gripFactor = Math.min(2.0, Math.max(0.1, gripFactor));
|
|
|
|
// Correct sideways velocity back toward forward direction
|
|
// Stronger at low speeds, weaker at high speeds (realism)
|
|
let speedFactor = Math.max(0.3, 1.0 - (speed / 40.0));
|
|
let correctionStrength = gripFactor * speedFactor * 0.15;
|
|
|
|
// Apply sideways velocity correction
|
|
if (Math.abs(sidewaysSpeed) > 0.5) {
|
|
let correction = -sidewaysSpeed * correctionStrength;
|
|
veh.velocity = new Vec3(
|
|
vel.x + rightX * correction,
|
|
vel.y + rightY * correction,
|
|
vel.z
|
|
);
|
|
}
|
|
}
|
|
|
|
// ===== ACCELERATION BOOST (Behavioral) =====
|
|
if (handlingMods.acceleration > 1.0 && physicsEmulation.isAccelerating && speed > 1) {
|
|
let maxSpeed = physicsEmulation.maxSpeedLimit * handlingMods.topSpeed;
|
|
|
|
// Only boost if below max speed
|
|
if (speed < maxSpeed) {
|
|
let boostFactor = (handlingMods.acceleration - 1.0) * 0.08;
|
|
// Add velocity along forward vector
|
|
let boostX = forwardX * boostFactor * Math.min(speed, 20);
|
|
let boostY = forwardY * boostFactor * Math.min(speed, 20);
|
|
|
|
veh.velocity = new Vec3(vel.x + boostX, vel.y + boostY, vel.z);
|
|
}
|
|
}
|
|
|
|
// ===== TOP SPEED CONTROL =====
|
|
let maxSpeed = 35 * handlingMods.topSpeed; // Base ~35 m/s
|
|
if (speed > maxSpeed) {
|
|
// Smoothly reduce to max speed (not instant)
|
|
let factor = maxSpeed / speed;
|
|
factor = 0.95 + factor * 0.05; // Gradual approach
|
|
veh.velocity = new Vec3(vel.x * factor, vel.y * factor, vel.z);
|
|
}
|
|
// Speed boost when above normal threshold
|
|
else if (handlingMods.topSpeed > 1.0 && speed > 20 && speed < maxSpeed && physicsEmulation.isAccelerating) {
|
|
let pushFactor = (handlingMods.topSpeed - 1.0) * 0.015;
|
|
let pushX = forwardX * pushFactor * speed;
|
|
let pushY = forwardY * pushFactor * speed;
|
|
veh.velocity = new Vec3(vel.x + pushX, vel.y + pushY, vel.z);
|
|
}
|
|
|
|
// ===== BRAKING BEHAVIOR =====
|
|
if (physicsEmulation.isBraking && handlingMods.braking > 0 && speed > 1) {
|
|
// Smooth braking - reduce velocity magnitude
|
|
let brakeFactor = handlingMods.braking * 0.05;
|
|
let dampFactor = 1.0 - Math.min(0.15, brakeFactor);
|
|
|
|
// Preserve directional stability while braking
|
|
let newSpeed = speed * dampFactor;
|
|
if (newSpeed > 0.5) {
|
|
let ratio = newSpeed / speed;
|
|
veh.velocity = new Vec3(vel.x * ratio, vel.y * ratio, vel.z);
|
|
}
|
|
}
|
|
|
|
// ===== STABILITY / ANTI-ROLL =====
|
|
if (physicsEmulation.stabilityEnabled && speed > 5) {
|
|
// Get current turn velocity
|
|
let currentTurn = veh.turnVelocity;
|
|
|
|
// Dampen excessive rotation (X and Y axes = roll/pitch)
|
|
let stabilityFactor = physicsEmulation.stabilityStrength * 0.2;
|
|
let antiRollFactor = physicsEmulation.antiRollStrength * 0.3;
|
|
|
|
// Cap extreme rotational velocities to prevent flipping
|
|
let maxRoll = 1.5 - (antiRollFactor * 0.5);
|
|
let dampedX = currentTurn.x;
|
|
let dampedY = currentTurn.y;
|
|
let dampedZ = currentTurn.z;
|
|
|
|
// Anti-roll: prevent excessive X/Y rotation
|
|
if (Math.abs(dampedX) > maxRoll) {
|
|
dampedX = dampedX * (1 - antiRollFactor * 0.5);
|
|
}
|
|
if (Math.abs(dampedY) > maxRoll) {
|
|
dampedY = dampedY * (1 - antiRollFactor * 0.5);
|
|
}
|
|
|
|
// At high speed, dampen all rotations for stability
|
|
if (speed > 25) {
|
|
let highSpeedDamp = 1 - (stabilityFactor * Math.min(1, (speed - 25) / 20));
|
|
dampedX *= highSpeedDamp;
|
|
dampedY *= highSpeedDamp;
|
|
// Keep Z (yaw) mostly intact for steering feel
|
|
dampedZ *= (1 - stabilityFactor * 0.3);
|
|
}
|
|
|
|
// Apply damped turn velocity
|
|
if (dampedX !== currentTurn.x || dampedY !== currentTurn.y || dampedZ !== currentTurn.z) {
|
|
veh.turnVelocity = new Vec3(dampedX, dampedY, dampedZ);
|
|
}
|
|
}
|
|
} catch(e) {
|
|
// Silent fail for physics processing
|
|
}
|
|
|
|
// Fly mode - WASD controls altitude
|
|
if (toggleStates.flyMode) {
|
|
try {
|
|
let pos = veh.position;
|
|
let vel = veh.velocity;
|
|
let heading = veh.heading || 0;
|
|
|
|
// Anti-gravity - keep vehicle airborne
|
|
if (vel.z < 0) {
|
|
veh.velocity = new Vec3(vel.x, vel.y, vel.z * 0.5);
|
|
}
|
|
|
|
// Lift vehicle
|
|
veh.position = new Vec3(pos.x, pos.y, pos.z + 0.1);
|
|
|
|
// Apply forward force based on heading
|
|
let forwardX = Math.sin(heading) * -2;
|
|
let forwardY = Math.cos(heading) * 2;
|
|
veh.velocity = new Vec3(vel.x + forwardX * 0.1, vel.y + forwardY * 0.1, 0.5);
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Vehicle shoots RPG
|
|
if (toggleStates.vehShootRPG) {
|
|
let now = Date.now();
|
|
if (now - lastVehShot > 500) { // Fire every 500ms when key held
|
|
try {
|
|
let pos = veh.position;
|
|
let heading = veh.heading || 0;
|
|
// Shoot from front of vehicle
|
|
let frontX = pos.x + Math.sin(heading) * -5;
|
|
let frontY = pos.y + Math.cos(heading) * 5;
|
|
let fromPos = new Vec3(frontX, frontY, pos.z + 1);
|
|
let toX = frontX + Math.sin(heading) * -100;
|
|
let toY = frontY + Math.cos(heading) * 100;
|
|
let toPos = new Vec3(toX, toY, pos.z + 1);
|
|
|
|
// Shoot projectile
|
|
natives.shootSingleBulletBetweenCoords(
|
|
fromPos.x, fromPos.y, fromPos.z,
|
|
toPos.x, toPos.y, toPos.z,
|
|
100, true, 18, localPlayer, true, true, 100
|
|
);
|
|
lastVehShot = now;
|
|
} catch(e) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rainbow sky effect
|
|
if (toggleStates.rainbowSky && processCounter % 3 === 0) {
|
|
try {
|
|
rainbowHue = (rainbowHue + 2) % 360;
|
|
let rgb = hsvToRgb(rainbowHue, 0.7, 1);
|
|
natives.setSkyboxTint(rgb.r, rgb.g, rgb.b);
|
|
} catch(e) {}
|
|
}
|
|
|
|
// Static sky color
|
|
if (!toggleStates.rainbowSky && skyColorIndex > 0) {
|
|
try {
|
|
let color = skyColors[skyColorIndex];
|
|
natives.setSkyboxTint(color.r, color.g, color.b);
|
|
} catch(e) {}
|
|
}
|
|
|
|
// COMPLETELY disable phone when menu is open
|
|
if (menuOpen) {
|
|
try {
|
|
// Multiple methods to kill phone
|
|
natives.destroyMobilePhone();
|
|
natives.scriptIsUsingMobilePhone(true);
|
|
|
|
// Block player input to phone
|
|
natives.taskUseMobilePhone(localPlayer, false);
|
|
|
|
// Force phone off
|
|
if (natives.getPlayerIsUsingMobilePhone(0)) {
|
|
natives.destroyMobilePhone();
|
|
}
|
|
} catch(e) {}
|
|
}
|
|
});
|
|
|
|
// HSV to RGB conversion for rainbow effects
|
|
function hsvToRgb(h, s, v) {
|
|
let r, g, b;
|
|
let i = Math.floor(h / 60) % 6;
|
|
let f = h / 60 - i;
|
|
let p = v * (1 - s);
|
|
let q = v * (1 - f * s);
|
|
let t = v * (1 - (1 - f) * s);
|
|
|
|
switch (i) {
|
|
case 0: r = v; g = t; b = p; break;
|
|
case 1: r = q; g = v; b = p; break;
|
|
case 2: r = p; g = v; b = t; break;
|
|
case 3: r = p; g = q; b = v; break;
|
|
case 4: r = t; g = p; b = v; break;
|
|
case 5: r = v; g = p; b = q; break;
|
|
}
|
|
|
|
return {
|
|
r: Math.round(r * 255),
|
|
g: Math.round(g * 255),
|
|
b: Math.round(b * 255)
|
|
};
|
|
}
|
|
|
|
// Neon lights rendering - draw colored lights under vehicle using drawLightWithRange
|
|
addEventHandler("OnProcess", function(event) {
|
|
if (!toggleStates.neonLights || !localPlayer || !localPlayer.vehicle) return;
|
|
|
|
try {
|
|
let veh = localPlayer.vehicle;
|
|
let pos = veh.position;
|
|
let heading = veh.heading || 0;
|
|
|
|
// Convert heading to radians for rotation
|
|
let cosH = Math.cos(heading);
|
|
let sinH = Math.sin(heading);
|
|
|
|
// Neon light positions relative to vehicle center
|
|
// Format: { x: side offset, y: front/back, z: height, range: light radius }
|
|
let neonPoints = [
|
|
// Left side strip (front to back)
|
|
{ x: -1.2, y: 1.5, z: -0.4, range: 3.0 },
|
|
{ x: -1.2, y: 0.5, z: -0.4, range: 3.0 },
|
|
{ x: -1.2, y: -0.5, z: -0.4, range: 3.0 },
|
|
{ x: -1.2, y: -1.5, z: -0.4, range: 3.0 },
|
|
// Right side strip (front to back)
|
|
{ x: 1.2, y: 1.5, z: -0.4, range: 3.0 },
|
|
{ x: 1.2, y: 0.5, z: -0.4, range: 3.0 },
|
|
{ x: 1.2, y: -0.5, z: -0.4, range: 3.0 },
|
|
{ x: 1.2, y: -1.5, z: -0.4, range: 3.0 },
|
|
// Front strip
|
|
{ x: -0.6, y: 2.0, z: -0.4, range: 2.5 },
|
|
{ x: 0.6, y: 2.0, z: -0.4, range: 2.5 },
|
|
// Rear strip
|
|
{ x: -0.6, y: -2.0, z: -0.4, range: 2.5 },
|
|
{ x: 0.6, y: -2.0, z: -0.4, range: 2.5 }
|
|
];
|
|
|
|
// Draw each neon light point
|
|
for (let i = 0; i < neonPoints.length; i++) {
|
|
let point = neonPoints[i];
|
|
|
|
// Rotate point by vehicle heading
|
|
let worldX = pos.x + (point.x * cosH - point.y * sinH);
|
|
let worldY = pos.y + (point.x * sinH + point.y * cosH);
|
|
let worldZ = pos.z + point.z;
|
|
|
|
// Draw light with range (GTA IV native)
|
|
// drawLightWithRange(x, y, z, r, g, b, range, intensity)
|
|
try {
|
|
natives.drawLightWithRange(
|
|
worldX, worldY, worldZ,
|
|
neonColor.r, neonColor.g, neonColor.b,
|
|
point.range, 1.0
|
|
);
|
|
} catch(e1) {
|
|
// Fallback: try alternate parameter order
|
|
try {
|
|
natives.drawLightWithRange(
|
|
worldX, worldY, worldZ,
|
|
neonColor.r / 255, neonColor.g / 255, neonColor.b / 255,
|
|
point.range, 100.0
|
|
);
|
|
} catch(e2) {}
|
|
}
|
|
}
|
|
} catch(e) {
|
|
console.log("[ModMenu] Neon error: " + e);
|
|
}
|
|
});
|
|
|
|
// Explosive ammo - detect player shooting
|
|
let lastPlayerPos = null;
|
|
addEventHandler("OnPedWeaponShoot", function(event, ped, weapon) {
|
|
if (!toggleStates.explosiveAmmo) return;
|
|
if (ped !== localPlayer) return;
|
|
|
|
try {
|
|
// Create explosion at impact point
|
|
// Since we can't get exact impact, create small explosion in front
|
|
let pos = localPlayer.position;
|
|
let heading = localPlayer.heading || 0;
|
|
let dist = 20; // Distance in front
|
|
let expX = pos.x + Math.sin(heading) * -dist;
|
|
let expY = pos.y + Math.cos(heading) * dist;
|
|
|
|
// Small delay then explode
|
|
setTimeout(function() {
|
|
try {
|
|
natives.addExplosion(expX, expY, pos.z, 0, 2.0, true, false, 0.5);
|
|
} catch(e) {}
|
|
}, 100);
|
|
} catch(e) {}
|
|
});
|
|
|
|
// ============================================================================
|
|
// INITIALIZATION
|
|
// ============================================================================
|
|
|
|
// ============================================================================
|
|
// HANDLING EDITOR FUNCTIONS
|
|
// ============================================================================
|
|
|
|
// Apply a single handling value to the current vehicle
|
|
// LAYER B: All handling effects are achieved through Physics Emulation
|
|
// This does NOT modify real handling.dat values - instead it configures
|
|
// the physics emulation system to simulate the desired behavior
|
|
function applyHandlingValue(param, value) {
|
|
if (!localPlayer || !localPlayer.vehicle) return;
|
|
|
|
let veh = localPlayer.vehicle;
|
|
|
|
try {
|
|
switch(param) {
|
|
// ===== TRACTION & GRIP (Physics Emulation) =====
|
|
case "tractionCurveMax":
|
|
// Controls grip assist strength in physics emulation
|
|
// Higher = more sideways velocity correction
|
|
physicsEmulation.gripAssistStrength = value / 2.0;
|
|
// Also try native if available (may work on some servers)
|
|
try { natives.setCarTraction(veh, value); } catch(e) {}
|
|
console.log("[Handling] Grip Assist Strength: " + physicsEmulation.gripAssistStrength.toFixed(2));
|
|
break;
|
|
|
|
case "tractionCurveMin":
|
|
// Minimum grip affects how much the car slides at low speeds
|
|
let combinedGrip = (handlingValues.tractionCurveMax + value) / 4.0;
|
|
physicsEmulation.gripAssistStrength = combinedGrip;
|
|
try { natives.setCarTraction(veh, combinedGrip * 2); } catch(e) {}
|
|
break;
|
|
|
|
case "tractionBias":
|
|
// Front/rear grip distribution - affects stability
|
|
// Lower value = more front grip = understeer
|
|
// Higher value = more rear grip = oversteer
|
|
physicsEmulation.stabilityStrength = 1.0 + (0.5 - value);
|
|
physicsEmulation.antiRollStrength = 0.5 + (0.5 - value) * 0.3;
|
|
console.log("[Handling] Stability adjusted for bias: " + value.toFixed(2));
|
|
break;
|
|
|
|
case "tractionLoss":
|
|
// Higher = easier to lose traction
|
|
if (value > 1.0) {
|
|
physicsEmulation.gripAssistStrength *= (1.0 / value);
|
|
}
|
|
console.log("[Handling] Traction Loss: " + value.toFixed(2) + " - Grip reduced to: " + physicsEmulation.gripAssistStrength.toFixed(2));
|
|
break;
|
|
|
|
// ===== SUSPENSION (Physics Emulation + Visual) =====
|
|
case "suspensionForce":
|
|
// Stiffness affects stability - stiffer = more stable at speed
|
|
physicsEmulation.stabilityStrength = value / 2.0;
|
|
suspensionOffset = (value - 2.0) * 0.02;
|
|
console.log("[Handling] Suspension stiffness -> Stability: " + physicsEmulation.stabilityStrength.toFixed(2));
|
|
break;
|
|
|
|
case "suspensionRaise":
|
|
// Try API method, fallback to visual only
|
|
try {
|
|
for (let wheelId = 0; wheelId < 4; wheelId++) {
|
|
veh.setSuspensionHeight(wheelId, value);
|
|
}
|
|
console.log("[Handling] Suspension height set: " + value);
|
|
} catch(e) {
|
|
// Visual feedback only
|
|
}
|
|
suspensionOffset = value;
|
|
// Higher ride height = less stable
|
|
physicsEmulation.antiRollStrength = Math.max(0.2, 0.5 - value);
|
|
break;
|
|
|
|
case "suspensionCompDamp":
|
|
// Damping affects how quickly stability corrections happen
|
|
physicsEmulation.stabilityStrength *= (value / 1.0);
|
|
break;
|
|
|
|
// ===== ENGINE & SPEED (Physics Emulation) =====
|
|
case "driveForce":
|
|
// Engine power -> Acceleration boost multiplier
|
|
handlingMods.acceleration = value / 0.25;
|
|
physicsEmulation.accelBoostEnabled = (handlingMods.acceleration > 1.0);
|
|
physicsEmulation.accelBoostMultiplier = handlingMods.acceleration;
|
|
console.log("[Handling] Acceleration Boost: " + handlingMods.acceleration.toFixed(2) + "x");
|
|
break;
|
|
|
|
case "initialDriveMaxVel":
|
|
// Top speed -> Speed limit in physics emulation
|
|
handlingMods.topSpeed = value / 200.0;
|
|
physicsEmulation.maxSpeedLimit = value / 3.6; // Convert to m/s
|
|
console.log("[Handling] Max Speed: " + value + " km/h (" + physicsEmulation.maxSpeedLimit.toFixed(1) + " m/s)");
|
|
break;
|
|
|
|
case "brakeForce":
|
|
// Brake strength -> Braking multiplier
|
|
handlingMods.braking = value / 0.8;
|
|
physicsEmulation.brakeMultiplier = handlingMods.braking;
|
|
physicsEmulation.brakeAssistEnabled = (handlingMods.braking > 0);
|
|
console.log("[Handling] Brake Multiplier: " + handlingMods.braking.toFixed(2) + "x");
|
|
break;
|
|
|
|
// ===== WEIGHT & BALANCE (Physics Emulation) =====
|
|
case "mass":
|
|
// Mass affects momentum and stability
|
|
// Try direct API first
|
|
try {
|
|
veh.mass = value;
|
|
console.log("[Handling] Mass set: " + value + " kg");
|
|
} catch(e1) {
|
|
try { natives.setCarMass(veh, value); } catch(e2) {}
|
|
}
|
|
// Heavier = more stable, less responsive
|
|
let massFactor = value / 1500.0;
|
|
physicsEmulation.stabilityStrength *= massFactor;
|
|
physicsEmulation.gripAssistStrength *= (1 / Math.sqrt(massFactor));
|
|
break;
|
|
|
|
case "centreOfMassZ":
|
|
// Center of mass height affects roll tendency
|
|
// Higher = less stable, more likely to flip
|
|
physicsEmulation.antiRollStrength = Math.max(0.2, 0.5 - value * 2);
|
|
// Simulate through turn velocity dampening
|
|
if (value > 0.1) {
|
|
physicsEmulation.stabilityStrength *= (1 - value * 0.5);
|
|
}
|
|
console.log("[Handling] CoM Height: " + value + " -> Anti-Roll: " + physicsEmulation.antiRollStrength.toFixed(2));
|
|
break;
|
|
|
|
// ===== STEERING (Stored for reference) =====
|
|
case "steeringLock":
|
|
// Steering angle is engine-controlled, store for display only
|
|
console.log("[Handling] Steering Lock: " + value + " degrees (visual only)");
|
|
break;
|
|
}
|
|
} catch(e) {
|
|
console.log("[HandlingEditor] Error applying " + param + ": " + e);
|
|
}
|
|
}
|
|
|
|
// Reset handling to default values
|
|
function resetHandlingToDefault() {
|
|
handlingValues = {
|
|
tractionCurveMax: 2.0,
|
|
tractionCurveMin: 1.8,
|
|
tractionBias: 0.5,
|
|
suspensionForce: 2.0,
|
|
suspensionCompDamp: 1.0,
|
|
suspensionReboundDamp: 1.2,
|
|
suspensionUpperLimit: 0.12,
|
|
suspensionLowerLimit: -0.10,
|
|
suspensionRaise: 0.0,
|
|
driveForce: 0.25,
|
|
driveInertia: 1.0,
|
|
initialDriveMaxVel: 200.0,
|
|
brakeForce: 0.8,
|
|
mass: 1500.0,
|
|
centreOfMassZ: 0.0,
|
|
steeringLock: 35.0,
|
|
tractionLoss: 0.8
|
|
};
|
|
|
|
// Reset visual feedback
|
|
tyreGlowColors = { fr: {r:0,g:255,b:100}, fl: {r:0,g:255,b:100}, rr: {r:0,g:255,b:100}, rl: {r:0,g:255,b:100} };
|
|
engineGlow.intensity = 0;
|
|
brakeGlow.intensity = 0;
|
|
suspensionOffset = 0;
|
|
selectedHandlingParam = "";
|
|
|
|
// ===== RESET PHYSICS EMULATION TO DEFAULTS =====
|
|
physicsEmulation.gripAssistEnabled = true;
|
|
physicsEmulation.gripAssistStrength = 1.0;
|
|
physicsEmulation.accelBoostEnabled = false;
|
|
physicsEmulation.accelBoostMultiplier = 1.0;
|
|
physicsEmulation.maxSpeedLimit = 60.0;
|
|
physicsEmulation.brakeAssistEnabled = true;
|
|
physicsEmulation.brakeMultiplier = 1.0;
|
|
physicsEmulation.stabilityEnabled = true;
|
|
physicsEmulation.stabilityStrength = 1.0;
|
|
physicsEmulation.antiRollStrength = 0.5;
|
|
|
|
// Apply defaults to vehicle using proper API
|
|
if (localPlayer && localPlayer.vehicle) {
|
|
let veh = localPlayer.vehicle;
|
|
|
|
// Reset traction (try native)
|
|
try {
|
|
natives.setCarTraction(veh, 2.0);
|
|
} catch(e) {}
|
|
|
|
// Reset mass using physical.mass property
|
|
try {
|
|
veh.mass = 1500.0;
|
|
} catch(e1) {
|
|
try { natives.setCarMass(veh, 1500.0); } catch(e2) {}
|
|
}
|
|
|
|
// Reset suspension height
|
|
try {
|
|
for (let wheelId = 0; wheelId < 4; wheelId++) {
|
|
veh.setSuspensionHeight(wheelId, 0.0);
|
|
}
|
|
} catch(e) {}
|
|
|
|
// Reset turn velocity
|
|
try {
|
|
veh.turnVelocity = new Vec3(0, 0, 0);
|
|
} catch(e) {}
|
|
|
|
// Repair vehicle
|
|
try { natives.fixCar(veh); } catch(e) {}
|
|
}
|
|
|
|
// Reset handling mods
|
|
handlingMods = {
|
|
grip: 1.0,
|
|
acceleration: 1.0,
|
|
topSpeed: 1.0,
|
|
braking: 1.0
|
|
};
|
|
|
|
console.log("[Handling] Reset to defaults - Physics Emulation reset");
|
|
}
|
|
|
|
// Apply handling presets - Configures PHYSICS EMULATION for each setup
|
|
function applyHandlingPreset(preset) {
|
|
switch(preset) {
|
|
case "race":
|
|
// RACE: High grip, fast acceleration, high top speed, strong brakes
|
|
handlingValues.tractionCurveMax = 3.5;
|
|
handlingValues.tractionCurveMin = 3.0;
|
|
handlingValues.tractionLoss = 0.5;
|
|
handlingValues.suspensionForce = 4.0;
|
|
handlingValues.suspensionCompDamp = 2.0;
|
|
handlingValues.suspensionRaise = -0.05;
|
|
handlingValues.driveForce = 0.50;
|
|
handlingValues.initialDriveMaxVel = 300.0;
|
|
handlingValues.brakeForce = 1.5;
|
|
handlingValues.mass = 1200.0;
|
|
handlingValues.centreOfMassZ = -0.1;
|
|
|
|
// Physics Emulation: Maximum grip and stability
|
|
physicsEmulation.gripAssistEnabled = true;
|
|
physicsEmulation.gripAssistStrength = 1.8;
|
|
physicsEmulation.stabilityStrength = 1.5;
|
|
physicsEmulation.antiRollStrength = 0.7;
|
|
physicsEmulation.maxSpeedLimit = 85.0; // ~300 km/h
|
|
break;
|
|
|
|
case "offroad":
|
|
// OFFROAD: Good grip, high suspension, heavy, stable
|
|
handlingValues.tractionCurveMax = 2.5;
|
|
handlingValues.tractionCurveMin = 2.0;
|
|
handlingValues.tractionLoss = 0.6;
|
|
handlingValues.suspensionForce = 1.5;
|
|
handlingValues.suspensionCompDamp = 0.6;
|
|
handlingValues.suspensionRaise = 0.12;
|
|
handlingValues.driveForce = 0.35;
|
|
handlingValues.initialDriveMaxVel = 180.0;
|
|
handlingValues.brakeForce = 1.0;
|
|
handlingValues.mass = 2000.0;
|
|
handlingValues.centreOfMassZ = 0.1;
|
|
|
|
// Physics Emulation: Good grip, soft response
|
|
physicsEmulation.gripAssistEnabled = true;
|
|
physicsEmulation.gripAssistStrength = 1.2;
|
|
physicsEmulation.stabilityStrength = 0.8;
|
|
physicsEmulation.antiRollStrength = 0.4; // More body roll allowed
|
|
physicsEmulation.maxSpeedLimit = 50.0;
|
|
break;
|
|
|
|
case "lowrider":
|
|
// LOWRIDER: Slow, bouncy, low, cruising style
|
|
handlingValues.tractionCurveMax = 2.0;
|
|
handlingValues.tractionCurveMin = 1.8;
|
|
handlingValues.tractionLoss = 0.8;
|
|
handlingValues.suspensionForce = 0.8;
|
|
handlingValues.suspensionCompDamp = 0.4;
|
|
handlingValues.suspensionRaise = -0.12;
|
|
handlingValues.driveForce = 0.20;
|
|
handlingValues.initialDriveMaxVel = 150.0;
|
|
handlingValues.brakeForce = 0.6;
|
|
handlingValues.mass = 1800.0;
|
|
handlingValues.centreOfMassZ = -0.15;
|
|
|
|
// Physics Emulation: Smooth, stable, not fast
|
|
physicsEmulation.gripAssistEnabled = true;
|
|
physicsEmulation.gripAssistStrength = 1.0;
|
|
physicsEmulation.stabilityStrength = 1.2;
|
|
physicsEmulation.antiRollStrength = 0.6;
|
|
physicsEmulation.maxSpeedLimit = 42.0; // ~150 km/h
|
|
break;
|
|
}
|
|
|
|
// Apply all values through physics emulation system
|
|
for (let param in handlingValues) {
|
|
applyHandlingValue(param, handlingValues[param]);
|
|
}
|
|
|
|
// Update handling mods based on preset
|
|
handlingMods.acceleration = handlingValues.driveForce / 0.25;
|
|
handlingMods.topSpeed = handlingValues.initialDriveMaxVel / 200.0;
|
|
handlingMods.braking = handlingValues.brakeForce / 0.8;
|
|
handlingMods.grip = handlingValues.tractionCurveMax / 2.0;
|
|
|
|
// Update visuals
|
|
updateAllHandlingVisuals();
|
|
console.log("[Handling] Applied preset: " + preset + " (Physics Emulation configured)");
|
|
}
|
|
|
|
// Update visual feedback based on parameter
|
|
function updateHandlingVisuals(param, value) {
|
|
switch(param) {
|
|
case "tractionCurveMax":
|
|
case "tractionCurveMin":
|
|
case "tractionLoss":
|
|
// Tyre color: Red = low grip, Green = high grip
|
|
let gripFactor = Math.min(1, Math.max(0, (value - 0.5) / 4.0));
|
|
let r = Math.floor(255 * (1 - gripFactor));
|
|
let g = Math.floor(255 * gripFactor);
|
|
tyreGlowColors.fr = tyreGlowColors.fl = tyreGlowColors.rr = tyreGlowColors.rl = {r: r, g: g, b: 50};
|
|
break;
|
|
case "tractionBias":
|
|
// Front vs rear tyre colors
|
|
let frontGrip = 1 - value;
|
|
let rearGrip = value;
|
|
tyreGlowColors.fr = tyreGlowColors.fl = {r: Math.floor(255 * (1 - frontGrip)), g: Math.floor(255 * frontGrip), b: 50};
|
|
tyreGlowColors.rr = tyreGlowColors.rl = {r: Math.floor(255 * (1 - rearGrip)), g: Math.floor(255 * rearGrip), b: 50};
|
|
break;
|
|
case "driveForce":
|
|
// Engine glow intensity based on power
|
|
engineGlow.intensity = Math.min(1, value / 0.5);
|
|
engineGlow.r = 255;
|
|
engineGlow.g = Math.floor(100 + 100 * (1 - engineGlow.intensity));
|
|
engineGlow.b = 0;
|
|
break;
|
|
case "brakeForce":
|
|
// Brake glow
|
|
brakeGlow.intensity = Math.min(1, value / 2.0);
|
|
break;
|
|
case "suspensionRaise":
|
|
suspensionOffset = value;
|
|
break;
|
|
case "mass":
|
|
// Heavier = darker shadow/glow underneath
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update all visuals based on current handling values
|
|
function updateAllHandlingVisuals() {
|
|
updateHandlingVisuals("tractionCurveMax", handlingValues.tractionCurveMax);
|
|
updateHandlingVisuals("driveForce", handlingValues.driveForce);
|
|
updateHandlingVisuals("brakeForce", handlingValues.brakeForce);
|
|
updateHandlingVisuals("suspensionRaise", handlingValues.suspensionRaise);
|
|
}
|
|
|
|
// Check if we're in a handling editor submenu
|
|
function isInHandlingEditor() {
|
|
return currentMenu === "handlingEditor" ||
|
|
currentMenu.startsWith("handling_");
|
|
}
|
|
|
|
// ============================================================================
|
|
// HANDLING EDITOR VISUALIZATION - Clean & Informative
|
|
// ============================================================================
|
|
|
|
// Render the handling editor visualization
|
|
addEventHandler("OnDrawnHUD", function(event) {
|
|
// Only render when in handling editor menus and menu is open
|
|
if (!menuOpen || !isInHandlingEditor()) {
|
|
handlingEditorAnim = Math.max(0, handlingEditorAnim - 0.15);
|
|
if (handlingEditorAnim <= 0) return;
|
|
} else {
|
|
handlingEditorAnim = Math.min(1, handlingEditorAnim + 0.12);
|
|
}
|
|
|
|
let theme = getTheme();
|
|
let alpha = Math.floor(255 * handlingEditorAnim);
|
|
|
|
// Panel position (right side of screen, below status panel)
|
|
let panelX = 500;
|
|
let panelY = 120;
|
|
let panelW = 380;
|
|
let panelH = 520;
|
|
|
|
// Slide in animation from right
|
|
let slideOffset = (1 - handlingEditorAnim) * 150;
|
|
panelX += slideOffset;
|
|
|
|
// ===== PANEL BACKGROUND =====
|
|
let bgColor = toColour(15, 15, 20, Math.floor(235 * handlingEditorAnim));
|
|
drawRect(panelX, panelY, panelW, panelH, bgColor);
|
|
|
|
// Border
|
|
let borderColor = toColour(theme.primary.r, theme.primary.g, theme.primary.b, Math.floor(alpha * 0.8));
|
|
drawRect(panelX, panelY, panelW, 2, borderColor);
|
|
drawRect(panelX, panelY + panelH - 2, panelW, 2, borderColor);
|
|
drawRect(panelX, panelY, 2, panelH, borderColor);
|
|
drawRect(panelX + panelW - 2, panelY, 2, panelH, borderColor);
|
|
|
|
// ===== HEADER =====
|
|
let headerBg = toColour(25, 25, 35, alpha);
|
|
drawRect(panelX + 2, panelY + 2, panelW - 4, 35, headerBg);
|
|
|
|
let titleColor = toColour(255, 255, 255, alpha);
|
|
drawText("HANDLING TUNER", panelX + 12, panelY + 10, titleColor, 16);
|
|
|
|
// Vehicle status
|
|
let statusText = "NO VEHICLE";
|
|
let statusColor = toColour(255, 80, 80, alpha);
|
|
if (localPlayer && localPlayer.vehicle) {
|
|
statusText = "ACTIVE";
|
|
statusColor = toColour(80, 255, 80, alpha);
|
|
}
|
|
drawText(statusText, panelX + panelW - 70, panelY + 12, statusColor, 11);
|
|
|
|
// ===== TOP-DOWN CAR DIAGRAM =====
|
|
let carCenterX = panelX + panelW / 2;
|
|
let carCenterY = panelY + 130;
|
|
let carW = 70;
|
|
let carH = 120;
|
|
let carX = carCenterX - carW / 2;
|
|
let carY = carCenterY - carH / 2;
|
|
|
|
// Suspension height visual offset (subtle)
|
|
let suspOffset = suspensionOffset * 30;
|
|
carY += suspOffset;
|
|
|
|
// Car body outline
|
|
let bodyOutline = toColour(60, 60, 70, alpha);
|
|
drawRect(carX - 1, carY - 1, carW + 2, carH + 2, bodyOutline);
|
|
|
|
// Car body
|
|
let bodyColor = toColour(50, 50, 60, alpha);
|
|
drawRect(carX, carY, carW, carH, bodyColor);
|
|
|
|
// Windshield
|
|
let glassColor = toColour(80, 100, 120, alpha);
|
|
drawRect(carX + 10, carY + 25, carW - 20, 25, glassColor);
|
|
drawRect(carX + 12, carY + carH - 35, carW - 24, 18, glassColor);
|
|
|
|
// ===== WHEELS WITH GRIP INDICATOR =====
|
|
let wheelW = 18;
|
|
let wheelH = 30;
|
|
let wheels = [
|
|
{ x: carX - wheelW + 3, y: carY + 12, color: tyreGlowColors.fl, label: "FL" },
|
|
{ x: carX + carW - 3, y: carY + 12, color: tyreGlowColors.fr, label: "FR" },
|
|
{ x: carX - wheelW + 3, y: carY + carH - 12 - wheelH, color: tyreGlowColors.rl, label: "RL" },
|
|
{ x: carX + carW - 3, y: carY + carH - 12 - wheelH, color: tyreGlowColors.rr, label: "RR" }
|
|
];
|
|
|
|
for (let i = 0; i < wheels.length; i++) {
|
|
let w = wheels[i];
|
|
|
|
// Wheel background
|
|
let wheelBg = toColour(25, 25, 30, alpha);
|
|
drawRect(w.x, w.y, wheelW, wheelH, wheelBg);
|
|
|
|
// Grip indicator bar on wheel (colored based on traction)
|
|
let gripBarH = Math.floor(wheelH * 0.8);
|
|
let gripColor = toColour(w.color.r, w.color.g, w.color.b, alpha);
|
|
drawRect(w.x + 2, w.y + (wheelH - gripBarH) / 2, 4, gripBarH, gripColor);
|
|
|
|
// Wheel outline
|
|
let wheelOutline = toColour(80, 80, 90, alpha);
|
|
drawRect(w.x, w.y, wheelW, 1, wheelOutline);
|
|
drawRect(w.x, w.y + wheelH - 1, wheelW, 1, wheelOutline);
|
|
}
|
|
|
|
// ===== TRACTION BIAS ARROWS =====
|
|
let biasY = carCenterY;
|
|
let frontBias = 1 - handlingValues.tractionBias;
|
|
let rearBias = handlingValues.tractionBias;
|
|
|
|
// Front bias indicator (above car)
|
|
let frontArrowColor = toColour(100, 200, 100, Math.floor(alpha * frontBias));
|
|
drawRect(carCenterX - 15, carY - 15, 30, 3, frontArrowColor);
|
|
drawText(Math.floor(frontBias * 100) + "%", carCenterX - 12, carY - 28, frontArrowColor, 9);
|
|
|
|
// Rear bias indicator (below car)
|
|
let rearArrowColor = toColour(100, 200, 100, Math.floor(alpha * rearBias));
|
|
drawRect(carCenterX - 15, carY + carH + 12, 30, 3, rearArrowColor);
|
|
drawText(Math.floor(rearBias * 100) + "%", carCenterX - 12, carY + carH + 18, rearArrowColor, 9);
|
|
|
|
// Center of mass indicator
|
|
let comY = carCenterY + (handlingValues.centreOfMassZ * 50);
|
|
let comColor = toColour(255, 200, 0, alpha);
|
|
drawRect(carCenterX - 4, comY - 4, 8, 8, comColor);
|
|
|
|
// ===== DATA TABLE =====
|
|
let tableX = panelX + 15;
|
|
let tableY = panelY + 210;
|
|
let labelCol = toColour(160, 160, 170, alpha);
|
|
let valueCol = toColour(255, 255, 255, alpha);
|
|
let rowH = 22;
|
|
|
|
// Section: Traction
|
|
let sectionColor = toColour(theme.accent.r, theme.accent.g, theme.accent.b, alpha);
|
|
drawText("TRACTION", tableX, tableY, sectionColor, 11);
|
|
tableY += 18;
|
|
|
|
drawText("Grip Level:", tableX, tableY, labelCol, 10);
|
|
drawText(handlingValues.tractionCurveMax.toFixed(2), tableX + 140, tableY, valueCol, 10);
|
|
let gripDesc = handlingValues.tractionCurveMax < 1.5 ? "Slippery" : handlingValues.tractionCurveMax < 2.5 ? "Normal" : handlingValues.tractionCurveMax < 3.5 ? "High" : "Maximum";
|
|
drawText(gripDesc, tableX + 200, tableY, toColour(tyreGlowColors.fr.r, tyreGlowColors.fr.g, tyreGlowColors.fr.b, alpha), 10);
|
|
tableY += rowH;
|
|
|
|
drawText("Traction Loss:", tableX, tableY, labelCol, 10);
|
|
drawText(handlingValues.tractionLoss.toFixed(2), tableX + 140, tableY, valueCol, 10);
|
|
let lossDesc = handlingValues.tractionLoss < 0.5 ? "Stable" : handlingValues.tractionLoss < 1.0 ? "Normal" : "Loose";
|
|
drawText(lossDesc, tableX + 200, tableY, labelCol, 10);
|
|
tableY += rowH;
|
|
|
|
drawText("Bias (F/R):", tableX, tableY, labelCol, 10);
|
|
drawText(Math.floor(frontBias * 100) + "/" + Math.floor(rearBias * 100), tableX + 140, tableY, valueCol, 10);
|
|
tableY += rowH + 8;
|
|
|
|
// Section: Engine
|
|
drawText("ENGINE", tableX, tableY, sectionColor, 11);
|
|
tableY += 18;
|
|
|
|
drawText("Power:", tableX, tableY, labelCol, 10);
|
|
drawText(handlingValues.driveForce.toFixed(2), tableX + 140, tableY, valueCol, 10);
|
|
let powerPct = Math.floor((handlingValues.driveForce / 1.2) * 100);
|
|
let powerColor = powerPct > 100 ? toColour(255, 150, 0, alpha) : valueCol;
|
|
drawText(powerPct + "%", tableX + 200, tableY, powerColor, 10);
|
|
tableY += rowH;
|
|
|
|
drawText("Top Speed:", tableX, tableY, labelCol, 10);
|
|
drawText(handlingValues.initialDriveMaxVel.toFixed(0) + " km/h", tableX + 140, tableY, valueCol, 10);
|
|
tableY += rowH;
|
|
|
|
drawText("Brake Force:", tableX, tableY, labelCol, 10);
|
|
drawText(handlingValues.brakeForce.toFixed(2), tableX + 140, tableY, valueCol, 10);
|
|
let brakePct = Math.floor((handlingValues.brakeForce / 3.0) * 100);
|
|
drawText(brakePct + "%", tableX + 200, tableY, valueCol, 10);
|
|
tableY += rowH + 8;
|
|
|
|
// Section: Suspension
|
|
drawText("SUSPENSION", tableX, tableY, sectionColor, 11);
|
|
tableY += 18;
|
|
|
|
drawText("Height:", tableX, tableY, labelCol, 10);
|
|
let heightDesc = handlingValues.suspensionRaise < -0.08 ? "Slammed" : handlingValues.suspensionRaise < -0.02 ? "Low" : handlingValues.suspensionRaise < 0.02 ? "Stock" : handlingValues.suspensionRaise < 0.08 ? "Raised" : "Lifted";
|
|
drawText(heightDesc, tableX + 140, tableY, valueCol, 10);
|
|
drawText((handlingValues.suspensionRaise >= 0 ? "+" : "") + handlingValues.suspensionRaise.toFixed(2), tableX + 200, tableY, labelCol, 10);
|
|
tableY += rowH;
|
|
|
|
drawText("Stiffness:", tableX, tableY, labelCol, 10);
|
|
let stiffDesc = handlingValues.suspensionForce < 1.5 ? "Soft" : handlingValues.suspensionForce < 3.0 ? "Normal" : handlingValues.suspensionForce < 5.0 ? "Firm" : "Race";
|
|
drawText(stiffDesc, tableX + 140, tableY, valueCol, 10);
|
|
drawText(handlingValues.suspensionForce.toFixed(1), tableX + 200, tableY, labelCol, 10);
|
|
tableY += rowH + 8;
|
|
|
|
// Section: Weight
|
|
drawText("WEIGHT", tableX, tableY, sectionColor, 11);
|
|
tableY += 18;
|
|
|
|
drawText("Mass:", tableX, tableY, labelCol, 10);
|
|
drawText(handlingValues.mass.toFixed(0) + " kg", tableX + 140, tableY, valueCol, 10);
|
|
let massDesc = handlingValues.mass < 1000 ? "Light" : handlingValues.mass < 2000 ? "Normal" : handlingValues.mass < 3500 ? "Heavy" : "Tank";
|
|
drawText(massDesc, tableX + 200, tableY, labelCol, 10);
|
|
tableY += rowH;
|
|
|
|
drawText("CoM Height:", tableX, tableY, labelCol, 10);
|
|
let comDesc = handlingValues.centreOfMassZ < -0.1 ? "Low" : handlingValues.centreOfMassZ < 0.1 ? "Center" : "High";
|
|
drawText(comDesc, tableX + 140, tableY, valueCol, 10);
|
|
tableY += rowH + 8;
|
|
|
|
// ===== LIVE VEHICLE DATA =====
|
|
if (localPlayer && localPlayer.vehicle) {
|
|
let veh = localPlayer.vehicle;
|
|
drawText("LIVE DATA", tableX, tableY, sectionColor, 11);
|
|
tableY += 18;
|
|
|
|
try {
|
|
let vel = veh.velocity;
|
|
let speed = Math.sqrt(vel.x * vel.x + vel.y * vel.y) * 3.6; // m/s to km/h
|
|
drawText("Speed:", tableX, tableY, labelCol, 10);
|
|
drawText(speed.toFixed(0) + " km/h", tableX + 140, tableY, toColour(0, 200, 255, alpha), 10);
|
|
tableY += rowH;
|
|
} catch(e) {}
|
|
|
|
try {
|
|
let health = veh.health;
|
|
drawText("Health:", tableX, tableY, labelCol, 10);
|
|
let healthColor = health > 700 ? toColour(0, 255, 0, alpha) : health > 300 ? toColour(255, 200, 0, alpha) : toColour(255, 50, 50, alpha);
|
|
drawText(health.toFixed(0), tableX + 140, tableY, healthColor, 10);
|
|
} catch(e) {}
|
|
}
|
|
|
|
// ===== FOOTER =====
|
|
let footerY = panelY + panelH - 22;
|
|
let footerColor = toColour(100, 100, 110, alpha);
|
|
drawText("Changes apply in real-time", tableX, footerY, footerColor, 9);
|
|
});
|
|
|
|
addEventHandler("OnResourceStart", function(event, resource) {
|
|
console.log("[ModMenu] Client loaded - Press F5 to open menu");
|
|
});
|
|
|
|
console.log("[ModMenu] Script initialized");
|