Skip to content

Commit

Permalink
Add downstate, condi dmg, stealth, blocks, dodges, buff uptimes (#29)
Browse files Browse the repository at this point in the history
* added downstate count
* added dodges
* added condi dmg + power dmg
* added stealth
* added blocks
* added uptime for all squad buffs
* showing avgs for buffs in raid overview summary
* changed down_contrib to dmg_against_downed
* calculating attendance percentage with duration of fights
  • Loading branch information
Freyavf authored Jun 14, 2024
1 parent 5cdaecc commit bcbdbb8
Show file tree
Hide file tree
Showing 6 changed files with 330 additions and 119 deletions.
39 changes: 26 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,34 @@ Currently supported stats are:
- all damage
- damage dealt to target (in wvw: equivalent to damage dealt to players)
- damage dealt to everything else (in wvw: siege, npcs, ...)
- all condition damage
- condition damage dealt to target (in wvw: equivalent to damage dealt to players)
- condition damage dealt to everything else (in wvw: siege, npcs, ...)
- all power damage
- power damage dealt to target (in wvw: equivalent to damage dealt to players)
- power damage dealt to everything else (in wvw: siege, npcs, ...)
- spike damage (maximum damage dealt within 1s)
- killing hits
- downing hits
- down contribution (damage on downs)
- damage against downed players
- boon rips
- interrupts
- cleanses
- stability output (generation squad)
- protection output (generation squad)
- aegis output (generation squad)
- resistance output (generation squad)
- resolution output (generation squad)
- quickness output (generation squad)
- might output (generation squad)
- fury output (generation squad)
- alacrity output (generation squad)
- superspeed output (generation squad)
- swiftness output (generation squad)
- vigor output (generation squad)
- dodges
- blocks
- stability (output to squad, uptime)
- protection (output to squad, uptime)
- aegis (output to squad, uptime)
- resistance (output to squad, uptime)
- resolution (output to squad, uptime)
- quickness (output to squad, uptime)
- might (output to squad, uptime)
- fury (output to squad, uptime)
- alacrity (output to squad, uptime)
- superspeed (output to squad, uptime)
- swiftness (output to squad, uptime)
- vigor (output to squad, uptime)
- stealth (output to squad, uptime)
- all healing output
- healing dealt to target (in wvw: equivalent to healing on players)
- healing dealt to everything else (in wvw: npcs, pets, ...)
Expand All @@ -33,6 +43,9 @@ Currently supported stats are:
- total damage taken
- damage absorbed by barrier
- hp lost (= total damage taken - damage absorbed by barrier)
- condition damage taken
- power damage taken
- downstates
- deaths

Healing and barrier output can only be analyzed when contained in the logs, i.e., the [healing addon for arcdps](https://github.com/Krappa322/arcdps_healing_stats/releases) is installed. They will only be analyzed for players who also have the addon installed, since data may be incomplete for others.
Expand Down
28 changes: 20 additions & 8 deletions io_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ def write_stats_xls(players, top_players, stat, xls_output_filename, config):

# sort in descending order, unless it's a stat where low values are good and total or avg are sorted
sort_ascending = [False for x in config.sort_xls_by[stat]]
if stat == 'deaths' or stat == 'stripped' or stat == 'dist' or 'dmg_taken' in stat:
if stat == 'deaths' or stat == 'downstate' or stat == 'stripped' or stat == 'dist' or 'dmg_taken' in stat:
for i, val in enumerate(config.sort_xls_by[stat]):
if val == "avg" or val == "total":
sort_ascending[i] = True
# always sort strings in ascending order
for i, val in enumerate(config.sort_xls_by[stat]):
if is_string_column(val):
sort_ascending[i] = True

df = create_panda_dataframe(players, top_players, stat, sorting_columns, sort_ascending, config)

df.to_excel(writer, sheet_name = config.stat_names[stat], startrow = 3, index = False, header = False)
Expand All @@ -106,22 +106,24 @@ def write_stats_xls(players, top_players, stat, xls_output_filename, config):

column_names = [config.xls_column_names[c] for c in list(df) if c in config.xls_column_names]
column_names.append("Times Top "+str(config.num_players_considered_top[stat]))
column_names.append("Percentage Top"+str(config.num_players_considered_top[stat]))
column_names.append("Percentage Top "+str(config.num_players_considered_top[stat]))

# rename the columns for the xls
if stat == 'spike_dmg':
column_names.append("Maximum "+stat)
else:
column_names.append("Total "+stat)

if stat == 'deaths' or stat == 'kills' or stat == 'downs':
if stat == 'deaths' or stat == 'kills' or stat == 'downstate' or stat == 'downs':
column_names.append("Average "+stat+" per min "+config.duration_for_averages[stat])
elif stat == 'spike_dmg':
column_names.append("Average "+stat+" over all fights")
elif stat in config.squad_buff_ids and stat in config.buffs_not_stacking:
column_names.append("Average "+stat+" in %")
elif stat not in config.self_buff_ids:
column_names.append("Average "+stat+" per s "+config.duration_for_averages[stat])
if stat in config.squad_buff_ids:
column_names.append(stat+" Uptime in %")
for i in range(len(column_names)):
header_cell = sheet.cell(row=3, column=(i+1))
header_cell.value = column_names[i]
Expand Down Expand Up @@ -233,7 +235,10 @@ def create_panda_dataframe_overview(fights, overall_squad_stats, overall_raid_st
num_enemies.append(overall_raid_stats['mean_enemies'])
kills.append(overall_raid_stats['total_kills'])
for stat in config.stats_to_compute:
stats[stat].append(overall_squad_stats['total'][stat])
if stat in config.squad_buff_abbrev.values():
stats[stat].append(overall_squad_stats['avg'][stat])
else:
stats[stat].append(overall_squad_stats['total'][stat])

data = {"": first_col,
"#": fight_num,
Expand Down Expand Up @@ -271,10 +276,17 @@ def create_panda_dataframe(players, top_players, stat, sorting_columns, sort_asc
duration_present = (players[top_players[i]].duration_present[config.duration_for_averages[stat]] for i in range(len(top_players)))
consistency_stats = (players[top_players[i]].consistency_stats[stat] for i in range(len(top_players)))
portion_top_stats = (players[top_players[i]].portion_top_stats[stat]*100 for i in range(len(top_players)))
total_stats = (players[top_players[i]].total_stats[stat] for i in range(len(top_players)))
total_stats = list()
if stat in config.squad_buff_abbrev.values():
total_stats = (players[top_players[i]].total_stats[stat]['gen'] for i in range(len(top_players)))
else:
total_stats = (players[top_players[i]].total_stats[stat] for i in range(len(top_players)))
average_stats = list()
if stat not in config.self_buff_ids:
average_stats = (players[top_players[i]].average_stats[stat] for i in range(len(top_players)))
uptime_stats = list()
if stat in config.squad_buff_abbrev.values():
uptime_stats = (players[top_players[i]].total_stats[stat]['uptime'] for i in range(len(top_players)))
data = {"account": accounts,
"name": names,
"profession": professions,
Expand All @@ -285,11 +297,11 @@ def create_panda_dataframe(players, top_players, stat, sorting_columns, sort_asc
"total": total_stats}
if stat not in config.self_buff_ids:
data["avg"] = average_stats
if stat in config.squad_buff_abbrev.values():
data["uptime"] = uptime_stats

df = pd.DataFrame(data)
print(stat)
#if stat == 'interrupts':
# print(df)

df.sort_values(sorting_columns, ascending=sort_ascending, inplace=True)
return df
150 changes: 118 additions & 32 deletions json_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,32 @@ def get_stat_from_player_json(player_json, stat, fight, player_duration_present,
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 ###
Expand Down Expand Up @@ -300,7 +326,7 @@ def get_stat_from_player_json(player_json, stat, fight, player_duration_present,
# 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).")
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'])

Expand All @@ -317,6 +343,20 @@ def get_stat_from_player_json(player_json, stat, fight, player_duration_present,
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 ###
#################
Expand All @@ -339,6 +379,44 @@ def get_stat_from_player_json(player_json, stat, fight, player_duration_present,
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.")
Expand Down Expand Up @@ -368,11 +446,11 @@ def get_stat_from_player_json(player_json, stat, fight, player_duration_present,
return -1
return int(player_json['statsAll'][0]['downed'])

if stat == 'down_contrib':
if 'statsAll' not in player_json or len(player_json['statsAll']) == 0 or 'downContribution' not in player_json['statsAll'][0]:
config.errors.append("Could not find statsAll or downed in json to determine down contribution.")
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]['downContribution'])
return int(player_json['statsAll'][0]['againstDownedDamage'])

##################################
### Incoming / Outgoing strips ###
Expand Down Expand Up @@ -468,35 +546,17 @@ def get_stat_from_player_json(player_json, stat, fight, player_duration_present,
config.errors.append("Could not find regen in json to determine hits_from_regen.")
return -1

#############
### Auras ###
#############

if 'aura' in stat and stat in config.squad_buff_ids:
if 'buffUptimes' not in player_json:
config.errors.append("Could not find buffUptimes in json to determine "+stat+".")
return -1
for buff in player_json['buffUptimes']:
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 'uptime' not in buff['buffData'][0]:
config.errors.append("Could not find entry for buffData or uptime in json to determine "+stat+".")
return -1
return float(buff['buffData'][0]['uptime'])
config.errors.append("Could not find the buff "+stat+" in the json. Treating as 0.")
return 0.

###################
### Squad Buffs ###
###################

if stat in config.squad_buff_ids:
if 'squadBuffs' not in player_json:
config.errors.append("Could not find squadBuffs in json to determine "+stat+".")
return -1
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:
Expand All @@ -506,11 +566,34 @@ def get_stat_from_player_json(player_json, stat, fight, player_duration_present,
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 -1
return float(buff['buffData'][0]['generation'])
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.")
return 0.
vals['gen'] = 0.
vals['uptime'] = 0.
return vals

##################
### Self Buffs ###
Expand All @@ -534,9 +617,12 @@ def get_stat_from_player_json(player_json, stat, fight, player_duration_present,
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.")
config.errors.append("Stat "+stat+" is currently not supported! Treating it as 0.")
return 0


Expand Down
Loading

0 comments on commit bcbdbb8

Please sign in to comment.