[{"data":1,"prerenderedAt":88},["ShallowReactive",2],{"site":3,"config-slug-site":15,"config-product-th-x3d-menu":21,"config-public-x3d-menu":83},{"site":4,"contact":10,"seo":13,"payment":14},{"name":5,"host":6,"default_locale":7,"locales":8},"X3D Developer","fivem.x3d-developer.com","th",[7,9],"en",{"discord_invite":11,"email":12,"line":12},"https:\u002F\u002Fdiscord.gg\u002FNA5kPVynFv","",{"google_site_verification":12,"bing_site_verification":12},{"bank_name":12,"account_name":12,"account_no":12,"promptpay_id":12},{"site":16,"contact":18,"seo":19,"payment":20},{"name":5,"host":6,"default_locale":7,"locales":17},[7,9],{"discord_invite":11,"email":12,"line":12},{"google_site_verification":12,"bing_site_verification":12},{"bank_name":12,"account_name":12,"account_no":12,"promptpay_id":12},{"id":22,"slug":23,"name":24,"summary":25,"price_thb":26,"license_mode":27,"cover_key":28,"cover_url":29,"category_slugs":30,"tags":32,"latest_version":48,"latest_released_at":49,"published_at":50,"has_docs":51,"config":52,"rent_price_satang":53,"description_md":54,"docs_md":55,"seo_title":56,"seo_description":57,"media":58,"versions":72},"7337afbc-ca0d-4071-9de4-2cc04bd1b4e6","x3d-menu","X3D เมนูหลัก","เมนูหลักของเซิร์ฟ — การ์ดระบบ ประกาศ อีเวนต์มีช่วงเวลา คู่มือเมือง และหน้าตั้งค่าของผู้เล่น ไม่ต้องลงฐานข้อมูล ต่อกับเฟรมเวิร์กไหนก็ได้",0,"none","public\u002Fproducts\u002Fx3d-menu\u002Fcover\u002F8a1dbb30861b0387.jpg","\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-menu\u002Fcover\u002F8a1dbb30861b0387.jpg",[31],"hud",[33,34,35,36,37,38,39,40,41,42,43,44,45,46,47],"esx","standalone","เมนูหลัก","main menu","menu","ประกาศ","อีเวนต์","คู่มือเมือง","ฟรี","free","เมนูเมือง","กฎเมือง","เมนู F4","ตั้งค่าผู้เล่น","แบนเนอร์อีเวนต์","1.1.4","2026-10-12T17:21:29.681926Z","2026-09-21T04:20:29.437521Z",true,"public",null,"# เมนูหลัก + ระบบ\u002Fประกาศ\u002Fอีเวนต์\u002Fคู่มือ + ตั้งค่าผู้เล่น — ฟรี ไม่ต้องลงฐานข้อมูล\n\nทุกเซิร์ฟเวอร์ต้องมีเมนูหลักสักอัน — การ์ดรวมระบบทั้งหมดให้ผู้เล่นกดเข้าแทนที่จะจำคำสั่งเอง ประกาศ\u002Fอีเวนต์ที่มีช่วงเวลาบนหน้าแรก คู่มือกฎเมืองแยกหมวด และแท็บตั้งค่าที่สคริปต์อื่นเพิ่มสวิตช์เข้ามาเองได้ `x3d_menu` ทำครบในไฟล์เดียว ผ่านแผง Nuxt 4 UI พร้อมใช้\n\n---\n\n## ฟีเจอร์\n\n- [+] ตั้งค่าง่าย ปรับได้หมดใน config หรือตั้งผ่านเว็บได้เลย\n- [+] การ์ดระบบกดเปิดคำสั่งของสคริปต์อื่น ซ่อนเองถ้าไม่ได้ลง resource\n- [+] ปักรายการโปรดและค้นหาระบบได้ แยกกริดความสะดวก\n- [+] ประกาศบนหน้าแรก ใส่รูป สี ลำดับเองได้\n- [+] อีเวนต์ตั้งช่วงเวลาเริ่ม-จบ นอกช่วงหายเอง\n- [+] คู่มือกฎเมืองแยกหมวด มีช่องค้นหา\n- [+] แอดมินแก้ประกาศ อีเวนต์ คู่มือในเกมด้วยคำสั่ง ไม่ต้องรีสตาร์ท\n- [+] สคริปต์อื่นเพิ่มสวิตช์ในหน้าตั้งค่าผู้เล่นได้\n- [+] ผู้เล่นเลือกธีม peach \u002F jade และสีเองได้ สลับไทย\u002Fอังกฤษ\n- [+] ใส่ลิงก์ Discord YouTube Facebook บนหน้าแรกได้\n- [+] ไม่ต้องลงฐานข้อมูล ไม่ต้องใช้เฟรมเวิร์ก สลับเป็น MySQL ได้\n- [+] เขียนเชื่อมกับสคริปต์อื่นได้ที่ addons\u002F\n\n---\n\n## การ์ดระบบ แบ่งเป็น \"ระบบ\" กับ \"ความสะดวก\" ซ่อนเองถ้า resource ยังไม่ได้ลง\n\n`Config.Systems` หรือ export `RegisterSystem` ตอน runtime เพิ่มการ์ดได้ทั้งสองทาง แต่ละการ์ดผูกกับ `resource` ของตัวเองได้ — resource นั้นไม่ได้ `started` การ์ดจะถูกซ่อนอัตโนมัติ ไม่ต้องมาคอยลบเอง `category` แยกการ์ดเต็มรูปแบบ (`'system'`) ออกจากทางลัดความสะดวกแบบปักหมุด\u002Fสลับด่วน (`'qol'`) เป็นกริดคนละส่วน คำสั่งที่ยิงตอนกดมาจาก cache ฝั่งเซิร์ฟเวอร์เท่านั้น ไม่เชื่อ command string จากตัวพาเนลเอง\n\n## ประกาศ อีเวนต์มีช่วงเวลา คู่มือแยกหมวด — แก้ได้จากคำสั่งแอดมิน ไม่ต้องรีสตาร์ท\n\nอีเวนต์ตั้ง `start_at`\u002F`end_at` แล้วนอกช่วงเวลาจะหายจากพาเนลเองแม้เปิด `is_active` ไว้ แบนเนอร์หน้าแรกบอกเองว่า \"จะเริ่ม\", \"เหลืออีก N วัน\" หรือ \"กำลังจัด\" ข้อมูลทั้งหมด (ระบบ\u002Fประกาศ\u002Fอีเวนต์\u002Fคู่มือ) seed จาก `config.lua` แค่ครั้งแรกที่ resource เริ่ม หลังจากนั้นแก้ผ่านคำสั่งแอดมิน `\u002Fmenusystem` `\u002Fmenuevent` `\u002Fmenuannouncement` `\u002Fmenuguide` — เพิ่ม\u002Fแก้\u002Fลบ\u002Fดูรายการ ไม่ต้องรีสตาร์ทเซิร์ฟ\n\n## ตั้งค่าผู้เล่น: ธีม peach\u002Fjade สีกำหนดเอง และสวิตช์ที่สคริปต์อื่นเพิ่มเองได้\n\n`RegisterSetting` เพิ่มสวิตช์ (toggle\u002Fselect\u002Fslider) เข้าแท็บตั้งค่าจาก resource ไหนก็ได้ ค่าจริงของผู้เล่นเก็บฝั่ง client ผ่าน KVP แล้ว replicate ขึ้น `LocalPlayer.state` ให้ resource อื่นอ่านได้ทันทีผ่าน export `GetSetting` หรือ state bag ตรง ๆ พร้อม hook `x3d_menu:onSetting` แจ้งทุกครั้งที่ผู้เล่นเปลี่ยนค่าอะไร\n\n## ไม่ต้องลงฐานข้อมูล\n\nเนื้อหาที่แอดมินคุม (ระบบ\u002Fประกาศ\u002Fอีเวนต์\u002Fคู่มือ) เก็บเป็นไฟล์ JSON ในตัว resource เอง flush ทุก 30 วินาทีและตอนผู้เล่นออก\u002Fresource หยุดแบบปกติ อยากได้ MySQL สำหรับเซิร์ฟเวอร์ใหญ่? สลับ 4 ฟังก์ชันในไฟล์เดียวไปใช้บล็อก oxmysql ที่คอมเมนต์ไว้ท้ายไฟล์ พร้อม `install.sql`\n\n## ต้องมี\n\nไม่มี · ไม่ต้องใช้ฐานข้อมูล ไม่ต้องใช้เฟรมเวิร์ก ไม่มีไลเซนส์ · วางแล้ว `ensure` ได้เลย\n\n**ฟรี** · แก้โค้ดได้ตามใจ · ขอแค่อย่านำไปขายต่อและอย่าอัปโหลดใหม่ในชื่อตัวเอง\n","## การติดตั้ง\n\n1. แตกไฟล์แล้ว **เปลี่ยนชื่อโฟลเดอร์เป็น `x3d_menu`** (สคริปต์อื่นเรียก export ด้วยชื่อนี้)\n2. วางใน `resources\u002F`\n3. เพิ่มใน `server.cfg`:\n\n```\nensure x3d_menu\n```\n\nเสร็จแล้ว — เก็บข้อมูลเป็นไฟล์ `data\u002Fmenu.json` ในตัว resource เอง ไม่ต้องรัน SQL ใด ๆ\n\nผู้เล่นเปิดเมนูด้วย `\u002Fmenu` หรือปุ่ม **F4** (`Config.Key`, ว่าง = ไม่ผูกปุ่ม) ปิดเมนูตอนตายหรือค้าง pause menu อัตโนมัติ\n\nในพาเนล 4 แท็บ: **หน้าแรก** (ประกาศ + แบนเนอร์อีเวนต์ที่กำลังจัด), **ระบบ** (ค้นหา, ปักรายการโปรด, การ์ด \"ระบบทั้งหมด\" กับกริดแยก \"ความสะดวก\"), **คู่มือ** (แยกหมวด + ค้นหา), **ตั้งค่า** (สวิตช์ที่ตั้งไว้ + ภาษา th\u002Fen + ธีม peach\u002Fjade + สีกำหนดเอง)\n\nไม่ต้องใช้ฐานข้อมูล ไม่ต้องใช้เฟรมเวิร์ก ไม่มีไลเซนส์\n\n## ระบบในเมนู (แท็บ \"ระบบ\")\n\nการ์ดที่กดแล้วยิงคำสั่งของสคริปต์อื่น — สั่งจาก `Config.Systems` (seed ครั้งแรกเท่านั้น) หรือ export `RegisterSystem` ตอน runtime:\n\n| field | ทำอะไร |\n|---|---|\n| `system_id` | คีย์ไม่ซ้ำ — ใช้ตรวจ favorite และเป็น id ที่ `\u002Fmenusystem edit\\|remove` เรียกหา |\n| `label` \u002F `description` \u002F `icon` | ข้อความ + ไอคอน heroicons บนการ์ด |\n| `command` | คำสั่งที่ยิงตอนกด — ไม่ใส่ = การ์ดกดไม่ได้ |\n| `resource` | resource ที่การ์ดนี้ผูกอยู่ — ไม่ได้ `started` = การ์ดถูกซ่อน (เว้นแต่ `always_show`) |\n| `always_show` | โชว์เสมอไม่สนสถานะ resource |\n| `category` | `'system'` (ค่าเริ่มต้น) = การ์ดเต็มรูปแบบ ขึ้นกริด \"ระบบทั้งหมด\"; `'qol'` = ทางลัดความสะดวก ขึ้นกริดแยก \"ความสะดวก\" — ค่าอื่นถูกปฏิเสธ |\n| `sort_order` | เรียงจากน้อยไปมาก |\n\n`.command` ที่ใช้จริงตอนกดมาจาก cache ฝั่ง client ที่ server เพิ่งส่งให้เท่านั้น ไม่เชื่อ command string จาก NUI ตรง ๆ\n\nตัวอย่างที่มากับ config: การ์ด `x3d_mailbox`\u002F`x3d_dailylogin` (ซ่อนเองถ้าเซิร์ฟไม่ได้ลง 2 resource นี้) และการ์ด `qol` ตัวอย่าง \"ล้างหมุดนำทาง\" ที่ใช้งานได้จริงทันที\n\nรายการโปรด (ดาวที่การ์ด) เป็นของผู้เล่นแต่ละคน เก็บฝั่ง client\n\n## ประกาศ + อีเวนต์ (แท็บ \"หน้าแรก\")\n\n`Config.Announcements` — การ์ดข้อความบนหน้าแรก มี `title`\u002F`content`\u002F`image_url`\u002F`color`\u002F`is_active`\u002F`sort_order`\n\n`Config.Events` — แบนเนอร์บนสุดของหน้าแรก มี `start_at`\u002F`end_at` เป็น unix timestamp วินาที ไม่ใส่ = ไม่จำกัดฝั่งนั้น นอกช่วงเวลาจะไม่โชว์เลยแม้ `is_active = true` (เซิร์ฟกรองให้ก่อนส่ง) แบนเนอร์แสดง \"จะเริ่ม\", \"เหลืออีก N วัน\" หรือ \"กำลังจัด\" ตามเวลาปัจจุบัน\n\nทั้งสองอย่าง seed จาก config ครั้งแรก แก้ทีหลังผ่าน `\u002Fmenuannouncement` \u002F `\u002Fmenuevent`\n\n## คู่มือ\u002Fกฎเมือง (แท็บ \"คู่มือ\")\n\n`Config.Guide` — แถวมี `category`\u002F`title`\u002F`content`\u002F`image_url`\u002F`is_active`\u002F`sort_order` พาเนลจัดกลุ่มตาม `category` เอง + ช่องค้นหา แก้ทีหลังผ่าน `\u002Fmenuguide`\n\n## ตั้งค่า (แท็บ \"ตั้งค่า\")\n\n`Config.Settings` คือ spec ว่าจะวาด item ไหนบ้าง — ไม่ใช่ค่าจริงของผู้เล่น เพิ่มจาก resource อื่นตอน runtime ได้ด้วย export `RegisterSetting`:\n\n```lua\nexports['x3d_menu']:RegisterSetting('ทั่วไป', {\n    id = 'show_minimap', label = 'แสดงมินิแมป', type = 'toggle', default_value = true,\n})\nexports['x3d_menu']:RegisterSetting('กราฟิก', {\n    id = 'quality', label = 'คุณภาพเอฟเฟกต์', type = 'select', default_value = 'high',\n    options = { { value = 'low', label = 'ต่ำ' }, { value = 'high', label = 'สูง' } },\n})\n```\n\n`type` มี `'toggle'` \u002F `'select'` \u002F `'slider'` (`min`\u002F`max` สำหรับ slider) ห้ามลงทะเบียนซ้ำ `id` เดิม และ `id` ที่ระบบจองไว้ (`theme`, `custom_color`, `favorites`) ลงทะเบียนทับไม่ได้ — สเปกใหม่ถูก broadcast ให้ client ทันทีเพื่อตั้งค่าเริ่มต้น\n\nในพาเนลเอง: ภาษา th\u002Fen (เก็บใน browser `localStorage` — เป็นภาษาของตัวพาเนล ไม่ผ่านเซิร์ฟเวอร์), ธีม peach\u002Fjade กับสีกำหนดเอง — ทุกคนเลือกได้เท่ากัน ไม่มีการล็อกธีมด้วยไอเทม\n\n## ที่เก็บข้อมูล — สองที่ คนละหน้าที่\n\n**ของผู้เล่นแต่ละคน** (ธีม, สีกำหนดเอง, ค่า toggle\u002Fselect\u002Fslider, รายการโปรด) — เก็บฝั่ง client ล้วน ๆ ผ่าน resource KVP แล้ว replicate ขึ้น `LocalPlayer.state` ภายใต้คีย์ `x3dmenu:\u003Cid>` resource อื่นอ่านได้ฝั่งเซิร์ฟผ่าน export `GetSetting` หรือ `Player(src).state['x3dmenu:\u003Cid>']` ตรง ๆ เป็น `nil` ถ้าผู้เล่นยังไม่เคยตั้งค่า id นั้น\n\n**เนื้อหาที่แอดมินคุม** (ระบบ, ประกาศ, อีเวนต์, คู่มือ) — เก็บฝั่ง server ผ่าน storage adapter ไฟล์ JSON เดี่ยว (`Config.Storage.File`, ค่าเริ่มต้น `data\u002Fmenu.json`) โหลดครั้งเดียวตอน resource เริ่ม เขียนกลับทุก `Config.Storage.FlushSeconds` วินาที และตอนผู้เล่นออก\u002Fresource หยุด ไฟล์เสียอ่านไม่ได้ → ย้ายไปเก็บเป็น `data\u002Fmenu.broken.json` แล้วเริ่มใหม่ `Config.Systems`\u002F`Config.Events`\u002F`Config.Announcements`\u002F`Config.Guide` ถูกนำเข้า storage แค่ครั้งแรกที่ resource เริ่มทำงาน หลังจากนั้นแก้ `config.lua` จะไม่มีผลอีก — แก้ผ่านคำสั่งแอดมินแทน\n\nอยากใช้ MySQL แทน (เฉพาะเนื้อหาแอดมิน ของผู้เล่นยังอยู่ที่ client KVP เหมือนเดิม):\n\n1. รัน `install.sql` ผ่าน oxmysql\n2. เปิด `addons\u002Fstorage\u002Fserver.lua` เลื่อนลงล่างสุด คัดลอกบล็อก oxmysql ทับ 4 ฟังก์ชัน `Storage.Get\u002FSet\u002FAll\u002FFlush` ด้านบน\n\n## Exports\n\n```lua\n-- การ์ดระบบใหม่ — ปฏิเสธ (คืน false) ถ้า system_id ซ้ำ, def ไม่ครบ, หรือ category ไม่ใช่ 'system'\u002F'qol'\nexports['x3d_menu']:RegisterSystem({\n    system_id = 'my_shop', label = 'ร้านค้า', description = 'ซื้อของใช้ในเมือง',\n    icon = 'i-heroicons-shopping-cart', command = 'openmyshop', resource = 'my_shop',\n    always_show = false, is_active = true, sort_order = 5, category = 'system',\n})\n\nexports['x3d_menu']:RegisterSetting(section, def)   --> boolean\nexports['x3d_menu']:GetSetting(src, 'show_hud')     --> ค่าจริง หรือ nil ถ้ายังไม่เคยตั้ง\n```\n\n## Hook — server-internal (ไม่ใช่ net event)\n\n```lua\nAddEventHandler('x3d_menu:onSetting', function(src, id, value)\n    if id == 'show_hud' then\n        print(('player %s set show_hud = %s'):format(src, tostring(value)))\n    end\nend)\n```\n\nยิงทุกครั้งที่ผู้เล่นคนไหนเปลี่ยนค่าอะไรใน `x3dmenu:*` (รวม theme\u002Fcustom_color\u002Ffavorites ด้วย ไม่ใช่แค่ค่าใน `Config.Settings`)\n\n## คำสั่งแอดมิน\n\nจัดการ 4 คอลเลกชัน (`systems`\u002F`events`\u002F`announcements`\u002F`guide`) ทีละอย่าง เรียกจาก in-game console ได้เสมอ เรียกจากผู้เล่นในเกมต้องมี ace `Config.AdminAce` (ค่าเริ่มต้น `x3d_menu.admin`):\n\n```\n\u002Fmenusystem add \u003Cjson>          -- json ต้องมี system_id, label\n\u002Fmenusystem edit \u003Csystem_id> \u003Cjson>\n\u002Fmenusystem remove \u003Csystem_id>\n\u002Fmenusystem list\n\n\u002Fmenuevent add \u003Cjson>           -- json ต้องมี title\n\u002Fmenuevent edit \u003Cid> \u003Cjson>\n\u002Fmenuevent remove \u003Cid>\n\u002Fmenuevent list\n\n\u002Fmenuannouncement add \u003Cjson>    -- json ต้องมี title\n\u002Fmenuannouncement edit \u003Cid> \u003Cjson>\n\u002Fmenuannouncement remove \u003Cid>\n\u002Fmenuannouncement list\n\n\u002Fmenuguide add \u003Cjson>           -- json ต้องมี title, content, category\n\u002Fmenuguide edit \u003Cid> \u003Cjson>\n\u002Fmenuguide remove \u003Cid>\n\u002Fmenuguide list\n```\n\n`add`\u002F`edit` ตรวจ field ที่พาเนลต้องใช้ทุกครั้ง — ขาดแล้วปฏิเสธ ไม่เขียนลง storage เลย `edit` ห้ามเปลี่ยน id ไปชนแถวอื่นที่มีอยู่แล้ว `remove`\u002F`edit` กับ id ที่ไม่มีจริงตอบกลับว่าไม่พบโดยไม่ throw `list` ส่ง JSON ดิบของทั้งคอลเลกชันกลับไม่แก้อะไรใน storage\n\n## ความปลอดภัยที่ทดสอบไว้\n\n- **คำสั่งของการ์ดมาจาก cache ฝั่ง server เท่านั้น** — ไม่เชื่อ command string ที่ NUI ส่งมาเอง\n- **แถวจากคำสั่งแอดมินถูกตรวจก่อนเขียนลง storage เสมอ** ทั้งตอน `add` และตอน `edit` (ตรวจผลลัพธ์หลัง merge)\n- **`id` จองไว้ของระบบ (`theme`\u002F`custom_color`\u002F`favorites`) ลงทะเบียนทับ\u002Fเขียนทับไม่ได้** ทั้งฝั่ง server และ client\n- **request\u002Fresponse ทุกเส้นทางตอบกลับเสมอแม้โดนปฏิเสธ** — จำกัด 1 ครั้ง\u002Fวินาที\u002Fคนต่อ action ผู้เล่นออกจากเกมแล้ว throttle bucket ของคนนั้นถูกล้างทิ้งทันที\n- **คีย์ผู้เล่นเป็น identifier ถาวรเสมอ** ไม่ใช่ server id\n\n```bash\npip install lupa\npython tools\u002Flua-test\u002Fx3d_menu_check.py\n```\n\n## ตั้งค่า (`config.lua`)\n\n| ค่า | ทำอะไร |\n|---|---|\n| `Config.Locale` | ภาษาเริ่มต้นของข้อความฝั่ง Lua (`th`\u002F`en`) — คนละส่วนกับภาษาของพาเนล |\n| `Config.Command` \u002F `Config.Key` | คำสั่งเปิด (`\u002Fmenu`) และปุ่มลัด (`F4`) |\n| `Config.AdminAce` | ace ที่ใช้สั่ง `\u002Fmenusystem` `\u002Fmenuevent` `\u002Fmenuannouncement` `\u002Fmenuguide` |\n| `Config.Storage.File` \u002F `.FlushSeconds` | ที่เก็บไฟล์ JSON ของเนื้อหาแอดมิน และความถี่ flush |\n| `Config.SocialLinks` | ลิงก์โซเชียลบนหน้าแรก |\n| `Config.Systems` \u002F `Config.Events` \u002F `Config.Announcements` \u002F `Config.Guide` | ข้อมูลตั้งต้น — seed เข้า storage แค่ครั้งเดียว |\n| `Config.Settings` | spec ของสวิตช์ในแท็บตั้งค่า |\n\n## ช่วยเหลือ\n\nDiscord: discord.gg\u002Fx3d-developer · เว็บ: x3d-developer.com\n","สคริปต์เมนูหลัก FiveM ฟรี ไม่ต้องใช้ฐานข้อมูล","เมนูหลัก FiveM ฟรี รวมการ์ดระบบ ประกาศ อีเวนต์ตามช่วงเวลา คู่มือเมือง ตั้งค่าผู้เล่นและธีม ไม่ต้องลงฐานข้อมูล แอดมินแก้เนื้อหาในเกมได้",[59,62,64,66,68,70],{"kind":60,"url":61},"image","\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-menu\u002Fmedia\u002Fdf5554e733bb3344.png",{"kind":60,"url":63},"\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-menu\u002Fmedia\u002Fb69a7d2d674141e6.png",{"kind":60,"url":65},"\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-menu\u002Fmedia\u002Fac244d9e6dbda554.png",{"kind":60,"url":67},"\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-menu\u002Fmedia\u002F6d4299351032214d.png",{"kind":60,"url":69},"\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-menu\u002Fmedia\u002F60886675555c9c64.png",{"kind":60,"url":71},"\u002Fapi\u002Fv1\u002Fmedia\u002Fpublic\u002Fproducts\u002Fx3d-menu\u002Fmedia\u002F4a5b1d1684d6d441.png",[73,75,79],{"version":48,"released_at":49,"changelog_md":74},"### Changed\n\n- Store images now carry English captions for the English store page",{"version":76,"released_at":77,"changelog_md":78},"1.1.1","2026-10-12T03:26:17.490932Z","### Changed\n\n- UI font is now Kanit (one family for Thai and English), bundled inside the script; the Thai font now really loads in game (FiveM's browser dropped the old font declaration and fell back to a system font)",{"version":80,"released_at":81,"changelog_md":82},"1.0.0","2026-09-21T04:20:20.653667Z","### Added\n- Main menu panel (`\u002Fmenu`, F4) ported from cc_menu: home with announcements and a timed event banner, systems grid with favourites, guidebook with categories and search, settings tab\n- Systems come from `Config.Systems` or the `RegisterSystem` export, split into the full `system` grid and the `qol` convenience grid, hidden automatically while their resource is not started\n- Events carry a `start_at`\u002F`end_at` window and are filtered server-side, so an out-of-window event never reaches the panel\n- Settings spec from `Config.Settings` plus the `RegisterSetting` export, with `toggle`, `select` and `slider` controls; a newly registered spec is broadcast so its default is readable without the player opening the panel\n- Per-player theme, custom colour, setting values and favourites live in client KVP and are republished to `LocalPlayer.state` under `x3dmenu:\u003Cid>`, readable server-side through `GetSetting(src, id)` or the state bag directly\n- Admin-managed content (systems, announcements, events, guidebook) lives in the storage adapter: a JSON file by default, with an oxmysql block shipped commented in the same file\n- Admin commands `\u002Fmenusystem`, `\u002Fmenuevent`, `\u002Fmenuannouncement`, `\u002Fmenuguide` with `add`, `edit`, `remove` and `list`, gated by the `x3d_menu.admin` ace and always available from the console\n- Server event `x3d_menu:onSetting(src, id, value)` for reacting to a player's choice\n- Every net event resolves `source` itself, is rate limited per action per second, validates its arguments before use and always answers; a card's command is taken from the server's cache, never from the NUI",{"slug":23,"version":48,"latest_version":48,"files":84,"options":53},[85],{"path":86,"content":87},"config.lua","Config = {}\n\n-- ===== ทั่วไป \u002F General =====\n\n-- เฟรมเวิร์ก: 'auto' (ตรวจอัตโนมัติ) | 'esx' | 'standalone'\n-- Framework: 'auto' (detect automatically) | 'esx' | 'standalone'\nConfig.Framework = 'auto'\n\n-- ภาษาเริ่มต้นของข้อความฝั่ง Lua: 'th' | 'en' \u002F Default language for Lua-side messages: 'th' | 'en'\nConfig.Locale  = 'th'\n\n-- คำสั่งเปิดเมนู (\u002Fmenu) \u002F Command that opens the menu (\u002Fmenu)\nConfig.Command = 'menu'\n\n-- ปุ่มเปิดเมนู ('' = ไม่ผูกปุ่ม) \u002F Key that opens the menu ('' = no key bound)\nConfig.Key     = 'F4'\n\n-- สิทธิ์ ACE ของแอดมิน (ใช้กับ \u002Fmenusystem \u002Fmenuevent \u002Fmenuannouncement \u002Fmenuguide)\n-- Admin ACE permission (used by \u002Fmenusystem \u002Fmenuevent \u002Fmenuannouncement \u002Fmenuguide)\nConfig.AdminAce = 'x3d_menu.admin'\n\n-- ===== ที่เก็บข้อมูล \u002F Storage =====\n\n-- ไฟล์ JSON ที่เก็บเนื้อหาที่แอดมินแก้ และความถี่การเซฟ (วินาที)\n-- JSON file holding admin-managed content, and how often it is saved (seconds)\nConfig.Storage = {\n    File         = 'data\u002Fmenu.json',\n    FlushSeconds = 30,\n}\n\n-- ===== ลิงก์โซเชียล \u002F Social links =====\n\n-- ลิงก์โซเชียลบนหน้าแรก — ปิดอันไหนก็ตั้ง enabled = false\n-- Social links on the home tab; set enabled = false to hide one\n-- เช่น \u002F e.g. { id = 'discord', label = 'Discord', icon = 'i-simple-icons-discord', url = 'https:\u002F\u002Fdiscord.gg\u002Fexample', enabled = true }\nConfig.SocialLinks = {\n    { id = 'discord',  label = 'Discord',  icon = 'i-simple-icons-discord',  url = 'https:\u002F\u002Fdiscord.gg\u002Fexample',      enabled = true },\n    { id = 'youtube',  label = 'YouTube',  icon = 'i-simple-icons-youtube',  url = 'https:\u002F\u002Fyoutube.com\u002F@example',    enabled = true },\n    { id = 'facebook', label = 'Facebook', icon = 'i-simple-icons-facebook', url = 'https:\u002F\u002Ffacebook.com\u002Fexample',    enabled = true },\n}\n\n-- ===== ระบบในเมนู \u002F Menu systems =====\n\n-- ═══════════════════════════════════════════════════════════════\n--  ระบบในเมนู (การ์ดในแท็บ \"ระบบ\") — seed เข้า storage แค่ครั้งแรกที่ resource\n--  เริ่มทำงานเท่านั้น (ตรวจจาก storage key `_seeded`) หลังจากนั้นแก้ไข\u002Fเพิ่ม\u002Fลบ\n--  ทำผ่านคำสั่ง \u002Fmenusystem หรือ export RegisterSystem — แก้ไฟล์นี้ทีหลังจะไม่มีผลอีก\n--\n--  resource     (ไม่บังคับ) ชื่อ resource ของระบบนี้ — ถ้าใส่ไว้แล้ว resource นั้นไม่ได้\n--                start จะไม่โชว์การ์ดนี้ในเมนู เว้นแต่ always_show = true\n--  always_show  โชว์เสมอไม่สนสถานะ resource (หรือไม่ได้ใส่ resource เลยก็โชว์เสมออยู่แล้ว)\n--  category     'system' (ค่าเริ่มต้นถ้าไม่ใส่) = การ์ดระบบเต็มรูปแบบ ขึ้นแท็บ \"ระบบทั้งหมด\";\n--                'qol' = ทางลัดความสะดวก (ปักหมุด\u002Fสลับด่วน ไม่มี UI เป็นของตัวเอง) ขึ้นกริดแยก\n--                \"ความสะดวก\" ด้านล่าง — ใส่ค่าอื่นนอกจากสองตัวนี้จะถูกปฏิเสธที่ RegisterSystem\n--\n--  System cards (the \"Systems\" tab) — seeded into storage only the first time the resource\n--  starts (checked by the storage key `_seeded`); after that edit\u002Fadd\u002Fremove through\n--  \u002Fmenusystem or the RegisterSystem export — changing this file later has no effect\n--\n--  resource     (optional) the system's resource name — if set and that resource is not\n--                started, the card is hidden unless always_show = true\n--  always_show  show regardless of resource state (a card without resource is always shown)\n--  category     'system' (default) = full system card in the \"all systems\" tab;\n--                'qol' = quick convenience shortcut (pin \u002F quick toggle, no UI of its own) in the\n--                separate \"convenience\" grid below — any other value is rejected by RegisterSystem\n-- ═══════════════════════════════════════════════════════════════\nConfig.Systems = {\n    -- ตัวอย่างที่เปิดใช้จริง — ผูกกับ resource ของ X3D เอง ถ้าเซิร์ฟไม่ได้ลงไว้\n    -- การ์ดจะถูกซ่อนอัตโนมัติ (resource + always_show = false) ไม่ต้องมาลบเอง\n    -- Live examples bound to X3D resources; if the server does not run them\n    -- the card hides itself (resource + always_show = false), no cleanup needed\n    {\n        system_id   = 'x3d_mailbox',\n        label       = 'กล่องจดหมาย',\n        description = 'รับของจากทีมงานและโค้ดรับของ',\n        icon        = 'i-heroicons-envelope',\n        command     = 'mailbox',\n        resource    = 'x3d_mailbox',\n        always_show = false,\n        is_active   = true,\n        sort_order  = 1,\n        category    = 'system',\n    },\n    {\n        system_id   = 'x3d_dailylogin',\n        label       = 'เช็คอินรายวัน',\n        description = 'รับรางวัลประจำวันและโบนัสเวลาเล่น',\n        icon        = 'i-heroicons-calendar-days',\n        command     = 'dailylogin',\n        resource    = 'x3d_dailylogin',\n        always_show = false,\n        is_active   = true,\n        sort_order  = 2,\n        category    = 'system',\n    },\n    {\n        system_id   = 'qol_waypoint_clear',\n        label       = 'ล้างหมุดนำทาง',\n        description = 'ลบหมุดบนแผนที่ทิ้ง',\n        icon        = 'i-heroicons-map-pin',\n        command     = 'x3dmenu_clearwaypoint',\n        always_show = true,\n        is_active   = true,\n        sort_order  = 100,\n        category    = 'qol',\n    },\n\n    -- {\n    --     system_id   = 'inventory',\n    --     label       = 'กระเป๋าเป้',\n    --     description = 'เปิดกระเป๋าเป้ของคุณ',\n    --     icon        = 'i-heroicons-shopping-bag',\n    --     command     = 'openInventory',\n    --     badge_image = nil,\n    --     resource    = 'ox_inventory',\n    --     always_show = false,\n    --     is_active   = true,\n    --     sort_order  = 1,\n    --     category    = 'system',\n    -- },\n    -- {\n    --     system_id   = 'qol_waypoint_home',\n    --     label       = 'ปักหมุดบ้าน',\n    --     description = 'ตั้งเส้นทางไปบ้านของคุณ',\n    --     icon        = 'i-heroicons-home',\n    --     command     = 'waypointhome',\n    --     resource    = 'esx_property',\n    --     always_show = false,\n    --     is_active   = true,\n    --     sort_order  = 100,\n    --     category    = 'qol',\n    -- },\n    -- {\n    --     system_id   = 'qol_waypoint_job',\n    --     label       = 'ปักหมุดที่ทำงาน',\n    --     description = 'ตั้งเส้นทางไปจุดทำงานล่าสุด',\n    --     icon        = 'i-heroicons-briefcase',\n    --     command     = 'waypointjob',\n    --     always_show = true,\n    --     is_active   = true,\n    --     sort_order  = 101,\n    --     category    = 'qol',\n    -- },\n}\n\n-- ===== ประกาศ \u002F Announcements =====\n\n-- ประกาศหน้าแรก — seed ครั้งแรกเหมือนกัน แก้ทีหลังผ่าน \u002Fmenuannouncement\n-- Home-tab announcements, also seeded once; edit later through \u002Fmenuannouncement\n-- เช่น \u002F e.g. { title = 'ยินดีต้อนรับ', content = 'ขอบคุณที่เข้ามาเล่นกับเรา', color = '#F59E0B', is_active = true, sort_order = 1 }\nConfig.Announcements = {\n    -- { title = 'ยินดีต้อนรับ', content = 'ขอบคุณที่เข้ามาเล่นกับเรา',\n    --   image_url = nil, color = '#F59E0B', is_active = true, sort_order = 1 },\n}\n\n-- ===== อีเวนต์ \u002F Events =====\n\n-- อีเวนต์ที่มีช่วงเวลา — start_at\u002Fend_at เป็น unix timestamp (วินาที), ไม่ใส่ = ไม่จำกัดฝั่งนั้น\n-- นอกช่วงเวลาจะไม่โชว์ในพาเนลแม้ is_active = true\n-- Timed events: start_at\u002Fend_at are unix timestamps (seconds), omitted = unlimited on that side\n-- Outside the window the event is hidden in the panel even when is_active = true\n-- เช่น \u002F e.g. { title = 'อีเวนต์ทดสอบ', start_at = 1790000000, end_at = 1790100000, is_active = true, sort_order = 1 }\nConfig.Events = {\n    -- { title = 'อีเวนต์ทดสอบ', description = 'รายละเอียดอีเวนต์', image_url = nil, color = '#F59E0B',\n    --   start_at = 1790000000, end_at = 1790100000, is_active = true, sort_order = 1 },\n}\n\n-- ===== คู่มือ \u002F Guide =====\n\n-- คู่มือ\u002Fกฎเมือง — จัดกลุ่มด้วย category\n-- City guide \u002F rules, grouped by category\n-- เช่น \u002F e.g. { category = 'ทั่วไป', title = 'เริ่มต้นเล่น', content = 'ข้อความคู่มือ...', is_active = true, sort_order = 1 }\nConfig.Guide = {\n    -- { category = 'ทั่วไป', title = 'เริ่มต้นเล่น', content = 'ข้อความคู่มือ...',\n    --   image_url = nil, is_active = true, sort_order = 1 },\n}\n\n-- ===== ตั้งค่าผู้เล่น \u002F Player settings =====\n\n-- ═══════════════════════════════════════════════════════════════\n--  ตั้งค่าในพาเนล (แท็บ \"ตั้งค่า\") — เก็บฝั่ง client เท่านั้น (KVP) ไม่ผ่าน server เลย\n--  เพิ่มจาก resource อื่นตอน runtime ได้ด้วย export RegisterSetting(section, def)\n--  type: 'toggle' | 'select' | 'slider'\n--\n--  Panel settings (the \"Settings\" tab) — stored client side only (KVP), never sent to the server\n--  Other resources can add more at runtime with the RegisterSetting(section, def) export\n--  type: 'toggle' | 'select' | 'slider'\n-- ═══════════════════════════════════════════════════════════════\nConfig.Settings = {\n    {\n        -- ชื่อหมวดในแท็บตั้งค่า \u002F Section name in the settings tab\n        section = 'ทั่วไป',\n        -- สวิตช์ในหมวดนี้: id = คีย์เก็บค่า, label = ชื่อที่โชว์, default_value = ค่าเริ่มต้น\n        -- Switches in this section: id = storage key, label = shown name, default_value = default\n        items = {\n            { id = 'show_hud',       label = 'แสดง HUD',       type = 'toggle', default_value = true },\n            { id = 'show_crosshair', label = 'แสดง Crosshair', type = 'toggle', default_value = false },\n        },\n    },\n}\n",1792316309253]