AngoHF commited on
Commit
3a1196e
·
1 Parent(s): 55f0dce

5.2 commit

Browse files
.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, current_frame, player_id, skill_level, skill_stack, critical, parser):
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, current_frame, player_id, skill_level, skill_stack, critical, parser):
188
- super().record(current_frame, player_id, skill_level, skill_stack, critical, parser)
189
  bind_skill = self.bind_skill
190
- if not parser.ticks[player_id][bind_skill]:
191
- parser.stacks[player_id][bind_skill] = 0
192
- parser.ticks[player_id][bind_skill] = self.tick
193
- parser.stacks[player_id][bind_skill] = min(parser.stacks[player_id][bind_skill] + 1, self.max_stack)
194
- parser.snapshot[player_id][bind_skill] = parser.status[player_id].copy()
195
 
196
 
197
  class DotConsumeSkill(Skill):
198
- def record(self, current_frame, player_id, skill_level, skill_stack, critical, parser):
199
- super().record(current_frame, player_id, skill_level, skill_stack, critical, parser)
200
  bind_skill = self.bind_skill
201
- skill_tuple, status_tuple = parser.last_dot[player_id][bind_skill]
202
  skill_id, skill_level, skill_stack = skill_tuple
203
- parser.ticks[player_id][skill_id] += 1
204
- tick = min(parser.ticks[player_id][skill_id], self.tick)
205
- current_record = parser.records[player_id][-1]
206
- current_record[(skill_id, skill_level, skill_stack * tick)][status_tuple].append(
207
- current_record[skill_tuple][status_tuple].pop()
208
  )
209
- parser.ticks[player_id][skill_id] -= tick
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, current_frame, player_id, skill_level, skill_stack, critical, parser):
330
- super().record(current_frame, player_id, skill_level, skill_stack, critical, parser)
331
  skill_tuple = (self.skill_id, skill_level, skill_stack)
332
- status_tuple = parser.available_status(player_id, self.skill_id)
333
- current_record = parser.records[player_id][-1]
334
- current_record[skill_tuple][status_tuple].append(
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, current_frame, player_id, skill_level, skill_stack, critical, parser):
342
- skill_tuple, status_tuple = super().record(current_frame, player_id, skill_level, skill_stack, critical, parser)
343
- parser.last_dot[player_id][self.skill_id] = (skill_tuple, status_tuple)
344
- parser.ticks[player_id][self.skill_id] -= 1
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.top_widget, self.dashboard_widget,
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, 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():
 
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
- 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()):
 
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: int
29
- current_frame: int
 
30
 
31
- id2name: Dict[int, str]
32
- name2id: Dict[str, int]
33
- records: Dict[int, List[RECORD_TYPE]]
34
- buffers: Dict[int, Dict[int, List[BUFFER_TYPE]]]
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
- start_time: Dict[int, List[int]]
43
- end_time: Dict[int, List[int]]
44
- record_index: Dict[int, Dict[str, int]]
45
 
46
- select_talents: Dict[int, List[int]]
47
- select_equipments: Dict[int, Dict[int, Dict[str, int | list]]]
48
 
49
- school: Dict[int, School]
 
50
 
51
- def duration(self, player_id, i):
52
- return round((self.end_time[player_id][i] - self.start_time[player_id][i]) / FRAME_PER_SECOND, 3)
53
 
54
- def available_status(self, player_id, skill_id):
55
- current_status = []
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
- snapshot_status = []
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
- return tuple(current_status), tuple(snapshot_status)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
  def reset(self):
74
  self.current_frame = 0
75
 
 
 
76
  self.id2name = {}
77
  self.name2id = {}
78
- self.records = defaultdict(list)
79
- self.buffers = defaultdict(lambda: defaultdict(list))
80
- self.status = defaultdict(dict)
 
 
 
 
 
 
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.start_time = defaultdict(list)
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
- if not buff_stack:
154
- self.status[player_id].pop((buff_id, buff_level), None)
155
- else:
156
- self.status[player_id][(buff_id, buff_level)] = buff_stack
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- self.buffers[self.current_frame][player_id].append((skill_id, skill_level, skill_stack, critical))
173
-
174
- if len(self.start_time[player_id]) == len(self.end_time[player_id]):
175
- self.start_time[player_id].append(self.current_frame)
176
- self.records[player_id].append(defaultdict(lambda: defaultdict(list)))
177
-
178
- def record(self, current_frame, player_id, skill_id, skill_level, skill_stack, critical):
179
- skill = self.school[player_id].skills[skill_id]
180
- skill.record(current_frame, player_id, skill_level, skill_stack, critical, self)
181
-
182
- def parse_record(self):
183
- last_frame = self.current_frame - BUFFER_DELAY
184
- pop_frames = [frame for frame in self.buffers if frame <= last_frame]
185
- for pop_frame in pop_frames:
186
- for player_id, buffers in self.buffers.pop(pop_frame).items():
187
- for buffer_tuple in buffers:
188
- self.record(pop_frame, player_id, *buffer_tuple)
189
-
190
- # def parse_buffer(self):
191
- # frames = [frame for frame in self.buffers if frame <= self.current_frame]
192
- # for frame in frames:
193
- # for player_id, buffers in self.buffers.pop(frame).items():
194
- # for buffer_tuple in buffers:
195
- # self.status[player_id].pop(buffer_tuple, None)
 
 
 
 
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
- if self.current_frame != int(row[1]):
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
- self.record_index = {
230
- player_id: {
231
- f"{i + 1}:{round((end_time - self.start_time[player_id][i]) / FRAME_PER_SECOND, 3)}": i
232
- for i, end_time in enumerate(self.end_time[player_id])
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)