Files
genshin-assistant/src/lib/artifactStore.ts
T

88 lines
2.7 KiB
TypeScript

import type { ParsedArtifactCandidate } from "./artifactOcrParser.js";
import type { StoredArtifactRecord } from "../types/storage.js";
/**
* Signature used inside one scan session to detect that the detail panel
* actually changed after a click. Includes the equipped character so two
* otherwise identical pieces on different characters still count as new.
*/
export function sessionSignature(parsed: ParsedArtifactCandidate) {
return [
parsed.name,
parsed.slot,
parsed.level,
parsed.mainStat,
parsed.mainValue,
parsed.setName,
parsed.equipped,
parsed.substats.join("|"),
].join("::");
}
/**
* Signature used for the persistent store. Excludes the equipped character,
* so re-equipping an artifact updates the existing record instead of
* duplicating it. Level is part of the identity because the local DB now
* persists partially leveled pieces as distinct scan states.
*/
export function storeSignature(parsed: ParsedArtifactCandidate) {
return [
parsed.name,
parsed.slot,
parsed.level,
parsed.mainStat,
parsed.mainValue,
parsed.setName,
parsed.substats.join("|"),
].join("::");
}
export function hashId(value: string) {
let hash = 5381;
for (let index = 0; index < value.length; index++) {
hash = ((hash << 5) + hash + value.charCodeAt(index)) >>> 0;
}
return `${hash.toString(16).padStart(8, "0")}-${value.length.toString(16)}`;
}
export function toStoredArtifact(parsed: ParsedArtifactCandidate, source: string, needsReview: boolean, locked?: boolean): StoredArtifactRecord {
return {
id: hashId(storeSignature(parsed)),
name: parsed.name,
slot: parsed.slot,
level: parsed.level,
setName: parsed.setName,
mainStat: parsed.mainStat,
mainValue: parsed.mainValue,
substats: [...parsed.substats],
equipped: parsed.equipped,
confidence: parsed.confidence,
needsReview,
locked,
source,
};
}
export function isReviewOnlyArtifactSource(source: string | null | undefined) {
return /^review-/i.test(source ?? "");
}
export function storedArtifactStrength(record: StoredArtifactRecord | null | undefined) {
if (!record) return 0;
return (
(record.confidence ?? 0)
+ Math.min(16, (record.substats?.length ?? 0) * 4)
+ (record.needsReview ? -10 : 8)
+ (record.equipped && !/not detected/i.test(record.equipped) ? 3 : 0)
);
}
export function resolveStoredArtifactSource(existingSource: string | null | undefined, incomingSource: string | null | undefined) {
const existing = existingSource ?? "";
const incoming = incomingSource ?? "";
if (!incoming) return existing;
if (!existing) return incoming;
if (isReviewOnlyArtifactSource(incoming) && !isReviewOnlyArtifactSource(existing)) return existing;
return incoming;
}