nss_npc by Nightshift Studio
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.
Since 1.2.0 the resource also handles the world around those NPCs:
- Wild animals and herds at any coordinate, wandering around an anchor point.
- Real wild behaviour, animals flee from players and horses buck when mounted.
- Optional: Respawn, a hunted herd fills back up after a configurable delay.
- Ambient no spawn zones that keep the engine from populating an area.
- Admin only setup helper that draws anchors and zone centres in game.
- Validators for animal and zone definitions, same style as the NPC one.
Open ToDo's
- Currently, nothing.
Known issues
- To small NPCs slowly drifting to ground on spawn. Ensure the NPC is placed a little more down on the z-axis.
Fixed in nss_libs 0.37.4: an NPC that the engine streamed out and back in kept standing in the world, because the stored handle pointed nowhere and the script could no longer reach it. Update nss_libs if you still see leftover NPCs after a restart.
Setup script
- Ensure that the
nss_npcfolder is in yourresourcesfolder. - Create your NPC definitions as files in the
npcsfolder. See jack_the_butcher.demo.lua and jacks_little_brother.demo.lua for examples. - Rename config.demo.lua to
config.luaand fill in the values. - Optional, for animals: create your herds as files in the
animalsfolder (see deer_heartlands.demo.lua) and list them inconfig_animals.lua. - Optional, for no spawn zones: same with the
zonesfolder (see valentine_saloon.demo.lua) andconfig_zones.lua. - Optional, for the setup helper: set your admin groups in
config_dev.lua. - Add
ensure nss_npcto yourserver.cfg. - Restart your server.
Steps 4 to 6 are optional, the three config files ship with empty lists. The *.demo.lua files are never loaded, they are templates you copy from.
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,
},
},
},
},
}Animal definition
Herds live as files in the animals folder (see deer_heartlands.demo.lua) and are listed in config_animals.lua.
Unlike the static NPCs, animals are networked: everyone sees the same ones in the same place, and an animal shot by one player is dead for all of them. That is required, not a preference. Animals wander, and local ones would take different paths on every client. Hunting scripts also need a network id to identify the carcass server side, and a local ped has none.
The server hands out one build order per herd, so count is the number of animals that really stand there, no matter how many players are watching. The last player to leave the spawn radius cleans up.
Two values matter more than the others:
wander_radius_in_metersmust be smaller thanspawn_radius_in_meters. The range check measures the distance to the anchor, not to the animal, so with a larger roaming radius an animal could stand next to a player and still disappear. The validator rejects this.spread_in_metersshould not be too small. An animal is about two meters long, and animals spawned inside each other get pushed apart by the engine.
respawn_delay_in_ms fills a herd back up after animals disappeared. 0 or nil switches it off, then a hunted herd stays down until the last player leaves the spawn radius and comes back.
An animal only counts as gone once its entity is really away, so a carcass left lying around still belongs to the herd. That is on purpose: GetEntityHealth reads 0 for living animals on a RedM server often enough to be useless, so the server goes by existence, which it can check reliably. Replacements appear on the roaming ring at the spot furthest from every player, and if even that spot is closer than 60 meters to someone, the server waits for the next tick instead.
No spawn zone definition
Zones live as files in the zones folder (see valentine_saloon.demo.lua) and are listed in config_zones.lua.
A zone does not delete anything, it stops the supply: the engine no longer places ambient peds inside the box, and what already stands disappears once it despawns on its own. Deleting peds in a loop instead crashes the client in gta-net-rdr3.dll without a lua error before it.
Config.NoSpawnZoneSettings.arm_distance_in_meters (default 120) is how far ahead of a zone the box is armed. Ambient peds spawn in a radius around the player, so arming only on entry means entering an already populated area.
Only ONE zone can be active at a time. The engine keeps a single global box. If the player is near several zones the nearest one wins, so zones should not overlap.
clear_on_enter additionally clears the area once per entry. Your own NPCs are safe, but script peds owned by other resources can be caught, so leave it off unless you need the immediate effect.
Setup helper (admins only)
Settings are in config_dev.lua. Regular players see nothing and no drawing thread runs for them: the display only turns on after the server confirmed the group of the requesting player. Being able to type the command is not enough.
/npcdevtoggles the display. It puts markers on herd anchors (green) and zone centers (red), and blips on the map for both. A solid marker means the herd stands or the zone is armed right now./npcposprints your own position as a ready to pastecoords = { ... }line into the F8 console.
Markers are only visible close by, the blips show the whole layout at once, which is what you want while placing. Each herd and zone also gets a radius blip: for a herd that circle is its roaming area and matches exactly, for a zone it sits inside the square box, so the corners are not covered. Switch the blips off with Config.Dev.blips.enabled if the map gets too busy, the markers stay.
Everything disappears again when you switch the helper off.
Markers, not polygons, on purpose: DrawPoly flickers and has already caused a client crash on this server.
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.
