Appearance
🚀 AFU.Shop
ระบบร้านค้าแบบ 3D Marker + NPC + Blip ผู้เล่นเดินเข้าใกล้ร้านแล้วกดปุ่มเปิดร้าน ซื้อของผ่าน UI ได้หลายหมวดหมู่ (สูงสุด 8 หมวด) รองรับหลายตำแหน่งต่อร้าน จำกัดอาชีพที่เข้าร้านได้ และเลือกได้ว่าร้านนั้นใช้เงินประเภทใดได้บ้าง
ข้อมูลสคริปต์
- Framework: ESX เท่านั้น (
es_extended) - Dependencies:
es_extended— บังคับoxmysql— บังคับAFU.Toastify— แนะนำ (ใช้แสดง Hint "กด E เพื่อเข้าสู่...")unknow_notify— แนะนำ (ใช้แสดง Notification แจ้งเตือนต่างๆ)
- เวอร์ชัน: 2.3 ·
fx_version cerulean·lua54
⚠️ ต้องใช้ License Token
สคริปต์นี้มีระบบตรวจสอบ License ผู้ซื้อต้องมี Token ที่ถูกต้อง และตั้งค่าใน server.cfg ก่อนเสมอ หากไม่มี Token ที่ถูกต้อง Resource จะไม่ทำงาน
ซื้อ Token ได้ที่ awayfromus.dev
🛠️ Installation
1. Download & Placement
นำ Resource ไปวางในโฟลเดอร์ resources/ ของเซิร์ฟเวอร์:
text
resources/
└── AFU.Shop/
├── Config/ ← ✅ โฟลเดอร์เดียวท่ีคุณสามารถแก้ไขได้
│ ├── settings.config.lua (ตั้งค่าหลัก: ปุ่ม, ภาษี, ฟอนต์)
│ ├── notify.config.lua (ข้อความ Notification)
│ ├── client.functions.config.lua (Hooks ฝั่ง Client)
│ ├── server.functions.config.lua (Hooks ฝั่ง Server / Log)
│ ├── suspects.config.lua (Hooks ตรวจพบพฤติกรรมผิดปกติ)
│ └── Market/ (ไฟล์ร้านค้า — 1 ไฟล์ = 1 ร้าน)
├── Interface/ ← 🔒 UI (ห้ามแก้ไข)
├── Source/ ← 🔒 ไฟล์หลักรหัสลับ (ห้ามแก้ไข)
└── fxmanifest.lua2. Dependencies
ตรวจสอบและติดตั้ง Resource ที่จำเป็นก่อน Start:
es_extended— บังคับ (ต้อง start ก่อน)oxmysql— บังคับ (ต้อง start ก่อน)AFU.Toastify— แนะนำ (ถ้าไม่มี จะไม่แสดง Hint กด E แต่ร้านยังทำงานได้)unknow_notify— แนะนำ (ถ้าไม่มี จะไม่แสดง Notification แจ้งเตือน แต่ร้านยังทำงานได้)
3. Database
ไม่ต้อง Import SQL
Resource นี้ ไม่มีไฟล์ SQL ให้ import และไม่ได้สร้างตารางเอง — รายการสินค้าทั้งหมดทำงานผ่าน Config
แต่คุณต้อง ลงทะเบียน Item ในระบบ Inventory ของ ESX เอง ให้ครบทุก name ที่ใช้ใน Config (เช่น bandage, armor, WEAPON_PISTOL ฯลฯ) ไม่เช่นนั้นซื้อของแล้วจะไม่ได้รับไอเทม
4. server.cfg
เพิ่มบรรทัดต่อไปนี้ใน server.cfg:
cfg
# License Token (จำเป็น) — ซื้อได้ที่ awayfromus.dev
set afu_license "YOUR_TOKEN_HERE"
# ลำดับการ start ที่ถูกต้อง
ensure oxmysql
ensure es_extended
ensure AFU.Toastify
ensure unknow_notify
ensure AFU.Shopข้อควรจำ
- ลำดับการ start สำคัญมาก:
oxmysqlและes_extendedต้อง start ก่อนAFU.Shopเสมอ - หากไม่ใส่
set afu_licenseสคริปต์จะไม่ทำงาน (console จะแจ้ง License token not configured) - Resource นี้ถูกเข้ารหัสโค้ดไว้ (Obfuscated) — แก้ไขได้เฉพาะไฟล์ในโฟลเดอร์
Config/เท่านั้น ห้ามแก้ไขไฟล์ในSource/เด็ดขาด
⚙️ Configuration
คำเตือน
โปรดแก้ไขเฉพาะค่าในไฟล์ Config/ เท่านั้น ห้ามแก้ไขไฟล์ระบบหลัก (Source/) เนื่องจากมีการเข้ารหัสโค้ดไว้ การแก้ไขไฟล์หลักอาจทำให้ License สิ้นผลหรือสคริปต์ไม่ทำงาน
4.1 ไฟล์ Config/settings.config.lua — ตั้งค่าหลัก
| ตัวแปร (Option) | ชนิดข้อมูล (Type) | ค่าเริ่มต้น (Default) | คำอธิบาย (Description) |
|---|---|---|---|
Shop.ClientConfig.openKey | number | 38 | Control ID ของปุ่มเปิดร้านค้า (ค่าเริ่มต้น 38 = ปุ่ม E) เปลี่ยนเป็น ID ปุ่มอื่นได้ตามต้องการ |
Shop.ClientConfig.pathImage | string | 'nui://inventory/web/assets/icons/' | Path ของรูปไอคอน Item ใน UI ต้องตรงกับโฟลเดอร์ไอคอนของ Inventory ที่ใช้จริง (ต้องมีไฟล์ <item_name>.png ครบทุก Item) |
Shop.ClientConfig.fontName | string | 'Kanit' | ชื่อฟอนต์ที่ใช้วาด 3D Text เหนือร้านและชื่อ Blip ต้องเป็นฟอนต์ที่โหลดอยู่ในเกมแล้ว |
Shop.MoneyTax.bank | number | 7 | อัตราภาษี (%) ที่หักเพิ่มเมื่อจ่ายเงินผ่านธนาคาร |
Shop.MoneyTax.money | number | 0 | อัตราภาษี (%) ที่หักเพิ่มเมื่อจ่ายเงินสด |
Shop.MoneyTax.black_money | number | 0 | อัตราภาษี (%) ที่หักเพิ่มเมื่อจ่ายเงินสกปรก |
Shop.MoneyTag | string | 'AC' | ตัวอักษรย่อสกุลเงินที่แสดงใน UI (เช่น AC, $, THB) |
หมายเหตุเรื่องภาษี
ภาษีจะถูกคำนวณเพิ่มบนราคาสินค้า เช่น ราคา 100 + ภาษีธนาคาร 7% = จ่ายจริง 107 (UI จะแสดง +7% ที่ปุ่มจ่ายธนาคาร)
4.2 ไฟล์ Config/Market/*.lua — กำหนดร้านค้า
ร้านค้าแต่ละแห่งเป็นไฟล์ .lua แยกกันในโฟลเดอร์ Config/Market/ (มีตัวอย่างมาให้ 3 ไฟล์: general.market.lua, ambulance.market.lua, waepon.market.lua)
สร้างร้านใหม่: คัดลอกไฟล์ร้านใดร้านหนึ่งแล้วแก้ค่า หรือสร้างไฟล์ใหม่ตามโครงสร้างด้านล่าง (ระบบจะโหลดทุกไฟล์ในโฟลเดอร์ Market/ อัตโนมัติ)
| ตัวแปร (Option) | ชนิดข้อมูล (Type) | บังคับ | คำอธิบาย (Description) |
|---|---|---|---|
label | string | ✅ | ชื่อร้านค้า (แสดงบน 3D Text, Blip และใน UI) |
npc | number (model hash) | ❌ | โมเดล NPC ตัวขายของ เช่น `u_m_m_fibarchitect` ถ้าไม่ต้องการ NPC ให้ลบหรือ comment บรรทัดนี้ทิ้ง |
animSet | table | ✅ (ถ้ามี NPC) | Animation ของ NPC: { dict = 'ชื่อ animation dictionary', anim = 'ชื่อ animation' } |
job | table<string> | ✅ | รายชื่ออาชีพ (job name ใน ESX) ที่อนุญาตให้เข้าร้านได้ ถ้าเว้นว่าง {} = ทุกคนเข้าได้ |
blip | table | ✅ | ตั้งค่า Blip: sprite (ID รูป), scale (ขนาด), color (สี), hide (ใส่ true เพื่อซ่อน Blip) |
moneyAllow | table<string> | ✅ | ประเภทเงินที่อนุญาตให้จ่ายได้: 'bank', 'cash', 'black_money' — UI จะแสดงปุ่มจ่ายเฉพาะประเภทที่เลือก |
marker | table | ❌ | Marker 3D เหนือร้าน: sprite (ID รูป), color = {r, g, b, a} (0–255), size (ขนาด) ถ้าไม่ต้องการ marker ให้ลบทิ้ง |
Objects | table | ❌ | Prop/Objects ที่จะ spawn ตามตำแหน่งร้าน — รูปแบบเดียว: { model = prop_stone } หรือหลายอัน: { { model = prop_stone}, { model =prop_table } } |
position | table<vector4> | ✅ | ตำแหน่งร้าน (ใส่ได้หลายตำแหน่ง) แต่ละจุดต้องเป็น vector4(x, y, z, w) โดย w = ทิศทาง (heading) |
store | table | ✅ | รายการสินค้า — สูงสุด 8 หมวดหมู่ แต่ละหมวด: { label = 'ชื่อหมวด', items = { { name, label, price }, ... } } |
โครงสร้างตัวอย่าง
lua
local shop = {
label = 'ร้านค้าทั่วไป',
npc = `u_m_m_fibarchitect`, -- ลบทิ้งถ้าไม่ต้องการ NPC
animSet = {
dict = 'anim@amb@nightclub@djs@solomun@',
anim = 'sol_idle_left_b_sol'
},
job = { 'ambulance' }, -- ว่าง {} = ทุกคนเข้าได้
blip = {
sprite = 52,
scale = 0.6,
color = 43
-- hide = true, -- ใส่เพื่อซ่อน Blip
},
moneyAllow = { 'bank', 'cash' }, -- เลือกจาก: bank / cash / black_money
marker = {
sprite = 22,
color = { r = 112, g = 188, b = 187, a = 255 },
size = 0.8
},
Objects = { model = `prop_stone` }, -- ลบทิ้งถ้าไม่ต้องการ prop
position = {
vector4(-301.1, -925.2, 31.08, 75.42),
vector4(324.74, -230.21, 54.22, 66.59),
},
store = {
{
label = 'ทั่วไป',
items = {
{ name = 'bandage', label = 'Bandage', price = 20 },
{ name = 'armor', label = 'Armor', price = 80 },
}
},
{
label = 'อาวุธ',
items = {
{ name = 'WEAPON_PISTOL', label = 'ปืนพก', price = 30000 },
}
}
}
}
table.insert(Shops, shop)4.3 รายละเอียดการตั้งค่าเพิ่มเติม (Detailed Setup)
📍 Locations / Coordinates
- ระบุตำแหน่งด้วย
vector4(x, y, z, w)—wคือ heading (ทิศหันของ NPC/prop) - ร้านเดียวมีได้หลายตำแหน่ง — NPC, Blip, Marker และ prop จะ spawn ทุกตำแหน่งที่ระบุ
- เมื่อผู้เล่นเข้าใกล้ตำแหน่งร้าน ภายใน 20 เมตร จะเห็น 3D Text + Marker / เข้าใกล้ ภายใน 3 เมตร จะแสดง Hint กดปุ่ม
- ใช้
/setcoordsหรือcopycoordsในเกมเพื่อหาพิกัด
🛒 Items & Rewards
name— ชื่อ Item ในระบบ Inventory ของ ESX (ต้องตรงกับชื่อ item ที่ลงทะเบียนไว้)label— ชื่อที่แสดงใน UI (ภาษาไทย/อังกฤษก็ได้)price— ราคาต่อ 1 ชิ้น (ค่า0= ของฟรี)- จำนวนซื้อสูงสุดต่อครั้ง: 9,999 ชิ้นต่อ Item (จำกัดใน UI)
- อาวุธ: Item ที่ชื่อขึ้นต้นด้วย
WEAPON_(เช่นWEAPON_PISTOL) ระบบจะเพิ่มเป็นอาวุธให้อัตโนมัติ — ต้องมีในรายการ weapons ของ ESX เช่นกัน - ระบบจะตรวจสอบ น้ำหนัก (weight) และ limit ของ Inventory ESX ก่อนซื้อ — ถ้าเกินจะไม่ซื้อ
💰 เงิน & อาชีพ
moneyAllow:'bank'— เงินในธนาคาร (accountbank)'cash'— เงินสด (accountmoney)'black_money'— เงินสกปรก (accountblack_money— ต้องมี account นี้อยู่ในระบบ)
job: ใส่ job name (ไม่ใช่ label) ตามที่ ESX กำหนด เช่น'ambulance','police'— ถ้าใส่ผิดผู้เล่นจะเข้าร้านไม่ได้
🎮 ปุ่มเปิดร้าน
Shop.ClientConfig.openKey = 38คือปุ่ม E — ดูรายการ Control ID ทั้งหมดจาก NativesHub (หมวด Controls)
ร้านจะปิดเองในกรณีต่อไปนี้
- ผู้เล่นเดินออกจากร้านเกิน 6 เมตร
- ผู้เล่นตาย/ใกล้ตาย
- ผู้เล่นกด
ESCหรือXใน UI
4.4 ไฟล์ Config/notify.config.lua — ปรับข้อความ Notification
แก้ไขข้อความในฟังก์ชันเหล่านี้ได้ (ใช้ exports.unknow_notify:add):
| ฟังก์ชัน | สถานการณ์ |
|---|---|
Notify.bankAccountNotEnough | เงินในธนาคารไม่พอ |
Notify.cashAccountNotEnough | เงินสดไม่พอ |
Notify.jobNotAllow(jobLabel) | อาชีพไม่ได้รับอนุญาตให้เข้าร้าน (รับพารามิเตอร์ jobLabel = ชื่ออาชีพของผู้เล่น) |
Notify.inventoryNotEnoughSpace | กระเป๋าเต็ม/น้ำหนักเกิน |
4.5 ฟังก์ชัน Customization (Hooks)
Config/client.functions.config.lua (ฝั่ง Client)
| ฟังก์ชัน | เมื่อไหร่ | หมายเหตุ |
|---|---|---|
Functions.willOpenShop(shop) | ตอนผู้เล่นกดปุ่มเปิดร้าน (ก่อนเปิด) | return true = ให้เปิดร้าน / return false = ห้ามเปิด — ใช้เช็ครายการเพิ่มเติม เช่น check cooldown, check distance |
Functions.openedShop(shop) | หลังจากร้านเปิดแล้ว | ว่างไว้ให้ custom เช่น เล่นเสียง, log |
Functions.closedShop() | ตอนผู้เล่นออกจากร้าน | ว่างไว้ให้ custom |
Functions.showHelpNotification(shopLabel) | ตอนเข้าใกล้ร้าน (แสดง Hint กดปุ่ม) | ค่าเริ่มต้นใช้ AFU.Toastify — แก้ไขข้อความ/รูปแบบได้ |
Functions.jobNotAllow() | ตอนผู้เล่นอาชีพที่ไม่ได้รับอนุญาตกดเข้าร้าน | ว่างไว้ให้ custom (Notification แจ้งเตือนส่งจาก notify.config.lua แยกต่างหาก) |
พารามิเตอร์ shop ที่ส่งเข้าฟังก์ชัน: shop.label, shop.id, shop.job, shop.position (x, y, z, w), shop.marker
Config/server.functions.config.lua (ฝั่ง Server)
| ฟังก์ชัน | เมื่อไหร่ | หมายเหตุ |
|---|---|---|
Functions.validateLimitItems(xPlayer, listItem) | ก่อนตัดเงินและเพิ่มไอเทม | ค่าเริ่มต้น: คำนวณน้ำหนัก/limit ของ ESX — return true = อนุญาตซื้อ / return false = ยกเลิก (UI แจ้ง "ของเต็มแล้ว") listItem = { { name = 'item', amount = 5 }, ... } |
Functions.getPlayerAccount(xPlayer, moneyType) | ตอนดึง account ของผู้เล่น | ค่าเริ่มต้น: xPlayer.getAccount(moneyType) — ปรับได้ถ้าใช้ระบบเงิน custom |
Functions.boughtItems(playerId, xPlayer, listItem, typeMoney, totalMoney) | หลังซื้อสำเร็จแล้ว | จุดสำหรับต่อ Log / Discord Webhook — ค่าเริ่มต้นแค่ print รายการที่ซื้อ |
Config/suspects.config.lua (ฝั่ง Server — Anti-Cheat)
ฟังก์ชันเหล่านี้ทำงานเมื่อระบบตรวจพบพฤติกรรมผิดปกติ (ค่าเริ่มต้น: print ลง console):
| ฟังก์ชัน | สถานการณ์ |
|---|---|
Suspects.moneyNotCheckFromClient(xPlayer, playerId) | เงินไม่พอแต่ข้อมูลมาถึงฝั่ง Server (ผิดปกติ) |
Suspects.tryToBuyUnknowShop(xPlayer, playerId) | พยายามซื้อจากร้านที่ไม่อยู่ใน Config |
Suspects.notAllowJob(xPlayer, playerId) | พยายามซื้อจากร้านที่อาชีพไม่ได้รับอนุญาต |
TIP
แนะนำต่อฟังก์ชันเหล่านี้กับระบบ Log/Discord ของเซิร์ฟเวอร์เพื่อจับผู้ดัดแปลงสคริปต์
💬 Commands & Permissions
ไม่มี Command
Resource นี้ ไม่มี Command ใดๆ — การใช้งานเป็นแบบ proximity:
- เดินเข้าใกล้ตำแหน่งร้าน (20 เมตร) → เห็น 3D Text + Marker
- เข้าใกล้ 3 เมตร → แสดง Hint กดปุ่ม
- กดปุ่ม E (
openKey) → เปิด UI ร้านค้า - กด ESC / X → ปิดร้าน
Permissions (ผ่าน Config)
- จำกัดอาชีพ: กำหนดที่ฟิลด์
jobในไฟล์ร้าน — ใส่ job name ที่อนุญาต, ว่าง = ทุกคน - จำกัดเงิน: กำหนดที่ฟิลด์
moneyAllowในไฟล์ร้าน — เลือกประเภทเงินที่จ่ายได้
🔌 Public Exports & Events
INFO
Resource นี้ ไม่มี Public Export หรือ Event ที่เปิดให้ Resource อื่นเรียกใช้ — ทุกอย่างควบคุมผ่าน Config ในโฟลเดอร์ Config/
❓ Troubleshooting
สคริปต์ไม่ทำงาน / Console แจ้ง License
- ตรวจสอบว่าใส่
set afu_license "TOKEN"ในserver.cfgแล้วหรือยัง (ต้องอยู่ในบรรทัดก่อนensure AFU.Shop) - ตรวจสอบว่า Token ยังไม่หมดอายุ และตรงกับ Resource นี้
- ตรวจสอบว่า
oxmysqlและes_extendedstart สำเร็จก่อน (ดู console) - Restart เซิร์ฟเวอร์อีกครั้ง
ร้านไม่แสดง / ไม่มี Blip / ไม่มี Marker
- ตรวจสอบ
position— พิกัดต้องถูกต้องและเป็นvector4(x, y, z, w)(ห้ามลืมw) - ตรวจสอบว่าไฟล์ร้านอยู่ในโฟลเดอร์
Config/Market/และจบด้วยtable.insert(Shops, shop) - Blip ถูกซ่อน: ตรวจสอบว่าไม่ได้ใส่
hide = trueในblip - Restart เซิร์ฟเวอร์หลังแก้ Config
กด E แล้วร้านไม่เปิด
- ตรวจสอบ
openKey— ค่า38คือปุ่ม E ถ้ามักถูกใช้กับ Interaction อื่น อาจต้องเปลี่ยนปุ่ม - ตรวจสอบว่าผู้เล่น ไม่ได้อยู่ในรถ และ ไม่ตาย
- ตรวจสอบฟิลด์
job— ถ้าผู้เล่นไม่ใช่อาชีพที่อนุญาต จะถูกแจ้งเตือนแทน - ตรวจสอบ
Functions.willOpenShop— ถ้า returnfalseร้านจะไม่เปิด
ซื้อของแล้วไม่ได้รับไอเทม
- ตรวจสอบว่า
nameของ Item ใน Config ตรงตัว กับชื่อ item ในระบบ Inventory ESX (case-sensitive) - ตรวจสอบว่า Item นั้นมีอยู่ในตาราง items ของ ESX แล้ว
- สำหรับอาวุธ: ชื่อต้องขึ้นต้นด้วย
WEAPON_และมีในระบบ weapons ของ ESX - ดู console ฝั่ง Server — ถ้ามีข้อความ
Suspect:แสดงว่าระบบตรวจพบความผิดปกติ
UI ไม่มีรูปไอคอน
- แก้
Shop.ClientConfig.pathImageให้ตรงกับ path ไอคอนของ Inventory ที่ใช้จริง - ตรวจสอบว่ามีไฟล์
<item_name>.pngครบทุก Item ในโฟลเดอร์นั้น
3D Text / Blip ขึ้นเป็นกล่องหรือฟอนต์ผิด
- ฟอนต์
Kanit(หรือฟอนต์ที่ระบุในShop.ClientConfig.fontName) ต้องถูกโหลดอยู่ในเกม — ใช้ resource โหลดฟอนต์ของเซิร์ฟเวอร์คุณ
แจ้ง "เงินในธนาคารไม่พอ" ทั้งที่มีเงิน
- ตรวจสอบว่า
moneyAllowของร้านตรงกับประเภทเงินที่มี (เช่น ร้านรับเฉพาะblack_moneyแต่ผู้เล่นมีแต่เงินสด) - ภาษี: ถ้าใช้เงินธนาคาร ระบบจะบวกภาษี
Shop.MoneyTax.bankเข้ายอดรวม — เงินต้องพอรวมภาษีด้วย