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
// (for God Whim).
static const vector<uint16_t> ALL_ASSIST_CARD_IDS_TRIAL = {
0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD,
0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106,
0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x010F,
0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C,
0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132, 0x0133, 0x0134, 0x0135,
0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D, 0x013E,
0x013F, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0148,
0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F, 0x0240, 0x0241, 0x0242};
0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102,
0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x010F, 0x0121,
0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132,
0x0133, 0x0134, 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D, 0x013E, 0x013F, 0x0140,
0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0148, 0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F, 0x0240,
0x0241, 0x0242};
static const vector<uint16_t> ALL_ASSIST_CARD_IDS_FINAL = {
0x0018, 0x0019, 0x001A, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA,
0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102, 0x0103,
0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C,
0x010D, 0x010E, 0x010F, 0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129,
0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132,
0x0133, 0x0134, 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B,
0x013C, 0x013D, 0x013E, 0x013F, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144,
0x0145, 0x0146, 0x0148, 0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F,
0x0240, 0x0241, 0x0242};
0x0018, 0x0019, 0x001A, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF,
0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D,
0x010E, 0x010F, 0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F,
0x0130, 0x0131, 0x0132, 0x0133, 0x0134, 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D,
0x013E, 0x013F, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0148, 0x014A, 0x014B, 0x014C, 0x014D,
0x014E, 0x023F, 0x0240, 0x0241, 0x0242};
return is_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) {
auto this_deck_entry = this->deck_entries[client_id];
auto other_deck_entry = this->deck_entries[z];
if (this_deck_entry && other_deck_entry &&
(this_deck_entry->team_id == other_deck_entry->team_id)) {
if (this_deck_entry && other_deck_entry && (this_deck_entry->team_id == other_deck_entry->team_id)) {
affected = true;
}
} else if ((ce->def.target_mode == TargetMode::SELF) && (z == client_id)) {
affected = true;
} else if (ce->def.target_mode == TargetMode::EVERYONE) {
} else if (((ce->def.target_mode == TargetMode::SELF) && (z == client_id)) ||
(ce->def.target_mode == TargetMode::EVERYONE)) {
affected = true;
}
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)) {
return true;
}
} else if ((ce->def.target_mode == TargetMode::SELF) && (client_id == z)) {
return true;
} else if (ce->def.target_mode == TargetMode::EVERYONE) {
} else if (((ce->def.target_mode == TargetMode::SELF) && (client_id == z)) ||
(ce->def.target_mode == TargetMode::EVERYONE)) {
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 {
if (index < this->num_active_assists) {
return this->active_assist_effects[index];
}
return AssistEffect::NONE;
return (index < this->num_active_assists) ? this->active_assist_effects[index] : AssistEffect::NONE;
}
void AssistServer::populate_effects() {
+30 -49
View File
@@ -7,11 +7,7 @@ using namespace std;
namespace Episode3 {
Card::Card(
uint16_t card_id,
uint16_t card_ref,
uint16_t client_id,
shared_ptr<Server> server)
Card::Card(uint16_t card_id, uint16_t card_ref, uint16_t client_id, shared_ptr<Server> server)
: w_server(server),
w_player_state(server->get_player_state(client_id)),
client_id(client_id),
@@ -132,8 +128,7 @@ ssize_t Card::apply_abnormal_condition(
if (cond.type == eff.type) {
existing_cond_index = z;
if ((!is_nte && eff.type == ConditionType::MV_BONUS) ||
((cond.card_definition_effect_index == def_effect_index) &&
(cond.card_ref == target_card_ref))) {
((cond.card_definition_effect_index == def_effect_index) && (cond.card_ref == target_card_ref))) {
break;
}
} else {
@@ -173,11 +168,7 @@ ssize_t Card::apply_abnormal_condition(
cond.condition_giver_card_ref = sc_card_ref;
cond.card_definition_effect_index = def_effect_index;
cond.order = 10;
if (dice_roll_value < 0) {
cond.dice_roll_value = this->player_state()->roll_dice_with_effects(1);
} else {
cond.dice_roll_value = dice_roll_value;
}
cond.dice_roll_value = (dice_roll_value < 0) ? this->player_state()->roll_dice_with_effects(1) : dice_roll_value;
cond.flags = 0;
cond.value = 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);
for (size_t z = 0; z < num_assists; z++) {
auto eff = s->assist_server->get_active_assist_by_index(z);
if ((eff == AssistEffect::RANSOM) &&
(attacker_card->action_chain.chain.attack_medium == AttackMedium::PHYSICAL)) {
if ((eff == AssistEffect::RANSOM) && (attacker_card->action_chain.chain.attack_medium == AttackMedium::PHYSICAL)) {
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);
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);
log.debug_f("hp set to {}", this->current_hp);
if ((effective_damage > 0) &&
(attacker_ps->stats.max_attack_damage < effective_damage)) {
if ((effective_damage > 0) && (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);
}
@@ -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(EffectWhen::BEFORE_ANY_CARD_ATTACK, attacker_card, this->shared_from_this(), 0x10, nullptr);
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(
EffectWhen::BEFORE_ANY_CARD_ATTACK, attacker_card, this->shared_from_this(), 0x10, nullptr);
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) &&
(ps->get_sc_card().get() == this)) {
if ((s->map_and_rules->rules.hp_type == HPType::DEFEAT_TEAM) && (ps->get_sc_card().get() == this)) {
for (size_t set_index = 0; set_index < 8; set_index++) {
auto card = ps->get_set_card(set_index);
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 this_team_id = ps->get_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) {
return -0x60;
}
if (!this->server()->ruler_server->card_ref_can_move(
this->client_id, this->card_ref, 1)) {
if (!this->server()->ruler_server->card_ref_can_move(this->client_id, this->card_ref, 1)) {
return -0x7B;
}
// 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) {
// Probably Resta
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 + attacker_card->action_chain.chain.effective_tp,
this->max_hp);
this->current_hp = min<int16_t>(this->current_hp + attacker_card->action_chain.chain.effective_tp, this->max_hp);
}
this->propagate_shared_hp_if_needed();
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(
ConditionType cond_type,
uint16_t card_ref,
uint8_t def_effect_index,
uint16_t value,
uint16_t* out_value) const {
ConditionType cond_type, 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);
}
@@ -697,9 +680,7 @@ int32_t Card::move_to_location(const Location& loc) {
this->card_flags = this->card_flags | 0x80;
// On NTE, traps happen now, not after the Move phase
if (s->options.is_nte() &&
this->def_entry->def.is_sc() &&
((s->overlay_state.tiles[loc.y][loc.x] & 0xF0) == 0x40)) {
if (s->options.is_nte() && this->def_entry->def.is_sc() && ((s->overlay_state.tiles[loc.y][loc.x] & 0xF0) == 0x40)) {
for (size_t z = 0; z < 4; z++) {
auto other_ps = s->player_states[z];
if (!other_ps) {
@@ -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))) {
for (size_t other_client_id = 0; other_client_id < 4; other_client_id++) {
auto other_ps = this->server()->player_states[other_client_id];
if ((other_client_id != this->client_id) && other_ps &&
(other_ps->get_team_id() == this->team_id)) {
if ((other_client_id != this->client_id) && other_ps && (other_ps->get_team_id() == this->team_id)) {
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) {
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();
this->action_chain.compute_attack_medium(s);
@@ -930,7 +911,8 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
} else {
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));
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);
}
@@ -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();
if (other_sc_card &&
(abs(this->loc.x - other_sc_card->loc.x) < 2) &&
(abs(this->loc.y - other_sc_card->loc.y) < 2)) {
(abs(this->loc.x - other_sc_card->loc.x) < 2) && (abs(this->loc.y - other_sc_card->loc.y) < 2)) {
num_scs_in_range++;
}
}
@@ -1106,10 +1087,12 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
int16_t damage = 0;
if (this->action_chain.chain.attack_medium == AttackMedium::TECH) {
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) {
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 {
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
? (damage * this->action_chain.chain.damage_multiplier)
: 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) {
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) {
this->action_chain.chain.unknown_card_ref_a3 =
(card_ref == 0xFFFF) ? this->card_ref : card_ref;
this->action_chain.chain.unknown_card_ref_a3 = (card_ref == 0xFFFF) ? this->card_ref : card_ref;
}
void Card::unknown_802379DC(const ActionState& pa) {
@@ -1359,8 +1342,7 @@ void Card::dice_phase_before() {
cond.remaining_turns--;
}
if (cond.remaining_turns < 1) {
s->card_special->apply_stat_deltas_to_card_from_condition_and_clear_cond(
cond, this->shared_from_this());
s->card_special->apply_stat_deltas_to_card_from_condition_and_clear_cond(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()));
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
if (as) {
string as_str = as->str(s);
log.debug_f("as = {}", as_str);
log.debug_f("as = {}", as->str(s));
} else {
log.debug_f("as = null");
}
+154 -179
View File
@@ -84,7 +84,8 @@ void CardSpecial::AttackEnvStats::clear() {
this->target_current_hp = 0;
}
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);
}
@@ -454,10 +455,7 @@ bool CardSpecial::apply_defense_condition(
}
bool CardSpecial::apply_defense_conditions(
const ActionState& as,
EffectWhen when,
shared_ptr<Card> defender_card,
uint32_t flags) {
const ActionState& as, EffectWhen when, shared_ptr<Card> defender_card, uint32_t flags) {
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);
}
@@ -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();
if (sc_card) {
ret |= this->apply_stats_deltas_to_card_from_all_conditions_with_card_ref(
card_ref, sc_card);
ret |= this->apply_stats_deltas_to_card_from_all_conditions_with_card_ref(card_ref, sc_card);
}
for (size_t set_index = 0; set_index < 8; set_index++) {
auto set_card = ps->get_set_card(set_index);
if (set_card) {
ret |= this->apply_stats_deltas_to_card_from_all_conditions_with_card_ref(
card_ref, set_card);
ret |= this->apply_stats_deltas_to_card_from_all_conditions_with_card_ref(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()));
bool is_nte = s->options.is_nte();
string cond_str = cond.str(s);
log.debug_f("cond: {}", cond_str);
log.debug_f("cond: {}", cond.str(s));
ConditionType cond_type = cond.type;
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(
shared_ptr<const Card> card,
ConditionType cond_type,
uint16_t card_ref,
uint16_t match_card_ref) const {
shared_ptr<const Card> card, ConditionType cond_type, uint16_t card_ref, uint16_t match_card_ref) const {
size_t z = 0;
while ((z < 9) &&
((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) {
return false;
}
return !this->server()->ruler_server->card_ref_or_any_set_card_has_condition_46(
card->get_card_ref());
return !this->server()->ruler_server->card_ref_or_any_set_card_has_condition_46(card->get_card_ref());
}
void CardSpecial::compute_attack_ap(
shared_ptr<const Card> target_card,
int16_t* out_value,
uint16_t attacker_card_ref) {
shared_ptr<const Card> target_card, int16_t* out_value, uint16_t attacker_card_ref) {
auto s = this->server();
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;
uint8_t target_card_team_id = target_card
? target_card->player_state()->get_team_id()
: 0xFF;
uint8_t target_card_team_id = target_card ? target_card->player_state()->get_team_id() : 0xFF;
size_t 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.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
: 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
: 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
: attacker_card->action_chain.chain.damage;
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;
// Note: The (z < 8) conditions in these two loops are not present in the
// original code.
for (z = 0;
((target_card_ref != z_ref) && (z < 8) && ((z_ref = pa.action_card_refs[z]) != 0xFFFF));
z++) {
for (z = 0; ((target_card_ref != z_ref) && (z < 8) && ((z_ref = pa.action_card_refs[z]) != 0xFFFF)); z++) {
}
ast.action_cards_ap = 0;
@@ -1012,8 +1000,7 @@ shared_ptr<Card> CardSpecial::compute_replaced_target_based_on_conditions(
if (num_candidates > 0) {
uint8_t a = target_ps->roll_dice_with_effects(2);
uint8_t b = target_ps->roll_dice_with_effects(1);
return s->card_for_set_card_ref(
candidate_card_refs[(a + b) - ((a + b) / num_candidates) * num_candidates]);
return s->card_for_set_card_ref(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++) {
auto ps = this->server()->get_player_state(client_id);
if (ps) {
ret += this->count_action_cards_with_condition_for_current_attack(
ps->get_sc_card(), cond_type, card_ref);
ret += this->count_action_cards_with_condition_for_current_attack(ps->get_sc_card(), cond_type, card_ref);
for (size_t set_index = 0; set_index < 8; set_index++) {
ret += this->count_action_cards_with_condition_for_current_attack(
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;
}
size_t CardSpecial::count_cards_with_card_id_except_card_ref(
uint16_t card_id, uint16_t card_ref) const {
size_t CardSpecial::count_cards_with_card_id_except_card_ref(uint16_t card_id, uint16_t card_ref) const {
size_t ret = 0;
for (size_t client_id = 0; client_id < 4; 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++) {
auto card = ps->get_set_card(set_index);
if (card &&
(card->get_card_ref() != card_ref) &&
(card->get_definition()->def.card_id == card_id)) {
if (card && (card->get_card_ref() != card_ref) && (card->get_definition()->def.card_id == card_id)) {
ret++;
}
}
@@ -1393,8 +1376,7 @@ ActionState CardSpecial::create_attack_state_from_card_action_chain(
shared_ptr<const Card> attacker_card) const {
ActionState ret;
if (attacker_card) {
ret.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(
attacker_card->get_card_ref(), 4);
ret.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card->get_card_ref(), 4);
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(
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(
shared_ptr<const Card> attacker_card,
shared_ptr<const Card> defender_card) const {
shared_ptr<const Card> attacker_card, shared_ptr<const Card> defender_card) const {
ActionState ret;
if (defender_card && attacker_card) {
size_t count = 0;
@@ -1422,18 +1403,15 @@ ActionState CardSpecial::create_defense_state_for_card_pair_action_chains(
}
}
if (defender_card) {
ret.target_card_refs[0] = this->send_6xB4x06_if_card_ref_invalid(
defender_card->get_card_ref(), 8);
ret.target_card_refs[0] = this->send_6xB4x06_if_card_ref_invalid(defender_card->get_card_ref(), 8);
}
if (attacker_card) {
ret.original_attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(
attacker_card->get_card_ref(), 9);
ret.original_attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card->get_card_ref(), 9);
}
return ret;
}
void CardSpecial::destroy_card_if_hp_zero(
shared_ptr<Card> card, uint16_t attacker_card_ref) {
void CardSpecial::destroy_card_if_hp_zero(shared_ptr<Card> card, uint16_t attacker_card_ref) {
if (card && (card->get_current_hp() <= 0)) {
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();
auto set_card = s->card_for_set_card_ref(set_card_ref);
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]) {
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':
if (is_nte) {
return false;
@@ -1515,11 +1499,6 @@ bool CardSpecial::evaluate_effect_arg2_condition(
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': {
if (set_card_has_ability_trap) {
return false;
@@ -1596,7 +1575,8 @@ bool CardSpecial::evaluate_effect_arg2_condition(
auto ce = card->get_definition();
return ((ce->def.card_class() == CardClass::GUARD_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
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));
case 's': {
auto ce = card->get_definition();
return ((ce->def.self_cost >= arg2_text[1] - '0') &&
(ce->def.self_cost <= arg2_text[2] - '0'));
return ((ce->def.self_cost >= arg2_text[1] - '0') && (ce->def.self_cost <= arg2_text[2] - '0'));
}
case 't': {
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);
} else if (when == EffectWhen::BEFORE_DICE_PHASE_THIS_TEAM_TURN) {
uint32_t y = set_card->unknown_a9 & 0xFFFFFFFE;
if ((set_card->unknown_a9 > 0) &&
(y == (y / (v & 0xFFFFFFFE)) * (v & 0xFFFFFFFE))) {
if ((set_card->unknown_a9 > 0) && (y == (y / (v & 0xFFFFFFFE)) * (v & 0xFFFFFFFE))) {
return true;
}
} else {
uint32_t y = set_card->unknown_a9;
if ((set_card->unknown_a9 > 0) &&
(y == (y / (v + 1)) * (v + 1))) {
if ((set_card->unknown_a9 > 0) && (y == (y / (v + 1)) * (v + 1))) {
return true;
}
}
@@ -1708,13 +1685,17 @@ bool CardSpecial::evaluate_effect_arg2_condition(
throw logic_error("this should be impossible");
}
int32_t CardSpecial::evaluate_effect_expr(
const AttackEnvStats& ast,
const char* expr,
DiceRoll& dice_roll) const {
int32_t CardSpecial::evaluate_effect_expr(const AttackEnvStats& ast, const char* expr, DiceRoll& dice_roll) const {
auto log = this->server()->log_stack("evaluate_effect_expr: ");
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);
}
@@ -1835,8 +1816,7 @@ bool CardSpecial::execute_effect(
}
} else if (card->action_metadata.check_flag(0x10) &&
(cond.card_ref != card->get_card_ref()) &&
(cond.condition_giver_card_ref != card->get_card_ref())) {
(cond.card_ref != card->get_card_ref()) && (cond.condition_giver_card_ref != card->get_card_ref())) {
return false;
}
@@ -1916,7 +1896,9 @@ bool CardSpecial::execute_effect(
case ConditionType::GIVE_DAMAGE:
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 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);
new_hp = max<int16_t>(new_hp, 0);
if (new_hp != current_hp) {
@@ -2226,8 +2208,7 @@ bool CardSpecial::execute_effect(
if (hand_size > 0) {
uint8_t a = ps->roll_dice_with_effects(2);
uint8_t b = ps->roll_dice_with_effects(1);
uint16_t card_ref = ps->card_ref_for_hand_index(
(a + b) - ((a + b) / hand_size) * hand_size);
uint16_t card_ref = ps->card_ref_for_hand_index((a + b) - ((a + b) / hand_size) * hand_size);
if (card_ref != 0xFFFF) {
ps->discard_ref_from_hand(card_ref);
}
@@ -2264,7 +2245,8 @@ bool CardSpecial::execute_effect(
card->action_metadata.defense_card_ref_count = 0;
} else {
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;
@@ -2418,7 +2400,13 @@ bool CardSpecial::execute_effect(
auto& cond = attacker_card->action_chain.conditions[z];
if (cond.type != ConditionType::UNKNOWN_49) {
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:
if (unknown_p7 & 4) {
this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0xA0, positive_expr_value, 0, 1);
card->ap = is_nte
? (card->ap + positive_expr_value)
: clamp<int16_t>(card->ap + positive_expr_value, -99, 99);
card->ap = is_nte ? (card->ap + positive_expr_value) : clamp<int16_t>(card->ap + positive_expr_value, -99, 99);
}
return true;
case ConditionType::TP_GROWTH:
if (unknown_p7 & 4) {
this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x80, positive_expr_value, 0, 1);
card->tp = is_nte
? (card->tp + positive_expr_value)
: clamp<int16_t>(card->tp + positive_expr_value, -99, 99);
card->tp = is_nte ? (card->tp + positive_expr_value) : clamp<int16_t>(card->tp + positive_expr_value, -99, 99);
}
return true;
@@ -2593,8 +2577,7 @@ bool CardSpecial::execute_effect(
if (is_nte) {
return false;
}
if ((unknown_p7 & 0x40) &&
(static_cast<uint16_t>(attack_medium) == ((s->get_round_num() >> 1) & 1) + 1)) {
if ((unknown_p7 & 0x40) && (static_cast<uint16_t>(attack_medium) == ((s->get_round_num() >> 1) & 1) + 1)) {
card->action_metadata.attack_bonus = 0;
}
return true;
@@ -2668,10 +2651,7 @@ bool CardSpecial::execute_effect(
}
const Condition* CardSpecial::find_condition_with_parameters(
shared_ptr<const Card> card,
ConditionType cond_type,
uint16_t set_card_ref,
uint8_t def_effect_index) const {
shared_ptr<const Card> card, ConditionType cond_type, uint16_t set_card_ref, uint8_t def_effect_index) const {
if (this->server()->options.is_nte()) {
// 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(
shared_ptr<Card> card,
ConditionType cond_type,
uint16_t set_card_ref,
uint8_t def_effect_index) const {
shared_ptr<Card> card, ConditionType cond_type, uint16_t set_card_ref, uint8_t def_effect_index) const {
return const_cast<Condition*>(this->find_condition_with_parameters(
static_cast<shared_ptr<const Card>>(card), cond_type, set_card_ref, def_effect_index));
}
void CardSpecial::get_card1_loc_with_card2_opposite_direction(
Location* out_loc,
shared_ptr<const Card> card1,
shared_ptr<const Card> card2) {
Location* out_loc, shared_ptr<const Card> card1, shared_ptr<const Card> card2) {
if (card1) {
if (!card2 || (static_cast<uint8_t>(card2->facing_direction) & 0x80)) {
*out_loc = card1->loc;
@@ -2749,12 +2724,7 @@ uint16_t CardSpecial::get_card_id_with_effective_range(
}
void CardSpecial::get_effective_ap_tp(
StatSwapType type,
int16_t* effective_ap,
int16_t* effective_tp,
int16_t hp,
int16_t ap,
int16_t tp) {
StatSwapType type, int16_t* effective_ap, int16_t* effective_tp, int16_t hp, int16_t ap, int16_t tp) {
switch (type) {
case StatSwapType::NONE:
*effective_ap = ap;
@@ -2842,8 +2812,11 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
int16_t p_target_type,
bool apply_usability_filters) const {
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));
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");
auto log = s->log_stack(std::format(
"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;
@@ -2871,9 +2844,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
log.debug_f("card1_loc={}", card1_loc_str);
}
AttackMedium attack_medium = card2
? card2->action_chain.chain.attack_medium
: AttackMedium::UNKNOWN;
AttackMedium attack_medium = card2 ? card2->action_chain.chain.attack_medium : AttackMedium::UNKNOWN;
log.debug_f("attack_medium={}", phosg::name_for_enum(attack_medium));
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());
for (uint16_t result_card_ref : result_card_refs) {
auto result_card = s->card_for_set_card_ref(result_card_ref);
if (result_card &&
(result_card->get_definition()->def.type != CardType::ITEM) &&
(card1 != result_card)) {
if (result_card && (result_card->get_definition()->def.type != CardType::ITEM) && (card1 != 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) {
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]);
if (result_card &&
result_card->get_definition() &&
!result_card->get_definition()->def.is_sc()) {
if (result_card && result_card->get_definition() && !result_card->get_definition()->def.is_sc()) {
ret.emplace_back(result_card);
}
}
} else if (card2 &&
card2->get_definition() &&
!card2->get_definition()->def.is_sc()) {
} else if (card2 && card2->get_definition() && !card2->get_definition()->def.is_sc()) {
ret.emplace_back(card2);
}
break;
@@ -3250,8 +3215,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
case 0x28: { // p40
auto log3940 = log.sub("(p39/p40) ");
ret = this->find_all_set_cards_with_cost_in_range(
(p_target_type == 0x27) ? 4 : 0,
(p_target_type == 0x27) ? 99 : 3);
(p_target_type == 0x27) ? 4 : 0, (p_target_type == 0x27) ? 99 : 3);
if (log3940.should_log(phosg::LogLevel::L_DEBUG)) {
for (const auto& card : ret) {
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()) {
for (size_t z = 0; z < 8; z++) {
auto result_card = ps->get_set_card(z);
if (result_card && (card1 != result_card) &&
(result_card->get_definition()->def.type == CardType::ITEM)) {
if (result_card && (card1 != result_card) && (result_card->get_definition()->def.type == CardType::ITEM)) {
bool already_in_ret = false;
for (auto c : ret) {
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++) {
auto result_card = ps->get_set_card(set_index);
if (result_card && (card1 != result_card) &&
(result_card->get_definition()->def.type == CardType::ITEM)) {
if (result_card && (card1 != result_card) && (result_card->get_definition()->def.type == CardType::ITEM)) {
bool should_add = true;
for (auto c : ret) {
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(
const Condition& cond,
const ActionState& as,
shared_ptr<const Card> card) const {
const Condition& cond, const ActionState& as, shared_ptr<const Card> card) const {
auto s = this->server();
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(
shared_ptr<const Card> card,
uint16_t attacker_card_ref,
uint8_t dice_roll_value,
bool unknown_p5) const {
shared_ptr<const Card> card, uint16_t attacker_card_ref, uint8_t dice_roll_value, bool unknown_p5) const {
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
cmd.effect.flags = 0x02;
cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 10);
@@ -3636,8 +3593,7 @@ void CardSpecial::send_6xB4x06_for_card_destroyed(
auto s = this->server();
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
cmd.effect.flags = 0x04;
cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(
attacker_card_ref, 0x13);
cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x13);
cmd.effect.target_card_ref = destroyed_card->get_card_ref();
cmd.effect.value = 0;
cmd.effect.operation = s->options.is_nte() ? 0x78 : 0x7E;
@@ -3776,9 +3732,7 @@ bool CardSpecial::should_return_card_ref_to_hand_on_destruction(
continue;
}
auto cond_type = card->action_chain.conditions[cond_index].type;
if ((cond_type == ConditionType::RETURN) &&
!(card->card_flags & 1) &&
(card->get_card_ref() == card_ref)) {
if ((cond_type == ConditionType::RETURN) && !(card->card_flags & 1) && (card->get_card_ref() == card_ref)) {
return true;
} else if ((cond_type == ConditionType::REBORN) &&
!(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(
vector<shared_ptr<const Card>>* out_cards,
int32_t* out_damage_sum,
size_t* out_damage_count) const {
vector<shared_ptr<const Card>>* out_cards, int32_t* out_damage_sum, size_t* out_damage_count) const {
auto log = this->server()->log_stack("sum_last_attack_damage: ");
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(
shared_ptr<const Card> attacker_card,
shared_ptr<Card> target_card,
int16_t* inout_unknown_p4) {
shared_ptr<const Card> attacker_card, shared_ptr<Card> target_card, int16_t* inout_unknown_p4) {
auto s = this->server();
// Note: This check is not part of the original implementation.
@@ -3941,8 +3891,7 @@ void CardSpecial::check_for_defense_interference(
return;
}
uint16_t ally_sc_card_ref = s->ruler_server->get_ally_sc_card_ref(
target_card->get_card_ref());
uint16_t ally_sc_card_ref = s->ruler_server->get_ally_sc_card_ref(target_card->get_card_ref());
if (ally_sc_card_ref == 0xFFFF) {
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);
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;
}
@@ -3984,8 +3934,7 @@ void CardSpecial::check_for_defense_interference(
if (target_ps->unknown_a17 >= 1) {
return;
}
auto entry = get_interference_probability_entry(
target_card_id, ally_sc_card_id, false);
auto entry = get_interference_probability_entry(target_card_id, ally_sc_card_id, false);
if (!entry || (s->get_random(99) >= entry->defense_probability)) {
return;
}
@@ -4019,7 +3968,12 @@ void CardSpecial::evaluate_and_apply_effects(
{
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}",
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) {
@@ -4076,13 +4030,15 @@ void CardSpecial::evaluate_and_apply_effects(
dice_roll.unknown_a2 = 3;
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));
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) {
effect_log.debug_f("does not apply (effect.when={}, when={})", phosg::name_for_enum(card_effect.when), phosg::name_for_enum(when));
continue;
@@ -4199,9 +4155,16 @@ void CardSpecial::evaluate_and_apply_effects(
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(
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");
}
}
@@ -4472,8 +4435,7 @@ const InterferenceProbabilityEntry* get_interference_probability_entry(
const auto& entry = entries[z];
uint16_t current_column_card_id = entry.card_id;
if ((entry.attack_probability != 0xFF) || (entry.defense_probability != 0xFF)) {
if ((row_card_id == current_row_card_id) &&
(column_card_id == current_column_card_id)) {
if ((row_card_id == current_row_card_id) && (column_card_id == current_column_card_id)) {
uint8_t v = is_attack ? entry.attack_probability : entry.defense_probability;
if (current_max <= v) {
ret_entry = &entry;
@@ -4488,11 +4450,9 @@ const InterferenceProbabilityEntry* get_interference_probability_entry(
return ret_entry;
}
void CardSpecial::on_card_destroyed(
shared_ptr<Card> attacker_card, shared_ptr<Card> destroyed_card) {
void CardSpecial::on_card_destroyed(shared_ptr<Card> attacker_card, shared_ptr<Card> destroyed_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(
attacker_card, destroyed_card);
ActionState defense_as = this->create_defense_state_for_card_pair_action_chains(attacker_card, destroyed_card);
uint16_t destroyed_card_ref = destroyed_card->get_card_ref();
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);
}
vector<shared_ptr<const Card>> CardSpecial::find_cards_in_hp_range(
int16_t min, int16_t max) const {
vector<shared_ptr<const Card>> CardSpecial::find_cards_in_hp_range(int16_t min, int16_t max) const {
vector<shared_ptr<const Card>> ret;
for (size_t client_id = 0; client_id < 4; 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) {
auto s = this->server();
auto log = s->log_stack("apply_effects_after_attack_target_resolution: ");
string as_str = as.str(s);
log.debug_f("as={}", as_str);
log.debug_f("as={}", as.str(s));
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);
@@ -4878,7 +4836,12 @@ void CardSpecial::dice_phase_before_for_card(shared_ptr<Card> card) {
template <EffectWhen When1, EffectWhen When2>
void CardSpecial::apply_effects_on_phase_change_t(shared_ptr<Card> unknown_p2, const ActionState* existing_as) {
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();
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++) {
auto card = s->card_for_set_card_ref(as.target_card_refs[z]);
if (card) {
ActionState target_as = this->create_defense_state_for_card_pair_action_chains(
unknown_p2, card);
ActionState target_as = this->create_defense_state_for_card_pair_action_chains(unknown_p2, card);
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++) {
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) {
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) {
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;
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++) {
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(EffectWhen::ATTACK_DAMAGE_ADJUSTMENT, as.action_card_refs[z], as, unknown_p2->get_card_ref());
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(
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++) {
card = this->server()->card_for_set_card_ref(as.target_card_refs[z]);
if (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(EffectWhen::DEFENSE_DAMAGE_ADJUSTMENT, card->get_card_ref(), defense_as, unknown_p2->get_card_ref());
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(
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) {
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) {
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);
@@ -5009,8 +4978,7 @@ void CardSpecial::check_for_attack_interference(shared_ptr<Card> unknown_p2) {
return;
}
uint16_t ally_sc_card_ref = this->server()->ruler_server->get_ally_sc_card_ref(
unknown_p2->get_card_ref());
uint16_t ally_sc_card_ref = this->server()->ruler_server->get_ally_sc_card_ref(unknown_p2->get_card_ref());
if (ally_sc_card_ref == 0xFFFF) {
return;
}
@@ -5081,7 +5049,12 @@ template <
void CardSpecial::apply_effects_before_or_after_attack(shared_ptr<Card> unknown_p2) {
auto s = this->server();
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);
@@ -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++) {
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++) {
auto set_card = s->card_for_set_card_ref(as.target_card_refs[z]);
if (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(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++) {
this->evaluate_and_apply_effects(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());
this->evaluate_and_apply_effects(
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) {
ActionState as = this->create_defense_state_for_card_pair_action_chains(
unknown_p2, unknown_p3);
ActionState as = this->create_defense_state_for_card_pair_action_chains(unknown_p2, unknown_p3);
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::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(
CardClass card_class) const {
vector<shared_ptr<const Card>> CardSpecial::find_all_sc_cards_of_class(CardClass card_class) const {
vector<shared_ptr<const Card>> ret;
for (size_t z = 0; z < 4; 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(
uint16_t row_card_id,
uint16_t column_card_id,
bool is_attack);
uint16_t row_card_id, uint16_t column_card_id, bool is_attack);
class CardSpecial {
public:
@@ -77,9 +75,8 @@ public:
/* 70 */ uint32_t effective_ap_if_not_tech2; // "tt" in expr
/* 74 */ uint32_t team_dice_bonus; // "lv" in expr
/* 78 */ uint32_t sc_effective_ap; // "adm" in expr
// The following fields do not exist in Trial Edition. Because this struct
// is never sent to the client, we use the full struct even when playing
// Trial Edition, just for simplicity.
// The following fields do not exist in Trial Edition. Because this struct is never sent to the client, we use the
// full struct even when playing Trial Edition, just for simplicity.
/* 7C */ uint32_t attack_bonus; // "ddm" in expr
/* 80 */ uint32_t num_sword_type_items_on_team; // "sat" in expr
/* 84 */ uint32_t target_attack_bonus; // "edm" in expr
@@ -126,26 +123,14 @@ public:
uint32_t flags,
bool unknown_p8);
bool apply_defense_conditions(
const ActionState& as,
EffectWhen when,
std::shared_ptr<Card> defender_card,
uint32_t flags);
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);
const ActionState& as, EffectWhen when, std::shared_ptr<Card> defender_card, uint32_t flags);
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(
std::shared_ptr<const Card> card,
ConditionType cond_type,
uint16_t card_ref,
uint16_t match_card_ref) const;
std::shared_ptr<const Card> card, ConditionType cond_type, uint16_t card_ref, uint16_t match_card_ref) const;
bool card_is_destroyed(std::shared_ptr<const Card> card) const;
void compute_attack_ap(
std::shared_ptr<const Card> target_card,
int16_t* out_value,
uint16_t attacker_card_ref);
void compute_attack_ap(std::shared_ptr<const Card> target_card, int16_t* out_value, uint16_t attacker_card_ref);
AttackEnvStats compute_attack_env_stats(
const ActionState& pa,
std::shared_ptr<const Card> card,
@@ -166,21 +151,16 @@ public:
StatSwapType compute_stat_swap_type(std::shared_ptr<const Card> card) const;
void compute_team_dice_bonus(uint8_t team_id);
bool condition_applies_on_sc_or_item_attack(const Condition& cond) const;
size_t count_action_cards_with_condition_for_all_current_attacks(
ConditionType cond_type, uint16_t card_ref) const;
size_t count_action_cards_with_condition_for_all_current_attacks(ConditionType cond_type, uint16_t card_ref) const;
size_t count_action_cards_with_condition_for_current_attack(
std::shared_ptr<const Card> card, ConditionType cond_type, uint16_t card_ref) const;
size_t count_cards_with_card_id_except_card_ref(
uint16_t card_id, uint16_t card_ref) const;
size_t count_cards_with_card_id_except_card_ref(uint16_t card_id, uint16_t card_ref) const;
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;
ActionState create_attack_state_from_card_action_chain(
std::shared_ptr<const Card> attacker_card) const;
ActionState create_attack_state_from_card_action_chain(std::shared_ptr<const Card> attacker_card) const;
ActionState create_defense_state_for_card_pair_action_chains(
std::shared_ptr<const Card> attacker_card,
std::shared_ptr<const Card> defender_card) const;
void destroy_card_if_hp_zero(
std::shared_ptr<Card> card, uint16_t attacker_card_ref);
std::shared_ptr<const Card> attacker_card, std::shared_ptr<const Card> defender_card) const;
void destroy_card_if_hp_zero(std::shared_ptr<Card> card, uint16_t attacker_card_ref);
bool evaluate_effect_arg2_condition(
const ActionState& as,
std::shared_ptr<const Card> card,
@@ -190,10 +170,7 @@ public:
uint16_t sc_card_ref,
uint8_t random_percent,
EffectWhen when) const;
int32_t evaluate_effect_expr(
const AttackEnvStats& ast,
const char* expr,
DiceRoll& dice_roll) const;
int32_t evaluate_effect_expr(const AttackEnvStats& ast, const char* expr, DiceRoll& dice_roll) const;
bool execute_effect(
Condition& cond,
std::shared_ptr<Card> card,
@@ -208,25 +185,14 @@ public:
uint16_t set_card_ref,
uint8_t def_effect_index) const;
Condition* find_condition_with_parameters(
std::shared_ptr<Card> card,
ConditionType cond_type,
uint16_t set_card_ref,
uint8_t def_effect_index) const;
std::shared_ptr<Card> card, ConditionType cond_type, uint16_t set_card_ref, uint8_t def_effect_index) const;
static void get_card1_loc_with_card2_opposite_direction(
Location* out_loc,
std::shared_ptr<const Card> card1,
std::shared_ptr<const Card> card2);
Location* out_loc, std::shared_ptr<const Card> card1, std::shared_ptr<const Card> card2);
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;
static void get_effective_ap_tp(
StatSwapType type,
int16_t* effective_ap,
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;
StatSwapType type, int16_t* effective_ap, 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(
uint16_t card_ref,
uint8_t def_effect_index,
@@ -244,18 +210,12 @@ public:
bool is_card_targeted_by_condition(
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);
const CardDefinition::Effect* original_definition_for_condition(
const Condition& cond) const;
const CardDefinition::Effect* original_definition_for_condition(const Condition& cond) const;
bool card_ref_has_ability_trap(const Condition& eff) const;
void send_6xB4x06_for_exp_change(
std::shared_ptr<const Card> card,
uint16_t attacker_card_ref,
uint8_t dice_roll_value,
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;
std::shared_ptr<const Card> card, uint16_t attacker_card_ref, uint8_t dice_roll_value, 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(
std::shared_ptr<const Card> card,
uint16_t attacker_card_ref,
@@ -268,19 +228,14 @@ public:
std::shared_ptr<const Card> card,
uint16_t target_card_ref,
uint16_t sc_card_ref) const;
bool should_return_card_ref_to_hand_on_destruction(
uint16_t card_ref) const;
bool should_return_card_ref_to_hand_on_destruction(uint16_t card_ref) const;
size_t sum_last_attack_damage(
std::vector<std::shared_ptr<const Card>>* out_cards,
int32_t* out_damage_sum,
size_t* out_damage_count) const;
std::vector<std::shared_ptr<const Card>>* out_cards, int32_t* out_damage_sum, size_t* out_damage_count) const;
void update_condition_orders(std::shared_ptr<Card> card);
int16_t max_all_attack_bonuses(size_t* out_count) const;
void apply_effects_after_card_move(std::shared_ptr<Card> card);
void check_for_defense_interference(
std::shared_ptr<const Card> attacker_card,
std::shared_ptr<Card> target_card,
int16_t* inout_unknown_p4);
std::shared_ptr<const Card> attacker_card, std::shared_ptr<Card> target_card, int16_t* inout_unknown_p4);
void evaluate_and_apply_effects(
EffectWhen when,
uint16_t set_card_ref,
@@ -294,20 +249,19 @@ public:
ConditionType exclude_cond = ConditionType::NONE,
AssistEffect include_eff = AssistEffect::NONE,
AssistEffect exclude_eff = AssistEffect::NONE) const;
void clear_invalid_conditions_on_card(
std::shared_ptr<Card> card, const ActionState& as);
void on_card_destroyed(
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;
void clear_invalid_conditions_on_card(std::shared_ptr<Card> card, const ActionState& as);
void on_card_destroyed(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_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_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> 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>> 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(
const std::vector<std::shared_ptr<const Card>>& cards,
std::shared_ptr<const Card> card1,
@@ -334,10 +288,8 @@ public:
void apply_effects_before_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);
void unknown_8024A6DC(
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;
void unknown_8024A6DC(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;
private:
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) {}
bool Location::operator==(const Location& other) const {
return (this->x == other.x) &&
(this->y == other.y) &&
(this->direction == other.direction) &&
(this->unused == other.unused);
return (this->x == other.x) && (this->y == other.y) &&
(this->direction == other.direction) && (this->unused == other.unused);
}
bool Location::operator!=(const Location& other) const {
return !this->operator==(other);
@@ -176,13 +174,9 @@ Direction turn_around(Direction d) {
}
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
// probably because that card class just doesn't exist on NTE.
if (is_nte) {
return (cc == CardClass::TECH) || (cc == CardClass::PHOTON_BLAST);
} else {
return (cc == CardClass::TECH) || (cc == CardClass::PHOTON_BLAST) || (cc == CardClass::BOSS_TECH);
}
// NTE does not consider BOSS_TECH to be a tech-like card class, but that's probably because that card class just
// doesn't exist on NTE.
return (cc == CardClass::TECH) || (cc == CardClass::PHOTON_BLAST) || (!is_nte && (cc == CardClass::BOSS_TECH));
}
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"},
});
// 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({
/* n00 */ "Always true",
/* n01 */ "Card is Hunters-side SC",
@@ -730,8 +699,7 @@ void CardDefinition::decode_range() {
}
string name_for_rank(CardRank rank) {
static const vector<const char*> names(
{"N1", "R1", "S", "E", "N2", "N3", "N4", "R2", "R3", "R4", "SS", "D1", "D2"});
static const vector<const char*> names({"N1", "R1", "S", "E", "N2", "N3", "N4", "R2", "R3", "R4", "SS", "D1", "D2"});
try {
return names.at(static_cast<uint8_t>(rank) - 1);
} catch (const out_of_range&) {
@@ -1155,9 +1123,7 @@ void PlayerConfig::decrypt() {
return;
}
decrypt_trivial_gci_data(
&this->card_counts,
offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts),
this->basis);
&this->card_counts, offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts), this->basis);
this->is_encrypted = 0;
this->basis = 0;
}
@@ -1170,9 +1136,7 @@ void PlayerConfig::encrypt(uint8_t basis) {
this->decrypt();
}
decrypt_trivial_gci_data(
&this->card_counts,
offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts),
basis);
&this->card_counts, offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts), basis);
this->is_encrypted = 1;
this->basis = basis;
}
@@ -1215,8 +1179,7 @@ PlayerConfigNTE::PlayerConfigNTE(const PlayerConfig& config)
last_online_battle_start_timestamp(config.last_online_battle_start_timestamp),
unknown_t3(config.unknown_t3),
unknown_a14(config.unknown_a14) {
// TODO: Do we need to recompute card_count_checksums? (Here or in operator
// PlayerConfig?)
// TODO: Do we need to recompute card_count_checksums? (Here or in operator PlayerConfig?)
}
PlayerConfigNTE::operator PlayerConfig() const {
@@ -1390,8 +1353,7 @@ string Rules::str() const {
if (static_cast<uint8_t>(this->hp_type) == 0xFF) {
tokens.emplace_back("hp_type=(open)");
} else {
tokens.emplace_back(std::format("hp_type=({:02X})",
static_cast<uint8_t>(this->hp_type)));
tokens.emplace_back(std::format("hp_type=({:02X})", static_cast<uint8_t>(this->hp_type)));
}
break;
}
@@ -1439,8 +1401,7 @@ string Rules::str() const {
if (static_cast<uint8_t>(this->dice_exchange_mode) == 0xFF) {
tokens.emplace_back("dice_exchange=(open)");
} else {
tokens.emplace_back(std::format("dice_exchange=({:02X})",
static_cast<uint8_t>(this->dice_exchange_mode)));
tokens.emplace_back(std::format("dice_exchange=({:02X})", static_cast<uint8_t>(this->dice_exchange_mode)));
}
break;
}
@@ -1479,8 +1440,7 @@ string Rules::str() const {
if (static_cast<uint8_t>(this->allowed_cards) == 0xFF) {
tokens.emplace_back("allowed_cards=(open)");
} else {
tokens.emplace_back(std::format("allowed_cards=({:02X})",
static_cast<uint8_t>(this->allowed_cards)));
tokens.emplace_back(std::format("allowed_cards=({:02X})", static_cast<uint8_t>(this->allowed_cards)));
}
break;
}
@@ -1518,20 +1478,12 @@ RulesTrial::RulesTrial(const Rules& r)
no_assist_cards(r.no_assist_cards),
disable_dialogue(r.disable_dialogue),
dice_exchange_mode(r.dice_exchange_mode) {
if (r.max_dice_value == r.min_dice_value) {
this->atk_die_behavior = r.max_dice_value;
} else {
this->atk_die_behavior = 0; // Random
}
this->atk_die_behavior = (r.max_dice_value == r.min_dice_value) ? r.max_dice_value : 0;
if (r.def_dice_value_range == 0xFF) {
this->def_die_behavior = 0xFF;
} else {
auto def_range = r.def_dice_range(false);
if (def_range.first == def_range.second) {
this->def_die_behavior = def_range.first;
} else {
this->def_die_behavior = 0;
}
this->def_die_behavior = (def_range.first == def_range.second) ? def_range.first : 0;
}
}
@@ -1795,10 +1747,7 @@ phosg::JSON MapDefinition::EntryState::json() const {
default:
deck_type_json = this->deck_type;
}
return phosg::JSON::dict({
{"PlayerType", std::move(player_type_json)},
{"DeckType", std::move(deck_type_json)},
});
return phosg::JSON::dict({{"PlayerType", std::move(player_type_json)}, {"DeckType", std::move(deck_type_json)}});
}
// TODO:
@@ -1807,15 +1756,10 @@ phosg::JSON MapDefinition::EntryState::json() const {
string MapDefinition::CameraSpec::str() const {
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})]",
this->unknown_a1[0], this->unknown_a1[1],
this->unknown_a1[2], this->unknown_a1[3],
this->unknown_a1[4], this->unknown_a1[5],
this->unknown_a1[6], this->unknown_a1[7],
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]);
this->unknown_a1[0], this->unknown_a1[1], this->unknown_a1[2], this->unknown_a1[3], this->unknown_a1[4],
this->unknown_a1[5], this->unknown_a1[6], this->unknown_a1[7], 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 {
@@ -1830,21 +1774,19 @@ string MapDefinition::str(const CardIndex* card_index, Language language) const
}
};
lines.emplace_back(std::format("Map {:08X}: {}x{}",
this->map_number, this->width, this->height));
lines.emplace_back(std::format("Map {:08X}: {}x{}", this->map_number, this->width, this->height));
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(" tiles:");
add_map(this->map_tiles);
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}]",
this->start_tile_definitions[0][0], this->start_tile_definitions[0][1],
this->start_tile_definitions[0][2], this->start_tile_definitions[0][3],
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][3],
this->start_tile_definitions[1][4], this->start_tile_definitions[1][5]));
this->start_tile_definitions[0][0], this->start_tile_definitions[0][1], this->start_tile_definitions[0][2],
this->start_tile_definitions[0][3], 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][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 w = 0; w < 2; w++) {
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);
lines.emplace_back(std::format(
" unused1: {:08X} {:08X} {:08X} {:08X} {:08X}",
this->overlay_state.unused1[0],
this->overlay_state.unused1[1],
this->overlay_state.unused1[2],
this->overlay_state.unused1[3],
this->overlay_state.unused1[4]));
this->overlay_state.unused1[0], this->overlay_state.unused1[1], this->overlay_state.unused1[2],
this->overlay_state.unused1[3], this->overlay_state.unused1[4]));
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}",
this->overlay_state.trap_tile_colors_nte[0],
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[4],
this->overlay_state.trap_tile_colors_nte[5],
this->overlay_state.trap_tile_colors_nte[6],
this->overlay_state.trap_tile_colors_nte[7],
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]));
this->overlay_state.trap_tile_colors_nte[0], 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[4], this->overlay_state.trap_tile_colors_nte[5],
this->overlay_state.trap_tile_colors_nte[6], this->overlay_state.trap_tile_colors_nte[7],
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(
" 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[1],
this->overlay_state.trap_card_ids_nte[2],
this->overlay_state.trap_card_ids_nte[3],
this->overlay_state.trap_card_ids_nte[4],
this->overlay_state.trap_card_ids_nte[5],
this->overlay_state.trap_card_ids_nte[6],
this->overlay_state.trap_card_ids_nte[7],
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]));
this->overlay_state.trap_card_ids_nte[0], 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[4], this->overlay_state.trap_card_ids_nte[5],
this->overlay_state.trap_card_ids_nte[6], this->overlay_state.trap_card_ids_nte[7],
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(" 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);
break;
}
lines.emplace_back(std::format(
" entry_states[{}]: {} / {}", z, player_type, deck_type));
lines.emplace_back(std::format(" entry_states[{}]: {} / {}", z, player_type, deck_type));
}
return phosg::join(lines, "\n");
}
@@ -2480,8 +2402,7 @@ CardIndex::CardIndex(
const auto& footer = r.pget<RELFileFooterBE>(r.size() - sizeof(RELFileFooterBE));
uint32_t offset = r.pget_u32b(footer.root_offset);
uint32_t count = r.pget_u32b(footer.root_offset + 4);
if (offset > decompressed_data.size() ||
((offset + count * sizeof(CardDefinition)) > decompressed_data.size())) {
if (offset > decompressed_data.size() || ((offset + count * sizeof(CardDefinition)) > decompressed_data.size())) {
throw runtime_error("definitions array reference out of bounds");
}
CardDefinition* defs = reinterpret_cast<CardDefinition*>(decompressed_data.data() + offset);
@@ -2497,8 +2418,7 @@ CardIndex::CardIndex(
auto entry = make_shared<CardEntry>(CardEntry{def, "", "", "", {}});
if (!this->card_definitions.emplace(entry->def.card_id, entry).second) {
throw runtime_error(std::format(
"duplicate card id: {:08X}", entry->def.card_id));
throw runtime_error(std::format("duplicate card id: {:08X}", entry->def.card_id));
}
// 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);
} else {
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->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()));
}
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());
}
return compressed_map_list;
+23 -3
View File
@@ -521,11 +521,31 @@ struct CardDefinition {
/* 02 */ pstring<TextEncoding::ASCII, 0x0F> expr;
// when specifies in which phase the effect should activate.
/* 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;
// 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;
// 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;
// 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.
+14 -21
View File
@@ -92,8 +92,7 @@ bool DeckState::draw_card_by_ref(uint16_t card_ref) {
auto& entry = this->entries[index];
if (entry.state == CardState::DISCARDED) {
// If the card is discarded, then it should be before the draw index, and we
// can just change its state.
// If the card is discarded, then it should be before the draw index, and we can just change its state.
entry.state = CardState::IN_HAND;
return true;
}
@@ -102,9 +101,8 @@ bool DeckState::draw_card_by_ref(uint16_t card_ref) {
return false;
}
// If the card is still drawable, we need to move it so it's just in front of
// the draw index, then immediately draw it. Ep3 NTE does not handle this
// case, but we do even when playing NTE.
// If the card is still drawable, we need to move it so it's just in front of the draw index, then immediately draw
// it. Ep3 NTE does not handle this case, but we do even when playing NTE.
size_t ref_index;
for (ref_index = 0; ref_index < this->card_refs.size(); ref_index++) {
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) {
return 0xFFFF;
}
uint8_t index = index_for_card_ref(card_ref);
if (index < this->entries.size()) {
return this->entries[index].card_id;
} else {
return 0xFFFF;
}
return (index < this->entries.size()) ? this->entries[index].card_id : 0xFFFF;
}
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
// the already-drawn part of the deck
// For any cards that are still in hand or still in play, move their refs to the already-drawn part of the deck
this->draw_index = 0;
for (size_t z = 0; z < this->entries.size(); z++) {
if (this->entries[z].state != CardState::DRAWABLE) {
@@ -196,8 +188,7 @@ void DeckState::redraw_initial_hand(bool is_nte) {
this->draw_index = 1;
if (is_nte || this->shuffle_enabled) {
// Get the next 5 cards from the deck, and put the previous 5 cards after
// them (so they will be shuffled back in).
// Get the next 5 cards from the deck, and put the previous 5 cards after them (so they will be shuffled back in).
for (uint8_t z = 0; z < 5; z++) {
uint8_t index = z + this->draw_index;
uint16_t temp_ref = this->card_refs[index];
@@ -274,11 +265,9 @@ void DeckState::shuffle() {
size_t max = this->num_drawable_cards();
for (size_t z = 0; z < this->card_refs.size(); z++) {
// Note: This is the way Sega originally implemented shuffling - they just
// do N swaps on the entire array. A more uniform way to do it would be to
// 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.
// Note: This is the way Sega originally implemented shuffling - they just do N swaps on the entire array. A more
// uniform way to do it would be to 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.
uint8_t index1 = 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];
@@ -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 {
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++) {
const auto& e = this->entries[z];
shared_ptr<const CardIndex::CardEntry> ce;
+3 -7
View File
@@ -28,9 +28,8 @@ struct DeckEntry {
/* 00 */ pstring<TextEncoding::MARKED, 0x10> name;
/* 10 */ le_uint32_t team_id;
/* 14 */ parray<le_uint16_t, 31> card_ids;
// If the following flag is not set to 3, then the God Whim assist effect can
// use cards that are hidden from the player during deck building. The client
// always sets this to 3, and it's not clear why this even exists.
// If the following flag is not set to 3, then the God Whim assist effect can use cards that are hidden from the
// player during deck building. The client always sets this to 3, and it's not clear why this even exists.
/* 52 */ uint8_t god_whim_flag;
/* 53 */ uint8_t unused1;
/* 54 */ le_uint16_t player_level;
@@ -56,10 +55,7 @@ public:
};
template <typename CardIDT>
DeckState(
uint8_t client_id,
const parray<CardIDT, 0x1F>& card_ids,
std::shared_ptr<Server> server)
DeckState(uint8_t client_id, const parray<CardIDT, 0x1F>& card_ids, std::shared_ptr<Server> server)
: server(server),
client_id(client_id),
draw_index(1),
+41 -88
View File
@@ -110,9 +110,7 @@ void PlayerState::init() {
this->set_card_action_chains,
this->set_card_action_metadatas);
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);
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;
}
void PlayerState::apply_assist_card_effect_on_set(
shared_ptr<PlayerState> setter_ps) {
void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter_ps) {
auto s = this->server();
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());
if ((assist_effect == AssistEffect::RESISTANCE) ||
(assist_effect == AssistEffect::INDEPENDENT)) {
if ((assist_effect == AssistEffect::RESISTANCE) || (assist_effect == AssistEffect::INDEPENDENT)) {
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);
if (other_ps.get() != this) {
other_ps->deck_state->draw_card_by_ref(this->card_refs[7]);
other_ps->set_card_from_hand(
this->card_refs[7], 0xF, nullptr, client_id, 1);
other_ps->set_card_from_hand(this->card_refs[7], 0xF, nullptr, client_id, 1);
}
}
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++) {
auto card = (set_index == -1)
? other_ps->get_sc_card()
: other_ps->get_set_card(set_index);
auto card = (set_index == -1) ? other_ps->get_sc_card() : other_ps->get_set_card(set_index);
if (card) {
for (size_t cond_index = 0; cond_index < 9; cond_index++) {
auto& cond = card->action_chain.conditions[cond_index];
@@ -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++) {
auto card = (set_index == -1)
? other_ps->get_sc_card()
: other_ps->get_set_card(set_index);
auto card = (set_index == -1) ? other_ps->get_sc_card() : other_ps->get_set_card(set_index);
if (card) {
for (size_t cond_index = 0; cond_index < 9; cond_index++) {
auto& cond = card->action_chain.conditions[cond_index];
@@ -577,8 +568,7 @@ void PlayerState::discard_all_attack_action_cards_from_hand() {
for (size_t hand_index = 0; hand_index < 6; hand_index++) {
uint16_t card_ref = temp_card_refs[hand_index];
auto ce = s->definition_for_card_ref(card_ref);
if (ce && (ce->def.type == CardType::ACTION) &&
(ce->def.card_class() != CardClass::DEFENSE_ACTION)) {
if (ce && (ce->def.type == CardType::ACTION) && (ce->def.card_class() != CardClass::DEFENSE_ACTION)) {
this->discard_ref_from_hand(card_ref);
}
}
@@ -771,9 +761,8 @@ void PlayerState::draw_hand(ssize_t override_count) {
}
void PlayerState::draw_initial_hand() {
// Note: The original code called this->deck_state->init_card_states here, but
// we don't because that logic is now in the DeckState constructor, and this
// function should only be called during PlayerState construction (so, shortly
// Note: The original code called this->deck_state->init_card_states here, but we don't because that logic is now in
// the DeckState constructor, and this function should only be called during PlayerState construction (so, shortly
// after DeckState construction as well).
this->deck_state->restart();
this->card_refs.clear(0xFFFF);
@@ -782,10 +771,7 @@ void PlayerState::draw_initial_hand() {
}
int32_t PlayerState::error_code_for_client_setting_card(
uint16_t card_ref,
uint8_t card_index,
const Location* loc,
uint8_t assist_target_client_id) const {
uint16_t card_ref, uint8_t card_index, const Location* loc, uint8_t assist_target_client_id) const {
auto s = this->server();
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) {
return -0x7E;
}
if ((ce->def.type == CardType::CREATURE) &&
!s->map_and_rules->tile_is_vacant(loc->x, loc->y)) {
if ((ce->def.type == CardType::CREATURE) && !s->map_and_rules->tile_is_vacant(loc->x, loc->y)) {
return -0x7A;
}
return 0;
@@ -834,9 +819,7 @@ int32_t PlayerState::error_code_for_client_setting_card(
}
vector<uint16_t> PlayerState::get_all_cards_within_range(
const parray<uint8_t, 9 * 9>& range,
const Location& loc,
uint8_t target_team_id) const {
const parray<uint8_t, 9 * 9>& range, const Location& loc, uint8_t target_team_id) const {
auto s = this->server();
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;
for (size_t client_id = 0; client_id < 4; client_id++) {
auto other_ps = s->player_states[client_id];
if (other_ps &&
((target_team_id == 0xFF) || (target_team_id == other_ps->get_team_id()))) {
if (other_ps && ((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);
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 {
auto s = this->server();
// Note: The original code checks if this->w_server is null before doing this.
// We don't check because that should never happen, and server() will throw if
// it does.
// Note: The original code checks if this->w_server is null before doing this. We don't check because that should
// never happen, and server() will throw if it does.
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) {
// NTE appears to have a bug here (or some obviated code): it searches for an
// entry with the given reason, then ignores the result of that search and
// always returns the first entry instead.
// NTE appears to have a bug here (or some obviated code): it searches for an entry with the given reason, then
// ignores the result of that search and always returns the first entry instead. That code is:
// size_t z;
// for (size_t z = 0; z < this->discard_log_card_refs.size(); z++) {
// if ((this->discard_log_card_refs[z] != 0xFFFF) && (this->discard_log_reasons[z] == reason)) {
@@ -1030,9 +1010,7 @@ void PlayerState::move_null_hand_refs_to_end() {
void PlayerState::on_cards_destroyed() {
auto s = this->server();
// {card_ref: should_return_to_hand}
unordered_multimap<uint16_t, bool> card_refs_map;
unordered_multimap<uint16_t, bool> card_refs_map; // {card_ref: should_return_to_hand}
for (size_t z = 0; z < 8; z++) {
auto card = this->set_cards[z];
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);
for (size_t client_id = 0; client_id < 4; client_id++) {
auto other_ps = s->get_player_state(client_id);
if (other_ps &&
((other_ps->card_refs[6] != 0xFFFF) || (!is_nte && (other_ps->set_assist_card_id != 0xFFFF)))) {
if (other_ps && ((other_ps->card_refs[6] != 0xFFFF) || (!is_nte && (other_ps->set_assist_card_id != 0xFFFF)))) {
uint16_t card_id = 0x0130;
while (card_id == 0x0130) { // God Whim
size_t index = s->get_random(assist_card_ids.size());
@@ -1355,8 +1332,7 @@ bool PlayerState::set_card_from_hand(
return 0;
}
this->card_refs[card_index + 1] = card_ref;
// Note: NTE doesn't call the destructor on the existing card, if there is
// one. Is that a bug?
// Note: NTE doesn't call the destructor on the existing card, if there is one. Is that a bug?
this->set_cards[card_index - 7] = make_shared<Card>(s->card_id_for_card_ref(card_ref), card_ref, this->client_id, s);
auto new_card = this->set_cards[card_index - 7];
new_card->init();
@@ -1365,8 +1341,7 @@ bool PlayerState::set_card_from_hand(
new_card->loc.x = loc->x;
new_card->loc.y = loc->y;
}
// Note: NTE doesn't track this, but NTE can't use it anyway, so we don't
// check for NTE here.
// Note: NTE doesn't track this, but NTE can't use it anyway, so we don't check for NTE here.
this->stats.num_item_or_creature_cards_set++;
} else if (ce->def.type == CardType::ASSIST) {
@@ -1436,7 +1411,6 @@ bool PlayerState::set_card_from_hand(
void PlayerState::set_initial_location() {
auto s = this->server();
auto mr = s->map_and_rules;
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(
shared_ptr<const Card> card) {
void PlayerState::set_map_occupied_bit_for_card_on_warp_tile(shared_ptr<const Card> card) {
if (!card) {
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) &&
(s->warp_positions[warp_type][warp_end][1] == card->loc.y)) {
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][1]);
s->warp_positions[warp_type][warp_end ^ 1][0], 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();
if (this->sc_card && !(this->sc_card->card_flags & 2)) {
s->map_and_rules->set_occupied_bit_for_tile(
this->sc_card->loc.x, this->sc_card->loc.y);
s->map_and_rules->set_occupied_bit_for_tile(this->sc_card->loc.x, this->sc_card->loc.y);
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++) {
auto card = this->set_cards[set_index];
if (card) {
s->map_and_rules->set_occupied_bit_for_tile(
card->loc.x, card->loc.y);
s->map_and_rules->set_occupied_bit_for_tile(card->loc.x, card->loc.y);
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;
}
bool PlayerState::subtract_or_check_atk_or_def_points_for_action(
const ActionState& pa, bool deduct_points) {
bool PlayerState::subtract_or_check_atk_or_def_points_for_action(const ActionState& pa, bool deduct_points) {
auto s = this->server();
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.sc_card_ref = this->sc_card_ref;
cmd.state.assist_card_ref2 = this->card_refs[6];
cmd.state.assist_card_set_number = (this->card_refs[6] == 0xFFFF)
? 0
: this->assist_card_set_number;
cmd.state.assist_card_set_number = (this->card_refs[6] == 0xFFFF) ? 0 : this->assist_card_set_number;
cmd.state.assist_card_id = this->set_assist_card_id;
cmd.state.assist_remaining_turns = this->assist_remaining_turns;
cmd.state.assist_delay_turns = this->assist_delay_turns;
@@ -1591,8 +1558,7 @@ G_UpdateHand_Ep3_6xB4x02 PlayerState::prepare_6xB4x02() const {
return cmd;
}
void PlayerState::update_hand_and_equip_state_and_send_6xB4x02_if_needed(
bool always_send) {
void PlayerState::update_hand_and_equip_state_and_send_6xB4x02_if_needed(bool always_send) {
auto cmd = this->prepare_6xB4x02();
if (always_send || memcmp(&this->hand_and_equip, &cmd.state, sizeof(this->hand_and_equip))) {
*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()) {
this->discard_set_assist_card();
size_t index = s->get_random(candidate_card_refs.size());
this->set_card_from_hand(
candidate_card_refs[index], 15, nullptr, this->client_id, 1);
this->set_card_from_hand(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++) {
this->get_short_status_for_card_index_in_hand(
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 hand refs anyway, but we might as well
// be as consistent as possible.
this->get_short_status_for_card_index_in_hand(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
// hand refs anyway, but we might as well be as consistent as possible.
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(
const parray<uint8_t, 9 * 9>& range,
const Location& loc,
CardType type) const {
const parray<uint8_t, 9 * 9>& range, const Location& loc, CardType type) const {
auto s = this->server();
vector<uint16_t> ret;
@@ -1726,8 +1687,7 @@ void PlayerState::move_phase_before() {
void PlayerState::handle_before_turn_assist_effects() {
auto s = this->server();
if ((this->assist_delay_turns > 0) &&
(--this->assist_delay_turns == 0)) {
if ((this->assist_delay_turns > 0) && (--this->assist_delay_turns == 0)) {
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);
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();
break;
case AssistEffect::ATK_DICE_2:
// Note: This behavior doesn't match the card description. Is it
// supposed to add 2 or multiply by 2?
// Note: This behavior doesn't match the card description. Is it supposed to add 2 or multiply by 2?
this->atk_points = min<int16_t>(this->atk_points + 2, 9);
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
break;
@@ -1885,9 +1844,7 @@ void PlayerState::dice_phase_before() {
this->compute_total_set_cards_cost();
this->unknown_a14 = 0;
if ((this->assist_remaining_turns > 0) &&
(this->assist_remaining_turns < 90) &&
(this->assist_delay_turns == 0)) {
if ((this->assist_remaining_turns > 0) && (this->assist_remaining_turns < 90) && (this->assist_delay_turns == 0)) {
this->assist_remaining_turns--;
if (this->assist_remaining_turns < 1) {
this->discard_set_assist_card();
@@ -1984,12 +1941,10 @@ void PlayerState::roll_main_dice_or_apply_after_effects() {
auto s = this->server();
const auto& rules = s->map_and_rules->rules;
// In NTE, the dice behave differently - there is no minimum, and instead the
// player can specify a fixed value for each die or a random value (1-6). The
// implementation of this function is therefore quite different on NTE, but
// since we already support custom ranges for ATK and DEF dice, we just use
// the non-NTE logic and assign the dice ranges at battle start time to yield
// the NTE behavior. (See RulesTrial in DataIndexes.cc for how this is done.)
// In NTE, the dice behave differently - there is no minimum, and instead the player can specify a fixed value for
// each die or a random value (1-6). The implementation of this function is therefore quite different on NTE, but
// since we already support custom ranges for ATK and DEF dice, we just use the non-NTE logic and assign the dice
// ranges at battle start time to yield the NTE behavior. (See RulesTrial in DataIndexes.cc for how this is done.)
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 dice_boost = s->get_team_exp(current_team_turn) /
(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);
uint8_t dice_boost = s->get_team_exp(current_team_turn) / (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->team_dice_bonus[current_team_turn] = clamp<int16_t>(dice_boost, 0, 8);
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
}
+15 -23
View File
@@ -15,9 +15,8 @@ namespace Episode3 {
class Server;
enum AssistFlag : uint32_t {
// Note: This enum is a uint32_t even though only 16 bits are used because
// the corresponding field in the protocol is a 32-bit field. There may also
// be bits used only by the client which are not documented here.
// Note: This enum is a uint32_t even though only 16 bits are used because the corresponding field in the protocol is
// a 32-bit field. There may also be bits used only by the client which are not documented here.
// clang-format off
NONE = 0x0000,
@@ -70,14 +69,9 @@ public:
void draw_hand(ssize_t override_count = 0);
void draw_initial_hand();
int32_t error_code_for_client_setting_card(
uint16_t card_ref,
uint8_t card_index,
const Location* loc,
uint8_t assist_target_client_id) const;
uint16_t card_ref, uint8_t card_index, const Location* loc, uint8_t assist_target_client_id) const;
std::vector<uint16_t> get_all_cards_within_range(
const parray<uint8_t, 9 * 9>& range,
const Location& loc,
uint8_t target_team_id) const;
const parray<uint8_t, 9 * 9>& range, const Location& loc, uint8_t target_team_id) const;
uint8_t get_atk_points() const;
void get_short_status_for_card_index_in_hand(size_t hand_index, CardShortStatus* stat) const;
std::shared_ptr<DeckState> get_deck();
@@ -128,9 +122,7 @@ public:
G_UpdateShortStatuses_Ep3_6xB4x04 prepare_6xB4x04() const;
void send_6xB4x04_if_needed(bool always_send = false);
std::vector<uint16_t> get_card_refs_within_range_from_all_players(
const parray<uint8_t, 9 * 9>& range,
const Location& loc,
CardType type) const;
const parray<uint8_t, 9 * 9>& range, const Location& loc, CardType type) const;
void draw_phase_before();
void action_phase_before();
void move_phase_before();
@@ -169,10 +161,10 @@ public:
uint16_t sc_card_ref;
// This array is unfortunately heterogeneous; specifically:
// [0] through [5] are hand refs
// [6] is the current assist card ref (which may belong to another player)
// [7] is the previous assist card ref
// [8] through [15] are set refs
// [0] through [5] are hand refs
// [6] is the current assist card ref (which may belong to another player)
// [7] is the previous assist card ref
// [8] through [15] are set refs
parray<uint16_t, 0x10> card_refs;
std::shared_ptr<DeckState> deck_state;
@@ -190,12 +182,12 @@ public:
Direction start_facing_direction;
std::shared_ptr<HandAndEquipState> hand_and_equip;
// Like card_refs above, these arrays are also heterogeneous, but the indices
// are not the same as for card_refs! THe indices here are:
// [0] is the SC card status
// [1] through [6] are hand cards
// [7] through [14] are set cards
// [15] is the assist card
// Like card_refs above, these arrays are also heterogeneous, but the indices are not the same as for card_refs! The
// indices here are:
// [0] is the SC card status
// [1] through [6] are hand cards
// [7] through [14] are set cards
// [15] is the assist card
std::shared_ptr<parray<CardShortStatus, 0x10>> 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 {
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(
"Condition[type={}, turns={}, a_arg={}, dice={}, flags={:02X}, "
"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->flags,
this->card_definition_effect_index,
card_ref_str,
s->debug_str_for_card_ref(this->card_ref),
this->value,
giver_ref_str,
s->debug_str_for_card_ref(this->condition_giver_card_ref),
this->random_percent,
this->value8,
this->order,
@@ -101,14 +99,10 @@ void EffectResult::clear() {
}
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(
"EffectResult[att_ref={}, target_ref={}, value={}, "
"cur_hp={}, ap={}, tp={}, flags={:02X}, op={}, "
"cond_index={}, dice={}]",
attacker_ref_str,
target_ref_str,
"EffectResult[att_ref={}, target_ref={}, value={}, cur_hp={}, ap={}, tp={}, flags={:02X}, op={}, cond_index={}, dice={}]",
s->debug_str_for_card_ref(this->attacker_card_ref),
s->debug_str_for_card_ref(this->target_card_ref),
this->value,
this->current_hp,
this->ap,
@@ -137,15 +131,12 @@ bool CardShortStatus::operator!=(const CardShortStatus& other) 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(
"CardShortStatus[ref={}, cur_hp={}, flags={:08X}, loc={}, "
"u1={:04X}, max_hp={}, u2={}]",
ref_str,
"CardShortStatus[ref={}, cur_hp={}, flags={:08X}, loc={}, u1={:04X}, max_hp={}, u2={}]",
s->debug_str_for_card_ref(this->card_ref),
this->current_hp,
this->card_flags,
loc_s,
this->loc.str(),
this->unused1,
this->max_hp,
this->unused2);
@@ -188,23 +179,16 @@ void ActionState::clear() {
}
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(
"ActionState[client={:X}, u={}, facing={}, attacker_ref={}, "
"def_ref={}, target_refs={}, action_refs={}, "
"orig_attacker_ref={}]",
"ActionState[client={:X}, u={}, facing={}, attacker_ref={}, def_ref={}, target_refs={}, action_refs={}, orig_attacker_ref={}]",
this->client_id,
this->unused,
phosg::name_for_enum(this->facing_direction),
attacker_ref_s,
defense_ref_s,
target_refs_s,
action_refs_s,
original_attacker_ref_s);
s->debug_str_for_card_ref(this->attacker_card_ref),
s->debug_str_for_card_ref(this->defense_card_ref),
s->debug_str_for_card_refs(this->target_card_refs),
s->debug_str_for_card_refs(this->action_card_refs),
s->debug_str_for_card_ref(this->original_attacker_card_ref));
}
ActionChain::ActionChain() {
@@ -239,24 +223,17 @@ bool ActionChain::operator!=(const ActionChain& other) 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(
"ActionChain[eff_ap={}, eff_tp={}, ap_bonus={}, damage={}, "
"acting_ref={}, unknown_ref_a3={}, attack_action_refs={}, "
"attack_action_ref_count={}, medium={}, target_ref_count={}, "
"subphase={}, strikes={}, damage_mult={}, attack_num={}, "
"tp_bonus={}, phys_bonus_nte={}, tech_bonus_nte={}, card_ap={}, "
"card_tp={}, flags={:08X}, target_refs={}]",
"ActionChain[eff_ap={}, eff_tp={}, ap_bonus={}, damage={}, acting_ref={}, unknown_ref_a3={}, attack_action_refs={}, "
"attack_action_ref_count={}, medium={}, target_ref_count={}, 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_tp,
this->ap_effect_bonus,
this->damage,
acting_card_ref_s,
unknown_card_ref_a3_s,
attack_action_card_refs_s,
s->debug_str_for_card_ref(this->acting_card_ref),
s->debug_str_for_card_ref(this->unknown_card_ref_a3),
s->debug_str_for_card_refs(this->attack_action_card_refs),
this->attack_action_card_ref_count,
phosg::name_for_enum(this->attack_medium),
this->target_card_ref_count,
@@ -270,7 +247,7 @@ std::string ActionChain::str(shared_ptr<const Server> s) const {
this->card_ap,
this->card_tp,
this->flags,
target_card_refs_s);
s->debug_str_for_card_refs(this->target_card_refs));
}
void ActionChain::clear() {
@@ -406,8 +383,7 @@ void ActionChainWithConds::set_flags(uint32_t flags) {
this->chain.flags |= flags;
}
void ActionChainWithConds::add_attack_action_card_ref(
uint16_t card_ref, shared_ptr<Server> server) {
void ActionChainWithConds::add_attack_action_card_ref(uint16_t card_ref, shared_ptr<Server> server) {
if (card_ref != 0xFFFF) {
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) {
if (card_ref != 0xFFFF &&
this->chain.target_card_ref_count < this->chain.target_card_refs.size()) {
if (card_ref != 0xFFFF && 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;
}
}
@@ -440,11 +415,7 @@ void ActionChainWithConds::compute_attack_medium(shared_ptr<Server> server) {
}
bool ActionChainWithConds::get_condition_value(
ConditionType cond_type,
uint16_t card_ref,
uint8_t def_effect_index,
uint16_t value,
uint16_t* out_value) const {
ConditionType cond_type, uint16_t card_ref, uint8_t def_effect_index, uint16_t value, uint16_t* out_value) const {
bool any_found = false;
uint8_t max_order = 10;
for (size_t z = 0; z < 9; z++) {
@@ -466,8 +437,7 @@ bool ActionChainWithConds::get_condition_value(
return any_found;
}
void ActionChainWithConds::set_action_subphase_from_card(
shared_ptr<const Card> card) {
void ActionChainWithConds::set_action_subphase_from_card(shared_ptr<const Card> card) {
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 {
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(
"ActionMetadata[ref={}, target_ref_count={}, def_ref_count={}, "
"subphase={}, def_power={}, def_bonus={}, "
"att_bonus={}, flags={:08X}, target_refs={}, "
"defense_refs={}, original_attacker_refs={}]",
card_ref_s,
"ActionMetadata[ref={}, target_ref_count={}, def_ref_count={}, subphase={}, def_power={}, def_bonus={}, "
"att_bonus={}, flags={:08X}, target_refs={}, defense_refs={}, original_attacker_refs={}]",
s->debug_str_for_card_ref(this->card_ref),
this->target_card_ref_count,
this->defense_card_ref_count,
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->attack_bonus,
this->flags,
target_card_refs_s,
defense_card_refs_s,
original_attacker_card_refs_s);
s->debug_str_for_card_refs(this->target_card_refs),
s->debug_str_for_card_refs(this->defense_card_refs),
s->debug_str_for_card_refs(this->original_attacker_card_refs));
}
void ActionMetadata::clear() {
@@ -605,8 +569,7 @@ void ActionMetadata::clear() {
this->action_subphase = ActionSubphase::INVALID_FF;
this->defense_power = 0;
this->defense_bonus = 0;
// TODO: Ep3 NTE doesn't set attack_bonus to zero here. Is the field just
// unused in NTE?
// TODO: Ep3 NTE doesn't set attack_bonus to zero here. Is the field just unused in NTE?
this->attack_bonus = 0;
this->flags = 0;
this->target_card_refs.clear(0xFFFF);
@@ -652,16 +615,13 @@ void ActionMetadata::clear_target_card_refs() {
}
void ActionMetadata::add_target_card_ref(uint16_t card_ref) {
if (card_ref != 0xFFFF &&
this->target_card_ref_count < this->target_card_refs.size()) {
if ((card_ref != 0xFFFF) && (this->target_card_ref_count < this->target_card_refs.size())) {
this->target_card_refs[this->target_card_ref_count++] = card_ref;
}
}
void ActionMetadata::add_defense_card_ref(
uint16_t defense_card_ref,
shared_ptr<Card> card,
uint16_t original_attacker_card_ref) {
uint16_t defense_card_ref, shared_ptr<Card> card, uint16_t original_attacker_card_ref) {
if ((defense_card_ref != 0xFFFF) && (this->defense_card_ref_count < 8)) {
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;
@@ -675,21 +635,10 @@ HandAndEquipState::HandAndEquipState() {
}
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(
"HandAndEquipState[dice=[{}, {}], atk={}, def={}, atk2={}, "
"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={}, "
"assist_turns={}, assist_delay={}, atk_bonus={}, def_bonus={}, "
"u2=[{}, {}]]",
"HandAndEquipState[dice=[{}, {}], atk={}, def={}, atk2={}, 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={}, "
"assist_turns={}, assist_delay={}, atk_bonus={}, def_bonus={}, u2=[{}, {}]]",
this->dice_results[0],
this->dice_results[1],
this->atk_points,
@@ -699,15 +648,15 @@ std::string HandAndEquipState::str(shared_ptr<const Server> s) const {
this->total_set_cards_cost,
this->is_cpu_player,
this->assist_flags,
hand_card_refs_s,
assist_card_ref_s,
set_card_refs_s,
sc_card_ref_s,
hand_card_refs2_s,
set_card_refs2_s,
assist_card_ref2_s,
s->debug_str_for_card_refs(this->hand_card_refs),
s->debug_str_for_card_ref(this->assist_card_ref),
s->debug_str_for_card_refs(this->set_card_refs),
s->debug_str_for_card_ref(this->sc_card_ref),
s->debug_str_for_card_refs(this->hand_card_refs2),
s->debug_str_for_card_refs(this->set_card_refs2),
s->debug_str_for_card_ref(this->assist_card_ref2),
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_delay_turns,
this->atk_bonuses,
@@ -795,17 +744,15 @@ void PlayerBattleStats::clear() {
float PlayerBattleStats::score(size_t num_rounds) const {
// Note: This formula doesn't match the formula on PSO-World, which is:
// 35
// + (Attack Damage - Damage Taken)
// + (Max Card Combo x 3)
// - (Story Character Damage x 1.8)
// - (Turns x 2.7)
// + (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 correct. Is the PSO-World formula simply
// incorrect, or is it from e.g. the Japanese version, which may have a
// 35 + (Attack Damage - Damage Taken)
// + (Max Card Combo x 3)
// - (Story Character Damage x 1.8)
// - (Turns x 2.7)
// + (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
// 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?
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 {
@@ -817,10 +764,8 @@ const char* PlayerBattleStats::rank_name(size_t num_rounds) const {
}
constexpr size_t RANK_THRESHOLD_COUNT = 9;
static const float RANK_THRESHOLDS[RANK_THRESHOLD_COUNT] = {
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 float RANK_THRESHOLDS[RANK_THRESHOLD_COUNT] = {15, 25, 30, 40, 50, 60, 65, 75, 85};
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) {
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 (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;
}
if ((ss.loc.y < anchor_loc.y - 4) || (ss.loc.y > anchor_loc.y + 4)) {
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;
}
+3 -7
View File
@@ -102,8 +102,7 @@ struct ActionState {
} __packed_ws__(ActionState, 0x64);
struct ActionChain {
// Note: Episode 3 Trial Edition has a different format for this structure.
// See ActionChainWithCondsTrial for details.
// Note: Trial Edition has a different format for this structure. See ActionChainWithCondsTrial for details.
/* 00 */ int8_t effective_ap;
/* 01 */ int8_t effective_tp;
/* 02 */ int8_t ap_effect_bonus;
@@ -196,8 +195,7 @@ struct ActionChainWithCondsTrial {
/* 0022 */ int8_t card_ap;
/* 0023 */ int8_t card_tp;
/* 0024 */ le_uint32_t flags;
// The only difference between this structure and ActionChainWithConds is that
// these two fields are in the opposite order.
// The only difference between this structure and ActionChainWithConds is that these two fields are swapped.
/* 0028 */ parray<Condition, 9> conditions;
/* 00B8 */ parray<le_uint16_t, 4 * 9> target_card_refs;
/* 0100 */
@@ -236,9 +234,7 @@ struct ActionMetadata {
void clear_target_card_refs();
void add_target_card_ref(uint16_t card_ref);
void add_defense_card_ref(
uint16_t defense_card_ref,
std::shared_ptr<Card> card,
uint16_t original_attacker_card_ref);
uint16_t defense_card_ref, std::shared_ptr<Card> card, uint16_t original_attacker_card_ref);
std::string str(std::shared_ptr<const Server> s) const;
} __packed_ws__(ActionMetadata, 0x74);
+82 -135
View File
@@ -25,8 +25,7 @@ void compute_effective_range(
ret.clear(0);
parray<uint32_t, 6> range_def;
if (card_id == 0xFFFE) {
// Heavy Fog: one tile directly in front
if (card_id == 0xFFFE) { // Heavy Fog: one tile directly in front
range_def[3] = 0x00000100;
} else {
shared_ptr<const CardIndex::CardEntry> ce;
@@ -40,11 +39,11 @@ void compute_effective_range(
}
}
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) {
// Entire field
if (range_def[0] == 0x000FFFFF) { // Entire field
ret.clear(2);
if (log) {
log->debug_f("compute_effective_range: entire field (2)");
@@ -65,7 +64,9 @@ void compute_effective_range(
if (log) {
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}",
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];
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) {
for (size_t y = 0; y < 9; y++) {
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 = sc_ce->def.is_named_android_sc();
if (sc_is_named_android &&
!has_permission_effect &&
(left_ce->def.type == CardType::ITEM)) {
if (sc_is_named_android && !has_permission_effect && (left_ce->def.type == CardType::ITEM)) {
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++) {
uint8_t right_color = left_ce->def.right_colors[x];
if ((right_color != 0) &&
(!sc_is_named_android_without_permission_effect || (right_color != 3))) {
if ((right_color != 0) && (!sc_is_named_android_without_permission_effect || (right_color != 3))) {
for (size_t y = 0; y < 8; y++) {
if (right_color == right_ce->def.left_colors[y]) {
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
// cards' left/right colors. It may still be allowed if Permission is in
// effect, though.
// If we get here, then the linkage does not make sense based only on the cards' left/right colors. It may still be
// allowed if Permission is in effect, though.
// Ignore Permission effect if the left card is another action card (the Tech
// color linkage must make sense in that case). (The way they do this is kind
// 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 an oversight.)
// Ignore Permission effect if the left card is another action card (the Tech color linkage must make sense in that
// case). (The way they do this is kind 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
// an oversight.)
if (has_permission_effect &&
(left_ce->def.type != CardType::HUNTERS_SC) &&
(left_ce->def.type != CardType::ARKZ_SC) &&
@@ -198,17 +196,14 @@ shared_ptr<const Server> RulerServer::server() const {
return s;
}
ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(
uint16_t card_ref) {
ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(uint16_t 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(
uint16_t card_ref) const {
const ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(uint16_t card_ref) const {
uint8_t client_id = client_id_for_card_ref(card_ref);
if (client_id != 0xFF) {
// There appears to be a bug in Trial Edition: the bound on this loop is
// 0x10, not 9.
// There appears to be a bug in Trial Edition: the bound on this loop is 0x10, not 9.
for (size_t z = 0; z < 9; z++) {
const auto* chain = &this->set_card_action_chains[client_id]->at(z);
if (card_ref == chain->chain.acting_card_ref) {
@@ -219,8 +214,7 @@ const ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(
return nullptr;
}
bool RulerServer::any_attack_action_card_is_support_tech_or_support_pb(
const ActionState& pa) const {
bool RulerServer::any_attack_action_card_is_support_tech_or_support_pb(const ActionState& pa) const {
if (pa.attacker_card_ref != 0xFFFF) {
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]);
@@ -292,8 +286,7 @@ bool RulerServer::card_has_pierce_or_rampage(
const auto& sc_status = short_statuses->at(0);
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.
if (ce &&
(!is_nte || (ce->def.type == CardType::HUNTERS_SC)) &&
if (ce && (!is_nte || (ce->def.type == CardType::HUNTERS_SC)) &&
(this->get_card_ref_max_hp(sc_status.card_ref) <= sc_status.current_hp * 2)) {
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(
pa.attacker_card_ref);
const auto* chain = this->action_chain_with_conds_for_card_ref(pa.attacker_card_ref);
if (chain) {
for (ssize_t z = 8; z >= 0; z--) {
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) &&
(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;
}
@@ -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--) {
auto ce = this->definition_for_card_ref(
pa.action_card_refs[last_action_card_index]);
auto ce = this->definition_for_card_ref(pa.action_card_refs[last_action_card_index]);
if (!ce) {
continue;
}
@@ -560,8 +552,7 @@ bool RulerServer::card_ref_can_attack(uint16_t card_ref) {
return true;
}
size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(
client_id);
size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(client_id);
for (size_t z = 0; z < num_assists; z++) {
if (this->assist_server->get_active_assist_by_index(z) == AssistEffect::PERMISSION) {
return true;
@@ -571,8 +562,7 @@ bool RulerServer::card_ref_can_attack(uint16_t card_ref) {
return !ce->def.cannot_attack;
}
bool RulerServer::card_ref_can_move(
uint8_t client_id, uint16_t card_ref, bool ignore_atk_points) const {
bool RulerServer::card_ref_can_move(uint8_t client_id, uint16_t card_ref, bool ignore_atk_points) const {
if (client_id == 0xFF) {
return false;
}
@@ -644,8 +634,7 @@ bool RulerServer::card_ref_can_move(
}
}
bool RulerServer::card_ref_has_class_usability_condition(
uint16_t card_ref) const {
bool RulerServer::card_ref_has_class_usability_condition(uint16_t card_ref) const {
auto ce = this->definition_for_card_ref(card_ref);
if (ce) {
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);
}
bool RulerServer::card_ref_is_aerial_or_has_free_maneuver(
uint16_t card_ref) const {
bool RulerServer::card_ref_is_aerial_or_has_free_maneuver(uint16_t card_ref) const {
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));
}
bool RulerServer::card_ref_or_any_set_card_has_condition_46(
uint16_t card_ref) const {
bool RulerServer::card_ref_or_any_set_card_has_condition_46(uint16_t card_ref) const {
uint16_t card_id = this->card_id_for_card_ref(card_ref);
if (card_id == 0xFFFF) {
return false;
@@ -752,8 +739,7 @@ bool RulerServer::card_ref_or_sc_has_fixed_range(uint16_t card_ref) const {
return false;
}
return this->find_condition_on_card_ref(
this->short_statuses[client_id]->at(0).card_ref, ConditionType::FIXED_RANGE);
return this->find_condition_on_card_ref(this->short_statuses[client_id]->at(0).card_ref, ConditionType::FIXED_RANGE);
}
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;
// Note: In the original code, it seems atk was signed, which doesn't make
// much sense. We've fixed that here.
// if (atk < 0) { // Uhhh what? This is supposed to be impossible
// Note: In the original code, it seems atk was signed, which doesn't make much sense.
// if (atk < 0) { // This is supposed to be impossible
// return false;
// }
@@ -833,8 +818,7 @@ bool RulerServer::check_pierce_and_rampage(
return false;
}
if ((card_ref != 0xFFFF) &&
(!card_short_status || !this->card_exists_by_status(*card_short_status))) {
if ((card_ref != 0xFFFF) && (!card_short_status || !this->card_exists_by_status(*card_short_status))) {
return false;
}
@@ -971,8 +955,7 @@ bool RulerServer::check_usability_or_condition_apply(
}
log.debug_f("criterion_code={}", phosg::name_for_enum(criterion_code));
// For item usability checks, prevent criteria that depend on player
// positioning/team setup
// For item usability checks, prevent criteria that depend on player positioning/team setup
if (is_item_usability_check &&
((criterion_code == CriterionCode::SAME_TEAM) ||
(criterion_code == CriterionCode::SAME_PLAYER) ||
@@ -984,13 +967,11 @@ bool RulerServer::check_usability_or_condition_apply(
criterion_code = CriterionCode::NONE;
}
// Presumably this odd-looking expression here is used to handle two different
// cases. When checking for a condition, def_effect_index should be non-0xFF,
// 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 true if the criterion passes. If
// neither of these cases apply, we should return false as a failsafe even if
// the criterion passes. NTE did not have such a check.
// Presumably this odd-looking expression here is used to handle two different cases. When checking for a condition,
// def_effect_index should be non-0xFF, 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
// true if the criterion passes. If 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);
switch (criterion_code) {
case CriterionCode::NONE:
@@ -1359,9 +1340,7 @@ bool RulerServer::check_usability_or_condition_apply(
}
uint16_t RulerServer::compute_attack_or_defense_costs(
const ActionState& pa,
bool allow_mighty_knuckle,
uint8_t* out_ally_cost) const {
const ActionState& pa, bool allow_mighty_knuckle, uint8_t* out_ally_cost) const {
int16_t final_cost = 1;
bool has_mighty_knuckle = false;
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);
uint16_t sc_card_ref_if_item = 0xFFFF;
if ((client_id != 0xFF) && ce && (ce->def.type == CardType::ITEM) &&
this->short_statuses[client_id]) {
if ((client_id != 0xFF) && ce && (ce->def.type == CardType::ITEM) && this->short_statuses[client_id]) {
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)) {
target_mode = sc_ce->def.target_mode;
const char* target_mode_name = name_for_target_mode(target_mode);
log.debug_f("sc_ce overrides target mode with {} ({})",
target_mode_name, static_cast<uint8_t>(target_mode));
log.debug_f("sc_ce overrides target mode with {} ({})", 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(
uint16_t defense_card_ref,
uint16_t attacker_card_ref,
uint16_t attacker_sc_card_ref) const {
uint16_t defense_card_ref, 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 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);
@@ -1654,8 +1629,7 @@ bool RulerServer::defense_card_matches_any_attack_card_top_color(const ActionSta
if (!ce) {
throw runtime_error("defense card definition is missing");
}
const auto* chain = this->action_chain_with_conds_for_card_ref(
pa.original_attacker_card_ref);
const auto* chain = this->action_chain_with_conds_for_card_ref(pa.original_attacker_card_ref);
if (chain->chain.attack_action_card_ref_count < 1) {
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)) {
@@ -1681,10 +1655,7 @@ shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_ref(uint
}
int32_t RulerServer::error_code_for_client_setting_card(
uint8_t client_id,
uint16_t card_ref,
const Location* loc,
uint8_t assist_target_client_id) const {
uint8_t client_id, uint16_t card_ref, const Location* loc, uint8_t assist_target_client_id) const {
if (client_id > 3) {
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 (team_id != 1) {
if ((loc->x > 0) && (loc->x < this->map_and_rules->map.width - 1)) {
if ((loc->y < this->map_and_rules->map.height - summon_cost - 1) &&
(loc->y > 0)) {
if ((loc->y < this->map_and_rules->map.height - summon_cost - 1) && (loc->y > 0)) {
return 0;
}
if (loc->y == 1) {
@@ -1852,8 +1822,7 @@ int32_t RulerServer::error_code_for_client_setting_card(
}
}
} else {
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 ((summon_cost + 1 <= loc->y) && (loc->y < this->map_and_rules->map.height - 1)) {
return 0;
}
@@ -1965,8 +1934,7 @@ bool RulerServer::flood_fill_move_path(
size_t num_occupied_tiles,
size_t num_vacant_tiles) const {
auto state = this->map_and_rules;
if ((x < 1) || (x >= state->map.width - 1) ||
(y < 1) || (y >= state->map.height - 1)) {
if ((x < 1) || (x >= state->map.width - 1) || (y < 1) || (y >= state->map.height - 1)) {
return 0;
}
@@ -1979,15 +1947,12 @@ bool RulerServer::flood_fill_move_path(
} else {
uint32_t cost = this->get_path_cost(
chain,
num_vacant_tiles + num_occupied_tiles + 1,
is_aerial ? num_occupied_tiles : 0);
chain, num_vacant_tiles + num_occupied_tiles + 1, is_aerial ? num_occupied_tiles : 0);
if (max_atk_points < cost) {
return 0;
}
visited_map->at(x * 0x10 + y) = 1;
if (path && (path->end_loc.x == x) && (path->end_loc.y == y) &&
((path->length == -1) || (cost < path->cost))) {
if (path && (path->end_loc.x == x) && (path->end_loc.y == y) && ((path->length == -1) || (cost < path->cost))) {
ret = true;
path->reset_totals();
path->remaining_distance = max_distance;
@@ -2005,8 +1970,7 @@ bool RulerServer::flood_fill_move_path(
int16_t new_max_distance = max_distance - 1;
if (new_max_distance > 0) {
static const int8_t offsets[4][2] = {
{1, 0}, {0, -1}, {-1, 0}, {0, 1}};
static const int8_t offsets[4][2] = {{1, 0}, {0, -1}, {-1, 0}, {0, 1}};
Direction dirs[3] = {direction, turn_left(direction), turn_right(direction)};
for (size_t dir_index = 0; dir_index < 3; dir_index++) {
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 {
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)
? this->card_id_for_card_ref(card_ref)
: card_id_override;
uint16_t card_id = (card_id_override == 0xFFFF) ? this->card_id_for_card_ref(card_ref) : card_id_override;
log.debug_f("card_id=#{:04X}", card_id);
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);
auto orig_ce = this->definition_for_card_id(card_id);
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;
}
}
@@ -2123,8 +2086,7 @@ uint8_t RulerServer::get_card_ref_max_hp(uint16_t card_ref) const {
}
}
bool RulerServer::get_creature_summon_area(
uint8_t client_id, Location* out_loc, uint8_t* out_region_size) const {
bool RulerServer::get_creature_summon_area(uint8_t client_id, Location* out_loc, uint8_t* out_region_size) const {
if (!this->map_and_rules || (client_id > 3)) {
return false;
}
@@ -2155,8 +2117,7 @@ bool RulerServer::get_creature_summon_area(
region_size = this->map_and_rules->map.height - 3;
break;
default:
// This case isn't in the original code; probably it fell through to one
// of the above
// This case isn't in the original code; probably it fell through to one of the above
return false;
}
@@ -2169,27 +2130,20 @@ bool RulerServer::get_creature_summon_area(
return true;
}
shared_ptr<HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id(
uint8_t client_id) {
shared_ptr<HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id(uint8_t client_id) {
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(
uint8_t client_id) const {
shared_ptr<const HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id(uint8_t client_id) const {
return (client_id < 4) ? this->hand_and_equip_states[client_id] : nullptr;
}
bool RulerServer::get_move_path_length_and_cost(
uint32_t client_id,
uint32_t card_ref,
const Location& loc,
uint32_t* out_length,
uint32_t* out_cost) const {
uint32_t client_id, uint32_t card_ref, const Location& loc, uint32_t* out_length, uint32_t* out_cost) const {
MovePath path;
parray<uint8_t, 0x100> visited_map;
path.end_loc = loc;
if (!this->check_move_path_and_get_cost(
client_id, card_ref, &visited_map, &path, out_cost)) {
if (!this->check_move_path_and_get_cost(client_id, card_ref, &visited_map, &path, out_cost)) {
return false;
}
@@ -2206,9 +2160,7 @@ bool RulerServer::get_move_path_length_and_cost(
}
ssize_t RulerServer::get_path_cost(
const ActionChainWithConds& chain,
ssize_t path_length,
ssize_t cost_penalty) const {
const ActionChainWithConds& chain, ssize_t path_length, ssize_t cost_penalty) const {
for (size_t x = 0; x < 9; x++) {
const auto& cond = chain.conditions[x];
if (cond.type == ConditionType::SET_MV_COST_TO_0) {
@@ -2253,13 +2205,11 @@ bool RulerServer::is_attack_valid(const ActionState& pa) {
return false;
}
// Note: The original code has a case here that results in error code -0x5E,
// triggered by a function returning false. However, that function always
// returns true and has no side effects, so we've omitted the case here.
// Note: The original code has a case here that results in error code -0x5E, triggered by a function returning false.
// However, that function always 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);
if (!attacker_card_status ||
!this->card_ref_can_attack(attacker_card_ref) ||
if (!attacker_card_status || !this->card_ref_can_attack(attacker_card_ref) ||
(attacker_card_status->card_flags & 0x500)) {
this->error_code3 = -0x6F;
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_chain = this->action_chain_with_conds_for_card_ref(attacker_card_ref);
if (!attacker_chain ||
(attacker_chain->chain.acting_card_ref != attacker_card_ref) ||
!attacker_ce ||
if (!attacker_chain || (attacker_chain->chain.acting_card_ref != attacker_card_ref) || !attacker_ce ||
((attacker_ce->def.type != CardType::HUNTERS_SC &&
(attacker_ce->def.type != CardType::ARKZ_SC) &&
(attacker_ce->def.type != CardType::CREATURE) &&
@@ -2313,7 +2261,9 @@ bool RulerServer::is_attack_valid(const ActionState& pa) {
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);
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);
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)) {
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) {
// This error code is present in the original code, but is no longer possible
// since we require pa instead of using a pointer.
// This error code is present in the original code, but is no longer possible since we require pa instead.
// if (!pa) {
// this->error_code3 = -0x78;
// return false;
@@ -2450,9 +2401,8 @@ bool RulerServer::is_defense_valid(const ActionState& pa) {
return false;
}
// Note: The original code has a case here that results in error code -0x5E,
// triggered by a function returning false. However, that function always
// returns true and has no side effects, so we've omitted the case here.
// Note: The original code has a case here that results in error code -0x5E, triggered by a function returning false.
// However, that function always 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]);
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(
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
// as compared to the global array of the same name.
// TODO: Figure out why this difference exists and document it.
// Note: This function has opposite behavior for the UP and DOWN directions as compared to the global array of the
// same name. TODO: Figure out why this difference exists and document it.
switch (loc.direction) {
case Direction::LEFT:
*out_x_offset = -1;
@@ -2629,8 +2578,7 @@ void RulerServer::register_player(
this->set_card_action_metadatas[client_id] = set_card_action_metadatas;
}
void RulerServer::replace_D1_D2_rank_cards_with_Attack(
parray<le_uint16_t, 0x1F>& card_ids) const {
void RulerServer::replace_D1_D2_rank_cards_with_Attack(parray<le_uint16_t, 0x1F>& card_ids) const {
for (size_t z = 0; z < card_ids.size(); z++) {
auto ce = this->definition_for_card_id(card_ids[z]);
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(
const parray<le_uint16_t, 0x1F>& card_ids,
const parray<uint8_t, 0x2F0>* owned_card_counts) const {
const parray<le_uint16_t, 0x1F>& card_ids, const parray<uint8_t, 0x2F0>* owned_card_counts) const {
for (size_t z = 0; z < card_ids.size(); z++) {
if (!this->definition_for_card_id(card_ids.at(z))) {
return -0x7C;
+2 -4
View File
@@ -152,8 +152,7 @@ public:
uint32_t get_card_id_with_effective_range(
uint16_t card_ref, uint16_t card_id_override, TargetMode* out_target_mode) const;
uint8_t get_card_ref_max_hp(uint16_t card_ref) const;
bool get_creature_summon_area(
uint8_t client_id, Location* out_loc, uint8_t* out_region_size) const;
bool get_creature_summon_area(uint8_t client_id, Location* out_loc, uint8_t* out_region_size) const;
std::shared_ptr<HandAndEquipState> get_hand_and_equip_state_for_client_id(uint8_t client_id);
std::shared_ptr<const HandAndEquipState> get_hand_and_equip_state_for_client_id(uint8_t client_id) const;
bool get_move_path_length_and_cost(
@@ -191,8 +190,7 @@ public:
const CardShortStatus* short_status_for_card_ref(uint16_t card_ref) const;
bool should_allow_attacks_on_current_turn() const;
int32_t verify_deck(
const parray<le_uint16_t, 0x1F>& card_ids,
const parray<uint8_t, 0x2F0>* owned_card_counts = nullptr) const;
const parray<le_uint16_t, 0x1F>& card_ids, const parray<uint8_t, 0x2F0>* owned_card_counts = nullptr) const;
private:
std::weak_ptr<Server> w_server;
+79 -113
View File
@@ -12,12 +12,10 @@ using namespace std;
namespace Episode3 {
// This is (obviously) not the original string. The original string is:
// "03/05/29 18:00 by K.Toya" (NTE)
// "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya" (Final)
static const char* VERSION_SIGNATURE =
"newserv Ep3 based on [V1][FINAL2.0] 03/09/13 15:30 by K.Toya";
static const char* VERSION_SIGNATURE_NTE =
"newserv Ep3 NTE based on 03/05/29 18:00 by K.Toya";
// NTE: "03/05/29 18:00 by K.Toya"
// Final: "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya"
static const char* VERSION_SIGNATURE = "newserv Ep3 based on [V1][FINAL2.0] 03/09/13 15:30 by K.Toya";
static const char* VERSION_SIGNATURE_NTE = "newserv Ep3 NTE based on 03/05/29 18:00 by K.Toya";
Server::PresenceEntry::PresenceEntry() {
this->clear();
@@ -103,10 +101,9 @@ void Server::init() {
this->card_special = make_shared<CardSpecial>(this->shared_from_this());
// Note: The original implementation calls the default PSOV2Encryption
// constructor for random_crypt, which just uses 0 as the seed. It then
// re-seeds the generator later. We instead expect the caller to provide a
// seeded generator, and we don't re-seed it at all.
// Note: The original implementation calls the default PSOV2Encryption constructor for random_crypt, which just uses
// 0 as the seed. It then re-seeds the generator later. We instead expect the caller to provide a seeded generator,
// and we don't re-seed it at all.
// this->random_crypt = make_shared<PSOV2Encryption>(0);
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();
}
// Note: Sega's servers sent battle commands with the 60 command. The handlers
// for 60, 62, and C9 on the client are identical, so we choose to use C9
// instead because it's unique to Episode 3, and therefore seems more
// appropriate to convey battle commands.
// Note: Sega's servers sent battle commands with the 60 command. The handlers for 60, 62, and C9 on the client are
// identical, so we choose to use C9 instead because it's unique to Episode 3, and therefore seems more appropriate
// to convey Episode 3 battle commands.
send_command(l, command, 0x00, data, size);
for (auto watcher_l : l->watcher_lobbies) {
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 {
// Note: This function is not part of the original implementation; it was
// factored out from its callsites in this file and the strings were changed.
// Note: This function is not part of the original implementation; it was factored out from its callsites in this
// file and the strings were changed.
// NTE doesn't have the date_str2 field, but we send it anyway to make
// debugging easier.
// NTE doesn't have the date_str2 field, but we send it anyway to make debugging easier.
G_ServerVersionStrings_Ep3_6xB4x46 cmd;
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);
cmd.date_str2.encode(std::format("newserv {} compiled at {}", GIT_REVISION_HASH, build_date), Language::ENGLISH);
this->send(cmd);
@@ -293,7 +290,8 @@ string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> ma
phosg::StringWriter w;
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);
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) {
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);
}
@@ -339,8 +338,8 @@ void Server::send_commands_for_joining_spectator(std::shared_ptr<Channel> ch) co
// (send 6xB4x4F for client_id)
// }
ch->send(0xC9, 0x00, this->prepare_6xB4x07_decks_update());
// TODO: Sega sends 6xB4x05 here again; why? Is that necessary? They also
// send 6xB4x02 again for each player after that (but not 6xB4x04)
// TODO: Sega sends 6xB4x05 here again; why? Is that necessary? They also send 6xB4x02 again for each player after
// that (but not 6xB4x04)
ch->send(0xC9, 0x00, this->prepare_6xB4x1C_names_update());
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");
}
// TODO: Is it possible for there to be gaps in the set cards array? If not,
// we could just do a direct array lookup here instead of this loop
// TODO: Is it possible for there to be gaps in the set cards array? If not, we could just do a direct array lookup
// here instead of this loop
shared_ptr<Card> set_card = nullptr;
for (size_t set_index = 0; set_index < 8; 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 {
return (client_id < 4)
? this->presence_entries[client_id].player_present
: false;
return (client_id < 4) ? this->presence_entries[client_id].player_present : false;
}
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->map_and_rules->rules.overall_time_limit > 0) {
// Battle time limits are specified in increments of 5 minutes.
// Note: This part is not based on the original code because the timing
// facilities used are different.
// Battle time limits are specified in increments of 5 minutes. This part is not based on the original code
// because the timing facilities used are different.
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);
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() {
this->num_pending_attacks = 0;
this->unknown_a15 = 1;
// Annoyingly, this is the original logic. We use an enum because it appears
// that this can only ever be 0 or 2, but we may have to delete the enum if
// that turns out to be false.
// Annoyingly, this is the original logic. We use an enum because it appears that this can only ever be 0 or 2, but
// we may have to delete the enum if that turns out to be false.
this->action_subphase = static_cast<ActionSubphase>(static_cast<uint8_t>(this->action_subphase) + 2);
if (this->options.is_nte()) {
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 = this->card_for_set_card_ref(this->send_6xB4x06_if_card_ref_invalid(
pa->original_attacker_card_ref, 2));
card = this->card_for_set_card_ref(this->send_6xB4x06_if_card_ref_invalid(pa->original_attacker_card_ref, 2));
if (card) {
card = this->card_for_set_card_ref(pa->target_card_refs[0]);
if (card) {
@@ -1100,8 +1094,7 @@ void Server::move_phase_after() {
auto ps = this->player_states[client_id];
if (ps) {
auto sc_card = ps->get_sc_card();
if (sc_card && (sc_card->card_flags & 0x80) &&
(sc_card->loc.x == trap_x) && (sc_card->loc.y == trap_y)) {
if (sc_card && (sc_card->card_flags & 0x80) && (sc_card->loc.x == trap_x) && (sc_card->loc.y == trap_y)) {
should_trigger = true;
break;
}
@@ -1111,7 +1104,7 @@ void Server::move_phase_after() {
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
vector<uint16_t>{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C},
// 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);
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.
@@ -1145,9 +1138,7 @@ void Server::move_phase_after() {
auto ps = this->player_states[client_id];
if (ps) {
auto sc_card = ps->get_sc_card();
if (sc_card &&
(abs(sc_card->loc.x - trap_x) < 2) &&
(abs(sc_card->loc.y - trap_y) < 2) &&
if (sc_card && (abs(sc_card->loc.x - trap_x) < 2) && (abs(sc_card->loc.y - trap_y) < 2) &&
ps->replace_assist_card_by_id(trap_card_id)) {
G_EnqueueAnimation_Ep3_6xB4x2C cmd;
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->send_6xB4x50();
// }
// We instead use an implementation that consumes a constant amount of
// randomness per pass.
// We instead use an implementation that consumes a constant amount of randomness per pass.
if (this->num_trap_tiles_of_type[trap_type] == 2) {
this->chosen_trap_tile_index_of_type[trap_type] ^= 1;
this->send_6xB4x50_trap_tile_locations();
} else if (this->num_trap_tiles_of_type[trap_type] > 2) {
// Generate a new random index, but forbid it from matching the existing
// index
// Generate a new random index, but forbid it from matching the existing index
uint8_t new_index = this->get_random(this->num_trap_tiles_of_type[trap_type] - 1);
if (new_index >= this->chosen_trap_tile_index_of_type[trap_type]) {
new_index++;
@@ -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++) {
auto ally_ps = this->get_player_state(z);
if ((z != setter_client_id) && ally_ps) {
if ((ally_ps->get_team_id() == setter_ps->get_team_id()) &&
(ally_ps->get_atk_points() >= ally_cost)) {
if ((ally_ps->get_team_id() == setter_ps->get_team_id()) && (ally_ps->get_atk_points() >= ally_cost)) {
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;
}
} else {
// TODO: It'd be nice to do this in a constant-randomness way, but I'm
// lazy, and this matches Sega's original implementation. The less-lazy
// way to do it would be to roll three dice: one in the range [1, N],
// one in the range [3, N], and one in the range [1, 2] to decide
// whether to swap the first two results.
// TODO: It'd be nice to do this in a constant-randomness way, but I'm lazy, and this matches Sega's original
// implementation. The less-lazy way to do it would be to roll three dice: one in the range [1, 2] to decide
// 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
// do the same for the DEF die.
for (size_t z = 0; z < 200; z++) {
ps->roll_main_dice_or_apply_after_effects();
if ((ps->get_atk_points() >= 3) || (ps->get_def_points() >= 3)) {
@@ -1431,12 +1418,14 @@ void Server::set_phase_after() {
if (ps) {
auto card = ps->get_sc_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++) {
auto card = ps->get_set_card(set_index);
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++) {
auto ps = this->player_states[client_id];
if (ps &&
(ps->get_assist_turns_remaining() == 90) &&
(ps->assist_delay_turns < 1)) {
if (ps && (ps->get_assist_turns_remaining() == 90) && (ps->assist_delay_turns < 1)) {
ps->discard_set_assist_card();
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;
for (size_t y = 0; y < 0x10; y++) {
for (size_t x = 0; x < 0x10; x++) {
if ((this->overlay_state.tiles[y][x] == (trap_type | 0x40)) &&
(num_trap_tiles < 8)) {
if ((this->overlay_state.tiles[y][x] == (trap_type | 0x40)) && (num_trap_tiles < 8)) {
this->trap_tile_locs[trap_type][num_trap_tiles][0] = x;
this->trap_tile_locs[trap_type][num_trap_tiles][1] = y;
num_trap_tiles++;
@@ -1639,7 +1625,6 @@ void Server::setup_and_start_battle() {
}
}
this->num_trap_tiles_of_type[trap_type] = num_trap_tiles;
if (num_trap_tiles > 0) {
this->chosen_trap_tile_index_of_type[trap_type] = this->get_random(num_trap_tiles);
}
@@ -1684,8 +1669,7 @@ void Server::setup_and_start_battle() {
this->send_6xB4x46();
// Re-send game metadata to spectator teams, since loading the battle scene
// seems to delete it
// Re-send game metadata to spectator teams, since loading the battle scene seems to delete it
auto l = this->lobby.lock();
if (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;
for (size_t z = 0; z < 4; z++) {
auto ps = this->player_states[z];
if (!ps) {
cmd.state.client_sc_card_types[z] = CardType::INVALID_FF;
} else {
cmd.state.client_sc_card_types[z] = ps->get_sc_card_type();
}
cmd.state.client_sc_card_types[z] = ps ? ps->get_sc_card_type() : CardType::INVALID_FF;
}
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() {
// Returns true if the battle can begin
auto log = this->log_stack("update_registration_phase: ");
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;
if (expected_size < data.size()) {
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) {
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;
if ((this->setup_phase != SetupPhase::HAND_REDRAW_OPTION) &&
(this->setup_phase != SetupPhase::STARTER_ROLLS)) {
if ((this->setup_phase != SetupPhase::HAND_REDRAW_OPTION) && (this->setup_phase != SetupPhase::STARTER_ROLLS)) {
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::BATTLE_STARTED)) {
*this->map_and_rules = in_cmd.map_and_rules_state;
// The client will likely send incorrect values for the extended rules (or
// in the case of NTE, no values at all, since the Rules structure is
// smaller). So, use the values from the last chosen map if applicable, or
// the values from the $dicerange command if available.
// The client will likely send incorrect values for the extended rules (or in the case of NTE, no values at all,
// since the Rules structure is smaller). So, use the values from the last chosen map if applicable, or the values
// from the $dicerange command if available.
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;
auto& server_rules = this->map_and_rules->rules;
// NTE can specify the DEF dice value range in its Rules struct, so we use
// that unless the map or $dicerange overrides it.
// NTE can specify the DEF dice value range in its Rules struct, so we use that unless the map or $dicerange
// overrides it.
server_rules.def_dice_value_range = (map_rules && (map_rules->def_dice_value_range != 0xFF))
? map_rules->def_dice_value_range
: (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
: 0;
// If this match is part of a tournament, ignore the rules sent by the
// client and use the tournament rules instead.
// If this match is part of a tournament, ignore the rules sent by the client and use the tournament rules instead.
if (this->options.tournament) {
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);
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "HARD RESET");
// In the original implementation, this command recreates the server object.
// This is possible because the dispatch function is not part of the server
// object in the original implementation; however, in our implementation, it
// is, so we don't support this. The original implementation did this:
// In the original implementation, this command recreates the server object. This is possible because the dispatch
// function is not part of the server object in the original implementation; however, in our implementation, it is,
// so we don't support this. The original implementation did this:
// this->base()->recreate_server(); // Destroys *this, which we can't do
// root_card_server = this->server;
// *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;
}
// Note: This check is not part of the original code. This replaces a
// disconnecting player with a CPU if the battle is in progress.
// Note: This check is not part of the original code. This replaces a disconnecting player with a CPU if the battle
// is in progress.
auto l = this->lobby.lock();
if (l && !l->clients[in_cmd.entry.client_id]) {
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();
if (l) {
// Note: Sega's implementation doesn't set EX results values here; they
// did it at game join time instead. We do it here for code simplicity.
// Note: Sega's implementation doesn't set EX results values here; they did it at game join time instead. We do
// it here for code simplicity.
if (!this->options.is_nte() && 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++) {
auto ps = this->player_states[z];
if (ps && (this->current_team_turn1 != ps->get_team_id())) {
if (!ps->get_sc_card()->check_card_flag(2) &&
(this->defense_list_ended_for_client[z] == 0)) {
if (!ps->get_sc_card()->check_card_flag(2) && (this->defense_list_ended_for_client[z] == 0)) {
all_defense_lists_ended = false;
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) {
const auto& in_cmd = check_size_t<G_OverallTimeLimitExpired_Ep3_CAx3A>(data);
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "TIME EXPIRED");
// We don't need to do anything here because the overall time limit is tracked
// server-side instead.
// We don't need to do anything here because the overall time limit is tracked server-side instead.
}
void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const string& data) {
@@ -2579,9 +2553,8 @@ void Server::send_6xB6x41_to_all_clients() const {
}
if (this->battle_record && this->battle_record->writable()) {
// TODO: It's not great that we just pick the first one; ideally we'd put
// all of them in the recording and send the appropriate one to the client
// in the playback lobby
// TODO: It's not great that we just pick the first one; ideally we'd put all of them in the recording and send
// the appropriate one to the client in the playback lobby
for (string& data : map_commands_by_language) {
if (!data.empty()) {
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);
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
// implementation is not based on the original code.
// Note: Sega's implmentation completely ignores this command. This implementation is not based on the original code.
auto& dest_counts = this->client_card_counts[in_cmd.header.sender_client_id];
dest_counts = in_cmd.card_id_to_count;
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++) {
auto ps = this->player_states[client_id];
// Possible bug: shouldn't we check should_block_assist_effects_for_client
// here too? If the player has a card with the same HP as another one that
// would be destroyed, it looks like the card can be destroyed even if the
// client should be immune to assist effects here.
// Possible bug: shouldn't we check should_block_assist_effects_for_client here too? If the player has a card with
// the same HP as another one that would be destroyed, it looks like the card can be destroyed even if the client
// should be immune to assist effects here.
if (ps) {
for (size_t set_index = 0; set_index < 8; 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;
for (size_t z = 0; z < 8; z++) {
auto set_card = ps->get_set_card(z);
if (set_card &&
(set_card != target_card) &&
!(set_card->card_flags & 2) &&
set_card->is_guard_item()) {
if (set_card && (set_card != target_card) && !(set_card->card_flags & 2) && set_card->is_guard_item()) {
found_guard_item = set_card;
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
// in this array; we instead clear the entire array.
// Note: The original code only writes a single FFFF after the last card ref in this array; we instead clear the
// entire array.
as->target_card_refs.clear(0xFFFF);
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);
@@ -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
// clear the entire array first.
// Note: This is different from the original code in the same way as above: we clear the entire array first.
as->target_card_refs.clear(0xFFFF);
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);
@@ -3148,8 +3115,7 @@ void Server::unknown_802402F4() {
}
}
vector<shared_ptr<Card>> Server::const_cast_set_cards_v(
const vector<shared_ptr<const Card>>& cards) {
vector<shared_ptr<Card>> Server::const_cast_set_cards_v(const vector<shared_ptr<const Card>>& cards) {
// TODO: This is dumb. Figure out a not-dumb way to do this.
vector<shared_ptr<Card>> ret;
for (auto const_card : cards) {
+42 -52
View File
@@ -20,53 +20,43 @@ struct Lobby;
namespace Episode3 {
/**
* This implementation of Episode 3 battles is derived from Sega's original
* server implementation, reverse-engineered from the Episode 3 client
* executable. The control flow, function breakdown, and structure definitions
* in these files map very closely to how their server implementation was
* 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
* Card.hh/cc
* CardSpecial.hh/cc
* DeckState.hh/cc
* MapState.hh/cc
* PlayerState.hh/cc
* PlayerStateSubordinates.hh/cc
* RulerServer.hh/cc
* Server.hh/cc
*
* There are likely undiscovered bugs in this code, some originally written by
* Sega, but more written by me as I manually transcribed and updated this code.
*
* Class ownership levels (classes may only contain weak_ptrs, not shared_ptrs,
* to classes at the same or higher level):
* - Server
* - - RulerServer
* - - - AssistServer
* - - - CardSpecial
* - - - - StateFlags
* - - - - DeckEntry
* - - - - PlayerState
* - - - - - Card
* - - - - - - CardShortStatus
* - - - - - - DeckState
* - - - - - - HandAndEquipState
* - - - - - - MapAndRulesState / OverlayState
* - - - - - - - Everything within DataIndexes
*/
// This implementation of Episode 3 battles is derived from Sega's original server implementation, reverse-engineered
// from the Episode 3 client executable. The control flow, function breakdown, and structure definitions in these files
// map very closely to how their server implementation was written; notable differences (due to necessary environment
// differences or bug fixes) are described in the comments therein. There are likely undiscovered bugs in this code,
// some originally written by Sega, but more written by me as I manually transcribed and updated this code.
// The following files are direct reverse-engineerings of Sega's original code, except where noted in the comments:
// AssistServer.hh/cc
// Card.hh/cc
// CardSpecial.hh/cc
// DeckState.hh/cc
// MapState.hh/cc
// PlayerState.hh/cc
// PlayerStateSubordinates.hh/cc
// RulerServer.hh/cc
// Server.hh/cc
// Class ownership levels (classes may contain weak_ptrs but not shared_ptrs to classes at the same or higher level):
// - Server
// - - RulerServer
// - - - AssistServer
// - - - CardSpecial
// - - - - StateFlags
// - - - - DeckEntry
// - - - - PlayerState
// - - - - - Card
// - - - - - - CardShortStatus
// - - - - - - DeckState
// - - - - - - HandAndEquipState
// - - - - - - MapAndRulesState / OverlayState
// - - - - - - - Everything within DataIndexes
class Server : public std::enable_shared_from_this<Server> {
// In the original code, there is a TCardServerBase class and a TCardServer
// class, with the former containing some basic parts of the game state and
// a pointer to the latter. It seems these two classes exist (instead of one
// big class) so that the force reset command could be implemented; however,
// it appears that that command is never sent by the client, so we combine
// the two classes into one in our implementation.
// In the original code, there is a TCardServerBase class and a TCardServer class, with the former containing some
// basic parts of the game state and a pointer to the latter. It seems these two classes exist (instead of one big
// class) so that the force reset command could be implemented; however, it appears that that command is never sent
// by the client, so we combine the two classes into one in our implementation.
public:
struct Options {
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_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_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_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);
@@ -266,12 +257,12 @@ public:
G_UpdateDecks_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const;
G_SetPlayerNames_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const;
static std::string prepare_6xB6x41_map_definition(std::shared_ptr<const MapIndex::Map> map, 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;
G_SetTrapTileLocations_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const;
std::vector<std::shared_ptr<Card>> const_cast_set_cards_v(
const std::vector<std::shared_ptr<const Card>>& cards);
std::vector<std::shared_ptr<Card>> const_cast_set_cards_v(const std::vector<std::shared_ptr<const Card>>& cards);
private:
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;
uint32_t unknown_a10;
uint32_t overall_time_expired;
// Note: In the original implementation, this is a uint32_t and is measured in
// seconds. In our environment, the simplest implementation uses now(), which
// returns microseconds, so we use a uint64_t instead.
// Note: In the original implementation, this is a uint32_t and is measured in seconds. In our environment, the
// simplest implementation uses now(), which returns microseconds, so we use a uint64_t instead.
uint64_t battle_start_usecs;
uint32_t should_copy_prev_states_to_current_states;
std::shared_ptr<CardSpecial> card_special;
+38 -73
View File
@@ -67,10 +67,7 @@ string Tournament::Team::str() const {
return ret + "]";
}
void Tournament::Team::register_player(
shared_ptr<Client> c,
const string& team_name,
const string& password) {
void Tournament::Team::register_player(shared_ptr<Client> c, const string& team_name, const string& password) {
if (this->players.size() >= this->max_players) {
throw runtime_error("team is full");
}
@@ -104,8 +101,7 @@ void Tournament::Team::register_player(
bool Tournament::Team::unregister_player(uint32_t account_id) {
size_t index;
for (index = 0; index < this->players.size(); index++) {
if (this->players[index].is_human() &&
(this->players[index].account_id == account_id)) {
if (this->players[index].is_human() && (this->players[index].account_id == account_id)) {
break;
}
}
@@ -123,12 +119,10 @@ bool Tournament::Team::unregister_player(uint32_t account_id) {
return false;
}
// If the tournament has already started, make the team forfeit their game.
// If any player withdraws from a team after the registration phase, the
// entire team essentially forfeits their entry.
// If the tournament has already started, make the team forfeit their game. If any player withdraws from a team
// after the registration phase, the entire team essentially forfeits their entry.
if (tournament->get_state() != Tournament::State::REGISTRATION) {
// Look through the pending matches to see if this team is involved in any
// of them
// Look through the pending matches to see if this team is involved in any of them
for (auto match : tournament->pending_matches) {
if (!match->preceding_a || !match->preceding_b) {
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 {
// If the tournament has not started yet, just remove the player from the team
if (!tournament->all_player_account_ids.erase(account_id)) {
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(
shared_ptr<Tournament> tournament,
shared_ptr<Match> preceding_a,
shared_ptr<Match> preceding_b)
shared_ptr<Tournament> tournament, shared_ptr<Match> preceding_a, shared_ptr<Match> preceding_b)
: tournament(tournament),
preceding_a(preceding_a),
preceding_b(preceding_b),
@@ -197,9 +188,7 @@ Tournament::Match::Match(
this->round_num = this->preceding_a->round_num + 1;
}
Tournament::Match::Match(
shared_ptr<Tournament> tournament,
shared_ptr<Team> winner_team)
Tournament::Match::Match(shared_ptr<Tournament> tournament, shared_ptr<Team> winner_team)
: tournament(tournament),
preceding_a(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);
return true;
}
// If neither preceding winner team has any humans on it, skip this match
// entirely and just make one team advance arbitrarily (note that this also
// handles the case where both preceding winner teams are empty)
// If neither preceding winner team has any humans on it, skip this match entirely and just make one team advance
// arbitrarily (note that this also handles the case where both preceding winner teams are empty)
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);
return true;
@@ -247,8 +235,8 @@ void Tournament::Match::on_winner_team_set() {
tournament->pending_matches.erase(this->shared_from_this());
// Resolve the following match if possible (this skips CPU-only matches). If
// the following match can't be resolved, mark it pending.
// Resolve the following match if possible (this skips CPU-only matches). If the following match can't be resolved,
// mark it pending.
auto following = this->following.lock();
if (following && !following->resolve_if_skippable()) {
tournament->pending_matches.emplace(following);
@@ -259,8 +247,8 @@ void Tournament::Match::on_winner_team_set() {
tournament->current_state = Tournament::State::COMPLETE;
}
// Unlink the losing team's players (if any) - this allows them to enter
// another tournament before this tournament has ended
// Unlink the losing team's players (if any) - this allows them to enter another tournament before this tournament
// has ended
if (this->preceding_a && this->preceding_b) {
auto losing_team = (this->winner_team == this->preceding_a->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) {
throw logic_error("set_winner_team called on zero-round match");
}
if ((team != this->preceding_a->winner_team) &&
(team != this->preceding_b->winner_team)) {
if ((team != this->preceding_a->winner_team) && (team != this->preceding_b->winner_team)) {
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();
}
shared_ptr<Tournament::Team> Tournament::Match::opponent_team_for_team(
shared_ptr<Team> team) const {
shared_ptr<Tournament::Team> Tournament::Match::opponent_team_for_team(shared_ptr<Team> team) const {
if (!this->preceding_a || !this->preceding_b) {
throw logic_error("zero-round matches do not have opponents");
}
@@ -342,9 +328,7 @@ Tournament::Tournament(
}
Tournament::Tournament(
shared_ptr<const MapIndex> map_index,
shared_ptr<const COMDeckIndex> com_deck_index,
const phosg::JSON& json)
shared_ptr<const MapIndex> map_index, shared_ptr<const COMDeckIndex> com_deck_index, const phosg::JSON& json)
: log(std::format("[Tournament:{}] ", json.get_string("name"))),
map_index(map_index),
com_deck_index(com_deck_index),
@@ -394,8 +378,7 @@ void Tournament::init() {
} else {
// Create empty teams
while (this->teams.size() < this->num_teams) {
auto t = make_shared<Team>(
this->shared_from_this(), this->teams.size(), (this->flags & Flag::IS_2V2) ? 2 : 1);
auto t = make_shared<Team>(this->shared_from_this(), this->teams.size(), (this->flags & Flag::IS_2V2) ? 2 : 1);
this->teams.emplace_back(t);
}
is_registration_complete = false;
@@ -444,9 +427,7 @@ void Tournament::init() {
// If both preceding matches of the following match are resolved, put
// the following match on the queue since it may be resolvable as well
auto following = match->following.lock();
if (following &&
following->preceding_a->winner_team &&
following->preceding_b->winner_team) {
if (following && following->preceding_a->winner_team && following->preceding_b->winner_team) {
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");
}
// Create the zero-round matches, and make them all pending if registration
// is still open
// Create the zero-round matches, and make them all pending if registration is still open
this->zero_round_matches.clear();
for (const auto& team : this->teams) {
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) {
vector<shared_ptr<Match>> next_round_matches;
for (size_t z = 0; z < current_round_matches.size(); z += 2) {
auto m = make_shared<Match>(
this->shared_from_this(),
current_round_matches[z],
current_round_matches[z + 1]);
auto m = make_shared<Match>(this->shared_from_this(), current_round_matches[z], current_round_matches[z + 1]);
current_round_matches[z]->following = m;
current_round_matches[z + 1]->following = 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;
}
shared_ptr<Tournament::Match> Tournament::next_match_for_team(
shared_ptr<Team> team) const {
shared_ptr<Tournament::Match> Tournament::next_match_for_team(shared_ptr<Team> team) const {
if (this->current_state == Tournament::State::REGISTRATION) {
return nullptr;
}
@@ -561,8 +537,7 @@ shared_ptr<Tournament::Match> Tournament::next_match_for_team(
if (!match->preceding_a || !match->preceding_b) {
throw logic_error("zero-round match is pending after tournament registration phase");
}
if ((team == match->preceding_a->winner_team) ||
(team == match->preceding_b->winner_team)) {
if ((team == match->preceding_a->winner_team) || (team == match->preceding_b->winner_team)) {
return match;
}
}
@@ -573,8 +548,7 @@ shared_ptr<Tournament::Match> Tournament::get_final_match() const {
return this->final_match;
}
shared_ptr<Tournament::Team> Tournament::team_for_account_id(
uint32_t account_id) const {
shared_ptr<Tournament::Team> Tournament::team_for_account_id(uint32_t account_id) const {
if (!this->all_player_account_ids.count(account_id)) {
return nullptr;
}
@@ -601,9 +575,8 @@ void Tournament::start() {
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),
// don't allow the tournament to start (because it would enter the COMPLETE
// state immediately)
// If there aren't enough entrants (1 if has_com_teams is false, else 2), don't allow the tournament to start
// (because it would enter the COMPLETE state immediately)
size_t num_human_teams = 0;
for (size_t z = 0; z < this->teams.size(); z++) {
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 both of these flags are set, pack the human teams into the lowest part
// of the teams list so we can resize the tournament to the smallest
// possible size. This is OK since we're going to shuffle them later anyway
// If both of these flags are set, pack the human teams into the lowest part of the teams list so we can resize the
// tournament to the smallest possible size. This is OK since we're going to shuffle them later anyway
size_t r_offset = 0, w_offset = 0;
for (; r_offset < this->teams.size(); r_offset++) {
if (this->teams[r_offset]->has_any_human_players()) {
@@ -630,8 +602,8 @@ void Tournament::start() {
}
if (this->flags & Flag::RESIZE_ON_START) {
// Resize the tournament by repeatedly deleting the second half of it, until
// the second half contains human players or the tournament size is 4
// Resize the tournament by repeatedly deleting the second half of it, until the second half contains human players
// or the tournament size is 4
while (this->teams.size() > 4) {
size_t 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->create_bracket_matches();
// Assign names to COM teams, and assign COM decks to all empty slots unless
// has_com_teams is false
// Assign names to COM teams, and assign COM decks to all empty slots unless has_com_teams is false
for (size_t z = 0; z < this->zero_round_matches.size(); z++) {
auto m = this->zero_round_matches[z];
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()) {
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
// only one human on it, add a COM
// If we allow all-COM teams, or this is a 2v2 tournament and the team has only one human on it, add a COM
if (has_com_teams || !t->players.empty()) {
// TODO: Don't allow duplicate COM decks, nor duplicate COM SCs on the
// same team
// TODO: Don't allow duplicate COM decks, nor duplicate COM SCs on the same team
while (t->players.size() < t->max_players) {
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& player : team->players) {
auto c = player.client.lock();
// Note: The last check here is to make sure the client is still linked
// with this instance of the tournament - an intervening shell command
// `reload ep3` could have changed the client's linkage
// Note: The last check here is to make sure the client is still linked with this instance of the tournament - an
// intervening shell command `reload ep3` could have changed the client's linkage
if (c && (c->version() == Version::GC_EP3) && (c->ep3_tournament_team.lock() == team)) {
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);
tourn->init();
if (!this->name_to_tournament.emplace(tourn->get_name(), tourn).second) {
// This is logic_error instead of runtime_error because phosg::JSON dicts are
// supposed to already have unique keys
// This is logic_error instead of runtime_error because phosg::JSON dicts already have unique keys
throw logic_error("multiple tournaments have the same name: " + tourn->get_name());
}
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");
}
auto t = make_shared<Tournament>(
this->map_index, this->com_deck_index, name, map, rules, num_teams, flags);
auto t = make_shared<Tournament>(this->map_index, this->com_deck_index, name, map, rules, num_teams, flags);
t->init();
if (!this->name_to_tournament.emplace(t->get_name(), t).second) {
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) {
// This can be called before the game server exists, so do nothing in that
// case
// This can be called before the game server exists, so do nothing in that case
if (s->game_server) {
for (const auto& c : s->game_server->all_clients()) {
this->link_client(c);
+8 -18
View File
@@ -62,16 +62,10 @@ public:
size_t num_rounds_cleared;
bool is_active;
Team(
std::shared_ptr<Tournament> tournament,
size_t index,
size_t max_players);
Team(std::shared_ptr<Tournament> tournament, size_t index, size_t max_players);
std::string str() const;
void register_player(
std::shared_ptr<Client> c,
const std::string& team_name,
const std::string& password);
void register_player(std::shared_ptr<Client> c, const std::string& team_name, const std::string& password);
bool unregister_player(uint32_t account_id);
bool has_any_human_players() const;
@@ -91,9 +85,7 @@ public:
std::shared_ptr<Tournament> tournament,
std::shared_ptr<Match> preceding_a,
std::shared_ptr<Match> preceding_b);
Match(
std::shared_ptr<Tournament> tournament,
std::shared_ptr<Team> winner_team);
Match(std::shared_ptr<Tournament> tournament, std::shared_ptr<Team> winner_team);
std::string str() const;
bool resolve_if_skippable();
@@ -180,14 +172,12 @@ private:
std::set<uint32_t> all_player_account_ids;
std::unordered_set<std::shared_ptr<Match>> pending_matches;
// This vector contains all teams in the original starting order of the
// tournament (that is, all teams in the first round). The order within this
// vector determines which team will play against which other team in the
// first round: [0] will play against [1], [2] will play against [3], etc.
// This vector contains all teams in the original starting order of the tournament (that is, all teams in the first
// round). The order within this vector determines which team will play against which other team in the first round:
// [0] will play against [1], [2] will play against [3], etc.
std::vector<std::shared_ptr<Team>> teams;
// The tournament begins with a "zero round", in which each team automatically
// "wins" a match, putting them into 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
// The tournament begins with a "zero round", in which each team automatically "wins" a match, putting them into 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
// no preceding round.
std::vector<std::shared_ptr<Match>> zero_round_matches;
std::shared_ptr<Match> final_match;