Skip to content

Commit 92f7627

Browse files
committed
Merge branch 'chore/stamp-content-version-1.10.0'
2 parents 083b707 + c800c96 commit 92f7627

5 files changed

Lines changed: 334 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,66 @@ Do not hand-edit `plugin.json` `version`, the README badge, or the `**Version:**
143143
| `mcp-server/data/natives_rdr3.json` | Local RDR3 native lookup index |
144144
| `mcp-server/data/events.json` | Indexed canonical CFX event names and payloads |
145145
| `mcp-server/data/docs_index.json` | Indexed `docs.fivem.net` snippets for offline search |
146+
147+
# context-mode — MANDATORY routing rules
148+
149+
You have context-mode MCP tools available. These rules are NOT optional — they protect your context window from flooding. A single unrouted command can dump 56 KB into context and waste the entire session.
150+
151+
## BLOCKED commands — do NOT attempt these
152+
153+
### curl / wget — BLOCKED
154+
Any Bash command containing `curl` or `wget` is intercepted and replaced with an error message. Do NOT retry.
155+
Instead use:
156+
- `ctx_fetch_and_index(url, source)` to fetch and index web pages
157+
- `ctx_execute(language: "javascript", code: "const r = await fetch(...)")` to run HTTP calls in sandbox
158+
159+
### Inline HTTP — BLOCKED
160+
Any Bash command containing `fetch('http`, `requests.get(`, `requests.post(`, `http.get(`, or `http.request(` is intercepted and replaced with an error message. Do NOT retry with Bash.
161+
Instead use:
162+
- `ctx_execute(language, code)` to run HTTP calls in sandbox — only stdout enters context
163+
164+
### WebFetch — BLOCKED
165+
WebFetch calls are denied entirely. The URL is extracted and you are told to use `ctx_fetch_and_index` instead.
166+
Instead use:
167+
- `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` to query the indexed content
168+
169+
## REDIRECTED tools — use sandbox equivalents
170+
171+
### Bash (>20 lines output)
172+
Bash is ONLY for: `git`, `mkdir`, `rm`, `mv`, `cd`, `ls`, `npm install`, `pip install`, and other short-output commands.
173+
For everything else, use:
174+
- `ctx_batch_execute(commands, queries)` — run multiple commands + search in ONE call
175+
- `ctx_execute(language: "shell", code: "...")` — run in sandbox, only stdout enters context
176+
177+
### Read (for analysis)
178+
If you are reading a file to **Edit** it → Read is correct (Edit needs content in context).
179+
If you are reading to **analyze, explore, or summarize** → use `ctx_execute_file(path, language, code)` instead. Only your printed summary enters context. The raw file content stays in the sandbox.
180+
181+
### Grep (large results)
182+
Grep results can flood context. Use `ctx_execute(language: "shell", code: "grep ...")` to run searches in sandbox. Only your printed summary enters context.
183+
184+
## Tool selection hierarchy
185+
186+
1. **GATHER**: `ctx_batch_execute(commands, queries)` — Primary tool. Runs all commands, auto-indexes output, returns search results. ONE call replaces 30+ individual calls.
187+
2. **FOLLOW-UP**: `ctx_search(queries: ["q1", "q2", ...])` — Query indexed content. Pass ALL questions as array in ONE call.
188+
3. **PROCESSING**: `ctx_execute(language, code)` | `ctx_execute_file(path, language, code)` — Sandbox execution. Only stdout enters context.
189+
4. **WEB**: `ctx_fetch_and_index(url, source)` then `ctx_search(queries)` — Fetch, chunk, index, query. Raw HTML never enters context.
190+
5. **INDEX**: `ctx_index(content, source)` — Store content in FTS5 knowledge base for later search.
191+
192+
## Subagent routing
193+
194+
When spawning subagents (Agent/Task tool), the routing block is automatically injected into their prompt. Bash-type subagents are upgraded to general-purpose so they have access to MCP tools. You do NOT need to manually instruct subagents about context-mode.
195+
196+
## Output constraints
197+
198+
- Keep responses under 500 words.
199+
- Write artifacts (code, configs, PRDs) to FILES — never return them as inline text. Return only: file path + 1-line description.
200+
- When indexing content, use descriptive source labels so others can `ctx_search(source: "label")` later.
201+
202+
## ctx commands
203+
204+
| Command | Action |
205+
|---------|--------|
206+
| `ctx stats` | Call the `ctx_stats` MCP tool and display the full output verbatim |
207+
| `ctx doctor` | Call the `ctx_doctor` MCP tool, run the returned shell command, display as checklist |
208+
| `ctx upgrade` | Call the `ctx_upgrade` MCP tool, run the returned shell command, display as checklist |

drug-sell/client/main.lua

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
local Framework, FW = nil, nil
2+
3+
local function resolveFramework()
4+
local choice = Config.Framework
5+
if choice == 'esx' or (choice == 'auto' and GetResourceState('es_extended') == 'started') then
6+
Framework = 'esx'
7+
FW = exports['es_extended']:getSharedObject()
8+
elseif choice == 'qb' or (choice == 'auto' and GetResourceState('qb-core') == 'started') then
9+
Framework = 'qb'
10+
FW = exports['qb-core']:GetCoreObject()
11+
else
12+
Framework = 'standalone'
13+
end
14+
end
15+
16+
local function notify(msg, kind)
17+
if Framework == 'esx' then
18+
FW.ShowNotification(msg)
19+
elseif Framework == 'qb' then
20+
FW.Functions.Notify(msg, kind or 'primary')
21+
else
22+
BeginTextCommandThefeedPost('STRING')
23+
AddTextComponentSubstringPlayerName(msg)
24+
EndTextCommandThefeedPostTicker(false, true)
25+
end
26+
end
27+
28+
-- Returns the nearest pedestrian (non-player, alive) within Config.SellDistance.
29+
local function getNearestPed()
30+
local playerPed = PlayerPedId()
31+
local coords = GetEntityCoords(playerPed)
32+
local handle, ped = FindFirstPed()
33+
local success, nearest, nearestDist = true, nil, Config.SellDistance
34+
35+
repeat
36+
if DoesEntityExist(ped)
37+
and not IsPedAPlayer(ped)
38+
and not IsPedDeadOrDying(ped, true)
39+
and not IsEntityDead(ped) then
40+
local dist = #(coords - GetEntityCoords(ped))
41+
if dist < nearestDist then
42+
nearest = ped
43+
nearestDist = dist
44+
end
45+
end
46+
success, ped = FindNextPed(handle)
47+
until not success
48+
49+
EndFindPed(handle)
50+
return nearest
51+
end
52+
53+
-- Pick a random drug the player can attempt to sell. The server re-validates inventory.
54+
local function pickDrug()
55+
return Config.Drugs[math.random(1, #Config.Drugs)]
56+
end
57+
58+
local lastSale = 0
59+
60+
local function attemptSale()
61+
if Config.RequireOnFoot and IsPedInAnyVehicle(PlayerPedId(), false) then
62+
return
63+
end
64+
65+
local now = GetGameTimer()
66+
if now - lastSale < Config.Cooldown * 1000 then
67+
notify('You need to lay low for a moment.', 'error')
68+
return
69+
end
70+
71+
local ped = getNearestPed()
72+
if not ped then
73+
notify('No one nearby to sell to.', 'error')
74+
return
75+
end
76+
77+
lastSale = now
78+
79+
local drug = pickDrug()
80+
local roll = math.random()
81+
local pedNet = PedToNet(ped)
82+
83+
if roll <= Config.Chances.accept then
84+
notify(('Selling %s...'):format(drug.label), 'primary')
85+
TaskTurnPedToFaceEntity(ped, PlayerPedId(), 1500)
86+
Wait(1200)
87+
TriggerServerEvent('drug-sell:server:sell', drug.item)
88+
elseif roll <= Config.Chances.accept + Config.Chances.decline then
89+
notify('They waved you off.', 'error')
90+
TaskSmartFleePed(ped, PlayerPedId(), 60.0, -1, false, false)
91+
SetPedKeepTask(ped, true)
92+
else
93+
notify('Wrong customer. They are calling the cops!', 'error')
94+
TaskSmartFleePed(ped, PlayerPedId(), 100.0, -1, false, false)
95+
SetPedKeepTask(ped, true)
96+
local coords = GetEntityCoords(PlayerPedId())
97+
TriggerServerEvent(Config.PoliceAlertEvent, { x = coords.x, y = coords.y, z = coords.z })
98+
end
99+
end
100+
101+
CreateThread(function()
102+
resolveFramework()
103+
while true do
104+
Wait(0)
105+
if IsControlJustReleased(0, Config.SellKey) then
106+
attemptSale()
107+
end
108+
end
109+
end)
110+
111+
RegisterNetEvent('drug-sell:client:saleResult', function(ok, label, amount)
112+
if ok then
113+
notify(('Sold for $%d.'):format(amount), 'success')
114+
else
115+
notify(('You have no %s to sell.'):format(label or 'drugs'), 'error')
116+
end
117+
end)

drug-sell/config.lua

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
Config = {}
2+
3+
-- 'auto' resolves ESX or QBCore at runtime. Force with 'esx' / 'qb' / 'standalone'.
4+
Config.Framework = 'auto'
5+
6+
-- Key to offer a sale to the nearest valid ped. 38 = E
7+
Config.SellKey = 38
8+
9+
-- Max distance (meters) a ped can be from the player to sell to.
10+
Config.SellDistance = 2.5
11+
12+
-- Seconds the player must wait between sale attempts.
13+
Config.Cooldown = 6
14+
15+
-- Selling is blocked while in a vehicle when true.
16+
Config.RequireOnFoot = true
17+
18+
-- Per-sale outcome chances (must sum to <= 1.0; remainder = ped ignores you).
19+
Config.Chances = {
20+
accept = 0.55, -- buys the drug
21+
decline = 0.30, -- walks off, no sale
22+
callCops = 0.15, -- refuses and alerts police
23+
}
24+
25+
-- Police alert: dispatched as a server event you can hook into your CAD/dispatch.
26+
Config.PoliceAlertEvent = 'drug-sell:server:policeAlert'
27+
28+
-- Define your custom drugs here. `item` must match the inventory item name.
29+
Config.Drugs = {
30+
{
31+
label = 'Weed Baggie',
32+
item = 'weed_baggie',
33+
minPrice = 40,
34+
maxPrice = 80,
35+
amount = 1, -- units removed per successful sale
36+
},
37+
{
38+
label = 'Cocaine',
39+
item = 'cocaine_bag',
40+
minPrice = 120,
41+
maxPrice = 220,
42+
amount = 1,
43+
},
44+
{
45+
label = 'Meth',
46+
item = 'meth_pouch',
47+
minPrice = 90,
48+
maxPrice = 160,
49+
amount = 1,
50+
},
51+
}
52+
53+
-- Where cash lands on a sale: 'cash' (money) or 'bank'.
54+
Config.PayAccount = 'cash'

drug-sell/fxmanifest.lua

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
fx_version 'cerulean'
2+
games { 'gta5' }
3+
4+
name 'drug-sell'
5+
author 'TMHSDigital'
6+
description 'Sell custom drugs to nearby pedestrians (ESX/QBCore bridge, standalone fallback)'
7+
version '1.0.0'
8+
9+
lua54 true
10+
11+
shared_script 'config.lua'
12+
13+
client_script 'client/main.lua'
14+
server_script 'server/main.lua'

drug-sell/server/main.lua

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
local Framework, FW = nil, nil
2+
3+
local function resolveFramework()
4+
local choice = Config.Framework
5+
if choice == 'esx' or (choice == 'auto' and GetResourceState('es_extended') == 'started') then
6+
Framework = 'esx'
7+
FW = exports['es_extended']:getSharedObject()
8+
elseif choice == 'qb' or (choice == 'auto' and GetResourceState('qb-core') == 'started') then
9+
Framework = 'qb'
10+
FW = exports['qb-core']:GetCoreObject()
11+
else
12+
Framework = 'standalone'
13+
end
14+
end
15+
resolveFramework()
16+
17+
local function findDrug(item)
18+
for _, drug in ipairs(Config.Drugs) do
19+
if drug.item == item then
20+
return drug
21+
end
22+
end
23+
return nil
24+
end
25+
26+
-- Returns true if the player held >= amount and the item was removed.
27+
local function removeItem(src, item, amount)
28+
if Framework == 'esx' then
29+
local xPlayer = FW.GetPlayerFromId(src)
30+
if not xPlayer then return false end
31+
local inv = xPlayer.getInventoryItem(item)
32+
if not inv or inv.count < amount then return false end
33+
xPlayer.removeInventoryItem(item, amount)
34+
return true
35+
elseif Framework == 'qb' then
36+
local Player = FW.Functions.GetPlayer(src)
37+
if not Player then return false end
38+
local has = Player.Functions.GetItemByName(item)
39+
if not has or has.amount < amount then return false end
40+
return Player.Functions.RemoveItem(item, amount)
41+
else
42+
-- Standalone: trust nothing, but no inventory backend exists. Treat as held.
43+
return true
44+
end
45+
end
46+
47+
local function addMoney(src, amount)
48+
if Framework == 'esx' then
49+
local xPlayer = FW.GetPlayerFromId(src)
50+
if not xPlayer then return end
51+
if Config.PayAccount == 'bank' then
52+
xPlayer.addAccountMoney('bank', amount)
53+
else
54+
xPlayer.addMoney(amount)
55+
end
56+
elseif Framework == 'qb' then
57+
local Player = FW.Functions.GetPlayer(src)
58+
if not Player then return end
59+
Player.Functions.AddMoney(Config.PayAccount == 'bank' and 'bank' or 'cash', amount)
60+
end
61+
end
62+
63+
RegisterNetEvent('drug-sell:server:sell', function(item)
64+
local src = source
65+
if not src or src <= 0 then return end
66+
67+
local drug = findDrug(item)
68+
if not drug then return end
69+
70+
if not removeItem(src, drug.item, drug.amount) then
71+
TriggerClientEvent('drug-sell:client:saleResult', src, false, drug.label, 0)
72+
return
73+
end
74+
75+
local price = math.random(drug.minPrice, drug.maxPrice) * drug.amount
76+
addMoney(src, price)
77+
TriggerClientEvent('drug-sell:client:saleResult', src, true, drug.label, price)
78+
end)
79+
80+
RegisterNetEvent(Config.PoliceAlertEvent, function(coords)
81+
local src = source
82+
if not src or src <= 0 then return end
83+
if type(coords) ~= 'table' or not coords.x then return end
84+
-- Hook your dispatch/CAD here. Example: notify on-duty police.
85+
print(('[drug-sell] Police alert from %d at %.1f, %.1f, %.1f'):format(src, coords.x, coords.y, coords.z))
86+
end)

0 commit comments

Comments
 (0)