feat: Update README with branch version and single-server defaults; enhance inventory and room handling for equipped items

This commit is contained in:
Alexa
2026-06-28 02:15:58 +03:00
parent 7da20f2047
commit fbfabaf26e
6 changed files with 99 additions and 12 deletions
@@ -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; }
}
/// <summary>POST <c>api/equipment/v1/update</c> — 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,
};
}
@@ -2534,6 +2534,8 @@ public class RoomsController(
/// verifying ownership and intra-room uniqueness.</summary>
[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<IActionResult> 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<IActionResult> 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.</summary>
[HttpPost("rooms/{roomId:long}/subrooms/{subRoomId:long}/move")]
[HttpPost("roomserver/rooms/{roomId:long}/subrooms/{subRoomId:long}/move")]
[Authorize]
public async Task<IActionResult> MoveSubRoom(long roomId, long subRoomId, [FromBody] SubRoomMoveRequest req)
{
@@ -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,
@@ -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