mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-26 07:28:45 -07:00
feat: update PrivateInstanceService to include baseName in instance updates
This commit is contained in:
@@ -198,6 +198,32 @@ public class AvatarGiftsController(
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(gift.EquipmentPrefabName) ||
|
||||
!string.IsNullOrWhiteSpace(gift.EquipmentModificationGuid))
|
||||
{
|
||||
var itemSlug = !string.IsNullOrWhiteSpace(gift.EquipmentModificationGuid)
|
||||
? StoreService.EquipmentSkinSlug(gift.EquipmentPrefabName, gift.EquipmentModificationGuid)
|
||||
: gift.EquipmentPrefabName;
|
||||
if (!string.IsNullOrWhiteSpace(itemSlug))
|
||||
{
|
||||
var row = await db.PlayerInventory
|
||||
.FirstOrDefaultAsync(p => p.PlayerId == pid && p.ItemSlug == itemSlug);
|
||||
if (row is null)
|
||||
{
|
||||
db.PlayerInventory.Add(new PlayerInventoryEntity
|
||||
{
|
||||
PlayerId = pid,
|
||||
ItemSlug = itemSlug,
|
||||
Quantity = 1,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
row.Quantity = Math.Max(row.Quantity, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply currency / XP / level rewards bundled with the gift.
|
||||
// The previous implementation only handled the avatar item path,
|
||||
// so token gifts from the admin SPA marked themselves consumed
|
||||
|
||||
@@ -302,9 +302,10 @@ public class MessagesController(
|
||||
// PRIVATE instance: the sender created an invite-only match
|
||||
// (via /goto/room/X?CreatePrivateInstance=1). The
|
||||
// roomInstanceId the watch sends maps to a registered
|
||||
// PrivateInstance and only the owner is allowed to invite
|
||||
// anyone in. Enforces our private-match access control —
|
||||
// no random can DM their way past the invite list.
|
||||
// PrivateInstance and the owner or someone already inside
|
||||
// that exact live instance can invite. That keeps access
|
||||
// control intact while still letting a guest in someone
|
||||
// else's dorm invite a friend from the in-game watch.
|
||||
//
|
||||
// PUBLIC room: the sender is in a public room / a dorm /
|
||||
// any non-private room. RoomInstanceId is just
|
||||
@@ -319,12 +320,23 @@ public class MessagesController(
|
||||
var inst = await privateInstances.GetAsync(roomInstanceId);
|
||||
if (inst is not null)
|
||||
{
|
||||
if (inst.OwnerPlayerId != Me) return Forbid();
|
||||
var senderPresence = presence.GetRoom(Me);
|
||||
var senderIsInInstance = senderPresence is not null
|
||||
&& senderPresence.RoomInstanceId == roomInstanceId;
|
||||
if (inst.OwnerPlayerId != Me && !senderIsInInstance)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"[invite] from={From} tried to invite to private instance={Instance} ownedBy={Owner} while presenceInstance={PresenceInstance}; rejecting",
|
||||
Me, roomInstanceId, inst.OwnerPlayerId, senderPresence?.RoomInstanceId);
|
||||
return Forbid();
|
||||
}
|
||||
resolvedRoomId = inst.RoomId;
|
||||
roomName = await db.Rooms
|
||||
.Where(r => r.Id == inst.RoomId)
|
||||
.Select(r => r.Name)
|
||||
.FirstOrDefaultAsync() ?? "Private match";
|
||||
roomName = !string.IsNullOrWhiteSpace(inst.Name)
|
||||
? inst.Name
|
||||
: await db.Rooms
|
||||
.Where(r => r.Id == inst.RoomId)
|
||||
.Select(r => r.Name)
|
||||
.FirstOrDefaultAsync() ?? "Private match";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -371,6 +371,9 @@ public class RoomsController(
|
||||
[HttpPost("api/rooms/v4/saveData")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> SaveData([FromBody] SaveRoomSceneRequest body)
|
||||
=> await SaveDataCore(body);
|
||||
|
||||
private async Task<IActionResult> SaveDataCore(SaveRoomSceneRequest body, long? routeRoomId = null)
|
||||
{
|
||||
var pid = this.RequireCurrentPlayerId();
|
||||
|
||||
@@ -381,7 +384,7 @@ public class RoomsController(
|
||||
// on every /goto). This is the only signal we have without a
|
||||
// RoomId on the request body.
|
||||
var current = presence.GetRoom(pid);
|
||||
if (current is null)
|
||||
if (current is null && routeRoomId is null)
|
||||
{
|
||||
// No active room — without it we can't pick a target row.
|
||||
// Return a degenerate RoomScene so the deserializer doesn't
|
||||
@@ -401,11 +404,18 @@ public class RoomsController(
|
||||
});
|
||||
}
|
||||
|
||||
var roomId = current.RoomId;
|
||||
var roomId = routeRoomId ?? current!.RoomId;
|
||||
var room = await rooms.GetByIdAsync(roomId);
|
||||
if (room is null) return NotFound(new { error = "room_not_found" });
|
||||
|
||||
var newBlob = body.RoomDataFilename?.Trim() ?? string.Empty;
|
||||
var newBlob = body.EffectiveRoomDataFilename.Trim();
|
||||
if (string.IsNullOrWhiteSpace(newBlob))
|
||||
{
|
||||
logger.LogWarning(
|
||||
"[rooms-save] missing blob filename player={PlayerId} room={RoomId} requestedScene={RequestedSceneId} routeRoom={RouteRoomId}",
|
||||
pid, roomId, body.RoomSceneId, routeRoomId);
|
||||
return BadRequest(new { error = "missing_room_data_filename" });
|
||||
}
|
||||
var savedAt = DateTime.UtcNow;
|
||||
|
||||
// Multi-scene rooms have a RoomScenes row per scene. The 2020
|
||||
@@ -494,30 +504,35 @@ public class RoomsController(
|
||||
logger.LogInformation(
|
||||
"[rooms-save] saveData player={PlayerId} room={RoomId} requestedScene={RequestedSceneId} savedScene={SavedSceneId} blob={BlobName} sceneRow={SceneRowFound} scenes={SceneCount}",
|
||||
pid, roomId, body.RoomSceneId, savedSceneId, newBlob, sceneRow is not null, sceneRowsForRoom.Count);
|
||||
var playersInInstance = onlinePresence.OnlinePlayerIds()
|
||||
.Where(playerId =>
|
||||
{
|
||||
var playerRoom = presence.GetRoom(playerId);
|
||||
return playerRoom is not null
|
||||
&& playerRoom.RoomId == roomId
|
||||
&& playerRoom.RoomInstanceId == current.RoomInstanceId;
|
||||
})
|
||||
.Append(pid)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
var activeInstanceId = current is not null && current.RoomId == roomId
|
||||
? current.RoomInstanceId
|
||||
: 0L;
|
||||
var playersInInstance = activeInstanceId == 0
|
||||
? new[] { pid }
|
||||
: onlinePresence.OnlinePlayerIds()
|
||||
.Where(playerId =>
|
||||
{
|
||||
var playerRoom = presence.GetRoom(playerId);
|
||||
return playerRoom is not null
|
||||
&& playerRoom.RoomId == roomId
|
||||
&& playerRoom.RoomInstanceId == activeInstanceId;
|
||||
})
|
||||
.Append(pid)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
logger.LogInformation(
|
||||
"[rooms-save] fanout room update player={PlayerId} room={RoomId} instance={InstanceId} blob={BlobName} recipients={RecipientCount}",
|
||||
pid, roomId, current.RoomInstanceId, newBlob, playersInInstance.Length);
|
||||
pid, roomId, activeInstanceId, newBlob, playersInInstance.Length);
|
||||
|
||||
var updatedRoomInstances = new Dictionary<long, RoomInstanceDto>();
|
||||
foreach (var playerId in playersInInstance)
|
||||
{
|
||||
var currentPresence = presence.GetRoom(playerId);
|
||||
if (currentPresence is null && playerId == pid)
|
||||
if (currentPresence is null && playerId == pid && current is not null)
|
||||
{
|
||||
currentPresence = current;
|
||||
}
|
||||
if (currentPresence is null) continue;
|
||||
if (currentPresence is null || currentPresence.RoomId != roomId) continue;
|
||||
|
||||
currentPresence.DataBlob = newBlob;
|
||||
presence.SetRoom(playerId, currentPresence);
|
||||
@@ -601,9 +616,16 @@ public class RoomsController(
|
||||
{
|
||||
public long RoomSceneId { get; set; }
|
||||
public string? RoomDataFilename { get; set; }
|
||||
[System.Text.Json.Serialization.JsonPropertyName("filename")]
|
||||
public string? Filename { get; set; }
|
||||
public List<long>? InventionUsages { get; set; }
|
||||
public string? InventionUsageBase64 { get; set; }
|
||||
public CreatorActionContextDto? CreatorActionContext { get; set; }
|
||||
public long RequestPlayerId { get; set; }
|
||||
public int SaveRequestPlayerId { get; set; }
|
||||
|
||||
public string EffectiveRoomDataFilename =>
|
||||
RoomDataFilename ?? Filename ?? string.Empty;
|
||||
}
|
||||
|
||||
public class CreatorActionContextDto
|
||||
@@ -2403,7 +2425,7 @@ public class RoomsController(
|
||||
[FromBody] SaveRoomSceneRequest body)
|
||||
{
|
||||
body.RoomSceneId = subRoomId;
|
||||
return SaveData(body);
|
||||
return SaveDataCore(body, roomId);
|
||||
}
|
||||
|
||||
private async Task<IActionResult> MutateScene(long roomId, long subRoomId, Action<RoomSceneEntity> mutator)
|
||||
|
||||
@@ -103,6 +103,7 @@ public class StorefrontsBuyController(
|
||||
StoreService.TryGetAvatarItemPayload(item.Slug, out var avatarItemType, out avatarItemDesc);
|
||||
var inventoryAvatarDesc = StoreService.InventoryAvatarItemDesc(avatarItemDesc);
|
||||
StoreService.TryGetConsumableItemDesc(item.Slug, out var consumableItemDesc);
|
||||
StoreService.TryGetEquipmentPayload(item.Slug, out var equipmentPrefabName, out var equipmentModificationGuid);
|
||||
if (StoreService.TryGetHairDyePayload(item.Slug, out var hairDyeConsumableDesc, out var hairDyeColorGuid))
|
||||
{
|
||||
avatarItemType = 1;
|
||||
@@ -116,6 +117,8 @@ public class StorefrontsBuyController(
|
||||
AvatarItemType = string.IsNullOrEmpty(inventoryAvatarDesc) ? null : avatarItemType,
|
||||
AvatarItemDescOrHairDyeDesc = inventoryAvatarDesc,
|
||||
ConsumableItemDesc = consumableItemDesc,
|
||||
EquipmentPrefabName = equipmentPrefabName,
|
||||
EquipmentModificationGuid = equipmentModificationGuid,
|
||||
CurrencyType = req.CurrencyType,
|
||||
Currency = 0,
|
||||
Xp = 0,
|
||||
@@ -158,6 +161,8 @@ public class StorefrontsBuyController(
|
||||
AvatarItemType = gift.AvatarItemType,
|
||||
AvatarItemDesc = avatarItemDesc,
|
||||
ConsumableItemDesc = gift.ConsumableItemDesc ?? string.Empty,
|
||||
EquipmentPrefabName = gift.EquipmentPrefabName,
|
||||
EquipmentModificationGuid = gift.EquipmentModificationGuid,
|
||||
Message = gift.Message ?? string.Empty,
|
||||
});
|
||||
}
|
||||
@@ -341,10 +346,13 @@ public class StorefrontsBuyController(
|
||||
var avatarItemType = 0;
|
||||
var avatarItemDesc = string.Empty;
|
||||
var consumableItemDesc = string.Empty;
|
||||
var equipmentPrefabName = string.Empty;
|
||||
var equipmentModificationGuid = string.Empty;
|
||||
if (grantedSlug is not null)
|
||||
{
|
||||
StoreService.TryGetAvatarItemPayload(grantedSlug, out avatarItemType, out avatarItemDesc);
|
||||
StoreService.TryGetConsumableItemDesc(grantedSlug, out consumableItemDesc);
|
||||
StoreService.TryGetEquipmentPayload(grantedSlug, out equipmentPrefabName, out equipmentModificationGuid);
|
||||
if (StoreService.TryGetHairDyePayload(grantedSlug, out var hairDyeConsumableDesc, out var hairDyeColorGuid))
|
||||
{
|
||||
avatarItemType = 1;
|
||||
@@ -378,8 +386,8 @@ public class StorefrontsBuyController(
|
||||
AvatarItemDesc = avatarItemDesc,
|
||||
AvatarItemDescOrHairDyeDesc = StoreService.InventoryAvatarItemDesc(avatarItemDesc),
|
||||
ConsumableItemDesc = consumableItemDesc,
|
||||
EquipmentPrefabName = string.Empty,
|
||||
EquipmentModificationGuid = string.Empty,
|
||||
EquipmentPrefabName = equipmentPrefabName,
|
||||
EquipmentModificationGuid = equipmentModificationGuid,
|
||||
CurrencyType = currencyType,
|
||||
Currency = 0,
|
||||
Message = string.Empty,
|
||||
|
||||
@@ -1073,6 +1073,9 @@ public class GoToController(
|
||||
// load (black screen).
|
||||
var isDorm = roomName.Equals("DormRoom", StringComparison.OrdinalIgnoreCase);
|
||||
var dormOwnerId = this.CurrentPlayerId();
|
||||
var dormInstanceName = isDorm && dormOwnerId is long ownerForDisplay
|
||||
? await BuildDormInstanceNameAsync(ownerForDisplay)
|
||||
: roomName;
|
||||
var photonRoom = (isDorm && dormOwnerId is long ownerForName)
|
||||
? $"^dormroom_p{ownerForName}"
|
||||
: $"^{roomName.ToLowerInvariant()}_{roomId}";
|
||||
@@ -1099,7 +1102,7 @@ public class GoToController(
|
||||
// Lowercase enum names — that's the wire format Photon uses too.
|
||||
PhotonRegionId = photonRegion,
|
||||
PhotonRoomId = photonRoom,
|
||||
Name = roomName,
|
||||
Name = dormInstanceName,
|
||||
MaxCapacity = 8,
|
||||
IsFull = false,
|
||||
IsPrivate = false,
|
||||
@@ -1123,7 +1126,7 @@ public class GoToController(
|
||||
ownerPlayerId: ownerPid,
|
||||
roomId: resp.RoomInstance.RoomId,
|
||||
subRoomId: 0,
|
||||
baseName: resp.RoomInstance.Name,
|
||||
baseName: dormInstanceName,
|
||||
location: resp.RoomInstance.Location,
|
||||
dataBlob: resp.RoomInstance.DataBlob,
|
||||
photonRegion: resp.RoomInstance.PhotonRegionId,
|
||||
@@ -1136,6 +1139,23 @@ public class GoToController(
|
||||
return resp;
|
||||
}
|
||||
|
||||
private async Task<string> BuildDormInstanceNameAsync(long ownerPlayerId)
|
||||
{
|
||||
var player = await db.Players.AsNoTracking()
|
||||
.Where(p => p.Id == ownerPlayerId)
|
||||
.Select(p => new { p.DisplayName, p.Username })
|
||||
.FirstOrDefaultAsync();
|
||||
var name = !string.IsNullOrWhiteSpace(player?.DisplayName)
|
||||
? player!.DisplayName
|
||||
: !string.IsNullOrWhiteSpace(player?.Username)
|
||||
? player!.Username
|
||||
: $"Player{ownerPlayerId}";
|
||||
var cleaned = new string(name
|
||||
.Select(ch => char.IsLetterOrDigit(ch) || ch == '_' || ch == '-' ? ch : '_')
|
||||
.ToArray()).Trim('_');
|
||||
return string.IsNullOrWhiteSpace(cleaned) ? $"Player{ownerPlayerId}_dorm" : $"{cleaned}_dorm";
|
||||
}
|
||||
|
||||
private async Task<string> ResolveInitialDataBlobAsync(long roomId, bool isDorm, long? dormOwnerId)
|
||||
{
|
||||
var room = await db.Rooms.AsNoTracking().FirstOrDefaultAsync(r => r.Id == roomId);
|
||||
|
||||
@@ -338,6 +338,7 @@ public class MatchPlayerController(
|
||||
var dataBlob = await ResolveDataBlobForPresenceAsync(roomId, playerId);
|
||||
var photonRegion = (config["Photon:CloudRegion"] ?? "us").ToLowerInvariant();
|
||||
var photonRoomId = $"^dormroom_p{playerId}";
|
||||
var dormInstanceName = await BuildDormInstanceNameAsync(playerId);
|
||||
|
||||
// EnsureForDormAsync owns the deterministic dorm instance-id
|
||||
// formula; reusing it here guarantees the heartbeat's
|
||||
@@ -346,7 +347,7 @@ public class MatchPlayerController(
|
||||
ownerPlayerId: playerId,
|
||||
roomId: roomId,
|
||||
subRoomId: 0,
|
||||
baseName: "DormRoom",
|
||||
baseName: dormInstanceName,
|
||||
location: location,
|
||||
dataBlob: dataBlob,
|
||||
photonRegion: photonRegion,
|
||||
@@ -361,7 +362,7 @@ public class MatchPlayerController(
|
||||
Location = location,
|
||||
PhotonRegionId = photonRegion,
|
||||
PhotonRoomId = photonRoomId,
|
||||
Name = "DormRoom",
|
||||
Name = dormInstanceName,
|
||||
MaxCapacity = 8,
|
||||
IsFull = false,
|
||||
IsPrivate = true,
|
||||
@@ -371,6 +372,23 @@ public class MatchPlayerController(
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<string> BuildDormInstanceNameAsync(long ownerPlayerId)
|
||||
{
|
||||
var player = await db.Players.AsNoTracking()
|
||||
.Where(p => p.Id == ownerPlayerId)
|
||||
.Select(p => new { p.DisplayName, p.Username })
|
||||
.FirstOrDefaultAsync();
|
||||
var name = !string.IsNullOrWhiteSpace(player?.DisplayName)
|
||||
? player!.DisplayName
|
||||
: !string.IsNullOrWhiteSpace(player?.Username)
|
||||
? player!.Username
|
||||
: $"Player{ownerPlayerId}";
|
||||
var cleaned = new string(name
|
||||
.Select(ch => char.IsLetterOrDigit(ch) || ch == '_' || ch == '-' ? ch : '_')
|
||||
.ToArray()).Trim('_');
|
||||
return string.IsNullOrWhiteSpace(cleaned) ? $"Player{ownerPlayerId}_dorm" : $"{cleaned}_dorm";
|
||||
}
|
||||
|
||||
private async Task<string> ResolveDataBlobForPresenceAsync(long roomId, int playerId)
|
||||
{
|
||||
var room = await db.Rooms.AsNoTracking().FirstOrDefaultAsync(r => r.Id == roomId);
|
||||
|
||||
Reference in New Issue
Block a user