Przejdź do głównej zawartości

Eksporty i state bagi

Dwa wejścia, a tańsze na początku.

phoneapi <id> wypisuje wszystko poniżej dla jednego gracza w konsoli serwera. Jeśli coś tutaj nie odpowiada tak, jak oczekujesz, uruchom to, zanim przeczytasz dalej — to szybsze niż zgadywanie, po której stronie jest błąd.

State bag nie wymaga callbacka, eventu ani żadnych ustaleń z tym zasobem. To odczyt tabeli na tej maszynie, która pyta; przetrwa restart tego zasobu pod Twoimi stopami i działa zarówno z klienta, jak i z serwera.

-- server
if Player(src).state.onCallWith then ... end
-- client, about somebody else
if Player(GetPlayerFromServerId(id)).state.phoneOpen then ... end
BagTyp
phoneNumberstringnumer telefonu, który mają przy sobie
phoneNamestringnazwa, którą ten telefon pokazuje, gdy do kogoś dzwoni
phoneDevicestring | nilktóry to przedmiot-telefon w trybie przedmiotu. nil, gdy telefony należą do postaci
phoneOpenbooleantelefon jest wyjęty i widoczny na ekranie
onCallWithnumber | nilsource po drugiej stronie
callAnsweredbooleanktoś odebrał. false, dopóki jeszcze dzwoni
speakerphoneboolean
mutedCallbooleanwyciszyli samych siebie
otherMutedCallbooleandruga strona wyciszyła samą siebie
flashlightbooleanich latarka jest włączona. Każdy klient rysuje promień na tym pedzie
phoneCamerabooleanwizjer jest włączony: zaraz zrobią zdjęcie
phoneSignalnumberile kresek, od 0 do Config.Signal.Bars. 0 to telefon, do którego miasto nie sięga

Trzy rzeczy, które warto wiedzieć:

Zapisuje je serwer. Klient też wie to wszystko i mógłby je publikować w mniejszej liczbie linijek, ale klient może kłamać w każdej z tych kwestii, a bag, który czyta anticheat, musi być wart czytania.

Dzwonienie liczy się jako bycie w rozmowie. onCallWith jest ustawiane w chwili, gdy telefon zaczyna dzwonić, bo linia jest zajęta — to, co jeszcze nie jest prawdą, to że ktoś odebrał, i za to odpowiada callAnswered.

onCallWith może mieć wartość nil podczas prawdziwej rozmowy. Drugim końcem może być czyjaś przeglądarka, a nie gracz w mieście. callAnswered nadal ma wartość true; po prostu nie ma source’a, który dałoby się wskazać.

Bagu flashlight nie było, dopóki latarka faktycznie nie zaczęła czegoś oświetlać, na zasadzie, że bag, który zawsze ma wartość false, jest gorszy niż brak bagu — jedno to brakująca funkcja, drugie to błędna odpowiedź. Teraz coś oświetla, więc jest publikowany.

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 zwraca to samo, co usłyszałby od telefonu on sam — { ok = false, reason = 'busy' }, 'airplane', 'unknown', 'offline' — ponieważ uruchamia ten sam handler, który uruchamia własny numerator telefonu. Jest jedna ścieżka kodu i jeden zestaw reguł, w którym można się pomylić.

Eksport, którego odpowiedzią jest nil, nie zwraca żadnej wartości, a nie wartość nil. Wrzucony wprost na listę argumentów nie zamienia się w "nil" — skraca tę listę. Wpisz go najpierw do zmiennej lokalnej:

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

Pytania, na które inny zasób musiał sam wciąż odpowiadać. Każde z nich to wyszukiwanie, które telefon już wykonuje, więc jest jedna ścieżka kodu i jeden zestaw reguł, w którym można się pomylić.

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 odpowiadające nil to nie to samo co odpowiedź 0: nil oznacza, że ten serwer nigdy nie włączył baterii. Skrypt, który traktuje je tak samo, uniemożliwi komuś wezwanie karetki w mieście, w którym telefon nigdy się nie rozładowuje.

AddPhoto sprawdza adres URL względem hostów, na które ten serwer pozwala, dokładnie tak jak sprawdzane jest zdjęcie zrobione aparatem. Galeria, która wyświetli dowolny adres, to galeria, którą ktoś może wskazać na cokolwiek.

SocialPost i SetRadioChannel uruchamiają własne handlery telefonu, więc podlegają temu, czemu podlega ekran: brak konta w tej aplikacji oznacza brak posta, a kanał, którego ta praca nie może mieć, jest tu również odrzucany.

Sześć monet, wycenianych przez serwer w cyklu, do którego nikt nie ma dostępu z klienta. Wszystko poniżej jest podawane w monetach i w pieniądzach — nigdy w milionowych częściach, w których liczy zasób — więc skrypt płacący komuś ćwierć czegoś pisze 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 odmawia zamiast zejść poniżej zera, więc jego odpowiedź to zarazem “czy było ich stać” — o co faktycznie chce zapytać sklep sprzedający coś za monety:

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

Portfel należy do postaci albo do telefonu, a to ustawia Config.Apps.Crypto.Wallet. W trybie urządzenia kradzież telefonu kradnie razem z nim portfel; w trybie postaci skradziony telefon pokazuje złodziejowi pustą aplikację. Eksporty przyjmują source w obu przypadkach i nie muszą wiedzieć, który to.

phonecrypto w konsoli wypisuje rynek, phonecrypto <id> wypisuje to, co posiada dany gracz, a phonecrypto price <coin> <money> przesuwa cenę ręcznie.

Oba włącza się lub wyłącza w configu — shared/config/battery.lua i Config.Flashlight — a domyślnie bateria jest wyłączona, a latarka włączona.

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

Ładowarka nie musi być przedmiotem w kieszeni. Config.Battery.Charger.Item daje Ci jedną od ręki, a ChargePhone służy do wszystkiego innego: kabel w samochodzie, wtyczka na ścianie, prop za biurkiem.

phonebattery [charge|unplug|full|empty] <id> ustawia poziom, i tak testuje się baterię, nie testując własnej cierpliwości.

Admini w grze (i konsola) mają kilka komend do sprawdzania, dlaczego coś nie robi tego, co zamierzałeś. Tylko patrzą albo celowo zmieniają telefon jednego gracza; żadna z nich niczego nie wymyśla.

phoneapi <id>state bagi, eksporty i bateria jednego gracza
phoneappslista aplikacji tak, jak rozwiązał ją config
phonedevice [backup|restore] <id>który telefon trzymają i jakie kopie przechowują
phonebattery [charge|unplug|full|empty] <id>

phoneapi wypisuje też, czy ten telefon ma zasięg i co jest dla niego wstrzymywane, czyli łańcuch od “brak zasięgu” do “powiadomienie dotarło” w dwóch linijkach. Przechodzi przez cztery miejsca i nie było sposobu, by zobaczyć, w którym coś poszło nie tak.

Poziom nie jest timerem. Wiersz zapamiętuje, gdzie był, kiedy i co telefon robił — był otwarty, w kieszeni czy się ładował — a aktualny poziom to arytmetyka na zegarze. Stu graczy kosztuje sto liczb zamiast stu timerów, a telefon pozostawiony na ładowaniu przez restart serwera wraca naładowany, a nie zamrożony.

(To i reszta współistnienia — ped, prop, tryb przedmiotu — zebrane są w Twój zasób obok telefonu.)

Zdjęcie obejmuje cały kadr. Cokolwiek Twój zasób rysował w chwili naciśnięcia migawki — podpowiedź pomocy, etykietę markera, pasek postępu — jest na zdjęciu, a ten zasób nie jest w stanie Ci tego uniemożliwić.

phoneCamera jest po to, byś mógł powstrzymać się sam. Ma wartość true od momentu otwarcia wizjera aż do jego zamknięcia, a jedna linijka w pętli rysowania to całe rozwiązanie:

if LocalPlayer.state.phoneCamera then goto skip end

Własny HUD telefonu już ustępuje miejsca: Config.Apps.Camera.HideHud ukrywa minimapę i paski zdrowia gry, dopóki aparat jest włączony, więc to, co ktoś kadruje, jest tym, co dostaje.

Latarka to state bag flashlight, więc każdy inny klient rysuje promień na pedzie tego gracza. Świeci tylko wtedy, gdy telefon jest wyjęty, i gaśnie, gdy telefon znika.

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 = })

Nic tutaj nie wykracza poza kontrole, które telefon stosuje wobec siebie. Open i OpenApp dostają tę samą odmowę, jaką dostałby gracz naciskający F1, z tych samych powodów — telefon, który otwiera się, gdy jego właściciel nie żyje, bo zasób grzecznie o to poprosił, nie jest telefonem z regułami. CanOpen i Restricted istnieją po to, byś mógł zapytać zanim poprosisz, i wyszarzyć przycisk zamiast oferować taki, który nic nie robi.

Nazwy bagów odpowiadają tym z lb-phone tam, gdzie znaczenie jest takie samo, więc skrypt napisany pod tamten telefon czyta ten bez zmian.