# PlusEMU documentation Set up your PlusEMU hotel, manage ranks and permissions, and configure camera, Wired, rooms, and the economy. Source: https://docs.plusemu.dev/ PlusEMU is a Habbo Hotel server emulator written in C# on .NET 10. It runs your hotel's rooms, users, furniture, games, and economy. The [Volt client](https://github.com/plus-emulator/Volt) connects players to the emulator in their browser. Use these guides to set up a hotel, manage staff access, and configure the main features. ## Start here | Your task | Guide | | --- | --- | | Run PlusEMU for the first time | [Install your hotel](/getting-started/installation/) | | Connect the database and client | [Configuration](/getting-started/configuration/) | | Install a new release | [Update PlusEMU](/getting-started/updates/) | | Understand ranks and permissions | [Ranks and permissions](/administration/ranks-and-permissions/) | | Give or remove staff access | [Assign and remove roles](/administration/manage-roles/) | | Add a permission to a feature | [Create roles and permissions](/administration/custom-permissions/) | | Set up room photos | [Set up the camera](/features/camera-setup/) | ## Explore the features - [Camera](/features/camera/) saves room photos and thumbnails through a separate renderer service. - [Wired](/features/wired/) lets room builders connect triggers, conditions, and effects without writing server code. - [Rooms and building](/features/rooms/) covers room settings, building tools, room games, and community features. - [Catalogue and economy](/features/economy/) covers the catalogue, currencies, trading, and other ways to exchange items. The [ranks and permissions guide](/administration/ranks-and-permissions/) explains how access works. Read [Assign and remove roles](/administration/manage-roles/) to give or remove access. Read [Create roles and permissions](/administration/custom-permissions/) to define a new role or permission. ## What you need A running hotel combines the emulator, a MariaDB or MySQL database, a compatible client, and the client's assets. Your website handles registration and login. The camera renderer is a separate service that you can add when you need photos. The [installation guide](/getting-started/installation/) lists what you need before you start: a server, a domain, and a Cloudflare account. ## Source and releases These guides focus on the current [PlusEMU project](https://github.com/plus-emulator/PlusEMU). Older forks and releases may use different commands or settings. Use the [release notes](https://github.com/plus-emulator/PlusEMU/releases) when you update an existing hotel. For a language model or another tool, use the [Markdown pages and complete text export](/using-these-docs/). --- # Create roles and permissions Create a custom role, grant existing permissions, and add a permission key for a new server feature. Source: https://docs.plusemu.dev/administration/custom-permissions/ Use a custom role when several users need the same access. Use a new permission key when you add a server feature with its own access check. ## Create a custom role This example creates an `event_host` role that can send alerts in rooms the user owns. Run the SQL against your emulator database after taking a backup. 1. Check that the slug is unused and that the permission exists: ```sql SELECT id, slug FROM roles WHERE slug = 'event_host'; SELECT `key`, is_orphan FROM acl_permissions WHERE `key` = 'command.roomalert'; ``` Continue only if the first query returns no role and the second returns the key with `is_orphan = 0`. On a new database, the emulator has not checked its keys yet, so many keys still show `is_orphan = 1`. Start the emulator once, then run the query again. 2. Create the role and grant the permission in one transaction: ```sql START TRANSACTION; INSERT INTO roles (slug, name, description, weight, security_level, is_staff) VALUES ('event_host', 'Event host', 'Hosts room events.', 10, 1, 0); INSERT INTO role_permissions (role_id, permission_key) SELECT id, 'command.roomalert' FROM roles WHERE slug = 'event_host'; COMMIT; ``` If either insert fails, run `ROLLBACK` instead of `COMMIT`. Choose a weight below the staff who will assign this role. 3. In room chat, reload the access definitions: ```text :update permissions ``` This requires both `command.update` and `command.update_rights`. A server restart also loads the definitions. 4. Assign the role from an account that meets the [role management requirements](/administration/manage-roles/#check-the-hierarchy): ```text :giverole Alice event_host ``` 5. Have Alice enter `:commands` in a room. Check that `:roomalert` appears. 6. In a room Alice owns, enter `:roomalert Welcome to the event`. Another user in the room should receive the alert. Sending a room alert in someone else's room also requires `moderation.alert`. ## Grant an existing permission To add another registered permission to the same role, insert an exact key: ```sql INSERT INTO role_permissions (role_id, permission_key) SELECT id, 'command.roombadge' FROM roles WHERE slug = 'event_host'; ``` Use a key from the active registry: ```sql SELECT `key`, description FROM acl_permissions WHERE is_orphan = 0 ORDER BY `key`; ``` Reload with `:update permissions` after each set of changes. The reload refreshes online users too. Grant exact keys when possible. A wildcard such as `command.*` also grants new matching permissions after a future update. ## Add a new permission key Adding a row to `acl_permissions` alone does not implement a permission. The running server builds its registry from code and marks unregistered database entries as orphaned. To add access control for a new server feature: 1. Add a public string constant to [`HabboHotel/Permissions/PermissionKeys.cs`](https://github.com/plus-emulator/PlusEMU/blob/master/HabboHotel/Permissions/PermissionKeys.cs). For an event schedule feature, an example is: ```csharp public const string EventsScheduleManage = "events.schedule.manage"; ``` 2. Check the permission in the server operation before changing data: ```csharp if (!session.GetHabbo().Access.Can(PermissionKeys.EventsScheduleManage)) { return; } ``` This example belongs in an authenticated handler where `session.GetHabbo()` is available. A client-side button check alone does not protect the operation. 3. Build and deploy the changed emulator. Restart it so the registry includes the new key. 4. Add `events.schedule.manage` to the intended role's `role_permissions` rows. 5. Reload permissions and verify the feature with both an allowed account and an account without the permission. For a chat command, use `command.`, where `` is the command's `Key` value. `CommandManager` checks this permission automatically. Register the constant as well. For an incoming packet handler, `[RequiresPermission(PermissionKeys.EventsScheduleManage)]` applies a server permission check before the handler runs. If the attribute lists several permissions, the user needs all of them. See [Ranks and permissions](/administration/ranks-and-permissions/) for how grants, denies, and multiple roles combine. --- # Assign and remove roles Give permanent or temporary roles and remove assignments with the in-game role commands. Source: https://docs.plusemu.dev/administration/manage-roles/ Use `:giverole` and `:takerole` in room chat. Both commands work for online and offline users. You need `housekeeping.roles.manage` and the permission for the command you use: - `command.giverole` to assign a role. - `command.takerole` to remove a role. In a room, enter `:commands` to see the commands available to your account. The list checks command permissions. A listed role command still needs `housekeeping.roles.manage` and must pass the hierarchy checks below. ## Assign a permanent role Use the account's username and the role's exact slug: ```text :giverole Alice moderator ``` The server confirms `Assigned role 'moderator' to Alice (permanent).` The assignment adds a role. It does not remove other roles that Alice already holds. ## Assign a temporary role Add a whole number of days from 1 to 3650: ```text :giverole Alice trial_moderator 7 ``` The server confirms the expiry time in UTC. The role stops contributing access when that time arrives. Giving the same role again replaces its expiry. Omitting the number of days makes the assignment permanent. ## Remove a role Enter: ```text :takerole Alice moderator ``` The server confirms `Revoked role 'moderator' from Alice.` Other roles and individual permission overrides stay in place. If Alice still has the same permission through another role, removing `moderator` does not remove that permission. Successful changes refresh online users immediately and create an entry in `acl_audit_log`. ## Check the hierarchy Both commands require your weight to be higher than the target user's weight and the selected role's weight. You cannot edit yourself or assign or remove `default`. To assign a role, you must also meet all of these conditions: - Your security level is at least the role's security level. - You hold every registered permission that the role grants. - Your numeric limits are at least the limits configured on the role. If none of your roles sets a limit, PlusEMU uses its built-in default for that limit. - If the role has `is_staff = 1`, you already hold a staff role. An owner therefore cannot use these commands to appoint another owner with the same weight. The database operator must handle that account setup deliberately. ## Find the role slug If you have database access, list the current roles: ```sql SELECT slug, name, weight, security_level, is_staff FROM roles ORDER BY weight, slug; ``` Use `trial_moderator`, for example, rather than its display name `Trial Moderator`. Slugs are case-sensitive. If a command reports an unknown role, it also lists roles that your account may assign to that user. See [Troubleshoot permissions](/administration/permission-troubleshooting/) for refused changes. --- # Troubleshoot permissions Find missing command access, refused role changes, stale permissions, and grants that do not take effect. Source: https://docs.plusemu.dev/administration/permission-troubleshooting/ Start with the exact permission key and the affected username. The queries below only read data. Replace `Alice` with the account you want to inspect. ## A command is missing Enter `:commands` while in a room. Chat commands require `command.`, such as `command.giverole`. For role commands, also check `housekeeping.roles.manage`. For `:update permissions`, also check `command.update_rights`. Use the dotted keys in these docs. Some error messages still show the old underscore names, such as `command_update_rights`. The real key is `command.update_rights`. ## A role change is refused Compare your roles and the target user's roles: ```sql SELECT u.username, r.slug, r.weight, r.security_level, ur.expires_at FROM user_roles ur JOIN users u ON u.id = ur.user_id JOIN roles r ON r.id = ur.role_id WHERE u.username IN ('Alice', 'YourUsername') ORDER BY u.username, r.weight DESC; ``` Every user also has `default`, even without a row in `user_roles`. Check its values separately if you changed that role. Confirm that: - The target is another user with a lower active weight. - The selected role has a lower weight than yours. - The selected role is not `default`. - For removal, the user has an explicit assignment for that role. - For assignment, you meet the permission, limit, security level, and staff requirements in [Assign and remove roles](/administration/manage-roles/#check-the-hierarchy). An expired role does not contribute to a user's weight or permissions. ## A grant has no effect 1. Inspect the user's individual overrides: ```sql SELECT up.permission_key, up.effect, up.expires_at, up.reason FROM user_permissions up JOIN users u ON u.id = up.user_id WHERE u.username = 'Alice'; ``` An active matching `deny` overrides grants from every role. Check prefix wildcards and `*` as well as the exact key. 2. Check that the key is registered: ```sql SELECT `key`, is_orphan FROM acl_permissions WHERE `key` = 'camera.use'; ``` After the emulator has started at least once, a key with `is_orphan = 1` is not in the server's registry. Correct a typo or implement the key in code. See [Add a new permission key](/administration/custom-permissions/#add-a-new-permission-key). 3. After database changes, run `:update permissions` from an authorized account, or restart the emulator. 4. Try the actual feature again. A visible command or button does not confirm that every feature-specific condition passes. Changing `users.rank` alone does not grant access. Assign a role instead. ## Access remains after removing a role Check the user's other active roles and individual grants. Permissions combine across roles, and all users retain the implicit `default` role. Also check that a previous `:giverole` command did not replace a temporary expiry with a permanent assignment. ## Inspect recent changes `:giverole`, `:takerole` and automatic expiry cleanup create audit entries. Direct SQL changes do not. ```sql SELECT created_at, actor_id, action, target_id, payload FROM acl_audit_log WHERE target_type = 'user' AND target_id = (SELECT id FROM users WHERE username = 'Alice') ORDER BY id DESC LIMIT 20; ``` Direct SQL edits bypass the access service's audit log and hierarchy checks. Prefer the role commands for ordinary assignments. --- # Ranks and permissions Understand how roles, permissions, and rank levels control access in PlusEMU. Source: https://docs.plusemu.dev/administration/ranks-and-permissions/ PlusEMU gives users access through roles. A user can hold several roles, such as `moderator` and `vip`, at the same time. Every user also receives the `default` role automatically. This role supplies ordinary player permissions, including `camera.use` in the fresh database. ## Roles and rank levels A role groups permissions and numeric limits. Its `slug` is the name used in commands, such as `trial_moderator`. Two role values have different jobs: - `weight` controls who outranks whom. A larger value means higher authority. - `security_level` is the rank level sent to the client. The database allows values from 0 to 7. The user's highest active role weight determines their authority. The highest active security level determines their client rank. These values can come from different roles. The fresh database includes these staff roles: | Role slug | Weight | Security level | | --- | --- | --- | | `trial_moderator` | 20 | 2 | | `moderator` | 30 | 3 | | `senior_moderator` | 40 | 4 | | `administrator` | 50 | 5 | | `manager` | 60 | 6 | | `community_leader` | 70 | 7 | | `developer` | 80 | 7 | | `owner` | 90 | 7 | For example, an owner outranks a developer even though both have security level 7. The `users.rank` column stores a value derived from active roles. Changing this column alone does not grant a role or its permissions. See [Assign and remove roles](/administration/manage-roles/). ## How permissions combine A permission is a named action, such as `camera.use`, `command.ban`, or `moderation.tool`. PlusEMU combines the permissions from all active roles and any individual grants. A higher role weight does not automatically inherit another role's permissions. Individual overrides live in `user_permissions`. A `deny` override wins over every matching grant, including a wildcard grant. PlusEMU has no chat command for overrides. Add or remove rows in `user_permissions` with SQL, then run `:update permissions`. Permission patterns support: - An exact key, such as `camera.use`. - A prefix wildcard, such as `command.*`. - `*`, which matches every registered permission. Keys are case-sensitive. A prefix wildcard includes the dot, so `command.*` matches `command.ban`. Patterns such as `command.b*` are not supported. Wildcards expand against the server's permission registry. A database row with an unknown key does not create a new working permission. ## Expiry, limits, and client details Role assignments and individual overrides can have an expiry time. Expired assignments stop contributing access. PlusEMU refreshes affected online users automatically. For a numeric limit, PlusEMU takes the largest configured value across the user's active roles. Examples include `limit.daily_respects` and `limit.flood_tolerance`. The primary role supplies the role name and badge sent to the client. PlusEMU selects the role with the highest weight, then the lowest role ID if weights match. Habbo Club membership has its own records. Assigning a role does not create a timed Habbo Club subscription. See [Create roles and permissions](/administration/custom-permissions/) to change access, or [Troubleshoot permissions](/administration/permission-troubleshooting/) when a grant does not work. --- # Set up the camera Connect the private camera renderer, prepare its assets, serve photos, and diagnose failures. Source: https://docs.plusemu.dev/features/camera-setup/ This guide uses a Linux installer layout. The camera renderer is a separate Node.js process. It needs the emulator, a current Volt build containing `camera.html`, and compatible renderer assets. ## Prepare the renderer files Get the source version that matches your emulator release. From a PlusEMU source checkout, run: ```bash cd CameraRenderer npm ci npx playwright install chromium ``` On a Linux host missing Chromium system libraries, install them with `npx playwright install-deps chromium`. Install the browser as the account that will run the renderer. Use the deployed Volt directory as `CAMERA_PAGE_ROOT`. For the Linux installer, confirm this file exists: ```bash ls /var/www/hotel/client/camera.html ``` If it is missing, deploy a compatible Volt build before continuing. ## Prepare compatible assets The renderer's local asset layout differs from the installer's public `hotel-files` layout. Do not point `CAMERA_ASSET_ROOT` at that directory without preparing the expected files. For the example below, place compatible assets under `/srv/plusemu-camera-assets` with this structure: ```text /srv/plusemu-camera-assets/ furniture/nitro/*.nitro furniture/json/ProductData.json clothes/nitro/*.nitro clothes/json/FigureData.json clothes/json/FigureMap.json effects/nitro/*.nitro effects/json/EffectMap.json gamedata/json/HabboAvatarActions.json pets/*.nitro ``` Use the asset set that matches your client. The current renderer requests `.nitro` filenames. A hotel asset pack containing only `.hab` files is not a drop-in local asset source for these defaults. The example reads furniture data from the emulator through `CAMERA_FURNIDATA_URL`. Without that variable, the renderer also needs `furniture/json/FurnitureData.json` under the asset root. Point `CAMERA_IMAGES_ROOT` at the directory containing `Habbo-Stories/` for camera effects. In an installer layout, the image directory is `/var/www/hotel/hotel-files/c_images`. An existing compatible asset service can replace local game assets through `CAMERA_ASSET_ORIGIN`. This must be an HTTPS origin, such as `https://assets.example.com`, with no path. It must serve `/assets/...` and `/gamedata/...` using the paths requested by the renderer. The renderer still reads camera effect images locally. ## Set the shared secret Generate a private secret of at least 32 characters. For example: ```bash openssl rand -hex 32 ``` Set the following keys inside the existing `Camera` section of the emulator's `Config/config.json`. Keep its existing `Effects` list. ```json { "RendererUrl": "http://127.0.0.1:3921/render", "Bearer": "replace-with-your-private-secret", "OutputDirectory": "/var/www/hotel/emulator/camera", "TimeoutSeconds": 15, "MaxConcurrency": 2 } ``` Keep the renderer private. `RendererUrl` must use HTTP, a private address, and the `/render` path. PlusEMU rejects redirects and public renderer addresses. Give the emulator account write access to the output directory. The renderer and web server need read access. Keep the bearer out of client configuration and public files. ## Start the renderer From `CameraRenderer`, enter the same secret without saving it in shell history: ```bash read -rsp 'Camera secret: ' CAMERA_RENDERER_SECRET export CAMERA_RENDERER_SECRET ``` Start the renderer with your prepared paths: ```bash CAMERA_PAGE_ROOT=/var/www/hotel/client \ CAMERA_ASSET_ROOT=/srv/plusemu-camera-assets \ CAMERA_IMAGES_ROOT=/var/www/hotel/hotel-files/c_images \ CAMERA_MEDIA_ROOT=/var/www/hotel/emulator/camera \ CAMERA_FURNIDATA_URL=http://127.0.0.1:8080/api/gamedata/furnidata \ CAMERA_RENDERER_CONFIG=/var/www/hotel/client/configuration/renderer-config.json \ CAMERA_UI_CONFIG=/var/www/hotel/client/configuration/ui-config.json \ node server.mjs ``` Look for both `Trusted camera renderer listening on 127.0.0.1:3921` and `Trusted camera page ready`. A listening process can still have asset or browser startup failures. After the foreground check succeeds, run the same process under your service manager with these environment variables and a protected secret file. Restart it after each Volt deployment. ## Serve the photos Add this location inside the hotel's nginx `server` block. Adjust the alias if you chose a different output directory. ```nginx location /camera/ { alias /var/www/hotel/emulator/camera/; autoindex off; limit_except GET HEAD { deny all; } add_header X-Content-Type-Options nosniff always; add_header Cache-Control "no-cache" always; } ``` Check and reload nginx: ```bash sudo nginx -t sudo systemctl reload nginx ``` This location serves image files, not the private renderer. The refresh policy also lets room thumbnails change without retaining an old image. The installer already sets `thumbnails.url` in Volt's `ui-config.json` to `/camera/thumbnail/%thumbnail%.png`. Check that it still has this value. Restart PlusEMU to load the camera connection settings: ```bash sudo systemctl restart plusemu ``` Current fresh databases already contain camera tables. Updates apply database migrations at startup. Do not replay old SQL update files on a current installation. ## Check a photo 1. Sign in with an account that has `camera.use`. 2. Enter a room and take a photo. 3. Apply an available effect and confirm that the preview changes. 4. Purchase a wall photograph and place it. 5. Capture a room thumbnail with an account that has room management rights. 6. Reload the navigator and check the thumbnail. For a generated photo, request its actual `/camera/.png` URL in the browser. A successful renderer preview alone does not confirm public photo delivery. ## Troubleshoot the camera | Symptom | Check | | --- | --- | | Renderer rejects captures | The emulator bearer and `CAMERA_RENDERER_SECRET` match, with at least 32 characters | | Renderer listens but never becomes ready | `camera.html`, Chromium libraries, renderer file permissions, and the first asset error in its log | | Missing asset error | The exact requested filename exists in the expected local tree or configured asset origin | | Camera unavailable for one account | `camera.enabled`, `camera.use`, capture cooldowns, and daily quotas | | Preview works but placed photos fail | The `/camera/` web route, output directory permissions, and `CAMERA_MEDIA_ROOT` | | Photo purchase fails | The account balance, point currency type `0`, and a valid `camera.item_id` | | Old photo draft fails | Take a new photo in the current room and session | | Thumbnail stays unchanged | The thumbnail URL and cache rules on nginx and Cloudflare | See [camera settings](/features/camera/#access-and-settings) for prices and quotas. --- # Camera Room photos, wall photographs, thumbnails, camera permissions, prices, and limits. Source: https://docs.plusemu.dev/features/camera/ The camera takes room photos, applies effects, and creates wall photographs. Room thumbnails use the same rendering service. PlusEMU sends a snapshot of the room to a private renderer. That renderer uses `camera.html` from the Volt build. Players choose the viewpoint and effects, but cannot upload a replacement image through camera packets. The installer does not start this renderer. Fresh installs already enable the camera, but photos fail until the renderer runs. Follow [camera setup](/features/camera-setup/), or set `camera.enabled` to `0` until it is ready. ## Photos and thumbnails A photo keeps the room snapshot from the moment the player takes it. Editing the photo changes its effects without capturing the room again. Photo checkout requires the same client session and original room. Leaving the room or reconnecting invalidates the active draft. Drafts expire after 30 minutes. Photos are 320 by 320 pixels. Small previews and room thumbnails are 110 by 110 pixels. The emulator writes photos under its `Camera.OutputDirectory` and returns URLs under `/camera/`. Only the room owner, an admin of the room's group, or staff with `room.owner.any` can capture a room thumbnail. Volt's thumbnail URL is: ```json { "thumbnails.url": "/camera/thumbnail/%thumbnail%.png" } ``` ## Access and settings Camera use requires both `camera.enabled = 1` in `server_settings` and the `camera.use` permission. The current permission check does not use the old `camera.permission` setting. These are the current fresh-install values. Existing hotels can have different values. | `server_settings` key | Default | Meaning | | --- | --- | --- | | `camera.enabled` | `1` | Enables camera access | | `camera.price.credits` | `2` | Credits per wall photograph | | `camera.price.points` | `0` | Duckets per wall photograph | | `camera.price.points.type` | `0` | Point type for wall photographs | | `camera.price.publish.points` | `1` | Duckets per publication | | `camera.price.publish.points.type` | `0` | Point type for publications | | `camera.publish.cooldown` | `180` | Seconds between publications | | `camera.render.cooldown` | `5` | Seconds between new captures | | `camera.render.daily` | `50` | New captures per account per UTC day | | `camera.render.edit.cooldown` | `1` | Seconds between effect renders | | `camera.render.edit.daily` | `200` | Effect renders per account per UTC day | | `camera.thumbnail.cooldown` | `15` | Seconds between thumbnail captures | | `camera.competition.enabled` | `0` | Competition submissions are disabled | `camera.price.points.type` and `camera.price.publish.points.type` must stay `0`, meaning duckets. Other point types are rejected. `camera.item_id` identifies the wall furniture definition with interaction type `camera_picture`. Its numeric ID belongs to your database. Do not copy an ID from another hotel. Staff with `command.update` and `command.update_configuration` can reload database settings with `:update settings`. Changes to the `Camera` section of `Config/config.json` require an emulator restart. ## Effects The renderer reads available effects from `camera.available.effects` in the client configuration. Effect names must also exist in the emulator's `Camera.Effects` list. Names are case-sensitive. Each emulator entry has a `Name` and `MinLevel`. An empty renderer catalogue permits photos without effects. ## Competition limitation Competition submissions are disabled by default. PlusEMU has no verified-email flag for this flow. Setting `camera.competition.require_email` to a value other than `0` rejects submissions. For connection failures, missing assets, or broken photo URLs, use [camera troubleshooting](/features/camera-setup/#troubleshoot-the-camera). --- # Catalogue and economy Credits, activity points, catalogue offers, trading, marketplace, crafting, and staff currency commands. Source: https://docs.plusemu.dev/features/economy/ PlusEMU supports catalogue purchases, player trading, marketplace offers, crafting, recycling, vouchers, and club offers. Hotel operators control the available content and prices through the database and management tools. ## Currencies Credits are separate from activity points. Activity points include duckets, diamonds, and other currency types. A catalogue offer can use credits, points, or both. Current databases store activity-point balances in `user_currencies`. An older website that expects only legacy currency columns needs an update before use with this schema. The installer selects an Atom CMS version for the emulator's database layout. ## Catalogue and furniture data Catalogue pages organize offers. Furniture definitions describe the items that those offers sell. PlusEMU generates `FurnitureData.json` from the furniture and catalogue data so clients can identify the items. The public endpoint is: ```text /api/gamedata/furnidata ``` A staff account with `command.update` and `command.update_catalog` can reload catalogue content with: ```text :update catalog ``` The reload also rebuilds generated furniture data. Furniture artwork still needs matching client assets. Adding a database offer alone does not create an image or animation. ## Trading and marketplace Trading exchanges eligible inventory items between players. Room trading settings and player restrictions can prevent a trade. Furniture definitions decide whether an item can be traded. The marketplace lists eligible furniture for sale. Marketplace eligibility is separate from trade eligibility. A tradeable item is not automatically sellable on the marketplace. Gift purchases, crafting recipes, and recycler rewards depend on their own catalogue and database configuration. ## Give currency to a player Authorized staff can use the following chat commands. Replace `PlayerName` and the amount with the intended recipient and value. ```text :give PlayerName credits 100 :give PlayerName duckets 50 :give PlayerName diamonds 10 ``` These examples add currency. The account needs `command.give` and the permission for the currency: `command.give_coins` for credits, `command.give_pixels` for duckets, or `command.give_diamonds` for diamonds. The recipient must be online. The staff account must outrank another recipient. If it does not, the command does nothing and shows no message. A staff account can target itself if it has the required currency permission. Use the smallest permission set needed for the job. Access to one currency command does not imply access to the others. ## Check a catalogue change After a change, reopen the catalogue and confirm that the offer has the expected price and currency. Buy one item with a test account, then check the balance and inventory. If the purchase succeeds but the furniture is missing or blank, check the furniture definition, generated furniture data, and matching client asset. If the price or page is stale, check that the catalogue reload reached the emulator. --- # Rooms and building Room settings, furniture placement, build height, room games, and community features. Source: https://docs.plusemu.dev/features/rooms/ Rooms hold furniture, visitors, pets, bots, and games. The navigator lets players find and create rooms. Room owners control access and the settings for their own rooms. ## Room settings Room settings include the name, description, category, tags, visitor limit, and entry access. Other settings control trading, pets, walking through users, wall visibility, and floor and wall thickness. Owners can also choose who can mute, kick, or ban visitors. These room settings are separate from hotel-wide staff roles. Groups have their own membership and room rights. Giving someone rights in one room does not give them a hotel staff role. ## Building tools Players place furniture from their inventory and adjust its position, rotation, and state. The floor editor changes the room layout. Stack tools and the build-height command provide more control over furniture placement. To place or move furniture at a fixed height, use: ```text :bh 2.5 ``` The supported range is `0` to `40`. Values are rounded to two decimal places. Type `:bh` without a value to return to normal stacking. The command changes placement height for your current room user. It does not grant furniture editing rights. [Wired](/features/wired/) adds room interactions. The [camera](/features/camera/) provides wall photographs and navigator thumbnails. ## Games PlusEMU includes Battle Banzai, Freeze, Football, Ice Tag, and SnowStorm. Room games use their matching furniture and game rules. SnowStorm has its own game flow. A working game needs the corresponding catalogue items and client assets. The presence of an emulator game handler does not add missing assets to an existing hotel. ## Community features Friends and messenger support conversations outside the current room. Groups and forums support shared communities. Room polls and word quizzes provide activities inside rooms. Achievements, talent tracks, quests, and campaigns provide progression. Their visible content depends on the hotel's database and configuration. --- # Wired Build room interactions with triggers, conditions, effects, selectors, variables, and the Wired Web API. Source: https://docs.plusemu.dev/features/wired/ Wired connects furniture and room events without changing the emulator's code. Builders use it for games, puzzles, movement, messages, and score systems. ## How a stack works A basic Wired stack groups boxes on the same floor tile. | Box | Purpose | | --- | --- | | Trigger | Starts the stack when an event happens, such as a user entering or speaking | | Condition | Checks whether the stack can run | | Effect | Changes the room, furniture, or selected users | | Selector | Chooses the furniture or users that other boxes act on | | Add-on | Changes how the stack chooses, limits, or runs its effects | A greeting stack can combine a room-entry trigger with a message effect. Conditions can restrict who receives the message. Configure each box in the client before checking the result in the room. Effects can have delays. Moving, editing, or removing a box can cancel its unfinished work. Ordinary effects attached to a user also stop when that user's room visit ends. ## Variables, links, and scores Variables store values that room logic can read and change. PlusEMU includes variable triggers, conditions, effects, and variable-driven visual effects. Wired Label, Wired Link, and Room Linker support more complex room setups. Leaderboard boxes support score displays. Wired chests and contracts support controlled exchanges of items. Available boxes depend on the emulator version, catalogue, and client assets. A box needs a matching furniture definition and editor, as well as server support. ## Hide Wired furniture The room owner, or staff who can manage any room, can type: ```text :hidewired ``` The command toggles Wired furniture visibility and saves the choice for the room. Run it again to show the boxes for editing. ## Web API The Variables Web API add-on generates read and write keys for a room. External tools send those keys in the `X-Wired-Read-Key` and `X-Wired-Write-Key` headers. The API is served by the emulator's `WebServer`, normally on `127.0.0.1:8080`. On an nginx installation, add this route to the hotel's server block: ```nginx location /api/public/ { proxy_pass http://127.0.0.1:8080; } ``` Check nginx configuration before reloading it. The installer's existing furniture-data route does not expose the Wired API by itself. On a Windows installation, IIS (Internet Information Services) needs a matching rewrite rule from `api/public/` to `http://127.0.0.1:8080/api/public/`. For example, list the variables in room `123` with its read key: ```bash curl 'https://your-domain/api/public/rooms/123/variables' \ -H 'X-Wired-Read-Key: replace-with-room-read-key' ``` The API also has routes under `/api/public/rooms/{roomId}/variables_profile/` for variable profiles. Use a read key for read-only integrations. Keep write keys out of public pages and client bundles. For complete route and request definitions, see [WiredWebApiEndpoints.cs](https://github.com/plus-emulator/PlusEMU/blob/master/Communication/Http/WiredWebApiEndpoints.cs). ## When a stack does nothing Check the trigger and conditions first. Confirm that selected users or furniture still exist in the room and that the relevant boxes share the intended tile. If the box does not appear in the catalogue or has no usable editor, check the matching client assets and furniture definitions. Restarting a room cannot add missing catalogue content. --- # Configuration Find emulator, client, and website settings, service commands, and the database connection. Source: https://docs.plusemu.dev/getting-started/configuration/ PlusEMU, Volt, and Atom CMS each have their own settings. The installer connects them during setup. ## File locations | File or directory | Linux installer | Windows installer | | --- | --- | --- | | Hotel root | `/var/www/hotel` | `C:\Hotel` | | Emulator settings | `/var/www/hotel/emulator/Config/config.json` | `C:\Hotel\emulator\Config\config.json` | | Client configuration | `/var/www/hotel/client/configuration/` | `C:\Hotel\client\configuration\` | | Website settings | `/var/www/hotel/cms/.env` | `C:\Hotel\cms\.env` | | Saved installer answers and credentials | `/etc/plusemu/hotel.env` | `C:\ProgramData\PlusEMU\hotel.json` | | Installer log | `/var/log/plusemu-install.log` | `C:\ProgramData\PlusEMU\install.log` | For a manual installation, `Config/config.json` is relative to the emulator's working directory. Logging uses `Config/nlog.config`. ## Emulator settings | Section | Purpose | Default address | | --- | --- | --- | | `Database` | MariaDB or MySQL connection and migration settings | `127.0.0.1:3306` | | `Nitro` | WebSocket listener used by Volt | `127.0.0.1:2096` | | `Flash` | TCP listener for the AIR client | `127.0.0.1:1232` | | `Rcon` | Remote control connection used by the website | `127.0.0.1:30001` | | `WebServer` | Generated furniture data and HTTP APIs | `127.0.0.1:8080` | | `Camera` | Private renderer connection and photo storage | `127.0.0.1:3921` | | `AirClient` | AIR gamedata, launcher settings, and tickets | No public game host by default | The installer keeps the emulator and database on local addresses. The web server forwards public client requests to the emulator. Changes to `Config/config.json` take effect after an emulator restart. Game settings stored in the `server_settings` database table work differently. You can reload them without a restart. Staff need both `command.update` and `command.update_configuration` to reload them with `:update settings`. ## Restart and inspect the emulator On Linux: ```bash sudo systemctl restart plusemu sudo journalctl -u plusemu -f ``` On Windows, use an Administrator PowerShell: ```powershell Restart-Service PlusEMU Get-Content C:\Hotel\emulator\service\shawl_for_PlusEMU_rCURRENT.log -Tail 50 -Wait ``` ## Client connection and furniture data Volt reads `renderer-config.json` and `ui-config.json` from its configuration directory. The installer sets `socket.url` in `renderer-config.json` to your public WebSocket address. Current PlusEMU builds generate furniture data from the database. When the emulator serves furniture data during installation, the installer sets `furnidata.url` in `renderer-config.json` to: ```text https://your-domain/api/gamedata/furnidata ``` To check the emulator directly on the server: ```bash curl http://127.0.0.1:8080/api/gamedata/furnidata ``` A JSON response confirms that the emulator serves furniture data. If the public URL fails while the local URL works, inspect the web server route for `/api/gamedata/furnidata`. The [Wired Web API](/features/wired/#web-api) needs an additional `/api/public/` route. The camera needs a public [photo directory](/features/camera-setup/#serve-the-photos). ## Database access The installer uses database `plus`, username `hotel`, host `127.0.0.1`, and port `3306`. Retrieve the database password from the protected installer file. Its key is `DB_PASSWORD` on Linux or `DbPassword` on Windows. These files also contain the initial admin password. Keep their contents private. On Linux, connect with: ```bash mariadb --host=127.0.0.1 --port=3306 --user=hotel --password plus ``` Enter the password at the prompt. For a desktop database client, use an SSH tunnel to `127.0.0.1:3306` through the server's SSH address. Keep the database port private. --- # Install your hotel Install PlusEMU, Volt, and Atom CMS on Linux or Windows Server, then connect your domain. Source: https://docs.plusemu.dev/getting-started/installation/ Use the PlusEMU installer to set up the game server, Volt client, Atom CMS website, MariaDB, and hotel assets together. ## Prepare a server Start with a fresh server. The installer changes the web server, database, and firewall configuration. | Requirement | Linux | Windows | | --- | --- | --- | | Operating system | Debian 12 or 13; Ubuntu 22.04, 24.04, or 26.04 | Windows Server 2019, 2022, or 2025 | | Architecture | x64 or ARM64 | x64 | | Free disk space | 10 GB | 15 GB on `C:` | | Access | Root or `sudo` | PowerShell as Administrator | Have a domain and a Cloudflare account ready. The installer generates the instructions for your domain after installation. ## Run the installer On Linux, run: ```bash curl -fsSL https://raw.githubusercontent.com/plus-emulator/plusemu-installer/main/install.sh | sudo bash ``` On Windows, open PowerShell as Administrator and run: ```powershell irm https://raw.githubusercontent.com/plus-emulator/plusemu-installer/main/install.ps1 | iex ``` Both commands download and run the installer. To read the script first, download `install.sh` or `install.ps1` from the [installer repository](https://github.com/plus-emulator/plusemu-installer). Then run `sudo bash install.sh` on Linux, or `.\install.ps1` in an Administrator PowerShell on Windows. Answer the prompts for your domain, WebSocket address, hotel name, admin username, and admin email. Keep the suggested WebSocket address, `wss://your-domain/ws`, unless your setup needs a separate hostname. Save the initial admin login details shown at the end. The installer also saves its answers and credentials in a protected local file. ## Connect your domain Open the setup guide that the installer prints or opens. On Linux, the installer prints a private address like `http:////`. The token is random. On Windows, the guide opens in your browser, and a copy named **Hotel setup guide** is saved on the desktop. Follow that guide to: 1. Add the domain to Cloudflare and change its nameservers. 2. Add the proxied DNS records for your server. 3. Generate a private key and certificate-signing request on the server. 4. Request a Cloudflare Origin CA certificate and install the signed certificate on the server. 5. Set Cloudflare SSL/TLS to **Full (strict)** and turn on **Always Use HTTPS**. 6. Check that WebSockets are enabled. Install the origin certificate before selecting **Full (strict)**. The installer starts with a temporary self-signed certificate. Keep the private key on the server and paste only the certificate-signing request into Cloudflare. ## Check your hotel 1. Open `https://your-domain` and sign in with the initial admin account. 2. Change the initial password. 3. Open the client and enter a room. 4. Open `https://your-domain/housekeeping` to manage the website. If installation stops, read the error in the installer log, fix its cause, and rerun the same command. The installer reuses your saved answers and keeps the parts that are already installed. For Cloudflare connection errors, use the troubleshooting section in the generated guide. It contains the paths and commands for your server. ## Next steps Read [configuration](/getting-started/configuration/) for file locations and service commands. Camera rendering needs a [separate setup](/features/camera-setup/). For an emulator-only installation, use the [PlusEMU release archives](https://github.com/plus-emulator/PlusEMU/releases) and the [manual setup instructions](https://github.com/plus-emulator/PlusEMU#setup). The archives are self-contained and do not require a separate .NET installation. --- # Update PlusEMU Replace the emulator safely and understand automatic database migrations and backups. Source: https://docs.plusemu.dev/getting-started/updates/ Update the emulator from a [PlusEMU release](https://github.com/plus-emulator/PlusEMU/releases). Running the installer again does not update the emulator or the client. It does fetch Atom CMS again and rewrite the web server configuration, so do not use it to update. ## Prepare the update 1. Read the release notes for client, website, and configuration requirements. 2. Download the archive for your system: `plusemu-linux-x64.tar.gz`, `plusemu-linux-arm64.tar.gz`, or `plusemu-win-x64.zip`. 3. Check the archive against the release's `SHA256SUMS` file. On Linux, run `sha256sum --check --ignore-missing SHA256SUMS` in the download directory. 4. Back up the database, emulator configuration, website configuration, and camera photos. 5. Stop the emulator before replacing its executable. On Linux: ```bash sudo systemctl stop plusemu ``` On Windows: ```powershell Stop-Service PlusEMU ``` Extract the release into a separate directory. Compare its `Config/config.json` with your installed settings and add any required new keys. Keep your database password, the camera token (`Camera.Bearer`), and your custom settings. Replace the installed executable and the `snowstorm/` directory with the new ones. Compare `Config/figuredata.xml` with the release copy. Keep the previous files with your backup. On Linux, preserve executable permissions and allow the `plusemu` service account to read the new file. ## Start the new version On Linux: ```bash sudo systemctl start plusemu sudo journalctl -u plusemu -f ``` On Windows: ```powershell Start-Service PlusEMU Get-Content C:\Hotel\emulator\service\shawl_for_PlusEMU_rCURRENT.log -Tail 50 -Wait ``` PlusEMU applies pending database migrations before loading the hotel. With the default settings, it writes a full database backup first and stops if that backup fails. After startup, sign in, enter a room, and open the catalogue. Check the log for migration or configuration errors. If you also update Volt, preserve its hotel configuration and restart the camera renderer so it loads the new `camera.html` build. ## Inspect migrations separately To see migration status, run the new executable from the directory containing your installed `Config/`. On Linux, run it as the `plusemu` service account, because only that account can read `Config/config.json`: ```bash cd /var/www/hotel/emulator sudo -u plusemu ./"Plus Emulator" --migrate-status ``` On Windows, use an Administrator PowerShell: ```powershell cd C:\Hotel\emulator & '.\Plus Emulator.exe' --migrate-status ``` The status command changes nothing. To apply pending migrations and exit without starting the hotel, use `--migrate` instead. Keep the emulator service stopped while you run it. On Linux, also run `--migrate` as `plusemu`, so later backups can write to the backup directory. Migration scripts are compiled into the executable. PlusEMU records applied migrations in `schema_migrations`. The release also includes `Database/Migrations/` and `Database/FreshInstall.sql` for reference. For a database from before migration 60, set `Database.Migrations.FurnidataFile` to the hotel's existing `FurnitureData.json` if the emulator stops and asks for it. ## Recover from a failed migration Read the migration filename and failed statement in the log. Fix the cause before starting again, or restore the database backup and matching previous executable. Database schema changes can remain after a failed migration. Replacing the executable alone does not restore the previous database state. The default backup directory is `backups` under the emulator's working directory. `Database.Migrations.BackupDirectory` can change it. These migration backups do not replace regular backups of the whole hotel. --- # Use these docs with AI Read individual Markdown pages or download the complete PlusEMU documentation for a language model. Source: https://docs.plusemu.dev/using-these-docs/ Every documentation page has a **View as Markdown** link below its title. Use that link when you want to paste a page into a language model or read it without the website layout. ## Choose an export | File | Contents | | --- | --- | | [llms.txt](/llms.txt) | Page titles, descriptions, and links to each Markdown file. | | [llms-full.txt](/llms-full.txt) | All published pages in one plain-text file. | | [Home page as Markdown](/markdown/index.md) | An example of an individual Markdown page. | The site creates these files from the documentation during each build. Each exported page includes its source URL. ## Give a model the right context Include your PlusEMU release, the feature you are configuring, and the relevant documentation page. Remove passwords, authentication tokens, and personal information from any configuration or logs you share. Check generated commands before running them. A model may suggest settings from another emulator or an older PlusEMU release. You can download all pages with this command: ```sh curl --fail --output plusemu-docs.txt https://docs.plusemu.dev/llms-full.txt ```