Appearance
🚀 AFU.VehicleShop
ระบบร้านขายยานพาหนะแบบ 3D Marker + NPC + Blip ผู้เล่นเดินเข้าใกล้ร้านแล้วกดปุ่มเปิดร้าน เลือกดูรถแบบ 360° ใน UI ปรับสีได้ ทดลองขับ (Test Drive) ได้ก่อนซื้อ รองรับหลายตำแหน่งต่อร้าน จำกัดอาชีพที่เข้าร้านได้ จำกัดจำนวนรถที่ขายได้ผ่าน Database และให้ Admin แจกรถผ่านคำสั่ง
ข้อมูลสคริปต์
- Framework: ESX เท่านั้น (
es_extended) - Dependencies:
es_extended— บังคับoxmysql— บังคับAFU.Toastify— แนะนำ (ใช้แสดง Hint "กด E เพื่อเข้าสู่ร้านค้ารถ")unknow_notify— แนะนำ (ใช้แสดง Notification แจ้งเตือนต่างๆ)AFU.VehicleKeys— แนะนำ (ซิงค์กุญแจให้ผู้เล่นหลังซื้อ/รับรถ)AFU.Garage— แนะนำ (รีเฟรชรถในโรงจอดรถหลังซื้อ/รับรถ)nc_discordlogs— ไม่บังคับ (ส่งบันทึกการซื้อขายรถไป Discord)
- เวอร์ชัน: 2.8.5 ·
fx_version cerulean·lua54
⚠️ ต้องใช้ License ที่ถูกต้อง
สคริปต์นี้มีระบบตรวจสอบ License ผู้ซื้อต้องมี Token/License ที่ถูกต้อง จากผู้พัฒนา หากไม่มี License ที่ถูกต้อง Resource จะไม่ทำงาน
ซื้อ Token ได้ที่ awayfromus.store
🛠️ Installation
1. Download & Placement
นำ Resource ไปวางในโฟลเดอร์ resources/ ของเซิร์ฟเวอร์:
text
resources/
└── AFU.VehicleShop/
├── Config/ ← ✅ โฟลเดอร์เดียวที่คุณสามารถแก้ไขได้
│ ├── settings.config.lua (ตั้งค่าหลัก: ปุ่ม, ฟอนต์, ภาษี, Test Drive, Marker)
│ ├── commands.config.lua (ชื่อคำสั่ง Admin)
│ ├── secure.config.lua (License / Admin List / Event Framework)
│ ├── queries.config.lua (SQL Queries + Discord Log)
│ ├── notify.config.lua (ข้อความ Notification)
│ ├── functions.config.lua (Hooks ฝั่ง Client)
│ ├── suspects.config.lua (Hooks ตรวจพบพฤติกรรมผิดปกติ)
│ └── Shop/ (ไฟล์ร้าน — 1 ไฟล์ = 1 ร้าน)
├── nui/ ← 🔒 UI (ห้ามแก้ไข)
├── Source/ ← 🔒 ไฟล์หลักเข้ารหัสลับ (ห้ามแก้ไข)
├── sql/ ← ไฟล์ SQL สำหรับ Import
└── fxmanifest.lua2. Dependencies
ตรวจสอบและติดตั้ง Resource ที่จำเป็นก่อน Start:
es_extended— บังคับ (ต้อง start ก่อน)oxmysql— บังคับ (ต้อง start ก่อน)AFU.Toastify— แนะนำ (ถ้าไม่มี จะไม่แสดง Hint กด E แต่ร้านยังทำงานได้)unknow_notify— แนะนำ (ถ้าไม่มี จะไม่แสดง Notification แจ้งเตือน แต่ร้านยังทำงานได้)AFU.VehicleKeys— แนะนำ (ถ้าไม่มี ผู้เล่นอาจต้องกดรับกุญแจเองหลังซื้อรถ)AFU.Garage— แนะนำ (ถ้าไม่มี รถที่ซื้อมาอาจไม่ขึ้นในโรงจอดรถทันที)nc_discordlogs— ไม่บังคับ (ถ้าไม่มี จะไม่ส่ง Log การซื้อรถไป Discord)
3. Database
Import ไฟล์ SQL ทั้ง 2 ไฟล์ในโฟลเดอร์ sql/:
ไฟล์ที่ 1: sql/initial.sql — ตาราง owned_vehicles (ตารางรถที่ผู้เล่นเป็นเจ้าของ)
sql
CREATE TABLE IF NOT EXISTS `owned_vehicles` (
`owner` varchar(22) COLLATE utf8mb4_bin NOT NULL,
`state` int(1) NOT NULL DEFAULT 0,
`plate` varchar(7) COLLATE utf8mb4_bin NOT NULL,
`vehicle` longtext COLLATE utf8mb4_bin NOT NULL DEFAULT '{}',
`label` varchar(50) COLLATE utf8mb4_bin NOT NULL DEFAULT '',
`type` varchar(20) COLLATE utf8mb4_bin NOT NULL DEFAULT 'car',
`job` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`plate`) USING BTREE,
KEY `owner` (`owner`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=COMPACT;หมายเหตุ
- หากเซิร์ฟเวอร์คุณ มีตาราง
owned_vehiclesอยู่แล้ว (จาก AFU.Garage หรือระบบรถเดิม) ให้ข้ามไฟล์นี้ — ระบบจะใช้ตารางเดิม - คอลัมน์
plateเป็นvarchar(7)— ป้ายทะเบียนที่ระบบสร้างมีรูปแบบABC 123(7 ตัวอักษรพอดี) หากใช้ระบบป้ายทะเบียนยาวกว่านี้ต้องขยายคอลัมน์เอง
ไฟล์ที่ 2: sql/vehicle.pools.sql — ตาราง afu.vehicle_pools (จำกัดจำนวนรถที่ขายได้)
sql
CREATE TABLE IF NOT EXISTS `afu.vehicle_pools` (
`label` varchar(255) NOT NULL,
`available_total` int(11) NOT NULL DEFAULT 10
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='Limit sell vehicle database.';วิธีใช้ตารางจำกัดจำนวนรถ
- ใส่แถวในตารางโดยให้
labelตรงตัว กับlabelของรถในไฟล์ Config ร้าน (เช่นT20,OFFICER C) available_total= จำนวนคันที่ยังขายได้ — ทุกครั้งที่ขายได้ 1 คัน ระบบจะหักลง 1 และอัปเดต Database ตาม- เมื่อจำนวนลงถึง
0รถรุ่นนั้นจะ หมดสต็อก (ผู้เล่นเปิดร้านแล้วจะปิดร้านอัตโนมัติ) - รถรุ่นใด ไม่ มีแถวในตาราง = ขายได้ไม่จำกัด
- รีเซ็ตสต็อก: แก้ค่า
available_totalใน Database ใหม่แล้ว Restart เซิร์ฟเวอร์
4. server.cfg
เพิ่มบรรทัดต่อไปนี้ใน server.cfg:
cfg
# ลำดับการ start ที่ถูกต้อง
ensure oxmysql
ensure es_extended
ensure AFU.Toastify
ensure unknow_notify
ensure AFU.VehicleKeys
ensure AFU.Garage
ensure AFU.VehicleShopข้อควรจำ
- ลำดับการ start สำคัญมาก:
oxmysqlและes_extendedต้อง start ก่อนAFU.VehicleShopเสมอ - Resource นี้ถูกเข้ารหัสโค้ดไว้ (Obfuscated) — แก้ไขได้เฉพาะไฟล์ในโฟลเดอร์
Config/เท่านั้น ห้ามแก้ไขไฟล์ในSource/หรือnui/เด็ดขาด
⚙️ Configuration
คำเตือน
โปรดแก้ไขเฉพาะค่าในไฟล์ Config/ เท่านั้น ห้ามแก้ไขไฟล์ระบบหลัก (Source/, nui/) เนื่องจากมีการเข้ารหัสโค้ดไว้ การแก้ไขไฟล์หลักอาจทำให้ License สิ้นผลหรือสคริปต์ไม่ทำงาน
ไฟล์ Config/settings.config.lua — ตั้งค่าหลัก
| ตัวแปร (Option) | ชนิดข้อมูล (Type) | ค่าเริ่มต้น (Default) | คำอธิบาย (Description) |
|---|---|---|---|
Settings.keyOpen | number | 38 | Control ID ของปุ่มเปิดร้าน (ค่าเริ่มต้น 38 = ปุ่ม E) เปลี่ยนเป็น ID ปุ่มอื่นได้ตามต้องการ |
Settings.fontName | string | 'Kanit' | ชื่อฟอนต์ที่ใช้วาด 3D Text เหนือร้านและชื่อ Blip (ฟอนต์ Kanit ถูกฝังมาใน Resource แล้ว) |
Settings.Tax.Bank | number | 50 | อัตราภาษี (%) ที่บวกเพิ่มเมื่อจ่ายเงินผ่านธนาคาร — ค่าเริ่มต้น 50% สูงมาก ปรับตามต้องการ |
Settings.DebugPotentialBlackScreen | boolean | true | แสดงข้อความ debug ใน console ระหว่างรอ Teleport (ตอน Test Drive) ปิดเป็น false ได้ |
Settings.TestDrive.Time | number | 15000 | ระยะเวลา Test Drive (มิลลิวินาที) — 15000 = 15 วินาที |
Settings.TestDrive.BucketsExclude | table<number> | {[0..3] = true} | Routing Buckets ที่ระบบห้ามใช้สำหรับ Test Drive (ค่าเริ่มต้นปิด Bucket 0–3 ไว้) |
Settings.TestDrive.Locations | table<vector4> | พิกัด 1 จุด | จุด Teleport สำหรับ Test Drive รถ/เครื่องบิน — ใส่ได้หลายจุด ระบบจะสุ่มเลือก 1 จุด |
Settings.TestDrive.BoatLocations | table<vector4> | พิกัด 1 จุด | จุด Teleport สำหรับ Test Drive เรือ — แยกจากรถ ใส่ได้หลายจุด |
Settings.Marker.sprite | number | 2 | Sprite ID ของ Marker 3D เหนือร้าน (0–44) |
Settings.Marker.color | table | {r=56, g=158, b=166, a=255} | สีของ Marker (ค่า 0–255) |
Settings.Marker.size | number | 0.3 | ขนาดของ Marker |
Settings.ExportTextUI.PressE | function | — | Hook แสดง Hint "กด E" เมื่อเข้าใกล้ร้าน (ค่าเริ่มต้นใช้ AFU.Toastify) — แก้ไขข้อความ/รูปแบบได้ |
Settings.ExportTextUI.HidePressE | function | — | Hook ซ่อน Hint เมื่อเดินออก — ค่าเริ่มต้นเรียก exports['AFU.Toastify']:hide() |
หมายเหตุเรื่องภาษี
ภาษีจะบวกเพิ่มบนราคารถเมื่อเลือกจ่ายด้วยเงินธนาคาร เช่น ราคา 1,000,000 + ภาษี 50% = จ่ายจริง 1,500,000 (UI จะแสดงอัตราภาษีที่ปุ่มจ่าย) ปรับค่า Settings.Tax.Bank ตามนโยบายเซิร์ฟเวอร์
ไฟล์ Config/Shop/*.lua — กำหนดร้านขายรถ
ร้านแต่ละแห่งเป็นไฟล์ .lua แยกกันในโฟลเดอร์ Config/Shop/ (มีตัวอย่างมาให้ 5 ไฟล์: ร้านทั่วไป, ร้านเรือ, ร้านตำรวจ, ร้านบินตำรวจ, ร้านบินพยาบาล)
สร้างร้านใหม่: คัดลอกไฟล์ร้านใดร้านหนึ่งแล้วแก้ค่า หรือสร้างไฟล์ใหม่ตามโครงสร้างด้านล่าง (ระบบจะโหลดทุกไฟล์ในโฟลเดอร์ Shop/ อัตโนมัติ)
| ตัวแปร (Option) | ชนิดข้อมูล (Type) | บังคับ | คำอธิบาย (Description) |
|---|---|---|---|
shopName | string | ✅ | ชื่อร้าน (แสดงบน 3D Text, Blip และใน UI) — ชื่อซ้ำไม่ได้ |
ped | number (model hash) | ❌ | โมเดล NPC ตัวขายรถ เช่น `a_m_m_prolhost_01` ถ้าไม่ต้องการ NPC ให้ลบหรือ comment บรรทัดนี้ทิ้ง |
animation | table | ✅ (ถ้ามี NPC) | Animation ของ NPC: { dict = 'ชื่อ animation dictionary', name = 'ชื่อ animation' } |
blip | table | ✅ | ตั้งค่า Blip: hide (ใส่ true เพื่อซ่อน Blip), sprite (ID รูป), scale (ขนาด), color (สี) |
coords | vector4 | ✅ | ตำแหน่งร้าน (จุดที่ผู้เล่นเดินเข้าใกล้เพื่อเปิดร้าน, จุด spawn NPC/Blip/Marker) — w = heading |
showCoords | vector4 | ✅ | ตำแหน่งที่วางรถโชว์ 360° ในร้าน — ต้องเป็นจุดที่รถจอดได้และกล้องไม่ชนสิ่งกีดขวาง |
boughtCoords | table<vector4> | ✅ | ตำแหน่งจุดส่งมอบรถหลังซื้อเสร็จ — ใส่ได้หลายจุด ระบบจะเลือกจุดที่ว่าง (รัศมี 18 เมตร) |
requireJob | table<string> | ✅ | รายชื่ออาชีพ (job name ใน ESX) ที่อนุญาตให้เข้าร้านได้ ถ้าเว้นว่าง {} = ทุกคนเข้าได้ |
vehicles | table | ✅ | รายการรถ — ดูโครงสร้างด้านล่าง |
โครงสร้าง vehicles (รายการรถ)
text
vehicles = {
{
category = 'ชื่อหมวดหมู่', -- ชื่อหมวดที่แสดงใน UI
cars = {
{
model = `adder`, -- model hash ของรถ (ห้ามผิด ไม่อย่างนั้นร้านจะโหลดรถไม่ได้)
label = 'Adder', -- ชื่อรถที่แสดงใน UI + ใช้จับคู่กับตาราง afu.vehicle_pools
type = 'car', -- 'car' | 'boat' | 'helicopter' | 'plane' (ถ้าไม่ใส่ ระบบจะตรวจจาก model เอง)
price = 1000000, -- ราคารถ (ถ้าจ่ายด้วยธนาคาร จะบวกภาษี Settings.Tax.Bank)
weight = 0, -- น้ำหนัก (เก็บข้อมูลไว้)
job = 'police', -- (ไม่บังคับ) อาชีพที่รถคันนี้ผูกกับ — บันทึกเข้า Database
job_grade = 5, -- (ไม่บังคับ) ขั้นขั้นต่ำของอาชีพ — บันทึกเข้า Database
defaultColor = { -- สีเริ่มต้นของรถ (ปรับได้อีกใน UI)
primary = { multiplyColor = 5, r = 0, g = 0, b = 0 },
secondary = { multiplyColor = 5, r = 0, g = 0, b = 0 }
}
},
...
}
},
...
}โครงสร้างตัวอย่าง (ไฟล์ร้าน)
lua
local vehicleShop = {
shopName = 'Car',
ped = `a_m_m_prolhost_01`,
animation = {
dict = 'amb@world_human_clipboard@male@idle_a',
name = 'idle_a'
},
blip = {
hide = false,
sprite = 227,
scale = 1.0,
color = 0
},
coords = vector4(-56.62, -1099.12, 26.42, 28.74),
showCoords = vector4(-45.55, -1097.78, 25.33, 100.48),
boughtCoords = {
vector4(-44.97, -1077.26, 26.61, 70.86),
vector4(-31.04, -1081.12, 26.54, 69.45),
},
requireJob = {
-- เว้นว่าง = ทุกคนเข้าได้ / ใส่ 'police' = เฉพาะตำรวจ
},
vehicles = {
{
category = 'Starter',
cars = {
{
model = `weevil`,
label = 'Weevil',
type = 'car',
price = 4500,
weight = 0,
defaultColor = {
primary = { multiplyColor = 5, r = 0, g = 0, b = 0 },
secondary = { multiplyColor = 5, r = 0, g = 0, b = 0 }
}
}
}
}
}
}
table.insert(ServerConfig.VehiclesShop, vehicleShop)ไฟล์ Config/secure.config.lua — Security & Admin
| ตัวแปร (Option) | ชนิดข้อมูล (Type) | ค่าเริ่มต้น (Default) | คำอธิบาย (Description) |
|---|---|---|---|
ServerConfig.getEvents.frameworkEvent | string | 'esx:getSharedObject' | Event สำหรับดึง Object ของ Framework — ไม่ต้องแก้ ยกเว้นใช้ระบบ Framework แบบ custom ที่ไม่ใช้ Event นี้ |
ServerConfig.UseAdminPlateByPrefix | boolean | false | ถ้าเป็น true รถที่ Admin แจกผ่านคำสั่งจะใช้ป้ายทะเบียนขึ้นต้นด้วยค่าใน AdminList (เช่น HEX 123) |
ServerConfig.AdminList | table<string> | {} | รายชื่อ Steam Hex Identifier ของ Admin ที่ใช้คำสั่งแจกรถได้ — รูปแบบ: ['steam:1100001358fb54d'] = 'HEX' (ค่าหลัง = คือคำนำหน้าป้ายทะเบียน) |
วิธีหา Steam Hex Identifier
ดูได้จาก console ของเซิร์ฟเวอร์เมื่อผู้เล่นเข้าเกม หรือใช้คำสั่ง /id ในเกม — ใช้ค่า steam:xxxxxxxxxxxxxxx เป็น key ใน AdminList
ไฟล์ Config/commands.config.lua — คำสั่ง
| ตัวแปร (Option) | ชนิดข้อมูล (Type) | ค่าเริ่มต้น (Default) | คำอธิบาย (Description) |
|---|---|---|---|
Commands.giveCar | string | 'gc' | ชื่อคำสั่ง Admin สำหรับแจกรถ (ไม่ใส่ / นำหน้า) |
ไฟล์ Config/notify.config.lua — ปรับข้อความ Notification
แก้ไขข้อความในฟังก์ชันเหล่านี้ได้ (ใช้ exports.unknow_notify:add):
| ฟังก์ชัน | สถานการณ์ |
|---|---|
Notify.CheckingArea(second) | ระหว่างรอหาจุดส่งมอบรถที่ว่างหลังซื้อรถ |
Notify.NotEnoughMoney(type) | เงินสด/เงินธนาคารไม่พอ (type = 'bank' หรือ 'money') |
Notify.RestricJob() | อาชีพไม่ได้รับอนุญาตให้เข้าร้านนี้ |
Notify.CantLoadModel() | โหลดโมเดลรถไม่ได้ (ชื่อ model ผิด) |
ฟังก์ชัน Customization (Hooks)
Config/functions.config.lua (ฝั่ง Client)
| ฟังก์ชัน | เมื่อไหร่ | หมายเหตุ |
|---|---|---|
Functions.boughtVehicle(plate, vehicle, vehicleProperties) | หลังซื้อรถสำเร็จและรถ spawn แล้ว | plate = ป้ายทะเบียน, vehicle = EntityID ของรถ, vehicleProperties = ข้อมูลรถ — จุดสำหรับต่อ Log/Notification พิเศษ |
Functions.createdPed(shopName, pedEntity, requireJobs) | หลัง spawn NPC ร้านแล้ว | pedEntity = EntityID ของ NPC — ปรับแต่ง NPC ได้ (เช่น ตั้งชื่อ, ให้คุย) |
Functions.openShop(shopName, requireJobs) | ตอนผู้เล่นเปิดร้าน | ว่างไว้ให้ custom เช่น เล่นเสียง, log |
Functions.closedShop(shopName, requireJobs) | ตอนผู้เล่นออกจากร้าน | ว่างไว้ให้ custom |
Functions.RecievedCehicleFromCommand(plate) | ตอนผู้เล่นได้รับรถจากคำสั่ง Admin | plate = ป้ายทะเบียนรถที่ได้รับ |
Config/suspects.config.lua (ฝั่ง Server — Anti-Cheat)
ฟังก์ชันเหล่านี้ทำงานเมื่อระบบตรวจสอบฝั่ง Server ตรวจพบพฤติกรรมผิดปกติ (ค่าเริ่มต้น: ว่างเปล่า):
| ฟังก์ชัน | สถานการณ์ |
|---|---|
Suspects.changeModelVehicle(xPlayer, playerId, vehicleProperties, price) | ผู้เล่นพยายามซื้อโดยเปลี่ยน model ของรถ |
Suspects.buyingNotMatchPrice(xPlayer, playerId, vehicleProperties, price) | ผู้เล่นส่งราคาซื้อที่ไม่ตรงกับราคาใน Config |
Suspects.buyingUnknownShop(xPlayer, playerId, vehicleProperties, price) | ผู้เล่นยิง event ซื้อจากร้านที่ไม่รู้จัก |
Suspects.buyingUnknownCategory(xPlayer, playerId, vehicleProperties, price) | ผู้เล่นยิง event ซื้อหมวดหมู่/รถที่ไม่รู้จัก |
Suspects.notHaveValidateData(xPlayer, playerId) | ผู้เล่นส่งข้อมูลซื้อรถที่ไม่ครบถ้วน |
TIP
แนะนำต่อฟังก์ชันใน suspects.config.lua กับระบบ Log/Discord ของเซิร์ฟเวอร์เพื่อจับผู้ดัดแปลงสคริปต์
รายละเอียดการตั้งค่าเพิ่มเติม (Detailed Setup)
📍 Locations / Coordinates
- ระบุตำแหน่งด้วย
vector4(x, y, z, w)—wคือกางของทิศทาง (heading) - แต่ละร้านมี 3 กลุ่มพิกัด ที่ต้องตั้ง:
coords— จุดร้าน: จุดที่ผู้เล่นเข้าใกล้เพื่อเปิดร้าน + จุด spawn NPC/Blip/MarkershowCoords— จุดวางรถโชว์ 360° (ต้องเป็นพื้นที่โล่งที่รถจอดได้)boughtCoords— จุดส่งมอบรถหลังซื้อ (ใส่ได้หลายจุดเพื่อป้องกันจุดติด)
- เมื่อผู้เล่นเข้าใกล้ตำแหน่งร้าน ภายใน 18 เมตร จะเห็น 3D Text + Marker / เข้าใกล้ ภายใน 3 เมตร จะแสดง Hint กดปุ่ม
- ใช้
/setcoordsหรือcopycoordsในเกมเพื่อหาพิกัด
🚗 Items & Rewards (รายการรถ)
model— model hash ของรถในเกม (เช่น`adder`) — ต้องถูกต้อง ถ้าผิด รถรุ่นนั้นจะไม่แสดงในร้าน (console จะแจ้งcan't load model of ...)label— ชื่อรถที่แสดงใน UI — ใช้จับคู่กับตารางafu.vehicle_poolsเพื่อกำหนดจำนวนขายได้type— ประเภทยานพาหนะ:'car','boat','helicopter','plane'— ถ้าไม่ใส่ ระบบจะตรวจจาก model เองprice— ราคารถ (ถ้าจ่ายด้วยธนาคาร จะบวกภาษีSettings.Tax.Bank)job/job_grade— (ไม่บังคับ) ผูกอาชีพกับรถคันนั้น (เช่น รถตำรวจ) — บันทึกเข้า DatabasedefaultColor— สีเริ่มต้นของรถ:multiplyColor= รหัสสีมาตรฐานของ GTA (ดูจาก palette สี) หรือใช้ค่าr/g/bสำหรับสี custom- จำกัดจำนวนขาย: ใส่แถวในตาราง
afu.vehicle_poolsโดยให้labelตรงกับlabelของรถ
🧪 Test Drive (ทดลองขับ)
- ผู้เล่นกดปุ่มทดลองขับใน UI → ระบบจะ Teleport ผู้เล่น + รถไปที่จุดใน
Settings.TestDrive.Locations(หรือBoatLocationsสำหรับเรือ) - ระยะเวลา:
Settings.TestDrive.Time(ค่าเริ่มต้น 15 วินาที) — หมดเวลาหรือผู้เล่นกดออก ระบบจะพาผู้เล่นกลับจุดเดิมของร้าน - ระหว่าง Test Drive ผู้เล่นจะถูกแยก Routing Bucket (ผู้เล่นคนอื่นไม่เห็น) — Bucket ที่ห้ามใช้กำหนดใน
Settings.TestDrive.BucketsExclude - ตั้งจุด Test Drive ให้เป็นพื้นที่โล่ง (ถนน/ลาน) และอยู่ไม่ไกลจากแผนที่มากเกินไป
💰 เงิน & อาชีพ
- ผู้เล่นเลือกจ่ายด้วย เงินสด (
money) หรือ ธนาคาร (bank) ใน UI — จ่ายผ่านธนาคารจะบวกภาษีSettings.Tax.Bank requireJob: ใส่ job name (ไม่ใช่ label) ตามที่ ESX กำหนด เช่น'ambulance','police'— ถ้าใส่ผิดผู้เล่นจะเข้าร้านไม่ได้
💬 Commands & Permissions
Player Commands
ไม่มี — ผู้เล่นใช้ระบบ proximity:
- เดินเข้าใกล้ตำแหน่งร้าน (18 เมตร) → เห็น 3D Text + Marker
- เข้าใกล้ 3 เมตร → แสดง Hint กดปุ่ม
- กดปุ่ม E (
Settings.keyOpen) → เปิด UI ร้านรถ - เลือกหมวด/รุ่น → ดู 360° → ปรับสี → ทดลองขับ (ถ้าต้องการ) → ซื้อ
- หลังซื้อ → รถ spawn ที่จุด
boughtCoordsที่ว่าง ผู้เล่นถูกส่งเข้ารถอัตโนมัติ
Admin Commands
/gc <playerId> <vehicleModel> [job]— แจกรถให้ผู้เล่น (Permission: ต้องมี Steam Hex อยู่ในServerConfig.AdminList)playerId— ID ของผู้เล่นเป้าหมาย (เช่น1)vehicleModel— ชื่อ model ของรถ (เช่นadder)job— (ไม่บังคับ) อาชีพที่ผูกกับรถ (เช่นmechanic)- ตัวอย่าง:
/gc 1 adder mechanic= ให้ผู้เล่น ID 1 รับรถ Adder ในชื่ออาชีพ mechanic - รถที่แจกจะถูกบันทึกเข้า Database เช่นกัน (ราคาบันทึกเป็น
command) และผู้เล่นจะได้รับรถทันที
🔌 Public Exports & Events
Exports (ฝั่ง Client)
lua
-- สร้างป้ายทะเบียนสุ่มที่ไม่ซ้ำ (รูปแบบ "ABC 123") — ใช้กับระบบอื่นที่ต้องการป้ายทะเบียน
local plate = exports['AFU.VehicleShop']:generatePlate()
-- ตรวจสอบว่าผู้เล่นอยู่ในร้านรถหรือไม่ (return boolean)
local isOnShop = exports['AFU.VehicleShop']:isOnVehicleShop()Exports (ฝั่ง Server)
lua
-- สร้างป้ายทะเบียนสุ่มที่ไม่ซ้ำกับรถทั้งหมดในเซิร์ฟเวอร์
local plate = exports['AFU.VehicleShop']:generatePlate()Events ที่ระบบเรียกใช้ (สำหรับ Resource ที่ทำงานร่วมกัน)
| Event / Export | ฝั่ง | เมื่อไหร่ |
|---|---|---|
exports['AFU.VehicleKeys']:syncKey(plate) | Server | หลังซื้อ/แจกรถสำเร็จ — ซิงค์กุญแจรถให้ผู้เล่น (ถ้ามี AFU.VehicleKeys) |
exports['AFU.Garage']:fetchVehicles(playerId) | Server | หลังซื้อ/แจกรถสำเร็จ — รีเฟรชรายการรถในโรงจอดรถ (ถ้ามี AFU.Garage) |
exports.nc_discordlogs:Discord(...) | Server | หลังซื้อรถสำเร็จ — ส่ง Log ไป Discord webhook ชื่อ vehicle_shop (ต้องกำหนด webhook ชื่อ vehicle_shop ใน Config ของ nc_discordlogs) |
Hooks ฝั่ง Client (ปรับแต่งใน Config/)
ดูตาราง Hooks ในหัวข้อ ฟังก์ชัน Customization — จุดต่อสำหรับ Log, เสียง, Notification และระบบตรวจจับการโกง
❓ Troubleshooting
สคริปต์ไม่ทำงาน / ร้านไม่ขึ้น
- ตรวจสอบว่า
oxmysqlและes_extendedstart สำเร็จก่อน (ดู console) - ตรวจสอบว่า License/Token ที่ถูกต้อง (console จะแจ้งปัญหา License)
- ตรวจสอบว่าไฟล์ร้านอยู่ในโฟลเดอร์
Config/Shop/และจบด้วยtable.insert(ServerConfig.VehiclesShop, vehicleShop) - Restart เซิร์ฟเวอร์หลังแก้ Config
รถบางรุ่นไม่แสดงในร้าน
- Console จะแจ้ง
can't load model of <label>— ตรวจสอบว่าmodelใน Config ถูกต้อง (เช่น`weevil`ไม่ใช่'weevil') - ตรวจสอบว่า model นั้นมีอยู่ในเกม (ไม่ถูก DLC/patch ตัดออก)
กด E แล้วร้านไม่เปิด
- ตรวจสอบ
Settings.keyOpen— ค่า38คือปุ่ม E ถ้าถูกใช้กับ Interaction อื่น อาจต้องเปลี่ยนปุ่ม - ตรวจสอบว่าผู้เล่น ไม่ได้อยู่ในรถ และ ไม่ตาย
- ตรวจสอบฟิลด์
requireJob— ถ้าผู้เล่นไม่ใช่อาชีพที่ได้รับอนุญาต จะถูกแจ้งเตือนแทน - ตรวจสอบว่า
AFU.Toastifystart แล้ว (ถ้าไม่มีจะไม่มี Hint แต่กด E ได้ตามปกติ)
ซื้อรถแล้วไม่ได้รับรถ / รถไม่ขึ้นใน Garage
- ตรวจสอบว่า import ตาราง
owned_vehiclesแล้ว (หรือมีตารางเดิมอยู่) - ตรวจสอบว่า
AFU.Garagestart แล้ว (ถ้าใช้ระบบ Garage ของ AFU) - ตรวจสอบ
boughtCoords— ต้องเป็นจุดที่ว่างจริง (รัศมี 18 เมตร) ถ้าจุดติดหมด ระบบจะรอจนกว่าจะว่าง - ดู console ฝั่ง Server — ถ้ามีข้อความจาก
Suspectsแสดงว่าระบบตรวจพบความผิดปกติ
รถหมดสต็อก / เปิดร้านแล้วปิดเอง
- ระบบอ่านจำนวนขายได้จากตาราง
afu.vehicle_pools— ถ้าavailable_totalของรถรุ่นนั้นเป็น0ร้านจะปิดเองเมื่อผู้เล่นเลือกซื้อ - รีเซ็ตสต็อก: อัปเดตค่า
available_totalใน Database แล้ว Restart เซิร์ฟเวอร์
Test Drive ไม่ทำงาน / Teleport ไม่ถึง
- ตรวจสอบพิกัดใน
Settings.TestDrive.Locations/BoatLocations— ต้องเป็นจุดที่รถ/เรืออยู่ได้จริง - Console จะแสดง
wating... to test drive coordsถ้าผู้เล่นไม่ถูก teleport — ตรวจว่าพิกัดถูกต้อง - ตรวจสอบว่าไม่มีการใช้ Routing Bucket 0–3 ของระบบอื่น (ค่า
Settings.TestDrive.BucketsExclude)
ป้ายทะเบียนซ้ำ / Error เกี่ยวกับ plate
- ตรวจสอบว่าคอลัมน์
plateในตารางowned_vehiclesเป็นvarchar(7)หรือใหญ่กว่า (ป้ายที่ระบบสร้างยาว 7 ตัวอักษร) - ระบบจะสุ่มป้ายใหม่จนกว่าจะไม่ซ้ำกับรถทั้งหมดในเซิร์ฟเวอร์ — ถ้า Database มีป้ายซ้ำกับค่าเริ่มต้น อาจต้องล้างข้อมูลซ้ำออก
Hint กด E ไม่แสดง
- Hint ใช้
AFU.Toastify— ตรวจสอบว่า Resource นี้ start แล้ว - ถ้าไม่ต้องการ Hint สามารถปรับฟังก์ชัน
Settings.ExportTextUI.PressE/HidePressEในConfig/settings.config.luaเป็นระบบอื่นได้