use db to calculate best quest for item
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.pyc
|
||||||
122
mhdb.py
Executable file
122
mhdb.py
Executable file
@@ -0,0 +1,122 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
|
||||||
|
class Quest(object):
|
||||||
|
def __init__(self, quest_row, quest_rewards):
|
||||||
|
self._row = quest_row
|
||||||
|
self._rewards = quest_rewards
|
||||||
|
|
||||||
|
self.id = quest_row["_id"]
|
||||||
|
self.name = quest_row["name"]
|
||||||
|
self.stars = quest_row["stars"]
|
||||||
|
self.hub = quest_row["hub"]
|
||||||
|
self.goal = quest_row["goal"]
|
||||||
|
self.rank = get_rank(self.hub, self.stars)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s (%s %s* %s)" % (self.name, self.hub, self.stars, self.rank)
|
||||||
|
|
||||||
|
|
||||||
|
def get_rank(hub, stars):
|
||||||
|
if hub == "Caravan":
|
||||||
|
if stars < 6:
|
||||||
|
return "LR"
|
||||||
|
elif stars < 10:
|
||||||
|
return "HR"
|
||||||
|
return "G"
|
||||||
|
if hub == "Guild":
|
||||||
|
if stars < 4:
|
||||||
|
return "LR"
|
||||||
|
elif stars < 8:
|
||||||
|
return "HR"
|
||||||
|
return "G"
|
||||||
|
|
||||||
|
|
||||||
|
class MHDB(object):
|
||||||
|
def __init__(self, path):
|
||||||
|
self.conn = sqlite3.connect(path)
|
||||||
|
self.conn.row_factory = sqlite3.Row
|
||||||
|
self.cache = {}
|
||||||
|
|
||||||
|
def _get_memoized(self, key, query, *args):
|
||||||
|
if key in self.cache:
|
||||||
|
v = self.cache[key].get(args)
|
||||||
|
if v is not None:
|
||||||
|
return v
|
||||||
|
else:
|
||||||
|
self.cache[key] = {}
|
||||||
|
cursor = self.conn.execute(query, args)
|
||||||
|
v = self.cache[key][args] = cursor.fetchall()
|
||||||
|
return v
|
||||||
|
|
||||||
|
def get_item(self, item_id):
|
||||||
|
v = self._get_memoized("item", """
|
||||||
|
SELECT * FROM items
|
||||||
|
WHERE _id=?
|
||||||
|
""", item_id)
|
||||||
|
return v[0]
|
||||||
|
|
||||||
|
def get_item_by_name(self, name):
|
||||||
|
v = self._get_memoized("item", """
|
||||||
|
SELECT * FROM items
|
||||||
|
WHERE name=?
|
||||||
|
""", name)
|
||||||
|
return v[0]
|
||||||
|
|
||||||
|
def get_monster_by_name(self, name):
|
||||||
|
v = self._get_memoized("monster", """
|
||||||
|
SELECT * FROM monsters
|
||||||
|
WHERE name=?
|
||||||
|
""", name)
|
||||||
|
return v[0]
|
||||||
|
|
||||||
|
def get_monster(self, monster_id):
|
||||||
|
v = self._get_memoized("monster", """
|
||||||
|
SELECT * FROM monsters
|
||||||
|
WHERE _id=?
|
||||||
|
""", monster_id)
|
||||||
|
return v[0]
|
||||||
|
|
||||||
|
def get_quest(self, quest_id):
|
||||||
|
v = self._get_memoized("quest", """
|
||||||
|
SELECT * FROM quests
|
||||||
|
WHERE _id=?
|
||||||
|
""", quest_id)
|
||||||
|
return v[0]
|
||||||
|
|
||||||
|
def get_quest_rewards(self, quest_id):
|
||||||
|
v = self._get_memoized("quest_rewards", """
|
||||||
|
SELECT * FROM quest_rewards
|
||||||
|
WHERE quest_id=?
|
||||||
|
""", quest_id)
|
||||||
|
return v
|
||||||
|
|
||||||
|
def get_monster_rewards(self, monster_id, rank):
|
||||||
|
v = self._get_memoized("monster_rewards", """
|
||||||
|
SELECT * FROM hunting_rewards
|
||||||
|
WHERE monster_id=? AND rank=?
|
||||||
|
""", monster_id, rank)
|
||||||
|
return v
|
||||||
|
|
||||||
|
def get_quest_monsters(self, quest_id):
|
||||||
|
v = self._get_memoized("quest_monsters", """
|
||||||
|
SELECT monster_id FROM monster_to_quest
|
||||||
|
WHERE quest_id=? AND unstable='no'
|
||||||
|
""", quest_id)
|
||||||
|
return v
|
||||||
|
|
||||||
|
def get_item_quests(self, item_id):
|
||||||
|
cursor = self.conn.execute("""
|
||||||
|
SELECT DISTINCT quest_id FROM quest_rewards
|
||||||
|
WHERE item_id=?
|
||||||
|
""", (item_id,))
|
||||||
|
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
quests = []
|
||||||
|
for r in rows:
|
||||||
|
quest_row = self.get_quest(r["quest_id"])
|
||||||
|
rewards_rows = self.get_quest_rewards(r["quest_id"])
|
||||||
|
quests.append(Quest(quest_row, rewards_rows))
|
||||||
|
|
||||||
|
return quests
|
||||||
120
mhprob.py
120
mhprob.py
@@ -43,6 +43,27 @@
|
|||||||
# B: ./mhprop.py 5 1 1 69
|
# B: ./mhprop.py 5 1 1 69
|
||||||
# For great luck, replace 69 with 90
|
# For great luck, replace 69 with 90
|
||||||
|
|
||||||
|
CAP_SKILL_NONE = 0
|
||||||
|
CAP_SKILL_EXPERT = 1
|
||||||
|
CAP_SKILL_MASTER = 2
|
||||||
|
CAP_SKILL_GOD = 3
|
||||||
|
|
||||||
|
LUCK_SKILL_NONE = 0
|
||||||
|
LUCK_SKILL_GOOD = 1
|
||||||
|
LUCK_SKILL_GREAT = 2
|
||||||
|
|
||||||
|
QUEST_A = "A"
|
||||||
|
QUEST_B = "B"
|
||||||
|
QUEST_SUB = "Sub"
|
||||||
|
|
||||||
|
CARVING_SKILL_NONE = 0
|
||||||
|
CARVING_SKILL_PRO = 1
|
||||||
|
CARVING_SKILL_FELYNE_LOW = 2
|
||||||
|
CARVING_SKILL_FELYNE_HI = 3
|
||||||
|
CARVING_SKILL_CELEBRITY = 4
|
||||||
|
CARVING_SKILL_GOD = 5
|
||||||
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def _quest_reward_p(reward_percent, reward_count):
|
def _quest_reward_p(reward_percent, reward_count):
|
||||||
@@ -84,6 +105,105 @@ def quest_reward_p(reward_percent, min_rewards, max_rewards, extend_percent=69):
|
|||||||
return p * 100
|
return p * 100
|
||||||
|
|
||||||
|
|
||||||
|
def reward_expected_c(min_rewards, max_rewards, extend_percent):
|
||||||
|
total_p = 0.0
|
||||||
|
expected_attempts = 0.0
|
||||||
|
for reward_count in xrange(min_rewards, max_rewards + 1):
|
||||||
|
p = _reward_count_p(reward_count, min_rewards, max_rewards,
|
||||||
|
extend_percent)
|
||||||
|
expected_attempts += p * reward_count
|
||||||
|
#print "P(C = %d) = %0.4f" % (reward_count, p)
|
||||||
|
total_p += p
|
||||||
|
assert abs(total_p - 1.0) < .00001, "total = %f" % total_p
|
||||||
|
return expected_attempts
|
||||||
|
|
||||||
|
|
||||||
|
def quest_reward_expected_c(line=QUEST_A, luck_skill=LUCK_SKILL_NONE):
|
||||||
|
"""
|
||||||
|
Expected number of rewards from specified quest line with given skills.
|
||||||
|
|
||||||
|
Note: if the quest has fixed rewards that aren't the desired item, it will
|
||||||
|
reduce the expected count for the desired item. Just subtract the number
|
||||||
|
of fixed items from the output to get the actual value.
|
||||||
|
"""
|
||||||
|
if luck_skill == LUCK_SKILL_NONE:
|
||||||
|
extend_p = 69
|
||||||
|
elif luck_skill == LUCK_SKILL_GOOD:
|
||||||
|
extend_p = 81
|
||||||
|
elif luck_skill == LUCK_SKILL_GREAT:
|
||||||
|
extend_p = 90
|
||||||
|
else:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
if line == QUEST_A:
|
||||||
|
min_c = 3
|
||||||
|
max_c = 8
|
||||||
|
elif line == QUEST_B:
|
||||||
|
min_c = 1
|
||||||
|
max_c = 8
|
||||||
|
elif line == QUEST_SUB:
|
||||||
|
min_c = 1
|
||||||
|
max_c = 4
|
||||||
|
else:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
return reward_expected_c(min_c, max_c, extend_p)
|
||||||
|
|
||||||
|
|
||||||
|
def capture_reward_expected_c(cap_skill=CAP_SKILL_NONE):
|
||||||
|
"""
|
||||||
|
Expected value for number of capture rewards given the specified
|
||||||
|
capture skill (none by default).
|
||||||
|
"""
|
||||||
|
if cap_skill == CAP_SKILL_NONE:
|
||||||
|
min_c = 2
|
||||||
|
max_c = 3
|
||||||
|
extend_p = 69
|
||||||
|
elif cap_skill == CAP_SKILL_EXPERT:
|
||||||
|
return 3
|
||||||
|
elif cap_skill == CAP_SKILL_MASTER:
|
||||||
|
min_c = 3
|
||||||
|
max_c = 4
|
||||||
|
extend_p = 69
|
||||||
|
elif cap_skill == CAP_SKILL_GOD:
|
||||||
|
return 4
|
||||||
|
else:
|
||||||
|
raise ValueError()
|
||||||
|
return reward_expected_c(min_c, max_c, extend_p)
|
||||||
|
|
||||||
|
|
||||||
|
def carve_delta_expected_c(carve_skill):
|
||||||
|
"""
|
||||||
|
Expected value for the number of extra carves with the given skill.
|
||||||
|
|
||||||
|
Word on the street is that since Tri the felyne skills do not stack with
|
||||||
|
the armor skills, i.e. if you have Carving Celebrity plus you get at
|
||||||
|
least one extra carves and the felyne skills do nothing.
|
||||||
|
"""
|
||||||
|
if carve_skill == CARVING_SKILL_CELEBRITY:
|
||||||
|
# Description: Increases the number of carving chances by one and prevents knockbacks while carving.
|
||||||
|
return 1
|
||||||
|
elif carve_skill == CARVING_SKILL_GOD:
|
||||||
|
# Description: Increases the number of carving chances by one (maybe more) and prevents knockbacks while carving.
|
||||||
|
min_c = 1
|
||||||
|
max_c = 2
|
||||||
|
extend_p = 50
|
||||||
|
elif carve_skill == CARVING_SKILL_FELYNE_LOW:
|
||||||
|
min_c = 0
|
||||||
|
max_c = 1
|
||||||
|
extend_p = 25
|
||||||
|
elif carve_skill == CARVING_SKILL_FELYNE_HI:
|
||||||
|
min_c = 0
|
||||||
|
max_c = 1
|
||||||
|
extend_p = 50
|
||||||
|
elif carve_skill in (CARVING_SKILL_NONE, CARVING_SKILL_PRO):
|
||||||
|
# Description: Prevents knockbacks from attacks while carving.
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
raise ValueError()
|
||||||
|
return reward_expected_c(min_c, max_c, extend_p)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# in percent
|
# in percent
|
||||||
reward_percent = int(sys.argv[1])
|
reward_percent = int(sys.argv[1])
|
||||||
|
|||||||
151
mhrewards.py
Executable file
151
mhrewards.py
Executable file
@@ -0,0 +1,151 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import mhdb
|
||||||
|
import mhprob
|
||||||
|
|
||||||
|
db = mhdb.MHDB("mh4u.db")
|
||||||
|
|
||||||
|
|
||||||
|
def _format_range(min_v, max_v):
|
||||||
|
if min_v == max_v:
|
||||||
|
return "%5.2f%%" % min_v
|
||||||
|
else:
|
||||||
|
return "%5.2f%% to %5.2f%%" % (min_v, max_v)
|
||||||
|
|
||||||
|
|
||||||
|
def get_quests(item_name):
|
||||||
|
"""
|
||||||
|
Get a list of the quests for acquiring a given item and the probability
|
||||||
|
of getting the item, depending on cap or kill and luck skills.
|
||||||
|
"""
|
||||||
|
item_row = db.get_item_by_name(item_name)
|
||||||
|
item_id = item_row["_id"]
|
||||||
|
quests = db.get_item_quests(item_id)
|
||||||
|
for q in quests:
|
||||||
|
print q
|
||||||
|
print " %20s" % "= Quest"
|
||||||
|
quest_ev = 0
|
||||||
|
sub_used = False
|
||||||
|
|
||||||
|
fixed_other_rewards = dict(A=0, B=0, Sub=0)
|
||||||
|
total_reward_p = dict(A=0, B=0, Sub=0)
|
||||||
|
for reward in q._rewards:
|
||||||
|
slot = reward["reward_slot"]
|
||||||
|
#reward_item_row = db.get_item(reward["item_id"])
|
||||||
|
#print slot, reward_item_row["name"], reward["percentage"]
|
||||||
|
if reward["percentage"] == 100:
|
||||||
|
if reward["item_id"] != item_id:
|
||||||
|
fixed_other_rewards[slot] += 1
|
||||||
|
else:
|
||||||
|
total_reward_p[slot] += reward["percentage"]
|
||||||
|
|
||||||
|
# sanity check values from the db
|
||||||
|
for slot in total_reward_p.keys():
|
||||||
|
if total_reward_p[slot] not in (0, 100):
|
||||||
|
print "WARNING: bad total p for %s = %d" \
|
||||||
|
% (slot, total_reward_p[slot])
|
||||||
|
|
||||||
|
for reward in q._rewards:
|
||||||
|
slot = reward["reward_slot"]
|
||||||
|
#reward_item_row = db.get_item(reward["item_id"])
|
||||||
|
#print slot, reward_item_row["name"], reward["percentage"]
|
||||||
|
if reward["item_id"] == item_id:
|
||||||
|
totals = [mhprob.quest_reward_expected_c(slot, skill)
|
||||||
|
for skill in xrange(mhprob.LUCK_SKILL_NONE,
|
||||||
|
mhprob.LUCK_SKILL_GREAT+1)]
|
||||||
|
|
||||||
|
|
||||||
|
evs = [((i - fixed_other_rewards[slot])
|
||||||
|
* reward["stack_size"] * reward["percentage"])
|
||||||
|
for i in totals]
|
||||||
|
|
||||||
|
print " %20s %d %5.2f%%" % (reward["reward_slot"],
|
||||||
|
reward["stack_size"],
|
||||||
|
evs[0]),
|
||||||
|
print " (%2d%% each)" % reward["percentage"],
|
||||||
|
if len(totals) > 1:
|
||||||
|
print " %s" % " ".join("%0.2f" % i for i in evs[1:]),
|
||||||
|
print
|
||||||
|
|
||||||
|
quest_ev += evs[0]
|
||||||
|
if reward["reward_slot"] == "Sub":
|
||||||
|
sub_used = True
|
||||||
|
monsters = db.get_quest_monsters(q.id)
|
||||||
|
|
||||||
|
cap_ev = [quest_ev, quest_ev]
|
||||||
|
kill_ev = [quest_ev, quest_ev]
|
||||||
|
shiny_ev = 0
|
||||||
|
for m in monsters:
|
||||||
|
mid = m["monster_id"]
|
||||||
|
monster = db.get_monster(mid)
|
||||||
|
print " %20s" % ("= " + monster["name"] + " " + q.rank)
|
||||||
|
rewards = db.get_monster_rewards(mid, q.rank)
|
||||||
|
for reward in rewards:
|
||||||
|
cap = kill = shiny = False
|
||||||
|
if reward["item_id"] == item_id:
|
||||||
|
if reward["condition"] == "Body Carve":
|
||||||
|
totals = [
|
||||||
|
3 + mhprob.carve_delta_expected_c(skill)
|
||||||
|
for skill in xrange(mhprob.CARVING_SKILL_PRO,
|
||||||
|
mhprob.CARVING_SKILL_GOD+1)
|
||||||
|
]
|
||||||
|
cap = False
|
||||||
|
kill = True
|
||||||
|
elif reward["condition"] == "Tail Carve":
|
||||||
|
totals = [
|
||||||
|
1 + mhprob.carve_delta_expected_c(skill)
|
||||||
|
for skill in xrange(mhprob.CARVING_SKILL_PRO,
|
||||||
|
mhprob.CARVING_SKILL_GOD+1)
|
||||||
|
]
|
||||||
|
cap = kill = True
|
||||||
|
elif reward["condition"] == "Capture":
|
||||||
|
totals = [
|
||||||
|
mhprob.capture_reward_expected_c(skill)
|
||||||
|
for skill in xrange(mhprob.CAP_SKILL_NONE,
|
||||||
|
mhprob.CAP_SKILL_GOD+1)
|
||||||
|
]
|
||||||
|
cap = True
|
||||||
|
kill = False
|
||||||
|
else:
|
||||||
|
totals = [1]
|
||||||
|
# don't include Shiny in ev calculations
|
||||||
|
if reward["condition"].startswith("Shiny"):
|
||||||
|
cap = kill = False
|
||||||
|
shiny = True
|
||||||
|
elif reward["condition"].startswith("Break"):
|
||||||
|
cap = kill = True
|
||||||
|
else:
|
||||||
|
raise ValueError("Unknown condition: "
|
||||||
|
+ reward["condition"])
|
||||||
|
|
||||||
|
evs = [i * reward["stack_size"] * reward["percentage"]
|
||||||
|
for i in totals]
|
||||||
|
if cap:
|
||||||
|
cap_ev[0] += evs[0]
|
||||||
|
cap_ev[1] += evs[-1]
|
||||||
|
if kill:
|
||||||
|
kill_ev[0] += evs[0]
|
||||||
|
kill_ev[1] += evs[-1]
|
||||||
|
if shiny:
|
||||||
|
shiny_ev += evs[0]
|
||||||
|
|
||||||
|
print " %20s %d %5.2f%%" % (reward["condition"],
|
||||||
|
reward["stack_size"],
|
||||||
|
evs[0]),
|
||||||
|
print " (%2d%% each)" % reward["percentage"],
|
||||||
|
if len(totals) > 1:
|
||||||
|
print " %s" % " ".join("%0.2f" % i for i in evs[1:]),
|
||||||
|
print
|
||||||
|
print " %20s" % "= Totals"
|
||||||
|
print " %20s %s" % \
|
||||||
|
("Kill", _format_range(*kill_ev))
|
||||||
|
print " %20s %s" % \
|
||||||
|
("Cap", _format_range(*cap_ev))
|
||||||
|
print " %20s %5.2f%%" % ("Shiny", shiny_ev)
|
||||||
|
print
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
|
||||||
|
get_quests(sys.argv[1])
|
||||||
Reference in New Issue
Block a user