Migration
Release notes
Last updated on
What changed in opx_infinity and opx_lib, newest first, starting with the replacement of the opx77_* resources in September 2026.
Newest first. The version is the one in each resource's open77.lua.
Merged into main after 0.2.0; the manifest still says 0.2.0. Planned for the next 0.2.x.
| What players see now | Pages |
|---|
| Vehicles burn fuel while the engine runs; at 0 the engine is cut and will not stay started. The vehicle dial shows a FUEL line. AVs take no fuel. | fuel |
| Pumps at surveyed stations: E or the eye opens a menu, pay cash or bank, a cancelable bar fills the car; a cut-short bar bills what went in. | At a pump |
A Fuel can (petrolcan) bought and refilled at a pump and poured anywhere. | The jerry can |
| An owned car keeps its tank through a garage. | vehicles |
A Gas Station pin (drop_point) per open station. | blips |
| Upgrading | Do |
|---|
| No station is open out of the box. The five shipped Night City stations are placeholders, disabled at boot. | Survey them with /opx.fuel.capture and paste the lines into config/fuel.lua. Until then only a can or staff fills a tank. |
| New config | config/fuel.lua (new module); config/blips.lua category fuel; config/hud.lua VEHICLE.FUEL. Missing keys take their defaults. |
| New item | petrolcan. |
| Database | opx77_vehicles.fuel (FLOAT NULL) is added on boot by OPX.Schema.AddColumns; the database user needs ALTER. Existing cars come out at INITIAL_PERCENT. |
| New ACL | command.opx.fuel.set, command.opx.fuel.capture, command.opx.fuel.stations — grant command.opx.fuel.*. |
| New exports | GetFuel, GetFuelState (read), SetFuel (write: add your resource to SERVER.EXPORTS.WRITERS). A resource reading Entity(vehicle).state.fuel the ox way gets litres, not a percentage. |
| New event | opx:on:fuel:changed. Net events opx:net:fuel:*. |
| open77_fuel | Keep it if you like: with STAND_DOWN = true this module stops burning while it runs. See open77_fuel. |
Merged into main and shipped in the manifest version 0.2.0 (PR #123). One table per theme. Before updating a running server or a resource that calls OPX, read Upgrading from PR #95. (#98, a WebUI polish pass, is not covered here.)
| What players see now | Before | Pages |
|---|
Hunger and thirst fall on the server's clock and go up only by eating or drinking, staff (/opx.needs.set) or a server export. A modified client can no longer stay fed. | The client decayed its own needs and pushed them; the server stored whatever came. | needs |
Global chat needs a loaded character: on the character screen a line answers you speak once your character is in the city. Staff (STAFF_PERMISSION) still can. | Any admitted connection spoke to everyone. | chat |
| An outfit share code is typed inside a shop's fitting room and the pieces are billed at that shop's prices on Save. | A code dressed you for free, anywhere. | shops |
| A give asks the receiver: an Accept / Refuse card with the item, the count and where the giver stands, never a name. 15 s to answer. | The stack moved on the giver's press alone. | Give and drop |
| Change | Pages |
|---|
Bandage, Bounce Back and MaxDoc heal 15 %, 40 %, 75 % of maximum health (HEALING); refused at full health and while down; nothing is used up unless the heal goes through. | Medical items |
| Withdrawing notes shows one toast; notes handed over are told to both players as money. Drawing a weapon closes the inventory. The give list refreshes every time a slot card opens. | inventory |
| A shop uniform is billed on Save, not when picked; Cancel costs nothing. The fitting room shows the bill before Save (On Save: 1,050 €$). A long browse no longer ends in "Your outfit was not saved." (kept alive up to 30 min). | shops |
| Contacts: DELETE on each row (two presses). A full list (64) refuses new contacts instead of dropping the oldest; offline contacts show greyed. X is calling you. / Calling X... toasts are gone. | calls |
| Garage toasts name the car (Brought out Archer Hella · AA111AA., Recalled … here.). On foot, the garage door opens the list (Open your garage). | garages |
| Getting into a car with a crate puts it down at your feet; it no longer flies back to its point. | hauling |
| The dealership says You bought X first, then where the car waits. | dealership |
| The spawn menu waits for a slow name form or fitting room (up to 30 min) instead of placing the player under it. | spawn |
Job, grade and spawn-place names are translated (character.job.<job>, character.grade.<job>.<level>, spawn.place.<id>); English text unchanged. | character, spawn |
| Change | Pages |
|---|
Two staff door writes at once run one after the other; a wait over 5 s answers doorlock.error.busy. A numeric door code of 2^63 or more is refused. | doorlock |
| Two players fetching at one garage get two exits (a bay is promised while the car spawns). | garages |
| Crafting and hauling refunds and pay go to the character, not the connection: leaving or switching character mid-action loses nothing and pays nobody else. | crafting, hauling |
Money balances stop at ±(2^53−1) (money.tooLarge); a staff rename can no longer break saves (name column cut to 64). | character |
Names refuse invisible and direction-changing characters (character.nameInvisible); accents and other alphabets still pass. | character |
Broken UTF-8 in chat, contact names and labels is mended to ? instead of breaking saves. | OPX.Text.Clean |
A chat config missing MAX_LENGTH / RATE_MS / READY_MS takes the defaults instead of silencing chat. | chat |
| Dealership deposits and crafting placements are idempotent: a lost answer never pays twice or refunds an order that exists. | dealership, crafting |
Stashes opened by players are put away when idle; a lift finishes its trip when a rider leaves; two vehicle registrations for one character cannot pass the cap (vehicle.registering). | inventory, elevators, vehicles |
| Change | Pages |
|---|
A downed player cannot craft, buy or sell at a dealer, use a garage, a shop, a fitting room, a car key or a stash (error.incapacitated and module codes). One check for all: OPX.Life.Down. | Error codes |
A contact offer out of reach answers only tooFar: it can no longer probe whether a stranger is down, loading or on a call. | calls |
opx:net:vehicles:spawn / store removed: no car out from anywhere, none put away from across the map. | vehicles events |
Refusals name back only a key the server knows; shop outfit ids must be whole numbers; the inventory spends its rate window before reading the verb; clothing:ask has a 1 s floor. | garages, inventory |
| What | Do |
|---|
Needs are server-owned (API change). The client needs contract lost SetNeeds and AddNeeds; opx:net:needs:push and opx:net:needs:pushed are removed. The client export GetNeeds is read only. | Move needs from your server side: exports GetNeeds, AddNeeds, SetNeeds (add your resource to SERVER.EXPORTS.WRITERS for the two writes; all three must be awaited). Inside opx_infinity, the new server contract. |
New server event opx:on:needs:changed (playerId, { citizenId, values, changed, source }). | Listen there instead of a client hook. opx:net:needs:values now carries a third reason argument. |
Removed net events opx:net:vehicles:spawn, opx:net:vehicles:store. | A client that fired them gets nothing. Use a garage, or vehicles.Spawn / SetVehicleState on the server. |
Gives need consent. A player give is an offer; opx:on:inventory:items fires only when the receiver accepts. New net events giveOffer, giveAnswer, giveWithdrawn; new codes give_pending, give_busy, give_gone, give_changed. | A script that watched for a give must wait for the event. AddItem and staff gives ask nobody. |
Chat needs a character. opx:net:chat:say from a connection with no loaded character is refused chat.notLoaded. | Give staff accounts STAFF_PERMISSION. Server code keeps using the chat contract or BroadcastChat. |
Share codes are redeemed only in a shop's fitting room and billed (shops.redeemAtShop, shops.redeemInRoom). | Tell players; nothing to configure. |
Downed refusals. Dealership, garages and vehicle keys answer error.incapacitated; crafting crafting.downed; shops shops.downed; gunsmith gunsmith.dead; inventory OpenStash (and the export) dead. | Handle the new codes if you branch on them. |
| New config | config/inventory.lua HEALING; config/chat.lua STAFF_PERMISSION. Missing keys take their defaults. |
| Removed config | config/needs.lua PUSH_MS, PUSH_DELTA. Delete them. |
| New ACL | command.opx.needs.set for /opx.needs.set. |
| New codes | money.tooLarge (from AddMoney / RemoveMoney), character.nameInvisible, vehicle.registering (from AddVehicle), doorlock.error.busy (door write exports), calls.error.contactsFull, inventory.error.full_health, error.incapacitated. |
| Changed behaviour | Medical items heal and are refused at full health; uniforms are billed on Save; a full contact list refuses instead of trimming; the garage door on foot opens the list; a crate is put down when its carrier gets in a car; a player-opened stash can be unloaded when idle; a lift no longer returns to floor 0 when a rider leaves. |
| Removed locale keys | calls.placed, calls.ringing, garages.prompt.driveIn. garages.broughtOut / storedAway take {vehicle} instead of {plate}. An override of them must follow. |
New in lib/ and core | OPX.Life.Down, OPX.Storage.EnsureColumn, OPX.Schema.AddColumns. Ask OPX.Life.Down at any door a player could press from the floor. |
| Contracts | appearance client PriceWardrobe, server PricedRoom and the bill option of AllowClothingSave; needs server contract. |
| Other new net events | opx:net:appearance:roomAlive, opx:net:calls:forget (page channel calls:forget), opx:net:spawn:waiting. The garages, crafting, dealership and gunsmith answers echo the key only when the server knows it. |
| Database | opx77_crafting_orders.token is added on boot by OPX.Schema.AddColumns; if it cannot be added, boot is degraded (schema failed). The database user needs ALTER. |
| Framework docs moved (PRs #121, #122) | The framework README is now a front page; its manual is docs/MANUAL.md and the workflow CONTRIBUTING.md. New: docs/IN_GAME_CHECKLIST.md, docs/PLATFORM_REQUESTS.md. |
Merged into main; the manifest version is still 0.1.2. One table per theme. Before updating a running server or a resource that calls OPX, read Upgrading from PR #87.
In roleplay a name is learnt by meeting someone. The data below is removed at the server, not hidden on the page. Overview: Privacy.
| What players see now | Before | Pages |
|---|
| No name over anyone's head: every remote plate is hidden. | The character name (and before that the account gamertag) over every player. | character client |
Chat lines signed Player #12 / Joueur #12. | The character name, else the gamertag, else part of the account id, to the whole server. | chat |
| A call names a person only if they are in your contacts; otherwise Unknown caller. "X joined / hung up / declined" follows the same rule. | A third person on a call learnt every name; a contact offer told its sender the other's name. | calls |
The give list reads To your left · 1.2 m with #12. | #12 (PR #90 briefly showed the character name). | inventory |
A dealership offer reads You are being offered a {model}; the seller's list Sell to the person in front of you (#12). | The seller's and buyer's account names. | dealership |
A duo emote invitation reads The person next to you invites you: {emote}. | The asker's name, and the other's name in every notice. | animations |
| The eye's Show server ID row copies the server id only. | Identifiers: server id and citizen id. | target |
| Staff still see who is who on their name tags. | The tags read the public state bag. | admin |
| Under the hood | Pages |
|---|
The player state bag carries no key (citizenId, name, charInfo, job, gang, username, life are cleared once). GetPlayerState / GetPlayerName / GetPlayerIdentity stay on the client contract and answer empty. | State bag, Writing a resource |
Probes closed: a give checks reach first (too_far for any id out of reach); a dealership offer always goes out and only the buyer learns they cannot pay; an id outside the showroom always answers buyerNotInZone. | inventory codes, dealership codes |
The doorlock state push is { id, state }: no by. The staff tag list now carries character and citizenId. | doorlock events, admin events |
| Change | Pages |
|---|
| E (garages, dealership, fitting rooms, teleports, lifts, doors): when two spots are in reach, the nearer one acts and only its key-strip row is drawn. A lockpick under way holds E. With the list open, E closes it. | One press, one owner |
| X: a cancelable bar > ringing call (decline) > outgoing call (withdraw) > carried crate > own emote > call in progress (hang up). Cancelling an eat during a call no longer hangs up; X with a crate during a call only drops the crate. | progress, calls |
| Y answers a ringing call and the hotbar peek stays silent; otherwise Y peeks. | calls |
| Escape closes only the top layer (menu, form, panel, door panel, chat, call hologram); inside a submenu it goes back one level. | The WebUI page, menu |
| H, Y and X ignore a press while chat or a form holds the keyboard. E during a bar or while down no longer toasts anywhere but at a teleport or lift. The crate-drop hint names the key the player rebound. | calls, hauling |
Ranks are config: KEY_PRIORITY. Only mappings bound to the same physical key contend. | Core configuration |
| Change | Pages |
|---|
Every staff and player row names its action (Spawn a vehicle, Give or take money, Lock door / Unlock door, Lock vehicle / Unlock vehicle, Take elevator: {place}, Open fitting room everywhere). The confirmation's second row repeats the action. Saved staff spots and outfits are deleted only after a confirmation. | admin, target |
Menu rows take confirm = true or { title, yes, no }: a two-row level whose first row keeps things as they are. | Confirm rows |
A danger eye row asks for a second click (Confirm: …) unless it sets confirm = false; any row can opt in. This includes rows other resources add with AddTarget. | target row, AddTarget |
One toast per refusal: no raw codes on screen, no second toast after OPX.Refuse. OPX.Refuse takes params. | OPX.Refuse |
One toast kind per situation: too fast, rate limited and busy are warnings; too far, not allowed, no permission are errors (OPX.Result.Kind). | OPX.Result.Kind |
Money reads 4,500 €$ / 4 500 €$, €$ (bank) / €$ (banque) everywhere (OPX.Locale.Money). The HUD's money captions are translated. | OPX.Locale.Money |
The garage list reads Villefort Cortes · 8XK2L0P, the same name as the key (OPX.Vehicle.DisplayName). | garages |
French catalogues fixed (accents, vous); every key exists in both languages; no raw key flashes before the catalogue arrives. | Locales |
| The gunsmith's Use workbench / Open armoury stock rows work (they did nothing). | gunsmith |
| Change | Pages |
|---|
| Spawn: a Where I left off card for a character with a stored position. "Spawned at …" appears after the move; a refused move says so. | spawn |
Inventory: Give and Drop on a stack ask how many; refusals say the real reason once; the receiver reads only +2 Water; four hotbar presses inside one bite eat one item; the trunk panel's Close works; Escape closes the innermost dialog first. | Give and drop |
| Calls: a ring that dies updates the caller and files a missed call on both sides; a disconnect while ringing is said; a contact offer says sent / declined / not answered; going down closes the hologram. | calls |
| Chat: a second line within 800 ms says it was not sent. | chat |
| Garages: sitting in someone else's car at the entry says Only your own vehicles go in here; one toast naming the garage. Dealership: a failed hand-over says the car waits in the garage; a zero commission is worded. | garages, dealership |
| Hauling: the truck leaving mid-load ends the load; a refused pickup frees the hands at once; the drop hint appears after the lift. | hauling |
| Money notes: You took 1,000,000 €$ out as notes. / You put 4,500 €$ in notes back in your wallet. | inventory |
Shops: You cannot afford that: {total} needed; a paid uniform says Paid {total} at {shop}; a queued outfit save is not dropped. Crafting: going off duty closes the bench. | shops, crafting |
Join: the fitting room's timer waits while the name form is up; a short name reads Each name takes between 2 and 32 letters. /opx.delete <id> needs confirm. Doorlock: a pick that opens and breaks says both. | character, doorlock |
| Change | Pages |
|---|
The server allows REACH_SLACK (1 m) beyond the drawn radius at garages, dealers, fitting rooms, teleports, lifts, shops, crafting and gunsmith benches and vehicle keys: a press at the edge of a marker is no longer refused. | OPX.Spots.ServerReach |
Lifts scan every 500 ms (SCAN_MS, was 2000); teleports allow 6 requests per window (REQUESTS_PER_WINDOW, was 4). Lockpicking uses doorlock's REQUEST_MS. A downed rider reads Not while you are down. | elevators, teleports |
| Character code fallbacks match the config (autosave 300 s, heading report 5000 ms, 3 slots, row ceiling 60). | character |
| Change | Pages |
|---|
New server exports: reads (GetPlayers, GetJobs, GetGangs, GetItem(s), GetInventory, CanCarryItem, vehicles and keys), runtime items (RegisterItem), use handlers, OpenStash, Notify, server-judged progress bars, crafting benches. | Server exports |
New client exports: eye rows (AddTarget…), key-strip groups (ShowPrompt…), panels, crafting bench, local reads. | Client exports |
New public events, e.g. opx:on:inventory:items, opx:on:progress:finished. | Events |
| What | Do |
|---|
Doorlock contract codes renamed (API change). CreateDoor / EditDoor / RemoveDoor and the doorlock contract's Create / Edit / Remove answer doorlock.error.<code> instead of doorlock.<code> (e.g. doorlock.save_failed → doorlock.error.save_failed, doorlock.bad_door → doorlock.error.bad_door). | Update any res.error == 'doorlock.…' comparison. doorlock.unknownDoor, doorlock.badState, doorlock.noDoor, doorlock.notStaff are unchanged. See CreateDoor. |
doorlock.bad_<field> → doorlock.error.bad_field on an edit with a wrongly typed field; the field is in res.detail. | Branch on bad_field and read detail. |
Eye danger rows confirm by default (API behaviour). A row added with AddTarget and danger = true now needs two clicks. | Add confirm = false to keep one click. |
Player state bag is empty. Open77.state.player(id) no longer has citizenId, name, charInfo, job, gang, username, life. | Use the client export GetPlayerData (own player) or the server exports (others). Never relay a name to a stranger. |
| Net payloads changed (module code and anything listening to them). | opx:net:animations:invite lost its name argument: it is now (inviteId, what, inviteMs); opx:net:animations:notice params carry no name; opx:net:dealership:offered has no seller; opx:net:doorlock:state has no by; opx:net:calls:state names are per viewer (Unknown caller); opx:net:inventory:nearby rows are { id, distance, side? }; opx:net:chat:message author is Player #<id>; opx:net:spawn:close comes twice (second with result, placed); opx:net:spawn:offer has resume; opx:net:runtime:notify has params and a warning kind. |
Toast kinds. OPX.Refuse, OPX.NotifyLocale and OPX.Toast.Locale turn error/warning into the situation's kind (OPX.Result.Kind). | Nothing, unless you relied on "too fast" being red. |
OPX.Refuse(source, code, operation, icon, params) | Optional 5th argument. Remove a NotifyLocale that followed a Refuse only to carry params. |
| New config | config/client.lua KEY_PRIORITY; config/shared.lua REACH_SLACK. Missing keys take their defaults. |
| Changed defaults | elevators.SCAN_MS 2000 → 500; teleports.REQUESTS_PER_WINDOW 4 → 6. A copied config keeps the old value: update it. |
| Removed config | character.ORIGINS (nothing read it). Delete the block. |
| Removed locale keys | 26 unused keys (#90), e.g. target.hint, target.back, inventory.title, inventory.ui.slots, calls.incoming.*, calls.live.*, inventory.notify.received; chat.author.unknown → chat.author.player; target.identify.* reworded (no {citizen}). An override of one of them is now unused. |
New in lib/ | OPX.Locale.Money, OPX.Vehicle.DisplayName, OPX.Result.Kind, OPX.Spots.ServerReach, the key contest (OPX.Spots.Key.Owns, Contend, Shows, Layer, TopLayer…). A module sharing E or X should enter the contest. |
progress.State() | Adds cancelable, cancelKey, cancelledAtMs. |
/opx.delete <id> | Now /opx.delete <id> confirm. |
Merged into main; the manifest version is still 0.1.2. One table per group of PRs, newest first. Before updating a running server, read Upgrading from PR #57.
| Change | Pages |
|---|
The emote picker (F3, /e) offers every platform animation: 104 on op77.123, in 12 families (Sitting, Dance and music, Work, On the move…). Animations a newer build adds appear at the next start. One-shot gestures (wave, shrug…) play once for their real length; the picker shows 16 per page and has a search. | animations, catalogue |
Emotes with a nearby player: carry or be carried, escort, hand something over, look after someone, two shortcuts, or any two animations, one each. From the picker's With a nearby player or /e with <move|name> [name]. | With a nearby player |
The nearest player within 3 m is invited and answers from a menu or with /e accept / /e decline; an invitation expires after 15 s, the distance is checked again on accept, and the stop key ends the pair for both. Invitations count against RATE_LIMIT. | animations |
New config PLATFORM and SHARED; DISABLED also takes platform profile ids and paired-move ids. New events opx:net:animations:duo, invite, uninvite, reply, notice; offer now arrives in parts. | Configuration, Events |
| Change | Pages |
|---|
| New module doorlock, a port of ox_doorlock: doors open by job, gang, grade, key item, character or code. Press E at the door or use the eye's Lock door / Unlock door. Doors can relock themselves and play a sound. | doorlock |
| A pickable door takes a lockpick: one progress bar per step, the rolls are made by the server. The lockpick must still be in the bag, and the player not down, when the attempt ends. | At a door |
Staff panel: /opx.doorlock [closest], the F9 World → Manage door locks row or the eye's Manage door. A searchable, paged table of doors; settings in tabs (General, Characters, Groups, Items, Lockpick, Sound); Pick in world, Give me a key, copy and apply settings. | Staff panel |
Admins are asked for the key or code like anyone else. The bypass is the plain right opx.doorlock.bypass and only works with STAFF_BYPASS = true (off by default). Staff holding command.opx.doorlock.save see the door's code in the panel. | Who may turn a door |
Two backends: networked (the platform's open77_doors, refused to everyone server-side) and local (each client holds its own copy — weaker). | Backends |
| Doors saved by the first version carry over at boot; keys already cut keep opening their door. | Migration |
Creator exports GetDoor, GetDoorFromName, GetAllDoors, SetDoorState, SetDoorLocked, CreateDoor, EditDoor, RemoveDoor, and the public server event opx:on:doorlock:changed. | Server exports → Doors, Events |
| Change | Pages |
|---|
| Staff menu lists are paged at 20 rows and redraw on their own thread; screens built over a whole catalogue no longer close the menu. | admin |
New raw rights opx.admin.immune (staff commands refuse you, target_protected) and opx.admin.override (act on an immune player, audited). command.* grants do not include them. | Raw rights |
Grants are narrower: bag commands on a weapon item also need the matching weapon.* command; observe without self.noclip ends after 2 minutes; a typed vehicle id must be within VEHICLES.REACH (100 m) unless you hold opx.admin.vehicle.anywhere; player.health 0 needs the kill grant (kill_not_granted). | What a grant does not imply |
One heavy request per operator at a time (busy); one announcement per 10 s server-wide (ANNOUNCE.COOLDOWN_MS). Bag views, character lists and finds are audited. | admin |
A prompt shown to -1 is refused instead of reaching everyone; the pause key asks a dismiss = 'ask' panel before closing it; the menu spec takes yield for long lists. | prompts, panel, menu |
| Change | Pages |
|---|
| Holocalls go to contacts only; offering your contact again to the same player waits 30 s. A 3-way leave says who hung up. | calls |
Dealership: the offer screen closes when the offer is withdrawn (new opx:net:dealership:withdrawn); a bought car is handed over beside the dealer if the spot is taken, or waits in your garage. Refunds and commissions reach the right character, even offline. | dealership |
Garages refuse to store a car with passengers (garages.passengers). Cars out at a server stop keep their condition. | garages, vehicles |
Hauling only sells from a truck you own or hold a key to (new server contract vehiclekeys.Holds). | hauling, vehiclekeys |
Lifts: E away from a lift says nothing; lifts no longer go dead after 10 minutes and are reclaimed after a restart; a downed player cannot ride. Teleport pads no longer stick on "already on your way". weather.SetTime is validated. | elevators, teleports, weather |
Creators: one marker, key and scan helper for every spot module, OPX.Spots.* (lib/client/spots.lua). | Spots on the client |
| Change | Pages |
|---|
| The inventory key just opens the bag; it never picks up a pile you stand next to. | inventory |
Every droppable item shows a pile prop the platform hosts (MODEL aliases; a weapon on the ground is a decorative prop). | Pile models |
Bags, trunks and stashes with more than 12 stacks save again. An item in use cannot be moved, split, sorted or used twice (in_use); moving a drawn weapon puts it away first (holstering). You only see piles in your own instance (new opx:net:inventory:bucket). | inventory, Events |
| A money hook can no longer let one balance pay twice; crafting queues cannot exceed their cap; shop doors and the gunsmith chest have cooldowns; lockpicks are re-checked when an attempt ends. | character, crafting, gunsmith |
| Change | Pages |
|---|
| Joining no longer drops your face or leaves you in the default look: the face goes on once the platform has reset the body. A character switch or a resource restart no longer leaves you invisible to others. | Joining the world |
| Fitting-room purchases are billed by the server before the look is saved: if you cannot pay, your old clothes come back; if the save fails, you are refunded. Saved outfits and share codes grant one save of exactly that look. | shops, appearance server |
| Your look is sent to others at most every 2 s, and an unchanged look is not sent again. Quitting while down no longer gives a free revive. | appearance events, downed |
| Change | Pages |
|---|
| Callbacks that ran past the client's per-resume instruction budget were split: the staff search no longer closes the menu, F9 no longer needs two presses. Joining, blips, the fitting room, panels, chat commands and garage lists are lighter. | Two runtimes |
| For module authors: keep handlers cheap and yield in loops; the test suite has a budget meter. | Writing a module, Contributing |
| Change | Pages |
|---|
Server toasts appear at the configured position (NOTIFY_POSITION, now top_right); a long toast is cut (title 96 bytes, message 384) instead of refused. | OPX.Notify |
A refused gate release is retried, so players are not left on the loading screen. On client stop, keyboard and cursor are released first (OPX.UI.ReleaseAllFocus); Modules.Stop yields between modules. | core |
Both export halves go through one shared gate, OPX.Export; export audit lines are kept per calling resource. | core shared |
The WebUI bundle is 12% lighter; payload hardening (records(), own()) and focus fixes (hologram, an unclosable menu, keys meant for a view opened over the menu). | The WebUI page |
| Change | Pages |
|---|
New module loading (config/loading.lua): the HUD and other views step aside during the game's own loading screen, and loads in play (teleport, lift, respawn) get the OPX cover with progress. The join never shows the cover. Needs an updated client and the screen.read permission. | loading |
| Change | Pages |
|---|
Creator exports are published on hosts where exports is a callable table (Open77). Before, the boot log said the host had no exports and nothing was published. | For creators |
| The target eye: the row under the pointer steps out like the menu's chosen row, and every row of a column appears together. | target |
| A menu reopened by a form's Enter no longer fires the row under the cursor: a late press waits for its release. | menu |
Most of this is drop-in. Check these:
| What | Do |
|---|
SHARED.NOTIFY_POSITION = 'top-right' | Now spelled 'top_right'; the hyphen is still accepted. |
config/doorlock.lua from PR #70 | Rewritten in PR #80: LOCKPICK.ITEM → LOCKPICK.ITEMS (list), LOCKPICK.DEFAULT_DIFFICULTY → LOCKPICK.DEFAULT, LOCKPICK.BREAK_CHANCE → { SUCCESS, FAIL }, SOUNDS → { LIST, DEFAULT = { LOCK, UNLOCK } }; in DOORS, X/Y/Z → COORDS, LOCKED → STATE, DIFFICULTY → LOCKPICK_DIFFICULTY. Saved doors carry over. See Migration from the first table. |
| Staff opening every door | STAFF_BYPASS is now false and the right is opx.doorlock.bypass (was command.opx.doorlock.bypass). Grant it and set STAFF_BYPASS = true to keep the old behaviour. |
| Staff immunity | opx.admin.immune, opx.admin.override and opx.admin.vehicle.anywhere are raw rights: add them to acl.jsonc explicitly. |
| New config | config/loading.lua; admin: PLACEMENT.OBSERVE_MS, ANNOUNCE.COOLDOWN_MS, VEHICLES.REACH, LINKS.DOORLOCK; animations: PLATFORM, SHARED. Missing keys take their defaults. |
| Removed for module code | OPX.UI.Serve (use OPX.UI.On plus OPX.UI.Answer), OPX.Surface.HasFocus, the local event opx:on:chat:submitted, the net events opx:net:shops:bill and opx:net:shops:restore; admin codes bad_amount, bad_type, vetoed, doors_unavailable. |
| Changed signatures | appearance.OpenWardrobe(playerId, options) and AllowClothingSave(playerId, owner, options) take options; weather.SetTime returns a Result; opx:net:animations:offer arrives in parts. |
Merged into main; the manifest version is still 0.1.2.
| Change | Pages |
|---|
Elevators are ridden with the key opx.elevators.use (E by default, KEY; DEFAULT = false declares none, leaving OpenPanel the only way in). A prompts row names the key within USE_RADIUS of a lift; prompts is now optional. | elevators |
Progress bars marked cancelable can be cancelled with CANCEL_KEY (opx.progress.cancel, X by default; DEFAULT = false = no player cancel). The page channel progress:cancel is gone; progress:show carries { label, durationMs, cancelable, cancelKey }. | progress |
Weather commands go through OPX.Command.Register (suggested, aliasable); answers are toasts (OPX.CommandNotice) or read-backs (OPX.CommandResult). opx:net:weather:notice and the legacy chat:ready / chat:addSuggestions handling are gone. | weather |
Command read-backs (opx:net:runtime:commandResult, OPX.CommandResult) carry kind ('info'|'error') instead of type, so a refused read-back draws in the error colour. | Runtime events |
Blips: one pin per garage location, at its menu point (the entry door is not pinned). Garage and dealer pins come from config (GARAGES, SPOTS) plus legacy rows adopted at boot, own bucket only. | blips |
An ammo slot holds at most the ammo item's AMMO.MAX (rounds per box), never above MAX_STACK. | inventory |
Dealership: server Accept(buyer, token, yes, destKey?) and client Decide(yes, garage?) take the buyer's garage (nil = vehicles default; unknown = dealership.noSuchGarage). A failed company deposit goes to the new table opx77_company_pending and is paid in by a sweep (boot, then every 60 s), never twice. opx:on:dealership:sold carries garage and pending. | dealership |
Inventory contract: new server HolsterWeapon(playerId) (errors bad_argument, not_armed). /opx.admin.weapon.holster works through it and no longer always refuses. | inventory, admin |
OPX.Storage.EnsureIndex and OPX.Schema.AddIndexes add an index to a table that predates it. idx_opx77_characters_seen is now added automatically: no manual ALTER any more. | Storage |
Config removed: SERVER.AUTOSAVE_MS, SERVER.SAMPLE_MS, the provider key of the MODULES comment in config/shared.lua (only enabled is read), the menu's STATUS_MS. | Core configuration |
Downed SUSPENDERS default is { admin = true } (opx77_admin removed). | downed |
| Change | Pages |
|---|
New server exports SetJob, SetGang, RemoveJob, RemoveGang, SetDuty, GetMetadata, SetMetadata (per-resource ext.<resource>.<key>, limits in SERVER.EXPORTS.METADATA), IsDown, Revive. New client exports Subscribe/Unsubscribe for a fixed list of client events. New codes export.badValue, export.tooLarge, export.notSubscribable. | Server exports, Client exports |
One host constant OPX.Host.KEYBINDS_CHANGED = 'open77:keybinds:changed' for every module. Four modules used to listen on onKeybindsChanged, which nothing raises, so they never redrew a key after a rebind. | Runtime events |
| Clothing pictures are named by a 10-digit hash of their slug; every shipped path stays at 47 characters or less (a test enforces it), because one over-long path fails the whole resource on a client. The calls view registers its state handler before wiring the page. | appearance |
Notes on open77_validate false positives; the admin model commands answer models_unavailable because players.setModel is in no published build. | Troubleshooting |
Merged into main after the 2026-10-02 changes below; the manifest version is still 0.1.2.
| Change | Pages |
|---|
Creator API. Other resources can call curated server exports and client exports and listen to public server events. Who may call is SERVER.EXPORTS.READ/WRITERS (writers empty by default) and CLIENT.EXPORTS.CALLERS. New codes export.callerDenied, export.badArgument, export.booting, export.mustAwait. New core function OPX.Publish. | For creators, core config |
New contract functions: character.AddMoneyOffline, character.PublicView; inventory AddToStash, RemoveFromStash, CountInStash, RemoveWhere (codes stash_namespace, stash_cap); chat server contract Send, Broadcast; vehiclekeys.Revoke, RevokeAll; vehicles.SetState (codes vehicle.impounded, vehicle.busy, vehicle.badState). | character, inventory, chat, vehiclekeys, vehicles |
New hooks job:beforeSet, gang:beforeSet (codes job.vetoed, gang.vetoed) and money:beforeAddOffline. The internal job and gang events are now also raised when a membership is removed. | character |
Calls: a Cancel button and the opx:net:calls:withdraw event take back an outgoing call without touching a live one; leaving a call withdraws a pending join invite. The unused dismiss/repop card and CARD_DWELL_S are gone. | calls |
Fitting room: garment names and pictures (garments-*.lua, web/images/clothing/, tools/generate-garments.mjs); the panel tiles spec gains optional labels and images; categories read Top, Jacket, Pants, Shoes. | appearance, panel |
Merged into main; the manifest version is still 0.1.2.
| Change | Pages |
|---|
New module vehiclekeys: keys are inventory items (vehicle_key, metadata plate, label) that lock and unlock a vehicle from the target eye. Garages' bring-out and dealership purchases hand out a key; staff get /opx.admin.vehicle.key. | vehiclekeys, garages, dealership, admin |
Inventory contract: CountWhere, TrunkLocked, AddToTrunk, RemoveFromTrunk, CountInTrunk; new refusal codes locked and hands_full. | inventory |
Hauling reworked into a crate job sold to drop-off NPCs (new IsCarrying, DROP and SELLERS events, hauling_crate item, per-site models and NPCs). The HAUL_PAY_PER_CRATE tunable is now applied. New manifest permission world.npcs. | hauling |
Hauling: an unpaid sale puts crates back anywhere they fit, and records the rest for a refund (not_paid_lost, audit event hauling.crateLost); load and sell also check the routing bucket (wrong_bucket). Vehicle keys accept any printable ASCII plate of 1–16 characters, matched exactly, and a raising lock native answers lockRefused. | hauling, vehiclekeys |
HUD: contract ApplyVanilla; the shipped config hides every vanilla HUD component except the minimap. | hud |
| Calls: one call screen (the holo); hanging up also withdraws an outgoing call that was not answered yet. | calls |
The first release of the single resource. It replaces twenty-one opx77_* resources (sixteen of them were documented on this site).
| Change | Read |
|---|
One resource, opx_infinity, with a core runtime and modules under modules/<id>/. | Overview |
Modules talk through in-process contracts (OPX.Api); no Open77 exports are published. | Modules and contracts |
Event names moved to opx:net:, opx:on:, opx:in:. | Events and channels |
Commands moved from opx77.* to opx.*, with optional short aliases. | Core configuration |
| One WebUI page for every screen. | The WebUI page |
| Garages, dealers and showroom cars are written in config; legacy tables are adopted at boot. | garages, dealership |
| Modules that had no page on the old site: spawn, entry, theme, diagnostics, downed, target, progress, panel, calls, blips, shops, crafting, gunsmith, hauling, teleports, garages, dealership, clothing, vehicles. | Modules |
The client library opx_infinity depends on. Delivered as files, loaded with require('@opx_lib'), client only. See opx_lib.