parent
f91b11e293
commit
9968cd4cdd
@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
import _pathfix
|
||||||
|
|
||||||
|
from mhapi.db import MHDB
|
||||||
|
from mhapi.damage import MotionValueDB, WeaponMonsterDamage
|
||||||
|
|
||||||
|
|
||||||
|
def percent_change(a, b):
|
||||||
|
if a == 0:
|
||||||
|
return b
|
||||||
|
return (100.0 * (b-a) / a)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
print "Usage: %s 'monster name' 'weapon name'+" % sys.argv[0]
|
||||||
|
sys.exit(os.EX_USAGE)
|
||||||
|
|
||||||
|
sharp_plus = bool(int(sys.argv[1]))
|
||||||
|
monster_name = sys.argv[2]
|
||||||
|
weapon_names = sys.argv[3:]
|
||||||
|
|
||||||
|
db = MHDB(_pathfix.db_path)
|
||||||
|
motiondb = MotionValueDB(_pathfix.motion_values_path)
|
||||||
|
|
||||||
|
monster = db.get_monster_by_name(monster_name)
|
||||||
|
if not monster:
|
||||||
|
raise ValueError("Monster '%s' not found" % monster_name)
|
||||||
|
monster_damage = db.get_monster_damage(monster["_id"])
|
||||||
|
weapons = []
|
||||||
|
for name in weapon_names:
|
||||||
|
weapon = db.get_weapon_by_name(name)
|
||||||
|
if not weapon:
|
||||||
|
raise ValueError("Weapon '%s' not found" % name)
|
||||||
|
weapons.append(weapon)
|
||||||
|
|
||||||
|
monster_breaks = db.get_monster_breaks(monster["_id"])
|
||||||
|
weapon_type = weapons[0]["wtype"]
|
||||||
|
motion = motiondb[weapon_type].average
|
||||||
|
print "Weapon Type: %s" % weapon_type
|
||||||
|
print "Average Motion: %0.1f" % motion
|
||||||
|
print "Monster Breaks: %s" % ", ".join(monster_breaks)
|
||||||
|
weapon_damage_map = dict()
|
||||||
|
for name, row in zip(weapon_names, weapons):
|
||||||
|
row_type = row["wtype"]
|
||||||
|
if row_type != weapon_type:
|
||||||
|
raise ValueError("Weapon '%s' is different type" % name)
|
||||||
|
try:
|
||||||
|
weapon_damage_map[name] = WeaponMonsterDamage(row,
|
||||||
|
monster, monster_damage,
|
||||||
|
motion, sharp_plus,
|
||||||
|
monster_breaks)
|
||||||
|
except ValueError as e:
|
||||||
|
print str(e)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
damage_map_base = weapon_damage_map[weapon_names[0]]
|
||||||
|
parts = damage_map_base.parts
|
||||||
|
|
||||||
|
for part in parts:
|
||||||
|
tdiffs = [percent_change(
|
||||||
|
damage_map_base[part].total,
|
||||||
|
weapon_damage_map[w][part].total
|
||||||
|
)
|
||||||
|
for w in weapon_names[1:]]
|
||||||
|
ediffs = [percent_change(
|
||||||
|
damage_map_base[part].element,
|
||||||
|
weapon_damage_map[w][part].element
|
||||||
|
)
|
||||||
|
for w in weapon_names[1:]]
|
||||||
|
bdiffs = [percent_change(
|
||||||
|
damage_map_base[part].break_diff(),
|
||||||
|
weapon_damage_map[w][part].break_diff()
|
||||||
|
)
|
||||||
|
for w in weapon_names[1:]]
|
||||||
|
tdiff_s = ",".join("%+0.1f%%" % i for i in tdiffs)
|
||||||
|
ediff_s = ",".join("%+0.1f%%" % i for i in ediffs)
|
||||||
|
bdiff_s = ",".join("%+0.1f%%" % i for i in bdiffs)
|
||||||
|
damage = damage_map_base[part]
|
||||||
|
print "%22s%s h%02d %0.2f (%s) h%02d %0.2f (%s) %+0.2f (%s)" \
|
||||||
|
% (part, "*" if damage.is_breakable() else " ",
|
||||||
|
damage.hitbox,
|
||||||
|
damage.total,
|
||||||
|
tdiff_s,
|
||||||
|
damage.ehitbox,
|
||||||
|
damage.element,
|
||||||
|
ediff_s,
|
||||||
|
damage.break_diff(),
|
||||||
|
bdiff_s)
|
||||||
|
|
||||||
|
print " --------------------"
|
||||||
|
|
||||||
|
for avg_type in "uniform raw weakpart_raw element weakpart_element break_raw break_element break_only".split():
|
||||||
|
base = damage_map_base.averages[avg_type]
|
||||||
|
diffs = [percent_change(
|
||||||
|
base,
|
||||||
|
weapon_damage_map[w].averages[avg_type]
|
||||||
|
)
|
||||||
|
for w in weapon_names[1:]]
|
||||||
|
|
||||||
|
diff_s = ",".join("%+0.1f%%" % i for i in diffs)
|
||||||
|
|
||||||
|
print "%22s %0.2f (%s)" % (avg_type, base, diff_s)
|
||||||
@ -0,0 +1,565 @@
|
|||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
import json
|
||||||
|
import difflib
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
WEAKPART_WEIGHT = 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def raw_damage(true_raw, sharpness, affinity, monster_hitbox, motion):
|
||||||
|
"""
|
||||||
|
Calculate raw damage to a monster part with the given true raw,
|
||||||
|
sharpness, monster raw weakness, and weapon motion value.
|
||||||
|
"""
|
||||||
|
return (true_raw
|
||||||
|
* Sharpness.raw_modifier(sharpness)
|
||||||
|
* (1 + (affinity / 400.0))
|
||||||
|
* motion / 100.0
|
||||||
|
* monster_hitbox / 100.0)
|
||||||
|
|
||||||
|
|
||||||
|
def element_damage(element, sharpness, monster_ehitbox):
|
||||||
|
"""
|
||||||
|
Calculate elemental damage to a monster part with the given elemental
|
||||||
|
attack, the given sharpness, and the given monster elemental weakness.
|
||||||
|
Note that this is independent of the motion value of the attack.
|
||||||
|
"""
|
||||||
|
return (element / 10.0
|
||||||
|
* Sharpness.element_modifier(sharpness)
|
||||||
|
* monster_ehitbox / 100.0)
|
||||||
|
|
||||||
|
|
||||||
|
class Sharpness(object):
|
||||||
|
"""
|
||||||
|
Enumeration for weapon sharpness.
|
||||||
|
"""
|
||||||
|
|
||||||
|
RED = 0
|
||||||
|
ORANGE = 1
|
||||||
|
YELLOW = 2
|
||||||
|
GREEN = 3
|
||||||
|
BLUE = 4
|
||||||
|
WHITE = 5
|
||||||
|
PURPLE = 6
|
||||||
|
|
||||||
|
ALL = range(0, PURPLE + 1)
|
||||||
|
|
||||||
|
_modifier = {
|
||||||
|
RED: (0.50, 0.25),
|
||||||
|
ORANGE: (0.75, 0.50),
|
||||||
|
YELLOW: (1.00, 0.75),
|
||||||
|
GREEN: (1.05, 1.00),
|
||||||
|
BLUE: (1.20, 1.06),
|
||||||
|
WHITE: (1.32, 1.12),
|
||||||
|
PURPLE: (1.44, 1.20),
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def raw_modifier(cls, sharpness):
|
||||||
|
return cls._modifier[sharpness][0]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def element_modifier(cls, sharpness):
|
||||||
|
return cls._modifier[sharpness][1]
|
||||||
|
|
||||||
|
|
||||||
|
class MotionType(object):
|
||||||
|
CUT = "cut"
|
||||||
|
IMPACT = "impact"
|
||||||
|
FIXED = "fixed"
|
||||||
|
|
||||||
|
|
||||||
|
class MotionValue(object):
|
||||||
|
def __init__(self, name, types, powers):
|
||||||
|
self.name = name
|
||||||
|
self.types = types
|
||||||
|
self.powers = powers
|
||||||
|
self.average = sum(self.powers) / len(self.powers)
|
||||||
|
|
||||||
|
|
||||||
|
class WeaponTypeMotionValues(object):
|
||||||
|
def __init__(self, weapon_type, motion_data):
|
||||||
|
self.weapon_type = weapon_type
|
||||||
|
self.motion_values = dict()
|
||||||
|
for d in motion_data:
|
||||||
|
name = d["name"]
|
||||||
|
self.motion_values[name] = MotionValue(name, d["type"], d["power"])
|
||||||
|
|
||||||
|
self.average = (sum(mv.average
|
||||||
|
for mv in self.motion_values.itervalues())
|
||||||
|
/ len(self))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.motion_values)
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self.motion_values.keys()
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.motion_values[key]
|
||||||
|
|
||||||
|
|
||||||
|
class MotionValueDB(object):
|
||||||
|
def __init__(self, json_path):
|
||||||
|
with open(json_path) as f:
|
||||||
|
self._raw_data = json.load(f)
|
||||||
|
|
||||||
|
self.motion_values_map = dict()
|
||||||
|
|
||||||
|
for d in self._raw_data:
|
||||||
|
wtype = d["name"]
|
||||||
|
if wtype == "Sword":
|
||||||
|
wtype = "Sword and Shield"
|
||||||
|
self.motion_values_map[wtype] = WeaponTypeMotionValues(wtype,
|
||||||
|
d["motions"])
|
||||||
|
|
||||||
|
def __getitem__(self, weapon_type):
|
||||||
|
return self.motion_values_map[weapon_type]
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self.motion_values_map.keys()
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.motion_values_map)
|
||||||
|
|
||||||
|
|
||||||
|
class WeaponType(object):
|
||||||
|
"""
|
||||||
|
Enumeration for weapon types.
|
||||||
|
"""
|
||||||
|
SWITCH_AXE = "Switch Axe"
|
||||||
|
HAMMER = "Hammer"
|
||||||
|
HUNTING_HORN = "Hunting Horn"
|
||||||
|
GREAT_SWORD = "Great Sword"
|
||||||
|
CHARGE_BLADE = "Charge Blade"
|
||||||
|
LONG_SWORD = "Long Sword"
|
||||||
|
INSECT_GLAIVE = "Insect Glaive"
|
||||||
|
LANCE = "Lance"
|
||||||
|
GUNLANCE = "Gunlance"
|
||||||
|
HEAVY_BOWGUN = "Heavy Bowgun"
|
||||||
|
SWORD_AND_SHIELD = "Sword and Shield"
|
||||||
|
DUAL_BLADES = "Dual Blades"
|
||||||
|
LIGHT_BOWGUN = "Light Bowgun"
|
||||||
|
BOW = "Bow"
|
||||||
|
|
||||||
|
IMPACT = "impact"
|
||||||
|
CUT = "cut"
|
||||||
|
SHOT = "shot"
|
||||||
|
MIXED = "cut/impact"
|
||||||
|
|
||||||
|
_multiplier = {
|
||||||
|
"Switch Axe": 5.4,
|
||||||
|
"Hammer": 5.2,
|
||||||
|
"Hunting Horn": 5.2,
|
||||||
|
"Great Sword": 4.8,
|
||||||
|
"Charge Blade": 3.6,
|
||||||
|
"Long Sword": 3.3,
|
||||||
|
"Insect Glaive": 3.1,
|
||||||
|
"Lance": 2.3,
|
||||||
|
"Gunlance": 2.3,
|
||||||
|
"Heavy Bowgun": 1.5,
|
||||||
|
"Sword and Shield": 1.4,
|
||||||
|
"Dual Blades": 1.4,
|
||||||
|
"Light Bowgun": 1.3,
|
||||||
|
"Bow": 1.2,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def all(cls):
|
||||||
|
return cls._multiplier.keys()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def damage_type(cls, weapon_type):
|
||||||
|
if weapon_type in (cls.HAMMER, cls.HUNTING_HORN):
|
||||||
|
return cls.IMPACT
|
||||||
|
elif weapon_type == cls.LANCE:
|
||||||
|
return cls.MIXED
|
||||||
|
elif weapon_type in (cls.LIGHT_BOWGUN, cls.HEAVY_BOWGUN, cls.BOW):
|
||||||
|
return cls.SHOT
|
||||||
|
else:
|
||||||
|
return cls.CUT
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def multiplier(cls, weapon_type):
|
||||||
|
return cls._multiplier[weapon_type]
|
||||||
|
|
||||||
|
|
||||||
|
class WeaponMonsterDamage(object):
|
||||||
|
"""
|
||||||
|
Class for calculating how much damage a weapon does to a monster.
|
||||||
|
Does not include overall monster defense.
|
||||||
|
"""
|
||||||
|
def __init__(self, weapon_row, monster_row, monster_damage_rows, motion,
|
||||||
|
sharp_plus=False, breakable_parts=None):
|
||||||
|
self.weapon = weapon_row
|
||||||
|
self.monster = monster_row
|
||||||
|
self.monster_damage = monster_damage_rows
|
||||||
|
self.motion = motion
|
||||||
|
self.sharp_plus = sharp_plus
|
||||||
|
self.breakable_parts = breakable_parts
|
||||||
|
|
||||||
|
self.damage_map = defaultdict(PartDamage)
|
||||||
|
self.average = 0
|
||||||
|
self.weakness_weighted = 0
|
||||||
|
self.best_weighted = 0
|
||||||
|
self.break_weighted = 0
|
||||||
|
|
||||||
|
self.weapon_type = self.weapon["wtype"]
|
||||||
|
self.true_raw = (self.weapon["attack"]
|
||||||
|
/ WeaponType.multiplier(self.weapon_type))
|
||||||
|
sharp = _parse_sharpness(self.weapon)
|
||||||
|
if sharp_plus:
|
||||||
|
self.sharpness = sharp[1]
|
||||||
|
else:
|
||||||
|
self.sharpness = sharp[0]
|
||||||
|
#print "sharpness=", self.sharpness
|
||||||
|
self.affinity = int(self.weapon["affinity"] or 0)
|
||||||
|
self.damage_type = WeaponType.damage_type(self.weapon_type)
|
||||||
|
self.etype = self.weapon["element"]
|
||||||
|
self.eattack = self.weapon["element_attack"]
|
||||||
|
|
||||||
|
self.parts = []
|
||||||
|
self.break_count = 0
|
||||||
|
|
||||||
|
self.averages = dict(
|
||||||
|
uniform=0,
|
||||||
|
raw=0,
|
||||||
|
element=0,
|
||||||
|
weakpart_raw=0,
|
||||||
|
weakpart_element=0,
|
||||||
|
)
|
||||||
|
self.max_raw_part = (None, 0)
|
||||||
|
self.max_element_part = (None, 0)
|
||||||
|
self._calculate_damage()
|
||||||
|
|
||||||
|
def _calculate_damage(self):
|
||||||
|
for row in self.monster_damage:
|
||||||
|
part = row["body_part"]
|
||||||
|
alt = None
|
||||||
|
m = re.match(r"([^(]+) \(([^)]+)\)", part)
|
||||||
|
if m:
|
||||||
|
part = m.group(1)
|
||||||
|
alt = m.group(2)
|
||||||
|
#print part, alt
|
||||||
|
hitbox = 0
|
||||||
|
hitbox_cut = int(row["cut"])
|
||||||
|
hitbox_impact = int(row["impact"])
|
||||||
|
if self.damage_type == WeaponType.CUT:
|
||||||
|
hitbox = hitbox_cut
|
||||||
|
elif self.damage_type == WeaponType.IMPACT:
|
||||||
|
hitbox = hitbox_impact
|
||||||
|
elif self.damage_type == WeaponType.MIXED:
|
||||||
|
hitbox = max(hitbox_cut, hitbox_impact)
|
||||||
|
|
||||||
|
raw = raw_damage(self.true_raw, self.sharpness, self.affinity,
|
||||||
|
hitbox, self.motion)
|
||||||
|
|
||||||
|
element = 0
|
||||||
|
ehitbox = 0
|
||||||
|
if self.etype in "Fire Water Ice Thunder Dragon".split():
|
||||||
|
ehitbox = int(row[str(self.etype.lower())])
|
||||||
|
element = element_damage(self.eattack, self.sharpness, ehitbox)
|
||||||
|
|
||||||
|
part_damage = self.damage_map[part]
|
||||||
|
part_damage.set_damage(raw, element, hitbox, ehitbox, state=alt)
|
||||||
|
if not part_damage.part:
|
||||||
|
part_damage.part = part
|
||||||
|
if alt is None:
|
||||||
|
if (self.breakable_parts
|
||||||
|
and _part_find(part, self.breakable_parts)):
|
||||||
|
part_damage.breakable = True
|
||||||
|
if hitbox > self.max_raw_part[1]:
|
||||||
|
self.max_raw_part = (part, hitbox)
|
||||||
|
if ehitbox > self.max_element_part[1]:
|
||||||
|
self.max_element_part = (part, ehitbox)
|
||||||
|
for d in self.damage_map.values():
|
||||||
|
if d.is_breakable():
|
||||||
|
self.break_count += 1
|
||||||
|
self.parts = self.damage_map.keys()
|
||||||
|
self.averages["uniform"] = self.uniform()
|
||||||
|
self.averages["raw"] = self.weighted_raw()
|
||||||
|
self.averages["element"] = self.weighted_element()
|
||||||
|
self.averages["weakpart_raw"] = self.weakpart_weighted_raw()
|
||||||
|
self.averages["weakpart_element"] = self.weakpart_weighted_element()
|
||||||
|
self.averages["break_raw"] = self.break_weakpart_raw()
|
||||||
|
self.averages["break_element"] = self.break_weakpart_element()
|
||||||
|
self.averages["break_only"] = self.break_only()
|
||||||
|
|
||||||
|
def uniform(self):
|
||||||
|
average = 0.0
|
||||||
|
for part, damage in self.damage_map.iteritems():
|
||||||
|
average += damage.average()
|
||||||
|
return average / len(self.damage_map)
|
||||||
|
|
||||||
|
def weighted_raw(self):
|
||||||
|
"""
|
||||||
|
Average damage weighted by non-broken raw hitbox. For each part the
|
||||||
|
damage is averaged across broken vs non-broken, weighted by the
|
||||||
|
default of broken for 25% of the hits.
|
||||||
|
"""
|
||||||
|
average = 0.0
|
||||||
|
total_hitbox = 0.0
|
||||||
|
for part, damage in self.damage_map.iteritems():
|
||||||
|
average += damage.average() * damage.hitbox
|
||||||
|
total_hitbox += damage.hitbox
|
||||||
|
if total_hitbox == 0:
|
||||||
|
return 0
|
||||||
|
return average / total_hitbox
|
||||||
|
|
||||||
|
def weighted_element(self):
|
||||||
|
"""
|
||||||
|
Average damage weighted by non-broken element hitbox.
|
||||||
|
"""
|
||||||
|
average = 0.0
|
||||||
|
total_ehitbox = 0.0
|
||||||
|
for part, damage in self.damage_map.iteritems():
|
||||||
|
average += damage.average() * damage.ehitbox
|
||||||
|
total_ehitbox += damage.ehitbox
|
||||||
|
if total_ehitbox == 0:
|
||||||
|
return 0
|
||||||
|
return average / total_ehitbox
|
||||||
|
|
||||||
|
def weakpart_weighted_raw(self, weak_weight=WEAKPART_WEIGHT):
|
||||||
|
other_weight = (1 - weak_weight) / (len(self.parts) - 1)
|
||||||
|
average = 0
|
||||||
|
for part, damage in self.damage_map.iteritems():
|
||||||
|
if part == self.max_raw_part[0]:
|
||||||
|
weight = weak_weight
|
||||||
|
else:
|
||||||
|
weight = other_weight
|
||||||
|
average += damage.average() * weight
|
||||||
|
return average
|
||||||
|
|
||||||
|
def weakpart_weighted_element(self, weak_weight=WEAKPART_WEIGHT):
|
||||||
|
other_weight = (1 - weak_weight) / (len(self.parts) - 1)
|
||||||
|
average = 0
|
||||||
|
for part, damage in self.damage_map.iteritems():
|
||||||
|
if part == self.max_element_part[0]:
|
||||||
|
weight = weak_weight
|
||||||
|
else:
|
||||||
|
weight = other_weight
|
||||||
|
average += damage.average() * weight
|
||||||
|
return average
|
||||||
|
|
||||||
|
def break_weakpart_raw(self):
|
||||||
|
"""
|
||||||
|
Split evenly among break parts and weakest raw part.
|
||||||
|
"""
|
||||||
|
if not self.break_count:
|
||||||
|
return 0
|
||||||
|
average = 0.0
|
||||||
|
count = self.break_count + 1
|
||||||
|
for part, damage in self.damage_map.iteritems():
|
||||||
|
if part == self.max_raw_part[0]:
|
||||||
|
average += damage.average()
|
||||||
|
if damage.is_breakable():
|
||||||
|
count -= 1
|
||||||
|
elif damage.is_breakable():
|
||||||
|
# for breaks, assume attack until broken, unless it's a
|
||||||
|
# weak part and covered above
|
||||||
|
average += damage.total
|
||||||
|
return average / count
|
||||||
|
|
||||||
|
def break_weakpart_element(self):
|
||||||
|
"""
|
||||||
|
Split evenly among break parts and weakest element part.
|
||||||
|
"""
|
||||||
|
if not self.break_count:
|
||||||
|
return 0
|
||||||
|
average = 0.0
|
||||||
|
count = self.break_count + 1
|
||||||
|
for part, damage in self.damage_map.iteritems():
|
||||||
|
if part == self.max_element_part[0]:
|
||||||
|
# If weakpart is also a break, assume continue attacking
|
||||||
|
# even after broken
|
||||||
|
average += damage.average()
|
||||||
|
if damage.is_breakable():
|
||||||
|
count -= 1
|
||||||
|
elif damage.is_breakable():
|
||||||
|
# for breaks that aren't the weakpart, assume attack until
|
||||||
|
# broken and then go back to weakpart
|
||||||
|
average += damage.total
|
||||||
|
return average / count
|
||||||
|
|
||||||
|
def break_only(self):
|
||||||
|
"""
|
||||||
|
Split evenly among break parts. If there are breaks that are weak
|
||||||
|
to element but not to raw or vice versa, this will represent that
|
||||||
|
when comparing weapons.
|
||||||
|
"""
|
||||||
|
if not self.break_count:
|
||||||
|
return 0
|
||||||
|
average = 0.0
|
||||||
|
for part, damage in self.damage_map.iteritems():
|
||||||
|
if damage.is_breakable():
|
||||||
|
# attack until broken, then move to next break
|
||||||
|
average += damage.total
|
||||||
|
return average / self.break_count
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.damage_map[key]
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self.parts
|
||||||
|
|
||||||
|
|
||||||
|
class PartDamageState(object):
|
||||||
|
def __init__(self, raw, element, hitbox, ehitbox, state=None):
|
||||||
|
self.raw = raw
|
||||||
|
self.element = element
|
||||||
|
self.hitbox = hitbox
|
||||||
|
self.ehitbox = ehitbox
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
|
||||||
|
class PartDamage(object):
|
||||||
|
"""
|
||||||
|
Class to represent the damage done to a single hitzone on a monster,
|
||||||
|
default state and alternate state (broken, enraged, etc).
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.states = dict()
|
||||||
|
self.part = None
|
||||||
|
self.breakable = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def raw(self):
|
||||||
|
return self.states[None].raw
|
||||||
|
|
||||||
|
@property
|
||||||
|
def element(self):
|
||||||
|
return self.states[None].element
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hitbox(self):
|
||||||
|
return self.states[None].hitbox
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ehitbox(self):
|
||||||
|
return self.states[None].ehitbox
|
||||||
|
|
||||||
|
@property
|
||||||
|
def break_raw(self):
|
||||||
|
if "Break Part" in self.states:
|
||||||
|
return self.states["Break Part"].raw
|
||||||
|
else:
|
||||||
|
return self.raw
|
||||||
|
|
||||||
|
@property
|
||||||
|
def break_element(self):
|
||||||
|
if "Break Part" in self.states:
|
||||||
|
return self.states["Break Part"].element
|
||||||
|
else:
|
||||||
|
return self.element
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rage_raw(self):
|
||||||
|
if "Enraged" in self.states:
|
||||||
|
return self.states["Enraged"].raw
|
||||||
|
else:
|
||||||
|
return self.raw
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rage_element(self):
|
||||||
|
if "Enraged" in self.states:
|
||||||
|
return self.states["Enraged"].element
|
||||||
|
else:
|
||||||
|
return self.element
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total(self):
|
||||||
|
return self.raw + self.element
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_break(self):
|
||||||
|
return self.break_raw + self.break_element
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_rage(self):
|
||||||
|
return self.rage_raw + self.rage_element
|
||||||
|
|
||||||
|
def break_diff(self):
|
||||||
|
return self.total_break - self.total
|
||||||
|
|
||||||
|
def rage_diff(self):
|
||||||
|
return self.total_rage - self.total
|
||||||
|
|
||||||
|
def is_breakable(self):
|
||||||
|
# If the part has a hitbox with different damage in the break
|
||||||
|
# rows from the db, or if it's explicitly marked as breakable
|
||||||
|
# (done by checking hunt rewards for breaks).
|
||||||
|
return self.break_diff() > 0 or self.breakable
|
||||||
|
|
||||||
|
def average(self, break_weight=0.25, rage_weight=0.5):
|
||||||
|
if self.break_diff():
|
||||||
|
assert not self.rage_diff()
|
||||||
|
return self.average_break(break_weight)
|
||||||
|
else:
|
||||||
|
return self.average_rage(rage_weight)
|
||||||
|
|
||||||
|
def average_break(self, break_weight=0.25):
|
||||||
|
return (self.total_break * break_weight
|
||||||
|
+ self.total * (1 - break_weight))
|
||||||
|
|
||||||
|
def average_rage(self, rage_weight=0.5):
|
||||||
|
return (self.total_rage * rage_weight
|
||||||
|
+ self.total * (1 - rage_weight))
|
||||||
|
|
||||||
|
def set_damage(self, raw, element, hitbox, ehitbox, state=None):
|
||||||
|
if state == "Without Hide":
|
||||||
|
state = "Break Part"
|
||||||
|
self.states[state] = PartDamageState(raw, element,
|
||||||
|
hitbox, ehitbox, state)
|
||||||
|
|
||||||
|
|
||||||
|
def _part_find(part, breaks):
|
||||||
|
if (part == "Wing" and "Wing" not in breaks
|
||||||
|
and "Talon" in breaks):
|
||||||
|
# for Teostra
|
||||||
|
return "Talon"
|
||||||
|
if (part == "Head" and "Head" not in breaks
|
||||||
|
and "Horn" in breaks):
|
||||||
|
# for Fatalis
|
||||||
|
return "Horn"
|
||||||
|
if (part == "Winglegs" and "Winglegs" not in breaks
|
||||||
|
and "Wing Leg" in breaks):
|
||||||
|
# for Gore
|
||||||
|
return "Wing Leg"
|
||||||
|
#print "part_find", part, breaks
|
||||||
|
matches = difflib.get_close_matches(part, breaks, 1, 0.8)
|
||||||
|
if matches:
|
||||||
|
return matches[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def element_attack_up(value):
|
||||||
|
return value * 1.1
|
||||||
|
|
||||||
|
|
||||||
|
def element_x_attack_up(value, level=1):
|
||||||
|
value = value * (1 + .05 * level)
|
||||||
|
if level == 1:
|
||||||
|
value += 40
|
||||||
|
elif level == 2:
|
||||||
|
value += 60
|
||||||
|
elif level == 3:
|
||||||
|
value += 90
|
||||||
|
else:
|
||||||
|
raise ValueError("level must be 1, 2, or 3")
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_sharpness(weapon_row):
|
||||||
|
"""
|
||||||
|
Parse the sharpness field from a weapon row, to determine
|
||||||
|
the max sharpness of the weapon with and without sharpness +1.
|
||||||
|
"""
|
||||||
|
db_values = weapon_row["sharpness"].split(" ")
|
||||||
|
sharpness = [Sharpness.RED, Sharpness.RED]
|
||||||
|
for i, db_value in enumerate(db_values):
|
||||||
|
values = [int(s) for s in db_value.split(".")]
|
||||||
|
for s in Sharpness.ALL:
|
||||||
|
if values[s] > 0:
|
||||||
|
sharpness[i] = s
|
||||||
|
return sharpness
|
||||||
Loading…
Reference in new issue