def test_remove_the_only_status(self): poison = Status(5) poison.remove('poison') assert poison.id == np.array([0]) assert poison.name == np.array(['normal']) assert poison.duration == np.array([float('inf')]) assert poison.volatile == np.array([False])
def test_reduce_the_duration_by_1_where_the_duration_was_1(self): burn = Status('burn', 1) confused = Status('confused', 5) mixed = burn + confused mixed.reduce() assert mixed.name == ['confused'] assert mixed.duration == [4] assert mixed.volatile == [True]
def test_trick_room_reverses_the_order(self): p1 = Pokemon('shuckle') p2 = Pokemon('deoxys-speed') p1_move, p2_move = Move('snatch'), Move('snatch') p1.status += Status('trick-room', 5) p2.status += Status('trick-room', 5) f1, m1, f2, m2 = attacking_order(p1, p1_move, p2, p2_move) assert p1 == f1 assert p2 == f2
def ailment_inflictor(f1, m1, f2, m2): """Inflicts ailment to the selected target.""" ailment_id = m1.meta_ailment_id ailment_chance = 100. if np.isnan(m1.ailment_chance) else m1.ailment_chance lasting_turns = None if np.isnan(m1.min_turns) else randint( m1.min_turns, m1.max_turns + 1) ailment = Status(ailment_id, lasting_turns) if binomial(1, ailment_chance / 100.): if m1.target_id == 7: # Self-inflicted ailment f1.status += ailment if m1.effect == 38: # User sleeps for two turns, completely healing itself. # At the beginning of each round, ``is_mobile()`` # should check if 'rest' is in ``f1.flags`` and if # 'sleep' is **not** in ``f1.status``. If both are # ``True``, then that means the user once slept and now # 'sleep' status has worn off. # Then restore all user's current hp. Heal the user and # remove the flag. f1.flags['rest'] = True elif m1.target_id == 14: # ailment inflicted to all pokemons. f1.status += ailment f2.status += ailment else: # ailment inflicted to the opponent f2.status += ailment
def test_ability_override_field_effects(self): p1 = Pokemon('shuckle') p2 = Pokemon('deoxys-speed') p1.ability = which_ability('stall') p2.ability = 1 p1_move, p2_move = Move('snatch'), Move('snatch') p1.status += Status('trick-room') p2.status += Status('trick-room') f1, m1, f2, m2 = attacking_order(p1, p1_move, p2, p2_move) assert p1 == f2 assert p2 == f1
def calculate_damage(f1, m1, f2, m2): """Calculate the damage including the moves dealing direct damages and regular damages. """ effect = m1.effect_id def immuned(damage): """A simple filter for damage that takes type-immunity into account. """ return 0 if efficacy(m1.type, f2.types) == 0 else damage if effect == 27: # User waits for two turns. # On the second turn, the user inflicts twice the damage it # accumulated on the last Pokémon to hit it. Damage inflicted # is [typeless]{mechanic:typeless}. # # This move cannot be selected by []{move:sleep-talk}. # XXX: group moves with `charge` flag into a new function. f1.flags += Status('bide', 2) return 0 elif effect == 41: # Inflicts [typeless]{mechanic:typeless} damage equal to half # the target's remaining [HP]{mechanic:hp}. return f2.current.hp / 2. elif effect == 42: # Inflicts 40 points of damage. return immuned(40.) elif effect == 88: # Inflicts damage equal to the user's level. Type immunity # applies, but other type effects are ignored. return immuned(f1.level) elif effect == 89: # Inflicts [typeless]{mechanic:typeless} damage between 50% and # 150% of the user's level, selected at random in increments of # 10%. return f1.level * randint(5, 15) / 10. elif effect == 90: # Targets the last opposing Pokémon to hit the user with a # physical move this turn. # Inflicts twice the damage that move did to the user. # If there is no eligible target, this move will fail. # Type immunity applies, but other type effects are ignored. if f1.order == 2: received_damage = f1.history.damage[0] if m2.damage_class_id == 2: return immuned(received_damage * 2) return 0 elif effect == 131: # Inflicts exactly 20 damage. return immuned(20.) elif effect == 145: # Targets the last opposing Pokémon to hit the user with a # [special]{mechanic:special} move this turn. # Inflicts twice the damage that move did to the user. # If there is no eligible target, this move will # [fail]{mechanic:fail}. # Type immunity applies, but other type effects are ignored. if f1.order == 2: received_damage = f1.history.damage[0] if received_damage and m2.damage_class_id == 3: return immuned(received_damage * 2) return 0 elif effect == 155: # Inflicts {mechanic:typeless} {mechanic:regular-damage}. # Every Pokémon in the user's party, excepting those that have # fainted or have a {mechanic:major-status-effect}, attacks the # target. # Calculated stats are ignored; the base stats for the target # and assorted attackers are used instead. # The random factor in the damage formula is not used. # []{type:dark} Pokémon still get [STAB]{mechanic:stab}. damage = 0 for pokemon in f1.trainer.party(): if pokemon.status.volatile.all(): damage += regular_damage(pokemon, m1, f2, m2) return damage elif effect == 190: # Inflicts exactly enough damage to lower the target's # {mechanic:hp} to equal the user's. If the target's HP is not # higher than the user's, this move has no effect. # Type immunity applies, but other type effects are ignored. # This effect counts as damage for moves that respond to damage. return immuned( np.clip(a=f2.current.hp - f1.current.hp, a_min=0, a_max=f2.current.hp)) elif effect == 228: # Targets the last opposing Pokémon to hit the user with a # damaging move this turn. # Inflicts 1.5× the damage that move did to the user. # If there is no eligible target, this move will fail. # Type immunity applies, but other type effects are ignored. if f1.order == 2: received_damage = f1.history.damage[0] if m2.damage_class_id != 1: return immuned(received_damage * 1.5) return 0. elif effect == 321: # Inflicts damage equal to the user's remaining # [HP]{mechanic:hp}. User faints. damage = f1.current.hp f1.current.hp = 0 return damage else: # All cases up to Gen.5 should be covered. return regular_damage(f1, m1, f2, m2)
def effect(f1, m1, f2, m2): """Activates m1's effect if it is a unique effect. Exceptions ---------- Move id | Effect id -------------|----------- 18, 46 | 29 54 | 47 100 | 154 113 | 36 """ effect = m1.effect_id if not np.isnan(m1.healing): # A positive heal cures the user; a negative heal damages # the user, based on the user's max hp. f1.current.hp += m1.healing * f1.stats.hp // 100. if not np.isnan(m1.flinch_chance) and f2.order == 2: # oxymoron? # If the move makes the opponent flinch, then add `flinch` # to the opponent's status. if binomial(1, m1.flinch_chance / 100.): f2.status += Status('flinch', 1) if not str(m1.stat_change).isnumeric(): # stat-changers stat_changer(f1, m1, f2, m2) if m1.meta_category_id in [1, 5]: # moves that inflicts status conditions. ailment_inflictor(f1, m1, f2, m2) if effect == 26: # Removes [stat]{mechanic:stat}, [accuracy]{mechanic:accuracy}, # and [evasion]{mechanic:evasion} modifiers from every Pokémon # on the [field]{mechanic:field}. # # This does not count as a stat reduction for the purposes of # []{ability:clear-body} or []{ability:white-smoke}. for f in [f1, f2]: for stat in f.STAT_NAMES: f.current[stat] = f.stats[stat] for stat in ['accuracy', 'evasion']: f.current[stat] = 100. elif effect == 58: # User copies the target's species, weight, type, # [ability]{mechanic:ability}, [calculated stats]{mechanic: # calculated-stats} (except [HP]{mechanic:hp}), and moves. # Copied moves will all have 5 [PP]{mechanic:pp} remaining. # [IV]{mechanic:iv}s are copied for the purposes of []{move: # hidden-power}, but stats are not recalculated. # # []{item:choice-band}, []{item:choice-scarf}, and []{item: # choice-specs} stay in effect, and the user must select a new # move. # # This move cannot be copied by []{move:mirror-move}, nor forced # by []{move:encore}. pass # XXX: passed. elif effect == 83: # This move is replaced by the target's last successfully used # move, and its PP changes to 5. If the target hasn't used a # move since entering the field, if it tried to use a move this # turn and [failed]{mechanic:fail}, or if the user already knows # the targeted move, this move will fail. This effect vanishes # when the user leaves the field. # # If []{move:chatter}, []{move:metronome}, []{move:mimic}, # []{move:sketch}, or []{move:struggle} is selected, this move # will [fail]{mechanic:fail}. # # This move cannot be copied by []{move:mirror-move}, nor # selected by []{move:assist} or []{move:metronome}, nor forced # by []{move:encore}. if 'last-successfully-used-move' in f2.flags: m1 = Move(f2.flags['last-successfully-used-move']) m1.pp = 5 elif effect == 84: # Selects any move at random and uses it. # Moves the user already knows are not eligible. # Assist, meta, protection, and reflection moves are also not # eligible; specifically, []{move:assist}, []{move:chatter}, # []{move:copycat}, []{move:counter}, []{move:covet}, # []{move:destiny-bond}, []{move:detect}, []{move:endure}, # []{move:feint}, []{move:focus-punch}, []{move:follow-me}, # []{move:helping-hand}, []{move:me-first}, []{move:metronome}, # []]{move:mimic}, []{move:mirror-coat}, []{move:mirror-move}, # []{move:protect}, []{move:quick-guard}, []{move:sketch}, # []{move:sleep-talk}, []{move:snatch}, []{move:struggle}, # []{move:switcheroo}, []{move:thief}, []{move:trick}, and # []{move:wide-guard} will not be selected by this move. # # This move cannot be copied by []{move:mimic} or # []{move:mirror-move}, nor selected by []{move:assist}, # []{move:metronome}, or []{move:sleep-talk}. ineligible_moves = deque([x.name for x in f1.moves]) ineligible_moves.extend([ 'assist', 'chatter', 'copycat', 'counter', 'covet', 'destiny-bond', 'detect', 'endure', 'feint', 'focus-punch', 'follow-me', 'helping-hand', 'me-first', 'metronome', 'mimic', 'mirror-coat', 'mirror-move', 'protect', 'quick-guard', 'sketch', 'sleep-talk', 'snatch', 'struggle', 'switcheroo', 'thief', 'trick', 'wide-guard' ]) all_moves = deque([x for x in tb.moves.identifier.values]) eligible_moves = list(set(all_moves) - set(ineligible_moves)) m1 = np.random.choice(eligible_moves) elif effect == 95: # If the user targets the same target again before the end of # the next turn, the move it uses is guaranteed to hit. # This move itself also ignores [accuracy]{mechanic:accuracy} # and [evasion]{mechanic:evasion} modifiers. # # One-hit KO moves are also guaranteed to hit, as long as the # user is equal or higher level than the target. This effect # also allows the user to hit Pokémon that are off the field # due to moves such as []{move:dig} or []{move:fly}. # # If the target uses []{move:detect} or []{move:protect} while # under the effect of this move, the user is not guaranteed to # hit, but has a (100 - accuracy)% chance to break through the # protection. # # This effect is passed on by []{move:baton-pass}. # XXX: finish its counterpart in ``makes_hit`` f2.status += Status('taking-aim', 2) elif effect == 101: # Lowers the PP of the target's last used move by 4. # If the target hasn't used a move since entering the [field] # {mechanic:field}, if it tried to use a move this turn and # [failed]{mechanic:failed}, or if its last used move has 0 PP # remaining, this move will fail. move_names = [x.name for x in f2.moves] try: last_move = f2.flags['last-successfully-used-move'] index = np.where(move_names == last_move)[0][0] f2.moves[index].pp -= 4 except KeyError: pass elif effect == 112: pass
def setUpStatus(): poison = Status(5) burn = Status('burn') confused = Status('confused', 5) disabled = Status('disabled', 4) yield poison, burn, confused, disabled
def test_status_volatility(self): assert Status(20).volatile[0] == True assert Status(0).volatile[0] == False
def test_declare_a_custom_status(self): trick_room = Status('trick-room', 5) assert trick_room.id[0] >= 100000 assert trick_room.duration[0] == 5 assert trick_room.volatile[0] == True
def test_declare_a_status_by_name_from_the_table(self): assert Status('poison').id[0] == 5
def test_declare_a_status_with_a_timer(self): assert Status(5, 5).duration[0] == 5
def test_declare_a_status_by_id_from_the_table(self): assert Status(5).name[0] == 'poison'
def test_remove_a_non_existing_status(self): poison = Status(5) with pytest.raises(KeyError): poison.remove('burn')