From 9028b9f63126879070b80f71d4bb1cc082f8279a Mon Sep 17 00:00:00 2001 From: David Stone Date: Sun, 4 Feb 2024 12:23:09 -0700 Subject: [PATCH] Hitting self in confusion is no longer a normal move. --- CMakeLists.txt | 3 + source/tm/ability_blocks_move.cpp | 1 - source/tm/battle.cpp | 43 ++++--- source/tm/can_execute_move.cpp | 4 - source/tm/clients/client_battle.cpp | 1 + source/tm/clients/make_client_battle_impl.cpp | 4 + .../tm/clients/pokemon_online/conversion.cpp | 13 ++- .../battle_message_handler.cpp | 7 ++ .../battle_message_handler_impl.cpp | 105 ++++++++++-------- source/tm/critical_hit_probability.cpp | 3 - source/tm/move/accuracy.cpp | 1 - source/tm/move/base_power.cpp | 1 - source/tm/move/call_move_impl.cpp | 30 ++--- source/tm/move/category.cpp | 1 - source/tm/move/hit_self.cpp | 37 ++++++ source/tm/move/move_name.cpp | 9 +- source/tm/move/power.cpp | 2 +- source/tm/move/pp.cpp | 1 - source/tm/move/selection.cpp | 1 - source/tm/move/side_effects_impl.cpp | 1 - source/tm/move/target.cpp | 1 - source/tm/pokemon/active_pokemon.cpp | 51 +++------ source/tm/pokemon/applied_damage.cpp | 53 +++++++++ source/tm/pokemon/can_be_koed.cpp | 46 ++++++++ source/tm/pokemon/last_used_move.cpp | 5 + source/tm/string_conversions/move_name.cpp | 4 - source/tm/test/battle.cpp | 34 ++++++ .../test/clients/netbattle/read_team_file.cpp | 1 - .../battle_message_handler.cpp | 38 +++++++ source/tm/type/move_type.cpp | 1 - source/tm/visible_damage_to_actual_damage.cpp | 18 ++- 31 files changed, 354 insertions(+), 166 deletions(-) create mode 100644 source/tm/move/hit_self.cpp create mode 100644 source/tm/pokemon/applied_damage.cpp create mode 100644 source/tm/pokemon/can_be_koed.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2728bea26..8a0804888 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ target_sources(tm_common PUBLIC source/tm/move/executed_move.cpp source/tm/move/future_selection.cpp source/tm/move/healing_move_fails_in_generation_1.cpp + source/tm/move/hit_self.cpp source/tm/move/initial_move.cpp source/tm/move/irrelevant_action.cpp source/tm/move/is_blocked_by_gravity.cpp @@ -137,6 +138,8 @@ target_sources(tm_common PUBLIC source/tm/pokemon/active_pokemon.cpp source/tm/pokemon/active_status.cpp source/tm/pokemon/any_pokemon.cpp + source/tm/pokemon/applied_damage.cpp + source/tm/pokemon/can_be_koed.cpp source/tm/pokemon/change_hp.cpp source/tm/pokemon/confusion.cpp source/tm/pokemon/disable.cpp diff --git a/source/tm/ability_blocks_move.cpp b/source/tm/ability_blocks_move.cpp index 11dc07aa7..27411783e 100644 --- a/source/tm/ability_blocks_move.cpp +++ b/source/tm/ability_blocks_move.cpp @@ -53,7 +53,6 @@ export constexpr auto ability_blocks_move(Generation const generation, Ability c return generation >= Generation::five; case MoveName::Fire_Fang: return generation != Generation::four; - case MoveName::Hit_Self: case MoveName::Struggle: return false; default: diff --git a/source/tm/battle.cpp b/source/tm/battle.cpp index 58a37eceb..e59a66235 100644 --- a/source/tm/battle.cpp +++ b/source/tm/battle.cpp @@ -10,6 +10,7 @@ import tm.move.call_move; import tm.move.causes_recoil; import tm.move.do_switch; import tm.move.future_selection; +import tm.move.hit_self; import tm.move.irrelevant_action; import tm.move.known_move; import tm.move.move; @@ -62,24 +63,6 @@ namespace technicalmachine { using namespace bounded::literal; using namespace std::string_view_literals; -constexpr auto get_actual_damage( - bool const ai_is_user, - MoveName const executed, - Damage const damage, - auto const user_pokemon, - auto const other_pokemon -) -> ActualDamage { - auto const damaged_is_user = executed == MoveName::Hit_Self; - auto const damaged_is_ai = !ai_is_user xor damaged_is_user; - auto const old_hp = damaged_is_user ? user_pokemon.hp() : other_pokemon.hp(); - return visible_damage_to_actual_damage( - damage, - damaged_is_ai, - old_hp, - other_pokemon.substitute() - ); -} - constexpr auto move_should_have_recoil( Generation const generation, Used const move, @@ -276,12 +259,11 @@ struct Battle { user_pokemon.set_base_ability(*recoil_ability); } - auto const damage = get_actual_damage( - ai_is_user, - move.executed, + auto const damage = visible_damage_to_actual_damage( move.damage, - user_pokemon, - other_pokemon + !ai_is_user, + other_pokemon.hp(), + other_pokemon.substitute() ); call_move( user_team, @@ -302,6 +284,21 @@ struct Battle { }); } + auto hit_self_in_confusion(bool const ai_is_user, VisibleHP const damage) & -> void { + apply_to_teams(ai_is_user, [&](auto & user_team, auto const &) { + auto const actual_damage = visible_damage_to_actual_damage( + damage, + ai_is_user, + user_team.pokemon().hp() + ); + hit_self( + user_team, + actual_damage.value, + environment() + ); + }); + } + auto end_turn(bool const ai_went_first, EndOfTurnFlags const first_flags, EndOfTurnFlags const last_flags) & -> void { apply_to_teams(ai_went_first, [&](auto & first, auto & last) { end_of_turn(first, first_flags, last, last_flags, m_environment); diff --git a/source/tm/can_execute_move.cpp b/source/tm/can_execute_move.cpp index 183fd71a9..8d66ed8ba 100644 --- a/source/tm/can_execute_move.cpp +++ b/source/tm/can_execute_move.cpp @@ -52,10 +52,6 @@ export constexpr auto can_attempt_move_execution(any_active_pokemon auto const u } export constexpr auto can_execute_move(any_active_pokemon auto const user, Move const move, Environment const environment, bool const is_recharging, bool const is_fully_paralyzed) -> bool { - if (move.name() == MoveName::Hit_Self) { - BOUNDED_ASSERT(!is_recharging); - return true; - } return !user.flinched() and !blocks_selection_and_execution(user, move.name(), environment) and !is_fully_paralyzed and !is_recharging; } diff --git a/source/tm/clients/client_battle.cpp b/source/tm/clients/client_battle.cpp index 21a3b1f6c..d7f21d437 100644 --- a/source/tm/clients/client_battle.cpp +++ b/source/tm/clients/client_battle.cpp @@ -65,6 +65,7 @@ export struct ClientBattle { virtual auto end_turn(bool ai_went_first, EndOfTurnFlags first_flags, EndOfTurnFlags last_flags) & -> void = 0; virtual auto use_move(bool ai_is_user, MoveResult, bool user_status_was_cleared) & -> void = 0; virtual auto use_switch(bool ai_is_user, Switch) & -> void = 0; + virtual auto hit_self_in_confusion(bool ai_is_user, VisibleHP const damage) & -> void = 0; // TODO: Delete this function virtual auto cures_target_status(bool is_ai, MoveName) const -> bool = 0; diff --git a/source/tm/clients/make_client_battle_impl.cpp b/source/tm/clients/make_client_battle_impl.cpp index 069378e72..1203fd9ba 100644 --- a/source/tm/clients/make_client_battle_impl.cpp +++ b/source/tm/clients/make_client_battle_impl.cpp @@ -135,6 +135,10 @@ struct ClientBattleImpl final : ClientBattle { m_battle.use_switch(ai_is_user, switch_); } + auto hit_self_in_confusion(bool ai_is_user, VisibleHP const damage) & -> void final { + m_battle.hit_self_in_confusion(ai_is_user, damage); + } + auto end_turn(bool const ai_went_first, EndOfTurnFlags const first_flags, EndOfTurnFlags const last_flags) & -> void final { m_battle.end_turn(ai_went_first, first_flags, last_flags); } diff --git a/source/tm/clients/pokemon_online/conversion.cpp b/source/tm/clients/pokemon_online/conversion.cpp index a9592b5a0..f158b3eb2 100644 --- a/source/tm/clients/pokemon_online/conversion.cpp +++ b/source/tm/clients/pokemon_online/conversion.cpp @@ -1887,14 +1887,21 @@ export constexpr auto item_to_id(Item const item) -> ItemID { } } -export using MoveID = bounded::integer<1, bounded::normalize> - bounded::integer(MoveName::Regular_Begin) + 1_bi>>; +export using MoveID = bounded::integer< + 1, + bounded::normalize< + bounded::constant> - + bounded::constant> + + 1_bi + > +>; export constexpr auto id_to_move(MoveID const id) -> MoveName { - return static_cast(id + bounded::integer(MoveName::Regular_Begin) - 1_bi); + return static_cast(id + bounded::constant> - 1_bi); } export constexpr auto move_to_id(MoveName const move) -> MoveID { - auto const move_id = bounded::integer(move) - bounded::integer(MoveName::Regular_Begin) + 1_bi; + auto const move_id = bounded::integer(move) - bounded::constant> + 1_bi; BOUNDED_ASSERT(move_id > 0_bi); return ::bounded::assume_in_range(move_id); } diff --git a/source/tm/clients/pokemon_showdown/battle_message_handler.cpp b/source/tm/clients/pokemon_showdown/battle_message_handler.cpp index f1f563621..8c81d3216 100644 --- a/source/tm/clients/pokemon_showdown/battle_message_handler.cpp +++ b/source/tm/clients/pokemon_showdown/battle_message_handler.cpp @@ -41,6 +41,12 @@ struct SwitchState { VisibleHP hp; }; +struct HitSelf { + Party party; + StatusName status; + VisibleHP hp; +}; + export struct BattleMessageHandler { BattleMessageHandler(Party party, GenerationGeneric teams); @@ -61,6 +67,7 @@ export struct BattleMessageHandler { private: auto use_move(MoveState) -> void; auto use_switch(SwitchState) -> void; + auto hit_self_in_confusion(HitSelf) -> void; auto handle_switch_message(SwitchMessage) -> TeamIndex; auto handle_end_of_turn(EndOfTurnState) -> void; diff --git a/source/tm/clients/pokemon_showdown/battle_message_handler_impl.cpp b/source/tm/clients/pokemon_showdown/battle_message_handler_impl.cpp index 4e13ce611..36774d1f9 100644 --- a/source/tm/clients/pokemon_showdown/battle_message_handler_impl.cpp +++ b/source/tm/clients/pokemon_showdown/battle_message_handler_impl.cpp @@ -70,7 +70,8 @@ auto BattleMessageHandler::handle_message(std::span const b using ActionBuilder = tv::variant< Nothing, MoveStateBuilder, - Switches + Switches, + HitSelf >; auto action_builder = ActionBuilder(Nothing()); auto end_of_turn_state = EndOfTurnStateBuilder(); @@ -78,15 +79,15 @@ auto BattleMessageHandler::handle_message(std::span const b auto set_value_on_pokemon = [&](Party const party, auto const value) -> void { auto const for_ai = party == m_party; auto const index = tv::visit(action_builder, tv::overload( - [](Nothing) -> tv::optional { - return tv::none; - }, [](MoveStateBuilder const & move_builder) -> tv::optional { return move_builder.phaze_index(); }, [=](Switches const switches) -> tv::optional { auto const ptr = find_switch(switches, party); return BOUNDED_CONDITIONAL(ptr, ptr->index, tv::none); + }, + [](auto) -> tv::optional { + return tv::none; } )); if (index) { @@ -96,23 +97,25 @@ auto BattleMessageHandler::handle_message(std::span const b } }; - auto use_previous_move = [&](MoveStateBuilder & builder) -> void { - if (auto const move_state = builder.complete()) { - use_move(*move_state); - } - }; - auto use_previous_switches = [&](Switches const switches) { - // TODO: Timing for both sides replacing a fainting Pokemon - for (auto const switch_ : containers::reversed(switches)) { - use_switch(switch_); + auto const do_action = tv::overload( + [](Nothing) {}, + [&](MoveStateBuilder & builder) -> void { + if (auto const move_state = builder.complete()) { + use_move(*move_state); + } + }, + [&](Switches const switches) { + // TODO: Timing for both sides replacing a fainting Pokemon + for (auto const switch_ : containers::reversed(switches)) { + use_switch(switch_); + } + }, + [&](HitSelf const hit_self) { + hit_self_in_confusion(hit_self); } - }; + ); auto use_previous_action = [&] -> void { - tv::visit(action_builder, tv::overload( - [](Nothing) {}, - use_previous_move, - use_previous_switches - )); + tv::visit(action_builder, do_action); action_builder = Nothing(); }; @@ -226,9 +229,6 @@ auto BattleMessageHandler::handle_message(std::span const b } }; tv::visit(action_builder, tv::overload( - [&](Nothing) { - natural_status_recovery(); - }, [&](MoveStateBuilder & builder) { auto const move_name = builder.executed_move(); auto const move_cured_status = move_name and ( @@ -238,12 +238,12 @@ auto BattleMessageHandler::handle_message(std::span const b if (move_cured_status) { builder.status_from_move(party, StatusName::clear); } else { - use_previous_move(builder); + do_action(builder); natural_status_recovery(); } }, - [&](Switches & switches) { - use_previous_switches(switches); + [&](auto const previous) { + do_action(previous); natural_status_recovery(); } )); @@ -267,17 +267,16 @@ auto BattleMessageHandler::handle_message(std::span const b }, [&](HitSelfMessage const message) { use_previous_action(); - auto const party = message.party; - // TODO: You cannot select Hit Self, you just execute it. This - // matters for things like priority or determining whether - // Sucker Punch succeeds. As a workaround for now, say the user - // selected Struggle. - auto & builder = make_move_builder(); - builder.use_move(party, MoveName::Struggle); - builder.use_move(party, MoveName::Hit_Self); - builder.damage(party, message.hp); - builder.set_expected(party, message.status); - builder.set_expected(party, message.hp); + tv::visit(action_builder, tv::overload( + [&](Nothing) { + action_builder.emplace([&] { + return HitSelf(message.party, message.status, message.hp); + }); + }, + [](auto) { + throw std::runtime_error("Tried to hit self and take another action"); + } + )); }, [&](RecoilMessage const message) { tv::visit(action_builder, tv::overload( @@ -315,6 +314,9 @@ auto BattleMessageHandler::handle_message(std::span const b } ptr->status = message.status; ptr->hp = message.hp; + }, + [](HitSelf) { + throw std::runtime_error("Got multiple HP messages for hitting self in confusion"); } )); }, @@ -330,18 +332,15 @@ auto BattleMessageHandler::handle_message(std::span const b }, [&](SwitchMessage const message) { auto & switches = tv::visit(action_builder, tv::overload( - [&](Nothing) -> Switches & { - return action_builder.emplace(bounded::construct); - }, - [&](MoveStateBuilder & move_builder) -> Switches & { - use_previous_move(move_builder); - return action_builder.emplace(bounded::construct); - }, [&](Switches & s) -> Switches & { if (find_switch(s, message.party)) { throw std::runtime_error("Tried to switch in the same Pokemon twice"); } return s; + }, + [&](auto & previous) -> Switches & { + do_action(previous); + return action_builder.emplace(bounded::construct); } )); auto const index = handle_switch_message(message); @@ -373,6 +372,7 @@ auto BattleMessageHandler::handle_message(std::span const b [&](StartConfusionMessage const message) { tv::visit(action_builder, tv::overload( [&](MoveStateBuilder & builder) { + // TODO: Rampage moves? builder.confuse(other(message.party)); }, [](auto) { @@ -382,9 +382,6 @@ auto BattleMessageHandler::handle_message(std::span const b }, [&](MoveStatus const message) { tv::visit(action_builder, tv::overload( - [](Nothing) { - throw std::runtime_error("Move status without a move"); - }, [&](MoveStateBuilder & builder) { builder.status_from_move(message.party, message.status); }, @@ -400,6 +397,9 @@ auto BattleMessageHandler::handle_message(std::span const b throw std::runtime_error("Toxic poison changed to poison from switching in later generations"); } ptr->status = message.status; + }, + [](auto) { + throw std::runtime_error("Move status without a move"); } )); }, @@ -428,15 +428,18 @@ auto BattleMessageHandler::handle_message(std::span const b throw std::runtime_error("End of turn did not complete"); } }, - [&](MoveStateBuilder) { + [](MoveStateBuilder) { throw std::runtime_error("Should not have a move state builder at the start of a turn"); }, [&](Switches const switches) { - use_previous_switches(switches); + do_action(switches); if (m_client_battle->is_end_of_turn()) { throw std::runtime_error("Should not have pending switches before we handle the end of turn"); } action_builder = Nothing(); + }, + [](HitSelf) { + throw std::runtime_error("Should not be hitting self at the start of a turn"); } )); action_builder = Nothing(); @@ -494,6 +497,12 @@ auto BattleMessageHandler::use_switch(SwitchState const data) -> void { try_correct_hp_and_status(data_is_for_ai, data.hp, data.status); } +auto BattleMessageHandler::hit_self_in_confusion(HitSelf const data) -> void { + auto const data_is_for_ai = data.party == m_party; + m_client_battle->hit_self_in_confusion(data_is_for_ai, data.hp); + try_correct_hp_and_status(data_is_for_ai, data.hp, data.status); +} + auto BattleMessageHandler::handle_switch_message(SwitchMessage const message) -> TeamIndex { if (message.party == m_party) { auto const index = m_client_battle->ai_has( diff --git a/source/tm/critical_hit_probability.cpp b/source/tm/critical_hit_probability.cpp index 12f744d1b..a3a3d6df7 100644 --- a/source/tm/critical_hit_probability.cpp +++ b/source/tm/critical_hit_probability.cpp @@ -39,9 +39,6 @@ enum class MoveCriticalHit { constexpr auto move_critical_hit(Generation const generation, MoveName const move_name) { switch (move_name) { - // Weird moves - case MoveName::Hit_Self: return MoveCriticalHit::never; - // Generation 1 case MoveName::Pound: return MoveCriticalHit::normal; case MoveName::Karate_Chop: return MoveCriticalHit::high; diff --git a/source/tm/move/accuracy.cpp b/source/tm/move/accuracy.cpp index 0fcb67b72..4a89ee0f5 100644 --- a/source/tm/move/accuracy.cpp +++ b/source/tm/move/accuracy.cpp @@ -25,7 +25,6 @@ export using BaseAccuracy = tv::optional>; export constexpr auto accuracy(Generation const generation, MoveName const move, Environment const environment, bool const weather_blocked, bool const user_is_poison) -> BaseAccuracy { using tv::none; switch (move) { - case MoveName::Hit_Self: return none; case MoveName::Pound: return 100_bi; case MoveName::Karate_Chop: return 100_bi; case MoveName::Double_Slap: return 85_bi; diff --git a/source/tm/move/base_power.cpp b/source/tm/move/base_power.cpp index df4128e75..6a6aad48a 100644 --- a/source/tm/move/base_power.cpp +++ b/source/tm/move/base_power.cpp @@ -1091,7 +1091,6 @@ constexpr auto base_power(UserTeam const & attacker_team, ExecutedMove auto const & attacker = attacker_team.pokemon(); auto const & defender = defender_team.pokemon(); switch (executed.move.name) { - case MoveName::Hit_Self: return 40_bi; case MoveName::Pound: return 40_bi; case MoveName::Karate_Chop: return 50_bi; case MoveName::Double_Slap: return 15_bi; diff --git a/source/tm/move/call_move_impl.cpp b/source/tm/move/call_move_impl.cpp index 64282fb16..3bc03865b 100644 --- a/source/tm/move/call_move_impl.cpp +++ b/source/tm/move/call_move_impl.cpp @@ -121,8 +121,8 @@ constexpr auto activate_target_ability(any_mutable_active_pokemon auto const use } } -template -auto use_move(UserTeam & user, ExecutedMove const executed, Target const target, OtherTeamType & other, OtherAction const other_action, Environment & environment, ActualDamage const actual_damage) -> void { +template +auto use_move(UserTeam & user, ExecutedMove const executed, Target const target, OtherTeam & other, OtherAction const other_action, Environment & environment, ActualDamage const actual_damage) -> void { constexpr auto generation = generation_from; auto const user_pokemon = user.pokemon(); auto const other_pokemon = other.pokemon(); @@ -153,12 +153,7 @@ auto use_move(UserTeam & user, ExecutedMove const executed, Target con if (effects == Substitute::absorbs) { return; } - constexpr auto hit_self_combination = std::same_as and !std::same_as>>; - if constexpr (hit_self_combination) { - BOUNDED_ASSERT(executed.move.name == MoveName::Hit_Self); - } else { - executed.side_effect(user, other, environment, damage_done); - } + executed.side_effect(user, other, environment, damage_done); // Should this check if we did any damage or if the move is damaging? if (damage_done != 0_bi) { @@ -171,11 +166,7 @@ auto use_move(UserTeam & user, ExecutedMove const executed, Target con } // TODO: When do target abilities activate? - if constexpr (hit_self_combination) { - BOUNDED_ASSERT(executed.contact_ability_effect == ContactAbilityEffect::nothing); - } else { - activate_target_ability(user_pokemon, other_pokemon, environment, executed.contact_ability_effect); - } + activate_target_ability(user_pokemon, other_pokemon, environment, executed.contact_ability_effect); } } @@ -227,20 +218,21 @@ auto try_use_move(UserTeam & user, UsedMove const move, OtherTeam const move, OtherTeam +constexpr auto hit_self( + UserTeam & user, + damage_type const damage, + Environment const environment +) -> void { + auto const user_pokemon = user.pokemon(); + user_pokemon.advance_confusion(); + // TODO: ??? + constexpr auto irrelevant_move = MoveName::Struggle; + auto const applied = applied_damage(user_pokemon.as_const(), irrelevant_move, damage, environment); + user_pokemon.indirect_damage(environment, applied.damage); + user_pokemon.hit_self(); +} + +} // namespace technicalmachine diff --git a/source/tm/move/move_name.cpp b/source/tm/move/move_name.cpp index 274315cc6..1eed488b9 100644 --- a/source/tm/move/move_name.cpp +++ b/source/tm/move/move_name.cpp @@ -11,12 +11,8 @@ import std_module; namespace technicalmachine { export enum class MoveName : std::uint16_t { - // Weird moves - Hit_Self = 6, - // Generation 1 - Regular_Begin, - Pound = Regular_Begin, + Pound = 6, Karate_Chop, Double_Slap, Comet_Punch, @@ -891,7 +887,7 @@ export enum class MoveName : std::uint16_t { } // namespace technicalmachine template<> -constexpr auto numeric_traits::min_value = technicalmachine::MoveName::Hit_Self; +constexpr auto numeric_traits::min_value = technicalmachine::MoveName::Pound; template<> constexpr auto numeric_traits::max_value = technicalmachine::MoveName::Eerie_Spell; @@ -900,7 +896,6 @@ namespace technicalmachine { export constexpr auto is_regular(MoveName const move) -> bool { switch (move) { - case MoveName::Hit_Self: case MoveName::Struggle: return false; default: diff --git a/source/tm/move/power.cpp b/source/tm/move/power.cpp index 45056ccd2..2cff35f47 100644 --- a/source/tm/move/power.cpp +++ b/source/tm/move/power.cpp @@ -325,7 +325,7 @@ constexpr auto attacker_ability_power_modifier(AttackerPokemon const attacker, K }; switch (attacker.ability()) { case Ability::Technician: - return rational(BOUNDED_CONDITIONAL(base <= 60_bi and move.name != MoveName::Hit_Self, 3_bi, 2_bi), 2_bi); + return rational(BOUNDED_CONDITIONAL(base <= 60_bi, 3_bi, 2_bi), 2_bi); case Ability::Blaze: return rational(BOUNDED_CONDITIONAL(pinch_ability_activates(Type::Fire), 3_bi, 2_bi), 2_bi); case Ability::Overgrow: diff --git a/source/tm/move/pp.cpp b/source/tm/move/pp.cpp index 04ec6bab0..64ba208ff 100644 --- a/source/tm/move/pp.cpp +++ b/source/tm/move/pp.cpp @@ -40,7 +40,6 @@ constexpr auto calculate_max(tv::optional const base, pp_ups_type con constexpr auto base_pp(Generation const generation, MoveName const move) -> tv::optional { using tv::none; switch (move) { - case MoveName::Hit_Self: return none; case MoveName::Pound: return 35_bi; case MoveName::Karate_Chop: return 25_bi; case MoveName::Double_Slap: return 10_bi; diff --git a/source/tm/move/selection.cpp b/source/tm/move/selection.cpp index 3375830d8..21657ecd8 100644 --- a/source/tm/move/selection.cpp +++ b/source/tm/move/selection.cpp @@ -32,7 +32,6 @@ export struct Selection { constexpr Selection(MoveName const move): m_value(move) { - BOUNDED_ASSERT(move != MoveName::Hit_Self); } constexpr Selection(Switch const switch_): m_value(switch_.value()) diff --git a/source/tm/move/side_effects_impl.cpp b/source/tm/move/side_effects_impl.cpp index eba88a5a9..dd660a008 100644 --- a/source/tm/move/side_effects_impl.cpp +++ b/source/tm/move/side_effects_impl.cpp @@ -1897,7 +1897,6 @@ auto possible_side_effects(MoveName const move, UserPokemon const original_user, case MoveName::Helping_Hand: case MoveName::Hex: case MoveName::Hidden_Power: - case MoveName::Hit_Self: case MoveName::Hone_Claws: case MoveName::Horn_Attack: case MoveName::Horn_Drill: diff --git a/source/tm/move/target.cpp b/source/tm/move/target.cpp index 6542a4233..3b239024f 100644 --- a/source/tm/move/target.cpp +++ b/source/tm/move/target.cpp @@ -268,7 +268,6 @@ export constexpr auto move_target(Generation const generation, MoveName const mo case MoveName::Harden: case MoveName::Heal_Order: case MoveName::Healing_Wish: - case MoveName::Hit_Self: case MoveName::Hone_Claws: case MoveName::Imprison: case MoveName::Ingrain: diff --git a/source/tm/pokemon/active_pokemon.cpp b/source/tm/pokemon/active_pokemon.cpp index 5e95f5355..d3628503e 100644 --- a/source/tm/pokemon/active_pokemon.cpp +++ b/source/tm/pokemon/active_pokemon.cpp @@ -18,6 +18,7 @@ import tm.move.regular_moves; import tm.pokemon.active_status; import tm.pokemon.any_pokemon; +import tm.pokemon.applied_damage; import tm.pokemon.change_hp; import tm.pokemon.confusion; import tm.pokemon.disable; @@ -488,10 +489,6 @@ constexpr auto activate_berserk_gene(any_mutable_active_pokemon auto pokemon, En pokemon.remove_item(); } -constexpr bool cannot_ko(MoveName const move) { - return move == MoveName::False_Swipe; -} - constexpr auto yawn_can_apply(any_active_pokemon auto const target, Environment const environment, bool const either_is_uproaring, bool const sleep_clause_activates) { return !sleep_clause_activates and @@ -589,7 +586,7 @@ struct AnyMutableActivePokemon : ActivePokemonImpl { this->m_flags.confusion.activate(); } } - constexpr auto handle_confusion() const { + constexpr auto advance_confusion() const { this->m_flags.confusion.do_turn(); } constexpr auto curse() const { @@ -682,8 +679,6 @@ struct AnyMutableActivePokemon : ActivePokemonImpl { } } - - constexpr auto hit_with_leech_seed() const { this->m_flags.leech_seeded = true; } @@ -947,21 +942,18 @@ struct AnyMutableActivePokemon : ActivePokemonImpl { if (this->m_flags.substitute and interaction == Substitute::absorbs) { return this->m_flags.substitute.damage(damage); } - auto const original_hp = this->hp().current(); - auto const block_ko = original_hp <= damage and handle_ko(move, environment); - auto const applied_damage = block_ko ? - bounded::assume_in_range(original_hp - 1_bi) : - bounded::min(damage, original_hp); - - indirect_damage(environment, applied_damage); - this->m_flags.direct_damage_received = applied_damage; - this->m_flags.last_used_move.direct_damage(applied_damage); - + auto const applied = applied_damage(this->as_const(), move, damage, environment); + indirect_damage(environment, applied.damage); + this->m_flags.direct_damage_received = applied.damage; + this->m_flags.last_used_move.direct_damage(applied.damage); + if (applied.consume_item) { + remove_item(); + } // TODO: Resolve ties properly - if (this->last_used_move().is_destiny_bonded() and applied_damage == original_hp) { + if (this->last_used_move().is_destiny_bonded() and this->hp().current() == 0_bi) { faint(user); } - return applied_damage; + return applied.damage; } constexpr auto successfully_use_move(MoveName const move) const { @@ -970,26 +962,11 @@ struct AnyMutableActivePokemon : ActivePokemonImpl { constexpr auto unsuccessfully_use_move(MoveName const move) const { this->m_flags.last_used_move.unsuccessful_move(move); } - -private: - constexpr auto handle_ko(MoveName const move, Environment const environment) const { - if (cannot_ko(move) or this->last_used_move().is_enduring()) { - return true; - } - auto const hp = this->hp(); - if (hp.current() != hp.max()) { - return false; - } - if (generation >= Generation::five and this->ability() == Ability::Sturdy) { - return true; - } - if (this->item(environment) == Item::Focus_Sash) { - remove_item(); - return true; - } - return false; + constexpr auto hit_self() const { + this->m_flags.last_used_move.hit_self(); } +private: constexpr auto activate_pinch_item(Environment const environment) const -> void { // TODO: Confusion damage does not activate healing berries in Generation 5+ auto consume = [&] { remove_item(); }; diff --git a/source/tm/pokemon/applied_damage.cpp b/source/tm/pokemon/applied_damage.cpp new file mode 100644 index 000000000..d93265aad --- /dev/null +++ b/source/tm/pokemon/applied_damage.cpp @@ -0,0 +1,53 @@ +// Copyright David Stone 2024. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +export module tm.pokemon.applied_damage; + +import tm.move.damage_type; +import tm.move.move_name; + +import tm.pokemon.any_pokemon; +import tm.pokemon.can_be_koed; + +import tm.stat.current_hp; + +import tm.environment; + +import bounded; + +namespace technicalmachine { +using namespace bounded::literal; + +constexpr bool cannot_ko(MoveName const move) { + return move == MoveName::False_Swipe; +} + +struct AppliedDamage { + CurrentHP damage; + bool consume_item; +}; +export constexpr auto applied_damage( + any_active_pokemon auto pokemon, + MoveName const move, + damage_type const damage, + Environment const environment +) -> AppliedDamage { + auto const hp = pokemon.hp(); + auto const actual = bounded::min(damage, hp.max()); + if (hp.current() > damage) { + return AppliedDamage(actual, false); + } + auto const capped = bounded::assume_in_range(hp.current() - 1_bi); + if (cannot_ko(move)) { + return AppliedDamage(capped, false); + } + switch (can_be_koed(pokemon, environment)) { + case CanBeKOed::yes: return AppliedDamage(actual, false); + case CanBeKOed::no: return AppliedDamage(capped, false); + case CanBeKOed::consume_item: return AppliedDamage(capped, true); + } +} + +} // namespace technicalmachine diff --git a/source/tm/pokemon/can_be_koed.cpp b/source/tm/pokemon/can_be_koed.cpp new file mode 100644 index 000000000..3f81b6f03 --- /dev/null +++ b/source/tm/pokemon/can_be_koed.cpp @@ -0,0 +1,46 @@ +// Copyright David Stone 2024. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +export module tm.pokemon.can_be_koed; + +import tm.pokemon.any_pokemon; + +import tm.ability; +import tm.environment; +import tm.generation; +import tm.item; + +namespace technicalmachine { + +export enum class CanBeKOed { + yes, + no, + consume_item +}; + +// TODO: Focus Band activation? +export template +constexpr auto can_be_koed( + PokemonType const pokemon, + Environment const environment +) -> CanBeKOed { + if (pokemon.last_used_move().is_enduring()) { + return CanBeKOed::no; + } + auto const hp = pokemon.hp(); + if (hp.current() != hp.max()) { + return CanBeKOed::yes; + } + constexpr auto generation = generation_from; + if (generation >= Generation::five and pokemon.ability() == Ability::Sturdy) { + return CanBeKOed::no; + } + if (pokemon.item(environment) == Item::Focus_Sash) { + return CanBeKOed::consume_item; + } + return CanBeKOed::yes; +} + +} // namespace technicalmachine diff --git a/source/tm/pokemon/last_used_move.cpp b/source/tm/pokemon/last_used_move.cpp index b047477c4..e968be43a 100644 --- a/source/tm/pokemon/last_used_move.cpp +++ b/source/tm/pokemon/last_used_move.cpp @@ -119,6 +119,11 @@ export struct LastUsedMove { } } + constexpr auto hit_self() & -> void { + m_move = tv::none; + m_moved_this_turn = true; + } + constexpr auto moved_this_turn() const { return m_moved_this_turn; } diff --git a/source/tm/string_conversions/move_name.cpp b/source/tm/string_conversions/move_name.cpp index 8e509fda8..ebfedd68e 100644 --- a/source/tm/string_conversions/move_name.cpp +++ b/source/tm/string_conversions/move_name.cpp @@ -26,9 +26,6 @@ using namespace bounded::literal; export constexpr auto to_string(MoveName const move) -> std::string_view { switch (move) { - // Weird moves - case MoveName::Hit_Self: return "Hit self in confusion"; - case MoveName::Pound: return "Pound"; case MoveName::Karate_Chop: return "Karate Chop"; case MoveName::Double_Slap: return "Double Slap"; @@ -1244,7 +1241,6 @@ constexpr auto from_string(std::string_view const str) -> MoveName { {"highhorsepower", MoveName::High_Horsepower}, {"highjumpkick", MoveName::High_Jump_Kick}, {"hijumpkick", MoveName::High_Jump_Kick}, - {"hitselfinconfusion", MoveName::Hit_Self}, {"holdback", MoveName::Hold_Back}, {"holdhands", MoveName::Hold_Hands}, {"honeclaws", MoveName::Hone_Claws}, diff --git a/source/tm/test/battle.cpp b/source/tm/test/battle.cpp index 6227b8491..cfc31aeaf 100644 --- a/source/tm/test/battle.cpp +++ b/source/tm/test/battle.cpp @@ -202,5 +202,39 @@ TEST_CASE("Handle Toxic in generation 1", "[Battle]") { } } +TEST_CASE("Handle hitting self in confusion", "[Battle]") { + constexpr auto generation = Generation::one; + auto battle = Battle( + KnownTeam({{ + { + .species = Species::Exeggutor, + .moves = {{ + MoveName::Psychic, + }} + }, + }}), + make_seen_team({ + {.species = Species::Gengar}, + }) + ); + + battle.use_move( + false, + Used(MoveName::Confuse_Ray), + false + ); + + battle.hit_self_in_confusion( + true, + visible_hp(355_bi, 393_bi) + ); + + auto const hp = battle.ai().pokemon().hp(); + CHECK(hp.current() == 355_bi); + CHECK(hp.max() == 393_bi); + + +} + } // namespace } // namespace technicalmachine diff --git a/source/tm/test/clients/netbattle/read_team_file.cpp b/source/tm/test/clients/netbattle/read_team_file.cpp index ea43f3ddc..4688dcbee 100644 --- a/source/tm/test/clients/netbattle/read_team_file.cpp +++ b/source/tm/test/clients/netbattle/read_team_file.cpp @@ -217,7 +217,6 @@ constexpr auto expected_netbattle_supremacy_team = InitialTeam( + { + { + .species = Species::Exeggutor, + .moves = {{MoveName::Psychic}} + }, + }, + { + {.species = Species::Gengar}, + } + ); + + auto const result = handler.handle_message(ps::EventBlock({ + ps::SeparatorMessage(), + ps::MoveMessage(Party(1_bi), MoveName::Confuse_Ray, did_not_miss), + ps::StartConfusionMessage(Party(0_bi)), + ps::HitSelfMessage(Party(0_bi), StatusName::clear, visible_hp(355_bi, 393_bi)), + ps::SeparatorMessage(), + ps::EndOfTurnMessage(), + ps::TurnMessage(2_bi), + })); + + expected->use_move( + false, + Used(MoveName::Confuse_Ray), + false + ); + expected->hit_self_in_confusion( + true, + visible_hp(355_bi, 393_bi) + ); + handle_end_turn(*expected); + + check_state(result, expected->state(), TurnCount(2_bi)); +} + #if 0 // TODO: Figure out how to handle this case diff --git a/source/tm/type/move_type.cpp b/source/tm/type/move_type.cpp index 60ea8db2c..3af09bf35 100644 --- a/source/tm/type/move_type.cpp +++ b/source/tm/type/move_type.cpp @@ -18,7 +18,6 @@ namespace technicalmachine { // If `move` is Hidden Power, type must not be `none` export constexpr auto move_type(Generation const generation, MoveName const move, tv::optional const hidden_power) -> Type { switch (move) { - case MoveName::Hit_Self: return Type::Typeless; case MoveName::Pound: return Type::Normal; case MoveName::Karate_Chop: return generation == Generation::one ? Type::Normal : Type::Fighting; case MoveName::Double_Slap: return Type::Normal; diff --git a/source/tm/visible_damage_to_actual_damage.cpp b/source/tm/visible_damage_to_actual_damage.cpp index eb721eaf4..a4bdd9e7c 100644 --- a/source/tm/visible_damage_to_actual_damage.cpp +++ b/source/tm/visible_damage_to_actual_damage.cpp @@ -47,6 +47,18 @@ struct MoveHitSubstitute { Substitute m_other_substitute; }; +export constexpr auto visible_damage_to_actual_damage( + VisibleHP const damage, + bool const damaged_has_exact_hp, + HP const old_hp +) -> ActualDamage::Known { + auto const new_hp = damaged_has_exact_hp ? + damage.current.value() : + to_real_hp(old_hp.max(), damage).value; + auto const value = bounded::check_in_range(old_hp.current() - new_hp); + return ActualDamage::Known(value); +} + export constexpr auto visible_damage_to_actual_damage( Damage const damage, bool const damaged_has_exact_hp, @@ -56,11 +68,7 @@ export constexpr auto visible_damage_to_actual_damage( return tv::visit(damage, tv::overload( no_damage_function, [&](VisibleHP const hp) -> ActualDamage { - auto const new_hp = damaged_has_exact_hp ? - hp.current.value() : - to_real_hp(old_hp.max(), hp).value; - auto const value = bounded::check_in_range(old_hp.current() - new_hp); - return ActualDamage::Known(value); + return visible_damage_to_actual_damage(hp, damaged_has_exact_hp, old_hp); }, MoveHitSubstitute(other_substitute) ));