diff --git a/src/Episode3/Card.cc b/src/Episode3/Card.cc index 0bf5100d..bb0c7bf3 100644 --- a/src/Episode3/Card.cc +++ b/src/Episode3/Card.cc @@ -446,6 +446,11 @@ void Card::destroy_set_card(shared_ptr attacker_card) { } int32_t Card::error_code_for_move_to_location(const Location& loc) const { + // TODO: NTE has different logic here, which appears to be similar enough to + // the final logic that I didn't bother to reverse-engineer it completely. + // Eventually, we should revisit this, but I suspect doing so would be a lot + // of tedium for little to no benefit. + if (this->player_state()->assist_flags & AssistFlag::IS_SKIPPING_TURN) { return -0x76; } @@ -629,6 +634,8 @@ uint8_t Card::get_team_id() const { } int32_t Card::move_to_location(const Location& loc) { + auto s = this->server(); + int32_t code = this->error_code_for_move_to_location(loc); if (code) { return code; @@ -636,7 +643,7 @@ int32_t Card::move_to_location(const Location& loc) { uint32_t path_cost; uint32_t path_length; - if (!this->server()->ruler_server->get_move_path_length_and_cost( + if (!s->ruler_server->get_move_path_length_and_cost( this->client_id, this->card_ref, loc, &path_length, &path_cost)) { return -0x79; } @@ -646,20 +653,48 @@ int32_t Card::move_to_location(const Location& loc) { this->loc = loc; this->card_flags = this->card_flags | 0x80; + // On NTE, traps happen now, not after the Move phase + if (s->options.is_trial() && + this->def_entry->def.is_sc() && + ((s->overlay_state.tiles[loc.y][loc.x] & 0xF0) == 0x40)) { + for (size_t z = 0; z < 4; z++) { + auto other_ps = s->player_states[z]; + if (!other_ps) { + continue; + } + auto other_sc = other_ps->get_sc_card(); + if (!other_sc) { + continue; + } + + if ((abs(other_sc->loc.x - loc.x) < 2) && (abs(other_sc->loc.y - loc.y) < 2)) { + uint8_t trap_type = s->overlay_state.tiles[loc.y][loc.x] & 0x0F; + uint16_t trap_card_id = s->overlay_state.trap_card_ids_nte[trap_type]; + if (other_ps->replace_assist_card_by_id(trap_card_id)) { + G_Unknown_Ep3_6xB4x2C cmd; + cmd.change_type = 1; + cmd.client_id = other_ps->client_id; + cmd.unknown_a2[0] = trap_card_id; + s->send(cmd); + } + } + } + } + for (size_t warp_type = 0; warp_type < 5; warp_type++) { 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)) { + if ((s->warp_positions[warp_type][warp_end][0] == this->loc.x) && + (s->warp_positions[warp_type][warp_end][1] == this->loc.y)) { 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]; - this->loc.y = this->server()->warp_positions[warp_type][warp_end ^ 1][1]; + this->loc.x = s->warp_positions[warp_type][warp_end ^ 1][0]; + this->loc.y = s->warp_positions[warp_type][warp_end ^ 1][1]; cmd.change_type = 0; cmd.card_refs.clear(0xFFFF); cmd.card_refs[0] = this->card_ref; cmd.unknown_a2.clear(0xFFFFFFFF); - this->server()->send(cmd); + s->send(cmd); return 0; } } @@ -1197,17 +1232,25 @@ void Card::unknown_80237A90(const ActionState& pa, uint16_t action_card_ref) { this->facing_direction = pa.facing_direction; this->action_chain.add_attack_action_card_ref(action_card_ref, s); - for (size_t z = 0; z < 4; z++) { - if (s->ruler_server->count_rampage_targets_for_attack(pa, z) != 0) { - this->action_chain.set_flags(0x200 << z); + if (s->options.is_trial()) { + if (s->ruler_server->count_targets_with_rampage_and_not_pierce_nte(pa)) { + this->action_chain.set_flags(0x02); } - if (s->ruler_server->attack_action_has_pierce_and_not_rampage(pa, z)) { - this->action_chain.set_flags(0x2000 << z); + if (s->ruler_server->count_targets_with_pierce_and_not_rampage_nte(pa)) { + this->action_chain.set_flags(0x80); + } + } else { + for (size_t z = 0; z < 4; z++) { + if (s->ruler_server->count_rampage_targets_for_attack(pa, z) != 0) { + this->action_chain.set_flags(0x200 << z); + } + if (s->ruler_server->attack_action_has_pierce_and_not_rampage(pa, z)) { + this->action_chain.set_flags(0x2000 << z); + } + } + if (s->ruler_server->any_attack_action_card_is_support_tech_or_support_pb(pa)) { + this->action_chain.set_flags(0x20000); } - } - - if (s->ruler_server->any_attack_action_card_is_support_tech_or_support_pb(pa)) { - this->action_chain.set_flags(0x20000); } if (this->action_chain.chain.target_card_ref_count == 0) { diff --git a/src/Episode3/CardSpecial.cc b/src/Episode3/CardSpecial.cc index 967e6687..f73ee30d 100644 --- a/src/Episode3/CardSpecial.cc +++ b/src/Episode3/CardSpecial.cc @@ -3810,25 +3810,28 @@ int16_t CardSpecial::max_all_attack_bonuses(size_t* out_count) const { void CardSpecial::unknown_80244AA8(shared_ptr card) { ActionState as = this->create_attack_state_from_card_action_chain(card); - for (size_t client_id = 0; client_id < 4; client_id++) { - auto ps = this->server()->player_states[client_id]; - if (ps) { - auto other_card = ps->get_sc_card(); - if (other_card) { - this->clear_invalid_conditions_on_card(other_card, as); - } - for (size_t set_index = 0; set_index < 8; set_index++) { - auto other_card = ps->get_set_card(set_index); + bool is_trial = this->server()->options.is_trial(); + if (!is_trial) { + for (size_t client_id = 0; client_id < 4; client_id++) { + auto ps = this->server()->player_states[client_id]; + if (ps) { + auto other_card = ps->get_sc_card(); if (other_card) { this->clear_invalid_conditions_on_card(other_card, as); } + for (size_t set_index = 0; set_index < 8; set_index++) { + auto other_card = ps->get_set_card(set_index); + if (other_card) { + this->clear_invalid_conditions_on_card(other_card, as); + } + } } } + this->apply_defense_conditions(as, 0x27, card, 0x04); + this->evaluate_and_apply_effects(0x27, card->get_card_ref(), as, 0xFFFF); } - this->apply_defense_conditions(as, 0x27, card, 4); - this->evaluate_and_apply_effects(0x27, card->get_card_ref(), as, 0xFFFF); - this->apply_defense_conditions(as, 0x13, card, 4); + this->apply_defense_conditions(as, 0x13, card, is_trial ? 0x1F : 0x04); this->evaluate_and_apply_effects(0x13, card->get_card_ref(), as, 0xFFFF); } @@ -4661,15 +4664,9 @@ void CardSpecial::unknown_8024AAB8(const ActionState& as) { if (this->send_6xB4x06_if_card_ref_invalid(as.original_attacker_card_ref, 0x1F) == 0xFFFF) { this->evaluate_and_apply_effects( - 0x01, - as.action_card_refs[z], - as, - this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 0x21)); + 0x01, as.action_card_refs[z], as, this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 0x21)); this->evaluate_and_apply_effects( - 0x0B, - as.action_card_refs[z], - as, - this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 0x22)); + 0x0B, as.action_card_refs[z], as, this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 0x22)); } else { uint16_t card_ref = this->send_6xB4x06_if_card_ref_invalid(as.target_card_refs[0], 0x20); if (card_ref != 0xFFFF) { @@ -4851,7 +4848,7 @@ shared_ptr CardSpecial::sc_card_for_card(shared_ptr unknown_p2) { void CardSpecial::unknown_8024A9D8(const ActionState& pa, uint16_t action_card_ref) { for (size_t z = 0; (z < 8) && (pa.action_card_refs[z] != 0xFFFF); z++) { - if ((action_card_ref == 0xFFFF) || (action_card_ref == pa.action_card_refs[z])) { + if (this->server()->options.is_trial() || (action_card_ref == 0xFFFF) || (action_card_ref == pa.action_card_refs[z])) { if (pa.original_attacker_card_ref == 0xFFFF) { this->evaluate_and_apply_effects(0x29, pa.action_card_refs[z], pa, pa.attacker_card_ref); this->evaluate_and_apply_effects(0x2A, pa.action_card_refs[z], pa, pa.attacker_card_ref); diff --git a/src/Episode3/DataIndexes.cc b/src/Episode3/DataIndexes.cc index 160adeb6..720c69c9 100644 --- a/src/Episode3/DataIndexes.cc +++ b/src/Episode3/DataIndexes.cc @@ -1466,8 +1466,8 @@ RulesTrial::RulesTrial(const Rules& r) : overall_time_limit(r.overall_time_limit), phase_time_limit(r.phase_time_limit), allowed_cards(r.allowed_cards), - atk_dice_max(r.max_dice), - def_dice_max(r.def_dice_range ? (r.def_dice_range & 0x0F) : r.max_dice), + atk_die_behavior((r.max_dice == r.min_dice) ? r.max_dice : 0), + def_die_behavior((r.min_def_dice() == r.max_def_dice()) ? r.max_def_dice() : 0), disable_deck_shuffle(r.disable_deck_shuffle), disable_deck_loop(r.disable_deck_loop), char_hp(r.char_hp), @@ -1481,8 +1481,13 @@ RulesTrial::operator Rules() const { ret.overall_time_limit = this->overall_time_limit; ret.phase_time_limit = this->phase_time_limit; ret.allowed_cards = this->allowed_cards; - ret.min_dice = 1; - ret.max_dice = this->atk_dice_max; + if (this->atk_die_behavior) { + ret.min_dice = this->atk_die_behavior; + ret.max_dice = this->atk_die_behavior; + } else { + ret.min_dice = 1; + ret.max_dice = 6; + } ret.disable_deck_shuffle = this->disable_deck_shuffle; ret.disable_deck_loop = this->disable_deck_loop; ret.char_hp = this->char_hp; @@ -1491,7 +1496,11 @@ RulesTrial::operator Rules() const { ret.disable_dialogue = this->disable_dialogue; ret.dice_exchange_mode = this->dice_exchange_mode; ret.disable_dice_boost = 0; - ret.def_dice_range = 0x10 | (this->def_dice_max ? this->def_dice_max : 0x06); + if (this->def_die_behavior) { + ret.def_dice_range = (this->def_die_behavior << 4) | this->def_die_behavior; + } else { + ret.def_dice_range = 0x16; + } ret.unused.clear(0); return ret; } diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index c14881e5..f0ce998a 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -984,8 +984,11 @@ struct RulesTrial { /* 00 */ uint8_t overall_time_limit = 0; /* 01 */ uint8_t phase_time_limit = 0; /* 02 */ AllowedCards allowed_cards = AllowedCards::ALL; - /* 03 */ uint8_t atk_dice_max = 6; - /* 04 */ uint8_t def_dice_max = 6; + // In NTE, the dice behave differently than in non-NTE. A zero in either of + // these fields means the corresponding die is random in the range [1, 6]; + // any nonzero value means that die will always take that value. + /* 03 */ uint8_t atk_die_behavior = 0; + /* 04 */ uint8_t def_die_behavior = 0; /* 05 */ uint8_t disable_deck_shuffle = 0; /* 06 */ uint8_t disable_deck_loop = 0; /* 07 */ uint8_t char_hp = 15; @@ -1185,6 +1188,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests // 30-34 = teleporters (2 of each value may be present) // 40-44 = traps (one of each type is chosen at random to be a real trap at // battle start time) + // 40-4F = traps on NTE // 50 = blocked by metal box (appears as improperly-z-buffered teal cube in // preview; behaves like 10 and 20 in game) // The assist cards that each trap type can contain are: diff --git a/src/Episode3/MapState.cc b/src/Episode3/MapState.cc index 11ca93eb..031516eb 100644 --- a/src/Episode3/MapState.cc +++ b/src/Episode3/MapState.cc @@ -43,7 +43,7 @@ void MapAndRulesState::clear() { this->num_team0_players = 0; this->unused2 = 0; this->start_facing_directions = 0; - this->unused3 = 0; + this->unknown_a3 = 0; this->map_number = 0; this->unused4 = 0; this->rules.clear(); @@ -77,7 +77,7 @@ MapAndRulesStateTrial::MapAndRulesStateTrial(const MapAndRulesState& state) num_team0_players(state.num_team0_players), unused2(state.unused2), unused5(state.start_facing_directions), - unknown_a3(state.unused3), + unknown_a3(state.unknown_a3), map_number(state.map_number), unused4(state.unused4), rules(state.rules) {} @@ -92,7 +92,7 @@ MapAndRulesStateTrial::operator MapAndRulesState() const { ret.num_team0_players = this->num_team0_players; ret.unused2 = this->unused2; ret.start_facing_directions = this->unused5; - ret.unused3 = this->unknown_a3; + ret.unknown_a3 = this->unknown_a3; ret.map_number = this->map_number; ret.unused4 = this->unused4; ret.rules = this->rules; @@ -109,7 +109,7 @@ void OverlayState::clear() { } this->unused1.clear(0); this->unused2.clear(0); - this->unused3.clear(0); + this->trap_card_ids_nte.clear(0); } } // namespace Episode3 diff --git a/src/Episode3/MapState.hh b/src/Episode3/MapState.hh index 0ef7a86d..752e17bd 100644 --- a/src/Episode3/MapState.hh +++ b/src/Episode3/MapState.hh @@ -31,7 +31,7 @@ struct MapAndRulesState { /* 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; + /* 0118 */ be_uint32_t unknown_a3 = 0; /* 011C */ le_uint32_t map_number = 0; /* 0120 */ be_uint32_t unused4 = 0; /* 0124 */ Rules rules; @@ -71,7 +71,7 @@ struct OverlayState { parray, 0x10> tiles; parray unused1; parray unused2; - parray unused3; + parray trap_card_ids_nte; // Unused on non-NTE OverlayState(); void clear(); diff --git a/src/Episode3/PlayerState.cc b/src/Episode3/PlayerState.cc index cf627b04..26f5d22c 100644 --- a/src/Episode3/PlayerState.cc +++ b/src/Episode3/PlayerState.cc @@ -176,11 +176,17 @@ void PlayerState::apply_assist_card_effect_on_set( } if (hand_index < 6) { - for (size_t z = 0; z < 0x10; z++) { - if (this->deck_state->draw_card_by_ref(this->discard_log_card_refs[z])) { - this->card_refs[hand_index] = this->discard_log_card_refs[z]; - this->discard_log_card_refs[z] = 0xFFFF; - break; + if (s->options.is_trial()) { + if (this->deck_state->draw_card_by_ref(this->discard_log_card_refs[0])) { + this->pop_from_discard_log(0); + } + } else { + for (size_t z = 0; z < 0x10; z++) { + if (this->deck_state->draw_card_by_ref(this->discard_log_card_refs[z])) { + this->card_refs[hand_index] = this->discard_log_card_refs[z]; + this->discard_log_card_refs[z] = 0xFFFF; + break; + } } } } @@ -202,7 +208,9 @@ void PlayerState::apply_assist_card_effect_on_set( case AssistEffect::SKIP_SET: case AssistEffect::SKIP_ACT: - this->assist_delay_turns = 2; + if (!s->options.is_trial()) { + this->assist_delay_turns = 2; + } break; case AssistEffect::NECROMANCER: { @@ -244,11 +252,16 @@ void PlayerState::apply_assist_card_effect_on_set( } } } - this->on_cards_destroyed(); + bool is_trial = s->options.is_trial(); + if (!is_trial) { + this->on_cards_destroyed(); + } this->atk_points = min(9, this->atk_points + (total_cost >> 1)); this->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); - s->send_6xB4x05(); + if (!is_trial) { + s->send_6xB4x05(); + } break; } @@ -307,86 +320,104 @@ void PlayerState::apply_assist_card_effect_on_set( s->update_battle_state_flags_and_send_6xB4x03_if_needed(); this->num_destroyed_fcs = 0; - s->team_num_cards_destroyed[this->team_id] = 0; - for (size_t client_id = 0; client_id < 4; client_id++) { - const auto other_ps = s->get_player_state(client_id); - if (other_ps && (this->team_id == other_ps->get_team_id())) { - auto card = other_ps->get_sc_card(); - if (card) { - card->num_cards_destroyed_by_team_at_set_time = 0; - card->num_destroyed_ally_fcs = 0; - } - for (size_t set_index = 0; set_index < 8; set_index++) { - auto set_card = other_ps->get_set_card(set_index); - if (set_card) { - set_card->num_cards_destroyed_by_team_at_set_time = 0; - set_card->num_destroyed_ally_fcs = 0; + if (!s->options.is_trial()) { + s->team_num_cards_destroyed[this->team_id] = 0; + for (size_t client_id = 0; client_id < 4; client_id++) { + const auto other_ps = s->get_player_state(client_id); + if (other_ps && (this->team_id == other_ps->get_team_id())) { + auto card = other_ps->get_sc_card(); + if (card) { + card->num_cards_destroyed_by_team_at_set_time = 0; + card->num_destroyed_ally_fcs = 0; + } + for (size_t set_index = 0; set_index < 8; set_index++) { + auto set_card = other_ps->get_set_card(set_index); + if (set_card) { + set_card->num_cards_destroyed_by_team_at_set_time = 0; + set_card->num_destroyed_ally_fcs = 0; + } } } } } break; - case AssistEffect::SLOW_TIME: + case AssistEffect::SLOW_TIME: { + bool is_trial = 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) { continue; } - if (other_ps->assist_remaining_turns < 10) { + if (is_trial + ? (other_ps->assist_remaining_turns != 90 && other_ps->assist_remaining_turns != 99) + : (other_ps->assist_remaining_turns < 10)) { other_ps->assist_remaining_turns = min(9, other_ps->assist_remaining_turns << 1); } - for (ssize_t set_index = -1; set_index < 8; set_index++) { + for (ssize_t set_index = is_trial ? 0 : -1; set_index < 8; set_index++) { auto card = (set_index == -1) ? other_ps->get_sc_card() : other_ps->get_set_card(set_index); if (card) { for (size_t cond_index = 0; cond_index < 9; cond_index++) { auto& cond = card->action_chain.conditions[cond_index]; - if ((cond.type != ConditionType::NONE) && - (cond.remaining_turns < 10)) { + if (cond.type == ConditionType::NONE) { + continue; + } + if (is_trial) { + if (cond.remaining_turns < 49) { + cond.remaining_turns <<= 1; + } + } else if (cond.remaining_turns < 10) { cond.remaining_turns = min(9, cond.remaining_turns << 1); } } } } - - other_ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); - other_ps->send_set_card_updates(); + if (!is_trial) { + other_ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); + other_ps->send_set_card_updates(); + } } break; + } - case AssistEffect::QUICK_TIME: + case AssistEffect::QUICK_TIME: { + bool is_trial = 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) { continue; } - if (other_ps->assist_remaining_turns < 10) { + if (is_trial + ? (other_ps->assist_remaining_turns != 90 && other_ps->assist_remaining_turns != 99) + : (other_ps->assist_remaining_turns < 10)) { other_ps->assist_remaining_turns = ((other_ps->assist_remaining_turns + 1) >> 1); } - for (ssize_t set_index = -1; set_index < 8; set_index++) { + for (ssize_t set_index = is_trial ? 0 : -1; set_index < 8; set_index++) { auto card = (set_index == -1) ? other_ps->get_sc_card() : other_ps->get_set_card(set_index); if (card) { for (size_t cond_index = 0; cond_index < 9; cond_index++) { auto& cond = card->action_chain.conditions[cond_index]; - if ((cond.type != ConditionType::NONE) && - (cond.remaining_turns < 10)) { + if ((cond.type != ConditionType::NONE) && (cond.remaining_turns < (is_trial ? 99 : 10))) { cond.remaining_turns = (cond.remaining_turns + 1) >> 1; } } } } - other_ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); - other_ps->send_set_card_updates(); + if (!is_trial) { + other_ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); + other_ps->send_set_card_updates(); + } } break; + } case AssistEffect::SQUEEZE: this->set_random_assist_card_from_hand_for_free(); @@ -397,7 +428,7 @@ void PlayerState::apply_assist_card_effect_on_set( break; case AssistEffect::SKIP_TURN: - if (!setter_ps || (setter_ps->team_id == this->team_id)) { + if (!s->options.is_trial() && (!setter_ps || (setter_ps->team_id == this->team_id))) { this->assist_delay_turns = 6; } else { this->assist_delay_turns = 5; @@ -819,6 +850,10 @@ uint8_t PlayerState::get_atk_points() const { return this->atk_points; } +uint8_t PlayerState::get_atk_points_nte() const { + return min(this->atk_points2_max, this->atk_points); +} + void PlayerState::get_short_status_for_card_index_in_hand( size_t hand_index, CardShortStatus* stat) const { stat->card_ref = this->card_refs[hand_index - 1]; @@ -913,7 +948,7 @@ bool PlayerState::is_team_turn() const { } void PlayerState::log_discard(uint16_t card_ref, uint16_t reason) { - for (size_t z = 15; z > 0; z--) { + for (size_t z = this->discard_log_card_refs.size() - 1; z > 0; z--) { this->discard_log_card_refs[z] = this->discard_log_card_refs[z - 1]; this->discard_log_reasons[z] = this->discard_log_reasons[z - 1]; } @@ -921,6 +956,27 @@ void PlayerState::log_discard(uint16_t card_ref, uint16_t reason) { this->discard_log_reasons[0] = reason; } +uint16_t PlayerState::pop_from_discard_log(uint16_t) { + // NTE appears to have a bug here (or some obviated code): it searches for an + // entry with the given reason, then ignores the result of that search and + // always returns the first entry instead. + // size_t z; + // for (size_t z = 0; z < this->discard_log_card_refs.size(); z++) { + // if ((this->discard_log_card_refs[z] != 0xFFFF) && (this->discard_log_reasons[z] == reason)) { + // break; + // } + // } + + uint16_t ret = this->discard_log_card_refs[0]; + for (size_t z = 0; z < this->discard_log_card_refs.size() - 1; z++) { + this->discard_log_card_refs[z] = this->discard_log_card_refs[z + 1]; + this->discard_log_reasons[z] = this->discard_log_reasons[z + 1]; + } + this->discard_log_card_refs[this->discard_log_card_refs.size() - 1] = 0xFFFF; + this->discard_log_reasons[this->discard_log_reasons.size() - 1] = 0; + return ret; +} + bool PlayerState::move_card_to_location_by_card_index(size_t card_index, const Location& new_loc) { auto s = this->server(); @@ -1042,16 +1098,18 @@ 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()); + bool is_trial = s->options.is_trial(); + const auto& assist_card_ids = all_assist_card_ids(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))) { + ((other_ps->card_refs[6] != 0xFFFF) || (!is_trial && (other_ps->set_assist_card_id != 0xFFFF)))) { uint16_t card_id = 0x0130; while (card_id == 0x0130) { // God Whim size_t index = s->get_random(assist_card_ids.size()); card_id = assist_card_ids[index]; - if (!this->god_whim_can_use_hidden_cards) { + // In NTE, God Whim can use ANY card, even unobtainable cards. + if (!is_trial && !this->god_whim_can_use_hidden_cards) { auto ce = s->definition_for_card_id(card_id); if (!ce || ce->def.cannot_drop) { continue; @@ -1078,13 +1136,15 @@ bool PlayerState::replace_assist_card_by_id(uint16_t card_id) { this->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); s->assist_server->populate_effects(); - for (size_t client_id = 0; client_id < 4; client_id++) { - auto other_ps = s->get_player_state(client_id); - if (other_ps) { - uint32_t prev_assist_flags = other_ps->assist_flags; - other_ps->set_assist_flags_from_assist_effects(); - if (prev_assist_flags != other_ps->assist_flags) { - other_ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); + if (!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) { + uint32_t prev_assist_flags = other_ps->assist_flags; + other_ps->set_assist_flags_from_assist_effects(); + if (prev_assist_flags != other_ps->assist_flags) { + other_ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); + } } } } @@ -1283,12 +1343,15 @@ bool PlayerState::set_card_from_hand( this->deck_state->set_card_ref_in_play(card_ref); + bool is_trial = s->options.is_trial(); auto ce = s->definition_for_card_ref(card_ref); if (ce->def.type == CardType::ITEM || ce->def.type == CardType::CREATURE) { if ((card_index < 7) || (card_index >= 15)) { return 0; } this->card_refs[card_index + 1] = card_ref; + // Note: NTE doesn't call the destructor on the existing card, if there is + // one. Is that a bug? this->set_cards[card_index - 7] = make_shared(s->card_id_for_card_ref(card_ref), card_ref, this->client_id, s); auto new_card = this->set_cards[card_index - 7]; new_card->init(); @@ -1297,6 +1360,8 @@ bool PlayerState::set_card_from_hand( new_card->loc.x = loc->x; new_card->loc.y = loc->y; } + // Note: NTE doesn't track this, but NTE can't use it anyway, so we don't + // check for NTE here. this->stats.num_item_or_creature_cards_set++; } else if (ce->def.type == CardType::ASSIST) { @@ -1316,7 +1381,9 @@ bool PlayerState::set_card_from_hand( target_ps->assist_card_set_number = s->next_assist_card_set_number++; this->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); - target_ps->apply_assist_card_effect_on_set(this->shared_from_this()); + if (!is_trial) { + target_ps->apply_assist_card_effect_on_set(this->shared_from_this()); + } target_ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); s->assist_server->populate_effects(); @@ -1331,26 +1398,33 @@ bool PlayerState::set_card_from_hand( other_ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); } } + if (is_trial) { + target_ps->apply_assist_card_effect_on_set(this->shared_from_this()); + } } + // NTE doesn't track this, but NTE also doesn't have access to it. this->stats.num_assist_cards_set++; } + // NTE doesn't track this, but NTE also doesn't have access to it. this->stats.num_cards_set++; this->compute_total_set_cards_cost(); s->card_special->on_card_set(this->shared_from_this(), card_ref); - if (ce->def.type == CardType::ASSIST) { + if (!is_trial && (ce->def.type == CardType::ASSIST)) { s->check_for_destroyed_cards_and_send_6xB4x05_6xB4x02(); } this->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); s->send_6xB4x05(); - G_Unknown_Ep3_6xB4x4A cmd; - cmd.card_refs.clear(0xFFFF); - cmd.card_refs[0] = card_ref; - cmd.client_id = this->client_id; - cmd.entry_count = 1; - cmd.round_num = s->get_round_num(); - s->send(cmd); + if (!is_trial) { + G_Unknown_Ep3_6xB4x4A cmd; + cmd.card_refs.clear(0xFFFF); + cmd.card_refs[0] = card_ref; + cmd.client_id = this->client_id; + cmd.entry_count = 1; + cmd.round_num = s->get_round_num(); + s->send(cmd); + } return true; } @@ -1682,6 +1756,7 @@ int16_t PlayerState::get_assist_turns_remaining() { bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) { auto s = this->server(); + bool is_trial = s->options.is_trial(); auto attacker_card = s->card_for_set_card_ref(pa.attacker_card_ref); if (attacker_card) { @@ -1689,67 +1764,80 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) { } auto action_type = s->ruler_server->get_pending_action_type(pa); - this->subtract_or_check_atk_or_def_points_for_action(pa, 1); + if (!is_trial) { + this->subtract_or_check_atk_or_def_points_for_action(pa, 1); + } if (action_type == ActionType::ATTACK) { - G_Unknown_Ep3_6xB4x4A cmd; - cmd.card_refs.clear(0xFFFF); - cmd.client_id = this->client_id; - cmd.round_num = s->get_round_num(); - cmd.entry_count = 0; auto card = s->card_for_set_card_ref(pa.attacker_card_ref); if (card) { card->loc.direction = pa.facing_direction; + + G_Unknown_Ep3_6xB4x4A cmd; + cmd.card_refs.clear(0xFFFF); + cmd.client_id = this->client_id; + cmd.round_num = s->get_round_num(); + cmd.entry_count = 0; size_t z = 0; do { card->unknown_80237A90(pa, pa.action_card_refs[z]); card->unknown_802379BC(pa.action_card_refs[z]); - if (pa.action_card_refs[z] != 0xFFFF) { - cmd.card_refs[z] = pa.action_card_refs[z]; - cmd.entry_count++; - } - auto ce = s->definition_for_card_ref(pa.action_card_refs[z]); - if (ce) { - auto card_class = ce->def.card_class(); - if ((card_class == CardClass::TECH) || - (card_class == CardClass::PHOTON_BLAST) || - (card_class == CardClass::BOSS_TECH)) { - this->stats.num_tech_cards_set++; + if (!is_trial) { + if (pa.action_card_refs[z] != 0xFFFF) { + cmd.card_refs[z] = pa.action_card_refs[z]; + cmd.entry_count++; } - if ((card_class == CardClass::ATTACK_ACTION) || - (card_class == CardClass::CONNECT_ONLY_ATTACK_ACTION) || - (card_class == CardClass::BOSS_ATTACK_ACTION)) { - this->stats.num_attack_actions_set++; + auto ce = s->definition_for_card_ref(pa.action_card_refs[z]); + if (ce) { + auto card_class = ce->def.card_class(); + if ((card_class == CardClass::TECH) || + (card_class == CardClass::PHOTON_BLAST) || + (card_class == CardClass::BOSS_TECH)) { + this->stats.num_tech_cards_set++; + } + if ((card_class == CardClass::ATTACK_ACTION) || + (card_class == CardClass::CONNECT_ONLY_ATTACK_ACTION) || + (card_class == CardClass::BOSS_ATTACK_ACTION)) { + this->stats.num_attack_actions_set++; + } + this->stats.num_cards_set++; } - this->stats.num_cards_set++; } z++; } while ((z < 8) && (pa.action_card_refs[z] != 0xFFFF)); + // Note: This is never sent on NTE because entry_count will always be zero if (cmd.entry_count > 0) { s->send(cmd); } } } else if (action_type == ActionType::DEFENSE) { - G_Unknown_Ep3_6xB4x4A cmd; - cmd.card_refs.clear(0xFFFF); - cmd.client_id = this->client_id; - cmd.round_num = s->get_round_num(); for (size_t z = 0; (z < 4 * 9) && (pa.target_card_refs[z] != 0xFFFF); z++) { auto target_card = s->card_for_set_card_ref(pa.target_card_refs[z]); if (target_card) { target_card->unknown_802379DC(pa); - if (this->client_id == target_card->get_client_id()) { - this->stats.defense_actions_set_on_self++; - } else { - this->stats.defense_actions_set_on_ally++; + if (!is_trial) { + if (this->client_id == target_card->get_client_id()) { + this->stats.defense_actions_set_on_self++; + } else { + this->stats.defense_actions_set_on_ally++; + } + this->stats.num_cards_set++; } - this->stats.num_cards_set++; } } - cmd.card_refs[0] = pa.defense_card_ref; - cmd.entry_count = 1; - s->send(cmd); + if (!is_trial) { + G_Unknown_Ep3_6xB4x4A cmd; + cmd.card_refs.clear(0xFFFF); + cmd.client_id = this->client_id; + cmd.round_num = s->get_round_num(); + cmd.card_refs[0] = pa.defense_card_ref; + cmd.entry_count = 1; + s->send(cmd); + } + } + if (is_trial) { + this->subtract_or_check_atk_or_def_points_for_action(pa, 1); } for (size_t z = 0; (z < pa.action_card_refs.size()) && (pa.action_card_refs[z] != 0xFFFF); z++) { this->discard_ref_from_hand(pa.action_card_refs[z]); @@ -1869,6 +1957,13 @@ void PlayerState::roll_main_dice_or_apply_after_effects() { auto s = this->server(); const auto& rules = s->map_and_rules->rules; + // In NTE, the dice behave differently - there is no minimum, and instead the + // player can specify a fixed value for each die or a random value (1-6). The + // implementation of this function is therefore quite different on NTE, but + // since we already support custom ranges for ATK and DEF dice, we just use + // the non-NTE logic and assign the dice ranges at battle start time to yield + // the NTE behavior. (See RulesTrial in DataIndexes.cc for how this is done.) + uint8_t min_atk_dice = rules.min_dice; uint8_t max_atk_dice = rules.max_dice; if (min_atk_dice == 0) { @@ -1882,6 +1977,12 @@ void PlayerState::roll_main_dice_or_apply_after_effects() { max_atk_dice = min_atk_dice; min_atk_dice = t; } + uint8_t atk_dice_range_width = (max_atk_dice - min_atk_dice) + 1; + if (atk_dice_range_width < 2) { + this->dice_results[0] = min_atk_dice; + } else { + this->dice_results[0] = min_atk_dice + s->get_random(atk_dice_range_width); + } uint8_t min_def_dice = rules.min_def_dice() ? rules.min_def_dice() : rules.min_dice; uint8_t max_def_dice = rules.max_def_dice() ? rules.max_def_dice() : rules.max_dice; @@ -1896,23 +1997,11 @@ void PlayerState::roll_main_dice_or_apply_after_effects() { max_def_dice = min_def_dice; min_def_dice = t; } - - // In NTE, the dice aren't actually rolled here; they are instead rolled in - // dice_phase_before, and only the after effects are processed here. - if (!s->options.is_trial()) { - uint8_t atk_dice_range_width = (max_atk_dice - min_atk_dice) + 1; - if (atk_dice_range_width < 2) { - this->dice_results[0] = min_atk_dice; - } else { - this->dice_results[0] = min_atk_dice + s->get_random(atk_dice_range_width); - } - - uint8_t def_dice_range_width = (max_def_dice - min_def_dice) + 1; - if (def_dice_range_width < 2) { - this->dice_results[1] = min_def_dice; - } else { - this->dice_results[1] = min_def_dice + s->get_random(def_dice_range_width); - } + uint8_t def_dice_range_width = (max_def_dice - min_def_dice) + 1; + if (def_dice_range_width < 2) { + this->dice_results[1] = min_def_dice; + } else { + this->dice_results[1] = min_def_dice + s->get_random(def_dice_range_width); } bool should_exchange = false; diff --git a/src/Episode3/PlayerState.hh b/src/Episode3/PlayerState.hh index 2909b789..ce40468a 100644 --- a/src/Episode3/PlayerState.hh +++ b/src/Episode3/PlayerState.hh @@ -76,6 +76,7 @@ public: const Location& loc, uint8_t target_team_id) const; uint8_t get_atk_points() const; + uint8_t get_atk_points_nte() const; void get_short_status_for_card_index_in_hand(size_t hand_index, CardShortStatus* stat) const; std::shared_ptr get_deck(); uint8_t get_def_points() const; @@ -95,6 +96,7 @@ public: bool is_mulligan_allowed() const; bool is_team_turn() const; void log_discard(uint16_t card_ref, uint16_t reason); + uint16_t pop_from_discard_log(uint16_t reason); 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(); diff --git a/src/Episode3/PlayerStateSubordinates.cc b/src/Episode3/PlayerStateSubordinates.cc index 143089ae..237765f7 100644 --- a/src/Episode3/PlayerStateSubordinates.cc +++ b/src/Episode3/PlayerStateSubordinates.cc @@ -481,31 +481,33 @@ 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 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 conditions; -/* 00B8 */ parray target_card_refs; -/* 0100 */ +uint8_t ActionChainWithConds::get_adjusted_move_ability_nte(uint8_t ability) const { + for (size_t z = 0; z < this->conditions.size(); z++) { + const auto& cond = this->conditions[z]; + switch (cond.type) { + case ConditionType::IMMOBILE: + case ConditionType::FREEZE: + ability = 0; + break; + case ConditionType::SET_MV_COST_TO_0: + ability = 99; + break; + case ConditionType::ADD_1_TO_MV_COST: + ability--; + break; + case ConditionType::SCALE_MV_COST: + if (cond.value == 0) { + ability = 99; + } else { + ability /= cond.value; + } + break; + default: + break; + } + } + return ability; +} ActionChainWithCondsTrial::ActionChainWithCondsTrial(const ActionChainWithConds& src) : effective_ap(src.chain.effective_ap), diff --git a/src/Episode3/PlayerStateSubordinates.hh b/src/Episode3/PlayerStateSubordinates.hh index be946206..aa36554d 100644 --- a/src/Episode3/PlayerStateSubordinates.hh +++ b/src/Episode3/PlayerStateSubordinates.hh @@ -169,6 +169,8 @@ struct ActionChainWithConds { void set_action_subphase_from_card(std::shared_ptr card); bool can_apply_attack() const; + uint8_t get_adjusted_move_ability_nte(uint8_t ability) const; + std::string str() const; } __attribute__((packed)); diff --git a/src/Episode3/RulerServer.cc b/src/Episode3/RulerServer.cc index 7cabf87e..bbbdfea8 100644 --- a/src/Episode3/RulerServer.cc +++ b/src/Episode3/RulerServer.cc @@ -237,19 +237,25 @@ bool RulerServer::card_has_pierce_or_rampage( uint16_t action_card_ref, uint8_t def_effect_index, AttackMedium attack_medium) const { - auto short_statuses = (client_id != 0xFF) ? this->short_statuses[client_id] : nullptr; + auto short_statuses = (client_id < 4) ? this->short_statuses[client_id] : nullptr; *out_has_rampage = false; - if (cond_type == ConditionType::NONE) { - return false; + bool ret; + bool is_trial = this->server()->options.is_trial(); + if (is_trial) { + ret = true; + } else { + if (cond_type == ConditionType::NONE) { + return false; + } + ret = this->check_usability_or_apply_condition_for_card_refs( + action_card_ref, + attacker_card_ref, + // Original code omitted this null check and presumably could crash here + short_statuses ? short_statuses->at(0).card_ref.load() : 0xFFFF, + def_effect_index, + attack_medium); } - bool ret = this->check_usability_or_apply_condition_for_card_refs( - action_card_ref, - attacker_card_ref, - // Original code omitted this null check and presumably could crash here - short_statuses ? short_statuses->at(0).card_ref.load() : 0xFFFF, - def_effect_index, - attack_medium); switch (cond_type) { case ConditionType::RAMPAGE: @@ -282,7 +288,10 @@ bool RulerServer::card_has_pierce_or_rampage( if (short_statuses) { const auto& sc_status = short_statuses->at(0); auto ce = this->definition_for_card_ref(sc_status.card_ref); - if (ce && (this->get_card_ref_max_hp(sc_status.card_ref) <= sc_status.current_hp * 2)) { + // This appears to be an NTE bug: Major Pierce doesn't work on Arkz SCs. + if (ce && + (!is_trial || (ce->def.type == CardType::HUNTERS_SC)) && + (this->get_card_ref_max_hp(sc_status.card_ref) <= sc_status.current_hp * 2)) { return ret; } } @@ -292,8 +301,7 @@ bool RulerServer::card_has_pierce_or_rampage( } } -bool RulerServer::attack_action_has_rampage_and_not_pierce( - const ActionState& pa, uint16_t card_ref) const { +bool RulerServer::attack_action_has_rampage_and_not_pierce(const ActionState& pa, uint16_t card_ref) const { uint16_t orig_card_ref; uint16_t effective_range_card_id; TargetMode effective_target_mode; @@ -367,15 +375,15 @@ bool RulerServer::attack_action_has_rampage_and_not_pierce( return false; } -bool RulerServer::attack_action_has_pierce_and_not_rampage( - const ActionState& pa, uint8_t client_id) { - if ((client_id_for_card_ref(pa.attacker_card_ref) == 0xFF) || (client_id == 0xFF)) { +bool RulerServer::attack_action_has_pierce_and_not_rampage(const ActionState& pa, uint8_t client_id) const { + if ((client_id_for_card_ref(pa.attacker_card_ref) == 0xFF) || (client_id >= 4)) { return false; } + bool is_trial = this->server()->options.is_trial(); auto attack_medium = this->get_attack_medium(pa); auto stat = this->short_statuses[client_id]; - if (!stat || !this->card_exists_by_status(stat->at(0)) || (stat->at(0).card_ref == 0xFFFF)) { + if (!stat || (!is_trial && !this->card_exists_by_status(stat->at(0))) || (stat->at(0).card_ref == 0xFFFF)) { return false; } @@ -394,6 +402,33 @@ bool RulerServer::attack_action_has_pierce_and_not_rampage( last_action_card_index = z; } + auto check_chain = [&]() -> optional { + const auto* chain = this->action_chain_with_conds_for_card_ref(pa.attacker_card_ref); + if (chain) { + for (ssize_t cond_index = 8; cond_index >= 0; cond_index--) { + bool has_rampage = false; + if (this->card_has_pierce_or_rampage( + client_id, chain->conditions[cond_index].type, &has_rampage, + pa.attacker_card_ref, chain->conditions[cond_index].card_ref, + chain->conditions[cond_index].card_definition_effect_index, + attack_medium)) { + return true; + } + if (has_rampage) { + return false; + } + } + } + return nullopt; + }; + + if (is_trial) { + auto res = check_chain(); + if (res.has_value()) { + return res.value(); + } + } + for (; last_action_card_index >= 0; last_action_card_index--) { auto ce = this->definition_for_card_ref( pa.action_card_refs[last_action_card_index]); @@ -420,20 +455,10 @@ bool RulerServer::attack_action_has_pierce_and_not_rampage( } } - const auto* chain = this->action_chain_with_conds_for_card_ref(pa.attacker_card_ref); - if (chain) { - for (ssize_t cond_index = 8; cond_index >= 0; cond_index--) { - bool has_rampage = false; - if (this->card_has_pierce_or_rampage( - client_id, chain->conditions[cond_index].type, &has_rampage, - pa.attacker_card_ref, chain->conditions[cond_index].card_ref, - chain->conditions[cond_index].card_definition_effect_index, - attack_medium)) { - return true; - } - if (has_rampage) { - return false; - } + if (!is_trial) { + auto res = check_chain(); + if (res.has_value()) { + return res.value(); } } @@ -795,10 +820,13 @@ bool RulerServer::check_pierce_and_rampage( uint16_t action_card_ref, uint8_t def_effect_index, AttackMedium attack_medium) const { + bool is_trial = this->server()->options.is_trial(); + + // Note: NTE doesn't set this to zero; it apparently expects the caller to. *out_has_pierce = false; const auto* card_short_status = this->short_status_for_card_ref(card_ref); - if (cond_type == ConditionType::NONE) { + if (!is_trial && (cond_type == ConditionType::NONE)) { return false; } @@ -820,8 +848,9 @@ bool RulerServer::check_pierce_and_rampage( client_short_statuses = nullptr; } - bool apply_check_result = this->check_usability_or_apply_condition_for_card_refs( - action_card_ref, attacker_card_ref, card_ref, def_effect_index, attack_medium); + bool apply_check_result = (is_trial || + this->check_usability_or_apply_condition_for_card_refs( + action_card_ref, attacker_card_ref, card_ref, def_effect_index, attack_medium)); switch (cond_type) { case ConditionType::PIERCE: @@ -1366,6 +1395,7 @@ uint16_t RulerServer::compute_attack_or_defense_costs( cost_bias++; } + bool is_trial = this->server()->options.is_trial(); if (pa.action_card_refs[0] == 0xFFFF) { total_cost = cost_bias + 1; } else { @@ -1388,13 +1418,15 @@ uint16_t RulerServer::compute_attack_or_defense_costs( if (this->card_has_mighty_knuckle(pa.action_card_refs[z])) { has_mighty_knuckle = true; } - size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(pa.client_id); - for (size_t w = 0; w < num_assists; w++) { - auto assist_effect = this->assist_server->get_active_assist_by_index(w); - if (assist_effect == AssistEffect::INFLATION) { - assist_cost_bias++; - } else if (assist_effect == AssistEffect::DEFLATION) { - assist_cost_bias--; + if (!is_trial) { + size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(pa.client_id); + for (size_t w = 0; w < num_assists; w++) { + auto assist_effect = this->assist_server->get_active_assist_by_index(w); + if (assist_effect == AssistEffect::INFLATION) { + assist_cost_bias++; + } else if (assist_effect == AssistEffect::DEFLATION) { + assist_cost_bias--; + } } } } @@ -1403,7 +1435,11 @@ uint16_t RulerServer::compute_attack_or_defense_costs( size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(pa.client_id); for (size_t w = 0; w < num_assists; w++) { auto assist_effect = this->assist_server->get_active_assist_by_index(w); - if ((assist_effect == AssistEffect::BATTLE_ROYALE) && + if (!is_trial && (assist_effect == AssistEffect::INFLATION)) { + assist_cost_bias++; + } else if (!is_trial && (assist_effect == AssistEffect::DEFLATION)) { + assist_cost_bias--; + } else if ((assist_effect == AssistEffect::BATTLE_ROYALE) && (pa.action_card_refs[0] == 0xFFFF)) { total_cost = 0; final_cost = 0; @@ -1412,7 +1448,9 @@ uint16_t RulerServer::compute_attack_or_defense_costs( if (has_mighty_knuckle) { if (!allow_mighty_knuckle) { - final_cost = 0; + if (!is_trial) { + final_cost = 0; + } } else { final_cost = max(final_cost, this->hand_and_equip_states[pa.client_id]->atk_points); } @@ -1455,9 +1493,11 @@ bool RulerServer::compute_effective_range_and_target_mode_for_attack( auto target_mode = ce->def.target_mode; if (this->card_ref_or_sc_has_fixed_range(pa.attacker_card_ref)) { card_id = this->card_id_for_card_ref(pa.attacker_card_ref); - auto sc_ce = this->definition_for_card_id(card_id); - if (sc_ce && (static_cast(target_mode) < 6)) { - target_mode = sc_ce->def.target_mode; + if (!this->server()->options.is_trial()) { + auto sc_ce = this->definition_for_card_id(card_id); + if (sc_ce && (static_cast(target_mode) < 6)) { + target_mode = sc_ce->def.target_mode; + } } } @@ -1480,8 +1520,7 @@ bool RulerServer::compute_effective_range_and_target_mode_for_attack( return true; } -size_t RulerServer::count_rampage_targets_for_attack( - const ActionState& pa, uint8_t client_id) const { +size_t RulerServer::count_rampage_targets_for_attack(const ActionState& pa, uint8_t client_id) const { if (client_id == 0xFF) { return 0; } @@ -1638,7 +1677,8 @@ int32_t RulerServer::error_code_for_client_setting_card( return -0x76; } - if (!this->is_card_ref_in_hand(card_ref)) { + bool is_trial = this->server()->options.is_trial(); + if (!is_trial && !this->is_card_ref_in_hand(card_ref)) { return -0x5E; } @@ -1679,7 +1719,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, this->server()->options.is_trial()); - if (((eff == AssistEffect::LEGACY) || (eff == AssistEffect::EXCHANGE)) && + if (((eff == AssistEffect::LEGACY) || (!is_trial && (eff == AssistEffect::EXCHANGE))) && (assist_target_client_id != 0xFF) && (assist_target_client_id != client_id_for_card_ref(card_ref))) { return -0x75; @@ -1718,8 +1758,8 @@ int32_t RulerServer::error_code_for_client_setting_card( if ((ce->def.type == CardType::ITEM) || (ce->def.type == CardType::CREATURE)) { int16_t existing_fcs_cost = 0; - bool limit_summoning_by_count = this->find_condition_on_card_ref( - short_statuses->at(0).card_ref, ConditionType::FC_LIMIT_BY_COUNT); + bool limit_summoning_by_count = !is_trial && + this->find_condition_on_card_ref(short_statuses->at(0).card_ref, ConditionType::FC_LIMIT_BY_COUNT); for (size_t z = 7; z < 15; z++) { const auto& this_status = short_statuses->at(z); if ((this_status.card_ref != 0xFFFF) && this->card_exists_by_status(this_status)) { @@ -1758,71 +1798,91 @@ int32_t RulerServer::error_code_for_client_setting_card( return 0; } - Location summon_area_loc; - uint8_t summon_area_size; - if (!this->get_creature_summon_area( - client_id, &summon_area_loc, &summon_area_size)) { - if (team_id != 1) { - if ((loc->x > 0) && (loc->x < this->map_and_rules->map.width - 1)) { - if ((loc->y < this->map_and_rules->map.height - summon_cost - 1) && - (loc->y > 0)) { - return 0; + if (is_trial) { + // It seems NTE assumes that teams always start on the same ends of the + // map; non-NTE removes this restriction. + if (team_id == 1) { + if (((loc->x < 1) || + (loc->x >= this->map_and_rules->map.width - 1) || + (loc->y < summon_cost + 1) || + (loc->y >= this->map_and_rules->map.height - 1)) && + (loc->y != this->map_and_rules->map.height - 2)) { + return -0x7E; + } + } else if (((loc->x < 1) || + (loc->x >= this->map_and_rules->map.width - 1) || + (loc->y < 1) || + (loc->y >= this->map_and_rules->map.height - summon_cost - 1)) && + (loc->y != 1)) { + return -0x7E; + } + + } else { + Location summon_area_loc; + uint8_t summon_area_size; + if (!this->get_creature_summon_area(client_id, &summon_area_loc, &summon_area_size)) { + if (team_id != 1) { + if ((loc->x > 0) && (loc->x < this->map_and_rules->map.width - 1)) { + if ((loc->y < this->map_and_rules->map.height - summon_cost - 1) && + (loc->y > 0)) { + return 0; + } + if (loc->y == 1) { + return 0; + } } - if (loc->y == 1) { - return 0; + } else { + if ((loc->x > 0) && + (loc->x < this->map_and_rules->map.width - 1)) { + if ((summon_cost + 1 <= loc->y) && (loc->y < this->map_and_rules->map.height - 1)) { + return 0; + } + if (loc->y == this->map_and_rules->map.height - 2) { + return 0; + } } } + return -0x7E; + } + + int32_t x_offset, y_offset; + this->offsets_for_direction(summon_area_loc, &x_offset, &y_offset); + if (x_offset == 0) { + if ((loc->x < 1) && (loc->x >= this->map_and_rules->map.width - 1)) { + return -0x7E; + } } else { - if ((loc->x > 0) && - (loc->x < this->map_and_rules->map.width - 1)) { - if ((summon_cost + 1 <= loc->y) && (loc->y < this->map_and_rules->map.height - 1)) { - return 0; + int16_t diff = max(summon_area_size - summon_cost, 0); + if (x_offset > 0) { + if (loc->x < summon_area_loc.x) { + return -0x7E; } - if (loc->y == this->map_and_rules->map.height - 2) { - return 0; + if (loc->x > summon_area_loc.x + diff) { + return -0x7E; + } + } else if (x_offset < 0) { + if ((loc->x > summon_area_loc.x) || (loc->x < summon_area_loc.x - diff)) { + return -0x7E; } } } - return -0x7E; - } - - int32_t x_offset, y_offset; - this->offsets_for_direction(summon_area_loc, &x_offset, &y_offset); - if (x_offset == 0) { - if ((loc->x < 1) && (loc->x >= this->map_and_rules->map.width - 1)) { - return -0x7E; - } - } else { - int16_t diff = max(summon_area_size - summon_cost, 0); - if (x_offset > 0) { - if (loc->x < summon_area_loc.x) { + if (y_offset == 0) { + if ((loc->y < 1) && (loc->y >= this->map_and_rules->map.height - 1)) { return -0x7E; } - if (loc->x > summon_area_loc.x + diff) { - return -0x7E; - } - } else if (x_offset < 0) { - if ((loc->x > summon_area_loc.x) || (loc->x < summon_area_loc.x - diff)) { - return -0x7E; - } - } - } - if (y_offset == 0) { - if ((loc->y < 1) && (loc->y >= this->map_and_rules->map.height - 1)) { - return -0x7E; - } - } else { - int16_t diff = max(summon_area_size - summon_cost, 0); - if (y_offset > 0) { - if (loc->y < summon_area_loc.y) { - return -0x7E; - } - if (loc->y > summon_area_loc.y + diff) { - return -0x7E; - } - } else if (y_offset < 0) { - if ((loc->y > summon_area_loc.y) || (loc->y < summon_area_loc.y - diff)) { - return -0x7E; + } else { + int16_t diff = max(summon_area_size - summon_cost, 0); + if (y_offset > 0) { + if (loc->y < summon_area_loc.y) { + return -0x7E; + } + if (loc->y > summon_area_loc.y + diff) { + return -0x7E; + } + } else if (y_offset < 0) { + if ((loc->y > summon_area_loc.y) || (loc->y < summon_area_loc.y - diff)) { + return -0x7E; + } } } } @@ -2026,7 +2086,7 @@ uint8_t RulerServer::get_card_ref_max_hp(uint16_t card_ref) const { return 0; } else if (((ce->def.type == CardType::HUNTERS_SC) || (ce->def.type == CardType::ARKZ_SC)) && (this->map_and_rules->rules.char_hp > 0) && - !this->card_ref_is_boss_sc(card_ref)) { + (this->server()->options.is_trial() || !this->card_ref_is_boss_sc(card_ref))) { return this->map_and_rules->rules.char_hp; } else { return ce->def.hp.stat; @@ -2175,7 +2235,7 @@ bool RulerServer::is_attack_valid(const ActionState& pa) { return false; } - if (attacker_card_status->card_flags & 2) { + if (!this->server()->options.is_trial() && (attacker_card_status->card_flags & 2)) { this->error_code3 = -0x60; return false; } @@ -2291,7 +2351,9 @@ bool RulerServer::is_attack_or_defense_valid(const ActionState& pa) { return false; } - int16_t cost = this->compute_attack_or_defense_costs(pa, false, nullptr); + // NTE apparently does not check the action's cost here + bool is_trial = this->server()->options.is_trial(); + int16_t cost = is_trial ? 0 : this->compute_attack_or_defense_costs(pa, false, nullptr); switch (this->get_pending_action_type(pa)) { case ActionType::ATTACK: @@ -2387,8 +2449,9 @@ bool RulerServer::is_defense_valid(const ActionState& pa) { } } - if (this->find_condition_on_card_ref(pa.target_card_refs[0], ConditionType::HOLD) || - this->find_condition_on_card_ref(pa.target_card_refs[0], ConditionType::CANNOT_DEFEND)) { + if (!this->server()->options.is_trial() && + (this->find_condition_on_card_ref(pa.target_card_refs[0], ConditionType::HOLD) || + this->find_condition_on_card_ref(pa.target_card_refs[0], ConditionType::CANNOT_DEFEND))) { this->error_code3 = -0x63; return false; } @@ -2417,30 +2480,53 @@ size_t RulerServer::max_move_distance_for_card_ref(uint32_t card_ref) const { return 0; } - ssize_t ret = ce->def.mv.stat; - - Condition cond; - if (this->find_condition_on_card_ref(card_ref, ConditionType::MV_BONUS, &cond, nullptr, true)) { - ret += cond.value; - } - if (this->find_condition_on_card_ref(card_ref, ConditionType::SET_MV, &cond, nullptr, true)) { - ret = cond.value; - } - ret = max(0, ret); - - size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(client_id); - bool has_stamina_effect = false; - for (size_t z = 0; z < num_assists; z++) { - auto eff = this->assist_server->get_active_assist_by_index(z); - if (eff == AssistEffect::SNAIL_PACE) { - return 1; + if (this->server()->options.is_trial()) { + if (ce->def.type == CardType::ITEM) { + return ce->def.mv.stat; } - if (eff == AssistEffect::STAMINA) { - has_stamina_effect = true; - } - } - return (has_stamina_effect) ? 9 : min(9, ret); + Condition cond; + if (this->find_condition_on_card_ref(card_ref, ConditionType::SET_MV, &cond)) { + return cond.value; + } + + size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(client_id); + bool has_stamina_effect = false; + for (size_t z = 0; z < num_assists; z = z + 1) { + auto assist = this->assist_server->get_active_assist_by_index(z); + if (assist == AssistEffect::SNAIL_PACE) { + return 1; + } else if (assist == AssistEffect::STAMINA) { + has_stamina_effect = true; + } + } + return has_stamina_effect ? 99 : ce->def.mv.stat; + + } else { + ssize_t ret = ce->def.mv.stat; + Condition cond; + if (this->find_condition_on_card_ref(card_ref, ConditionType::MV_BONUS, &cond, nullptr, true)) { + ret += cond.value; + } + if (this->find_condition_on_card_ref(card_ref, ConditionType::SET_MV, &cond, nullptr, true)) { + ret = cond.value; + } + ret = max(0, ret); + + size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(client_id); + bool has_stamina_effect = false; + for (size_t z = 0; z < num_assists; z++) { + auto eff = this->assist_server->get_active_assist_by_index(z); + if (eff == AssistEffect::SNAIL_PACE) { + return 1; + } + if (eff == AssistEffect::STAMINA) { + has_stamina_effect = true; + } + } + + return has_stamina_effect ? 9 : min(9, ret); + } } RulerServer::MovePath::MovePath() @@ -2552,9 +2638,11 @@ int32_t RulerServer::set_cost_for_card(uint8_t client_id, uint16_t card_ref) con return -0x7D; } + bool is_trial = this->server()->options.is_trial(); auto short_statuses = this->short_statuses[client_id]; int32_t ret = ce->def.self_cost; - if (short_statuses && + if (!is_trial && + short_statuses && this->card_exists_by_status(short_statuses->at(0)) && this->find_condition_on_card_ref(short_statuses->at(0).card_ref, ConditionType::UNKNOWN_69)) { ret = 0; @@ -2587,9 +2675,8 @@ int32_t RulerServer::set_cost_for_card(uint8_t client_id, uint16_t card_ref) con for (size_t z = 0; z < num_assists; z++) { auto eff = this->assist_server->get_active_assist_by_index(z); if (eff == AssistEffect::LAND_PRICE) { - // Note: Original code had an extra addend (ret < 0 && (ret & 1) != 0), - // but ret cannot be negatve here, so we omit it. - ret += ret >> 1; + // In NTE, Land Price is apparently 2x rather than 1.5x + ret = is_trial ? (ret << 1) : (ret + (ret >> 1)); } else if (eff == AssistEffect::DEFLATION) { ret = max(0, ret - 1); } else if (eff == AssistEffect::INFLATION) { @@ -2674,4 +2761,24 @@ int32_t RulerServer::verify_deck( return 0; } +size_t RulerServer::count_targets_with_rampage_and_not_pierce_nte(const ActionState& as) const { + size_t ret = 0; + for (size_t z = 0; (z < as.target_card_refs.size()) && (as.target_card_refs[z] != 0xFFFF); z++) { + if (this->attack_action_has_rampage_and_not_pierce(as, as.target_card_refs[z])) { + ret++; + } + } + return ret; +} + +size_t RulerServer::count_targets_with_pierce_and_not_rampage_nte(const ActionState& as) const { + size_t ret = 0; + for (size_t z = 0; (z < as.target_card_refs.size()) && (as.target_card_refs[z] != 0xFFFF); z++) { + if (this->attack_action_has_pierce_and_not_rampage(as, client_id_for_card_ref(as.target_card_refs[z]))) { + ret++; + } + } + return ret; +} + } // namespace Episode3 diff --git a/src/Episode3/RulerServer.hh b/src/Episode3/RulerServer.hh index bf939ffd..11035a62 100644 --- a/src/Episode3/RulerServer.hh +++ b/src/Episode3/RulerServer.hh @@ -61,7 +61,9 @@ public: 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_pierce_and_not_rampage(const ActionState& pa, uint8_t client_id) const; + size_t count_targets_with_rampage_and_not_pierce_nte(const ActionState& as) const; + size_t count_targets_with_pierce_and_not_rampage_nte(const ActionState& as) const; 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; diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index 3d89239f..8688b451 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -938,9 +938,15 @@ void Server::end_action_phase() { // that this can only ever be 0 or 2, but we may have to delete the enum if // that turns out to be false. this->action_subphase = static_cast(static_cast(this->action_subphase) + 2); - this->copy_player_states_to_prev_states(); - this->unknown_8023EEF4(); - this->send_set_card_updates_and_6xB4x04_if_needed(); + if (this->options.is_trial()) { + this->unknown_8023EEF4(); + this->update_battle_state_flags_and_send_6xB4x03_if_needed(0); + this->send_6xB4x02_for_all_players_if_needed(); + } else { + this->copy_player_states_to_prev_states(); + this->unknown_8023EEF4(); + this->send_set_card_updates_and_6xB4x04_if_needed(); + } } bool Server::enqueue_attack_or_defense(uint8_t client_id, ActionState* pa) { @@ -982,8 +988,7 @@ bool Server::enqueue_attack_or_defense(uint8_t client_id, ActionState* pa) { size_t attack_index = this->num_pending_attacks++; this->pending_attacks[attack_index] = *pa; ps->set_action_cards_for_action_state(*pa); - auto card = this->card_for_set_card_ref(this->send_6xB4x06_if_card_ref_invalid( - pa->attacker_card_ref, 1)); + auto card = this->card_for_set_card_ref(this->send_6xB4x06_if_card_ref_invalid(pa->attacker_card_ref, 1)); if (card) { card->card_flags |= 0x400; auto card_ps = card->player_state(); @@ -1481,6 +1486,8 @@ void Server::set_player_deck_valid(uint8_t client_id) { } void Server::setup_and_start_battle() { + bool is_trial = this->options.is_trial(); + this->setup_phase = SetupPhase::STARTER_ROLLS; // Note: This is where original implementation re-seeds random_crypt (it uses @@ -1488,14 +1495,16 @@ void Server::setup_and_start_battle() { for (size_t z = 0; z < 4; z++) { if (!this->check_presence_entry(z)) { - this->name_entries[z].clear(); + if (!is_trial) { + this->name_entries[z].clear(); + } } else { this->player_states[z] = make_shared(z, this->shared_from_this()); this->player_states[z]->init(); } } - if (this->map_and_rules->rules.hp_type == HPType::COMMON_HP) { + if (!is_trial && (this->map_and_rules->rules.hp_type == HPType::COMMON_HP)) { int16_t team_hp[2] = {99, 99}; for (size_t z = 0; z < 4; z++) { auto ps = this->player_states[z]; @@ -1542,13 +1551,22 @@ void Server::setup_and_start_battle() { } } - // this->__unused6__ = 0; + // Non-NTE: + // this->__unused6__ = 0; + // NTE: + // this->unknown_a1 = 0; + // this->unknown_a2 = 0; for (size_t warp_type = 0; warp_type < 5; warp_type++) { this->warp_positions[warp_type][0].clear(0xFF); this->warp_positions[warp_type][1].clear(0xFF); } + this->num_trap_tiles_nte = 0; + for (size_t z = 0; z < 0x10; z++) { + this->trap_tile_locs_nte[z].clear(0xFF); + } + for (size_t y = 0; y < 0x10; y++) { for (size_t x = 0; x < 0x10; x++) { uint8_t tile_spec = this->overlay_state.tiles[y][x]; @@ -1564,59 +1582,71 @@ void Server::setup_and_start_battle() { } } else if ((tile_type == 0x10) || (tile_type == 0x20) || (tile_type == 0x50)) { this->map_and_rules->map.tiles[y][x] = 0; + } else if (is_trial && (tile_type == 0x40) && (this->num_trap_tiles_nte < 0x10)) { + this->trap_tile_locs_nte[this->num_trap_tiles_nte][0] = x; + this->trap_tile_locs_nte[this->num_trap_tiles_nte][1] = y; + this->num_trap_tiles_nte++; } } } - for (size_t trap_type = 0; trap_type < 5; trap_type++) { - this->chosen_trap_tile_index_of_type[trap_type] = 0xFF; + if (!is_trial) { + for (size_t trap_type = 0; trap_type < 5; trap_type++) { + this->chosen_trap_tile_index_of_type[trap_type] = 0xFF; - size_t num_trap_tiles = 0; - for (size_t y = 0; y < 0x10; y++) { - for (size_t x = 0; x < 0x10; x++) { - if ((this->overlay_state.tiles[y][x] == (trap_type | 0x40)) && - (num_trap_tiles < 8)) { - this->trap_tile_locs[trap_type][num_trap_tiles][0] = x; - this->trap_tile_locs[trap_type][num_trap_tiles][1] = y; - num_trap_tiles++; + size_t num_trap_tiles = 0; + for (size_t y = 0; y < 0x10; y++) { + for (size_t x = 0; x < 0x10; x++) { + if ((this->overlay_state.tiles[y][x] == (trap_type | 0x40)) && + (num_trap_tiles < 8)) { + this->trap_tile_locs[trap_type][num_trap_tiles][0] = x; + this->trap_tile_locs[trap_type][num_trap_tiles][1] = y; + num_trap_tiles++; + } } } - } - this->num_trap_tiles_of_type[trap_type] = num_trap_tiles; + this->num_trap_tiles_of_type[trap_type] = num_trap_tiles; - if (num_trap_tiles > 0) { - this->chosen_trap_tile_index_of_type[trap_type] = this->get_random(num_trap_tiles); + if (num_trap_tiles > 0) { + this->chosen_trap_tile_index_of_type[trap_type] = this->get_random(num_trap_tiles); + } } } - this->send_6xB4x02_for_all_players_if_needed(true); - this->send_6xB4x05(); + if (is_trial) { + this->send_all_state_updates(); + this->send_6xB4x02_for_all_players_if_needed(); - for (size_t z = 0; z < 4; z++) { - auto ps = this->player_states[z]; - if (ps) { - ps->send_set_card_updates(true); - } - } - - this->send_all_state_updates(); - this->send_6xB4x1C_names_update(); - this->registration_phase = RegistrationPhase::BATTLE_STARTED; - this->update_battle_state_flags_and_send_6xB4x03_if_needed(true); - this->send_6xB4x50_trap_tile_locations(); - - if (this->options.is_trial()) { G_UpdateMap_Ep3NTE_6xB4x05 cmd; cmd.state = *this->map_and_rules; cmd.start_battle = 1; this->send(cmd); + } else { + this->send_6xB4x02_for_all_players_if_needed(true); + this->send_6xB4x05(); + + for (size_t z = 0; z < 4; z++) { + auto ps = this->player_states[z]; + if (ps) { + ps->send_set_card_updates(true); + } + } + + this->send_all_state_updates(); + this->send_6xB4x1C_names_update(); + } + + this->registration_phase = RegistrationPhase::BATTLE_STARTED; + this->update_battle_state_flags_and_send_6xB4x03_if_needed(true); + + if (!is_trial) { + this->send_6xB4x50_trap_tile_locations(); G_UpdateMap_Ep3_6xB4x05 cmd; cmd.state = *this->map_and_rules; cmd.start_battle = 1; this->send(cmd); } - this->battle_start_usecs = now(); this->send_6xB4x46(); @@ -1848,16 +1878,18 @@ void Server::handle_CAx0D_end_non_action_phase(shared_ptr, const string& throw runtime_error("invalid client ID"); } - 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); + if (!this->options.is_trial()) { + 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, static_cast(in_cmd.battle_phase.load())); 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.response_phase = this->options.is_trial() ? 0 : 2; this->send(out_cmd_fin); } @@ -1923,7 +1955,9 @@ void Server::handle_CAx0F_set_card_from_hand(shared_ptr, const string& d if (in_cmd.client_id >= 4) { error_code = -0x78; } - if (error_code == 0) { + + bool always_send_response = (error_code == 0); + if (always_send_response) { this->ruler_server->error_code1 = 0; auto ps = this->player_states.at(in_cmd.client_id); if (!ps) { @@ -1935,12 +1969,14 @@ void Server::handle_CAx0F_set_card_from_hand(shared_ptr, const string& d this->ruler_server->error_code1 = error_code; } - G_ActionResult_Ep3_6xB4x1E out_cmd; - out_cmd.sequence_num = in_cmd.header.sequence_num; - out_cmd.error_code = this->options.is_trial() ? (was_set ? 0 : 1) : this->ruler_server->error_code1; - this->send(out_cmd); + if (!this->options.is_trial() || always_send_response) { + G_ActionResult_Ep3_6xB4x1E out_cmd; + out_cmd.sequence_num = in_cmd.header.sequence_num; + 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); + this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, this->ruler_server->error_code1); } void Server::handle_CAx10_move_fc_to_location(shared_ptr, const string& data) { @@ -1961,6 +1997,7 @@ void Server::handle_CAx10_move_fc_to_location(shared_ptr, const string& if (in_cmd.client_id >= 4) { error_code = -0x78; } + if (error_code == 0) { auto ps = this->player_states.at(in_cmd.client_id); if (!ps) { @@ -1973,12 +2010,14 @@ void Server::handle_CAx10_move_fc_to_location(shared_ptr, const string& this->ruler_server->error_code2 = error_code; } - 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); + if (!this->options.is_trial() || (this->ruler_server->error_code2 == 0)) { + G_ActionResult_Ep3_6xB4x1E out_cmd; + out_cmd.sequence_num = in_cmd.header.sequence_num; + out_cmd.error_code = this->options.is_trial() ? 0 : this->ruler_server->error_code2; + this->send(out_cmd); + } - this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, out_cmd.error_code); + this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, this->ruler_server->error_code2); } void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr, const string& data) { @@ -2009,12 +2048,15 @@ void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr, const st this->ruler_server->error_code3 = error_code; } - 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); + bool is_trial = this->options.is_trial(); + if (!is_trial || (error_code == 0)) { + G_ActionResult_Ep3_6xB4x1E out_cmd; + out_cmd.sequence_num = in_cmd.header.sequence_num; + out_cmd.error_code = is_trial ? !!this->ruler_server->error_code3 : this->ruler_server->error_code3; + this->send(out_cmd); + } - this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, out_cmd.error_code); + this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, this->ruler_server->error_code3); } void Server::handle_CAx12_end_attack_list(shared_ptr, const string& data) { @@ -2033,9 +2075,11 @@ void Server::handle_CAx12_end_attack_list(shared_ptr, const string& data this->end_attack_list_for_client(in_cmd.client_id); } - G_ActionResult_Ep3_6xB4x1E out_cmd; - out_cmd.sequence_num = in_cmd.header.sequence_num; - this->send(out_cmd); + if (!this->options.is_trial() || (error_code == 0)) { + G_ActionResult_Ep3_6xB4x1E out_cmd; + out_cmd.sequence_num = in_cmd.header.sequence_num; + this->send(out_cmd); + } this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code); } @@ -2194,7 +2238,14 @@ void Server::handle_CAx1D_start_battle(shared_ptr, const string& data) { in_cmd.header.subsubcommand, "START BATTLE"); if (!this->battle_in_progress) { - if (!this->update_registration_phase()) { + bool is_trial = this->options.is_trial(); + + bool should_start = false; + if (is_trial) { + should_start = (this->registration_phase == RegistrationPhase::REGISTERED); + } else if (this->update_registration_phase()) { + should_start = true; + } else { G_RejectBattleStartRequest_Ep3_6xB4x53 out_cmd; out_cmd.setup_phase = this->setup_phase; out_cmd.registration_phase = this->registration_phase; @@ -2206,7 +2257,9 @@ void Server::handle_CAx1D_start_battle(shared_ptr, const string& data) { this->presence_entries[z].clear(); } this->battle_in_progress = false; - } else { + } + + if (should_start) { auto l = this->lobby.lock(); if (l) { if (l->battle_record) { @@ -2668,6 +2721,7 @@ void Server::unknown_8023EEF4() { return; } + bool is_trial = this->options.is_trial(); while (this->unknown_a14 < this->num_pending_attacks_with_cards) { auto card = this->attack_cards[this->unknown_a14]; if (this->get_current_team_turn() == card->get_team_id()) { @@ -2675,7 +2729,11 @@ void Server::unknown_8023EEF4() { log.debug("card @%04hX #%04hX can attack", card->get_card_ref(), card->get_card_id()); string as_str = as.str(); log.debug("as: %s", as_str.c_str()); - this->replace_targets_due_to_destruction_or_conditions(&as); + if (is_trial) { + this->replace_targets_due_to_destruction_nte(&as); + } else { + this->replace_targets_due_to_destruction_or_conditions(&as); + } as_str = as.str(); log.debug("as after target replacement: %s", as_str.c_str()); if (this->any_target_exists_for_attack(as)) { @@ -2697,28 +2755,35 @@ void Server::unknown_8023EEF4() { 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); + if (is_trial) { + this->replace_targets_due_to_destruction_nte(&cmd.state); + } else { + this->replace_targets_due_to_destruction_or_conditions(&cmd.state); + } ActionState as = cmd.state; this->send(cmd); this->card_special->unknown_8024AAB8(as); - this->attack_cards[this->unknown_a14]->compute_action_chain_results(1, 0); - this->attack_cards[this->unknown_a14]->unknown_80236374(this->attack_cards[this->unknown_a14], &as); - if (!this->attack_cards[this->unknown_a14]->action_chain.check_flag(0x40)) { - this->card_special->unknown_8024945C(this->attack_cards[this->unknown_a14], as); - } - this->attack_cards[this->unknown_a14]->compute_action_chain_results(1, 0); - this->attack_cards[this->unknown_a14]->unknown_80236374(this->attack_cards[this->unknown_a14], &as); - if (!this->attack_cards[this->unknown_a14]->action_chain.check_flag(0x40)) { - this->card_special->unknown_8024966C(this->attack_cards[this->unknown_a14], &as); - } - this->attack_cards[this->unknown_a14]->compute_action_chain_results(1, 0); - this->attack_cards[this->unknown_a14]->unknown_80236374(this->attack_cards[this->unknown_a14], &as); - this->attack_cards[this->unknown_a14]->send_6xB4x4E_4C_4D_if_needed(); - for (size_t z = 0; z < 4; z++) { - auto ps = this->player_states[z]; - if (ps) { - ps->send_set_card_updates(); + + if (!is_trial) { + this->attack_cards[this->unknown_a14]->compute_action_chain_results(1, 0); + this->attack_cards[this->unknown_a14]->unknown_80236374(this->attack_cards[this->unknown_a14], &as); + if (!this->attack_cards[this->unknown_a14]->action_chain.check_flag(0x40)) { + this->card_special->unknown_8024945C(this->attack_cards[this->unknown_a14], as); + } + this->attack_cards[this->unknown_a14]->compute_action_chain_results(1, 0); + this->attack_cards[this->unknown_a14]->unknown_80236374(this->attack_cards[this->unknown_a14], &as); + if (!this->attack_cards[this->unknown_a14]->action_chain.check_flag(0x40)) { + this->card_special->unknown_8024966C(this->attack_cards[this->unknown_a14], &as); + } + this->attack_cards[this->unknown_a14]->compute_action_chain_results(1, 0); + this->attack_cards[this->unknown_a14]->unknown_80236374(this->attack_cards[this->unknown_a14], &as); + this->attack_cards[this->unknown_a14]->send_6xB4x4E_4C_4D_if_needed(); + for (size_t z = 0; z < 4; z++) { + auto ps = this->player_states[z]; + if (ps) { + ps->send_set_card_updates(); + } } } @@ -2733,8 +2798,11 @@ void Server::unknown_8023EEF4() { this->update_battle_state_flags_and_send_6xB4x03_if_needed(); this->send_6xB4x02_for_all_players_if_needed(); } - this->update_battle_state_flags_and_send_6xB4x03_if_needed(); - this->send_6xB4x02_for_all_players_if_needed(); + + if (!is_trial) { + this->update_battle_state_flags_and_send_6xB4x03_if_needed(); + this->send_6xB4x02_for_all_players_if_needed(); + } } void Server::execute_bomb_assist_effect() { @@ -2771,10 +2839,58 @@ void Server::execute_bomb_assist_effect() { } } -void Server::replace_targets_due_to_destruction_or_conditions( - ActionState* as) { - auto attacker_card = this->card_for_set_card_ref( - this->send_6xB4x06_if_card_ref_invalid(as->attacker_card_ref, 3)); +void Server::replace_targets_due_to_destruction_nte(ActionState* as) { + auto attacker_card = this->card_for_set_card_ref(this->send_6xB4x06_if_card_ref_invalid(as->attacker_card_ref, 3)); + + for (size_t z = 0; z < 0x24; z++) { + auto target_card = this->card_for_set_card_ref(as->target_card_refs[z]); + if (!target_card) { + break; + } + if ((target_card->card_flags & 2) || + (target_card->get_definition()->def.type != CardType::ITEM) || + attacker_card->action_chain.check_flag(0x02)) { + continue; + } + auto ps = target_card->player_state(); + shared_ptr found_guard_item; + for (size_t z = 0; z < 8; z++) { + auto set_card = ps->get_set_card(z); + if (set_card && + (set_card != target_card) && + !(set_card->card_flags & 2) && + set_card->is_guard_item()) { + found_guard_item = set_card; + break; + } + } + auto replaced_target = found_guard_item; + if (!found_guard_item) { + for (size_t z = 0; z < 8; z++) { + auto set_card = ps->get_set_card(z); + if (set_card && (set_card != target_card) && !(set_card->card_flags & 2)) { + replaced_target = set_card; + break; + } + } + } + as->target_card_refs[z] = replaced_target ? replaced_target->get_card_ref() : ps->get_sc_card()->get_card_ref(); + } + + size_t write_offset = 0; + for (size_t z = 0; z < as->target_card_refs.size(); z++) { + if (as->target_card_refs[z] != 0xFFFF) { + if (z != write_offset) { + as->target_card_refs[write_offset] = as->target_card_refs[z]; + } + write_offset++; + } + } + as->target_card_refs.clear_after(write_offset, 0xFFFF); +} + +void Server::replace_targets_due_to_destruction_or_conditions(ActionState* as) { + auto attacker_card = this->card_for_set_card_ref(this->send_6xB4x06_if_card_ref_invalid(as->attacker_card_ref, 3)); if (!attacker_card) { as->target_card_refs[0] = 0xFFFF; return; diff --git a/src/Episode3/Server.hh b/src/Episode3/Server.hh index 97a795de..23a16160 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -217,6 +217,7 @@ public: 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_nte(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; @@ -312,6 +313,8 @@ public: parray num_trap_tiles_of_type; parray chosen_trap_tile_index_of_type; parray, 8>, 5> trap_tile_locs; + parray, 0x10> trap_tile_locs_nte; + size_t num_trap_tiles_nte; ActionState pb_action_states[4]; parray has_done_pb; parray, 4> has_done_pb_with_client; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 2576f836..5e05231d 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -78,7 +78,7 @@ static shared_ptr proxy_options_menu_for_client(shared_ptrproxy_allow_save_files) {