NPC & AI
Project Intel NPC — Talk to ANY Ped. They Remember. They Gossip. They Snitch.
Original Lua/JavaScript resource developed independently by WebMerc. Requires FiveM (GTA V). No Rockstar game files are included.
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.
- Version: 1.63.0
- Author: LastResort Development — intelnpc.com
- Buy: fivem-last-resort.tebex.io
- Support: help@intelnpc.com
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
- What it is — the four pillars
- Framework support & bridges
- The ambient cast — six categories
- Personality archetypes — seven types
- Native ped behaviour
- The reputation system
- The economy loop — errands, bounties, trade
- Daily routines
- The dialogue system
- Adding a static persona
- The police system
- Traffic personality
- Vehicle collision
- The social intelligence layer
- Facial expressions
- NPC spawning model & density manager
- The city life layer
- Spoken dialogue — voice casting & speech sync 18a. AI conversations — optional add-on
- Language packs
- Persistence
- Discord logging
- Exports reference
- Server events reference
- Server callbacks reference
- Dev commands
- Compatibility
- Theming
- Licensing
- 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_territoriesships empty, so nothing happens until you author those boxes yourself. An authored table is a request, not unasked-for tuning. Set itfalseto bring it under the master switch too.
What an existing install will notice on upgrade
- 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_fleeandlife_point_disable_explosion_reactionstotrue,life_point_suffers_critical_hitstofalseandlife_point_alertness = 0. Config.block_temporary_eventsactually works now. It was silently overridden totruefor every NPC byIntelNpcRelations_AssignToPed. An owner who set itfalseand saw no effect will now get the effect.- 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_msinstead 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 todata/jobs.luaand 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_jobscosts the job'sfail_repwith 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_radiusof 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_distanceof 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 = truetakes 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 withConfig.routines.night_mood_bias— the same NPC reads warier at 3 a.m. than at noon. Dialogue trees can branch on it through thetime_of_day_nighttag they already receive. - Arrival lines.
voice_linefloats 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_actionhooks: 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_thresholdplus 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 perrecover_ms(2 min by default) of being left alone. A persona with an authoredangry_close_thresholdis 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.randomwith 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(default3) 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(default6) caps the re-rolls. Setline_no_repeat_depth = 0for 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 theSetPoliceSystemEnabledexport, 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_PROPERTIEScarries R*'s owneVehicleNodeProperties, and this readsTRAFFIC_LIGHT(1 << 8) rather than matching prop models, so it works on any map without a hash list. (extra_light_hasheswas 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, andclient/ai_suppression.luareads 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 belowmax_stars(default1, so zero and one star are both covered and lethal force begins at two stars), police do not shoot. Every cop withinradius(default 120 m) is forced toWEAPON_UNARMEDwith weapon switching blocked, accuracy 0,CA_ALWAYS_FIGHTcleared,CA_CAN_BUSTset andTASK_ARREST_PEDissued at close range; withdisarmon (the default) the firearm is physically taken, because holstering alone lets the combat AI re-draw it. What the officer OWNS is read withGET_BEST_PED_WEAPON, notGET_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 — abovemax_starspolice 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, defaulttrue). Accuracy and the recorded weapon are snapshotted per cop and handed straight back withrestore_ammorounds; the combat attributes have no getter in GTA, so they are returned to the engine's own defaults. The band also always yields whileIsNativeDispatchInCharge()is true. It spawns nothing, says nothing and takes no bribes. The full key list is the seven inConfig.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, aSET_WANTED_LEVEL_DIFFICULTYdial,require_police_systemandyield_to_resourceshere, 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 inConfig.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 ownwas_assaultedmemory tag — a 6 h, gossip-worthy event, distinct from a bystander'ssaw_assault, usable as a dialogue tag exactly like thesaw_*tags. He then gets his own archetype'son_hitreaction: 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.expressionoverrides 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_activeceiling 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, theox_libproximity 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.IsScenarioTypeEnabledis a separate, per-spawn check: a scenario type disabled globally (seeConfig.disabled_scenario_types) is not dropped, it just rotates the entry's pool at spawn time and shows up as theskips: ... disabled-typecounter in/intel_status. - Per-preset switches under
presets_enabled, and afurniture.enabledmaster for the fourfurniture_*presets. That master does not reach thered_light_districtpreset, 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.luaand 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 bymax_vehicles, faded in bymax_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_distanceandmax_player_distance, with every other role insidecast_max_gapof 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_payloadhops the speaker's newest gossip-worthy memory into the rest of the cast for real, through the sameNpcState_AbsorbGossippath the gossip loop uses — hop count and confidence decay included. A line with abubble_seenvariant switches to it when the cast actually remembers you. - You can cut in. Inside the entry's
interject.radiusyou get a prompt (helpbox, floatingtext3d, ornone); 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 needstrusted, the dapfamiliarplus thegangcategory, the hand-over onlyneutral. - 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 = falseuses 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. Flipnetworked = trueto 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 = falsekeeps your own ped out of it entirely. - The animation pairs are validated at build time. Every
dict/clip_a/clip_bshipped 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 indata/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_nameis validated at boot. A name GTA does not have makesSetAmbientVoiceNamea 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 toConfig.default_gta_voice_nameinstead of being silently wrong.Config.default_gta_voice_nameitself is checked the same way. Turn the check off withConfig.validate_persona_voices = false. - A persona keeps its model's own voice.
Config.default_gta_voice_nameused 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 toConfig.voiceless_persona_models; setConfig.force_persona_default_voice = trueto 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_fand_by_context_xfor names that encode no sex — and the pool is chosen withIsPedMale, 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 withA_F_*names before it ever reachedA_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:
- 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 throughtone_contexts. - 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.
- 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.
- While the line speaks, the client polls
IsAmbientSpeechPlayingon 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/:
- the
lr:localeconvar — one line inserver.cfgsets every LastResort resource at once:setr lr:locale hu - the
locale,ox:locale,esx:localeandtxAdmin-localeconvars, in that order - on the client only, the player's own GTA language setting
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 appliessql/lastresort_intel_npc.sqlitself (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. Withsqlselected 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 inclient/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 withConfig.bridge.notify, or keep plain toasts only withConfig.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. SoConfig.simpleasf.rep_rankshipsfalse: 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. SetConfig.simpleasf.rep_rank = trueto get the rank banner back.
- What the trust rank surface is, and why it ships off. Every persona
keeps a reputation number that your own behaviour moves — the
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. Withlastresort_chatabsent, the built-in floating bubble takes over on the very next line, and the feed write speaks the stockchat:addMessageevent, which the default chat resource understands too. Tune or disable inConfig.integrations.chat.lastresort_levels(optional) — when it is running, server-validated milestones award bonus playtime minutes through itsGrantBonusMinutesexport, taggedintel_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 inConfig.integrations.levels. Withlastresort_levelsabsent, every award is a silent no-op.lastresort_intel_npc_ai(optional add-on) — free-typed AI conversations; see AI conversations. Detected withGetResourceStateon 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 stopslastresort_npc_chatautomatically at boot, in either start order, and prints why to the server console. Remove itsensureline; keep NPC Chat only on servers without Intel NPC.- NUI focus and blur are arbitrated suite-wide through the client-local
lrNuiOwnerstate key, so this panel never clears focus that another resource took after it.
Other resources
- External wanted / dispatch systems. The old companion resource
lrr_wantedis 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, sodispatchRequestedis the event to bind andIsInPoliceScene()is a permanently-falseleftover, 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_enableddoes not exist; it was renamed toConfig.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
- Store: https://fivem-last-resort.tebex.io
- Website & docs: https://intelnpc.com
- Support: help@intelnpc.com
- Install guide: INSTALLATION.md · Release history: CHANGELOG.md
© 2026 LastResort Development. All rights reserved.
Installation
- Install and start
ox_lib3.x. - Copy
lastresort_intel_npcinto theresourcesdirectory. Do not rename it. - Add this after its dependencies and any optional LastResort resources in
server.cfg:
set onesync on
ensure ox_lib
ensure lastresort_intel_npc
Do not also ensure lastresort_npc_chat — see step 6.
3 — Restart and read the console
Expected server-console output on a clean boot:
[lastresort_intel_npc bridge] server: framework=qbox inventory=ox notify=simpleasf target=ox
The client prints its own [lastresort_intel_npc bridge] client: ... line in
F8. The framework= / inventory= / notify= / target= values are what
auto-detection picked — if one is wrong for your stack, force it in
Config.bridge (shared/config.lua).
The
loaded with N static persona(s)line only prints when the verbose convar in step 4 is on — it goes through the operator-log gate, not
If you have edited data/personas.lua, watch F8 for a yellow line like
persona "your_persona_id" declares gta_voice_name "...", which is not a real GTA ambient voice. It is exactly the check that caught Maggie's voice name in
this release. That check runs on every boot: a voice name GTA does not have
makes the NPC permanently mute with nothing else to explain it, so the persona
falls back to Config.default_gta_voice_name and tells you. Fix the name, or
set Config.validate_persona_voices = false to silence the check.
4 — Debug / verbose convars (optional)
Dev tooling (F12 dev menu, /intel_dev, all /intel_* test commands, 3D
overlays, map blips) is gated behind Config.debug, which ships on so a
fresh install can be driven immediately. Turn it off for production with
setr lastresort_intel_npc:debug 0, which overrides the config file.
The 3D surfaces above every NPC's head are the exception: Config.debug_3d_mode
ships 'off', so a fresh install has nothing floating over pedestrians. Set it
to 'basic' or 'full' when you need it, pick the mode in-game from
F12 → Modules → Above heads, or set setr lastresort_intel_npc:debug 1,
which switches it to 'basic'. The same section carries the other two over-head
surfaces, both also off by default: the coloured status dot
(Config.mood_icon_enabled — talk / busy / hostile within 18 m) and the name
above an NPC (Config.show_npc_name_tooltip, which draws only while the 3D
overlay is off). All three are session-only from the panel — it never writes your
config file.
F12 is also Steam's default screenshot key — if you launch FiveM through
Steam, either change the overlay bind, rebind the dev menu in
Settings → Key Bindings → FiveM, or set Config.dev_menu_key.
Control both from server.cfg without editing files — use setr so clients
receive them:
setr lastresort_intel_npc:debug 1 # 1 = dev tools on, 0 = force production
setr lastresort_intel_npc:verbose_server_log 1 # operator-facing console diagnostics
Both accept 1/true to force on and 0/false to force off; leave them out
entirely to use whatever shared/config.lua says. A fresh install ships
production-safe, so only add these on a dev/test server.
Who may use the dev tools — grant the ace (v1.46.0)
Config.debug is a feature switch, not a permission check. Two dev events
reach the server (the police master toggle and the test-weapon give), and they
are gated behind an ace. Nobody holds it by default, so an unconfigured server
is closed rather than open:
add_ace group.admin lastresort_intel_npc.dev allow
The server console always holds it, which is what txAdmin and rcon use, so
you can drive the dev tools from the console without granting anything. If a
player fires a dev event without the ace the server refuses it and prints who
tried. Before v1.46.0 these were gated on Config.debug alone — which ships
on — so any connected client could flip the police system server-wide.
5 — Discord logging (optional, but read this if you use it)
Webhook URLs are read from the server convars first and from
shared/config.lua second — both work. Prefer the convar: shared/config.lua
ships to every client that connects, and any player who reads a URL there can
spam or delete your channel.
Turn the module on in the config:
-- shared/config.lua
Config.discord = {
enabled = true,
...
}
…and put the URLs in server.cfg:
# default channel — used by any category without its own override
set lastresort_intel_npc:discord_webhook "https://discord.com/api/webhooks/1234/abcd"
# optional per-category channels: money, police, shop, npc, admin
set lastresort_intel_npc:discord_webhook:money "https://discord.com/api/webhooks/1235/efgh"
set lastresort_intel_npc:discord_webhook:police "https://discord.com/api/webhooks/1236/ijkl"
set lastresort_intel_npc:discord_webhook:shop "https://discord.com/api/webhooks/1237/mnop"
If your URLs are already in Config.discord.webhook / .webhooks, they keep
working — the server just prints one line at boot pointing at the convar.
Per-event toggles live in Config.discord.events; red-light/speeding triggers
and witness alerts default off because they are high volume.
5.1 — Language (optional)
-- shared/config.lua
Config.locale = 'auto' -- 'auto' | 'en' | 'hu' | …; shipped packs: 'en', 'hu'
'auto' picks the language itself: the lr:locale convar first — setr lr:locale hu in server.cfg sets every LastResort resource at once — then locale / ox:locale / esx:locale / txAdmin-locale, then the player's own GTA language on the client, then English. Only a code with a pack in locales/ counts. The server console prints [lastresort_intel_npc] locale: hu (lr:locale convar) once at boot so you can see what it chose. Set a code instead of 'auto' to pin one.
Every player-facing line — menus, the dialogue panel, NPC hails, refusals, kick
lines, the police scenes — comes from locales/<code>.lua. Add your own by
copying locales/en.lua to locales/xx.lua, renaming the Locales['en'] table
to Locales['xx'] and translating the values; keys you leave out fall back to
English. Restart the resource to pick a new file up. See "Language packs" in
README.md.
5.2 — SQL persistence (optional)
NPC memory, reputation, quests, errand runs, the bounty board and bribe history default to FiveM's KVP store, which needs no setup but never appears in a MySQL backup. To move them into your database:
-- shared/config.lua
Config.persistence = {
backend = 'sql',
table_prefix = 'lastresort_intel_npc_',
}
Restart the resource. With oxmysql running it applies
sql/lastresort_intel_npc.sql itself and prints:
[lastresort_intel_npc persistence] SQL backend active (tables `lastresort_intel_npc_*`)
Nothing to import by hand. If you would rather run the file yourself, replace
its {{prefix}} token with your configured prefix first. If oxmysql is not
running the resource says so in one line and keeps using KVP — it never fails
to start over this, and oxmysql is not a dependency.
Older installs may still have a lastresort_intel_npc_ai_memory table from a
previous version. It is no longer created or read; leaving it in place breaks
nothing, and you can drop it when you want the space back.
5.3 — City life layer (optional, life points are ON)
Config.life_points ships enabled since v1.11.0. It spawns ambient scenario
peds — sunbathers, bench sitters, someone at the ATM, and the night-only
red_light_district preset that gives the prostitute hail lines and the street
service flow someone to work with. These are real peds on top of R*'s ambient
population, so:
Config.life_points.max_active(default 12) is the hard ceiling across every preset. On a server that is already heavy, drop it to 6 and watch resmon.Config.life_points.enabled = falseremoves the whole layer.Config.life_points.presets_enabledswitches presets off one at a time — e.g.red_light_district = falseif you do not want the red-light posts.Config.life_points.furniture.enabled = falsedrops only the fourfurniture_*presets that sit peds on ATMs, benches, meters and bus stops. It does not touch the red-light preset.
The other four city-life modules — Config.scenario_groups,
Config.world_ambience, Config.street_scenes and Config.paired_scenes —
all ship on as well.
5.4 — Street scenes (ships ON)
Config.street_scenes.enabled = true by default. The peds
already standing on the pavement around you start having authored conversations
with each other — real GTA ambient speech in their own voices, the resource's
normal bubbles over their heads, and a prompt to cut in and open the dialogue
panel on whoever you interrupted.
Nothing here spawns anyone. The module borrows peds out of the population you already have and hands every one of them back, so it does not move your ped budget at all.
Config.street_scenes.presets_enabledswitches whole groups off in one line —street_talk,corner,cops.chance(default 0.25) andscan_interval_ms(default 20000) control how often a scene is even attempted;global_cooldown_msis the floor between two of them.interject_promptis'help'(the game's help box),'text3d'(floating text) or'none'.interject_controldefaults to 51 — INPUT_CONTEXT, E — change it if E is already spoken for on your server.bubbles = falsekeeps the audio and the gossip and drops the subtitles.- The seven shipped scripts and the full entry schema are in
data/street_scenes.lua.
While it is off there is one sleeping interval thread and nothing else: no peds touched, no natives called.
5.5 — Paired interactions (ships ON)
Config.paired_scenes.enabled = true by default. A greeting
from an NPC who trusts you becomes an actual handshake, a familiar gang contact
gets a dap, and giving an item, bribing or tipping becomes a real hand-over.
It is a cosmetic and it never changes an interaction outcome. The dialogue panel opens either way — after the handshake when there was one, immediately when the scene was refused or degraded.
trust_tier_minper scene is the gate —trustedfor the handshake,familiarplus thegangcategory for the dap,neutralfor the hand-over.ambient_tier_thresholdsis where an ordinary pedestrian's tier comes from.networked = false(the default) means only the player who triggered the scene sees the two peds animate. Set it totrueif you want other players in range to see it, at the cost of a network scene per interaction.player_in_scene = falsekeeps your own ped out of the animation entirely — use it if handing the player's ped over for ~2 seconds is unacceptable on your server.dev_target_range(12 m) anddev_target_min_dot(0.5, ~60°) decide which ped/intel_pairedacts on — the same forward-cone search quick-talk uses, not a hairline camera ray. Widen them if the dev command keeps answering no ped in front of you on a ped standing slightly off-centre.scenes.hug_flavour.enabledshipsfalseon purpose.
While it is off there is one sleeping 120 s cooldown sweep and nothing else:
IntelNpcPaired_Try answers false immediately and every call site keeps its
current behaviour.
5.6 — Spoken dialogue panel (ships ON)
Config.dialogue_speech.enabled = true by default. Every line
the dialogue panel types is paired with a tone-matched GTA ambient bark in the
NPC's voice, with the typewriter finishing when the audio does.
Listen to it before you go live. The written line is yours and translated; the
bark is a canned English pedestrian line that has nothing to do with the text.
On an immersive or non-English server that mismatch may read worse than
silence — enabled = false turns it off, sync_typewriter = false keeps the
audio but unhooks the typewriter from it.
Config.dialogue_speech.sync_typewriter = falsekeeps the audio but stops it driving the typing.Config.dialogue_speech.tone_contextsremaps any line tone to a different speech context.- The full key table is in the README under "Spoken dialogue — voice casting & speech sync".
While it is off there are no timers and no natives called — the panel behaves
exactly as it did in v1.11.0. Config.validate_persona_voices is a separate
switch and still runs.
5.7 — Vehicle collision (ships ON, new in v1.19.0)
Config.vehicle_collision.enabled = true by default. It does two things while
you are driving, and nothing at all while you are not:
- Below
lethal_kmh(35) a pedestrian or a car occupant you hit is floored atsurvive_floorhealth (110) rather than dying, and GTA's critical-damage multiplier is removed from anyone insideradiusof your moving vehicle. Abovelethal_kmhimpacts stay lethal. - Contact is detected with
IS_ENTITY_TOUCHING_ENTITYrather than from a health drop, so a ped reacts to being nudged even when completely unhurt — and a driver reacts when you tap their car, which never fired before.
Worth knowing before you ship it:
- The softening is borrowed. Peds near your car are measurably harder to
one-shot for as long as you are driving past them, and it is handed back the
moment they leave the radius, you leave the driver's seat, or the resource
stops. GTA exposes no getter for that flag, so the restore writes the engine
default (
true), not the ped's prior value — if another resource had deliberately set it tofalseon a ped, this hands it back astrue. - It does not touch the wanted level. A nudge and a bump never make you
wanted; a hit is reported by
client/police.luaand only whenConfig.police_system_enabledis on. The victim's reaction fires either way. - It needs no other module. With police off — the default — being run over now produces a reaction, which it previously did not.
enabled = falserestores vanilla GTA collision exactly. Nothing else in the resource depends on it.
If you are upgrading and had tuned Config.police_ped_hit.protect_nearby_peds,
note that it never ran: nothing has read it since v1.17.0. Its replacement is
this block, and this one is on.
5.8 — Keys worth reviewing after install (v1.16.0)
Three new config surfaces ship on by default, and they are on whether or not
you copy the blocks across. If you keep a heavily customised shared/config.lua
and paste nothing, the built-in fallbacks are the new defaults, not the
pre-v1.16.0 tuning — concretely: the non-lethal police band is active at zero
and one star (lethal force starts at two), the anti-repeat line picker is
active, and ambient, dialogue and life-point peds run on GTA's own reaction
defaults. Upgrade the Lua and the
config together.
If you want a genuinely no-behaviour-change upgrade, the minimal paste is:
Config.police_nonlethal = { enabled = false }
Config.line_no_repeat_depth = 0
Config.native_ped_behaviour = {
enabled = false,
disable_shocking_events = true,
clear_flee_attributes = true,
brave_disable_bullet_reactions = true,
combat_disable_bullet_reactions = true,
dialogue_flee_lock = true,
dialogue_bullet_lock = true,
life_point_block_events = true,
life_point_never_flee = true,
life_point_disable_explosion_reactions = true,
life_point_suffers_critical_hits = false,
life_point_alertness = 0,
}
Config.native_ped_behaviour — leave it alone to get GTA's native
pedestrian behaviour. Every key inside names its native default if you want to
re-enable the old tuning. It only ever suppresses overrides on peds the engine
owns; peds this resource spawns (static personas, life points, police-module
peds) are still configured by the persona_* / life_point_* keys in the same
table. Verify after boot with /intel_native_behaviour — it should print
Config.native_ped_behaviour: present and enabled: true. If it prints
MISSING, the block was not pasted — each override then falls back to its
v1.16.0 default, which is not the v1.15.3 tuning.
With this on, the resource no longer fights other scripts over ambient-ped
attributes, so conflicts with ped-behaviour or realistic-AI resources should
disappear. Config.gang_territories and Config.disable_ai_police remain the
two places where this resource still reaches for engine-owned peds; both ship
off/empty.
Config.police_nonlethal — police never use lethal force at or below a
configurable wanted level (default 1 star, zero stars included — lethal force
starts at two). A cop you shoot or ram drops out of the band and fights back at
full strength (self_defence, default true). The band spawns nothing and
takes no bribes; it only disarms and restores. If you run your own police or
dispatch resource and the two collide, set enabled = false — there is no
yield_to_resources list and no require_police_system flag, despite what
older copies of this page said. Verify with /intel_nonlethal.
Config.line_no_repeat_depth / Config.line_no_repeat_tries (default 3 /
6) — the anti-repeat dialogue line picker. No new dependency, no new file, no
manifest change. Set line_no_repeat_depth = 0 for the old pure-random picking.
If you maintain a custom locales/ pack: refusal.prostitute is a new key.
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.
One realism note that is not a bug: Config.invincible_peds = true ships as the
default and makes resource-spawned peds fully proofed, melee included, so a
punch does no physical damage at all. Since v1.16.0 the reaction fires
regardless — the struck ped fights back, flees or moves to arrest — but if you
want peds to actually take the hit, set Config.invincible_peds = false, or
Config.life_points.invincible = false for ambient life-point peds only.
6 — Superseded and optional resources
lastresort_npc_chat — stopped automatically
Intel NPC ships every NPC Chat feature and more. Running both means two panels fighting over the same ped, so Intel NPC stops it at boot — in either start order — and prints:
[lastresort_intel_npc] stopping lastresort_npc_chat: lastresort_intel_npc supersedes it and already provides NPC chat
Remove its ensure line from server.cfg. Nothing else is required.
lastresort_intel_npc_ai (AI conversations)
Optional. Start it after this resource and put its key in server.cfg:
ensure lastresort_intel_npc_ai
set lastresort_intel_npc_ai:openai_key "sk-..."
Its own INSTALLATION.md has the rest. Which NPCs talk through it is decided
here, in Config.ai_bridge.
lastresort_notify (deep HUD integration)
Start it before this resource to light up the deep surfaces (mood toasts,
provoke meter, achievement banners, wanted stars, ring timers, dispatch cards,
news captions, pickpocket skillcheck). Without it every hook silently no-ops and
plain toasts are used — it is never a dependency. Configure the surfaces in
Config.simpleasf; verify with /intelfx_check, demo with /intelfx_demo.
One surface in that list is opt-in: Config.simpleasf.rep_rank ships
false. It is the XP-style rank banner for a persona's trust tier, and after a
conversation it reads as a game overlay on a roleplay server — it also lands
over the NPC's face. With it off, a trust change is a quiet toast outside the
dialogue panel and the header's gold trust bar inside it, and trust-tier
promotions still raise their achievement banner. Set it to true if you want
the banner.
lastresort_chat (speech bubbles and feed)
While it is running, every floating NPC line — hails, gossip, dialogue
replies, police chatter — renders through the chat's 3D speech bubble, and the
line lands in the chat feed as speaker: line on the overhearing client. A
player who turned speech bubbles off in the chat settings keeps that choice.
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, so
the default chat resource works too. Tune or disable in
Config.integrations.chat — it is never a dependency.
lastresort_levels (progression)
If it is running, quest acceptance (1 min), quest completion (5 min) and
trust-building interactions (1 min, max one per minute per player) award bonus
playtime minutes through its GrantBonusMinutes export. Tune the amounts in
Config.integrations.levels, or set enabled = false there to never call it.
With lastresort_levels absent it is a silent no-op — no config change needed.
ox_target / qb-target
If either is running, persona and ambient interactions surface through it
automatically. Without one, the built-in fallback activates: look at an NPC and
press E (rebindable in GTA keybinds; default via
Config.builtin_target.key).
ox_inventory native shop UI
By default persona shops open a built-in, server-validated context menu that
works on every framework. To use the native ox_inventory shop UI instead, set
Config.shop_use_ox_inventory = true and register the catalog in your
ox_inventory/data/shops.lua:
['kati_neni_247'] = {
name = "Maggie's 24/7",
inventory = {
{ name = 'cigarette', price = 2 },
{ name = 'pack_cigs', price = 18 },
},
},
(kati_neni_247 is the historic shop id for Maggie's 24/7 — it is referenced
from data/dialogue_lines.lua, client/shop.lua and server/main.lua, so keep
it as it is unless you change all three.)
7 — Qbox stacks only (optional but recommended): defang qbx_smallresources
Several qbx_smallresources sub-resources actively fight the police features:
they suppress ambient cop spawning, clamp SetMaxWantedLevel to 0 and hide the
wanted-stars HUD. Intel NPC re-asserts each flag at 4 Hz, so it works without
these edits — it is just cleaner to disable the upstream interference once
instead of racing it forever. Skipping this still works; you may see mild
flicker in the millisecond windows where the competing resource wins the race.
8.1 — qbx_ignore/client.lua
-- SetCreateRandomCops(false)
-- SetCreateRandomCopsNotOnScenarios(false)
-- SetCreateRandomCopsOnScenarios(false)
8.2 — qbx_disableservices/config.lua
Lift the max-wanted clamp so RP heat sticks. Keep police dispatch services off so native dispatch cannot stack shooting cops on the scripted RP unit:
return {
maxWantedLevel = 5, -- was 0
enabledServices = {
[1] = false, -- PoliceAutomobile
[3] = true, -- FireDepartment
[5] = true, -- AmbulanceDepartment
[11] = true, -- Gangs (faction backup)
[15] = false, -- ArmyVehicle (R*'s eDispatchType: 13 SwatHelicopter, 14 PoliceBoat, 15 ArmyVehicle)
},
}
8.3 — qbx_hudcomponents/config.lua
Remove HUD component 1 (HUD_WANTED_STARS) from the disable list:
hudComponents = {2, 3, 4, 7, 9, 13, 19, 20, 21, 22} -- 1 removed
Then restart qbx_ignore, qbx_disableservices, qbx_hudcomponents and
lastresort_intel_npc.
The police module itself ships disabled (
Config.police_system_enabled = false, alpha). These edits prepare the stack for when you turn it on — via the F12 dev menu, theSetPoliceSystemEnabledexport, or the config flag.
8 — Verify
- Walk up to Maggie at the Davis 24/7 gas station: Talk is the one option on her (target eye, or E on the built-in fallback, which opens the conversation straight away), and G starts a conversation with any ped you look at. Her shop and the single cigarette are offered inside the conversation, in the action row under the replies.
1.1. Errands: in that conversation pick "Got any work for me?" → "I'll take it." A tracker and a routed blip appear;
/intel_jobslists it and can abandon it. Errands that move items need matching inventory item names — setConfig.npc_jobs.item_steps_enabled = falseif your server has none. 1.2. Bounties: talk to Erika or Denny (the shipped brokers) once or twice to earn trust, then "I want a price on someone."/intel_bountiesshows the board to everyone; NPC contracts are on it from the first boot. 1.3. Trade: open Maggie's shop. The header names how she reads you and the prices follow it; Haggle pushes for a discount — and can insult her. - Check both consoles for the
[lastresort_intel_npc bridge]lines and confirm the detected framework / inventory / notify / target are what you expect. - If
lastresort_notifyis installed, run/intelfx_check;/intelfx_demois the guided tour. - With
setr lastresort_intel_npc:debug 1, press F12 (or/intel_dev) and use Simulate hit,/intel_whatand/intel_statusto exercise the systems. /intel_native_behaviourshould printConfig.native_ped_behaviour: presentandenabled: true;/intel_nonlethalshould printenabled=true engaged=… applies=… disarm=true,stars=0/1,cops held=…andexcluded by self-defence=…— it prints neither amax_stars=nor ansd_excluded=token, despite what older copies of this page said. Both needConfig.debug.- Punch a gang member: he should fight back rather than walk on, and
/intel_whaton him afterwards should show thewas_assaultedmemory tag — notsaw_assault. - Performance: F8 →
resmon 1and watch thelastresort_intel_npcrow while you stand still, then while you walk a crowd, then with the panel open. See the Performance section of README.md for the ranges to expect.
Troubleshooting
Configure
- Leave framework, target, inventory, and notification detection on
autounless your stack needs a fixed provider. - NPC behavior, interaction distances, city life, street scenes, and paired interactions are configured under
config/. - Set
setr lr:locale eninserver.cfgto choose one language for the LastResort suite. - Configure Discord webhooks as server-only convars. Never put secrets in shared Lua files.
Verify
- Check that the console reports
ox_liband the expected framework providers. - Approach an NPC and test the target option or E-key fallback.
- Test one dialogue, shop, and crime interaction, then restart the resource and check cleanup.
- Review all startup warnings before opening the server to players.
Important
- QBox installations also require
ox_inventory. ox_targetandqb-targetare optional; the E-key interaction works without them.- Do not run
lastresort_npc_chatalongside it because Intel NPC already includes that feature. - Disable debug, verbose, demo, and permission-bypass options before going live.
Changelog — LastResort Intel NPC
Newest first.
v1.63.0 — 2026-09-24
New — AI conversations through the optional lastresort_intel_npc_ai add-on
This resource still contacts no external service. When the separate add-on is running and has its owner's API key, the conversation panel becomes free talk and the add-on's model directs the NPC; without it nothing changes.
- Free-typed replies. A text field above the replies; any letter key starts typing, ENTER sends. AI reply suggestions sit on 1–3 with a suggested tag, the scripted topics stay under them (
Config.ai_bridge.keep_scripted_topics). A FREE TALK chip in the header, a teal typing cue while the NPC thinks, the NPC's name in the header once a random pedestrian introduces themselves - The NPC acts on the model's call: emotion to GTA facial idles and the mood badge, the delivery to a GTA speech context through the speech-synced panel (new tone
ai_goodbye→GENERIC_BYE), a gesture as a secondary upper-body animation, and the closing reaction — ends it, walks away cursing, curses, flips you off, flees, hands up, begs, calls the cops, attacks, draws, arrest warning — through the existing reaction set, with the NPC marked panicked or hostile before the dialogue hold is released so the reaction is not cleared - Lie tells: a lie shows as a one-second head turn away (secondary head IK), a stressed face and a tell line in the panel,
Config.ai_bridge.lie_tell_chanceof the time; the focus look-at is put back afterwards - Audience: a shouting match, a public insult or a bizarre player raises the engine's shocking event on the NPC (
EVENT_SHOCKING_SEEN_CONFRONTATION111,_SEEN_INSULT113,_SEEN_WEIRD_PED119), read back withIS_SHOCKING_EVENT_IN_SPHERE; a refused event is removed and traced with its cause - Gossip: six new gossip-worthy events (
ai_praise,ai_complaint,ai_threat,ai_generous,ai_suspicious,ai_scandal) carry the NPC's rumour through the existing gossip network; they also surface asheard_ai_*tags for scripted dialogue - Deals: an offer from the model becomes a gold Accept button in the action row. The server stores one pending offer per player and executes it on Accept inside a price band (
Config.ai_bridge.deals): shop items, secrets for sale, bribes for corruptible cops. New callbacklastresort-intel-npc:aiDealAccept, new eventlastresort-intel-npc:server:aiDeal - Intel: personas can carry
ai.secrets(teaser, text, waypoint, trust gate, price). Bounty brokers and informants share the bounty board fromintel.bounty_min_trust; informant and witness reports become street talk. A reveal arrives as a teal intel card, a notification and a waypoint, and the NPC points at the place - Trust from the model is applied by the server and capped (
Config.ai_bridge.trust: +1 a line, +2 a conversation, −2 a line) data/personas.luapersona fieldai(lore,speech,look,accent,language,voice,no_violence,knows_bounties,secrets), orai = falseto keep one persona scripteddata/generated/ped_profiles.lua— ped model hash → model name and engine personality for 533 ambient, gang, service and unique peds, generated from the game's ped index, so the model knows it is playing a young hipster or an old rich womanserver/ai_bridge.luaexports for the add-on only (AiPersona,AiContext,AiConversationStart,AiConversationEnd,AiTrust,AiOfferDeal,AiReveal), refused for any other calling resource- The speech-synced typewriter takes an external voice source (
IntelNpcSpeech_PrepareExternal): an AI voice line holds the text like a GTA bark does, and a line whose audio never arrives releases as silent - In AI conversations the idle timeout is
Config.ai_bridge.idle_timeout_ms(45 s), typing counts as activity, the scripted "impatient" line and asides stay quiet, and a slow reply never counts as the player being idle
Changed
tools/generate.js1.3.0: validates the 17 AI gesture clips in bothgestures@m@standing@casualandgestures@f@standing@casualand 9 facial idles in both generic facial clipsets (62 pairs, was 10), generatesped_profiles.lua, and no longer scansdata/generated/for model names. Every generated file carries the new version lineIntelShop_OwnedBy(persona_id)(server) andIntelShop_ItemLabel(item)(client) globals,IntelBounty_PublicBoard()(server)
Fixed
- The dev lab's event table had two wrong
eEventTypenumbers:MELEE_ACTIONwas 41 (that isEVENT_LEADER_UNHOLSTERED_WEAPON) andSHOCKING_INJURED_PEDwas 94 (EVENT_SHOCKING_PED_KNOCKED_INTO_BY_PLAYER). Now 42 and 97, checked against the enum dump
Tests
tests/ai_bridge_server.test.lua(9) andtests/ai_bridge_client.test.lua(13),lua5.4, stubbed engine with a coroutine scheduler
v1.62.1 — 2026-09-05
Fixed — your saved standing is no longer announced as a fresh loss
Spawning in threw a trust toast at you for every persona you had a stored reputation with — "Maggie · Trust down (−10)" for something that happened days ago, and a screen-centre rank overlay for a saved positive. Nothing had changed; the restore was reporting itself.
- A restore is not a change.
client:persistRestorepushed every saved row throughReputation_Set, which sees the fresh table at 0, calls that a move and firesIntelFx_OnRepChange. The saved set now replaces the table throughReputation_Restoreinstead: same numbers, same clamp to −10..+10, no toast and no overlay. A move made in the session still announces itself exactly as before, and the panel's trust bar is untouched - Legacy
ambient_<uid>rows are dropped by the restore itself. They were handed toReputation_Set, which refuses an ambient key — so the prune pass that ran afterwards always found nothing to drop and reported nothing, while the refusal printed a yellow console warning on every single load. The rows are now counted and dropped where they arrive, and the line in the console says how many tests/reputation.test.lua— first harness for this module: bootsclient/reputation.luaandclient/persistence.luaagainst a stubbed engine and asserts a silent restore, the clamp, the dropped ambient rows, and that a real change still fires exactly one toast.lua5.4 tests/reputation.test.lua
v1.62.0 — 2026-09-04
Fixed — patience is per NPC and it is remembered
Every NPC in the city showed 9 / 9 patience, and it was back to 9 / 9 the
moment you spoke to them again. Two things, both in build_session: the total
was Config.dialogue_angry_close_threshold for everyone, and asked_count
started every session at 0.
- Each ped rolls its own patience once and keeps it. Base
dialogue_angry_close_threshold, plus an offset for who they are —Config.dialogue_patience.by_archetypefor ambient peds (an aggressive type loses 4, a cop 2, a stoic or a coward gains 1),by_personalityfor personas (grumpy −3, tough −2 … gossipy +2) — plus aspreadof ±2, clamped tomin..max(3..12). The roll lives in the ped's memory next to everything else it remembers about you, so the same NPC has the same patience next time and two strangers do not. The README has promised "Aggressive — zero patience" and "All tunable. Patience…" since the archetype table shipped; this is the first build where the dialogue patience actually reads that table - Burnt patience carries over. Six questions, walk off, come back: six are
still gone, and the meter opens at 3 / 9. It recovers one question per
Config.dialogue_patience.recover_ms(2 minutes) of being left alone; an NPC that threw you out stays at 0 until it does, and the first question you ask before then ends it again.recover_ms = 0disables recovery. The burnt count is written with the rest of the close bookkeeping, so every exit v1.61.0 taught the resource to record — walking away included — records it - The grumpy point scales with the roll at the ratio the two base numbers
have (5 of 9): a 5-patience ped turns grumpy at 3, a 12 at 7. A persona that
authors
mood_shift_thresholdorangry_close_thresholdindata/personas.luais used as written — never rolled, never read back from memory, so editing it takes effect on the next conversation - The mood-shift and last-question checks fire on
>=with a one-shot each, because a session can now start past either point; they used==and would have skipped straight over a seeded count - A disguised player burns from a fresh count: the disguise hides who is asking, and the patience was burnt by that someone. The ped's own roll stays
- Persisted for personas (
patience_total,patience_used,patience_used_at_ms) through the same snapshot as the rest of the memory; ambient peds keep theirs formemory_ambient_max_age_mslike everything else they remember
Changed — panel
- The patience row sizes itself to the ped's total. Nine 4 px segments with their 3 px gaps just about fit the stylesheet's 56 px; twelve would have been under 2 px each
v1.61.0 — 2026-09-04
Fixed — NPCs actually remember the conversation you just had with them
Talk to someone, walk away, come back: they greeted you like a stranger and the whole topic list was askable again from the top. The causes were all in the wiring rather than in the memory model — the tags, the counters and the topic history were being computed correctly the whole time and then dropped on the floor.
- Walking away recorded nothing at all. Drifting out of range, the ped
leaving or dying, getting into a car, a patrol claiming the ped — every one of
those closes went through
IntelNpcNui_ForceClose, which drops the NUI handlers without runningon_close_request, and thenend_focus, which nils the session. So the way a player ends a conversation most often wrote no interaction count, no close reason, no carried mood and no topic list.client/focus.luarecords those closes now, with the reason it actually detected, andIntelNpcDialogue_CloseBookkeepingis idempotent per session so a close that named its own reason keeps it and is never counted twice - Ambient peds never recorded which topics they had been asked. The write was
gated on
session.persona_id, which isnilby construction for every random pedestrian — soMemory_RecordTopicwas never reached for the NPCs players talk to most, and the 30-minutememory_topic_ttl_msband never applied to them because nothing was stored to expire. The read side inplace_rowcarried the same wrong gate independently. Both key onmemory_keynow, likemark_asked's own repeat check,build_session'spre_askedand every other reader already did - A re-opened conversation replayed its opening line word for word. Inside
the five-minute resume window (
Config.memory_session_resume_ms) the cached greeting was replayed verbatim and the re-meet line was explicitly suppressed, so a resume was indistinguishable from a first meeting. A resume now opens with the recognition line — "Back again, huh." — instead of the stored greeting; a genuinely new conversation still gets the recognition line in front of a freshly picked greeting, as before - A recycled entity handle could inherit a stranger's memory. Where a ped carries no state bag the memory key falls back to the raw entity handle, and GTA reuses handles. The ped's model is stamped alongside the key, so a handle that comes back on a different model starts clean rather than greeting the player as a regular. There is no engine getter for "is this the same ped", and the model cannot prove that it is — it can only prove that it is not, which is the half that matters here
- A conversation open at a resource stop was lost whole.
client/focus.luatears the session down in its ownonResourceStop, which the manifest order puts after the persistence flush, so nothing it recorded could reach the snapshot. The in-flight conversation is recorded inclient/persistence.luaimmediately before the flush instead
Added — the 3D surfaces over NPCs are switchable from F12
- An "Above heads" section at the top of the Modules tab. The three surfaces
drawn over an NPC's head are together and reachable without editing
shared/config.lua: the 3D overlay as off / basic / full with the current mode marked, and switches for the status dot (Config.mood_icon_enabled— talk / busy / hostile, 18 m) and the name above an NPC (Config.show_npc_name_tooltip, which only draws while the 3D overlay is off, and the row says so). Both switches defaulted off and were previously config-only, which is exactly the kind of look-at-one-thing surface a live toggle is worth most for - The dot applies on the tick you flip it, through a new
IntelNpcMoodIcon_SetEnabled. WritingConfig.mood_icon_enabledalone was picked up only on its module loop's 2 s idle wait — long enough for whoever flipped the switch to decide it had done nothing - The overlay action reads the mode back after setting it and reports what actually took, and refuses a mode that is not one of the three by name
Fixed — panel
lp-btn onwas already written for the ped-story view picker and had no CSS rule at all, so the picked view looked identical to the unpicked one
v1.60.0 — 2026-09-04
Changed — the panel is rounder, glassier and alive
- Roundness and glass. 22px panel, 14px rows and cards, pill chips and keys; the panel is translucent (
--panel-bgalpha .80, raise it innui/theme.cssfor a more opaque box) with the sunset horizon wash and an inner highlight. No backdrop blur — the NUI layer composites over the game and has nothing to blur - Animation vocabulary. The panel springs in and eases out, the plate and portrait land a beat later, the sunset rail breathes, the name shimmers and glows while the NPC is speaking, replies cascade in with a stagger, the cursor row lifts with a light sweep, a picked row and a picked action flash, the trust fill glows on a change and both meters grow from empty on open, the mood pill pops when the mood changes, a burnt patience segment flashes, the body-language line fades in when it changes, the hint keys press when you press them, and a blinking caret rides the typewriter
- Restored from v1.58.0. The portrait initials, the body-language line in the plate, the witness chip's label, the patience count next to its segments, the impatience warning pill, the tone tag with its icon on every reply that has one, always-visible reply subtitles, the follow-up / cost / skill / lock / leave tags with icons, the "recent:" trail above the replies, icons on stage directions, the actions header with a count, trust and composure label icons and fill glow, a bigger log and reply list, T as a shortcut into the action row
- The replies no longer fold away. Folding shortened a bottom-anchored panel from its pinned edge, so the plate and the line being read dropped by the list's height on every exchange and jumped back with the next set. The list now stays where it is and recedes to a quarter opacity with the picked row lit, and a "thinking" cue with the speaker's name pulses in the log until the answer starts
- A default cursor. The first reply is highlighted when a set arrives, so ⏎ always has something to pick; the mouse moves it, ↑ ↓ move it, and only one row is ever lit (the hover style was removed in favour of the cursor, which the mouse drives)
- ⏎ during the audio hold picks in one press instead of first cutting the bark and needing a second press
Fixed
- The "scrollable" fade on the reply list was measured while the rows were still arriving; it is measured after the cascade now
- Meter fills animated in from the previous conversation's value on open; they reset to empty first
- The trust meter showed the previous action's value. The panel read the local cache, which the server only updates later on
client:repSet, so the delta popped one action late and on the wrong reply. The server's answer now reaches the open panel the moment it lands, with the real delta - Follow-ups with a type were dead content. A follow-up carrying
type = 'beg'/'tip'orclose = true(Joe's cigarette, bread and beer asks, the gang tree's farewell) ran through the plain-answer path and ignored the type; they go through the same branches as a topic now and keep their thread - The player's farewell line was printed twice on a polite close through the authored farewell topic
- The name prompt counted as idle. Typing your name in the introduce-yourself dialog could get you kicked mid-typing; the idle clock restarts when the prompt returns
- The idle watcher quit on its first tick when no camera focus existed (focus disabled in config, or a static persona whose ped is not spawned), taking the impatience line, the idle kick and the drew-a-weapon reactions with it
- An error inside a selection handler left the panel locked for the rest of the conversation. The bridge now prints the error and unlocks
- A selection resumed after a server round-trip kept messaging a conversation that had already ended (idle kick during the call); those paths return once the session is gone
- The
ambient.<category>speaker-name fallback never fired, because a missing locale key returns the key rather than nil - The close watchdog could fire while the
closemessage was still queued behind a line being typed, dropping NUI focus on a panel still on screen; the watchdog is fed from the moment the close is queued - An unlock without a new reply set left the picked row lit and the keyboard cursor unsynced
- Long names and roles were ellipsized because the chip column never shrank
v1.59.0 — 2026-09-04
Changed — the dialogue panel is a scene, not a chat window
- One panel, bottom-right. The floating header and the chat shell were two boxes of the same width on the same edge, and the shell grew upward into the header whenever the topic bar was expanded. Name plate, dialogue log, replies and the action row are now one box, with a pixel height budget written from JS (
--scene-max) so the log and the reply list shrink before the panel can grow off the top of the screen - A script, not bubbles. The log shows the speaker in small caps, the line under it, and stage directions in italics between (body language, a trust change, a warning). The avatar initials, the chat bubbles, the "You" bubble alignment, the three typing dots, the blinking caret, the "Waiting for response…" spinner and the "recent:" trail are gone — every one of them read as a chatbot
- Numbered replies plus an action row replace the collapsible "Bring up a topic" bar with its count badge and T key. Conversation lines are the numbered rows; the things you do (tip, bribe, flirt, threaten, beg, the persona's shop or job) are small buttons under them. ↑ ↓ and ⏎ pick a reply, TAB moves to the actions, 1–9, SPACE and ESC are as before
- Replies fold away while the NPC answers and open again with the next set — a reply is on screen exactly when it can be given. The picked row stays lit for a beat first so the player sees which one registered, on the number keys as much as on the mouse
- Choice previews render inline under the row on hover or cursor. They were a floating tooltip positioned outside a row with
overflow: hidden, so they had never been visible - The hover lift works. Each row's enter animation used
fill-mode: both, whose final transform outranked the hover transform forever - Less ornament: no film grain, no breathing neon rail, no letterbox bar across the top, one sunset hairline on the panel and the gradient on the name.
nui/theme.csskeeps every token;--bubble-npcnow colours the reply under the cursor
Changed — the E menu is Talk
- The target menu on a ped carries one option: Talk. Give item, Rob, Mug, Pickpocket, Bribe, Tip, Threaten, Flirt, Help up, Take photo, Frisk, Drag away and Drop are gone from the menu; money, threats, flirting and begging live inside the conversation as actions, where they always were wired into reputation, perception and the witness network. On the built-in look-at fallback, E opens the conversation directly and the prompt reads
[E] Talk; the prompt hides while a conversation is open - A persona's own
interactions(buy an item, browse the shop, take a job) moved with them: they are actions in that persona's conversation rather than rows next to Talk.IntelNpcInteraction_Run(persona_id, index)runs one from anywhere
Changed — conversations flow
- No more lost clicks. Selecting a reply was gated on a 900 ms wall clock (
Config.dialogue_click_cooldown_ms), so a player who skipped a line and answered quickly saw the row light up and nothing happen. Selection is now gated on the previous answer finishing: the panel locks itself on a pick and unlocks with the next reply set. The config key is kept but no longer read - Replies appear when the text is done, not when the audio is. With
Config.dialogue_speechon, the typewriter used to hold the whole panel until the bark ended (up to six seconds), which read as the panel hanging. Only the NPC's next line and the close wait for the audio now; picking a reply cuts the bark, the way interrupting someone does - Follow-up siblings stay reachable. Picking one of three follow-ups used to bury the other two: the response showed only that follow-up's children, and the parent topic was then locked as "Asked". A thread now keeps every follow-up whose parent has been asked on the table, closest first
- A leaf answer returns to the topics. The "Ask something else / End conversation" dead-end after every answer is gone; "Ask something else" stays only while a thread still has follow-ups
- Asked topics stop cluttering the list. One whose thread still has unanswered follow-ups comes back as a way into that thread ("more to ask"); one with nothing left sits muted at the bottom, still selectable — re-asking it gets the "I just told you" line and earns no standing. That line pool (
feature.dont_remember) had never fired: the flag it reads was reset before it was read - The NPC's counter question follows their answer as one voice, without the "They look at you, waiting" pill
- A beat before some answers.
bridge.<mood>pools ("Hm.", "Ugh. Fine.", "Now that's interesting you ask.") are prepended to an answer atConfig.dialogue_bridge_chance(0.4), never on the first answer and never on an interaction outcome - One unprompted remark per conversation. After
Config.dialogue_aside_after(2) answered questions the NPC says something nobody asked for —aside.*pools keyed on wanted, bloody, armed, masked, rain, the hour, then their mood — before the topics come back.Config.dialogue_aside_enabled/Config.dialogue_aside_chance(0.7) - Leaving is a line, both ways. The leave row is the tree's own farewell topic where it has one, otherwise one of
leave.linespicked per session; the NPC answers fromfarewell.<mood>, and either way it is the polite close that reports the standing point. ESC is walking off without a word and stays a stage direction - Impatience is spoken. At the idle warning the NPC says an
impatient.<mood>line in the log (and in their voice, with speech on) and the patience meter dims, instead of a pulsing HUD pill - The patience meter counts what they will still answer. The live watcher added one to it, so it read "1" at the point where the next question ends the conversation
- The mood-shift notice, the last-question warning (which was hardcoded English) and the "nothing more to ask" line are stage directions in the log; "One more question and they're done with you" is
dialogue.last_question_warning - New config keys are backfilled in
shared/defaults.lua, so an existing config gets them without editing
Removed
nui.topics_bar,nui.waiting,nui.key_topics,nui.recent,nui.impatience,dialogue.npc_asks_back,dialogue.back_to_topics_sub,dialogue.locked_reputation,dialogue.already_asked,dialogue.all_topics_done,dialogue.npc_annoyed, the thirteentarget.*menu labels and theinteract.*strings behind them, from both languages.interact.unknown_reasonstays (jobs, bounties and the shop use it)
v1.58.0 — 2026-09-04
Changed
- The 3D debug overlay no longer ships on.
Config.debug_3d_modewas'basic', andConfig.debugships on so a fresh install is drivable — so out of the box every pedestrian within 25 m carried two rows of text over their head: name/category, archetype and distance on the first, live state (panic,hostile,busy,irr 2/3,rep -4,voice cast,driver:…,fighting you,fleeing) on the second. That is a development tool, and on a live server it reads as a broken HUD rather than as diagnostics. The default is now'off'. Nothing else aboutConfig.debugchanged: the dev menu,/intel_*commands and the map blips are where they were, and an existingshared/config.luathat already setsdebug_3d_modekeeps its own value setr lastresort_intel_npc:debug 1now setsdebug_3d_mode = 'basic', so the convar still gives a dev server the overlay along with the rest of the tooling;debug 0sets'off'. The convar previously wroteConfig.show_debug_3d, a legacy key that is only consulted whendebug_3d_modeis nil — and the shipped config always set it, so that line had no effect on the overlay at all
Fixed
- The overlay held a per-frame slot while drawing nothing. The frame callback was registered whenever
Config.debugwas on, then returned immediately if the mode was'off'— a no-op every frame, on every install, for as long as the overlay was switched off. It is now registered only when something will actually be drawn, which is what the README's performance section always claimed. Four copies of the mode-resolution expression collapsed into oneoverlay_mode()
v1.57.0 — 2026-08-31
Added
shared/defaults.lua, the config backfill this resource never had.shared/config.luais the file you edit and keep, so a release can never assume a new key exists — the copy on your server is the one you wrote months ago, and a feature added in an update would read nil there, run nothing and print no reason. Any key added from here on gets its default in the backfill, which loads immediately after the config and fills nil keys only, so a value you have set always wins.
Fixed
- README masthead still read v1.56.0 after the version bump.
v1.56.0 — 2026-08-29
Fixed — exports that could not do what the table promised
IsInPoliceScene()had been permanentlyfalsefor eleven versions. It answered by callingIntelPolice_HasActiveScene()andIntelPolice_HasWaitingPed(); both were deleted when the police module went native-dispatch-only, and because the call was guarded withand, the export silently returnedfalseinstead of erroring. README told server owners to check it before their own dispatch sends a unit, and INSTALLATION.md repeated the instruction. It now returnsfalsewith the reason written next to it, both docs say so, and both point at the real hook,lastresort-intel-npc:server:dispatchRequested. The export is kept rather than deleted so a server already calling it does not error- The
scenefield on the ped-hit payload wasIntelPolice_OwnsDispatch(ped)— a third deleted probe, so the field was alwaysniland nothing on the server read it. Removed - README described
AddReputation(id, d)as "adds a delta, same clamping". It has refused and returnedfalsesince v1.42.0, when reputation became server-owned.SetReputation(id, v)was described as setting reputation; it writes the local display value, which the next server restore overwrites. Both rows now say what the functions do
Fixed — money and rewards reported as facts before they happened
server:itemBoughtfired on every purchase attempt, including the failures.default_handle_buy_itemhas four early exits — no item, not enough cash, the withdrawal refused, the inventory full — and the caller triggered the event unconditionally right after it. Any external shop-log or analytics resource recorded sales that never happened. The handler now returns a boolean and the event is gated on it. A refund that the money adapter refuses after a full-inventory rollback is now logged loudly instead of vanishing- A job reported the cash it rolled, not the cash it paid.
Adapters.money.AddCashreturnsfalsewhen the framework refuses the deposit; the return was discarded, and the reply, the chat line andserver:jobCompletedall carried the rolled amount regardless.cashis now what was actually deposited, a refusal setspayout_failed, and the player is told rather than shown an unexplained $0 - A job payout was rendered as a purchase.
client/jobs.luaannounced it throughIntelFx_PurchaseToast, which draws-totalunder the word "Purchase" and follows it with "Added to your pockets" — so earning $500 read as spending $500 on nothing. NewIntelFx_RewardToastdraws it as money in. The shop keepsPurchaseToast, because that one really is a purchase
Fixed — config that documented a system that no longer exists
Config.police_patroladvertised two police fleets and nothing read them. Cruise patrols withmax_active, spawn and despawn distances, car and ped models, speed; a response unit with accuracy ranges and a despawn timer — 17 keys in all, plus a 16-line comment arguing why cruise patrols ship on by default. Every spawner behind them was deleted when the module went native-dispatch-only, so a server owner could setcruise.max_active = 6, restart, and change nothing. The dead keys are gone. The three that are read —bribe_amounts,bribe_refusal_chance,bribe_distance_m, all server-side — keep their path so existing configs still workConfig.police_nonlethalcarried orphaned comment fragments describing dispatch-service switching, aSET_WANTED_LEVEL_DIFFICULTYdial,require_police_systemandyield_to_resources. None of the four was ever implemented in the band. The block now states that its seven keys are the whole feature, and README and INSTALLATION.md stop offering the phantoms as integration advice
Fixed — panels and tests that could not fail
- Two in-game test steps passed unconditionally.
pol_nonlethalprintedc.nonlethal_on/nonlethal_engaged/cops_held/self_defence_excluded/dispatch_offfromIntelPolice_Counters(), a table that has never carried any of those five keys — they belong toIntelPolice_NonLethal_Counters()in the other module — so it printednils and returnedtrueevery time.pol_cleanupassertedcruise + response + spawned_peds == 0against three counters the module hard-codes to zero, and told the tester to expect spawned cops to disappear. Both now read the real API and have real failure paths, andpol_cleanupsays what/intel_police_clearactually does: it drops the wanted level - The scene panel's police block read five keys that were never sent. Same cause: the panel asks for the non-lethal band's state and was handed the wanted module's counters. The band always read "off" and the two counts read
undefinedno matter how many officers were held.client/scene_panel.luanow merges in the band's own counters. The three permanent-zero patrol rows are gone,dispatch suppressedis relabelleddispatch suppression requested—ENABLE_DISPATCH_SERVICEhas no getter, so it is a request and never a confirmation — and thewaiting ped/scenerows went with the scripted scenes they reported - Three buttons labelled "cycle the 3D overlay" opened the dev menu. All three sent
/intel_dev. There was no command for the cycle at all —toggle_3d_overlaywas a file-scope local reachable only from a menu row. It is registered as/intel_overlay, allowlisted, and the buttons point at it Reputation_OnPoliteClosereturned "persona trust +1" while the line above it explains the point is only reported — the server prices it and may pay nothing. It says reported now
Fixed — a second pass: the public API, the metrics, the phantom hotkey
- The perception fallback for events GTA has no crime type for had never raised a single star. It fired
client:adjustWantedMin, a handler whose first line isif Config.police_system_enabled then return end— that event is the EXTERNAL path, for when this module's police system is off. The branch that fired it sits insideif ... and Config.police_system_enabled then, so the event could only ever reach a handler that returned immediately. It goes throughIntelPolice_Bumpnow — the module's own clamped setter — and the trace reports the level that actually landed, or names why nothing did - The speeding toast printed the star count as a speed.
police.speeding_descread "A patrol clocked you at %dkm/h" and was formatted withstars, so 140 km/h past an officer said "clocked you at 1km/h". The measurement was already in scope; both languages now take the speed and the stars - Every metric the client reported was discarded at the door.
metric()sent(group, key)as two positional arguments and the server's handler opens withif type(payload) ~= 'table' then return end. Independently, the two keys it sent were not in the server's bucket, which drops unknown keys. Both fixed peaceful_resolutionsnever counted one. It anddamages_paidwere triggered back to back through the metric NET event, whose handler is rate-limited at 100 ms per source — and a server-localTriggerEventcarries no source, so both shared one bucket and the second was always dropped. Server metrics now bump directly- Seven metrics in a public export sat at zero forever.
GetMetrics()and/intel_metricsreportedpre_call_timeouts,combat_resolutions,bribe_paid,bribe_refused,bribe_no_moneyandsessions_closed_normalas live counters. The first two counted the scripted police scene deleted in v1.17.0 and are gone; the three bribe outcomes are wired to the bribe that does exist;sessions_openedis wired;sessions_closed_normalis gone because the server cannot observe a close - The dev police toggle claimed a change the server may have refused. Both the F12 panel and the ox_lib menu set the client flag, fired a net event with three silent exits (debug off, no ace, rate limited) and reported the new state as fact. It is a callback now: it answers, refusals name their cause, and the UI says which halves actually moved
- "B — Apologize" was on the HUD and nothing was bound to B. No
RegisterKeyMapping, no command, no handler — the one-press apology belonged to the pre-call panel deleted in v1.17.0 and only the on-screen promise survived. The hint,Config.apology_quickkey_enabled,Config.apology_keyand the locale string are gone - *Fifty orphaned `police.` locale strings** — the responding officer's lines, the talk-down, the surrender and hang-up notices, the patrol blip, the tow — removed from both languages, which stay in sync
- Three documented server callbacks did not exist.
preCallBribe,bribeRespondingCopandlistQuestswere rows in the README's callbacks table; an external resource calling any of them waits forever. Removed, with the real errand callbacks named in place oflistQuests.bribeSpawnedCopis real but was described as bribing "a scripted patrol officer" — it bribes an officer GTA dispatched, and the row now says what the server actually checks server:policeBribereportedkind = 'patrol'for a module that has spawned no patrol since v1.17.0, and the Discord log said the player "bribed their way out of a police scene". It is'native_cop', and the log says an officer took the money- The README's A–E flow described the police module as it was before v1.17.0 — a pre-call talk-down window, a cruiser that drives in, an officer who walks over, a seven-choice cop scene. All of it was deleted years of versions ago. The flow now describes contact detection, the severity tiers, the report roll, the hand-off to native dispatch with a search centre at the scene, and the non-lethal band
Config.police_wanted_clamp_enableddoes not exist and both docs told owners to set it tofalse"so the two don't fight over the wanted level". Nothing needs disabling: the ceiling caps only what this module asks for and never lowers a level anyone else set- A dev-test step told the tester to look for the pre-call panel and to mark the test failed when it did not appear
Fixed — a third pass: the medium sweep
-
Behaviour, not wording:
-
A clamped raise could LOWER the wanted level.
bump_wantedcompared the current level against the UNCLAMPED request and then handed that number to a setter that clamps it. At three stars with a request for four the guard did not fire (3 >= 4 is false), the setter clamped 4 down to the module ceiling of 2, and the player dropped from three stars to two — a raise that lowered a level this module never set, which it must never do -
client:adjustWantedwas a registered net event with no sender. It set an ABSOLUTE level, so unlikeadjustWantedMinit could lower stars, and any other resource on the server could fire it. Deleted -
A purchase from a persona has never moved their opinion of you. The server passes
interaction.type— the literal'buy_item'— intoGrantProven, which looks it up inConfig.reputation_interaction_delta. There was nobuy_itemkey, so the grant returned nil every time. The key exists now (+1, smaller than a tip), and the grant runs after the sale instead of before it — a player with no cash or a full inventory used to earn the goodwill anyway -
A manual bounty claim destroyed the kill proof every other poster needed. The block above
ledger_namesstates the rule — one death settles every bounty already standing on that victim — and calls consuming the record "the WRONG fix". The manual claim path did exactly that.posted_beforeis the rule and it needs no deletion -
START_ALARMis void and the alarm was never read back.IS_ALARM_PLAYINGis a real getter, this file's own header says so, and it was only ever asked elsewhere. An alarm that does not sound now says so — and says the NPCs are still reacting to a sound the player cannot hear -
Void natives reported as facts:
-
SET_PLAYER_WANTED_LEVELis void, and both the police module and the dev menu reported the level they ASKED for.GET_PLAYER_WANTED_LEVELis free; a refusal is now named, with the usual cause (another resource's max-level clamp) -
The non-lethal hold traced four void writes as done. Two can be checked —
GET_PED_ACCURACY, andGET_BEST_PED_WEAPONfor what the officer still OWNS after the disarm — and a cop who kept his pistol is now reported loudly. The two combat attributes have no getter and are reported as requested -
The gesture diagnostic's header claimed "every answer here is read from the engine". Six of seven are;
layer_onis our own bookkeeping, because none of the sevenSET_PED_CAN_PLAY_*_ANIMSnatives has a getter. Its final verdict said "the engine is gesturing", which nothing can know -
Restores that were resets (all three kept the scope, which is the real protection — only names this resource wrote are touched):
-
client/audio.lua's header promised every emitter and zone was "put back". There is no getter for either, so what goes back is the engine default -
client/crowd.lua's header promised every recruit's blocking flag was "snapshotted before the write".SET_BLOCKING_OF_NON_TEMPORARY_EVENTSis one of GTA's write-only setters — there is nothing to snapshot -
/intel_lab dispatch_enumlogged "all dispatch services restored to enabled" -
client/duck.lua's header promised the release "escalates toSTOP_AUDIO_SCENES()" eleven lines after explaining why that native is never called here. The NUI repeated the promise -
Counters and labels that did not mean what they said:
-
/intel_patrolprinted "%d guards walking one right now" from a count of peds assigned a route.verified— the count that means walking — was already on the next line -
/intel_lookat_testprinted "%d people glanced at you" from the number of pedsTASK_LOOK_AT_ENTITYwas issued on -
The red-light trace and toast said "an officer saw you". Nothing established that — only
any_cop_within(), a distance sweep. Proximity is not witnessing -
The debug overlay's header promises "an empty row 2 means a perfectly ordinary pedestrian". Two chips were unconditional:
own voicefor any ped with no cast voice, andin a carfor any ped in a vehicle. Row 2 was never empty -
The speeding toast formatted the star count into a
%dkm/hstring -
Surfaces and switches:
-
IntelFx_OnWaitingdeleted. A countdown ring plus a key hint bar for the pre-call window and cop scene, both deleted in v1.17.0. Nothing in gameplay called it; its own dismiss loop polledIntelPolice_IsInScene, a fifth undefined police global, so even in the demo the ring vanished on the first tick. A countdown the player cannot act on says there is a window, and there is not.Config.simpleasf_fx.police_timersand seven locale strings went with it -
IntelFx_OnDispatchfinally has the hook its config advertises. It is raised where the wanted level READ BACK higher afterREPORT_CRIME— the one moment the engine demonstrably took the call. Only the two radio codes the file's own header sources are used; the rest read911rather than an invented number -
IntelPolice_IsInSceneremoved from all four files that called it — the mood icons, the FX ring, the inspector'spolice-scenetag and the focus guard. All four were guarded withand, so all four silently answered false -
The item, purchase and reward toasts returned
trueeven when the simpleasf export threw and the integration was marked down — so every caller's fallback to a plain notify was skipped and the message vanished -
The F12 hail button said "Hail forced." on all five gates.
try_hailalready knew which one closed, for its trace line, and threw it away. It returns(ok, why)now and all four callers report it -
The hail trace said the line went "in the chat feed". That feed is opt-in and defaults OFF; the floating hail frame is what the player actually reads and was the one surface never mentioned
-
The Modules tab's "Police system" and "Verbose server log" switches write only the client's config. Both labels say so; the Police tab's ace-gated version is the one that moves the server
-
SetPoliceSystemEnabled()likewise, in the export table and in README -
award_rep_gainawards no reputation — it grants a levels bonus and readsConfig.reputation_interaction_deltaonly as an on/off gate. Renamed -
The ESX test-pistol route reported a
pcallstatus as "added through ESX addWeapon". ESX'saddWeaponreturns nothing -
client/dev_menu.luaclaimed "the preflight harness now fails the build". There is no build step for a FiveM resource and no such harness in the tree. Its "Dump cruise patrols" button dumps the wanted state -
The control panel's header claimed "nothing here has its own private truth". The Tests tab is a hardcoded array in the NUI, kept in step with
client/dev_test.luaby hand -
Docs:
-
README still said Version 1.16.7, "Five tabs" (there are nine), "all 74 of its actions, verified by a coverage check" (there is no coverage check) and "139 clickable actions" (a number derived from nothing), and "Fourteen" experiments where the bench has sixteen
-
extra_light_hasheswas advertised for custom-map traffic lights and exists nowhere; the junction and its lights come fromGET_VEHICLE_NODE_PROPERTIES, which needs no hash list -
/intel_police_clearwas "Reset all police state";/intel_nonlethallisted four fields it does not print; INSTALLATION.md said/intel_police_dumpprints cruise patrols -
*145 orphaned `police.
locale strings** removed from both languages — the complete dialogue trees of the pre-call talk-down, the mid-call conversation and the seven-choice cop scene, plus the responding officer's lines, the surrender and hang-up notices, the patrol blip and the tow. Sixpolice.*` keys are live and they are the six that remain
Fixed — a fourth pass: the low sweep
- "Wipe hail cooldowns" left the global gap in place.
IntelNpcHail_ResetCooldowncleared only the per-ped table;try_hailreturns onLastGlobalHailMsbefore it ever looks at a ped, so for up to 25 s the wipe changed nothing - The Patience pill was off by one. The kick fires at
asked_count > hard, so the pill read "0 left" while one question remained - The informant Discord embed has never logged a coordinate. It read
payload.coords; both senders useevent_coords client:patrolBribedhad no handler anywhere. It was the stand-down signal for the scripted patrol unit deleted in v1.17.0AddPersona()returnedtrueafter a failed registration.IntelNpcSpawn_RegisterRuntimereturns false for a persona with nocoords— the entry lands in the table and nothing will ever spawn it- The overlay's reputation colours were written for a ±100 scale.
rep <= threshold + 10put the red band at or below +5 on a scale that stops at +10, andrep > 30made the green band unreachable. The archetype thresholds it is fed run −2 to −9.client/npc_state.lua's fallback was-50, which no ped could ever cross - Three void writes now use the getters that exist:
SET_NEW_WAYPOINT→IS_WAYPOINT_ACTIVE(the bounty toast said "Marked on your map" regardless),SET_ENTITY_HEALTH→GET_ENTITY_HEALTH(the collision softener said "put back to %d"),SET_AMBIENT_VOICE_NAME→GET_AMBIENT_VOICE_NAME_HASH(a persona "speaks as X" even when the build ships no such voice) - Five more that have no getter now say what they asked for: the manhunt release, the incident delete (where
IS_INCIDENT_VALIDis ambiguous by design), the crowd's "gathered",/intel_engine_police's three config-intent values, and/intel_ambience release— which restores the radio on/off flag from a real getter but writesloudto false and never puts the station back - The cop bribe answered "(cleared N stars)". The clear happens on the client and the server never reads it back
trace_clearandped_forgetreported success when the trace module was not loaded; the command runner promised console output for commands that print none- The inspector's
event queue: gunshot/shot/…labels invited reading 124 (SHOT_FIRED, the shooter's own event) as "this ped was shot" Config.mood_icon_enableddraws no mood — the colour comes from whether the ped will talk to you and from the reputation tier. The key keeps its name so existing configs load; the comment stops lying about it- The Lab tab pointed at
docs/NATIVE_AUDIT.md, a file that does not exist - The Tests tab's "simpleasf master switch" was rendered as a button, so it carried no state and its first click could be a silent no-op
client/dev_menu.luaclaimed the ox_lib menu "owns" the per-persona memory and reputation editors "which the panel does not carry" — the Personas tab has all of them- The whole FX layer was hardcoded English while the locale pack's header tells a server owner that copying and translating one file covers everything. Ten strings moved to
fx.*keys in both packs - The dispatch card's meta line read "Caller on scene · plate noted". There is no caller entity and no plate is ever queried
- The dialogue header's
Zone · HH:MMpill was computed once at open and never updated. GTA's clock runs ~30× real time, so a two-minute conversation left it an hour wrong. The zone stays; the clock is gone GetReputation()is the client's cached copy of the server's last answer, not a live read. Both the export and README say so- INSTALLATION.md's verify step told you to look for
max_stars=andsd_excluded=tokens that/intel_nonlethaldoes not print
Fixed — the seventh dead police global, found by scanning for the bug class
-
The lie hunt found five functions that no longer exist being called through an
andguard, so each answered false in silence forever. Rather than trust that five was all of them, the whole tree was scanned for the pattern — and it was not. -
IntelPolice_TryEngageandIntelPolice_GetNearestWaiterare defined nowhere, and were called from eight places acrossclient/quick_talk.luaandclient/dialogue.lua. They were the entry into the scripted cop scene — "the officer is waiting for you; press G" — deleted in the v1.17.0 cut. Because every call site guarded them,waiterwas always nil, every engage branch fell through without a word, and thequick_talk.step_out_*notice could never appear -
The wrong-officer redirect in both files went with them: it fired on
PoliceSpawnedPedSet, which this module has not put a ped in since the same cut, and its only job was to hand you to a waiter that cannot exist. A cop GTA dispatched now falls through to the ordinary ambient dialogue, which is the right behaviour for a ped this resource does not own. Four more orphaned locale strings removed from both packs -
The scan is now a harness. It walks every
.luain the resource, builds the set of names the resource actually defines, and fails on any guarded use —X and X(,if X then X(,X and X.,X and X[— of a resource-prefixed name that is not in it. The six dead police names are also asserted by name. This class of bug cost seven silent failures; it cannot hide again
Fixed — reputation dev controls (carried over from the same sweep)
- The NUI reputation buttons still read
= 50 friendlyand= 80 trustedfrom the pre-v1.42.0 0–100 scale. v1.54.0 fixed the ox_lib menu and left the primary surface untouched. They are+1 / −1 / = −10 hostile / = 0 neutral / = +5 friendly / = +10 trustedon the real −10..+10 scale - The trace filter toggled the wrong way: the payload reported the class's state before the click, so the first press on any filter sent the opposite of what the button then showed
- "Wipe all reputation" cleared only the local table; the server restored every row on the next load. It calls the new ACE-gated
server:devWipeReputationnow - The rep toast printed the old number as "is now N", because it read the local value before the server's answer arrived. It no longer quotes a value
- The memory dump button never printed anything
- Three panel replies said the resource had done something it had only asked for (hail, scene, hit test)
- EN and HU locale strings promised wanted stars the engine had not been asked for and might refuse; they say reported/requested
v1.55.0 — 2026-08-29
Fixed — nothing claims the engine acted when it cannot know
- Two contradictory truths about
REPORT_CRIME, in one resource.client/police.luadocuments that the native is void, that a successfulpcallproves nothing, and then reads the wanted level back ~900 ms later to reportcrime_native_tookorcrime_native_ignored.client/perception.luakept its own copy of the call and the crime-id map, and reportedfiled = truefrom thepcallalone — tracing that the crime "went through GTA's own dispatch". The duplicate is deleted; there is one implementation and one crime map, and the trace now says the crime was handed over, that the outcome is not knowable at that point, and where the engine's actual answer appears - The manhunt traced two
pcallbooleans under the keysis_wantedandpolice_focus. Both natives are void with no getter, so those keys named engine state that had never been read. They areset_is_wanted_call_ok/set_police_focus_call_oknow, the message says the calls were made rather than that they landed, and the open question — whether the flags apply at all when another client owns the vehicle — is marked[NEEDS IN-GAME TEST]instead of being quietly answered /intel_witnessprinted "N played by the ENGINE" from a counter fed bypcall(TaskShockingEventReact, ...). That native is void: the count was tasks that did not error.engine_reacts→engine_react_tasked,engine_reactions→engine_tasked, and the command says "TASKED to the engine" with a line explaining why that is the honest wordIntelNpcAudio_Emitterand_Zonereturned thepcallboolean as their success value, so every caller was told the emitter or zone had been switched. Both natives are void with no getter. They areEmitterCall/ZoneCallnow and return "the call was made" plus a reason; the old names remain as aliases so no caller breaks
Note on the tooling
- The first scan written to find these read a
return_typekey the native dump does not have. Every one of its 6416 entries came backNoneand it produced a list of 110 false findings. The field isresults; the real count is 12, of which 4 were lies, 1 is a documented void-with-out-params case, and the rest were already honest. A tool that cannot be trusted is worse than no tool, so the harness now pins the dump's shape as its first assertion.
v1.54.0 — 2026-08-29
Fixed — the dev tools were lying about reputation
-
Asked whether everything was testable in dev, the answer turned out to be no — and the reputation controls were actively misleading.
-
The F12 "Rep +10 / -10" buttons moved a number the server never had.
Reputation_Addbecame local display only in v1.50.0, so the button reported a change, the panel showed it, and the shop went on pricing at the old value. Every reputation-gated feature — shop prices, job eligibility, bounty trust — was untestable, because the thing the gates read never moved -
And they were on the wrong scale. Reputation is clamped to −10…+10, so the old
Rep = 50andRep = 80buttons both landed on 10: the same button twice. The buttons now span the real range -
server:devSetReputationwrites what the server actually stores and answers with it, behind the same ace as every other dev event. The control panel's rep action goes the same way
Added — so a refusal can be seen
/intel_authority. Almost every server callback refuses with a named reason —too_far,not_a_cop,no_wanted_reading,bad_shop— and the player saw none of them, so a silent no-op was indistinguishable from a working gate. The server now keeps the last dozen refusals per player and this prints them, with your citizen id, routing bucket, the wanted level as the server reads it, and which shops are in reach of their owner/intel_rep. Prints the server's stored reputation beside the local one and flags any disagreement — a local number the server does not have is exactly the bug above. It also shows whether a dialogue session is open, since since v1.52.0 talking earns nothing without one
Note
IntelNpc_DevAllowedis exposed after the local it wraps, deliberately: a closure written above alocal functioncaptures the nil global of that name. That trap has now bitten this resource six times and is commented at the site
v1.53.0 — 2026-08-29
Security — a bounty could be collected without a kill
- Half health counted as death.
victim_is_downcompared health against a flatdead_health_max = 100, andGET_ENTITY_HEALTH's own DESC gives a player's range as [0 to 200]. So a target at exactly half health — alive, running, shooting back — read as a corpse to the bounty gate. Wound a target to 100 HP, stay within the claim radius, open the board and click: paid, with no kill anywhere in the chain. Death is now zero, and a framework that leaves downed players above zero opts in with a fraction of that ped's own maximum, which cannot be confused with a health value the way 100 was - The damage ledger recorded hits that never landed on the victim. The
weaponDamageEventhandler resolved each hit entity and passedNetworkGetEntityOwner(entity)in as the victim — but that returns whichever client is simulating the entity, normally the nearest player. Shooting the target's parked car, or an NPC standing beside them, wrote a damage record against the target with no bullet touching them. The hit now only counts when the thing hit is that player's own ped - A bounty could be settled by a kill that predated it. The automatic payout never cleared the damage record, so a second bounty posted on the same still- down victim was satisfied by the same old record and the same body. Consuming the record would have been the wrong fix — one death legitimately settles every bounty already standing on that victim, and the manual claim is the fallback for a transient refusal — so the actual rule is stated instead: the kill must postdate the posting
Security — the SQL backend announced itself without checking
sql_install_schemaran its statements and returned true unconditionally.sql_execswallows a failure into a log line, so a database the resource could not write to still printed "SQL backend active" while every reputation, job and bounty write went nowhere — an owner would find out from players reporting lost progress. The tables are now proved throughinformation_schemabefore anyone is told they are live, and a missing table keeps the KVP store with the reason named- The reputation write no longer has a window where the player has nothing. It was a per-citizen
DELETEfollowed by anINSERTfrom the delete's callback, untransacted: two interleaved writes left the older snapshot standing, and a crash between them lost the citizen's entire reputation. The table is keyed(citizenid, persona_id), so the write is one idempotent UPSERT, and the prune of dropped personas runs after it — the worst a crash can now cost is a stale row
Fixed — the red-light detector punished obeying the light
- "Detector B" is deleted, and nothing replaces it. It remembered that you had stopped at a light and reported you when
IS_VEHICLE_STOPPED_AT_TRAFFIC_LIGHTSwent false and you moved — but that is exactly what happens when the light turns green. A player who stopped at red, waited, and pulled away legally was booked for running it - The surviving detector's header no longer calls its inference "correct". It quotes the rule it is bending — "another car stopped at a light does not prove YOUR light is red" — and carries a
[NEEDS IN-GAME TEST]for whether cars queued behind you during a green discharge still read as held
Fixed — a collision module was changing headshot lethality
SetPedSuffersCriticalHits(ped, false)ran as the first statement of the per-ped handler, before the contact test, so every human within the scan radius of any moving car lost its critical-damage multiplier. That native's DESC is "Headshotting a player no longer one shots them" — a vehicle-collision module was silently altering gunfire in a bubble around traffic. It now runs only on actual contact- And it overwrote this resource's own configured value.
spawn.luasets that flag on every persona frompersona_suffers_critical_hits, and collision wrote a guessedtrueback — drive past your own shopkeeper and he became headshot-killable, permanently, against the config. This is not the "we cannot know a stranger's prior value" case: the value is written down, and we clobbered it. Resource-owned peds are now skipped, using the guard that already existed
v1.52.0 — 2026-08-29
Security — reputation was still farmable from a modified client
- v1.42.0 moved the NUMBER to the server and left the OCCURRENCE with the client. That is not server authority — it is a client-authored occurrence with a server-authored delta. A modified client standing nowhere near anybody could fire
server:repEvent(persona, 'interaction'),'gift','topic'and be paid for each, then spend the standing on cheaper shop prices, reputation-gated jobs and bounty trust - v1.50.0 made it worse. The reason families it added (
interaction:<kind>) each got their own rate-limit bucket, so the 750 ms gap between reports gated nothing at all repEventnow pays penalties only. A positive that reaches it is discarded, and the positive table is unreachable from the network. A negative is still accepted deliberately: reputation is per-citizen, so forging one only lowers your own standing, and the reaction penalties have no other witness- Positives now come from a path that already proved the event. A completed shop purchase, an item the server itself removed from the inventory, an interaction looked up by index in the persona's own list — each grants the reputation where it happens, through a server-only function with no net event behind it
- Talking is the one reward with no other witness, so the server opens the conversation.
dialogueOpenchecks the persona is real and that the player is standing at its own authored coordinates — server-owned data a client cannot move — and returns a session id. Topics are reported against it: the session belongs to one player and one persona, it expires, proximity is re-checked so walking away stops paying, each topic id pays at most once, and the whole conversation is capped. That does not make the client honest; it bounds what dishonesty is worth to what a real conversation already earns
Security — you could bribe yourself
bribeSpawnedCopaccepted any nearby ped as a police officer. It checked that the net id resolved to a ped within reach — and a player's own ped satisfies both, at distance zero. So you could name yourself as the officer and have the server take a one-star bribe and clear a real five-star level. Any passing civilian worked too- It now refuses a player ped (walking the real connected-player list, since
IS_PED_A_PLAYERis a client native), requires a police model, and requires the same routing bucket before the distance is even compared - Every model in the list is verified against
tools/refs/peds.json, the game's own index, rather than typed from memory
v1.51.0 — 2026-08-28
Added — the police look for the CRIME SCENE, and for the CAR
-
Two things GTA V has always modelled and this resource never switched on. Both are single natives; neither is a new system.
-
A crime happens somewhere, and the police now search THERE. The engine keeps a per-player wanted search centre and works outward from it. Left alone it defaults to the player — so a star effectively told the police where you were standing, and leaving the scene achieved nothing. It is now pinned to the position the crime was actually witnessed at, which the perception layer already knew and was already sending to the server; it simply was never given to the engine. Walking away from a scene is finally worth something
-
SET_PLAYER_WANTED_CENTRE_POSITION0x520E541A97A13354. R's own scripts call it with two arguments and passGET_ENTITY_COORDSstraight in — the DESC records "P2: Not set by any call" — so the `Vector3` is an IN pointer, not an out-param -
It is verified, not announced.
GET_PLAYER_WANTED_CENTRE_POSITION0x0C92BA89F1AF26F8reads it back, and the centre only counts as pinned if the engine agrees withinverify_tolerance_m. A refusal is named -
The police can be looking for your CAR. A crime committed from a vehicle now flags that vehicle, so ditching it is a real escape route rather than a cosmetic one
-
SET_VEHICLE_IS_WANTED0xF7EC25A3EBEEC726andSET_POLICE_FOCUS_WILL_TRACK_VEHICLE0x4E74E62E0A97E901, whose DESC reads "Sets flag on vehicle that changes behaviour in relation to when player gets wanted level" -
Neither has a getter, so the module records exactly which vehicles it flagged and clears only those handles when the star goes out. It never sweeps the vehicle pool, which would clear a flag another resource set
-
Both fire only when an actor was proven (v1.48.0's attribution split), so an explosion you merely witnessed does not start a manhunt against you
-
New
Config.manhuntand/intel_manhunt, which prints what the engine says the search centre is rather than what we asked for
Not used, deliberately
GET_WANTED_LEVEL_RADIUS0x085DEB493BE80812looks like the natural partner for the search centre. Its own DESC says "Remnant from GTA IV. Does nothing in GTA V." Read the DESC; do not pair on the name
v1.50.0 — 2026-08-28
Fixed — the v1.42.0 reputation migration was half-finished
- Every authored negative dialogue outcome RAISED reputation.
apply_rep_deltawas rewritten to report a reason instead of a number, but it reported'topic'whatever the sign of the authored delta — and the server paystopic+1. Soapply_rep_delta(session, ped, -10)for insulting a persona improved their opinion of you by one. Every negativerep_deltain the dialogue trees was inverted. The reason is now derived from the delta's sign, and a negative is bucketed by magnitude (dialogue:minor|major|severe) so authored intent survives without the client ever sending a number - Six gameplay paths never got migrated at all and have silently not persisted since v1.42.0: target-menu interactions and gifts (
target_options.lua), reaction penalties against static personas (reactions.lua, two sites), the polite-close point (reputation.lua), and the publicAddReputationexport. All now report a reason - The server can price them. New reason families let the client name which authored entry fired —
interaction:<kind>,reaction:<field>,dialogue:<bucket>— and the server reads the value out of the same sharedConfigtable the content author edits. The client still never sends a number - The trust toast had been dead. It hung off
Reputation_Add; once every path started reporting, the value arrived throughclient:repSet→Reputation_Set, which never fired it. Moved toReputation_Set, the one place the number changes, so every route in shows it exports:AddReputationnow returns false and says why, instead of returning true after applying a number that vanished on the next restore
Fixed — the v1.47.0 facing cone leaked out of the HUD
- A gunshot behind a ped's back stopped being heard. v1.47.0 added
IS_PED_FACING_PEDto fix the panel's "Witnesses" pill, but put it in the sharedped_can_witnesshelper. That helper also servescollect_witnesses, and the events called withrequires_los = falseare called that way because they are heard, not seen — gunshot, explosion, alarm. The cone ran ahead of the line-of-sight branch, making it a stricter gate than the raycast that was deliberately declined. v1.49.0's own notes say the opposite in writing: "a gunshot is an AUDIBLE event and sound carries through walls" - Worse for an explosion: the cone was tested against the player, so a blast 150 m away was only noticed by peds who happened to be looking at you
- The gate now applies only to seen events. The witness trace says which of the two regimes ran instead of always printing "LOS not required"
Fixed — a paid bribe never cleared the wanted level
bribeSpawnedCoprequiresConfig.police_system_enabled, and the client'sadjustWantedhandler stands aside when that is on. So on every server where the bribe was reachable at all, the server took the money and the clear was swallowed. Paid clears now go throughclient:wantedPaidOff, which clears unconditionally — the bribe is this module's own decision, not a foreign write
Removed
- 49 orphaned nested config keys, and the preflight harness that missed them. It checked only the 270 top-level
Config.Xkeys and then reported "every config key is read" — but 86 of those are tables holding 661 more, none of them looked at.Config.police_ped_hitdeclared 27 keys and the code reads 3; the other 24 described a scripted persona-driven police reaction that has not existed since the v1.17.0 native-dispatch cut.Config.police_spawnis gone entirely — all five keys dead and nothing read the table either - The harness now walks nested keys too, and names out loud the four tables it cannot scan because they are read with a computed key (
('star_%d'):format(n)and friends) rather than skipping them silently - Its first implementation matched only 14 of the 86 tables and reported green. A scan that sees a sixth of its subject is worse than none, because it reports success; replaced with a line-based walk that agrees with an independent check
v1.49.0 — 2026-08-28
Fixed
- A regression from v1.48.0, found and closed here. When
dispatch_eventgained itsactor_is_playerargument, three call sites in the polling thread — gunshot, melee and drug use — were not updated, so the argument arrivednil. Since v1.48.0 those three events have quietly had no consequence at all: no wanted level, nothing recorded in NPC memory, nothing leaked to informants. It failed silently because it removed behaviour, and the v1.48.0 harness only covered the two paths that change had touched. The harness now fails if anydispatch_eventcall passes fewer than four arguments
Changed — the engine knows what you fired and how to file it
- A suppressor is the whole point of a suppressor. Every shot was heard at the same 100 m whether it came from a silenced pistol or a shotgun.
IS_PED_CURRENT_WEAPON_SILENCEDis the engine saying one is fitted, and a suppressed shot now carriesConfig.perception_suppressed_range(25 m) - A snowball is not a shooting.
GET_WEAPON_DAMAGE_TYPE's own DESC enumerates the classes — 1 is "no damage (flare, snowball, petrolcan)", 3 is bullet, 5 is explosive. Only a bullet or an explosive raises a gunshot now; a flare gun used to bring the police - The police consequence goes through GTA's own crime system. A witnessed crime became a direct wanted-level write. The engine models this:
REPORT_CRIME(player, crimeType, wantedLvlThresh)files it with GTA's dispatch, which then decides the radio call, the search area and how the level builds. The crime numbers are transcribed fromREPORT_CRIME's own DESC — 28 Shots fired, 22 An explosion, 11 Assault on a civilian — andGET_WANTED_LEVEL_THRESHOLDturns the configured star count into the value the native wants, so severity is still yours; it is just spent through the engine instead of written over its head - Events GTA has no crime type for (drug use, an alarm) keep the direct path. That is not a fallback for a native, it is covering something the engine does not model — and when
REPORT_CRIMEitself refuses, the console says so rather than the crime vanishing
v1.48.0 — 2026-08-28
Fixed — a shop alarm made you a bomber
- Any explosion within 200 m was treated as the player's. A car cooking off across the street, another player's grenade, a scripted set piece — all of it handed the player a wanted star, was recorded in NPC memory as they blew that up, and leaked to informants. The detector only ever asked "did a blast happen near me", which is not the same question as "did I cause it"
- A shop alarm raised a perception
explosion. That is a 200 m radius around the player: every civilian in it force-cowered for 8 seconds, the player was recorded as having caused an explosion, informants leaked it, and a wanted star was applied. A siren two streets away made the player a bomber IntelNpcAudio_Alarm(coords, ...)took a coordinate it never read.START_ALARMtakes a name: alarms in GTA V are map-bound, the engine sounds them where they live, and there is no position to pass. Every caller handed one over and the function dropped it
Changed — three questions, asked separately
-
The perception layer now separates what the NPCs observed, who did it, and whether there is a police consequence. Only a proven actor gets the third.
-
The engine answers attribution, and it takes two natives together.
GET_PED_LAST_WEAPON_IMPACT_COORDgives the point where the player's own last shot landed;IS_EXPLOSION_IN_SPHEREasks whether the blast is at that point. If it is, the explosion is theirs. If it is anywhere else, the engine is not claiming they did it and neither do we -
That native is published as
(Ped ped, Vector3* coords)and takes one argument in Lua, returning(found, coords)— so throughpcallthe coord is the third value. Its DESC is quoted at the call site -
An alarm is its own event. Heard at
Config.perception_alarm_range, reacted to, and on a never-attributed list that a caller cannot override.sirenis there too -
Not everything makes people cower. A gunshot, blast, fight or robbery is a threat; an alarm is news. A hundred metres of civilians curling up because a shop siren went off read as a bug because it was one
-
A melee is attributed to the player only when the victim resolver names someone — and since v1.47.0 that only happens off
GET_MELEE_TARGET_FOR_PEDorHAS_ENTITY_BEEN_DAMAGED_BY_ENTITY, so a named victim is proof -
witnessAlertnow carriesactor = 'player' | 'unknown', so the server can tell an observation from an accusation -
New:
Config.perception_explosion_attribution_radius(5.0),Config.perception_alarm_range(100.0).Config.audio.alarm_eventnow defaults to'alarm'./intel_devand the control panel can fire an alarm witness event to see the difference
v1.47.0 — 2026-08-28
Fixed — two things the script claimed that the engine never said
- The "Witnesses" pill on the dialogue panel was a body count. It counted every human within 25 m: facing away counted, behind a wall counted, inside a building counted. The panel then showed that number to the player as a fact about who had seen them. Proximity is not witnessing
- It now asks the perception layer, which runs the engine's own two-part test:
IS_PED_FACING_PEDfirst — its DESC says the angle is the ped's view cone — and the ordinary LOS raycast only on whoever passes. A clear line of sight is not having seen it: a ped with its back turned has a perfect LOS, because nothing is between you - Cheap gate first, deliberately.
HAS_ENTITY_CLEAR_LOS_TO_ENTITY_IN_FRONTdoes both halves in one call and is not used: its own DESC calls it "one of the most CPU demanding BOOL natives in the game; avoid calling this in things like nested for-loops", and this walks the ped pool. With twenty back-turned peds nearby the old path ran twenty raycasts; the new one runs none - There were two definitions of "did this ped see it" — the reaction fan-out's and the panel's. There is one now, and the fan-out's was the correct one
- A forward cone was naming melee victims. When the player was swinging, whoever stood nearest inside a 0.35-dot cone became "the victim" — undamaged, untouched, possibly just walking past. That cost twice: a victim is recorded in memory as someone you assaulted, and a victim is excluded from the witness list. So swinging at air beside a bystander framed them as your victim and deleted the only person who actually watched you do it
- The cone is gone and nothing replaces it.
GET_MELEE_TARGET_FOR_PEDandHAS_ENTITY_BEEN_DAMAGED_BY_ENTITYare the only two answers to "who did I just hit", and when neither names anyone the answer is nobody — which is what the engine says when you swung and connected with nothing. The bystander stays a witness and reacts to the engine's shocking event, which is both correct and the better scene /intel_witnessnow reports which engine answer named each victim, and how often the player swung and hit no oneConfig.perception_victim_cone_dotis deleted with the cone it configured
v1.46.0 — 2026-08-28
Security — the biggest hole found so far
- Any player could clear a five-star wanted level for $200, from anywhere on the map, with no police present. Three separate defects chained:
server:devSetPoliceEnabledwas gated onConfig.debugand nothing else.Config.debugshipstrueon purpose so a fresh install can be driven immediately — it is a development convenience, never an authorisation check, and there was no ACE check anywhere in this resource. Any connected client could fire the raw event and flippolice_system_enabledserver-wide, for every player on the serverbribeSpawnedCopthen had no target and no proximity check of any kind: no cop had to exist, let alone be nearby- It priced the bribe from
payload.stars, a number the client supplied. Claiming one star at a real five charged $200 instead of $75,000, rolled a 15% refusal instead of 95%, and cleared everything.bribeCophad the same pricing bug - A refused bribe could lower your wanted level. Both refusal branches sent
client:adjustWanted— an absolute level — so being refused at a real five stars set you to two, for free, with no cash taken. They now sendclient:adjustWantedMin, which only ever raises - Ambient robbery paid out unverified with OneSync off. The target proof (net id, server-side entity resolve, ped check, server-measured distance, single-use
Entity().stateburn) was gated ononesync_enabled(), so on a non-OneSync server the whole block was skipped and loot was paid on the client's word — no ped required, roughly $450/minute. It now refuses with the cause named in the console
Changed
- The engine says how wanted you are, not the client.
GET_PLAYER_WANTED_LEVELis a CFX native withapiset: server(int GET_PLAYER_WANTED_LEVEL(char* playerSrc)), verified against the native declaration rather than assumed, so the server reads the real value. There is no fallback to the client's number — a fallback is the same lie one hop further away. If the native will not answer, the bribe is refused and the console says why bribeSpawnedCopnow requires the cop's network id, resolves it server-side, confirms it is a ped, and enforcesConfig.police_patrol.response.bribe_distance_m— a key that already existed in the config and that nothing read- Dev events are gated by an ace. Grant it in
server.cfg:add_ace group.admin lastresort_intel_npc.dev allow. Nobody holds it by default, so an unconfigured server is closed rather than open; the console always holds it, which is what txAdmin and rcon use
v1.45.0 — 2026-08-28
Removed
server/lifecycle.luawas a six-line no-op that looked like a safety feature. It registeredentityCreating, readConfig.safety_zones, returned, and never once calledCancelEvent()— it blocked nothing, while a server owner reading the manifest would believe entity creation was being filtered. The file, its manifest entry and the config key are gone- Eleven commands were offered and none of them existed. The audit named three (
intel_police_spawn,intel_police_respond,intel_force_call); a mechanical sweep of every button and doc line against every registration found eight more (intel_force_precall,intel_force_fight,intel_force_flee,intel_force_curse,intel_force_flipoff,intel_hit_reset,intel_hit_dump,intel_police_state). They were deleted in an earlier "remove the dev tooling" pass which left their dev-menu buttons and README lines behind, so seven F12 buttons silently did nothing when pressed. The control panel's allowlist comment claimed it held "only the dev commands this resource itself registers", and that was untrue client:spawnResponsePatrolhad no consumer anywhere. The server sent it on every ped-hit call; no client listened. It described a scripted responder fleet that was never built — dispatch here is the engine's.server:dispatchRequestedis the real hook for another resource- Nine config keys declared and never read. Three of them (
police_wanted_watcher_*) documented a watcher that "spawns ONE scripted RP cop unit", which is precisely the thing this resource does not have; the other six were marked RESERVED and stayed that way. A key that promises a behaviour the code lacks is worse than a missing one, because an owner turns it off to stop something that was never happening
Added
- A preflight harness that keeps all of the above closed. It fails if a manifest entry names a file that is not on disk, if a
.luafile is shipped but never loaded, if the control panel or the dev menu offers a command nothing registers, if the README or install guide documents one, or if a config key is declared and never read. It resolves commands registered under a name read fromConfigas well as literal ones —intel_bounties,intel_jobsandintelfx_demoare registered that way and a literal-only scan wrongly calls them dead
v1.44.0 — 2026-08-28
Fixed
- No persona has ever offered a job in conversation.
fetch_offersinclient/jobs.luawas missing thelib.callback.awaitline entirely, so thelistit then tested was a nil global: the guard was always true, every fetch returned an empty offer list, and the three authored jobs were unreachable. The server sets the pending offer inside that same callback, sojobs:acceptcould only ever have answeredno_offeras well. The whole errand system was dead content, which is why the broken quest path below had survived next to it - Pete offered a job that did not exist. His dialogue has promised
pete_paper_deliverysince it was written, and there was no such job in the registry — the old quest path accepted any string, so the promise was taken and led nowhere. The job now exists, paying the $20 Pete says out loud, with English and Hungarian lines
Security
- A second quest state machine, and it was the exploitable one.
server/main.luakept aPlayerQueststable keyed by source, so it emptied on every reconnect; it was fed only by a client event, read only by a callback nothing called, and completed by nothing at all server:questOfferedaccepted any string as a quest id — no registry, no persona linkage — and paidlevels.Grant(src, 'quest_accept')for each. That is twenty fabricated ids per life and a reconnect for twenty more: a bonus-minutes farmserver:questCompletepaid up to $1000 for any quest id, and nothing in this resource ever triggered it — it was reachable only by another resource on the server- The persisted
questsdocument it implied is written by nothing yet was still restored to the client: a third state machine that only ever existed as a shape - All of it is deleted and nothing replaces it.
data/jobs.luaplusserver/jobs.luaalready is the quest system and it is the better one: a job names its persona, its reputation gate, its cooldown, its steps and its reward; the server issues a time-limited offer, re-validates every step, checks proximity, and grants the levels itself. Atype = 'quest'dialogue topic now goes through it
Changed
- The client stopped applying reputation locally before the server answers. Since v1.42.0 the server writes the value and announces it on
client:repSet; when it refuses — rate limit, ambient persona, a reason it does not pay for — no answer comes back, so the optimistic add left the panel showing a number that was never stored. Dialogue now reports the reason and displays what comes back Config.reputation_quest_accept_bonusis removed. Accepting work is the jobs system's event and it paysConfig.npc_jobs.accept_rep_bonusfrom the server; two keys for one event meant the one you edited was not necessarily the one that applied
v1.43.0 — 2026-08-27
Security
- A shop was not tied to the persona who sells from it.
shopPrices,shopHaggleandshopBuyeach took a shop id and a persona id from the client, and checked only that a catalog existed for the shop and that the player stood near the submitted persona. Nothing connected the two. Stand next to any NPC in the game, send Maggie's shop id, and you bought her goods at your standing with someone who does not own the shop — nowhere near her counter. The haggle state is keyed by shop id, so the same trick won a discount off a persona who sells nothing - The server now decides who owns a shop, and the binding is derived from the persona data rather than written out by hand, so it cannot drift: a persona owns a shop if one of its own
interactionsopens it, or if its dialogue tree has apost_actionthat does. Two personas claiming one shop is reported at startup as the data error it is, and a catalog nobody owns says so instead of quietly selling nothing - One resolver for all three paths. The persona is looked up from the shop id; the persona the client sent is compared against it, logged when it disagrees, and then discarded. Proximity is checked against the owner, and the price comes from your standing with the owner
client/dialogue.luacarried a hardcoded fallback shop id. Abuy_itemstep with no shop of its own charged against Maggie's catalog, whoever you were talking to. It now asks the persona which shop it sells from, and a persona that sells nothing gets no purchase rather than someone else's.client/interaction.lualikewise stopped passing a persona id off as a shop id- Two server-side reputation grants still went through the client. Job rewards and the haggle-insult penalty were sent to the client as a delta for it to apply. Since v1.42.0 stopped the save flush accepting client-written reputation, those two silently stopped persisting at all — a job could pay out standing that vanished on reconnect, and insulting a shopkeeper cost nothing. Both now call the server's
AdjustReputationand announce the stored value onclient:repSet. Theclient:npcReputationevent is deleted at both ends: there is one writer of reputation and it is the server
v1.42.0 — 2026-08-27
Security
- Reputation was whatever the client said it was. The save flush (
persistFlush) took a whole snapshot from the client and wrote each table through verbatim. Reputation gates shop prices, job eligibility and who may place a bounty, so a player could set their standing with any persona to anything and then buy at that price.questsandlastBribewent the same way, gating progression and cooldowns - The flush now accepts narrative memory and nothing else. Memory decides which lines an NPC has available; nothing on the server reads it to decide whether money moves, so the client may still own it. Every other key in the snapshot is refused and the refusal is named in the log
- Reputation changes go through one server function.
AdjustReputationreads the stored value, applies a single delta, clamps it to −10…+10, saves, and returns what it actually stored. It is the only path that writes reputation - The client reports a REASON, never a number.
repEventtakes a persona and a reason word (interaction,topic,gift,quest_accept, or a penalty); the server looks the delta up in its own table. A reason it does not pay out for changes nothing. Ambient personas are refused server-side as well as client-side, and each player is rate limited per persona and reason - The client is told the value it ended up with.
client:repSetpushes the canonical number back, so the panel shows what the server agrees with rather than a number the client made up
Changed
- Ducking no longer has a "panic release".
STOP_AUDIO_SCENES()takes no arguments and drops every audio scene on the client, including another resource's phone call, cutscene or interior mixer. A scene that will not stop is now retried by name, then ducking stands itself down for the session and says which scene did it. A feature that switched itself off with a named cause beats a client whose audio we silenced
v1.41.0 — 2026-08-27
Security
- Bounties paid out on a claim the victim's own client made up.
bountyDeathReport(killer_server_id)took the killer's server id from the victim, and that name was the only evidence behind every payout. Nothing proved the victim had died, and nothing tied the named killer to the death. Two players standing next to each other, one carrying a bounty, could mint money without a shot being fired - Three server-side facts now replace it, and all three must hold. Damage: a
weaponDamageEventledger, fed from the attacker's side of the wire, naming this victim recently — a victim can no longer nominate anyone. Death: the server reads the victim ped's health itself. Instance:GetPlayerRoutingBucketmust match, so two players in different instances at the same coordinate cannot satisfy each other's proximity check - The client death report survives only as a TRIGGER. It makes the server go and look, and it now carries no arguments at all — there is nothing left in it to forge. The client-side helper that read the last damager is deleted
- One gate, both paths. The automatic payout and the manual claim call the same
kill_is_proven(), so there is exactly one definition of "this player killed that player" and it lives on the server. Every refusal names its cause in the log instead of failing silently - The payout is atomic. The bounty moves
open → claiming → paidand is taken off the board before the money moves, so two claims landing in the same tick cannot both pay. A failed payout returns it toopenrather than losing it.open_bounties()now excludes anything mid-payout Config.bounty_board.damage_ledger_ttl_ms(120000) anddead_health_max(100)
Verification
- 15-check harness, proven by reintroducing the exploit: 3 failures. It also asserts the two forward-reference orderings that this codebase has been bitten by before —
cfg()before the ledger uses it, andkiller_is_over_victimbefore the gate that calls it. Both were briefly wrong while writing this and were caught the same way
v1.40.0 — 2026-08-27
Fixed
- The collision survivability floor HEALED a dying ped.
apply_floor()set health tosurvive_floor(110) whenever post-impact health was below it, with no check against pre-impact health. A ped already dying at 10 HP, clipped at 5 km/h, was set to 110 — the impact healed it by a hundred. The rule is "a light knock does not kill", not "a light knock is a medkit": the floor can now at most undo the damage that impact caused - The wanted clamp lowered OTHER resources' wanted level. A 250 ms watchdog pulled any level above this module's ceiling back down, whoever set it — its own trace said "something outside this resource set N stars; clamped back". A heist granting four stars was dragged to two and the engine's higher-tier response disappeared
- Our own requests were already capped at the source in
set_wanted_clamped()and still are. The watchdog now observes and reports; it never writes.Config.police_wanted_clamp_enabled→Config.police_wanted_observe_enabled Config.perception_enabled = falsewas overwritten back to true.if not X then X = true endalso catches an explicitly configuredfalse, so the switch could not be turned off. Now== nil- Reading a vehicle seat SPAWNED a ped.
client/world_ambience.luacalledGET_PED_IN_VEHICLE_SEATwith noIS_VEHICLE_SEAT_FREEguard, and that native's own DESC says: "If there is no ped in the seat, and the game considers the vehicle as ambient population, this will create a random occupant ped in the seat." The radio-candidate scan was increasing the population as a side effect of looking.client/collision.luaalready guarded this correctly; the same guard is now used
Changed — ownership
STOP_AUDIO_SCENES()andSTOP_ALL_ALARMS()are gone. Both take no arguments and drop everything on the client, including scenes and alarms other resources started. v1.38.0 shipped the first as "the panic release", which was exactly the kind of plausible-sounding unverified claim to avoid- The duck now retries the named stop, and if that will not clear it says so and disables ducking for the session rather than reaching for a global. Resource stop releases only our own scene; the probe never escalates
client/audio.luastops only the alarms it started, by namelua54 'yes'removed. It is not required, verified against the official Cfx manifest docs: "This setting is deprecated and you do not need to enable it. As of June 2025, Lua 5.3 has been deprecated and all Lua scripts now use the 5.4 version."
Documentation
- A native is not safe merely because it is native. Decide who owns the state before writing it. Never use a global cleanup native to release a local claim. With no getter and no proof of the previous value, do not guess
- Do not present an inference as an engine fact. Proximity is not witnessing; a nearby explosion does not prove the player caused it; another car stopped at a light does not prove your light is red
v1.39.0 — 2026-08-27
Added
- A5 · Red-light posts — an officer standing at a lit junction, watching traffic. There is no coordinate table anywhere in it. GTA's own map data says which road nodes are junctions and which of those are light-controlled, so the feature reads the map instead of shipping a list of places
- Two bits, not one.
eVehicleNodePropertiescarriesJUNCTION = 1 << 7(128) andTRAFFIC_LIGHT = 1 << 8(256), and both are tested. A junction without lights is a stop-sign post, not a red-light one — the existing red-light detector only ever needed bit 8, so bit 7 had never been read - The post goes on the pavement via
GET_SAFE_COORD_FOR_PEDand is then proved off the road withIS_POINT_ON_ROAD. If it is still on the road, no officer is placed at all rather than one standing in a traffic lane. AVector3.Zeroreturn is treated as "found nowhere", never as the origin - The officer gets the engine's own scenario at that position, with a negative
timeToLeave— which the parameter's own description documents as settingIdleForever, not an undocumented idiom.IS_PED_USING_SCENARIOsays whether it actually took GET_STREET_NAME_AT_COORDsupplies the post's name and a second, independent witness that a coord is an intersection:crossingRoadis non-zero only where two named roads meet, which the engine computes by a different route than the node flags/intel_junctions— read-only. Prints what the map says about every road node around you: junction or not, lit or stop-sign, lane count, street names. The junction finder can be checked without placing anything/intel_redlight,/intel_redlight_test,/intel_redlight_clear,Config.redlight_post, a module switch in F12 → Modules, and a Red-light posts group in Try this
Verification
- 40-check harness. All three out-param traps are exercised individually, because each is silent:
GET_VEHICLE_NODE_PROPERTIESreturns a BOOL plus two out-params, so flags is the fourthpcallvalue — the harness feeds density 15 and asserts it is not mistaken for flagsGET_NTH_CLOSEST_VEHICLE_NODE_WITH_HEADINGis published with ten parameters and takes seven in Lua — the harness asserts the arity and that the fifth argument is the search flags, not a positionGET_STREET_NAME_AT_COORDreturns void, so there is no leading found-flag — the harness asserts the street is the first returned value and the crossing road the second
v1.38.0 — 2026-08-27
Added
- C7 · Ducking — the world gets quieter while an NPC is talking to you, and the ENGINE does it.
START_AUDIO_SCENEactivates one of GTA's own dynamic-mixer scenes and the game lowers ambience, radio and score by its own rules. No volume is touched by hand, and nothing is faked if the engine refuses - The RELEASE is what gets verified. A duck that fails to start is a quiet feature; a duck that fails to stop leaves the whole world muffled and nothing else would ever notice. Every release is read back with
IS_AUDIO_SCENE_ACTIVE, and a scene that will not stop escalates toSTOP_AUDIO_SCENES()with a loud console line saying whether that cleared it - A watchdog releases a duck held past
max_hold_ms, andonResourceStopcalls the panic release unconditionally — bookkeeping cannot be trusted on the way out - Anchored on the panel's open/closed transition, not on focus.
IsOpenwas written in five places andset_focusin six, including the publicIntelNpcNui_SetFocuswrapper — a fifth entrance that is easy to miss. All five assignments now run through oneset_open(), so a conversation that ends down a path nobody thought of still releases the mix - The player's phone radio is snapshotted and restored to what it was — never switched on for someone who had it off
/intel_duck_probe— scene names cannot be validated. No native and no Rockstar dump lists the mixer scenes a build ships, so unlike ped models and scenario names they cannot be checked at build time. Each candidate is started, read back, and stopped again, and the results printed. Re-probing clears the session's remembered refusals so it genuinely re-tests/intel_duck,Config.dialogue_duck, a module switch in F12 → Modules, and a Ducking group in Try this- 34-check harness, including the stuck-release path and the case where the panic release itself fails
Fixed
- A remembered winning scene was reused even after it had been removed from
Config.dialogue_duck.scenes. Caught by the harness, not in play
v1.37.0 — 2026-08-27
Added
- C3 · Gestures — and the answer was that the engine already does them. GTA V gestures a ped automatically while it speaks: the gesture layer rides on ambient speech, picks clips from the ped's own gesture group, and needs no animation dictionary, no clip name and no task. We had simply never switched it on
- Of the seven natives in the
SET_PED_CAN_PLAY_*family this resource called exactly one.SET_PED_CAN_PLAY_GESTURE_ANIMS— the one the whole feature turns on — had never been called anywhere in the codebase - Now switched on for a ped as its line begins, along with the base ambient clip a gesture layers over, lip-sync (
SET_PED_CAN_PLAY_VISEME_ANIMS), arm IK (gestures are arm motion; IK off damps them), andSET_PED_CAN_USE_AUTO_CONVERSATION_LOOKAT— the engine aiming a speaking ped's head at whoever it is talking to, by itself. That last one is the native version of whatclient/lookat.luadoes by hand for bystanders, at one call instead of a watcher - Nothing here is a task. These are per-ped capability switches, so no scenario, patrol or conversation can be cancelled by them — the harness asserts that no task native and no
REQUEST_ANIM_DICTis ever issued SET_PED_CAN_PLAY_AMBIENT_IDLESis deliberately not used: its first bool isbBlockIdleClips, so its polarity is inverted and calling it withtruewould block exactly what the others enableSET_PED_GESTURE_GROUPcan give each archetype its own gesture vocabulary, so a gang member and a shopkeeper move differently without one animation played by hand. Off by default — the group names are not enumerated anywhere in the native database, and an unknown one degrades to the ped's default rather than breaking/intel_gestures_why— none of the seven switches has a getter, so "did it take" is not readable. What is readable is the driver and the causes, and this reads all of them from the engine: the model's ambient voice (GET_AMBIENT_VOICE_NAME_HASH— no voice, no speech, no gesture), whether another resource muted the ped (IS_AMBIENT_SPEECH_DISABLED), config flag 17 holding them still, and whether they are speaking at all/intel_gestures,Config.gestures, a module switch in F12 → Modules, and a Gestures group in Try this- 32-check harness
v1.36.3 — 2026-08-27
Build tooling
-
Not shipped —
tools/is excluded from the Tebex package. -
Scenario names now fail the build too, like model names. v1.33.1's note claimed scenario names had been validated at build time "since the start". They had not:
generate.jsbuilds the whitelist fromscenariosCompact.json, and the only check on the names the resource actually uses was at runtime —client/life_points.luawarns and drops an unknown one, so the ped simply never appears and the message scrolls past -
All 237 scenario names in the resource were swept against the game's 247-entry dump: every one is real, so nothing was broken. The gap was that the next typo would only have surfaced at runtime
-
Proven both ways, like the model check: the clean tree passes, and a reintroduced typo fails the build with file, line and a suggestion
Documentation
- An out-param in the MIDDLE of the published signature is dropped from the Lua argument list.
GET_CLOSEST_VEHICLE_NODE_WITH_HEADINGis published with 8 params and takes 6;GET_NTH_CLOSEST_VEHICLE_NODE_WITH_HEADING10 and takes 7;GET_SAFE_COORD_FOR_PED6 and takes 5 - A
voidnative has no leading status.GET_STREET_NAME_AT_COORDyields both values through out-params, sopcallgives(ok, street, crossing) - An out-param's name is not its meaning.
GET_CLOSEST_ROAD'swidthis the median gap between forward and backward lanes, not the road's width - Road nodes:
JUNCTIONis bit 7 (128) andTRAFFIC_LIGHTis bit 8 (256) — only the second is currently used, and a junction without lights is a stop-sign post rather than a red-light one.GET_SAFE_COORD_FOR_PEDwants flags = 16 (its own DESC: "16 works for me, 0 crashed the script"), and afalsereturn means abandon the placement, never fall through to the origin
v1.36.2 — 2026-08-27
Fixed
- Hit an officer and he could not shoot back — he had been disarmed for good. The band recorded
GET_CURRENT_PED_WEAPON, which reports the equipped weapon. A cop on patrol carries his pistol holstered, so that returnedWEAPON_UNARMED, nothing was recorded,RemoveAllPedWeaponsstripped the pistol anyway, and release handed back nothing GET_BEST_PED_WEAPON(ped, ignoreAmmoCount)answers what he has, not what is in his hand, and is what gets recorded now- The hand-back is read back:
HAS_PED_GOT_WEAPONconfirms it, withp2false because the native's own DESC says it otherwise always returns false. If the engine refuses, the console says so and names the officer Config.police_nonlethal.restore_ammo(default 200)- Punching a civilian made them fight you to the death.
client/reactions.luacallsIntelPolice_MarkSelfDefencefor every ped the player damages within 30 m, and neither end checked for a cop. So any struck pedestrian gotSetPedAsEnemy+TaskCombatPed— a primary task that clobbers whatever the archetype system had just decided. A coward fought to the death like everyone else - Self-defence is a rule about officers leaving the non-lethal band. The guard now lives inside
IntelPolice_MarkSelfDefence, so both call sites get it, and a civilian's answer goes back toclient/reactions.luawhere it belongs
Verification
- New 13-check harness that drives the module's real enforcement loop rather than poking at internals. Both fixes were reverted in turn to confirm it fails on each: 3 failures for the holstered pistol, 3 for the missing cop guard
v1.36.1 — 2026-08-27
Fixed
- Police shot at one star. Two separate bugs, both real.
- The band was switched off by default, by something unrelated.
band_should_apply()requiredConfig.police_system_enabled, which shipsfalse. That flag belongs to the alpha module that decides when you get stars; this band decides whether police shoot at the stars vanilla GTA hands out on its own. Coupling them meant the band never applied out of the box — the exact thing the file exists to prevent. They are now independent;Config.police_nonlethal.enabledis the only switch that governs it - The wrong combat attribute was being set, off by seven. The code wrote
SetPedCombatAttributes(ped, 46, true)with the comment-- CA_CAN_BUST CA_CAN_BUSTis 39.46isCA_CAN_FIGHT_ARMED_PEDS_WHEN_NOT_ARMED- So every held officer was disarmed and then told "you may take on armed people while unarmed", and was never told he may arrest. Release then cleared 46 — a flag this module never meant to touch
data/ped_flags.luahas carried the verified numbers since v1.15.4, when five of them were last found wrong. This file was bypassing that table with raw integers. It now uses the named constants, and no file anywhere passes a bare number toSetPedCombatAttributes
Verification
- New 27-check harness. It extracts R*'s own
eCombatAttributeenum fromSET_PED_COMBAT_ATTRIBUTES' description and checks every entry indata/ped_flags.luaagainst it by name, so an off-by-seven cannot survive again. It also fails the build on any raw integer passed to that native
v1.36.0 — 2026-08-27
Changed
- The engine has a witness system, so the engine does it now. v1.35.0 found
ADD_SHOCKING_EVENT_*/TASK_SHOCKING_EVENT_REACTand documented them while shipping a hand-rolled fan-out next to them. That was the wrong call and it is reversed - Every assault now raises a real GTA shocking event on the victim (
ADD_SHOCKING_EVENT_FOR_ENTITY), confirmed withIS_SHOCKING_EVENT_IN_SPHERE. The engine propagates it, decides how far it carries, and who notices — including peds this resource never looks at - The visible reaction is played by
TASK_SHOCKING_EVENT_REACT: the ped's own reaction set, its own animations, its own personality.scream_flee,walk_away_cursing,stand_groundandcomplyare no longer tasks we issue - Nothing is hand-built when the engine refuses. The console names the cause and the crowd simply does not react
- What is still ours is only the cast layer — gang solidarity, same-set backup, and calling the police module. The engine has no concept of "his set got slapped" and does not know our police module exists
Config.cast_witness.shocking_duration; the event kind is selectable (melee,shot,run_over,killed, …) and picks one of the engine's owneEventTypevalues
Added
/intel_witness_native— settles the one thing that was still unproven, on your build. Raises a real shocking event and asks every nearby ped whatHAS_PED_RECEIVED_EVENTsays at 0.5s, 1.5s and 3s, alongside how many are visibly reacting. Each of the three possible outcomes is spelled out/intel_witness— engine events raised vs refused, and reactions played by the engine vs by us
Fixed
- A refused event left the PREVIOUS one live, so peds were tasked to react to an assault that had already finished. Caught by the harness, not in play
v1.35.0 — 2026-08-27
Fixed
- Every witness reacted on the SAME FRAME. Eight bystanders screaming and bolting in perfect unison was the most artificial-looking thing the system did
- Reactions now travel outward from the victim, nearest first, over about a second. The decision is still synchronous — who reacts, how, and the gang-attacker cap are all settled in one pass, so nothing races; only the visible reaction is deferred
- A witness who has vanished, died, or started panicking before their turn comes round is skipped rather than tasked
react_delay_base_ms/react_delay_spread_ms/react_delay_jitter_ms- A clear line of sight is not the same as having seen it. A ped standing with its back to you has a perfect line of sight — nothing is between you — so people facing the other way, thirty metres off, reacted to things they could not possibly have seen
IS_PED_FACING_PED(0xD71649DB0A545AA3) gates it now; its own documentation says the angle is the ped's view coneHAS_ENTITY_CLEAR_LOS_TO_ENTITY_IN_FRONTdoes both halves in one call and was rejected on its own DESC: "one of the most CPU demanding BOOL natives in the game; avoid calling this in things like nested for-loops." The collector walks the wholeCPedpool. So the cheap angle test gates first and the LOS raycast only confirms what passed itrequire_facing(default true) andview_cone_deg(default 120)- The trace now separates three numbers that were one: how many saw it, how many were in range but facing away, and how many had no line of sight
- The inspect panel said "these five" under six rows. The sixth is a different kind of refusal that had been filed with the rest: the other five are setters we write and cannot read back, while
GET_PED_SCRIPT_TASK_COMMANDis a native that does not exist at all - The two are now stated separately, and the count is derived from the list rather than typed, so it cannot drift again
- All six claims re-verified against the native database: the five setters exist with no getter in any namespace, and
GET_PED_SCRIPT_TASK_COMMANDis genuinely absent —GET_SCRIPT_TASK_STATUSonly answers "is this task running", one hash at a time, which is what the "our task" row above asks it
Documentation
docs/NATIVE_AUDIT.md§8.8, including §8.8.1: the engine has its own witness system (ADD_SHOCKING_EVENT_*,TASK_SHOCKING_EVENT_REACT,IS_SHOCKING_EVENT_IN_SPHERE), recorded with hashes so it is not re-investigated from scratch. It could replace the perception half outright; it cannot replace the casting half, because the engine has no concept of "his set got slapped". Blocked on theHAS_PED_RECEIVED_EVENTMP read-back that §7 already flags
v1.34.0 — 2026-08-27
Changed
- The "They are getting worked up" provoke meter is OFF by default. It landed in the screen centre, over the face of the NPC you were provoking — the one place a HUD element is most in the way
- The signal is not lost. The ped's own reaction is the signal; the 3D overlay shows the irritation ladder against its threshold; and inside a dialogue the same thing already goes into the panel's chat stream instead, which is unchanged
Config.simpleasf.provoke_meter = truebrings it back, and there is now a switch for it in F12 → Modules
Added
- A master switch for the whole simpleasf FX layer in F12 → Modules (
Config.simpleasf.enabled). Toasts, banners, wanted stars, dispatch cards, achievement surfaces — all of it off in one click. Nothing stops working; it stops drawing - A On-screen surfaces group in Try this
v1.33.2 — 2026-08-27
Fixed
/intel_statusreportedscan: 0 runsin a way that reads as broken when it usually is not. Chasing that line in a real log cost a detour: life points scan every 15s after a 3s boot delay, street scenes every 20s after a 6s one, so any status printed inside the first interval honestly shows zero- Four states were collapsed into one number. They are now told apart: the module is switched off; the scan loop never started (its data failed to load, and it already warns separately); it has not come round yet, now printed as
none yet — first scan in 12s, then every 15s; or it has genuinely run - 8-check harness holding the four apart, including the exact case from the log
v1.33.1 — 2026-08-27
Fixed
- Four life points were spawning the wrong people, silently. This is the
validation drops: 0 scenarios, 4 modelsline in/intel_status— the runtime was catching it and saying so; nothing had gone back and fixed the names a_m_y_tourist_01anda_m_y_tourist_02do not exist. There is exactly one male tourist in the game index:a_m_m_tourist_01s_m_y_busker_01does not exist — the busker iss_m_o_busker_01g_m_y_ballas_01does not exist (dev menu) — the Ballas street ped isg_m_y_ballasout_01- Affected
downtown_tourist,vinewood_blvd_tourists,del_perro_promenade_touristsandvespucci_boardwalk_performers - Verified against the game's own ped index (1109 entries), not from memory
Build tooling
-
Not shipped —
tools/is excluded from the Tebex package. -
A wrong model name is silent, and now it fails the build. At runtime
IsModelInCdimagesays no,lib.requestModelnever loads it, andCreatePedsubstitutes a default civilian — so nothing errors and the wrong person simply stands there -
Scenario names have been validated against
scenariosCompact.jsonsince the start, which is exactly why the runtime reported 0 scenario drops and 4 model drops. Model names had no such check. Now they do -
tools/fetch_refs.shalso fetchespeds.json;tools/generate.jsfails the build on any model name not in it, naming the file, the line, and the real alternatives (s_m_y_busker_01 → did you mean: s_m_o_busker_01) -
Proven in both directions: clean tree passes with 65 distinct names across 104 files; each removed fix is caught again when reintroduced
v1.33.0 — 2026-08-27
Fixed
- A SWAT officer, a soldier, a paramedic, a fireman and every ped another resource spawns were all "coward".
DefaultArchetypeByCategory.defaultwas'coward', and that table is the last word for any ambient ped Utils.get_ped_categorynames six things — civilian, cop, gang, homeless, prostitute, animal — and returns'default'for everything else. That is nine of the thirtyePedTypevalues: dealer, medic, fireman, criminal, special, mission, SWAT, army, and any prostitute not listed by model. All nine landed on the catch-allPED_TYPE_MISSIONis the one that made it look universal: it is what peds created by other scripts generally are- The catch-all is now
stoic. An unrecognised ped is not a coward, it is unremarkable — andstoicwas already fully specified in every provoke, reaction and mood table, so nothing else had to move Reactions_GetArchetypereturned'coward'for a ped that does not exist. Alsostoicnow
Added
ArchetypeByPedType— consulted only when the category came backdefault, so dialogue, hails, street scenes and life points (which all branch on the category) are untouched. It changes how a ped reacts, nothing else. Medic → compliant, fireman → brave, dealer and criminal → aggressive, prostitute → cunning, SWAT and army → cop.SPECIALandMISSIONare left out deliberately: a mission ped is whatever another resource made it, so there is nothing honest to say about its temperament- This is also the first time
braveandcunningreach an ambient ped at all. The panel has always advertised seven archetypes; only four could ever be assigned Reactions_GetArchetypeWhy(ped)returns the archetype and the decision that produced it. The inspect panel now prints it under the archetype row: a persona that names one, the category, the raw ped type, or the fallback admitting it is a fallback/intel_archetypes— a census of everyone within 80 m grouped by archetype, by what decided it, and byGET_PED_TYPE. "Why is everyone the same?" is now a table rather than a hunch- 22-check harness covering the resolution order, all nine previously-miscast ped types, and that every archetype named is one that actually exists
v1.32.0 — 2026-08-27
Fixed
- Crime scenes deleted themselves six seconds after the engine accepted them.
/intel_scene_diagreportedCreateIncidentWithEntity ... after 3s: valid=1in the same session where/intel_scene_testprinted "CREATE_INCIDENT would not take this scene". Both were right; the module's conclusion was wrong IS_INCIDENT_VALIDis conclusive at exactly one moment: right after the create, where invalid can only mean refusal. Read later it is ambiguous — an incident the engine has finished with is retired and reads invalid exactly like one it dropped. The module re-read it at six seconds, treated the stale id as a refusal and calledIntelNpcIncident_Close(...), which destroyed scenes the engine had accepted and was working- Acceptance is now decided at the create. Nothing that happens afterwards can delete a scene that the engine took
- "Did anything actually turn up" is now measured instead of inferred.
GET_VEHICLE_CLASSreturns 18 for Emergency — ambulance, police and fire, from the native's own enum — so a scan of the vehicle pool answers it without naming a single model./intel_scene_statereports the count of emergency vehicles standing at live scenes, separately from the wanted-level counter, which only ever counted units sent after you - The refusal message no longer tells you to run a diagnostic that has already answered its question
Added
/intel_scene_why— four probes, one variable each, for when scenes are accepted but nothing arrives. A bare incident; the same plus theSET_INCIDENT_REQUESTED_UNITScall; one anchored on a bystander instead of you; one raised while another is already open. Each samplesIS_INCIDENT_VALIDon a ladder out to twelve seconds and prints the millisecond it died at, then the verdict names which variable shortened it — or says plainly that none of them didConfig.crime_scene.responder_scan_radius(default 140.0)- Crime scenes now have a Try this group, and
docs/NATIVE_AUDIT.mdgains §8.7, which corrects §8.5
v1.31.0 — 2026-08-27
Added
- C4 · The street looks over. Shove somebody until they snap, or hit somebody, and the people around it now glance at you — then carry on with whatever they were doing
- Nothing is spawned and nobody is tasked away from what they were doing.
TASK_LOOK_AT_ENTITYis a secondary task: head IK layered over the ped's existing task. A ped mid-scenario keeps its scenario and still turns to look. That is the exact opposite of the v1.29.0 crime-scene bug, where a turn task issued after a scenario silently cancelled it - This is the cheap half of "people noticed". The crowd director (
Config.crowd) is the expensive half — it recruits people, walks them into a ring and holds them there. Right for a body in the street, far too much for a shove that is over in ten seconds - Verified, not announced.
IS_PED_HEADTRACKING_ENTITYis a real getter, so/intel_lookatprints asked vs. engine-confirmed. A gap between the two is honest: the engine declined those - Capped at 5 heads per moment within 16 m, with an 8 s per-ped cooldown, so it cannot be spammed into a staring crowd
Config.look_atblock, a module switch in F12 → Modules, a panel section under F12 → Actions,/intel_lookatand/intel_lookat_test- Covered by a 28-check harness (cap, cooldown, eligibility, refusal reasons, that the confirmed count is the engine's and not the script's, and that no task capable of clobbering another one is ever issued)
v1.30.0 — 2026-08-27
Fixed
- The resource spiked to 0.25 ms in resmon, against a documented ~0.02 ms ambient budget. That was v1.23.0's basic 3D overlay, and it was mine. Making it the default was right; reading its contents inside the frame tick was not
- The basic overlay shows a dozen things per ped — three state latches, the irritation ladder against its threshold, per-ped rep, whether a voice was cast over the model's own, the driver archetype, in-combat, fleeing. Measured from the source: 21 state reads per ped. Inside a 60 fps frame tick with twenty pedestrians on screen that is 25,200 reads per second
- Drawing has to be per frame. Reading does not — none of it changes faster than a person can read a two-row label. The overlay now collects a snapshot on a 250 ms interval and the frame tick only draws it: 1,680 reads per second, a 15× reduction, with zero state reads left in the per-frame path. Both numbers are measured from the shipped source, not estimated
client/mood_icon.luahad always worked this way — precompute on an interval, draw from the list. The overlay now matches itConfig.debug_3d_refresh_ms(default 250) tunes it.'full'mode stays deliberately uncached: you turn that on to stare at one NPC, not to walk down a street with it- Costs nothing at all with
Config.debugoff, as before
v1.29.2 — 2026-08-27
Fixed
-
Crime scenes never opened, and the diagnostic found out why. Measured on b3751 with
/intel_scene_diag, both dispatch services, repeatable: -
| Form | valid immediately | valid after 3 s | |---|:--:|:--:| |
CREATE_INCIDENT(svc, x, y, z, units, radius)| yes | no | |CREATE_INCIDENT(..., 0, 0)— 9-param | yes | no | |CREATE_INCIDENT_WITH_ENTITY(svc, ped, units, radius)| yes | yes | |CREATE_INCIDENT_WITH_ENTITY(..., 0, 0)| yes | yes | -
Our hypothesis was wrong. The native database's "NativeDB Added Parameter 8 / 9" note made the extra parameters the obvious suspect. Both arities behave identically — the arity is irrelevant. (The
false, falsevariant raisesbad argument #1 to '_ii' (number expected, got boolean): those parameters are ints in the Lua binding whatever they mean, so that form is dropped from the diagnostic.) -
The discriminator is entity versus coordinates. An incident on bare coordinates is valid for an instant and gone within three seconds; one on a ped stays valid.
client/incident.luanow always uses the entity form, and a scene opened on a place borrows the nearest ped withinanchor_search_radiusas its anchor — at a crime scene that is a bystander, which is the right thing to point dispatch at anyway -
Dispatch health was confirmed alongside and was not a factor:
CanCreateRandomCops() = true,GetMaxWantedLevel() = 5, all fourteen services enabled -
Recorded in
docs/NATIVE_AUDIT.md§8.5, marked[NEEDS RE-TEST AFTER A GAME UPDATE]— this is build-observed behaviour, not documented behaviour, and/intel_scene_diagreproduces the whole table in about forty seconds
v1.29.1 — 2026-08-27
Added
/intel_scene_diag— measureCREATE_INCIDENTinstead of guessing at it. The native database listsCREATE_INCIDENTwith seven parameters and then notes "NativeDB Added Parameter 8: Any p7 / Added Parameter 9: Any p8" — newer builds take two more than the published signature, andCREATE_INCIDENT_WITH_ENTITYgained two as well. Which arity a build wants is documented nowhere, and calling the wrong one fails silently- The diagnostic calls every form — 7-param, 9-param with ints, 9-param with bools, and both arities of the entity variant — against both the ambulance and police services, prints each one's raw returns, and checks the incident immediately and again after three seconds. A form that returns true and stays valid is the one this build wants
Changed
- The "no scene" message now says what is actually wrong. It used to list things to go and check. It now asks
IntelNpcEnginePolice_State()first and names the real cause: the engine police stack standing down and why, something on the server switching police off (with the resource named), the stack not having asserted dispatch yet, orclient/engine_police.luanot being loaded at all. Only when dispatch genuinely looks healthy does it point at the native itself and at/intel_scene_diag
v1.29.0 — 2026-08-27
Removed
- The crime-scene dressing.
client/incident.luano longer spawns three peds on medic and investigation scenarios whenCREATE_INCIDENTrefuses. If the engine will not take the incident there is no scene, and the console says what to check: dispatch services on (Config.engine_police) and a road the engine can drive a vehicle down.Config.crime_scene.dressingandmax_dressingare gone - The scripted patrol walker.
client/patrol.luano longer walks a route from Lua whenTASK_PATROLrefuses the task — a scripted route-walker is not the engine doing it, it is us pretending the engine did. A guard the native refuses simply does not patrol, and/intel_patrol_diagmeasures all eight parameter combinations and reports whether any works on this build.Config.patrol.walker_fallback,walk_timeout_msandarrive_radiusare gone - The script-side wanted level.
client/police.luano longer raises a star itself whenREPORT_CRIMEdoes not. The engine owns the decision — did an officer witness it, does it warrant a star — and if it says no, you do not become wanted. The wanted level is still read back afterwards, but only so the trace can report which happened.Config.police_crime_reportingand its'script'/'verified'modes are gone
Where the resource now stands on this
-
Of the seven city-life modules, six spawn no peds at all and do their work entirely through natives — crowd, traffic, audio, collision, crime scenes and the engine police stack.
client/patrol.luais the only one that creates a ped, and only to populate a route you asked for or a test command you ran. -
The harnesses were rewritten to assert the new contract rather than the old one: a dropped incident produces nothing at all, a refused
TASK_PATROLproduces neither a scripted walk nor a substitute scenario, and the refusal is counted and reported in both cases.
v1.28.1 — 2026-08-27
Fixed
- The crime-scene peds stood doing nothing.
dress()issuedTaskTurnPedToFaceCoordafterTaskStartScenarioInPlace, and tasks replace each other — so the ped turned to face the scene and the scenario was cancelled on the same tick. They now face the scene by spawn heading and no turn task is issued at all. Asserted, along with a sweep confirming no other module has the same ordering - Every crime scene fell back to hand-dressing, even when the engine had accepted the incident. The verify required
_GET_NUM_DISPATCHED_UNITS_FOR_PLAYER > 0, but that native counts units sent after the player — a wanted-level response — so an incident that has nothing to do with the player correctly reads zero there.IS_INCIDENT_VALIDis the authority and is now the only condition; the unit counter is reported as context rather than used as a gate. This is why/intel_scene_stateread "0 run by the ENGINE, 3 dressed by hand" - NPC chatter was landing in the chat's SYSTEM channel.
Config.integrations.chat.feedwrote every bubbled line with a plainchat:addMessagecarrying a colour and two args and no template — and stock chat, qb-chat and ox_chat each decide for themselves what an untyped message is, with several filing it under system. A passer-by muttering about the weather showing up as a system message is worse than it not showing up at all, and it was the default. It now defaults tofalseand is strictly opt-in for a server that has checked where its own chat puts the line. The 3D bubble above the ped is the intended surface and is unaffected
v1.28.0 — 2026-08-27
Added
Config.crime_scene(client/incident.lua) — A4, crime scenes. The brief described hand-placing a paramedic onCODE_HUMAN_MEDIC_KNEELand an investigator onCODE_HUMAN_POLICE_INVESTIGATE: scenery that looks right and does nothing. Searching the natives first found an entire incident system underneath thatCREATE_INCIDENT_WITH_ENTITY(service, ped, units, radius, &id)raises an incident on a body and the engine dispatches real units — an ambulance that drives there with its siren on, paramedics who get out and work the body, police who secure the scene. All pathed, animated and owned by the game, for one native call.SET_INCIDENT_REQUESTED_UNITSadds the police to the same incident rather than raising a second one- Two getters make it honest.
IS_INCIDENT_VALIDsays the incident still exists and_GET_NUM_DISPATCHED_UNITS_FOR_PLAYERsays whether anything was actually sent, so "the engine handled it" is checked rather than announced. When the engine sends nothing — dispatch off, no road nearby — the hand-placed scenario dressing from the original design runs instead, and the trace and/intel_scene_statesay which of the two you are looking at - The onlookers are still ours: no native gathers bystanders around a body, so that goes to the crowd director, which borrows pedestrians who are already there
/intel_scene_testand/intel_scene_state; also in Try this- 22 assertions: the incident is raised on the body for the ambulance service, police join the same incident, nothing is hand-spawned while the engine is working, dressing appears only when it sends nothing, a refused
CREATE_INCIDENTstill produces a scene, closing deletes the incident and every ped it placed, caps hold, distance closes it, and disabling opens nothing
Changed
- One verified spawner for the whole resource (
IntelNpcSpawn_Verified, published fromclient/spawn.lua). It validates the model, loads it and readslib.requestModel's return, then checks the ped that came back is the model asked for — a substituted default civilian is deleted rather than put to work.client/patrol.luanow uses it instead of its own copy, so the bug that made the patrol test spawn passers-by has exactly one place it could ever come back from
v1.27.1 — 2026-08-27
Fixed
- The patrol test spawned civilians instead of guards.
client/patrol.luacalledlib.requestModelinside a barepcalland threw the return value away — so it only knew the call had not errored, never whether the model had actually loaded. An unloaded model reachesCreatePed, and GTA quietly substitutes a default civilian.client/spawn.luaandclient/life_points.luahad always done this correctly; the patrol module was the odd one out - All three of its spawn sites now go through one helper that validates the model exists (
IsModelInCdimage+IsModelValid), loads it and reads the answer, then verifies the ped that came back is the model that was asked for. A substituted ped is deleted rather than put to work Config.patrol.test_modelnames the model the test commands use- One failed spawn stopped a route ever spawning again. A route whose model failed to load recorded an empty list against its id, which made the early-out at the top of the spawner permanent — a single streaming hiccup disabled that route for the session. The route is only marked as spawned if a guard actually was. Found by the harness, not in play
/intel_patrol_diagnow says what it is doing. It deliberately switches the scripted walker off so only the native is measured, which means most of the guards it spawns are supposed to stand still — that is the measurement. It said none of this, so a minute of guards standing around looked like a broken command. It now states that up front, prints progress per combination, and its verdict distinguishes "no combination works on this build" (a real answer, and the walker still handles it) from a failure
v1.27.0 — 2026-08-27
Added
Config.audio(client/audio.lua) — D5, audio direction. Alarms, one-shot world sounds, venue music through static emitters, ambient-zone state and audio-scene mix ducking. Four native families, no permanent threads — only the ones that stop what they started- Alarms are verified, not hoped for. An alarm name is a string the game either knows or does not, and a wrong one is a silent no-op — the classic way this feature "works" in a script and produces nothing in the world. But
PREPARE_ALARMreturns a BOOL andIS_ALARM_PLAYINGis a real getter, so the configured name is prepared, the answer read, and the next candidate tried on refusal. Whichever the game accepts is remembered and tried first next time; if none is accepted the console says so rather than leaving you wondering why the shop is quiet - An alarm is also an event. It goes into the ordinary perception pipeline as an audible one, so witnesses, memory and gossip follow without this module knowing anything about them
- Global state is restored. Static emitters and ambient zones are engine globals: leaving a venue's music switched off outlives the resource. Every emitter, zone and scene touched is recorded and put back on stop
- One-shot sounds use sound id
-1, whichGET_SOUND_ID's own documentation prescribes for fire-and-forget playback, so no sound id can leak - Audio scenes report whether they actually started (
IS_AUDIO_SCENE_ACTIVE) instead of assuming the name was real /intel_alarm_testand/intel_audio; also in the F12 panel's Try this- 20 assertions in real Lua: a bogus name never reaches
StartAlarm, an accepted one is used and remembered, the alarm reaches perception as the configured event type, it stops itself, perception can be opted out of, sounds are fire-and-forget, an unknown audio scene answers false, every global write is recorded for restore, and disabling writes nothing
v1.26.0 — 2026-08-27
Added
Config.engine_police(client/engine_police.lua) — vanilla GTA police, on and kept on.SetCreateRandomCops, both scenario variants, all fourteen dispatch services,SetDispatchCopsForPlayer,SetPoliceIgnorePlayer(false), radar blips and the engine wanted ceiling are now asserted every few seconds instead of assumed- This resource was part of the problem.
client/ai_suppression.luaonly ever wrote those natives inside two branches that are both off by default, so with the shipped config it never touched them at all — it just inherited whatever the last resource to load had decided. Most server templates (qbx_smallresourcesand friends) switch random cops and dispatch off at startup and nothing turns them back on - It verifies the two things the engine will answer.
CAN_CREATE_RANDOM_COPS()is a real getter: if it reads false right after we set it true, something is fighting us, and the console names the likely resource instead of leaving you to guess.GET_MAX_WANTED_LEVEL()reading 0 means you can never become wanted whatever any police module does — that one is shouted about - Stands down where it should:
Config.disable_ai_policewins, and it will not fightai_suppressionwhile that is deliberately suppressing dispatch - State on the Police tab and
/intel_engine_police - 20 assertions in real Lua, including the override being detected and named, a zero wanted ceiling being reported, selective service disabling, and every stand-down path
Changed
- The current-scene panel can be scrolled, and says when it cannot show everything. It is
pointer-events: noneby design so it can never steal a click from the game — which also made it impossible to scroll, and with the trace feed now taking the bottom of that edge there is more content than room - The body accepts the wheel only while the F12 panel is open, when NUI focus is already taken and nothing is stolen from the game
- When content is clipped, a sunset bar at the foot says so — and once scrollable, how much more there is
- Visual pass: the horizon rail and gradient title from the control panel, a visible thin scrollbar (one nobody can see is the same as none), a warmer near-opaque ground and more breathing room
- Measured at 1280×720, 1920×1080 and 2560×1440: overflow is announced with the panel closed, the body is inert then, and it scrolls with the panel open
v1.25.0 — 2026-08-27
Added
- Patrol routes work whether or not
TASK_PATROLdoes. Two releases of coaxing the native produced a guard who stood still, and a feature should not be hostage to a native with three undocumented parameters and no read-back. The engine path is still tried first because it costs nothing; when the verify says it was refused, the route is now walked from script — go to the node, face its look-at point, play its scenario for its wait, go to the next one - One 500 ms thread per walking guard, not per guard and not per frame, and it stops on release or death.
Config.patrol.walker_fallback = falserestores the old stand-at-your-post behaviour SET_PED_KEEP_TASKis now set beforeTASK_PATROL, so the engine cannot quietly drop the task it was just given/intel_patrol_diagmeasures the native instead of guessing at it. It spawns one guard perTASK_PATROL(p2, p3, p4)combination — all eight — on short routes laid out in front of you, watches which ones actually move, and prints the winner. If one works, those three values go inConfig.patroland the engine walks your guards again. If none does, that is a fact worth having rather than a third round of guessing- The walker is switched off for the duration of the test, so only the native is being measured
Changed
- The debug trace feed is bottom-LEFT by default. The F12 control panel owns the right edge, so keeping the feed opposite it means both are readable at once, which is the point of having the feed open
- The current-scene panel is pinned down the same edge, so it now stops above the feed rather than overlapping it
- The step-aside the feed does when the control panel opens now only applies when the feed is on the right; a left-anchored feed was being pushed into mid-screen for no reason
- Measured at 1280×720, 1920×1080 and 2560×1440: the feed is bottom-left and the scene panel clears it at all three
v1.24.2 — 2026-08-27
Fixed
- The shipped patrol route spawned a prisoner.
s_m_y_prismuscl_01is the Bolingbroke inmate —prismusclis prisoner muscle, not "muscular guy for the beach". The Vespucci route now usess_m_m_security_01, a private security guard, anddata/patrols.luawarns about the pair by name so the next person does not make the same read /intel_patrol_testput the guard on a route that was somewhere else. It spawned him next to you but assigned a route fromdata/patrols.lua, whose nodes are at fixed world coordinates. Stand anywhere but there and the guard has to path across the map before he can walk a beat — which looks exactly like "he just stands there"- With no argument it now lays a 16 m square around where you are standing and spawns the guard on it, so you can watch him do a lap.
Config.patrol.test_route_sizesizes it - With a route id it spawns him at that route's first node instead of next to you, for the same reason
- Repeating the test replaces the temporary route rather than stacking a new one each time
- The verify could call a working patrol a refusal. It measured movement
verify_msafter tasking, but a guard stands at his first node for that node'swaitbefore walking anywhere — so any route whose first wait was the longer of the two read as "TASK_PATROL did not take" and got the poor guard stood back down on a scenario. The wait is now measured from when he could first move: the first node's wait plusverify_ms - Asserted: a route whose first node waits 9 s with a 4 s grace is no longer read as a refusal
v1.24.1 — 2026-08-27
Fixed
/intel_patrol_testfroze the player in place. It put the patrol route on the player's own ped. A player ped is driven by input, not by AI tasks, soTASK_PATROLdid nothing — and then v1.24.0's own verify step read "he did not move", cleared the player's tasks, blocked his ambient events and dropped him into a guard scenario. That is a freeze with no way out, caused by the safety net rather than caught by itPatrol_Assignnow refuses a player ped outright, with the reason/intel_patrol_testspawns a guard next to you and puts him on the route, which is the thing worth looking at anyway./intel_patrol_clearremoves the guards it spawned- Four assertions added for exactly this: assigning a player is refused, no task is issued, and no scenario is forced on them
/intel_unstick— an escape hatch, because a debug tool that can strand you needs one. Clears tasks, unblocks ambient events, unfreezes position and closes any open dialogue on your own ped. Also in the F12 panel's Try this tabclient/focus.luanow refuses a player ped too. Focus blocks ambient events, clears tasks and can put its subject into a scenario — the same three writes that caused the freeze. No caller passes a player today, but "no caller does today" is precisely the reasoning that stranded one
v1.24.0 — 2026-08-27
Added
Config.patrol+data/patrols.lua(client/patrol.lua) — D1, guards that walk a beat. The engine has a whole patrol system and almost nobody uses it, because its natives are underspecified in two ways that both make it silently do nothing:OPEN_PATROL_ROUTE's own documentation says the name "must start withmiss_to be properly created". A route named anything else is accepted by every call and then does not exist. Route ids indata/patrols.luaare plain names and the prefix is added for you, so it cannot be forgottenADD_PATROL_ROUTE_NODEtakes eight floats and the second three are a LOOK-AT POINT, not a second position. Reading them as a destination is the usual way this ends with guards facing walls. The data file names the two separately (coordsandlook) so they cannot be confused- Links are directional: a cyclic route links the last node back to the first, a there-and-back route links each pair both ways
- Scenario names are validated against
data/generated/scenarios.luaon build. A typo is named in the console and replaced, rather than producing a guard who stands doing nothing with no error - Routes may spawn their own guards (
model+count) with distance spawn/cull, or you can hand any ped toPatrol_Assign(ped, route_id) - After
TASK_PATROLthe engine owns the guard — no thread follows him - Assignment is verified, not assumed.
TASK_PATROL(ped, name, p2, p3, p4)has three parameters nobody has ever documented and nothing can be read back, so a tasked guard is checked a moment later for having actually moved. One that did not is put on a scenario at his first node, so he stands post rather than standing idle in the road, and the trace says which happened. The three parameters are exposed asConfig.patrol.task_p2/p3/p4rather than buried, because they are what to try if guards refuse to walk - Try it with
/intel_patrol_test, check it with/intel_patrol - Verified in real Lua — 25 assertions: the
miss_prefix is always present, the build order is open → nodes → links → close → create, the look-at triple is distinct from the position triple, a cyclic route closes its ring while a linear one links both ways, a bad scenario name is replaced, a one-node route and an unknown id are both refused, a route is built once and reused by later guards, a guard who moved is confirmed walking, one who did not is stood at his post, and release hands the ped back
Changed
- The crowd director and patrol routes have module switches in the F12 panel's Modules tab, and "Guards on a beat" is a new entry in Try this
/intel_patroland/intel_patrol_testjoined the command allowlist; all 19 command buttons in the NUI are checked against it
v1.23.0 — 2026-08-27
Added
Config.crowd(client/crowd.lua) — D3, the crowd director. One shared service rather than a feature: something happens and it gathers the pedestrians who are already there around it. A4 (crime scenes), D2 (news events) and D9 (fame) are all specified to consume it rather than each growing their own copy- It borrows peds, it does not spawn them. Every recruit is an engine pedestrian who was walking past anyway, so a crowd costs no population budget and no model loading. The contract that matters is giving them back:
/intel_crowdprints whether any were borrowed and never returned, and it says so in red if the count is ever non-zero - Four profiles —
watch(turn and look),film(plus the phone-filming scenario),gawk(approach to a ring, then watch),cheer(approach and applaud). Danger disperses any of them throughTASK_SMART_FLEE_PED, the engine's own flee, releasing every recruit on the same tick - Never recruits a persona, a ped already mid-reaction, a panicked or hostile one, anyone in a vehicle, or a ped another crowd already claimed
- API:
CrowdDirector_Claim(anchor, profile, opts),_Release,_Disperse,_DisperseAll,_Counters. Try it with/intel_crowd_test [profile] - Verified in real Lua against the module's own thread — 27 assertions, weighted at the give-them-back contract: caps hold, no ped is ever in two crowds, releasing restores the blocking flag and hands the ped to
TASK_WANDER_STANDARD, a ped that was in a scenario is not forced to wander, every recruit of a dispersed crowd gets the flee task and is unblocked, a despawned recruit stops being claimed, crowds expire and end when you walk away or the anchor vanishes, andrecruited == releasedexactly with nobody left claimed
Changed
- The 3D overlay above NPCs has a basic mode, and it is the default.
Config.debug_3d_mode = 'basic'draws two rows: row 1 is identity, row 2 is only what is actually true right now — panic, hostile, busy, irritation against its threshold, per-ped rep, whether a voice was cast over the model's own, the driver archetype if they are in a car, fighting, fleeing. An empty row 2 means an ordinary pedestrian, so a busy one stands out.'full'is the old everything-at-once wall,'off'is nothing - The dev-menu button cycles basic → full → off. It previously flipped
Config.show_debug_3d, which since the mode key exists would have made the button do nothing at all - The Tests tab is now "Try this". It was a 38-step checklist with pass/fail/skip bookkeeping that nobody ticked and that went stale the moment a module shipped. It is now one action and one observable result per entry, covering what actually ships — the zero-damage nudge, the survivable clip, the driver who answers when you rear-end him, unarmed aiming, the aim that fires once, persona voices, the red light without stopping, the non-lethal band, crowds, and trust on a stranger versus a persona. No bookkeeping
Fixed
- Two more panel buttons were refused.
/intel_crowdand/intel_crowd_testjoinedActions['command']'s allowlist; all 17 command buttons in the NUI are now checked against it rather than assumed
v1.22.0 — 2026-08-27
Added
Config.traffic_personality(client/traffic.lua) — the drivers around you are people too. Vanilla ambient traffic is one driver repeated: same aggression, same ability, same style. Each car withinradiusnow gets a personality 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 — not a second, parallel personality system- It is all engine. Three natives do the whole job:
SET_DRIVE_TASK_DRIVING_STYLE(the flag composition, taken fromdata/generated/driving_styles.lua, which is built from the game's own flag table rather than hand-written constants),SET_DRIVER_AGGRESSIVENESSandSET_DRIVER_ABILITY. After those three writes the engine drives the car and nothing polls it again unless you provoke it - Who does what: coward gives way and rarely honks · compliant obeys lights · stoic ignores you · cunning does not fight but notes you down, so a hit from you enters the ordinary witness pipeline, memory and gossip · brave honks nearly half the time · cop obeys lights with the highest ability · aggressive is reckless, almost always honks, and swerves at you
- Driving into a car now reaches its driver:
client/collision.luahands the contact to this module, which decides the reaction by archetype - Network-correct.
TASK_VEHICLE_TEMP_ACTIONis documented as not applying to remote clones, so the swerve and brake reactions request control first and are skipped when it is not granted. The three personality writes are local and need no ownership - Skipped by default: mission vehicles, and anything that is not street traffic (cycles, boats, helicopters, planes, trains)
- Honest limit, documented in the config: 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
- Live state on the Police tab of the F12 panel and
/intel_traffic - Verified in real Lua by driving the module's own thread against a stubbed engine — 22 assertions: the three writes happen exactly once per car and are never repeated, the archetype is stable across scans, each archetype reacts in character, reactions are rate-limited, a remote clone is never given a temp action, mission vehicles / helicopters / empty cars / distant cars are all skipped, leaving the radius hands the car back, and disabling the module writes nothing at all
Fixed
- The collision panel's console button was refused.
/intel_collisionwas added to the panel in v1.19.0 but never toActions['command']'s allowlist, so pressing it answered "not one of this resource's dev commands". Both it and the new/intel_trafficare allowlisted, and all 13 command buttons in the NUI are now checked against the allowlist rather than assumed
v1.21.0 — 2026-08-27
Changed
- The whole UI is retinted to "Leonida" — the Vice look. Not a dark panel with pink in it; three things carry it and they are documented in
nui/theme.cssso a server retinting it keeps the identity: - the ground is warm navy, never neutral black — every surface keeps a little magenta so nothing reads as grey
- the accent is a gradient, pink → orange → amber, and it sits on the type (
--lr-sunsetthroughbackground-clip) as well as on rails. The panel title, the NPC's name and a 2px horizon rail under the panel header all carry it. That is what makes it read as this rather than generic neon - exactly one cold colour, the cyan, used sparingly — water, screens, investigation
- the sun in
--lr-horizonis low and off to one side rather than centred, so a panel looks lit from somewhere instead of glowing on its own - Measured, not eyeballed: rendered against a bright sunset backdrop at 1280×720 through 2560×1440. Nothing overflows and there is no horizontal page scroll at any of them
- The panel body is near-opaque now (was
.86). The game blurs the world behind it, but blur is not darkness — measured against a bright sky the translucent version washed the hint text out completely. The sunset belongs on the accents, not through the card - The two faintest text tiers were lifted (
--lr-dim,--lr-faint) for the same reason: they were unreadable over anything bright
Fixed
- The speaker's name could render invisible.
.npc-namesetcolor: transparentunconditionally alongsidebackground-clip: text— where the clip is unsupported that leaves nothing on screen. Both it and the new panel title declare a solid colour first and only go transparent inside an@supportsguard
Docs
- "escrow" no longer appears in the README. Both mentions were denials ("no escrow"), but the resource is not sold escrowed and the word does not belong in the docs at all. The two lines now say what actually ships: unencrypted, unobfuscated, every file readable and editable
v1.20.0 — 2026-08-27
Added
REPORT_CRIME— the engine decides the wanted level. GTA V already has a crime id for every trigger this module raises: 2 red light, 4 speeding, 12 assault on an officer, 15 pedestrian struck. Handing it the report hands it the whole decision — whether an officer really witnessed it, whether it earns a star, which dispatch answers — replacing ourany_cop_within()radius scan and thechance_per_violationdice roll with the game's own answerREPORT_CRIMEreturnsvoidand its crime table is a community decompilation, so it is not trusted blind.Config.police_crime_reportingships as'verified': report the crime, readGET_PLAYER_WANTED_LEVELback afterpolice_crime_verify_ms, and raise the star from script only if the engine did nothing. The trace names which acted —crime_native_tookorcrime_native_ignored.'native'never second-guesses,'script'never asks- Prerequisite already met:
IsNativeDispatchInCharge()has returned true since v1.17.0, so there is a live dispatch to report into
Changed
- Running a red light is detected from the engine instead of from a stop. There is no native that reads a traffic light's phase — re-verified across all 6,416 entries;
SET_ENTITY_TRAFFICLIGHT_OVERRIDEwrites one with no getter. But the phase does not have to be read: the map's own road graph flags the node (GET_VEHICLE_NODE_PROPERTIES,TRAFFIC_LIGHT = 1 << 8), andIS_VEHICLE_STOPPED_AT_TRAFFIC_LIGHTSon a car the engine is holding tells you that direction is red. Crossing a light-controlled node above the speed floor while a same-direction vehicle is held there is running a red - Heading is what makes it correct. Cross traffic at a junction is also stopped — at its own red, while yours is green — so counting it would book the player for obeying the light. Only vehicles within
same_direction_arc(45°) count - This closes the gap the module shipped with and the audit recorded as unfixable: the old rule polled the native on your own car and fired when you pulled away, so it required you to have stopped first — blowing straight through, the actual offence, was the one case it could not see. The stop-then-go path is kept as a second detector for empty junctions
- No hand-built coordinate list: the junction data is the map's
- Verified in real Lua against the rules themselves — 14 assertions covering node-flag reading, the same-direction discriminator, cross traffic, oncoming traffic, range, and heading wraparound across the 360° seam
docs/NATIVE_AUDIT.md§4 corrected. It claimed "a correct red-light detector is not buildable from natives", which assumed a detector must read the light. §8.1 records the correction and what remains genuinely unreadable. Its dismissal ofREPORT_CRIMEwas answering the wrong question — it is not a detector but a decision, which §8.2 covers- §8.3 gives the honest overall answer: the 3–5% replaceable headline stands. This lane is tens of lines, not thousands, and §4's list of what has no native equivalent is unchanged in every other respect
Fixed
- A
pcallout-param offset that would have silently disabled the new detector.GET_VEHICLE_NODE_PROPERTIESreturns a BOOL plus two out-params andpcallprepends its own status, so the flags are the fourth value. Reading the third givesdensity(0–15), leaving bit 8 permanently clear. Caught by asserting on the real signature before shipping - A forward-reference that would have thrown at runtime. The cop-assault watcher is defined above the new crime-reporting function, so a
local functionthere compiled as a nil global lookup. Forward-declared, and confirmed against the compiled bytecode that no_ENVlookup remains
v1.19.3 — 2026-08-27
Fixed
-
Aiming a weapon at an NPC re-triggered him every few seconds and flooded the debug trace. The aim thread polls at 300 ms and called
Reactions_Triggeron every tick the reticle was over a ped. Two things came out of that one line: -
the NPC re-reacted every time the reaction debounce expired, so keeping a gun on someone restarted his reaction roughly every four seconds instead of threatening him once;
-
every debounced call still wrote a trace line, so one held aim produced about three
debouncedrows per second and buried everything else in the feed — exactly when you were looking at it. -
Aiming is now edge-triggered: it fires once when the reticle lands on a ped and not again for that ped until the aim has been off him for
Config.aim_rearm_ms(6 s). Swinging onto somebody else fires immediately, because that genuinely is a new threat. Verified against the rule itself: the first aim fires, forty polls over twelve seconds fire zero more, a different ped fires at once, flicking straight back does not, and after the re-arm it does again -
The trace feed collapses repeats. Some call sites are polled rather than edge-triggered, and a poll reporting the same answer every tick should be one row with a count, not N identical rows. A repeat of the same system + event + ped + reason inside
Config.trace.collapse_ms(2 s) now increments the count on the row already there — the panel showsdebounced ×47instead of forty-seven lines. Nothing is lost: the reason text is identical by definition, and a count plus a last-seen time is strictly more information than N copies. A different ped, a different reason, or a gap longer than the window all still get their own row, andcollapse_ms = 0shows every line -
The NUI updates the existing row in place rather than appending, so the collapsing holds on the JavaScript side too
-
Verified against the real bus: 34 identical calls produce 1 row whose counts account for all 34, a second ped is never folded into the first, a different reason is never folded either, a repeat past the window starts a fresh row, and switching it off restores every line
v1.19.2 — 2026-08-27
Fixed
-
Aiming at an NPC with bare fists made them run away.
IS_PLAYER_FREE_AIMINGis true whenever the aim button is held, weapon or not, and the aim reaction never checked. So right-clicking while unarmed firedon_aimed_at— the archetype's threatened reaction, which for most of them is panic and flee — on whoever was under the reticle, and every NPC you so much as looked at bolted. Aiming now needs something to threaten with. Melee counts: a bat or a knife pointed at someone is a threat, bare hands are not.Config.aim_requires_weapon = falserestores the old behaviour. When an aim is ignored the trace says so, rate-limited, because the aim button is held rather than tapped -
Saying hello and goodbye to a random pedestrian built "trust" with them, and the toast was titled with an internal id. A polite close called
Reputation_OnPoliteClose(session.memory_key), and for an ambient ped that key isambient_<uid>— a runtime identifier for one pedestrian who will despawn in minutes. Three things went wrong from that one argument: -
It was written into
PlayerReputation, which is persisted to the database. Every stranger the player was polite to left a permanent saved row that can never match a persona again, growing without bound -
It fired the trust toast, whose title falls back to the raw id when no persona matches — that is the "ambient ped" text on the trust popup
-
It made the trust system look pointless, which was the fair complaint: a stranger you will never see again should not be building a relationship with you
-
Now: a named persona earns the point exactly as before. An ambient ped earns nothing unless
Config.reputation_ambient_polite_closeis turned on, and even then it goes on the ped (NpcState_AddRep), where it decays and disappears with him — never into the saved store.Reputation_AddandReputation_Setnow refuse anambient_*key outright with one console warning, so no other call site can pollute a persisted store either, and restore prunes the rows older versions already wrote. The toast never shows a raw internal key: an unmatched ambient id renders as "Stranger" -
Verified in real Lua against the module itself: a stranger earns nothing and writes nothing, Mahoney still earns his point and his toast carries his name, the cooldown holds, opting ambient standing back in puts it on the ped and leaves the store clean, both write paths refuse ambient keys, and an already-polluted save is cleaned while real personas survive
-
A first-ever polite close was refused while
GetGameTimer()was still belowpolite_close_cooldown_ms.PoliteCloseLastTickMs[id] or 0made "never happened" indistinguishable from "happened at time zero". Same latent bug the collision harness caught in v1.19.0, in code that predates it
v1.19.1 — 2026-08-27
Fixed
- The dev panel's test pistol was confiscated the moment it was given. On an inventory-managed server —
ox_inventoryon qbox,qb-inventoryon qb — the inventory owns your weapons, and a weapon handed over withGiveWeaponToPedis one the inventory has never heard of, so it strips it back off within about a second. v1.18.0 detected that and named the resource responsible, which explained the problem without solving it. The pistol now comes from the inventory on those servers (Adapters.inventory.AddItem, via a newConfig.debug-gated, rate-limited server event), so it stays. The native give is used only where no inventory resource is running, and is still verified after the fact rather than assumed - The item id differs by inventory and is resolved per bridge:
WEAPON_PISTOLfor ox,weapon_pistolfor qb-inventory. ESX goes throughaddWeapon/removeWeaponfirst and falls through to the item route - Taking the pistol back removes the same item it added, so the toggle still undoes exactly what it did and never touches a weapon of your own
- When the inventory has no pistol item registered, it now says so and names the resource to add it to, instead of failing silently
v1.19.0 — 2026-08-27
Fixed
- Every static NPC had the wrong voice, including Sergeant Mahoney.
Config.default_gta_voice_namewas handed to every persona that declared nogta_voice_nameof its own — nine of the ten shipped — and written over the model's voice withSetAmbientVoiceName. Those models already owned a correct voice:s_m_y_cop_01,s_m_m_doctor_01,s_m_y_dockwork_01,a_m_m_genfat_01,a_m_m_business_01,a_f_m_business_02,s_f_y_hooker_01. The game picked each of them to match its model, so the override could only ever make things worse. It is now offered only to models that genuinely have no voice — freemode and player models, which ship with no pedvoicegroup at all and are silent without it — and it is sex-checked against the ped first, so the one shipped persona that needs it (Erika,mp_f_freemode_01) gets a female voice rather than the configured male one. This is the same reasoning v1.16.2 applied to ambient pedestrians ("keep their own voice") and never applied to personas. Verified in real Lua against the shipped data and the shipped persona list: all nine real-model personas keep their own voice, the freemode persona is never left mute, no forced voice is ever the wrong sex, andConfig.force_persona_default_voice = truerestores the old behaviour exactly. Extend the voiceless list withConfig.voiceless_persona_models - A persona declaring a voice of the wrong sex is now refused rather than applied. That is a config mistake, not an instruction: it is dropped with one console warning naming the persona, and the model keeps its own voice
Config.police_ped_hit.protect_nearby_pedswas dead config. Nothing has read it since the v1.17.0 police cut, so the health floor it advertised never ran no matter what you set it to. It is removed, and the annotation says where the behaviour actually lives now.fight_chance_by_archetype,female_fight_multiplier,fight_chance_rangeanddriver_tail_reactionsare dead in the same way; they are kept so existing configs still load, and are now marked as doing nothing
Added
Config.vehicle_collision(client/collision.lua) — ships on. Two separate bugs behind one complaint:- A light tap killed them. GTA applies a critical-damage multiplier to vehicle impacts, so a 15 km/h clip could be fatal and the driver of a car you barely touched could die in the crash. Below
lethal_kmh(35) the victim is floored atsurvive_floorhealth (110) instead of dying, and the critical multiplier is removed from anyone near your moving vehicle — borrowed, and handed back the moment they leave the radius, you stop driving, or the resource stops. Abovelethal_kmhnothing is floored - They did not react when you drove into them, however many times. Every reaction path in this resource hung off a health drop, so a damage-free nudge produced nothing at all. Contact now comes from
IS_ENTITY_TOUCHING_ENTITY, independently of damage — and for a ped in a car, from your vehicle touching theirs, because a driver you rear-end is not touching your car, the two cars are. That is why traffic never reacted - Three tiers by your speed at contact: under
nudge_kmh(8) a nudge (turns, curses, leans on the horn), underbump_kmh(25) a bump (provoked through the normal path, so the archetype decides), at or above a hit (reacts as a struck person). A nudge and a bump never make you wanted; the star on a hit is stillclient/police.lua's decision and still only when the police module is on — but the victim reacts either way, which with police off (the default) previously produced nothing whatsoever - Live tiers, counters and a switch are on the Police tab of the F12 panel;
/intel_collisionprints the same thing - Verified in real Lua by driving the module's own thread body against a stubbed engine: a zero-damage nudge reacts, the same victim does not re-react inside
same_ped_cooldown_msbut does after it, a 20 km/h bump provokes rather than curses, a 20 km/h impact that would have killed is floored to 110 while a 60 km/h one stays lethal, a car-on-car tap reaches the driver without the two peds ever touching, an empty seat is never filled by the scan (GET_PED_IN_VEHICLE_SEATcreates an occupant in one), andenabled = falseis genuinely vanilla - Two bugs that harness caught in the module itself before it shipped: the tier was being decided from a speed read up to
interval_msbefore the contact, which made the same impact register as a nudge or a bump at random; and a victim's first-ever contact was suppressed whileGetGameTimer()was still below the cooldown
v1.18.0 — 2026-08-27
Removed
- The police module is now the wanted level and nothing else — and it says ALPHA where you can actually see it: the F12 panel's Police tab, the README heading, and the boot warning.
client/police.luawent from 3,529 lines to 546 - Gone: the pre-call window where the driver you hit walked over to talk, the apology / bribe / threaten panel, the responding officer who got out of his car and held a conversation, the bribe economy behind both (client panels and both server callbacks), the resource's own cruise and response patrols, the non-lethal band, and
client/apology_quickkey.luaentirely - Kept: the four triggers that decide when you should be wanted — hitting an officer, running a red light in front of one, hitting a pedestrian and being reported for it, and speeding past one — plus the clamp that stops this module pushing you past
Config.police_max_wanted_stars. The victim of a hit still reacts, because that is the NPC simulation and it runs throughclient/reactions.lualike any other provocation - The response is now GTA's own dispatch. That has a consequence worth knowing:
Config.cops_passive_modenow defaults to false, because with it true the dispatch services are suppressed and you get a wanted star nobody ever answers./intel_police_dumpwarns if it finds that combination - This behaviour is not behind a config key — it is in git history at v1.16.7
Fixed
-
The armed toggle wiped your whole loadout.
toggle_armed— surfaced in the panel's Player & World tab as "toggle armed (pistol)" — calledRemoveAllPedWeaponson its off-branch, so pressing it while carrying anything of your own took everything, not the test pistol it had given. It now gives and takes back only its own pistol; if you already carry one it holsters instead of confiscating, and says so -
Police shot you at one star again. That was a direct consequence of removing the non-lethal band in this same version — the band was exactly what held their fire. It is back as
client/police_nonlethal.lua, but minimal: hold fire, nothing else. No conversation, no bribe, no spawning, ~250 lines against the 2,600 that were cut. It reads the sameConfig.police_nonlethalkeys as before, so an existing config still applies -
Weather, time and the test pistol silently did nothing on a framework server. All three were fire-and-forget client-side calls that reported success regardless — so when a weather-sync resource or an inventory resource overwrote them a tick later, the button looked broken rather than outvoted. Each now applies, verifies, and reports the truth: weather and time check the value 700 ms later and name the sync resource that took it back, and the pistol checks whether it survived and names the inventory that stripped it. Weather and time also gained a hold variant that re-applies every frame for 15 s, which wins against a sync resource that pushes on an interval
Changed
- The debug now states the RULE, not just what happened. The Police tab opens with what is supposed to occur at each wanted level — below the ceiling police must not shoot, above it is vanilla, an officer you shoot leaves the band for good, band off means vanilla everywhere — then shows whether the rule currently applies and the sentence saying why not. Underneath, each of the four wanted triggers states its own condition, including the red-light known gap.
/intel_nonlethalprints the same rule and the same live verdict, so the console and the panel cannot disagree
v1.17.0 — 2026-08-26
Fixed
- The debug trace stopped updating after a while. Its flusher was a single thread with an unprotected
SendNUIMessagein the loop: one payload that would not encode killed the thread while tracing still read as ON — and because the toggle returned early when the requested state already matched, it could not be restarted. The flusher is now started once and lives for the resource's lifetime, idling while off, and a failed batch is reported and dropped instead of taking the loop with it - Looking at an NPC changed its voice.
/intel_inspectprobes the voice system every refresh, and that probe ran the real casting path — which latches a fallback voice into the ped as a side effect. A debug surface must not alter what it reports on, so the observing path is now explicitly dry: it answers the same question and writes nothing. The speech path itself is unchanged and still latches as before - The control panel and the current-scene panel both hung off the screen. Sized by content with a
max-heightcap, tall content grew the rendered box past its own computed height — measured in a real browser at 2560x1440, the control panel computed 1353px and drew 1805px, overhanging the top and the bottom. Both are now pinned to two opposite edges, so the viewport decides the height and no amount of content can change it. Verified by measuring every tab at 1280x720, 1366x768, 1600x900, 1920x1080 and 2560x1440: all inside, no horizontal page scroll - The control panel's tab row was clipped. Eight tabs in a non-wrapping row need 705px inside the shell's 592px of usable width, and the shell clips — so Modules, Tests, Lab and Trace were rendered outside the clip and could not be clicked. The row wraps now
- The current-scene panel could run off the bottom of the screen. It carried a
max-heighton the shell and another on its body, so the body could exceed the shell. It is now a proper flex column: fixed header, scrolling body, bounded by the viewport
Changed
-
No CSS
backdrop-filteranywhere. It is not rendered reliably by FiveM's CEF. The control panel now blurs the world through the game's ownTRIGGER_SCREENBLUR_FADE_IN/_OUT(Config.control_panel.blur_ms), and the two always-on surfaces are translucent with a text shadow rather than blurred -
The control panel opens on the right, not the centre, as a full-height sidebar, and no longer dims the screen — the backdrop is transparent and click-through
-
The debug trace is smaller and defaults to the bottom-right, clear of the scene panel and of a left-hand chat. Position and size are configurable (
Config.trace.side/vertical/margin/margin_y/width/height), and it steps aside to the left while the control panel is open so both stay readable -
The blue sphere over the inspected ped is off by default (
Config.debug_inspect.marker). It read as a game marker rather than a debug aid, and the panel already names the ped handle -
The F12 keybind registers under a new name.
defaultKeyonly applies the first time FiveM sees a keybind name; the old name carried whatever key was already stored against it on that client, which is why an F12 default could still open on another key -
DisableShockingEventswrote ped config flag 281, which isDisableGoToWritheWhenInjured. The correct index is 294, confirmed against the 468-entry enum carried inSET_PED_CONFIG_FLAG's own documentation. The key has shippedfalseby default throughout, so no default install was affected — but anyone who turned it on was suppressing the wounded-crawl animation and never touching shocking events. The customer-facing comment inshared/config.luanamed the wrong number too, and now names 294 -
A ped you once fought stayed permanently hostile.
configure_for_combatput the ped intoLRR_DRIVER_RAGE— a group that hatesPLAYERat level 5 — with no snapshot and nothing to restore it. The group is now snapshotted before the write and handed back on every combat-watchdog exit where the ped is still alive. Routing it through the ordinary hands-off wrapper would have been wrong: that wrapper skips borrowed engine peds, which would have left ambient NPCs unable to fight back at all, so a deliberately unconditional variant carries the snapshot instead. The restore is likewise its own function rather than the shared one — the shared restore writes its recorded value into both the current and the default group hash, which is right for the wrapper that sets both and wrong here, where only the current hash changes; reusing it would have overwritten a ped's default group with its pre-combat current group whenever the two differed. Verified against a stubbed engine: default preserved on restore, double-restore a no-op, and the shared path demonstrably corrupting the case the dedicated one protects -
The red-light detector's prop-scanning path had never once fired. It called
GetTrafficlightState, which is not a FiveM native — it is MTA:SA's — and thepcallaround it swallowed the nil call from the day it was written. The dead path and its prop-hash table are gone. Detection continues throughIsVehicleStoppedAtTrafficLights, which is real and always did the actual work -
Two
SetPedAsCop(c, false)calls removed. The native's own documentation states that toggling it false has no effect; both were no-ops sitting in cop-spawn paths and reading as though they did something
Removed
ped_is_handsupinclient/target_options.lua. It read ped config flag 296, which isNeverReactToPedOnRoof— and there is no hands-up flag anywhere in the 468-entry enum. The function was never called from anywhere in the resource, so it is deleted rather than repaired; inventing behaviour for dead code would have been the worse fix
Added
/intel_menu— one NUI with everything in it (client/control_panel.lua,Config.debugonly, bound to F12). Five tabs: Actions — all fourteen reactions on the ped you are facing, provoke, force a hail, every street and paired scene, the police module, the diagnostic commands, teleport to any persona. Modules — live on/off for every module and every debug surface, 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. Trace — per-system filters for the debug feed- It is a front end for what already exists rather than a second copy: the actions call the same functions the commands call, the checklist is the same plan
client/dev_test.luaowns, and the experiments are the same bench inclient/dev_lab.lua. Nothing in it has its own private truth - The only debug surface that takes NUI focus, released through
IntelNpcNui_SetFocuswhich owns the shared cursor bag. ESC closes it - The
commandaction runs from a fixed allowlist of this resource's own dev commands — arbitrary text and anything chained past a;is refused - It carries the whole of the older ox_lib dev menu, verified rather than assumed: a coverage check maps every one of that menu's 74 actions onto a panel button and reports 74/74. Player state, wanted level, all seven weathers, all four time blocks, the four test-ped spawns, per-persona teleport / force-open dialogue / despawn / mood, the reputation and memory editors including wipe ALL, the news roller, the hail category switches, the four perception triggers, every police toggle and every force-a-reaction command. 139 clickable actions against the old menu's 74
- It drives
client/dev_menu.lua's own helpers through a publishedIntelNpcDevOpsnamespace rather than reimplementing any of them, so the two surfaces cannot drift on what "toggle bloody" or "spawn a cop" means. The ox_lib menu itself is untouched and still on/intel_dev— F12 now opens the panel, andConfig.control_panel_on_key = falsegives F12 back to it - Per-NPC decision tree and timeline — the NPC tab of
/intel_menu. Pick the ped you are facing (or any NPC that has decided something) and read back exactly why it behaved the way it did, in two views: - Tree — grouped under the event that caused them. One gunshot opens an episode; the witness scan, each witness's chosen reaction and the state each reaction left behind all hang off it. That is the causal chain, top-down
- Timeline — the same decisions in time order with the gap between each, for when the question is sequence rather than cause
- An episode is one root cause and everything it produced. Roots are opened where they actually are — the perception witness scan and the witness fan-out — and it expires (
Config.trace.episode_ms, default 8 s) so a later unrelated event cannot be swallowed into it. No module had to learn about the tree: the bus stamps the current episode, and the views assemble it - Bounded on both axes:
ped_keepentries per NPC (default 40) andped_maxNPCs (default 24, least-recently-seen evicted), so a busy street cannot grow it for a session /intel_debug— the debug trace: WHY something happened (client/trace.lua). Counters tell you a reaction fired; they never tell you why that one. This does. A live feed of each decision with its reason and the numbers behind it:reactions— "on_aimed_at → beg_for_mercy (archetype 'timid' maps this trigger straight to it)", and when a random swap fires, which chance it beat. Also why nothing happened: debounced, no archetype, no mapping for that triggerperception— "gunshot: 3 of 9 nearby peds witnessed it. 6 rejected (out of 100m range, no line of sight, dead, or not human)", and whether the report roll beatperception_dispatch_chancewitness— "backs the victim up: same set 'ballas'. Attacker 1 of 4 allowed." versus "only watches: he is gang 'vagos', the victim is 'ballas' — not his set, so no solidarity"gossip— which ped told which, about what, at which hop of how many allowedstate— why a ped now refuses to talk, and for how longvoice— own voice, cast fallback, or silent, with the reason- Colour-coded per system, capped ring buffer, filterable at the source from the panel. Free while off: every call site is guarded and the bus returns on its first line, so a disabled trace costs one boolean read per decision. While on, entries are batched to the NUI every 250 ms rather than one message per event
- The instrumentation only reports what the code had already decided — no decision point was changed to produce a line
/intel_test— the guided test flow (client/dev_test.lua,Config.debugonly). Not another pile of dev commands: an ordered walkthrough of every mechanic in the resource, 38 steps across 18 groups, from boot and bridges through perception, NPC state, all fourteen reactions, gang witness fan-out, gossip, dialogue, voice, hails, life points, street and paired scenes, ambience, police, economy, persistence, the open engine questions, and shutdown- Each step states what to do and what should happen, switches on the debug that belongs to it, and streams the relevant state live — printing only when something changes, so the console stays readable
- Steps that can be verified without you carry an automatic
check: generated data present, life-point spawn/release balance, street-scene cast fully restored after an abort, police state actually reset to zero, gossip hops greater than zero, and — for the v1.17.0 fix — that a ped which once fought is no longer inLRR_DRIVER_RAGE pass/fail <note>/skiprecord and advance;reportprints the whole table with every failure note. Progress persists across a resource restart, so a full pass can be spread over several sessions- Inline actors so a step never makes you leave the flow:
/intel_test react <name>,/intel_test provoke,/intel_test hail - Step text follows
Config.locale— English and Hungarian /intel_inspect— the single-ped inspector (client/debug_inspect.lua,Config.debugonly). Everything knowable about the ped under your crosshair, in three columns: who it is, what THIS resource believes about it, and what the ENGINE will admit to. The third column is the point — and it ends with the questions the engine refuses to answer, named one by one: seeing range, hearing range, combat attributes, flee attributes and event blocking all have setters and no getters, and there is no native at all that reports which script task a ped is running. Those six gaps are the reasonrelationships.luamust snapshot before it writes, so the panel shows them rather than hiding them- Because nothing reports the running task, the panel asks
GET_SCRIPT_TASK_STATUSabout each task this resource knows how to issue and reports whichever answers — an honest answer to a question the engine will not answer directly /intel_inspect pinlocks the panel to the current ped so you can walk around it;/intel_inspect dumpprints one full record to the console- Costs nothing while closed: the frame tick is registered only while the panel is open, and every read is throttled to
Config.debug_inspect.refresh_ms /intel_scene— the always-on current-scene panel (client/scene_panel.lua,Config.debugonly). The world inspector to/intel_inspect's ped inspector: live counters for every module, the running street and paired scenes with their cast, the non-lethal band and how many cops it is holding, wanted level, dispatch state, zone, time block, and what the frame dispatcher is actually drawing right now- It is a NUI surface, so the browser owns the redraw and Lua costs nothing per frame. The snapshot is compared against the last one and the message is skipped when they match, so a scene that is not changing sends nothing at all after the first tick
- The one genuinely expensive thing it can do — sweeping the ped pool to count NPCs by owning module — is behind
/intel_scene deep, off by default - It renders as a sibling of the dialogue root rather than inside it, so it stays visible whether or not a conversation is open, and it never takes NUI focus
/intel_lab— the engine-audit experiment bench (client/dev_lab.lua,Config.debugonly). Fourteen experiments, one command each, that settle the questions the audit could not answer from documents. Nothing runs until an experiment is started, every experiment self-stops after five minutes, and spawned test peds are always cleaned up./intel_lablists them,/intel_lab dumpreplays the log- Run
witness_entitiesfirst: it is the cheapest and can invalidate the largest proposed change on its own - Where a native could not be confirmed to exist, the harness reports the gap rather than printing a plausible-looking value —
NetworkGetEntityOwneris a Cfx addition absent from the R* native database, so ownership showsn/arather than a misleading-1 docs/NATIVE_AUDIT.md— the full audit: the replacement table, per-system verdicts with the refuted claims kept in (so nobody re-opens a settled question), the sequenced migration plan, and the explicit list of what has no native equivalent, so nobody deletes the reason the resource exists
Changed
Config.police_redlight.extra_light_hashesis deprecated and ignored. It fed the dead prop scanner. The key still loads so existing configs do not break. The comment now records the known gap this leaves: a player who blows through a red without ever slowing is not caught — a case that was never actually caught in practice
v1.16.7 — 2026-08-26
Fixed
- A cop in the non-lethal band can no longer draw a shotgun. The band holstered the weapon and forbade switching, but the ped still OWNED the gun — and GTA's combat AI re-draws an owned weapon the instant it engages, which is exactly the shotgun coming out at one star. Firearms are now physically removed for the duration and the recorded weapon is handed back on release (
Config.police_nonlethal.disarm, default true) - The window a freshly dispatched officer could still fire in is narrower: the enforcement pass runs every 400 ms rather than 750. A cop dispatched into the band arrives armed and is disarmed on the next pass, so that interval is the honest width of the gap — it is not zero
v1.16.6 — 2026-08-26
Fixed
- Police no longer open fire on a player with no wanted level.
nonlethal_should_applybegan withif stars <= 0 then return false end, so the non-lethal band deliberately skipped zero stars and left every cop fully lethal against a player who had done nothing. The band now applies wheneverstars <= max_stars, so at the defaultmax_stars = 1lethal force begins at two stars. The arrest task is unchanged from before — it is still only issued from one star up, so an innocent player walking past a cop is neither shot at nor grabbed max_starsis read throughtonumber. Writing it as a string (max_stars = "2") threw inside the enforcement thread and killed the band for the rest of the session, which put police back to fully lethal at zero stars with nothing in the console pointing at the config
Added
Config.police_nonlethal.self_defence(defaulttrue). A cop you shoot or ram drops out of the band immediately and fights back with everything, at any wanted level: his snapshot is handed back,CA_ALWAYS_FIGHTis set, fleeing is disabled and he re-draws a sidearm if the band had holstered him. He is also no longer handed an arrest task while he is fighting. The exclusion is latched at every point this resource consumes GTA's last-damage flag — the cop-attack scan, the reaction sweep, the damage attributor and both scene escalations — so a second thread clearing that flag can no longer erase the fact that you shot him./intel_nonlethalnow printsself_defenceand how many cops it currently excludes
Changed
max_stars = 0no longer disables the band. It now means "band at zero stars only". Audit your existing value: usemax_stars = -1to keep the feature installed with the band switched off, orenabled = falseto remove it entirely
v1.16.5 — 2026-08-26
Fixed
- The wrong-sex voice, for real this time. v1.16.3 was meant to stop a man being handed a woman's voice, but it edited an older shape of the function than the one actually shipping — the live
sexed_poolsstill readmale ~= false, which sent unknown-sex peds down the male branch, and still returned the unsexed pool (277 named story characters, several of them women) and the mixed pool (sorted female-first) as fallbacks behind it. Either could be reached. Now the function answers with exactly one pool — the ped's own sex — or nothing at all, in which case the ped speaks in its own voice. Proven against the shipped data: no voice appears in both the male and the female pool, no female-named voice sits in a male pool or the reverse, and an unreadable sex is never handed a pooled voice
v1.16.4 — 2026-08-26
Fixed
- An NPC no longer says the same line twice. While the dialogue panel is open it already types every line the NPC speaks, but the same line was still being pushed out as a floating bubble and written into the chat feed — so one reply arrived two or three times over. v1.15.0 suppressed only the built-in floating copy and left the chat bubble and the feed writing. Now, for the ped you are actually talking to, the panel is the only place the line appears; ambient lines, gossip and street scenes are untouched and still bubble and log as before
v1.16.3 — 2026-08-26
Fixed
- A known sex is never handed another sex's voice. The sexed pools themselves were clean, but the fallback chain behind them was not: a male ped that found no male entry for a context dropped through to the unsexed pool — 277 named story characters, several of them women — and then to the mixed pool, which is alphabetically female-first. Now, when the ped's sex is readable, only that sex's pool is consulted; nothing matching means the ped speaks in its own voice instead. Only a ped whose sex cannot be read still sees the unsexed pool. A latched fallback of the wrong sex is dropped rather than reused. Verified over 4,000 simulated peds: zero wrong-sex picks
v1.16.2 — 2026-08-26
Fixed
- The spoken panel stops saying the same thing every time.
tone_contextsmapped each reply tone to exactly ONE speech context, so every greeting wasGENERIC_HIand every conversation lineCHAT_STATEforever — flatter than the ambient speech this resource has always used, which picks from a pool per category. A tone may now be a single context or a pool, the shipped map is pools throughout, and the pick avoids what was said last, sized to the pool so a two-entry pool strictly alternates instead of repeating
v1.16.1 — 2026-08-26
Fixed
- Closing a conversation no longer disarms the ped you were talking to.
end_focusrestored the shared behaviour snapshot withcombat = true, and that snapshot is written by whoever touches the ped first — for a brave, aggressive or cop archetype that is spawn-timebrave_always_fight, recorded as "was off". So every conversation with such a ped silently clearedCA_ALWAYS_FIGHT, re-creating "punch him and he walks on" for that whole class of ped. The dialogue camera now releases exactly the two locks it took (the task hold and, when enabled,CA_DISABLE_BULLET_REACTIONS) and leaves everyone else's snapshot alone. It also hands a persona back its own blocking value instead of a hardcodedfalse - A cop released from the non-lethal band is handed back correctly.
nonlethal_restoreforcedCA_ALWAYS_FIGHTon for every released cop — including engine dispatch cops that never had it — and never clearedCA_CAN_BUST, so each one-star encounter left permanent state on engine-owned police. The four attributes the band writes are now recorded per cop before the first write and replayed on release:CA_CAN_BUSTcleared,CA_ALWAYS_FLEEandCA_PERFECT_ACCURACYback to the engine default, andCA_ALWAYS_FIGHTrestored only on cops this resource spawned and set it on - A melee lock-on can no longer fabricate an assault victim.
Perception_ResolveMeleeVictimconsultedGET_MELEE_TARGET_FOR_PED(with a 7 m tolerance) before the "is the player actually swinging" gate, so raising your fists near a stranger tagged himwas_assaultedand fired hison_hitreaction. Both the lock-on channel and the forward cone are now behind that gate, which is whatshared/config.luaand the v1.16.0 notes always claimed - The struck ped is reliably excluded from his own witness list. Two threads raced over GTA's last-damage flag: the reaction sweep cleared it every 500 ms while the perception resolver read it every 750 ms. The resolver now consults a short-lived cache of the ped the sweep actually saw take damage (
Config.perception_victim_memory_ms), and the melee-fight witness sweep waitsConfig.perception_melee_witness_delay_ms(600 ms) so the punch has landed before witnesses are collected — a swing edge-triggers before any damage exists, which is why the victim used to end up in his own witness list victim_by_damageno longer counts vehicle damage as a punch.HAS_ENTITY_BEEN_DAMAGED_BY_ENTITY's third argument wastrue, so a ped clipped by your car and still standing was returned as the melee victimpersona_block_eventsandpersona_never_fleeare live keys again.client/spawn.luapassed them only as the hands-off allow key, which is short-circuited for resource-owned peds, so their values were never read. They now gate the write the way thelife_point_*keys already didConfig.police_nonlethal.interval_ms = 0no longer produces a permanent per-frame thread. In Lua0 or 750is0; the read site now clamps to a 100 ms floorlethal_dispatch_servicesused the wrongeDispatchTypeids. The default list disabled undefined service12and never disabled PoliceBoat. Corrected to{ 2, 4, 8, 13, 14, 15 }(2 PoliceHelicopter, 4 SwatAutomobile, 8 PoliceRoadBlock, 13 SwatHelicopter, 14 PoliceBoat, 15 ArmyVehicle) againstENABLE_DISPATCH_SERVICE's own published enum, in bothshared/config.luaandclient/police.luawas_assaultedis no longer spread by gossip. Gossip copies an event onto the neighbour under the same key, so a first-person "I was assaulted" record was landing on peds who were never touched — the exact inverse of what the key documents. It shipsfalseinConfig.gossip_worthy_events; bystanders still getsaw_assaultfrom the witness sweep- The
/intel_devmenu's "Spawn test ped" entries assign their relationship group again. The demo ped was never marked resource-owned, so the assignment silently no-opped /intel_native_behaviourtells the truth. It printed every override as APPLIED whenever the config table was missing or the master switch was off; it now evaluates each key's real call-site gate, and flags the six keys whose call site only ever runs on peds this resource spawns
Changed
- Anna is a reporter and Denny is a mechanic in
data/personas.lua. Every line either of them speaks was already written that way — Anna introduces herself as "Anna Vargas, Tribune", Pete's referral topic calls her "the reporter", and all 33 of Denny's greetings are shop talk — butrolesaid "a bookkeeper at the pier" and "a cafe owner with ears everywhere", which is what{persona_role:...}renders mid-conversation was_assaultedhas lines behind it. The tag was live in the dialogue engine but nothing indata/required it, so the victim-memory feature was invisible in play. Civilian, gang, cop, homeless, working-girl and default ambient trees now greet the player who hit them- Ambient greeting pools filled out further (thinnest is now 12 lines), and Anna and Denny each got a sixth evening greeting
Documentation
shared/config.lua,CHANGELOG.mdandINSTALLATION.mdclaimed that deletingConfig.native_ped_behaviourfalls back to "the v1.15.3 tuning exactly as it shipped". It does not — every key is read with a call-site fallback and those fallbacks are the v1.16.0 defaults, so deleting the table gives you the shipped defaults. All three now say so, andshared/config.lualists the eleven key values that produce a genuine v1.15.3 rollbackINSTALLATION.md§5.7 said you could ship the new Lua without the new config blocks "without any behaviour change". The opposite was true: the non-lethal band and the anti-repeat picker are both on when their config is absent. The section now says so and spells out the minimal paste for a true no-change upgrade- Corrected the v1.16.0 content figures: ambient dialogue 1108 → 1768, static personas 647 → 965, single-variant pools 44 → 28 (not "none remain"), thinnest ambient greeting pool 3 → 12, and "six to eight" evening/night greetings for the four personas that had one of each
routine_night_seeing_range/_hearing_rangeno longer imply a real snapshot: GTA exposes no getter, so the resource writes back a fixed 30.0 when the night block ends. The same caveat is now stated for the cleared flee bitfieldassign_relationship_group/assign_block_eventsare documented as reserved for callers who invokeIntelNpcRelations_AssignToPedon a borrowed ped — nothing in the shipped resource doesREADME.md's non-lethal paragraph now describes what is actually restored on a released cop
Added
Config.perception_victim_memory_ms(default1500) andConfig.perception_melee_witness_delay_ms(default600), both documented inshared/config.luaPerception_NoteStruckPed(ped)— the reaction sweep hands the resolver the ped it just saw take damage, before it clears GTA's last-damage flag. Call it from your own damage handler if you task melee damage yourself
v1.16.0 — 2026-08-26
Fixed
- Punch a ped and he no longer walks on.
data/ped_flags.lua's combat-attribute numbers were wrong, verified againstPED::SET_PED_COMBAT_ATTRIBUTES(0x9F7794730795E019) in FiveM's own native database:CanUseCoverwas reallyCA_ALWAYS_FIGHT, andDisableBulletReactionswas reallyCA_ALWAYS_FLEE. Soconfigure_for_combat— the function every single fight reaction goes through — was setting always flee on the ped it was about to task into combat, and the gang-toughness sweep was never setting always fight at all. Every key name is unchanged; the numbers are corrected and the file now carries the full verified enum - The ped you hit is no longer recorded as a witness of his own assault.
saw_assaultwas landing in the victim's own memory because the melee watcher collected every ped in visual range and the victim is by definition in visual range. The struck ped is now resolved properly (GET_MELEE_TARGET_FOR_PED, a damage read, and a lock-on and forward-cone fallback that only fire while the player is genuinely swinging, so a mere melee lock-on cannot fabricate a victim), excluded from the witness sweep, and given his ownwas_assaultedmemory tag with a 6 h TTL, which ambient civilians, gang members, cops, homeless and working girls now have lines for. It is deliberately not gossip-worthy: gossip copies an event onto the neighbour under the same key, so spreading a first-person "I was assaulted" record would land it on peds who were never touched - Being punched now produces the victim's own reaction. A gang member fights back, a coward flees, a cop moves to arrest — chosen by the victim's archetype, through the same
on_hittrigger as before. It no longer depends on the ped taking damage, so damage-proofed ambient peds react too, and the victim is no longer swept intobring_witnesses_to_face's task clear, which was separately cancelling his reaction - Reaction cooldowns are tracked per trigger. Aiming a weapon at an NPC no longer swallows the punch that follows a second later.
on_hitgets its own shorter cooldown (Config.reaction_hit_debounce_ms, 2500 ms) cop_engage_combatwas clearingCA_ALWAYS_FIGHTon responding officers one line before tasking them into combat- Ambient peds are no longer shipped with five stacked "ignore the world" flags.
IntelNpcRelations_AssignToPedwas setting blocking-of-non-temporary-events, disable-shocking-events (config flag 281), disable-bullet-reactions and a wiped flee bitfield on every ped it touched, and life points additionally had alertness pinned to 0 and critical hits switched off. A ped carrying all of those cannot notice being punched. All of them are now opt-in override keys underConfig.native_ped_behaviour, defaulting to GTA's own values Config.block_temporary_eventsworks again.IntelNpcRelations_AssignToPedslammed it back to a hardcodedtruenine lines afterclient/spawn.luahad read the key, so setting itfalsehad no effect at all- Peds you talk to are handed back to the engine unchanged.
client/focus.luacleared the flee bitfield and set disable-bullet-reactions on every ped a conversation opened on — including engine-owned pedestrians — and never restored either. Every random ped you had ever spoken to was left permanently unable to flee. Both are now off by default on borrowed peds and snapshot-restored on every exit path, hostile and panicked included - A witness that cowers stops cowering. The
ForceCowerflee flag was set on every civilian witness and never cleared, so a ped that cowered once stayed cower-flagged for the rest of its life. It is cleared afterConfig.perception_cower_ms(8 s) - Stopping the resource while it was suppressing police no longer leaves the session broken.
client/ai_suppression.luacould exit with dispatch services dead andSetPoliceIgnorePlayer(true)still set; it now restores them on stop, and only if it actually suppressed anything {player_name}expands in ambient dialogue.render_topic_menubuilt its mention context fromsession.persona_id, which is nil for an ambient ped, while every other line in the file already usedsession.memory_key— the key the player's name is actually stored under. Introduce yourself to a pedestrian and his greetings degraded to "friend" from then on- Conversations stopped sounding monotonous. The line picker was a bare
math.randomover the pool with no memory of its last pick — measured over 400 draws, a 3-line pool repeated itself back-to-back 34% of the time and a 5-line pool 20%, which is what made large pools sound small. Every pool now keeps a short ring of the lines it just used and re-rolls while the pick is still in that ring; measured back-to-back repeats drop to zero on every pool the player actually hears
Added
Config.native_ped_behaviour— one table, one rule: engine-owned pedestrians keep GTA's ownpedpersonalitytuning, peds this resource spawns are configured by this resource. Every key is an override switch whose annotation names the native default, so vanilla behaviour is one line away, and everything this resource does change on a borrowed ped is snapshotted before the write and restored when the ped is let go. Deleting the whole table does not restore v1.15.3: every key is read with a fallback at its call site and those fallbacks are the v1.16.0 defaults, so you get the shipped defaults (GTA's own reaction defaults on ambient, dialogue and life-point peds). The table's own block comment lists the eleven keys to set for a genuine v1.15.3 rollbackConfig.police_nonlethal— police never use lethal force at or below a configurable wanted level (default 1 star). GTA's own levers first: lethal dispatch services off, optionalSET_WANTED_LEVEL_DIFFICULTY, cops forced toWEAPON_UNARMEDwith weapon switching blocked and accuracy 0,CA_ALWAYS_FIGHTcleared,CA_CAN_BUSTset andTASK_ARREST_PEDat close range. This is a band, not a pacifier — abovemax_starspolice are unchanged. Every touched cop is snapshotted and restored, and the whole thing no-ops when another resource owns the police (yield_to_resources,require_police_system, or native dispatch being in charge)Config.line_no_repeat_depth/Config.line_no_repeat_tries(default3/6) — the anti-repeat line picker. Depth is capped automatically at half the pool size, so small pools alternate instead of cycling in a fixed order.0restores pure-random pickingConfig.perception_victim_enabled/_range/_cone_dot/Config.perception_cower_msandConfig.reaction_hit_debounce_ms, all documented inshared/config.lua- 660 new ambient dialogue lines and 318 new static-persona lines (ambient 1108 → 1768, static 647 → 965). 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 what is left are rob/mug outcome and counter-question stubs. The thinnest ambient greeting pools went from 3 lines to 12+, and every static persona that had exactly one evening and one night greeting now has six to eight
- Mood tags are live in dialogue.
Config.mood_poolwas rolled, displayed on the panel and applied to the face, but across 1108 ambient entries not one carried a mood tag — so every civilian spoke in one voice regardless of the badge. 221 ambient and 126 static entries are now mood-written, and ambient dialogue also uses the memory tags (first_time,met_recently,long_time_no_see,frequent_visitor,known_to_npc) and perception tags (saw_assault,heard_saw_shooting, …) that it previously ignored entirely refusal.prostitutein both language packs — working girls fell through to the 3-linerefusal.default. The other fiverefusal.*pools were expanded too, with existing lines kept verbatim, and refusals are the single most-heard ambient string set on the server (Config.ambient_refusal_chanceis 0.75 for gang and 0.85 for cop)was_assaultedinConfig.gossip_worthy_events(shippedfalse— see above) andConfig.action_memory_event_ttl_ms- New globals for anyone extending the resource:
IntelNpcBehaviour_*(apply + snapshot + restore),IntelNpcOwnedPedSet,IntelNpcCombatAttr(the verifiedeCombatAttributenames),pick_freshinshared/locale.luaandUtils.pick_contextual_rawfor the un-wrapped original picker - Dev commands
/intel_native_behaviourand/intel_nonlethal(both needConfig.debug)
Changed
- Life-point peds react like ordinary pedestrians. They flee, flinch, take critical hits and can become alert.
shared/config.luaalways said life points are deliberately not static personas, "which is what lets hail / perception / gossip / trauma treat them like any engine ped" — the decoration contradicted that. For the old statue behaviour setlife_point_block_events,life_point_never_fleeandlife_point_disable_explosion_reactionstotrue,life_point_suffers_critical_hitstofalseandlife_point_alertness = 0 - A ped this resource tasks into a fight now flinches when it is hit.
CA_DISABLE_BULLET_REACTIONSinconfigure_for_combatis behindConfig.native_ped_behaviour.combat_disable_bullet_reactions, off by default — a hostile ped that shows no reaction to being shot is the visual half of "he walks on" IntelNpcRelations_AssignToPed(ped, group_name, opts)takes an optional third argument ({ block_events = bool }). The two-argument call is unchanged and still followsConfig.block_temporary_events- The night-routine block no longer pins persona alertness to 0 during the day, fighting the engine's own dynamic alertness on every tick; it restores the pre-night value instead. Its values are exposed as
routine_night_alertness/routine_night_seeing_range/routine_night_hearing_range
v1.15.3 — 2026-08-26
Fixed
- Paired scenes no longer degrade every time. The walk-in required the player to move less than 0.5 m and turn less than 35 degrees while the NPC covered the distance — nobody stands that still, so the handshake aborted on
degraded_driftbefore it started. The gate now asks the question it meant to ask: are you still with this NPC — close enough, still roughly facing them, and not wandered off from where you began - No more teleport after a paired scene. A life-point ped left its scenario spot to shake your hand, and the re-task watchdog then warped it back the instant the scene released it. It walks back now instead
v1.15.2 — 2026-08-26
Fixed
- Ambient NPCs keep their own voice.
Config.dialogue_speech.fallback_for_unknown_voiceshippedtrue, which cast a pool voice over every ped the resource had not cast itself — but the game had already given that ped a voice matching its model, correct in sex and character. Overriding it is what put a woman's voice on a man, and v1.15.1's sex-split pools only narrowed the damage instead of removing the cause. It shipsfalse: an untracked ped speaks in its own voice, or stays silent if that voice has no take for the line. Personas, whose voice this resource does cast and can therefore check, still fall back within their own sex
v1.15.1 — 2026-08-26
Fixed
- A man gets a man's voice. The fallback voice pools in
data/generated/voice_contexts.luaare now split by sex (_by_context_m/_by_context_f/_by_context_x) and the pick is made againstIsPedMale. The old single pool was capped alphabetically, so every AF name sorted ahead of every AM one and a 24-nameGENERIC_HIpool ended up entirely female — every man who needed a fallback line got a woman's voice Config.default_gta_voice_nameis no longer handed to female personas unchanged. It ships as a male voice, so the three shipped female personas that declare nogta_voice_nameof their own (Erika, Anna, Candy) were given it at spawn — and becauseSetAmbientVoiceNamewas already male, the new sexed fallback could never correct them. The default is now checked against the ped at spawn and swapped for that sex's voice when it provably does not match. A default whose name encodes no sex, or a persona that names its own voice, is left exactly as configured- A fallback voice no longer changes between lines of one conversation. The pick was stable per context, not per ped, so the same NPC could say hello as one person and goodbye as another. The chosen voice is now latched to the ped and reused for every following line it has a take for
- A missing sexed pool no longer silences every NPC. The
_m -> _x -> _by_contextfallback chain was walked withipairsover a table that could holdnil, which stops at the first hole and did nothing at all when the first pool was absent. The chain is walked explicitly now, and adata/generated/voice_contexts.luaolder than this change prints one clear warning telling you to regenerate it - Every whitelisted speech context now has a male and a female pool.
GENERIC_FUCK_YOUis supported only by named story characters, whose names carry no sex, so both sexes fell through to one mixed pool where roughly a third of the entries were women. The generator now sexes those named voices Config.simpleasf.rep_rank = falseno longer silences two other surfaces. The flag early-returned out of the whole rep-change hook, which also killed the trust-down toast and the trust-tier promotion achievement banner thatConfig.simpleasf.achievementsstill advertised as on. It now gates the screen-centre rank/XP overlay and nothing else — with it off, a trust change outside the dialogue panel is a quiet toast, and promotions still raise their achievement
Changed
Config.simpleasf.rep_rankshipsfalse. The XP-style rank banner after a conversation reads as a game-y overlay on a roleplay server. The dialogue panel header's trust bar carries the same signal without covering the NPC's face. Turn it back on withConfig.simpleasf.rep_rank = true/intel_pairedpicks the ped you are looking at with the same forward-cone search the quick-talk key uses, instead of a hairline camera ray that missed a ped standing slightly off-centre and reported "no ped in front of you"tools/generate.jsis v1.2.0. The generated header's counts now cover all four pool tables instead of only_by_context, and the metadata-key note names all six keys. Output is byte-identical across re-runs, as before
New
Config.paired_scenes.dev_target_range(12.0 m) andConfig.paired_scenes.dev_target_min_dot(0.5, ~60°) — the search radius and cone that decide which ped/intel_pairedacts on
v1.15.0 — 2026-08-26
Changed
- Every city-life module ships on.
Config.scenario_groups,Config.street_scenes,Config.paired_scenesandConfig.dialogue_speechjoinlife_pointsandworld_ambience— a feature nobody can see is not a feature. Each one is still a singleenabled = falseaway from off, and every sub-toggle and cap is unchanged - README and INSTALLATION restate what ships on, and the spoken-panel section says plainly what to listen for before going live: the bark is a canned English line, not your written text
v1.14.0 — 2026-08-26
Changed
Config.world_ambienceships on, with its car radios, ambient boats and garbage-truck rounds all enabled — the layer did nothing on a fresh install, which is not what an ambience feature is forConfig.debugships on, so/intel_dev, the F12 dev menu, every/intel_*command and the debug overlays work the moment the resource starts. Turn it off for production withsetr lastresort_intel_npc:debug 0inserver.cfg— the convar overrides the config file, so you never edit it back
v1.13.0 — 2026-08-26
New
- Street scenes (
Config.street_scenes, ships OFF) — authored two- and three-ped conversations that play out on the pavement between peds that were already standing there. Nothing spawns: 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— corner gossip, a street argument, a lost tourist, an evening corner deal, a three-hander shift change, a panhandler, and two officers killing time — in the same commented style asdata/life_points.lua, with the full entry schema documented in the file header - Speech and bubbles never overlap. One line at a time, one speaker at a time; the next line cannot begin until the previous bubble has expired, and the previous speaker's audio is stopped explicitly when the speaker changes. A line whose voice has no take for its context (which fails silently in the engine) holds for a length derived from its own text instead of stalling the scene
- A line marked
gossip_payloadperforms a real gossip hop at the moment it is spoken — the speaker's newest gossip-worthy memory is pushed into the rest of the cast through the sameNpcState_AbsorbGossippath the gossip loop uses, hop count and confidence decay included. A line with abubble_seenvariant switches to it when the cast actually remembers the player - You can cut in. Inside the entry's
interject.radiusa prompt appears (helpbox, floatingtext3d, ornone); taking it ends the scene, restores the whole cast and opens the normal dialogue panel on the nearest speaker - Every abort path restores every cast ped — damage, death, despawn, gunfire, panic, hostility, fleeing, ragdoll, combat, entering a vehicle, a cast ped leaving its leash, player death, teleporting away, the config being flipped off mid-scene and resource stop all route through one teardown: tasks cleared, blocking flag cleared, busy cleared, back to the ambient AI. A wall-clock watchdog and a guarded tick chain cover the rest: a scene that outlives its own line budget, or a tick that throws, tears the whole cast down instead of stranding it
- A scene is client-local. No event, no state bag, no server call — each player sees it on their own copy of the population
- Paired synchronized interactions (
Config.paired_scenes, ships OFF) — trust made physical. A greeting from an NPC who trusts you becomes an actual handshake, a familiar gang contact gets a dap, and giving an item, bribing or tipping 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. The NPC cannot reach the slot, the ground is not flat, you turned away, the anim dictionary would not stream, the ped got scared — every one of those degrades silently to exactly the behaviour of the previous version, and the dialogue panel opens either way: after the handshake when there was one, immediately when there was not. A half-played handshake looks worse than no handshake
- Initiator-only by default.
networked = falseuses 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.networked = truewidens it when you want it - Both peds are handed back on every path — tasks cleared, look cleared, keep-task off, blocking flag off, busy cleared. A watchdog armed the moment the NPC is tasked ends a scene whose poll chain died, and the module being flipped off mid-scene ends it on the next poll. 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 = falsekeeps the player's own ped out of it entirely data/generated/anims.luanow carries validated animation pairs. Everydict/clip_a/clip_bnamed inConfig.paired_scenes— including the fist-bump alternative the config comments document as a one-line swap — is checked at build time against the reference anim data, and a typo fails the build instead of shipping a T-pose. The table was empty until now/intel_scene_play— street-scene status, every registered scene with its preset, weight, role and line counts, remaining cooldown and whether it is allowed right now, plus a live row per running scene./intel_scene_play <id>force-starts one past chance, cooldowns, time block and the distance floor;/intel_scene_play stopaborts every live scene and restores every cast ped. Gated onConfig.debug/intel_paired <kind>— force the named paired interaction on the ped you are looking at, past the enabled flag, trust tier, category and cooldown, and report the outcome or the refusal reason. Gated onConfig.debug/intel_statusgrows a counter block for each module — scenes registered and dropped, live runs, cast peds, scans, attempts, cast failures, lines with own-voice / fallback / silent / timed-out splits, bubbles, gossip hops and interjections for street scenes; requests, starts, completions, every refusal gate, every degrade path and the last kind and reason for paired interactions- New client globals —
IntelNpcScenes_AbortAll(reason),IntelNpcPaired_Try(ped, kind, opts, on_done),IntelNpcPaired_Eligible(ped, kind),IntelNpcPaired_IsActive(ped),IntelNpcPaired_Abort(reason), and the eventlastresort-intel-npc:client:pairedScene - New locale keys —
street_scenes.*,scenes.*,paired.*,status.street_scenes_headerandstatus.paired_scenes_header(enandhushipped)
Changed
- A hail no longer fires at a ped another module is already using. All three hail candidate scanners (personas, ambient peds and the prostitute scanner) now skip a ped marked busy, which closes the window where a hail could re-task a ped mid-street-scene and spin them away from the conversation
- The life-point scenario watchdog leaves a borrowed ped alone, the same way it already left a hailing ped alone: it now also stands down while the ped is marked busy or is in a live paired interaction, so it cannot re-task a ped out from under a scene it did not start
- Opening an ambient dialogue tries the dap and then the handshake before the panel opens. When one is accepted the panel is skipped for that press — the handshake is the greeting — and the paired scene fires before the focus camera freezes the player, which a synchronized scene cannot survive. With
Config.paired_scenes.enabled = false(the default) both calls return immediately and the panel opens exactly as before - Giving an item, bribing or tipping fires the hand-over after the interaction has already succeeded, so the animation never sits between the player and the result
- The city life layer is now five modules, not three.
shared/config.lua, the README and INSTALLATION all describe it that way; life points remain the only one that ships on
v1.12.0 — 2026-08-26
New
- Spoken dialogue panel (
Config.dialogue_speech, ships OFF) — every line the dialogue panel types can be paired with a tone-matched GTA ambient bark in the NPC's own voice, and the typewriter's completion is tied to the audio instead of to the character count. The line's tone (greet,farewell,kick,bribe,threaten,investigate… the same tone keys the panel colours its choice chips with) maps to a speech context throughConfig.dialogue_speech.tone_contexts - The audio and the text start together. The panel tells the client the moment the typewriter actually begins, so the bark no longer leads by the length of the "…typing" pause
- It cannot hang. The written
typingSpeedis the floor — audio only ever makes the text hold on its last character or drain faster (catch_up_ms) to catch up, never type slower. The panel enforces its ownmax_wait_msdeadline independently of the client, so a bark that never starts (very common — 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 - No permanent threads.
IsAmbientSpeechPlayingis polled on apoll_interval_mstimer that only exists while a line is actually speaking - Persona voice validation (
Config.validate_persona_voices, ships ON) — every persona'sgta_voice_nameandConfig.default_gta_voice_nameare checked at boot against the shipped voice list. A name GTA does not have makesSetAmbientVoiceNamea silent no-op and leaves the NPC mute forever with nothing in the console to explain it; now it produces one clear warning naming the persona and the bad value, and falls back to a valid voice instead of being silently wrong - Per-context fallback voice casting — no voice in the game covers every context this resource uses, so there is no single "does everything" fallback. Each context has its own pool of voices known to have a take for it, preferring ordinary ambient civilian voices, and the pick is deterministic per ped so the same NPC always falls back to the same voice. Exposed as
IntelNpcVoice_ForContext(ped, context)→supported, fallbackVoice; the answer is cached per ped and swept when the ped is gone data/generated/voice_contexts.lua— generated reference data shipped with the resource: which ambient voice names are real, and which of 17 ambient speech contexts each of them has a take for. Client-side only, nothing fetched or parsed at runtime/intel_voice <context|tone>— for the ped you are looking at: the voice this resource assigned it and whether that name is real, whether the context is checkable, whether the ped's own voice has a take for it, the fallback that would be cast, and the exact native call that would be made. Gated onConfig.debug/intel_statusgrows a voice/speech 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)- New locale keys —
status.voice_headerand thevoice.*family (enandhushipped)
Changed
- Maggie's
gta_voice_namewasA_F_M_BUSINESS_02_BLACK_FULL_01, which does not exist — that variant was never shipped by the game, soSetAmbientVoiceNamesilently did nothing and she has been falling back to whatever her model came with since she was written. Corrected toA_F_M_BUSINESS_02_WHITE_MINI_01, the real ambient voice for hera_f_m_business_02model. The new boot check is what found it - While a panel line owns an NPC, the older per-pool speech is suppressed for that NPC (
Config.speech_on_dialogue_open,speech_on_topic_select,speech_on_farewell) so the two cannot cut each other off. Those flags still drive every speech outside the panel, and withConfig.dialogue_speech.enabled = falsenothing changes at all data/generated/is described as build-time reference data shipped with the resource in README, INSTALLATION, CHANGELOG,shared/config.luaand the generated files' own headers — one consistent description everywhere: committed Lua tables, built offline by a developer-only step, never fetched or parsed at runtime- The developer-only build tooling was tidied and renamed — the fetch step is now
tools/fetch_refs.shand writes to the git-ignoredtools/refs/;tools/README.md,tools/generate.js, its error messages and the regenerate line in everydata/generated/*.luaheader all follow. Nothing loaded or executed changes —tools/is still absent fromfxmanifest.luaand server owners still never run it. The generated tables' data is unchanged; only their headers were rewritten, and a re-run of the generator is byte-identical.tools/is now documented as excluded from the release package rather than as something the buyer may delete
v1.11.0 — 2026-08-26
New
- Red-light posts (
red_light_districtlife-point preset) — night-only working girls on the kerb in Strawberry, Davis, Mission Row, Textile City, Cypress Flats, La Mesa and Banning, and a higher-class scenario uptown in West Vinewood, Rockford Hills, Vespucci Canals and Del Perro. They stand on the world's real bus-stop pavement points, not hand-typed coordinates - The prostitute hail lines finally fire. The dedicated scanner, the 14 authored lines, the per-category cooldown and the service flow have all shipped since v1.5.0 — what was missing was anyone to hail from, because the vanilla population almost never spawns a working girl. Mechanics only; no new adult content
Config.life_points.presets_enabled.red_light_districtturns the whole preset off in one line
Changed
Config.life_pointsnow ships ON. It was off by default since v1.9.0, which meant nothing spawned and the red-light preset above could never fire on a stock install.max_activeis unchanged at 12 — that hard ceiling is still the performance budget, so lower it to 6 (or setConfig.life_points.enabled = false) if your server is already heavy- *
furniture.enablednow gates only the four `furniture_` presets**, as its name and its own comment always claimed. It used to switch off every zone-form entry, which would have silently taken the red-light preset with it - Bus-stop waiters are day-only (
time_blockmorning/day/evening). They share the generated bus-stop points with the red-light entries, and in the zones that hold exactly one stop a waiter parked there in the evening blocked the kerb all night - A hailing ped is left alone by the scenario watchdog, so a working girl no longer turns away mid-line when the 15 s re-task tick lands inside the 8 s bubble
- The prostitute hail bubble uses the localised speaker name (
ambient.prostitute) instead of a hardcoded English "Working Girl", so the Hungarian pack reads correctly
v1.10.0 — 2026-08-26
New
- Provoking an NPC now has a physical answer chosen by their character — past the snap they either throw hands, bolt, or fall through to their archetype's own reaction (beg, curse and walk off, or square up), instead of the old silent walk-off.
Config.provoke_flee_chanceis the new roll (cowards 0.80, the brave and the armed 0.00), taken only when the fight roll does not win - The fight and flee outcomes now leave a record — trauma, fear, the persona reputation hit and the memory tag are applied on all three post-snap branches, not just the fall-through one
Changed
- NPCs are easier to push over the edge. Archetype provoke thresholds drop (coward 6 → 4, brave 4 → 3, everyone else 5 → 3, gang 3 → 2, cop unchanged at 3) and
Config.npc_default_provoke_thresholdfollows 5 → 3 - The two-jab floor and the first-provocation guard both still stand —
Config.npc_min_provoke_thresholdandConfig.npc_first_provoke_never_snapsare unchanged, so the first jab of an encounter always lands at least one step short of the snap and nobody swings on a single click. The extra ease comes from the lowered thresholds, not from removing the guard - More of them fight back —
Config.provoke_fight_chanceraised across the board (compliant 0.15 → 0.35, cunning 0.10 → 0.25, stoic 0.35 → 0.55, brave 0.65 → 0.85,default0.15 → 0.35) - A provoked cop warns before drawing — the fight branch routes the cop archetype through its own
arrest_warninginstead of straight todraw_and_fight - The dev menu opens with F12, not F7 (
Config.dev_menu_key) - A slimmer trust and composure readout in the dialogue header — fixed-width labels, 3px tracks and tighter rows, so the two bars stop competing with the NPC's name
v1.9.0 — 2026-08-26
New
- The city life layer — three modules, all shipping OFF —
Config.life_points,Config.scenario_groupsandConfig.world_ambience. No permanent per-frame threads: every loop runs at one of the resource's existing cadences (15 s hail tick, 30 s routines tick) - Life points (
data/life_points.lua,client/life_points.lua) — declarative "extras casting": 16 authored entries across five presets (Vespucci beach, downtown tourists, gang blocks, labor, night life) plus four that seat peds on the world's own furniture — ATMs, benches, parking meters, bus stops — read from the generated world-point tables, 20 in all. They are tagged with a category, so hail, perception, gossip and trauma pick them up like any engine ped; they are not personas and have no dialogue tree, persistence or blip - Life-point safety rails — hard
max_activeceiling, 80 m/140 m spawn-cull hysteresis, minimum gap to the player and to other peds, per-zone counts scaled byConfig.density_manager.by_zone, and four independent release paths (teleport jump, distance cull, proximity-point exit, death/respawn). Every scenario name is validated at boot against the generated scenario table and every model againstIsModelInCdimage/IsModelValid; anything that does not resolve is dropped with a console warning naming the entry.IsScenarioTypeEnabledis checked separately on every spawn attempt — a globally disabled type rotates the entry's pool instead of dropping it, and surfaces as thedisabled-typeskip counter in/intel_status - Life-point scenario re-task (
Config.life_points.retask_enabled, on by default) — life points are deliberately outside the static-persona set, so perception, witness reactions and the reaction layer clear their tasks when they turn a bystander to face you. The scan tick now checks whether a ped has fallen out of its scenario and puts it back, so an authored preset survives the first player interaction instead of leaving everyone standing. Counted asre-tasksin/intel_status - Persona seat warp (
Config.life_points.routine_seat_warp, off by default) — a static persona whose scheduled spot lands within a few metres of a real seat point sits on it instead of playing a standing animation beside it; wired into both the spawn path andRoutines_TrySoftRelocate - Dormant scenario groups (
Config.scenario_groups) — flips on the ambient content Rockstar ships switched off (the blimp, LSIA airliners, cinema queues, bank ATM crowds, the Ammu-Nation clerk, impound cops). Nothing spawns; the module only writes the switch. Names are validated against the generated group table andDoesScenarioGroupExist, matched case-insensitively, and each group's original state is recorded and restored on resource stop - World ambience (
Config.world_ambience) — ambient NPC car radios tuned to real stations and audible from outside (never your car, another player's car, a mission entity, a resource-created vehicle or a car carrying a persona; prior radio state recorded and restored, capped and faded in), plus ambient boat traffic and garbage-truck rounds re-asserted against engine resets data/generated/— committed, human-readable reference tables built at build time from reference data: scenario names with their kinds, 223 scenario group names, radio stations, driving styles, and world prop coordinates pre-bucketed by zone. Client-side only, and only the ones a module actually reads are listed infxmanifest.lua—driving_styles.luaand its table are staged for a later phase and are not loaded. Thetools/pipeline that produces them is developer-only — server owners never run it- New dev commands —
/intel_life_reload(re-validate and re-register life points without a full restart),/intel_scenario_groups,/intel_ambienceand/intel_ambience release. All gated onConfig.debug /intel_statusgrows two counter blocks — one per city life module,pcall-guarded so a disabled or absent module cannot break the command- New locale keys —
status.life_points_header,status.world_ambience_header(enandhushipped)
Changed
shared/config.luagrew to ~2,400 commented lines — the three new blocks sit next toConfig.density_manager,Config.disabled_scenario_typesandConfig.persona_blocking_area_radius, with every threshold, count, distance and cooldown exposed and annotated
Known trade-off
Config.disabled_scenario_typesstill ships with'WORLD_HUMAN_MUSICIAN'disabled (the "street musicians at the docks intel drop" fix). The Vespucci boardwalk-performer life point detects this at spawn and rotates to its other scenarios, so it runs in a degraded state until you remove that line — deliberately left as the operator's call- Releasing a touched vehicle radio always clears the loud flag: the game exposes no getter for it, so a car another resource had set loud comes back un-loud
v1.8.0 — 2026-08-26
New
- New locale keys —
nui.ends_chat,nui.tone_*(22 tone labels),dialogue.body_provoked_high/low,fx.snapped(enandhushipped)
Changed
- The picked reply stays visible — choosing a reply (mouse or 1–9) lights that row pink and recedes the rest while the answer round-trips, instead of swapping the whole list for a spinner
- The sentence leads every reply row — choice text now sits above the metadata chips, the generic
dialogue/topictone chip and the ever-present "Continue" chip are gone, and the row that ends the conversation is the one that gets a marker (nui.ends_chat); raw tone keys (introduce_self, …) render through the locale pack (nui.tone_*) instead of leaking underscores - Chat stream de-cluttered — the real-world wall-clock timestamp on every bubble is gone and the speaker label only renders when the voice changes, so an NPC's bubbles read as one grouped voice under the header
- Legibility pass, computed against WCAG — the recent-choices trail moves to full-strength tone colours at 11px (was ~2.8:1, now ≥5.5:1), disabled rows keep their lock-reason chip at full strength instead of dimming the whole row to 0.42, system messages move from 11px tracked mono to the display face at 12px muted, the 1–9 key numerals step up to 17px warm white, and the choice subtitle drops the italic triple de-emphasis
- Every remaining status merged top-right — the impatience warning moves from floating over the choices into the NPC header under the composure bar; the provoke escalation and mid-conversation trust-rank overlays no longer render screen-centre while the panel is open (the header trust bar and chat-stream notes carry them); money / item / trust-down toasts anchor to
Config.simpleasf.dialogue_toast_position(top-center) during dialogue; the built-in floating bubble no longer duplicates panel lines over the in-session NPC's head; the mood-icon rects pause while the panel is open - The header paints complete on open — composure and patience are seeded in the open payload instead of popping in ~300 ms later mid-entrance, and the witnesses + patience pills share one chip row so the header stays balanced; the witnesses pill only turns warning-orange when someone is actually watching
- Long choice lists announce themselves — rows past the fold dissolve into a scroll fade, rows 8+ join the entrance stagger, and choices/topics are keyboard-focusable (Enter activates, the focus ring finally renders)
- Reduced-motion players see trust deltas — the +/- popup shows statically for 1.5 s instead of living only inside the disabled float animation
- Spacing normalised onto the token scale — the ad-hoc 5/6/7/9/14/15 px gaps across the header, stream, topics and choices move to
--lr-space-* - Hungarian covered — the provoke path's hardcoded English (
Furious, body-language lines,They snapped!) routes through the locale packs (dialogue.body_provoked_*,fx.snapped,mood.*)
v1.7.0 — 2026-08-26
Changed
- The conversation status lives in the header — a teal composure bar under the trust bar and an hourglass patience chip beside the mood badge, refreshed live during the conversation; the floating bottom-centre notify card is now off by default (
Config.simpleasf.conversation_card = truebrings it back) - A more readable chat — taller message area, wider bubbles with looser line-height, a teal edge on NPC bubbles so the two voices separate at a glance, softer tracking on the small-caps labels
- Reply rows lost their echo — the intent chip no longer repeats the reply text when the two were identical; it falls back to the tone name
- New locale keys —
nui.composure,nui.patience(enandhushipped)
v1.6.0 — 2026-08-26
Changed
- One HUD surface instead of two — the top-left corner stack (location, witnesses, duration) is gone; the dialogue screen carries a single panel in the top-right
- Witnesses live in the NPC header — an amber eye chip under the mood badge, hidden while the count is unknown; location and time were already in the header's tag line, and the duration counter is retired
- A calmer header — flatter background wash, tighter spacing, smaller avatar without the pulsing dot, so the identity, mood, witness count and trust read at a glance
v1.5.0 — 2026-08-26
New
lastresort_chatintegration — while the chat resource is running, every floating NPC line (hails, gossip, dialogue replies, police chatter) renders through its 3D speech bubble, and the line lands in the chat feed as speaker: line on the overhearing client- Built-in fallback — with
lastresort_chatabsent the built-in bubble renderer takes over on the very next line, and the feed write speaks the stockchat:addMessageevent so the default chat resource understands it too; a player who turned speech bubbles off in the chat settings keeps that choice Config.integrations.chat— master toggle, separatebubbles/feedswitches, the bubble's chat mode (say/loud/gentle) and the feed colour- While the chat renders the bubble, its
speechBubblessettings govern lifetime and styling — the per-line durations and the gold speaker sub-line apply only to the built-in renderer shared/adapters/chat.lua— the whole integration is one pcall-wrapped adapter, probed per line, so a chat restart mid-session needs nothing from this resource
v1.4.0 — 2026-08-26
Changed
- The dialogue panel wears the Vice look — deep purple-black night ground, hot pink accent, the four-stop sunset gradient (pink → rose → orange → gold) on the panel's leading edge and display type
- Teal is the one cool accent — info and telemetry colours move off generic blue
- The trust meter fills gold, and every reply-tone colour is re-cast into the same palette
- Notify-card accents now match the panel, so both surfaces read as one product
nui/theme.cssdefaults re-documented — the token notes in the override sheet reflect the new palette- The NPC tag is a condensed uppercase eyebrow — display face at 11px with 0.18em tracking in the accent colour (was 13px muted body text), and the message-header tracking widens from 0.16em to 0.18em
- The docs say what is true: no escrow — the README no longer claims Tebex asset escrow; every file ships readable and editable, and the licensing section says so plainly
Removed
escrow_ignorefromfxmanifest.lua— inert on an un-escrowed install and it contradicted the docs- The Keymaster entitlement troubleshooting entry — that error cannot occur on an un-escrowed resource
v1.3.1 — 2026-08-12
Fixed
- A stuck cursor after closing the dialogue panel — the panel now always releases its own NUI focus on close, even when another suite resource has signed the shared
lrNuiOwnerstate key
v1.3.0 — 2026-08-11
Changed
- General improvements and internal cleanup
- Stability and consistency fixes across the dialogue panel and the config
- README and INSTALLATION brought back in line with what ships
Fixed
- Assorted minor bugs found in testing
v1.2.0 — 2026-07-26
New
- NPC errands — personas offer authored jobs from
data/jobs.lua, with a HUD tracker, map blip and/intel_jobs - Errands are server-validated — every step, and the run survives a restart; expiry or abandon costs trust
- Bounty board — broker personas take a stake-funded price on another player;
/intel_bountiesreads the shared board - Bounty payouts settle server-side on a confirmed kill, and standing NPC contracts settle off the rob/mug flow
- Trade pricing — shop prices scale with reputation tier (
Config.trade) - Haggling — a server-rolled attempt wins a session discount or insults the persona
- Routines build-out —
hiddenoff-shift blocks, in-place re-tasking, night alertness, a night mood skew, per-block arrival lines - New config and tables —
Config.npc_jobs,Config.bounty_board,Config.trade,Config.routines, thenpc_jobsandglobaltables, thebounty_brokerflag
Changed
- Purchases charge the quoted per-player price, not the flat catalog number; the menu shows the tier and any discount
- Persona proximity accepts every spot in that persona's schedule, so a routine can never strand an interaction
v1.1.0 — 2026-07-26
New
- Language packs —
locales/*.luaselected byConfig.locale, withhushipped fully translated - SQL persistence —
Config.persistence.backend = 'sql'moves memory, reputation, quests and bribes into oxmysql - Graceful degrade — a missing oxmysql falls back to KVP with one console line
nui/theme.css— open override sheet for the dialogue panellastresort_levelsbridge — quest completions and validated rep gains award bonus minutes; a silent no-op without it- Drug-use and explosion perception, a witness-report TTL, a default ambient voice and concurrent backup units
- New exports —
IntelPolice_HasWaitingPed(),IntelPolice_OwnsDispatch(ped),PersistenceAdapter.Backend()
Changed
- Every player-facing string moved into the locale packs — a pool left in config still overrides its locale key
- The dialogue panel carries no English in its markup — the label pack rides in the NUI open payload
- Persistence flushes on state change (debounced) instead of only on the 5-minute checkpoint
- The Discord webhook convar is read first, config second
shared/config.luais English throughout —napszak_bounds→time_of_day_bounds, both old names still honoured- Zero permanent per-frame threads, and 13 whole-pool ped/object walks became spatial queries
police_ped_hit.protect_nearby_pedsships disabled and restores every max-health value it borrows- NUI focus is arbitrated across the suite on the
lrNuiOwnerstate key lastresort_npc_chatis stopped automatically at boot in either start order
Fixed
- Surrender, the one-press apology, dispatch suppression and
IsInPoliceScenewere dead code — their locals were declared below their first read - Peacefully resolved 911 scenes leaked two cops and a cruiser, and the response-patrol handler was a no-op
- Quick-talk aiming read an async shape test on the frame it started
- Explosions were bound to a game event that never reaches Lua
- Entity ownership is acquired before tasking or deleting a networked ped
- Rob, mug and pickpocket payouts are validated against the real ped by network id and burned with a replicated state
- 3D text is clamped to the engine's 99-byte limit and glyph-mapped
- Personas with no
gta_voice_nameare audible, a witness can report more than once, and Pete's "off the record" topic is gated correctly
Removed
- Every external network request from the NUI — the Font Awesome link and the iconify runtime, replaced by a vendored Lucide font
- The per-frame head-IK thread during dialogue
- The unused "WIP" notify placeholder and Erika's inert gated demo interaction
v0.53.0 — 2026-07-23
Changed
- Production defaults restored —
Config.debugandConfig.verbose_server_logship asfalse - *The F7 dev menu,
/intel_devand every `/intel_test command** are off on a default install;setr lastresort_intel_npc:debug 1` turns them on - The debug convar override runs on both runtimes, so server gates and client tooling always agree
/intel_gangtestgoes through the shared dev-command wrapper like every other test command- NUI focus is released unconditionally on stop, so the cursor can never stay stuck
Fixed
setr lastresort_intel_npc:verbose_server_logwas documented but never read
v0.52.0 — 2026-07-23
New
- Framework bridges — Qbox, qb-core, ESX Legacy and standalone, auto-detected for framework, inventory, notify and target
- Built-in target fallback — look at an NPC and press E, with no target resource running
- Standalone mode — in-memory session ledger, item flows that succeed without a backend, license-keyed persistence
- News and street context —
data/news.luafeeds small talk, and rumour-mill gossip carries hop-decayed confidence lastresort_notifyintegration — mood toasts, conversation card, provoke meter, trust bars, wanted stars, dispatch cards, minigames- Facial expressions — archetype and persona baselines, session-mood faces, panicked and hostile overrides, ambient sweep
- Cast-aware witness reactions — gang backup, gang witnesses mocking civilians, cops warning or drawing, civilians fleeing
- Composure system — one 0–100 nervousness score per NPC, feeding dialogue, refusals and the point where they stop talking
- Zone density manager — per-zone ambient multipliers shaping the engine's own population, with scenario blocking around personas
- Discord webhook logging — batched, rate-limit-aware embeds with per-category webhooks and per-event toggles
- Server-side persistence — KVP snapshots of memory, reputation, quests and bribes, restored on join
- Police additions — sight-aware cop placement with fade-in, a wanted-level watcher, roaming cruise patrols
- More police controls — a configurable star ceiling, per-archetype car-hit reactions, a flee-the-scene warning phase, the one-press apology (B)
- Static persona roster grown from 1 to 10 — Maggie, Erika, Pete, Joe, Tony, Doc, Denny, Anna, Sergeant Mahoney, Candy
- Quality of life — quick-talk hotkey (G), NUI auto-scaling, dialogue drift grace, idle gossip, mood carry-over, ops metrics, vehicle NPC helpers
Changed
- Renamed
lrr_intel_npc→lastresort_intel_npc— exports, event prefix, callbacks, convars, KVP keys and keybinds all moved with it - Author → LastResort Development (intelnpc.com)
- Hard dependencies reduced to
ox_libonly — qbx_core, ox_target and ox_inventory are optional bridges now - Ambient spawning delegated to the game population — the resource only spawns static personas and set-pieces
- The police module ships disabled with an explicit alpha warning, toggleable live without a restart
- Persona shops default to a built-in server-validated menu — the ox_inventory shop UI is opt-in
- Config surface reorganized —
use_ox_target→target_enabled+bridge.target, and friends; the wanted cap is configurable - Reputation tuning — threaten −3, provoke −2, a new topic nudges +1 with an anti-farm guard, polite closes cap at Familiar
Removed
- The dynamic persona pool — the script-side crowd of random NPCs is replaced by the game's own population
- Hard dependencies on qbx_core, ox_target and ox_inventory
- The separate
lrr_wantedrequirement — its triggers are hosted in the police module; the relay events remain
v0.38 — Initial Tebex release (as lrr_intel_npc)
- Every NPC is a person — categories, archetypes, session moods, per-NPC memory including witnessed crimes
- Recursive dialogue trees in a chat-style NUI — typewriter lines, mood badge, live trust meter, conditional responses
- Six ambient categories routed by ped model — Civilian, Gang, Cop, Homeless, Prostitute, Default, each with its own refusal rate
- Seven personality archetypes — coward, compliant, brave, aggressive, cunning, stoic, cop — with tunable patience, thresholds and bribe prices
- Reputation on a −10..+10 scale across six tiers, moved by actions rather than chatter
- Social intelligence layer — witness reliability tiers, disguise scoring, NPC-to-NPC gossip with per-hop decay, alibi windows, trauma, informant leaks, bribes
- Police system (alpha) — car-hit reactions, the pre-call talk-down window, the bystander witness mechanic, a calmly responding RP unit, a seven-choice cop scene
- Police triggers — red-light and speeding detectors, cruise patrols, star-scaled bribes, server-validated cash flows
- Operator-first — ~1,300 lines of commented config, master toggles for every module, a dev menu and dev command suite
- Integration surface — client exports, public server events and validated server callbacks
- Full source, no obfuscation
Download an older version
- v1.63.0Latest
New — AI conversations through the optional
lastresort_intel_npc_aiadd-onThis resource still contacts no external service. When the separate add-on is running and has its owner's API key, the conversation panel becomes free talk and the add-on's model directs the NPC; without it nothing changes.
- Free-typed replies. A text field above the replies; any letter key starts typing, ENTER sends. AI reply suggestions sit on 1–3 with a suggested tag, the scripted topics stay under them (
Config.ai_bridge.keep_scripted_topics). A FREE TALK chip in the header, a teal typing cue while the NPC thinks, the NPC's name in the header once a random pedestrian introduces themselves - The NPC acts on the model's call: emotion to GTA facial idles and the mood badge, the delivery to a GTA speech context through the speech-synced panel (new tone
ai_goodbye→GENERIC_BYE), a gesture as a secondary upper-body animation, and the closing reaction — ends it, walks away cursing, curses, flips you off, flees, hands up, begs, calls the cops, attacks, draws, arrest warning — through the existing reaction set, with the NPC marked panicked or hostile before the dialogue hold is released so the reaction is not cleared
- Free-typed replies. A text field above the replies; any letter key starts typing, ENTER sends. AI reply suggestions sit on 1–3 with a suggested tag, the scripted topics stay under them (
- v1.62.1
Fixed — your saved standing is no longer announced as a fresh loss
Spawning in threw a trust toast at you for every persona you had a stored reputation with — "Maggie · Trust down (−10)" for something that happened days ago, and a screen-centre rank overlay for a saved positive. Nothing had changed; the restore was reporting itself.
- A restore is not a change.
client:persistRestorepushed every saved row throughReputation_Set, which sees the fresh table at 0, calls that a move and firesIntelFx_OnRepChange. The saved set now replaces the table throughReputation_Restoreinstead: same numbers, same clamp to −10..+10, no toast and no overlay. A move made in the session still announces itself exactly as before, and the panel's trust bar is untouched - Legacy
ambient_<uid>rows are dropped by the restore itself. They were handed toReputation_Set, which refuses an ambient key — so the prune pass that ran afterwards always found nothing to drop and reported nothing, while the refusal printed a yellow console warning on every single load. The rows are now counted and dropped where they arrive, and the line in the console says how many tests/reputation.test.lua— first harness for this module: bootsclient/reputation.luaandclient/persistence.luaagainst a stubbed engine
- A restore is not a change.
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.
