From 28c1755d999834236acaba2cb390fcd3e53a7828 Mon Sep 17 00:00:00 2001 From: Alexa <53471801+Alexa-RR@users.noreply.github.com> Date: Sun, 28 Jun 2026 02:15:58 +0300 Subject: [PATCH] feat: Update README with branch version and single-server defaults; enhance inventory and room handling for equipped items --- .../API/Inventory/InventoryController.cs | 33 ++++++++++++- .../API/Rooms/V2/RoomsController.cs | 8 ++++ .../Match/MatchPlayerController.cs | 6 +-- .../Match/PlayerPresenceController.cs | 14 +++--- .../Services/NotificationService.cs | 3 +- README.md | 47 ++++++++++++++++++- 6 files changed, 99 insertions(+), 12 deletions(-) diff --git a/DorkNet.Server/Controllers/API/Inventory/InventoryController.cs b/DorkNet.Server/Controllers/API/Inventory/InventoryController.cs index 496ddd4..5f3fb68 100644 --- a/DorkNet.Server/Controllers/API/Inventory/InventoryController.cs +++ b/DorkNet.Server/Controllers/API/Inventory/InventoryController.cs @@ -66,6 +66,13 @@ public class InventoryController( public string PrefabName { get; set; } = string.Empty; public string ModificationGuid { get; set; } = string.Empty; public bool Favorited { get; set; } + // The 2023 client sends Equipped/IsEquipped when a weapon skin is + // equipped (distinct from Favorited). We only have one IsActive flag, + // and for skins "active" == "equipped", so honor whichever the client + // sent. Without this the equip was dropped (only Favorited was read) + // and the skin never stuck. + public bool? Equipped { get; set; } + public bool? IsEquipped { get; set; } } /// POST api/equipment/v1/update — toggle the @@ -103,7 +110,27 @@ public class InventoryController( // to themselves). return Forbid(); } - row.IsActive = req.Favorited; + // "equipped" is the meaningful state for weapon skins; prefer it, + // fall back to Favorited for plain tool-pin updates. + var active = req.Equipped ?? req.IsEquipped ?? req.Favorited; + + // One skin equipped per weapon: when equipping a catalog-backed skin, + // clear the equipped flag on the player's OTHER skins for the SAME + // prefab (slugs share the "{prefix}{prefab}:" head) so the weapon + // doesn't end up with two "equipped" skins. + if (active && !string.IsNullOrWhiteSpace(req.ModificationGuid) + && !string.IsNullOrWhiteSpace(req.PrefabName)) + { + var prefabPrefix = StoreService.EquipmentSkinSlug(req.PrefabName, string.Empty); + var siblings = await db.PlayerInventory + .Where(inv => inv.PlayerId == pid && inv.IsActive + && inv.ItemSlug != slug + && inv.ItemSlug.StartsWith(prefabPrefix)) + .ToListAsync(); + foreach (var sib in siblings) sib.IsActive = false; + } + + row.IsActive = active; await db.SaveChangesAsync(); return Ok(new { Updated = true }); } @@ -261,7 +288,11 @@ public class InventoryController( FriendlyName = item.DisplayName, Tooltip = item.Description, Rarity = rarity, + // IsActive is the equipped/active flag; surface it under every key + // the 2023 client reads so the equipped skin shows on reload. Favorited = inv.IsActive, + Equipped = inv.IsActive, + IsEquipped = inv.IsActive, }; } diff --git a/DorkNet.Server/Controllers/API/Rooms/V2/RoomsController.cs b/DorkNet.Server/Controllers/API/Rooms/V2/RoomsController.cs index 2c7b005..0580cbc 100644 --- a/DorkNet.Server/Controllers/API/Rooms/V2/RoomsController.cs +++ b/DorkNet.Server/Controllers/API/Rooms/V2/RoomsController.cs @@ -2534,6 +2534,8 @@ public class RoomsController( /// verifying ownership and intra-room uniqueness. [HttpPost("rooms/{roomId:long}/subrooms/{subRoomId:long}/name")] [HttpPut("rooms/{roomId:long}/subrooms/{subRoomId:long}/name")] + [HttpPost("roomserver/rooms/{roomId:long}/subrooms/{subRoomId:long}/name")] + [HttpPut("roomserver/rooms/{roomId:long}/subrooms/{subRoomId:long}/name")] [Authorize] public async Task RenameSubroom( long roomId, @@ -3353,8 +3355,13 @@ public class RoomsController( return Ok(SceneWire(clone)); } + // Subroom settings save POSTs here on the roomserver host ("failed to + // modify room" = the bare-only routes 404'd). Same roomserver/ prefix as + // the sibling subroom mutations. [HttpPost("rooms/{roomId:long}/subrooms/{subRoomId:long}/modify")] [HttpPut("rooms/{roomId:long}/subrooms/{subRoomId:long}/modify")] + [HttpPost("roomserver/rooms/{roomId:long}/subrooms/{subRoomId:long}/modify")] + [HttpPut("roomserver/rooms/{roomId:long}/subrooms/{subRoomId:long}/modify")] [Authorize] public Task ModifySubRoom(long roomId, long subRoomId, [FromBody] SubRoomModifyRequest req) => MutateScene(roomId, subRoomId, s => @@ -3370,6 +3377,7 @@ public class RoomsController( /// scene. Body NewIndex is the target OrderIndex; the in-between /// rows shift to fill the gap. [HttpPost("rooms/{roomId:long}/subrooms/{subRoomId:long}/move")] + [HttpPost("roomserver/rooms/{roomId:long}/subrooms/{subRoomId:long}/move")] [Authorize] public async Task MoveSubRoom(long roomId, long subRoomId, [FromBody] SubRoomMoveRequest req) { diff --git a/DorkNet.Server/Controllers/Match/MatchPlayerController.cs b/DorkNet.Server/Controllers/Match/MatchPlayerController.cs index acfb36c..fd96f96 100644 --- a/DorkNet.Server/Controllers/Match/MatchPlayerController.cs +++ b/DorkNet.Server/Controllers/Match/MatchPlayerController.cs @@ -158,7 +158,7 @@ public class MatchPlayerController( return Ok(new PresenceDto { PlayerId = playerId, - StatusVisibility = 1, + StatusVisibility = 0, // Everyone (was 1 = FriendsOnly) DeviceClass = 0, VrMovementMode = 0, RoomInstance = room, @@ -174,7 +174,7 @@ public class MatchPlayerController( return Ok(new PresenceDto { PlayerId = playerId, - StatusVisibility = value ?? 1, + StatusVisibility = value ?? 0, // default Everyone; honor explicit client choice DeviceClass = 0, VrMovementMode = 0, RoomInstance = room, @@ -190,7 +190,7 @@ public class MatchPlayerController( return Ok(new PresenceDto { PlayerId = playerId, - StatusVisibility = 1, + StatusVisibility = 0, // Everyone (was 1 = FriendsOnly) DeviceClass = 0, VrMovementMode = value ?? 0, RoomInstance = room, diff --git a/DorkNet.Server/Controllers/Match/PlayerPresenceController.cs b/DorkNet.Server/Controllers/Match/PlayerPresenceController.cs index c832966..1942b42 100644 --- a/DorkNet.Server/Controllers/Match/PlayerPresenceController.cs +++ b/DorkNet.Server/Controllers/Match/PlayerPresenceController.cs @@ -98,12 +98,14 @@ public class PlayerPresenceController( return new PlayerPresenceDto { PlayerId = (int)id, - // StatusVisibility is the player's *preference* (would - // they like to appear online if they were online?). For - // offline players we still report Visible so when they - // come back the friend's cached preference is correct, - // and IsOnline=false is what gates the "Online" badge. - StatusVisibility = 1, // Visible + // StatusVisibility is the player's *preference* for who may + // see them online. The enum is Everyone=0 / FriendsOnly=1 / + // Nobody=2 — so the previous value 1 (mislabeled "Visible") + // actually meant FriendsOnly, and anyone who wasn't your + // friend saw you as OFFLINE. On a private server everyone + // should see everyone, so report Everyone (0). IsOnline=false + // still gates the "Online" badge for offline players. + StatusVisibility = 0, // Everyone DeviceClass = 0, // Desktop / unknown — not VR VrMovementMode = 0, // Full instance details are safe, and necessary, when the diff --git a/DorkNet.Server/Services/NotificationService.cs b/DorkNet.Server/Services/NotificationService.cs index 0fdfe73..842c2c3 100644 --- a/DorkNet.Server/Services/NotificationService.cs +++ b/DorkNet.Server/Services/NotificationService.cs @@ -270,7 +270,8 @@ public class NotificationService( var msg = new System.Text.Json.Nodes.JsonObject { ["playerId"] = (int)playerId, - ["statusVisibility"] = 1, + // Everyone=0 (was 1=FriendsOnly, which hid you from non-friends). + ["statusVisibility"] = 0, ["deviceClass"] = 0, ["vrMovementMode"] = 0, ["roomInstance"] = redacted is null diff --git a/README.md b/README.md index 9bfd528..439e2f0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# DorkNet — `december-2020-12-18` +# DorkNet — `march-2023-03-21` This is the **December 2020.12.18** branch of DorkNet — a self-hostable reimplementation of the Rec Room backend tuned to the wire protocol of @@ -298,6 +298,51 @@ and room-move presence fan-out. --- +## Single-server defaults (review before going public) + +Several values are hardcoded or defaulted for the convenience of running a +small, trusted, single-operator server. They work out of the box for a group +of friends but should be reviewed / adapted before exposing the server to the +public internet: + +- **Client build version (`appVersion`).** Hardcoded to this branch's client + build (`20230317`) in the presence/match/notification responses + (`MatchPlayerController`, `PlayerPresenceController`, `NotificationService`, + `RoomsController`). It must match the build everyone runs, or players get a + "version mismatch" and can't join each other. If you serve a different + client build, change it (ideally hoist it to one shared constant). +- **No real authentication.** Accounts are keyed by a per-install `deviceId` + the client generates (`PlayerService.GetOrCreateByDeviceAsync`) — **not** a + verified identity (Steam ID is deliberately ignored so emulators work). + Anyone who can reach the server with a patched client gets/creates an + account. Use **Settings → Server → disable signups** + the + [signup-code](#signup-codes) flow to gate who can join; there is no password + wall by default. +- **First account becomes admin.** The first account created on a fresh DB is + granted `IsAdmin` so there's always a root admin. On a public server, create + your admin account first and verify no one else beat you to it. +- **Online status = Everyone.** Presence reports `StatusVisibility = 0` + (Everyone) so every player appears online to every other player, matching + the [everyone-is-friends](#everyone-is-friends-toggle) convenience. A public + server may instead want to persist and honor each player's own visibility + preference. +- **Room / matchmaking capacity.** New matchmade sessions and subrooms default + to **8** players and are capped at **40** (`GameSessionService`, + `RoomSceneEntity.MaxPlayers`, admin `MaxCapacity` clamp). The in-game "Max + Player Count in This Subroom" slider raises it per room (slider ceiling 40). +- **Avatar unlock clamp.** The wardrobe clamp defaults high + (`Avatar:MaxUnlockedItems` 4000 / per-slot 1000) for this desktop client — + tune in config if you change the ownership model. +- **Hosts / domain.** The apex and per-service subdomains come from + `DomainConfig` (`Domain:Apex`) / `appsettings`; set these to your own + domain. The `Everyone-is-friends`, signup-disable, and similar switches live + in **admin Settings**, not source. + +None of these are security boundaries as shipped — treat the server as trusted +LAN/friends infrastructure unless you harden the items above. + +--- + ## Contributing See [main's CONTRIBUTING guide](../../blob/main/CONTRIBUTING.md) for