def func(self): """Run the @check command""" caller = self.caller retainer = None is_retainer = "retainer" in self.switches flub = "flub" in self.switches if not self.args: if is_retainer: caller.msg( "Usage: @check/retainer <id>|<stat>[+<skill>][ at <difficulty number>][=receiver1,receiver2,etc]" ) return else: caller.msg( "Usage: @check <stat>[+<skill>][ at <difficulty number>][=receiver1,receiver2,etc]" ) return args = self.lhs if self.rhs else self.args args = args.lower() quiet = bool(self.rhs) # NOTE: The order of calls here matters, due to str.split() usage. # Retainer ID -> Difficulty -> Stat/Skill. try: if is_retainer: args, retainer_id = self._extract_retainer_id(args) retainer = caller.player_ob.retainers.get(id=retainer_id) args, difficulty = self._extract_difficulty(args) stat, skill = self._extract_stat_skill(args) except ValueError as err: caller.msg(str(err)) return except Agent.DoesNotExist: caller.msg("No retainer found with ID %s." % retainer_id) return stats_and_skills.do_dice_check( caller=caller, retainer=retainer, stat=stat, skill=skill, difficulty=difficulty, quiet=quiet, flub=flub, ) if quiet: self._send_quiet_roll_msg()
def roll_flee_success(self): """ Determines if we can flee. Called during initiative. If successful, we're added to the list of people who can now gtfo. If someone is covering our retreat, we succeed automatically. We must outroll every player attempting to block us to flee otherwise. """ if self.covered_by: return True myroll = do_dice_check(self.character, stat="dexterity", skill="dodge", difficulty=0) for guy in self.blocker_list: if myroll < do_dice_check(guy, stat="dexterity", skill="brawl", difficulty=0): return False return True
def roll_attack(self, target, penalty=0): """ Returns our roll to hit with an attack. Half of our roll is randomized. """ autohit = self.damage is not None diff = 2 # base difficulty before mods penalty -= self.atk_modifiers diff += penalty diff += self.difficulty_mod if not autohit: roll = do_dice_check(self.attacker, stat=self.attack_stat, skill=self.attack_skill, difficulty=diff) else: roll_object = Roll() roll_object.character_name = self.attacker_name if self.attack_skill: roll_object.skills = {self.attack_skill: self.attack_skill_value} else: roll_object.stat_keep = True roll_object.skill_keep = False roll_object.stats = {self.attack_stat: self.attack_stat_value} roll = roll_object.roll() if self.attacker_is_npc: roll = self.modify_difficulty_by_risk(roll) if roll > 2: roll = (roll//2) + randint(0, (roll//2)) if not autohit: roll += self.get_modifier(target, RollModifier.ATTACK) return roll
def roll_damage(self, target, penalty=0, dmgmult=1.0): """ Returns our roll for damage against target. If damage is to be enhanced, (dmgmult > 1.0) it is done pre-mitigation, here. """ keep_dice = self.handler.weapon_damage + 1 try: keep_dice += self.attacker.attributes.get(self.damage_stat)//2 except (TypeError, AttributeError, ValueError): pass if keep_dice < 3: keep_dice = 3 diff = 0 # base difficulty before mods diff += penalty damage = do_dice_check(self.attacker, stat=self.handler.damage_stat, stat_keep=True, difficulty=diff, bonus_dice=self.handler.weapon_damage, keep_override=keep_dice) damage += self.handler.flat_damage_bonus if dmgmult > 1.0: # if dmg is enhanced, it is done pre-mitigation damage = int(damage * dmgmult) if self.attacker_is_npc: damage = self.modify_difficulty_by_risk(damage) if damage <= 0: damage = 1 # 3/4ths of our damage is purely random damage = damage//4 + randint(0, ((damage * 3)//4)+1) damage += self.get_modifier(target, RollModifier.DAMAGE) return damage
def roll_initiative(self): """Rolls and stores initiative for the character.""" self.initiative = do_dice_check(self.character, stat_list=["dexterity", "composure"], stat_keep=True, difficulty=0) self.tiebreaker = randint(1, 1000000000)
def roll_fatigue(self): """ Chance of incrementing our fatigue penalty. The difficulty is the number of combat actions we've taken plus our armor penalty. """ if self.combat_handler.multiple: # figure out way later to track fatigue for units return if self.character.db.never_tire: return armor_penalty = 0 if hasattr(self.character, 'armor_penalties'): armor_penalty = self.character.armor_penalties penalty = armor_penalty self.num_actions += 1 + (0.12 * armor_penalty) penalty += self.num_actions + 25 keep = self.fatigue_soak penalty = int(penalty) penalty = penalty/2 + randint(0, penalty/2) myroll = do_dice_check(self.character, stat_list=["strength", "stamina", "dexterity", "willpower"], skill="athletics", keep_override=keep, difficulty=int(penalty), divisor=2) myroll += randint(0, 25) if myroll < 0 and self.fatigue_gained_this_turn < 1: self._fatigue_penalty += 0.5 self.fatigue_gained_this_turn += 0.5
def roll_for_fame(self): """ Rolls for amount of fame the item generates, minimum 2 fame. The fashion model's social clout and skill check of composure + performance is made exponential to be an enormous swing in the efficacy of fame generated: Someone whose roll+social_clout is 50 will be hundreds of times as effective as someone who flubs the roll. """ from world.stats_and_skills import do_dice_check char = self.fashion_model.player.char_ob roll = do_dice_check(caller=char, stat="composure", skill="performance", difficulty=30) roll = pow(max((roll + char.social_clout * 5), 1), 1.5) percentage = max(roll / 100.0, 0.01) level_mod = self.fashion_item.item_data.recipe.level / 6.0 percentage *= max(level_mod, 0.01) percentage *= max((self.fashion_item.item_data.quality_level / 40.0), 0.01) percentage = max(percentage, 0.2) # they get either their percentage of the item's worth, their modified roll, or 4, whichever is highest self.fame = min( max(int(self.fashion_item.item_worth * percentage), max(int(roll), 4)), self.FAME_CAP, ) self.save()
def func(self): if not self.args: self.msg("You must provide a direction to assist the party with!") return last_assist = self.caller.db.shardhaven_last_assist if last_assist and time.time() - last_assist < 1800: self.msg("You cannot assist through a direction again yet.") return exit_objs = self.caller.search(self.args, quiet=True, global_search=False, typeclass='typeclasses.exits.ShardhavenInstanceExit') if not exit_objs or len(exit_objs) == 0: self.msg("There doesn't appear to be a shardhaven exit by that name!") return if len(exit_objs) > 1: self.msg("That matches too many exits!") return exit_obj = exit_objs[0] haven_exit = exit_obj.haven_exit if not haven_exit: self.msg("Something is horribly wrong with that exit; it's not set up as a Shardhaven exit.") return if not haven_exit.obstacle: self.msg("There's no obstacle in that direction to assist with!") return self.caller.db.shardhaven_last_assist = time.time() roll = do_dice_check(self.caller, "wits", "leadership", 30, quiet=False) haven_exit.modify_diff(amount=roll / 2, reason="%s assisted with a leadership roll" % self.caller.name) self.caller.location.msg_contents("%s attempts to assist the party with the obstacle to the %s, " "adjusting the difficulty." % (self.caller.name, exit_obj.direction_name))
def func(self): if not self.args: self.msg("You must provide a direction to sneak!") return exit_objs = self.caller.search(self.args, quiet=True, global_search=False, typeclass='typeclasses.exits.ShardhavenInstanceExit') if not exit_objs or len(exit_objs) == 0: self.msg("There doesn't appear to be a shardhaven exit by that name!") return if len(exit_objs) > 1: self.msg("That matches too many exits!") return exit_obj = exit_objs[0] if not exit_obj.passable(self.caller): self.msg("You cannot sneak that way; there's still an obstacle there you have to pass!") return roll = do_dice_check(self.caller, "dexterity", "stealth", 25, quiet=False) if roll < 0: self.caller.location.msg_contents("%s attempts to sneak %s, but makes noise as they do so!" % (self.caller.name, exit_obj.direction_name)) elif roll > 1: self.caller.location.msg_contents("%s moves stealthily through %s." % (self.caller.name, exit_obj.direction_name)) self.caller.ndb.shardhaven_sneak_value = roll self.caller.execute_cmd(exit_obj.direction_name)
def do_crafting_roll(char, recipe, diffmod=0, diffmult=1.0, room=None): diff = int(recipe.difficulty * diffmult) - diffmod ability = get_ability_val(char, recipe) skill = recipe.skill if skill in ("all", "any"): skill = get_highest_crafting_skill(char) stat = "luck" if char.traits.luck > char.traits.dexterity else "dexterity" can_crit = False try: if char.roster.roster.name == "Active": can_crit = True except AttributeError: pass # use real name if we're not present (someone using our shop, for example). If we're here, use masked name real_name = char.location != room return do_dice_check( char, stat=stat, difficulty=diff, skill=skill, bonus_dice=ability, quiet=False, announce_room=room, can_crit=can_crit, use_real_name=real_name, )
def roll_for_fame(self): """ Rolls for amount of fame the item generates, minimum 2 fame. The fashion model's social clout and skill check of composure + performance is made exponential to be an enormous swing in the efficacy of fame generated: Someone whose roll+social_clout is 50 will be hundreds of times as effective as someone who flubs the roll. """ from world.stats_and_skills import do_dice_check char = self.fashion_model.player.char_ob total_weight = 0 for obj in char.location.contents: if obj.is_typeclass("typeclasses.characters.Character") and obj != char: self.characters.add(obj) total_weight += 11 - (obj.db.social_rank or 10) self.multiplier = min(total_weight / 25.0, 7.) roll = do_dice_check(caller=char, stat="composure", skill="performance", difficulty=30) roll = pow(max((roll + char.social_clout * 3), 1), 1.5) percentage = max(roll/100.0, 0.01) level_mod = self.fashion_item.recipe.level/6.0 percentage *= max(level_mod, 0.01) percentage *= max((self.fashion_item.quality_level/40.0), 0.01) percentage *= self.multiplier # they get either their percentage of the item's worth, their modified roll, or 4, whichever is highest self.fame = min(max(int(self.fashion_item.item_worth * percentage), max(int(roll), 4)), self.FAME_CAP) self.save()
def intimidate(caller, diff): result = do_dice_check( caller, stat="command", skill="intimidation", difficulty=diff ) if result > 0: return True return False
def investigate(rumor, caller, diff): result = do_dice_check( caller, stat="perception", skill="investigation", difficulty=diff ) if result > 0: senders = rumor.db_sender_objects.all() return senders
def recovery_test(self, diff_mod=0, free=False): """ A mechanism for healing characters. Whenever they get a recovery test, they heal the result of a willpower+stamina roll, against a base difficulty of 0. diff_mod can change that difficulty value, and with a higher difficulty can mean it can heal a negative value, resulting in the character getting worse off. We go ahead and change the player's health now, but leave the result of the roll in the caller's hands to trigger other checks - death checks if we got worse, unconsciousness checks, whatever. """ diff = 0 + diff_mod roll = do_dice_check(self, stat_list=["willpower", "stamina"], difficulty=diff) if roll > 0: self.msg("You feel better.") else: self.msg("You feel worse.") applied_damage = self.dmg - roll # how much dmg character has after the roll if applied_damage < 0: applied_damage = 0 # no remaining damage self.db.damage = applied_damage if not free: self.db.last_recovery_test = time.time() return roll
def get_refund_chance(self): """Gets our chance of material refund based on a skill check""" roll = do_dice_check(self.caller, stat="dexterity", skill="legerdemain", quiet=False) return max(roll, 1)
def train_agent(self, trainer, conditioning): """ Gives xp to this agent if they haven't been trained yet this week. The skill used to train them is based on our type - animal ken for animals, teaching for non-animals. """ # use real name if we're not present. If we're here, use masked name use_real_name = self.location != trainer.location name = trainer.key if use_real_name else str(trainer) self.conditioning += conditioning roll = do_dice_check(trainer, stat="command", skill=self.training_skill, difficulty=self.training_difficulty, quiet=False, use_real_name=use_real_name) if roll < 0: trainer.msg("You have failed to teach them anything.") msg = "%s has attempted to train %s, but utterly failed to teach them anything." % ( name, self) else: self.agent.xp += roll self.agent.save() trainer.msg("You have trained %s, giving them %s xp." % (self, roll)) msg = "%s has trained %s, giving them %s xp." % (name, self, roll) self.conditioning = 0 self.inform_owner(msg) print "Training log: %s" % msg
def get_refund_chance(): """Gets our chance of material refund based on a skill check""" from world.stats_and_skills import do_dice_check roll = do_dice_check(caller, stat="dexterity", skill="legerdemain", quiet=False) return max(roll, 1)
def sensing_check(self, difficulty=15, invis=False, allow_wake=False): """ See if the character detects something that is hiding or invisible. The difficulty is supplied by the calling function. Target can be included for additional situational """ if not self.conscious and not allow_wake: return -100 roll = do_dice_check(self, stat="perception", stat_keep=True, difficulty=difficulty) return roll
def sense_ambush(self, attacker, sneaking=False, invis=False): """ Returns the dice roll of our attempt to detect an ambusher. """ diff = 0 # base difficulty if sneaking: diff += 15 sense = self.char.sensing_check(difficulty=0, invis=invis) stealth = do_dice_check(attacker, stat="dexterity", skill="stealth") return sense - stealth
def search_for_deal_roll(self, material, amount): """Does the roll to search for a deal. Positive roll * 5000 is how much of the value of the material they're able to buy/sell. Args: material: The type of material we're looking for amount: Max amount much we're looking to buy/sell Returns: The amount we're able to buy/sell and a modifier to haggling rolls Raises: HaggleError if they fail to find a deal. """ from math import ceil skill = self.caller.traits.get_highest_skill( ("economics", "streetwise")).name difficulty = 20 bonus = 0 roll = do_dice_check( self.caller, skill=skill, stat="perception", difficulty=difficulty, quiet=False, ) if roll < 0: raise HaggleError( "You failed to find anyone willing to deal with you at all.") if material in HaggledDeal.VALID_RESOURCES: # resources are worth 500 each value_per_object = 500 else: value_per_object = round(pow(material.value, 1.05)) value_we_found = roll * 5000.0 value_for_amount = value_we_found / value_per_object if value_for_amount < 1.0: penalty = int((1.0 - value_for_amount) * -100) amount_found = 1 self.msg( "You had trouble finding a deal for such a valuable item. " "Haggling rolls will have a penalty of %s." % penalty) return amount_found, penalty # minimum of 1 amount_found = max(int(ceil(value_we_found / value_per_object)), 1) if amount_found > amount: bonus = min(amount_found - amount, 25) self.msg( "Due to your success in searching for a deal, haggling rolls will have a bonus of %s." % bonus) return min(amount, amount_found), bonus
def noble_discovery_check(self): """Checks if a noble loses fame for haggling""" rank = self.caller.db.social_rank or 10 if rank > 6: return msg = "Engaging in crass mercantile haggling is considered beneath those of high social rank." if do_dice_check(stat="wits", skill="stealth", difficulty=30) < 1: fame_loss = self.caller.player_ob.Dominion.assets.fame // 100 if not fame_loss: msg += " You were noticed, but fortunately, so few people know of you that it hardly matters." else: msg += " Unfortunately, you were noticed and lose %d fame." % fame_loss self.caller.player_ob.Dominion.assets.fame -= fame_loss self.caller.player_ob.Dominion.assets.save() else: msg += " Fortunately, no one noticed this time." self.caller.msg(msg)
def recovery_test(self, diff_mod=0, free=False): """ A mechanism for healing characters. Whenever they get a recovery test, they heal the result of a willpower+stamina roll, against a base difficulty of 0. diff_mod can change that difficulty value, and with a higher difficulty can mean it can heal a negative value, resulting in the character getting worse off. We go ahead and change the player's health now, but leave the result of the roll in the caller's hands to trigger other checks - death checks if we got worse, unconsciousness checks, whatever. """ # no helping us if we're dead if self.db.health_status == "dead": return diff = 0 + diff_mod roll = do_dice_check(self, stat_list=["willpower", "stamina"], difficulty=diff) self.change_health(roll) if not free: self.db.last_recovery_test = time.time() return roll
def haggle(self): """Alters the terms of our deal. Pray they do not alter it further.""" if not self.caller.player_ob.pay_action_points(5): return self.noble_discovery_check() difficulty = randint(-15, 65) - self.roll_bonus clout = self.caller.social_clout if clout > 0: difficulty -= randint(0, clout) roll = do_dice_check( self.caller, stat="charm", skill_list=["haggling", "haggling", "haggling", "streetwise"], difficulty=difficulty) if roll <= self.discount_roll: self.caller.msg("You failed to find a better deal.\n%s" % self.display()) else: self.discount_roll = roll self.save() self.caller.msg("You have found a better deal:\n%s" % self.display())
def haggle(self): """Alters the terms of our deal. Pray they do not alter it further.""" if not self.caller.player_ob.pay_action_points(5): return self.noble_discovery_check() difficulty = randint(1, 50) - self.roll_bonus clout = self.caller.social_clout if clout > 0: difficulty -= randint(0, clout) try: prest_factor = int(self.caller.player_ob.Dominion.assets.prestige_mod) if prest_factor > 0: difficulty -= randint(0, prest_factor) except (AttributeError, ValueError, TypeError): pass roll = do_dice_check(self.caller, stat="charm", skill="haggling", difficulty=difficulty) if roll <= self.discount_roll: self.caller.msg("You failed to find a better deal.\n%s" % self.display()) else: self.discount_roll = roll self.save() self.caller.msg("You have found a better deal:\n%s" % self.display())
def func(self): """Run the @check command""" caller = self.caller skill = None maximum_difference = 100 flub = "flub" in self.switches if not self.args: caller.msg("Usage: @check <stat>[+<skill>][ at <difficulty number>][=receiver1,receiver2,etc]") return args = self.lhs if self.rhs else self.args args = args.lower() # if args contains ' at ', then we split into halves. otherwise, it's default of 6 diff_list = args.split(' at ') difficulty = stats_and_skills.DIFF_DEFAULT if len(diff_list) > 1: if not diff_list[1].isdigit() or not 0 < int(diff_list[1]) < maximum_difference: caller.msg("Difficulty must be a number between 1 and %s." % maximum_difference) return difficulty = int(diff_list[1]) args = diff_list[0] arg_list = args.split("+") if len(arg_list) > 1: skill = arg_list[1].strip() stat = arg_list[0].strip() matches = stats_and_skills.get_partial_match(stat, "stat") if not matches or len(matches) > 1: caller.msg("There must be one unique match for a character stat. Please check spelling and try again.") return # get unique string that matches stat stat = matches[0] if skill: matches = stats_and_skills.get_partial_match(skill, "skill") if not matches: # check for a skill not in the normal valid list if skill in caller.db.skills: matches = [skill] else: caller.msg("No matches for a skill by that name. Check spelling and try again.") return if len(matches) > 1: caller.msg("There must be one unique match for a character skill. Please check spelling and try again.") return skill = matches[0] quiet = bool(self.rhs) stats_and_skills.do_dice_check(caller, stat, skill, difficulty, quiet=quiet, flub=flub) if quiet: namelist = [name.strip() for name in self.rhs.split(",") if caller.search(name.strip(), use_nicks=True)] roll_msg = Roll.build_msg(caller.ndb.last_roll) + " " + "(Private roll sent to: %s)" % ", ".join(namelist) caller.msg(roll_msg) # they have a recipient list; only tell those people (and GMs) for name in namelist: recipient = caller.search(name, use_nicks=True) recipient.msg(roll_msg, options={'roll':True}) # GMs always get to see rolls. staff_list = [x for x in caller.location.contents if x.check_permstring("Builders")] for GM in staff_list: GM.msg("{w(Private roll) {n" + roll_msg) return
def take_damage(self, victim, dmg): """ This is where the consequences of final damage are applied to a victim. They can be knocked unconscious or killed, and any combat they're in is informed. Characters who are incapacitated are moved to the appropriate dictionary. Health rating is 10xsta + 10. Unconsciousness checks are after health rating is exceeded. When damage is double health rating, death checks begin. Player characters will always fall unconscious first, then be required to make death checks after further damage, with the exception of extraordinary situations. NPCs, on the other hand, can be killed outright. Args: victim: Our target dmg: The amount of damage after all mitgation/reductions """ allow_one_shot = True affect_real_dmg = self.affect_real_dmg can_kill = self.can_kill if self.combat: allow_one_shot = self.combat.ndb.random_deaths loc = victim.location # some flags so messaging is in proper order knock_uncon = False kill = False remove = False glass_jaw = victim.glass_jaw is_npc = victim.is_npc message = "" # max hp is (stamina * 10) + 10 max_hp = victim.max_hp # apply AE damage to multinpcs if we're cleaving if self.cleaving and hasattr(victim, 'ae_dmg'): victim.ae_dmg += dmg victim.change_health(-dmg, quiet=True, affect_real_dmg=affect_real_dmg, wake=False) grace_period = False # one round delay between incapacitation and death for PCs if allowed if victim.dmg > max_hp: # if we're not incapacitated, we start making checks for it if victim.conscious and not victim.sleepless: # check is sta + willpower against % dmg past uncon to stay conscious if not glass_jaw: diff = int((float(victim.dmg - max_hp)/max_hp) * 100) consc_check = do_dice_check(victim, stat_list=["stamina", "willpower"], skill="survival", stat_keep=True, difficulty=diff, quiet=False) else: consc_check = -1 if consc_check >= 0: if not self.private: message = "%s remains capable of fighting." % victim grace_period = True # we can't be killed if we succeeded this check to remain standing # we're done, so send the message for the attack else: knock_uncon = True # for PCs who were knocked unconscious this round if not is_npc and not grace_period and not allow_one_shot: grace_period = True # if allow_one_shot is off, we can't be killed yet # PC/NPC who was already unconscious before attack, or an NPC who was knocked unconscious by our attack if not grace_period: # we are allowed to kill the character dt = victim.death_threshold diff = int((float(victim.dmg - int(dt * max_hp))/int(dt * max_hp)) * 100) if affect_real_dmg and not is_npc and not glass_jaw: diff = self.modify_difficulty_by_risk(diff) if diff < 0: diff = 0 # npcs always die. Sucks for them. if not glass_jaw and do_dice_check(victim, stat_list=["stamina", "willpower"], skill="survival", stat_keep=True, difficulty=diff, quiet=False) >= 0: message = "%s remains alive, but close to death." % victim if victim.combat.multiple: # was incapacitated but not killed, but out of fight and now we're on another targ if affect_real_dmg: victim.real_dmg = victim.ae_dmg else: victim.temp_dmg = victim.ae_dmg elif not victim.combat.multiple: if affect_real_dmg: kill = can_kill # remove a 'killed' character from combat whether it was a real death or fake remove = True else: if affect_real_dmg: kill = can_kill else: knock_uncon = True if loc and message: loc.msg_contents(message, options={'roll': True}) if knock_uncon: victim.fall_asleep(uncon=True, verb="incapacitated", affect_real_dmg=affect_real_dmg) if kill: victim.death_process(affect_real_dmg=affect_real_dmg) if victim.combat.multiple: try: if victim.quantity <= 0: remove = True except AttributeError: pass if self.combat and remove: self.combat.remove_combatant(victim)
def roll_defense(self, target, penalty=0): """ Returns target's roll to avoid being hit. We use the highest roll out of parry, block, and dodge. Half of our roll is then randomized. """ if not target.conscious: return -self.AUTO_HIT defense = target.combat # making defense easier than attack to slightly lower combat lethality diff = -2 # base difficulty before mods penalty -= defense.defense_modifier diff += penalty if defense.state: defense.state.times_attacked += 1 total = None def change_total(current_total, new_roll): """Helper function to change total of defense rolls""" if new_roll >= 2: new_roll = (new_roll//2) + randint(0, (new_roll//2)) if not current_total: current_total = new_roll elif new_roll > 0: if current_total > new_roll: current_total += new_roll//2 else: current_total = (current_total//2) + new_roll elif new_roll > current_total: current_total = (current_total + new_roll)//2 return current_total, new_roll if self.can_be_parried and defense.can_parry: parry_diff = diff + 10 parry_roll = int(do_dice_check(target, stat=defense.attack_stat, skill=self.attack_skill, difficulty=parry_diff)) if parry_roll > 1: parry_roll = (parry_roll//2) + randint(0, (parry_roll//2)) total = parry_roll else: parry_roll = -1000 if self.can_be_blocked and defense.can_block: try: block_diff = diff + defense.dodge_penalty except (AttributeError, TypeError, ValueError): block_diff = diff block_roll = int(do_dice_check(target, stat="dexterity", skill="dodge", difficulty=block_diff)) total, block_roll = change_total(total, block_roll) else: block_roll = -1000 if self.can_be_dodged and defense.can_dodge: # dodging is easier than parrying dodge_diff = diff - 10 try: dodge_diff += defense.dodge_penalty except (AttributeError, TypeError, ValueError): pass dodge_roll = int(do_dice_check(target, stat="dexterity", skill="dodge", difficulty=dodge_diff)) total, dodge_roll = change_total(total, dodge_roll) else: dodge_roll = -1000 if total is None: total = -1000 # return our highest defense roll if parry_roll > block_roll and parry_roll > dodge_roll: defense.last_defense_method = "parry" elif block_roll > parry_roll and block_roll > dodge_roll: defense.last_defense_method = "block" else: defense.last_defense_method = "dodge" return total