generated from nhcarrigan/template
Compare commits
4 Commits
48477ee286
...
7d1126e8ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
7d1126e8ad
|
|||
|
ec0763819e
|
|||
|
4a9ecbf706
|
|||
|
96868c4143
|
@@ -122,7 +122,7 @@ export const defaultBosses: Array<Boss> = [
|
|||||||
// ── Shadow Marshes ────────────────────────────────────────────────────────
|
// ── Shadow Marshes ────────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
bountyRunestones: 20,
|
bountyRunestones: 20,
|
||||||
crystalReward: 700,
|
crystalReward: 1500,
|
||||||
currentHp: 6_000_000,
|
currentHp: 6_000_000,
|
||||||
damagePerSecond: 1200,
|
damagePerSecond: 1200,
|
||||||
description:
|
description:
|
||||||
@@ -140,7 +140,7 @@ export const defaultBosses: Array<Boss> = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
bountyRunestones: 25,
|
bountyRunestones: 25,
|
||||||
crystalReward: 1500,
|
crystalReward: 3000,
|
||||||
currentHp: 12_000_000,
|
currentHp: 12_000_000,
|
||||||
damagePerSecond: 2400,
|
damagePerSecond: 2400,
|
||||||
description:
|
description:
|
||||||
@@ -158,7 +158,7 @@ export const defaultBosses: Array<Boss> = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
bountyRunestones: 30,
|
bountyRunestones: 30,
|
||||||
crystalReward: 3000,
|
crystalReward: 6000,
|
||||||
currentHp: 20_000_000,
|
currentHp: 20_000_000,
|
||||||
damagePerSecond: 4000,
|
damagePerSecond: 4000,
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ export const defaultQuests: Array<Quest> = [
|
|||||||
},
|
},
|
||||||
// ── Shadow Marshes ────────────────────────────────────────────────────────
|
// ── Shadow Marshes ────────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
combatPowerRequired: 5_000_000,
|
combatPowerRequired: 2_000_000,
|
||||||
description:
|
description:
|
||||||
"A cursed lake shrouded in permanent twilight. Strange energies pulse beneath its surface.",
|
"A cursed lake shrouded in permanent twilight. Strange energies pulse beneath its surface.",
|
||||||
durationSeconds: 45 * 60,
|
durationSeconds: 45 * 60,
|
||||||
@@ -233,6 +233,7 @@ export const defaultQuests: Array<Quest> = [
|
|||||||
rewards: [
|
rewards: [
|
||||||
{ amount: 5_000_000, type: "gold" },
|
{ amount: 5_000_000, type: "gold" },
|
||||||
{ amount: 5000, type: "essence" },
|
{ amount: 5000, type: "essence" },
|
||||||
|
{ amount: 150, type: "crystals" },
|
||||||
{ targetId: "peasant_3", type: "upgrade" },
|
{ targetId: "peasant_3", type: "upgrade" },
|
||||||
],
|
],
|
||||||
status: "locked",
|
status: "locked",
|
||||||
@@ -249,6 +250,7 @@ export const defaultQuests: Array<Quest> = [
|
|||||||
rewards: [
|
rewards: [
|
||||||
{ amount: 20_000_000, type: "gold" },
|
{ amount: 20_000_000, type: "gold" },
|
||||||
{ amount: 20_000, type: "essence" },
|
{ amount: 20_000, type: "essence" },
|
||||||
|
{ amount: 500, type: "crystals" },
|
||||||
{ targetId: "shadow_assassin", type: "adventurer" },
|
{ targetId: "shadow_assassin", type: "adventurer" },
|
||||||
],
|
],
|
||||||
status: "locked",
|
status: "locked",
|
||||||
|
|||||||
@@ -71,8 +71,7 @@ const shuffleWithSeed = <T>(array: Array<T>, seed: number): Array<T> => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const challengeTypes: Array<DailyChallengeType> = [
|
const progressionChallengeTypes: Array<DailyChallengeType> = [
|
||||||
"clicks",
|
|
||||||
"bossesDefeated",
|
"bossesDefeated",
|
||||||
"questsCompleted",
|
"questsCompleted",
|
||||||
"prestige",
|
"prestige",
|
||||||
@@ -80,7 +79,8 @@ const challengeTypes: Array<DailyChallengeType> = [
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates 3 daily challenges for the given date string, deterministically.
|
* Generates 3 daily challenges for the given date string, deterministically.
|
||||||
* Picks one challenge from 3 different randomly-selected types.
|
* Always includes a "clicks" challenge (always completable regardless of
|
||||||
|
* progression), then picks 2 more from the remaining types.
|
||||||
* @param dateString - The date string (YYYY-MM-DD) to generate challenges for.
|
* @param dateString - The date string (YYYY-MM-DD) to generate challenges for.
|
||||||
* @returns An array of 3 DailyChallenge objects.
|
* @returns An array of 3 DailyChallenge objects.
|
||||||
*/
|
*/
|
||||||
@@ -88,8 +88,10 @@ const generateDailyChallenges = (
|
|||||||
dateString: string,
|
dateString: string,
|
||||||
): Array<DailyChallenge> => {
|
): Array<DailyChallenge> => {
|
||||||
const seed = dateSeed(dateString);
|
const seed = dateSeed(dateString);
|
||||||
const selectedTypes = shuffleWithSeed([ ...challengeTypes ], seed).
|
const selectedTypes: Array<DailyChallengeType> = [
|
||||||
slice(0, 3);
|
"clicks",
|
||||||
|
...shuffleWithSeed([ ...progressionChallengeTypes ], seed).slice(0, 2),
|
||||||
|
];
|
||||||
|
|
||||||
return selectedTypes.map((type, index) => {
|
return selectedTypes.map((type, index) => {
|
||||||
const templates = dailyChallengeTemplates.filter((template) => {
|
const templates = dailyChallengeTemplates.filter((template) => {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import type {
|
|||||||
} from "@elysium/types";
|
} from "@elysium/types";
|
||||||
|
|
||||||
const basePrestigeGoldThreshold = 1_000_000;
|
const basePrestigeGoldThreshold = 1_000_000;
|
||||||
const runestonesPerPrestigeLevel = 10;
|
const runestonesPerPrestigeLevel = 15;
|
||||||
const milestoneInterval = 5;
|
const milestoneInterval = 5;
|
||||||
const milestoneRunestonesPerInterval = 25;
|
const milestoneRunestonesPerInterval = 25;
|
||||||
|
|
||||||
|
|||||||
@@ -46,13 +46,24 @@ describe("generateDailyChallenges", () => {
|
|||||||
expect(a.map((c) => c.id)).toEqual(b.map((c) => c.id));
|
expect(a.map((c) => c.id)).toEqual(b.map((c) => c.id));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("always includes a clicks challenge regardless of date", async () => {
|
||||||
|
vi.setSystemTime(LA_MIDNIGHT_2024_01_15);
|
||||||
|
const { generateDailyChallenges } = await import("../../src/services/dailyChallenges.js");
|
||||||
|
const day1 = generateDailyChallenges("2024-01-15");
|
||||||
|
const day2 = generateDailyChallenges("2024-01-16");
|
||||||
|
expect(day1.some((c) => c.type === "clicks")).toBe(true);
|
||||||
|
expect(day2.some((c) => c.type === "clicks")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("generates different challenges for different dates", async () => {
|
it("generates different challenges for different dates", async () => {
|
||||||
vi.setSystemTime(LA_MIDNIGHT_2024_01_15);
|
vi.setSystemTime(LA_MIDNIGHT_2024_01_15);
|
||||||
const { generateDailyChallenges } = await import("../../src/services/dailyChallenges.js");
|
const { generateDailyChallenges } = await import("../../src/services/dailyChallenges.js");
|
||||||
const day1 = generateDailyChallenges("2024-01-15");
|
const day1 = generateDailyChallenges("2024-01-15");
|
||||||
const day2 = generateDailyChallenges("2024-01-16");
|
const day2 = generateDailyChallenges("2024-01-16");
|
||||||
// They should differ in at least one challenge ID (types vary by seed)
|
// The 2 non-clicks types should vary by seed between dates
|
||||||
expect(day1.map((c) => c.type)).not.toEqual(day2.map((c) => c.type));
|
const day1NonClicks = day1.filter((c) => c.type !== "clicks").map((c) => c.type);
|
||||||
|
const day2NonClicks = day2.filter((c) => c.type !== "clicks").map((c) => c.type);
|
||||||
|
expect(day1NonClicks).not.toEqual(day2NonClicks);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -102,21 +102,21 @@ describe("isEligibleForPrestige", () => {
|
|||||||
|
|
||||||
describe("calculateRunestones", () => {
|
describe("calculateRunestones", () => {
|
||||||
it("calculates basic runestones formula", () => {
|
it("calculates basic runestones formula", () => {
|
||||||
// floor(cbrt(4_000_000 / 1_000_000)) × 10 = floor(cbrt(4)) × 10 = 1 × 10 = 10
|
// floor(cbrt(4_000_000 / 1_000_000)) × 15 = floor(cbrt(4)) × 15 = 1 × 15 = 15
|
||||||
const result = calculateRunestones({ totalGoldEarned: 4_000_000, prestigeCount: 0, purchasedUpgradeIds: [] });
|
const result = calculateRunestones({ totalGoldEarned: 4_000_000, prestigeCount: 0, purchasedUpgradeIds: [] });
|
||||||
expect(result).toBe(10);
|
expect(result).toBe(15);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies echo runestone multiplier", () => {
|
it("applies echo runestone multiplier", () => {
|
||||||
// floor(cbrt(4)) × 10 = 10; × 2 = 20
|
// floor(cbrt(4)) × 15 = 15; × 2 = 30
|
||||||
const result = calculateRunestones({ totalGoldEarned: 4_000_000, prestigeCount: 0, purchasedUpgradeIds: [], echoRunestoneMultiplier: 2 });
|
const result = calculateRunestones({ totalGoldEarned: 4_000_000, prestigeCount: 0, purchasedUpgradeIds: [], echoRunestoneMultiplier: 2 });
|
||||||
expect(result).toBe(20);
|
expect(result).toBe(30);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("applies purchased runestone upgrade multiplier", () => {
|
it("applies purchased runestone upgrade multiplier", () => {
|
||||||
// With "runestone_gain_1" purchased (multiplier 1.25): floor(10 × 1.25) = 12
|
// With "runestone_gain_1" purchased (multiplier 1.25): floor(15 × 1.25) = 18
|
||||||
const result = calculateRunestones({ totalGoldEarned: 4_000_000, prestigeCount: 0, purchasedUpgradeIds: ["runestone_gain_1"] });
|
const result = calculateRunestones({ totalGoldEarned: 4_000_000, prestigeCount: 0, purchasedUpgradeIds: ["runestone_gain_1"] });
|
||||||
expect(result).toBe(12);
|
expect(result).toBe(18);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("caps base runestones before multipliers", () => {
|
it("caps base runestones before multipliers", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user