Example #1
0
    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
Example #2
0
 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()
Example #3
0
 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()
Example #4
0
    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()
Example #5
0
    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)
Example #6
0
    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!')
Example #7
0
    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