Pular para o conteúdo

Exports e state bags

Dois caminhos de acesso, começando pelo mais leve.

phoneapi <id> imprime tudo o que está abaixo para um jogador no console do servidor. Se algo aqui não estiver respondendo o que você espera, execute esse comando antes de continuar lendo — é mais rápido do que tentar adivinhar qual lado está errado.

Uma state bag não precisa de callback, nem de evento, nem de acordo prévio com este recurso. É uma tabela lida na máquina que estiver perguntando, sobrevive caso este recurso seja reiniciado por baixo do seu e funciona tanto no cliente quanto no servidor.

-- server
if Player(src).state.onCallWith then ... end
-- client, about somebody else
if Player(GetPlayerFromServerId(id)).state.phoneOpen then ... end
BagTipo
phoneNumberstringo número do telefone que a pessoa está carregando
phoneNamestringo nome que esse telefone mostra quando liga para alguém
phoneDevicestring | nilqual item de telefone é este, no modo item. nil quando os telefones pertencem aos personagens
phoneOpenbooleano telefone está na mão e na tela
onCallWithnumber | nilo source do outro lado da linha
callAnsweredbooleanalguém atendeu. false enquanto ainda está chamando
speakerphoneboolean
mutedCallbooleana pessoa silenciou o próprio microfone
otherMutedCallbooleano outro lado silenciou o próprio microfone
flashlightbooleana lanterna está acesa. Todo cliente desenha o feixe de luz naquele ped
phoneCamerabooleano visor da câmera está aberto: a pessoa está prestes a tirar uma foto
phoneSignalnumberquantas barras, de 0 até Config.Signal.Bars. 0 é um telefone que a cidade não consegue alcançar

Três coisas que vale a pena saber:

Elas são escritas pelo servidor. O cliente também sabe de tudo isso e poderia publicar em menos linhas, mas um cliente pode mentir sobre tudo, e uma state bag que um anticheat lê precisa ser confiável.

Chamando já conta como estar em uma ligação. onCallWith é definido no momento em que um telefone toca, porque a linha está ocupada — o que ainda não é verdade é que alguém tenha atendido, e para isso existe callAnswered.

onCallWith pode ser nil durante uma chamada real. O outro lado pode ser o navegador de alguém em vez de um jogador na cidade. callAnswered continua sendo true; simplesmente não há um source para indicar.

Não existia a state bag flashlight até que a lanterna realmente iluminasse alguma coisa, pelo princípio de que uma bag que é sempre false é pior do que uma bag que não existe — uma é um recurso ausente, a outra é uma resposta errada. Agora ela ilumina, então é publicada.

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 mode
exports.mic_phone:GetDevice(src) --> the phone item's id, or nil
exports.mic_phone:HasPhone(src) --> are they carrying one at all
exports.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 number
exports.mic_phone:NumberOf(identifier) --> works for somebody who is not online
exports.mic_phone:IdentifierOf(number)
exports.mic_phone:Call(src, number) --> rings it from their phone, exactly as dialling would
exports.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 retorna exatamente o que seria dito ao próprio telefone — { ok = false, reason = 'busy' }, 'airplane', 'unknown', 'offline' — porque executa o mesmo manipulador que o discador do telefone usa. Há apenas um caminho de código e um único conjunto de regras.

Um export cuja resposta é nil não retorna valor algum, em vez de retornar um valor nil. Se for colocado diretamente em uma lista de argumentos, ele não vira "nil" — ele encurta a lista. Guarde-o em uma variável local primeiro:

local with = exports.mic_phone:CallWith(src) -- and not print(exports.mic_phone:CallWith(src), x)

As perguntas que outros recursos sempre precisavam responder por conta própria. Cada uma delas é uma consulta que o telefone já faz, mantendo um único caminho de código e um único conjunto de regras.

exports.mic_phone:GetBattery(src) --> 0-100, or nil when this city has no battery at all
exports.mic_phone:SetBattery(src, 40) --> your own charger, or a story that drains one
exports.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 bag
exports.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 reboot
exports.mic_phone:GetContacts(src)
exports.mic_phone:GetInvoices(src) --> every invoice on that phone, paid and unpaid
exports.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 up
exports.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 nil
exports.mic_phone:SetRadioChannel(src, 12) --> 0 takes them off it

GetBattery responder nil não é o mesmo que responder 0: nil significa que este servidor nunca ativou o sistema de bateria. Um script que trata os dois da mesma forma impede alguém de chamar uma ambulância em uma cidade onde a bateria do telefone nunca acaba.

AddPhoto verifica a URL contra os hosts permitidos por este servidor, exatamente como é verificado quando a câmera tira uma foto. Uma galeria que exibe qualquer endereço é uma galeria que alguém pode apontar para qualquer coisa.

SocialPost e SetRadioChannel executam os próprios manipuladores do telefone, portanto obedecem ao que a tela obedece: sem conta naquele aplicativo significa sem publicação, e um canal de rádio que este emprego não pode acessar também é recusado aqui.

Seis moedas, precificadas pelo servidor em um temporizador que ninguém consegue alcançar a partir do cliente. Tudo abaixo é em moedas e em dinheiro — nunca nos milionésimos em que o recurso calcula internamente —, de modo que um script que paga a alguém um quarto de alguma coisa diz 0.25.

exports.mic_phone:Coins() --> every coin: id, name, symbol, price, change, window, history
exports.mic_phone:CoinPrice('santo') --> 42137.55, in money. nil when there is no such coin
exports.mic_phone:GetCrypto(src, 'santo') --> 0.4218
exports.mic_phone:GetCryptoValue(src) --> what their whole portfolio is worth, in money
exports.mic_phone:AddCrypto(src, 'omen', 12.5) --> true when it landed
exports.mic_phone:RemoveCrypto(src, 'omen', 12.5) --> false when they did not have it, and nothing moves
exports.mic_phone:SetCoinPrice('omen', 0.4) --> move the market for a story

RemoveCrypto recusa a operação em vez de deixar o saldo negativo, então sua resposta também significa “eles puderam pagar” — que é o que uma loja vendendo algo por criptomoeda realmente quer perguntar:

if exports.mic_phone:RemoveCrypto(src, 'omen', price) then giveThem(thing) end

A carteira pertence ao personagem ou ao telefone, e isso é definido em Config.Apps.Crypto.Wallet. No modo dispositivo, roubar um telefone rouba o portfólio junto com ele; no modo personagem, um telefone roubado mostra ao ladrão um aplicativo vazio. Os exports recebem um source de qualquer maneira e não precisam saber qual modo está ativo.

phonecrypto no console imprime o mercado, phonecrypto <id> imprime o que aquele jogador possui e phonecrypto price <coin> <money> altera o preço manualmente.

Ambos são ativados ou desativados pela configuração — shared/config/battery.lua e Config.Flashlight — e, por padrão, a bateria vem desligada e a lanterna ligada.

exports.mic_phone:BatteryLevel(src) --> 0-100, or 100 when the battery is switched off
exports.mic_phone:IsPhoneCharging(src)
exports.mic_phone:ChargePhone(src, true) -- plug in; false unplugs

Um carregador não precisa ser um item no bolso. Config.Battery.Charger.Item já entrega um pronto para uso, e ChargePhone serve para todo o resto: um cabo no carro, uma tomada na parede, um prop atrás de uma mesa.

phonebattery [charge|unplug|full|empty] <id> altera a carga, que é como você testa a bateria sem testar a própria paciência.

Os administradores dentro do jogo (e no console) têm alguns comandos para descobrir por que algo não está fazendo o que você pretendia. Eles apenas consultam ou alteram o telefone de um jogador de propósito; nenhum deles inventa dados.

phoneapi <id>state bags, exports e a bateria de um jogador
phoneappsa lista de aplicativos conforme resolvida pela configuração
phonedevice [backup|restore] <id>qual telefone a pessoa está segurando e as cópias que possui
phonebattery [charge|unplug|full|empty] <id>

phoneapi também imprime se aquele telefone tem sinal e o que está retido esperando por ele, mostrando toda a cadeia desde “sem sinal” até “a notificação chegou” em duas linhas. Isso passa por quatro lugares e não havia como ver qual deles tinha falhado.

O nível da bateria não é um temporizador. Uma linha no banco lembra onde estava, quando e o que o telefone estava fazendo — aberto, no bolso ou carregando — e o nível atual é pura aritmética no relógio. Cem jogadores custam cem números em vez de cem temporizadores, e um telefone deixado carregando durante a reinicialização do servidor volta carregado em vez de congelado.

Mantendo os seus avisos fora do álbum de fotos de alguém

Seção intitulada “Mantendo os seus avisos fora do álbum de fotos de alguém”

(Isto e o restante sobre coexistência — o ped, o prop, o modo item — estão reunidos em o seu recurso ao lado dele.)

Uma fotografia captura o quadro inteiro. Qualquer coisa que o seu recurso estivesse desenhando quando o obturador disparou — um aviso de ajuda, o texto de um marcador, uma barra de progresso — sai na foto, e este recurso não pode impedir você de desenhá-lo.

phoneCamera existe para que você mesmo possa parar de desenhar. Ele fica true desde o momento em que o visor da câmera abre até fechar, e uma única linha em um loop de desenho resolve tudo:

if LocalPlayer.state.phoneCamera then goto skip end

O próprio HUD do telefone já sai da frente: Config.Apps.Camera.HideHud oculta o minimapa e as barras de vida do jogo enquanto a câmera estiver aberta, para que a pessoa fotografe exatamente o que está enquadrando.

A lanterna é uma state bag flashlight, de modo que todos os outros clientes desenham o feixe de luz no ped daquele jogador. Ela só ilumina enquanto o telefone está na mão e se apaga quando o telefone é guardado.

exports.mic_phone:Open() / Close() / Toggle() / IsOpen()
exports.mic_phone:CanOpen() --> ok, reason
exports.mic_phone:Restricted() --> 'Dead' | 'Cuffed' | 'Swimming' | … | nil
exports.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 = })

Nada aqui passa por cima das verificações que o telefone aplica a si mesmo. Open e OpenApp recebem a mesma recusa que um jogador pressionando F1 receberia, pelos mesmos motivos — um telefone que abre enquanto seu dono está morto só porque um recurso pediu com educação não é um telefone com regras. CanOpen e Restricted existem para que você possa perguntar antes de tentar abrir e desativar um botão na interface em vez de oferecer um que não faz nada.

Os nomes das state bags coincidem com os do lb-phone onde o significado é o mesmo, portanto um script escrito para aquele telefone lê este sem precisar de alterações.