diff --git a/resources/chat/client.js b/resources/chat/client.js new file mode 100644 index 0000000..321db02 --- /dev/null +++ b/resources/chat/client.js @@ -0,0 +1,677 @@ +// ============================================================================ +// CUSTOM CHAT UI - Client Side +// Modern glossy UI matching MD Revolution menu style +// ============================================================================ + +// ============================================================================ +// UI THEME - Dark Glassmorphism (matching mod menu) +// ============================================================================ +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 + bgMessage: { r: 18, g: 22, b: 28 }, // Message background + + // 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 joins + error: { r: 248, g: 81, b: 73 }, // Red for leaves + warning: { r: 210, g: 153, b: 34 }, // Yellow/gold for warnings + info: { r: 88, g: 166, b: 255 }, // Blue for info + + // Chat specific colors + playerName: { r: 0, g: 212, b: 255 }, // Player name color (cyan) + chatWhite: { r: 255, g: 255, b: 255 }, // Normal chat + chatAction: { r: 200, g: 100, b: 255 }, // /me actions (purple) + chatWhisper: { r: 255, g: 255, b: 100 }, // Private messages (yellow) + chatShout: { r: 255, g: 100, b: 100 }, // Shout (red) + chatOOC: { r: 150, g: 150, b: 150 }, // OOC (gray) + chatSystem: { r: 100, g: 200, b: 255 }, // System messages (light blue) + chatLocal: { r: 200, g: 255, b: 200 }, // Local chat (light green) + + // Border colors + border: { r: 48, g: 54, b: 61 }, // Subtle border + borderFocus: { r: 0, g: 212, b: 255 } // Focused border (accent) +}; + +// ============================================================================ +// CHAT STATE +// ============================================================================ +let chatMessages = []; +const maxMessages = 50; +let chatVisible = true; +let chatInputActive = false; +let chatInputText = ""; +let chatScrollOffset = 0; +let chatFont = null; + +// Animation state +let animTime = 0; +let chatFadeAlpha = 1.0; +let lastMessageTime = 0; +let chatFadeDelay = 10000; // 10 seconds before fade +let chatFadeSpeed = 0.02; + +// Track if player is in game (spawned) +let isPlayerInGame = false; + +// Welcome/Leave notification queue +let notifications = []; +let notificationDuration = 5000; // 5 seconds + +// Chat dimensions +const chat = { + x: 25, + y: 50, + width: 550, + messageHeight: 22, + maxVisibleMessages: 12, + padding: 12, + inputHeight: 32, + borderRadius: 4, + // Font sizes for consistent text alignment + fontSize: 10, + timestampSize: 10, + inputFontSize: 10 +}; + +// ============================================================================ +// RESOURCE START - Load font +// ============================================================================ +addEventHandler("OnResourceStart", function(event, resource) { + if (resource == thisResource) { + try { + // Create font using lucasFont.createDefaultFont() like the mod menu does + chatFont = lucasFont.createDefaultFont(16.0, "Arial", "Regular"); + console.log("[Chat] Custom chat UI initialized with font"); + } catch(e) { + console.log("[Chat] Font load error: " + e); + try { + // Fallback to Tahoma + chatFont = lucasFont.createDefaultFont(16.0, "Tahoma"); + console.log("[Chat] Using fallback font Tahoma"); + } catch(e2) { + console.log("[Chat] Fallback font also failed: " + e2); + } + } + } +}); + +// ============================================================================ +// NETWORK EVENTS - Receive messages from server +// ============================================================================ +addNetworkHandler("chatMessage", function(text, type, playerName) { + // Receives individual parameters instead of object for better compatibility + console.log("[Chat] Received message: " + text + " type: " + type + " from: " + playerName); + addChatMessage(text, type || "normal", playerName || null); +}); + +addNetworkHandler("playerJoined", function(playerName) { + // Receives player name directly + console.log("[Chat] Player joined: " + playerName); + addNotification(playerName + " joined the server", "join"); + addChatMessage(playerName + " has joined the server", "system"); +}); + +addNetworkHandler("playerLeft", function(playerName, reason) { + // Receives individual parameters + console.log("[Chat] Player left: " + playerName + " reason: " + reason); + let reasonText = reason || "Disconnected"; + addNotification(playerName + " left the server", "leave"); + addChatMessage(playerName + " has left the server (" + reasonText + ")", "system"); +}); + +// ============================================================================ +// CHAT FUNCTIONS +// ============================================================================ + +function addChatMessage(text, type, playerName) { + let message = { + text: text, + type: type || "normal", + playerName: playerName, + timestamp: Date.now(), + alpha: 1.0 + }; + + chatMessages.push(message); + + // Limit messages + if (chatMessages.length > maxMessages) { + chatMessages.shift(); + } + + // Reset fade timer + lastMessageTime = Date.now(); + chatFadeAlpha = 1.0; + + // Auto-scroll to bottom + let totalMessages = chatMessages.length; + if (totalMessages > chat.maxVisibleMessages) { + chatScrollOffset = totalMessages - chat.maxVisibleMessages; + } +} + +function addNotification(text, type) { + notifications.push({ + text: text, + type: type, // "join" or "leave" + timestamp: Date.now(), + alpha: 1.0, + y: 0 // Will be animated + }); +} + +function getMessageColor(type) { + switch(type) { + case "action": return UI.chatAction; + case "whisper": return UI.chatWhisper; + case "shout": return UI.chatShout; + case "ooc": return UI.chatOOC; + case "system": return UI.chatSystem; + case "local": return UI.chatLocal; + case "join": return UI.success; + case "leave": return UI.error; + default: return UI.chatWhite; + } +} + +// ============================================================================ +// DRAWING FUNCTIONS +// ============================================================================ + +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) {} +} + +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) {} +} + +function drawGradientRectV(x, y, w, h, colourTop, colourBottom) { + try { + let pos = new Vec2(x, y); + let size = new Vec2(w, h); + graphics.drawRectangle(null, pos, size, colourTop, colourTop, colourBottom, colourBottom); + } catch(e) {} +} + +function drawText(text, x, y, colour, size) { + if (chatFont != null) { + try { + let pos = new Vec2(x, y); + chatFont.render(text, pos, chat.width - 20, 0.0, 0.0, size || 11, colour, false, false, false, true); + } catch(e) {} + } +} + +// ============================================================================ +// RENDER CHAT UI +// ============================================================================ +addEventHandler("OnDrawnHUD", function(event) { + animTime += 0.016; // ~60fps + + // Update player in-game state + isPlayerInGame = checkIfPlayerInGame(); + + let screenWidth = 1920; + let screenHeight = 1080; + try { + screenWidth = game.width || 1920; + screenHeight = game.height || 1080; + } catch(e) {} + + // Don't render anything if player is in lobby (not spawned) + if (!isPlayerInGame) { + // Close chat input if it was open when entering lobby + if (chatInputActive) { + closeChatInput(); + } + return; + } + + // Update fade + if (Date.now() - lastMessageTime > chatFadeDelay && !chatInputActive) { + chatFadeAlpha = Math.max(0.3, chatFadeAlpha - chatFadeSpeed); + } + + // Draw notifications first (top right) + drawNotifications(screenWidth, screenHeight); + + // Skip chat drawing if hidden + if (!chatVisible && !chatInputActive) return; + + let alpha = Math.floor(255 * chatFadeAlpha); + if (chatInputActive) alpha = 255; + + // Calculate chat area + let chatAreaHeight = chat.maxVisibleMessages * chat.messageHeight + chat.padding * 2; + let totalHeight = chatAreaHeight + (chatInputActive ? chat.inputHeight + 8 : 0); + + // ======================================== + // MAIN CHAT PANEL - Glossy background + // ======================================== + + // Outer glow effect (subtle) + let glowAlpha = Math.floor(30 * chatFadeAlpha); + let glowCol = toColour(UI.accent.r, UI.accent.g, UI.accent.b, glowAlpha); + drawRect(chat.x - 2, chat.y - 2, chat.width + 4, chatAreaHeight + 4, glowCol); + + // Main panel background with transparency + let bgAlpha = Math.floor(200 * chatFadeAlpha); + let panelBg = toColour(UI.bgDark.r, UI.bgDark.g, UI.bgDark.b, bgAlpha); + drawRect(chat.x, chat.y, chat.width, chatAreaHeight, panelBg); + + // Glassmorphism overlay (subtle gradient) + let glossTop = toColour(255, 255, 255, Math.floor(8 * chatFadeAlpha)); + let glossBottom = toColour(255, 255, 255, 0); + drawGradientRectV(chat.x, chat.y, chat.width, 30, glossTop, glossBottom); + + // Top accent line + let accentAlpha = Math.floor(255 * chatFadeAlpha); + let accentCol = toColour(UI.accent.r, UI.accent.g, UI.accent.b, accentAlpha); + drawRect(chat.x, chat.y, chat.width, 2, accentCol); + + // Side accent line (left) + let sideAccent = toColour(UI.accent.r, UI.accent.g, UI.accent.b, Math.floor(150 * chatFadeAlpha)); + drawRect(chat.x, chat.y, 2, chatAreaHeight, sideAccent); + + // ======================================== + // CHAT HEADER + // ======================================== + let headerHeight = 28; + let headerBg = toColour(UI.bgPanel.r, UI.bgPanel.g, UI.bgPanel.b, bgAlpha); + drawRect(chat.x, chat.y + 2, chat.width, headerHeight, headerBg); + + // Calculate vertical centering for header text + let headerTextY = chat.y + Math.floor((headerHeight + 4 - 11) / 2); + + // Header text + let headerTextCol = toColour(UI.accent.r, UI.accent.g, UI.accent.b, alpha); + drawText("CHAT", chat.x + chat.padding, headerTextY, headerTextCol, 11); + + // Online count (placeholder - can be updated via network) + let onlineCol = toColour(UI.textSecondary.r, UI.textSecondary.g, UI.textSecondary.b, alpha); + drawText("Press T to chat", chat.x + chat.width - 115, headerTextY + 1, onlineCol, chat.fontSize); + + // Header bottom border + let headerBorder = toColour(UI.border.r, UI.border.g, UI.border.b, Math.floor(100 * chatFadeAlpha)); + drawRect(chat.x + chat.padding, chat.y + headerHeight + 2, chat.width - chat.padding * 2, 1, headerBorder); + + // ======================================== + // CHAT MESSAGES + // ======================================== + let messageStartY = chat.y + headerHeight + chat.padding; + let visibleCount = Math.min(chatMessages.length, chat.maxVisibleMessages); + let startIndex = Math.max(0, chatMessages.length - chat.maxVisibleMessages - chatScrollOffset); + + for (let i = 0; i < visibleCount && startIndex + i < chatMessages.length; i++) { + let msg = chatMessages[startIndex + i]; + let msgY = messageStartY + (i * chat.messageHeight); + + // Message background (alternating subtle) + if (i % 2 === 0) { + let msgBgAlpha = Math.floor(30 * chatFadeAlpha); + let msgBg = toColour(UI.bgMessage.r, UI.bgMessage.g, UI.bgMessage.b, msgBgAlpha); + drawRect(chat.x + 4, msgY - 2, chat.width - 8, chat.messageHeight, msgBg); + } + + // Get message color based on type + let msgColor = getMessageColor(msg.type); + let textCol = toColour(msgColor.r, msgColor.g, msgColor.b, alpha); + + // Calculate vertical centering offset for text in message row + let textY = msgY + Math.floor((chat.messageHeight - chat.fontSize) / 2) - 2; + + // Draw timestamp + let time = new Date(msg.timestamp); + let timeStr = "[" + padZero(time.getHours()) + ":" + padZero(time.getMinutes()) + "]"; + let timeCol = toColour(UI.textMuted.r, UI.textMuted.g, UI.textMuted.b, Math.floor(alpha * 0.7)); + drawText(timeStr, chat.x + chat.padding, textY, timeCol, chat.timestampSize); + + // Draw message text - timestamp takes approximately 50 pixels + let textX = chat.x + chat.padding + 52; + + if (msg.playerName && msg.type === "normal") { + // Player name in accent color + let nameCol = toColour(UI.playerName.r, UI.playerName.g, UI.playerName.b, alpha); + drawText(msg.playerName + ":", textX, textY, nameCol, chat.fontSize); + // Message text - use consistent character width calculation (6 pixels per char for size 10) + let nameWidth = (msg.playerName.length + 1) * 6 + 8; + drawText(msg.text, textX + nameWidth, textY, textCol, chat.fontSize); + } else { + drawText(msg.text, textX, textY, textCol, chat.fontSize); + } + } + + // ======================================== + // SCROLLBAR (if needed) + // ======================================== + if (chatMessages.length > chat.maxVisibleMessages) { + let scrollbarX = chat.x + chat.width - 6; + let scrollbarY = messageStartY; + let scrollbarHeight = chat.maxVisibleMessages * chat.messageHeight; + + // Scrollbar track + let trackCol = toColour(UI.bgHover.r, UI.bgHover.g, UI.bgHover.b, Math.floor(100 * chatFadeAlpha)); + drawRect(scrollbarX, scrollbarY, 4, scrollbarHeight, trackCol); + + // Scrollbar thumb + let thumbHeight = Math.max(20, (chat.maxVisibleMessages / chatMessages.length) * scrollbarHeight); + let thumbY = scrollbarY + ((startIndex / (chatMessages.length - chat.maxVisibleMessages)) * (scrollbarHeight - thumbHeight)); + let thumbCol = toColour(UI.accent.r, UI.accent.g, UI.accent.b, Math.floor(200 * chatFadeAlpha)); + drawRect(scrollbarX, thumbY, 4, thumbHeight, thumbCol); + } + + // ======================================== + // INPUT BOX (when active) + // ======================================== + if (chatInputActive) { + let inputY = chat.y + chatAreaHeight + 6; + + // Input background + let inputBg = toColour(UI.bgPanel.r, UI.bgPanel.g, UI.bgPanel.b, 240); + drawRect(chat.x, inputY, chat.width, chat.inputHeight, inputBg); + + // Input border glow + let pulseAlpha = Math.floor(180 + Math.sin(animTime * 4) * 50); + let inputBorder = toColour(UI.accent.r, UI.accent.g, UI.accent.b, pulseAlpha); + drawRect(chat.x, inputY, chat.width, 2, inputBorder); + drawRect(chat.x, inputY + chat.inputHeight - 2, chat.width, 2, inputBorder); + drawRect(chat.x, inputY, 2, chat.inputHeight, inputBorder); + drawRect(chat.x + chat.width - 2, inputY, 2, chat.inputHeight, inputBorder); + + // Calculate vertical centering for input text + let inputTextY = inputY + Math.floor((chat.inputHeight - chat.inputFontSize) / 2) - 1; + + // Input label "Say:" with consistent positioning + let labelCol = toColour(UI.textMuted.r, UI.textMuted.g, UI.textMuted.b, 255); + drawText("Say:", chat.x + chat.padding, inputTextY, labelCol, chat.inputFontSize); + + // Input text with cursor - "Say:" takes about 30 pixels + let cursorBlink = Math.floor(animTime * 2) % 2 === 0; + let inputTextCol = toColour(UI.textPrimary.r, UI.textPrimary.g, UI.textPrimary.b, 255); + let displayText = chatInputText + (cursorBlink ? "|" : ""); + drawText(displayText, chat.x + chat.padding + 36, inputTextY, inputTextCol, chat.inputFontSize); + } + + // ======================================== + // BOTTOM PANEL FADE + // ======================================== + let fadeHeight = 20; + let fadeTop = toColour(UI.bgDark.r, UI.bgDark.g, UI.bgDark.b, 0); + let fadeBottom = toColour(UI.bgDark.r, UI.bgDark.g, UI.bgDark.b, bgAlpha); + drawGradientRectV(chat.x, chat.y + chatAreaHeight - fadeHeight, chat.width, fadeHeight, fadeTop, fadeBottom); + + // Bottom border + drawRect(chat.x, chat.y + chatAreaHeight - 1, chat.width, 1, accentCol); +}); + +// ======================================== +// DRAW NOTIFICATIONS (Join/Leave) +// ======================================== +function drawNotifications(screenWidth, screenHeight) { + let notifX = screenWidth - 350; + let notifY = 100; + let notifHeight = 45; + let notifSpacing = 8; + + // Update and draw each notification + for (let i = notifications.length - 1; i >= 0; i--) { + let notif = notifications[i]; + let age = Date.now() - notif.timestamp; + + // Remove old notifications + if (age > notificationDuration) { + notifications.splice(i, 1); + continue; + } + + // Calculate alpha (fade in/out) + let fadeIn = Math.min(1, age / 200); + let fadeOut = Math.min(1, (notificationDuration - age) / 500); + notif.alpha = fadeIn * fadeOut; + + // Calculate slide animation + let slideIn = Math.min(1, age / 300); + let slideX = notifX + (1 - slideIn) * 100; + + let alpha = Math.floor(255 * notif.alpha); + let displayIndex = notifications.length - 1 - i; + let yPos = notifY + displayIndex * (notifHeight + notifSpacing); + + // Notification background + let bgAlpha = Math.floor(220 * notif.alpha); + let bgCol = toColour(UI.bgDark.r, UI.bgDark.g, UI.bgDark.b, bgAlpha); + drawRect(slideX, yPos, 320, notifHeight, bgCol); + + // Glossy top effect + let glossTop = toColour(255, 255, 255, Math.floor(15 * notif.alpha)); + let glossBottom = toColour(255, 255, 255, 0); + drawGradientRectV(slideX, yPos, 320, 15, glossTop, glossBottom); + + // Left accent bar based on type + let accentColor = notif.type === "join" ? UI.success : UI.error; + let accentBarCol = toColour(accentColor.r, accentColor.g, accentColor.b, alpha); + drawRect(slideX, yPos, 4, notifHeight, accentBarCol); + + // Icon (+ for join, - for leave) + let iconText = notif.type === "join" ? "+" : "-"; + let iconCol = toColour(accentColor.r, accentColor.g, accentColor.b, alpha); + drawText(iconText, slideX + 15, yPos + 12, iconCol, 18); + + // Notification text + let textCol = toColour(UI.textPrimary.r, UI.textPrimary.g, UI.textPrimary.b, alpha); + drawText(notif.text, slideX + 40, yPos + 14, textCol, 11); + + // Subtitle + let subtitleText = notif.type === "join" ? "Welcome to the server!" : "Goodbye!"; + let subtitleCol = toColour(UI.textSecondary.r, UI.textSecondary.g, UI.textSecondary.b, Math.floor(alpha * 0.7)); + drawText(subtitleText, slideX + 40, yPos + 28, subtitleCol, 9); + + // Outer glow + let glowAlpha = Math.floor(40 * notif.alpha); + let glowCol = toColour(accentColor.r, accentColor.g, accentColor.b, glowAlpha); + drawRect(slideX - 2, yPos - 2, 324, notifHeight + 4, glowCol); + } +} + +// ======================================== +// HELPER FUNCTIONS +// ======================================== +function padZero(num) { + return num < 10 ? "0" + num : num.toString(); +} + +// Check if player is spawned in game (not in lobby) +function checkIfPlayerInGame() { + try { + // localPlayer is null/undefined when player is not spawned (in lobby) + if (typeof localPlayer !== 'undefined' && localPlayer != null) { + return true; + } + } catch(e) {} + return false; +} + +// ============================================================================ +// KEY CODE CONSTANTS (in case they're not predefined) +// SDL key codes use ASCII values for letters (lowercase) +// ============================================================================ +const KEY_T = typeof SDLK_t !== 'undefined' ? SDLK_t : 116; // 't' = ASCII 116 +const KEY_RETURN = typeof SDLK_RETURN !== 'undefined' ? SDLK_RETURN : 13; +const KEY_ESCAPE = typeof SDLK_ESCAPE !== 'undefined' ? SDLK_ESCAPE : 27; +const KEY_BACKSPACE = typeof SDLK_BACKSPACE !== 'undefined' ? SDLK_BACKSPACE : 8; +const KEY_PAGEUP = typeof SDLK_PAGEUP !== 'undefined' ? SDLK_PAGEUP : 1073741899; +const KEY_PAGEDOWN = typeof SDLK_PAGEDOWN !== 'undefined' ? SDLK_PAGEDOWN : 1073741902; + +// ============================================================================ +// INPUT HANDLING - Using bindKey for more reliable input capture +// ============================================================================ + +// Bind T key to open chat +bindKey(KEY_T, KEYSTATE_UP, function(event) { + // Only allow opening chat if player is in game (not in lobby) + if (!chatInputActive && isPlayerInGame) { + openChatInput(); + } +}); + +// Function to open chat input +function openChatInput() { + chatInputActive = true; + chatInputText = ""; + chatFadeAlpha = 1.0; + lastMessageTime = Date.now(); + try { + gui.showCursor(true); + } catch(e) { + console.log("[Chat] Could not show cursor: " + e); + } + console.log("[Chat] Chat input opened"); +} + +// Function to close chat input +function closeChatInput() { + chatInputActive = false; + chatInputText = ""; + try { + gui.showCursor(false); + } catch(e) { + console.log("[Chat] Could not hide cursor: " + e); + } + console.log("[Chat] Chat input closed"); +} + +// Function to send chat message +function sendChatMessage() { + if (chatInputText.length > 0) { + triggerNetworkEvent("chatSendMessage", chatInputText); + console.log("[Chat] Sent message: " + chatInputText); + } + closeChatInput(); +} + +addEventHandler("OnKeyUp", function(event, key, scancode, mods) { + // Escape to close chat + if ((key === KEY_ESCAPE || key === SDLK_ESCAPE) && chatInputActive) { + closeChatInput(); + return; + } + + // Enter to send message + if ((key === KEY_RETURN || key === SDLK_RETURN) && chatInputActive) { + sendChatMessage(); + return; + } + + // Page up/down for scrolling + if ((key === KEY_PAGEUP || key === SDLK_PAGEUP) && chatMessages.length > chat.maxVisibleMessages) { + chatScrollOffset = Math.min(chatScrollOffset + 3, chatMessages.length - chat.maxVisibleMessages); + chatFadeAlpha = 1.0; + lastMessageTime = Date.now(); + } + if (key === KEY_PAGEDOWN || key === SDLK_PAGEDOWN) { + chatScrollOffset = Math.max(0, chatScrollOffset - 3); + chatFadeAlpha = 1.0; + lastMessageTime = Date.now(); + } +}); + +// Handle text input +addEventHandler("OnCharacter", function(event, character) { + if (chatInputActive) { + // Filter out control characters + if (character.charCodeAt(0) >= 32) { + chatInputText += character; + } + // Note: OnCharacter event is not cancellable in GTA Connected + } +}); + +addEventHandler("OnKeyDown", function(event, key, scancode, mods) { + if (!chatInputActive) return; + + // Backspace + if ((key === KEY_BACKSPACE || key === SDLK_BACKSPACE) && chatInputText.length > 0) { + chatInputText = chatInputText.slice(0, -1); + } + // Note: OnKeyDown event is not cancellable in GTA Connected +}); + +// ============================================================================ +// MOUSE WHEEL SCROLLING +// ============================================================================ +addEventHandler("OnMouseWheel", function(event, x, y) { + if (chatMessages.length <= chat.maxVisibleMessages) return; + + // Check if mouse is over chat area + let mouseX = gui.cursorPosition.x; + let mouseY = gui.cursorPosition.y; + + if (mouseX >= chat.x && mouseX <= chat.x + chat.width && + mouseY >= chat.y && mouseY <= chat.y + chat.maxVisibleMessages * chat.messageHeight + 50) { + + if (y > 0) { + chatScrollOffset = Math.min(chatScrollOffset + 2, chatMessages.length - chat.maxVisibleMessages); + } else { + chatScrollOffset = Math.max(0, chatScrollOffset - 2); + } + + chatFadeAlpha = 1.0; + lastMessageTime = Date.now(); + } +}); + +// ============================================================================ +// PROCESS - Keep native chat disabled +// ============================================================================ +addEventHandler("OnProcess", function(event) { + // Keep native chat disabled while our custom chat is active + try { + if (typeof setChatWindowEnabled === 'function') { + setChatWindowEnabled(false); + } + } catch(e) {} +}); + +// ============================================================================ +// DISABLE DEFAULT CHAT ON RESOURCE START +// ============================================================================ +addEventHandler("OnResourceReady", function(event, resource) { + if (resource == thisResource) { + try { + // Disable default chat window + if (typeof setChatWindowEnabled === 'function') { + setChatWindowEnabled(false); + } + console.log("[Chat] Native chat disabled"); + } catch(e) { + console.log("[Chat] Could not disable default chat: " + e); + } + + // Add a welcome message - will only show when player is in game + addChatMessage("Revolution chat loaded! Press T to type.", "system"); + } +}); + +console.log("[Chat] Revolution chat loaded - Custom glossy UI enabled"); diff --git a/resources/chat/meta.xml b/resources/chat/meta.xml new file mode 100644 index 0000000..8b83d87 --- /dev/null +++ b/resources/chat/meta.xml @@ -0,0 +1,6 @@ + + + +