model and db refactor, damage improvements
This commit is contained in:
@@ -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
|
||||
|
||||
250
mhapi/db.py
250
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)
|
||||
|
||||
257
mhapi/model.py
257
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 RowModel(object):
|
||||
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"] }
|
||||
|
||||
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 as_json(self):
|
||||
data = self.as_dict()
|
||||
return json.dumps(data)
|
||||
def fields(self):
|
||||
return self._data.keys()
|
||||
|
||||
def as_data(self):
|
||||
return self._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:
|
||||
|
||||
104
mhapi/skills.py
104
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
|
||||
|
||||
11
mhapi/util.py
Normal file
11
mhapi/util.py
Normal file
@@ -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]
|
||||
Reference in New Issue
Block a user