88 lines
2.7 KiB
TypeScript
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;
|
|
}
|