Skip to content

🚀 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.lua

2. 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.openKeynumber38Control ID ของปุ่มเปิดร้านค้า (ค่าเริ่มต้น 38 = ปุ่ม E) เปลี่ยนเป็น ID ปุ่มอื่นได้ตามต้องการ
Shop.ClientConfig.pathImagestring'nui://inventory/web/assets/icons/'Path ของรูปไอคอน Item ใน UI ต้องตรงกับโฟลเดอร์ไอคอนของ Inventory ที่ใช้จริง (ต้องมีไฟล์ <item_name>.png ครบทุก Item)
Shop.ClientConfig.fontNamestring'Kanit'ชื่อฟอนต์ที่ใช้วาด 3D Text เหนือร้านและชื่อ Blip ต้องเป็นฟอนต์ที่โหลดอยู่ในเกมแล้ว
Shop.MoneyTax.banknumber7อัตราภาษี (%) ที่หักเพิ่มเมื่อจ่ายเงินผ่านธนาคาร
Shop.MoneyTax.moneynumber0อัตราภาษี (%) ที่หักเพิ่มเมื่อจ่ายเงินสด
Shop.MoneyTax.black_moneynumber0อัตราภาษี (%) ที่หักเพิ่มเมื่อจ่ายเงินสกปรก
Shop.MoneyTagstring'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)
labelstringชื่อร้านค้า (แสดงบน 3D Text, Blip และใน UI)
npcnumber (model hash)โมเดล NPC ตัวขายของ เช่น `u_m_m_fibarchitect` ถ้าไม่ต้องการ NPC ให้ลบหรือ comment บรรทัดนี้ทิ้ง
animSettable✅ (ถ้ามี NPC)Animation ของ NPC: { dict = 'ชื่อ animation dictionary', anim = 'ชื่อ animation' }
jobtable<string>รายชื่ออาชีพ (job name ใน ESX) ที่อนุญาตให้เข้าร้านได้ ถ้าเว้นว่าง {} = ทุกคนเข้าได้
bliptableตั้งค่า Blip: sprite (ID รูป), scale (ขนาด), color (สี), hide (ใส่ true เพื่อซ่อน Blip)
moneyAllowtable<string>ประเภทเงินที่อนุญาตให้จ่ายได้: 'bank', 'cash', 'black_money' — UI จะแสดงปุ่มจ่ายเฉพาะประเภทที่เลือก
markertableMarker 3D เหนือร้าน: sprite (ID รูป), color = {r, g, b, a} (0–255), size (ขนาด) ถ้าไม่ต้องการ marker ให้ลบทิ้ง
ObjectstableProp/Objects ที่จะ spawn ตามตำแหน่งร้าน — รูปแบบเดียว: { model = prop_stone } หรือหลายอัน: { { model = prop_stone}, { model =prop_table } }
positiontable<vector4>ตำแหน่งร้าน (ใส่ได้หลายตำแหน่ง) แต่ละจุดต้องเป็น vector4(x, y, z, w) โดย w = ทิศทาง (heading)
storetableรายการสินค้า — สูงสุด 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' — เงินในธนาคาร (account bank)
    • 'cash' — เงินสด (account money)
    • 'black_money' — เงินสกปรก (account black_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:

  1. เดินเข้าใกล้ตำแหน่งร้าน (20 เมตร) → เห็น 3D Text + Marker
  2. เข้าใกล้ 3 เมตร → แสดง Hint กดปุ่ม
  3. กดปุ่ม E (openKey) → เปิด UI ร้านค้า
  4. กด ESC / X → ปิดร้าน

Permissions (ผ่าน Config)

  • จำกัดอาชีพ: กำหนดที่ฟิลด์ job ในไฟล์ร้าน — ใส่ job name ที่อนุญาต, ว่าง = ทุกคน
  • จำกัดเงิน: กำหนดที่ฟิลด์ moneyAllow ในไฟล์ร้าน — เลือกประเภทเงินที่จ่ายได้

🔌 Public Exports & Events

INFO

Resource นี้ ไม่มี Public Export หรือ Event ที่เปิดให้ Resource อื่นเรียกใช้ — ทุกอย่างควบคุมผ่าน Config ในโฟลเดอร์ Config/


❓ Troubleshooting

สคริปต์ไม่ทำงาน / Console แจ้ง License
  1. ตรวจสอบว่าใส่ set afu_license "TOKEN" ใน server.cfg แล้วหรือยัง (ต้องอยู่ในบรรทัดก่อน ensure AFU.Shop)
  2. ตรวจสอบว่า Token ยังไม่หมดอายุ และตรงกับ Resource นี้
  3. ตรวจสอบว่า oxmysql และ es_extended start สำเร็จก่อน (ดู console)
  4. Restart เซิร์ฟเวอร์อีกครั้ง
ร้านไม่แสดง / ไม่มี Blip / ไม่มี Marker
  1. ตรวจสอบ position — พิกัดต้องถูกต้องและเป็น vector4(x, y, z, w) (ห้ามลืม w)
  2. ตรวจสอบว่าไฟล์ร้านอยู่ในโฟลเดอร์ Config/Market/ และจบด้วย table.insert(Shops, shop)
  3. Blip ถูกซ่อน: ตรวจสอบว่าไม่ได้ใส่ hide = true ใน blip
  4. Restart เซิร์ฟเวอร์หลังแก้ Config
กด E แล้วร้านไม่เปิด
  1. ตรวจสอบ openKey — ค่า 38 คือปุ่ม E ถ้ามักถูกใช้กับ Interaction อื่น อาจต้องเปลี่ยนปุ่ม
  2. ตรวจสอบว่าผู้เล่น ไม่ได้อยู่ในรถ และ ไม่ตาย
  3. ตรวจสอบฟิลด์ job — ถ้าผู้เล่นไม่ใช่อาชีพที่อนุญาต จะถูกแจ้งเตือนแทน
  4. ตรวจสอบ Functions.willOpenShop — ถ้า return false ร้านจะไม่เปิด
ซื้อของแล้วไม่ได้รับไอเทม
  1. ตรวจสอบว่า name ของ Item ใน Config ตรงตัว กับชื่อ item ในระบบ Inventory ESX (case-sensitive)
  2. ตรวจสอบว่า Item นั้นมีอยู่ในตาราง items ของ ESX แล้ว
  3. สำหรับอาวุธ: ชื่อต้องขึ้นต้นด้วย WEAPON_ และมีในระบบ weapons ของ ESX
  4. ดู 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 เข้ายอดรวม — เงินต้องพอรวมภาษีด้วย