|
|
|
@ -0,0 +1,252 @@
|
|
|
|
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Note: requires python-Levenshtein, available as package in debian and
|
|
|
|
|
|
|
|
# ubuntu
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import os.path
|
|
|
|
|
|
|
|
import codecs
|
|
|
|
|
|
|
|
from collections import namedtuple
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from Levenshtein import distance
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import _pathfix
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from mhapi.db import MHDB, Quest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QuestMonster = namedtuple("QuestMonster", "id name")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ALL_NAMES = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_utf8_writer(writer):
|
|
|
|
|
|
|
|
return codecs.getwriter("utf8")(writer)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_unstable(db, quest_id, monster_id, value):
|
|
|
|
|
|
|
|
if value not in ("yes", "no"):
|
|
|
|
|
|
|
|
raise ValueError("unstable must be 'yes' or 'no'")
|
|
|
|
|
|
|
|
cur = db.cursor()
|
|
|
|
|
|
|
|
cur.execute("""
|
|
|
|
|
|
|
|
UPDATE monster_to_quest
|
|
|
|
|
|
|
|
SET unstable=?
|
|
|
|
|
|
|
|
WHERE quest_id=? AND monster_id=?
|
|
|
|
|
|
|
|
""", (value, quest_id, monster_id))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_quests(db):
|
|
|
|
|
|
|
|
quests = db.get_quests()
|
|
|
|
|
|
|
|
for quest_row in quests:
|
|
|
|
|
|
|
|
quest = Quest(quest_row)
|
|
|
|
|
|
|
|
if not quest.name:
|
|
|
|
|
|
|
|
assert quest.hub == "Event"
|
|
|
|
|
|
|
|
#print "WARN: skipping non localized event quest: %d" \
|
|
|
|
|
|
|
|
# % quest_row["_id"]
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
if quest.goal.startswith("Deliver "):
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
if quest.goal.startswith("Survive until "):
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
if quest.goal.startswith("Return on "):
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
check_hunts(db, quest)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def lstrip(s, prefix):
|
|
|
|
|
|
|
|
if s.startswith(prefix):
|
|
|
|
|
|
|
|
return s[len(prefix):]
|
|
|
|
|
|
|
|
return s
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def rstrip(s, postfix):
|
|
|
|
|
|
|
|
if s.endswith(postfix):
|
|
|
|
|
|
|
|
return s[:-len(postfix)]
|
|
|
|
|
|
|
|
return s
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_monster(name):
|
|
|
|
|
|
|
|
name = name.strip()
|
|
|
|
|
|
|
|
#print name,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
name = lstrip(name, "and ")
|
|
|
|
|
|
|
|
name = lstrip(name, "a ")
|
|
|
|
|
|
|
|
name = lstrip(name, "an ")
|
|
|
|
|
|
|
|
while name[0].isdigit():
|
|
|
|
|
|
|
|
name = name[1:]
|
|
|
|
|
|
|
|
name = name.strip()
|
|
|
|
|
|
|
|
name = lstrip(name, "Frenzied ")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
name = rstrip(name, " Frenzy")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
name = rstrip(name, " or repel it")
|
|
|
|
|
|
|
|
name = rstrip(name, " before time expires or deliver a Paw Pass Ticket")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Break/Wound subquests
|
|
|
|
|
|
|
|
name = rstrip(name, " head")
|
|
|
|
|
|
|
|
name = rstrip(name, " back")
|
|
|
|
|
|
|
|
name = rstrip(name, " chest")
|
|
|
|
|
|
|
|
name = rstrip(name, " tail")
|
|
|
|
|
|
|
|
name = rstrip(name, " leg claw")
|
|
|
|
|
|
|
|
name = rstrip(name, " claw")
|
|
|
|
|
|
|
|
name = rstrip(name, " front leg")
|
|
|
|
|
|
|
|
name = rstrip(name, " wingtalon")
|
|
|
|
|
|
|
|
name = rstrip(name, " right wingarm")
|
|
|
|
|
|
|
|
name = rstrip(name, " left wingarm")
|
|
|
|
|
|
|
|
name = rstrip(name, " wingarm")
|
|
|
|
|
|
|
|
name = rstrip(name, " wing arm")
|
|
|
|
|
|
|
|
name = rstrip(name, " horn")
|
|
|
|
|
|
|
|
name = rstrip(name, " chin")
|
|
|
|
|
|
|
|
name = rstrip(name, " Jaw")
|
|
|
|
|
|
|
|
name = rstrip(name, " jaw")
|
|
|
|
|
|
|
|
name = rstrip(name, " wing")
|
|
|
|
|
|
|
|
name = rstrip(name, " Wing")
|
|
|
|
|
|
|
|
name = rstrip(name, " wings")
|
|
|
|
|
|
|
|
name = rstrip(name, " horn")
|
|
|
|
|
|
|
|
name = rstrip(name, " horns")
|
|
|
|
|
|
|
|
name = rstrip(name, " outer hide")
|
|
|
|
|
|
|
|
name = rstrip(name, " hide")
|
|
|
|
|
|
|
|
name = rstrip(name, " crest")
|
|
|
|
|
|
|
|
name = rstrip(name, " poison spikes")
|
|
|
|
|
|
|
|
name = rstrip(name, " dorsal fin")
|
|
|
|
|
|
|
|
name = rstrip(name, " top fin")
|
|
|
|
|
|
|
|
name = rstrip(name, " fin")
|
|
|
|
|
|
|
|
name = rstrip(name, " comb")
|
|
|
|
|
|
|
|
name = rstrip(name, " body")
|
|
|
|
|
|
|
|
name = rstrip(name, " feelers")
|
|
|
|
|
|
|
|
name = rstrip(name, " blowhole")
|
|
|
|
|
|
|
|
name = rstrip(name, " ear")
|
|
|
|
|
|
|
|
name = rstrip(name, " ears")
|
|
|
|
|
|
|
|
name = rstrip(name, " hind leg")
|
|
|
|
|
|
|
|
name = rstrip(name, " shell")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
name = lstrip(name, "the ")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
name = rstrip(name, "'s")
|
|
|
|
|
|
|
|
name = rstrip(name, "'")
|
|
|
|
|
|
|
|
name = rstrip(name, u"’")
|
|
|
|
|
|
|
|
name = rstrip(name, u"’s")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#print "=>", name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_goal_monster_names(goal):
|
|
|
|
|
|
|
|
if goal == "None":
|
|
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
if goal.startswith("Deliver ") or goal.startswith("Topple "):
|
|
|
|
|
|
|
|
# TODO: subquest, could parse the item and look up which monster
|
|
|
|
|
|
|
|
# it's from
|
|
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
if goal == "Supress its Frenzy (2x)":
|
|
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
goal = lstrip(goal, "Hunt ")
|
|
|
|
|
|
|
|
# type in 253
|
|
|
|
|
|
|
|
goal = lstrip(goal, "Hunta ")
|
|
|
|
|
|
|
|
goal = lstrip(goal, "Slay ")
|
|
|
|
|
|
|
|
goal = lstrip(goal, "Capture ")
|
|
|
|
|
|
|
|
goal = lstrip(goal, "Repel ")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# sub quests
|
|
|
|
|
|
|
|
goal = lstrip(goal, "Wound ")
|
|
|
|
|
|
|
|
goal = lstrip(goal, "Sever ")
|
|
|
|
|
|
|
|
# typo in 71
|
|
|
|
|
|
|
|
goal = lstrip(goal, "Severthe ")
|
|
|
|
|
|
|
|
goal = lstrip(goal, "Break ")
|
|
|
|
|
|
|
|
goal = lstrip(goal, "Suppress ")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ", and" in goal:
|
|
|
|
|
|
|
|
parts = goal.split(",")
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
parts = goal.split(" and ")
|
|
|
|
|
|
|
|
return [_parse_monster(p) for p in parts]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_goal_monsters(db, goal):
|
|
|
|
|
|
|
|
names = parse_goal_monster_names(goal)
|
|
|
|
|
|
|
|
#print quest.goal, names
|
|
|
|
|
|
|
|
monsters = []
|
|
|
|
|
|
|
|
for name in names:
|
|
|
|
|
|
|
|
if name == "all large monsters":
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
m = db.get_monster_by_name(name)
|
|
|
|
|
|
|
|
if m is None and name.endswith("s"):
|
|
|
|
|
|
|
|
name2 = name.rstrip("s")
|
|
|
|
|
|
|
|
m = db.get_monster_by_name(name2)
|
|
|
|
|
|
|
|
if m is not None:
|
|
|
|
|
|
|
|
name = name2
|
|
|
|
|
|
|
|
if m is None:
|
|
|
|
|
|
|
|
name2 = fuzzy_find(name)
|
|
|
|
|
|
|
|
m = db.get_monster_by_name(name2)
|
|
|
|
|
|
|
|
if m is not None:
|
|
|
|
|
|
|
|
print "Fuzzy match: %s => %s" % (name, name2)
|
|
|
|
|
|
|
|
name = name2
|
|
|
|
|
|
|
|
if m is None:
|
|
|
|
|
|
|
|
print "ERROR: can't find monster '%s'" % name
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
monsters.append(QuestMonster(m["_id"], name))
|
|
|
|
|
|
|
|
return monsters
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fuzzy_find(name, max_distance=3):
|
|
|
|
|
|
|
|
best = (None, 10000)
|
|
|
|
|
|
|
|
for n in ALL_NAMES:
|
|
|
|
|
|
|
|
d = distance(name, n)
|
|
|
|
|
|
|
|
if d < best[1]:
|
|
|
|
|
|
|
|
best = (n, d)
|
|
|
|
|
|
|
|
if best[1] <= max_distance:
|
|
|
|
|
|
|
|
return best[0]
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_hunts(db, quest):
|
|
|
|
|
|
|
|
all_names = db.get_monster_names()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
db_expected = set()
|
|
|
|
|
|
|
|
db_expected_unstable = set()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
goal_expected = set(get_goal_monsters(db, quest.goal))
|
|
|
|
|
|
|
|
sub_expected = set(get_goal_monsters(db, quest.sub_goal))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
monsters = db.get_quest_monsters(quest.id)
|
|
|
|
|
|
|
|
for m in monsters:
|
|
|
|
|
|
|
|
monster = db.get_monster(m["monster_id"])
|
|
|
|
|
|
|
|
qm = QuestMonster(monster["_id"], monster["name"])
|
|
|
|
|
|
|
|
if m["unstable"] == "yes":
|
|
|
|
|
|
|
|
db_expected_unstable.add(qm)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
db_expected.add(qm)
|
|
|
|
|
|
|
|
if goal_expected != db_expected:
|
|
|
|
|
|
|
|
missing = goal_expected - db_expected
|
|
|
|
|
|
|
|
skip = False
|
|
|
|
|
|
|
|
if (len(goal_expected) == 1 and len(db_expected) == 1):
|
|
|
|
|
|
|
|
# handle subspecious and Apex - e.g. when the goal lists the
|
|
|
|
|
|
|
|
# bare name, but in the db it's listed as Apex NAME, assume
|
|
|
|
|
|
|
|
# the db data is correct. When this happens, the
|
|
|
|
|
|
|
|
# susbspecious / apex id is 1 greater than the normal
|
|
|
|
|
|
|
|
# monster id.
|
|
|
|
|
|
|
|
goal = next(iter(goal_expected))
|
|
|
|
|
|
|
|
db = next(iter(db_expected))
|
|
|
|
|
|
|
|
if goal[0] == db[0] - 1 and db[1].endswith(goal[1]):
|
|
|
|
|
|
|
|
skip = True
|
|
|
|
|
|
|
|
if not skip:
|
|
|
|
|
|
|
|
print ">", quest.id, quest.name
|
|
|
|
|
|
|
|
print " goal:", quest.goal
|
|
|
|
|
|
|
|
print " sub:", quest.sub_goal
|
|
|
|
|
|
|
|
print " parsed:", goal_expected
|
|
|
|
|
|
|
|
if sub_expected and not sub_expected < goal_expected:
|
|
|
|
|
|
|
|
print " sub prsd:", sub_expected
|
|
|
|
|
|
|
|
print " db:", db_expected
|
|
|
|
|
|
|
|
print " db unstb:", db_expected_unstable
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
|
|
from _pathfix import db_path
|
|
|
|
|
|
|
|
db_file = os.path.join(db_path, "mh4u.db")
|
|
|
|
|
|
|
|
db = MHDB(db_file)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ALL_NAMES = [row["name"] for row in db.get_monster_names()]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
sys.stdout = get_utf8_writer(sys.stdout)
|
|
|
|
|
|
|
|
sys.stderr = get_utf8_writer(sys.stderr)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
check_quests(db)
|