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 bagi — odpowiedzi, które nic nie kosztują
Dział zatytułowany „State bagi — odpowiedzi, które nic nie kosztują”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.
-- serverif Player(src).state.onCallWith then ... end
-- client, about somebody elseif Player(GetPlayerFromServerId(id)).state.phoneOpen then ... end| Bag | Typ | |
|---|---|---|
phoneNumber | string | numer telefonu, który mają przy sobie |
phoneName | string | nazwa, którą ten telefon pokazuje, gdy do kogoś dzwoni |
phoneDevice | string | nil | który to przedmiot-telefon w trybie przedmiotu. nil, gdy telefony należą do postaci |
phoneOpen | boolean | telefon jest wyjęty i widoczny na ekranie |
onCallWith | number | nil | source po drugiej stronie |
callAnswered | boolean | ktoś odebrał. false, dopóki jeszcze dzwoni |
speakerphone | boolean | |
mutedCall | boolean | wyciszyli samych siebie |
otherMutedCall | boolean | druga strona wyciszyła samą siebie |
flashlight | boolean | ich latarka jest włączona. Każdy klient rysuje promień na tym pedzie |
phoneCamera | boolean | wizjer jest włączony: zaraz zrobią zdjęcie |
phoneSignal | number | ile 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.
Eksporty serwerowe
Dział zatytułowany „Eksporty serwerowe”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 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)Sam telefon
Dział zatytułowany „Sam telefon”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 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 itGetBattery 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, 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 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) endPortfel 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.
Bateria i latarka
Dział zatytułowany „Bateria i latarka”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 offexports.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.
Komendy admina
Dział zatytułowany „Komendy admina”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 |
phoneapps | lista 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.
Trzymanie Twojej podpowiedzi z dala od albumu zdjęć
Dział zatytułowany „Trzymanie Twojej podpowiedzi z dala od albumu zdjęć”(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 endWł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.
Eksporty klienckie
Dział zatytułowany „Eksporty klienckie”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 = })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.