generated from nhcarrigan/template
feat: vampire boss panel and thralls panel with combat simulation
Implements VampireBossPanel (zone filtering, HP bar, battle modal with rewards/casualties) and VampireThrallsPanel (batch buy with geometric cost scaling). Wires challengeVampireBoss, dismissVampireBattle, and buyVampireThrall into GameContext with correct sort-key ordering.
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
type GameState,
|
||||
type GoddessBossChallengeResponse,
|
||||
type GoddessExploreCollectResponse,
|
||||
type VampireBossChallengeResponse,
|
||||
type LoginBonusResult,
|
||||
type NumberFormat,
|
||||
type Quest,
|
||||
@@ -49,6 +50,7 @@ import {
|
||||
buyPrestigeUpgrade as buyPrestigeUpgradeApi,
|
||||
challengeBoss as challengeBossApi,
|
||||
challengeGoddessBoss as challengeGoddessBossApi,
|
||||
challengeVampireBoss as challengeVampireBossApi,
|
||||
collectExploration as collectExplorationApi,
|
||||
collectGoddessExploration as collectGoddessExplorationApi,
|
||||
consecrate as consecrateApi,
|
||||
@@ -768,6 +770,26 @@ interface GameContextValue {
|
||||
collectGoddessExploration: (
|
||||
areaId: string,
|
||||
)=> Promise<GoddessExploreCollectResponse>;
|
||||
|
||||
/**
|
||||
* Challenge a vampire boss — runs full server-side vampire combat simulation.
|
||||
*/
|
||||
challengeVampireBoss: (bossId: string)=> Promise<void>;
|
||||
|
||||
/**
|
||||
* Vampire battle result to display (null when no battle pending).
|
||||
*/
|
||||
vampireBattleResult: VampireBossChallengeResponse | null;
|
||||
|
||||
/**
|
||||
* Dismiss the vampire battle result modal.
|
||||
*/
|
||||
dismissVampireBattle: ()=> void;
|
||||
|
||||
/**
|
||||
* Buy one or more thralls (client-side blood deduction).
|
||||
*/
|
||||
buyVampireThrall: (thrallId: string, quantity: number)=> void;
|
||||
}
|
||||
|
||||
export interface BattleResult {
|
||||
@@ -820,6 +842,8 @@ export const GameProvider = ({
|
||||
const [ bossError, setBossError ] = useState<string | null>(null);
|
||||
const [ goddessBattleResult, setGoddessBattleResult ]
|
||||
= useState<GoddessBossChallengeResponse | null>(null);
|
||||
const [ vampireBattleResult, setVampireBattleResult ]
|
||||
= useState<VampireBossChallengeResponse | null>(null);
|
||||
const [ showConsecrationToast, setShowConsecrationToast ] = useState(false);
|
||||
const [ showEnlightenmentToast, setShowEnlightenmentToast ] = useState(false);
|
||||
const syncErrorTimerReference = useRef<ReturnType<typeof setTimeout> | null>(
|
||||
@@ -2085,6 +2109,115 @@ export const GameProvider = ({
|
||||
setGoddessBattleResult(null);
|
||||
}, []);
|
||||
|
||||
const challengeVampireBoss = useCallback(async(bossId: string) => {
|
||||
setVampireBattleResult(null);
|
||||
try {
|
||||
const result = await challengeVampireBossApi({ bossId });
|
||||
if (result.signature !== undefined) {
|
||||
signatureReference.current = result.signature;
|
||||
localStorage.setItem("elysium_save_signature", result.signature);
|
||||
}
|
||||
setState((previous) => {
|
||||
if (previous?.vampire === undefined) {
|
||||
return previous;
|
||||
}
|
||||
const updatedBosses = previous.vampire.bosses.map((boss) => {
|
||||
return boss.id === bossId
|
||||
? {
|
||||
...boss,
|
||||
currentHp: result.bossNewHp,
|
||||
status: result.won
|
||||
? ("defeated" as const)
|
||||
: ("available" as const),
|
||||
}
|
||||
: boss;
|
||||
});
|
||||
const updatedThralls = result.casualties === undefined
|
||||
? previous.vampire.thralls
|
||||
: previous.vampire.thralls.map((thrall) => {
|
||||
const casualty = result.casualties?.find((c) => {
|
||||
return c.thrallId === thrall.id;
|
||||
});
|
||||
return casualty === undefined
|
||||
? thrall
|
||||
: {
|
||||
...thrall,
|
||||
count: Math.max(0, thrall.count - casualty.killed),
|
||||
};
|
||||
});
|
||||
return {
|
||||
...previous,
|
||||
resources: {
|
||||
...previous.resources,
|
||||
blood: (previous.resources.blood ?? 0)
|
||||
+ (result.rewards?.blood ?? 0),
|
||||
},
|
||||
vampire: {
|
||||
...previous.vampire,
|
||||
awakening: {
|
||||
...previous.vampire.awakening,
|
||||
soulShards: previous.vampire.awakening.soulShards
|
||||
+ (result.rewards?.soulShards ?? 0),
|
||||
},
|
||||
bosses: updatedBosses,
|
||||
siring: {
|
||||
...previous.vampire.siring,
|
||||
ichor: previous.vampire.siring.ichor
|
||||
+ (result.rewards?.ichor ?? 0),
|
||||
},
|
||||
thralls: updatedThralls,
|
||||
},
|
||||
};
|
||||
});
|
||||
setVampireBattleResult(result);
|
||||
} catch (error_: unknown) {
|
||||
logError("challenge_vampire_boss", error_);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const dismissVampireBattle = useCallback(() => {
|
||||
setVampireBattleResult(null);
|
||||
}, []);
|
||||
|
||||
const buyVampireThrall = useCallback(
|
||||
(thrallId: string, quantity: number) => {
|
||||
setState((previous) => {
|
||||
if (previous?.vampire === undefined) {
|
||||
return previous;
|
||||
}
|
||||
const thrall = previous.vampire.thralls.find((t) => {
|
||||
return t.id === thrallId;
|
||||
});
|
||||
if (thrall === undefined) {
|
||||
return previous;
|
||||
}
|
||||
const geometric = thrall.baseCost * (1 - Math.pow(1.15, quantity));
|
||||
const normalised = geometric / (1 - 1.15);
|
||||
const totalCost = normalised * Math.pow(1.15, thrall.count);
|
||||
const currentBlood = previous.resources.blood ?? 0;
|
||||
if (currentBlood < totalCost) {
|
||||
return previous;
|
||||
}
|
||||
return {
|
||||
...previous,
|
||||
resources: {
|
||||
...previous.resources,
|
||||
blood: currentBlood - totalCost,
|
||||
},
|
||||
vampire: {
|
||||
...previous.vampire,
|
||||
thralls: previous.vampire.thralls.map((t) => {
|
||||
return t.id === thrallId
|
||||
? { ...t, count: t.count + quantity }
|
||||
: t;
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const consecrate = useCallback(async() => {
|
||||
try {
|
||||
const result = await consecrateApi({});
|
||||
@@ -3046,8 +3179,10 @@ export const GameProvider = ({
|
||||
buyGoddessUpgrade,
|
||||
buyPrestigeUpgrade,
|
||||
buyUpgrade,
|
||||
buyVampireThrall,
|
||||
challengeBoss,
|
||||
challengeGoddessBoss,
|
||||
challengeVampireBoss,
|
||||
collectExploration,
|
||||
collectGoddessExploration,
|
||||
completeChapter,
|
||||
@@ -3071,6 +3206,7 @@ export const GameProvider = ({
|
||||
dismissPrestigeToast,
|
||||
dismissStoryChapter,
|
||||
dismissTranscendenceToast,
|
||||
dismissVampireBattle,
|
||||
enableNotifications,
|
||||
enableSounds,
|
||||
enlighten,
|
||||
@@ -3124,6 +3260,7 @@ export const GameProvider = ({
|
||||
unlockedAchievements,
|
||||
unlockedCodexEntryIds,
|
||||
unlockedStoryChapterIds,
|
||||
vampireBattleResult,
|
||||
};
|
||||
}, [
|
||||
apotheosis,
|
||||
@@ -3141,8 +3278,10 @@ export const GameProvider = ({
|
||||
buyGoddessUpgrade,
|
||||
buyPrestigeUpgrade,
|
||||
buyUpgrade,
|
||||
buyVampireThrall,
|
||||
challengeBoss,
|
||||
challengeGoddessBoss,
|
||||
challengeVampireBoss,
|
||||
collectExploration,
|
||||
collectGoddessExploration,
|
||||
completeChapter,
|
||||
@@ -3166,6 +3305,7 @@ export const GameProvider = ({
|
||||
dismissPrestigeToast,
|
||||
dismissStoryChapter,
|
||||
dismissTranscendenceToast,
|
||||
dismissVampireBattle,
|
||||
enableNotifications,
|
||||
enableSounds,
|
||||
enlighten,
|
||||
@@ -3218,6 +3358,7 @@ export const GameProvider = ({
|
||||
unlockedAchievements,
|
||||
unlockedCodexEntryIds,
|
||||
unlockedStoryChapterIds,
|
||||
vampireBattleResult,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user