Page cover

💭mad-thoughts

Turn 💬 notifications into 📖 narratives.

Tebex Package

Introduction

A thought bubble notification system designed for immersive roleplay.

In traditional FiveM servers, notifications often break immersion with meta-gaming information: "Not enough police online" or "You don't have the required skill level." These messages force players to acknowledge game mechanics rather than staying in character.

mad-thoughts transforms this experience by displaying notifications as the character's inner thoughts. When a player encounters a locked door they can't pick, instead of seeing "Lockpicking skill too low," they'll think: "This lock looks too complex for me..."


Preview

Watch the preview video to see mad-thoughts in action, or view the screenshots below.

mad-thoughts Preview Video
Screenshots


Features & Benefits

  • 💭 Immersive Thought Bubbles: Displays character thoughts above the player's head

  • 🎨 Rich Customisation: Icons, colors, and timing options

  • 👉 Scenario System: Automatic thoughts based on gameplay situations

  • 💬 Message Variety: Random selection prevents repetition

  • 📍 Location Awareness: Thoughts trigger in specific areas

  • 🔁 Sequential Thoughts: Create story-driven thought chains

  • 🌐 Server-Wide Thoughts: Send thoughts to all connected players

  • 💻 Developer-Friendly: Simple exports for easy implementation

⭐ Perfect For:

  • Skill check feedback that doesn't break immersion

  • Subtle environmental storytelling

  • Replacing meta-gaming notifications with in-character thoughts

  • Adding personality and depth to player characters

  • Creating a more cinematic and immersive experience

Transform your server's notification system from immersion-breaking text to character-driven thoughts. Your players will feel more connected to their characters, and your world will feel more authentic and alive.


Scenarios & Locations

The real power of mad-thoughts comes from automatic triggers that create thoughts based on specific conditions. These features transform passive notifications into immersive character experiences. While the resource includes several pre-built examples, you're encouraged to create ones tailored to your server.


Scenario-Based Thoughts

Scenarios are condition-driven thoughts that monitor your player's state and environment. When specific conditions are met, they automatically trigger appropriate thoughts. Common scenario types include:

  • Health & Status: Low health, stress levels, character needs

  • Environmental: Weather conditions, time of day, underwater

  • Action-Based: Swimming, running, combat situations

  • Character Needs: Hunger, thirst, fatigue

Scenarios use custom check functions to determine when to trigger, making them highly flexible for any roleplay situation.


Location-Based Thoughts

Location triggers create area-specific thoughts that enhance environmental storytelling. Players automatically think contextually appropriate thoughts when entering designated areas.

Location triggers support both pre-configured areas (set in config.lua) and dynamic registration and destruction, giving you complete control over when and where thoughts appear.

Circular Areas

Perfect for simple zones around points of interest:

  • Hospitals and police stations

  • Beaches, mountains, and landmarks

  • Buildings and neighborhoods

  • General area triggers

Polygon Zones

Ideal for complex, custom-shaped areas:

  • Detailed building interiors

  • Irregularly shaped districts

  • Precise zone boundaries

  • Custom map areas and routes


How to Use

Basic Thoughts

Client-side usage:

-- Basic notification
exports['mad-thoughts']:thought("I can't do this right now...")

-- With duration (10 seconds)
exports['mad-thoughts']:thought("I wonder what that means...", 10)

-- With icon
exports['mad-thoughts']:thought("Something doesn't feel right about this...", 5, "fas fa-question-circle")

-- With icon and color
exports['mad-thoughts']:thought("I need to get out of here!", 8, "fas fa-exclamation", "#e74c3c")

Server-side usage:

-- Send to all players
exports['mad-thoughts']:thought("Everyone sees this message")

-- Send to specific player by server ID
exports['mad-thoughts']:thought(1, "Only player 1 sees this message", 10)

-- Send to all players explicitly
exports['mad-thoughts']:thought(-1, "Everyone sees this message", 5, "fas fa-bullhorn")

-- With full customisation for specific player
exports['mad-thoughts']:thought(5, "Player 5 only message", 8, "fas fa-exclamation", "#e74c3c")

Parameters

  • target (server-side only): Player server ID (number), -1 for all players, or omit for all players

  • message: The text content of the thought (string)

  • duration: How long the thought displays in seconds (number, default: 5)

  • icon: Font Awesome icon class (string, default: "fas fa-comment-dots")

  • color: Hex color code for the icon and border (string, default: "#F2F2F2")


Using predefined types

Client-side usage:

exports['mad-thoughts']:info("I just remembered where I left my keys...", 5)
exports['mad-thoughts']:success("I finally figured it out!", 5)
exports['mad-thoughts']:warning("This doesn't look safe...", 5)
exports['mad-thoughts']:error("I've made a terrible mistake...", 5)

Server-side usage:

-- Send to all players
exports['mad-thoughts']:info("Server-wide information message", 5)
exports['mad-thoughts']:success("Everyone sees this success message", 5)
exports['mad-thoughts']:warning("A warning for all players", 5)
exports['mad-thoughts']:error("Something is seriously wrong!", 5)

-- Send to specific player (by server ID)
exports['mad-thoughts']:info(1, "Server-wide information message", 5)
exports['mad-thoughts']:success(5, "Everyone sees this success message", 5)
exports['mad-thoughts']:warning(12, "A warning for all players", 5)
exports['mad-thoughts']:error(27, "Something is seriously wrong!", 5)

Parameters

  • target (server-side only): Player server ID (number), -1 for all players, or omit for all players

  • message: The text content of the thought (string)

  • duration: How long the thought displays in seconds (number, default: 5)

Predefined types use the following icons and colors:

  • info: "fas fa-info-circle" with blue color (#2B78FC)

  • success: "fas fa-check-circle" with green color (#06CE6B)

  • warning: "fas fa-exclamation-triangle" with orange color (#FB8607)

  • error: "fas fa-times-circle" with red color (#fe2436)


Sequential Thoughts

Display multiple thoughts in sequence:

Client-side usage:

exports['mad-thoughts']:thoughtChain({
    {message = "That's strange...", duration = 3},
    {message = "I wonder if anyone is here...", duration = 4, icon = "fas fa-question"},
    {message = "Better be careful.", duration = 3, color = "#e74c3c"}
}, 4000)

Server-side usage:

-- Send to all players
exports['mad-thoughts']:thoughtChain({
    {message = "That's strange...", duration = 3},
    {message = "I wonder if anyone is here...", duration = 4, icon = "fas fa-question"},
    {message = "Better be careful.", duration = 3, color = "#e74c3c"}
}, 4000)

-- Send to specific player (by server ID)
exports['mad-thoughts']:thoughtChain(1, {
    {message = "That's strange...", duration = 3},
    {message = "I wonder if anyone is here...", duration = 4, icon = "fas fa-question"},
    {message = "Better be careful.", duration = 3, color = "#e74c3c"}
}, 3000)

Parameters

  • target (server-side only): Player server ID (number), -1 for all players, or omit for all players

  • thoughts: Array of thought objects, each with message, duration, icon, and color

  • delay: Time between thoughts in milliseconds (default: 3s)

  • initialDelay: Optional delay before starting the sequence (default: 0s)


Dynamic Scenario and Location Triggers

The system allows you to dynamically create and remove thought triggers. This is especially useful for:

  • Scenario and location triggers that appear only during certain jobs, heists, or events

  • Temporary areas that should trigger thoughts for a limited time

  • Dynamic story elements that change based on server state

Note: Scenario and location trigger exports are available on both client-side and server-side:

  • Client-side: Registers triggers for the local player only

  • Server-side: Registers triggers for all connected players and automatically applies them to players who join later

Creating Custom Scenarios

Scenarios automatically trigger thoughts based on conditions you define. Each scenario includes a check function that determines when to trigger the thought.

Scenario Registration

Client-side usage:

exports['mad-thoughts']:registerScenario('my_custom_scenario', {
    check = function(ped)
        return IsPedAimingFromCover(ped)
    end,
    message = "Ready, aim, fire...",
    duration = 6,
    icon = "fas fa-crosshairs",
    color = "#FF0000",
    cooldown = 30
})

Server-side usage:

exports['mad-thoughts']:registerScenario('my_custom_server_scenario', {
    check = function(ped)
        return IsFlashLightOn(ped)
    end,
    message = "Let's light things up!",
    duration = 4,
    icon = "fas fa-lightbulb",
    color = "#FFDD44",
    cooldown = 10
})

exports['mad-thoughts']:registerScenario('my_custom_server_scenario', {
    check = function(ped)
        return IsFlashLightOn(ped)
    end,
    message = {
        "I should have brought an umbrella...",
        "This rain is really coming down...",
        "At least the rain will wash away the evidence..."
    },
    duration = 4,
    icon = "fas fa-lightbulb",
    color = "#FFDD44",
    cooldown = 10
})

Parameters

  • scenarioId: Unique identifier for this scenario (string) - choose a descriptive name

  • check: Function that returns true when the scenario should trigger. Use client-side functions for client registration, server-side functions for server registration.

  • message: The thought text (string or array for random selection)

  • duration: How long the thought displays (seconds)

  • icon: Font Awesome icon class

  • color: Hex color code for styling

  • cooldown: Minimum seconds between triggers

Creating Custom Locations

Location triggers create thoughts when players enter specific areas. The system supports both circular areas and complex polygon zones.

Circular Zones Registration

Define a radius around a point to create your zone where the thought will trigger:

Client-side usage:

exports['mad-thoughts']:registerLocation('my_custom_location', {
    coords = vector3(228.44, -786.26, 30.70),
    distance = 20.0,
    message = "This place looks familiar...",
    duration = 5,
    icon = "fas fa-building",
    color = "#3498db",
    cooldown = 300
})

Server-side usage:

-- Registers the location for players
exports['mad-thoughts']:registerLocation('my_custom_location', {
    coords = vector3(228.44, -786.26, 30.70),
    distance = 20.0,
    message = "This place has seen better days...",
    duration = 5,
    icon = "fas fa-building",
    color = "#3498db",
    cooldown = 300
})

Parameters

  • locationId: Unique identifier for this location (string) - choose a descriptive name like 'hospital_entrance' or 'police_station'

  • coords: Center point coordinates (vector3)

  • distance: Trigger radius in game units

  • message: The thought text (string or array)

  • duration: Display duration in seconds

  • icon: Font Awesome icon class

  • color: Hex color code

  • cooldown: Seconds between triggers

Polygon Zone Registration

For complex, custom-shaped areas, use polygon zones:

Client-side usage:

exports['mad-thoughts']:registerPolygonZone('my_custom_polyzone', {
    points = {
        vector3(447.9, -998.8, 25.8),
        vector3(450.3, -998.2, 25.8),
        vector3(449.9, -995.5, 25.8),
        vector3(447.2, -995.6, 25.8),
        vector3(446.3, -997.9, 25.8),
    },
    thickness = 4.0,
    message = "This custom zone feels different...",
    duration = 5,
    icon = "fas fa-draw-polygon",
    color = "#9b59b6",
    cooldown = 300
})

Server-side usage:

-- Registers the polygon zone for all connected players
exports['mad-thoughts']:registerPolygonZone('my_custom_polyzone', {
    points = {
        vector3(447.9, -998.8, 25.8),
        vector3(450.3, -998.2, 25.8),
        vector3(449.9, -995.5, 25.8),
        vector3(447.2, -995.6, 25.8),
        vector3(446.3, -997.9, 25.8),
    },
    thickness = 4.0,
    message = "Everyone feels uneasy in this restricted area...",
    duration = 5,
    icon = "fas fa-draw-polygon",
    color = "#9b59b6",
    cooldown = 300
})

Parameters

  • zoneId: Unique identifier for this polygon zone (string) - choose a descriptive name like 'gang_territory' or 'event_area'

  • points: Array of vector3 coordinates defining the polygon shape

  • thickness: Vertical height of the zone (optional, default: 4.0)

  • message: The thought text (string or array)

  • duration: Display duration in seconds

  • icon: Font Awesome icon class

  • color: Hex color code

  • cooldown: Seconds between triggers

Note: Polygon zones require all Z coordinates to be on the same plane. The system automatically handles this requirement:

  • When mixed Z coordinates are detected (e.g., some points at 25.0, others at 30.0), the system automatically calculates the average Z value

Removing Custom Triggers

Clean up your custom scenarios and locations when they're no longer needed:

Client-side usage:

-- Remove a scenario you previously registered
exports['mad-thoughts']:removeScenario('my_custom_scenario')

-- Remove a circular location you previously registered
exports['mad-thoughts']:removeLocation('my_custom_location')

-- Remove a polygon zone you previously registered
exports['mad-thoughts']:removePolygonZone('my_custom_polyzone')

Server-side usage:

-- Remove triggers for all connected players
exports['mad-thoughts']:removeScenario('my_custom_scenario')
exports['mad-thoughts']:removeLocation('my_custom_location')
exports['mad-thoughts']:removePolygonZone('my_custom_polyzone')

Parameters

  • id: The unique identifier of the trigger to remove (string, required)

    • For scenarios: The scenario ID you used when registering

    • For locations: The location ID you used when registering

    • For polygon zones: The zone ID you used when registering


Server-Wide Thoughts

You can send thoughts to all connected players:

From a Client Script

-- Legacy. Using exports['mad-thoughts']:thought() is recommended instead
-- Send a thought to all players on the server
exports['mad-thoughts']:sendThoughtToAll(
  "Everyone should see this message!", -- message
  5, -- duration in seconds
  "fas fa-bullhorn", -- icon
  "#E67E22" -- color (orange)
)

Parameters

  • message: The text content of the thought (string, required)

  • duration: How long the thought displays in seconds (number, optional, default: 5)

  • icon: Font Awesome icon class (string, optional, default: "fas fa-comment-dots")

  • color: Hex color code for the icon and border (string, optional, default: "#F2F2F2")

From a Server Script

-- Use the unified 'thought' export
exports['mad-thoughts']:thought("Everyone sees this message")
exports['mad-thoughts']:thought(-1, "Explicitly send to all players", 5, "fas fa-bullhorn")

-- Or use predefined types
exports['mad-thoughts']:info("Server-wide information message")
exports['mad-thoughts']:success("Everyone sees this success message")
-- Legacy. Using exports['mad-thoughts']:thought() is recommended instead
exports['mad-thoughts']:sendThoughtToAll(
  "Server announcement as a thought", -- message
  5, -- duration in seconds
  "fas fa-server", -- icon
  "#3498db" -- color (blue)
)

Via Command

-- Basic usage
/globalthought "This is a global announcement"

-- With custom duration (in seconds)
/globalthought "This is a global announcement" "10"

-- With custom icon
/globalthought "This is a global announcement" "10" "fa-solid fa-bell"

-- With custom color
/globalthought "This is a global announcement" "10" "fa-solid fa-bell" "#FF5733"

Parameters

  • message: The text content of the thought (string, required)

  • duration: How long the thought displays in seconds (string, optional, default: "5")

  • icon: Font Awesome icon class (string, optional, default: "fas fa-comment-dots")

  • color: Hex color code for the icon and border (string, optional, default: "#F2F2F2")

This command requires the group.admin permission.


Player-Specific Thoughts

You can send thoughts to specific players in several ways:

exports['mad-thoughts']:thought(1, "Private message for player 1")
exports['mad-thoughts']:info(5, "Info message for player 5 only")
exports['mad-thoughts']:warning(3, "Warning for player 3", 8)
exports['mad-thoughts']:error(1, "Error message for player 1", 6)

From a Client Script

-- Send a thought to a specific player
exports['mad-thoughts']:sendThoughtToPlayer(
  1, -- target player's server ID
  "This message is only for you", -- message
  5, -- duration in seconds
  "fas fa-user-secret", -- icon
  "#9B59B6" -- color (purple)
)

Parameters

  • playerId: The target player's server ID (number, required)

  • message: The text content of the thought (string, required)

  • duration: How long the thought displays in seconds (number, optional, default: 5)

  • icon: Font Awesome icon class (string, optional, default: "fas fa-comment-dots")

  • color: Hex color code for the icon and border (string, optional, default: "#F2F2F2")

Via Command

-- Basic usage
/playerthought 1 "This is a private thought"

-- With custom duration (in seconds)
/playerthought 1 "This is a private thought" "10"

-- With custom icon
/playerthought 1 "This is a private thought" "10" "fa-solid fa-crown"

-- With custom color
/playerthought 1 "This is a private thought" "10" "fa-solid fa-crown" "#9B59B6"

Parameters

  • playerId: The target player's server ID (number, required)

  • message: The text content of the thought (string, required)

  • duration: How long the thought displays in seconds (string, optional, default: "5")

  • icon: Font Awesome icon class (string, optional, default: "fas fa-comment-dots")

  • color: Hex color code for the icon and border (string, optional, default: "#F2F2F2")

This command requires the group.admin permission.


Examples

Making NPCs More Immersive

-- Example: When player approaches a mission NPC
exports['mad-thoughts']:thought("I wonder if I should talk to this stranger...", 5)

Skill Check Feedback

-- Example: Instead of "Lockpicking failed"
exports['mad-thoughts']:error("I can't seem to get these tumblers right...", 4)

Environmental Storytelling

-- Example: When entering an abandoned building
exports['mad-thoughts']:thoughtChain({
    {message = "This place gives me the creeps...", duration = 4},
    {message = "Feels like someone's watching me...", duration = 3, icon = "fas fa-eye"}
}, 3000)

Proximity

-- Example: Send a thought to nearby players
local function notifyNearbyPlayers(message, duration, icon, color)
    local myCoords = GetEntityCoords(cache.ped)
    local players = GetActivePlayers()
    
    for _, playerId in ipairs(players) do
        if playerId ~= PlayerId() then
            local targetPed = GetPlayerPed(playerId)
            local targetCoords = GetEntityCoords(targetPed)
            local distance = #(myCoords - targetCoords)
            
            if distance <= 10.0 then
                exports['mad-thoughts']:sendThoughtToPlayer(
                    GetPlayerServerId(playerId),
                    message or "This guy looks suspicious",
                    duration or 5,
                    icon or "fas fa-exclamation",
                    color or "#e74c3c"
                )
            end
        end
    end
end

-- Example: When a player discovers something important
RegisterNetEvent('discovered')
AddEventHandler('discovered', function()
    -- Thought for the player who made the discovery
    exports['mad-thoughts']:thought("What's this strange artifact?", 5)
    
    -- Send a different thought to nearby players
    notifyNearbyPlayers(
        "Did someone just find something?",
        5,
        "fas fa-question-circle",
        "#9B59B6"
    )
end)

Group Coordination

-- Example: Server: Coordinating with team members
RegisterNetEvent('heist:beginOperation')
AddEventHandler('heist:beginOperation', function(teamMembers)
    for role, playerId in pairs(teamMembers) do
        if role == "hacker" then
            exports['mad-thoughts']:thought(
                playerId,
                "I need to focus on bypassing the security system...",
                5,
                "fas fa-laptop-code",
                "#3498db"
            )
        elseif role == "lookout" then
            exports['mad-thoughts']:warning(
                playerId,
                "I should keep an eye out for guards...",
                5
            )
        elseif role == "driver" then
            exports['mad-thoughts']:success(
                playerId,
                "Engine's running. Ready for a quick getaway...",
                5
            )
        end
    end
end)

Dependencies


Installation

  1. Ensure you have the required dependency

  2. Place the folder in your server's resources directory

  3. Add start mad-thoughts to your server.cfg

  4. Configure scenarios & locations in the config.lua file to match your server's needs


Configuration

The resource comes with a comprehensive config.lua file that includes:

  1. Ready-to-use templates for scenarios and locations

  2. Example circular and polygon locations for popular map areas

  3. Example scenarios for common situations (underwater, low health, weather conditions)

  4. Detailed comments explaining every configuration option

Thought Positioning

mad-thoughts supports two positioning modes for displaying thoughts:

-- in config.lua
ThoughtLocation = "immersive", -- "immersive" or "static"
  • Immersive (Default): Attaches thoughts to the player's head bone - more realistic for roleplay

  • Static: Places thoughts at a fixed position above the player - more predictable and easier to read

mad-thoughts config file

Debug

The resource includes debug visualizations to help you set up and test your location triggers:

Enable debug mode in your config.lua:

Debug = true, -- boolean (true/false | default: false)

When debug mode is enabled, you'll see:

  • Visual indicators showing location trigger zones as red spheres

  • Location IDs displayed above each zone

This is particularly useful when:

  • Setting up new location triggers to ensure they're in the right place

  • Testing the detection radius of your locations

Debug prints are handled via ox_lib prints.

Debug mode displaying locations

Support

Join our Discord community for support, regular updates and to request new features.

Escrow Protection

This resource is protected by FiveM's escrow system to safeguard against unauthorised distribution. The core functionality is secured while maintaining full customisability through the extensive configuration system.

Despite the security measures:

  • All configuration options remain fully editable.

  • The UI remains fully editable (html, css, js)

  • Server owners retain complete control over all settings.

  • No functionality is limited by the protection system.

This approach ensures you receive a premium, leak-protected resource while maintaining the freedom to customise it for your server's unique needs.

Last updated