เอกสาร

X3D Mailbox (โค้ดรับของ)

กล่องจดหมายออฟไลน์พร้อมของแนบ + โค้ดรับของ /redeem ฟรี ไม่ต้องลงฐานข้อมูล — ส่งไอเทมและเงินให้ผู้เล่นที่ออฟไลน์ แยกแท็บจดหมาย/ตู้นิรภัย ต่อได้ทุกเฟรมเวิร์ก

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

การติดตั้ง

  1. แตกไฟล์แล้ว เปลี่ยนชื่อโฟลเดอร์เป็น x3d_mailbox (สคริปต์อื่นเรียก export ด้วยชื่อนี้)
  2. วางใน resources/
  3. เพิ่มใน server.cfg:
ensure x3d_mailbox

เสร็จแล้ว — เก็บข้อมูลเป็นไฟล์ data/mailbox.json ในตัว resource เอง ไม่ต้องรัน SQL ใด ๆ

ผู้เล่นเปิดกล่องจดหมายด้วย /mailbox หรือปุ่ม F7 (Config.Key, ว่าง = ไม่ผูกปุ่ม)

ในพาเนล: สองแท็บ จดหมาย (หมดอายุได้) กับ ตู้นิรภัย (ช่องที่สอง แยกนับ unread/รับทั้งหมดกันคนละกล่อง — ใช้ส่งของจากโค้ดรับของได้), กดรับทีละฉบับหรือ รับทั้งหมด ในคลิกเดียว (เฉพาะแท็บที่เปิดอยู่), ลบ จดหมายที่รับของแล้ว, นับถอยหลังเวลาหมดอายุ, แลกโค้ดผ่านช่องกรอกในพาเนล และเด้งแจ้งเตือนทันทีถ้าผู้เล่นออนไลน์อยู่ตอนได้จดหมายใหม่

ไม่ต้องใช้ฐานข้อมูล ไม่ต้องใช้เฟรมเวิร์ก ไม่มีไลเซนส์ · ทำงานกับ ESX ได้ทันที และทำงานแบบ standalone ได้ถ้าไม่มีเฟรมเวิร์ก

ต่อกับเฟรมเวิร์ก — 4 adapter ใน addons/

โฟลเดอร์ หน้าที่
addons/framework/ Framework.IsBusy() (client) — เปิดกล่องจดหมายตอนนี้ไม่ได้ไหม (ตาย/pause menu); Framework.Identifier(src) / Framework.IsAdmin(src) (server) — คีย์ถาวรของผู้เล่นและสิทธิ์แอดมิน
addons/notify/ Notify(message, kind) (client) — การแจ้งเตือน
addons/reward/ Reward.CanCarry / Reward.GiveItem / Reward.GiveMoney (server) — ระบบไอเทม/เงิน
addons/storage/ Storage.Get/Set/All/Flush (server) — ที่เก็บข้อมูล (ดูหัวข้อถัดไป)

ทุกไฟล์มีค่าเริ่มต้นที่ใช้กับ ESX ได้ทันที (เช็ก GetResourceState('es_extended') เอง) และมีตัวอย่าง QBCore / ox_lib คอมเมนต์ไว้ท้ายไฟล์ให้คัดลอกทับ ไม่มีเฟรมเวิร์กเลยก็ยังเปิดกล่องจดหมายได้ แค่รับของแนบไม่ได้จนกว่าจะต่อ Reward.GiveItem/GiveMoney เอง (มี print เตือนใน console ถ้ายังไม่ได้ต่อ)

เก็บข้อมูล — JSON ค่าเริ่มต้น หรือสลับเป็น MySQL

ค่าเริ่มต้น: ไฟล์ JSON เดี่ยว (Config.Storage.File, ค่าเริ่มต้น data/mailbox.json) โหลดครั้งเดียวตอน resource เริ่ม เขียนกลับทุก Config.Storage.FlushSeconds วินาที (ค่าเริ่มต้น 30) และตอนผู้เล่นออก/resource หยุด ไฟล์เสียอ่านไม่ได้ → ย้ายไปเก็บเป็น data/mailbox.broken.json แล้วเริ่มไฟล์ใหม่ ไม่ทับทิ้งของเดิม

อยากใช้ MySQL แทน:

  1. ตั้ง Config.Storage.Driver = 'oxmysql' — ตาราง x3d_mailbox ถูกสร้างให้อัตโนมัติ ไม่ต้อง include @oxmysql
  2. รีสตาร์ท resource เป็นอันเสร็จ (จะแก้ 4 ฟังก์ชัน Storage.* ใน addons/storage/server.lua ก็ต่อเมื่อต้องการที่เก็บข้อมูลแบบเขียนเอง)

Exports

-- target = server id (number) หรือ identifier (string) ก็ได้
exports['x3d_mailbox']:SendMail(target, {
    title   = 'ของรางวัลกิจกรรม',
    body    = 'ขอบคุณที่เข้าร่วม',
    sender  = 'ทีมงาน',           -- ไม่ใส่ = ใช้ชื่อกล่องจดหมาย
    box     = 'mail',             -- 'mail' (ค่าเริ่มต้น, หมดอายุได้) หรือ 'safe' (ตู้ที่สอง)
    expires = os.time() + 86400,  -- unix timestamp, ไม่ใส่ = ไม่หมดอายุ
    items   = { { item = 'bread', count = 5 } },
    money   = { { account = 'money', amount = 500 } },
})

exports['x3d_mailbox']:SendMailAll({ title = 'ประกาศ', body = 'เซิร์ฟเวอร์จะปิดปรับปรุงคืนนี้' })

exports['x3d_mailbox']:GetMails(src)         --> { {id, title, body, sender, at, expires, read, claimed, box, items, money}, ... }
exports['x3d_mailbox']:GetUnread(src)        --> number (รวมทุกกล่อง)
exports['x3d_mailbox']:GetUnread(src, 'safe') --> number (เฉพาะตู้นิรภัย — ใส่ 'mail'/'safe' ได้)

GetMails กรองจดหมายที่หมดอายุออกให้อัตโนมัติ และแปะ label ภาษาไทยจาก Config.ItemLabels/Config.AccountLabels ลงบนสำเนาที่ส่งออกไป (ไม่แตะของจริงใน storage) GetUnread นับเฉพาะฉบับที่ยังไม่อ่านและไม่หมดอายุ คืนกลับเป็นตัวเลขเท่านั้น ไม่มี label

SendMail รับรูปแบบที่กล่องจดหมายเจ้าอื่นใช้ด้วย — subject, cash/bank, items = {{ name, count }}, expiresInDays — ย้ายจากกล่องจดหมายเก่าแค่เปลี่ยนชื่อ export

Events — server-internal (ไม่ใช่ net event)

AddEventHandler('x3d_mailbox:onSend', function(identifier, mail) end)
AddEventHandler('x3d_mailbox:onClaim', function(src, mail, ok) end)

x3d_mailbox:onSend ยิงทันทีที่จดหมายถูกสร้าง (ทั้งจาก export และคำสั่งแอดมิน)

x3d_mailbox:onClaim ยิงหลังพยายามจ่ายของเสมอ — mail.claimed เป็น true เสมอไม่ว่าจะสำเร็จหรือไม่ (ธงถูกปักไว้ก่อนจ่ายของโดยตั้งใจ กันรับซ้ำ) ต้องเช็กผลจริงที่ arg ที่ 3 — ok เป็น true เฉพาะตอนของแนบทุกชิ้นจ่ายสำเร็จไม่ throw:

AddEventHandler('x3d_mailbox:onClaim', function(src, mail, ok)
    if not ok then print(('mail %s claimed by %s but a reward failed'):format(mail.id, src)) end
end)

คำสั่งแอดมิน

/mailsend <server id หรือ identifier> <หัวข้อ> <เนื้อหา…> และ /mailsendall <หัวข้อ> <เนื้อหา…> — เรียกจาก in-game console ได้เสมอ เรียกจากผู้เล่นในเกมต้องมี ace Config.AdminAce (ค่าเริ่มต้น x3d_mailbox.admin)

ความปลอดภัยที่ทดสอบไว้

  • รับซ้ำไม่ได้ — ธง claimed ถูกเขียนลง storage ก่อนจ่ายของทุกครั้ง ไม่มี yield คั่นระหว่างเช็กกับปักธง บวกล็อก InFlight[identifier] กันสอง event ที่มาพร้อมกัน
  • ของพังไม่ล็อกผู้เล่นค้าง — การจ่ายของห่อด้วย pcall จดหมายยังถูกทำเครื่องหมายว่ารับแล้วแม้จ่ายพัง (item key พิมพ์ผิดจาก /mailsend ก็ไม่ทำให้ผู้เล่นกดรับซ้ำได้ไม่รู้จบ) — ได้ของบางส่วน (partial delivery) ผู้เล่นเห็นข้อความแจ้งแยกต่างหาก ไม่ถูกเงียบเป็น success และของที่จ่ายไปแล้วไม่ถูกย้อนคืน (กันจ่ายซ้ำ)
  • จำนวนถูกปัดเป็นจำนวนเต็มก่อนเสมอ — ของแนบที่เป็นเศษส่วนหรือติดลบเข้าไม่ถึงกระเป๋าผู้เล่น
  • คีย์ผู้เล่นเป็น identifier ถาวรเสมอ ไม่ใช่ server id
  • เพดาน Config.MaxMails ไม่ลบจดหมายที่ยังไม่ได้รับของ — ล้นแล้วทิ้งฉบับที่รับของไปแล้วหรือหมดอายุแล้วก่อน (เก่าสุดก่อน) ฉบับที่แค่อ่านแล้วแต่ยังไม่ได้รับของและยังไม่หมดอายุ ของแนบยังอยู่ ไม่ถูกลบ
  • รับทั้งหมด = 1 action ไม่ใช่ N — วนจ่ายด้วยตรรกะเดียวกับกดรับทีละฉบับ เฉพาะกล่องที่เปิดแท็บอยู่ (จดหมาย/ตู้นิรภัยไม่ปนกัน) ฉบับที่กระเป๋าไม่พอถูกข้าม ยังอยู่ให้กดรับใหม่ทีหลัง ไม่มีฉบับไหนถูกจ่ายซ้ำ
  • ลบได้เฉพาะจดหมายที่ไม่มีของค้างรับ — ต้องรับของก่อน (หรือไม่มีของแนบตั้งแต่แรก) — ข้อยกเว้นคือจดหมายที่หมดอายุแล้ว ลบได้เสมอเพราะรับของไม่ได้อีกต่อไปแล้ว (ของตกเป็นโมฆะ) — mail id ที่ client ส่งมาถูกมองหาเฉพาะในลิสต์ของผู้เล่นคนนั้นเองเท่านั้น
  • /redeem และ event รับโค้ดใช้ throttle + ล็อกเดียวกัน — สแปมคำสั่งรัว ๆ ไม่ทำให้ได้ของหลายรอบ ฟังก์ชันรับโค้ดเองก็ single-flight ต่อคน กันสอง call ชนกันแม้สลับไปใช้ storage แบบ await
  • คีย์ Config.Codes ถูกปรับให้ตรงรูปแบบอัตโนมัติตอนเริ่ม — พิมพ์คีย์ตัวเล็กหรือผสมมาก็ไม่ตกหล่นเงียบ ๆ (มีคำเตือนใน console ถ้าต้องปรับ)
  • จำกัด 1 ครั้ง/วินาที/คนต่อ action (open/read/claim/claimAll/delete/redeem แยก bucket)
pip install lupa
python tools/lua-test/x3d_mailbox_check.py

ตั้งค่า (config.lua)

ค่า ทำอะไร
Config.Command / Config.Key คำสั่งเปิด (/mailbox) และปุ่มลัด (F7)
Config.MaxMails เก็บต่อคนได้กี่ฉบับ
Config.AdminAce ace ที่ใช้สั่ง /mailsend /mailsendall
Config.Storage.File / .FlushSeconds ที่เก็บไฟล์ JSON และความถี่ flush
Config.ItemLabels / Config.AccountLabels ชื่อไทยที่โชว์ในกล่องจดหมาย
Config.ItemImage path รูปไอเทมของ inventory ที่เซิร์ฟใช้ ({item} = ชื่อไอเทม), ว่าง = ไม่โชว์รูป
Config.ExpiryWarnHours จดหมายเหลือเวลาน้อยกว่านี้ (ชั่วโมง) โชว์นับถอยหลังสีแดงในพาเนล (ค่าเริ่มต้น 24)
Config.CanCarryExport export ของ inventory ที่เซิร์ฟใช้ สำหรับเช็กว่ากระเป๋ารับไหว — ว่าง = ใช้ ESX canCarryItem · ขึ้น "กระเป๋าเต็ม" ทั้งที่ยังว่าง ให้ตั้งค่านี้
  • Config.DefaultExpireDays — จดหมายที่ส่งโดยไม่ระบุวันหมดอายุ จะหมดอายุเมื่อครบกี่วันที่ตั้งไว้ (ตู้นิรภัยไม่หมดอายุ)
  • Config.CheckCarry — false = รับของได้แม้กระเป๋าเต็ม
  • Config.NotifyUnreadOnJoin — แจ้งจำนวนจดหมายที่ยังไม่อ่านตอนผู้เล่นโหลดเข้าเกม
  • ชื่อไอเทมที่ไม่มีใน Config.ItemLabels จะใช้ชื่อจากเฟรมเวิร์กแทน

โค้ดรับของ (redeem code)

ผู้เล่นพิมพ์ /redeem <โค้ด> หรือกรอกโค้ดในช่องบนพาเนลกล่องจดหมายเลยก็ได้ แล้วของจะถูก ส่งเป็นจดหมาย เข้ากล่อง — กระเป๋าเต็มก็ไม่มีวันหาย เพราะไปรออยู่ในจดหมาย

โค้ดถาวรใส่ใน config.lua:

Config.Codes = {
    WELCOME2026 = {
        maxUses       = 0,        -- 0 = ไม่จำกัดจำนวนครั้งทั้งเซิร์ฟ
        oncePerPlayer = true,     -- คนละครั้งเดียว (ค่าเริ่มต้น)
        title         = 'ยินดีต้อนรับ',
        items         = { { item = 'bread', count = 2 } },
        money         = { { account = 'bank', amount = 1000 } },
    },
    EVENTONLY = {
        maxUses = 100,            -- 100 คนแรกเท่านั้น
        expires = 1790000000,     -- เวลา unix ที่โค้ดหมดอายุ
        box     = 'safe',         -- ส่งเข้าตู้นิรภัยแทนกล่องจดหมาย (ไม่บังคับ, ค่าเริ่มต้น 'mail')
        money   = { { account = 'money', amount = 5000 } },
    },
}

สร้างโค้ดตอนรัน (หายเมื่อรีสตาร์ท) จาก resource อื่น:

exports['x3d_mailbox']:AddCode('EVENT2026', { maxUses = 50, money = { { account = 'bank', amount = 2000 } } })
exports['x3d_mailbox']:RemoveCode('EVENT2026')
exports['x3d_mailbox']:GetCodeUsage('EVENT2026')   --> { uses = 12, by = { ['license:xxx'] = true } }
exports['x3d_mailbox']:Redeem(src, 'EVENT2026')    --> ok, reason

AddEventHandler('x3d_mailbox:onRedeem', function(src, code, def) end)

ยอดใช้โค้ดเก็บอยู่ในที่เดียวกับจดหมาย (คีย์ระบบ __redeem) — อยู่ครบหลังรีสตาร์ท และคำสั่งกระจายจดหมายทั้งเซิร์ฟไม่แตะคีย์นี้

ช่วยเหลือ

Discord: discord.gg/x3d-developer · เว็บ: x3d-developer.com

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