UgcIconPose holds the icon camera, model rotation (yaw/pitch/roll, YXZ) and
the crop to the projected bounds; RenderIcon uses it. The parameter list gains
the model's turn (defaults 0, so icons stay the same). POST /admin/assembly
returns a module combination's assembled .nif (turned by the build type's
AdditionalModelRotation), made on a worker and kept in a small cache.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client mounts brickdb.zip with LDD's own zip reader. It is lenient about
compression, order and extra entries, but loses the whole brick database
when the files sit under a wrapping folder or "./", use backslashes, carry
ZIP64 end records, or set the data descriptor flag with sizes still in the
local header. tools/brickdb/repack.py rebuilds the shipped layout (a
round trip gives the identical file), adds or replaces entries from an
overlay folder, and checks a zip against the reader's rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restarts can be scheduled in seconds (30 s to 24 h; minutes still accepted), with 30 second and 1 minute choices for
quick restarts. The restart banner only names who scheduled it when that is known: staff who manage restarts read it
from /api/server/restart, everyone else gets when and why from the status, which leaves the name out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selecting a brick-built model in the 3D view shows its UGC data under
Selected: icons now and before, state, waiting until, UGC id, owner,
name and description, bricks, triangles before and after hidden faces
were removed, timings, and links to the UGC viewer, NIF and LXFML, with
Make again for UGC managers. The property page's model rows have the
same block, opened on demand.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/api/ugc_links/model/:id gives what is known of a brick-built model: its
state, attempts, when it was made or waits until, owner, the name and
description it was given where it is placed, and the UGC server's
stats for it and its previous version. /api/ugc_links/file/:id/:name
hands out its NIF, previous NIF and previous icon. Both let in whoever
may view a property the model is placed on, as the icon route does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/ugc?item=<id>&kind=model|modular opens that model's or car's viewer,
looking it up when it is not on the list's first page. Opening an item
puts it in the address bar, closing it takes it out, and Back and
Forward close and reopen it, so a creation can be shared or bookmarked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The /ugc page's routes and the UGC links' icon and mesh routes each had
their own copy of the curl request and its short cache. Both use
UgcFetch now: the URL is worked out on the web thread, the request and
cache run on worker threads. Players' errors still don't name the
internal URL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client asks for a model's metadata (name, owner, behaviors and its blueprint's
bricks and box) when it shows a brick built model item's tooltip, a model on a
property or an exhibit, and shows BBB_LOADING_BLUEPRINT until it gets it. The
world server never answered. It now does as live did: UG data for the model
(found among the player's items by subkey, else among placed models) and, for a
brick built model, the blueprint data from its ugc row and LXFML.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The editor on the UGC page works for player models as well as cars and
rockets: its sliders come from /api/ugc/icon/params (UgcIconParams), it
shows the item's kind (player models, or the build type named from the
client's data), previews with the UGC server, and saves the kind's preset or
the item's own values, resets them and draws a kind's icons again.
The light settings whose defaults changed have new names (icon_world_light,
icon_sun_light, icon_shadow_strength), so the darker values in existing
ugcconfig.ini files no longer apply. Request ids are read safely (a missing
id crashed the dashboard).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A model with no bricks has nothing to make: it gets its own state
(is_optimized = 3), isn't counted as a failure or retried, shows as Empty
on the UGC page (with its own filter and count), in the status and in
Prometheus, and its downloads answer 404 like HKX. State names come from
the enum (magic_enum). Migrations dlu/mysql/87 and dlu/sqlite/70 move the
rows that failed only because they had no bricks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the 1.10.64 client tested, 3D services ignore boot.cfg's UGC and patch
server lines and ask http://127.0.0.1:80/lwoclient/UserBrickModels/...; the
UGC server now answers that path too (useful when it runs on port 80), and
the docs say to keep UGCUSE3DSERVICES=7:0 (the client builds the models from
the world's LXFML; verified that property models load) until it can.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Compared block by block with res/BrickModels/ndmade (and nif.xml for
20.3.0.9, LU's version, user version 0): every shape now has the same four
properties in the same order (material with glossiness 4, alpha blending by
the vertex alpha, specular off, vertex colors as ambient and diffuse), nodes
have flags 0x110 and shapes 0x10. A generated NIF put in place of a game
model and spawned in the 1.10.64 client renders with its colors.
Readers treat a blended shape as transparent only where it is see-through
(material or vertex alpha below 1), for the icons and the 3D view.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each model took about 18 MB (the .nif uncompressed beside its .gz, the
unbaked .nif for the dashboard), so the 2 GB cap held about 110 models and
the server kept evicting and remaking them while clients got 408s. Only the
.gz files are stored now (the dashboard's copies are inflated when asked
for), and the previous version keeps only its icon, stats and .nif.gz.
The LXFML needs no making: its downloads come from the ugc row (kept in
memory for the last few), so they're never waited for or evicted. Every
client download is logged with its answer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brighter icons: a world light, a fill from the camera, a highlight, exposure
and contrast; with the defaults the icons' mean luminance matches the game's
own model icons (118 against 120 on a scratch set). Every icon parameter is
listed once (UgcIconParams: key, setting, range, default); the settings,
the dashboard's settings entries and the icon editor come from that list.
Presets per kind (player models, each car or rocket build type from
ModularBuildComponent) and overrides per model or module combination are in
ugc_icon_settings.
Saved models wait ugc_debounce_seconds (sharedconfig) after the owner's last
save before they're made (ugc.process_after); a client asking for one, the
owner leaving the world or a reset ends the wait.
Cars and rockets: one icon per combination of modules (sorted LOTs), shared
by every build of it; builds of a combination made already are marked made
right away, the client's per-blueprint downloads serve the shared files.
The dashboard can delete one item's files, purge by filter or all, preview
icons with any values on the UGC server (/admin routes, master password),
save presets and overrides, and draw a kind's icons again (icons only).
Migrations dlu/mysql/86 and dlu/sqlite/69. Not done yet: the dashboard
editor's lighting controls in the page script (routes are there), docs for
it, the empty-model state, /ugc?item= links, the shared fetch helper; the
storage size and property loading bugs are next.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The icon was rendered from a second mesh built only for it (the icon
renderer's color corrections, no variation, occlusion worked out again). It
is now drawn from the .nif just made, read back with NifFile at LOD 0, so it
shows exactly what the game shows: the color variation, the removed faces and
the lighting baked into the vertex colors (so it adds no occlusion of its
own). icon_correct_colors and icon_color_variation are gone; the camera and
light settings stay. Cars and rockets are drawn as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Player-built models the UGC server made are drawn from their NIF, falling back
to the LXFML; a Generated models switch turns it off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UGC Search (/ugc_search) finds creations by name, id or LOT with where each
one is. Property pages and character inventories show the icon the UGC server
made of each creation, its state and a link to /ugc?item=&kind=, for whoever
may view the page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IUgcLookup: search ugc and ugc_modular_build by id, creator, property, model
name or LOT, and find where a creation is placed or mailed. The SQL is shared
by MySQL and SQLite (UgcLookupSql.h).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A brick built model item's config has blueprintid (lowercase), which the saved
item XML didn't keep (only blueprintID), so a picked up model lost its blueprint
on the next load and could not be placed again. It is now saved as x@bp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A step by step table of LU Toolbox's pipeline against the UGC server's, the
new settings and their defaults, the measured effect of max_cpu_percent and
max_memory_mb, and the dashboard's gallery, viewer and internal address.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The /ugc page no longer needs the browser to reach the UGC server: the
dashboard fetches its status (/api/ugc/server/status), the files it made
(/api/ugc/files/<kind>/<id>/<file>) and its NIFs converted for the 3D view
(/api/ugc/mesh/<id>, NifFile like the scenery) from ugc_internal_url
(default http://127.0.0.1:2008) with libcurl on the worker threads, keeping
small answers briefly. ugc_public_url is only an "open on the UGC server"
link now.
The page gets an icon gallery beside the list, filtered by kind, state and a
search by id or owner (GetUgcProcessList/GetModularBuildProcessList take a
search), and a viewer: the generated NIF in 3D at any LOD, now or before it
was made again, with wireframe, vertex color and baked lighting switches, the
LXFML beside it, the icon now and before, and the stats with triangles before
and after hidden faces were removed. The status box shows CPU, memory, the
jobs' memory estimate with their limits, and throttling. The settings page
lists the UGC server's new settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LU Toolbox's Combine Transparent is off by default, so each transparent brick
is its own object and shape (the client can sort them). combine_transparent=1
joins them as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parity with LU Toolbox's Process Model, Bake Lighting and icon renderer, with
its defaults as the settings' defaults:
- Colors from its LU palette (UgcPalette: LU colors, LDD colors mapped to the
nearest LU one, unknown ones black), transparent bricks at 58.82% opacity, a
brick transparent only when all of its materials are.
- Color variation: each brick's material gets its HSV value shifted in a 2.224
gamma by up to 5% (times the color's own amount), from a random number of
the model, brick and material, so reprocessing gives the same colors in
every LOD. Icons get none, and the icon renderer's color corrections.
- LODs 0 and 2 with its distance logic, written as NiLODNode/NiRangeLODData
like its exports and the game's own brick models, shapes divided at 65535
vertices along the longest side like divide_mesh.
- Ambient occlusion like its AO-only bake: 64 rays per vertex, distance 5,
after hidden surface removal, transparent bricks neither baked nor
occluding, glow colors added.
- Icons from its icon scene: 50 mm lens at 53.4/19.5 degrees, sun of 2.5 at
21/50.3 degrees with soft shadows, grey world light with occlusion; LOD 0's
hidden surface removal and occlusion are reused for them.
- Optional ground plane for hidden surface removal; stats.json per model and
the previous version's previews kept for comparing.
Budgets, applied live on config reload: max_cpu_percent (workers account
their thread CPU time and sleep to stay under it, long renders included),
worker_nice, max_memory_mb (jobs are estimated from their brick count and
wait until they fit), max_model_bricks and pause_hours. /status and the
traffic report show CPU, memory and throttling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Server Status gets a UGC row while master starts it, and staff with
health_view a UGC Server card: up time, waiting/made/failed, busy workers and
storage, linking to /ugc. Server Health adds UGC to the uptime history and a
Servers table with every server's process, memory, CPU and last report.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Online state with the time it came up, down/up alerts, a UGC column in the
health history, /api/servers (every server with its process memory and CPU)
and /api/servers/ugc, and UGC gauges for Prometheus.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The server list now carries whether master starts the UGC server, whether it
is connected and the pid it was started as. Settings reloads reach it like
auth and chat, and shutdown waits for it. The UGC server sends its totals and
storage with its traffic reports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Servers now log to logs/<Server>/ (worlds to logs/WorldServer/<zone>/<clone>/) with the start time in the file name.
The dashboard and UGC server log the same way, and the System Log page and log search read the whole folder tree
instead of only the top of logs/.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client works out on its own when its racer goes the wrong way and
shows a 6 second countdown, but it only moves the car when the server
sends RacingSetPlayerResetInfo, which DLU never did for this.
The server now follows the reset planes the way the client's
LWORacingControlComponent does (1.10.64): each path waypoint is a plane
facing along its rotation; the racer starts between planes 0 and 1,
moves forward when in front of the upcoming plane and back when behind
the last one (CheckUpcomingResetPlane @ 0x00c7edf0, CheckLastResetPlane @
0x00c7f1a0, CrossResetPlaneBackward @ 0x00cba380). Driving back
through a second plane in a row starts the client's countdown
(UpdateWrongWayCount @ 0x00be5c10, 6 seconds); going forward through a plane ends it. When
it runs out, the racer gets the same reset as an unsmashed reset: reset
info for their furthest point and RacingResetPlayerToLastReset. Resets
sent for smashes keep the planes in step as well.
Fixes issue 764.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The buyback inventory grew by 9 slots whenever it was nearly full, so
the vendor's buyback page kept resizing. Live kept 27 items: a 2014
capture of 29 sales shows that on the 28th, the server sent
RemoveItemFromInventory (buyback inventory) for the first item sold,
then added the new one.
The buyback inventory now keeps its size. Before a sale needs a new
buyback slot and the inventory holds 27 or more items, the oldest items
(lowest object ID: each sale gives a new, higher ID) are removed. Sales
that fit on an existing buyback stack remove nothing. The removal is not
counted again by the economy ledger, which counted the items as gone
when they were sold.
Fixes issue 1129.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deleting an item now follows its ItemComponent delResIndex row in the
DeletionRestrictions table, the way the client's shared inventory code
decides it (LWOInventoryComponent_Common::CanRemoveFromInventory @
0x00ce0d20, CheckDeletionRestrictionIndex @ 0x00c94c20):
- missing, unrestricted, unknown-type or empty rows allow it;
- LOTS_INCLUDED: another item of any listed LOT must remain;
- LOTS_EXCLUDED: other items of every listed LOT must remain;
- ANY_RESTRICTION / ALL_RESTRICTIONS: any / all listed rows allow it;
- ZONE: only in the listed maps; ALWAYS_RESTRICTED: never.
Operators (GM level 9) may delete anything, as in the client. A refused
delete is logged and the item stays.
ItemComponent.minNumRequired is not used: the client never reads it, so
its meaning can't be verified.
Issue 960: the rocket (6416, row 8) and the classic rocket parts (rows 1-3)
have rows that keep at least one rocket or part, so the last rocket can
no longer be deleted and strand the player.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The character list selected index 0 and was ordered by last login, so
the characters swapped places on the selection screen after each
session. Live kept them in the order they were made and selected the
one with the latest last login (a new character counts as logged in
when it is made): 2014 captures show e.g. a new fourth character
appended and selected (index 3), and after the next login the list in
the same order with index 0 for the character played last.
Characters are now sorted by ID (creation order) and the one with the
latest last login is selected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mail sent to someone who is not in the sender's world (system mail with
no address, and all player-to-player mail) now reaches them: the world
sends a MailNotify (Chat::MAIL, unused until now) to the chat server,
which passes it to the world the receiver is in, and that world sends
the client its unread count with a NewMail NotificationResponse.
Receivers in the same world are told directly. Player-to-player mail
did not notify the receiver at all before.
Replaces the TODO in Mail::SendMail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unequipping an item uncasts its equip skills; ApplyBuffBehavior::UnCast
now removes the buff with bFromUnEquip set, as live did (2014 captures:
RemoveBuff for buffs 3, 4, 5, 50 and 61 always carried bFromUnEquip,
and those are exactly the cancel_on_unequip buffs in the CDClient).
The client only drops a buff for such a removal when it was added with
cancelOnUnEquip (LWOBuffComponent::RemoveBuffIcon @ 0x00cf99b0), so
BuffComponent::RemoveBuff does the same to stay in step with it. Also
corrects the bFromRemoveBehavior comment: the client does not ignore the
message, it only removes buffs added with cancelOnRemoveBuff.
Replaces the TODO in InventoryComponent::RemoveBuff.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIRE FIX. The 1.10.64 client writes and reads the immunity flags in
alphabetical order after the u32 state (GameMessage::SetStatusImmunity::
Serialize @ 0x00d8f140; the field offsets are named by the Flash export
at 0x00d8f410): BasicAttack, DOT, ImaginationGain, ImaginationLoss,
Interrupt, Knockback, PullToPoint, QuickbuildInterrupt, Speed. DLU wrote
them in declaration order, so e.g. a knockback immunity reached the
client as an imagination-gain immunity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WIRE FIX. DLU sent NotifyNotEnoughInvSpace with the ID of
VehicleNotifyFinishedRace (1396). The 1.10.64 client registers it as
NOTIFY_NOT_ENOUGH_INV_SPACE (1516, 0x00545c90) and reads freeSlotsNeeded
followed by the optional inventoryType (0x00d8b850), which is what the
payload already was. Only the message ID changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Make keys with a permission picker grouped like the Permissions page
(only the maker's own permissions), limits, restrictions and expiry;
the list shows scope, lost permissions, limits, today's use, last use
and requests, with rotate and revoke. Staff with api_keys_manage see
and revoke other accounts' keys under the rank rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
POST /api/tables/... only reads, so read-only keys may use it (and the
API docs list it for them). Keys limited to some addresses can't open
the WebSocket, whose address isn't checked, nor keys whose allowed
paths leave out /ws. Refusals are audited without an account target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bearer keys (dlk_...) are checked per request against their owner's
current account (ban, lock, demotion, sign out everywhere stop or narrow
them at once) and their scope: permission routes need the permission in
scope, read-only keys only read, level-only routes need an all-permission
key, and session-only paths (sign-in, password, 2FA, key management)
are never reachable with a key. Per-key rate limit and daily quota with
429 and X-RateLimit/X-Quota/Retry-After headers; usage is written in
batches every minute. WebSocket subscriptions honour the scope too.
Routes to list, make, rotate and revoke keys; staff with
api_keys_manage can see and revoke others' keys under the rank rules.
POST /api/auth/token now makes an all-permission key. Audit entries for
create/rotate/revoke/denied, and actions done with a key are attributed
to "user (key name)".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>