🧩 为 Agency-Phone 开发自定义应用
把应用放到手机上有两种方式,选择适合你的一种:
| 方式 | 适用于 | 需要改动的内容 |
|---|---|---|
| A. SDK(外部资源) | 添加带自有图标应用的开发者和脚本 | 只改你自己的资源,无需修改 agency-phone。 |
| B. Drop-in(在 agency-phone 内部) | 想在手机界面中加入原生界面的服主 | config.lua + apps/(界面还需 html/js) |
如果你要把应用发布给其他服务器,请使用方式 A。它不需要我们的源代码,更新后也能继续使用。
A SDK:从你自己的资源注册应用
agency-phone 提供导出函数,任何资源都能在运行时把带自有图标的应用放到手机主屏幕上。应用界面留在你自己的资源中;手机负责显示图标,玩家点按时把控制权交给你。无需修改 agency-phone,也不需要解锁版本。
最小示例
-- client side of YOUR resource
CreateThread(function()
Wait(1500) -- let agency-phone boot
exports['agency-phone']:RegisterApp({
id = 'myshop',
label = 'My Shop',
icon = 'fa-solid fa-cart-shopping', -- FontAwesome 6 class
-- image = 'https://cfx-nui-myresource/icon.png', -- or your own image instead
color = '#ff9500',
colorDark = '#e08600',
})
end)
-- fired when the player taps your icon on the phone
AddEventHandler('agency-phone:client:openApp', function(appId)
if appId ~= 'myshop' then return end
exports['agency-phone']:ClosePhone()
-- open your own NUI here
end)
RegisterApp 配置
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 唯一 id,小写,不含空格。 |
label | string | 是 | 图标下方显示的名称。 |
icon | string | 否 | FontAwesome 6 类名。设置了 image 时忽略。 |
image | string | 否 | 自定义图标图片,例如 https://cfx-nui-<resource>/icon.png。 |
color / colorDark | string | 否 | 图标背后的渐变色(十六进制)。 |
slot | number | 否 | 在主屏幕上的位置。不填则自动放置。 |
storeDesc | string | 否 | 应用在 Store 页面上的简短说明。不填则使用 label。 |
SDK 导出(客户端)
| 导出 | 说明 |
|---|---|
RegisterApp(config) | 添加或更新你的应用。返回 true,或 false, err。 |
UnregisterApp(id) | 移除你的应用。你的资源停止时也会自动移除。 |
GetRegisteredApps() | 所有已注册外部应用的表。 |
ClosePhone() | 关闭手机,例如在打开你自己的 NUI 之前。 |
点按事件
玩家点按你的图标时,agency-phone 会在客户端同时触发以下两个事件,任选其一即可:
| 事件 | 参数 |
|---|---|
agency-phone:client:openApp | appId(按你的 id 过滤) |
agency-phone:client:openApp:<yourId> | 无(已限定为你的应用) |
在你的 fxmanifest.lua 中添加 dependency 'agency-phone',以确保启动顺序正确。
B Drop-in:手机内的原生界面
适用于由手机自身绘制的界面(而不是你的资源)。这需要修改 agency-phone 自己的文件。
| 步骤 | 文件 | escrow 版本中可编辑? |
|---|---|---|
1. 在 AgencyConfig.Apps 中添加应用条目 | config.lua | ✓ 是 |
2. RegisterNUICallback 逻辑 | apps/<id>_client.lua(+ _server.lua) | ✓ 是 |
3. 界面 <div id="app-<id>"> | html/index.html | ✗ 已加密 |
4. openApp() 中的 case '<id>': + 加载器 | html/js/phone.js | ✗ 已加密 |
第 3 步和第 4 步位于 escrow 加密的文件中,因此原生界面需要源码版或解锁版。如果你只有 escrow 版本,请改用方式 A(界面放在你自己的资源中)。资源内的参考文件:apps/testapp_client.lua 和 apps/dispatch_client.lua。
📋 手机的其他导出
客户端
| 导出 | 返回 | 说明 |
|---|---|---|
IsPhoneOpen() | boolean | 手机当前是否打开。 |
IsPhonePoweredOn() | boolean | 设备是否开机。 |
HasPhone() | boolean | 玩家是否拥有手机物品。 |
HasCarrierContract() | boolean | 是否有有效的 eSIM 或合约。 |
GetPhoneData() | table | 手机状态快照。 |
PhoneNotification(title, text, icon, color, timeout, acceptIcon, denyIcon) | void | 手机内通知。 |
StartCall(contact) | void | 拨打 { name, number }。 |
StartAgencyPayCheckout(opts) | void | AgencyPay 结账(见下文)。 |
服务端
| 导出 | 说明 |
|---|---|
SendMail(identifier, mailData) | 向玩家投递邮件(在线或离线)。 |
AddChirp(data) | 在 Pulse 社交动态中发布消息。 |
VerifyAgencyPayCharge(source, requestId, minAmount) | 在发放商品前于服务端确认 AgencyPay 结账已付款(见下文)。 |
⇄ NUI ↔ Lua 桥接(方式 B)
| 方向 | JS 端 | Lua 端 |
|---|---|---|
| JS → Lua | fetchNUI('callbackName', data) | RegisterNUICallback('callbackName', fn) |
| Lua → JS | 在 window.onmessage 中监听 action | SendNUIMessage({ action = '...' }) |
💳 AgencyPay 结账
可从任意资源向玩家的钱包收款。
exports['agency-phone']:StartAgencyPayCheckout({
merchant = 'My Shop', title = 'Agency Pay', subtitle = 'Checkout',
label = 'Premium Coffee', amount = 24.50,
icon = 'fa-solid fa-mug-hot', accent = '#ff9f0a',
clientEvent = 'myshop:client:agencyPayResult',
serverEvent = 'myshop:server:agencyPayResult',
metadata = { orderId = 'order_2048' },
})
RegisterNetEvent('myshop:client:agencyPayResult', function(result)
if result.status == 'paid' then
-- deliver the goods
end
end)
结果中包含 status(paid / cancelled / failed)、amount、cardId、balance 以及你的 metadata。
从客户端传到你资源里的 paid 结果本身并不能证明什么。请给 StartAgencyPayCheckout 传入你自己的 id,并在发放任何东西之前于服务端调用 exports['agency-phone']:VerifyAgencyPayCharge(source, id, amount)。只有当该玩家在过去 60 秒内支付了这笔结账时,它才返回 true 和金额,并会把这笔付款标记为已使用,防止同一笔付款被领取两次。
🐛 故障排查
| 现象 | 解决方法 |
|---|---|
| 图标不显示(SDK) | 把 agency-phone 更新到最新的 Keymaster 版本,在 RegisterApp 前加 Wait(1500),并在清单中添加 dependency 'agency-phone'。 |
| 点按没有反应(SDK) | 你的 agency-phone:client:openApp 处理函数必须与你的 id 完全一致(区分大小写)。 |
| 自定义图片不显示 | 使用完整的 NUI 地址 https://cfx-nui-<your-resource>/path/icon.png,并在清单的 files { } 中列出该文件。 |
| 图标不显示(drop-in) | 在配置条目中设置 enabled = true 和 installed = true。 |
有疑问或需要帮助?欢迎加入 Agency Scripts 的 Discord。
