Agency-Phone Custom Apps
Jetzt mit AgencyOS27: kostenloses Update für alle, die das Phone schon haben.

🧩 Eine eigene App für das Agency-Phone bauen
Es gibt zwei Wege, eine App auf das Phone zu bringen. Nimm den, der zu dir passt:
| Weg | Passt für | Was du anfasst |
|---|---|---|
| A. SDK (eigene Resource) | Anbieter und Scripts, die eine App mit eigenem Icon mitbringen | Nur deine eigene Resource. Keine Änderung an agency-phone. |
| B. Drop-in (in agency-phone) | Serverbetreiber, die einen nativen Bildschirm in der Oberfläche des Phones wollen | config.lua + apps/ (+ html/js für einen Bildschirm) |
Wenn du eine App an andere Server auslieferst, nimm Weg A. Er braucht keinen Quelltext von uns und funktioniert auch nach Updates weiter.
A SDK: eine App aus der eigenen Resource anmelden
agency-phone stellt Exports bereit, mit denen jede Resource zur Laufzeit eine App mit eigenem Icon auf den Startbildschirm des Phones bringt. Die Oberfläche deiner App liegt in deiner eigenen Resource; das Phone zeigt das Icon und übergibt dir die Kontrolle, sobald der Spieler es antippt. Keine Änderung an agency-phone, kein freigeschalteter Build.
Minimalbeispiel
-- 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)
Konfiguration von RegisterApp
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
id | string | ja | Eindeutige id, kleingeschrieben, ohne Leerzeichen. |
label | string | ja | Name unter dem Icon. |
icon | string | nein | FontAwesome-6-Klasse. Wird ignoriert, wenn image gesetzt ist. |
image | string | nein | Eigenes Icon-Bild, z. B. https://cfx-nui-<resource>/icon.png. |
color / colorDark | string | nein | Farbverlauf hinter dem Icon (Hex). |
slot | number | nein | Position auf dem Startbildschirm. Ohne Angabe wird sie automatisch gewählt. |
storeDesc | string | nein | Kurzbeschreibung auf der Store-Seite deiner App. Ohne Angabe gilt das Label. |
SDK-Exports (Client)
| Export | Beschreibung |
|---|---|
RegisterApp(config) | App hinzufügen oder aktualisieren. Gibt true zurück, oder false, err. |
UnregisterApp(id) | App entfernen. Passiert auch von selbst, wenn deine Resource stoppt. |
GetRegisteredApps() | Tabelle aller angemeldeten externen Apps. |
ClosePhone() | Phone schließen, z. B. bevor deine eigene NUI aufgeht. |
Das Antipp-Event
Tippt der Spieler auf dein Icon, löst agency-phone beide Events auf dem Client aus. Nimm das, was dir lieber ist:
| Event | Argumente |
|---|---|
agency-phone:client:openApp | appId (nach deiner id filtern) |
agency-phone:client:openApp:<yourId> | keine (gilt schon nur für deine App) |
Trag dependency 'agency-phone' in deine fxmanifest.lua ein, damit die Startreihenfolge stimmt.
B Drop-in, ein nativer Bildschirm im Phone
Für einen Bildschirm, den das Phone selbst zeichnet (nicht deine Resource). Dabei werden Dateien von agency-phone geändert.
| Schritt | Datei | Im Escrow-Build änderbar? |
|---|---|---|
1. App-Eintrag in AgencyConfig.Apps | config.lua | ✓ ja |
2. Logik mit RegisterNUICallback | apps/<id>_client.lua (+ _server.lua) | ✓ ja |
3. Bildschirm <div id="app-<id>"> | html/index.html | ✗ verschlüsselt |
4. case '<id>': in openApp() + Loader | html/js/phone.js | ✗ verschlüsselt |
Schritt 3 und 4 liegen in Dateien, die Escrow verschlüsselt, ein nativer Bildschirm braucht also einen Build mit Quelltext bzw. einen freigeschalteten Build. Hast du nur die Escrow-Version, nimm stattdessen Weg A (dein Bildschirm liegt dann in deiner eigenen Resource). Beispieldateien in der Resource: apps/testapp_client.lua und apps/dispatch_client.lua.
📋 Weitere Exports des Phones
Client
| Export | Rückgabe | Beschreibung |
|---|---|---|
IsPhoneOpen() | boolean | Phone ist gerade offen. |
IsPhonePoweredOn() | boolean | Gerät ist eingeschaltet. |
HasPhone() | boolean | Spieler hat ein Phone-Item. |
HasCarrierContract() | boolean | Aktive eSIM bzw. aktiver Vertrag. |
GetPhoneData() | table | Momentaufnahme des Phone-Zustands. |
PhoneNotification(title, text, icon, color, timeout, acceptIcon, denyIcon) | void | Benachrichtigung auf dem Phone. |
StartCall(contact) | void | Ruft { name, number } an. |
StartAgencyPayCheckout(opts) | void | AgencyPay-Kasse (siehe unten). |
Server
| Export | Beschreibung |
|---|---|
SendMail(identifier, mailData) | Mail an einen Spieler zustellen (online oder offline). |
AddChirp(data) | Beitrag im Pulse-Feed veröffentlichen. |
VerifyAgencyPayCharge(source, requestId, minAmount) | Eine bezahlte AgencyPay-Kasse auf dem Server bestätigen, bevor du Ware ausgibst (siehe unten). |
⇄ Brücke zwischen NUI und Lua (Weg B)
| Richtung | JS-Seite | Lua-Seite |
|---|---|---|
| JS → Lua | fetchNUI('callbackName', data) | RegisterNUICallback('callbackName', fn) |
| Lua → JS | in window.onmessage auf action hören | SendNUIMessage({ action = '...' }) |
💳 AgencyPay-Kasse
Belaste die Wallet des Spielers aus jeder beliebigen Resource.
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)
Das Ergebnis enthält status (paid / cancelled / failed), amount, cardId, balance und deine metadata.
Ein paid, das vom Client bei deiner Resource ankommt, beweist für sich allein nichts. Gib StartAgencyPayCheckout eine eigene id mit und ruf auf dem Server exports['agency-phone']:VerifyAgencyPayCharge(source, id, amount) auf, bevor du etwas herausgibst. Das liefert true und den Betrag nur, wenn dieser Spieler genau diese Kasse in den letzten 60 Sekunden bezahlt hat, und verbraucht die Zahlung, damit dieselbe Zahlung nicht zweimal eingelöst werden kann.
🐛 Fehlersuche
| Problem | Lösung |
|---|---|
| Icon erscheint nicht (SDK) | agency-phone auf den neuesten Keymaster-Build aktualisieren, Wait(1500) vor RegisterApp und dependency 'agency-phone' ins Manifest eintragen. |
| Antippen bewirkt nichts (SDK) | Dein Handler für agency-phone:client:openApp muss genau deine id prüfen (Groß- und Kleinschreibung zählt). |
| Eigenes Bild wird nicht angezeigt | Eine vollständige NUI-Adresse https://cfx-nui-<your-resource>/path/icon.png verwenden und die Datei im Manifest unter files { } eintragen. |
| Icon erscheint nicht (Drop-in) | enabled = true und installed = true im Config-Eintrag. |
Fragen oder Hilfe nötig? Komm in den Discord von Agency Scripts.