WindFade commited on
Commit
7132222
1 Parent(s): 17a347d

:sparkles:(features): add support for Zixiagong (#1)

Browse files
.gitignore ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### JetBrains template
2
+ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
3
+ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
4
+
5
+ # User-specific stuff
6
+ .idea/**/workspace.xml
7
+ .idea/**/tasks.xml
8
+ .idea/**/usage.statistics.xml
9
+ .idea/**/dictionaries
10
+ .idea/**/shelf
11
+
12
+ # AWS User-specific
13
+ .idea/**/aws.xml
14
+
15
+ # Generated files
16
+ .idea/**/contentModel.xml
17
+
18
+ # Sensitive or high-churn files
19
+ .idea/**/dataSources/
20
+ .idea/**/dataSources.ids
21
+ .idea/**/dataSources.local.xml
22
+ .idea/**/sqlDataSources.xml
23
+ .idea/**/dynamic.xml
24
+ .idea/**/uiDesigner.xml
25
+ .idea/**/dbnavigator.xml
26
+
27
+ # Gradle
28
+ .idea/**/gradle.xml
29
+ .idea/**/libraries
30
+
31
+ # Gradle and Maven with auto-import
32
+ # When using Gradle or Maven with auto-import, you should exclude module files,
33
+ # since they will be recreated, and may cause churn. Uncomment if using
34
+ # auto-import.
35
+ # .idea/artifacts
36
+ # .idea/compiler.xml
37
+ # .idea/jarRepositories.xml
38
+ # .idea/modules.xml
39
+ # .idea/*.iml
40
+ # .idea/modules
41
+ # *.iml
42
+ # *.ipr
43
+
44
+ # CMake
45
+ cmake-build-*/
46
+
47
+ # Mongo Explorer plugin
48
+ .idea/**/mongoSettings.xml
49
+
50
+ # File-based project format
51
+ *.iws
52
+
53
+ # IntelliJ
54
+ out/
55
+
56
+ # mpeltonen/sbt-idea plugin
57
+ .idea_modules/
58
+
59
+ # JIRA plugin
60
+ atlassian-ide-plugin.xml
61
+
62
+ # Cursive Clojure plugin
63
+ .idea/replstate.xml
64
+
65
+ # SonarLint plugin
66
+ .idea/sonarlint/
67
+
68
+ # Crashlytics plugin (for Android Studio and IntelliJ)
69
+ com_crashlytics_export_strings.xml
70
+ crashlytics.properties
71
+ crashlytics-build.properties
72
+ fabric.properties
73
+
74
+ # Editor-based Rest Client
75
+ .idea/httpRequests
76
+
77
+ # Android studio 3.1+ serialized cache file
78
+ .idea/caches/build_file_checksums.ser
79
+
80
+ ### Qt template
81
+ # C++ objects and libs
82
+ *.slo
83
+ *.lo
84
+ *.o
85
+ *.a
86
+ *.la
87
+ *.lai
88
+ *.so
89
+ *.so.*
90
+ *.dll
91
+ *.dylib
92
+
93
+ # Qt-es
94
+ object_script.*.Release
95
+ object_script.*.Debug
96
+ *_plugin_import.cpp
97
+ /.qmake.cache
98
+ /.qmake.stash
99
+ *.pro.user
100
+ *.pro.user.*
101
+ *.qbs.user
102
+ *.qbs.user.*
103
+ *.moc
104
+ moc_*.cpp
105
+ moc_*.h
106
+ qrc_*.cpp
107
+ ui_*.h
108
+ *.qmlc
109
+ *.jsc
110
+ Makefile*
111
+ *build-*
112
+ *.qm
113
+ *.prl
114
+
115
+ # Qt unit tests
116
+ target_wrapper.*
117
+
118
+ # QtCreator
119
+ *.autosave
120
+
121
+ # QtCreator Qml
122
+ *.qmlproject.user
123
+ *.qmlproject.user.*
124
+
125
+ # QtCreator CMake
126
+ CMakeLists.txt.user*
127
+
128
+ # QtCreator 4.8< compilation database
129
+ compile_commands.json
130
+
131
+ # QtCreator local machine specific files for imported projects
132
+ *creator.user*
133
+
134
+ *_qmlcache.qrc
135
+
136
+ ### macOS template
137
+ # General
138
+ .DS_Store
139
+ .AppleDouble
140
+ .LSOverride
141
+
142
+ # Icon must end with two \r
143
+ Icon
144
+
145
+ # Thumbnails
146
+ ._*
147
+
148
+ # Files that might appear in the root of a volume
149
+ .DocumentRevisions-V100
150
+ .fseventsd
151
+ .Spotlight-V100
152
+ .TemporaryItems
153
+ .Trashes
154
+ .VolumeIcon.icns
155
+ .com.apple.timemachine.donotpresent
156
+
157
+ # Directories potentially created on remote AFP share
158
+ .AppleDB
159
+ .AppleDesktop
160
+ Network Trash Folder
161
+ Temporary Items
162
+ .apdisk
163
+
164
+ ### Python template
165
+ # Byte-compiled / optimized / DLL files
166
+ __pycache__/
167
+ *.py[cod]
168
+ *$py.class
169
+
170
+ # C extensions
171
+
172
+ # Distribution / packaging
173
+ .Python
174
+ build/
175
+ develop-eggs/
176
+ dist/
177
+ downloads/
178
+ eggs/
179
+ .eggs/
180
+ lib/
181
+ lib64/
182
+ parts/
183
+ sdist/
184
+ var/
185
+ wheels/
186
+ share/python-wheels/
187
+ *.egg-info/
188
+ .installed.cfg
189
+ *.egg
190
+ MANIFEST
191
+
192
+ # PyInstaller
193
+ # Usually these files are written by a python script from a template
194
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
195
+ *.manifest
196
+ *.spec
197
+
198
+ # Installer logs
199
+ pip-log.txt
200
+ pip-delete-this-directory.txt
201
+
202
+ # Unit test / coverage reports
203
+ htmlcov/
204
+ .tox/
205
+ .nox/
206
+ .coverage
207
+ .coverage.*
208
+ .cache
209
+ nosetests.xml
210
+ coverage.xml
211
+ *.cover
212
+ *.py,cover
213
+ .hypothesis/
214
+ .pytest_cache/
215
+ cover/
216
+
217
+ # Translations
218
+ *.mo
219
+ *.pot
220
+
221
+ # Django stuff:
222
+ *.log
223
+ local_settings.py
224
+ db.sqlite3
225
+ db.sqlite3-journal
226
+
227
+ # Flask stuff:
228
+ instance/
229
+ .webassets-cache
230
+
231
+ # Scrapy stuff:
232
+ .scrapy
233
+
234
+ # Sphinx documentation
235
+ docs/_build/
236
+
237
+ # PyBuilder
238
+ .pybuilder/
239
+ target/
240
+
241
+ # Jupyter Notebook
242
+ .ipynb_checkpoints
243
+
244
+ # IPython
245
+ profile_default/
246
+ ipython_config.py
247
+
248
+ # pyenv
249
+ # For a library or package, you might want to ignore these files since the code is
250
+ # intended to run in multiple environments; otherwise, check them in:
251
+ # .python-version
252
+
253
+ # pipenv
254
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
255
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
256
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
257
+ # install all needed dependencies.
258
+ #Pipfile.lock
259
+
260
+ # poetry
261
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
262
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
263
+ # commonly ignored for libraries.
264
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
265
+ #poetry.lock
266
+
267
+ # pdm
268
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
269
+ #pdm.lock
270
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
271
+ # in version control.
272
+ # https://pdm.fming.dev/#use-with-ide
273
+ .pdm.toml
274
+
275
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
276
+ __pypackages__/
277
+
278
+ # Celery stuff
279
+ celerybeat-schedule
280
+ celerybeat.pid
281
+
282
+ # SageMath parsed files
283
+ *.sage.py
284
+
285
+ # Environments
286
+ .env
287
+ .venv
288
+ env/
289
+ venv/
290
+ ENV/
291
+ env.bak/
292
+ venv.bak/
293
+
294
+ # Spyder project settings
295
+ .spyderproject
296
+ .spyproject
297
+
298
+ # Rope project settings
299
+ .ropeproject
300
+
301
+ # mkdocs documentation
302
+ /site
303
+
304
+ # mypy
305
+ .mypy_cache/
306
+ .dmypy.json
307
+ dmypy.json
308
+
309
+ # Pyre type checker
310
+ .pyre/
311
+
312
+ # pytype static type analyzer
313
+ .pytype/
314
+
315
+ # Cython debug symbols
316
+ cython_debug/
317
+
318
+ # PyCharm
319
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
320
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
321
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
322
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
323
+ #.idea/
324
+
schools/__init__.py CHANGED
@@ -9,7 +9,7 @@ from base.skill import Skill
9
  from schools import bei_ao_jue, gu_feng_jue
10
  from schools import shan_hai_xin_jue, ling_hai_jue, tai_xu_jian_yi, fen_shan_jing
11
  from schools import yi_jin_jing, tian_luo_gui_dao, hua_jian_you
12
- from schools import wu_fang, bing_xin_jue, mo_wen
13
 
14
 
15
  @dataclass
@@ -100,6 +100,14 @@ SUPPORT_SCHOOL = {
100
  recipe_gains=yi_jin_jing.RECIPE_GAINS, recipes=yi_jin_jing.RECIPES,
101
  gains=yi_jin_jing.GAINS, display_attrs={"spunk": "元气", **MAGICAL_DISPLAY_ATTRS}
102
  ),
 
 
 
 
 
 
 
 
103
  10015: School(
104
  school="纯阳", major="身法", kind="外功", attribute=tai_xu_jian_yi.TaiXuJianYi, formation="北斗七星阵",
105
  skills=tai_xu_jian_yi.SKILLS, buffs=tai_xu_jian_yi.BUFFS, prepare=tai_xu_jian_yi.prepare,
@@ -188,4 +196,4 @@ SUPPORT_SCHOOL = {
188
  recipe_gains=shan_hai_xin_jue.RECIPE_GAINS, recipes=shan_hai_xin_jue.RECIPES,
189
  gains=shan_hai_xin_jue.GAINS, display_attrs={"agility": "身法", **PHYSICAL_DISPLAY_ATTRS}
190
  ),
191
- }
 
9
  from schools import bei_ao_jue, gu_feng_jue
10
  from schools import shan_hai_xin_jue, ling_hai_jue, tai_xu_jian_yi, fen_shan_jing
11
  from schools import yi_jin_jing, tian_luo_gui_dao, hua_jian_you
12
+ from schools import wu_fang, bing_xin_jue, mo_wen, zi_xia_gong
13
 
14
 
15
  @dataclass
 
100
  recipe_gains=yi_jin_jing.RECIPE_GAINS, recipes=yi_jin_jing.RECIPES,
101
  gains=yi_jin_jing.GAINS, display_attrs={"spunk": "元气", **MAGICAL_DISPLAY_ATTRS}
102
  ),
103
+ 10014: School(
104
+ school="纯阳", major="根骨", kind="内功", attribute=zi_xia_gong.ZiXiaGong, formation="九宫八卦阵",
105
+ skills=zi_xia_gong.SKILLS, buffs=zi_xia_gong.BUFFS, prepare=zi_xia_gong.prepare,
106
+ talent_gains=zi_xia_gong.TALENT_GAINS, talents=zi_xia_gong.TALENTS,
107
+ talent_decoder=zi_xia_gong.TALENT_DECODER, talent_encoder=zi_xia_gong.TALENT_ENCODER,
108
+ recipe_gains=zi_xia_gong.RECIPE_GAINS, recipes=zi_xia_gong.RECIPES,
109
+ gains=zi_xia_gong.GAINS, display_attrs={"spirit": "根骨", **MAGICAL_DISPLAY_ATTRS}
110
+ ),
111
  10015: School(
112
  school="纯阳", major="身法", kind="外功", attribute=tai_xu_jian_yi.TaiXuJianYi, formation="北斗七星阵",
113
  skills=tai_xu_jian_yi.SKILLS, buffs=tai_xu_jian_yi.BUFFS, prepare=tai_xu_jian_yi.prepare,
 
196
  recipe_gains=shan_hai_xin_jue.RECIPE_GAINS, recipes=shan_hai_xin_jue.RECIPES,
197
  gains=shan_hai_xin_jue.GAINS, display_attrs={"agility": "身法", **PHYSICAL_DISPLAY_ATTRS}
198
  ),
199
+ }
schools/zi_xia_gong/__init__.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from schools.zi_xia_gong.skills import SKILLS
2
+ from schools.zi_xia_gong.buffs import BUFFS
3
+ from schools.zi_xia_gong.talents import TALENT_GAINS, TALENTS, TALENT_DECODER, TALENT_ENCODER
4
+ from schools.zi_xia_gong.recipes import RECIPE_GAINS, RECIPES
5
+ from schools.zi_xia_gong.gains import GAINS
6
+ from schools.zi_xia_gong.attribute import ZiXiaGong
7
+
8
+
9
+ def prepare(self, player_id):
10
+ self.status[player_id][(17918, 1)] = 1
schools/zi_xia_gong/attribute.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base.attribute import MagicalAttribute
2
+ from base.constant import BINARY_SCALE
3
+
4
+
5
+ class ZiXiaGong(MagicalAttribute):
6
+ SPIRIT_TO_ATTACK_POWER = 1792 / BINARY_SCALE
7
+ SPIRIT_TO_CRITICAL_STRIKE = 573 / BINARY_SCALE
8
+
9
+ def __init__(self):
10
+ super().__init__()
11
+ self.magical_attack_power_base += 3725
12
+ self.magical_critical_strike_base += 1788
13
+ self.pve_addition += 51
14
+
15
+ @property
16
+ def extra_magical_attack_power(self):
17
+ return int(self.spirit * self.SPIRIT_TO_ATTACK_POWER)
18
+
19
+ @property
20
+ def extra_magical_critical_strike(self):
21
+ return int(self.spirit * self.SPIRIT_TO_CRITICAL_STRIKE)
schools/zi_xia_gong/buffs.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+
3
+ from base.buff import Buff
4
+ from general.buffs import GENERAL_BUFFS
5
+
6
+ BUFFS: Dict[int, Buff | dict] = {
7
+ 375: {
8
+ "buff_name": "破苍穹",
9
+ "gain_attributes": {
10
+ "magical_critical_strike_gain": [300, 350, 400, 450, 0, 600, 700, 800, 900, 500, 500, 1000, 500, 1000],
11
+ "magical_critical_power_gain": [61, 71, 81, 82, 102, 122, 143, 163, 184, 102, 102, 204, 102, 204],
12
+ "all_shield_ignore": [0] * 12 + [614] * 2
13
+ }
14
+ },
15
+ 1439: {
16
+ "buff_name": "气涌",
17
+ "activate": False,
18
+ "gain_attributes": {
19
+ "magical_critical_strike_gain": 400,
20
+ "magical_critical_power_gain": 41
21
+ }
22
+ },
23
+ 1908: {
24
+ "buff_name": "会神",
25
+ "gain_attributes": {
26
+ "magical_critical_power_gain": 204,
27
+ }
28
+ },
29
+ 2757: {
30
+ "buff_name": "紫气东来",
31
+ "gain_attributes": {
32
+ "physical_attack_power_gain": [256, 256, 512, 256],
33
+ "magical_attack_power_gain": [256, 256, 512, 256],
34
+ "all_critical_strike_gain": 2500,
35
+ "all_critical_power_gain": 256
36
+ }
37
+ },
38
+ 9966: {
39
+ "buff_name": "六合独尊加伤害实际表现",
40
+ "gain_skills": {
41
+ 18670: {
42
+ "skill_damage_addition": [358, 716, 1075, 1433]
43
+ }
44
+ }
45
+ },
46
+ # 12550: {
47
+ # "buff_name": "气剑提升四象轮回伤害",
48
+ # "gain_skills": {
49
+ # 896: {
50
+ # "skill_damage_addition": [40, 81, 122, 163, 204]
51
+ # }
52
+ # }
53
+ # },
54
+ # 12551: {
55
+ # "buff_name": "气剑提升两仪化形伤害",
56
+ # "gain_skills": {
57
+ # skill_id: {
58
+ # "skill_damage_addition": [40, 81, 122, 163, 204]
59
+ # } for skill_id in (3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448)
60
+ # }
61
+ # },
62
+ 17918: {
63
+ "buff_name": "镇山河",
64
+ "gain_skills": {
65
+ skill_id: {
66
+ "skill_pve_addition": 1331
67
+ } for skill_id in (18649, 18650, 18651, 18652, 18653, 22014)
68
+ }
69
+ }
70
+ }
71
+
72
+ for buff_id, detail in BUFFS.items():
73
+ BUFFS[buff_id] = Buff(buff_id)
74
+ for attr, value in detail.items():
75
+ setattr(BUFFS[buff_id], attr, value)
76
+
77
+ for buff_id, buff in GENERAL_BUFFS.items():
78
+ BUFFS[buff_id] = buff
schools/zi_xia_gong/gains.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from base.recipe import damage_addition_recipe, critical_strike_recipe
2
+ from general.gains.equipment import EQUIPMENT_GAINS, CriticalSet
3
+ from base.gain import Gain
4
+
5
+ GAINS = {
6
+ # 气涌4%双会套装
7
+ 1914: CriticalSet(1439),
8
+ # 万世不竭10%套装
9
+ 818: damage_addition_recipe([18649, 18650, 18651, 18652, 18653, 22014], 102),
10
+ # 无界套装
11
+ 4602: Gain(),
12
+ # 四象轮回5%橙武
13
+ 1520: damage_addition_recipe([896], 51),
14
+ # TODO 太极无极5%橙武
15
+ 1521: Gain(),
16
+ # 四象轮回5%小橙武
17
+ 1136: critical_strike_recipe([896], 500),
18
+ # 橙武特效
19
+ 2418: Gain(),
20
+ # 四象轮回·神兵
21
+ 1931: Gain(),
22
+ # 无界特效1
23
+ 17300: Gain(),
24
+ # 无界特效2
25
+ 17239: Gain(),
26
+ **EQUIPMENT_GAINS,
27
+ }
schools/zi_xia_gong/recipes.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, List
2
+
3
+ from base.gain import Gain
4
+ from base.recipe import damage_addition_recipe, critical_strike_recipe
5
+
6
+ RECIPE_GAINS: Dict[str, Dict[str, Gain]] = {
7
+ "两仪化形": {
8
+ "5%伤害": damage_addition_recipe([3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448], 51),
9
+ "4%伤害": damage_addition_recipe([3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448], 41),
10
+ "3%伤害": damage_addition_recipe([3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448], 31),
11
+ "4%会心": critical_strike_recipe([3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448], 400),
12
+ "3%会心": critical_strike_recipe([3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448], 300),
13
+ "2%会心": critical_strike_recipe([3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448], 200),
14
+ },
15
+ "四象轮回": {
16
+ "5%伤害": damage_addition_recipe([896], 51),
17
+ "4%伤害": damage_addition_recipe([896], 41),
18
+ "3%伤害": damage_addition_recipe([896], 31),
19
+ "4%会心": critical_strike_recipe([896], 400),
20
+ "3%会心": critical_strike_recipe([896], 300),
21
+ "2%会心": critical_strike_recipe([896], 200),
22
+ },
23
+ "万世不竭": {
24
+ "5%伤害": damage_addition_recipe([18649, 18650, 18651, 18652, 18653, 22014], 51),
25
+ "4%伤害": damage_addition_recipe([18649, 18650, 18651, 18652, 18653, 22014], 41),
26
+ "3%伤害": damage_addition_recipe([18649, 18650, 18651, 18652, 18653, 22014], 31),
27
+ }
28
+ }
29
+
30
+ RECIPES: Dict[str, List[str]] = {
31
+ "两仪化形": ["5%伤害", "4%伤害", "4%会心", "3%伤害", "3%会心", "2%会心"],
32
+ "四象轮回": ["5%伤害", "4%伤害", "4%会心", "3%伤害", "3%会心", "2%会心"],
33
+ "万世不竭": ["5%伤害", "4%伤害", "3%伤害"]
34
+ }
schools/zi_xia_gong/skills.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+
3
+ from base.skill import Skill, DotSkill, PhysicalDamage, MagicalDamage, MagicalDotDamage
4
+ from general.skills import GENERAL_SKILLS
5
+
6
+ SKILLS: Dict[int, Skill | dict] = {
7
+ 303: {
8
+ "skill_class": MagicalDamage,
9
+ "skill_name": "三才化生",
10
+ "damage_base": 780 * 0.1,
11
+ "damage_rand": 78 * 0.1,
12
+ "attack_power_cof": 16
13
+ },
14
+ **{
15
+ skill_id: {
16
+ "skill_class": MagicalDamage,
17
+ "skill_name": "五方行尽",
18
+ "damage_base": [(51 * i / 100) for i in range(1, 11)],
19
+ "attack_power_cof": [(8 * i * 1.4) for i in range(1, 11)],
20
+ } for skill_id in (327, 328, 329, 330, 331, 461, 462, 463, 464, 465)
21
+ },
22
+ 896: {
23
+ "skill_class": MagicalDamage,
24
+ "skill_name": "四象轮回",
25
+ "damage_base": 1260 + 827 - 1907,
26
+ "damage_rand": 20,
27
+ "attack_power_cof": 170 * 1.1 * 1.1 * 0.95 * 0.9 * 1.05 * 1.05 * 1.1 * 2.07
28
+ },
29
+ **{
30
+ skill_id: {
31
+ "skill_class": MagicalDamage,
32
+ "skill_name": "两仪化形",
33
+ "damage_base": [(16526 + 10742 * i / 100) for i in range(1, 11)],
34
+ "damage_rand": [273 * i / 100 for i in range(1, 11)],
35
+ "attack_power_cof": [(22.5 * i * 0.85 * 1.1 * 1.1 * 1.05 * 0.9 * 1.05 * 1.05 * 1.1 * 1.1 * 1.32) for i in
36
+ range(1, 11)],
37
+ } for skill_id in (3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448)
38
+ },
39
+ **{
40
+ skill_id: {
41
+ "skill_class": MagicalDamage,
42
+ "skill_name": "两仪化形",
43
+ "damage_base": 1298 * 2.1,
44
+ "damage_rand": 1298 * 2.1,
45
+ "attack_power_cof": [(14 * i * 0.8) for i in range(1, 11)],
46
+ } for skill_id in (6091, 6092, 6093, 6094, 6095, 6096, 6097, 6098, 6099, 6100)
47
+ },
48
+ 6424: {
49
+ "skill_class": MagicalDotDamage,
50
+ "skill_name": "气竭(DOT)",
51
+ "damage_base": 10,
52
+ "attack_power_cof": 229 * 1.7,
53
+ "interval": 48
54
+ },
55
+ 18121: {
56
+ "skill_class": PhysicalDamage,
57
+ "skill_name": "三柴剑法",
58
+ "attack_power_cof": 16,
59
+ "weapon_damage_cof": 1024,
60
+ "skill_damage_addition": 205
61
+ },
62
+ **{
63
+ skill_id: {
64
+ "skill_class": MagicalDamage,
65
+ "skill_name": "飞剑",
66
+ "damage_base": 50,
67
+ "attack_power_cof": 40 * 0.75 * 1.15 * 1.1 * 1.45,
68
+ } for skill_id in (18649, 18650, 18651, 18652, 18653)
69
+ },
70
+ 18670: {
71
+ "skill_class": MagicalDamage,
72
+ "skill_name": "六合独尊",
73
+ "damage_base": 1038 / 16,
74
+ "damage_rand": 104 / 2,
75
+ "attack_power_cof": 82 * 2
76
+ },
77
+ 22014: {
78
+ "skill_class": MagicalDamage,
79
+ "skill_name": "万世不竭",
80
+ "damage_base": 1150,
81
+ "damage_rand": 78,
82
+ "attack_power_cof": 300 * 1.1 * 1.15 * 1.1 * 1.12
83
+ },
84
+ 25770: {
85
+ "skill_class": MagicalDamage,
86
+ "skill_name": "四象轮回·神兵",
87
+ "damage_base": 20,
88
+ "damage_rand": 2,
89
+ "attack_power_cof": 65
90
+ },
91
+ 32813: {
92
+ "skill_class": MagicalDamage,
93
+ "skill_name": "破",
94
+ "surplus_cof": [
95
+ 1024 * 1024 * (0.06 - 1),
96
+ 1024 * 1024 * (0.30 - 1),
97
+ 1024 * 1024 * (0.83 - 1),
98
+ 1024 * 1024 * (0.60 - 1)
99
+ ]
100
+ },
101
+ 33592: {
102
+ "skill_class": DotSkill,
103
+ "skill_name": "气竭",
104
+ "bind_skill": 6424,
105
+ "max_stack": 3,
106
+ "tick": 10
107
+ },
108
+ 36439: {
109
+ "skill_class": MagicalDamage,
110
+ "skill_name": "颠越苍穹击",
111
+ "damage_base": 1038,
112
+ "damage_rand": 104,
113
+ "attack_power_cof": 155 * 0.9
114
+ }
115
+ }
116
+
117
+ for skill_id, detail in SKILLS.items():
118
+ SKILLS[skill_id] = detail.pop('skill_class')(skill_id)
119
+ for attr, value in detail.items():
120
+ setattr(SKILLS[skill_id], attr, value)
121
+
122
+ for skill_id, skill in GENERAL_SKILLS.items():
123
+ SKILLS[skill_id] = skill
schools/zi_xia_gong/talents.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+
3
+ from base.gain import Gain
4
+ from base.skill import Skill
5
+
6
+
7
+ class 雾锁(Gain):
8
+ def add_skills(self, skills: Dict[int, Skill]):
9
+ skills[896].skill_damage_addition += 102
10
+
11
+ def sub_skills(self, skills: Dict[int, Skill]):
12
+ skills[896].skill_damage_addition -= 102
13
+
14
+
15
+ class 白虹(Gain):
16
+ def add_skills(self, skills: Dict[int, Skill]):
17
+ skills[896].skill_critical_strike += 1000
18
+ skills[896].skill_critical_power += 102
19
+
20
+ def sub_skills(self, skills: Dict[int, Skill]):
21
+ skills[896].skill_critical_strike -= 1000
22
+ skills[896].skill_critical_power -= 102
23
+
24
+
25
+ class 霜锋(Gain):
26
+ def add_skills(self, skills: Dict[int, Skill]):
27
+ for skill_id in (3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448):
28
+ skills[skill_id].skill_damage_addition += 102
29
+ skills[18670].skill_damage_addition += 102
30
+
31
+ def sub_skills(self, skills: Dict[int, Skill]):
32
+ for skill_id in (3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448):
33
+ skills[skill_id].skill_damage_addition -= 102
34
+ skills[18670].skill_damage_addition -= 102
35
+
36
+
37
+ class 跬步(Gain):
38
+ def add_skills(self, skills: Dict[int, Skill]):
39
+ skills[896].skill_damage_addition += 204
40
+ for skill_id in (3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448):
41
+ skills[skill_id].skill_damage_addition += 204
42
+
43
+ def sub_skills(self, skills: Dict[int, Skill]):
44
+ skills[896].skill_damage_addition -= 204
45
+ for skill_id in (3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448):
46
+ skills[skill_id].skill_damage_addition -= 204
47
+
48
+
49
+ class 重光(Gain):
50
+ def add_skills(self, skills: Dict[int, Skill]):
51
+ for i, skill_id in enumerate([18649, 18650, 18651, 18652, 18653]):
52
+ skills[skill_id].skill_damage_addition += int(i * 0.15 * 1024)
53
+
54
+ def sub_skills(self, skills: Dict[int, Skill]):
55
+ for i, skill_id in enumerate([18649, 18650, 18651, 18652, 18653]):
56
+ skills[skill_id].skill_damage_addition -= int(i * 0.15 * 1024)
57
+
58
+
59
+ TALENT_GAINS: Dict[int, Gain] = {
60
+ 5840: 雾锁("雾锁"),
61
+ 5827: 白虹("白虹"),
62
+ 5823: Gain("心固"),
63
+ 5828: 霜锋("霜锋"),
64
+ 357: Gain("化三清"),
65
+ 5846: Gain("无形"),
66
+ 23614: Gain("归元"),
67
+ 5819: Gain("同尘"),
68
+ 18695: 跬步("跬步"),
69
+ 32411: Gain("正气"),
70
+ 14834: Gain("抱阳"),
71
+ 18679: Gain("浮生"),
72
+ 24945: Gain("破势"),
73
+ 18669: 重光("重光"),
74
+ 14613: Gain("固本"),
75
+ }
76
+
77
+ TALENTS = [
78
+ [5840, 5827],
79
+ [5823, 5828],
80
+ [357, 5846],
81
+ [23614],
82
+ [5819],
83
+ [18695],
84
+ [32411],
85
+ [14834],
86
+ [18679],
87
+ [24945],
88
+ [18669],
89
+ [14613]
90
+ ]
91
+ TALENT_DECODER = {talent_id: talent.gain_name for talent_id, talent in TALENT_GAINS.items()}
92
+ TALENT_ENCODER = {v: k for k, v in TALENT_DECODER.items()}
utils/parser.py CHANGED
@@ -4,17 +4,19 @@ from base.constant import FRAME_PER_SECOND
4
  from schools import *
5
  from utils.lua import parse
6
 
7
- FRAME_TYPE, PLAYER_ID_TYPE, PLAYER_NAME_TYPE, TARGET_ID_TYPE, PET_ID_TYPE = int, int, int, int, int
8
  CASTER_ID_TYPE = PLAYER_ID_TYPE | PET_ID_TYPE
9
  SKILL_ID_TYPE, SKILL_LEVEL_TYPE, SKILL_STACK_TYPE, SKILL_CRITICAL_TYPE = int, int, int, bool
 
10
  SKILL_TYPE = Tuple[SKILL_ID_TYPE, SKILL_LEVEL_TYPE, SKILL_STACK_TYPE]
11
  BUFF_ID_TYPE, BUFF_LEVEL_TYPE, BUFF_STACK_TYPE = int, int, int
12
  BUFF_TYPE = Tuple[BUFF_ID_TYPE, BUFF_LEVEL_TYPE]
 
13
 
14
- CURRENT_STATUS_TYPE, SNAPSHOT_STATUS_TYPE, TARGET_STATUS_TYPE = tuple, tuple, tuple
15
- STATUS_TUPLE = Tuple[CURRENT_STATUS_TYPE, SNAPSHOT_STATUS_TYPE, TARGET_STATUS_TYPE]
16
- TIMELINE_TYPE = Tuple[FRAME_TYPE, SKILL_CRITICAL_TYPE]
17
- SUB_RECORD_TYPE = Dict[STATUS_TUPLE, List[TIMELINE_TYPE]]
18
  RECORD_TYPE = Dict[SKILL_TYPE, SUB_RECORD_TYPE]
19
 
20
  LABEL_MAPPING = {
@@ -33,33 +35,30 @@ LABEL_MAPPING = {
33
  }
34
  EMBED_MAPPING: Dict[tuple, int] = {(5, 24449 - i): 8 - i for i in range(8)}
35
 
 
 
36
 
37
  class Parser:
38
  current_player: PLAYER_ID_TYPE
39
  current_caster: CASTER_ID_TYPE
40
- current_target: TARGET_ID_TYPE
41
- current_skill: SKILL_ID_TYPE
42
  current_frame: FRAME_TYPE
 
43
 
44
- id2name: Dict[PLAYER_ID_TYPE | TARGET_ID_TYPE, PLAYER_NAME_TYPE]
45
- name2id: Dict[PLAYER_NAME_TYPE, PLAYER_ID_TYPE | TARGET_ID_TYPE]
46
  pets: Dict[PET_ID_TYPE, PLAYER_ID_TYPE]
47
- records: Dict[PLAYER_ID_TYPE, Dict[TARGET_ID_TYPE, RECORD_TYPE]]
48
-
49
- shift_buffs: Dict[FRAME_TYPE, Dict[PLAYER_ID_TYPE, Dict[BUFF_TYPE, BUFF_STACK_TYPE]]]
50
- hidden_buffs: Dict[TARGET_ID_TYPE, Dict[PLAYER_ID_TYPE, Dict[BUFF_TYPE, FRAME_TYPE]]]
51
 
52
- player_buffs: Dict[PLAYER_ID_TYPE, Dict[BUFF_TYPE, BUFF_STACK_TYPE]]
53
- target_buffs: Dict[TARGET_ID_TYPE, Dict[PLAYER_ID_TYPE, Dict[BUFF_TYPE, BUFF_STACK_TYPE]]]
 
54
 
55
- stacks: Dict[TARGET_ID_TYPE, Dict[PLAYER_ID_TYPE, Dict[SKILL_ID_TYPE, int]]]
56
- ticks: Dict[TARGET_ID_TYPE, Dict[PLAYER_ID_TYPE, Dict[SKILL_ID_TYPE, int]]]
57
 
58
- pet_snapshot: Dict[PET_ID_TYPE, Dict[BUFF_TYPE, BUFF_STACK_TYPE]]
59
- dot_snapshot: Dict[TARGET_ID_TYPE, Dict[PLAYER_ID_TYPE, Dict[SKILL_ID_TYPE, Dict[BUFF_TYPE, BUFF_STACK_TYPE]]]]
60
-
61
- last_dot: Dict[TARGET_ID_TYPE, Dict[PLAYER_ID_TYPE, Dict[SKILL_ID_TYPE, Tuple[SKILL_TYPE, Tuple[tuple, tuple]]]]]
62
- next_dot: Dict[TARGET_ID_TYPE, Dict[PLAYER_ID_TYPE, Dict[SKILL_ID_TYPE, int]]]
63
 
64
  start_frame: FRAME_TYPE
65
  end_frame: FRAME_TYPE
@@ -67,59 +66,43 @@ class Parser:
67
  select_talents: Dict[PLAYER_ID_TYPE, List[int]]
68
  select_equipments: Dict[PLAYER_ID_TYPE, Dict[int, Dict[str, int | list]]]
69
 
70
- players: Dict[PLAYER_ID_TYPE, School]
71
- targets: Dict[PLAYER_ID_TYPE, List[TARGET_ID_TYPE]]
72
 
73
  @property
74
  def current_school(self):
75
- return self.players[self.current_player]
76
-
77
- @property
78
- def current_targets(self):
79
- return self.targets[self.current_player]
80
 
81
  @property
82
  def current_records(self):
83
- return self.records[self.current_player][self.current_target]
84
 
85
  @property
86
  def current_hidden_buffs(self):
87
- return self.hidden_buffs[self.current_target][self.current_player]
88
-
89
- @property
90
- def current_player_buffs(self):
91
- return self.player_buffs[self.current_player]
92
 
93
  @property
94
- def current_target_buffs(self):
95
- return self.target_buffs[self.current_target][self.current_player]
96
 
97
  @property
98
  def current_snapshot(self):
99
- if self.current_caster in self.pet_snapshot:
100
- return self.pet_snapshot[self.current_caster]
101
- else:
102
- return self.dot_snapshot[self.current_target][self.current_player].get(self.current_skill, {})
103
-
104
- @property
105
- def current_dot_snapshot(self):
106
- return self.dot_snapshot[self.current_target][self.current_player]
107
 
108
  @property
109
  def current_stacks(self):
110
- return self.stacks[self.current_target][self.current_player]
111
 
112
  @property
113
  def current_ticks(self):
114
- return self.ticks[self.current_target][self.current_player]
115
 
116
  @property
117
  def current_last_dot(self):
118
- return self.last_dot[self.current_target][self.current_player]
119
 
120
  @property
121
  def current_next_dot(self):
122
- return self.next_dot[self.current_target][self.current_player]
123
 
124
  @property
125
  def duration(self):
@@ -128,33 +111,29 @@ class Parser:
128
  def reset(self):
129
  self.current_frame = 0
130
 
 
 
131
  self.id2name = {}
132
  self.name2id = {}
133
  self.pets = {}
134
 
135
- self.records = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(list))))
136
-
137
- self.hidden_buffs = defaultdict(lambda: defaultdict(dict))
138
- self.shift_buffs = defaultdict(lambda: defaultdict(dict))
139
-
140
- self.player_buffs = defaultdict(dict)
141
- self.target_buffs = defaultdict(lambda: defaultdict(dict))
142
 
143
- self.stacks = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: 1)))
144
- self.ticks = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: 0)))
 
145
 
146
- self.pet_snapshot = dict()
147
- self.dot_snapshot = defaultdict(lambda: defaultdict(dict))
148
- self.last_dot = defaultdict(lambda: defaultdict(dict))
149
- self.next_dot = defaultdict(lambda: defaultdict(dict))
 
150
 
151
  self.start_frame = 0
152
 
153
  self.select_talents = {}
154
  self.select_equipments = {}
155
-
156
- self.players = {}
157
- self.targets = defaultdict(list)
158
 
159
  @staticmethod
160
  def parse_equipments(detail):
@@ -176,90 +155,70 @@ class Parser:
176
  def parse_talents(detail):
177
  return [row[1] for row in detail]
178
 
179
- def parse_player(self, row):
180
  detail = row.strip("{}").split(",")
181
  player_id, school_id = int(detail[0]), int(detail[3])
182
  if player_id in self.id2name or school_id not in SUPPORT_SCHOOL:
183
  return
184
-
185
- if isinstance(detail := parse(row), list) and (school := SUPPORT_SCHOOL.get(detail[3])):
186
  player_name = detail[1]
187
  self.id2name[player_id] = player_name
188
  self.name2id[player_name] = player_id
189
- self.select_equipments[player_id] = self.parse_equipments(detail[5])
190
- self.select_talents[player_id] = self.parse_talents(detail[6])
191
- if any(talent not in school.talent_gains for talent in self.select_talents[player_id]):
192
- return
193
- self.players[player_id] = school
194
-
195
- def parse_npc(self, row):
196
- detail = row.strip("{}").split(",")
197
- npc_id, player_id = int(detail[0]), int(detail[3])
198
- if npc_id in self.id2name:
199
- return
200
-
201
- npc_name = detail[1]
202
- self.id2name[npc_id] = npc_name
203
- self.name2id[npc_name] = npc_id
204
- if player_id:
205
- self.pets[npc_id] = player_id
206
-
207
- def parse_pet(self, row):
208
- detail = row.strip("{}").split(",")
209
- pet_id, player_id = int(detail[0]), int(detail[3])
210
- if pet_id in self.pets:
211
- self.pet_snapshot[pet_id] = self.player_buffs[player_id].copy()
212
 
213
  def parse_shift_buff(self, row):
214
  detail = row.strip("{}").split(",")
215
  player_id = int(detail[0])
216
- if player_id not in self.players:
217
  return
218
-
219
  buff_id, buff_stack, buff_level = int(detail[4]), int(detail[5]), int(detail[8])
220
- if buff_id not in self.players[player_id].buffs:
221
  return
222
 
223
- frame_shift = self.players[player_id].buffs[buff_id].frame_shift
224
  if frame_shift:
225
- self.shift_buffs[self.current_frame + frame_shift][player_id][(buff_id, buff_level)] = buff_stack
226
 
227
  def parse_shift_status(self):
228
- for frame in list(self.shift_buffs):
229
  if frame > self.current_frame:
230
  break
231
- for player_id, shift_buffs in self.shift_buffs.pop(frame).items():
232
- for buff, buff_stack in shift_buffs.items():
233
  if buff_stack:
234
- self.player_buffs[player_id][buff] = buff_stack
235
  else:
236
- self.player_buffs[player_id].pop(buff, None)
237
 
238
  def parse_hidden_buffs(self):
239
- for target_id in self.hidden_buffs:
240
- for player_id, hidden_buffs in self.hidden_buffs[target_id].items():
241
- for buff, end_frame in hidden_buffs.items():
242
- if end_frame < self.current_frame:
243
- self.target_buffs[target_id][player_id].pop(buff, None)
244
 
245
- def parse_buff(self, row):
246
  detail = row.strip("{}").split(",")
247
  player_id = int(detail[0])
248
- if player_id not in self.players:
249
  return
250
 
251
  buff_id, buff_stack, buff_level = int(detail[4]), int(detail[5]), int(detail[8])
252
- if buff_id not in self.players[player_id].buffs:
253
  return
254
 
255
- frame_shift = self.players[player_id].buffs[buff_id].frame_shift
256
  if frame_shift:
257
  return
258
 
259
  if buff_stack:
260
- self.player_buffs[player_id][(buff_id, buff_level)] = buff_stack
261
  else:
262
- self.player_buffs[player_id].pop((buff_id, buff_level), None)
263
 
264
  def parse_skill(self, row):
265
  detail = row.strip("{}").split(",")
@@ -269,56 +228,51 @@ class Parser:
269
  else:
270
  player_id = caster_id
271
 
272
- if player_id not in self.players:
273
  return
274
 
275
  react, skill_id, skill_level, critical = int(detail[2]), int(detail[4]), int(detail[5]), detail[6] == "true"
276
- if react or skill_id not in self.players[player_id].skills:
277
  return
278
 
279
  if not self.start_frame:
280
- self.start_frame = self.current_frame
281
 
282
  self.current_player = player_id
283
  self.current_caster = caster_id
284
- if target_id not in self.current_targets:
285
- self.current_targets.append(target_id)
286
- self.current_target = target_id
287
- self.current_skill = skill_id
288
- skill = self.players[player_id].skills[skill_id]
289
- skill.skill_level = skill_level
290
- skill.record(critical, self)
291
-
292
- def status(self, skill_id):
 
 
 
 
 
293
  current_status = []
294
- for (buff_id, buff_level), buff_stack in self.current_player_buffs.items():
295
  buff = self.current_school.buffs[buff_id]
296
  if buff.gain_attributes:
297
  current_status.append((buff_id, buff_level, buff_stack))
298
  elif buff.gain_skills and skill_id in buff.gain_skills:
299
  current_status.append((buff_id, buff_level, buff_stack))
300
 
301
- self.current_skill = skill_id
302
  snapshot_status = []
303
- for (buff_id, buff_level), buff_stack in self.current_snapshot.items():
304
  buff = self.current_school.buffs[buff_id]
305
  if buff.gain_attributes:
306
  snapshot_status.append((buff_id, buff_level, buff_stack))
307
  elif buff.gain_skills and skill_id in buff.gain_skills:
308
  snapshot_status.append((buff_id, buff_level, buff_stack))
309
 
310
- target_status = []
311
- for (buff_id, buff_level), buff_stack in self.current_target_buffs.items():
312
- buff = self.current_school.buffs[buff_id]
313
- if buff.gain_attributes:
314
- target_status.append((buff_id, buff_level, buff_stack))
315
- elif buff.gain_skills and skill_id in buff.gain_skills:
316
- target_status.append((buff_id, buff_level, buff_stack))
317
-
318
- return tuple(current_status), tuple(snapshot_status), tuple(target_status)
319
 
320
  def __call__(self, file_name):
321
- self.file_name = file_name
322
  self.reset()
323
  lines = open(file_name).readlines()
324
  rows = []
@@ -326,11 +280,9 @@ class Parser:
326
  row = line.split("\t")
327
  rows.append(row)
328
  if row[4] == "4":
329
- self.parse_player(row[-1])
330
- elif row[4] == "8":
331
- self.parse_npc(row[-1])
332
 
333
- for player_id, school in self.players.items():
334
  school.prepare(self, player_id)
335
  for talent_id in self.select_talents[player_id]:
336
  school.talent_gains[talent_id].add_skills(school.skills)
@@ -347,24 +299,16 @@ class Parser:
347
  if row[4] == "8":
348
  self.parse_pet(row[-1])
349
  elif row[4] == "13":
350
- self.parse_buff(row[-1])
351
  elif row[4] == "21":
352
  self.parse_skill(row[-1])
353
 
354
  self.end_frame = self.current_frame
355
 
356
- for player_id, school in self.players.items():
357
  for talent_id in self.select_talents[player_id]:
358
  school.talent_gains[talent_id].sub_skills(school.skills)
359
 
360
- for player_id in self.records:
361
- player_record = defaultdict(lambda: defaultdict(list))
362
- for target_id, records in self.records[player_id].items():
363
- for skill_tuple, status in records.items():
364
- for status_tuple, timeline in status.items():
365
- player_record[skill_tuple][status_tuple] += timeline
366
- self.records[player_id][0] = player_record
367
-
368
 
369
  if __name__ == '__main__':
370
  parser = Parser()
 
4
  from schools import *
5
  from utils.lua import parse
6
 
7
+ FRAME_TYPE, PLAYER_ID_TYPE, PLAYER_NAME_TYPE, PET_ID_TYPE = int, int, int, int
8
  CASTER_ID_TYPE = PLAYER_ID_TYPE | PET_ID_TYPE
9
  SKILL_ID_TYPE, SKILL_LEVEL_TYPE, SKILL_STACK_TYPE, SKILL_CRITICAL_TYPE = int, int, int, bool
10
+ SKILL_BUFFER_TYPE = Tuple[SKILL_ID_TYPE, SKILL_LEVEL_TYPE, SKILL_CRITICAL_TYPE]
11
  SKILL_TYPE = Tuple[SKILL_ID_TYPE, SKILL_LEVEL_TYPE, SKILL_STACK_TYPE]
12
  BUFF_ID_TYPE, BUFF_LEVEL_TYPE, BUFF_STACK_TYPE = int, int, int
13
  BUFF_TYPE = Tuple[BUFF_ID_TYPE, BUFF_LEVEL_TYPE]
14
+ STATUS_TYPE = Tuple[BUFF_ID_TYPE, BUFF_LEVEL_TYPE, BUFF_STACK_TYPE]
15
 
16
+ SNAPSHOT_TYPE = Dict[SKILL_ID_TYPE | PET_ID_TYPE, Dict[BUFF_TYPE, BUFF_STACK_TYPE]]
17
+
18
+ TIMELINE_TYPE = List[Tuple[FRAME_TYPE, SKILL_CRITICAL_TYPE]]
19
+ SUB_RECORD_TYPE = Dict[Tuple[tuple, tuple], TIMELINE_TYPE]
20
  RECORD_TYPE = Dict[SKILL_TYPE, SUB_RECORD_TYPE]
21
 
22
  LABEL_MAPPING = {
 
35
  }
36
  EMBED_MAPPING: Dict[tuple, int] = {(5, 24449 - i): 8 - i for i in range(8)}
37
 
38
+ BUFFER_DELAY = 0
39
+
40
 
41
  class Parser:
42
  current_player: PLAYER_ID_TYPE
43
  current_caster: CASTER_ID_TYPE
 
 
44
  current_frame: FRAME_TYPE
45
+ frames: List[FRAME_TYPE]
46
 
47
+ id2name: Dict[PLAYER_ID_TYPE, PLAYER_NAME_TYPE]
48
+ name2id: Dict[PLAYER_NAME_TYPE, PLAYER_ID_TYPE]
49
  pets: Dict[PET_ID_TYPE, PLAYER_ID_TYPE]
50
+ records: Dict[PLAYER_ID_TYPE, RECORD_TYPE]
 
 
 
51
 
52
+ hidden_buffs: Dict[PLAYER_ID_TYPE, Dict[BUFF_TYPE, FRAME_TYPE]]
53
+ shift_status: Dict[FRAME_TYPE, Dict[PLAYER_ID_TYPE, Dict[BUFF_TYPE, BUFF_STACK_TYPE]]]
54
+ status: Dict[PLAYER_ID_TYPE, Dict[BUFF_TYPE, BUFF_STACK_TYPE]]
55
 
56
+ stacks: Dict[PLAYER_ID_TYPE, Dict[SKILL_ID_TYPE, int]]
57
+ ticks: Dict[PLAYER_ID_TYPE, Dict[SKILL_ID_TYPE, int]]
58
 
59
+ snapshot: Dict[PLAYER_ID_TYPE, SNAPSHOT_TYPE]
60
+ last_dot: Dict[PLAYER_ID_TYPE, Dict[SKILL_ID_TYPE, Tuple[SKILL_TYPE, Tuple[tuple, tuple]]]]
61
+ next_dot: Dict[PLAYER_ID_TYPE, Dict[SKILL_ID_TYPE, int]]
 
 
62
 
63
  start_frame: FRAME_TYPE
64
  end_frame: FRAME_TYPE
 
66
  select_talents: Dict[PLAYER_ID_TYPE, List[int]]
67
  select_equipments: Dict[PLAYER_ID_TYPE, Dict[int, Dict[str, int | list]]]
68
 
69
+ school: Dict[PLAYER_ID_TYPE, School]
 
70
 
71
  @property
72
  def current_school(self):
73
+ return self.school[self.current_player]
 
 
 
 
74
 
75
  @property
76
  def current_records(self):
77
+ return self.records[self.current_player]
78
 
79
  @property
80
  def current_hidden_buffs(self):
81
+ return self.hidden_buffs[self.current_player]
 
 
 
 
82
 
83
  @property
84
+ def current_status(self):
85
+ return self.status[self.current_player]
86
 
87
  @property
88
  def current_snapshot(self):
89
+ return self.snapshot[self.current_player]
 
 
 
 
 
 
 
90
 
91
  @property
92
  def current_stacks(self):
93
+ return self.stacks[self.current_player]
94
 
95
  @property
96
  def current_ticks(self):
97
+ return self.ticks[self.current_player]
98
 
99
  @property
100
  def current_last_dot(self):
101
+ return self.last_dot[self.current_player]
102
 
103
  @property
104
  def current_next_dot(self):
105
+ return self.next_dot[self.current_player]
106
 
107
  @property
108
  def duration(self):
 
111
  def reset(self):
112
  self.current_frame = 0
113
 
114
+ self.frames = []
115
+
116
  self.id2name = {}
117
  self.name2id = {}
118
  self.pets = {}
119
 
120
+ self.records = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
 
 
 
 
 
 
121
 
122
+ self.hidden_buffs = defaultdict(dict)
123
+ self.shift_status = defaultdict(lambda: defaultdict(dict))
124
+ self.status = defaultdict(lambda: defaultdict(int))
125
 
126
+ self.stacks = defaultdict(lambda: defaultdict(lambda: 1))
127
+ self.ticks = defaultdict(lambda: defaultdict(lambda: 0))
128
+ self.snapshot = defaultdict(dict)
129
+ self.last_dot = defaultdict(dict)
130
+ self.next_dot = defaultdict(dict)
131
 
132
  self.start_frame = 0
133
 
134
  self.select_talents = {}
135
  self.select_equipments = {}
136
+ self.school = {}
 
 
137
 
138
  @staticmethod
139
  def parse_equipments(detail):
 
155
  def parse_talents(detail):
156
  return [row[1] for row in detail]
157
 
158
+ def parse_info(self, row):
159
  detail = row.strip("{}").split(",")
160
  player_id, school_id = int(detail[0]), int(detail[3])
161
  if player_id in self.id2name or school_id not in SUPPORT_SCHOOL:
162
  return
163
+ if isinstance(detail := parse(row), list):
 
164
  player_name = detail[1]
165
  self.id2name[player_id] = player_name
166
  self.name2id[player_name] = player_id
167
+ if school := SUPPORT_SCHOOL.get(detail[3]):
168
+ self.select_equipments[player_id] = self.parse_equipments(detail[5])
169
+ self.select_talents[player_id] = self.parse_talents(detail[6])
170
+ if any(talent not in school.talent_gains for talent in self.select_talents[player_id]):
171
+ return
172
+ self.school[player_id] = school
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
  def parse_shift_buff(self, row):
175
  detail = row.strip("{}").split(",")
176
  player_id = int(detail[0])
177
+ if player_id not in self.school:
178
  return
 
179
  buff_id, buff_stack, buff_level = int(detail[4]), int(detail[5]), int(detail[8])
180
+ if buff_id not in self.school[player_id].buffs:
181
  return
182
 
183
+ frame_shift = self.school[player_id].buffs[buff_id].frame_shift
184
  if frame_shift:
185
+ self.shift_status[self.current_frame + frame_shift][player_id][(buff_id, buff_level)] = buff_stack
186
 
187
  def parse_shift_status(self):
188
+ for frame in list(self.shift_status):
189
  if frame > self.current_frame:
190
  break
191
+ for player_id, status_buffer in self.shift_status.pop(frame).items():
192
+ for buff, buff_stack in status_buffer.items():
193
  if buff_stack:
194
+ self.status[player_id][buff] = buff_stack
195
  else:
196
+ self.status[player_id].pop(buff, None)
197
 
198
  def parse_hidden_buffs(self):
199
+ for player_id, hidden_buffs in self.hidden_buffs.items():
200
+ for buff, end_frame in hidden_buffs.items():
201
+ if end_frame < self.current_frame:
202
+ self.status[player_id].pop(buff, None)
 
203
 
204
+ def parse_status(self, row):
205
  detail = row.strip("{}").split(",")
206
  player_id = int(detail[0])
207
+ if player_id not in self.school:
208
  return
209
 
210
  buff_id, buff_stack, buff_level = int(detail[4]), int(detail[5]), int(detail[8])
211
+ if buff_id not in self.school[player_id].buffs:
212
  return
213
 
214
+ frame_shift = self.school[player_id].buffs[buff_id].frame_shift
215
  if frame_shift:
216
  return
217
 
218
  if buff_stack:
219
+ self.status[player_id][(buff_id, buff_level)] = buff_stack
220
  else:
221
+ self.status[player_id].pop((buff_id, buff_level), None)
222
 
223
  def parse_skill(self, row):
224
  detail = row.strip("{}").split(",")
 
228
  else:
229
  player_id = caster_id
230
 
231
+ if player_id not in self.school:
232
  return
233
 
234
  react, skill_id, skill_level, critical = int(detail[2]), int(detail[4]), int(detail[5]), detail[6] == "true"
235
+ if react or skill_id not in self.school[player_id].skills:
236
  return
237
 
238
  if not self.start_frame:
239
+ self.start_frame = self.current_frame - 1
240
 
241
  self.current_player = player_id
242
  self.current_caster = caster_id
243
+ skill = self.school[player_id].skills[skill_id]
244
+ skill.record(skill_level, critical, self)
245
+
246
+ def parse_pet(self, row):
247
+ detail = row.strip("{}").split(",")
248
+ pet_id, player_id = int(detail[0]), int(detail[3])
249
+ if player_id in self.school:
250
+ self.pets[pet_id] = player_id
251
+ self.snapshot[player_id][pet_id] = self.status[player_id].copy()
252
+
253
+ def available_status(self, skill_id, snapshot_id=None):
254
+ if not snapshot_id:
255
+ snapshot_id = skill_id
256
+
257
  current_status = []
258
+ for (buff_id, buff_level), buff_stack in self.current_status.items():
259
  buff = self.current_school.buffs[buff_id]
260
  if buff.gain_attributes:
261
  current_status.append((buff_id, buff_level, buff_stack))
262
  elif buff.gain_skills and skill_id in buff.gain_skills:
263
  current_status.append((buff_id, buff_level, buff_stack))
264
 
 
265
  snapshot_status = []
266
+ for (buff_id, buff_level), buff_stack in self.current_snapshot.get(snapshot_id, {}).items():
267
  buff = self.current_school.buffs[buff_id]
268
  if buff.gain_attributes:
269
  snapshot_status.append((buff_id, buff_level, buff_stack))
270
  elif buff.gain_skills and skill_id in buff.gain_skills:
271
  snapshot_status.append((buff_id, buff_level, buff_stack))
272
 
273
+ return tuple(current_status), tuple(snapshot_status)
 
 
 
 
 
 
 
 
274
 
275
  def __call__(self, file_name):
 
276
  self.reset()
277
  lines = open(file_name).readlines()
278
  rows = []
 
280
  row = line.split("\t")
281
  rows.append(row)
282
  if row[4] == "4":
283
+ self.parse_info(row[-1])
 
 
284
 
285
+ for player_id, school in self.school.items():
286
  school.prepare(self, player_id)
287
  for talent_id in self.select_talents[player_id]:
288
  school.talent_gains[talent_id].add_skills(school.skills)
 
299
  if row[4] == "8":
300
  self.parse_pet(row[-1])
301
  elif row[4] == "13":
302
+ self.parse_status(row[-1])
303
  elif row[4] == "21":
304
  self.parse_skill(row[-1])
305
 
306
  self.end_frame = self.current_frame
307
 
308
+ for player_id, school in self.school.items():
309
  for talent_id in self.select_talents[player_id]:
310
  school.talent_gains[talent_id].sub_skills(school.skills)
311
 
 
 
 
 
 
 
 
 
312
 
313
  if __name__ == '__main__':
314
  parser = Parser()