Merge pull request #6 from iDisaster/claude/enhance-gta-server-9Dezg

Claude/enhance gta server 9 dezg
This commit is contained in:
iDisaster
2026-01-13 19:13:38 +04:00
committed by GitHub
2 changed files with 352 additions and 74 deletions

View File

@@ -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) {}
}
});

View File

@@ -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);
});
// ============================================================================