generated from nhcarrigan/template
feat: extend quest content through endgame to cover P60–P160 (#175)
This commit is contained in:
@@ -1306,6 +1306,54 @@ export const defaultQuests: Array<Quest> = [
|
|||||||
status: "locked",
|
status: "locked",
|
||||||
zoneId: "reality_forge",
|
zoneId: "reality_forge",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
combatPowerRequired: 1e59,
|
||||||
|
description:
|
||||||
|
"The deep levels of the Forge, where the most experimental realities are stored in a state of near-completion. Your guild discovers that several of these abandoned projects are disturbingly familiar.",
|
||||||
|
durationSeconds: 24 * 60 * 60,
|
||||||
|
id: "forge_depths",
|
||||||
|
name: "The Forge Depths",
|
||||||
|
prerequisiteIds: [ "forge_chronicle" ],
|
||||||
|
rewards: [
|
||||||
|
{ amount: 5e62, type: "gold" },
|
||||||
|
{ amount: 1.5e59, type: "essence" },
|
||||||
|
{ amount: 8e54, type: "crystals" },
|
||||||
|
],
|
||||||
|
status: "locked",
|
||||||
|
zoneId: "reality_forge",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combatPowerRequired: 3e64,
|
||||||
|
description:
|
||||||
|
"The underlying structure that defines the laws for every reality the Forge produces — a lattice of constraints so fundamental that violating them would undo everything. Your guild crosses it carefully.",
|
||||||
|
durationSeconds: 24 * 60 * 60,
|
||||||
|
id: "prime_matrix",
|
||||||
|
name: "The Prime Matrix",
|
||||||
|
prerequisiteIds: [ "forge_depths" ],
|
||||||
|
rewards: [
|
||||||
|
{ amount: 2e67, type: "gold" },
|
||||||
|
{ amount: 6e63, type: "essence" },
|
||||||
|
{ amount: 3e59, type: "crystals" },
|
||||||
|
],
|
||||||
|
status: "locked",
|
||||||
|
zoneId: "reality_forge",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combatPowerRequired: 8e69,
|
||||||
|
description:
|
||||||
|
"The complete record of every reality the Forge has ever produced, indexed, annotated, and preserved. Your universe has a surprisingly detailed entry with several editorial notes.",
|
||||||
|
durationSeconds: 24 * 60 * 60,
|
||||||
|
id: "creation_archive",
|
||||||
|
name: "The Creation Archive",
|
||||||
|
prerequisiteIds: [ "prime_matrix" ],
|
||||||
|
rewards: [
|
||||||
|
{ amount: 1e72, type: "gold" },
|
||||||
|
{ amount: 3e68, type: "essence" },
|
||||||
|
{ amount: 1.5e64, type: "crystals" },
|
||||||
|
],
|
||||||
|
status: "locked",
|
||||||
|
zoneId: "reality_forge",
|
||||||
|
},
|
||||||
// ── Cosmic Maelstrom ──────────────────────────────────────────────────────
|
// ── Cosmic Maelstrom ──────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
combatPowerRequired: 1.8e46,
|
combatPowerRequired: 1.8e46,
|
||||||
@@ -1406,6 +1454,54 @@ export const defaultQuests: Array<Quest> = [
|
|||||||
status: "locked",
|
status: "locked",
|
||||||
zoneId: "cosmic_maelstrom",
|
zoneId: "cosmic_maelstrom",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
combatPowerRequired: 2e73,
|
||||||
|
description:
|
||||||
|
"The deepest layer of the maelstrom — where the storms have been spiralling for so long they have created something resembling order. Your guild navigates it by learning to read the shape of chaos.",
|
||||||
|
durationSeconds: 24 * 60 * 60,
|
||||||
|
id: "maelstrom_deep",
|
||||||
|
name: "The Deep Maelstrom",
|
||||||
|
prerequisiteIds: [ "maelstrom_codex" ],
|
||||||
|
rewards: [
|
||||||
|
{ amount: 5e76, type: "gold" },
|
||||||
|
{ amount: 1.5e73, type: "essence" },
|
||||||
|
{ amount: 8e68, type: "crystals" },
|
||||||
|
],
|
||||||
|
status: "locked",
|
||||||
|
zoneId: "cosmic_maelstrom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combatPowerRequired: 5e79,
|
||||||
|
description:
|
||||||
|
"The point at which all the storm currents converge — not because they are drawn there, but because there is nowhere else left to go. Your guild stands in the geometric centre of cosmic fury.",
|
||||||
|
durationSeconds: 24 * 60 * 60,
|
||||||
|
id: "maelstrom_nexus",
|
||||||
|
name: "The Storm Nexus",
|
||||||
|
prerequisiteIds: [ "maelstrom_deep" ],
|
||||||
|
rewards: [
|
||||||
|
{ amount: 2e83, type: "gold" },
|
||||||
|
{ amount: 6e79, type: "essence" },
|
||||||
|
{ amount: 3e75, type: "crystals" },
|
||||||
|
],
|
||||||
|
status: "locked",
|
||||||
|
zoneId: "cosmic_maelstrom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combatPowerRequired: 1e86,
|
||||||
|
description:
|
||||||
|
"The record of every storm that has ever been — an archive written in lightning, indexed in thunder, preserved in the kind of silence that only exists at the exact centre of infinite noise.",
|
||||||
|
durationSeconds: 24 * 60 * 60,
|
||||||
|
id: "storm_chronicle",
|
||||||
|
name: "The Storm Chronicle",
|
||||||
|
prerequisiteIds: [ "maelstrom_nexus" ],
|
||||||
|
rewards: [
|
||||||
|
{ amount: 1e90, type: "gold" },
|
||||||
|
{ amount: 3e86, type: "essence" },
|
||||||
|
{ amount: 1.5e82, type: "crystals" },
|
||||||
|
],
|
||||||
|
status: "locked",
|
||||||
|
zoneId: "cosmic_maelstrom",
|
||||||
|
},
|
||||||
// ── Primeval Sanctum ──────────────────────────────────────────────────────
|
// ── Primeval Sanctum ──────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
combatPowerRequired: 7.2e49,
|
combatPowerRequired: 7.2e49,
|
||||||
@@ -1504,6 +1600,54 @@ export const defaultQuests: Array<Quest> = [
|
|||||||
status: "locked",
|
status: "locked",
|
||||||
zoneId: "primeval_sanctum",
|
zoneId: "primeval_sanctum",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
combatPowerRequired: 3e92,
|
||||||
|
description:
|
||||||
|
"The deepest chambers of the sanctum — where the primordia are not preserved but still occurring, still being, still becoming for the first and only time. The floor hums with unfinished creation.",
|
||||||
|
durationSeconds: 24 * 60 * 60,
|
||||||
|
id: "sanctum_deep",
|
||||||
|
name: "The Deep Sanctum",
|
||||||
|
prerequisiteIds: [ "sanctum_chronicle" ],
|
||||||
|
rewards: [
|
||||||
|
{ amount: 8e95, type: "gold" },
|
||||||
|
{ amount: 2.5e92, type: "essence" },
|
||||||
|
{ amount: 1e88, type: "crystals" },
|
||||||
|
],
|
||||||
|
status: "locked",
|
||||||
|
zoneId: "primeval_sanctum",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combatPowerRequired: 8e98,
|
||||||
|
description:
|
||||||
|
"The crossroads between everything primeval and everything that came after — a threshold so old that every subsequent age of the universe is, from its perspective, still ongoing.",
|
||||||
|
durationSeconds: 24 * 60 * 60,
|
||||||
|
id: "sanctum_nexus",
|
||||||
|
name: "The Primeval Nexus",
|
||||||
|
prerequisiteIds: [ "sanctum_deep" ],
|
||||||
|
rewards: [
|
||||||
|
{ amount: 4e102, type: "gold" },
|
||||||
|
{ amount: 1.2e99, type: "essence" },
|
||||||
|
{ amount: 5e94, type: "crystals" },
|
||||||
|
],
|
||||||
|
status: "locked",
|
||||||
|
zoneId: "primeval_sanctum",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combatPowerRequired: 2e105,
|
||||||
|
description:
|
||||||
|
"The sanctum's final gift to those who reached its depths: a full accounting of what it means to have existed before time had opinions about how things should go. Your guild is the first to read it.",
|
||||||
|
durationSeconds: 24 * 60 * 60,
|
||||||
|
id: "primeval_archive",
|
||||||
|
name: "The Primeval Archive",
|
||||||
|
prerequisiteIds: [ "sanctum_nexus" ],
|
||||||
|
rewards: [
|
||||||
|
{ amount: 2e109, type: "gold" },
|
||||||
|
{ amount: 6e105, type: "essence" },
|
||||||
|
{ amount: 2.5e101, type: "crystals" },
|
||||||
|
],
|
||||||
|
status: "locked",
|
||||||
|
zoneId: "primeval_sanctum",
|
||||||
|
},
|
||||||
// ── The Absolute ──────────────────────────────────────────────────────────
|
// ── The Absolute ──────────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
combatPowerRequired: 3e53,
|
combatPowerRequired: 3e53,
|
||||||
@@ -1601,4 +1745,52 @@ export const defaultQuests: Array<Quest> = [
|
|||||||
status: "locked",
|
status: "locked",
|
||||||
zoneId: "the_absolute",
|
zoneId: "the_absolute",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
combatPowerRequired: 5e111,
|
||||||
|
description:
|
||||||
|
"Beyond the end of everything, there is more. Not in contradiction — but in the way that answers, once found, reveal the next question. Your guild goes further than the concept of further was designed to accommodate.",
|
||||||
|
durationSeconds: 24 * 60 * 60,
|
||||||
|
id: "absolute_beyond",
|
||||||
|
name: "Beyond the Absolute",
|
||||||
|
prerequisiteIds: [ "absolute_dominion" ],
|
||||||
|
rewards: [
|
||||||
|
{ amount: 1e118, type: "gold" },
|
||||||
|
{ amount: 3e114, type: "essence" },
|
||||||
|
{ amount: 1.5e110, type: "crystals" },
|
||||||
|
],
|
||||||
|
status: "locked",
|
||||||
|
zoneId: "the_absolute",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combatPowerRequired: 1e118,
|
||||||
|
description:
|
||||||
|
"The region that exists past the end of existence — a space defined not by what it contains but by being the place where containment no longer applies. Your guild navigates it by not needing it to make sense.",
|
||||||
|
durationSeconds: 24 * 60 * 60,
|
||||||
|
id: "absolute_depth",
|
||||||
|
name: "The Absolute Depth",
|
||||||
|
prerequisiteIds: [ "absolute_beyond" ],
|
||||||
|
rewards: [
|
||||||
|
{ amount: 5e124, type: "gold" },
|
||||||
|
{ amount: 1.5e121, type: "essence" },
|
||||||
|
{ amount: 7e116, type: "crystals" },
|
||||||
|
],
|
||||||
|
status: "locked",
|
||||||
|
zoneId: "the_absolute",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
combatPowerRequired: 3e124,
|
||||||
|
description:
|
||||||
|
"The final record: not of what happened, but of the fact that it happened at all. A guild from a mortal realm reached the end of all things and chose to keep going. The universe notes this with something that is not quite surprise.",
|
||||||
|
durationSeconds: 24 * 60 * 60,
|
||||||
|
id: "absolute_chronicle",
|
||||||
|
name: "The Absolute Chronicle",
|
||||||
|
prerequisiteIds: [ "absolute_depth" ],
|
||||||
|
rewards: [
|
||||||
|
{ amount: 2e131, type: "gold" },
|
||||||
|
{ amount: 6e127, type: "essence" },
|
||||||
|
{ amount: 3e123, type: "crystals" },
|
||||||
|
],
|
||||||
|
status: "locked",
|
||||||
|
zoneId: "the_absolute",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -881,6 +881,30 @@ describe("debug route", () => {
|
|||||||
expect(body.bossesPatched).toBe(1);
|
expect(body.bossesPatched).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("patches boss when only equipmentRewards differ (covers savedRewards branch)", async () => {
|
||||||
|
const state = makeState({
|
||||||
|
bosses: [{ id: "troll_king", status: "available", currentHp: 1000, maxHp: 1000, upgradeRewards: ["click_2"], bountyRunestonesClaimed: false, damagePerSecond: 5, goldReward: 10_000, essenceReward: 25, crystalReward: 5, equipmentRewards: [], prestigeRequirement: 0, zoneId: "verdant_vale", bountyRunestones: 1, name: "The Troll King", description: "Gruk the Immovable has terrorised the trade roads for decades. Merchants will pay handsomely for his head." }] as GameState["bosses"],
|
||||||
|
});
|
||||||
|
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||||
|
vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never);
|
||||||
|
const res = await syncNewContent();
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
const body = await res.json() as { bossesPatched: number };
|
||||||
|
expect(body.bossesPatched).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("patches boss when only bountyRunestones differs with all other fields matching", async () => {
|
||||||
|
const state = makeState({
|
||||||
|
bosses: [{ id: "troll_king", status: "available", currentHp: 1000, maxHp: 1000, upgradeRewards: ["click_2"], bountyRunestonesClaimed: false, damagePerSecond: 5, goldReward: 10_000, essenceReward: 25, crystalReward: 5, equipmentRewards: ["iron_sword", "chainmail", "mages_focus"], prestigeRequirement: 0, zoneId: "verdant_vale", bountyRunestones: 99, name: "The Troll King", description: "Gruk the Immovable has terrorised the trade roads for decades. Merchants will pay handsomely for his head." }] as GameState["bosses"],
|
||||||
|
});
|
||||||
|
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||||
|
vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never);
|
||||||
|
const res = await syncNewContent();
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
const body = await res.json() as { bossesPatched: number };
|
||||||
|
expect(body.bossesPatched).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
it("skips boss stat patching for bosses not in defaults", async () => {
|
it("skips boss stat patching for bosses not in defaults", async () => {
|
||||||
const state = makeState({
|
const state = makeState({
|
||||||
bosses: [{ id: "nonexistent_boss_xyz", status: "available", currentHp: 100, maxHp: 1, upgradeRewards: [], bountyRunestonesClaimed: false, damagePerSecond: 1, goldReward: 1, essenceReward: 1, crystalReward: 1, equipmentRewards: [], prestigeRequirement: 0, zoneId: "old_zone", bountyRunestones: 0, name: "Ghost", description: "Old" }] as GameState["bosses"],
|
bosses: [{ id: "nonexistent_boss_xyz", status: "available", currentHp: 100, maxHp: 1, upgradeRewards: [], bountyRunestonesClaimed: false, damagePerSecond: 1, goldReward: 1, essenceReward: 1, crystalReward: 1, equipmentRewards: [], prestigeRequirement: 0, zoneId: "old_zone", bountyRunestones: 0, name: "Ghost", description: "Old" }] as GameState["bosses"],
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ export const computeEffectiveAdventurerStats = (
|
|||||||
|
|
||||||
const runestonesIncome = state.prestige.runestonesIncomeMultiplier ?? 1;
|
const runestonesIncome = state.prestige.runestonesIncomeMultiplier ?? 1;
|
||||||
const runestonesEssence = state.prestige.runestonesEssenceMultiplier ?? 1;
|
const runestonesEssence = state.prestige.runestonesEssenceMultiplier ?? 1;
|
||||||
const prestigeCombatMultiplier = Math.pow(PRESTIGE_COMBAT_BASE, state.prestige.count);
|
const prestigeCombatMultiplier = PRESTIGE_COMBAT_BASE ** state.prestige.count;
|
||||||
const echoIncome = state.transcendence?.echoIncomeMultiplier ?? 1;
|
const echoIncome = state.transcendence?.echoIncomeMultiplier ?? 1;
|
||||||
const echoCombatMultiplier = state.transcendence?.echoCombatMultiplier ?? 1;
|
const echoCombatMultiplier = state.transcendence?.echoCombatMultiplier ?? 1;
|
||||||
const craftedGoldMultiplier
|
const craftedGoldMultiplier
|
||||||
@@ -383,7 +383,7 @@ export const computePartyCombatPower = (state: GameState): number => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const prestigeMultiplier = Math.pow(PRESTIGE_COMBAT_BASE, state.prestige.count);
|
const prestigeMultiplier = PRESTIGE_COMBAT_BASE ** state.prestige.count;
|
||||||
|
|
||||||
const equipmentCombatMultiplier = state.equipment.
|
const equipmentCombatMultiplier = state.equipment.
|
||||||
filter((item) => {
|
filter((item) => {
|
||||||
@@ -477,7 +477,7 @@ export const computeProjectedRunestones = (state: GameState): number => {
|
|||||||
: 1;
|
: 1;
|
||||||
const runestoneMult = gain1Mult * gain2Mult;
|
const runestoneMult = gain1Mult * gain2Mult;
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- optional chained game state field */
|
/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- optional chained game state field */
|
||||||
const echoMult: number = state.transcendence?.echoRunestoneMultiplier ?? 1;
|
const echoMult: number = state.transcendence?.echoPrestigeRunestoneMultiplier ?? 1;
|
||||||
return Math.floor(base * runestoneMult * echoMult);
|
return Math.floor(base * runestoneMult * echoMult);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user