generated from nhcarrigan/template
feat: add equipment set bonuses and boss bounty runestones
- Define EquipmentSet type + computeSetBonuses utility in packages/types - Add setId field to Equipment type and assign sets to 27 equipment items - Create 9 named equipment sets (Iron Vanguard → Eternal Throne) with 2pc/3pc bonuses - Apply set combat multiplier in boss route - Apply set gold/click multipliers in tick engine and click handler - Include set bonuses in anti-cheat delta validation - Show active set bonus strip + set badge per card in EquipmentPanel - Add boss first-kill bounty runestones (scaling 1–10 per boss tier) - Update AboutPanel and IDEAS.md
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
import type { BossChallengeResponse, GameState } from "@elysium/types";
|
||||
import { computeSetBonuses } from "@elysium/types";
|
||||
import { Hono } from "hono";
|
||||
import type { HonoEnv } from "../types/hono.js";
|
||||
import { prisma } from "../db/client.js";
|
||||
import { DEFAULT_BOSSES } from "../data/bosses.js";
|
||||
import { DEFAULT_EQUIPMENT_SETS } from "../data/equipmentSets.js";
|
||||
import { authMiddleware } from "../middleware/auth.js";
|
||||
import { updateChallengeProgress } from "../services/dailyChallenges.js";
|
||||
|
||||
export const bossRouter = new Hono();
|
||||
export const bossRouter = new Hono<HonoEnv>();
|
||||
|
||||
bossRouter.use("*", authMiddleware);
|
||||
|
||||
@@ -25,6 +29,9 @@ const calculatePartyStats = (
|
||||
.filter((e) => e.equipped && e.bonus.combatMultiplier != null)
|
||||
.reduce((mult, e) => mult * (e.bonus.combatMultiplier ?? 1), 1);
|
||||
|
||||
const equippedItemIds = (state.equipment ?? []).filter((e) => e.equipped).map((e) => e.id);
|
||||
const setCombatMultiplier = computeSetBonuses(equippedItemIds, DEFAULT_EQUIPMENT_SETS).combatMultiplier;
|
||||
|
||||
let partyDPS = 0;
|
||||
let partyMaxHp = 0;
|
||||
|
||||
@@ -52,7 +59,7 @@ const calculatePartyStats = (
|
||||
partyMaxHp += adventurer.level * 50 * adventurer.count;
|
||||
}
|
||||
|
||||
partyDPS *= equipmentCombatMultiplier;
|
||||
partyDPS *= equipmentCombatMultiplier * setCombatMultiplier;
|
||||
|
||||
return { partyDPS, partyMaxHp };
|
||||
};
|
||||
@@ -185,12 +192,18 @@ bossRouter.post("/challenge", async (context) => {
|
||||
state.resources.crystals += crystalsAwarded;
|
||||
}
|
||||
|
||||
// First-kill bounty — look up authoritative bounty from static data
|
||||
const staticBoss = DEFAULT_BOSSES.find((b) => b.id === body.bossId);
|
||||
const bountyRunestones = staticBoss?.bountyRunestones ?? 0;
|
||||
state.prestige.runestones += bountyRunestones;
|
||||
|
||||
rewards = {
|
||||
gold: boss.goldReward,
|
||||
essence: boss.essenceReward,
|
||||
crystals: boss.crystalReward,
|
||||
upgradeIds: boss.upgradeRewards,
|
||||
equipmentIds: equipmentRewards,
|
||||
bountyRunestones,
|
||||
};
|
||||
} else {
|
||||
bossHpAtBattleEnd = Math.max(
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import type { GameState, SaveRequest } from "@elysium/types";
|
||||
import { computeSetBonuses } from "@elysium/types";
|
||||
import { createHmac } from "node:crypto";
|
||||
import { Hono } from "hono";
|
||||
import type { HonoEnv } from "../types/hono.js";
|
||||
import { prisma } from "../db/client.js";
|
||||
import { DEFAULT_ACHIEVEMENTS } from "../data/achievements.js";
|
||||
import { DEFAULT_ADVENTURERS } from "../data/adventurers.js";
|
||||
import { DEFAULT_BOSSES } from "../data/bosses.js";
|
||||
import { DEFAULT_EQUIPMENT } from "../data/equipment.js";
|
||||
import { DEFAULT_EQUIPMENT_SETS } from "../data/equipmentSets.js";
|
||||
import { DEFAULT_QUESTS } from "../data/quests.js";
|
||||
import { authMiddleware } from "../middleware/auth.js";
|
||||
import { getOrResetDailyChallenges } from "../services/dailyChallenges.js";
|
||||
@@ -44,6 +47,8 @@ const computeMaxPassiveIncome = (
|
||||
(mult, e) => mult * (e.bonus.goldMultiplier ?? 1),
|
||||
1,
|
||||
);
|
||||
const equippedItemIds = equippedItems.map((e) => e.id);
|
||||
const setGoldMultiplier = computeSetBonuses(equippedItemIds, DEFAULT_EQUIPMENT_SETS).goldMultiplier;
|
||||
const runestonesIncome = state.prestige.runestonesIncomeMultiplier ?? 1;
|
||||
const runestonesEssence = state.prestige.runestonesEssenceMultiplier ?? 1;
|
||||
|
||||
@@ -70,7 +75,8 @@ const computeMaxPassiveIncome = (
|
||||
upgradeMultiplier *
|
||||
prestige *
|
||||
runestonesIncome *
|
||||
equipmentGoldMultiplier;
|
||||
equipmentGoldMultiplier *
|
||||
setGoldMultiplier;
|
||||
|
||||
essencePerSecond +=
|
||||
adventurer.essencePerSecond *
|
||||
@@ -93,9 +99,11 @@ const computeMaxClickGoldPerSecond = (state: GameState): number => {
|
||||
.filter((u) => u.purchased && u.target === "click")
|
||||
.reduce((mult, u) => mult * u.multiplier, 1);
|
||||
|
||||
const equipmentClickMultiplier = (state.equipment ?? [])
|
||||
.filter((e) => e.equipped && e.bonus.clickMultiplier != null)
|
||||
const equippedItems = (state.equipment ?? []).filter((e) => e.equipped);
|
||||
const equipmentClickMultiplier = equippedItems
|
||||
.filter((e) => e.bonus.clickMultiplier != null)
|
||||
.reduce((mult, e) => mult * (e.bonus.clickMultiplier ?? 1), 1);
|
||||
const setClickMultiplier = computeSetBonuses(equippedItems.map((e) => e.id), DEFAULT_EQUIPMENT_SETS).clickMultiplier;
|
||||
|
||||
const runestonesClick = state.prestige.runestonesClickMultiplier ?? 1;
|
||||
|
||||
@@ -104,7 +112,8 @@ const computeMaxClickGoldPerSecond = (state: GameState): number => {
|
||||
clickMultiplier *
|
||||
state.prestige.productionMultiplier *
|
||||
runestonesClick *
|
||||
equipmentClickMultiplier;
|
||||
equipmentClickMultiplier *
|
||||
setClickMultiplier;
|
||||
|
||||
return clickPower * CLICK_BUFFER_CPS;
|
||||
};
|
||||
@@ -284,7 +293,7 @@ const validateAndSanitize = (incoming: GameState, previous: GameState): GameStat
|
||||
return { ...incoming, resources, bosses, quests, achievements, prestige };
|
||||
};
|
||||
|
||||
export const gameRouter = new Hono();
|
||||
export const gameRouter = new Hono<HonoEnv>();
|
||||
|
||||
gameRouter.use("*", authMiddleware);
|
||||
|
||||
@@ -610,8 +619,8 @@ gameRouter.post("/save", async (context) => {
|
||||
|
||||
await prisma.gameState.upsert({
|
||||
where: { discordId },
|
||||
create: { discordId, state: stateToSave, updatedAt: now },
|
||||
update: { state: stateToSave, updatedAt: now },
|
||||
create: { discordId, state: stateToSave as unknown as never, updatedAt: now },
|
||||
update: { state: stateToSave as unknown as never, updatedAt: now },
|
||||
});
|
||||
|
||||
const signature = secret ? computeHmac(JSON.stringify(stateToSave), secret) : undefined;
|
||||
|
||||
Reference in New Issue
Block a user