4u stars filter, static damage gen, rise updates

main
Bryce Allen 3 years ago
parent 6b57d498b6
commit 228c594ca9

4
.gitignore vendored

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

@ -4,14 +4,18 @@ import sys
import argparse import argparse
import shlex import shlex
import copy import copy
import codecs
import os
import os.path
from collections import defaultdict
import _pathfix import _pathfix
from mhapi.db import MHDB, MHDBX 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.model import SharpnessLevel, Weapon, ItemStars
from mhapi import skills 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): def weapon_match_tuple(arg):
@ -65,7 +69,7 @@ def _make_db_sharpness_string(level_string):
def weapon_stats_tuple(arg): def weapon_stats_tuple(arg):
parts = arg.split(",") parts = arg.split(",")
#print "parts %r" % parts #print("parts %r" % parts)
if len(parts) < 4: if len(parts) < 4:
print("not enough parts") print("not enough parts")
raise ValueError("Bad arg, use 'name,weapon_type,sharpness,raw'") raise ValueError("Bad arg, use 'name,weapon_type,sharpness,raw'")
@ -160,8 +164,8 @@ def _add_skill_args(parser):
type=int, choices=list(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") help="1-4 for CE+1, +2, +3 and Critical God")
parser.add_argument("-e", "--element-up", parser.add_argument("-e", "--element-up",
type=int, choices=list(range(0, 5)), default=0, type=int, choices=list(range(0, 6)), default=0,
help="1-4 for (element) Atk +1, +2, +3 and" help="1-5 for (element) Atk +1, +2, +3 and"
" Element Attack Up") " Element Attack Up")
parser.add_argument("-t", "--artillery", parser.add_argument("-t", "--artillery",
type=int, choices=[0,1,2], default=0, 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", parser.add_argument("--mhw", "--monster-hunter-world", action="store_true",
default=False, default=False,
help="Adjusted attack, use MHWorld values") 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="*", parser.add_argument("-m", "--match", nargs="*",
help="WEAPON_TYPE,ELEMENT_OR_STATUS_OR_RAW" help="WEAPON_TYPE,ELEMENT_OR_STATUS_OR_RAW"
+" Include all matching weapons in their final form." +" Include all matching weapons in their final form."
@ -216,19 +223,27 @@ def parse_args(argv):
+" Examples: 'Great Sword,Raw'" +" Examples: 'Great Sword,Raw'"
+" 'Sword and Shield,Para'" +" 'Sword and Shield,Para'"
+" 'HH,Blast' 'Hammer'", +" 'HH,Blast' 'Hammer'",
type=weapon_match_tuple, default=[]) type=weapon_match_tuple, default=[],
action="append")
parser.add_argument("-w", "--weapon-custom", nargs="*", parser.add_argument("-w", "--weapon-custom", nargs="*",
help="NAME,WEAPON_TYPE,TRUE_RAW,AFFINITY,SHARPNESS" help="NAME,WEAPON_TYPE,TRUE_RAW,AFFINITY,SHARPNESS"
+"ELEMENT_TYPE,ELEMENT_ATTACK" +"ELEMENT_TYPE,ELEMENT_ATTACK"
+" Add weapon based on stats." +" Add weapon based on stats."
+" Examples: 'DinoSnS,SnS,190,0,Blue,Fire,30'" +" Examples: 'DinoSnS,SnS,190,0,Blue,Fire,30'"
+" 'AkantorHam,Hammer,240,25,Green'", +" 'AkantorHam,Hammer,240,25,Green'",
type=weapon_stats_tuple, default=[]) type=weapon_stats_tuple, default=[],
action="append")
parser.add_argument("-q", "--quest-level", parser.add_argument("-q", "--quest-level",
help="village,guild[,permit[,arena]]", help="village,guild[,permit[,arena]]",
type=quest_level_tuple) type=quest_level_tuple)
parser.add_argument("monster", parser.add_argument("-r", "--rarity",
help="Full name of monster") 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="*", parser.add_argument("weapon", nargs="*",
help="One or more weapons of same class to compare," help="One or more weapons of same class to compare,"
" full names") " full names")
@ -273,6 +288,99 @@ def _print_headers(parts, damage_map_base):
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): def print_sorted_damage(names, damage_map_base, weapon_damage_map, parts):
def uniform_average(weapon): def uniform_average(weapon):
return weapon_damage_map[weapon].averages["uniform"] return weapon_damage_map[weapon].averages["uniform"]
@ -296,7 +404,8 @@ def print_sorted_damage(names, damage_map_base, weapon_damage_map, parts):
print("% 2d" % part_damage.average(), end=' ') print("% 2d" % part_damage.average(), end=' ')
print() print()
if len(names) > 1: # this is super buggy
if False and len(names) > 1:
w1 = weapon_damage_map[names_sorted[0]] w1 = weapon_damage_map[names_sorted[0]]
w2 = weapon_damage_map[names_sorted[1]] w2 = weapon_damage_map[names_sorted[1]]
m, ratio = w1.compare_break_even(w2) m, ratio = w1.compare_break_even(w2)
@ -387,33 +496,19 @@ def match_quest_level(match_level, weapon_level):
return True return True
def main(): def run_comparison(args, db, motiondb, game_uses_true_raw, item_stars=None):
args = parse_args(None)
game_uses_true_raw = False
if args.quest_level:
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
else:
db = MHDB(game="4u", include_item_components=comps)
motiondb = MotionValueDB(_pathfix.motion_values_path)
monster = db.get_monster_by_name(args.monster) monster = db.get_monster_by_name(args.monster)
if not monster: if not monster:
raise ValueError("Monster '%s' not found" % args.monster) raise ValueError("Monster '%s' not found" % args.monster)
monster_damage = db.get_monster_damage(monster.id) 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 = [] weapons = []
weapon_type = None weapon_type = None
names_set = set() names_set = set()
@ -430,10 +525,14 @@ def main():
if skill_args: if skill_args:
skill_args_map[name] = skill_args skill_args_map[name] = skill_args
#print("args match", args.match)
for match_tuple in args.match: for match_tuple in args.match:
# TODO: better validation # TODO: better validation
if isinstance(match_tuple, list):
# TODO: is this a bug in argparse!!!????
match_tuple = match_tuple[0]
wtype, element = match_tuple wtype, element = match_tuple
if args.quest_level: if args.quest_level or args.rarity:
final=None final=None
else: else:
final=1 final=1
@ -448,7 +547,7 @@ def main():
names_set.add(w.name) names_set.add(w.name)
if args.weapon_custom: if args.weapon_custom:
weapons.extend(args.weapon_custom) weapons.extend([w[0] for w in args.weapon_custom])
if not weapons: if not weapons:
print("Err: no matching weapons") print("Err: no matching weapons")
@ -457,6 +556,29 @@ def main():
names = [w.name for w in weapons] names = [w.name for w in weapons]
monster_breaks = db.get_monster_breaks(monster.id) 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"] weapon_type = weapons[0]["wtype"]
if args.phial and weapon_type != "Charge Blade": 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")
@ -477,7 +599,6 @@ def main():
limit_parts = None limit_parts = None
if args.quest_level: if args.quest_level:
item_stars = ItemStars(db)
village, guild, permit, arena = 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() weapons2 = dict()
@ -504,6 +625,48 @@ def main():
weapons = list(weapons2.values()) weapons = list(weapons2.values())
names = [w.name for w in weapons] 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() weapon_damage_map = dict()
for row in weapons: for row in weapons:
name = row["name"] name = row["name"]
@ -512,6 +675,7 @@ def main():
raise ValueError( raise ValueError(
"Weapon '%s' is different type, got '%s' expected '%s'" "Weapon '%s' is different type, got '%s' expected '%s'"
% (name, row_type, weapon_type)) % (name, row_type, weapon_type))
#print(name, row)
try: try:
skill_args = skill_args_map.get(name, args) skill_args = skill_args_map.get(name, args)
wd = WeaponMonsterDamage(row, wd = WeaponMonsterDamage(row,
@ -526,7 +690,8 @@ def main():
limit_parts=args.parts, limit_parts=args.parts,
frenzy_bonus=skill_args.frenzy, frenzy_bonus=skill_args.frenzy,
is_true_attack=game_uses_true_raw, is_true_attack=game_uses_true_raw,
blunt_power=skill_args.blunt_power) blunt_power=skill_args.blunt_power,
game=db.game)
print("%-20s: %4.0f %2.0f%%" % (name, wd.attack, wd.affinity), end=' ') print("%-20s: %4.0f %2.0f%%" % (name, wd.attack, wd.affinity), end=' ')
if wd.etype: if wd.etype:
if wd.etype2: if wd.etype2:
@ -534,13 +699,16 @@ def main():
% (wd.eattack, wd.etype, wd.eattack2, wd.etype2), end=' ') % (wd.eattack, wd.etype, wd.eattack2, wd.etype2), end=' ')
else: else:
print("(%4.0f %s)" % (wd.eattack, wd.etype), end=' ') print("(%4.0f %s)" % (wd.eattack, wd.etype), end=' ')
print(SharpnessLevel.name(wd.sharpness), end=' ') print(SharpnessLevel.name(wd.sharpness), wd.sharpness_points, end=' ')
if skill_args != args: if skill_args != args:
print("{%s}" % ",".join(sn print("{%s}" % ",".join(sn
for sn in get_skill_names(skill_args) for sn in get_skill_names(skill_args)
if sn)) if sn))
else: 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 weapon_damage_map[name] = wd
except ValueError as e: except ValueError as e:
print(str(e)) print(str(e))
@ -564,6 +732,162 @@ def main():
print_sorted_damage(names, damage_map_base, print_sorted_damage(names, damage_map_base,
weapon_damage_map, parts) 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__': if __name__ == '__main__':
main() main()

@ -9,7 +9,7 @@ import argparse
import _pathfix import _pathfix
from mhapi.db import MHDB from mhapi.db import MHDB, MHDBX
from mhapi import model from mhapi import model
ENTITIES = """item weapon monster armor ENTITIES = """item weapon monster armor
@ -202,11 +202,12 @@ def weapon_json(db, path):
] ]
data["horn_melodies"] = melodies[w.horn_notes] data["horn_melodies"] = melodies[w.horn_notes]
stars = item_stars.get_weapon_stars(w) if db.game == "4u":
data["village_stars"] = stars["Village"] stars = item_stars.get_weapon_stars(w)
data["guild_stars"] = stars["Guild"] data["village_stars"] = stars["Village"]
data["permit_stars"] = stars["Permit"] data["guild_stars"] = stars["Guild"]
data["arena_stars"] = stars["Arena"] data["permit_stars"] = stars["Permit"]
data["arena_stars"] = stars["Arena"]
all_data.append(data) all_data.append(data)
@ -274,10 +275,13 @@ def horn_melody_json(db, path):
def main(): def main():
args = parse_args() 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: 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: if args.entities:
for entity in args.entities: for entity in args.entities:
@ -288,7 +292,10 @@ def main():
args.entities = ENTITIES args.entities = ENTITIES
if db.game != "4u": if db.game != "4u":
args.entities.remove("wyporium") try:
args.entities.remove("wyporium")
except:
pass
for entity in args.entities: for entity in args.entities:
fn = globals()["%s_json" % entity] fn = globals()["%s_json" % entity]

@ -18,6 +18,7 @@ Returns list of dict, e.g.:
import sys import sys
import re import re
import json import json
import lxml.etree
import requests import requests
@ -34,14 +35,27 @@ MONSTER_RE = re.compile(
'(?:</td>)?<td style="[^"]*background-color:#EBEBEB;[^"]*">\s*' '(?:</td>)?<td style="[^"]*background-color:#EBEBEB;[^"]*">\s*'
'<a href="([^"]*)" [^>]* title="([^"]*)"') '<a href="([^"]*)" [^>]* title="([^"]*)"')
# Old, MHX
"""
MONSTER_LINK_RE = re.compile( MONSTER_LINK_RE = re.compile(
'<a href="(/wiki/[^/"]*)"\s+class="image image-thumbnail link-internal"\s+' '<a href="(/wiki/[^/"]*)"\s+class="image image-thumbnail link-internal"\s+'
'title="([^"]*)"\s+>') 'title="([^"]*)"\s+>')
JAPANESE_NAME_STR = '<h3 class="pi-data-label pi-secondary-font">Japanese:</h3>' JAPANESE_NAME_STR = '<h3 class="pi-data-label pi-secondary-font">Japanese:</h3>'
JAPANESE_NAME_RE = re.compile( JAPANESE_NAME_RE = re.compile(
'<div class="pi-data-value pi-font">(.*)</div>') '<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): def parse_wikia_monsters(f):
section = None section = None
@ -72,20 +86,17 @@ def parse_wikia_monsters(f):
def get_jp_names(monster_path): def get_jp_names(monster_path):
url = "http://monsterhunter.wikia.com" + monster_path url = "http://monsterhunter.wikia.com" + monster_path
r = requests.get(url) r = requests.get(url)
lines = r.text.split("\n") root = lxml.etree.HTML(r.text)
names = [] names = []
while lines:
line = lines.pop(0).strip() rbs = root.xpath('//h2[@data-source="Japanese Name"]//rb')
if JAPANESE_NAME_STR not in line: names.append(rbs[0].text)
continue
line = lines.pop(0).strip() divs = root.xpath('//div[@data-source="Japanese Title"]//div')
while line == "": if divs:
line = lines.pop(0).strip() names.append(divs[0].text)
m = JAPANESE_NAME_RE.match(line)
assert m, "No match: " + line
names.append(parse_japanese_name(m.group(1)))
if len(names) == 2:
break
return names return names

Binary file not shown.

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

@ -187,7 +187,8 @@ class WeaponMonsterDamage(object):
critical_eye_skill=skills.CriticalEye.NONE, critical_eye_skill=skills.CriticalEye.NONE,
element_skill=skills.ElementAttackUp.NONE, element_skill=skills.ElementAttackUp.NONE,
awaken=False, artillery_level=0, limit_parts=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.weapon = weapon_row
self.monster = monster_row self.monster = monster_row
self.monster_damage = monster_damage self.monster_damage = monster_damage
@ -222,13 +223,21 @@ class WeaponMonsterDamage(object):
else: else:
self.true_raw = (self.weapon["attack"] self.true_raw = (self.weapon["attack"]
/ WeaponType.multiplier(self.weapon_type)) / WeaponType.multiplier(self.weapon_type))
if sharp_plus == 1:
self.sharpness = self.weapon.sharpness_plus.max if sharp_plus:
elif sharp_plus == 2: if game == "mhr":
self.sharpness = self.weapon.sharpness_plus2.max 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: else:
self.sharpness = self.weapon.sharpness.max self.sharpness, self.sharpness_points = self.weapon.sharpness.get_max_points()
#print "sharpness=", self.sharpness
self.sharpness_name = SharpnessLevel.name(self.sharpness)
if self.weapon["affinity"]: if self.weapon["affinity"]:
if (isinstance(self.weapon["affinity"], str) if (isinstance(self.weapon["affinity"], str)
and "/" in self.weapon["affinity"]): and "/" in self.weapon["affinity"]):
@ -403,10 +412,10 @@ class WeaponMonsterDamage(object):
artillery_level=self.artillery_level) artillery_level=self.artillery_level)
self.cb_phial_damage[part][level] = damage_tuple self.cb_phial_damage[part][level] = damage_tuple
def uniform(self): def uniform(self, break_weight=0.25):
average = 0.0 average = 0.0
for part, damage in self.damage_map.items(): for part, damage in self.damage_map.items():
average += damage.average() average += damage.average(break_weight)
return average / len(self.damage_map) return average / len(self.damage_map)
def weighted_raw(self): def weighted_raw(self):
@ -554,7 +563,12 @@ class WeaponMonsterDamage(object):
part = row["body_part"] part = row["body_part"]
hitbox = int(row[raw_type]) hitbox = int(row[raw_type])
if self.etype: if self.etype:
ehitbox = int(row[str(self.etype.lower())]) try:
ehitbox = int(row[str(self.etype.lower())])
except IndexError:
ehitbox = 0
print("WARN: bad etype {}, row = {}".format(
self.etype, row))
else: else:
ehitbox = 0 ehitbox = 0
@ -618,6 +632,20 @@ class PartDamage(object):
def ehitbox(self): def ehitbox(self):
return self.states[None].ehitbox 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 @property
def break_raw(self): def break_raw(self):
if "Break Part" in self.states: if "Break Part" in self.states:
@ -668,7 +696,8 @@ class PartDamage(object):
# If the part has a hitbox with different damage in the break # If the part has a hitbox with different damage in the break
# rows from the db, or if it's explicitly marked as breakable # rows from the db, or if it's explicitly marked as breakable
# (done by checking hunt rewards for breaks). # (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): def average(self, break_weight=0.25, rage_weight=0.5):
if self.break_diff(): if self.break_diff():
@ -688,7 +717,8 @@ class PartDamage(object):
+ self.total * (1 - rage_weight)) + self.total * (1 - rage_weight))
def set_damage(self, raw, element, hitbox, ehitbox, state=None): 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" state = "Break Part"
self.states[state] = PartDamageState(raw, element, self.states[state] = PartDamageState(raw, element,
hitbox, ehitbox, state) hitbox, ehitbox, state)

@ -7,6 +7,7 @@ import sqlite3
import json import json
from mhapi import model from mhapi import model
from mhapi.util import WEAPON_TYPES
def field_model(key): def field_model(key):
@ -257,10 +258,15 @@ class MHDB(object):
return self._query_all("search_item", query, tuple(args), return self._query_all("search_item", query, tuple(args),
no_cache=True, model_cls=model.Item) no_cache=True, model_cls=model.Item)
def get_monsters(self): def get_monsters(self, monster_class=None):
return self._query_all("monsters", """ args = []
SELECT * FROM monsters where = []
""", model_cls=model.Monster) 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): def get_monster_names(self):
""" """
@ -317,12 +323,17 @@ class MHDB(object):
WHERE quest_id=? WHERE quest_id=?
""", (quest_id,)) """, (quest_id,))
def get_monster_quests(self, monster_id, rank): def get_monster_quests(self, monster_id, rank=None):
return self._query_all("monster_quests", """
SELECT DISTINCT quests.* FROM quests, monster_to_quest query = """SELECT DISTINCT quests.* FROM quests, monster_to_quest
WHERE monster_to_quest.quest_id = quests._id WHERE monster_to_quest.quest_id = quests._id
AND monster_to_quest.monster_id=? AND rank=? AND monster_to_quest.monster_id=?"""
""", (monster_id, rank), model_cls=model.Quest) 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): def get_item_quests(self, item_id):
""" """
@ -381,6 +392,14 @@ class MHDB(object):
WHERE monster_id=? WHERE monster_id=?
""", (monster_id,), collection_cls=model.MonsterDamage) """, (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): def get_weapons(self):
# Note: weapons only available via JP DLC have no localized # Note: weapons only available via JP DLC have no localized
# name, filter them out. # name, filter them out.
@ -613,6 +632,13 @@ class MHDB(object):
item_data.set_components(ccomps, ucomps) 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): class MHDBX(object):
""" """
@ -626,12 +652,14 @@ class MHDBX(object):
""" """
Loads JSON data, keeps in memory. Loads JSON data, keeps in memory.
""" """
self.game = game
module_path = os.path.dirname(__file__) module_path = os.path.dirname(__file__)
self._mhx_db_path = os.path.abspath(os.path.join(module_path, "..", self._mhx_db_path = os.path.abspath(os.path.join(module_path, "..",
"db", game)) "db", game))
self._4udb = MHDB() self._4udb = MHDB(game="gu")
self._weapon_list = [] self._weapon_list = []
self._weapons_by_name = {} self._weapons_by_name = {}
self._weapons_by_id = {}
self._monsters_by_name = {} self._monsters_by_name = {}
self._monster_damage = {} self._monster_damage = {}
@ -644,10 +672,23 @@ class MHDBX(object):
with open(os.path.join(self._mhx_db_path, "weapon_list.json")) as f: with open(os.path.join(self._mhx_db_path, "weapon_list.json")) as f:
wlist = json.load(f) wlist = json.load(f)
for i, wdata in enumerate(wlist): 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) weapon = model.Weapon(wdata)
self._weapon_list.append(weapon) self._weapon_list.append(weapon)
self._weapons_by_name[weapon.name_jp] = weapon self._weapons_by_name[weapon.name_jp] = weapon
self._weapons_by_id[weapon.id] = weapon
def _load_monsters(self): def _load_monsters(self):
names_path = os.path.join(self._mhx_db_path, names_path = os.path.join(self._mhx_db_path,
@ -675,7 +716,26 @@ class MHDBX(object):
row["monster_id"] = mid row["monster_id"] = mid
damage_rows.append(row) damage_rows.append(row)
self._monster_damage[mid] = model.MonsterDamage(damage_rows) 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): def get_weapon_by_name(self, name):
return self._weapons_by_name.get(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 with no element. Otherwise @element is searched for in both
awaken and native, and can be a status or an element. 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 = [] results = []
for w in self._weapon_list: for w in self._weapon_list:
if wtype is not None and w.wtype != wtype: if wtype is not None and w.wtype != wtype:

@ -1,8 +1,11 @@
import os
import string import string
import json import json
from typing import NamedTuple
import urllib.request, urllib.parse, urllib.error import urllib.request, urllib.parse, urllib.error
import re import re
import difflib import difflib
from collections import namedtuple
from mhapi.util import EnumBase from mhapi.util import EnumBase
@ -162,7 +165,7 @@ class SharpnessLevel(EnumBase):
PURPLE: (1.44, 1.20), PURPLE: (1.44, 1.20),
} }
# for mhx, mhgen, mhxx, and likely mhw # for mhx, mhgen, mhxx
_modifier_mhx = { _modifier_mhx = {
RED: (0.50, 0.25), RED: (0.50, 0.25),
ORANGE: (0.75, 0.50), ORANGE: (0.75, 0.50),
@ -172,14 +175,48 @@ class SharpnessLevel(EnumBase):
WHITE: (1.32, 1.125), 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 @classmethod
def raw_modifier(cls, sharpness): 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 @classmethod
def element_modifier(cls, sharpness): 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): class WeaponSharpness(ModelBase):
@ -195,32 +232,54 @@ class WeaponSharpness(ModelBase):
self.value_list = [int(s) for s in db_string_or_list.split(".")] 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 # For MHX, Gen, no purple sharpness, but keep model the same for
# simplicity # simplicity
if len(self.value_list) < SharpnessLevel.PURPLE + 1: while len(self.value_list) < SharpnessLevel.PURPLE + 1:
self.value_list.append(0) self.value_list.append(0)
self._max = None self._max = None
@property @property
def max(self): def max(self):
if self._max is None: if self._max is None:
self._max = SharpnessLevel.RED for i in range(SharpnessLevel.PURPLE, -1, -1):
for i in range(SharpnessLevel.PURPLE+1): if self.value_list[i] > 0:
if self.value_list[i] == 0:
break
else:
self._max = i self._max = i
break
return self._max 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): def as_data(self):
return self.value_list return self.value_list
def __str__(self):
return ",".join(str(v) for v in self.value_list)
class ItemCraftable(RowModel): class ItemCraftable(RowModel):
_list_fields = ["id", "name"] _list_fields = ["id", "name"]
def __init__(self, item_row): def __init__(self, item_row):
super(ItemCraftable, self).__init__(item_row) super(ItemCraftable, self).__init__(item_row)
self.create_components = None if "create_components" not in item_row:
self.upgrade_components = None self.create_components = None
if "upgrade_components" not in item_row:
self.upgrade_components = None
def set_components(self, create_components, upgrade_components): def set_components(self, create_components, upgrade_components):
self.create_components = create_components self.create_components = create_components
@ -230,10 +289,10 @@ class ItemCraftable(RowModel):
data = super(ItemCraftable, self).as_data() data = super(ItemCraftable, self).as_data()
if self.create_components is not None: if self.create_components is not None:
data["create_components"] = dict((item.name, item.quantity) 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: if self.upgrade_components is not None:
data["upgrade_components"] = dict((item.name, item.quantity) data["upgrade_components"] = dict((item.name, item.quantity)
for item in self.upgrade_components) for item in _item_list(self.upgrade_components))
return data return data
@ -373,6 +432,8 @@ class Weapon(ItemCraftable):
def __init__(self, weapon_item_row): def __init__(self, weapon_item_row):
super(Weapon, self).__init__(weapon_item_row) super(Weapon, self).__init__(weapon_item_row)
self._parse_sharpness() self._parse_sharpness()
if "name_jp" not in self._data:
self._data["name_jp"] = self._data["name"]
def _parse_sharpness(self): def _parse_sharpness(self):
""" """
@ -385,11 +446,12 @@ class Weapon(ItemCraftable):
if isinstance(self._row["sharpness"], list): if isinstance(self._row["sharpness"], list):
# MHX JSON data, already desired format, but doesn't have # MHX JSON data, already desired format, but doesn't have
# purple so we append 0 # purple so we append 0
self.sharpness = WeaponSharpness(self._row["sharpness"] + [0]) row_sharpness = self._row["sharpness"]
self.sharpness_plus = WeaponSharpness( row_sharpness_plus = self._row.get("sharpness_plus", row_sharpness)
self._row["sharpness_plus"] + [0]) row_sharpness_plus2 = self._row.get("sharpness_plus2", row_sharpness)
self.sharpness_plus2 = WeaponSharpness( self.sharpness = WeaponSharpness(row_sharpness)
self._row["sharpness_plus2"] + [0]) self.sharpness_plus = WeaponSharpness(row_sharpness_plus)
self.sharpness_plus2 = WeaponSharpness(row_sharpness_plus2)
else: else:
# 4U or gen data from db # 4U or gen data from db
parts = self._row["sharpness"].split(" ") parts = self._row["sharpness"].split(" ")
@ -410,6 +472,10 @@ class Weapon(ItemCraftable):
# english weapons, and not for Japanese DLC weapons. # english weapons, and not for Japanese DLC weapons.
return ord(self.name[0]) < 128 return ord(self.name[0]) < 128
@property
def sharpness_name(self):
return SharpnessLevel.name(self.sharpness)
class Monster(RowModel): class Monster(RowModel):
_list_fields = ["id", "class", "name"] _list_fields = ["id", "class", "name"]
@ -459,6 +525,12 @@ class MonsterPartStateDamage(RowModel):
def __ne__(self, other): def __ne__(self, other):
return not self.__eq__(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): class MonsterPartDamage(ModelBase):
""" """
@ -493,6 +565,49 @@ class MonsterPartDamage(ModelBase):
damage=self.states 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): class MonsterDamage(ModelBase):
""" """
@ -518,6 +633,10 @@ class MonsterDamage(ModelBase):
self.parts[part] = MonsterPartDamage(part) self.parts[part] = MonsterPartDamage(part)
self.parts[part].add_state(state, row) 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): def as_data(self):
return dict( return dict(
states=list(self.states), states=list(self.states),
@ -534,6 +653,49 @@ class MonsterDamage(ModelBase):
#print "part %s is breakable [by rewards]" % name #print "part %s is breakable [by rewards]" % name
part_damage.breakable = True 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): def get_decoration_values(skill_id, decorations):
""" """
@ -622,7 +784,7 @@ def get_costs(db, weapon):
for cost in costs: for cost in costs:
cost["zenny"] += upgrade_cost cost["zenny"] += upgrade_cost
cost["path"] += [weapon] cost["path"] += [weapon]
for item in weapon.upgrade_components: for item in _item_list(weapon.upgrade_components):
if item.type == "Weapon": if item.type == "Weapon":
continue continue
if item.name not in cost["components"]: if item.name not in cost["components"]:
@ -638,7 +800,7 @@ def get_costs(db, weapon):
create_cost = dict(zenny=zenny, create_cost = dict(zenny=zenny,
path=[weapon], path=[weapon],
components={}) components={})
for item in weapon.create_components: for item in _item_list(weapon.create_components):
create_cost["components"][item.name] = item.quantity create_cost["components"][item.name] = item.quantity
costs = [create_cost] + costs costs = [create_cost] + costs
if weapon.buy: if weapon.buy:
@ -649,16 +811,48 @@ def get_costs(db, weapon):
return 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): class ItemStars(object):
""" """
Get the game progress (in hub stars) required to make an item. Caches Get the game progress (in hub stars) required to make an item. Caches
values. values.
""" """
def __init__(self, db): def __init__(self, db):
self.db = db self.db = db
self._item_stars = {} # item id -> stars dict self._item_stars = {} # item id -> stars dict
self._weapon_stars = {} # weapon id -> stars dict self._weapon_stars = {} # weapon id -> stars dict
self._monster_stars = {} # monster id -> stars dict
self._wyporium_trades = {} self._wyporium_trades = {}
if self.db.game == "4u": if self.db.game == "4u":
@ -804,7 +998,26 @@ class ItemStars(object):
else: else:
print("Error: unknown hub", quest.hub) print("Error: unknown hub", quest.hub)
# if available guild or village, then null out permit/arena values, 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 # because they are more useful for filtering if limited to items
# exclusively available from permit or arena. Allows matching # exclusively available from permit or arena. Allows matching
# on based on meeting specified critera for # on based on meeting specified critera for
@ -816,3 +1029,21 @@ class ItemStars(object):
self._item_stars[item_id] = stars self._item_stars[item_id] = stars
return 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

@ -53,6 +53,18 @@ WTYPE_ABBR = dict(
) )
DAMAGE_TYPES = """
cut
impact
shot
fire
water
thunder
ice
dragon
""".split()
class EnumBase(object): class EnumBase(object):
_names = dict() _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()

@ -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) { 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", $.getJSON(DATA_PATH + "items.json",
function(data) { function(data) {
$(selector).autocomplete({ source: 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) { function load_weapon_data(ready_fn) {
if (typeof DATA_PATH == "undefined") { if (typeof DATA_PATH == "undefined") {
DATA_PATH = get_base_path() + "/jsonapi/"; DATA_PATH = get_base_path() + "/jsonapi/";

@ -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> title="<%= parent_name %>">(parent)</a>
<% } %> <% } %>
</td> </td>
<% if (village_stars) { %> <% if (village_stars) { %>
<td> <td>
Village <%= village_stars %> Village <%= village_stars %>
</td> </td>
<% } %> <% } %>
<% if (guild_stars) { %> <% if (guild_stars) { %>
<td> <td>
Guild <%= guild_stars %> Guild <%= guild_stars %>
</td> </td>
<% } %> <% } %>
<% if (rarity) { %>
<td>
Rarity <%= rarity %>
</td>
<% } %>
</tr> </tr>
</table> </table>
<% if (children.length) { %> <% if (children.length) { %>

Loading…
Cancel
Save