text
config/customer/08_integrations.lua = ชื่อ resource / export / option
config/functions/                 = Customer hook / override ที่ตั้งใจให้แก้
internal/functions/               = Default adapter logic ของระบบ

External Notification#

Toast ปกติของ nX Police สามารถเลือกใช้ Provider ภายในหรือ External Notify ได้จาก Config

1. เลือก Provider#

lua
Config.Notification = {
    Enabled = true,
    Provider = 'external',
    Duration = 4500,
    External = {
        Resource = 'my_notify',
        FallbackToInternal = true,
        TypeMap = {
            info = 'info',
            success = 'success',
            error = 'error',
            warning = 'warning'
        }
    }
}

Resource คือชื่อ resource แจ้งเตือนภายนอก หากการเชื่อมของคุณไม่ต้องระบุชื่อ resource สามารถปล่อยค่าว่างได้

2. เชื่อม Adapter จุดเดียว#

แก้เฉพาะ Functions.Client.ExternalNotify() ใน config/functions/client.lua ฟังก์ชันต้องคืน true เมื่อแสดงสำเร็จ หรือ false, reason เมื่อเรียกไม่ได้

lua
-- ตัวอย่าง Export
function Functions.Client.ExternalNotify(message, kind, duration, settings)
    exports['my_notify']:Notify(message, kind, duration)
    return true
end

-- ตัวอย่าง Event
-- function Functions.Client.ExternalNotify(message, kind, duration, settings)
--     TriggerEvent('my_notify:show', message, kind, duration)
--     return true
-- end

3. TypeMap#

nX Police ใช้ชนิดแจ้งเตือนมาตรฐาน info, success, error, warning หาก Notify ปลายทางใช้ชื่ออื่นให้เปลี่ยนเฉพาะค่าฝั่งขวา เช่น warning = 'warn' หรือ info = 'inform'

4. Fallback ที่แนะนำ#

ตั้ง FallbackToInternal = true เพื่อให้ Toast ภายในทำงานแทนเมื่อ resource ภายนอกหยุด, restart, export error หรือ Adapter คืน false ถ้าปิด fallback ข้อความนั้นจะไม่แสดงเมื่อ External Provider ล้มเหลว

อย่าทำให้ข้อความสำคัญหาย
Production แนะนำเปิด FallbackToInternal เสมอ โดยเฉพาะข้อความคดี เงิน Mailbox Jail และ permission ที่ผู้เล่นต้องเห็นผลลัพธ์ทันที

5. อะไรถูกส่งไป External Notify#

ส่งออกเฉพาะ Toast ปกติ เท่านั้น ส่วน Dispatch Card, Jail HUD, Police Command, Report Chat, Mailbox, Countdown และ overlay ที่มี layout เฉพาะยังใช้ UI ของ nX Police. สำหรับประกาศหน่วยงาน/ประกาศภายในใช้ Adapter แยกด้านล่าง.

6. ใช้ Notification จาก resource อื่น#

lua
-- Client
exports['nX_police']:Notify('บันทึกสำเร็จ', 'success', 4500)

-- Server
exports['nX_police']:NotifyPlayer(playerSource, 'คุณได้รับข้อความจากหน่วยงาน', 'info', 4500)

Public API ทั้งสองแบบใช้การตั้งค่า Notification Provider และ TypeMap ชุดเดียวกับ nX Police

External Department Announcement UI#

หากเซิร์ฟเวอร์มี UI ประกาศของตัวเอง สามารถแทน UI ประกาศของ nX Police ได้จาก config/functions/client.lua โดยไม่ต้องแก้ Core.

lua
Functions.Client.DepartmentAnnouncement = function(data)
    exports['my_announcement']:Show({
        title = data.title,
        message = data.message,
        duration = data.duration,
        type = data.kind
    })
    return true
end

return true หมายถึง Resource ภายนอกจัดการ UI แล้วและ nX Police จะไม่แสดง Announcement UI เดิมซ้ำ. ถ้า Adapter คืน false, reason หรือเกิด error ระบบจะ fallback กลับ UI ประกาศเดิมของ nX Police.

ข้อมูลที่ส่งให้ Adapter ได้แก่ kind, scope, audience, internal, title, message, duration, announcementId, caseId, suspectName, minutes และ charges. สำหรับประกาศภายใน audience ใช้ค่า all, on_duty หรือ off_duty.

lua
-- ตัวอย่าง Event
Functions.Client.DepartmentAnnouncement = function(data)
    TriggerEvent('my_announcement:show', data.title, data.message, data.duration, data)
    return true
end
UI ไม่ใช่ Authority
Adapter นี้เปลี่ยนเฉพาะหน้าตา/วิธีแสดงผลฝั่ง Client. สิทธิ์ส่งประกาศและการคัดกรองผู้รับประกาศภายในยังทำฝั่ง Server ของ nX Police; External UI ไม่ได้ทำให้ประชาชนหรือกลุ่มที่ไม่ได้รับสิทธิ์เห็นประกาศเพิ่ม

Duty Lifecycle Hooks#

สำหรับ resource ภายนอกที่ต้องทำงานเมื่อเจ้าหน้าที่ กดเข้าเวร/ออกเวรสำเร็จ ให้ override hook ที่ config/functions/ แทนการดัก esx:setJob ทั่วทั้งเซิร์ฟเวอร์.

lua
-- config/functions/client.lua
Functions.Client.OnDutyChanged = function(onDuty, data)
    if onDuty then
        TriggerEvent('my_policehud:setDuty', true)
    else
        TriggerEvent('my_policehud:setDuty', false)
    end
end

-- config/functions/server.lua
Functions.Server.OnDutyChanged = function(source, xPlayer, onDuty, data)
    TriggerEvent('my_resource:policeDuty', source, onDuty)
end

Hook ทั้ง Client และ Server จะถูกเรียกหลัง nX_police:toggleDuty ผ่านการตรวจสิทธิ์/ระยะ/คุมขัง/อุปกรณ์, เปลี่ยน Job และ Server verify สถานะสำเร็จแล้วเท่านั้น. เข้าเวรหนึ่งครั้งเรียกหนึ่งครั้ง และออกเวรหนึ่งครั้งเรียกหนึ่งครั้ง.

data มีสถานะประกอบ เช่น onDuty, previousOnDuty, trigger (ui/point), mode, job และข้อมูล loadout ที่เกี่ยวข้อง.

ไม่ใช่ passive state listener
OnDutyChanged ไม่ทำงานตอนเข้าเซิร์ฟเวอร์, reconnect, restart nX_police, passive job/state sync, เปิด/refresh UI หรือเพียงเรียก IsOnDuty(). จึงเหมาะกับ radio, voice, HUD, uniform/presentation และ external integration ที่ต้องเริ่ม/หยุดตามการกดเวรจริง

External UI + Cuff Whitelist#

UI ภายนอกที่เปิดด้วยปุ่มให้ส่งชื่อปุ่มหรือ FiveM Control ID ที่ใช้เปิด UI เข้า nX Police ก่อนเปิดผ่าน CanOpenUi API นี้ตรวจทั้ง Config.UI.AllowWhileDead และ Cuff Whitelist ในจุดเดียว

lua
-- ตัวอย่าง: phone ใช้ F1
local openKey = 'F1'
local uiOk = exports['nX_police']:CanOpenUi('phone', openKey)
if not uiOk then return end

local active = exports['nX_police']:SetCuffUiActive('phone', true, openKey)
if not active then return end
SetNuiFocus(true, true)
-- เปิด UI ตาม API ของ resource คุณ

-- ตอนปิด UI
SetNuiFocus(false, false)
exports['nX_police']:SetCuffUiActive('phone', false)

เมื่อ Config.UI.AllowWhileDead=false ผู้เล่นที่ตายจะไม่ได้รับอนุญาตให้เปิด UI ที่เชื่อม policy นี้ ส่วนตอนถูกล็อคมือ ปุ่มหรือ Control ID ที่ใช้เปิด UI ต้องอยู่ใน Config.Cuff.Whitelist หากเปิด UI จาก export/event โดยไม่มีปุ่ม ให้ resource ต้นทางกำหนดและส่ง input ที่ใช้เป็นสิทธิ์ให้ชัดเจน

อย่าตั้ง active ค้าง
Resource ภายนอกต้องเรียก SetCuffUiActive(name, false) ทุกครั้งเมื่อปิด UI เพื่อให้สถานะตรงกับหน้าจอจริง

Inventory#

ค่า Default ใช้ Driver='auto' และรองรับ nc_inventory ที่ตรวจพบ หากใช้ Inventory อื่นให้ตั้ง Driver='generic', ชื่อ Resource/Export ให้ตรง และแก้เฉพาะ Adapter ที่เปิดให้เชื่อม

lua
Config.Integrations.Inventory = {
    Enabled = true,
    Driver = 'generic',
    Resource = 'my_inventory',
    SearchExport = 'OpenPlayerInventory',
    AddExport = 'AddItem',
    RemoveExport = 'RemoveItem',
    UseExportForMutations = true
}

NC Driver - Manual fxmanifest integration#

nX Police ใช้ Compatibility Layer สำหรับ nc_inventory และ nc_vehiclestorage โดยลูกค้าติดตั้งเองใน fxmanifest.lua ของ NC เท่านั้น ไม่ต้องแก้ Customer Config ของ NC และไม่มีโปรแกรมติดตั้งอัตโนมัติ

แก้เฉพาะ fxmanifest.lua
เพิ่มเฉพาะบรรทัด @nX_police/compat ตามตำแหน่งด้านล่าง ห้าม copy โค้ด adapter ไปแปะใน Config ของ NC และห้ามย้ายลำดับ hook เพราะ Config ต้องถูกโหลดก่อน adapter

nc_inventory — เพิ่ม 2 บรรทัด#

lua
-- nc_inventory/fxmanifest.lua
server_scripts {
    '@oxmysql/lib/MySQL.lua',
    'config/config.database.lua',
    'config/functions/config.framework.server.lua',
    'config/functions/config.functions.server.lua',
    '@nX_police/compat/nc_inventory/server.lua',
    'server/main.lua'
}

client_scripts {
    'config/config.locale.lua',
    'config/config.rarity.lua',
    'config/config.images.lua',
    'config/config.descriptions.lua',
    'config/functions/config.functions.client.lua',
    '@nX_police/compat/nc_inventory/compat.lua',
    'client/main.lua',
}

nc_vehiclestorage — เพิ่ม 2 บรรทัด#

lua
-- nc_vehiclestorage/fxmanifest.lua
shared_scripts {
    -- config/storage entries ของ NC ตามเดิม
    'config/config.extensions.lua',
    '@nX_police/compat/nc_vehiclestorage/storage.lua'
}

server_scripts {
    '@oxmysql/lib/MySQL.lua',
    'config/config.database.lua',
    'config/functions/config.framework.server.lua',
    'config/functions/config.functions.server.lua',
    '@nX_police/compat/nc_vehiclestorage/server.lua',
    'server/main.lua'
}

หลังแก้ให้ตรวจว่าทั้ง 4 path มีอย่างละหนึ่งครั้ง แล้ว restart resource ที่เกี่ยวข้องและรัน nxpolice_doctor

หลัง NC Update
ถ้าอัปเดต NC แล้ว fxmanifest.lua ถูกเขียนทับ ให้เพิ่มบรรทัด @nX_police/compat ทั้ง 4 จุดกลับตามคู่มือนี้อีกครั้ง
Migration จาก Installer รุ่นเก่า
ถ้าเคยใช้ Installer รุ่นที่แทรกโค้ดลง Config ให้ค้นหา marker NEXUSLAB NXPOLICE BEGIN/END ใน nc_inventory และ nc_vehiclestorage แล้วลบเฉพาะ block ที่อยู่ระหว่าง marker ของ NEXUSLAB ให้ครบ ก่อนใช้ fxmanifest integration แบบใหม่

nc_inventory 1.8.7b+#

Built-in driver ของ nX Police รองรับ Search, Police Safe, account config และ secondary-inventory action ผ่าน Compatibility Hook ที่ลูกค้าเพิ่มใน fxmanifest.lua ตามคู่มือด้านบน

Server adapter อ่าน Config.Accounts และ Config.AccountsActionList จาก nc_inventory จริง เพื่อให้ account เช่น money, cash, black_money, dirty_money ใช้ policy ของเซิร์ฟเวอร์ลูกค้าโดยไม่ hardcode ใน nX Police

Cuff Whitelist กับ Inventory
ถ้า nc_inventory ใช้ keybinding = T และต้องการให้เปิดขณะถูกใส่กุญแจมือ ให้ใส่ 'T' ใน Config.Cuff.Whitelist เท่านั้น ตาม policy ของเซิร์ฟเวอร์ และให้ resource ภายนอกใช้ CanOpenUi / SetCuffUiActive

สำหรับ Generic Inventory สามารถเปิด UseExportForMutations=true และ override Functions.Server.InventoryMutation(), SafeSnapshot(), SafeMutation() หรือ UpdateWeaponAmmo() ให้ตรง signature ของ inventory ที่ใช้จริง

nc_vehiclestorage - Vehicle Storage#

Vehicle Storage ยังคงเป็นเจ้าของข้อมูล trunk ตามปกติ ส่วน nX Police ทำ server authorization/seizure redirect ผ่าน update-safe hook โดยตำรวจต้องผ่าน policy ฝั่ง server ก่อนเสมอ

server.cfg
ensure nX_police
ensure nc_inventory
ensure nc_vehiclestorage
การใช้งานผลลัพธ์
เจ้าของรถหยิบ Item / Weapon / เงินจากรถตัวเองทำงานตาม nc_vehiclestorage ปกติ ของเข้ากระเป๋าเจ้าของรถ
ตำรวจหยิบ Item จากรถคนอื่นต้องเป็น police ที่กำลังเข้าเวร แล้วส่งของกลางเข้า Police Safe
ตำรวจหยิบ Weapon จากรถคนอื่นต้องเป็น police ที่กำลังเข้าเวร แล้วส่งอาวุธของกลางเข้า Police Safe พร้อมข้อมูลที่ระบบรองรับ
ตำรวจหยิบ Money / Account จากรถคนอื่นชื่อ Account และสิทธิ์ search อ่านจาก nc_inventory จริง; รายการที่ NC อนุญาตจะถูกส่งเข้าบัญชีหน่วยงาน
ตำรวจออกเวร / ผู้เล่นทั่วไปเปิดท้ายรถของคนอื่นผ่านสิทธิ์ตำรวจไม่ได้
รถตัวเองไม่ใช่ของกลาง
ตำรวจที่เปิดรถของตัวเองจะใช้พฤติกรรม NC ปกติ ของจะเข้ากระเป๋าตำรวจ ไม่ถูกส่งเข้า Police Safe
ไม่ต้องแก้ NC Core เพื่อข้ามสิทธิ์
ให้ติดตั้งเฉพาะ Compatibility Layer ตามตัวอย่าง การเปิด/ยึดของจากรถคนอื่นต้องผ่าน nX Police ซึ่งบังคับกติกา fixed ฝั่ง Server ว่าต้องเป็น police ที่กำลังเข้าเวร; ไม่มี VehicleTrunkSeizure config หรือ Search Ability แยกสำหรับ path นี้

ถ้าการส่งของกลางเข้าหน่วยงานไม่สำเร็จ ระบบจะใช้เส้นทางคืนรายการตามสถานะที่ตรวจสอบได้เพื่อลดความเสี่ยงของหาย และแจ้งผู้ใช้งานเมื่อรายการไม่สมบูรณ์

Return value ของ Adapter สำหรับ Inventory อื่น#

ถ้าใช้ Generic Inventory และเปิด UseExportForMutations=true Export ปลายทางต้องคืนผลสำเร็จให้ชัดเจนตามรูปแบบด้านล่าง ค่าอื่นจะถือว่ารายการไม่สำเร็จ

lua
-- รูปแบบ success ที่ยอมรับ
return true
-- หรือ
return { success = true }
return { ok = true }
return { changed = true }

จุด Adapter หลักฝั่ง Server คือ Functions.Server.InventoryMutation(), SafeSnapshot(), SafeMutation() และ UpdateWeaponAmmo() ถ้า signature ของ Inventory ต่างจากตัวอย่าง ให้แก้เฉพาะ Adapter จุดนี้ให้ตรงกับ API ของ Inventory

Adapter Boundaries อื่น#

  • Functions.Server.RequestScreenshot() — ปรับให้ตรงกับ screenshot resource ที่ใช้
  • Functions.Server.CallPlaypass() — ปรับให้ตรงกับ Playpass ที่เซิร์ฟเวอร์ใช้
  • Functions.Client.OpenSearchInventory() / OpenPoliceSafe() — ปรับวิธีเปิด Inventory/Search/Safe เมื่อ API ต่างจากค่าเริ่มต้น
  • Functions.Client.RunQuickIntegration() — จุดเชื่อม Action ของ Quick Menu กับ event/export/command ของ resource อื่น
ชื่อ Event/Export ไม่ใช่ Permission
Quick Menu สามารถเรียก client event, server event, export หรือ command ได้ แต่ชื่อเหล่านี้ไม่ใช่ระบบป้องกัน Resource ปลายทางต้องตรวจ job, duty, rank/ability, target, distance และจำนวนที่ Server ของตัวเองทุกครั้ง

ยึดของเข้าตู้หน่วยงาน#

เมื่อ Inventory รองรับ Search hooks สามารถให้ของที่ยึดเข้า Agency Safe โดยตรง:

lua
exports['nX_police']:ShouldRouteSearchSeizure(officerSource, targetSource, itemName, itemCount, itemType, searchType)

exports['nX_police']:DepositSearchSeizure(officerSource, targetSource, itemName, itemCount, itemType, searchType)

บันทึกหลักฐานหลัง Remove สำเร็จ#

lua
exports['nX_police']:RecordSeizedEvidence(
    officerSource, suspectSource, itemName, amount, itemType, itemLabel, suspectName
)
เรียกหลังยึดสำเร็จเท่านั้น
RecordSeizedEvidence มีหน้าที่บันทึกหลักฐาน ไม่ควรเรียกก่อน Inventory ยืนยันการ Remove เพราะอาจทำให้ข้อมูลหลักฐานไม่ตรงของจริง

Screenshot / Safe / Playpass#

  • Integrations.Screenshot — ตั้ง Enabled, Resource และ Export สำหรับการจับรูป
  • Integrations.Safe — ตั้ง Resource และชื่อ Export ที่ใช้เปิด/อัปเดต Secondary Inventory
  • Integrations.Playpass — ตั้ง Enabled, Resource และชื่อตารางที่ระบบ Playpass ใช้

Profile Avatar#

Config.Integrations.ProfileAvatar เลือก Source='discord' หรือ 'steam', ขนาด Discord avatar และรูปแบบ Steam avatar ได้

Credential ให้ตั้งฝั่ง Server เท่านั้น ตัวอย่าง server.cfg:

server.cfg
set nX_police_discord_bot_token "YOUR_BOT_TOKEN"
set nX_police_steam_web_api_key "YOUR_STEAM_WEB_API_KEY"
Secret ต้องอยู่ฝั่ง Server
อย่าใส่ Discord Bot Token หรือ Steam Web API Key ใน config/customer, config/data, config/locales หรือ Client code

Optional Resources#

  • screenshot-basic — ใช้แนบ Screenshot ใน Discord Log เมื่อเปิด Event ที่รองรับ
  • Playpass / Profile Avatar — เปิดใช้ตาม Integration ที่เซิร์ฟเวอร์มีจริง