def _resolve_shield_block(self, attack_result: ContestResult, damage_mult: float) -> Tuple[bool, float]: blocks = (block for block in self.defender.get_shield_blocks() if block.source not in self.used_sources) self.use_shield = self.defender.mind.get_melee_block( self.separation, blocks) if self.use_shield is not None and self.use_shield.can_block( self.separation): block_damage_mult = get_parry_damage_mult(self.use_attack.force, self.use_shield.force) if block_damage_mult < damage_mult: modifier = get_block_difficulty(self.defender).to_modifier( ) + self.use_shield.contest_modifier shield_result = ContestResult(self.defender, self.use_shield.combat_test, modifier) block_result = OpposedResult(shield_result, attack_result) print( f'{self.defender} attempts to block with {self.use_shield.source}!' ) print(block_result.format_details()) self.used_sources.append(self.use_shield.source) if block_result.success: return True, block_damage_mult return False, damage_mult
def _resolve_evade_knockdown(self): if self.defender.stance > Stance.Prone: acro_test = ContestResult( self.defender, SKILL_ACROBATICS, self.defender.get_resist_knockdown_modifier()) acro_result = OpposedResult(acro_test, self.attack_result) print(acro_result.format_details()) if not acro_result.success: self.defender.knock_down()
def apply(self) -> None: opponent = self.combat.melee.get_opponent(self.user) modifier = opponent.get_resist_knockdown_modifier(+1) acro_result = ContestResult(opponent, SKILL_ACROBATICS, modifier) knockdown_result = OpposedResult(self.combat.attack_result, acro_result) print(knockdown_result.format_details()) if knockdown_result.success: opponent.knock_down()
def _injury_check(self, amount: float, attack_result: ContestResult) -> None: threshold = 4 / 3 * self.size * self.parent.max_health injury_steps = int(amount / threshold - 1.0) if injury_steps > 0: grade = DifficultyGrade.VeryEasy.get_step(injury_steps) injury_test = ContestResult(self.parent, SKILL_ENDURANCE, grade.to_modifier()) injury_result = OpposedResult( injury_test, attack_result ) if attack_result is not None else UnopposedResult(injury_test) print(injury_result.format_details()) if not injury_result.success: print(f'{self.parent} suffers an injury to the {self}.') self.injure_part()
def _resolve_melee_defence(self) -> None: attack_modifier = get_combat_difficulty(self.attacker).to_modifier() defend_modifier = get_combat_difficulty(self.defender).to_modifier() attack_result = ContestResult(self.attacker, self.use_attack.combat_test, attack_modifier) defend_result = ContestResult(self.defender, self.use_defence.combat_test, defend_modifier) primary_result = OpposedResult(attack_result, defend_result) print( f'{self.attacker} attacks {self.defender} at {self.separation} distance: {self.use_attack.name} vs {self.use_defence.name}!' ) print(primary_result.format_details()) damage_mult = 1.0 if not primary_result.success: damage_mult = get_parry_damage_mult(self.use_attack.force, self.use_defence.force) # defender may attempt to block is_blocking, damage_mult = self._resolve_shield_block( attack_result, damage_mult) self.attacker_crit = 0 self.defender_crit = 0 if primary_result.success: self.attacker_crit = primary_result.crit_level else: self.defender_crit = primary_result.crit_level hitloc = None if damage_mult > 0: hitloc = get_random_hitloc(self.defender) self.primary_result = primary_result self.is_blocking = is_blocking self.hitloc = hitloc self.damage_mult = damage_mult self.damage = self.use_attack.damage self.armpen = self.use_attack.armpen self.used_sources.append(self.use_attack.source) self.used_sources.append(self.use_defence.source)
def apply_wounds(self, amount: float, bodypart: BodyPart, attack_result: Optional[ContestResult] = None) -> None: prev_health = self.health self._health -= amount if self.health <= 0 < prev_health: self.stun(can_defend=False) if self.health <= -self.max_health: # unlikely to die instantly if taking damage to a non-vital body part grade = DifficultyGrade.Standard if bodypart.is_vital( ) else DifficultyGrade.VeryEasy injury_test = ContestResult(self, SKILL_ENDURANCE, grade.to_modifier()) injury_result = OpposedResult( injury_test, attack_result ) if attack_result is not None else UnopposedResult(injury_test) print(injury_result.format_details()) if not injury_result.success: self.kill() print(f'{self} is killed!') elif self.is_conscious(): self.set_conscious(False) print(f'{self} is incapacitated!') elif self.health <= 0 and self.is_conscious(): injury_test = ContestResult(self, SKILL_ENDURANCE) injury_result = OpposedResult( injury_test, attack_result ) if attack_result is not None else UnopposedResult(injury_test) print(f'{self} is seriously wounded!') print(injury_result.format_details()) if not injury_result.success: self.set_conscious(False) print(f'{self} is incapacitated!')
def resolve(self) -> Optional[Action]: melee = self.protagonist.get_melee_combat(self.opponent) verb = 'close' if self.target_range <= melee.get_separation( ) else 'open' print( f'{self.protagonist} attempts to {verb} distance with {self.opponent} ({melee.get_separation()} -> {self.target_range}).' ) success = True start_range = melee.get_separation() final_range = melee.get_range_shift(self.target_range) # determine opponent's reaction reaction = self.opponent.mind.choose_change_range_response(self) if reaction == 'attack': if not self.allow_opportunity_attack() or not can_interrupt_action( self.opponent): reaction = 'contest' if reaction == 'contest': contest = OpposedResult( ContestResult(self.protagonist, SKILL_EVADE, self.get_contest_modifier(self.protagonist)), ContestResult(self.opponent, SKILL_EVADE, self.get_contest_modifier(self.opponent))) print(contest.format_details()) success = contest.success elif reaction == 'attack': # an attack of opportunity is allowed only if the change is not contested use_attack = self.opponent.mind.get_opportunity_attack( self.protagonist, self.get_opportunity_attack_ranges()) if use_attack is not None: attack_ranges = ( reach for reach in self.get_opportunity_attack_ranges() if use_attack.can_attack(reach)) attack_range = max(attack_ranges, default=None) if attack_range is not None: melee.change_separation(attack_range) combat = MeleeCombatResolver(self.opponent, self.protagonist) if combat.generate_attack_results(opportunity_attack=True): # interrupt their current action to make the opportunity attack action = self.opponent.get_current_action() attack_action = OpportunityAttackAction(action) self.opponent.set_current_action(attack_action) # resolve the outcome of the attack melee.change_separation(final_range) combat.resolve_critical_effects() combat.resolve_damage() combat.resolve_secondary_attacks() if melee.get_separation() != final_range: success = False # range change disrupted by a critical effect if success: melee.change_separation(final_range) print( f'{self.protagonist} {verb}s distance with {self.opponent} ({start_range} -> {melee.get_separation()}).' ) return None