diff --git a/bin/mhdamage.py b/bin/mhdamage.py index fc7bc7c..93ea6b4 100755 --- a/bin/mhdamage.py +++ b/bin/mhdamage.py @@ -67,7 +67,7 @@ def parse_args(argv): help="add Sharpness +1 skill, default off") parser.add_argument("-f", "--awaken", action="store_true", default=False, - help="add Awaken (FreeElement), default off") + help="add Awaken (FreeElemnt), default off") parser.add_argument("-a", "--attack-up", type=int, choices=range(0, 5), default=0, help="1-4 for AuS, M, L, XL") @@ -81,9 +81,21 @@ def parse_args(argv): parser.add_argument("-t", "--artillery", type=int, choices=[0,1,2], default=0, 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", help="Limit analysis to specified parts" +" (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="*", help="WEAPON_TYPE,ELEMENT_OR_STATUS_OR_RAW" +" Include all matching weapons in their final form." @@ -104,6 +116,121 @@ def parse_args(argv): return parser.parse_args(argv) +def print_sorted_phial_damage(names, damage_map_base, weapon_damage_map, parts, + level): + def cb_levelN(weapon): + return avg_phial(weapon_damage_map[weapon], level=level) + + def avg_phial(wd, level=5): + total = 0.0 + for part in parts: + total += sum(wd.cb_phial_damage[part][level]) + return total / len(parts) + + names_sorted = list(names) + names_sorted.sort(key=cb_levelN, reverse=True) + + _print_headers(parts, damage_map_base) + + for name in names_sorted: + print "%-20s:" % name, + damage_map = weapon_damage_map[name] + print "%0.2f" % avg_phial(damage_map, level=level), + for part in parts: + part_damage = damage_map[part] + print "%0.2f" % sum(damage_map.cb_phial_damage[part][level]), + print + + +def _print_headers(parts, damage_map_base): + print + 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) + + +def print_sorted_damage(names, damage_map_base, weapon_damage_map, parts): + def uniform_average(weapon): + return weapon_damage_map[weapon].averages["uniform"] + + names_sorted = list(names) + names_sorted.sort(key=uniform_average, reverse=True) + + _print_headers(parts, damage_map_base) + + #print + #print " | ".join(["%-15s" % "Avg"] + parts) + #print " | ".join([" "] + [str(damage_map_base[part].hitbox) + # for part in parts]) + + for name in names_sorted: + print "%-20s:" % name, + damage_map = weapon_damage_map[name] + 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: + tdiffs = [percent_change( + damage_map_base[part].total, + weapon_damage_map[w][part].total + ) + for w in names[1:]] + ediffs = [percent_change( + damage_map_base[part].element, + weapon_damage_map[w][part].element + ) + for w in names[1:]] + bdiffs = [percent_change( + damage_map_base[part].break_diff(), + weapon_damage_map[w][part].break_diff() + ) + for w in names[1:]] + tdiff_s = ",".join("%+0.1f%%" % i for i in tdiffs) + ediff_s = ",".join("%+0.1f%%" % i for i in ediffs) + bdiff_s = ",".join("%+0.1f%%" % i for i in bdiffs) + damage = damage_map_base[part] + print "%22s%s h%02d %0.2f (%s) h%02d %0.2f (%s) %+0.2f (%s)" \ + % (part, "*" if damage.is_breakable() else " ", + damage.hitbox, + damage.total, + tdiff_s, + damage.ehitbox, + damage.element, + ediff_s, + damage.break_diff(), + bdiff_s) + if weapon_type == "Charge Blade": + for level in (0, 1, 2, 3, 5): + print " " * 20, level, + for wname in names: + wd = weapon_damage_map[wname] + damage = wd.cb_phial_damage[part][level] + print "(%0.f, %0.f, %0.f);" % damage, + print + + print " --------------------" + + for avg_type in "uniform raw weakpart_raw element weakpart_element break_raw break_element break_only".split(): + base = damage_map_base.averages[avg_type] + diffs = [percent_change( + base, + weapon_damage_map[w].averages[avg_type] + ) + for w in names[1:]] + + diff_s = ",".join("%+0.1f%%" % i for i in diffs) + + print "%22s %0.2f (%s)" % (avg_type, base, diff_s) + + if __name__ == '__main__': args = parse_args(None) @@ -117,20 +244,27 @@ if __name__ == '__main__': weapons = [] weapon_type = None - - 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) - weapons.extend(match_weapons) + 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) @@ -139,6 +273,9 @@ if __name__ == '__main__': 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 @@ -171,7 +308,8 @@ if __name__ == '__main__': element_skill=args.element_up, awaken=args.awaken, artillery_level=args.artillery, - limit_parts=args.parts) + 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: @@ -185,62 +323,20 @@ if __name__ == '__main__': print str(e) sys.exit(1) - damage_map_base = weapon_damage_map[weapons[0].name] + damage_map_base = weapon_damage_map[names[0]] if limit_parts: parts = limit_parts else: parts = damage_map_base.parts - for part in parts: - tdiffs = [percent_change( - damage_map_base[part].total, - weapon_damage_map[w][part].total - ) - for w in names[1:]] - ediffs = [percent_change( - damage_map_base[part].element, - weapon_damage_map[w][part].element - ) - for w in names[1:]] - bdiffs = [percent_change( - damage_map_base[part].break_diff(), - weapon_damage_map[w][part].break_diff() - ) - for w in names[1:]] - tdiff_s = ",".join("%+0.1f%%" % i for i in tdiffs) - ediff_s = ",".join("%+0.1f%%" % i for i in ediffs) - bdiff_s = ",".join("%+0.1f%%" % i for i in bdiffs) - damage = damage_map_base[part] - print "%22s%s h%02d %0.2f (%s) h%02d %0.2f (%s) %+0.2f (%s)" \ - % (part, "*" if damage.is_breakable() else " ", - damage.hitbox, - damage.total, - tdiff_s, - damage.ehitbox, - damage.element, - ediff_s, - damage.break_diff(), - bdiff_s) - if weapon_type == "Charge Blade": - for level in (0, 1, 2, 3, 5): - print " " * 20, level, - for wname in names: - wd = weapon_damage_map[wname] - damage = wd.cb_phial_damage[part][level] - print "(%0.f, %0.f, %0.f);" % damage, - print - - print " --------------------" - - for avg_type in "uniform raw weakpart_raw element weakpart_element break_raw break_element break_only".split(): - base = damage_map_base.averages[avg_type] - diffs = [percent_change( - base, - weapon_damage_map[w].averages[avg_type] - ) - for w in names[1:]] - - diff_s = ",".join("%+0.1f%%" % i for i in diffs) - - print "%22s %0.2f (%s)" % (avg_type, base, diff_s) + 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) diff --git a/mhapi/damage.py b/mhapi/damage.py index c692b8b..14fcaa1 100644 --- a/mhapi/damage.py +++ b/mhapi/damage.py @@ -166,7 +166,8 @@ class WeaponMonsterDamage(object): attack_skill=skills.AttackUp.NONE, critical_eye_skill=skills.CriticalEye.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.monster = monster_row self.monster_damage = monster_damage @@ -179,6 +180,10 @@ class WeaponMonsterDamage(object): self.awaken = awaken self.artillery_level = artillery_level 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.average = 0 @@ -198,13 +203,22 @@ class WeaponMonsterDamage(object): self.sharpness = self.weapon.sharpness.max #print "sharpness=", self.sharpness if self.weapon["affinity"]: - # handle chaotic gore affinity - use average, which is - # probably not quite right but at least allows an initial - # comparison point - parts = [int(x) for x in self.weapon["affinity"].split("/")] - self.affinity = sum(parts)/len(parts) + if "/" in self.weapon["affinity"]: + self.chaotic = True + # Handle chaotic gore affinity, e.g. -35/10. This means that + # 35% of the time it does a negative critical (75% damage) + # 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: self.affinity = 0 + self.affinity += self.frenzy_bonus self.damage_type = WeaponType.damage_type(self.weapon_type) self.etype = self.weapon["element"] self.eattack = self.weapon["element_attack"] @@ -305,7 +319,13 @@ class WeaponMonsterDamage(object): self.max_raw_part = (part, hitbox) if ehitbox > self.max_element_part[1]: 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(): self.break_count += 1 self.parts = self.damage_map.keys()