README

Visit our Discord / Homepage / Store !


Requirements / Dependencies

The following resources are required for nss_npc to work:


Changelog

See CHANGELOG.md for more information.


Features

Simple static NPC generator that is optimized for performance to prevent entity overflow (e.g. if too many NPCs are spawned then no more other entities like animals does not spawn anymore).

  • Creates static NPCs at specific coordinates.

  • Performance-system to prevent entity overflow.

  • NPCs spawn on client if player reach a certain distance.

  • Optional: Multiple animations.

  • Optional: Multiple time windows (e.g. NPC only spawn between 8am - 12am and 2pm - 8pm).

  • Optional: Enable weapon.

  • Configurable Scale

  • Configurable model and outfit

  • Configurable spawn radius

  • Optional: Blip support

  • Optional: Blip color changes if NPC is visible between time window

  • NPC definition validator to help you out if you make a mistake in your config.


Open ToDo's

  • Currently, nothing.


Known issues

  • If the player idles too long and the game camera changes to idle animation then sometimes the NPC gets a new entity number so the script does not know the real entity anymore. If you now restart the script the NPC will not be removed.

  • To small NPCs slowly drifting to ground on spawn. Ensure the NPC is placed a little more down on the z-axis.


Setup script

  1. Ensure that the nss_npc folder is in your resources folder.

  2. Create your NPCs definitions as files in the npcs folder. See jack_the_butcher.demo.lua and jacks_little_brother.demo.lua for examples.

  3. Rename config.demo.lua to config.lua and fill in the values.

  4. Add ensure nss_npc to your server.cfg.

  5. Restart your server.


NPC definition

Advanced example of all available options

---@type NssNpcDefinition
JACK_THE_BUTCHER = {

    id = 'jack_the_butcher', -- Unique id for this npc. Only A-Z, a-z, 0-9 and _ are allowed.

    coords = {
        x = -760.56,
        y = -1250.08,
        z = 43.41,
        heading = 0.0, -- Value between 0.0 and 360.0
    },

    -- Set this to true if you want to prevent that the NPC is automatically placed on ground on spawn.
    -- Tipp: If you have issues with NPCs stuck in the ground on spawn then set this to true (in most case inside of custom buildings).
    prevent_place_on_ground = false, 

    look = {
        model = "S_M_M_NBXRIVERBOATDEALERS_01", -- See https://github.com/femga/rdr3_discoveries/blob/f729ba03f75a591ce5c841642dc873345242f612/peds/peds_list.lua for a list of all models
        outfit = 0, -- Optional. nil = random. Specific outfit 0 for this model. Models have a specific range of outfits that has to be considered, see model list link above.
        scale = 1.0, -- Optional. Default is 1.0
    },

    -- Animations are optional, set nil or remove complete section if you don't want to use animations
    -- You can have so many animations as you want. 
    -- The animations will be played in the order you define them.
    -- See https://raw.githubusercontent.com/femga/rdr3_discoveries/b7c5ab68b4f3be78bc353b961a64baf77b327b26/animations/ingameanims/ingameanims_list.lua for a list of all animations
    animations = {
        {
            anim_dict = "amb_camp@prop_camp_butcher@resting@rabbit@male_a@idle_a",
            anim_name = "idle_c",
            delay_in_ms = 10000, -- 10 seconds until next animation
            loop = true,
            times = 1, -- because "loop = true" we need to set times to 1
            anim_only_upper_body = true, -- animate only upper body (e.g. the npc sits on a chair)
        },

        {
            anim_dict = "amb_camp@prop_camp_butcher@resting@rabbit@male_a@idle_a",
            anim_name = "idle_b",
            delay_in_ms = 8000, -- because "times = 3" the animation will be repeated 3 times with a delay of 8 seconds then the next animation will be played
            loop = false,
            times = 3,
            anim_only_upper_body = false,
        },

        {
            anim_dict = "amb_camp@prop_camp_butcher@resting@rabbit@male_a@idle_a",
            anim_name = "idle_a",
            delay_in_ms = 5000, -- 5 seconds until next animation
            loop = true,
            times = 1,
            anim_only_upper_body = true,
        },
    },

    -- Scenarios are optional, set nil or remove the property if you don't want to use scenarios
    scenario = 'WORLD_HUMAN_WRITE_NOTEBOOK', -- See https://github.com/femga/rdr3_discoveries/blob/b7c5ab68b4f3be78bc353b961a64baf77b327b26/animations/scenarios/scenario_types_with_conditional_anims.lua for a list of scenarios

    -- Weapons are optional, set nil or remove the property if you don't want to use weapons
    weapon = 'weapon_shotgun_semiauto', -- See https://github.com/femga/rdr3_discoveries/blob/f729ba03f75a591ce5c841642dc873345242f612/weapons/weapons.lua for a list of all weapons

    -- Spawn radius is optional, default is 100 meters
    -- Do not set it to high because of the players graphics settings render distance. If an entity is out of the 
    -- render distance it will be removed and replaced with a new entity id if the player comes back. Currently the script
    -- can not handle this.
    spawn_radius_in_meters = 100,

    -- Time windows are optional, set nil or remove the complete section if you don't want to use time windows
    -- You can have so many time windows as you want.
    time_windows = {
        {
            start_hour = 8, -- Hour between 0 and 23 but not higher than end_hour.
            start_minute = 0, -- Minute between 0 and 59.
            end_hour = 12,
            end_minute = 0,
        },

        {
            start_hour = 14,
            start_minute = 0,
            end_hour = 18,
            end_minute = 0,
        },
    },

    -- Blip is optional, set nil or remove the complete section if you don't want to use a blip
    blip = {

        -- See https://github.com/femga/rdr3_discoveries/tree/master/useful_info_from_rpfs/textures/blips_mp
        -- or https://github.com/femga/rdr3_discoveries/tree/master/useful_info_from_rpfs/textures/blips for a list of all blip icons
        icon = 'blip_mp_game_race_horse',

        -- See https://github.com/femga/rdr3_discoveries/tree/master/useful_info_from_rpfs/blip_modifiers for a list of all blip colors
        default_color = 'BLIP_MODIFIER_MP_COLOR_31', -- White
        time_window_color = 'BLIP_MODIFIER_MP_COLOR_8', -- Green. If nil no color change will be applied on time window

        scale = 0.2, -- Value between 0.1 and 0.5, 0.2 is default. If nil default is used.
        title = 'Jack the Butcher', -- Can be empty but should be set for better overview.

        -- See https://github.com/femga/rdr3_discoveries/tree/master/useful_info_from_rpfs/blip_modifiers for a list of all blip modifiers
        modifiers = {
            'BLIP_MODIFIER_LOCKED', -- Little lock at the blip
            'BLIP_MODIFIER_MP_SUPPLIES_OUTLINE_COLOR_2', -- Red outline
        },
    },

    -- Prompts are optional, set nil or remove the complete section if you don't want to use prompts
    prompts = {

        -- You can have multiple prompt pages. Each prompt page has a label and a list of prompts.
        {
            page_label = 'Jack the Butcher', -- Label of the prompt page (it is possible to have multiple switchable prompt pages)
            page_radius_in_meters = 2, -- Radius in which the player can interact with the prompt page

            -- List of prompts for this prompt page
            page_prompts = {

                -- Simple pressed example
                -- The `prompt_callback` is called once if the player has pressed the prompt key.
                {
                    prompt_label = 'Buy meat', -- Label of the prompt
                    prompt_key = 0xD9D0E1C0, -- Spacebar, see https://github.com/mja00/redm-shit/blob/master/nuiweaponspawner/config.lua for key hashes
                    prompt_type = 'just_pressed', -- Possible types are 'just_pressed', 'pressed', 'just_released', 'released', 'standard_hold_prompt', 'standardized_hold_prompt'

                    -- Callback will be called if player has fulfilled the prompt.
                    -- The attribute `entity` is nil if the NPC is currently not created.
                    prompt_callback = function(entity --[[ number|nil ]], npc_id --[[ string ]], npc_name --[[ string ]])
                        print('By meat from ' .. npc_name .. '!', entity, npc_id) -- Debug message
                        -- Do your stuff here, e.g. trigger an event, command or whatever.
                    end
                },

                -- Advanced hold mode
                -- The `prompt_callback` is called each frame during the prompt key is hold by the player.
                {
                    prompt_label = 'Do something',
                    prompt_key = 0x6319DB71, -- Up key
                    prompt_type = 'standard_hold_prompt',

                    prompt_callback = function(entity --[[ number|nil ]], npc_id --[[ string ]], npc_name --[[ string ]])
                        print(npc_name .. ' is doing something...', entity, npc_id) -- Debug message
                        -- Do your stuff here, e.g. trigger an event, command or whatever.
                    end,
                },

                -- Advanced press/release example
                -- The `prompt_callback` is called once if the player has pressed the prompt key.
                -- The `prompt_release_callback` is called once if the player has released the prompt key.
                {
                    prompt_label = 'High five',
                    prompt_key = 0x05CA7C52, -- Down key
                    prompt_type = 'standard_hold_prompt',

                    prompt_callback = function(entity --[[ number|nil ]], npc_id --[[ string ]], npc_name --[[ string ]])
                        print(npc_name .. ' starting gives you a high five!', entity, npc_id) -- Debug message
                        -- Do your stuff here, e.g. trigger an event, command or whatever.
                    end,

                    prompt_release_callback = function(entity --[[ number|nil ]], npc_id --[[ string ]], npc_name --[[ string ]])
                        print(npc_name .. ' finishing gives you a high five!', entity, npc_id) -- Debug message
                        -- Do your stuff here, e.g. trigger an event, command or whatever.
                    end,
                },
            },
        },
    },
}

FAQ

Can I combine animations and scenarios?

Yes, but be aware that some animations can stop scenarios and vice versa. So you have to test it out.

Can I combine animations and weapons?

Yes, but be aware that some animations do not work with weapons or result in dropping the weapon. So you have to test it out.

What is the best spawn radius?

It depends on if the NPC is outside or inside. If the NPC is outside then we recommend a radius of 100 meters otherwhise 20 meters.

If your server has really a lot of players and NPCs than you should reduce the radius to 50 meters for outside.

What is the "entity overflow"?

If too many entities are spawned then no more other entities like animals spawn anymore. This is a known issue of REDM / RDR2. This script helps you to avoid this situation if you have a lot of NPCs.

Can I put all my NPCs in one file?

Yes, but we recommend to split them up into multiple files. This makes it easier to maintain your NPCs.

Example for one file with all NPCs directly in the config:

---@type NssNpcConfig
Config = {}

---@type NssNpcDefinition[]
Config.NpcList = {
    {
        id = 'jacks_little_brother',
        coords = { x = -762.56, y = -1251.08, z = 43.0, heading = 45.0, },
        look = { model = "S_M_M_NBXRIVERBOATDEALERS_01", scale = 0.8, }, -- random outfit because property outfit is not set
    },

    {
        id = 'jacks_bigger_brother',
        coords = { x = -765.56, y = -1251.08, z = 43.0, heading = 45.0, },
        look = { model = "S_M_M_NBXRIVERBOATDEALERS_01", outfit = 1, scale = 1.0, },
    },
}

The NPC does not spawn, what can I do?

  • Check if the NPC z-axis is not below the ground. If so then increase the z-axis value.

  • Check if you have defined a time window and if the current time is within that time window.

  • Check if you have defined a spawn radius and if the player is within that radius.

  • Check if you have not too many entities spawned already.

  • Check if there are related errors in the client console or server console. Sometimes the config is not valid. It could be possible that a bug in the script causes this. In this case please contact us.

What can I do if the NPC stucks in ground, e.g. if spawned in a building?

You can use prevent_place_on_ground to prevent this. But be aware that the NPC will spawn in the air.

Last updated