generated from nhcarrigan/template
feat: initial elysium idle game prototype
Sets up the full monorepo with pnpm workspaces. Includes shared types package, Hono API with Discord OAuth/JWT auth, Prisma v6 + MongoDB Atlas, and React + Vite frontend with game loop, five tabs, and Discord-linked save/load.
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import type { GameState } from "@elysium/types";
|
||||
|
||||
/**
|
||||
* Pure function — applies one game tick to the state.
|
||||
* deltaSeconds: time elapsed since last tick.
|
||||
* Returns a new GameState (does not mutate the original).
|
||||
*/
|
||||
export const applyTick = (state: GameState, deltaSeconds: number): GameState => {
|
||||
let goldGained = 0;
|
||||
let essenceGained = 0;
|
||||
|
||||
for (const adventurer of state.adventurers) {
|
||||
if (!adventurer.unlocked || adventurer.count === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const upgradeMultiplier = state.upgrades
|
||||
.filter(
|
||||
(u) =>
|
||||
u.purchased &&
|
||||
(u.target === "global" ||
|
||||
(u.target === "adventurer" && u.adventurerId === adventurer.id)),
|
||||
)
|
||||
.reduce((mult, upgrade) => mult * upgrade.multiplier, 1);
|
||||
|
||||
const prestige = state.prestige.productionMultiplier;
|
||||
|
||||
goldGained +=
|
||||
adventurer.goldPerSecond * adventurer.count * upgradeMultiplier * prestige * deltaSeconds;
|
||||
|
||||
essenceGained +=
|
||||
adventurer.essencePerSecond *
|
||||
adventurer.count *
|
||||
upgradeMultiplier *
|
||||
prestige *
|
||||
deltaSeconds;
|
||||
}
|
||||
|
||||
// Complete active quests
|
||||
const now = Date.now();
|
||||
let questGold = 0;
|
||||
let questEssence = 0;
|
||||
let questCrystals = 0;
|
||||
|
||||
const updatedQuests = state.quests.map((quest) => {
|
||||
if (
|
||||
quest.status !== "active" ||
|
||||
quest.startedAt == null ||
|
||||
now < quest.startedAt + quest.durationSeconds * 1000
|
||||
) {
|
||||
return quest;
|
||||
}
|
||||
|
||||
const completed = { ...quest, status: "completed" as const };
|
||||
for (const reward of quest.rewards) {
|
||||
if (reward.type === "gold" && reward.amount != null) {
|
||||
questGold += reward.amount;
|
||||
} else if (reward.type === "essence" && reward.amount != null) {
|
||||
questEssence += reward.amount;
|
||||
} else if (reward.type === "crystals" && reward.amount != null) {
|
||||
questCrystals += reward.amount;
|
||||
}
|
||||
}
|
||||
return completed;
|
||||
});
|
||||
|
||||
const newGold = state.resources.gold + goldGained + questGold;
|
||||
const newEssence = state.resources.essence + essenceGained + questEssence;
|
||||
|
||||
return {
|
||||
...state,
|
||||
resources: {
|
||||
...state.resources,
|
||||
gold: newGold,
|
||||
essence: newEssence,
|
||||
crystals: state.resources.crystals + questCrystals,
|
||||
},
|
||||
player: {
|
||||
...state.player,
|
||||
totalGoldEarned: state.player.totalGoldEarned + goldGained + questGold,
|
||||
},
|
||||
quests: updatedQuests,
|
||||
lastTickAt: now,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the effective click power, including upgrades.
|
||||
*/
|
||||
export const calculateClickPower = (state: GameState): number => {
|
||||
const clickMultiplier = state.upgrades
|
||||
.filter((u) => u.purchased && u.target === "click")
|
||||
.reduce((mult, upgrade) => mult * upgrade.multiplier, 1);
|
||||
|
||||
return state.baseClickPower * clickMultiplier * state.prestige.productionMultiplier;
|
||||
};
|
||||
Reference in New Issue
Block a user