Ep3 NTE checkpoint 4

This commit is contained in:
Martin Michelsen
2024-02-07 00:36:18 -08:00
parent 7fad72ef9c
commit ba53f67097
15 changed files with 789 additions and 413 deletions
+58 -15
View File
@@ -446,6 +446,11 @@ void Card::destroy_set_card(shared_ptr<Card> 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) {
+18 -21
View File
@@ -3810,25 +3810,28 @@ int16_t CardSpecial::max_all_attack_bonuses(size_t* out_count) const {
void CardSpecial::unknown_80244AA8(shared_ptr<Card> 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<Card> CardSpecial::sc_card_for_card(shared_ptr<Card> 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);
+14 -5
View File
@@ -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;
}
+6 -2
View File
@@ -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:
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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<parray<uint8_t, 0x10>, 0x10> tiles;
parray<le_uint32_t, 5> unused1;
parray<le_uint32_t, 0x10> unused2;
parray<le_uint16_t, 0x10> unused3;
parray<le_uint16_t, 0x10> trap_card_ids_nte; // Unused on non-NTE
OverlayState();
void clear();
+198 -109
View File
@@ -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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<Card>(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;
+2
View File
@@ -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<DeckState> 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();
+27 -25
View File
@@ -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<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 */
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),
+2
View File
@@ -169,6 +169,8 @@ struct ActionChainWithConds {
void set_action_subphase_from_card(std::shared_ptr<const Card> card);
bool can_apply_attack() const;
uint8_t get_adjusted_move_ability_nte(uint8_t ability) const;
std::string str() const;
} __attribute__((packed));
+245 -138
View File
@@ -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<bool> {
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<int16_t>(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<uint8_t>(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<uint8_t>(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<int16_t>(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<int16_t>(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<int16_t>(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<int16_t>(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<ssize_t>(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<ssize_t>(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<ssize_t>(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<ssize_t>(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<int32_t>(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
+3 -1
View File
@@ -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;
+206 -90
View File
@@ -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<ActionSubphase>(static_cast<uint8_t>(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<PlayerState>(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<Client>, 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<BattlePhase>(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<Client>, 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<Client>, 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<Client>, const string& data) {
@@ -1961,6 +1997,7 @@ void Server::handle_CAx10_move_fc_to_location(shared_ptr<Client>, 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<Client>, 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<Client>, const string& data) {
@@ -2009,12 +2048,15 @@ void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr<Client>, 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<Client>, const string& data) {
@@ -2033,9 +2075,11 @@ 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_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<Client>, 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<Client>, 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<Card> found_guard_item;
for (size_t z = 0; z < 8; z++) {
auto set_card = ps->get_set_card(z);
if (set_card &&
(set_card != target_card) &&
!(set_card->card_flags & 2) &&
set_card->is_guard_item()) {
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;
+3
View File
@@ -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<uint8_t, 5> num_trap_tiles_of_type;
parray<uint8_t, 5> chosen_trap_tile_index_of_type;
parray<parray<parray<uint8_t, 2>, 8>, 5> trap_tile_locs;
parray<parray<uint8_t, 2>, 0x10> trap_tile_locs_nte;
size_t num_trap_tiles_nte;
ActionState pb_action_states[4];
parray<uint8_t, 4> has_done_pb;
parray<parray<uint8_t, 4>, 4> has_done_pb_with_client;
+1 -1
View File
@@ -78,7 +78,7 @@ static shared_ptr<const Menu> proxy_options_menu_for_client(shared_ptr<const Cli
add_flag_option(ProxyOptionsMenuItemID::EP3_INFINITE_TIME, Client::Flag::PROXY_EP3_INFINITE_TIME_ENABLED,
"Infinite time", "Disable overall and\nper-phase time limits\nin battle");
add_flag_option(ProxyOptionsMenuItemID::EP3_UNMASK_WHISPERS, Client::Flag::PROXY_EP3_UNMASK_WHISPERS,
"Unmask whispers", "Show contents of\nwhisper messages even\nif they are not for\nyou");
"Unmask whispers", "Show contents of\nwhisper messages\neven if they are not\nfor you");
}
}
if (s->proxy_allow_save_files) {