mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 15:09:26 -07:00
228 lines
7.6 KiB
C#
228 lines
7.6 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.API.Storefront;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.Extensions;
|
|
using RadicadeServer.RecNet.Accounts.Local;
|
|
using RadicadeServer.RecNet.Avatar;
|
|
using RadicadeServer.RecNet.Enums.Store;
|
|
using RadicadeServer.RecNet.Rooms;
|
|
|
|
namespace RadicadeServer.Utils
|
|
{
|
|
public static class RoomJoinGiftUtils
|
|
{
|
|
private const string ClaimKeyPrefix = "Radicade.RoomJoinGift.";
|
|
|
|
/// <summary>
|
|
/// Whether a room's own Rooms row describes a box at all. A room can hold several boxes in
|
|
/// <c>RoomJoinGifts</c>; this answers for the single one that row still carries, which is
|
|
/// the whole story for a room saved before that table existed.
|
|
/// </summary>
|
|
public static bool HasConfiguredReward(RoomData room)
|
|
{
|
|
return room.JoinGiftCurrencyAmount > 0 ||
|
|
room.JoinGiftXp > 0 ||
|
|
!string.IsNullOrWhiteSpace(room.JoinGiftAvatarItem) ||
|
|
!string.IsNullOrWhiteSpace(room.JoinGiftEquipmentItem) ||
|
|
!string.IsNullOrWhiteSpace(room.JoinGiftConsumableItem);
|
|
}
|
|
|
|
public static bool HasConfiguredReward(RoomJoinGift gift)
|
|
{
|
|
return gift.CurrencyAmount > 0 ||
|
|
gift.Xp > 0 ||
|
|
!string.IsNullOrWhiteSpace(gift.AvatarItem) ||
|
|
!string.IsNullOrWhiteSpace(gift.EquipmentItem) ||
|
|
!string.IsNullOrWhiteSpace(gift.ConsumableItem);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The single box a room's own row describes, as a gift row. Used for a room that has no
|
|
/// rows in <c>RoomJoinGifts</c>: it is the same box, read from where it has always lived.
|
|
/// </summary>
|
|
public static RoomJoinGift Snapshot(RoomData room)
|
|
{
|
|
return new RoomJoinGift
|
|
{
|
|
RoomJoinGiftId = 0,
|
|
RoomId = room.RoomId,
|
|
SortOrder = 0,
|
|
Version = room.JoinGiftVersion,
|
|
Enabled = room.JoinGiftEnabled,
|
|
OncePerPlayer = room.JoinGiftOncePerPlayer,
|
|
Title = room.JoinGiftTitle,
|
|
Message = room.JoinGiftMessage,
|
|
AvatarItem = room.JoinGiftAvatarItem,
|
|
AvatarItemType = room.JoinGiftAvatarItemType,
|
|
EquipmentItem = room.JoinGiftEquipmentItem,
|
|
EquipmentModificationGuid = room.JoinGiftEquipmentModificationGuid,
|
|
ConsumableItem = room.JoinGiftConsumableItem,
|
|
CurrencyType = room.JoinGiftCurrencyType,
|
|
CurrencyAmount = room.JoinGiftCurrencyAmount,
|
|
Xp = room.JoinGiftXp,
|
|
Context = room.JoinGiftContext,
|
|
Rarity = room.JoinGiftRarity,
|
|
FromPlayerId = room.JoinGiftFromPlayerId
|
|
};
|
|
}
|
|
|
|
/// <summary>The boxes a room hands out, in the order the panel lists them.</summary>
|
|
public static async Task<List<RoomJoinGift>> ConfiguredGiftsAsync(RecDBContext ctx, long roomId)
|
|
{
|
|
return await ctx.RoomJoinGifts
|
|
.Where(x => x.RoomId == roomId)
|
|
.OrderBy(x => x.SortOrder)
|
|
.ThenBy(x => x.RoomJoinGiftId)
|
|
.ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hands every box this room is configured to give to the player. A room with several
|
|
/// boxes gives several; a box the player has already claimed under its current version is
|
|
/// skipped, and each box is claimed on its own so changing one does not silence the rest.
|
|
/// </summary>
|
|
public static async Task<bool> TryDeliverAsync(RecDBContext ctx, long playerId, RoomData room)
|
|
{
|
|
// The room's own switch stays the master: a room that is not handing anything out
|
|
// hands out nothing, whichever boxes its list happens to hold.
|
|
if (!room.JoinGiftEnabled)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var configured = await ConfiguredGiftsAsync(ctx, room.RoomId);
|
|
|
|
// A room with no list still has the one box its Rooms row describes.
|
|
var boxes = configured.Count > 0
|
|
? configured
|
|
: [Snapshot(room)];
|
|
|
|
var delivered = false;
|
|
foreach (var box in boxes)
|
|
{
|
|
if (!box.Enabled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!HasConfiguredReward(box))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var claimKey = ClaimKey(room.RoomId, box);
|
|
if (box.OncePerPlayer && await HasClaimedAsync(ctx, playerId, claimKey))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var giftBox = new Gift
|
|
{
|
|
GiftDropId = NewGiftDropId(room.RoomId, box.Version, box.RoomJoinGiftId),
|
|
ItemSetId = 0,
|
|
ItemSetFriendlyName = room.Name,
|
|
FriendlyName = string.IsNullOrWhiteSpace(box.Title) ? "Room Visit Gift" : box.Title,
|
|
Tooltip = box.Message ?? "",
|
|
IsQuery = false,
|
|
Unique = false,
|
|
SubscribersOnly = false,
|
|
ConsumableItemDesc = box.ConsumableItem ?? "",
|
|
AvatarItemDesc = box.AvatarItem ?? "",
|
|
AvatarItemType = box.AvatarItemType,
|
|
EquipmentPrefabName = box.EquipmentItem ?? "",
|
|
EquipmentModificationGuid = box.EquipmentModificationGuid ?? "",
|
|
CurrencyType = box.CurrencyType,
|
|
Currency = Math.Max(0, box.CurrencyAmount),
|
|
Xp = Math.Max(0, box.Xp),
|
|
Context = box.Context,
|
|
Rarity = box.Rarity,
|
|
// The box names its sender to the client, which shows it on the box; a room that
|
|
// names no sender leaves it at zero rather than inventing one.
|
|
FromPlayerId = Math.Max(0, box.FromPlayerId)
|
|
};
|
|
|
|
await StorefrontShared.PersistGiftBoxAsync(ctx, playerId, giftBox);
|
|
|
|
if (box.OncePerPlayer)
|
|
{
|
|
await MarkClaimedAsync(ctx, playerId, claimKey);
|
|
}
|
|
|
|
delivered = true;
|
|
}
|
|
|
|
return delivered;
|
|
}
|
|
|
|
public static async Task<bool> TryDeliverAsync(RecDBContext ctx, long playerId, long roomId)
|
|
{
|
|
var room = await ctx.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomId);
|
|
if (room == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return await TryDeliverAsync(ctx, playerId, room);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A gift drop id for one box. Two boxes handed out in the same millisecond must not share
|
|
/// an id - the player's boxes are a list keyed by it, so the second would overwrite the
|
|
/// first - so a listed box also mixes in which row it came from. A room's own single box
|
|
/// keeps the id shape it has always had.
|
|
/// </summary>
|
|
private static long NewGiftDropId(long roomId, int version, long giftId)
|
|
{
|
|
var timePart = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() % 1_000_000_000;
|
|
var roomPart = Math.Abs(roomId % 100_000);
|
|
var versionPart = Math.Abs(version % 1_000);
|
|
|
|
if (giftId <= 0)
|
|
return 9_000_000_000_000 + roomPart * 10_000_000 + versionPart * 10_000 + timePart % 10_000;
|
|
|
|
var giftPart = giftId % 1_000;
|
|
return 9_000_000_000_000 + roomPart * 1_000_000_000 + giftPart * 1_000_000 + versionPart * 1_000 + timePart % 1_000;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The key a player's once-per-player claim is remembered under. A room's own single box
|
|
/// keeps the key it has always used, so this change hands nobody a second copy of a box
|
|
/// they already claimed; a listed box adds its row and version.
|
|
/// </summary>
|
|
private static string ClaimKey(long roomId, RoomJoinGift box)
|
|
{
|
|
if (box.RoomJoinGiftId <= 0)
|
|
return $"{ClaimKeyPrefix}{roomId}.{box.Version}";
|
|
|
|
return $"{ClaimKeyPrefix}{roomId}.{box.RoomJoinGiftId}.{box.Version}";
|
|
}
|
|
|
|
private static async Task<bool> HasClaimedAsync(RecDBContext ctx, long playerId, string key)
|
|
{
|
|
var settings = await ctx.PlayerSettings.FirstOrDefaultAsync(x => x.AccountId == playerId);
|
|
if (settings == null) return false;
|
|
|
|
return settings.DeserializeSettings().Any(x => x.Key == key && x.Value == "1");
|
|
}
|
|
|
|
private static async Task MarkClaimedAsync(RecDBContext ctx, long playerId, string key)
|
|
{
|
|
var settings = await ctx.PlayerSettings.FirstOrDefaultAsync(x => x.AccountId == playerId);
|
|
if (settings == null)
|
|
{
|
|
settings = new PlayerSettings { AccountId = playerId };
|
|
await ctx.PlayerSettings.AddAsync(settings);
|
|
}
|
|
|
|
var list = settings.DeserializeSettings();
|
|
var existing = list.FirstOrDefault(x => x.Key == key);
|
|
if (existing == null)
|
|
list.Add(new Setting { Key = key, Value = "1" });
|
|
else
|
|
existing.Value = "1";
|
|
|
|
settings.Settings = list.ToJson();
|
|
}
|
|
}
|
|
}
|