Skip to content

Commit f27594d

Browse files
Refactoring the code,
1 parent d822df6 commit f27594d

13 files changed

Lines changed: 486 additions & 368 deletions

.gitignore

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
2-
__pycache__/ArtsInfo.cpython-38.pyc
3-
__pycache__/ocr.cpython-38.pyc
41
artifacts.genshinart.json
2+
ArtScanner/artifacts.dat
3+
ArtScanner/artifacts.dat.index
4+
ArtScanner/artifacts.dat.lock
5+
ArtScanner/artifacts.dat.tmp
6+
**/__pycache__/**
File renamed without changes.
File renamed without changes.
File renamed without changes.

datagen.py renamed to ArtScanner/Tools/datagen.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
import numpy as np
33
from PIL import ImageFont, Image, ImageDraw
4-
import ArtsInfo
4+
from .. import ArtsInfo
55

66
MainAttrDatabase = json.load(open('ReliquaryLevelExcelConfigData.json'))
77
SubAttrDatabase = json.load(open('ReliquaryAffixExcelConfigData.json'))
@@ -41,7 +41,7 @@ def generate_images(texts, font_size_range=(15, 40)):
4141
result.append(generate_image(text, font_size_range=font_size_range))
4242
return np.array(result)
4343

44-
fonts = {i:ImageFont.truetype("genshin2.ttf", i) for i in range(10,100)}
44+
fonts = {i:ImageFont.truetype("genshin.ttf", i) for i in range(10,100)}
4545
def generate_image(text, font_size_range=(15, 40)):
4646
pos = np.random.randint(0, 10), np.random.randint(0, 10)
4747
backcolor = (

ArtScanner/art_saver.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import ArtsInfo
2+
from utils import decodeValue
3+
import ZODB, ZODB.FileStorage
4+
import persistent, transaction
5+
from enum import IntEnum as Enum
6+
import json
7+
8+
class ArtifactType(Enum):
9+
FLOWER = 0
10+
PLUME = 1
11+
SANDS = 2
12+
GOBLET = 3
13+
CIRCLET = 4
14+
@classmethod
15+
def fromString(cls, s):
16+
try:
17+
return getattr(cls, s.upper())
18+
except:
19+
return cls(ArtsInfo.TypeNamesGenshinArt.index(s))
20+
21+
class ArtifactStatType(Enum):
22+
FIGHT_PROP_CRITICAL = 0
23+
FIGHT_PROP_CRITICAL_HURT = 1
24+
FIGHT_PROP_ATTACK = 2
25+
FIGHT_PROP_ATTACK_PERCENT = 3
26+
FIGHT_PROP_ELEMENT_MASTERY = 4
27+
FIGHT_PROP_CHARGE_EFFICIENCY = 5
28+
FIGHT_PROP_HP = 6
29+
FIGHT_PROP_HP_PERCENT = 7
30+
FIGHT_PROP_DEFENSE = 8
31+
FIGHT_PROP_DEFENSE_PERCENT = 9
32+
FIGHT_PROP_PHYSICAL_ADD_HURT = 10
33+
FIGHT_PROP_HEAL_ADD = 11
34+
FIGHT_PROP_ROCK_ADD_HURT = 12
35+
FIGHT_PROP_WIND_ADD_HURT = 13
36+
FIGHT_PROP_ICE_ADD_HURT = 14
37+
FIGHT_PROP_WATER_ADD_HURT = 15
38+
FIGHT_PROP_FIRE_ADD_HURT = 16
39+
FIGHT_PROP_ELEC_ADD_HURT = 17
40+
FIGHT_PROP_GRASS_ADD_HURT = 18
41+
FIGHT_PROP_FIRE_SUB_HURT = 19
42+
43+
class ArtifactStat:
44+
def __init__(self, name, value):
45+
name = ArtsInfo.AttrName2Ids[name]
46+
value = decodeValue(value)
47+
if type(value) == float and (name+'_PERCENT') in ArtsInfo.AttrNamesGensinArt:
48+
name += '_PERCENT'
49+
self.type = getattr(ArtifactStatType, name)
50+
self.value = value
51+
52+
class Artifact(persistent.Persistent):
53+
def __init__(self, info, image):
54+
'''
55+
info: dict with keys:
56+
'name': str, name of artifact
57+
'type': str, type of artifact
58+
'level': str/int, upgraded level of artifact, example: '+0', '0', 1
59+
'star': int, rarity, 1-5
60+
'main_attr_name': str, name of main stat
61+
'main_attr_value': str/int/float, main stat value, example: '38.5%', '4,760', 144
62+
'subattr_{i}': str, substat description, i could be 1-4, example: '暴击率+3.5%', '攻击力+130'
63+
image: PIL.Image, screenshot of the artifact, will be shrinked to 300x512 to save space
64+
'''
65+
typeid = ArtsInfo.TypeNames.index(info['type'])
66+
setid = [i for i,v in enumerate(ArtsInfo.ArtNames) if info['name'] in v][0]
67+
self.name = info['name']
68+
self.type = ArtifactType(typeid)
69+
self.setname = ArtsInfo.SetNamesGenshinArt[setid]
70+
self.level = decodeValue(info['level'])
71+
self.rarity = info['star']
72+
self.stat = ArtifactStat(info['main_attr_name'], info['main_attr_value'])
73+
self.substats = [ArtifactStat(*info[tag].split('+')) for tag in sorted(info.keys()) if "subattr_" in tag]
74+
self.image = image.resize((300, 512))
75+
76+
class ArtDatabase:
77+
def __init__(self, path='artifacts.dat'):
78+
self.storage = ZODB.FileStorage.FileStorage(path)
79+
self.db = ZODB.DB(self.storage)
80+
self.conn = self.db.open()
81+
self.root = self.conn.root()
82+
if 'size' not in self.root:
83+
self.root['size'] = 0
84+
85+
def add(self, info, art_img):
86+
try:
87+
self.root[str(self.root['size'])] = Artifact(info, art_img)
88+
self.root['size'] += 1
89+
transaction.commit()
90+
return True
91+
except Exception as e:
92+
raise
93+
return False
94+
95+
def exportGenshinArtJSON(self, path):
96+
result = {"version":"1", "flower":[], "feather":[], "sand":[], "cup":[], "head":[]}
97+
for art_id in range(self.root['size']):
98+
art = self.root[str(art_id)]
99+
result[ArtsInfo.TypeNamesGenshinArt[art.type]].append(
100+
{
101+
"setName": art.setname,
102+
"position": ArtsInfo.TypeNamesGenshinArt[art.type],
103+
"detailName": art.name,
104+
"mainTag": {
105+
'name': ArtsInfo.AttrNamesGensinArt[art.stat.type.name],
106+
'value': art.stat.value
107+
},
108+
"normalTags": [
109+
{
110+
'name': ArtsInfo.AttrNamesGensinArt[stat.type.name],
111+
'value': stat.value
112+
}
113+
for stat in art.substats
114+
],
115+
"omit": False,
116+
"id":art_id,
117+
'level': art.level,
118+
'star': art.rarity
119+
}
120+
)
121+
f = open(path, "wb")
122+
s = json.dumps(result, ensure_ascii=False)
123+
f.write(s.encode('utf-8'))
124+
f.close()

ArtScanner/art_scanner_logic.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
2+
import time
3+
import win32gui
4+
import mouse, math
5+
from utils import captureWindow
6+
7+
class GameInfo:
8+
def __init__(self, hwnd, force_rect=None):
9+
self.hwnd = hwnd
10+
self.w, self.h = win32gui.GetClientRect(self.hwnd)[2:]
11+
self.left, self.top = win32gui.ClientToScreen(self.hwnd, (0,0))
12+
13+
def calculateCoordinates(self):
14+
self.scale_ratio = min(self.w/2560, self.h/1440)
15+
16+
self.art_width = 164.39*self.scale_ratio
17+
self.art_height = 203.21*self.scale_ratio
18+
self.art_expand = 6*self.scale_ratio
19+
20+
self.art_gap_x = 30.89*self.scale_ratio
21+
self.art_gap_y = 30.35*self.scale_ratio
22+
23+
self.art_info_width = 656*self.scale_ratio
24+
self.art_info_height = 1119*self.scale_ratio
25+
26+
self.left_margin = (199.33 if self.w<2*self.h else 295.33)*self.scale_ratio
27+
self.right_margin = (871.33 if self.w<2*self.h else 967.33)*self.scale_ratio
28+
self.info_margin = 0 if self.w<2*self.h else 96*self.scale_ratio
29+
30+
self.art_cols = int(math.floor((self.w-self.left_margin-self.right_margin+self.art_gap_x)/(self.art_width+self.art_gap_x)))
31+
32+
self.art_shift = ((self.w-self.left_margin-self.right_margin+self.art_gap_x) - self.art_cols*(self.art_width+self.art_gap_x))/2
33+
34+
self.first_art_x = self.left_margin + self.art_shift
35+
self.first_art_y = 161*self.scale_ratio
36+
37+
self.art_info_top = 160*self.scale_ratio
38+
self.art_info_left = self.w-837*self.scale_ratio - self.info_margin
39+
# scroll_keypt_x = left_margin + art_shift + 158*scale_ratio
40+
# scroll_keypt_y = 1270*scale_ratio+h-1440*scale_ratio
41+
self.scroll_fin_keypt_x = self.left_margin + self.art_shift + 10*self.scale_ratio
42+
self.scroll_fin_keypt_y = 335*self.scale_ratio
43+
44+
self.art_rows = int(round((1270*self.scale_ratio+self.h-1440*self.scale_ratio-self.first_art_y+self.art_gap_y)/(self.art_height+self.art_gap_y)))
45+
self.incomplete_lastrow = (1270*self.scale_ratio+self.h-1440*self.scale_ratio-self.first_art_y+self.art_gap_y)/(self.art_height+self.art_gap_y)-self.art_rows<0.7
46+
47+
48+
49+
class ArtScannerLogic:
50+
def __init__(self, game_info):
51+
self.game_info = game_info
52+
self.stopped = False
53+
54+
def interrupt(self):
55+
self.stopped = True
56+
57+
58+
def waitSwitched(self, art_center_x, art_center_y, min_wait=0.1, max_wait=3):
59+
total_wait = 0
60+
while True:
61+
pix = captureWindow(self.game_info.hwnd, (
62+
art_center_x-self.game_info.art_width/2-self.game_info.art_expand,
63+
art_center_y,
64+
art_center_x-self.game_info.art_width/2-self.game_info.art_expand+1.5,
65+
art_center_y+1.5))
66+
if sum(pix.getpixel((0,0)))/3>200:
67+
return True
68+
else:
69+
time.sleep(min_wait)
70+
total_wait += min_wait
71+
if total_wait>max_wait:
72+
return False
73+
74+
def getArtCenter(self, row, col):
75+
art_center_x = self.game_info.first_art_x+(self.game_info.art_width+self.game_info.art_gap_x)*col+self.game_info.art_width/2
76+
art_center_y = self.game_info.first_art_y +(self.game_info.art_height+self.game_info.art_gap_y)*row+self.game_info.art_height/5
77+
return art_center_x, art_center_y
78+
79+
def scanRows(self, rows, callback):
80+
'''
81+
callback: function to take in artifact image and do what ever you want
82+
'''
83+
rows = list(rows)
84+
if len(rows)<1:
85+
return True
86+
art_center_x, art_center_y = self.getArtCenter(rows[0], 0)
87+
mouse.move(self.game_info.left+art_center_x, self.game_info.top+art_center_y)
88+
mouse.click()
89+
for art_row in rows:
90+
for art_col in range(self.game_info.art_cols):
91+
if self.stopped:
92+
return False
93+
if self.waitSwitched(art_center_x, art_center_y, min_wait=0.1, max_wait=3):
94+
art_img = captureWindow(self.game_info.hwnd, (
95+
self.game_info.art_info_left,
96+
self.game_info.art_info_top,
97+
self.game_info.art_info_left+self.game_info.art_info_width,
98+
self.game_info.art_info_top+self.game_info.art_info_height))
99+
if art_col==self.game_info.art_cols-1:
100+
art_row += 1
101+
art_col = 0
102+
else:
103+
art_col += 1
104+
if art_row in rows:
105+
art_center_x, art_center_y = self.getArtCenter(art_row, art_col)
106+
mouse.move(self.game_info.left+art_center_x, self.game_info.top+art_center_y)
107+
mouse.click()
108+
callback(art_img)
109+
else:
110+
return False
111+
return True
112+
113+
def alignFirstRow(self):
114+
mouse.move(self.game_info.left+self.game_info.first_art_x, self.game_info.top+self.game_info.first_art_y)
115+
pix = captureWindow(self.game_info.hwnd, (
116+
self.game_info.scroll_fin_keypt_x,
117+
self.game_info.scroll_fin_keypt_y,
118+
self.game_info.scroll_fin_keypt_x+1.5,
119+
self.game_info.scroll_fin_keypt_y+1.5))
120+
if pix.getpixel((0,0))[0]!=233 or pix.getpixel((0,0))[1]!=229 or pix.getpixel((0,0))[2]!=220:
121+
for _ in range(3):
122+
mouse.wheel(1)
123+
time.sleep(0.1)
124+
self.scrollToRow(0)
125+
126+
127+
def scrollToRow(self, target_row, max_scrolls=20, extra_scroll=0):
128+
in_between_row = False
129+
rows_scrolled = 0
130+
lines_scrolled = 0
131+
while True:
132+
pix = captureWindow(self.game_info.hwnd, (
133+
self.game_info.scroll_fin_keypt_x,
134+
self.game_info.scroll_fin_keypt_y,
135+
self.game_info.scroll_fin_keypt_x+1.5,
136+
self.game_info.scroll_fin_keypt_y+1.5))
137+
if pix.getpixel((0,0))[0]!=233 or pix.getpixel((0,0))[1]!=229 or pix.getpixel((0,0))[2]!=220:
138+
# if in_between_row==False:
139+
# print('到行之间了')
140+
in_between_row = True
141+
elif in_between_row:
142+
in_between_row = False
143+
rows_scrolled += 1
144+
lines_scrolled = 0
145+
# print(f'已翻{rows_scrolled}行')
146+
if rows_scrolled >= target_row:
147+
for _ in range(extra_scroll):
148+
mouse.wheel(-1)
149+
return rows_scrolled
150+
if lines_scrolled > max_scrolls:
151+
return rows_scrolled
152+
for _ in range(6 if lines_scrolled==0 and target_row>0 else 1):
153+
mouse.wheel(-1)
154+
lines_scrolled += 1
155+
# print('翻一下')
156+
time.sleep(0.05)

0 commit comments

Comments
 (0)