def run(self): self.assert_state('new') self.message.BattleStart() sendouts = [] exclude = [] for side in self.sides: for spot in side.spots: monster = spot.trainer.get_first_inactive_monster( exclude=exclude) sendouts.append((spot, monster)) self.release_monster(spot, monster) self.sort_by_speed( sendouts, key=lambda x: x[1].stats.speed, reverse=True, ) for spot, mon in sendouts: Effect.send_out(spot.battler) self.state = 'waiting' self.ask_for_commands() self.command_loop()
def roll_accuracy(self, hit): if hit.accuracy is None or Effect.ensure_hit(hit): return True else: hit.accuracy = hit.accuracy * hit.user.stats.accuracy / hit.target.stats.evasion # XXX: Is this the correct rounding? hit.accuracy = Fraction(int(hit.accuracy * 100), 100) accuracy = Effect.modify_accuracy(hit, hit.accuracy) return self.field.flip_coin(accuracy, "Determine hit")
def do_use(self, **kwargs): self.field.message.UseMove(battler=self.user, moveeffect=self) Effect.move_used(self) self.targets = list(self.get_targets(**kwargs)) self.deduct_pp() self.user.used_move_effects.append(self) hits = self.use(**kwargs) if hits: Effect.move_hits_done(self, [hit for hit in hits if hit]) return hits
def determine_critical_hit(self, hit): if Effect.prevent_critical_hit(hit): return False stage = Effect.critical_hit_stage(hit, 1) rate = {1: Fraction(1, 16), 2: Fraction(1, 8), 3: Fraction(1, 4), 4: Fraction(1, 3), 5: Fraction(1, 2)}[ min(stage, 5) ] hit.is_critical = self.field.flip_coin(rate, "Determine critical hit") if hit.is_critical: return True else: return Effect.force_critical_hit(self)
def calculate_damage(self, hit): damage_class = hit.damage_class user = hit.user target = hit.target loader = self.field.loader if damage_class.identifier == "physical": attack_stat = loader.load_stat("attack") defense_stat = loader.load_stat("defense") else: attack_stat = loader.load_stat("special-attack") defense_stat = loader.load_stat("special-defense") self.field.message.effectivity(hit=hit) if not hit.effectivity: return None hit.is_critical = hit.move_effect.determine_critical_hit(hit) if hit.is_critical: self.field.message.CriticalHit(hit=hit) attack = user.get_stat(attack_stat, min_change_level=0) defense = target.get_stat(defense_stat, max_change_level=0) else: attack = user.stats[attack_stat] defense = target.stats[defense_stat] damage = (user.level * 2 // 5 + 2) * hit.power * attack // 50 // defense damage = Effect.modify_move_damage(hit, damage) if damage < 1: damage = 1 return damage
def process_replacements(self): """Send out monsters to replace those that have fainted.""" self.assert_state('waiting_replacements') commands = [command for command in self.commands.values() if command.command == 'switch'] for command in commands: battler = command.battler spot = battler.spot self.switch(spot, command.replacement) trick_factor = Effect.speed_factor(self, 1) commands.sort(key=lambda c: -c.replacement.stats.speed * trick_factor) for command in commands: Effect.send_out(command.battler.spot.battler) self.ask_for_commands()
def _get_effectivity(self): if not self.type: return 1 effectivity = 1 efficacies = self.type.damage_efficacies for type in self.target.types: efficacy, = [e for e in efficacies if e.target_type == type] effectivity *= Fraction(efficacy.damage_factor, 100) return Effect.modify_effectivity(self, effectivity)
def key(effect): try: spot = effect.subject.spot except AttributeError: turn_order = speed = 0 else: turn_order = spot.turn_order speed = spot.battler.stats.speed * Effect.speed_factor(spot, 1) return (major_tier, EndTurnOrder.tier_effect, -speed, turn_order, minor_tier)
def command_allowed(self, command, ignore_pp=False): if command.battler.fainted and command.command != 'switch': return False if command.command == 'move': move = command.move if move is None: # Forced move! pass else: if move.kind == self.struggle: # Struggle: We trust the trainer that there are no usable # moves left return True if move.pp <= 0 and not ignore_pp: return False if Effect.prevent_move_selection(command): return False return True elif command.command == 'switch': replacement = command.replacement if replacement.fainted: # Can't send in a fainted monster return False for battler in self.battlers: if command.replacement is battler.monster: # Can't switch to an already active battler return False if command in self.commands.values(): # Cant switch to a monster being already switched in! # Note that this makes the result of command_allowed() # dependent on commands already issued this turn return False battler = command.request.battler if not battler or battler.fainted: return True return not Effect.prevent_switch(command) elif command.command == 'run': return self.allow_run raise NotImplementedError(command)
def __init__(self, move_effect, target, **kwargs): self.move_effect = move_effect self.target = target self.field = move_effect.field self.user = move_effect.user self.type = move_effect.type self.accuracy = move_effect.accuracy self.damage_class = move_effect.damage_class self.args = kwargs self.effectivity = self._get_effectivity() if move_effect.power is None: self.power = None else: self.power = Effect.modify_base_power(self, move_effect.power)
def speed_key(major_minor): """Decorator for end-of-turn effects. Sort by major tier, then the subject speed, then the minor tier """ major_tier, minor_tier = major_minor def key(effect): try: spot = effect.subject.spot except AttributeError: turn_order = speed = 0 else: turn_order = spot.turn_order speed = spot.battler.stats.speed * Effect.speed_factor(spot, 1) return (major_tier, EndTurnOrder.tier_effect, -speed, turn_order, minor_tier) return Effect.orderkey(key)
def handle_turn(self): self.assert_state('processing') commands = [] for battler, command in self.commands.items(): commands.append(command) self.message.TurnStart(turn=self.turn_number) Effect.begin_turn(self) self.turnCommands = commands = self.sort_commands(commands) self.turn_order = [c.request.spot for c in commands] move_effects = {} for command in commands: if command.command == 'move': command.move_effect = command.move.get_effect( command.request.battler, command.target) command.move_effect.begin_turn() for command in commands: battler = command.battler spot = battler.spot self.message.SubturnStart(battler=battler, turn=self.turn_number) if command.command == 'move': if not command.battler.fainted: command.move_effect.attempt_use() elif command.command == 'switch': self.switch(command.request.spot, command.replacement) Effect.send_out(command.battler.spot.battler) else: raise NotImplementedError(command) self.message.SubturnEnd(battler=battler, turn=self.turn_number) if self.check_win(): return Effect.end_turn(self) if self.state == 'finished' or self.check_win(): return self.message.TurnEnd(turn=self.turn_number) # That's it for this turn! del self.turn_order if not self.ended: self.state = 'waiting' self.ask_for_commands()
def command_sort_order(self, command): speed_value = command.request.battler.stats.speed trick_factor = Effect.speed_factor(self, 1) speed_sort_value = -speed_value * trick_factor if command.command == 'move': return ( 1, -command.move.priority, speed_sort_value, ) elif command.command == 'switch': # XXX: Should switch-ins be sorted by speed? return ( 0, speed_sort_value, ) else: raise NotImplementedError(command.command)
def get_stat(self, stat, min_change_level=None, max_change_level=None): if stat.identifier == 'hp': return self.monster.stats[stat] level = self.stat_levels[stat] if min_change_level is not None and level < min_change_level: level = min_change_level if max_change_level is not None and level > max_change_level: level = max_change_level if stat in self.monster.stats: base = self.monster.stats[stat] numerator = denominator = 2 round_func = int else: base = 1 numerator = denominator = 3 round_func = Fraction if level < 0: denominator -= level elif level > 0: numerator += level value = round_func(base * Fraction(numerator, denominator)) return Effect.modify_stat(self, value, stat)
def do_damage(self, damage, direct=False, message_class=messages.HPChange, **extra_message_args): Effect.damage_done(self, damage) return -self.change_hp(-damage, direct, message_class, **extra_message_args)
def withdraw(self, battler): if not battler.fainted: self.message.Withdraw(battler=battler) Effect.withdraw(battler) battler.spot.battler = None
def attempt_hit(self, hit): if Effect.prevent_hit(hit): return None elif not self.roll_accuracy(hit): return self.miss(hit) return self.do_hit(hit)
def do_hit(self, hit): Effect.move_hit(hit) return self.hit(hit)
def do_damage(self, hit): hit.damage = self.calculate_damage(hit) if hit.damage: hit.damage = hit.target.do_damage(hit.damage, direct=True) Effect.move_damage_done(hit) return hit.damage
def attempt_use(self, **kwargs): if Effect.prevent_use(self): return return self.do_use()
def attempt_secondary_effect(self, hit): if not hit.target.fainted: chance = Effect.modify_secondary_chance(hit, self.secondary_effect_chance) if self.field.flip_coin(chance, "Attempt secondary effect"): self.do_secondary_effect(hit)
def deduct_pp(self): if self.ppless not in self.flags: delta = -min(self.move.pp, Effect.pp_reduction(self, 1)) self.move.pp += delta self.field.message.PPChange(move=self.move, battler=self.user, delta=delta, pp=self.move.pp, cause=self)