Spaces:
Runtime error
Runtime error
5.2 commit
Browse files- .github/workflows/nuitka-app.yml +1 -1
- base/skill.py +26 -32
- qt/app.py +1 -1
- qt/components/dashboard.py +0 -2
- qt/scripts/dashboard.py +3 -12
- qt/scripts/top.py +1 -3
- schools/__init__.py +0 -10
- utils/parser.py +145 -100
.github/workflows/nuitka-app.yml
CHANGED
|
@@ -26,7 +26,7 @@ jobs:
|
|
| 26 |
- name: Install dependencies
|
| 27 |
run: |
|
| 28 |
python -m pip install --upgrade pip
|
| 29 |
-
pip install - ./requirements.txt
|
| 30 |
- uses: Nuitka/Nuitka-Action@main
|
| 31 |
with:
|
| 32 |
nuitka-version: main
|
|
|
|
| 26 |
- name: Install dependencies
|
| 27 |
run: |
|
| 28 |
python -m pip install --upgrade pip
|
| 29 |
+
pip install -r ./requirements.txt
|
| 30 |
- uses: Nuitka/Nuitka-Action@main
|
| 31 |
with:
|
| 32 |
nuitka-version: main
|
base/skill.py
CHANGED
|
@@ -168,7 +168,7 @@ class Skill:
|
|
| 168 |
else:
|
| 169 |
self._skill_shield_gain = [skill_shield_gain]
|
| 170 |
|
| 171 |
-
def record(self,
|
| 172 |
pass
|
| 173 |
|
| 174 |
def __call__(self, attribute: Attribute):
|
|
@@ -184,38 +184,33 @@ class BuffConsumeSkill(Skill):
|
|
| 184 |
|
| 185 |
|
| 186 |
class DotSkill(Skill):
|
| 187 |
-
def record(self,
|
| 188 |
-
super().record(
|
| 189 |
bind_skill = self.bind_skill
|
| 190 |
-
if not parser.
|
| 191 |
-
parser.
|
| 192 |
-
parser.
|
| 193 |
-
parser.
|
| 194 |
-
parser.
|
| 195 |
|
| 196 |
|
| 197 |
class DotConsumeSkill(Skill):
|
| 198 |
-
def record(self,
|
| 199 |
-
super().record(
|
| 200 |
bind_skill = self.bind_skill
|
| 201 |
-
skill_tuple, status_tuple = parser.
|
| 202 |
skill_id, skill_level, skill_stack = skill_tuple
|
| 203 |
-
parser.
|
| 204 |
-
tick = min(parser.
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
current_record[skill_tuple][status_tuple].pop()
|
| 208 |
)
|
| 209 |
-
parser.
|
| 210 |
|
| 211 |
|
| 212 |
class PureSkill(Skill):
|
| 213 |
def __call__(self, attribute: Attribute):
|
| 214 |
-
damage = init_result(
|
| 215 |
-
self.damage_base, self.damage_rand,
|
| 216 |
-
0, 0,
|
| 217 |
-
0, 0, 0, 0
|
| 218 |
-
)
|
| 219 |
|
| 220 |
damage = level_reduction_result(damage, attribute.level_reduction)
|
| 221 |
damage = vulnerable_result(damage, attribute.vulnerable)
|
|
@@ -326,22 +321,21 @@ class MixingSkill(Skill):
|
|
| 326 |
|
| 327 |
|
| 328 |
class Damage(Skill):
|
| 329 |
-
def record(self,
|
| 330 |
-
super().record(
|
| 331 |
skill_tuple = (self.skill_id, skill_level, skill_stack)
|
| 332 |
-
status_tuple = parser.available_status(
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
(current_frame - parser.start_time[player_id][-1], critical)
|
| 336 |
)
|
| 337 |
return skill_tuple, status_tuple
|
| 338 |
|
| 339 |
|
| 340 |
class DotDamage(Damage):
|
| 341 |
-
def record(self,
|
| 342 |
-
skill_tuple, status_tuple = super().record(
|
| 343 |
-
parser.
|
| 344 |
-
parser.
|
| 345 |
|
| 346 |
|
| 347 |
class PetDamage(Damage):
|
|
|
|
| 168 |
else:
|
| 169 |
self._skill_shield_gain = [skill_shield_gain]
|
| 170 |
|
| 171 |
+
def record(self, skill_level, skill_stack, critical, parser):
|
| 172 |
pass
|
| 173 |
|
| 174 |
def __call__(self, attribute: Attribute):
|
|
|
|
| 184 |
|
| 185 |
|
| 186 |
class DotSkill(Skill):
|
| 187 |
+
def record(self, skill_level, skill_stack, critical, parser):
|
| 188 |
+
super().record(skill_level, skill_stack, critical, parser)
|
| 189 |
bind_skill = self.bind_skill
|
| 190 |
+
if not parser.current_ticks[bind_skill]:
|
| 191 |
+
parser.current_stacks[bind_skill] = 0
|
| 192 |
+
parser.current_ticks[bind_skill] = self.tick
|
| 193 |
+
parser.current_stacks[bind_skill] = min(parser.current_stacks[bind_skill] + 1, self.max_stack)
|
| 194 |
+
parser.current_snapshot[bind_skill] = parser.current_status.copy()
|
| 195 |
|
| 196 |
|
| 197 |
class DotConsumeSkill(Skill):
|
| 198 |
+
def record(self, skill_level, skill_stack, critical, parser):
|
| 199 |
+
super().record(skill_level, skill_stack, critical, parser)
|
| 200 |
bind_skill = self.bind_skill
|
| 201 |
+
skill_tuple, status_tuple = parser.current_last_dot[bind_skill]
|
| 202 |
skill_id, skill_level, skill_stack = skill_tuple
|
| 203 |
+
parser.current_ticks[skill_id] += 1
|
| 204 |
+
tick = min(parser.current_ticks[skill_id], self.tick)
|
| 205 |
+
parser.current_records[(skill_id, skill_level, skill_stack * tick)][status_tuple].append(
|
| 206 |
+
parser.current_records[skill_tuple][status_tuple].pop()
|
|
|
|
| 207 |
)
|
| 208 |
+
parser.current_ticks[skill_id] -= tick
|
| 209 |
|
| 210 |
|
| 211 |
class PureSkill(Skill):
|
| 212 |
def __call__(self, attribute: Attribute):
|
| 213 |
+
damage = init_result(self.damage_base, self.damage_rand, 0, 0, 0, 0, 0, 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
|
| 215 |
damage = level_reduction_result(damage, attribute.level_reduction)
|
| 216 |
damage = vulnerable_result(damage, attribute.vulnerable)
|
|
|
|
| 321 |
|
| 322 |
|
| 323 |
class Damage(Skill):
|
| 324 |
+
def record(self, skill_level, skill_stack, critical, parser):
|
| 325 |
+
super().record(skill_level, skill_stack, critical, parser)
|
| 326 |
skill_tuple = (self.skill_id, skill_level, skill_stack)
|
| 327 |
+
status_tuple = parser.available_status(self.skill_id)
|
| 328 |
+
parser.current_records[skill_tuple][status_tuple].append(
|
| 329 |
+
(parser.current_frame - parser.start_frame, critical)
|
|
|
|
| 330 |
)
|
| 331 |
return skill_tuple, status_tuple
|
| 332 |
|
| 333 |
|
| 334 |
class DotDamage(Damage):
|
| 335 |
+
def record(self, skill_level, skill_stack, critical, parser):
|
| 336 |
+
skill_tuple, status_tuple = super().record(skill_level, skill_stack, critical, parser)
|
| 337 |
+
parser.current_last_dot[self.skill_id] = (skill_tuple, status_tuple)
|
| 338 |
+
parser.current_ticks[self.skill_id] -= 1
|
| 339 |
|
| 340 |
|
| 341 |
class PetDamage(Damage):
|
qt/app.py
CHANGED
|
@@ -82,7 +82,7 @@ class MainWindow(QMainWindow):
|
|
| 82 |
equipments = equipments_script(self.equipments_widget)
|
| 83 |
consumables = consumables_script(self.consumable_widget)
|
| 84 |
bonuses = bonuses_script(parser, self.bonus_widget)
|
| 85 |
-
dashboard_script(parser, self.
|
| 86 |
talents, recipes, equipments, consumables, bonuses)
|
| 87 |
|
| 88 |
|
|
|
|
| 82 |
equipments = equipments_script(self.equipments_widget)
|
| 83 |
consumables = consumables_script(self.consumable_widget)
|
| 84 |
bonuses = bonuses_script(parser, self.bonus_widget)
|
| 85 |
+
dashboard_script(parser, self.dashboard_widget,
|
| 86 |
talents, recipes, equipments, consumables, bonuses)
|
| 87 |
|
| 88 |
|
qt/components/dashboard.py
CHANGED
|
@@ -32,8 +32,6 @@ class DashboardWidget(QWidget):
|
|
| 32 |
top_layout = QHBoxLayout()
|
| 33 |
layout.addLayout(top_layout)
|
| 34 |
|
| 35 |
-
self.fight_select = ComboWithLabel("选择战斗")
|
| 36 |
-
top_layout.addWidget(self.fight_select)
|
| 37 |
self.target_level = ComboWithLabel("目标等级", items=[str(level) for level in SHIELD_BASE_MAP])
|
| 38 |
top_layout.addWidget(self.target_level)
|
| 39 |
self.duration = DoubleSpinWithLabel("战斗时长", maximum=3600, value=180)
|
|
|
|
| 32 |
top_layout = QHBoxLayout()
|
| 33 |
layout.addLayout(top_layout)
|
| 34 |
|
|
|
|
|
|
|
| 35 |
self.target_level = ComboWithLabel("目标等级", items=[str(level) for level in SHIELD_BASE_MAP])
|
| 36 |
top_layout.addWidget(self.target_level)
|
| 37 |
self.duration = DoubleSpinWithLabel("战斗时长", maximum=3600, value=180)
|
qt/scripts/dashboard.py
CHANGED
|
@@ -46,24 +46,15 @@ def detail_content(detail: Detail):
|
|
| 46 |
return damage_content, gradient_content
|
| 47 |
|
| 48 |
|
| 49 |
-
def dashboard_script(parser: Parser,
|
| 50 |
dashboard_widget: DashboardWidget, talents: Talents, recipes: Recipes,
|
| 51 |
equipments: Equipments, consumables: Consumables, bonuses: Bonuses):
|
| 52 |
|
| 53 |
-
def select_fight(text):
|
| 54 |
-
player_id = parser.current_player
|
| 55 |
-
index = parser.record_index[player_id][text]
|
| 56 |
-
dashboard_widget.duration.set_value(parser.duration(player_id, index))
|
| 57 |
-
|
| 58 |
-
dashboard_widget.fight_select.combo_box.currentTextChanged.connect(select_fight)
|
| 59 |
-
|
| 60 |
def formulate():
|
| 61 |
duration = dashboard_widget.duration.spin_box.value()
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
record = parser.records[player_id][parser.record_index[player_id][record_index]]
|
| 65 |
|
| 66 |
-
school = parser.school[player_id]
|
| 67 |
attribute = school.attribute()
|
| 68 |
attribute.target_level = int(dashboard_widget.target_level.combo_box.currentText())
|
| 69 |
for attr, value in equipments.attrs.items():
|
|
|
|
| 46 |
return damage_content, gradient_content
|
| 47 |
|
| 48 |
|
| 49 |
+
def dashboard_script(parser: Parser,
|
| 50 |
dashboard_widget: DashboardWidget, talents: Talents, recipes: Recipes,
|
| 51 |
equipments: Equipments, consumables: Consumables, bonuses: Bonuses):
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
def formulate():
|
| 54 |
duration = dashboard_widget.duration.spin_box.value()
|
| 55 |
+
record = parser.current_records
|
| 56 |
+
school = parser.school[parser.current_player]
|
|
|
|
| 57 |
|
|
|
|
| 58 |
attribute = school.attribute()
|
| 59 |
attribute.target_level = int(dashboard_widget.target_level.combo_box.currentText())
|
| 60 |
for attr, value in equipments.attrs.items():
|
qt/scripts/top.py
CHANGED
|
@@ -46,9 +46,7 @@ def top_script(
|
|
| 46 |
config_choices = list(CONFIG.get(school.school, {}))
|
| 47 |
config_widget.config_select.set_items(config_choices, default_index=-1)
|
| 48 |
""" Update dashboard """
|
| 49 |
-
|
| 50 |
-
dashboard_widget.fight_select.set_items(record_index, default_index=0)
|
| 51 |
-
dashboard_widget.duration.set_value(parser.duration(player_id, parser.record_index[player_id][record_index[0]]))
|
| 52 |
|
| 53 |
""" Update talent options """
|
| 54 |
for i, talent_widget in enumerate(talents_widget.values()):
|
|
|
|
| 46 |
config_choices = list(CONFIG.get(school.school, {}))
|
| 47 |
config_widget.config_select.set_items(config_choices, default_index=-1)
|
| 48 |
""" Update dashboard """
|
| 49 |
+
dashboard_widget.duration.set_value(parser.duration)
|
|
|
|
|
|
|
| 50 |
|
| 51 |
""" Update talent options """
|
| 52 |
for i, talent_widget in enumerate(talents_widget.values()):
|
schools/__init__.py
CHANGED
|
@@ -8,16 +8,6 @@ from base.skill import Skill
|
|
| 8 |
|
| 9 |
from schools import bei_ao_jue, shan_hai_xin_jue, ling_hai_jue, wu_fang, gu_feng_jue, tai_xu_jian_yi, tian_luo_gui_dao
|
| 10 |
|
| 11 |
-
SKILL_TYPE = Tuple[int, int, int]
|
| 12 |
-
BUFFER_TYPE = Tuple[int, int, int, bool]
|
| 13 |
-
# BUFFER_TYPE = Tuple[int, int]
|
| 14 |
-
BUFF_TYPE = Tuple[int, int, int]
|
| 15 |
-
TIMELINE_TYPE = List[Tuple[int, bool]]
|
| 16 |
-
SUB_RECORD_TYPE = Dict[Tuple[tuple, tuple], TIMELINE_TYPE]
|
| 17 |
-
RECORD_TYPE = Dict[SKILL_TYPE, SUB_RECORD_TYPE]
|
| 18 |
-
STATUS_TYPE = Dict[Tuple[int, int], int]
|
| 19 |
-
SNAPSHOT_TYPE = Dict[int, STATUS_TYPE]
|
| 20 |
-
|
| 21 |
|
| 22 |
@dataclass
|
| 23 |
class School:
|
|
|
|
| 8 |
|
| 9 |
from schools import bei_ao_jue, shan_hai_xin_jue, ling_hai_jue, wu_fang, gu_feng_jue, tai_xu_jian_yi, tian_luo_gui_dao
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
@dataclass
|
| 13 |
class School:
|
utils/parser.py
CHANGED
|
@@ -4,6 +4,19 @@ from base.constant import FRAME_PER_SECOND
|
|
| 4 |
from schools import *
|
| 5 |
from utils.lua import parse
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
LABEL_MAPPING = {
|
| 9 |
2: "远程武器",
|
|
@@ -21,71 +34,89 @@ LABEL_MAPPING = {
|
|
| 21 |
}
|
| 22 |
EMBED_MAPPING: Dict[tuple, int] = {(5, 24449 - i): 8 - i for i in range(8)}
|
| 23 |
|
| 24 |
-
BUFFER_DELAY = 2
|
| 25 |
|
| 26 |
|
| 27 |
class Parser:
|
| 28 |
-
current_player:
|
| 29 |
-
current_frame:
|
|
|
|
| 30 |
|
| 31 |
-
id2name: Dict[
|
| 32 |
-
name2id: Dict[
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
status: Dict[int, STATUS_TYPE]
|
| 36 |
-
snapshot: Dict[int, SNAPSHOT_TYPE]
|
| 37 |
-
last_dot: Dict[int, Dict[int, Tuple[Tuple[int, int, int], Tuple[tuple, tuple]]]]
|
| 38 |
-
stacks: Dict[int, Dict[int, int]]
|
| 39 |
-
ticks: Dict[int, Dict[int, int]]
|
| 40 |
-
pets: Dict[int, int]
|
| 41 |
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
| 48 |
|
| 49 |
-
|
|
|
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
for (buff_id, buff_level), buff_stack in self.status[player_id].items():
|
| 57 |
-
buff = self.school[player_id].buffs[buff_id]
|
| 58 |
-
if buff.gain_attributes:
|
| 59 |
-
current_status.append((buff_id, buff_level, buff_stack))
|
| 60 |
-
elif buff.gain_skills and skill_id in buff.gain_skills:
|
| 61 |
-
current_status.append((buff_id, buff_level, buff_stack))
|
| 62 |
|
| 63 |
-
|
| 64 |
-
for (buff_id, buff_level), buff_stack in self.snapshot[player_id].get(skill_id, {}).items():
|
| 65 |
-
buff = self.school[player_id].buffs[buff_id]
|
| 66 |
-
if buff.gain_attributes:
|
| 67 |
-
snapshot_status.append((buff_id, buff_level, buff_stack))
|
| 68 |
-
elif buff.gain_skills and skill_id in buff.gain_skills:
|
| 69 |
-
snapshot_status.append((buff_id, buff_level, buff_stack))
|
| 70 |
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
def reset(self):
|
| 74 |
self.current_frame = 0
|
| 75 |
|
|
|
|
|
|
|
| 76 |
self.id2name = {}
|
| 77 |
self.name2id = {}
|
| 78 |
-
self.
|
| 79 |
-
self.
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
self.snapshot = defaultdict(dict)
|
| 82 |
self.last_dot = defaultdict(dict)
|
| 83 |
-
self.stacks = defaultdict(lambda: defaultdict(lambda: 1))
|
| 84 |
-
self.ticks = defaultdict(lambda: defaultdict(int))
|
| 85 |
-
self.pets = {}
|
| 86 |
|
| 87 |
-
self.
|
| 88 |
-
self.end_time = defaultdict(list)
|
| 89 |
|
| 90 |
self.select_talents = {}
|
| 91 |
self.select_equipments = {}
|
|
@@ -131,18 +162,8 @@ class Parser:
|
|
| 131 |
if player_id in self.school:
|
| 132 |
self.pets[pet_id] = player_id
|
| 133 |
|
| 134 |
-
def parse_time(self, row):
|
| 135 |
-
detail = row.strip("{}").split(",")
|
| 136 |
-
player_id = int(detail[0])
|
| 137 |
-
if player_id not in self.school:
|
| 138 |
-
return
|
| 139 |
-
if detail[1] == "true" and len(self.start_time[player_id]) == len(self.end_time[player_id]):
|
| 140 |
-
self.start_time[player_id].append(self.current_frame)
|
| 141 |
-
self.records[player_id].append(defaultdict(lambda: defaultdict(list)))
|
| 142 |
-
elif detail[1] == "false" and len(self.start_time[player_id]) - len(self.end_time[player_id]) == 1:
|
| 143 |
-
self.end_time[player_id].append(self.current_frame)
|
| 144 |
-
|
| 145 |
def parse_buff(self, row):
|
|
|
|
| 146 |
detail = row.strip("{}").split(",")
|
| 147 |
player_id = int(detail[0])
|
| 148 |
if player_id not in self.school:
|
|
@@ -150,10 +171,26 @@ class Parser:
|
|
| 150 |
buff_id, buff_stack, buff_level = int(detail[4]), int(detail[5]), int(detail[8])
|
| 151 |
if buff_id not in self.school[player_id].buffs:
|
| 152 |
return
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
def parse_skill(self, row):
|
| 159 |
detail = row.strip("{}").split(",")
|
|
@@ -169,30 +206,34 @@ class Parser:
|
|
| 169 |
if react or skill_id not in self.school[player_id].skills:
|
| 170 |
return
|
| 171 |
skill_stack = self.stacks[player_id][skill_id]
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
self.
|
| 177 |
-
|
| 178 |
-
def
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
|
| 197 |
def __call__(self, file_name):
|
| 198 |
self.reset()
|
|
@@ -202,19 +243,11 @@ class Parser:
|
|
| 202 |
if row[4] == "4":
|
| 203 |
self.parse_info(row[-1])
|
| 204 |
|
| 205 |
-
for player_id, school in self.school.items():
|
| 206 |
-
for talent_id in self.select_talents[player_id]:
|
| 207 |
-
school.talent_gains[talent_id].add_skills(school.skills)
|
| 208 |
-
|
| 209 |
for line in lines:
|
| 210 |
row = line.split("\t")
|
| 211 |
-
|
| 212 |
-
self.parse_record()
|
| 213 |
-
self.current_frame = int(row[1])
|
| 214 |
|
| 215 |
match row[4]:
|
| 216 |
-
case "5":
|
| 217 |
-
self.parse_time(row[-1])
|
| 218 |
case "8":
|
| 219 |
self.parse_pet(row[-1])
|
| 220 |
case "13":
|
|
@@ -222,14 +255,26 @@ class Parser:
|
|
| 222 |
case "21":
|
| 223 |
self.parse_skill(row[-1])
|
| 224 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
for player_id, school in self.school.items():
|
| 226 |
for talent_id in self.select_talents[player_id]:
|
| 227 |
school.talent_gains[talent_id].sub_skills(school.skills)
|
| 228 |
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
for player_id in self.end_time
|
| 235 |
-
}
|
|
|
|
| 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 |
+
SKILL_ID_TYPE, SKILL_LEVEL_TYPE, SKILL_STACK_TYPE, SKILL_CRITICAL_TYPE = int, int, int, bool
|
| 9 |
+
SKILL_BUFFER_TYPE = Tuple[SKILL_ID_TYPE, SKILL_LEVEL_TYPE, SKILL_STACK_TYPE, SKILL_CRITICAL_TYPE]
|
| 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 |
+
STATUS_BUFFER_TYPE = Tuple[BUFF_ID_TYPE, BUFF_LEVEL_TYPE]
|
| 13 |
+
STATUS_TYPE = Tuple[BUFF_ID_TYPE, BUFF_LEVEL_TYPE, BUFF_STACK_TYPE]
|
| 14 |
+
|
| 15 |
+
SNAPSHOT_TYPE = Dict[SKILL_ID_TYPE, List[STATUS_TYPE]]
|
| 16 |
+
|
| 17 |
+
TIMELINE_TYPE = List[Tuple[FRAME_TYPE, SKILL_CRITICAL_TYPE]]
|
| 18 |
+
SUB_RECORD_TYPE = Dict[Tuple[tuple, tuple], TIMELINE_TYPE]
|
| 19 |
+
RECORD_TYPE = Dict[SKILL_TYPE, SUB_RECORD_TYPE]
|
| 20 |
|
| 21 |
LABEL_MAPPING = {
|
| 22 |
2: "远程武器",
|
|
|
|
| 34 |
}
|
| 35 |
EMBED_MAPPING: Dict[tuple, int] = {(5, 24449 - i): 8 - i for i in range(8)}
|
| 36 |
|
| 37 |
+
BUFFER_DELAY = -2
|
| 38 |
|
| 39 |
|
| 40 |
class Parser:
|
| 41 |
+
current_player: PLAYER_ID_TYPE
|
| 42 |
+
current_frame: FRAME_TYPE
|
| 43 |
+
frames: List[FRAME_TYPE]
|
| 44 |
|
| 45 |
+
id2name: Dict[PLAYER_ID_TYPE, PLAYER_NAME_TYPE]
|
| 46 |
+
name2id: Dict[PLAYER_NAME_TYPE, PLAYER_ID_TYPE]
|
| 47 |
+
pets: Dict[PET_ID_TYPE, PLAYER_ID_TYPE]
|
| 48 |
+
records: Dict[PLAYER_ID_TYPE, RECORD_TYPE]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
+
skills: Dict[FRAME_TYPE, Dict[PLAYER_ID_TYPE, List[SKILL_BUFFER_TYPE]]]
|
| 51 |
+
status_buffer: Dict[PLAYER_ID_TYPE, Dict[STATUS_BUFFER_TYPE, Tuple[BUFF_STACK_TYPE, FRAME_TYPE]]]
|
| 52 |
+
status: Dict[FRAME_TYPE, Dict[PLAYER_ID_TYPE, List[STATUS_TYPE]]]
|
| 53 |
|
| 54 |
+
stacks: Dict[PLAYER_ID_TYPE, Dict[SKILL_ID_TYPE, int]]
|
| 55 |
+
ticks: Dict[PLAYER_ID_TYPE, Dict[SKILL_ID_TYPE, int]]
|
| 56 |
|
| 57 |
+
snapshot: Dict[PLAYER_ID_TYPE, SNAPSHOT_TYPE]
|
| 58 |
+
last_dot: Dict[PLAYER_ID_TYPE, Dict[SKILL_ID_TYPE, Tuple[SKILL_TYPE, Tuple[tuple, tuple]]]]
|
| 59 |
|
| 60 |
+
start_frame: FRAME_TYPE
|
| 61 |
+
end_frame: FRAME_TYPE
|
| 62 |
|
| 63 |
+
select_talents: Dict[PLAYER_ID_TYPE, List[int]]
|
| 64 |
+
select_equipments: Dict[PLAYER_ID_TYPE, Dict[int, Dict[str, int | list]]]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
+
school: Dict[PLAYER_ID_TYPE, School]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
+
@property
|
| 69 |
+
def current_records(self):
|
| 70 |
+
return self.records[self.current_player]
|
| 71 |
+
|
| 72 |
+
@property
|
| 73 |
+
def current_skills(self):
|
| 74 |
+
return self.skills[self.current_frame][self.current_player]
|
| 75 |
+
|
| 76 |
+
@property
|
| 77 |
+
def current_status(self):
|
| 78 |
+
return self.status[self.current_frame][self.current_player]
|
| 79 |
+
|
| 80 |
+
@property
|
| 81 |
+
def current_snapshot(self):
|
| 82 |
+
return self.snapshot[self.current_player]
|
| 83 |
+
|
| 84 |
+
@property
|
| 85 |
+
def current_stacks(self):
|
| 86 |
+
return self.stacks[self.current_player]
|
| 87 |
+
|
| 88 |
+
@property
|
| 89 |
+
def current_ticks(self):
|
| 90 |
+
return self.ticks[self.current_player]
|
| 91 |
+
|
| 92 |
+
@property
|
| 93 |
+
def current_last_dot(self):
|
| 94 |
+
return self.last_dot[self.current_player]
|
| 95 |
+
|
| 96 |
+
@property
|
| 97 |
+
def duration(self):
|
| 98 |
+
return round((self.end_frame - self.start_frame) / FRAME_PER_SECOND, 3)
|
| 99 |
|
| 100 |
def reset(self):
|
| 101 |
self.current_frame = 0
|
| 102 |
|
| 103 |
+
self.frames = []
|
| 104 |
+
|
| 105 |
self.id2name = {}
|
| 106 |
self.name2id = {}
|
| 107 |
+
self.pets = {}
|
| 108 |
+
self.records = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
|
| 109 |
+
|
| 110 |
+
self.skills = defaultdict(lambda: defaultdict(list))
|
| 111 |
+
self.status_buffer = defaultdict(dict)
|
| 112 |
+
self.status = defaultdict(lambda: defaultdict(list))
|
| 113 |
+
|
| 114 |
+
self.stacks = defaultdict(lambda: defaultdict(lambda: 1))
|
| 115 |
+
self.ticks = defaultdict(lambda: defaultdict(lambda: 0))
|
| 116 |
self.snapshot = defaultdict(dict)
|
| 117 |
self.last_dot = defaultdict(dict)
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
+
self.start_frame = 0
|
|
|
|
| 120 |
|
| 121 |
self.select_talents = {}
|
| 122 |
self.select_equipments = {}
|
|
|
|
| 162 |
if player_id in self.school:
|
| 163 |
self.pets[pet_id] = player_id
|
| 164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
def parse_buff(self, row):
|
| 166 |
+
|
| 167 |
detail = row.strip("{}").split(",")
|
| 168 |
player_id = int(detail[0])
|
| 169 |
if player_id not in self.school:
|
|
|
|
| 171 |
buff_id, buff_stack, buff_level = int(detail[4]), int(detail[5]), int(detail[8])
|
| 172 |
if buff_id not in self.school[player_id].buffs:
|
| 173 |
return
|
| 174 |
+
|
| 175 |
+
if (buff_id, buff_level) in self.status_buffer[player_id]:
|
| 176 |
+
buffer_stack, start_frame = self.status_buffer[player_id][(buff_id, buff_level)]
|
| 177 |
+
if buff_stack == buffer_stack:
|
| 178 |
+
return
|
| 179 |
+
for frame in range(start_frame + BUFFER_DELAY, self.current_frame + BUFFER_DELAY):
|
| 180 |
+
self.status[frame][player_id].append((buff_id, buff_level, buffer_stack))
|
| 181 |
+
self.status_buffer[player_id].pop((buff_id, buff_level))
|
| 182 |
+
|
| 183 |
+
# self.status_buffer[player_id][(buff_id, buff_level, buffer_stack)] = self.status_buffer[player_id].get(
|
| 184 |
+
# (buff_id, buff_level, buffer_stack), []) + [(start_frame, self.current_frame)]
|
| 185 |
+
if buff_stack:
|
| 186 |
+
self.status_buffer[player_id][(buff_id, buff_level)] = (buff_stack, self.current_frame)
|
| 187 |
+
|
| 188 |
+
def clear_status_buffer(self):
|
| 189 |
+
self.end_frame = self.current_frame
|
| 190 |
+
for player_id, status in self.status_buffer.items():
|
| 191 |
+
for (buff_id, buff_level), (buff_stack, start_frame) in status.items():
|
| 192 |
+
for frame in range(start_frame + BUFFER_DELAY, self.end_frame + BUFFER_DELAY):
|
| 193 |
+
self.status[frame][player_id].append((buff_id, buff_level, buff_stack))
|
| 194 |
|
| 195 |
def parse_skill(self, row):
|
| 196 |
detail = row.strip("{}").split(",")
|
|
|
|
| 206 |
if react or skill_id not in self.school[player_id].skills:
|
| 207 |
return
|
| 208 |
skill_stack = self.stacks[player_id][skill_id]
|
| 209 |
+
|
| 210 |
+
self.skills[self.current_frame][player_id].append((skill_id, skill_level, skill_stack, critical))
|
| 211 |
+
|
| 212 |
+
if not self.start_frame:
|
| 213 |
+
self.start_frame = self.current_frame
|
| 214 |
+
|
| 215 |
+
def available_status(self, skill_id):
|
| 216 |
+
current_status = []
|
| 217 |
+
for buff_id, buff_level, buff_stack in self.current_status:
|
| 218 |
+
buff = self.school[self.current_player].buffs[buff_id]
|
| 219 |
+
if buff.gain_attributes:
|
| 220 |
+
current_status.append((buff_id, buff_level, buff_stack))
|
| 221 |
+
elif buff.gain_skills and skill_id in buff.gain_skills:
|
| 222 |
+
current_status.append((buff_id, buff_level, buff_stack))
|
| 223 |
+
|
| 224 |
+
snapshot_status = []
|
| 225 |
+
for buff_id, buff_level, buff_stack in self.current_snapshot.get(skill_id, []):
|
| 226 |
+
buff = self.school[self.current_player].buffs[buff_id]
|
| 227 |
+
if buff.gain_attributes:
|
| 228 |
+
snapshot_status.append((buff_id, buff_level, buff_stack))
|
| 229 |
+
elif buff.gain_skills and skill_id in buff.gain_skills:
|
| 230 |
+
snapshot_status.append((buff_id, buff_level, buff_stack))
|
| 231 |
+
|
| 232 |
+
return tuple(current_status), tuple(snapshot_status)
|
| 233 |
+
|
| 234 |
+
def record(self, skill_id, skill_level, skill_stack, critical):
|
| 235 |
+
skill = self.school[self.current_player].skills[skill_id]
|
| 236 |
+
skill.record(skill_level, skill_stack, critical, self)
|
| 237 |
|
| 238 |
def __call__(self, file_name):
|
| 239 |
self.reset()
|
|
|
|
| 243 |
if row[4] == "4":
|
| 244 |
self.parse_info(row[-1])
|
| 245 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
for line in lines:
|
| 247 |
row = line.split("\t")
|
| 248 |
+
self.current_frame = int(row[1])
|
|
|
|
|
|
|
| 249 |
|
| 250 |
match row[4]:
|
|
|
|
|
|
|
| 251 |
case "8":
|
| 252 |
self.parse_pet(row[-1])
|
| 253 |
case "13":
|
|
|
|
| 255 |
case "21":
|
| 256 |
self.parse_skill(row[-1])
|
| 257 |
|
| 258 |
+
self.clear_status_buffer()
|
| 259 |
+
|
| 260 |
+
for player_id, school in self.school.items():
|
| 261 |
+
for talent_id in self.select_talents[player_id]:
|
| 262 |
+
school.talent_gains[talent_id].add_skills(school.skills)
|
| 263 |
+
|
| 264 |
+
for frame, players in self.skills.items():
|
| 265 |
+
self.current_frame = frame
|
| 266 |
+
for player_id, skills in players.items():
|
| 267 |
+
self.current_player = player_id
|
| 268 |
+
for skill_id, skill_level, skill_stack, critical in skills:
|
| 269 |
+
skill = self.school[player_id].skills[skill_id]
|
| 270 |
+
skill.record(skill_level, skill_stack, critical, self)
|
| 271 |
+
|
| 272 |
for player_id, school in self.school.items():
|
| 273 |
for talent_id in self.select_talents[player_id]:
|
| 274 |
school.talent_gains[talent_id].sub_skills(school.skills)
|
| 275 |
|
| 276 |
+
|
| 277 |
+
if __name__ == '__main__':
|
| 278 |
+
parser = Parser()
|
| 279 |
+
parser("../new.jcl")
|
| 280 |
+
print(1)
|
|
|
|
|
|