Export e state bag
Due modi per interagire, partendo da quello a costo zero.
phoneapi <id> stampa nella console del server tutto ciò che trovi qui sotto per un singolo giocatore. Se qualcosa non restituisce quello che ti aspetti, esegui questo comando prima di continuare a leggere: fai molto prima rispetto a tirare a indovinare quale lato abbia torto.
State bag — le risposte che non costano nulla
Sezione intitolata “State bag — le risposte che non costano nulla”Uno state bag non richiede callback, né eventi, né accordi con questa risorsa. È una tabella letta direttamente sulla macchina che fa la domanda, sopravvive se questa risorsa viene riavviata sotto i tuoi piedi e funziona allo stesso modo sia lato client che lato server.
-- serverif Player(src).state.onCallWith then ... end
-- client, about somebody elseif Player(GetPlayerFromServerId(id)).state.phoneOpen then ... end| Bag | Tipo | |
|---|---|---|
phoneNumber | string | il numero del telefono che ha con sé |
phoneName | string | il nome che quel telefono mostra quando chiama qualcuno |
phoneDevice | string | nil | quale oggetto telefono è, in modalità oggetto. nil quando i telefoni appartengono ai personaggi |
phoneOpen | boolean | il telefono è in mano e visibile a schermo |
onCallWith | number | nil | il source all’altro capo della linea |
callAnswered | boolean | qualcuno ha risposto. false mentre sta ancora squillando |
speakerphone | boolean | |
mutedCall | boolean | il giocatore ha disattivato il proprio microfono |
otherMutedCall | boolean | l’altro capo della linea ha disattivato il proprio microfono |
flashlight | boolean | la torcia è accesa. Ogni client disegna il fascio di luce su quel ped |
phoneCamera | boolean | il mirino è attivo: sta per scattare una fotografia |
phoneSignal | number | quante tacche, da 0 a Config.Signal.Bars. 0 significa un telefono irraggiungibile dalla città |
Tre cose che vale la pena sapere:
Sono scritti dal server. Anche il client conosce tutte queste informazioni e potrebbe pubblicarle in meno righe, ma un client può mentire su tutto, e uno state bag letto da un anticheat deve essere degno di fiducia.
Squillare conta già come essere in chiamata. onCallWith viene impostato nell’istante in cui il telefono squilla, perché la linea è impegnata — ciò che non è ancora vero è che qualcuno abbia risposto, e quello è indicato da callAnswered.
onCallWith può essere nil durante una chiamata reale. Dall’altra parte potrebbe esserci il browser di qualcuno anziché un giocatore in città. callAnswered è comunque true; semplicemente non c’è un source di gioco da indicare.
Lo state bag flashlight non esisteva finché la torcia non ha iniziato a illuminare davvero qualcosa, per il principio che un bag sempre false è peggio di un bag che non c’è: uno è una funzione mancante, l’altro è una risposta sbagliata. Ora illumina, quindi viene pubblicato.
Export lato server
Sezione intitolata “Export lato server”exports.mic_phone:GetNumber(src) --> '555-010-2048'exports.mic_phone:GetName(src) --> 'Jean Dupont'exports.mic_phone:GetIdentifier(src) --> 'char1:abc…' or 'device:abc…' in item modeexports.mic_phone:GetDevice(src) --> the phone item's id, or nilexports.mic_phone:HasPhone(src) --> are they carrying one at allexports.mic_phone:IsOpen(src)exports.mic_phone:IsInCall(src)exports.mic_phone:CallWith(src) --> the other source, or nil
exports.mic_phone:GetSource(number) --> src, for a numberexports.mic_phone:NumberOf(identifier) --> works for somebody who is not onlineexports.mic_phone:IdentifierOf(number)
exports.mic_phone:Call(src, number) --> rings it from their phone, exactly as dialling wouldexports.mic_phone:EndCall(src)exports.mic_phone:AddContact(src, name, number, { email =, notes =, favorite = })exports.mic_phone:RemoveContact(src, number)exports.mic_phone:SendNotification(src, { app =, title =, body = })exports.mic_phone:OpenApp(src, 'messages')exports.mic_phone:OpenPhone(src) / ClosePhone(src)
exports.mic_phone:SendMessage(number, text, from)exports.mic_phone:SendMail(identifierOrNumber, fromName, fromAddress, subject, body)exports.mic_phone:SendInvoice(senderSrc, targetNumber, label, amount, society)Call restituisce esattamente ciò che verrebbe detto al telefono stesso — { ok = false, reason = 'busy' }, 'airplane', 'unknown', 'offline' — perché esegue lo stesso identico gestore usato dal tastierino del telefono. C’è un solo percorso di codice e un solo insieme di regole.
Un export la cui risposta è nil non restituisce alcun valore, non un valore nil. Se inserito direttamente in una lista di argomenti non diventa "nil", ma accorcia la lista. Salvalo prima in una variabile locale:
local with = exports.mic_phone:CallWith(src) -- and not print(exports.mic_phone:CallWith(src), x)Il telefono stesso
Sezione intitolata “Il telefono stesso”Le domande a cui le altre risorse dovevano sempre rispondere da sole. Ognuna è una ricerca che il telefono fa già internamente, così c’è un solo percorso di codice e un solo insieme di regole.
exports.mic_phone:GetBattery(src) --> 0-100, or nil when this city has no battery at allexports.mic_phone:SetBattery(src, 40) --> your own charger, or a story that drains oneexports.mic_phone:IsCharging(src)exports.mic_phone:HasAirplaneMode(src) --> the city cannot reach them; their own phone still works
exports.mic_phone:GetSignal(src) --> 0 to Config.Signal.Bars. Also the `phoneSignal` state bagexports.mic_phone:SetJammed(src, true) --> takes their bars away: a heist, a prison, a story
exports.mic_phone:GetGallery(src) --> every picture: { id, url, width, height, kind, created }exports.mic_phone:AddPhoto(src, url, w, h) --> puts one in it, and it appears without a rebootexports.mic_phone:GetContacts(src)
exports.mic_phone:GetInvoices(src) --> every invoice on that phone, paid and unpaidexports.mic_phone:UnpaidInvoices(src) --> count, total
exports.mic_phone:SendLocation(number, x, y, 'The docks', from) --> a map pin in a conversation
exports.mic_phone:GetSocialHandle(src, 'instapic') --> 'nightshift', or nil if they never signed upexports.mic_phone:SocialPost(src, 'twixel', { text = 'Anybody seen a blue Sultan?' })exports.mic_phone:SetVerified(src, 'twixel', true) --> the tick beside their name, per app
exports.mic_phone:GetRadioChannel(src) --> 12, or nilexports.mic_phone:SetRadioChannel(src, 12) --> 0 takes them off itQuando GetBattery risponde nil non significa 0: nil vuol dire che questo server non ha mai attivato la batteria. Uno script che tratta i due valori allo stesso modo impedisce a qualcuno di chiamare un’ambulanza in una città dove il telefono non si scarica mai.
AddPhoto controlla l’URL rispetto agli host consentiti da questo server, esattamente come viene controllata una foto scattata dalla fotocamera. Una galleria che mostra qualsiasi indirizzo è una galleria che chiunque può far puntare a qualsiasi cosa.
SocialPost e SetRadioChannel eseguono i gestori interni del telefono, quindi rispettano le stesse regole dello schermo: nessun account su quell’app significa nessun post, e un canale radio riservato a un altro lavoro viene rifiutato anche qui.
Il mercato
Sezione intitolata “Il mercato”Sei criptovalute, il cui prezzo viene aggiornato dal server su un timer irraggiungibile dal client. Tutto ciò che segue è espresso in monete e in denaro — mai nei milionesimi usati internamente dalla risorsa — quindi uno script che paga a qualcuno un quarto di moneta scrive semplicemente 0.25.
exports.mic_phone:Coins() --> every coin: id, name, symbol, price, change, window, historyexports.mic_phone:CoinPrice('santo') --> 42137.55, in money. nil when there is no such coinexports.mic_phone:GetCrypto(src, 'santo') --> 0.4218exports.mic_phone:GetCryptoValue(src) --> what their whole portfolio is worth, in money
exports.mic_phone:AddCrypto(src, 'omen', 12.5) --> true when it landedexports.mic_phone:RemoveCrypto(src, 'omen', 12.5) --> false when they did not have it, and nothing movesexports.mic_phone:SetCoinPrice('omen', 0.4) --> move the market for a storyRemoveCrypto rifiuta l’operazione anziché andare in negativo, quindi la sua risposta significa anche “aveva abbastanza fondi per pagare” — che è esattamente ciò che vuole sapere un negozio che vende articoli in criptovaluta:
if exports.mic_phone:RemoveCrypto(src, 'omen', price) then giveThem(thing) endIl portafoglio appartiene al personaggio oppure al dispositivo, in base a Config.Apps.Crypto.Wallet. In modalità dispositivo, rubare un telefono significa rubare anche il portafoglio; in modalità personaggio, un telefono rubato mostra al ladro un’app vuota. Gli export accettano in ogni caso un source e non hanno bisogno di sapere quale modalità sia attiva.
phonecrypto nella console stampa il mercato, phonecrypto <id> stampa ciò che possiede quel giocatore e phonecrypto price <coin> <money> modifica il prezzo manualmente.
Batteria e torcia
Sezione intitolata “Batteria e torcia”Entrambe si attivano o disattivano dalla configurazione — shared/config/battery.lua e Config.Flashlight — e per impostazione predefinita la batteria è spenta mentre la torcia è accesa.
exports.mic_phone:BatteryLevel(src) --> 0-100, or 100 when the battery is switched offexports.mic_phone:IsPhoneCharging(src)exports.mic_phone:ChargePhone(src, true) -- plug in; false unplugsUn caricabatterie non deve per forza essere un oggetto in tasca. Config.Battery.Charger.Item te ne fornisce uno già pronto, mentre ChargePhone serve per tutto il resto: un cavo in auto, una presa a muro, un prop dietro una scrivania.
phonebattery [charge|unplug|full|empty] <id> cambia lo stato di carica, così puoi testare la batteria senza mettere alla prova la tua pazienza.
Comandi di amministrazione
Sezione intitolata “Comandi di amministrazione”Gli amministratori in gioco (e dalla console) hanno a disposizione alcuni comandi per capire perché qualcosa non si sta comportando come previsto. Si limitano a leggere, oppure modificano di proposito il telefono di un singolo giocatore; nessuno di essi inventa dati.
phoneapi <id> | state bag, export e batteria per un singolo giocatore |
phoneapps | l’elenco delle app così come risolto dalla configurazione |
phonedevice [backup|restore] <id> | quale telefono ha in mano e quali copie di backup possiede |
phonebattery [charge|unplug|full|empty] <id> |
phoneapi stampa anche se quel telefono ha segnale e cosa è in attesa di essergli consegnato, riassumendo in due righe l’intera catena da “nessun segnale” a “notifica arrivata”. Il flusso attraversa quattro punti diversi e prima non c’era modo di vedere quale si fosse inceppato.
Il livello della batteria non usa un timer. Una riga ricorda a quanto era, quando e cosa stava facendo il telefono — aperto, in tasca o in carica — e il livello attuale è semplice aritmetica sull’orologio. Cento giocatori costano cento numeri invece di cento timer, e un telefono lasciato in carica durante un riavvio del server torna carico anziché rimasto congelato.
Tenere i tuoi avvisi fuori dall’album fotografico altrui
Sezione intitolata “Tenere i tuoi avvisi fuori dall’album fotografico altrui”(Questo argomento e il resto della convivenza tra risorse — il ped, il prop, la modalità oggetto — sono raccolti in la tua risorsa accanto al telefono.)
Una fotografia cattura l’intero fotogramma. Qualsiasi cosa la tua risorsa stesse disegnando sullo schermo al momento dello scatto — un suggerimento tasti, l’etichetta di un marker, una barra di avanzamento — finisce nella foto, e questa risorsa non può impedirti di disegnarla.
phoneCamera esiste affinché tu possa fermarti da solo. È true dal momento in cui si apre il mirino fino a quando si chiude, e basta una sola riga nel tuo ciclo di disegno per risolvere tutto:
if LocalPlayer.state.phoneCamera then goto skip endL’HUD del telefono si toglie già di mezzo da solo: Config.Apps.Camera.HideHud nasconde la minimappa e le barre della vita del gioco finché la fotocamera è aperta, così ciò che viene inquadrato è esattamente ciò che viene salvato.
La torcia è uno state bag flashlight, perciò ogni altro client disegna il fascio di luce sul ped di quel giocatore. Illumina solo mentre il telefono è in mano e si spegne quando il telefono viene riposto.
Export lato client
Sezione intitolata “Export lato client”exports.mic_phone:Open() / Close() / Toggle() / IsOpen()exports.mic_phone:CanOpen() --> ok, reasonexports.mic_phone:Restricted() --> 'Dead' | 'Cuffed' | 'Swimming' | … | nilexports.mic_phone:OpenApp('bank') / CloseApp()exports.mic_phone:GetNumber() / GetDevice() / HasPhone() / IsBooted()exports.mic_phone:IsInCall() / IsTablet() / SetTablet(true)exports.mic_phone:Notify({ app =, title =, body = })Nulla qui scavalca i controlli che il telefono applica a se stesso. Open e OpenApp ricevono lo stesso rifiuto che riceverebbe un giocatore premendo F1, per gli stessi motivi: un telefono che si apre mentre il proprietario è a terra morto solo perché una risorsa lo ha chiesto gentilmente non è un telefono con delle regole. CanOpen e Restricted servono proprio per chiedere prima di provare ad aprire, permettendoti di disabilitare un pulsante nell’interfaccia invece di mostrarne uno che non fa nulla.
I nomi degli state bag coincidono con quelli di lb-phone dove il significato è lo stesso, quindi uno script scritto per quel telefono legge questo senza bisogno di modifiche.