เอกสาร

X3D เมนูหลัก

เมนูหลักของเซิร์ฟ — การ์ดระบบ ประกาศ อีเวนต์มีช่วงเวลา คู่มือเมือง และหน้าตั้งค่าของผู้เล่น ไม่ต้องลงฐานข้อมูล ต่อกับเฟรมเวิร์กไหนก็ได้

ซื้อ / รับฟรีตั้งค่า config

การติดตั้ง

  1. แตกไฟล์แล้ว เปลี่ยนชื่อโฟลเดอร์เป็น x3d_menu (สคริปต์อื่นเรียก export ด้วยชื่อนี้)
  2. วางใน resources/
  3. เพิ่มใน 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 เหมือนเดิม):

  1. รัน install.sql ผ่าน oxmysql
  2. เปิด 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

ดูหน้าสินค้า