generated from nhcarrigan/template
feat: add zone-scaled quest failure chance
Quests now have a random chance of failing when their timer expires. On failure the quest resets to available (no rewards lost, just time). Failure chance scales by zone: 10% in Verdant Vale up to 40% in the end-game zones. The Quest type gains lastFailedAt so the UI can show a warning on subsequent attempts.
This commit is contained in:
@@ -60,6 +60,9 @@ const QuestCard = ({ quest, partyCombatPower, unlockHint, zoneHint }: QuestCardP
|
|||||||
{!zoneHint && unlockHint && <p className="unlock-hint">📜 Complete: {unlockHint}</p>}
|
{!zoneHint && unlockHint && <p className="unlock-hint">📜 Complete: {unlockHint}</p>}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{quest.status === "available" && quest.lastFailedAt != null && (
|
||||||
|
<p className="quest-failed-hint">⚠️ Last attempt failed</p>
|
||||||
|
)}
|
||||||
{quest.status === "available" && (
|
{quest.status === "available" && (
|
||||||
<button
|
<button
|
||||||
className="start-quest-button"
|
className="start-quest-button"
|
||||||
|
|||||||
@@ -46,6 +46,32 @@ const checkAchievements = (state: GameState): Achievement[] => {
|
|||||||
/** Maximum value any resource can accumulate to. Beyond this JS floats lose all useful precision. */
|
/** Maximum value any resource can accumulate to. Beyond this JS floats lose all useful precision. */
|
||||||
export const RESOURCE_CAP = 1e300;
|
export const RESOURCE_CAP = 1e300;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Probability of quest failure per zone — scales from 10% (early game) to 40% (end game).
|
||||||
|
* On failure the quest resets to "available" with no rewards; the player must wait the
|
||||||
|
* full duration again on their next attempt.
|
||||||
|
*/
|
||||||
|
const ZONE_FAILURE_CHANCE: Record<string, number> = {
|
||||||
|
verdant_vale: 0.10,
|
||||||
|
shattered_ruins: 0.12,
|
||||||
|
frozen_peaks: 0.14,
|
||||||
|
shadow_marshes: 0.16,
|
||||||
|
volcanic_depths: 0.18,
|
||||||
|
astral_void: 0.20,
|
||||||
|
celestial_reaches: 0.22,
|
||||||
|
abyssal_trench: 0.24,
|
||||||
|
infernal_court: 0.26,
|
||||||
|
crystalline_spire: 0.28,
|
||||||
|
void_sanctum: 0.30,
|
||||||
|
eternal_throne: 0.32,
|
||||||
|
primordial_chaos: 0.34,
|
||||||
|
infinite_expanse: 0.36,
|
||||||
|
reality_forge: 0.38,
|
||||||
|
cosmic_maelstrom: 0.40,
|
||||||
|
primeval_sanctum: 0.40,
|
||||||
|
the_absolute: 0.40,
|
||||||
|
};
|
||||||
|
|
||||||
const capResource = (value: number): number => Math.min(value, RESOURCE_CAP);
|
const capResource = (value: number): number => Math.min(value, RESOURCE_CAP);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -128,6 +154,11 @@ export const applyTick = (state: GameState, deltaSeconds: number): GameState =>
|
|||||||
return quest;
|
return quest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const failureChance = ZONE_FAILURE_CHANCE[quest.zoneId] ?? 0.20;
|
||||||
|
if (Math.random() < failureChance) {
|
||||||
|
return { ...quest, status: "available" as const, startedAt: undefined, lastFailedAt: now };
|
||||||
|
}
|
||||||
|
|
||||||
for (const reward of quest.rewards) {
|
for (const reward of quest.rewards) {
|
||||||
if (reward.type === "gold" && reward.amount != null) {
|
if (reward.type === "gold" && reward.amount != null) {
|
||||||
questGold += reward.amount;
|
questGold += reward.amount;
|
||||||
|
|||||||
@@ -1156,6 +1156,14 @@ body {
|
|||||||
margin-top: 0.2rem;
|
margin-top: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quest-failed-hint {
|
||||||
|
color: #e6a817;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-style: italic;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.unlock-hint {
|
.unlock-hint {
|
||||||
color: var(--colour-text-muted);
|
color: var(--colour-text-muted);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
|
|||||||
@@ -25,4 +25,6 @@ export interface Quest {
|
|||||||
zoneId: string;
|
zoneId: string;
|
||||||
/** Minimum party combat power required to start this quest */
|
/** Minimum party combat power required to start this quest */
|
||||||
combatPowerRequired?: number;
|
combatPowerRequired?: number;
|
||||||
|
/** Unix timestamp of the most recent failed attempt (if any) */
|
||||||
|
lastFailedAt?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user