Release Healer Man 0.1.40

This commit is contained in:
phenom committed 2026-09-13 13:21:27 -04:00
1 parent ed0d2fea3d
commit 197a64117c
215 files changed
+5131 -181

No files matched your search

+81
View File
@@ -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 },
};
}