damage: add sorted list mode as default, cb mode
This commit is contained in:
248
bin/mhdamage.py
248
bin/mhdamage.py
@@ -67,7 +67,7 @@ def parse_args(argv):
|
|||||||
help="add Sharpness +1 skill, default off")
|
help="add Sharpness +1 skill, default off")
|
||||||
parser.add_argument("-f", "--awaken", action="store_true",
|
parser.add_argument("-f", "--awaken", action="store_true",
|
||||||
default=False,
|
default=False,
|
||||||
help="add Awaken (FreeElement), default off")
|
help="add Awaken (FreeElemnt), default off")
|
||||||
parser.add_argument("-a", "--attack-up",
|
parser.add_argument("-a", "--attack-up",
|
||||||
type=int, choices=range(0, 5), default=0,
|
type=int, choices=range(0, 5), default=0,
|
||||||
help="1-4 for AuS, M, L, XL")
|
help="1-4 for AuS, M, L, XL")
|
||||||
@@ -81,9 +81,21 @@ def parse_args(argv):
|
|||||||
parser.add_argument("-t", "--artillery",
|
parser.add_argument("-t", "--artillery",
|
||||||
type=int, choices=[0,1,2], default=0,
|
type=int, choices=[0,1,2], default=0,
|
||||||
help="0-2 for no artillery, novice, god")
|
help="0-2 for no artillery, novice, god")
|
||||||
|
parser.add_argument("-z", "--frenzy",
|
||||||
|
help="With virus affinity boost, must be either"
|
||||||
|
+" 15 (normal) or 30 (with Frenzy Res skill)",
|
||||||
|
type=int, choices=[0, 15, 30], default=0)
|
||||||
parser.add_argument("-p", "--parts",
|
parser.add_argument("-p", "--parts",
|
||||||
help="Limit analysis to specified parts"
|
help="Limit analysis to specified parts"
|
||||||
+" (comma separated list)")
|
+" (comma separated list)")
|
||||||
|
parser.add_argument("-l", "--phial",
|
||||||
|
help="Show CB phial damage at the sepcified level"
|
||||||
|
+" (1, 2, 3, 5=ultra) instead of normal motion"
|
||||||
|
+" values.",
|
||||||
|
type=int, choices=[0, 1, 2, 3, 5], default=0)
|
||||||
|
parser.add_argument("-d", "--diff", action="store_true", default=False,
|
||||||
|
help="Show percent difference in damage to each part"
|
||||||
|
+" from first weapon in list.")
|
||||||
parser.add_argument("-m", "--match", nargs="*",
|
parser.add_argument("-m", "--match", nargs="*",
|
||||||
help="WEAPON_TYPE,ELEMENT_OR_STATUS_OR_RAW"
|
help="WEAPON_TYPE,ELEMENT_OR_STATUS_OR_RAW"
|
||||||
+" Include all matching weapons in their final form."
|
+" Include all matching weapons in their final form."
|
||||||
@@ -104,94 +116,67 @@ def parse_args(argv):
|
|||||||
return parser.parse_args(argv)
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def print_sorted_phial_damage(names, damage_map_base, weapon_damage_map, parts,
|
||||||
args = parse_args(None)
|
level):
|
||||||
|
def cb_levelN(weapon):
|
||||||
|
return avg_phial(weapon_damage_map[weapon], level=level)
|
||||||
|
|
||||||
db = MHDB(_pathfix.db_path)
|
def avg_phial(wd, level=5):
|
||||||
motiondb = MotionValueDB(_pathfix.motion_values_path)
|
total = 0.0
|
||||||
|
for part in parts:
|
||||||
|
total += sum(wd.cb_phial_damage[part][level])
|
||||||
|
return total / len(parts)
|
||||||
|
|
||||||
monster = db.get_monster_by_name(args.monster)
|
names_sorted = list(names)
|
||||||
if not monster:
|
names_sorted.sort(key=cb_levelN, reverse=True)
|
||||||
raise ValueError("Monster '%s' not found" % args.monster)
|
|
||||||
monster_damage = db.get_monster_damage(monster.id)
|
|
||||||
|
|
||||||
weapons = []
|
_print_headers(parts, damage_map_base)
|
||||||
weapon_type = None
|
|
||||||
|
|
||||||
for match_tuple in args.match:
|
for name in names_sorted:
|
||||||
# TODO: better validation
|
print "%-20s:" % name,
|
||||||
wtype, element = match_tuple
|
damage_map = weapon_damage_map[name]
|
||||||
match_weapons = db.get_weapons_by_query(wtype=wtype, element=element,
|
print "%0.2f" % avg_phial(damage_map, level=level),
|
||||||
final=1)
|
for part in parts:
|
||||||
weapons.extend(match_weapons)
|
part_damage = damage_map[part]
|
||||||
|
print "%0.2f" % sum(damage_map.cb_phial_damage[part][level]),
|
||||||
|
print
|
||||||
|
|
||||||
for name in args.weapon:
|
|
||||||
weapon = db.get_weapon_by_name(name)
|
|
||||||
if not weapon:
|
|
||||||
raise ValueError("Weapon '%s' not found" % name)
|
|
||||||
weapons.append(weapon)
|
|
||||||
|
|
||||||
if not weapons:
|
def _print_headers(parts, damage_map_base):
|
||||||
print "Err: no matching weapons"
|
print
|
||||||
sys.exit(1)
|
avg_hitbox = (sum(damage_map_base[part].hitbox for part in parts)
|
||||||
|
/ float(len(parts)))
|
||||||
|
cols = ["%s (%d)" % (part, damage_map_base[part].hitbox)
|
||||||
|
for part in parts]
|
||||||
|
cols = ["%s (%d)" % ("Avg", avg_hitbox)] + cols
|
||||||
|
print " | ".join(cols)
|
||||||
|
|
||||||
names = [w.name for w in weapons]
|
|
||||||
|
|
||||||
monster_breaks = db.get_monster_breaks(monster.id)
|
def print_sorted_damage(names, damage_map_base, weapon_damage_map, parts):
|
||||||
weapon_type = weapons[0]["wtype"]
|
def uniform_average(weapon):
|
||||||
motion = motiondb[weapon_type].average
|
return weapon_damage_map[weapon].averages["uniform"]
|
||||||
print "Weapon Type: %s" % weapon_type
|
|
||||||
print "Average Motion: %0.1f" % motion
|
|
||||||
print "Monster Breaks: %s" % ", ".join(monster_breaks)
|
|
||||||
skill_names = ["Sharpness +1" if args.sharpness_plus_one else "",
|
|
||||||
"Awaken" if args.awaken else "",
|
|
||||||
skills.AttackUp.name(args.attack_up),
|
|
||||||
skills.CriticalEye.name(args.critical_eye),
|
|
||||||
skills.ElementAttackUp.name(args.element_up)]
|
|
||||||
print "Skills:", ", ".join(skill for skill in skill_names if skill)
|
|
||||||
|
|
||||||
if args.parts:
|
names_sorted = list(names)
|
||||||
limit_parts = args.parts.split(",")
|
names_sorted.sort(key=uniform_average, reverse=True)
|
||||||
else:
|
|
||||||
limit_parts = None
|
|
||||||
|
|
||||||
weapon_damage_map = dict()
|
_print_headers(parts, damage_map_base)
|
||||||
for row in weapons:
|
|
||||||
name = row["name"]
|
|
||||||
row_type = row["wtype"]
|
|
||||||
if row_type != weapon_type:
|
|
||||||
raise ValueError("Weapon '%s' is different type" % name)
|
|
||||||
try:
|
|
||||||
wd = WeaponMonsterDamage(row,
|
|
||||||
monster, monster_damage,
|
|
||||||
motion, args.sharpness_plus_one,
|
|
||||||
monster_breaks,
|
|
||||||
attack_skill=args.attack_up,
|
|
||||||
critical_eye_skill=args.critical_eye,
|
|
||||||
element_skill=args.element_up,
|
|
||||||
awaken=args.awaken,
|
|
||||||
artillery_level=args.artillery,
|
|
||||||
limit_parts=args.parts)
|
|
||||||
print "%-20s: %4.0f %2.0f%%" % (name, wd.attack, wd.affinity),
|
|
||||||
if wd.etype:
|
|
||||||
if wd.etype2:
|
|
||||||
print "(%4.0f %s, %4.0f %s)" \
|
|
||||||
% (wd.eattack, wd.etype, wd.eattack2, wd.etype2),
|
|
||||||
else:
|
|
||||||
print "(%4.0f %s)" % (wd.eattack, wd.etype),
|
|
||||||
print SharpnessLevel.name(wd.sharpness)
|
|
||||||
weapon_damage_map[name] = wd
|
|
||||||
except ValueError as e:
|
|
||||||
print str(e)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
damage_map_base = weapon_damage_map[weapons[0].name]
|
#print
|
||||||
|
#print " | ".join(["%-15s" % "Avg"] + parts)
|
||||||
|
#print " | ".join([" "] + [str(damage_map_base[part].hitbox)
|
||||||
|
# for part in parts])
|
||||||
|
|
||||||
if limit_parts:
|
for name in names_sorted:
|
||||||
parts = limit_parts
|
print "%-20s:" % name,
|
||||||
else:
|
damage_map = weapon_damage_map[name]
|
||||||
parts = damage_map_base.parts
|
print "%0.2f" % damage_map.averages["uniform"],
|
||||||
|
for part in parts:
|
||||||
|
part_damage = damage_map[part]
|
||||||
|
print "%0.2f" % part_damage.total,
|
||||||
|
print
|
||||||
|
|
||||||
|
|
||||||
|
def print_damage_percent_diff(names, damage_map_base, weapon_damage_map, parts):
|
||||||
for part in parts:
|
for part in parts:
|
||||||
tdiffs = [percent_change(
|
tdiffs = [percent_change(
|
||||||
damage_map_base[part].total,
|
damage_map_base[part].total,
|
||||||
@@ -244,3 +229,114 @@ if __name__ == '__main__':
|
|||||||
diff_s = ",".join("%+0.1f%%" % i for i in diffs)
|
diff_s = ",".join("%+0.1f%%" % i for i in diffs)
|
||||||
|
|
||||||
print "%22s %0.2f (%s)" % (avg_type, base, diff_s)
|
print "%22s %0.2f (%s)" % (avg_type, base, diff_s)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
args = parse_args(None)
|
||||||
|
|
||||||
|
db = MHDB(_pathfix.db_path)
|
||||||
|
motiondb = MotionValueDB(_pathfix.motion_values_path)
|
||||||
|
|
||||||
|
monster = db.get_monster_by_name(args.monster)
|
||||||
|
if not monster:
|
||||||
|
raise ValueError("Monster '%s' not found" % args.monster)
|
||||||
|
monster_damage = db.get_monster_damage(monster.id)
|
||||||
|
|
||||||
|
weapons = []
|
||||||
|
weapon_type = None
|
||||||
|
names_set = set()
|
||||||
|
|
||||||
|
for name in args.weapon:
|
||||||
|
weapon = db.get_weapon_by_name(name)
|
||||||
|
if not weapon:
|
||||||
|
raise ValueError("Weapon '%s' not found" % name)
|
||||||
|
names_set.add(name)
|
||||||
|
weapons.append(weapon)
|
||||||
|
|
||||||
|
for match_tuple in args.match:
|
||||||
|
# TODO: better validation
|
||||||
|
wtype, element = match_tuple
|
||||||
|
match_weapons = db.get_weapons_by_query(wtype=wtype, element=element,
|
||||||
|
final=1)
|
||||||
|
for w in match_weapons:
|
||||||
|
# skip weapons already explicitly names in arg list.
|
||||||
|
# Especially useful in diff mode.
|
||||||
|
if w.name in names_set:
|
||||||
|
continue
|
||||||
|
weapons.append(w)
|
||||||
|
|
||||||
|
if not weapons:
|
||||||
|
print "Err: no matching weapons"
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
names = [w.name for w in weapons]
|
||||||
|
|
||||||
|
monster_breaks = db.get_monster_breaks(monster.id)
|
||||||
|
weapon_type = weapons[0]["wtype"]
|
||||||
|
if args.phial and weapon_type != "Charge Blade":
|
||||||
|
print "ERROR: phial option is only supported for Charge Blade"
|
||||||
|
sys.exit(1)
|
||||||
|
motion = motiondb[weapon_type].average
|
||||||
|
print "Weapon Type: %s" % weapon_type
|
||||||
|
print "Average Motion: %0.1f" % motion
|
||||||
|
print "Monster Breaks: %s" % ", ".join(monster_breaks)
|
||||||
|
skill_names = ["Sharpness +1" if args.sharpness_plus_one else "",
|
||||||
|
"Awaken" if args.awaken else "",
|
||||||
|
skills.AttackUp.name(args.attack_up),
|
||||||
|
skills.CriticalEye.name(args.critical_eye),
|
||||||
|
skills.ElementAttackUp.name(args.element_up)]
|
||||||
|
print "Skills:", ", ".join(skill for skill in skill_names if skill)
|
||||||
|
|
||||||
|
if args.parts:
|
||||||
|
limit_parts = args.parts.split(",")
|
||||||
|
else:
|
||||||
|
limit_parts = None
|
||||||
|
|
||||||
|
weapon_damage_map = dict()
|
||||||
|
for row in weapons:
|
||||||
|
name = row["name"]
|
||||||
|
row_type = row["wtype"]
|
||||||
|
if row_type != weapon_type:
|
||||||
|
raise ValueError("Weapon '%s' is different type" % name)
|
||||||
|
try:
|
||||||
|
wd = WeaponMonsterDamage(row,
|
||||||
|
monster, monster_damage,
|
||||||
|
motion, args.sharpness_plus_one,
|
||||||
|
monster_breaks,
|
||||||
|
attack_skill=args.attack_up,
|
||||||
|
critical_eye_skill=args.critical_eye,
|
||||||
|
element_skill=args.element_up,
|
||||||
|
awaken=args.awaken,
|
||||||
|
artillery_level=args.artillery,
|
||||||
|
limit_parts=args.parts,
|
||||||
|
frenzy_bonus=args.frenzy)
|
||||||
|
print "%-20s: %4.0f %2.0f%%" % (name, wd.attack, wd.affinity),
|
||||||
|
if wd.etype:
|
||||||
|
if wd.etype2:
|
||||||
|
print "(%4.0f %s, %4.0f %s)" \
|
||||||
|
% (wd.eattack, wd.etype, wd.eattack2, wd.etype2),
|
||||||
|
else:
|
||||||
|
print "(%4.0f %s)" % (wd.eattack, wd.etype),
|
||||||
|
print SharpnessLevel.name(wd.sharpness)
|
||||||
|
weapon_damage_map[name] = wd
|
||||||
|
except ValueError as e:
|
||||||
|
print str(e)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
damage_map_base = weapon_damage_map[names[0]]
|
||||||
|
|
||||||
|
if limit_parts:
|
||||||
|
parts = limit_parts
|
||||||
|
else:
|
||||||
|
parts = damage_map_base.parts
|
||||||
|
|
||||||
|
if args.diff:
|
||||||
|
print_damage_percent_diff(names, damage_map_base,
|
||||||
|
weapon_damage_map, parts)
|
||||||
|
elif args.phial:
|
||||||
|
print_sorted_phial_damage(names, damage_map_base,
|
||||||
|
weapon_damage_map, parts,
|
||||||
|
level=args.phial)
|
||||||
|
else:
|
||||||
|
print_sorted_damage(names, damage_map_base,
|
||||||
|
weapon_damage_map, parts)
|
||||||
|
|||||||
@@ -166,7 +166,8 @@ class WeaponMonsterDamage(object):
|
|||||||
attack_skill=skills.AttackUp.NONE,
|
attack_skill=skills.AttackUp.NONE,
|
||||||
critical_eye_skill=skills.CriticalEye.NONE,
|
critical_eye_skill=skills.CriticalEye.NONE,
|
||||||
element_skill=skills.ElementAttackUp.NONE,
|
element_skill=skills.ElementAttackUp.NONE,
|
||||||
awaken=False, artillery_level=0, limit_parts=None):
|
awaken=False, artillery_level=0, limit_parts=None,
|
||||||
|
frenzy_bonus=0):
|
||||||
self.weapon = weapon_row
|
self.weapon = weapon_row
|
||||||
self.monster = monster_row
|
self.monster = monster_row
|
||||||
self.monster_damage = monster_damage
|
self.monster_damage = monster_damage
|
||||||
@@ -179,6 +180,10 @@ class WeaponMonsterDamage(object):
|
|||||||
self.awaken = awaken
|
self.awaken = awaken
|
||||||
self.artillery_level = artillery_level
|
self.artillery_level = artillery_level
|
||||||
self.limit_parts = limit_parts
|
self.limit_parts = limit_parts
|
||||||
|
# 15 normaly for overcoming the virus, 30 with frenzy res skill
|
||||||
|
assert frenzy_bonus in (0, 15, 30)
|
||||||
|
self.frenzy_bonus = frenzy_bonus
|
||||||
|
self.chaotic = False
|
||||||
|
|
||||||
self.damage_map = defaultdict(PartDamage)
|
self.damage_map = defaultdict(PartDamage)
|
||||||
self.average = 0
|
self.average = 0
|
||||||
@@ -198,13 +203,22 @@ class WeaponMonsterDamage(object):
|
|||||||
self.sharpness = self.weapon.sharpness.max
|
self.sharpness = self.weapon.sharpness.max
|
||||||
#print "sharpness=", self.sharpness
|
#print "sharpness=", self.sharpness
|
||||||
if self.weapon["affinity"]:
|
if self.weapon["affinity"]:
|
||||||
# handle chaotic gore affinity - use average, which is
|
if "/" in self.weapon["affinity"]:
|
||||||
# probably not quite right but at least allows an initial
|
self.chaotic = True
|
||||||
# comparison point
|
# Handle chaotic gore affinity, e.g. -35/10. This means that
|
||||||
parts = [int(x) for x in self.weapon["affinity"].split("/")]
|
# 35% of the time it does a negative critical (75% damage)
|
||||||
self.affinity = sum(parts)/len(parts)
|
# and 10% of the time does a positive critical (125%
|
||||||
|
# damage). If frenzied (overcome virus which lasts 45
|
||||||
|
# seconds), the negative affinity becomes positive
|
||||||
|
# instead (35 + 10 = 45 in the example).
|
||||||
|
self.affinity = sum(
|
||||||
|
abs(int(x)) if self.frenzy_bonus else int(x)
|
||||||
|
for x in self.weapon["affinity"].split("/"))
|
||||||
|
else:
|
||||||
|
self.affinity = int(self.weapon["affinity"])
|
||||||
else:
|
else:
|
||||||
self.affinity = 0
|
self.affinity = 0
|
||||||
|
self.affinity += self.frenzy_bonus
|
||||||
self.damage_type = WeaponType.damage_type(self.weapon_type)
|
self.damage_type = WeaponType.damage_type(self.weapon_type)
|
||||||
self.etype = self.weapon["element"]
|
self.etype = self.weapon["element"]
|
||||||
self.eattack = self.weapon["element_attack"]
|
self.eattack = self.weapon["element_attack"]
|
||||||
@@ -305,7 +319,13 @@ class WeaponMonsterDamage(object):
|
|||||||
self.max_raw_part = (part, hitbox)
|
self.max_raw_part = (part, hitbox)
|
||||||
if ehitbox > self.max_element_part[1]:
|
if ehitbox > self.max_element_part[1]:
|
||||||
self.max_element_part = (part, ehitbox)
|
self.max_element_part = (part, ehitbox)
|
||||||
for d in self.damage_map.values():
|
|
||||||
|
for part in self.damage_map.keys():
|
||||||
|
if None not in self.damage_map[part].states:
|
||||||
|
#print "Failed to parse part:", part
|
||||||
|
del self.damage_map[part]
|
||||||
|
|
||||||
|
for part, d in self.damage_map.iteritems():
|
||||||
if d.is_breakable():
|
if d.is_breakable():
|
||||||
self.break_count += 1
|
self.break_count += 1
|
||||||
self.parts = self.damage_map.keys()
|
self.parts = self.damage_map.keys()
|
||||||
|
|||||||
Reference in New Issue
Block a user