"""Regenerate and audit levelup-rewards.json from the 2023 catalogue. The server enforces two things over a level-up box: the deny-list in Server/Utils/LevelUpRewardPool.cs, which can never be overridden, and the allow-list in levelup-rewards.json, which is the reviewed set of items a box may contain. This script rebuilds that allow-list and prints what it kept and what it refused, so a change to the catalogue or the deny-list can be reviewed as a diff. The patterns below mirror LevelUpRewardPool.DeniedPatterns and DeniedTags and MUST be kept in step with them. The server's copy is the source of truth - the allow-list is already filtered by it at pick time, so a pattern missing here only means a candidate that the server would still refuse; it can never mean an item the server would allow. Run this from anywhere with the repo checked out: python Server/RadicadeServer-Config/generate-levelup-rewards.py Then read the audit, delete anything from the JSON that should not be there, and remember that an item with no name, tooltip or tag to judge it by cannot be classified here at all. """ import json import os import re # The script sits beside the files it reads, so it works from any working directory. CONFIG = os.path.dirname(os.path.abspath(__file__)) DENIED = [ "exclusive", "early access", "alpha", "art festival", "rec con", "reccon", "rrcc", "raw data", "sfvrcc", "contest", "trophy", "competition", "moderator", "volunteer", "video partner", "creator program", "partnered creator", "partner", "bug bounty", "security", "ethical hackers", "gold notes", "seller swag", "sponsored league", "rro-", "rro specific", "promo", "promotional", "sponsor", "collab", "licensed", "prom", "mrbeast", "mr beast", "miraculous", "nba", "class of", "graduation", "class)", "creator conference", "for earning", "available until", "unlocked by visiting", "unlocked during", "xbox", "launch reward", "launch exclusive", "reward for", "given to", "space marine", "space visor", "(gold)", "limited", "never return", "founder", "founders", "halloween", "holiday", "thanksgiving", "invasion", "elseware", "new year", "carnivale", "reclympics", "rec rocks", "anniversary", "2020 glasses", "developer", "dev only", "staff only", "admin only", "test item", "debug", ] DENIED_TAGS = {"halloween", "holidays", "thanksgiving", "invasion", "elseware", "reccon", "reccon2021"} def denied_name(text): if not text: return None low = text.lower() for p in DENIED: if p in low: return p return None def denied(item): for field in ("FriendlyName", "Tooltip"): hit = denied_name(item.get(field) or "") if hit: return field + ":" + hit tags = (item.get("TagList") or "").lower() for tag in re.split(r"[,\s]+", tags): if tag.strip() in DENIED_TAGS: return "tag:" + tag.strip() return None def main(): catalog = json.load(open(CONFIG + r"\2022-avatar.json", encoding="utf-8")) resolvable = set( d.strip().lower() for d in json.load(open(CONFIG + r"\avatar-item-lookup.json", encoding="utf-8"))["descriptions"] ) kept, refused = [], {} for item in catalog: desc = (item.get("AvatarItemDesc") or "").strip() if not desc or item.get("IsBaseAvatarItem"): continue rarity = item.get("Rarity", 0) if rarity not in (10, 20, 30, 50): refused.setdefault("rarity", []).append(item.get("FriendlyName")) continue if desc.lower() not in resolvable: refused.setdefault("unresolvable", []).append(item.get("FriendlyName")) continue why = denied(item) if why: refused.setdefault(why.split(":")[0], []).append(item.get("FriendlyName")) continue kept.append((rarity, item.get("FriendlyName") or "", desc)) kept.sort(key=lambda row: (row[0], row[1].lower())) per_tier = {} for rarity, _, _ in kept: per_tier[rarity] = per_tier.get(rarity, 0) + 1 # ---- consumables: the shipped global list minus the deny-list, the premium and the seasonal consumables = json.load(open(CONFIG + r"\consumables.json", encoding="utf-8")) # Event, survey, licensed and paid-category consumables that no name pattern can identify. extra_denied = [ "hair dye", "coupon", "snowman", "sparkler", "dracula", "goblin king", "ghost beard", "witch film", "surve", "survey", "forbidden", "forsaken", "premium", "creator bubbly", ] consumable_kept, consumable_refused = [], {} for item in consumables: name = item.get("FriendlyName") or "" why = denied_name(name) if not why: for pattern in extra_denied: if pattern in name.lower(): why = pattern break if why: consumable_refused.setdefault(why, []).append(name) else: consumable_kept.append((name, item["ConsumableItemDesc"])) out = { "AvatarItems": [desc for _, _, desc in kept], "Consumables": [desc for _, desc in consumable_kept], } path = CONFIG + r"\levelup-rewards.json" with open(path, "w", encoding="utf-8", newline="\r\n") as fh: json.dump(out, fh, indent=2) fh.write("\n") print("wrote", path) print("avatar allowed:", len(kept), "per rarity:", per_tier) print("consumables allowed:", len(consumable_kept)) print() print("== refused avatar items by reason ==") for reason, names in sorted(refused.items(), key=lambda kv: -len(kv[1])): print(f"{reason:20} {len(names):5} e.g. {names[:4]}") print() print("== refused consumables ==") for reason, names in sorted(consumable_refused.items(), key=lambda kv: -len(kv[1])): print(f"{reason:20} {len(names):5} {names[:6]}") print() print("== kept consumables ==") for name, _ in consumable_kept: print(" ", name) print() print("== sample of kept avatar items (first 2 per tier) ==") seen = {} for rarity, name, _ in kept: if seen.get(rarity, 0) >= 2: continue seen[rarity] = seen.get(rarity, 0) + 1 print(f" {rarity}: {name}") print(" ... a further", len(kept) - sum(seen.values()), "omitted") print() print("== denied check: items the brief names explicitly ==") for probe in ["White Hat", "Winged Headphones", "Rec Con Hoodie", "Space Visor", "Royal Cape", "Top Hat (Gold)", "Hallow's Blazer", "Prom", "MrBeast", "Early Access", "Alpha Team", "Notes Headphones"]: hits = [(i.get("FriendlyName"), denied(i)) for i in catalog if probe.lower() in (i.get("FriendlyName") or "").lower()] print(f"{probe!r}: " + (", ".join(f"{n}->{w}" for n, w in hits) if hits else "no match")) main()