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.

936 lines
33 KiB

"""
Calculate expected values for monster hunter items and find the best quests
and hunts for getting an item with specified skills.
"""
from __future__ import print_function
from collections import OrderedDict
from mhapi import stats
from mhapi.model import Quest
from mhapi.skills import LuckSkill, CapSkill, CarvingSkill
SKILL_CARVING = "carving"
SKILL_CAP = "cap"
SKILL_NONE = None
STRAT_KILL = "kill"
STRAT_CAP = "cap"
STRAT_SHINY = "shiny"
STRAT_CAP_OR_KILL = "cap/kill"
ITEM_TYPES = "Bone Bug Coin/Ticket Fish Flesh Meat Ore Plant Sac/Fluid".split()
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 find_item(db, item_name, err_out):
item_row = db.get_item_by_name(item_name)
if item_row is None:
print("Item '%s' not found. Listing partial matches:" % item_name,
file=err_out)
terms = item_name.split()
for term in terms:
if len(term) < 2:
# single char terms aren't very useful, too many results
continue
print("= Matching term '%s'" % term, file=err_out)
rows = db.search_item_name(term, ITEM_TYPES)
for row in rows:
print(" ", row["name"], file=err_out)
return None
return item_row
class GatherReward(object):
def __init__(self, gathering_row):
self.item_id = gathering_row["item_id"]
self.location_id = gathering_row["location_id"]
self.area = gathering_row["area"]
self.site = gathering_row["site"]
self.rank = gathering_row["rank"]
self.stack_size = gathering_row["quantity"]
self.percentage = gathering_row["percentage"]
def expected_value(self):
# TODO: add gathering skill
# Assume average of 3 gathers on every site. Simplistic but
# should be a reasonable approximation to start with.
return 3 * self.percentage
def print(self, out, indent=2):
area_site = "%s %s" % (self.area, self.site)
out.write("%s%20s %d %5.2f / 100"
% (" " * indent, area_site, self.stack_size,
self.expected_value()))
out.write(" (%2d each)" % self.percentage)
out.write("\n")
class GatherLocation(object):
"""
Track total expected value for an item in one location/rank.
"""
def __init__(self, location_row, rank, gathering_rows):
self.location_id = location_row.id
self.location_name = location_row["name"]
self.rank = rank
self._rewards = []
self._ev = 0
self._explorer_ev = 0
self._add_rewards(gathering_rows)
def _add_rewards(self, rows):
for r in rows:
if (r["location_id"] == self.location_id
and r["rank"] == self.rank):
gr = GatherReward(r)
self._rewards.append(gr)
self._explorer_ev += gr.expected_value()
if gr.area != "Secret":
self._ev += gr.expected_value()
def expected_value(self, explorer=False):
if explorer:
return self._explorer_ev
else:
return self._ev
def print(self, out, indent=2):
for gr in self._rewards:
gr.print(out, indent)
def __nonzero__(self):
return bool(len(self._rewards))
def __len__(self):
return len(self._rewards)
class QuestReward(object):
def __init__(self, reward, fixed_rewards):
self.slot = reward["reward_slot"]
self.stack_size = reward["stack_size"]
self.percentage = reward["percentage"]
self.item_id = reward["item_id"]
self.fixed_rewards = fixed_rewards[self.slot]
self.skill_delta = 0
self.evs = self._calculate_ev()
def expected_value(self, luck_skill=LuckSkill.NONE,
cap_skill=None, carving_skill=None):
return self.evs[luck_skill]
def expected_values(self):
return self.evs
def _calculate_ev(self):
if self.percentage == 100:
# fixed reward, always one draw regardless of luck skill
evs = [1 * self.percentage * self.stack_size] * 4
self.skill_delta = 0
else:
# variable reward, expected number of draws depends on luck skill
counts = [stats.quest_reward_expected_c(self.slot, skill)
for skill in xrange(LuckSkill.NONE,
LuckSkill.AMAZING+1)]
evs = [((count - self.fixed_rewards)
* self.stack_size * self.percentage)
for count in counts]
self.skill_delta = evs[-1] - evs[0]
return evs
def print(self, out, indent=2):
out.write("%s%20s %d %5.2f / 100"
% (" " * indent, self.slot, self.stack_size,
self.evs[0]))
out.write(" (%2d each)" % self.percentage)
if self.skill_delta:
out.write(" %s" % " ".join("%0.2f" % i for i in self.evs[1:]))
out.write("\n")
class QuestItemExpectedValue(object):
"""
Calculate the expected value for an item across all rewards for a quest.
@param item_id: database id of item
@param quest_rewards: list of rows from quest_rewards table for a single
quest
"""
def __init__(self, item_id, quest):
self.item_id = item_id
self.quest = quest
self.fixed_rewards = dict(A=0, B=0, Sub=0)
self.total_reward_p = dict(A=0, B=0, Sub=0)
# renormalize percentages if total is > 100
self.normalize_reward_p = dict(A=1, B=1, Sub=1)
# dict mapping slot name to list of lists
# of the form (slot, list_of_expected_values).
self.slot_rewards = dict(A=[], B=[], Sub=[])
self.total_expected_values = [0, 0, 0, 0]
self._set_rewards(quest.rewards)
def is_sub(self):
"""Item is available from sub quest"""
return len(self.slot_rewards["Sub"]) > 0
def is_main(self):
"""Item is available from main quest"""
return (len(self.slot_rewards["A"]) > 0
or len(self.slot_rewards["B"]) > 0)
def expected_value(self, luck_skill=LuckSkill.NONE,
cap_skill=None, carving_skill=None):
return self.total_expected_values[luck_skill]
def _check_totals(self):
# sanity check values from the db
for slot in self.total_reward_p.keys():
total_p = self.total_reward_p[slot]
if total_p not in (0, 100):
#print("WARNING: bad total p for %s = %d, renormalizing"
# % (slot, total_p))
self.normalize_reward_p[slot] = (100.0 / total_p)
def _set_rewards(self, rewards):
# preprocessing step - figure out how many fixed rewards there
# are, which we need to know in order to figure out how many
# chances there are to get other rewards.
for reward in rewards:
slot = reward["reward_slot"]
if reward["percentage"] == 100:
self.fixed_rewards[slot] += 1
else:
self.total_reward_p[slot] += reward["percentage"]
self._check_totals()
for reward in rewards:
if reward["item_id"] != self.item_id:
continue
self._add_reward(reward)
def _add_reward(self, r):
mutable_r = dict(r)
# don't adjust fixed rewards
if mutable_r["percentage"] != 100:
mutable_r["percentage"] *= self.normalize_reward_p[r["reward_slot"]]
reward = QuestReward(mutable_r, self.fixed_rewards)
self.slot_rewards[reward.slot].append(reward)
evs = reward.expected_values()
for i in xrange(len(evs)):
self.total_expected_values[i] += evs[i]
def print(self, out, indent=2):
for slot in ("A", "B", "Sub"):
for qr in self.slot_rewards[slot]:
qr.print(out, indent)
class HuntReward(object):
def __init__(self, reward):
self.condition = reward["condition"]
self.stack_size = reward["stack_size"]
self.percentage = reward["percentage"]
self.item_id = reward["item_id"]
if not self.percentage:
# TODO: this is an error in the db, print warning in higher
# level code
self.percentage = 0
self.cap = False
self.kill = False
self.shiny = False
self.skill = SKILL_NONE
self.evs = self._calculate_evs()
def expected_value(self, strategy, luck_skill=None,
cap_skill=CapSkill.NONE,
carving_skill=CarvingSkill.NONE):
if strategy == STRAT_CAP:
if not self.cap:
return 0
elif strategy == STRAT_KILL:
if not self.kill:
return 0
elif strategy == STRAT_SHINY:
if not self.shiny:
return 0
else:
raise ValueError("strategy must be STRAT_CAP or STRAT_KILL")
if self.skill == SKILL_CAP:
return self.evs[cap_skill]
elif self.skill == SKILL_CARVING:
return self.evs[carving_skill]
else:
return self.evs[0]
def print(self, out, indent=2):
out.write("%s%20s %d %5.2f / 100"
% (" " * indent, self.condition,
self.stack_size, self.evs[0]))
out.write(" (%2d each)" % self.percentage)
if len(self.evs) > 1:
out.write(" " + " ".join("%0.2f" % i for i in self.evs[1:]))
out.write("\n")
def as_data(self):
d = dict(condition=self.condition,
stack_size=self.stack_size,
percentage=self.percentage,
item_id=self.item_id,
cap=self.cap,
kill=self.kill,
shiny=self.shiny)
kill_ev = dict()
cap_ev = dict()
for skill in xrange(CarvingSkill.NONE, CarvingSkill.GOD+1):
kill_ev[CarvingSkill.name(skill)] = \
self.expected_value(STRAT_CAP, carving_skill=skill)
for skill in xrange(CapSkill.NONE, CapSkill.GOD+1):
cap_ev[CapSkill.name(skill)] = self.expected_value(STRAT_CAP,
cap_skill=skill)
d["kill_expected_value"] = kill_ev
d["cap_expected_value"] = cap_ev
return d
def _calculate_evs(self):
if self.condition == "Tail Carve":
self.skill = SKILL_CARVING
self.cap = True
self.kill = True
counts = [
1 + stats.carve_delta_expected_c(skill)
for skill in xrange(CarvingSkill.PRO,
CarvingSkill.GOD+1)
]
elif self.condition == "Body Carve (Apparent Death)":
# Gypceros fake death. Assume one carve, it's dangerous to try
# for two.
counts = [1]
self.cap = True
self.kill = True
elif self.condition == "Body Carve":
# TODO: some monsters have 4 body carves
self.skill = SKILL_CARVING
self.cap = False
self.kill = True
counts = [
3 + stats.carve_delta_expected_c(skill)
for skill in xrange(CarvingSkill.PRO,
CarvingSkill.GOD+1)
]
elif self.condition.startswith("Body Carve (KO"):
# Kelbi
self.skill = SKILL_CARVING
self.cap = True
self.kill = True
counts = [
1 + stats.carve_delta_expected_c(skill)
for skill in xrange(CarvingSkill.PRO,
CarvingSkill.GOD+1)
]
elif "Carve" in self.condition:
# Mouth Carve: Dah'ren Mohran
# Upper Body Carve: Dalamadur
# Lower Body Carve: Dalamadur
# Head Carve: Dalamadur
# TODO: separate these out, some have >3 carves, not sure
# about others
self.skill = SKILL_CARVING
self.cap = False
self.kill = True
counts = [
3 + stats.carve_delta_expected_c(skill)
for skill in xrange(CarvingSkill.PRO,
CarvingSkill.GOD+1)
]
elif self.condition == "Capture":
self.skill = SKILL_CAP
self.cap = True
self.kill = False
counts = [
stats.capture_reward_expected_c(skill)
for skill in xrange(CapSkill.NONE,
CapSkill.GOD+1)
]
elif self.condition == "Virus Reward":
# TODO: not sure how these work
# Assume 1 always for easy comparison. My guess is that you
# always get 1 from frenzied monsters and have a chance at 2+.
# The question is do the cances of getting more than one
# change depending on the monster, e.g. do Apex monsters
# give more rewards or just higher rarity crystals?
self.cap = True
self.kill = True
counts = [1]
else:
counts = [1]
if self.condition.startswith("Shiny"):
# don't include shiny in total, makes it easier to
# calculate separately since shinys are variable by
# monster
self.cap = False
self.kill = False
self.shiny = True
elif self.condition.startswith("Break"):
self.cap = True
self.kill = True
elif self.condition in ("Bug-Catching Back", "Mining Back",
"Mining Ore", "Mining Scale"):
# TODO: it's easy to get more than one here, would be nice
# to separate these out like shinys.
self.cap = True
self.kill = True
else:
raise ValueError("Unknown condition: '%s'"
% self.condition)
evs = [(i * self.stack_size * self.percentage) for i in counts]
return evs
class RankAndSkills(object):
"""
Helper to track the best strategy with a given set of skills and hunter
rank.
"""
def __init__(self, rank="G",
luck_skill=LuckSkill.NONE,
cap_skill=CapSkill.NONE,
carving_skill=CarvingSkill.NONE,
explorer=False):
self.rank = rank
self.luck_skill = luck_skill
self.cap_skill = cap_skill
self.carving_skill = carving_skill
self.explorer = explorer
if self.rank == "LR":
assert not explorer, "Explorer is not available in low rank"
self.best = None
def _rank_available(self, rank):
if self.rank == "LR" and rank != "LR":
return False
if self.rank == "HR" and rank == "G":
return False
return True
def _compare_strats(self, kill_strat, cap_strat):
"""
Compare kill vs cap, and compare the best with current best. If cap
and kill are the same, keep track that it doesn't matter which is
used.
"""
if kill_strat == cap_strat:
new_strat = kill_strat
new_strat.strat = STRAT_CAP_OR_KILL
elif kill_strat > cap_strat:
new_strat = kill_strat
else:
new_strat = cap_strat
return self._compare_best(new_strat)
def _compare_best(self, new_strat):
if self.best is None:
self.best = new_strat
return True
elif new_strat > self.best:
self.best = new_strat
return True
return False
def add_gather_option(self, gather_location):
if not self._rank_available(gather_location.rank):
return False
# strat is ignored
gather_strat = ItemStrategy(STRAT_CAP)
gather_strat.add_gather_location(gather_location)
self._compare_best(gather_strat)
def add_hunt_option(self, hunt_item):
if not self._rank_available(hunt_item.monster_rank):
return False
kill_strat = ItemStrategy(STRAT_KILL,
cap_skill=self.cap_skill,
carving_skill=self.carving_skill)
cap_strat = ItemStrategy(STRAT_CAP,
cap_skill=self.cap_skill,
carving_skill=self.carving_skill)
for strat in (kill_strat, cap_strat):
strat.add_hunt_item(hunt_item)
self._compare_strats(kill_strat, cap_strat)
def add_quest_option(self, quest_item, hunt_items, gather_location):
if not self._rank_available(quest_item.quest.rank):
return False
cap_strat = ItemStrategy(STRAT_CAP,
luck_skill=self.luck_skill,
cap_skill=self.cap_skill,
carving_skill=self.carving_skill,
explorer=self.explorer)
kill_strat = ItemStrategy(STRAT_KILL,
luck_skill=self.luck_skill,
cap_skill=self.cap_skill,
carving_skill=self.carving_skill,
explorer=self.explorer)
for strat in (cap_strat, kill_strat):
strat.set_quest_item(quest_item)
if gather_location:
strat.set_gather_location(gather_location)
for hi in hunt_items:
strat.add_hunt_item(hi)
self._compare_strats(kill_strat, cap_strat)
class ItemStrategy(object):
"""
Encapsulate a specific strategy for getting an item, including kill vs
cap and skills.
"""
def __init__(self, strat,
luck_skill=None, cap_skill=None, carving_skill=None,
explorer=False):
self.strat = strat
self.luck_skill = luck_skill
self.cap_skill = cap_skill
self.carving_skill = carving_skill
self.explorer = explorer
self.hunt_items = []
self.quest_item = None
self.gather_location = None
self.hunt_ev = 0
self.quest_ev = 0
self.gather_ev = 0
self.ev = 0
def add_hunt_item(self, hunt_item):
self.hunt_items.append(hunt_item)
ev = hunt_item.expected_value(self.strat,
carving_skill=self.carving_skill,
cap_skill=self.cap_skill)
self.hunt_ev += ev
self.ev += ev
def set_quest_item(self, quest_item):
"""
Allow adding a quest and luck skill after create, e.g. to an
existing hunt only strategy returned by get_best_strategy.
"""
assert self.quest_item is None
self.quest_item = quest_item
ev = self.quest_item.expected_value(luck_skill=self.luck_skill)
self.quest_ev = ev
self.ev += ev
def set_gather_location(self, gather_location):
assert self.gather_location is None
self.gather_location = gather_location
ev = gather_location.expected_value(self.explorer)
self.gather_ev = ev
self.ev += ev
@property
def hunt_item(self):
assert len(self.hunt_items) == 1
return self.hunt_items[0]
def print(self, out):
if self.quest_item:
out.write("(QUEST) " + self.quest_item.quest.one_line_u())
out.write(" %s [%5.2f]\n" % (self.strat, self.ev))
elif self.gather_location:
out.write("(GATHER) %s %s [%5.2f]\n" %
(self.gather_location.location_name,
self.gather_location.rank,
self.ev))
else:
out.write("(HUNT) %s %s %s [%5.2f]\n" %
(self.hunt_item.monster_name,
self.hunt_item.monster_rank,
self.strat, self.ev))
def is_same_strat(self, other):
if self.strat != other.strat:
return False
if self.quest_item != other.quest_item:
return False
if len(self.hunt_items) != len(other.hunt_items):
return False
if self.hunt_ev != other.hunt_ev:
return False
if self.quest_ev != other.quest_ev:
return False
if self.gather_ev != other.gather_ev:
return False
for self_hi, other_hi in zip(self.hunt_items, other.hunt_items):
if self_hi.monster_name != other_hi.monster_name:
return False
if self_hi.monster_rank != other_hi.monster_rank:
return False
return True
def __cmp__(self, other):
return cmp(self.ev, other.ev)
class HuntItemExpectedValue(object):
"""
Calculate the expected value for an item from hunting a monster, including
all ways of getting the item.
@param item_id: database id of item
@param hunt_rewards: list of rows from hunt_rewards table for a single
monster and rank
"""
def __init__(self, item_id, monster_name, monster_rank, hunt_rewards):
self.item_id = item_id
self.monster_name = monster_name
self.monster_rank = monster_rank
self.matching_rewards = []
self._set_rewards(hunt_rewards)
def expected_value(self, strategy, luck_skill=None,
cap_skill=CapSkill.NONE,
carving_skill=CarvingSkill.NONE):
ev = 0
for reward in self.matching_rewards:
ev += reward.expected_value(strategy,
luck_skill=luck_skill,
cap_skill=cap_skill,
carving_skill=carving_skill)
return ev
def __nonzero__(self):
return bool(len(self.matching_rewards))
def __len__(self):
return len(self.matching_rewards)
def print(self, out, indent=2):
for hr in self.matching_rewards:
hr.print(out, indent)
def as_data(self):
d = dict(monster_name=self.monster_name,
monster_rank=self.monster_rank,
rewards=[r.as_data() for r in self.matching_rewards])
kill_ev = dict()
cap_ev = dict()
for skill in xrange(CarvingSkill.NONE, CarvingSkill.GOD+1):
kill_ev[CarvingSkill.name(skill)] = \
self.expected_value(STRAT_CAP, carving_skill=skill)
for skill in xrange(CapSkill.NONE, CapSkill.GOD+1):
cap_ev[CapSkill.name(skill)] = self.expected_value(STRAT_CAP,
cap_skill=skill)
d["kill_expected_value"] = kill_ev
d["cap_expected_value"] = cap_ev
return d
def _set_rewards(self, rewards):
for reward in rewards:
if reward["item_id"] != self.item_id:
continue
# TODO: warn when percentage == 0
self._add_reward(reward)
def _add_reward(self, r):
reward = HuntReward(r)
self.matching_rewards.append(reward)
class ItemRewards(object):
def __init__(self, db, item_row):
self.db = db
self.item_row = item_row
self.item_id = item_row.id
wyp_row = db.get_wyporium_trade(self.item_id)
if wyp_row is not None:
self.trade_unlock_quest = db.get_quest(wyp_row["unlock_quest_id"])
self.trade_item_row = self.item_row
self.trade_item_id = self.item_id
self.item_id = wyp_row["item_out_id"]
self.item_row = db.get_item(wyp_row["item_out_id"])
else:
self.trade_item_row = None
self.trade_item_id = None
self.trade_unlock_quest = None
self.rank_skill_sets = OrderedDict()
for rank in "G HR LR".split():
self.rank_skill_sets[rank] = OrderedDict([
("No skills",
RankAndSkills(rank)),
("Capture God",
RankAndSkills(rank, cap_skill=CapSkill.GOD)),
("Carving God",
RankAndSkills(rank, carving_skill=CarvingSkill.GOD)),
("Amazing Luck",
RankAndSkills(rank, luck_skill=LuckSkill.AMAZING)),
])
if rank != "LR":
self.rank_skill_sets[rank]["Explorer"] = \
RankAndSkills(rank, explorer=True)
self._hunt_items = OrderedDict()
self._quest_items = OrderedDict()
self._gather_items = OrderedDict()
self._find_gather_items()
self._find_hunt_items()
self._find_quest_items()
def is_empty(self):
return (not self._hunt_items and not self._quest_items
and not self._gather_items)
def _find_gather_items(self):
gathering_rows = self.db.get_item_gathering(self.item_id)
locations = self.db.get_locations()
for loc in locations:
for rank in "LR HR G".split():
gl = GatherLocation(loc, rank, gathering_rows)
if gl:
key = (loc.id, rank)
self._gather_items[key] = gl
def _find_hunt_items(self):
monsters = self.db.get_item_monsters(self.item_id)
for m in monsters:
mid = m["monster_id"]
rank = m["rank"]
monster = self.db.get_monster(mid)
reward_rows = self.db.get_monster_rewards(mid, rank)
hunt_item = HuntItemExpectedValue(self.item_id, monster["name"],
rank, reward_rows)
if not hunt_item:
continue
key = (mid, rank)
self._hunt_items[key] = hunt_item
for rank, skill_sets in self.rank_skill_sets.iteritems():
for s in skill_sets.itervalues():
s.add_hunt_option(hunt_item)
def get_hunt_item(self, monster_id, monster_rank):
key = (monster_id, monster_rank)
return self._hunt_items.get(key)
def _find_quest_items(self):
"""
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.
"""
quests = self.db.get_item_quests(self.item_id)
if not quests:
return
for q in quests:
quest_item = QuestItemExpectedValue(self.item_id, q)
self._quest_items[q.id] = quest_item
quest_monsters = self.db.get_quest_monsters(quest_item.quest.id)
hunt_items = []
for m in quest_monsters:
mid = m["monster_id"]
# It looks like every monster other than the first is
# marked as stable. This looks like it's usually correct
# for single monster quests, but wrong for multi monster
# quests, so skip the unstable monsters for single monster
# quests.
unstable = (m["unstable"] == "yes")
if unstable:
continue
hunt_item = self.get_hunt_item(mid, quest_item.quest.rank)
if hunt_item:
hunt_items.append(hunt_item)
gather_key = (quest_item.quest.location_id, quest_item.quest.rank)
gather_location = self._gather_items.get(gather_key)
for rank, skill_sets in self.rank_skill_sets.iteritems():
for s in skill_sets.itervalues():
s.add_quest_option(quest_item, hunt_items, gather_location)
def print_gather_locations(self, out):
if not self._gather_items:
return
for gl in self._gather_items.itervalues():
out.write("(GATHER) %s %s\n"
% (gl.location_name, gl.rank))
gl.print(out, indent=2)
out.write(" %20s\n" % "= Totals")
out.write(" %20s %d / 100\n"
% ("All", gl.expected_value()))
out.write("\n")
def print_monsters(self, out):
if not self._hunt_items:
return
for hunt_item in self._hunt_items.itervalues():
out.write("(HUNT) %s %s\n"
% (hunt_item.monster_name, hunt_item.monster_rank))
hunt_item.print(out, indent=2)
kill_ev = [0, 0]
kill_ev[0] = hunt_item.expected_value(STRAT_KILL)
kill_ev[1] = hunt_item.expected_value(STRAT_KILL,
carving_skill=CarvingSkill.GOD)
cap_ev = [0, 0]
cap_ev[0] = hunt_item.expected_value(STRAT_CAP)
cap_ev[1] = hunt_item.expected_value(STRAT_CAP,
cap_skill=CapSkill.GOD)
shiny_ev = hunt_item.expected_value(STRAT_SHINY)
out.write(" %20s\n" % "= Totals")
out.write(" %20s %s / 100\n"
% ("Kill", _format_range(*kill_ev)))
out.write(" %20s %s / 100\n"
% ("Cap", _format_range(*cap_ev)))
if shiny_ev:
out.write(" %20s %5.2f / 100\n" % ("Shiny", shiny_ev))
out.write("\n")
def print_recommended_hunts(self, out):
out.write("*** Poogie Recommends ***\n")
for rank, skill_sets in self.rank_skill_sets.iteritems():
no_skill_best = skill_sets["No skills"].best
if no_skill_best is None:
# not available at this rank
continue
out.write("> " + rank + "\n")
for name, skill_set in skill_sets.iteritems():
if skill_set.best is None:
# Don't print out a rank with no options
continue
if (name != "No skills"
and skill_set.best.is_same_strat(no_skill_best)):
# Don't print out a skill set that doesn't differ from
# no skills
continue
out.write(" [%-12s] " % name)
skill_set.best.print(out)
out.write("\n")
def print_quests(self, out):
"""
Get a list of the quests for acquiring a given item and the probability
of getting the item, depending on cap or kill and luck skills.
"""
for quest_item in self._quest_items.itervalues():
out.write("(QUEST) " + unicode(quest_item.quest) + "\n")
out.write(" %20s" % "= Quest\n")
quest_item.print(out, indent=2)
quest_monsters = self.db.get_quest_monsters(quest_item.quest.id)
quest_ev = quest_item.expected_value()
gather_key = (quest_item.quest.location_id, quest_item.quest.rank)
gather_location = self._gather_items.get(gather_key)
if gather_location:
quest_ev += gather_location.expected_value()
cap_ev = [quest_ev, quest_ev]
kill_ev = [quest_ev, quest_ev]
shiny_ev = 0
for m in quest_monsters:
mid = m["monster_id"]
hunt_item = self.get_hunt_item(mid, quest_item.quest.rank)
if hunt_item is None or m["unstable"] == "yes":
continue
kill_ev[0] += hunt_item.expected_value(STRAT_KILL)
kill_ev[1] += hunt_item.expected_value(STRAT_KILL,
carving_skill=CarvingSkill.GOD)
cap_ev[0] += hunt_item.expected_value(STRAT_CAP)
cap_ev[1] += hunt_item.expected_value(STRAT_CAP,
cap_skill=CapSkill.GOD)
shiny_ev = hunt_item.expected_value(STRAT_SHINY)
if kill_ev[0] == 0 and cap_ev[0] == 0 and shiny_ev == 0:
continue
out.write(" %20s\n"
% ("= " + hunt_item.monster_name
+ " " + hunt_item.monster_rank))
hunt_item.print(out, indent=2)
if gather_location:
out.write(" %20s\n"
% ("= " + gather_location.location_name
+ " " + gather_location.rank))
gather_location.print(out, indent=2)
out.write(" %20s\n" % "= Totals")
if quest_monsters:
out.write(" %20s %s / 100\n"
% ("Kill", _format_range(*kill_ev)))
out.write(" %20s %s / 100\n"
% ("Cap", _format_range(*cap_ev)))
if shiny_ev:
out.write(" %20s %5.2f / 100\n" % ("Shiny", shiny_ev))
else:
out.write(" %20s %d / 100\n"
% ("Quest+Gather", quest_ev))
out.write("\n")
def print_all(self, out):
if self.is_empty():
out.write("ERROR: data for this item is not yet available\n")
return
#out.write("item id: %d\n" % self.item_id)
if self.trade_unlock_quest:
item_name = self.item_row["name"]
out.write("*** Wyporium trade for '%s'\n" % item_name)
out.write(" Unlocked by quest '%s'\n"
% unicode(self.trade_unlock_quest).split("\n")[0])
out.write("\n")
self.print_recommended_hunts(out)
self.print_gather_locations(out)
self.print_monsters(out)
self.print_quests(out)