ango commited on
Commit
970efde
·
1 Parent(s): 5825182

04.15 commit

Browse files
qt/app.py CHANGED
@@ -4,7 +4,9 @@ import sys
4
  from PySide6.QtGui import QIcon
5
 
6
  from qt.components.top import TopWidget
 
7
  from qt.scripts.top import top_script
 
8
  from qt.components.equipments import EquipmentsWidget
9
  from qt.scripts.equipments import equipments_script
10
  from qt.components.consumables import ConsumablesWidget
@@ -18,8 +20,8 @@ from qt.scripts.recipes import recipes_script
18
  from qt.components.dashboard import DashboardWidget
19
  from qt.scripts.dashboard import dashboard_script
20
 
21
- from PySide6.QtWidgets import QApplication, QMainWindow, QStyleFactory, QVBoxLayout, QGridLayout, QWidget, QSizePolicy, \
22
- QHBoxLayout, QTabWidget
23
 
24
 
25
  class MainWindow(QMainWindow):
@@ -38,37 +40,42 @@ class MainWindow(QMainWindow):
38
  layout = QVBoxLayout(self.central_widget)
39
 
40
  self.top_widget = TopWidget()
 
 
 
 
 
 
41
  self.bottom_widget = QWidget()
42
- bottom_layout = QHBoxLayout(self.bottom_widget)
43
- layout.addWidget(self.top_widget)
44
- layout.addWidget(self.bottom_widget)
45
 
46
- self.config_widget = QTabWidget()
 
47
  self.dashboard_widget = DashboardWidget()
48
- bottom_layout.addWidget(self.config_widget, 1)
49
  bottom_layout.addWidget(self.dashboard_widget, 1)
50
 
51
  self.equipments_widget = EquipmentsWidget()
52
- self.config_widget.addTab(self.equipments_widget, "配装")
53
  self.consumable_widget = ConsumablesWidget()
54
- self.config_widget.addTab(self.consumable_widget, "消耗品")
55
  self.talents_widget = TalentsWidget()
56
- self.config_widget.addTab(self.talents_widget, "奇穴")
57
  self.recipes_widget = RecipesWidget()
58
- self.config_widget.addTab(self.recipes_widget, "秘籍")
59
 
60
  parser = top_script(
61
- self.top_widget, self.bottom_widget, self.dashboard_widget,
62
  self.talents_widget, self.recipes_widget, self.equipments_widget, self.consumable_widget,
63
  )
 
64
  equipments = equipments_script(self.equipments_widget)
65
  consumables = consumables_script(self.consumable_widget)
66
  talents = talents_script(self.talents_widget)
67
  recipes = recipes_script(self.recipes_widget)
68
  dashboard_script(parser, self.dashboard_widget, talents, recipes, equipments, consumables)
69
 
70
- self.bottom_widget.hide()
71
-
72
 
73
  if __name__ == "__main__":
74
  app = QApplication(sys.argv)
 
4
  from PySide6.QtGui import QIcon
5
 
6
  from qt.components.top import TopWidget
7
+ from qt.scripts.config import config_script
8
  from qt.scripts.top import top_script
9
+ from qt.components.config import ConfigWidget
10
  from qt.components.equipments import EquipmentsWidget
11
  from qt.scripts.equipments import equipments_script
12
  from qt.components.consumables import ConsumablesWidget
 
20
  from qt.components.dashboard import DashboardWidget
21
  from qt.scripts.dashboard import dashboard_script
22
 
23
+ from PySide6.QtWidgets import QApplication, QMainWindow, QStyleFactory
24
+ from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QTabWidget
25
 
26
 
27
  class MainWindow(QMainWindow):
 
40
  layout = QVBoxLayout(self.central_widget)
41
 
42
  self.top_widget = TopWidget()
43
+ layout.addWidget(self.top_widget, 1)
44
+
45
+ self.config_widget = ConfigWidget()
46
+ layout.addWidget(self.config_widget, 1)
47
+ self.config_widget.hide()
48
+
49
  self.bottom_widget = QWidget()
50
+ layout.addWidget(self.bottom_widget, 8)
51
+ self.bottom_widget.hide()
 
52
 
53
+ bottom_layout = QHBoxLayout(self.bottom_widget)
54
+ self.detail_widget = QTabWidget()
55
  self.dashboard_widget = DashboardWidget()
56
+ bottom_layout.addWidget(self.detail_widget, 1)
57
  bottom_layout.addWidget(self.dashboard_widget, 1)
58
 
59
  self.equipments_widget = EquipmentsWidget()
60
+ self.detail_widget.addTab(self.equipments_widget, "配装")
61
  self.consumable_widget = ConsumablesWidget()
62
+ self.detail_widget.addTab(self.consumable_widget, "消耗品")
63
  self.talents_widget = TalentsWidget()
64
+ self.detail_widget.addTab(self.talents_widget, "奇穴")
65
  self.recipes_widget = RecipesWidget()
66
+ self.detail_widget.addTab(self.recipes_widget, "秘籍")
67
 
68
  parser = top_script(
69
+ self.top_widget, self.config_widget, self.bottom_widget, self.dashboard_widget,
70
  self.talents_widget, self.recipes_widget, self.equipments_widget, self.consumable_widget,
71
  )
72
+ config_script(parser, self.config_widget, self.equipments_widget)
73
  equipments = equipments_script(self.equipments_widget)
74
  consumables = consumables_script(self.consumable_widget)
75
  talents = talents_script(self.talents_widget)
76
  recipes = recipes_script(self.recipes_widget)
77
  dashboard_script(parser, self.dashboard_widget, talents, recipes, equipments, consumables)
78
 
 
 
79
 
80
  if __name__ == "__main__":
81
  app = QApplication(sys.argv)
qt/components/__init__.py CHANGED
@@ -1,7 +1,6 @@
1
- from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QAbstractItemView, QTableWidgetItem, \
2
- QHeaderView, QSizePolicy, QListWidgetItem, QSpacerItem, QListView
3
- from PySide6.QtWidgets import QComboBox, QRadioButton, QTextBrowser, QTextEdit, QSpinBox, QListWidget, QTableWidget
4
- from PySide6.QtCore import Qt
5
 
6
 
7
  class LabelWidget(QWidget):
@@ -143,20 +142,16 @@ class SpinWithLabel(LabelWidget):
143
 
144
 
145
  class TextWithLabel(LabelWidget):
146
- def __init__(self, label, stretch: bool = True, editable: bool = False):
147
  super().__init__(label)
148
  layout = QVBoxLayout(self)
149
 
150
- if editable:
151
- self.text_browser = QTextEdit()
152
- else:
153
- self.text_browser = QTextBrowser()
154
 
155
  layout.addWidget(self.label)
156
  layout.addWidget(self.text_browser)
157
 
158
- if stretch:
159
- layout.addStretch()
160
 
161
  def set_text(self, text):
162
  self.text_browser.setText(text)
 
1
+ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel
2
+ from PySide6.QtWidgets import QAbstractItemView, QTableWidgetItem, QHeaderView, QListView
3
+ from PySide6.QtWidgets import QComboBox, QRadioButton, QTextBrowser, QLineEdit, QSpinBox, QListWidget, QTableWidget
 
4
 
5
 
6
  class LabelWidget(QWidget):
 
142
 
143
 
144
  class TextWithLabel(LabelWidget):
145
+ def __init__(self, label):
146
  super().__init__(label)
147
  layout = QVBoxLayout(self)
148
 
149
+ self.text_browser = QLineEdit()
 
 
 
150
 
151
  layout.addWidget(self.label)
152
  layout.addWidget(self.text_browser)
153
 
154
+ layout.addStretch()
 
155
 
156
  def set_text(self, text):
157
  self.text_browser.setText(text)
qt/components/config.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton
2
+
3
+ from qt.components import ComboWithLabel, TextWithLabel
4
+
5
+
6
+ class ConfigWidget(QWidget):
7
+ def __init__(self):
8
+ super().__init__()
9
+ layout = QVBoxLayout(self)
10
+ top_layout = QHBoxLayout()
11
+ layout.addLayout(top_layout)
12
+ bottom_layout = QHBoxLayout()
13
+ layout.addLayout(bottom_layout)
14
+
15
+ self.config_select = ComboWithLabel("Select")
16
+ top_layout.addWidget(self.config_select, 1)
17
+ self.load_config = QPushButton("Load")
18
+ bottom_layout.addWidget(self.load_config, 1)
19
+ self.config_name = TextWithLabel("Name")
20
+ top_layout.addWidget(self.config_name, 1)
21
+ self.save_config = QPushButton("Save")
22
+ bottom_layout.addWidget(self.save_config, 1)
qt/components/dashboard.py CHANGED
@@ -1,6 +1,6 @@
1
  from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTabWidget
2
 
3
- from qt.components import ComboWithLabel, SpinWithLabel, TextWithLabel, LabelWithLabel, TableWithLabel
4
  from base.constant import SHIELD_BASE_MAP
5
 
6
 
 
1
  from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTabWidget
2
 
3
+ from qt.components import ComboWithLabel, SpinWithLabel, LabelWithLabel, TableWithLabel
4
  from base.constant import SHIELD_BASE_MAP
5
 
6
 
qt/components/equipments.py CHANGED
@@ -4,8 +4,7 @@ import os
4
  from qt.constant import POSITION_MAP, STONES_POSITIONS, EQUIPMENTS_DIR, ENCHANTS_DIR, STONES_DIR, MAX_STONE_ATTR
5
  from qt.constant import EMBED_POSITIONS, MAX_EMBED_LEVEL, MAX_STONE_LEVEL, SPECIAL_ENCHANT_POSITIONS
6
  from qt.components import ComboWithLabel, RadioWithLabel, TableWithLabel
7
- from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QTabWidget, QSizePolicy, QSpacerItem
8
- from PySide6.QtCore import Qt
9
 
10
 
11
  class EquipmentWidget(QWidget):
 
4
  from qt.constant import POSITION_MAP, STONES_POSITIONS, EQUIPMENTS_DIR, ENCHANTS_DIR, STONES_DIR, MAX_STONE_ATTR
5
  from qt.constant import EMBED_POSITIONS, MAX_EMBED_LEVEL, MAX_STONE_LEVEL, SPECIAL_ENCHANT_POSITIONS
6
  from qt.components import ComboWithLabel, RadioWithLabel, TableWithLabel
7
+ from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QTabWidget
 
8
 
9
 
10
  class EquipmentWidget(QWidget):
qt/scripts/config.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+
4
+ from qt.components.config import ConfigWidget
5
+ from qt.components.equipments import EquipmentsWidget
6
+ from utils.parser import Parser
7
+
8
+
9
+ if not os.path.exists("config"):
10
+ CONFIG = {}
11
+ else:
12
+ CONFIG = json.load(open("config", encoding="utf-8"))
13
+
14
+
15
+ def config_script(parser: Parser, config_widget: ConfigWidget, equipments_widget: EquipmentsWidget):
16
+ def load_config():
17
+ config_name = config_widget.config_select.combo_box.currentText()
18
+ config = CONFIG.get(parser.school.school, {}).get(config_name, {})
19
+
20
+ for label, equipment in equipments_widget.items():
21
+ if 'equipment' not in config[label]:
22
+ continue
23
+ else:
24
+ index = equipment.equipment.combo_box.findText(config[label]['equipment'])
25
+ equipment.equipment.combo_box.setCurrentIndex(index)
26
+
27
+ equipment.strength_level.combo_box.setCurrentIndex(config[label]['strength_level'])
28
+ if 'enchant' in config[label]:
29
+ index = equipment.enchant.combo_box.findText(config[label]['enchant'])
30
+ equipment.enchant.combo_box.setCurrentIndex(index)
31
+ if 'special_enchant' in config[label]:
32
+ if equipment.special_enchant.radio_button.isChecked() != config[label]['special_enchant']:
33
+ equipment.special_enchant.radio_button.click()
34
+ if 'embed_levels' in config[label]:
35
+ for i, embed_level in enumerate(equipment.embed_levels):
36
+ embed_level.combo_box.setCurrentIndex(config[label]['embed_levels'][i])
37
+ if 'stone_level' in config[label]:
38
+ equipment.stone_level.combo_box.setCurrentIndex(config[label]['stone_level'])
39
+ if 'stone_attrs' in config[label]:
40
+ for i, stone_attr in enumerate(equipment.stone_attrs):
41
+ index = equipment.stone_attrs[i].combo_box.findText(config[label]['stone_attrs'][i])
42
+ stone_attr.combo_box.setCurrentIndex(index)
43
+
44
+ config_widget.config_name.text_browser.setText(config_name)
45
+
46
+ config_widget.load_config.clicked.connect(load_config)
47
+
48
+ def save_config():
49
+ config_name = config_widget.config_name.text_browser.text()
50
+ if parser.school.school not in CONFIG:
51
+ CONFIG[parser.school.school] = {}
52
+ if config_name not in CONFIG[parser.school.school]:
53
+ CONFIG[parser.school.school][config_name] = {}
54
+ config = CONFIG[parser.school.school][config_name]
55
+
56
+ for label, equipment in equipments_widget.items():
57
+ config[label] = {}
58
+ if not (text := equipment.equipment.combo_box.currentText()):
59
+ continue
60
+ else:
61
+ config[label]['equipment'] = text
62
+ config[label]['strength_level'] = equipment.strength_level.combo_box.currentIndex()
63
+ if equipment.enchant:
64
+ config[label]['enchant'] = equipment.enchant.combo_box.currentText()
65
+ if equipment.special_enchant:
66
+ config[label]['special_enchant'] = equipment.special_enchant.radio_button.isChecked()
67
+ if equipment.embed_levels:
68
+ config[label]['embed_levels'] = [
69
+ embed_level.combo_box.currentIndex() for embed_level in equipment.embed_levels
70
+ ]
71
+ if equipment.stone_level:
72
+ config[label]['stone_level'] = equipment.stone_level.combo_box.currentIndex()
73
+ if equipment.stone_attrs:
74
+ config[label]['stone_attrs'] = [
75
+ stone_attr.combo_box.currentText() for stone_attr in equipment.stone_attrs
76
+ ]
77
+ json.dump(CONFIG, open("config", "w", encoding="utf-8"), ensure_ascii=False)
78
+
79
+ config_choices = list(CONFIG.get(parser.school.school, {}))
80
+ if current_select := config_widget.config_select.combo_box.currentText():
81
+ default_index = config_choices.index(current_select)
82
+ else:
83
+ default_index = -1
84
+ config_widget.config_select.set_items(config_choices, default_index=default_index)
85
+
86
+ config_widget.save_config.clicked.connect(save_config)
qt/scripts/dashboard.py CHANGED
@@ -33,8 +33,9 @@ def detail_content(detail):
33
  damage_content = [
34
  ["命中伤害", f"{detail['damage']}"],
35
  ["会心伤害", f"{detail['critical_damage']}"],
36
- ["期望会心", f"{round(detail['critical_strike'] * 100, 2)}%"],
37
- ["期望伤害", f"{round(detail['expected_damage'], 2)}"]
 
38
  ]
39
  gradient_content = [
40
  [ATTR_TYPE_TRANSLATE[k], f"{round(v / detail['expected_damage'] * 100, 2)}%"]
@@ -109,6 +110,7 @@ def dashboard_script(parser: Parser,
109
  detail_widget.skill_combo.set_items(skill_choices, default_index=default_index)
110
 
111
  def set_status(_):
 
112
  detail_widget = dashboard_widget.detail_widget
113
  skill = detail_widget.skill_combo.combo_box.currentText()
114
  status = detail_widget.status_combo.combo_box.currentText()
 
33
  damage_content = [
34
  ["命中伤害", f"{detail['damage']}"],
35
  ["会心伤害", f"{detail['critical_damage']}"],
36
+ ["期望伤害", f"{round(detail['expected_damage'], 2)}"],
37
+ ["会心", f"{round(detail['critical_strike'] * 100, 2)}%"],
38
+ ["期望会心", f"{round(detail['expected_critical_strike'] * 100, 2)}%"],
39
  ]
40
  gradient_content = [
41
  [ATTR_TYPE_TRANSLATE[k], f"{round(v / detail['expected_damage'] * 100, 2)}%"]
 
110
  detail_widget.skill_combo.set_items(skill_choices, default_index=default_index)
111
 
112
  def set_status(_):
113
+ set_detail(None)
114
  detail_widget = dashboard_widget.detail_widget
115
  skill = detail_widget.skill_combo.combo_box.currentText()
116
  status = detail_widget.status_combo.combo_box.currentText()
qt/scripts/top.py CHANGED
@@ -1,6 +1,7 @@
1
  from PySide6.QtWidgets import QFileDialog, QWidget
2
 
3
  from general.consumables import FOODS, POTIONS, WEAPON_ENCHANTS, SNACKS, WINES, SPREADS
 
4
  from qt.components.consumables import ConsumablesWidget
5
  from qt.components.dashboard import DashboardWidget
6
  from qt.components.equipments import EquipmentsWidget
@@ -11,19 +12,22 @@ from qt.components.top import TopWidget
11
  # from general.consumables import FOODS, POTIONS, WEAPON_ENCHANTS, SPREADS, SNACKS, WINES
12
  # from general.gains.formation import FORMATIONS
13
  from qt.constant import MAX_RECIPES, MAX_STONE_LEVEL
 
14
  from utils.parser import Parser
15
 
16
 
17
- def top_script(top_widget: TopWidget, config_widget: QWidget, dashboard_widget: DashboardWidget,
18
- talents_widget: TalentsWidget, recipes_widget: RecipesWidget,
19
- equipments_widget: EquipmentsWidget, consumables_widget: ConsumablesWidget
20
- ):
21
  parser = Parser()
22
 
23
  def upload_logs():
24
  file_name = QFileDialog(top_widget, "Choose File").getOpenFileName()
25
  parser(file_name[0])
26
  school = parser.school
 
 
 
27
  """ Update dashboard """
28
  record_index = list(parser.record_index)
29
  dashboard_widget.fight_select.set_items(record_index)
@@ -78,6 +82,7 @@ def top_script(top_widget: TopWidget, config_widget: QWidget, dashboard_widget:
78
  consumables_widget.spread.set_items([""] + SPREADS[school.major] + SPREADS[school.kind])
79
 
80
  config_widget.show()
 
81
 
82
  top_widget.upload_button.clicked.connect(upload_logs)
83
 
 
1
  from PySide6.QtWidgets import QFileDialog, QWidget
2
 
3
  from general.consumables import FOODS, POTIONS, WEAPON_ENCHANTS, SNACKS, WINES, SPREADS
4
+ from qt.components.config import ConfigWidget
5
  from qt.components.consumables import ConsumablesWidget
6
  from qt.components.dashboard import DashboardWidget
7
  from qt.components.equipments import EquipmentsWidget
 
12
  # from general.consumables import FOODS, POTIONS, WEAPON_ENCHANTS, SPREADS, SNACKS, WINES
13
  # from general.gains.formation import FORMATIONS
14
  from qt.constant import MAX_RECIPES, MAX_STONE_LEVEL
15
+ from qt.scripts.config import CONFIG
16
  from utils.parser import Parser
17
 
18
 
19
+ def top_script(top_widget: TopWidget, config_widget: ConfigWidget, bottom_widget: QWidget,
20
+ dashboard_widget: DashboardWidget, talents_widget: TalentsWidget, recipes_widget: RecipesWidget,
21
+ equipments_widget: EquipmentsWidget, consumables_widget: ConsumablesWidget):
 
22
  parser = Parser()
23
 
24
  def upload_logs():
25
  file_name = QFileDialog(top_widget, "Choose File").getOpenFileName()
26
  parser(file_name[0])
27
  school = parser.school
28
+ """ Update config """
29
+ config_choices = list(CONFIG.get(school.school, {}))
30
+ config_widget.config_select.set_items(config_choices, default_index=-1)
31
  """ Update dashboard """
32
  record_index = list(parser.record_index)
33
  dashboard_widget.fight_select.set_items(record_index)
 
82
  consumables_widget.spread.set_items([""] + SPREADS[school.major] + SPREADS[school.kind])
83
 
84
  config_widget.show()
85
+ bottom_widget.show()
86
 
87
  top_widget.upload_button.clicked.connect(upload_logs)
88
 
schools/bei_ao_jue/skills.py CHANGED
@@ -59,6 +59,13 @@ SKILLS: Dict[int, Skill | dict] = {
59
  [280],
60
  "interval": 48
61
  },
 
 
 
 
 
 
 
62
  16933: {
63
  "skill_class": PhysicalDamage,
64
  "skill_name": "惊燕式",
@@ -299,6 +306,12 @@ SKILLS: Dict[int, Skill | dict] = {
299
  "attack_power_cof": 380,
300
  "interval": 48
301
 
 
 
 
 
 
 
302
  }
303
  }
304
 
 
59
  [280],
60
  "interval": 48
61
  },
62
+ 17060: {
63
+ "skill_class": Skill,
64
+ "skill_name": "闹须弥",
65
+ "bind_skill": 11447,
66
+ "tick": 8
67
+
68
+ },
69
  16933: {
70
  "skill_class": PhysicalDamage,
71
  "skill_name": "惊燕式",
 
306
  "attack_power_cof": 380,
307
  "interval": 48
308
 
309
+ },
310
+ 26934: {
311
+ "skill_class": Skill,
312
+ "skill_name": "背水沉舟",
313
+ "bind_skill": 19555,
314
+ "tick": 8
315
  }
316
  }
317
 
utils/analyzer.py CHANGED
@@ -11,7 +11,7 @@ def filter_status(status, school: School, skill_id):
11
  if buff.gain_attributes or skill_id in buff.gain_skills:
12
  buffs.append(buff)
13
 
14
- return buffs
15
 
16
 
17
  def add_buffs(current_buffs, snapshot_buffs, attribute: Attribute, skill: Skill):
@@ -42,7 +42,6 @@ def analyze_details(record, duration: int, attribute: Attribute, school: School)
42
  total_gradients = {attr: 0. for attr in attribute.grad_attrs}
43
  duration *= 1000
44
 
45
- existed_buffs = []
46
  for skill, status in record.items():
47
  skill_id, skill_level, skill_stack = skill
48
  skill: Skill = school.skills[skill_id]
@@ -51,7 +50,13 @@ def analyze_details(record, duration: int, attribute: Attribute, school: School)
51
  skill_detail = {}
52
  details[skill.display_name] = skill_detail
53
  for (current_status, snapshot_status), timeline in status.items():
54
- timeline = [t for t in timeline if t < duration]
 
 
 
 
 
 
55
  if not timeline:
56
  continue
57
 
@@ -59,7 +64,7 @@ def analyze_details(record, duration: int, attribute: Attribute, school: School)
59
  snapshot_buffs = filter_status(snapshot_status, school, skill_id)
60
  add_buffs(current_buffs, snapshot_buffs, attribute, skill)
61
 
62
- damage, critical_strike, critical_damage, expected_damage = skill(attribute)
63
  gradients = analyze_gradients(skill, attribute)
64
 
65
  sub_buffs(current_buffs, snapshot_buffs, attribute, skill)
@@ -68,16 +73,18 @@ def analyze_details(record, duration: int, attribute: Attribute, school: School)
68
  for attr, residual_damage in gradients.items():
69
  total_gradients[attr] += residual_damage * len(timeline)
70
 
71
- buffs = (",".join(buff.display_name for buff in current_buffs) +
72
- ";" +
73
- ",".join(buff.display_name for buff in snapshot_buffs))
 
74
  if not buffs:
75
- buffs = "~~~~~"
76
  skill_detail[buffs] = dict(
77
  damage=damage,
78
- critical_strike=critical_strike,
79
  critical_damage=critical_damage,
80
  expected_damage=expected_damage,
 
 
81
  # "timeline": [round(t / 1000, 3) for t in timeline],
82
  count=len(timeline),
83
  gradients=gradients
@@ -95,10 +102,10 @@ def analyze_summary(details):
95
  for skill, skill_detail in details.items():
96
  skill = skill.split("/")[0]
97
  if skill not in summary:
98
- summary[skill] = {"count": 0, "hit": 0, "critical": 0, "damage": 0}
99
  for buff, detail in skill_detail.items():
100
  summary[skill]["count"] += detail['count']
101
- summary[skill]["critical"] += detail['count'] * detail['critical_strike']
102
  summary[skill]["damage"] += detail['count'] * detail['expected_damage']
103
 
104
  return summary
 
11
  if buff.gain_attributes or skill_id in buff.gain_skills:
12
  buffs.append(buff)
13
 
14
+ return tuple(buffs)
15
 
16
 
17
  def add_buffs(current_buffs, snapshot_buffs, attribute: Attribute, skill: Skill):
 
42
  total_gradients = {attr: 0. for attr in attribute.grad_attrs}
43
  duration *= 1000
44
 
 
45
  for skill, status in record.items():
46
  skill_id, skill_level, skill_stack = skill
47
  skill: Skill = school.skills[skill_id]
 
50
  skill_detail = {}
51
  details[skill.display_name] = skill_detail
52
  for (current_status, snapshot_status), timeline in status.items():
53
+ hit_timeline, critical_timeline = [], []
54
+ for timestamp, critical in timeline:
55
+ if critical:
56
+ critical_timeline.append(timestamp)
57
+ else:
58
+ hit_timeline.append(timestamp)
59
+ timeline = [t for t in timeline if t[0] < duration]
60
  if not timeline:
61
  continue
62
 
 
64
  snapshot_buffs = filter_status(snapshot_status, school, skill_id)
65
  add_buffs(current_buffs, snapshot_buffs, attribute, skill)
66
 
67
+ damage, expected_critical_strike, critical_damage, expected_damage = skill(attribute)
68
  gradients = analyze_gradients(skill, attribute)
69
 
70
  sub_buffs(current_buffs, snapshot_buffs, attribute, skill)
 
73
  for attr, residual_damage in gradients.items():
74
  total_gradients[attr] += residual_damage * len(timeline)
75
 
76
+ buffs = ",".join(buff.display_name for buff in current_buffs)
77
+ if snapshot_buffs and current_buffs != snapshot_buffs:
78
+ buffs += f"({','.join(buff.display_name for buff in snapshot_buffs)})"
79
+
80
  if not buffs:
81
+ buffs = "~"
82
  skill_detail[buffs] = dict(
83
  damage=damage,
 
84
  critical_damage=critical_damage,
85
  expected_damage=expected_damage,
86
+ critical_strike=len(critical_timeline) / (len(critical_timeline) + len(hit_timeline)),
87
+ expected_critical_strike=expected_critical_strike,
88
  # "timeline": [round(t / 1000, 3) for t in timeline],
89
  count=len(timeline),
90
  gradients=gradients
 
102
  for skill, skill_detail in details.items():
103
  skill = skill.split("/")[0]
104
  if skill not in summary:
105
+ summary[skill] = {"count": 0, "critical": 0, "damage": 0}
106
  for buff, detail in skill_detail.items():
107
  summary[skill]["count"] += detail['count']
108
+ summary[skill]["critical"] += detail['count'] * detail['expected_critical_strike']
109
  summary[skill]["damage"] += detail['count'] * detail['expected_damage']
110
 
111
  return summary
utils/parser.py CHANGED
@@ -1,5 +1,6 @@
1
  from dataclasses import dataclass
2
  from typing import Dict, List, Type, Union, Tuple
 
3
 
4
  from base.attribute import Attribute
5
  from base.buff import Buff
@@ -124,8 +125,8 @@ class Parser:
124
  self.records = []
125
  self.status = {}
126
  self.snapshot = {}
127
- self.stacks = {}
128
- self.ticks = {}
129
 
130
  self.start_time = []
131
  self.end_time = []
@@ -161,16 +162,16 @@ class Parser:
161
  self.status[(buff_id, buff_level)] = buff_stack
162
 
163
  def parse_skill(self, detail, timestamp):
164
- skill_id, skill_level = detail[4], detail[5]
165
  if skill_id not in self.school.skills:
166
  return
167
 
168
- skill_stack = max(1, self.stacks.get(skill_id, 0))
169
-
170
- if self.ticks.get(skill_id):
171
  self.ticks[skill_id] -= 1
172
- if not self.ticks[skill_id]:
173
- self.stacks[skill_id] = 0
174
 
175
  skill_tuple = (skill_id, skill_level, skill_stack)
176
  skill = self.school.skills[skill_id]
@@ -181,10 +182,10 @@ class Parser:
181
  else:
182
  if skill_tuple not in self.current_record:
183
  self.current_record[skill_tuple] = {}
184
- status = self.available_status(skill_id)
185
- if status not in self.current_record[skill_tuple]:
186
- self.current_record[skill_tuple][status] = []
187
- self.current_record[skill_tuple][status].append(int(timestamp) - self.start_time[-1])
188
 
189
  def __call__(self, file_name):
190
  self.reset()
 
1
  from dataclasses import dataclass
2
  from typing import Dict, List, Type, Union, Tuple
3
+ from collections import defaultdict
4
 
5
  from base.attribute import Attribute
6
  from base.buff import Buff
 
125
  self.records = []
126
  self.status = {}
127
  self.snapshot = {}
128
+ self.stacks = defaultdict(int)
129
+ self.ticks = defaultdict(int)
130
 
131
  self.start_time = []
132
  self.end_time = []
 
162
  self.status[(buff_id, buff_level)] = buff_stack
163
 
164
  def parse_skill(self, detail, timestamp):
165
+ skill_id, skill_level, critical = detail[4], detail[5], detail[6]
166
  if skill_id not in self.school.skills:
167
  return
168
 
169
+ timestamp = int(timestamp) - self.start_time[-1]
170
+ skill_stack = max(1, self.stacks[skill_id])
171
+ if self.ticks[skill_id]:
172
  self.ticks[skill_id] -= 1
173
+ if not self.ticks[skill_id]:
174
+ self.stacks[skill_id] = 0
175
 
176
  skill_tuple = (skill_id, skill_level, skill_stack)
177
  skill = self.school.skills[skill_id]
 
182
  else:
183
  if skill_tuple not in self.current_record:
184
  self.current_record[skill_tuple] = {}
185
+ status_tuple = self.available_status(skill_id)
186
+ if status_tuple not in self.current_record[skill_tuple]:
187
+ self.current_record[skill_tuple][status_tuple] = []
188
+ self.current_record[skill_tuple][status_tuple].append((timestamp, critical))
189
 
190
  def __call__(self, file_name):
191
  self.reset()