Independent third-party developer. Not affiliated with, endorsed, sponsored or approved by Rockstar Games, Take-Two Interactive, Cfx.re or FiveM. GTA, Grand Theft Auto and FiveM are trademarks of their respective owners.

NPC & AI

Project Intel NPC — Talk to ANY Ped. They Remember. They Gossip. They Snitch.

v1.63.0

€50.00€120.00-58%

Original Lua/JavaScript resource developed independently by WebMerc. Requires FiveM (GTA V). No Rockstar game files are included.

  • Download from your account right after payment
  • Payment through Stripe
  • Full source code. No escrow, no license checks.
  • Free updates while the script is maintained

Independent third-party developer. Not affiliated with, endorsed, sponsored or approved by Rockstar Games, Take-Two Interactive, Cfx.re or FiveM. GTA, Grand Theft Auto and FiveM are trademarks of their respective owners.

Read it before you buy

These are the same files that come in the download, updated with every release.

Intel NPC — lastresort_intel_npc

Every NPC is a person. The city wakes up.

Intel NPC is a FiveM resource that turns GTA V's ambient crowds into a city of people: every pedestrian gets a category, a personality archetype, a session mood, a facial expression and a memory of what you did to them, plus a game-style dialogue panel with recursive topic trees, a witness / gossip / disguise / informant layer, and a calm-RP police module that answers a 911 with an officer who walks up and talks instead of opening fire. Every NPC line in this resource is authored content — nothing is generated at runtime, and no dialogue path contacts an external service. (The only outbound request the resource can make at all is the optional Discord webhook, which ships off and has to be turned on with your own URL.) Free-typed AI conversations are a separate, optional add-on — see AI conversations.

It is built for server owners who already have the map and the lore and want the streets to stop feeling empty — it runs on Qbox, qb-core, ESX Legacy and standalone with ox_lib as its only hard dependency, and every subsystem has a master toggle so you can ship all of it or just the parts you want.


Release specifications

Code is accessible Yes — every file, unencrypted and unobfuscated: the runtime Lua, the dialogue panel HTML, shared/config.lua (~3,100 commented lines), everything in data/, locales/*.lua, sql/*.sql, nui/theme.css and all *.md ship readable and editable.
Subscription-based No
Lines of code ~62,000 lines of Lua across 114 files — including ~13,700 lines of build-time generated reference data under data/generated/ — plus a ~4,400-line single-file NUI (markup, CSS and JS in one document).
Requirements FiveM server on fx_version 'cerulean'; ox_lib 3.0+ (the only hard dependency); OneSync on for the server-side robbery validation (it degrades to the pre-OneSync path when off). Optional and auto-detected: qbx_core / qb-core / es_extended, ox_inventory / qb-inventory, ox_target / qb-target, any notify resource, lastresort_chat for the speech bubbles and feed. Optional: oxmysql for the SQL persistence backend.
Support Yes — help@intelnpc.com, plus the Tebex ticket queue.

Performance

This resource owns zero permanent per-frame threads. A single frame dispatcher sleeps at Wait(500) while nothing is registered; the density writer, the 3D hail bubbles, the mood dots and the debug overlay each claim a frame slot only while they have something to draw and release it the moment they don't. Everything else is an interval loop: the reaction aim sampler at 300 ms, the hit scan at 500 ms (skipped entirely unless the player used force in the last 5 s), perception at 750 ms, hail attempts every 15 s, gossip every 45 s, persona routines every 30 s, and the city life layer's own loops at the same cadences — life points every 15 s, ambient car radios every 15 s, scenario-group and world-toggle re-asserts every 30 s, the street-scene scan every 20 s and a 120 s sweep of the paired-scene cooldown table. Config.dialogue_speech adds two more, and only while it is on: the speech poll at 150 ms for the duration of a spoken line, and a 30 s sweep of the per-ped voice cache that exists only while at least one tracked ped is alive. A live street scene runs one 200 ms chain and a paired interaction one 120 ms chain, both only for the seconds the scene is actually playing.

The numbers below are the ranges those workloads produce. They are not a marketing "0.00ms" — the resource does real work when NPCs are around, and what you measure depends on ped density, your ped-model tables and which modules you enabled.

State What is running Client (ms)
Idle Panel closed, no NPC within hail range, police module off (the shipped default). Interval loops only, frame registry empty. 0.01 – 0.03
Ambient Walking a busy street. Hail scanner, perception, reaction sampler and the expression sweep all seeing peds; mood dots off (shipped default). 0.03 – 0.09
Active Dialogue panel open: camera focus, typewriter, live trust/mood updates, NUI compositing. This is what "active" means for this resource — one conversation, not a busy server. 0.06 – 0.14
Police scene Police module enabled and a live 911: scene entities, the officer state machine, the ring timer and the wanted clamp watchdog. 0.10 – 0.22

The server side is event-driven — callbacks, rate limits and the once-per-second Discord drain — and sits at the bottom of profiler output between interactions.

Reproduce it yourself:

F8 → resmon 1

Watch the lastresort_intel_npc row. Stand still somewhere empty for the idle figure, walk Vinewood Boulevard or the Strawberry 24/7 for the ambient figure, press G on a ped for the active figure. resmon 2 breaks the number down per script file. For the server side, run profiler record 500 then profiler view in the server console.

If you want the cheapest possible install: Config.hails_enabled = false, Config.gossip_enabled = false, Config.perception_enabled = false and the police module off removes most of the ambient-state work — the dialogue, memory, reputation and target layers do not depend on any of them.


Table of contents

  1. What it is — the four pillars
  2. Framework support & bridges
  3. The ambient cast — six categories
  4. Personality archetypes — seven types
  5. Native ped behaviour
  6. The reputation system
  7. The economy loop — errands, bounties, trade
  8. Daily routines
  9. The dialogue system
  10. Adding a static persona
  11. The police system
  12. Traffic personality
  13. Vehicle collision
  14. The social intelligence layer
  15. Facial expressions
  16. NPC spawning model & density manager
  17. The city life layer
  18. Spoken dialogue — voice casting & speech sync 18a. AI conversations — optional add-on
  19. Language packs
  20. Persistence
  21. Discord logging
  22. Exports reference
  23. Server events reference
  24. Server callbacks reference
  25. Dev commands
  26. Compatibility
  27. Theming
  28. Licensing
  29. Support

What it is — the four pillars

Every FiveM server hits the same wall: hand-painted MLOs, years of lore — and a new player still walks past dozens of identical idle peds with the same blank stare and nothing to say. Meanwhile the police side breaks in the opposite direction: native dispatch guns players down for jaywalking, and disabling it leaves no cops at all. Intel NPC is the answer to both, in one resource.

1 — Every NPC is a person

Random ambient peds and scripted characters alike get a category, a personality archetype, a session mood, a facial expression and a persistent memory of every interaction with the player.

  • 6 categories + 7 personality archetypes
  • Per-NPC memory: met-before status, topics asked, observed states, last exit mood
  • Action memory — they remember the crime they saw you commit

2 — Recursive dialogue trees

A dialogue scene, not a chat window: a name plate, a script-style log of who said what, numbered replies and an action row, with topic trees that recurse to any depth, a mood word and a live trust meter, driven by the authored dialogue in data/dialogue_lines.lua.

  • Conditional responses by world & player state
  • post_action: buy item, open shop, fire events
  • Topic randomization keeps revisits fresh

3 — The calm-RP police system (alpha, ships disabled)

Hit a car, the driver reacts by personality. Pay damages, bribe, apologize or fight. A scripted RP cop unit drives in calmly — no native dispatch gunning you down on sight. Honest disclosure: this module is in a strongly alpha state, ships disabled by default, and is scheduled for a rewrite — see The police system.

4 — The social intelligence layer

A witness, gossip, disguise, alibi, trauma, composure and informant network that gives your wanted system real consequence — and lets detective RP actually function.


Framework support & bridges

Everything framework-specific goes through auto-detected bridges — no manual configuration on a standard stack:

Surface Auto-detected backends (in priority order)
Framework qbx_core → qb-core → es_extended → standalone
Inventory ox_inventory → qb-inventory / framework items → none
Notify lastresort_notify → okokNotify → mythic_notify → ox_lib → QBCore → ESX → native GTA feed
Target ox_target → qb-target → built-in fallback

Every value can be forced in Config.bridge (shared/config.lua) when auto-detection picks wrong on an exotic stack. The detected result is printed to both consoles at boot:

[lastresort_intel_npc bridge] server: framework=qb inventory=ox notify=okok

When the bridge re-detects at runtime (a backend resource starts or stops), the lastresort-intel-npc:bridgeChanged event fires with the new mapping.

Standalone mode

With no framework running, money-gated features use an in-memory session ledger (Config.bridge.standalone_start_cash, default $5,000) and item flows succeed without a backend (Config.bridge.standalone_items_succeed). Money and items do not persist across reconnects; NPC memory and reputation DO persist even in standalone (keyed on the license identifier).

The interaction menu without a target resource

When neither ox_target nor qb-target is running, the built-in fallback activates: look at an NPC within range and press E (rebindable in the GTA keybinds menu; default via Config.builtin_target.key) to open the same interaction menu as a context menu.


The ambient cast — six categories

Every category routes its own greeting pool, topic tree, refusal pool, voice profile and behavioral baseline — determined automatically by the ped's model. Not everyone wants to talk to you; each category rolls a refusal chance (cached per ped, so the same ped consistently refuses), and player state stacks on top: bloody +20%, wanted +25%, masked +20%, armed +15%.

Category Refusal Character
Civilian ~15% The default citizen — small talk, directions, the social fabric. Reacts sharply to a bloody or armed player; most likely to dial 911 when you act up.
Gang ~75% Short, hostile, territorial. "Wrong block." Per-gang identity from the ped model — hit one and his own set backs him up.
Cop ~85% "Move along, citizen." Won't usually chat — but a corruptible cop persona exposes a star-scaled "look the other way" bribe branch.
Homeless ~30% They call out first. Give them a smoke or a dollar and trust shifts instantly. Build it high enough and they drop street-level intel.
Prostitute — A dedicated category with its own hail scanner: pings far more often than any other cast (own 12 s scan loop, ~45 s per-ped cooldown vs several minutes for civilians) and gets a 1.4× hail boost at night. Four service tiers from "Just talk" to "Spend the night", each server-validated.
Default ~20% The quiet fallback for ped types the heuristics don't recognize. Shorter trees, minimal greetings, animals rejected entirely.

Hail behaviour (NPCs addressing YOU first) is per-category too, with per-category cooldowns and time-of-day multipliers (Config.hail_time_of_day_mul) so it never turns into a market bazaar.


Personality archetypes — seven types

Bump a coward with a bat in hand and they sprint. Bump a brave one and they tell you to put it away. Bump a cunning one — they smile, give directions, then call 911 the moment you turn your back.

Archetype Behaviour
Coward Bows to threats, runs from violence, panic-dials 911.
Compliant The average citizen — follows polite asks, won't pick a fight.
Brave Stands their ground, will talk while armed but tells you to holster.
Aggressive Squares up fast, draws first, zero patience.
Cunning Calm outside, computing inside — takes bribes, calls cops smiling.
Stoic Minimal reaction, walks from problems, won't be drawn into a fight.
Cop Overrides everything — won't street-fight, won't be bribed at the scene.

All tunable. Patience (Config.dialogue_patience.by_archetype), provoke threshold, fight curve, bribe price — one config table (data/character_archetypes.lua + shared/config.lua). Realism overrides sit on top: some compliant women call the cops instead of folding, a rare brave-leaning civilian squares up, some cowards just curse and walk off. A gang toughness sweep keeps gang peds from cowering at gunshots like vanilla civilians.


Native ped behaviour

The rule: engine-owned pedestrians keep GTA's own tuning. Peds this resource spawns are configured by this resource.

GTA already tunes every ambient pedestrian through its model's pedpersonality entry — how alert it is, how far it sees and hears, when it flinches, cowers, flees or fights back. Up to v1.15.3 this resource overwrote a great deal of that, including on peds the engine owns and this resource merely borrows for a conversation, and it never put the values back. Five independent "ignore the world" flags ended up stacked on peds that were supposed to read as ordinary population: blocking of non-temporary events, disable-shocking-events, disable bullet reactions, a wiped flee bitfield and alertness pinned to 0. A ped carrying all five cannot notice being punched.

Config.native_ped_behaviour is the master switch. Every key inside it is an override: false/nil keeps GTA's default, true re-enables the old tuning. Each key's annotation in shared/config.lua names the native default, so vanilla behaviour is always one line away.

Group Keys Applies to
Relationship assignment assign_relationship_group, assign_block_events, disable_shocking_events, clear_flee_attributes, brave_always_fight, brave_disable_bullet_reactions IntelNpcRelations_AssignToPed — in the shipped resource that is only ever called on peds we spawn (personas, life points, the dev demo ped); the keys are there for anyone calling it on a borrowed ped from their own code
Combat combat_disable_bullet_reactions any ped this resource tasks into a fight
Dialogue dialogue_hold, dialogue_flee_lock, dialogue_bullet_lock the ped you are talking to
Night routines routine_night, routine_night_alertness, routine_night_seeing_range, routine_night_hearing_range static personas at night
Static personas persona_block_events, persona_never_flee, persona_suffers_critical_hits, persona_disable_explosion_reactions peds we spawn
Life points life_point_block_events, life_point_never_flee, life_point_suffers_critical_hits, life_point_disable_explosion_reactions, life_point_alertness peds we spawn
Gang territories gang_territory_relationships engine peds created inside a Config.gang_territories box

Only two overrides still reach an engine-owned ped with the master switch on, and both are deliberate. /intel_native_behaviour prints brave_always_fight as APPLIED because its own value is true — but the call site behind it never runs on a borrowed ped, so no ambient pedestrian is touched by it either:

  • dialogue_hold — without it a random pedestrian walks away in the middle of the conversation you opened with him. It is a task hold, it is released on every exit path including hostile and panicked, and one line turns it off.
  • gang_territory_relationships — Config.gang_territories ships empty, so nothing happens until you author those boxes yourself. An authored table is a request, not unasked-for tuning. Set it false to bring it under the master switch too.

What an existing install will notice on upgrade

  1. Life points react like normal pedestrians. They flee, flinch, take critical hits and can become alert. For the old statue behaviour set life_point_block_events, life_point_never_flee and life_point_disable_explosion_reactions to true, life_point_suffers_critical_hits to false and life_point_alertness = 0.
  2. Config.block_temporary_events actually works now. It was silently overridden to true for every NPC by IntelNpcRelations_AssignToPed. An owner who set it false and saw no effect will now get the effect.
  3. Peds you talk to are handed back to the engine unchanged. Flee attributes and bullet reactions are no longer left permanently modified on random pedestrians after a conversation, and a witness that cowered is un-cowered after Config.perception_cower_ms instead of staying cower-flagged for life.

Extending the resource

Two globals back all of this: IntelNpcBehaviour_* (apply + snapshot + restore) and IntelNpcOwnedPedSet (the registry of peds this resource spawned). The rule for anyone writing add-on code against this resource is plain: *do not call SetPedCombatAttributes / SetPedFleeAttributes / SetPedAlertness / SetBlockingOfNonTemporaryEvents / SetPedConfigFlag directly on a ped — go through `IntelNpcBehaviour_, so the write is snapshotted and restored.** IntelNpcRelations_AssignToPed(ped, group_name, opts)takes an optional third argument ({ block_events = bool }); omitting it keeps the old two-argument behaviour and followsConfig.block_temporary_events`.

The honest limitation: GTA exposes no getter for combat attributes, flee attributes or seeing/hearing range, so those are restored to the documented native default rather than to a truly captured value, and a cleared flee bitfield cannot be restored at all. That is exactly why clear_flee_attributes and dialogue_flee_lock ship off.

/intel_native_behaviour (needs Config.debug) prints whether the config block is present, the switch state, the owned-ped and live-snapshot counts, and one line per override saying whether it is APPLIED or suppressed on ambient peds.


The reputation system

A single-digit −10 to +10 scale across six tiers, tracked per static persona and clamped at both ends. Casual conversation barely moves the meter; physical and material choices do.

Ambient pedestrians do not have reputation. A random stranger you will never see again is not someone you build a relationship with, and the store this table lives in is persisted to the database — an entry for a pedestrian who despawns in two minutes is a permanent row that can never match anything again. An ambient ped's standing lives on the ped itself (NpcState_AddRep), decays, and is gone when he is. Config.reputation_ambient_polite_close can grant a stranger the polite-close point, and even then it goes on the ped, never into the saved store.

Tiers: Hostile (below −4) · Wary (−4 to −2) · Neutral (−1 to 0) · Familiar (+1 to +3) · Friendly (+4 to +6) · Trusted (+7 to +10)

Action Delta
Help an NPC up +4
Bribe to look away +3
Tip on a purchase +2
Quest accepted +2
Flirt / banter / hire +1
New topic asked (chat) +1 (anti-farm guarded — re-asking grants nothing)
Beg −1
Provoke −2
Threaten −3
Pickpocket −3
Mug −5
Rob at gunpoint −6

Getting kicked out of a conversation costs reputation too (drawing a weapon mid-chat −3, provoking them into an attack −4). A polite close grants +1 on a 5-minute cooldown — but only to a named persona, and only up to Familiar; beyond that you need real actions. Reputation gates dialogue topics (min_reputation on any topic — keep the value inside −10..+10 or the topic can never unlock) and persists across reconnects and server restarts.


The economy loop — errands, bounties, trade

Trust is not just a number on a meter: it is what gets you work, what gets you a price, and what you burn when you take a contract on somebody.

NPC errands (data/jobs.lua)

Ask a persona "Got any work for me?" in the dialogue panel. They describe the errand, you take it or pass, and a tracker appears — HUD panel, map blip and a GPS route to the current step.

  • Authored, not coded. A job is a table: giver, trust requirement, time limit, reward and an ordered list of steps. Five step types ship: goto, scout (be somewhere and stay a while), deliver_item, fetch_item, talk_to. Add a job to data/jobs.lua and the persona starts offering it — no Lua anywhere else.
  • Server-validated end to end. Every step advance is re-checked against the server's own copy of the player: real position (a persona step accepts any of that persona's scheduled spots), real inventory through the item bridge, a dwell clock the server keeps. The payout is rolled server-side and clamped by Config.npc_jobs.max_reward_cash.
  • Survives a restart. The run lives in the character's persistence document (npc_jobs), so restarting the resource mid-errand keeps the step, the deadline and the reward.
  • Consequences. Taking one on earns a little trust; letting the deadline pass or abandoning it from /intel_jobs costs the job's fail_rep with the persona who trusted you with it.

Three errands ship: Maggie's deposit run, Pete's dockside manifest check and Denny's coffee round (the only one that touches items — turn item steps off with Config.npc_jobs.item_steps_enabled = false if your server has no matching item names).

Bounty board (data/bounties.lua)

Personas flagged bounty_broker = true (Erika and Denny ship flagged) take a price on another player from anyone who has earned Config.bounty_board.min_reputation with them. /intel_bounties reads the board from anywhere.

  • The stake is paid up front and held by the board, so a payout can never print money — the broker's cut (broker_fee) is the only thing that leaves the economy. An unclaimed bounty is refunded when it expires, even if the poster was offline when it lapsed.
  • Collected, not claimed. When the target drops, their own client reports who did it and the server checks that the killer is standing inside claim_radius of the body before it pays. One claim per bounty, ever — the settle is atomic on the server.
  • NPC contracts are the standing grudges in data/bounties.lua: a persona paying to have another persona leaned on. They settle off the already server-validated rob / mug flow, with a per-player faucet cooldown.

Trade and haggling (Config.trade)

Shop prices scale with what the persona behind the counter thinks of you — trusted pays 85%, hostile pays 125%, and a stranger pays exactly the catalog price the resource has always charged. The shelf label and the purchase callback compute the price from the same shared table, so the number you read is the number you pay.

Haggle sits at the top of the shop menu. The server rolls it against your trust: win and the counter runs cheaper for the next few minutes, fail and they shrug — but a bad roll (worse when they already dislike you) lands as an insult, costs trust and closes the haggling for insult_cooldown_ms. The discount is stored server-side for the session it was won for; a client cannot invent one.


Daily routines

A persona's schedule maps each time-of-day block (Config.time_of_day_bounds) to where they are and what they are doing:

schedule = {
    morning = { coords = vec4(...), scenario = 'WORLD_HUMAN_CLIPBOARD' },
    evening = { coords = vec4(...), scenario = 'WORLD_HUMAN_SMOKING',
                voice_line = 'routines.pete_night' },
    night   = { hidden = true },   -- off shift: not in the world at all
}
  • Cheap moves. When the new block leaves them within Config.routines.soft_relocate_distance of where they stand, the ped is re-tasked in place instead of being despawned and respawned — no flicker, no model reload. A real move still does the full relocate, and never while you are mid-conversation with them.
  • Off shift. hidden = true takes the persona out of the world for that block (Anna books off the pier at 22:00) and brings them back when it ends.
  • After dark. During the night block personas raise their alertness and stop ignoring what happens around them (Config.routines.night_alertness), and their opening mood skews with Config.routines.night_mood_bias — the same NPC reads warier at 3 a.m. than at noon. Dialogue trees can branch on it through the time_of_day_night tag they already receive.
  • Arrival lines. voice_line floats a line over their head as they take up the new spot.

Erika (Vinewood by day, club doors after dark), Denny, Pete, Anna and Maggie ship with schedules. Errand steps and shop proximity follow a persona to every one of their scheduled spots, so a routine never strands a hand-in.


The dialogue system

Walk up, press G (default, rebindable under FiveM > Talk to the NPC you are looking at — also /intel_talk) or E on the ped in front of you, and the dialogue panel opens: a name plate with the NPC's portrait initials, role, zone and mood, their body language, the trust, composure and patience meters, a script-style log (speaker, line, stage directions between), the numbered replies with their tone tags and, under them, the action row for the things you do rather than say (tip, bribe, flirt, threaten, the persona's shop or job).

  • Replies are bound to 1–9, ↑ ↓ and ⏎ pick one, SPACE skips the line, TAB moves to the action row, ESC walks away without a word
  • Conversations flow: a follow-up's siblings stay on the table after you pick one, a leaf answer drops you back on the topics instead of a "back / end" screen, a topic you already asked comes back as a way into its unanswered follow-ups, and re-asking an answered one gets the "I just told you" treatment and no standing. The NPC adds a short beat before some answers in their mood ("Hm.", "Ugh. Fine.") and, once per conversation, says something nobody asked for — the rain, the hour, the gun on your hip. Leaving through the leave line is a polite close with a farewell in their mood; ESC is walking off mid-sentence
  • Optionally the NPC speaks while the line types — a tone-matched GTA ambient bark in their own voice, with the typewriter's finish tied to the audio (Spoken dialogue; ships off)
  • Recursive topic trees to any depth, with conditional responses by world & player state (weather, clock, zone, armed, bloody, masked, wanted, time-of-day block)
  • post_action hooks: buy an item, open a shop catalog, fire server events, offer quests
  • Topic randomization + session moods keep revisits fresh; an impatience warning pulses before they walk off. Patience is per NPC and remembered (v1.62.0): each ped rolls its own total once — the base Config.dialogue_angry_close_threshold plus an offset for who they are (Config.dialogue_patience.by_archetype / by_personality: an aggressive type snaps at 3–7 questions, a gossip sits through 9–12) plus a random spread — and keeps it. What you burn carries over: walk off after six questions and come back a minute later, six are still gone; they come back one per recover_ms (2 min by default) of being left alone. A persona with an authored angry_close_threshold is never rolled
  • A camera focus locks the scene while you talk; the NPC halts, turns and holds a head-track on you for the whole conversation
  • Per-NPC memory (met before, topics asked, your name if you introduced yourself, last exit mood, their patience roll and how much of it you burnt) — static-persona memory survives restarts via server-side KVP snapshots, ambient-ped memory is keyed by a replicated state-bag uid so it survives re-streaming. Ambient peds record their asked topics too, from v1.61.0: before that the write was gated on a persona id an ambient ped does not have, so every random pedestrian offered the whole topic list again on the next conversation. Where there is no state bag the key falls back to the entity handle, and GTA recycles handles — the ped's model is stamped alongside it, so a handle that comes back on a different model starts clean instead of inheriting a stranger's memory
  • Ships with 10 hand-authored static personas (Maggie the 24/7 attendant, Erika the reporter, Pete, Joe, Tony, Doc, Denny, Anna, Sergeant Mahoney, Candy) — all declarative data in data/personas.lua + data/dialogue_lines.lua
  • Anti-repeat line picker. Every line, bubble, refusal and farewell is drawn from a pool, and until v1.16.0 the draw was a bare math.random with no memory — a 3-line pool repeated itself back-to-back a third of the time, which is what made large pools sound small. Each pool now keeps a short ring of the lines it just used and re-rolls while the pick is still in that ring. Config.line_no_repeat_depth (default 3) is how many recent lines a pool refuses to repeat; it is capped automatically at half the pool size, so tiny pools alternate instead of cycling in a predictable order. Config.line_no_repeat_tries (default 6) caps the re-rolls. Set line_no_repeat_depth = 0 for the old pure-random picking
  • Mood tags are live. Config.mood_pool (friendly · neutral · grumpy · distracted · curious) was rolled per session, shown on the panel and applied to the face — but not one of the 1108 ambient lines was written against it, so every civilian in the city spoke in one voice regardless of the mood badge. Mood now genuinely changes wording: a grumpy cop says "It's early, I'm undercaffeinated, and you're in my space", a friendly one "Morning. Genuinely, how can I help?"
  • Bigger, more varied stock content — ambient dialogue 1108 → 1768 lines, static personas 647 → 965, with mood, time-of-day, weather and memory variants across every greeting and farewell pool. Single-variant pools are down from 44 to 28: every greeting, farewell and response pool the player hears in normal conversation now has multiple variants, and the 28 that remain are rob/mug outcome and counter-question stubs. The thinnest ambient greeting pools went from 3 lines to 12+

When writing your own dialogue, the tags available to require / any / forbid are the player and world tags (armed, bloody, injured, masked, wanted, rainy, sunny, snowy, foggy, morning, day, evening, night), the mood tags above, the memory tags (first_time, met_recently, long_time_no_see, frequent_visitor, known_to_npc, unknown_to_npc) and the perception tags (saw_assault, saw_shooting, saw_robbery, saw_drug_use, saw_explosion, was_assaulted, plus the gossip-hop forms heard_saw_shooting and friends). Server owners adding custom personas should write one to three lines per mood in the pools the player hears most — greetings, farewells and the pinned topics.

The target menu on any ped carries one option: Talk. Money, threats, flirting, begging and the rest are offered inside the conversation, in the action row under the replies, where they are wired into reputation, perception and the witness network — a persona's own interactions (buy an item, browse the shop, take a job) appear there too. On the built-in look-at fallback E opens the conversation directly instead of a one-row menu.


Adding a static persona

Add an entry to Config.personas in data/personas.lua and (unless you set dynamic = true) a matching tree under the same dialogue_id in data/dialogue_lines.lua. Both files are plain Lua, yours to edit. Minimum viable persona:

{
    id          = 'andrew',                       -- unique; also the reputation/memory key
    name        = 'Andrew',
    model       = 'a_m_y_business_03',
    coords      = vec4(123.0, 456.0, 78.0, 90.0), -- x, y, z, heading
    scenario    = 'WORLD_HUMAN_SMOKING',          -- optional idle scenario
    dialogue_id = 'andrew',                       -- key in data/dialogue_lines.lua
    archetype   = 'compliant',                    -- one of the seven
    role        = 'a paralegal on his break',     -- one line, shown under the name
    interactions = {
        { type = 'dialogue', label = 'Talk to Andrew', icon = 'fa-solid fa-comments' },
    },
},

Optional fields worth knowing: schedule (per-time-of-day coords / scenario, keyed by the block names in Config.time_of_day_bounds), hail_lines (without them the persona never hails you while Config.hail_only_if_has_lines is true), gta_voice_name (falls back to Config.default_gta_voice_name; must be a real GTA ambient voice name — see Spoken dialogue), corruptible, informant, routingBucket, appearance (freemode component/prop/head-blend schema — Erika demonstrates all of it) and dynamic = true (route to the ambient tree for the ped's category instead of a hand-authored one — Candy demonstrates it).

The ped spawns the next time a player walks within Config.spawn_distance of the coords. The same schema works at runtime through the AddPersona export:

exports.lastresort_intel_npc:AddPersona({ id = 'andrew', name = 'Andrew', ... })
exports.lastresort_intel_npc:RemovePersona('andrew')

The police system — ⚠ ALPHA

Honest disclosure — read before enabling. The integrated police module (red-light / ped-hit / speeding triggers, scripted patrols, wanted handling) is in a strongly alpha state and is scheduled to be rewritten on different logic. It ships disabled by default (Config.police_system_enabled = false). "Off" does not strip the module — every runtime path checks the flag per tick, so you can flip it on LIVE from the F12 dev menu or the SetPoliceSystemEnabled export, no restart needed. Run it on a test server first. Every other layer — dialogue, memory, trust, witness, hail, disguise, gossip, trauma, alibi, persona, informant, corruption, routines — is independent of it.

The A–E flow

What this is not, any more. Up to v1.17.0 this module ran a scripted police RP scene: a pre-call talk-down window, a cruiser that drove in, an officer who walked over, a seven-choice cop dialogue. All of it was deleted. The module is native-dispatch-only: it decides when you should be wanted and hands that to GTA, which sends its own police. It spawns nothing.

A — Contact. client/collision.lua reads the engine rather than guessing: IS_ENTITY_TOUCHING_ENTITY for the hit itself and HAS_ENTITY_BEEN_DAMAGED_BY_ENTITY to confirm who did it, on foot and vehicle-to-vehicle. The victim reacts as a person through the ordinary reaction library — by archetype, exactly as any other provocation — whether or not anything comes of it legally.

B — Severity. The health drop and the relative speed put the hit in one of three tiers, light / moderate / severe (Config.police_ped_hit.tiers). Severity is the only thing that scales the consequence; there is no talk-down window to buy your way out of it.

C — Whether it gets reported. Each tier has its own report chance (report_chance, default 0.20 / 0.55 / 0.90). A missed roll is traced as hit_unreported and nothing further happens — no star, no record. Reporting is what makes you wanted, not the hit. When it is reported, a nearby ped places the call visibly: phone animation, an angry line, and a bystander who photographs you a few seconds later.

D — The engine answers. The crime goes to GTA's own dispatch through REPORT_CRIME where a crime type exists, and to a direct clamped raise where it does not. client/manhunt.lua gives the search a centre at the scene (SET_PLAYER_WANTED_CENTRE_POSITION) instead of the engine's default of your own position, and flags the car you did it in, so driving away from the scene and swapping vehicles both mean something. Every responding unit is GTA's.

E — What the police then do. At or below Config.police_nonlethal.max_stars (default 1) the non-lethal band holds fire: cops close in, try to arrest, and their firearms are physically taken and handed back on release. Above it they behave exactly as vanilla. A corruptible cop persona can still be bribed through the ordinary dialogue tree — priced, rolled and paid server-side, against a ped the server verifies is a real engine-dispatched officer and not your own. Server-side cash flows are validated, rate-limited, range-clamped and cooldown-gated.

Also in the module

  • Red-light running in front of a cop and speeding past one — both wanted-roll triggers with cooldowns. The junction and its lights come from the engine: GET_VEHICLE_NODE_PROPERTIES carries R*'s own eVehicleNodeProperties, and this reads TRAFFIC_LIGHT (1 << 8) rather than matching prop models, so it works on any map without a hash list. (extra_light_hashes was documented here and exists nowhere in the resource.)
  • Cop presence for those detectors is GTA's own. This module spawns no patrols of any kind. If your map is too quiet for the red-light and speeding checks to fire, raise the engine's ambient police through Config.engine_police — there is no fleet here to turn up
  • Native dispatch is the response. A star raised by this module is answered by GTA's own police — no scripted responder, no scripted arrest scene. IsNativeDispatchInCharge() is always true, and client/ai_suppression.lua reads it to stand down so the engine's cops are let through
  • Star ceiling: Config.police_max_wanted_stars (default 2) caps the stars this module asks for. It is not a cap on the player: a higher level set by the engine or another resource is noted in the trace and left alone, so there is no clamp to disable when an external wanted system runs alongside
  • Star-scaled corruption bribes on corruptible cop personas ($250 at 1 star up to $100,000 at 5 — increasingly likely to be refused)
  • No lethal force below two stars (Config.police_nonlethal, on by default): while the player's wanted level is at or below max_stars (default 1, so zero and one star are both covered and lethal force begins at two stars), police do not shoot. Every cop within radius (default 120 m) is forced to WEAPON_UNARMED with weapon switching blocked, accuracy 0, CA_ALWAYS_FIGHT cleared, CA_CAN_BUST set and TASK_ARREST_PED issued at close range; with disarm on (the default) the firearm is physically taken, because holstering alone lets the combat AI re-draw it. What the officer OWNS is read with GET_BEST_PED_WEAPON, not GET_CURRENT_PED_WEAPON — a patrolling cop's pistol is holstered, so the equipped-weapon reading recorded nothing and release handed nothing back. This is a band, not a pacifier — above max_stars police behave exactly as before, and a cop you shoot or ram drops out of the band immediately and fights back at full strength, at any wanted level (self_defence, default true). Accuracy and the recorded weapon are snapshotted per cop and handed straight back with restore_ammo rounds; the combat attributes have no getter in GTA, so they are returned to the engine's own defaults. The band also always yields while IsNativeDispatchInCharge() is true. It spawns nothing, says nothing and takes no bribes. The full key list is the seven in Config.police_nonlethal — enabled, max_stars, radius, disarm, restore_ammo, self_defence, interval_ms — and nothing else; earlier builds of this page described dispatch-service switching, a SET_WANTED_LEVEL_DIFFICULTY dial, require_police_system and yield_to_resources here, and none of the four was ever implemented in the band. Live state: /intel_nonlethal
  • Optional ped durability floor while you drive (Config.police_ped_hit.protect_nearby_peds, off by default): a minor bump knocks pedestrians down instead of killing them. While it is on, every civilian within the radius of your car is temporarily tougher and immune to critical hits; the boost is snapshotted and handed back when they leave the radius, you leave the driver's seat, you disable it or the resource stops. It changes combat feel near roads, which is why it is opt-in.

Traffic personality

Config.traffic_personality — ships on. Vanilla ambient traffic is one driver repeated: same aggression, same ability, same style. Every car within radius (60 m) gets a personality drawn from the same seven archetypes the pedestrians use, so the coward who brakes for you and the aggressive one who leans on the horn are the characters you already meet on foot.

It is all engine. Three natives do the whole job and nothing polls a car afterwards:

Native What it sets
SET_DRIVE_TASK_DRIVING_STYLE the eVehicleDrivingFlags composition — stops at lights, swerves, overtakes
SET_DRIVER_AGGRESSIVENESS 0.0–1.0, how hard they push
SET_DRIVER_ABILITY 0.0–1.0, how well they actually drive

The style values come from data/generated/driving_styles.lua, built at build time from the game's own flag table — not hand-written constants.

Archetype On the road
coward calm, gives way, almost never sounds the horn
compliant obeys lights, occasional horn
stoic normal, ignores you
cunning rushed. Does not fight — notes you down: a hit from you enters the ordinary witness pipeline, memory and gossip
brave rushed, horn nearly half the time
cop obeys lights, highest ability
aggressive reckless, horn almost always, swerves at you

Driving into a car reaches its driver: client/collision.lua hands the contact over and the archetype decides the reaction, rate-limited per vehicle.

TASK_VEHICLE_TEMP_ACTION cannot be applied to remote clones, so the swerve and brake reactions request network control first and are skipped when it is not granted. Mission vehicles are left alone, and cycles, boats, helicopters, planes and trains are not treated as street traffic.

Honest limit: none of the three natives has a getter, so releasing a car writes the engine's own defaults back rather than whatever it had before. For ambient traffic that is the same thing; for a car another resource had tuned it is not, which is why mission vehicles are skipped.

Live state is on the Police tab of the F12 panel; /intel_traffic prints the same thing.


Vehicle collision

Config.vehicle_collision — ships on. Two complaints this answers, and they need different levers:

"a light tap kills them." GTA applies a critical-damage multiplier to vehicle impacts, so a 15 km/h clip can be fatal to a pedestrian, and the driver of a car you barely touch can die in the crash. Below lethal_kmh (35) the victim is floored at survive_floor health (110) instead of dying, and the critical multiplier is removed from anyone near your moving vehicle. That softening is borrowed: it is handed back the moment they leave the radius, you stop driving, or the resource stops. Above lethal_kmh nothing is floored — hit someone at 60 km/h and they die, as they should.

"they do not react when I drive into them, even repeatedly." Every reaction path in this resource hung off a health drop, so a damage-free nudge produced nothing at all — you could push a pedestrian down the street and he would never once look at you. Contact is now read from IS_ENTITY_TOUCHING_ENTITY, independently of damage. And for a ped sitting in a car, contact is read between your vehicle and theirs: a driver you rear-end is not touching your car, the two cars are, which is why traffic never reacted to being nudged.

The tier is decided by your speed at the moment of contact:

speed tier what happens
under nudge_kmh (8) nudge annoyed — turns, curses, leans on the horn
under bump_kmh (25) bump angry — provoked through the ordinary path, so the archetype decides whether they square up or back off
bump_kmh and up hit the victim reacts as a struck person

The wanted level is not part of this. A nudge and a bump never make you wanted. A hit is reported — or not — by client/police.lua, and only when Config.police_system_enabled is on. The victim's reaction fires either way, which is the point: with the police module off (its default) being run over used to produce nothing whatsoever.

same_ped_cooldown_ms (1200) is the minimum gap between two reactions from the same victim, so driving into someone repeatedly gets a reaction each time without stacking them on one another.

Live state and the tier table are on the Police tab of the F12 panel, and /intel_collision prints the same thing to the console.

Turning enabled = false restores vanilla GTA collision exactly.


The social intelligence layer

  • Perception & witnesses — gunshots, explosions, fights and drug use are noticed within audible/visual range; each witness gets a reliability tier — HIGH (cop, business, clean civilian), MEDIUM (most), LOW (drunk, homeless, traumatised) — degraded by distance, and forwarded to whatever wanted system you run via witnessReported. Drug use is detected from the animation clips listed in Config.perception_drug_anims, so it works with any drug resource without depending on one.
  • Cast-aware witness reactions — hit a gang member in view of his own set and the set attacks (per-gang identity from Config.gang_model_gangs — Ballas back Ballas, not Vagos); gang members watching a civilian get hit stay calm and mock; cops go warning → draw; civilians flee or call per archetype; the homeless scatter.
  • Composure — every NPC carries a 0–100 score (calm / uneasy / tense / agitated / furious) fed by provocations, witnessed violence and fear; it drives dialogue flavour, refusal odds and the point where an enraged NPC stops talking and acts.
  • The victim of an assault is not a witness of it. Punch a ped and the resource resolves who you actually hit (GET_MELEE_TARGET_FOR_PED, a damage read and a forward-cone fallback that only fires while you are genuinely swinging), excludes him from the witness sweep, and gives him his own was_assaulted memory tag — a 6 h, gossip-worthy event, distinct from a bystander's saw_assault, usable as a dialogue tag exactly like the saw_* tags. He then gets his own archetype's on_hit reaction: a gang member fights back, a coward flees, a cop moves to arrest. The reaction no longer depends on the ped taking damage, so damage-proofed ambient peds react too.
  • Gossip — NPCs near each other share memories on a slow tick; each hop degrades the detail (100 → 80 → 60 → 40 → 20%). Only gossip-worthy events propagate.
  • Disguise scoring — helmet, mask, hood, sunglasses and clothing changes stack to a 0–100 score; above 65 the NPC treats you as a stranger, impairing memory recall and witness identification quality.
  • Alibi windows — personas you talked to can vouch for you for 30 minutes; the memory module records every dialogue session's time window for external systems to query.
  • Informants — flagged personas silently leak crimes committed in their sight to the police (informantReported), unless your disguise score beats the informant threshold.
  • Trauma — violence leaves marks: shaken (refusal +20%), traumatised (refusal +50%, hostile to strangers), broken (refuses dialogue entirely) — decaying over time.

Facial expressions

Config.expressions fixes the all-neutral GTA face using the engine's facial-idle override with vanilla mood clips — no anim loading, works on every human ped:

  • Archetype/persona baselines for static NPCs (persona.expression overrides per NPC)
  • Session-mood faces during dialogue — a mood swing shows on the face instantly
  • Stressed/angry overrides for panicked and hostile states
  • A weighted ambient sweep gives nearby game-spawned peds persistent random moods, with per-archetype pools, so gang models never smile and cops read stern

NPC spawning model & density manager

  • Ambient NPCs are the game's job. Ambient population density is shaped per zone via Config.density_manager (dense downtown, sparse Blaine County), a population intercept retags engine-spawned peds at creation time, and every behaviour system attaches to the engine's own peds by entity handle. The density natives are written from exactly one place, and only while the multiplier actually differs from the engine default — a zone mapped to 1.0 costs nothing.
  • Static personas (data/personas.lua) are the only ambient-tier peds the resource spawns, with optional time-of-day schedules (schedule + Config.time_of_day_bounds), scenario-blocking areas so R* peds don't spawn on top of them, and an optional server-authoritative spawn mode (Config.use_server_spawn).
  • The resource still spawns a few purpose-built entities on demand: the 911-response cop unit + vehicle and the optional courier / parked-lookout helpers (client/vehicle_npc.lua). These are scripted set-pieces, not ambient crowd.

The city life layer

Five modules that answer the same complaint from a different angle than the dialogue system does: the streets are not empty because the peds have nothing to say, they are empty because nobody is doing anything. Life points ship on since v1.11.0; the other four ship off. Turn those on one at a time and watch resmon — the cost is not the Lua (all five are interval loops at the resource's existing cadences, zero permanent per-frame threads), it is the extra peds and vehicles you are asking the engine to simulate.

Life points — Config.life_points

Declarative "extras casting". data/life_points.lua places ambient scenario peds: sunbathers on Vespucci sand, tourists with a map at Legion Square, a corner on Grove Street, welders at the LS Port, smokers outside Bahama Mamas — 16 authored entries across five presets. A second form reads the world's own furniture out of data/generated/world_points.lua and puts someone at the ATM, on the bench, at the parking meter, at the bus stop — six more entries, 22 in all across ten presets.

The last two of those are the red_light_district preset: night-only working girls standing on the real bus-stop pavement points in Strawberry, Davis, Mission Row, Textile City, Cypress Flats, La Mesa and Banning, plus a higher-class scenario in West Vinewood, Rockford Hills, Vespucci Canals and Del Perro. This is what makes the prostitute hail lines and the street-service flow reachable — the scanner and the lines have shipped for releases, but the vanilla population almost never spawns anyone for them to key off. Mechanics only, no new adult content, and presets_enabled.red_light_district = false removes the whole thing. The bus-stop waiters are day-only so the kerb is free at night.

These are not personas: no dialogue tree, no persistence, no blip. What they do get is a category tag, which is the one thing hail, perception, gossip and trauma actually key off — so a life point joins the existing simulation instead of standing next to it. A gang-preset ped gets hailed like a gang ped and remembered like one.

  • Hard max_active ceiling across every preset — this is the performance budget.
  • Spawn/despawn hysteresis (80 m / 140 m), a minimum gap to the player and to other peds, and per-zone counts scaled by Config.density_manager.by_zone.
  • Four independent release paths: a teleport jump larger than teleport_release_distance, ordinary distance cull, the ox_lib proximity point's exit, and player death / respawn. Every ped is deleted cleanly on resource stop.
  • Every scenario name is validated at boot against the generated scenario table, every model against IsModelInCdimage / IsModelValid. An entry that does not resolve is dropped with a console warning naming it, never silently. IsScenarioTypeEnabled is a separate, per-spawn check: a scenario type disabled globally (see Config.disabled_scenario_types) is not dropped, it just rotates the entry's pool at spawn time and shows up as the skips: ... disabled-type counter in /intel_status.
  • Per-preset switches under presets_enabled, and a furniture.enabled master for the four furniture_* presets. That master does not reach the red_light_district preset, which answers only to its own switch.
  • retask_enabled (on by default): because life points sit inside the simulation rather than beside it, the reaction layers clear their tasks when they turn a bystander to face you. Each scan puts a ped that has fallen out of its scenario back into it, so a preset survives the first player interaction. Peds that are fleeing, in combat, hostile or ragdolled are left alone.
  • Optional routine_seat_warp: when a static persona's scheduled spot lands within a few metres of a real seat, they sit on it instead of playing a standing animation beside it.

Note on the Vespucci busker: Config.disabled_scenario_types ships with 'WORLD_HUMAN_MUSICIAN' disabled (it caused street musicians to stack on a docks intel drop). The boardwalk-performer entry detects that at spawn and rotates to its other scenarios, so it runs in a degraded state until you remove that line. That trade-off is yours to make.

Dormant scenario groups — Config.scenario_groups

Rockstar ships a lot of hand-placed ambient content switched off in a stock session: the blimp, the airliners over LSIA, the cinema queues, the bank ATM crowds, the Ammu-Nation clerk, the impound cops. Each is a named scenario group the engine already knows how to populate — nothing spawns here, the module only flips the switch, which makes it the cheapest ambience in the resource.

Every name is validated twice, against data/generated/scenario_groups.lua (223 generated names) and against DoesScenarioGroupExist on the running build; an unknown or build-missing name is skipped with one console warning. Capitalisation does not matter. Each group's original state is recorded before it is touched and restored on resource stop, so a /restart never leaves the world changed. The config ships a commented catalogue of the ~32 groups whose effect is actually documented, with the armed/security ones called out separately — those peds react to you.

World ambience — Config.world_ambience (ships ON)

  • Ambient car radios. Picks a few of the traffic cars around you, switches the radio on, tunes it to a real station from data/generated/radio_stations.lua and makes it audible from outside. Never touches your vehicle (or the one you just left), another player's vehicle, a mission entity, anything this resource created, or a car carrying a persona. Each vehicle's prior radio state is recorded and restored when it leaves range or the resource stops. Capped by max_vehicles, faded in by max_per_scan. One honest limitation: the game exposes no getter for "is this radio loud", so release always clears the loud flag.
  • Ambient boats and garbage-truck rounds — two world flags stock sessions often come up with off, re-asserted every 30 s against engine resets and handed back on stop.

Street scenes — Config.street_scenes (ships ON)

Authored two- and three-ped conversations that play out on the pavement between peds that were already standing there. Nothing here spawns anyone: the module borrows peds out of the live population, plays real GTA ambient speech through their own voices, puts the resource's normal bubbles over their heads, and hands every borrowed ped straight back to the ambient AI.

Seven scripts ship in data/street_scenes.lua, in the same commented style as data/life_points.lua and with the full entry schema documented in its header: corner gossip, a street argument, a lost tourist, an evening corner deal, a three-hander shift change, a panhandler, and two officers killing time.

  • It is a scene, not a spawn. A scan every 20 s looks for a cast in the population around you between min_player_distance and max_player_distance, with every other role inside cast_max_gap of the anchor. No cast, no scene — the module never forces the world to fit the script.
  • Speech and bubbles never overlap. One line at a time, one speaker at a time. The next line cannot start until the previous bubble has expired, and a line that produced no audio (the voice has no take for that context) holds for a length derived from its own text instead of stalling.
  • Real memory, real gossip. A line marked gossip_payload hops the speaker's newest gossip-worthy memory into the rest of the cast for real, through the same NpcState_AbsorbGossip path the gossip loop uses — hop count and confidence decay included. A line with a bubble_seen variant switches to it when the cast actually remembers you.
  • You can cut in. Inside the entry's interject.radius you get a prompt (help box, floating text3d, or none); taking it ends the scene, restores the whole cast and opens the normal dialogue panel on the nearest speaker.
  • Every abort path restores. Damage, death, despawn, gunfire, panic, hostility, fleeing, ragdoll, combat, entering a vehicle, a cast ped wandering off its leash, your death, you teleporting away, the config being flipped off mid-scene, or the resource stopping — all route through one teardown that clears tasks, clears the blocking flag, clears busy and hands every ped back.
  • Client-local. A scene is a cosmetic on your own copy of the population. No event, no state bag, no server call.

Every threshold, cadence, distance, cooldown and chance is exposed in Config.street_scenes, and presets_enabled switches whole groups (street_talk, corner, cops) off in one line.

Paired interactions — Config.paired_scenes (ships ON)

Trust made physical. A greeting from someone who trusts you becomes an actual handshake, a familiar gang contact gets a dap, and a give / bribe / tip becomes a real hand-over: the NPC walks into the slot in front of you and both of you play the two halves of one synchronized animation. A hug ships as flavour, disabled.

  • It is a cosmetic and nothing else. It never gates, blocks or changes an interaction outcome. Every failure path — the NPC cannot reach the slot, the ground is not flat, you turned away, the anim dictionary would not stream, the ped got scared — silently degrades to exactly the behaviour you have today: the dialogue panel opens either way, after the handshake when there was one and immediately when there was not. A half-played handshake looks worse than no handshake.
  • Trust is the gate. Static personas go through their reputation tier; ambient peds through the per-ped rep, with the (deliberately low) bands in ambient_tier_thresholds. The handshake needs trusted, the dap familiar plus the gang category, the hand-over only neutral.
  • Where it fires. Opening an ambient dialogue tries the dap and then the handshake before the panel — the handshake is the greeting. Giving an item, bribing or tipping fires the hand-over. All three are no-ops while the module is off.
  • Initiator-only by default. networked = false uses the local synchronized-scene family, so only the player who triggered it sees the two peds animate: no network traffic and no way to desync anyone else's view of the ped. Flip networked = true to widen it.
  • Both peds are handed back. Tasks cleared, look cleared, keep-task off, blocking flag off, busy cleared. A ped that was in a scenario is left for the engine to put back, a persona pulled off its authored spot is walked back to it, everything else goes to the wander AI. player_in_scene = false keeps your own ped out of it entirely.
  • The animation pairs are validated at build time. Every dict / clip_a / clip_b shipped in the config — including the fist-bump alternative the comments document as a one-line swap — is checked against the reference anim data and listed in data/generated/anims.lua. A typo fails the build rather than shipping a T-pose.

It never runs on its own. Something has to ask for it: IntelNpcPaired_Try(ped, kind, opts, on_done), TriggerEvent('lastresort-intel-npc:client:pairedScene', ped, kind, opts), or /intel_paired <kind> with Config.debug on. It also refuses outright while the dialogue panel's focus camera is up — that path freezes the player, which a synchronized scene cannot survive.

The generated reference data

data/generated/ holds committed, human-readable Lua tables — scenario names and their kinds, scenario group names, radio stations, driving styles, ambient voice/speech-context coverage, and world prop coordinates pre-bucketed by zone. They are build-time reference data shipped with the resource and load client-side only. Nothing is fetched, downloaded or parsed at runtime. Server owners never build them; tools/ is developer-only and nothing in it runs at runtime.

Dev commands

/intel_life_reload, /intel_scenario_groups, /intel_ambience (and /intel_ambience release), /intel_scene_play and /intel_paired, all gated on Config.debug — see Dev commands. /intel_status grows a counter block for each module.


Spoken dialogue — voice casting & speech sync

Two halves of one feature: making sure every NPC has a voice that actually exists, and — optionally — making them audibly say the line the dialogue panel is typing.

Voice casting (always on)

data/generated/voice_contexts.lua is generated reference data shipped with the resource. It records which GTA ambient voice names are real, and which ambient speech contexts each of them has a take for. Nothing is fetched or parsed at runtime — it is a plain Lua table loaded client-side.

What comes out of that:

  • Every persona's gta_voice_name is validated at boot. A name GTA does not have makes SetAmbientVoiceName a silent no-op, which leaves the NPC mute forever with nothing in the console to explain it. Now you get one clear warning naming the persona and the bad value, and the persona falls back to Config.default_gta_voice_name instead of being silently wrong. Config.default_gta_voice_name itself is checked the same way. Turn the check off with Config.validate_persona_voices = false.
  • A persona keeps its model's own voice. Config.default_gta_voice_name used to be handed to every persona that declared none — nine of the ten shipped, Sergeant Mahoney among them — so models that already owned a correct voice (s_m_y_cop_01, s_m_m_doctor_01, a_m_m_genfat_01…) had it overwritten with an ambient Beverly Hills one. A model's own voice is always right in sex and character, because the game picked it to match the model, so it is now left alone. The default is offered only to models that genuinely have none — freemode and player models, which ship with no pedvoicegroup at all and would otherwise be mute — and it is sex-checked against the ped first, so a voiceless female model gets a female voice. Add your own addon models to Config.voiceless_persona_models; set Config.force_persona_default_voice = true to put the old blanket override back.
  • A declared voice of the wrong sex is refused. A persona naming a female voice on a male model is a config mistake, not an instruction: it is dropped with one console warning and the model keeps its own voice.
  • Per-context fallback casting, split by sex. No single voice in the game covers every context this resource uses, so there is no such thing as one "does everything" fallback voice. Instead each context has its own small pool of voices known to have a take for it, preferring ordinary ambient civilian voices. Each pool exists three times over — _by_context_m, _by_context_f and _by_context_x for names that encode no sex — and the pool is chosen with IsPedMale, falling back _m/_f → _x → the unsexed pool. That split is the whole point of the table: the pools are capped at 24 names and sorted alphabetically, so a single mixed pool filled up with A_F_* names before it ever reached A_M_*, and every man who needed a fallback line got a woman's voice.
  • The fallback is latched to the ped, not to the line. The first substitute voice cast for an NPC is remembered and reused for every following line it has a take for, so one NPC does not say hello as one person and goodbye as another. It is only re-cast when the remembered voice has no take at all for the next context, and the replacement comes from the same sexed pool.

Exposed to other modules as IntelNpcVoice_ForContext(ped, context), which answers supported, fallbackVoice: true, nil means play it in the ped's own voice, false, 'SOME_VOICE' means cast that voice for the line, and false, nil means nobody can say it — stay silent. The answer is cached per ped and dropped when the ped is gone.

Speech-synced typewriter (Config.dialogue_speech, ships ON)

With Config.dialogue_speech.enabled = true, every line the panel types is paired with a GTA ambient bark:

  1. The line's tone (greet, farewell, kick, bribe, threaten, investigate … the same tone keys the panel colours its choice chips with) is mapped to a speech context through tone_contexts.
  2. Voice casting above decides whether that plays in the NPC's own voice or a cast fallback. If neither works the line just types silently, exactly like today.
  3. The panel tells the client the moment the typewriter starts, so the audio and the text begin together rather than the audio leading by the "…typing" pause.
  4. While the line speaks, the client polls IsAmbientSpeechPlaying on an interval — never a per-frame loop, and the timer only exists while a line is actually speaking. When the audio stops, the typewriter completes.

It can never hang. The written line's own typingSpeed is the floor: audio only ever makes the text hold on its last character or drain faster to catch up, never type slower. The panel keeps its own hard deadline (max_wait_ms) independent of the client, so a bark that never starts — very common, since a voice with no take for a context fails silently — or one that runs long still completes the line. SPACE and ESC both cut the audio.

A note on the audio. The written line is yours and translated; the audio is a canned English pedestrian bark with no relation to the text. On an immersive or non-English server that mismatch reads worse than silence, so it is opt-in — flip it on and listen before you ship it. While it is off, nothing in the module runs: no timers, no natives, no extra payload to the panel — the per-ped voice cache is not even populated, so its sweep never arms. Config.validate_persona_voices is independent of this toggle and still runs.

Key Default What it does
enabled false Master toggle.
sync_typewriter true Tie the typewriter's completion to the audio. false = play the bark, don't couple it.
poll_interval_ms 150 How often the client asks whether the line is still playing.
max_wait_ms 6000 Hard ceiling on waiting for audio. The panel enforces its own copy of this too.
start_grace_ms 500 How long to give the engine to start the line before deciding no audio is coming.
catch_up_ms 6 Per-character delay used to drain the rest of the text when the audio finishes first.
speech_param SPEECH_PARAMS_FORCE_NORMAL_CLEAR SPEECH_PARAMS_* string for panel lines.
fallback_for_unknown_voice true Ambient peds were never assigned a voice by this resource, so cast a known-good one for the context.
fallback_for_persona_voice true Cast a fallback when the persona's own voice has no take for the context.
cache_sweep_interval_ms 30000 Housekeeping interval for the per-ped support cache.
tone_contexts 30+ entries Line tone → speech context.
default_context CHAT_STATE Used for a tone that is not in the table. false = stay silent on unmapped tones.

While a panel line owns an NPC, the older per-pool speech (Config.speech_on_dialogue_open, speech_on_topic_select, speech_on_farewell) is suppressed for that NPC so the two cannot talk over each other. Those flags still drive every speech outside the panel.

Dev command

/intel_voice <context|tone> (needs Config.debug) reports, for the ped you are looking at: the voice this resource assigned it and whether that name is real, whether the context is one of the checkable ones, whether the ped's own voice has a take for it, the fallback voice that would be cast, and the exact native call that would be made. /intel_status grows a counter block — validation results, per-ped cache hit rate, own-voice vs fallback vs degraded answers, and how the last spoken lines ended (natural, never started, timed out, skipped).


AI conversations — optional add-on

With the separate resource lastresort_intel_npc_ai running and its owner's OpenAI key set, the conversation panel becomes free talk: a text field above the replies, three reply suggestions written for the moment, and an NPC whose face, voice line, gesture, lies, trust, deals, secrets, rumours and closing reaction are directed by the model every turn. The request is made by the add-on, from your server, with your key; this resource only hands it the situation and acts on the validated answer. Without the add-on nothing here changes.

What stays in this resource's hands, whatever the model says:

  • Reactions come from the existing set and only the ones the NPC's archetype allows.
  • Trust is applied by the server and capped by Config.ai_bridge.trust.
  • Money and goods move only when the player presses Accept on an offer, standing at the NPC, inside Config.ai_bridge.deals' price band.
  • Secrets are offered to the model only when your stored reputation meets their min_trust, and the model sees only the teaser: the text and the waypoint go from this server to the player.

Config.ai_bridge chooses which NPCs use it (personas, categories), and a persona can carry an ai table (lore, speech style, look, accent, language, voice, no_violence, secrets) or ai = false. The add-on's own README covers the model, voices, memory, limits and console commands.


Language packs

Every player-facing line is in locales/. Config.locale picks the pack, and ships as 'auto':

-- shared/config.lua
Config.locale = 'auto'   -- 'auto' | 'en' | 'hu' | …; shipped packs: 'en', 'hu'

Config.locale = 'auto' resolves once at start, on the server and on each client, and takes the first source that names a language with a pack in locales/:

  1. the lr:locale convar — one line in server.cfg sets every LastResort resource at once: setr lr:locale hu
  2. the locale, ox:locale, esx:locale and txAdmin-locale convars, in that order
  3. on the client only, the player's own GTA language setting
  4. en

Codes are normalised before matching (hu_HU → hu, en-US → en, pt_BR → pt-br), and a code with no pack is skipped rather than failing. The server console names the result once at boot — [lastresort_intel_npc] locale: hu (lr:locale convar) / [lastresort_intel_npc] locale: en (default); the client prints nothing. Use setr, not set, for lr:locale: a client resolves its own copy, a set convar never reaches it, and the client would fall through to the player's game language while the server stayed on its own result. An explicit code such as 'hu' skips detection entirely; an explicit code with no pack falls back to English as before.

locales/en.lua holds the complete key set and is always the fallback, so a pack only has to carry the keys it changes. To add a language, copy locales/en.lua to locales/xx.lua, rename the Locales['en'] table to Locales['xx'], translate the values (never the keys) and point Config.locale at it — the locales/*.lua glob picks the file up on restart.

What lives there: menu entries, the dialogue panel's own labels, ambient speaker names, mood and trust-tier names, the NPC kick lines, per-category hails and refusals, the ambient gossip / bodycam / reaction pools, the news headlines and the full police module (pre-call driver, mid-call driver and on-scene officer scenes, every notification). The dialogue panel itself carries no English: client/nui_dialogue.lua pushes the whole nui.* label set into the NUI open payload and the page writes it into its markup.

Two rules for translators. %s / %d placeholders have to survive in the same order — locales/hu.lua is a working example of all of them. And a pool entry shaped { text = '...', tags = { require = { 'armed' } } } keeps its tags untouched; only text is translated.

Line pools that used to live in shared/config.lua (Config.kick_lines, Config.hail_lines_by_category, Config.default_refusals, Config.police_ped_hit.pre_call_lines) still work: a pool filled in there overrides the locale pack for that one key, so a customised config keeps its own lines.

refusal.prostitute is new in v1.16.0. A custom pack missing it is safe — the English pack fills in, and before that the code falls back to refusal.default — but a translated pack should add it. The other five refusal.* pools grew; existing lines were kept verbatim, so a pack that overrides them keeps working.

Persona dialogue trees in data/ are content, not UI, and stay where they are — they are per-character writing, and every string in them is already open for editing.


Persistence

NPC memory, reputation, quest state and bribe history are per character. Config.persistence.backend chooses where they live:

Config.persistence = {
    backend      = 'kvp',                     -- 'kvp' | 'sql'
    table_prefix = 'lastresort_intel_npc_',
    flush_debounce_ms = 8000,
}
  • kvp (default) — FiveM's own key-value store. No setup, but the rows are invisible to your MySQL backups and stay on the machine that wrote them.
  • sql — oxmysql. Set the key and restart: the resource applies sql/lastresort_intel_npc.sql itself (CREATE TABLE IF NOT EXISTS, safe on every boot), so there is nothing to import by hand. Import it manually if you prefer — replace the {{prefix}} token with your configured prefix first. With sql selected and oxmysql not running, the adapter prints one line and keeps using KVP; nothing is lost.

Tables: reputation (relational — one row per character/persona, so SELECT actually tells you something), plus memory, quests, last_bribe, npc_jobs and global (JSON documents). oxmysql stays optional — it is not in dependencies. Older installs may still have an ai_memory table from a previous version — it is no longer created or read and can be dropped.

State is written whenever it changes: closing a conversation, a trust move, a remembered action, telling an NPC your name or handing over a gift schedules a write flush_debounce_ms later, so a burst inside one conversation costs one flush. The 5-minute checkpoint remains as a crash floor.

Loading it back is silent. What arrives when you spawn is the standing you already had, not something that happened just now, so the restore writes the numbers straight into the table — no trust toast, no rank overlay. Only a move made in this session is announced.


Discord logging

Enable the module in the config. The URL can live in server.cfg or in the config; the convar is read first and is the safer home, because shared/config.lua is downloaded by every connecting player:

-- shared/config.lua
Config.discord = { enabled = true }
# server.cfg
set lastresort_intel_npc:discord_webhook        "https://discord.com/api/webhooks/..."
set lastresort_intel_npc:discord_webhook:money  "https://discord.com/api/webhooks/..."
set lastresort_intel_npc:discord_webhook:police "https://discord.com/api/webhooks/..."

Categories are money, police, shop, npc and admin; anything without its own convar falls back to the default one. Config.discord.webhook / Config.discord.webhooks are read whenever no convar is set, so an install that configured them there keeps logging with nothing to change.

Logged events (each toggleable in Config.discord.events): cop bribes, rob/mug/pickpocket/tip payouts, shop + persona purchases, items given, hit-and-run damages paid, police-scene bribes, informant leaks, quests, street services, resource-start diagnostics. Red-light/speeding triggers and witness alerts exist but default off (high volume). Messages are queued and batched (≤10 embeds per POST, 1 request/second per webhook). Your own scripts can post into the same queue with the server export exports.lastresort_intel_npc:DiscordLog(category, title, description, fields, color).


Exports reference

Client-side unless marked. Call via exports.lastresort_intel_npc:Name(args).

Dialogue & reputation

Export Description
OpenStaticDialogue(id) Open the NUI chat with a static persona.
OpenAmbientDialogue(entity) Open the NUI chat with any ped via ambient routing.
IsDialogueOpen() Bool — is the NUI panel currently open?
GetReputation(id) The number this client is showing: the server's last answer for this player and persona, cached from client:repSet. Not a live read.
SetReputation(id, v) Local display only. Writes the number this client's panel shows; the next restore from the server overwrites it. It does not persist.
AddReputation(id, d) Refuses and returns false, printing why. Reputation has been server-owned since v1.42.0 and the save flush rejects client writes. Trigger lastresort-intel-npc:server:repEvent with a reason the server prices instead.
GetReputationTier(id) trusted / friendly / familiar / neutral / wary / hostile.

Personas, police & reactions

Export Description
AddPersona(data) Registers a persona at runtime; returns true, or false if the id is taken.
RemovePersona(id) Despawns and unregisters a persona.
GetPersona(id) / ListPersonas() Returns a persona table, or the live persona array.
SetPoliceSystemEnabled(bool) Turns the police detectors on or off on this client only. The server holds its own copy of the flag and that is what gates the ped-hit relay, the bribes and dispatch — set it in your own server script too if you need both halves.
IsPoliceSystemEnabled() Is the module on for this client? Same caveat as above.
IsInPoliceScene() Always false. The police module runs no scripted scenes and spawns no officers, so there is never a scene to report. Kept so existing callers do not error. To avoid two resources answering the same 911, listen to the server event lastresort-intel-npc:server:dispatchRequested instead.
TriggerReaction(ped, name) Fires a named reaction directly.
IntelNpc_GetShopCatalog(shop_id) Returns a persona shop catalog table.
GetActiveJobs() The errands this player is currently running (tracker payload).
GetBountyBoard() The board as this client last received it.
GetPlayerJobs(src) (server) A player's live errand state.
GetBountyBoard() (server) The authoritative open board.
GetMetrics() (server) The ops metrics snapshot intel_metrics prints.
GetPersonaPed(id) (server) The server-owned ped handle, when Config.use_server_spawn is on.
DiscordLog(category, title, description, fields, color) (server) Post an embed into this resource's Discord queue.

City life integration globals

Client-side globals, not exports — the city life modules keep their state in file-scope locals, so these are the handles another resource on the same client can call. Every one of them is a no-op when the module that defines it is off.

Global Description
IntelNpcAmbience_ExcludeVehicle(veh) Permanently blacklists a vehicle from the ambient car radios and restores its radio if we were already holding it. Call this on any vehicle your own resource owns and does not want touched.
IntelNpcAmbience_ReleaseVehicle(veh) Restores one vehicle's radio now and forgets the handle. Unlike the above it does not blacklist, so the car can be picked again later.
IntelNpcLife_ReleaseAll(reason) Deletes every life-point ped immediately. For a resource that needs the area clear — a cutscene, a scripted event, a map change. They re-fill on the next scan.
IntelNpcScenes_AbortAll(reason) Ends every live street scene and restores every borrowed ped — tasks cleared, blocking flag cleared, busy cleared, back to the ambient AI. Call it before your own cutscene or scripted event.
IntelNpcPaired_Try(ped, kind, opts, on_done) Asks for a paired interaction on ped. kind is a key of Config.paired_scenes.scenes. Returns true when the request was accepted — the scene can still degrade during the walk-in, so wait for on_done(success, reason), which fires exactly once either way. Returns false plus a reason string when it was refused outright. Answers false instantly while the module is off.
IntelNpcPaired_Eligible(ped, kind) Same gates as above without starting anything — true, or false plus the reason.
IntelNpcPaired_IsActive(ped) true while a paired scene is running; with a ped handle, only while that ped (or your own) is in it. Check it before re-tasking a ped of your own.
IntelNpcPaired_Abort(reason) Ends the live paired scene now and hands both peds back.

IsInPoliceScene() used to be the cross-resource surface for the police module's scene state, answered by three resource-local probes — IntelPolice_HasActiveScene(), IntelPolice_HasWaitingPed() and IntelPolice_OwnsDispatch(ped). None of those exist any more. When the police module became native-dispatch-only it stopped spawning officers, holding cop peds and running scripted scenes altogether, and the three probes were deleted with the code they read. The export kept calling them through and, so it answered false in silence while this paragraph still described a live gate.

There is no replacement, because there is no longer anything to gate: this module never sends a unit of its own. It raises the player's wanted level and GTA's own dispatch answers it. IsInPoliceScene() is retained returning false so existing callers do not error. The real hook for an external dispatch is the server event lastresort-intel-npc:server:dispatchRequested, and IsNativeDispatchInCharge() (also always true now) is what the non-lethal band reads to stand itself down.


Server events reference

Every player action that involves the server fires a public event, so an external resource — your wanted system, jobs, logging — can listen server-side:

RegisterNetEvent / AddEventHandler on:

'lastresort-intel-npc:server:itemBought'            -- item purchase completed
'lastresort-intel-npc:server:jobRequested'          -- take_job interaction
'lastresort-intel-npc:server:witnessReported'       -- a witness saw a crime (tier counts only on the perception path)
'lastresort-intel-npc:server:informantReported'     -- an informant leaked an event
'lastresort-intel-npc:server:copBribed'             -- cop bribery succeeded
'lastresort-intel-npc:server:prostituteServicePaid' -- street service paid
'lastresort-intel-npc:server:policeEvent'           -- police trigger fired (red_light, speeding, ped_hit)
'lastresort-intel-npc:server:dispatchRequested'     -- an NPC called 911; bind this if you run your own dispatch
'lastresort-intel-npc:server:moneyInteraction'      -- rob / mug / pickpocket / bribe / tip settled
'lastresort-intel-npc:server:itemGiven'             -- player gifted an item to an NPC
'lastresort-intel-npc:server:damagesPaid'           -- damages settled through the payDamages callback
'lastresort-intel-npc:server:policeBribe'           -- an engine-dispatched officer took a bribe (kind = 'native_cop')
'lastresort-intel-npc:server:shopPurchase'          -- persona shop purchase
'lastresort-intel-npc:server:questBroadcast'        -- quest / errand accepted by a player
'lastresort-intel-npc:server:questCompleted'        -- quest / errand turned in
'lastresort-intel-npc:server:jobAccepted'           -- NPC errand accepted (persona_id, job_id)
'lastresort-intel-npc:server:jobCompleted'          -- NPC errand delivered (persona_id, job_id, paid)
'lastresort-intel-npc:server:jobFailed'             -- errand expired or abandoned
'lastresort-intel-npc:server:bountyPlaced'          -- a player posted a bounty (stake already taken)
'lastresort-intel-npc:server:bountyClaimed'         -- a bounty or NPC contract paid out
'lastresort-intel-npc:server:haggleInsult'          -- a haggle landed as an insult
'lastresort-intel-npc:server:metricsTick'           -- 60 s ops metrics snapshot
'lastresort-intel-npc:bridgeChanged'                -- a bridge backend re-detected (shared)

Server callbacks reference

Cash-validated, rate-limited, range-clamped and cooldown-gated (Config.amount_clamps). Each returns a structured success/failure object.

local res = lib.callback.await('lastresort-intel-npc:payDamages', false, { amount = 250 })
-- success: { success = true, amount = 250 }
-- failure: { success = false, reason = 'no_money' }
Callback Purpose
lastresort-intel-npc:payDamages Settle damages with a hit-and-run victim. This module never calls it — the panel that did was deleted in v1.17.0 — so it exists purely as a hook for your own scene.
lastresort-intel-npc:bribeSpawnedCop Bribe an officer GTA dispatched. The name is historical; nothing here spawns police. The server checks the ped against the game's own cop model list, refuses a player ped, requires the same routing bucket and proximity, and prices the bribe from the server's own wanted reading.
lastresort-intel-npc:bribeCop Corruptible cop persona — star-scaled bribe.
lastresort-intel-npc:shopBuy Persona shop purchase.
lastresort-intel-npc:prostituteService Street service tiers.
lastresort-intel-npc:interaction Rob / mug / pickpocket / bribe / tip money flow.
lastresort-intel-npc:giveItem Gift an item to an NPC.
lastresort-intel-npc:beg Begging flow (homeless).
lastresort-intel-npc:jobs:sync Every errand this player has open, with progress. (listQuests used to be documented here and was never registered — it belonged to a second, parallel quest table deleted in v1.44.0. server/jobs.lua is the quest system; its callbacks are jobs:available, jobs:accept, jobs:progress, jobs:sync.)
lastresort-intel-npc:jobs:available Errands this persona can offer right now.
lastresort-intel-npc:jobs:accept Take the offered errand on.
lastresort-intel-npc:jobs:progress Re-validate the current step and advance it.
lastresort-intel-npc:jobs:sync The player's live errand state (restart recovery).
lastresort-intel-npc:bounty:list The open board.
lastresort-intel-npc:bounty:nearby Players close enough to name in a posting.
lastresort-intel-npc:bounty:place Post a bounty (stake taken up front).
lastresort-intel-npc:bounty:claim Collect a bounty on a target you just dropped.
lastresort-intel-npc:shopPrices Per-player shop quote (reputation + haggle).
lastresort-intel-npc:shopHaggle Push for a discount at a persona's counter.

The rob / mug / pickpocket branch of :interaction requires the client to name its target by network id; the server re-derives the ped, checks type and distance against its own copy of the player, and marks the ped looted. Under OneSync this is enforced; with onesync off the server falls back to the pre-OneSync path and the rate limit is the whole bound.


Dev commands

/intel_talk is always available (also bound to G), as are /intel_jobs (errand list + abandon), /intel_bounties (the board), /intelfx_check, /intelfx_demo and the server-console commands. Everything else below is a NO-OP unless Config.debug is on. It ships on so a fresh install is drivable; turn it off for production with setr lastresort_intel_npc:debug 0, which overrides the config file.

The one dev surface that does not come with it is the 3D overlay above every NPC's head — the two-row identity/state label. Config.debug_3d_mode ships 'off', because text over every pedestrian reads as a broken HUD on a live server. Turn it on with Config.debug_3d_mode = 'basic' (or 'full'), by cycling it from F12 → Modules for the session, or with setr lastresort_intel_npc:debug 1, which switches it to 'basic' along with the rest of the dev tooling.

Command What it does
/intel_dev Open the dev menu (F12 keybind by default).
/intel_status Print persona / target / integration diagnostics.
/intel_tp <id> Teleport to a persona for testing.
/intel_what Print category / archetype / sex / model of the closest ped.
/intel_voice <context\|tone> For the ped you are looking at: assigned voice, whether that voice has a take for the context, and the fallback voice that would be cast instead. Accepts a tone key (greet, kick, …) or a raw SPEECH_CONTEXT.
/intel_hit_test light\|moderate\|severe Simulate a car hit on the closest civilian.
/intel_police_clear Sets the wanted level to 0. That is the module's whole police state — it holds no scenes, peds or vehicles to clear.
/intel_gangtest Dry-run the gang witness fan-out on the nearest gang ped.
/intel_native_behaviour Whether Config.native_ped_behaviour is present, the master switch state, owned-ped and live-snapshot counts, and one line per override saying whether it is APPLIED or suppressed on engine-owned peds.
/intel_nonlethal The live non-lethal band: the rule in words, enabled / engaged / applies / disarm, current stars against max_stars, cops held right now, how many are excluded by self-defence, and the lifetime held/released totals.
/intel_life_reload Re-validate data/life_points.lua, re-register the anchors and print every surviving entry.
/intel_scenario_groups Per configured scenario group: resolved name, exists, live state, wanted state, recorded original.
/intel_ambience / /intel_ambience release Car-radio and world-toggle state; release restores every touched vehicle immediately.
/intel_scene_play / /intel_scene_play <id> / /intel_scene_play stop Street-scene status and every registered scene with its preset, weight, cooldown and whether it is allowed right now; with an id, force-starts that scene ignoring chance, cooldowns, time block and the distance floor; stop aborts every live scene and restores every cast ped.
/intel_paired <kind> Force the named paired interaction on the ped you are looking at, past the enabled flag, trust tier, category and cooldown. The ped is picked by the same forward cone as quick-talk (Config.paired_scenes.dev_target_range 12 m, dev_target_min_dot 0.5 ≈ 60°), not a hairline ray. Reports the outcome or the refusal reason.
/intel_inspect / /intel_inspect pin / /intel_inspect dump The single-ped inspector: everything knowable about the ped under your crosshair — identity, the state this resource holds, and the state the engine will admit to, plus an explicit list of the questions the engine refuses to answer. pin locks the panel to the current ped so you can walk around it; dump prints one full record to the console.
/intel_scene / /intel_scene deep / /intel_scene dump The always-on current-scene panel: live module counters, running street and paired scenes and their cast, the non-lethal band and how many cops it holds, wanted level, dispatch state, zone and time block, and what the frame dispatcher is actually drawing. deep adds a nearby-ped sweep counting NPCs by owning module; dump prints one snapshot as JSON.
/intel_menu (also F12) The control panel — one NUI with everything in it. Nine tabs: Actions (every reaction on the ped you are facing, provoke, force a hail, teleport to any persona), World, NPC, Personas, Police, Modules (live on/off for every module and debug surface, plus Above heads — the 3D overlay's off / basic / full and switches for the coloured status dot and the name over an NPC; this session only, it never writes your config file), Tests (the checklist as clickable steps, each with what to do, what to expect, a "prepare" button that switches on the debug that step needs, and its automatic check), Lab (the sixteen engine experiments) and Trace (per-system filters for the debug feed). This is the only debug surface that takes NUI focus, and ESC closes it. The ox_lib menu is unchanged and still on /intel_dev; set Config.control_panel_on_key = false to give F12 back to it. (This row used to claim five tabs, "all 74 of its actions, verified by a coverage check" and "139 clickable actions in total". There is no coverage check in the resource and neither number was derived from anything.)
/intel_debug The debug trace — why something happened, not just that it did. A live feed of every decision the resource makes with the reason it made it: "on_aimed_at → beg_for_mercy (archetype 'timid' maps this trigger straight to it)", "gunshot: 3 of 9 nearby peds witnessed it, 6 rejected (out of range, no line of sight, dead, or not human)", "backs the victim up: same set 'ballas', attacker 1 of 4 allowed", "SILENT on GENERIC_HI — no voice of this ped's sex has a take for that context". Colour-coded per system, filterable from the panel's Trace tab, and free while off.
/intel_test / /intel_test start [group] / /intel_test pass\|fail\|skip\|check\|next\|prev\|repeat / /intel_test report The guided test flow. An ordered walkthrough of every mechanic in the resource — 38 steps across 18 groups, from boot and bridges through perception, reactions, gossip, dialogue, voice, life points, scenes, police, economy and shutdown. Each step tells you what to do and what should happen, switches on the debug that belongs to it, runs whatever it can verify by itself (check), and streams the relevant state live while the step is open — printing only when something changes. You answer pass / fail <note> / skip and it moves on. Progress is saved, so a full pass can be spread over several sessions, and report prints the whole result table with every failure note. Step text follows Config.locale (English and Hungarian).
/intel_lab / /intel_lab <experiment> / /intel_lab stop / /intel_lab dump The engine-audit experiment bench. Sixteen experiments that settle the open questions about what the engine will tell a script — whether the engine will tell a script who witnessed a shooting, whether event read-backs survive multiplayer, what each flee-attribute bit really does. Nothing runs until you start one, each self-stops after five minutes, and spawned test peds are always cleaned up. Run witness_entities first.
/intelfx_check / /intelfx_demo lastresort_notify integration diagnostics / guided tour.
intel_metrics (server console) Print the ops metrics counters.

Compatibility

Frameworks

Framework Status
Qbox (qbx_core) Supported, auto-detected. Requires ox_inventory, which Qbox ships.
qb-core / qbus Supported, auto-detected. Works with qb-inventory or ox_inventory.
ESX Legacy (es_extended) Supported, auto-detected.
Standalone (no framework) Supported. Money runs on an in-memory session ledger; NPC memory and reputation still persist on the license id.

ox_lib 3.0+ is the only hard dependency on every one of them. ox_target / qb-target and ox_inventory / qb-inventory are optional and auto-detected; without a target resource the built-in look-at + E fallback takes over.

Suite integrations — all optional, all runtime-detected

Each of these is probed at runtime and pcall-wrapped. Intel NPC boots, runs and sells on its own with none of them installed.

  • lastresort_notify (optional) — becomes the notification surface the moment it is running, and unlocks the deep HUD layer in client/simpleasf_fx.lua: mood-swing toasts, an optional floating conversation card (off by default — the dialogue panel header now carries mood / composure / trust / patience), the provoke segment meter (it hands off to the dialogue panel while it is open — the header trust bar and chat-stream escalation notes carry the same signal without covering the NPC), the per-persona trust rank surface (opt-in — ships off, see below), achievement banners, money-counter and item toasts, neon wanted stars, police ring timers with [G]/[B] hints, dispatch cards, WEAZEL news captions and the pickpocket skillcheck. Diagnostics: /intelfx_check, guided tour: /intelfx_demo. Without it, every hook silently no-ops and plain toasts render instead. Force a different notify system with Config.bridge.notify, or keep plain toasts only with Config.simpleasf.enabled = false.
    • What the trust rank surface is, and why it ships off. Every persona keeps a reputation number that your own behaviour moves — the Config.reputation_* deltas for tipping, bribing, helping, insulting, stealing and so on. The rank surface is that number drawn as a level-up banner: an XP bar for the tier you are in, the tier ladder being hostile → wary → neutral → familiar → friendly → trusted, and a promotion flourish when you cross into the next one. It is accurate, but an XP banner across the middle of the screen after a conversation reads as a game overlay on a roleplay server, and it lands on the NPC's face. So Config.simpleasf.rep_rank ships false: a trust change outside the dialogue panel is a quiet toast instead, inside the panel it is the header's gold trust bar, and trust-tier promotions still raise their achievement banner either way. Set Config.simpleasf.rep_rank = true to get the rank banner back.
  • lastresort_chat (optional) — when it is running, every floating NPC line — hails, gossip, dialogue replies, police chatter — renders through the chat's own 3D speech bubble (PedSpeech), and the same line is written into the chat feed as speaker: line, on the overhearing client only, so the feed mirrors what that player could actually hear. A player who turned speech bubbles off in the chat settings keeps that choice — the built-in renderer does not redraw over their head. While the chat renders the bubble, its own speech-bubble settings govern lifetime and styling — the per-line durations and the gold speaker sub-line of the built-in renderer apply only to the built-in draw. With lastresort_chat absent, the built-in floating bubble takes over on the very next line, and the feed write speaks the stock chat:addMessage event, which the default chat resource understands too. Tune or disable in Config.integrations.chat.
  • lastresort_levels (optional) — when it is running, server-validated milestones award bonus playtime minutes through its GrantBonusMinutes export, tagged intel_npc:<key> in its ledger: accepting a quest (1 min), completing one (5 min) and a trust-building interaction such as a tip, bribe, help-up, flirt or hire (1 min, throttled to one per minute per player so it cannot be farmed). Tune or disable it in Config.integrations.levels. With lastresort_levels absent, every award is a silent no-op.
  • lastresort_intel_npc_ai (optional add-on) — free-typed AI conversations; see AI conversations. Detected with GetResourceState on every conversation open; stop it and the next conversation is scripted again.
  • lastresort_npc_chat (superseded) — Intel NPC ships everything NPC Chat does and more, and two chat panels fighting over the same ped is a broken experience. Intel NPC stops lastresort_npc_chat automatically at boot, in either start order, and prints why to the server console. Remove its ensure line; keep NPC Chat only on servers without Intel NPC.
  • NUI focus and blur are arbitrated suite-wide through the client-local lrNuiOwner state key, so this panel never clears focus that another resource took after it.

Other resources

  • External wanted / dispatch systems. The old companion resource lrr_wanted is not required — the police module hosts everything it did. External systems can hook the relay events (witnessReported, informantReported, policeEvent, copBribed, dispatchRequested). There is nothing to de-duplicate against — this module spawns no officers and answers no 911 itself, it only raises the wanted level for GTA's own dispatch, so dispatchRequested is the event to bind and IsInPoliceScene() is a permanently-false leftover, not a gate. If your system escalates past this module's star ceiling nothing needs disabling: Config.police_max_wanted_stars (default 2) caps only what this module asks for, and it never lowers a level another resource set — a higher level is noted and left alone. (Config.police_wanted_clamp_enabled does not exist; it was renamed to Config.police_wanted_observe_enabled, which switches off that observation notice and nothing else.)
  • qbx_smallresources (Qbox stacks): several sub-resources suppress ambient cops, clamp the max wanted level to 0 and hide the wanted-star HUD. Intel NPC re-asserts its flags at 4 Hz so it works on a stock setup, but INSTALLATION.md has the clean three-file edit that stops the two racing each other.
  • Other NPC-dialogue resources. Anything that also opens a panel on G or takes NUI focus on a ped will collide. Rebind Config.quick_talk_key, or run one of the two.

Config migration (0.21 → 0.22)

Old New
Config.use_ox_target Config.target_enabled + Config.bridge.target
Config.integrations.use_qbx_money Config.integrations.use_framework_money
Config.integrations.use_ox_inventory Config.integrations.use_inventory
Config.dynamic_persona_pool_* removed (game population + density manager)
hardcoded 2-star wanted cap Config.police_max_wanted_stars (caps this module's own requests only)
Config.napszak_bounds (0.53 → 1.0) Config.time_of_day_bounds (old key honoured for one release)

Theming

The dialogue panel is themed from nui/theme.css — an override sheet that ships empty and loads after the panel's own styles. Uncomment a line, restart the resource, done. Every token below is documented in the file itself.

Variable Controls
--lr-accent Primary highlight: topic-key badges, focus ring, active states.
--lr-primary-soft Lighter partner of the accent — hover states, speaker name.
--lr-bg, --lr-surface Deepest background, and the cards / meters / chips on top of it.
--lr-border, --lr-border-soft, --lr-border-edge Hairlines and the lit top edge of raised panels.
--panel-bg, --bubble-npc The chat shell and the NPC message bubble (both take alpha).
--lr-text, --lr-muted, --lr-dim, --lr-faint The four-step text ramp.
--lr-success, --lr-warning, --lr-danger, --lr-gold Friendly trust and timers, tense mood and witness counter, hostile trust, the trust-meter fill.
--tone-friendly, --tone-neutral, --tone-aggressive, --tone-flirt, --tone-bribe, --tone-leave, --tone-investigate One colour per reply-option kind, shown on the left edge of each choice.
--lr-radius-sm/md/lg, --lr-radius-pill Corner radii.
--lr-dur-fast, --lr-dur, --lr-dur-slow, --lr-ease Animation durations and the shared settle curve. A player with "reduce motion" enabled overrides these anyway.
--display, --body, --mono Oswald / DM Sans / JetBrains Mono, all vendored under nui/assets/fonts. Add your own @font-face in theme.css before pointing these elsewhere.

Config-side UI keys, in shared/config.lua:

Key Effect
Config.nui_scale user multiplier, min / max clamp and base reference height for the panel's resolution auto-scale. Ships at user = 0.85.
Config.simpleasf.*_position Anchors for the deep HUD surfaces when lastresort_notify is running (mood_toast_position, conversation_card_position, dialogue_toast_position for money / item / trust toasts fired mid-conversation).
Config.simpleasf.duration_scale Global multiplier on how long those surfaces stay on screen. Real gameplay timers are never scaled.
Config.discord.colors Per-category embed colours for the Discord log.

Icon names in Lua stay Font Awesome tokens (fa-solid fa-comments) so target menus and existing customisations keep working; the panel maps each one onto the bundled Lucide face, falling back to a chat glyph for anything unmapped.


Licensing

Open source. Unencrypted, unobfuscated, no license check — every line of the runtime Lua, the dialogue panel HTML, the config, the data/ content, the locales, the SQL and nui/theme.css ships readable and editable. If you need a hook the config cannot reach, ask — the exports and public events above exist for exactly that, and the list grows on request.

Licence. One purchase covers unlimited servers you run yourself, with lifetime updates — no subscription, no per-server fee, nothing phoning home. Redistribution, resale, sharing the files or re-uploading them anywhere is not permitted.


Support

© 2026 LastResort Development. All rights reserved.

What you get

  • The full source code. Every Lua, config and UI file, readable and editable. Rename it, restyle it or hook your own systems into it.
  • No escrow, no license checks. No key and nothing that phones home. It keeps running as long as your server does.
  • Free updates. New versions land in your account for as long as we maintain the script. Download the latest one whenever you like.
  • One community per purchase. Use it on that community's live, development and test servers. Reselling or re-uploading the code is not allowed.

Get Project Intel NPC — Talk to ANY Ped. They Remember. They Gossip. They Snitch.

€50.00, paid once through Stripe. The download is in your account right after payment.

Project Intel NPC — Talk to ANY Ped. They Remember. They Gossip. They Snitch.

€50.00€120.00-58%