วิธีสร้าง Command FiveM ด้วย RegisterCommand

RegisterCommand() คือ Native ของ FiveM สำหรับสร้าง Command ที่ผู้เล่นหรือ Server Console สามารถเรียกใช้งานได้ เช่น /car, /heal, /coords, /job หรือ Command สำหรับ Admin

ตัวอย่างพื้นฐานที่สุดใน Lua คือ

RegisterCommand('hello', function(source, args, rawCommand)
    print('Hello FiveM')
end, false)

เมื่อผู้เล่นพิมพ์

/hello

Function ที่ลงทะเบียนไว้จะทำงาน

RegisterCommand() มีรูปแบบหลักคือ

RegisterCommand(
    commandName,
    handler,
    restricted
)

โดยมี 3 ส่วนสำคัญคือ

commandName
→ ชื่อ Command

handler
→ Function ที่ทำงานเมื่อ Command ถูกเรียก

restricted
→ จำกัดด้วย ACE Permission หรือไม่

FiveM Developer ควรใช้ RegisterCommand() แทนระบบ Command แบบเก่าที่อาศัย Event อย่าง chatMessage หรือ rconCommand เพราะ Cfx.re แนะนำ RegisterCommand() และรองรับระบบ ACL/ACE รวมถึง Console และ Core Functionality อื่นของ FiveM

① RegisterCommand FiveM คืออะไร

RegisterCommand() ใช้ประกาศ Command ใหม่ให้ FiveM รู้จัก

ตัวอย่าง

RegisterCommand(
    'test',
    function()
        print('Command executed')
    end,
    false
)

จากนั้นใช้

/test

Command จะเรียก Handler ที่กำหนดไว้

Concept คือ

Player / Console
↓
Command
↓
RegisterCommand
↓
Handler
↓
Script Logic

② Syntax ของ RegisterCommand

รูปแบบพื้นฐานคือ

RegisterCommand(
    commandName,
    handler,
    restricted
)

ตัวอย่าง

RegisterCommand(
    'hello',
    function(source, args, rawCommand)
        print('Hello')
    end,
    false
)

Parameter สำคัญคือ

source
args
rawCommand

ซึ่งควรเข้าใจให้ชัดก่อนสร้าง Command จริง

③ commandName คืออะไร

คือชื่อ Command

ตัวอย่าง

RegisterCommand(
    'car',
    function()
    end,
    false
)

ผู้เล่นใช้

/car

ถ้าสร้าง

RegisterCommand(
    'coords',
    function()
    end,
    false
)

ก็ใช้

/coords

โดยทั่วไปไม่ต้องใส่ / ใน RegisterCommand()

ใช้เพียง

coords

ไม่ใช่

/coords

④ Handler คืออะไร

Handler คือ Function ที่ทำงานเมื่อ Command ถูกเรียก

ตัวอย่าง

RegisterCommand(
    'hello',
    function(source, args, rawCommand)

        print(
            'Hello FiveM'
        )

    end,
    false
)

Code ภายใน Function คือ Command Logic

สามารถเรียก

  • Native

  • Function

  • Event

  • Export

  • Framework API

  • Database Logic ฝั่ง Server

ได้ตาม Context

⑤ source คืออะไร

source มีความหมายสำคัญโดยเฉพาะฝั่ง Server

ตัวอย่าง

RegisterCommand(
    'whoami',
    function(source)
        print(
            'Source:',
            source
        )
    end,
    false
)

ถ้า Player เรียก Server Command

source
=
Player Server ID

จึงสามารถใช้หา Player Name หรือ Player Object ต่อได้

⑥ source ฝั่ง Server Console เป็นอะไร

สำหรับ Server-side RegisterCommand() ค่า source สามารถใช้แยก Player กับ Console ได้

Cfx.re ยก Pattern ว่า

if source > 0 then
    print('Player command')
else
    print('Console command')
end

ดังนั้น Concept คือ

source > 0
→ Player

source == 0
→ Server Console

มีประโยชน์มากสำหรับ Admin Command

⑦ ตัวอย่าง Command ที่ใช้ได้เฉพาะ Console

RegisterCommand(
    'servertest',
    function(source)

        if source ~= 0 then
            return
        end

        print(
            'Command executed from console'
        )

    end,
    false
)

Player พยายามใช้

/servertest

ก็จะถูก Return ออก

เหมาะกับ Server Maintenance Command บางประเภท

⑧ args คืออะไร

args เป็น Table ของ Arguments ที่ผู้ใช้ใส่หลังชื่อ Command

ตัวอย่าง

/car sultan

ใน Handler

args[1]

จะเป็นประมาณ

sultan

ตัวอย่าง

RegisterCommand(
    'test',
    function(source, args)

        print(args[1])

    end,
    false
)

ใช้

/test hello

จะได้

hello

⑨ args หลายตัวทำอย่างไร

สมมติ Command

/pay 15 500

จะได้ประมาณ

args[1] = 15
args[2] = 500

ตัวอย่าง

RegisterCommand(
    'pay',
    function(source, args)

        local target =
            args[1]

        local amount =
            args[2]

        print(
            target,
            amount
        )

    end,
    false
)

แต่ค่าใน args ควรถูก Validate ก่อนใช้

⑩ args เป็น String หรือ Number

Arguments จาก Command ควรถือว่าเป็นข้อมูลข้อความที่ต้อง Parse ตาม Use Case

ถ้าต้องการ Number ใช้

local target =
    tonumber(args[1])

local amount =
    tonumber(args[2])

จากนั้นตรวจ

if not target then
    return
end

if not amount then
    return
end

อย่าเอา args[1] ไปใช้เป็น Number โดยไม่ตรวจ

⑪ rawCommand คืออะไร

rawCommand คือข้อความ Command ที่ผู้ใช้ป้อนในรูปแบบเต็ม

สมมติ

/message 15 hello world

args ถูกแยกเป็นแต่ละ Argument

แต่ rawCommand จะมีข้อมูล Command ในรูปแบบดิบที่ถูกป้อน

มีประโยชน์กับระบบที่ต้อง Parse ข้อความเอง เช่น

  • Announcement

  • Message

  • Reason

  • Admin Note

แต่สำหรับ Command ง่าย ๆ อาจไม่จำเป็นต้องใช้

⑫ ตัวอย่าง args พร้อม Default Value

ตัวอย่างจากแนวทาง Cfx.re สามารถใช้

local vehicleName =
    args[1] or 'adder'

ดังนั้น

/car sultan

ได้

sultan

แต่ถ้าใช้เพียง

/car

จะใช้ Default

adder

นี่เป็น Pattern ที่ดีสำหรับ Optional Arguments

⑬ restricted คืออะไร

Parameter ตัวที่สามของ RegisterCommand() คือ Boolean

false

หรือ

true

ตัวอย่าง

RegisterCommand(
    'test',
    function()
    end,
    false
)

หมายถึงไม่ได้ใช้ Automatic ACE Restriction ของ Command นี้

แต่ถ้าเป็น

RegisterCommand(
    'admin',
    function()
    end,
    true
)

Command จะถูกจำกัดด้วย ACE Permission

⑭ restricted = false หมายถึงอะไร

ตัวอย่าง

RegisterCommand(
    'coords',
    function()
    end,
    false
)

Command ไม่ถูกบังคับด้วย ACE Object ที่ RegisterCommand() สร้างจากชื่อ Command

ไม่ได้หมายความว่า Command ต้องเปิดให้ทุกคนเสมอไป

Developer ยังสามารถเขียน Permission Check เองภายใน Handler ได้

แต่ถ้าต้องการใช้ ACE System โดยตรง restricted = true เป็นวิธีที่สะอาดกว่าในหลายกรณี

⑮ restricted = true หมายถึงอะไร

สมมติ

RegisterCommand(
    'kick',
    function()
        -- logic
    end,
    true
)

Cfx.re ระบุว่า Restricted Command จะใช้ ACL Object ในรูปแบบ

command.kick

ผู้ใช้ต้องได้รับ Permission ของ Object นี้จึงจะ Execute Command ได้

ดังนั้น

Command:
kick

ACE Object:
command.kick

⑯ ACE Permission คืออะไร

ACE เป็น Permission System ของ FiveM Server

ใช้ควบคุมว่า Principal หรือ Identifier ใดมีสิทธิ์ใช้ Object อะไร

แนวคิดคือ

Player / Group
↓
ACE Permission
↓
command.xxx
↓
อนุญาต / ปฏิเสธ

เหมาะกับ Admin Commands ที่ไม่ควรเปิดให้ Player ทุกคน

⑰ ตัวอย่าง ACE สำหรับ Command

สมมติ Server มี

RegisterCommand(
    'admincar',
    function(source)
        -- admin logic
    end,
    true
)

Command Object คือ

command.admincar

ใน server.cfg สามารถออกแบบ Permission เช่น

add_ace group.admin command.admincar allow

จากนั้นสมาชิก group.admin ที่ถูกกำหนดอย่างถูกต้องจึงสามารถใช้ Command ได้

⑱ ทำไม restricted ดีกว่าเช็กชื่อ Player

ไม่ควรเขียน Permission แบบ

if GetPlayerName(source) ==
    'John' then

    -- admin
end

เพราะชื่อ Player ไม่ใช่ระบบ Permission ที่เหมาะสม

ควรใช้

  • ACE

  • Framework Permission

  • Identifier/Role System

  • Admin Resource

ตาม Architecture

restricted = true ทำให้ใช้ ACE Integration ของ FiveM ได้ตรง ๆ

⑲ RegisterCommand ใช้ฝั่ง Client ได้ไหม

ได้

ตัวอย่าง client.lua

RegisterCommand(
    'coords',
    function()

        local ped =
            PlayerPedId()

        local coords =
            GetEntityCoords(ped)

        print(coords)

    end,
    false
)

Player ใช้

/coords

แล้ว Client ของ Player คนนั้นจะอ่านตำแหน่ง Local Player

⑳ Client Command เหมาะกับอะไร

ตัวอย่าง

  • เปิด UI

  • Debug Client

  • Animation

  • Camera

  • Local Coordinates

  • Toggle HUD

  • Toggle Client Feature

ตัวอย่าง

RegisterCommand(
    'togglehud',
    function()

        TriggerEvent(
            'hud:toggle'
        )

    end,
    false
)

ถ้าเป็น Presentation Logic แบบ Local Client ก็เหมาะกับ Client Command

㉑ Server Command เหมาะกับอะไร

Server Command เหมาะกับสิ่งที่ต้องการ Server Authority เช่น

  • Admin

  • Player Management

  • Money

  • Inventory

  • Job

  • Server Maintenance

  • Database Action

  • Global Announcement

ตัวอย่าง

RegisterCommand(
    'playername',
    function(source)

        if source <= 0 then
            return
        end

        local name =
            GetPlayerName(source)

        print(name)

    end,
    false
)

㉒ Command ที่เกี่ยวกับ Money ควรอยู่ฝั่งไหน

ควรตัดสินผลฝั่ง Server

ไม่ควรทำ

Client Command
↓
เพิ่มเงินจริงใน Client

แล้วถือว่าเป็น Economy Authority

ระบบ Money ต้องใช้ Server-side Framework/Economy Data

ถ้ามี Client Command เป็นตัว Trigger Request ก็ยังต้องให้ Server Validate ต่อ

㉓ ตัวอย่าง Client Command ส่ง Server Event

Client

RegisterCommand(
    'finishjob',
    function()

        TriggerServerEvent(
            'job:finish'
        )

    end,
    false
)

Server

RegisterNetEvent(
    'job:finish',
    function()

        local src =
            source

        -- validate job
        -- validate position
        -- calculate reward

    end
)

Command เป็นเพียง Input Method

Server Event ยังต้อง Validate ตามปกติ

㉔ Command ไม่ใช่ Security Boundary

นี่สำคัญมาก

สมมติ Client Command /finishjob เรียก

TriggerServerEvent(
    'job:finish'
)

ถึงคุณจะซ่อน Command หรือไม่บอก Player ว่ามี Command นี้อยู่ ก็ไม่ได้ทำให้ Server Event ปลอดภัย

Server Event ต้อง Validate เหมือนเดิม

ดังนั้น

Command Hidden
≠
Secure Event

㉕ ตัวอย่าง Command พร้อมตรวจ Argument

RegisterCommand(
    'teleportid',
    function(source, args)

        local target =
            tonumber(args[1])

        if not target then
            print(
                'Invalid player ID'
            )

            return
        end

        print(
            'Target:',
            target
        )

    end,
    false
)

ก่อนใช้ Argument ต้องตรวจว่ามีและ Convert สำเร็จ

㉖ ตรวจ Amount อย่างไร

ตัวอย่าง

local amount =
    tonumber(args[2])

if not amount then
    return
end

if amount <= 0 then
    return
end

อาจเพิ่ม Maximum

if amount > 100000 then
    return
end

ตาม Business Rule

อย่าเชื่อ Command Arguments เพียงเพราะผู้ใช้เป็น Player จริง

㉗ Command Message หลายคำทำอย่างไร

สมมติ

/announce Server restart in 10 minutes

args จะถูกแยกเป็น

Server
restart
in
10
minutes

สามารถรวมกลับ

local message =
    table.concat(
        args,
        ' '
    )

ตัวอย่าง

RegisterCommand(
    'announce',
    function(source, args)

        local message =
            table.concat(
                args,
                ' '
            )

        print(message)

    end,
    true
)

เหมาะกับ Announcement Command

㉘ ควรตรวจ Empty Message

ตัวอย่าง

if #args == 0 then
    return
end

หรือ

local message =
    table.concat(
        args,
        ' '
    )

if message == '' then
    return
end

ป้องกัน Command ที่ไม่มี Argument ตาม Requirement

㉙ ตัวอย่าง Announcement Server Command

RegisterCommand(
    'announce',
    function(source, args)

        local message =
            table.concat(
                args,
                ' '
            )

        if message == '' then
            return
        end

        TriggerClientEvent(
            'chat:addMessage',
            -1,
            {
                args = {
                    'SERVER',
                    message
                }
            }
        )

    end,
    true
)

เมื่อ restricted = true ควรกำหนด ACE ของ

command.announce

ให้ Admin Group ที่ต้องการ

㉚ ส่งหา Player คนเดียวด้วย Command

ตัวอย่าง Server Command

RegisterCommand(
    'message',
    function(source, args)

        local target =
            tonumber(args[1])

        if not target then
            return
        end

        local message =
            table.concat(
                args,
                ' ',
                2
            )

        if message == '' then
            return
        end

        TriggerClientEvent(
            'chat:addMessage',
            target,
            {
                args = {
                    'SERVER',
                    message
                }
            }
        )

    end,
    true
)

ควรตรวจด้วยว่า Target Player มีอยู่จริงตามระบบก่อนใช้งานจริง

㉛ ตรวจ Player มีอยู่หรือไม่

ตัวอย่างหนึ่งคือ

local targetName =
    GetPlayerName(target)

if not targetName then
    return
end

จากนั้นจึงทำ Action

อย่าสมมติว่า Player ID ที่ Admin ใส่มาจะมีอยู่เสมอ

㉜ RegisterCommand ใช้จาก Server Console ได้ไหม

Server-side Command สามารถถูกเรียกจาก Server Console ได้

Cfx.re ระบุว่าหนึ่งในข้อดีของ RegisterCommand() คือรองรับ Console Usage และ Integrated ACL System

ดังนั้น Handler ควรออกแบบว่าต้องการรองรับ

Player
Console
หรือทั้งสอง

อย่างชัดเจน

㉝ Command ที่ Console ใช้ต้องระวัง source

หาก Console เรียก Command

source = 0

ดังนั้น Code อย่าง

GetPlayerName(source)

อาจไม่เหมาะหาก source คือ Console

ควรตรวจ

if source == 0 then
    -- console path
else
    -- player path
end

ก่อนใช้ Player-specific Logic

㉞ ตัวอย่างรองรับทั้ง Player และ Console

RegisterCommand(
    'wherefrom',
    function(source)

        if source == 0 then
            print(
                'Executed from console'
            )

            return
        end

        print(
            'Executed by player:',
            source
        )

    end,
    false
)

Flow ชัดเจนและลด Error

㉟ Client RegisterCommand มี source แบบ Server ไหม

Parameter Signature คล้ายกัน แต่ source มีความหมายสำคัญเป็น Player ID โดยเฉพาะบน Server

สำหรับ Client Command Developer มักไม่จำเป็นต้องใช้ source

จึงเห็นรูปแบบ

RegisterCommand(
    'coords',
    function()

        -- client logic

    end,
    false
)

ได้บ่อย

㊱ RegisterCommand กับ RegisterKeyMapping

FiveM สามารถสร้าง Command แล้วผูกกับ Key Binding

ตัวอย่าง Concept

RegisterCommand(
    '+handsup',
    function()
        handsUp = true
    end,
    false
)

RegisterCommand(
    '-handsup',
    function()
        handsUp = false
    end,
    false
)

แล้วผูก

RegisterKeyMapping(
    '+handsup',
    'Hands Up',
    'keyboard',
    'i'
)

นี่ทำให้ผู้เล่นสามารถปรับ Key Binding ผ่าน Settings ได้ตามระบบ FiveM

㊲ ทำไม KeyMapping ใช้ Command

FiveM Input Mapping สามารถผูก Key กับ Command

Flow คือ

Keyboard
↓
RegisterKeyMapping
↓
Command
↓
RegisterCommand Handler

จึงไม่ต้อง Hardcode Keyboard Native สำหรับทุก Feature

เหมาะกับ

  • Hands Up

  • Push-to-talk Feature

  • Menu

  • Interaction

  • Toggle UI

ตาม Use Case

㊳ +command และ -command คืออะไร

Pattern เช่น

+handsup
-handsup

ใช้สร้าง Press/Release Behavior

ตัวอย่าง

RegisterCommand(
    '+handsup',
    function()
        handsUp = true
    end,
    false
)

RegisterCommand(
    '-handsup',
    function()
        handsUp = false
    end,
    false
)

กดปุ่ม

+handsup

ปล่อยปุ่ม

-handsup

เหมาะกับ Action ที่ต้องรู้ Press State

㊴ RegisterCommand กับ Chat

ผู้เล่นมักใช้ Command จาก Chat เช่น

/car
/coords
/help

แต่ RegisterCommand() ไม่ได้ผูกกับ chatMessage Event แบบเก่าโดยตรง

Cfx.re แนะนำใช้ RegisterCommand() เพราะระบบ Command ถูกจัดการใน Core และรองรับ ACL/Console ได้ดีกว่าแนวทางเก่า

㊵ ไม่ควรใช้ chatMessage สร้าง Command ใหม่

เอกสาร Cfx.re ระบุชัดว่าควรใช้ RegisterCommand() แทนการจับ chatMessage เพื่อ Parse Command เอง

เหตุผลรวมถึง

  • Integrated ACL

  • Console Usage

  • Core Functionality

  • Command Handling ที่เป็นมาตรฐาน

ดังนั้น Resource ใหม่ควรเริ่มด้วย RegisterCommand()

㊶ rconCommand ยังควรใช้ไหม

Cfx.re ระบุว่า Event

rconCommand

เป็น Deprecated และแนะนำให้ใช้ REGISTER_COMMAND พร้อม restricted Flag แทน

ดังนั้น Resource ใหม่ไม่ควรเริ่มจาก API เก่านี้หากไม่มีเหตุผลเรื่อง Compatibility

㊷ RegisterCommand กับ ESX

ESX Resource สามารถใช้ FiveM RegisterCommand() ได้ตามปกติ

หลังจาก Handler ทำงาน สามารถเรียก ESX API เช่น

GetPlayerFromId
Player Data
Permission
Job

ตาม Version ของ Framework

แต่ Command ที่เกี่ยวข้องกับ Money/Inventory ต้องยัง Validate ฝั่ง Server

㊸ RegisterCommand กับ QBCore

QBCore ก็สามารถใช้ RegisterCommand() ของ FiveM ได้

QBCore ยังมี Command Layer/API ของ Framework เองในบางระบบ

ถ้า Resource ต้องเป็น Framework-independent การใช้ FiveM Core RegisterCommand() แล้วเชื่อม Framework ภายในอาจช่วยลด Coupling

แต่เลือกตาม Architecture ของ Server

㊹ RegisterCommand กับ Qbox

หลักเดียวกัน

FiveM Core Command ยังใช้ได้ไม่ว่า Server จะเป็น

ESX
QBCore
Qbox
Standalone

Framework สามารถเพิ่ม Permission และ Player Data Layer เพิ่มเติมด้านบน

แต่พื้นฐาน Command ของ FiveM ยังเหมือนเดิม

㊺ Command สำหรับ Admin ควรใช้ Client หรือ Server

Server

โดยเฉพาะ Action เช่น

  • Kick

  • Ban

  • Give Money

  • Give Item

  • Set Job

  • Delete Character

  • Server Management

Permission Check และ Action สำคัญควรอยู่ Server-side

Client สามารถมี UI หรือ Input แต่ไม่ควรเป็น Authority

㊻ ตัวอย่าง Admin Command ที่ไม่ดี

RegisterCommand(
    'givemoney',
    function(source, args)

        local amount =
            tonumber(args[1])

        TriggerServerEvent(
            'admin:giveMoney',
            amount
        )

    end,
    false
)

ถ้า Server Event ไม่ตรวจ Admin Permission ผู้เล่นสามารถพยายามเรียก Event โดยตรงได้

Command ไม่ได้ป้องกัน Event

㊼ Admin Command ที่ดีกว่า

สร้าง Server Command

RegisterCommand(
    'givemoney',
    function(source, args)

        local target =
            tonumber(args[1])

        local amount =
            tonumber(args[2])

        if not target
        or not amount then
            return
        end

        if amount <= 0 then
            return
        end

        -- server-side economy action

    end,
    true
)

แล้วใช้ ACE Permission

command.givemoney

หรือ Permission Layer ของ Framework ตามระบบ

㊽ restricted=true เพียงพอทุกระบบไหม

สำหรับ ACE-protected Command มันช่วยควบคุมว่าใคร Execute Command ได้

แต่ Business Logic ยังควร Validate Input เช่น

  • Target

  • Amount

  • Player Exists

  • Range

  • Resource State

ACE ตอบคำถามว่า

ใครใช้ Command ได้?

ไม่ใช่

Arguments ทุกอย่างถูกต้องไหม?

จึงต้องทำทั้ง Permission และ Validation

㊾ Command Name ควรตั้งอย่างไร

ชื่อควร

  • สั้น

  • จำง่าย

  • สื่อความหมาย

  • ไม่ชนกับ Resource อื่นง่ายเกินไป

ตัวอย่าง

coords
car
repair
announce
setjob

สำหรับ Internal/Admin Command ที่เฉพาะ Resource อาจใช้ Prefix

garage_debug
inventory_reload

ตาม Convention ของ Server

㊿ ไม่ควรสร้าง Command Generic เกินไป

เช่น

/do
/run
/action
/x

แล้วใช้ Argument แรกตัดสิน Action จำนวนมาก

/do giveMoney
/do giveItem
/do kick
/do ban

Command แบบ Generic มากเกินไปทำให้

  • Permission ยาก

  • Audit ยาก

  • Help ยาก

  • Security Review ยาก

แยก Command ตาม Responsibility มักชัดกว่า

51 ควรมี Usage Message ไหม

ควรสำหรับ Command ที่มี Arguments

ตัวอย่าง

RegisterCommand(
    'pay',
    function(source, args)

        if not args[1]
        or not args[2] then

            print(
                'Usage: /pay [id] [amount]'
            )

            return
        end

    end,
    false
)

สำหรับ Player Command อาจส่งข้อความผ่าน Chat/Notification แทน print()

UX จะดีกว่า

52 Command ควรมี Error Message ไหม

ควรมีข้อความที่ช่วยผู้ใช้แก้ไข

เช่น

Player ID ไม่ถูกต้อง
จำนวนเงินต้องมากกว่า 0
ไม่พบ Player
คุณไม่มีสิทธิ์ใช้ Command นี้

แต่ไม่ควรส่งข้อมูลภายในที่ Sensitive เช่น

  • SQL Error

  • Secret

  • Stack Trace

  • Token

ให้ Player โดยไม่จำเป็น

53 Command Logging สำคัญไหม

สำหรับ Admin Command สำคัญ ควรพิจารณา Log

ตัวอย่าง

Admin Source
Command
Target
Action
Amount
Timestamp

ช่วย Audit เมื่อเกิดปัญหา

แต่หลีกเลี่ยงการ Log Secret หรือข้อมูล Sensitive โดยไม่จำเป็น

54 Command Spam มีผลไหม

ได้

Player สามารถเรียก Command ซ้ำเร็ว ๆ ได้ในบางกรณี

ถ้า Handler ทำงานหนัก เช่น

  • Database Query

  • Entity Spawn

  • HTTP Request

  • Inventory Transaction

ควรพิจารณา Cooldown/Rate Limit ตาม Use Case

RegisterCommand() ไม่ได้ Rate Limit Business Logic ให้คุณทั้งหมดโดยอัตโนมัติ

55 Command Query Database ทุกครั้งได้ไหม

ได้ถ้าจำเป็น

แต่ต้องดู Frequency

Command Admin ที่ใช้วันละไม่กี่ครั้งต่างจาก Public Command ที่ Player หลายร้อยคน Spam ได้

ถ้าข้อมูล Cache ได้ ควรพิจารณา Cache

ถ้า Query ช้า ควร Optimize SQL

อย่าแก้ Performance ด้วยการเพิ่ม Wait() ใน Command แบบสุ่ม

56 RegisterCommand มีผลต่อ Performance ไหม

การ Register Command จำนวนหนึ่งไม่ใช่ปัญหาหลัก

Cost จะเกิดตอน Handler ทำงาน

เช่น

Command
↓
Loop 1000 Entities
↓
Database Query
↓
HTTP Request
↓
Broadcast Event

ถ้า Logic หนัก Command ก็หนัก

จึงต้อง Optimize Handler ไม่ใช่กลัวตัว RegisterCommand() เอง

57 วิธี Debug RegisterCommand

เริ่มจาก

RegisterCommand(
    'test',
    function(source, args, rawCommand)

        print(
            'Command called'
        )

        print(
            'source:',
            source
        )

        print(
            'arg1:',
            args[1]
        )

        print(
            'raw:',
            rawCommand
        )

    end,
    false
)

จากนั้นลอง

/test hello

ดู F8 หากเป็น Client Command

ดู Server Console หากเป็น Server Command

58 RegisterCommand ไม่ทำงานตรวจอะไร

ตรวจตามลำดับ

① Resource Start หรือยัง
② client.lua/server.lua ถูกโหลดหรือไม่
③ Command Name ถูกไหม
④ มี Error ก่อน RegisterCommand หรือไม่
⑤ Command ชนกับ Resource อื่นหรือไม่
⑥ restricted=true แต่ไม่มี Permission หรือไม่
⑦ ใช้ Client/Server Context ถูกหรือไม่
⑧ ดู F8 หรือ Server Console แล้วหรือยัง

ถ้า Restricted Command ใช้ไม่ได้ ให้ตรวจ ACE Configuration เพิ่มด้วย

59 Checklist ก่อนสร้าง FiveM Command

ก่อนสร้าง Command ใหม่ ให้ถาม

① Command ใช้ Client หรือ Server?
② Command Name คืออะไร?
③ Player ใช้ได้หรือ Console เท่านั้น?
④ มี Arguments อะไร?
⑤ ต้อง tonumber หรือ Validate Type ไหม?
⑥ ต้องตรวจ Range ไหม?
⑦ Target Player มีอยู่จริงไหม?
⑧ เป็น Admin Command หรือไม่?
⑨ ใช้ restricted=true + ACE ได้ไหม?
⑩ ต้องใช้ Framework Permission หรือไม่?
⑪ Handler มี Database/HTTP งานหนักไหม?
⑫ ต้อง Rate Limit หรือไม่?
⑬ Action สำคัญอยู่ Server-side หรือยัง?
⑭ ต้อง Log หรือไม่?
⑮ ต้องมี Usage/Error Message หรือไม่?

Checklist นี้ช่วยให้ Command พร้อมใช้งานจริงมากกว่าเขียนแค่ให้ /command ทำงานได้

⑥⓪ ตัวอย่าง RegisterCommand สำหรับมือใหม่

client.lua

สร้าง /coords

RegisterCommand(
    'coords',
    function()

        local ped =
            PlayerPedId()

        local coords =
            GetEntityCoords(ped)

        print(
            ('x: %.2f y: %.2f z: %.2f')
            :format(
                coords.x,
                coords.y,
                coords.z
            )
        )

    end,
    false
)

Command นี้เหมาะกับ Client เพราะ Coordinates ของ Local Player สามารถอ่านด้วย Client Native โดยตรง

server.lua

สร้าง /whoami

RegisterCommand(
    'whoami',
    function(source)

        if source == 0 then
            print(
                'Server Console'
            )

            return
        end

        local playerName =
            GetPlayerName(source)

        print(
            ('Player %s: %s')
            :format(
                source,
                playerName or 'unknown'
            )
        )

    end,
    false
)

ตัวอย่างนี้ช่วยให้เห็นความแตกต่างระหว่าง Client และ Server Commands

ตัวอย่าง Restricted Admin Command

server.lua

RegisterCommand(
    'announce',
    function(source, args)

        local message =
            table.concat(
                args,
                ' '
            )

        if message == '' then
            return
        end

        TriggerClientEvent(
            'chat:addMessage',
            -1,
            {
                args = {
                    'SERVER',
                    message
                }
            }
        )

    end,
    true
)

เมื่อ Command คือ

announce

ACE Object คือ

command.announce

จากนั้นกำหนด Permission ให้ Group ที่ต้องการใน Server Configuration

Flow คือ

Admin
↓
/announce
↓
RegisterCommand
↓
ACE ตรวจสิทธิ์
↓
Handler
↓
TriggerClientEvent
↓
ข้อความถึงผู้เล่น

นี่เป็น Pattern ที่เหมาะกับ Command ที่ต้องจำกัดสิทธิ์

คำถามที่พบบ่อยเกี่ยวกับ FiveM RegisterCommand

RegisterCommand คืออะไร

คือ Native สำหรับสร้าง Player/Console Command ใน FiveM

Syntax คืออะไร

RegisterCommand(
    commandName,
    handler,
    restricted
)

source คืออะไร

บน Server คือ Player ID ของผู้ที่เรียก Command และโดยทั่วไป 0 หมายถึง Server Console

args คืออะไร

Table ของ Arguments ที่ใส่ต่อท้าย Command

rawCommand คืออะไร

ข้อความ Command ที่ถูกป้อนในรูปแบบดิบ

restricted คืออะไร

Boolean ที่บอกว่า Command ต้องใช้ ACE Restriction หรือไม่

restricted=true ใช้ Permission อะไร

Cfx.re ใช้ Object รูปแบบ

command.commandName

เช่น /kick

command.kick

RegisterCommand ใช้ Client ได้ไหม

ได้

RegisterCommand ใช้ Server ได้ไหม

ได้

Command Client กับ Server ต่างกันอย่างไร

Client Command เหมาะกับ Local Gameplay/UI ส่วน Server Command เหมาะกับ Authority, Database, Admin และ Player Management

RegisterCommand ใช้จาก Console ได้ไหม

Server-side Command รองรับ Server Console ตามระบบ FiveM

ใช้ chatMessage สร้าง Command ดีไหม

Cfx.re แนะนำใช้ RegisterCommand() แทน เพราะรองรับ ACL และ Core Command Functionality ดีกว่า

rconCommand ยังควรใช้ไหม

เอกสารปัจจุบันระบุว่า rconCommand Deprecated และแนะนำ RegisterCommand() พร้อม restricted Flag แทน

Command Admin ควรอยู่ Client หรือ Server

Server เป็นหลักสำหรับ Permission และ Action สำคัญ

restricted=true ทำให้ Command ปลอดภัยทั้งหมดไหม

ช่วยควบคุม Permission แต่ยังต้อง Validate Arguments และ Business Logic

RegisterCommand ใช้กับ ESX ได้ไหม

ได้

RegisterCommand ใช้กับ QBCore ได้ไหม

ได้

RegisterCommand ใช้กับ Qbox ได้ไหม

ได้

RegisterCommand ผูก Key ได้ไหม

ได้ โดยใช้ RegisterKeyMapping() ร่วมกับ Command ตามระบบ Key Mapping ของ FiveM

สรุปวิธีสร้าง Command FiveM ด้วย RegisterCommand

RegisterCommand() คือวิธีมาตรฐานสำหรับสร้าง Command ใน FiveM

โครงสร้างพื้นฐานคือ

RegisterCommand(
    'commandname',
    function(source, args, rawCommand)

        -- command logic

    end,
    false
)

สิ่งที่ต้องจำคือ

source
→ ใครเรียก Command โดยเฉพาะฝั่ง Server

args
→ Arguments ที่ผู้ใช้ส่งมา

rawCommand
→ Command ดิบ

restricted
→ ACE Restriction

ถ้าเป็น Command ทั่วไปใช้

false

ตาม Requirement

ถ้าเป็น Command ที่ต้องใช้ ACE Permission สามารถใช้

true

แล้วกำหนดสิทธิ์ผ่าน Object รูปแบบ

command.commandName

และหลักสำคัญที่สุดสำหรับ Admin Command คือ

Command Input
↓
Permission
↓
Validate Arguments
↓
Server-side Business Logic
↓
Action
↓
Log เมื่อจำเป็น

ไม่ใช่แค่สร้าง /givemoney แล้วเชื่อค่าที่ถูกส่งเข้ามาทั้งหมด

สำหรับคนที่เรียน FiveM Developer กับ comsiam การใช้ RegisterCommand() ให้คล่องช่วยได้มากกว่าการสร้างคำสั่ง Chat เพราะมันสามารถใช้เป็นเครื่องมือ Debug Resource, ทดสอบ Native, เรียก Event และสร้าง Admin Tool ได้อย่างรวดเร็ว

หลักจาก comsiam ที่ควรจำคือ Public Command ใช้ Validation ส่วน Privileged Command ต้องมีทั้ง Permission และ Validation เพราะสองอย่างทำหน้าที่คนละเรื่อง

Comments

Popular posts from this blog

FiveM ยังน่าเล่นไหม? Enhanced เปลี่ยน FiveM แค่ไหน

FiveM คืออะไร เล่นอย่างไร สำหรับมือใหม่ เริ่มต้นตั้งแต่ศูนย์

วิธีตั้ง Admin Permission ด้วย add_ace และ add_principal FiveM แบบละเอียด