add Ep3 battle replay test
This commit is contained in:
+111
-15
@@ -3340,6 +3340,7 @@ Action a_replay_ep3_battle_commands(
|
||||
.rand_crypt = make_shared<MT19937Generator>(seed),
|
||||
.tournament = nullptr,
|
||||
.trap_card_ids = {},
|
||||
.output_queue = nullptr,
|
||||
};
|
||||
if (is_trial) {
|
||||
options.behavior_flags |= Episode3::BehaviorFlag::IS_TRIAL_EDITION;
|
||||
@@ -3365,36 +3366,131 @@ Action a_replay_ep3_battle_commands(
|
||||
|
||||
Action a_replay_ep3_battle_record(
|
||||
"replay-ep3-battle-record", nullptr, +[](phosg::Arguments& args) {
|
||||
auto rec = make_shared<Episode3::BattleRecord>(read_input_data(args));
|
||||
auto record_data = read_input_data(args);
|
||||
if (args.get<bool>("compressed")) {
|
||||
record_data = prs_decompress(record_data);
|
||||
}
|
||||
auto rec = make_shared<Episode3::BattleRecord>(record_data);
|
||||
|
||||
bool use_color = isatty(fileno(stdout));
|
||||
|
||||
auto s = make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_ep3_cards();
|
||||
s->load_ep3_maps();
|
||||
|
||||
bool is_trial = (get_cli_version(args, Version::GC_EP3) == Version::GC_EP3_NTE);
|
||||
|
||||
bool is_nte = rec->get_behavior_flags() & Episode3::BehaviorFlag::IS_TRIAL_EDITION;
|
||||
auto output_queue = std::make_shared<std::deque<std::string>>();
|
||||
Episode3::Server::Options options = {
|
||||
.card_index = s->ep3_card_index,
|
||||
.map_index = s->ep3_map_index,
|
||||
.behavior_flags = (Episode3::BehaviorFlag::IGNORE_CARD_COUNTS |
|
||||
Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES |
|
||||
Episode3::BehaviorFlag::DISABLE_MASKING |
|
||||
Episode3::BehaviorFlag::LOG_COMMANDS_IF_LOBBY_MISSING),
|
||||
.behavior_flags = rec->get_behavior_flags() & ~(Episode3::BehaviorFlag::LOG_COMMANDS_IF_LOBBY_MISSING),
|
||||
.opt_rand_stream = make_shared<phosg::StringReader>(rec->get_random_stream()),
|
||||
.rand_crypt = make_shared<DisabledRandomGenerator>(),
|
||||
.tournament = nullptr,
|
||||
.trap_card_ids = {},
|
||||
.output_queue = output_queue,
|
||||
};
|
||||
if (is_trial) {
|
||||
options.behavior_flags |= Episode3::BehaviorFlag::IS_TRIAL_EDITION;
|
||||
}
|
||||
options.behavior_flags |= Episode3::BehaviorFlag::LOG_COMMANDS_IF_LOBBY_MISSING;
|
||||
auto server = make_shared<Episode3::Server>(nullptr, std::move(options));
|
||||
server->init();
|
||||
for (const auto& command : rec->get_all_server_data_commands()) {
|
||||
phosg::log_info_f("Server data command");
|
||||
phosg::print_data(stderr, command, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
server->on_server_data_input(nullptr, command);
|
||||
|
||||
// Ignore commands generated by the server when it's constructed (these
|
||||
// are not included in the battle record)
|
||||
output_queue->clear();
|
||||
|
||||
std::array<bool, 4> players_present = {false, false, false, false};
|
||||
for (const auto& ev : rec->get_all_events()) {
|
||||
switch (ev.type) {
|
||||
case Episode3::BattleRecord::Event::Type::SET_INITIAL_PLAYERS:
|
||||
ev.print(stdout);
|
||||
for (const auto& player : ev.players) {
|
||||
players_present.at(player.lobby_data.client_id) = true;
|
||||
phosg::fwrite_fmt(stderr, "Player {} is present\n", player.lobby_data.client_id.load());
|
||||
}
|
||||
break;
|
||||
case Episode3::BattleRecord::Event::Type::PLAYER_JOIN:
|
||||
case Episode3::BattleRecord::Event::Type::PLAYER_LEAVE:
|
||||
case Episode3::BattleRecord::Event::Type::CHAT_MESSAGE:
|
||||
case Episode3::BattleRecord::Event::Type::GAME_COMMAND:
|
||||
case Episode3::BattleRecord::Event::Type::EP3_GAME_COMMAND:
|
||||
ev.print(stdout);
|
||||
break;
|
||||
case Episode3::BattleRecord::Event::Type::BATTLE_COMMAND:
|
||||
// Ignore the map command (this is handled separately) and 6xB4x4B
|
||||
// (which is only generated when a lobby is present)
|
||||
if (ev.data.empty() || (static_cast<uint8_t>(ev.data[0]) == 0xB6) || (ev.data.at(4) == 0x4B)) {
|
||||
ev.print(stdout);
|
||||
} else {
|
||||
if (use_color) {
|
||||
phosg::print_color_escape(stdout, phosg::TerminalFormat::FG_RED, phosg::TerminalFormat::BOLD, phosg::TerminalFormat::END);
|
||||
}
|
||||
ev.print(stdout);
|
||||
if (use_color) {
|
||||
phosg::print_color_escape(stdout, phosg::TerminalFormat::NORMAL, phosg::TerminalFormat::END);
|
||||
fflush(stdout);
|
||||
}
|
||||
if (output_queue->empty()) {
|
||||
phosg::fwrite_fmt(stderr, "Output queue is empty, but expected battle command:\n");
|
||||
phosg::print_data(stderr, ev.data, 0, nullptr, phosg::PrintDataFlags::OFFSET_16_BITS | phosg::PrintDataFlags::PRINT_ASCII);
|
||||
throw std::runtime_error("Output did not match expectations");
|
||||
}
|
||||
// Hack: don't check the last field in 6xB4x46 since it contains
|
||||
// a timestamp on non-NTE
|
||||
bool matched = false;
|
||||
if ((ev.data.at(4) == 0x46) && !is_nte) {
|
||||
auto received_cmd = check_size_t<G_ServerVersionStrings_Ep3_6xB4x46>(output_queue->front());
|
||||
auto expected_cmd = check_size_t<G_ServerVersionStrings_Ep3_6xB4x46>(ev.data);
|
||||
received_cmd.date_str2.clear(0);
|
||||
expected_cmd.date_str2.clear(0);
|
||||
matched = !memcmp(&received_cmd, &expected_cmd, sizeof(received_cmd));
|
||||
} else {
|
||||
matched = (output_queue->front() == ev.data);
|
||||
}
|
||||
if (!matched) {
|
||||
const void* prev = (ev.data.size() == output_queue->front().size()) ? ev.data.data() : nullptr;
|
||||
phosg::fwrite_fmt(stderr, "Output queue front did not match expected command; expected:\n");
|
||||
phosg::print_data(stderr, ev.data, 0, nullptr, phosg::PrintDataFlags::OFFSET_16_BITS | phosg::PrintDataFlags::PRINT_ASCII);
|
||||
phosg::fwrite_fmt(stderr, "Received:\n");
|
||||
phosg::print_data(stderr, output_queue->front(), 0, prev, phosg::PrintDataFlags::OFFSET_16_BITS | phosg::PrintDataFlags::PRINT_ASCII);
|
||||
throw std::runtime_error("Output did not match expectations");
|
||||
}
|
||||
output_queue->pop_front();
|
||||
}
|
||||
break;
|
||||
case Episode3::BattleRecord::Event::Type::SERVER_DATA_COMMAND:
|
||||
if (use_color) {
|
||||
phosg::print_color_escape(stdout, phosg::TerminalFormat::FG_GREEN, phosg::TerminalFormat::BOLD, phosg::TerminalFormat::END);
|
||||
}
|
||||
ev.print(stdout);
|
||||
if (use_color) {
|
||||
phosg::print_color_escape(stdout, phosg::TerminalFormat::NORMAL, phosg::TerminalFormat::END);
|
||||
fflush(stdout);
|
||||
}
|
||||
if (!output_queue->empty()) {
|
||||
phosg::fwrite_fmt(stderr, "Received extra output after preceding SERVER_DATA event:\n");
|
||||
phosg::print_data(stderr, output_queue->front());
|
||||
throw std::runtime_error("Output did not match expectations");
|
||||
}
|
||||
// Hack: Set the CPU player flag if the player isn't present in the
|
||||
// recording (normally this is done by checking the Lobby, but
|
||||
// there's no Lobby during a replay)
|
||||
if (ev.data.at(4) == 0x1B) {
|
||||
string mutable_data = ev.data;
|
||||
auto& cmd = check_size_t<G_SetPlayerName_Ep3_CAx1B>(mutable_data);
|
||||
cmd.entry.is_cpu_player = !players_present.at(cmd.entry.client_id);
|
||||
phosg::fwrite_fmt(stderr, "Overriding is_cpu_player with {}\n", cmd.entry.is_cpu_player ? "true" : "false");
|
||||
server->on_server_data_input(nullptr, mutable_data);
|
||||
} else {
|
||||
server->on_server_data_input(nullptr, ev.data);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("unknown event type: {}");
|
||||
}
|
||||
}
|
||||
if (!output_queue->empty()) {
|
||||
phosg::fwrite_fmt(stderr, "Received extra output after recording completed:\n");
|
||||
phosg::print_data(stderr, output_queue->front());
|
||||
throw std::runtime_error("Output did not match expectations");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user