mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 15:09:26 -07:00
291 lines
12 KiB
C#
291 lines
12 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.API.Storefront;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.Extensions;
|
|
using RadicadeServer.RecNet.Avatar;
|
|
using RadicadeServer.RecNet.Enums.Players.Avatar;
|
|
using RadicadeServer.RecNet.Enums.Store;
|
|
using RadicadeServer.RecNet.Leveling;
|
|
using RadicadeServer.RecNet.Progression;
|
|
using RadicadeServer.RecNet.Store;
|
|
|
|
namespace RadicadeServer.Utils
|
|
{
|
|
public static class LevelUpGiftService
|
|
{
|
|
public static List<LevelProgressionMap> ParseMap()
|
|
{
|
|
var map = FirstTimeUtils.LevelProgressionMap.FromJson<List<LevelProgressionMap>>() ?? new List<LevelProgressionMap>();
|
|
return map.OrderBy(x => x.Level).ToList();
|
|
}
|
|
|
|
public const int MaxLevel = 50;
|
|
|
|
public static int ComputeLevel(List<LevelProgressionMap> map, int xp)
|
|
{
|
|
int cumulative = 0;
|
|
int level = 1;
|
|
foreach (var entry in map.OrderBy(x => x.Level))
|
|
{
|
|
if (entry.Level <= 0) continue;
|
|
cumulative += entry.RequiredXp;
|
|
if (xp < cumulative) break;
|
|
level = entry.Level;
|
|
}
|
|
return level > MaxLevel ? MaxLevel : level;
|
|
}
|
|
|
|
public static int ThresholdXpForLevel(List<LevelProgressionMap> map, int level)
|
|
{
|
|
int threshold = 0;
|
|
foreach (var entry in map.OrderBy(x => x.Level))
|
|
{
|
|
if (entry.Level <= 0 || entry.Level > level) continue;
|
|
threshold += entry.RequiredXp;
|
|
}
|
|
return threshold;
|
|
}
|
|
|
|
public static int RequiredXpForLevel(List<LevelProgressionMap> map, int level) =>
|
|
ThresholdXpForLevel(map, level) - ThresholdXpForLevel(map, level - 1);
|
|
|
|
public static int DisplayLevel(int level) => level > MaxLevel ? MaxLevel : level;
|
|
|
|
public static int XpIntoCurrentLevel(List<LevelProgressionMap> map, int level, int totalXp)
|
|
{
|
|
if (level >= MaxLevel) return RequiredXpForLevel(map, MaxLevel);
|
|
|
|
int reached = ComputeLevel(map, totalXp);
|
|
int from = level < reached ? level : reached;
|
|
|
|
int into = totalXp - ThresholdXpForLevel(map, from);
|
|
return into < 0 ? 0 : into;
|
|
}
|
|
|
|
public static int XpIntoCurrentLevel(ProgressionData data) =>
|
|
XpIntoCurrentLevel(ParseMap(), data.Level, data.XP);
|
|
|
|
public const bool LevelUpGiftDropsAreSeeded = false;
|
|
|
|
|
|
private static List<AvatarItem>? _avatarItems;
|
|
private static List<ConsumableItem>? _consumables;
|
|
|
|
internal static List<AvatarItem> ClothingCatalogue(string? json = null)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(json)) return ParseClothingCatalogue(json);
|
|
return _avatarItems ??= ParseClothingCatalogue(DBConfig.DefaultAvatarCatalog);
|
|
}
|
|
|
|
private static List<AvatarItem> ParseClothingCatalogue(string json) =>
|
|
(json.FromJson<List<AvatarItem>>() ?? new List<AvatarItem>())
|
|
.Where(x => x != null && !string.IsNullOrWhiteSpace(x.AvatarItemDesc) && !x.IsBaseAvatarItem)
|
|
.OrderBy(x => x.AvatarItemId)
|
|
.ThenBy(x => x.AvatarItemDesc, StringComparer.Ordinal)
|
|
.ToList();
|
|
|
|
private static List<ConsumableItem> ConsumableCatalogue(string? json = null)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(json)) return ParseConsumableCatalogue(json);
|
|
return _consumables ??= ParseConsumableCatalogue(DBConfig.DefaultConsumableItems);
|
|
}
|
|
|
|
private static List<ConsumableItem> ParseConsumableCatalogue(string json) =>
|
|
(json.FromJson<List<ConsumableItem>>() ?? new List<ConsumableItem>())
|
|
.Where(x => x != null && !string.IsNullOrWhiteSpace(x.ConsumableItemDesc))
|
|
.OrderBy(x => x.ConsumableItemDesc, StringComparer.Ordinal)
|
|
.ToList();
|
|
|
|
public static Gift? BuildItemReward(
|
|
int level,
|
|
string? avatarCatalogJson = null,
|
|
string? consumableCatalogJson = null,
|
|
ISet<string>? owned = null,
|
|
Random? random = null,
|
|
LevelUpRewardPool.Pool? pool = null)
|
|
{
|
|
if (!LevelRewardTable.IsItemLevel(level)) return null;
|
|
|
|
var ownedKeys = owned ?? new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
var rng = random ?? Random.Shared;
|
|
|
|
return LevelRewardTable.KindForLevel(level) == LevelRewardTable.RewardKind.Consumable
|
|
? BuildConsumableReward(level, consumableCatalogJson, ownedKeys, rng, pool)
|
|
: BuildClothingReward(level, avatarCatalogJson, consumableCatalogJson, ownedKeys, rng, pool);
|
|
}
|
|
|
|
private static Gift? BuildClothingReward(
|
|
int level,
|
|
string? avatarCatalogJson,
|
|
string? consumableCatalogJson,
|
|
ISet<string> owned,
|
|
Random random,
|
|
LevelUpRewardPool.Pool? pool)
|
|
{
|
|
var rarity = LevelRewardTable.RarityForLevel(level);
|
|
var pick = LevelUpRewardPool.PickAvatarItem(ClothingCatalogue(avatarCatalogJson), rarity, owned, random, pool);
|
|
if (pick == null)
|
|
{
|
|
return BuildConsumableReward(level, consumableCatalogJson, owned, random, pool);
|
|
}
|
|
|
|
var label = LevelRewardTable.StarLabel(rarity);
|
|
return new Gift
|
|
{
|
|
FriendlyName = $"Level {level} Reward - {label} {pick.FriendlyName}",
|
|
Tooltip = $"Congrats on getting to Level {level}!",
|
|
AvatarItemDesc = pick.AvatarItemDesc,
|
|
AvatarItemType = AvatarItemType.Outfit,
|
|
Rarity = rarity,
|
|
Currency = 0,
|
|
Xp = 0
|
|
};
|
|
}
|
|
|
|
private static Gift? BuildConsumableReward(
|
|
int level,
|
|
string? consumableCatalogJson,
|
|
ISet<string> owned,
|
|
Random random,
|
|
LevelUpRewardPool.Pool? pool)
|
|
{
|
|
var pick = LevelUpRewardPool.PickConsumable(ConsumableCatalogue(consumableCatalogJson), owned, random, pool);
|
|
if (pick == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var token = ConsumableCatalog.ToClientDesc(pick.ConsumableItemDesc);
|
|
if (string.IsNullOrWhiteSpace(token)) return null;
|
|
|
|
return new Gift
|
|
{
|
|
FriendlyName = $"Level {level} Reward - {pick.FriendlyName}",
|
|
Tooltip = $"Congrats on getting to Level {level}!",
|
|
ConsumableItemDesc = token,
|
|
Rarity = pick.Rarity,
|
|
Currency = 0,
|
|
Xp = 0
|
|
};
|
|
}
|
|
|
|
public static Gift? BuildConsumableGift(
|
|
string? consumableCatalogJson, ISet<string> owned, Random random, LevelUpRewardPool.Pool? pool = null)
|
|
{
|
|
var pick = LevelUpRewardPool.PickConsumable(ConsumableCatalogue(consumableCatalogJson), owned, random, pool);
|
|
if (pick == null) return null;
|
|
|
|
var token = ConsumableCatalog.ToClientDesc(pick.ConsumableItemDesc);
|
|
if (string.IsNullOrWhiteSpace(token)) return null;
|
|
|
|
return new Gift
|
|
{
|
|
FriendlyName = pick.FriendlyName,
|
|
Tooltip = pick.FriendlyName,
|
|
ConsumableItemDesc = token,
|
|
Rarity = pick.Rarity,
|
|
Currency = 0,
|
|
Xp = 0
|
|
};
|
|
}
|
|
|
|
public static async Task<HashSet<string>> CollectOwnedKeysAsync(
|
|
RecDBContext db, long playerId, GiftContext? pendingContext = GiftContext.LevelUp)
|
|
{
|
|
var owned = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
var unlocks = await db.Unlocks.FirstOrDefaultAsync(x => x.AccountId == playerId);
|
|
if (unlocks != null)
|
|
{
|
|
foreach (var item in unlocks.GetAvatarItems())
|
|
if (!string.IsNullOrWhiteSpace(item?.AvatarItemDesc))
|
|
owned.Add(LevelUpRewardPool.OwnershipKey(item.AvatarItemDesc));
|
|
|
|
foreach (var item in unlocks.GetConsumables())
|
|
if (!string.IsNullOrWhiteSpace(item?.ConsumableItemDesc))
|
|
owned.Add(LevelUpRewardPool.OwnershipKey(item.ConsumableItemDesc));
|
|
}
|
|
|
|
var gifts = await db.Gifts.FirstOrDefaultAsync(x => x.AccountId == playerId);
|
|
foreach (var pending in StorefrontShared.ParseGiftList(gifts?.Data))
|
|
{
|
|
if (pending == null) continue;
|
|
if (pendingContext.HasValue && pending.Context != pendingContext.Value) continue;
|
|
Remember(owned, pending);
|
|
}
|
|
|
|
return owned;
|
|
}
|
|
|
|
private static void Remember(HashSet<string> owned, Gift gift)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(gift.AvatarItemDesc))
|
|
owned.Add(LevelUpRewardPool.OwnershipKey(gift.AvatarItemDesc));
|
|
if (!string.IsNullOrWhiteSpace(gift.ConsumableItemDesc))
|
|
owned.Add(LevelUpRewardPool.OwnershipKey(gift.ConsumableItemDesc));
|
|
}
|
|
|
|
public static async Task<List<Gift>> DeliverLevelUpGiftsAsync(RecDBContext db, long playerId, bool recomputeFromXp, int? fromLevel = null, bool notify = true)
|
|
{
|
|
var progression = await db.Progression.FirstOrDefaultAsync(x => x.PlayerId == playerId);
|
|
if (progression == null) return new List<Gift>();
|
|
|
|
var map = ParseMap();
|
|
int oldLevel = fromLevel ?? progression.Level;
|
|
int target = progression.Level;
|
|
if (recomputeFromXp)
|
|
{
|
|
int computed = ComputeLevel(map, progression.XP);
|
|
if (computed > target) target = computed;
|
|
}
|
|
if (target <= oldLevel) return new List<Gift>();
|
|
|
|
var json = progression.GetProgressionJson() ?? new ProgressionJson();
|
|
var delivered = json.DeliveredLevelUpGiftLevels ?? new List<int>();
|
|
|
|
var owned = await CollectOwnedKeysAsync(db, playerId);
|
|
var random = Random.Shared;
|
|
|
|
var toPersist = new List<Gift>();
|
|
foreach (var entry in map.OrderBy(e => e.Level))
|
|
{
|
|
if (entry.Level <= oldLevel) continue;
|
|
if (entry.Level > target) break;
|
|
if (delivered.Contains(entry.Level)) continue;
|
|
|
|
var gift = LevelRewardTable.IsItemLevel(entry.Level)
|
|
? BuildItemReward(entry.Level, owned: owned, random: random)
|
|
: null;
|
|
if (gift == null) continue;
|
|
|
|
Remember(owned, gift);
|
|
|
|
gift.GiftDropId = StorefrontShared.NewGiftDropId();
|
|
gift.Context = GiftContext.LevelUp;
|
|
gift.FromPlayerId = 0;
|
|
|
|
toPersist.Add(gift);
|
|
delivered.Add(entry.Level);
|
|
}
|
|
|
|
if (toPersist.Count == 0) return new List<Gift>();
|
|
|
|
json.DeliveredLevelUpGiftLevels = delivered;
|
|
progression.Progress = json.ToJson();
|
|
progression.Level = target;
|
|
|
|
foreach (var gift in toPersist)
|
|
{
|
|
await StorefrontShared.PersistGiftBoxAsync(db, playerId, gift, notify: false);
|
|
}
|
|
await db.SaveChangesAsync();
|
|
if (notify)
|
|
{
|
|
foreach (var gift in toPersist)
|
|
await StorefrontShared.SendGiftPackageNotificationAsync(playerId, gift);
|
|
}
|
|
return toPersist;
|
|
}
|
|
}
|
|
}
|