X3D เมนูหลัก
เมนูหลักของเซิร์ฟ — การ์ดระบบ ประกาศ อีเวนต์มีช่วงเวลา คู่มือเมือง และหน้าตั้งค่าของผู้เล่น ไม่ต้องลงฐานข้อมูล ต่อกับเฟรมเวิร์กไหนก็ได้
ซื้อ / รับฟรีตั้งค่า configการติดตั้ง
- แตกไฟล์แล้ว เปลี่ยนชื่อโฟลเดอร์เป็น
x3d_menu(สคริปต์อื่นเรียก export ด้วยชื่อนี้) - วางใน
resources/ - เพิ่มใน
server.cfg:
ensure x3d_menu
เสร็จแล้ว — เก็บข้อมูลเป็นไฟล์ data/menu.json ในตัว resource เอง ไม่ต้องรัน SQL ใด ๆ
ผู้เล่นเปิดเมนูด้วย /menu หรือปุ่ม F4 (Config.Key, ว่าง = ไม่ผูกปุ่ม) ปิดเมนูตอนตายหรือค้าง pause menu อัตโนมัติ
ในพาเนล 4 แท็บ: หน้าแรก (ประกาศ + แบนเนอร์อีเวนต์ที่กำลังจัด), ระบบ (ค้นหา, ปักรายการโปรด, การ์ด "ระบบทั้งหมด" กับกริดแยก "ความสะดวก"), คู่มือ (แยกหมวด + ค้นหา), ตั้งค่า (สวิตช์ที่ตั้งไว้ + ภาษา th/en + ธีม peach/jade + สีกำหนดเอง)
ไม่ต้องใช้ฐานข้อมูล ไม่ต้องใช้เฟรมเวิร์ก ไม่มีไลเซนส์
ระบบในเมนู (แท็บ "ระบบ")
การ์ดที่กดแล้วยิงคำสั่งของสคริปต์อื่น — สั่งจาก Config.Systems (seed ครั้งแรกเท่านั้น) หรือ export RegisterSystem ตอน runtime:
| field | ทำอะไร |
|---|---|
system_id |
คีย์ไม่ซ้ำ — ใช้ตรวจ favorite และเป็น id ที่ /menusystem edit|remove เรียกหา |
label / description / icon |
ข้อความ + ไอคอน heroicons บนการ์ด |
command |
คำสั่งที่ยิงตอนกด — ไม่ใส่ = การ์ดกดไม่ได้ |
resource |
resource ที่การ์ดนี้ผูกอยู่ — ไม่ได้ started = การ์ดถูกซ่อน (เว้นแต่ always_show) |
always_show |
โชว์เสมอไม่สนสถานะ resource |
category |
'system' (ค่าเริ่มต้น) = การ์ดเต็มรูปแบบ ขึ้นกริด "ระบบทั้งหมด"; 'qol' = ทางลัดความสะดวก ขึ้นกริดแยก "ความสะดวก" — ค่าอื่นถูกปฏิเสธ |
sort_order |
เรียงจากน้อยไปมาก |
.command ที่ใช้จริงตอนกดมาจาก cache ฝั่ง client ที่ server เพิ่งส่งให้เท่านั้น ไม่เชื่อ command string จาก NUI ตรง ๆ
ตัวอย่างที่มากับ config: การ์ด x3d_mailbox/x3d_dailylogin (ซ่อนเองถ้าเซิร์ฟไม่ได้ลง 2 resource นี้) และการ์ด qol ตัวอย่าง "ล้างหมุดนำทาง" ที่ใช้งานได้จริงทันที
รายการโปรด (ดาวที่การ์ด) เป็นของผู้เล่นแต่ละคน เก็บฝั่ง client
ประกาศ + อีเวนต์ (แท็บ "หน้าแรก")
Config.Announcements — การ์ดข้อความบนหน้าแรก มี title/content/image_url/color/is_active/sort_order
Config.Events — แบนเนอร์บนสุดของหน้าแรก มี start_at/end_at เป็น unix timestamp วินาที ไม่ใส่ = ไม่จำกัดฝั่งนั้น นอกช่วงเวลาจะไม่โชว์เลยแม้ is_active = true (เซิร์ฟกรองให้ก่อนส่ง) แบนเนอร์แสดง "จะเริ่ม", "เหลืออีก N วัน" หรือ "กำลังจัด" ตามเวลาปัจจุบัน
ทั้งสองอย่าง seed จาก config ครั้งแรก แก้ทีหลังผ่าน /menuannouncement / /menuevent
คู่มือ/กฎเมือง (แท็บ "คู่มือ")
Config.Guide — แถวมี category/title/content/image_url/is_active/sort_order พาเนลจัดกลุ่มตาม category เอง + ช่องค้นหา แก้ทีหลังผ่าน /menuguide
ตั้งค่า (แท็บ "ตั้งค่า")
Config.Settings คือ spec ว่าจะวาด item ไหนบ้าง — ไม่ใช่ค่าจริงของผู้เล่น เพิ่มจาก resource อื่นตอน runtime ได้ด้วย export RegisterSetting:
exports['x3d_menu']:RegisterSetting('ทั่วไป', {
id = 'show_minimap', label = 'แสดงมินิแมป', type = 'toggle', default_value = true,
})
exports['x3d_menu']:RegisterSetting('กราฟิก', {
id = 'quality', label = 'คุณภาพเอฟเฟกต์', type = 'select', default_value = 'high',
options = { { value = 'low', label = 'ต่ำ' }, { value = 'high', label = 'สูง' } },
})
type มี 'toggle' / 'select' / 'slider' (min/max สำหรับ slider) ห้ามลงทะเบียนซ้ำ id เดิม และ id ที่ระบบจองไว้ (theme, custom_color, favorites) ลงทะเบียนทับไม่ได้ — สเปกใหม่ถูก broadcast ให้ client ทันทีเพื่อตั้งค่าเริ่มต้น
ในพาเนลเอง: ภาษา th/en (เก็บใน browser localStorage — เป็นภาษาของตัวพาเนล ไม่ผ่านเซิร์ฟเวอร์), ธีม peach/jade กับสีกำหนดเอง — ทุกคนเลือกได้เท่ากัน ไม่มีการล็อกธีมด้วยไอเทม
ที่เก็บข้อมูล — สองที่ คนละหน้าที่
ของผู้เล่นแต่ละคน (ธีม, สีกำหนดเอง, ค่า toggle/select/slider, รายการโปรด) — เก็บฝั่ง client ล้วน ๆ ผ่าน resource KVP แล้ว replicate ขึ้น LocalPlayer.state ภายใต้คีย์ x3dmenu:<id> resource อื่นอ่านได้ฝั่งเซิร์ฟผ่าน export GetSetting หรือ Player(src).state['x3dmenu:<id>'] ตรง ๆ เป็น nil ถ้าผู้เล่นยังไม่เคยตั้งค่า id นั้น
เนื้อหาที่แอดมินคุม (ระบบ, ประกาศ, อีเวนต์, คู่มือ) — เก็บฝั่ง server ผ่าน storage adapter ไฟล์ JSON เดี่ยว (Config.Storage.File, ค่าเริ่มต้น data/menu.json) โหลดครั้งเดียวตอน resource เริ่ม เขียนกลับทุก Config.Storage.FlushSeconds วินาที และตอนผู้เล่นออก/resource หยุด ไฟล์เสียอ่านไม่ได้ → ย้ายไปเก็บเป็น data/menu.broken.json แล้วเริ่มใหม่ Config.Systems/Config.Events/Config.Announcements/Config.Guide ถูกนำเข้า storage แค่ครั้งแรกที่ resource เริ่มทำงาน หลังจากนั้นแก้ config.lua จะไม่มีผลอีก — แก้ผ่านคำสั่งแอดมินแทน
อยากใช้ MySQL แทน (เฉพาะเนื้อหาแอดมิน ของผู้เล่นยังอยู่ที่ client KVP เหมือนเดิม):
- รัน
install.sqlผ่าน oxmysql - เปิด
addons/storage/server.luaเลื่อนลงล่างสุด คัดลอกบล็อก oxmysql ทับ 4 ฟังก์ชันStorage.Get/Set/All/Flushด้านบน
Exports
-- การ์ดระบบใหม่ — ปฏิเสธ (คืน false) ถ้า system_id ซ้ำ, def ไม่ครบ, หรือ category ไม่ใช่ 'system'/'qol'
exports['x3d_menu']:RegisterSystem({
system_id = 'my_shop', label = 'ร้านค้า', description = 'ซื้อของใช้ในเมือง',
icon = 'i-heroicons-shopping-cart', command = 'openmyshop', resource = 'my_shop',
always_show = false, is_active = true, sort_order = 5, category = 'system',
})
exports['x3d_menu']:RegisterSetting(section, def) --> boolean
exports['x3d_menu']:GetSetting(src, 'show_hud') --> ค่าจริง หรือ nil ถ้ายังไม่เคยตั้ง
Hook — server-internal (ไม่ใช่ net event)
AddEventHandler('x3d_menu:onSetting', function(src, id, value)
if id == 'show_hud' then
print(('player %s set show_hud = %s'):format(src, tostring(value)))
end
end)
ยิงทุกครั้งที่ผู้เล่นคนไหนเปลี่ยนค่าอะไรใน x3dmenu:* (รวม theme/custom_color/favorites ด้วย ไม่ใช่แค่ค่าใน Config.Settings)
คำสั่งแอดมิน
จัดการ 4 คอลเลกชัน (systems/events/announcements/guide) ทีละอย่าง เรียกจาก in-game console ได้เสมอ เรียกจากผู้เล่นในเกมต้องมี ace Config.AdminAce (ค่าเริ่มต้น x3d_menu.admin):
/menusystem add <json> -- json ต้องมี system_id, label
/menusystem edit <system_id> <json>
/menusystem remove <system_id>
/menusystem list
/menuevent add <json> -- json ต้องมี title
/menuevent edit <id> <json>
/menuevent remove <id>
/menuevent list
/menuannouncement add <json> -- json ต้องมี title
/menuannouncement edit <id> <json>
/menuannouncement remove <id>
/menuannouncement list
/menuguide add <json> -- json ต้องมี title, content, category
/menuguide edit <id> <json>
/menuguide remove <id>
/menuguide list
add/edit ตรวจ field ที่พาเนลต้องใช้ทุกครั้ง — ขาดแล้วปฏิเสธ ไม่เขียนลง storage เลย edit ห้ามเปลี่ยน id ไปชนแถวอื่นที่มีอยู่แล้ว remove/edit กับ id ที่ไม่มีจริงตอบกลับว่าไม่พบโดยไม่ throw list ส่ง JSON ดิบของทั้งคอลเลกชันกลับไม่แก้อะไรใน storage
ความปลอดภัยที่ทดสอบไว้
- คำสั่งของการ์ดมาจาก cache ฝั่ง server เท่านั้น — ไม่เชื่อ command string ที่ NUI ส่งมาเอง
- แถวจากคำสั่งแอดมินถูกตรวจก่อนเขียนลง storage เสมอ ทั้งตอน
addและตอนedit(ตรวจผลลัพธ์หลัง merge) idจองไว้ของระบบ (theme/custom_color/favorites) ลงทะเบียนทับ/เขียนทับไม่ได้ ทั้งฝั่ง server และ client- request/response ทุกเส้นทางตอบกลับเสมอแม้โดนปฏิเสธ — จำกัด 1 ครั้ง/วินาที/คนต่อ action ผู้เล่นออกจากเกมแล้ว throttle bucket ของคนนั้นถูกล้างทิ้งทันที
- คีย์ผู้เล่นเป็น identifier ถาวรเสมอ ไม่ใช่ server id
pip install lupa
python tools/lua-test/x3d_menu_check.py
ตั้งค่า (config.lua)
| ค่า | ทำอะไร |
|---|---|
Config.Locale |
ภาษาเริ่มต้นของข้อความฝั่ง Lua (th/en) — คนละส่วนกับภาษาของพาเนล |
Config.Command / Config.Key |
คำสั่งเปิด (/menu) และปุ่มลัด (F4) |
Config.AdminAce |
ace ที่ใช้สั่ง /menusystem /menuevent /menuannouncement /menuguide |
Config.Storage.File / .FlushSeconds |
ที่เก็บไฟล์ JSON ของเนื้อหาแอดมิน และความถี่ flush |
Config.SocialLinks |
ลิงก์โซเชียลบนหน้าแรก |
Config.Systems / Config.Events / Config.Announcements / Config.Guide |
ข้อมูลตั้งต้น — seed เข้า storage แค่ครั้งเดียว |
Config.Settings |
spec ของสวิตช์ในแท็บตั้งค่า |
ช่วยเหลือ
Discord: discord.gg/x3d-developer · เว็บ: x3d-developer.com