model and db refactor, damage improvements

main
Bryce Allen 11 years ago
parent 9968cd4cdd
commit 4e562ed537

@ -33,7 +33,7 @@ if __name__ == '__main__':
db_path = os.path.join(db_path, "..", "db", "mh4u.db") db_path = os.path.join(db_path, "..", "db", "mh4u.db")
db = MHDB(db_path) 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 # write all names json to /items.json
items_file = os.path.join(outdir, "items.json") items_file = os.path.join(outdir, "items.json")
@ -48,13 +48,13 @@ if __name__ == '__main__':
else: else:
out.write(", ") out.write(", ")
out.write('"') out.write('"')
out.write(item["name"]) out.write(item.name)
out.write('"') out.write('"')
out.write("]") out.write("]")
for item in items: for item in items:
name = item["name"] name = item.name
item_id = item["_id"] item_id = item.id
encoded_name = name.encode("utf8") encoded_name = name.encode("utf8")
item_file = os.path.join(outdir, encoded_name + ".txt") item_file = os.path.join(outdir, encoded_name + ".txt")
print "Writing", item_id, item_file print "Writing", item_id, item_file

@ -1,12 +1,14 @@
#!/usr/bin/env python #!/usr/bin/env python
import sys import sys
import os import argparse
import _pathfix import _pathfix
from mhapi.db import MHDB from mhapi.db import MHDB
from mhapi.damage import MotionValueDB, WeaponMonsterDamage from mhapi.damage import MotionValueDB, WeaponMonsterDamage
from mhapi.model import SharpnessLevel
from mhapi import skills
def percent_change(a, b): def percent_change(a, b):
@ -15,50 +17,89 @@ def percent_change(a, b):
return (100.0 * (b-a) / a) return (100.0 * (b-a) / a)
if __name__ == '__main__': def parse_args(argv):
if len(sys.argv) < 4: parser = argparse.ArgumentParser(
print "Usage: %s 'monster name' 'weapon name'+" % sys.argv[0] "Calculate damage to monster from different weapons of the"
sys.exit(os.EX_USAGE) " 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])) if __name__ == '__main__':
monster_name = sys.argv[2] args = parse_args(None)
weapon_names = sys.argv[3:]
db = MHDB(_pathfix.db_path) db = MHDB(_pathfix.db_path)
motiondb = MotionValueDB(_pathfix.motion_values_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: if not monster:
raise ValueError("Monster '%s' not found" % monster_name) raise ValueError("Monster '%s' not found" % args.monster)
monster_damage = db.get_monster_damage(monster["_id"]) monster_damage = db.get_monster_damage(monster.id)
weapons = [] weapons = []
for name in weapon_names: for name in args.weapon:
weapon = db.get_weapon_by_name(name) weapon = db.get_weapon_by_name(name)
if not weapon: if not weapon:
raise ValueError("Weapon '%s' not found" % name) raise ValueError("Weapon '%s' not found" % name)
weapons.append(weapon) weapons.append(weapon)
monster_breaks = db.get_monster_breaks(monster["_id"]) monster_breaks = db.get_monster_breaks(monster.id)
weapon_type = weapons[0]["wtype"] weapon_type = weapons[0]["wtype"]
motion = motiondb[weapon_type].average motion = motiondb[weapon_type].average
print "Weapon Type: %s" % weapon_type print "Weapon Type: %s" % weapon_type
print "Average Motion: %0.1f" % motion print "Average Motion: %0.1f" % motion
print "Monster Breaks: %s" % ", ".join(monster_breaks) 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() weapon_damage_map = dict()
for name, row in zip(weapon_names, weapons): for name, row in zip(args.weapon, weapons):
row_type = row["wtype"] row_type = row["wtype"]
if row_type != weapon_type: if row_type != weapon_type:
raise ValueError("Weapon '%s' is different type" % name) raise ValueError("Weapon '%s' is different type" % name)
try: try:
weapon_damage_map[name] = WeaponMonsterDamage(row, wd = WeaponMonsterDamage(row,
monster, monster_damage, monster, monster_damage,
motion, sharp_plus, motion, args.sharpness_plus_one,
monster_breaks) 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: except ValueError as e:
print str(e) print str(e)
sys.exit(1) 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 parts = damage_map_base.parts
for part in parts: for part in parts:
@ -66,17 +107,17 @@ if __name__ == '__main__':
damage_map_base[part].total, damage_map_base[part].total,
weapon_damage_map[w][part].total weapon_damage_map[w][part].total
) )
for w in weapon_names[1:]] for w in args.weapon[1:]]
ediffs = [percent_change( ediffs = [percent_change(
damage_map_base[part].element, damage_map_base[part].element,
weapon_damage_map[w][part].element weapon_damage_map[w][part].element
) )
for w in weapon_names[1:]] for w in args.weapon[1:]]
bdiffs = [percent_change( bdiffs = [percent_change(
damage_map_base[part].break_diff(), damage_map_base[part].break_diff(),
weapon_damage_map[w][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) tdiff_s = ",".join("%+0.1f%%" % i for i in tdiffs)
ediff_s = ",".join("%+0.1f%%" % i for i in ediffs) ediff_s = ",".join("%+0.1f%%" % i for i in ediffs)
bdiff_s = ",".join("%+0.1f%%" % i for i in bdiffs) bdiff_s = ",".join("%+0.1f%%" % i for i in bdiffs)
@ -100,7 +141,7 @@ if __name__ == '__main__':
base, base,
weapon_damage_map[w].averages[avg_type] 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_s = ",".join("%+0.1f%%" % i for i in diffs)

File diff suppressed because one or more lines are too long

@ -4,6 +4,9 @@ import json
import difflib import difflib
import re import re
from mhapi import skills
from mhapi.model import SharpnessLevel
WEAKPART_WEIGHT = 0.5 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. sharpness, monster raw weakness, and weapon motion value.
""" """
return (true_raw return (true_raw
* Sharpness.raw_modifier(sharpness) * SharpnessLevel.raw_modifier(sharpness)
* (1 + (affinity / 400.0)) * (1 + (affinity / 400.0))
* motion / 100.0 * motion / 100.0
* monster_hitbox / 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. Note that this is independent of the motion value of the attack.
""" """
return (element / 10.0 return (element / 10.0
* Sharpness.element_modifier(sharpness) * SharpnessLevel.element_modifier(sharpness)
* monster_ehitbox / 100.0) * 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): class MotionType(object):
CUT = "cut" CUT = "cut"
IMPACT = "impact" IMPACT = "impact"
@ -192,13 +161,21 @@ class WeaponMonsterDamage(object):
Does not include overall monster defense. Does not include overall monster defense.
""" """
def __init__(self, weapon_row, monster_row, monster_damage_rows, motion, 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.weapon = weapon_row
self.monster = monster_row self.monster = monster_row
self.monster_damage = monster_damage_rows self.monster_damage = monster_damage_rows
self.motion = motion self.motion = motion
self.sharp_plus = sharp_plus self.sharp_plus = sharp_plus
self.breakable_parts = breakable_parts 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.damage_map = defaultdict(PartDamage)
self.average = 0 self.average = 0
@ -209,16 +186,25 @@ class WeaponMonsterDamage(object):
self.weapon_type = self.weapon["wtype"] self.weapon_type = self.weapon["wtype"]
self.true_raw = (self.weapon["attack"] self.true_raw = (self.weapon["attack"]
/ WeaponType.multiplier(self.weapon_type)) / WeaponType.multiplier(self.weapon_type))
sharp = _parse_sharpness(self.weapon)
if sharp_plus: if sharp_plus:
self.sharpness = sharp[1] self.sharpness = self.weapon.sharpness_plus.max
else: else:
self.sharpness = sharp[0] self.sharpness = self.weapon.sharpness.max
#print "sharpness=", self.sharpness #print "sharpness=", self.sharpness
self.affinity = int(self.weapon["affinity"] or 0) self.affinity = int(self.weapon["affinity"] or 0)
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"]
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.parts = []
self.break_count = 0 self.break_count = 0
@ -234,8 +220,13 @@ class WeaponMonsterDamage(object):
self.max_element_part = (None, 0) self.max_element_part = (None, 0)
self._calculate_damage() self._calculate_damage()
@property
def attack(self):
return self.true_raw * WeaponType.multiplier(self.weapon_type)
def _calculate_damage(self): 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"] part = row["body_part"]
alt = None alt = None
m = re.match(r"([^(]+) \(([^)]+)\)", part) m = re.match(r"([^(]+) \(([^)]+)\)", part)
@ -548,18 +539,3 @@ def element_x_attack_up(value, level=1):
value += 90 value += 90
else: else:
raise ValueError("level must be 1, 2, or 3") 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

@ -2,19 +2,69 @@
Module for accessing the sqlite monster hunter db from Module for accessing the sqlite monster hunter db from
""" """
import os.path
import sqlite3 import sqlite3
from mhapi import model 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): 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 = sqlite3.connect(path)
self.conn.row_factory = sqlite3.Row self.conn.row_factory = sqlite3.Row
self.use_cache = use_cache self.use_cache = use_cache
self.cache = {} self.cache = {}
def _get_memoized(self, key, query, *args): def _query_one(self, key, query, args=(), model_cls=None,
if self.use_cache: 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: if key in self.cache:
v = self.cache[key].get(args) v = self.cache[key].get(args)
if v is not None: if v is not None:
@ -22,10 +72,14 @@ class MHDB(object):
else: else:
self.cache[key] = {} self.cache[key] = {}
cursor = self.conn.execute(query, args) cursor = self.conn.execute(query, args)
v = cursor.fetchall() rows = cursor.fetchall()
if self.use_cache: if model_cls:
self.cache[key][args] = v rows = [model_cls(row) for row in rows]
return v 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): def cursor(self):
return self.conn.cursor() return self.conn.cursor()
@ -36,41 +90,65 @@ class MHDB(object):
def close(self): def close(self):
return self.conn.close() 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): def get_item_names(self, item_types):
"""
List of unicode strings.
"""
item_types.sort() item_types.sort()
placeholders = ", ".join(["?"] * len(item_types)) placeholders = ", ".join(["?"] * len(item_types))
v = self._get_memoized("item_names", """ return self._query_all("item_names", """
SELECT _id, name FROM items SELECT _id, name FROM items
WHERE type IN (%s) WHERE type IN (%s)
""" % placeholders, *item_types) """ % placeholders, tuple(item_types), model_cls=field_model("name"))
return v
def get_monster_names(self): def get_items(self, item_types=None):
v = self._get_memoized("monster_names", """ """
SELECT name FROM monsters List of item objects.
""") """
return v 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): def get_item(self, item_id):
v = self._get_memoized("item", """ """
Single item object or None.
"""
return self._query_one("item", """
SELECT * FROM items SELECT * FROM items
WHERE _id=? WHERE _id=?
""", item_id) """, (item_id,), model_cls=model.Item)
return v[0] if v else None
def get_item_by_name(self, name): 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 SELECT * FROM items
WHERE name=? WHERE name=?
""", name) """, (name,), model_cls=model.Item)
return v[0] if v else None
def get_wyporium_trade(self, item_id): 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 SELECT * FROM wyporium
WHERE item_in_id=? WHERE item_in_id=?
""", item_id) """, (item_id,))
return v[0] if v else None
def search_item_name(self, term, item_type=None): def search_item_name(self, term, item_type=None):
""" """
@ -92,42 +170,50 @@ class MHDB(object):
query += "AND type = ?" query += "AND type = ?"
args += [item_type] args += [item_type]
cursor = self.conn.execute(query, args) return self._query_all("search_item", query, tuple(args),
return cursor.fetchall() no_cache=True, model_cls=model.Item)
def get_monster_by_name(self, name): def get_monsters(self):
v = self._get_memoized("monster", """ return self._query_all("monsters", """
SELECT * FROM monsters SELECT * FROM monsters
WHERE name=? """, model_cls=model.Monster)
""", name)
return v[0] if v else None 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): def get_monster(self, monster_id):
v = self._get_memoized("monster", """ return self._query_one("monster", """
SELECT * FROM monsters SELECT * FROM monsters
WHERE _id=? WHERE _id=?
""", monster_id) """, (monster_id,), model_cls=model.Monster)
return v[0] if v else None
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): def get_quest(self, quest_id):
v = self._get_memoized("quest", """ return self._query_one("quest", """
SELECT * FROM quests SELECT * FROM quests
WHERE _id=? WHERE _id=?
""", quest_id) """, (quest_id,), model_cls=model.Quest)
return v[0] if v else None
def get_quests(self): def get_quests(self):
v = self._get_memoized("quests", """ return self._query_all("quests", """
SELECT * FROM quests SELECT * FROM quests
""") """, model_cls=model.Quest)
return v
def get_quest_rewards(self, quest_id): def get_quest_rewards(self, quest_id):
v = self._get_memoized("quest_rewards", """ return self._query_all("quest_rewards", """
SELECT * FROM quest_rewards SELECT * FROM quest_rewards
WHERE quest_id=? WHERE quest_id=?
""", quest_id) """, (quest_id,))
return v
def get_monster_rewards(self, monster_id, rank=None): def get_monster_rewards(self, monster_id, rank=None):
q = """ q = """
@ -136,19 +222,18 @@ class MHDB(object):
""" """
if rank is not None: if rank is not None:
q += "AND rank=?" q += "AND rank=?"
v = self._get_memoized("monster_rewards", q, monster_id, rank) args = (monster_id, rank)
else: else:
v = self._get_memoized("monster_rewards", q, monster_id) args = (monster_id,)
return v return self._query_all("monster_rewards", q, args)
def get_quest_monsters(self, quest_id): 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 SELECT monster_id, unstable FROM monster_to_quest
WHERE quest_id=? WHERE quest_id=?
""", quest_id) """, (quest_id,))
return v
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 Get a list of quests that provide the specified item in quest
reqards. Returns a list of quest objects, which encapsulate the reqards. Returns a list of quest objects, which encapsulate the
@ -163,79 +248,70 @@ class MHDB(object):
quests = [] quests = []
for r in rows: for r in rows:
quest_row = self.get_quest(r["quest_id"]) quest_id = r["quest_id"]
rewards_rows = self.get_quest_rewards(r["quest_id"]) quest = self.get_quest(quest_id)
quests.append(model.Quest(quest_row, rewards_rows)) quest.rewards = self.get_quest_rewards(quest_id)
quests.append(quest)
return quests return quests
def get_item_monsters(self, item_id): 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 SELECT DISTINCT monster_id, rank FROM hunting_rewards
WHERE item_id=? WHERE item_id=?
""", item_id) """, (item_id,))
return v
def get_item_gathering(self, item_id): def get_item_gathering(self, item_id):
v = self._get_memoized("item_gathering", """ return self._query_all("item_gathering", """
SELECT * FROM gathering SELECT * FROM gathering
WHERE item_id=? WHERE item_id=?
""", item_id) """, (item_id,))
return v
def get_location(self, location_id): def get_location(self, location_id):
v = self._get_memoized("location", """ self._query_one("location", """
SELECT * FROM locations SELECT * FROM locations
WHERE _id=? WHERE _id=?
""", location_id) """, (location_id,), model_cls=model.Location)
return v
def get_locations(self): def get_locations(self):
v = self._get_memoized("locations", """ return self._query_all("locations", """
SELECT * FROM locations SELECT * FROM locations
""") """, model_cls=model.Location)
return v
def get_monster_damage(self, monster_id): def get_monster_damage(self, monster_id):
v = self._get_memoized("monster_damage", """ return self._query_all("monster_damage", """
SELECT * FROM monster_damage SELECT * FROM monster_damage
WHERE monster_id=? WHERE monster_id=?
""", monster_id) """, (monster_id,), collection_cls=model.MonsterDamage)
return v
def get_weapons(self): def get_weapons(self):
v = self._get_memoized("weapons", """ return self._query_all("weapons", """
SELECT * FROM weapons SELECT * FROM weapons
LEFT JOIN items ON weapons._id = items._id LEFT JOIN items ON weapons._id = items._id
""") """, model_cls=model.Weapon)
return v
def get_weapon(self, weapon_id): def get_weapon(self, weapon_id):
v = self._get_memoized("weapon", """ return self._query_one("weapon", """
SELECT * FROM weapons SELECT * FROM weapons
LEFT JOIN items ON weapons._id = items._id LEFT JOIN items ON weapons._id = items._id
WHERE weapons._id=? WHERE weapons._id=?
""", weapon_id) """, (weapon_id,), model_cls=model.Weapon)
return v[0] if v else None
def get_weapon_by_name(self, name): def get_weapon_by_name(self, name):
v = self._get_memoized("weapon", """ return self._query_one("weapon", """
SELECT * FROM weapons SELECT * FROM weapons
LEFT JOIN items ON weapons._id = items._id LEFT JOIN items ON weapons._id = items._id
WHERE items.name=? WHERE items.name=?
""", name) """, (name,), model_cls=model.Weapon)
return v[0] if v else None
def get_monster_breaks(self, monster_id): 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 SELECT DISTINCT condition FROM hunting_rewards
WHERE monster_id=? AND condition LIKE 'Break %' WHERE monster_id=? AND condition LIKE 'Break %'
""", monster_id) """, (monster_id,), model_cls=model)
return [row["condition"][len("Break "):] for row in v]

@ -1,28 +1,92 @@
import string import string
import json 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): def __init__(self, row):
self._row = row
self.id = row["_id"] 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): def __getattr__(self, name):
try: try:
return self._row[name] return self._data[name]
except IndexError: except IndexError:
raise AttributeError("'%s' object has no attribute '%s'" raise AttributeError("'%s' object has no attribute '%s'"
% (self.__class__.__name__, name)) % (self.__class__.__name__, name))
def as_dict(self): def __getitem__(self, key):
d = dict(self._row) return self._data[key]
d["id"] = d["_id"]
del d["_id"] def fields(self):
return d return self._data.keys()
def as_data(self):
return self._data
def as_json(self): def as_list_data(self):
data = self.as_dict() list_data = {}
return json.dumps(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 "<mhapi.model.%s %d>" % (self.__class__.__name__, self.id)
class Quest(RowModel): class Quest(RowModel):
@ -47,11 +111,178 @@ class Quest(RowModel):
or " all " in self.goal) or " all " in self.goal)
def one_line_u(self): 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): def full_u(self):
return self._full_template.substitute(self.as_dict()) return self._full_template.substitute(self.as_data())
def __unicode__(self): def __unicode__(self):
return self.full_u() 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
)

@ -78,7 +78,7 @@ class GatherLocation(object):
Track total expected value for an item in one location/rank. Track total expected value for an item in one location/rank.
""" """
def __init__(self, location_row, rank, gathering_rows): 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.location_name = location_row["name"]
self.rank = rank self.rank = rank
self._rewards = [] self._rewards = []
@ -675,12 +675,11 @@ class ItemRewards(object):
def __init__(self, db, item_row): def __init__(self, db, item_row):
self.db = db self.db = db
self.item_row = item_row 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) wyp_row = db.get_wyporium_trade(self.item_id)
if wyp_row is not None: if wyp_row is not None:
self.trade_unlock_quest = Quest( self.trade_unlock_quest = db.get_quest(wyp_row["unlock_quest_id"])
db.get_quest(wyp_row["unlock_quest_id"]))
self.trade_item_row = self.item_row self.trade_item_row = self.item_row
self.trade_item_id = self.item_id self.trade_item_id = self.item_id
self.item_id = wyp_row["item_out_id"] self.item_id = wyp_row["item_out_id"]
@ -728,7 +727,7 @@ class ItemRewards(object):
for rank in "LR HR G".split(): for rank in "LR HR G".split():
gl = GatherLocation(loc, rank, gathering_rows) gl = GatherLocation(loc, rank, gathering_rows)
if gl: if gl:
key = (loc["_id"], rank) key = (loc.id, rank)
self._gather_items[key] = gl self._gather_items[key] = gl
def _find_hunt_items(self): 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 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. 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: if not quests:
return return
for q in quests: for q in quests:

@ -1,14 +1,8 @@
from mhapi.util import EnumBase
class SkillEnum(object):
_names = dict()
@classmethod class CapSkill(EnumBase):
def name(cls, skill_id):
return cls._names[skill_id]
class CapSkill(SkillEnum):
NONE = 0 NONE = 0
EXPERT = 1 EXPERT = 1
MASTER = 2 MASTER = 2
@ -20,7 +14,7 @@ class CapSkill(SkillEnum):
GOD: "Capture God" } GOD: "Capture God" }
class LuckSkill(SkillEnum): class LuckSkill(EnumBase):
NONE = 0 NONE = 0
GOOD = 1 GOOD = 1
GREAT = 2 GREAT = 2
@ -32,7 +26,7 @@ class LuckSkill(SkillEnum):
AMAZING: "Magnificent Luck" } AMAZING: "Magnificent Luck" }
class CarvingSkill(SkillEnum): class CarvingSkill(EnumBase):
NONE = 0 NONE = 0
PRO = 0 # prevent knockbacks but no extra carves PRO = 0 # prevent knockbacks but no extra carves
FELYNE_LOW = 1 FELYNE_LOW = 1
@ -51,3 +45,91 @@ QUEST_A = "A"
QUEST_B = "B" QUEST_B = "B"
QUEST_SUB = "Sub" 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

@ -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]
Loading…
Cancel
Save