mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
644 lines
28 KiB
C#
644 lines
28 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Text.RegularExpressions;
|
|
using RadicadeServer.API.Storefront;
|
|
using RadicadeServer.Extensions;
|
|
using RadicadeServer.RecNet.Avatar;
|
|
using RadicadeServer.RecNet.Enums.Players.Avatar;
|
|
using RadicadeServer.RecNet.Enums.Store;
|
|
|
|
namespace RadicadeServer.Utils
|
|
{
|
|
public static class RroRewardPools
|
|
{
|
|
public sealed record ItemSet(string Family, string Colour);
|
|
|
|
public const string Unranked = "";
|
|
|
|
public const int SelectionSize = 3;
|
|
|
|
public static readonly string[] QuestRanks = ["C", "B", "A", "S"];
|
|
|
|
|
|
private static readonly Dictionary<GiftContext, ItemSet[]> Pools = new()
|
|
{
|
|
[GiftContext.Quest_Goblin_C] =
|
|
[
|
|
new("Archer", "Tan"), new("Archer", "Yellow"),
|
|
new("Knight", "Blue"), new("Knight", "Grey"),
|
|
],
|
|
[GiftContext.Quest_Goblin_B] =
|
|
[
|
|
new("Archer", "Red"),
|
|
new("Knight", "Cherry"),
|
|
],
|
|
[GiftContext.Quest_Goblin_A] =
|
|
[
|
|
new("Archer", "Blue"), new("Archer", "Green"),
|
|
new("Knight", "White"), new("Knight", "Yellow"),
|
|
],
|
|
[GiftContext.Quest_Goblin_S] =
|
|
[
|
|
new("Archer", "Black"),
|
|
new("Knight", "Black"),
|
|
],
|
|
|
|
[GiftContext.Quest_SciFi_C] =
|
|
[
|
|
new("Space Marine", "Blue"), new("Space Marine", "Orange"),
|
|
new("Smuggler", "Brown"), new("Smuggler", "Green"),
|
|
],
|
|
[GiftContext.Quest_SciFi_B] =
|
|
[
|
|
new("Space Marine", "Pink"), new("Space Marine", "Red"),
|
|
new("Smuggler", "Cherry"), new("Smuggler", "Yellow"),
|
|
],
|
|
[GiftContext.Quest_SciFi_A] =
|
|
[
|
|
new("Space Marine", "Purple"), new("Space Marine", "White"),
|
|
new("Smuggler", "Grey"), new("Smuggler", "Orange"),
|
|
],
|
|
[GiftContext.Quest_SciFi_S] =
|
|
[
|
|
new("Space Marine", "Black"),
|
|
new("Smuggler", "Black"),
|
|
],
|
|
|
|
[GiftContext.Quest_Cauldron_C] =
|
|
[
|
|
new("Witch Hunter", "Purple"), new("Witch Hunter", "Green"),
|
|
new("Wizard", "Blue"), new("Wizard", "Green"),
|
|
],
|
|
[GiftContext.Quest_Cauldron_B] =
|
|
[
|
|
new("Witch Hunter", "Red"),
|
|
new("Wizard", "Red"),
|
|
],
|
|
[GiftContext.Quest_Cauldron_A] =
|
|
[
|
|
new("Witch Hunter", "White"),
|
|
new("Wizard", "White"),
|
|
],
|
|
[GiftContext.Quest_Cauldron_S] =
|
|
[
|
|
new("Witch Hunter", "Black"),
|
|
new("Wizard", "Black"), new("Wizard", "Yellow"),
|
|
],
|
|
|
|
[GiftContext.Quest_Dracula1_C] = [new("Vampire Hunter", "Blue")],
|
|
[GiftContext.Quest_Dracula1_B] = [new("Vampire Hunter", "Red")],
|
|
[GiftContext.Quest_Dracula1_A] = [new("Vampire Hunter", "Leather")],
|
|
[GiftContext.Quest_Dracula1_S] = [new("Vampire Hunter", "Midnight")],
|
|
[GiftContext.Quest_Dracula1_SS] = [new("Dracula", "")],
|
|
|
|
[GiftContext.StuntRunner_TheMainEvent_C] =
|
|
[
|
|
new("Runner", "Green"), new("Runner", "Yellow"),
|
|
],
|
|
[GiftContext.StuntRunner_TheMainEvent_B] =
|
|
[
|
|
new("Runner", "Purple"), new("Runner", "Black"),
|
|
],
|
|
[GiftContext.StuntRunner_TheMainEvent_A] =
|
|
[
|
|
new("Stunt", "Fuchsia"), new("Stunt", "Blue"),
|
|
],
|
|
[GiftContext.StuntRunner_TheMainEvent_S] =
|
|
[
|
|
new("Stunt", "Green"), new("Stunt", "Red"),
|
|
],
|
|
[GiftContext.StuntRunner_TheMainEvent_SS] = [new("Feathered Hair", "")],
|
|
|
|
[GiftContext.Quest_Pirate1_C] =
|
|
[
|
|
new("Scallywag Bracers", "Brown"), new("Scallywag Belt", "Brown"),
|
|
new("Scallywag Bracers", "Red"), new("Scallywag Belt", "Red"),
|
|
new("Pirate Captain Cuffs", "Green"), new("Pirate Captain Belt", "Green"),
|
|
],
|
|
[GiftContext.Quest_Pirate1_B] =
|
|
[
|
|
new("Scallywag Belt", "Brown"), new("Scallywag Vest", "Brown"),
|
|
new("Scallywag Belt", "Red"), new("Scallywag Vest", "Red"),
|
|
new("Pirate Captain Belt", "Red"), new("Pirate Captain Jacket", "Red"),
|
|
new("Pirate Captain Belt", "Blue"), new("Pirate Captain Jacket", "Blue"),
|
|
],
|
|
[GiftContext.Quest_Pirate1_A] =
|
|
[
|
|
new("Scallywag Vest", "Brown"), new("Scallywag Hat", "Brown"),
|
|
new("Scallywag Vest", "Red"), new("Scallywag Hat", "Red"),
|
|
new("Pirate Captain Jacket", "Red"), new("Pirate Captain Hat", "Red"),
|
|
new("Pirate Captain Jacket", "Blue"), new("Pirate Captain Hat", "Blue"),
|
|
],
|
|
[GiftContext.Quest_Pirate1_S] =
|
|
[
|
|
new("Pirate Captain Cuffs", "Gold"), new("Pirate Captain Belt", "Gold"),
|
|
new("Pirate Captain Jacket", "Gold"), new("Pirate Captain Hat", "Gold"),
|
|
new("Pirate Eyepatch", "Black"), new("Pirate Eyepatch", "Skull"),
|
|
],
|
|
|
|
[GiftContext.Paintball_ClearCut] = [new("Lumberjack", "")],
|
|
[GiftContext.Paintball_Homestead] = [new("Farmer Hat", ""), new("Farmer Overalls", "")],
|
|
[GiftContext.Paintball_Quarry] = [new("Construction", "")],
|
|
[GiftContext.Paintball_River] = [new("Angler", "")],
|
|
[GiftContext.Paintball_Dam] = [new("Electrician", "")],
|
|
[GiftContext.Paintball_DriveIn] = [new("Mechanic", "")],
|
|
[GiftContext.Discgolf_Propulsion] = [new("Scientist", "")],
|
|
[GiftContext.Discgolf_Lake] =
|
|
[
|
|
new("Golfer Hat", "Tan"), new("Golfer Hat", "Blue"), new("Golfer Hat", "Grey"),
|
|
new("Golfer Shirt", "Tan"), new("Golfer Shirt", "Blue"), new("Golfer Shirt", "Grey"),
|
|
new("Golfer Gloves", "Tan"), new("Golfer Gloves", "Blue"), new("Golfer Gloves", "Grey"),
|
|
],
|
|
[GiftContext.Charades] = [new("Beret", ""), new("Artist Beret", "")],
|
|
[GiftContext.Soccer] = [new("Soccer", "")],
|
|
[GiftContext.Paddleball] = [new("Tennis", "")],
|
|
[GiftContext.Dodgeball] = [new("Dodgeball Shirt", "")],
|
|
[GiftContext.Lasertag] = [],
|
|
[GiftContext.Bowling] = [new("Dude Sweater", "")],
|
|
[GiftContext.RecRoyale] = [],
|
|
};
|
|
|
|
public static readonly IReadOnlyList<string> Unresolved =
|
|
[
|
|
"Crimson Cauldron S: the Brown Witch Hunter set - the catalogue's Witch Hunter pieces come in black, blue, green, leather, purple, red and white, and none in brown.",
|
|
"Isle of Lost Skulls X: no special-tier reward is named.",
|
|
"Isle of Lost Skulls B and A: the verified tables name a Pirate Captain Belt (Blue) for B and a Pirate Captain Hat (Blue) for A. The catalogue carries the Captain's blue jacket but no blue belt or hat, so those two pieces are not paid; the rest of each rank's table is.",
|
|
"Crimson Cauldron S: the Brown Witch Hunter is a real S-rank colour, but no item in the shipped catalogue carries it - its Witch Hunter pieces are black, blue, green, leather, purple, red and white. The descriptor or the in-game name is needed to name it here.",
|
|
"Rec Royale: its Season 1 rewards came from the Frontier Pass, a progression system of its own, so a completed match draws nothing. The Pass itself is not implemented.",
|
|
"Crescendo X: no special-tier reward is named.",
|
|
"Stunt Runner D and X: no reward is named for either tier, and the catalogue has no item for one.",
|
|
"Rec Royale: no concrete reward item is named.",
|
|
"Laser Tag: not a random-drop activity at all - a completed game pays tickets, and the shop they are spent in is not implemented yet.",
|
|
"Dodgeball: the definitions call the set the Dodger Vest in cyan, purple and orange; the catalogue carries the same three pieces as Dodgeball Shirt in orange, purple and teal.",
|
|
];
|
|
|
|
public static readonly IReadOnlyList<GiftContext> Deprecated =
|
|
[
|
|
GiftContext.DEPRECATED_Paintball_ClearCut,
|
|
GiftContext.DEPRECATED_Paintball_Homestead,
|
|
GiftContext.DEPRECATED_Paintball_Quarry,
|
|
GiftContext.DEPRECATED_Paintball_River,
|
|
GiftContext.DEPRECATED_Paintball_Dam,
|
|
GiftContext.DEPRECATED_Paintball_DriveIn,
|
|
GiftContext.DEPRECATED_Discgolf_Propulsion,
|
|
GiftContext.DEPRECATED_Discgolf_Lake,
|
|
GiftContext.DEPRECATED_Charades,
|
|
GiftContext.DEPRECATED_Soccer,
|
|
GiftContext.DEPRECATED_Paddleball,
|
|
GiftContext.DEPRECATED_Dodgeball,
|
|
GiftContext.DEPRECATED_Lasertag,
|
|
GiftContext.DEPRECATED_Bowling,
|
|
];
|
|
|
|
public static bool IsDeprecated(GiftContext context) => Deprecated.Contains(context);
|
|
|
|
public static IReadOnlyCollection<GiftContext> RewardableContexts => Pools.Keys;
|
|
|
|
public static IReadOnlyList<ItemSet> SetsFor(GiftContext context) =>
|
|
Pools.TryGetValue(context, out var sets) ? sets : [];
|
|
|
|
|
|
private static readonly Dictionary<string, Dictionary<string, GiftContext>> ByRoom =
|
|
new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["GoldenTrophy"] = Ranks(
|
|
("C", GiftContext.Quest_Goblin_C), ("B", GiftContext.Quest_Goblin_B),
|
|
("A", GiftContext.Quest_Goblin_A), ("S", GiftContext.Quest_Goblin_S)),
|
|
["TheRiseofJumbotron"] = Ranks(
|
|
("C", GiftContext.Quest_SciFi_C), ("B", GiftContext.Quest_SciFi_B),
|
|
("A", GiftContext.Quest_SciFi_A), ("S", GiftContext.Quest_SciFi_S)),
|
|
["CrimsonCauldron"] = Ranks(
|
|
("C", GiftContext.Quest_Cauldron_C), ("B", GiftContext.Quest_Cauldron_B),
|
|
("A", GiftContext.Quest_Cauldron_A), ("S", GiftContext.Quest_Cauldron_S)),
|
|
["Crescendo"] = Ranks(
|
|
("C", GiftContext.Quest_Dracula1_C), ("B", GiftContext.Quest_Dracula1_B),
|
|
("A", GiftContext.Quest_Dracula1_A), ("S", GiftContext.Quest_Dracula1_S)),
|
|
["IsleOfLostSkulls"] = Ranks(
|
|
("C", GiftContext.Quest_Pirate1_C), ("B", GiftContext.Quest_Pirate1_B),
|
|
("A", GiftContext.Quest_Pirate1_A), ("S", GiftContext.Quest_Pirate1_S)),
|
|
["StuntRunner"] = Ranks(
|
|
("C", GiftContext.StuntRunner_TheMainEvent_C),
|
|
("B", GiftContext.StuntRunner_TheMainEvent_B),
|
|
("A", GiftContext.StuntRunner_TheMainEvent_A),
|
|
("S", GiftContext.StuntRunner_TheMainEvent_S),
|
|
("SS", GiftContext.StuntRunner_TheMainEvent_SS),
|
|
("S+", GiftContext.StuntRunner_TheMainEvent_SS),
|
|
("SPlus", GiftContext.StuntRunner_TheMainEvent_SS)),
|
|
|
|
["PaintballClearcut"] = UnrankedContext(GiftContext.Paintball_ClearCut),
|
|
["Clearcut"] = UnrankedContext(GiftContext.Paintball_ClearCut),
|
|
["PaintballHomestead"] = UnrankedContext(GiftContext.Paintball_Homestead),
|
|
["Homestead"] = UnrankedContext(GiftContext.Paintball_Homestead),
|
|
["PaintballQuarry"] = UnrankedContext(GiftContext.Paintball_Quarry),
|
|
["Quarry"] = UnrankedContext(GiftContext.Paintball_Quarry),
|
|
["PaintballRiver"] = UnrankedContext(GiftContext.Paintball_River),
|
|
["River"] = UnrankedContext(GiftContext.Paintball_River),
|
|
["PaintballSpillway"] = UnrankedContext(GiftContext.Paintball_Dam),
|
|
["Spillway"] = UnrankedContext(GiftContext.Paintball_Dam),
|
|
["PaintballDriveIn"] = UnrankedContext(GiftContext.Paintball_DriveIn),
|
|
["DriveIn"] = UnrankedContext(GiftContext.Paintball_DriveIn),
|
|
|
|
["DiscGolfPropulsion"] = UnrankedContext(GiftContext.Discgolf_Propulsion),
|
|
["PropulsionTestRange"] = UnrankedContext(GiftContext.Discgolf_Propulsion),
|
|
["DiscGolfLake"] = UnrankedContext(GiftContext.Discgolf_Lake),
|
|
|
|
["3DCharades"] = UnrankedContext(GiftContext.Charades),
|
|
["Legacy3DCharades"] = UnrankedContext(GiftContext.Charades),
|
|
["Soccer"] = UnrankedContext(GiftContext.Soccer),
|
|
["Paddleball"] = UnrankedContext(GiftContext.Paddleball),
|
|
["Dodgeball"] = UnrankedContext(GiftContext.Dodgeball),
|
|
["DodgeballVR"] = UnrankedContext(GiftContext.Dodgeball),
|
|
["LaserTag"] = UnrankedContext(GiftContext.Lasertag),
|
|
["Bowling"] = UnrankedContext(GiftContext.Bowling),
|
|
["BowlingAlley"] = UnrankedContext(GiftContext.Bowling),
|
|
["RecRoyaleSolos"] = UnrankedContext(GiftContext.RecRoyale),
|
|
["RecRoyaleSquads"] = UnrankedContext(GiftContext.RecRoyale),
|
|
};
|
|
|
|
private static Dictionary<string, GiftContext> Ranks(params (string Rank, GiftContext Context)[] ranks) =>
|
|
ranks.ToDictionary(r => r.Rank, r => r.Context, StringComparer.OrdinalIgnoreCase);
|
|
|
|
private static readonly Dictionary<string, string> RoomAliases =
|
|
new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["TheMainEvent"] = "StuntRunner",
|
|
["StuntRunnerTheMainEvent"] = "StuntRunner",
|
|
["StuntRunnerMainEvent"] = "StuntRunner",
|
|
};
|
|
|
|
private static string? ResolveRoomName(string? roomName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(roomName)) return null;
|
|
var trimmed = roomName.Trim();
|
|
return RoomAliases.TryGetValue(trimmed, out var target) ? target : trimmed;
|
|
}
|
|
|
|
public static string? RoomForContext(GiftContext context)
|
|
{
|
|
foreach (var (room, ranks) in ByRoom)
|
|
{
|
|
if (ranks.Values.Contains(context)) return room;
|
|
}
|
|
|
|
return ConsumableRooms.TryGetValue(context, out var consumableRoom) ? consumableRoom : null;
|
|
}
|
|
|
|
private static readonly Dictionary<GiftContext, string> ConsumableRooms = new()
|
|
{
|
|
[GiftContext.Quest_Goblin_Consumable] = "GoldenTrophy",
|
|
[GiftContext.Quest_Cauldron_Consumable] = "CrimsonCauldron",
|
|
[GiftContext.Quest_Pirate1_Consumable] = "IsleOfLostSkulls",
|
|
[GiftContext.Quest_Dracula1_Consumable] = "Crescendo",
|
|
[GiftContext.Quest_Scifi_Consumable] = "TheRiseofJumbotron",
|
|
[GiftContext.StuntRunner_TheMainEvent_Consumable] = "StuntRunner",
|
|
};
|
|
|
|
private static Dictionary<string, GiftContext> UnrankedContext(GiftContext context) =>
|
|
new(StringComparer.OrdinalIgnoreCase) { [Unranked] = context };
|
|
|
|
private static readonly Dictionary<string, GiftContext> NoContexts = new();
|
|
|
|
public static IReadOnlyDictionary<string, GiftContext> ContextsForRoom(string? roomName)
|
|
{
|
|
var resolved = ResolveRoomName(roomName);
|
|
if (resolved == null) return NoContexts;
|
|
return ByRoom.TryGetValue(resolved, out var ranks) ? ranks : NoContexts;
|
|
}
|
|
|
|
public static bool IsRewardActivity(string? roomName) => ContextsForRoom(roomName).Count > 0;
|
|
|
|
public static bool IsQuestContext(GiftContext context)
|
|
{
|
|
var room = RoomForContext(context);
|
|
if (room == null) return false;
|
|
|
|
var contexts = ContextsForRoom(room);
|
|
return QuestRanks.Any(rank => contexts.ContainsKey(rank));
|
|
}
|
|
|
|
public static bool TryContext(string? roomName, string? rank, out GiftContext context)
|
|
{
|
|
context = GiftContext.None;
|
|
var contexts = ContextsForRoom(roomName);
|
|
if (contexts.Count == 0) return false;
|
|
return contexts.TryGetValue(string.IsNullOrWhiteSpace(rank) ? Unranked : rank.Trim(), out context);
|
|
}
|
|
|
|
public static List<GiftContext> LowerRanksOf(GiftContext context)
|
|
{
|
|
var lower = new List<GiftContext>();
|
|
|
|
foreach (var ranks in ByRoom.Values)
|
|
{
|
|
if (!ranks.ContainsValue(context)) continue;
|
|
|
|
var index = Array.FindIndex(QuestRanks, rank => ranks.TryGetValue(rank, out var paid) && paid == context);
|
|
if (index < 0) break;
|
|
|
|
for (int i = index - 1; i >= 0; i--)
|
|
if (ranks.TryGetValue(QuestRanks[i], out var weaker)) lower.Add(weaker);
|
|
break;
|
|
}
|
|
|
|
return lower;
|
|
}
|
|
|
|
public static bool PaysNoItemReward(string? roomName) =>
|
|
string.Equals(roomName?.Trim(), "RecRoyaleSolos", StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(roomName?.Trim(), "RecRoyaleSquads", StringComparison.OrdinalIgnoreCase);
|
|
|
|
public static bool OffersSelection(string? roomName) =>
|
|
ContextsForRoom(roomName).ContainsKey(Unranked);
|
|
|
|
public static bool TryFloorContext(string? roomName, out GiftContext context)
|
|
{
|
|
context = GiftContext.None;
|
|
var contexts = ContextsForRoom(roomName);
|
|
if (contexts.Count == 0) return false;
|
|
|
|
if (contexts.TryGetValue(Unranked, out var only))
|
|
{
|
|
context = only;
|
|
return true;
|
|
}
|
|
|
|
foreach (var rank in QuestRanks)
|
|
{
|
|
if (contexts.TryGetValue(rank, out var ranked))
|
|
{
|
|
context = ranked;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
public static List<AvatarItem> Resolve(ItemSet set, string? catalogueJson = null)
|
|
{
|
|
var colour = Normalise(set.Colour);
|
|
|
|
var pattern = string.IsNullOrEmpty(colour)
|
|
? @"^\s*" + Regex.Escape(set.Family) + @"\b"
|
|
: @"^\s*" + Regex.Escape(set.Family) + @"\b.*\(\s*" + Regex.Escape(colour) + @"\s*\)\s*$";
|
|
|
|
var rx = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
|
|
|
return LevelUpGiftService.ClothingCatalogue(catalogueJson)
|
|
.Where(item => rx.IsMatch(Normalise(item.FriendlyName)))
|
|
.OrderBy(item => item.AvatarItemId)
|
|
.ThenBy(item => item.AvatarItemDesc, StringComparer.Ordinal)
|
|
.ToList();
|
|
}
|
|
|
|
private static string Normalise(string? value) =>
|
|
(value ?? "").Replace("gray", "grey", StringComparison.OrdinalIgnoreCase);
|
|
|
|
private static readonly ConcurrentDictionary<GiftContext, List<AvatarItem>> PoolCache = new();
|
|
|
|
public static List<AvatarItem> Resolve(GiftContext context, string? catalogueJson = null) =>
|
|
string.IsNullOrWhiteSpace(catalogueJson)
|
|
? PoolCache.GetOrAdd(context, ctx => ResolveCore(ctx, null))
|
|
: ResolveCore(context, catalogueJson);
|
|
|
|
private static List<AvatarItem> ResolveCore(GiftContext context, string? catalogueJson) =>
|
|
SetsFor(context)
|
|
.SelectMany(set => Resolve(set, catalogueJson))
|
|
.GroupBy(item => item.AvatarItemDesc, StringComparer.Ordinal)
|
|
.Select(group => group.First())
|
|
.OrderBy(item => item.AvatarItemId)
|
|
.ThenBy(item => item.AvatarItemDesc, StringComparer.Ordinal)
|
|
.ToList();
|
|
|
|
public static AvatarItem? Pick(
|
|
GiftContext context,
|
|
ISet<string>? owned = null,
|
|
Random? random = null,
|
|
string? catalogueJson = null)
|
|
{
|
|
var pool = Resolve(context, catalogueJson);
|
|
if (pool.Count == 0) return null;
|
|
|
|
var ownedKeys = owned ?? new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
var unowned = pool
|
|
.Where(item => !ownedKeys.Contains(LevelUpRewardPool.OwnershipKey(item.AvatarItemDesc)))
|
|
.ToList();
|
|
|
|
return unowned.Count == 0 ? null : unowned[(random ?? Random.Shared).Next(unowned.Count)];
|
|
}
|
|
|
|
public static Gift? BuildGift(
|
|
GiftContext context,
|
|
ISet<string>? owned = null,
|
|
Random? random = null,
|
|
string? catalogueJson = null)
|
|
{
|
|
var pick = Pick(context, owned, random, catalogueJson);
|
|
if (pick == null) return null;
|
|
|
|
return new Gift
|
|
{
|
|
FriendlyName = pick.FriendlyName,
|
|
Tooltip = pick.FriendlyName,
|
|
AvatarItemDesc = pick.AvatarItemDesc,
|
|
AvatarItemType = AvatarItemType.Outfit,
|
|
Rarity = (Rarity)pick.Rarity,
|
|
CurrencyType = CurrencyType.Invalid,
|
|
Currency = 0,
|
|
Xp = 0,
|
|
Context = context
|
|
};
|
|
}
|
|
|
|
public static List<Gift> BuildDrops(
|
|
GiftContext context,
|
|
bool selection,
|
|
ISet<string>? owned = null,
|
|
Random? random = null,
|
|
string? catalogueJson = null)
|
|
{
|
|
var drops = new List<Gift>();
|
|
var claimed = owned == null
|
|
? new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
: new HashSet<string>(owned, StringComparer.OrdinalIgnoreCase);
|
|
var rng = random ?? Random.Shared;
|
|
|
|
for (int i = 0; i < (selection ? SelectionSize : 1); i++)
|
|
{
|
|
var gift = BuildGift(context, claimed, rng, catalogueJson);
|
|
if (gift == null) break;
|
|
|
|
drops.Add(gift);
|
|
claimed.Add(LevelUpRewardPool.OwnershipKey(gift.AvatarItemDesc));
|
|
}
|
|
|
|
return drops;
|
|
}
|
|
|
|
|
|
public static bool PaysTickets(string? roomName) =>
|
|
string.Equals(roomName?.Trim(), "LaserTag", StringComparison.OrdinalIgnoreCase);
|
|
|
|
public const int LaserTagTicketsPerGame = 25;
|
|
|
|
public const int LaserTagFirstGamesBonus = 100;
|
|
|
|
public const int LaserTagBonusGamesPerDay = 5;
|
|
|
|
public static double DropChanceFor(GiftContext context) =>
|
|
context == GiftContext.Bowling ? 1.0 / 250.0 : 1.0;
|
|
|
|
public const int CompletionTokenAmount = 50;
|
|
|
|
public static Gift TokenGift(GiftContext context, int amount, string? message = null) => new()
|
|
{
|
|
GiftDropId = StorefrontShared.NewGiftDropId(),
|
|
FriendlyName = TokenTitle(amount),
|
|
Tooltip = string.IsNullOrWhiteSpace(message) ? "Reward for playing!" : message,
|
|
CurrencyType = CurrencyType.RecCenterTokens,
|
|
Currency = amount,
|
|
Context = context,
|
|
Rarity = amount >= 200 ? Rarity.Rare : Rarity.Common
|
|
};
|
|
|
|
public static string TokenTitle(int amount) => $"{amount} Tokens";
|
|
|
|
public const double TokenBoxChance = 0.12;
|
|
|
|
public static List<Gift> BuildFallbackSelection(
|
|
ISet<string>? owned = null,
|
|
Random? random = null,
|
|
string? catalogueJson = null,
|
|
string? message = null)
|
|
{
|
|
var rng = random ?? Random.Shared;
|
|
var drops = BuildFallbackDrops(selection: true, owned: owned, random: random, catalogueJson: catalogueJson)
|
|
.Take(SelectionSize)
|
|
.ToList();
|
|
|
|
if (drops.Count == 0)
|
|
return [TokenGift(GiftContext.GameRewards_Tokens, CompletionTokenAmount, message)];
|
|
|
|
var tokenBox = TokenGift(GiftContext.GameRewards_Tokens, CompletionTokenAmount, message);
|
|
|
|
if (drops.Count < SelectionSize)
|
|
drops.Add(tokenBox);
|
|
else if (rng.NextDouble() < TokenBoxChance)
|
|
drops[^1] = tokenBox;
|
|
|
|
return drops;
|
|
}
|
|
|
|
public static List<Gift> BuildDailyChallengeSelection(
|
|
ISet<string>? owned = null,
|
|
Random? random = null,
|
|
string? catalogueJson = null)
|
|
{
|
|
var drops = BuildFallbackDrops(selection: true, owned: owned, random: random, catalogueJson: catalogueJson)
|
|
.Take(SelectionSize)
|
|
.ToList();
|
|
|
|
foreach (var drop in drops)
|
|
drop.Context = GiftContext.Daily_Challenge_Complete;
|
|
|
|
return drops;
|
|
}
|
|
|
|
|
|
public const GiftContext ReacquisitionContext = GiftContext.Reacquisition;
|
|
|
|
private static readonly int[] NonRareRarities = [10, 20];
|
|
|
|
private static List<AvatarItem>? _reacquisitionPool;
|
|
|
|
private static readonly Regex RewardFamilyPattern = new(
|
|
@"^\s*(" + string.Join("|",
|
|
Pools.Values.SelectMany(sets => sets)
|
|
.Select(set => Regex.Escape(set.Family))
|
|
.Distinct(StringComparer.OrdinalIgnoreCase)) + @")\b",
|
|
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
|
|
|
public static List<AvatarItem> ReacquisitionPool(string? catalogueJson = null)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(catalogueJson))
|
|
return FilterNonRare(LevelUpGiftService.ClothingCatalogue(catalogueJson));
|
|
return _reacquisitionPool ??= FilterNonRare(LevelUpGiftService.ClothingCatalogue(null));
|
|
}
|
|
|
|
private static List<AvatarItem> FilterNonRare(List<AvatarItem> catalogue) =>
|
|
catalogue
|
|
.Where(item => item != null
|
|
&& !string.IsNullOrWhiteSpace(item.AvatarItemDesc)
|
|
&& NonRareRarities.Contains(item.Rarity)
|
|
&& !LevelUpRewardPool.IsAlwaysDenied(item)
|
|
&& !IsRewardSetItem(item))
|
|
.OrderBy(item => item.AvatarItemId)
|
|
.ThenBy(item => item.AvatarItemDesc, StringComparer.Ordinal)
|
|
.ToList();
|
|
|
|
public static bool IsRewardSetItem(AvatarItem item) =>
|
|
item != null && RewardFamilyPattern.IsMatch(item.FriendlyName ?? string.Empty);
|
|
|
|
private static readonly HashSet<string> EmptyOwned = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
public static List<Gift> BuildFallbackDrops(
|
|
bool selection,
|
|
ISet<string>? owned = null,
|
|
Random? random = null,
|
|
string? catalogueJson = null,
|
|
string? consumableCatalogJson = null,
|
|
LevelUpRewardPool.Pool? consumablePool = null)
|
|
{
|
|
var rng = random ?? Random.Shared;
|
|
var has = owned == null
|
|
? new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
: new HashSet<string>(owned, StringComparer.OrdinalIgnoreCase);
|
|
|
|
if (!selection)
|
|
{
|
|
var consumable = BuildFallbackConsumable(has, rng, consumableCatalogJson, consumablePool);
|
|
return consumable == null ? [] : [consumable];
|
|
}
|
|
|
|
var pool = ReacquisitionPool(catalogueJson);
|
|
var unowned = pool
|
|
.Where(item => !has.Contains(LevelUpRewardPool.OwnershipKey(item.AvatarItemDesc)))
|
|
.ToList();
|
|
|
|
return (unowned.Count > 0 ? unowned : pool)
|
|
.OrderBy(_ => rng.Next())
|
|
.Take(SelectionSize)
|
|
.Select(item => new Gift
|
|
{
|
|
FriendlyName = item.FriendlyName,
|
|
Tooltip = item.FriendlyName,
|
|
AvatarItemDesc = item.AvatarItemDesc,
|
|
AvatarItemType = AvatarItemType.Outfit,
|
|
Rarity = (Rarity)item.Rarity,
|
|
CurrencyType = CurrencyType.Invalid,
|
|
Currency = 0,
|
|
Xp = 0,
|
|
Context = ReacquisitionContext
|
|
})
|
|
.ToList();
|
|
}
|
|
|
|
public static Gift? BuildFallbackConsumable(
|
|
ISet<string>? owned = null,
|
|
Random? random = null,
|
|
string? consumableCatalogJson = null,
|
|
LevelUpRewardPool.Pool? pool = null)
|
|
{
|
|
var rng = random ?? Random.Shared;
|
|
var has = owned ?? EmptyOwned;
|
|
|
|
var gift = LevelUpGiftService.BuildConsumableGift(consumableCatalogJson, has, rng, pool)
|
|
?? LevelUpGiftService.BuildConsumableGift(consumableCatalogJson, EmptyOwned, rng, pool);
|
|
if (gift == null) return null;
|
|
|
|
gift.Context = ReacquisitionContext;
|
|
return gift;
|
|
}
|
|
}
|
|
}
|