diff --git a/resources/modmenu/client.js b/resources/modmenu/client.js index f37163f..9d6af9a 100644 --- a/resources/modmenu/client.js +++ b/resources/modmenu/client.js @@ -1,5 +1,5 @@ // ============================================================================ -// MOD MENU - Client Side +// REVOLUTION MOD MENU - Client Side // Interactive GUI menu for all players // Press F5 to open/close menu, Arrow keys to navigate, Enter to select // ============================================================================ @@ -11,29 +11,27 @@ let selectedIndex = 0; let menuStack = []; let scrollOffset = 0; +// Animation state +let animTime = 0; +let smoothScrollY = 0; +let targetScrollY = 0; +let titlePulse = 0; +let menuOpenAnim = 0; +let selectedPulse = 0; + // Font for text rendering (will be loaded on resource start) let menuFont = null; - -// Menu colors using toColour for integer format -const colors = { - 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) -}; +let titleFont = null; // Menu dimensions in pixels - positioned on right side const menu = { x: 1050, - y: 120, - width: 320, - headerHeight: 45, - itemHeight: 38, - footerHeight: 32, - maxVisibleItems: 12 + y: 100, + width: 340, + headerHeight: 60, + itemHeight: 42, + footerHeight: 35, + maxVisibleItems: 11 }; // Player list cache @@ -54,7 +52,7 @@ let handlingMods = { const menuData = { main: { - title: "MOD MENU", + title: "REVOLUTION", items: [ { label: "Self Options", action: "submenu", target: "self" }, { label: "Vehicle Spawner", action: "submenu", target: "vehicles" }, @@ -399,13 +397,29 @@ addEventHandler("OnKeyUp", function(event, key, scanCode, mods) { menuStack = []; // Show cursor and DISABLE controls (second param = false disables controls) gui.showCursor(true, false); + // Kill phone immediately + try { + natives.destroyMobilePhone(); + natives.scriptIsUsingMobilePhone(true); + } catch(e) {} } else { // Hide cursor and ENABLE controls (second param = true enables controls) gui.showCursor(false, true); + // Allow phone again + try { + natives.scriptIsUsingMobilePhone(false); + } catch(e) {} } return; } + // Kill phone on ANY key press while menu is open + if (menuOpen) { + try { + natives.destroyMobilePhone(); + } catch(e) {} + } + if (!menuOpen) return; // Navigation - simple key handling @@ -711,9 +725,24 @@ function openPlayerMenu(playerData) { // NETWORK HANDLERS // ============================================================================ -addNetworkHandler("ModMenu:PlayerList", function(players) { - playerList = players; - showNotification("Found " + players.length + " players"); +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) { @@ -1042,11 +1071,32 @@ addNetworkHandler("ModMenu:ExecuteSkinChange", function(skinId) { }); // ============================================================================ -// RENDERING +// RENDERING - REVOLUTION MOD MENU (Red & Black Eye-Melting Theme) // ============================================================================ +// Animation update +addEventHandler("OnProcess", function(event) { + // Update animation time + animTime += 0.05; + titlePulse += 0.1; + selectedPulse += 0.15; + + // Smooth scroll interpolation + smoothScrollY += (targetScrollY - smoothScrollY) * 0.25; + + // Menu open animation + if (menuOpen && menuOpenAnim < 1) { + menuOpenAnim += 0.12; + if (menuOpenAnim > 1) menuOpenAnim = 1; + } else if (!menuOpen && menuOpenAnim > 0) { + menuOpenAnim -= 0.18; + if (menuOpenAnim < 0) menuOpenAnim = 0; + } +}); + +// Main menu rendering addEventHandler("OnDrawnHUD", function(event) { - if (!menuOpen) return; + if (menuOpenAnim <= 0) return; let currentData = menuData[currentMenu]; let items = getCurrentMenuItems(); @@ -1055,63 +1105,262 @@ addEventHandler("OnDrawnHUD", function(event) { let visibleCount = Math.min(items.length, menu.maxVisibleItems); 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); + let animAlpha = Math.floor(255 * menuOpenAnim); - // Draw header - drawRect(menu.x, menu.y, menu.width, menu.headerHeight, colors.header); - drawText(title, menu.x + 10, menu.y + 12, colors.text, 18); + // Calculate animated position (slide in from right) + let slideOffset = (1 - menuOpenAnim) * 150; + let baseX = menu.x + slideOffset; + let baseY = menu.y; + + // ===== PULSING RED OUTER GLOW ===== + let glowPulse = Math.sin(animTime * 3) * 0.4 + 0.6; + let glowSize = 12 + Math.sin(animTime * 2) * 5; + + // Red glow with pulse + let redIntensity = Math.floor(200 + Math.sin(animTime * 4) * 55); + for (let g = 4; g >= 1; g--) { + let gAlpha = Math.floor((40 / g) * glowPulse * menuOpenAnim); + let gCol = toColour(redIntensity, 0, 0, gAlpha); + drawRect(baseX - glowSize * g, baseY - glowSize * g, + menu.width + glowSize * g * 2, totalHeight + glowSize * g * 2 + 10, gCol); + } + + // ===== MAIN BACKGROUND - Deep Black ===== + let bgColor = toColour(8, 8, 12, Math.floor(245 * menuOpenAnim)); + drawRect(baseX, baseY, menu.width, totalHeight + 10, bgColor); + + // ===== ANIMATED RED BORDER ===== + let borderPulse = Math.sin(animTime * 5) * 50 + 200; + let borderColor = toColour(Math.floor(borderPulse), 20, 30, Math.floor(220 * menuOpenAnim)); + + // Animated border thickness + let borderW = 3 + Math.sin(animTime * 6) * 1; + drawRect(baseX, baseY, menu.width, borderW, borderColor); // Top + drawRect(baseX, baseY + totalHeight + 10 - borderW, menu.width, borderW, borderColor); // Bottom + drawRect(baseX, baseY, borderW, totalHeight + 10, borderColor); // Left + drawRect(baseX + menu.width - borderW, baseY, borderW, totalHeight + 10, borderColor); // Right + + // Corner accents - brighter red + let cornerSize = 15 + Math.sin(animTime * 4) * 5; + let cornerColor = toColour(255, 50, 50, Math.floor(200 * menuOpenAnim)); + drawRect(baseX, baseY, cornerSize, 3, cornerColor); + drawRect(baseX, baseY, 3, cornerSize, cornerColor); + drawRect(baseX + menu.width - cornerSize, baseY, cornerSize, 3, cornerColor); + drawRect(baseX + menu.width - 3, baseY, 3, cornerSize, cornerColor); + drawRect(baseX, baseY + totalHeight + 7, cornerSize, 3, cornerColor); + drawRect(baseX, baseY + totalHeight + 10 - cornerSize, 3, cornerSize, cornerColor); + drawRect(baseX + menu.width - cornerSize, baseY + totalHeight + 7, cornerSize, 3, cornerColor); + drawRect(baseX + menu.width - 3, baseY + totalHeight + 10 - cornerSize, 3, cornerSize, cornerColor); + + // ===== HEADER - Black to Red Gradient ===== + let headerRed = Math.floor(180 + Math.sin(animTime * 3) * 40); + let headerLeft = toColour(headerRed, 15, 25, animAlpha); + let headerRight = toColour(60, 5, 10, animAlpha); + drawGradientRect(baseX + 4, baseY + 4, menu.width - 8, menu.headerHeight - 4, headerLeft, headerRight); + + // Header inner glow line + let lineGlow = Math.sin(animTime * 6) * 0.3 + 0.7; + let headerLineColor = toColour(255, 80, 80, Math.floor(150 * lineGlow * menuOpenAnim)); + drawRect(baseX + 4, baseY + menu.headerHeight - 2, menu.width - 8, 2, headerLineColor); + + // ===== ANIMATED TITLE ===== + let titleY = baseY + 10; + + // Title glow effect + let titleGlowPulse = Math.sin(titlePulse) * 0.5 + 0.5; + let titleGlowColor = toColour(255, 50, 50, Math.floor(100 * titleGlowPulse * menuOpenAnim)); + drawText("REVOLUTION", baseX + 14, titleY + 2, titleGlowColor, 24); + drawText("REVOLUTION", baseX + 8, titleY + 2, titleGlowColor, 24); + + // Title shadow + let shadowColor = toColour(0, 0, 0, Math.floor(200 * menuOpenAnim)); + drawText("REVOLUTION", baseX + 12, titleY + 3, shadowColor, 24); + + // Main title - pulsing red to white + let titleRed = Math.floor(255); + let titleOther = Math.floor(180 + Math.sin(titlePulse * 2) * 75); + let titleColor = toColour(titleRed, titleOther, titleOther, animAlpha); + drawText("REVOLUTION", baseX + 10, titleY, titleColor, 24); + + // Subtitle with flicker + let betaFlicker = Math.sin(animTime * 8) > 0.3 ? 1 : 0.7; + let betaColor = toColour(180, 180, 180, Math.floor(180 * betaFlicker * menuOpenAnim)); + drawText("ModMenu (Beta)", baseX + 12, titleY + 30, betaColor, 11); + + // Animated line under title + let lineWidth = 100 + Math.sin(animTime * 5) * 40; + let lineX = baseX + (menu.width - lineWidth) / 2; + let linePulse = Math.sin(animTime * 8) * 55 + 200; + let underlineColor = toColour(Math.floor(linePulse), 30, 40, Math.floor(220 * menuOpenAnim)); + drawRect(lineX, baseY + menu.headerHeight - 6, lineWidth, 2, underlineColor); + + // ===== MENU ITEMS ===== + let yPos = baseY + menu.headerHeight; + targetScrollY = scrollOffset * menu.itemHeight; - // 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; + let itemY = yPos + (i - scrollOffset) * menu.itemHeight; - drawRect(menu.x, yPos, menu.width, menu.itemHeight, bgColor); + // Selection animation + let selectOffset = 0; + let selectGlow = 0; + if (isSelected) { + selectOffset = Math.sin(selectedPulse) * 4; + selectGlow = Math.sin(selectedPulse * 1.5) * 0.4 + 0.6; + } - let label = item.label; + if (isSelected) { + // ===== SELECTED ITEM - Pulsing Red ===== + let selRed = Math.floor(150 + selectGlow * 80); + let selColor = toColour(selRed, 20, 30, Math.floor(230 * menuOpenAnim)); + + // Outer glow + let selGlowColor = toColour(255, 40, 50, Math.floor(50 * menuOpenAnim)); + drawRect(baseX + 2, itemY - 3, menu.width - 4, menu.itemHeight + 6, selGlowColor); + + // Main selection background + drawRect(baseX + 6 + selectOffset, itemY, menu.width - 12, menu.itemHeight - 2, selColor); + + // Left indicator bar - bright red pulsing + let barPulse = Math.sin(selectedPulse * 2) * 55 + 200; + let barColor = toColour(255, Math.floor(barPulse - 150), Math.floor(barPulse - 150), animAlpha); + drawRect(baseX + 6, itemY, 5, menu.itemHeight - 2, barColor); + + // Right edge highlight + let rightColor = toColour(255, 80, 80, Math.floor(100 * menuOpenAnim)); + drawRect(baseX + menu.width - 8, itemY, 2, menu.itemHeight - 2, rightColor); + + } else if (item.action === "none") { + // Separator + let sepColor = toColour(40, 15, 20, Math.floor(180 * menuOpenAnim)); + drawRect(baseX + 6, itemY, menu.width - 12, menu.itemHeight - 2, sepColor); + // Separator line + let sepLineColor = toColour(100, 30, 40, Math.floor(150 * menuOpenAnim)); + drawRect(baseX + 20, itemY + menu.itemHeight/2 - 1, menu.width - 40, 1, sepLineColor); + } else { + // Normal item - dark with subtle red tint + let normRed = 25 + (i % 2) * 5; + let normColor = toColour(normRed, 12, 15, Math.floor(200 * menuOpenAnim)); + drawRect(baseX + 6, itemY, menu.width - 12, menu.itemHeight - 2, normColor); + + // Subtle left border on hover area + let leftBorderColor = toColour(80, 20, 25, Math.floor(100 * menuOpenAnim)); + drawRect(baseX + 6, itemY, 2, menu.itemHeight - 2, leftBorderColor); + } + + // Item text + let textX = baseX + 22 + (isSelected ? selectOffset + 6 : 0); + let textBright = isSelected ? 255 : 200; + let textColor = toColour(textBright, textBright, textBright, animAlpha); + + if (item.action === "none") { + let sepTextColor = toColour(150, 100, 110, Math.floor(200 * menuOpenAnim)); + drawText(item.label, baseX + 20, itemY + 12, sepTextColor, 11); + } else { + drawText(item.label, textX, itemY + 12, textColor, 14); + } + + // ===== TOGGLE INDICATORS ===== if (item.action === "toggle") { - label += toggleStates[item.target] ? " [ON]" : " [OFF]"; - } - if (item.action === "submenu") { - label += " >>"; + let isOn = toggleStates[item.target]; + let stateText = isOn ? "ON" : "OFF"; + let stateX = baseX + menu.width - 60; + + if (isOn) { + // GREEN ON - Pulsing + let greenPulse = Math.sin(animTime * 6) * 40 + 215; + let onBgColor = toColour(30, Math.floor(greenPulse * 0.4), 30, animAlpha); + let onTextColor = toColour(80, Math.floor(greenPulse), 80, animAlpha); + drawRect(stateX - 8, itemY + 8, 52, 24, onBgColor); + // Green border + let onBorderColor = toColour(50, Math.floor(greenPulse), 50, animAlpha); + drawRect(stateX - 8, itemY + 8, 52, 2, onBorderColor); + drawRect(stateX - 8, itemY + 30, 52, 2, onBorderColor); + drawText(stateText, stateX + 5, itemY + 12, onTextColor, 13); + } else { + // RED OFF + let offBgColor = toColour(80, 25, 30, animAlpha); + let offTextColor = toColour(255, 90, 90, animAlpha); + drawRect(stateX - 8, itemY + 8, 52, 24, offBgColor); + // Red border + let offBorderColor = toColour(150, 40, 50, animAlpha); + drawRect(stateX - 8, itemY + 8, 52, 2, offBorderColor); + drawRect(stateX - 8, itemY + 30, 52, 2, offBorderColor); + drawText(stateText, stateX + 2, itemY + 12, offTextColor, 13); + } } - drawText(label, menu.x + 15, yPos + 10, colors.text, 14); - yPos += menu.itemHeight; + // Submenu arrow + if (item.action === "submenu") { + let arrowX = baseX + menu.width - 32 + (isSelected ? Math.sin(animTime * 10) * 5 : 0); + let arrowBright = isSelected ? 255 : 150; + let arrowColor = toColour(arrowBright, arrowBright * 0.6, arrowBright * 0.6, animAlpha); + drawText(">>", arrowX, itemY + 12, arrowColor, 14); + } } - // Draw footer - drawRect(menu.x, yPos, menu.width, menu.footerHeight, colors.footer); - drawText("UP/DOWN | ENTER | BACKSPACE", menu.x + 10, yPos + 8, colors.subText, 12); + // ===== FOOTER ===== + let footerY = yPos + visibleCount * menu.itemHeight; + let footerColor = toColour(20, 8, 12, Math.floor(230 * menuOpenAnim)); + drawRect(baseX + 4, footerY, menu.width - 8, menu.footerHeight, footerColor); - // Scroll indicator + // Footer top line + let footerLineColor = toColour(120, 40, 50, Math.floor(180 * menuOpenAnim)); + drawRect(baseX + 4, footerY, menu.width - 8, 2, footerLineColor); + + // Footer text + let footerTextColor = toColour(180, 150, 150, Math.floor(200 * menuOpenAnim)); + drawText("UP/DOWN | ENTER | BACK", baseX + 25, footerY + 10, footerTextColor, 11); + + // ===== SCROLL BAR ===== if (items.length > menu.maxVisibleItems) { - let scrollText = (scrollOffset + 1) + "-" + Math.min(scrollOffset + visibleCount, items.length) + "/" + items.length; - drawText(scrollText, menu.x + menu.width - 60, menu.y + 12, colors.subText, 12); + let scrollTrackColor = toColour(40, 15, 20, Math.floor(150 * menuOpenAnim)); + let scrollTrackY = baseY + menu.headerHeight + 5; + let scrollTrackH = visibleCount * menu.itemHeight - 10; + drawRect(baseX + menu.width - 12, scrollTrackY, 6, scrollTrackH, scrollTrackColor); + + let scrollPct = scrollOffset / Math.max(1, items.length - visibleCount); + let scrollBarH = Math.max(30, scrollTrackH * (visibleCount / items.length)); + let scrollBarY = scrollTrackY + scrollPct * (scrollTrackH - scrollBarH); + + let scrollPulse = Math.sin(animTime * 4) * 30 + 180; + let scrollBarColor = toColour(Math.floor(scrollPulse), 50, 60, Math.floor(220 * menuOpenAnim)); + drawRect(baseX + menu.width - 12, scrollBarY, 6, scrollBarH, scrollBarColor); } }); // Draw rectangle using graphics API function drawRect(x, y, w, h, colour) { - let pos = new Vec2(x, y); - let size = new Vec2(w, h); - graphics.drawRectangle(null, pos, size, colour, colour, colour, 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) { - let pos = new Vec2(x, y); - menuFont.render(text, pos, menu.width, 0.0, 0.0, size, colour, false, false, false, true); + 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) {} } - // If no font, text won't render but menu boxes will still show } // ============================================================================ -// NOTIFICATIONS +// NOTIFICATIONS - Animated // ============================================================================ let notifications = []; @@ -1120,27 +1369,46 @@ function showNotification(text) { notifications.push({ text: text, time: Date.now(), - duration: 1000 + duration: 1500 }); } addEventHandler("OnDrawnHUD", function(event) { let now = Date.now(); - let yPos = 200; + let yPos = 180; for (let i = 0; i < notifications.length; i++) { let notif = notifications[i]; let elapsed = now - notif.time; if (elapsed < notif.duration) { - // Quick fade: start fading at 700ms, fully gone by 1000ms - let alpha = elapsed < 700 ? 200 : Math.floor(200 * (notif.duration - elapsed) / 300); - let bgColor = toColour(20, 20, 20, alpha); - let textColor = toColour(255, 255, 100, Math.min(255, alpha + 55)); + // Animated notification + let progress = elapsed / notif.duration; + let slideIn = Math.min(1, elapsed / 200) * 300; + let fadeOut = elapsed > notif.duration - 300 ? (notif.duration - elapsed) / 300 : 1; + let alpha = Math.floor(220 * fadeOut); - drawRect(10, yPos, 280, 30, bgColor); - drawText(notif.text, 20, yPos + 8, textColor, 14); - yPos += 35; + // Rainbow border + let notifHue = (animTime * 80 + i * 60) % 360; + let notifRGB = hsvToRgb(notifHue, 0.8, 0.8); + + // Glow + let glowCol = toColour(notifRGB.r, notifRGB.g, notifRGB.b, Math.floor(40 * fadeOut)); + drawRect(10 - slideIn + 300, yPos - 3, 290, 36, glowCol); + + // Background + let bgColor = toColour(20, 20, 30, alpha); + drawRect(15 - slideIn + 300, yPos, 280, 30, bgColor); + + // Border + let borderCol = toColour(notifRGB.r, notifRGB.g, notifRGB.b, alpha); + drawRect(15 - slideIn + 300, yPos, 3, 30, borderCol); + + // Text + let textColor = toColour(255, 255, 255, alpha); + drawText(notif.text, 25 - slideIn + 300, yPos + 8, textColor, 13); + + yPos += 40; } } @@ -1362,10 +1630,20 @@ addEventHandler("OnProcess", function(event) { } catch(e) {} } - // Block phone input when menu is open + // 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) {} } }); diff --git a/resources/modmenu/server.js b/resources/modmenu/server.js index 8de2c14..5a53c99 100644 --- a/resources/modmenu/server.js +++ b/resources/modmenu/server.js @@ -160,27 +160,27 @@ addNetworkHandler("ModMenu:TeleportToPlayer", function(client, targetId) { addNetworkHandler("ModMenu:GetPlayers", function(client) { let clients = getClients(); - let playerList = []; + let playerNames = ""; + let playerIds = ""; console.log("[ModMenu] Getting players, found " + clients.length + " clients"); for (let i = 0; i < clients.length; i++) { let c = clients[i]; - // Skip the requesting player (optional - include self for testing) - // if (c.index === client.index) continue; - - // Only add players with valid data if (c && c.name) { - playerList.push({ - id: c.index, - name: c.name || ("Player " + c.index) - }); + if (playerNames.length > 0) { + playerNames += "|"; + playerIds += "|"; + } + playerNames += c.name; + playerIds += c.index; console.log("[ModMenu] Added player: " + c.name + " (ID: " + c.index + ")"); } } - console.log("[ModMenu] Sending " + playerList.length + " players to " + client.name); - triggerNetworkEvent("ModMenu:PlayerList", client, playerList); + console.log("[ModMenu] Sending players to " + client.name + ": " + playerNames); + // Send as separate strings instead of array + triggerNetworkEvent("ModMenu:PlayerList", client, playerNames, playerIds); }); // ============================================================================