generated from nhcarrigan/template
feat: vampire mode chunk 3 - sync/sanitize and initial state
Add initialVampireState() and vampireSpread validation to mirror the goddess mode pattern. Also lint-fix pre-existing style issues across all Chunk 2 vampire data and type files.
This commit is contained in:
@@ -886,6 +886,175 @@ const validateAndSanitize = (
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Vampire state: preserve server-only currencies (ichor, soul shards, blood) at
|
||||
* previous values, and apply the same forward-only rules to bosses/quests/achievements
|
||||
* and exploration materials that the mortal and goddess realms use.
|
||||
* Blood income will be computed and allowed to grow once Chunk 7 adds vampire tick logic.
|
||||
*/
|
||||
// eslint-disable-next-line capitalized-comments -- v8 ignore
|
||||
/* v8 ignore next 154 -- @preserve */
|
||||
let vampireSpread: object = {};
|
||||
const previousVampire = previous.vampire;
|
||||
const incomingVampire = incoming.vampire;
|
||||
if (!incomingVampire && previousVampire) {
|
||||
vampireSpread = { vampire: previousVampire };
|
||||
} else if (incomingVampire) {
|
||||
const vampireBosses = incomingVampire.bosses.map((boss) => {
|
||||
const matchingBoss = previousVampire?.bosses.find((storedBoss) => {
|
||||
return storedBoss.id === boss.id;
|
||||
});
|
||||
if (!matchingBoss) {
|
||||
return boss;
|
||||
}
|
||||
if (matchingBoss.status === "defeated" && boss.status !== "defeated") {
|
||||
return { ...boss, currentHp: 0, status: "defeated" as const };
|
||||
}
|
||||
return boss;
|
||||
});
|
||||
const vampireQuests = incomingVampire.quests.map((quest) => {
|
||||
const matchingQuest = previousVampire?.quests.find((storedQuest) => {
|
||||
return storedQuest.id === quest.id;
|
||||
});
|
||||
if (!matchingQuest) {
|
||||
return quest;
|
||||
}
|
||||
// eslint-disable-next-line stylistic/max-len -- Long condition; splitting would reduce readability
|
||||
if (matchingQuest.status === "completed" && quest.status !== "completed") {
|
||||
return { ...matchingQuest };
|
||||
}
|
||||
return quest;
|
||||
});
|
||||
// eslint-disable-next-line stylistic/max-len -- Long variable name; splitting would reduce readability
|
||||
const vampireAchievements = incomingVampire.achievements.map((achievement) => {
|
||||
const matchingAchievement = previousVampire?.achievements.find(
|
||||
(storedAchievement) => {
|
||||
return storedAchievement.id === achievement.id;
|
||||
},
|
||||
);
|
||||
if (!matchingAchievement) {
|
||||
return achievement;
|
||||
}
|
||||
const wasUnlocked = matchingAchievement.unlockedAt !== null;
|
||||
const isNowNull = achievement.unlockedAt === null;
|
||||
if (wasUnlocked && isNowNull) {
|
||||
return { ...achievement, unlockedAt: matchingAchievement.unlockedAt };
|
||||
}
|
||||
const isFuture
|
||||
= achievement.unlockedAt !== null && achievement.unlockedAt > now;
|
||||
if (isFuture) {
|
||||
const safeUnlockedAt = matchingAchievement.unlockedAt ?? null;
|
||||
return { ...achievement, unlockedAt: safeUnlockedAt };
|
||||
}
|
||||
return achievement;
|
||||
});
|
||||
const previousVampireExploration = previousVampire?.exploration;
|
||||
let vampireExploration = incomingVampire.exploration;
|
||||
if (previousVampireExploration) {
|
||||
const previousMaterialMap = new Map(
|
||||
previousVampireExploration.materials.map((mat) => {
|
||||
return [ mat.materialId, mat.quantity ] as const;
|
||||
}),
|
||||
);
|
||||
// eslint-disable-next-line stylistic/max-len -- Long variable name; splitting would reduce readability
|
||||
const materials = incomingVampire.exploration.materials.map((material) => {
|
||||
const previousQuantity
|
||||
= previousMaterialMap.get(material.materialId) ?? 0;
|
||||
return {
|
||||
...material,
|
||||
quantity: Math.min(material.quantity, previousQuantity),
|
||||
};
|
||||
});
|
||||
const vampireRecipeIds = [
|
||||
...new Set([
|
||||
...previousVampireExploration.craftedRecipeIds,
|
||||
...incomingVampire.exploration.craftedRecipeIds,
|
||||
]),
|
||||
];
|
||||
vampireExploration = {
|
||||
...incomingVampire.exploration,
|
||||
// eslint-disable-next-line stylistic/max-len -- Long field name; splitting would reduce readability
|
||||
craftedBloodMultiplier: previousVampireExploration.craftedBloodMultiplier,
|
||||
// eslint-disable-next-line stylistic/max-len -- Long field name; splitting would reduce readability
|
||||
craftedCombatMultiplier: previousVampireExploration.craftedCombatMultiplier,
|
||||
// eslint-disable-next-line stylistic/max-len -- Long field name; splitting would reduce readability
|
||||
craftedIchorMultiplier: previousVampireExploration.craftedIchorMultiplier,
|
||||
craftedRecipeIds: vampireRecipeIds,
|
||||
materials: materials,
|
||||
};
|
||||
}
|
||||
const siring = previousVampire
|
||||
? {
|
||||
...incomingVampire.siring,
|
||||
count: Math.min(
|
||||
incomingVampire.siring.count,
|
||||
previousVampire.siring.count,
|
||||
),
|
||||
ichor: Math.min(
|
||||
incomingVampire.siring.ichor,
|
||||
previousVampire.siring.ichor,
|
||||
),
|
||||
productionMultiplier: previousVampire.siring.productionMultiplier,
|
||||
}
|
||||
: incomingVampire.siring;
|
||||
const awakening = previousVampire
|
||||
? {
|
||||
...incomingVampire.awakening,
|
||||
count: Math.min(
|
||||
incomingVampire.awakening.count,
|
||||
previousVampire.awakening.count,
|
||||
),
|
||||
soulShards: Math.min(
|
||||
incomingVampire.awakening.soulShards,
|
||||
previousVampire.awakening.soulShards,
|
||||
),
|
||||
// eslint-disable-next-line stylistic/max-len -- Long field name; splitting would reduce readability
|
||||
soulShardsBloodMultiplier: previousVampire.awakening.soulShardsBloodMultiplier,
|
||||
// eslint-disable-next-line stylistic/max-len -- Long field name; splitting would reduce readability
|
||||
soulShardsCombatMultiplier: previousVampire.awakening.soulShardsCombatMultiplier,
|
||||
// eslint-disable-next-line stylistic/max-len -- Long field name; splitting would reduce readability
|
||||
soulShardsMetaMultiplier: previousVampire.awakening.soulShardsMetaMultiplier,
|
||||
// eslint-disable-next-line stylistic/max-len -- Long field name; splitting would reduce readability
|
||||
soulShardsSiringIchorMultiplier: previousVampire.awakening.soulShardsSiringIchorMultiplier,
|
||||
// eslint-disable-next-line stylistic/max-len -- Long field name; splitting would reduce readability
|
||||
soulShardsSiringThresholdMultiplier: previousVampire.awakening.soulShardsSiringThresholdMultiplier,
|
||||
}
|
||||
: incomingVampire.awakening;
|
||||
vampireSpread = {
|
||||
vampire: {
|
||||
...incomingVampire,
|
||||
achievements: vampireAchievements,
|
||||
awakening: awakening,
|
||||
bosses: vampireBosses,
|
||||
eternalSovereignty: {
|
||||
count: Math.min(
|
||||
incomingVampire.eternalSovereignty.count,
|
||||
previousVampire?.eternalSovereignty.count ?? 0,
|
||||
),
|
||||
},
|
||||
exploration: vampireExploration,
|
||||
lifetimeBloodEarned: Math.min(
|
||||
incomingVampire.lifetimeBloodEarned,
|
||||
previousVampire?.lifetimeBloodEarned ?? 0,
|
||||
),
|
||||
lifetimeBossesDefeated: Math.min(
|
||||
incomingVampire.lifetimeBossesDefeated,
|
||||
previousVampire?.lifetimeBossesDefeated ?? 0,
|
||||
),
|
||||
lifetimeQuestsCompleted: Math.min(
|
||||
incomingVampire.lifetimeQuestsCompleted,
|
||||
previousVampire?.lifetimeQuestsCompleted ?? 0,
|
||||
),
|
||||
quests: vampireQuests,
|
||||
siring: siring,
|
||||
totalBloodEarned: Math.min(
|
||||
incomingVampire.totalBloodEarned,
|
||||
previousVampire?.totalBloodEarned ?? 0,
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...incoming,
|
||||
achievements,
|
||||
@@ -900,6 +1069,7 @@ const validateAndSanitize = (
|
||||
...storySpread,
|
||||
...dailyChallengesSpread,
|
||||
...goddessSpread,
|
||||
...vampireSpread,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user