accurate banner pools: standard, pickup, limited, fes with weighted rate-up
This commit is contained in:
1 parent
5e67782e58
commit
ed95c7ac30
2 files changed
+92
-26
No files matched your search
+80
-12
@@ -99,29 +99,97 @@ class StudentDB:
|
|||||||
if rarity in self.by_rarity:
|
if rarity in self.by_rarity:
|
||||||
self.by_rarity[rarity].append(s)
|
self.by_rarity[rarity].append(s)
|
||||||
|
|
||||||
|
# Pre-build standard pool (IsLimited == 0 only) and full pool
|
||||||
|
self._std_pool: dict[int, list[dict]] = {1: [], 2: [], 3: []}
|
||||||
|
self._limited_pool: list[dict] = [] # all IsLimited == 1 chars
|
||||||
|
for s in self.students:
|
||||||
|
limit = s.get("IsLimited") or 0
|
||||||
|
if limit >= 2:
|
||||||
|
continue
|
||||||
|
r = s["StarGrade"]
|
||||||
|
if limit == 0 and r in self._std_pool:
|
||||||
|
self._std_pool[r].append(s)
|
||||||
|
elif limit == 1:
|
||||||
|
self._limited_pool.append(s)
|
||||||
|
|
||||||
self._loaded = True
|
self._loaded = True
|
||||||
log.info(
|
log.info(
|
||||||
"Loaded %d students (1*: %d, 2*: %d, 3*: %d) — %d welfare excluded",
|
"Loaded %d students (std: %d, limited: %d, welfare: %d)",
|
||||||
len(self.students),
|
len(self.students),
|
||||||
len(self.by_rarity[1]),
|
sum(len(v) for v in self._std_pool.values()),
|
||||||
len(self.by_rarity[2]),
|
len(self._limited_pool),
|
||||||
len(self.by_rarity[3]),
|
|
||||||
excluded,
|
excluded,
|
||||||
)
|
)
|
||||||
|
|
||||||
def random_by_rarity(self, rarity: int, exclude_limited: bool = False) -> dict:
|
def get(self, student_id: int) -> Optional[dict]:
|
||||||
"""Pick a random student of the given base rarity.
|
return self.by_id.get(student_id)
|
||||||
|
|
||||||
If exclude_limited is True, only returns standard-pool characters
|
def get_by_name(self, name: str) -> Optional[dict]:
|
||||||
(IsLimited == 0). Used by Regular Recruitment to exclude banner-only units.
|
return self.by_name.get(name.lower())
|
||||||
|
|
||||||
|
def build_pool(self, banner: Optional[dict]) -> dict[int, list[dict]]:
|
||||||
|
"""Build the effective character pool for a banner.
|
||||||
|
|
||||||
|
Returns {1: [...], 2: [...], 3: [...]} with characters pullable on this banner.
|
||||||
|
|
||||||
|
- Regular Recruitment (banner=None): standard pool only
|
||||||
|
- PickupGacha / LimitedGacha: standard pool + rate-up students
|
||||||
|
- FesGacha: standard pool + all limiteds + rate-ups
|
||||||
|
"""
|
||||||
|
pool: dict[int, list[dict]] = {
|
||||||
|
r: list(self._std_pool[r]) for r in (1, 2, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if banner is None:
|
||||||
|
return pool
|
||||||
|
|
||||||
|
gtype = banner.get("gachaType", "")
|
||||||
|
rateup_names = [n.lower() for n in banner.get("rateups", [])]
|
||||||
|
|
||||||
|
# For Fes banners, add ALL past limited characters to the pool
|
||||||
|
if gtype == "FesGacha":
|
||||||
|
for s in self._limited_pool:
|
||||||
|
r = s["StarGrade"]
|
||||||
|
if r in pool:
|
||||||
|
pool[r].append(s)
|
||||||
|
|
||||||
|
# Add rate-up students (they might already be in the pool, but we need
|
||||||
|
# to identify them for weighted selection later)
|
||||||
|
for name in rateup_names:
|
||||||
|
s = self.get_by_name(name)
|
||||||
|
if s and (s.get("IsLimited") or 0) < 2:
|
||||||
|
r = s["StarGrade"]
|
||||||
|
if r in pool and s not in pool[r]:
|
||||||
|
pool[r].append(s)
|
||||||
|
|
||||||
|
return pool
|
||||||
|
|
||||||
|
def weighted_pick(self, pool: list[dict], rateup_names: list[str], rarity: int) -> dict:
|
||||||
|
"""Pick a student from a pool with rate-up weighting.
|
||||||
|
|
||||||
|
For 3★ rarity, rate-up students each get ~23% weight (matching the
|
||||||
|
real game's 0.7% per rate-up out of 3% total). For 1-2★, uniform.
|
||||||
"""
|
"""
|
||||||
pool = self.by_rarity.get(rarity, [])
|
|
||||||
if exclude_limited:
|
|
||||||
pool = [s for s in pool if (s.get("IsLimited") or 0) == 0]
|
|
||||||
if not pool:
|
if not pool:
|
||||||
pool = self.students
|
return random.choice(self.students)
|
||||||
|
|
||||||
|
if rarity == 3 and rateup_names:
|
||||||
|
# Weighted: rate-up chars get 3x weight each vs normal chars
|
||||||
|
weights = []
|
||||||
|
for s in pool:
|
||||||
|
if s["Name"].lower() in rateup_names:
|
||||||
|
weights.append(3.0)
|
||||||
|
else:
|
||||||
|
weights.append(1.0)
|
||||||
|
return random.choices(pool, weights=weights, k=1)[0]
|
||||||
|
|
||||||
return random.choice(pool)
|
return random.choice(pool)
|
||||||
|
|
||||||
|
def random_by_rarity(self, rarity: int) -> dict:
|
||||||
|
"""Pick any student of given rarity from the full pullable pool (fallback)."""
|
||||||
|
pool = self.by_rarity.get(rarity, [])
|
||||||
|
return random.choice(pool) if pool else random.choice(self.students)
|
||||||
|
|
||||||
def wiki_portrait(self, student: dict) -> str:
|
def wiki_portrait(self, student: dict) -> str:
|
||||||
"""Wiki face-portrait CDN URL for a student dict (252x204)."""
|
"""Wiki face-portrait CDN URL for a student dict (252x204)."""
|
||||||
return wiki_portrait_url(student)
|
return wiki_portrait_url(student)
|
||||||
|
|||||||
+12
-14
@@ -230,8 +230,13 @@ class BlueArchiveGacha(commands.Cog):
|
|||||||
# "Opening envelope" teaser
|
# "Opening envelope" teaser
|
||||||
teaser = await ctx.reply("Opening recruitment envelope" + ("s..." if count > 1 else "..."))
|
teaser = await ctx.reply("Opening recruitment envelope" + ("s..." if count > 1 else "..."))
|
||||||
|
|
||||||
# Determine pool scope: regular banner excludes limiteds
|
# Build the banner-specific character pool
|
||||||
is_regular = (banner_id is None or banner_id == "regular")
|
if banner_id and banner_id != "regular" and banner:
|
||||||
|
pool = db.build_pool(banner)
|
||||||
|
rateup_names = [n.lower() for n in banner.get("rateups", [])]
|
||||||
|
else:
|
||||||
|
pool = db.build_pool(None) # regular recruitment
|
||||||
|
rateup_names = []
|
||||||
|
|
||||||
# Perform pulls
|
# Perform pulls
|
||||||
pulls = []
|
pulls = []
|
||||||
@@ -242,18 +247,11 @@ class BlueArchiveGacha(commands.Cog):
|
|||||||
r = PULL10_RATES
|
r = PULL10_RATES
|
||||||
|
|
||||||
rarity = roll_rarity(r)
|
rarity = roll_rarity(r)
|
||||||
student = db.random_by_rarity(rarity, exclude_limited=is_regular)
|
rarity_pool = pool.get(rarity, [])
|
||||||
|
if rarity_pool:
|
||||||
# Check for rate-up weighting (simplified: 30% chance to hit rate-up if 3★)
|
student = db.weighted_pick(rarity_pool, rateup_names, rarity)
|
||||||
if banner and rarity == 3 and banner.get("rateups"):
|
else:
|
||||||
rateup_names = [n.lower() for n in banner["rateups"]]
|
student = db.random_by_rarity(rarity) # fallback
|
||||||
if random.random() < 0.30:
|
|
||||||
# Try to find the rate-up student in our DB
|
|
||||||
for rn in rateup_names:
|
|
||||||
s = db.get_by_name(rn)
|
|
||||||
if s and s["StarGrade"] == 3:
|
|
||||||
student = s
|
|
||||||
break
|
|
||||||
|
|
||||||
pulls.append(student)
|
pulls.append(student)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user