-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathjson_helper.py
732 lines (636 loc) · 35.6 KB
/
json_helper.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
#!/usr/bin/env python3
import math
from stat_classes import Fight, Config
from io_helper import myprint
# get ids of buffs in the log from the buff map
# Input:
# player_json: json data with the player info. In a json file as parsed by Elite Insights, one entry of the 'players' list.
# config: config to use in top stats computation
# changes config.buffs_stacking_intensity and config.buffs_stacking_duration inplace
def get_buff_ids_from_json(json_data, config, log):
buffs = json_data['buffMap']
for buff_id, buff in buffs.items():
if buff['name'] in config.squad_buff_abbrev:
abbrev_name = config.squad_buff_abbrev[buff['name']]
config.squad_buff_ids[abbrev_name] = buff_id[1:]
if buff['stacking']:
config.buffs_stacking_intensity.append(abbrev_name)
elif 'aura' in abbrev_name:
config.buffs_not_stacking.append(abbrev_name)
else:
config.buffs_stacking_duration.append(abbrev_name)
if buff['name'] in config.self_buff_abbrev:
abbrev_name = config.self_buff_abbrev[buff['name']]
config.self_buff_ids[abbrev_name] = buff_id[1:]
# check that all buff ids were found
found_all_ids = True
for buff, abbrev in config.self_buff_abbrev.items():
if abbrev not in config.self_buff_ids:
myprint(log, "id for buff "+buff+" could not be found. This is not necessarily an error, the buff might just not be present in this log.", "info", config)
found_all_ids = False
for buff, abbrev in config.squad_buff_abbrev.items():
if abbrev not in config.squad_buff_ids:
myprint(log, "id for buff "+buff+" could not be found. This is not necessarily an error, the buff might just not be present in this log.", "info", config)
found_all_ids = False
return found_all_ids
# get stats for this fight from fight_json
# Input:
# fight_json = json object including one fight
# config = the config to use for top stat computation
# log = log file to write to
def get_stats_from_fight_json(fight_json, config, log):
# get fight duration in min and sec
fight_duration_json = fight_json['duration']
split_duration = fight_duration_json.split('h ', 1)
hours = 0
mins = 0
secs = 0
if len(split_duration) > 1:
hours = int(split_duration[0])
split_duration = fight_duration_json.split('m ', 1)
if len(split_duration) > 1:
mins = int(split_duration[0])
split_duration = split_duration[1].split('s', 1)
if len(split_duration) > 1:
secs = int(split_duration[0])
myprint(log, "duration: "+str(hours)+"h "+str(mins)+"m "+str(secs)+"s", "debug", config)
duration = hours*3600 + mins*60 + secs
num_allies = len(fight_json['players'])
num_enemies = 0
num_kills = 0
for enemy in fight_json['targets']:
if 'enemyPlayer' in enemy and enemy['enemyPlayer'] == True:
num_enemies += 1
# if combat replay data is there, add number of times this player died to total num kills
if 'defenses' in enemy:
num_kills += enemy['defenses'][0]['deadCount']
# initialize fight
fight = Fight()
fight.duration = duration
fight.enemies = num_enemies
fight.allies = num_allies
fight.kills = num_kills
fight.start_time = fight_json['timeStartStd']
fight.end_time = fight_json['timeEndStd']
fight.total_stats = {key: 0 for key in config.stats_to_compute}
fight.avg_stats = {key: 0 for key in config.stats_to_compute}
# skip fights that last less than min_fight_duration seconds
if(duration < config.min_fight_duration):
fight.skipped = True
print_string = "\nFight only took "+str(hours)+"h "+str(mins)+"m "+str(secs)+"s. Skipping fight."
myprint(log, print_string, "info")
# skip fights with less than min_allied_players allies
if num_allies < config.min_allied_players:
fight.skipped = True
print_string = "\nOnly "+str(num_allies)+" allied players involved. Skipping fight."
myprint(log, print_string, "info")
# skip fights with less than min_enemy_players enemies
if num_enemies < config.min_enemy_players:
fight.skipped = True
print_string = "\nOnly "+str(num_enemies)+" enemies involved. Skipping fight."
myprint(log, print_string, "info")
# get players using healing addon, if the addon was used
if 'usedExtensions' not in fight_json:
fight.players_running_healing_addon = []
else:
extensions = fight_json['usedExtensions']
for extension in extensions:
if extension['name'] == "Healing Stats":
fight.players_running_healing_addon = extension['runningExtension']
# TODO don't write polling rate, inch_to_pixel, tag_positions_until_death?
fight.polling_rate = fight_json['combatReplayMetaData']['pollingRate']
fight.inch_to_pixel = fight_json['combatReplayMetaData']['inchToPixel']
# get commander positions
tag_positions = list()
commander_found = False
i = 0
for player in fight_json['players']:
if player['hasCommanderTag']:
if commander_found:
# found a second player with commander tag -> distance to tag ambiguous, don't use it
tag_positions = list()
break
commander_found = True
tag_positions = player['combatReplayData']['positions']
tag_down_time, tag_death_time = get_first_down_and_death_time(player)
if tag_death_time > 0:
death_time = int(player['combatReplayData']['dead'][0][0] / fight.polling_rate)
tag_positions = tag_positions[:death_time]
commander_found = True
fight.tag_positions_until_death = tag_positions
return fight
# get account, character name and profession from json object
# Input:
# player_json: json data with the player info. In a json file as parsed by Elite Insights, one entry of the 'players' list.
# Output: account, character name, profession
def get_basic_player_data_from_json(player_json):
account = player_json['account']
name = player_json['name']
profession = player_json['profession']
not_in_squad = player_json['notInSquad']
return account, name, profession, not_in_squad
# get the first time the player went down, leading to death and the corresponding time of death, or -1, -1 if they never died
# Input:
# player_json: json data with the player info. In a json file as parsed by Elite Insights, one entry of the 'players' list.
def get_first_down_and_death_time(player_json):
first_down_time = -1
first_death_time = -1
player_deaths = dict(player_json['combatReplayData']['dead'])
player_downs = dict(player_json['combatReplayData']['down'])
# find the first player downstate event that lead to death
for death_begin, death_end in player_deaths.items():
for down_begin, down_end in player_downs.items():
if death_begin == down_end:
# down times are logged in ms -> divide by 1000
first_down_time = down_begin / 1000
first_death_time = death_begin / 1000
return first_down_time, first_death_time
return first_down_time, first_death_time
# get average distance to tag given the player and tag positions
def get_distance_to_tag(player_positions, tag_positions, inch_to_pixel):
player_distances = list()
for position,tag_position in zip(player_positions, tag_positions):
deltaX = position[0] - tag_position[0]
deltaY = position[1] - tag_position[1]
player_distances.append(math.sqrt(deltaX * deltaX + deltaY * deltaY))
if len(player_distances) > 0:
return (sum(player_distances) / len(player_distances)) / inch_to_pixel
else:
return -1
# TODO treat -1
# get value of stat from player_json
# return -1 if stat is not available or cannot be computed; or player was not present in the fight according to the duration_present relevant for the respective stat
# Input:
# player_json: json data with the player info. In a json file as parsed by Elite Insights, one entry of the 'players' list.
# stat: the stat being considered
# fight: information about the fight
# player_duration_present: the player.duration_present dict for this player, needed for some stat computations
# config: the config used for top stats computation
def get_stat_from_player_json(player_json, stat, fight, player_duration_present, config):
#######################
### Fight durations ###
#######################
if stat == 'time_active':
if 'activeTimes' not in player_json:
config.errors.append("Could not find activeTimes in json to determine time_active.")
return -1
return round(int(player_json['activeTimes'][0])/1000)
if stat == 'time_in_combat':
return round(sum_breakpoints(get_combat_time_breakpoints(player_json)) / 1000)
if stat == 'time_not_running_back':
if fight.tag_positions_until_death == list():
config.errors.append("Could not find tag positions to determine time_not_running_back.")
return -1
if 'combatReplayData' not in player_json or 'dead' not in player_json['combatReplayData'] or 'down' not in player_json['combatReplayData'] or 'statsAll' not in player_json or len(player_json['statsAll']) != 1 or 'distToCom' not in player_json['statsAll'][0]:
config.errors.append("json is missing combatReplayData or entries for dead, down, or distToCom to determine time_not_running_back.")
return -1
player_dist_to_tag = player_json['statsAll'][0]['distToCom']
player_positions = player_json['combatReplayData']['positions']
player_distances = list()
first_down_time, first_death_time = get_first_down_and_death_time(player_json)
first_tag_down_time = len(fight.tag_positions_until_death) * fight.polling_rate / 1000
# if player didn't go down and die, use time when com died
if first_down_time < 0 or first_down_time < first_tag_down_time:
first_down_time = first_tag_down_time
# check the avg distance to tag until a player died to see if they were running back
# if nobody was running back, just use the avg distance as computed by arcdps / EI
if first_down_time < len(player_positions) * fight.polling_rate / 1000:
first_down_position_index = int(first_down_time * 1000 / fight.polling_rate)
player_dist_to_tag = get_distance_to_tag(player_positions[:first_down_position_index], fight.tag_positions_until_death[:first_down_position_index], fight.inch_to_pixel)
# an average distance of more than 2000 until player or tag died likely means that the player was running back from the beginning
if player_dist_to_tag > 2000:
#print(f"distance of {player_json['name']} is {player_dist_to_tag}")
first_down_time = 0
# positions are recorded with polling rate in ms -> to get the time, need to multiply by that and divide by 1000
return first_down_time
#############
### group ###
#############
if stat == 'group':
if 'group' not in player_json:
config.errors.append("Could not find group in json.")
return -1
return int(player_json['group'])
########################################################
### check that fight duration is valid for this stat ###
########################################################
if config.duration_for_averages[stat] not in player_duration_present or player_duration_present[config.duration_for_averages[stat]] <= 0:
config.errors.append("Player was not in this fight according to duration_present relevant for stat"+stat+", or duration_present was not computed yet.")
return -1
################
### cleanses ###
################
if stat == 'cleanses':
if 'support' not in player_json or len(player_json['support']) != 1 or 'condiCleanse' not in player_json['support'][0]:
config.errors.append("Could not find support or an entry for condiCleanse in json.")
return -1
return int(player_json['support'][0]['condiCleanse'])
##############
### deaths ###
##############
if stat == 'deaths':
# TODO split by death on tag / off tag
if 'defenses' not in player_json or len(player_json['defenses']) != 1 or 'deadCount' not in player_json['defenses'][0]:
config.errors.append("Could not find defenses or an entry for deadCount in json.")
return -1
return int(player_json['defenses'][0]['deadCount'])
#################
### downstate ###
#################
if stat == 'downstate':
if 'defenses' not in player_json or len(player_json['defenses']) != 1 or 'downCount' not in player_json['defenses'][0]:
config.errors.append("Could not find defenses or an entry for downCount in json.")
return -1
return int(player_json['defenses'][0]['downCount'])
#################
### dodges ###
#################
if stat == 'dodges':
if 'defenses' not in player_json or len(player_json['defenses']) != 1 or 'dodgeCount' not in player_json['defenses'][0]:
config.errors.append("Could not find defenses or an entry for dodgeCount in json.")
return -1
return int(player_json['defenses'][0]['dodgeCount'])
##############
### blocks ###
##############
if stat == 'blocks':
if 'defenses' not in player_json or len(player_json['defenses']) != 1 or 'blockedCount' not in player_json['defenses'][0]:
config.errors.append("Could not find defenses or an entry for blockedCount in json.")
return -1
return int(player_json['defenses'][0]['blockedCount'])
################
### distance ###
################
if stat == 'dist':
if fight.tag_positions_until_death == list():
config.errors.append("Could not find tag positions to determine distance to tag.")
return -1
if 'combatReplayData' not in player_json or 'dead' not in player_json['combatReplayData'] or 'down' not in player_json['combatReplayData'] or 'statsAll' not in player_json or len(player_json['statsAll']) != 1 or 'distToCom' not in player_json['statsAll'][0]:
config.errors.append("json is missing combat replay data or entries for dead, down, or distToCom to determine distance to tag.")
return -1
# TODO this is hardcoded to not_running_back. make it possible to use active, total or in_combat too?
player_dist_to_tag = player_json['statsAll'][0]['distToCom']
if config.duration_for_averages[stat] == 'not_running_back':
first_down_time = player_duration_present['not_running_back']
player_positions = player_json['combatReplayData']['positions']
# if player or tag died before the fight ended, compute average distance until the first down time that lead to death
num_valid_positions = int(first_down_time * 1000 / fight.polling_rate)
player_dist_to_tag = get_distance_to_tag(player_positions[:num_valid_positions], fight.tag_positions_until_death[:num_valid_positions], fight.inch_to_pixel)
elif config.duration_for_averages[stat] == 'in_combat':
config.errors.append("average distance over time in combat is not implemented yet. Using overall average distance instead.")
return float(player_dist_to_tag)
#################
### Dmg Taken ###
#################
# includes dmg absorbed by barrier
if stat == 'dmg_taken_total':
if 'defenses' not in player_json or len(player_json['defenses']) != 1 or 'damageTaken' not in player_json['defenses'][0]:
config.errors.append("Could not find defenses or an entry for damageTaken in json to determine dmg_taken_total.")
return -1
return int(player_json['defenses'][0]['damageTaken'])
if stat == 'dmg_taken_absorbed':
if 'defenses' not in player_json or len(player_json['defenses']) != 1 or 'damageBarrier' not in player_json['defenses'][0]:
config.errors.append("Could not find defenses or an entry for damageBarrier in json to determine dmg_taken_absorbed.")
return -1
return int(player_json['defenses'][0]['damageBarrier'])
if stat == 'dmg_taken_hp_lost':
total_dmg_taken = get_stat_from_player_json(player_json, 'dmg_taken_total', fight, player_duration_present, config)
dmg_absorbed = get_stat_from_player_json(player_json, 'dmg_taken_absorbed', fight, player_duration_present, config)
if total_dmg_taken < 0 or dmg_absorbed < 0:
return -1
return total_dmg_taken - dmg_absorbed
# includes dmg absorbed by barrier
if stat == 'condi_dmg_taken_total':
if 'defenses' not in player_json or len(player_json['defenses']) != 1 or 'conditionDamageTaken' not in player_json['defenses'][0]:
config.errors.append("Could not find defenses or an entry for conditionDamageTaken in json to determine condi_dmg_taken_total.")
return -1
return int(player_json['defenses'][0]['conditionDamageTaken'])
# includes dmg absorbed by barrier
if stat == 'power_dmg_taken_total':
if 'defenses' not in player_json or len(player_json['defenses']) != 1 or 'powerDamageTaken' not in player_json['defenses'][0]:
config.errors.append("Could not find defenses or an entry for powerDamageTaken in json to determine power_dmg_taken_total.")
return -1
return int(player_json['defenses'][0]['powerDamageTaken'])
#################
### Dmg Dealt ###
#################
if stat == 'dmg_total':
if 'dpsAll' not in player_json or len(player_json['dpsAll']) != 1 or 'damage' not in player_json['dpsAll'][0]:
config.errors.append("Could not find dpsAll or an entry for damage in json to determine dmg_total.")
return -1
return int(player_json['dpsAll'][0]['damage'])
if stat == 'dmg_players':
if 'targetDamage1S' not in player_json:
config.errors.append("Could not find targetDamage1S in json to determine dmg_players.")
return -1
return sum(target[0][-1] for target in player_json['targetDamage1S'])
if stat == 'dmg_other':
total_dmg = get_stat_from_player_json(player_json, 'dmg_total', fight, player_duration_present, config)
players_dmg = get_stat_from_player_json(player_json, 'dmg_players', fight, player_duration_present, config)
if total_dmg < 0 or players_dmg < 0:
return -1
return total_dmg - players_dmg
if stat == 'condi_dmg_total':
if 'dpsAll' not in player_json or len(player_json['dpsAll']) != 1 or 'condiDamage' not in player_json['dpsAll'][0]:
config.errors.append("Could not find dpsAll or an entry for condiDamage in json to determine condi_dmg.")
return -1
return int(player_json['dpsAll'][0]['condiDamage'])
if stat == 'condi_dmg_players':
if 'targetConditionDamage1S' not in player_json:
config.errors.append("Could not find targetConditionDamage1S in json to determine condi_dmg_players.")
return -1
return sum(target[0][-1] for target in player_json['targetConditionDamage1S'])
if stat == 'condi_dmg_other':
total_condi_dmg = get_stat_from_player_json(player_json, 'condi_dmg_total', fight, player_duration_present, config)
players_condi_dmg = get_stat_from_player_json(player_json, 'condi_dmg_players', fight, player_duration_present, config)
if total_condi_dmg < 0 or players_condi_dmg < 0:
return -1
return total_condi_dmg - players_condi_dmg
if stat == 'power_dmg_total':
if 'dpsAll' not in player_json or len(player_json['dpsAll']) != 1 or 'powerDamage' not in player_json['dpsAll'][0]:
config.errors.append("Could not find dpsAll or an entry for powerDamage in json to determine power_dmg.")
return -1
return int(player_json['dpsAll'][0]['powerDamage'])
if stat == 'power_dmg_players':
if 'targetPowerDamage1S' not in player_json:
config.errors.append("Could not find targetPowerDamage1S in json to determine power_dmg_players.")
return -1
return sum(target[0][-1] for target in player_json['targetPowerDamage1S'])
if stat == 'power_dmg_other':
total_power_dmg = get_stat_from_player_json(player_json, 'power_dmg_total', fight, player_duration_present, config)
players_power_dmg = get_stat_from_player_json(player_json, 'power_dmg_players', fight, player_duration_present, config)
if total_power_dmg < 0 or players_power_dmg < 0:
return -1
return total_power_dmg - players_power_dmg
if stat == 'spike_dmg':
if 'targetDamage1S' not in player_json:
config.errors.append("Could not find targetDamage1S in json to determine spike_dmg.")
return -1
spike_dmg = -1
last_dmg = 0
for t in range(len(player_json['targetDamage1S'][0][0])):
new_dmg = sum(player_json['targetDamage1S'][enemy][0][t] for enemy in range(len(player_json['targetDamage1S'])))
spike_dmg = max(spike_dmg, new_dmg - last_dmg)
last_dmg = new_dmg
return spike_dmg
##############################
### Kill/Down contribution ###
##############################
if stat == 'kills':
if 'statsTargets' not in player_json or len(player_json['statsTargets']) == 0:
config.errors.append("Could not find statsTargets in json to determine number of kills.")
return -1
kills = sum(enemy[0]['killed'] for enemy in player_json['statsTargets'])
return kills
if stat == 'downs':
if 'statsAll' not in player_json or len(player_json['statsAll']) == 0 or 'downed' not in player_json['statsAll'][0]:
config.errors.append("Could not find statsAll or downed in json to determine number of downed.")
return -1
return int(player_json['statsAll'][0]['downed'])
if stat == 'dmg_against_downed':
if 'statsAll' not in player_json or len(player_json['statsAll']) == 0 or 'againstDownedDamage' not in player_json['statsAll'][0]:
config.errors.append("Could not find statsAll or againstDownedDamage in json to determine dmg against downed.")
return -1
return int(player_json['statsAll'][0]['againstDownedDamage'])
if stat == 'down_contrib':
if 'statsTargets' not in player_json or len(player_json['statsTargets']) == 0:
config.errors.append("Could not find statsTargets in json to determine down contribution.")
return -1
down_contribution = sum([stats[0]['downContribution'] for stats in player_json['statsTargets']])
return down_contribution
##################################
### Incoming / Outgoing strips ###
##################################
if stat == 'strips':
if 'support' not in player_json or len(player_json['support']) != 1 or 'boonStrips' not in player_json['support'][0]:
config.errors.append("Could not find support or an entry for boonStrips in json to determine strips.")
return -1
return int(player_json['support'][0]['boonStrips'])
if stat == 'stripped':
if 'defenses' not in player_json or len(player_json['defenses']) != 1 or 'boonStrips' not in player_json['defenses'][0]:
config.errors.append("Could not find defenses or an entry for boonStrips in json to determine stripped.")
return -1
return int(player_json['defenses'][0]['boonStrips'])
################
### Interrupts #
################
if stat == 'interrupts':
if 'statsTargets' not in player_json or len(player_json['statsTargets']) == 0:
config.errors.append("Could not find statsTargets in json to determine player interrupts.")
return -1
interrupts = sum([stats[0][stat] for stats in player_json['statsTargets']])
return interrupts
##############################
### Heal, Barrier, revives ###
##############################
if stat == 'heal_total':
# check if healing was logged, save it
if player_json['name'] not in fight.players_running_healing_addon:
return -1
if 'extHealingStats' not in player_json or 'outgoingHealing' not in player_json['extHealingStats']:
config.errors.append("Could not find extHealingStats or an entry for outgoingHealing in json to determine heal_total.")
return -1
return player_json['extHealingStats']['outgoingHealing'][0]['healing']
if stat == 'heal_players':
# check if healing was logged, save it
if player_json['name'] not in fight.players_running_healing_addon:
return -1
if 'extHealingStats' not in player_json or 'alliedHealing1S' not in player_json['extHealingStats']:
config.errors.append("Could not find extHealingStats or an entry for alliedHealing1S in json to determine heal_players.")
return -1
return sum([healing[0][-1] for healing in player_json['extHealingStats']['alliedHealing1S']])
if stat == 'heal_other':
# check if healing was logged, save it
total_heal = get_stat_from_player_json(player_json, 'heal_total', fight, player_duration_present, config)
player_heal = get_stat_from_player_json(player_json, 'heal_players', fight, player_duration_present, config)
if total_heal < 0 or player_heal < 0:
return -1
return total_heal - player_heal
if stat == 'barrier':
# check if barrier was logged, save it
if player_json['name'] not in fight.players_running_healing_addon:
return -1
if 'extBarrierStats' not in player_json or 'outgoingBarrier' not in player_json['extBarrierStats']:
config.errors.append("Could not find extBarrierStats or an entry for outgoingBarrier in json to determine barrier.")
return -1
return player_json['extBarrierStats']['outgoingBarrier'][0]['barrier']
# TODO fix output for heal from regen
if stat == 'heal_from_regen':
# check if healing was logged, look for regen
if player_json['name'] not in fight.players_running_healing_addon:
return -1
if 'extHealingStats' not in player_json or 'totalHealingDist' not in player_json['extHealingStats']:
config.errors.append("Could not find extHealingStats or an entry for totalHealingDist in json to determine heal_from_regen.")
return -1
healing_json = player_json['extHealingStats']['totalHealingDist'][0]
for healing_json2 in healing_json:
if 'id' in healing_json2 and healing_json2['id'] == int(config.squad_buff_ids['regen']):
return healing_json2['totalHealing']
config.errors.append("Could not find regen in json to determine heal_from_regen.")
return -1
if stat == 'hits_from_regen':
# check if healing was logged, look for regen
if player_json['name'] not in fight.players_running_healing_addon:
return -1
if 'extHealingStats' not in player_json or 'totalHealingDist' not in player_json['extHealingStats']:
config.errors.append("Could not find extHealingStats or an entry for totalHealingDist in json to determine hits_from_regen.")
return -1
healing_json = player_json['extHealingStats']['totalHealingDist'][0]
for healing_json2 in healing_json:
if 'id' in healing_json2 and healing_json2['id'] == int(config.squad_buff_ids['regen']):
return int(healing_json2['hits'])
config.errors.append("Could not find regen in json to determine hits_from_regen.")
return -1
if stat == 'resurrects':
if 'support' not in player_json or len(player_json['support']) != 1 or 'resurrects' not in player_json['support'][0]:
config.errors.append("Could not find support or an entry for resurrects in json to determine resurrects.")
return -1
return int(player_json['support'][0]['resurrects'])
###################
### Squad Buffs ###
###################
if stat in config.squad_buff_ids:
vals = {'gen': -1, 'uptime': -1}
squad_gen = -1
if 'squadBuffs' not in player_json or 'buffUptimes' not in player_json:
config.errors.append("Could not find squadBuffs or buffUptimes in json to determine "+stat+".")
return vals
# get buffs in squad generation -> need to loop over all buffs
for buff in player_json['squadBuffs']:
if 'id' not in buff:
continue
# find right buff
buffId = buff['id']
if buffId == int(config.squad_buff_ids[stat]):
if 'buffData' not in buff or len(buff['buffData']) == 0 or 'generation' not in buff['buffData'][0]:
config.errors.append("Could not find entry for buffData or generation in json to determine "+stat+".")
return vals
squad_gen = float(buff['buffData'][0]['generation'])
break
# get buffs in uptime -> need to loop over all buffs
for buff in player_json['buffUptimes']:
#TODO fix
if 'id' not in buff:
continue
# find right buff
buffId = buff['id']
if buffId == int(config.squad_buff_ids[stat]):
if stat in config.buffs_stacking_intensity:
if 'buffData' not in buff or len(buff['buffData']) == 0 or 'presence' not in buff['buffData'][0]:
config.errors.append("Could not find entry for buffData or presence in json to determine "+stat+".")
return vals
vals = {'gen': squad_gen, 'uptime': float(buff['buffData'][0]['presence'])}
else:
if 'buffData' not in buff or len(buff['buffData']) == 0 or 'uptime' not in buff['buffData'][0]:
config.errors.append("Could not find entry for buffData or uptime in json to determine "+stat+".")
return vals
vals = {'gen': squad_gen, 'uptime': float(buff['buffData'][0]['uptime'])}
return vals
config.errors.append("Could not find the buff "+stat+" in the json. Treating as 0.")
vals['gen'] = 0.
vals['uptime'] = 0.
return vals
##################
### Self Buffs ###
##################
# for self buffs, only check if they were there (1) or not (0)
if stat in config.self_buff_ids:
if 'selfBuffs' not in player_json:
config.errors.append("Could not find selfBuffs in json to determine "+stat+".")
return -1
for buff in player_json['selfBuffs']:
if 'id' not in buff:
continue
# find right buff
buffId = buff['id']
if buffId == int(config.self_buff_ids[stat]):
if 'buffData' not in buff or len(buff['buffData']) == 0 or 'generation' not in buff['buffData'][0]:
config.errors.append("Could not find entry for buffData or generation in json to determine "+stat+".")
return -1
return 1
config.errors.append("Could not find the buff "+stat+" in the json. Treating as 0.")
return 0
if stat in config.squad_buff_abbrev.values():
vals = {'gen': -1, 'uptime': -1}
return vals
if stat not in config.self_buff_abbrev.values() and stat not in config.squad_buff_abbrev.values():
config.errors.append("Stat "+stat+" is currently not supported! Treating it as 0.")
return 0
# find the first time a player took or dealt damage after initial_time
# Input:
# initial_time = check for first time this player was in combat after this time in the fight
# player_json = the json data for this player in this fight
# Output:
# First time the player took or dealt damage after initial_time
def get_combat_start_from_player_json(initial_time, player_json):
start_combat = -1
if ('healthPercents' not in player_json or len(player_json['healthPercents']) == 0) and ('powerDamage1S' not in player_json or len(player_json['powerDamage1S']) == 0):
return start_combat
if 'healthPercents' in player_json:
last_health_percent = 100
for change in player_json['healthPercents']:
# look for last timestamp before initial time
if change[0] < initial_time:
last_health_percent = change[1]
continue
if change[1] - last_health_percent < 0:
# got dmg
start_combat = change[0]
break
last_health_percent = change[1]
# from initial time until end of the fight, check when player dealt (power) dmg the first time
# not using condi, because condis can still tick after a player died
if 'powerDamage1S' in player_json and len(player_json['powerDamage1S']) > 0:
for i in range(math.ceil(initial_time/1000), len(player_json['powerDamage1S'][0])):
if i == 0:
continue
if player_json['powerDamage1S'][0][i] != player_json['powerDamage1S'][0][i-1]:
if start_combat == -1:
# if start_combat is -1 so far, the player didn't take damage until now -> this is the start of combat
start_combat = i*1000
else:
# otherwise he took dmg, check which was first
start_combat = min(start_combat, i*1000)
break
if start_combat == -1:
start_combat = initial_time
return start_combat
# find the combat breakpoints, i.e., start and end points of this player being in combat (interrupted by death)
# Input:
# player_json = the json data for this player in this fight
# Output:
# List of start and end timestamps of the player being in combat
def get_combat_time_breakpoints(player_json):
start_combat = get_combat_start_from_player_json(0, player_json)
end_combat = (len(player_json['damage1S'][0]))*1000
if 'combatReplayData' not in player_json:
print("WARNING: combatReplayData not in json, using activeTimes as time in combat")
# time_active = duration the player was not dead
return [start_combat, min(start_combat + get_stat_from_player_json(player_json, 'time_active', None, None, None) * 1000, end_combat)]
replay = player_json['combatReplayData']
if 'dead' not in replay:
return [start_combat, end_combat]
breakpoints = []
playerDeaths = dict(replay['dead'])
playerDowns = dict(replay['down'])
# need corresponding down event for each death event. down end = death start
for deathStart, deathEnd in playerDeaths.items():
for downStart, downEnd in playerDowns.items():
if deathStart == downEnd:
if start_combat != -1:
breakpoints.append([start_combat, deathStart])
start_combat = get_combat_start_from_player_json(deathEnd + 1000, player_json)
break
if start_combat != -1 and start_combat < end_combat:
breakpoints.append([start_combat, end_combat])
return breakpoints
# compute the time in combat from the breakpoints as determined in get_combat_time_breakpoints
# Input:
# breakpoints = list of [start combat, end combat] items
# Output:
# total time in combat
def sum_breakpoints(breakpoints):
combat_time = 0
for [start, end] in breakpoints:
combat_time += (end - start)
return combat_time