diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 7dbecf3a..f4166821 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -1762,6 +1762,54 @@ static void server_command_ep3_set_def_dice_range(shared_ptr c, const st } } +static void server_command_ep3_replace_assist_card(shared_ptr c, const std::string& args) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); + check_is_game(l, true); + check_is_ep3(c, true); + check_cheats_enabled(l, c); + + if (l->episode != Episode::EP3) { + throw logic_error("non-Ep3 client in Ep3 game"); + } + if (!l->ep3_server) { + send_text_message(c, "$C6Episode 3 server\nis not initialized"); + return; + } + if (l->ep3_server->setup_phase != Episode3::SetupPhase::MAIN_BATTLE) { + send_text_message(c, "$C6Battle has not\nyet begun"); + return; + } + if (args.empty()) { + send_text_message(c, "$C6Missing arguments"); + return; + } + + uint8_t client_id; + string card_name; + if (isdigit(args[0])) { + auto tokens = split(args, ' ', 1); + client_id = stoul(tokens.at(0), nullptr, 0); + card_name = tokens.at(1); + } else { + client_id = c->lobby_client_id; + card_name = args; + } + + shared_ptr ce; + try { + ce = l->ep3_server->options.card_index->definition_for_name_normalized(card_name); + } catch (const out_of_range&) { + send_text_message(c, "$C6Card not found"); + return; + } + if (ce->def.type != Episode3::CardType::ASSIST) { + send_text_message(c, "$C6Card is not an\nAssist card"); + return; + } + l->ep3_server->force_replace_assist_card(client_id, ce->def.card_id); +} + static void server_command_ep3_unset_field_character(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); auto l = c->require_lobby(); @@ -1823,10 +1871,7 @@ static void server_command_get_ep3_battle_stat(shared_ptr c, const std:: send_text_message(c, "$C6Battle has not\nyet started"); return; } - if (c->lobby_client_id >= 4) { - throw logic_error("client ID is too large"); - } - auto ps = l->ep3_server->player_states[c->lobby_client_id]; + auto ps = l->ep3_server->player_states.at(c->lobby_client_id); if (!ps) { send_text_message(c, "$C6Player is missing"); return; @@ -1950,6 +1995,7 @@ static const unordered_map chat_commands({ {"$saverec", {server_command_saverec, nullptr}}, {"$sc", {server_command_send_client, proxy_command_send_client}}, {"$secid", {server_command_secid, proxy_command_secid}}, + {"$setassist", {server_command_ep3_replace_assist_card, nullptr}}, {"$si", {server_command_server_info, nullptr}}, {"$silence", {server_command_silence, nullptr}}, {"$song", {server_command_song, proxy_command_song}}, diff --git a/src/Episode3/CardSpecial.cc b/src/Episode3/CardSpecial.cc index cb2eae5b..255d2e84 100644 --- a/src/Episode3/CardSpecial.cc +++ b/src/Episode3/CardSpecial.cc @@ -1850,7 +1850,7 @@ bool CardSpecial::execute_effect( if (client_id == 0xFF) { return false; } - auto ps = this->server()->player_states[client_id]; + auto ps = this->server()->player_states.at(client_id); if (!ps) { return false; } @@ -1993,7 +1993,7 @@ bool CardSpecial::execute_effect( if (client_id == 0xFF) { return false; } - auto ps = this->server()->player_states[client_id]; + auto ps = this->server()->player_states.at(client_id); if (!ps) { return false; } @@ -2034,8 +2034,8 @@ 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[attacker_client_id]; - auto target_ps = this->server()->player_states[target_client_id]; + auto attacker_ps = this->server()->player_states.at(attacker_client_id); + auto target_ps = this->server()->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(); @@ -2157,8 +2157,8 @@ 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[client_id]) { - uint8_t team_id = this->server()->player_states[client_id]->get_team_id(); + 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 ((clamped_expr_value + existing_exp) < 0) { clamped_expr_value = -existing_exp; @@ -3672,7 +3672,7 @@ void CardSpecial::evaluate_and_apply_effects( DiceRoll dice_roll; uint8_t client_id = client_id_for_card_ref(dice_cmd.effect.target_card_ref); - auto set_card_ps = (client_id == 0xFF) ? nullptr : this->server()->player_states[client_id]; + auto set_card_ps = (client_id == 0xFF) ? nullptr : this->server()->player_states.at(client_id); dice_roll.value = 1; if (set_card_ps) { diff --git a/src/Episode3/PlayerState.cc b/src/Episode3/PlayerState.cc index c28ce598..25812132 100644 --- a/src/Episode3/PlayerState.cc +++ b/src/Episode3/PlayerState.cc @@ -42,7 +42,7 @@ PlayerState::PlayerState(uint8_t client_id, shared_ptr server) void PlayerState::init() { auto s = this->server(); - if (s->player_states[this->client_id].get() != this) { + if (s->player_states.at(this->client_id).get() != this) { // Note: The original code handles this, but we don't. This appears not to // ever happen, so we didn't bother implementing it. throw logic_error("replacing a player state object is not permitted"); @@ -1267,7 +1267,7 @@ bool PlayerState::set_card_from_hand( return false; } - auto target_ps = s->player_states[assist_target_client_id]; + auto target_ps = s->player_states.at(assist_target_client_id); if (target_ps) { uint16_t prev_assist_card_ref = target_ps->card_refs[6]; target_ps->discard_set_assist_card(); @@ -1752,7 +1752,7 @@ void PlayerState::unknown_8023C174() { this->send_set_card_updates(0); } -void PlayerState::handle_homesick_assist_effect(shared_ptr card) { +void PlayerState::handle_homesick_assist_effect_from_bomb(shared_ptr card) { if (!card) { return; } diff --git a/src/Episode3/PlayerState.hh b/src/Episode3/PlayerState.hh index 178ba9c3..29a2e4ce 100644 --- a/src/Episode3/PlayerState.hh +++ b/src/Episode3/PlayerState.hh @@ -139,7 +139,7 @@ public: int16_t get_assist_turns_remaining(); bool set_action_cards_for_action_state(const ActionState& pa); void unknown_8023C174(); - void handle_homesick_assist_effect(std::shared_ptr card); + void handle_homesick_assist_effect_from_bomb(std::shared_ptr card); void apply_main_die_assist_effects(uint8_t* die_value) const; void roll_main_dice(); void unknown_8023C110(); diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index ccfabe7f..c5bfde98 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -418,7 +418,7 @@ shared_ptr Server::card_for_set_card_ref(uint16_t card_ref) { if (client_id == 0xFF) { return nullptr; } - auto ps = this->player_states[client_id]; + auto ps = this->player_states.at(client_id); if (!ps) { return nullptr; } @@ -444,7 +444,7 @@ shared_ptr Server::card_for_set_card_ref(uint16_t card_ref) const { if (client_id == 0xFF) { return nullptr; } - auto ps = this->player_states[client_id]; + auto ps = this->player_states.at(client_id); if (!ps) { return nullptr; } @@ -464,10 +464,11 @@ shared_ptr Server::card_for_set_card_ref(uint16_t card_ref) const { uint16_t Server::card_id_for_card_ref(uint16_t card_ref) const { uint8_t client_id = client_id_for_card_ref(card_ref); if (client_id != 0xFF) { - if (!this->player_states[client_id]) { + auto ps = this->player_states.at(client_id); + if (!ps) { return 0xFFFF; } - auto deck = this->player_states[client_id]->get_deck(); + auto deck = ps->get_deck(); if (deck) { return deck->card_id_for_card_ref(card_ref); } @@ -558,8 +559,19 @@ bool Server::check_for_battle_end() { return ret; } +void Server::force_replace_assist_card(uint8_t client_id, uint16_t card_id) { + auto ps = this->player_states.at(client_id); + if (!ps) { + throw runtime_error("player does not exist"); + } + ps->replace_assist_card_by_id(card_id); +} + void Server::force_destroy_field_character(uint8_t client_id, size_t visible_index) { - auto ps = this->player_states[client_id]; + auto ps = this->player_states.at(client_id); + if (!ps) { + throw runtime_error("player does not exist"); + } // TODO: Is it possible for there to be gaps in the set cards array? If not, // we could just do a direct array lookup here instead of this loop @@ -584,7 +596,7 @@ void Server::force_destroy_field_character(uint8_t client_id, size_t visible_ind } void Server::force_battle_result(uint8_t specified_client_id, bool set_winner) { - auto specified_ps = this->player_states[specified_client_id]; + auto specified_ps = this->player_states.at(specified_client_id); for (size_t z = 0; z < 4; z++) { auto ps = this->player_states[z]; if (ps) { @@ -824,7 +836,7 @@ void Server::end_attack_list_for_client(uint8_t client_id) { return; } - auto ps = this->player_states[client_id]; + auto ps = this->player_states.at(client_id); if (!ps) { return; } @@ -1670,10 +1682,11 @@ void Server::handle_CAx0B_mulligan_hand(shared_ptr, const string& data) error_code = -0x78; } if (error_code == 0) { - if (!this->player_states[in_cmd.client_id]) { + auto ps = this->player_states.at(in_cmd.client_id); + if (!ps) { error_code = -0x72; } else { - this->player_states[in_cmd.client_id]->do_mulligan(); + ps->do_mulligan(); } } @@ -1709,18 +1722,17 @@ void Server::handle_CAx0C_end_mulligan_phase(shared_ptr, const string& d this->send(out_cmd_ack); if (error_code == 0) { - if (!this->player_states[in_cmd.client_id]) { + auto ps = this->player_states.at(in_cmd.client_id); + if (!ps) { error_code = -0x72; } else { this->clients_done_in_mulligan_phase[in_cmd.client_id] = true; - auto ps = this->player_states[in_cmd.client_id]; ps->assist_flags |= AssistFlag::READY_TO_END_PHASE; ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); bool all_clients_ready = true; for (size_t z = 0; z < 4; z++) { - if (this->player_states[z] && - !this->clients_done_in_mulligan_phase[z]) { + if (this->player_states[z] && !this->clients_done_in_mulligan_phase[z]) { all_clients_ready = false; break; } @@ -1781,7 +1793,7 @@ void Server::handle_CAx0E_discard_card_from_hand(shared_ptr, const strin } if (error_code == 0) { - auto ps = this->player_states[in_cmd.client_id]; + auto ps = this->player_states.at(in_cmd.client_id); if (!ps) { error_code = -0x72; } else if (!(ps->assist_flags & AssistFlag::IS_SKIPPING_TURN)) { @@ -1824,11 +1836,11 @@ void Server::handle_CAx0F_set_card_from_hand(shared_ptr, const string& d } if (error_code == 0) { this->ruler_server->error_code1 = 0; - if (!this->player_states[in_cmd.client_id]) { + auto ps = this->player_states.at(in_cmd.client_id); + if (!ps) { this->ruler_server->error_code1 = -0x72; } else { - this->player_states[in_cmd.client_id]->set_card_from_hand( - in_cmd.card_ref, in_cmd.set_index, &in_cmd.loc, in_cmd.assist_target_player, 0); + ps->set_card_from_hand(in_cmd.card_ref, in_cmd.set_index, &in_cmd.loc, in_cmd.assist_target_player, 0); } } else { this->ruler_server->error_code1 = error_code; @@ -1861,12 +1873,12 @@ void Server::handle_CAx10_move_fc_to_location(shared_ptr, const string& error_code = -0x78; } if (error_code == 0) { - if (!this->player_states[in_cmd.client_id]) { + auto ps = this->player_states.at(in_cmd.client_id); + if (!ps) { this->ruler_server->error_code2 = -0x72; } else { this->ruler_server->error_code2 = 0; - this->player_states[in_cmd.client_id]->move_card_to_location_by_card_index( - in_cmd.set_index, in_cmd.loc); + ps->move_card_to_location_by_card_index(in_cmd.set_index, in_cmd.loc); } } else { this->ruler_server->error_code2 = error_code; @@ -2655,7 +2667,7 @@ void Server::execute_bomb_assist_effect() { auto card = ps->get_set_card(set_index); if (card && !(card->card_flags & 2) && ((card->get_current_hp() == max_hp) || (card->get_current_hp() == min_hp))) { - card->player_state()->handle_homesick_assist_effect(card); + card->player_state()->handle_homesick_assist_effect_from_bomb(card); } } } diff --git a/src/Episode3/Server.hh b/src/Episode3/Server.hh index d63bdd9d..4de02d5b 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -115,6 +115,7 @@ public: void send_commands_for_joining_spectator(Channel& ch) const; void force_battle_result(uint8_t surrendered_client_id, bool set_winner); + void force_replace_assist_card(uint8_t client_id, uint16_t card_id); void force_destroy_field_character(uint8_t client_id, size_t set_index); __attribute__((format(printf, 2, 3))) void send_debug_message_printf(const char* fmt, ...) const; @@ -288,7 +289,7 @@ public: uint32_t should_copy_prev_states_to_current_states; std::shared_ptr card_special; std::shared_ptr state_flags; - std::shared_ptr player_states[4]; + std::array, 4> player_states; parray clients_done_in_mulligan_phase; uint32_t num_pending_attacks_with_cards; std::shared_ptr attack_cards[0x20];