update formatting in src/Episode3
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user