mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-27 16:09:07 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,643 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user