Release Healer Man 0.1.40
This commit is contained in:
1 parent
ed0d2fea3d
commit
197a64117c
215 files changed
+5131
-181
No files matched your search
@@ -0,0 +1,81 @@
|
||||
export const BOSS_CARD_RARITIES = ["normal", "rare", "epic", "legendary"] as const;
|
||||
export type BossCardRarity = (typeof BOSS_CARD_RARITIES)[number];
|
||||
|
||||
export const BOSS_CARD_RARITY_INFO = {
|
||||
normal: { label: "Normal", chance: 1, odds: "100%", color: "#d8d5cb" },
|
||||
rare: { label: "Rare", chance: 1 / 10, odds: "1 in 10", color: "#69b4ff" },
|
||||
epic: { label: "Epic", chance: 1 / 50, odds: "1 in 50", color: "#c493ff" },
|
||||
legendary: { label: "Legendary", chance: 1 / 100, odds: "1 in 100", color: "#ffb45b" },
|
||||
} as const;
|
||||
|
||||
export type BossCardCounts = Partial<Record<BossCardRarity, number>>;
|
||||
export interface BossCardCollection {
|
||||
readonly counts: Readonly<Record<string, BossCardCounts>>;
|
||||
/** Last reward per spawned boss prevents shared-world replay and reconnect duplicates. */
|
||||
readonly lastClaims: Readonly<Record<string, string>>;
|
||||
}
|
||||
|
||||
export interface BossCardDrop {
|
||||
readonly bossKey: string;
|
||||
readonly spawnId: string;
|
||||
readonly claimId: string;
|
||||
readonly rarities: readonly BossCardRarity[];
|
||||
}
|
||||
|
||||
export function emptyBossCardCollection(): BossCardCollection {
|
||||
return { counts: {}, lastClaims: {} };
|
||||
}
|
||||
|
||||
export function normalizeBossCardCollection(value: unknown): BossCardCollection {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return emptyBossCardCollection();
|
||||
const raw = value as Partial<BossCardCollection>;
|
||||
const counts: Record<string, BossCardCounts> = Object.create(null);
|
||||
if (raw.counts && typeof raw.counts === "object" && !Array.isArray(raw.counts)) {
|
||||
for (const [key, candidate] of Object.entries(raw.counts)) {
|
||||
if (!key || !candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue;
|
||||
const entry: BossCardCounts = {};
|
||||
for (const rarity of BOSS_CARD_RARITIES) {
|
||||
const count = candidate[rarity];
|
||||
if (typeof count === "number" && Number.isFinite(count) && count >= 1) {
|
||||
entry[rarity] = Math.min(Number.MAX_SAFE_INTEGER, Math.floor(count));
|
||||
}
|
||||
}
|
||||
if (Object.keys(entry).length) counts[key] = entry;
|
||||
}
|
||||
}
|
||||
const lastClaims = raw.lastClaims && typeof raw.lastClaims === "object" && !Array.isArray(raw.lastClaims)
|
||||
? Object.fromEntries(Object.entries(raw.lastClaims).filter(([key, claim]) => key && typeof claim === "string"))
|
||||
: {};
|
||||
return { counts, lastClaims };
|
||||
}
|
||||
|
||||
/** Independent bonus rolls; Normal is always awarded, including with all three bonuses. */
|
||||
export function rollBossCards(random: () => number = Math.random): readonly BossCardRarity[] {
|
||||
return BOSS_CARD_RARITIES.filter((rarity) => {
|
||||
if (rarity === "normal") return true;
|
||||
const roll = random();
|
||||
return Number.isFinite(roll) && Math.max(0, Math.min(1, roll)) < BOSS_CARD_RARITY_INFO[rarity].chance;
|
||||
});
|
||||
}
|
||||
|
||||
export function collectBossCardDrop(collection: BossCardCollection, drop: BossCardDrop): BossCardCollection {
|
||||
const lastClaim = collection.lastClaims[drop.spawnId];
|
||||
if (lastClaim === drop.claimId) return collection;
|
||||
// Generated claims begin with the death's epoch time. Ignore older snapshots
|
||||
// even after the same spawn has awarded a subsequent kill.
|
||||
if (lastClaim && /^\d+:/.test(lastClaim) && /^\d+:/.test(drop.claimId)) {
|
||||
const [lastTime, lastRevision] = lastClaim.split(":").map(Number);
|
||||
const [time, revision] = drop.claimId.split(":").map(Number);
|
||||
if (time < lastTime || (time === lastTime && revision <= lastRevision)) return collection;
|
||||
}
|
||||
const counts = { ...collection.counts[drop.bossKey] };
|
||||
for (const rarity of new Set(drop.rarities)) {
|
||||
if (BOSS_CARD_RARITIES.includes(rarity)) {
|
||||
counts[rarity] = Math.min(Number.MAX_SAFE_INTEGER, (counts[rarity] ?? 0) + 1);
|
||||
}
|
||||
}
|
||||
return {
|
||||
counts: { ...collection.counts, [drop.bossKey]: counts },
|
||||
lastClaims: { ...collection.lastClaims, [drop.spawnId]: drop.claimId },
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user