Compare commits

...

5 Commits

4
.gitignore vendored

@ -2,3 +2,7 @@
README.html
RECOMMENDATIONS.html
tmp/
*.bak
web/jsonapi/
web/*/rewards/
web/*/damage/

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# Set PYTHONPATH in lighttpd or other server config.

@ -1,10 +1,10 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
"""
Script to generate static rewards files for all items.
"""
import codecs
import urllib
import urllib.request, urllib.parse, urllib.error
import os.path
import _pathfix
@ -27,7 +27,7 @@ if __name__ == '__main__':
elif len(sys.argv) == 2:
outdir = sys.argv[1]
else:
print("Usage: %s [outdir]" % sys.argv[0])
print(("Usage: %s [outdir]" % sys.argv[0]))
sys.exit(os.EX_USAGE)
err_out = get_utf8_writer(sys.stderr)
@ -46,7 +46,7 @@ if __name__ == '__main__':
# write all names json to /items.json
items_file = os.path.join(outdir, "items.json")
print "Writing", items_file
print("Writing", items_file)
with open(items_file, "w") as f:
out = get_utf8_writer(f)
out.write("[")
@ -66,7 +66,7 @@ if __name__ == '__main__':
item_id = item.id
encoded_name = name.encode("utf8")
item_file = os.path.join(outdir, encoded_name + ".txt")
print "Writing", item_id, item_file
print("Writing", item_id, item_file)
with open(item_file, "w") as f:
out = get_utf8_writer(f)
item_row = rewards.find_item(db, name, err_out)

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
import sys
import argparse
@ -57,7 +57,7 @@ def find_armors(args):
matches = difflib.get_close_matches(skill_name, skill_tree_names,
1, 0.5)
if matches:
print "Fuzzy Match:", matches[0]
print("Fuzzy Match:", matches[0])
sid = skill_tree_id_map.get(matches[0])
skill_name = matches[0]
args.skills[i] = skill_name
@ -71,8 +71,8 @@ def find_armors(args):
d.set_skills(db.get_item_skills(d.id))
decoration_values = get_decoration_values(sid, ds)[1]
decorations[sid] = (ds, decoration_values)
print "%s[%s]:" % (skill_name, sid), ", ".join(d.name for d in 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"
@ -82,7 +82,7 @@ def find_armors(args):
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)
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
@ -113,12 +113,12 @@ def find_armors(args):
if args.type and a.slot != args.type:
continue
total = skill_totals[a.id]
print skill_totals[a.id], a.one_line_u(),
print(skill_totals[a.id], a.one_line_u(), end=' ')
if args.resist:
print args.resist.title(), a[args.resist + "_res"]
print(args.resist.title(), a[args.resist + "_res"])
else:
print
print " ", a.one_line_skills_u(args.skills)
print()
print(" ", a.one_line_skills_u(args.skills))
def str_lower(x):

@ -1,17 +1,21 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
import sys
import argparse
import shlex
import copy
import codecs
import os
import os.path
from collections import defaultdict
import _pathfix
from mhapi.db import MHDB, MHDBX
from mhapi.damage import MotionValueDB, WeaponMonsterDamage
from mhapi.damage import MotionValueDB, WeaponMonsterDamage, WeaponType
from mhapi.model import SharpnessLevel, Weapon, ItemStars
from mhapi import skills
from mhapi.util import ELEMENTS, WEAPON_TYPES, WTYPE_ABBR
from mhapi.util import ELEMENTS, WEAPON_TYPES, WTYPE_ABBR, DAMAGE_TYPES
def weapon_match_tuple(arg):
@ -54,7 +58,7 @@ def _make_db_sharpness_string(level_string):
level_value = SharpnessLevel.__dict__[level_string.upper()]
#print "level value", level_value
values = []
for i in xrange(SharpnessLevel.PURPLE+1):
for i in range(SharpnessLevel.PURPLE+1):
if i <= level_value:
values.append("1")
else:
@ -65,9 +69,9 @@ def _make_db_sharpness_string(level_string):
def weapon_stats_tuple(arg):
parts = arg.split(",")
#print "parts %r" % parts
#print("parts %r" % parts)
if len(parts) < 4:
print "not enough parts"
print("not enough parts")
raise ValueError("Bad arg, use 'name,weapon_type,sharpness,raw'")
weapon = {}
weapon["name"] = parts[0]
@ -154,14 +158,14 @@ def _add_skill_args(parser):
default=False,
help="add Awaken (FreeElemnt), default off")
parser.add_argument("-a", "--attack-up",
type=int, choices=range(0, 5), default=0,
type=int, choices=list(range(0, 5)), default=0,
help="1-4 for AuS, M, L, XL")
parser.add_argument("-c", "--critical-eye",
type=int, choices=range(0, 5), default=0,
type=int, choices=list(range(0, 5)), default=0,
help="1-4 for CE+1, +2, +3 and Critical God")
parser.add_argument("-e", "--element-up",
type=int, choices=range(0, 5), default=0,
help="1-4 for (element) Atk +1, +2, +3 and"
type=int, choices=list(range(0, 6)), default=0,
help="1-5 for (element) Atk +1, +2, +3 and"
" Element Attack Up")
parser.add_argument("-t", "--artillery",
type=int, choices=[0,1,2], default=0,
@ -206,6 +210,9 @@ def parse_args(argv):
parser.add_argument("--mhw", "--monster-hunter-world", action="store_true",
default=False,
help="Adjusted attack, use MHWorld values")
parser.add_argument("--mhr", "--monster-hunter-rise", action="store_true",
default=False,
help="True attack, use MHRise values")
parser.add_argument("-m", "--match", nargs="*",
help="WEAPON_TYPE,ELEMENT_OR_STATUS_OR_RAW"
+" Include all matching weapons in their final form."
@ -216,19 +223,27 @@ def parse_args(argv):
+" Examples: 'Great Sword,Raw'"
+" 'Sword and Shield,Para'"
+" 'HH,Blast' 'Hammer'",
type=weapon_match_tuple, default=[])
type=weapon_match_tuple, default=[],
action="append")
parser.add_argument("-w", "--weapon-custom", nargs="*",
help="NAME,WEAPON_TYPE,TRUE_RAW,AFFINITY,SHARPNESS"
+"ELEMENT_TYPE,ELEMENT_ATTACK"
+" Add weapon based on stats."
+" Examples: 'DinoSnS,SnS,190,0,Blue,Fire,30'"
+" 'AkantorHam,Hammer,240,25,Green'",
type=weapon_stats_tuple, default=[])
type=weapon_stats_tuple, default=[],
action="append")
parser.add_argument("-q", "--quest-level",
help="village,guild[,permit[,arena]]",
type=quest_level_tuple)
parser.add_argument("monster",
help="Full name of monster")
parser.add_argument("-r", "--rarity",
help="include weapons of given type with max rarity",
type=int, nargs="?")
parser.add_argument("--html-out",
help="Write table of values as HTML and save to path")
parser.add_argument("--html-site",
help="Write entire site of all monster & quest levels")
parser.add_argument("-n", "--monster", help="Full name of monster")
parser.add_argument("weapon", nargs="*",
help="One or more weapons of same class to compare,"
" full names")
@ -253,24 +268,117 @@ def print_sorted_phial_damage(names, damage_map_base, weapon_damage_map, parts,
_print_headers(parts, damage_map_base)
for name in names_sorted:
print "%-20s:" % name,
print("%-20s:" % name, end=' ')
damage_map = weapon_damage_map[name]
print "%0.2f" % avg_phial(damage_map, level=level),
print("%0.2f" % avg_phial(damage_map, level=level), end=' ')
for part in parts:
part_damage = damage_map[part]
#print "%0.2f" % sum(damage_map.cb_phial_damage[part][level]),
print "%0.2f:%0.2f:%0.2f" % damage_map.cb_phial_damage[part][level],
print
print("%0.2f:%0.2f:%0.2f" % damage_map.cb_phial_damage[part][level], end=' ')
print()
def _print_headers(parts, damage_map_base):
print
print()
avg_hitbox = (sum(damage_map_base[part].hitbox for part in parts)
/ float(len(parts)))
cols = ["%s (%d)" % (part, damage_map_base[part].hitbox)
for part in parts]
cols = ["%s (%d)" % ("Avg", avg_hitbox)] + cols
print " | ".join(cols)
print(" | ".join(cols))
def write_damage_html(path, monster, monster_damage, quest_level, names,
damage_map_base, weapon_damage_map, parts,
part_max_damage, monster_breaks, monster_stars):
print(path)
def uniform_average(weapon):
return weapon_damage_map[weapon].averages["uniform"]
names_sorted = list(names)
names_sorted.sort(key=uniform_average, reverse=True)
from mako.lookup import TemplateLookup
from mako.runtime import Context
tlookup = TemplateLookup(directories=["templates/damage"],
output_encoding="utf-8",
input_encoding="utf-8")
damage_template = tlookup.get_template("/monster_damage.html")
wtype = damage_map_base.weapon.wtype
weapon_damage_type = WeaponType.damage_type(wtype)
damage_types = list(DAMAGE_TYPES)
weapon_types = list(WEAPON_TYPES)
weapon_types.remove("Bow")
weapon_types.remove("Light Bowgun")
weapon_types.remove("Heavy Bowgun")
with codecs.open(path, "w", "utf8") as f:
template_args = dict(
monster=monster.name,
monster_damage=monster_damage,
damage_types=DAMAGE_TYPES,
weapon_types=weapon_types,
weapon_type=wtype,
weapon_damage_type=weapon_damage_type,
village_stars=quest_level[0],
guild_stars=quest_level[1],
part_names=parts,
part_max_damage=part_max_damage,
weapon_names=names_sorted,
weapon_damage_map=weapon_damage_map,
monster_breaks=set(monster_breaks),
monster_stars=monster_stars
)
ctx = Context(f, **template_args)
damage_template.render_context(ctx)
def write_damage_html_by_rarity(path, rarity, monster, monster_damage, names,
damage_map_base, weapon_damage_map, parts,
part_max_damage):
print(path)
def uniform_average(weapon):
return weapon_damage_map[weapon].averages["uniform"]
names_sorted = list(names)
names_sorted.sort(key=uniform_average, reverse=True)
from mako.lookup import TemplateLookup
from mako.runtime import Context
tlookup = TemplateLookup(directories=["templates/damage"],
output_encoding="utf-8",
input_encoding="utf-8")
damage_template = tlookup.get_template("/monster_damage_by_rarity.html")
wtype = damage_map_base.weapon.wtype
weapon_damage_type = WeaponType.damage_type(wtype)
damage_types = list(DAMAGE_TYPES)
weapon_types = list(WEAPON_TYPES)
weapon_types.remove("Bow")
weapon_types.remove("Light Bowgun")
weapon_types.remove("Heavy Bowgun")
with codecs.open(path, "w", "utf8") as f:
template_args = dict(
monster=monster.name,
monster_damage=monster_damage,
rarity=rarity,
damage_types=DAMAGE_TYPES,
weapon_types=weapon_types,
weapon_type=wtype,
weapon_damage_type=weapon_damage_type,
part_names=parts,
part_max_damage=part_max_damage,
weapon_names=names_sorted,
weapon_damage_map=weapon_damage_map,
)
ctx = Context(f, **template_args)
damage_template.render_context(ctx)
def print_sorted_damage(names, damage_map_base, weapon_damage_map, parts):
@ -288,31 +396,36 @@ def print_sorted_damage(names, damage_map_base, weapon_damage_map, parts):
# for part in parts])
for name in names_sorted:
print "%-20s:" % name,
print("%-20s:" % name, end=' ')
damage_map = weapon_damage_map[name]
print "%0.2f" % damage_map.averages["uniform"],
print("%0.2f" % damage_map.averages["uniform"], end=' ')
for part in parts:
part_damage = damage_map[part]
print "% 2d" % part_damage.average(),
print
print("% 2d" % part_damage.average(), end=' ')
print()
if len(names) > 1:
# this is super buggy
if False and len(names) > 1:
w1 = weapon_damage_map[names_sorted[0]]
w2 = weapon_damage_map[names_sorted[1]]
m, ratio = w1.compare_break_even(w2)
print
print "Comparison of '%s' and '%s'" % (
names_sorted[0], names_sorted[1])
print "Hitbox ratio:", m, "%0.2f" % ratio
print()
print("Comparison of '%s' and '%s'" % (
names_sorted[0], names_sorted[1]))
print("Hitbox ratio:", m, "%0.2f" % ratio)
for line in w1.get_raw_element_ratios():
if w1.etype:
re_ratios = w1.get_raw_element_ratios()
else:
re_ratios = w2.get_raw_element_ratios()
for line in re_ratios:
line = list(line)
if m*line[3] > m*ratio:
line.append(names_sorted[0])
else:
line.append(names_sorted[1])
# (part, raw, element, ratio)
print "%-22s %02d %02d %0.2f %s" % tuple(line)
print("%-22s %02d %02d %0.2f %s" % tuple(line))
def print_damage_percent_diff(names, damage_map_base, weapon_damage_map, parts):
@ -336,7 +449,7 @@ def print_damage_percent_diff(names, damage_map_base, weapon_damage_map, parts):
ediff_s = ",".join("%+0.1f%%" % i for i in ediffs)
bdiff_s = ",".join("%+0.1f%%" % i for i in bdiffs)
damage = damage_map_base[part]
print "%22s%s h%02d %0.2f (%s) h%02d %0.2f (%s) %+0.2f (%s)" \
print("%22s%s h%02d %0.2f (%s) h%02d %0.2f (%s) %+0.2f (%s)" \
% (part, "*" if damage.is_breakable() else " ",
damage.hitbox,
damage.total,
@ -345,17 +458,17 @@ def print_damage_percent_diff(names, damage_map_base, weapon_damage_map, parts):
damage.element,
ediff_s,
damage.break_diff(),
bdiff_s)
bdiff_s))
if weapon_type == "Charge Blade":
for level in (0, 1, 2, 3, 5):
print " " * 20, level,
print(" " * 20, level, end=' ')
for wname in names:
wd = weapon_damage_map[wname]
damage = wd.cb_phial_damage[part][level]
print "(%0.f, %0.f, %0.f);" % damage,
print
print("(%0.f, %0.f, %0.f);" % damage, end=' ')
print()
print " --------------------"
print(" --------------------")
for avg_type in "uniform raw weakpart_raw element weakpart_element break_raw break_element break_only".split():
base = damage_map_base.averages[avg_type]
@ -367,7 +480,7 @@ def print_damage_percent_diff(names, damage_map_base, weapon_damage_map, parts):
diff_s = ",".join("%+0.1f%%" % i for i in diffs)
print "%22s %0.2f (%s)" % (avg_type, base, diff_s)
print("%22s %0.2f (%s)" % (avg_type, base, diff_s))
def match_quest_level(match_level, weapon_level):
@ -383,32 +496,19 @@ def match_quest_level(match_level, weapon_level):
return True
def main():
args = parse_args(None)
game_uses_true_raw = False
if args.monster_hunter_cross:
db = MHDBX()
game_uses_true_raw = True
elif args.monster_hunter_gen:
if args.quest_level:
comps = True
else:
comps = False
db = MHDB(game="gu", include_item_components=comps)
game_uses_true_raw = True
elif args.monster_hunter_world:
db = MHDBX(game="mhw")
game_uses_true_raw = False
else:
db = MHDB(game="4u")
motiondb = MotionValueDB(_pathfix.motion_values_path)
def run_comparison(args, db, motiondb, game_uses_true_raw, item_stars=None):
monster = db.get_monster_by_name(args.monster)
if not monster:
raise ValueError("Monster '%s' not found" % args.monster)
monster_damage = db.get_monster_damage(monster.id)
if not monster_damage.is_valid():
print("WARN: invalid damage data for monster '%s'" % args.monster)
return
if item_stars is None:
item_stars = ItemStars(db)
weapons = []
weapon_type = None
names_set = set()
@ -425,10 +525,14 @@ def main():
if skill_args:
skill_args_map[name] = skill_args
#print("args match", args.match)
for match_tuple in args.match:
# TODO: better validation
if isinstance(match_tuple, list):
# TODO: is this a bug in argparse!!!????
match_tuple = match_tuple[0]
wtype, element = match_tuple
if args.quest_level:
if args.quest_level or args.rarity:
final=None
else:
final=1
@ -443,28 +547,51 @@ def main():
names_set.add(w.name)
if args.weapon_custom:
weapons.extend(args.weapon_custom)
weapons.extend([w[0] for w in args.weapon_custom])
if not weapons:
print "Err: no matching weapons"
print("Err: no matching weapons")
sys.exit(1)
names = [w.name for w in weapons]
monster_breaks = db.get_monster_breaks(monster.id)
part_names = monster_damage.keys()
for i in range(len(monster_breaks)):
if monster_breaks[i] not in monster_damage:
plural = monster_breaks[i] + "s"
if plural in monster_damage:
monster_breaks[i] = plural
states = monster_damage.state_names()
print("States:", states)
print("%-20s" % monster.name, " | ".join(monster_damage.keys()))
for dtype in DAMAGE_TYPES:
print(dtype[:2], ":", end=" ")
for part_name, part_damage in monster_damage.items():
if part_damage.state_diff(dtype):
print("% 2d (% 2d)"
% (part_damage[dtype],
part_damage.get_alt_state(dtype)),
end=" ")
else:
print("% 2d" % part_damage[dtype], end=" ")
print()
weapon_type = weapons[0]["wtype"]
if args.phial and weapon_type != "Charge Blade":
print "ERROR: phial option is only supported for Charge Blade"
print("ERROR: phial option is only supported for Charge Blade")
sys.exit(1)
motion = motiondb[weapon_type].average
print "Weapon Type: %s" % weapon_type
print "Average Motion: %0.1f" % motion
print("Weapon Type: %s" % weapon_type)
print("Average Motion: %0.1f" % motion)
if args.motion:
motion = args.motion
print "Specified Motion: %0.1f" % motion
print "Monster Breaks: %s" % ", ".join(monster_breaks)
print("Specified Motion: %0.1f" % motion)
print("Monster Breaks: %s" % ", ".join(monster_breaks))
skill_names = get_skill_names(args)
print "Common Skills:", ", ".join(skill for skill in skill_names if skill)
print("Common Skills:", ", ".join(skill for skill in skill_names if skill))
if args.parts:
limit_parts = args.parts.split(",")
@ -473,24 +600,73 @@ def main():
if args.quest_level:
village, guild, permit, arena = args.quest_level
print "Filter by Quest Levels:", args.quest_level
print("Filter by Quest Levels:", args.quest_level)
weapons2 = dict()
for w in weapons:
if (not match_quest_level(village, w["village_stars"])
and not match_quest_level(guild, w["guild_stars"])):
if "village_stars" in w:
stars = dict(Village=w["village_stars"],
Guild=w["guild_stars"],
Permit=w["permit_stars"],
Arena=w["arena_stars"])
else:
stars = item_stars.get_weapon_stars(w)
if (not match_quest_level(village, stars["Village"])
and not match_quest_level(guild, stars["Guild"])):
continue
if not match_quest_level(permit, w["permit_stars"]):
if not match_quest_level(permit, stars["Permit"]):
continue
if not match_quest_level(arena, w["arena_stars"]):
if not match_quest_level(arena, stars["Arena"]):
continue
weapons2[w.id] = w
parent_ids = set(w.parent_id for w in weapons2.values())
for wid in weapons2.keys():
for wid in list(weapons2.keys()):
if wid in parent_ids:
del weapons2[wid]
weapons = weapons2.values()
weapons = list(weapons2.values())
names = [w.name for w in weapons]
if args.rarity:
print("Filter by max rarity:", args.rarity)
weapons2 = dict()
by_name = dict()
for w in weapons:
if w.rarity <= args.rarity:
weapons2[w.id] = w
by_name[w.name] = w
# TODO: don't have parent ids for mhrise yet
if False and args.mhr:
# hack to remove most common dups
for wname in list(by_name.keys()):
if wname.endswith("+"):
base = wname[:-1]
suffix = "+"
else:
parts = wname.rsplit(" ", maxsplit=1)
if len(parts) == 1:
continue
base, suffix = parts
parent_name = None
if suffix == "+" or base.endswith("+"):
parent_name = base.rstrip("+")
elif suffix in ("II", "III", "VI", "VII"):
parent_name = wname[:-1]
elif suffix == "IV":
parent_name = base + " III"
elif suffix == "V":
parent_name = base + " IV"
if parent_name:
print("parent", parent_name)
if parent_name in by_name:
del weapons2[by_name[parent_name].id]
else:
parent_ids = set(w.parent_id for w in weapons2.values())
for wid in list(weapons2.keys()):
if wid in parent_ids:
del weapons2[wid]
weapons = list(weapons2.values())
names = [w.name for w in weapons]
part_max_damage = defaultdict(int)
weapon_damage_map = dict()
for row in weapons:
name = row["name"]
@ -499,6 +675,7 @@ def main():
raise ValueError(
"Weapon '%s' is different type, got '%s' expected '%s'"
% (name, row_type, weapon_type))
#print(name, row)
try:
skill_args = skill_args_map.get(name, args)
wd = WeaponMonsterDamage(row,
@ -513,24 +690,28 @@ def main():
limit_parts=args.parts,
frenzy_bonus=skill_args.frenzy,
is_true_attack=game_uses_true_raw,
blunt_power=skill_args.blunt_power)
print "%-20s: %4.0f %2.0f%%" % (name, wd.attack, wd.affinity),
blunt_power=skill_args.blunt_power,
game=db.game)
print("%-20s: %4.0f %2.0f%%" % (name, wd.attack, wd.affinity), end=' ')
if wd.etype:
if wd.etype2:
print "(%4.0f %s, %4.0f %s)" \
% (wd.eattack, wd.etype, wd.eattack2, wd.etype2),
print("(%4.0f %s, %4.0f %s)" \
% (wd.eattack, wd.etype, wd.eattack2, wd.etype2), end=' ')
else:
print "(%4.0f %s)" % (wd.eattack, wd.etype),
print SharpnessLevel.name(wd.sharpness),
print("(%4.0f %s)" % (wd.eattack, wd.etype), end=' ')
print(SharpnessLevel.name(wd.sharpness), wd.sharpness_points, end=' ')
if skill_args != args:
print "{%s}" % ",".join(sn
print("{%s}" % ",".join(sn
for sn in get_skill_names(skill_args)
if sn)
if sn))
else:
print
print()
for part in wd.parts:
if wd[part].average() > part_max_damage[part]:
part_max_damage[part] = wd[part].average()
weapon_damage_map[name] = wd
except ValueError as e:
print str(e)
print(str(e))
sys.exit(1)
damage_map_base = weapon_damage_map[names[0]]
@ -551,6 +732,162 @@ def main():
print_sorted_damage(names, damage_map_base,
weapon_damage_map, parts)
if args.html_out:
if args.mhr:
if not args.rarity:
print("Error: --html-out with --mrh requires --rarity")
sys.exit(1)
write_damage_html_by_rarity(args.html_out, args.rarity,
monster, monster_damage,
names, damage_map_base,
weapon_damage_map, parts,
part_max_damage)
else:
if not args.quest_level:
print("Error: --html-out requires quest level (-q)")
sys.exit(1)
monster_stars = item_stars.get_monster_stars(monster.id)
write_damage_html(args.html_out, monster, monster_damage,
args.quest_level, names,
damage_map_base, weapon_damage_map, parts,
part_max_damage, monster_breaks,
monster_stars)
def write_html_site(args, db, motiondb, game_uses_true_raw):
if db.game == "4u":
village_max = 5
guild_max = 2
else:
raise ValueError("Not implemented")
monsters = db.get_monsters("Boss")
monster_names = [monster.name for monster in monsters]
weapon_types = db.get_weapon_types()
item_stars = ItemStars(db)
monster_stars = {}
for monster in monsters:
monster_stars[monster.id] = item_stars.get_monster_stars(monster.id)
n = 0
base_dir = args.html_site
for monster in monsters:
stars = monster_stars[monster.id]
if stars["Village"] is None:
vrange = [0]
else:
if stars["Village"] > 2:
start = stars["Village"] - 1
else:
start = stars["Village"]
vrange = list(range(start, 11))
if start > 2:
vrange = [0] + vrange
if stars["Guild"] is None:
grange = [0]
else:
if stars["Guild"] > 1:
start = stars["Guild"] - 1
else:
start = stars["Guild"]
grange = list(range(start, 11))
if start > 1:
grange = [0] + grange
for v in vrange:
for g in grange:
if v == 0:
if g == 0:
continue
v = 1
if g == 0:
g = 1
quest_dir = "v{}g{}".format(v, g)
quest_path = os.path.join(base_dir, monster.name, quest_dir)
if not os.path.isdir(quest_path):
os.makedirs(quest_path)
for wtype in weapon_types:
if "Bowgun" in wtype or wtype == "Bow":
continue
args.html_out = os.path.join(quest_path, wtype + ".html")
if os.path.isfile(args.html_out):
print(args.html_out, " exists, skipping")
continue
args.html_site = None
n += 1
args.monster = monster.name
args.quest_level = (v if v else 1, g if g else 1,
None, None)
args.match = [(wtype, None)]
run_comparison(args, db, motiondb, game_uses_true_raw,
item_stars=item_stars)
print("n =", n)
def write_html_site_rise(args, db, motiondb, game_uses_true_raw=True):
monsters = db.get_monsters()
weapon_types = db.get_weapon_types()
n = 0
base_dir = args.html_site
for monster in monsters:
for rarity in range(1, 11):
args.rarity = rarity
rarity_dir = "r{}".format(rarity)
mpath = os.path.join(base_dir, monster.name, rarity_dir)
if not os.path.isdir(mpath):
os.makedirs(mpath)
for wtype in weapon_types:
if "Bowgun" in wtype or wtype == "Bow":
continue
args.html_out = os.path.join(mpath, wtype + ".html")
if os.path.isfile(args.html_out):
print(args.html_out, " exists, skipping")
continue
args.html_site = None
n += 1
args.monster = monster.name
args.match = [(wtype, None)]
print(rarity, wtype)
run_comparison(args, db, motiondb, game_uses_true_raw)
print("n =", n)
def main():
args = parse_args(None)
game_uses_true_raw = False
if args.quest_level or args.html_site:
comps = True
else:
comps = False
if args.monster_hunter_cross:
db = MHDBX()
game_uses_true_raw = True
elif args.monster_hunter_gen:
db = MHDB(game="gu", include_item_components=comps)
game_uses_true_raw = True
elif args.mhw:
db = MHDBX(game="mhw")
game_uses_true_raw = False
SharpnessLevel._modifier = SharpnessLevel._modifier_mhw
elif args.mhr:
db = MHDBX(game="mhr")
game_uses_true_raw = True
SharpnessLevel._modifier = SharpnessLevel._modifier_mhw
else:
db = MHDB(game="4u", include_item_components=comps)
motiondb = MotionValueDB(_pathfix.motion_values_path)
if args.html_site:
if args.mhr:
write_html_site_rise(args, db, motiondb, game_uses_true_raw)
else:
write_html_site(args, db, motiondb, game_uses_true_raw)
else:
run_comparison(args, db, motiondb, game_uses_true_raw)
if __name__ == '__main__':
main()

@ -1,10 +1,10 @@
#!/usr/bin/env python
#!/usr/bin/env python3
"""
Script to find the most lucrative monster parts to farm for money.
"""
import codecs
import urllib
import urllib.request, urllib.parse, urllib.error
import os.path
import sys
@ -55,8 +55,8 @@ def print_top_items(db, rank="G"):
value = item_value(item)
if value < min_value:
break
print " %-20s % 7.f % 6d (% 5.f)" % \
(item.name, value, int(item.sell), ev[item.id])
print(" %-20s % 7.f % 6d (% 5.f)" % \
(item.name, value, int(item.sell), ev[item.id]))
if __name__ == '__main__':

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
"""
Calculate probability of getting at least one of a monster part from one
@ -71,22 +71,22 @@ if __name__ == '__main__':
max_rewards = 8 - fixed_rewards
if min_rewards < 0:
print "Error: fixed_rewards (%d) must be less than or equal to " \
"guaranteeed_rewards (%d)" % (fixed_rewards, guarenteed_rewards)
print("Error: fixed_rewards (%d) must be less than or equal to " \
"guaranteeed_rewards (%d)" % (fixed_rewards, guarenteed_rewards))
sys.exit(1)
total_p = 0.0
expected_attempts = 0.0
for reward_count in xrange(min_rewards, max_rewards + 1):
for reward_count in range(min_rewards, max_rewards + 1):
p = stats._reward_count_p(reward_count, min_rewards, max_rewards,
extend_percent)
expected_attempts += p * reward_count
# probability of getting @reward_count rewards that could be the
# desired item
print "P(C = %d) = %0.4f" % (reward_count, p)
print("P(C = %d) = %0.4f" % (reward_count, p))
total_p += p
# expected value for number of rewards that could be the desired item
print "E(C) = %0.2f" % expected_attempts
print("E(C) = %0.2f" % expected_attempts)
# math check, make sure all possibilities add up to 1, allowing for
# some floating point precision loss.
@ -96,5 +96,5 @@ if __name__ == '__main__':
max_rewards, extend_percent)
expected = expected_attempts * reward_percent / 100.0
print "P(N > 0) = %0.2f%%" % p_at_least_one
print "E(N) = %0.4f" % expected
print("P(N > 0) = %0.2f%%" % p_at_least_one)
print("E(N) = %0.4f" % expected)

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
import _pathfix
@ -75,7 +75,7 @@ if __name__ == '__main__':
import os.path
if len(sys.argv) != 2:
print("Usage: %s 'item name'" % sys.argv[0])
print(("Usage: %s 'item name'" % sys.argv[0]))
sys.exit(os.EX_USAGE)
item_name = canonical_item_name(sys.argv[1])

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
import sys
import argparse
@ -40,8 +40,8 @@ if __name__ == '__main__':
else:
for cost in costs:
components = cost["components"]
print "=", ", ".join([w.name for w in cost["path"]])
print " Zenny", cost["zenny"]
for item_name in sorted(components.iterkeys()):
print "%20s %2d" % (item_name, components[item_name])
print
print("=", ", ".join([w.name for w in cost["path"]]))
print(" Zenny", cost["zenny"])
for item_name in sorted(components.keys()):
print("%20s %2d" % (item_name, components[item_name]))
print()

@ -1,15 +1,15 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
import os
import json
import sys
import errno
import urllib
import urllib.request, urllib.parse, urllib.error
import argparse
import _pathfix
from mhapi.db import MHDB
from mhapi.db import MHDB, MHDBX
from mhapi import model
ENTITIES = """item weapon monster armor
@ -42,7 +42,7 @@ SAFE_CHARS = " &'+\""
def file_path(path, model_object, alt_name_field=None):
if alt_name_field:
key = urllib.quote(model_object[alt_name_field].encode("utf8"),
key = urllib.parse.quote(model_object[alt_name_field].encode("utf8"),
SAFE_CHARS)
else:
key = str(model_object.id)
@ -57,7 +57,7 @@ def write_list_file(path, model_list):
def write_index_file(path, indexes):
for key, data in indexes.iteritems():
for key, data in indexes.items():
index_path = os.path.join(path, "_index_%s.json" % key)
with open(index_path, "w") as f:
json.dump(data, f, cls=model.ModelJSONEncoder, indent=2)
@ -106,7 +106,7 @@ def armor_json(db, path):
a.update_indexes(indexes)
skills = db.get_item_skills(a.id)
if not skills:
print "WARN: armor '%s' (%d) has no skills" % (a.name, a.id)
print("WARN: armor '%s' (%d) has no skills" % (a.name, a.id))
a.set_skills(skills)
all_data.append(a.as_data())
@ -130,7 +130,7 @@ def decoration_json(db, path):
a.update_indexes(indexes)
skills = db.get_item_skills(a.id)
if not skills:
print "WARN: decoration '%s' (%d) has no skills" % (a.name, a.id)
print("WARN: decoration '%s' (%d) has no skills" % (a.name, a.id))
a.set_skills(skills)
all_data.append(a.as_data())
@ -202,11 +202,12 @@ def weapon_json(db, path):
]
data["horn_melodies"] = melodies[w.horn_notes]
stars = item_stars.get_weapon_stars(w)
data["village_stars"] = stars["Village"]
data["guild_stars"] = stars["Guild"]
data["permit_stars"] = stars["Permit"]
data["arena_stars"] = stars["Arena"]
if db.game == "4u":
stars = item_stars.get_weapon_stars(w)
data["village_stars"] = stars["Village"]
data["guild_stars"] = stars["Guild"]
data["permit_stars"] = stars["Permit"]
data["arena_stars"] = stars["Arena"]
all_data.append(data)
@ -254,7 +255,7 @@ def wyporium_json(db, path):
if not k.startswith("wyporium"):
continue
trade_map[item.id][k] = all_data[k]
print trade_map
print(trade_map)
mkdirs_p(path)
write_map_file(path, trade_map)
@ -274,21 +275,27 @@ def horn_melody_json(db, path):
def main():
args = parse_args()
db = MHDB(game=args.game, include_item_components=True)
if args.game in ("mhx", "mhr"):
db = MHDBX(game=args.game)
else:
db = MHDB(game=args.game, include_item_components=True)
if not args.outpath:
args.outpath = os.path.join(_pathfix.web_path, "jsonapi")
args.outpath = os.path.join(_pathfix.web_path, "jsonapi", args.game)
if args.entities:
for entity in args.entities:
if entity not in ENTITIES:
print "Unknown entity: %s" % entity
print("Unknown entity: %s" % entity)
sys.exit(1)
else:
args.entities = ENTITIES
if db.game != "4u":
args.entities.remove("wyporium")
try:
args.entities.remove("wyporium")
except:
pass
for entity in args.entities:
fn = globals()["%s_json" % entity]

@ -12,11 +12,11 @@ from mhapi.util import get_utf8_writer
def print_header_nav(title, pid):
print """
print("""
<div data-role="header" data-position="fixed">
<a href="#page-menu" class="ui-btn-left ui-btn ui-btn-inline ui-mini ui-corner-all ui-btn-icon-left ui-icon-bars">Menu</a>
<h1>%s</h1>
""".strip() % title
""".strip() % title)
alt_pid = None
if pid.endswith("-en"):
@ -27,11 +27,11 @@ def print_header_nav(title, pid):
alt_title = "en"
if alt_pid is not None:
print """
print("""
<a href="#%s" class="ui-btn-right ui-btn ui-btn-inline ui-mini">%s</a>
""".strip() % (alt_pid, alt_title)
""".strip() % (alt_pid, alt_title))
print " </div>"
print(" </div>")
def mk_html_list(dict_list, keys, sort_keys, divider_fn="auto"):
@ -39,7 +39,7 @@ def mk_html_list(dict_list, keys, sort_keys, divider_fn="auto"):
print ('<ul data-role="listview" data-filter="true"'
' data-autodividers="true">')
else:
print '<ul data-role="listview" data-filter="true">'
print('<ul data-role="listview" data-filter="true">')
if isinstance(sort_keys, types.FunctionType):
sort_fn = sort_keys
@ -55,26 +55,26 @@ def mk_html_list(dict_list, keys, sort_keys, divider_fn="auto"):
if divider_fn not in (None, "auto"):
divider_text = divider_fn(d, prev_d)
if divider_text is not None:
print ' <li data-role="list-divider">%s</li>' % divider_text
print " <li>"
print(' <li data-role="list-divider">%s</li>' % divider_text)
print(" <li>")
for i, k in enumerate(keys):
value = d[k]
if k in ("section", "description"):
if value:
print ' <p class="ui-li-desc">%s</p>' % value
print(' <p class="ui-li-desc">%s</p>' % value)
continue
elif k == "title_jp" and i != 0:
# NB: for monster by title we want it to be a normal column
if value:
print ' <p class="ui-li-desc">Title: %s</p>' % value
print(' <p class="ui-li-desc">Title: %s</p>' % value)
continue
if value.endswith(".png"):
value = ('<img class="icon" src="../img/icons_items/%s" />'
% value)
print ' <span class="%s">%s</span>' % (k, value)
print " </li>"
print(' <span class="%s">%s</span>' % (k, value))
print(" </li>")
prev_d = d
print '</ul>'
print('</ul>')
def _main():
@ -87,7 +87,7 @@ def _main():
carve_items = db.get_items(item_types=("Flesh",))
print """<!DOCTYPE html>
print("""<!DOCTYPE html>
<html>
<head>
<title>Poogie Translate</title>
@ -133,26 +133,26 @@ def _main():
</ul>
</div>
</div>
"""
""")
stree_path = os.path.join(_pathfix.project_path, "db",
"mhx_skill_tree_list.json")
with open(stree_path) as f:
stree_list = json.load(f)
print '<div data-role="page" id="page-skilltrees-en">'
print('<div data-role="page" id="page-skilltrees-en">')
print_header_nav("Skill Trees (en)", "page-skilltrees-en")
print '<div data-role="main" class="ui-content">'
print('<div data-role="main" class="ui-content">')
mk_html_list(stree_list, ("name", "name_jp"), ("name",))
print '</div>'
print '</div>'
print('</div>')
print('</div>')
print '<div data-role="page" id="page-skilltrees-jp">'
print('<div data-role="page" id="page-skilltrees-jp">')
print_header_nav("Skill Trees (jp)", "page-skilltrees-jp")
print '<div data-role="main" class="ui-content">'
print('<div data-role="main" class="ui-content">')
mk_html_list(stree_list, ("name_jp", "name"), jplen_sort_fn,
divider_fn=jplen_divider_fn)
print '</div>'
print '</div>'
print('</div>')
print('</div>')
def item_divider_fn(d, prev_d):
prefix = _icon_prefix(d)
@ -160,29 +160,29 @@ def _main():
if prefix != prev_prefix:
return prefix
return None
print '<div data-role="page" id="page-item-usable">'
print('<div data-role="page" id="page-item-usable">')
print_header_nav("Items: Usable", "page-item-usable")
print '<div data-role="main" class="ui-content">'
print('<div data-role="main" class="ui-content">')
mk_html_list(items, ("icon_name", "name", "name_jp"),
("icon_name", "name"), divider_fn=item_divider_fn)
print '</div>'
print '</div>'
print('</div>')
print('</div>')
print '<div data-role="page" id="page-item-gather">'
print('<div data-role="page" id="page-item-gather">')
print_header_nav("Items: Gatherable", "page-item-gather")
print '<div data-role="main" class="ui-content">'
print('<div data-role="main" class="ui-content">')
mk_html_list(gather_items, ("icon_name", "name", "name_jp"),
("icon_name", "name"), divider_fn=item_divider_fn)
print '</div>'
print '</div>'
print('</div>')
print('</div>')
print '<div data-role="page" id="page-item-carve">'
print('<div data-role="page" id="page-item-carve">')
print_header_nav("Items: Carve", "page-item-carve")
print '<div data-role="main" class="ui-content">'
print('<div data-role="main" class="ui-content">')
mk_html_list(carve_items, ("icon_name", "name", "name_jp"),
("icon_name", "name"), divider_fn=item_divider_fn)
print '</div>'
print '</div>'
print('</div>')
print('</div>')
ha_path = os.path.join(_pathfix.project_path, "db", "hunter_arts.json")
with open(ha_path) as f:
@ -194,21 +194,21 @@ def _main():
elif d["section"] != prev_d["section"]:
return d["section"]
return None
print '<div data-role="page" id="page-hunterarts-en">'
print('<div data-role="page" id="page-hunterarts-en">')
print_header_nav("Hunter Arts (en)", "page-hunterarts-en")
print '<div data-role="main" class="ui-content">'
print('<div data-role="main" class="ui-content">')
mk_html_list(ha_list, ("name", "name_jp", "description"), None,
divider_fn=ha_divider_fn)
print '</div>'
print '</div>'
print('</div>')
print('</div>')
print '<div data-role="page" id="page-hunterarts-jp">'
print('<div data-role="page" id="page-hunterarts-jp">')
print_header_nav("Hunter Arts (jp)", "page-hunterarts-jp")
print '<div data-role="main" class="ui-content">'
print('<div data-role="main" class="ui-content">')
mk_html_list(ha_list, ("name_jp", "name", "section", "description"),
jplen_sort_fn, divider_fn=jplen_divider_fn)
print '</div>'
print '</div>'
print('</div>')
print('</div>')
monster_path = os.path.join(_pathfix.project_path, "db",
@ -216,35 +216,35 @@ def _main():
with open(monster_path) as f:
monster_list = json.load(f)
print '<div data-role="page" id="page-monsters-en">'
print('<div data-role="page" id="page-monsters-en">')
print_header_nav("Monsters (en)", "page-monsters-en")
print '<div data-role="main" class="ui-content">'
print('<div data-role="main" class="ui-content">')
mk_html_list(monster_list, ("name", "name_jp", "title_jp"), ("name",))
print '</div>'
print '</div>'
print('</div>')
print('</div>')
print '<div data-role="page" id="page-monsters-jp">'
print('<div data-role="page" id="page-monsters-jp">')
print_header_nav("Monsters (jp)", "page-monsters-jp")
print '<div data-role="main" class="ui-content">'
print('<div data-role="main" class="ui-content">')
mk_html_list(monster_list, ("name_jp", "name", "title_jp"), ("name_jp",))
print '</div>'
print '</div>'
print('</div>')
print('</div>')
titled_monster_list = [m for m in monster_list if m["title_jp"]]
print '<div data-role="page" id="page-monsters-title">'
print('<div data-role="page" id="page-monsters-title">')
print_header_nav("Monster Titles", "page-monsters-title")
print '<div data-role="main" class="ui-content">'
print('<div data-role="main" class="ui-content">')
mk_html_list(titled_monster_list, ("title_jp", "name"), ("title_jp",),
divider_fn=None)
print '</div>'
print '</div>'
print('</div>')
print('</div>')
print """
print("""
</body>
"""
""")
def _icon_prefix(d):
if d is None:

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
import sys
import json

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf8 -*-
"""
Parse hunter arts name, name_jp, and description from wikia:
@ -10,7 +10,7 @@ Returns list of dict, e.g.:
"section": "Heavy Bowgun",
"description": "",
"name": "Acceleration Shower I",
"name_jp": "\u30a2\u30af\u30bb\u30eb\u30b7\u30e3\u30ef\u30fc I"
"name_jp": "\\u30a2\\u30af\\u30bb\\u30eb\\u30b7\\u30e3\\u30ef\\u30fc I"
},
...
]
@ -62,7 +62,7 @@ def parse_wikia_hunter_arts(f):
def _main():
with open(sys.argv[1]) as f:
data = parse_wikia_hunter_arts(f)
print json.dumps(data, indent=2)
print(json.dumps(data, indent=2))
if __name__ == '__main__':

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf8 -*-
"""
Parse monster names and jp names for monster hunter X.
@ -18,6 +18,7 @@ Returns list of dict, e.g.:
import sys
import re
import json
import lxml.etree
import requests
@ -34,14 +35,27 @@ MONSTER_RE = re.compile(
'(?:</td>)?<td style="[^"]*background-color:#EBEBEB;[^"]*">\s*'
'<a href="([^"]*)" [^>]* title="([^"]*)"')
# Old, MHX
"""
MONSTER_LINK_RE = re.compile(
'<a href="(/wiki/[^/"]*)"\s+class="image image-thumbnail link-internal"\s+'
'title="([^"]*)"\s+>')
JAPANESE_NAME_STR = '<h3 class="pi-data-label pi-secondary-font">Japanese:</h3>'
JAPANESE_NAME_RE = re.compile(
'<div class="pi-data-value pi-font">(.*)</div>')
"""
MONSTER_LINK_RE = re.compile(
'<a href="(/wiki/[^/"]*)" title="([^"]*)">([^<>]+)</a>')
"""
<h2 class="pi-item pi-item-spacing pi-title" data-source="Japanese Name"><ruby lang="ja"><rb lang="ja-Hani">ドスフロギィ<br/>(Dosufurogi)</rb></ruby></h2>
"""
JAPANESE_NAME_RE = re.compile('<h2 class="pi-item pi-item-spacing pi-title" data-source="Japanese Name"><ruby lang="ja"><rb lang="[^"]*">([^<>]*)<br/>.*</rb></ruby></h2>')
JAPANESE_TITLE_RE = re.compile(
'<div class="pi-data-value pi-font">([^<>]*)</div>')
def parse_wikia_monsters(f):
section = None
@ -55,7 +69,7 @@ def parse_wikia_monsters(f):
m = SECTION_RE.match(line)
if m:
section = m.group(1)
print >>sys.stderr, "section", section
print("section", section, file=sys.stderr)
continue
if section not in ["Large Monsters", "Small Monsters"]:
continue
@ -72,20 +86,17 @@ def parse_wikia_monsters(f):
def get_jp_names(monster_path):
url = "http://monsterhunter.wikia.com" + monster_path
r = requests.get(url)
lines = r.text.split("\n")
root = lxml.etree.HTML(r.text)
names = []
while lines:
line = lines.pop(0).strip()
if JAPANESE_NAME_STR not in line:
continue
line = lines.pop(0).strip()
while line == "":
line = lines.pop(0).strip()
m = JAPANESE_NAME_RE.match(line)
assert m, "No match: " + line
names.append(parse_japanese_name(m.group(1)))
if len(names) == 2:
break
rbs = root.xpath('//h2[@data-source="Japanese Name"]//rb')
names.append(rbs[0].text)
divs = root.xpath('//div[@data-source="Japanese Title"]//div')
if divs:
names.append(divs[0].text)
return names
@ -108,16 +119,16 @@ def _main():
name = m["name"]
names = get_jp_names(m["href"])
if len(names) == 0:
print >>sys.stderr, "ERROR: no names for %s" % name
print("ERROR: no names for %s" % name, file=sys.stderr)
names = ["", ""]
if len(names) == 1:
print >>sys.stderr, "ERROR: no title for %s" % name
print("ERROR: no title for %s" % name, file=sys.stderr)
names.append("")
m["name_jp"] = names[0]
m["title_jp"] = names[1]
if m["title_jp"] in ("None", "N/A", "(?)"):
m["title_jp"] = ""
print json.dumps(monster_list, indent=2)
print(json.dumps(monster_list, indent=2))
if __name__ == '__main__':

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf8 -*-
"""
Parse skill tree names and jp names for monster hunter X.
@ -81,7 +81,7 @@ def parse_wikia_skill_trees(f):
def _main():
if len(sys.argv) != 4:
print "Usage: %s infile out_strees.json out_skills.json" % sys.argv[0]
print("Usage: %s infile out_strees.json out_skills.json" % sys.argv[0])
sys.exit(1)
with open(sys.argv[1]) as f:
strees, skills = parse_wikia_skill_trees(f)

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
import _pathfix
@ -15,4 +15,4 @@ if __name__ == '__main__':
try:
httpd.serve_forever()
except KeyboardInterrupt:
print "^C"
print("^C")

@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
import sys
import argparse
@ -21,7 +21,7 @@ def main():
if args.item:
item = db.get_item_by_name(args.item)
if item is None:
print "Item '%s' not found" % args.item
print("Item '%s' not found" % args.item)
sys.exit(1)
if item.type == "Materials":
stars = item_stars.get_material_stars(item.id)
@ -30,15 +30,15 @@ def main():
elif args.weapon:
weapon = db.get_weapon_by_name(args.weapon)
if weapon is None:
print "Weapon '%s' not found" % args.weapon
print("Weapon '%s' not found" % args.weapon)
sys.exit(1)
stars = item_stars.get_weapon_stars(weapon)
else:
print "Specify -w or -i"
print("Specify -w or -i")
sys.exit(1)
for k, v in stars.iteritems():
print k, v
for k, v in stars.items():
print(k, v)
if __name__ == '__main__':

@ -12,7 +12,7 @@ from mhapi.db import MHDB
def apply_update(db, row):
quest = db.get_quest(row["id"])
if quest.goal == row["goal"]:
print "quest", row["id"], row["name"], "already updated, skipping"
print("quest", row["id"], row["name"], "already updated, skipping")
return
cur = db.cursor()
cur.execute("""UPDATE quests SET
@ -21,9 +21,9 @@ def apply_update(db, row):
AND name=?""",
(row["goal"], row["id"], row["name"]))
if cur.rowcount == 1:
print "quest", row["id"], row["name"], "goal updated:", row["goal"]
print("quest", row["id"], row["name"], "goal updated:", row["goal"])
else:
print "ERROR", "quest", row["id"], row["name"], "update failed"
print("ERROR", "quest", row["id"], row["name"], "update failed")
if __name__ == '__main__':

@ -123,8 +123,8 @@ def _parse_monster(name):
name = rstrip(name, "'s")
name = rstrip(name, "'")
name = rstrip(name, u"")
name = rstrip(name, u"s")
name = rstrip(name, "")
name = rstrip(name, "s")
#print "=>", name
@ -202,7 +202,7 @@ def fuzzy_find(name):
def check_hunts(db, quest):
print ">", quest.id, quest.name,
print(">", quest.id, quest.name, end=' ')
monsters_match = False
@ -240,30 +240,30 @@ def check_hunts(db, quest):
if monsters_match and not errors:
# useful for doing grep -v on output
print " *OK*"
print(" *OK*")
elif monsters_match:
print " *MISSPELLING*"
print " goal:", quest.goal
print " sub:", quest.sub_goal
print(" *MISSPELLING*")
print(" goal:", quest.goal)
print(" sub:", quest.sub_goal)
for err in errors:
print " ", err
print(" ", err)
else:
print " *MISMATCH*",
print(" *MISMATCH*", end=' ')
if errors:
print " *MISSPELLING*",
print
print(" *MISSPELLING*", end=' ')
print()
for err in errors:
print " ", err
print " goal:", quest.goal
print " sub:", quest.sub_goal
print " parsed:", goal_expected
print(" ", err)
print(" goal:", quest.goal)
print(" sub:", quest.sub_goal)
print(" parsed:", goal_expected)
if sub_expected and not sub_expected < goal_expected:
# print if sub monster looks like it's not one of the
# main monsters. This will false positive when main quest
# is hunt all large monsters.
print " sub prsd:", sub_expected
print " db:", db_expected
print " db unstb:", db_expected_unstable
print(" sub prsd:", sub_expected)
print(" db:", db_expected)
print(" db unstb:", db_expected_unstable)
if __name__ == '__main__':

@ -12,7 +12,7 @@ stdout = get_utf8_writer(sys.stdout)
def set_weapon_final(db, weapon, value):
print >>stdout, "weapon_final", weapon.id, weapon.name, value
print("weapon_final", weapon.id, weapon.name, value, file=stdout)
cur = db.cursor()
cur.execute("""UPDATE weapons SET
final=? WHERE _id=?""",

Binary file not shown.

@ -0,0 +1,38 @@
Iron Sword,1500
Buster Sword,2550
Giant Jawblade,5250
Ravager Blade,5250
Iron Katana,1500
Canine Katana,3300
Eager Cleaver,10950
Hunter's Knife,1500
Assassin's Dagger,3300
Chief Kris,4200
Matched Slicers,1500
Chief's Scythes,3300
Dual Hatchets,4200
War Hammer,1500
War Mace,2550
Bone Bludgeon+,4200
Iron Striker+,5250
Metal Bagpipe,1500
Hunter's Horn,4200
Heavy Bagpipe+,5250
Iron Lance,1500
Knight Lance,2550
Rampart,5250
Spiked Javelin,6450
Iron Gunlance,1500
Jaggid Gunlance,4200
Defender's Gunlance,6450
Bone Axe,1950
Wild Axe,4200
Power Gasher,5250
Elite Blad,1950
Bone Staff,3300
Cross Bowgun,2550
Cross Bowgun+,4200
Bone Shooter,2100
Bone Shooter+,5400
Hunter's Bow I,1500
Hunter's Stoutbow I,4200
1 Iron Sword 1500
2 Buster Sword 2550
3 Giant Jawblade 5250
4 Ravager Blade 5250
5 Iron Katana 1500
6 Canine Katana 3300
7 Eager Cleaver 10950
8 Hunter's Knife 1500
9 Assassin's Dagger 3300
10 Chief Kris 4200
11 Matched Slicers 1500
12 Chief's Scythes 3300
13 Dual Hatchets 4200
14 War Hammer 1500
15 War Mace 2550
16 Bone Bludgeon+ 4200
17 Iron Striker+ 5250
18 Metal Bagpipe 1500
19 Hunter's Horn 4200
20 Heavy Bagpipe+ 5250
21 Iron Lance 1500
22 Knight Lance 2550
23 Rampart 5250
24 Spiked Javelin 6450
25 Iron Gunlance 1500
26 Jaggid Gunlance 4200
27 Defender's Gunlance 6450
28 Bone Axe 1950
29 Wild Axe 4200
30 Power Gasher 5250
31 Elite Blad 1950
32 Bone Staff 3300
33 Cross Bowgun 2550
34 Cross Bowgun+ 4200
35 Bone Shooter 2100
36 Bone Shooter+ 5400
37 Hunter's Bow I 1500
38 Hunter's Stoutbow I 4200

File diff suppressed because it is too large Load Diff

@ -0,0 +1,254 @@
[
{
"name": "Rathian",
"link": "/monster/001_00.html"
},
{
"name": "Apex Rathian",
"link": "/monster/001_07.html"
},
{
"name": "Rathalos",
"link": "/monster/002_00.html"
},
{
"name": "Apex Rathalos",
"link": "/monster/002_07.html"
},
{
"name": "Khezu",
"link": "/monster/003_00.html"
},
{
"name": "Basarios",
"link": "/monster/004_00.html"
},
{
"name": "Diablos",
"link": "/monster/007_00.html"
},
{
"name": "Apex Diablos",
"link": "/monster/007_07.html"
},
{
"name": "Daimyo Hermitaur",
"link": "/monster/019_00.html"
},
{
"name": "Shogun Ceanataur",
"link": "/monster/020_00.html"
},
{
"name": "Rajang",
"link": "/monster/023_00.html"
},
{
"name": "Furious Rajang",
"link": "/monster/023_05.html"
},
{
"name": "Kushala Daora",
"link": "/monster/024_00.html"
},
{
"name": "Chameleos",
"link": "/monster/025_00.html"
},
{
"name": "Teostra",
"link": "/monster/027_00.html"
},
{
"name": "Tigrex",
"link": "/monster/032_00.html"
},
{
"name": "Nargacuga",
"link": "/monster/037_00.html"
},
{
"name": "Barioth",
"link": "/monster/042_00.html"
},
{
"name": "Barroth",
"link": "/monster/044_00.html"
},
{
"name": "Royal Ludroth",
"link": "/monster/047_00.html"
},
{
"name": "Great Baggi",
"link": "/monster/054_00.html"
},
{
"name": "Zinogre",
"link": "/monster/057_00.html"
},
{
"name": "Apex Zinogre",
"link": "/monster/057_07.html"
},
{
"name": "Great Wroggi",
"link": "/monster/059_00.html"
},
{
"name": "Arzuros",
"link": "/monster/060_00.html"
},
{
"name": "Apex Arzuros",
"link": "/monster/060_07.html"
},
{
"name": "Lagombi",
"link": "/monster/061_00.html"
},
{
"name": "Volvidon",
"link": "/monster/062_00.html"
},
{
"name": "Gore Magala",
"link": "/monster/071_00.html"
},
{
"name": "Shagaru Magala",
"link": "/monster/072_00.html"
},
{
"name": "Seregios",
"link": "/monster/077_00.html"
},
{
"name": "Astalos",
"link": "/monster/081_00.html"
},
{
"name": "Mizutsune",
"link": "/monster/082_00.html"
},
{
"name": "Apex Mizutsune",
"link": "/monster/082_07.html"
},
{
"name": "Crimson Glow Valstrax",
"link": "/monster/086_05.html"
},
{
"name": "Magnamalo",
"link": "/monster/089_00.html"
},
{
"name": "Scorned Magnamalo",
"link": "/monster/089_05.html"
},
{
"name": "Bishaten",
"link": "/monster/090_00.html"
},
{
"name": "Blood Orange Bishaten",
"link": "/monster/090_01.html"
},
{
"name": "Aknosom",
"link": "/monster/091_00.html"
},
{
"name": "Tetranadon",
"link": "/monster/092_00.html"
},
{
"name": "Somnacanth",
"link": "/monster/093_00.html"
},
{
"name": "Aurora Somnacanth",
"link": "/monster/093_01.html"
},
{
"name": "Rakna-Kadaki",
"link": "/monster/094_00.html"
},
{
"name": "Pyre Rakna-Kadaki",
"link": "/monster/094_01.html"
},
{
"name": "Almudron",
"link": "/monster/095_00.html"
},
{
"name": "Magma Almudron",
"link": "/monster/095_01.html"
},
{
"name": "Wind Serpent Ibushi",
"link": "/monster/096_00.html"
},
{
"name": "Goss Harag",
"link": "/monster/097_00.html"
},
{
"name": "Great Izuchi",
"link": "/monster/098_00.html"
},
{
"name": "Thunder Serpent Narwa",
"link": "/monster/099_00.html"
},
{
"name": "Narwa the Allmother",
"link": "/monster/099_05.html"
},
{
"name": "Anjanath",
"link": "/monster/100_00.html"
},
{
"name": "Pukei-Pukei",
"link": "/monster/102_00.html"
},
{
"name": "Kulu-Ya-Ku",
"link": "/monster/107_00.html"
},
{
"name": "Jyuratodus",
"link": "/monster/108_00.html"
},
{
"name": "Tobi-Kadachi",
"link": "/monster/109_00.html"
},
{
"name": "Bazelgeuse",
"link": "/monster/118_00.html"
},
{
"name": "Malzeno",
"link": "/monster/132_00.html"
},
{
"name": "Lunagaron",
"link": "/monster/133_00.html"
},
{
"name": "Garangolm",
"link": "/monster/134_00.html"
},
{
"name": "Gaismagorm",
"link": "/monster/135_00.html"
},
{
"name": "Espinas",
"link": "/monster/136_00.html"
}
]

File diff suppressed because it is too large Load Diff

@ -19,7 +19,7 @@ def set_carve_counts(db, monster_carves):
for m in monsters:
rewards = db.get_monster_rewards(m.id)
mc = monster_carves.get(m.name)
print "===", m.name
print("===", m.name)
for r in rewards:
condition = r["condition"]
if "Carve" not in condition:
@ -34,13 +34,13 @@ def set_carve_counts(db, monster_carves):
elif condition == "Tail Carve":
stack_size = 1
else:
print "WARN: unknown condition %s.%s" \
% (m.name, condition)
print("WARN: unknown condition %s.%s" \
% (m.name, condition))
else:
assert False, "Unknown monster class: %s" % m["class"]
if r["stack_size"] == stack_size:
continue
print " ", condition, r["stack_size"], "=>", stack_size
print(" ", condition, r["stack_size"], "=>", stack_size)
cur = db.cursor()
cur.execute("""UPDATE hunting_rewards
SET stack_size=? WHERE _id=?""",

@ -23,7 +23,7 @@ def _add_column(cursor, table, column_spec):
def _set_stars(cursor, item_id, stars):
for k in stars.keys():
for k in list(stars.keys()):
col = k.lower() + "_stars"
q = "UPDATE items SET %s=? WHERE _id=?" % col
cursor.execute(q, (stars[k], item_id))
@ -45,7 +45,7 @@ def main():
items = db.get_items(exclude_types=["", "Armor", "Palico Weapon",
"Decoration"])
for item in items:
print item.id, item.type, item.name
print(item.id, item.type, item.name)
if item.type == "Materials":
stars = item_stars.get_material_stars(item.id)
elif item.type == "Weapon":

@ -21,7 +21,7 @@ def set_quest_ranks(db):
for quest in quests:
if not quest["name"]:
assert quest["hub"] == "Event"
print "WARN: skipping non localized event quest: %d" % quest.id
print("WARN: skipping non localized event quest: %d" % quest.id)
continue
set_quest_rank(db, quest)
@ -35,19 +35,19 @@ def set_quest_rank(db, quest):
rewards = db.get_quest_rewards(quest_id)
rank = guess_quest_rank_from_rewards(db, rewards)
if rank is None:
print "WARN: quest '%s' has no flesh rewards, assuming lower rank"\
% (quest.name.encode("utf8"),)
print("WARN: quest '%s' has no flesh rewards, assuming lower rank"\
% (quest.name.encode("utf8"),))
rank = rank_stars_guess[0]
elif rank not in rank_stars_guess:
print "ERROR: quest '%s' reward guess '%s' not in stars guess '%s'"\
% (quest.name, rank, rank_stars_guess)
print("ERROR: quest '%s' reward guess '%s' not in stars guess '%s'"\
% (quest.name, rank, rank_stars_guess))
else:
rank = rank_stars_guess
assert rank in "LR HR G".split()
quest.rank = rank
print quest.one_line_u()
print(quest.one_line_u())
cur = db.cursor()
cur.execute("UPDATE quests SET rank=? WHERE _id=?", (rank, quest_id))

@ -0,0 +1,39 @@
#!/usr/bin/env python2
import os.path
import codecs
import csv
import _pathfix
from mhapi.db import MHDB
def set_buy(db, item_id, buy):
print("buy", item_id, buy)
cur = db.cursor()
cur.execute("""UPDATE items SET
buy=? WHERE _id=?""",
(buy, item_id))
def set_buy_by_name(db, name, buy):
cur = db.cursor()
cur.execute("""UPDATE items SET
buy=? WHERE name=?""",
(buy, name))
rowid = cur.lastrowid
print("buy", rowid, name, buy)
if __name__ == '__main__':
db = MHDB(game="4u")
delta_file_path = os.path.join(_pathfix.db_path, "mh4u", "weapon_shop.csv")
with open(delta_file_path) as f:
reader = csv.reader(f)
for row in reader:
name = row[0]
value = int(row[1])
set_buy_by_name(db, name, value)
db.commit()
db.close()

@ -10,7 +10,7 @@ from mhapi.db import MHDB
def set_upgrade_cost(db, item_id, upgrade_cost):
print "upgrade_cost", item_id, upgrade_cost
print("upgrade_cost", item_id, upgrade_cost)
cur = db.cursor()
cur.execute("""UPDATE weapons SET
upgrade_cost=? WHERE _id=?""",
@ -18,7 +18,7 @@ def set_upgrade_cost(db, item_id, upgrade_cost):
def set_creation_cost(db, item_id, creation_cost):
print "creation_cost", item_id, creation_cost
print("creation_cost", item_id, creation_cost)
cur = db.cursor()
cur.execute("""UPDATE weapons SET
creation_cost=? WHERE _id=?""",

@ -78,14 +78,14 @@ class WeaponTypeMotionValues(object):
self.motion_values[name] = MotionValue(name, d["type"], d["power"])
self.average = (sum(mv.average
for mv in self.motion_values.itervalues())
for mv in self.motion_values.values())
/ len(self))
def __len__(self):
return len(self.motion_values)
def keys(self):
return self.motion_values.keys()
return list(self.motion_values.keys())
def __getitem__(self, key):
return self.motion_values[key]
@ -109,7 +109,7 @@ class MotionValueDB(object):
return self.motion_values_map[weapon_type]
def keys(self):
return self.motion_values_map.keys()
return list(self.motion_values_map.keys())
def __len__(self):
return len(self.motion_values_map)
@ -158,7 +158,7 @@ class WeaponType(object):
@classmethod
def all(cls):
return cls._multiplier.keys()
return list(cls._multiplier.keys())
@classmethod
def damage_type(cls, weapon_type):
@ -187,7 +187,8 @@ class WeaponMonsterDamage(object):
critical_eye_skill=skills.CriticalEye.NONE,
element_skill=skills.ElementAttackUp.NONE,
awaken=False, artillery_level=0, limit_parts=None,
frenzy_bonus=0, blunt_power=False, is_true_attack=False):
frenzy_bonus=0, blunt_power=False, is_true_attack=False,
game="4u"):
self.weapon = weapon_row
self.monster = monster_row
self.monster_damage = monster_damage
@ -222,15 +223,23 @@ class WeaponMonsterDamage(object):
else:
self.true_raw = (self.weapon["attack"]
/ WeaponType.multiplier(self.weapon_type))
if sharp_plus == 1:
self.sharpness = self.weapon.sharpness_plus.max
elif sharp_plus == 2:
self.sharpness = self.weapon.sharpness_plus2.max
if sharp_plus:
if game == "mhr":
sharp_n = self.weapon.sharpness_plus.get_rise_handicraft(sharp_plus)
self.sharpness, self.sharpness_points = sharp_n.get_max_points()
else:
if sharp_plus == 1:
self.sharpness, self.sharpness_points = self.weapon.sharpness_plus.get_max_points()
elif sharp_plus == 2:
self.sharpness, self.sharpness_points = self.weapon.sharpness_plus2.get_max_points()
else:
self.sharpness = self.weapon.sharpness.max
#print "sharpness=", self.sharpness
self.sharpness, self.sharpness_points = self.weapon.sharpness.get_max_points()
self.sharpness_name = SharpnessLevel.name(self.sharpness)
if self.weapon["affinity"]:
if (isinstance(self.weapon["affinity"], basestring)
if (isinstance(self.weapon["affinity"], str)
and "/" in self.weapon["affinity"]):
self.chaotic = True
# Handle chaotic gore affinity, e.g. -35/10. This means that
@ -358,7 +367,7 @@ class WeaponMonsterDamage(object):
part_damage.part = part
if alt is None:
if (self.breakable_parts
and _break_find(part, self.monster_damage.parts.keys(),
and _break_find(part, list(self.monster_damage.parts.keys()),
self.breakable_parts)):
part_damage.breakable = True
if hitbox > self.max_raw_part[1]:
@ -366,15 +375,15 @@ class WeaponMonsterDamage(object):
if ehitbox > self.max_element_part[1]:
self.max_element_part = (part, ehitbox)
for part in self.damage_map.keys():
for part in list(self.damage_map.keys()):
if None not in self.damage_map[part].states:
#print "Failed to parse part:", part
del self.damage_map[part]
for part, d in self.damage_map.iteritems():
for part, d in self.damage_map.items():
if d.is_breakable():
self.break_count += 1
self.parts = self.damage_map.keys()
self.parts = list(self.damage_map.keys())
self.averages["uniform"] = self.uniform()
self.averages["raw"] = self.weighted_raw()
self.averages["element"] = self.weighted_element()
@ -403,10 +412,10 @@ class WeaponMonsterDamage(object):
artillery_level=self.artillery_level)
self.cb_phial_damage[part][level] = damage_tuple
def uniform(self):
def uniform(self, break_weight=0.25):
average = 0.0
for part, damage in self.damage_map.iteritems():
average += damage.average()
for part, damage in self.damage_map.items():
average += damage.average(break_weight)
return average / len(self.damage_map)
def weighted_raw(self):
@ -417,7 +426,7 @@ class WeaponMonsterDamage(object):
"""
average = 0.0
total_hitbox = 0.0
for part, damage in self.damage_map.iteritems():
for part, damage in self.damage_map.items():
average += damage.average() * damage.hitbox
total_hitbox += damage.hitbox
if total_hitbox == 0:
@ -430,7 +439,7 @@ class WeaponMonsterDamage(object):
"""
average = 0.0
total_ehitbox = 0.0
for part, damage in self.damage_map.iteritems():
for part, damage in self.damage_map.items():
average += damage.average() * damage.ehitbox
total_ehitbox += damage.ehitbox
if total_ehitbox == 0:
@ -444,7 +453,7 @@ class WeaponMonsterDamage(object):
else:
other_weight = (1 - weak_weight) / (len(self.parts) - 1)
average = 0
for part, damage in self.damage_map.iteritems():
for part, damage in self.damage_map.items():
if part == self.max_raw_part[0]:
weight = weak_weight
else:
@ -459,7 +468,7 @@ class WeaponMonsterDamage(object):
else:
other_weight = (1 - weak_weight) / (len(self.parts) - 1)
average = 0
for part, damage in self.damage_map.iteritems():
for part, damage in self.damage_map.items():
if part == self.max_element_part[0]:
weight = weak_weight
else:
@ -475,7 +484,7 @@ class WeaponMonsterDamage(object):
return 0
average = 0.0
count = self.break_count + 1
for part, damage in self.damage_map.iteritems():
for part, damage in self.damage_map.items():
if part == self.max_raw_part[0]:
average += damage.average()
if damage.is_breakable():
@ -494,7 +503,7 @@ class WeaponMonsterDamage(object):
return 0
average = 0.0
count = self.break_count + 1
for part, damage in self.damage_map.iteritems():
for part, damage in self.damage_map.items():
if part == self.max_element_part[0]:
# If weakpart is also a break, assume continue attacking
# even after broken
@ -516,7 +525,7 @@ class WeaponMonsterDamage(object):
if not self.break_count:
return 0
average = 0.0
for part, damage in self.damage_map.iteritems():
for part, damage in self.damage_map.items():
if damage.is_breakable():
# attack until broken, then move to next break
average += damage.total
@ -553,8 +562,21 @@ class WeaponMonsterDamage(object):
for row in self.monster_damage._rows:
part = row["body_part"]
hitbox = int(row[raw_type])
ehitbox = int(row[str(self.etype.lower())])
hitboxes.append((part, hitbox, ehitbox, float(hitbox) / ehitbox))
if self.etype:
try:
ehitbox = int(row[str(self.etype.lower())])
except IndexError:
ehitbox = 0
print("WARN: bad etype {}, row = {}".format(
self.etype, row))
else:
ehitbox = 0
if ehitbox > 0:
ratio = float(hitbox) / ehitbox
else:
ratio = 0
hitboxes.append((part, hitbox, ehitbox, ratio))
return hitboxes
def nohitbox_damage(self, motion=None):
@ -610,6 +632,20 @@ class PartDamage(object):
def ehitbox(self):
return self.states[None].ehitbox
@property
def break_hitbox(self):
if "Break Part" in self.states:
return self.states["Break Part"].hitbox
else:
return self.hitbox
@property
def break_ehitbox(self):
if "Break Part" in self.states:
return self.states["Break Part"].ehitbox
else:
return self.ehitbox
@property
def break_raw(self):
if "Break Part" in self.states:
@ -660,7 +696,8 @@ class PartDamage(object):
# If the part has a hitbox with different damage in the break
# rows from the db, or if it's explicitly marked as breakable
# (done by checking hunt rewards for breaks).
return self.break_diff() > 0 or self.breakable
return (self.breakable or self.raw != self.break_raw
or self.element != self.break_element)
def average(self, break_weight=0.25, rage_weight=0.5):
if self.break_diff():
@ -680,7 +717,8 @@ class PartDamage(object):
+ self.total * (1 - rage_weight))
def set_damage(self, raw, element, hitbox, ehitbox, state=None):
if state == "Without Hide":
if state in ("Without Hide", "Charged", "Tail Inflated", "Savaged",
"Enraged", "Ice Shield"):
state = "Break Part"
self.states[state] = PartDamageState(raw, element,
hitbox, ehitbox, state)

@ -7,6 +7,7 @@ import sqlite3
import json
from mhapi import model
from mhapi.util import WEAPON_TYPES
def field_model(key):
@ -257,10 +258,15 @@ class MHDB(object):
return self._query_all("search_item", query, tuple(args),
no_cache=True, model_cls=model.Item)
def get_monsters(self):
return self._query_all("monsters", """
SELECT * FROM monsters
""", model_cls=model.Monster)
def get_monsters(self, monster_class=None):
args = []
where = []
if monster_class is not None:
where.append("WHERE class = ?")
args.append(monster_class)
args = tuple(args)
q = "SELECT * FROM monsters " + "\n".join(where)
return self._query_all("monsters", q, args, model_cls=model.Monster)
def get_monster_names(self):
"""
@ -317,12 +323,17 @@ class MHDB(object):
WHERE quest_id=?
""", (quest_id,))
def get_monster_quests(self, monster_id, rank):
return self._query_all("monster_quests", """
SELECT DISTINCT quests.* FROM quests, monster_to_quest
WHERE monster_to_quest.quest_id = quests._id
AND monster_to_quest.monster_id=? AND rank=?
""", (monster_id, rank), model_cls=model.Quest)
def get_monster_quests(self, monster_id, rank=None):
query = """SELECT DISTINCT quests.* FROM quests, monster_to_quest
WHERE monster_to_quest.quest_id = quests._id
AND monster_to_quest.monster_id=?"""
args = [monster_id]
if rank is not None:
query += " AND rank=?"
args += [rank]
return self._query_all("monster_quests", query,
tuple(args), model_cls=model.Quest)
def get_item_quests(self, item_id):
"""
@ -381,6 +392,14 @@ class MHDB(object):
WHERE monster_id=?
""", (monster_id,), collection_cls=model.MonsterDamage)
def get_weapon_types(self):
"""
List of strings.
"""
return self._query_all("weapon_types", """
SELECT DISTINCT wtype FROM weapons
""", model_cls=field_model("wtype"))
def get_weapons(self):
# Note: weapons only available via JP DLC have no localized
# name, filter them out.
@ -613,6 +632,13 @@ class MHDB(object):
item_data.set_components(ccomps, ucomps)
def _get_rise_num_slots(slot_list):
num_slots = 0
nslots = len(slot_list)
for i in range(nslots - 1, -1, -1):
num_slots += 10**(nslots - i - 1) * slot_list[i]
return num_slots
class MHDBX(object):
"""
@ -626,12 +652,14 @@ class MHDBX(object):
"""
Loads JSON data, keeps in memory.
"""
self.game = game
module_path = os.path.dirname(__file__)
self._mhx_db_path = os.path.abspath(os.path.join(module_path, "..",
"db", game))
self._4udb = MHDB()
self._4udb = MHDB(game="gu")
self._weapon_list = []
self._weapons_by_name = {}
self._weapons_by_id = {}
self._monsters_by_name = {}
self._monster_damage = {}
@ -644,10 +672,23 @@ class MHDBX(object):
with open(os.path.join(self._mhx_db_path, "weapon_list.json")) as f:
wlist = json.load(f)
for i, wdata in enumerate(wlist):
wdata["_id"] = i
if "_id" not in wdata:
wdata["_id"] = i
keys = ["awaken", "horn_notes",
"element", "element_attack",
"element_2", "element_2_attack",
"bug_level", "phial", "phial_value",
"shelling_type", "shelling_level",
"buy"]
for k in keys:
if k not in wdata:
wdata[k] = None
if self.game == "mhr":
wdata["num_slots"] = _get_rise_num_slots(wdata["slots"])
weapon = model.Weapon(wdata)
self._weapon_list.append(weapon)
self._weapons_by_name[weapon.name_jp] = weapon
self._weapons_by_id[weapon.id] = weapon
def _load_monsters(self):
names_path = os.path.join(self._mhx_db_path,
@ -662,20 +703,39 @@ class MHDBX(object):
self._monsters_by_name[d["name"]] = model.Monster(d)
with open(hitboxes_path) as f:
damage_map = json.load(f)
for name, damage in damage_map.iteritems():
for name, damage in damage_map.items():
mid = self._monsters_by_name[name].id
damage_rows = []
for part, data in damage.iteritems():
for part, data in damage.items():
if part.startswith("_"):
continue
row = dict((k.lower(), v) for k, v in data.iteritems())
row = dict((k.lower(), v) for k, v in data.items())
row["body_part"] = part
row["ko"] = 100 if part == "Head" else 0
row["_id"] = 0
row["monster_id"] = mid
damage_rows.append(row)
self._monster_damage[mid] = model.MonsterDamage(damage_rows)
self._monster_breaks[mid] = damage["_breaks"]
self._monster_breaks[mid] = damage.get("_breaks", [])
def get_monsters(self):
return list(self._monsters_by_name.values())
def get_weapons(self):
return list(self._weapon_list)
def get_weapon(self, weapon_id):
return self._weapons_by_id[weapon_id]
def get_weapons_by_parent(self, parent_id):
result = []
for w in self._weapon_list:
if w.parent_id == parent_id:
result.append(w)
return result
def get_weapon_types(self):
return WEAPON_TYPES
def get_weapon_by_name(self, name):
return self._weapons_by_name.get(name)
@ -705,9 +765,10 @@ class MHDBX(object):
with no element. Otherwise @element is searched for in both
awaken and native, and can be a status or an element.
@final should be string '1' or '0'
@final None or string '1' or '0'
"""
final = int(final)
if final is not None:
final = int(final)
results = []
for w in self._weapon_list:
if wtype is not None and w.wtype != wtype:

@ -1,8 +1,11 @@
import os
import string
import json
import urllib
from typing import NamedTuple
import urllib.request, urllib.parse, urllib.error
import re
import difflib
from collections import namedtuple
from mhapi.util import EnumBase
@ -57,7 +60,7 @@ class RowModel(ModelBase):
return key in self._data
def fields(self):
return self._data.keys()
return list(self._data.keys())
def as_data(self):
return self._data
@ -69,7 +72,7 @@ class RowModel(ModelBase):
return list_data
def update_indexes(self, data):
for key_field, value_fields in self._indexes.iteritems():
for key_field, value_fields in self._indexes.items():
if key_field not in data:
data[key_field] = {}
self.update_index(key_field, value_fields, data[key_field])
@ -86,7 +89,7 @@ class RowModel(ModelBase):
def __str__(self):
if "name" in self._data and self.name is not None:
name = urllib.quote(self.name, safe=" ")
name = urllib.parse.quote(self.name, safe=" ")
else:
name = str(self.id)
return "%s '%s'" % (self.__class__.__name__, name)
@ -139,7 +142,7 @@ class SharpnessLevel(EnumBase):
WHITE = 5
PURPLE = 6
ALL = range(0, PURPLE + 1)
ALL = list(range(0, PURPLE + 1))
_names = {
RED: "Red",
@ -162,7 +165,7 @@ class SharpnessLevel(EnumBase):
PURPLE: (1.44, 1.20),
}
# for mhx, mhgen, mhxx, and likely mhw
# for mhx, mhgen, mhxx
_modifier_mhx = {
RED: (0.50, 0.25),
ORANGE: (0.75, 0.50),
@ -172,14 +175,48 @@ class SharpnessLevel(EnumBase):
WHITE: (1.32, 1.125),
}
# world, rise
# https://twitter.com/AsteriskAmpers1/status/1372886666940137479
# https://monsterhunterworld.wiki.fextralife.com/Sharpness
# https://monsterhunterrise.wiki.fextralife.com/Sharpness
_modifier_mhw = {
RED: (0.50, 0.25),
ORANGE: (0.75, 0.50),
YELLOW: (1.00, 0.75),
GREEN: (1.05, 1.00),
BLUE: (1.20, 1.0625),
WHITE: (1.32, 1.15),
PURPLE: (1.39, 1.25),
}
@classmethod
def raw_modifier(cls, sharpness):
return cls._modifier[sharpness][0]
if _game() == "4u":
d = cls._modifier
elif _game() in ["gen", "gu", "mhx"]:
d = cls._modifier_mhx
else:
d = cls._modifier_mhw
return d[sharpness][0]
@classmethod
def element_modifier(cls, sharpness):
return cls._modifier[sharpness][1]
if _game() == "4u":
d = cls._modifier
elif _game() in ["gen", "gu", "mhx"]:
d = cls._modifier_mhx
else:
d = cls._modifier_mhw
return d[sharpness][1]
_GAME = None
def _game():
global _GAME
if _GAME is None:
_GAME = os.environ.get("MHAPI_GAME", "4u")
assert _GAME in ("4u", "gen", "gu", "mhx", "mhw", "mhr")
return _GAME
class WeaponSharpness(ModelBase):
@ -195,32 +232,54 @@ class WeaponSharpness(ModelBase):
self.value_list = [int(s) for s in db_string_or_list.split(".")]
# For MHX, Gen, no purple sharpness, but keep model the same for
# simplicity
if len(self.value_list) < SharpnessLevel.PURPLE + 1:
while len(self.value_list) < SharpnessLevel.PURPLE + 1:
self.value_list.append(0)
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:
for i in range(SharpnessLevel.PURPLE, -1, -1):
if self.value_list[i] > 0:
self._max = i
break
return self._max
def get_max_points(self):
return (self.max, self.value_list[self.max])
def get_rise_handicraft(self, n):
"""In Rise, there are 5 levels of Handicraft, each give 5 extra points; this
can be used to subtract off from sharpness_plus row"""
alt_values = list(self.value_list)
assert n >= 0 and n <= 5
minus_points = 25 - n * 5
for i in range(SharpnessLevel.PURPLE, -1, -1):
val = alt_values[i]
if minus_points <= val:
alt_values[i] -= minus_points
break
else:
alt_values[i] = 0
minus_points -= val
return WeaponSharpness(alt_values)
def as_data(self):
return self.value_list
def __str__(self):
return ",".join(str(v) for v in self.value_list)
class ItemCraftable(RowModel):
_list_fields = ["id", "name"]
def __init__(self, item_row):
super(ItemCraftable, self).__init__(item_row)
self.create_components = None
self.upgrade_components = None
if "create_components" not in item_row:
self.create_components = None
if "upgrade_components" not in item_row:
self.upgrade_components = None
def set_components(self, create_components, upgrade_components):
self.create_components = create_components
@ -230,10 +289,10 @@ class ItemCraftable(RowModel):
data = super(ItemCraftable, self).as_data()
if self.create_components is not None:
data["create_components"] = dict((item.name, item.quantity)
for item in self.create_components)
for item in _item_list(self.create_components))
if self.upgrade_components is not None:
data["upgrade_components"] = dict((item.name, item.quantity)
for item in self.upgrade_components)
for item in _item_list(self.upgrade_components))
return data
@ -304,7 +363,7 @@ class Armor(ItemWithSkills):
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):
for slots in range(len(decoration_values), 0, -1):
if slots_left == 0:
break
decoration_value = decoration_values[slots-1]
@ -373,6 +432,8 @@ class Weapon(ItemCraftable):
def __init__(self, weapon_item_row):
super(Weapon, self).__init__(weapon_item_row)
self._parse_sharpness()
if "name_jp" not in self._data:
self._data["name_jp"] = self._data["name"]
def _parse_sharpness(self):
"""
@ -385,11 +446,12 @@ class Weapon(ItemCraftable):
if isinstance(self._row["sharpness"], list):
# MHX JSON data, already desired format, but doesn't have
# purple so we append 0
self.sharpness = WeaponSharpness(self._row["sharpness"] + [0])
self.sharpness_plus = WeaponSharpness(
self._row["sharpness_plus"] + [0])
self.sharpness_plus2 = WeaponSharpness(
self._row["sharpness_plus2"] + [0])
row_sharpness = self._row["sharpness"]
row_sharpness_plus = self._row.get("sharpness_plus", row_sharpness)
row_sharpness_plus2 = self._row.get("sharpness_plus2", row_sharpness)
self.sharpness = WeaponSharpness(row_sharpness)
self.sharpness_plus = WeaponSharpness(row_sharpness_plus)
self.sharpness_plus2 = WeaponSharpness(row_sharpness_plus2)
else:
# 4U or gen data from db
parts = self._row["sharpness"].split(" ")
@ -410,6 +472,10 @@ class Weapon(ItemCraftable):
# english weapons, and not for Japanese DLC weapons.
return ord(self.name[0]) < 128
@property
def sharpness_name(self):
return SharpnessLevel.name(self.sharpness)
class Monster(RowModel):
_list_fields = ["id", "class", "name"]
@ -459,6 +525,12 @@ class MonsterPartStateDamage(RowModel):
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
return str(self.as_data())
def __repr__(self):
return repr(self.as_data())
class MonsterPartDamage(ModelBase):
"""
@ -493,6 +565,49 @@ class MonsterPartDamage(ModelBase):
damage=self.states
)
def __str__(self):
return str(self.as_data())
def __repr__(self):
return repr(self.as_data())
def state_names(self):
return list(self.states.keys())
def get(self, damage_type):
return self.get_state(damage_type)
def get_break(self, damage_type):
self.get_state(damage_type, "Break Part")
def get_alt_state(self, damage_type):
return self.get_state(damage_type, self.alt_state)
def get_state(self, damage_type, state="Default"):
if state not in self.states:
state = "Default"
return self.states[state][damage_type]
def __getitem__(self, damage_type):
return self.get(damage_type)
@property
def alt_state(self):
if "Break Part" in self.states:
return "Break Part"
elif len(self.states) > 1:
alt_states = list(self.states.keys())
if "Default" in alt_states:
alt_states.remove("Default")
return alt_states[0]
else:
return "Default"
def state_diff(self, damage_type, state=None):
if state is None:
state = self.alt_state
return self.get_state(damage_type, state) - self.get(damage_type)
class MonsterDamage(ModelBase):
"""
@ -518,6 +633,10 @@ class MonsterDamage(ModelBase):
self.parts[part] = MonsterPartDamage(part)
self.parts[part].add_state(state, row)
def is_valid(self):
# TODO: more validation
return (len(self.states) > 0 and len(self.parts) > 0)
def as_data(self):
return dict(
states=list(self.states),
@ -529,11 +648,54 @@ class MonsterDamage(ModelBase):
Set breakable flag on parts based on the breakable list from
rewards (use MHDB.get_monster_breaks).
"""
for name, part_damage in self.parts.iteritems():
for name, part_damage in self.parts.items():
if _break_find(name, self.parts, breakable_list):
#print "part %s is breakable [by rewards]" % name
part_damage.breakable = True
def state_names(self):
names = list(self.states)
names.sort()
if "Default" in names:
names.remove("Default")
names.insert(0, "Default")
return names
def keys(self):
return self.parts.keys()
def values(self):
return self.parts.values()
def items(self):
return self.parts.items()
def __len__(self):
return len(self.parts)
def __iter__(self):
return iter(self.parts)
@property
def alt_state(self):
alt_states = set(self.states)
alt_states.remove("Default")
if alt_states:
return alt_states.pop()
return "Default"
def avg(self, damage_type, state="Default"):
return sum(part.get_state(damage_type, state)
for part in self.values()) / len(self)
def alt_avg(self, damage_type):
return sum(part.get_alt_state(damage_type)
for part in self.values()) / len(self)
def max(self, damage_type, state="Default"):
return max(part.get_state(damage_type, state)
for part in self.values())
def get_decoration_values(skill_id, decorations):
"""
@ -610,19 +772,19 @@ def get_costs(db, weapon):
upgrade_cost = int(weapon.upgrade_cost)
except ValueError:
upgrade_cost = 0
print "WARN: bad upgrade cost for '%s' (%s): '%s'" \
% (weapon.name, weapon.id, weapon.upgrade_cost)
print("WARN: bad upgrade cost for '%s' (%s): '%s'" \
% (weapon.name, weapon.id, weapon.upgrade_cost))
except UnicodeError:
upgrade_cost = 0
cost_display = urllib.quote(weapon.upgrade_cost)
print "WARN: bad upgrade cost for '%s' (%s): '%s'" \
% (weapon.name, weapon.id, cost_display)
cost_display = urllib.parse.quote(weapon.upgrade_cost)
print("WARN: bad upgrade cost for '%s' (%s): '%s'" \
% (weapon.name, weapon.id, cost_display))
parent_weapon = db.get_weapon(weapon.parent_id)
costs = get_costs(db, parent_weapon)
for cost in costs:
cost["zenny"] += upgrade_cost
cost["path"] += [weapon]
for item in weapon.upgrade_components:
for item in _item_list(weapon.upgrade_components):
if item.type == "Weapon":
continue
if item.name not in cost["components"]:
@ -632,28 +794,65 @@ def get_costs(db, weapon):
try:
zenny = int(weapon.creation_cost)
except ValueError:
print "WARN: bad creation cost for '%s': '%s'" \
% (weapon.name, weapon.creation_cost)
print("WARN: bad creation cost for '%s': '%s'" \
% (weapon.name, weapon.creation_cost))
zenny = weapon.upgrade_cost or 0
create_cost = dict(zenny=zenny,
path=[weapon],
components={})
for item in weapon.create_components:
for item in _item_list(weapon.create_components):
create_cost["components"][item.name] = item.quantity
costs = [create_cost] + costs
if weapon.buy:
buy_cost = dict(zenny=int(weapon.buy),
path=[weapon],
components={})
costs = [buy_cost] + costs
return costs
CompItem = namedtuple("CompItem", "name quantity type")
def _item_list(comps):
if comps is None or isinstance(comps, list):
return comps
elif isinstance(comps, dict):
items = []
for k, v in comps.items():
items.append(CompItem(k, v, "material"))
return items
else:
raise ValueError("Unknown component type")
def rank_quest_level(game, hub, rank):
if game != "4u":
raise NotImplementedError()
if hub == "Village":
if rank == "G":
return 10
elif rank == "HR":
return 7
else:
return 1
elif hub == "Guild":
if rank == "G":
return 8
elif rank == "HR":
return 4
else:
return 1
class ItemStars(object):
"""
Get the game progress (in hub stars) required to make an item. Caches
values.
"""
def __init__(self, db):
self.db = db
self._item_stars = {} # item id -> stars dict
self._weapon_stars = {} # weapon id -> stars dict
self._monster_stars = {} # monster id -> stars dict
self._wyporium_trades = {}
if self.db.game == "4u":
@ -678,8 +877,12 @@ class ItemStars(object):
costs = get_costs(self.db, weapon)
# find least 'expensive' path
for c in costs:
# don't calculate stars from buy cost (buys aren't available
# until after item is craftable, sometimes much later)
if not c["components"]:
continue
current_stars = self._get_component_stars(c)
for k, v in current_stars.iteritems():
for k, v in current_stars.items():
if v is None:
continue
if stars[k] is None or v < stars[k]:
@ -734,7 +937,7 @@ class ItemStars(object):
if "Scrap" in item.name:
continue
istars = self.get_item_stars(item.id)
for k, v in stars.items():
for k, v in list(stars.items()):
if istars[k] > v:
stars[k] = istars[k]
break
@ -765,7 +968,7 @@ class ItemStars(object):
gather_locations = set()
for gather in gathering:
gather_locations.add((gather["location_id"], gather["rank"]))
for location_id, rank in list(gather_locations):
for location_id, rank in gather_locations:
gather_quests = self.db.get_location_quests(location_id, rank)
quests.extend(gather_quests)
@ -773,7 +976,7 @@ class ItemStars(object):
monster_ranks = set()
for monster in monsters:
monster_ranks.add((monster["monster_id"], monster["rank"]))
for monster_id, rank in list(monster_ranks):
for monster_id, rank in monster_ranks:
monster_quests = self.db.get_monster_quests(monster_id, rank)
quests.extend(monster_quests)
@ -785,17 +988,36 @@ class ItemStars(object):
if quest.stars == 0:
# ignore training quests
if "Training" not in quest.name:
print "Error: non training quest has 0 stars", \
quest.id, quest.name
print("Error: non training quest has 0 stars", \
quest.id, quest.name)
continue
if quest.hub in stars:
current = stars[quest.hub]
if current is None or quest.stars < current:
stars[quest.hub] = quest.stars
else:
print "Error: unknown hub", quest.hub
# if available guild or village, then null out permit/arena values,
print("Error: unknown hub", quest.hub)
if stars["Village"] is None and stars["Guild"] is None:
# not available from quests or gathering, may be an
# Everwood only monster (4u). Set stars based on rank. Note
# that this is imperfect, because some monsters have special
# quests and unlock them appearing in the Everwood, but at least
# will prevent totally broken G-rank weapons showing up in
# low rank comparisons.
min_village = 10
min_guild = 10
for _, rank in monster_ranks:
v = rank_quest_level(self.db.game, "Village", rank)
if v < min_village:
min_village = v
g = rank_quest_level(self.db.game, "Guild", rank)
if g < min_guild:
min_guild = g
stars["Village"] = min_village
stars["Guild"] = min_guild
# If available guild or village, then null out permit/arena values,
# because they are more useful for filtering if limited to items
# exclusively available from permit or arena. Allows matching
# on based on meeting specified critera for
@ -807,3 +1029,21 @@ class ItemStars(object):
self._item_stars[item_id] = stars
return stars
def get_monster_stars(self, monster_id):
stars = self._monster_stars.get(monster_id)
if stars is not None:
return stars
stars = dict(Village=None, Guild=None, Permit=None, Arena=None,
Event=None)
quests = self.db.get_monster_quests(monster_id)
for quest in quests:
if quest.hub == "Caravan":
quest.hub = "Village"
if stars[quest.hub] is None or quest.stars < stars[quest.hub]:
stars[quest.hub] = quest.stars
self._monster_stars[monster_id] = stars
return stars

@ -3,7 +3,7 @@ Calculate expected values for monster hunter items and find the best quests
and hunts for getting an item with specified skills.
"""
from __future__ import print_function
from collections import OrderedDict
from mhapi import stats
@ -105,7 +105,7 @@ class GatherLocation(object):
for gr in self._rewards:
gr.print(out, indent)
def __nonzero__(self):
def __bool__(self):
return bool(len(self._rewards))
def __len__(self):
@ -146,7 +146,7 @@ class QuestReward(object):
* (LuckSkill.AMAZING - LuckSkill.NONE + 1))
else:
counts = [stats.quest_reward_expected_c(self.slot, skill)
for skill in xrange(LuckSkill.NONE,
for skill in range(LuckSkill.NONE,
LuckSkill.AMAZING+1)]
@ -218,7 +218,7 @@ class QuestItemExpectedValue(object):
# chances there are to get other rewards.
fixed_seen = dict(A=set(), B=set(), Sub=set())
dups = dict()
for i in xrange(len(rewards)):
for i in range(len(rewards)):
reward = rewards[i]
slot = reward["reward_slot"]
if reward["percentage"] == 100:
@ -251,7 +251,7 @@ class QuestItemExpectedValue(object):
self.slot_rewards[reward.slot].append(reward)
evs = reward.expected_values()
for i in xrange(len(evs)):
for i in range(len(evs)):
self.total_expected_values[i] += evs[i]
def print(self, out, indent=2):
@ -320,10 +320,10 @@ class HuntReward(object):
shiny=self.shiny)
kill_ev = dict()
cap_ev = dict()
for skill in xrange(CarvingSkill.NONE, CarvingSkill.GOD+1):
for skill in range(CarvingSkill.NONE, CarvingSkill.GOD+1):
kill_ev[CarvingSkill.name(skill)] = \
self.expected_value(STRAT_CAP, carving_skill=skill)
for skill in xrange(CapSkill.NONE, CapSkill.GOD+1):
for skill in range(CapSkill.NONE, CapSkill.GOD+1):
cap_ev[CapSkill.name(skill)] = self.expected_value(STRAT_CAP,
cap_skill=skill)
@ -357,7 +357,7 @@ class HuntReward(object):
self.kill = False
counts = [
stats.capture_reward_expected_c(skill)
for skill in xrange(CapSkill.NONE,
for skill in range(CapSkill.NONE,
CapSkill.GOD+1)
]
elif self.condition == "Virus Reward":
@ -393,7 +393,7 @@ class HuntReward(object):
if self.skill == SKILL_CARVING:
counts = [
self.stack_size + stats.carve_delta_expected_c(skill)
for skill in xrange(CarvingSkill.NONE,
for skill in range(CarvingSkill.NONE,
CarvingSkill.GOD+1)
]
@ -624,7 +624,7 @@ class HuntItemExpectedValue(object):
carving_skill=carving_skill)
return ev
def __nonzero__(self):
def __bool__(self):
return bool(len(self.matching_rewards))
def __len__(self):
@ -640,10 +640,10 @@ class HuntItemExpectedValue(object):
rewards=[r.as_data() for r in self.matching_rewards])
kill_ev = dict()
cap_ev = dict()
for skill in xrange(CarvingSkill.NONE, CarvingSkill.GOD+1):
for skill in range(CarvingSkill.NONE, CarvingSkill.GOD+1):
kill_ev[CarvingSkill.name(skill)] = \
self.expected_value(STRAT_CAP, carving_skill=skill)
for skill in xrange(CapSkill.NONE, CapSkill.GOD+1):
for skill in range(CapSkill.NONE, CapSkill.GOD+1):
cap_ev[CapSkill.name(skill)] = self.expected_value(STRAT_CAP,
cap_skill=skill)
@ -737,8 +737,8 @@ class ItemRewards(object):
key = (mid, rank)
self._hunt_items[key] = hunt_item
for rank, skill_sets in self.rank_skill_sets.iteritems():
for s in skill_sets.itervalues():
for rank, skill_sets in self.rank_skill_sets.items():
for s in skill_sets.values():
s.add_hunt_option(hunt_item)
def get_hunt_item(self, monster_id, monster_rank):
@ -777,15 +777,15 @@ class ItemRewards(object):
gather_key = (quest_item.quest.location_id, quest_item.quest.rank)
gather_location = self._gather_items.get(gather_key)
for rank, skill_sets in self.rank_skill_sets.iteritems():
for s in skill_sets.itervalues():
for rank, skill_sets in self.rank_skill_sets.items():
for s in skill_sets.values():
s.add_quest_option(quest_item, hunt_items, gather_location)
def print_gather_locations(self, out):
if not self._gather_items:
return
for gl in self._gather_items.itervalues():
for gl in self._gather_items.values():
out.write("(GATHER) %s %s\n"
% (gl.location_name, gl.rank))
gl.print(out, indent=2)
@ -798,7 +798,7 @@ class ItemRewards(object):
if not self._hunt_items:
return
for hunt_item in self._hunt_items.itervalues():
for hunt_item in self._hunt_items.values():
out.write("(HUNT) %s %s\n"
% (hunt_item.monster_name, hunt_item.monster_rank))
hunt_item.print(out, indent=2)
@ -826,13 +826,13 @@ class ItemRewards(object):
def print_recommended_hunts(self, out):
out.write("*** Poogie Recommends ***\n")
for rank, skill_sets in self.rank_skill_sets.iteritems():
for rank, skill_sets in self.rank_skill_sets.items():
no_skill_best = skill_sets["No skills"].best
if no_skill_best is None:
# not available at this rank
continue
out.write("> " + rank + "\n")
for name, skill_set in skill_sets.iteritems():
for name, skill_set in skill_sets.items():
if skill_set.best is None:
# Don't print out a rank with no options
continue
@ -850,8 +850,8 @@ 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.
"""
for quest_item in self._quest_items.itervalues():
out.write("(QUEST) " + unicode(quest_item.quest) + "\n")
for quest_item in self._quest_items.values():
out.write("(QUEST) " + str(quest_item.quest) + "\n")
out.write(" %20s" % "= Quest\n")
quest_item.print(out, indent=2)
@ -921,7 +921,7 @@ class ItemRewards(object):
item_name = self.item_row["name"]
out.write("*** Wyporium trade for '%s'\n" % item_name)
out.write(" Unlocked by quest '%s'\n"
% unicode(self.trade_unlock_quest).split("\n")[0])
% str(self.trade_unlock_quest).split("\n")[0])
out.write("\n")
self.print_recommended_hunts(out)

@ -42,7 +42,7 @@ def _reward_count_p(reward_count, min_rewards, max_rewards, extend_percent):
p = 1.0
extend_p = extend_percent / 100.0
stop_p = 1.0 - extend_p
for i in xrange(min_rewards+1, reward_count+1):
for i in range(min_rewards+1, reward_count+1):
p *= extend_p
if reward_count < max_rewards:
p *= stop_p
@ -56,7 +56,7 @@ def quest_reward_p(reward_percent, min_rewards, max_rewards, extend_percent=69):
extra attempt.
"""
p = 0.0
for reward_count in xrange(min_rewards, max_rewards + 1):
for reward_count in range(min_rewards, max_rewards + 1):
p += (_reward_count_p(reward_count, min_rewards, max_rewards,
extend_percent)
* _quest_reward_p(reward_percent, reward_count))
@ -71,7 +71,7 @@ def reward_expected_c(min_rewards, max_rewards, extend_percent):
"""
total_p = 0.0
expected_attempts = 0.0
for reward_count in xrange(min_rewards, max_rewards + 1):
for reward_count in range(min_rewards, max_rewards + 1):
p = _reward_count_p(reward_count, min_rewards, max_rewards,
extend_percent)
expected_attempts += p * reward_count

@ -53,6 +53,18 @@ WTYPE_ABBR = dict(
)
DAMAGE_TYPES = """
cut
impact
shot
fire
water
thunder
ice
dragon
""".split()
class EnumBase(object):
_names = dict()

@ -0,0 +1,268 @@
#!/usr/bin/env python3
import os.path
import sys
import re
import json
import lxml.etree
import requests
#WTYPES = ["Great Sword", "Long Sword", "Sword and Shield", "Dual Blades", "Lance", "Gunlance", "Hammer"]
WTYPES = ["Great Sword", "Lance", "Hammer"]
WIDTH_RE = re.compile(r'width: *(\d+)%;')
PART_RE = re.compile(r'(.*) x(\d+)( Points)?')
# MR Bone 20 pts.
PART_RE_MR = re.compile(r'(.*) (\d+) +pts\.?')
"""
<div class="progress" style="max-width: 100%; min-width: 100px;">
<div class="progress-bar danger-color-dark" style="width: 11%;">
&nbsp;
</div>
<div class="progress-bar warning-color-dark" style="width: 20%;">
&nbsp;
</div>
<div class="progress-bar warning-color" style="width: 12%;">
&nbsp;
</div>
<div class="progress-bar success-color" style="width: 0%;">
&nbsp;
</div>
<div class="progress-bar primary-color-dark" style="width: 0%;">
&nbsp;
</div>
<div class="progress-bar white" style="width: 0%;">
&nbsp;
</div>
</div>
"""
def parse_sharpness(div):
values = []
divs = div.xpath('div')
for div in divs:
style = div.get("style")
m = WIDTH_RE.match(style)
if m:
values.append(int(m.group(1)))
return values
def parse_rampage(td):
return td.xpath('ul/li/a/text()')
def parse_crafting(td):
materials = {}
for li in td.xpath('ul/li'):
atext = li.xpath('a/text()')
litext = li.xpath('text()')
if litext:
litext = litext[0].strip()
else:
print("Unknown format: ", lxml.etree.tostring(td))
return {}
if litext.endswith('\xa0'):
litext = litext.rstrip('\xa0')
if litext.endswith('.'):
litext = litext.rstrip('.')
if litext.endswith('l'):
litext = litext[:-1] + '1'
if litext.startswith('+ '):
atext += '+'
litext = litext[2:]
if litext.startswith('x'):
litext = litext[1:]
if atext:
atext = atext[0].strip()
if litext.endswith(" Points"):
litext = litext.rstrip(" Points")
atext += " Points"
#print("atext '" + atext + "' '" + litext + "'")
try:
materials[atext] = clean_int(litext)
except Exception as e:
print("WARN: failed parsing ", atext, litext)
if litext == 'l':
materials[atext] = 1
elif litext.isdigit():
materials['zenny'] = clean_int(litext)
else:
m = PART_RE.match(litext)
if not m:
m = PART_RE_MR.match(litext)
if m:
materials[m.group(1) + ' Points'] = int(m.group(2))
elif m.group(2):
materials[m.group(1) + ' Points'] = int(m.group(2))
else:
materials[m.group(1)] = int(m.group(2))
return materials
def clean_text(t):
t = t.strip()
t = t.rstrip('\xa0')
return t
def clean_int(s):
s = clean_text(s)
if not s:
return 0
return int(s)
def parse_element(td):
#pp("td", td)
etype = td.xpath('a/text()')
if etype:
values = td.xpath('./text()')
if values:
value = clean_int(values[0].strip())
return dict(type=etype[0], attack=value)
return dict(type=None, attack=None)
def parse_rarity(td):
text = td.xpath('.//text()')
if text:
parts = text[0].split()
if len(parts) > 1:
return clean_int(text[0].split()[1])
return 8
def parse_slots(td):
slots = []
for img in td.xpath('.//img'):
title = img.get("title")
if title and title.startswith('gem_'):
parts = title.split("_")
level = int(parts[2])
slots.append(level)
return slots
def adjust_slots_rampage(data):
if data['rarity'] >= 8:
data['rampage_slot'] = data['slots'][-1]
data['slots'] = data['slots'][:-1]
else:
data['rampage_slot'] = 0
def gl_parse_tr(tr):
data = {}
cells = tr.xpath('td')
#print(lxml.etree.tostring(cells[9]))
# Name
name = cells[0]
#print(name)
data['name'] = name.xpath('a/text()')[0]
data['slots'] = parse_slots(name)
data['sharpness'] = parse_sharpness(name.xpath('div')[0])
data['attack'] = clean_int(cells[1].text)
element = parse_element(cells[2])
data['element'] = element['type']
data['element_attack'] = element['attack']
data['element_2'] = None
data['element_2_attack'] = None
data['affinity'] = clean_int(cells[3].text.rstrip('%'))
data['defense'] = clean_int(cells[4].text)
data['shot_type'] = cells[5].text
data['level'] = clean_int(cells[6].text.split()[1])
data['rarity'] = parse_rarity(cells[7])
data['rampage_skills'] = parse_rampage(cells[8])
data['crafting'] = parse_crafting(cells[9])
adjust_slots_rampage(data)
return data
def default_parse_tr(tr):
data = {}
cells = tr.xpath('td')
#print(lxml.etree.tostring(cells[9]))
if len(cells) == 10:
return gl_parse_tr(tr)
#print("cels", [c.text for c in cells])
# Name
name = cells[0]
data['name'] = name.xpath('a/text()')[0]
data['slots'] = parse_slots(name)
data['sharpness'] = parse_sharpness(name.xpath('div')[0])
data['attack'] = clean_int(cells[1].text)
element = parse_element(cells[2])
data['element'] = element['type']
data['element_attack'] = element['attack']
data['element_2'] = None
data['element_2_attack'] = None
data['affinity'] = clean_int(cells[3].text.rstrip('%'))
data['defense'] = clean_int(cells[4].text)
data['rarity'] = parse_rarity(cells[5])
data['rampage_skills'] = parse_rampage(cells[6])
data['crafting'] = parse_crafting(cells[7])
adjust_slots_rampage(data)
return data
def parse_fextralife_weapons(text):
root = lxml.etree.HTML(text)
weapons = []
table = root.xpath('//div[@id="wiki-content-block"]//table')[0]
rows = table.xpath('tbody/tr')
#print("nrows", len(rows))
for tr in rows:
data = default_parse_tr(tr)
weapons.append(data)
return weapons
def pp(name, e):
if isinstance(e, list):
for i, ei in enumerate(e):
pp(name + "[" + str(i) + "]", ei)
else:
print(name, e.tag)
print(lxml.etree.tostring(e, pretty_print=True))
def _main():
indir = sys.argv[1]
outpath = sys.argv[2]
weapon_list_all = []
for wtype in WTYPES:
print(wtype)
fpath = os.path.join(indir, wtype + ".html")
with open(fpath) as f:
text = f.read()
weapon_list = parse_fextralife_weapons(text)
for w in weapon_list:
w["wtype"] = wtype
weapon_list_all.extend(weapon_list)
with open(outpath, "w") as f:
json.dump(weapon_list_all, f, indent=2)
if __name__ == '__main__':
_main()

@ -0,0 +1,159 @@
#!/usr/bin/env python3
import sys
import os.path
import time
import re
import json
import lxml.etree
import requests
PART_HEADER_MAP = dict(Slash="Cut",
Impact="Impact",
Shot="Shot",
Fire="Fire",
Water="Water",
Ice="Ice",
Thunder="Thunder",
Dragon="Dragon")
def _td_part_id(td):
s = td.xpath('.//text()')[0].strip()
if s.startswith("["):
s = s[1:2]
return int(s)
def _td_part_break(td):
text = td.text or ""
text = text.strip()
if text:
m = re.match(r"\(x(\d+)\) (\d+)", text)
print(text, m, m.group(1), m.group(2))
return dict(count=int(m.group(1)), damage=int(m.group(2)))
return dict(count=0, damage=0)
def _td_part_sever(td):
text = td.text or ""
text = text.strip()
if text:
m = re.match(r"\((\w+)\) (\d+)", text)
return dict(type=m.group(1), damage=int(m.group(2)))
return dict(type="", damage=0)
def get_monster_data(link):
hit_data = {}
base = "https://mhrise.mhrice.info"
url = base + link
result = requests.get(url)
root = lxml.etree.HTML(result.content)
sections = root.xpath("//section")
hit_table = None
parts_table = None
for section in sections:
h2 = section.xpath('h2')
if h2 and h2[0].text:
if hit_table is None and h2[0].text.lower().startswith("hitzone"):
hit_table = section.xpath('.//table')[0]
elif parts_table is None and h2[0].text.lower().startswith("parts"):
parts_table = section.xpath('.//table')[0]
#pp("hit_table", hit_table)
#pp("tr", hit_table.xpath('thead/tr'))
header_cells = hit_table.xpath('thead/tr/th')
header_names = [th.text for th in header_cells]
#print("names", header_names)
rows = hit_table.xpath('tbody/tr')
part_id_name_map = {}
for row in rows:
if 'invalid' in row.attrib.get('class', ""):
continue
#pp("tr", row)
cols = dict(zip(header_names, row.xpath('td')))
name_td = cols["Name"]
#pp("name_td", name_td)
name_en_span = name_td.xpath('.//span[@lang="en"]/span')
if not name_en_span:
continue
name = name_en_span[0].text
#pp("part", cols["Part"].xpath('.//text()'))
part_id = _td_part_id(cols["Part"])
part_id_name_map[part_id] = name
hit_data[name] = {}
for k in PART_HEADER_MAP.keys():
hit_data[name][PART_HEADER_MAP[k]] = int(cols[k].text)
#print(hit_data)
return hit_data
# add break/sever data
header_cells = parts_table.xpath('thead/tr/th')
header_names = [th.text for th in header_cells]
#print(header_names)
rows = parts_table.xpath('tbody/tr')
breaks = []
for row in rows:
if 'invalid' in row.attrib.get('class', ""):
continue
cols = dict(zip(header_names, row.xpath('td')))
part_id = _td_part_id(cols["Part"])
part_name = part_id_name_map[part_id]
hit_data[part_name]["_stagger"] = int(cols["Stagger"].text)
part_break = cols["Break"].text or ""
part_sever = cols["Sever"].text or ""
part_break = part_break.strip()
part_sever = part_sever.strip()
hit_data[part_name]["_break"] = _td_part_break(cols["Break"])
hit_data[part_name]["_sever"] = _td_part_sever(cols["Sever"])
if part_break or part_sever:
breaks.append(part_name)
hit_data["_breaks"] = breaks
return hit_data
def pp(name, e):
if isinstance(e, list):
for i, ei in enumerate(e):
pp(name + "[" + str(i) + "]", ei)
else:
print(name, e.tag)
print(lxml.etree.tostring(e, pretty_print=True))
def get_monster_list():
result = requests.get("https://mhrise.mhrice.info/monster.html")
root = lxml.etree.HTML(result.content)
monster_li = root.xpath('//ul[@id="slist-monster"]//li')
monsters = []
for li in monster_li:
name = li.xpath('.//span[@lang="en"]/span')[0].text
link = li.xpath('a')[0].attrib['href']
monsters.append(dict(name=name, link=link))
return monsters
def _main():
outdir = sys.argv[1]
monster_list = get_monster_list()
with open(os.path.join(outdir, "monster_list.json"), "w") as f:
json.dump(monster_list, f, indent=2)
monster_hitboxes = {}
for m in monster_list:
print(m["name"])
try:
monster_hitboxes[m["name"]] = get_monster_data(m["link"])
except Exception as e:
print("ERR: failed to parse hitzones for ", m["name"])
print(repr(e), str(e))
time.sleep(0.5)
with open(os.path.join(outdir, "monster_hitboxes.json"), "w") as f:
json.dump(monster_hitboxes, f, indent=2)
if __name__ == '__main__':
_main()

@ -0,0 +1,352 @@
#!/usr/bin/env python3
import sys
import os.path
import time
import re
import json
from pprint import pprint
from collections import defaultdict
import lxml.etree
import requests
import _pathfix
from mhapi.util import WEAPON_TYPES
MAX_PER_TYPE = 100000
def pp(name, e):
if isinstance(e, list):
for i, ei in enumerate(e):
pp(name + "[" + str(i) + "]", ei)
else:
print(name, e.tag)
print(lxml.etree.tostring(e, pretty_print=True))
def parse_sharpness(value_span):
bar_span = value_span.xpath('.//span[@class="mh-sharpness-bar"]')[0]
sharp_spans = bar_span.xpath('.//span')
i = 0
last_color_num = -1
values = []
values_plus = []
for sharp_span in sharp_spans:
# <span class="mh-sharpness mh-sharpness-color-0" style="left:0%;width:47.5%;"></span>
attr_style = sharp_span.attrib["style"]
attr_class = sharp_span.attrib["class"]
classes = attr_class.split()
half = False
for class_name in classes:
if class_name.startswith("mh-sharpness-color-"):
color_num = int(class_name[-1])
if class_name == "mh-sharpness-half":
half = True
styles = attr_style.split(";")
for s in styles:
s = s.strip()
if not s:
continue
parts = s.split(":")
if parts[0] == "width":
value = int(2*float(parts[1].rstrip("%")))
break
if value == 0:
continue
if half:
if not values_plus:
values_plus = list(values)
if color_num == last_color_num:
values_plus[-1] += value
else:
values_plus.append(value)
else:
# fill in missing colors, if any
while i < color_num:
values.append(0)
i += 1
values.append(value)
i += 1
last_color_num = color_num
return values, values_plus
def _map_element(e):
if e == "Bomb":
return "Blast"
if e == "Paralyze":
return "Paralysis"
return e
def get_weapon_details(wtype, name, link):
data = dict(wtype=wtype, name=name)
url = "https://mhrise.mhrice.info" + link
result = requests.get(url)
root = lxml.etree.HTML(result.content)
icon_div = root.xpath('//div[@class="mh-title-icon"]/div[@class="mh-colored-icon"]/div')[0]
rarity_class = icon_div.attrib["class"]
data["rarity"] = int(rarity_class.split("-")[-1])
stat_div = root.xpath('//div[@class="mh-kvlist"]')[0]
kvlist = stat_div.xpath('.//p[@class="mh-kv"]')
for kv in kvlist:
spans = kv.xpath('span')
key = spans[0].text.strip().lower()
if key in set(["attack", "affinity", "defense"]):
value = spans[1].text
value = value.rstrip("%")
data[key.lower()] = int(value)
elif key == "element":
value_spans = spans[1].xpath("span")
value = value_spans[0].text.strip()
if value:
parts = value.split()
if parts[0] == "None":
data["element"] = None
data["element_attack"] = None
else:
data["element"] = _map_element(parts[0])
data["element_attack"] = int(parts[1])
if len(value_spans) > 1:
value = value_spans[1].text.strip()
parts = value.split()
data["element_2"] = _map_element(parts[0])
data["element_2_attack"] = int(parts[1])
else:
data["element_2"] = None
data["element_2_attack"] = None
elif key == "slot":
# <img alt="A level-2 slot" class="mh-slot" src="/resources/slot_1.png">
# <img alt="A level-4 slot" class="mh-slot-large" src="/resources/slot_3.png">
slots = []
value_span = spans[1]
slot_imgs = value_span.xpath('.//span[@class="mh-slot-outer"]/img')
for slot_img in slot_imgs:
src = slot_img.attrib["src"]
m = re.match(r".*/slot_(\d+)\.png", src)
if m:
svalue = int(m.group(1)) + 1
slots.append(svalue)
data["slots"] = slots
elif key == "rampage slot":
slots = []
value_span = spans[1]
slot_imgs = value_span.xpath('.//span[@class="mh-slot-outer"]/img')
for slot_img in slot_imgs:
src = slot_img.attrib["src"]
m = re.match(r".*/slot_(\d+).png", src)
if m:
svalue = int(m.group(1)) + 1
slots.append(svalue)
data["rampage_slots"] = slots
elif key == "sharpness":
value_span = spans[1]
sharp, sharp_plus = parse_sharpness(value_span)
data["sharpness"] = sharp
data["sharpness_plus"] = sharp_plus
elif key == "bottle":
value = spans[1].text.strip()
if wtype == "Charge Blade":
key = "phial"
if value == "Power":
value = "Impact"
if value == "StrongElement":
value = "Element"
if wtype == "Switch Axe":
key = "phial"
parts = value.split()
value = parts[0]
if value == "StrongElement":
value = "Element"
if value == "DownStamina":
value = "Exhaust"
phial_num = int(parts[1])
if phial_num > 0:
data["phial_value"] = phial_num
data[key] = value
elif key == "type":
value = spans[1].text.strip()
parts = value.split()
value = parts[0]
if len(parts) > 1:
level = int(parts[1])
data["shelling_level"] = level
if wtype == "Gunlance":
key = "shelling_type"
if value == "Radial":
value = "Long"
elif value == "Diffusion":
value = "Wide"
data[key] = value
elif key == "insect level":
value = spans[1].text.strip()
data["bug_level"] = int(value)
sections = root.xpath("//section")
craft_table = None
for section in sections:
h2 = section.xpath("h2/text()")
if h2 and h2[0] == "Crafting":
craft_table = section.xpath("div/table/tbody")[0]
break
if craft_table is not None:
rows = craft_table.xpath("tr")
for row in rows:
cells = row.findall("td")
craft_type = cells[0].text.strip()
if craft_type.startswith("Forge"):
zenny, comps = get_components(cells)
data["creation_cost"] = zenny
data["create_components"] = comps
elif craft_type.startswith("Upgrade"):
zenny, comps = get_components(cells)
data["upgrade_cost"] = zenny
data["upgrade_components"] = comps
return data
def get_components(cells):
zenny = int(cells[1].text)
cmat_text = cells[2].text
components = {}
if cmat_text != "-":
cmat_name = cells[2].xpath('.//span[@lang="en"]/span')[0].text
cmat_points_string = cells[2].xpath("span")[0].tail
cmat_points = int(cmat_points_string.split(" ")[0])
components[cmat_name] = cmat_points
li_mats = cells[3].xpath("ul/li")
for li in li_mats:
count = int(li.text.strip().rstrip("x"))
name = li.xpath('.//span[@lang="en"]/span')[0].text
components[name] = count
return (zenny, components)
def get_rice_id(link):
# /weapon/GreatSword_026.html
fname_base, _ = os.path.splitext(os.path.basename(link))
_, tail = fname_base.rsplit("_", maxsplit=1)
return int(tail)
def get_weapon_list(wtype, id_offset):
if wtype == "Sword and Shield":
ftype = "short_sword"
elif wtype == "Hunting Horn":
ftype = "horn"
elif wtype == "Gunlance":
ftype = "gun_lance"
elif wtype == "Switch Axe":
ftype = "slash_axe"
elif wtype == "Charge Blade":
ftype = "charge_axe"
else:
ftype = wtype.lower().replace(" ", "_")
list_fname = ftype + ".html"
result = requests.get("https://mhrise.mhrice.info/weapon/" + list_fname)
root = lxml.etree.HTML(result.content)
weapon_tree_li = root.xpath('//div[@class="mh-weapon-tree"]//li')
weapons = []
seen = set()
for li in weapon_tree_li:
listack = [li]
name_stack = [None]
while listack:
current_li = listack.pop()
parent_name = name_stack.pop()
a = current_li.xpath('a[@class="mh-icon-text"]')[0]
sublists = current_li.xpath('ul/li')
name = a.xpath('.//span[@lang="en"]/span')[0].text
link = a.attrib['href']
name_stack.extend([name] * len(sublists))
listack.extend(sublists)
if link in seen:
print("WARN: Duplicate ", name, link)
continue
seen.add(link)
id_ = get_rice_id(link) + id_offset
final = (len(sublists) == 0)
wdata = dict(name=name, link=link, _id=id_, parent_name=parent_name, final=final)
weapons.append(wdata)
return weapons
def test_details():
tests = [
("Great Sword", "Sinister Shadowblade+", "/weapon/GreatSword_403.html"),
("Great Sword", "Redwing Claymore I", "/weapon/GreatSword_068.html"),
("Great Sword", "Defender Great Sword I", "/weapon/GreatSword_132.html"),
("Great Sword", "Kamura Warrior Cleaver", "/weapon/GreatSword_300.html"),
("Dual Blades", "Blood Wind Skards+", "/weapon/DualBlades_319.html"),
("Switch Axe", "Arzuros Jubilax", "/weapon/SlashAxe_323.html"),
("Switch Axe", "Leave-Taker+", "/weapon/SlashAxe_307.html"),
("Insect Glaive", "Fine Kamura Glaive", "/weapon/InsectGlaive_302.html"),
]
for t in tests:
print(t)
d = get_weapon_details(*t)
pprint(d)
print()
def _main():
weapons_type_name_map = defaultdict(dict)
weapons_data = []
outdir = sys.argv[1]
outfile = os.path.join(outdir, "weapon_list.json")
if os.path.exists(outfile):
print("Loading existing data from ", outfile)
with open(outfile) as f:
old_data = json.load(f)
for d in old_data:
wtype_name_map = weapons_type_name_map[d["wtype"]]
if d["name"] in wtype_name_map:
print("Removing duplicate ", d["wtype"], d["name"])
continue
wtype_name_map[d["name"]] = d
for itype, wtype in enumerate(WEAPON_TYPES):
wtype_name_map = weapons_type_name_map[wtype]
weapons = get_weapon_list(wtype, (itype+1) * MAX_PER_TYPE)
if not weapons:
print("WARN: no weapons of type", wtype)
continue
name_id_map = {}
for w in weapons:
# always re-calculate IDs
name_id_map[w["name"]] = w["_id"]
if w["parent_name"]:
w["parent_id"] = name_id_map[w["parent_name"]]
else:
w["parent_id"] = None
data = wtype_name_map.get(w["name"])
if data is not None:
print("UP ", wtype, w["_id"], w["name"], w["link"])
data.update(w)
weapons_data.append(data)
continue
print("ADD", wtype, w["_id"], w["name"], w["link"])
data = get_weapon_details(wtype, w["name"], w["link"])
data.update(w)
weapons_data.append(data)
time.sleep(0.5)
with open(os.path.join(outdir, "weapon_list.json"), "w") as f:
json.dump(weapons_data, f, indent=2)
if __name__ == '__main__':
#test_details()
_main()

@ -1,7 +1,7 @@
#!/usr/bin/env python2
# vim: set fileencoding=utf8 :
import urllib
import urllib.request, urllib.parse, urllib.error
import os
import json
import sys
@ -34,74 +34,74 @@ _RANGED_TYPES = ["Bow", "Light Bowgun", "Heavy Bowgun"]
_ELEMENT_MAP = {
u"": "Fire",
u"": "Water",
u"": "Thunder",
u"": "Ice",
u"": "Dragon",
u"": "Poison",
u"麻痺": "Paralysis",
u"睡眠": "Sleep",
u"爆破": "Blast",
"": "Fire",
"": "Water",
"": "Thunder",
"": "Ice",
"": "Dragon",
"": "Poison",
"麻痺": "Paralysis",
"睡眠": "Sleep",
"爆破": "Blast",
}
_GL_SHOT_TYPES = {
u"通常": "Normal",
u"放射": "Long",
u"拡散": "Wide",
"通常": "Normal",
"放射": "Long",
"拡散": "Wide",
}
_SA_PHIAL_TYPES = {
u"強撃ビン": "Power",
u"減気ビン": "Exhaust",
u"滅龍ビン": "Dragon",
u"強属性ビン": "Element",
u"毒ビン": "Poison",
u"麻痺ビン": "Paralysis",
"強撃ビン": "Power",
"減気ビン": "Exhaust",
"滅龍ビン": "Dragon",
"強属性ビン": "Element",
"毒ビン": "Poison",
"麻痺ビン": "Paralysis",
}
_CB_PHIAL_TYPES = {
u"榴弾ビン": "Impact",
u"強属性ビン": "Element",
"榴弾ビン": "Impact",
"強属性ビン": "Element",
}
_BUG_TYPES = {
u"切断": "Cutting",
u"打撃": "Impact",
"切断": "Cutting",
"打撃": "Impact",
}
_BOW_ARC_TYPES = {
u"集中型": "Focus",
u"放散型": "Wide",
u"爆裂型": "Blast",
"集中型": "Focus",
"放散型": "Wide",
"爆裂型": "Blast",
}
_BOW_SHOT_TYPES = {
u"連射": "Rapid",
u"拡散": "Spread",
u"貫通": "Pierce",
u"重射": "Heavy",
"連射": "Rapid",
"拡散": "Spread",
"貫通": "Pierce",
"重射": "Heavy",
}
_BOW_COATINGS = {
u"強1": "Power 1",
u"強2": "Power 2",
u"属1": "Element 1",
u"属2": "Element 2",
u"": "C. Range",
u"": "Paint",
u"": "Poison",
u"": "Paralysis",
u"": "Sleep",
u"": "Exhaust",
u"": "Blast",
"強1": "Power 1",
"強2": "Power 2",
"属1": "Element 1",
"属2": "Element 2",
"": "C. Range",
"": "Paint",
"": "Poison",
"": "Paralysis",
"": "Sleep",
"": "Exhaust",
"": "Blast",
}
@ -195,8 +195,8 @@ def _add_phial_or_shot_data(data, td_element):
elif data["wtype"] == "Bow":
data["arc_type"] = _BOW_ARC_TYPES[text]
else:
msg = u"Unexpected element for wtype '%s'" % data["wtype"]
print >>sys.stderr, msg, text
msg = "Unexpected element for wtype '%s'" % data["wtype"]
print(msg, text, file=sys.stderr)
raise ValueError(msg)
@ -223,7 +223,7 @@ def _get_detailed_sharpness(name, href, parser):
weapon_level = 1
tmp_path = os.path.join(_pathfix.project_path, "tmp")
fpath = os.path.join(tmp_path, "details-%s.html" % (base_name))
urllib.urlretrieve(href, fpath)
urllib.request.urlretrieve(href, fpath)
with open(fpath) as f:
tree = etree.parse(f, parser)
data1 = tree.xpath('//*/div[@class="data1"]')
@ -252,7 +252,7 @@ def _get_detailed_sharpness(name, href, parser):
heads = tr.xpath('./th')
if heads:
for j, th in enumerate(heads):
if u"斬れ味" in th.text:
if "斬れ味" in th.text:
sharpness_col = j
continue
@ -265,8 +265,8 @@ def _get_detailed_sharpness(name, href, parser):
name = names[i]
try:
sharpness_levels = _parse_sharpness_td(sharpness_cell)
except KeyError, ValueError:
print >>sys.stderr, "bad sharpness:", href, name
except KeyError as ValueError:
print("bad sharpness:", href, name, file=sys.stderr)
raise
SHARPNESS[name] = sharpness_levels
#print name, sharpness_levels
@ -322,11 +322,11 @@ def _parse_hh_attr_td(td_element):
affinity = int(span.text.strip())
text_lines = td_element.text.strip().split("\n")
for line in text_lines:
if line.startswith(u"防御+"):
if line.startswith("防御+"):
defense = int(line[3:])
if td_element.tail:
slots = td_element.tail.count(u"")
slots = td_element.tail.count("")
return attack, affinity, defense, elements, slots
@ -347,13 +347,13 @@ def _parse_elements_td(td_element):
affinity = int(span.text.strip())
text_lines = td_element.text.strip().split("\n")
for line in text_lines:
if line.startswith(u"防御+"):
if line.startswith("防御+"):
defense = int(line[3:])
return affinity, defense, elements
def _parse_element(text):
for jp_element in sorted(_ELEMENT_MAP.keys(), key=lambda s: len(s),
for jp_element in sorted(list(_ELEMENT_MAP.keys()), key=lambda s: len(s),
reverse=True):
if text.startswith(jp_element):
value = int(text[len(jp_element):])
@ -385,7 +385,7 @@ def _parse_name_td(td_element):
def _parse_slots_td(td_element):
text = td_element.text
if text:
return text.count(u"")
return text.count("")
return 0
@ -406,9 +406,9 @@ def _parse_sharpness_td(td_element):
if sub.text is None:
continue
current.append(sub.text.count("."))
for level in xrange(3):
for level in range(3):
sharpness = sharpness_levels[level]
for i in xrange(len(sharpness), 6):
for i in range(len(sharpness), 6):
sharpness.append(0)
return sharpness_levels
@ -422,27 +422,27 @@ def _main():
raise
weapon_list = []
parser = etree.HTMLParser()
for wtype, urls in _WEAPON_URLS.iteritems():
for wtype, urls in _WEAPON_URLS.items():
for i, url in enumerate(urls):
fpath = os.path.join(tmp_path, "%s-%d.html" % (wtype, i))
urllib.urlretrieve(_BASE_URL + url, fpath)
urllib.request.urlretrieve(_BASE_URL + url, fpath)
with open(fpath) as f:
tree = etree.parse(f, parser)
wlist = extract_weapon_list(wtype, tree, parser)
weapon_list.extend(wlist)
print json.dumps(weapon_list, indent=2)
print(json.dumps(weapon_list, indent=2))
def _test_details():
parser = etree.HTMLParser()
# final level has same name
_get_detailed_sharpness(u"ベルダーハンマー",
_get_detailed_sharpness("ベルダーハンマー",
"http://wiki.mhxg.org/ida/219225.html", parser)
# final level has different name
_get_detailed_sharpness(u"テッケン",
_get_detailed_sharpness("テッケン",
"http://wiki.mhxg.org/ida/230575.html", parser)
# final level >= 10 (two chars)
_get_detailed_sharpness(u"ウィルガシェルプレス",
_get_detailed_sharpness("ウィルガシェルプレス",
"http://wiki.mhxg.org/ida/228545.html", parser)

@ -1,7 +1,7 @@
#!/usr/bin/env python2
# vim: set fileencoding=utf8 :
import urllib
import urllib.request, urllib.parse, urllib.error
import os
import json
import sys
@ -17,7 +17,7 @@ _PAGES = {
"items": "MHX:_Item_List",
}
_CIRCLE = u"\u26ab"
_CIRCLE = "\u26ab"
def extract_names_and_icons(tree):
@ -90,11 +90,11 @@ def _translate_icon_name(s):
def _main():
tmp_path = os.path.join(_pathfix.project_path, "tmp")
outdir = os.path.join(_pathfix.project_path, "db", "mhx")
for name, page in _PAGES.iteritems():
for name, page in _PAGES.items():
fpath = os.path.join(tmp_path, "wikia-%s.html" % name)
opath = os.path.join(outdir, name.replace("-", "_") + ".json")
parser = etree.HTMLParser()
urllib.urlretrieve(_BASE_URL + page, fpath)
urllib.request.urlretrieve(_BASE_URL + page, fpath)
with open(fpath) as f:
tree = etree.parse(f, parser)
data = extract_names_and_icons(tree)

@ -1,7 +1,7 @@
#!/usr/bin/env python2
# vim: set fileencoding=utf8 :
import urllib
import urllib.request, urllib.parse, urllib.error
import os
import json
import sys
@ -14,7 +14,7 @@ _BASE_URL = "http://monsterhunter.wikia.com/wiki/"
_PAGE = "MHX:_Palico_Skills"
_CIRCLE = u"\u26ab"
_CIRCLE = "\u26ab"
def extract_arts_and_skills(tree):
@ -29,7 +29,7 @@ def extract_arts_and_skills(tree):
rows = list(table)
for row in rows:
cols, is_header = _get_column_cells_texts(row)
print is_header, cols
print(is_header, cols)
continue
if is_header:
if len(cols) == 1:
@ -100,13 +100,13 @@ def _main():
tmp_path = os.path.join(_pathfix.project_path, "tmp")
fpath = os.path.join(tmp_path, "wikia-palico-skills.html")
parser = etree.HTMLParser()
urllib.urlretrieve(_BASE_URL + _PAGE, fpath)
urllib.request.urlretrieve(_BASE_URL + _PAGE, fpath)
with open(fpath) as f:
tree = etree.parse(f, parser)
arts, skills = extract_arts_and_skills(tree)
#print json.dumps(weapon_list, indent=2)
print json.dumps(arts, indent=2)
print json.dumps(skills, indent=2)
print(json.dumps(arts, indent=2))
print(json.dumps(skills, indent=2))
if __name__ == '__main__':

@ -0,0 +1,36 @@
## -*- coding: utf-8 -*-
<!DOCTYPE html>
<html>
<head>
<title>Poogie's Calculator - ${self.title()}</title>
<meta charset="utf-8" />
<!-- Include meta tag to ensure proper rendering and touch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="/js/jquery.min.js"></script>
<!--link rel="stylesheet" href="/css/jquery-ui.min.css" /-->
<!--script src="/js/jquery-ui.min.js"></script-->
<script src="/js/common.js"></script>
</head>
<link rel="stylesheet" href="/css/poogie.css">
<style>
body {
font-size: 1em;
line-height: 1.3;
font-family: sans-serif;
}
img.icon { width: 20px; height: 20px; }
</style>
<body>
<h1><%block name="title"/></h1>
${self.body()}
</body>

@ -0,0 +1,67 @@
<%inherit file="base.html" />
<div id="poogie-header">
<div class="poogie-header-content">
<div class="poogie-header-title">Menu</div>
</div>
</div>
<div id="poogie-content">
<div class="pure-menu">
<ul class="pure-menu-list">
<li class="pure-menu-item poogie-menu-item">
<a class="pure-menu-link"
href="skilltrees-en.html">Skill Trees</a>
</li>
<li class="pure-menu-item poogie-menu-item">
<a class="pure-menu-link" href="hunterarts.html">Hunter Arts</a>
</li>
<li class="pure-menu-item poogie-menu-item">
<a class="pure-menu-link" href="monsters-en.html">Monsters</a>
</li>
<li class="pure-menu-item poogie-menu-item">
<a class="pure-menu-link"
href="monster-titles.html">Monster Titles</a>
</li>
<li class="pure-menu-heading poogie-menu-heading">Items</li>
<li class="pure-menu-item poogie-menu-item">
<a class="pure-menu-link"
href="items-en.html">Items (Usable, Gatherable)</a>
</li>
<li class="pure-menu-item poogie-menu-item">
<a class="pure-menu-link"
href="items-carve-en.html">Monster Carves</a>
</li>
<li class="pure-menu-heading poogie-menu-heading">Old Sites (searchable but slow load)</li>
<li class="pure-menu-item poogie-menu-item">
<a class="pure-menu-link"
href="site-jqueryui.html">Version 2 (home menu navigation)</a>
</li>
<li class="pure-menu-item poogie-menu-item">
<a class="pure-menu-link"
href="site-jqueryui-tabbed.html">Version 1 (tabbed navigation)</a>
</li>
<li class="pure-menu-heading poogie-menu-heading">Acknowledgments / Sources</li>
<li class="pure-menu-item poogie-menu-item">
<a class="pure-menu-link"
href="http://monsterhunter.wikia.com/wiki/MHX"
>Monster Hunter X Wiki (Wikia)</a>
</li>
<li class="pure-menu-item poogie-menu-item">
<a class="pure-menu-link"
href="https://github.com/kamegami13/MonsterHunter4UDatabase">MonsterHunter4UDatabase</a>
</li>
<li class="pure-menu-item poogie-menu-item">
<a class="pure-menu-link"
href="http://creativecommons.org/licenses/by-sa/3.0/">License CC-BY-SA</a>
</li>
</ul>
</div>
</div>

@ -0,0 +1,312 @@
## -*- coding: utf-8 -*-
<!DOCTYPE html>
<html>
<head>
<title>Poogie's Calculator: ${monster} v${village_stars} g${guild_stars}</title>
<meta charset="utf-8" />
<!-- Include meta tag to ensure proper rendering and touch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="/js/jquery.min.js"></script>
<link rel="stylesheet" href="/css/jquery-ui.min.css" />
<script src="/js/jquery-ui.min.js"></script>
<script src="/js/common.js"></script>
<link rel="stylesheet" href="/css/poogie.css">
<style>
body {
font-size: 1em;
line-height: 1.3;
font-family: sans-serif;
}
img.icon { width: 20px; height: 20px; }
.sharpness-bar {
border: 1px #a9a9a9 solid;
min-width: 82px;
height: 10px;
background-color: #a9a9a9;
float: left;
clear: both;
}
.sharpness-bar span {
display: inline-block;
height: 100%;
float: left;
}
.sharpness-bar .red {
background-color: #C00C38 !important;
}
.sharpness-bar .orange {
background-color: #E85018 !important;
}
.sharpness-bar .yellow {
background-color: #F0C830 !important;
}
.sharpness-bar .green {
background-color: #58D000 !important;
}
.sharpness-bar .blue {
background-color: #3068E8 !important;
}
.sharpness-bar .white {
background-color: #F0F0F0 !important;
}
.sharpness-bar .purple {
background-color: #c3c !important;
}
#sharpness_popup {
position: absolute;
display: none;
border: 1px solid;
background: rgba(204, 204, 204, 0.9);
z-index: 10;
}
.sharpness-box {
float: left;
width: 10px;
height: 10px;
margin: 1px;
border: 1px solid rgba(0, 0, 0, .2);
}
.Red {
background-color: #C00C38 !important;
}
.Orange {
background-color: #E85018 !important;
}
.Yellow {
background-color: #F0C830 !important;
}
.Green {
background-color: #58D000 !important;
}
.Blue {
background-color: #3068E8 !important;
}
.White {
background-color: #F0F0F0 !important;
}
.Purple {
background-color: #c3c !important;
}
</style>
<script type="text/javascript">
function load_monster_damage(form) {
var url = "/mh4u/damage/" + $("#monster").val()
+ "/v" + $("#village_stars").val()
+ "g" + $("#guild_stars").val()
+ "/" + $("#weapon_type").val() + ".html";
window.location.href = url;
}
$(document).ready(function(){
setup_monster_autocomplete("mh4u", "#monster");
$( "#village_stars" ).spinner({
spin: function(event, ui) {
if (${monster_stars["Village"]} == 0) {
if (ui.value != 0) {
return false;
}
} else if (ui.value < ${monster_stars["Village"]}) {
return false;
} else if (ui.value > 10) {
return false;
}
}
});
$( "#guild_stars" ).spinner({
spin: function(event, ui) {
if (${monster_stars["Guild"]} == 0) {
if (ui.value != 0) {
return false;
}
} else if (ui.value < ${monster_stars["Guild"]}) {
return false;
} else if (ui.value > 10) {
return false;
}
}
});
});
</script>
</head>
<body>
<form action="javascript:void(0);" onsubmit="load_monster_damage(this)">
<span>
<input id="monster" name="monster" type="text" size="20"
value="${monster}" /></td>
<select id="weapon_type" name="weapon_type">
% for wname in weapon_types:
% if wname == weapon_type:
<option value="${wname}" selected="selected">${wname}</option>
% else:
<option value="${wname}">${wname}</option>
% endif
% endfor
</select>
</span>
<span>
<label for="village_stars">Village:</label>
<input id="village_stars" name="village_stars" value="${village_stars}"
style="width: 18px">
<label for="guild_stars">Guild:</label>
<input id="guild_stars" name="guild_stars" value="${guild_stars}"
style="width: 18px">
<input type="submit" value="Go" />
</span>
</form>
<p>Breaks: ${", ".join(monster_breaks)}</p>
% if monster_damage.alt_state != "Default":
<p>Alternate state: ${monster_damage.alt_state}</p>
% endif
<table border="1" cellpadding="2" cellspacing="0">
<tr>
<th colspan="6">Weapon</th>
<th>Avg</th>
% for part in part_names:
% if part in monster_breaks:
<th style="background: pink;">${part}</th>
% else:
<th>${part}</th>
% endif
% endfor
</tr>
% for dtype in damage_types:
<% max_damage = monster_damage.max(dtype) %>
<% avg_damage = monster_damage.avg(dtype) %>
<% alt_avg_damage = monster_damage.alt_avg(dtype) %>
<tr style="border: 0px;">
<td colspan="6" align="left">
<img style="height:.8em"
title="dtype"
src="/img/${dtype.capitalize()}.png" />
${dtype}
</td>
<td align="right">
% if avg_damage > 0 or alt_avg_damage > 0:
${round(avg_damage, 1)}
% if alt_avg_damage != avg_damage:
(${round(alt_avg_damage, 1)})
% endif
<img style="height:.8em"
title="dtype"
src="/img/${dtype.capitalize()}.png" />
% else:
&nbsp;
% endif
</td>
% for part_name, part in monster_damage.items():
<% bgcolor = "lightyellow" if (part[dtype] > 0 and part[dtype] == max_damage) else "white" %>
% if part.state_diff(dtype) != 0:
<td align="right" style="background: ${bgcolor}">
% if part[dtype] > 0 or part.get_alt_state(dtype) > 0:
${part[dtype]} (${part.get_alt_state(dtype)})
<img style="height:.8em"
title="dtype"
src="/img/${dtype.capitalize()}.png" />
% else:
&nbsp;
% endif
</td>
% else:
<td align="right" style="background: ${bgcolor}">
% if part[dtype] > 0:
${part[dtype]}
<img style="height:.8em"
title="dtype"
src="/img/${dtype.capitalize()}.png" />
% else:
&nbsp;
%endif
</td>
% endif
% endfor
</tr>
% endfor
% for weapon in weapon_names:
<% damage = weapon_damage_map[weapon] %>
<% affinity = str(int(damage.affinity)) + "%" if damage.affinity else "&nbsp;" %>
<% avg = damage.uniform(0.0) %>
<% avg_break = damage.uniform(1.0) %>
<tr style="border: 0px;">
<td align="left">
<a href="/mh4u/weaponplanner.html?weapon=${weapon | u}">${weapon}</a>
</td>
<td align="right">${int(damage.attack)}</td>
<td align="right">${affinity}</td>
<td align="center" title="${damage.weapon.sharpness}">
<div class="sharpness-box ${damage.sharpness_name}" />
</td>
% if damage.eattack > 0:
<td align="right">
${int(damage.eattack)}
<img style="height:.8em"
title="${damage.etype}"
src="/img/${damage.etype}.png" />
</td>
% else:
<td>&nbsp;</td>
% endif
% if damage.eattack2 > 0:
<td align="right">
${int(damage.eattack2)}
<img style="height:.8em"
title="${damage.etype2}"
src="/img/${damage.etype2}.png" />
</td>
% else:
<td>&nbsp;</td>
% endif
<td align="right">
${round(avg, 1)}
% if avg != avg_break:
(${round(avg_break, 1)})
% endif
</td>
% for part in part_names:
<% bgcolor = "yellow" if damage[part].average() == part_max_damage[part] else "white" %>
<td align="right" style="background: ${bgcolor};">
${int(damage[part].total)}
% if damage[part].total_break != damage[part].total:
(${int(damage[part].total_break)})
% endif
</td>
% endfor
</tr>
% endfor
</table>
<div id="sharpness_popup"></div>
</body>

@ -0,0 +1,279 @@
## -*- coding: utf-8 -*-
<!DOCTYPE html>
<html>
<head>
<title>Poogie's Calculator: ${monster} r${rarity}</title>
<meta charset="utf-8" />
<!-- Include meta tag to ensure proper rendering and touch zooming -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="/js/jquery.min.js"></script>
<link rel="stylesheet" href="/css/jquery-ui.min.css" />
<script src="/js/jquery-ui.min.js"></script>
<script src="/js/common.js"></script>
<link rel="stylesheet" href="/css/poogie.css">
<style>
body {
font-size: 1em;
line-height: 1.3;
font-family: sans-serif;
}
img.icon { width: 20px; height: 20px; }
.sharpness-bar {
border: 1px #a9a9a9 solid;
min-width: 82px;
height: 10px;
background-color: #a9a9a9;
float: left;
clear: both;
}
.sharpness-bar span {
display: inline-block;
height: 100%;
float: left;
}
.sharpness-bar .red {
background-color: #C00C38 !important;
}
.sharpness-bar .orange {
background-color: #E85018 !important;
}
.sharpness-bar .yellow {
background-color: #F0C830 !important;
}
.sharpness-bar .green {
background-color: #58D000 !important;
}
.sharpness-bar .blue {
background-color: #3068E8 !important;
}
.sharpness-bar .white {
background-color: #F0F0F0 !important;
}
.sharpness-bar .purple {
background-color: #c3c !important;
}
#sharpness_popup {
position: absolute;
display: none;
border: 1px solid;
background: rgba(204, 204, 204, 0.9);
z-index: 10;
}
.sharpness-box {
float: left;
width: 10px;
height: 10px;
margin: 1px;
border: 1px solid rgba(0, 0, 0, .2);
}
.Red {
background-color: #C00C38 !important;
}
.Orange {
background-color: #E85018 !important;
}
.Yellow {
background-color: #F0C830 !important;
}
.Green {
background-color: #58D000 !important;
}
.Blue {
background-color: #3068E8 !important;
}
.White {
background-color: #F0F0F0 !important;
}
.Purple {
background-color: #c3c !important;
}
</style>
<script type="text/javascript">
function load_monster_damage(form) {
var url = "/mhr/damage/" + $("#monster").val()
+ "/r" + $("#rarity").val()
+ "/" + $("#weapon_type").val() + ".html";
window.location.href = url;
}
$(document).ready(function(){
setup_monster_autocomplete("mhr", "#monster");
$( "#rarity" ).spinner({
spin: function(event, ui) {
if (ui.value > 10) {
return false;
}
}
});
});
</script>
</head>
<body>
<form action="javascript:void(0);" onsubmit="load_monster_damage(this)">
<span>
<input id="monster" name="monster" type="text" size="20"
value="${monster}" /></td>
<select id="weapon_type" name="weapon_type">
% for wname in weapon_types:
% if wname == weapon_type:
<option value="${wname}" selected="selected">${wname}</option>
% else:
<option value="${wname}">${wname}</option>
% endif
% endfor
</select>
</span>
<span>
<label for="rarity">Rarity:</label>
<input id="rarity" name="rarity" value="${rarity}"
style="width: 18px">
<input type="submit" value="Go" />
</span>
</form>
<table border="1" cellpadding="2" cellspacing="0">
<tr>
<th colspan="6">Weapon</th>
<th>Avg</th>
% for part in part_names:
<th>${part}</th>
% endfor
</tr>
% for dtype in damage_types:
<% max_damage = monster_damage.max(dtype) %>
<% avg_damage = monster_damage.avg(dtype) %>
<% alt_avg_damage = monster_damage.alt_avg(dtype) %>
<tr style="border: 0px;">
<td colspan="6" align="left">
<img style="height:.8em"
title="dtype"
src="/img/${dtype.capitalize()}.png" />
${dtype}
</td>
<td align="right">
% if avg_damage > 0 or alt_avg_damage > 0:
${round(avg_damage, 1)}
% if alt_avg_damage != avg_damage:
(${round(alt_avg_damage, 1)})
% endif
<img style="height:.8em"
title="dtype"
src="/img/${dtype.capitalize()}.png" />
% else:
&nbsp;
% endif
</td>
% for part_name, part in monster_damage.items():
<% bgcolor = "lightyellow" if (part[dtype] > 0 and part[dtype] == max_damage) else "white" %>
% if part.state_diff(dtype) != 0:
<td align="right" style="background: ${bgcolor}">
% if part[dtype] > 0 or part.get_alt_state(dtype) > 0:
${part[dtype]} (${part.get_alt_state(dtype)})
<img style="height:.8em"
title="dtype"
src="/img/${dtype.capitalize()}.png" />
% else:
&nbsp;
% endif
</td>
% else:
<td align="right" style="background: ${bgcolor}">
% if part[dtype] > 0:
${part[dtype]}
<img style="height:.8em"
title="dtype"
src="/img/${dtype.capitalize()}.png" />
% else:
&nbsp;
%endif
</td>
% endif
% endfor
</tr>
% endfor
% for weapon in weapon_names:
<% damage = weapon_damage_map[weapon] %>
<% affinity = str(int(damage.affinity)) + "%" if damage.affinity else "&nbsp;" %>
<% avg = damage.uniform(0.0) %>
<% avg_break = damage.uniform(1.0) %>
<tr style="border: 0px;">
<td align="left">
<a href="/mhr/weaponplanner.html?weapon=${weapon | u}">${weapon}</a>
</td>
<td align="right">${int(damage.attack)}</td>
<td align="right">${affinity}</td>
<td align="center" title="${damage.weapon.sharpness}">
<div class="sharpness-box ${damage.sharpness_name}" />
</td>
% if damage.eattack > 0:
<td align="right">
${int(damage.eattack)}
<img style="height:.8em"
title="${damage.etype}"
src="/img/${damage.etype}.png" />
</td>
% else:
<td>&nbsp;</td>
% endif
% if damage.eattack2 > 0:
<td align="right">
${int(damage.eattack2)}
<img style="height:.8em"
title="${damage.etype2}"
src="/img/${damage.etype2}.png" />
</td>
% else:
<td>&nbsp;</td>
% endif
<td align="right">
${round(avg, 1)}
% if avg != avg_break:
(${round(avg_break, 1)})
% endif
</td>
% for part in part_names:
<% bgcolor = "yellow" if damage[part].average() == part_max_damage[part] else "white" %>
<td align="right" style="background: ${bgcolor};">
${int(damage[part].total)}
% if damage[part].total_break != damage[part].total:
(${int(damage[part].total_break)})
% endif
</td>
% endfor
</tr>
% endfor
</table>
<div id="sharpness_popup"></div>
</body>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -135,7 +135,7 @@ function normalize_name(s) {
function setup_item_autocomplete(selector) {
var DATA_PATH = get_base_path() + "/rewards/";
var DATA_PATH = get_base_path() + "/mh4u/rewards/";
$.getJSON(DATA_PATH + "items.json",
function(data) {
$(selector).autocomplete({ source: data });
@ -143,6 +143,16 @@ function setup_item_autocomplete(selector) {
}
function setup_monster_autocomplete(game, selector) {
var DATA_PATH = "/jsonapi/" + game + "/";
$.getJSON(DATA_PATH + "monster/_list.json",
function(data) {
var boss = data.filter(a => (a["class"] == "Boss" || a["class"] == "Large"));
var boss_names = boss.map(a => a["name"]);
$(selector).autocomplete({ source: boss_names });
});
}
function load_weapon_data(ready_fn) {
if (typeof DATA_PATH == "undefined") {
DATA_PATH = get_base_path() + "/jsonapi/";

@ -7,9 +7,9 @@
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/themes/smoothness/jquery-ui.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.min.js"></script>
<script type="text/javascript" src="js/ejs_production.js"></script>
<script type="text/javascript" src="/js/ejs_production.js"></script>
<script src="js/common.js"></script>
<script src="/js/common.js"></script>
<style>
label {
@ -34,7 +34,7 @@
</style>
<script type="text/javascript">
var DATA_PATH = get_base_path() + "/jsonapi/";
var DATA_PATH = "/jsonapi/mh4u/";
var TYPES = ["Head", "Body", "Arms", "Waist", "Legs"];
var GEAR = ["Weapon", "Head", "Body", "Arms", "Waist", "Legs", "Talisman"];
var ELEMENTS = ["fire", "water", "thunder", "ice", "dragon"];
@ -60,9 +60,9 @@
"Waist": [], "Legs": [], "Weapon": [] };
var slots_left = {};
var template_skills = new EJS({ url: "templates/skills.ejs" });
var template_resist = new EJS({ url: "templates/resistance.ejs" });
var template_decorations = new EJS({ url: "templates/decorations.ejs" });
var template_skills = new EJS({ url: "/templates/skills.ejs" });
var template_resist = new EJS({ url: "/templates/resistance.ejs" });
var template_decorations = new EJS({ url: "/templates/decorations.ejs" });
$(document).ready(function(){
$.getJSON(DATA_PATH + "armor/_index_name.json",

@ -0,0 +1,345 @@
<html>
<head>
<title>Poogie's Weapon List (Rise)</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/themes/smoothness/jquery-ui.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.min.js"></script>
<script type="text/javascript" src="../js/ejs_production.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<style>
label {
font-weight: bold;
}
body {
font-family: sans, sans-serif;
}
td.plus {
background-color: LightCyan;
}
td.minus {
background-color: LightPink;
}
td.num {
text-align: right;
}
/*@media (max-width: 600) {*/
.flexbox {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.sharpness-bar {
border: 1px #a9a9a9 solid;
min-width: 90px;
height: 7px;
background-color: #a9a9a9;
float: left;
clear: both;
}
.sharpness-bar span {
display: inline-block;
height: 100%;
float: left;
}
.sharpness-bar .red {
background-color: #C00C38 !important;
}
.sharpness-bar .orange {
background-color: #E85018 !important;
}
.sharpness-bar .yellow {
background-color: #F0C830 !important;
}
.sharpness-bar .green {
background-color: #58D000 !important;
}
.sharpness-bar .blue {
background-color: #3068E8 !important;
}
.sharpness-bar .white {
background-color: #F0F0F0 !important;
}
.sharpness-bar .purple {
background-color: #c3c !important;
}
#sharpness_popup {
position: absolute;
display: none;
border: 1px solid;
background: rgba(204, 204, 204, 0.9);
z-index: 10;
}
#cp_div {
display: none;
}
</style>
<script type="text/javascript">
var WEAPON_LIST = null;
$.ajax({
url: "../jsonapi/mhr/weapon/_all.json",
async: false,
dataType: "json",
success: function (data) {
WEAPON_LIST = data;
console.log("weapon count " + WEAPON_LIST.length);
}
});
var template_row = new EJS({ url: "/templates/weaponrow-rise.ejs" });
$(document).ready(function(){
init_page();
$("#sharpness_popup").on("click", function(evt) {
$(this).html("").offset({top:0, left:0}).hide();
});
$("#weapon_table").on("click", "#sharpness_td", function(evt) {
var td_obj = $(evt.currentTarget);
var offset = td_obj.offset();
var sharpness = td_obj.data("sharpness");
$("#sharpness_popup").html(sharpness).offset(offset).show();
});
});
function init_page() {
var qs = load_qs();
$(window).on("popstate", function(e) {
var oe = e.originalEvent;
if (oe.state !== null) {
console.log("popState:" + JSON.stringify(oe.state));
update_weapon_list(oe.state);
}
});
$("#search").click(function(evt) {
var state = get_ui_state();
save_state(state);
update_weapon_list(state);
});
if (qs) {
update_weapon_list(qs);
}
}
function load_qs() {
if ($.QueryString["weapon_type"]) {
load_state($.QueryString);
return $.QueryString;
}
return null;
}
function get_ui_state() {
return { "weapon_type": $("#weapon_type").val(),
"weapon_element": $("#weapon_element").val(),
"weapon_final": $("#weapon_final").is(":checked"),
"weapon_name_text": $("#weapon_name_text").val(),
"weapon_rarity": $("#weapon_rarity").val() };
}
function load_state(state) {
$("#weapon_type").val(state["weapon_type"]);
$("#weapon_element").val(state["weapon_element"]);
if (typeof final == "string") {
final = final.toLowerCase();
state["weapon_final"] = (final == "true" || final == "1");
}
$("#weapon_final").prop("checked", state["weapon_final"]);
$("#weapon_name_text").val(state["weapon_name_text"]);
$("#weapon_rarity").val(state["weapon_rarity"]);
}
function save_state(state, replace) {
var url = "weaponlist.html?" + encode_qs(state);
if (replace) {
window.history.replaceState(state, "", url);
} else {
window.history.pushState(state, "", url);
}
}
function weapon_predicate(state, weapon_data) {
var weapon_type = state["weapon_type"];
var weapon_element = state["weapon_element"];
var final_only = state["weapon_final"];
var rarity = state["weapon_rarity"];
var weapon_names = state["weapon_name_text"].split("|");
if (final_only && !Boolean(weapon_data["final"])) {
return false;
}
if (rarity != "Any" && weapon_data["rarity"] > rarity) {
return false;
}
if (weapon_type != "All" && weapon_type != weapon_data["wtype"]) {
return false;
}
if (weapon_element != "All"
&& weapon_element != weapon_data["element"]
&& weapon_element != weapon_data["element_2"]
&& weapon_element != weapon_data["awaken"]
&& weapon_element != weapon_data["phial"]) {
if (weapon_element != "None"
|| weapon_data["element"] != null
|| weapon_data["awaken"] != null) {
return false;
}
}
if (weapon_names && !list_match(weapon_names, [weapon_data["name"]])) {
return false;
}
return true;
}
function list_match(needles, string_list) {
var found = false;
for (var i=0; i<string_list.length; i++) {
for (var j=0; j<needles.length; j++) {
if (string_list[i].search(needles[j]) >= 0) {
found = true;
break;
}
if (found) {
break;
}
}
}
return found;
}
function update_weapon_list(state) {
var match_count = 0;
console.log("updating weapon list: " + JSON.stringify(state));
var results = [];
$.each(WEAPON_LIST, function(i, weapon_data) {
if (weapon_predicate(state, weapon_data)) {
weapon_data["id"] = i;
weapon_data["sharpness_width"] = 0.4;
match_count += 1;
set_sharpness_titles(weapon_data);
set_bow_values(weapon_data);
weapon_data["wtype_short"] =
WEAPON_TYPE_ABBR[weapon_data["wtype"]];
weapon_data["ELEMENT_ABBR"] = ELEMENT_ABBR;
var html = template_row.render(weapon_data);
results.push([weapon_data, html]);
}
});
results.sort(function (a, b) {
avals = get_weapon_sort_values(a[0]);
bvals = get_weapon_sort_values(b[0]);
return cmp_arrays(bvals, avals);
});
$("#weapon_table").empty();
$.each(results, function(i, pair) {
$("#weapon_table").append(pair[1]);
});
console.log("match count: " + match_count);
}
</script>
</head>
<body>
<div>
<table>
<tr>
<td><label for="weapon_type"
title="Only show weapons of this type"
>Type:</label></td>
<td><select id="weapon_type">
<option value="All">All</option>
<option value="Great Sword">Great Sword</option>
<option value="Long Sword">Long Sword</option>
<option value="Sword and Shield">Sword and Shield</option>
<option value="Dual Blades">Dual Blades</option>
<option value="Hammer">Hammer</option>
<option value="Hunting Horn">Hunting Horn</option>
<option value="Lance">Lance</option>
<option value="Gunlance">Gunlance</option>
<option value="Switch Axe">Switch Axe</option>
<option value="Charge Blade">Charge Blade</option>
<option value="Insect Glaive">Insect Glaive</option>
<!--option value="Light Bowgun">Light Bowgun</option-->
<!--option value="Heavy Bowgun">Heavy Bowgun</option-->
<option value="Bow">Bow</option>
</select></td>
<td><label for="weapon_element"
title="Only show weapons with this element (native or requiring awaken)"
>Element:</label></td>
<td><select id="weapon_element">
<option value="All">All</option>
<option value="None">None</option>
<option value="Fire">Fire</option>
<option value="Water">Water</option>
<option value="Thunder">Thunder</option>
<option value="Ice">Ice</option>
<option value="Dragon">Dragon</option>
<option value="Poison">Poison</option>
<option value="Paralysis">Paralysis</option>
<option value="Sleep">Sleep</option>
<option value="Blast">Blast</option>
</select></td>
<td><label>Rarity:</label>
<select id="weapon_rarity">
<option value="Any">*</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
</select></td>
<td><label for="weapon_final"
title="Only show weapons with no furthur upgrades"
>Final?</label></td>
<td><input id="weapon_final" type="checkbox" /></td>
<td><button id="search">Search</button></td>
</tr>
<tr>
<td colspan="8">
<label for="weapon_name_text"
title="Show only weapons with a match in the name. List of strings separated by '|' (vertical bar)."
>Name:</label>
<input id="weapon_name_text" size="15" />
</td>
</tr>
</table>
</div>
<table id="weapon_table">
</table>
<div id="sharpness_popup"></div>
</body>

@ -0,0 +1,284 @@
<html>
<head>
<title>Poogie's Weapon Planner (Rise)</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/themes/smoothness/jquery-ui.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.min.js"></script>
<script type="text/javascript" src="/js/ejs_production.js"></script>
<script type="text/javascript" src="/js/common.js"></script>
<style>
label {
font-weight: bold;
}
body {
font-family: sans, sans-serif;
}
td.plus {
background-color: LightCyan;
}
td.minus {
background-color: LightPink;
}
td.num {
text-align: right;
}
/*@media (max-width: 600) {*/
.flexbox {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.sharpness-bar {
border: 1px #d3d3d3 solid;
min-width: 92px;
height: 10px;
background-color: #d3d3d3;
float: left;
clear: both;
}
.sharpness-bar span {
display: inline-block;
height: 100%;
float: left;
}
.sharpness-bar .red {
background-color: #C00C38 !important;
}
.sharpness-bar .orange {
background-color: #E85018 !important;
}
.sharpness-bar .yellow {
background-color: #F0C830 !important;
}
.sharpness-bar .green {
background-color: #58D000 !important;
}
.sharpness-bar .blue {
background-color: #3068E8 !important;
}
.sharpness-bar .white {
background-color: #F0F0F0 !important;
}
.sharpness-bar .purple {
background-color: #c3c !important;
}
</style>
<script type="text/javascript">
var DATA_PATH = "/jsonapi/mhr/";
var template_path = new EJS({ url: "/templates/weaponpath.ejs" });
var template_stats = new EJS({ url: "/templates/weaponstats.ejs" });
$(document).ready(function(){
setup_weapon_autocomplete("#weapon", autocomplete_predicate,
init_page, update_search);
});
function init_page() {
load_qs();
$("#search").click(update_search);
$(window).on("popstate", function(e) {
var oe = e.originalEvent;
if (oe.state !== null) {
console.log("popState:" + JSON.stringify(oe.state));
$("#weapon_type").val(oe.state["weapon_type"]);
$("#weapon_type").change();
show_trees(oe.state["weapon"]);
}
});
$("#weapon_type").change(function(evt) {
update_weapon_autocomplete("#weapon", autocomplete_predicate,
update_search);
$("#weapon").val("");
});
}
function load_qs() {
var wtype = $.QueryString["weapon_type"];
var weapon = $.QueryString["weapon"];
if (!wtype) {
wtype = "All";
}
$("#weapon_type").val(wtype);
$("#weapon_type").change();
if (weapon) {
show_trees(weapon);
console.log("replaceState: " + weapon);
save_state(get_state(), true);
}
}
function get_state() {
return { "weapon": $("#weapon").val(),
"weapon_type": $("#weapon_type").val() };
}
function save_state(state, replace) {
var url = "/mhr/weaponplanner.html?" + encode_qs(state);
if (replace) {
window.history.replaceState(state, "", url);
} else {
window.history.pushState(state, "", url);
}
}
function autocomplete_predicate(weapon_data) {
var weapon_type = $("#weapon_type").val();
if (weapon_type != "All" && weapon_type != weapon_data["wtype"]) {
return false;
}
return true;
}
function update_search() {
var weapon_name = $("#weapon").val();
if (!weapon_name) return;
if (window.history.state
&& window.history.state["weapon"] == weapon_name) {
console.log("weapon not changed, skipping update");
return;
}
show_trees(weapon_name);
console.log("pushState: " + weapon_name);
save_state(get_state(), false);
}
function show_trees(weapon_name) {
console.log("show_trees '" + weapon_name + "'");
if (!weapon_name) return;
weapon_id = WEAPON_NAME_IDX[weapon_name][0];
console.log("show_trees(" + weapon_name + "): " + weapon_id);
$("#weapon").val(weapon_name);
$("#results").html("");
$("#weapon_stats").html("");
$.getJSON(DATA_PATH + "weapon/" + weapon_id + ".json",
function(data) {
set_sharpness_titles(data);
//set_horn_melodies_title(data);
if (data["parent_id"]) {
var parent_obj = WEAPON_ID_IDX[data["parent_id"]][0];
data["parent_name"] = parent_obj["name"];
} else {
data["parent_name"] = null;
}
data["sharpness_width"] = 0.4;
data["sharpness_plus2"] = null;
data["village_stars"] = 0
data["guild_stars"] = 0
var html = template_stats.render(data);
$("#weapon_stats").html(html);
});
$.getJSON(DATA_PATH + "weapon/" + weapon_id + "_tree.json",
function(data) {
// first pass: collect all components and sort them
var all_dict = {};
for (i=0; i<data.length; i++) {
var components = Object.keys(data[i]["components"]);
for (j=0; j<components.length; j++) {
all_dict[components[j]] = 0;
}
}
var all_components = Object.keys(all_dict);
all_components.sort();
// second pass: generate the fieldset for each weapon
// path. Note that the template uses all components
// to order the components and make them line up
for (i=0; i<data.length; i++) {
delta = {};
path = data[i];
components = path["components"]
path_string = "";
for (j=0; j<path["path"].length; j++) {
if (j != 0) {
path_string += " -&gt; ";
}
path_string += path["path"][j]["name"];
}
path["path_string"] = path_string.replace(/"/g,
'&quot;');
path["all_components"] = all_components;
path["component_list"] = Object.keys(components);
if (i > 0) {
prev_comps = data[i-1]["components"];
$.each(components, function(name, quantity) {
if (name in prev_comps) {
delta[name] = components[name]
- prev_comps[name];
}
});
}
path["delta"] = delta;
path["component_list"].sort();
path["trade_names"] = [];
for (j=0; j<all_components.length; j++) {
var name = all_components[j];
path["trade_names"][j] = "";
}
var html = template_path.render(path);
$("#results").append(html);
}
});
}
</script>
</head>
<body>
<div>
<table>
<tr>
<td><label for="weapon_type">Type:</label></td>
<td><select id="weapon_type">
<option value="All">All</option>
<option value="Great Sword">Great Sword</option>
<option value="Long Sword">Long Sword</option>
<option value="Sword and Shield">Sword and Shield</option>
<option value="Dual Blades">Dual Blades</option>
<option value="Hammer">Hammer</option>
<option value="Hunting Horn">Hunting Horn</option>
<option value="Lance">Lance</option>
<option value="Gunlance">Gunlance</option>
<option value="Switch Axe">Switch Axe</option>
<option value="Charge Blade">Charge Blade</option>
<option value="Insect Glaive">Insect Glaive</option>
<option value="Light Bowgun">Light Bowgun</option>
<option value="Heavy Bowgun">Heavy Bowgun</option>
<option value="Bow">Bow</option>
</select></td>
</tr>
<tr>
<td><label for="weapon">Weapon:</label></td>
<td><input id="weapon" name="weapon" size="20" />
<button id="search">Ask Poogie</button></td>
</tr>
</table>
</div>
<div id="weapon_stats"></div>
<div id="results" class="flexbox"></div>
</body>

@ -0,0 +1,63 @@
<tr title="id: <%= id %>">
<td><% if (final == 1) { %>
<strong>*</strong>
<% } else { %>
&nbsp;
<% } %>
</td>
<td>
<a href="weaponplanner.html?weapon=<%= encodeURIComponent(name) %>"><%= name %></a>
</td>
<td><%= wtype_short %></td>
<td style="text-align:right"><%= attack %></td>
<td style="text-align:right"><% if (affinity) { %><%= affinity %>%<% } %></td>
<td>
<% if (element) { %>
<img style="height:.8em" title="<%= element %>"
src="/img/<%= element %>.png" /> <%= element_attack %>
<%= ELEMENT_ABBR[element] %>
<% if (element_2) { %>
<img style="height:.8em" title="<%= element_2 %>"
src="/img/<%= element_2 %>.png" /> <%= element_2_attack %>
<%= ELEMENT_ABBR[element_2] %>
<% } %>
<% } %>
</td>
<td id="sharpness_td"
data-sharpness="<%= sharpness_all_title %>"
data-id="<%= id %>">
<% if (sharpness) { %>
<div class="sharpness-bar" title="<%= sharpness_all_title %>">
<span style="width:<%= sharpness[0] * sharpness_width %>px" class="red"></span>
<span style="width:<%= sharpness[1] * sharpness_width %>px" class="orange"></span>
<span style="width:<%= sharpness[2] * sharpness_width %>px" class="yellow"></span>
<span style="width:<%= sharpness[3] * sharpness_width %>px" class="green"></span>
<span style="width:<%= sharpness[4] * sharpness_width %>px" class="blue"></span>
<span style="width:<%= sharpness[5] * sharpness_width %>px" class="white"></span>
<% if (sharpness.length > 6) { %>
<span style="width:<%= sharpness[6] * sharpness_width %>px" class="purple"></span>
<% } %>
</div>
<% } %>
<% if (sharpness_plus) { %>
<div class="sharpness-bar" title="<%= sharpness_all_title %>">
<span style="width:<%= sharpness_plus[0] * sharpness_width %>px" class="red"></span>
<span style="width:<%= sharpness_plus[1] * sharpness_width %>px" class="orange"></span>
<span style="width:<%= sharpness_plus[2] * sharpness_width %>px" class="yellow"></span>
<span style="width:<%= sharpness_plus[3] * sharpness_width %>px" class="green"></span>
<span style="width:<%= sharpness_plus[4] * sharpness_width %>px" class="blue"></span>
<span style="width:<%= sharpness_plus[5] * sharpness_width %>px" class="white"></span>
<% if (sharpness_plus.length > 6) { %>
<span style="width:<%= sharpness_plus[6] * sharpness_width %>px" class="purple"></span>
<% } %>
</div>
<% } %>
</td>
<td style="text-align:right"><%= phial %>
<% if (phial_value) { %><%= phial_value %><% } %>
</td>
<td style="text-align:right"><%= shelling_type %>
<% if (shelling_level) { %><%= shelling_level %><% } %>
</td>
<td style="text-align:right"><%= bug_level %></td>
<td><%= defense ? "+" + defense + " Def" : "" %></td>

@ -81,17 +81,25 @@
title="<%= parent_name %>">(parent)</a>
<% } %>
</td>
<% if (village_stars) { %>
<td>
Village <%= village_stars %>
</td>
<% } %>
<% if (guild_stars) { %>
<td>
Guild <%= guild_stars %>
</td>
<% } %>
<% if (rarity) { %>
<td>
Rarity <%= rarity %>
</td>
<% } %>
</tr>
</table>
<% if (children.length) { %>

Loading…
Cancel
Save