From f33dcf59855ddfdb96b36299499849c7bccc27ad Mon Sep 17 00:00:00 2001 From: Bryce Allen Date: Sat, 27 Feb 2016 16:31:49 -0600 Subject: [PATCH] add mhx damage --- bin/mhdamage.py | 37 ++++++++++++++++++---------- mhapi/damage.py | 33 ++++++++++++++++++++----- mhapi/db.py | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ mhapi/model.py | 39 ++++++++++++++++++++++------- 4 files changed, 146 insertions(+), 28 deletions(-) diff --git a/bin/mhdamage.py b/bin/mhdamage.py index 42c15e2..1f45c9d 100755 --- a/bin/mhdamage.py +++ b/bin/mhdamage.py @@ -7,8 +7,8 @@ import copy import _pathfix -from mhapi.db import MHDB -from mhapi.damage import MotionValueDB, WeaponMonsterDamage, WeaponType +from mhapi.db import MHDB, MHDBX +from mhapi.damage import MotionValueDB, WeaponMonsterDamage from mhapi.model import SharpnessLevel, Weapon from mhapi import skills from mhapi.util import ELEMENTS, WEAPON_TYPES, WTYPE_ABBR @@ -31,30 +31,29 @@ def weapon_match_tuple(arg): def _make_db_sharpness_string(level_string): - print "level string", level_string + #print "level string", level_string level_value = SharpnessLevel.__dict__[level_string.upper()] - print "level value", level_value + #print "level value", level_value values = [] for i in xrange(SharpnessLevel.PURPLE+1): if i <= level_value: values.append("1") else: values.append("0") - print "sharp values %r" % values + #print "sharp values %r" % values return " ".join([".".join(values)] * 2) def weapon_stats_tuple(arg): parts = arg.split(",") - print "parts %r" % parts + #print "parts %r" % parts if len(parts) < 4: print "not enough parts" raise ValueError("Bad arg, use 'name,weapon_type,sharpness,raw'") weapon = {} weapon["name"] = parts[0] weapon["wtype"] = get_wtype_match(parts[1]) - multiplier = WeaponType.multiplier(weapon["wtype"]) - weapon["attack"] = multiplier * int(parts[2]) + weapon["attack"] = int(parts[2]) weapon["affinity"] = parts[3] weapon["sharpness"] = _make_db_sharpness_string(parts[4]) if len(parts) == 5: @@ -64,13 +63,13 @@ def weapon_stats_tuple(arg): weapon["element"] = get_element_match(parts[5]) weapon["element_attack"] = int(parts[6]) else: - print "bad part number" + #print "bad part number" raise ValueError("Bad arg, use 'name,weapon_type,sharpness,raw'") weapon["element_2"] = None weapon["awaken"] = None weapon["element_2_attack"] = None weapon["_id"] = -1 - print "making model" + #print "making model" return Weapon(weapon) @@ -118,7 +117,8 @@ def get_skill_names(args): "Awaken" if args.awaken else "", skills.AttackUp.name(args.attack_up), skills.CriticalEye.name(args.critical_eye), - skills.ElementAttackUp.name(args.element_up)] + skills.ElementAttackUp.name(args.element_up), + "Blunt Power" if args.blunt_power else ""] def percent_change(a, b): @@ -151,6 +151,9 @@ def _add_skill_args(parser): 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("-b", "--blunt-power", action="store_true", + default=False, + help="Blunt Power (MHX), default off") def parse_args(argv): @@ -172,6 +175,9 @@ def parse_args(argv): 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("-x", "--monster-hunter-cross", action="store_true", + default=False, + help="Assume weapons are true attack, use MHX values") parser.add_argument("-m", "--match", nargs="*", help="WEAPON_TYPE,ELEMENT_OR_STATUS_OR_RAW" +" Include all matching weapons in their final form." @@ -318,7 +324,10 @@ def print_damage_percent_diff(names, damage_map_base, weapon_damage_map, parts): if __name__ == '__main__': args = parse_args(None) - db = MHDB(_pathfix.db_path) + if args.monster_hunter_cross: + db = MHDBX() + else: + db = MHDB() motiondb = MotionValueDB(_pathfix.motion_values_path) monster = db.get_monster_by_name(args.monster) @@ -399,7 +408,9 @@ if __name__ == '__main__': awaken=skill_args.awaken, artillery_level=skill_args.artillery, limit_parts=args.parts, - frenzy_bonus=skill_args.frenzy) + frenzy_bonus=skill_args.frenzy, + is_true_attack=args.monster_hunter_cross, + blunt_power=skill_args.blunt_power) print "%-20s: %4.0f %2.0f%%" % (name, wd.attack, wd.affinity), if wd.etype: if wd.etype2: diff --git a/mhapi/damage.py b/mhapi/damage.py index 8811fd6..560e387 100644 --- a/mhapi/damage.py +++ b/mhapi/damage.py @@ -28,13 +28,13 @@ def raw_damage(true_raw, sharpness, affinity, monster_hitbox, motion): * monster_hitbox / 100.0) -def element_damage(element, sharpness, monster_ehitbox): +def element_damage(raw_element, sharpness, monster_ehitbox): """ Calculate elemental damage to a monster part with the given elemental attack, the given sharpness, and the given monster elemental weakness. Note that this is independent of the motion value of the attack. """ - return floor(element / 10.0 + return floor(raw_element * SharpnessLevel.element_modifier(sharpness) * monster_ehitbox / 100.0) @@ -171,7 +171,7 @@ class WeaponMonsterDamage(object): critical_eye_skill=skills.CriticalEye.NONE, element_skill=skills.ElementAttackUp.NONE, awaken=False, artillery_level=0, limit_parts=None, - frenzy_bonus=0): + frenzy_bonus=0, blunt_power=False, is_true_attack=False): self.weapon = weapon_row self.monster = monster_row self.monster_damage = monster_damage @@ -183,6 +183,8 @@ class WeaponMonsterDamage(object): self.element_skill = element_skill self.awaken = awaken self.artillery_level = artillery_level + self.blunt_power = blunt_power + self.is_true_attack = is_true_attack self.limit_parts = limit_parts # 15 normaly for overcoming the virus, 30 with frenzy res skill assert frenzy_bonus in (0, 15, 30) @@ -199,15 +201,19 @@ class WeaponMonsterDamage(object): self.cb_phial_damage = defaultdict(dict) self.weapon_type = self.weapon["wtype"] - self.true_raw = (self.weapon["attack"] - / WeaponType.multiplier(self.weapon_type)) + if is_true_attack: + self.true_raw = self.weapon["attack"] + else: + self.true_raw = (self.weapon["attack"] + / WeaponType.multiplier(self.weapon_type)) if sharp_plus: self.sharpness = self.weapon.sharpness_plus.max else: self.sharpness = self.weapon.sharpness.max #print "sharpness=", self.sharpness if self.weapon["affinity"]: - if "/" in self.weapon["affinity"]: + if (isinstance(self.weapon["affinity"], str) + and "/" 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) @@ -241,6 +247,11 @@ class WeaponMonsterDamage(object): else: self.eattack2 = 0 + if not self.is_true_attack and self.eattack: + self.eattack /= 10 + if self.eattack2: + self.eattack2 /= 10 + self.true_raw = skills.AttackUp.modified(attack_skill, self.true_raw) self.affinity = skills.CriticalEye.modified(critical_eye_skill, @@ -250,6 +261,14 @@ class WeaponMonsterDamage(object): self.eattack2 = skills.ElementAttackUp.modified(element_skill, self.eattack2) + if self.blunt_power: + if self.sharpness in (SharpnessLevel.RED, SharpnessLevel.ORANGE): + self.true_raw += 30 + elif self.sharpness == SharpnessLevel.YELLOW: + self.true_raw += 25 + elif self.sharpness == SharpnessLevel.GREEN: + self.true_raw += 15 + self.parts = [] self.break_count = 0 @@ -266,6 +285,8 @@ class WeaponMonsterDamage(object): @property def attack(self): + if self.is_true_attack: + return self.true_raw return self.true_raw * WeaponType.multiplier(self.weapon_type) def _calculate_damage(self): diff --git a/mhapi/db.py b/mhapi/db.py index 4fca2e4..93722f3 100644 --- a/mhapi/db.py +++ b/mhapi/db.py @@ -4,6 +4,7 @@ Module for accessing the sqlite monster hunter db from import os.path import sqlite3 +import json from mhapi import model @@ -528,3 +529,67 @@ class MHDB(object): else: ucomps = None item_data.set_components(ccomps, ucomps) + + + +class MHDBX(object): + """ + Wrapper around Monster Hunter Cross (X) JSON data. Attempts limited + compatibility with original 4U MHDB class. + + Uses MHDB object, as temporariy hack for MHX data that is not yet + available or integrated. + """ + def __init__(self): + """ + Loads JSON data, keeps in memory. + """ + module_path = os.path.dirname(__file__) + mhx_db_path = os.path.abspath(os.path.join(module_path, "..", + "db", "mhx")) + + self._4udb = MHDB() + self._weapon_list = [] + self._weapons_by_name = {} + with open(os.path.join(mhx_db_path, "weapon_list.json")) as f: + wlist = json.load(f) + for i, wdata in enumerate(wlist): + wdata["_id"] = i + weapon = model.Weapon(wdata) + self._weapon_list.append(weapon) + self._weapons_by_name[weapon.name_jp] = weapon + + def get_weapon_by_name(self, name): + return self._weapons_by_name.get(name) + + def get_monster_by_name(self, *args, **kwargs): + return self._4udb.get_monster_by_name(*args, **kwargs) + + def get_monster_damage(self, *args, **kwargs): + return self._4udb.get_monster_damage(*args, **kwargs) + + def get_monster_breaks(self, *args, **kwargs): + return self._4udb.get_monster_breaks(*args, **kwargs) + + def get_weapons_by_query(self, wtype=None, element=None, + final=None): + """ + @element can have the special value 'Raw' to search for weapons + with no element. Otherwise @element is searched for in both + awaken and native, and can be a status or an element. + + @final should be string '1' or '0' + """ + final = int(final) + results = [] + for w in self._weapon_list: + if wtype is not None and w.wtype != wtype: + continue + if (element is not None + and element not in (w.element, w.element_2) + and not (element == "Raw" and not w.element)): + continue + if final is not None and w.final != final: + continue + results.append(w) + return results diff --git a/mhapi/model.py b/mhapi/model.py index 7fefb39..a4c7da5 100644 --- a/mhapi/model.py +++ b/mhapi/model.py @@ -162,6 +162,16 @@ class SharpnessLevel(EnumBase): PURPLE: (1.44, 1.20), } + _modifier_mhx = { + RED: (0.50, 0.25), + ORANGE: (0.75, 0.50), + YELLOW: (1.00, 0.75), + GREEN: (1.05, 1.00), + BLUE: (1.20, 1.0625), + WHITE: (1.32, 1.125), + } + + @classmethod def raw_modifier(cls, sharpness): return cls._modifier[sharpness][0] @@ -177,8 +187,11 @@ class WeaponSharpness(ModelBase): points at each level. E.g. the 0th item in the list is the amount of RED sharpness, the 1st item is ORANGE, etc. """ - def __init__(self, db_string): - self.value_list = [int(s) for s in db_string.split(".")] + def __init__(self, db_string_or_list): + if isinstance(db_string_or_list, list): + self.value_list = db_string_or_list + else: + self.value_list = [int(s) for s in db_string_or_list.split(".")] self._max = None @property @@ -364,13 +377,21 @@ class Weapon(ItemCraftable): if self.wtype in ("Light Bowgun", "Heavy Bowgun", "Bow"): self._data["sharpness"] = self._data["sharpness_plus"] = None return - parts = self._row["sharpness"].split(" ") - if len(parts) != 2: - raise ValueError("Bad sharpness value in db: '%s'" - % self._row["sharpness"]) - normal, plus = parts - self._data["sharpness"] = WeaponSharpness(normal) - self._data["sharpness_plus"] = WeaponSharpness(plus) + if isinstance(self._row["sharpness"], list): + # MHX JSON data, already desired format, but doesn't have + # purple so we append 0 + self.sharpness = WeaponSharpness(self._row["sharpness"] + [0]) + self.sharpness_plus = WeaponSharpness( + self._row["sharpness_plus"] + [0]) + else: + # 4U data from db + parts = self._row["sharpness"].split(" ") + if len(parts) != 2: + raise ValueError("Bad sharpness value in db: '%s'" + % self._row["sharpness"]) + normal, plus = parts + self._data["sharpness"] = WeaponSharpness(normal) + self._data["sharpness_plus"] = WeaponSharpness(plus) def is_not_localized(self): return (self.name == self.name_jp)