add armor search, damage fixes
This commit is contained in:
109
bin/mharmor.py
Executable file
109
bin/mharmor.py
Executable file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import codecs
|
||||
|
||||
import _pathfix
|
||||
|
||||
from mhapi.db import MHDB
|
||||
|
||||
def get_utf8_writer(writer):
|
||||
return codecs.getwriter("utf8")(writer)
|
||||
|
||||
def parse_args(argv):
|
||||
parser = argparse.ArgumentParser(description=
|
||||
"Find armor with the specified skills and sort by"
|
||||
" (max points, defense). Takes into account native points"
|
||||
" and slots that fit decorations for the first skill."
|
||||
)
|
||||
parser.add_argument("-g", "--gunner", action="store_true",
|
||||
default=False,
|
||||
help="search for gunner instead of blademaster")
|
||||
parser.add_argument("-d", "--min-defense", type=int,
|
||||
help="Only include armors with min defense")
|
||||
parser.add_argument("-t", "--type",
|
||||
help="Head, Body, Arms, Waist, or Legs")
|
||||
parser.add_argument("skills", nargs="+",
|
||||
help="One or more armor skills to search for")
|
||||
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def find_armors(args):
|
||||
db = MHDB(_pathfix.db_path)
|
||||
|
||||
skills = {}
|
||||
skill_ids = [] # preserve arg order
|
||||
decorations = {}
|
||||
for skill_name in args.skills:
|
||||
sid = db.get_skill_tree_id(skill_name)
|
||||
if sid is None:
|
||||
raise ValueError("Skill '%s' not found" % skill_name)
|
||||
skills[skill_name] = sid
|
||||
skill_ids.append(sid)
|
||||
#print skill_name, sid
|
||||
ds = db.get_decorations_by_skills([sid])
|
||||
for d in ds:
|
||||
d.set_skills(db.get_item_skills(d.id))
|
||||
decoration_values = get_decoration_values(sid, ds)
|
||||
decorations[sid] = (ds, decoration_values)
|
||||
print "%s[%s]:" % (skill_name, sid), ", ".join(d.name for d in ds), \
|
||||
decoration_values
|
||||
|
||||
htype = "Gunner" if args.gunner else "Blade"
|
||||
|
||||
armors = db.get_armors_by_skills(skill_ids, htype)
|
||||
|
||||
skill_totals = {}
|
||||
for a in armors:
|
||||
skills = db.get_item_skills(a.id)
|
||||
if not skills:
|
||||
print "Error getting skills for '%s' (%d)" % (a.name, a.id)
|
||||
sys.exit(1)
|
||||
a.set_skills(skills)
|
||||
# calculate total using decorations for first skill only. This
|
||||
# works great if all skill shave same slot values; if not it's
|
||||
# very messy to figure out what is 'best'
|
||||
total = 0
|
||||
first = True
|
||||
for sid in skill_ids:
|
||||
if first:
|
||||
dv = decorations[sid][1]
|
||||
first = False
|
||||
else:
|
||||
dv = []
|
||||
total += a.skill(sid, dv)
|
||||
skill_totals[a.id] = total
|
||||
|
||||
armors.sort(key=lambda a: (skill_totals[a.id], a.defense), reverse=True)
|
||||
|
||||
for a in armors:
|
||||
if args.min_defense and a.defense < args.min_defense:
|
||||
continue
|
||||
if args.type and a.slot != args.type:
|
||||
continue
|
||||
total = skill_totals[a.id]
|
||||
print a.id, skill_totals[a.id], a.one_line_u()
|
||||
print " ", a.one_line_skills_u(args.skills)
|
||||
|
||||
|
||||
def get_decoration_values(skill_id, decorations):
|
||||
# TODO: write script to compute this and shove innto skill_tree table
|
||||
values = [0, 0, 0]
|
||||
for d in decorations:
|
||||
assert d.num_slots is not None
|
||||
# some skills like Handicraft have multiple decorations with
|
||||
# same number of slots - use the best one
|
||||
new = d.skills[skill_id]
|
||||
current = values[d.num_slots-1]
|
||||
if new > current:
|
||||
values[d.num_slots-1] = new
|
||||
return values
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parse_args(None)
|
||||
|
||||
sys.stdout = get_utf8_writer(sys.stdout)
|
||||
find_armors(args)
|
||||
@@ -18,9 +18,11 @@ def percent_change(a, b):
|
||||
|
||||
|
||||
def parse_args(argv):
|
||||
parser = argparse.ArgumentParser(
|
||||
parser = argparse.ArgumentParser(description=
|
||||
"Calculate damage to monster from different weapons of the"
|
||||
" same class"
|
||||
" same class. The average motion value for the weapon class"
|
||||
" is used for raw damage calculations, to get a rough idea of"
|
||||
" the relative damage from raw vs element when comparing."
|
||||
)
|
||||
parser.add_argument("-s", "--sharpness-plus-one", action="store_true",
|
||||
default=False,
|
||||
|
||||
@@ -5,6 +5,7 @@ import json
|
||||
import sys
|
||||
import errno
|
||||
from collections import defaultdict
|
||||
import urllib
|
||||
|
||||
import _pathfix
|
||||
|
||||
@@ -20,6 +21,17 @@ def mkdirs_p(path):
|
||||
raise
|
||||
|
||||
|
||||
SAFE_CHARS = " &'+\""
|
||||
|
||||
|
||||
def file_path(path, model_object, use_name=False):
|
||||
if use_name and "name" in model_object:
|
||||
key = urllib.quote(model_object.name.encode("utf8"), SAFE_CHARS)
|
||||
else:
|
||||
key = str(model_object.id)
|
||||
return os.path.join(path, "%s.json" % key)
|
||||
|
||||
|
||||
def write_list_file(path, model_list):
|
||||
list_path = os.path.join(path, "_list.json")
|
||||
with open(list_path, "w") as f:
|
||||
@@ -39,7 +51,7 @@ def monster_json(db, path):
|
||||
|
||||
indexes = {}
|
||||
for m in monsters:
|
||||
monster_path = os.path.join(path, "%s.json" % m.id)
|
||||
monster_path = file_path(path, m)
|
||||
m.update_indexes(indexes)
|
||||
data = m.as_data()
|
||||
damage = db.get_monster_damage(m.id)
|
||||
@@ -58,7 +70,7 @@ def weapon_json(db, path):
|
||||
|
||||
indexes = {}
|
||||
for w in weapons:
|
||||
weapon_path = os.path.join(path, "%s.json" % w.id)
|
||||
weapon_path = file_path(path, w)
|
||||
w.update_indexes(indexes)
|
||||
with open(weapon_path, "w") as f:
|
||||
w.json_dump(f)
|
||||
@@ -73,7 +85,7 @@ def items_json(db, path):
|
||||
|
||||
indexes = {}
|
||||
for item in items:
|
||||
item_path = os.path.join(path, "%s.json" % item.id)
|
||||
item_path = file_path(path, item)
|
||||
item.update_indexes(indexes)
|
||||
with open(item_path, "w") as f:
|
||||
item.json_dump(f)
|
||||
|
||||
@@ -199,6 +199,11 @@ class WeaponMonsterDamage(object):
|
||||
self.etype = self.weapon.awaken
|
||||
self.eattack = self.weapon.awaken_attack
|
||||
|
||||
if self.eattack:
|
||||
self.eattack = int(self.eattack)
|
||||
else:
|
||||
self.eattack = 0
|
||||
|
||||
self.true_raw = skills.AttackUp.modified(attack_skill,
|
||||
self.true_raw)
|
||||
self.affinity = skills.CriticalEye.modified(critical_eye_skill,
|
||||
|
||||
104
mhapi/db.py
104
mhapi/db.py
@@ -102,12 +102,12 @@ class MHDB(object):
|
||||
"""
|
||||
List of unicode strings.
|
||||
"""
|
||||
item_types.sort()
|
||||
args = sorted(item_types)
|
||||
placeholders = ", ".join(["?"] * len(item_types))
|
||||
return self._query_all("item_names", """
|
||||
SELECT _id, name FROM items
|
||||
WHERE type IN (%s)
|
||||
""" % placeholders, tuple(item_types), model_cls=field_model("name"))
|
||||
""" % placeholders, tuple(args), model_cls=field_model("name"))
|
||||
|
||||
def get_items(self, item_types=None):
|
||||
"""
|
||||
@@ -115,7 +115,7 @@ class MHDB(object):
|
||||
"""
|
||||
q = "SELECT * FROM items"
|
||||
if item_types:
|
||||
item_types.sort()
|
||||
item_types = sorted(item_types)
|
||||
placeholders = ", ".join(["?"] * len(item_types))
|
||||
q += "\nWHERE type IN (%s)" % placeholders
|
||||
item_types = tuple(item_types)
|
||||
@@ -304,6 +304,104 @@ class MHDB(object):
|
||||
WHERE items.name=?
|
||||
""", (name,), model_cls=model.Weapon)
|
||||
|
||||
def get_armors(self):
|
||||
return self._query_all("armors", """
|
||||
SELECT * FROM armor
|
||||
LEFT JOIN items ON armor._id = items._id
|
||||
""", model_cls=model.Armor)
|
||||
|
||||
def get_armor(self, armor_id):
|
||||
return self._query_one("armor", """
|
||||
SELECT * FROM armor
|
||||
LEFT JOIN items ON armor._id = items._id
|
||||
WHERE armor._id=?
|
||||
""", (armor_id,), model_cls=model.Armor)
|
||||
|
||||
def get_armor_by_name(self, name):
|
||||
return self._query_one("armor", """
|
||||
SELECT * FROM armor
|
||||
LEFT JOIN items ON armor._id = items._id
|
||||
WHERE items.name=?
|
||||
""", (name,), model_cls=model.Armor)
|
||||
|
||||
def get_item_skills(self, item_id):
|
||||
return self._query_all("item_skills", """
|
||||
SELECT item_to_skill_tree.*, skill_trees.name
|
||||
FROM item_to_skill_tree
|
||||
LEFT JOIN skill_trees
|
||||
ON item_to_skill_tree.skill_tree_id = skill_trees._id
|
||||
WHERE item_to_skill_tree.item_id=?
|
||||
""", (item_id,), model_cls=model.ItemSkill)
|
||||
|
||||
def get_decorations(self):
|
||||
return self._query_all("decorations", """
|
||||
SELECT *
|
||||
FROM decorations
|
||||
INNER JOIN items
|
||||
ON items._id = decorations._id
|
||||
""", model_cls=model.Decoration)
|
||||
|
||||
def get_decoration(self, decoration_id):
|
||||
return self._query_one("decoration", """
|
||||
SELECT *
|
||||
FROM decorations
|
||||
INNER JOIN items
|
||||
ON items._id = decorations._id
|
||||
WHERE decorations._id = ?
|
||||
""", (decoration_id,), model_cls=model.Decoration)
|
||||
|
||||
def get_decoration_by_name(self, name):
|
||||
return self._query_all("decoration", """
|
||||
SELECT *
|
||||
FROM decorations
|
||||
INNER JOIN items
|
||||
ON items._id = decorations._id
|
||||
WHERE items.name = ?
|
||||
""", (name,), model_cls=model.Decoration)
|
||||
|
||||
def get_skill_tree_id(self, skill_tree_name):
|
||||
result = self._query_one("skill", """
|
||||
SELECT _id FROM skill_trees
|
||||
WHERE name=?
|
||||
""", (skill_tree_name,))
|
||||
if result:
|
||||
return result["_id"]
|
||||
return None
|
||||
|
||||
def get_decorations_by_skills(self, skill_tree_ids):
|
||||
args = sorted(skill_tree_ids)
|
||||
placeholders = ", ".join(["?"] * len(skill_tree_ids))
|
||||
return self._query_all("decorations", """
|
||||
SELECT items.*, decorations.*
|
||||
FROM item_to_skill_tree
|
||||
LEFT JOIN items
|
||||
ON items._id = item_to_skill_tree.item_id
|
||||
LEFT JOIN decorations
|
||||
ON decorations._id = item_to_skill_tree.item_id
|
||||
WHERE items.type = 'Decoration'
|
||||
AND item_to_skill_tree.skill_tree_id IN (%s)
|
||||
AND item_to_skill_tree.point_value > 0
|
||||
GROUP BY item_to_skill_tree.item_id
|
||||
""" % placeholders, tuple(args), model_cls=model.Decoration)
|
||||
|
||||
def get_armors_by_skills(self, skill_tree_ids, hunter_type):
|
||||
args = sorted(skill_tree_ids)
|
||||
placeholders = ", ".join(["?"] * len(skill_tree_ids))
|
||||
args += [hunter_type]
|
||||
return self._query_all("decorations", """
|
||||
SELECT items.*, armor.*
|
||||
FROM item_to_skill_tree
|
||||
LEFT JOIN items
|
||||
ON items._id = item_to_skill_tree.item_id
|
||||
LEFT JOIN armor
|
||||
ON armor._id = item_to_skill_tree.item_id
|
||||
WHERE items.type = 'Armor'
|
||||
AND item_to_skill_tree.skill_tree_id IN (%s)
|
||||
AND item_to_skill_tree.point_value > 0
|
||||
AND armor.hunter_type IN ('Both', ?)
|
||||
GROUP BY item_to_skill_tree.item_id
|
||||
""" % placeholders, tuple(args), model_cls=model.Armor)
|
||||
|
||||
def get_monster_breaks(self, monster_id):
|
||||
"""
|
||||
List of strings.
|
||||
|
||||
@@ -54,6 +54,9 @@ class RowModel(ModelBase):
|
||||
def __getitem__(self, key):
|
||||
return self._data[key]
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._data
|
||||
|
||||
def fields(self):
|
||||
return self._data.keys()
|
||||
|
||||
@@ -190,6 +193,76 @@ class WeaponSharpness(ModelBase):
|
||||
return self.value_list
|
||||
|
||||
|
||||
class ItemWithSkills(RowModel):
|
||||
def __init__(self, item_row):
|
||||
super(ItemWithSkills, self).__init__(item_row)
|
||||
self.skills = None
|
||||
self.skill_ids = []
|
||||
self.skill_names = []
|
||||
|
||||
def set_skills(self, item_skills):
|
||||
self.skills = {}
|
||||
for s in item_skills:
|
||||
self.skills[s.skill_tree_id] = s.point_value
|
||||
self.skills[s.name] = s.point_value
|
||||
self.skill_ids.append(s.skill_tree_id)
|
||||
self.skill_names.append(s.name)
|
||||
|
||||
def skill(self, skill_id_or_name):
|
||||
return self.skills.get(skill_id_or_name, 0)
|
||||
|
||||
def one_line_skills_u(self, skill_names=None):
|
||||
if skill_names is None:
|
||||
skill_names = sorted(self.skill_names)
|
||||
return ", ".join("%s %d" % (name, self.skills[name])
|
||||
for name in skill_names
|
||||
if name in self.skills)
|
||||
|
||||
|
||||
class Armor(ItemWithSkills):
|
||||
_indexes = { "name": ["id"],
|
||||
"slot": ["id", "name"] }
|
||||
|
||||
_one_line_template = string.Template(
|
||||
"$name ($slot) Def $defense-$max_defense Slot $num_slots"
|
||||
)
|
||||
|
||||
def __init__(self, armor_item_row):
|
||||
super(Armor, self).__init__(armor_item_row)
|
||||
|
||||
def one_line_u(self):
|
||||
return self._one_line_template.substitute(self.as_data())
|
||||
|
||||
def skill(self, skill_id_or_name, decoration_values=()):
|
||||
"""
|
||||
decoration_values should be a list of points from the given
|
||||
number of slots, e.g. [1, 3] means that one slot gets 1 point
|
||||
and two slots get 3 points, [1, 0, 4] means that one slot gets 1 point,
|
||||
there is no two slot gem, and three slots gets 4 points.
|
||||
"""
|
||||
assert self.skills is not None
|
||||
total = self.skills.get(skill_id_or_name, 0)
|
||||
slots_left = self.num_slots
|
||||
for slots in xrange(len(decoration_values), 0, -1):
|
||||
if slots_left == 0:
|
||||
break
|
||||
decoration_value = decoration_values[slots-1]
|
||||
if not decoration_value:
|
||||
continue
|
||||
if slots <= slots_left:
|
||||
total += decoration_value
|
||||
slots_left -= slots
|
||||
return total
|
||||
|
||||
|
||||
class Decoration(ItemWithSkills):
|
||||
pass
|
||||
|
||||
|
||||
class ItemSkill(RowModel):
|
||||
pass
|
||||
|
||||
|
||||
class Weapon(RowModel):
|
||||
_list_fields = ["id", "wtype", "name"]
|
||||
_indexes = { "name": ["id"],
|
||||
|
||||
Reference in New Issue
Block a user