Spaces:
Runtime error
Runtime error
ango
commited on
Commit
·
3393f9d
1
Parent(s):
0f5c288
04.20 commit
Browse files- base/buff.py +1 -1
- base/skill.py +1 -1
- qt/app.py +4 -2
- qt/components/dashboard.py +2 -2
- qt/components/top.py +9 -3
- qt/scripts/bonuses.py +2 -2
- qt/scripts/config.py +18 -12
- qt/scripts/dashboard.py +9 -5
- qt/scripts/top.py +22 -9
- utils/parser.py +113 -86
base/buff.py
CHANGED
@@ -29,7 +29,7 @@ class Buff:
|
|
29 |
|
30 |
@property
|
31 |
def display_name(self):
|
32 |
-
return f"{self.buff_name}
|
33 |
|
34 |
def level_value(self, value):
|
35 |
if isinstance(value, list):
|
|
|
29 |
|
30 |
@property
|
31 |
def display_name(self):
|
32 |
+
return f"{self.buff_name}#{self.buff_id}-{self.buff_level}-{self.buff_stack}"
|
33 |
|
34 |
def level_value(self, value):
|
35 |
if isinstance(value, list):
|
base/skill.py
CHANGED
@@ -40,7 +40,7 @@ class Skill:
|
|
40 |
|
41 |
@property
|
42 |
def display_name(self):
|
43 |
-
return f"{self.skill_name}
|
44 |
|
45 |
@property
|
46 |
def damage_base(self):
|
|
|
40 |
|
41 |
@property
|
42 |
def display_name(self):
|
43 |
+
return f"{self.skill_name}#{self.skill_id}-{self.skill_level}-{self.skill_stack}"
|
44 |
|
45 |
@property
|
46 |
def damage_base(self):
|
qt/app.py
CHANGED
@@ -73,7 +73,8 @@ class MainWindow(QMainWindow):
|
|
73 |
self.equipments_widget, self.consumable_widget, self.bonus_widget
|
74 |
)
|
75 |
config_script(
|
76 |
-
parser, self.config_widget,
|
|
|
77 |
self.equipments_widget, self.consumable_widget, self.bonus_widget
|
78 |
)
|
79 |
talents = talents_script(self.talents_widget)
|
@@ -81,7 +82,8 @@ class MainWindow(QMainWindow):
|
|
81 |
equipments = equipments_script(self.equipments_widget)
|
82 |
consumables = consumables_script(self.consumable_widget)
|
83 |
bonuses = bonuses_script(parser, self.bonus_widget)
|
84 |
-
dashboard_script(parser, self.
|
|
|
85 |
|
86 |
|
87 |
if __name__ == "__main__":
|
|
|
73 |
self.equipments_widget, self.consumable_widget, self.bonus_widget
|
74 |
)
|
75 |
config_script(
|
76 |
+
parser, self.config_widget,
|
77 |
+
self.talents_widget, self.recipes_widget,
|
78 |
self.equipments_widget, self.consumable_widget, self.bonus_widget
|
79 |
)
|
80 |
talents = talents_script(self.talents_widget)
|
|
|
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.top_widget, self.dashboard_widget,
|
86 |
+
talents, recipes, equipments, consumables, bonuses)
|
87 |
|
88 |
|
89 |
if __name__ == "__main__":
|
qt/components/dashboard.py
CHANGED
@@ -9,9 +9,9 @@ class DetailWidget(QWidget):
|
|
9 |
super().__init__()
|
10 |
layout = QVBoxLayout(self)
|
11 |
self.details = {}
|
12 |
-
self.skill_combo = ComboWithLabel("选择技能", info="
|
13 |
layout.addWidget(self.skill_combo)
|
14 |
-
self.status_combo = ComboWithLabel("选择增益", info="
|
15 |
layout.addWidget(self.status_combo)
|
16 |
detail_table = QWidget()
|
17 |
detail_table_layout = QHBoxLayout(detail_table)
|
|
|
9 |
super().__init__()
|
10 |
layout = QVBoxLayout(self)
|
11 |
self.details = {}
|
12 |
+
self.skill_combo = ComboWithLabel("选择技能", info="技能名字#技能ID-技能等级-技能层数")
|
13 |
layout.addWidget(self.skill_combo)
|
14 |
+
self.status_combo = ComboWithLabel("选择增益", info="增益名字#增益ID-增益等级-增益层数")
|
15 |
layout.addWidget(self.status_combo)
|
16 |
detail_table = QWidget()
|
17 |
detail_table_layout = QHBoxLayout(detail_table)
|
qt/components/top.py
CHANGED
@@ -1,12 +1,18 @@
|
|
1 |
-
from PySide6.QtWidgets import QWidget,
|
|
|
|
|
|
|
2 |
|
3 |
|
4 |
class TopWidget(QWidget):
|
5 |
def __init__(self):
|
6 |
super().__init__()
|
7 |
-
layout =
|
8 |
self.setLayout(layout)
|
9 |
|
10 |
self.upload_button = QPushButton("请上传JCL")
|
11 |
-
|
12 |
layout.addWidget(self.upload_button)
|
|
|
|
|
|
|
|
|
|
1 |
+
from PySide6.QtWidgets import QWidget, QHBoxLayout, QPushButton
|
2 |
+
from PySide6.QtCore import Qt
|
3 |
+
|
4 |
+
from qt.components import ComboWithLabel
|
5 |
|
6 |
|
7 |
class TopWidget(QWidget):
|
8 |
def __init__(self):
|
9 |
super().__init__()
|
10 |
+
layout = QHBoxLayout()
|
11 |
self.setLayout(layout)
|
12 |
|
13 |
self.upload_button = QPushButton("请上传JCL")
|
|
|
14 |
layout.addWidget(self.upload_button)
|
15 |
+
self.player_select = ComboWithLabel("请选择角色")
|
16 |
+
layout.addWidget(self.player_select)
|
17 |
+
self.player_select.hide()
|
18 |
+
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
qt/scripts/bonuses.py
CHANGED
@@ -20,13 +20,13 @@ class Bonuses(dict):
|
|
20 |
def bonuses_script(parser: Parser, bonuses_widget: BonusesWidget):
|
21 |
bonuses = Bonuses()
|
22 |
|
23 |
-
def formation_update(
|
24 |
widget = bonuses_widget.formation
|
25 |
formation = widget.formation.combo_box.currentText()
|
26 |
core_rate = widget.core_rate.spin_box.value()
|
27 |
formation_rate = widget.rate.spin_box.value()
|
28 |
|
29 |
-
if formation == parser.school.formation:
|
30 |
widget.core_rate.show()
|
31 |
else:
|
32 |
core_rate = 0
|
|
|
20 |
def bonuses_script(parser: Parser, bonuses_widget: BonusesWidget):
|
21 |
bonuses = Bonuses()
|
22 |
|
23 |
+
def formation_update(_):
|
24 |
widget = bonuses_widget.formation
|
25 |
formation = widget.formation.combo_box.currentText()
|
26 |
core_rate = widget.core_rate.spin_box.value()
|
27 |
formation_rate = widget.rate.spin_box.value()
|
28 |
|
29 |
+
if formation == parser.school[parser.current_player].formation:
|
30 |
widget.core_rate.show()
|
31 |
else:
|
32 |
core_rate = 0
|
qt/scripts/config.py
CHANGED
@@ -8,6 +8,7 @@ from qt.components.equipments import EquipmentsWidget
|
|
8 |
from qt.components.consumables import ConsumablesWidget
|
9 |
from qt.components.recipes import RecipesWidget
|
10 |
from qt.components.talents import TalentsWidget
|
|
|
11 |
from utils.parser import Parser
|
12 |
|
13 |
if not os.path.exists("config"):
|
@@ -78,7 +79,8 @@ def config_script(
|
|
78 |
|
79 |
def load_config():
|
80 |
config_name = config_widget.config_select.combo_box.currentText()
|
81 |
-
|
|
|
82 |
if not config:
|
83 |
return
|
84 |
category = config_widget.config_category.combo_box.currentText()
|
@@ -149,10 +151,12 @@ def config_script(
|
|
149 |
|
150 |
def save_config():
|
151 |
config_name = config_widget.config_name.text_browser.text()
|
152 |
-
|
153 |
-
|
154 |
-
if
|
155 |
-
CONFIG[
|
|
|
|
|
156 |
"equipments": {},
|
157 |
"consumables": {},
|
158 |
"bonuses": {"formation": {}, "team_gains": {}},
|
@@ -160,28 +164,30 @@ def config_script(
|
|
160 |
"recipes": []
|
161 |
}
|
162 |
|
163 |
-
save_equipments(CONFIG[
|
164 |
-
save_consumables(CONFIG[
|
165 |
-
save_bonuses(CONFIG[
|
166 |
# save_recipes(CONFIG[parser.school.school][config_name]['recipes'])
|
167 |
json.dump(CONFIG, open("config", "w", encoding="utf-8"), ensure_ascii=False)
|
168 |
|
169 |
config_widget.config_select.set_items(
|
170 |
-
list(CONFIG.get(
|
171 |
)
|
172 |
|
173 |
config_widget.save_config.clicked.connect(save_config)
|
174 |
|
175 |
def delete_config():
|
176 |
config_name = config_widget.config_name.text_browser.text()
|
177 |
-
|
|
|
|
|
178 |
return
|
179 |
|
180 |
-
CONFIG[
|
181 |
|
182 |
json.dump(CONFIG, open("config", "w", encoding="utf-8"), ensure_ascii=False)
|
183 |
|
184 |
-
config_widget.config_select.set_items(list(CONFIG.get(
|
185 |
config_widget.config_name.text_browser.clear()
|
186 |
|
187 |
config_widget.delete_config.clicked.connect(delete_config)
|
|
|
8 |
from qt.components.consumables import ConsumablesWidget
|
9 |
from qt.components.recipes import RecipesWidget
|
10 |
from qt.components.talents import TalentsWidget
|
11 |
+
from qt.components.top import TopWidget
|
12 |
from utils.parser import Parser
|
13 |
|
14 |
if not os.path.exists("config"):
|
|
|
79 |
|
80 |
def load_config():
|
81 |
config_name = config_widget.config_select.combo_box.currentText()
|
82 |
+
player_id = parser.current_player
|
83 |
+
config = CONFIG.get(parser.school[player_id].school, {}).get(config_name, {})
|
84 |
if not config:
|
85 |
return
|
86 |
category = config_widget.config_category.combo_box.currentText()
|
|
|
151 |
|
152 |
def save_config():
|
153 |
config_name = config_widget.config_name.text_browser.text()
|
154 |
+
player_id = parser.current_player
|
155 |
+
school = parser.school[player_id].school
|
156 |
+
if school not in CONFIG:
|
157 |
+
CONFIG[school] = {}
|
158 |
+
if config_name not in CONFIG[school]:
|
159 |
+
CONFIG[school][config_name] = {
|
160 |
"equipments": {},
|
161 |
"consumables": {},
|
162 |
"bonuses": {"formation": {}, "team_gains": {}},
|
|
|
164 |
"recipes": []
|
165 |
}
|
166 |
|
167 |
+
save_equipments(CONFIG[school][config_name]['equipments'])
|
168 |
+
save_consumables(CONFIG[school][config_name]['consumables'])
|
169 |
+
save_bonuses(CONFIG[school][config_name]['bonuses'])
|
170 |
# save_recipes(CONFIG[parser.school.school][config_name]['recipes'])
|
171 |
json.dump(CONFIG, open("config", "w", encoding="utf-8"), ensure_ascii=False)
|
172 |
|
173 |
config_widget.config_select.set_items(
|
174 |
+
list(CONFIG.get(school, {})), keep_index=True, default_index=-1
|
175 |
)
|
176 |
|
177 |
config_widget.save_config.clicked.connect(save_config)
|
178 |
|
179 |
def delete_config():
|
180 |
config_name = config_widget.config_name.text_browser.text()
|
181 |
+
player_id = parser.current_player
|
182 |
+
school = parser.school[player_id].school
|
183 |
+
if config_name not in CONFIG.get(school, {}):
|
184 |
return
|
185 |
|
186 |
+
CONFIG[school].pop(config_name)
|
187 |
|
188 |
json.dump(CONFIG, open("config", "w", encoding="utf-8"), ensure_ascii=False)
|
189 |
|
190 |
+
config_widget.config_select.set_items(list(CONFIG.get(school, {})), default_index=-1)
|
191 |
config_widget.config_name.text_browser.clear()
|
192 |
|
193 |
config_widget.delete_config.clicked.connect(delete_config)
|
qt/scripts/dashboard.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
from typing import Dict
|
2 |
|
3 |
from qt.components.dashboard import DashboardWidget
|
|
|
4 |
from qt.constant import ATTR_TYPE_TRANSLATE
|
5 |
from qt.scripts.bonuses import Bonuses
|
6 |
from qt.scripts.consumables import Consumables
|
@@ -45,21 +46,24 @@ def detail_content(detail: Detail):
|
|
45 |
return damage_content, gradient_content
|
46 |
|
47 |
|
48 |
-
def dashboard_script(parser: Parser,
|
49 |
dashboard_widget: DashboardWidget, talents: Talents, recipes: Recipes,
|
50 |
equipments: Equipments, consumables: Consumables, bonuses: Bonuses):
|
51 |
|
52 |
def select_fight(text):
|
53 |
-
|
54 |
-
|
|
|
55 |
|
56 |
dashboard_widget.fight_select.combo_box.currentTextChanged.connect(select_fight)
|
57 |
|
58 |
def formulate():
|
59 |
duration = dashboard_widget.duration.spin_box.value()
|
60 |
-
|
|
|
|
|
61 |
|
62 |
-
school = parser.school
|
63 |
attribute = school.attribute()
|
64 |
attribute.target_level = int(dashboard_widget.target_level.combo_box.currentText())
|
65 |
for attr, value in equipments.attrs.items():
|
|
|
1 |
from typing import Dict
|
2 |
|
3 |
from qt.components.dashboard import DashboardWidget
|
4 |
+
from qt.components.top import TopWidget
|
5 |
from qt.constant import ATTR_TYPE_TRANSLATE
|
6 |
from qt.scripts.bonuses import Bonuses
|
7 |
from qt.scripts.consumables import Consumables
|
|
|
46 |
return damage_content, gradient_content
|
47 |
|
48 |
|
49 |
+
def dashboard_script(parser: Parser, top_widget: TopWidget,
|
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 |
+
player_id = parser.current_player
|
63 |
+
record_index = dashboard_widget.fight_select.combo_box.currentText()
|
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():
|
qt/scripts/top.py
CHANGED
@@ -27,22 +27,35 @@ def top_script(
|
|
27 |
if not file_name[0]:
|
28 |
return
|
29 |
parser(file_name[0])
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
""" Update config """
|
32 |
config_choices = list(CONFIG.get(school.school, {}))
|
33 |
config_widget.config_select.set_items(config_choices, default_index=-1)
|
34 |
""" Update dashboard """
|
35 |
-
record_index = list(parser.record_index)
|
36 |
-
dashboard_widget.fight_select.set_items(record_index)
|
37 |
-
dashboard_widget.duration.set_value(parser.duration(parser.record_index[record_index[0]]))
|
38 |
|
39 |
""" Update talent options """
|
40 |
for i, talent_widget in enumerate(talents_widget.values()):
|
41 |
talents = school.talents[i]
|
42 |
-
default_index = talents.index(parser.select_talents[i]) + 1
|
43 |
talent_widget.set_items(
|
44 |
-
[""] + [school.talent_decoder[talent] for talent in talents],
|
45 |
-
keep_index=True, default_index=default_index
|
46 |
)
|
47 |
|
48 |
""" Update recipe options """
|
@@ -71,7 +84,7 @@ def top_script(
|
|
71 |
if not (current_index := equipment_widget.stone_level.combo_box.currentIndex()):
|
72 |
current_index = MAX_STONE_LEVEL
|
73 |
equipment_widget.stone_level.combo_box.setCurrentIndex(current_index)
|
74 |
-
if select_equipment := parser.select_equipments.get(label, {}):
|
75 |
if equipment := equipment_widget.equipment_mapping.get(select_equipment['equipment']):
|
76 |
if equipment in equipment_widget.equipment.items:
|
77 |
equipment_widget.equipment.combo_box.setCurrentText(equipment)
|
@@ -97,6 +110,6 @@ def top_script(
|
|
97 |
config_widget.show()
|
98 |
bottom_widget.show()
|
99 |
|
100 |
-
top_widget.
|
101 |
|
102 |
return parser
|
|
|
27 |
if not file_name[0]:
|
28 |
return
|
29 |
parser(file_name[0])
|
30 |
+
top_widget.player_select.set_items(
|
31 |
+
[parser.id2name[player_id] for player_id in parser.school], keep_index=True, default_index=0
|
32 |
+
)
|
33 |
+
top_widget.player_select.show()
|
34 |
+
select_player(None)
|
35 |
+
|
36 |
+
top_widget.upload_button.clicked.connect(upload_logs)
|
37 |
+
|
38 |
+
def select_player(_):
|
39 |
+
player_name = top_widget.player_select.combo_box.currentText()
|
40 |
+
if not player_name:
|
41 |
+
return
|
42 |
+
player_id = parser.name2id[player_name]
|
43 |
+
parser.current_player = player_id
|
44 |
+
school = parser.school[player_id]
|
45 |
""" Update config """
|
46 |
config_choices = list(CONFIG.get(school.school, {}))
|
47 |
config_widget.config_select.set_items(config_choices, default_index=-1)
|
48 |
""" Update dashboard """
|
49 |
+
record_index = list(parser.record_index[player_id])
|
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()):
|
55 |
talents = school.talents[i]
|
56 |
+
default_index = talents.index(parser.select_talents[player_id][i]) + 1
|
57 |
talent_widget.set_items(
|
58 |
+
[""] + [school.talent_decoder[talent] for talent in talents], default_index=default_index
|
|
|
59 |
)
|
60 |
|
61 |
""" Update recipe options """
|
|
|
84 |
if not (current_index := equipment_widget.stone_level.combo_box.currentIndex()):
|
85 |
current_index = MAX_STONE_LEVEL
|
86 |
equipment_widget.stone_level.combo_box.setCurrentIndex(current_index)
|
87 |
+
if select_equipment := parser.select_equipments[player_id].get(label, {}):
|
88 |
if equipment := equipment_widget.equipment_mapping.get(select_equipment['equipment']):
|
89 |
if equipment in equipment_widget.equipment.items:
|
90 |
equipment_widget.equipment.combo_box.setCurrentText(equipment)
|
|
|
110 |
config_widget.show()
|
111 |
bottom_widget.show()
|
112 |
|
113 |
+
top_widget.player_select.combo_box.currentTextChanged.connect(select_player)
|
114 |
|
115 |
return parser
|
utils/parser.py
CHANGED
@@ -9,6 +9,14 @@ from base.skill import Skill
|
|
9 |
from schools import bei_ao_jue
|
10 |
from utils.lua import parse
|
11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
|
13 |
@dataclass
|
14 |
class School:
|
@@ -75,7 +83,6 @@ SUPPORT_SCHOOL = {
|
|
75 |
)
|
76 |
}
|
77 |
|
78 |
-
|
79 |
LABEL_MAPPING = {
|
80 |
2: "远程武器",
|
81 |
3: "上衣",
|
@@ -94,42 +101,40 @@ EMBED_MAPPING = {(5, 24449 - i): 8 - i for i in range(8)}
|
|
94 |
|
95 |
|
96 |
class Parser:
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
record_index: Dict[str, int]
|
106 |
-
|
107 |
-
fight_flag: bool
|
108 |
|
109 |
-
|
110 |
-
|
|
|
|
|
111 |
|
112 |
-
|
|
|
113 |
|
114 |
-
|
115 |
-
return round((self.end_time[i] - self.start_time[i]) / 1000, 3)
|
116 |
|
117 |
-
|
118 |
-
|
119 |
-
return self.records[len(self.start_time) - 1]
|
120 |
|
121 |
-
def available_status(self, skill_id):
|
122 |
current_status = []
|
123 |
-
for (buff_id, buff_level), buff_stack in self.status.items():
|
124 |
-
buff = self.school.buffs[buff_id]
|
125 |
if buff.gain_attributes:
|
126 |
current_status.append((buff_id, buff_level, buff_stack))
|
127 |
elif buff.gain_skills and skill_id in buff.gain_skills:
|
128 |
current_status.append((buff_id, buff_level, buff_stack))
|
129 |
|
130 |
snapshot_status = []
|
131 |
-
for (buff_id, buff_level), buff_stack in self.snapshot.get(skill_id, {}).items():
|
132 |
-
buff = self.school.buffs[buff_id]
|
133 |
if buff.gain_attributes:
|
134 |
snapshot_status.append((buff_id, buff_level, buff_stack))
|
135 |
elif buff.gain_skills and skill_id in buff.gain_skills:
|
@@ -138,103 +143,125 @@ class Parser:
|
|
138 |
return tuple(current_status), tuple(snapshot_status)
|
139 |
|
140 |
def reset(self):
|
141 |
-
self.
|
142 |
-
|
143 |
-
self.records =
|
144 |
-
self.status =
|
145 |
-
self.snapshot =
|
146 |
-
self.stacks = defaultdict(
|
147 |
-
self.ticks = defaultdict(int)
|
148 |
-
|
149 |
-
self.
|
150 |
-
self.
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
self.school = None
|
155 |
-
|
156 |
-
def parse_equipments(self, detail):
|
157 |
self.select_equipments = {}
|
|
|
|
|
|
|
|
|
|
|
158 |
for row in detail:
|
159 |
if not (label := LABEL_MAPPING.get(row[0])):
|
160 |
continue
|
161 |
-
select_equipment =
|
162 |
select_equipment['equipment'] = row[2]
|
163 |
select_equipment['strength_level'] = row[3]
|
164 |
select_equipment['embed_levels'] = [EMBED_MAPPING.get(tuple(e), 0) for e in row[4]]
|
165 |
select_equipment['enchant'] = row[5]
|
|
|
166 |
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
self.
|
179 |
-
self.
|
180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
181 |
else:
|
182 |
-
self.end_time.append(int(timestamp))
|
183 |
-
self.fight_flag = False
|
184 |
|
185 |
def parse_buff(self, row):
|
186 |
-
detail = row.split(",")
|
|
|
|
|
|
|
187 |
buff_id, buff_stack, buff_level = int(detail[4]), int(detail[5]), int(detail[8])
|
188 |
-
if buff_id not in self.school.buffs:
|
189 |
return
|
190 |
if not buff_stack:
|
191 |
-
self.status.pop((buff_id, buff_level))
|
192 |
else:
|
193 |
-
self.status[(buff_id, buff_level)] = buff_stack
|
194 |
|
195 |
def parse_skill(self, row, timestamp):
|
196 |
-
detail = row.split(",")
|
|
|
|
|
|
|
197 |
skill_id, skill_level, critical = int(detail[4]), int(detail[5]), detail[6] == "true"
|
198 |
-
if skill_id not in self.school.skills:
|
199 |
return
|
200 |
-
timestamp = int(timestamp) - self.start_time[-1]
|
201 |
-
skill_stack =
|
202 |
-
if self.ticks[skill_id]:
|
203 |
-
self.ticks[skill_id] -= 1
|
204 |
-
if not self.ticks[skill_id]:
|
205 |
-
self.stacks[skill_id
|
206 |
|
207 |
skill_tuple = (skill_id, skill_level, skill_stack)
|
208 |
-
skill = self.school.skills[skill_id]
|
209 |
if bind_skill := skill.bind_skill:
|
210 |
-
self.stacks[bind_skill] = min(self.stacks[bind_skill] + 1, skill.max_stack)
|
211 |
-
self.ticks[bind_skill] = skill.tick if not self.ticks[bind_skill] else skill.tick - 1
|
212 |
-
self.snapshot[bind_skill] = self.status.copy()
|
213 |
else:
|
214 |
-
|
215 |
-
|
216 |
-
status_tuple
|
217 |
-
if status_tuple not in self.current_record[skill_tuple]:
|
218 |
-
self.current_record[skill_tuple][status_tuple] = []
|
219 |
-
self.current_record[skill_tuple][status_tuple].append((timestamp, critical))
|
220 |
|
221 |
def __call__(self, file_name):
|
222 |
self.reset()
|
223 |
lines = open(file_name).readlines()
|
224 |
for line in lines:
|
225 |
row = line.split("\t")
|
226 |
-
if row[4] == "4"
|
227 |
-
|
228 |
-
|
229 |
for line in lines:
|
230 |
row = line.split("\t")
|
231 |
if row[4] == "5":
|
232 |
-
self.parse_time(
|
233 |
elif row[4] == "13":
|
234 |
self.parse_buff(row[-1])
|
235 |
-
elif row[4] == "21"
|
236 |
self.parse_skill(row[-1], row[3])
|
237 |
|
238 |
self.record_index = {
|
239 |
-
|
|
|
|
|
|
|
|
|
240 |
}
|
|
|
9 |
from schools import bei_ao_jue
|
10 |
from utils.lua import parse
|
11 |
|
12 |
+
SKILL_TYPE = Tuple[int, int, int]
|
13 |
+
BUFF_TYPE = Tuple[int, int, int]
|
14 |
+
TIMELINE_TYPE = List[Tuple[int, bool]]
|
15 |
+
SUB_RECORD_TYPE = Dict[Tuple[tuple, tuple], TIMELINE_TYPE]
|
16 |
+
RECORD_TYPE = Dict[SKILL_TYPE, SUB_RECORD_TYPE]
|
17 |
+
STATUS_TYPE = Dict[Tuple[int, int], int]
|
18 |
+
SNAPSHOT_TYPE = Dict[int, STATUS_TYPE]
|
19 |
+
|
20 |
|
21 |
@dataclass
|
22 |
class School:
|
|
|
83 |
)
|
84 |
}
|
85 |
|
|
|
86 |
LABEL_MAPPING = {
|
87 |
2: "远程武器",
|
88 |
3: "上衣",
|
|
|
101 |
|
102 |
|
103 |
class Parser:
|
104 |
+
current_player: int
|
105 |
+
id2name: Dict[int, str]
|
106 |
+
name2id: Dict[str, int]
|
107 |
+
records: Dict[int, List[RECORD_TYPE]]
|
108 |
+
status: Dict[int, STATUS_TYPE]
|
109 |
+
snapshot: Dict[int, SNAPSHOT_TYPE]
|
110 |
+
stacks: Dict[int, Dict[int, int]]
|
111 |
+
ticks: Dict[int, Dict[int, int]]
|
|
|
|
|
|
|
112 |
|
113 |
+
fight_flag: Dict[int, bool]
|
114 |
+
start_time: Dict[int, List[int]]
|
115 |
+
end_time: Dict[int, List[int]]
|
116 |
+
record_index: Dict[int, Dict[str, int]]
|
117 |
|
118 |
+
select_talents: Dict[int, List[int]]
|
119 |
+
select_equipments: Dict[int, Dict[int, Dict[str, int | list]]]
|
120 |
|
121 |
+
school: Dict[int, School]
|
|
|
122 |
|
123 |
+
def duration(self, player_id, i):
|
124 |
+
return round((self.end_time[player_id][i] - self.start_time[player_id][i]) / 1000, 3)
|
|
|
125 |
|
126 |
+
def available_status(self, player_id, skill_id):
|
127 |
current_status = []
|
128 |
+
for (buff_id, buff_level), buff_stack in self.status[player_id].items():
|
129 |
+
buff = self.school[player_id].buffs[buff_id]
|
130 |
if buff.gain_attributes:
|
131 |
current_status.append((buff_id, buff_level, buff_stack))
|
132 |
elif buff.gain_skills and skill_id in buff.gain_skills:
|
133 |
current_status.append((buff_id, buff_level, buff_stack))
|
134 |
|
135 |
snapshot_status = []
|
136 |
+
for (buff_id, buff_level), buff_stack in self.snapshot[player_id].get(skill_id, {}).items():
|
137 |
+
buff = self.school[player_id].buffs[buff_id]
|
138 |
if buff.gain_attributes:
|
139 |
snapshot_status.append((buff_id, buff_level, buff_stack))
|
140 |
elif buff.gain_skills and skill_id in buff.gain_skills:
|
|
|
143 |
return tuple(current_status), tuple(snapshot_status)
|
144 |
|
145 |
def reset(self):
|
146 |
+
self.id2name = {}
|
147 |
+
self.name2id = {}
|
148 |
+
self.records = defaultdict(list)
|
149 |
+
self.status = defaultdict(dict)
|
150 |
+
self.snapshot = defaultdict(dict)
|
151 |
+
self.stacks = defaultdict(lambda: defaultdict(lambda: 1))
|
152 |
+
self.ticks = defaultdict(lambda: defaultdict(int))
|
153 |
+
|
154 |
+
self.fight_flag = defaultdict(bool)
|
155 |
+
self.start_time = defaultdict(list)
|
156 |
+
self.end_time = defaultdict(list)
|
157 |
+
|
158 |
+
self.select_talents = {}
|
|
|
|
|
|
|
159 |
self.select_equipments = {}
|
160 |
+
self.school = {}
|
161 |
+
|
162 |
+
@staticmethod
|
163 |
+
def parse_equipments(detail):
|
164 |
+
select_equipments = {}
|
165 |
for row in detail:
|
166 |
if not (label := LABEL_MAPPING.get(row[0])):
|
167 |
continue
|
168 |
+
select_equipment = select_equipments[label] = {}
|
169 |
select_equipment['equipment'] = row[2]
|
170 |
select_equipment['strength_level'] = row[3]
|
171 |
select_equipment['embed_levels'] = [EMBED_MAPPING.get(tuple(e), 0) for e in row[4]]
|
172 |
select_equipment['enchant'] = row[5]
|
173 |
+
return select_equipments
|
174 |
|
175 |
+
@staticmethod
|
176 |
+
def parse_talents(detail):
|
177 |
+
return [row[1] for row in detail]
|
178 |
+
|
179 |
+
def parse_info(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 |
+
if isinstance(detail := parse(row), list):
|
185 |
+
player_name = detail[1]
|
186 |
+
self.id2name[player_id] = player_name
|
187 |
+
self.name2id[player_name] = player_id
|
188 |
+
if school := SUPPORT_SCHOOL.get(detail[3]):
|
189 |
+
self.school[player_id] = school
|
190 |
+
self.select_equipments[player_id] = self.parse_equipments(detail[5])
|
191 |
+
self.select_talents[player_id] = self.parse_talents(detail[6])
|
192 |
+
|
193 |
+
def parse_time(self, row, timestamp):
|
194 |
+
detail = row.strip("{}").split(",")
|
195 |
+
player_id = int(detail[0])
|
196 |
+
if player_id not in self.school:
|
197 |
+
return
|
198 |
+
if detail[1] == "true":
|
199 |
+
self.start_time[player_id].append(int(timestamp))
|
200 |
+
self.records[player_id].append(defaultdict(lambda: defaultdict(list)))
|
201 |
+
self.fight_flag[player_id] = True
|
202 |
else:
|
203 |
+
self.end_time[player_id].append(int(timestamp))
|
204 |
+
self.fight_flag[player_id] = False
|
205 |
|
206 |
def parse_buff(self, row):
|
207 |
+
detail = row.strip("{}").split(",")
|
208 |
+
player_id = int(detail[0])
|
209 |
+
if player_id not in self.school:
|
210 |
+
return
|
211 |
buff_id, buff_stack, buff_level = int(detail[4]), int(detail[5]), int(detail[8])
|
212 |
+
if buff_id not in self.school[player_id].buffs:
|
213 |
return
|
214 |
if not buff_stack:
|
215 |
+
self.status[player_id].pop((buff_id, buff_level), None)
|
216 |
else:
|
217 |
+
self.status[player_id][(buff_id, buff_level)] = buff_stack
|
218 |
|
219 |
def parse_skill(self, row, timestamp):
|
220 |
+
detail = row.strip("{}").split(",")
|
221 |
+
player_id = int(detail[0])
|
222 |
+
if not self.fight_flag[player_id] or player_id not in self.school:
|
223 |
+
return
|
224 |
skill_id, skill_level, critical = int(detail[4]), int(detail[5]), detail[6] == "true"
|
225 |
+
if skill_id not in self.school[player_id].skills:
|
226 |
return
|
227 |
+
timestamp = int(timestamp) - self.start_time[player_id][-1]
|
228 |
+
skill_stack = self.stacks[player_id][skill_id]
|
229 |
+
if self.ticks[player_id][skill_id]:
|
230 |
+
self.ticks[player_id][skill_id] -= 1
|
231 |
+
if not self.ticks[player_id][skill_id]:
|
232 |
+
self.stacks[player_id].pop(skill_id)
|
233 |
|
234 |
skill_tuple = (skill_id, skill_level, skill_stack)
|
235 |
+
skill = self.school[player_id].skills[skill_id]
|
236 |
if bind_skill := skill.bind_skill:
|
237 |
+
self.stacks[player_id][bind_skill] = min(self.stacks[player_id][bind_skill] + 1, skill.max_stack)
|
238 |
+
self.ticks[player_id][bind_skill] = skill.tick if not self.ticks[player_id][bind_skill] else skill.tick - 1
|
239 |
+
self.snapshot[player_id][bind_skill] = self.status[player_id].copy()
|
240 |
else:
|
241 |
+
current_record = self.records[player_id][len(self.start_time) - 1]
|
242 |
+
status_tuple = self.available_status(player_id, skill_id)
|
243 |
+
current_record[skill_tuple][status_tuple].append((timestamp, critical))
|
|
|
|
|
|
|
244 |
|
245 |
def __call__(self, file_name):
|
246 |
self.reset()
|
247 |
lines = open(file_name).readlines()
|
248 |
for line in lines:
|
249 |
row = line.split("\t")
|
250 |
+
if row[4] == "4":
|
251 |
+
self.parse_info(row[-1])
|
|
|
252 |
for line in lines:
|
253 |
row = line.split("\t")
|
254 |
if row[4] == "5":
|
255 |
+
self.parse_time(row[-1], row[3])
|
256 |
elif row[4] == "13":
|
257 |
self.parse_buff(row[-1])
|
258 |
+
elif row[4] == "21":
|
259 |
self.parse_skill(row[-1], row[3])
|
260 |
|
261 |
self.record_index = {
|
262 |
+
player_id: {
|
263 |
+
f"{i + 1}:{round((end_time - self.start_time[player_id][i]) / 1000, 3)}": i
|
264 |
+
for i, end_time in enumerate(self.end_time[player_id])
|
265 |
+
}
|
266 |
+
for player_id in self.end_time
|
267 |
}
|