From c6e930b994e01e3cb1fcf24a2f80597f511cbb1b Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 4 Feb 2024 16:06:11 -0800 Subject: [PATCH] Ep3 NTE battles checkpoint 2 --- src/CommandFormats.hh | 98 +-- src/Episode3/Card.cc | 208 ++--- src/Episode3/Card.hh | 12 +- src/Episode3/CardSpecial.cc | 969 ++++++++++++++---------- src/Episode3/DataIndexes.cc | 25 +- src/Episode3/DataIndexes.hh | 6 +- src/Episode3/PlayerState.cc | 58 +- src/Episode3/PlayerState.hh | 1 + src/Episode3/PlayerStateSubordinates.cc | 2 +- src/Episode3/RulerServer.cc | 23 +- src/Episode3/Server.cc | 60 +- src/Episode3/Server.hh | 2 + 12 files changed, 861 insertions(+), 603 deletions(-) diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 6d64bc4f..aefc3b6e 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -6230,16 +6230,16 @@ struct G_UpdateActionChainAndMetadata_Ep3_6xB4x0A { // 6xB3x0B / CAx0B: Redraw initial hand (immediately before battle) -struct G_RedrawInitialHand_Ep3_6xB3x0B_CAx0B { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_RedrawInitialHand_Ep3_6xB3x0B_CAx0B) / 4, 0, 0x0B, 0, 0, 0, 0, 0}; +struct G_RedrawInitialHand_Ep3_CAx0B { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_RedrawInitialHand_Ep3_CAx0B) / 4, 0, 0x0B, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; parray unused2; } __packed__; // 6xB3x0C / CAx0C: End initial redraw phase -struct G_EndInitialRedrawPhase_Ep3_6xB3x0C_CAx0C { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndInitialRedrawPhase_Ep3_6xB3x0C_CAx0C) / 4, 0, 0x0C, 0, 0, 0, 0, 0}; +struct G_EndInitialRedrawPhase_Ep3_CAx0C { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndInitialRedrawPhase_Ep3_CAx0C) / 4, 0, 0x0C, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; parray unused2; } __packed__; @@ -6249,24 +6249,24 @@ struct G_EndInitialRedrawPhase_Ep3_6xB3x0C_CAx0C { // current phase. This command isn't used for ending the attack or defense // phases; for those phases, CAx12 and CAx28 are used instead. -struct G_EndNonAttackPhase_Ep3_6xB3x0D_CAx0D { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndNonAttackPhase_Ep3_6xB3x0D_CAx0D) / 4, 0, 0x0D, 0, 0, 0, 0, 0}; +struct G_EndNonAttackPhase_Ep3_CAx0D { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndNonAttackPhase_Ep3_CAx0D) / 4, 0, 0x0D, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; parray unused2; } __packed__; // 6xB3x0E / CAx0E: Discard card from hand -struct G_DiscardCardFromHand_Ep3_6xB3x0E_CAx0E { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_DiscardCardFromHand_Ep3_6xB3x0E_CAx0E) / 4, 0, 0x0E, 0, 0, 0, 0, 0}; +struct G_DiscardCardFromHand_Ep3_CAx0E { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_DiscardCardFromHand_Ep3_CAx0E) / 4, 0, 0x0E, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; le_uint16_t card_ref = 0xFFFF; } __packed__; // 6xB3x0F / CAx0F: Set card from hand -struct G_SetCardFromHand_Ep3_6xB3x0F_CAx0F { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetCardFromHand_Ep3_6xB3x0F_CAx0F) / 4, 0, 0x0F, 0, 0, 0, 0, 0}; +struct G_SetCardFromHand_Ep3_CAx0F { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetCardFromHand_Ep3_CAx0F) / 4, 0, 0x0F, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; le_uint16_t card_ref = 0xFFFF; le_uint16_t set_index = 0; @@ -6276,8 +6276,8 @@ struct G_SetCardFromHand_Ep3_6xB3x0F_CAx0F { // 6xB3x10 / CAx10: Move field character -struct G_MoveFieldCharacter_Ep3_6xB3x10_CAx10 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MoveFieldCharacter_Ep3_6xB3x10_CAx10) / 4, 0, 0x10, 0, 0, 0, 0, 0}; +struct G_MoveFieldCharacter_Ep3_CAx10 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MoveFieldCharacter_Ep3_CAx10) / 4, 0, 0x10, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; le_uint16_t set_index = 0; Episode3::Location loc; @@ -6289,8 +6289,8 @@ struct G_MoveFieldCharacter_Ep3_6xB3x10_CAx10 { // sent once for each attack (even if it includes multiple cards); in the // defense case, this command is sent once for each defense card. -struct G_EnqueueAttackOrDefense_Ep3_6xB3x11_CAx11 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EnqueueAttackOrDefense_Ep3_6xB3x11_CAx11) / 4, 0, 0x11, 0, 0, 0, 0, 0}; +struct G_EnqueueAttackOrDefense_Ep3_CAx11 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EnqueueAttackOrDefense_Ep3_CAx11) / 4, 0, 0x11, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; parray unused2; Episode3::ActionState entry; @@ -6300,24 +6300,30 @@ struct G_EnqueueAttackOrDefense_Ep3_6xB3x11_CAx11 { // This command informs the server that the client is done playing attacks in // the current round. (In the defense phase, CAx28 is used instead.) -struct G_EndAttackList_Ep3_6xB3x12_CAx12 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndAttackList_Ep3_6xB3x12_CAx12) / 4, 0, 0x12, 0, 0, 0, 0, 0}; +struct G_EndAttackList_Ep3_CAx12 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndAttackList_Ep3_CAx12) / 4, 0, 0x12, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; parray unused2; } __packed__; // 6xB3x13 / CAx13: Set map state during setup -struct G_SetMapState_Ep3_6xB3x13_CAx13 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetMapState_Ep3_6xB3x13_CAx13) / 4, 0, 0x13, 0, 0, 0, 0, 0}; +struct G_SetMapState_Ep3NTE_CAx13 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetMapState_Ep3NTE_CAx13) / 4, 0, 0x13, 0, 0, 0, 0, 0}; + Episode3::MapAndRulesStateTrial map_and_rules_state; + Episode3::OverlayState overlay_state; +} __packed__; + +struct G_SetMapState_Ep3_CAx13 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetMapState_Ep3_CAx13) / 4, 0, 0x13, 0, 0, 0, 0, 0}; Episode3::MapAndRulesState map_and_rules_state; Episode3::OverlayState overlay_state; } __packed__; // 6xB3x14 / CAx14: Set player deck during setup -struct G_SetPlayerDeck_Ep3_6xB3x14_CAx14 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetPlayerDeck_Ep3_6xB3x14_CAx14) / 4, 0, 0x14, 0, 0, 0, 0, 0}; +struct G_SetPlayerDeck_Ep3_CAx14 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetPlayerDeck_Ep3_CAx14) / 4, 0, 0x14, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; uint8_t is_cpu_player = 0; uint8_t unused2 = 0; @@ -6327,8 +6333,8 @@ struct G_SetPlayerDeck_Ep3_6xB3x14_CAx14 { // 6xB3x15 / CAx15: Hard-reset server state // This command appears to be completely unused; the client never sends it. -struct G_HardResetServerState_Ep3_6xB3x15_CAx15 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_HardResetServerState_Ep3_6xB3x15_CAx15) / 4, 0, 0x15, 0, 0, 0, 0, 0}; +struct G_HardResetServerState_Ep3_CAx15 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_HardResetServerState_Ep3_CAx15) / 4, 0, 0x15, 0, 0, 0, 0, 0}; // No arguments } __packed__; @@ -6356,8 +6362,8 @@ struct G_ForceDisconnect_Ep3_6xB5x1A { // Curiously, this command can be used during a non-setup phase; the server // should ignore the command's contents but still send a 6xB4x1C in response. -struct G_SetPlayerName_Ep3_6xB3x1B_CAx1B { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetPlayerName_Ep3_6xB3x1B_CAx1B) / 4, 0, 0x1B, 0, 0, 0, 0, 0}; +struct G_SetPlayerName_Ep3_CAx1B { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetPlayerName_Ep3_CAx1B) / 4, 0, 0x1B, 0, 0, 0, 0, 0}; Episode3::NameEntry entry; } __packed__; @@ -6373,8 +6379,8 @@ struct G_SetPlayerNames_Ep3_6xB4x1C { // response to this command) that includes RegistrationPhase::BATTLE_STARTED and // a SetupPhase value other than REGISTRATION. -struct G_StartBattle_Ep3_6xB3x1D_CAx1D { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_StartBattle_Ep3_6xB3x1D_CAx1D) / 4, 0, 0x1D, 0, 0, 0, 0, 0}; +struct G_StartBattle_Ep3_CAx1D { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_StartBattle_Ep3_CAx1D) / 4, 0, 0x1D, 0, 0, 0, 0, 0}; } __packed__; // 6xB4x1E: Action result @@ -6412,8 +6418,8 @@ struct G_Unknown_Ep3_6xB5x20 { // 6xB3x21 / CAx21: End battle -struct G_EndBattle_Ep3_6xB3x21_CAx21 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndBattle_Ep3_6xB3x21_CAx21) / 4, 0, 0x21, 0, 0, 0, 0, 0}; +struct G_EndBattle_Ep3_CAx21 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndBattle_Ep3_CAx21) / 4, 0, 0x21, 0, 0, 0, 0, 0}; le_uint32_t unused2 = 0; } __packed__; @@ -6455,8 +6461,8 @@ struct G_Unknown_Ep3_6xB5x27 { // This command informs the server that the client is done playing defense // cards. (In the attack phase, CAx12 is used instead.) -struct G_EndDefenseList_Ep3_6xB3x28_CAx28 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndDefenseList_Ep3_6xB3x28_CAx28) / 4, 0, 0x28, 0, 0, 0, 0, 0}; +struct G_EndDefenseList_Ep3_CAx28 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndDefenseList_Ep3_CAx28) / 4, 0, 0x28, 0, 0, 0, 0, 0}; uint8_t unused1 = 0; uint8_t client_id = 0; parray unused2; @@ -6488,8 +6494,8 @@ struct G_Unknown_Ep3_6xB4x2A { // It seems Sega's servers completely ignored this command. The command name is // based on a debug message found nearby. -struct G_ExecLegacyCard_Ep3_6xB3x2B_CAx2B { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_ExecLegacyCard_Ep3_6xB3x2B_CAx2B) / 4, 0, 0x2B, 0, 0, 0, 0, 0}; +struct G_ExecLegacyCard_Ep3_CAx2B { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_ExecLegacyCard_Ep3_CAx2B) / 4, 0, 0x2B, 0, 0, 0, 0, 0}; le_uint16_t unused2 = 0; parray unused3; } __packed__; @@ -6592,8 +6598,8 @@ struct G_SubtractAllyATKPoints_Ep3_6xB4x33 { // 6xB3x34 / CAx34: Photon blast request -struct G_PhotonBlastRequest_Ep3_6xB3x34_CAx34 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_PhotonBlastRequest_Ep3_6xB3x34_CAx34) / 4, 0, 0x34, 0, 0, 0, 0, 0}; +struct G_PhotonBlastRequest_Ep3_CAx34 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_PhotonBlastRequest_Ep3_CAx34) / 4, 0, 0x34, 0, 0, 0, 0, 0}; uint8_t ally_client_id = 0; uint8_t reason = 0; le_uint16_t card_ref = 0xFFFF; @@ -6624,8 +6630,8 @@ struct G_RecreatePlayer_Ep3_6xB5x36 { // 6xB3x37 / CAx37: Ready to advance from starting rolls phase -struct G_AdvanceFromStartingRollsPhase_Ep3_6xB3x37_CAx37 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_AdvanceFromStartingRollsPhase_Ep3_6xB3x37_CAx37) / 4, 0, 0x37, 0, 0, 0, 0, 0}; +struct G_AdvanceFromStartingRollsPhase_Ep3_CAx37 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_AdvanceFromStartingRollsPhase_Ep3_CAx37) / 4, 0, 0x37, 0, 0, 0, 0, 0}; uint8_t client_id = 0; parray unused2; } __packed__; @@ -6661,8 +6667,8 @@ struct G_UpdateAllPlayerStatistics_Ep3_6xB4x39 { // It seems Sega's servers completely ignored this command and used server-side // timing instead. newserv does the same. -struct G_OverallTimeLimitExpired_Ep3_6xB3x3A_CAx3A { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_OverallTimeLimitExpired_Ep3_6xB3x3A_CAx3A) / 4, 0, 0x3A, 0, 0, 0, 0, 0}; +struct G_OverallTimeLimitExpired_Ep3_CAx3A { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_OverallTimeLimitExpired_Ep3_CAx3A) / 4, 0, 0x3A, 0, 0, 0, 0, 0}; } __packed__; // 6xB4x3B: Load current environment @@ -6748,16 +6754,16 @@ struct G_OpenBlockingMenu_Ep3_6xB5x3F { // 6xB3x40 / CAx40: Request map list // The server should respond with a 6xB6x40 command. -struct G_MapListRequest_Ep3_6xB3x40_CAx40 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MapListRequest_Ep3_6xB3x40_CAx40) / 4, 0, 0x40, 0, 0, 0, 0, 0}; +struct G_MapListRequest_Ep3_CAx40 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MapListRequest_Ep3_CAx40) / 4, 0, 0x40, 0, 0, 0, 0, 0}; } __packed__; // 6xB3x41 / CAx41: Request map data // The server should respond with a 6xB6x41 command containing the definition of // the specified map. -struct G_MapDataRequest_Ep3_6xB3x41_CAx41 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MapDataRequest_Ep3_6xB3x41_CAx41) / 4, 0, 0x41, 0, 0, 0, 0, 0}; +struct G_MapDataRequest_Ep3_CAx41 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MapDataRequest_Ep3_CAx41) / 4, 0, 0x41, 0, 0, 0, 0, 0}; le_uint32_t map_number = 0; } __packed__; @@ -6865,8 +6871,8 @@ struct G_SetSpectatorCARDLevel_Ep3_6xB5x47 { // 6xB3x48 / CAx48: End turn -struct G_EndTurn_Ep3_6xB3x48_CAx48 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndTurn_Ep3_6xB3x48_CAx48) / 4, 0, 0x48, 0, 0, 0, 0, 0}; +struct G_EndTurn_Ep3_CAx48 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndTurn_Ep3_CAx48) / 4, 0, 0x48, 0, 0, 0, 0, 0}; uint8_t client_id = 0; parray unused2; } __packed__; @@ -6883,8 +6889,8 @@ struct G_EndTurn_Ep3_6xB3x48_CAx48 { // that callsite to implement one of the deck validity checks. // Episode 3 Trial Edition does not send this command. -struct G_CardCounts_Ep3_6xB3x49_CAx49 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_CardCounts_Ep3_6xB3x49_CAx49) / 4, 0, 0x49, 0, 0, 0, 0, 0}; +struct G_CardCounts_Ep3_CAx49 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_CardCounts_Ep3_CAx49) / 4, 0, 0x49, 0, 0, 0, 0, 0}; uint8_t basis = 0; parray unused; // This is encrypted with the trivial algorithm (see decrypt_trivial_gci_data) diff --git a/src/Episode3/Card.cc b/src/Episode3/Card.cc index ebc85a5c..c6a236f2 100644 --- a/src/Episode3/Card.cc +++ b/src/Episode3/Card.cc @@ -122,14 +122,16 @@ ssize_t Card::apply_abnormal_condition( int16_t value, int8_t dice_roll_value, int8_t random_percent) { - auto log = this->server()->log_stack(string_printf("apply_abnormal_condition(%02hhX, @%04X, @%04X, %hd, %hhd, %hhd): ", def_effect_index, target_card_ref, sc_card_ref, value, dice_roll_value, random_percent)); + auto s = this->server(); + auto log = s->log_stack(string_printf("apply_abnormal_condition(%02hhX, @%04X, @%04X, %hd, %hhd, %hhd): ", def_effect_index, target_card_ref, sc_card_ref, value, dice_roll_value, random_percent)); + bool is_trial = s->options.is_trial(); ssize_t existing_cond_index; for (size_t z = 0; z < this->action_chain.conditions.size(); z++) { const auto& cond = this->action_chain.conditions[z]; if (cond.type == eff.type) { existing_cond_index = z; - if (eff.type == ConditionType::MV_BONUS || + if ((!is_trial && eff.type == ConditionType::MV_BONUS) || ((cond.card_definition_effect_index == def_effect_index) && (cond.card_ref == target_card_ref))) { break; @@ -165,7 +167,7 @@ ssize_t Card::apply_abnormal_condition( log.debug("MV_BONUS combines => existing_cond_value = %hd", existing_cond_value); } - this->server()->card_special->apply_stat_deltas_to_card_from_condition_and_clear_cond(cond, this->shared_from_this()); + s->card_special->apply_stat_deltas_to_card_from_condition_and_clear_cond(cond, this->shared_from_this()); cond.type = eff.type; cond.card_ref = target_card_ref; cond.condition_giver_card_ref = sc_card_ref; @@ -205,14 +207,15 @@ ssize_t Card::apply_abnormal_condition( string cond_str = cond.str(); log.debug("wrote condition %zd => %s", cond_index, cond_str.c_str()); - this->server()->card_special->update_condition_orders(this->shared_from_this()); - - for (size_t z = 0; z < this->action_chain.conditions.size(); z++) { - if (this->action_chain.conditions[z].type == ConditionType::NONE) { - continue; + if (!is_trial) { + s->card_special->update_condition_orders(this->shared_from_this()); + for (size_t z = 0; z < this->action_chain.conditions.size(); z++) { + if (this->action_chain.conditions[z].type == ConditionType::NONE) { + continue; + } + string cond_str = cond.str(); + log.debug("sorted conditions: [%zu] => %s", z, cond_str.c_str()); } - string cond_str = cond.str(); - log.debug("sorted conditions: [%zu] => %s", z, cond_str.c_str()); } return cond_index; @@ -368,25 +371,27 @@ int16_t Card::compute_defense_power_for_attacker_card( } void Card::destroy_set_card(shared_ptr attacker_card) { + auto s = this->server(); + auto ps = this->player_state(); + this->current_hp = 0; if (!(this->card_flags & 2)) { - if (!this->server()->ruler_server->card_ref_or_any_set_card_has_condition_46(this->card_ref)) { - this->server()->card_special->on_card_destroyed( + if (!s->ruler_server->card_ref_or_any_set_card_has_condition_46(this->card_ref)) { + s->card_special->on_card_destroyed( attacker_card, this->shared_from_this()); this->card_flags = this->card_flags | 2; this->update_stats_on_destruction(); - this->player_state()->stats.num_owned_cards_destroyed++; + ps->stats.num_owned_cards_destroyed++; if (attacker_card && (attacker_card->team_id != this->team_id)) { attacker_card->player_state()->stats.num_opponent_cards_destroyed++; - this->server()->add_team_exp(this->team_id ^ 1, 3); + s->add_team_exp(this->team_id ^ 1, 3); } if ((this->sc_card_type == CardType::HUNTERS_SC) && (this->def_entry->def.type == CardType::ITEM)) { - auto sc_card = this->player_state()->get_sc_card(); - if (!(sc_card->card_flags & 2) && - !sc_card->get_attack_condition_value(ConditionType::ELUDE, 0xFFFF, 0xFF, 0xFFFF, nullptr)) { + auto sc_card = ps->get_sc_card(); + if (!(sc_card->card_flags & 2) && !sc_card->get_condition_value(ConditionType::ELUDE)) { int16_t hp = sc_card->get_current_hp(); sc_card->set_current_hp(hp - 1); sc_card->player_state()->stats.sc_damage_taken++; @@ -396,7 +401,7 @@ void Card::destroy_set_card(shared_ptr attacker_card) { cmd.effect.attacker_card_ref = attacker_card->card_ref; cmd.effect.target_card_ref = sc_card->card_ref; cmd.effect.value = 1; - this->server()->send(cmd); + s->send(cmd); } if (sc_card->get_current_hp() < 1) { sc_card->destroy_set_card(attacker_card); @@ -404,10 +409,10 @@ void Card::destroy_set_card(shared_ptr attacker_card) { } } - if ((this->server()->map_and_rules->rules.hp_type == HPType::DEFEAT_TEAM) && - (this->player_state()->get_sc_card().get() == this)) { + if ((s->map_and_rules->rules.hp_type == HPType::DEFEAT_TEAM) && + (ps->get_sc_card().get() == this)) { for (size_t set_index = 0; set_index < 8; set_index++) { - auto card = this->player_state()->get_set_card(set_index); + auto card = ps->get_set_card(set_index); if (card) { card->card_flags |= 2; } @@ -415,27 +420,27 @@ void Card::destroy_set_card(shared_ptr attacker_card) { } for (size_t client_id = 0; client_id < 4; client_id++) { - if (!this->server()->player_states[client_id]) { + if (!s->player_states[client_id]) { continue; } - size_t num_assists = this->server()->assist_server->compute_num_assist_effects_for_client(client_id); + size_t num_assists = s->assist_server->compute_num_assist_effects_for_client(client_id); for (size_t z = 0; z < num_assists; z++) { - auto eff = this->server()->assist_server->get_active_assist_by_index(z); + auto eff = s->assist_server->get_active_assist_by_index(z); if (eff == AssistEffect::HOMESICK) { if (client_id == this->client_id) { - this->player_state()->return_set_card_to_hand2(this->card_ref); + ps->return_set_card_to_hand2(this->card_ref); } } else if (eff == AssistEffect::INHERITANCE) { - uint8_t other_team_id = this->server()->player_states[client_id]->get_team_id(); - uint8_t this_team_id = this->player_state()->get_team_id(); + uint8_t other_team_id = s->player_states[client_id]->get_team_id(); + uint8_t this_team_id = ps->get_team_id(); if (this_team_id == other_team_id) { - this->server()->add_team_exp(team_id, this->max_hp); + s->add_team_exp(team_id, this->max_hp); } } } } - } else if (this->w_destroyer_sc_card.expired() && attacker_card) { + } else if (!this->w_destroyer_sc_card.lock() && attacker_card) { this->w_destroyer_sc_card = attacker_card->player_state()->get_sc_card(); } } @@ -560,14 +565,27 @@ void Card::execute_attack(shared_ptr attacker_card) { } } -bool Card::get_attack_condition_value( +bool Card::get_condition_value( ConditionType cond_type, uint16_t card_ref, uint8_t def_effect_index, uint16_t value, uint16_t* out_value) const { - return this->action_chain.get_condition_value( - cond_type, card_ref, def_effect_index, value, out_value); + return this->action_chain.get_condition_value(cond_type, card_ref, def_effect_index, value, out_value); +} + +Condition* Card::find_condition(ConditionType cond_type) { + for (size_t z = 0; z < this->action_chain.conditions.size(); z++) { + auto& cond = this->action_chain.conditions[z]; + if (cond.type == cond_type) { + return &cond; + } + } + return nullptr; +} + +const Condition* Card::find_condition(ConditionType cond_type) const { + return const_cast(this)->find_condition(cond_type); } shared_ptr Card::get_definition() const { @@ -808,10 +826,12 @@ void Card::clear_action_chain_and_metadata_and_most_flags() { this->action_metadata.card_ref = this->card_ref; } -void Card::compute_action_chain_results( - bool apply_action_conditions, bool ignore_this_card_ap_tp) { - auto log = this->server()->log_stack(string_printf("compute_action_chain_results(@%04hX #%04hX): ", this->get_card_ref(), this->get_card_id())); - this->action_chain.compute_attack_medium(this->server()); +void Card::compute_action_chain_results(bool apply_action_conditions, bool ignore_this_card_ap_tp) { + auto s = this->server(); + auto log = s->log_stack(string_printf("compute_action_chain_results(@%04hX #%04hX): ", this->get_card_ref(), this->get_card_id())); + bool is_trial = s->options.is_trial(); + + this->action_chain.compute_attack_medium(s); this->action_chain.chain.strike_count = 1; this->action_chain.chain.ap_effect_bonus = 0; this->action_chain.chain.tp_effect_bonus = 0; @@ -824,16 +844,19 @@ void Card::compute_action_chain_results( int16_t card_ap; int16_t card_tp; - auto stat_swap_type = this->server()->card_special->compute_stat_swap_type(this->shared_from_this()); + auto stat_swap_type = is_trial ? StatSwapType::NONE : s->card_special->compute_stat_swap_type(this->shared_from_this()); log.debug("stat_swap_type = %zu (0=none, 1=a/t, 2=a/h)", static_cast(stat_swap_type)); - this->server()->card_special->get_effective_ap_tp( - stat_swap_type, &card_ap, &card_tp, this->get_current_hp(), this->ap, this->tp); + s->card_special->get_effective_ap_tp(stat_swap_type, &card_ap, &card_tp, this->get_current_hp(), this->ap, this->tp); log.debug("card_ap = %hd, card_tp = %hd", card_ap, card_tp); - int16_t effective_ap = card_ap; - int16_t effective_tp = card_tp; + int16_t effective_ap = this->ap; + int16_t effective_tp = this->tp; + + // This option doesn't exist in NTE + ignore_this_card_ap_tp &= !is_trial; + for (size_t z = 0; (!ignore_this_card_ap_tp && (z < 8) && (z < this->action_chain.chain.attack_action_card_ref_count)); z++) { - auto ce = this->server()->definition_for_card_ref(this->action_chain.chain.attack_action_card_refs[z]); + auto ce = s->definition_for_card_ref(this->action_chain.chain.attack_action_card_refs[z]); if (ce) { effective_ap += ce->def.ap.stat; effective_tp += ce->def.tp.stat; @@ -842,11 +865,12 @@ void Card::compute_action_chain_results( } // Add AP/TP from MAG items to SC's AP/TP + auto ps = this->player_state(); if (this->def_entry->def.is_sc()) { for (size_t set_index = 0; set_index < 8; set_index++) { - auto card = this->player_state()->get_set_card(set_index); + auto card = ps->get_set_card(set_index); if ((card && (card->def_entry->def.card_class() == CardClass::MAG_ITEM)) && !(card->card_flags & 2)) { - this->server()->card_special->get_effective_ap_tp( + s->card_special->get_effective_ap_tp( stat_swap_type, &card_ap, &card_tp, card->get_current_hp(), card->ap, card->tp); effective_ap += card_ap; effective_tp += card_tp; @@ -857,7 +881,7 @@ void Card::compute_action_chain_results( } if ((this->def_entry->def.type == CardType::ITEM) && this->sc_def_entry) { - auto sc_card = this->player_state()->get_sc_card(); + auto sc_card = ps->get_sc_card(); sc_card->compute_action_chain_results(apply_action_conditions, true); effective_ap += sc_card->action_chain.chain.effective_ap + sc_card->action_chain.chain.ap_effect_bonus; effective_tp += sc_card->action_chain.chain.effective_tp + sc_card->action_chain.chain.tp_effect_bonus; @@ -866,50 +890,58 @@ void Card::compute_action_chain_results( } if (!this->action_chain.check_flag(0x10)) { - this->action_chain.chain.effective_ap = min(effective_ap, 99); + this->action_chain.chain.effective_ap = is_trial ? effective_ap : min(effective_ap, 99); log.debug("set chain effective_ap = %hd", this->action_chain.chain.effective_ap); } if (!this->action_chain.check_flag(0x20)) { - this->action_chain.chain.effective_tp = min(effective_tp, 99); + this->action_chain.chain.effective_tp = is_trial ? effective_tp : min(effective_tp, 99); log.debug("set chain effective_tp = %hd", this->action_chain.chain.effective_tp); } if (apply_action_conditions) { - this->server()->card_special->apply_action_conditions( - 3, this->shared_from_this(), this->shared_from_this(), 1, nullptr); + auto this_sh = this->shared_from_this(); + s->card_special->apply_action_conditions(3, this_sh, this_sh, 1, nullptr); log.debug("applied action conditions (1)"); } else { log.debug("skipped applying action conditions (1)"); } - size_t num_assists = this->server()->assist_server->compute_num_assist_effects_for_client(this->client_id); + size_t num_assists = s->assist_server->compute_num_assist_effects_for_client(this->client_id); for (size_t z = 0; z < num_assists; z++) { - switch (this->server()->assist_server->get_active_assist_by_index(z)) { + switch (s->assist_server->get_active_assist_by_index(z)) { case AssistEffect::POWERLESS_RAIN: - if (this->card_type_is_sc_or_creature() && + if (!is_trial && + this->card_type_is_sc_or_creature() && (this->action_chain.chain.attack_medium == AttackMedium::PHYSICAL)) { this->action_chain.chain.ap_effect_bonus -= 2; } break; case AssistEffect::BRAVE_WIND: - if (this->card_type_is_sc_or_creature() && + if (!is_trial && + this->card_type_is_sc_or_creature() && (this->action_chain.chain.attack_medium == AttackMedium::PHYSICAL)) { this->action_chain.chain.ap_effect_bonus += 2; } break; case AssistEffect::INFLUENCE: - if (this->card_type_is_sc_or_creature()) { - int16_t count = this->player_state()->count_set_refs(); + if (!is_trial && + this->card_type_is_sc_or_creature()) { + int16_t count = ps->count_set_refs(); this->action_chain.chain.ap_effect_bonus += (count >> 1); } break; case AssistEffect::AP_ABSORPTION: - if (this->action_chain.chain.attack_medium == AttackMedium::TECH) { + if (!is_trial && (this->action_chain.chain.attack_medium == AttackMedium::TECH)) { this->action_chain.chain.tp_effect_bonus += 2; } break; + case AssistEffect::FIX: + if (is_trial && !this->def_entry->def.is_sc()) { + this->action_chain.chain.ap_effect_bonus = 2 - this->action_chain.chain.card_ap; + } + break; case AssistEffect::TECH_FIELD: - if (this->card_type_is_sc_or_creature()) { + if (is_trial ? this->def_entry->def.is_sc() : this->card_type_is_sc_or_creature()) { this->action_chain.chain.tp_effect_bonus += 2; } break; @@ -962,7 +994,7 @@ void Card::compute_action_chain_results( if (this->def_entry->def.is_sc()) { size_t num_scs_in_range = 0; for (size_t client_id = 0; client_id < 4; client_id++) { - auto other_ps = this->server()->get_player_state(client_id); + auto other_ps = s->get_player_state(client_id); if (!other_ps || (client_id == this->client_id) || (other_ps->get_team_id() != this->team_id)) { continue; } @@ -980,8 +1012,8 @@ void Card::compute_action_chain_results( break; case AssistEffect::VENGEANCE: if (!this->def_entry->def.is_sc()) { - this->action_chain.chain.ap_effect_bonus += - (this->server()->team_num_ally_fcs_destroyed[this->team_id] / 3); + size_t denom = is_trial ? 2 : 3; + this->action_chain.chain.ap_effect_bonus += (s->team_num_ally_fcs_destroyed[this->team_id] / denom); } break; default: @@ -999,42 +1031,46 @@ void Card::compute_action_chain_results( } else { log.debug("(unknown attack medium) damage = 0"); } - this->action_chain.chain.damage = min( - damage * this->action_chain.chain.damage_multiplier, 99); + + this->action_chain.chain.damage = is_trial + ? (damage * this->action_chain.chain.damage_multiplier) + : min(damage * this->action_chain.chain.damage_multiplier, 99); log.debug("overall chain damage = %hd (base) * %hhd (mult) = %hhd", damage, this->action_chain.chain.damage_multiplier, this->action_chain.chain.damage); if (apply_action_conditions) { - this->server()->card_special->apply_action_conditions( - 0x03, this->shared_from_this(), this->shared_from_this(), 2, nullptr); + auto this_sh = this->shared_from_this(); + s->card_special->apply_action_conditions(0x03, this_sh, this_sh, 2, nullptr); log.debug("applied action conditions (2)"); - if (this->action_chain.check_flag(0x100)) { + if (!is_trial && this->action_chain.check_flag(0x100)) { this->action_chain.chain.damage = min(this->action_chain.chain.damage + 5, 99); log.debug("(has flag 0x100) chain damage = %hhd", this->action_chain.chain.damage); } } else { - log.debug("applied action conditions (2)"); + log.debug("skipped applying action conditions (2)"); } - num_assists = this->server()->assist_server->compute_num_assist_effects_for_client(this->get_client_id()); - for (size_t z = 0; z < num_assists; z++) { - switch (this->server()->assist_server->get_active_assist_by_index(z)) { - case AssistEffect::AP_ABSORPTION: - if (this->action_chain.chain.attack_medium == AttackMedium::PHYSICAL) { - this->action_chain.chain.damage = 0; - } - break; - case AssistEffect::SILENT_COLOSSEUM: - if (this->action_chain.chain.damage >= 7) { - this->action_chain.chain.damage = 0; - } - break; - case AssistEffect::FIX: - if (!this->def_entry->def.is_sc()) { - this->action_chain.chain.damage = 2; - } - break; - default: - break; + if (!is_trial) { + num_assists = s->assist_server->compute_num_assist_effects_for_client(this->get_client_id()); + for (size_t z = 0; z < num_assists; z++) { + switch (s->assist_server->get_active_assist_by_index(z)) { + case AssistEffect::AP_ABSORPTION: + if (this->action_chain.chain.attack_medium == AttackMedium::PHYSICAL) { + this->action_chain.chain.damage = 0; + } + break; + case AssistEffect::SILENT_COLOSSEUM: + if (this->action_chain.chain.damage >= 7) { + this->action_chain.chain.damage = 0; + } + break; + case AssistEffect::FIX: + if (!this->def_entry->def.is_sc()) { + this->action_chain.chain.damage = 2; + } + break; + default: + break; + } } } } diff --git a/src/Episode3/Card.hh b/src/Episode3/Card.hh index 3b433d58..b662e782 100644 --- a/src/Episode3/Card.hh +++ b/src/Episode3/Card.hh @@ -51,12 +51,14 @@ public: void destroy_set_card(std::shared_ptr attacker_card); int32_t error_code_for_move_to_location(const Location& loc) const; void execute_attack(std::shared_ptr attacker_card); - bool get_attack_condition_value( + bool get_condition_value( ConditionType cond_type, - uint16_t card_ref, - uint8_t def_effect_index, - uint16_t value, - uint16_t* out_value) const; + uint16_t card_ref = 0xFFFF, + uint8_t def_effect_index = 0xFF, + uint16_t value = 0xFFFF, + uint16_t* out_value = nullptr) const; + Condition* find_condition(ConditionType cond_type); + const Condition* find_condition(ConditionType cond_type) const; std::shared_ptr get_definition() const; uint16_t get_card_ref() const; uint16_t get_card_id() const; diff --git a/src/Episode3/CardSpecial.cc b/src/Episode3/CardSpecial.cc index 7b500322..f4c40980 100644 --- a/src/Episode3/CardSpecial.cc +++ b/src/Episode3/CardSpecial.cc @@ -240,8 +240,8 @@ void CardSpecial::apply_action_conditions( shared_ptr defender_card, uint32_t flags, const ActionState* as) { - ActionState temp_as; + ActionState temp_as; if (attacker_card == defender_card) { temp_as = this->create_attack_state_from_card_action_chain(attacker_card); if (as) { @@ -251,6 +251,7 @@ void CardSpecial::apply_action_conditions( temp_as = this->create_defense_state_for_card_pair_action_chains( attacker_card, defender_card); } + this->apply_defense_conditions(temp_as, when, defender_card, flags); } @@ -304,84 +305,11 @@ bool CardSpecial::apply_defense_condition( attacker_card_ref = defense_state.original_attacker_card_ref; } - bool defender_has_ability_trap = this->card_ref_has_ability_trap(*defender_cond); + auto s = this->server(); + bool is_trial = s->options.is_trial(); + bool defender_has_ability_trap = !is_trial && this->card_ref_has_ability_trap(*defender_cond); - if (!(flags & 4) || - this->is_card_targeted_by_condition(*defender_cond, defense_state, defender_card)) { - if ((when == 2) && (defender_cond->type == ConditionType::GUOM) && (flags & 4)) { - CardShortStatus stat = defender_card->get_short_status(); - if (stat.card_flags & 4) { - G_ApplyConditionEffect_Ep3_6xB4x06 cmd; - cmd.effect.flags = 0x04; - cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x0E); - cmd.effect.target_card_ref = defender_card->get_card_ref(); - cmd.effect.value = 0; - cmd.effect.operation = -static_cast(defender_cond->type); - cmd.effect.condition_index = cond_index; - this->server()->send(cmd); - this->apply_stat_deltas_to_card_from_condition_and_clear_cond( - *defender_cond, defender_card); - defender_card->send_6xB4x4E_4C_4D_if_needed(); - return false; - } - } - - if ((when == 4) && (flags & 4) && !defender_has_ability_trap && - (defender_cond->type == ConditionType::ACID)) { - int16_t hp = defender_card->get_current_hp(); - if (hp > 0) { - this->send_6xB4x06_for_stat_delta( - defender_card, defender_cond->card_ref, 0x20, -1, 0, 1); - defender_card->set_current_hp(hp - 1); - this->destroy_card_if_hp_zero(defender_card, defender_cond->condition_giver_card_ref); - } - } - - if (!orig_eff || (orig_eff->when != when)) { - flags = flags & 0xFFFFFFFB; - } - - if ((flags == 0) || defender_has_ability_trap) { - return false; - } - - DiceRoll dice_roll; - dice_roll.client_id = defender_card->get_client_id(); - dice_roll.unknown_a2 = 3; - dice_roll.value = defender_cond->dice_roll_value; - dice_roll.value_used_in_expr = false; - uint8_t original_cond_flags = defender_cond->flags; - - auto astats = this->compute_attack_env_stats( - defense_state, defender_card, dice_roll, defender_cond->card_ref, - defender_cond->condition_giver_card_ref); - - string expr = orig_eff->expr.decode(); - int16_t expr_value = this->evaluate_effect_expr(astats, expr.c_str(), dice_roll); - this->execute_effect( - *defender_cond, defender_card, expr_value, defender_cond->value, - orig_eff->type, flags, attacker_card_ref); - if (flags & 4) { - if (!(defender_card->card_flags & 2)) { - defender_card->compute_action_chain_results(true, false); - } - defender_card->action_chain.chain.card_ap = defender_card->ap; - defender_card->action_chain.chain.card_tp = defender_card->tp; - defender_card->send_6xB4x4E_4C_4D_if_needed(); - } - - if (dice_roll.value_used_in_expr && !(original_cond_flags & 1) && !unknown_p8) { - defender_cond->flags |= 1; - G_ApplyConditionEffect_Ep3_6xB4x06 cmd; - cmd.effect.flags = 0x08; - cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x10); - cmd.effect.target_card_ref = defender_cond->card_ref; - cmd.effect.dice_roll_value = dice_roll.value; - this->server()->send(cmd); - } - return true; - - } else { + if ((is_trial || (flags & 4)) && !this->is_card_targeted_by_condition(*defender_cond, defense_state, defender_card)) { if (defender_cond->type != ConditionType::NONE) { G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; @@ -389,13 +317,101 @@ bool CardSpecial::apply_defense_condition( cmd.effect.target_card_ref = defender_card->get_card_ref(); cmd.effect.value = 0; cmd.effect.operation = -static_cast(defender_cond->type); - this->server()->send(cmd); + s->send(cmd); } - this->apply_stat_deltas_to_card_from_condition_and_clear_cond( - *defender_cond, defender_card); + this->apply_stat_deltas_to_card_from_condition_and_clear_cond(*defender_cond, defender_card); defender_card->send_6xB4x4E_4C_4D_if_needed(); return false; } + + if ((when == 0x02) && (defender_cond->type == ConditionType::GUOM) && (flags & 4)) { + CardShortStatus stat = defender_card->get_short_status(); + if (stat.card_flags & 4) { + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; + cmd.effect.flags = 0x04; + cmd.effect.attacker_card_ref = attacker_card_ref; + cmd.effect.target_card_ref = defender_card->get_card_ref(); + cmd.effect.value = 0; + cmd.effect.operation = -static_cast(defender_cond->type); + cmd.effect.condition_index = cond_index; + s->send(cmd); + this->apply_stat_deltas_to_card_from_condition_and_clear_cond(*defender_cond, defender_card); + defender_card->send_6xB4x4E_4C_4D_if_needed(); + return false; + } + } + + if (s->options.is_trial()) { + auto defender_ps = defender_card->player_state(); + if ((when == 0x0F) && (flags & 4) && (defender_cond->type == ConditionType::DROP) && defender_ps) { + auto defender_sc_card = defender_ps->get_sc_card(); + uint8_t defender_team_id = defender_ps->get_team_id(); + if (defender_sc_card && s->team_exp[defender_team_id]) { + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; + cmd.effect.flags = 0x04; + cmd.effect.attacker_card_ref = defender_cond->card_ref; + cmd.effect.target_card_ref = defender_sc_card->get_card_ref(); + cmd.effect.value = 0; + cmd.effect.operation = 0x2E; + s->send(cmd); + } + s->team_exp[defender_team_id] = max(s->team_exp[defender_team_id] - 3, 0); + this->compute_team_dice_bonus(defender_team_id); + } + } + + if ((when == 0x04) && (flags & 4) && (defender_cond->type == ConditionType::ACID)) { + int16_t hp = defender_card->get_current_hp(); + if (hp > 0) { + this->send_6xB4x06_for_stat_delta(defender_card, defender_cond->card_ref, 0x20, -1, 0, 1); + defender_card->set_current_hp(hp - 1); + this->destroy_card_if_hp_zero(defender_card, defender_cond->condition_giver_card_ref); + } + } + + if (!orig_eff || (orig_eff->when != when)) { + flags &= ~4; + } + if ((flags == 0) || defender_has_ability_trap) { + return false; + } + + DiceRoll dice_roll; + dice_roll.client_id = defender_card->get_client_id(); + dice_roll.unknown_a2 = 3; + dice_roll.value = defender_cond->dice_roll_value; + dice_roll.value_used_in_expr = false; + uint8_t original_cond_flags = defender_cond->flags; + + auto astats = this->compute_attack_env_stats( + defense_state, defender_card, dice_roll, defender_cond->card_ref, + defender_cond->condition_giver_card_ref); + + string expr = orig_eff->expr.decode(); + int16_t expr_value = this->evaluate_effect_expr(astats, expr.c_str(), dice_roll); + this->execute_effect( + *defender_cond, defender_card, expr_value, defender_cond->value, + orig_eff->type, flags, attacker_card_ref); + if (flags & 4) { + if (is_trial || !(defender_card->card_flags & 2)) { + defender_card->compute_action_chain_results(true, false); + } + defender_card->action_chain.chain.card_ap = defender_card->ap; + defender_card->action_chain.chain.card_tp = defender_card->tp; + defender_card->send_6xB4x4E_4C_4D_if_needed(); + } + + if (dice_roll.value_used_in_expr && !(original_cond_flags & 1) && !unknown_p8) { + defender_cond->flags |= 1; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; + cmd.effect.flags = 0x08; + cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x10); + cmd.effect.target_card_ref = defender_cond->card_ref; + cmd.effect.dice_roll_value = dice_roll.value; + s->send(cmd); + } + + return true; } bool CardSpecial::apply_defense_conditions( @@ -442,6 +458,7 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit log.debug("cond: %s", cond_str.c_str()); ConditionType cond_type = cond.type; + // Note: NTE does not clamp the value here. int16_t cond_value = clamp(cond.value, -99, 99); uint8_t cond_flags = cond.flags; uint16_t cond_card_ref = card->get_card_ref(); @@ -485,15 +502,10 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit break; case ConditionType::AP_OVERRIDE: if (cond_flags & 2) { - // Note: The original code calls a function here that returns a - // Condition pointer; however, the called function searches the card's - // condition list and then ignores the result and unconditionally - // returns null, completely obviating the non-null case here. We - // implement the non-null case for documentation purposes, but it - // appears to be completely dead code. It's unclear if this is a legit - // bug in the original code, or if it was a debug feature or - // late-development intentional change. - Condition* other_cond = nullptr; // return_null???(card, ConditionType::AP_OVERRIDE); + // Note: In NTE, this case behaves intuitively, but in non-NTE, it seems + // that find_condition was changed to always return null. Perhaps this + // was an accident, or perhaps not, but we implement both behaviors. + Condition* other_cond = s->options.is_trial() ? card->find_condition(ConditionType::AP_OVERRIDE) : nullptr; if (!other_cond) { if (!s->options.is_trial()) { this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, -cond_value, 0, 0); @@ -509,9 +521,8 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit break; case ConditionType::TP_OVERRIDE: if (cond_flags & 2) { - // Like AP_OVERRIDE above, the non-null case here is dead code in the - // original code as well. - Condition* other_cond = nullptr; // return_null???(card, ConditionType::TP_OVERRIDE) + // See note in the AP_OVERRIDE case about why non-NTE always uses null. + Condition* other_cond = s->options.is_trial() ? card->find_condition(ConditionType::TP_OVERRIDE) : nullptr; if (!other_cond) { if (!s->options.is_trial()) { this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, -cond_value, 0, 0); @@ -667,12 +678,10 @@ void CardSpecial::compute_attack_ap( } } - if (attacker_card && - attacker_card->get_attack_condition_value(ConditionType::UNKNOWN_7D, 0xFFFF, 0xFF, 0xFFFF, nullptr)) { + if (attacker_card && attacker_card->get_condition_value(ConditionType::UNKNOWN_7D)) { *out_value = *out_value * 1.5f; } - if (target_card && - target_card->get_attack_condition_value(ConditionType::UNKNOWN_7D, 0xFFFF, 0xFF, 0xFFFF, nullptr)) { + if (target_card && target_card->get_condition_value(ConditionType::UNKNOWN_7D)) { *out_value = 0; } } @@ -849,8 +858,11 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( uint8_t def_effect_index, uint32_t* unknown_p11, uint16_t sc_card_ref) { - auto attacker_card = this->server()->card_for_set_card_ref(attacker_card_ref); - auto target_card = this->server()->card_for_set_card_ref(target_card_ref); + auto s = this->server(); + bool is_trial = s->options.is_trial(); + + auto attacker_card = s->card_for_set_card_ref(attacker_card_ref); + auto target_card = s->card_for_set_card_ref(target_card_ref); uint8_t target_client_id = client_id_for_card_ref(target_card_ref); uint8_t target_team_id = 0xFF; if (unknown_p9) { @@ -868,15 +880,16 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( target_card_loc.x = 0; target_card_loc.y = 0; target_card_loc.direction = Direction::RIGHT; + } else if (is_trial) { + target_card_loc = target_card->loc; } else { - this->get_card1_loc_with_card2_opposite_direction( - &target_card_loc, target_card, attacker_card); + this->get_card1_loc_with_card2_opposite_direction(&target_card_loc, target_card, attacker_card); } auto attack_medium = attacker_card ? attacker_card->action_chain.chain.attack_medium : AttackMedium::INVALID_FF; - if ((this->server()->get_battle_phase() != BattlePhase::ACTION) || - (this->server()->get_current_action_subphase() == ActionSubphase::ATTACK)) { + if ((s->get_battle_phase() != BattlePhase::ACTION) || + (s->get_current_action_subphase() == ActionSubphase::ATTACK)) { return nullptr; } if (target_card_ref == attacker_card_ref) { @@ -886,21 +899,26 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( return nullptr; } + uint32_t pierce_flag = is_trial ? 0x00000080 : (0x00002000 << target_client_id); bool has_pierce = ((target_client_id != 0xFF) && attacker_card && - (attacker_card->action_chain.check_flag(0x00002000 << target_client_id))); + (attacker_card->action_chain.check_flag(pierce_flag))); + + if (has_pierce && is_trial) { + return nullptr; + } // Handle Parry if present if (target_card && !(target_card->card_flags & 3)) { for (size_t x = 0; x < 9; x++) { auto& cond = target_card->action_chain.conditions[x]; - if ((unknown_p7 == 0) && this->card_ref_has_ability_trap(cond)) { + if (!is_trial && (unknown_p7 == 0) && this->card_ref_has_ability_trap(cond)) { continue; } if (cond.type == ConditionType::NONE) { continue; } - if (!this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( + if (!s->ruler_server->check_usability_or_apply_condition_for_card_refs( target_card->action_chain.conditions[x].card_ref, target_card->get_card_ref(), attacker_card_ref, @@ -912,7 +930,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( continue; } auto target_ps = target_card->player_state(); - if (has_pierce || (unknown_p7 != 0) || !target_ps) { + if (has_pierce || (!is_trial && (unknown_p7 != 0)) || !target_ps) { continue; } @@ -921,7 +939,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( // the Gifoie card's ID (00D9) for compute_effective_range. // TODO: We should fix this so it doesn't rely on a fixed card definition. parray range; - compute_effective_range(range, this->server()->options.card_index, 0x00D9, target_card_loc, this->server()->map_and_rules); + compute_effective_range(range, s->options.card_index, 0x00D9, target_card_loc, s->map_and_rules); auto card_refs_in_parry_range = target_ps->get_all_cards_within_range( range, target_card_loc, 0xFF); @@ -938,7 +956,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( if (target_card_ref == card_ref) { continue; } - auto ce = this->server()->definition_for_card_ref(card_ref); + auto ce = s->definition_for_card_ref(card_ref); if (ce && ((ce->def.type == CardType::HUNTERS_SC) || (ce->def.type == CardType::ARKZ_SC))) { continue; } @@ -949,7 +967,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( if (num_candidates > 0) { uint8_t a = target_ps->roll_dice_with_effects(2); uint8_t b = target_ps->roll_dice_with_effects(1); - return this->server()->card_for_set_card_ref( + return s->card_for_set_card_ref( candidate_card_refs[(a + b) - ((a + b) / num_candidates) * num_candidates]); } } @@ -961,9 +979,11 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( // card with a true value is returned instead of a random entry from the // entire array. The original code only puts false values into the priority // array, effectively rendering it unused, so we've omitted it entirely. + // Curiously, this code does not exist in NTE, so it seems it was added after + // NTE but never used. vector> candidate_cards; for (size_t client_id = 0; client_id < 4; client_id++) { - auto other_ps = this->server()->get_player_state(client_id); + auto other_ps = s->get_player_state(client_id); if (!other_ps) { continue; } @@ -976,13 +996,13 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( for (size_t z = 0; (z < 9) && (candidate_cards.size() < 36); z++) { auto& cond = other_set_card->action_chain.conditions[z]; - if ((unknown_p7 == 0) && this->card_ref_has_ability_trap(cond)) { + if (!is_trial && (unknown_p7 == 0) && this->card_ref_has_ability_trap(cond)) { continue; } if (cond.type == ConditionType::NONE) { continue; } - if (!this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( + if (!s->ruler_server->check_usability_or_apply_condition_for_card_refs( other_set_card->action_chain.conditions[z].card_ref, other_set_card->get_card_ref(), attacker_card_ref, @@ -994,7 +1014,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( switch (other_set_card->action_chain.conditions[z].type) { case ConditionType::GUARD_CREATURE: if (!has_pierce && - (unknown_p7 != 0) && + (is_trial || (unknown_p7 != 0)) && ((unknown_p3 != 0) || (unknown_p4 != 0)) && (target_client_id == client_id) && target_card && @@ -1004,12 +1024,12 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( break; case ConditionType::DEFENDER: if (!has_pierce && - (unknown_p7 == 0) && + (is_trial || (unknown_p7 == 0)) && (unknown_p4 != 0) && (target_card_ref == other_set_card->action_chain.conditions[z].condition_giver_card_ref)) { candidate_cards.emplace_back(other_set_card); - if (unknown_p11 && (def_effect_index != 0xFF) && (set_card_ref != 0xFFFF) && - !this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( + if (!is_trial && unknown_p11 && (def_effect_index != 0xFF) && (set_card_ref != 0xFFFF) && + !s->ruler_server->check_usability_or_apply_condition_for_card_refs( set_card_ref, sc_card_ref, other_set_card->get_card_ref(), def_effect_index, attack_medium)) { *unknown_p11 = 1; } @@ -1017,7 +1037,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( break; case ConditionType::UNKNOWN_39: if (!has_pierce && - (unknown_p7 == 0) && + (is_trial || (unknown_p7 == 0)) && (unknown_p3 != 0) && (target_card_ref == other_set_card->action_chain.conditions[z].condition_giver_card_ref)) { candidate_cards.emplace_back(other_set_card); @@ -1025,7 +1045,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( break; case ConditionType::SURVIVAL_DECOYS: if (!has_pierce && - (unknown_p7 == 0) && + (is_trial || (unknown_p7 == 0)) && attacker_card && (attacker_card->action_chain.chain.target_card_ref_count > 1) && (unknown_p3 != 0) && @@ -1034,7 +1054,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( } break; case ConditionType::REFLECT: - if ((unknown_p7 == 0) && (unknown_p3 != 0)) { + if (!is_trial && (unknown_p7 == 0) && (unknown_p3 != 0)) { if (target_card_ref == other_set_card->action_chain.conditions[z].condition_giver_card_ref) { if (unknown_p9) { *unknown_p9 = 0; @@ -1052,16 +1072,16 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( } auto other_sc = other_ps->get_sc_card(); - if (other_sc && !(other_sc->card_flags & 3)) { + if (other_sc && !(other_sc->card_flags & (is_trial ? 1 : 3))) { for (size_t z = 0; (z < 9) && (candidate_cards.size() < 36); z++) { auto& cond = other_sc->action_chain.conditions[z]; - if ((unknown_p7 == 0) && this->card_ref_has_ability_trap(cond)) { + if (!is_trial && (unknown_p7 == 0) && this->card_ref_has_ability_trap(cond)) { continue; } if (cond.type == ConditionType::NONE) { continue; } - if (!this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( + if (!s->ruler_server->check_usability_or_apply_condition_for_card_refs( cond.card_ref, other_sc->get_card_ref(), attacker_card_ref, @@ -1073,7 +1093,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( switch (cond.type) { case ConditionType::GUARD_CREATURE: if (!has_pierce && - (unknown_p7 != 0) && + (is_trial || (unknown_p7 != 0)) && ((unknown_p3 != 0) || (unknown_p4 != 0)) && (target_client_id == client_id) && target_card && @@ -1083,12 +1103,12 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( break; case ConditionType::DEFENDER: if (!has_pierce && - (unknown_p7 == 0) && + (is_trial || (unknown_p7 == 0)) && (unknown_p4 != 0) && (target_card_ref == cond.condition_giver_card_ref)) { candidate_cards.emplace_back(other_sc); - if (unknown_p11 && (def_effect_index != 0xFF) && (set_card_ref != 0xFFFF) && - !this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( + if (!is_trial && unknown_p11 && (def_effect_index != 0xFF) && (set_card_ref != 0xFFFF) && + !s->ruler_server->check_usability_or_apply_condition_for_card_refs( set_card_ref, sc_card_ref, other_sc->get_card_ref(), def_effect_index, attack_medium)) { *unknown_p11 = 1; } @@ -1096,7 +1116,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( break; case ConditionType::UNKNOWN_39: if (!has_pierce && - (unknown_p7 == 0) && + (is_trial || (unknown_p7 == 0)) && (unknown_p3 != 0) && (target_card_ref == cond.condition_giver_card_ref)) { candidate_cards.emplace_back(other_sc); @@ -1104,7 +1124,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( break; case ConditionType::SURVIVAL_DECOYS: if (!has_pierce && - (unknown_p7 == 0) && + (is_trial || (unknown_p7 == 0)) && attacker_card && (attacker_card->action_chain.chain.target_card_ref_count > 1) && (unknown_p3 != 0) && @@ -1113,7 +1133,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( } break; case ConditionType::REFLECT: - if ((unknown_p7 == 0) && (unknown_p3 != 0)) { + if (!is_trial && (unknown_p7 == 0) && (unknown_p3 != 0)) { if (target_card_ref == cond.condition_giver_card_ref) { if (unknown_p9) { *unknown_p9 = 0; @@ -1202,9 +1222,10 @@ StatSwapType CardSpecial::compute_stat_swap_type(shared_ptr card) co } void CardSpecial::compute_team_dice_bonus(uint8_t team_id) { - uint8_t value = this->server()->team_exp[team_id] / (this->server()->team_client_count[team_id] * 12); + auto s = this->server(); + uint8_t value = s->team_exp[team_id] / (s->team_client_count[team_id] * 12); this->adjust_dice_boost_if_team_has_condition_52(team_id, &value, 0); - this->server()->team_dice_bonus[team_id] = min(value, 8); + s->team_dice_bonus[team_id] = min(value, 8); } bool CardSpecial::condition_has_when_20_or_21(const Condition& cond) const { @@ -1292,7 +1313,8 @@ size_t CardSpecial::count_cards_with_card_id_except_card_ref( vector> CardSpecial::get_all_set_cards_by_team_and_class( CardClass card_class, uint8_t team_id, bool exclude_destroyed_cards) const { - if (this->server()->options.is_trial()) { + auto s = this->server(); + if (s->options.is_trial()) { team_id = 0xFF; exclude_destroyed_cards = false; } @@ -1307,7 +1329,7 @@ vector> CardSpecial::get_all_set_cards_by_team_and_class( }; for (size_t client_id = 0; client_id < 4; client_id++) { - auto ps = this->server()->get_player_state(client_id); + auto ps = s->get_player_state(client_id); if (!ps) { continue; } @@ -1383,20 +1405,27 @@ bool CardSpecial::evaluate_effect_arg2_condition( // they are non-null at all callsites, so we've replaced them with references // (and eliminated the null checks within this function). + auto s = this->server(); + uint16_t attacker_card_ref = as.attacker_card_ref; if (attacker_card_ref == 0xFFFF) { attacker_card_ref = as.original_attacker_card_ref; } - auto set_card = this->server()->card_for_set_card_ref(set_card_ref); - bool set_card_has_ability_trap = (set_card && - (this->card_has_condition_with_ref(set_card, ConditionType::ABILITY_TRAP, 0xFFFF, 0xFFFF))); + auto set_card = s->card_for_set_card_ref(set_card_ref); + bool set_card_has_ability_trap = + (!s->options.is_trial() && + set_card && + this->card_has_condition_with_ref(set_card, ConditionType::ABILITY_TRAP, 0xFFFF, 0xFFFF)); switch (arg2_text[0]) { case 'C': - card = this->server()->card_for_set_card_ref(set_card_ref); + if (s->options.is_trial()) { + return false; + } + card = s->card_for_set_card_ref(set_card_ref); if (!card) { - card = this->server()->card_for_set_card_ref(sc_card_ref); + card = s->card_for_set_card_ref(sc_card_ref); } if (!card) { return false; @@ -1408,7 +1437,7 @@ bool CardSpecial::evaluate_effect_arg2_condition( if ((ch1 > 9) || (ch2 > 9)) { return false; } - auto ps = this->server()->get_player_state(client_id_for_card_ref(card->get_card_ref())); + auto ps = s->get_player_state(client_id_for_card_ref(card->get_card_ref())); if (!ps) { return false; } @@ -1441,7 +1470,7 @@ bool CardSpecial::evaluate_effect_arg2_condition( } case 'b': { - auto attacker_card = this->server()->card_for_set_card_ref(attacker_card_ref); + auto attacker_card = s->card_for_set_card_ref(attacker_card_ref); return (attacker_card && (attacker_card->action_chain.chain.damage <= atoi(arg2_text + 1))); } @@ -1470,73 +1499,73 @@ bool CardSpecial::evaluate_effect_arg2_condition( return (atoi(arg2_text + 1) >= card->get_current_hp()); case 'm': { - auto attacker_card = this->server()->card_for_set_card_ref(attacker_card_ref); + auto attacker_card = s->card_for_set_card_ref(attacker_card_ref); return (attacker_card && (attacker_card->action_chain.chain.damage >= atoi(arg2_text + 1))); } case 'n': switch (atoi(arg2_text + 1)) { - case 0: // n00 + case 0x00: // n00 return true; - case 1: // n01 + case 0x01: // n01 return (!card || (card->get_definition()->def.type == CardType::HUNTERS_SC)); - case 2: // n02 + case 0x02: // n02 for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { - auto target_card = this->server()->card_for_set_card_ref(as.target_card_refs[z]); + auto target_card = s->card_for_set_card_ref(as.target_card_refs[z]); if (target_card && target_card->check_card_flag(2)) { return true; } } return false; - case 3: // n03 + case 0x03: // n03 for (size_t z = 0; z < 8; z++) { uint16_t action_card_ref = as.action_card_refs[z]; if (action_card_ref != 0xFFFF) { - auto ce = this->server()->definition_for_card_ref(action_card_ref); - if (card_class_is_tech_like(ce->def.card_class())) { + auto ce = s->definition_for_card_ref(action_card_ref); + if (card_class_is_tech_like(ce->def.card_class(), s->options.is_trial())) { return true; } } } return false; - case 4: // n04 - return card->action_chain.check_flag(0x0001E000); - case 5: // n05 - return card->action_chain.check_flag(0x00001E00); - case 6: // n06 + case 0x04: // n04 + return card->action_chain.check_flag(s->options.is_trial() ? 0x00000080 : 0x0001E000); + case 0x05: // n05 + return card->action_chain.check_flag(s->options.is_trial() ? 0x00000002 : 0x00001E00); + case 0x06: // n06 return (card->get_definition()->def.card_class() == CardClass::NATIVE_CREATURE); - case 7: // n07 + case 0x07: // n07 return (card->get_definition()->def.card_class() == CardClass::A_BEAST_CREATURE); - case 8: // n08 + case 0x08: // n08 return (card->get_definition()->def.card_class() == CardClass::MACHINE_CREATURE); - case 9: // n09 + case 0x09: // n09 return (card->get_definition()->def.card_class() == CardClass::DARK_CREATURE); - case 10: // n10 + case 0x0A: // n10 return (card->get_definition()->def.card_class() == CardClass::SWORD_ITEM); - case 11: // n11 + case 0x0B: // n11 return (card->get_definition()->def.card_class() == CardClass::GUN_ITEM); - case 12: // n12 + case 0x0C: // n12 return (card->get_definition()->def.card_class() == CardClass::CANE_ITEM); - case 13: { // n13 + case 0x0D: { // n13 auto ce = card->get_definition(); return ((ce->def.card_class() == CardClass::GUARD_ITEM) || - (ce->def.card_class() == CardClass::MAG_ITEM) || - this->server()->ruler_server->find_condition_on_card_ref( + (!s->options.is_trial() && (ce->def.card_class() == CardClass::MAG_ITEM)) || + s->ruler_server->find_condition_on_card_ref( card->get_card_ref(), ConditionType::GUARD_CREATURE, 0, 0, 0)); } - case 14: // n14 + case 0x0E: // n14 return card->get_definition()->def.is_sc(); - case 15: // n15 + case 0x0F: // n15 return ((card->action_chain.chain.attack_action_card_ref_count == 0) && (card->action_metadata.defense_card_ref_count == 0)); - case 16: // n16 - return this->server()->ruler_server->card_ref_is_aerial(card->get_card_ref()); - case 17: { // n17 - auto sc_card = this->server()->card_for_set_card_ref(sc_card_ref); + case 0x10: // n16 + return s->ruler_server->card_ref_is_aerial(card->get_card_ref()); + case 0x11: { // n17 + auto sc_card = s->card_for_set_card_ref(sc_card_ref); int16_t this_ap = card->ap; int16_t other_ap = -1; if (!sc_card) { - auto ce = this->server()->definition_for_card_ref(sc_card_ref); + auto ce = s->definition_for_card_ref(sc_card_ref); if (ce) { other_ap = ce->def.ap.stat; } @@ -1545,31 +1574,35 @@ bool CardSpecial::evaluate_effect_arg2_condition( } return (other_ap == this_ap); } - case 18: // n18 + case 0x12: // n18 for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { - auto target_card = this->server()->card_for_set_card_ref(as.target_card_refs[z]); + auto target_card = s->card_for_set_card_ref(as.target_card_refs[z]); if (target_card && target_card->get_definition()->def.is_sc()) { return true; } } return false; - case 19: // n19 - return this->server()->ruler_server->find_condition_on_card_ref( + case 0x13: // n19 + return s->ruler_server->find_condition_on_card_ref( card->get_card_ref(), ConditionType::PARALYZE, 0, 0, 0); - case 20: // n20 - return this->server()->ruler_server->find_condition_on_card_ref( + case 0x14: // n20 + return s->ruler_server->find_condition_on_card_ref( card->get_card_ref(), ConditionType::FREEZE, 0, 0, 0); - case 21: { // n21 - uint8_t client_id = client_id_for_card_ref(sc_card_ref); - if (client_id != 0xFF) { - return card->action_chain.check_flag(0x00002000 << client_id); + case 0x15: { // n21 + if (!s->options.is_trial()) { + uint8_t client_id = client_id_for_card_ref(sc_card_ref); + if (client_id != 0xFF) { + return card->action_chain.check_flag(0x00002000 << client_id); + } } return false; } - case 22: { // n22 - uint8_t client_id = client_id_for_card_ref(sc_card_ref); - if (client_id != 0xFF) { - return card->action_chain.check_flag(0x00000200 << client_id); + case 0x16: { // n22 + if (!s->options.is_trial()) { + uint8_t client_id = client_id_for_card_ref(sc_card_ref); + if (client_id != 0xFF) { + return card->action_chain.check_flag(0x00000200 << client_id); + } } return false; } @@ -1581,9 +1614,9 @@ bool CardSpecial::evaluate_effect_arg2_condition( case 'o': { uint8_t v = atoi(arg2_text + 1); if ((v / 10) == 1) { - auto new_card = this->server()->card_for_set_card_ref(set_card_ref); + auto new_card = s->card_for_set_card_ref(set_card_ref); if (!new_card) { - new_card = this->server()->card_for_set_card_ref(sc_card_ref); + new_card = s->card_for_set_card_ref(sc_card_ref); } if (new_card) { card = new_card; @@ -1593,21 +1626,23 @@ bool CardSpecial::evaluate_effect_arg2_condition( card, ConditionType::ANY, set_card_ref, ((v % 10) == 0) ? 0xFF : (v % 10)) != nullptr); } case 'r': - return !set_card_has_ability_trap && (random_percent < atoi(arg2_text + 1)); + return (!set_card_has_ability_trap || s->options.is_trial()) && (random_percent < atoi(arg2_text + 1)); case 's': { auto ce = card->get_definition(); return ((ce->def.self_cost >= arg2_text[1] - '0') && (ce->def.self_cost <= arg2_text[2] - '0')); } case 't': { - auto set_card = this->server()->card_for_set_card_ref(set_card_ref); + auto set_card = s->card_for_set_card_ref(set_card_ref); if (!set_card) { return false; } uint8_t v = atoi(arg2_text + 1); // TODO: Figure out what this logic actually does and rename the variables // or comment it appropriately. - if (when == 4) { + if (s->options.is_trial()) { + return (v < set_card->unknown_a9); + } else if (when == 4) { uint32_t y = set_card->unknown_a9 & 0xFFFFFFFE; if ((set_card->unknown_a9 > 0) && (y == (y / (v & 0xFFFFFFFE)) * (v & 0xFFFFFFFE))) { @@ -1726,35 +1761,43 @@ bool CardSpecial::execute_effect( ConditionType cond_type, uint32_t unknown_p7, uint16_t attacker_card_ref) { - auto log = this->server()->log_stack(string_printf("execute_effect(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id())); + auto s = this->server(); + auto log = s->log_stack(string_printf("execute_effect(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id())); { string cond_str = cond.str(); log.debug("cond=%s, card=@%04hX, expr_value=%hd, unknown_p5=%hd, cond_type=%s, unknown_p7=%" PRIu32 ", attacker_card_ref=@%04hX", cond_str.c_str(), ref_for_card(card), expr_value, unknown_p5, name_for_condition_type(cond_type), unknown_p7, attacker_card_ref); } + bool is_trial = s->options.is_trial(); + int16_t clamped_expr_value = clamp(expr_value, -99, 99); - int16_t clamped_unknown_p5 = clamp(unknown_p5, -99, 99); cond.value8 = clamped_expr_value; - if (this->card_ref_has_ability_trap(cond)) { - return false; - } - if (card->card_flags & 1) { + if (!is_trial) { + if (this->card_ref_has_ability_trap(cond)) { + return false; + } + if (card->card_flags & 1) { + return false; + } + if ((card->card_flags & 3) || + (card->action_metadata.check_flag(0x10) && + (cond.card_ref != card->get_card_ref()) && + (cond.condition_giver_card_ref != card->get_card_ref()))) { + unknown_p7 &= ~4; + } + if (unknown_p7 == 0) { + return false; + } + + } else if (card->action_metadata.check_flag(0x10) && + (cond.card_ref != card->get_card_ref()) && + (cond.condition_giver_card_ref != card->get_card_ref())) { return false; } - if ((card->card_flags & 3) || - (card->action_metadata.check_flag(0x10) && - (cond.card_ref != card->get_card_ref()) && - (cond.condition_giver_card_ref != card->get_card_ref()))) { - unknown_p7 = unknown_p7 & 0xFFFFFFFB; - } - if (unknown_p7 == 0) { - return false; - } - - int16_t positive_expr_value = max(0, clamped_expr_value); - clamped_unknown_p5 = max(0, clamped_unknown_p5); - auto attacker_sc = this->server()->card_for_set_card_ref(attacker_card_ref); + int16_t positive_expr_value = max(clamped_expr_value, 0); + int16_t clamped_unknown_p5 = clamp(unknown_p5, 0, is_trial ? unknown_p5 : 99); + auto attacker_sc = s->card_for_set_card_ref(attacker_card_ref); auto attack_medium = attacker_sc ? attacker_sc->action_chain.chain.attack_medium : AttackMedium::UNKNOWN; switch (cond_type) { @@ -1770,7 +1813,6 @@ bool CardSpecial::execute_effect( case ConditionType::UNUSED_13: case ConditionType::ACID: case ConditionType::ADD_1_TO_MV_COST: - case ConditionType::ABILITY_TRAP: case ConditionType::FREEZE: case ConditionType::MAJOR_PIERCE: case ConditionType::HEAVY_PIERCE: @@ -1790,8 +1832,12 @@ bool CardSpecial::execute_effect( case ConditionType::AP_BOOST: if (unknown_p7 & 1) { - card->action_chain.chain.ap_effect_bonus = clamp( - card->action_chain.chain.ap_effect_bonus + positive_expr_value, -99, 99); + if (is_trial) { + card->action_chain.chain.ap_effect_bonus += positive_expr_value; + } else { + card->action_chain.chain.ap_effect_bonus = clamp( + card->action_chain.chain.ap_effect_bonus + positive_expr_value, -99, 99); + } } return true; @@ -1809,15 +1855,19 @@ bool CardSpecial::execute_effect( case ConditionType::TP_BOOST: if (unknown_p7 & 1) { - card->action_chain.chain.tp_effect_bonus = clamp( - card->action_chain.chain.tp_effect_bonus + positive_expr_value, -99, 99); + if (is_trial) { + card->action_chain.chain.tp_effect_bonus += positive_expr_value; + } else { + card->action_chain.chain.tp_effect_bonus = clamp( + card->action_chain.chain.tp_effect_bonus + positive_expr_value, -99, 99); + } } return true; case ConditionType::GIVE_DAMAGE: if ((unknown_p7 & 4) != 0) { - int16_t current_hp = clamp(card->get_current_hp(), -99, 99); - int16_t new_hp = clamp(current_hp - positive_expr_value, -99, 99); + int16_t current_hp = is_trial ? card->get_current_hp() : clamp(card->get_current_hp(), -99, 99); + int16_t new_hp = is_trial ? (current_hp - positive_expr_value) : clamp(current_hp - positive_expr_value, -99, 99); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x20, -positive_expr_value, 0, 1); new_hp = max(new_hp, 0); if (new_hp != current_hp) { @@ -1830,8 +1880,8 @@ bool CardSpecial::execute_effect( case ConditionType::A_T_SWAP_0C: case ConditionType::A_T_SWAP_PERM: if (unknown_p7 & 4) { - int16_t ap = clamp(card->ap, -99, 99); - int16_t tp = clamp(card->tp, -99, 99); + int16_t ap = is_trial ? card->ap : clamp(card->ap, -99, 99); + int16_t tp = is_trial ? card->tp : clamp(card->tp, -99, 99); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0xA0, tp - ap, 0, 0); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x80, ap - tp, 0, 0); card->ap = tp; @@ -1843,8 +1893,8 @@ bool CardSpecial::execute_effect( case ConditionType::A_H_SWAP: case ConditionType::A_H_SWAP_PERM: if (unknown_p7 & 4) { - int16_t ap = clamp(card->ap, -99, 99); - int16_t hp = clamp(card->get_current_hp(), -99, 99); + int16_t ap = is_trial ? card->ap : clamp(card->ap, -99, 99); + int16_t hp = is_trial ? card->get_current_hp() : clamp(card->get_current_hp(), -99, 99); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0xA0, hp - ap, 0, 0); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x20, ap - hp, 1, 0); cond.flags |= 2; @@ -1875,7 +1925,7 @@ bool CardSpecial::execute_effect( if (client_id == 0xFF) { return false; } - auto ps = this->server()->player_states.at(client_id); + auto ps = s->player_states.at(client_id); if (!ps) { return false; } @@ -1914,7 +1964,7 @@ bool CardSpecial::execute_effect( case ConditionType::CURSE: if (unknown_p7 & 4) { for (size_t z = 0; z < card->action_chain.chain.target_card_ref_count; z++) { - auto target_card = this->server()->card_for_set_card_ref( + auto target_card = s->card_for_set_card_ref( card->action_chain.chain.target_card_refs[z]); if (target_card) { CardShortStatus stat = target_card->get_short_status(); @@ -1949,6 +1999,10 @@ bool CardSpecial::execute_effect( } return true; + case ConditionType::ABILITY_TRAP: + + return false; + case ConditionType::ANTI_ABNORMALITY_1: if (unknown_p7 & 4) { for (ssize_t z = 8; z >= 0; z--) { @@ -1973,7 +2027,7 @@ bool CardSpecial::execute_effect( cmd.effect.value = 0; cmd.effect.operation = -static_cast(cond.type); cmd.effect.condition_index = z; - this->server()->send(cmd); + s->send(cmd); this->apply_stat_deltas_to_card_from_condition_and_clear_cond(cond, card); card->send_6xB4x4E_4C_4D_if_needed(); } @@ -1983,7 +2037,7 @@ bool CardSpecial::execute_effect( case ConditionType::UNKNOWN_1E: if (unknown_p7 & 4) { - auto sc_card = this->server()->card_for_set_card_ref(attacker_card_ref); + auto sc_card = s->card_for_set_card_ref(attacker_card_ref); if (!sc_card || (sc_card->action_chain.chain.attack_medium == AttackMedium::PHYSICAL)) { int16_t hp = clamp(card->get_current_hp(), -99, 99); int16_t new_hp = lround(hp * 0.5f); @@ -2018,7 +2072,7 @@ bool CardSpecial::execute_effect( if (client_id == 0xFF) { return false; } - auto ps = this->server()->player_states.at(client_id); + auto ps = s->player_states.at(client_id); if (!ps) { return false; } @@ -2059,23 +2113,23 @@ bool CardSpecial::execute_effect( uint8_t attacker_client_id = client_id_for_card_ref(cond.card_ref); uint8_t target_client_id = client_id_for_card_ref(card->get_card_ref()); if ((attacker_client_id != 0xFF) && (target_client_id != 0xFF)) { - auto attacker_ps = this->server()->player_states.at(attacker_client_id); - auto target_ps = this->server()->player_states.at(target_client_id); + auto attacker_ps = s->player_states.at(attacker_client_id); + auto target_ps = s->player_states.at(target_client_id); if (attacker_ps && target_ps) { uint8_t attacker_team_id = attacker_ps->get_team_id(); uint8_t target_team_id = target_ps->get_team_id(); - if (positive_expr_value < this->server()->team_exp[target_team_id]) { - this->server()->team_exp[attacker_team_id] += positive_expr_value; - this->server()->team_exp[target_team_id] -= positive_expr_value; + if (positive_expr_value < s->team_exp[target_team_id]) { + s->team_exp[attacker_team_id] += positive_expr_value; + s->team_exp[target_team_id] -= positive_expr_value; } else { - positive_expr_value = this->server()->team_exp[target_team_id]; - this->server()->team_exp[attacker_team_id] += this->server()->team_exp[target_team_id]; - this->server()->team_exp[target_team_id] = 0; + positive_expr_value = s->team_exp[target_team_id]; + s->team_exp[attacker_team_id] += s->team_exp[target_team_id]; + s->team_exp[target_team_id] = 0; } this->compute_team_dice_bonus(attacker_team_id); this->compute_team_dice_bonus(target_team_id); this->send_6xB4x06_for_exp_change(card, attacker_card_ref, -positive_expr_value, 1); - this->server()->update_battle_state_flags_and_send_6xB4x03_if_needed(); + s->update_battle_state_flags_and_send_6xB4x03_if_needed(); } } } @@ -2109,11 +2163,11 @@ bool CardSpecial::execute_effect( if (ps) { uint8_t team_id = ps->get_team_id(); int16_t delta = 0; - if (this->server()->team_exp[team_id] < 4) { - this->server()->team_exp[team_id] = 0; + if (s->team_exp[team_id] < 4) { + s->team_exp[team_id] = 0; } else { delta = -3; - this->server()->team_exp[team_id] -= 3; + s->team_exp[team_id] -= 3; } this->compute_team_dice_bonus(team_id); this->send_6xB4x06_for_exp_change(card, attacker_card_ref, delta, 1); @@ -2182,18 +2236,18 @@ bool CardSpecial::execute_effect( case ConditionType::GIVE_OR_TAKE_EXP: if (unknown_p7 & 4) { uint8_t client_id = client_id_for_card_ref(card->get_card_ref()); - if ((client_id != 0xFF) && this->server()->player_states.at(client_id)) { - uint8_t team_id = this->server()->player_states.at(client_id)->get_team_id(); - int32_t existing_exp = this->server()->team_exp[team_id]; + if ((client_id != 0xFF) && s->player_states.at(client_id)) { + uint8_t team_id = s->player_states.at(client_id)->get_team_id(); + int32_t existing_exp = s->team_exp[team_id]; if ((clamped_expr_value + existing_exp) < 0) { clamped_expr_value = -existing_exp; - this->server()->team_exp[team_id] = 0; + s->team_exp[team_id] = 0; } else { - this->server()->team_exp[team_id] = existing_exp + clamped_expr_value; + s->team_exp[team_id] = existing_exp + clamped_expr_value; } this->send_6xB4x06_for_exp_change(card, attacker_card_ref, clamped_expr_value, 1); this->compute_team_dice_bonus(team_id); - this->server()->update_battle_state_flags_and_send_6xB4x03_if_needed(); + s->update_battle_state_flags_and_send_6xB4x03_if_needed(); } } return true; @@ -2214,9 +2268,9 @@ bool CardSpecial::execute_effect( } for (uint16_t card_ref : card_refs) { - auto sc_card = this->server()->card_for_set_card_ref(card_ref); + auto sc_card = s->card_for_set_card_ref(card_ref); if (sc_card && (sc_card->get_current_hp() > 0)) { - if (this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( + if (s->ruler_server->check_usability_or_apply_condition_for_card_refs( cond.card_ref, cond.condition_giver_card_ref, sc_card->get_card_ref(), cond.card_definition_effect_index, attack_medium)) { @@ -2256,7 +2310,7 @@ bool CardSpecial::execute_effect( case ConditionType::UNKNOWN_49: if (unknown_p7 & 4) { - auto attacker_card = this->server()->card_for_set_card_ref(attacker_card_ref); + auto attacker_card = s->card_for_set_card_ref(attacker_card_ref); if (attacker_card && (attacker_card != card)) { for (ssize_t z = 8; z >= 0; z--) { this->apply_stat_deltas_to_card_from_condition_and_clear_cond( @@ -2292,7 +2346,7 @@ bool CardSpecial::execute_effect( case ConditionType::COPY: if (unknown_p7 & 4) { - auto attacker_card = this->server()->card_for_set_card_ref(attacker_card_ref); + auto attacker_card = s->card_for_set_card_ref(attacker_card_ref); if (attacker_card && (attacker_card != card)) { int16_t new_ap = clamp((positive_expr_value < 51) ? (card->ap / 2) : card->ap, -99, 99); int16_t new_tp = clamp((positive_expr_value < 51) ? (card->tp / 2) : card->tp, -99, 99); @@ -2392,7 +2446,7 @@ bool CardSpecial::execute_effect( case ConditionType::PERIODIC_FIELD: if ((unknown_p7 & 0x40) && - (static_cast(attack_medium) == ((this->server()->get_round_num() >> 1) & 1) + 1)) { + (static_cast(attack_medium) == ((s->get_round_num() >> 1) & 1) + 1)) { card->action_metadata.attack_bonus = 0; } return true; @@ -2433,9 +2487,9 @@ bool CardSpecial::execute_effect( } for (uint16_t card_ref : card_refs) { - auto set_card = this->server()->card_for_set_card_ref(card_ref); + auto set_card = s->card_for_set_card_ref(card_ref); if (set_card && (set_card->get_current_hp() > 0)) { - if (this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( + if (s->ruler_server->check_usability_or_apply_condition_for_card_refs( cond.card_ref, cond.condition_giver_card_ref, set_card->get_card_ref(), @@ -2458,24 +2512,42 @@ const Condition* CardSpecial::find_condition_with_parameters( ConditionType cond_type, uint16_t set_card_ref, uint8_t def_effect_index) const { - const Condition* ret = nullptr; - uint8_t max_order = 9; - for (size_t z = 0; z < 9; z++) { - if (card->action_chain.conditions[z].type == ConditionType::NONE) { - continue; + + if (this->server()->options.is_trial()) { + // The NTE version of this function returns a boolean instead of a pointer; + // we always return a pointer for simplicity reasons, even for NTE. + for (size_t z = 0; z < 9; z++) { + auto& cond = card->action_chain.conditions[z]; + auto orig_eff = this->original_definition_for_condition(cond); + + if (((cond_type == ConditionType::ANY) || (cond.type == cond_type)) && + ((set_card_ref == 0xFFFF) || (cond.card_ref == set_card_ref)) && + ((def_effect_index != 0xFF) || (orig_eff || (orig_eff->effect_num == def_effect_index)))) { + return &cond; + } } - auto& cond = card->action_chain.conditions[z]; - auto orig_eff = this->original_definition_for_condition(cond); - if (!this->card_ref_has_ability_trap(cond) && - ((cond_type == ConditionType::ANY) || (cond.type == cond_type)) && - ((set_card_ref == 0xFFFF) || (cond.card_ref == set_card_ref)) && - ((def_effect_index == 0xFF) || (orig_eff && (orig_eff->effect_num == def_effect_index))) && - (!ret || (max_order < cond.order))) { - max_order = cond.order; - ret = &cond; + return nullptr; + + } else { + const Condition* ret = nullptr; + uint8_t max_order = 9; + for (size_t z = 0; z < 9; z++) { + if (card->action_chain.conditions[z].type == ConditionType::NONE) { + continue; + } + auto& cond = card->action_chain.conditions[z]; + auto orig_eff = this->original_definition_for_condition(cond); + if (!this->card_ref_has_ability_trap(cond) && + ((cond_type == ConditionType::ANY) || (cond.type == cond_type)) && + ((set_card_ref == 0xFFFF) || (cond.card_ref == set_card_ref)) && + ((def_effect_index == 0xFF) || (orig_eff && (orig_eff->effect_num == def_effect_index))) && + (!ret || (max_order < cond.order))) { + max_order = cond.order; + ret = &cond; + } } + return ret; } - return ret; } Condition* CardSpecial::find_condition_with_parameters( @@ -2506,7 +2578,10 @@ void CardSpecial::get_card1_loc_with_card2_opposite_direction( uint16_t CardSpecial::get_card_id_with_effective_range( shared_ptr card1, uint16_t default_card_id, shared_ptr card2) const { - if (card2 && !(static_cast(card2->facing_direction) & 0x80)) { + auto s = this->server(); + if (s->options.is_trial()) { + return default_card_id; + } else if (card2 && !(static_cast(card2->facing_direction) & 0x80)) { return this->server()->ruler_server->get_card_id_with_effective_range( card1 ? card1->get_card_ref() : 0xFFFF, default_card_id, 0); } @@ -2606,19 +2681,20 @@ vector> CardSpecial::get_targeted_cards_for_condition( const ActionState& as, int16_t p_target_type, bool apply_usability_filters) const { - auto log = this->server()->log_stack(string_printf("get_targeted_cards_for_condition(@%04hX, %hhu, @%04hX): ", card_ref, def_effect_index, setter_card_ref)); + auto s = this->server(); + auto log = s->log_stack(string_printf("get_targeted_cards_for_condition(@%04hX, %hhu, @%04hX): ", card_ref, def_effect_index, setter_card_ref)); log.debug("card_ref=@%04hX, def_effect_index=%02hhX, setter_card_ref=@%04hX, as, p_target_type=%hd, apply_usability_filters=%s", card_ref, def_effect_index, setter_card_ref, p_target_type, apply_usability_filters ? "true" : "false"); vector> ret; uint8_t client_id = client_id_for_card_ref(card_ref); - auto card1 = this->server()->card_for_set_card_ref(card_ref); + auto card1 = s->card_for_set_card_ref(card_ref); if (!card1) { - card1 = this->server()->card_for_set_card_ref(setter_card_ref); + card1 = s->card_for_set_card_ref(setter_card_ref); } log.debug("card1=@%04hX", ref_for_card(card1)); - auto card2 = this->server()->card_for_set_card_ref((as.attacker_card_ref == 0xFFFF) + auto card2 = s->card_for_set_card_ref((as.attacker_card_ref == 0xFFFF) ? as.original_attacker_card_ref : as.attacker_card_ref); log.debug("card2=@%04hX", ref_for_card(card2)); @@ -2642,7 +2718,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( auto add_card_refs = [&](const vector& result_card_refs) -> void { for (uint16_t result_card_ref : result_card_refs) { - auto result_card = this->server()->card_for_set_card_ref(result_card_ref); + auto result_card = s->card_for_set_card_ref(result_card_ref); if (result_card) { ret.emplace_back(result_card); } @@ -2650,9 +2726,9 @@ vector> CardSpecial::get_targeted_cards_for_condition( }; switch (p_target_type) { - case 1: // p01 - case 5: { // p05 - auto result_card = this->server()->card_for_set_card_ref(setter_card_ref); + case 0x01: // p01 + case 0x05: { // p05 + auto result_card = s->card_for_set_card_ref(setter_card_ref); if (result_card) { log.debug("(p01/p05) result_card=@%04hX", ref_for_card(result_card)); ret.emplace_back(result_card); @@ -2661,10 +2737,10 @@ vector> CardSpecial::get_targeted_cards_for_condition( } break; } - case 2: // p02 + case 0x02: // p02 if (as.original_attacker_card_ref == 0xFFFF) { for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { - auto result_card = this->server()->card_for_set_card_ref(as.target_card_refs[z]); + auto result_card = s->card_for_set_card_ref(as.target_card_refs[z]); if (result_card) { ret.emplace_back(result_card); } @@ -2673,137 +2749,150 @@ vector> CardSpecial::get_targeted_cards_for_condition( ret.emplace_back(card2); } break; - case 3: // p03 + case 0x03: // p03 if (card1) { - auto ce = this->server()->definition_for_card_ref(card_ref); + auto ce = s->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (ce && ps) { uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2); parray range; - compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, s->options.card_index, range_card_id, card1_loc, s->map_and_rules); add_card_refs(ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM)); } } if (card1) { - auto ce = this->server()->definition_for_card_ref(card_ref); + auto ce = s->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (ce && ps) { uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2); parray range; - compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, s->options.card_index, range_card_id, card1_loc, s->map_and_rules); add_card_refs(ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id())); } } break; - case 4: // p04 + case 0x04: // p04 size_t z; for (z = 0; (z < 9) && (as.action_card_refs[z] != 0xFFFF) && (as.action_card_refs[z] != card_ref); z++) { } for (; (z < 9) && (as.action_card_refs[z] != 0xFFFF); z++) { - auto result_card = this->server()->card_for_set_card_ref(as.action_card_refs[z]); + auto result_card = s->card_for_set_card_ref(as.action_card_refs[z]); if (result_card) { ret.emplace_back(result_card); } } break; - case 6: // p06 + case 0x06: // p06 ret = this->get_attacker_card_and_sc_if_item(as); break; - case 7: { // p07 + case 0x07: { // p07 auto card = this->get_attacker_card(as); if (card) { ret.emplace_back(card); } break; } - case 8: { // p08 + case 0x08: { // p08 auto card = this->sc_card_for_client_id(client_id); if (card) { ret.emplace_back(card); } break; } - case 9: // p09 + case 0x09: // p09 if (card1) { - auto ce = this->server()->definition_for_card_ref(card_ref); + auto ce = s->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (ce && ps) { uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2); parray range; - compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, s->options.card_index, range_card_id, card1_loc, s->map_and_rules); add_card_refs(ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id())); } } break; - case 10: // p10 + case 0x0A: // p10 ret = this->find_all_cards_on_same_or_other_team(client_id, true); - ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + if (!s->options.is_trial()) { + ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + } break; - case 11: // p11 + case 0x0B: // p11 ret = this->find_all_set_cards_on_client_team(client_id); - ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + if (!s->options.is_trial()) { + ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + } break; - case 12: // p12 - ret = this->find_all_cards_by_aerial_attribute(false); - ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + case 0x0C: // p12 + if (s->options.is_trial()) { + ret = this->find_cards_by_condition_inc_exc( + ConditionType::NONE, ConditionType::AERIAL, AssistEffect::NONE, AssistEffect::FLY); + } else { + ret = this->find_all_cards_by_aerial_attribute(false); + ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + } break; - case 13: // p13 + case 0x0D: // p13 ret = this->find_cards_by_condition_inc_exc(ConditionType::FREEZE); - ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + if (!s->options.is_trial()) { + ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + } break; - case 14: // p14 + case 0x0E: // p14 ret = this->find_cards_in_hp_range(-1000, 3); - ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + if (!s->options.is_trial()) { + ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + } break; - case 15: // p15 + case 0x0F: // p15 ret = this->get_all_set_cards(); - ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + if (!s->options.is_trial()) { + ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + } break; - case 16: { // p16 + case 0x10: { // p16 ret = this->find_cards_in_hp_range(8, 1000); - string range_refs_str = refs_str_for_cards_vector(ret); - log.debug("(p16) candidate cards = [%s]", range_refs_str.c_str()); - ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); - range_refs_str = refs_str_for_cards_vector(ret); - log.debug("(p16) filtered cards = [%s]", range_refs_str.c_str()); + if (!s->options.is_trial()) { + ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + } break; } - case 17: { // p17 - auto result_card = this->server()->card_for_set_card_ref(card_ref); + case 0x11: { // p17 + auto result_card = s->card_for_set_card_ref(card_ref); if (result_card) { ret.emplace_back(result_card); } break; } - case 18: { // p18 + case 0x12: { // p18 auto card = this->sc_card_for_client_id(client_id); if (card) { ret.emplace_back(card); } break; } - case 19: // p19 + case 0x13: // p19 ret = this->find_all_sc_cards_of_class(CardClass::HU_SC); break; - case 20: // p20 + case 0x14: // p20 ret = this->find_all_sc_cards_of_class(CardClass::RA_SC); break; - case 21: // p21 + case 0x15: // p21 ret = this->find_all_sc_cards_of_class(CardClass::FO_SC); break; - case 22: // p22 + case 0x16: // p22 if (card1) { - auto def = this->server()->definition_for_card_ref(card_ref); + auto def = s->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (def && ps) { // TODO: Again, Sega hardcodes the Gifoie card's ID here... we // should fix this eventually. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, s->options.card_index, range_card_id, card1_loc, s->map_and_rules); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()); for (uint16_t result_card_ref : result_card_refs) { - auto result_card = this->server()->card_for_set_card_ref(result_card_ref); + auto result_card = s->card_for_set_card_ref(result_card_ref); if (result_card && (result_card->get_definition()->def.type != CardType::ITEM) && (card1 != result_card)) { @@ -2816,22 +2905,22 @@ vector> CardSpecial::get_targeted_cards_for_condition( } } break; - case 23: { // p23 + case 0x17: { // p23 auto log23 = log.sub("(p23) "); if (card1) { - auto def = this->server()->definition_for_card_ref(card_ref); + auto def = s->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (def && ps) { // TODO: Again with the Gifoie hardcoding... uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); log23.debug("effective range card ID is #%04hX", range_card_id); parray range; - compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules, &log23); + compute_effective_range(range, s->options.card_index, range_card_id, card1_loc, s->map_and_rules, &log23); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); log23.debug("%zu result card refs", result_card_refs.size()); for (uint16_t result_card_ref : result_card_refs) { auto result_log = log23.subf("(result @%04hX) ", result_card_ref); - auto result_card = this->server()->card_for_set_card_ref(result_card_ref); + auto result_card = s->card_for_set_card_ref(result_card_ref); if (!result_card) { result_log.debug("result card not found"); } else if (result_card->get_definition()->def.type == CardType::ITEM) { @@ -2849,40 +2938,48 @@ vector> CardSpecial::get_targeted_cards_for_condition( } break; } - case 24: // p24 + case 0x18: // p24 ret = this->find_cards_by_condition_inc_exc(ConditionType::PARALYZE); break; - case 25: // p25 - ret = this->find_all_cards_by_aerial_attribute(true); + case 0x19: // p25 + if (s->options.is_trial()) { + // This appears to be a copy/paste error in NTE that was fixed in the + // final version. Presumably include_cond should be ConditionType::FLY + // here, not PARALYZE. + ret = this->find_cards_by_condition_inc_exc( + ConditionType::PARALYZE, ConditionType::NONE, AssistEffect::FLY, AssistEffect::NONE); + } else { + ret = this->find_all_cards_by_aerial_attribute(true); + } break; - case 26: // p26 + case 0x1A: // p26 ret = this->find_cards_damaged_by_at_least(1); break; - case 27: // p27 + case 0x1B: // p27 ret = this->get_all_set_cards_by_team_and_class(CardClass::NATIVE_CREATURE, 0xFF, false); break; - case 28: // p28 + case 0x1C: // p28 ret = this->get_all_set_cards_by_team_and_class(CardClass::A_BEAST_CREATURE, 0xFF, false); break; - case 29: // p29 + case 0x1D: // p29 ret = this->get_all_set_cards_by_team_and_class(CardClass::MACHINE_CREATURE, 0xFF, false); break; - case 30: // p30 + case 0x1E: // p30 ret = this->get_all_set_cards_by_team_and_class(CardClass::DARK_CREATURE, 0xFF, false); break; - case 31: // p31 + case 0x1F: // p31 ret = this->get_all_set_cards_by_team_and_class(CardClass::SWORD_ITEM, 0xFF, false); break; - case 32: // p32 + case 0x20: // p32 ret = this->get_all_set_cards_by_team_and_class(CardClass::GUN_ITEM, 0xFF, false); break; - case 33: // p33 + case 0x21: // p33 ret = this->get_all_set_cards_by_team_and_class(CardClass::CANE_ITEM, 0xFF, false); break; - case 34: // p34 + case 0x22: // p34 if (as.original_attacker_card_ref == 0xFFFF) { for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { - auto result_card = this->server()->card_for_set_card_ref(as.target_card_refs[z]); + auto result_card = s->card_for_set_card_ref(as.target_card_refs[z]); if (result_card && result_card->get_definition() && !result_card->get_definition()->def.is_sc()) { @@ -2895,18 +2992,18 @@ vector> CardSpecial::get_targeted_cards_for_condition( ret.emplace_back(card2); } break; - case 35: // p35 + case 0x23: // p35 if (card1) { - auto def = this->server()->definition_for_card_ref(card_ref); + auto def = s->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (def && ps) { // TODO: Again with the Gifoie hardcoding... uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, s->options.card_index, range_card_id, card1_loc, s->map_and_rules); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { - auto result_card = this->server()->card_for_set_card_ref(result_card_ref); + auto result_card = s->card_for_set_card_ref(result_card_ref); if (result_card) { auto ce = result_card->get_definition(); if (ce->def.type == CardType::HUNTERS_SC) { @@ -2929,38 +3026,42 @@ vector> CardSpecial::get_targeted_cards_for_condition( } } break; - case 36: // p36 + case 0x24: { // p36 + // On NTE, this includes SCs and items; on other versions, it's SCs only + static const auto should_include = +[](shared_ptr ce, bool is_trial) -> bool { + return (ce && (ce->def.is_sc() || (is_trial ? (ce->def.type == CardType::ITEM) : false))); + }; + bool is_trial = s->options.is_trial(); if (as.original_attacker_card_ref == 0xFFFF) { for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { - auto result_card = this->server()->card_for_set_card_ref(as.target_card_refs[z]); - if (result_card && - result_card->get_definition() && - result_card->get_definition()->def.is_sc()) { + auto result_card = s->card_for_set_card_ref(as.target_card_refs[z]); + if (result_card && should_include(result_card->get_definition(), is_trial)) { ret.emplace_back(result_card); } } - } else if (card2 && - card2->get_definition() && - card2->get_definition()->def.is_sc()) { + } else if (card2 && should_include(card2->get_definition(), is_trial)) { ret.emplace_back(card2); } break; - case 37: // p37 + } + case 0x25: // p37 ret = this->find_all_cards_on_same_or_other_team(client_id, false); - ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + if (!s->options.is_trial()) { + ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + } break; - case 38: // p38 + case 0x26: // p38 if (card1) { - auto def = this->server()->definition_for_card_ref(card_ref); + auto def = s->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (def && ps) { // TODO: Yet another Gifoie hardcode location :( uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, s->options.card_index, range_card_id, card1_loc, s->map_and_rules); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()); for (uint16_t result_card_ref : result_card_refs) { - auto result_card = this->server()->card_for_set_card_ref(result_card_ref); + auto result_card = s->card_for_set_card_ref(result_card_ref); if (result_card && (result_card->get_definition()->def.type != CardType::ITEM) && (result_card->get_card_ref() != card_ref)) { @@ -2970,24 +3071,29 @@ vector> CardSpecial::get_targeted_cards_for_condition( } } break; - case 39: // p39 + case 0x27: // p39 ret = this->find_all_set_cards_with_cost_in_range(4, 99); - ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + if (!s->options.is_trial()) { + ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + } break; - case 40: // p40 + case 0x28: // p40 ret = this->find_all_set_cards_with_cost_in_range(0, 3); - ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + if (!s->options.is_trial()) { + ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); + } break; - case 41: { // p41 + case 0x29: { // p41 auto ps = card1->player_state(); if (card1 && ps) { // TODO: Sigh. Gifoie again. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); - auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); + compute_effective_range(range, s->options.card_index, range_card_id, card1_loc, s->map_and_rules); + auto result_card_refs = ps->get_all_cards_within_range( + range, card1_loc, s->options.is_trial() ? card1->get_team_id() : 0xFF); for (uint16_t result_card_ref : result_card_refs) { - auto result_card = this->server()->card_for_set_card_ref(result_card_ref); + auto result_card = s->card_for_set_card_ref(result_card_ref); if (result_card && (result_card != card1) && (result_card->get_card_ref() != card_ref) && @@ -2996,26 +3102,28 @@ vector> CardSpecial::get_targeted_cards_for_condition( } } - for (size_t z = 0; z < 8; z++) { - auto result_card = ps->get_set_card(z); - if (result_card && (card1 != result_card) && - (result_card->get_definition()->def.type == CardType::ITEM)) { - bool already_in_ret = false; - for (auto c : ret) { - if (c == result_card) { - already_in_ret = true; - break; + if (!s->options.is_trial()) { + for (size_t z = 0; z < 8; z++) { + auto result_card = ps->get_set_card(z); + if (result_card && (card1 != result_card) && + (result_card->get_definition()->def.type == CardType::ITEM)) { + bool already_in_ret = false; + for (auto c : ret) { + if (c == result_card) { + already_in_ret = true; + break; + } + } + if (!already_in_ret) { + ret.emplace_back(result_card); } - } - if (!already_in_ret) { - ret.emplace_back(result_card); } } } } break; } - case 42: { // p42 + case 0x2A: { // p42 auto check_card = [&](shared_ptr result_card) -> void { if (result_card) { ret.emplace_back(result_card); @@ -3031,16 +3139,19 @@ vector> CardSpecial::get_targeted_cards_for_condition( }; if (as.original_attacker_card_ref == 0xFFFF) { for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { - check_card(this->server()->card_for_set_card_ref(as.target_card_refs[z])); + check_card(s->card_for_set_card_ref(as.target_card_refs[z])); } } else if (card2) { check_card(card2); } break; } - case 43: // p43 + case 0x2B: // p43 + if (s->options.is_trial()) { + break; + } for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { - auto result_card = this->server()->card_for_set_card_ref(as.target_card_refs[z]); + auto result_card = s->card_for_set_card_ref(as.target_card_refs[z]); if (!result_card) { continue; } @@ -3054,8 +3165,11 @@ vector> CardSpecial::get_targeted_cards_for_condition( } } break; - case 44: { // p44 - auto ps = this->server()->get_player_state(client_id); + case 0x2C: { // p44 + if (s->options.is_trial()) { + break; + } + auto ps = s->get_player_state(client_id); if (ps) { for (size_t z = 0; z < 8; z++) { auto result_card = ps->get_set_card(z); @@ -3067,23 +3181,29 @@ vector> CardSpecial::get_targeted_cards_for_condition( } break; } - case 45: // p45 + case 0x2D: // p45 + if (s->options.is_trial()) { + break; + } this->sum_last_attack_damage(&ret, nullptr, nullptr); ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); break; - case 46: // p46 + case 0x2E: // p46 + if (s->options.is_trial()) { + break; + } if (card1) { - auto def = this->server()->definition_for_card_ref(card_ref); + auto def = s->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (def && ps) { // TODO: Yet another hardcoded card ID... but this time it's Cross // Slay instead of Gifoie uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x009C, card2); parray range; - compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, s->options.card_index, range_card_id, card1_loc, s->map_and_rules); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { - auto result_card = this->server()->card_for_set_card_ref(result_card_ref); + auto result_card = s->card_for_set_card_ref(result_card_ref); if (result_card && (result_card->get_definition()->def.type != CardType::ITEM)) { ret.emplace_back(result_card); } @@ -3091,7 +3211,10 @@ vector> CardSpecial::get_targeted_cards_for_condition( } } break; - case 47: { // p47 + case 0x2F: { // p47 + if (s->options.is_trial()) { + break; + } uint8_t client_id = client_id_for_card_ref(as.original_attacker_card_ref); if (client_id != 0xFF) { auto card = this->sc_card_for_client_id(client_id); @@ -3101,18 +3224,21 @@ vector> CardSpecial::get_targeted_cards_for_condition( } break; } - case 48: // p48 + case 0x30: // p48 + if (s->options.is_trial()) { + break; + } if (card1) { - auto ce = this->server()->definition_for_card_ref(card_ref); + auto ce = s->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (ce && ps) { // TODO: Sigh. Gifoie. Sigh. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, s->options.card_index, range_card_id, card1_loc, s->map_and_rules); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { - auto result_card = this->server()->card_for_set_card_ref(result_card_ref); + auto result_card = s->card_for_set_card_ref(result_card_ref); if (result_card) { auto result_ce = result_card->get_definition(); if (result_ce->def.type == CardType::HUNTERS_SC) { @@ -3133,23 +3259,26 @@ vector> CardSpecial::get_targeted_cards_for_condition( } } } - auto setter_card = this->server()->card_for_set_card_ref(setter_card_ref); + auto setter_card = s->card_for_set_card_ref(setter_card_ref); if (setter_card) { ret.emplace_back(setter_card); } } break; - case 49: // p49 + case 0x31: // p49 + if (s->options.is_trial()) { + break; + } if (card1) { auto ps = card1->player_state(); if (ps) { // TODO: One more Gifoie here. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, s->options.card_index, range_card_id, card1_loc, s->map_and_rules); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()); for (uint16_t result_card_ref : result_card_refs) { - auto result_card = this->server()->card_for_set_card_ref(result_card_ref); + auto result_card = s->card_for_set_card_ref(result_card_ref); if (result_card && (result_card != card1) && (result_card->get_card_ref() != card_ref) && result_card->get_definition()->def.is_fc()) { @@ -3180,7 +3309,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( if (apply_usability_filters) { vector> filtered_ret; for (auto c : ret) { - if (this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( + if (s->ruler_server->check_usability_or_apply_condition_for_card_refs( card_ref, setter_card_ref, c->get_card_ref(), def_effect_index, attack_medium)) { filtered_ret.emplace_back(c); log.debug("usability filter: kept card @%04hX", ref_for_card(c)); @@ -3209,14 +3338,15 @@ bool CardSpecial::is_card_targeted_by_condition( const Condition& cond, const ActionState& as, shared_ptr card) const { - auto ce = this->server()->definition_for_card_ref(cond.card_ref); - auto sc_card = this->server()->card_for_set_card_ref(cond.card_ref); + auto s = this->server(); + auto ce = s->definition_for_card_ref(cond.card_ref); + auto sc_card = s->card_for_set_card_ref(cond.card_ref); if (cond.type != ConditionType::NONE) { if ((!sc_card || ((sc_card != card) && (sc_card->card_flags & 2))) && ce && ((ce->def.type == CardType::ITEM) || ce->def.is_sc()) && (cond.remaining_turns != 100) && - (client_id_for_card_ref(card->get_card_ref()) == client_id_for_card_ref(cond.card_ref))) { + (s->options.is_trial() || (client_id_for_card_ref(card->get_card_ref()) == client_id_for_card_ref(cond.card_ref)))) { return false; } if (cond.remaining_turns == 102) { @@ -3350,14 +3480,15 @@ void CardSpecial::send_6xB4x06_for_stat_delta( } } + bool is_trial = this->server()->options.is_trial(); G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = flags | 2; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 10); cmd.effect.target_card_ref = card->get_card_ref(); cmd.effect.value = -hp_delta; - cmd.effect.ap = clamp(card->ap, 0, 99); + cmd.effect.ap = is_trial ? card->ap : clamp(card->ap, 0, 99); cmd.effect.current_hp = clamp(card->get_current_hp(), 0, 99); - cmd.effect.tp = clamp(card->tp, 0, 99); + cmd.effect.tp = is_trial ? card->tp : clamp(card->tp, 0, 99); if (!unknown_p7) { cmd.effect.current_hp |= 0x80; } @@ -3378,22 +3509,36 @@ bool CardSpecial::should_cancel_condition_due_to_anti_abnormality( (card->get_card_ref() != sc_card_ref))) { return true; } - auto ce = card->get_definition(); - if (ce->def.is_sc() && (eff.type == ConditionType::FREEZE)) { - return true; + + auto s = this->server(); + if (s->options.is_trial()) { + if (this->card_has_condition_with_ref(card, ConditionType::ABILITY_TRAP, 0xFFFF, 0xFFFF)) { + return true; + } + } else { + if (card->get_definition()->def.is_sc() && (eff.type == ConditionType::FREEZE)) { + return true; + } } + switch (eff.type) { + case ConditionType::GUOM: + case ConditionType::CURSE: + if (s->options.is_trial()) { + return false; + } case ConditionType::IMMOBILE: case ConditionType::HOLD: - case ConditionType::GUOM: case ConditionType::PARALYZE: case ConditionType::ACID: - case ConditionType::CURSE: case ConditionType::FREEZE: case ConditionType::DROP: { - const auto* cond = this->find_condition_with_parameters(card, ConditionType::ANTI_ABNORMALITY_2, 0xFFFF, 0xFF); - return (cond != nullptr) || - this->server()->ruler_server->card_ref_is_boss_sc(card->get_card_ref()); + if (s->options.is_trial()) { + return (card->find_condition(ConditionType::ANTI_ABNORMALITY_2) != nullptr); + } else { + const auto* cond = this->find_condition_with_parameters(card, ConditionType::ANTI_ABNORMALITY_2, 0xFFFF, 0xFF); + return (cond != nullptr) || s->ruler_server->card_ref_is_boss_sc(card->get_card_ref()); + } } default: return false; @@ -3676,7 +3821,12 @@ void CardSpecial::evaluate_and_apply_effects( return; } - // TODO: NTE has an extra check here. Implement it. + if (s->options.is_trial()) { + auto set_card = s->card_for_set_card_ref(set_card_ref); + if ((set_card != nullptr) && set_card->get_condition_value(ConditionType::ABILITY_TRAP)) { + return; + } + } uint16_t as_attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 2); if (as_attacker_card_ref == 0xFFFF) { @@ -3822,7 +3972,7 @@ void CardSpecial::evaluate_and_apply_effects( cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(as_attacker_card_ref, 0x14); cmd.effect.target_card_ref = target_card->get_card_ref(); - cmd.effect.value = (target_card->action_chain).conditions[applied_cond_index].remaining_turns; + cmd.effect.value = s->options.is_trial() ? 0 : target_card->action_chain.conditions[applied_cond_index].remaining_turns; cmd.effect.operation = static_cast(card_effect.type); s->send(cmd); @@ -4146,7 +4296,8 @@ void CardSpecial::on_card_destroyed( for (size_t cond_index = 0; cond_index < 9; cond_index++) { auto& cond = attacker_card->action_chain.conditions[cond_index]; if (cond.type == ConditionType::CURSE) { - this->execute_effect(cond, attacker_card, 0, 0, ConditionType::CURSE, 4, 0xFFFF); + bool is_trial = this->server()->options.is_trial(); + this->execute_effect(cond, attacker_card, 0, 0, ConditionType::CURSE, is_trial ? 0x1F : 0x04, 0xFFFF); } } } diff --git a/src/Episode3/DataIndexes.cc b/src/Episode3/DataIndexes.cc index 20c67229..160adeb6 100644 --- a/src/Episode3/DataIndexes.cc +++ b/src/Episode3/DataIndexes.cc @@ -341,10 +341,15 @@ const char* name_for_direction(Direction d) { } } -bool card_class_is_tech_like(CardClass cc) { - return (cc == CardClass::TECH) || - (cc == CardClass::PHOTON_BLAST) || - (cc == CardClass::BOSS_TECH); +bool card_class_is_tech_like(CardClass cc, bool is_trial) { + // NTE does not consider BOSS_TECH to be a tech-like card class, but that's + // probably because that card class just doesn't exist on NTE. Still, we + // handle + if (is_trial) { + return (cc == CardClass::TECH) || (cc == CardClass::PHOTON_BLAST); + } else { + return (cc == CardClass::TECH) || (cc == CardClass::PHOTON_BLAST) || (cc == CardClass::BOSS_TECH); + } } static const unordered_map description_for_expr_token({ @@ -745,8 +750,14 @@ string CardDefinition::Effect::str_for_arg(const string& arg) { } catch (const out_of_range&) { return arg + " (Req. condition: unknown)"; } - case 'o': - return arg + " (Req. prev effect conditions passed)"; + case 'o': { + const char* suffix = ((value / 10) == 1) ? " on opponent card" : " on self"; + if (value == 0) { + return string_printf("%s (Req. any previous effect%s)", arg.c_str(), suffix); + } else { + return string_printf("%s (Req. effect %zu passed%s)", arg.c_str(), static_cast(value % 10), suffix); + } + } case 'p': try { return string_printf("%s (Target: %s)", arg.c_str(), description_for_p_target.at(value)); @@ -1456,7 +1467,7 @@ RulesTrial::RulesTrial(const Rules& r) phase_time_limit(r.phase_time_limit), allowed_cards(r.allowed_cards), atk_dice_max(r.max_dice), - def_dice_max(r.max_dice), + def_dice_max(r.def_dice_range ? (r.def_dice_range & 0x0F) : r.max_dice), disable_deck_shuffle(r.disable_deck_shuffle), disable_deck_loop(r.disable_deck_loop), char_hp(r.char_hp), diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index 76fa6bed..c14881e5 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -164,7 +164,7 @@ enum class CardClass : uint16_t { }; const char* name_for_card_class(CardClass cc); -bool card_class_is_tech_like(CardClass cc); +bool card_class_is_tech_like(CardClass cc, bool is_trial); enum class TargetMode : uint8_t { NONE = 0x00, // Used for defense cards, mags, shields, etc. @@ -300,7 +300,7 @@ enum class ConditionType : uint8_t { UNKNOWN_75 = 0x75, REFLECT = 0x76, // Generate reverse attack UNKNOWN_77 = 0x77, - ANY = 0x78, // Not a real condition; used as a wildcard in search functions + ANY = 0x78, // Not a real condition; used as a wildcard in search functions. Has value 0x64 on NTE UNKNOWN_79 = 0x79, UNKNOWN_7A = 0x7A, UNKNOWN_7B = 0x7B, @@ -984,7 +984,7 @@ struct RulesTrial { /* 00 */ uint8_t overall_time_limit = 0; /* 01 */ uint8_t phase_time_limit = 0; /* 02 */ AllowedCards allowed_cards = AllowedCards::ALL; - /* 03 */ uint8_t atk_dice_max = 1; // TODO: Are these actually maxes? Look at the dice roll function + /* 03 */ uint8_t atk_dice_max = 6; /* 04 */ uint8_t def_dice_max = 6; /* 05 */ uint8_t disable_deck_shuffle = 0; /* 06 */ uint8_t disable_deck_loop = 0; diff --git a/src/Episode3/PlayerState.cc b/src/Episode3/PlayerState.cc index 2499c8f5..f21eb15c 100644 --- a/src/Episode3/PlayerState.cc +++ b/src/Episode3/PlayerState.cc @@ -93,7 +93,7 @@ void PlayerState::init() { this->draw_initial_hand(); if (s->options.is_trial()) { this->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); - // TODO: NTE calls 80243310(1) here + this->send_set_card_updates(true); } s->assist_server->hand_and_equip_states[this->client_id] = this->hand_and_equip; @@ -1167,25 +1167,29 @@ uint8_t PlayerState::roll_dice_with_effects(size_t num_dice) { void PlayerState::send_set_card_updates(bool always_send) { auto s = this->server(); + bool is_trial = s->options.is_trial(); - uint16_t mask; - if (!this->sc_card) { + uint16_t mask = 0; + if (this->sc_card) { + this->sc_card->send_6xB4x4E_4C_4D_if_needed(always_send); + } else if (is_trial) { + this->send_6xB4x0A_for_set_card(0); + } else { this->set_card_action_chains->at(0).clear(); this->set_card_action_metadatas->at(0).clear(); - mask = 1; - } else { - this->sc_card->send_6xB4x4E_4C_4D_if_needed(always_send); - mask = 0; + mask |= 1; } for (size_t set_index = 0; set_index < 8; set_index++) { auto card = this->set_cards[set_index]; - if (!card) { + if (card) { + card->send_6xB4x4E_4C_4D_if_needed(always_send); + } else if (is_trial) { + this->send_6xB4x0A_for_set_card(set_index + 1); + } else { mask |= 1 << (set_index + 1); this->set_card_action_chains->at(set_index + 1).clear(); this->set_card_action_metadatas->at(set_index + 1).clear(); - } else { - card->send_6xB4x4E_4C_4D_if_needed(always_send); } } @@ -1773,7 +1777,7 @@ void PlayerState::unknown_8023C174() { AssistFlag::ELIGIBLE_FOR_DICE_BOOST); this->set_assist_flags_from_assist_effects(); this->update_hand_and_equip_state_and_send_6xB4x02_if_needed(0); - this->send_set_card_updates(0); + this->send_set_card_updates(); } void PlayerState::handle_homesick_assist_effect_from_bomb(shared_ptr card) { @@ -1960,4 +1964,36 @@ void PlayerState::compute_team_dice_bonus_after_draw_phase() { this->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); } +void PlayerState::send_6xB4x0A_for_set_card(size_t set_index) { + if (set_index >= 9) { + return; + } + + auto s = this->server(); + + // The original code (in NTE) calls memcmp here, but then ignores the results + // and always copies the chain and metadata. + // this->set_card_action_chains->at(set_index) == this->unknown_a12; + // this->set_card_action_metadatas->at(set_index) == this->unknown_a13; + this->set_card_action_chains->at(set_index) = this->unknown_a12; + this->set_card_action_metadatas->at(set_index) = this->unknown_a13; + + if (s->options.is_trial()) { + G_UpdateActionChainAndMetadata_Ep3NTE_6xB4x0A cmd; + cmd.client_id = this->client_id; + cmd.index = set_index; + cmd.chain = this->unknown_a12; + cmd.metadata = this->unknown_a13; + s->send(cmd); + + } else { + G_UpdateActionChainAndMetadata_Ep3_6xB4x0A cmd; + cmd.client_id = this->client_id; + cmd.index = set_index; + cmd.chain = this->unknown_a12; + cmd.metadata = this->unknown_a13; + s->send(cmd); + } +} + } // namespace Episode3 diff --git a/src/Episode3/PlayerState.hh b/src/Episode3/PlayerState.hh index b5fbe663..4da706e9 100644 --- a/src/Episode3/PlayerState.hh +++ b/src/Episode3/PlayerState.hh @@ -138,6 +138,7 @@ public: void roll_main_dice(); void unknown_8023C110(); void compute_team_dice_bonus_after_draw_phase(); + void send_6xB4x0A_for_set_card(size_t set_index); private: std::weak_ptr w_server; diff --git a/src/Episode3/PlayerStateSubordinates.cc b/src/Episode3/PlayerStateSubordinates.cc index 6453a195..143089ae 100644 --- a/src/Episode3/PlayerStateSubordinates.cc +++ b/src/Episode3/PlayerStateSubordinates.cc @@ -439,7 +439,7 @@ void ActionChainWithConds::compute_attack_medium(shared_ptr server) { if (!ce) { continue; } - if (card_class_is_tech_like(ce->def.card_class())) { + if (card_class_is_tech_like(ce->def.card_class(), server->options.is_trial())) { this->chain.attack_medium = AttackMedium::TECH; } } diff --git a/src/Episode3/RulerServer.cc b/src/Episode3/RulerServer.cc index 2727d44c..7cabf87e 100644 --- a/src/Episode3/RulerServer.cc +++ b/src/Episode3/RulerServer.cc @@ -636,9 +636,11 @@ bool RulerServer::card_ref_has_free_maneuver(uint16_t card_ref) const { } bool RulerServer::card_ref_is_aerial(uint16_t card_ref) const { - const auto* stat = this->short_status_for_card_ref(card_ref); - if (!stat || !this->card_exists_by_status(*stat)) { - return false; + if (!this->server()->options.is_trial()) { + const auto* stat = this->short_status_for_card_ref(card_ref); + if (!stat || !this->card_exists_by_status(*stat)) { + return false; + } } uint8_t client_id = client_id_for_card_ref(card_ref); @@ -905,7 +907,8 @@ bool RulerServer::check_usability_or_condition_apply( uint8_t def_effect_index, bool is_item_usability_check, AttackMedium attack_medium) const { - auto log = this->server()->log_stack(string_printf("check_usability_or_condition_apply(%02hhX, #%04hX, %02hhX, #%04hX, #%04hX, %02hhX, %s, %s): ", client_id1, card_id1, client_id2, card_id2, card_id3, def_effect_index, is_item_usability_check ? "true" : "false", name_for_attack_medium(attack_medium))); + auto s = this->server(); + auto log = s->log_stack(string_printf("check_usability_or_condition_apply(%02hhX, #%04hX, %02hhX, #%04hX, #%04hX, %02hhX, %s, %s): ", client_id1, card_id1, client_id2, card_id2, card_id3, def_effect_index, is_item_usability_check ? "true" : "false", name_for_attack_medium(attack_medium))); if (static_cast(attack_medium) & 0x80) { attack_medium = AttackMedium::UNKNOWN; @@ -918,7 +921,7 @@ bool RulerServer::check_usability_or_condition_apply( log.debug("ce1 missing"); return false; } - if ((ce1->def.type == CardType::ITEM) && this->card_id_is_boss_sc(card_id2)) { + if (!s->options.is_trial() && (ce1->def.type == CardType::ITEM) && this->card_id_is_boss_sc(card_id2)) { log.debug("ce1 is item and card_id2 is boss sc"); return false; } @@ -954,8 +957,8 @@ bool RulerServer::check_usability_or_condition_apply( // creature card is usable, the two client IDs should be the same or the // second should not be given, so we'd return true if the criterion passes. If // neither of these cases apply, we should return false as a failsafe even if - // the criterion passes. - bool ret = (!(def_effect_index & 0x80) || (client_id1 == client_id2)) || (client_id2 == 0xFF); + // the criterion passes. NTE did not have such a check. + bool ret = s->options.is_trial() || (!(def_effect_index & 0x80) || (client_id1 == client_id2)) || (client_id2 == 0xFF); switch (criterion_code) { case CriterionCode::NONE: return ret; @@ -1371,13 +1374,14 @@ uint16_t RulerServer::compute_attack_or_defense_costs( tech_cost_bias = -1; } + auto s = this->server(); for (size_t z = 0; pa.action_card_refs[z] != 0xFFFF; z++) { auto ce = this->definition_for_card_ref(pa.action_card_refs[z]); if (has_mighty_knuckle || !ce || (ce->def.type != CardType::ACTION)) { return 99; } total_cost += (ce->def.self_cost + cost_bias); - if (card_class_is_tech_like(ce->def.card_class())) { + if (card_class_is_tech_like(ce->def.card_class(), s->options.is_trial())) { total_cost += tech_cost_bias; } total_ally_cost += ce->def.ally_cost; @@ -2520,13 +2524,14 @@ void RulerServer::replace_D1_D2_rank_cards_with_Attack( } AttackMedium RulerServer::get_attack_medium(const ActionState& pa) const { + bool is_trial = this->server()->options.is_trial(); for (size_t z = 0; z < 8; z++) { uint16_t card_ref = pa.action_card_refs[z]; if (card_ref == 0xFFFF) { return AttackMedium::PHYSICAL; } auto ce = this->definition_for_card_ref(card_ref); - if (ce && card_class_is_tech_like(ce->def.card_class())) { + if (ce && card_class_is_tech_like(ce->def.card_class(), is_trial)) { return AttackMedium::TECH; } } diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index 94ad6995..bbf5fbb0 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -822,7 +822,6 @@ void Server::draw_phase_after() { } if (no_winner_specified) { if (this->options.is_trial()) { - // TODO: This looks like an incomplete version of compute_losing_team_id_and_add_winner_flags; reconcile this throw runtime_error("unimplemented NTE condition"); } else { this->compute_losing_team_id_and_add_winner_flags(0); @@ -1705,7 +1704,7 @@ void Server::on_server_data_input(shared_ptr sender_c, const string& dat } void Server::handle_CAx0B_mulligan_hand(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "REDRAW"); if (in_cmd.client_id >= 4) { @@ -1738,7 +1737,7 @@ void Server::handle_CAx0B_mulligan_hand(shared_ptr, const string& data) } void Server::handle_CAx0C_end_mulligan_phase(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "SETUP ADV 2"); if (in_cmd.client_id >= 4) { @@ -1796,7 +1795,7 @@ void Server::handle_CAx0C_end_mulligan_phase(shared_ptr, const string& d } void Server::handle_CAx0D_end_non_action_phase(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END PHASE"); if (in_cmd.client_id >= 4) { throw runtime_error("invalid client ID"); @@ -1816,7 +1815,7 @@ void Server::handle_CAx0D_end_non_action_phase(shared_ptr, const string& } void Server::handle_CAx0E_discard_card_from_hand(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "DISCARD"); if (in_cmd.client_id >= 4) { @@ -1856,7 +1855,7 @@ void Server::handle_CAx0E_discard_card_from_hand(shared_ptr, const strin } void Server::handle_CAx0F_set_card_from_hand(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "SET FC"); if (in_cmd.client_id >= 4) { throw runtime_error("invalid client ID"); @@ -1898,7 +1897,7 @@ void Server::handle_CAx0F_set_card_from_hand(shared_ptr, const string& d } void Server::handle_CAx10_move_fc_to_location(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "MOVE"); if (in_cmd.client_id >= 4) { @@ -1936,7 +1935,7 @@ void Server::handle_CAx10_move_fc_to_location(shared_ptr, const string& } void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "ENQUEUE ACT"); if (in_cmd.client_id >= 4) { @@ -1972,7 +1971,7 @@ void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr, const st } void Server::handle_CAx12_end_attack_list(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "END ATK LIST"); if (in_cmd.client_id >= 4) { @@ -1994,8 +1993,9 @@ void Server::handle_CAx12_end_attack_list(shared_ptr, const string& data this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code); } -void Server::handle_CAx13_update_map_during_setup(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); +template +void Server::handle_CAx13_update_map_during_setup_t(shared_ptr, const string& data) { + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.header.subsubcommand, "UPDATE MAP"); @@ -2036,8 +2036,16 @@ void Server::handle_CAx13_update_map_during_setup(shared_ptr, const stri } } +void Server::handle_CAx13_update_map_during_setup(shared_ptr c, const string& data) { + if (this->options.is_trial()) { + this->handle_CAx13_update_map_during_setup_t(c, data); + } else { + this->handle_CAx13_update_map_during_setup_t(c, data); + } +} + void Server::handle_CAx14_update_deck_during_setup(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "UPDATE DECK"); @@ -2062,7 +2070,7 @@ void Server::handle_CAx14_update_deck_during_setup(shared_ptr, const str if (verify_error) { throw runtime_error(string_printf("invalid deck: -0x%" PRIX32, verify_error)); } - if (!(this->options.behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) { + if (!this->options.is_trial() && !(this->options.behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) { this->ruler_server->replace_D1_D2_rank_cards_with_Attack(entry.card_ids); } *this->deck_entries[in_cmd.client_id] = in_cmd.entry; @@ -2084,7 +2092,7 @@ void Server::handle_CAx14_update_deck_during_setup(shared_ptr, const str } void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.header.subsubcommand, "HARD RESET"); @@ -2102,7 +2110,7 @@ void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr, con } void Server::handle_CAx1B_update_player_name(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.entry.client_id, in_cmd.header.subsubcommand, "UPDATE NAME"); @@ -2134,7 +2142,7 @@ void Server::handle_CAx1B_update_player_name(shared_ptr, const string& d } void Server::handle_CAx1D_start_battle(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.header.subsubcommand, "START BATTLE"); @@ -2171,7 +2179,7 @@ void Server::handle_CAx1D_start_battle(shared_ptr, const string& data) { } void Server::handle_CAx21_end_battle(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.header.subsubcommand, "END BATTLE"); if (this->setup_phase == SetupPhase::BATTLE_ENDED) { @@ -2186,7 +2194,7 @@ void Server::handle_CAx21_end_battle(shared_ptr, const string& data) { } void Server::handle_CAx28_end_defense_list(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "END DEF LIST"); if (in_cmd.client_id >= 4) { @@ -2239,13 +2247,13 @@ void Server::handle_CAx28_end_defense_list(shared_ptr, const string& dat } void Server::handle_CAx2B_legacy_set_card(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message(in_cmd.header.subsubcommand, "EXEC LEGACY"); // Sega's original implementation does nothing here, so we do nothing as well. } void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); uint8_t card_ref_client_id = client_id_for_card_ref(in_cmd.card_ref); this->send_debug_command_received_message( @@ -2322,7 +2330,7 @@ void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr, const str } void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "SETUP ADV 1"); if (in_cmd.client_id >= 4) { @@ -2352,14 +2360,14 @@ void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(shared } void Server::handle_CAx3A_time_limit_expired(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message(in_cmd.header.subsubcommand, "TIME EXPIRED"); // We don't need to do anything here because the overall time limit is tracked // server-side instead. } void Server::handle_CAx40_map_list_request(shared_ptr sender_c, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.header.subsubcommand, "MAP LIST"); @@ -2431,7 +2439,7 @@ void Server::send_6xB6x41_to_all_clients() const { } void Server::handle_CAx41_map_request(shared_ptr, const string& data) { - const auto& cmd = check_size_t(data); + const auto& cmd = check_size_t(data); this->send_debug_command_received_message( cmd.header.subsubcommand, "MAP DATA"); @@ -2440,7 +2448,7 @@ void Server::handle_CAx41_map_request(shared_ptr, const string& data) { } void Server::handle_CAx48_end_turn(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "END TURN"); if (in_cmd.client_id >= 4) { @@ -2458,7 +2466,7 @@ void Server::handle_CAx48_end_turn(shared_ptr, const string& data) { } void Server::handle_CAx49_card_counts(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.header.sender_client_id, in_cmd.header.subsubcommand, "CARD COUNTS"); diff --git a/src/Episode3/Server.hh b/src/Episode3/Server.hh index 96f77535..f3fc3842 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -195,6 +195,8 @@ public: void handle_CAx10_move_fc_to_location(std::shared_ptr sender_c, const std::string& data); void handle_CAx11_enqueue_attack_or_defense(std::shared_ptr sender_c, const std::string& data); void handle_CAx12_end_attack_list(std::shared_ptr sender_c, const std::string& data); + template + void handle_CAx13_update_map_during_setup_t(std::shared_ptr sender_c, const std::string& data); void handle_CAx13_update_map_during_setup(std::shared_ptr sender_c, const std::string& data); void handle_CAx14_update_deck_during_setup(std::shared_ptr sender_c, const std::string& data); void handle_CAx15_unused_hard_reset_server_state(std::shared_ptr sender_c, const std::string& data);