generated from nhcarrigan/template
fix: resolve all 8 open bug tickets (#242–#249)
- #242: use formatNumber for crystals in resource bar to respect notation setting - #243: include current-run gold in companion unlock progress (client + server) - #244: skip rendering empty reward spans for zero-crystal quest rewards - #245/#248: skip auto-save while auto-prestige is in-flight to prevent optimistic lock collision - #246: generate and upload CDN images for crystal_pulse, crystal_surge, crystal_tempest upgrades - #247: merge daily challenge progress in validateAndSanitize (take max of client vs server) to prevent stale auto-saves rolling back server-side completions - #249: clear cached signature after buying prestige upgrade to prevent mismatch on next save
This commit is contained in:
@@ -163,7 +163,8 @@ const CompanionPanel = (): JSX.Element => {
|
||||
const progressByUnlockType: Record<string, number> = {
|
||||
apotheosis: state.apotheosis?.count ?? 0,
|
||||
lifetimeBosses: state.player.lifetimeBossesDefeated,
|
||||
lifetimeGold: state.player.lifetimeGoldEarned,
|
||||
// eslint-disable-next-line stylistic/max-len -- Long expression; splitting would reduce readability
|
||||
lifetimeGold: state.player.lifetimeGoldEarned + state.player.totalGoldEarned,
|
||||
lifetimeQuests: state.player.lifetimeQuestsCompleted,
|
||||
prestige: state.prestige.count,
|
||||
transcendence: state.transcendence?.count ?? 0,
|
||||
|
||||
@@ -114,6 +114,9 @@ const QuestCard = ({
|
||||
}
|
||||
<div className="quest-rewards">
|
||||
{quest.rewards.map((reward, rewardIndex) => {
|
||||
if (reward.type === "crystals" && (reward.amount ?? 0) === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<span className="reward-tag" key={`${reward.type}-${reward.targetId ?? String(reward.amount ?? rewardIndex)}`}>
|
||||
{reward.type === "gold"
|
||||
@@ -121,7 +124,6 @@ const QuestCard = ({
|
||||
{reward.type === "essence"
|
||||
&& `✨ ${formatNumber(reward.amount ?? 0)}`}
|
||||
{reward.type === "crystals"
|
||||
&& (reward.amount ?? 0) > 0
|
||||
&& `💎 ${formatNumber(reward.amount ?? 0)}`}
|
||||
{reward.type === "upgrade" && "🔓 Upgrade"}
|
||||
{reward.type === "adventurer" && "👥 New Adventurer"}
|
||||
|
||||
@@ -218,7 +218,7 @@ const ResourceBar = ({
|
||||
: ""}`}>
|
||||
<span className="resource-icon">{"💎"}</span>
|
||||
<span className="resource-value">
|
||||
{formatInteger(crystals)}
|
||||
{formatNumber(crystals)}
|
||||
</span>
|
||||
<span className="resource-label">{"Crystals"}</span>
|
||||
{crystalsFull
|
||||
|
||||
@@ -1356,10 +1356,11 @@ export const GameProvider = ({
|
||||
newlyFailedQuestsReference.current = [];
|
||||
}
|
||||
|
||||
// Auto-save every 30 seconds (skip if a force sync is in-flight to avoid signature collisions)
|
||||
// Auto-save every 30 seconds (skip if a force sync or auto-prestige is in-flight to avoid signature collisions)
|
||||
if (Date.now() - lastSaveReference.current >= autoSaveIntervalMs) {
|
||||
lastSaveReference.current = Date.now();
|
||||
if (stateReference.current !== null && !isSyncingReference.current) {
|
||||
// eslint-disable-next-line stylistic/max-len -- Long condition; splitting would reduce readability
|
||||
if (stateReference.current !== null && !isSyncingReference.current && !isAutoPrestigingReference.current) {
|
||||
void saveGame({
|
||||
state: stateReference.current,
|
||||
...signatureReference.current === null
|
||||
@@ -1856,6 +1857,13 @@ export const GameProvider = ({
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
/*
|
||||
* Buying a prestige upgrade mutates DB state; clear the cached signature
|
||||
* so the next auto-save doesn't collide with a stale one.
|
||||
*/
|
||||
signatureReference.current = null;
|
||||
localStorage.removeItem("elysium_save_signature");
|
||||
} catch (error_: unknown) {
|
||||
logError("buy_prestige_upgrade", error_);
|
||||
// Silently ignore — server errors shouldn't crash the UI
|
||||
|
||||
Reference in New Issue
Block a user