Exports y state bags
Dos formas de entrar, y la barata primero.
phoneapi <id> imprime todo lo de abajo para un jugador, en la consola del servidor. Si algo de aquí no responde lo que esperas, ejecútalo antes de seguir leyendo: es más rápido que adivinar qué lado está mal.
State bags: las respuestas que no cuestan nada
Sección titulada «State bags: las respuestas que no cuestan nada»Un state bag no necesita callback, ni evento, ni ningún acuerdo con este recurso. Es una tabla que se lee en la máquina que pregunta, sobrevive a que este recurso se reinicie por debajo de ti y funciona igual desde el cliente que desde el servidor.
-- serverif Player(src).state.onCallWith then ... end
-- client, about somebody elseif Player(GetPlayerFromServerId(id)).state.phoneOpen then ... end| Bag | Tipo | |
|---|---|---|
phoneNumber | string | el número del teléfono que lleva encima |
phoneName | string | el nombre que muestra ese teléfono cuando llama a alguien |
phoneDevice | string | nil | qué ítem de teléfono es, en modo ítem. nil cuando los teléfonos pertenecen a personajes |
phoneOpen | boolean | el teléfono está fuera y en pantalla |
onCallWith | number | nil | el source del otro extremo |
callAnswered | boolean | alguien ha contestado. false mientras sigue sonando |
speakerphone | boolean | |
mutedCall | boolean | se ha silenciado a sí mismo |
otherMutedCall | boolean | el otro extremo se ha silenciado |
flashlight | boolean | tiene la linterna encendida. Todos los clientes dibujan el haz en ese ped |
phoneCamera | boolean | hay un visor abierto: está a punto de hacer una foto |
phoneSignal | number | cuántas barras, de 0 a Config.Signal.Bars. 0 es un teléfono al que la ciudad no puede llegar |
Tres cosas que conviene saber:
Los escribe el servidor. El cliente también lo sabe todo y podría publicarlo en menos líneas, pero un cliente puede mentir sobre todo ello, y un bag que lee un anticheat tiene que merecer la pena leerlo.
Que esté sonando cuenta como estar en una llamada. onCallWith se establece en el momento en que suena un teléfono, porque la línea está ocupada; lo que todavía no es cierto es que alguien haya contestado, y eso es callAnswered.
onCallWith puede ser nil durante una llamada real. El otro extremo puede ser el navegador de alguien en lugar de un jugador en la ciudad. callAnswered sigue siendo true; simplemente no hay ningún source que nombrar.
No hubo bag flashlight hasta que la linterna iluminó algo de verdad, porque un bag que siempre es false es peor que un bag que no existe: uno es una función que falta, el otro es una respuesta incorrecta. Ahora ilumina algo, así que se publica.
Exports de servidor
Sección titulada «Exports de servidor»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 devuelve lo que se le habría dicho al propio teléfono ({ ok = false, reason = 'busy' }, 'airplane', 'unknown', 'offline'), porque ejecuta el mismo handler que ejecuta el marcador del propio teléfono. Hay un solo camino de código y un solo conjunto de reglas que pueden fallar.
Un export cuya respuesta es nil no devuelve ningún valor, no un valor nil. Si lo metes directamente en una lista de argumentos no se convierte en "nil": acorta la lista. Guárdalo antes en una variable local:
local with = exports.mic_phone:CallWith(src) -- and not print(exports.mic_phone:CallWith(src), x)El teléfono en sí
Sección titulada «El teléfono en sí»Las preguntas que otros recursos tenían que responderse por su cuenta una y otra vez. Cada una es una consulta que el teléfono ya hace, así que hay un solo camino de código y un solo conjunto de reglas que pueden fallar.
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 itQue GetBattery responda nil no es lo mismo que responder 0: nil significa que este servidor nunca activó la batería. Un script que los trata igual impide que alguien llame a una ambulancia en una ciudad donde el teléfono nunca se queda sin batería.
AddPhoto comprueba la URL contra los hosts que permite este servidor, exactamente igual que se comprueba una foto hecha con la cámara. Una galería que muestra cualquier dirección es una galería a la que alguien puede apuntar a cualquier cosa.
SocialPost y SetRadioChannel ejecutan los handlers del propio teléfono, así que obedecen lo mismo que obedece la pantalla: sin cuenta en esa app no hay publicación, y un canal que este trabajo no puede tener se rechaza aquí también.
El mercado
Sección titulada «El mercado»Seis monedas, con precio fijado por el servidor en un temporizador que nadie puede alcanzar desde un cliente. Todo lo de abajo está en monedas y en dinero (nunca en las millonésimas con las que cuenta el recurso), así que un script que paga a alguien un cuarto de algo pone 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 se niega en lugar de quedar en negativo, así que su respuesta también es “¿ha podido pagar?”, que es justo lo que quiere preguntar una tienda que vende algo a cambio de monedas:
if exports.mic_phone:RemoveCrypto(src, 'omen', price) then giveThem(thing) endEl monedero pertenece al personaje o al teléfono, y eso lo decide Config.Apps.Crypto.Wallet. En modo dispositivo, robar un teléfono es robar la cartera con él; en modo personaje, un teléfono robado le muestra al ladrón una app vacía. Los exports reciben un source en ambos casos y no necesitan saber cuál es.
phonecrypto en la consola imprime el mercado, phonecrypto <id> imprime lo que tiene ese jugador y phonecrypto price <coin> <money> lo mueve a mano.
Batería y linterna
Sección titulada «Batería y linterna»Las dos se activan o desactivan desde la config (shared/config/battery.lua y Config.Flashlight): la batería viene desactivada por defecto y la linterna activada.
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 cargador no tiene por qué ser un ítem en el bolsillo. Config.Battery.Charger.Item te da uno de serie, y ChargePhone es para todo lo demás: un cable en un coche, un enchufe en la pared, un prop detrás de un escritorio.
phonebattery [charge|unplug|full|empty] <id> la mueve, que es como pruebas una batería sin poner a prueba tu paciencia.
Comandos de administrador
Sección titulada «Comandos de administrador»Los administradores en el juego (y la consola) tienen unos cuantos comandos para averiguar por qué algo no hace lo que querías. Solo miran, o cambian el teléfono de un jugador a propósito; ninguno se inventa nada.
phoneapi <id> | state bags, exports y la batería de un jugador |
phoneapps | la lista de apps tal como la ha resuelto la config |
phonedevice [backup|restore] <id> | qué teléfono tiene en la mano y las copias que conserva |
phonebattery [charge|unplug|full|empty] <id> |
phoneapi también imprime si ese teléfono tiene señal y qué se le está guardando, que es la cadena desde “sin señal” hasta “ha llegado la notificación” en dos líneas. Pasa por cuatro sitios y no había forma de ver cuál había fallado.
El nivel no es un temporizador. Una fila recuerda dónde estaba, cuándo y qué estaba haciendo el teléfono (abierto, en el bolsillo o cargando), y el nivel actual es aritmética sobre el reloj. Cien jugadores cuestan cien números en lugar de cien temporizadores, y un teléfono que se quedó cargando durante un reinicio del servidor vuelve cargado en lugar de congelado.
Que tu aviso no acabe en el álbum de fotos de nadie
Sección titulada «Que tu aviso no acabe en el álbum de fotos de nadie»(Esto y el resto de la convivencia —el ped, el prop, el modo ítem— está reunido en tu recurso junto a él.)
Una fotografía es el encuadre entero. Lo que tu recurso estuviera dibujando cuando saltó el obturador (un aviso de ayuda, una etiqueta de marcador, una barra de progreso) sale en la foto, y este recurso no puede impedir que lo dibujes.
phoneCamera está ahí para que puedas impedírtelo tú. Es true desde el momento en que se abre un visor hasta que se cierra, y una línea en un bucle de dibujo es todo el arreglo:
if LocalPlayer.state.phoneCamera then goto skip endEl HUD del propio teléfono ya se aparta: Config.Apps.Camera.HideHud oculta el minimapa y las barras de salud del juego mientras la cámara está abierta, así que lo que alguien encuadra es lo que obtiene.
La linterna es un state bag flashlight, así que todos los demás clientes dibujan el haz en el ped de ese jugador. Solo alumbra mientras el teléfono está fuera, y se apaga cuando el teléfono se guarda.
Exports de cliente
Sección titulada «Exports de cliente»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 = })Nada de esto va más allá de las comprobaciones que el teléfono se aplica a sí mismo. Open y OpenApp reciben la misma negativa que recibiría un jugador pulsando F1, por las mismas razones: un teléfono que se abre mientras su dueño está muerto porque un recurso lo ha pedido amablemente no es un teléfono con reglas. CanOpen y Restricted están ahí para que puedas preguntar antes de pedir, y poner en gris un botón en lugar de ofrecer uno que no hace nada.
Nombres
Sección titulada «Nombres»Los nombres de los bags coinciden con los de lb-phone cuando el significado es el mismo, así que un script escrito para ese teléfono lee este sin cambios.