Ep3 NTE battles checkpoint 1
This commit is contained in:
+2
-2
@@ -547,14 +547,14 @@ static void server_command_auction(shared_ptr<Client> c, const std::string&) {
|
||||
check_license_flag(c, License::Flag::DEBUG);
|
||||
auto l = c->require_lobby();
|
||||
if (l->is_game() && l->is_ep3()) {
|
||||
G_InitiateCardAuction_GC_Ep3_6xB5x42 cmd;
|
||||
G_InitiateCardAuction_Ep3_6xB5x42 cmd;
|
||||
cmd.header.sender_client_id = c->lobby_client_id;
|
||||
send_command_t(l, 0xC9, 0x00, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
static void proxy_command_auction(shared_ptr<ProxyServer::LinkedSession> ses, const std::string&) {
|
||||
G_InitiateCardAuction_GC_Ep3_6xB5x42 cmd;
|
||||
G_InitiateCardAuction_Ep3_6xB5x42 cmd;
|
||||
cmd.header.sender_client_id = ses->lobby_client_id;
|
||||
ses->client_channel.send(0xC9, 0x00, &cmd, sizeof(cmd));
|
||||
ses->server_channel.send(0xC9, 0x00, &cmd, sizeof(cmd));
|
||||
|
||||
+213
-190
File diff suppressed because it is too large
Load Diff
@@ -6,21 +6,38 @@ using namespace std;
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
// Note: This order matches the order that the cards are defined in the original
|
||||
// code. This is relevant for consistency of results when choosing a random card
|
||||
// (for God Whim).
|
||||
const vector<uint16_t> ALL_ASSIST_CARD_IDS = {
|
||||
0x0018, 0x0019, 0x001A, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA,
|
||||
0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102, 0x0103,
|
||||
0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C,
|
||||
0x010D, 0x010E, 0x010F, 0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129,
|
||||
0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132,
|
||||
0x0133, 0x0134, 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B,
|
||||
0x013C, 0x013D, 0x013E, 0x013F, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144,
|
||||
0x0145, 0x0146, 0x0148, 0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F,
|
||||
0x0240, 0x0241, 0x0242};
|
||||
const vector<uint16_t>& all_assist_card_ids(bool is_trial) {
|
||||
// Note: This order matches the order that the cards are defined in the original
|
||||
// code. This is relevant for consistency of results when choosing a random card
|
||||
// (for God Whim).
|
||||
static const vector<uint16_t> ALL_ASSIST_CARD_IDS_TRIAL = {
|
||||
0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD,
|
||||
0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106,
|
||||
0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x010F,
|
||||
0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C,
|
||||
0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132, 0x0133, 0x0134, 0x0135,
|
||||
0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D, 0x013E,
|
||||
0x013F, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0148,
|
||||
0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F, 0x0240, 0x0241, 0x0242};
|
||||
static const vector<uint16_t> ALL_ASSIST_CARD_IDS_FINAL = {
|
||||
0x0018, 0x0019, 0x001A, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA,
|
||||
0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102, 0x0103,
|
||||
0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C,
|
||||
0x010D, 0x010E, 0x010F, 0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129,
|
||||
0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132,
|
||||
0x0133, 0x0134, 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B,
|
||||
0x013C, 0x013D, 0x013E, 0x013F, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144,
|
||||
0x0145, 0x0146, 0x0148, 0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F,
|
||||
0x0240, 0x0241, 0x0242};
|
||||
return is_trial ? ALL_ASSIST_CARD_IDS_TRIAL : ALL_ASSIST_CARD_IDS_FINAL;
|
||||
}
|
||||
|
||||
AssistEffect assist_effect_number_for_card_id(uint16_t card_id) {
|
||||
AssistEffect assist_effect_number_for_card_id(uint16_t card_id, bool is_trial) {
|
||||
static const unordered_map<uint16_t, AssistEffect> card_id_to_effect_final_only({
|
||||
{0x0018, /* 0x0049 */ AssistEffect::DICE_FEVER_PLUS},
|
||||
{0x0019, /* 0x004A */ AssistEffect::RICH_PLUS},
|
||||
{0x001A, /* 0x004B */ AssistEffect::CHARITY_PLUS},
|
||||
});
|
||||
static const unordered_map<uint16_t, AssistEffect> card_id_to_effect({
|
||||
{0x00F5, /* 0x0001 */ AssistEffect::DICE_HALF},
|
||||
{0x00F6, /* 0x0002 */ AssistEffect::DICE_PLUS_1},
|
||||
@@ -94,15 +111,18 @@ AssistEffect assist_effect_number_for_card_id(uint16_t card_id) {
|
||||
{0x0240, /* 0x0046 */ AssistEffect::BOMB},
|
||||
{0x0241, /* 0x0047 */ AssistEffect::SKIP_TURN},
|
||||
{0x0242, /* 0x0048 */ AssistEffect::BATTLE_ROYALE},
|
||||
{0x0018, /* 0x0049 */ AssistEffect::DICE_FEVER_PLUS},
|
||||
{0x0019, /* 0x004A */ AssistEffect::RICH_PLUS},
|
||||
{0x001A, /* 0x004B */ AssistEffect::CHARITY_PLUS},
|
||||
});
|
||||
try {
|
||||
return card_id_to_effect.at(card_id);
|
||||
} catch (const out_of_range&) {
|
||||
return AssistEffect::NONE;
|
||||
}
|
||||
if (!is_trial) {
|
||||
try {
|
||||
return card_id_to_effect_final_only.at(card_id);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
return AssistEffect::NONE;
|
||||
}
|
||||
|
||||
AssistServer::AssistServer(shared_ptr<Server> server)
|
||||
@@ -224,6 +244,7 @@ AssistEffect AssistServer::get_active_assist_by_index(size_t index) const {
|
||||
}
|
||||
|
||||
void AssistServer::populate_effects() {
|
||||
bool is_trial = this->server()->options.is_trial();
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
this->assist_card_defs[z] = nullptr;
|
||||
this->assist_effects[z] = AssistEffect::NONE;
|
||||
@@ -232,7 +253,7 @@ void AssistServer::populate_effects() {
|
||||
uint16_t card_id = hes->assist_card_id == 0xFFFF
|
||||
? this->card_id_for_card_ref(hes->assist_card_ref)
|
||||
: hes->assist_card_id.load();
|
||||
this->assist_effects[z] = assist_effect_number_for_card_id(card_id);
|
||||
this->assist_effects[z] = assist_effect_number_for_card_id(card_id, is_trial);
|
||||
if (this->assist_effects[z] != AssistEffect::NONE) {
|
||||
this->assist_card_defs[z] = this->definition_for_card_id(card_id);
|
||||
}
|
||||
|
||||
@@ -13,9 +13,8 @@ namespace Episode3 {
|
||||
|
||||
class Server;
|
||||
|
||||
extern const std::vector<uint16_t> ALL_ASSIST_CARD_IDS;
|
||||
|
||||
AssistEffect assist_effect_number_for_card_id(uint16_t card_id);
|
||||
const std::vector<uint16_t>& all_assist_card_ids(bool is_trial);
|
||||
AssistEffect assist_effect_number_for_card_id(uint16_t card_id, bool is_trial);
|
||||
|
||||
class AssistServer {
|
||||
public:
|
||||
|
||||
@@ -191,7 +191,7 @@ bool BattleRecord::is_map_definition_event(const Event& ev) {
|
||||
if (ev.type == Event::Type::BATTLE_COMMAND) {
|
||||
auto& header = check_size_t<G_CardBattleCommandHeader>(ev.data, 0xFFFF);
|
||||
if (header.subcommand == 0xB6) {
|
||||
auto& header = check_size_t<G_MapSubsubcommand_GC_Ep3_6xB6>(ev.data, 0xFFFF);
|
||||
auto& header = check_size_t<G_MapSubsubcommand_Ep3_6xB6>(ev.data, 0xFFFF);
|
||||
if (header.subsubcommand == 0x41) {
|
||||
return true;
|
||||
}
|
||||
|
||||
+78
-44
@@ -31,9 +31,12 @@ Card::Card(
|
||||
current_defense_power(0) {}
|
||||
|
||||
void Card::init() {
|
||||
auto s = this->server();
|
||||
auto ps = this->player_state();
|
||||
|
||||
this->clear_action_chain_and_metadata_and_most_flags();
|
||||
this->team_id = this->player_state()->get_team_id();
|
||||
this->def_entry = this->server()->definition_for_card_id(this->card_id);
|
||||
this->team_id = ps->get_team_id();
|
||||
this->def_entry = s->definition_for_card_id(this->card_id);
|
||||
if (!this->def_entry) {
|
||||
// The original implementation replaces the card ID and definition with 0009
|
||||
// (Saber) if the SC is Hunters-side, and 0056 (Booma) if the SC is
|
||||
@@ -42,27 +45,39 @@ void Card::init() {
|
||||
// prevent it instead.
|
||||
throw runtime_error("card definition is missing");
|
||||
}
|
||||
this->sc_card_ref = this->player_state()->get_sc_card_ref();
|
||||
this->sc_def_entry = this->server()->definition_for_card_id(
|
||||
this->player_state()->get_sc_card_id());
|
||||
this->sc_card_type = this->player_state()->get_sc_card_type();
|
||||
this->max_hp = this->def_entry->def.hp.stat;
|
||||
this->current_hp = this->def_entry->def.hp.stat;
|
||||
this->sc_card_ref = ps->get_sc_card_ref();
|
||||
this->sc_def_entry = s->definition_for_card_id(ps->get_sc_card_id());
|
||||
this->sc_card_type = ps->get_sc_card_type();
|
||||
if (this->sc_card_ref == this->card_ref) {
|
||||
int16_t rules_char_hp = this->server()->map_and_rules->rules.char_hp;
|
||||
int16_t base_char_hp = (rules_char_hp == 0) ? 15 : rules_char_hp;
|
||||
int16_t hp = clamp<int16_t>(base_char_hp + this->def_entry->def.hp.stat, 1, 99);
|
||||
this->max_hp = hp;
|
||||
this->current_hp = hp;
|
||||
if (s->options.is_trial()) {
|
||||
if (s->map_and_rules->rules.char_hp) {
|
||||
this->max_hp = s->map_and_rules->rules.char_hp;
|
||||
this->current_hp = s->map_and_rules->rules.char_hp;
|
||||
} else {
|
||||
this->max_hp = this->def_entry->def.hp.stat;
|
||||
this->current_hp = this->def_entry->def.hp.stat;
|
||||
}
|
||||
} else {
|
||||
int16_t rules_char_hp = s->map_and_rules->rules.char_hp;
|
||||
int16_t base_char_hp = (rules_char_hp == 0) ? 15 : rules_char_hp;
|
||||
int16_t hp = clamp<int16_t>(base_char_hp + this->def_entry->def.hp.stat, 1, 99);
|
||||
this->max_hp = hp;
|
||||
this->current_hp = hp;
|
||||
}
|
||||
} else {
|
||||
this->max_hp = this->def_entry->def.hp.stat;
|
||||
this->current_hp = this->def_entry->def.hp.stat;
|
||||
}
|
||||
this->ap = this->def_entry->def.ap.stat;
|
||||
this->tp = this->def_entry->def.tp.stat;
|
||||
this->num_ally_fcs_destroyed_at_set_time = this->server()->team_num_ally_fcs_destroyed[this->team_id];
|
||||
this->num_cards_destroyed_by_team_at_set_time = this->server()->team_num_cards_destroyed[this->team_id];
|
||||
this->num_ally_fcs_destroyed_at_set_time = s->team_num_ally_fcs_destroyed[this->team_id];
|
||||
this->num_cards_destroyed_by_team_at_set_time = s->team_num_cards_destroyed[this->team_id];
|
||||
this->action_chain.chain.card_ap = this->ap;
|
||||
this->action_chain.chain.card_tp = this->tp;
|
||||
this->loc.direction = this->player_state()->start_facing_direction;
|
||||
if (this->sc_card_ref != this->card_ref) {
|
||||
this->loc.direction = ps->start_facing_direction;
|
||||
// Ep3 NTE always sends 6xB4x0A at construction time; final only does for
|
||||
// non-SC cards
|
||||
if (s->options.is_trial() || (this->sc_card_ref != this->card_ref)) {
|
||||
this->send_6xB4x4E_4C_4D_if_needed();
|
||||
}
|
||||
}
|
||||
@@ -245,7 +260,7 @@ bool Card::check_card_flag(uint32_t flags) const {
|
||||
void Card::commit_attack(
|
||||
int16_t damage,
|
||||
shared_ptr<Card> attacker_card,
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06* cmd,
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06* cmd,
|
||||
size_t strike_number,
|
||||
int16_t* out_effective_damage) {
|
||||
auto log = this->server()->log_stack(string_printf("commit_attack(@%04hX #%04hX, @%04hX #%04hX => %hd (str%zu)): ", this->get_card_ref(), this->get_card_id(), attacker_card->get_card_ref(), attacker_card->get_card_id(), damage, strike_number));
|
||||
@@ -303,7 +318,7 @@ void Card::commit_attack(
|
||||
log.debug("card destroyed");
|
||||
}
|
||||
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd_to_send;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd_to_send;
|
||||
if (cmd) {
|
||||
cmd_to_send = *cmd;
|
||||
}
|
||||
@@ -376,7 +391,7 @@ void Card::destroy_set_card(shared_ptr<Card> attacker_card) {
|
||||
sc_card->set_current_hp(hp - 1);
|
||||
sc_card->player_state()->stats.sc_damage_taken++;
|
||||
if (attacker_card && (attacker_card->team_id != this->team_id)) {
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x41;
|
||||
cmd.effect.attacker_card_ref = attacker_card->card_ref;
|
||||
cmd.effect.target_card_ref = sc_card->card_ref;
|
||||
@@ -465,7 +480,7 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
||||
log.debug("ap != 0 or flag 0x20 set; continuing...");
|
||||
}
|
||||
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x01;
|
||||
cmd.effect.attacker_card_ref = attacker_card->card_ref;
|
||||
cmd.effect.target_card_ref = this->card_ref;
|
||||
@@ -618,7 +633,7 @@ int32_t Card::move_to_location(const Location& loc) {
|
||||
for (size_t warp_end = 0; warp_end < 2; warp_end++) {
|
||||
if ((this->server()->warp_positions[warp_type][warp_end][0] == this->loc.x) &&
|
||||
(this->server()->warp_positions[warp_type][warp_end][1] == this->loc.y)) {
|
||||
G_Unknown_GC_Ep3_6xB4x2C cmd;
|
||||
G_Unknown_Ep3_6xB4x2C cmd;
|
||||
cmd.loc.x = this->loc.x;
|
||||
cmd.loc.y = this->loc.y;
|
||||
this->loc.x = this->server()->warp_positions[warp_type][warp_end ^ 1][0];
|
||||
@@ -650,12 +665,14 @@ void Card::propagate_shared_hp_if_needed() {
|
||||
}
|
||||
|
||||
void Card::send_6xB4x4E_4C_4D_if_needed(bool always_send) {
|
||||
auto ps = this->player_state();
|
||||
|
||||
ssize_t index = -1;
|
||||
if (this->card_ref == this->player_state()->get_sc_card_ref()) {
|
||||
if (this->card_ref == ps->get_sc_card_ref()) {
|
||||
index = 0;
|
||||
} else {
|
||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||
if (this->card_ref == this->player_state()->get_set_ref(set_index)) {
|
||||
if (this->card_ref == ps->get_set_ref(set_index)) {
|
||||
index = set_index + 1;
|
||||
break;
|
||||
}
|
||||
@@ -668,28 +685,44 @@ void Card::send_6xB4x4E_4C_4D_if_needed(bool always_send) {
|
||||
|
||||
this->action_chain.chain.card_ap = this->ap;
|
||||
this->action_chain.chain.card_tp = this->tp;
|
||||
this->send_6xB4x4E_if_needed(always_send);
|
||||
|
||||
auto& chain = this->player_state()->set_card_action_chains->at(index);
|
||||
if (always_send || (chain != this->action_chain)) {
|
||||
auto& chain = ps->set_card_action_chains->at(index);
|
||||
auto& metadata = ps->set_card_action_metadatas->at(index);
|
||||
|
||||
auto s = this->server();
|
||||
if (s->options.is_trial()) {
|
||||
chain = this->action_chain;
|
||||
if (!this->server()->get_should_copy_prev_states_to_current_states()) {
|
||||
G_UpdateActionChain_GC_Ep3_6xB4x4C cmd;
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.index = index;
|
||||
cmd.chain = this->action_chain.chain;
|
||||
this->server()->send(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
auto& metadata = this->player_state()->set_card_action_metadatas->at(index);
|
||||
if (always_send || (metadata != this->action_metadata)) {
|
||||
metadata = this->action_metadata;
|
||||
G_UpdateActionMetadata_GC_Ep3_6xB4x4D cmd;
|
||||
|
||||
G_UpdateActionChainAndMetadata_Ep3NTE_6xB4x0A cmd;
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.index = index;
|
||||
cmd.chain = this->action_chain;
|
||||
cmd.metadata = this->action_metadata;
|
||||
this->server()->send(cmd);
|
||||
s->send(cmd);
|
||||
|
||||
} else {
|
||||
this->send_6xB4x4E_if_needed(always_send);
|
||||
|
||||
if (always_send || (chain != this->action_chain)) {
|
||||
chain = this->action_chain;
|
||||
if (!s->get_should_copy_prev_states_to_current_states()) {
|
||||
G_UpdateActionChain_Ep3_6xB4x4C cmd;
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.index = index;
|
||||
cmd.chain = this->action_chain.chain;
|
||||
s->send(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
if (always_send || (metadata != this->action_metadata)) {
|
||||
metadata = this->action_metadata;
|
||||
G_UpdateActionMetadata_Ep3_6xB4x4D cmd;
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.index = index;
|
||||
cmd.metadata = this->action_metadata;
|
||||
s->send(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,7 +745,7 @@ void Card::send_6xB4x4E_if_needed(bool always_send) {
|
||||
if ((prev_conds != curr_conds) || (always_send != 0)) {
|
||||
prev_conds = curr_conds;
|
||||
if (!this->server()->get_should_copy_prev_states_to_current_states()) {
|
||||
G_UpdateCardConditions_GC_Ep3_6xB4x4E cmd;
|
||||
G_UpdateCardConditions_Ep3_6xB4x4E cmd;
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.index = chain_index;
|
||||
cmd.conditions = this->action_chain.conditions;
|
||||
@@ -745,12 +778,13 @@ void Card::set_current_hp(
|
||||
}
|
||||
|
||||
void Card::update_stats_on_destruction() {
|
||||
auto s = this->server();
|
||||
this->player_state()->num_destroyed_fcs++;
|
||||
this->server()->team_num_ally_fcs_destroyed[this->team_id]++;
|
||||
this->server()->team_num_cards_destroyed[this->team_id]++;
|
||||
s->team_num_ally_fcs_destroyed[this->team_id]++;
|
||||
s->team_num_cards_destroyed[this->team_id]++;
|
||||
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
auto other_ps = this->server()->player_states[client_id];
|
||||
auto other_ps = s->player_states[client_id];
|
||||
if (other_ps && (other_ps->get_team_id() == this->team_id)) {
|
||||
auto card = other_ps->get_sc_card();
|
||||
if (card) {
|
||||
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
void commit_attack(
|
||||
int16_t damage,
|
||||
std::shared_ptr<Card> attacker_card,
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06* cmd,
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06* cmd,
|
||||
size_t strike_number,
|
||||
int16_t* out_effective_damage);
|
||||
int16_t compute_defense_power_for_attacker_card(
|
||||
|
||||
+92
-59
@@ -63,7 +63,7 @@ void CardSpecial::AttackEnvStats::clear() {
|
||||
this->num_item_or_creature_cards_in_hand = 0;
|
||||
this->num_destroyed_ally_fcs = 0;
|
||||
this->target_team_num_set_cards = 0;
|
||||
this->condition_giver_team_num_set_cards = 0;
|
||||
this->non_target_team_num_set_cards = 0;
|
||||
this->num_native_creatures = 0;
|
||||
this->num_a_beast_creatures = 0;
|
||||
this->num_machine_creatures = 0;
|
||||
@@ -104,7 +104,7 @@ void CardSpecial::AttackEnvStats::print(FILE* stream) const {
|
||||
fprintf(stream, "(dm) effective_ap_if_not_tech = %" PRIu32 "\n", this->effective_ap_if_not_tech);
|
||||
fprintf(stream, "(dn) unknown_a1 = %" PRIu32 "\n", this->unknown_a1);
|
||||
fprintf(stream, "(edm) target_attack_bonus = %" PRIu32 "\n", this->target_attack_bonus);
|
||||
fprintf(stream, "(ef) condition_giver_team_num_set_cards = %" PRIu32 "\n", this->condition_giver_team_num_set_cards);
|
||||
fprintf(stream, "(ef) non_target_team_num_set_cards = %" PRIu32 "\n", this->non_target_team_num_set_cards);
|
||||
fprintf(stream, "(ehp) target_current_hp = %" PRIu32 "\n", this->target_current_hp);
|
||||
fprintf(stream, "(f) num_set_cards = %" PRIu32 "\n", this->num_set_cards);
|
||||
fprintf(stream, "(fdm) total_last_attack_damage = %" PRIu32 "\n", this->total_last_attack_damage);
|
||||
@@ -130,9 +130,7 @@ void CardSpecial::AttackEnvStats::print(FILE* stream) const {
|
||||
fprintf(stream, "(wd) num_cane_type_items = %" PRIu32 "\n", this->num_cane_type_items);
|
||||
}
|
||||
|
||||
CardSpecial::CardSpecial(shared_ptr<Server> server)
|
||||
: w_server(server),
|
||||
unknown_a2(0) {}
|
||||
CardSpecial::CardSpecial(shared_ptr<Server> server) : w_server(server) {}
|
||||
|
||||
shared_ptr<Server> CardSpecial::server() {
|
||||
auto s = this->w_server.lock();
|
||||
@@ -229,7 +227,7 @@ void CardSpecial::adjust_dice_boost_if_team_has_condition_52(
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < 9; z++) {
|
||||
if (!this->card_ref_has_ability_trap(card->action_chain.conditions[z]) &&
|
||||
if ((this->server()->options.is_trial() || !this->card_ref_has_ability_trap(card->action_chain.conditions[z])) &&
|
||||
(card->action_chain.conditions[z].type == ConditionType::UNKNOWN_52)) {
|
||||
*inout_dice_boost = *inout_dice_boost * card->action_chain.conditions[z].value8;
|
||||
}
|
||||
@@ -313,7 +311,7 @@ bool CardSpecial::apply_defense_condition(
|
||||
if ((when == 2) && (defender_cond->type == ConditionType::GUOM) && (flags & 4)) {
|
||||
CardShortStatus stat = defender_card->get_short_status();
|
||||
if (stat.card_flags & 4) {
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
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();
|
||||
@@ -374,7 +372,7 @@ bool CardSpecial::apply_defense_condition(
|
||||
|
||||
if (dice_roll.value_used_in_expr && !(original_cond_flags & 1) && !unknown_p8) {
|
||||
defender_cond->flags |= 1;
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
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;
|
||||
@@ -385,7 +383,7 @@ bool CardSpecial::apply_defense_condition(
|
||||
|
||||
} else {
|
||||
if (defender_cond->type != ConditionType::NONE) {
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x04;
|
||||
cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x0D);
|
||||
cmd.effect.target_card_ref = defender_card->get_card_ref();
|
||||
@@ -438,7 +436,8 @@ bool CardSpecial::apply_stat_deltas_to_all_cards_from_all_conditions_with_card_r
|
||||
}
|
||||
|
||||
bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condition& cond, shared_ptr<Card> card) {
|
||||
auto log = this->server()->log_stack(string_printf("apply_stat_deltas_to_card_from_condition_and_clear_cond(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id()));
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("apply_stat_deltas_to_card_from_condition_and_clear_cond(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id()));
|
||||
string cond_str = cond.str();
|
||||
log.debug("cond: %s", cond_str.c_str());
|
||||
|
||||
@@ -454,8 +453,10 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
int16_t ap = clamp<int16_t>(card->ap, -99, 99);
|
||||
int16_t tp = clamp<int16_t>(card->tp, -99, 99);
|
||||
log.debug("A_T_SWAP_0C: swapping AP (%hd) and TP (%hd)", ap, tp);
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, tp - ap, 0, 0);
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, ap - tp, 0, 0);
|
||||
if (!s->options.is_trial()) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, tp - ap, 0, 0);
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, ap - tp, 0, 0);
|
||||
}
|
||||
card->ap = tp;
|
||||
card->tp = ap;
|
||||
} else {
|
||||
@@ -468,8 +469,10 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
int16_t hp = clamp<int16_t>(card->get_current_hp(), -99, 99);
|
||||
if (hp != ap) {
|
||||
log.debug("A_H_SWAP: swapping AP (%hd) and HP (%hd)", ap, hp);
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, hp - ap, 0, 0);
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x20, ap - hp, 0, 0);
|
||||
if (!s->options.is_trial()) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, hp - ap, 0, 0);
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x20, ap - hp, 0, 0);
|
||||
}
|
||||
card->set_current_hp(ap, true, true);
|
||||
card->ap = hp;
|
||||
this->destroy_card_if_hp_zero(card, cond_card_ref);
|
||||
@@ -492,8 +495,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
// late-development intentional change.
|
||||
Condition* other_cond = nullptr; // return_null???(card, ConditionType::AP_OVERRIDE);
|
||||
if (!other_cond) {
|
||||
this->send_6xB4x06_for_stat_delta(
|
||||
card, cond_card_ref, 0xA0, -cond_value, 0, 0);
|
||||
if (!s->options.is_trial()) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, -cond_value, 0, 0);
|
||||
}
|
||||
card->ap = max<int16_t>(card->ap - cond_value, 0);
|
||||
log.debug("AP_OVERRIDE: subtracting %hd from AP => %hd", cond_value, card->ap);
|
||||
} else {
|
||||
@@ -509,7 +513,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
// original code as well.
|
||||
Condition* other_cond = nullptr; // return_null???(card, ConditionType::TP_OVERRIDE)
|
||||
if (!other_cond) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, -cond_value, 0, 0);
|
||||
if (!s->options.is_trial()) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, -cond_value, 0, 0);
|
||||
}
|
||||
card->tp = max<int16_t>(card->tp - cond_value, 0);
|
||||
log.debug("TP_OVERRIDE: subtracting %hd from TP => %hd", cond_value, card->tp);
|
||||
} else {
|
||||
@@ -521,7 +527,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
break;
|
||||
case ConditionType::MISC_AP_BONUSES:
|
||||
if (cond_flags & 2) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, -cond_value, 0, 0);
|
||||
if (!s->options.is_trial()) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, -cond_value, 0, 0);
|
||||
}
|
||||
card->ap = max<int16_t>(card->ap - cond_value, 0);
|
||||
log.debug("MISC_AP_BONUSES: subtracting %hd from AP => %hd", cond_value, card->ap);
|
||||
} else {
|
||||
@@ -530,7 +538,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
break;
|
||||
case ConditionType::MISC_TP_BONUSES:
|
||||
if (cond_flags & 2) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, -cond_value, 0, 0);
|
||||
if (!s->options.is_trial()) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, -cond_value, 0, 0);
|
||||
}
|
||||
card->tp = max<int16_t>(card->tp - cond_value, 0);
|
||||
log.debug("MISC_TP_BONUSES: subtracting %hd from TP => %hd", cond_value, card->tp);
|
||||
} else {
|
||||
@@ -538,6 +548,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
}
|
||||
break;
|
||||
case ConditionType::AP_SILENCE:
|
||||
if (!s->options.is_trial()) {
|
||||
goto trial_unimplemented;
|
||||
}
|
||||
if (cond_flags & 2) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, cond_value, 0, 0);
|
||||
card->ap = max<int16_t>(card->ap + cond_value, 0);
|
||||
@@ -547,6 +560,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
}
|
||||
break;
|
||||
case ConditionType::TP_SILENCE:
|
||||
if (!s->options.is_trial()) {
|
||||
goto trial_unimplemented;
|
||||
}
|
||||
if (cond_flags & 2) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, cond_value, 0, 0);
|
||||
card->tp = max<int16_t>(card->tp + cond_value, 0);
|
||||
@@ -555,6 +571,7 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
log.debug("TP_SILENCE: required flag is missing");
|
||||
}
|
||||
break;
|
||||
trial_unimplemented:
|
||||
default:
|
||||
log.debug("%s: no adjustments for condition type", name_for_condition_type(cond_type));
|
||||
break;
|
||||
@@ -666,15 +683,15 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats(
|
||||
const DiceRoll& dice_roll,
|
||||
uint16_t target_card_ref,
|
||||
uint16_t condition_giver_card_ref) {
|
||||
auto log = this->server()->log_stack("compute_attack_env_stats: ");
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack("compute_attack_env_stats: ");
|
||||
|
||||
string pa_str = pa.str();
|
||||
log.debug("pa=%s, card=@%04hX #%04hX, dice_roll=%hhu, target=@%04hX, condition_giver=@%04hX", pa_str.c_str(), card->get_card_ref(), card->get_card_id(), dice_roll.value, target_card_ref, condition_giver_card_ref);
|
||||
|
||||
this->action_state = pa;
|
||||
auto attacker_card = this->server()->card_for_set_card_ref(pa.attacker_card_ref);
|
||||
auto attacker_card = s->card_for_set_card_ref(pa.attacker_card_ref);
|
||||
if (!attacker_card && (pa.original_attacker_card_ref != 0xFFFF)) {
|
||||
attacker_card = this->server()->card_for_set_card_ref(pa.original_attacker_card_ref);
|
||||
attacker_card = s->card_for_set_card_ref(pa.original_attacker_card_ref);
|
||||
log.debug("attacker=@%04hX #%04hX (from original)", attacker_card->get_card_ref(), attacker_card->get_card_id());
|
||||
} else if (attacker_card) {
|
||||
log.debug("attacker=@%04hX #%04hX (from set)", attacker_card->get_card_ref(), attacker_card->get_card_id());
|
||||
@@ -687,15 +704,15 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats(
|
||||
auto ps = card->player_state();
|
||||
log.debug("base ps = %hhu", ps->client_id);
|
||||
ast.num_set_cards = ps->count_set_cards();
|
||||
auto condition_giver_card = this->server()->card_for_set_card_ref(condition_giver_card_ref);
|
||||
auto target_card = this->server()->card_for_set_card_ref(target_card_ref);
|
||||
auto condition_giver_card = s->card_for_set_card_ref(condition_giver_card_ref);
|
||||
auto target_card = s->card_for_set_card_ref(target_card_ref);
|
||||
if (!target_card) {
|
||||
target_card = condition_giver_card;
|
||||
}
|
||||
|
||||
size_t ps_num_set_cards = 0;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
auto other_ps = this->server()->get_player_state(z);
|
||||
auto other_ps = s->get_player_state(z);
|
||||
if (other_ps) {
|
||||
ps_num_set_cards += other_ps->count_set_cards();
|
||||
}
|
||||
@@ -707,19 +724,19 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats(
|
||||
: 0xFF;
|
||||
|
||||
size_t target_team_num_set_cards = 0;
|
||||
size_t condition_giver_team_num_set_cards = 0;
|
||||
size_t non_target_team_num_set_cards = 0;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
auto other_ps = this->server()->get_player_state(z);
|
||||
auto other_ps = s->get_player_state(z);
|
||||
if (other_ps) {
|
||||
if (target_card_team_id == other_ps->get_team_id()) {
|
||||
target_team_num_set_cards += other_ps->count_set_cards();
|
||||
} else {
|
||||
condition_giver_team_num_set_cards += other_ps->count_set_cards();
|
||||
non_target_team_num_set_cards += other_ps->count_set_cards();
|
||||
}
|
||||
}
|
||||
}
|
||||
ast.target_team_num_set_cards = target_team_num_set_cards;
|
||||
ast.condition_giver_team_num_set_cards = condition_giver_team_num_set_cards;
|
||||
ast.non_target_team_num_set_cards = non_target_team_num_set_cards;
|
||||
|
||||
ast.num_native_creatures = this->get_all_set_cards_by_team_and_class(CardClass::NATIVE_CREATURE, 0xFF, true).size();
|
||||
ast.num_a_beast_creatures = this->get_all_set_cards_by_team_and_class(CardClass::A_BEAST_CREATURE, 0xFF, true).size();
|
||||
@@ -736,14 +753,19 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats(
|
||||
if (card_ref == 0xFFFF) {
|
||||
continue;
|
||||
}
|
||||
auto ce = this->server()->definition_for_card_id(card_ref);
|
||||
auto ce = s->definition_for_card_id(card_ref);
|
||||
if (ce && ((ce->def.type == CardType::ITEM) || (ce->def.type == CardType::CREATURE))) {
|
||||
num_item_or_creature_cards_in_hand++;
|
||||
}
|
||||
}
|
||||
ast.num_item_or_creature_cards_in_hand = num_item_or_creature_cards_in_hand;
|
||||
|
||||
ast.num_destroyed_ally_fcs = card->num_destroyed_ally_fcs;
|
||||
if (s->options.is_trial()) {
|
||||
ast.num_destroyed_ally_fcs = s->team_num_cards_destroyed[ps->get_team_id()] - card->num_ally_fcs_destroyed_at_set_time;
|
||||
} else {
|
||||
ast.num_destroyed_ally_fcs = card->num_destroyed_ally_fcs;
|
||||
}
|
||||
|
||||
// Note: The original implementation has dice_roll as optional, but since it's
|
||||
// provided at all callsites, we require it (and hence don't check for nullptr
|
||||
// here)
|
||||
@@ -753,7 +775,7 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats(
|
||||
ast.effective_tp = card->action_chain.chain.effective_tp;
|
||||
ast.current_hp = card->get_current_hp();
|
||||
ast.max_hp = card->get_max_hp();
|
||||
ast.team_dice_bonus = card ? this->server()->team_dice_bonus[card->get_team_id()] : 0;
|
||||
ast.team_dice_bonus = card ? s->team_dice_bonus[card->get_team_id()] : 0;
|
||||
|
||||
ast.effective_ap_if_not_tech = (!attacker_card || (attacker_card->action_chain.chain.attack_medium == AttackMedium::TECH))
|
||||
? 0
|
||||
@@ -802,8 +824,7 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats(
|
||||
ast.action_cards_ap = 0;
|
||||
ast.action_cards_tp = 0;
|
||||
for (; (z < 9) && (pa.action_card_refs[z] != 0xFFFF); z++) {
|
||||
this->unknown_a2 = pa.action_card_refs[z];
|
||||
auto ce = this->server()->definition_for_card_ref(pa.action_card_refs[z]);
|
||||
auto ce = s->definition_for_card_ref(pa.action_card_refs[z]);
|
||||
if (ce) {
|
||||
if (ce->def.ap.type != CardDefinition::Stat::Type::MINUS_STAT) {
|
||||
ast.action_cards_ap += ce->def.ap.stat;
|
||||
@@ -1271,6 +1292,10 @@ size_t CardSpecial::count_cards_with_card_id_except_card_ref(
|
||||
|
||||
vector<shared_ptr<const Card>> 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()) {
|
||||
team_id = 0xFF;
|
||||
exclude_destroyed_cards = false;
|
||||
}
|
||||
vector<shared_ptr<const Card>> ret;
|
||||
auto check_card = [&](shared_ptr<const Card> card) -> void {
|
||||
if (card &&
|
||||
@@ -1941,7 +1966,7 @@ bool CardSpecial::execute_effect(
|
||||
(cond.type == ConditionType::FREEZE) ||
|
||||
(cond.type == ConditionType::UNKNOWN_1E) ||
|
||||
(cond.type == ConditionType::DROP)) {
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x04;
|
||||
cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x0C);
|
||||
cmd.effect.target_card_ref = card->get_card_ref();
|
||||
@@ -3254,7 +3279,7 @@ void CardSpecial::send_6xB4x06_for_exp_change(
|
||||
uint16_t attacker_card_ref,
|
||||
uint8_t dice_roll_value,
|
||||
bool unknown_p5) const {
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x02;
|
||||
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();
|
||||
@@ -3274,27 +3299,29 @@ void CardSpecial::send_6xB4x06_for_exp_change(
|
||||
|
||||
void CardSpecial::send_6xB4x06_for_card_destroyed(
|
||||
shared_ptr<const Card> destroyed_card, uint16_t attacker_card_ref) const {
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
auto s = this->server();
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x04;
|
||||
cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(
|
||||
attacker_card_ref, 0x13);
|
||||
cmd.effect.target_card_ref = destroyed_card->get_card_ref();
|
||||
cmd.effect.value = 0;
|
||||
cmd.effect.operation = 0x7E;
|
||||
cmd.effect.operation = s->options.is_trial() ? 0x78 : 0x7E;
|
||||
this->server()->send(cmd);
|
||||
}
|
||||
|
||||
uint16_t CardSpecial::send_6xB4x06_if_card_ref_invalid(
|
||||
uint16_t card_ref, int16_t value) const {
|
||||
if (!this->server()->card_ref_is_empty_or_has_valid_card_id(card_ref)) {
|
||||
auto s = this->server();
|
||||
if (!s->options.is_trial() && !s->card_ref_is_empty_or_has_valid_card_id(card_ref)) {
|
||||
if (value != 0) {
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x04;
|
||||
cmd.effect.attacker_card_ref = 0xFFFF;
|
||||
cmd.effect.target_card_ref = 0xFFFF;
|
||||
cmd.effect.value = value;
|
||||
cmd.effect.operation = 0x7E;
|
||||
this->server()->send(cmd);
|
||||
s->send(cmd);
|
||||
}
|
||||
card_ref = 0xFFFF;
|
||||
}
|
||||
@@ -3323,7 +3350,7 @@ void CardSpecial::send_6xB4x06_for_stat_delta(
|
||||
}
|
||||
}
|
||||
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
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();
|
||||
@@ -3611,7 +3638,7 @@ void CardSpecial::check_for_defense_interference(
|
||||
|
||||
target_ps->unknown_a17++;
|
||||
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x04;
|
||||
cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card->get_card_ref(), 0x12);
|
||||
cmd.effect.target_card_ref = target_card->get_card_ref();
|
||||
@@ -3631,26 +3658,32 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
uint16_t sc_card_ref,
|
||||
bool apply_defense_condition_to_all_cards,
|
||||
uint16_t apply_defense_condition_to_card_ref) {
|
||||
auto log = this->server()->log_stack(string_printf("evaluate_and_apply_effects(%02hhX, @%04hX, @%04hX): ", when, set_card_ref, sc_card_ref));
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(string_printf("evaluate_and_apply_effects(%02hhX, @%04hX, @%04hX): ", when, set_card_ref, sc_card_ref));
|
||||
{
|
||||
string as_str = as.str();
|
||||
log.debug("when=%02hhX, set_card_ref=@%04hX, as=%s, sc_card_ref=@%04hX, apply_defense_condition_to_all_cards=%s, apply_defense_condition_to_card_ref=@%04hX",
|
||||
when, set_card_ref, as_str.c_str(), sc_card_ref, apply_defense_condition_to_all_cards ? "true" : "false", apply_defense_condition_to_card_ref);
|
||||
}
|
||||
|
||||
set_card_ref = this->send_6xB4x06_if_card_ref_invalid(set_card_ref, 1);
|
||||
if (!s->options.is_trial()) {
|
||||
set_card_ref = this->send_6xB4x06_if_card_ref_invalid(set_card_ref, 1);
|
||||
}
|
||||
|
||||
auto ce = this->server()->definition_for_card_ref(set_card_ref);
|
||||
if (!ce) {
|
||||
log.debug("ce missing");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: NTE has an extra check here. Implement it.
|
||||
|
||||
uint16_t as_attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 2);
|
||||
if (as_attacker_card_ref == 0xFFFF) {
|
||||
as_attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(as.original_attacker_card_ref, 3);
|
||||
}
|
||||
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 dice_cmd;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 dice_cmd;
|
||||
dice_cmd.effect.target_card_ref = set_card_ref;
|
||||
bool as_action_card_refs_contains_set_card_ref = false;
|
||||
bool as_action_card_refs_contains_duplicate_of_set_card = false;
|
||||
@@ -3659,7 +3692,7 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
as_action_card_refs_contains_set_card_ref = true;
|
||||
break;
|
||||
}
|
||||
auto action_ce = this->server()->definition_for_card_ref(as.action_card_refs[z]);
|
||||
auto action_ce = s->definition_for_card_ref(as.action_card_refs[z]);
|
||||
if (action_ce && (action_ce->def.card_id == ce->def.card_id)) {
|
||||
as_action_card_refs_contains_duplicate_of_set_card = true;
|
||||
}
|
||||
@@ -3667,12 +3700,12 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
|
||||
bool unknown_v1 = as_action_card_refs_contains_duplicate_of_set_card && as_action_card_refs_contains_set_card_ref;
|
||||
|
||||
uint8_t random_percent = this->server() ? this->server()->get_random(99) : 0;
|
||||
uint8_t random_percent = s->get_random(99);
|
||||
bool any_expr_used_dice_roll = false;
|
||||
|
||||
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.at(client_id);
|
||||
auto set_card_ps = (client_id == 0xFF) ? nullptr : s->player_states.at(client_id);
|
||||
|
||||
dice_roll.value = 1;
|
||||
if (set_card_ps) {
|
||||
@@ -3705,7 +3738,8 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
string refs_str = refs_str_for_cards_vector(targeted_cards);
|
||||
effect_log.debug("targeted_cards=[%s]", refs_str.c_str());
|
||||
bool all_targets_matched = false;
|
||||
if (!targeted_cards.empty() &&
|
||||
if (!s->options.is_trial() &&
|
||||
!targeted_cards.empty() &&
|
||||
((card_effect.type == ConditionType::UNKNOWN_64) ||
|
||||
(card_effect.type == ConditionType::MISC_DEFENSE_BONUSES) ||
|
||||
(card_effect.type == ConditionType::MOSTLY_HALFGUARDS))) {
|
||||
@@ -3724,9 +3758,9 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
}
|
||||
}
|
||||
if (count == targeted_cards.size()) {
|
||||
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) {
|
||||
set_card = this->server()->card_for_set_card_ref(sc_card_ref);
|
||||
set_card = s->card_for_set_card_ref(sc_card_ref);
|
||||
}
|
||||
targeted_cards.clear();
|
||||
if (set_card != nullptr) {
|
||||
@@ -3784,13 +3818,13 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
}
|
||||
|
||||
if (applied_cond_index >= 0) {
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
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.operation = static_cast<int8_t>(card_effect.type);
|
||||
this->server()->send(cmd);
|
||||
s->send(cmd);
|
||||
|
||||
// Note: The original code has this check outside of the if
|
||||
// (applied_cond_index >= 0) block, but this is a bug since
|
||||
@@ -3826,7 +3860,7 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
dice_cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(
|
||||
as_attacker_card_ref, 0x15);
|
||||
dice_cmd.effect.dice_roll_value = dice_roll.value;
|
||||
this->server()->send(dice_cmd);
|
||||
s->send(dice_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3903,7 +3937,7 @@ void CardSpecial::clear_invalid_conditions_on_card(
|
||||
if (cond.type != ConditionType::NONE) {
|
||||
if (!this->is_card_targeted_by_condition(cond, as, card)) {
|
||||
if (cond.type != ConditionType::NONE) {
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x04;
|
||||
cmd.effect.attacker_card_ref = 0xFFFF;
|
||||
cmd.effect.target_card_ref = card->get_card_ref();
|
||||
@@ -4350,7 +4384,6 @@ void CardSpecial::unknown_8024AAB8(const ActionState& as) {
|
||||
auto log = this->server()->log_stack("unknown_8024AAB8: ");
|
||||
string as_str = as.str();
|
||||
log.debug("as=%s", as_str.c_str());
|
||||
this->unknown_action_state_a1 = as;
|
||||
|
||||
for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) {
|
||||
uint16_t card_ref = this->send_6xB4x06_if_card_ref_invalid(
|
||||
@@ -4614,7 +4647,7 @@ void CardSpecial::check_for_attack_interference(shared_ptr<Card> unknown_p2) {
|
||||
ps->unknown_a16++;
|
||||
unknown_p2->action_chain.set_flags(0x100);
|
||||
|
||||
G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x04;
|
||||
cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(
|
||||
unknown_p2->get_card_ref(), 0x11);
|
||||
|
||||
+43
-42
@@ -45,45 +45,49 @@ public:
|
||||
};
|
||||
|
||||
struct AttackEnvStats {
|
||||
uint32_t num_set_cards; // "f" in expr
|
||||
uint32_t dice_roll_value1; // "d" in expr
|
||||
uint32_t effective_ap; // "ap" in expr
|
||||
uint32_t effective_tp; // "tp" in expr
|
||||
uint32_t current_hp; // "hp" in expr
|
||||
uint32_t max_hp; // "mhp" in expr
|
||||
uint32_t effective_ap_if_not_tech; // "dm" in expr
|
||||
uint32_t effective_ap_if_not_physical; // "tdm" in expr
|
||||
uint32_t player_num_destroyed_fcs; // "tf" in expr
|
||||
uint32_t player_num_atk_points; // "ac" in expr
|
||||
uint32_t defined_max_hp; // "php" in expr
|
||||
uint32_t dice_roll_value2; // "dc" in expr
|
||||
uint32_t card_cost; // "cs" in expr
|
||||
uint32_t total_num_set_cards; // "a" in expr
|
||||
uint32_t action_cards_ap; // "kap" in expr
|
||||
uint32_t action_cards_tp; // "ktp" in expr
|
||||
uint32_t unknown_a1; // "dn" in expr
|
||||
uint32_t num_item_or_creature_cards_in_hand; // "hf" in expr
|
||||
uint32_t num_destroyed_ally_fcs; // "df" in expr
|
||||
uint32_t target_team_num_set_cards; // "ff" in expr
|
||||
uint32_t condition_giver_team_num_set_cards; // "ef" in expr
|
||||
uint32_t num_native_creatures; // "bi" in expr
|
||||
uint32_t num_a_beast_creatures; // "ab" in expr
|
||||
uint32_t num_machine_creatures; // "mc" in expr
|
||||
uint32_t num_dark_creatures; // "dk" in expr
|
||||
uint32_t num_sword_type_items; // "sa" in expr
|
||||
uint32_t num_gun_type_items; // "gn" in expr
|
||||
uint32_t num_cane_type_items; // "wd" in expr
|
||||
uint32_t effective_ap_if_not_tech2; // "tt" in expr
|
||||
uint32_t team_dice_bonus; // "lv" in expr
|
||||
uint32_t sc_effective_ap; // "adm" in expr
|
||||
uint32_t attack_bonus; // "ddm" in expr
|
||||
uint32_t num_sword_type_items_on_team; // "sat" in expr
|
||||
uint32_t target_attack_bonus; // "edm" in expr
|
||||
uint32_t last_attack_preliminary_damage; // "ldm" in expr
|
||||
uint32_t last_attack_damage; // "rdm" in expr
|
||||
uint32_t total_last_attack_damage; // "fdm" in expr
|
||||
uint32_t last_attack_damage_count; // "ndm" in expr
|
||||
uint32_t target_current_hp; // "ehp" in expr
|
||||
/* 00 */ uint32_t num_set_cards; // "f" in expr
|
||||
/* 04 */ uint32_t dice_roll_value1; // "d" in expr
|
||||
/* 08 */ uint32_t effective_ap; // "ap" in expr
|
||||
/* 0C */ uint32_t effective_tp; // "tp" in expr
|
||||
/* 10 */ uint32_t current_hp; // "hp" in expr
|
||||
/* 14 */ uint32_t max_hp; // "mhp" in expr
|
||||
/* 18 */ uint32_t effective_ap_if_not_tech; // "dm" in expr
|
||||
/* 1C */ uint32_t effective_ap_if_not_physical; // "tdm" in expr
|
||||
/* 20 */ uint32_t player_num_destroyed_fcs; // "tf" in expr
|
||||
/* 24 */ uint32_t player_num_atk_points; // "ac" in expr
|
||||
/* 28 */ uint32_t defined_max_hp; // "php" in expr
|
||||
/* 2C */ uint32_t dice_roll_value2; // "dc" in expr
|
||||
/* 30 */ uint32_t card_cost; // "cs" in expr
|
||||
/* 34 */ uint32_t total_num_set_cards; // "a" in expr
|
||||
/* 38 */ uint32_t action_cards_ap; // "kap" in expr
|
||||
/* 3C */ uint32_t action_cards_tp; // "ktp" in expr
|
||||
/* 40 */ uint32_t unknown_a1; // "dn" in expr
|
||||
/* 44 */ uint32_t num_item_or_creature_cards_in_hand; // "hf" in expr
|
||||
/* 48 */ uint32_t num_destroyed_ally_fcs; // "df" in expr
|
||||
/* 4C */ uint32_t target_team_num_set_cards; // "ff" in expr
|
||||
/* 50 */ uint32_t non_target_team_num_set_cards; // "ef" in expr
|
||||
/* 54 */ uint32_t num_native_creatures; // "bi" in expr
|
||||
/* 58 */ uint32_t num_a_beast_creatures; // "ab" in expr
|
||||
/* 5C */ uint32_t num_machine_creatures; // "mc" in expr
|
||||
/* 60 */ uint32_t num_dark_creatures; // "dk" in expr
|
||||
/* 64 */ uint32_t num_sword_type_items; // "sa" in expr
|
||||
/* 68 */ uint32_t num_gun_type_items; // "gn" in expr
|
||||
/* 6C */ uint32_t num_cane_type_items; // "wd" in expr
|
||||
/* 70 */ uint32_t effective_ap_if_not_tech2; // "tt" in expr
|
||||
/* 74 */ uint32_t team_dice_bonus; // "lv" in expr
|
||||
/* 78 */ uint32_t sc_effective_ap; // "adm" in expr
|
||||
// The following fields do not exist in Trial Edition. Because this struct
|
||||
// is never sent to the client, we use the full struct even when playing
|
||||
// Trial Edition, just for simplicity.
|
||||
/* 7C */ uint32_t attack_bonus; // "ddm" in expr
|
||||
/* 80 */ uint32_t num_sword_type_items_on_team; // "sat" in expr
|
||||
/* 84 */ uint32_t target_attack_bonus; // "edm" in expr
|
||||
/* 88 */ uint32_t last_attack_preliminary_damage; // "ldm" in expr
|
||||
/* 8C */ uint32_t last_attack_damage; // "rdm" in expr
|
||||
/* 90 */ uint32_t total_last_attack_damage; // "fdm" in expr
|
||||
/* 94 */ uint32_t last_attack_damage_count; // "ndm" in expr
|
||||
/* 98 */ uint32_t target_current_hp; // "ehp" in expr
|
||||
/* 9C */
|
||||
|
||||
AttackEnvStats();
|
||||
void clear();
|
||||
@@ -333,9 +337,6 @@ public:
|
||||
|
||||
private:
|
||||
std::weak_ptr<Server> w_server;
|
||||
ActionState unknown_action_state_a1;
|
||||
ActionState action_state;
|
||||
uint16_t unknown_a2;
|
||||
};
|
||||
|
||||
} // namespace Episode3
|
||||
|
||||
@@ -36,6 +36,7 @@ enum BehaviorFlag : uint32_t {
|
||||
DISABLE_MASKING = 0x00000080,
|
||||
DISABLE_INTERFERENCE = 0x00000100,
|
||||
ALLOW_NON_COM_INTERFERENCE = 0x00000200,
|
||||
IS_TRIAL_EDITION = 0x00000400,
|
||||
};
|
||||
|
||||
enum class StatSwapType : uint8_t {
|
||||
|
||||
@@ -88,15 +88,16 @@ bool DeckState::draw_card_by_ref(uint16_t card_ref) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the card is discarded, then it should be before the draw index, and we
|
||||
// can just change its state.
|
||||
if (this->entries[index].state == CardState::DISCARDED) {
|
||||
// If the card is discarded, then it should be before the draw index, and we
|
||||
// can just change its state.
|
||||
this->entries[index].state = CardState::IN_HAND;
|
||||
return true;
|
||||
|
||||
// If the card is still drawable, we need to move it so it's just in front of
|
||||
// the draw index, then immediately draw it
|
||||
} else if (this->entries[index].state == CardState::DRAWABLE) {
|
||||
// If the card is still drawable, we need to move it so it's just in front
|
||||
// of the draw index, then immediately draw it. Ep3 NTE does not handle this
|
||||
// case, but we do even when playing NTE.
|
||||
ssize_t ref_index;
|
||||
for (ref_index = this->card_refs.size(); ref_index >= 0; ref_index--) {
|
||||
if (this->card_refs[ref_index] == card_ref) {
|
||||
@@ -182,7 +183,7 @@ void DeckState::restart() {
|
||||
this->shuffle();
|
||||
}
|
||||
|
||||
void DeckState::do_mulligan() {
|
||||
void DeckState::do_mulligan(bool is_trial) {
|
||||
for (size_t z = 0; z < this->entries.size(); z++) {
|
||||
if (this->entries[z].state == CardState::DISCARDED) {
|
||||
this->entries[z].state = CardState::DRAWABLE;
|
||||
@@ -190,7 +191,7 @@ void DeckState::do_mulligan() {
|
||||
}
|
||||
this->draw_index = 1;
|
||||
|
||||
if (this->shuffle_enabled) {
|
||||
if (is_trial || this->shuffle_enabled) {
|
||||
// Get the next 5 cards from the deck, and put the previous 5 cards after
|
||||
// them (so they will be shuffled back in).
|
||||
for (uint8_t z = 0; z < 5; z++) {
|
||||
|
||||
@@ -94,7 +94,7 @@ public:
|
||||
|
||||
void restart();
|
||||
void shuffle();
|
||||
void do_mulligan();
|
||||
void do_mulligan(bool is_trial);
|
||||
|
||||
void print(FILE* stream, std::shared_ptr<const CardIndex> card_index = nullptr) const;
|
||||
|
||||
|
||||
@@ -68,6 +68,37 @@ void MapAndRulesState::clear_occupied_bit_for_tile(uint8_t x, uint8_t y) {
|
||||
this->map.tiles[y][x] &= 0xEF;
|
||||
}
|
||||
|
||||
MapAndRulesStateTrial::MapAndRulesStateTrial(const MapAndRulesState& state)
|
||||
: map(state.map),
|
||||
num_players(state.num_players),
|
||||
unused1(state.unused1),
|
||||
environment_number(state.environment_number),
|
||||
num_players_per_team(state.num_players_per_team),
|
||||
num_team0_players(state.num_team0_players),
|
||||
unused2(state.unused2),
|
||||
unused5(state.start_facing_directions),
|
||||
unknown_a3(state.unused3),
|
||||
map_number(state.map_number),
|
||||
unused4(state.unused4),
|
||||
rules(state.rules) {}
|
||||
|
||||
MapAndRulesStateTrial::operator MapAndRulesState() const {
|
||||
MapAndRulesState ret;
|
||||
ret.map = this->map;
|
||||
ret.num_players = this->num_players;
|
||||
ret.unused1 = this->unused1;
|
||||
ret.environment_number = this->environment_number;
|
||||
ret.num_players_per_team = this->num_players_per_team;
|
||||
ret.num_team0_players = this->num_team0_players;
|
||||
ret.unused2 = this->unused2;
|
||||
ret.start_facing_directions = this->unused5;
|
||||
ret.unused3 = this->unknown_a3;
|
||||
ret.map_number = this->map_number;
|
||||
ret.unused4 = this->unused4;
|
||||
ret.rules = this->rules;
|
||||
return ret;
|
||||
}
|
||||
|
||||
OverlayState::OverlayState() {
|
||||
this->clear();
|
||||
}
|
||||
|
||||
+32
-12
@@ -10,8 +10,8 @@
|
||||
namespace Episode3 {
|
||||
|
||||
struct MapState {
|
||||
/* 0000 */ le_uint16_t width;
|
||||
/* 0002 */ le_uint16_t height;
|
||||
/* 0000 */ le_uint16_t width = 0;
|
||||
/* 0002 */ le_uint16_t height = 0;
|
||||
/* 0004 */ parray<parray<uint8_t, 0x10>, 0x10> tiles;
|
||||
/* 0104 */ parray<parray<uint8_t, 6>, 2> start_tile_definitions;
|
||||
/* 0110 */
|
||||
@@ -24,16 +24,16 @@ struct MapState {
|
||||
|
||||
struct MapAndRulesState {
|
||||
/* 0000 */ MapState map;
|
||||
/* 0110 */ uint8_t num_players;
|
||||
/* 0111 */ uint8_t unused1;
|
||||
/* 0112 */ uint8_t environment_number;
|
||||
/* 0113 */ uint8_t num_players_per_team;
|
||||
/* 0114 */ uint8_t num_team0_players;
|
||||
/* 0115 */ uint8_t unused2;
|
||||
/* 0116 */ le_uint16_t start_facing_directions;
|
||||
/* 0118 */ uint32_t unused3;
|
||||
/* 011C */ le_uint32_t map_number;
|
||||
/* 0120 */ uint32_t unused4;
|
||||
/* 0110 */ uint8_t num_players = 0;
|
||||
/* 0111 */ uint8_t unused1 = 0;
|
||||
/* 0112 */ uint8_t environment_number = 0;
|
||||
/* 0113 */ uint8_t num_players_per_team = 0;
|
||||
/* 0114 */ uint8_t num_team0_players = 0;
|
||||
/* 0115 */ uint8_t unused2 = 0;
|
||||
/* 0116 */ le_uint16_t start_facing_directions = 0;
|
||||
/* 0118 */ be_uint32_t unused3 = 0;
|
||||
/* 011C */ le_uint32_t map_number = 0;
|
||||
/* 0120 */ be_uint32_t unused4 = 0;
|
||||
/* 0124 */ Rules rules;
|
||||
/* 0138 */
|
||||
|
||||
@@ -47,6 +47,26 @@ struct MapAndRulesState {
|
||||
void clear_occupied_bit_for_tile(uint8_t x, uint8_t y);
|
||||
} __attribute__((packed));
|
||||
|
||||
struct MapAndRulesStateTrial {
|
||||
/* 0000 */ MapState map;
|
||||
/* 0110 */ uint8_t num_players = 0;
|
||||
/* 0111 */ uint8_t unused1 = 0;
|
||||
/* 0112 */ uint8_t environment_number = 0;
|
||||
/* 0113 */ uint8_t num_players_per_team = 0;
|
||||
/* 0114 */ uint8_t num_team0_players = 0;
|
||||
/* 0115 */ uint8_t unused2 = 0;
|
||||
/* 0116 */ le_uint16_t unused5 = 0;
|
||||
/* 0118 */ be_uint32_t unknown_a3 = 0;
|
||||
/* 011C */ le_uint32_t map_number = 0;
|
||||
/* 0120 */ be_uint32_t unused4 = 0;
|
||||
/* 0124 */ RulesTrial rules;
|
||||
/* 0130 */
|
||||
|
||||
MapAndRulesStateTrial() = default;
|
||||
MapAndRulesStateTrial(const MapAndRulesState& state);
|
||||
operator MapAndRulesState() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct OverlayState {
|
||||
parray<parray<uint8_t, 0x10>, 0x10> tiles;
|
||||
parray<le_uint32_t, 5> unused1;
|
||||
|
||||
+65
-33
@@ -91,6 +91,10 @@ void PlayerState::init() {
|
||||
this->sc_card = make_shared<Card>(this->deck_state->sc_card_id(), this->sc_card_ref, this->client_id, s);
|
||||
this->sc_card->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
|
||||
}
|
||||
|
||||
s->assist_server->hand_and_equip_states[this->client_id] = this->hand_and_equip;
|
||||
s->assist_server->card_short_statuses[this->client_id] = this->card_short_statuses;
|
||||
@@ -152,7 +156,7 @@ void PlayerState::apply_assist_card_effect_on_set(
|
||||
assist_card_id = s->card_id_for_card_ref(this->card_refs[6]);
|
||||
}
|
||||
|
||||
auto assist_effect = assist_effect_number_for_card_id(assist_card_id);
|
||||
auto assist_effect = assist_effect_number_for_card_id(assist_card_id, s->options.is_trial());
|
||||
if ((assist_effect == AssistEffect::RESISTANCE) ||
|
||||
(assist_effect == AssistEffect::INDEPENDENT)) {
|
||||
this->assist_card_set_number = 0;
|
||||
@@ -415,7 +419,7 @@ void PlayerState::apply_dice_effects() {
|
||||
case AssistEffect::DICE_FEVER:
|
||||
for (size_t die_index = 0; die_index < 2; die_index++) {
|
||||
if (this->dice_results[die_index] > 0) {
|
||||
this->dice_results[die_index] = 5;
|
||||
this->dice_results[die_index] = s->options.is_trial() ? 6 : 5;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -434,6 +438,9 @@ void PlayerState::apply_dice_effects() {
|
||||
}
|
||||
break;
|
||||
case AssistEffect::DICE_FEVER_PLUS:
|
||||
if (s->options.is_trial()) {
|
||||
break;
|
||||
}
|
||||
for (size_t die_index = 0; die_index < 2; die_index++) {
|
||||
if (this->dice_results[die_index] > 0) {
|
||||
this->dice_results[die_index] = 6;
|
||||
@@ -475,10 +482,18 @@ void PlayerState::compute_total_set_cards_cost() {
|
||||
|
||||
size_t PlayerState::count_set_cards() const {
|
||||
size_t ret = 0;
|
||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||
auto card = this->set_cards[set_index];
|
||||
if (card && !(card->card_flags & 2)) {
|
||||
ret++;
|
||||
if (this->server()->options.is_trial()) {
|
||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||
if (this->card_refs[8 + set_index] != 0xFFFF) {
|
||||
ret++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||
auto card = this->set_cards[set_index];
|
||||
if (card && !(card->card_flags & 2)) {
|
||||
ret++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -559,7 +574,7 @@ void PlayerState::discard_and_redraw_hand() {
|
||||
this->discard_ref_from_hand(this->card_refs[0]);
|
||||
}
|
||||
|
||||
G_Unknown_GC_Ep3_6xB4x2C cmd;
|
||||
G_Unknown_Ep3_6xB4x2C cmd;
|
||||
cmd.change_type = 3;
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
@@ -654,17 +669,21 @@ bool PlayerState::do_mulligan() {
|
||||
this->discard_ref_from_hand(this->card_refs[0]);
|
||||
}
|
||||
|
||||
G_Unknown_GC_Ep3_6xB4x2C cmd;
|
||||
cmd.change_type = 3;
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
cmd.unknown_a2.clear(0xFFFFFFFF);
|
||||
s->send(cmd);
|
||||
if (!s->options.is_trial()) {
|
||||
G_Unknown_Ep3_6xB4x2C cmd;
|
||||
cmd.change_type = 3;
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
cmd.unknown_a2.clear(0xFFFFFFFF);
|
||||
s->send(cmd);
|
||||
}
|
||||
|
||||
this->deck_state->do_mulligan();
|
||||
this->deck_state->do_mulligan(s->options.is_trial());
|
||||
this->draw_hand(5);
|
||||
|
||||
this->discard_log_card_refs.clear(0xFFFF);
|
||||
if (!s->options.is_trial()) {
|
||||
this->discard_log_card_refs.clear(0xFFFF);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -675,7 +694,7 @@ void PlayerState::draw_hand(ssize_t override_count) {
|
||||
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++) {
|
||||
auto eff = s->assist_server->get_active_assist_by_index(z);
|
||||
if (eff == AssistEffect::RICH_PLUS) {
|
||||
if ((eff == AssistEffect::RICH_PLUS) && !s->options.is_trial()) {
|
||||
count = 4 - this->get_hand_size();
|
||||
} else if (eff == AssistEffect::RICH) {
|
||||
count = 6 - this->get_hand_size();
|
||||
@@ -694,7 +713,7 @@ void PlayerState::draw_hand(ssize_t override_count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s->get_setup_phase() == SetupPhase::MAIN_BATTLE) {
|
||||
if (!s->options.is_trial() && (s->get_setup_phase() == SetupPhase::MAIN_BATTLE)) {
|
||||
this->stats.num_cards_drawn++;
|
||||
}
|
||||
}
|
||||
@@ -893,8 +912,7 @@ void PlayerState::log_discard(uint16_t card_ref, uint16_t reason) {
|
||||
this->discard_log_reasons[0] = reason;
|
||||
}
|
||||
|
||||
bool PlayerState::move_card_to_location_by_card_index(
|
||||
size_t card_index, const Location& new_loc) {
|
||||
bool PlayerState::move_card_to_location_by_card_index(size_t card_index, const Location& new_loc) {
|
||||
auto s = this->server();
|
||||
|
||||
shared_ptr<Card> card;
|
||||
@@ -1015,14 +1033,15 @@ void PlayerState::on_cards_destroyed() {
|
||||
void PlayerState::replace_all_set_assists_with_random_assists() {
|
||||
auto s = this->server();
|
||||
|
||||
const auto& assist_card_ids = all_assist_card_ids(s->options.is_trial());
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
auto other_ps = s->get_player_state(client_id);
|
||||
if (other_ps &&
|
||||
((other_ps->card_refs[6] != 0xFFFF) || (other_ps->set_assist_card_id != 0xFFFF))) {
|
||||
uint16_t card_id = 0x0130;
|
||||
while (card_id == 0x0130) { // God Whim
|
||||
size_t index = s->get_random(ALL_ASSIST_CARD_IDS.size());
|
||||
card_id = ALL_ASSIST_CARD_IDS[index];
|
||||
size_t index = s->get_random(assist_card_ids.size());
|
||||
card_id = assist_card_ids[index];
|
||||
if (!this->god_whim_can_use_hidden_cards) {
|
||||
auto ce = s->definition_for_card_id(card_id);
|
||||
if (!ce || ce->def.cannot_drop) {
|
||||
@@ -1171,7 +1190,7 @@ void PlayerState::send_set_card_updates(bool always_send) {
|
||||
}
|
||||
|
||||
if (mask && !s->get_should_copy_prev_states_to_current_states()) {
|
||||
G_ClearSetCardConditions_GC_Ep3_6xB4x4F cmd;
|
||||
G_ClearSetCardConditions_Ep3_6xB4x4F cmd;
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.clear_mask = mask;
|
||||
s->send(cmd);
|
||||
@@ -1307,7 +1326,7 @@ bool PlayerState::set_card_from_hand(
|
||||
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
||||
s->send_6xB4x05();
|
||||
|
||||
G_Unknown_GC_Ep3_6xB4x4A cmd;
|
||||
G_Unknown_Ep3_6xB4x4A cmd;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
cmd.card_refs[0] = card_ref;
|
||||
cmd.client_id = this->client_id;
|
||||
@@ -1442,8 +1461,8 @@ void PlayerState::subtract_atk_points(uint8_t cost) {
|
||||
this->atk_points2 = min<uint8_t>(this->atk_points, this->atk_points2_max);
|
||||
}
|
||||
|
||||
G_UpdateHand_GC_Ep3_6xB4x02 PlayerState::prepare_6xB4x02() const {
|
||||
G_UpdateHand_GC_Ep3_6xB4x02 cmd;
|
||||
G_UpdateHand_Ep3_6xB4x02 PlayerState::prepare_6xB4x02() const {
|
||||
G_UpdateHand_Ep3_6xB4x02 cmd;
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.state.dice_results = this->dice_results;
|
||||
cmd.state.atk_points = this->atk_points;
|
||||
@@ -1487,13 +1506,14 @@ void PlayerState::update_hand_and_equip_state_and_send_6xB4x02_if_needed(
|
||||
|
||||
void PlayerState::set_random_assist_card_from_hand_for_free() {
|
||||
auto s = this->server();
|
||||
bool is_trial = s->options.is_trial();
|
||||
|
||||
vector<uint16_t> candidate_card_refs;
|
||||
for (size_t hand_index = 0; hand_index < 6; hand_index++) {
|
||||
uint16_t card_ref = this->card_refs[hand_index];
|
||||
auto ce = s->definition_for_card_ref(card_ref);
|
||||
if (ce && (ce->def.type == CardType::ASSIST) &&
|
||||
(assist_effect_number_for_card_id(ce->def.card_id) != AssistEffect::SQUEEZE)) {
|
||||
(assist_effect_number_for_card_id(ce->def.card_id, is_trial) != AssistEffect::SQUEEZE)) {
|
||||
candidate_card_refs.emplace_back(card_ref);
|
||||
}
|
||||
}
|
||||
@@ -1506,8 +1526,8 @@ void PlayerState::set_random_assist_card_from_hand_for_free() {
|
||||
}
|
||||
}
|
||||
|
||||
G_UpdateShortStatuses_GC_Ep3_6xB4x04 PlayerState::prepare_6xB4x04() const {
|
||||
G_UpdateShortStatuses_GC_Ep3_6xB4x04 cmd;
|
||||
G_UpdateShortStatuses_Ep3_6xB4x04 PlayerState::prepare_6xB4x04() const {
|
||||
G_UpdateShortStatuses_Ep3_6xB4x04 cmd;
|
||||
cmd.client_id = this->client_id;
|
||||
// Note: The original code calls memset to clear all the short status structs
|
||||
// at once. We don't do this because the default constructor has already
|
||||
@@ -1550,7 +1570,7 @@ void PlayerState::send_6xB4x04_if_needed(bool always_send) {
|
||||
if (always_send || (cmd.card_statuses != *this->card_short_statuses)) {
|
||||
auto s = this->server();
|
||||
*this->card_short_statuses = cmd.card_statuses;
|
||||
if (!s->get_should_copy_prev_states_to_current_states()) {
|
||||
if (s->options.is_trial() || !s->get_should_copy_prev_states_to_current_states()) {
|
||||
s->send(cmd);
|
||||
}
|
||||
}
|
||||
@@ -1654,7 +1674,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
this->subtract_or_check_atk_or_def_points_for_action(pa, 1);
|
||||
|
||||
if (action_type == ActionType::ATTACK) {
|
||||
G_Unknown_GC_Ep3_6xB4x4A cmd;
|
||||
G_Unknown_Ep3_6xB4x4A cmd;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.round_num = s->get_round_num();
|
||||
@@ -1693,7 +1713,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
}
|
||||
|
||||
} else if (action_type == ActionType::DEFENSE) {
|
||||
G_Unknown_GC_Ep3_6xB4x4A cmd;
|
||||
G_Unknown_Ep3_6xB4x4A cmd;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
cmd.client_id = this->client_id;
|
||||
cmd.round_num = s->get_round_num();
|
||||
@@ -1777,14 +1797,26 @@ void PlayerState::handle_homesick_assist_effect_from_bomb(shared_ptr<Card> card)
|
||||
if (s->assist_server->get_active_assist_by_index(z) == AssistEffect::HOMESICK) {
|
||||
this->return_set_card_to_hand2(card_ref);
|
||||
this->log_discard(card_ref, 1);
|
||||
this->set_cards[set_index]->card_flags |= 2;
|
||||
// On NTE, the card is destroyed immediately
|
||||
if (s->options.is_trial()) {
|
||||
this->set_cards[set_index]->update_stats_on_destruction();
|
||||
this->set_cards[set_index].reset();
|
||||
} else {
|
||||
this->set_cards[set_index]->card_flags |= 2;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->deck_state->set_card_ref_drawable_next(card_ref)) {
|
||||
this->log_discard(card_ref, 1);
|
||||
this->set_cards[set_index]->card_flags |= 2;
|
||||
// On NTE, the card is destroyed immediately
|
||||
if (s->options.is_trial()) {
|
||||
this->set_cards[set_index]->update_stats_on_destruction();
|
||||
this->set_cards[set_index].reset();
|
||||
} else {
|
||||
this->set_cards[set_index]->card_flags |= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,7 @@ public:
|
||||
void discard_all_attack_action_cards_from_hand();
|
||||
void discard_all_item_and_creature_cards_from_hand();
|
||||
void discard_and_redraw_hand();
|
||||
bool discard_card_or_add_to_draw_pile(
|
||||
uint16_t card_ref, bool add_to_draw_pile);
|
||||
bool discard_card_or_add_to_draw_pile(uint16_t card_ref, bool add_to_draw_pile);
|
||||
void discard_random_hand_card();
|
||||
bool discard_ref_from_hand(uint16_t card_ref);
|
||||
void discard_set_assist_card();
|
||||
@@ -76,8 +75,7 @@ public:
|
||||
const Location& loc,
|
||||
uint8_t target_team_id) const;
|
||||
uint8_t get_atk_points() const;
|
||||
void get_short_status_for_card_index_in_hand(
|
||||
size_t hand_index, CardShortStatus* stat) const;
|
||||
void get_short_status_for_card_index_in_hand(size_t hand_index, CardShortStatus* stat) const;
|
||||
std::shared_ptr<DeckState> get_deck();
|
||||
uint8_t get_def_points() const;
|
||||
uint8_t get_dice_result(size_t which) const;
|
||||
@@ -96,8 +94,7 @@ public:
|
||||
bool is_mulligan_allowed() const;
|
||||
bool is_team_turn() const;
|
||||
void log_discard(uint16_t card_ref, uint16_t reason);
|
||||
bool move_card_to_location_by_card_index(
|
||||
size_t card_index, const Location& new_loc);
|
||||
bool move_card_to_location_by_card_index(size_t card_index, const Location& new_loc);
|
||||
void move_null_hand_refs_to_end();
|
||||
void on_cards_destroyed();
|
||||
void replace_all_set_assists_with_random_assists();
|
||||
@@ -115,18 +112,15 @@ public:
|
||||
uint8_t assist_target_client_id,
|
||||
bool skip_error_checks_and_atk_sub);
|
||||
void set_initial_location();
|
||||
void set_map_occupied_bit_for_card_on_warp_tile(
|
||||
std::shared_ptr<const Card> card);
|
||||
void set_map_occupied_bit_for_card_on_warp_tile(std::shared_ptr<const Card> card);
|
||||
void set_map_occupied_bits_for_sc_and_creatures();
|
||||
void subtract_def_points(uint8_t cost);
|
||||
bool subtract_or_check_atk_or_def_points_for_action(
|
||||
const ActionState& pa, bool deduct_points);
|
||||
bool subtract_or_check_atk_or_def_points_for_action(const ActionState& pa, bool deduct_points);
|
||||
void subtract_atk_points(uint8_t cost);
|
||||
G_UpdateHand_GC_Ep3_6xB4x02 prepare_6xB4x02() const;
|
||||
void update_hand_and_equip_state_and_send_6xB4x02_if_needed(
|
||||
bool always_send = false);
|
||||
G_UpdateHand_Ep3_6xB4x02 prepare_6xB4x02() const;
|
||||
void update_hand_and_equip_state_and_send_6xB4x02_if_needed(bool always_send = false);
|
||||
void set_random_assist_card_from_hand_for_free();
|
||||
G_UpdateShortStatuses_GC_Ep3_6xB4x04 prepare_6xB4x04() const;
|
||||
G_UpdateShortStatuses_Ep3_6xB4x04 prepare_6xB4x04() const;
|
||||
void send_6xB4x04_if_needed(bool always_send = false);
|
||||
std::vector<uint16_t> get_card_refs_within_range_from_all_players(
|
||||
const parray<uint8_t, 9 * 9>& range,
|
||||
|
||||
@@ -481,6 +481,83 @@ bool ActionChainWithConds::can_apply_attack() const {
|
||||
return this->check_flag(4) ? false : (this->chain.target_card_ref_count != 0);
|
||||
}
|
||||
|
||||
/* 0000 */ int8_t effective_ap;
|
||||
/* 0001 */ int8_t effective_tp;
|
||||
/* 0002 */ int8_t ap_effect_bonus;
|
||||
/* 0003 */ int8_t damage;
|
||||
/* 0004 */ le_uint16_t acting_card_ref;
|
||||
/* 0006 */ le_uint16_t unknown_card_ref_a3;
|
||||
/* 0008 */ parray<le_uint16_t, 8> attack_action_card_refs;
|
||||
/* 0018 */ uint8_t attack_action_card_ref_count;
|
||||
/* 0019 */ AttackMedium attack_medium;
|
||||
/* 001A */ uint8_t target_card_ref_count;
|
||||
/* 001B */ ActionSubphase action_subphase;
|
||||
/* 001C */ uint8_t strike_count;
|
||||
/* 001D */ int8_t damage_multiplier;
|
||||
/* 001E */ uint8_t attack_number;
|
||||
/* 001F */ int8_t tp_effect_bonus;
|
||||
/* 0020 */ uint8_t unused1;
|
||||
/* 0021 */ uint8_t unused2;
|
||||
/* 0022 */ int8_t card_ap;
|
||||
/* 0023 */ int8_t card_tp;
|
||||
/* 0024 */ le_uint32_t flags;
|
||||
// The only difference between this structure and ActionChainWithConds is that
|
||||
// these two fields have changed orders.
|
||||
/* 0028 */ parray<Condition, 9> conditions;
|
||||
/* 00B8 */ parray<le_uint16_t, 4 * 9> target_card_refs;
|
||||
/* 0100 */
|
||||
|
||||
ActionChainWithCondsTrial::ActionChainWithCondsTrial(const ActionChainWithConds& src)
|
||||
: effective_ap(src.chain.effective_ap),
|
||||
effective_tp(src.chain.effective_tp),
|
||||
ap_effect_bonus(src.chain.ap_effect_bonus),
|
||||
damage(src.chain.damage),
|
||||
acting_card_ref(src.chain.acting_card_ref),
|
||||
unknown_card_ref_a3(src.chain.unknown_card_ref_a3),
|
||||
attack_action_card_refs(src.chain.attack_action_card_refs),
|
||||
attack_action_card_ref_count(src.chain.attack_action_card_ref_count),
|
||||
attack_medium(src.chain.attack_medium),
|
||||
target_card_ref_count(src.chain.target_card_ref_count),
|
||||
action_subphase(src.chain.action_subphase),
|
||||
strike_count(src.chain.strike_count),
|
||||
damage_multiplier(src.chain.damage_multiplier),
|
||||
attack_number(src.chain.attack_number),
|
||||
tp_effect_bonus(src.chain.tp_effect_bonus),
|
||||
unused1(src.chain.unused1),
|
||||
unused2(src.chain.unused2),
|
||||
card_ap(src.chain.card_ap),
|
||||
card_tp(src.chain.card_tp),
|
||||
flags(src.chain.flags),
|
||||
conditions(src.conditions),
|
||||
target_card_refs(src.chain.target_card_refs) {}
|
||||
|
||||
ActionChainWithCondsTrial::operator ActionChainWithConds() const {
|
||||
ActionChainWithConds ret;
|
||||
ret.chain.effective_ap = this->effective_ap;
|
||||
ret.chain.effective_tp = this->effective_tp;
|
||||
ret.chain.ap_effect_bonus = this->ap_effect_bonus;
|
||||
ret.chain.damage = this->damage;
|
||||
ret.chain.acting_card_ref = this->acting_card_ref;
|
||||
ret.chain.unknown_card_ref_a3 = this->unknown_card_ref_a3;
|
||||
ret.chain.attack_action_card_refs = this->attack_action_card_refs;
|
||||
ret.chain.attack_action_card_ref_count = this->attack_action_card_ref_count;
|
||||
ret.chain.attack_medium = this->attack_medium;
|
||||
ret.chain.target_card_ref_count = this->target_card_ref_count;
|
||||
ret.chain.action_subphase = this->action_subphase;
|
||||
ret.chain.strike_count = this->strike_count;
|
||||
ret.chain.damage_multiplier = this->damage_multiplier;
|
||||
ret.chain.attack_number = this->attack_number;
|
||||
ret.chain.tp_effect_bonus = this->tp_effect_bonus;
|
||||
ret.chain.unused1 = this->unused1;
|
||||
ret.chain.unused2 = this->unused2;
|
||||
ret.chain.card_ap = this->card_ap;
|
||||
ret.chain.card_tp = this->card_tp;
|
||||
ret.chain.flags = this->flags;
|
||||
ret.chain.target_card_refs = this->target_card_refs;
|
||||
ret.conditions = this->conditions;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ActionMetadata::ActionMetadata() {
|
||||
this->clear();
|
||||
}
|
||||
@@ -531,6 +608,8 @@ void ActionMetadata::clear() {
|
||||
this->action_subphase = ActionSubphase::INVALID_FF;
|
||||
this->defense_power = 0;
|
||||
this->defense_bonus = 0;
|
||||
// TODO: Ep3 NTE doesn't set attack_bonus to zero here. Is the field just
|
||||
// unused in NTE?
|
||||
this->attack_bonus = 0;
|
||||
this->flags = 0;
|
||||
this->target_card_refs.clear(0xFFFF);
|
||||
@@ -759,6 +838,23 @@ const char* PlayerBattleStats::name_for_rank(uint8_t rank) {
|
||||
return RANK_NAMES[rank];
|
||||
}
|
||||
|
||||
PlayerBattleStatsTrial::PlayerBattleStatsTrial(const PlayerBattleStats& data)
|
||||
: damage_given(data.damage_given.load()),
|
||||
damage_taken(data.damage_taken.load()),
|
||||
num_opponent_cards_destroyed(data.num_opponent_cards_destroyed.load()),
|
||||
num_owned_cards_destroyed(data.num_owned_cards_destroyed.load()),
|
||||
total_move_distance(data.total_move_distance.load()) {}
|
||||
|
||||
PlayerBattleStatsTrial::operator PlayerBattleStats() const {
|
||||
PlayerBattleStats ret;
|
||||
ret.damage_given = this->damage_given.load();
|
||||
ret.damage_taken = this->damage_taken.load();
|
||||
ret.num_opponent_cards_destroyed = this->num_opponent_cards_destroyed.load();
|
||||
ret.num_owned_cards_destroyed = this->num_owned_cards_destroyed.load();
|
||||
ret.total_move_distance = this->total_move_distance.load();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool is_card_within_range(
|
||||
const parray<uint8_t, 9 * 9>& range,
|
||||
const Location& anchor_loc,
|
||||
|
||||
@@ -101,6 +101,8 @@ struct ActionState {
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ActionChain {
|
||||
// Note: Episode 3 Trial Edition has a different format for this structure.
|
||||
// See ActionChainWithCondsTrial for details.
|
||||
/* 00 */ int8_t effective_ap;
|
||||
/* 01 */ int8_t effective_tp;
|
||||
/* 02 */ int8_t ap_effect_bonus;
|
||||
@@ -170,6 +172,38 @@ struct ActionChainWithConds {
|
||||
std::string str() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ActionChainWithCondsTrial {
|
||||
/* 0000 */ int8_t effective_ap;
|
||||
/* 0001 */ int8_t effective_tp;
|
||||
/* 0002 */ int8_t ap_effect_bonus;
|
||||
/* 0003 */ int8_t damage;
|
||||
/* 0004 */ le_uint16_t acting_card_ref;
|
||||
/* 0006 */ le_uint16_t unknown_card_ref_a3;
|
||||
/* 0008 */ parray<le_uint16_t, 8> attack_action_card_refs;
|
||||
/* 0018 */ uint8_t attack_action_card_ref_count;
|
||||
/* 0019 */ AttackMedium attack_medium;
|
||||
/* 001A */ uint8_t target_card_ref_count;
|
||||
/* 001B */ ActionSubphase action_subphase;
|
||||
/* 001C */ uint8_t strike_count;
|
||||
/* 001D */ int8_t damage_multiplier;
|
||||
/* 001E */ uint8_t attack_number;
|
||||
/* 001F */ int8_t tp_effect_bonus;
|
||||
/* 0020 */ uint8_t unused1;
|
||||
/* 0021 */ uint8_t unused2;
|
||||
/* 0022 */ int8_t card_ap;
|
||||
/* 0023 */ int8_t card_tp;
|
||||
/* 0024 */ le_uint32_t flags;
|
||||
// The only difference between this structure and ActionChainWithConds is that
|
||||
// these two fields have changed orders.
|
||||
/* 0028 */ parray<Condition, 9> conditions;
|
||||
/* 00B8 */ parray<le_uint16_t, 4 * 9> target_card_refs;
|
||||
/* 0100 */
|
||||
|
||||
ActionChainWithCondsTrial() = default;
|
||||
ActionChainWithCondsTrial(const ActionChainWithConds& src);
|
||||
operator ActionChainWithConds() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ActionMetadata {
|
||||
/* 00 */ le_uint16_t card_ref;
|
||||
/* 02 */ uint8_t target_card_ref_count;
|
||||
@@ -275,6 +309,19 @@ struct PlayerBattleStats {
|
||||
static const char* name_for_rank(uint8_t rank);
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PlayerBattleStatsTrial {
|
||||
/* 00 */ le_uint32_t damage_given = 0;
|
||||
/* 04 */ le_uint32_t damage_taken = 0;
|
||||
/* 08 */ le_uint32_t num_opponent_cards_destroyed = 0;
|
||||
/* 0C */ le_uint32_t num_owned_cards_destroyed = 0;
|
||||
/* 10 */ le_uint32_t total_move_distance = 0;
|
||||
/* 14 */
|
||||
|
||||
PlayerBattleStatsTrial() = default;
|
||||
PlayerBattleStatsTrial(const PlayerBattleStats& data);
|
||||
operator PlayerBattleStats() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
std::vector<uint16_t> get_card_refs_within_range(
|
||||
const parray<uint8_t, 9 * 9>& range,
|
||||
const Location& loc,
|
||||
|
||||
@@ -204,6 +204,8 @@ const ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(
|
||||
uint16_t card_ref) const {
|
||||
uint8_t client_id = client_id_for_card_ref(card_ref);
|
||||
if (client_id != 0xFF) {
|
||||
// There appears to be a bug in Trial Edition: the bound on this loop is
|
||||
// 0x10, not 9.
|
||||
for (size_t z = 0; z < 9; z++) {
|
||||
const auto* chain = &this->set_card_action_chains[client_id]->at(z);
|
||||
if (card_ref == chain->chain.acting_card_ref) {
|
||||
@@ -1672,7 +1674,7 @@ int32_t RulerServer::error_code_for_client_setting_card(
|
||||
}
|
||||
|
||||
// Check for assists that can only be set on yourself
|
||||
auto eff = assist_effect_number_for_card_id(ce->def.card_id);
|
||||
auto eff = assist_effect_number_for_card_id(ce->def.card_id, this->server()->options.is_trial());
|
||||
if (((eff == AssistEffect::LEGACY) || (eff == AssistEffect::EXCHANGE)) &&
|
||||
(assist_target_client_id != 0xFF) &&
|
||||
(assist_target_client_id != client_id_for_card_ref(card_ref))) {
|
||||
|
||||
+15
-30
@@ -49,12 +49,9 @@ public:
|
||||
std::shared_ptr<Server> server();
|
||||
std::shared_ptr<const Server> server() const;
|
||||
|
||||
ActionChainWithConds* action_chain_with_conds_for_card_ref(
|
||||
uint16_t card_ref);
|
||||
const ActionChainWithConds* action_chain_with_conds_for_card_ref(
|
||||
uint16_t card_ref) const;
|
||||
bool any_attack_action_card_is_support_tech_or_support_pb(
|
||||
const ActionState& pa) const;
|
||||
ActionChainWithConds* action_chain_with_conds_for_card_ref(uint16_t card_ref);
|
||||
const ActionChainWithConds* action_chain_with_conds_for_card_ref(uint16_t card_ref) const;
|
||||
bool any_attack_action_card_is_support_tech_or_support_pb(const ActionState& pa) const;
|
||||
bool card_has_pierce_or_rampage(
|
||||
uint8_t client_id,
|
||||
ConditionType cond_type,
|
||||
@@ -63,27 +60,21 @@ public:
|
||||
uint16_t action_card_ref,
|
||||
uint8_t def_effect_index,
|
||||
AttackMedium attack_medium) const;
|
||||
bool attack_action_has_rampage_and_not_pierce(
|
||||
const ActionState& pa, uint16_t card_ref) const;
|
||||
bool attack_action_has_pierce_and_not_rampage(
|
||||
const ActionState& pa, uint8_t client_id);
|
||||
bool attack_action_has_rampage_and_not_pierce(const ActionState& pa, uint16_t card_ref) const;
|
||||
bool attack_action_has_pierce_and_not_rampage(const ActionState& pa, uint8_t client_id);
|
||||
bool card_exists_by_status(const CardShortStatus& stat) const;
|
||||
bool card_has_mighty_knuckle(uint32_t card_ref) const;
|
||||
uint16_t card_id_for_card_ref(uint16_t card_ref) const;
|
||||
static bool card_id_is_boss_sc(uint16_t card_id);
|
||||
static bool card_id_is_support_tech_or_support_pb(uint16_t card_id);
|
||||
bool card_ref_can_attack(uint16_t card_ref);
|
||||
bool card_ref_can_move(
|
||||
uint8_t client_id, uint16_t card_ref, bool ignore_atk_points) const;
|
||||
bool card_ref_has_class_usability_condition(
|
||||
uint16_t card_ref) const;
|
||||
bool card_ref_can_move(uint8_t client_id, uint16_t card_ref, bool ignore_atk_points) const;
|
||||
bool card_ref_has_class_usability_condition(uint16_t card_ref) const;
|
||||
bool card_ref_has_free_maneuver(uint16_t card_ref) const;
|
||||
bool card_ref_is_aerial(uint16_t card_ref) const;
|
||||
bool card_ref_is_aerial_or_has_free_maneuver(
|
||||
uint16_t card_ref) const;
|
||||
bool card_ref_is_aerial_or_has_free_maneuver(uint16_t card_ref) const;
|
||||
bool card_ref_is_boss_sc(uint32_t card_ref) const;
|
||||
bool card_ref_or_any_set_card_has_condition_46(
|
||||
uint16_t card_ref) const;
|
||||
bool card_ref_or_any_set_card_has_condition_46(uint16_t card_ref) const;
|
||||
bool card_ref_or_sc_has_fixed_range(uint16_t card_ref) const;
|
||||
bool check_move_path_and_get_cost(
|
||||
uint8_t client_id,
|
||||
@@ -123,14 +114,12 @@ public:
|
||||
uint16_t* out_effective_card_id,
|
||||
TargetMode* out_effective_target_mode,
|
||||
uint16_t* out_orig_card_ref) const;
|
||||
size_t count_rampage_targets_for_attack(
|
||||
const ActionState& pa, uint8_t client_id) const;
|
||||
size_t count_rampage_targets_for_attack(const ActionState& pa, uint8_t client_id) const;
|
||||
bool defense_card_can_apply_to_attack(
|
||||
uint16_t defense_card_ref,
|
||||
uint16_t attacker_card_ref,
|
||||
uint16_t attacker_sc_card_ref) const;
|
||||
bool defense_card_matches_any_attack_card_top_color(
|
||||
const ActionState& pa) const;
|
||||
bool defense_card_matches_any_attack_card_top_color(const ActionState& pa) const;
|
||||
std::shared_ptr<const CardIndex::CardEntry> definition_for_card_ref(uint16_t card_ref) const;
|
||||
int32_t error_code_for_client_setting_card(
|
||||
uint8_t client_id,
|
||||
@@ -157,17 +146,14 @@ public:
|
||||
size_t num_occupied_tiles,
|
||||
size_t num_vacant_tiles) const;
|
||||
uint16_t get_ally_sc_card_ref(uint16_t card_ref) const;
|
||||
std::shared_ptr<const CardIndex::CardEntry> definition_for_card_id(
|
||||
uint32_t card_id) const;
|
||||
std::shared_ptr<const CardIndex::CardEntry> definition_for_card_id(uint32_t card_id) const;
|
||||
uint32_t get_card_id_with_effective_range(
|
||||
uint16_t card_ref, uint16_t card_id_override, TargetMode* out_target_mode) const;
|
||||
uint8_t get_card_ref_max_hp(uint16_t card_ref) const;
|
||||
bool get_creature_summon_area(
|
||||
uint8_t client_id, Location* out_loc, uint8_t* out_region_size) const;
|
||||
std::shared_ptr<HandAndEquipState> get_hand_and_equip_state_for_client_id(
|
||||
uint8_t client_id);
|
||||
std::shared_ptr<const HandAndEquipState> get_hand_and_equip_state_for_client_id(
|
||||
uint8_t client_id) const;
|
||||
std::shared_ptr<HandAndEquipState> get_hand_and_equip_state_for_client_id(uint8_t client_id);
|
||||
std::shared_ptr<const HandAndEquipState> get_hand_and_equip_state_for_client_id(uint8_t client_id) const;
|
||||
bool get_move_path_length_and_cost(
|
||||
uint32_t client_id,
|
||||
uint32_t card_ref,
|
||||
@@ -188,8 +174,7 @@ public:
|
||||
std::shared_ptr<StateFlags> state_flags,
|
||||
std::shared_ptr<AssistServer> assist_server);
|
||||
size_t max_move_distance_for_card_ref(uint32_t card_ref) const;
|
||||
static void offsets_for_direction(
|
||||
const Location& loc, int32_t* out_x_offset, int32_t* out_y_offset);
|
||||
static void offsets_for_direction(const Location& loc, int32_t* out_x_offset, int32_t* out_y_offset);
|
||||
void register_player(
|
||||
uint8_t client_id,
|
||||
std::shared_ptr<HandAndEquipState> hes,
|
||||
|
||||
+255
-202
@@ -14,6 +14,8 @@ namespace Episode3 {
|
||||
// "[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();
|
||||
@@ -203,7 +205,7 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
|
||||
|
||||
string masked_data;
|
||||
if (enable_masking &&
|
||||
(l->base_version != Version::GC_EP3_NTE) &&
|
||||
!this->options.is_trial() &&
|
||||
!(this->options.behavior_flags & BehaviorFlag::DISABLE_MASKING) &&
|
||||
(size >= 8)) {
|
||||
masked_data.assign(reinterpret_cast<const char*>(data), size);
|
||||
@@ -233,18 +235,25 @@ 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.
|
||||
G_ServerVersionStrings_GC_Ep3_6xB4x46 cmd46;
|
||||
cmd46.version_signature.encode(VERSION_SIGNATURE, 1);
|
||||
cmd46.date_str1.encode(format_time(this->options.card_index->definitions_mtime() * 1000000), 1);
|
||||
string date_str2 = string_printf(
|
||||
"Random:%08" PRIX32 "+%08" PRIX32,
|
||||
this->options.random_crypt->seed(),
|
||||
this->options.random_crypt->absolute_offset());
|
||||
if (this->last_chosen_map) {
|
||||
date_str2 += string_printf(" Map:%08" PRIX32, this->last_chosen_map->map_number);
|
||||
if (this->options.is_trial()) {
|
||||
G_ServerVersionStrings_Ep3NTE_6xB4x46 cmd;
|
||||
cmd.version_signature.encode(VERSION_SIGNATURE_NTE, 1);
|
||||
cmd.date_str1.encode(format_time(this->options.card_index->definitions_mtime() * 1000000), 1);
|
||||
this->send(cmd);
|
||||
} else {
|
||||
G_ServerVersionStrings_Ep3_6xB4x46 cmd;
|
||||
cmd.version_signature.encode(VERSION_SIGNATURE, 1);
|
||||
cmd.date_str1.encode(format_time(this->options.card_index->definitions_mtime() * 1000000), 1);
|
||||
string date_str2 = string_printf(
|
||||
"Random:%08" PRIX32 "+%08" PRIX32,
|
||||
this->options.random_crypt->seed(),
|
||||
this->options.random_crypt->absolute_offset());
|
||||
if (this->last_chosen_map) {
|
||||
date_str2 += string_printf(" Map:%08" PRIX32, this->last_chosen_map->map_number);
|
||||
}
|
||||
cmd.date_str2.encode(date_str2, 1);
|
||||
this->send(cmd);
|
||||
}
|
||||
cmd46.date_str2.encode(date_str2, 1);
|
||||
this->send(cmd46);
|
||||
}
|
||||
|
||||
string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> map, uint8_t language, bool is_trial) {
|
||||
@@ -253,8 +262,8 @@ string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> ma
|
||||
const auto& compressed = vm->compressed(is_trial);
|
||||
|
||||
StringWriter w;
|
||||
uint32_t subcommand_size = (compressed.size() + sizeof(G_MapData_GC_Ep3_6xB6x41) + 3) & (~3);
|
||||
w.put<G_MapData_GC_Ep3_6xB6x41>({{{{0xB6, 0, 0}, subcommand_size}, 0x41, {}}, vm->map->map_number.load(), compressed.size(), 0});
|
||||
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.write(compressed);
|
||||
return std::move(w.str());
|
||||
}
|
||||
@@ -271,8 +280,7 @@ void Server::send_commands_for_joining_spectator(Channel& ch) const {
|
||||
}
|
||||
|
||||
if (this->last_chosen_map) {
|
||||
string data = this->prepare_6xB6x41_map_definition(
|
||||
this->last_chosen_map, ch.language, (ch.version == Version::GC_EP3_NTE));
|
||||
string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, ch.language, this->options.is_trial());
|
||||
this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(ch.language), this->last_chosen_map->map_number);
|
||||
ch.send(0x6C, 0x00, data);
|
||||
}
|
||||
@@ -286,10 +294,14 @@ void Server::send_commands_for_joining_spectator(Channel& ch) const {
|
||||
ch.send(0xC9, 0x00, ps->prepare_6xB4x04());
|
||||
}
|
||||
}
|
||||
{
|
||||
G_UpdateMap_GC_Ep3_6xB4x05 cmd_05;
|
||||
cmd_05.state = *this->map_and_rules;
|
||||
ch.send(0xC9, 0x00, cmd_05);
|
||||
if (ch.version == Version::GC_EP3_NTE) {
|
||||
G_UpdateMap_Ep3NTE_6xB4x05 cmd;
|
||||
cmd.state = *this->map_and_rules;
|
||||
ch.send(0xC9, 0x00, cmd);
|
||||
} else {
|
||||
G_UpdateMap_Ep3_6xB4x05 cmd;
|
||||
cmd.state = *this->map_and_rules;
|
||||
ch.send(0xC9, 0x00, cmd);
|
||||
}
|
||||
// TODO: Sega does something like this; do we have to do this too?
|
||||
// for (uint8_t client_id = 0; client_id < 4; client_id++) {
|
||||
@@ -302,7 +314,7 @@ void Server::send_commands_for_joining_spectator(Channel& ch) const {
|
||||
ch.send(0xC9, 0x00, this->prepare_6xB4x1C_names_update());
|
||||
ch.send(0xC9, 0x00, this->prepare_6xB4x50_trap_tile_locations());
|
||||
{
|
||||
G_LoadCurrentEnvironment_GC_Ep3_6xB4x3B cmd_3B;
|
||||
G_LoadCurrentEnvironment_Ep3_6xB4x3B cmd_3B;
|
||||
ch.send(0xC9, 0x00, &cmd_3B, sizeof(cmd_3B));
|
||||
}
|
||||
}
|
||||
@@ -740,7 +752,7 @@ void Server::dice_phase_after() {
|
||||
size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(client_id);
|
||||
for (size_t z = 0; z < num_assists; z++) {
|
||||
auto eff = this->assist_server->get_active_assist_by_index(z);
|
||||
if ((eff == AssistEffect::CHARITY_PLUS) || (eff == AssistEffect::CHARITY)) {
|
||||
if ((eff == AssistEffect::CHARITY) || (!this->options.is_trial() && (eff == AssistEffect::CHARITY_PLUS))) {
|
||||
int16_t exp_delta = (eff == AssistEffect::CHARITY_PLUS) ? -1 : 1;
|
||||
for (size_t other_client_id = 0; other_client_id < 4; other_client_id++) {
|
||||
auto other_ps = this->player_states[other_client_id];
|
||||
@@ -798,7 +810,8 @@ void Server::draw_phase_after() {
|
||||
}
|
||||
}
|
||||
|
||||
if (this->overall_time_expired || (this->round_num >= 1000)) {
|
||||
// Apparently the hard limit of 1000 was added after NTE was released
|
||||
if (this->overall_time_expired || (!this->options.is_trial() && (this->round_num >= 1000))) {
|
||||
bool no_winner_specified = true;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
auto ps = this->player_states[z];
|
||||
@@ -808,7 +821,12 @@ void Server::draw_phase_after() {
|
||||
}
|
||||
}
|
||||
if (no_winner_specified) {
|
||||
this->compute_losing_team_id_and_add_winner_flags(0);
|
||||
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);
|
||||
}
|
||||
}
|
||||
this->round_num--;
|
||||
this->set_battle_ended();
|
||||
@@ -1009,106 +1027,108 @@ bool Server::is_registration_complete() const {
|
||||
}
|
||||
|
||||
void Server::move_phase_after() {
|
||||
for (size_t trap_type = 0; trap_type < 5; trap_type++) {
|
||||
uint8_t trap_tile_index = this->chosen_trap_tile_index_of_type[trap_type];
|
||||
if (trap_tile_index == 0xFF) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool should_trigger = false;
|
||||
int16_t trap_x = this->trap_tile_locs[trap_type][trap_tile_index][0];
|
||||
int16_t trap_y = this->trap_tile_locs[trap_type][trap_tile_index][1];
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
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)) {
|
||||
should_trigger = true;
|
||||
break;
|
||||
}
|
||||
if (!this->options.is_trial()) {
|
||||
for (size_t trap_type = 0; trap_type < 5; trap_type++) {
|
||||
uint8_t trap_tile_index = this->chosen_trap_tile_index_of_type[trap_type];
|
||||
if (trap_tile_index == 0xFF) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!should_trigger) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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
|
||||
vector<uint16_t>{0x0131, 0x012B, 0x0133},
|
||||
// Purple: Powerless Rain, Trash 1, Empty Hand, Skip Draw
|
||||
vector<uint16_t>{0x00FA, 0x0125, 0x0126, 0x0137},
|
||||
// Green: Brave Wind, Homesick, Fly
|
||||
vector<uint16_t>{0x00FB, 0x014E, 0x0107},
|
||||
// Yellow: Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix
|
||||
vector<uint16_t>{0x00F6, 0x0242, 0x014B, 0x0145, 0x012D}};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// This is the original implementation. We do something smarter instead.
|
||||
// uint16_t trap_card_id = 0;
|
||||
// while (trap_card_id == 0) {
|
||||
// trap_card_id = TRAP_CARD_IDS[trap_type][this->get_random(5)];
|
||||
// }
|
||||
uint16_t trap_card_id = 0xFFFF;
|
||||
if (trap_card_ids->size() == 1) {
|
||||
trap_card_id = trap_card_ids->at(0);
|
||||
} else if (trap_card_ids->size() > 1) {
|
||||
trap_card_id = trap_card_ids->at(this->get_random(trap_card_ids->size()));
|
||||
}
|
||||
|
||||
if (trap_card_id != 0xFFFF) {
|
||||
bool should_trigger = false;
|
||||
int16_t trap_x = this->trap_tile_locs[trap_type][trap_tile_index][0];
|
||||
int16_t trap_y = this->trap_tile_locs[trap_type][trap_tile_index][1];
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
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) &&
|
||||
ps->replace_assist_card_by_id(trap_card_id)) {
|
||||
G_Unknown_GC_Ep3_6xB4x2C cmd;
|
||||
cmd.change_type = 0x01;
|
||||
cmd.client_id = client_id;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
cmd.loc.x = trap_x;
|
||||
cmd.loc.y = trap_y;
|
||||
cmd.loc.direction = static_cast<Direction>(trap_type);
|
||||
cmd.unknown_a2[0] = trap_card_id;
|
||||
cmd.unknown_a2[1] = 0xFFFFFFFF;
|
||||
this->send(cmd);
|
||||
if (sc_card && (sc_card->card_flags & 0x80) &&
|
||||
(sc_card->loc.x == trap_x) && (sc_card->loc.y == trap_y)) {
|
||||
should_trigger = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: This is the original implementation:
|
||||
// if (this->num_trap_tiles_of_type[trap_type] > 1) {
|
||||
// uint8_t new_index = this->chosen_trap_tile_index_of_type[trap_type];
|
||||
// while (new_index == this->chosen_trap_tile_index_of_type[trap_type]) {
|
||||
// new_index = this->get_random(this->num_trap_tiles_of_type[trap_type]);
|
||||
// }
|
||||
// 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.
|
||||
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
|
||||
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++;
|
||||
if (!should_trigger) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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
|
||||
vector<uint16_t>{0x0131, 0x012B, 0x0133},
|
||||
// Purple: Powerless Rain, Trash 1, Empty Hand, Skip Draw
|
||||
vector<uint16_t>{0x00FA, 0x0125, 0x0126, 0x0137},
|
||||
// Green: Brave Wind, Homesick, Fly
|
||||
vector<uint16_t>{0x00FB, 0x014E, 0x0107},
|
||||
// Yellow: Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix
|
||||
vector<uint16_t>{0x00F6, 0x0242, 0x014B, 0x0145, 0x012D}};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// This is the original implementation. We do something smarter instead.
|
||||
// uint16_t trap_card_id = 0;
|
||||
// while (trap_card_id == 0) {
|
||||
// trap_card_id = TRAP_CARD_IDS[trap_type][this->get_random(5)];
|
||||
// }
|
||||
uint16_t trap_card_id = 0xFFFF;
|
||||
if (trap_card_ids->size() == 1) {
|
||||
trap_card_id = trap_card_ids->at(0);
|
||||
} else if (trap_card_ids->size() > 1) {
|
||||
trap_card_id = trap_card_ids->at(this->get_random(trap_card_ids->size()));
|
||||
}
|
||||
|
||||
if (trap_card_id != 0xFFFF) {
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
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) &&
|
||||
ps->replace_assist_card_by_id(trap_card_id)) {
|
||||
G_Unknown_Ep3_6xB4x2C cmd;
|
||||
cmd.change_type = 0x01;
|
||||
cmd.client_id = client_id;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
cmd.loc.x = trap_x;
|
||||
cmd.loc.y = trap_y;
|
||||
cmd.loc.direction = static_cast<Direction>(trap_type);
|
||||
cmd.unknown_a2[0] = trap_card_id;
|
||||
cmd.unknown_a2[1] = 0xFFFFFFFF;
|
||||
this->send(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: This is the original implementation:
|
||||
// if (this->num_trap_tiles_of_type[trap_type] > 1) {
|
||||
// uint8_t new_index = this->chosen_trap_tile_index_of_type[trap_type];
|
||||
// while (new_index == this->chosen_trap_tile_index_of_type[trap_type]) {
|
||||
// new_index = this->get_random(this->num_trap_tiles_of_type[trap_type]);
|
||||
// }
|
||||
// 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.
|
||||
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
|
||||
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++;
|
||||
}
|
||||
this->chosen_trap_tile_index_of_type[trap_type] = new_index;
|
||||
this->send_6xB4x50_trap_tile_locations();
|
||||
}
|
||||
this->chosen_trap_tile_index_of_type[trap_type] = new_index;
|
||||
this->send_6xB4x50_trap_tile_locations();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1127,8 +1147,8 @@ void Server::action_phase_before() {
|
||||
}
|
||||
}
|
||||
|
||||
G_SetPlayerNames_GC_Ep3_6xB4x1C Server::prepare_6xB4x1C_names_update() const {
|
||||
G_SetPlayerNames_GC_Ep3_6xB4x1C cmd;
|
||||
G_SetPlayerNames_Ep3_6xB4x1C Server::prepare_6xB4x1C_names_update() const {
|
||||
G_SetPlayerNames_Ep3_6xB4x1C cmd;
|
||||
cmd.entries = this->name_entries;
|
||||
return cmd;
|
||||
}
|
||||
@@ -1138,7 +1158,7 @@ void Server::send_6xB4x1C_names_update() {
|
||||
}
|
||||
|
||||
int8_t Server::send_6xB4x33_remove_ally_atk_if_needed(const ActionState& pa) {
|
||||
G_SubtractAllyATKPoints_GC_Ep3_6xB4x33 cmd;
|
||||
G_SubtractAllyATKPoints_Ep3_6xB4x33 cmd;
|
||||
|
||||
bool has_ally_cost = false;
|
||||
uint8_t ally_cost = 0;
|
||||
@@ -1194,8 +1214,8 @@ int8_t Server::send_6xB4x33_remove_ally_atk_if_needed(const ActionState& pa) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
G_UpdateDecks_GC_Ep3_6xB4x07 Server::prepare_6xB4x07_decks_update() const {
|
||||
G_UpdateDecks_GC_Ep3_6xB4x07 cmd07;
|
||||
G_UpdateDecks_Ep3_6xB4x07 Server::prepare_6xB4x07_decks_update() const {
|
||||
G_UpdateDecks_Ep3_6xB4x07 cmd07;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
if (!this->check_presence_entry(z)) {
|
||||
cmd07.entries_present[z] = 0;
|
||||
@@ -1212,9 +1232,15 @@ G_UpdateDecks_GC_Ep3_6xB4x07 Server::prepare_6xB4x07_decks_update() const {
|
||||
void Server::send_all_state_updates() {
|
||||
this->send(this->prepare_6xB4x07_decks_update());
|
||||
|
||||
G_UpdateMap_GC_Ep3_6xB4x05 cmd;
|
||||
cmd.state = *this->map_and_rules;
|
||||
this->send(cmd);
|
||||
if (this->options.is_trial()) {
|
||||
G_UpdateMap_Ep3NTE_6xB4x05 cmd;
|
||||
cmd.state = *this->map_and_rules;
|
||||
this->send(cmd);
|
||||
} else {
|
||||
G_UpdateMap_Ep3_6xB4x05 cmd;
|
||||
cmd.state = *this->map_and_rules;
|
||||
this->send(cmd);
|
||||
}
|
||||
|
||||
this->send_6xB4x02_for_all_players_if_needed();
|
||||
}
|
||||
@@ -1535,10 +1561,17 @@ void Server::setup_and_start_battle() {
|
||||
this->update_battle_state_flags_and_send_6xB4x03_if_needed(true);
|
||||
this->send_6xB4x50_trap_tile_locations();
|
||||
|
||||
G_UpdateMap_GC_Ep3_6xB4x05 cmd05;
|
||||
cmd05.state = *this->map_and_rules;
|
||||
cmd05.start_battle = 1;
|
||||
this->send(cmd05);
|
||||
if (this->options.is_trial()) {
|
||||
G_UpdateMap_Ep3NTE_6xB4x05 cmd;
|
||||
cmd.state = *this->map_and_rules;
|
||||
cmd.start_battle = 1;
|
||||
this->send(cmd);
|
||||
} else {
|
||||
G_UpdateMap_Ep3_6xB4x05 cmd;
|
||||
cmd.state = *this->map_and_rules;
|
||||
cmd.start_battle = 1;
|
||||
this->send(cmd);
|
||||
}
|
||||
|
||||
this->battle_start_usecs = now();
|
||||
|
||||
@@ -1552,8 +1585,8 @@ void Server::setup_and_start_battle() {
|
||||
}
|
||||
}
|
||||
|
||||
G_SetStateFlags_GC_Ep3_6xB4x03 Server::prepare_6xB4x03() const {
|
||||
G_SetStateFlags_GC_Ep3_6xB4x03 cmd;
|
||||
G_SetStateFlags_Ep3_6xB4x03 Server::prepare_6xB4x03() const {
|
||||
G_SetStateFlags_Ep3_6xB4x03 cmd;
|
||||
cmd.state.turn_num = this->round_num;
|
||||
cmd.state.battle_phase = this->battle_phase;
|
||||
cmd.state.current_team_turn1 = this->current_team_turn1;
|
||||
@@ -1579,7 +1612,7 @@ G_SetStateFlags_GC_Ep3_6xB4x03 Server::prepare_6xB4x03() const {
|
||||
}
|
||||
|
||||
void Server::update_battle_state_flags_and_send_6xB4x03_if_needed(bool always_send) {
|
||||
G_SetStateFlags_GC_Ep3_6xB4x03 cmd = this->prepare_6xB4x03();
|
||||
G_SetStateFlags_Ep3_6xB4x03 cmd = this->prepare_6xB4x03();
|
||||
if (always_send || (*this->state_flags != cmd.state)) {
|
||||
*this->state_flags = cmd.state;
|
||||
this->send(cmd);
|
||||
@@ -1672,7 +1705,7 @@ void Server::on_server_data_input(shared_ptr<Client> sender_c, const string& dat
|
||||
}
|
||||
|
||||
void Server::handle_CAx0B_mulligan_hand(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_RedrawInitialHand_GC_Ep3_6xB3x0B_CAx0B>(data);
|
||||
const auto& in_cmd = check_size_t<G_RedrawInitialHand_Ep3_6xB3x0B_CAx0B>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "REDRAW");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
@@ -1695,16 +1728,17 @@ void Server::handle_CAx0B_mulligan_hand(shared_ptr<Client>, const string& data)
|
||||
}
|
||||
}
|
||||
|
||||
G_ActionResult_GC_Ep3_6xB4x1E out_cmd;
|
||||
out_cmd.sequence_num = in_cmd.header.sequence_num.load();
|
||||
out_cmd.error_code = error_code;
|
||||
this->send(out_cmd);
|
||||
|
||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, out_cmd.error_code);
|
||||
if (!this->options.is_trial() || (error_code == 0)) {
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd;
|
||||
out_cmd.sequence_num = in_cmd.header.sequence_num.load();
|
||||
out_cmd.error_code = error_code;
|
||||
this->send(out_cmd);
|
||||
}
|
||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code);
|
||||
}
|
||||
|
||||
void Server::handle_CAx0C_end_mulligan_phase(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndInitialRedrawPhase_GC_Ep3_6xB3x0C_CAx0C>(data);
|
||||
const auto& in_cmd = check_size_t<G_EndInitialRedrawPhase_Ep3_6xB3x0C_CAx0C>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "SETUP ADV 2");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
@@ -1721,7 +1755,11 @@ void Server::handle_CAx0C_end_mulligan_phase(shared_ptr<Client>, const string& d
|
||||
error_code = -0x78;
|
||||
}
|
||||
|
||||
G_ActionResult_GC_Ep3_6xB4x1E out_cmd_ack;
|
||||
if (this->options.is_trial() && error_code) {
|
||||
return;
|
||||
}
|
||||
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd_ack;
|
||||
out_cmd_ack.sequence_num = in_cmd.header.sequence_num;
|
||||
out_cmd_ack.response_phase = 1;
|
||||
this->send(out_cmd_ack);
|
||||
@@ -1748,7 +1786,7 @@ void Server::handle_CAx0C_end_mulligan_phase(shared_ptr<Client>, const string& d
|
||||
}
|
||||
}
|
||||
|
||||
G_ActionResult_GC_Ep3_6xB4x1E out_cmd_fin;
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd_fin;
|
||||
out_cmd_fin.sequence_num = in_cmd.header.sequence_num;
|
||||
out_cmd_fin.response_phase = 2;
|
||||
out_cmd_fin.error_code = error_code;
|
||||
@@ -1758,28 +1796,27 @@ void Server::handle_CAx0C_end_mulligan_phase(shared_ptr<Client>, const string& d
|
||||
}
|
||||
|
||||
void Server::handle_CAx0D_end_non_action_phase(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndNonAttackPhase_GC_Ep3_6xB3x0D_CAx0D>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "END PHASE");
|
||||
const auto& in_cmd = check_size_t<G_EndNonAttackPhase_Ep3_6xB3x0D_CAx0D>(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");
|
||||
}
|
||||
|
||||
G_ActionResult_GC_Ep3_6xB4x1E out_cmd_ack;
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd_ack;
|
||||
out_cmd_ack.sequence_num = in_cmd.header.sequence_num;
|
||||
out_cmd_ack.response_phase = 1;
|
||||
this->send(out_cmd_ack);
|
||||
|
||||
this->set_client_id_ready_to_advance_phase(in_cmd.client_id);
|
||||
|
||||
G_ActionResult_GC_Ep3_6xB4x1E out_cmd_fin;
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd_fin;
|
||||
out_cmd_fin.sequence_num = in_cmd.header.sequence_num;
|
||||
out_cmd_fin.response_phase = 2;
|
||||
this->send(out_cmd_fin);
|
||||
}
|
||||
|
||||
void Server::handle_CAx0E_discard_card_from_hand(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_DiscardCardFromHand_GC_Ep3_6xB3x0E_CAx0E>(data);
|
||||
const auto& in_cmd = check_size_t<G_DiscardCardFromHand_Ep3_6xB3x0E_CAx0E>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "DISCARD");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
@@ -1809,18 +1846,18 @@ void Server::handle_CAx0E_discard_card_from_hand(shared_ptr<Client>, const strin
|
||||
}
|
||||
}
|
||||
|
||||
G_ActionResult_GC_Ep3_6xB4x1E out_cmd;
|
||||
out_cmd.sequence_num = in_cmd.header.sequence_num;
|
||||
out_cmd.error_code = error_code;
|
||||
this->send(out_cmd);
|
||||
|
||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, out_cmd.error_code);
|
||||
if (!this->options.is_trial() || (error_code == 0)) {
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd;
|
||||
out_cmd.sequence_num = in_cmd.header.sequence_num;
|
||||
out_cmd.error_code = error_code;
|
||||
this->send(out_cmd);
|
||||
}
|
||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code);
|
||||
}
|
||||
|
||||
void Server::handle_CAx0F_set_card_from_hand(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_SetCardFromHand_GC_Ep3_6xB3x0F_CAx0F>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "SET FC");
|
||||
const auto& in_cmd = check_size_t<G_SetCardFromHand_Ep3_6xB3x0F_CAx0F>(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");
|
||||
}
|
||||
@@ -1836,6 +1873,7 @@ void Server::handle_CAx0F_set_card_from_hand(shared_ptr<Client>, const string& d
|
||||
error_code = -0x78;
|
||||
}
|
||||
|
||||
bool was_set = false;
|
||||
if (in_cmd.client_id >= 4) {
|
||||
error_code = -0x78;
|
||||
}
|
||||
@@ -1845,22 +1883,22 @@ void Server::handle_CAx0F_set_card_from_hand(shared_ptr<Client>, const string& d
|
||||
if (!ps) {
|
||||
this->ruler_server->error_code1 = -0x72;
|
||||
} else {
|
||||
ps->set_card_from_hand(in_cmd.card_ref, in_cmd.set_index, &in_cmd.loc, in_cmd.assist_target_player, 0);
|
||||
was_set = 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;
|
||||
}
|
||||
|
||||
G_ActionResult_GC_Ep3_6xB4x1E out_cmd;
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd;
|
||||
out_cmd.sequence_num = in_cmd.header.sequence_num;
|
||||
out_cmd.error_code = this->ruler_server->error_code1;
|
||||
out_cmd.error_code = this->options.is_trial() ? (was_set ? 0 : 1) : this->ruler_server->error_code1;
|
||||
this->send(out_cmd);
|
||||
|
||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, out_cmd.error_code);
|
||||
}
|
||||
|
||||
void Server::handle_CAx10_move_fc_to_location(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_MoveFieldCharacter_GC_Ep3_6xB3x10_CAx10>(data);
|
||||
const auto& in_cmd = check_size_t<G_MoveFieldCharacter_Ep3_6xB3x10_CAx10>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "MOVE");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
@@ -1889,7 +1927,7 @@ void Server::handle_CAx10_move_fc_to_location(shared_ptr<Client>, const string&
|
||||
this->ruler_server->error_code2 = error_code;
|
||||
}
|
||||
|
||||
G_ActionResult_GC_Ep3_6xB4x1E out_cmd;
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd;
|
||||
out_cmd.sequence_num = in_cmd.header.sequence_num;
|
||||
out_cmd.error_code = this->ruler_server->error_code2;
|
||||
this->send(out_cmd);
|
||||
@@ -1898,7 +1936,7 @@ void Server::handle_CAx10_move_fc_to_location(shared_ptr<Client>, const string&
|
||||
}
|
||||
|
||||
void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EnqueueAttackOrDefense_GC_Ep3_6xB3x11_CAx11>(data);
|
||||
const auto& in_cmd = check_size_t<G_EnqueueAttackOrDefense_Ep3_6xB3x11_CAx11>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "ENQUEUE ACT");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
@@ -1916,7 +1954,7 @@ void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr<Client>, const st
|
||||
this->ruler_server->error_code3 = 0;
|
||||
ActionState pa = in_cmd.entry;
|
||||
if (this->enqueue_attack_or_defense(in_cmd.client_id, &pa)) {
|
||||
G_SetActionState_GC_Ep3_6xB4x09 out_cmd;
|
||||
G_SetActionState_Ep3_6xB4x09 out_cmd;
|
||||
out_cmd.client_id = in_cmd.client_id;
|
||||
out_cmd.state = in_cmd.entry;
|
||||
this->send(out_cmd);
|
||||
@@ -1925,7 +1963,7 @@ void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr<Client>, const st
|
||||
this->ruler_server->error_code3 = error_code;
|
||||
}
|
||||
|
||||
G_ActionResult_GC_Ep3_6xB4x1E out_cmd;
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd;
|
||||
out_cmd.sequence_num = in_cmd.header.sequence_num;
|
||||
out_cmd.error_code = this->ruler_server->error_code3;
|
||||
this->send(out_cmd);
|
||||
@@ -1934,7 +1972,7 @@ void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr<Client>, const st
|
||||
}
|
||||
|
||||
void Server::handle_CAx12_end_attack_list(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndAttackList_GC_Ep3_6xB3x12_CAx12>(data);
|
||||
const auto& in_cmd = check_size_t<G_EndAttackList_Ep3_6xB3x12_CAx12>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "END ATK LIST");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
@@ -1949,7 +1987,7 @@ void Server::handle_CAx12_end_attack_list(shared_ptr<Client>, const string& data
|
||||
this->end_attack_list_for_client(in_cmd.client_id);
|
||||
}
|
||||
|
||||
G_ActionResult_GC_Ep3_6xB4x1E out_cmd;
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd;
|
||||
out_cmd.sequence_num = in_cmd.header.sequence_num;
|
||||
this->send(out_cmd);
|
||||
|
||||
@@ -1957,7 +1995,7 @@ void Server::handle_CAx12_end_attack_list(shared_ptr<Client>, const string& data
|
||||
}
|
||||
|
||||
void Server::handle_CAx13_update_map_during_setup(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_SetMapState_GC_Ep3_6xB3x13_CAx13>(data);
|
||||
const auto& in_cmd = check_size_t<G_SetMapState_Ep3_6xB3x13_CAx13>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.header.subsubcommand, "UPDATE MAP");
|
||||
|
||||
@@ -1999,7 +2037,7 @@ void Server::handle_CAx13_update_map_during_setup(shared_ptr<Client>, const stri
|
||||
}
|
||||
|
||||
void Server::handle_CAx14_update_deck_during_setup(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_SetPlayerDeck_GC_Ep3_6xB3x14_CAx14>(data);
|
||||
const auto& in_cmd = check_size_t<G_SetPlayerDeck_Ep3_6xB3x14_CAx14>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "UPDATE DECK");
|
||||
|
||||
@@ -2046,7 +2084,7 @@ void Server::handle_CAx14_update_deck_during_setup(shared_ptr<Client>, const str
|
||||
}
|
||||
|
||||
void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_HardResetServerState_GC_Ep3_6xB3x15_CAx15>(data);
|
||||
const auto& in_cmd = check_size_t<G_HardResetServerState_Ep3_6xB3x15_CAx15>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.header.subsubcommand, "HARD RESET");
|
||||
|
||||
@@ -2064,7 +2102,7 @@ void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr<Client>, con
|
||||
}
|
||||
|
||||
void Server::handle_CAx1B_update_player_name(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_SetPlayerName_GC_Ep3_6xB3x1B_CAx1B>(data);
|
||||
const auto& in_cmd = check_size_t<G_SetPlayerName_Ep3_6xB3x1B_CAx1B>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.entry.client_id, in_cmd.header.subsubcommand, "UPDATE NAME");
|
||||
|
||||
@@ -2088,7 +2126,7 @@ void Server::handle_CAx1B_update_player_name(shared_ptr<Client>, const string& d
|
||||
}
|
||||
}
|
||||
|
||||
G_SetPlayerNames_GC_Ep3_6xB4x1C out_cmd;
|
||||
G_SetPlayerNames_Ep3_6xB4x1C out_cmd;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
out_cmd.entries[z] = this->name_entries[z];
|
||||
}
|
||||
@@ -2096,13 +2134,13 @@ void Server::handle_CAx1B_update_player_name(shared_ptr<Client>, const string& d
|
||||
}
|
||||
|
||||
void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_StartBattle_GC_Ep3_6xB3x1D_CAx1D>(data);
|
||||
const auto& in_cmd = check_size_t<G_StartBattle_Ep3_6xB3x1D_CAx1D>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.header.subsubcommand, "START BATTLE");
|
||||
|
||||
if (!this->battle_in_progress) {
|
||||
if (!this->update_registration_phase()) {
|
||||
G_RejectBattleStartRequest_GC_Ep3_6xB4x53 out_cmd;
|
||||
G_RejectBattleStartRequest_Ep3_6xB4x53 out_cmd;
|
||||
out_cmd.setup_phase = this->setup_phase;
|
||||
out_cmd.registration_phase = this->registration_phase;
|
||||
out_cmd.state = *this->map_and_rules;
|
||||
@@ -2121,7 +2159,7 @@ void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
|
||||
}
|
||||
// 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 (l->ep3_ex_result_values) {
|
||||
if ((l->base_version != Version::GC_EP3_NTE) && l->ep3_ex_result_values) {
|
||||
this->send(*l->ep3_ex_result_values);
|
||||
}
|
||||
}
|
||||
@@ -2133,7 +2171,7 @@ void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
|
||||
}
|
||||
|
||||
void Server::handle_CAx21_end_battle(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndBattle_GC_Ep3_6xB3x21_CAx21>(data);
|
||||
const auto& in_cmd = check_size_t<G_EndBattle_Ep3_6xB3x21_CAx21>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.header.subsubcommand, "END BATTLE");
|
||||
if (this->setup_phase == SetupPhase::BATTLE_ENDED) {
|
||||
@@ -2148,14 +2186,14 @@ void Server::handle_CAx21_end_battle(shared_ptr<Client>, const string& data) {
|
||||
}
|
||||
|
||||
void Server::handle_CAx28_end_defense_list(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndDefenseList_GC_Ep3_6xB3x28_CAx28>(data);
|
||||
const auto& in_cmd = check_size_t<G_EndDefenseList_Ep3_6xB3x28_CAx28>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "END DEF LIST");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
}
|
||||
|
||||
G_ActionResult_GC_Ep3_6xB4x1E out_cmd_ack;
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd_ack;
|
||||
out_cmd_ack.sequence_num = in_cmd.header.sequence_num;
|
||||
out_cmd_ack.response_phase = 1;
|
||||
this->send(out_cmd_ack);
|
||||
@@ -2194,20 +2232,20 @@ void Server::handle_CAx28_end_defense_list(shared_ptr<Client>, const string& dat
|
||||
this->unknown_a10 = 0;
|
||||
}
|
||||
|
||||
G_ActionResult_GC_Ep3_6xB4x1E out_cmd_fin;
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd_fin;
|
||||
out_cmd_fin.sequence_num = in_cmd.header.sequence_num;
|
||||
out_cmd_fin.response_phase = 2;
|
||||
this->send(out_cmd_fin);
|
||||
}
|
||||
|
||||
void Server::handle_CAx2B_legacy_set_card(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_ExecLegacyCard_GC_Ep3_6xB3x2B_CAx2B>(data);
|
||||
const auto& in_cmd = check_size_t<G_ExecLegacyCard_Ep3_6xB3x2B_CAx2B>(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<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_PhotonBlastRequest_GC_Ep3_6xB3x34_CAx34>(data);
|
||||
const auto& in_cmd = check_size_t<G_PhotonBlastRequest_Ep3_6xB3x34_CAx34>(data);
|
||||
|
||||
uint8_t card_ref_client_id = client_id_for_card_ref(in_cmd.card_ref);
|
||||
this->send_debug_command_received_message(
|
||||
@@ -2237,7 +2275,7 @@ void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr<Client>, const str
|
||||
}
|
||||
}
|
||||
if (accepted) {
|
||||
G_PhotonBlastStatus_GC_Ep3_6xB4x35 out_cmd;
|
||||
G_PhotonBlastStatus_Ep3_6xB4x35 out_cmd;
|
||||
out_cmd.accepted = 0;
|
||||
out_cmd.card_ref = in_cmd.card_ref;
|
||||
out_cmd.client_id = card_ref_client_id;
|
||||
@@ -2271,7 +2309,7 @@ void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr<Client>, const str
|
||||
}
|
||||
this->has_done_pb[card_ref_client_id] = false;
|
||||
|
||||
G_PhotonBlastStatus_GC_Ep3_6xB4x35 out_cmd;
|
||||
G_PhotonBlastStatus_Ep3_6xB4x35 out_cmd;
|
||||
out_cmd.client_id = card_ref_client_id;
|
||||
out_cmd.accepted = 1;
|
||||
out_cmd.card_ref = in_cmd.card_ref;
|
||||
@@ -2284,7 +2322,7 @@ void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr<Client>, const str
|
||||
}
|
||||
|
||||
void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_AdvanceFromStartingRollsPhase_GC_Ep3_6xB3x37_CAx37>(data);
|
||||
const auto& in_cmd = check_size_t<G_AdvanceFromStartingRollsPhase_Ep3_6xB3x37_CAx37>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "SETUP ADV 1");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
@@ -2314,14 +2352,14 @@ 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_GC_Ep3_6xB3x3A_CAx3A>(data);
|
||||
const auto& in_cmd = check_size_t<G_OverallTimeLimitExpired_Ep3_6xB3x3A_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.
|
||||
}
|
||||
|
||||
void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_MapListRequest_GC_Ep3_6xB3x40_CAx40>(data);
|
||||
const auto& in_cmd = check_size_t<G_MapListRequest_Ep3_6xB3x40_CAx40>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.header.subsubcommand, "MAP LIST");
|
||||
|
||||
@@ -2335,9 +2373,8 @@ void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const st
|
||||
const auto& list_data = this->options.map_index->get_compressed_list(num_players, language);
|
||||
|
||||
StringWriter w;
|
||||
uint32_t subcommand_size = (list_data.size() + sizeof(G_MapList_GC_Ep3_6xB6x40) + 3) & (~3);
|
||||
w.put<G_MapList_GC_Ep3_6xB6x40>(
|
||||
G_MapList_GC_Ep3_6xB6x40{{{{0xB6, 0, 0}, subcommand_size}, 0x40, {}}, list_data.size(), 0});
|
||||
uint32_t subcommand_size = (list_data.size() + sizeof(G_MapList_Ep3_6xB6x40) + 3) & (~3);
|
||||
w.put<G_MapList_Ep3_6xB6x40>(G_MapList_Ep3_6xB6x40{{{{0xB6, 0, 0}, subcommand_size}, 0x40, {}}, list_data.size(), 0});
|
||||
w.write(list_data);
|
||||
while (w.size() & 3) {
|
||||
w.put_u8(0);
|
||||
@@ -2360,7 +2397,7 @@ void Server::send_6xB6x41_to_all_clients() const {
|
||||
}
|
||||
if (map_commands_by_language[c->language()].empty()) {
|
||||
map_commands_by_language[c->language()] = this->prepare_6xB6x41_map_definition(
|
||||
this->last_chosen_map, c->language(), (l->base_version == Version::GC_EP3_NTE));
|
||||
this->last_chosen_map, c->language(), this->options.is_trial());
|
||||
}
|
||||
this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(c->language()), this->last_chosen_map->map_number);
|
||||
send_command(c, 0x6C, 0x00, map_commands_by_language[c->language()]);
|
||||
@@ -2394,7 +2431,7 @@ void Server::send_6xB6x41_to_all_clients() const {
|
||||
}
|
||||
|
||||
void Server::handle_CAx41_map_request(shared_ptr<Client>, const string& data) {
|
||||
const auto& cmd = check_size_t<G_MapDataRequest_GC_Ep3_6xB3x41_CAx41>(data);
|
||||
const auto& cmd = check_size_t<G_MapDataRequest_Ep3_6xB3x41_CAx41>(data);
|
||||
this->send_debug_command_received_message(
|
||||
cmd.header.subsubcommand, "MAP DATA");
|
||||
|
||||
@@ -2403,7 +2440,7 @@ void Server::handle_CAx41_map_request(shared_ptr<Client>, const string& data) {
|
||||
}
|
||||
|
||||
void Server::handle_CAx48_end_turn(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndTurn_GC_Ep3_6xB3x48_CAx48>(data);
|
||||
const auto& in_cmd = check_size_t<G_EndTurn_Ep3_6xB3x48_CAx48>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.client_id, in_cmd.header.subsubcommand, "END TURN");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
@@ -2415,13 +2452,13 @@ void Server::handle_CAx48_end_turn(shared_ptr<Client>, const string& data) {
|
||||
ps->draw_hand(0);
|
||||
}
|
||||
|
||||
G_ActionResult_GC_Ep3_6xB4x1E out_cmd;
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd;
|
||||
out_cmd.sequence_num = in_cmd.header.sequence_num;
|
||||
this->send(out_cmd);
|
||||
}
|
||||
|
||||
void Server::handle_CAx49_card_counts(shared_ptr<Client>, const string& data) {
|
||||
const auto& in_cmd = check_size_t<G_CardCounts_GC_Ep3_6xB3x49_CAx49>(data);
|
||||
const auto& in_cmd = check_size_t<G_CardCounts_Ep3_6xB3x49_CAx49>(data);
|
||||
this->send_debug_command_received_message(
|
||||
in_cmd.header.sender_client_id, in_cmd.header.subsubcommand, "CARD COUNTS");
|
||||
|
||||
@@ -2601,7 +2638,7 @@ void Server::unknown_8023EEF4() {
|
||||
log.debug("a14 (%" PRIu32 ") < num_pending_attacks_with_cards (%" PRIu32 ")", this->unknown_a14, this->num_pending_attacks_with_cards);
|
||||
this->defense_list_ended_for_client.clear(false);
|
||||
|
||||
G_SetActionState_GC_Ep3_6xB4x29 cmd;
|
||||
G_SetActionState_Ep3_6xB4x29 cmd;
|
||||
cmd.unknown_a1 = this->unknown_a14;
|
||||
cmd.state = this->pending_attacks_with_cards[this->unknown_a14];
|
||||
this->replace_targets_due_to_destruction_or_conditions(&cmd.state);
|
||||
@@ -2877,20 +2914,36 @@ vector<shared_ptr<Card>> Server::const_cast_set_cards_v(
|
||||
}
|
||||
|
||||
void Server::send_6xB4x39() const {
|
||||
G_UpdateAllPlayerStatistics_GC_Ep3_6xB4x39 cmd;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
if (this->player_states[z]) {
|
||||
cmd.stats[z] = this->player_states[z]->stats;
|
||||
if (this->options.is_trial()) {
|
||||
G_UpdateAllPlayerStatistics_Ep3NTE_6xB4x39 cmd;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
if (this->player_states[z]) {
|
||||
cmd.stats[z] = this->player_states[z]->stats;
|
||||
}
|
||||
}
|
||||
this->send(cmd);
|
||||
} else {
|
||||
G_UpdateAllPlayerStatistics_Ep3_6xB4x39 cmd;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
if (this->player_states[z]) {
|
||||
cmd.stats[z] = this->player_states[z]->stats;
|
||||
}
|
||||
}
|
||||
this->send(cmd);
|
||||
}
|
||||
this->send(cmd);
|
||||
}
|
||||
|
||||
void Server::send_6xB4x05() {
|
||||
this->compute_all_map_occupied_bits();
|
||||
G_UpdateMap_GC_Ep3_6xB4x05 cmd;
|
||||
cmd.state = *this->map_and_rules;
|
||||
this->send(cmd);
|
||||
if (this->options.is_trial()) {
|
||||
G_UpdateMap_Ep3NTE_6xB4x05 cmd;
|
||||
cmd.state = *this->map_and_rules;
|
||||
this->send(cmd);
|
||||
} else {
|
||||
G_UpdateMap_Ep3_6xB4x05 cmd;
|
||||
cmd.state = *this->map_and_rules;
|
||||
this->send(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void Server::send_6xB4x02_for_all_players_if_needed(bool always_send) {
|
||||
@@ -2902,8 +2955,8 @@ void Server::send_6xB4x02_for_all_players_if_needed(bool always_send) {
|
||||
}
|
||||
}
|
||||
|
||||
G_SetTrapTileLocations_GC_Ep3_6xB4x50 Server::prepare_6xB4x50_trap_tile_locations() const {
|
||||
G_SetTrapTileLocations_GC_Ep3_6xB4x50 cmd;
|
||||
G_SetTrapTileLocations_Ep3_6xB4x50 Server::prepare_6xB4x50_trap_tile_locations() const {
|
||||
G_SetTrapTileLocations_Ep3_6xB4x50 cmd;
|
||||
for (size_t trap_type = 0; trap_type < 5; trap_type++) {
|
||||
uint8_t trap_index = this->chosen_trap_tile_index_of_type[trap_type];
|
||||
if (trap_index != 0xFF) {
|
||||
|
||||
+13
-13
@@ -74,6 +74,10 @@ public:
|
||||
std::shared_ptr<PSOLFGEncryption> random_crypt;
|
||||
std::shared_ptr<const Tournament> tournament;
|
||||
std::array<std::vector<uint16_t>, 5> trap_card_ids;
|
||||
|
||||
inline bool is_trial() const {
|
||||
return (this->behavior_flags & BehaviorFlag::IS_TRIAL_EDITION);
|
||||
}
|
||||
};
|
||||
Server(std::shared_ptr<Lobby> lobby, Options&& options);
|
||||
~Server() noexcept(false);
|
||||
@@ -102,7 +106,7 @@ public:
|
||||
if (cmd.header.size != sizeof(cmd) / 4) {
|
||||
throw std::logic_error("outbound command size field is incorrect");
|
||||
}
|
||||
if (cmd.header.subsubcommand == 0x06) {
|
||||
if (!this->options.is_trial() && (cmd.header.subsubcommand == 0x06)) {
|
||||
this->num_6xB4x06_commands_sent++;
|
||||
this->prev_num_6xB4x06_commands_sent = this->num_6xB4x06_commands_sent;
|
||||
if (this->num_6xB4x06_commands_sent > 0x100) {
|
||||
@@ -179,9 +183,8 @@ public:
|
||||
void move_phase_before();
|
||||
void set_player_deck_valid(uint8_t client_id);
|
||||
void setup_and_start_battle();
|
||||
G_SetStateFlags_GC_Ep3_6xB4x03 prepare_6xB4x03() const;
|
||||
void update_battle_state_flags_and_send_6xB4x03_if_needed(
|
||||
bool always_send = false);
|
||||
G_SetStateFlags_Ep3_6xB4x03 prepare_6xB4x03() const;
|
||||
void update_battle_state_flags_and_send_6xB4x03_if_needed(bool always_send = false);
|
||||
bool update_registration_phase();
|
||||
void on_server_data_input(std::shared_ptr<Client> sender_c, const std::string& data);
|
||||
void handle_CAx0B_mulligan_hand(std::shared_ptr<Client> sender_c, const std::string& data);
|
||||
@@ -209,12 +212,10 @@ public:
|
||||
void handle_CAx49_card_counts(std::shared_ptr<Client> sender_c, const std::string& data);
|
||||
void compute_losing_team_id_and_add_winner_flags(uint32_t flags);
|
||||
uint32_t get_team_exp(uint8_t team_id) const;
|
||||
uint32_t send_6xB4x06_if_card_ref_invalid(
|
||||
uint16_t card_ref, int16_t negative_value);
|
||||
uint32_t send_6xB4x06_if_card_ref_invalid(uint16_t card_ref, int16_t negative_value);
|
||||
void unknown_8023EEF4();
|
||||
void execute_bomb_assist_effect();
|
||||
void replace_targets_due_to_destruction_or_conditions(
|
||||
ActionState* as);
|
||||
void replace_targets_due_to_destruction_or_conditions(ActionState* as);
|
||||
bool any_target_exists_for_attack(const ActionState& as);
|
||||
uint8_t get_current_team_turn2() const;
|
||||
void unknown_8023EE48();
|
||||
@@ -225,12 +226,11 @@ public:
|
||||
void send_6xB4x02_for_all_players_if_needed(bool always_send = false);
|
||||
void send_6xB4x50_trap_tile_locations() const;
|
||||
|
||||
G_UpdateDecks_GC_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const;
|
||||
G_SetPlayerNames_GC_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const;
|
||||
static std::string prepare_6xB6x41_map_definition(
|
||||
std::shared_ptr<const MapIndex::Map> map, uint8_t language, bool is_trial);
|
||||
G_UpdateDecks_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const;
|
||||
G_SetPlayerNames_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const;
|
||||
static std::string prepare_6xB6x41_map_definition(std::shared_ptr<const MapIndex::Map> map, uint8_t language, bool is_trial);
|
||||
void send_6xB6x41_to_all_clients() const;
|
||||
G_SetTrapTileLocations_GC_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const;
|
||||
G_SetTrapTileLocations_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const;
|
||||
|
||||
std::vector<std::shared_ptr<Card>> const_cast_set_cards_v(
|
||||
const std::vector<std::shared_ptr<const Card>>& cards);
|
||||
|
||||
@@ -438,6 +438,11 @@ void Lobby::create_ep3_server() {
|
||||
.tournament = tourn,
|
||||
.trap_card_ids = s->ep3_trap_card_ids,
|
||||
};
|
||||
if (this->base_version == Version::GC_EP3_NTE) {
|
||||
options.behavior_flags |= Episode3::BehaviorFlag::IS_TRIAL_EDITION;
|
||||
} else {
|
||||
options.behavior_flags &= (~Episode3::BehaviorFlag::IS_TRIAL_EDITION);
|
||||
}
|
||||
this->ep3_server = make_shared<Episode3::Server>(this->shared_from_this(), std::move(options));
|
||||
this->ep3_server->init();
|
||||
}
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
std::shared_ptr<Episode3::BattleRecord> battle_record; // Not used in watcher games
|
||||
std::shared_ptr<Episode3::BattleRecordPlayer> battle_player; // Only used in replay games
|
||||
std::shared_ptr<Episode3::Tournament::Match> tournament_match;
|
||||
std::shared_ptr<const G_SetEXResultValues_GC_Ep3_6xB4x4B> ep3_ex_result_values;
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_ex_result_values;
|
||||
|
||||
// Lobby stuff
|
||||
uint8_t event;
|
||||
|
||||
+30
-21
@@ -838,7 +838,7 @@ constexpr on_command_t S_P_C4 = &S_C4<S_ChoiceSearchResultEntry_PC_C4>;
|
||||
constexpr on_command_t S_B_C4 = &S_C4<S_ChoiceSearchResultEntry_BB_C4>;
|
||||
|
||||
static HandlerResult S_G_E4(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
|
||||
auto& cmd = check_size_t<S_CardBattleTableState_GC_Ep3_E4>(data);
|
||||
auto& cmd = check_size_t<S_CardBattleTableState_Ep3_E4>(data);
|
||||
bool modified = false;
|
||||
for (size_t x = 0; x < 4; x++) {
|
||||
if (cmd.entries[x].guild_card_number == ses->remote_guild_card_number) {
|
||||
@@ -960,9 +960,9 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||
if (is_ep3(ses->version()) && (data.size() >= 0x14)) {
|
||||
if (static_cast<uint8_t>(data[0]) == 0xB6) {
|
||||
const auto& header = check_size_t<G_MapSubsubcommand_GC_Ep3_6xB6>(data, 0xFFFF);
|
||||
const auto& header = check_size_t<G_MapSubsubcommand_Ep3_6xB6>(data, 0xFFFF);
|
||||
if (header.subsubcommand == 0x00000041) {
|
||||
const auto& cmd = check_size_t<G_MapData_GC_Ep3_6xB6x41>(data, 0xFFFF);
|
||||
const auto& cmd = check_size_t<G_MapData_Ep3_6xB6x41>(data, 0xFFFF);
|
||||
string filename = string_printf("map%08" PRIX32 ".%" PRIu64 ".mnmd",
|
||||
cmd.map_number.load(), now());
|
||||
string map_data = prs_decompress(
|
||||
@@ -994,18 +994,27 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
|
||||
if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_TIME_ENABLED) && (header.subcommand == 0xB4)) {
|
||||
if (header.subsubcommand == 0x3D) {
|
||||
auto& cmd = check_size_t<G_SetTournamentPlayerDecks_GC_Ep3_6xB4x3D>(data);
|
||||
auto& cmd = check_size_t<G_SetTournamentPlayerDecks_Ep3_6xB4x3D>(data);
|
||||
if (cmd.rules.overall_time_limit || cmd.rules.phase_time_limit) {
|
||||
cmd.rules.overall_time_limit = 0;
|
||||
cmd.rules.phase_time_limit = 0;
|
||||
modified = true;
|
||||
}
|
||||
} else if (header.subsubcommand == 0x05) {
|
||||
auto& cmd = check_size_t<G_UpdateMap_GC_Ep3_6xB4x05>(data);
|
||||
if (cmd.state.rules.overall_time_limit || cmd.state.rules.phase_time_limit) {
|
||||
cmd.state.rules.overall_time_limit = 0;
|
||||
cmd.state.rules.phase_time_limit = 0;
|
||||
modified = true;
|
||||
if (ses->version() == Version::GC_EP3_NTE) {
|
||||
auto& cmd = check_size_t<G_UpdateMap_Ep3NTE_6xB4x05>(data);
|
||||
if (cmd.state.rules.overall_time_limit || cmd.state.rules.phase_time_limit) {
|
||||
cmd.state.rules.overall_time_limit = 0;
|
||||
cmd.state.rules.phase_time_limit = 0;
|
||||
modified = true;
|
||||
}
|
||||
} else {
|
||||
auto& cmd = check_size_t<G_UpdateMap_Ep3_6xB4x05>(data);
|
||||
if (cmd.state.rules.overall_time_limit || cmd.state.rules.phase_time_limit) {
|
||||
cmd.state.rules.overall_time_limit = 0;
|
||||
cmd.state.rules.phase_time_limit = 0;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1064,7 +1073,7 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
if (data[4] == 0x1A) {
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
} else if (data[4] == 0x36) {
|
||||
const auto& cmd = check_size_t<G_RecreatePlayer_GC_Ep3_6xB5x36>(data);
|
||||
const auto& cmd = check_size_t<G_RecreatePlayer_Ep3_6xB5x36>(data);
|
||||
if (ses->is_in_game && (cmd.client_id >= 4)) {
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
}
|
||||
@@ -1073,7 +1082,7 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
} else if ((static_cast<uint8_t>(data[0]) == 0xBD) &&
|
||||
ses->config.check_flag(Client::Flag::PROXY_EP3_UNMASK_WHISPERS) &&
|
||||
is_ep3(ses->version())) {
|
||||
auto& cmd = check_size_t<G_WordSelectDuringBattle_GC_Ep3_6xBD>(data);
|
||||
auto& cmd = check_size_t<G_WordSelectDuringBattle_Ep3_6xBD>(data);
|
||||
if (cmd.private_flags & (1 << ses->lobby_client_id)) {
|
||||
cmd.private_flags &= ~(1 << ses->lobby_client_id);
|
||||
modified = true;
|
||||
@@ -1112,7 +1121,7 @@ static HandlerResult C_GXB_61(shared_ptr<ProxyServer::LinkedSession> ses, uint16
|
||||
} else {
|
||||
C_CharacterData_V3_61_98* pd;
|
||||
if (flag == 4) { // Episode 3
|
||||
auto& ep3_pd = check_size_t<C_CharacterData_GC_Ep3_61_98>(data);
|
||||
auto& ep3_pd = check_size_t<C_CharacterData_Ep3_61_98>(data);
|
||||
if (ep3_pd.ep3_config.is_encrypted) {
|
||||
decrypt_trivial_gci_data(
|
||||
&ep3_pd.ep3_config.card_counts,
|
||||
@@ -1286,7 +1295,7 @@ static HandlerResult S_13_A7(shared_ptr<ProxyServer::LinkedSession> ses, uint16_
|
||||
static HandlerResult S_G_B7(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
|
||||
if (is_ep3(ses->version())) {
|
||||
if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) {
|
||||
auto& cmd = check_size_t<S_RankUpdate_GC_Ep3_B7>(data);
|
||||
auto& cmd = check_size_t<S_RankUpdate_Ep3_B7>(data);
|
||||
if (cmd.current_meseta != 1000000) {
|
||||
cmd.current_meseta = 1000000;
|
||||
return HandlerResult::Type::MODIFIED;
|
||||
@@ -1328,7 +1337,7 @@ static HandlerResult S_G_B8(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t
|
||||
static HandlerResult S_G_B9(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
|
||||
if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||
try {
|
||||
const auto& header = check_size_t<S_UpdateMediaHeader_GC_Ep3_B9>(data, 0xFFFF);
|
||||
const auto& header = check_size_t<S_UpdateMediaHeader_Ep3_B9>(data, 0xFFFF);
|
||||
|
||||
if (data.size() - sizeof(header) < header.size) {
|
||||
throw runtime_error("Media data size extends beyond end of command; not saving file");
|
||||
@@ -1363,8 +1372,8 @@ static HandlerResult S_G_B9(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t
|
||||
static HandlerResult S_G_EF(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
|
||||
if (is_ep3(ses->version())) {
|
||||
if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) {
|
||||
auto& cmd = check_size_t<S_StartCardAuction_GC_Ep3_EF>(data,
|
||||
offsetof(S_StartCardAuction_GC_Ep3_EF, unused), 0xFFFF);
|
||||
auto& cmd = check_size_t<S_StartCardAuction_Ep3_EF>(data,
|
||||
offsetof(S_StartCardAuction_Ep3_EF, unused), 0xFFFF);
|
||||
if (cmd.points_available != 0x7FFF) {
|
||||
cmd.points_available = 0x7FFF;
|
||||
return HandlerResult::Type::MODIFIED;
|
||||
@@ -1382,7 +1391,7 @@ static HandlerResult S_B_EF(shared_ptr<ProxyServer::LinkedSession>, uint16_t, ui
|
||||
|
||||
static HandlerResult S_G_BA(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
|
||||
if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) {
|
||||
auto& cmd = check_size_t<S_MesetaTransaction_GC_Ep3_BA>(data);
|
||||
auto& cmd = check_size_t<S_MesetaTransaction_Ep3_BA>(data);
|
||||
if (cmd.current_meseta != 1000000) {
|
||||
cmd.current_meseta = 1000000;
|
||||
return HandlerResult::Type::MODIFIED;
|
||||
@@ -1477,10 +1486,10 @@ constexpr on_command_t S_B_65_67_68 = &S_65_67_68_EB<S_JoinLobby_BB_65_67_68>;
|
||||
template <typename CmdT>
|
||||
static HandlerResult S_64(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t flag, string& data) {
|
||||
CmdT* cmd;
|
||||
S_JoinGame_GC_Ep3_64* cmd_ep3 = nullptr;
|
||||
S_JoinGame_Ep3_64* cmd_ep3 = nullptr;
|
||||
if (ses->sub_version >= 0x40) {
|
||||
cmd = &check_size_t<CmdT>(data, sizeof(S_JoinGame_GC_Ep3_64));
|
||||
cmd_ep3 = &check_size_t<S_JoinGame_GC_Ep3_64>(data);
|
||||
cmd = &check_size_t<CmdT>(data, sizeof(S_JoinGame_Ep3_64));
|
||||
cmd_ep3 = &check_size_t<S_JoinGame_Ep3_64>(data);
|
||||
} else if (ses->version() == Version::XB_V3) {
|
||||
// Schtserv doesn't send the unknown_a1 field in this command, and we don't
|
||||
// use it here, so we allow it to be omitted.
|
||||
@@ -1541,7 +1550,7 @@ constexpr on_command_t S_X_64 = &S_64<S_JoinGame_XB_64>;
|
||||
constexpr on_command_t S_B_64 = &S_64<S_JoinGame_BB_64>;
|
||||
|
||||
static HandlerResult S_E8(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
|
||||
auto& cmd = check_size_t<S_JoinSpectatorTeam_GC_Ep3_E8>(data);
|
||||
auto& cmd = check_size_t<S_JoinSpectatorTeam_Ep3_E8>(data);
|
||||
|
||||
ses->clear_lobby_players(12);
|
||||
ses->floor = 0;
|
||||
|
||||
+10
-10
@@ -1228,7 +1228,7 @@ static void on_B7_Ep3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
}
|
||||
|
||||
static void on_BA_Ep3(shared_ptr<Client> c, uint16_t command, uint32_t, string& data) {
|
||||
const auto& in_cmd = check_size_t<C_MesetaTransaction_GC_Ep3_BA>(data);
|
||||
const auto& in_cmd = check_size_t<C_MesetaTransaction_Ep3_BA>(data);
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->lobby.lock();
|
||||
bool is_lobby = l && !l->is_game();
|
||||
@@ -1252,7 +1252,7 @@ static void on_BA_Ep3(shared_ptr<Client> c, uint16_t command, uint32_t, string&
|
||||
total_meseta_earned = c->license->ep3_total_meseta_earned;
|
||||
}
|
||||
|
||||
S_MesetaTransaction_GC_Ep3_BA out_cmd = {current_meseta, total_meseta_earned, in_cmd.request_token};
|
||||
S_MesetaTransaction_Ep3_BA out_cmd = {current_meseta, total_meseta_earned, in_cmd.request_token};
|
||||
send_command(c, command, 0x03, &out_cmd, sizeof(out_cmd));
|
||||
}
|
||||
|
||||
@@ -1281,7 +1281,7 @@ static bool add_next_game_client(shared_ptr<Lobby> l) {
|
||||
|
||||
auto s = c->require_server_state();
|
||||
if (tourn) {
|
||||
G_SetStateFlags_GC_Ep3_6xB4x03 state_cmd;
|
||||
G_SetStateFlags_Ep3_6xB4x03 state_cmd;
|
||||
state_cmd.state.turn_num = 1;
|
||||
state_cmd.state.battle_phase = Episode3::BattlePhase::INVALID_00;
|
||||
state_cmd.state.current_team_turn1 = 0xFF;
|
||||
@@ -1498,7 +1498,7 @@ static void on_ep3_battle_table_state_updated(shared_ptr<Lobby> l, int16_t table
|
||||
}
|
||||
|
||||
static void on_E4_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string& data) {
|
||||
const auto& cmd = check_size_t<C_CardBattleTableState_GC_Ep3_E4>(data);
|
||||
const auto& cmd = check_size_t<C_CardBattleTableState_Ep3_E4>(data);
|
||||
auto l = c->require_lobby();
|
||||
|
||||
if (cmd.seat_number >= 4) {
|
||||
@@ -1539,7 +1539,7 @@ static void on_E4_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string& dat
|
||||
}
|
||||
|
||||
static void on_E5_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string& data) {
|
||||
check_size_t<S_CardBattleTableConfirmation_GC_Ep3_E5>(data);
|
||||
check_size_t<S_CardBattleTableConfirmation_Ep3_E5>(data);
|
||||
auto l = c->require_lobby();
|
||||
if (l->is_game() || !l->is_ep3()) {
|
||||
throw runtime_error("battle table command sent in non-CARD lobby");
|
||||
@@ -3032,7 +3032,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
||||
if (!is_ep3(c->version())) {
|
||||
throw runtime_error("non-Episode 3 client sent Episode 3 player data");
|
||||
}
|
||||
const auto* cmd3 = &check_size_t<C_CharacterData_GC_Ep3_61_98>(data);
|
||||
const auto* cmd3 = &check_size_t<C_CharacterData_Ep3_61_98>(data);
|
||||
c->ep3_config = make_shared<Episode3::PlayerConfig>(cmd3->ep3_config);
|
||||
cmd = reinterpret_cast<const C_CharacterData_V3_61_98*>(cmd3);
|
||||
if (c->config.specific_version == 0x33000000) {
|
||||
@@ -4646,7 +4646,7 @@ static void on_EE_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string& dat
|
||||
}
|
||||
|
||||
if (flag == 0xD0) {
|
||||
auto& cmd = check_size_t<SC_TradeCards_GC_Ep3_EE_FlagD0_FlagD3>(data);
|
||||
auto& cmd = check_size_t<SC_TradeCards_Ep3_EE_FlagD0_FlagD3>(data);
|
||||
|
||||
if (c->pending_card_trade) {
|
||||
throw runtime_error("player started a card trade when one is already pending");
|
||||
@@ -4678,7 +4678,7 @@ static void on_EE_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string& dat
|
||||
// See the description of the D0 command in CommandFormats.hh for more
|
||||
// information on how this sequence is supposed to work. (The EE D0 command
|
||||
// is analogous to Episodes 1&2's D0 command.)
|
||||
S_AdvanceCardTradeState_GC_Ep3_EE_FlagD1 resp = {0};
|
||||
S_AdvanceCardTradeState_Ep3_EE_FlagD1 resp = {0};
|
||||
send_command_t(target_c, 0xEE, 0xD1, resp);
|
||||
if (target_c->pending_card_trade) {
|
||||
send_command_t(c, 0xEE, 0xD1, resp);
|
||||
@@ -4703,7 +4703,7 @@ static void on_EE_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string& dat
|
||||
if (target_c->pending_card_trade->confirmed) {
|
||||
send_execute_card_trade(c, target_c->pending_card_trade->card_to_count);
|
||||
send_execute_card_trade(target_c, c->pending_card_trade->card_to_count);
|
||||
S_CardTradeComplete_GC_Ep3_EE_FlagD4 resp = {1};
|
||||
S_CardTradeComplete_Ep3_EE_FlagD4 resp = {1};
|
||||
send_command_t(c, 0xEE, 0xD4, resp);
|
||||
send_command_t(target_c, 0xEE, 0xD4, resp);
|
||||
c->pending_card_trade.reset();
|
||||
@@ -4719,7 +4719,7 @@ static void on_EE_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string& dat
|
||||
}
|
||||
uint8_t other_client_id = c->pending_card_trade->other_client_id;
|
||||
c->pending_card_trade.reset();
|
||||
S_CardTradeComplete_GC_Ep3_EE_FlagD4 resp = {0};
|
||||
S_CardTradeComplete_Ep3_EE_FlagD4 resp = {0};
|
||||
send_command_t(c, 0xEE, 0xD4, resp);
|
||||
|
||||
// Cancel the other side of the trade too, if it's open
|
||||
|
||||
@@ -954,7 +954,7 @@ static void on_ep3_battle_subs(shared_ptr<Client> c, uint8_t command, uint8_t fl
|
||||
if (header.subsubcommand == 0x1A) {
|
||||
return;
|
||||
} else if (header.subsubcommand == 0x36) {
|
||||
const auto& cmd = check_size_t<G_RecreatePlayer_GC_Ep3_6xB5x36>(data, size);
|
||||
const auto& cmd = check_size_t<G_RecreatePlayer_Ep3_6xB5x36>(data, size);
|
||||
if (l->is_game() && (cmd.client_id >= 4)) {
|
||||
return;
|
||||
}
|
||||
@@ -2015,9 +2015,9 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr<Client> c, uint
|
||||
|
||||
} else if (is_ep3(c->version())) {
|
||||
|
||||
const auto& cmd = check_size_t<G_WordSelectDuringBattle_GC_Ep3_6xBD>(data, size);
|
||||
G_WordSelectDuringBattle_GC_Ep3_6xBD masked_cmd = {
|
||||
{0xBD, sizeof(G_WordSelectDuringBattle_GC_Ep3_6xBD) >> 2, cmd.header.client_id},
|
||||
const auto& cmd = check_size_t<G_WordSelectDuringBattle_Ep3_6xBD>(data, size);
|
||||
G_WordSelectDuringBattle_Ep3_6xBD masked_cmd = {
|
||||
{0xBD, sizeof(G_WordSelectDuringBattle_Ep3_6xBD) >> 2, cmd.header.client_id},
|
||||
0x0001,
|
||||
0x0001,
|
||||
// "Please use the Whispers function."
|
||||
|
||||
+13
-10
@@ -298,13 +298,13 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
auto& mask = check_size_t<S_JoinGame_DCNTE_64>(mask_data, mask_size);
|
||||
mask.variations.clear(0);
|
||||
} else {
|
||||
auto& mask = check_size_t<S_JoinGame_DC_64>(mask_data, mask_size, sizeof(S_JoinGame_GC_Ep3_64));
|
||||
auto& mask = check_size_t<S_JoinGame_DC_64>(mask_data, mask_size, sizeof(S_JoinGame_Ep3_64));
|
||||
mask.variations.clear(0);
|
||||
mask.rare_seed = 0;
|
||||
for (size_t offset = sizeof(S_JoinGame_GC_64) +
|
||||
offsetof(S_JoinGame_GC_Ep3_64::Ep3PlayerEntry, disp.visual.name_color_checksum);
|
||||
offsetof(S_JoinGame_Ep3_64::Ep3PlayerEntry, disp.visual.name_color_checksum);
|
||||
offset + 4 <= mask_size;
|
||||
offset += sizeof(S_JoinGame_GC_Ep3_64::Ep3PlayerEntry)) {
|
||||
offset += sizeof(S_JoinGame_Ep3_64::Ep3PlayerEntry)) {
|
||||
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
|
||||
}
|
||||
}
|
||||
@@ -349,7 +349,7 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
break;
|
||||
case 0xE8:
|
||||
if (is_gc(version)) {
|
||||
auto& mask = check_size_t<S_JoinSpectatorTeam_GC_Ep3_E8>(mask_data, mask_size);
|
||||
auto& mask = check_size_t<S_JoinSpectatorTeam_Ep3_E8>(mask_data, mask_size);
|
||||
mask.rare_seed = 0;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
mask.players[z].disp.visual.name_color_checksum = 0;
|
||||
@@ -365,9 +365,12 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
}
|
||||
break;
|
||||
case 0xC9:
|
||||
if (mask_size == 0xCC) {
|
||||
auto& mask = check_size_t<G_ServerVersionStrings_GC_Ep3_6xB4x46>(
|
||||
mask_data, mask_size);
|
||||
if (mask_size == sizeof(G_ServerVersionStrings_Ep3NTE_6xB4x46)) {
|
||||
auto& mask = check_size_t<G_ServerVersionStrings_Ep3NTE_6xB4x46>(mask_data, mask_size);
|
||||
mask.version_signature.clear(0);
|
||||
mask.date_str1.clear(0);
|
||||
} else if (mask_size == sizeof(G_ServerVersionStrings_Ep3_6xB4x46)) {
|
||||
auto& mask = check_size_t<G_ServerVersionStrings_Ep3_6xB4x46>(mask_data, mask_size);
|
||||
mask.version_signature.clear(0);
|
||||
mask.date_str1.clear(0);
|
||||
mask.date_str2.clear(0);
|
||||
@@ -375,15 +378,15 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
break;
|
||||
case 0x6C:
|
||||
if (is_gc(version) && mask_size >= 0x14) {
|
||||
const auto& cmd = check_size_t<G_MapList_GC_Ep3_6xB6x40>(cmd_data, cmd_size, 0xFFFF);
|
||||
const auto& cmd = check_size_t<G_MapList_Ep3_6xB6x40>(cmd_data, cmd_size, 0xFFFF);
|
||||
if ((cmd.header.header.basic_header.subcommand == 0xB6) &&
|
||||
(cmd.header.subsubcommand == 0x40)) {
|
||||
check_size_t<PSOCommandHeaderDCV3>(ev->mask, 0xFFFF).size = 0;
|
||||
auto& mask = check_size_t<G_MapList_GC_Ep3_6xB6x40>(mask_data, mask_size, 0xFFFF);
|
||||
auto& mask = check_size_t<G_MapList_Ep3_6xB6x40>(mask_data, mask_size, 0xFFFF);
|
||||
mask.header.header.size = 0;
|
||||
mask.compressed_data_size = 0;
|
||||
ev->allow_size_disparity = true;
|
||||
for (size_t z = sizeof(PSOCommandHeaderDCV3) + sizeof(G_MapList_GC_Ep3_6xB6x40); z < ev->mask.size(); z++) {
|
||||
for (size_t z = sizeof(PSOCommandHeaderDCV3) + sizeof(G_MapList_Ep3_6xB6x40); z < ev->mask.size(); z++) {
|
||||
ev->mask[z] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
+23
-23
@@ -801,7 +801,7 @@ void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const string& mess
|
||||
return;
|
||||
}
|
||||
StringWriter w;
|
||||
w.put<S_TimedMessageBoxHeader_GC_Ep3_EA>({frames});
|
||||
w.put<S_TimedMessageBoxHeader_Ep3_EA>({frames});
|
||||
w.write(encoded);
|
||||
w.put_u8(0);
|
||||
while (w.size() & 3) {
|
||||
@@ -1706,7 +1706,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
|
||||
|
||||
auto s = c->require_server_state();
|
||||
|
||||
S_JoinSpectatorTeam_GC_Ep3_E8 cmd;
|
||||
S_JoinSpectatorTeam_Ep3_E8 cmd;
|
||||
|
||||
cmd.variations.clear(0);
|
||||
cmd.client_id = c->lobby_client_id;
|
||||
@@ -1901,7 +1901,7 @@ void send_join_game(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
}
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3: {
|
||||
S_JoinGame_GC_Ep3_64 cmd;
|
||||
S_JoinGame_Ep3_64 cmd;
|
||||
size_t player_count = populate_v3_cmd(cmd);
|
||||
auto s = c->require_server_state();
|
||||
for (size_t x = 0; x < 4; x++) {
|
||||
@@ -2280,7 +2280,7 @@ void send_execute_card_trade(shared_ptr<Client> c, const vector<pair<uint32_t, u
|
||||
throw logic_error("cannot send trade cards command to non-Ep3 client");
|
||||
}
|
||||
|
||||
SC_TradeCards_GC_Ep3_EE_FlagD0_FlagD3 cmd;
|
||||
SC_TradeCards_Ep3_EE_FlagD0_FlagD3 cmd;
|
||||
constexpr size_t max_entries = sizeof(cmd.entries) / sizeof(cmd.entries[0]);
|
||||
if (card_to_count.size() > max_entries) {
|
||||
throw logic_error("too many items in execute card trade command");
|
||||
@@ -2405,7 +2405,7 @@ void send_warp(shared_ptr<Lobby> l, uint32_t floor, bool is_private) {
|
||||
}
|
||||
|
||||
void send_ep3_change_music(Channel& ch, uint32_t song) {
|
||||
G_ChangeLobbyMusic_GC_Ep3_6xBF cmd = {{0xBF, 0x02, 0}, song};
|
||||
G_ChangeLobbyMusic_Ep3_6xBF cmd = {{0xBF, 0x02, 0}, song};
|
||||
ch.send(0x60, 0x00, cmd);
|
||||
}
|
||||
|
||||
@@ -2758,7 +2758,7 @@ void send_ep3_media_update(
|
||||
uint32_t which,
|
||||
const string& compressed_data) {
|
||||
StringWriter w;
|
||||
w.put<S_UpdateMediaHeader_GC_Ep3_B9>({type, which, compressed_data.size(), 0});
|
||||
w.put<S_UpdateMediaHeader_Ep3_B9>({type, which, compressed_data.size(), 0});
|
||||
w.write(compressed_data);
|
||||
while (w.size() & 3) {
|
||||
w.put_u8(0);
|
||||
@@ -2770,12 +2770,12 @@ void send_ep3_rank_update(shared_ptr<Client> c) {
|
||||
auto s = c->require_server_state();
|
||||
uint32_t current_meseta = s->ep3_infinite_meseta ? 1000000 : c->license->ep3_current_meseta;
|
||||
uint32_t total_meseta_earned = s->ep3_infinite_meseta ? 1000000 : c->license->ep3_total_meseta_earned;
|
||||
S_RankUpdate_GC_Ep3_B7 cmd = {0, {}, current_meseta, total_meseta_earned, 0xFFFFFFFF};
|
||||
S_RankUpdate_Ep3_B7 cmd = {0, {}, current_meseta, total_meseta_earned, 0xFFFFFFFF};
|
||||
send_command_t(c, 0xB7, 0x00, cmd);
|
||||
}
|
||||
|
||||
void send_ep3_card_battle_table_state(shared_ptr<Lobby> l, uint16_t table_number) {
|
||||
S_CardBattleTableState_GC_Ep3_E4 cmd;
|
||||
S_CardBattleTableState_Ep3_E4 cmd;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
cmd.entries[z].state = 0;
|
||||
cmd.entries[z].unknown_a1 = 0;
|
||||
@@ -2806,7 +2806,7 @@ void send_ep3_card_battle_table_state(shared_ptr<Lobby> l, uint16_t table_number
|
||||
}
|
||||
|
||||
void send_ep3_set_context_token(shared_ptr<Client> c, uint32_t context_token) {
|
||||
G_SetContextToken_GC_Ep3_6xB4x1F cmd;
|
||||
G_SetContextToken_Ep3_6xB4x1F cmd;
|
||||
cmd.context_token = context_token;
|
||||
send_command_t(c, 0xC9, 0x00, cmd);
|
||||
}
|
||||
@@ -2818,7 +2818,7 @@ void send_ep3_confirm_tournament_entry(
|
||||
throw runtime_error("cannot send tournament entry command to Episode 3 Trial Edition client");
|
||||
}
|
||||
|
||||
S_ConfirmTournamentEntry_GC_Ep3_CC cmd;
|
||||
S_ConfirmTournamentEntry_Ep3_CC cmd;
|
||||
if (tourn) {
|
||||
auto s = c->require_server_state();
|
||||
cmd.tournament_name.encode(tourn->get_name(), c->language());
|
||||
@@ -2842,7 +2842,7 @@ void send_ep3_tournament_list(
|
||||
bool is_for_spectator_team_create) {
|
||||
auto s = c->require_server_state();
|
||||
|
||||
S_TournamentList_GC_Ep3_E0 cmd;
|
||||
S_TournamentList_Ep3_E0 cmd;
|
||||
size_t z = 0;
|
||||
for (const auto& it : s->ep3_tournament_index->all_tournaments()) {
|
||||
const auto& tourn = it.second;
|
||||
@@ -2884,7 +2884,7 @@ void send_ep3_tournament_entry_list(
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<const Episode3::Tournament> tourn,
|
||||
bool is_for_spectator_team_create) {
|
||||
S_TournamentEntryList_GC_Ep3_E2 cmd;
|
||||
S_TournamentEntryList_Ep3_E2 cmd;
|
||||
cmd.players_per_team = (tourn->get_flags() & Episode3::Tournament::Flag::IS_2V2) ? 2 : 1;
|
||||
size_t z = 0;
|
||||
for (const auto& team : tourn->all_teams()) {
|
||||
@@ -2914,7 +2914,7 @@ void send_ep3_tournament_entry_list(
|
||||
void send_ep3_tournament_details(
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<const Episode3::Tournament> tourn) {
|
||||
S_TournamentGameDetails_GC_Ep3_E3 cmd;
|
||||
S_TournamentGameDetails_Ep3_E3 cmd;
|
||||
auto vm = tourn->get_map()->version(c->language());
|
||||
cmd.name.encode(tourn->get_name(), c->language());
|
||||
cmd.map_name.encode(vm->map->name.decode(vm->language), c->language());
|
||||
@@ -2955,7 +2955,7 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
auto tourn = tourn_match ? tourn_match->tournament.lock() : nullptr;
|
||||
|
||||
if (tourn) {
|
||||
S_TournamentGameDetails_GC_Ep3_E3 cmd;
|
||||
S_TournamentGameDetails_Ep3_E3 cmd;
|
||||
cmd.name.encode(l->name, c->language());
|
||||
|
||||
auto vm = tourn->get_map()->version(c->language());
|
||||
@@ -2974,7 +2974,7 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
|
||||
if (primary_lobby) {
|
||||
auto serial_number_to_client = primary_lobby->clients_by_serial_number();
|
||||
auto describe_team = [&](S_TournamentGameDetails_GC_Ep3_E3::TeamEntry& team_entry, shared_ptr<const Episode3::Tournament::Team> team) -> void {
|
||||
auto describe_team = [&](S_TournamentGameDetails_Ep3_E3::TeamEntry& team_entry, shared_ptr<const Episode3::Tournament::Team> team) -> void {
|
||||
team_entry.team_name.encode(team->name, c->language());
|
||||
for (size_t z = 0; z < team->players.size(); z++) {
|
||||
auto& entry = team_entry.players[z];
|
||||
@@ -3014,7 +3014,7 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
send_command_t(c, 0xE3, flag, cmd);
|
||||
|
||||
} else {
|
||||
S_GameInformation_GC_Ep3_E1 cmd;
|
||||
S_GameInformation_Ep3_E1 cmd;
|
||||
cmd.game_name.encode(l->name, c->language());
|
||||
if (primary_lobby) {
|
||||
size_t num_players = 0;
|
||||
@@ -3043,7 +3043,7 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
// spectator count in the info window object. To account for this, we send
|
||||
// a mostly-blank E3 to set the spectator count, followed by an E1 with
|
||||
// the correct data.
|
||||
S_TournamentGameDetails_GC_Ep3_E3 cmd_E3;
|
||||
S_TournamentGameDetails_Ep3_E3 cmd_E3;
|
||||
cmd_E3.num_spectators = num_spectators;
|
||||
send_command_t(c, 0xE3, 0x04, cmd_E3);
|
||||
|
||||
@@ -3073,7 +3073,7 @@ void send_ep3_set_tournament_player_decks(shared_ptr<Client> c) {
|
||||
throw runtime_error("tournament is deleted");
|
||||
}
|
||||
|
||||
G_SetTournamentPlayerDecks_GC_Ep3_6xB4x3D cmd;
|
||||
G_SetTournamentPlayerDecks_Ep3_6xB4x3D cmd;
|
||||
cmd.rules = tourn->get_rules();
|
||||
cmd.map_number = tourn->get_map()->map_number;
|
||||
cmd.player_slot = 0xFF;
|
||||
@@ -3139,7 +3139,7 @@ void send_ep3_tournament_match_result(shared_ptr<Lobby> l, uint32_t meseta_rewar
|
||||
if (!lc) {
|
||||
continue;
|
||||
}
|
||||
auto write_player_names = [&](G_TournamentMatchResult_GC_Ep3_6xB4x51::NamesEntry& entry, shared_ptr<const Episode3::Tournament::Team> team) -> void {
|
||||
auto write_player_names = [&](G_TournamentMatchResult_Ep3_6xB4x51::NamesEntry& entry, shared_ptr<const Episode3::Tournament::Team> team) -> void {
|
||||
for (size_t z = 0; z < team->players.size(); z++) {
|
||||
const auto& player = team->players[z];
|
||||
if (player.is_human()) {
|
||||
@@ -3155,7 +3155,7 @@ void send_ep3_tournament_match_result(shared_ptr<Lobby> l, uint32_t meseta_rewar
|
||||
}
|
||||
};
|
||||
|
||||
G_TournamentMatchResult_GC_Ep3_6xB4x51 cmd;
|
||||
G_TournamentMatchResult_Ep3_6xB4x51 cmd;
|
||||
cmd.match_description.encode((match == tourn->get_final_match())
|
||||
? string_printf("(%s) Final match", tourn->get_name().c_str())
|
||||
: string_printf("(%s) Round %zu", tourn->get_name().c_str(), match->round_num),
|
||||
@@ -3197,7 +3197,7 @@ void send_ep3_update_game_metadata(shared_ptr<Lobby> l) {
|
||||
auto s = l->require_server_state();
|
||||
|
||||
{
|
||||
G_SetGameMetadata_GC_Ep3_6xB4x52 cmd;
|
||||
G_SetGameMetadata_Ep3_6xB4x52 cmd;
|
||||
cmd.total_spectators = total_spectators;
|
||||
if ((l->base_version != Version::GC_EP3_NTE) &&
|
||||
!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
|
||||
@@ -3230,7 +3230,7 @@ void send_ep3_update_game_metadata(shared_ptr<Lobby> l) {
|
||||
}
|
||||
add_color_inplace(text);
|
||||
for (auto watcher_l : l->watcher_lobbies) {
|
||||
G_SetGameMetadata_GC_Ep3_6xB4x52 cmd;
|
||||
G_SetGameMetadata_Ep3_6xB4x52 cmd;
|
||||
cmd.local_spectators = 0;
|
||||
for (auto c : watcher_l->clients) {
|
||||
cmd.local_spectators += (c.get() != nullptr);
|
||||
@@ -3513,7 +3513,7 @@ void send_ep3_card_auction(shared_ptr<Lobby> l) {
|
||||
? s->ep3_card_index_trial
|
||||
: s->ep3_card_index;
|
||||
|
||||
S_StartCardAuction_GC_Ep3_EF cmd;
|
||||
S_StartCardAuction_Ep3_EF cmd;
|
||||
cmd.points_available = s->ep3_card_auction_points;
|
||||
for (size_t z = 0; z < num_cards; z++) {
|
||||
uint64_t v = random_object<uint64_t>() % distribution_size;
|
||||
|
||||
+2
-2
@@ -845,8 +845,8 @@ void ServerState::load_config() {
|
||||
}
|
||||
|
||||
{
|
||||
auto parse_ep3_ex_result_cmd = [&](const JSON& src) -> shared_ptr<G_SetEXResultValues_GC_Ep3_6xB4x4B> {
|
||||
auto ret = make_shared<G_SetEXResultValues_GC_Ep3_6xB4x4B>();
|
||||
auto parse_ep3_ex_result_cmd = [&](const JSON& src) -> shared_ptr<G_SetEXResultValues_Ep3_6xB4x4B> {
|
||||
auto ret = make_shared<G_SetEXResultValues_Ep3_6xB4x4B>();
|
||||
const auto& win_json = src.at("Win");
|
||||
for (size_t z = 0; z < min<size_t>(win_json.size(), 10); z++) {
|
||||
ret->win_entries[z].threshold = win_json.at(z).at(0).as_int();
|
||||
|
||||
+3
-3
@@ -129,9 +129,9 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::shared_ptr<const Episode3::CardIndex> ep3_card_index_trial;
|
||||
std::shared_ptr<const Episode3::MapIndex> ep3_map_index;
|
||||
std::shared_ptr<const Episode3::COMDeckIndex> ep3_com_deck_index;
|
||||
std::shared_ptr<const G_SetEXResultValues_GC_Ep3_6xB4x4B> ep3_default_ex_values;
|
||||
std::shared_ptr<const G_SetEXResultValues_GC_Ep3_6xB4x4B> ep3_tournament_ex_values;
|
||||
std::shared_ptr<const G_SetEXResultValues_GC_Ep3_6xB4x4B> ep3_tournament_final_round_ex_values;
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_default_ex_values;
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_ex_values;
|
||||
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_final_round_ex_values;
|
||||
std::shared_ptr<const QuestCategoryIndex> quest_category_index;
|
||||
std::shared_ptr<const QuestIndex> default_quest_index;
|
||||
std::shared_ptr<const QuestIndex> ep3_download_quest_index;
|
||||
|
||||
Reference in New Issue
Block a user