Add: daily_request_recognition; get_daily_rewards (#3)

* Add: daily_request_recognition; get_daily_rewards

* Fix: typo

* Fix: typo

* Upd: delete eval; use one loop to handle 5 active point rewards;

* Upd: change DAILY_QUEST_GOTO/REWARD pattern; update swipe private method name; extract keyword compare method

* Upd: move warning to single page recognition

* Upd: merge from main

* Add: methods that load daily quests keywords from QuestData.json

* Upd: avoid read TextMap twice

* Upd: revert Keyword.find method

* Add: preprocess of keyword extract; after_process of daily quest ocr

* Upd: move assets to daily/reward

* Upd: simplify ocr result replacement
This commit is contained in:
Hengyu 2023-06-13 00:24:38 +08:00 committed by GitHub
parent f12f40643b
commit 012fffbb31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 562 additions and 4 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -1,4 +1,5 @@
import os
import re
import typing as t
from functools import cached_property
@ -54,6 +55,19 @@ class TextMap:
return 0, ''
def replace_templates(text: str) -> str:
"""
Replace templates in data to make sure it equals to what is shown in game
Examples:
replace_templates("Complete Echo of War #4 time(s)")
== "Complete Echo of War 1 time(s)"
"""
text = re.sub(r'#4', '1', text)
text = re.sub(r'</?\w+>', '', text)
return text
class KeywordExtract:
def __init__(self):
self.text_map: dict[str, TextMap] = {lang: TextMap(lang) for lang in UI_LANGUAGES}
@ -90,10 +104,17 @@ class KeywordExtract:
with gen.Object(key=en, object_class=keyword_class):
gen.ObjectAttr(key='id', value=index + 1)
for lang in UI_LANGUAGES:
gen.ObjectAttr(key=lang, value=self.find_keyword(keyword, lang=lang)[1])
gen.ObjectAttr(key=lang, value=replace_templates(self.find_keyword(keyword, lang=lang)[1]))
gen.write(output_file)
def load_daily_quests_keywords(self, lang='cn'):
daily_quest = read_file(os.path.join(TextMap.DATA_FOLDER, 'ExcelOutput', 'DailyQuest.json'))
quest_data = read_file(os.path.join(TextMap.DATA_FOLDER, 'ExcelOutput', 'QuestData.json'))
quests_hash = [quest_data[quest_id]["QuestTitle"]["Hash"] for quest_id in daily_quest]
quest_keywords = [self.text_map[lang].find(quest_hash)[1] for quest_hash in quests_hash]
self.load_keywords(quest_keywords, lang)
def generate():
ex = KeywordExtract()
@ -101,6 +122,8 @@ def generate():
ex.write_keywords(keyword_class='DungeonNav', output_file='./tasks/dungeon/keywords/nav.py')
ex.load_keywords(['行动摘要', '生存索引', '每日实训'])
ex.write_keywords(keyword_class='DungeonTab', output_file='./tasks/dungeon/keywords/tab.py')
ex.load_daily_quests_keywords()
ex.write_keywords(keyword_class='DailyQuest', output_file='./tasks/daily/keywords/daily_quest.py')
if __name__ == '__main__':

View File

@ -6,7 +6,7 @@ from typing import ClassVar
from module.exception import ScriptError
import module.config.server as server
REGEX_PUNCTUATION = re.compile(r'[ ,.\'",。\-—/\\\n\t()]')
REGEX_PUNCTUATION = re.compile(r'[ ,.\'",。\-—/\\\n\t()「」『』【】]')
def parse_name(n):
@ -15,8 +15,9 @@ def parse_name(n):
def text_to_variable(text):
text = re.sub('[ \-]', '_', text)
text = re.sub('[()]', '', text)
text = re.sub(r'[ \-]', '_', text)
text = re.sub(r'[(),#]|</?\w+>|\'s', '', text)
text = re.sub(r'[#_]?\d+(_times?)?', '', text)
return text

View File

@ -0,0 +1,215 @@
from module.base.button import Button, ButtonWrapper
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.button_extract ```
ACTIVE_POINTS_1_CHECKED = ButtonWrapper(
name='ACTIVE_POINTS_1_CHECKED',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_1_CHECKED.png',
area=(377, 139, 424, 207),
search=(357, 119, 444, 227),
color=(223, 205, 185),
button=(377, 139, 424, 207),
),
)
ACTIVE_POINTS_1_LOCKED = ButtonWrapper(
name='ACTIVE_POINTS_1_LOCKED',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_1_LOCKED.png',
area=(378, 141, 423, 185),
search=(358, 121, 443, 205),
color=(222, 222, 222),
button=(378, 141, 423, 185),
),
)
ACTIVE_POINTS_1_UNLOCK = ButtonWrapper(
name='ACTIVE_POINTS_1_UNLOCK',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_1_UNLOCK.png',
area=(377, 141, 424, 185),
search=(357, 121, 444, 205),
color=(233, 178, 98),
button=(377, 141, 424, 185),
),
)
ACTIVE_POINTS_2_CHECKED = ButtonWrapper(
name='ACTIVE_POINTS_2_CHECKED',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_2_CHECKED.png',
area=(560, 141, 606, 205),
search=(540, 121, 626, 225),
color=(221, 202, 180),
button=(560, 141, 606, 205),
),
)
ACTIVE_POINTS_2_LOCKED = ButtonWrapper(
name='ACTIVE_POINTS_2_LOCKED',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_2_LOCKED.png',
area=(561, 141, 606, 185),
search=(541, 121, 626, 205),
color=(222, 222, 222),
button=(561, 141, 606, 185),
),
)
ACTIVE_POINTS_2_UNLOCK = ButtonWrapper(
name='ACTIVE_POINTS_2_UNLOCK',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_2_UNLOCK.png',
area=(561, 141, 606, 185),
search=(541, 121, 626, 205),
color=(244, 192, 99),
button=(561, 141, 606, 185),
),
)
ACTIVE_POINTS_3_CHECKED = ButtonWrapper(
name='ACTIVE_POINTS_3_CHECKED',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_3_CHECKED.png',
area=(743, 141, 788, 205),
search=(723, 121, 808, 225),
color=(221, 201, 179),
button=(743, 141, 788, 205),
),
)
ACTIVE_POINTS_3_LOCKED = ButtonWrapper(
name='ACTIVE_POINTS_3_LOCKED',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_3_LOCKED.png',
area=(743, 141, 788, 185),
search=(723, 121, 808, 205),
color=(222, 222, 222),
button=(743, 141, 788, 185),
),
)
ACTIVE_POINTS_3_UNLOCK = ButtonWrapper(
name='ACTIVE_POINTS_3_UNLOCK',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_3_UNLOCK.png',
area=(744, 141, 788, 185),
search=(724, 121, 808, 205),
color=(231, 176, 94),
button=(744, 141, 788, 185),
),
)
ACTIVE_POINTS_4_CHECKED = ButtonWrapper(
name='ACTIVE_POINTS_4_CHECKED',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_4_CHECKED.png',
area=(925, 141, 970, 205),
search=(905, 121, 990, 225),
color=(221, 201, 179),
button=(925, 141, 970, 205),
),
)
ACTIVE_POINTS_4_LOCKED = ButtonWrapper(
name='ACTIVE_POINTS_4_LOCKED',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_4_LOCKED.png',
area=(926, 141, 971, 185),
search=(906, 121, 991, 205),
color=(222, 222, 222),
button=(926, 141, 971, 185),
),
)
ACTIVE_POINTS_4_UNLOCK = ButtonWrapper(
name='ACTIVE_POINTS_4_UNLOCK',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_4_UNLOCK.png',
area=(926, 141, 971, 185),
search=(906, 121, 991, 205),
color=(237, 182, 97),
button=(926, 141, 971, 185),
),
)
ACTIVE_POINTS_5_CHECKED = ButtonWrapper(
name='ACTIVE_POINTS_5_CHECKED',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_5_CHECKED.png',
area=(1108, 141, 1154, 205),
search=(1088, 121, 1174, 225),
color=(221, 201, 179),
button=(1108, 141, 1154, 205),
),
)
ACTIVE_POINTS_5_LOCKED = ButtonWrapper(
name='ACTIVE_POINTS_5_LOCKED',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_5_LOCKED.png',
area=(1108, 141, 1153, 185),
search=(1088, 121, 1173, 205),
color=(222, 222, 222),
button=(1108, 141, 1153, 185),
),
)
ACTIVE_POINTS_5_UNLOCK = ButtonWrapper(
name='ACTIVE_POINTS_5_UNLOCK',
share=Button(
file='./assets/share/daily/reward/ACTIVE_POINTS_5_UNLOCK.png',
area=(1109, 141, 1154, 185),
search=(1089, 121, 1174, 205),
color=(237, 182, 96),
button=(1109, 141, 1154, 185),
),
)
DAILY_QUEST_FULL = ButtonWrapper(
name='DAILY_QUEST_FULL',
cn=Button(
file='./assets/cn/daily/reward/DAILY_QUEST_FULL.png',
area=(159, 546, 298, 564),
search=(139, 526, 318, 584),
color=(128, 111, 80),
button=(159, 546, 298, 564),
),
)
DAILY_QUEST_GOTO = ButtonWrapper(
name='DAILY_QUEST_GOTO',
share=Button(
file='./assets/share/daily/reward/DAILY_QUEST_GOTO.png',
area=(170, 544, 188, 563),
search=(150, 524, 208, 583),
color=(157, 156, 156),
button=(170, 544, 188, 563),
),
)
DAILY_QUEST_LEFT_START = ButtonWrapper(
name='DAILY_QUEST_LEFT_START',
share=Button(
file='./assets/share/daily/reward/DAILY_QUEST_LEFT_START.png',
area=(1100, 187, 1135, 625),
search=(1080, 167, 1155, 645),
color=(214, 214, 213),
button=(1100, 187, 1135, 625),
),
)
DAILY_QUEST_REWARD = ButtonWrapper(
name='DAILY_QUEST_REWARD',
share=Button(
file='./assets/share/daily/reward/DAILY_QUEST_REWARD.png',
area=(168, 544, 188, 564),
search=(148, 524, 208, 584),
color=(103, 83, 44),
button=(168, 544, 188, 564),
),
)
DAILY_QUEST_RIGHT_END = ButtonWrapper(
name='DAILY_QUEST_RIGHT_END',
share=Button(
file='./assets/share/daily/reward/DAILY_QUEST_RIGHT_END.png',
area=(142, 207, 175, 669),
search=(122, 187, 195, 689),
color=(219, 219, 218),
button=(142, 207, 175, 669),
),
)
OCR_DAILY_QUEST = ButtonWrapper(
name='OCR_DAILY_QUEST',
share=Button(
file='./assets/share/daily/reward/OCR_DAILY_QUEST.png',
area=(119, 232, 1165, 595),
search=(99, 212, 1185, 615),
color=(212, 210, 206),
button=(119, 232, 1165, 595),
),
)

128
tasks/daily/daily_quest.py Normal file
View File

@ -0,0 +1,128 @@
import numpy as np
from module.base.timer import Timer
from module.logger import *
from module.ocr.ocr import Ocr, OcrResultButton
from tasks.daily.assets.assets_daily_reward import *
from tasks.daily.keywords import DailyQuest
from tasks.dungeon.keywords import KEYWORDS_DUNGEON_TAB
from tasks.dungeon.ui import DungeonUI
class DailyQuestOcr(Ocr):
def __init__(self, button: ButtonWrapper, lang=None, name=None):
super().__init__(button, lang, name)
def after_process(self, result):
result = super().after_process(result)
if self.lang == 'ch':
result = result.replace("宇审", "宇宙")
result = result.replace("响J", "响」")
return result
class DailyQuestUI(DungeonUI):
def _ensure_position(self, direction: str, skip_first_screenshot=True):
interval = Timer(5)
if direction == 'left':
template = DAILY_QUEST_LEFT_START
elif direction == 'right':
template = DAILY_QUEST_RIGHT_END
else:
logger.warning(f'Unknown drag direction: {direction}')
return
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if self.appear(template):
logger.info(f"Ensure position: {direction}")
break
if interval.reached():
self._daily_quests_swipe(direction)
interval.reset()
continue
def _daily_quests_swipe(self, direction: str):
vector = np.random.uniform(0.65, 0.85)
if direction == 'left':
swipe_vector = (vector * OCR_DAILY_QUEST.width, 0)
elif direction == 'right':
swipe_vector = (-vector * OCR_DAILY_QUEST.width, 0)
else:
logger.warning(f'Unknown drag direction: {direction}')
return
self.device.swipe_vector(swipe_vector, box=OCR_DAILY_QUEST.button,
random_range=(-10, -10, 10, 10), name='DAILY_QUEST_DRAG')
def _ocr_single_page(self) -> list[OcrResultButton]:
ocr = DailyQuestOcr(OCR_DAILY_QUEST)
ocr.merge_thres_y = 20
results = ocr.matched_ocr(self.device.image, DailyQuest)
if len(results) < 4:
logger.warning(f"Recognition failed at {4 - len(results)} quests on one page")
return results
def daily_quests_recognition(self):
logger.info("Recognizing daily quests")
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Daily_Training)
self._ensure_position('left')
results = self._ocr_single_page()
self._ensure_position('right')
results += [result for result in self._ocr_single_page() if result not in results]
logger.info("Daily quests recognition complete")
return results
def _get_quest_reward(self, skip_first_screenshot=True):
self._ensure_position('left')
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if self.appear(DAILY_QUEST_FULL) or self.appear(DAILY_QUEST_GOTO):
break
if self.appear_then_click(DAILY_QUEST_REWARD):
continue
def _no_reward_to_get(self):
return (
(self.appear(ACTIVE_POINTS_1_LOCKED) or self.appear(ACTIVE_POINTS_1_CHECKED))
and (self.appear(ACTIVE_POINTS_2_LOCKED) or self.appear(ACTIVE_POINTS_2_CHECKED))
and (self.appear(ACTIVE_POINTS_3_LOCKED) or self.appear(ACTIVE_POINTS_3_CHECKED))
and (self.appear(ACTIVE_POINTS_4_LOCKED) or self.appear(ACTIVE_POINTS_4_CHECKED))
and (self.appear(ACTIVE_POINTS_5_LOCKED) or self.appear(ACTIVE_POINTS_5_CHECKED))
)
def _get_active_point_reward(self, skip_first_screenshot=True):
while 1:
if skip_first_screenshot:
skip_first_screenshot = False
else:
self.device.screenshot()
if self._no_reward_to_get():
break
if self.handle_reward():
continue
if self.appear_then_click(ACTIVE_POINTS_1_UNLOCK):
continue
if self.appear_then_click(ACTIVE_POINTS_2_UNLOCK):
continue
if self.appear_then_click(ACTIVE_POINTS_3_UNLOCK):
continue
if self.appear_then_click(ACTIVE_POINTS_4_UNLOCK):
continue
if self.appear_then_click(ACTIVE_POINTS_5_UNLOCK):
continue
def get_daily_rewards(self):
self.dungeon_tab_goto(KEYWORDS_DUNGEON_TAB.Daily_Training)
logger.info("Getting quest rewards")
self._get_quest_reward()
logger.info("Getting active point rewards")
self._get_active_point_reward()
logger.info("All daily reward got")

View File

@ -0,0 +1,2 @@
import tasks.daily.keywords.daily_quest as KEYWORDS_DAILY_QUEST
from tasks.daily.keywords.classes import DailyQuest

View File

@ -0,0 +1,9 @@
from dataclasses import dataclass
from typing import ClassVar
from module.ocr.keyword import Keyword
@dataclass
class DailyQuest(Keyword):
instances: ClassVar = {}

View File

@ -0,0 +1,180 @@
from .classes import DailyQuest
# This file was auto-generated, do not modify it manually. To generate:
# ``` python -m dev_tools.keyword_extract ```
Complete_Daily_Mission = DailyQuest(
id=1,
cn='完成1个日常任务',
cht='完成1個每日任務',
en='Complete 1 Daily Mission',
jp='デイリークエストを1回クリアする',
)
Clear_Calyx_Golden = DailyQuest(
id=2,
cn='完成1次「拟造花萼',
cht='完成1次「擬造花萼',
en='Clear Calyx (Golden) 1 time(s)',
jp='「疑似花萼」を1回クリアする',
)
Complete_Calyx_Crimson = DailyQuest(
id=3,
cn='完成1次「拟造花萼',
cht='完成1次「擬造花萼',
en='Complete Calyx (Crimson) 1 time',
jp='「疑似花萼」を1回クリアする',
)
Clear_Stagnant_Shadow = DailyQuest(
id=4,
cn='完成1次「凝滞虚影」',
cht='完成1次「凝滯虛影」',
en='Clear Stagnant Shadow 1 time(s)',
jp='「凝結虚影」を1回クリアする',
)
Clear_Cavern_of_Corrosion = DailyQuest(
id=5,
cn='完成1次「侵蚀隧洞」',
cht='完成1次「侵蝕隧洞」',
en='Clear Cavern of Corrosion 1 time(s)',
jp='「侵蝕トンネル」を1回クリアする',
)
In_a_single_battle_inflict_Weakness_Break_of_different_Types = DailyQuest(
id=6,
cn='单场战斗中触发3种不同属性的弱点击破',
cht='單場戰鬥中觸發3種不同屬性的弱點擊破',
en='In a single battle, inflict 3 Weakness Break of different Types',
jp='一度の戦闘で、異なる3種の属性の弱点撃破を発動する',
)
Inflict_Weakness_Break = DailyQuest(
id=7,
cn='累计触发弱点击破效果5次',
cht='累積觸發弱點擊破效果5次',
en='Inflict Weakness Break 5 times',
jp='累計で弱点撃破効果を5回発動する',
)
Defeat_a_total_of_enemies = DailyQuest(
id=8,
cn='累计消灭20个敌人',
cht='累積消滅20個敵人',
en='Defeat a total of 20 enemies',
jp='敵を累計で20体倒す',
)
Enter_combat_by_attacking_enemy_Weakness_and_win = DailyQuest(
id=9,
cn='利用弱点进入战斗并获胜3次',
cht='利用弱點進入戰鬥並獲勝3次',
en="Enter combat by attacking enemy's Weakness and win 3 times",
jp='弱点を攻撃して戦闘に入り、3回勝利する',
)
Use_Technique = DailyQuest(
id=10,
cn='累计施放2次秘技',
cht='累計施放2次秘技',
en='Use Technique 2 times',
jp='秘技を累計2回発動する',
)
Go_on_assignment = DailyQuest(
id=11,
cn='派遣1次委托',
cht='派遣1次委託',
en='Go on assignment 1 time',
jp='依頼に1回派遣する',
)
Take_photo = DailyQuest(
id=12,
cn='拍照1次',
cht='拍照1次',
en='Take 1 photo',
jp='1回撮影する',
)
Destroy_destructible_objects = DailyQuest(
id=13,
cn='累计击碎3个可破坏物',
cht='累計擊碎3個可破壞物',
en='Destroy 3 destructible objects',
jp='破壊できるオブジェクトを累計で3つ破壊する',
)
Complete_Forgotten_Hall = DailyQuest(
id=14,
cn='完成1次「忘却之庭」',
cht='完成1次「忘卻之庭」',
en='Complete Forgotten Hall 1 time',
jp='「忘却の庭」を1回クリアする',
)
Complete_Echo_of_War = DailyQuest(
id=15,
cn='完成1次「历战余响」',
cht='完成1次「歷戰餘響」',
en='Complete Echo of War 1 time(s)',
jp='「歴戦余韻」を1回クリアする',
)
Complete_stage_in_Simulated_Universe_Any_world = DailyQuest(
id=16,
cn='通关「模拟宇宙」任意世界的1个区域',
cht='完成「模擬宇宙」任意世界的1個區域',
en='Complete 1 stage in Simulated Universe (Any world)',
jp='「模擬宇宙」のエリアを1つクリアする任意の世界',
)
Obtain_victory_in_combat_with_support_characters = DailyQuest(
id=17,
cn='使用支援角色并获得战斗胜利1次',
cht='使用支援角色並獲得戰鬥勝利1次',
en='Obtain victory in combat with support characters 1 time',
jp='サポートキャラを使い、戦闘に1回勝利する',
)
Use_an_Ultimate_to_deal_the_final_blow = DailyQuest(
id=18,
cn='施放终结技造成制胜一击1次',
cht='施放終結技造成制勝一擊1次',
en='Use an Ultimate to deal the final blow 1 time',
jp='必殺技で最後の一撃を1回与える',
)
Level_up_any_character = DailyQuest(
id=19,
cn='将任意角色等级提升1次',
cht='將任意角色等級提升1次',
en='Level up any character 1 time',
jp='任意のキャラを1回レベルアップする',
)
Level_up_any_Light_Cone = DailyQuest(
id=20,
cn='将任意光锥等级提升1次',
cht='將任意光錐等級提升1次',
en='Level up any Light Cone 1 time',
jp='任意の光円錐を1回レベルアップする',
)
Level_up_any_Relic = DailyQuest(
id=21,
cn='将任意遗器等级提升1次',
cht='將任意遺器等級提升1次',
en='Level up any Relic 1 time',
jp='任意の遺物を1回レベルアップする',
)
Salvage_any_Relic = DailyQuest(
id=22,
cn='分解任意1件遗器',
cht='分解任意1件遺器',
en='Salvage any Relic',
jp='任意の遺物1つを分解する',
)
Synthesize_Consumable = DailyQuest(
id=23,
cn='合成1次消耗品',
cht='合成1次消耗品',
en='Synthesize Consumable 1 time',
jp='消耗品を1回合成する',
)
Synthesize_material = DailyQuest(
id=24,
cn='合成1次材料',
cht='合成1次素材',
en='Synthesize material 1 time',
jp='素材を1回合成する',
)
Use_Consumables = DailyQuest(
id=25,
cn='使用1件消耗品',
cht='使用1件消耗品',
en='Use Consumables 1 time',
jp='消耗品を1個使う',
)