You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mhapi/db/check_quest_monsters.py

253 lines
7.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/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)