1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FiveM(QBCore)で自動販売機スクリプトを開発してみた

Posted at

はじめに

FiveMの街での飲食店のオープンしていないことが住民にとって不便でもあり、店員としても常に開けるのは負担が大きいため、自動販売機を設置してプレイヤーが購入できるようにした。自動販売機の在庫や売上の回収は、店員(特定ジョブ+ランク)のみが出来るように作成した。飲食店の売上のために心なき(NPC)への販売模擬機能も搭載した。

自動販売機スクリプトの内容について

自動販売機の構成としては、以下の通りである。

【仕様】
 ・プレイヤーが商品を購入できること
 ・各自動販売機ごと、各商品ごとに在庫管理ができること
 ・サーバ瞑想(FiveM再起動)時にランダム個数を販売する(NPCへの販売模擬)
 ・在庫の補充は、特定ジョブができること
 ・売上の回収は、特定ジョブ+特定ランク以上ができること

【動作環境】
 ・QBCore, ox_lib, ox_target
  ※心の目で見た際のメニューをox_libのメニューで実装。

動作画面

・商品購入の動作
 容量の関係で早送りみたいになっているが、操作としては、自動販売機に対して、心の目でアクセスして、メニューを表示。
商品を選び、個数を入力すると、支払いを行い、商品を受け取る。
(右上にあるBankのお金が減っているのが確認できる)

vending_machine.gif

スクリプトの概要

スクリーンショット 2024-11-14 10.34.38.png

ソースコード(一部紹介)

ソースコードの一部を紹介する。
また、Scriptの配布やソースコードの公開については現在検討中のため、ポイントだけ記載する。

◼️ config.lua
取り扱う商品や、自動販売機の設定ファイル
商品名などはqb-core配下のitems.luaから取得しても良かったが、当時はそこまで理解しきれていなかったため、config.luaで記載している状況である。

config.lua
Config = {}

-- 商品名と日本語ラベルの対応表をConfigで管理
Config.ProductLabels = {
    --猫カフェ
    kira_kira_currye = 'キラキラカレー',
    strawberry_shortcake = 'いちごショートケーキ',
    doki_doki_pancakes = 'どきどきパンケーキ',
    --(省略)
}

--自動販売機の設定
Config.VendingMachines = {
    {
        id = 'Vending_Machine_001',-- 自動販売機のユニークID
        model = 'prop_vend_snak_01', -- 自動販売機のモデル 
        coords = vector3(-572.53, -1071.41, 21.33), -- 猫カフェ前
        heading = 0.0, --向き
        prices = {
            ["kira_kira_currye"] = 7500,
            ["strawberry_shortcake"] = 7500,
            ["doki_doki_pancakes"] = 7500,
            --(省略)
        },
        earnings = 0,
        allowedJob = 'uwu', --補充、売上金回収ができるジョブ設定
	    allowedGrade = 2, -- 売上金を回収できる最低ジョブレベルを指定
        autoSell = true,  -- ランダム販売を有効にする(心なきへの販売模擬)
        randomMin = 1,  -- ランダム販売の最小値
        randomMax = 10   -- ランダム販売の最大値
    },
    --(以下自動販売機設置分続く)
}

◼️vending_machine.json
自動販売機の在庫や売上金を管理しているDBファイル

vending_machine.json
[
  {
    "earnings": 1725000,
    "inventory": {
      "kira_kira_currye": 124,
      "doki_doki_pancakes": 96,
      "strawberry_shortcake": 101
    },
    "id": "Vending_Machine_001"
  }
]

idがconfig.luaの自動販売機と紐づいている。
earningsは、自動販売機に入っている売上金額である。

スクリプト起動中にJSONファイルを直接書き換えても、反映されない。
メモリ上にロードされているため、書き換えたとしても、自動販売機が利用された際にメモリ上のデータに下記変わる。
反映させたい場合は、スクリプトの再起動が必要である。

◼️自動販売機のpropの出現

client.lua
-- 自動販売機を生成し、配置
local vendingMachineEntity = CreateObject(vendingMachineModel, vendingMachineCoords.x, vendingMachineCoords.y, vendingMachineCoords.z, true, false, true)
SetEntityHeading(vendingMachineEntity, vendingMachineHeading)
FreezeEntityPosition(vendingMachineEntity, true)
SetModelAsNoLongerNeeded(vendingMachineModel)

◼️自動販売機に心の目で選択した際のメニュー表示(ox_targetを利用)

config.lua
-- ox_target Interaction (座標ベースのターゲットゾーンを追加)
for _, machineConfig in ipairs(Config.VendingMachines) do
    exports['ox_target']:addSphereZone({
        coords = machineConfig.coords,
        radius = 1.5,
        options = {
            {
                name = 'vendingMachineBuy',
                label = '商品を購入する',
                icon = 'fa-solid fa-shopping-cart',
                onSelect = function(data)
                    showProductMenu(machineConfig.coords)
                end
            },
            {
                name = 'vendingMachineRestock',
                label = '商品を補充する',
                icon = 'fa-solid fa-box',
                onSelect = function(data)
                    showRestockMenu(machineConfig.coords)
                end
            },
            {
                name = 'vendingMachineCollect',
                label = '売上金を回収する',
                icon = 'fa-solid fa-money-bill-wave',
                onSelect = function(data)
                    collectEarnings(machineConfig.coords)
                end
            }
        }
    })
end

上記コードの以下の部分で、ox_targetのメニューが表示できる座標と範囲を指定している。
最初は自動販売機のpropのIDに紐づけようとしたがうまくいかず、座標に変更した。

client.lua
    exports['ox_target']:addSphereZone({
        coords = machineConfig.coords,
        radius = 1.5,

◼️商品購入メニューと販売処理
商品メニュー(ox_target)の生成は、client.luaで実施する。

config.lua
table.insert(options, {
    title = label,
    description = " 在庫: " .. quantity .. "個, 価格: $" .. price,
    onSelect = function()
        local input = exports['ox_lib']:inputDialog('購入数量', {'数量'})
        if input and tonumber(input[1]) and tonumber(input[1]) > 0 then
            -- server.luaの販売処理を呼び出す
            TriggerServerEvent('vendingMachine:buyProduct', uniqueID, product, tonumber(input[1]), price)
        else
            exports['ox_lib']:notify({type = 'error', description = '無効な数量です。'})
        end
    end
})

-- 上記で作成したメニューを表示させる部分
if #options > 0 then
    exports['ox_lib']:registerContext({
        id = 'vending_machine_menu',
        title = '購入する商品を選択',
        options = options
    })

    exports['ox_lib']:showContext('vending_machine_menu')
else
    exports['ox_lib']:notify({type = 'error', description = '商品がありません。'})
end

アイテムの購入処理は、Server.luaで実施する。

server.lua
-- 商品購入処理(抜粋)
if machine and machine.inventory[productName] and machine.inventory[productName] >= quantity then
    local price = getProductPrice(uniqueID, productName) * quantity  -- Configから価格を取得
    if Player.Functions.RemoveMoney('bank', price, 'vendingMachine-purchase') then --cash or bank
        -- 自動販売機の商品ごとの在庫減少
        machine.inventory[productName] = machine.inventory[productName] - quantity
        -- 自動販売機の商品ごとの売上金額に加算
        machine.earnings = machine.earnings + price
        -- プレイヤーにアイテム付与
        Player.Functions.AddItem(productName, quantity)
        TriggerClientEvent('inventory:client:ItemBox', src, QBCore.Shared.Items[productName], "add")
        -- プレイヤーに購入メッセージ表示
        TriggerClientEvent('QBCore:Notify', src, productName .. 'を' .. quantity .. '個購入しました!', 'success')
        -- JSONファイルへの書き込み処理を呼び出し
        saveVendingData()
    else
        TriggerClientEvent('QBCore:Notify', src, '所持金が不足しています。', 'error')
    end
else
    TriggerClientEvent('QBCore:Notify', src, '在庫がありません。', 'error')
end

※QBCore:Notifyでメッセージを出力したり、exports['ox_lib']:notifyでメッセージを出力したり、チグハグな状態ではある...

◼️売上回収処理
特定のジョブかつジョブレベルだけ操作できるようにし、他のプレイヤーが操作しようとすると、ox_libでエラーメッセージを出力する。

client.lua
--プレイヤーのジョブとランクを取得
local playerJob = GetPlayerJob()

for _, machineConfig in ipairs(Config.VendingMachines) do
    if #(coords - machineConfig.coords) < 2.0 then
        if playerJob.name == machineConfig.allowedJob and playerJob.grade.level >= machineConfig.allowedGrade then
                --server.luaの売上を回収する処理を呼び出す
                TriggerServerEvent('vendingMachine:collectEarnings', machineConfig.id)
                return
        end
    end
end
exports['ox_lib']:notify({type = 'error', description = 'この操作を行う権限がありません。'})

少し宣伝

FiveMの35VILLAGE RPというサーバで遊んでいます!

犯罪の種類が多かったり、白ジョブ、フリージョブもたくさんあるサーバです。
私が作った自作Scriptもいつくか入っているので、ぜひ遊びに来てください!

最後に

今回の自動販売機のスクリプトは、2作目のスクリプトでpropの生成などのやり方を把握することができた。
また、苦労した点としては、ox_targetを自動販売機のIDと紐づけて実施しようとしたがうまくいかず、座標に対してox_targetを指定することで、メニューを開けるようになった。
課題として、config.luaに自動販売機を追加した際に、JSONファイルも手動で記載する必要がある。一応、JSONファイルに自動販売機が存在しない場合にJSONファイルに追加する処理は入れているものの、期待した形式になっていない状況である。この辺りについては、随時改修していこうと思う。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?