From 4e562ed537ee62635bba97179a516aa23db01786 Mon Sep 17 00:00:00 2001 From: Bryce Allen Date: Wed, 22 Apr 2015 22:29:40 -0500 Subject: [PATCH] model and db refactor, damage improvements --- bin/genrewards.py | 8 +- bin/mhdamage.py | 87 ++++++++++---- db/motion_values.json | 1 + mhapi/damage.py | 88 ++++++--------- mhapi/db.py | 250 ++++++++++++++++++++++++++-------------- mhapi/model.py | 257 +++++++++++++++++++++++++++++++++++++++--- mhapi/rewards.py | 11 +- mhapi/skills.py | 102 +++++++++++++++-- mhapi/util.py | 11 ++ 9 files changed, 616 insertions(+), 199 deletions(-) create mode 100644 db/motion_values.json create mode 100644 mhapi/util.py diff --git a/bin/genrewards.py b/bin/genrewards.py index 0806321..57d2757 100755 --- a/bin/genrewards.py +++ b/bin/genrewards.py @@ -33,7 +33,7 @@ if __name__ == '__main__': db_path = os.path.join(db_path, "..", "db", "mh4u.db") db = MHDB(db_path) - items = db.get_item_names(rewards.ITEM_TYPES) + items = db.get_items(rewards.ITEM_TYPES) # write all names json to /items.json items_file = os.path.join(outdir, "items.json") @@ -48,13 +48,13 @@ if __name__ == '__main__': else: out.write(", ") out.write('"') - out.write(item["name"]) + out.write(item.name) out.write('"') out.write("]") for item in items: - name = item["name"] - item_id = item["_id"] + name = item.name + item_id = item.id encoded_name = name.encode("utf8") item_file = os.path.join(outdir, encoded_name + ".txt") print "Writing", item_id, item_file diff --git a/bin/mhdamage.py b/bin/mhdamage.py index 1301107..1570725 100755 --- a/bin/mhdamage.py +++ b/bin/mhdamage.py @@ -1,12 +1,14 @@ #!/usr/bin/env python import sys -import os +import argparse import _pathfix from mhapi.db import MHDB from mhapi.damage import MotionValueDB, WeaponMonsterDamage +from mhapi.model import SharpnessLevel +from mhapi import skills def percent_change(a, b): @@ -15,50 +17,89 @@ def percent_change(a, b): return (100.0 * (b-a) / a) -if __name__ == '__main__': - if len(sys.argv) < 4: - print "Usage: %s 'monster name' 'weapon name'+" % sys.argv[0] - sys.exit(os.EX_USAGE) +def parse_args(argv): + parser = argparse.ArgumentParser( + "Calculate damage to monster from different weapons of the" + " same class" + ) + parser.add_argument("-s", "--sharpness-plus-one", action="store_true", + default=False, + help="add Sharpness +1 skill, default off") + parser.add_argument("-f", "--awaken", action="store_true", + default=False, + help="add Awaken (FreeElement), default off") + parser.add_argument("-a", "--attack-up", + type=int, choices=xrange(0, 5), default=0, + help="1-4 for AuS, M, L, XL") + parser.add_argument("-c", "--critical-eye", + type=int, choices=xrange(0, 5), default=0, + help="1-4 for CE+1, +2, +3 and Critical God") + parser.add_argument("-e", "--element-up", + type=int, choices=xrange(0, 5), default=0, + help="1-4 for (element) Atk +1, +2, +3 and" + " Element Attack Up") + parser.add_argument("monster", + help="Full name of monster") + parser.add_argument("weapon", nargs="+", + help="One or more weapons of same class to compare," + " full names") + + return parser.parse_args(argv) + - sharp_plus = bool(int(sys.argv[1])) - monster_name = sys.argv[2] - weapon_names = sys.argv[3:] +if __name__ == '__main__': + args = parse_args(None) db = MHDB(_pathfix.db_path) motiondb = MotionValueDB(_pathfix.motion_values_path) - monster = db.get_monster_by_name(monster_name) + monster = db.get_monster_by_name(args.monster) if not monster: - raise ValueError("Monster '%s' not found" % monster_name) - monster_damage = db.get_monster_damage(monster["_id"]) + raise ValueError("Monster '%s' not found" % args.monster) + monster_damage = db.get_monster_damage(monster.id) weapons = [] - for name in weapon_names: + 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) - monster_breaks = db.get_monster_breaks(monster["_id"]) + monster_breaks = db.get_monster_breaks(monster.id) weapon_type = weapons[0]["wtype"] 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) weapon_damage_map = dict() - for name, row in zip(weapon_names, weapons): + for name, row in zip(args.weapon, weapons): row_type = row["wtype"] if row_type != weapon_type: raise ValueError("Weapon '%s' is different type" % name) try: - weapon_damage_map[name] = WeaponMonsterDamage(row, - monster, monster_damage, - motion, sharp_plus, - monster_breaks) + 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) + print "%-20s: %4.0f %2.0f%%" % (name, wd.attack, wd.affinity), + if wd.etype: + 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[weapon_names[0]] + damage_map_base = weapon_damage_map[args.weapon[0]] parts = damage_map_base.parts for part in parts: @@ -66,17 +107,17 @@ if __name__ == '__main__': damage_map_base[part].total, weapon_damage_map[w][part].total ) - for w in weapon_names[1:]] + for w in args.weapon[1:]] ediffs = [percent_change( damage_map_base[part].element, weapon_damage_map[w][part].element ) - for w in weapon_names[1:]] + for w in args.weapon[1:]] bdiffs = [percent_change( damage_map_base[part].break_diff(), weapon_damage_map[w][part].break_diff() ) - for w in weapon_names[1:]] + for w in args.weapon[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) @@ -100,7 +141,7 @@ if __name__ == '__main__': base, weapon_damage_map[w].averages[avg_type] ) - for w in weapon_names[1:]] + for w in args.weapon[1:]] diff_s = ",".join("%+0.1f%%" % i for i in diffs) diff --git a/db/motion_values.json b/db/motion_values.json new file mode 100644 index 0000000..b74c792 --- /dev/null +++ b/db/motion_values.json @@ -0,0 +1 @@ +[{"id": 1, "motions": [{"type": [0], "name": "Overhead Slash", "power": [48]}, {"type": [0], "name": "Rising Slash", "power": [46]}, {"type": [0], "name": "Side Slash", "power": [36]}, {"type": [0], "name": "Side Slap", "power": [18]}, {"type": [0], "name": "Side Slap (Poise)", "power": [18]}, {"type": [0], "name": "Overhead Slash (Poise)", "power": [48]}, {"type": [0], "name": "Overhead Slash (Lv 1) (Poise)", "power": [65]}, {"type": [0], "name": "Overhead Slash (Lv 2) (Poise)", "power": [77]}, {"type": [0], "name": "Overhead Slash (Lv 3) (Poise)", "power": [110]}, {"type": [0], "name": "Overhead Slash (After Lv 3) (Poise)", "power": [77]}, {"type": [0], "name": "Strong Charged Slash (Poise)", "power": [52]}, {"type": [0], "name": "Strong Charged Slash (Lv 1) (Poise)", "power": [70]}, {"type": [0], "name": "Strong Charged Slash (Lv 2) (Poise)", "power": [85]}, {"type": [0], "name": "Strong Charged Slash (Lv 3) (Poise)", "power": [115]}, {"type": [0], "name": "Strong Swiping Slash (Poise)", "power": [48]}, {"type": [0], "name": "Strong Swiping Slash (Lv 1) (Poise)", "power": [52]}, {"type": [0], "name": "Strong Swiping Slash (Lv 2) (Poise)", "power": [66]}, {"type": [0], "name": "Strong Swiping Slash (Lv 3) (Poise)", "power": [110]}, {"type": [0], "name": "Unsheathe Attack (Poise)", "power": [48]}], "name": "Great Sword"}, {"id": 2, "motions": [{"type": [0], "name": "Step Slash", "power": [26]}, {"type": [0], "name": "Aerial Attack 1", "power": [30]}, {"type": [0], "name": "Aerial Attack 1 (Spirit Gauge Empty)", "power": [16]}, {"type": [0, 0], "name": "Aerial Attack 2", "power": [12, 36]}, {"type": [0, 0], "name": "Aerial Attack 2 (Spirit Gauge Empty)", "power": [4, 16]}, {"type": [0], "name": "Step In Attack", "power": [30]}, {"type": [0], "name": "Step In Attack (Spirit Gauge Empty)", "power": [18]}, {"type": [0], "name": "Aerial Attack", "power": [26]}, {"type": [0], "name": "Overhead Slash", "power": [23]}, {"type": [0], "name": "Thrust", "power": [14]}, {"type": [0], "name": "Thrust (After Spirit Blade)", "power": [14]}, {"type": [0], "name": "Rising Slash", "power": [18]}, {"type": [0], "name": "Rising Slash (After Evade)", "power": [15]}, {"type": [0], "name": "Fade Slash", "power": [24]}, {"type": [0], "name": "Side Fade Slash", "power": [24]}, {"type": [0], "name": "Spirit Blade 1", "power": [28]}, {"type": [0], "name": "Spirit Blade 1 (Spirit Gauge Empty)", "power": [16]}, {"type": [0], "name": "Spirit Blade 2", "power": [30]}, {"type": [0, 0, 0], "name": "Spirit Blade 3", "power": [12, 14, 34]}, {"type": [0], "name": "Spirit Roundslash", "power": [42]}], "name": "Long Sword"}, {"id": 3, "motions": [{"type": [0], "name": "Forward Slash", "power": [18]}, {"type": [0], "name": "Rising Slash", "power": [14]}, {"type": [0], "name": "Chop", "power": [13]}, {"type": [1, 0], "name": "Sword/Shield Combo", "power": [10, 20]}, {"type": [0], "name": "Roundslash", "power": [24]}, {"type": [0], "name": "Side Slash", "power": [14]}, {"type": [0], "name": "Lateral Slash", "power": [21]}, {"type": [0], "name": "Return Stroke", "power": [19]}, {"type": [1], "name": "Shield Attack", "power": [8]}, {"type": [1], "name": "Shield Bash", "power": [16]}, {"type": [1, 0], "name": "Charged Slash", "power": [20, 37]}, {"type": [0], "name": "Guard Slash", "power": [14]}, {"type": [0], "name": "Jump Forward Slash", "power": [20]}, {"type": [0], "name": "Jumping Slash", "power": [20]}, {"type": [0], "name": "Jumping Rising Slash", "power": [18]}], "name": "Sword"}, {"id": 4, "motions": [{"type": [0, 0, 0, 0], "name": "Unsheathe Attack", "power": [7, 7, 7, 7], "element": [0, 1, 0, 1]}, {"type": [0, 0, 0, 0, 0, 0], "name": "Spinning Blade Dance", "power": [7, 7, 7, 7, 10, 10], "element": [0, 1, 0, 1, 0, 1]}, {"type": [0], "name": "Rising Slash", "power": [18], "element": [0]}, {"type": [0, 0], "name": "Double Slash", "power": [10, 13], "element": [0, 1]}, {"type": [0, 0], "name": "Double Slash Return Stroke", "power": [10, 12], "element": [0, 1]}, {"type": [0, 0, 0], "name": "Circle Slash", "power": [12, 14, 14], "element": [0, 0, 1]}, {"type": [0, 0], "name": "Roundslash", "power": [7, 10], "element": [0, 1]}, {"type": [0, 0, 0], "name": "Jumping Slash (Right)", "power": [16, 6, 8], "element": [0, 1, 0]}, {"type": [0, 0, 0, 0, 0, 0], "name": "Demon Mode: Jumping Slashes (Right)", "power": [18, 6, 10, 16, 6, 8], "element": [0, 1, 0, 1, 0, 1]}, {"type": [0, 0], "name": "Jumping Slash (Left)", "power": [9, 12], "element": [0, 1]}, {"type": [0, 0, 0], "name": "Jumping Slash (Left) (Poise)", "power": [16, 6, 8], "element": [0, 1, 0]}, {"type": [0, 0, 0, 0, 0, 0], "name": "Demon Mode: Jumping Slashes (Left)", "power": [18, 6, 10, 16, 6, 8], "element": [0, 1, 0, 1, 0, 1]}, {"type": [0, 0, 0, 0, 0, 0, 0, 0], "name": "Demon Flurry", "power": [8, 8, 6, 8, 8, 6, 20, 20], "element": [0, 1, 0, 1, 0, 1, 0, 1]}, {"type": [0, 0], "name": "Double Slash (Aerial)", "power": [10, 13], "element": [0, 1]}, {"type": [0, 0, 0, 0], "name": "Aerial Spinning Blade Dance", "power": [12, 15, 15, 12], "element": [0, 1, 0, 1]}, {"type": [0, 0], "name": "Aerial Spinning Blade Dance Finisher", "power": [23, 23], "element": [0, 1]}, {"type": [0, 0, 0, 0, 0, 0], "name": "Demon Mode: Basic Combo", "power": [6, 8, 6, 8, 14, 14], "element": [0, 1, 0, 1, 0, 1]}, {"type": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "name": "Blade Dance", "power": [29, 4, 4, 4, 4, 4, 4, 4, 4, 6, 18, 18], "element": [0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1]}], "name": "Dual Blades"}, {"id": 5, "motions": [{"type": [1], "name": "Unsheathe Attack", "power": [20]}, {"type": [1], "name": "Ground Smash", "power": [42]}, {"type": [1], "name": "Repeat Ground Smash", "power": [20]}, {"type": [1], "name": "Side Smash", "power": [15]}, {"type": [1], "name": "Upswing", "power": [90]}, {"type": [1], "name": "Rising Smash (Charge Lv 1)", "power": [25]}, {"type": [1], "name": "Forward Smash (Charge Lv 2)", "power": [20]}, {"type": [1], "name": "Uppercut Smash (Charge Lv 2)", "power": [40]}, {"type": [1, 1], "name": "Superpound (Charge Lv 3)", "power": [15, 76]}, {"type": [1, 1, 1, 1, 1, 1], "name": "Spinning Bludgeon", "power": [20, 10, 10, 10, 10, 10]}, {"type": [1], "name": "Spinning Bludgeon Hook", "power": [60]}, {"type": [1], "name": "Spinning Bludgeon Upswing", "power": [90]}, {"type": [1], "name": "Spinning Bludgeon Slam", "power": [40]}, {"type": [1], "name": "Aerial Unsheathe Attack", "power": [42]}, {"type": [1], "name": "Aerial Attack (Charge Lv 1)", "power": [65]}, {"type": [1], "name": "Aerial Attack (Charge Lv 2)", "power": [70]}, {"type": [1], "name": "Aerial Attack (Charge Lv 3)", "power": [80]}], "name": "Hammer"}, {"id": 6, "motions": [{"type": [1], "name": "Jumping Slam", "power": [36]}, {"type": [1], "name": "Slam (Right)", "power": [30]}, {"type": [1], "name": "Slam (Right, Bounce)", "power": [27]}, {"type": [1], "name": "Slam (Left)", "power": [30]}, {"type": [1], "name": "Slam (Left, Bounce)", "power": [27]}, {"type": [1], "name": "Slam (Forward)", "power": [33]}, {"type": [1], "name": "Slam (Backward)", "power": [15]}, {"type": [1], "name": "Slam (Backward, Bounce)", "power": [27]}, {"type": [0], "name": "Hilt Stab", "power": [10]}, {"type": [1, 1], "name": "Superpound", "power": [15, 45]}, {"type": [1, 1], "name": "Encore Flourish", "power": [12, 22]}, {"type": [1], "name": "Forward Recital", "power": [20]}, {"type": [1], "name": "Side Recital (Right, Left)", "power": [35]}, {"type": [1], "name": "Backward Recital (Horn Contact)", "power": [30]}, {"type": [1], "name": "Backward Recital (Handle Contact)", "power": [25]}, {"type": [1], "name": "Encore (Right, 2nd Note)", "power": [33]}, {"type": [1], "name": "Encore (Right, 3rd Note Above)", "power": [35]}, {"type": [1], "name": "Encore (Left, 2nd Note)", "power": [33]}, {"type": [1], "name": "Encore (Left, 3rd Note Above)", "power": [35]}, {"type": [1], "name": "Encore (Back, 2nd Note)", "power": [33]}, {"type": [1], "name": "Encore (Back, 3rd Note Above)", "power": [40]}], "name": "Hunting Horn"}, {"id": 7, "motions": [{"type": [0], "name": "Mid Thrust 1", "power": [27]}, {"type": [0], "name": "Mid Thrust 2", "power": [20]}, {"type": [0], "name": "Mid Thrust 3", "power": [27]}, {"type": [0], "name": "High Thrust 1,2", "power": [22]}, {"type": [0], "name": "High Thrust 3", "power": [27]}, {"type": [0], "name": "Sweep", "power": [20]}, {"type": [0], "name": "Thrust Feint", "power": [20]}, {"type": [0], "name": "Cancelled Counter-thrust", "power": [22]}, {"type": [0], "name": "Charged Counter-thrust", "power": [50]}, {"type": [0], "name": "Guard Thrust", "power": [20]}, {"type": [1], "name": "Shield Attack", "power": [14]}, {"type": [0], "name": "Jumping Thrust", "power": [30]}, {"type": [0], "name": "Dash Jump Finishing Blow", "power": [50]}, {"type": [0], "name": "Dash Attack", "power": [16]}, {"type": [0], "name": "Dash Jump Thrust", "power": [25]}, {"type": [0], "name": "Finishing Blow", "power": [50]}, {"type": [0], "name": "Reverse Attack", "power": [50]}], "name": "Lance"}, {"id": 8, "motions": [{"type": [0], "name": "Step Thrust", "power": [32]}, {"type": [0], "name": "Dash Thrust", "power": [30]}, {"type": [0], "name": "Mid Thrust", "power": [24]}, {"type": [0], "name": "Rising Slash", "power": [28]}, {"type": [0], "name": "High Thrust 1,2,3", "power": [18]}, {"type": [0], "name": "Slam Attack", "power": [40]}, {"type": [0], "name": "Jumping Overhead Smash", "power": [44]}, {"type": [0], "name": "Jump Reload Slam", "power": [44]}, {"type": [0], "name": "Jump Thrust", "power": [25]}], "name": "Gunlance"}, {"id": 9, "motions": [{"type": [0], "name": "Axe: Jump Slash", "power": [43]}, {"type": [0], "name": "Sword: Jump Morph Slash", "power": [43]}, {"type": [0], "name": "Axe: Step Slash", "power": [19]}, {"type": [0], "name": "Axe: Overhead Slash", "power": [40]}, {"type": [0], "name": "Axe: Side Slash", "power": [23]}, {"type": [0], "name": "Axe: Rising Slash", "power": [28]}, {"type": [0], "name": "Axe: Hack 'n' Slash", "power": [24]}, {"type": [0], "name": "Axe: Finishing Sweep", "power": [57]}, {"type": [0], "name": "Sword: Morph Slash", "power": [23]}, {"type": [0], "name": "Sword: Jump Slash", "power": [30]}, {"type": [0], "name": "Axe: Jump Morph Slash", "power": [30]}, {"type": [0], "name": "Sword: Overhead Slash", "power": [30]}, {"type": [0], "name": "Sword: Side Slash", "power": [25]}, {"type": [0], "name": "Sword: Rising Slash", "power": [25]}, {"type": [0, 0], "name": "Sword: Double Slash", "power": [28, 36]}, {"type": [0], "name": "Axe: Morph Slash", "power": [30]}, {"type": [0], "name": "Sword: Jump Element Discharge", "power": [28]}, {"type": [0], "name": "Sword: Element Discharge", "power": [13]}, {"type": [0], "name": "Sword: Element Discharge (Roll)", "power": [13]}, {"type": [0], "name": "Sword: Element Discharge Burst", "power": [50]}, {"type": [0], "name": "Sword: Element Discharge Finisher", "power": [80]}], "name": "Switch Axe"}, {"id": 10, "motions": [{"type": [0], "name": "Sword: Forward Slash", "power": [22]}, {"type": [0], "name": "Sword: Jump Slash (From Dash Slash)", "power": [35]}, {"type": [0], "name": "Sword: Dash Slash", "power": [22]}, {"type": [0], "name": "Sword: Rising Slash", "power": [14]}, {"type": [0], "name": "Sword: Return Stroke", "power": [17]}, {"type": [0], "name": "Sword: Roundslash", "power": [30]}, {"type": [0], "name": "Sword: Charged Single Slash", "power": [16]}, {"type": [0, 0], "name": "Sword: Charged Double Slash", "power": [30, 20]}, {"type": [0], "name": "Morph to Sword", "power": [30]}, {"type": [0, 0], "name": "Shield Attack", "power": [8, 12]}, {"type": [0], "name": "Axe: Jump Slash", "power": [47]}, {"type": [0], "name": "Axe: Jump Slash (From Dash Slash)", "power": [50]}, {"type": [0], "name": "Axe: Slam Attack", "power": [47]}, {"type": [0], "name": "Morph to Axe", "power": [47]}, {"type": [0], "name": "Axe: Overhead Slash", "power": [40]}, {"type": [0], "name": "Axe: Rising Slash", "power": [40]}, {"type": [0], "name": "Axe: Side Slash", "power": [20]}, {"type": [0], "name": "Axe: Element Discharge 1", "power": [26]}, {"type": [0], "name": "Axe: Dash Element Discharge 1", "power": [26]}, {"type": [0, 0], "name": "Axe: Element Discharge 2", "power": [18, 80]}, {"type": [0], "name": "Axe: Amped Element Discharge", "power": [90]}, {"type": [0], "name": "Axe: Amped Element Discharge (Phials Empty)", "power": [50]}, {"type": [0, 0], "name": "Axe: Super Amped Element Discharge (Phials Empty)", "power": [20, 90]}, {"type": [0, 0, 0], "name": "Axe: Super Amped Element Discharge", "power": [25, 99, 100]}], "name": "Charge Blade"}, {"id": 11, "motions": [{"type": [0], "name": "Thrust", "power": [15]}, {"type": [0, 0], "name": "Reaping Slash", "power": [18, 12]}, {"type": [0], "name": "Sweep", "power": [24]}, {"type": [0, 0], "name": "Sweep (Attack up)", "power": [16, 26]}, {"type": [0, 0], "name": "Rising Slash", "power": [26, 20]}, {"type": [0, 0, 0], "name": "Rising Slash (Attack up)", "power": [28, 16, 18]}, {"type": [0], "name": "Slash (Between Backward Sweep, Rising Slash)", "power": [28]}, {"type": [0, 0], "name": "Double Slash", "power": [18, 24]}, {"type": [0, 0, 0], "name": "Double Slash (Attack up)", "power": [16, 14, 28]}, {"type": [0], "name": "Backward Sweep", "power": [26]}, {"type": [0, 0], "name": "Backward Sweep (Attack up)", "power": [18, 30]}, {"type": [0], "name": "Overhead Smash", "power": [28]}, {"type": [0], "name": "Backflip Slash", "power": [20]}, {"type": [0], "name": "Slam", "power": [30]}, {"type": [0, 0], "name": "Slam (Attack up)", "power": [24, 38]}, {"type": [0], "name": "Jumping Slash", "power": [24]}, {"type": [0, 0], "name": "Jumping Slash (Attack up)", "power": [20, 10]}, {"type": [0], "name": "Mark Target (Aim, Fire)", "power": [10]}, {"type": [-1], "name": "Kinsect: Harvest Extract", "power": [45]}, {"type": [-1], "name": "Kinsect: Attack", "power": [80]}], "name": "Insect Glaive"}] \ No newline at end of file diff --git a/mhapi/damage.py b/mhapi/damage.py index 9eb21ba..56fedcd 100644 --- a/mhapi/damage.py +++ b/mhapi/damage.py @@ -4,6 +4,9 @@ import json import difflib import re +from mhapi import skills +from mhapi.model import SharpnessLevel + WEAKPART_WEIGHT = 0.5 @@ -14,7 +17,7 @@ def raw_damage(true_raw, sharpness, affinity, monster_hitbox, motion): sharpness, monster raw weakness, and weapon motion value. """ return (true_raw - * Sharpness.raw_modifier(sharpness) + * SharpnessLevel.raw_modifier(sharpness) * (1 + (affinity / 400.0)) * motion / 100.0 * monster_hitbox / 100.0) @@ -27,44 +30,10 @@ def element_damage(element, sharpness, monster_ehitbox): Note that this is independent of the motion value of the attack. """ return (element / 10.0 - * Sharpness.element_modifier(sharpness) + * SharpnessLevel.element_modifier(sharpness) * monster_ehitbox / 100.0) -class Sharpness(object): - """ - Enumeration for weapon sharpness. - """ - - RED = 0 - ORANGE = 1 - YELLOW = 2 - GREEN = 3 - BLUE = 4 - WHITE = 5 - PURPLE = 6 - - ALL = range(0, PURPLE + 1) - - _modifier = { - RED: (0.50, 0.25), - ORANGE: (0.75, 0.50), - YELLOW: (1.00, 0.75), - GREEN: (1.05, 1.00), - BLUE: (1.20, 1.06), - WHITE: (1.32, 1.12), - PURPLE: (1.44, 1.20), - } - - @classmethod - def raw_modifier(cls, sharpness): - return cls._modifier[sharpness][0] - - @classmethod - def element_modifier(cls, sharpness): - return cls._modifier[sharpness][1] - - class MotionType(object): CUT = "cut" IMPACT = "impact" @@ -192,13 +161,21 @@ class WeaponMonsterDamage(object): Does not include overall monster defense. """ def __init__(self, weapon_row, monster_row, monster_damage_rows, motion, - sharp_plus=False, breakable_parts=None): + sharp_plus=False, breakable_parts=None, + attack_skill=skills.AttackUp.NONE, + critical_eye_skill=skills.CriticalEye.NONE, + element_skill=skills.ElementAttackUp.NONE, + awaken=False): self.weapon = weapon_row self.monster = monster_row self.monster_damage = monster_damage_rows self.motion = motion self.sharp_plus = sharp_plus self.breakable_parts = breakable_parts + self.attack_skill = attack_skill + self.critical_eye_skill = critical_eye_skill + self.element_skill = element_skill + self.awaken = awaken self.damage_map = defaultdict(PartDamage) self.average = 0 @@ -209,16 +186,25 @@ class WeaponMonsterDamage(object): self.weapon_type = self.weapon["wtype"] self.true_raw = (self.weapon["attack"] / WeaponType.multiplier(self.weapon_type)) - sharp = _parse_sharpness(self.weapon) if sharp_plus: - self.sharpness = sharp[1] + self.sharpness = self.weapon.sharpness_plus.max else: - self.sharpness = sharp[0] + self.sharpness = self.weapon.sharpness.max #print "sharpness=", self.sharpness self.affinity = int(self.weapon["affinity"] or 0) self.damage_type = WeaponType.damage_type(self.weapon_type) self.etype = self.weapon["element"] self.eattack = self.weapon["element_attack"] + if not self.etype and self.awaken: + self.etype = self.weapon.awaken + self.eattack = self.weapon.awaken_attack + + self.true_raw = skills.AttackUp.modified(attack_skill, + self.true_raw) + self.affinity = skills.CriticalEye.modified(critical_eye_skill, + self.affinity) + self.eattack = skills.ElementAttackUp.modified(element_skill, + self.eattack) self.parts = [] self.break_count = 0 @@ -234,8 +220,13 @@ class WeaponMonsterDamage(object): self.max_element_part = (None, 0) self._calculate_damage() + @property + def attack(self): + return self.true_raw * WeaponType.multiplier(self.weapon_type) + def _calculate_damage(self): - for row in self.monster_damage: + for row in self.monster_damage._rows: + # TODO: refactor to take advantage of new model part = row["body_part"] alt = None m = re.match(r"([^(]+) \(([^)]+)\)", part) @@ -548,18 +539,3 @@ def element_x_attack_up(value, level=1): value += 90 else: raise ValueError("level must be 1, 2, or 3") - - -def _parse_sharpness(weapon_row): - """ - Parse the sharpness field from a weapon row, to determine - the max sharpness of the weapon with and without sharpness +1. - """ - db_values = weapon_row["sharpness"].split(" ") - sharpness = [Sharpness.RED, Sharpness.RED] - for i, db_value in enumerate(db_values): - values = [int(s) for s in db_value.split(".")] - for s in Sharpness.ALL: - if values[s] > 0: - sharpness[i] = s - return sharpness diff --git a/mhapi/db.py b/mhapi/db.py index a0eb5d5..04b5f1b 100644 --- a/mhapi/db.py +++ b/mhapi/db.py @@ -2,19 +2,69 @@ Module for accessing the sqlite monster hunter db from """ +import os.path import sqlite3 from mhapi import model + +def field_model(key): + """ + Model to replace each row with the value of single field in the row, + with the specified key. + """ + def model_fn(row): + return row[key] + return model_fn + + +def _db_path(): + module_path = os.path.dirname(__file__) + project_path = os.path.abspath(os.path.join(module_path, "..")) + return os.path.join(project_path, "db", "mh4u.db") + + class MHDB(object): - def __init__(self, path, use_cache=True): + """ + Wrapper around the Android App sqlite3 db. The following conventions + are used: + + - get_ENTITY_NAME will return a single entity by id + - get_ENTITY_NAME_by_name will return a single entity by name + - get_ENTITY_NAMEs will return a list of all entities in the db + - get_ENTITY_NAME_names will return a list of all names of the + entities in the db, possibly with a type param. + """ + + def __init__(self, path=None, use_cache=False): + """ + If use_cache=True, a lot of memory could be used. No attempt is + made to de-dupe data between keys, e.g. if you access an item + by id and by name, it will be fetched and stored in the cache + twice. Disk cache, sqlite caching, and the smallness of the + database should make in-memory caching unnecessary for most use + cases. + """ + if path is None: + path = _db_path() self.conn = sqlite3.connect(path) self.conn.row_factory = sqlite3.Row self.use_cache = use_cache self.cache = {} - def _get_memoized(self, key, query, *args): - if self.use_cache: + def _query_one(self, key, query, args=(), model_cls=None, + no_cache=False): + values = self._query_all(key, query, args, model_cls, no_cache) + if values: + return values[0] + else: + return None + + def _query_all(self, key, query, args=(), model_cls=None, + no_cache=False, collection_cls=None): + assert isinstance(args, tuple) + assert model_cls is None or collection_cls is None + if self.use_cache and not no_cache: if key in self.cache: v = self.cache[key].get(args) if v is not None: @@ -22,10 +72,14 @@ class MHDB(object): else: self.cache[key] = {} cursor = self.conn.execute(query, args) - v = cursor.fetchall() - if self.use_cache: - self.cache[key][args] = v - return v + rows = cursor.fetchall() + if model_cls: + rows = [model_cls(row) for row in rows] + if collection_cls: + rows = collection_cls(rows) + if self.use_cache and not no_cache: + self.cache[key][args] = rows + return rows def cursor(self): return self.conn.cursor() @@ -36,41 +90,65 @@ class MHDB(object): def close(self): return self.conn.close() + def get_item_types(self): + """ + List of strings. + """ + return self._query_all("item_types", """ + SELECT DISTINCT type FROM items + """, model_cls=field_model("type")) + def get_item_names(self, item_types): + """ + List of unicode strings. + """ item_types.sort() placeholders = ", ".join(["?"] * len(item_types)) - v = self._get_memoized("item_names", """ + return self._query_all("item_names", """ SELECT _id, name FROM items WHERE type IN (%s) - """ % placeholders, *item_types) - return v + """ % placeholders, tuple(item_types), model_cls=field_model("name")) - def get_monster_names(self): - v = self._get_memoized("monster_names", """ - SELECT name FROM monsters - """) - return v + def get_items(self, item_types=None): + """ + List of item objects. + """ + q = "SELECT * FROM items" + if item_types: + item_types.sort() + placeholders = ", ".join(["?"] * len(item_types)) + q += "\nWHERE type IN (%s)" % placeholders + item_types = tuple(item_types) + else: + item_types = () + return self._query_all("items", q, item_types, model_cls=model.Item) def get_item(self, item_id): - v = self._get_memoized("item", """ + """ + Single item object or None. + """ + return self._query_one("item", """ SELECT * FROM items WHERE _id=? - """, item_id) - return v[0] if v else None + """, (item_id,), model_cls=model.Item) def get_item_by_name(self, name): - v = self._get_memoized("item", """ + """ + Single item object or None. + """ + return self._query_one("item", """ SELECT * FROM items WHERE name=? - """, name) - return v[0] if v else None + """, (name,), model_cls=model.Item) def get_wyporium_trade(self, item_id): - v = self._get_memoized("wyporium", """ + """ + Single wyporium row or None. + """ + return self._query_one("wyporium", """ SELECT * FROM wyporium WHERE item_in_id=? - """, item_id) - return v[0] if v else None + """, (item_id,)) def search_item_name(self, term, item_type=None): """ @@ -92,42 +170,50 @@ class MHDB(object): query += "AND type = ?" args += [item_type] - cursor = self.conn.execute(query, args) - return cursor.fetchall() + return self._query_all("search_item", query, tuple(args), + no_cache=True, model_cls=model.Item) - def get_monster_by_name(self, name): - v = self._get_memoized("monster", """ + def get_monsters(self): + return self._query_all("monsters", """ SELECT * FROM monsters - WHERE name=? - """, name) - return v[0] if v else None + """, model_cls=model.Monster) + + def get_monster_names(self): + """ + List of unicode strings. + """ + return self._query_all("monster_names", """ + SELECT name FROM monsters + """, model_cls=field_model("name")) def get_monster(self, monster_id): - v = self._get_memoized("monster", """ + return self._query_one("monster", """ SELECT * FROM monsters WHERE _id=? - """, monster_id) - return v[0] if v else None + """, (monster_id,), model_cls=model.Monster) + + def get_monster_by_name(self, name): + return self._query_one("monster", """ + SELECT * FROM monsters + WHERE name=? + """, (name,), model_cls=model.Monster) def get_quest(self, quest_id): - v = self._get_memoized("quest", """ + return self._query_one("quest", """ SELECT * FROM quests WHERE _id=? - """, quest_id) - return v[0] if v else None + """, (quest_id,), model_cls=model.Quest) def get_quests(self): - v = self._get_memoized("quests", """ + return self._query_all("quests", """ SELECT * FROM quests - """) - return v + """, model_cls=model.Quest) def get_quest_rewards(self, quest_id): - v = self._get_memoized("quest_rewards", """ + return self._query_all("quest_rewards", """ SELECT * FROM quest_rewards WHERE quest_id=? - """, quest_id) - return v + """, (quest_id,)) def get_monster_rewards(self, monster_id, rank=None): q = """ @@ -136,19 +222,18 @@ class MHDB(object): """ if rank is not None: q += "AND rank=?" - v = self._get_memoized("monster_rewards", q, monster_id, rank) + args = (monster_id, rank) else: - v = self._get_memoized("monster_rewards", q, monster_id) - return v + args = (monster_id,) + return self._query_all("monster_rewards", q, args) def get_quest_monsters(self, quest_id): - v = self._get_memoized("quest_monsters", """ + return self._query_all("quest_monsters", """ SELECT monster_id, unstable FROM monster_to_quest WHERE quest_id=? - """, quest_id) - return v + """, (quest_id,)) - def get_item_quest_objects(self, item_id): + def get_item_quests(self, item_id): """ Get a list of quests that provide the specified item in quest reqards. Returns a list of quest objects, which encapsulate the @@ -163,79 +248,70 @@ class MHDB(object): quests = [] for r in rows: - quest_row = self.get_quest(r["quest_id"]) - rewards_rows = self.get_quest_rewards(r["quest_id"]) - quests.append(model.Quest(quest_row, rewards_rows)) + quest_id = r["quest_id"] + quest = self.get_quest(quest_id) + quest.rewards = self.get_quest_rewards(quest_id) + quests.append(quest) return quests def get_item_monsters(self, item_id): - v = self._get_memoized("item_monsters", """ + return self._query_all("item_monsters", """ SELECT DISTINCT monster_id, rank FROM hunting_rewards WHERE item_id=? - """, item_id) - - return v + """, (item_id,)) def get_item_gathering(self, item_id): - v = self._get_memoized("item_gathering", """ + return self._query_all("item_gathering", """ SELECT * FROM gathering WHERE item_id=? - """, item_id) - - return v + """, (item_id,)) def get_location(self, location_id): - v = self._get_memoized("location", """ + self._query_one("location", """ SELECT * FROM locations WHERE _id=? - """, location_id) - - return v + """, (location_id,), model_cls=model.Location) def get_locations(self): - v = self._get_memoized("locations", """ + return self._query_all("locations", """ SELECT * FROM locations - """) - - return v + """, model_cls=model.Location) def get_monster_damage(self, monster_id): - v = self._get_memoized("monster_damage", """ + return self._query_all("monster_damage", """ SELECT * FROM monster_damage WHERE monster_id=? - """, monster_id) - - return v + """, (monster_id,), collection_cls=model.MonsterDamage) def get_weapons(self): - v = self._get_memoized("weapons", """ + return self._query_all("weapons", """ SELECT * FROM weapons LEFT JOIN items ON weapons._id = items._id - """) - - return v + """, model_cls=model.Weapon) def get_weapon(self, weapon_id): - v = self._get_memoized("weapon", """ + return self._query_one("weapon", """ SELECT * FROM weapons LEFT JOIN items ON weapons._id = items._id WHERE weapons._id=? - """, weapon_id) - return v[0] if v else None + """, (weapon_id,), model_cls=model.Weapon) def get_weapon_by_name(self, name): - v = self._get_memoized("weapon", """ + return self._query_one("weapon", """ SELECT * FROM weapons LEFT JOIN items ON weapons._id = items._id WHERE items.name=? - """, name) - return v[0] if v else None + """, (name,), model_cls=model.Weapon) def get_monster_breaks(self, monster_id): - v = self._get_memoized("monster_breaks", """ + """ + List of strings. + """ + def model(row): + return row["condition"][len("Break "):] + + return self._query_all("monster_breaks", """ SELECT DISTINCT condition FROM hunting_rewards WHERE monster_id=? AND condition LIKE 'Break %' - """, monster_id) - - return [row["condition"][len("Break "):] for row in v] + """, (monster_id,), model_cls=model) diff --git a/mhapi/model.py b/mhapi/model.py index 706f2de..ef24fc7 100644 --- a/mhapi/model.py +++ b/mhapi/model.py @@ -1,28 +1,92 @@ import string import json +import urllib +from collections import defaultdict +import re +from mhapi.util import EnumBase + + +class ModelJSONEncoder(json.JSONEncoder): + def default(self, o): + if hasattr(o, "as_data"): + return o.as_data() + return json.JSONEncoder.default(self, o) + + +class ModelBase(object): + def as_data(self): + raise NotImplemented() + + def as_list_data(self): + raise NotImplemented() + + def json_dumps(self, indent=2): + data = self.as_data() + return json.dumps(data, cls=ModelJSONEncoder, indent=indent) + + def json_dump(self, fp, indent=2): + json.dump(self, fp, cls=ModelJSONEncoder, indent=indent) + + +class RowModel(ModelBase): + _list_fields = ["id", "name"] + _exclude_fields = [] + _indexes = { "name": ["id"] } -class RowModel(object): def __init__(self, row): - self._row = row self.id = row["_id"] + self._row = row + self._data = dict(row) + del self._data["_id"] + self._data["id"] = self.id + for f in self._exclude_fields: + del self._data[f] def __getattr__(self, name): try: - return self._row[name] + return self._data[name] except IndexError: raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) - def as_dict(self): - d = dict(self._row) - d["id"] = d["_id"] - del d["_id"] - return d + def __getitem__(self, key): + return self._data[key] + + def fields(self): + return self._data.keys() + + def as_data(self): + return self._data - def as_json(self): - data = self.as_dict() - return json.dumps(data) + def as_list_data(self): + list_data = {} + for key in self._list_fields: + list_data[key] = self[key] + return list_data + + def update_indexes(self, data): + for key_field, value_fields in self._indexes.iteritems(): + if key_field not in data: + data[key_field] = {} + self.update_index(key_field, value_fields, data[key_field]) + + def update_index(self, key_field, value_fields, data): + item = dict((k, self[k]) for k in value_fields) + key_value = self[key_field] + if key_value not in data: + data[key_value] = [] + data[key_value].append(item) + + def __str__(self): + if "name" in self._data: + name = urllib.quote(self.name, safe=" ") + else: + name = str(self.id) + return "%s '%s'" % (self.__class__.__name__, name) + + def __repr__(self): + return "" % (self.__class__.__name__, self.id) class Quest(RowModel): @@ -47,11 +111,178 @@ class Quest(RowModel): or " all " in self.goal) def one_line_u(self): - return self._one_line_template.substitute(self.as_dict()) + return self._one_line_template.substitute(self.as_data()) def full_u(self): - return self._full_template.substitute(self.as_dict()) + return self._full_template.substitute(self.as_data()) def __unicode__(self): return self.full_u() + +class SharpnessLevel(EnumBase): + """ + Enumeration for weapon sharpness levels. + """ + + RED = 0 + ORANGE = 1 + YELLOW = 2 + GREEN = 3 + BLUE = 4 + WHITE = 5 + PURPLE = 6 + + ALL = range(0, PURPLE + 1) + + _names = { + RED: "Red", + ORANGE: "Orange", + YELLOW: "Yellow", + GREEN: "Green", + BLUE: "Blue", + WHITE: "White", + PURPLE: "Purple", + } + + _modifier = { + RED: (0.50, 0.25), + ORANGE: (0.75, 0.50), + YELLOW: (1.00, 0.75), + GREEN: (1.05, 1.00), + BLUE: (1.20, 1.06), + WHITE: (1.32, 1.12), + PURPLE: (1.44, 1.20), + } + + @classmethod + def raw_modifier(cls, sharpness): + return cls._modifier[sharpness][0] + + @classmethod + def element_modifier(cls, sharpness): + return cls._modifier[sharpness][1] + + +class WeaponSharpness(ModelBase): + """ + Representation of the sharpness of a weapon, as a list of sharpness + 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(".")] + self._max = None + + @property + def max(self): + if self._max is None: + self._max = SharpnessLevel.RED + for i in xrange(SharpnessLevel.PURPLE+1): + if self.value_list[i] == 0: + break + else: + self._max = i + return self._max + + def as_data(self): + return self.value_list + + +class Weapon(RowModel): + _list_fields = ["id", "wtype", "name"] + _indexes = { "name": ["id"], + "wtype": ["id", "name"] } + + def __init__(self, weapon_item_row): + super(Weapon, self).__init__(weapon_item_row) + + self._parse_sharpness() + + def _parse_sharpness(self): + """ + Replace the sharpness field with parsed models for the normal + sharpness and the sharpness with Sharpness+1 skill. + """ + 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) + + +class Monster(RowModel): + _list_fields = ["id", "class", "name"] + + +class Item(RowModel): + _list_fields = ["id", "type", "name"] + _indexes = { "name": ["id"], + "type": ["id", "name"] } + + +class Location(RowModel): + pass + + +class MonsterPartStateDamage(RowModel): + """ + Model for the damage to the monster on a particular hitbox and in + a particulare state. + """ + _exclude_fields = ["monster_id", "body_part"] + + def __init__(self, part, state, row): + super(MonsterPartStateDamage, self).__init__(row) + self._data["part"] = part + self._data["state"] = state + + +class MonsterPartDamage(ModelBase): + """ + Model for collecting the damage to the monster on a particular hitbox + across different states. + """ + def __init__(self, part): + self.part = part + self.states = {} + + def add_state(self, state, damage_row): + self.states[state] = MonsterPartStateDamage(self.part, state, + damage_row) + + def as_data(self): + return self.states + + +class MonsterDamage(ModelBase): + """ + Model for the damage weakness to the monster in all the + different states and all the different hitboxes. + """ + def __init__(self, damage_rows): + self._rows = damage_rows + self.parts = {} + self.states = set() + for row in damage_rows: + part = row["body_part"] + state = "Normal" + m = re.match(r"([^(]+) \(([^)]+)\)", part) + if m: + part = m.group(1) + state = m.group(2) + self.states.add(state) + if part not in self.parts: + self.parts[part] = MonsterPartDamage(part) + self.parts[part].add_state(state, row) + + def as_data(self): + return dict( + states=list(self.states), + parts=self.parts + ) diff --git a/mhapi/rewards.py b/mhapi/rewards.py index 1ae12fc..3025495 100644 --- a/mhapi/rewards.py +++ b/mhapi/rewards.py @@ -78,7 +78,7 @@ class GatherLocation(object): Track total expected value for an item in one location/rank. """ def __init__(self, location_row, rank, gathering_rows): - self.location_id = location_row["_id"] + self.location_id = location_row.id self.location_name = location_row["name"] self.rank = rank self._rewards = [] @@ -675,12 +675,11 @@ class ItemRewards(object): def __init__(self, db, item_row): self.db = db self.item_row = item_row - self.item_id = item_row["_id"] + self.item_id = item_row.id wyp_row = db.get_wyporium_trade(self.item_id) if wyp_row is not None: - self.trade_unlock_quest = Quest( - db.get_quest(wyp_row["unlock_quest_id"])) + self.trade_unlock_quest = db.get_quest(wyp_row["unlock_quest_id"]) self.trade_item_row = self.item_row self.trade_item_id = self.item_id self.item_id = wyp_row["item_out_id"] @@ -728,7 +727,7 @@ class ItemRewards(object): for rank in "LR HR G".split(): gl = GatherLocation(loc, rank, gathering_rows) if gl: - key = (loc["_id"], rank) + key = (loc.id, rank) self._gather_items[key] = gl def _find_hunt_items(self): @@ -759,7 +758,7 @@ class ItemRewards(object): Get a list of the quests for acquiring a given item and the probability of getting the item, depending on cap or kill and luck skills. """ - quests = self.db.get_item_quest_objects(self.item_id) + quests = self.db.get_item_quests(self.item_id) if not quests: return for q in quests: diff --git a/mhapi/skills.py b/mhapi/skills.py index 2f2d8c1..a2ba66d 100644 --- a/mhapi/skills.py +++ b/mhapi/skills.py @@ -1,14 +1,8 @@ +from mhapi.util import EnumBase -class SkillEnum(object): - _names = dict() - @classmethod - def name(cls, skill_id): - return cls._names[skill_id] - - -class CapSkill(SkillEnum): +class CapSkill(EnumBase): NONE = 0 EXPERT = 1 MASTER = 2 @@ -20,7 +14,7 @@ class CapSkill(SkillEnum): GOD: "Capture God" } -class LuckSkill(SkillEnum): +class LuckSkill(EnumBase): NONE = 0 GOOD = 1 GREAT = 2 @@ -32,7 +26,7 @@ class LuckSkill(SkillEnum): AMAZING: "Magnificent Luck" } -class CarvingSkill(SkillEnum): +class CarvingSkill(EnumBase): NONE = 0 PRO = 0 # prevent knockbacks but no extra carves FELYNE_LOW = 1 @@ -51,3 +45,91 @@ QUEST_A = "A" QUEST_B = "B" QUEST_SUB = "Sub" + +class CriticalEye(EnumBase): + NONE = 0 + ONE = 1 + TWO = 2 + THREE = 3 + GOD = 4 + + _names = { NONE: "", + ONE: "Critical Eye +1", + TWO: "Critical Eye +2", + THREE: "Critical Eye +3", + GOD: "Critical God" } + + _modifier = { NONE: 0, + ONE: 10, + TWO: 15, + THREE: 20, + GOD: 30 } + + @classmethod + def affinity_modifier(cls, skill): + return cls._modifier[skill] + + @classmethod + def modified(cls, skill, affinity): + return affinity + cls.affinity_modifier(skill) + + +class AttackUp(EnumBase): + NONE = 0 + SMALL = 1 + MEDIUM = 2 + LARGE = 3 + XLARGE = 4 + + _names = { NONE: "", + SMALL: "Attack Up (S)", + MEDIUM: "Attack Up (M)", + LARGE: "Attack Up (L)", + XLARGE: "Attack Up (XL)" } + + _modifier = { NONE: 0, + SMALL: 10, + MEDIUM: 15, + LARGE: 20, + XLARGE: 25 } + + @classmethod + def true_attack_modifier(cls, skill): + return cls._modifier[skill] + + @classmethod + def modified(cls, skill, true_attack): + return true_attack + cls.true_attack_modifier(skill) + + +class ElementAttackUp(EnumBase): + NONE = 0 + ONE = 1 + TWO = 2 + THREE = 3 + ELEMENT = 4 + + _names = { NONE: "", + ONE: "(element) Atk +1", + TWO: "(element) Atk +2", + THREE: "(element) Atk +3", + ELEMENT: "Element Atk Up" } + + + @classmethod + def modified(cls, skill, element): + if skill == cls.NONE: + return element + elif skill in (cls.ONE, cls.TWO, cls.THREE): + element = element * (1 + .05 * skill) + if skill == cls.ONE: + element += 40 + elif skill == cls.TWO: + element += 60 + elif skill == cls.THREE: + element += 90 + elif skill == cls.ELEMENT: + element *= 1.1 + else: + raise ValueError("Unknown element skill %s" % skill) + return element diff --git a/mhapi/util.py b/mhapi/util.py new file mode 100644 index 0000000..d7362e0 --- /dev/null +++ b/mhapi/util.py @@ -0,0 +1,11 @@ +""" +Shared utility classes and functions. +""" + + +class EnumBase(object): + _names = dict() + + @classmethod + def name(cls, enum_value): + return cls._names[enum_value]