From 7bc52ef9d4a04f70869b2472fa956e74eea92581 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Jan 2026 15:41:25 +0000 Subject: [PATCH 1/3] Fix client-side drawing API to use Vec2 and graphics API - Replace natives.drawRect with graphics.drawRectangle using Vec2 objects - Replace natives text functions with font.render using Vec2 position - Correct signatures: graphics.drawRectangle(null, Vec2, Vec2, colour...) - Correct signatures: font.render(text, Vec2, width, align, justify, size, colour) --- resources/modmenu/client.js | 72 ++++++++++--------------------------- 1 file changed, 18 insertions(+), 54 deletions(-) diff --git a/resources/modmenu/client.js b/resources/modmenu/client.js index ad7832f..b89bb6d 100644 --- a/resources/modmenu/client.js +++ b/resources/modmenu/client.js @@ -831,65 +831,29 @@ addEventHandler("OnDrawnHUD", function(event) { } }); -// Helper drawing functions using native drawing +// Helper drawing functions using GTAC graphics API function drawRect(x, y, width, height, colour) { - // Use natives for drawing rectangles - if (typeof natives !== "undefined" && natives.drawRect) { - // GTA IV native drawing - normalized coordinates (0-1) - let screenW = game.width || 1920; - let screenH = game.height || 1080; - - let nx = (x + width/2) / screenW; - let ny = (y + height/2) / screenH; - let nw = width / screenW; - let nh = height / screenH; - - // Extract RGBA from colour - let r = (colour >> 24) & 0xFF; - let g = (colour >> 16) & 0xFF; - let b = (colour >> 8) & 0xFF; - let a = colour & 0xFF; - - natives.drawRect(nx, ny, nw, nh, r, g, b, a); - } else { - // Fallback to graphics drawing - try { - graphics.drawRectangle(null, [x, y], [width, height], colour, colour, 0, 0, 0); - } catch(e) { - // Silent fail - } + // Use graphics.drawRectangle with Vec2 objects + // Signature: graphics.drawRectangle(surface, Vec2 position, Vec2 size, colour1, colour2, colour3, colour4, rotation) + try { + let pos = new Vec2(x, y); + let size = new Vec2(width, height); + graphics.drawRectangle(null, pos, size, colour, colour, colour, colour, 0); + } catch(e) { + // Silent fail } } function drawText(text, x, y, colour, centered, scale) { - // Use natives for drawing text - if (typeof natives !== "undefined" && natives.setTextScale) { - let screenW = game.width || 1920; - let screenH = game.height || 1080; - - let nx = x / screenW; - let ny = y / screenH; - - let r = (colour >> 24) & 0xFF; - let g = (colour >> 16) & 0xFF; - let b = (colour >> 8) & 0xFF; - let a = colour & 0xFF; - - natives.setTextFont(0); - natives.setTextScale(scale || 0.35, scale || 0.35); - natives.setTextColour(r, g, b, a); - if (centered) { - natives.setTextCentre(true); - } - natives.setTextDropshadow(2, 0, 0, 0, 255); - natives.displayTextWithLiteralString(nx, ny, "STRING", text); - } else { - // Fallback - try { - graphics.drawText(text, [x, y], colour, scale || 1.0, "arial", centered || false); - } catch(e) { - // Silent fail - } + // Use font.render with Vec2 position + // Signature: font.render(text, Vec2 position, width, align, justify, size, colour, wordWrap, colourCodes, ignoreColourCodes, shadow) + try { + let pos = new Vec2(x, y); + let textScale = (scale || 1.0) * 14; // Convert scale to pixel size + let align = centered ? 0.5 : 0.0; // 0.5 = center, 0.0 = left + font.render(text, pos, 500, align, 0.0, textScale, colour, false, false, false, true); + } catch(e) { + // Silent fail } } From 3c68c3a4a4cbe1f00fb61479b7ee748185f4b20a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Jan 2026 15:51:25 +0000 Subject: [PATCH 2/3] Rewrite mod menu drawing to use GTA IV natives with Vec2 - Move menu to right side of screen (x=0.73) to avoid chat - Use normalized coordinates (0-1) for screen positions - Use natives.drawRect(Vec2 pos, Vec2 size, r, g, b, a) - Use natives.displayText(Vec2) with addTextComponentString/drawText - Separate RGBA color components instead of toColour integer - Fix text rendering with proper native text function sequence --- resources/modmenu/client.js | 137 ++++++++++++++++++++---------------- 1 file changed, 78 insertions(+), 59 deletions(-) diff --git a/resources/modmenu/client.js b/resources/modmenu/client.js index b89bb6d..6589fde 100644 --- a/resources/modmenu/client.js +++ b/resources/modmenu/client.js @@ -10,28 +10,24 @@ let currentMenu = "main"; let selectedIndex = 0; let menuStack = []; // For back navigation -// Menu colors (RGBA) +// Menu colors (RGBA components) const colors = { - background: toColour(20, 20, 20, 200), - header: toColour(200, 50, 50, 255), - headerText: toColour(255, 255, 255, 255), - itemBg: toColour(40, 40, 40, 200), - itemBgSelected: toColour(200, 50, 50, 220), - itemText: toColour(255, 255, 255, 255), - itemTextSelected: toColour(255, 255, 255, 255), - footer: toColour(30, 30, 30, 200), - footerText: toColour(180, 180, 180, 255), - subText: toColour(150, 150, 150, 255) + bgR: 20, bgG: 20, bgB: 20, bgA: 200, + headerR: 200, headerG: 50, headerB: 50, headerA: 255, + itemR: 40, itemG: 40, itemB: 40, itemA: 200, + selectedR: 200, selectedG: 50, selectedB: 50, selectedA: 220, + textR: 255, textG: 255, textB: 255, textA: 255, + footerR: 30, footerG: 30, footerB: 30, footerA: 200 }; -// Menu dimensions +// Menu dimensions - positioned on RIGHT side to avoid chat const menu = { - x: 50, - y: 100, - width: 300, - headerHeight: 40, - itemHeight: 35, - footerHeight: 30, + x: 0.73, // Normalized (0-1) - right side of screen + y: 0.15, // Normalized (0-1) - top area + width: 0.24, + headerHeight: 0.045, + itemHeight: 0.038, + footerHeight: 0.035, maxVisibleItems: 12 }; @@ -421,7 +417,6 @@ addEventHandler("OnKeyUp", function(event, key, scanCode, mods) { selectedIndex = 0; scrollOffset = 0; menuStack = []; - // Show cursor - use gui if available if (typeof gui !== "undefined" && gui.showCursor) { gui.showCursor(true, true); } @@ -526,7 +521,6 @@ function getNetworkMenuItems() { // ACTION HANDLING // ============================================================================ -// Selected player for network options let selectedPlayer = null; function selectItem() { @@ -767,7 +761,7 @@ addNetworkHandler("ModMenu:Notification", function(message) { }); // ============================================================================ -// RENDERING - Using correct GTAC drawing API +// RENDERING - Using GTA IV Native Drawing Functions with Vec2 // ============================================================================ addEventHandler("OnDrawnHUD", function(event) { @@ -782,22 +776,28 @@ addEventHandler("OnDrawnHUD", function(event) { let totalHeight = menu.headerHeight + (visibleCount * menu.itemHeight) + menu.footerHeight; // Draw background - drawRect(menu.x - 5, menu.y - 5, menu.width + 10, totalHeight + 10, colors.background); + drawMenuRect(menu.x - 0.005, menu.y - 0.005, menu.width + 0.01, totalHeight + 0.01, + colors.bgR, colors.bgG, colors.bgB, colors.bgA); // Draw header - drawRect(menu.x, menu.y, menu.width, menu.headerHeight, colors.header); - drawText(title, menu.x + menu.width / 2, menu.y + 10, colors.headerText, true, 1.0); + drawMenuRect(menu.x, menu.y, menu.width, menu.headerHeight, + colors.headerR, colors.headerG, colors.headerB, colors.headerA); + drawMenuText(title, menu.x + menu.width / 2, menu.y + 0.012, 0.45, true); // Draw items let yPos = menu.y + menu.headerHeight; for (let i = scrollOffset; i < scrollOffset + visibleCount && i < items.length; i++) { let item = items[i]; let isSelected = (i === selectedIndex); - let bgColor = isSelected ? colors.itemBgSelected : colors.itemBg; - let textColor = isSelected ? colors.itemTextSelected : colors.itemText; // Draw item background - drawRect(menu.x, yPos, menu.width, menu.itemHeight, bgColor); + if (isSelected) { + drawMenuRect(menu.x, yPos, menu.width, menu.itemHeight, + colors.selectedR, colors.selectedG, colors.selectedB, colors.selectedA); + } else { + drawMenuRect(menu.x, yPos, menu.width, menu.itemHeight, + colors.itemR, colors.itemG, colors.itemB, colors.itemA); + } // Build label with state indicators let label = item.label; @@ -815,46 +815,55 @@ addEventHandler("OnDrawnHUD", function(event) { label += " >>"; } - drawText(label, menu.x + 15, yPos + 8, textColor, false, 0.9); + drawMenuText(label, menu.x + 0.01, yPos + 0.008, 0.35, false); yPos += menu.itemHeight; } // Draw footer - drawRect(menu.x, yPos, menu.width, menu.footerHeight, colors.footer); - drawText("UP/DOWN: Navigate | ENTER: Select | BACKSPACE: Back", menu.x + menu.width / 2, yPos + 8, colors.footerText, true, 0.6); + drawMenuRect(menu.x, yPos, menu.width, menu.footerHeight, + colors.footerR, colors.footerG, colors.footerB, colors.footerA); + drawMenuText("UP/DOWN: Nav | ENTER: Select | BACKSPACE: Back", + menu.x + menu.width / 2, yPos + 0.008, 0.25, true); // Draw scroll indicator if (items.length > menu.maxVisibleItems) { - let scrollText = (scrollOffset + 1) + "-" + Math.min(scrollOffset + visibleCount, items.length) + " / " + items.length; - drawText(scrollText, menu.x + menu.width - 50, menu.y + 12, colors.subText, false, 0.7); + let scrollText = (scrollOffset + 1) + "-" + Math.min(scrollOffset + visibleCount, items.length) + "/" + items.length; + drawMenuText(scrollText, menu.x + menu.width - 0.03, menu.y + 0.012, 0.3, false); } }); -// Helper drawing functions using GTAC graphics API -function drawRect(x, y, width, height, colour) { - // Use graphics.drawRectangle with Vec2 objects - // Signature: graphics.drawRectangle(surface, Vec2 position, Vec2 size, colour1, colour2, colour3, colour4, rotation) - try { - let pos = new Vec2(x, y); - let size = new Vec2(width, height); - graphics.drawRectangle(null, pos, size, colour, colour, colour, colour, 0); - } catch(e) { - // Silent fail - } +// Drawing functions using GTA IV natives with Vec2 +function drawMenuRect(x, y, w, h, r, g, b, a) { + // natives.drawRect expects: Vec2 position (center), Vec2 size, r, g, b, a + // Position is center-based, so adjust x and y + let centerX = x + w / 2; + let centerY = y + h / 2; + + let pos = new Vec2(centerX, centerY); + let size = new Vec2(w, h); + + natives.drawRect(pos, size, r, g, b, a); } -function drawText(text, x, y, colour, centered, scale) { - // Use font.render with Vec2 position - // Signature: font.render(text, Vec2 position, width, align, justify, size, colour, wordWrap, colourCodes, ignoreColourCodes, shadow) - try { - let pos = new Vec2(x, y); - let textScale = (scale || 1.0) * 14; // Convert scale to pixel size - let align = centered ? 0.5 : 0.0; // 0.5 = center, 0.0 = left - font.render(text, pos, 500, align, 0.0, textScale, colour, false, false, false, true); - } catch(e) { - // Silent fail +function drawMenuText(text, x, y, scale, centered) { + // Set up text properties + natives.setTextFont(0); + natives.setTextScale(scale, scale); + natives.setTextColour(255, 255, 255, 255); + natives.setTextDropshadow(2, 0, 0, 0, 255); + + if (centered) { + natives.setTextCentre(true); + } else { + natives.setTextCentre(false); } + + // displayText expects Vec2 position + let pos = new Vec2(x, y); + natives.displayText(pos, "STRING"); + natives.addTextComponentString(text); + natives.drawText(); } // ============================================================================ @@ -874,7 +883,7 @@ function showNotification(text) { // Draw notifications addEventHandler("OnDrawnHUD", function(event) { let now = Date.now(); - let yPos = 200; + let yPos = 0.25; for (let i = 0; i < notifications.length; i++) { let notif = notifications[i]; @@ -882,13 +891,23 @@ addEventHandler("OnDrawnHUD", function(event) { if (elapsed < notif.duration) { let alpha = elapsed < notif.duration - 500 ? 200 : Math.floor(200 * (notif.duration - elapsed) / 500); - let bgColor = toColour(20, 20, 20, alpha); - let textColor = toColour(255, 255, 100, alpha + 55); - drawRect(10, yPos, 300, 30, bgColor); - drawText(notif.text, 20, yPos + 6, textColor, false, 0.8); + // Draw notification background + drawMenuRect(0.01, yPos, 0.25, 0.035, 20, 20, 20, alpha); - yPos += 35; + // Draw notification text + natives.setTextFont(0); + natives.setTextScale(0.3, 0.3); + natives.setTextColour(255, 255, 100, Math.min(255, alpha + 55)); + natives.setTextDropshadow(2, 0, 0, 0, 255); + natives.setTextCentre(false); + + let pos = new Vec2(0.015, yPos + 0.008); + natives.displayText(pos, "STRING"); + natives.addTextComponentString(notif.text); + natives.drawText(); + + yPos += 0.04; } } From 889c3e56e59348052618fbde4dc8878e5a492735 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Jan 2026 03:45:07 +0000 Subject: [PATCH 3/3] Fix mod menu: control locking and use default font - Fix control locking: gui.showCursor(false, true) re-enables controls on close - Use lucasFont.createDefaultFont instead of loading external TTF file - Use graphics.drawRectangle with pixel coordinates for menu boxes - Use toColour() for color integers - Menu positioned at right side of screen (x=1050) --- resources/modmenu/client.js | 451 +++++++++--------------------------- 1 file changed, 114 insertions(+), 337 deletions(-) diff --git a/resources/modmenu/client.js b/resources/modmenu/client.js index 6589fde..dddf109 100644 --- a/resources/modmenu/client.js +++ b/resources/modmenu/client.js @@ -8,32 +8,34 @@ let menuOpen = false; let currentMenu = "main"; let selectedIndex = 0; -let menuStack = []; // For back navigation +let menuStack = []; +let scrollOffset = 0; -// Menu colors (RGBA components) +// Font for text rendering (will be loaded on resource start) +let menuFont = null; + +// Menu colors using toColour for integer format const colors = { - bgR: 20, bgG: 20, bgB: 20, bgA: 200, - headerR: 200, headerG: 50, headerB: 50, headerA: 255, - itemR: 40, itemG: 40, itemB: 40, itemA: 200, - selectedR: 200, selectedG: 50, selectedB: 50, selectedA: 220, - textR: 255, textG: 255, textB: 255, textA: 255, - footerR: 30, footerG: 30, footerB: 30, footerA: 200 + background: toColour(20, 20, 20, 200), + header: toColour(200, 50, 50, 255), + item: toColour(40, 40, 40, 200), + selected: toColour(200, 50, 50, 220), + text: toColour(255, 255, 255, 255), + footer: toColour(30, 30, 30, 200), + subText: toColour(180, 180, 180, 255) }; -// Menu dimensions - positioned on RIGHT side to avoid chat +// Menu dimensions in pixels - positioned on right side const menu = { - x: 0.73, // Normalized (0-1) - right side of screen - y: 0.15, // Normalized (0-1) - top area - width: 0.24, - headerHeight: 0.045, - itemHeight: 0.038, - footerHeight: 0.035, + x: 1050, + y: 120, + width: 320, + headerHeight: 45, + itemHeight: 38, + footerHeight: 32, maxVisibleItems: 12 }; -// Scroll offset for long menus -let scrollOffset = 0; - // Player list cache let playerList = []; @@ -75,9 +77,6 @@ const menuData = { { label: "Clear Wanted Level", action: "self_wanted" }, { label: "God Mode", action: "toggle", target: "godMode", state: false }, { label: "Never Wanted", action: "toggle", target: "neverWanted", state: false }, - { label: "Infinite Ammo", action: "toggle", target: "infiniteAmmo", state: false }, - { label: "Super Jump", action: "toggle", target: "superJump", state: false }, - { label: "Fast Run", action: "toggle", target: "fastRun", state: false }, { label: "Respawn", action: "self_respawn" }, { label: "Suicide", action: "self_suicide" }, { label: "Change Skin", action: "submenu", target: "skins" } @@ -95,11 +94,6 @@ const menuData = { { label: "Johnny Klebitz", action: "skin", value: -1784875845 }, { label: "Luis Lopez", action: "skin", value: -1403507487 }, { label: "Police Officer", action: "skin", value: -1320879687 }, - { label: "NOOSE", action: "skin", value: -1306011498 }, - { label: "Paramedic", action: "skin", value: 2136829318 }, - { label: "Firefighter", action: "skin", value: 1616659040 }, - { label: "Business Man", action: "skin", value: -268651930 }, - { label: "Hobo", action: "skin", value: 1943617350 }, { label: "Random Skin", action: "skin_random" } ] }, @@ -108,15 +102,12 @@ const menuData = { title: "VEHICLE SPAWNER", items: [ { label: "Sports Cars", action: "submenu", target: "veh_sports" }, - { label: "Super Cars", action: "submenu", target: "veh_super" }, { label: "Muscle Cars", action: "submenu", target: "veh_muscle" }, { label: "SUVs & Trucks", action: "submenu", target: "veh_suv" }, - { label: "Sedans & Compacts", action: "submenu", target: "veh_sedan" }, { label: "Motorcycles", action: "submenu", target: "veh_bikes" }, - { label: "Emergency Vehicles", action: "submenu", target: "veh_emergency" }, + { label: "Emergency", action: "submenu", target: "veh_emergency" }, { label: "Aircraft", action: "submenu", target: "veh_aircraft" }, { label: "Boats", action: "submenu", target: "veh_boats" }, - { label: "Special Vehicles", action: "submenu", target: "veh_special" }, { label: "Delete My Vehicles", action: "vehicle_delete" } ] }, @@ -130,23 +121,10 @@ const menuData = { { label: "Banshee", action: "spawn_vehicle", value: "banshee" }, { label: "Sultan", action: "spawn_vehicle", value: "sultan" }, { label: "Coquette", action: "spawn_vehicle", value: "coquette" }, - { label: "Feltzer", action: "spawn_vehicle", value: "feltzer" }, - { label: "F620", action: "spawn_vehicle", value: "f620" }, { label: "Buffalo", action: "spawn_vehicle", value: "buffalo" } ] }, - veh_super: { - title: "SUPER CARS", - items: [ - { label: "Entity XF", action: "spawn_vehicle", value: "entityxf" }, - { label: "Adder", action: "spawn_vehicle", value: "adder" }, - { label: "Vacca", action: "spawn_vehicle", value: "vacca" }, - { label: "Bullet", action: "spawn_vehicle", value: "bullet" }, - { label: "Cheetah", action: "spawn_vehicle", value: "cheetah" } - ] - }, - veh_muscle: { title: "MUSCLE CARS", items: [ @@ -154,10 +132,7 @@ const menuData = { { label: "Stallion", action: "spawn_vehicle", value: "stalion" }, { label: "Vigero", action: "spawn_vehicle", value: "vigero" }, { label: "Dukes", action: "spawn_vehicle", value: "dukes" }, - { label: "Ruiner", action: "spawn_vehicle", value: "ruiner" }, - { label: "Phoenix", action: "spawn_vehicle", value: "phoenix" }, - { label: "Gauntlet", action: "spawn_vehicle", value: "gauntlet" }, - { label: "Dominator", action: "spawn_vehicle", value: "dominator" } + { label: "Phoenix", action: "spawn_vehicle", value: "phoenix" } ] }, @@ -166,26 +141,8 @@ const menuData = { items: [ { label: "Patriot", action: "spawn_vehicle", value: "patriot" }, { label: "Cavalcade", action: "spawn_vehicle", value: "cavalcade" }, - { label: "Granger", action: "spawn_vehicle", value: "granger" }, { label: "Huntley", action: "spawn_vehicle", value: "huntley" }, - { label: "Landstalker", action: "spawn_vehicle", value: "landstalker" }, - { label: "Habanero", action: "spawn_vehicle", value: "habanero" }, - { label: "Serrano", action: "spawn_vehicle", value: "serrano" }, - { label: "Rebla", action: "spawn_vehicle", value: "rebla" } - ] - }, - - veh_sedan: { - title: "SEDANS & COMPACTS", - items: [ - { label: "Oracle", action: "spawn_vehicle", value: "oracle" }, - { label: "Schafter", action: "spawn_vehicle", value: "schafter" }, - { label: "Admiral", action: "spawn_vehicle", value: "admiral" }, - { label: "Vincent", action: "spawn_vehicle", value: "vincent" }, - { label: "Presidente", action: "spawn_vehicle", value: "presidente" }, - { label: "Cognoscenti", action: "spawn_vehicle", value: "cognoscenti" }, - { label: "Blista", action: "spawn_vehicle", value: "blista" }, - { label: "Premier", action: "spawn_vehicle", value: "premier" } + { label: "Landstalker", action: "spawn_vehicle", value: "landstalker" } ] }, @@ -195,26 +152,17 @@ const menuData = { { label: "NRG 900", action: "spawn_vehicle", value: "nrg900" }, { label: "PCJ 600", action: "spawn_vehicle", value: "pcj600" }, { label: "Sanchez", action: "spawn_vehicle", value: "sanchez" }, - { label: "Faggio", action: "spawn_vehicle", value: "faggio" }, - { label: "Bati", action: "spawn_vehicle", value: "bati" }, - { label: "Akuma", action: "spawn_vehicle", value: "akuma" }, - { label: "Double T", action: "spawn_vehicle", value: "double" }, - { label: "Hakuchou", action: "spawn_vehicle", value: "hakuchou" }, - { label: "Hexer", action: "spawn_vehicle", value: "hexer" }, - { label: "Daemon", action: "spawn_vehicle", value: "daemon" } + { label: "Faggio", action: "spawn_vehicle", value: "faggio" } ] }, veh_emergency: { - title: "EMERGENCY VEHICLES", + title: "EMERGENCY", items: [ { label: "Police Cruiser", action: "spawn_vehicle", value: "police" }, - { label: "Police Buffalo", 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: "Enforcer (SWAT)", action: "spawn_vehicle", value: "enforcer" } + { label: "Fire Truck", action: "spawn_vehicle", value: "firetruk" } ] }, @@ -223,9 +171,7 @@ const menuData = { items: [ { label: "Annihilator", action: "spawn_vehicle", value: "annihilator" }, { label: "Maverick", action: "spawn_vehicle", value: "maverick" }, - { label: "Police Maverick", action: "spawn_vehicle", value: "polmav" }, - { label: "Buzzard", action: "spawn_vehicle", value: "buzzard" }, - { label: "Shamal (Jet)", action: "spawn_vehicle", value: "shamal" } + { label: "Police Maverick", action: "spawn_vehicle", value: "polmav" } ] }, @@ -233,26 +179,8 @@ const menuData = { title: "BOATS", items: [ { label: "Jetmax", action: "spawn_vehicle", value: "jetmax" }, - { label: "Marquis", action: "spawn_vehicle", value: "marquis" }, { label: "Predator", action: "spawn_vehicle", value: "predator" }, - { label: "Tropic", action: "spawn_vehicle", value: "tropic" }, - { label: "Dinghy", action: "spawn_vehicle", value: "dinghy" }, - { label: "Squalo", action: "spawn_vehicle", value: "squalo" }, - { label: "Reefer", action: "spawn_vehicle", value: "reefer" } - ] - }, - - veh_special: { - title: "SPECIAL VEHICLES", - items: [ - { label: "Taxi", action: "spawn_vehicle", value: "taxi" }, - { label: "Stretch Limo", action: "spawn_vehicle", value: "stretch" }, - { label: "Bus", action: "spawn_vehicle", value: "bus" }, - { label: "Trashmaster", action: "spawn_vehicle", value: "trashmaster" }, - { label: "Forklift", action: "spawn_vehicle", value: "forklift" }, - { label: "Caddy", action: "spawn_vehicle", value: "caddy" }, - { label: "Bulldozer", action: "spawn_vehicle", value: "bulldozer" }, - { label: "Phantom", action: "spawn_vehicle", value: "phantom" } + { label: "Tropic", action: "spawn_vehicle", value: "tropic" } ] }, @@ -261,22 +189,9 @@ const menuData = { items: [ { label: "Repair Vehicle", action: "veh_repair" }, { label: "Flip Vehicle", action: "veh_flip" }, - { label: "Clean Vehicle", action: "veh_clean" }, - { label: "Max Upgrade", action: "veh_upgrade" }, { label: "Vehicle Colors", action: "submenu", target: "veh_colors" }, - { label: "--- Handling ---", action: "none" }, - { label: "Drift Mode", action: "toggle", target: "driftMode", state: false }, - { label: "Grip +", action: "handling", target: "grip", delta: 0.2 }, - { label: "Grip -", action: "handling", target: "grip", delta: -0.2 }, - { label: "Acceleration +", action: "handling", target: "acceleration", delta: 0.5 }, - { label: "Acceleration -", action: "handling", target: "acceleration", delta: -0.5 }, - { label: "Top Speed +", action: "handling", target: "topSpeed", delta: 0.3 }, - { label: "Top Speed -", action: "handling", target: "topSpeed", delta: -0.3 }, - { label: "Reset Handling", action: "handling_reset" }, - { label: "--- Special ---", action: "none" }, { label: "Indestructible", action: "toggle", target: "vehGodMode", state: false }, - { label: "Nitro Boost", action: "veh_nitro" }, - { label: "Super Brakes", action: "toggle", target: "superBrakes", state: false } + { label: "Nitro Boost", action: "veh_nitro" } ] }, @@ -289,11 +204,6 @@ const menuData = { { label: "Blue", action: "veh_color", value: [51, 51] }, { label: "Yellow", action: "veh_color", value: [42, 42] }, { label: "Green", action: "veh_color", value: [53, 53] }, - { label: "Orange", action: "veh_color", value: [38, 38] }, - { label: "Purple", action: "veh_color", value: [61, 61] }, - { label: "Pink", action: "veh_color", value: [68, 68] }, - { label: "Gold", action: "veh_color", value: [37, 37] }, - { label: "Chrome", action: "veh_color", value: [120, 120] }, { label: "Random", action: "veh_color_random" } ] }, @@ -301,50 +211,34 @@ const menuData = { network: { title: "NETWORK OPTIONS", items: [ - { label: ">> Refresh Player List <<", action: "refresh_players" }, - { label: "--- Players Online ---", action: "none" } + { label: "Refresh Player List", action: "refresh_players" }, + { label: "--- Players ---", action: "none" } ] }, teleport: { title: "TELEPORT LOCATIONS", items: [ - { label: "-- Algonquin --", action: "none" }, { label: "Star Junction", action: "teleport", value: { x: -252.0, y: 947.0, z: 15.0 } }, { label: "Middle Park", action: "teleport", value: { x: -365.0, y: 1163.0, z: 14.0 } }, - { label: "Rotterdam Tower", action: "teleport", value: { x: 237.0, y: 1002.0, z: 18.0 } }, - { label: "Chinatown", action: "teleport", value: { x: -141.0, y: 289.0, z: 14.0 } }, - { label: "Happiness Island", action: "teleport", value: { x: -722.0, y: -17.0, z: 3.0 } }, - { label: "-- Broker --", action: "none" }, - { label: "Broker Bridge", action: "teleport", value: { x: 932.0, y: -495.0, z: 15.0 } }, - { label: "Hove Beach", action: "teleport", value: { x: 1017.0, y: -505.0, z: 19.0 } }, { label: "Airport", action: "teleport", value: { x: 2140.0, y: 465.0, z: 6.0 } }, - { label: "-- Bohan --", action: "none" }, - { label: "South Bohan", action: "teleport", value: { x: 1243.0, y: -196.0, z: 26.0 } }, - { label: "-- Alderney --", action: "none" }, + { label: "Broker Bridge", action: "teleport", value: { x: 932.0, y: -495.0, z: 15.0 } }, { label: "Alderney City", action: "teleport", value: { x: -1149.0, y: 380.0, z: 21.0 } }, - { label: "Westdyke", action: "teleport", value: { x: -1745.0, y: 1157.0, z: 25.0 } }, - { label: "-- Special --", action: "none" }, - { label: "Helipad (High)", action: "teleport", value: { x: -290.0, y: -400.0, z: 81.0 } }, - { label: "Tower Top", action: "teleport", value: { x: 237.0, y: 1002.0, z: 200.0 } } + { label: "Happiness Island", action: "teleport", value: { x: -722.0, y: -17.0, z: 3.0 } } ] }, world: { title: "WORLD OPTIONS", items: [ - { label: "-- Time --", action: "none" }, { label: "Morning (8:00)", action: "world_time", value: 8 }, { label: "Noon (12:00)", action: "world_time", value: 12 }, { label: "Evening (18:00)", action: "world_time", value: 18 }, { label: "Night (0:00)", action: "world_time", value: 0 }, - { label: "-- Weather --", action: "none" }, - { label: "Extra Sunny", action: "world_weather", value: 0 }, { label: "Sunny", action: "world_weather", value: 1 }, { label: "Cloudy", action: "world_weather", value: 3 }, { label: "Rainy", action: "world_weather", value: 4 }, - { label: "Thunder", action: "world_weather", value: 7 }, - { label: "Foggy", action: "world_weather", value: 6 } + { label: "Thunder", action: "world_weather", value: 7 } ] }, @@ -352,26 +246,13 @@ const menuData = { title: "WEAPONS", items: [ { label: "Get All Weapons", action: "weapon_all" }, - { label: "-- Melee --", action: "none" }, - { label: "Baseball Bat", action: "weapon", value: 1 }, - { label: "Knife", action: "weapon", value: 2 }, - { label: "-- Pistols --", action: "none" }, { label: "Pistol", action: "weapon", value: 5 }, { label: "Desert Eagle", action: "weapon", value: 6 }, - { label: "-- Shotguns --", action: "none" }, { label: "Shotgun", action: "weapon", value: 9 }, - { label: "Combat Shotgun", action: "weapon", value: 10 }, - { label: "-- SMGs --", action: "none" }, - { label: "Micro SMG", action: "weapon", value: 11 }, { label: "SMG", action: "weapon", value: 12 }, - { label: "-- Rifles --", action: "none" }, { label: "Assault Rifle", action: "weapon", value: 14 }, - { label: "Carbine Rifle", action: "weapon", value: 15 }, { label: "Sniper Rifle", action: "weapon", value: 16 }, - { label: "-- Explosives --", action: "none" }, - { label: "RPG", action: "weapon", value: 18 }, - { label: "Grenades", action: "weapon", value: 19 }, - { label: "Molotov", action: "weapon", value: 20 } + { label: "RPG", action: "weapon", value: 18 } ] }, @@ -380,12 +261,8 @@ const menuData = { items: [ { label: "Launch Me Up", action: "fun_launch" }, { label: "Explode Me", action: "fun_explode" }, - { label: "Spawn Random Ped", action: "fun_ped" }, - { label: "Ragdoll", action: "fun_ragdoll" }, - { label: "Clear Area Peds", action: "fun_clearpeds" }, - { label: "Clear Area Vehicles", action: "fun_clearvehicles" }, - { label: "Chaos Mode", action: "toggle", target: "chaosMode", state: false }, - { label: "Drunk Mode", action: "toggle", target: "drunkMode", state: false } + { label: "Spawn Ped", action: "fun_ped" }, + { label: "Ragdoll", action: "fun_ragdoll" } ] } }; @@ -394,16 +271,29 @@ const menuData = { let toggleStates = { godMode: false, neverWanted: false, - infiniteAmmo: false, - superJump: false, - fastRun: false, - driftMode: false, - vehGodMode: false, - superBrakes: false, - chaosMode: false, - drunkMode: false + vehGodMode: false }; +// ============================================================================ +// FONT LOADING +// ============================================================================ + +addEventHandler("OnResourceReady", function(event, resource) { + // Use built-in default font (no external TTF file needed) + try { + menuFont = lucasFont.createDefaultFont(16.0, "Tahoma", false, false); + if (menuFont != null) { + console.log("[ModMenu] Default font created successfully"); + } + } catch(e) { + console.log("[ModMenu] Could not create default font: " + e); + } + + if (menuFont == null) { + console.log("[ModMenu] Font creation failed - text won't render"); + } +}); + // ============================================================================ // INPUT HANDLING // ============================================================================ @@ -417,13 +307,11 @@ addEventHandler("OnKeyUp", function(event, key, scanCode, mods) { selectedIndex = 0; scrollOffset = 0; menuStack = []; - if (typeof gui !== "undefined" && gui.showCursor) { - gui.showCursor(true, true); - } + // Show cursor and DISABLE controls (second param = false disables controls) + gui.showCursor(true, false); } else { - if (typeof gui !== "undefined" && gui.showCursor) { - gui.showCursor(false, false); - } + // Hide cursor and ENABLE controls (second param = true enables controls) + gui.showCursor(false, true); } return; } @@ -439,10 +327,6 @@ addEventHandler("OnKeyUp", function(event, key, scanCode, mods) { selectItem(); } else if (key === SDLK_BACKSPACE || key === SDLK_ESCAPE) { goBack(); - } else if (key === SDLK_LEFT) { - adjustValue(-1); - } else if (key === SDLK_RIGHT) { - adjustValue(1); } }); @@ -454,7 +338,6 @@ function navigateUp() { selectedIndex = items.length - 1; } } while (items[selectedIndex] && items[selectedIndex].action === "none"); - updateScroll(items); } @@ -466,7 +349,6 @@ function navigateDown() { selectedIndex = 0; } } while (items[selectedIndex] && items[selectedIndex].action === "none"); - updateScroll(items); } @@ -486,9 +368,8 @@ function goBack() { scrollOffset = prev.scroll; } else { menuOpen = false; - if (typeof gui !== "undefined" && gui.showCursor) { - gui.showCursor(false, false); - } + // IMPORTANT: Enable controls when closing menu + gui.showCursor(false, true); } } @@ -501,10 +382,9 @@ function getCurrentMenuItems() { function getNetworkMenuItems() { let items = [ - { label: ">> Refresh Player List <<", action: "refresh_players" }, - { label: "--- Players Online ---", action: "none" } + { label: "Refresh Player List", action: "refresh_players" }, + { label: "--- Players ---", action: "none" } ]; - for (let i = 0; i < playerList.length; i++) { items.push({ label: playerList[i].name, @@ -513,7 +393,6 @@ function getNetworkMenuItems() { playerData: playerList[i] }); } - return items; } @@ -555,7 +434,7 @@ function selectItem() { case "teleport": triggerNetworkEvent("ModMenu:Teleport", item.value.x, item.value.y, item.value.z); - showNotification("Teleporting to: " + item.label); + showNotification("Teleporting..."); break; case "self_health": @@ -580,7 +459,7 @@ function selectItem() { case "self_wanted": triggerNetworkEvent("ModMenu:SelfOption", "wanted"); - showNotification("Wanted level cleared!"); + showNotification("Wanted cleared!"); break; case "self_respawn": @@ -599,32 +478,22 @@ function selectItem() { case "skin_random": triggerNetworkEvent("ModMenu:ChangeSkin", "random"); - showNotification("Random skin applied!"); + showNotification("Random skin!"); break; case "veh_repair": triggerNetworkEvent("ModMenu:VehicleOption", "repair"); - showNotification("Vehicle repaired!"); + showNotification("Repaired!"); break; case "veh_flip": triggerNetworkEvent("ModMenu:VehicleOption", "flip"); - showNotification("Vehicle flipped!"); - break; - - case "veh_clean": - triggerNetworkEvent("ModMenu:VehicleOption", "clean"); - showNotification("Vehicle cleaned!"); - break; - - case "veh_upgrade": - triggerNetworkEvent("ModMenu:VehicleOption", "upgrade"); - showNotification("Vehicle upgraded!"); + showNotification("Flipped!"); break; case "veh_nitro": triggerNetworkEvent("ModMenu:VehicleOption", "nitro"); - showNotification("NITRO BOOST!"); + showNotification("NITRO!"); break; case "veh_color": @@ -636,29 +505,17 @@ function selectItem() { let c1 = Math.floor(Math.random() * 132); let c2 = Math.floor(Math.random() * 132); triggerNetworkEvent("ModMenu:VehicleColor", c1, c2); - showNotification("Random color applied!"); + showNotification("Random color!"); break; case "vehicle_delete": triggerNetworkEvent("ModMenu:DeleteVehicles"); - showNotification("Vehicles deleted!"); - break; - - case "handling": - handlingMods[item.target] = Math.max(0.1, Math.min(5.0, handlingMods[item.target] + item.delta)); - triggerNetworkEvent("ModMenu:Handling", item.target, handlingMods[item.target]); - showNotification(item.target + ": " + handlingMods[item.target].toFixed(1)); - break; - - case "handling_reset": - handlingMods = { grip: 1.0, acceleration: 1.0, topSpeed: 1.0, braking: 1.0, driftMode: false }; - triggerNetworkEvent("ModMenu:HandlingReset"); - showNotification("Handling reset!"); + showNotification("Deleted!"); break; case "world_time": triggerNetworkEvent("ModMenu:WorldTime", item.value); - showNotification("Time set to: " + item.value + ":00"); + showNotification("Time: " + item.value + ":00"); break; case "world_weather": @@ -668,23 +525,23 @@ function selectItem() { case "weapon": triggerNetworkEvent("ModMenu:GiveWeapon", item.value); - showNotification("Weapon given: " + item.label); + showNotification("Weapon given!"); break; case "weapon_all": triggerNetworkEvent("ModMenu:SelfOption", "weapons"); - showNotification("All weapons given!"); + showNotification("All weapons!"); break; case "refresh_players": triggerNetworkEvent("ModMenu:GetPlayers"); - showNotification("Refreshing player list..."); + showNotification("Refreshing..."); break; case "teleport_to_player": if (selectedPlayer) { triggerNetworkEvent("ModMenu:TeleportToPlayer", selectedPlayer.id); - showNotification("Teleporting to: " + selectedPlayer.name); + showNotification("Teleporting to " + selectedPlayer.name); } break; @@ -705,29 +562,6 @@ function selectItem() { case "fun_ragdoll": triggerNetworkEvent("ModMenu:Fun", "ragdoll"); break; - - case "fun_clearpeds": - triggerNetworkEvent("ModMenu:Fun", "clearpeds"); - showNotification("Area cleared!"); - break; - - case "fun_clearvehicles": - triggerNetworkEvent("ModMenu:Fun", "clearvehicles"); - showNotification("Vehicles cleared!"); - break; - } -} - -function adjustValue(direction) { - let items = getCurrentMenuItems(); - let item = items[selectedIndex]; - if (!item) return; - - if (item.action === "handling") { - let delta = item.delta * direction; - handlingMods[item.target] = Math.max(0.1, Math.min(5.0, handlingMods[item.target] + delta)); - triggerNetworkEvent("ModMenu:Handling", item.target, handlingMods[item.target]); - showNotification(item.target + ": " + handlingMods[item.target].toFixed(1)); } } @@ -735,12 +569,9 @@ function openPlayerMenu(playerData) { menuData.player_options = { title: playerData.name, items: [ - { label: "Teleport to Player", action: "teleport_to_player" }, - { label: "Spectate Player", action: "spectate_player" }, - { label: "Copy Position", action: "copy_pos" } + { label: "Teleport to Player", action: "teleport_to_player" } ] }; - menuStack.push({ menu: currentMenu, index: selectedIndex, scroll: scrollOffset }); currentMenu = "player_options"; selectedIndex = 0; @@ -748,7 +579,7 @@ function openPlayerMenu(playerData) { } // ============================================================================ -// NETWORK EVENT HANDLERS +// NETWORK HANDLERS // ============================================================================ addNetworkHandler("ModMenu:PlayerList", function(players) { @@ -756,12 +587,12 @@ addNetworkHandler("ModMenu:PlayerList", function(players) { showNotification("Found " + players.length + " players"); }); -addNetworkHandler("ModMenu:Notification", function(message) { - showNotification(message); +addNetworkHandler("ModMenu:Notification", function(msg) { + showNotification(msg); }); // ============================================================================ -// RENDERING - Using GTA IV Native Drawing Functions with Vec2 +// RENDERING // ============================================================================ addEventHandler("OnDrawnHUD", function(event) { @@ -771,99 +602,62 @@ addEventHandler("OnDrawnHUD", function(event) { let items = getCurrentMenuItems(); let title = currentData ? currentData.title : currentMenu.toUpperCase(); - // Calculate visible items let visibleCount = Math.min(items.length, menu.maxVisibleItems); let totalHeight = menu.headerHeight + (visibleCount * menu.itemHeight) + menu.footerHeight; // Draw background - drawMenuRect(menu.x - 0.005, menu.y - 0.005, menu.width + 0.01, totalHeight + 0.01, - colors.bgR, colors.bgG, colors.bgB, colors.bgA); + drawRect(menu.x - 5, menu.y - 5, menu.width + 10, totalHeight + 10, colors.background); // Draw header - drawMenuRect(menu.x, menu.y, menu.width, menu.headerHeight, - colors.headerR, colors.headerG, colors.headerB, colors.headerA); - drawMenuText(title, menu.x + menu.width / 2, menu.y + 0.012, 0.45, true); + drawRect(menu.x, menu.y, menu.width, menu.headerHeight, colors.header); + drawText(title, menu.x + 10, menu.y + 12, colors.text, 18); // Draw items let yPos = menu.y + menu.headerHeight; for (let i = scrollOffset; i < scrollOffset + visibleCount && i < items.length; i++) { let item = items[i]; let isSelected = (i === selectedIndex); + let bgColor = isSelected ? colors.selected : colors.item; - // Draw item background - if (isSelected) { - drawMenuRect(menu.x, yPos, menu.width, menu.itemHeight, - colors.selectedR, colors.selectedG, colors.selectedB, colors.selectedA); - } else { - drawMenuRect(menu.x, yPos, menu.width, menu.itemHeight, - colors.itemR, colors.itemG, colors.itemB, colors.itemA); - } + drawRect(menu.x, yPos, menu.width, menu.itemHeight, bgColor); - // Build label with state indicators let label = item.label; - if (item.action === "toggle") { - let state = toggleStates[item.target]; - label += state ? " [ON]" : " [OFF]"; + label += toggleStates[item.target] ? " [ON]" : " [OFF]"; } - - if (item.action === "handling" && handlingMods[item.target] !== undefined) { - label += " [" + handlingMods[item.target].toFixed(1) + "]"; - } - if (item.action === "submenu") { label += " >>"; } - drawMenuText(label, menu.x + 0.01, yPos + 0.008, 0.35, false); - + drawText(label, menu.x + 15, yPos + 10, colors.text, 14); yPos += menu.itemHeight; } // Draw footer - drawMenuRect(menu.x, yPos, menu.width, menu.footerHeight, - colors.footerR, colors.footerG, colors.footerB, colors.footerA); - drawMenuText("UP/DOWN: Nav | ENTER: Select | BACKSPACE: Back", - menu.x + menu.width / 2, yPos + 0.008, 0.25, true); + drawRect(menu.x, yPos, menu.width, menu.footerHeight, colors.footer); + drawText("UP/DOWN | ENTER | BACKSPACE", menu.x + 10, yPos + 8, colors.subText, 12); - // Draw scroll indicator + // Scroll indicator if (items.length > menu.maxVisibleItems) { let scrollText = (scrollOffset + 1) + "-" + Math.min(scrollOffset + visibleCount, items.length) + "/" + items.length; - drawMenuText(scrollText, menu.x + menu.width - 0.03, menu.y + 0.012, 0.3, false); + drawText(scrollText, menu.x + menu.width - 60, menu.y + 12, colors.subText, 12); } }); -// Drawing functions using GTA IV natives with Vec2 -function drawMenuRect(x, y, w, h, r, g, b, a) { - // natives.drawRect expects: Vec2 position (center), Vec2 size, r, g, b, a - // Position is center-based, so adjust x and y - let centerX = x + w / 2; - let centerY = y + h / 2; - - let pos = new Vec2(centerX, centerY); +// Draw rectangle using graphics API +function drawRect(x, y, w, h, colour) { + let pos = new Vec2(x, y); let size = new Vec2(w, h); - - natives.drawRect(pos, size, r, g, b, a); + graphics.drawRectangle(null, pos, size, colour, colour, colour, colour); } -function drawMenuText(text, x, y, scale, centered) { - // Set up text properties - natives.setTextFont(0); - natives.setTextScale(scale, scale); - natives.setTextColour(255, 255, 255, 255); - natives.setTextDropshadow(2, 0, 0, 0, 255); - - if (centered) { - natives.setTextCentre(true); - } else { - natives.setTextCentre(false); +// Draw text using loaded font or fallback +function drawText(text, x, y, colour, size) { + if (menuFont != null) { + let pos = new Vec2(x, y); + menuFont.render(text, pos, menu.width, 0.0, 0.0, size, colour, false, false, false, true); } - - // displayText expects Vec2 position - let pos = new Vec2(x, y); - natives.displayText(pos, "STRING"); - natives.addTextComponentString(text); - natives.drawText(); + // If no font, text won't render but menu boxes will still show } // ============================================================================ @@ -880,10 +674,9 @@ function showNotification(text) { }); } -// Draw notifications addEventHandler("OnDrawnHUD", function(event) { let now = Date.now(); - let yPos = 0.25; + let yPos = 200; for (let i = 0; i < notifications.length; i++) { let notif = notifications[i]; @@ -891,51 +684,36 @@ addEventHandler("OnDrawnHUD", function(event) { if (elapsed < notif.duration) { let alpha = elapsed < notif.duration - 500 ? 200 : Math.floor(200 * (notif.duration - elapsed) / 500); + let bgColor = toColour(20, 20, 20, alpha); + let textColor = toColour(255, 255, 100, Math.min(255, alpha + 55)); - // Draw notification background - drawMenuRect(0.01, yPos, 0.25, 0.035, 20, 20, 20, alpha); - - // Draw notification text - natives.setTextFont(0); - natives.setTextScale(0.3, 0.3); - natives.setTextColour(255, 255, 100, Math.min(255, alpha + 55)); - natives.setTextDropshadow(2, 0, 0, 0, 255); - natives.setTextCentre(false); - - let pos = new Vec2(0.015, yPos + 0.008); - natives.displayText(pos, "STRING"); - natives.addTextComponentString(notif.text); - natives.drawText(); - - yPos += 0.04; + drawRect(10, yPos, 280, 30, bgColor); + drawText(notif.text, 20, yPos + 8, textColor, 14); + yPos += 35; } } - // Clean expired notifications notifications = notifications.filter(function(n) { return now - n.time < n.duration; }); }); // ============================================================================ -// TOGGLE EFFECTS (Client-side processing) +// TOGGLE EFFECTS // ============================================================================ addEventHandler("OnProcess", function(event) { if (!localPlayer) return; - // God Mode if (toggleStates.godMode) { localPlayer.health = 200; localPlayer.armour = 100; } - // Never Wanted if (toggleStates.neverWanted) { localPlayer.wantedLevel = 0; } - // Vehicle God Mode if (localPlayer.vehicle && toggleStates.vehGodMode) { localPlayer.vehicle.health = 1000; } @@ -946,8 +724,7 @@ addEventHandler("OnProcess", function(event) { // ============================================================================ addEventHandler("OnResourceStart", function(event, resource) { - console.log("[ModMenu] Client script loaded!"); - console.log("[ModMenu] Press F5 to open the menu"); + console.log("[ModMenu] Client loaded - Press F5 to open menu"); }); -console.log("[ModMenu] Client loaded - Press F5 to open menu!"); +console.log("[ModMenu] Script initialized");