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 = 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

@ -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)

File diff suppressed because one or more lines are too long

@ -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

@ -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)

@ -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 "<mhapi.model.%s %d>" % (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
)

@ -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:

@ -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

@ -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