Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZO disc scanner #2598

Merged
merged 3 commits into from
Jan 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions libs/common/img-util/src/processing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function histogramAnalysis(
const height = imageData.height
const width = imageData.width
const p = imageData.data
return Array.from({ length: hori ? width : height }, (v, i) => {
return Array.from({ length: hori ? width : height }, (_, i) => {
let num = 0
for (let j = 0; j < (hori ? height : width); j++) {
const pixelIndex = hori
Expand Down Expand Up @@ -68,7 +68,7 @@ export function histogramContAnalysis(
const left = max * leftRange
const right = max * rightRange

return Array.from({ length: max }, (v, i) => {
return Array.from({ length: max }, (_, i) => {
if (i < left || i > right) return 0
let longest = 0
let num = 0
Expand Down Expand Up @@ -142,7 +142,7 @@ export function preprocessImage(pixelData: ImageData) {
}

// from https://github.com/processing/p5.js/blob/main/src/image/filters.js
function thresholdFilter(pixels: Uint8ClampedArray, level: number) {
export function thresholdFilter(pixels: Uint8ClampedArray, level: number) {
if (level === undefined) {
level = 0.5
}
Expand All @@ -162,7 +162,7 @@ function thresholdFilter(pixels: Uint8ClampedArray, level: number) {
}
}
// from https://css-tricks.com/manipulating-pixels-using-canvas/
function invertColors(pixels: Uint8ClampedArray) {
export function invertColors(pixels: Uint8ClampedArray) {
for (let i = 0; i < pixels.length; i += 4) {
pixels[i] = pixels[i] ^ 255 // Invert Red
pixels[i + 1] = pixels[i + 1] ^ 255 // Invert Green
Expand Down
7 changes: 5 additions & 2 deletions libs/zzz/consts/src/disc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ const subData = {
crit_dmg_: { B: 0.016, A: 0.032, S: 0.048 },
anomProf: { B: 3, A: 6, S: 9 },
} as const
export function getSubStatBaseVal(
export function getDiscSubStatBaseVal(
statKey: DiscSubStatKey,
rarity: DiscRarityKey
) {
Expand Down Expand Up @@ -140,9 +140,12 @@ const mainData = {
ether_dmg_: m123,
anomMas_: m123,
enerRegen_: { B: 0.2, A: 0.4, S: 0.6 },
impact: { B: 0.06, A: 0.12, S: 0.18 },
impact_: { B: 0.06, A: 0.12, S: 0.18 },
} as const

/**
* WARN: this only works for fully leveled discs
*/
frzyc marked this conversation as resolved.
Show resolved Hide resolved
export function getDiscMainStatVal(
rarity: DiscRarityKey,
mainStatKey: DiscMainStatKey,
Expand Down
49 changes: 26 additions & 23 deletions libs/zzz/db/src/Database/DataManagers/DiscDataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ export class DiscDataManager extends DataManager<
level,
slotKey,
mainStatKey,
substats: substats.map((substat) => ({
key: substat.key,
value: substat.value,
substats: substats.map(({ key, upgrades }) => ({
key,
upgrades,
})),
location,
lock,
Expand Down Expand Up @@ -238,7 +238,7 @@ export class DiscDataManager extends DataManager<
(substat, i) =>
!candidate.substats[i].key || // Candidate doesn't have anything on this slot
(substat.key === candidate.substats[i].key && // Or editor simply has better substat
substat.value >= candidate.substats[i].value)
substat.upgrades >= candidate.substats[i].upgrades)
)
)

Expand All @@ -254,15 +254,15 @@ export class DiscDataManager extends DataManager<
i // Has no extra roll
) =>
substat.key === candidate.substats[i].key &&
substat.value === candidate.substats[i].value
substat.upgrades === candidate.substats[i].upgrades
)
: substats.some(
(
substat,
i // Has extra rolls
) =>
candidate.substats[i].key
? substat.value > candidate.substats[i].value // Extra roll to existing substat
? substat.upgrades > candidate.substats[i].upgrades // Extra roll to existing substat
: substat.key // Extra roll to new substat
))
)
Expand All @@ -280,7 +280,7 @@ export class DiscDataManager extends DataManager<
candidate.substats.some(
(candidateSubstat) =>
substat.key === candidateSubstat.key && // Or same slot
substat.value === candidateSubstat.value
substat.upgrades === candidateSubstat.upgrades
)
)
)
Expand All @@ -297,11 +297,19 @@ export function validateDisc(
sortSubs = true
): IDisc | undefined {
if (!obj || typeof obj !== 'object') return undefined
const { setKey } = obj as IDisc
let { rarity, slotKey, level, mainStatKey, substats, location, lock, trash } =
obj as IDisc
let {
setKey,
rarity,
slotKey,
level,
mainStatKey,
substats,
location,
lock,
trash,
} = obj as IDisc

if (!allDiscSetKeys.includes(setKey)) return undefined // non-recoverable
if (!allDiscSetKeys.includes(setKey)) setKey = allDiscSetKeys[0]
if (!allDiscSlotKeys.includes(slotKey)) slotKey = '1'
if (!discSlotToMainStatKeys[slotKey].includes(mainStatKey))
mainStatKey = discSlotToMainStatKeys[slotKey][0]
Expand Down Expand Up @@ -344,23 +352,18 @@ function parseSubstats(
): ISubstat[] {
if (!Array.isArray(obj)) return []
const substats = (obj as ISubstat[])
.map(({ key = '', value = 0 }) => {
.map(({ key, upgrades = 0 }) => {
if (!key) return null
if (
!allDiscSubStatKeys.includes(key as DiscSubStatKey) ||
typeof value !== 'number' ||
!isFinite(value)
typeof upgrades !== 'number' ||
!Number.isFinite(upgrades)
)
return null
if (key) {
value = key.endsWith('_')
? Math.round(value * 1000) / 1000
: Math.round(value)
// TODO:
// const { low, high } = getSubstatRange(rarity, key)
// value = clamp(value, allowZeroSub ? 0 : low, high)
} else value = 0
return { key, value }

upgrades = Math.round(upgrades)

return { key, upgrades }
})
.filter(notEmpty) as ISubstat[]

Expand Down
9 changes: 1 addition & 8 deletions libs/zzz/db/src/Interfaces/IDbDisc.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import type { IDisc, ISubstat } from './IDisc'
import type { IDisc } from './IDisc'

export interface ICachedDisc extends IDisc {
id: string
mainStatVal: number
substats: ICachedSubstat[]
}

export interface ICachedSubstat extends ISubstat {
rolls: number
accurateValue: number
}
4 changes: 2 additions & 2 deletions libs/zzz/db/src/Interfaces/IDisc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import type {

export interface ISubstat {
key: DiscSubStatKey
value: number // TODO: should this be the # of rolls?
upgrades: number // This is the number of upgrades this sub receives.
}
export interface IDisc {
setKey: DiscSetKey
slotKey: DiscSlotKey
level: number
level: number // 0-15
rarity: DiscRarityKey
mainStatKey: DiscMainStatKey
location: string // TODO: CharacterKey
Expand Down
13 changes: 13 additions & 0 deletions libs/zzz/disc-scanner/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage",
"importSource": "@emotion/react"
}
]
],
"plugins": ["@emotion/babel-plugin"]
}
21 changes: 21 additions & 0 deletions libs/zzz/disc-scanner/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"extends": ["plugin:@nx/react", "../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"parserOptions": {
"project": "libs/zzz/disc-scanner/tsconfig.eslint.json"
},
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
7 changes: 7 additions & 0 deletions libs/zzz/disc-scanner/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# zzz-disc-scanner

This library was generated with [Nx](https://nx.dev).

## Running unit tests

Run `nx test zzz-disc-scanner` to execute the unit tests via [Jest](https://jestjs.io).
8 changes: 8 additions & 0 deletions libs/zzz/disc-scanner/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "zzz-disc-scanner",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/zzz/disc-scanner/src",
"projectType": "library",
"tags": [],
"targets": {}
}
1 change: 1 addition & 0 deletions libs/zzz/disc-scanner/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/ScanningQueue'
73 changes: 73 additions & 0 deletions libs/zzz/disc-scanner/src/lib/ScanningQueue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { Outstanding, Processed } from './processImg'
import { processEntry } from './processImg'

export type ScanningData = {
processedNum: number
outstandingNum: number
scanningNum: number
}
export type { Processed }

const maxProcessingCount = 3,
maxProcessedCount = 16

type textsFromImageFunc = (
imageData: ImageData,
options?: object | undefined
) => Promise<string[]>

export class ScanningQueue {
private debug: boolean
private textsFromImage: textsFromImageFunc
constructor(textsFromImage: textsFromImageFunc, debug = false) {
this.textsFromImage = textsFromImage
this.debug = debug
}
private processed = [] as Processed[]
private outstanding = [] as Outstanding[]
private scanning = [] as Promise<Processed>[]
callback = (() => {}) as (data: ScanningData) => void

addFiles(files: Outstanding[]) {
this.outstanding.push(...files)
this.processQueue()
}
processQueue() {
const numProcessing = Math.min(
maxProcessedCount - this.processed.length - this.scanning.length,
maxProcessingCount - this.scanning.length,
this.outstanding.length
)
numProcessing &&
this.outstanding.splice(0, numProcessing).map((p) => {
const prom = processEntry(p, this.textsFromImage, this.debug)
this.scanning.push(prom)
prom.then((procesResult) => {
const index = this.scanning.indexOf(prom)
if (index === -1) return // probably because the queue has been cleared.
this.scanning.splice(index, 1)
this.processed.push(procesResult)
this.processQueue()
})
})
this.callCB()
frzyc marked this conversation as resolved.
Show resolved Hide resolved
}
private callCB() {
this.callback({
processedNum: this.processed.length,
outstandingNum: this.outstanding.length,
scanningNum: this.scanning.length,
})
}
shiftProcessed(): Processed | undefined {
const procesesd = this.processed.shift()
if (procesesd) this.processQueue()
return procesesd
}
clearQueue() {
this.processed = []
this.outstanding = []
this.scanning = []
this.callCB()
}
}
14 changes: 14 additions & 0 deletions libs/zzz/disc-scanner/src/lib/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Color } from '@genshin-optimizer/common/img-util'

export const misreadCharactersInSubstatMap = [
{
pattern: /#/,
replacement: '+',
},
]

export const blackColor: Color = {
r: 0,
g: 0,
b: 0,
}
31 changes: 31 additions & 0 deletions libs/zzz/disc-scanner/src/lib/enStringMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Store the english translations to use with scanner

const elementalData: Record<string, string> = {
electric: 'Electric',
fire: 'Fire',
ice: 'Ice',
frost: 'Frost',
physical: 'Physical',
ether: 'Ether',
} as const

export const statMapEngMap = {
hp: 'HP',
hp_: 'HP',
atk: 'ATK',
atk_: 'ATK',
def: 'DEF',
def_: 'DEF',
pen: 'PEN',
pen_: 'PEN Ratio',
crit_: 'CRIT Rate',
crit_dmg_: 'CRIT DMG',
enerRegen_: 'Energy Regen',
impact_: 'Impact',
anomMas_: 'Anomaly Mastery',
anomProf: 'Anomaly Proficiency',
} as Record<string, string>

Object.entries(elementalData).forEach(([e, name]) => {
statMapEngMap[`${e}_dmg_`] = `${name} DMG Bonus`
})
Loading
Loading