update formatting in src/Episode3

This commit is contained in:
Martin Michelsen
2025-12-06 00:18:53 -08:00
parent 6291e42ba9
commit 976a281e93
18 changed files with 697 additions and 1125 deletions
+79 -113
View File
@@ -12,12 +12,10 @@ using namespace std;
namespace Episode3 {
// This is (obviously) not the original string. The original string is:
// "03/05/29 18:00 by K.Toya" (NTE)
// "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya" (Final)
static const char* VERSION_SIGNATURE =
"newserv Ep3 based on [V1][FINAL2.0] 03/09/13 15:30 by K.Toya";
static const char* VERSION_SIGNATURE_NTE =
"newserv Ep3 NTE based on 03/05/29 18:00 by K.Toya";
// NTE: "03/05/29 18:00 by K.Toya"
// Final: "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya"
static const char* VERSION_SIGNATURE = "newserv Ep3 based on [V1][FINAL2.0] 03/09/13 15:30 by K.Toya";
static const char* VERSION_SIGNATURE_NTE = "newserv Ep3 NTE based on 03/05/29 18:00 by K.Toya";
Server::PresenceEntry::PresenceEntry() {
this->clear();
@@ -103,10 +101,9 @@ void Server::init() {
this->card_special = make_shared<CardSpecial>(this->shared_from_this());
// Note: The original implementation calls the default PSOV2Encryption
// constructor for random_crypt, which just uses 0 as the seed. It then
// re-seeds the generator later. We instead expect the caller to provide a
// seeded generator, and we don't re-seed it at all.
// Note: The original implementation calls the default PSOV2Encryption constructor for random_crypt, which just uses
// 0 as the seed. It then re-seeds the generator later. We instead expect the caller to provide a seeded generator,
// and we don't re-seed it at all.
// this->random_crypt = make_shared<PSOV2Encryption>(0);
this->state_flags = make_shared<StateFlags>();
@@ -254,10 +251,9 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
size = masked_data.size();
}
// Note: Sega's servers sent battle commands with the 60 command. The handlers
// for 60, 62, and C9 on the client are identical, so we choose to use C9
// instead because it's unique to Episode 3, and therefore seems more
// appropriate to convey battle commands.
// Note: Sega's servers sent battle commands with the 60 command. The handlers for 60, 62, and C9 on the client are
// identical, so we choose to use C9 instead because it's unique to Episode 3, and therefore seems more appropriate
// to convey Episode 3 battle commands.
send_command(l, command, 0x00, data, size);
for (auto watcher_l : l->watcher_lobbies) {
send_command_if_not_loading(watcher_l, command, 0x00, data, size);
@@ -273,14 +269,15 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
}
void Server::send_6xB4x46() const {
// Note: This function is not part of the original implementation; it was
// factored out from its callsites in this file and the strings were changed.
// Note: This function is not part of the original implementation; it was factored out from its callsites in this
// file and the strings were changed.
// NTE doesn't have the date_str2 field, but we send it anyway to make
// debugging easier.
// NTE doesn't have the date_str2 field, but we send it anyway to make debugging easier.
G_ServerVersionStrings_Ep3_6xB4x46 cmd;
cmd.version_signature.encode(this->options.is_nte() ? VERSION_SIGNATURE_NTE : VERSION_SIGNATURE, Language::ENGLISH);
cmd.date_str1.encode(std::format("Card definitions: {:016X}", this->options.card_index->definitions_hash()), Language::ENGLISH);
cmd.date_str1.encode(
std::format("Card definitions: {:016X}", this->options.card_index->definitions_hash()),
Language::ENGLISH);
string build_date = phosg::format_time(BUILD_TIMESTAMP);
cmd.date_str2.encode(std::format("newserv {} compiled at {}", GIT_REVISION_HASH, build_date), Language::ENGLISH);
this->send(cmd);
@@ -293,7 +290,8 @@ string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> ma
phosg::StringWriter w;
uint32_t subcommand_size = (compressed->size() + sizeof(G_MapData_Ep3_6xB6x41) + 3) & (~3);
w.put<G_MapData_Ep3_6xB6x41>({{{{0xB6, 0, 0}, subcommand_size}, 0x41, {}}, vm->map->map_number.load(), compressed->size(), 0});
w.put<G_MapData_Ep3_6xB6x41>(
{{{{0xB6, 0, 0}, subcommand_size}, 0x41, {}}, vm->map->map_number.load(), compressed->size(), 0});
w.write(*compressed);
return std::move(w.str());
}
@@ -311,7 +309,8 @@ void Server::send_commands_for_joining_spectator(std::shared_ptr<Channel> ch) co
if (this->last_chosen_map) {
string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, ch->language, this->options.is_nte());
this->log().info_f("Sending {} version of map {:08X}", name_for_language(ch->language), this->last_chosen_map->map_number);
this->log().info_f(
"Sending {} version of map {:08X}", name_for_language(ch->language), this->last_chosen_map->map_number);
ch->send(0x6C, 0x00, data);
}
@@ -339,8 +338,8 @@ void Server::send_commands_for_joining_spectator(std::shared_ptr<Channel> ch) co
// (send 6xB4x4F for client_id)
// }
ch->send(0xC9, 0x00, this->prepare_6xB4x07_decks_update());
// TODO: Sega sends 6xB4x05 here again; why? Is that necessary? They also
// send 6xB4x02 again for each player after that (but not 6xB4x04)
// TODO: Sega sends 6xB4x05 here again; why? Is that necessary? They also send 6xB4x02 again for each player after
// that (but not 6xB4x04)
ch->send(0xC9, 0x00, this->prepare_6xB4x1C_names_update());
ch->send(0xC9, 0x00, this->prepare_6xB4x50_trap_tile_locations());
{
@@ -613,8 +612,8 @@ void Server::force_destroy_field_character(uint8_t client_id, size_t visible_ind
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
// 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
shared_ptr<Card> set_card = nullptr;
for (size_t set_index = 0; set_index < 8; set_index++) {
if (!ps->set_cards[set_index]) {
@@ -663,9 +662,7 @@ void Server::check_for_destroyed_cards_and_send_6xB4x05_6xB4x02() {
}
bool Server::check_presence_entry(uint8_t client_id) const {
return (client_id < 4)
? this->presence_entries[client_id].player_present
: false;
return (client_id < 4) ? this->presence_entries[client_id].player_present : false;
}
void Server::clear_player_flags_after_dice_phase() {
@@ -826,9 +823,8 @@ void Server::draw_phase_after() {
if (this->current_team_turn1 == this->first_team_turn) {
if (this->map_and_rules->rules.overall_time_limit > 0) {
// Battle time limits are specified in increments of 5 minutes.
// Note: This part is not based on the original code because the timing
// facilities used are different.
// Battle time limits are specified in increments of 5 minutes. This part is not based on the original code
// because the timing facilities used are different.
uint64_t limit_5mins = this->map_and_rules->rules.overall_time_limit;
uint64_t end_usecs = this->battle_start_usecs + (limit_5mins * 300 * 1000 * 1000);
if (phosg::now() >= end_usecs) {
@@ -924,9 +920,8 @@ void Server::end_attack_list_for_client(uint8_t client_id) {
void Server::end_action_phase() {
this->num_pending_attacks = 0;
this->unknown_a15 = 1;
// Annoyingly, this is the original logic. We use an enum because it appears
// that this can only ever be 0 or 2, but we may have to delete the enum if
// that turns out to be false.
// Annoyingly, this is the original logic. We use an enum because it appears that this can only ever be 0 or 2, but
// we may have to delete the enum if that turns out to be false.
this->action_subphase = static_cast<ActionSubphase>(static_cast<uint8_t>(this->action_subphase) + 2);
if (this->options.is_nte()) {
this->unknown_8023EEF4();
@@ -1005,8 +1000,7 @@ bool Server::enqueue_attack_or_defense(uint8_t client_id, ActionState* pa) {
card_ps->send_6xB4x04_if_needed();
}
}
card = this->card_for_set_card_ref(this->send_6xB4x06_if_card_ref_invalid(
pa->original_attacker_card_ref, 2));
card = this->card_for_set_card_ref(this->send_6xB4x06_if_card_ref_invalid(pa->original_attacker_card_ref, 2));
if (card) {
card = this->card_for_set_card_ref(pa->target_card_refs[0]);
if (card) {
@@ -1100,8 +1094,7 @@ void Server::move_phase_after() {
auto ps = this->player_states[client_id];
if (ps) {
auto sc_card = ps->get_sc_card();
if (sc_card && (sc_card->card_flags & 0x80) &&
(sc_card->loc.x == trap_x) && (sc_card->loc.y == trap_y)) {
if (sc_card && (sc_card->card_flags & 0x80) && (sc_card->loc.x == trap_x) && (sc_card->loc.y == trap_y)) {
should_trigger = true;
break;
}
@@ -1111,7 +1104,7 @@ void Server::move_phase_after() {
continue;
}
static const array<vector<uint16_t>, 5> default_trap_card_ids = {
static const array<vector<uint16_t>, 5> DEFAULT_TRAP_CARD_IDS = {
// Red: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace
vector<uint16_t>{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C},
// Blue: Gold Rush, Charity, Requiem
@@ -1125,7 +1118,7 @@ void Server::move_phase_after() {
const vector<uint16_t>* trap_card_ids = &this->options.trap_card_ids.at(trap_type);
if (trap_card_ids->empty()) {
trap_card_ids = &default_trap_card_ids.at(trap_type);
trap_card_ids = &DEFAULT_TRAP_CARD_IDS.at(trap_type);
}
// This is the original implementation. We do something smarter instead.
@@ -1145,9 +1138,7 @@ void Server::move_phase_after() {
auto ps = this->player_states[client_id];
if (ps) {
auto sc_card = ps->get_sc_card();
if (sc_card &&
(abs(sc_card->loc.x - trap_x) < 2) &&
(abs(sc_card->loc.y - trap_y) < 2) &&
if (sc_card && (abs(sc_card->loc.x - trap_x) < 2) && (abs(sc_card->loc.y - trap_y) < 2) &&
ps->replace_assist_card_by_id(trap_card_id)) {
G_EnqueueAnimation_Ep3_6xB4x2C cmd;
cmd.change_type = 0x01;
@@ -1173,14 +1164,12 @@ void Server::move_phase_after() {
// this->chosen_trap_tile_index_of_type[trap_type] = new_index;
// this->send_6xB4x50();
// }
// We instead use an implementation that consumes a constant amount of
// randomness per pass.
// We instead use an implementation that consumes a constant amount of randomness per pass.
if (this->num_trap_tiles_of_type[trap_type] == 2) {
this->chosen_trap_tile_index_of_type[trap_type] ^= 1;
this->send_6xB4x50_trap_tile_locations();
} else if (this->num_trap_tiles_of_type[trap_type] > 2) {
// Generate a new random index, but forbid it from matching the existing
// index
// Generate a new random index, but forbid it from matching the existing index
uint8_t new_index = this->get_random(this->num_trap_tiles_of_type[trap_type] - 1);
if (new_index >= this->chosen_trap_tile_index_of_type[trap_type]) {
new_index++;
@@ -1249,8 +1238,7 @@ int8_t Server::send_6xB4x33_remove_ally_atk_if_needed(const ActionState& pa) {
for (size_t z = 0; z < 4; z++) {
auto ally_ps = this->get_player_state(z);
if ((z != setter_client_id) && ally_ps) {
if ((ally_ps->get_team_id() == setter_ps->get_team_id()) &&
(ally_ps->get_atk_points() >= ally_cost)) {
if ((ally_ps->get_team_id() == setter_ps->get_team_id()) && (ally_ps->get_atk_points() >= ally_cost)) {
ally_has_sufficient_atk = true;
}
}
@@ -1376,11 +1364,10 @@ void Server::set_client_id_ready_to_advance_phase(uint8_t client_id, BattlePhase
ps->assist_flags |= AssistFlag::ELIGIBLE_FOR_DICE_BOOST;
}
} else {
// TODO: It'd be nice to do this in a constant-randomness way, but I'm
// lazy, and this matches Sega's original implementation. The less-lazy
// way to do it would be to roll three dice: one in the range [1, N],
// one in the range [3, N], and one in the range [1, 2] to decide
// whether to swap the first two results.
// TODO: It'd be nice to do this in a constant-randomness way, but I'm lazy, and this matches Sega's original
// implementation. The less-lazy way to do it would be to roll three dice: one in the range [1, 2] to decide
// which of ATK or DEF will be boosted, then roll the ATK die in range [1, N] (or [3, N] if it's boosted), and
// do the same for the DEF die.
for (size_t z = 0; z < 200; z++) {
ps->roll_main_dice_or_apply_after_effects();
if ((ps->get_atk_points() >= 3) || (ps->get_def_points() >= 3)) {
@@ -1431,12 +1418,14 @@ void Server::set_phase_after() {
if (ps) {
auto card = ps->get_sc_card();
if (card) {
this->card_special->apply_action_conditions(EffectWhen::AFTER_SET_PHASE, nullptr, card, is_nte ? 0x1F : 0x04, nullptr);
this->card_special->apply_action_conditions(
EffectWhen::AFTER_SET_PHASE, nullptr, card, is_nte ? 0x1F : 0x04, nullptr);
}
for (size_t set_index = 0; set_index < 8; set_index++) {
auto card = ps->get_set_card(set_index);
if (card) {
this->card_special->apply_action_conditions(EffectWhen::AFTER_SET_PHASE, nullptr, card, is_nte ? 0x1F : 0x04, nullptr);
this->card_special->apply_action_conditions(
EffectWhen::AFTER_SET_PHASE, nullptr, card, is_nte ? 0x1F : 0x04, nullptr);
}
}
}
@@ -1494,9 +1483,7 @@ void Server::set_phase_after() {
for (size_t client_id = 0; client_id < 4; client_id++) {
auto ps = this->player_states[client_id];
if (ps &&
(ps->get_assist_turns_remaining() == 90) &&
(ps->assist_delay_turns < 1)) {
if (ps && (ps->get_assist_turns_remaining() == 90) && (ps->assist_delay_turns < 1)) {
ps->discard_set_assist_card();
ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
}
@@ -1630,8 +1617,7 @@ void Server::setup_and_start_battle() {
size_t num_trap_tiles = 0;
for (size_t y = 0; y < 0x10; y++) {
for (size_t x = 0; x < 0x10; x++) {
if ((this->overlay_state.tiles[y][x] == (trap_type | 0x40)) &&
(num_trap_tiles < 8)) {
if ((this->overlay_state.tiles[y][x] == (trap_type | 0x40)) && (num_trap_tiles < 8)) {
this->trap_tile_locs[trap_type][num_trap_tiles][0] = x;
this->trap_tile_locs[trap_type][num_trap_tiles][1] = y;
num_trap_tiles++;
@@ -1639,7 +1625,6 @@ void Server::setup_and_start_battle() {
}
}
this->num_trap_tiles_of_type[trap_type] = num_trap_tiles;
if (num_trap_tiles > 0) {
this->chosen_trap_tile_index_of_type[trap_type] = this->get_random(num_trap_tiles);
}
@@ -1684,8 +1669,7 @@ void Server::setup_and_start_battle() {
this->send_6xB4x46();
// Re-send game metadata to spectator teams, since loading the battle scene
// seems to delete it
// Re-send game metadata to spectator teams, since loading the battle scene seems to delete it
auto l = this->lobby.lock();
if (l) {
send_ep3_update_game_metadata(l);
@@ -1709,11 +1693,7 @@ G_SetStateFlags_Ep3_6xB4x03 Server::prepare_6xB4x03() const {
cmd.state.tournament_flag = this->options.tournament ? 1 : 0;
for (size_t z = 0; z < 4; z++) {
auto ps = this->player_states[z];
if (!ps) {
cmd.state.client_sc_card_types[z] = CardType::INVALID_FF;
} else {
cmd.state.client_sc_card_types[z] = ps->get_sc_card_type();
}
cmd.state.client_sc_card_types[z] = ps ? ps->get_sc_card_type() : CardType::INVALID_FF;
}
return cmd;
}
@@ -1726,9 +1706,8 @@ void Server::update_battle_state_flags_and_send_6xB4x03_if_needed(bool always_se
}
}
// Returns true if the battle can begin
bool Server::update_registration_phase() {
// Returns true if the battle can begin
auto log = this->log_stack("update_registration_phase: ");
if (this->setup_phase != SetupPhase::REGISTRATION) {
@@ -1801,7 +1780,8 @@ void Server::on_server_data_input(shared_ptr<Client> sender_c, const string& dat
size_t expected_size = header.size * 4;
if (expected_size < data.size()) {
phosg::print_data(stderr, data);
throw runtime_error(std::format("command is incomplete: expected {:X} bytes, received {:X} bytes", expected_size, data.size()));
throw runtime_error(std::format(
"command is incomplete: expected {:X} bytes, received {:X} bytes", expected_size, data.size()));
}
if (header.subcommand != 0xB3) {
throw runtime_error("server data command is not 6xB3");
@@ -1867,8 +1847,7 @@ void Server::handle_CAx0C_end_redraw_initial_hand_phase(shared_ptr<Client>, cons
}
int32_t error_code = 0;
if ((this->setup_phase != SetupPhase::HAND_REDRAW_OPTION) &&
(this->setup_phase != SetupPhase::STARTER_ROLLS)) {
if ((this->setup_phase != SetupPhase::HAND_REDRAW_OPTION) && (this->setup_phase != SetupPhase::STARTER_ROLLS)) {
error_code = -0x5D;
}
@@ -2138,15 +2117,14 @@ void Server::handle_CAx13_update_map_during_setup_t(shared_ptr<Client> c, const
(this->registration_phase != RegistrationPhase::REGISTERED) &&
(this->registration_phase != RegistrationPhase::BATTLE_STARTED)) {
*this->map_and_rules = in_cmd.map_and_rules_state;
// The client will likely send incorrect values for the extended rules (or
// in the case of NTE, no values at all, since the Rules structure is
// smaller). So, use the values from the last chosen map if applicable, or
// the values from the $dicerange command if available.
// The client will likely send incorrect values for the extended rules (or in the case of NTE, no values at all,
// since the Rules structure is smaller). So, use the values from the last chosen map if applicable, or the values
// from the $dicerange command if available.
Language language = c ? c->language() : Language::ENGLISH;
const Rules* map_rules = this->last_chosen_map ? &this->last_chosen_map->version(language)->map->default_rules : nullptr;
auto& server_rules = this->map_and_rules->rules;
// NTE can specify the DEF dice value range in its Rules struct, so we use
// that unless the map or $dicerange overrides it.
// NTE can specify the DEF dice value range in its Rules struct, so we use that unless the map or $dicerange
// overrides it.
server_rules.def_dice_value_range = (map_rules && (map_rules->def_dice_value_range != 0xFF))
? map_rules->def_dice_value_range
: (this->def_dice_value_range_override != 0xFF)
@@ -2165,8 +2143,7 @@ void Server::handle_CAx13_update_map_during_setup_t(shared_ptr<Client> c, const
? this->def_dice_value_range_2v1_override
: 0;
// If this match is part of a tournament, ignore the rules sent by the
// client and use the tournament rules instead.
// If this match is part of a tournament, ignore the rules sent by the client and use the tournament rules instead.
if (this->options.tournament) {
this->map_and_rules->rules = this->options.tournament->get_rules();
}
@@ -2248,10 +2225,9 @@ void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr<Client>, con
const auto& in_cmd = check_size_t<G_HardResetServerState_Ep3_CAx15>(data);
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "HARD RESET");
// In the original implementation, this command recreates the server object.
// This is possible because the dispatch function is not part of the server
// object in the original implementation; however, in our implementation, it
// is, so we don't support this. The original implementation did this:
// In the original implementation, this command recreates the server object. This is possible because the dispatch
// function is not part of the server object in the original implementation; however, in our implementation, it is,
// so we don't support this. The original implementation did this:
// this->base()->recreate_server(); // Destroys *this, which we can't do
// root_card_server = this->server;
// *this->map_and_rules = *this->initial_map_and_rules;
@@ -2271,8 +2247,8 @@ void Server::handle_CAx1B_update_player_name(shared_ptr<Client>, const string& d
this->name_entries_valid[in_cmd.entry.client_id] = false;
}
// Note: This check is not part of the original code. This replaces a
// disconnecting player with a CPU if the battle is in progress.
// Note: This check is not part of the original code. This replaces a disconnecting player with a CPU if the battle
// is in progress.
auto l = this->lobby.lock();
if (l && !l->clients[in_cmd.entry.client_id]) {
this->name_entries[in_cmd.entry.client_id].is_cpu_player = 1;
@@ -2325,8 +2301,8 @@ void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
auto l = this->lobby.lock();
if (l) {
// Note: Sega's implementation doesn't set EX results values here; they
// did it at game join time instead. We do it here for code simplicity.
// Note: Sega's implementation doesn't set EX results values here; they did it at game join time instead. We do
// it here for code simplicity.
if (!this->options.is_nte() && l->ep3_ex_result_values) {
this->send(*l->ep3_ex_result_values);
}
@@ -2370,8 +2346,7 @@ void Server::handle_CAx28_end_defense_list(shared_ptr<Client>, const string& dat
for (size_t z = 0; z < 4; z++) {
auto ps = this->player_states[z];
if (ps && (this->current_team_turn1 != ps->get_team_id())) {
if (!ps->get_sc_card()->check_card_flag(2) &&
(this->defense_list_ended_for_client[z] == 0)) {
if (!ps->get_sc_card()->check_card_flag(2) && (this->defense_list_ended_for_client[z] == 0)) {
all_defense_lists_ended = false;
break;
}
@@ -2517,8 +2492,7 @@ void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(shared
void Server::handle_CAx3A_time_limit_expired(shared_ptr<Client>, const string& data) {
const auto& in_cmd = check_size_t<G_OverallTimeLimitExpired_Ep3_CAx3A>(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.
// 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<Client> sender_c, const string& data) {
@@ -2579,9 +2553,8 @@ void Server::send_6xB6x41_to_all_clients() const {
}
if (this->battle_record && this->battle_record->writable()) {
// TODO: It's not great that we just pick the first one; ideally we'd put
// all of them in the recording and send the appropriate one to the client
// in the playback lobby
// TODO: It's not great that we just pick the first one; ideally we'd put all of them in the recording and send
// the appropriate one to the client in the playback lobby
for (string& data : map_commands_by_language) {
if (!data.empty()) {
this->battle_record->add_command(BattleRecord::Event::Type::BATTLE_COMMAND, std::move(data));
@@ -2626,8 +2599,7 @@ void Server::handle_CAx49_card_counts(shared_ptr<Client>, const string& data) {
const auto& in_cmd = check_size_t<G_CardCounts_Ep3_CAx49>(data);
this->send_debug_command_received_message(in_cmd.header.sender_client_id, in_cmd.header.subsubcommand, "CARD COUNTS");
// Note: Sega's implmentation completely ignores this command. This
// implementation is not based on the original code.
// Note: Sega's implmentation completely ignores this command. This implementation is not based on the original code.
auto& dest_counts = this->client_card_counts[in_cmd.header.sender_client_id];
dest_counts = in_cmd.card_id_to_count;
decrypt_trivial_gci_data(dest_counts.data(), dest_counts.bytes(), in_cmd.basis);
@@ -2884,10 +2856,9 @@ void Server::execute_bomb_assist_effect() {
for (size_t client_id = 0; client_id < 4; client_id++) {
auto ps = this->player_states[client_id];
// Possible bug: shouldn't we check should_block_assist_effects_for_client
// here too? If the player has a card with the same HP as another one that
// would be destroyed, it looks like the card can be destroyed even if the
// client should be immune to assist effects here.
// Possible bug: shouldn't we check should_block_assist_effects_for_client here too? If the player has a card with
// the same HP as another one that would be destroyed, it looks like the card can be destroyed even if the client
// should be immune to assist effects here.
if (ps) {
for (size_t set_index = 0; set_index < 8; set_index++) {
auto card = ps->get_set_card(set_index);
@@ -2917,10 +2888,7 @@ void Server::replace_targets_due_to_destruction_nte(ActionState* as) {
shared_ptr<Card> found_guard_item;
for (size_t z = 0; z < 8; z++) {
auto set_card = ps->get_set_card(z);
if (set_card &&
(set_card != target_card) &&
!(set_card->card_flags & 2) &&
set_card->is_guard_item()) {
if (set_card && (set_card != target_card) && !(set_card->card_flags & 2) && set_card->is_guard_item()) {
found_guard_item = set_card;
break;
}
@@ -3039,8 +3007,8 @@ void Server::replace_targets_due_to_destruction_or_conditions(ActionState* as) {
}
}
// Note: The original code only writes a single FFFF after the last card ref
// in this array; we instead clear the entire array.
// Note: The original code only writes a single FFFF after the last card ref in this array; we instead clear the
// entire array.
as->target_card_refs.clear(0xFFFF);
for (size_t z = 0; z < phase1_replaced_card_refs.size(); z++) {
as->target_card_refs[z] = this->send_6xB4x06_if_card_ref_invalid(phase1_replaced_card_refs[z], 4);
@@ -3062,8 +3030,7 @@ void Server::replace_targets_due_to_destruction_or_conditions(ActionState* as) {
}
}
// Note: This is different from the original code in the same way as above: we
// clear the entire array first.
// Note: This is different from the original code in the same way as above: we clear the entire array first.
as->target_card_refs.clear(0xFFFF);
for (size_t z = 0; z < phase2_replaced_card_refs.size(); z++) {
as->target_card_refs[z] = this->send_6xB4x06_if_card_ref_invalid(phase2_replaced_card_refs[z], 4);
@@ -3148,8 +3115,7 @@ void Server::unknown_802402F4() {
}
}
vector<shared_ptr<Card>> Server::const_cast_set_cards_v(
const vector<shared_ptr<const Card>>& cards) {
vector<shared_ptr<Card>> Server::const_cast_set_cards_v(const vector<shared_ptr<const Card>>& cards) {
// TODO: This is dumb. Figure out a not-dumb way to do this.
vector<shared_ptr<Card>> ret;
for (auto const_card : cards) {