├── util
├── __init__.py
├── story_util.py
├── train_visitor_util.py
├── message_util.py
└── common.py
├── img
└── fandom.png
├── data
├── html_entities.xlsx
├── dialogues
│ ├── CHS
│ │ ├── messages.jsonl
│ │ ├── story.jsonl
│ │ └── train_visitor.jsonl
│ └── EN
│ │ ├── messages.jsonl
│ │ ├── story.jsonl
│ │ └── train_visitor.jsonl
└── misc
│ └── CHS
│ └── books.jsonl
├── statistics.py
├── get_dialogues.py
├── .gitignore
├── get_missions.py
├── get_misc.py
└── README.md
/util/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/img/fandom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrzjy/StarrailDialog/HEAD/img/fandom.png
--------------------------------------------------------------------------------
/data/html_entities.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrzjy/StarrailDialog/HEAD/data/html_entities.xlsx
--------------------------------------------------------------------------------
/statistics.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import glob
3 | import os
4 |
5 | if __name__ == "__main__":
6 | parser = argparse.ArgumentParser()
7 | parser.add_argument(
8 | "--repo",
9 | default="data",
10 | type=str,
11 | required=True,
12 | help="dir for extracted data",
13 | )
14 | args = parser.parse_args()
15 |
16 | for file in glob.iglob(os.path.join(args.repo, "dialogues"))
--------------------------------------------------------------------------------
/util/story_util.py:
--------------------------------------------------------------------------------
1 | import glob
2 | import json
3 | import os
4 |
5 | from util.common import parse_on_sequence, merge_on_sequence_sessions
6 |
7 |
8 | def get_story(
9 | repo: str,
10 | lang: str,
11 | map_hash_to_text: dict[str, str],
12 | map_sent_id_to_hash_info: dict[str, dict],
13 | max_count=-1,
14 | ):
15 | sessions = []
16 | for file in glob.iglob(
17 | os.path.join(repo, "Story/Mission/*/*.json"),
18 | recursive=True,
19 | ):
20 | with open(file, "r", encoding="utf-8") as f:
21 | info = json.load(f)
22 | sessions.extend(
23 | parse_on_sequence(info, map_hash_to_text, map_sent_id_to_hash_info)
24 | )
25 |
26 | output_dir = os.path.join("data", "dialogues", lang)
27 | os.makedirs(output_dir, exist_ok=True)
28 | output_path = os.path.join(output_dir, "story.jsonl")
29 | with open(output_path, "w", encoding="utf-8") as f:
30 | merged_sessions = merge_on_sequence_sessions(sessions)
31 | for count, merged_session in enumerate(merged_sessions):
32 | print(json.dumps(merged_session, ensure_ascii=False), file=f)
33 | if count + 1 >= max_count > 0:
34 | break
35 | print("Story: Total num of dialogues:", count)
36 |
--------------------------------------------------------------------------------
/util/train_visitor_util.py:
--------------------------------------------------------------------------------
1 | import glob
2 | import json
3 | import os
4 |
5 | from util.common import parse_on_sequence, merge_on_sequence_sessions
6 |
7 |
8 | def get_train_visitor(
9 | repo: str,
10 | lang: str,
11 | map_hash_to_text: dict[str, str],
12 | map_sent_id_to_hash_info: dict[str, dict],
13 | max_count=-1,
14 | ):
15 | sessions = []
16 | for file in glob.iglob(
17 | os.path.join(repo, "Config/Level/Mission/TrainVisitor/Act/*.json"),
18 | recursive=True,
19 | ):
20 | with open(file, "r", encoding="utf-8") as f:
21 | info = json.load(f)
22 | sessions.extend(
23 | parse_on_sequence(info, map_hash_to_text, map_sent_id_to_hash_info)
24 | )
25 |
26 | output_dir = os.path.join("data", "dialogues", lang)
27 | os.makedirs(output_dir, exist_ok=True)
28 | output_path = os.path.join(output_dir, "train_visitor.jsonl")
29 | with open(output_path, "w", encoding="utf-8") as f:
30 | merged_sessions = merge_on_sequence_sessions(sessions)
31 | for count, merged_session in enumerate(merged_sessions):
32 | print(json.dumps(merged_session, ensure_ascii=False), file=f)
33 | if count + 1 >= max_count > 0:
34 | break
35 | print("Train_visitor: Total num of dialogues:", count)
36 |
--------------------------------------------------------------------------------
/get_dialogues.py:
--------------------------------------------------------------------------------
1 | import argparse
2 |
3 | from util.common import load_text_hash_map, load_sentence_map
4 | from util.message_util import get_message
5 | from util.story_util import get_story
6 | from util.train_visitor_util import get_train_visitor
7 |
8 | if __name__ == "__main__":
9 | parser = argparse.ArgumentParser()
10 | parser.add_argument(
11 | "--repo",
12 | default="../StarRailData",
13 | type=str,
14 | required=True,
15 | help="data dir",
16 | )
17 | parser.add_argument("--lang", default="CHS", type=str, help="language type")
18 | parser.add_argument("--max_count", default=-1, type=str, help="max_count")
19 | args = parser.parse_args()
20 |
21 | map_hash_to_text = load_text_hash_map(args.repo, args.lang)
22 | map_hash_to_text["371857150"] = "{NICKNAME}"
23 |
24 | map_sent_id_to_hash_info = load_sentence_map(args.repo)
25 |
26 | get_message(args.repo, args.lang, map_hash_to_text, max_count=args.max_count)
27 | get_train_visitor(
28 | args.repo,
29 | args.lang,
30 | map_hash_to_text,
31 | map_sent_id_to_hash_info,
32 | max_count=args.max_count,
33 | )
34 | get_story(
35 | args.repo,
36 | args.lang,
37 | map_hash_to_text,
38 | map_sent_id_to_hash_info,
39 | max_count=args.max_count,
40 | )
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | tmp.py
11 | tmp/
12 | .Python
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | share/python-wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 | MANIFEST
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .nox/
45 | .coverage
46 | .coverage.*
47 | .cache
48 | nosetests.xml
49 | coverage.xml
50 | *.cover
51 | *.py,cover
52 | .hypothesis/
53 | .pytest_cache/
54 | cover/
55 |
56 | # Translations
57 | *.mo
58 | *.pot
59 |
60 | # Django stuff:
61 | *.log
62 | local_settings.py
63 | db.sqlite3
64 | db.sqlite3-journal
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | .pybuilder/
78 | target/
79 |
80 | # Jupyter Notebook
81 | .ipynb_checkpoints
82 |
83 | # IPython
84 | profile_default/
85 | ipython_config.py
86 |
87 | # pyenv
88 | # For a library or package, you might want to ignore these files since the code is
89 | # intended to run in multiple environments; otherwise, check them in:
90 | # .python-version
91 |
92 | # pipenv
93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
96 | # install all needed dependencies.
97 | #Pipfile.lock
98 |
99 | # poetry
100 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
101 | # This is especially recommended for binary packages to ensure reproducibility, and is more
102 | # commonly ignored for libraries.
103 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
104 | #poetry.lock
105 |
106 | # pdm
107 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
108 | #pdm.lock
109 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
110 | # in version control.
111 | # https://pdm.fming.dev/#use-with-ide
112 | .pdm.toml
113 |
114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115 | __pypackages__/
116 |
117 | # Celery stuff
118 | celerybeat-schedule
119 | celerybeat.pid
120 |
121 | # SageMath parsed files
122 | *.sage.py
123 |
124 | # Environments
125 | .env
126 | .venv
127 | env/
128 | venv/
129 | ENV/
130 | env.bak/
131 | venv.bak/
132 |
133 | # Spyder project settings
134 | .spyderproject
135 | .spyproject
136 |
137 | # Rope project settings
138 | .ropeproject
139 |
140 | # mkdocs documentation
141 | /site
142 |
143 | # mypy
144 | .mypy_cache/
145 | .dmypy.json
146 | dmypy.json
147 |
148 | # Pyre type checker
149 | .pyre/
150 |
151 | # pytype static type analyzer
152 | .pytype/
153 |
154 | # Cython debug symbols
155 | cython_debug/
156 |
157 | # PyCharm
158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160 | # and can be added to the global gitignore or merged into this file. For a more nuclear
161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
162 | .idea/
163 |
--------------------------------------------------------------------------------
/get_missions.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import copy
3 | import glob
4 | import json
5 | import os
6 | import re
7 |
8 | from util.common import text_normalization, load_text_hash_map, get_stable_hash
9 |
10 |
11 | def get_missions(repo: str, map_hash_to_text: dict, lang: str):
12 | def unhash_info(info: dict):
13 | for key, item in info.items():
14 | if isinstance(item, dict) and "Hash" in item:
15 | if str(item["Hash"]) in map_hash_to_text:
16 | info[key] = text_normalization(map_hash_to_text[str(item["Hash"])])
17 | else:
18 | print(f"warning: {item['Hash']} hash key not found")
19 | info[key] = "N/A"
20 | return info
21 |
22 | chapter_file = os.path.join(repo, "ExcelOutput/MissionChapterConfig.json")
23 | with open(chapter_file, "r", encoding="utf-8") as f:
24 | chapter_map = json.load(f)
25 | for chapter_id, info in chapter_map.items():
26 | keys = ["ChapterName", "StageName", "ChapterDesc"]
27 | chapter_map[chapter_id] = {
28 | key: map_hash_to_text.get(str(get_stable_hash(info[key])), "N/A")
29 | for key in keys if info[key] and str(get_stable_hash(info[key])) in map_hash_to_text
30 | }
31 |
32 | mission_file = os.path.join(repo, "ExcelOutput/MainMission.json")
33 | with open(mission_file, "r", encoding="utf-8") as f:
34 | mission_map = json.load(f)
35 | mission_map = {mission_id: unhash_info(info) for mission_id, info in mission_map.items()}
36 |
37 | submission_file = os.path.join(repo, "ExcelOutput/SubMission.json")
38 | with open(submission_file, "r", encoding="utf-8") as f:
39 | submission_map = json.load(f)
40 | submission_map = {mission_id: unhash_info(info) for mission_id, info in submission_map.items()}
41 |
42 | # find mission-submission hierarchy
43 | def get_submission_text_feature(submission: dict):
44 | return submission["TargetText"] + "|||" + submission["DescrptionText"]
45 |
46 | mission_hierarchy_map = {}
47 | for mission_dir in glob.iglob(os.path.join(repo, "Config/Level/Mission/*")):
48 | for file in glob.iglob(os.path.join(mission_dir, "MissionInfo_*.json")):
49 | with open(file, "r", encoding="utf-8") as f:
50 | info = json.load(f)
51 | if str(info["MainMissionID"]) not in mission_map or "N/A" in mission_map[str(info["MainMissionID"])]["Name"]:
52 | continue
53 | # there are repetitive texts (maybe sub tasks sharing the same submission)
54 | # we merge them with unique texts and a list of ids
55 | map_text_feature_to_submissions = {}
56 | for sub in info["SubMissionList"]:
57 | if str(sub["ID"]) not in submission_map:
58 | continue
59 | submission = submission_map[str(sub["ID"])]
60 | text_feature = get_submission_text_feature(submission)
61 | if text_feature not in map_text_feature_to_submissions:
62 | map_text_feature_to_submissions[text_feature] = []
63 | map_text_feature_to_submissions[text_feature].append(sub["ID"])
64 |
65 | merged_submissions = []
66 | for text_feature, submissions in map_text_feature_to_submissions.items():
67 | target, desc = text_feature.split("|||")
68 | if "N/A" in target:
69 | continue
70 | merged_submissions.append({"submission_ids": submissions, "target": target, "desc": desc})
71 |
72 | mission = mission_map[str(info["MainMissionID"])]
73 | mission = {
74 | "name": mission["Name"],
75 | "next_missions": mission["NextMainMissionList"],
76 | "next_track_mission": mission.get("NextTrackMainMission", None),
77 | "chapter_id": mission.get("ChapterID", None),
78 | "reward_id": mission.get("RewardID", None),
79 | }
80 | if mission["chapter_id"] and chapter_map.get(str(mission["chapter_id"])):
81 | mission["chapter"] = chapter_map.get(str(mission["chapter_id"]))
82 |
83 | mission_hierarchy_map[info["MainMissionID"]] = {
84 | "mission": mission,
85 | "submissions": merged_submissions
86 | }
87 |
88 | os.makedirs(f"data/missions/{lang}", exist_ok=True)
89 | with open(f"data/missions/{lang}/missions.json", "w", encoding="utf-8") as f:
90 | json.dump(mission_hierarchy_map, f, ensure_ascii=False, indent=4)
91 |
92 |
93 | if __name__ == "__main__":
94 | parser = argparse.ArgumentParser()
95 | parser.add_argument(
96 | "--repo",
97 | default="../StarRailData",
98 | type=str,
99 | required=True,
100 | help="data dir",
101 | )
102 | parser.add_argument("--lang", default="EN", type=str, help="language type")
103 | args = parser.parse_args()
104 |
105 | output_dir = "data"
106 | output_dir = os.path.join(output_dir, "misc", args.lang)
107 | os.makedirs(output_dir, exist_ok=True)
108 |
109 | map_hash_to_text = load_text_hash_map(args.repo, args.lang)
110 | map_hash_to_text["371857150"] = "N/A"
111 |
112 | get_missions(
113 | repo=args.repo,
114 | map_hash_to_text=map_hash_to_text,
115 | lang=args.lang,
116 | )
117 |
--------------------------------------------------------------------------------
/data/dialogues/CHS/messages.jsonl:
--------------------------------------------------------------------------------
1 | {"ID": 1150300, "StartMessageItemIDList": [115030004], "IsPerformMessage": true, "contacts": [{"ID": 1004, "Name": "瓦尔特", "IconPath": "SpriteOutput/AvatarRoundIcon/1004.png", "SignatureText": "列车组各位,随时保持联系", "ContactsType": 1, "ContactsCamp": "星穹列车"}], "messages": [{"ID": 115030004, "Sender": "瓦尔特", "ItemType": "Text", "MainText": "初步的研究结果出来了。这种空间扭曲现象催生的实体被称作「拟造花萼」", "OptionText": "{NICKNAME}", "NextItemIDList": [115030005], "SectionID": 1150300}, {"ID": 115030005, "Sender": "瓦尔特", "ItemType": "Text", "MainText": "这种实体中可能存有具备实用价值的现实资料。你们与其接触时要倍加小心,空间扭曲现象的本质就是无法解释的混沌,很难判断其中是否还蕴藏着某些危险要素", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1150300}, {"ID": 999990001, "Sender": "System", "ItemType": "Text", "MainText": "{NICKNAME}", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1150300}]}
2 | {"ID": 1000000, "StartMessageItemIDList": [100000000], "IsPerformMessage": true, "contacts": [{"ID": 1002, "Name": "丹恒", "IconPath": "SpriteOutput/AvatarRoundIcon/1002.png", "SignatureText": "智库相关找我", "ContactsType": 1, "ContactsCamp": "星穹列车"}], "messages": [{"ID": 100000000, "Sender": "丹恒", "ItemType": "Text", "MainText": "你好,我是丹恒。", "OptionText": "{NICKNAME}", "NextItemIDList": [100000001], "SectionID": 1000000}, {"ID": 100000001, "Sender": "丹恒", "ItemType": "Text", "MainText": "记得去找姬子。", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1000000}]}
3 | {"ID": 1000100, "StartMessageItemIDList": [100010000], "IsPerformMessage": true, "contacts": [{"ID": 1001, "Name": "三月七", "IconPath": "SpriteOutput/AvatarRoundIcon/1001.png", "SignatureText": "今天也是三月七~", "ContactsType": 1, "ContactsCamp": "星穹列车"}], "messages": [{"ID": 100010000, "Sender": "三月七", "ItemType": "Text", "MainText": "唷吼,我是三月七:D", "OptionText": "{NICKNAME}", "NextItemIDList": [100010001], "SectionID": 1000100}, {"ID": 100010001, "Sender": "三月七", "ItemType": "Text", "MainText": "姬子姐有事找你,别忘啦", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1000100}, {"ID": 999990002, "Sender": "System", "ItemType": "Text", "MainText": "{NICKNAME}", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1000100}]}
4 | {"ID": 1000200, "StartMessageItemIDList": [100020001], "IsPerformMessage": true, "contacts": [{"ID": 1013, "Name": "黑塔", "IconPath": "SpriteOutput/AvatarRoundIcon/1013.png", "SignatureText": "此号停用 | 商务联系:艾丝妲", "ContactsType": 1, "ContactsCamp": "空间站「黑塔」"}], "messages": [{"ID": 100020001, "Sender": "黑塔", "ItemType": "Text", "MainText": "嘿,{NICKNAME},我是黑塔,有好事找你", "OptionText": "{NICKNAME}", "NextItemIDList": [100020002], "SectionID": 1000200}, {"ID": 100020002, "Sender": "黑塔", "ItemType": "Text", "MainText": "速来我的办公室!等你!", "OptionText": "{NICKNAME}", "NextItemIDList": [100020003], "SectionID": 1000200}, {"ID": 100020003, "Sender": "PlayerAuto", "ItemType": "Text", "MainText": "?", "OptionText": "{NICKNAME}", "NextItemIDList": [100020004, 100020005], "SectionID": 1000200}, {"ID": 100020004, "Sender": "Player", "ItemType": "Text", "MainText": "可你就在我旁边啊", "OptionText": "可你就在我旁边啊", "NextItemIDList": [100020006], "SectionID": 1000200}, {"ID": 100020005, "Sender": "Player", "ItemType": "Text", "MainText": "你直接说不就行了", "OptionText": "你直接说不就行了", "NextItemIDList": [100020006], "SectionID": 1000200}, {"ID": 100020006, "Sender": "黑塔", "ItemType": "Text", "MainText": "[自动回复]您好,我现在有事不在,一会也不会和您联系", "OptionText": "{NICKNAME}", "NextItemIDList": [100020007], "SectionID": 1000200}, {"ID": 100020007, "Sender": "PlayerAuto", "ItemType": "Text", "MainText": "? ?", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1000200}, {"ID": 999990003, "Sender": "System", "ItemType": "Text", "MainText": "{NICKNAME}", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1000200}]}
5 | {"ID": 1010000, "StartMessageItemIDList": [101000000], "MainMissionLink": 2000100, "contacts": [{"ID": 1008, "Name": "阿兰", "IconPath": "SpriteOutput/AvatarRoundIcon/1008.png", "SignatureText": "佩佩紧急联系人", "ContactsType": 1, "ContactsCamp": "空间站「黑塔」"}], "messages": [{"ID": 101000000, "Sender": "阿兰", "ItemType": "Text", "MainText": "嗨!那个,是我…未经同意要了你的联系方式", "OptionText": "{NICKNAME}", "NextItemIDList": [101000001], "SectionID": 1010000}, {"ID": 101000001, "Sender": "阿兰", "ItemType": "Text", "MainText": "你还在「黑塔」吗?", "OptionText": "{NICKNAME}", "NextItemIDList": [101000002, 101000003, 101000004], "SectionID": 1010000}, {"ID": 101000002, "Sender": "Player", "ItemType": "Text", "MainText": "当然,我还没和你告别呢", "OptionText": "当然,我还没和你告别呢", "NextItemIDList": [101000005], "SectionID": 1010000}, {"ID": 101000003, "Sender": "Player", "ItemType": "Text", "MainText": "我已经跟着列车环游千星去啦", "OptionText": "我已经跟着列车环游千星去啦", "NextItemIDList": [101000005], "SectionID": 1010000}, {"ID": 101000004, "Sender": "Player", "ItemType": "Text", "MainText": "我在不在取决于你找我啥事儿…", "OptionText": "我在不在取决于你找我啥事儿…", "NextItemIDList": [101000005], "SectionID": 1010000}, {"ID": 101000005, "Sender": "阿兰", "ItemType": "Text", "MainText": "空间站正推行「复兴计划」,如果有空,希望你能过来帮把手", "OptionText": "{NICKNAME}", "NextItemIDList": [101000007], "SectionID": 1010000}, {"ID": 101000006, "Sender": "阿兰", "ItemType": "Text", "MainText": "我可不是想抓免费壮丁啊!巨款已经备下,只要你来,只要我有!", "OptionText": "{NICKNAME}", "NextItemIDList": [101000007], "SectionID": 1010000}, {"ID": 101000007, "Sender": "阿兰", "ItemType": "Text", "MainText": "不行再发下去的话我这个月的信用点就不够用了…这是最后一条,总之快来吧我等你!", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1010000}, {"ID": 101000008, "Sender": "Player", "ItemType": "Text", "MainText": "???不是说有巨款吗", "OptionText": "???不是说有巨款吗", "NextItemIDList": [], "SectionID": 1010000}, {"ID": 999990004, "Sender": "System", "ItemType": "Text", "MainText": "{NICKNAME}", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1010000}]}
6 | {"ID": -99999999999999, "messages": [{"ID": 900000001, "Sender": "System", "ItemType": "Text", "MainText": "对方目前无法接收消息", "OptionText": "{NICKNAME}", "NextItemIDList": []}]}
7 |
--------------------------------------------------------------------------------
/util/message_util.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import traceback
4 |
5 | from util.common import text_normalization
6 |
7 |
8 | _UNKNOWN_SECTION_ID = -99999999999999
9 |
10 |
11 | def get_message(repo: str, lang: str, map_hash_to_text: dict[str, str], max_count=-1):
12 | # get contacts camp
13 | with open(
14 | os.path.join(repo, "ExcelOutput/MessageContactsCamp.json"),
15 | "r",
16 | encoding="utf-8",
17 | ) as f:
18 | contact_camp_info = json.load(f)
19 |
20 | # get contacts name and signature
21 | with open(
22 | os.path.join(repo, "ExcelOutput/MessageContactsConfig.json"),
23 | "r",
24 | encoding="utf-8",
25 | ) as f:
26 | map_contact_to_info = json.load(f)
27 | for contact_id, info in map_contact_to_info.items():
28 | try:
29 | info["Name"] = text_normalization(
30 | map_hash_to_text[str(info["Name"]["Hash"])]
31 | )
32 | info["SignatureText"] = text_normalization(
33 | map_hash_to_text[str(info["SignatureText"]["Hash"])]
34 | )
35 | if "ContactsCamp" in info:
36 | info["ContactsCamp"] = text_normalization(
37 | map_hash_to_text[
38 | str(
39 | contact_camp_info[str(info["ContactsCamp"])]["Name"]["Hash"]
40 | )
41 | ]
42 | )
43 | map_contact_to_info[contact_id] = info
44 | except:
45 | print(traceback.format_exc())
46 |
47 | # get messages
48 | with open(
49 | os.path.join(repo, "ExcelOutput/MessageItemConfig.json"),
50 | "r",
51 | encoding="utf-8",
52 | ) as f:
53 | message_info = json.load(f)
54 | map_section_to_messages = {}
55 | for message_id, item in message_info.items():
56 | try:
57 | section_id = int(item["SectionID"])
58 | except KeyError:
59 | section_id = _UNKNOWN_SECTION_ID
60 | if section_id not in map_section_to_messages:
61 | map_section_to_messages[section_id] = []
62 | try:
63 | item["MainText"] = text_normalization(
64 | map_hash_to_text[str(item["MainText"]["Hash"])]
65 | )
66 | item["OptionText"] = text_normalization(
67 | map_hash_to_text[str(item["OptionText"]["Hash"])]
68 | )
69 | except KeyError:
70 | continue
71 | map_section_to_messages[section_id].append(item)
72 |
73 | # get message group
74 | with open(
75 | os.path.join(repo, "ExcelOutput/MessageGroupConfig.json"),
76 | "r",
77 | encoding="utf-8",
78 | ) as f:
79 | map_session_id_to_contacts = {}
80 | for key, info in json.load(f).items():
81 | for section_id in info["MessageSectionIDList"]:
82 | if section_id not in map_session_id_to_contacts:
83 | map_session_id_to_contacts[section_id] = []
84 | contact_id = info["MessageContactsID"]
85 | map_session_id_to_contacts[section_id].append(contact_id)
86 |
87 | with open(
88 | os.path.join(repo, "ExcelOutput/MessageSectionConfig.json"),
89 | "r",
90 | encoding="utf-8",
91 | ) as f:
92 | section_info = json.load(f)
93 |
94 | output_dir = os.path.join("data", "dialogues", lang)
95 | os.makedirs(output_dir, exist_ok=True)
96 | output_path = os.path.join(output_dir, "messages.jsonl")
97 | count = 0
98 | with open(output_path, "w", encoding="utf-8") as f:
99 | # normal sections
100 | for section_id, info in section_info.items():
101 | # add contacts information
102 | contact_ids = map_session_id_to_contacts[int(section_id)]
103 | info["contacts"] = [map_contact_to_info[str(c_id)] for c_id in contact_ids]
104 | try:
105 | info["messages"] = map_section_to_messages[int(section_id)]
106 | info["messages"] = list(
107 | filter(
108 | lambda m: not (
109 | m["Sender"] == "System" and m["MainText"] == "N/A"
110 | ),
111 | info["messages"],
112 | )
113 | )
114 |
115 | for message in info["messages"]:
116 | if "ContactsID" in message:
117 | message["Sender"] = map_contact_to_info[
118 | str(message["ContactsID"])
119 | ]["Name"]
120 | elif message["Sender"] == "NPC":
121 | message["Sender"] = info["contacts"][0]["Name"]
122 |
123 | except KeyError:
124 | print("warning: ", section_id, "No messages found")
125 | continue
126 | print(json.dumps(info, ensure_ascii=False), file=f)
127 | count += 1
128 | if count >= max_count > 0:
129 | break
130 |
131 | for message in map_section_to_messages[_UNKNOWN_SECTION_ID]:
132 | print(
133 | json.dumps(
134 | {"ID": _UNKNOWN_SECTION_ID, "messages": [message]},
135 | ensure_ascii=False,
136 | ),
137 | file=f,
138 | )
139 |
140 | print("Messages: Total num of dialogues:", count)
141 |
--------------------------------------------------------------------------------
/data/dialogues/EN/messages.jsonl:
--------------------------------------------------------------------------------
1 | {"ID": 1150300, "StartMessageItemIDList": [115030004], "IsPerformMessage": true, "contacts": [{"ID": 1004, "Name": "Welt", "IconPath": "SpriteOutput/AvatarRoundIcon/1004.png", "SignatureText": "Everyone on the Express, please constantly keep in touch", "ContactsType": 1, "ContactsCamp": "Astral Express"}], "messages": [{"ID": 115030004, "Sender": "Welt", "ItemType": "Text", "MainText": "The initial results are back. This entity born out of space distortions is called a Calyx", "OptionText": "{NICKNAME}", "NextItemIDList": [115030005], "SectionID": 1150300}, {"ID": 115030005, "Sender": "Welt", "ItemType": "Text", "MainText": "Such entities may contain reality data that has practical value. Exercise extreme caution when you come into contact with it. The very nature of spatial distortion is inexplicable chaos, and it's quite difficult to tell if there are any elements of danger hidden within", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1150300}, {"ID": 999990001, "Sender": "System", "ItemType": "Text", "MainText": "{NICKNAME}", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1150300}]}
2 | {"ID": 1000000, "StartMessageItemIDList": [100000000], "IsPerformMessage": true, "contacts": [{"ID": 1002, "Name": "Dan Heng", "IconPath": "SpriteOutput/AvatarRoundIcon/1002.png", "SignatureText": "For anything related to the data bank, come find me.", "ContactsType": 1, "ContactsCamp": "Astral Express"}], "messages": [{"ID": 100000000, "Sender": "Dan Heng", "ItemType": "Text", "MainText": "Hi. This is Dan Heng.", "OptionText": "{NICKNAME}", "NextItemIDList": [100000001], "SectionID": 1000000}, {"ID": 100000001, "Sender": "Dan Heng", "ItemType": "Text", "MainText": "Remember to go find Himeko.", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1000000}]}
3 | {"ID": 1000100, "StartMessageItemIDList": [100010000], "IsPerformMessage": true, "contacts": [{"ID": 1001, "Name": "March 7th", "IconPath": "SpriteOutput/AvatarRoundIcon/1001.png", "SignatureText": "Today is also March 7th~", "ContactsType": 1, "ContactsCamp": "Astral Express"}], "messages": [{"ID": 100010000, "Sender": "March 7th", "ItemType": "Text", "MainText": "Hey there, this is March :D", "OptionText": "{NICKNAME}", "NextItemIDList": [100010001], "SectionID": 1000100}, {"ID": 100010001, "Sender": "March 7th", "ItemType": "Text", "MainText": "Himeko wants to talk to you about something. Don't forget~", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1000100}, {"ID": 999990002, "Sender": "System", "ItemType": "Text", "MainText": "{NICKNAME}", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1000100}]}
4 | {"ID": 1000200, "StartMessageItemIDList": [100020001], "IsPerformMessage": true, "contacts": [{"ID": 1013, "Name": "Herta", "IconPath": "SpriteOutput/AvatarRoundIcon/1013.png", "SignatureText": "This account is disabled | Business Contact: Asta", "ContactsType": 1, "ContactsCamp": "Herta Space Station"}], "messages": [{"ID": 100020001, "Sender": "Herta", "ItemType": "Text", "MainText": "Hey, {NICKNAME}, it's Herta. I need you for something good", "OptionText": "{NICKNAME}", "NextItemIDList": [100020002], "SectionID": 1000200}, {"ID": 100020002, "Sender": "Herta", "ItemType": "Text", "MainText": "Come to my office quickly! I'm waiting!", "OptionText": "{NICKNAME}", "NextItemIDList": [100020003], "SectionID": 1000200}, {"ID": 100020003, "Sender": "PlayerAuto", "ItemType": "Text", "MainText": "?", "OptionText": "{NICKNAME}", "NextItemIDList": [100020004, 100020005], "SectionID": 1000200}, {"ID": 100020004, "Sender": "Player", "ItemType": "Text", "MainText": "But you're right next to me", "OptionText": "But you're right next to me", "NextItemIDList": [100020006], "SectionID": 1000200}, {"ID": 100020005, "Sender": "Player", "ItemType": "Text", "MainText": "Can't you just tell me", "OptionText": "Can't you just tell me", "NextItemIDList": [100020006], "SectionID": 1000200}, {"ID": 100020006, "Sender": "Herta", "ItemType": "Text", "MainText": "[Automatic reply] Hi, I'm currently unavailable, and I won't be contacting you later", "OptionText": "{NICKNAME}", "NextItemIDList": [100020007], "SectionID": 1000200}, {"ID": 100020007, "Sender": "PlayerAuto", "ItemType": "Text", "MainText": "???", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1000200}, {"ID": 999990003, "Sender": "System", "ItemType": "Text", "MainText": "{NICKNAME}", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1000200}]}
5 | {"ID": 1010000, "StartMessageItemIDList": [101000000], "MainMissionLink": 2000100, "contacts": [{"ID": 1008, "Name": "Arlan", "IconPath": "SpriteOutput/AvatarRoundIcon/1008.png", "SignatureText": "Peppy's emergency contact", "ContactsType": 1, "ContactsCamp": "Herta Space Station"}], "messages": [{"ID": 101000000, "Sender": "Arlan", "ItemType": "Text", "MainText": "Hey, um, it's me... I got your number without asking", "OptionText": "{NICKNAME}", "NextItemIDList": [101000001], "SectionID": 1010000}, {"ID": 101000001, "Sender": "Arlan", "ItemType": "Text", "MainText": "Are you still at the space station?", "OptionText": "{NICKNAME}", "NextItemIDList": [101000002, 101000003, 101000004], "SectionID": 1010000}, {"ID": 101000002, "Sender": "Player", "ItemType": "Text", "MainText": "Of course, we haven't said farewell yet", "OptionText": "Of course, we haven't said farewell yet", "NextItemIDList": [101000005], "SectionID": 1010000}, {"ID": 101000003, "Sender": "Player", "ItemType": "Text", "MainText": "I'm already on the Express to travel among the stars", "OptionText": "I'm already on the Express to travel among the stars", "NextItemIDList": [101000005], "SectionID": 1010000}, {"ID": 101000004, "Sender": "Player", "ItemType": "Text", "MainText": "That depends on what it is that you need my help with...", "OptionText": "Depends on what you need...", "NextItemIDList": [101000005], "SectionID": 1010000}, {"ID": 101000005, "Sender": "Arlan", "ItemType": "Text", "MainText": "The station is launching a program called \"Project Revival.\" If you have time, I'd like to ask for some help", "OptionText": "{NICKNAME}", "NextItemIDList": [101000007], "SectionID": 1010000}, {"ID": 101000006, "Sender": "Arlan", "ItemType": "Text", "MainText": "Not for free, of course! A very sizable sum has already been prepared. As long as you come on board, you can get paid as much as I can afford!", "OptionText": "{NICKNAME}", "NextItemIDList": [101000007], "SectionID": 1010000}, {"ID": 101000007, "Sender": "Arlan", "ItemType": "Text", "MainText": "Uh oh, my phone's credits for this month are running out... This is the last message. Come! I'll be waiting.", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1010000}, {"ID": 101000008, "Sender": "Player", "ItemType": "Text", "MainText": "??? Didn't you say there was a sizable sum?", "OptionText": "??? Didn't you say there was a sizable sum?", "NextItemIDList": [], "SectionID": 1010000}, {"ID": 999990004, "Sender": "System", "ItemType": "Text", "MainText": "{NICKNAME}", "OptionText": "{NICKNAME}", "NextItemIDList": [], "SectionID": 1010000}]}
6 | {"ID": -99999999999999, "messages": [{"ID": 900000001, "Sender": "System", "ItemType": "Text", "MainText": "The recipient cannot currently receive messages", "OptionText": "{NICKNAME}", "NextItemIDList": []}]}
7 |
--------------------------------------------------------------------------------
/data/dialogues/CHS/story.jsonl:
--------------------------------------------------------------------------------
1 | [{"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020113, "next_TalkSentenceID": "100020113", "role": "{NICKNAME}", "content": "这…是…哪里?"}, {"TalkSentenceID": 100020115, "next_TalkSentenceID": "100020115", "role": "{NICKNAME}", "content": "卡芙…卡?"}, {"TalkSentenceID": 100020118, "next_TalkSentenceID": "100020118", "role": "{NICKNAME}", "content": "…你是…谁?"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020113, "role": "{NICKNAME}", "content": "这…是…哪里?", "next_TalkSentenceID": 100020121}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020115, "role": "{NICKNAME}", "content": "卡芙…卡?", "next_TalkSentenceID": 100020121}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020118, "role": "{NICKNAME}", "content": "…你是…谁?", "next_TalkSentenceID": 100020121}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020121, "role": "卡芙卡", "content": "听我说:你的脑袋里现在一片混沌。你不清楚自己是谁,为什么在这儿,接下来要做什么;你觉得我很熟悉,却不清楚该不该信任我——"}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020126, "next_TalkSentenceID": "100020126", "role": "{NICKNAME}", "content": "喜欢…大概。"}, {"TalkSentenceID": 100020130, "next_TalkSentenceID": "100020130", "role": "{NICKNAME}", "content": "我不要…"}, {"TalkSentenceID": 100020133, "next_TalkSentenceID": "100020133", "role": "{NICKNAME}", "content": "你要去哪…"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020126, "role": "{NICKNAME}", "content": "喜欢…大概。", "next_TalkSentenceID": 100020136}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020130, "role": "{NICKNAME}", "content": "我不要…", "next_TalkSentenceID": 100020136}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020133, "role": "{NICKNAME}", "content": "你要去哪…", "next_TalkSentenceID": 100020136}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020136, "role": "银狼", "content": "还要说多久?按照剧本,{RUBY_B#「开拓」的旅行者}星穹列车{RUBY_E#}的人就快到了,我们不该跟他们照上面。"}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020139, "next_TalkSentenceID": "100020139", "role": "{NICKNAME}", "content": "不,不要…"}, {"TalkSentenceID": 100020140, "next_TalkSentenceID": "100020139", "role": "{NICKNAME}", "content": "别了…"}, {"TalkSentenceID": 100020141, "next_TalkSentenceID": "100020139", "role": "{NICKNAME}", "content": "卡芙…卡…"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020139, "role": "{NICKNAME}", "content": "不,不要…"}, {"type": "RPG.GameCore.EndPerformance"}]
2 | [{"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020208, "FinishKey": "0", "next_TalkSentenceID": "100020208", "role": "{NICKNAME}", "content": "我…什么都不记得了。"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020208, "role": "{NICKNAME}", "content": "我…什么都不记得了。"}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020207, "FinishKey": "0", "next_TalkSentenceID": "100020210", "role": "{NICKNAME}", "content": "我叫{NICKNAME}。"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020210, "role": "丹恒", "content": "{NICKNAME}吗?你好,我是丹恒,这是三月七。", "next_TalkSentenceID": 100020211}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020211, "role": "丹恒", "content": "这座空间站遭到了反物质军团的袭击,我们是受艾丝妲站长的委托前来救援的。"}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020212, "FinishKey": "0", "next_TalkSentenceID": "100020212", "role": "{NICKNAME}", "content": "反物质军团?"}, {"TalkSentenceID": 100020215, "FinishKey": "0", "next_TalkSentenceID": "100020215", "role": "{NICKNAME}", "content": "艾丝妲站长?"}, {"TalkSentenceID": 100020218, "FinishKey": "0", "next_TalkSentenceID": "100020218", "role": "{NICKNAME}", "content": "我应该去哪…"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020212, "role": "{NICKNAME}", "content": "反物质军团?", "next_TalkSentenceID": 100020211}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020215, "role": "{NICKNAME}", "content": "艾丝妲站长?", "next_TalkSentenceID": 100020211}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020218, "role": "{NICKNAME}", "content": "我应该去哪…", "next_TalkSentenceID": 100020220}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020220, "role": "三月七", "content": "星穹列车也停在那附近!所以不必担心怪物的袭击,我们会解决这次危机的!"}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020221, "FinishKey": "0", "next_TalkSentenceID": "100020221", "role": "{NICKNAME}", "content": "你们是谁?"}, {"TalkSentenceID": 100020225, "FinishKey": "0", "next_TalkSentenceID": "100020225", "role": "{NICKNAME}", "content": "星穹列车?"}, {"TalkSentenceID": 100020229, "FinishKey": "0", "next_TalkSentenceID": "100020229", "role": "{NICKNAME}", "content": "那快走吧。"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020221, "role": "{NICKNAME}", "content": "你们是谁?", "next_TalkSentenceID": 100020220}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020225, "role": "{NICKNAME}", "content": "星穹列车?", "next_TalkSentenceID": 100020220}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020229, "role": "{NICKNAME}", "content": "那快走吧。", "next_TalkSentenceID": 100020102}, {"type": "RPG.GameCore.EndPerformance"}]
3 | [{"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020302, "next_TalkSentenceID": "100020302", "role": "{NICKNAME}", "content": "成功了?"}, {"TalkSentenceID": 100020304, "next_TalkSentenceID": "100020304", "role": "{NICKNAME}", "content": "弄坏了?"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020302, "role": "{NICKNAME}", "content": "成功了?", "next_TalkSentenceID": 100020306}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020304, "role": "{NICKNAME}", "content": "弄坏了?", "next_TalkSentenceID": 100020306}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020306, "role": "三月七", "content": "要是万能的丹恒老师在就好了,他懂一堆莫名其妙的东西,没准连电梯也会修……"}, {"type": "RPG.GameCore.EndPerformance"}]
4 | [{"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100021010, "FinishKey": "0", "next_TalkSentenceID": "100021010", "role": "{NICKNAME}", "content": "没见过这么冒失的姑娘。"}, {"TalkSentenceID": 100021013, "FinishKey": "0", "next_TalkSentenceID": "100021013", "role": "{NICKNAME}", "content": "没见过这么活泼的姑娘。"}, {"TalkSentenceID": 100021016, "FinishKey": "0", "next_TalkSentenceID": "100021016", "role": "{NICKNAME}", "content": "那我还是放弃回答吧。"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100021010, "role": "{NICKNAME}", "content": "没见过这么冒失的姑娘。", "next_TalkSentenceID": 100021019}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100021013, "role": "{NICKNAME}", "content": "没见过这么活泼的姑娘。", "next_TalkSentenceID": 100021019}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100021016, "role": "{NICKNAME}", "content": "那我还是放弃回答吧。", "next_TalkSentenceID": 100021019}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100021019, "role": "姬子", "content": "哈哈,年轻人就是容易打成一片,看来你们已经相当熟络了。"}, {"type": "RPG.GameCore.EndPerformance"}]
5 | [{"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100040115, "FinishKey": "0", "next_TalkSentenceID": "100040115", "role": "{NICKNAME}", "content": "我…姑且应付不了…"}, {"TalkSentenceID": 100040116, "FinishKey": "0", "next_TalkSentenceID": "100040115", "role": "{NICKNAME}", "content": "「毁灭」的令使?"}, {"TalkSentenceID": 100040117, "next_TalkSentenceID": "100040115", "role": "{NICKNAME}", "content": "总觉得事情没那么顺利。"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100040115, "role": "{NICKNAME}", "content": "我…姑且应付不了…"}, {"type": "RPG.GameCore.EndPerformance"}]
6 |
--------------------------------------------------------------------------------
/util/common.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import re
4 |
5 | import pandas as pd
6 |
7 | try:
8 | df = pd.read_excel("data/html_entities.xlsx")
9 | map_html_codes_to_symbol = {
10 | row["Entity Name"]: row["Symbol"] if not pd.isna(row["Symbol"]) else ""
11 | for _, row in df.iterrows()
12 | }
13 | except:
14 | pass
15 |
16 |
17 | def text_normalization(content: str) -> str:
18 | # replace all html entities
19 | for entity in re.findall("&[a-zA-Z0-9]+;", content):
20 | if entity in map_html_codes_to_symbol:
21 | content = content.replace(entity, map_html_codes_to_symbol[entity])
22 | # remove html tags
23 | content = re.sub(r"\s*
\s*", " ", content)
24 | content = re.sub(r" ", " ", content)
25 | content = re.sub(r"<[^>]+>", "", content)
26 | return content
27 |
28 |
29 | def load_text_hash_map(repo: str, lang: str) -> dict[str, str]:
30 | # get text map
31 | with open(
32 | os.path.join(repo, "TextMap", f"TextMap{lang}.json"),
33 | "r",
34 | encoding="utf-8",
35 | ) as f:
36 | map_hash_to_text = json.load(f)
37 | return map_hash_to_text
38 |
39 |
40 | def load_sentence_map(repo: str) -> dict[str, dict]:
41 | with open(
42 | os.path.join(repo, "ExcelOutput/TalkSentenceConfig.json"),
43 | "r",
44 | encoding="utf-8",
45 | ) as f:
46 | return json.load(f)
47 |
48 |
49 | def get_speaker_content(
50 | sent_id, map_hash_to_text: dict[str, str], map_sent_id_to_hash_info: dict[str, dict]
51 | ) -> dict[str, str]:
52 | try:
53 | hash_info = map_sent_id_to_hash_info[str(sent_id)]
54 | speaker_name_hash_id = hash_info["TextmapTalkSentenceName"]["Hash"]
55 | sentence_hash_id = hash_info["TalkSentenceText"]["Hash"]
56 | speaker = text_normalization(map_hash_to_text[str(speaker_name_hash_id)])
57 | sentence = text_normalization(map_hash_to_text[str(sentence_hash_id)])
58 | except KeyError:
59 | print(f"warning: {sent_id} not found in text hash map")
60 | return None
61 | return {"role": speaker, "content": sentence}
62 |
63 |
64 | def parse_on_sequence(info: dict, map_hash_to_text: dict, map_sent_id_to_hash_info: dict) -> list:
65 | sessions = []
66 | for sequence in info.get("OnStartSequece", []):
67 | session = []
68 | for element in sequence.get("TaskList", []):
69 | if element["$type"] == "RPG.GameCore.PlayAndWaitSimpleTalk":
70 | sub_session = {"type": element["$type"], "conversations": []}
71 | for simple_talk in element["SimpleTalkList"]:
72 | speaker_content = get_speaker_content(
73 | simple_talk["TalkSentenceID"],
74 | map_hash_to_text,
75 | map_sent_id_to_hash_info,
76 | )
77 | if speaker_content:
78 | simple_talk["role"] = speaker_content["role"]
79 | simple_talk["content"] = speaker_content["content"]
80 | simple_talk["type"] = "PlayAndWaitSimpleTalk"
81 | sub_session["conversations"].append(simple_talk)
82 | if sub_session["conversations"]:
83 | session.append(sub_session)
84 | elif element["$type"] == "RPG.GameCore.PlayOptionTalk":
85 | sub_session = {"type": element["$type"], "options": []}
86 | for option in element["OptionList"]:
87 | try:
88 | speaker_content = get_speaker_content(
89 | option["TalkSentenceID"],
90 | map_hash_to_text,
91 | map_sent_id_to_hash_info,
92 | )
93 | option["next_TalkSentenceID"] = re.search(
94 | r"(TalkSentence_)*(?P\d+)",
95 | option["TriggerCustomString"],
96 | )["sent_id"]
97 | del option["TriggerCustomString"]
98 | del option["OptionIconType"]
99 | option["role"] = speaker_content["role"]
100 | option["content"] = speaker_content["content"]
101 | sub_session["options"].append(option)
102 | except:
103 | pass
104 | if sub_session["options"]:
105 | session.append(sub_session)
106 | elif element["$type"] in {
107 | "RPG.GameCore.WaitCustomString",
108 | "RPG.GameCore.TriggerCustomString",
109 | }:
110 | speaker_content = None
111 | try:
112 | sent_id = int(
113 | re.search(
114 | r"(TalkSentence_)*(?P\d+)",
115 | element["CustomString"]["Value"],
116 | )["sent_id"]
117 | )
118 | speaker_content = get_speaker_content(
119 | sent_id,
120 | map_hash_to_text,
121 | map_sent_id_to_hash_info,
122 | )
123 | except TypeError:
124 | sent_id = element["CustomString"]["Value"]
125 | except KeyError:
126 | continue
127 | sub_session = {
128 | "type": element["$type"],
129 | "TalkSentenceID": sent_id,
130 | }
131 | if speaker_content:
132 | sub_session["role"] = speaker_content["role"]
133 | sub_session["content"] = speaker_content["content"]
134 |
135 | session.append(sub_session)
136 | elif element["$type"] == "RPG.GameCore.EndPerformance":
137 | sub_session = {"type": "RPG.GameCore.EndPerformance"}
138 | if session:
139 | session.append(sub_session)
140 | if session:
141 | sessions.append(session)
142 |
143 | return sessions
144 |
145 |
146 | def merge_on_sequence_sessions(sessions: list):
147 | merged_sessions = []
148 | merged_session = []
149 | for session in sessions:
150 | for i, sub_session in enumerate(session):
151 | if (
152 | merged_session
153 | and merged_session[-1]["type"] == "RPG.GameCore.WaitCustomString"
154 | ):
155 | if (
156 | sub_session["type"] == "RPG.GameCore.PlayAndWaitSimpleTalk"
157 | and sub_session["conversations"][0]["TalkSentenceID"]
158 | == merged_session[-1]["TalkSentenceID"]
159 | ):
160 | del merged_session[-1]
161 |
162 | if (
163 | merged_session
164 | and sub_session["type"] == "RPG.GameCore.TriggerCustomString"
165 | ):
166 | merged_session[-1]["next_TalkSentenceID"] = sub_session[
167 | "TalkSentenceID"
168 | ]
169 | continue
170 |
171 | merged_session.append(sub_session)
172 |
173 | if (
174 | merged_session
175 | and merged_session[-1]["type"] == "RPG.GameCore.EndPerformance"
176 | ):
177 | merged_sessions.append(merged_session)
178 | merged_session = []
179 | return merged_sessions
180 |
181 |
182 | def get_stable_hash(s):
183 | hash1 = 5381
184 | hash2 = hash1
185 |
186 | i = 0
187 | while i < len(s) and s[i] != '\0':
188 | hash1 = ((hash1 << 5) + hash1) ^ ord(s[i])
189 | if i == len(s) - 1 or s[i + 1] == '\0':
190 | break
191 | hash2 = ((hash2 << 5) + hash2) ^ ord(s[i + 1])
192 | i += 2
193 |
194 | return (hash1 + (hash2 * 1566083941)) & 0xFFFFFFFF
195 |
196 |
197 | if __name__ == '__main__':
198 | # Example usage
199 | input_string = "SkillPointName_1001101"
200 | hash_value = get_stable_hash(input_string)
201 | print(f"The hash of '{input_string}' is: {hash_value}")
--------------------------------------------------------------------------------
/data/dialogues/CHS/train_visitor.jsonl:
--------------------------------------------------------------------------------
1 | [{"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100102, "ProtectTime": 0.3, "role": "艾丝妲", "content": "如果在列车的这个位置布设空间望远镜,应该能观测到更短的波长……", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100103, "ProtectTime": 0.3, "role": "艾丝妲", "content": "嗯,方向感觉还不错。", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100104, "next_TalkSentenceID": "500100105", "role": "{NICKNAME}", "content": "有新论文选题?"}, {"TalkSentenceID": 500100107, "next_TalkSentenceID": "500100108", "role": "{NICKNAME}", "content": "刚刚在工作吗?"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100105, "ProtectTime": 0.3, "role": "艾丝妲", "content": "是呀。之前苦思冥想不得其法,一登上列车,想法就接二连三地冒出来,灵感真是折磨人的小妖怪。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100106, "ProtectTime": 0.3, "role": "艾丝妲", "content": "这篇论文或许和列车有关…到时候还要请你们接受我的采访,多多提供一手资料。", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100110}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100108, "ProtectTime": 0.3, "role": "艾丝妲", "content": "当然不是啦。我又不是以工作名义来参观的,而是受到了{NICKNAME}的邀请,对吧?", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100109, "ProtectTime": 0.3, "role": "艾丝妲", "content": "所以在登上列车的那一刻,我就迫不及待调整至休假模式,全力睁大眼睛,寻找下一个论文选题。", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100110}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100110, "ProtectTime": 0.3, "role": "艾丝妲", "content": "——这些都不是重点啦。", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100111, "next_TalkSentenceID": "500100112", "role": "{NICKNAME}", "content": "今天就好好享受列车时光吧。"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100112, "ProtectTime": 0.3, "role": "艾丝妲", "content": "说得没错。", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100113, "ProtectTime": 0.3, "role": "艾丝妲", "content": "虽然之前也来过列车,不过有{NICKNAME}在的列车,我还是第一次来呢。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100114, "ProtectTime": 0.3, "role": "艾丝妲", "content": "我很期待哦。", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.EndPerformance"}]
2 | [{"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100202, "ProtectTime": 0.3, "role": "艾丝妲", "content": "如果能观测到更短的波长,那可以当作论文的新选题…不过不是今天的重点啦。", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100203, "next_TalkSentenceID": "500100204", "role": "{NICKNAME}", "content": "今天就好好享受列车时光吧。"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100204, "ProtectTime": 0.3, "role": "艾丝妲", "content": "说得没错。", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100205, "ProtectTime": 0.3, "role": "艾丝妲", "content": "虽然之前也来过列车,不过有{NICKNAME}在的列车,我还是第一次来呢。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100206, "ProtectTime": 0.3, "role": "艾丝妲", "content": "我很期待哦。", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.EndPerformance"}]
3 | [{"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100302, "ProtectTime": 0.3, "role": "艾丝妲", "content": "列车上有不少我只在目录上见过的奇物,虽然仅靠肉眼我也不能完全确信……", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100303, "next_TalkSentenceID": "500100304", "role": "{NICKNAME}", "content": "万能合成机?"}, {"TalkSentenceID": 500100307, "next_TalkSentenceID": "500100308", "role": "{NICKNAME}", "content": "留声机?"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100304, "ProtectTime": 0.3, "role": "艾丝妲", "content": "我猜它应该是合成机的原型机之一,底座上或许还留有空间站的奇物编码。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100305, "ProtectTime": 0.3, "role": "艾丝妲", "content": "但公司并未从一开始就推出「万能」系列,你猜为什么?", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100306, "ProtectTime": 0.3, "role": "艾丝妲", "content": "答案是他们还想赚普通合成机的钱~", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100310}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100308, "ProtectTime": 0.3, "role": "艾丝妲", "content": "唔,我倒不认为它是个奇物。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100309, "ProtectTime": 0.3, "role": "艾丝妲", "content": "它的曲库齐全得惊人,除了大量资金投入,我想象不到你们有其他办法来解决版权问题。", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100310}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100310, "ProtectTime": 0.3, "role": "艾丝妲", "content": "不过,最可疑的当属那边的沙发……", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100311, "ProtectTime": 0.3, "role": "艾丝妲", "content": "为什么{NICKNAME}你可以选择坐下,但我做不到。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100312, "ProtectTime": 0.3, "role": "艾丝妲", "content": "或许它有自己的一套选择机制,只有被这个奇物认可的人才被允许坐在上面。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100313, "ProtectTime": 0.3, "role": "艾丝妲", "content": "唔…我还得研究研究……", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.EndPerformance"}]
4 | [{"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100402, "ProtectTime": 0.3, "role": "艾丝妲", "content": "唔…那个沙发真的很可疑……", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100403, "ProtectTime": 0.3, "role": "艾丝妲", "content": "为什么{NICKNAME}你可以选择坐下,但我做不到。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100404, "ProtectTime": 0.3, "role": "艾丝妲", "content": "或许它有自己的一套选择机制,只有被这个奇物认可的人才被允许坐在上面。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100405, "ProtectTime": 0.3, "role": "艾丝妲", "content": "唔…我还得研究研究……", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.EndPerformance"}]
5 | [{"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100502, "ProtectTime": 0.3, "role": "艾丝妲", "content": "居然有这么古老的玩意儿…你们还在用这种老式留声机吗?", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100503, "next_TalkSentenceID": "500100504", "role": "{NICKNAME}", "content": "好歹还能用。"}, {"TalkSentenceID": 500100505, "next_TalkSentenceID": "500100506", "role": "{NICKNAME}", "content": "有什么问题吗?"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100504, "ProtectTime": 0.3, "role": "艾丝妲", "content": "那倒也是。", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100507}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100506, "ProtectTime": 0.3, "role": "艾丝妲", "content": "那倒没有,只是比较少见。", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100507}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100507, "ProtectTime": 0.3, "role": "艾丝妲", "content": "留声机…要听歌得用黑胶碟片吧?在空间站里,只有极少数的音乐爱好者才有收集实体唱片的习惯。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100508, "ProtectTime": 0.3, "role": "艾丝妲", "content": "要不然就是像古恩那种老头子才会用…不过我记得他听的是录音带。", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100509, "next_TalkSentenceID": "500100510", "role": "{NICKNAME}", "content": "那你平时怎么听歌?"}, {"TalkSentenceID": 500100511, "next_TalkSentenceID": "500100512", "role": "{NICKNAME}", "content": "你好像很了解这些。"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100510, "ProtectTime": 0.3, "role": "艾丝妲", "content": "和你们一样啊,用终端随便听听。", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100513}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100512, "ProtectTime": 0.3, "role": "艾丝妲", "content": "你是说音乐吗?我只是偶尔听听,称不上什么行家。", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100513}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100513, "ProtectTime": 0.3, "role": "艾丝妲", "content": "唔,我算是明白为什么总有人喜欢这种古老笨重又不方便的「大块头」了。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100514, "ProtectTime": 0.3, "role": "艾丝妲", "content": "因为「公司」旗下的音乐平台实在太乱了,像我就是十几个平台的付费会员。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100515, "ProtectTime": 0.3, "role": "艾丝妲", "content": "收费倒不贵,只是切换起来太麻烦了,还不如留声机呢——只要有碟片,想听什么就放什么,还能满足收藏欲。", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.EndPerformance"}]
6 |
--------------------------------------------------------------------------------
/data/dialogues/EN/story.jsonl:
--------------------------------------------------------------------------------
1 | [{"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020113, "next_TalkSentenceID": "100020113", "role": "{NICKNAME}", "content": "Where... am I?"}, {"TalkSentenceID": 100020115, "next_TalkSentenceID": "100020115", "role": "{NICKNAME}", "content": "Kafka...?"}, {"TalkSentenceID": 100020118, "next_TalkSentenceID": "100020118", "role": "{NICKNAME}", "content": "Who... are you?"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020113, "role": "{NICKNAME}", "content": "Where... am I?", "next_TalkSentenceID": 100020121}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020115, "role": "{NICKNAME}", "content": "Kafka...?", "next_TalkSentenceID": 100020121}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020118, "role": "{NICKNAME}", "content": "Who... are you?", "next_TalkSentenceID": 100020121}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020121, "role": "Kafka", "content": "Listen: You are in a daze right now. You don't know who you are, why you're here, or what you're going to do next. You think I look familiar, but you're not sure if you should trust me—"}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020126, "next_TalkSentenceID": "100020126", "role": "{NICKNAME}", "content": "I guess..."}, {"TalkSentenceID": 100020130, "next_TalkSentenceID": "100020130", "role": "{NICKNAME}", "content": "No... I don't want it..."}, {"TalkSentenceID": 100020133, "next_TalkSentenceID": "100020133", "role": "{NICKNAME}", "content": "Where are you going..."}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020126, "role": "{NICKNAME}", "content": "I guess...", "next_TalkSentenceID": 100020136}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020130, "role": "{NICKNAME}", "content": "No... I don't want it...", "next_TalkSentenceID": 100020136}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020133, "role": "{NICKNAME}", "content": "Where are you going...", "next_TalkSentenceID": 100020136}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020136, "role": "Silver Wolf", "content": "How long do you think you need? According to the script, the {RUBY_B#Trailblazers}Astral Express crew{RUBY_E#} is arriving soon. We should avoid being seen by them."}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020139, "next_TalkSentenceID": "100020139", "role": "{NICKNAME}", "content": "No..."}, {"TalkSentenceID": 100020140, "next_TalkSentenceID": "100020139", "role": "{NICKNAME}", "content": "Farewell..."}, {"TalkSentenceID": 100020141, "next_TalkSentenceID": "100020139", "role": "{NICKNAME}", "content": "Kafka..."}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020139, "role": "{NICKNAME}", "content": "No..."}, {"type": "RPG.GameCore.EndPerformance"}]
2 | [{"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020208, "FinishKey": "0", "next_TalkSentenceID": "100020208", "role": "{NICKNAME}", "content": "I... don't remember a thing."}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020208, "role": "{NICKNAME}", "content": "I... don't remember a thing."}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020207, "FinishKey": "0", "next_TalkSentenceID": "100020210", "role": "{NICKNAME}", "content": "My name is {NICKNAME}."}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020210, "role": "Dan Heng", "content": "{NICKNAME}? Nice to meet you. My name's Dan Heng, and this is March 7th.", "next_TalkSentenceID": 100020211}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020211, "role": "Dan Heng", "content": "This space station was just attacked by the Antimatter Legion. We came to help with the rescue at the request of Lead Researcher Asta."}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020212, "FinishKey": "0", "next_TalkSentenceID": "100020212", "role": "{NICKNAME}", "content": "The Antimatter Legion?"}, {"TalkSentenceID": 100020215, "FinishKey": "0", "next_TalkSentenceID": "100020215", "role": "{NICKNAME}", "content": "Lead Researcher Asta?"}, {"TalkSentenceID": 100020218, "FinishKey": "0", "next_TalkSentenceID": "100020218", "role": "{NICKNAME}", "content": "Where should I go?"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020212, "role": "{NICKNAME}", "content": "The Antimatter Legion?", "next_TalkSentenceID": 100020211}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020215, "role": "{NICKNAME}", "content": "Lead Researcher Asta?", "next_TalkSentenceID": 100020211}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020218, "role": "{NICKNAME}", "content": "Where should I go?", "next_TalkSentenceID": 100020220}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020220, "role": "March 7th", "content": "Plus that's where we parked the Astral Express! Don't you worry, we'll protect you from the monsters and clear up this mess."}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020221, "FinishKey": "0", "next_TalkSentenceID": "100020221", "role": "{NICKNAME}", "content": "Who are you guys?"}, {"TalkSentenceID": 100020225, "FinishKey": "0", "next_TalkSentenceID": "100020225", "role": "{NICKNAME}", "content": "The Astral Express?"}, {"TalkSentenceID": 100020229, "FinishKey": "0", "next_TalkSentenceID": "100020229", "role": "{NICKNAME}", "content": "Let's go then."}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020221, "role": "{NICKNAME}", "content": "Who are you guys?", "next_TalkSentenceID": 100020220}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020225, "role": "{NICKNAME}", "content": "The Astral Express?", "next_TalkSentenceID": 100020220}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020229, "role": "{NICKNAME}", "content": "Let's go then.", "next_TalkSentenceID": 100020102}, {"type": "RPG.GameCore.EndPerformance"}]
3 | [{"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020302, "next_TalkSentenceID": "100020302", "role": "{NICKNAME}", "content": "It worked?"}, {"TalkSentenceID": 100020304, "next_TalkSentenceID": "100020304", "role": "{NICKNAME}", "content": "It's broken?"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020302, "role": "{NICKNAME}", "content": "It worked?", "next_TalkSentenceID": 100020306}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020304, "role": "{NICKNAME}", "content": "It's broken?", "next_TalkSentenceID": 100020306}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020306, "role": "March 7th", "content": "Too bad Dan Heng's not here. He's like a walking encyclopedia... He knows a ton of complicated stuff — maybe even elevator repair..."}, {"type": "RPG.GameCore.EndPerformance"}]
4 | [{"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100021010, "FinishKey": "0", "next_TalkSentenceID": "100021010", "role": "{NICKNAME}", "content": "I've never met a more reckless girl in my life."}, {"TalkSentenceID": 100021013, "FinishKey": "0", "next_TalkSentenceID": "100021013", "role": "{NICKNAME}", "content": "I've never met a more lively girl in my life."}, {"TalkSentenceID": 100021016, "FinishKey": "0", "next_TalkSentenceID": "100021016", "role": "{NICKNAME}", "content": "In that case, I'd rather not answer."}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100021010, "role": "{NICKNAME}", "content": "I've never met a more reckless girl in my life.", "next_TalkSentenceID": 100021019}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100021013, "role": "{NICKNAME}", "content": "I've never met a more lively girl in my life.", "next_TalkSentenceID": 100021019}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100021016, "role": "{NICKNAME}", "content": "In that case, I'd rather not answer.", "next_TalkSentenceID": 100021019}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100021019, "role": "Himeko", "content": "Haha, look at you all, you've already gotten really close."}, {"type": "RPG.GameCore.EndPerformance"}]
5 | [{"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100040115, "FinishKey": "0", "next_TalkSentenceID": "100040115", "role": "{NICKNAME}", "content": "I... can't handle..."}, {"TalkSentenceID": 100040116, "FinishKey": "0", "next_TalkSentenceID": "100040115", "role": "{NICKNAME}", "content": "The Destruction's Emanator?"}, {"TalkSentenceID": 100040117, "next_TalkSentenceID": "100040115", "role": "{NICKNAME}", "content": "I still feel that things won't be going as planned."}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100040115, "role": "{NICKNAME}", "content": "I... can't handle..."}, {"type": "RPG.GameCore.EndPerformance"}]
6 |
--------------------------------------------------------------------------------
/get_misc.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import json
3 | import os
4 | import re
5 |
6 | from util.common import text_normalization, load_text_hash_map
7 |
8 |
9 | def get_misc(
10 | input_path: str, map_hash_to_text: dict, output_path: str = None, max_count=-1
11 | ):
12 | with open(input_path, "r", encoding="utf-8") as f:
13 | items = json.load(f)
14 |
15 | unique_set = set()
16 | count = 0
17 | outputs = []
18 | for idx, info in items.items():
19 | feature_str = ""
20 | try:
21 | for key, item in info.items():
22 | if isinstance(item, dict):
23 | if "Hash" in item:
24 | info[key] = text_normalization(
25 | map_hash_to_text[str(item["Hash"])]
26 | )
27 | feature_str += info[key]
28 | else:
29 | # there may be nested dict
30 | for subkey, subitem in item.items():
31 | if isinstance(subitem, dict) and "Hash" in subitem:
32 | item[subkey] = text_normalization(
33 | map_hash_to_text[str(subitem["Hash"])]
34 | )
35 | feature_str += item[subkey]
36 | if feature_str in unique_set:
37 | continue
38 | unique_set.add(feature_str)
39 | outputs.append(info)
40 | count += 1
41 | if 0 < max_count <= count:
42 | return
43 | except KeyError:
44 | print("warning: ", idx, "text hash not found")
45 |
46 | if output_path:
47 | with open(output_path, "w", encoding="utf-8") as f:
48 | for info in outputs:
49 | print(json.dumps(info, ensure_ascii=False), file=f)
50 | return outputs
51 |
52 |
53 | def get_avatar(repo: str, map_hash_to_text: dict, output_path: str, max_count=-1):
54 | repo = os.path.join(repo, "ExcelOutput")
55 | with open(os.path.join(repo, "VoiceAtlas.json"), "r", encoding="utf-8") as f:
56 | map_avatar_to_voiceline = {}
57 | for avatar_id, info in json.load(f).items():
58 | if avatar_id not in map_avatar_to_voiceline:
59 | map_avatar_to_voiceline[avatar_id] = []
60 | for sub_info in info.values():
61 | voice_line = {
62 | "title": text_normalization(
63 | map_hash_to_text[str(sub_info["VoiceTitle"]["Hash"])]
64 | ),
65 | "Voice_M": text_normalization(
66 | map_hash_to_text[str(sub_info["Voice_M"]["Hash"])]
67 | ),
68 | "Voice_F": text_normalization(
69 | map_hash_to_text[str(sub_info["Voice_F"]["Hash"])]
70 | ),
71 | "UnlockDesc": text_normalization(
72 | map_hash_to_text[str(sub_info["UnlockDesc"]["Hash"])]
73 | ),
74 | }
75 | for key in list(voice_line.keys()):
76 | if "N/A" in voice_line[key]:
77 | del voice_line[key]
78 | map_avatar_to_voiceline[avatar_id].append(voice_line)
79 |
80 | with open(os.path.join(repo, "StoryAtlas.json"), "r", encoding="utf-8") as f:
81 | map_avatar_to_story = {}
82 | for avatar_id, info in json.load(f).items():
83 | if avatar_id not in map_avatar_to_story:
84 | map_avatar_to_story[avatar_id] = {}
85 | for story_id, sub_info in info.items():
86 | map_avatar_to_story[avatar_id][story_id] = text_normalization(
87 | map_hash_to_text[str(sub_info["Story"]["Hash"])]
88 | )
89 |
90 | with open(os.path.join(repo, "AvatarCamp.json"), "r", encoding="utf-8") as f:
91 | map_camp_to_name = json.load(f)
92 | for camp_id, info in map_camp_to_name.items():
93 | map_camp_to_name[camp_id] = text_normalization(
94 | map_hash_to_text[str(info["Name"]["Hash"])]
95 | )
96 | with open(os.path.join(repo, "AvatarAtlas.json"), "r", encoding="utf-8") as f:
97 | map_avatar_to_atlas = json.load(f)
98 | for avatar_id, info in map_avatar_to_atlas.items():
99 | map_avatar_to_atlas[avatar_id] = {
100 | "camp": map_camp_to_name[str(info["CampID"])],
101 | "CV_CN": map_hash_to_text[str(info["CV_CN"]["Hash"])],
102 | "CV_JP": map_hash_to_text[str(info["CV_JP"]["Hash"])],
103 | "CV_KR": map_hash_to_text[str(info["CV_KR"]["Hash"])],
104 | "CV_EN": map_hash_to_text[str(info["CV_EN"]["Hash"])],
105 | }
106 |
107 | with open(os.path.join(repo, "AvatarSkillConfig.json"), "r", encoding="utf-8") as f:
108 | map_skill_to_info = json.load(f)
109 | for skill_id, info in map_skill_to_info.items():
110 | level_info_list = []
111 | for level_id, sub_info in info.items():
112 | param_list = [p["Value"] for p in sub_info["ParamList"]]
113 | simple_param_list = [p["Value"] for p in sub_info["SimpleParamList"]]
114 | level_info = {
115 | level_id: {
116 | key: text_normalization(
117 | map_hash_to_text.get(str(sub_info[key]["Hash"]), "N/A")
118 | )
119 | for key in [
120 | "SkillDesc",
121 | "SimpleSkillDesc",
122 | ]
123 | }
124 | }
125 | for i, (param, simple_param) in enumerate(
126 | zip(param_list, simple_param_list)
127 | ):
128 | if isinstance(param, float):
129 | param = f"{100 * param:0.0f}"
130 | simple_param = f"{100 * simple_param:0.0f}"
131 | else:
132 | param = f"{param}"
133 | simple_param = f"{simple_param}"
134 | level_info[level_id]["SkillDesc"] = re.sub(
135 | f"#{i+1}\[[^]]+]", param, level_info[level_id]["SkillDesc"]
136 | )
137 | level_info[level_id]["SimpleSkillDesc"] = re.sub(
138 | f"#{i+1}\[[^]]+]",
139 | simple_param,
140 | level_info[level_id]["SimpleSkillDesc"],
141 | )
142 | assert "#" not in level_info[level_id]["SkillDesc"]
143 | level_info_list.append(level_info)
144 |
145 | map_skill_to_info[skill_id] = {
146 | "SkillName": map_hash_to_text.get(str(sub_info["SkillName"]["Hash"])),
147 | "SkillTag": map_hash_to_text.get(str(sub_info["SkillTag"]["Hash"])),
148 | "SkillTypeDesc": map_hash_to_text.get(
149 | str(sub_info["SkillTypeDesc"]["Hash"])
150 | ),
151 | "levels": level_info_list,
152 | }
153 |
154 | with open(os.path.join(repo, "AvatarConfig.json"), "r", encoding="utf-8") as f:
155 | basic_info = json.load(f)
156 | map_avatar_to_info = {}
157 | for avatar_id, info in basic_info.items():
158 | if avatar_id not in map_avatar_to_story:
159 | continue
160 | map_avatar_to_info[avatar_id] = {
161 | # todo: AvatarCutinIntroText, AvatarDesc, AvatarFullName is none
162 | "basic": {
163 | "Name": text_normalization(
164 | map_hash_to_text[str(info["AvatarName"]["Hash"])]
165 | ),
166 | "Camp": map_avatar_to_atlas[avatar_id]["camp"],
167 | "AvatarVOTag": info["AvatarVOTag"],
168 | "DamageType": info["DamageType"],
169 | "AvatarBaseType": info["AvatarBaseType"],
170 | "CV": {
171 | k: v
172 | for k, v in map_avatar_to_atlas[avatar_id].items()
173 | if "CV" in k
174 | },
175 | },
176 | "dialogue_lines": map_avatar_to_voiceline[avatar_id],
177 | "story": map_avatar_to_story[avatar_id],
178 | "skill": [map_skill_to_info[str(s)] for s in info["SkillList"]],
179 | }
180 |
181 | count = 0
182 | with open(output_path, "w", encoding="utf-8") as f:
183 | # json.dump(map_avatar_to_info, f, ensure_ascii=False, indent=4)
184 | for i, (_, info) in enumerate(map_avatar_to_info.items()):
185 | avatar_name = info["basic"]["Name"]
186 | print(json.dumps({avatar_name: info}, ensure_ascii=False), file=f)
187 | count += 1
188 | if 0 < max_count <= count:
189 | break
190 |
191 |
192 | if __name__ == "__main__":
193 | parser = argparse.ArgumentParser()
194 | parser.add_argument(
195 | "--repo",
196 | default="../StarRailData",
197 | type=str,
198 | required=True,
199 | help="data dir",
200 | )
201 | parser.add_argument("--lang", default="CHS", type=str, help="language type")
202 | parser.add_argument("--max_count", default=-1, type=str, help="max_count")
203 | args = parser.parse_args()
204 |
205 | output_dir = "data"
206 | output_dir = os.path.join(output_dir, "misc", args.lang)
207 | os.makedirs(output_dir, exist_ok=True)
208 |
209 | map_hash_to_text = load_text_hash_map(args.repo, args.lang)
210 | map_hash_to_text["371857150"] = "N/A"
211 |
212 | # simple tasks
213 | for input_name, output_name in [
214 | ("BookSeriesConfig.json", "books.jsonl"),
215 | ("ItemConfig.json", "items.jsonl"),
216 | ("MazeBuff.json", "maze_buff.jsonl"),
217 | ]:
218 | get_misc(
219 | input_path=os.path.join(args.repo, "ExcelOutput", input_name),
220 | output_path=os.path.join(output_dir, output_name),
221 | map_hash_to_text=map_hash_to_text,
222 | max_count=args.max_count,
223 | )
224 |
225 | # complex tasks
226 | get_avatar(
227 | args.repo,
228 | map_hash_to_text,
229 | output_path=os.path.join(output_dir, "avatar.jsonl"),
230 | max_count=args.max_count,
231 | )
232 |
--------------------------------------------------------------------------------
/data/dialogues/EN/train_visitor.jsonl:
--------------------------------------------------------------------------------
1 | [{"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100102, "ProtectTime": 0.3, "role": "Asta", "content": "If a space telescope were deployed at this location on the Express, it would almost certainly be possible to observe shorter wavelengths...", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100103, "ProtectTime": 0.3, "role": "Asta", "content": "Mm, feels like a good angle.", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100104, "next_TalkSentenceID": "500100105", "role": "{NICKNAME}", "content": "Have you got a new thesis topic?"}, {"TalkSentenceID": 500100107, "next_TalkSentenceID": "500100108", "role": "{NICKNAME}", "content": "Here for work?"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100105, "ProtectTime": 0.3, "role": "Asta", "content": "Sure do! Y'know, I was racking my brains before I boarded the Express, but now I've got more ideas than I know what to do with. Inspiration is a fickle mistress.", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100106, "ProtectTime": 0.3, "role": "Asta", "content": "My thesis might just be related to the Express... I hope you'll agree to be interviewed later — I need first-hand information.", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100110}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100108, "ProtectTime": 0.3, "role": "Asta", "content": "Of course not — I'm here on your invitation. Isn't that right?", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100109, "ProtectTime": 0.3, "role": "Asta", "content": "That's why I went into vacation mode the second I boarded the Express — thesis topics don't unearth themselves y'know.", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100110}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100110, "ProtectTime": 0.3, "role": "Asta", "content": "...Of course, there are other things in life too.", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100111, "next_TalkSentenceID": "500100112", "role": "{NICKNAME}", "content": "Why not just soak in the Express for today."}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100112, "ProtectTime": 0.3, "role": "Asta", "content": "That's not a bad idea.", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100113, "ProtectTime": 0.3, "role": "Asta", "content": "I've been on the Express before, but this is my first visit with {NICKNAME} on board.", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100114, "ProtectTime": 0.3, "role": "Asta", "content": "Looking forward to it!", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.EndPerformance"}]
2 | [{"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100202, "ProtectTime": 0.3, "role": "Asta", "content": "If shorter wavelengths could be observed, it'd make for a good thesis... But that's not the focus of today.", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100203, "next_TalkSentenceID": "500100204", "role": "{NICKNAME}", "content": "Why not just soak in the Express for now."}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100204, "ProtectTime": 0.3, "role": "Asta", "content": "That's not a bad idea.", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100205, "ProtectTime": 0.3, "role": "Asta", "content": "I've been on the Express before, but this is my first visit with {NICKNAME} on board.", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100206, "ProtectTime": 0.3, "role": "Asta", "content": "Looking forward to it!", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.EndPerformance"}]
3 | [{"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100302, "ProtectTime": 0.3, "role": "Asta", "content": "There are quite a few curios on the Express that I've only seen in the catalogs, though I'll need to see them up close before I believe them...", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100303, "next_TalkSentenceID": "500100304", "role": "{NICKNAME}", "content": "Omni-Synthesizer?"}, {"TalkSentenceID": 500100307, "next_TalkSentenceID": "500100308", "role": "{NICKNAME}", "content": "Phonograph?"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100304, "ProtectTime": 0.3, "role": "Asta", "content": "I'm guessing that's one of the prototype synthesizers. There may be a curio code on its base.", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100305, "ProtectTime": 0.3, "role": "Asta", "content": "\"Omni\" makes it sound pretty capable, but the Corporation released other models onto the market first. Know why?", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100306, "ProtectTime": 0.3, "role": "Asta", "content": "Well, why make \"omni\" money when you can make ordinary money first~?", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100310}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100308, "ProtectTime": 0.3, "role": "Asta", "content": "Oh, I don't think that's a curio.", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100309, "ProtectTime": 0.3, "role": "Asta", "content": "It's amazing how many songs are stored in its library. Someone must have paid big money to get all the copyright issues sorted.", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100310}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100310, "ProtectTime": 0.3, "role": "Asta", "content": "Hmm... there's something weird about that sofa over there...", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100311, "ProtectTime": 0.3, "role": "Asta", "content": "{NICKNAME}, how come it lets you sit on it, but not me?", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100312, "ProtectTime": 0.3, "role": "Asta", "content": "Perhaps it has its own set of authorization mechanisms, and only those it recognizes are permitted to sit on it.", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100313, "ProtectTime": 0.3, "role": "Asta", "content": "Interesting... I'll have to have a closer look some time.", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.EndPerformance"}]
4 | [{"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100402, "ProtectTime": 0.3, "role": "Asta", "content": "Huh... there's something strange about that sofa.", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100403, "ProtectTime": 0.3, "role": "Asta", "content": "{NICKNAME}, how come it lets you sit on it, but not me?", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100404, "ProtectTime": 0.3, "role": "Asta", "content": "Perhaps it has its own set of authorization mechanisms, and only those it recognizes are permitted to sit on it.", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100405, "ProtectTime": 0.3, "role": "Asta", "content": "Interesting... I'll have to have a closer look some time.", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.EndPerformance"}]
5 | [{"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100502, "ProtectTime": 0.3, "role": "Asta", "content": "This must be ancient... Is the Express crew still using it?", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100503, "next_TalkSentenceID": "500100504", "role": "{NICKNAME}", "content": "It's aged well."}, {"TalkSentenceID": 500100505, "next_TalkSentenceID": "500100506", "role": "{NICKNAME}", "content": "Yeah, why?"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100504, "ProtectTime": 0.3, "role": "Asta", "content": "Indeed it has.", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100507}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100506, "ProtectTime": 0.3, "role": "Asta", "content": "Oh, nothing. It's just you don't see things like this every day.", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100507}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100507, "ProtectTime": 0.3, "role": "Asta", "content": "Phonographs...They use vinyl records, right? There's only a handful of folks on the space station who collect those things.", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100508, "ProtectTime": 0.3, "role": "Asta", "content": "I think they're more popular with the older crowd — I bet Gunn would know how to use them. Still, if I remember correctly, Gunn only listens to audio tapes...", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100509, "next_TalkSentenceID": "500100510", "role": "{NICKNAME}", "content": "How do you listen to music?"}, {"TalkSentenceID": 500100511, "next_TalkSentenceID": "500100512", "role": "{NICKNAME}", "content": "You seem to know a lot."}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100510, "ProtectTime": 0.3, "role": "Asta", "content": "Same as you guys — on terminals.", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100513}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100512, "ProtectTime": 0.3, "role": "Asta", "content": "About music? I'd say I'm more of a casual listener than a hardcore enthusiast.", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100513}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100513, "ProtectTime": 0.3, "role": "Asta", "content": "Huh... What with them being so big and heavy, I used to wonder why anyone would ever buy a Phonograph. Think I'm starting to get it now.", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100514, "ProtectTime": 0.3, "role": "Asta", "content": "It's hard to find the music you want on the streaming platforms managed by the IPC. I, for one, had to subscribe to at least a dozen of them.", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100515, "ProtectTime": 0.3, "role": "Asta", "content": "It's not like they're expensive or anything. It's just a hassle to switch between them to find the song you like. With a Phonograph, it's essentially just plug-and-play. It also scratches the itch to collect things I guess.", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.EndPerformance"}]
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # StarrailDialogue
2 |
3 | This is a small personal project that extracts Honkai: Star Rail text corpus (including dialogue and miscellaneous items).
4 |
5 | 本项目用于抽取崩坏:星穹铁道游戏的对话等文本语料
6 |
7 | ## Example
8 |
9 | ~~~
10 | # A message with Trailblazor
11 | {
12 | "contacts": [
13 | {
14 | "Name": "黑塔",
15 | "SignatureText": "此号停用 | 商务联系:艾丝妲",
16 | "ContactsCamp": "空间站「黑塔」"
17 | }
18 | ],
19 | "messages": [
20 | {
21 | "Sender": "黑塔",
22 | "MainText": "嘿,{NICKNAME},我是黑塔,有好事找你"
23 | },
24 | {
25 | "Sender": "黑塔",
26 | "MainText": "速来我的办公室!等你!"
27 | },
28 | {
29 | "Sender": "PlayerAuto",
30 | "MainText": "?"
31 | },
32 | {
33 | "Sender": "Player",
34 | "MainText": "可你就在我旁边啊",
35 | },
36 | {
37 | "Sender": "Player",
38 | "MainText": "你直接说不就行了",
39 | },
40 | {
41 | "Sender": "黑塔",
42 | "MainText": "[自动回复]您好,我现在有事不在,一会也不会和您联系",
43 | },
44 | {
45 | "Sender": "PlayerAuto",
46 | "MainText": "? ?",
47 | },
48 | {
49 | "Sender": "System",
50 | "MainText": "{NICKNAME}",
51 | }
52 | ]
53 | }
54 | ~~~
55 |
56 | ### Disclaimer
57 |
58 | While this project depends on the legendary [Dim's StarRailData](https://github.com/Dimbreath/StarRailData) project, there are other more plausible data sources to achieve the same thing:
59 |
60 | - For example, go to the relevant fandom statistics page: https://honkai-star-rail.fandom.com/wiki/Special:Statistics, and download directly the wiki data dump.
61 |
62 | 
63 |
64 | ### Steps
65 |
66 | The logic is simple:
67 | 1. Git clone [Dim's StarRailData](https://github.com/Dimbreath/StarRailData)
68 | 2. Git clone and cd to this repo
69 | 3. Run the extraction codes by specifying Dim's starrail data path
70 |
71 |
72 | ### Feature Support
73 |
74 | What data can be extracted:
75 |
76 | - [ ] Dialogue
77 | - [x] Messages: Text communications that the Trailblazer receives from other Characters and NPCs. [\[Ref\]](https://honkai-star-rail.fandom.com/wiki/Messages)
78 | - [x] Train visitor: Characters will periodically appear in the Astral Express and can be interacted with. [\[Ref\]](https://honkai-star-rail.fandom.com/wiki/Visitor_Verification)
79 | - [x] Story
80 | - [ ] etc
81 | - [ ] Misc
82 | - [x] Books
83 | - [x] Missions
84 | - [x] Items
85 | - [x] Maze buffs
86 | - [x] avatars
87 | - [ ] etc
88 |
89 | Note:
90 | - Extraction results are stored in the "data" folder. I won't provide full extraction results, please run the code yourself to get full extracted data.
91 | - Bugs or data problems possibly exist, feel free to PR (although the author is not very active...)
92 | - There are string variables (e.g., "{NICKNAME}" stands for trailblazer's name) in the corpus.
93 | - **Known Issues**:
94 | - There is incorrect / non-existent text hash, hence some texts would be shown as "N/A"
95 |
96 | #### Dialogues
97 |
98 | The resulting extraction remains structured, waiting for you to be further processed.
99 |
100 | - Code
101 |
102 | ~~~
103 | python get_dialogues.py --lang=CHS --repo=PATH_TO_STARRAIL_DATA
104 | ~~~
105 |
106 | - Example
107 |
108 | ~~~
109 | # messages.jsonl
110 | {"ID": 1150300, "StartMessageItemIDList": [115030004], "IsPerformMessage": true, "contacts": [{"ID": 1004, "Name": "瓦尔特", "IconPath": "SpriteOutput/AvatarRoundIcon/1004.png", "SignatureText": "列车组各位,随时保持联系", "ContactsType": 1, "ContactsCamp": "星穹列车"}], "messages": [{"ID": 115030004, "Sender": "瓦尔特", "ItemType": "Text", "MainText": "初步的研究结果出来了。这种空间扭曲现象催生的实体被称作「拟造花萼」", "OptionText": "N/A", "NextItemIDList": [115030005], "SectionID": 1150300}, {"ID": 115030005, "Sender": "瓦尔特", "ItemType": "Text", "MainText": "这种实体中可能存有具备实用价值的现实资料。你们与其接触时要倍加小心,空间扭曲现象的本质就是无法解释的混沌,很难判断其中是否还蕴藏着某些危险要素", "OptionText": "N/A", "NextItemIDList": [], "SectionID": 1150300}]}
111 |
112 | # train_visitor.jsonl
113 | [{"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100102, "ProtectTime": 0.3, "role": "艾丝妲", "content": "如果在列车的这个位置布设空间望远镜,应该能观测到更短的波长……", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100103, "ProtectTime": 0.3, "role": "艾丝妲", "content": "嗯,方向感觉还不错。", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100104, "next_TalkSentenceID": "500100105", "role": "{NICKNAME}", "content": "有新论文选题?"}, {"TalkSentenceID": 500100107, "next_TalkSentenceID": "500100108", "role": "{NICKNAME}", "content": "刚刚在工作吗?"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100105, "ProtectTime": 0.3, "role": "艾丝妲", "content": "是呀。之前苦思冥想不得其法,一登上列车,想法就接二连三地冒出来,灵感真是折磨人的小妖怪。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100106, "ProtectTime": 0.3, "role": "艾丝妲", "content": "这篇论文或许和列车有关…到时候还要请你们接受我的采访,多多提供一手资料。", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100110}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100108, "ProtectTime": 0.3, "role": "艾丝妲", "content": "当然不是啦。我又不是以工作名义来参观的,而是受到了{NICKNAME}的邀请,对吧?", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100109, "ProtectTime": 0.3, "role": "艾丝妲", "content": "所以在登上列车的那一刻,我就迫不及待调整至休假模式,全力睁大眼睛,寻找下一个论文选题。", "type": "PlayAndWaitSimpleTalk"}], "next_TalkSentenceID": 500100110}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100110, "ProtectTime": 0.3, "role": "艾丝妲", "content": "——这些都不是重点啦。", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 500100111, "next_TalkSentenceID": "500100112", "role": "{NICKNAME}", "content": "今天就好好享受列车时光吧。"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100112, "ProtectTime": 0.3, "role": "艾丝妲", "content": "说得没错。", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.PlayAndWaitSimpleTalk", "conversations": [{"TalkSentenceID": 500100113, "ProtectTime": 0.3, "role": "艾丝妲", "content": "虽然之前也来过列车,不过有{NICKNAME}在的列车,我还是第一次来呢。", "type": "PlayAndWaitSimpleTalk"}, {"TalkSentenceID": 500100114, "ProtectTime": 0.3, "role": "艾丝妲", "content": "我很期待哦。", "type": "PlayAndWaitSimpleTalk"}]}, {"type": "RPG.GameCore.EndPerformance"}]
114 |
115 | # story
116 | [{"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020113, "next_TalkSentenceID": "100020113", "role": "{NICKNAME}", "content": "这…是…哪里?"}, {"TalkSentenceID": 100020115, "next_TalkSentenceID": "100020115", "role": "{NICKNAME}", "content": "卡芙…卡?"}, {"TalkSentenceID": 100020118, "next_TalkSentenceID": "100020118", "role": "{NICKNAME}", "content": "…你是…谁?"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020113, "role": "{NICKNAME}", "content": "这…是…哪里?", "next_TalkSentenceID": 100020121}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020115, "role": "{NICKNAME}", "content": "卡芙…卡?", "next_TalkSentenceID": 100020121}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020118, "role": "{NICKNAME}", "content": "…你是…谁?", "next_TalkSentenceID": 100020121}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020121, "role": "卡芙卡", "content": "听我说:你的脑袋里现在一片混沌。你不清楚自己是谁,为什么在这儿,接下来要做什么;你觉得我很熟悉,却不清楚该不该信任我——"}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020126, "next_TalkSentenceID": "100020126", "role": "{NICKNAME}", "content": "喜欢…大概。"}, {"TalkSentenceID": 100020130, "next_TalkSentenceID": "100020130", "role": "{NICKNAME}", "content": "我不要…"}, {"TalkSentenceID": 100020133, "next_TalkSentenceID": "100020133", "role": "{NICKNAME}", "content": "你要去哪…"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020126, "role": "{NICKNAME}", "content": "喜欢…大概。", "next_TalkSentenceID": 100020136}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020130, "role": "{NICKNAME}", "content": "我不要…", "next_TalkSentenceID": 100020136}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020133, "role": "{NICKNAME}", "content": "你要去哪…", "next_TalkSentenceID": 100020136}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020136, "role": "银狼", "content": "还要说多久?按照剧本,{RUBY_B#「开拓」的旅行者}星穹列车{RUBY_E#}的人就快到了,我们不该跟他们照上面。"}, {"type": "RPG.GameCore.PlayOptionTalk", "options": [{"TalkSentenceID": 100020139, "next_TalkSentenceID": "100020139", "role": "{NICKNAME}", "content": "不,不要…"}, {"TalkSentenceID": 100020140, "next_TalkSentenceID": "100020139", "role": "{NICKNAME}", "content": "别了…"}, {"TalkSentenceID": 100020141, "next_TalkSentenceID": "100020139", "role": "{NICKNAME}", "content": "卡芙…卡…"}]}, {"type": "RPG.GameCore.WaitCustomString", "TalkSentenceID": 100020139, "role": "{NICKNAME}", "content": "不,不要…"}, {"type": "RPG.GameCore.EndPerformance"}]
117 | ~~~
118 |
119 | #### Known Issues
120 |
121 | 1. Some dialogues are incomplete, the relation between different sessions are unknown. (Better check out [hoyo_public_wiki_parser](https://github.com/mrzjy/hoyo_public_wiki_parser) to get more complete dialogues within a quest)
122 |
123 |
124 |
125 | #### Misc
126 |
127 | The resulting extraction contains miscellaneous items
128 |
129 | - Code
130 |
131 | ~~~
132 | python get_misc.py --lang=CHS --repo=PATH_TO_STARRAIL_DATA
133 | ~~~
134 |
135 | - Example
136 |
137 | ~~~
138 | # avatar.jsonl
139 | {"Himeko": {"basic": {"Name": "Himeko", "Camp": "Astral Express", "AvatarVOTag": "himeko", "DamageType": "Fire", "AvatarBaseType": "Mage", "CV": {"CV_CN": "林簌", "CV_JP": "田中理恵", "CV_KR": "김보나", "CV_EN": "Cia Court"}}, "dialogue_lines": [{"title": "First Meeting", "Voice_M": "Welcome aboard, {NICKNAME}. I'm Himeko, navigator of the Astral Express. I'm sure we're about to embark on a fascinating journey together.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Greeting", "Voice_M": "Hey there. Tired? How about a coffee?", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Parting", "Voice_M": "Relax, Pom-Pom and I are here to take care of the train.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "About Self: Relationship With the Astral Express", "Voice_M": "The Express and I are like old friends — we've helped each other a lot. I fixed it so that it could get back to its original route, and in return it takes me to unknown frontiers.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "About Self: Fixing the Express", "Voice_M": "As far as malfunctions on the train are concerned, I've usually dealt with them before you guys even realize anything's amiss.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Chat: Final Stop", "Voice_M": "I'm speculating, but I think the Express departs from Akivili's home world and returns there before starting the journey anew. So the Express doesn't have a \"final stop\" per se — it's up to you where you disembark.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Hobbies", "Voice_M": "I have a knack for blending different coffee beans. Can I make you a cup? I recommend not adding milk — black coffee is proper coffee.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Annoyances", "Voice_M": "Seems like only Dan Heng has a kind word for my coffee. What do you think? ...Well, I've always thought that bitter is better...", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Something to Share", "Voice_M": "When people live together it seems like nobody ever changes. Only when they're away from each other for a long time do surprising transformations occur...", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Knowledge", "Voice_M": "When we scour the universe for the things we seek, most of the time it's less of a \"Eureka, I found it!\" moment and more of a \"Is this it? What if I'm wrong?\" moment.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "About March 7th", "Voice_M": "If I woke up on the Express without any memories, there's no way I'd recover as quickly as March has. I admire her for that, but I also worry about her... If I'm ever too busy, please do take the time to chat with her.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "About Dan Heng", "Voice_M": "Dan Heng is the cautious type — I think he's gotten used to doing things by himself. He doesn't like to bother or inconvenience others. That's why I asked him to be the train's guard and maintain the data bank. I don't think he'd have joined us if there wasn't anything for him to do.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "About Welt", "Voice_M": "I think Welt's anxious that he can't keep up with you youngsters, but he sure seems young at heart to me...", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "About Pom-Pom", "Voice_M": "To Pom-Pom, I'll always be the train's mechanic, haha.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "About Dan Heng 2", "Voice_M": "He's not so uptight nowadays. I think {NICKNAME} and March helped him understand that relying on others sometimes really isn't a big deal.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Battle Begins: Weakness Break", "Voice_M": "I have something for you.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Battle Begins: Danger Alert", "Voice_M": "Be careful.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Turn Begins 1", "Voice_M": "Combat is a series of precise machinations.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Turn Begins 2", "Voice_M": "Time for a detailed overhaul.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Turn Idling", "Voice_M": "It won't be so easy next time.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Basic ATK", "Voice_M": "One at a time!", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Skill", "Voice_M": "Come one, come all.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Hit by Light Attack", "Voice_M": "Barely felt it.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Hit by Heavy Attack", "Voice_M": "You've got a lot of nerve.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Ultimate: Activate", "Voice_M": "Perhaps you still don't understand...", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Ultimate: Unleash", "Voice_M": "Humanity never conceals its desire to control the heavens... And I'm no exception.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Talent", "Voice_M": "You won't get away!", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Downed", "Voice_M": "I was... just getting... started...", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Return to Battle", "Voice_M": "Did you miss me?", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Health Recovery", "Voice_M": "Much obliged.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Technique", "Voice_M": "Burn away.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Battle Won", "Voice_M": "One step closer to the stars.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Treasure Opening", "Voice_M": "This is also the reward of trailblazing.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Successful Puzzle-Solving", "Voice_M": "Sometimes you gotta do things yourself.", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Enemy Target Found", "Voice_M": "Practice target?", "Voice_F": "N/A", "UnlockDesc": "N/A"}, {"title": "Returning to Town", "Voice_M": "Fancy a coffee?", "Voice_F": "N/A", "UnlockDesc": "N/A"}], "story": {"1": "An adventurous scientist who encountered the Astral Express as a young woman when it got stranded in her homeworld.\\nYears later, when Himeko finally repaired the Express and began her journey into the stars, she realized that this is only the beginning. On her journey to trailblaze new worlds, she would need many more companions...\\nAnd while they may have different destinations, they all gaze at the same starry sky.", "2": "The girl is lost.\\n\\nShe can't remember when she became lost. She just walked and walked, on and on into the dark night, chasing the sun and the moon, over and over again — until she falls.\\n\\nShe remembers what she looked like as a college freshman, remembers her chosen major — interstellar travel dynamics — and now she's lying face down in the mud.\\n\\nShe looks up to the stars, and just then sees meteors streaming down: one, two, three... And then more, smaller ones, flickering and flashing ever so finely before a magnificent blaze tore open the night.\\n\\nHer limbs drag her forward, leading her on to where the land meets the ocean. At the shoreline, the waters jostle against her like how the tide treats that stranded Express, alone and lost.\\n\\nShe walks in and see the scenery outside beginning to change. The Express shows her a myriad of magnificent worlds. They are faraway, beyond her homeworld, yet also close enough to be a simple train ride.\\n\\nShe tries to repair the Express. It starts up only briefly, but it is enough to skid across the sky of her home. She immediately sees the path home. From that altitude, the journey is so short, and even the ocean of her homeworld appears so insignificant.\\n\\nIt asked her whether she'd like to travel together. She wonders what kind of journey that would be.\\n\\n\"A journey to the beginning.\"\\n\"Let's go then.\" Without hesitation, the girl replied, \"Just as you brought me home, so would I take you home, too.\"", "3": "Himeko has a suitcase.\\n\\nThis suitcase is her treasure trove. Previously, she'd filled it with all kinds of train repair tools to fix up the Express. But now, it's packed with a molecular saw, an escaped satellite, and countless other contraptions — the embodiment of her whims and the proof of her resolute will.\\n\\nNo travel companion is more faithful to her than this suitcase. Passengers come and go on the Express, and perhaps not even \"the conductor\" would be able to accompany her and the Express from start to finish.\\n\\nBut she doesn't care. She didn't care when that pretentious blond man left without saying goodbye, just like how she didn't care about her distant homeworld and old friends.\\n\\nShe knows that this journey is lonely. Even if she could get to know like-minded travel companions, even if they showed her generous grace, even if she could witness the end of a complete journey with those companions — that's all just a momentary fluke.\\n\\nShe knows that this journey is lonely. Nobody can follow in the exact same footsteps as anyone else. Nobody can experience for someone else everything that happens along a journey. All she can rely on are her own two eyes and feet.\\n\\nThat's why she stores inside her suitcase all the sights her eyes have witnessed, and all the footprints her feet have left behind.", "4": "Himeko's memory is very good.\\n\\nThe longer the journey gets, the more travel companions she accrues. She can still remember many of them.\\n\\nShe remembers her awkward chit-chats with Pom-Pom, and how the first two passengers aboard the Express were Welt and his blond friend. She remembers how the taciturn Dan Heng defeated the monsters that can swallow stars with just one strike of his lance. She remembers how March 7th had awoken from her icy slumber, all the outfits she'd designed for March, and what March loves the most. She remembers how the Crew arrived at Herta Space Station, how she met {NICKNAME}, and how they'd embarked on a new journey once more.\\n\\nShe remembers the specifications for every single component of the Express and how they're assembled. She remembers when to oil the Express's bearings and when each plant on the Express needs watering. She remembers Pom-Pom's non-negotiable bottom line, and that Welt has rather juvenile hobbies. She remembers that Dan Heng is always pulling all-nighters to organize the data bank, and how March 7th loves to sleep in. She remembers the personality, habits, hobbies, birthdays, and other anniversary days of everyone aboard the Express. And she remembers much, much more.\\n\\nThe greatest pleasure for Himeko is that everyone can safely reach their destination on the Express.\\n\\n\"Traveling always has an end point. When it happens, I'll smile and say goodbye to everyone.\"\\n\\nShe always says that, and she'll definitely remember to do that.\\n\\nIt's memory that has formed the road she came from, and memory that will eventually return her to the seas from whence she came.", "5": "\"What a long journey.\" She says.\\n\\n\"I've been waiting for so, so long.\"████ looks at her: \"It wasn't bad luck that has led you down this path, but wanderlust and curiosity.\"\\n\\n\"Of course,\" she smiles, \"But I've experienced far less than what you've been through.\"\\n\\n\"No, I've never experienced the things you have.\" ████ shakes their head: \"There are as many routes as there are pairs of feet.\"\\n\\n\"Right now, we may be standing in the same place, but we harbor different thoughts and views.\"\\n\\nTogether, they look up to the stars in silence, and just then saw meteors streaming down: one, two, three... And then more, smaller ones, flickering and flashing ever so finely before a magnificent blaze tore open the night.\\n\\nA quiet voice disturbs the still air once more: \"What do you see?\"\\n\\n\"The stars have finished their journey.\" She says.\\n\\n████ laughs: \"I, instead, see that their journey is only just beginning.\"\\n\\nThey do not speak again.\\n\\n\"Let's go back. They're waiting for me.\"\\n\\n████ is silent, then asks: \"Has the journey so far made you happy?\"\\n\\nShe picks up her suitcase and walks back in the direction of the Express without looking back.\\n\\n\"Same as always.\""}, "skill": [{"SkillName": "Sawblade Tuning", "SkillTag": "Single Target", "SkillTypeDesc": "Basic ATK", "levels": [{"1": {"SkillDesc": "Deals Fire DMG equal to 50% of Himeko's ATK to a single enemy.", "SimpleSkillDesc": "Deals minor Fire DMG to a single enemy."}}, {"2": {"SkillDesc": "Deals Fire DMG equal to 60% of Himeko's ATK to a single enemy.", "SimpleSkillDesc": "Deals minor Fire DMG to a single enemy."}}, {"3": {"SkillDesc": "Deals Fire DMG equal to 70% of Himeko's ATK to a single enemy.", "SimpleSkillDesc": "Deals minor Fire DMG to a single enemy."}}, {"4": {"SkillDesc": "Deals Fire DMG equal to 80% of Himeko's ATK to a single enemy.", "SimpleSkillDesc": "Deals minor Fire DMG to a single enemy."}}, {"5": {"SkillDesc": "Deals Fire DMG equal to 90% of Himeko's ATK to a single enemy.", "SimpleSkillDesc": "Deals minor Fire DMG to a single enemy."}}, {"6": {"SkillDesc": "Deals Fire DMG equal to 1% of Himeko's ATK to a single enemy.", "SimpleSkillDesc": "Deals minor Fire DMG to a single enemy."}}, {"7": {"SkillDesc": "Deals Fire DMG equal to 110% of Himeko's ATK to a single enemy.", "SimpleSkillDesc": "Deals minor Fire DMG to a single enemy."}}, {"8": {"SkillDesc": "Deals Fire DMG equal to 120% of Himeko's ATK to a single enemy.", "SimpleSkillDesc": "Deals minor Fire DMG to a single enemy."}}, {"9": {"SkillDesc": "Deals Fire DMG equal to 130% of Himeko's ATK to a single enemy.", "SimpleSkillDesc": "Deals minor Fire DMG to a single enemy."}}]}, {"SkillName": "Molten Detonation", "SkillTag": "Blast", "SkillTypeDesc": "Skill", "levels": [{"1": {"SkillDesc": "Deals Fire DMG equal to 1% of Himeko's ATK to a single enemy and Fire DMG equal to 40% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"2": {"SkillDesc": "Deals Fire DMG equal to 110% of Himeko's ATK to a single enemy and Fire DMG equal to 44% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"3": {"SkillDesc": "Deals Fire DMG equal to 120% of Himeko's ATK to a single enemy and Fire DMG equal to 48% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"4": {"SkillDesc": "Deals Fire DMG equal to 130% of Himeko's ATK to a single enemy and Fire DMG equal to 52% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"5": {"SkillDesc": "Deals Fire DMG equal to 140% of Himeko's ATK to a single enemy and Fire DMG equal to 56% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"6": {"SkillDesc": "Deals Fire DMG equal to 150% of Himeko's ATK to a single enemy and Fire DMG equal to 60% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"7": {"SkillDesc": "Deals Fire DMG equal to 163% of Himeko's ATK to a single enemy and Fire DMG equal to 65% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"8": {"SkillDesc": "Deals Fire DMG equal to 175% of Himeko's ATK to a single enemy and Fire DMG equal to 70% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"9": {"SkillDesc": "Deals Fire DMG equal to 188% of Himeko's ATK to a single enemy and Fire DMG equal to 75% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"10": {"SkillDesc": "Deals Fire DMG equal to 2% of Himeko's ATK to a single enemy and Fire DMG equal to 80% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"11": {"SkillDesc": "Deals Fire DMG equal to 210% of Himeko's ATK to a single enemy and Fire DMG equal to 84% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"12": {"SkillDesc": "Deals Fire DMG equal to 220% of Himeko's ATK to a single enemy and Fire DMG equal to 88% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"13": {"SkillDesc": "Deals Fire DMG equal to 230% of Himeko's ATK to a single enemy and Fire DMG equal to 92% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"14": {"SkillDesc": "Deals Fire DMG equal to 240% of Himeko's ATK to a single enemy and Fire DMG equal to 96% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}, {"15": {"SkillDesc": "Deals Fire DMG equal to 250% of Himeko's ATK to a single enemy and Fire DMG equal to 1% of Himeko's ATK to enemies adjacent to it.", "SimpleSkillDesc": "Deals Fire DMG to a single enemy and minor Fire DMG to enemies adjacent to it."}}]}, {"SkillName": "Heavenly Flare", "SkillTag": "AoE", "SkillTypeDesc": "Ultimate", "levels": [{"1": {"SkillDesc": "Deals Fire DMG equal to 138% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"2": {"SkillDesc": "Deals Fire DMG equal to 147% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"3": {"SkillDesc": "Deals Fire DMG equal to 156% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"4": {"SkillDesc": "Deals Fire DMG equal to 166% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"5": {"SkillDesc": "Deals Fire DMG equal to 175% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"6": {"SkillDesc": "Deals Fire DMG equal to 184% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"7": {"SkillDesc": "Deals Fire DMG equal to 196% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"8": {"SkillDesc": "Deals Fire DMG equal to 207% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"9": {"SkillDesc": "Deals Fire DMG equal to 218% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"10": {"SkillDesc": "Deals Fire DMG equal to 230% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"11": {"SkillDesc": "Deals Fire DMG equal to 239% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"12": {"SkillDesc": "Deals Fire DMG equal to 248% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"13": {"SkillDesc": "Deals Fire DMG equal to 258% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"14": {"SkillDesc": "Deals Fire DMG equal to 267% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}, {"15": {"SkillDesc": "Deals Fire DMG equal to 276% of Himeko's ATK to all enemies. Himeko regenerates 5 extra Energy for each enemy defeated.", "SimpleSkillDesc": "Deals Fire DMG to all enemies and regenerates Energy if enemies are defeated."}}]}, {"SkillName": "Victory Rush", "SkillTag": "AoE", "SkillTypeDesc": "Talent", "levels": [{"1": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 70% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"2": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 77% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"3": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 84% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"4": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 91% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"5": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 98% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"6": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 105% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"7": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 114% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"8": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 122% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"9": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 131% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"10": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 140% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"11": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 147% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"12": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 154% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"13": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 161% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"14": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 168% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}, {"15": {"SkillDesc": "When an enemy is inflicted with Weakness Break, Himeko gains 1 point of Charge (max 3 points).\\nIf Himeko is fully Charged when an ally performs an attack, Himeko immediately performs 1 follow-up attack and deals Fire DMG equal to 175% of her ATK to all enemies, consuming all Charge points.\\nAt the start of the battle, Himeko gains 1 point of Charge.", "SimpleSkillDesc": "Gains a Charge stack when an enemy's Weakness is Broken.\\nAfter an ally performs an attack, if fully Charged, immediately performs a follow-up attack and deal Fire DMG to all enemies, consuming all Charge stacks.\\nGains 1 Charge stack at the start of each battle."}}]}, {"SkillName": "Attack", "SkillTag": null, "SkillTypeDesc": null, "levels": [{"1": {"SkillDesc": "Attack an enemy, and when the battle starts, reduce their Toughness of the corresponding Type.", "SimpleSkillDesc": "N/A"}}]}, {"SkillName": "Incomplete Combustion", "SkillTag": "Impair", "SkillTypeDesc": "Technique", "levels": [{"1": {"SkillDesc": "After using Technique, creates a dimension that lasts for 15 second(s). After entering battle with enemies in the dimension, there is a 1% base chance to increase Fire DMG taken by enemies by 10% for 2 turn(s). Only 1 dimension created by allies can exist at the same time.", "SimpleSkillDesc": "Creates a dimension. After entering combat with enemies in the dimension, there is a high chance to increase Fire DMG taken by enemies."}}]}]}}
140 |
141 | # books.jsonl
142 | {"BookSeriesID": 1, "BookSeries": "Floriography Manual Attached to a Bouquet", "BookSeriesComments": "Contains commonly used floriography in Belobog. The ways of the world are condensed into this manual.", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
143 |
144 | # items.jsonl
145 | {"ID": 2, "ItemMainType": "Virtual", "ItemSubType": "Virtual", "InventoryDisplayTag": 1, "Rarity": "Rare", "PurposeType": 11, "ItemName": "Credit", "ItemDesc": "This currency, used by the Interastral Peace Corporation to settle accounts with its customers, is now widely accepted as the hard currency for space travel.", "ItemBGDesc": "\"People run around fighting and trading for numbers in a terminal, but the truly precious cannot be bought.\"", "ItemIconPath": "SpriteOutput/ItemIcon/2.png", "ItemFigureIconPath": "SpriteOutput/ItemFigures/2.png", "ItemCurrencyIconPath": "SpriteOutput/ItemCurrency/2.png", "ItemAvatarIconPath": "", "PileLimit": 999999999, "CustomDataList": [], "ReturnItemIDList": []}
146 |
147 | # maze_buff.jsonl
148 | {"1": {"ID": 100201, "BuffSeries": 1, "BuffRarity": 1, "Lv": 1, "LvMax": 1, "ModifierName": "ADV_StageAbility_Maze_DanHeng", "InBattleBindingType": "CharacterSkill", "InBattleBindingKey": "SkillMaze", "ParamList": [], "BuffDescParamByAvatarSkillID": 100207, "BuffIcon": "SpriteOutput/BuffIcon/Inlevel/Icon1002Maze.png", "BuffName": "Splitting Spearhead", "BuffDesc": "At the start of the next battle, Dan Heng's ATK increases by #1[i]% for #2[i] turn(s).", "BuffSimpleDesc": "N/A", "BuffDescBattle": "At the start of the next battle, Dan Heng's ATK increases by #1[i]% for #2[i] turn(s).", "BuffEffect": "MazeBuffEffect_100201", "MazeBuffType": "Character", "MazeBuffIconType": "Other", "MazeBuffPool": 3, "IsDisplay": true}}
149 | ~~~
150 |
151 | #### Misc
152 |
153 | The resulting extraction contains missions information
154 |
155 | - Code
156 |
157 | ~~~
158 | python get_missions.py --lang=CHS --repo=PATH_TO_STARRAIL_DATA
159 | ~~~
160 |
161 | - Example
162 |
163 | ~~~
164 | {
165 | "mission": {
166 | "name": "Eye of the Storm",
167 | "next_missions": [],
168 | "next_track_mission": 1000202,
169 | "chapter_id": 100001,
170 | "reward_id": 11000201
171 | },
172 | "submissions": [
173 | {
174 | "submission_ids": [
175 | 100020104,
176 | 100020105
177 | ],
178 | "target": "Leave and see what's going on outside",
179 | "desc": "You move your gaze away from the unfamiliar ceiling and look at the variety of Curios around you. A woman's voice echoes in your head but you're in no position to confirm whether you can clearly remember it.\\n\"When you have a chance to make a choice, make one that you know you won't regret...\"\\nYou decide to first listen to March 7th and Dan Heng, who had just left. At least they don't look like bad people.\\nAlright, now that there are companions, it's time to leave this place."
180 | },
181 | {
182 | "submission_ids": [
183 | 100020106,
184 | 100020107
185 | ],
186 | "target": "Listen to March 7th and protect yourself",
187 | "desc": "You keep thinking about what March 7th said: \"The Legion are rampaging through the space station like a pack of wolves...\" It seems like the road ahead is going to be a hard one.\\nYou look at March 7th beside you and she nods at you with confidence.\\nYou then look at the baseball bat in your hand, which is glowing as if also full of confidence.\\nYou can do it too, give it a try!"
188 | },
189 | {
190 | "submission_ids": [
191 | 100020131,
192 | 100020109
193 | ],
194 | "target": "Go to the elevator in the central area",
195 | "desc": "It would seem that the only way to the space station's master control zone is through the elevator — it doesn't seem to be too far."
196 | }
197 | ]
198 | }
199 | ~~~
200 |
201 | ## FAQs
202 |
203 | 1. Interested in some game corpus?
204 |
205 | - [GenshinDialog](https://github.com/mrzjy/GenshinDialog)
206 | - [StarrailDialog](https://github.com/mrzjy/StarrailDialogue)
207 | - [ZZZDialog](https://github.com/mrzjy/ZZZDialog)
208 | - [HonkaiImpact3rdDialog](https://github.com/mrzjy/HonkaiImpact3rdDialog)
209 | - [ArknightsDialog](https://github.com/mrzjy/ArknightsDialog)
210 | - [WutheringDialog](https://github.com/mrzjy/WutheringDialog)
211 | - [hoyo_public_wiki_parser](https://github.com/mrzjy/hoyo_public_wiki_parser): Parse Hoyoverse public wiki data
212 |
--------------------------------------------------------------------------------
/data/misc/CHS/books.jsonl:
--------------------------------------------------------------------------------
1 | {"BookSeriesID": 1, "BookSeries": "随花束附赠的花语手册", "BookSeriesComments": "收录了贝洛伯格常用花语,人情世故浓缩在手册之中。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
2 | {"BookSeriesID": 2, "BookSeries": "咖啡师的手账残页", "BookSeriesComments": "从某本秘密手账上脱落下来的手写活页。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
3 | {"BookSeriesID": 3, "BookSeries": "《矿山员工安全手册》", "BookSeriesComments": "磨损严重的安全手册。下层区矿工人手一本。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
4 | {"BookSeriesID": 4, "BookSeries": "关于煦日节庆典相关事宜的安排", "BookSeriesComments": "不知何年的贝洛伯格传统节日庆典安排…但至少不是今年的。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
5 | {"BookSeriesID": 5, "BookSeries": "矿业机械订单列表", "BookSeriesComments": "一张矿业机械调度订单列表。见证了下层区尚且繁荣时货物与资金的流向。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
6 | {"BookSeriesID": 6, "BookSeries": "《「淬火工坊」全新启程!》", "BookSeriesComments": "「淬火工坊」的巨幅广告,张贴的位置就像纸上那些极具冲击力的大字一样惹眼。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
7 | {"BookSeriesID": 7, "BookSeries": "天桥下的告示", "BookSeriesComments": "一系列告示,似乎是铆钉镇居民在撤离时匆匆张贴在小镇入口的。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
8 | {"BookSeriesID": 8, "BookSeries": "《秘传:爆辣蓬蓬菇》", "BookSeriesComments": "铆钉镇餐厅名菜的保姆级教程。内容似乎是最高机密,但还是被你看见了。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
9 | {"BookSeriesID": 9, "BookSeries": "大矿区排班表", "BookSeriesComments": "矿工们每次开工时会在表上写明工作轮次和内容,但似乎已经被当作讨论版了。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
10 | {"BookSeriesID": 10, "BookSeries": "《矿工每周报》", "BookSeriesComments": "工头自发筹办的小报,旨让矿工们了解矿区内的生产生活要闻。", "BookSeriesNum": 3, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
11 | {"BookSeriesID": 11, "BookSeries": "《星尘玫瑰》场刊", "BookSeriesComments": "话剧《星尘玫瑰》的场刊,在购票后随票免费发放。", "BookSeriesNum": 3, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
12 | {"BookSeriesID": 12, "BookSeries": "《公民亚历珊德拉》场刊", "BookSeriesComments": "音乐剧《公民亚历珊德拉》的场刊,在购票后随票免费发放。", "BookSeriesNum": 3, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
13 | {"BookSeriesID": 13, "BookSeries": "地火组织的宣传单", "BookSeriesComments": "手写的宣传单,纸质粗糙,却胜在真挚。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
14 | {"BookSeriesID": 14, "BookSeries": "矿队的通牒", "BookSeriesComments": "一封措辞略有些严厉的通牒信,另附涂鸦。从中似乎可以窥见矿区争端从何而来。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
15 | {"BookSeriesID": 15, "BookSeries": "大矿区生活守则", "BookSeriesComments": "一张告示。一板一眼地写着矿石镇居民可以做和禁止做的事。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
16 | {"BookSeriesID": 16, "BookSeries": "「矿灯」巴列维的告顾客信", "BookSeriesComments": "一封巴列维停止公开鉴定古代遗物的公告。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
17 | {"BookSeriesID": 17, "BookSeries": "冰风暴成因", "BookSeriesComments": "一篇对话气候专家的采访,采访中讨论了冰风暴的成因,还涉及到了其它科学小知识。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
18 | {"BookSeriesID": 18, "BookSeries": "春神和战争之神雅利洛", "BookSeriesComments": "一则北方涅咯莎地区的传统神话故事,及民俗学家对该神话的解读分析。", "BookSeriesNum": 2, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
19 | {"BookSeriesID": 19, "BookSeries": "给新手检修员的留言", "BookSeriesComments": "一则贝洛伯格暖气工人之间的留言,与节前暖气管道的安全检修有关。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
20 | {"BookSeriesID": 20, "BookSeries": "煦日节前的日常生活采访", "BookSeriesComments": "一则刊登在《水晶日报•周末版》上的生活调查,煦日节的种种风俗从中可窥见一斑。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
21 | {"BookSeriesID": 21, "BookSeries": "潮流品牌「郊外」", "BookSeriesComments": "一篇潮流品牌「郊外」的杂志广告。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
22 | {"BookSeriesID": 22, "BookSeries": "矿毒清鼻用吸入剂说明书", "BookSeriesComments": "记录了某种处方的纸片,可以使受矿工职业病困扰的下层区居民暂时免受痛苦。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
23 | {"BookSeriesID": 23, "BookSeries": "某个民间科学家的日记", "BookSeriesComments": "质量堪忧的散落纸页,纸片的主人似乎自称是一名科学家。", "BookSeriesNum": 4, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
24 | {"BookSeriesID": 24, "BookSeries": "拼接而成的信件", "BookSeriesComments": "通过拼拼凑凑还原的信件,也许不该还原……", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
25 | {"BookSeriesID": 25, "BookSeries": "冷吃夕红鱼", "BookSeriesComments": "一家餐厅传单上的美食故事,堪称行为艺术的体现。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
26 | {"BookSeriesID": 26, "BookSeries": "穿过风雪的旅人", "BookSeriesComments": "一篇贝洛伯格教材中的课文,内容是一首幸存者迁徙的叙事诗。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
27 | {"BookSeriesID": 27, "BookSeries": "鲑鱼面包", "BookSeriesComments": "旅行杂志的专栏,向读者分享了一种郊外的全新生活方式。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
28 | {"BookSeriesID": 28, "BookSeries": "清唱剧首演盛况", "BookSeriesComments": "历史上清唱剧盛况的剪报,被小心地保存了起来。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
29 | {"BookSeriesID": 29, "BookSeries": "黑面包汽水冰山", "BookSeriesComments": "一则生活方式杂志内页广告,向读者推荐了某种甜点的做法。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
30 | {"BookSeriesID": 30, "BookSeries": "倍润矿脂", "BookSeriesComments": "随处可见的护肤品广告,贝洛伯格人民最熟悉的朋友。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
31 | {"BookSeriesID": 31, "BookSeries": "关于梦境分析", "BookSeriesComments": "精神科医生随手写下的笔记。梦到底几分可信?", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
32 | {"BookSeriesID": 32, "BookSeries": "关于清唱剧排练时间变更通知", "BookSeriesComments": "一则排练变更的通知,用于确保合唱团成员时间安排协调一致。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
33 | {"BookSeriesID": 33, "BookSeries": "贝洛伯格红肠", "BookSeriesComments": "国民美食的传单广告,还附有红肠的种种做法。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
34 | {"BookSeriesID": 34, "BookSeries": "小朋友的信", "BookSeriesComments": "小朋友遗落的信件,看起来是写给另外一个小朋友的。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
35 | {"BookSeriesID": 35, "BookSeries": "《地底百科:植物和菌类》", "BookSeriesComments": "下层区植物生态的研究报告,书内提及绝大多数植物不可食、不好吃;少数条目还附有烹饪指南。", "BookSeriesNum": 2, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
36 | {"BookSeriesID": 36, "BookSeries": "宝石蜥肉串食谱", "BookSeriesComments": "一系列往来信件,内容极其丰富,甚至还包含一份食谱。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
37 | {"BookSeriesID": 37, "BookSeries": "贝洛伯格七大不思议", "BookSeriesComments": "假如你读到这篇文字,以上不思议都将出现在你身边……", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
38 | {"BookSeriesID": 38, "BookSeries": "「黑塔」资产定损清单", "BookSeriesComments": "由「黑塔•万有应物科」拟定的资产定损清单,记载了数千项货损,其赔偿金额堪称天文数字,由星际和平公司一力承担。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
39 | {"BookSeriesID": 39, "BookSeries": "银河公义奖彰", "BookSeriesComments": "由「黑塔•银河法政科」签发的荣誉奖状,旨在表彰「局部正义」的伙伴。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
40 | {"BookSeriesID": 40, "BookSeries": "临时调任执行启示", "BookSeriesComments": "由「黑塔•万有应物科」负责人、奇物再收容小队队长「温命得」颁布的临时调任启示。", "BookSeriesNum": 1, "BookSeriesWorld": 1}
41 | {"BookSeriesID": 41, "BookSeries": "猎彗人来信", "BookSeriesComments": "猎彗人何塞•拉萨罗的天外来信,上面写满了垃圾话。", "BookSeriesNum": 3, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
42 | {"BookSeriesID": 42, "BookSeries": "猎彗人宝藏", "BookSeriesComments": "猎彗人何塞•拉萨罗的秘藏,一场事先张扬的恶作剧。", "BookSeriesNum": 4, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
43 | {"BookSeriesID": 43, "BookSeries": "卡利白秘方", "BookSeriesComments": "软饮界的无冕之王,哥们儿情谊的粗粝切片,携带着致命一蛰的幽默。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
44 | {"BookSeriesID": 44, "BookSeries": "星域界种指要:呜呜伯", "BookSeriesComments": "这本指要在表面上与一般的大部头书籍别无二致,但仔细观察,就会发现它的周身环绕着一层淡淡的星环,如同一个隐蔽的咒语。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
45 | {"BookSeriesID": 45, "BookSeries": "辛-41云乡讯音", "BookSeriesComments": "云乡讯音乃不同星球传回的天外回音,承载着科员故土与亲眷的讯息。由「黑塔•银河法政科」统一审阅并分发至对应科员。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
46 | {"BookSeriesID": 46, "BookSeries": "星神", "BookSeriesComments": "列车智库中的「星神」条目,记述了漫步深空星海之中的神秘存在。", "BookSeriesNum": 14, "BookSeriesWorld": 1}
47 | {"BookSeriesID": 47, "BookSeries": "派系", "BookSeriesComments": "列车智库中的「派系」条目,记述了充盈星海之间的种种派系势力。", "BookSeriesNum": 22, "BookSeriesWorld": 1}
48 | {"BookSeriesID": 48, "BookSeries": "科员荣仓终的日记", "BookSeriesComments": "记录着科员荣仓终不平之声与偏激心绪的纸页。落寞,蒙尘,无人惦念。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
49 | {"BookSeriesID": 49, "BookSeries": "呜呜伯行为考", "BookSeriesComments": "关于界种科藏品、灵质生物「呜呜伯」的行为习性研究报告。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
50 | {"BookSeriesID": 50, "BookSeries": "《黑塔研究图鉴•第七卷•交通》残页", "BookSeriesComments": "该卷图鉴收录了站内所有交通型奇物。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
51 | {"BookSeriesID": 51, "BookSeries": "【科员档案】古恩", "BookSeriesComments": "「黑塔」依照银河通行的隐私安全法收集、储存和使用科员信息。未经授权,相关人员不得向第三方披露任何档案。", "BookSeriesNum": 1, "BookSeriesWorld": 1}
52 | {"BookSeriesID": 52, "BookSeries": "《冒险鼹鼠队:隐形的宝藏》", "BookSeriesComments": "贝洛伯格著名的儿童冒险文学系列最新作", "BookSeriesNum": 7, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
53 | {"BookSeriesID": 53, "BookSeries": "【科员档案】何塞", "BookSeriesComments": "「黑塔」依照银河通行的隐私安全法收集、储存和使用科员信息。未经授权,相关人员不得向第三方披露任何档案。", "BookSeriesNum": 1, "BookSeriesWorld": 1}
54 | {"BookSeriesID": 54, "BookSeries": "【科员档案】荣仓终", "BookSeriesComments": "「黑塔」依照银河通行的隐私安全法收集、储存和使用科员信息。未经授权,相关人员不得向第三方披露任何档案。", "BookSeriesNum": 1, "BookSeriesWorld": 1}
55 | {"BookSeriesID": 55, "BookSeries": "再见黑塔", "BookSeriesComments": "科员洛奇•马丁内斯的诗作。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
56 | {"BookSeriesID": 56, "BookSeries": "宓堂传语", "BookSeriesComments": "自称「宓堂空境解语人」递交的密信,似乎意在劝诫水魈的同谋者。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
57 | {"BookSeriesID": 57, "BookSeries": "黑塔的手稿", "BookSeriesComments": "本书由「星际和平公司」出资赞助,系「天才俱乐部」#83席黑塔所撰写之手稿,阐述关于宇宙、星神、派系及其它一切:「这就是我要写的东西:孤独的神明和盲目的信众。」", "BookSeriesNum": 4, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
58 | {"BookSeriesID": 61, "BookSeries": "黑塔情诗", "BookSeriesComments": "科员洛奇•马丁内斯意图寄予心上人的诗作。", "BookSeriesNum": 3, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
59 | {"BookSeriesID": 62, "BookSeries": "讯号转译录:琥珀2157纪40月-43月", "BookSeriesComments": "讯号转译录:琥珀2157纪40月-43月", "BookSeriesNum": 1, "BookSeriesWorld": 1}
60 | {"BookSeriesID": 63, "BookSeries": "讯号转译录:琥珀2157纪44月-47月", "BookSeriesComments": "讯号转译录:琥珀2157纪44月-47月", "BookSeriesNum": 1, "BookSeriesWorld": 1}
61 | {"BookSeriesID": 64, "BookSeries": "讯号转译录:琥珀2157纪48月-51月", "BookSeriesComments": "讯号转译录:琥珀2157纪48月-51月", "BookSeriesNum": 1, "BookSeriesWorld": 1}
62 | {"BookSeriesID": 65, "BookSeries": "雪国冒险奇谭", "BookSeriesComments": "一套在民间热度极高的幻想小说,似乎已被贝洛伯格查禁……", "BookSeriesNum": 9, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
63 | {"BookSeriesID": 66, "BookSeries": "某个包裹上的附信", "BookSeriesComments": "一种曾经可能毁灭下层区的神秘生物的兴亡史。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
64 | {"BookSeriesID": 67, "BookSeries": "关于宠物「小麻」的日记", "BookSeriesComments": "一份宠物饲养日记…可是「虚拟宠物」能算宠物吗?", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
65 | {"BookSeriesID": 68, "BookSeries": "《地底百科:动物》", "BookSeriesComments": "一份年代久远的下层区动物生态的研究报告,部分动物已经不存于世了。", "BookSeriesNum": 2, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
66 | {"BookSeriesID": 69, "BookSeries": "「矿灯」古代遗物鉴定录", "BookSeriesComments": "一份巴列维留下的古代遗物鉴定记录。", "BookSeriesNum": 2, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
67 | {"BookSeriesID": 70, "BookSeries": "矿工病诊断书", "BookSeriesComments": "一份很有参考价值的矿工症诊断书。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
68 | {"BookSeriesID": 71, "BookSeries": "梅登矿道要塌了", "BookSeriesComments": "一首下层区人人都知道的儿歌,好事者考察了儿歌的出处。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
69 | {"BookSeriesID": 72, "BookSeries": "科鲁泽的信件", "BookSeriesComments": "科鲁泽随身携带的信件,好像未能寄回的家书。", "BookSeriesNum": 1, "BookSeriesWorld": 2}
70 | {"BookSeriesID": 73, "BookSeries": "祖辈的告诫", "BookSeriesComments": "巴拉金先祖留给后人的告诫,简短有力。", "BookSeriesNum": 1, "BookSeriesWorld": 2}
71 | {"BookSeriesID": 74, "BookSeries": "布洛妮娅的信", "BookSeriesComments": "布洛妮娅的信", "BookSeriesNum": 1, "BookSeriesWorld": 2}
72 | {"BookSeriesID": 75, "BookSeries": "瓦赫的信", "BookSeriesComments": "娜塔莎的哥哥瓦赫在被流放至雪原之后秘密寄出的信件。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
73 | {"BookSeriesID": 76, "BookSeries": "「风雪免疫」实验记录", "BookSeriesComments": "娜塔莎的哥哥瓦赫留下的笔记,记录了他为了研制名为「风雪免疫」的药物而进行的一系列相关实验。", "BookSeriesNum": 2, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
74 | {"BookSeriesID": 77, "BookSeries": "希拉的人物剧本", "BookSeriesComments": "由古恩•科诺列夫所创作的希拉人物剧本,其动机笼罩在迷雾中。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
75 | {"BookSeriesID": 78, "BookSeries": "告罪书", "BookSeriesComments": "古恩•科诺列夫因在空间站内执行某种神秘实验而留下的陈情告罪书。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
76 | {"BookSeriesID": 79, "BookSeries": "数据记录:乔安", "BookSeriesComments": "从终端机获取的科员乔安的设备浏览记录,应遵循阅后即焚的隐私保密原则。", "BookSeriesNum": 1, "BookSeriesWorld": 1}
77 | {"BookSeriesID": 80, "BookSeries": "数据记录:梅厄", "BookSeriesComments": "从终端机获取的科员梅厄的设备浏览记录,应遵循阅后即焚的隐私保密原则。", "BookSeriesNum": 1, "BookSeriesWorld": 1}
78 | {"BookSeriesID": 81, "BookSeries": "数据记录:克里斯", "BookSeriesComments": "从终端机获取的科员克里斯的设备浏览记录,应遵循阅后即焚的隐私保密原则。", "BookSeriesNum": 1, "BookSeriesWorld": 1}
79 | {"BookSeriesID": 82, "BookSeries": "艾丝妲的家书", "BookSeriesComments": "艾丝妲与家中长辈的书信往来,不知为何呈现出一场罗生门迷局。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
80 | {"BookSeriesID": 83, "BookSeries": "艾丝妲的购物清单", "BookSeriesComments": "艾丝妲的寰宇采购清单,收件人出乎意料地写着阿兰的名字。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
81 | {"BookSeriesID": 84, "BookSeries": "艾丝妲与「商业精英」的往来邮件副本", "BookSeriesComments": "艾丝妲与发明「宇宙高能元域空间」的「商业精英」的邮件往来副本。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
82 | {"BookSeriesID": 85, "BookSeries": "写给闯入者的便条", "BookSeriesComments": "未卜先知的站长写给闯入者的警示便条,不惩反奖,意在鼓励所有好奇的冒险,嘉奖一切知识的奔流。", "BookSeriesNum": 1, "BookSeriesWorld": 1}
83 | {"BookSeriesID": 86, "BookSeries": "一间好商铺,世代摇钱树", "BookSeriesComments": "废弃的房地产广告传单,现在已经是投资上的负面教材。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
84 | {"BookSeriesID": 87, "BookSeries": "矿业机械采购单据", "BookSeriesComments": "遗落在铆钉镇商业联合会旧址的交易凭证,使人难免怀念铆钉镇过去蓬勃的光景。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
85 | {"BookSeriesID": 89, "BookSeries": "生活物资需求征集", "BookSeriesComments": "一份生活物资需求征集单。看得出大矿区的矿头对于矿民的需求基本还算宽容。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
86 | {"BookSeriesID": 90, "BookSeries": "支离破碎的日记", "BookSeriesComments": "避难硐室中发现的日记残页。从中得以窥见重大事件如何扭转小人物的命运。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
87 | {"BookSeriesID": 91, "BookSeries": "在广场干道增设路障的通知", "BookSeriesComments": "铆钉镇沦陷前夕张贴的公告,意在警示居民注意避险。就结果而言,这一举措之成效微乎其微。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
88 | {"BookSeriesID": 92, "BookSeries": "油腻腻的食评册", "BookSeriesComments": "歌德大饭店主厨与客人之间交流新菜的留言手册,封皮上布满了可疑的油污。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
89 | {"BookSeriesID": 93, "BookSeries": "光荣的铁卫将为我们抵御外敌!", "BookSeriesComments": "可可利亚颁布上下层区封锁令时的动员令。对此,下层区的某些居民发出了不满的声音。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
90 | {"BookSeriesID": 94, "BookSeries": "逐梦者的信件", "BookSeriesComments": "一封未能送抵的信件。将脆弱的信封轻轻开启,你甚至能够听到梦碎的声音。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
91 | {"BookSeriesID": 95, "BookSeries": "「新•淬火工坊」开张!", "BookSeriesComments": "「新•淬火工坊」的宣传广告。你似乎在别处见过类似的名字。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
92 | {"BookSeriesID": 96, "BookSeries": "小朋友(已黑化)的观察日记", "BookSeriesComments": "一篇看起来略显幼稚的观察日记。日记的主人似乎受到了严重的精神创伤。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
93 | {"BookSeriesID": 97, "BookSeries": "军用机械例行检修记录表", "BookSeriesComments": "银鬃铁卫后勤部门定期贴出来的军械保养告示,可惜并没有多少人会去关注。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
94 | {"BookSeriesID": 98, "BookSeries": "说唱大战投票通道开启", "BookSeriesComments": "流浪者营地中某项赛事的宣传画。你有充分的理由怀疑这幅广告出自一名说唱歌手之手。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
95 | {"BookSeriesID": 99, "BookSeries": "会咬人,禁止靠近!", "BookSeriesComments": "稚嫩的笔迹写成的便条。大概是某个机器人机生轨迹的缩影。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
96 | {"BookSeriesID": 100, "BookSeries": "拳手招募单", "BookSeriesComments": "下层区到处派发的搏击俱乐部招聘传单,上面的宣传词写得比说唱还动听。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
97 | {"BookSeriesID": 101, "BookSeries": "《贝洛伯格的音乐家》", "BookSeriesComments": "装订成册且充满童心的童话故事。但由于某些原因,原本完整的故事现已散佚。", "BookSeriesNum": 7, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
98 | {"BookSeriesID": 102, "BookSeries": "未能寄出的家书", "BookSeriesComments": "脆弱的信纸上记录了贝洛伯格过去的严寒与温馨,这是过去的余温与某种见证。", "BookSeriesNum": 2, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
99 | {"BookSeriesID": 103, "BookSeries": "歌德宾馆下午茶餐单", "BookSeriesComments": "记录着歌德宾馆知名下午茶餐点的餐单,上面残留着贝洛伯格往昔的丰足与快乐。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
100 | {"BookSeriesID": 104, "BookSeries": "集会所的留言板", "BookSeriesComments": "「地火」组织的留言板,记录了平日里发生的各种闲杂琐事。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
101 | {"BookSeriesID": 105, "BookSeries": "致「炉心」基地全体的一封信", "BookSeriesComments": "一封来自过去的信,现由某位沉默且威严的机械生命体小心翼翼地保管着。之于其所有者,似乎蕴含着非凡的意义。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
102 | {"BookSeriesID": 106, "BookSeries": "边缘通路的旧报纸", "BookSeriesComments": "几篇过去的简报,以碎片化的形式记录了贝洛伯格多年之前的样貌。", "BookSeriesNum": 3, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
103 | {"BookSeriesID": 107, "BookSeries": "《歌德宾馆的夜与雾》", "BookSeriesComments": "某位以写爱情故事闻名的编剧,以歌德宾馆的历史为原型写下的小说,可惜现存的只有残本。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
104 | {"BookSeriesID": 108, "BookSeries": "《雪国往事》场刊", "BookSeriesComments": "歌剧《雪国往事》的场刊,在购票后随票免费发放。", "BookSeriesNum": 3, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
105 | {"BookSeriesID": 109, "BookSeries": "《蕾莎与特鲁太太》场刊", "BookSeriesComments": "音乐剧《蕾莎与特鲁太太》的场刊,在购票后随票免费发放。", "BookSeriesNum": 3, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
106 | {"BookSeriesID": 110, "BookSeries": "《路边野餐》场刊", "BookSeriesComments": "话剧《路边野餐》的场刊,在购票后随票免费发放。", "BookSeriesNum": 3, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
107 | {"BookSeriesID": 111, "BookSeries": "《白衣之后》场刊", "BookSeriesComments": "音乐剧《白衣之后》的场刊,在购票后随票免费发放。", "BookSeriesNum": 3, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
108 | {"BookSeriesID": 112, "BookSeries": "记长乐天夜游", "BookSeriesComments": "刊载在文学评议板块的杂文一篇,仙舟上忧思感怀之人总是有一些长篇大论。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
109 | {"BookSeriesID": 113, "BookSeries": "评至味盛苑 九和宴", "BookSeriesComments": "有关至味盛苑九和宴的食评,虽说文字详尽到感同身受,但是这字里行间难免有些刁钻。", "BookSeriesNum": 1, "BookSeriesWorld": 3}
110 | {"BookSeriesID": 114, "BookSeries": "星槎安全检查指南", "BookSeriesComments": "由天舶司印发,星槎进出流云渡时所需安全检查的官方指南。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
111 | {"BookSeriesID": 115, "BookSeries": "星槎进出港报告管理办法", "BookSeriesComments": "由天舶司印发,星槎进出港提交申请报告的规章制度。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
112 | {"BookSeriesID": 116, "BookSeries": "这星芋啵啵也太难喝了吧", "BookSeriesComments": "从电子屏上截取下来的论坛记录,讨论了星槎海的茶饮口味问题。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
113 | {"BookSeriesID": 117, "BookSeries": "回到未来", "BookSeriesComments": "仙舟星槎品牌「飞梭」推出的新款民用星槎广告。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
114 | {"BookSeriesID": 118, "BookSeries": "轻雨浥尘", "BookSeriesComments": "星槎海著名旅店「浥尘客栈」的广告。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
115 | {"BookSeriesID": 119, "BookSeries": "化外民也能学会的地道仙舟俗语100句", "BookSeriesComments": "由天舶司推出,方便化外民了解仙舟文化的小册子。", "BookSeriesNum": 3, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
116 | {"BookSeriesID": 120, "BookSeries": "《「克拉拉保护协会」成员火热募集中》", "BookSeriesComments": "字里行间充斥着异样热情的招募通告。只要加入该协会,你便能加入到保护克拉拉的行列当中。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
117 | {"BookSeriesID": 121, "BookSeries": "娜塔莎的名单", "BookSeriesComments": "对于工作繁重的地底居民来说,照顾孩子是生活中另一项难以忍受的苦役。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
118 | {"BookSeriesID": 122, "BookSeries": "密密麻麻的日程", "BookSeriesComments": "看过这份日程表的人,将会明白布洛妮娅为何叹气不断。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
119 | {"BookSeriesID": 123, "BookSeries": "《铁卫禁区近况汇总及相关意见》", "BookSeriesComments": "以严肃语气写成的信件报告,汇总了银鬃铁卫全军上下近期的状况。似乎出自现任铁卫情报官之手。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
120 | {"BookSeriesID": 124, "BookSeries": "水晶日报的送审样稿", "BookSeriesComments": "寒潮初至时被送往银鬃铁卫高层审核的新闻样稿。记载了初代大守护者阿丽萨•兰德持炎枪作战的英姿", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
121 | {"BookSeriesID": 125, "BookSeries": "《鼹鼠记》贝洛伯格古诗集", "BookSeriesComments": "贝洛伯格的原住民写下的诗集,原诗作者的名字已经无从考证。", "BookSeriesNum": 3, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
122 | {"BookSeriesID": 126, "BookSeries": "贝洛伯格学院期中测试卷", "BookSeriesComments": "一张不及格的测试卷,试卷主人似乎是一位叫卢森的孩子。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
123 | {"BookSeriesID": 127, "BookSeries": "防骗指南修订版", "BookSeriesComments": "深蓝骗局受害者协会编撰的防骗指南", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
124 | {"BookSeriesID": 128, "BookSeries": "征友启事", "BookSeriesComments": "华劳斯委托他人张贴满城市大街小巷的征友海报,大部分都被人撕下来了", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
125 | {"BookSeriesID": 129, "BookSeries": "银鬃铁卫宣誓词", "BookSeriesComments": "铁卫禁区中随处可见的银鬃铁卫宣誓词。可能和大部分公司的「企业价值观」类似,并没有太多人在意。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
126 | {"BookSeriesID": 130, "BookSeries": "艾丝妲的仪器采购合同", "BookSeriesComments": "艾丝妲自掏腰包给空间站购买仪器的采购合同,不知为何竟落入了有心人之手。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
127 | {"BookSeriesID": 131, "BookSeries": "「家用寻物仪」售后回复函", "BookSeriesComments": "「家用寻物仪」的制作商发给顾客艾丝妲的回复函,不知为何竟落入了有心人之手。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
128 | {"BookSeriesID": 132, "BookSeries": "星域界种指要:约特伍德体", "BookSeriesComments": "界种科的研究档案,记载着着星际间罕见的无机生物约特伍德体的资料。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
129 | {"BookSeriesID": 133, "BookSeries": "【录音带】愚人戏:祸祖砺兵寻旧恨,愚人挥剑斩金身", "BookSeriesComments": "一卷录音带转译而成的曲词本,这是部分老年科员爱听的愚人戏。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
130 | {"BookSeriesID": 134, "BookSeries": "维修科室的不传之秘", "BookSeriesComments": "维修科室的手写便条,记录着科室的「不传之秘」,在空间站遇袭后失传。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
131 | {"BookSeriesID": 135, "BookSeries": "空间站体检事项公告", "BookSeriesComments": "张贴在医疗舱内的科员体检事项公告。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
132 | {"BookSeriesID": 136, "BookSeries": "空间站介绍手册", "BookSeriesComments": "供访客浏览的空间站介绍手册。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
133 | {"BookSeriesID": 137, "BookSeries": "黑塔藏品间使用守则", "BookSeriesComments": "黑塔女士为藏品间列下的使用守则——虽然这个藏品间仅可供黑塔女士一人使用。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
134 | {"BookSeriesID": 138, "BookSeries": "研究报告:约特伍德水晶体", "BookSeriesComments": "一位科员针对约特伍德水晶开展地跨界实验,但由于疏忽导致实验样本出错,最后成为一份无效的厕纸报告。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
135 | {"BookSeriesID": 139, "BookSeries": "不完整的录音自动转译文档", "BookSeriesComments": "两位研究奇物的科员,在反物质军团入侵前夕留下实验录音,该文档系录音自动转译而成。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
136 | {"BookSeriesID": 140, "BookSeries": "障碍判别终端的攻击记录", "BookSeriesComments": "反物质军团入侵后,黑塔空间站的障碍判别终端记录了报错记录。在入侵结束后,科员亚伯拉罕在这里偷偷留下了自己的备注。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
137 | {"BookSeriesID": 141, "BookSeries": "科员们的留言便条", "BookSeriesComments": "空间站科员之间相互留言的便条,记录着空间站工作与生活中的细碎片段。", "BookSeriesNum": 6, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
138 | {"BookSeriesID": 142, "BookSeries": "空间站员工餐厅反馈记录表", "BookSeriesComments": "黑塔空间站餐厅的反馈表,里面记录了各位科员对于餐厅产品提出的意见以及期待,同时包含餐厅智能回复机器人给到的反馈。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
139 | {"BookSeriesID": 143, "BookSeries": "某位界种科科员的周记残页", "BookSeriesComments": "一位界种科科员的工作周记,这是被用力扯下的一页。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
140 | {"BookSeriesID": 144, "BookSeries": "科员间的聊天记录", "BookSeriesComments": "记录着空间站遇袭前两位科员闲聊的记录,现已被防卫科列入调查清单。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
141 | {"BookSeriesID": 145, "BookSeries": "约特伍德体是石头", "BookSeriesComments": "某位地概科科员针对约特伍德体尚未发送成功的顶级锐评,设备屏幕上跳动着「您已被拉黑」的温馨提醒。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
142 | {"BookSeriesID": 146, "BookSeries": "帕斯卡的日志", "BookSeriesComments": "似乎是某台自动机兵遗留的日志备忘,其中的日期已经模糊不清。", "BookSeriesNum": 3, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
143 | {"BookSeriesID": 147, "BookSeries": "爆炸头协会纲领", "BookSeriesComments": "空间站的爆炸头协会纲领,包含强烈的主观意志", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
144 | {"BookSeriesID": 148, "BookSeries": "波少百科12.0更新内容", "BookSeriesComments": "卡波特铁粉组定期更新的内容,主要是卡波特的日常行为记录", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
145 | {"BookSeriesID": 149, "BookSeries": "阿兰工作记录", "BookSeriesComments": "记录了阿兰近两周工作内容的笔记。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
146 | {"BookSeriesID": 150, "BookSeries": "教育部的难题", "BookSeriesComments": "无", "BookSeriesNum": 8, "BookSeriesWorld": 2}
147 | {"BookSeriesID": 151, "BookSeries": "裂界来电", "BookSeriesComments": "由边缘通路的一通裂界来电引发的故事。", "BookSeriesNum": 2, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
148 | {"BookSeriesID": 152, "BookSeries": "希莉儿", "BookSeriesComments": "你在裂界中与名为希莉儿的神秘女孩邂逅。她的故事等待着被揭开…", "BookSeriesNum": 3, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
149 | {"BookSeriesID": 153, "BookSeries": "不适合人偶的职业", "BookSeriesComments": "以十王司判官为主角的黑色小说,根据都市传说改编。连载于网络,点击量一般。", "BookSeriesNum": 3, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
150 | {"BookSeriesID": 154, "BookSeries": "守护星槎海", "BookSeriesComments": "以地衡司日常工作为主题的纪实视频文案,记录了一桩棘手的民事纠纷。", "BookSeriesNum": 3, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
151 | {"BookSeriesID": 155, "BookSeries": "仙舟风物志", "BookSeriesComments": "一位博识学会成员对仙舟上下的考察笔记。", "BookSeriesNum": 7, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
152 | {"BookSeriesID": 156, "BookSeries": "云骑斗步离", "BookSeriesComments": "狐人大鼓的唱本。讲述了一位云骑军将军与步离人的血战,在仙舟脍炙人口。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
153 | {"BookSeriesID": 157, "BookSeries": "来自远古的回声•仙舟传统曲艺大观", "BookSeriesComments": "西衍先生为化外民游客撰写的仙舟曲艺小科普。", "BookSeriesNum": 7, "BookSeriesWorld": 3}
154 | {"BookSeriesID": 158, "BookSeries": "冥想遗忘疗法:健康长生,直到永恒!", "BookSeriesComments": "自称是能够避免魔阴身的医疗广告。疗法继承人不惜做出违背祖宗的决定。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
155 | {"BookSeriesID": 159, "BookSeries": "万类宗汇", "BookSeriesComments": "仙舟的动物笔记。包括生物的外形,习性,相关的药材、食谱与俗谚。", "BookSeriesNum": 4, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
156 | {"BookSeriesID": 160, "BookSeries": "《帝弓迹躔歌》注疏", "BookSeriesComments": "以仙舟历史为主题的歌行体叙事诗,描绘了历史英雄「帝弓」波澜壮阔的一生。", "BookSeriesNum": 5, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
157 | {"BookSeriesID": 161, "BookSeries": "梅姐的笔记", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
158 | {"BookSeriesID": 165, "BookSeries": "梁沐的留言之一", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
159 | {"BookSeriesID": 166, "BookSeries": "梁沐的留言之二", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
160 | {"BookSeriesID": 167, "BookSeries": "梁沐的信件", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
161 | {"BookSeriesID": 168, "BookSeries": "渔公案", "BookSeriesComments": "仙舟上有名的侦探小说系列。讲述被人灌下毒药后蜕生成幼儿的神探渔公破解各种迷案的故事。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
162 | {"BookSeriesID": 169, "BookSeries": "药王秘传•证物集册", "BookSeriesComments": "从药王秘传党羽搜来的各种证物。", "BookSeriesNum": 9, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
163 | {"BookSeriesID": 170, "BookSeries": "将军的日记", "BookSeriesComments": "似乎是神策府将军的亲笔日记。内容不多,断断续续。且不知为何,内容全都与一猫有关。", "BookSeriesNum": 3, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
164 | {"BookSeriesID": 171, "BookSeries": "阿德勒的怪谈研究报告", "BookSeriesComments": "应物科科员阿德勒撰写的研究报告,记录着空间站中的各类怪谈。", "BookSeriesNum": 3, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
165 | {"BookSeriesID": 172, "BookSeries": "贝洛伯格大事年表•寒潮之前", "BookSeriesComments": "以克里珀堡官方口吻记载的、贝洛伯格寒潮未至时的部分历史。或许会对你有所帮助。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
166 | {"BookSeriesID": 173, "BookSeries": "通缉令纪念册", "BookSeriesComments": "你已经集齐了贝洛伯格中与你相关的全部通缉令。虽然身价不算高,但恭喜你!", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
167 | {"BookSeriesID": 174, "BookSeries": "奇物管理日志", "BookSeriesComments": "由艾丝妲管理并撰写的奇物档案,在空间站的终端上公开,供所有科员查询使用。", "BookSeriesNum": 3, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
168 | {"BookSeriesID": 175, "BookSeries": "《蝶影》杂志特刊:托蝶幻境千年十佳", "BookSeriesComments": "《蝶影》是仙舟联盟最具权威性的托蝶幻境专门刊物。这是一期特刊,盘点了在托蝶幻境一千年的发展史中,最为优秀的十部作品。", "BookSeriesNum": 10, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
169 | {"BookSeriesID": 176, "BookSeries": "被罗刹撕下的侦探小说扉页", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
170 | {"BookSeriesID": 177, "BookSeries": "罗浮天舶司商会名录", "BookSeriesComments": "天舶司的商会目录,记录着罗浮商会的运营范围。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
171 | {"BookSeriesID": 178, "BookSeries": "形虎拳馆 招生广告", "BookSeriesComments": "一页拳馆广告。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
172 | {"BookSeriesID": 179, "BookSeries": "提倡合理合法爱护动物,严查违法违规「放生」动物", "BookSeriesComments": "地衡司执事所张贴的布告。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
173 | {"BookSeriesID": 180, "BookSeries": "《仙舟通鉴》拾遗", "BookSeriesComments": "仙舟联盟原始史料汇总,是联盟历史进程的重要注脚。", "BookSeriesNum": 4, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
174 | {"BookSeriesID": 181, "BookSeries": "神秘的咨询信", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
175 | {"BookSeriesID": 182, "BookSeries": "感激的复信", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
176 | {"BookSeriesID": 183, "BookSeries": "哀伤的复信", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
177 | {"BookSeriesID": 184, "BookSeries": "云吟谱", "BookSeriesComments": "持明族的民间歌谣集。由一位箜篌师采集编纂。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
178 | {"BookSeriesID": 185, "BookSeries": "绿芙蓉的密信", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
179 | {"BookSeriesID": 186, "BookSeries": "魔阴身考", "BookSeriesComments": "一份丹鼎司的论文,作者考证了仙舟人「魔阴身」的来源和研究。", "BookSeriesNum": 2, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
180 | {"BookSeriesID": 187, "BookSeries": "来自「玉阙」仙舟的监察报告", "BookSeriesComments": "一份玉阙仙舟发来的情报。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
181 | {"BookSeriesID": 188, "BookSeries": "大敌名录", "BookSeriesComments": "神策府内的案牍卷宗,记录了与仙舟为敌的各路势力。", "BookSeriesNum": 4, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
182 | {"BookSeriesID": 189, "BookSeries": "大毫的日记", "BookSeriesComments": "长乐天地衡司公廨执事长大毫的日记。其中记录了他漫长的人生。", "BookSeriesNum": 2, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
183 | {"BookSeriesID": 190, "BookSeries": "帝弓垂迹录", "BookSeriesComments": "太卜司将「巡猎」降世临凡的观测记录整理成册,就成了这本《帝弓垂迹录》。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
184 | {"BookSeriesID": 191, "BookSeries": "一份卜测报告", "BookSeriesComments": "曾有卜者预见到罗浮即将遭遇灾难,可惜这份报告并未受到重视。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
185 | {"BookSeriesID": 192, "BookSeries": "《易镜窥奥》", "BookSeriesComments": "符玄在工作之余写下的草稿,谈论卜筮之道,算法和种种占卜相关的逸事。", "BookSeriesNum": 4, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
186 | {"BookSeriesID": 193, "BookSeries": "黄钟系统共鸣记录", "BookSeriesComments": "联盟的各艘仙舟之间会通过黄钟系统相互报告现状。这是「罗浮」仙舟星历8098年的黄钟系统共鸣记录。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
187 | {"BookSeriesID": 194, "BookSeries": "明阅的占卜记录", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
188 | {"BookSeriesID": 195, "BookSeries": "静斋的占卜记录", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
189 | {"BookSeriesID": 196, "BookSeries": "上国梦华录(残篇)", "BookSeriesComments": "一位历史学家对神降时代的追忆。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
190 | {"BookSeriesID": 197, "BookSeries": "给陌生人的回信", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
191 | {"BookSeriesID": 198, "BookSeries": "柘落丙字三十六号码头货运星槎时刻表", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
192 | {"BookSeriesID": 199, "BookSeries": "来自持明的诉折", "BookSeriesComments": "罗浮持明族龙师上呈六御的请愿书。", "BookSeriesNum": 2, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
193 | {"BookSeriesID": 200, "BookSeries": "云骑军巡索纪要", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
194 | {"BookSeriesID": 201, "BookSeries": "丢失星槎名录", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
195 | {"BookSeriesID": 202, "BookSeries": "药王秘传的指令", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
196 | {"BookSeriesID": 203, "BookSeries": "三百年前断更的武侠小说", "BookSeriesComments": "仙舟上曾经名噪一时的武侠小说。断更许久,令人唏嘘。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
197 | {"BookSeriesID": 204, "BookSeries": "捡到的卜签", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
198 | {"BookSeriesID": 205, "BookSeries": "星槎通航记录", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
199 | {"BookSeriesID": 206, "BookSeries": "罗浮仙舟情报手记", "BookSeriesComments": "丹恒整理的有关仙舟「罗浮」的情报。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
200 | {"BookSeriesID": 207, "BookSeries": "丹枢的日记", "BookSeriesComments": "丹鼎司丹士长丹枢的日记。其中记录了这位盲眼丹士与命运的抗争。", "BookSeriesNum": 2, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
201 | {"BookSeriesID": 208, "BookSeries": "仙舟医典述略", "BookSeriesComments": "丹鼎司内供医士们查询医学著作的综述。", "BookSeriesNum": 4, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
202 | {"BookSeriesID": 209, "BookSeries": "要药分剂", "BookSeriesComments": "丹鼎司药方汇编之大成。", "BookSeriesNum": 2, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
203 | {"BookSeriesID": 210, "BookSeries": "「药王秘传」追源及斥谬", "BookSeriesComments": "十王司判官编写的小册子,帮助公职人员认清「药王秘传」的本质。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
204 | {"BookSeriesID": 211, "BookSeries": "飞行士的交换日记", "BookSeriesComments": "搭档飞行士之间的交换日志,跨越了漫长的时间。", "BookSeriesNum": 2, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
205 | {"BookSeriesID": 212, "BookSeries": "观颐台卷宗调用记录", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
206 | {"BookSeriesID": 213, "BookSeries": "丹鼎司邸报栏的一页", "BookSeriesComments": "丹鼎司邸报栏评议版,丹鼎司医士们聊天吹牛的地方。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
207 | {"BookSeriesID": 214, "BookSeries": "经典病例选编", "BookSeriesComments": "丹鼎司编撰的病例选,以便检索查阅之用。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
208 | {"BookSeriesID": 215, "BookSeries": "未被销毁的信笺", "BookSeriesComments": "没来及销毁的往来信笺,似乎谈论着禁忌的丹方奇术。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
209 | {"BookSeriesID": 216, "BookSeries": "未被销毁的笔记", "BookSeriesComments": "没来及销毁的笔记,似乎正在打探罗浮十王司相关的情报。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
210 | {"BookSeriesID": 217, "BookSeries": "报告:一份罕见的植株样本", "BookSeriesComments": "陶德•雷奥登的调查报告,详细记录着有关魔阴身士兵的测试数据。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
211 | {"BookSeriesID": 218, "BookSeries": "桑博的留言", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
212 | {"BookSeriesID": 219, "BookSeries": "钟珊的来信", "BookSeriesComments": "「放生帮导师」钟珊留下的信件。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
213 | {"BookSeriesID": 220, "BookSeries": "希露瓦的简历", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
214 | {"BookSeriesID": 221, "BookSeries": "娜塔莎的简历", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
215 | {"BookSeriesID": 222, "BookSeries": "虎克的简历", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
216 | {"BookSeriesID": 223, "BookSeries": "杰帕德的简历", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
217 | {"BookSeriesID": 224, "BookSeries": "希儿的简历", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
218 | {"BookSeriesID": 225, "BookSeries": "饮月大逆判牍", "BookSeriesComments": "对「饮月之乱」主犯的一道判决。这道判决对仙舟的未来产生了巨大的影响。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
219 | {"BookSeriesID": 226, "BookSeries": "寒食、岁阳与冤案:禁火节民俗考", "BookSeriesComments": "一位博识学会成员对仙舟「禁火节」之民俗学来源的研究报告。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
220 | {"BookSeriesID": 227, "BookSeries": "龙师溸湍为龙尊造像记", "BookSeriesComments": "古老龙尊造像下铭刻的造像记,讲述了一位龙师筹备建造雕像的始末。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
221 | {"BookSeriesID": 228, "BookSeries": "工正淙衍刻石", "BookSeriesComments": "一块满怀憾恨的刻石,记录着一位工匠的留言。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
222 | {"BookSeriesID": 229, "BookSeries": "护珠人通信碑", "BookSeriesComments": "持明族护珠人专门负责看护古海中的持明卵,这是他们留下的石碑。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
223 | {"BookSeriesID": 230, "BookSeries": "贝尼尼学士的破碎笔记", "BookSeriesComments": "一份博识学会学士留下的破碎笔记。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
224 | {"BookSeriesID": 231, "BookSeries": "持明时调《六御审饮月》残篇钩沉", "BookSeriesComments": "一部失传已久的持明时调唱本。它曾因内容的煽动性而被禁绝了很多年,但如今已经再也掀不起任何风浪了。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
225 | {"BookSeriesID": 232, "BookSeries": "某持明少年的笔记", "BookSeriesComments": "一位持明族学童遗落在鳞渊境的笔记。上面记录着他对持明族历史的怀想。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
226 | {"BookSeriesID": 233, "BookSeries": "龙师会议记残碑", "BookSeriesComments": "一场发生在历史转折点中的龙师会议,被铭刻在一块饱经风霜的残碑上。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
227 | {"BookSeriesID": 234, "BookSeries": "罗浮古纹拓片考察", "BookSeriesComments": "一位学士在洞天各处留下的纹样拓片,似乎是在描绘仙舟历史中的事件…", "BookSeriesNum": 8, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
228 | {"BookSeriesID": 235, "BookSeries": "致克利欧学士的一封信", "BookSeriesComments": "一位博识学会成员写给弟子的信件。他希望弟子收到信后,可以立刻来罗浮见他。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
229 | {"BookSeriesID": 236, "BookSeries": "建木玄根观测记录碑", "BookSeriesComments": "罗浮持明受命不懈守望古老的建木残迹,观察其再生之势。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
230 | {"BookSeriesID": 237, "BookSeries": "涯海星槎胜览 • 朱明仙舟", "BookSeriesComments": "仙舟著名飞行士白珩留下的游记著作集中的一篇,记录着她出使朱明仙舟的始末。", "BookSeriesNum": 3, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
231 | {"BookSeriesID": 238, "BookSeries": "摘星社•金人巷旅游攻略", "BookSeriesComments": "长乐天旅行社制作的旅游手册。可以不看,但不可以没有。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
232 | {"BookSeriesID": 239, "BookSeries": "「尚滋味」收到的满分好评留言", "BookSeriesComments": "化外民向之丘在金人巷「尚滋味」留下的满分好评。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
233 | {"BookSeriesID": 240, "BookSeries": "真志摩的劳动雇佣合同", "BookSeriesComments": "真志摩与世宏签下的劳动雇佣合同。很显然,世宏没有仔细阅读。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
234 | {"BookSeriesID": 241, "BookSeries": "黑市商人调查笔记", "BookSeriesComments": "暗访记者彻查金人巷黑市商人的笔记。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
235 | {"BookSeriesID": 242, "BookSeries": "桂乃芬的美食探店台本", "BookSeriesComments": "仙舟网红桂乃芬制作的探店视频台本。用心程度可见一斑。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
236 | {"BookSeriesID": 243, "BookSeries": "仙舟联盟畅游手册", "BookSeriesComments": "摘星社发行的旅行手册,介绍了仙舟联盟各地的旅游胜地。", "BookSeriesNum": 2, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
237 | {"BookSeriesID": 244, "BookSeries": "符玄致叶尔孤白学士的信", "BookSeriesComments": "在一次成功的合作之后,作为感谢,符玄去信为博识学会提供了大衍穷观阵的技术文牍。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
238 | {"BookSeriesID": 245, "BookSeries": "仙舟联盟信仰危机小史", "BookSeriesComments": "一篇通识性质的历史科普短文。介绍了仙舟人在反抗「丰饶」以降、「巡猎」登神以前的信仰生活。", "BookSeriesNum": 4, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
239 | {"BookSeriesID": 246, "BookSeries": "祭晓虹二公文", "BookSeriesComments": "在对抗药王秘传的战斗中,一对狐人兄弟英勇牺牲。他们的战友用传统的方式为两位烈士写下祭文,以寄哀思。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
240 | {"BookSeriesID": 247, "BookSeries": "海滩漂流瓶", "BookSeriesComments": "无", "BookSeriesNum": 5, "BookSeriesWorld": 3}
241 | {"BookSeriesID": 248, "BookSeries": "阿往阿来相声精选", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
242 | {"BookSeriesID": 249, "BookSeries": "西衍先生评书精选", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
243 | {"BookSeriesID": 250, "BookSeries": "公司员工借条", "BookSeriesComments": "公司员工向当地人借款的打印凭据,被作为正面范例张贴。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
244 | {"BookSeriesID": 251, "BookSeries": "外派员工行动准则:雅利洛-Ⅵ版", "BookSeriesComments": "托帕向员工提出的行动准则,以维持风险投资部的对外形象。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
245 | {"BookSeriesID": 252, "BookSeries": "造物引擎部件修缮方案", "BookSeriesComments": "某研究员对造物引擎部件提出的设计方案,内容夸张且可行性甚微。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
246 | {"BookSeriesID": 253, "BookSeries": "造物引擎修缮计划详述", "BookSeriesComments": "对造物引擎修缮计划的较正规阐述,展望了非常遥远的未来。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
247 | {"BookSeriesID": 254, "BookSeries": "工地安全海报", "BookSeriesComments": "呼吁工人注意施工安全的宣传海报。", "BookSeriesNum": 1, "BookSeriesWorld": 2}
248 | {"BookSeriesID": 255, "BookSeries": "《矿工每周报第226期》", "BookSeriesComments": "具有官方性质的一期工人报。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
249 | {"BookSeriesID": 256, "BookSeries": "虎克的以太战线大秘籍", "BookSeriesComments": "某神秘高手玩家的游戏心得,据说只要将它参透,任何人都能登上冠军之巅。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
250 | {"BookSeriesID": 257, "BookSeries": "造物引擎项目策划书", "BookSeriesComments": "一份草拟完成的策划书,足以让造物引擎脱胎换骨。", "BookSeriesNum": 1, "BookSeriesWorld": 2}
251 | {"BookSeriesID": 258, "BookSeries": "炉灰小报:第一期", "BookSeriesComments": "由匿名民间作者撰写的地下刊物,对社会问题发表了较为尖锐的评论。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
252 | {"BookSeriesID": 259, "BookSeries": "公司出行安全手册", "BookSeriesComments": "星际和平公司为旗下穿梭装置制定的安全须知,制定了许多严苛的规则。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
253 | {"BookSeriesID": 260, "BookSeries": "《机甲乱斗:热血闪耀》", "BookSeriesComments": "在贝洛伯格突然兴起的幻想小说,描述了未来人用巨大机器与怪兽作战的故事。反映了因造物引擎产生的巨物崇拜风潮。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
254 | {"BookSeriesID": 261, "BookSeries": "螺丝咕姆的留言", "BookSeriesComments": "螺丝星贵族给下层区守护者的一份留言,探讨了雅利洛-Ⅵ上机器人发展为「硅基生命」的可能性。", "BookSeriesNum": 1, "BookSeriesWorld": 2, "IsShowInBookshelf": true}
255 | {"BookSeriesID": 262, "BookSeries": "来历不明的信笺", "BookSeriesComments": "闻君返乡,倒悬古海,力挽危澜,想必已重拾旧忆。依久旷之约,吾等当重游故地,历数往事,共浮一白。", "BookSeriesNum": 1, "BookSeriesWorld": 3}
256 | {"BookSeriesID": 263, "BookSeries": "罗刹留下的信笺", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
257 | {"BookSeriesID": 264, "BookSeries": "记忆梳理", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 1}
258 | {"BookSeriesID": 265, "BookSeries": "岁阳祓除通识手册", "BookSeriesComments": "十王司内部分发的除魔应急手册,是为紧急应对岁阳失控而准备的。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
259 | {"BookSeriesID": 266, "BookSeries": "绥园夜谭散页", "BookSeriesComments": "散落在绥园的志怪小说残页,似乎和此地幻境颇有渊源。", "BookSeriesNum": 2, "BookSeriesWorld": 3}
260 | {"BookSeriesID": 267, "BookSeries": "从石砖上拓下的信", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
261 | {"BookSeriesID": 268, "BookSeries": "罗浮若木灾异始末考摘要", "BookSeriesComments": "十王司判官针对建木危机之调查记录的摘要。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
262 | {"BookSeriesID": 269, "BookSeries": "狐人大鼓《六昧叹》", "BookSeriesComments": "一首狐人大鼓的唱本。讲述了狐人古老的传说。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
263 | {"BookSeriesID": 270, "BookSeries": "厌作人间语", "BookSeriesComments": "西衍先生创作的志怪小说集。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
264 | {"BookSeriesID": 271, "BookSeries": "步离人民歌集", "BookSeriesComments": "一本步离人民歌选集,由善宏学宫文化人类学科整理注疏。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
265 | {"BookSeriesID": 272, "BookSeries": "长乐天公廨报总务厅案呈", "BookSeriesComments": "长乐天公廨对近期若干工作的汇报。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
266 | {"BookSeriesID": 273, "BookSeries": "药王妖寇罪证稽录", "BookSeriesComments": "一份向十王司汇报的有关于丹鼎司档案库整理的发现报告 。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
267 | {"BookSeriesID": 274, "BookSeries": "不赦十恶详解", "BookSeriesComments": "一份关于仙舟最严重的罪行「十恶逆」的法律解释。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
268 | {"BookSeriesID": 275, "BookSeries": "十王司重犯名录", "BookSeriesComments": "一份十王司重犯的名单,记录了犯有「不赦十恶」的几名罪人。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
269 | {"BookSeriesID": 276, "BookSeries": "十王司讯问造翼者咥力笔录", "BookSeriesComments": "十王司对一位造翼者小头领的讯问记录。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
270 | {"BookSeriesID": 277, "BookSeries": "《渔公案周年特别篇》读后感", "BookSeriesComments": "一篇《渔公案周年特别篇》的读后感。措辞克制,情绪真挚。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
271 | {"BookSeriesID": 278, "BookSeries": "云骑军移送十王司重犯名单", "BookSeriesComments": "云骑军移交给十王司的重犯名单,所载者皆身犯不赦十恶。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
272 | {"BookSeriesID": 279, "BookSeries": "来自地衡司司衡的信函", "BookSeriesComments": "地衡司司衡寄给景元将军的一封信。", "BookSeriesNum": 1, "BookSeriesWorld": 3, "IsShowInBookshelf": true}
273 | {"BookSeriesID": 280, "BookSeries": "十四种幻觉的面庞", "BookSeriesComments": "一篇来自不知名纯美骑士的告罪书。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
274 | {"BookSeriesID": 281, "BookSeries": "《合巹记》唱本", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 3}
275 | {"BookSeriesID": 282, "BookSeries": "天才俱乐部:科研成果考", "BookSeriesComments": "一位不愿意透露姓名的人士留下的手稿,旨在探讨天才俱乐部诸位成员的成果(未遂)。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
276 | {"BookSeriesID": 283, "BookSeries": "阮•梅的日记残页", "BookSeriesComments": "从阮•梅的日记上撕下的残页,只言片语间记录着她近期的研究课题。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
277 | {"BookSeriesID": 284, "BookSeries": "阮•梅的实验日志", "BookSeriesComments": "不完整的实验日志,这是阮•梅在研究过程中留下的珍贵记录。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
278 | {"BookSeriesID": 285, "BookSeries": "界种科的植物培育计划表", "BookSeriesComments": "记录着界种科培养目标的计划表。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
279 | {"BookSeriesID": 286, "BookSeries": "警告!!!", "BookSeriesComments": "界种科张贴在植物培养间的警示贴。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
280 | {"BookSeriesID": 287, "BookSeries": "十全点心食谱摘录", "BookSeriesComments": "写有阮•梅亲笔批注的食谱集,字迹和她本人一样娟秀。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
281 | {"BookSeriesID": 288, "BookSeries": "情感的本质——一项生理学分析", "BookSeriesComments": "一位不知名人士的论文,探讨了情感在生理层面的产生原因与表达机制。", "BookSeriesNum": 1, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
282 | {"BookSeriesID": 289, "BookSeries": "真理医生的留言便条", "BookSeriesComments": "真理医生在「拜访」空间站时留下的反馈意见,字字彰显着他的真挚。", "BookSeriesNum": 4, "BookSeriesWorld": 1, "IsShowInBookshelf": true}
283 | {"BookSeriesID": 290, "BookSeries": "欢迎来到■■■■", "BookSeriesComments": "一盘诡异的录音带,一个恐怖的噩梦。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
284 | {"BookSeriesID": 291, "BookSeries": "乔凡娜的工作日记", "BookSeriesComments": "酒店入梦护理师总管乔凡娜的工作日记,记录着一次酒店客人的入梦事故。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
285 | {"BookSeriesID": 292, "BookSeries": "路易斯的艺术评论", "BookSeriesComments": "路易斯撰写的一系列期刊,是他为画家夏洛蒂的作品专门创作的艺术评论。", "BookSeriesNum": 3, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
286 | {"BookSeriesID": 293, "BookSeries": "梦境童谣•七口之家", "BookSeriesComments": "一篇在匹诺康尼流传的古怪童谣。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
287 | {"BookSeriesID": 294, "BookSeries": "《镍币影院特别放映:惊魂之夜》", "BookSeriesComments": "镍币影院「惊魂之夜特别放送活动」的传单。上面印刷了相关注意事项与当夜展映片单。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
288 | {"BookSeriesID": 295, "BookSeries": "苏乐达小百科", "BookSeriesComments": "苏乐达工厂印制的广告册,为消费者介绍了各种口味的苏乐达饮料。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
289 | {"BookSeriesID": 296, "BookSeries": "金槌府邸安全规范", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 4}
290 | {"BookSeriesID": 297, "BookSeries": "案情简述•其一", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 4}
291 | {"BookSeriesID": 298, "BookSeries": "案情简述•其二", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 4}
292 | {"BookSeriesID": 299, "BookSeries": "桑博的谏言?", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 4}
293 | {"BookSeriesID": 300, "BookSeries": "「钟表匠」的谏言", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 4}
294 | {"BookSeriesID": 301, "BookSeries": "《小小哈努行动》", "BookSeriesComments": "克劳克影业发行的限量版卡通故事书,讲述了「哈努兄弟」被变小后拯救美梦小镇的故事…书中似乎还夹杂了一些奇怪的对白。", "BookSeriesNum": 5, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
295 | {"BookSeriesID": 302, "BookSeries": "声乐之星的绝唱•「纯美之歌」音乐会", "BookSeriesComments": "歌星拉洛丝在匹诺康尼街头发放的精致传单,介绍了一场关于「纯美之星」的演唱会。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
296 | {"BookSeriesID": 303, "BookSeries": "《银幕冲击波》第280期", "BookSeriesComments": "匹诺康尼的著名影评杂志,由影评家夏布罗担任主笔,观点激进、言辞犀利。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
297 | {"BookSeriesID": 304, "BookSeries": "《忆域迷因受害者名单片段》", "BookSeriesComments": "一份忆域迷因受害者名单的手抄片段,是家族机密封存的机密之一。线人对内容的加密已被解明。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
298 | {"BookSeriesID": 305, "BookSeries": "《忆域洣洇辤嗐鍺洺啴爿葮(加密版)》", "BookSeriesComments": "一份忆域迷因受害者名单的手抄片段,是家族机密封存的机密之一。提供信息的线人对内容做了加密处理。", "BookSeriesNum": 1, "BookSeriesWorld": 4}
299 | {"BookSeriesID": 306, "BookSeries": "《布鲁斯之夜》宣传单", "BookSeriesComments": "一张「蓝调的时刻」的宣传单,文字间充满了浪漫的氛围。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
300 | {"BookSeriesID": 307, "BookSeries": "银河之星的故事", "BookSeriesComments": "经历「银河之星」莱斯利•迪恩的故事时收集到的线索。", "BookSeriesNum": 2, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
301 | {"BookSeriesID": 308, "BookSeries": "关于■号扭蛋机的研究", "BookSeriesComments": "由匹诺康尼逐梦客撰写的实验记录,据说是想揭开娱乐设施的隐藏秘密。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
302 | {"BookSeriesID": 309, "BookSeries": "新闻稿数则", "BookSeriesComments": "匹诺康尼电视台的数则新闻稿,记录了梦中的三两趣事。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
303 | {"BookSeriesID": 310, "BookSeries": "《梦境旅游指南-交通篇》", "BookSeriesComments": "一篇旅游指南,专门为游客介绍了匹诺康尼梦境中的几种常用交通方式。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
304 | {"BookSeriesID": 311, "BookSeries": "《梦境旅游指南-购物篇》", "BookSeriesComments": "旅游指南续作之一,专门为游客介绍几处带有匹诺康尼特色的购物地点。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
305 | {"BookSeriesID": 312, "BookSeries": "《梦境旅游指南-游玩篇》", "BookSeriesComments": "旅游指南续作之二,专门为游客介绍几处带有匹诺康尼特色的玩乐体验。", "BookSeriesNum": 1, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
306 | {"BookSeriesID": 313, "BookSeries": "盛会之星收藏卡", "BookSeriesComments": "由艾弗法出版集团旗下梦星娱乐™设计发行的一套匹诺康尼限量版明星卡牌,版权所有,翻版必究。", "BookSeriesNum": 4, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
307 | {"BookSeriesID": 314, "BookSeries": "柯柯娜的便条", "BookSeriesComments": "无", "BookSeriesNum": 1, "BookSeriesWorld": 4}
308 | {"BookSeriesID": 315, "BookSeries": "服务生汉密尔顿的笔记", "BookSeriesComments": "与辛格勒一同调查梦境酒店的异变时发现的一系列零碎的线索,出自一位名叫汉密尔顿的酒店服务生之手。", "BookSeriesNum": 4, "BookSeriesWorld": 4, "IsShowInBookshelf": true}
309 |
--------------------------------------------------------------------------------