update formatting in src/Episode3

This commit is contained in:
Martin Michelsen
2025-12-06 00:18:53 -08:00
parent 6291e42ba9
commit 976a281e93
18 changed files with 697 additions and 1125 deletions
+18 -29
View File
@@ -11,24 +11,19 @@ const vector<uint16_t>& all_assist_card_ids(bool is_nte) {
// code. This is relevant for consistency of results when choosing a random card // code. This is relevant for consistency of results when choosing a random card
// (for God Whim). // (for God Whim).
static const vector<uint16_t> ALL_ASSIST_CARD_IDS_TRIAL = { static const vector<uint16_t> ALL_ASSIST_CARD_IDS_TRIAL = {
0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102,
0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x010F, 0x0121,
0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x010F, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132,
0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x0133, 0x0134, 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D, 0x013E, 0x013F, 0x0140,
0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132, 0x0133, 0x0134, 0x0135, 0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0148, 0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F, 0x0240,
0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D, 0x013E, 0x0241, 0x0242};
0x013F, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0148,
0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F, 0x0240, 0x0241, 0x0242};
static const vector<uint16_t> ALL_ASSIST_CARD_IDS_FINAL = { static const vector<uint16_t> ALL_ASSIST_CARD_IDS_FINAL = {
0x0018, 0x0019, 0x001A, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x0018, 0x0019, 0x001A, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF,
0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102, 0x0103, 0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D,
0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010E, 0x010F, 0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F,
0x010D, 0x010E, 0x010F, 0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x0130, 0x0131, 0x0132, 0x0133, 0x0134, 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D,
0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132, 0x013E, 0x013F, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0148, 0x014A, 0x014B, 0x014C, 0x014D,
0x0133, 0x0134, 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, 0x014E, 0x023F, 0x0240, 0x0241, 0x0242};
0x013C, 0x013D, 0x013E, 0x013F, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144,
0x0145, 0x0146, 0x0148, 0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F,
0x0240, 0x0241, 0x0242};
return is_nte ? ALL_ASSIST_CARD_IDS_TRIAL : ALL_ASSIST_CARD_IDS_FINAL; return is_nte ? ALL_ASSIST_CARD_IDS_TRIAL : ALL_ASSIST_CARD_IDS_FINAL;
} }
@@ -174,13 +169,11 @@ uint32_t AssistServer::compute_num_assist_effects_for_client(uint16_t client_id)
if (ce->def.target_mode == TargetMode::TEAM) { if (ce->def.target_mode == TargetMode::TEAM) {
auto this_deck_entry = this->deck_entries[client_id]; auto this_deck_entry = this->deck_entries[client_id];
auto other_deck_entry = this->deck_entries[z]; auto other_deck_entry = this->deck_entries[z];
if (this_deck_entry && other_deck_entry && if (this_deck_entry && other_deck_entry && (this_deck_entry->team_id == other_deck_entry->team_id)) {
(this_deck_entry->team_id == other_deck_entry->team_id)) {
affected = true; affected = true;
} }
} else if ((ce->def.target_mode == TargetMode::SELF) && (z == client_id)) { } else if (((ce->def.target_mode == TargetMode::SELF) && (z == client_id)) ||
affected = true; (ce->def.target_mode == TargetMode::EVERYONE)) {
} else if (ce->def.target_mode == TargetMode::EVERYONE) {
affected = true; affected = true;
} }
if (affected) { if (affected) {
@@ -226,9 +219,8 @@ bool AssistServer::should_block_assist_effects_for_client(uint16_t client_id) co
(this->deck_entries[client_id]->team_id == this->deck_entries[z]->team_id)) { (this->deck_entries[client_id]->team_id == this->deck_entries[z]->team_id)) {
return true; return true;
} }
} else if ((ce->def.target_mode == TargetMode::SELF) && (client_id == z)) { } else if (((ce->def.target_mode == TargetMode::SELF) && (client_id == z)) ||
return true; (ce->def.target_mode == TargetMode::EVERYONE)) {
} else if (ce->def.target_mode == TargetMode::EVERYONE) {
return true; return true;
} }
} }
@@ -237,10 +229,7 @@ bool AssistServer::should_block_assist_effects_for_client(uint16_t client_id) co
} }
AssistEffect AssistServer::get_active_assist_by_index(size_t index) const { AssistEffect AssistServer::get_active_assist_by_index(size_t index) const {
if (index < this->num_active_assists) { return (index < this->num_active_assists) ? this->active_assist_effects[index] : AssistEffect::NONE;
return this->active_assist_effects[index];
}
return AssistEffect::NONE;
} }
void AssistServer::populate_effects() { void AssistServer::populate_effects() {
+30 -49
View File
@@ -7,11 +7,7 @@ using namespace std;
namespace Episode3 { namespace Episode3 {
Card::Card( Card::Card(uint16_t card_id, uint16_t card_ref, uint16_t client_id, shared_ptr<Server> server)
uint16_t card_id,
uint16_t card_ref,
uint16_t client_id,
shared_ptr<Server> server)
: w_server(server), : w_server(server),
w_player_state(server->get_player_state(client_id)), w_player_state(server->get_player_state(client_id)),
client_id(client_id), client_id(client_id),
@@ -132,8 +128,7 @@ ssize_t Card::apply_abnormal_condition(
if (cond.type == eff.type) { if (cond.type == eff.type) {
existing_cond_index = z; existing_cond_index = z;
if ((!is_nte && eff.type == ConditionType::MV_BONUS) || if ((!is_nte && eff.type == ConditionType::MV_BONUS) ||
((cond.card_definition_effect_index == def_effect_index) && ((cond.card_definition_effect_index == def_effect_index) && (cond.card_ref == target_card_ref))) {
(cond.card_ref == target_card_ref))) {
break; break;
} }
} else { } else {
@@ -173,11 +168,7 @@ ssize_t Card::apply_abnormal_condition(
cond.condition_giver_card_ref = sc_card_ref; cond.condition_giver_card_ref = sc_card_ref;
cond.card_definition_effect_index = def_effect_index; cond.card_definition_effect_index = def_effect_index;
cond.order = 10; cond.order = 10;
if (dice_roll_value < 0) { cond.dice_roll_value = (dice_roll_value < 0) ? this->player_state()->roll_dice_with_effects(1) : dice_roll_value;
cond.dice_roll_value = this->player_state()->roll_dice_with_effects(1);
} else {
cond.dice_roll_value = dice_roll_value;
}
cond.flags = 0; cond.flags = 0;
cond.value = value + existing_cond_value; cond.value = value + existing_cond_value;
cond.value8 = value + existing_cond_value; cond.value8 = value + existing_cond_value;
@@ -309,8 +300,7 @@ void Card::commit_attack(
size_t num_assists = s->assist_server->compute_num_assist_effects_for_client(this->client_id); size_t num_assists = s->assist_server->compute_num_assist_effects_for_client(this->client_id);
for (size_t z = 0; z < num_assists; z++) { for (size_t z = 0; z < num_assists; z++) {
auto eff = s->assist_server->get_active_assist_by_index(z); auto eff = s->assist_server->get_active_assist_by_index(z);
if ((eff == AssistEffect::RANSOM) && if ((eff == AssistEffect::RANSOM) && (attacker_card->action_chain.chain.attack_medium == AttackMedium::PHYSICAL)) {
(attacker_card->action_chain.chain.attack_medium == AttackMedium::PHYSICAL)) {
uint8_t team_id = this->player_state()->get_team_id(); uint8_t team_id = this->player_state()->get_team_id();
int16_t exp_amount = clamp<int16_t>(s->team_exp[team_id], 0, effective_damage); int16_t exp_amount = clamp<int16_t>(s->team_exp[team_id], 0, effective_damage);
s->team_exp[team_id] -= exp_amount; s->team_exp[team_id] -= exp_amount;
@@ -339,8 +329,7 @@ void Card::commit_attack(
this->current_hp = clamp<int16_t>(this->current_hp - effective_damage, 0, this->max_hp); this->current_hp = clamp<int16_t>(this->current_hp - effective_damage, 0, this->max_hp);
log.debug_f("hp set to {}", this->current_hp); log.debug_f("hp set to {}", this->current_hp);
if ((effective_damage > 0) && if ((effective_damage > 0) && (attacker_ps->stats.max_attack_damage < effective_damage)) {
(attacker_ps->stats.max_attack_damage < effective_damage)) {
attacker_ps->stats.max_attack_damage = effective_damage; attacker_ps->stats.max_attack_damage = effective_damage;
log.debug_f("attacker new max damage {}", effective_damage); log.debug_f("attacker new max damage {}", effective_damage);
} }
@@ -396,8 +385,10 @@ int16_t Card::compute_defense_power_for_attacker_card(shared_ptr<const Card> att
} }
} }
s->card_special->apply_action_conditions(EffectWhen::BEFORE_ANY_CARD_ATTACK, attacker_card, this->shared_from_this(), 0x08, nullptr); s->card_special->apply_action_conditions(
s->card_special->apply_action_conditions(EffectWhen::BEFORE_ANY_CARD_ATTACK, attacker_card, this->shared_from_this(), 0x10, nullptr); EffectWhen::BEFORE_ANY_CARD_ATTACK, attacker_card, this->shared_from_this(), 0x08, nullptr);
s->card_special->apply_action_conditions(
EffectWhen::BEFORE_ANY_CARD_ATTACK, attacker_card, this->shared_from_this(), 0x10, nullptr);
return this->action_metadata.defense_power + this->action_metadata.defense_bonus; return this->action_metadata.defense_power + this->action_metadata.defense_bonus;
} }
@@ -439,8 +430,7 @@ void Card::destroy_set_card(shared_ptr<Card> attacker_card) {
} }
} }
if ((s->map_and_rules->rules.hp_type == HPType::DEFEAT_TEAM) && if ((s->map_and_rules->rules.hp_type == HPType::DEFEAT_TEAM) && (ps->get_sc_card().get() == this)) {
(ps->get_sc_card().get() == this)) {
for (size_t set_index = 0; set_index < 8; set_index++) { for (size_t set_index = 0; set_index < 8; set_index++) {
auto card = ps->get_set_card(set_index); auto card = ps->get_set_card(set_index);
if (card) { if (card) {
@@ -464,7 +454,7 @@ void Card::destroy_set_card(shared_ptr<Card> attacker_card) {
uint8_t other_team_id = s->player_states[client_id]->get_team_id(); uint8_t other_team_id = s->player_states[client_id]->get_team_id();
uint8_t this_team_id = ps->get_team_id(); uint8_t this_team_id = ps->get_team_id();
if (this_team_id == other_team_id) { if (this_team_id == other_team_id) {
s->add_team_exp(team_id, this->max_hp); s->add_team_exp(this_team_id, this->max_hp);
} }
} }
} }
@@ -488,8 +478,7 @@ int32_t Card::error_code_for_move_to_location(const Location& loc) const {
if (this->card_flags & 2) { if (this->card_flags & 2) {
return -0x60; return -0x60;
} }
if (!this->server()->ruler_server->card_ref_can_move( if (!this->server()->ruler_server->card_ref_can_move(this->client_id, this->card_ref, 1)) {
this->client_id, this->card_ref, 1)) {
return -0x7B; return -0x7B;
} }
// Note: The original code passes non-null pointers here but ignores the // Note: The original code passes non-null pointers here but ignores the
@@ -529,9 +518,7 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
if (attacker_card->action_chain.chain.attack_medium == AttackMedium::UNKNOWN_03) { if (attacker_card->action_chain.chain.attack_medium == AttackMedium::UNKNOWN_03) {
// Probably Resta // Probably Resta
for (size_t strike_num = 0; strike_num < attacker_card->action_chain.chain.strike_count; strike_num++) { for (size_t strike_num = 0; strike_num < attacker_card->action_chain.chain.strike_count; strike_num++) {
this->current_hp = min<int16_t>( this->current_hp = min<int16_t>(this->current_hp + attacker_card->action_chain.chain.effective_tp, this->max_hp);
this->current_hp + attacker_card->action_chain.chain.effective_tp,
this->max_hp);
} }
this->propagate_shared_hp_if_needed(); this->propagate_shared_hp_if_needed();
cmd.effect.tp = attacker_card->action_chain.chain.effective_tp; cmd.effect.tp = attacker_card->action_chain.chain.effective_tp;
@@ -613,11 +600,7 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
} }
bool Card::get_condition_value( bool Card::get_condition_value(
ConditionType cond_type, ConditionType cond_type, uint16_t card_ref, uint8_t def_effect_index, uint16_t value, uint16_t* out_value) const {
uint16_t card_ref,
uint8_t def_effect_index,
uint16_t value,
uint16_t* out_value) const {
return this->action_chain.get_condition_value(cond_type, card_ref, def_effect_index, value, out_value); return this->action_chain.get_condition_value(cond_type, card_ref, def_effect_index, value, out_value);
} }
@@ -697,9 +680,7 @@ int32_t Card::move_to_location(const Location& loc) {
this->card_flags = this->card_flags | 0x80; this->card_flags = this->card_flags | 0x80;
// On NTE, traps happen now, not after the Move phase // On NTE, traps happen now, not after the Move phase
if (s->options.is_nte() && if (s->options.is_nte() && this->def_entry->def.is_sc() && ((s->overlay_state.tiles[loc.y][loc.x] & 0xF0) == 0x40)) {
this->def_entry->def.is_sc() &&
((s->overlay_state.tiles[loc.y][loc.x] & 0xF0) == 0x40)) {
for (size_t z = 0; z < 4; z++) { for (size_t z = 0; z < 4; z++) {
auto other_ps = s->player_states[z]; auto other_ps = s->player_states[z];
if (!other_ps) { if (!other_ps) {
@@ -752,8 +733,7 @@ void Card::propagate_shared_hp_if_needed() {
((this->def_entry->def.type == CardType::HUNTERS_SC) || (this->def_entry->def.type == CardType::ARKZ_SC))) { ((this->def_entry->def.type == CardType::HUNTERS_SC) || (this->def_entry->def.type == CardType::ARKZ_SC))) {
for (size_t other_client_id = 0; other_client_id < 4; other_client_id++) { for (size_t other_client_id = 0; other_client_id < 4; other_client_id++) {
auto other_ps = this->server()->player_states[other_client_id]; auto other_ps = this->server()->player_states[other_client_id];
if ((other_client_id != this->client_id) && other_ps && if ((other_client_id != this->client_id) && other_ps && (other_ps->get_team_id() == this->team_id)) {
(other_ps->get_team_id() == this->team_id)) {
other_ps->get_sc_card()->set_current_hp(this->current_hp, false); other_ps->get_sc_card()->set_current_hp(this->current_hp, false);
} }
} }
@@ -906,7 +886,8 @@ void Card::clear_action_chain_and_metadata_and_most_flags() {
void Card::compute_action_chain_results(bool apply_action_conditions, bool ignore_this_card_ap_tp) { void Card::compute_action_chain_results(bool apply_action_conditions, bool ignore_this_card_ap_tp) {
auto s = this->server(); auto s = this->server();
auto log = s->log_stack(std::format("compute_action_chain_results(@{:04X} #{:04X}): ", this->get_card_ref(), this->get_card_id())); auto log = s->log_stack(std::format(
"compute_action_chain_results(@{:04X} #{:04X}): ", this->get_card_ref(), this->get_card_id()));
bool is_nte = s->options.is_nte(); bool is_nte = s->options.is_nte();
this->action_chain.compute_attack_medium(s); this->action_chain.compute_attack_medium(s);
@@ -930,7 +911,8 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
} else { } else {
stat_swap_type = s->card_special->compute_stat_swap_type(this->shared_from_this()); stat_swap_type = s->card_special->compute_stat_swap_type(this->shared_from_this());
log.debug_f("stat_swap_type = {} (0=none, 1=a/t, 2=a/h)", static_cast<size_t>(stat_swap_type)); log.debug_f("stat_swap_type = {} (0=none, 1=a/t, 2=a/h)", static_cast<size_t>(stat_swap_type));
s->card_special->get_effective_ap_tp(stat_swap_type, &effective_ap, &effective_tp, this->get_current_hp(), this->ap, this->tp); s->card_special->get_effective_ap_tp(
stat_swap_type, &effective_ap, &effective_tp, this->get_current_hp(), this->ap, this->tp);
log.debug_f("effective_ap = {}, effective_tp = {}", effective_ap, effective_tp); log.debug_f("effective_ap = {}, effective_tp = {}", effective_ap, effective_tp);
} }
@@ -1082,8 +1064,7 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
} }
auto other_sc_card = other_ps->get_sc_card(); auto other_sc_card = other_ps->get_sc_card();
if (other_sc_card && if (other_sc_card &&
(abs(this->loc.x - other_sc_card->loc.x) < 2) && (abs(this->loc.x - other_sc_card->loc.x) < 2) && (abs(this->loc.y - other_sc_card->loc.y) < 2)) {
(abs(this->loc.y - other_sc_card->loc.y) < 2)) {
num_scs_in_range++; num_scs_in_range++;
} }
} }
@@ -1106,10 +1087,12 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
int16_t damage = 0; int16_t damage = 0;
if (this->action_chain.chain.attack_medium == AttackMedium::TECH) { if (this->action_chain.chain.attack_medium == AttackMedium::TECH) {
damage = this->action_chain.chain.effective_tp + this->action_chain.chain.tp_effect_bonus; damage = this->action_chain.chain.effective_tp + this->action_chain.chain.tp_effect_bonus;
log.debug_f("(tech) damage = {} (eff) + {} (bonus) = {}", this->action_chain.chain.effective_tp, this->action_chain.chain.tp_effect_bonus, damage); log.debug_f("(tech) damage = {} (eff) + {} (bonus) = {}",
this->action_chain.chain.effective_tp, this->action_chain.chain.tp_effect_bonus, damage);
} else if (this->action_chain.chain.attack_medium == AttackMedium::PHYSICAL) { } else if (this->action_chain.chain.attack_medium == AttackMedium::PHYSICAL) {
damage = this->action_chain.chain.effective_ap + this->action_chain.chain.ap_effect_bonus; damage = this->action_chain.chain.effective_ap + this->action_chain.chain.ap_effect_bonus;
log.debug_f("(physical) damage = {} (eff) + {} (bonus) = {}", this->action_chain.chain.effective_ap, this->action_chain.chain.ap_effect_bonus, damage); log.debug_f("(physical) damage = {} (eff) + {} (bonus) = {}",
this->action_chain.chain.effective_ap, this->action_chain.chain.ap_effect_bonus, damage);
} else { } else {
log.debug_f("(unknown attack medium) damage = 0"); log.debug_f("(unknown attack medium) damage = 0");
} }
@@ -1117,7 +1100,8 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
this->action_chain.chain.damage = is_nte this->action_chain.chain.damage = is_nte
? (damage * this->action_chain.chain.damage_multiplier) ? (damage * this->action_chain.chain.damage_multiplier)
: min<int16_t>(damage * this->action_chain.chain.damage_multiplier, 99); : min<int16_t>(damage * this->action_chain.chain.damage_multiplier, 99);
log.debug_f("overall chain damage = {} (base) * {} (mult) = {}", damage, this->action_chain.chain.damage_multiplier, this->action_chain.chain.damage); log.debug_f("overall chain damage = {} (base) * {} (mult) = {}",
damage, this->action_chain.chain.damage_multiplier, this->action_chain.chain.damage);
if (apply_action_conditions) { if (apply_action_conditions) {
auto this_sh = this->shared_from_this(); auto this_sh = this->shared_from_this();
@@ -1271,8 +1255,7 @@ void Card::unknown_80236374(shared_ptr<Card> other_card, const ActionState* as)
} }
void Card::unknown_802379BC(uint16_t card_ref) { void Card::unknown_802379BC(uint16_t card_ref) {
this->action_chain.chain.unknown_card_ref_a3 = this->action_chain.chain.unknown_card_ref_a3 = (card_ref == 0xFFFF) ? this->card_ref : card_ref;
(card_ref == 0xFFFF) ? this->card_ref : card_ref;
} }
void Card::unknown_802379DC(const ActionState& pa) { void Card::unknown_802379DC(const ActionState& pa) {
@@ -1359,8 +1342,7 @@ void Card::dice_phase_before() {
cond.remaining_turns--; cond.remaining_turns--;
} }
if (cond.remaining_turns < 1) { if (cond.remaining_turns < 1) {
s->card_special->apply_stat_deltas_to_card_from_condition_and_clear_cond( s->card_special->apply_stat_deltas_to_card_from_condition_and_clear_cond(cond, this->shared_from_this());
cond, this->shared_from_this());
} }
} }
} }
@@ -1382,8 +1364,7 @@ bool Card::unknown_80236554(shared_ptr<Card> other_card, const ActionState* as)
: std::format("unknown_80236554(@{:04X} #{:04X}, null): ", this->get_card_ref(), this->get_card_id())); : std::format("unknown_80236554(@{:04X} #{:04X}, null): ", this->get_card_ref(), this->get_card_id()));
if (log.should_log(phosg::LogLevel::L_DEBUG)) { if (log.should_log(phosg::LogLevel::L_DEBUG)) {
if (as) { if (as) {
string as_str = as->str(s); log.debug_f("as = {}", as->str(s));
log.debug_f("as = {}", as_str);
} else { } else {
log.debug_f("as = null"); log.debug_f("as = null");
} }
+154 -179
View File
@@ -84,7 +84,8 @@ void CardSpecial::AttackEnvStats::clear() {
this->target_current_hp = 0; this->target_current_hp = 0;
} }
uint32_t CardSpecial::AttackEnvStats::at(size_t index) const { uint32_t CardSpecial::AttackEnvStats::at(size_t index) const {
static_assert(sizeof(parray<uint32_t, 39>) == sizeof(AttackEnvStats), "CardSpecial::AttackEnvStats does not have exactly 39 entries"); static_assert(sizeof(parray<uint32_t, 39>) == sizeof(AttackEnvStats),
"CardSpecial::AttackEnvStats does not have exactly 39 entries");
return reinterpret_cast<const parray<uint32_t, 39>*>(this)->at(index); return reinterpret_cast<const parray<uint32_t, 39>*>(this)->at(index);
} }
@@ -454,10 +455,7 @@ bool CardSpecial::apply_defense_condition(
} }
bool CardSpecial::apply_defense_conditions( bool CardSpecial::apply_defense_conditions(
const ActionState& as, const ActionState& as, EffectWhen when, shared_ptr<Card> defender_card, uint32_t flags) {
EffectWhen when,
shared_ptr<Card> defender_card,
uint32_t flags) {
for (size_t z = 0; z < 9; z++) { for (size_t z = 0; z < 9; z++) {
this->apply_defense_condition(when, &defender_card->action_chain.conditions[z], z, as, defender_card, flags, 0); this->apply_defense_condition(when, &defender_card->action_chain.conditions[z], z, as, defender_card, flags, 0);
} }
@@ -474,14 +472,12 @@ bool CardSpecial::apply_stat_deltas_to_all_cards_from_all_conditions_with_card_r
} }
auto sc_card = ps->get_sc_card(); auto sc_card = ps->get_sc_card();
if (sc_card) { if (sc_card) {
ret |= this->apply_stats_deltas_to_card_from_all_conditions_with_card_ref( ret |= this->apply_stats_deltas_to_card_from_all_conditions_with_card_ref(card_ref, sc_card);
card_ref, sc_card);
} }
for (size_t set_index = 0; set_index < 8; set_index++) { for (size_t set_index = 0; set_index < 8; set_index++) {
auto set_card = ps->get_set_card(set_index); auto set_card = ps->get_set_card(set_index);
if (set_card) { if (set_card) {
ret |= this->apply_stats_deltas_to_card_from_all_conditions_with_card_ref( ret |= this->apply_stats_deltas_to_card_from_all_conditions_with_card_ref(card_ref, set_card);
card_ref, set_card);
} }
} }
} }
@@ -494,8 +490,7 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
auto log = s->log_stack(std::format("apply_stat_deltas_to_card_from_condition_and_clear_cond(@{:04X} #{:04X}): ", card->get_card_ref(), card->get_card_id())); auto log = s->log_stack(std::format("apply_stat_deltas_to_card_from_condition_and_clear_cond(@{:04X} #{:04X}): ", card->get_card_ref(), card->get_card_id()));
bool is_nte = s->options.is_nte(); bool is_nte = s->options.is_nte();
string cond_str = cond.str(s); log.debug_f("cond: {}", cond.str(s));
log.debug_f("cond: {}", cond_str);
ConditionType cond_type = cond.type; ConditionType cond_type = cond.type;
int16_t cond_value = is_nte ? cond.value.load() : clamp<int16_t>(cond.value, -99, 99); int16_t cond_value = is_nte ? cond.value.load() : clamp<int16_t>(cond.value, -99, 99);
@@ -643,10 +638,7 @@ bool CardSpecial::apply_stats_deltas_to_card_from_all_conditions_with_card_ref(
} }
bool CardSpecial::card_has_condition_with_ref( bool CardSpecial::card_has_condition_with_ref(
shared_ptr<const Card> card, shared_ptr<const Card> card, ConditionType cond_type, uint16_t card_ref, uint16_t match_card_ref) const {
ConditionType cond_type,
uint16_t card_ref,
uint16_t match_card_ref) const {
size_t z = 0; size_t z = 0;
while ((z < 9) && while ((z < 9) &&
((card->action_chain.conditions[z].type != cond_type) || ((card->action_chain.conditions[z].type != cond_type) ||
@@ -666,14 +658,11 @@ bool CardSpecial::card_is_destroyed(shared_ptr<const Card> card) const {
if (card->get_current_hp() > 0) { if (card->get_current_hp() > 0) {
return false; return false;
} }
return !this->server()->ruler_server->card_ref_or_any_set_card_has_condition_46( return !this->server()->ruler_server->card_ref_or_any_set_card_has_condition_46(card->get_card_ref());
card->get_card_ref());
} }
void CardSpecial::compute_attack_ap( void CardSpecial::compute_attack_ap(
shared_ptr<const Card> target_card, shared_ptr<const Card> target_card, int16_t* out_value, uint16_t attacker_card_ref) {
int16_t* out_value,
uint16_t attacker_card_ref) {
auto s = this->server(); auto s = this->server();
auto is_nte = s->options.is_nte(); auto is_nte = s->options.is_nte();
@@ -773,9 +762,7 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats(
} }
ast.total_num_set_cards = ps_num_set_cards; ast.total_num_set_cards = ps_num_set_cards;
uint8_t target_card_team_id = target_card uint8_t target_card_team_id = target_card ? target_card->player_state()->get_team_id() : 0xFF;
? target_card->player_state()->get_team_id()
: 0xFF;
size_t target_team_num_set_cards = 0; size_t target_team_num_set_cards = 0;
size_t non_target_team_num_set_cards = 0; size_t non_target_team_num_set_cards = 0;
@@ -831,13 +818,16 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats(
ast.max_hp = card->get_max_hp(); ast.max_hp = card->get_max_hp();
ast.team_dice_bonus = card ? s->team_dice_bonus[card->get_team_id()] : 0; ast.team_dice_bonus = card ? s->team_dice_bonus[card->get_team_id()] : 0;
ast.effective_ap_if_not_tech = (!attacker_card || (attacker_card->action_chain.chain.attack_medium == AttackMedium::TECH)) ast.effective_ap_if_not_tech =
(!attacker_card || (attacker_card->action_chain.chain.attack_medium == AttackMedium::TECH))
? 0 ? 0
: attacker_card->action_chain.chain.damage; : attacker_card->action_chain.chain.damage;
ast.effective_ap_if_not_tech2 = (!attacker_card || (attacker_card->action_chain.chain.attack_medium == AttackMedium::TECH)) ast.effective_ap_if_not_tech2 =
(!attacker_card || (attacker_card->action_chain.chain.attack_medium == AttackMedium::TECH))
? 0 ? 0
: attacker_card->action_chain.chain.damage; : attacker_card->action_chain.chain.damage;
ast.effective_ap_if_not_physical = (!attacker_card || (attacker_card->action_chain.chain.attack_medium == AttackMedium::PHYSICAL)) ast.effective_ap_if_not_physical =
(!attacker_card || (attacker_card->action_chain.chain.attack_medium == AttackMedium::PHYSICAL))
? 0 ? 0
: attacker_card->action_chain.chain.damage; : attacker_card->action_chain.chain.damage;
ast.sc_effective_ap = attacker_card ? attacker_card->action_chain.chain.damage : 0; ast.sc_effective_ap = attacker_card ? attacker_card->action_chain.chain.damage : 0;
@@ -870,9 +860,7 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats(
uint16_t z_ref = pa.attacker_card_ref; uint16_t z_ref = pa.attacker_card_ref;
// Note: The (z < 8) conditions in these two loops are not present in the // Note: The (z < 8) conditions in these two loops are not present in the
// original code. // original code.
for (z = 0; for (z = 0; ((target_card_ref != z_ref) && (z < 8) && ((z_ref = pa.action_card_refs[z]) != 0xFFFF)); z++) {
((target_card_ref != z_ref) && (z < 8) && ((z_ref = pa.action_card_refs[z]) != 0xFFFF));
z++) {
} }
ast.action_cards_ap = 0; ast.action_cards_ap = 0;
@@ -1012,8 +1000,7 @@ shared_ptr<Card> CardSpecial::compute_replaced_target_based_on_conditions(
if (num_candidates > 0) { if (num_candidates > 0) {
uint8_t a = target_ps->roll_dice_with_effects(2); uint8_t a = target_ps->roll_dice_with_effects(2);
uint8_t b = target_ps->roll_dice_with_effects(1); uint8_t b = target_ps->roll_dice_with_effects(1);
return s->card_for_set_card_ref( return s->card_for_set_card_ref(candidate_card_refs[(a + b) - ((a + b) / num_candidates) * num_candidates]);
candidate_card_refs[(a + b) - ((a + b) / num_candidates) * num_candidates]);
} }
} }
} }
@@ -1290,8 +1277,7 @@ size_t CardSpecial::count_action_cards_with_condition_for_all_current_attacks(
for (size_t client_id = 0; client_id < 4; client_id++) { for (size_t client_id = 0; client_id < 4; client_id++) {
auto ps = this->server()->get_player_state(client_id); auto ps = this->server()->get_player_state(client_id);
if (ps) { if (ps) {
ret += this->count_action_cards_with_condition_for_current_attack( ret += this->count_action_cards_with_condition_for_current_attack(ps->get_sc_card(), cond_type, card_ref);
ps->get_sc_card(), cond_type, card_ref);
for (size_t set_index = 0; set_index < 8; set_index++) { for (size_t set_index = 0; set_index < 8; set_index++) {
ret += this->count_action_cards_with_condition_for_current_attack( ret += this->count_action_cards_with_condition_for_current_attack(
ps->get_set_card(set_index), cond_type, card_ref); ps->get_set_card(set_index), cond_type, card_ref);
@@ -1338,8 +1324,7 @@ size_t CardSpecial::count_action_cards_with_condition_for_current_attack(
return ret; return ret;
} }
size_t CardSpecial::count_cards_with_card_id_except_card_ref( size_t CardSpecial::count_cards_with_card_id_except_card_ref(uint16_t card_id, uint16_t card_ref) const {
uint16_t card_id, uint16_t card_ref) const {
size_t ret = 0; size_t ret = 0;
for (size_t client_id = 0; client_id < 4; client_id++) { for (size_t client_id = 0; client_id < 4; client_id++) {
auto ps = this->server()->get_player_state(client_id); auto ps = this->server()->get_player_state(client_id);
@@ -1348,9 +1333,7 @@ size_t CardSpecial::count_cards_with_card_id_except_card_ref(
} }
for (size_t set_index = 0; set_index < 8; set_index++) { for (size_t set_index = 0; set_index < 8; set_index++) {
auto card = ps->get_set_card(set_index); auto card = ps->get_set_card(set_index);
if (card && if (card && (card->get_card_ref() != card_ref) && (card->get_definition()->def.card_id == card_id)) {
(card->get_card_ref() != card_ref) &&
(card->get_definition()->def.card_id == card_id)) {
ret++; ret++;
} }
} }
@@ -1393,8 +1376,7 @@ ActionState CardSpecial::create_attack_state_from_card_action_chain(
shared_ptr<const Card> attacker_card) const { shared_ptr<const Card> attacker_card) const {
ActionState ret; ActionState ret;
if (attacker_card) { if (attacker_card) {
ret.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid( ret.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card->get_card_ref(), 4);
attacker_card->get_card_ref(), 4);
for (size_t z = 0; z < attacker_card->action_chain.chain.attack_action_card_ref_count; z++) { for (size_t z = 0; z < attacker_card->action_chain.chain.attack_action_card_ref_count; z++) {
ret.action_card_refs[z] = this->send_6xB4x06_if_card_ref_invalid( ret.action_card_refs[z] = this->send_6xB4x06_if_card_ref_invalid(
attacker_card->action_chain.chain.attack_action_card_refs[z], 5); attacker_card->action_chain.chain.attack_action_card_refs[z], 5);
@@ -1408,8 +1390,7 @@ ActionState CardSpecial::create_attack_state_from_card_action_chain(
} }
ActionState CardSpecial::create_defense_state_for_card_pair_action_chains( ActionState CardSpecial::create_defense_state_for_card_pair_action_chains(
shared_ptr<const Card> attacker_card, shared_ptr<const Card> attacker_card, shared_ptr<const Card> defender_card) const {
shared_ptr<const Card> defender_card) const {
ActionState ret; ActionState ret;
if (defender_card && attacker_card) { if (defender_card && attacker_card) {
size_t count = 0; size_t count = 0;
@@ -1422,18 +1403,15 @@ ActionState CardSpecial::create_defense_state_for_card_pair_action_chains(
} }
} }
if (defender_card) { if (defender_card) {
ret.target_card_refs[0] = this->send_6xB4x06_if_card_ref_invalid( ret.target_card_refs[0] = this->send_6xB4x06_if_card_ref_invalid(defender_card->get_card_ref(), 8);
defender_card->get_card_ref(), 8);
} }
if (attacker_card) { if (attacker_card) {
ret.original_attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid( ret.original_attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card->get_card_ref(), 9);
attacker_card->get_card_ref(), 9);
} }
return ret; return ret;
} }
void CardSpecial::destroy_card_if_hp_zero( void CardSpecial::destroy_card_if_hp_zero(shared_ptr<Card> card, uint16_t attacker_card_ref) {
shared_ptr<Card> card, uint16_t attacker_card_ref) {
if (card && (card->get_current_hp() <= 0)) { if (card && (card->get_current_hp() <= 0)) {
card->destroy_set_card(this->server()->card_for_set_card_ref(attacker_card_ref)); card->destroy_set_card(this->server()->card_for_set_card_ref(attacker_card_ref));
} }
@@ -1462,9 +1440,15 @@ bool CardSpecial::evaluate_effect_arg2_condition(
bool is_nte = s->options.is_nte(); bool is_nte = s->options.is_nte();
auto set_card = s->card_for_set_card_ref(set_card_ref); auto set_card = s->card_for_set_card_ref(set_card_ref);
bool set_card_has_ability_trap = bool set_card_has_ability_trap =
(!is_nte && set_card && this->card_has_condition_with_ref(set_card, ConditionType::ABILITY_TRAP, 0xFFFF, 0xFFFF)); (!is_nte && set_card &&
this->card_has_condition_with_ref(set_card, ConditionType::ABILITY_TRAP, 0xFFFF, 0xFFFF));
switch (arg2_text[0]) { switch (arg2_text[0]) {
case 'b': {
auto attacker_card = s->card_for_set_card_ref(attacker_card_ref);
return (attacker_card && (attacker_card->action_chain.chain.damage <= atoi(arg2_text + 1)));
}
case 'C': case 'C':
if (is_nte) { if (is_nte) {
return false; return false;
@@ -1515,11 +1499,6 @@ bool CardSpecial::evaluate_effect_arg2_condition(
return false; return false;
} }
case 'b': {
auto attacker_card = s->card_for_set_card_ref(attacker_card_ref);
return (attacker_card && (attacker_card->action_chain.chain.damage <= atoi(arg2_text + 1)));
}
case 'd': { case 'd': {
if (set_card_has_ability_trap) { if (set_card_has_ability_trap) {
return false; return false;
@@ -1596,7 +1575,8 @@ bool CardSpecial::evaluate_effect_arg2_condition(
auto ce = card->get_definition(); auto ce = card->get_definition();
return ((ce->def.card_class() == CardClass::GUARD_ITEM) || return ((ce->def.card_class() == CardClass::GUARD_ITEM) ||
(!is_nte && (ce->def.card_class() == CardClass::MAG_ITEM)) || (!is_nte && (ce->def.card_class() == CardClass::MAG_ITEM)) ||
s->ruler_server->find_condition_on_card_ref(card->get_card_ref(), ConditionType::GUARD_CREATURE, 0, 0, 0)); s->ruler_server->find_condition_on_card_ref(
card->get_card_ref(), ConditionType::GUARD_CREATURE, 0, 0, 0));
} }
case 0x0E: // n14 case 0x0E: // n14
return card->get_definition()->def.is_sc(); return card->get_definition()->def.is_sc();
@@ -1674,8 +1654,7 @@ bool CardSpecial::evaluate_effect_arg2_condition(
return (!set_card_has_ability_trap || is_nte) && (random_percent < atoi(arg2_text + 1)); return (!set_card_has_ability_trap || is_nte) && (random_percent < atoi(arg2_text + 1));
case 's': { case 's': {
auto ce = card->get_definition(); auto ce = card->get_definition();
return ((ce->def.self_cost >= arg2_text[1] - '0') && return ((ce->def.self_cost >= arg2_text[1] - '0') && (ce->def.self_cost <= arg2_text[2] - '0'));
(ce->def.self_cost <= arg2_text[2] - '0'));
} }
case 't': { case 't': {
auto set_card = s->card_for_set_card_ref(set_card_ref); auto set_card = s->card_for_set_card_ref(set_card_ref);
@@ -1689,14 +1668,12 @@ bool CardSpecial::evaluate_effect_arg2_condition(
return (v < set_card->unknown_a9); return (v < set_card->unknown_a9);
} else if (when == EffectWhen::BEFORE_DICE_PHASE_THIS_TEAM_TURN) { } else if (when == EffectWhen::BEFORE_DICE_PHASE_THIS_TEAM_TURN) {
uint32_t y = set_card->unknown_a9 & 0xFFFFFFFE; uint32_t y = set_card->unknown_a9 & 0xFFFFFFFE;
if ((set_card->unknown_a9 > 0) && if ((set_card->unknown_a9 > 0) && (y == (y / (v & 0xFFFFFFFE)) * (v & 0xFFFFFFFE))) {
(y == (y / (v & 0xFFFFFFFE)) * (v & 0xFFFFFFFE))) {
return true; return true;
} }
} else { } else {
uint32_t y = set_card->unknown_a9; uint32_t y = set_card->unknown_a9;
if ((set_card->unknown_a9 > 0) && if ((set_card->unknown_a9 > 0) && (y == (y / (v + 1)) * (v + 1))) {
(y == (y / (v + 1)) * (v + 1))) {
return true; return true;
} }
} }
@@ -1708,13 +1685,17 @@ bool CardSpecial::evaluate_effect_arg2_condition(
throw logic_error("this should be impossible"); throw logic_error("this should be impossible");
} }
int32_t CardSpecial::evaluate_effect_expr( int32_t CardSpecial::evaluate_effect_expr(const AttackEnvStats& ast, const char* expr, DiceRoll& dice_roll) const {
const AttackEnvStats& ast,
const char* expr,
DiceRoll& dice_roll) const {
auto log = this->server()->log_stack("evaluate_effect_expr: "); auto log = this->server()->log_stack("evaluate_effect_expr: ");
if (log.min_level == phosg::LogLevel::L_DEBUG) { if (log.min_level == phosg::LogLevel::L_DEBUG) {
log.debug_f("ast, expr=\"{}\", dice_roll=(client_id={:02X}, a2={:02X}, value={:02X}, value_used_in_expr={}, a5={:04X})", expr, dice_roll.client_id, dice_roll.unknown_a2, dice_roll.value, dice_roll.value_used_in_expr ? "true" : "false", dice_roll.unknown_a5); log.debug_f(
"ast, expr=\"{}\", dice_roll=(client_id={:02X}, a2={:02X}, value={:02X}, value_used_in_expr={}, a5={:04X})",
expr,
dice_roll.client_id,
dice_roll.unknown_a2,
dice_roll.value,
dice_roll.value_used_in_expr ? "true" : "false",
dice_roll.unknown_a5);
ast.print(stderr); ast.print(stderr);
} }
@@ -1835,8 +1816,7 @@ bool CardSpecial::execute_effect(
} }
} else if (card->action_metadata.check_flag(0x10) && } else if (card->action_metadata.check_flag(0x10) &&
(cond.card_ref != card->get_card_ref()) && (cond.card_ref != card->get_card_ref()) && (cond.condition_giver_card_ref != card->get_card_ref())) {
(cond.condition_giver_card_ref != card->get_card_ref())) {
return false; return false;
} }
@@ -1916,7 +1896,9 @@ bool CardSpecial::execute_effect(
case ConditionType::GIVE_DAMAGE: case ConditionType::GIVE_DAMAGE:
if ((unknown_p7 & 4) != 0) { if ((unknown_p7 & 4) != 0) {
int16_t current_hp = is_nte ? card->get_current_hp() : clamp<int16_t>(card->get_current_hp(), -99, 99); int16_t current_hp = is_nte ? card->get_current_hp() : clamp<int16_t>(card->get_current_hp(), -99, 99);
int16_t new_hp = is_nte ? (current_hp - positive_expr_value) : clamp<int16_t>(current_hp - positive_expr_value, -99, 99); int16_t new_hp = is_nte
? (current_hp - positive_expr_value)
: clamp<int16_t>(current_hp - positive_expr_value, -99, 99);
this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x20, -positive_expr_value, 0, 1); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x20, -positive_expr_value, 0, 1);
new_hp = max<int16_t>(new_hp, 0); new_hp = max<int16_t>(new_hp, 0);
if (new_hp != current_hp) { if (new_hp != current_hp) {
@@ -2226,8 +2208,7 @@ bool CardSpecial::execute_effect(
if (hand_size > 0) { if (hand_size > 0) {
uint8_t a = ps->roll_dice_with_effects(2); uint8_t a = ps->roll_dice_with_effects(2);
uint8_t b = ps->roll_dice_with_effects(1); uint8_t b = ps->roll_dice_with_effects(1);
uint16_t card_ref = ps->card_ref_for_hand_index( uint16_t card_ref = ps->card_ref_for_hand_index((a + b) - ((a + b) / hand_size) * hand_size);
(a + b) - ((a + b) / hand_size) * hand_size);
if (card_ref != 0xFFFF) { if (card_ref != 0xFFFF) {
ps->discard_ref_from_hand(card_ref); ps->discard_ref_from_hand(card_ref);
} }
@@ -2264,7 +2245,8 @@ bool CardSpecial::execute_effect(
card->action_metadata.defense_card_ref_count = 0; card->action_metadata.defense_card_ref_count = 0;
} else { } else {
for (size_t z = 0; z < card->action_chain.chain.attack_action_card_ref_count; z++) { for (size_t z = 0; z < card->action_chain.chain.attack_action_card_ref_count; z++) {
this->apply_stat_deltas_to_all_cards_from_all_conditions_with_card_ref(card->action_chain.chain.attack_action_card_refs[z]); this->apply_stat_deltas_to_all_cards_from_all_conditions_with_card_ref(
card->action_chain.chain.attack_action_card_refs[z]);
} }
} }
card->action_chain.chain.attack_action_card_ref_count = 0; card->action_chain.chain.attack_action_card_ref_count = 0;
@@ -2418,7 +2400,13 @@ bool CardSpecial::execute_effect(
auto& cond = attacker_card->action_chain.conditions[z]; auto& cond = attacker_card->action_chain.conditions[z];
if (cond.type != ConditionType::UNKNOWN_49) { if (cond.type != ConditionType::UNKNOWN_49) {
this->execute_effect( this->execute_effect(
cond, attacker_card, positive_expr_value, clamped_unknown_p5, cond.type, unknown_p7, attacker_card_ref); cond,
attacker_card,
positive_expr_value,
clamped_unknown_p5,
cond.type,
unknown_p7,
attacker_card_ref);
} }
} }
} }
@@ -2428,18 +2416,14 @@ bool CardSpecial::execute_effect(
case ConditionType::AP_GROWTH: case ConditionType::AP_GROWTH:
if (unknown_p7 & 4) { if (unknown_p7 & 4) {
this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0xA0, positive_expr_value, 0, 1); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0xA0, positive_expr_value, 0, 1);
card->ap = is_nte card->ap = is_nte ? (card->ap + positive_expr_value) : clamp<int16_t>(card->ap + positive_expr_value, -99, 99);
? (card->ap + positive_expr_value)
: clamp<int16_t>(card->ap + positive_expr_value, -99, 99);
} }
return true; return true;
case ConditionType::TP_GROWTH: case ConditionType::TP_GROWTH:
if (unknown_p7 & 4) { if (unknown_p7 & 4) {
this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x80, positive_expr_value, 0, 1); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x80, positive_expr_value, 0, 1);
card->tp = is_nte card->tp = is_nte ? (card->tp + positive_expr_value) : clamp<int16_t>(card->tp + positive_expr_value, -99, 99);
? (card->tp + positive_expr_value)
: clamp<int16_t>(card->tp + positive_expr_value, -99, 99);
} }
return true; return true;
@@ -2593,8 +2577,7 @@ bool CardSpecial::execute_effect(
if (is_nte) { if (is_nte) {
return false; return false;
} }
if ((unknown_p7 & 0x40) && if ((unknown_p7 & 0x40) && (static_cast<uint16_t>(attack_medium) == ((s->get_round_num() >> 1) & 1) + 1)) {
(static_cast<uint16_t>(attack_medium) == ((s->get_round_num() >> 1) & 1) + 1)) {
card->action_metadata.attack_bonus = 0; card->action_metadata.attack_bonus = 0;
} }
return true; return true;
@@ -2668,10 +2651,7 @@ bool CardSpecial::execute_effect(
} }
const Condition* CardSpecial::find_condition_with_parameters( const Condition* CardSpecial::find_condition_with_parameters(
shared_ptr<const Card> card, shared_ptr<const Card> card, ConditionType cond_type, uint16_t set_card_ref, uint8_t def_effect_index) const {
ConditionType cond_type,
uint16_t set_card_ref,
uint8_t def_effect_index) const {
if (this->server()->options.is_nte()) { if (this->server()->options.is_nte()) {
// The NTE version of this function returns a boolean instead of a pointer; // The NTE version of this function returns a boolean instead of a pointer;
@@ -2711,18 +2691,13 @@ const Condition* CardSpecial::find_condition_with_parameters(
} }
Condition* CardSpecial::find_condition_with_parameters( Condition* CardSpecial::find_condition_with_parameters(
shared_ptr<Card> card, shared_ptr<Card> card, ConditionType cond_type, uint16_t set_card_ref, uint8_t def_effect_index) const {
ConditionType cond_type,
uint16_t set_card_ref,
uint8_t def_effect_index) const {
return const_cast<Condition*>(this->find_condition_with_parameters( return const_cast<Condition*>(this->find_condition_with_parameters(
static_cast<shared_ptr<const Card>>(card), cond_type, set_card_ref, def_effect_index)); static_cast<shared_ptr<const Card>>(card), cond_type, set_card_ref, def_effect_index));
} }
void CardSpecial::get_card1_loc_with_card2_opposite_direction( void CardSpecial::get_card1_loc_with_card2_opposite_direction(
Location* out_loc, Location* out_loc, shared_ptr<const Card> card1, shared_ptr<const Card> card2) {
shared_ptr<const Card> card1,
shared_ptr<const Card> card2) {
if (card1) { if (card1) {
if (!card2 || (static_cast<uint8_t>(card2->facing_direction) & 0x80)) { if (!card2 || (static_cast<uint8_t>(card2->facing_direction) & 0x80)) {
*out_loc = card1->loc; *out_loc = card1->loc;
@@ -2749,12 +2724,7 @@ uint16_t CardSpecial::get_card_id_with_effective_range(
} }
void CardSpecial::get_effective_ap_tp( void CardSpecial::get_effective_ap_tp(
StatSwapType type, StatSwapType type, int16_t* effective_ap, int16_t* effective_tp, int16_t hp, int16_t ap, int16_t tp) {
int16_t* effective_ap,
int16_t* effective_tp,
int16_t hp,
int16_t ap,
int16_t tp) {
switch (type) { switch (type) {
case StatSwapType::NONE: case StatSwapType::NONE:
*effective_ap = ap; *effective_ap = ap;
@@ -2842,8 +2812,11 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
int16_t p_target_type, int16_t p_target_type,
bool apply_usability_filters) const { bool apply_usability_filters) const {
auto s = this->server(); auto s = this->server();
auto log = s->log_stack(std::format("get_targeted_cards_for_condition(@{:04X}, {}, @{:04X}): ", card_ref, def_effect_index, setter_card_ref)); auto log = s->log_stack(std::format(
log.debug_f("card_ref=@{:04X}, def_effect_index={:02X}, setter_card_ref=@{:04X}, as, p_target_type={}, apply_usability_filters={}", card_ref, def_effect_index, setter_card_ref, p_target_type, apply_usability_filters ? "true" : "false"); "get_targeted_cards_for_condition(@{:04X}, {}, @{:04X}): ", card_ref, def_effect_index, setter_card_ref));
log.debug_f(
"card_ref=@{:04X}, def_effect_index={:02X}, setter_card_ref=@{:04X}, as, p_target_type={}, apply_usability_filters={}",
card_ref, def_effect_index, setter_card_ref, p_target_type, apply_usability_filters ? "true" : "false");
vector<shared_ptr<const Card>> ret; vector<shared_ptr<const Card>> ret;
@@ -2871,9 +2844,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
log.debug_f("card1_loc={}", card1_loc_str); log.debug_f("card1_loc={}", card1_loc_str);
} }
AttackMedium attack_medium = card2 AttackMedium attack_medium = card2 ? card2->action_chain.chain.attack_medium : AttackMedium::UNKNOWN;
? card2->action_chain.chain.attack_medium
: AttackMedium::UNKNOWN;
log.debug_f("attack_medium={}", phosg::name_for_enum(attack_medium)); log.debug_f("attack_medium={}", phosg::name_for_enum(attack_medium));
auto add_card_refs = [&](const vector<uint16_t>& result_card_refs) -> void { auto add_card_refs = [&](const vector<uint16_t>& result_card_refs) -> void {
@@ -3053,9 +3024,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id());
for (uint16_t result_card_ref : result_card_refs) { for (uint16_t result_card_ref : result_card_refs) {
auto result_card = s->card_for_set_card_ref(result_card_ref); auto result_card = s->card_for_set_card_ref(result_card_ref);
if (result_card && if (result_card && (result_card->get_definition()->def.type != CardType::ITEM) && (card1 != result_card)) {
(result_card->get_definition()->def.type != CardType::ITEM) &&
(card1 != result_card)) {
ret.emplace_back(result_card); ret.emplace_back(result_card);
} }
} }
@@ -3140,15 +3109,11 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
if (as.original_attacker_card_ref == 0xFFFF) { if (as.original_attacker_card_ref == 0xFFFF) {
for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) {
auto result_card = s->card_for_set_card_ref(as.target_card_refs[z]); auto result_card = s->card_for_set_card_ref(as.target_card_refs[z]);
if (result_card && if (result_card && result_card->get_definition() && !result_card->get_definition()->def.is_sc()) {
result_card->get_definition() &&
!result_card->get_definition()->def.is_sc()) {
ret.emplace_back(result_card); ret.emplace_back(result_card);
} }
} }
} else if (card2 && } else if (card2 && card2->get_definition() && !card2->get_definition()->def.is_sc()) {
card2->get_definition() &&
!card2->get_definition()->def.is_sc()) {
ret.emplace_back(card2); ret.emplace_back(card2);
} }
break; break;
@@ -3250,8 +3215,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
case 0x28: { // p40 case 0x28: { // p40
auto log3940 = log.sub("(p39/p40) "); auto log3940 = log.sub("(p39/p40) ");
ret = this->find_all_set_cards_with_cost_in_range( ret = this->find_all_set_cards_with_cost_in_range(
(p_target_type == 0x27) ? 4 : 0, (p_target_type == 0x27) ? 4 : 0, (p_target_type == 0x27) ? 99 : 3);
(p_target_type == 0x27) ? 99 : 3);
if (log3940.should_log(phosg::LogLevel::L_DEBUG)) { if (log3940.should_log(phosg::LogLevel::L_DEBUG)) {
for (const auto& card : ret) { for (const auto& card : ret) {
log3940.debug_f("found target @{:04X} #{:04X}", card->get_card_ref(), card->get_card_id()); log3940.debug_f("found target @{:04X} #{:04X}", card->get_card_ref(), card->get_card_id());
@@ -3290,8 +3254,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
if (!s->options.is_nte()) { if (!s->options.is_nte()) {
for (size_t z = 0; z < 8; z++) { for (size_t z = 0; z < 8; z++) {
auto result_card = ps->get_set_card(z); auto result_card = ps->get_set_card(z);
if (result_card && (card1 != result_card) && if (result_card && (card1 != result_card) && (result_card->get_definition()->def.type == CardType::ITEM)) {
(result_card->get_definition()->def.type == CardType::ITEM)) {
bool already_in_ret = false; bool already_in_ret = false;
for (auto c : ret) { for (auto c : ret) {
if (c == result_card) { if (c == result_card) {
@@ -3473,8 +3436,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
for (size_t set_index = 0; set_index < 8; set_index++) { for (size_t set_index = 0; set_index < 8; set_index++) {
auto result_card = ps->get_set_card(set_index); auto result_card = ps->get_set_card(set_index);
if (result_card && (card1 != result_card) && if (result_card && (card1 != result_card) && (result_card->get_definition()->def.type == CardType::ITEM)) {
(result_card->get_definition()->def.type == CardType::ITEM)) {
bool should_add = true; bool should_add = true;
for (auto c : ret) { for (auto c : ret) {
if (c == result_card) { if (c == result_card) {
@@ -3520,9 +3482,7 @@ vector<shared_ptr<Card>> CardSpecial::get_targeted_cards_for_condition(
} }
bool CardSpecial::is_card_targeted_by_condition( bool CardSpecial::is_card_targeted_by_condition(
const Condition& cond, const Condition& cond, const ActionState& as, shared_ptr<const Card> card) const {
const ActionState& as,
shared_ptr<const Card> card) const {
auto s = this->server(); auto s = this->server();
auto log = s->log_stack("is_card_targeted_by_condition: "); auto log = s->log_stack("is_card_targeted_by_condition: ");
@@ -3609,10 +3569,7 @@ bool CardSpecial::card_ref_has_ability_trap(const Condition& cond) const {
} }
void CardSpecial::send_6xB4x06_for_exp_change( void CardSpecial::send_6xB4x06_for_exp_change(
shared_ptr<const Card> card, shared_ptr<const Card> card, uint16_t attacker_card_ref, uint8_t dice_roll_value, bool unknown_p5) const {
uint16_t attacker_card_ref,
uint8_t dice_roll_value,
bool unknown_p5) const {
G_ApplyConditionEffect_Ep3_6xB4x06 cmd; G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
cmd.effect.flags = 0x02; cmd.effect.flags = 0x02;
cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 10); cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 10);
@@ -3636,8 +3593,7 @@ void CardSpecial::send_6xB4x06_for_card_destroyed(
auto s = this->server(); auto s = this->server();
G_ApplyConditionEffect_Ep3_6xB4x06 cmd; G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
cmd.effect.flags = 0x04; cmd.effect.flags = 0x04;
cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid( cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x13);
attacker_card_ref, 0x13);
cmd.effect.target_card_ref = destroyed_card->get_card_ref(); cmd.effect.target_card_ref = destroyed_card->get_card_ref();
cmd.effect.value = 0; cmd.effect.value = 0;
cmd.effect.operation = s->options.is_nte() ? 0x78 : 0x7E; cmd.effect.operation = s->options.is_nte() ? 0x78 : 0x7E;
@@ -3776,9 +3732,7 @@ bool CardSpecial::should_return_card_ref_to_hand_on_destruction(
continue; continue;
} }
auto cond_type = card->action_chain.conditions[cond_index].type; auto cond_type = card->action_chain.conditions[cond_index].type;
if ((cond_type == ConditionType::RETURN) && if ((cond_type == ConditionType::RETURN) && !(card->card_flags & 1) && (card->get_card_ref() == card_ref)) {
!(card->card_flags & 1) &&
(card->get_card_ref() == card_ref)) {
return true; return true;
} else if ((cond_type == ConditionType::REBORN) && } else if ((cond_type == ConditionType::REBORN) &&
!(card->card_flags & 3) && !(card->card_flags & 3) &&
@@ -3798,9 +3752,7 @@ bool CardSpecial::should_return_card_ref_to_hand_on_destruction(
} }
size_t CardSpecial::sum_last_attack_damage( size_t CardSpecial::sum_last_attack_damage(
vector<shared_ptr<const Card>>* out_cards, vector<shared_ptr<const Card>>* out_cards, int32_t* out_damage_sum, size_t* out_damage_count) const {
int32_t* out_damage_sum,
size_t* out_damage_count) const {
auto log = this->server()->log_stack("sum_last_attack_damage: "); auto log = this->server()->log_stack("sum_last_attack_damage: ");
size_t damage_count = 0; size_t damage_count = 0;
@@ -3924,9 +3876,7 @@ void CardSpecial::apply_effects_after_card_move(shared_ptr<Card> card) {
} }
void CardSpecial::check_for_defense_interference( void CardSpecial::check_for_defense_interference(
shared_ptr<const Card> attacker_card, shared_ptr<const Card> attacker_card, shared_ptr<Card> target_card, int16_t* inout_unknown_p4) {
shared_ptr<Card> target_card,
int16_t* inout_unknown_p4) {
auto s = this->server(); auto s = this->server();
// Note: This check is not part of the original implementation. // Note: This check is not part of the original implementation.
@@ -3941,8 +3891,7 @@ void CardSpecial::check_for_defense_interference(
return; return;
} }
uint16_t ally_sc_card_ref = s->ruler_server->get_ally_sc_card_ref( uint16_t ally_sc_card_ref = s->ruler_server->get_ally_sc_card_ref(target_card->get_card_ref());
target_card->get_card_ref());
if (ally_sc_card_ref == 0xFFFF) { if (ally_sc_card_ref == 0xFFFF) {
return; return;
} }
@@ -3963,7 +3912,8 @@ void CardSpecial::check_for_defense_interference(
} }
auto ally_hes = s->ruler_server->get_hand_and_equip_state_for_client_id(target_ally_client_id); auto ally_hes = s->ruler_server->get_hand_and_equip_state_for_client_id(target_ally_client_id);
if (!ally_hes || (!(s->options.behavior_flags & BehaviorFlag::ALLOW_NON_COM_INTERFERENCE) && !ally_hes->is_cpu_player)) { if (!ally_hes ||
(!(s->options.behavior_flags & BehaviorFlag::ALLOW_NON_COM_INTERFERENCE) && !ally_hes->is_cpu_player)) {
return; return;
} }
@@ -3984,8 +3934,7 @@ void CardSpecial::check_for_defense_interference(
if (target_ps->unknown_a17 >= 1) { if (target_ps->unknown_a17 >= 1) {
return; return;
} }
auto entry = get_interference_probability_entry( auto entry = get_interference_probability_entry(target_card_id, ally_sc_card_id, false);
target_card_id, ally_sc_card_id, false);
if (!entry || (s->get_random(99) >= entry->defense_probability)) { if (!entry || (s->get_random(99) >= entry->defense_probability)) {
return; return;
} }
@@ -4019,7 +3968,12 @@ void CardSpecial::evaluate_and_apply_effects(
{ {
string as_str = as.str(s); string as_str = as.str(s);
log.debug_f("when={}, set_card_ref=@{:04X}, as={}, sc_card_ref=@{:04X}, apply_defense_condition_to_all_cards={}, apply_defense_condition_to_card_ref=@{:04X}", log.debug_f("when={}, set_card_ref=@{:04X}, as={}, sc_card_ref=@{:04X}, apply_defense_condition_to_all_cards={}, apply_defense_condition_to_card_ref=@{:04X}",
phosg::name_for_enum(when), set_card_ref, as_str, sc_card_ref, apply_defense_condition_to_all_cards ? "true" : "false", apply_defense_condition_to_card_ref); phosg::name_for_enum(when),
set_card_ref,
as_str,
sc_card_ref,
apply_defense_condition_to_all_cards ? "true" : "false",
apply_defense_condition_to_card_ref);
} }
if (!is_nte) { if (!is_nte) {
@@ -4076,13 +4030,15 @@ void CardSpecial::evaluate_and_apply_effects(
dice_roll.unknown_a2 = 3; dice_roll.unknown_a2 = 3;
dice_roll.value_used_in_expr = false; dice_roll.value_used_in_expr = false;
log.debug_f("inputs: dice_roll={:02X}, random_percent={}, unknown_v1={}", dice_roll.value, random_percent, unknown_v1 ? "true" : "false"); log.debug_f("inputs: dice_roll={:02X}, random_percent={}, unknown_v1={}",
dice_roll.value, random_percent, unknown_v1 ? "true" : "false");
for (size_t def_effect_index = 0; (def_effect_index < 3) && !unknown_v1 && (ce->def.effects[def_effect_index].type != ConditionType::NONE); def_effect_index++) { for (size_t def_effect_index = 0;
(def_effect_index < 3) && !unknown_v1 && (ce->def.effects[def_effect_index].type != ConditionType::NONE);
def_effect_index++) {
auto effect_log = log.sub(std::format("(effect:{}) ", def_effect_index)); auto effect_log = log.sub(std::format("(effect:{}) ", def_effect_index));
const auto& card_effect = ce->def.effects[def_effect_index]; const auto& card_effect = ce->def.effects[def_effect_index];
string card_effect_str = card_effect.str(); effect_log.debug_f("effect: {}", card_effect.str());
effect_log.debug_f("effect: {}", card_effect_str);
if (card_effect.when != when) { if (card_effect.when != when) {
effect_log.debug_f("does not apply (effect.when={}, when={})", phosg::name_for_enum(card_effect.when), phosg::name_for_enum(when)); effect_log.debug_f("does not apply (effect.when={}, when={})", phosg::name_for_enum(card_effect.when), phosg::name_for_enum(when));
continue; continue;
@@ -4199,9 +4155,16 @@ void CardSpecial::evaluate_and_apply_effects(
target_card->action_chain.conditions[applied_cond_index].flags |= 1; target_card->action_chain.conditions[applied_cond_index].flags |= 1;
} }
if (apply_defense_condition_to_all_cards || (apply_defense_condition_to_card_ref == targeted_cards[z]->get_card_ref())) { if (apply_defense_condition_to_all_cards ||
(apply_defense_condition_to_card_ref == targeted_cards[z]->get_card_ref())) {
this->apply_defense_condition( this->apply_defense_condition(
when, &target_card->action_chain.conditions[applied_cond_index], applied_cond_index, as, target_card, 4, 1); when,
&target_card->action_chain.conditions[applied_cond_index],
applied_cond_index,
as,
target_card,
4,
1);
target_log.debug_f("applied defense condition"); target_log.debug_f("applied defense condition");
} }
} }
@@ -4472,8 +4435,7 @@ const InterferenceProbabilityEntry* get_interference_probability_entry(
const auto& entry = entries[z]; const auto& entry = entries[z];
uint16_t current_column_card_id = entry.card_id; uint16_t current_column_card_id = entry.card_id;
if ((entry.attack_probability != 0xFF) || (entry.defense_probability != 0xFF)) { if ((entry.attack_probability != 0xFF) || (entry.defense_probability != 0xFF)) {
if ((row_card_id == current_row_card_id) && if ((row_card_id == current_row_card_id) && (column_card_id == current_column_card_id)) {
(column_card_id == current_column_card_id)) {
uint8_t v = is_attack ? entry.attack_probability : entry.defense_probability; uint8_t v = is_attack ? entry.attack_probability : entry.defense_probability;
if (current_max <= v) { if (current_max <= v) {
ret_entry = &entry; ret_entry = &entry;
@@ -4488,11 +4450,9 @@ const InterferenceProbabilityEntry* get_interference_probability_entry(
return ret_entry; return ret_entry;
} }
void CardSpecial::on_card_destroyed( void CardSpecial::on_card_destroyed(shared_ptr<Card> attacker_card, shared_ptr<Card> destroyed_card) {
shared_ptr<Card> attacker_card, shared_ptr<Card> destroyed_card) {
ActionState attack_as = this->create_attack_state_from_card_action_chain(attacker_card); ActionState attack_as = this->create_attack_state_from_card_action_chain(attacker_card);
ActionState defense_as = this->create_defense_state_for_card_pair_action_chains( ActionState defense_as = this->create_defense_state_for_card_pair_action_chains(attacker_card, destroyed_card);
attacker_card, destroyed_card);
uint16_t destroyed_card_ref = destroyed_card->get_card_ref(); uint16_t destroyed_card_ref = destroyed_card->get_card_ref();
this->evaluate_and_apply_effects(EffectWhen::CARD_DESTROYED, destroyed_card_ref, defense_as, 0xFFFF); this->evaluate_and_apply_effects(EffectWhen::CARD_DESTROYED, destroyed_card_ref, defense_as, 0xFFFF);
@@ -4513,8 +4473,7 @@ void CardSpecial::on_card_destroyed(
this->send_6xB4x06_for_card_destroyed(destroyed_card, attack_as.attacker_card_ref); this->send_6xB4x06_for_card_destroyed(destroyed_card, attack_as.attacker_card_ref);
} }
vector<shared_ptr<const Card>> CardSpecial::find_cards_in_hp_range( vector<shared_ptr<const Card>> CardSpecial::find_cards_in_hp_range(int16_t min, int16_t max) const {
int16_t min, int16_t max) const {
vector<shared_ptr<const Card>> ret; vector<shared_ptr<const Card>> ret;
for (size_t client_id = 0; client_id < 4; client_id++) { for (size_t client_id = 0; client_id < 4; client_id++) {
auto ps = this->server()->get_player_state(client_id); auto ps = this->server()->get_player_state(client_id);
@@ -4786,8 +4745,7 @@ vector<shared_ptr<const Card>> CardSpecial::filter_cards_by_range(
void CardSpecial::apply_effects_after_attack_target_resolution(const ActionState& as) { void CardSpecial::apply_effects_after_attack_target_resolution(const ActionState& as) {
auto s = this->server(); auto s = this->server();
auto log = s->log_stack("apply_effects_after_attack_target_resolution: "); auto log = s->log_stack("apply_effects_after_attack_target_resolution: ");
string as_str = as.str(s); log.debug_f("as={}", as.str(s));
log.debug_f("as={}", as_str);
for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) { for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) {
uint16_t card_ref = this->send_6xB4x06_if_card_ref_invalid(as.action_card_refs[z], 0x1E); uint16_t card_ref = this->send_6xB4x06_if_card_ref_invalid(as.action_card_refs[z], 0x1E);
@@ -4878,7 +4836,12 @@ void CardSpecial::dice_phase_before_for_card(shared_ptr<Card> card) {
template <EffectWhen When1, EffectWhen When2> template <EffectWhen When1, EffectWhen When2>
void CardSpecial::apply_effects_on_phase_change_t(shared_ptr<Card> unknown_p2, const ActionState* existing_as) { void CardSpecial::apply_effects_on_phase_change_t(shared_ptr<Card> unknown_p2, const ActionState* existing_as) {
auto s = this->server(); auto s = this->server();
auto log = s->log_stack(std::format("apply_effects_on_phase_change_t<{}, {}>(@{:04X} #{:04X}): ", phosg::name_for_enum(When1), phosg::name_for_enum(When2), unknown_p2->get_card_ref(), unknown_p2->get_card_id())); auto log = s->log_stack(std::format(
"apply_effects_on_phase_change_t<{}, {}>(@{:04X} #{:04X}): ",
phosg::name_for_enum(When1),
phosg::name_for_enum(When2),
unknown_p2->get_card_ref(),
unknown_p2->get_card_id()));
bool is_nte = s->options.is_nte(); bool is_nte = s->options.is_nte();
ActionState as; ActionState as;
@@ -4904,8 +4867,7 @@ void CardSpecial::apply_effects_on_phase_change_t(shared_ptr<Card> unknown_p2, c
for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) {
auto card = s->card_for_set_card_ref(as.target_card_refs[z]); auto card = s->card_for_set_card_ref(as.target_card_refs[z]);
if (card) { if (card) {
ActionState target_as = this->create_defense_state_for_card_pair_action_chains( ActionState target_as = this->create_defense_state_for_card_pair_action_chains(unknown_p2, card);
unknown_p2, card);
this->evaluate_and_apply_effects(When2, as.target_card_refs[z], target_as, unknown_p2->get_card_ref()); this->evaluate_and_apply_effects(When2, as.target_card_refs[z], target_as, unknown_p2->get_card_ref());
for (size_t w = 0; (w < 8) && (target_as.action_card_refs[w] != 0xFFFF); w++) { for (size_t w = 0; (w < 8) && (target_as.action_card_refs[w] != 0xFFFF); w++) {
this->evaluate_and_apply_effects(When1, target_as.action_card_refs[w], target_as, card->get_card_ref()); this->evaluate_and_apply_effects(When1, target_as.action_card_refs[w], target_as, card->get_card_ref());
@@ -4925,11 +4887,13 @@ void CardSpecial::action_phase_before_for_card(shared_ptr<Card> unknown_p2) {
} }
void CardSpecial::unknown_8024945C(shared_ptr<Card> unknown_p2, const ActionState* existing_as) { void CardSpecial::unknown_8024945C(shared_ptr<Card> unknown_p2, const ActionState* existing_as) {
this->apply_effects_on_phase_change_t<EffectWhen::UNKNOWN_0A, EffectWhen::UNKNOWN_0A>(unknown_p2, this->server()->options.is_nte() ? nullptr : existing_as); this->apply_effects_on_phase_change_t<EffectWhen::UNKNOWN_0A, EffectWhen::UNKNOWN_0A>(
unknown_p2, this->server()->options.is_nte() ? nullptr : existing_as);
} }
void CardSpecial::unknown_8024966C(shared_ptr<Card> unknown_p2, const ActionState* existing_as) { void CardSpecial::unknown_8024966C(shared_ptr<Card> unknown_p2, const ActionState* existing_as) {
auto log = this->server()->log_stack(std::format("unknown_8024966C(@{:04X} #{:04X}): ", unknown_p2->get_card_ref(), unknown_p2->get_card_id())); auto log = this->server()->log_stack(std::format("unknown_8024966C(@{:04X} #{:04X}): ",
unknown_p2->get_card_ref(), unknown_p2->get_card_id()));
ActionState as; ActionState as;
if (!existing_as) { if (!existing_as) {
@@ -4966,16 +4930,20 @@ void CardSpecial::unknown_8024966C(shared_ptr<Card> unknown_p2, const ActionStat
} }
for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) { for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) {
this->evaluate_and_apply_effects(EffectWhen::ATTACK_STAT_OVERRIDES, as.action_card_refs[z], as, unknown_p2->get_card_ref()); this->evaluate_and_apply_effects(
this->evaluate_and_apply_effects(EffectWhen::ATTACK_DAMAGE_ADJUSTMENT, as.action_card_refs[z], as, unknown_p2->get_card_ref()); EffectWhen::ATTACK_STAT_OVERRIDES, as.action_card_refs[z], as, unknown_p2->get_card_ref());
this->evaluate_and_apply_effects(
EffectWhen::ATTACK_DAMAGE_ADJUSTMENT, as.action_card_refs[z], as, unknown_p2->get_card_ref());
} }
for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) {
card = this->server()->card_for_set_card_ref(as.target_card_refs[z]); card = this->server()->card_for_set_card_ref(as.target_card_refs[z]);
if (card) { if (card) {
ActionState defense_as = this->create_defense_state_for_card_pair_action_chains(unknown_p2, card); ActionState defense_as = this->create_defense_state_for_card_pair_action_chains(unknown_p2, card);
this->evaluate_and_apply_effects(EffectWhen::ATTACK_STAT_OVERRIDES, card->get_card_ref(), defense_as, unknown_p2->get_card_ref()); this->evaluate_and_apply_effects(
this->evaluate_and_apply_effects(EffectWhen::DEFENSE_DAMAGE_ADJUSTMENT, card->get_card_ref(), defense_as, unknown_p2->get_card_ref()); EffectWhen::ATTACK_STAT_OVERRIDES, card->get_card_ref(), defense_as, unknown_p2->get_card_ref());
this->evaluate_and_apply_effects(
EffectWhen::DEFENSE_DAMAGE_ADJUSTMENT, card->get_card_ref(), defense_as, unknown_p2->get_card_ref());
} }
} }
} }
@@ -4987,7 +4955,8 @@ 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) { 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++) { for (size_t z = 0; (z < 8) && (pa.action_card_refs[z] != 0xFFFF); z++) {
if (this->server()->options.is_nte() || (action_card_ref == 0xFFFF) || (action_card_ref == pa.action_card_refs[z])) { if (this->server()->options.is_nte() ||
(action_card_ref == 0xFFFF) || (action_card_ref == pa.action_card_refs[z])) {
if (pa.original_attacker_card_ref == 0xFFFF) { if (pa.original_attacker_card_ref == 0xFFFF) {
this->evaluate_and_apply_effects(EffectWhen::UNKNOWN_29, pa.action_card_refs[z], pa, pa.attacker_card_ref); this->evaluate_and_apply_effects(EffectWhen::UNKNOWN_29, pa.action_card_refs[z], pa, pa.attacker_card_ref);
this->evaluate_and_apply_effects(EffectWhen::UNKNOWN_2A, pa.action_card_refs[z], pa, pa.attacker_card_ref); this->evaluate_and_apply_effects(EffectWhen::UNKNOWN_2A, pa.action_card_refs[z], pa, pa.attacker_card_ref);
@@ -5009,8 +4978,7 @@ void CardSpecial::check_for_attack_interference(shared_ptr<Card> unknown_p2) {
return; return;
} }
uint16_t ally_sc_card_ref = this->server()->ruler_server->get_ally_sc_card_ref( uint16_t ally_sc_card_ref = this->server()->ruler_server->get_ally_sc_card_ref(unknown_p2->get_card_ref());
unknown_p2->get_card_ref());
if (ally_sc_card_ref == 0xFFFF) { if (ally_sc_card_ref == 0xFFFF) {
return; return;
} }
@@ -5081,7 +5049,12 @@ template <
void CardSpecial::apply_effects_before_or_after_attack(shared_ptr<Card> unknown_p2) { void CardSpecial::apply_effects_before_or_after_attack(shared_ptr<Card> unknown_p2) {
auto s = this->server(); auto s = this->server();
auto log = s->log_stack(std::format("apply_effects_before_or_after_attack<{}, {}, {}, {}>(@{:04X} #{:04X}): ", auto log = s->log_stack(std::format("apply_effects_before_or_after_attack<{}, {}, {}, {}>(@{:04X} #{:04X}): ",
phosg::name_for_enum(WhenAllCards), phosg::name_for_enum(WhenAttackerAndActionCards), phosg::name_for_enum(WhenAttackerOrHunterSCCard), phosg::name_for_enum(WhenTargetsAndActionCards), unknown_p2->get_card_ref(), unknown_p2->get_card_id())); phosg::name_for_enum(WhenAllCards),
phosg::name_for_enum(WhenAttackerAndActionCards),
phosg::name_for_enum(WhenAttackerOrHunterSCCard),
phosg::name_for_enum(WhenTargetsAndActionCards),
unknown_p2->get_card_ref(),
unknown_p2->get_card_id()));
ActionState as = this->create_attack_state_from_card_action_chain(unknown_p2); ActionState as = this->create_attack_state_from_card_action_chain(unknown_p2);
@@ -5119,17 +5092,21 @@ void CardSpecial::apply_effects_before_or_after_attack(shared_ptr<Card> unknown_
} }
for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) { for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) {
this->evaluate_and_apply_effects(WhenAllCards, as.action_card_refs[z], as, unknown_p2->get_card_ref()); this->evaluate_and_apply_effects(WhenAllCards, as.action_card_refs[z], as, unknown_p2->get_card_ref());
this->evaluate_and_apply_effects(WhenAttackerAndActionCards, as.action_card_refs[z], as, unknown_p2->get_card_ref()); this->evaluate_and_apply_effects(
WhenAttackerAndActionCards, as.action_card_refs[z], as, unknown_p2->get_card_ref());
} }
for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) {
auto set_card = s->card_for_set_card_ref(as.target_card_refs[z]); auto set_card = s->card_for_set_card_ref(as.target_card_refs[z]);
if (set_card) { if (set_card) {
ActionState target_as = this->create_defense_state_for_card_pair_action_chains(unknown_p2, set_card); ActionState target_as = this->create_defense_state_for_card_pair_action_chains(unknown_p2, set_card);
this->evaluate_and_apply_effects(WhenAllCards, set_card->get_card_ref(), target_as, unknown_p2->get_card_ref()); this->evaluate_and_apply_effects(WhenAllCards, set_card->get_card_ref(), target_as, unknown_p2->get_card_ref());
this->evaluate_and_apply_effects(WhenTargetsAndActionCards, set_card->get_card_ref(), target_as, unknown_p2->get_card_ref()); this->evaluate_and_apply_effects(
WhenTargetsAndActionCards, set_card->get_card_ref(), target_as, unknown_p2->get_card_ref());
for (size_t z = 0; (z < 8) && (target_as.action_card_refs[z] != 0xFFFF); z++) { for (size_t z = 0; (z < 8) && (target_as.action_card_refs[z] != 0xFFFF); z++) {
this->evaluate_and_apply_effects(WhenAllCards, target_as.action_card_refs[z], target_as, set_card->get_card_ref()); this->evaluate_and_apply_effects(
this->evaluate_and_apply_effects(WhenTargetsAndActionCards, target_as.action_card_refs[z], target_as, set_card->get_card_ref()); WhenAllCards, target_as.action_card_refs[z], target_as, set_card->get_card_ref());
this->evaluate_and_apply_effects(
WhenTargetsAndActionCards, target_as.action_card_refs[z], target_as, set_card->get_card_ref());
} }
} }
} }
@@ -5182,16 +5159,14 @@ bool CardSpecial::client_has_atk_dice_boost_condition(uint8_t client_id) {
} }
void CardSpecial::unknown_8024A6DC(shared_ptr<Card> unknown_p2, shared_ptr<Card> unknown_p3) { void CardSpecial::unknown_8024A6DC(shared_ptr<Card> unknown_p2, shared_ptr<Card> unknown_p3) {
ActionState as = this->create_defense_state_for_card_pair_action_chains( ActionState as = this->create_defense_state_for_card_pair_action_chains(unknown_p2, unknown_p3);
unknown_p2, unknown_p3);
for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) { for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) {
this->evaluate_and_apply_effects(EffectWhen::CARD_SET, as.action_card_refs[z], as, unknown_p3->get_card_ref()); this->evaluate_and_apply_effects(EffectWhen::CARD_SET, as.action_card_refs[z], as, unknown_p3->get_card_ref());
this->evaluate_and_apply_effects(EffectWhen::UNKNOWN_15, as.action_card_refs[z], as, unknown_p3->get_card_ref()); this->evaluate_and_apply_effects(EffectWhen::UNKNOWN_15, as.action_card_refs[z], as, unknown_p3->get_card_ref());
} }
} }
vector<shared_ptr<const Card>> CardSpecial::find_all_sc_cards_of_class( vector<shared_ptr<const Card>> CardSpecial::find_all_sc_cards_of_class(CardClass card_class) const {
CardClass card_class) const {
vector<shared_ptr<const Card>> ret; vector<shared_ptr<const Card>> ret;
for (size_t z = 0; z < 4; z++) { for (size_t z = 0; z < 4; z++) {
auto ps = this->server()->get_player_state(z); auto ps = this->server()->get_player_state(z);
+35 -83
View File
@@ -17,9 +17,7 @@ struct InterferenceProbabilityEntry {
}; };
const InterferenceProbabilityEntry* get_interference_probability_entry( const InterferenceProbabilityEntry* get_interference_probability_entry(
uint16_t row_card_id, uint16_t row_card_id, uint16_t column_card_id, bool is_attack);
uint16_t column_card_id,
bool is_attack);
class CardSpecial { class CardSpecial {
public: public:
@@ -77,9 +75,8 @@ public:
/* 70 */ uint32_t effective_ap_if_not_tech2; // "tt" in expr /* 70 */ uint32_t effective_ap_if_not_tech2; // "tt" in expr
/* 74 */ uint32_t team_dice_bonus; // "lv" in expr /* 74 */ uint32_t team_dice_bonus; // "lv" in expr
/* 78 */ uint32_t sc_effective_ap; // "adm" in expr /* 78 */ uint32_t sc_effective_ap; // "adm" in expr
// The following fields do not exist in Trial Edition. Because this struct // The following fields do not exist in Trial Edition. Because this struct is never sent to the client, we use the
// is never sent to the client, we use the full struct even when playing // full struct even when playing Trial Edition, just for simplicity.
// Trial Edition, just for simplicity.
/* 7C */ uint32_t attack_bonus; // "ddm" in expr /* 7C */ uint32_t attack_bonus; // "ddm" in expr
/* 80 */ uint32_t num_sword_type_items_on_team; // "sat" in expr /* 80 */ uint32_t num_sword_type_items_on_team; // "sat" in expr
/* 84 */ uint32_t target_attack_bonus; // "edm" in expr /* 84 */ uint32_t target_attack_bonus; // "edm" in expr
@@ -126,26 +123,14 @@ public:
uint32_t flags, uint32_t flags,
bool unknown_p8); bool unknown_p8);
bool apply_defense_conditions( bool apply_defense_conditions(
const ActionState& as, const ActionState& as, EffectWhen when, std::shared_ptr<Card> defender_card, uint32_t flags);
EffectWhen when, bool apply_stat_deltas_to_all_cards_from_all_conditions_with_card_ref(uint16_t card_ref);
std::shared_ptr<Card> defender_card, bool apply_stat_deltas_to_card_from_condition_and_clear_cond(Condition& cond, std::shared_ptr<Card> card);
uint32_t flags); bool apply_stats_deltas_to_card_from_all_conditions_with_card_ref(uint16_t card_ref, std::shared_ptr<Card> card);
bool apply_stat_deltas_to_all_cards_from_all_conditions_with_card_ref(
uint16_t card_ref);
bool apply_stat_deltas_to_card_from_condition_and_clear_cond(
Condition& cond, std::shared_ptr<Card> card);
bool apply_stats_deltas_to_card_from_all_conditions_with_card_ref(
uint16_t card_ref, std::shared_ptr<Card> card);
bool card_has_condition_with_ref( bool card_has_condition_with_ref(
std::shared_ptr<const Card> card, std::shared_ptr<const Card> card, ConditionType cond_type, uint16_t card_ref, uint16_t match_card_ref) const;
ConditionType cond_type,
uint16_t card_ref,
uint16_t match_card_ref) const;
bool card_is_destroyed(std::shared_ptr<const Card> card) const; bool card_is_destroyed(std::shared_ptr<const Card> card) const;
void compute_attack_ap( void compute_attack_ap(std::shared_ptr<const Card> target_card, int16_t* out_value, uint16_t attacker_card_ref);
std::shared_ptr<const Card> target_card,
int16_t* out_value,
uint16_t attacker_card_ref);
AttackEnvStats compute_attack_env_stats( AttackEnvStats compute_attack_env_stats(
const ActionState& pa, const ActionState& pa,
std::shared_ptr<const Card> card, std::shared_ptr<const Card> card,
@@ -166,21 +151,16 @@ public:
StatSwapType compute_stat_swap_type(std::shared_ptr<const Card> card) const; StatSwapType compute_stat_swap_type(std::shared_ptr<const Card> card) const;
void compute_team_dice_bonus(uint8_t team_id); void compute_team_dice_bonus(uint8_t team_id);
bool condition_applies_on_sc_or_item_attack(const Condition& cond) const; bool condition_applies_on_sc_or_item_attack(const Condition& cond) const;
size_t count_action_cards_with_condition_for_all_current_attacks( size_t count_action_cards_with_condition_for_all_current_attacks(ConditionType cond_type, uint16_t card_ref) const;
ConditionType cond_type, uint16_t card_ref) const;
size_t count_action_cards_with_condition_for_current_attack( size_t count_action_cards_with_condition_for_current_attack(
std::shared_ptr<const Card> card, ConditionType cond_type, uint16_t card_ref) const; std::shared_ptr<const Card> card, ConditionType cond_type, uint16_t card_ref) const;
size_t count_cards_with_card_id_except_card_ref( size_t count_cards_with_card_id_except_card_ref(uint16_t card_id, uint16_t card_ref) const;
uint16_t card_id, uint16_t card_ref) const;
std::vector<std::shared_ptr<const Card>> get_all_set_cards_by_team_and_class( std::vector<std::shared_ptr<const Card>> get_all_set_cards_by_team_and_class(
CardClass card_class, uint8_t team_id, bool exclude_destroyed_cards) const; CardClass card_class, uint8_t team_id, bool exclude_destroyed_cards) const;
ActionState create_attack_state_from_card_action_chain( ActionState create_attack_state_from_card_action_chain(std::shared_ptr<const Card> attacker_card) const;
std::shared_ptr<const Card> attacker_card) const;
ActionState create_defense_state_for_card_pair_action_chains( ActionState create_defense_state_for_card_pair_action_chains(
std::shared_ptr<const Card> attacker_card, std::shared_ptr<const Card> attacker_card, std::shared_ptr<const Card> defender_card) const;
std::shared_ptr<const Card> defender_card) const; void destroy_card_if_hp_zero(std::shared_ptr<Card> card, uint16_t attacker_card_ref);
void destroy_card_if_hp_zero(
std::shared_ptr<Card> card, uint16_t attacker_card_ref);
bool evaluate_effect_arg2_condition( bool evaluate_effect_arg2_condition(
const ActionState& as, const ActionState& as,
std::shared_ptr<const Card> card, std::shared_ptr<const Card> card,
@@ -190,10 +170,7 @@ public:
uint16_t sc_card_ref, uint16_t sc_card_ref,
uint8_t random_percent, uint8_t random_percent,
EffectWhen when) const; EffectWhen when) const;
int32_t evaluate_effect_expr( int32_t evaluate_effect_expr(const AttackEnvStats& ast, const char* expr, DiceRoll& dice_roll) const;
const AttackEnvStats& ast,
const char* expr,
DiceRoll& dice_roll) const;
bool execute_effect( bool execute_effect(
Condition& cond, Condition& cond,
std::shared_ptr<Card> card, std::shared_ptr<Card> card,
@@ -208,25 +185,14 @@ public:
uint16_t set_card_ref, uint16_t set_card_ref,
uint8_t def_effect_index) const; uint8_t def_effect_index) const;
Condition* find_condition_with_parameters( Condition* find_condition_with_parameters(
std::shared_ptr<Card> card, std::shared_ptr<Card> card, ConditionType cond_type, uint16_t set_card_ref, uint8_t def_effect_index) const;
ConditionType cond_type,
uint16_t set_card_ref,
uint8_t def_effect_index) const;
static void get_card1_loc_with_card2_opposite_direction( static void get_card1_loc_with_card2_opposite_direction(
Location* out_loc, Location* out_loc, std::shared_ptr<const Card> card1, std::shared_ptr<const Card> card2);
std::shared_ptr<const Card> card1,
std::shared_ptr<const Card> card2);
uint16_t get_card_id_with_effective_range( uint16_t get_card_id_with_effective_range(
std::shared_ptr<const Card> card1, uint16_t default_card_id, std::shared_ptr<const Card> card2) const; std::shared_ptr<const Card> card1, uint16_t default_card_id, std::shared_ptr<const Card> card2) const;
static void get_effective_ap_tp( static void get_effective_ap_tp(
StatSwapType type, StatSwapType type, int16_t* effective_ap, int16_t* effective_tp, int16_t hp, int16_t ap, int16_t tp);
int16_t* effective_ap, const char* get_next_expr_token(const char* expr, ExpressionTokenType* out_type, int32_t* out_value) const;
int16_t* effective_tp,
int16_t hp,
int16_t ap,
int16_t tp);
const char* get_next_expr_token(
const char* expr, ExpressionTokenType* out_type, int32_t* out_value) const;
std::vector<std::shared_ptr<const Card>> get_targeted_cards_for_condition( std::vector<std::shared_ptr<const Card>> get_targeted_cards_for_condition(
uint16_t card_ref, uint16_t card_ref,
uint8_t def_effect_index, uint8_t def_effect_index,
@@ -244,18 +210,12 @@ public:
bool is_card_targeted_by_condition( bool is_card_targeted_by_condition(
const Condition& cond, const ActionState& as, std::shared_ptr<const Card> card) const; const Condition& cond, const ActionState& as, std::shared_ptr<const Card> card) const;
void on_card_set(std::shared_ptr<PlayerState> ps, uint16_t card_ref); void on_card_set(std::shared_ptr<PlayerState> ps, uint16_t card_ref);
const CardDefinition::Effect* original_definition_for_condition( const CardDefinition::Effect* original_definition_for_condition(const Condition& cond) const;
const Condition& cond) const;
bool card_ref_has_ability_trap(const Condition& eff) const; bool card_ref_has_ability_trap(const Condition& eff) const;
void send_6xB4x06_for_exp_change( void send_6xB4x06_for_exp_change(
std::shared_ptr<const Card> card, std::shared_ptr<const Card> card, uint16_t attacker_card_ref, uint8_t dice_roll_value, bool unknown_p5) const;
uint16_t attacker_card_ref, void send_6xB4x06_for_card_destroyed(std::shared_ptr<const Card> destroyed_card, uint16_t attacker_card_ref) const;
uint8_t dice_roll_value, uint16_t send_6xB4x06_if_card_ref_invalid(uint16_t card_ref, int16_t value) const;
bool unknown_p5) const;
void send_6xB4x06_for_card_destroyed(
std::shared_ptr<const Card> destroyed_card, uint16_t attacker_card_ref) const;
uint16_t send_6xB4x06_if_card_ref_invalid(
uint16_t card_ref, int16_t value) const;
void send_6xB4x06_for_stat_delta( void send_6xB4x06_for_stat_delta(
std::shared_ptr<const Card> card, std::shared_ptr<const Card> card,
uint16_t attacker_card_ref, uint16_t attacker_card_ref,
@@ -268,19 +228,14 @@ public:
std::shared_ptr<const Card> card, std::shared_ptr<const Card> card,
uint16_t target_card_ref, uint16_t target_card_ref,
uint16_t sc_card_ref) const; uint16_t sc_card_ref) const;
bool should_return_card_ref_to_hand_on_destruction( bool should_return_card_ref_to_hand_on_destruction(uint16_t card_ref) const;
uint16_t card_ref) const;
size_t sum_last_attack_damage( size_t sum_last_attack_damage(
std::vector<std::shared_ptr<const Card>>* out_cards, std::vector<std::shared_ptr<const Card>>* out_cards, int32_t* out_damage_sum, size_t* out_damage_count) const;
int32_t* out_damage_sum,
size_t* out_damage_count) const;
void update_condition_orders(std::shared_ptr<Card> card); void update_condition_orders(std::shared_ptr<Card> card);
int16_t max_all_attack_bonuses(size_t* out_count) const; int16_t max_all_attack_bonuses(size_t* out_count) const;
void apply_effects_after_card_move(std::shared_ptr<Card> card); void apply_effects_after_card_move(std::shared_ptr<Card> card);
void check_for_defense_interference( void check_for_defense_interference(
std::shared_ptr<const Card> attacker_card, std::shared_ptr<const Card> attacker_card, std::shared_ptr<Card> target_card, int16_t* inout_unknown_p4);
std::shared_ptr<Card> target_card,
int16_t* inout_unknown_p4);
void evaluate_and_apply_effects( void evaluate_and_apply_effects(
EffectWhen when, EffectWhen when,
uint16_t set_card_ref, uint16_t set_card_ref,
@@ -294,20 +249,19 @@ public:
ConditionType exclude_cond = ConditionType::NONE, ConditionType exclude_cond = ConditionType::NONE,
AssistEffect include_eff = AssistEffect::NONE, AssistEffect include_eff = AssistEffect::NONE,
AssistEffect exclude_eff = AssistEffect::NONE) const; AssistEffect exclude_eff = AssistEffect::NONE) const;
void clear_invalid_conditions_on_card( void clear_invalid_conditions_on_card(std::shared_ptr<Card> card, const ActionState& as);
std::shared_ptr<Card> card, const ActionState& as); void on_card_destroyed(std::shared_ptr<Card> attacker_card, std::shared_ptr<Card> destroyed_card);
void on_card_destroyed( std::vector<std::shared_ptr<const Card>> find_cards_in_hp_range(int16_t min, int16_t max) const;
std::shared_ptr<Card> attacker_card, std::shared_ptr<Card> destroyed_card);
std::vector<std::shared_ptr<const Card>> find_cards_in_hp_range(
int16_t min, int16_t max) const;
std::vector<std::shared_ptr<const Card>> find_all_cards_by_aerial_attribute(bool is_aerial) const; std::vector<std::shared_ptr<const Card>> find_all_cards_by_aerial_attribute(bool is_aerial) const;
std::vector<std::shared_ptr<const Card>> find_cards_damaged_by_at_least(int16_t damage) const; std::vector<std::shared_ptr<const Card>> find_cards_damaged_by_at_least(int16_t damage) const;
std::vector<std::shared_ptr<const Card>> find_all_set_cards_on_client_team(uint8_t client_id) const; std::vector<std::shared_ptr<const Card>> find_all_set_cards_on_client_team(uint8_t client_id) const;
std::vector<std::shared_ptr<const Card>> find_all_cards_on_same_or_other_team(uint8_t client_id, bool same_team) const; std::vector<std::shared_ptr<const Card>> find_all_cards_on_same_or_other_team(
uint8_t client_id, bool same_team) const;
std::shared_ptr<const Card> sc_card_for_client_id(uint8_t client_id) const; std::shared_ptr<const Card> sc_card_for_client_id(uint8_t client_id) const;
std::shared_ptr<const Card> get_attacker_card(const ActionState& as) const; std::shared_ptr<const Card> get_attacker_card(const ActionState& as) const;
std::vector<std::shared_ptr<const Card>> get_attacker_card_and_sc_if_item(const ActionState& as) const; std::vector<std::shared_ptr<const Card>> get_attacker_card_and_sc_if_item(const ActionState& as) const;
std::vector<std::shared_ptr<const Card>> find_all_set_cards_with_cost_in_range(uint8_t min_cost, uint8_t max_cost) const; std::vector<std::shared_ptr<const Card>> find_all_set_cards_with_cost_in_range(
uint8_t min_cost, uint8_t max_cost) const;
std::vector<std::shared_ptr<const Card>> filter_cards_by_range( std::vector<std::shared_ptr<const Card>> filter_cards_by_range(
const std::vector<std::shared_ptr<const Card>>& cards, const std::vector<std::shared_ptr<const Card>>& cards,
std::shared_ptr<const Card> card1, std::shared_ptr<const Card> card1,
@@ -334,10 +288,8 @@ public:
void apply_effects_before_attack(std::shared_ptr<Card> card); void apply_effects_before_attack(std::shared_ptr<Card> card);
void apply_effects_after_attack(std::shared_ptr<Card> card); void apply_effects_after_attack(std::shared_ptr<Card> card);
bool client_has_atk_dice_boost_condition(uint8_t client_id); bool client_has_atk_dice_boost_condition(uint8_t client_id);
void unknown_8024A6DC( void unknown_8024A6DC(std::shared_ptr<Card> unknown_p2, std::shared_ptr<Card> unknown_p3);
std::shared_ptr<Card> unknown_p2, std::shared_ptr<Card> unknown_p3); std::vector<std::shared_ptr<const Card>> find_all_sc_cards_of_class(CardClass card_class) const;
std::vector<std::shared_ptr<const Card>> find_all_sc_cards_of_class(
CardClass card_class) const;
private: private:
std::weak_ptr<Server> w_server; std::weak_ptr<Server> w_server;
+51 -129
View File
@@ -102,10 +102,8 @@ Location::Location(uint8_t x, uint8_t y, Direction direction)
unused(0) {} unused(0) {}
bool Location::operator==(const Location& other) const { bool Location::operator==(const Location& other) const {
return (this->x == other.x) && return (this->x == other.x) && (this->y == other.y) &&
(this->y == other.y) && (this->direction == other.direction) && (this->unused == other.unused);
(this->direction == other.direction) &&
(this->unused == other.unused);
} }
bool Location::operator!=(const Location& other) const { bool Location::operator!=(const Location& other) const {
return !this->operator==(other); return !this->operator==(other);
@@ -176,13 +174,9 @@ Direction turn_around(Direction d) {
} }
bool card_class_is_tech_like(CardClass cc, bool is_nte) { bool card_class_is_tech_like(CardClass cc, bool is_nte) {
// NTE does not consider BOSS_TECH to be a tech-like card class, but that's // NTE does not consider BOSS_TECH to be a tech-like card class, but that's probably because that card class just
// probably because that card class just doesn't exist on NTE. // doesn't exist on NTE.
if (is_nte) { return (cc == CardClass::TECH) || (cc == CardClass::PHOTON_BLAST) || (!is_nte && (cc == CardClass::BOSS_TECH));
return (cc == CardClass::TECH) || (cc == CardClass::PHOTON_BLAST);
} else {
return (cc == CardClass::TECH) || (cc == CardClass::PHOTON_BLAST) || (cc == CardClass::BOSS_TECH);
}
} }
static const unordered_map<string, const char*> description_for_expr_token({ static const unordered_map<string, const char*> description_for_expr_token({
@@ -227,31 +221,6 @@ static const unordered_map<string, const char*> description_for_expr_token({
{"ehp", "Attacker HP"}, {"ehp", "Attacker HP"},
}); });
// Arguments are encoded as 3-character null-terminated strings (why?!), and are
// used for adding conditions to effects (e.g. making them only trigger in
// certain situations) or otherwise customizing their results. The arguments are
// heterogeneous based on their position; that is, the first argument always has
// the same meaning, and meaning letters that are valid in arg1 are not
// necessarily valid in arg2, etc.
// Argument meanings:
// a01 = ???
// e00 = effect lasts while equipped? (in contrast to tXX)
// pXX = who to target (see description_for_p_target)
// In arg2:
// bXX = require attack doing not more than XX damage
// cXY/CXY = linked items (require item with cYX/CYX to be equipped as well)
// dXY = roll one die; require result between X and Y inclusive
// hXX = require HP >= XX
// iXX = require HP <= XX
// mXX = require attack doing at least XX damage
// nXX = require condition (see description_for_n_condition below)
// oXX = seems to be "require previous random-condition effect to have happened"
// TODO: this is used as both o01 (recovery) and o11 (reflection)
// conditions - why the difference?
// rXX = randomly pass with XX% chance (if XX == 00, 100% chance?)
// sXY = require card cost between X and Y ATK points (inclusive)
// tXX = lasts XX turns, or activate after XX turns
static const vector<const char*> description_for_n_condition({ static const vector<const char*> description_for_n_condition({
/* n00 */ "Always true", /* n00 */ "Always true",
/* n01 */ "Card is Hunters-side SC", /* n01 */ "Card is Hunters-side SC",
@@ -730,8 +699,7 @@ void CardDefinition::decode_range() {
} }
string name_for_rank(CardRank rank) { string name_for_rank(CardRank rank) {
static const vector<const char*> names( static const vector<const char*> names({"N1", "R1", "S", "E", "N2", "N3", "N4", "R2", "R3", "R4", "SS", "D1", "D2"});
{"N1", "R1", "S", "E", "N2", "N3", "N4", "R2", "R3", "R4", "SS", "D1", "D2"});
try { try {
return names.at(static_cast<uint8_t>(rank) - 1); return names.at(static_cast<uint8_t>(rank) - 1);
} catch (const out_of_range&) { } catch (const out_of_range&) {
@@ -1155,9 +1123,7 @@ void PlayerConfig::decrypt() {
return; return;
} }
decrypt_trivial_gci_data( decrypt_trivial_gci_data(
&this->card_counts, &this->card_counts, offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts), this->basis);
offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts),
this->basis);
this->is_encrypted = 0; this->is_encrypted = 0;
this->basis = 0; this->basis = 0;
} }
@@ -1170,9 +1136,7 @@ void PlayerConfig::encrypt(uint8_t basis) {
this->decrypt(); this->decrypt();
} }
decrypt_trivial_gci_data( decrypt_trivial_gci_data(
&this->card_counts, &this->card_counts, offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts), basis);
offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts),
basis);
this->is_encrypted = 1; this->is_encrypted = 1;
this->basis = basis; this->basis = basis;
} }
@@ -1215,8 +1179,7 @@ PlayerConfigNTE::PlayerConfigNTE(const PlayerConfig& config)
last_online_battle_start_timestamp(config.last_online_battle_start_timestamp), last_online_battle_start_timestamp(config.last_online_battle_start_timestamp),
unknown_t3(config.unknown_t3), unknown_t3(config.unknown_t3),
unknown_a14(config.unknown_a14) { unknown_a14(config.unknown_a14) {
// TODO: Do we need to recompute card_count_checksums? (Here or in operator // TODO: Do we need to recompute card_count_checksums? (Here or in operator PlayerConfig?)
// PlayerConfig?)
} }
PlayerConfigNTE::operator PlayerConfig() const { PlayerConfigNTE::operator PlayerConfig() const {
@@ -1390,8 +1353,7 @@ string Rules::str() const {
if (static_cast<uint8_t>(this->hp_type) == 0xFF) { if (static_cast<uint8_t>(this->hp_type) == 0xFF) {
tokens.emplace_back("hp_type=(open)"); tokens.emplace_back("hp_type=(open)");
} else { } else {
tokens.emplace_back(std::format("hp_type=({:02X})", tokens.emplace_back(std::format("hp_type=({:02X})", static_cast<uint8_t>(this->hp_type)));
static_cast<uint8_t>(this->hp_type)));
} }
break; break;
} }
@@ -1439,8 +1401,7 @@ string Rules::str() const {
if (static_cast<uint8_t>(this->dice_exchange_mode) == 0xFF) { if (static_cast<uint8_t>(this->dice_exchange_mode) == 0xFF) {
tokens.emplace_back("dice_exchange=(open)"); tokens.emplace_back("dice_exchange=(open)");
} else { } else {
tokens.emplace_back(std::format("dice_exchange=({:02X})", tokens.emplace_back(std::format("dice_exchange=({:02X})", static_cast<uint8_t>(this->dice_exchange_mode)));
static_cast<uint8_t>(this->dice_exchange_mode)));
} }
break; break;
} }
@@ -1479,8 +1440,7 @@ string Rules::str() const {
if (static_cast<uint8_t>(this->allowed_cards) == 0xFF) { if (static_cast<uint8_t>(this->allowed_cards) == 0xFF) {
tokens.emplace_back("allowed_cards=(open)"); tokens.emplace_back("allowed_cards=(open)");
} else { } else {
tokens.emplace_back(std::format("allowed_cards=({:02X})", tokens.emplace_back(std::format("allowed_cards=({:02X})", static_cast<uint8_t>(this->allowed_cards)));
static_cast<uint8_t>(this->allowed_cards)));
} }
break; break;
} }
@@ -1518,20 +1478,12 @@ RulesTrial::RulesTrial(const Rules& r)
no_assist_cards(r.no_assist_cards), no_assist_cards(r.no_assist_cards),
disable_dialogue(r.disable_dialogue), disable_dialogue(r.disable_dialogue),
dice_exchange_mode(r.dice_exchange_mode) { dice_exchange_mode(r.dice_exchange_mode) {
if (r.max_dice_value == r.min_dice_value) { this->atk_die_behavior = (r.max_dice_value == r.min_dice_value) ? r.max_dice_value : 0;
this->atk_die_behavior = r.max_dice_value;
} else {
this->atk_die_behavior = 0; // Random
}
if (r.def_dice_value_range == 0xFF) { if (r.def_dice_value_range == 0xFF) {
this->def_die_behavior = 0xFF; this->def_die_behavior = 0xFF;
} else { } else {
auto def_range = r.def_dice_range(false); auto def_range = r.def_dice_range(false);
if (def_range.first == def_range.second) { this->def_die_behavior = (def_range.first == def_range.second) ? def_range.first : 0;
this->def_die_behavior = def_range.first;
} else {
this->def_die_behavior = 0;
}
} }
} }
@@ -1795,10 +1747,7 @@ phosg::JSON MapDefinition::EntryState::json() const {
default: default:
deck_type_json = this->deck_type; deck_type_json = this->deck_type;
} }
return phosg::JSON::dict({ return phosg::JSON::dict({{"PlayerType", std::move(player_type_json)}, {"DeckType", std::move(deck_type_json)}});
{"PlayerType", std::move(player_type_json)},
{"DeckType", std::move(deck_type_json)},
});
} }
// TODO: // TODO:
@@ -1807,15 +1756,10 @@ phosg::JSON MapDefinition::EntryState::json() const {
string MapDefinition::CameraSpec::str() const { string MapDefinition::CameraSpec::str() const {
return std::format( return std::format(
"CameraSpec[a1=({:g} {:g} {:g} {:g} {:g} {:g} {:g} {:g} {:g}) camera=({:g} {:g} {:g}) focus=({:g} {:g} {:g}) a2=({:g} {:g} {:g})]", "CameraSpec[a1=({:g} {:g} {:g} {:g} {:g} {:g} {:g} {:g} {:g}) camera=({:g} {:g} {:g}) focus=({:g} {:g} {:g}) a2=({:g} {:g} {:g})]",
this->unknown_a1[0], this->unknown_a1[1], this->unknown_a1[0], this->unknown_a1[1], this->unknown_a1[2], this->unknown_a1[3], this->unknown_a1[4],
this->unknown_a1[2], this->unknown_a1[3], this->unknown_a1[5], this->unknown_a1[6], this->unknown_a1[7], this->unknown_a1[8],
this->unknown_a1[4], this->unknown_a1[5], this->camera_x, this->camera_y, this->camera_z, this->focus_x, this->focus_y, this->focus_z,
this->unknown_a1[6], this->unknown_a1[7], this->unknown_a2[0], this->unknown_a2[1], this->unknown_a2[2]);
this->unknown_a1[8], this->camera_x,
this->camera_y, this->camera_z,
this->focus_x, this->focus_y,
this->focus_z, this->unknown_a2[0],
this->unknown_a2[1], this->unknown_a2[2]);
} }
string MapDefinition::str(const CardIndex* card_index, Language language) const { string MapDefinition::str(const CardIndex* card_index, Language language) const {
@@ -1830,21 +1774,19 @@ string MapDefinition::str(const CardIndex* card_index, Language language) const
} }
}; };
lines.emplace_back(std::format("Map {:08X}: {}x{}", lines.emplace_back(std::format("Map {:08X}: {}x{}", this->map_number, this->width, this->height));
this->map_number, this->width, this->height));
lines.emplace_back(std::format(" tag: {:08X}", this->tag)); lines.emplace_back(std::format(" tag: {:08X}", this->tag));
lines.emplace_back(std::format(" environment_number: {:02X} ({})", this->environment_number, name_for_environment_number(this->environment_number))); lines.emplace_back(std::format(" environment_number: {:02X} ({})",
this->environment_number, name_for_environment_number(this->environment_number)));
lines.emplace_back(std::format(" num_camera_zones: {:02X}", this->num_camera_zones)); lines.emplace_back(std::format(" num_camera_zones: {:02X}", this->num_camera_zones));
lines.emplace_back(" tiles:"); lines.emplace_back(" tiles:");
add_map(this->map_tiles); add_map(this->map_tiles);
lines.emplace_back(std::format( lines.emplace_back(std::format(
" start_tile_definitions: A:[1p: {:02X}; 2p: {:02X},{:02X}; 3p: {:02X},{:02X},{:02X}], B:[1p: {:02X}; 2p: {:02X},{:02X}; 3p: {:02X},{:02X},{:02X}]", " start_tile_definitions: A:[1p: {:02X}; 2p: {:02X},{:02X}; 3p: {:02X},{:02X},{:02X}], B:[1p: {:02X}; 2p: {:02X},{:02X}; 3p: {:02X},{:02X},{:02X}]",
this->start_tile_definitions[0][0], this->start_tile_definitions[0][1], this->start_tile_definitions[0][0], this->start_tile_definitions[0][1], this->start_tile_definitions[0][2],
this->start_tile_definitions[0][2], this->start_tile_definitions[0][3], this->start_tile_definitions[0][3], this->start_tile_definitions[0][4], this->start_tile_definitions[0][5],
this->start_tile_definitions[0][4], this->start_tile_definitions[0][5], this->start_tile_definitions[1][0], this->start_tile_definitions[1][1], this->start_tile_definitions[1][2],
this->start_tile_definitions[1][0], this->start_tile_definitions[1][1], this->start_tile_definitions[1][3], this->start_tile_definitions[1][4], this->start_tile_definitions[1][5]));
this->start_tile_definitions[1][2], this->start_tile_definitions[1][3],
this->start_tile_definitions[1][4], this->start_tile_definitions[1][5]));
for (size_t z = 0; z < this->num_camera_zones; z++) { for (size_t z = 0; z < this->num_camera_zones; z++) {
for (size_t w = 0; w < 2; w++) { for (size_t w = 0; w < 2; w++) {
lines.emplace_back(std::format(" camera zone {} (team {}):", z, w ? 'A' : 'B')); lines.emplace_back(std::format(" camera zone {} (team {}):", z, w ? 'A' : 'B'));
@@ -1862,47 +1804,28 @@ string MapDefinition::str(const CardIndex* card_index, Language language) const
add_map(this->overlay_state.tiles); add_map(this->overlay_state.tiles);
lines.emplace_back(std::format( lines.emplace_back(std::format(
" unused1: {:08X} {:08X} {:08X} {:08X} {:08X}", " unused1: {:08X} {:08X} {:08X} {:08X} {:08X}",
this->overlay_state.unused1[0], this->overlay_state.unused1[0], this->overlay_state.unused1[1], this->overlay_state.unused1[2],
this->overlay_state.unused1[1], this->overlay_state.unused1[3], this->overlay_state.unused1[4]));
this->overlay_state.unused1[2],
this->overlay_state.unused1[3],
this->overlay_state.unused1[4]));
lines.emplace_back(std::format( lines.emplace_back(std::format(
" trap_tile_colors_nte: {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X}", " trap_tile_colors_nte: {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X}",
this->overlay_state.trap_tile_colors_nte[0], this->overlay_state.trap_tile_colors_nte[0], this->overlay_state.trap_tile_colors_nte[1],
this->overlay_state.trap_tile_colors_nte[1], this->overlay_state.trap_tile_colors_nte[2], this->overlay_state.trap_tile_colors_nte[3],
this->overlay_state.trap_tile_colors_nte[2], this->overlay_state.trap_tile_colors_nte[4], this->overlay_state.trap_tile_colors_nte[5],
this->overlay_state.trap_tile_colors_nte[3], this->overlay_state.trap_tile_colors_nte[6], this->overlay_state.trap_tile_colors_nte[7],
this->overlay_state.trap_tile_colors_nte[4], this->overlay_state.trap_tile_colors_nte[8], this->overlay_state.trap_tile_colors_nte[9],
this->overlay_state.trap_tile_colors_nte[5], this->overlay_state.trap_tile_colors_nte[10], this->overlay_state.trap_tile_colors_nte[11],
this->overlay_state.trap_tile_colors_nte[6], this->overlay_state.trap_tile_colors_nte[12], this->overlay_state.trap_tile_colors_nte[13],
this->overlay_state.trap_tile_colors_nte[7], this->overlay_state.trap_tile_colors_nte[14], this->overlay_state.trap_tile_colors_nte[15]));
this->overlay_state.trap_tile_colors_nte[8],
this->overlay_state.trap_tile_colors_nte[9],
this->overlay_state.trap_tile_colors_nte[10],
this->overlay_state.trap_tile_colors_nte[11],
this->overlay_state.trap_tile_colors_nte[12],
this->overlay_state.trap_tile_colors_nte[13],
this->overlay_state.trap_tile_colors_nte[14],
this->overlay_state.trap_tile_colors_nte[15]));
lines.emplace_back(std::format( lines.emplace_back(std::format(
" trap_card_ids_nte: #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X}", " trap_card_ids_nte: #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X} #{:04X}",
this->overlay_state.trap_card_ids_nte[0], this->overlay_state.trap_card_ids_nte[0], this->overlay_state.trap_card_ids_nte[1],
this->overlay_state.trap_card_ids_nte[1], this->overlay_state.trap_card_ids_nte[2], this->overlay_state.trap_card_ids_nte[3],
this->overlay_state.trap_card_ids_nte[2], this->overlay_state.trap_card_ids_nte[4], this->overlay_state.trap_card_ids_nte[5],
this->overlay_state.trap_card_ids_nte[3], this->overlay_state.trap_card_ids_nte[6], this->overlay_state.trap_card_ids_nte[7],
this->overlay_state.trap_card_ids_nte[4], this->overlay_state.trap_card_ids_nte[8], this->overlay_state.trap_card_ids_nte[9],
this->overlay_state.trap_card_ids_nte[5], this->overlay_state.trap_card_ids_nte[10], this->overlay_state.trap_card_ids_nte[11],
this->overlay_state.trap_card_ids_nte[6], this->overlay_state.trap_card_ids_nte[12], this->overlay_state.trap_card_ids_nte[13],
this->overlay_state.trap_card_ids_nte[7], this->overlay_state.trap_card_ids_nte[14], this->overlay_state.trap_card_ids_nte[15]));
this->overlay_state.trap_card_ids_nte[8],
this->overlay_state.trap_card_ids_nte[9],
this->overlay_state.trap_card_ids_nte[10],
this->overlay_state.trap_card_ids_nte[11],
this->overlay_state.trap_card_ids_nte[12],
this->overlay_state.trap_card_ids_nte[13],
this->overlay_state.trap_card_ids_nte[14],
this->overlay_state.trap_card_ids_nte[15]));
lines.emplace_back(" default_rules: " + this->default_rules.str()); lines.emplace_back(" default_rules: " + this->default_rules.str());
lines.emplace_back(" name: " + this->name.decode(language)); lines.emplace_back(" name: " + this->name.decode(language));
@@ -2081,8 +2004,7 @@ string MapDefinition::str(const CardIndex* card_index, Language language) const
deck_type = std::format("({:02X})", this->entry_states[z].deck_type); deck_type = std::format("({:02X})", this->entry_states[z].deck_type);
break; break;
} }
lines.emplace_back(std::format( lines.emplace_back(std::format(" entry_states[{}]: {} / {}", z, player_type, deck_type));
" entry_states[{}]: {} / {}", z, player_type, deck_type));
} }
return phosg::join(lines, "\n"); return phosg::join(lines, "\n");
} }
@@ -2480,8 +2402,7 @@ CardIndex::CardIndex(
const auto& footer = r.pget<RELFileFooterBE>(r.size() - sizeof(RELFileFooterBE)); const auto& footer = r.pget<RELFileFooterBE>(r.size() - sizeof(RELFileFooterBE));
uint32_t offset = r.pget_u32b(footer.root_offset); uint32_t offset = r.pget_u32b(footer.root_offset);
uint32_t count = r.pget_u32b(footer.root_offset + 4); uint32_t count = r.pget_u32b(footer.root_offset + 4);
if (offset > decompressed_data.size() || if (offset > decompressed_data.size() || ((offset + count * sizeof(CardDefinition)) > decompressed_data.size())) {
((offset + count * sizeof(CardDefinition)) > decompressed_data.size())) {
throw runtime_error("definitions array reference out of bounds"); throw runtime_error("definitions array reference out of bounds");
} }
CardDefinition* defs = reinterpret_cast<CardDefinition*>(decompressed_data.data() + offset); CardDefinition* defs = reinterpret_cast<CardDefinition*>(decompressed_data.data() + offset);
@@ -2497,8 +2418,7 @@ CardIndex::CardIndex(
auto entry = make_shared<CardEntry>(CardEntry{def, "", "", "", {}}); auto entry = make_shared<CardEntry>(CardEntry{def, "", "", "", {}});
if (!this->card_definitions.emplace(entry->def.card_id, entry).second) { if (!this->card_definitions.emplace(entry->def.card_id, entry).second) {
throw runtime_error(std::format( throw runtime_error(std::format("duplicate card id: {:08X}", entry->def.card_id));
"duplicate card id: {:08X}", entry->def.card_id));
} }
// Some cards intentionally have the same name, so we just leave them // Some cards intentionally have the same name, so we just leave them
@@ -2801,7 +2721,8 @@ MapIndex::MapIndex(const string& directory, bool raise_on_any_failure) {
name); name);
} else { } else {
if (map_it->second->visibility_flags != visibility_flags) { if (map_it->second->visibility_flags != visibility_flags) {
throw std::runtime_error(std::format("visibility flags {:02X} for added map {} do not match existing flags {}", throw std::runtime_error(std::format(
"visibility flags {:02X} for added map {} do not match existing flags {}",
map_it->second->visibility_flags, file_path, visibility_flags)); map_it->second->visibility_flags, file_path, visibility_flags));
} }
map_it->second->add_version(vm); map_it->second->add_version(vm);
@@ -2951,7 +2872,8 @@ const string& MapIndex::get_compressed_list(size_t num_players, Language languag
is_trial ? "trial" : "final", num_players, compressed_map_list.size())); is_trial ? "trial" : "final", num_players, compressed_map_list.size()));
} }
size_t decompressed_size = sizeof(header) + entries_w.size() + strings_w.size(); size_t decompressed_size = sizeof(header) + entries_w.size() + strings_w.size();
static_game_data_log.info_f("Generated Episode 3 compressed {} map list for {} player(s) ({} maps; 0x{:X} -> 0x{:X} bytes)", static_game_data_log.info_f(
"Generated Episode 3 compressed {} map list for {} player(s) ({} maps; 0x{:X} -> 0x{:X} bytes)",
is_trial ? "trial" : "final", num_players, num_maps, decompressed_size, compressed_map_list.size()); is_trial ? "trial" : "final", num_players, num_maps, decompressed_size, compressed_map_list.size());
} }
return compressed_map_list; return compressed_map_list;
+23 -3
View File
@@ -521,11 +521,31 @@ struct CardDefinition {
/* 02 */ pstring<TextEncoding::ASCII, 0x0F> expr; /* 02 */ pstring<TextEncoding::ASCII, 0x0F> expr;
// when specifies in which phase the effect should activate. // when specifies in which phase the effect should activate.
/* 11 */ EffectWhen when; /* 11 */ EffectWhen when;
// arg1 generally specifies how long the effect activates for. // Arguments are encoded as 3-character null-terminated strings (why?!), and are used for adding conditions to
// effects (e.g. making them only trigger in certain situations) or otherwise customizing their results. The
// arguments are heterogeneous based on their position; that is, the first argument always has the same meaning,
// and meaning letters that are valid in arg1 are not necessarily valid in arg2, etc.
// arg1 specifies how long the effect activates for:
// a01 = argument value (meaning depends on condition type)
// eXX = effect lasts while equipped
// fXX = TODO (remaining_turns=100)
// rXX = TODO (remaining_turns=102)
// tXX = effect lasts for XX (decimal) turns
/* 12 */ pstring<TextEncoding::ASCII, 4> arg1; /* 12 */ pstring<TextEncoding::ASCII, 4> arg1;
// arg2 generally specifies a condition for when the effect activates. // arg2 restricts a condition to only activate in certain conditions:
// bXX = require attack doing not more than XX damage
// cXY/CXY = linked items (require item with cYX/CYX to be equipped as well)
// dXY = roll one die; require result between X and Y inclusive
// hXX = require HP >= XX
// iXX = require HP <= XX
// mXX = require attack doing at least XX damage
// nXX = require condition (see description_for_n_condition for values here)
// oXY = require effect #Y to have occurred (if X != 1, on this card; if X = 1, on opponent card)
// rXX = randomly pass with XX% chance
// sXY = require card cost between X and Y ATK points (inclusive)
// tXX = activate after XX turns from card set
/* 16 */ pstring<TextEncoding::ASCII, 4> arg2; /* 16 */ pstring<TextEncoding::ASCII, 4> arg2;
// arg3 generally specifies who is targeted by the effect. // arg3 specifies who is affected by the condition; see description_for_p_target for possible values.
/* 1A */ pstring<TextEncoding::ASCII, 4> arg3; /* 1A */ pstring<TextEncoding::ASCII, 4> arg3;
// apply_criterion can be used to apply an additional condition for when the effect should activate. For example, // apply_criterion can be used to apply an additional condition for when the effect should activate. For example,
// it can be used to make the effect only activate if the target is not a Story Character. // it can be used to make the effect only activate if the target is not a Story Character.
+14 -21
View File
@@ -92,8 +92,7 @@ bool DeckState::draw_card_by_ref(uint16_t card_ref) {
auto& entry = this->entries[index]; auto& entry = this->entries[index];
if (entry.state == CardState::DISCARDED) { if (entry.state == CardState::DISCARDED) {
// If the card is discarded, then it should be before the draw index, and we // If the card is discarded, then it should be before the draw index, and we can just change its state.
// can just change its state.
entry.state = CardState::IN_HAND; entry.state = CardState::IN_HAND;
return true; return true;
} }
@@ -102,9 +101,8 @@ bool DeckState::draw_card_by_ref(uint16_t card_ref) {
return false; return false;
} }
// If the card is still drawable, we need to move it so it's just in front of // If the card is still drawable, we need to move it so it's just in front of the draw index, then immediately draw
// the draw index, then immediately draw it. Ep3 NTE does not handle this // it. Ep3 NTE does not handle this case, but we do even when playing NTE.
// case, but we do even when playing NTE.
size_t ref_index; size_t ref_index;
for (ref_index = 0; ref_index < this->card_refs.size(); ref_index++) { for (ref_index = 0; ref_index < this->card_refs.size(); ref_index++) {
if (this->card_refs[ref_index] == card_ref) { if (this->card_refs[ref_index] == card_ref) {
@@ -131,13 +129,8 @@ uint16_t DeckState::card_id_for_card_ref(uint16_t card_ref) const {
if (card_ref == 0xFFFF) { if (card_ref == 0xFFFF) {
return 0xFFFF; return 0xFFFF;
} }
uint8_t index = index_for_card_ref(card_ref); uint8_t index = index_for_card_ref(card_ref);
if (index < this->entries.size()) { return (index < this->entries.size()) ? this->entries[index].card_id : 0xFFFF;
return this->entries[index].card_id;
} else {
return 0xFFFF;
}
} }
uint16_t DeckState::sc_card_id() const { uint16_t DeckState::sc_card_id() const {
@@ -167,8 +160,7 @@ void DeckState::restart() {
} }
} }
// For any cards that are still in hand or still in play, move their refs to // For any cards that are still in hand or still in play, move their refs to the already-drawn part of the deck
// the already-drawn part of the deck
this->draw_index = 0; this->draw_index = 0;
for (size_t z = 0; z < this->entries.size(); z++) { for (size_t z = 0; z < this->entries.size(); z++) {
if (this->entries[z].state != CardState::DRAWABLE) { if (this->entries[z].state != CardState::DRAWABLE) {
@@ -196,8 +188,7 @@ void DeckState::redraw_initial_hand(bool is_nte) {
this->draw_index = 1; this->draw_index = 1;
if (is_nte || this->shuffle_enabled) { if (is_nte || this->shuffle_enabled) {
// Get the next 5 cards from the deck, and put the previous 5 cards after // Get the next 5 cards from the deck, and put the previous 5 cards after them (so they will be shuffled back in).
// them (so they will be shuffled back in).
for (uint8_t z = 0; z < 5; z++) { for (uint8_t z = 0; z < 5; z++) {
uint8_t index = z + this->draw_index; uint8_t index = z + this->draw_index;
uint16_t temp_ref = this->card_refs[index]; uint16_t temp_ref = this->card_refs[index];
@@ -274,11 +265,9 @@ void DeckState::shuffle() {
size_t max = this->num_drawable_cards(); size_t max = this->num_drawable_cards();
for (size_t z = 0; z < this->card_refs.size(); z++) { for (size_t z = 0; z < this->card_refs.size(); z++) {
// Note: This is the way Sega originally implemented shuffling - they just // Note: This is the way Sega originally implemented shuffling - they just do N swaps on the entire array. A more
// do N swaps on the entire array. A more uniform way to do it would be to // uniform way to do it would be to instead swap each item with another random item (possibly itself) that
// instead swap each item with another random item (possibly itself) that // doesn't appear earlier than it in the array, but this is not what Sega did.
// doesn't appear earlier than it in the array, but this is not what Sega
// did.
uint8_t index1 = this->draw_index + s->get_random(max); uint8_t index1 = this->draw_index + s->get_random(max);
uint8_t index2 = this->draw_index + s->get_random(max); uint8_t index2 = this->draw_index + s->get_random(max);
uint16_t temp_ref = this->card_refs[index1]; uint16_t temp_ref = this->card_refs[index1];
@@ -309,7 +298,11 @@ static const char* name_for_card_state(DeckState::CardState st) {
void DeckState::print(FILE* stream, std::shared_ptr<const CardIndex> card_index) const { void DeckState::print(FILE* stream, std::shared_ptr<const CardIndex> card_index) const {
phosg::fwrite_fmt(stream, "DeckState: client_id={} draw_index={} card_ref_base=@{:04X} shuffle={} loop={}\n", phosg::fwrite_fmt(stream, "DeckState: client_id={} draw_index={} card_ref_base=@{:04X} shuffle={} loop={}\n",
this->client_id, this->draw_index, this->card_ref_base, this->shuffle_enabled ? "true" : "false", this->loop_enabled ? "true" : "false"); this->client_id,
this->draw_index,
this->card_ref_base,
this->shuffle_enabled ? "true" : "false",
this->loop_enabled ? "true" : "false");
for (size_t z = 0; z < 31; z++) { for (size_t z = 0; z < 31; z++) {
const auto& e = this->entries[z]; const auto& e = this->entries[z];
shared_ptr<const CardIndex::CardEntry> ce; shared_ptr<const CardIndex::CardEntry> ce;
+3 -7
View File
@@ -28,9 +28,8 @@ struct DeckEntry {
/* 00 */ pstring<TextEncoding::MARKED, 0x10> name; /* 00 */ pstring<TextEncoding::MARKED, 0x10> name;
/* 10 */ le_uint32_t team_id; /* 10 */ le_uint32_t team_id;
/* 14 */ parray<le_uint16_t, 31> card_ids; /* 14 */ parray<le_uint16_t, 31> card_ids;
// If the following flag is not set to 3, then the God Whim assist effect can // If the following flag is not set to 3, then the God Whim assist effect can use cards that are hidden from the
// use cards that are hidden from the player during deck building. The client // player during deck building. The client always sets this to 3, and it's not clear why this even exists.
// always sets this to 3, and it's not clear why this even exists.
/* 52 */ uint8_t god_whim_flag; /* 52 */ uint8_t god_whim_flag;
/* 53 */ uint8_t unused1; /* 53 */ uint8_t unused1;
/* 54 */ le_uint16_t player_level; /* 54 */ le_uint16_t player_level;
@@ -56,10 +55,7 @@ public:
}; };
template <typename CardIDT> template <typename CardIDT>
DeckState( DeckState(uint8_t client_id, const parray<CardIDT, 0x1F>& card_ids, std::shared_ptr<Server> server)
uint8_t client_id,
const parray<CardIDT, 0x1F>& card_ids,
std::shared_ptr<Server> server)
: server(server), : server(server),
client_id(client_id), client_id(client_id),
draw_index(1), draw_index(1),
+41 -88
View File
@@ -110,9 +110,7 @@ void PlayerState::init() {
this->set_card_action_chains, this->set_card_action_chains,
this->set_card_action_metadatas); this->set_card_action_metadatas);
s->ruler_server->set_client_team_id(this->client_id, this->team_id); s->ruler_server->set_client_team_id(this->client_id, this->team_id);
s->card_special->on_card_set(this->shared_from_this(), this->sc_card_ref); s->card_special->on_card_set(this->shared_from_this(), this->sc_card_ref);
this->god_whim_can_use_hidden_cards = (s->deck_entries[this->client_id]->god_whim_flag != 3); this->god_whim_can_use_hidden_cards = (s->deck_entries[this->client_id]->god_whim_flag != 3);
} }
@@ -153,8 +151,7 @@ bool PlayerState::draw_cards_allowed() const {
return true; return true;
} }
void PlayerState::apply_assist_card_effect_on_set( void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter_ps) {
shared_ptr<PlayerState> setter_ps) {
auto s = this->server(); auto s = this->server();
uint16_t assist_card_id = this->set_assist_card_id; uint16_t assist_card_id = this->set_assist_card_id;
@@ -163,8 +160,7 @@ void PlayerState::apply_assist_card_effect_on_set(
} }
auto assist_effect = assist_effect_number_for_card_id(assist_card_id, s->options.is_nte()); auto assist_effect = assist_effect_number_for_card_id(assist_card_id, s->options.is_nte());
if ((assist_effect == AssistEffect::RESISTANCE) || if ((assist_effect == AssistEffect::RESISTANCE) || (assist_effect == AssistEffect::INDEPENDENT)) {
(assist_effect == AssistEffect::INDEPENDENT)) {
this->assist_card_set_number = 0; this->assist_card_set_number = 0;
} }
@@ -314,8 +310,7 @@ void PlayerState::apply_assist_card_effect_on_set(
auto other_ps = s->get_player_state(client_id); auto other_ps = s->get_player_state(client_id);
if (other_ps.get() != this) { if (other_ps.get() != this) {
other_ps->deck_state->draw_card_by_ref(this->card_refs[7]); other_ps->deck_state->draw_card_by_ref(this->card_refs[7]);
other_ps->set_card_from_hand( other_ps->set_card_from_hand(this->card_refs[7], 0xF, nullptr, client_id, 1);
this->card_refs[7], 0xF, nullptr, client_id, 1);
} }
} }
break; break;
@@ -362,9 +357,7 @@ void PlayerState::apply_assist_card_effect_on_set(
} }
for (ssize_t set_index = is_nte ? 0 : -1; set_index < 8; set_index++) { for (ssize_t set_index = is_nte ? 0 : -1; set_index < 8; set_index++) {
auto card = (set_index == -1) auto card = (set_index == -1) ? other_ps->get_sc_card() : other_ps->get_set_card(set_index);
? other_ps->get_sc_card()
: other_ps->get_set_card(set_index);
if (card) { if (card) {
for (size_t cond_index = 0; cond_index < 9; cond_index++) { for (size_t cond_index = 0; cond_index < 9; cond_index++) {
auto& cond = card->action_chain.conditions[cond_index]; auto& cond = card->action_chain.conditions[cond_index];
@@ -404,9 +397,7 @@ void PlayerState::apply_assist_card_effect_on_set(
} }
for (ssize_t set_index = is_nte ? 0 : -1; set_index < 8; set_index++) { for (ssize_t set_index = is_nte ? 0 : -1; set_index < 8; set_index++) {
auto card = (set_index == -1) auto card = (set_index == -1) ? other_ps->get_sc_card() : other_ps->get_set_card(set_index);
? other_ps->get_sc_card()
: other_ps->get_set_card(set_index);
if (card) { if (card) {
for (size_t cond_index = 0; cond_index < 9; cond_index++) { for (size_t cond_index = 0; cond_index < 9; cond_index++) {
auto& cond = card->action_chain.conditions[cond_index]; auto& cond = card->action_chain.conditions[cond_index];
@@ -577,8 +568,7 @@ void PlayerState::discard_all_attack_action_cards_from_hand() {
for (size_t hand_index = 0; hand_index < 6; hand_index++) { for (size_t hand_index = 0; hand_index < 6; hand_index++) {
uint16_t card_ref = temp_card_refs[hand_index]; uint16_t card_ref = temp_card_refs[hand_index];
auto ce = s->definition_for_card_ref(card_ref); auto ce = s->definition_for_card_ref(card_ref);
if (ce && (ce->def.type == CardType::ACTION) && if (ce && (ce->def.type == CardType::ACTION) && (ce->def.card_class() != CardClass::DEFENSE_ACTION)) {
(ce->def.card_class() != CardClass::DEFENSE_ACTION)) {
this->discard_ref_from_hand(card_ref); this->discard_ref_from_hand(card_ref);
} }
} }
@@ -771,9 +761,8 @@ void PlayerState::draw_hand(ssize_t override_count) {
} }
void PlayerState::draw_initial_hand() { void PlayerState::draw_initial_hand() {
// Note: The original code called this->deck_state->init_card_states here, but // Note: The original code called this->deck_state->init_card_states here, but we don't because that logic is now in
// we don't because that logic is now in the DeckState constructor, and this // the DeckState constructor, and this function should only be called during PlayerState construction (so, shortly
// function should only be called during PlayerState construction (so, shortly
// after DeckState construction as well). // after DeckState construction as well).
this->deck_state->restart(); this->deck_state->restart();
this->card_refs.clear(0xFFFF); this->card_refs.clear(0xFFFF);
@@ -782,10 +771,7 @@ void PlayerState::draw_initial_hand() {
} }
int32_t PlayerState::error_code_for_client_setting_card( int32_t PlayerState::error_code_for_client_setting_card(
uint16_t card_ref, uint16_t card_ref, uint8_t card_index, const Location* loc, uint8_t assist_target_client_id) const {
uint8_t card_index,
const Location* loc,
uint8_t assist_target_client_id) const {
auto s = this->server(); auto s = this->server();
int32_t code = s->ruler_server->error_code_for_client_setting_card( int32_t code = s->ruler_server->error_code_for_client_setting_card(
@@ -816,8 +802,7 @@ int32_t PlayerState::error_code_for_client_setting_card(
if (this->card_refs[card_index + 1] != 0xFFFF) { if (this->card_refs[card_index + 1] != 0xFFFF) {
return -0x7E; return -0x7E;
} }
if ((ce->def.type == CardType::CREATURE) && if ((ce->def.type == CardType::CREATURE) && !s->map_and_rules->tile_is_vacant(loc->x, loc->y)) {
!s->map_and_rules->tile_is_vacant(loc->x, loc->y)) {
return -0x7A; return -0x7A;
} }
return 0; return 0;
@@ -834,9 +819,7 @@ int32_t PlayerState::error_code_for_client_setting_card(
} }
vector<uint16_t> PlayerState::get_all_cards_within_range( vector<uint16_t> PlayerState::get_all_cards_within_range(
const parray<uint8_t, 9 * 9>& range, const parray<uint8_t, 9 * 9>& range, const Location& loc, uint8_t target_team_id) const {
const Location& loc,
uint8_t target_team_id) const {
auto s = this->server(); auto s = this->server();
auto log = s->log_stack("get_all_cards_within_range: "); auto log = s->log_stack("get_all_cards_within_range: ");
@@ -846,8 +829,7 @@ vector<uint16_t> PlayerState::get_all_cards_within_range(
vector<uint16_t> ret; vector<uint16_t> ret;
for (size_t client_id = 0; client_id < 4; client_id++) { for (size_t client_id = 0; client_id < 4; client_id++) {
auto other_ps = s->player_states[client_id]; auto other_ps = s->player_states[client_id];
if (other_ps && if (other_ps && ((target_team_id == 0xFF) || (target_team_id == other_ps->get_team_id()))) {
((target_team_id == 0xFF) || (target_team_id == other_ps->get_team_id()))) {
auto card_refs = get_card_refs_within_range(range, loc, *other_ps->card_short_statuses, &log); auto card_refs = get_card_refs_within_range(range, loc, *other_ps->card_short_statuses, &log);
ret.insert(ret.end(), card_refs.begin(), card_refs.end()); ret.insert(ret.end(), card_refs.begin(), card_refs.end());
} }
@@ -945,9 +927,8 @@ bool PlayerState::is_hand_redraw_allowed() const {
bool PlayerState::is_team_turn() const { bool PlayerState::is_team_turn() const {
auto s = this->server(); auto s = this->server();
// Note: The original code checks if this->w_server is null before doing this. // Note: The original code checks if this->w_server is null before doing this. We don't check because that should
// We don't check because that should never happen, and server() will throw if // never happen, and server() will throw if it does.
// it does.
return s->get_current_team_turn() == this->team_id; return s->get_current_team_turn() == this->team_id;
} }
@@ -961,9 +942,8 @@ void PlayerState::log_discard(uint16_t card_ref, uint16_t reason) {
} }
uint16_t PlayerState::pop_from_discard_log(uint16_t) { uint16_t PlayerState::pop_from_discard_log(uint16_t) {
// NTE appears to have a bug here (or some obviated code): it searches for an // NTE appears to have a bug here (or some obviated code): it searches for an entry with the given reason, then
// entry with the given reason, then ignores the result of that search and // ignores the result of that search and always returns the first entry instead. That code is:
// always returns the first entry instead.
// size_t z; // size_t z;
// for (size_t z = 0; z < this->discard_log_card_refs.size(); 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)) { // if ((this->discard_log_card_refs[z] != 0xFFFF) && (this->discard_log_reasons[z] == reason)) {
@@ -1030,9 +1010,7 @@ void PlayerState::move_null_hand_refs_to_end() {
void PlayerState::on_cards_destroyed() { void PlayerState::on_cards_destroyed() {
auto s = this->server(); auto s = this->server();
// {card_ref: should_return_to_hand} unordered_multimap<uint16_t, bool> card_refs_map; // {card_ref: should_return_to_hand}
unordered_multimap<uint16_t, bool> card_refs_map;
for (size_t z = 0; z < 8; z++) { for (size_t z = 0; z < 8; z++) {
auto card = this->set_cards[z]; auto card = this->set_cards[z];
if (!card || !(card->card_flags & 2)) { if (!card || !(card->card_flags & 2)) {
@@ -1106,8 +1084,7 @@ void PlayerState::replace_all_set_assists_with_random_assists() {
const auto& assist_card_ids = all_assist_card_ids(is_nte); const auto& assist_card_ids = all_assist_card_ids(is_nte);
for (size_t client_id = 0; client_id < 4; client_id++) { for (size_t client_id = 0; client_id < 4; client_id++) {
auto other_ps = s->get_player_state(client_id); auto other_ps = s->get_player_state(client_id);
if (other_ps && if (other_ps && ((other_ps->card_refs[6] != 0xFFFF) || (!is_nte && (other_ps->set_assist_card_id != 0xFFFF)))) {
((other_ps->card_refs[6] != 0xFFFF) || (!is_nte && (other_ps->set_assist_card_id != 0xFFFF)))) {
uint16_t card_id = 0x0130; uint16_t card_id = 0x0130;
while (card_id == 0x0130) { // God Whim while (card_id == 0x0130) { // God Whim
size_t index = s->get_random(assist_card_ids.size()); size_t index = s->get_random(assist_card_ids.size());
@@ -1355,8 +1332,7 @@ bool PlayerState::set_card_from_hand(
return 0; return 0;
} }
this->card_refs[card_index + 1] = card_ref; this->card_refs[card_index + 1] = card_ref;
// Note: NTE doesn't call the destructor on the existing card, if there is // Note: NTE doesn't call the destructor on the existing card, if there is one. Is that a bug?
// 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); 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]; auto new_card = this->set_cards[card_index - 7];
new_card->init(); new_card->init();
@@ -1365,8 +1341,7 @@ bool PlayerState::set_card_from_hand(
new_card->loc.x = loc->x; new_card->loc.x = loc->x;
new_card->loc.y = loc->y; new_card->loc.y = loc->y;
} }
// Note: NTE doesn't track this, but NTE can't use it anyway, so we don't // Note: NTE doesn't track this, but NTE can't use it anyway, so we don't check for NTE here.
// check for NTE here.
this->stats.num_item_or_creature_cards_set++; this->stats.num_item_or_creature_cards_set++;
} else if (ce->def.type == CardType::ASSIST) { } else if (ce->def.type == CardType::ASSIST) {
@@ -1436,7 +1411,6 @@ bool PlayerState::set_card_from_hand(
void PlayerState::set_initial_location() { void PlayerState::set_initial_location() {
auto s = this->server(); auto s = this->server();
auto mr = s->map_and_rules; auto mr = s->map_and_rules;
uint8_t num_team_players; uint8_t num_team_players;
@@ -1485,8 +1459,7 @@ void PlayerState::set_initial_location() {
} }
} }
void PlayerState::set_map_occupied_bit_for_card_on_warp_tile( void PlayerState::set_map_occupied_bit_for_card_on_warp_tile(shared_ptr<const Card> card) {
shared_ptr<const Card> card) {
if (!card) { if (!card) {
return; return;
} }
@@ -1498,8 +1471,7 @@ void PlayerState::set_map_occupied_bit_for_card_on_warp_tile(
if ((s->warp_positions[warp_type][warp_end][0] == card->loc.x) && if ((s->warp_positions[warp_type][warp_end][0] == card->loc.x) &&
(s->warp_positions[warp_type][warp_end][1] == card->loc.y)) { (s->warp_positions[warp_type][warp_end][1] == card->loc.y)) {
s->map_and_rules->set_occupied_bit_for_tile( s->map_and_rules->set_occupied_bit_for_tile(
s->warp_positions[warp_type][warp_end ^ 1][0], s->warp_positions[warp_type][warp_end ^ 1][0], s->warp_positions[warp_type][warp_end ^ 1][1]);
s->warp_positions[warp_type][warp_end ^ 1][1]);
} }
} }
} }
@@ -1509,8 +1481,7 @@ void PlayerState::set_map_occupied_bits_for_sc_and_creatures() {
auto s = this->server(); auto s = this->server();
if (this->sc_card && !(this->sc_card->card_flags & 2)) { if (this->sc_card && !(this->sc_card->card_flags & 2)) {
s->map_and_rules->set_occupied_bit_for_tile( s->map_and_rules->set_occupied_bit_for_tile(this->sc_card->loc.x, this->sc_card->loc.y);
this->sc_card->loc.x, this->sc_card->loc.y);
this->set_map_occupied_bit_for_card_on_warp_tile(this->sc_card); this->set_map_occupied_bit_for_card_on_warp_tile(this->sc_card);
} }
@@ -1518,8 +1489,7 @@ void PlayerState::set_map_occupied_bits_for_sc_and_creatures() {
for (size_t set_index = 0; set_index < 8; set_index++) { for (size_t set_index = 0; set_index < 8; set_index++) {
auto card = this->set_cards[set_index]; auto card = this->set_cards[set_index];
if (card) { if (card) {
s->map_and_rules->set_occupied_bit_for_tile( s->map_and_rules->set_occupied_bit_for_tile(card->loc.x, card->loc.y);
card->loc.x, card->loc.y);
this->set_map_occupied_bit_for_card_on_warp_tile(card); this->set_map_occupied_bit_for_card_on_warp_tile(card);
} }
} }
@@ -1530,8 +1500,7 @@ void PlayerState::subtract_def_points(uint8_t cost) {
this->def_points -= cost; this->def_points -= cost;
} }
bool PlayerState::subtract_or_check_atk_or_def_points_for_action( bool PlayerState::subtract_or_check_atk_or_def_points_for_action(const ActionState& pa, bool deduct_points) {
const ActionState& pa, bool deduct_points) {
auto s = this->server(); auto s = this->server();
int16_t cost = this->compute_attack_or_defense_atk_costs(pa); int16_t cost = this->compute_attack_or_defense_atk_costs(pa);
@@ -1580,9 +1549,7 @@ G_UpdateHand_Ep3_6xB4x02 PlayerState::prepare_6xB4x02() const {
cmd.state.assist_card_ref = this->card_refs[6]; cmd.state.assist_card_ref = this->card_refs[6];
cmd.state.sc_card_ref = this->sc_card_ref; cmd.state.sc_card_ref = this->sc_card_ref;
cmd.state.assist_card_ref2 = this->card_refs[6]; cmd.state.assist_card_ref2 = this->card_refs[6];
cmd.state.assist_card_set_number = (this->card_refs[6] == 0xFFFF) cmd.state.assist_card_set_number = (this->card_refs[6] == 0xFFFF) ? 0 : this->assist_card_set_number;
? 0
: this->assist_card_set_number;
cmd.state.assist_card_id = this->set_assist_card_id; cmd.state.assist_card_id = this->set_assist_card_id;
cmd.state.assist_remaining_turns = this->assist_remaining_turns; cmd.state.assist_remaining_turns = this->assist_remaining_turns;
cmd.state.assist_delay_turns = this->assist_delay_turns; cmd.state.assist_delay_turns = this->assist_delay_turns;
@@ -1591,8 +1558,7 @@ G_UpdateHand_Ep3_6xB4x02 PlayerState::prepare_6xB4x02() const {
return cmd; return cmd;
} }
void PlayerState::update_hand_and_equip_state_and_send_6xB4x02_if_needed( void PlayerState::update_hand_and_equip_state_and_send_6xB4x02_if_needed(bool always_send) {
bool always_send) {
auto cmd = this->prepare_6xB4x02(); auto cmd = this->prepare_6xB4x02();
if (always_send || memcmp(&this->hand_and_equip, &cmd.state, sizeof(this->hand_and_equip))) { if (always_send || memcmp(&this->hand_and_equip, &cmd.state, sizeof(this->hand_and_equip))) {
*this->hand_and_equip = cmd.state; *this->hand_and_equip = cmd.state;
@@ -1618,8 +1584,7 @@ void PlayerState::set_random_assist_card_from_hand_for_free() {
if (!candidate_card_refs.empty()) { if (!candidate_card_refs.empty()) {
this->discard_set_assist_card(); this->discard_set_assist_card();
size_t index = s->get_random(candidate_card_refs.size()); size_t index = s->get_random(candidate_card_refs.size());
this->set_card_from_hand( this->set_card_from_hand(candidate_card_refs[index], 15, nullptr, this->client_id, 1);
candidate_card_refs[index], 15, nullptr, this->client_id, 1);
} }
} }
@@ -1641,11 +1606,9 @@ G_UpdateShortStatuses_Ep3_6xB4x04 PlayerState::prepare_6xB4x04() const {
} }
for (size_t hand_index = 0; hand_index < 6; hand_index++) { for (size_t hand_index = 0; hand_index < 6; hand_index++) {
this->get_short_status_for_card_index_in_hand( this->get_short_status_for_card_index_in_hand(hand_index + 1, &cmd.card_statuses[hand_index + 1]);
hand_index + 1, &cmd.card_statuses[hand_index + 1]); // This write is required to mimic memset()'s effect from the original code. This field is probably ignored for
// This write is required to mimic memset()'s effect from the original code. // hand refs anyway, but we might as well be as consistent as possible.
// This field is probably ignored for hand refs anyway, but we might as well
// be as consistent as possible.
cmd.card_statuses[hand_index + 1].unused1 = 0; cmd.card_statuses[hand_index + 1].unused1 = 0;
} }
@@ -1674,9 +1637,7 @@ void PlayerState::send_6xB4x04_if_needed(bool always_send) {
} }
vector<uint16_t> PlayerState::get_card_refs_within_range_from_all_players( vector<uint16_t> PlayerState::get_card_refs_within_range_from_all_players(
const parray<uint8_t, 9 * 9>& range, const parray<uint8_t, 9 * 9>& range, const Location& loc, CardType type) const {
const Location& loc,
CardType type) const {
auto s = this->server(); auto s = this->server();
vector<uint16_t> ret; vector<uint16_t> ret;
@@ -1726,8 +1687,7 @@ void PlayerState::move_phase_before() {
void PlayerState::handle_before_turn_assist_effects() { void PlayerState::handle_before_turn_assist_effects() {
auto s = this->server(); auto s = this->server();
if ((this->assist_delay_turns > 0) && if ((this->assist_delay_turns > 0) && (--this->assist_delay_turns == 0)) {
(--this->assist_delay_turns == 0)) {
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
size_t num_assists = s->assist_server->compute_num_assist_effects_for_client(this->client_id); size_t num_assists = s->assist_server->compute_num_assist_effects_for_client(this->client_id);
for (size_t z = 0; z < num_assists; z++) { for (size_t z = 0; z < num_assists; z++) {
@@ -1736,8 +1696,7 @@ void PlayerState::handle_before_turn_assist_effects() {
s->execute_bomb_assist_effect(); s->execute_bomb_assist_effect();
break; break;
case AssistEffect::ATK_DICE_2: case AssistEffect::ATK_DICE_2:
// Note: This behavior doesn't match the card description. Is it // Note: This behavior doesn't match the card description. Is it supposed to add 2 or multiply by 2?
// supposed to add 2 or multiply by 2?
this->atk_points = min<int16_t>(this->atk_points + 2, 9); this->atk_points = min<int16_t>(this->atk_points + 2, 9);
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
break; break;
@@ -1885,9 +1844,7 @@ void PlayerState::dice_phase_before() {
this->compute_total_set_cards_cost(); this->compute_total_set_cards_cost();
this->unknown_a14 = 0; this->unknown_a14 = 0;
if ((this->assist_remaining_turns > 0) && if ((this->assist_remaining_turns > 0) && (this->assist_remaining_turns < 90) && (this->assist_delay_turns == 0)) {
(this->assist_remaining_turns < 90) &&
(this->assist_delay_turns == 0)) {
this->assist_remaining_turns--; this->assist_remaining_turns--;
if (this->assist_remaining_turns < 1) { if (this->assist_remaining_turns < 1) {
this->discard_set_assist_card(); this->discard_set_assist_card();
@@ -1984,12 +1941,10 @@ void PlayerState::roll_main_dice_or_apply_after_effects() {
auto s = this->server(); auto s = this->server();
const auto& rules = s->map_and_rules->rules; const auto& rules = s->map_and_rules->rules;
// In NTE, the dice behave differently - there is no minimum, and instead the // In NTE, the dice behave differently - there is no minimum, and instead the player can specify a fixed value for
// player can specify a fixed value for each die or a random value (1-6). The // each die or a random value (1-6). The implementation of this function is therefore quite different on NTE, but
// 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
// since we already support custom ranges for ATK and DEF dice, we just use // ranges at battle start time to yield the NTE behavior. (See RulesTrial in DataIndexes.cc for how this is done.)
// 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.)
bool is_1p_2v1 = (s->team_client_count.at(this->get_team_id()) < s->team_client_count[this->get_team_id() ^ 1]); bool is_1p_2v1 = (s->team_client_count.at(this->get_team_id()) < s->team_client_count[this->get_team_id() ^ 1]);
@@ -2079,10 +2034,8 @@ void PlayerState::compute_team_dice_bonus_after_draw_phase() {
} }
uint8_t current_team_turn = s->get_current_team_turn(); uint8_t current_team_turn = s->get_current_team_turn();
uint8_t dice_boost = s->get_team_exp(current_team_turn) / uint8_t dice_boost = s->get_team_exp(current_team_turn) / (s->team_client_count[current_team_turn] * 12);
(s->team_client_count[current_team_turn] * 12); s->card_special->adjust_dice_boost_if_team_has_condition_52(current_team_turn, &dice_boost, 0);
s->card_special->adjust_dice_boost_if_team_has_condition_52(
current_team_turn, &dice_boost, 0);
s->team_dice_bonus[current_team_turn] = clamp<int16_t>(dice_boost, 0, 8); s->team_dice_bonus[current_team_turn] = clamp<int16_t>(dice_boost, 0, 8);
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
} }
+15 -23
View File
@@ -15,9 +15,8 @@ namespace Episode3 {
class Server; class Server;
enum AssistFlag : uint32_t { enum AssistFlag : uint32_t {
// Note: This enum is a uint32_t even though only 16 bits are used because // Note: This enum is a uint32_t even though only 16 bits are used because the corresponding field in the protocol is
// the corresponding field in the protocol is a 32-bit field. There may also // a 32-bit field. There may also be bits used only by the client which are not documented here.
// be bits used only by the client which are not documented here.
// clang-format off // clang-format off
NONE = 0x0000, NONE = 0x0000,
@@ -70,14 +69,9 @@ public:
void draw_hand(ssize_t override_count = 0); void draw_hand(ssize_t override_count = 0);
void draw_initial_hand(); void draw_initial_hand();
int32_t error_code_for_client_setting_card( int32_t error_code_for_client_setting_card(
uint16_t card_ref, uint16_t card_ref, uint8_t card_index, const Location* loc, uint8_t assist_target_client_id) const;
uint8_t card_index,
const Location* loc,
uint8_t assist_target_client_id) const;
std::vector<uint16_t> get_all_cards_within_range( std::vector<uint16_t> get_all_cards_within_range(
const parray<uint8_t, 9 * 9>& range, const parray<uint8_t, 9 * 9>& range, const Location& loc, uint8_t target_team_id) const;
const Location& loc,
uint8_t target_team_id) const;
uint8_t get_atk_points() const; uint8_t get_atk_points() const;
void get_short_status_for_card_index_in_hand(size_t hand_index, CardShortStatus* stat) const; void get_short_status_for_card_index_in_hand(size_t hand_index, CardShortStatus* stat) const;
std::shared_ptr<DeckState> get_deck(); std::shared_ptr<DeckState> get_deck();
@@ -128,9 +122,7 @@ public:
G_UpdateShortStatuses_Ep3_6xB4x04 prepare_6xB4x04() const; G_UpdateShortStatuses_Ep3_6xB4x04 prepare_6xB4x04() const;
void send_6xB4x04_if_needed(bool always_send = false); void send_6xB4x04_if_needed(bool always_send = false);
std::vector<uint16_t> get_card_refs_within_range_from_all_players( std::vector<uint16_t> get_card_refs_within_range_from_all_players(
const parray<uint8_t, 9 * 9>& range, const parray<uint8_t, 9 * 9>& range, const Location& loc, CardType type) const;
const Location& loc,
CardType type) const;
void draw_phase_before(); void draw_phase_before();
void action_phase_before(); void action_phase_before();
void move_phase_before(); void move_phase_before();
@@ -169,10 +161,10 @@ public:
uint16_t sc_card_ref; uint16_t sc_card_ref;
// This array is unfortunately heterogeneous; specifically: // This array is unfortunately heterogeneous; specifically:
// [0] through [5] are hand refs // [0] through [5] are hand refs
// [6] is the current assist card ref (which may belong to another player) // [6] is the current assist card ref (which may belong to another player)
// [7] is the previous assist card ref // [7] is the previous assist card ref
// [8] through [15] are set refs // [8] through [15] are set refs
parray<uint16_t, 0x10> card_refs; parray<uint16_t, 0x10> card_refs;
std::shared_ptr<DeckState> deck_state; std::shared_ptr<DeckState> deck_state;
@@ -190,12 +182,12 @@ public:
Direction start_facing_direction; Direction start_facing_direction;
std::shared_ptr<HandAndEquipState> hand_and_equip; std::shared_ptr<HandAndEquipState> hand_and_equip;
// Like card_refs above, these arrays are also heterogeneous, but the indices // Like card_refs above, these arrays are also heterogeneous, but the indices are not the same as for card_refs! The
// are not the same as for card_refs! THe indices here are: // indices here are:
// [0] is the SC card status // [0] is the SC card status
// [1] through [6] are hand cards // [1] through [6] are hand cards
// [7] through [14] are set cards // [7] through [14] are set cards
// [15] is the assist card // [15] is the assist card
std::shared_ptr<parray<CardShortStatus, 0x10>> card_short_statuses; std::shared_ptr<parray<CardShortStatus, 0x10>> card_short_statuses;
parray<CardShortStatus, 0x10> prev_card_short_statuses; parray<CardShortStatus, 0x10> prev_card_short_statuses;
+59 -112
View File
@@ -62,8 +62,6 @@ void Condition::clear_FF() {
} }
std::string Condition::str(shared_ptr<const Server> s) const { std::string Condition::str(shared_ptr<const Server> s) const {
auto card_ref_str = s->debug_str_for_card_ref(this->card_ref);
auto giver_ref_str = s->debug_str_for_card_ref(this->condition_giver_card_ref);
return std::format( return std::format(
"Condition[type={}, turns={}, a_arg={}, dice={}, flags={:02X}, " "Condition[type={}, turns={}, a_arg={}, dice={}, flags={:02X}, "
"def_eff_index={}, ref={}, value={}, giver_ref={} " "def_eff_index={}, ref={}, value={}, giver_ref={} "
@@ -74,9 +72,9 @@ std::string Condition::str(shared_ptr<const Server> s) const {
this->dice_roll_value, this->dice_roll_value,
this->flags, this->flags,
this->card_definition_effect_index, this->card_definition_effect_index,
card_ref_str, s->debug_str_for_card_ref(this->card_ref),
this->value, this->value,
giver_ref_str, s->debug_str_for_card_ref(this->condition_giver_card_ref),
this->random_percent, this->random_percent,
this->value8, this->value8,
this->order, this->order,
@@ -101,14 +99,10 @@ void EffectResult::clear() {
} }
std::string EffectResult::str(shared_ptr<const Server> s) const { std::string EffectResult::str(shared_ptr<const Server> s) const {
string attacker_ref_str = s->debug_str_for_card_ref(this->attacker_card_ref);
string target_ref_str = s->debug_str_for_card_ref(this->target_card_ref);
return std::format( return std::format(
"EffectResult[att_ref={}, target_ref={}, value={}, " "EffectResult[att_ref={}, target_ref={}, value={}, cur_hp={}, ap={}, tp={}, flags={:02X}, op={}, cond_index={}, dice={}]",
"cur_hp={}, ap={}, tp={}, flags={:02X}, op={}, " s->debug_str_for_card_ref(this->attacker_card_ref),
"cond_index={}, dice={}]", s->debug_str_for_card_ref(this->target_card_ref),
attacker_ref_str,
target_ref_str,
this->value, this->value,
this->current_hp, this->current_hp,
this->ap, this->ap,
@@ -137,15 +131,12 @@ bool CardShortStatus::operator!=(const CardShortStatus& other) const {
} }
std::string CardShortStatus::str(shared_ptr<const Server> s) const { std::string CardShortStatus::str(shared_ptr<const Server> s) const {
string loc_s = this->loc.str();
string ref_str = s->debug_str_for_card_ref(this->card_ref);
return std::format( return std::format(
"CardShortStatus[ref={}, cur_hp={}, flags={:08X}, loc={}, " "CardShortStatus[ref={}, cur_hp={}, flags={:08X}, loc={}, u1={:04X}, max_hp={}, u2={}]",
"u1={:04X}, max_hp={}, u2={}]", s->debug_str_for_card_ref(this->card_ref),
ref_str,
this->current_hp, this->current_hp,
this->card_flags, this->card_flags,
loc_s, this->loc.str(),
this->unused1, this->unused1,
this->max_hp, this->max_hp,
this->unused2); this->unused2);
@@ -188,23 +179,16 @@ void ActionState::clear() {
} }
std::string ActionState::str(shared_ptr<const Server> s) const { std::string ActionState::str(shared_ptr<const Server> s) const {
string attacker_ref_s = s->debug_str_for_card_ref(this->attacker_card_ref);
string defense_ref_s = s->debug_str_for_card_ref(this->defense_card_ref);
string original_attacker_ref_s = s->debug_str_for_card_ref(this->original_attacker_card_ref);
string target_refs_s = s->debug_str_for_card_refs(this->target_card_refs);
string action_refs_s = s->debug_str_for_card_refs(this->action_card_refs);
return std::format( return std::format(
"ActionState[client={:X}, u={}, facing={}, attacker_ref={}, " "ActionState[client={:X}, u={}, facing={}, attacker_ref={}, def_ref={}, target_refs={}, action_refs={}, orig_attacker_ref={}]",
"def_ref={}, target_refs={}, action_refs={}, "
"orig_attacker_ref={}]",
this->client_id, this->client_id,
this->unused, this->unused,
phosg::name_for_enum(this->facing_direction), phosg::name_for_enum(this->facing_direction),
attacker_ref_s, s->debug_str_for_card_ref(this->attacker_card_ref),
defense_ref_s, s->debug_str_for_card_ref(this->defense_card_ref),
target_refs_s, s->debug_str_for_card_refs(this->target_card_refs),
action_refs_s, s->debug_str_for_card_refs(this->action_card_refs),
original_attacker_ref_s); s->debug_str_for_card_ref(this->original_attacker_card_ref));
} }
ActionChain::ActionChain() { ActionChain::ActionChain() {
@@ -239,24 +223,17 @@ bool ActionChain::operator!=(const ActionChain& other) const {
} }
std::string ActionChain::str(shared_ptr<const Server> s) const { std::string ActionChain::str(shared_ptr<const Server> s) const {
string acting_card_ref_s = s->debug_str_for_card_ref(this->acting_card_ref);
string unknown_card_ref_a3_s = s->debug_str_for_card_ref(this->unknown_card_ref_a3);
string attack_action_card_refs_s = s->debug_str_for_card_refs(this->attack_action_card_refs);
string target_card_refs_s = s->debug_str_for_card_refs(this->target_card_refs);
return std::format( return std::format(
"ActionChain[eff_ap={}, eff_tp={}, ap_bonus={}, damage={}, " "ActionChain[eff_ap={}, eff_tp={}, ap_bonus={}, damage={}, acting_ref={}, unknown_ref_a3={}, attack_action_refs={}, "
"acting_ref={}, unknown_ref_a3={}, attack_action_refs={}, " "attack_action_ref_count={}, medium={}, target_ref_count={}, subphase={}, strikes={}, damage_mult={}, attack_num={}, "
"attack_action_ref_count={}, medium={}, target_ref_count={}, " "tp_bonus={}, phys_bonus_nte={}, tech_bonus_nte={}, card_ap={}, card_tp={}, flags={:08X}, target_refs={}]",
"subphase={}, strikes={}, damage_mult={}, attack_num={}, "
"tp_bonus={}, phys_bonus_nte={}, tech_bonus_nte={}, card_ap={}, "
"card_tp={}, flags={:08X}, target_refs={}]",
this->effective_ap, this->effective_ap,
this->effective_tp, this->effective_tp,
this->ap_effect_bonus, this->ap_effect_bonus,
this->damage, this->damage,
acting_card_ref_s, s->debug_str_for_card_ref(this->acting_card_ref),
unknown_card_ref_a3_s, s->debug_str_for_card_ref(this->unknown_card_ref_a3),
attack_action_card_refs_s, s->debug_str_for_card_refs(this->attack_action_card_refs),
this->attack_action_card_ref_count, this->attack_action_card_ref_count,
phosg::name_for_enum(this->attack_medium), phosg::name_for_enum(this->attack_medium),
this->target_card_ref_count, this->target_card_ref_count,
@@ -270,7 +247,7 @@ std::string ActionChain::str(shared_ptr<const Server> s) const {
this->card_ap, this->card_ap,
this->card_tp, this->card_tp,
this->flags, this->flags,
target_card_refs_s); s->debug_str_for_card_refs(this->target_card_refs));
} }
void ActionChain::clear() { void ActionChain::clear() {
@@ -406,8 +383,7 @@ void ActionChainWithConds::set_flags(uint32_t flags) {
this->chain.flags |= flags; this->chain.flags |= flags;
} }
void ActionChainWithConds::add_attack_action_card_ref( void ActionChainWithConds::add_attack_action_card_ref(uint16_t card_ref, shared_ptr<Server> server) {
uint16_t card_ref, shared_ptr<Server> server) {
if (card_ref != 0xFFFF) { if (card_ref != 0xFFFF) {
this->chain.attack_action_card_refs[this->chain.attack_action_card_ref_count++] = card_ref; this->chain.attack_action_card_refs[this->chain.attack_action_card_ref_count++] = card_ref;
} }
@@ -416,8 +392,7 @@ void ActionChainWithConds::add_attack_action_card_ref(
} }
void ActionChainWithConds::add_target_card_ref(uint16_t card_ref) { void ActionChainWithConds::add_target_card_ref(uint16_t card_ref) {
if (card_ref != 0xFFFF && if (card_ref != 0xFFFF && this->chain.target_card_ref_count < this->chain.target_card_refs.size()) {
this->chain.target_card_ref_count < this->chain.target_card_refs.size()) {
this->chain.target_card_refs[this->chain.target_card_ref_count++] = card_ref; this->chain.target_card_refs[this->chain.target_card_ref_count++] = card_ref;
} }
} }
@@ -440,11 +415,7 @@ void ActionChainWithConds::compute_attack_medium(shared_ptr<Server> server) {
} }
bool ActionChainWithConds::get_condition_value( bool ActionChainWithConds::get_condition_value(
ConditionType cond_type, ConditionType cond_type, uint16_t card_ref, uint8_t def_effect_index, uint16_t value, uint16_t* out_value) const {
uint16_t card_ref,
uint8_t def_effect_index,
uint16_t value,
uint16_t* out_value) const {
bool any_found = false; bool any_found = false;
uint8_t max_order = 10; uint8_t max_order = 10;
for (size_t z = 0; z < 9; z++) { for (size_t z = 0; z < 9; z++) {
@@ -466,8 +437,7 @@ bool ActionChainWithConds::get_condition_value(
return any_found; return any_found;
} }
void ActionChainWithConds::set_action_subphase_from_card( void ActionChainWithConds::set_action_subphase_from_card(shared_ptr<const Card> card) {
shared_ptr<const Card> card) {
this->chain.action_subphase = card->server()->get_current_action_subphase(); this->chain.action_subphase = card->server()->get_current_action_subphase();
} }
@@ -576,16 +546,10 @@ bool ActionMetadata::operator!=(const ActionMetadata& other) const {
} }
std::string ActionMetadata::str(shared_ptr<const Server> s) const { std::string ActionMetadata::str(shared_ptr<const Server> s) const {
string card_ref_s = s->debug_str_for_card_ref(this->card_ref);
string target_card_refs_s = s->debug_str_for_card_refs(this->target_card_refs);
string defense_card_refs_s = s->debug_str_for_card_refs(this->defense_card_refs);
string original_attacker_card_refs_s = s->debug_str_for_card_refs(this->original_attacker_card_refs);
return std::format( return std::format(
"ActionMetadata[ref={}, target_ref_count={}, def_ref_count={}, " "ActionMetadata[ref={}, target_ref_count={}, def_ref_count={}, subphase={}, def_power={}, def_bonus={}, "
"subphase={}, def_power={}, def_bonus={}, " "att_bonus={}, flags={:08X}, target_refs={}, defense_refs={}, original_attacker_refs={}]",
"att_bonus={}, flags={:08X}, target_refs={}, " s->debug_str_for_card_ref(this->card_ref),
"defense_refs={}, original_attacker_refs={}]",
card_ref_s,
this->target_card_ref_count, this->target_card_ref_count,
this->defense_card_ref_count, this->defense_card_ref_count,
phosg::name_for_enum(this->action_subphase), phosg::name_for_enum(this->action_subphase),
@@ -593,9 +557,9 @@ std::string ActionMetadata::str(shared_ptr<const Server> s) const {
this->defense_bonus, this->defense_bonus,
this->attack_bonus, this->attack_bonus,
this->flags, this->flags,
target_card_refs_s, s->debug_str_for_card_refs(this->target_card_refs),
defense_card_refs_s, s->debug_str_for_card_refs(this->defense_card_refs),
original_attacker_card_refs_s); s->debug_str_for_card_refs(this->original_attacker_card_refs));
} }
void ActionMetadata::clear() { void ActionMetadata::clear() {
@@ -605,8 +569,7 @@ void ActionMetadata::clear() {
this->action_subphase = ActionSubphase::INVALID_FF; this->action_subphase = ActionSubphase::INVALID_FF;
this->defense_power = 0; this->defense_power = 0;
this->defense_bonus = 0; this->defense_bonus = 0;
// TODO: Ep3 NTE doesn't set attack_bonus to zero here. Is the field just // TODO: Ep3 NTE doesn't set attack_bonus to zero here. Is the field just unused in NTE?
// unused in NTE?
this->attack_bonus = 0; this->attack_bonus = 0;
this->flags = 0; this->flags = 0;
this->target_card_refs.clear(0xFFFF); this->target_card_refs.clear(0xFFFF);
@@ -652,16 +615,13 @@ void ActionMetadata::clear_target_card_refs() {
} }
void ActionMetadata::add_target_card_ref(uint16_t card_ref) { void ActionMetadata::add_target_card_ref(uint16_t card_ref) {
if (card_ref != 0xFFFF && if ((card_ref != 0xFFFF) && (this->target_card_ref_count < this->target_card_refs.size())) {
this->target_card_ref_count < this->target_card_refs.size()) {
this->target_card_refs[this->target_card_ref_count++] = card_ref; this->target_card_refs[this->target_card_ref_count++] = card_ref;
} }
} }
void ActionMetadata::add_defense_card_ref( void ActionMetadata::add_defense_card_ref(
uint16_t defense_card_ref, uint16_t defense_card_ref, shared_ptr<Card> card, uint16_t original_attacker_card_ref) {
shared_ptr<Card> card,
uint16_t original_attacker_card_ref) {
if ((defense_card_ref != 0xFFFF) && (this->defense_card_ref_count < 8)) { if ((defense_card_ref != 0xFFFF) && (this->defense_card_ref_count < 8)) {
this->defense_card_refs[this->defense_card_ref_count] = defense_card_ref; this->defense_card_refs[this->defense_card_ref_count] = defense_card_ref;
this->original_attacker_card_refs[this->defense_card_ref_count] = original_attacker_card_ref; this->original_attacker_card_refs[this->defense_card_ref_count] = original_attacker_card_ref;
@@ -675,21 +635,10 @@ HandAndEquipState::HandAndEquipState() {
} }
std::string HandAndEquipState::str(shared_ptr<const Server> s) const { std::string HandAndEquipState::str(shared_ptr<const Server> s) const {
string assist_card_ref_s = s->debug_str_for_card_ref(this->assist_card_ref);
string assist_card_ref2_s = s->debug_str_for_card_ref(this->assist_card_ref2);
string assist_card_id_s = s->debug_str_for_card_id(this->assist_card_id);
string sc_card_ref_s = s->debug_str_for_card_ref(this->sc_card_ref);
string hand_card_refs_s = s->debug_str_for_card_refs(this->hand_card_refs);
string set_card_refs_s = s->debug_str_for_card_refs(this->set_card_refs);
string hand_card_refs2_s = s->debug_str_for_card_refs(this->hand_card_refs2);
string set_card_refs2_s = s->debug_str_for_card_refs(this->set_card_refs2);
return std::format( return std::format(
"HandAndEquipState[dice=[{}, {}], atk={}, def={}, atk2={}, " "HandAndEquipState[dice=[{}, {}], atk={}, def={}, atk2={}, a1={}, total_set_cost={}, is_cpu={}, assist_flags={:08X}, "
"a1={}, total_set_cost={}, is_cpu={}, assist_flags={:08X}, " "hand_refs={}, assist_ref={}, set_refs={}, sc_ref={}, hand_refs2={}, set_refs2={}, assist_ref2={}, assist_set_num={}, assist_card_id={}, "
"hand_refs={}, assist_ref={}, set_refs={}, sc_ref={}, hand_refs2={}, " "assist_turns={}, assist_delay={}, atk_bonus={}, def_bonus={}, u2=[{}, {}]]",
"set_refs2={}, assist_ref2={}, assist_set_num={}, assist_card_id={}, "
"assist_turns={}, assist_delay={}, atk_bonus={}, def_bonus={}, "
"u2=[{}, {}]]",
this->dice_results[0], this->dice_results[0],
this->dice_results[1], this->dice_results[1],
this->atk_points, this->atk_points,
@@ -699,15 +648,15 @@ std::string HandAndEquipState::str(shared_ptr<const Server> s) const {
this->total_set_cards_cost, this->total_set_cards_cost,
this->is_cpu_player, this->is_cpu_player,
this->assist_flags, this->assist_flags,
hand_card_refs_s, s->debug_str_for_card_refs(this->hand_card_refs),
assist_card_ref_s, s->debug_str_for_card_ref(this->assist_card_ref),
set_card_refs_s, s->debug_str_for_card_refs(this->set_card_refs),
sc_card_ref_s, s->debug_str_for_card_ref(this->sc_card_ref),
hand_card_refs2_s, s->debug_str_for_card_refs(this->hand_card_refs2),
set_card_refs2_s, s->debug_str_for_card_refs(this->set_card_refs2),
assist_card_ref2_s, s->debug_str_for_card_ref(this->assist_card_ref2),
this->assist_card_set_number, this->assist_card_set_number,
assist_card_id_s, s->debug_str_for_card_id(this->assist_card_id),
this->assist_remaining_turns, this->assist_remaining_turns,
this->assist_delay_turns, this->assist_delay_turns,
this->atk_bonuses, this->atk_bonuses,
@@ -795,17 +744,15 @@ void PlayerBattleStats::clear() {
float PlayerBattleStats::score(size_t num_rounds) const { float PlayerBattleStats::score(size_t num_rounds) const {
// Note: This formula doesn't match the formula on PSO-World, which is: // Note: This formula doesn't match the formula on PSO-World, which is:
// 35 // 35 + (Attack Damage - Damage Taken)
// + (Attack Damage - Damage Taken) // + (Max Card Combo x 3)
// + (Max Card Combo x 3) // - (Story Character Damage x 1.8)
// - (Story Character Damage x 1.8) // - (Turns x 2.7)
// - (Turns x 2.7) // + (Action Card Negated Damage x 0.8)
// + (Action Card Negated Damage x 0.8) // I don't know where that formula came from, but this one came from the USA Ep3 PsoV3.dol, so it's presumably
// I don't know where that formula came from, but this one came from the USA // correct. Is the PSO-World formula simply incorrect, or is it from e.g. the Japanese version, which may have a
// Ep3 PsoV3.dol, so it's presumably correct. Is the PSO-World formula simply
// incorrect, or is it from e.g. the Japanese version, which may have a
// different rank calculation function? // different rank calculation function?
return 38.0f + 0.8f * this->action_card_negated_damage - 2.3f * num_rounds - 1.8f * this->sc_damage_taken + 3.0f * this->max_attack_combo_size + (this->damage_given - this->damage_taken); return 38.0f + (0.8f * this->action_card_negated_damage) - (2.3f * num_rounds) - (1.8f * this->sc_damage_taken) + (3.0f * this->max_attack_combo_size) + (this->damage_given - this->damage_taken);
} }
uint8_t PlayerBattleStats::rank(size_t num_rounds) const { uint8_t PlayerBattleStats::rank(size_t num_rounds) const {
@@ -817,10 +764,8 @@ const char* PlayerBattleStats::rank_name(size_t num_rounds) const {
} }
constexpr size_t RANK_THRESHOLD_COUNT = 9; constexpr size_t RANK_THRESHOLD_COUNT = 9;
static const float RANK_THRESHOLDS[RANK_THRESHOLD_COUNT] = { static const float RANK_THRESHOLDS[RANK_THRESHOLD_COUNT] = {15, 25, 30, 40, 50, 60, 65, 75, 85};
15.0f, 25.0f, 30.0f, 40.0f, 50.0f, 60.0f, 65.0f, 75.0f, 85.0f}; static const char* RANK_NAMES[RANK_THRESHOLD_COUNT + 1] = {"E", "D", "D+", "C", "C+", "B", "B+", "A", "A+", "S"};
static const char* RANK_NAMES[RANK_THRESHOLD_COUNT + 1] = {
"E", "D", "D+", "C", "C+", "B", "B+", "A", "A+", "S"};
uint8_t PlayerBattleStats::rank_for_score(float score) { uint8_t PlayerBattleStats::rank_for_score(float score) {
size_t rank = 0; size_t rank = 0;
@@ -874,13 +819,15 @@ static bool is_card_within_range(
if ((ss.loc.x < anchor_loc.x - 4) || (ss.loc.x > anchor_loc.x + 4)) { if ((ss.loc.x < anchor_loc.x - 4) || (ss.loc.x > anchor_loc.x + 4)) {
if (log) { if (log) {
log->debug_f("is_card_within_range: (false) outside x range (ss.loc.x={}, anchor_loc.x={})", ss.loc.x, anchor_loc.x); log->debug_f(
"is_card_within_range: (false) outside x range (ss.loc.x={}, anchor_loc.x={})", ss.loc.x, anchor_loc.x);
} }
return false; return false;
} }
if ((ss.loc.y < anchor_loc.y - 4) || (ss.loc.y > anchor_loc.y + 4)) { if ((ss.loc.y < anchor_loc.y - 4) || (ss.loc.y > anchor_loc.y + 4)) {
if (log) { if (log) {
log->debug_f("is_card_within_range: (false) outside y range (ss.loc.y={}, anchor_loc.y={})", ss.loc.y, anchor_loc.y); log->debug_f(
"is_card_within_range: (false) outside y range (ss.loc.y={}, anchor_loc.y={})", ss.loc.y, anchor_loc.y);
} }
return false; return false;
} }
+3 -7
View File
@@ -102,8 +102,7 @@ struct ActionState {
} __packed_ws__(ActionState, 0x64); } __packed_ws__(ActionState, 0x64);
struct ActionChain { struct ActionChain {
// Note: Episode 3 Trial Edition has a different format for this structure. // Note: Trial Edition has a different format for this structure. See ActionChainWithCondsTrial for details.
// See ActionChainWithCondsTrial for details.
/* 00 */ int8_t effective_ap; /* 00 */ int8_t effective_ap;
/* 01 */ int8_t effective_tp; /* 01 */ int8_t effective_tp;
/* 02 */ int8_t ap_effect_bonus; /* 02 */ int8_t ap_effect_bonus;
@@ -196,8 +195,7 @@ struct ActionChainWithCondsTrial {
/* 0022 */ int8_t card_ap; /* 0022 */ int8_t card_ap;
/* 0023 */ int8_t card_tp; /* 0023 */ int8_t card_tp;
/* 0024 */ le_uint32_t flags; /* 0024 */ le_uint32_t flags;
// The only difference between this structure and ActionChainWithConds is that // The only difference between this structure and ActionChainWithConds is that these two fields are swapped.
// these two fields are in the opposite order.
/* 0028 */ parray<Condition, 9> conditions; /* 0028 */ parray<Condition, 9> conditions;
/* 00B8 */ parray<le_uint16_t, 4 * 9> target_card_refs; /* 00B8 */ parray<le_uint16_t, 4 * 9> target_card_refs;
/* 0100 */ /* 0100 */
@@ -236,9 +234,7 @@ struct ActionMetadata {
void clear_target_card_refs(); void clear_target_card_refs();
void add_target_card_ref(uint16_t card_ref); void add_target_card_ref(uint16_t card_ref);
void add_defense_card_ref( void add_defense_card_ref(
uint16_t defense_card_ref, uint16_t defense_card_ref, std::shared_ptr<Card> card, uint16_t original_attacker_card_ref);
std::shared_ptr<Card> card,
uint16_t original_attacker_card_ref);
std::string str(std::shared_ptr<const Server> s) const; std::string str(std::shared_ptr<const Server> s) const;
} __packed_ws__(ActionMetadata, 0x74); } __packed_ws__(ActionMetadata, 0x74);
+82 -135
View File
@@ -25,8 +25,7 @@ void compute_effective_range(
ret.clear(0); ret.clear(0);
parray<uint32_t, 6> range_def; parray<uint32_t, 6> range_def;
if (card_id == 0xFFFE) { if (card_id == 0xFFFE) { // Heavy Fog: one tile directly in front
// Heavy Fog: one tile directly in front
range_def[3] = 0x00000100; range_def[3] = 0x00000100;
} else { } else {
shared_ptr<const CardIndex::CardEntry> ce; shared_ptr<const CardIndex::CardEntry> ce;
@@ -40,11 +39,11 @@ void compute_effective_range(
} }
} }
if (log) { if (log) {
log->debug_f("compute_effective_range: range_def: {:05X} {:05X} {:05X} {:05X} {:05X} {:05X}", range_def[0], range_def[1], range_def[2], range_def[3], range_def[4], range_def[5]); log->debug_f("compute_effective_range: range_def: {:05X} {:05X} {:05X} {:05X} {:05X} {:05X}",
range_def[0], range_def[1], range_def[2], range_def[3], range_def[4], range_def[5]);
} }
if (range_def[0] == 0x000FFFFF) { if (range_def[0] == 0x000FFFFF) { // Entire field
// Entire field
ret.clear(2); ret.clear(2);
if (log) { if (log) {
log->debug_f("compute_effective_range: entire field (2)"); log->debug_f("compute_effective_range: entire field (2)");
@@ -65,7 +64,9 @@ void compute_effective_range(
if (log) { if (log) {
for (size_t y = 0; y < 9; y++) { for (size_t y = 0; y < 9; y++) {
log->debug_f("compute_effective_range: decoded_range: {:X} {:X} {:X} {:X} {:X} {:X} {:X} {:X} {:X}", log->debug_f("compute_effective_range: decoded_range: {:X} {:X} {:X} {:X} {:X} {:X} {:X} {:X} {:X}",
decoded_range[y * 9 + 0], decoded_range[y * 9 + 1], decoded_range[y * 9 + 2], decoded_range[y * 9 + 3], decoded_range[y * 9 + 4], decoded_range[y * 9 + 5], decoded_range[y * 9 + 6], decoded_range[y * 9 + 7], decoded_range[y * 9 + 8]); decoded_range[y * 9 + 0], decoded_range[y * 9 + 1], decoded_range[y * 9 + 2],
decoded_range[y * 9 + 3], decoded_range[y * 9 + 4], decoded_range[y * 9 + 5],
decoded_range[y * 9 + 6], decoded_range[y * 9 + 7], decoded_range[y * 9 + 8]);
} }
} }
@@ -98,7 +99,8 @@ void compute_effective_range(
} }
ret[y * 9 + x] = decoded_range[up_y * 9 + up_x]; ret[y * 9 + x] = decoded_range[up_y * 9 + up_x];
if (log) { if (log) {
log->debug_f("compute_effective_range: x={} y={} up_x={} up_y={} v={:X}", x, y, up_x, up_y, ret[y * 9 + x]); log->debug_f(
"compute_effective_range: x={} y={} up_x={} up_y={} v={:X}", x, y, up_x, up_y, ret[y * 9 + x]);
} }
} }
} }
@@ -108,7 +110,8 @@ void compute_effective_range(
if (log) { if (log) {
for (size_t y = 0; y < 9; y++) { for (size_t y = 0; y < 9; y++) {
log->debug_f("compute_effective_range: ret: {:X} {:X} {:X} {:X} {:X} {:X} {:X} {:X} {:X}", log->debug_f("compute_effective_range: ret: {:X} {:X} {:X} {:X} {:X} {:X} {:X} {:X} {:X}",
ret[y * 9 + 0], ret[y * 9 + 1], ret[y * 9 + 2], ret[y * 9 + 3], ret[y * 9 + 4], ret[y * 9 + 5], ret[y * 9 + 6], ret[y * 9 + 7], ret[y * 9 + 8]); ret[y * 9 + 0], ret[y * 9 + 1], ret[y * 9 + 2], ret[y * 9 + 3], ret[y * 9 + 4],
ret[y * 9 + 5], ret[y * 9 + 6], ret[y * 9 + 7], ret[y * 9 + 8]);
} }
} }
} }
@@ -124,9 +127,7 @@ bool card_linkage_is_valid(
bool sc_is_named_android_without_permission_effect = false; bool sc_is_named_android_without_permission_effect = false;
bool sc_is_named_android = sc_ce->def.is_named_android_sc(); bool sc_is_named_android = sc_ce->def.is_named_android_sc();
if (sc_is_named_android && if (sc_is_named_android && !has_permission_effect && (left_ce->def.type == CardType::ITEM)) {
!has_permission_effect &&
(left_ce->def.type == CardType::ITEM)) {
sc_is_named_android_without_permission_effect = true; sc_is_named_android_without_permission_effect = true;
} }
@@ -136,8 +137,7 @@ bool card_linkage_is_valid(
for (size_t x = 0; x < 8; x++) { for (size_t x = 0; x < 8; x++) {
uint8_t right_color = left_ce->def.right_colors[x]; uint8_t right_color = left_ce->def.right_colors[x];
if ((right_color != 0) && if ((right_color != 0) && (!sc_is_named_android_without_permission_effect || (right_color != 3))) {
(!sc_is_named_android_without_permission_effect || (right_color != 3))) {
for (size_t y = 0; y < 8; y++) { for (size_t y = 0; y < 8; y++) {
if (right_color == right_ce->def.left_colors[y]) { if (right_color == right_ce->def.left_colors[y]) {
return true; return true;
@@ -146,15 +146,13 @@ bool card_linkage_is_valid(
} }
} }
// If we get here, then the linkage does not make sense based only on the // If we get here, then the linkage does not make sense based only on the cards' left/right colors. It may still be
// cards' left/right colors. It may still be allowed if Permission is in // allowed if Permission is in effect, though.
// effect, though.
// Ignore Permission effect if the left card is another action card (the Tech // Ignore Permission effect if the left card is another action card (the Tech color linkage must make sense in that
// color linkage must make sense in that case). (The way they do this is kind // case). (The way they do this is kind of dumb - they should have checked that type == ACTION, but instead they
// of dumb - they should have checked that type == ACTION, but instead they // checked that type *isn't* most of the other types... but curiously, ASSIST is not checked. This is probably just
// checked that type *isn't* most of the other types... but curiously, ASSIST // an oversight.)
// is not checked. This is probably just an oversight.)
if (has_permission_effect && if (has_permission_effect &&
(left_ce->def.type != CardType::HUNTERS_SC) && (left_ce->def.type != CardType::HUNTERS_SC) &&
(left_ce->def.type != CardType::ARKZ_SC) && (left_ce->def.type != CardType::ARKZ_SC) &&
@@ -198,17 +196,14 @@ shared_ptr<const Server> RulerServer::server() const {
return s; return s;
} }
ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref( ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(uint16_t card_ref) {
uint16_t card_ref) {
return const_cast<ActionChainWithConds*>(as_const(*this).action_chain_with_conds_for_card_ref(card_ref)); return const_cast<ActionChainWithConds*>(as_const(*this).action_chain_with_conds_for_card_ref(card_ref));
} }
const ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref( const ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(uint16_t card_ref) const {
uint16_t card_ref) const {
uint8_t client_id = client_id_for_card_ref(card_ref); uint8_t client_id = client_id_for_card_ref(card_ref);
if (client_id != 0xFF) { if (client_id != 0xFF) {
// There appears to be a bug in Trial Edition: the bound on this loop is // There appears to be a bug in Trial Edition: the bound on this loop is 0x10, not 9.
// 0x10, not 9.
for (size_t z = 0; z < 9; z++) { for (size_t z = 0; z < 9; z++) {
const auto* chain = &this->set_card_action_chains[client_id]->at(z); const auto* chain = &this->set_card_action_chains[client_id]->at(z);
if (card_ref == chain->chain.acting_card_ref) { if (card_ref == chain->chain.acting_card_ref) {
@@ -219,8 +214,7 @@ const ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(
return nullptr; return nullptr;
} }
bool RulerServer::any_attack_action_card_is_support_tech_or_support_pb( bool RulerServer::any_attack_action_card_is_support_tech_or_support_pb(const ActionState& pa) const {
const ActionState& pa) const {
if (pa.attacker_card_ref != 0xFFFF) { if (pa.attacker_card_ref != 0xFFFF) {
for (size_t z = 0; (z < 8) && (pa.action_card_refs[z] != 0xFFFF); z++) { for (size_t z = 0; (z < 8) && (pa.action_card_refs[z] != 0xFFFF); z++) {
uint16_t card_id = this->card_id_for_card_ref(pa.action_card_refs[z]); uint16_t card_id = this->card_id_for_card_ref(pa.action_card_refs[z]);
@@ -292,8 +286,7 @@ bool RulerServer::card_has_pierce_or_rampage(
const auto& sc_status = short_statuses->at(0); const auto& sc_status = short_statuses->at(0);
auto ce = this->definition_for_card_ref(sc_status.card_ref); auto ce = this->definition_for_card_ref(sc_status.card_ref);
// This appears to be an NTE bug: Major Pierce doesn't work on Arkz SCs. // This appears to be an NTE bug: Major Pierce doesn't work on Arkz SCs.
if (ce && if (ce && (!is_nte || (ce->def.type == CardType::HUNTERS_SC)) &&
(!is_nte || (ce->def.type == CardType::HUNTERS_SC)) &&
(this->get_card_ref_max_hp(sc_status.card_ref) <= sc_status.current_hp * 2)) { (this->get_card_ref_max_hp(sc_status.card_ref) <= sc_status.current_hp * 2)) {
return ret; return ret;
} }
@@ -354,8 +347,7 @@ bool RulerServer::attack_action_has_rampage_and_not_pierce(const ActionState& pa
} }
} }
const auto* chain = this->action_chain_with_conds_for_card_ref( const auto* chain = this->action_chain_with_conds_for_card_ref(pa.attacker_card_ref);
pa.attacker_card_ref);
if (chain) { if (chain) {
for (ssize_t z = 8; z >= 0; z--) { for (ssize_t z = 8; z >= 0; z--) {
bool has_rampage = this->check_pierce_and_rampage( bool has_rampage = this->check_pierce_and_rampage(
@@ -396,7 +388,8 @@ bool RulerServer::attack_action_has_pierce_and_not_rampage(const ActionState& pa
} }
if ((card_ref1 != 0xFFFF) && if ((card_ref1 != 0xFFFF) &&
(card_ref1 != pa.attacker_card_ref) && (card_ref1 != pa.attacker_card_ref) &&
!this->check_usability_or_apply_condition_for_card_refs(card_ref1, pa.attacker_card_ref, stat->at(0).card_ref, 0xFF, AttackMedium::INVALID_FF)) { !this->check_usability_or_apply_condition_for_card_refs(
card_ref1, pa.attacker_card_ref, stat->at(0).card_ref, 0xFF, AttackMedium::INVALID_FF)) {
return false; return false;
} }
@@ -433,8 +426,7 @@ bool RulerServer::attack_action_has_pierce_and_not_rampage(const ActionState& pa
} }
for (; last_action_card_index >= 0; last_action_card_index--) { for (; last_action_card_index >= 0; last_action_card_index--) {
auto ce = this->definition_for_card_ref( auto ce = this->definition_for_card_ref(pa.action_card_refs[last_action_card_index]);
pa.action_card_refs[last_action_card_index]);
if (!ce) { if (!ce) {
continue; continue;
} }
@@ -560,8 +552,7 @@ bool RulerServer::card_ref_can_attack(uint16_t card_ref) {
return true; return true;
} }
size_t num_assists = this->assist_server->compute_num_assist_effects_for_client( size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(client_id);
client_id);
for (size_t z = 0; z < num_assists; z++) { for (size_t z = 0; z < num_assists; z++) {
if (this->assist_server->get_active_assist_by_index(z) == AssistEffect::PERMISSION) { if (this->assist_server->get_active_assist_by_index(z) == AssistEffect::PERMISSION) {
return true; return true;
@@ -571,8 +562,7 @@ bool RulerServer::card_ref_can_attack(uint16_t card_ref) {
return !ce->def.cannot_attack; return !ce->def.cannot_attack;
} }
bool RulerServer::card_ref_can_move( bool RulerServer::card_ref_can_move(uint8_t client_id, uint16_t card_ref, bool ignore_atk_points) const {
uint8_t client_id, uint16_t card_ref, bool ignore_atk_points) const {
if (client_id == 0xFF) { if (client_id == 0xFF) {
return false; return false;
} }
@@ -644,8 +634,7 @@ bool RulerServer::card_ref_can_move(
} }
} }
bool RulerServer::card_ref_has_class_usability_condition( bool RulerServer::card_ref_has_class_usability_condition(uint16_t card_ref) const {
uint16_t card_ref) const {
auto ce = this->definition_for_card_ref(card_ref); auto ce = this->definition_for_card_ref(card_ref);
if (ce) { if (ce) {
uint8_t criterion = static_cast<uint8_t>(ce->def.usable_criterion); uint8_t criterion = static_cast<uint8_t>(ce->def.usable_criterion);
@@ -685,8 +674,7 @@ bool RulerServer::card_ref_is_aerial(uint16_t card_ref) const {
return this->find_condition_on_card_ref(card_ref, ConditionType::AERIAL); return this->find_condition_on_card_ref(card_ref, ConditionType::AERIAL);
} }
bool RulerServer::card_ref_is_aerial_or_has_free_maneuver( bool RulerServer::card_ref_is_aerial_or_has_free_maneuver(uint16_t card_ref) const {
uint16_t card_ref) const {
return (this->card_ref_has_free_maneuver(card_ref) || this->card_ref_is_aerial(card_ref)); return (this->card_ref_has_free_maneuver(card_ref) || this->card_ref_is_aerial(card_ref));
} }
@@ -694,8 +682,7 @@ bool RulerServer::card_ref_is_boss_sc(uint32_t card_ref) const {
return this->card_id_is_boss_sc(this->card_id_for_card_ref(card_ref)); return this->card_id_is_boss_sc(this->card_id_for_card_ref(card_ref));
} }
bool RulerServer::card_ref_or_any_set_card_has_condition_46( bool RulerServer::card_ref_or_any_set_card_has_condition_46(uint16_t card_ref) const {
uint16_t card_ref) const {
uint16_t card_id = this->card_id_for_card_ref(card_ref); uint16_t card_id = this->card_id_for_card_ref(card_ref);
if (card_id == 0xFFFF) { if (card_id == 0xFFFF) {
return false; return false;
@@ -752,8 +739,7 @@ bool RulerServer::card_ref_or_sc_has_fixed_range(uint16_t card_ref) const {
return false; return false;
} }
return this->find_condition_on_card_ref( return this->find_condition_on_card_ref(this->short_statuses[client_id]->at(0).card_ref, ConditionType::FIXED_RANGE);
this->short_statuses[client_id]->at(0).card_ref, ConditionType::FIXED_RANGE);
} }
bool RulerServer::check_move_path_and_get_cost( bool RulerServer::check_move_path_and_get_cost(
@@ -772,9 +758,8 @@ bool RulerServer::check_move_path_and_get_cost(
} }
uint8_t atk = this->hand_and_equip_states[client_id]->atk_points; uint8_t atk = this->hand_and_equip_states[client_id]->atk_points;
// Note: In the original code, it seems atk was signed, which doesn't make // Note: In the original code, it seems atk was signed, which doesn't make much sense.
// much sense. We've fixed that here. // if (atk < 0) { // This is supposed to be impossible
// if (atk < 0) { // Uhhh what? This is supposed to be impossible
// return false; // return false;
// } // }
@@ -833,8 +818,7 @@ bool RulerServer::check_pierce_and_rampage(
return false; return false;
} }
if ((card_ref != 0xFFFF) && if ((card_ref != 0xFFFF) && (!card_short_status || !this->card_exists_by_status(*card_short_status))) {
(!card_short_status || !this->card_exists_by_status(*card_short_status))) {
return false; return false;
} }
@@ -971,8 +955,7 @@ bool RulerServer::check_usability_or_condition_apply(
} }
log.debug_f("criterion_code={}", phosg::name_for_enum(criterion_code)); log.debug_f("criterion_code={}", phosg::name_for_enum(criterion_code));
// For item usability checks, prevent criteria that depend on player // For item usability checks, prevent criteria that depend on player positioning/team setup
// positioning/team setup
if (is_item_usability_check && if (is_item_usability_check &&
((criterion_code == CriterionCode::SAME_TEAM) || ((criterion_code == CriterionCode::SAME_TEAM) ||
(criterion_code == CriterionCode::SAME_PLAYER) || (criterion_code == CriterionCode::SAME_PLAYER) ||
@@ -984,13 +967,11 @@ bool RulerServer::check_usability_or_condition_apply(
criterion_code = CriterionCode::NONE; criterion_code = CriterionCode::NONE;
} }
// Presumably this odd-looking expression here is used to handle two different // Presumably this odd-looking expression here is used to handle two different cases. When checking for a condition,
// cases. When checking for a condition, def_effect_index should be non-0xFF, // def_effect_index should be non-0xFF, so we'd return true if the criterion passes. When checking if an item or
// so we'd return true if the criterion passes. When checking if an item or // creature card is usable, the two client IDs should be the same or the second should not be given, so we'd return
// creature card is usable, the two client IDs should be the same or the // true if the criterion passes. If neither of these cases apply, we should return false as a failsafe even if the
// second should not be given, so we'd return true if the criterion passes. If // criterion passes. NTE did not have such a check.
// neither of these cases apply, we should return false as a failsafe even if
// the criterion passes. NTE did not have such a check.
bool ret = is_nte || (!(def_effect_index & 0x80) || (client_id1 == client_id2)) || (client_id2 == 0xFF); bool ret = is_nte || (!(def_effect_index & 0x80) || (client_id1 == client_id2)) || (client_id2 == 0xFF);
switch (criterion_code) { switch (criterion_code) {
case CriterionCode::NONE: case CriterionCode::NONE:
@@ -1359,9 +1340,7 @@ bool RulerServer::check_usability_or_condition_apply(
} }
uint16_t RulerServer::compute_attack_or_defense_costs( uint16_t RulerServer::compute_attack_or_defense_costs(
const ActionState& pa, const ActionState& pa, bool allow_mighty_knuckle, uint8_t* out_ally_cost) const {
bool allow_mighty_knuckle,
uint8_t* out_ally_cost) const {
int16_t final_cost = 1; int16_t final_cost = 1;
bool has_mighty_knuckle = false; bool has_mighty_knuckle = false;
int16_t cost_bias = 0; int16_t cost_bias = 0;
@@ -1383,8 +1362,7 @@ uint16_t RulerServer::compute_attack_or_defense_costs(
uint8_t client_id = client_id_for_card_ref(pa.attacker_card_ref); uint8_t client_id = client_id_for_card_ref(pa.attacker_card_ref);
uint16_t sc_card_ref_if_item = 0xFFFF; uint16_t sc_card_ref_if_item = 0xFFFF;
if ((client_id != 0xFF) && ce && (ce->def.type == CardType::ITEM) && if ((client_id != 0xFF) && ce && (ce->def.type == CardType::ITEM) && this->short_statuses[client_id]) {
this->short_statuses[client_id]) {
sc_card_ref_if_item = this->short_statuses[client_id]->at(0).card_ref; sc_card_ref_if_item = this->short_statuses[client_id]->at(0).card_ref;
} }
@@ -1514,8 +1492,7 @@ bool RulerServer::compute_effective_range_and_target_mode_for_attack(
if (sc_ce && (static_cast<uint8_t>(target_mode) < 6)) { if (sc_ce && (static_cast<uint8_t>(target_mode) < 6)) {
target_mode = sc_ce->def.target_mode; target_mode = sc_ce->def.target_mode;
const char* target_mode_name = name_for_target_mode(target_mode); const char* target_mode_name = name_for_target_mode(target_mode);
log.debug_f("sc_ce overrides target mode with {} ({})", log.debug_f("sc_ce overrides target mode with {} ({})", target_mode_name, static_cast<uint8_t>(target_mode));
target_mode_name, static_cast<uint8_t>(target_mode));
} }
} }
} }
@@ -1568,9 +1545,7 @@ size_t RulerServer::count_rampage_targets_for_attack(const ActionState& pa, uint
} }
bool RulerServer::defense_card_can_apply_to_attack( bool RulerServer::defense_card_can_apply_to_attack(
uint16_t defense_card_ref, uint16_t defense_card_ref, uint16_t attacker_card_ref, uint16_t attacker_sc_card_ref) const {
uint16_t attacker_card_ref,
uint16_t attacker_sc_card_ref) const {
uint16_t defense_card_id = this->card_id_for_card_ref(defense_card_ref); uint16_t defense_card_id = this->card_id_for_card_ref(defense_card_ref);
uint16_t attacker_sc_card_id = this->card_id_for_card_ref(attacker_sc_card_ref); uint16_t attacker_sc_card_id = this->card_id_for_card_ref(attacker_sc_card_ref);
uint16_t attacker_card_id = this->card_id_for_card_ref(attacker_card_ref); uint16_t attacker_card_id = this->card_id_for_card_ref(attacker_card_ref);
@@ -1654,8 +1629,7 @@ bool RulerServer::defense_card_matches_any_attack_card_top_color(const ActionSta
if (!ce) { if (!ce) {
throw runtime_error("defense card definition is missing"); throw runtime_error("defense card definition is missing");
} }
const auto* chain = this->action_chain_with_conds_for_card_ref( const auto* chain = this->action_chain_with_conds_for_card_ref(pa.original_attacker_card_ref);
pa.original_attacker_card_ref);
if (chain->chain.attack_action_card_ref_count < 1) { if (chain->chain.attack_action_card_ref_count < 1) {
auto other_ce = this->definition_for_card_ref(pa.original_attacker_card_ref); auto other_ce = this->definition_for_card_ref(pa.original_attacker_card_ref);
if (other_ce && other_ce->def.any_top_color_matches(ce->def)) { if (other_ce && other_ce->def.any_top_color_matches(ce->def)) {
@@ -1681,10 +1655,7 @@ shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_ref(uint
} }
int32_t RulerServer::error_code_for_client_setting_card( int32_t RulerServer::error_code_for_client_setting_card(
uint8_t client_id, uint8_t client_id, uint16_t card_ref, const Location* loc, uint8_t assist_target_client_id) const {
uint16_t card_ref,
const Location* loc,
uint8_t assist_target_client_id) const {
if (client_id > 3) { if (client_id > 3) {
return -0x7D; return -0x7D;
} }
@@ -1843,8 +1814,7 @@ int32_t RulerServer::error_code_for_client_setting_card(
if (!this->get_creature_summon_area(client_id, &summon_area_loc, &summon_area_size)) { if (!this->get_creature_summon_area(client_id, &summon_area_loc, &summon_area_size)) {
if (team_id != 1) { if (team_id != 1) {
if ((loc->x > 0) && (loc->x < this->map_and_rules->map.width - 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) && if ((loc->y < this->map_and_rules->map.height - summon_cost - 1) && (loc->y > 0)) {
(loc->y > 0)) {
return 0; return 0;
} }
if (loc->y == 1) { if (loc->y == 1) {
@@ -1852,8 +1822,7 @@ int32_t RulerServer::error_code_for_client_setting_card(
} }
} }
} else { } else {
if ((loc->x > 0) && if ((loc->x > 0) && (loc->x < this->map_and_rules->map.width - 1)) {
(loc->x < this->map_and_rules->map.width - 1)) {
if ((summon_cost + 1 <= loc->y) && (loc->y < this->map_and_rules->map.height - 1)) { if ((summon_cost + 1 <= loc->y) && (loc->y < this->map_and_rules->map.height - 1)) {
return 0; return 0;
} }
@@ -1965,8 +1934,7 @@ bool RulerServer::flood_fill_move_path(
size_t num_occupied_tiles, size_t num_occupied_tiles,
size_t num_vacant_tiles) const { size_t num_vacant_tiles) const {
auto state = this->map_and_rules; auto state = this->map_and_rules;
if ((x < 1) || (x >= state->map.width - 1) || if ((x < 1) || (x >= state->map.width - 1) || (y < 1) || (y >= state->map.height - 1)) {
(y < 1) || (y >= state->map.height - 1)) {
return 0; return 0;
} }
@@ -1979,15 +1947,12 @@ bool RulerServer::flood_fill_move_path(
} else { } else {
uint32_t cost = this->get_path_cost( uint32_t cost = this->get_path_cost(
chain, chain, num_vacant_tiles + num_occupied_tiles + 1, is_aerial ? num_occupied_tiles : 0);
num_vacant_tiles + num_occupied_tiles + 1,
is_aerial ? num_occupied_tiles : 0);
if (max_atk_points < cost) { if (max_atk_points < cost) {
return 0; return 0;
} }
visited_map->at(x * 0x10 + y) = 1; visited_map->at(x * 0x10 + y) = 1;
if (path && (path->end_loc.x == x) && (path->end_loc.y == y) && if (path && (path->end_loc.x == x) && (path->end_loc.y == y) && ((path->length == -1) || (cost < path->cost))) {
((path->length == -1) || (cost < path->cost))) {
ret = true; ret = true;
path->reset_totals(); path->reset_totals();
path->remaining_distance = max_distance; path->remaining_distance = max_distance;
@@ -2005,8 +1970,7 @@ bool RulerServer::flood_fill_move_path(
int16_t new_max_distance = max_distance - 1; int16_t new_max_distance = max_distance - 1;
if (new_max_distance > 0) { if (new_max_distance > 0) {
static const int8_t offsets[4][2] = { static const int8_t offsets[4][2] = {{1, 0}, {0, -1}, {-1, 0}, {0, 1}};
{1, 0}, {0, -1}, {-1, 0}, {0, 1}};
Direction dirs[3] = {direction, turn_left(direction), turn_right(direction)}; Direction dirs[3] = {direction, turn_left(direction), turn_right(direction)};
for (size_t dir_index = 0; dir_index < 3; dir_index++) { for (size_t dir_index = 0; dir_index < 3; dir_index++) {
if (static_cast<uint8_t>(dirs[dir_index]) > 3) { if (static_cast<uint8_t>(dirs[dir_index]) > 3) {
@@ -2061,9 +2025,7 @@ uint32_t RulerServer::get_card_id_with_effective_range(
uint16_t card_ref, uint16_t card_id_override, TargetMode* out_target_mode) const { uint16_t card_ref, uint16_t card_id_override, TargetMode* out_target_mode) const {
auto log = this->server()->log_stack(std::format("get_card_id_with_effective_range(@{:04X}, #{:04X}): ", card_ref, card_id_override)); auto log = this->server()->log_stack(std::format("get_card_id_with_effective_range(@{:04X}, #{:04X}): ", card_ref, card_id_override));
uint16_t card_id = (card_id_override == 0xFFFF) uint16_t card_id = (card_id_override == 0xFFFF) ? this->card_id_for_card_ref(card_ref) : card_id_override;
? this->card_id_for_card_ref(card_ref)
: card_id_override;
log.debug_f("card_id=#{:04X}", card_id); log.debug_f("card_id=#{:04X}", card_id);
if (card_id != 0xFFFF) { if (card_id != 0xFFFF) {
@@ -2079,7 +2041,8 @@ uint32_t RulerServer::get_card_id_with_effective_range(
card_id = this->card_id_for_card_ref(card_ref); card_id = this->card_id_for_card_ref(card_ref);
auto orig_ce = this->definition_for_card_id(card_id); auto orig_ce = this->definition_for_card_id(card_id);
if (orig_ce && (static_cast<uint8_t>(effective_target_mode) < 6)) { if (orig_ce && (static_cast<uint8_t>(effective_target_mode) < 6)) {
log.debug_f("ce valid for #{:04X} with effective target mode {}; overriding to {}", card_id, name_for_target_mode(effective_target_mode), name_for_target_mode(orig_ce->def.target_mode)); log.debug_f("ce valid for #{:04X} with effective target mode {}; overriding to {}",
card_id, name_for_target_mode(effective_target_mode), name_for_target_mode(orig_ce->def.target_mode));
effective_target_mode = orig_ce->def.target_mode; effective_target_mode = orig_ce->def.target_mode;
} }
} }
@@ -2123,8 +2086,7 @@ uint8_t RulerServer::get_card_ref_max_hp(uint16_t card_ref) const {
} }
} }
bool RulerServer::get_creature_summon_area( bool RulerServer::get_creature_summon_area(uint8_t client_id, Location* out_loc, uint8_t* out_region_size) const {
uint8_t client_id, Location* out_loc, uint8_t* out_region_size) const {
if (!this->map_and_rules || (client_id > 3)) { if (!this->map_and_rules || (client_id > 3)) {
return false; return false;
} }
@@ -2155,8 +2117,7 @@ bool RulerServer::get_creature_summon_area(
region_size = this->map_and_rules->map.height - 3; region_size = this->map_and_rules->map.height - 3;
break; break;
default: default:
// This case isn't in the original code; probably it fell through to one // This case isn't in the original code; probably it fell through to one of the above
// of the above
return false; return false;
} }
@@ -2169,27 +2130,20 @@ bool RulerServer::get_creature_summon_area(
return true; return true;
} }
shared_ptr<HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id( shared_ptr<HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id(uint8_t client_id) {
uint8_t client_id) {
return (client_id < 4) ? this->hand_and_equip_states[client_id] : nullptr; return (client_id < 4) ? this->hand_and_equip_states[client_id] : nullptr;
} }
shared_ptr<const HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id( shared_ptr<const HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id(uint8_t client_id) const {
uint8_t client_id) const {
return (client_id < 4) ? this->hand_and_equip_states[client_id] : nullptr; return (client_id < 4) ? this->hand_and_equip_states[client_id] : nullptr;
} }
bool RulerServer::get_move_path_length_and_cost( bool RulerServer::get_move_path_length_and_cost(
uint32_t client_id, uint32_t client_id, uint32_t card_ref, const Location& loc, uint32_t* out_length, uint32_t* out_cost) const {
uint32_t card_ref,
const Location& loc,
uint32_t* out_length,
uint32_t* out_cost) const {
MovePath path; MovePath path;
parray<uint8_t, 0x100> visited_map; parray<uint8_t, 0x100> visited_map;
path.end_loc = loc; path.end_loc = loc;
if (!this->check_move_path_and_get_cost( if (!this->check_move_path_and_get_cost(client_id, card_ref, &visited_map, &path, out_cost)) {
client_id, card_ref, &visited_map, &path, out_cost)) {
return false; return false;
} }
@@ -2206,9 +2160,7 @@ bool RulerServer::get_move_path_length_and_cost(
} }
ssize_t RulerServer::get_path_cost( ssize_t RulerServer::get_path_cost(
const ActionChainWithConds& chain, const ActionChainWithConds& chain, ssize_t path_length, ssize_t cost_penalty) const {
ssize_t path_length,
ssize_t cost_penalty) const {
for (size_t x = 0; x < 9; x++) { for (size_t x = 0; x < 9; x++) {
const auto& cond = chain.conditions[x]; const auto& cond = chain.conditions[x];
if (cond.type == ConditionType::SET_MV_COST_TO_0) { if (cond.type == ConditionType::SET_MV_COST_TO_0) {
@@ -2253,13 +2205,11 @@ bool RulerServer::is_attack_valid(const ActionState& pa) {
return false; return false;
} }
// Note: The original code has a case here that results in error code -0x5E, // Note: The original code has a case here that results in error code -0x5E, triggered by a function returning false.
// triggered by a function returning false. However, that function always // However, that function always returns true and has no side effects, so we've omitted the case here.
// returns true and has no side effects, so we've omitted the case here.
const auto* attacker_card_status = this->short_status_for_card_ref(attacker_card_ref); const auto* attacker_card_status = this->short_status_for_card_ref(attacker_card_ref);
if (!attacker_card_status || if (!attacker_card_status || !this->card_ref_can_attack(attacker_card_ref) ||
!this->card_ref_can_attack(attacker_card_ref) ||
(attacker_card_status->card_flags & 0x500)) { (attacker_card_status->card_flags & 0x500)) {
this->error_code3 = -0x6F; this->error_code3 = -0x6F;
return false; return false;
@@ -2272,9 +2222,7 @@ bool RulerServer::is_attack_valid(const ActionState& pa) {
auto attacker_ce = this->definition_for_card_ref(attacker_card_ref); auto attacker_ce = this->definition_for_card_ref(attacker_card_ref);
auto attacker_chain = this->action_chain_with_conds_for_card_ref(attacker_card_ref); auto attacker_chain = this->action_chain_with_conds_for_card_ref(attacker_card_ref);
if (!attacker_chain || if (!attacker_chain || (attacker_chain->chain.acting_card_ref != attacker_card_ref) || !attacker_ce ||
(attacker_chain->chain.acting_card_ref != attacker_card_ref) ||
!attacker_ce ||
((attacker_ce->def.type != CardType::HUNTERS_SC && ((attacker_ce->def.type != CardType::HUNTERS_SC &&
(attacker_ce->def.type != CardType::ARKZ_SC) && (attacker_ce->def.type != CardType::ARKZ_SC) &&
(attacker_ce->def.type != CardType::CREATURE) && (attacker_ce->def.type != CardType::CREATURE) &&
@@ -2313,7 +2261,9 @@ bool RulerServer::is_attack_valid(const ActionState& pa) {
return false; return false;
} }
auto left_card_ce = (z == 0) ? this->definition_for_card_ref(card_ref) : this->definition_for_card_ref(pa.action_card_refs[z - 1]); auto left_card_ce = (z == 0)
? this->definition_for_card_ref(card_ref)
: this->definition_for_card_ref(pa.action_card_refs[z - 1]);
auto right_card_ce = this->definition_for_card_ref(right_card_ref); auto right_card_ce = this->definition_for_card_ref(right_card_ref);
if (right_card_ce->def.type != CardType::ACTION) { if (right_card_ce->def.type != CardType::ACTION) {
@@ -2326,7 +2276,9 @@ bool RulerServer::is_attack_valid(const ActionState& pa) {
} }
uint8_t attacker_client_id = client_id_for_card_ref(pa.attacker_card_ref); uint8_t attacker_client_id = client_id_for_card_ref(pa.attacker_card_ref);
auto sc_ce = (attacker_client_id != 0xFF) ? this->definition_for_card_ref(this->set_card_action_chains[attacker_client_id]->at(0).chain.acting_card_ref) : nullptr; auto sc_ce = (attacker_client_id != 0xFF)
? this->definition_for_card_ref(this->set_card_action_chains[attacker_client_id]->at(0).chain.acting_card_ref)
: nullptr;
if (!card_linkage_is_valid(right_card_ce, left_card_ce, sc_ce, has_permission_effect)) { if (!card_linkage_is_valid(right_card_ce, left_card_ce, sc_ce, has_permission_effect)) {
this->error_code3 = -0x6B; this->error_code3 = -0x6B;
@@ -2363,8 +2315,7 @@ bool RulerServer::is_attack_valid(const ActionState& pa) {
} }
bool RulerServer::is_attack_or_defense_valid(const ActionState& pa) { bool RulerServer::is_attack_or_defense_valid(const ActionState& pa) {
// This error code is present in the original code, but is no longer possible // This error code is present in the original code, but is no longer possible since we require pa instead.
// since we require pa instead of using a pointer.
// if (!pa) { // if (!pa) {
// this->error_code3 = -0x78; // this->error_code3 = -0x78;
// return false; // return false;
@@ -2450,9 +2401,8 @@ bool RulerServer::is_defense_valid(const ActionState& pa) {
return false; return false;
} }
// Note: The original code has a case here that results in error code -0x5E, // Note: The original code has a case here that results in error code -0x5E, triggered by a function returning false.
// triggered by a function returning false. However, that function always // However, that function always returns true and has no side effects, so we've omitted the case here.
// returns true and has no side effects, so we've omitted the case here.
const auto* stat = this->short_status_for_card_ref(pa.target_card_refs[0]); const auto* stat = this->short_status_for_card_ref(pa.target_card_refs[0]);
if ((!stat || !this->card_exists_by_status(*stat)) || (stat->card_flags & 0x800)) { if ((!stat || !this->card_exists_by_status(*stat)) || (stat->card_flags & 0x800)) {
@@ -2590,9 +2540,8 @@ bool RulerServer::MovePath::is_valid() const {
void RulerServer::offsets_for_direction( void RulerServer::offsets_for_direction(
const Location& loc, int32_t* out_x_offset, int32_t* out_y_offset) { const Location& loc, int32_t* out_x_offset, int32_t* out_y_offset) {
// Note: This function has opposite behavior for the UP and DOWN directions // Note: This function has opposite behavior for the UP and DOWN directions as compared to the global array of the
// as compared to the global array of the same name. // same name. TODO: Figure out why this difference exists and document it.
// TODO: Figure out why this difference exists and document it.
switch (loc.direction) { switch (loc.direction) {
case Direction::LEFT: case Direction::LEFT:
*out_x_offset = -1; *out_x_offset = -1;
@@ -2629,8 +2578,7 @@ void RulerServer::register_player(
this->set_card_action_metadatas[client_id] = set_card_action_metadatas; this->set_card_action_metadatas[client_id] = set_card_action_metadatas;
} }
void RulerServer::replace_D1_D2_rank_cards_with_Attack( void RulerServer::replace_D1_D2_rank_cards_with_Attack(parray<le_uint16_t, 0x1F>& card_ids) const {
parray<le_uint16_t, 0x1F>& card_ids) const {
for (size_t z = 0; z < card_ids.size(); z++) { for (size_t z = 0; z < card_ids.size(); z++) {
auto ce = this->definition_for_card_id(card_ids[z]); auto ce = this->definition_for_card_id(card_ids[z]);
if (ce && ((ce->def.rank == CardRank::D1) || (ce->def.rank == CardRank::D2))) { if (ce && ((ce->def.rank == CardRank::D1) || (ce->def.rank == CardRank::D2))) {
@@ -2737,8 +2685,7 @@ bool RulerServer::should_allow_attacks_on_current_turn() const {
} }
int32_t RulerServer::verify_deck( int32_t RulerServer::verify_deck(
const parray<le_uint16_t, 0x1F>& card_ids, const parray<le_uint16_t, 0x1F>& card_ids, const parray<uint8_t, 0x2F0>* owned_card_counts) const {
const parray<uint8_t, 0x2F0>* owned_card_counts) const {
for (size_t z = 0; z < card_ids.size(); z++) { for (size_t z = 0; z < card_ids.size(); z++) {
if (!this->definition_for_card_id(card_ids.at(z))) { if (!this->definition_for_card_id(card_ids.at(z))) {
return -0x7C; return -0x7C;
+2 -4
View File
@@ -152,8 +152,7 @@ public:
uint32_t get_card_id_with_effective_range( uint32_t get_card_id_with_effective_range(
uint16_t card_ref, uint16_t card_id_override, TargetMode* out_target_mode) const; uint16_t card_ref, uint16_t card_id_override, TargetMode* out_target_mode) const;
uint8_t get_card_ref_max_hp(uint16_t card_ref) const; uint8_t get_card_ref_max_hp(uint16_t card_ref) const;
bool get_creature_summon_area( bool get_creature_summon_area(uint8_t client_id, Location* out_loc, uint8_t* out_region_size) const;
uint8_t client_id, Location* out_loc, uint8_t* out_region_size) const;
std::shared_ptr<HandAndEquipState> get_hand_and_equip_state_for_client_id(uint8_t client_id); std::shared_ptr<HandAndEquipState> get_hand_and_equip_state_for_client_id(uint8_t client_id);
std::shared_ptr<const HandAndEquipState> get_hand_and_equip_state_for_client_id(uint8_t client_id) const; std::shared_ptr<const HandAndEquipState> get_hand_and_equip_state_for_client_id(uint8_t client_id) const;
bool get_move_path_length_and_cost( bool get_move_path_length_and_cost(
@@ -191,8 +190,7 @@ public:
const CardShortStatus* short_status_for_card_ref(uint16_t card_ref) const; const CardShortStatus* short_status_for_card_ref(uint16_t card_ref) const;
bool should_allow_attacks_on_current_turn() const; bool should_allow_attacks_on_current_turn() const;
int32_t verify_deck( int32_t verify_deck(
const parray<le_uint16_t, 0x1F>& card_ids, const parray<le_uint16_t, 0x1F>& card_ids, const parray<uint8_t, 0x2F0>* owned_card_counts = nullptr) const;
const parray<uint8_t, 0x2F0>* owned_card_counts = nullptr) const;
private: private:
std::weak_ptr<Server> w_server; std::weak_ptr<Server> w_server;
+79 -113
View File
@@ -12,12 +12,10 @@ using namespace std;
namespace Episode3 { namespace Episode3 {
// This is (obviously) not the original string. The original string is: // This is (obviously) not the original string. The original string is:
// "03/05/29 18:00 by K.Toya" (NTE) // NTE: "03/05/29 18:00 by K.Toya"
// "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya" (Final) // Final: "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya"
static const char* VERSION_SIGNATURE = static const char* VERSION_SIGNATURE = "newserv Ep3 based on [V1][FINAL2.0] 03/09/13 15:30 by K.Toya";
"newserv Ep3 based on [V1][FINAL2.0] 03/09/13 15:30 by K.Toya"; static const char* VERSION_SIGNATURE_NTE = "newserv Ep3 NTE based on 03/05/29 18:00 by K.Toya";
static const char* VERSION_SIGNATURE_NTE =
"newserv Ep3 NTE based on 03/05/29 18:00 by K.Toya";
Server::PresenceEntry::PresenceEntry() { Server::PresenceEntry::PresenceEntry() {
this->clear(); this->clear();
@@ -103,10 +101,9 @@ void Server::init() {
this->card_special = make_shared<CardSpecial>(this->shared_from_this()); this->card_special = make_shared<CardSpecial>(this->shared_from_this());
// Note: The original implementation calls the default PSOV2Encryption // Note: The original implementation calls the default PSOV2Encryption constructor for random_crypt, which just uses
// constructor for random_crypt, which just uses 0 as the seed. It then // 0 as the seed. It then re-seeds the generator later. We instead expect the caller to provide a seeded generator,
// re-seeds the generator later. We instead expect the caller to provide a // and we don't re-seed it at all.
// seeded generator, and we don't re-seed it at all.
// this->random_crypt = make_shared<PSOV2Encryption>(0); // this->random_crypt = make_shared<PSOV2Encryption>(0);
this->state_flags = make_shared<StateFlags>(); this->state_flags = make_shared<StateFlags>();
@@ -254,10 +251,9 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
size = masked_data.size(); size = masked_data.size();
} }
// Note: Sega's servers sent battle commands with the 60 command. The handlers // Note: Sega's servers sent battle commands with the 60 command. The handlers for 60, 62, and C9 on the client are
// for 60, 62, and C9 on the client are identical, so we choose to use C9 // identical, so we choose to use C9 instead because it's unique to Episode 3, and therefore seems more appropriate
// instead because it's unique to Episode 3, and therefore seems more // to convey Episode 3 battle commands.
// appropriate to convey battle commands.
send_command(l, command, 0x00, data, size); send_command(l, command, 0x00, data, size);
for (auto watcher_l : l->watcher_lobbies) { for (auto watcher_l : l->watcher_lobbies) {
send_command_if_not_loading(watcher_l, command, 0x00, data, size); send_command_if_not_loading(watcher_l, command, 0x00, data, size);
@@ -273,14 +269,15 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
} }
void Server::send_6xB4x46() const { void Server::send_6xB4x46() const {
// Note: This function is not part of the original implementation; it was // Note: This function is not part of the original implementation; it was factored out from its callsites in this
// factored out from its callsites in this file and the strings were changed. // file and the strings were changed.
// NTE doesn't have the date_str2 field, but we send it anyway to make // NTE doesn't have the date_str2 field, but we send it anyway to make debugging easier.
// debugging easier.
G_ServerVersionStrings_Ep3_6xB4x46 cmd; G_ServerVersionStrings_Ep3_6xB4x46 cmd;
cmd.version_signature.encode(this->options.is_nte() ? VERSION_SIGNATURE_NTE : VERSION_SIGNATURE, Language::ENGLISH); cmd.version_signature.encode(this->options.is_nte() ? VERSION_SIGNATURE_NTE : VERSION_SIGNATURE, Language::ENGLISH);
cmd.date_str1.encode(std::format("Card definitions: {:016X}", this->options.card_index->definitions_hash()), Language::ENGLISH); cmd.date_str1.encode(
std::format("Card definitions: {:016X}", this->options.card_index->definitions_hash()),
Language::ENGLISH);
string build_date = phosg::format_time(BUILD_TIMESTAMP); string build_date = phosg::format_time(BUILD_TIMESTAMP);
cmd.date_str2.encode(std::format("newserv {} compiled at {}", GIT_REVISION_HASH, build_date), Language::ENGLISH); cmd.date_str2.encode(std::format("newserv {} compiled at {}", GIT_REVISION_HASH, build_date), Language::ENGLISH);
this->send(cmd); this->send(cmd);
@@ -293,7 +290,8 @@ string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> ma
phosg::StringWriter w; phosg::StringWriter w;
uint32_t subcommand_size = (compressed->size() + sizeof(G_MapData_Ep3_6xB6x41) + 3) & (~3); uint32_t subcommand_size = (compressed->size() + sizeof(G_MapData_Ep3_6xB6x41) + 3) & (~3);
w.put<G_MapData_Ep3_6xB6x41>({{{{0xB6, 0, 0}, subcommand_size}, 0x41, {}}, vm->map->map_number.load(), compressed->size(), 0}); w.put<G_MapData_Ep3_6xB6x41>(
{{{{0xB6, 0, 0}, subcommand_size}, 0x41, {}}, vm->map->map_number.load(), compressed->size(), 0});
w.write(*compressed); w.write(*compressed);
return std::move(w.str()); return std::move(w.str());
} }
@@ -311,7 +309,8 @@ void Server::send_commands_for_joining_spectator(std::shared_ptr<Channel> ch) co
if (this->last_chosen_map) { if (this->last_chosen_map) {
string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, ch->language, this->options.is_nte()); string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, ch->language, this->options.is_nte());
this->log().info_f("Sending {} version of map {:08X}", name_for_language(ch->language), this->last_chosen_map->map_number); this->log().info_f(
"Sending {} version of map {:08X}", name_for_language(ch->language), this->last_chosen_map->map_number);
ch->send(0x6C, 0x00, data); ch->send(0x6C, 0x00, data);
} }
@@ -339,8 +338,8 @@ void Server::send_commands_for_joining_spectator(std::shared_ptr<Channel> ch) co
// (send 6xB4x4F for client_id) // (send 6xB4x4F for client_id)
// } // }
ch->send(0xC9, 0x00, this->prepare_6xB4x07_decks_update()); ch->send(0xC9, 0x00, this->prepare_6xB4x07_decks_update());
// TODO: Sega sends 6xB4x05 here again; why? Is that necessary? They also // TODO: Sega sends 6xB4x05 here again; why? Is that necessary? They also send 6xB4x02 again for each player after
// send 6xB4x02 again for each player after that (but not 6xB4x04) // that (but not 6xB4x04)
ch->send(0xC9, 0x00, this->prepare_6xB4x1C_names_update()); ch->send(0xC9, 0x00, this->prepare_6xB4x1C_names_update());
ch->send(0xC9, 0x00, this->prepare_6xB4x50_trap_tile_locations()); ch->send(0xC9, 0x00, this->prepare_6xB4x50_trap_tile_locations());
{ {
@@ -613,8 +612,8 @@ void Server::force_destroy_field_character(uint8_t client_id, size_t visible_ind
throw runtime_error("player does not exist"); throw runtime_error("player does not exist");
} }
// TODO: Is it possible for there to be gaps in the set cards array? If not, // TODO: Is it possible for there to be gaps in the set cards array? If not, we could just do a direct array lookup
// we could just do a direct array lookup here instead of this loop // here instead of this loop
shared_ptr<Card> set_card = nullptr; shared_ptr<Card> set_card = nullptr;
for (size_t set_index = 0; set_index < 8; set_index++) { for (size_t set_index = 0; set_index < 8; set_index++) {
if (!ps->set_cards[set_index]) { if (!ps->set_cards[set_index]) {
@@ -663,9 +662,7 @@ void Server::check_for_destroyed_cards_and_send_6xB4x05_6xB4x02() {
} }
bool Server::check_presence_entry(uint8_t client_id) const { bool Server::check_presence_entry(uint8_t client_id) const {
return (client_id < 4) return (client_id < 4) ? this->presence_entries[client_id].player_present : false;
? this->presence_entries[client_id].player_present
: false;
} }
void Server::clear_player_flags_after_dice_phase() { void Server::clear_player_flags_after_dice_phase() {
@@ -826,9 +823,8 @@ void Server::draw_phase_after() {
if (this->current_team_turn1 == this->first_team_turn) { if (this->current_team_turn1 == this->first_team_turn) {
if (this->map_and_rules->rules.overall_time_limit > 0) { if (this->map_and_rules->rules.overall_time_limit > 0) {
// Battle time limits are specified in increments of 5 minutes. // Battle time limits are specified in increments of 5 minutes. This part is not based on the original code
// Note: This part is not based on the original code because the timing // because the timing facilities used are different.
// facilities used are different.
uint64_t limit_5mins = this->map_and_rules->rules.overall_time_limit; uint64_t limit_5mins = this->map_and_rules->rules.overall_time_limit;
uint64_t end_usecs = this->battle_start_usecs + (limit_5mins * 300 * 1000 * 1000); uint64_t end_usecs = this->battle_start_usecs + (limit_5mins * 300 * 1000 * 1000);
if (phosg::now() >= end_usecs) { if (phosg::now() >= end_usecs) {
@@ -924,9 +920,8 @@ void Server::end_attack_list_for_client(uint8_t client_id) {
void Server::end_action_phase() { void Server::end_action_phase() {
this->num_pending_attacks = 0; this->num_pending_attacks = 0;
this->unknown_a15 = 1; this->unknown_a15 = 1;
// Annoyingly, this is the original logic. We use an enum because it appears // Annoyingly, this is the original logic. We use an enum because it appears that this can only ever be 0 or 2, but
// that this can only ever be 0 or 2, but we may have to delete the enum if // we may have to delete the enum if that turns out to be false.
// that turns out to be false.
this->action_subphase = static_cast<ActionSubphase>(static_cast<uint8_t>(this->action_subphase) + 2); this->action_subphase = static_cast<ActionSubphase>(static_cast<uint8_t>(this->action_subphase) + 2);
if (this->options.is_nte()) { if (this->options.is_nte()) {
this->unknown_8023EEF4(); this->unknown_8023EEF4();
@@ -1005,8 +1000,7 @@ bool Server::enqueue_attack_or_defense(uint8_t client_id, ActionState* pa) {
card_ps->send_6xB4x04_if_needed(); card_ps->send_6xB4x04_if_needed();
} }
} }
card = this->card_for_set_card_ref(this->send_6xB4x06_if_card_ref_invalid( card = this->card_for_set_card_ref(this->send_6xB4x06_if_card_ref_invalid(pa->original_attacker_card_ref, 2));
pa->original_attacker_card_ref, 2));
if (card) { if (card) {
card = this->card_for_set_card_ref(pa->target_card_refs[0]); card = this->card_for_set_card_ref(pa->target_card_refs[0]);
if (card) { if (card) {
@@ -1100,8 +1094,7 @@ void Server::move_phase_after() {
auto ps = this->player_states[client_id]; auto ps = this->player_states[client_id];
if (ps) { if (ps) {
auto sc_card = ps->get_sc_card(); auto sc_card = ps->get_sc_card();
if (sc_card && (sc_card->card_flags & 0x80) && if (sc_card && (sc_card->card_flags & 0x80) && (sc_card->loc.x == trap_x) && (sc_card->loc.y == trap_y)) {
(sc_card->loc.x == trap_x) && (sc_card->loc.y == trap_y)) {
should_trigger = true; should_trigger = true;
break; break;
} }
@@ -1111,7 +1104,7 @@ void Server::move_phase_after() {
continue; continue;
} }
static const array<vector<uint16_t>, 5> default_trap_card_ids = { static const array<vector<uint16_t>, 5> DEFAULT_TRAP_CARD_IDS = {
// Red: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace // Red: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace
vector<uint16_t>{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C}, vector<uint16_t>{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C},
// Blue: Gold Rush, Charity, Requiem // Blue: Gold Rush, Charity, Requiem
@@ -1125,7 +1118,7 @@ void Server::move_phase_after() {
const vector<uint16_t>* trap_card_ids = &this->options.trap_card_ids.at(trap_type); const vector<uint16_t>* trap_card_ids = &this->options.trap_card_ids.at(trap_type);
if (trap_card_ids->empty()) { if (trap_card_ids->empty()) {
trap_card_ids = &default_trap_card_ids.at(trap_type); trap_card_ids = &DEFAULT_TRAP_CARD_IDS.at(trap_type);
} }
// This is the original implementation. We do something smarter instead. // This is the original implementation. We do something smarter instead.
@@ -1145,9 +1138,7 @@ void Server::move_phase_after() {
auto ps = this->player_states[client_id]; auto ps = this->player_states[client_id];
if (ps) { if (ps) {
auto sc_card = ps->get_sc_card(); auto sc_card = ps->get_sc_card();
if (sc_card && if (sc_card && (abs(sc_card->loc.x - trap_x) < 2) && (abs(sc_card->loc.y - trap_y) < 2) &&
(abs(sc_card->loc.x - trap_x) < 2) &&
(abs(sc_card->loc.y - trap_y) < 2) &&
ps->replace_assist_card_by_id(trap_card_id)) { ps->replace_assist_card_by_id(trap_card_id)) {
G_EnqueueAnimation_Ep3_6xB4x2C cmd; G_EnqueueAnimation_Ep3_6xB4x2C cmd;
cmd.change_type = 0x01; cmd.change_type = 0x01;
@@ -1173,14 +1164,12 @@ void Server::move_phase_after() {
// this->chosen_trap_tile_index_of_type[trap_type] = new_index; // this->chosen_trap_tile_index_of_type[trap_type] = new_index;
// this->send_6xB4x50(); // this->send_6xB4x50();
// } // }
// We instead use an implementation that consumes a constant amount of // We instead use an implementation that consumes a constant amount of randomness per pass.
// randomness per pass.
if (this->num_trap_tiles_of_type[trap_type] == 2) { if (this->num_trap_tiles_of_type[trap_type] == 2) {
this->chosen_trap_tile_index_of_type[trap_type] ^= 1; this->chosen_trap_tile_index_of_type[trap_type] ^= 1;
this->send_6xB4x50_trap_tile_locations(); this->send_6xB4x50_trap_tile_locations();
} else if (this->num_trap_tiles_of_type[trap_type] > 2) { } else if (this->num_trap_tiles_of_type[trap_type] > 2) {
// Generate a new random index, but forbid it from matching the existing // Generate a new random index, but forbid it from matching the existing index
// index
uint8_t new_index = this->get_random(this->num_trap_tiles_of_type[trap_type] - 1); uint8_t new_index = this->get_random(this->num_trap_tiles_of_type[trap_type] - 1);
if (new_index >= this->chosen_trap_tile_index_of_type[trap_type]) { if (new_index >= this->chosen_trap_tile_index_of_type[trap_type]) {
new_index++; new_index++;
@@ -1249,8 +1238,7 @@ int8_t Server::send_6xB4x33_remove_ally_atk_if_needed(const ActionState& pa) {
for (size_t z = 0; z < 4; z++) { for (size_t z = 0; z < 4; z++) {
auto ally_ps = this->get_player_state(z); auto ally_ps = this->get_player_state(z);
if ((z != setter_client_id) && ally_ps) { if ((z != setter_client_id) && ally_ps) {
if ((ally_ps->get_team_id() == setter_ps->get_team_id()) && if ((ally_ps->get_team_id() == setter_ps->get_team_id()) && (ally_ps->get_atk_points() >= ally_cost)) {
(ally_ps->get_atk_points() >= ally_cost)) {
ally_has_sufficient_atk = true; ally_has_sufficient_atk = true;
} }
} }
@@ -1376,11 +1364,10 @@ void Server::set_client_id_ready_to_advance_phase(uint8_t client_id, BattlePhase
ps->assist_flags |= AssistFlag::ELIGIBLE_FOR_DICE_BOOST; ps->assist_flags |= AssistFlag::ELIGIBLE_FOR_DICE_BOOST;
} }
} else { } else {
// TODO: It'd be nice to do this in a constant-randomness way, but I'm // TODO: It'd be nice to do this in a constant-randomness way, but I'm lazy, and this matches Sega's original
// lazy, and this matches Sega's original implementation. The less-lazy // implementation. The less-lazy way to do it would be to roll three dice: one in the range [1, 2] to decide
// way to do it would be to roll three dice: one in the range [1, N], // which of ATK or DEF will be boosted, then roll the ATK die in range [1, N] (or [3, N] if it's boosted), and
// one in the range [3, N], and one in the range [1, 2] to decide // do the same for the DEF die.
// whether to swap the first two results.
for (size_t z = 0; z < 200; z++) { for (size_t z = 0; z < 200; z++) {
ps->roll_main_dice_or_apply_after_effects(); ps->roll_main_dice_or_apply_after_effects();
if ((ps->get_atk_points() >= 3) || (ps->get_def_points() >= 3)) { if ((ps->get_atk_points() >= 3) || (ps->get_def_points() >= 3)) {
@@ -1431,12 +1418,14 @@ void Server::set_phase_after() {
if (ps) { if (ps) {
auto card = ps->get_sc_card(); auto card = ps->get_sc_card();
if (card) { if (card) {
this->card_special->apply_action_conditions(EffectWhen::AFTER_SET_PHASE, nullptr, card, is_nte ? 0x1F : 0x04, nullptr); this->card_special->apply_action_conditions(
EffectWhen::AFTER_SET_PHASE, nullptr, card, is_nte ? 0x1F : 0x04, nullptr);
} }
for (size_t set_index = 0; set_index < 8; set_index++) { for (size_t set_index = 0; set_index < 8; set_index++) {
auto card = ps->get_set_card(set_index); auto card = ps->get_set_card(set_index);
if (card) { if (card) {
this->card_special->apply_action_conditions(EffectWhen::AFTER_SET_PHASE, nullptr, card, is_nte ? 0x1F : 0x04, nullptr); this->card_special->apply_action_conditions(
EffectWhen::AFTER_SET_PHASE, nullptr, card, is_nte ? 0x1F : 0x04, nullptr);
} }
} }
} }
@@ -1494,9 +1483,7 @@ void Server::set_phase_after() {
for (size_t client_id = 0; client_id < 4; client_id++) { for (size_t client_id = 0; client_id < 4; client_id++) {
auto ps = this->player_states[client_id]; auto ps = this->player_states[client_id];
if (ps && if (ps && (ps->get_assist_turns_remaining() == 90) && (ps->assist_delay_turns < 1)) {
(ps->get_assist_turns_remaining() == 90) &&
(ps->assist_delay_turns < 1)) {
ps->discard_set_assist_card(); ps->discard_set_assist_card();
ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
} }
@@ -1630,8 +1617,7 @@ void Server::setup_and_start_battle() {
size_t num_trap_tiles = 0; size_t num_trap_tiles = 0;
for (size_t y = 0; y < 0x10; y++) { for (size_t y = 0; y < 0x10; y++) {
for (size_t x = 0; x < 0x10; x++) { for (size_t x = 0; x < 0x10; x++) {
if ((this->overlay_state.tiles[y][x] == (trap_type | 0x40)) && if ((this->overlay_state.tiles[y][x] == (trap_type | 0x40)) && (num_trap_tiles < 8)) {
(num_trap_tiles < 8)) {
this->trap_tile_locs[trap_type][num_trap_tiles][0] = x; this->trap_tile_locs[trap_type][num_trap_tiles][0] = x;
this->trap_tile_locs[trap_type][num_trap_tiles][1] = y; this->trap_tile_locs[trap_type][num_trap_tiles][1] = y;
num_trap_tiles++; num_trap_tiles++;
@@ -1639,7 +1625,6 @@ void Server::setup_and_start_battle() {
} }
} }
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) { if (num_trap_tiles > 0) {
this->chosen_trap_tile_index_of_type[trap_type] = this->get_random(num_trap_tiles); this->chosen_trap_tile_index_of_type[trap_type] = this->get_random(num_trap_tiles);
} }
@@ -1684,8 +1669,7 @@ void Server::setup_and_start_battle() {
this->send_6xB4x46(); this->send_6xB4x46();
// Re-send game metadata to spectator teams, since loading the battle scene // Re-send game metadata to spectator teams, since loading the battle scene seems to delete it
// seems to delete it
auto l = this->lobby.lock(); auto l = this->lobby.lock();
if (l) { if (l) {
send_ep3_update_game_metadata(l); send_ep3_update_game_metadata(l);
@@ -1709,11 +1693,7 @@ G_SetStateFlags_Ep3_6xB4x03 Server::prepare_6xB4x03() const {
cmd.state.tournament_flag = this->options.tournament ? 1 : 0; cmd.state.tournament_flag = this->options.tournament ? 1 : 0;
for (size_t z = 0; z < 4; z++) { for (size_t z = 0; z < 4; z++) {
auto ps = this->player_states[z]; auto ps = this->player_states[z];
if (!ps) { cmd.state.client_sc_card_types[z] = ps ? ps->get_sc_card_type() : CardType::INVALID_FF;
cmd.state.client_sc_card_types[z] = CardType::INVALID_FF;
} else {
cmd.state.client_sc_card_types[z] = ps->get_sc_card_type();
}
} }
return cmd; return cmd;
} }
@@ -1726,9 +1706,8 @@ void Server::update_battle_state_flags_and_send_6xB4x03_if_needed(bool always_se
} }
} }
// Returns true if the battle can begin
bool Server::update_registration_phase() { bool Server::update_registration_phase() {
// Returns true if the battle can begin
auto log = this->log_stack("update_registration_phase: "); auto log = this->log_stack("update_registration_phase: ");
if (this->setup_phase != SetupPhase::REGISTRATION) { if (this->setup_phase != SetupPhase::REGISTRATION) {
@@ -1801,7 +1780,8 @@ void Server::on_server_data_input(shared_ptr<Client> sender_c, const string& dat
size_t expected_size = header.size * 4; size_t expected_size = header.size * 4;
if (expected_size < data.size()) { if (expected_size < data.size()) {
phosg::print_data(stderr, data); phosg::print_data(stderr, data);
throw runtime_error(std::format("command is incomplete: expected {:X} bytes, received {:X} bytes", expected_size, data.size())); throw runtime_error(std::format(
"command is incomplete: expected {:X} bytes, received {:X} bytes", expected_size, data.size()));
} }
if (header.subcommand != 0xB3) { if (header.subcommand != 0xB3) {
throw runtime_error("server data command is not 6xB3"); throw runtime_error("server data command is not 6xB3");
@@ -1867,8 +1847,7 @@ void Server::handle_CAx0C_end_redraw_initial_hand_phase(shared_ptr<Client>, cons
} }
int32_t error_code = 0; int32_t error_code = 0;
if ((this->setup_phase != SetupPhase::HAND_REDRAW_OPTION) && if ((this->setup_phase != SetupPhase::HAND_REDRAW_OPTION) && (this->setup_phase != SetupPhase::STARTER_ROLLS)) {
(this->setup_phase != SetupPhase::STARTER_ROLLS)) {
error_code = -0x5D; error_code = -0x5D;
} }
@@ -2138,15 +2117,14 @@ void Server::handle_CAx13_update_map_during_setup_t(shared_ptr<Client> c, const
(this->registration_phase != RegistrationPhase::REGISTERED) && (this->registration_phase != RegistrationPhase::REGISTERED) &&
(this->registration_phase != RegistrationPhase::BATTLE_STARTED)) { (this->registration_phase != RegistrationPhase::BATTLE_STARTED)) {
*this->map_and_rules = in_cmd.map_and_rules_state; *this->map_and_rules = in_cmd.map_and_rules_state;
// The client will likely send incorrect values for the extended rules (or // The client will likely send incorrect values for the extended rules (or in the case of NTE, no values at all,
// in the case of NTE, no values at all, since the Rules structure is // since the Rules structure is smaller). So, use the values from the last chosen map if applicable, or the values
// smaller). So, use the values from the last chosen map if applicable, or // from the $dicerange command if available.
// the values from the $dicerange command if available.
Language language = c ? c->language() : Language::ENGLISH; Language language = c ? c->language() : Language::ENGLISH;
const Rules* map_rules = this->last_chosen_map ? &this->last_chosen_map->version(language)->map->default_rules : nullptr; const Rules* map_rules = this->last_chosen_map ? &this->last_chosen_map->version(language)->map->default_rules : nullptr;
auto& server_rules = this->map_and_rules->rules; auto& server_rules = this->map_and_rules->rules;
// NTE can specify the DEF dice value range in its Rules struct, so we use // NTE can specify the DEF dice value range in its Rules struct, so we use that unless the map or $dicerange
// that unless the map or $dicerange overrides it. // overrides it.
server_rules.def_dice_value_range = (map_rules && (map_rules->def_dice_value_range != 0xFF)) server_rules.def_dice_value_range = (map_rules && (map_rules->def_dice_value_range != 0xFF))
? map_rules->def_dice_value_range ? map_rules->def_dice_value_range
: (this->def_dice_value_range_override != 0xFF) : (this->def_dice_value_range_override != 0xFF)
@@ -2165,8 +2143,7 @@ void Server::handle_CAx13_update_map_during_setup_t(shared_ptr<Client> c, const
? this->def_dice_value_range_2v1_override ? this->def_dice_value_range_2v1_override
: 0; : 0;
// If this match is part of a tournament, ignore the rules sent by the // If this match is part of a tournament, ignore the rules sent by the client and use the tournament rules instead.
// client and use the tournament rules instead.
if (this->options.tournament) { if (this->options.tournament) {
this->map_and_rules->rules = this->options.tournament->get_rules(); this->map_and_rules->rules = this->options.tournament->get_rules();
} }
@@ -2248,10 +2225,9 @@ void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr<Client>, con
const auto& in_cmd = check_size_t<G_HardResetServerState_Ep3_CAx15>(data); const auto& in_cmd = check_size_t<G_HardResetServerState_Ep3_CAx15>(data);
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "HARD RESET"); this->send_debug_command_received_message(in_cmd.header.subsubcommand, "HARD RESET");
// In the original implementation, this command recreates the server object. // In the original implementation, this command recreates the server object. This is possible because the dispatch
// This is possible because the dispatch function is not part of the server // function is not part of the server object in the original implementation; however, in our implementation, it is,
// object in the original implementation; however, in our implementation, it // so we don't support this. The original implementation did this:
// is, so we don't support this. The original implementation did this:
// this->base()->recreate_server(); // Destroys *this, which we can't do // this->base()->recreate_server(); // Destroys *this, which we can't do
// root_card_server = this->server; // root_card_server = this->server;
// *this->map_and_rules = *this->initial_map_and_rules; // *this->map_and_rules = *this->initial_map_and_rules;
@@ -2271,8 +2247,8 @@ void Server::handle_CAx1B_update_player_name(shared_ptr<Client>, const string& d
this->name_entries_valid[in_cmd.entry.client_id] = false; this->name_entries_valid[in_cmd.entry.client_id] = false;
} }
// Note: This check is not part of the original code. This replaces a // Note: This check is not part of the original code. This replaces a disconnecting player with a CPU if the battle
// disconnecting player with a CPU if the battle is in progress. // is in progress.
auto l = this->lobby.lock(); auto l = this->lobby.lock();
if (l && !l->clients[in_cmd.entry.client_id]) { if (l && !l->clients[in_cmd.entry.client_id]) {
this->name_entries[in_cmd.entry.client_id].is_cpu_player = 1; this->name_entries[in_cmd.entry.client_id].is_cpu_player = 1;
@@ -2325,8 +2301,8 @@ void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
auto l = this->lobby.lock(); auto l = this->lobby.lock();
if (l) { if (l) {
// Note: Sega's implementation doesn't set EX results values here; they // Note: Sega's implementation doesn't set EX results values here; they did it at game join time instead. We do
// did it at game join time instead. We do it here for code simplicity. // it here for code simplicity.
if (!this->options.is_nte() && l->ep3_ex_result_values) { if (!this->options.is_nte() && l->ep3_ex_result_values) {
this->send(*l->ep3_ex_result_values); this->send(*l->ep3_ex_result_values);
} }
@@ -2370,8 +2346,7 @@ void Server::handle_CAx28_end_defense_list(shared_ptr<Client>, const string& dat
for (size_t z = 0; z < 4; z++) { for (size_t z = 0; z < 4; z++) {
auto ps = this->player_states[z]; auto ps = this->player_states[z];
if (ps && (this->current_team_turn1 != ps->get_team_id())) { if (ps && (this->current_team_turn1 != ps->get_team_id())) {
if (!ps->get_sc_card()->check_card_flag(2) && if (!ps->get_sc_card()->check_card_flag(2) && (this->defense_list_ended_for_client[z] == 0)) {
(this->defense_list_ended_for_client[z] == 0)) {
all_defense_lists_ended = false; all_defense_lists_ended = false;
break; break;
} }
@@ -2517,8 +2492,7 @@ void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(shared
void Server::handle_CAx3A_time_limit_expired(shared_ptr<Client>, const string& data) { void Server::handle_CAx3A_time_limit_expired(shared_ptr<Client>, const string& data) {
const auto& in_cmd = check_size_t<G_OverallTimeLimitExpired_Ep3_CAx3A>(data); const auto& in_cmd = check_size_t<G_OverallTimeLimitExpired_Ep3_CAx3A>(data);
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "TIME EXPIRED"); this->send_debug_command_received_message(in_cmd.header.subsubcommand, "TIME EXPIRED");
// We don't need to do anything here because the overall time limit is tracked // We don't need to do anything here because the overall time limit is tracked server-side instead.
// server-side instead.
} }
void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const string& data) { void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const string& data) {
@@ -2579,9 +2553,8 @@ void Server::send_6xB6x41_to_all_clients() const {
} }
if (this->battle_record && this->battle_record->writable()) { if (this->battle_record && this->battle_record->writable()) {
// TODO: It's not great that we just pick the first one; ideally we'd put // TODO: It's not great that we just pick the first one; ideally we'd put all of them in the recording and send
// all of them in the recording and send the appropriate one to the client // the appropriate one to the client in the playback lobby
// in the playback lobby
for (string& data : map_commands_by_language) { for (string& data : map_commands_by_language) {
if (!data.empty()) { if (!data.empty()) {
this->battle_record->add_command(BattleRecord::Event::Type::BATTLE_COMMAND, std::move(data)); this->battle_record->add_command(BattleRecord::Event::Type::BATTLE_COMMAND, std::move(data));
@@ -2626,8 +2599,7 @@ void Server::handle_CAx49_card_counts(shared_ptr<Client>, const string& data) {
const auto& in_cmd = check_size_t<G_CardCounts_Ep3_CAx49>(data); const auto& in_cmd = check_size_t<G_CardCounts_Ep3_CAx49>(data);
this->send_debug_command_received_message(in_cmd.header.sender_client_id, in_cmd.header.subsubcommand, "CARD COUNTS"); this->send_debug_command_received_message(in_cmd.header.sender_client_id, in_cmd.header.subsubcommand, "CARD COUNTS");
// Note: Sega's implmentation completely ignores this command. This // Note: Sega's implmentation completely ignores this command. This implementation is not based on the original code.
// implementation is not based on the original code.
auto& dest_counts = this->client_card_counts[in_cmd.header.sender_client_id]; auto& dest_counts = this->client_card_counts[in_cmd.header.sender_client_id];
dest_counts = in_cmd.card_id_to_count; dest_counts = in_cmd.card_id_to_count;
decrypt_trivial_gci_data(dest_counts.data(), dest_counts.bytes(), in_cmd.basis); decrypt_trivial_gci_data(dest_counts.data(), dest_counts.bytes(), in_cmd.basis);
@@ -2884,10 +2856,9 @@ void Server::execute_bomb_assist_effect() {
for (size_t client_id = 0; client_id < 4; client_id++) { for (size_t client_id = 0; client_id < 4; client_id++) {
auto ps = this->player_states[client_id]; auto ps = this->player_states[client_id];
// Possible bug: shouldn't we check should_block_assist_effects_for_client // Possible bug: shouldn't we check should_block_assist_effects_for_client here too? If the player has a card with
// here too? If the player has a card with the same HP as another one that // the same HP as another one that would be destroyed, it looks like the card can be destroyed even if the client
// would be destroyed, it looks like the card can be destroyed even if the // should be immune to assist effects here.
// client should be immune to assist effects here.
if (ps) { if (ps) {
for (size_t set_index = 0; set_index < 8; set_index++) { for (size_t set_index = 0; set_index < 8; set_index++) {
auto card = ps->get_set_card(set_index); auto card = ps->get_set_card(set_index);
@@ -2917,10 +2888,7 @@ void Server::replace_targets_due_to_destruction_nte(ActionState* as) {
shared_ptr<Card> found_guard_item; shared_ptr<Card> found_guard_item;
for (size_t z = 0; z < 8; z++) { for (size_t z = 0; z < 8; z++) {
auto set_card = ps->get_set_card(z); auto set_card = ps->get_set_card(z);
if (set_card && if (set_card && (set_card != target_card) && !(set_card->card_flags & 2) && set_card->is_guard_item()) {
(set_card != target_card) &&
!(set_card->card_flags & 2) &&
set_card->is_guard_item()) {
found_guard_item = set_card; found_guard_item = set_card;
break; break;
} }
@@ -3039,8 +3007,8 @@ void Server::replace_targets_due_to_destruction_or_conditions(ActionState* as) {
} }
} }
// Note: The original code only writes a single FFFF after the last card ref // Note: The original code only writes a single FFFF after the last card ref in this array; we instead clear the
// in this array; we instead clear the entire array. // entire array.
as->target_card_refs.clear(0xFFFF); as->target_card_refs.clear(0xFFFF);
for (size_t z = 0; z < phase1_replaced_card_refs.size(); z++) { for (size_t z = 0; z < phase1_replaced_card_refs.size(); z++) {
as->target_card_refs[z] = this->send_6xB4x06_if_card_ref_invalid(phase1_replaced_card_refs[z], 4); as->target_card_refs[z] = this->send_6xB4x06_if_card_ref_invalid(phase1_replaced_card_refs[z], 4);
@@ -3062,8 +3030,7 @@ void Server::replace_targets_due_to_destruction_or_conditions(ActionState* as) {
} }
} }
// Note: This is different from the original code in the same way as above: we // Note: This is different from the original code in the same way as above: we clear the entire array first.
// clear the entire array first.
as->target_card_refs.clear(0xFFFF); as->target_card_refs.clear(0xFFFF);
for (size_t z = 0; z < phase2_replaced_card_refs.size(); z++) { for (size_t z = 0; z < phase2_replaced_card_refs.size(); z++) {
as->target_card_refs[z] = this->send_6xB4x06_if_card_ref_invalid(phase2_replaced_card_refs[z], 4); as->target_card_refs[z] = this->send_6xB4x06_if_card_ref_invalid(phase2_replaced_card_refs[z], 4);
@@ -3148,8 +3115,7 @@ void Server::unknown_802402F4() {
} }
} }
vector<shared_ptr<Card>> Server::const_cast_set_cards_v( vector<shared_ptr<Card>> Server::const_cast_set_cards_v(const vector<shared_ptr<const Card>>& cards) {
const vector<shared_ptr<const Card>>& cards) {
// TODO: This is dumb. Figure out a not-dumb way to do this. // TODO: This is dumb. Figure out a not-dumb way to do this.
vector<shared_ptr<Card>> ret; vector<shared_ptr<Card>> ret;
for (auto const_card : cards) { for (auto const_card : cards) {
+42 -52
View File
@@ -20,53 +20,43 @@ struct Lobby;
namespace Episode3 { namespace Episode3 {
/** // This implementation of Episode 3 battles is derived from Sega's original server implementation, reverse-engineered
* This implementation of Episode 3 battles is derived from Sega's original // from the Episode 3 client executable. The control flow, function breakdown, and structure definitions in these files
* server implementation, reverse-engineered from the Episode 3 client // map very closely to how their server implementation was written; notable differences (due to necessary environment
* executable. The control flow, function breakdown, and structure definitions // differences or bug fixes) are described in the comments therein. There are likely undiscovered bugs in this code,
* in these files map very closely to how their server implementation was // some originally written by Sega, but more written by me as I manually transcribed and updated this code.
* written; notable differences (due to necessary environment differences or bug
* fixes) are described in the comments therein. // The following files are direct reverse-engineerings of Sega's original code, except where noted in the comments:
* // AssistServer.hh/cc
* The following files are direct reverse-engineerings of Sega's original code, // Card.hh/cc
* except where noted in the comments: // CardSpecial.hh/cc
* AssistServer.hh/cc // DeckState.hh/cc
* Card.hh/cc // MapState.hh/cc
* CardSpecial.hh/cc // PlayerState.hh/cc
* DeckState.hh/cc // PlayerStateSubordinates.hh/cc
* MapState.hh/cc // RulerServer.hh/cc
* PlayerState.hh/cc // Server.hh/cc
* PlayerStateSubordinates.hh/cc
* RulerServer.hh/cc // Class ownership levels (classes may contain weak_ptrs but not shared_ptrs to classes at the same or higher level):
* Server.hh/cc // - Server
* // - - RulerServer
* There are likely undiscovered bugs in this code, some originally written by // - - - AssistServer
* Sega, but more written by me as I manually transcribed and updated this code. // - - - CardSpecial
* // - - - - StateFlags
* Class ownership levels (classes may only contain weak_ptrs, not shared_ptrs, // - - - - DeckEntry
* to classes at the same or higher level): // - - - - PlayerState
* - Server // - - - - - Card
* - - RulerServer // - - - - - - CardShortStatus
* - - - AssistServer // - - - - - - DeckState
* - - - CardSpecial // - - - - - - HandAndEquipState
* - - - - StateFlags // - - - - - - MapAndRulesState / OverlayState
* - - - - DeckEntry // - - - - - - - Everything within DataIndexes
* - - - - PlayerState
* - - - - - Card
* - - - - - - CardShortStatus
* - - - - - - DeckState
* - - - - - - HandAndEquipState
* - - - - - - MapAndRulesState / OverlayState
* - - - - - - - Everything within DataIndexes
*/
class Server : public std::enable_shared_from_this<Server> { class Server : public std::enable_shared_from_this<Server> {
// In the original code, there is a TCardServerBase class and a TCardServer // In the original code, there is a TCardServerBase class and a TCardServer class, with the former containing some
// class, with the former containing some basic parts of the game state and // basic parts of the game state and a pointer to the latter. It seems these two classes exist (instead of one big
// a pointer to the latter. It seems these two classes exist (instead of one // class) so that the force reset command could be implemented; however, it appears that that command is never sent
// big class) so that the force reset command could be implemented; however, // by the client, so we combine the two classes into one in our implementation.
// it appears that that command is never sent by the client, so we combine
// the two classes into one in our implementation.
public: public:
struct Options { struct Options {
std::shared_ptr<const CardIndex> card_index; std::shared_ptr<const CardIndex> card_index;
@@ -241,7 +231,8 @@ public:
void handle_CAx28_end_defense_list(std::shared_ptr<Client> sender_c, const std::string& data); void handle_CAx28_end_defense_list(std::shared_ptr<Client> sender_c, const std::string& data);
void handle_CAx2B_legacy_set_card(std::shared_ptr<Client> sender_c, const std::string&); void handle_CAx2B_legacy_set_card(std::shared_ptr<Client> sender_c, const std::string&);
void handle_CAx34_subtract_ally_atk_points(std::shared_ptr<Client> sender_c, const std::string& data); void handle_CAx34_subtract_ally_atk_points(std::shared_ptr<Client> sender_c, const std::string& data);
void handle_CAx37_client_ready_to_advance_from_starter_roll_phase(std::shared_ptr<Client> sender_c, const std::string& data); void handle_CAx37_client_ready_to_advance_from_starter_roll_phase(
std::shared_ptr<Client> sender_c, const std::string& data);
void handle_CAx3A_time_limit_expired(std::shared_ptr<Client> sender_c, const std::string& data); void handle_CAx3A_time_limit_expired(std::shared_ptr<Client> sender_c, const std::string& data);
void handle_CAx40_map_list_request(std::shared_ptr<Client> sender_c, const std::string& data); void handle_CAx40_map_list_request(std::shared_ptr<Client> sender_c, const std::string& data);
void handle_CAx41_map_request(std::shared_ptr<Client> sender_c, const std::string& data); void handle_CAx41_map_request(std::shared_ptr<Client> sender_c, const std::string& data);
@@ -266,12 +257,12 @@ public:
G_UpdateDecks_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const; G_UpdateDecks_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const;
G_SetPlayerNames_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const; G_SetPlayerNames_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const;
static std::string prepare_6xB6x41_map_definition(std::shared_ptr<const MapIndex::Map> map, Language language, bool is_nte); static std::string prepare_6xB6x41_map_definition(
std::shared_ptr<const MapIndex::Map> map, Language language, bool is_nte);
void send_6xB6x41_to_all_clients() const; void send_6xB6x41_to_all_clients() const;
G_SetTrapTileLocations_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const; G_SetTrapTileLocations_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const;
std::vector<std::shared_ptr<Card>> const_cast_set_cards_v( std::vector<std::shared_ptr<Card>> const_cast_set_cards_v(const std::vector<std::shared_ptr<const Card>>& cards);
const std::vector<std::shared_ptr<const Card>>& cards);
private: private:
typedef void (Server::*handler_t)(std::shared_ptr<Client>, const std::string&); typedef void (Server::*handler_t)(std::shared_ptr<Client>, const std::string&);
@@ -326,9 +317,8 @@ public:
parray<uint8_t, 4> player_ready_to_end_phase; parray<uint8_t, 4> player_ready_to_end_phase;
uint32_t unknown_a10; uint32_t unknown_a10;
uint32_t overall_time_expired; uint32_t overall_time_expired;
// Note: In the original implementation, this is a uint32_t and is measured in // Note: In the original implementation, this is a uint32_t and is measured in seconds. In our environment, the
// seconds. In our environment, the simplest implementation uses now(), which // simplest implementation uses now(), which returns microseconds, so we use a uint64_t instead.
// returns microseconds, so we use a uint64_t instead.
uint64_t battle_start_usecs; uint64_t battle_start_usecs;
uint32_t should_copy_prev_states_to_current_states; uint32_t should_copy_prev_states_to_current_states;
std::shared_ptr<CardSpecial> card_special; std::shared_ptr<CardSpecial> card_special;
+38 -73
View File
@@ -67,10 +67,7 @@ string Tournament::Team::str() const {
return ret + "]"; return ret + "]";
} }
void Tournament::Team::register_player( void Tournament::Team::register_player(shared_ptr<Client> c, const string& team_name, const string& password) {
shared_ptr<Client> c,
const string& team_name,
const string& password) {
if (this->players.size() >= this->max_players) { if (this->players.size() >= this->max_players) {
throw runtime_error("team is full"); throw runtime_error("team is full");
} }
@@ -104,8 +101,7 @@ void Tournament::Team::register_player(
bool Tournament::Team::unregister_player(uint32_t account_id) { bool Tournament::Team::unregister_player(uint32_t account_id) {
size_t index; size_t index;
for (index = 0; index < this->players.size(); index++) { for (index = 0; index < this->players.size(); index++) {
if (this->players[index].is_human() && if (this->players[index].is_human() && (this->players[index].account_id == account_id)) {
(this->players[index].account_id == account_id)) {
break; break;
} }
} }
@@ -123,12 +119,10 @@ bool Tournament::Team::unregister_player(uint32_t account_id) {
return false; return false;
} }
// If the tournament has already started, make the team forfeit their game. // If the tournament has already started, make the team forfeit their game. If any player withdraws from a team
// If any player withdraws from a team after the registration phase, the // after the registration phase, the entire team essentially forfeits their entry.
// entire team essentially forfeits their entry.
if (tournament->get_state() != Tournament::State::REGISTRATION) { if (tournament->get_state() != Tournament::State::REGISTRATION) {
// Look through the pending matches to see if this team is involved in any // Look through the pending matches to see if this team is involved in any of them
// of them
for (auto match : tournament->pending_matches) { for (auto match : tournament->pending_matches) {
if (!match->preceding_a || !match->preceding_b) { if (!match->preceding_a || !match->preceding_b) {
throw logic_error("zero-round match is pending after tournament registration phase"); throw logic_error("zero-round match is pending after tournament registration phase");
@@ -142,9 +136,8 @@ bool Tournament::Team::unregister_player(uint32_t account_id) {
} }
} }
// If the tournament has not started yet, just remove the player from the
// team
} else { } else {
// If the tournament has not started yet, just remove the player from the team
if (!tournament->all_player_account_ids.erase(account_id)) { if (!tournament->all_player_account_ids.erase(account_id)) {
throw logic_error("player removed from team but not from tournament"); throw logic_error("player removed from team but not from tournament");
} }
@@ -183,9 +176,7 @@ size_t Tournament::Team::num_com_players() const {
} }
Tournament::Match::Match( Tournament::Match::Match(
shared_ptr<Tournament> tournament, shared_ptr<Tournament> tournament, shared_ptr<Match> preceding_a, shared_ptr<Match> preceding_b)
shared_ptr<Match> preceding_a,
shared_ptr<Match> preceding_b)
: tournament(tournament), : tournament(tournament),
preceding_a(preceding_a), preceding_a(preceding_a),
preceding_b(preceding_b), preceding_b(preceding_b),
@@ -197,9 +188,7 @@ Tournament::Match::Match(
this->round_num = this->preceding_a->round_num + 1; this->round_num = this->preceding_a->round_num + 1;
} }
Tournament::Match::Match( Tournament::Match::Match(shared_ptr<Tournament> tournament, shared_ptr<Team> winner_team)
shared_ptr<Tournament> tournament,
shared_ptr<Team> winner_team)
: tournament(tournament), : tournament(tournament),
preceding_a(nullptr), preceding_a(nullptr),
preceding_b(nullptr), preceding_b(nullptr),
@@ -228,9 +217,8 @@ bool Tournament::Match::resolve_if_skippable() {
this->set_winner_team(winner_a->players.empty() ? winner_b : winner_a); this->set_winner_team(winner_a->players.empty() ? winner_b : winner_a);
return true; return true;
} }
// If neither preceding winner team has any humans on it, skip this match // If neither preceding winner team has any humans on it, skip this match entirely and just make one team advance
// entirely and just make one team advance arbitrarily (note that this also // arbitrarily (note that this also handles the case where both preceding winner teams are empty)
// handles the case where both preceding winner teams are empty)
if (!winner_a->has_any_human_players() && !winner_b->has_any_human_players()) { if (!winner_a->has_any_human_players() && !winner_b->has_any_human_players()) {
this->set_winner_team((phosg::random_object<uint8_t>() & 1) ? winner_b : winner_a); this->set_winner_team((phosg::random_object<uint8_t>() & 1) ? winner_b : winner_a);
return true; return true;
@@ -247,8 +235,8 @@ void Tournament::Match::on_winner_team_set() {
tournament->pending_matches.erase(this->shared_from_this()); tournament->pending_matches.erase(this->shared_from_this());
// Resolve the following match if possible (this skips CPU-only matches). If // Resolve the following match if possible (this skips CPU-only matches). If the following match can't be resolved,
// the following match can't be resolved, mark it pending. // mark it pending.
auto following = this->following.lock(); auto following = this->following.lock();
if (following && !following->resolve_if_skippable()) { if (following && !following->resolve_if_skippable()) {
tournament->pending_matches.emplace(following); tournament->pending_matches.emplace(following);
@@ -259,8 +247,8 @@ void Tournament::Match::on_winner_team_set() {
tournament->current_state = Tournament::State::COMPLETE; tournament->current_state = Tournament::State::COMPLETE;
} }
// Unlink the losing team's players (if any) - this allows them to enter // Unlink the losing team's players (if any) - this allows them to enter another tournament before this tournament
// another tournament before this tournament has ended // has ended
if (this->preceding_a && this->preceding_b) { if (this->preceding_a && this->preceding_b) {
auto losing_team = (this->winner_team == this->preceding_a->winner_team) auto losing_team = (this->winner_team == this->preceding_a->winner_team)
? this->preceding_b->winner_team ? this->preceding_b->winner_team
@@ -278,8 +266,7 @@ void Tournament::Match::set_winner_team_without_triggers(shared_ptr<Team> team)
if (!this->preceding_a || !this->preceding_b) { if (!this->preceding_a || !this->preceding_b) {
throw logic_error("set_winner_team called on zero-round match"); throw logic_error("set_winner_team called on zero-round match");
} }
if ((team != this->preceding_a->winner_team) && if ((team != this->preceding_a->winner_team) && (team != this->preceding_b->winner_team)) {
(team != this->preceding_b->winner_team)) {
throw logic_error("winner team did not participate in match"); throw logic_error("winner team did not participate in match");
} }
@@ -298,8 +285,7 @@ void Tournament::Match::set_winner_team(shared_ptr<Team> team) {
this->on_winner_team_set(); this->on_winner_team_set();
} }
shared_ptr<Tournament::Team> Tournament::Match::opponent_team_for_team( shared_ptr<Tournament::Team> Tournament::Match::opponent_team_for_team(shared_ptr<Team> team) const {
shared_ptr<Team> team) const {
if (!this->preceding_a || !this->preceding_b) { if (!this->preceding_a || !this->preceding_b) {
throw logic_error("zero-round matches do not have opponents"); throw logic_error("zero-round matches do not have opponents");
} }
@@ -342,9 +328,7 @@ Tournament::Tournament(
} }
Tournament::Tournament( Tournament::Tournament(
shared_ptr<const MapIndex> map_index, shared_ptr<const MapIndex> map_index, shared_ptr<const COMDeckIndex> com_deck_index, const phosg::JSON& json)
shared_ptr<const COMDeckIndex> com_deck_index,
const phosg::JSON& json)
: log(std::format("[Tournament:{}] ", json.get_string("name"))), : log(std::format("[Tournament:{}] ", json.get_string("name"))),
map_index(map_index), map_index(map_index),
com_deck_index(com_deck_index), com_deck_index(com_deck_index),
@@ -394,8 +378,7 @@ void Tournament::init() {
} else { } else {
// Create empty teams // Create empty teams
while (this->teams.size() < this->num_teams) { while (this->teams.size() < this->num_teams) {
auto t = make_shared<Team>( auto t = make_shared<Team>(this->shared_from_this(), this->teams.size(), (this->flags & Flag::IS_2V2) ? 2 : 1);
this->shared_from_this(), this->teams.size(), (this->flags & Flag::IS_2V2) ? 2 : 1);
this->teams.emplace_back(t); this->teams.emplace_back(t);
} }
is_registration_complete = false; is_registration_complete = false;
@@ -444,9 +427,7 @@ void Tournament::init() {
// If both preceding matches of the following match are resolved, put // If both preceding matches of the following match are resolved, put
// the following match on the queue since it may be resolvable as well // the following match on the queue since it may be resolvable as well
auto following = match->following.lock(); auto following = match->following.lock();
if (following && if (following && following->preceding_a->winner_team && following->preceding_b->winner_team) {
following->preceding_a->winner_team &&
following->preceding_b->winner_team) {
match_queue.emplace(following); match_queue.emplace(following);
} }
} }
@@ -477,8 +458,7 @@ void Tournament::create_bracket_matches() {
throw logic_error("tournaments team count is not a power of 2"); throw logic_error("tournaments team count is not a power of 2");
} }
// Create the zero-round matches, and make them all pending if registration // Create the zero-round matches, and make them all pending if registration is still open
// is still open
this->zero_round_matches.clear(); this->zero_round_matches.clear();
for (const auto& team : this->teams) { for (const auto& team : this->teams) {
auto m = make_shared<Match>(this->shared_from_this(), team); auto m = make_shared<Match>(this->shared_from_this(), team);
@@ -493,10 +473,7 @@ void Tournament::create_bracket_matches() {
while (current_round_matches.size() > 1) { while (current_round_matches.size() > 1) {
vector<shared_ptr<Match>> next_round_matches; vector<shared_ptr<Match>> next_round_matches;
for (size_t z = 0; z < current_round_matches.size(); z += 2) { for (size_t z = 0; z < current_round_matches.size(); z += 2) {
auto m = make_shared<Match>( auto m = make_shared<Match>(this->shared_from_this(), current_round_matches[z], current_round_matches[z + 1]);
this->shared_from_this(),
current_round_matches[z],
current_round_matches[z + 1]);
current_round_matches[z]->following = m; current_round_matches[z]->following = m;
current_round_matches[z + 1]->following = m; current_round_matches[z + 1]->following = m;
next_round_matches.emplace_back(std::move(m)); next_round_matches.emplace_back(std::move(m));
@@ -552,8 +529,7 @@ shared_ptr<Tournament::Team> Tournament::get_winner_team() const {
return this->final_match->winner_team; return this->final_match->winner_team;
} }
shared_ptr<Tournament::Match> Tournament::next_match_for_team( shared_ptr<Tournament::Match> Tournament::next_match_for_team(shared_ptr<Team> team) const {
shared_ptr<Team> team) const {
if (this->current_state == Tournament::State::REGISTRATION) { if (this->current_state == Tournament::State::REGISTRATION) {
return nullptr; return nullptr;
} }
@@ -561,8 +537,7 @@ shared_ptr<Tournament::Match> Tournament::next_match_for_team(
if (!match->preceding_a || !match->preceding_b) { if (!match->preceding_a || !match->preceding_b) {
throw logic_error("zero-round match is pending after tournament registration phase"); throw logic_error("zero-round match is pending after tournament registration phase");
} }
if ((team == match->preceding_a->winner_team) || if ((team == match->preceding_a->winner_team) || (team == match->preceding_b->winner_team)) {
(team == match->preceding_b->winner_team)) {
return match; return match;
} }
} }
@@ -573,8 +548,7 @@ shared_ptr<Tournament::Match> Tournament::get_final_match() const {
return this->final_match; return this->final_match;
} }
shared_ptr<Tournament::Team> Tournament::team_for_account_id( shared_ptr<Tournament::Team> Tournament::team_for_account_id(uint32_t account_id) const {
uint32_t account_id) const {
if (!this->all_player_account_ids.count(account_id)) { if (!this->all_player_account_ids.count(account_id)) {
return nullptr; return nullptr;
} }
@@ -601,9 +575,8 @@ void Tournament::start() {
bool has_com_teams = (this->flags & Flag::HAS_COM_TEAMS); bool has_com_teams = (this->flags & Flag::HAS_COM_TEAMS);
// If there aren't enough entrants (1 if has_com_teams is false, else 2), // If there aren't enough entrants (1 if has_com_teams is false, else 2), don't allow the tournament to start
// don't allow the tournament to start (because it would enter the COMPLETE // (because it would enter the COMPLETE state immediately)
// state immediately)
size_t num_human_teams = 0; size_t num_human_teams = 0;
for (size_t z = 0; z < this->teams.size(); z++) { for (size_t z = 0; z < this->teams.size(); z++) {
if (this->teams[z]->has_any_human_players()) { if (this->teams[z]->has_any_human_players()) {
@@ -615,9 +588,8 @@ void Tournament::start() {
} }
if ((this->flags & Flag::SHUFFLE_ENTRIES) && (this->flags & Flag::RESIZE_ON_START)) { if ((this->flags & Flag::SHUFFLE_ENTRIES) && (this->flags & Flag::RESIZE_ON_START)) {
// If both of these flags are set, pack the human teams into the lowest part // If both of these flags are set, pack the human teams into the lowest part of the teams list so we can resize the
// of the teams list so we can resize the tournament to the smallest // tournament to the smallest possible size. This is OK since we're going to shuffle them later anyway
// possible size. This is OK since we're going to shuffle them later anyway
size_t r_offset = 0, w_offset = 0; size_t r_offset = 0, w_offset = 0;
for (; r_offset < this->teams.size(); r_offset++) { for (; r_offset < this->teams.size(); r_offset++) {
if (this->teams[r_offset]->has_any_human_players()) { if (this->teams[r_offset]->has_any_human_players()) {
@@ -630,8 +602,8 @@ void Tournament::start() {
} }
if (this->flags & Flag::RESIZE_ON_START) { if (this->flags & Flag::RESIZE_ON_START) {
// Resize the tournament by repeatedly deleting the second half of it, until // Resize the tournament by repeatedly deleting the second half of it, until the second half contains human players
// the second half contains human players or the tournament size is 4 // or the tournament size is 4
while (this->teams.size() > 4) { while (this->teams.size() > 4) {
size_t z; size_t z;
for (z = this->teams.size() >> 1; z < this->teams.size(); z++) { for (z = this->teams.size() >> 1; z < this->teams.size(); z++) {
@@ -661,8 +633,7 @@ void Tournament::start() {
this->current_state = State::IN_PROGRESS; this->current_state = State::IN_PROGRESS;
this->create_bracket_matches(); this->create_bracket_matches();
// Assign names to COM teams, and assign COM decks to all empty slots unless // Assign names to COM teams, and assign COM decks to all empty slots unless has_com_teams is false
// has_com_teams is false
for (size_t z = 0; z < this->zero_round_matches.size(); z++) { for (size_t z = 0; z < this->zero_round_matches.size(); z++) {
auto m = this->zero_round_matches[z]; auto m = this->zero_round_matches[z];
auto t = m->winner_team; auto t = m->winner_team;
@@ -677,11 +648,9 @@ void Tournament::start() {
if (this->com_deck_index->num_decks() < t->max_players - t->players.size()) { if (this->com_deck_index->num_decks() < t->max_players - t->players.size()) {
throw runtime_error("not enough COM decks to complete team"); throw runtime_error("not enough COM decks to complete team");
} }
// If we allow all-COM teams, or this is a 2v2 tournament and the team has // If we allow all-COM teams, or this is a 2v2 tournament and the team has only one human on it, add a COM
// only one human on it, add a COM
if (has_com_teams || !t->players.empty()) { if (has_com_teams || !t->players.empty()) {
// TODO: Don't allow duplicate COM decks, nor duplicate COM SCs on the // TODO: Don't allow duplicate COM decks, nor duplicate COM SCs on the same team
// same team
while (t->players.size() < t->max_players) { while (t->players.size() < t->max_players) {
t->players.emplace_back(this->com_deck_index->random_deck()); t->players.emplace_back(this->com_deck_index->random_deck());
} }
@@ -698,9 +667,8 @@ void Tournament::send_all_state_updates() const {
for (const auto& team : this->teams) { for (const auto& team : this->teams) {
for (const auto& player : team->players) { for (const auto& player : team->players) {
auto c = player.client.lock(); auto c = player.client.lock();
// Note: The last check here is to make sure the client is still linked // Note: The last check here is to make sure the client is still linked with this instance of the tournament - an
// with this instance of the tournament - an intervening shell command // intervening shell command `reload ep3` could have changed the client's linkage
// `reload ep3` could have changed the client's linkage
if (c && (c->version() == Version::GC_EP3) && (c->ep3_tournament_team.lock() == team)) { if (c && (c->version() == Version::GC_EP3) && (c->ep3_tournament_team.lock() == team)) {
send_ep3_confirm_tournament_entry(c, this->shared_from_this()); send_ep3_confirm_tournament_entry(c, this->shared_from_this());
} }
@@ -828,8 +796,7 @@ TournamentIndex::TournamentIndex(
auto tourn = make_shared<Tournament>(this->map_index, this->com_deck_index, *it.second); auto tourn = make_shared<Tournament>(this->map_index, this->com_deck_index, *it.second);
tourn->init(); tourn->init();
if (!this->name_to_tournament.emplace(tourn->get_name(), tourn).second) { if (!this->name_to_tournament.emplace(tourn->get_name(), tourn).second) {
// This is logic_error instead of runtime_error because phosg::JSON dicts are // This is logic_error instead of runtime_error because phosg::JSON dicts already have unique keys
// supposed to already have unique keys
throw logic_error("multiple tournaments have the same name: " + tourn->get_name()); throw logic_error("multiple tournaments have the same name: " + tourn->get_name());
} }
tourn->set_menu_item_id(this->menu_item_id_to_tournament.size()); tourn->set_menu_item_id(this->menu_item_id_to_tournament.size());
@@ -862,8 +829,7 @@ shared_ptr<Tournament> TournamentIndex::create_tournament(
throw runtime_error("there can be at most 32 tournaments at a time"); throw runtime_error("there can be at most 32 tournaments at a time");
} }
auto t = make_shared<Tournament>( auto t = make_shared<Tournament>(this->map_index, this->com_deck_index, name, map, rules, num_teams, flags);
this->map_index, this->com_deck_index, name, map, rules, num_teams, flags);
t->init(); t->init();
if (!this->name_to_tournament.emplace(t->get_name(), t).second) { if (!this->name_to_tournament.emplace(t->get_name(), t).second) {
throw runtime_error("a tournament with the same name already exists"); throw runtime_error("a tournament with the same name already exists");
@@ -942,8 +908,7 @@ void TournamentIndex::link_client(shared_ptr<Client> c) {
} }
void TournamentIndex::link_all_clients(std::shared_ptr<ServerState> s) { void TournamentIndex::link_all_clients(std::shared_ptr<ServerState> s) {
// This can be called before the game server exists, so do nothing in that // This can be called before the game server exists, so do nothing in that case
// case
if (s->game_server) { if (s->game_server) {
for (const auto& c : s->game_server->all_clients()) { for (const auto& c : s->game_server->all_clients()) {
this->link_client(c); this->link_client(c);
+8 -18
View File
@@ -62,16 +62,10 @@ public:
size_t num_rounds_cleared; size_t num_rounds_cleared;
bool is_active; bool is_active;
Team( Team(std::shared_ptr<Tournament> tournament, size_t index, size_t max_players);
std::shared_ptr<Tournament> tournament,
size_t index,
size_t max_players);
std::string str() const; std::string str() const;
void register_player( void register_player(std::shared_ptr<Client> c, const std::string& team_name, const std::string& password);
std::shared_ptr<Client> c,
const std::string& team_name,
const std::string& password);
bool unregister_player(uint32_t account_id); bool unregister_player(uint32_t account_id);
bool has_any_human_players() const; bool has_any_human_players() const;
@@ -91,9 +85,7 @@ public:
std::shared_ptr<Tournament> tournament, std::shared_ptr<Tournament> tournament,
std::shared_ptr<Match> preceding_a, std::shared_ptr<Match> preceding_a,
std::shared_ptr<Match> preceding_b); std::shared_ptr<Match> preceding_b);
Match( Match(std::shared_ptr<Tournament> tournament, std::shared_ptr<Team> winner_team);
std::shared_ptr<Tournament> tournament,
std::shared_ptr<Team> winner_team);
std::string str() const; std::string str() const;
bool resolve_if_skippable(); bool resolve_if_skippable();
@@ -180,14 +172,12 @@ private:
std::set<uint32_t> all_player_account_ids; std::set<uint32_t> all_player_account_ids;
std::unordered_set<std::shared_ptr<Match>> pending_matches; std::unordered_set<std::shared_ptr<Match>> pending_matches;
// This vector contains all teams in the original starting order of the // This vector contains all teams in the original starting order of the tournament (that is, all teams in the first
// tournament (that is, all teams in the first round). The order within this // round). The order within this vector determines which team will play against which other team in the first round:
// vector determines which team will play against which other team in the // [0] will play against [1], [2] will play against [3], etc.
// first round: [0] will play against [1], [2] will play against [3], etc.
std::vector<std::shared_ptr<Team>> teams; std::vector<std::shared_ptr<Team>> teams;
// The tournament begins with a "zero round", in which each team automatically // The tournament begins with a "zero round", in which each team automatically "wins" a match, putting them into the
// "wins" a match, putting them into the first round. This is just to make the // first round. This is just to make the data model easier to manage, so we don't have to have a type of match with
// data model easier to manage, so we don't have to have a type of match with
// no preceding round. // no preceding round.
std::vector<std::shared_ptr<Match>> zero_round_matches; std::vector<std::shared_ptr<Match>> zero_round_matches;
std::shared_ptr<Match> final_match; std::shared_ptr<Match> final_match;