#include "Server.hh" #include using namespace std; namespace Episode3 { CardSpecial::DiceRoll::DiceRoll() { this->clear(); } void CardSpecial::DiceRoll::clear() { this->client_id = 0; this->unknown_a2 = 0; this->value = 0; this->value_used_in_expr = 0; this->unknown_a5 = 0xFFFF; } CardSpecial::AttackEnvStats::AttackEnvStats() { this->clear(); } void CardSpecial::AttackEnvStats::clear() { this->num_set_cards = 0; this->dice_roll_value1 = 0; this->effective_ap = 0; this->effective_tp = 0; this->current_hp = 0; this->max_hp = 0; this->effective_ap_if_not_tech = 0; this->effective_ap_if_not_physical = 0; this->player_num_destroyed_fcs = 0; this->player_num_atk_points = 0; this->defined_max_hp = 0; this->dice_roll_value2 = 0; this->card_cost = 0; this->total_num_set_cards = 0; this->action_cards_ap = 0; this->action_cards_tp = 0; this->unknown_a1 = 0; this->num_item_or_creature_cards_in_hand = 0; this->num_destroyed_ally_fcs = 0; this->target_team_num_set_cards = 0; this->condition_giver_team_num_set_cards = 0; this->num_native_creatures = 0; this->num_a_beast_creatures = 0; this->num_machine_creatures = 0; this->num_dark_creatures = 0; this->num_sword_type_items = 0; this->num_gun_type_items = 0; this->num_cane_type_items = 0; this->effective_ap_if_not_tech2 = 0; this->team_dice_boost = 0; this->sc_effective_ap = 0; this->attack_bonus = 0; this->num_sword_type_items_on_team = 0; this->target_attack_bonus = 0; this->last_attack_preliminary_damage = 0; this->last_attack_damage = 0; this->total_last_attack_damage = 0; this->last_attack_damage_count = 0; this->target_current_hp = 0; } uint32_t CardSpecial::AttackEnvStats::at(size_t offset) const { constexpr size_t count = sizeof(*this) / sizeof(uint32_t); return reinterpret_cast*>(this)->at(offset); } CardSpecial::CardSpecial(shared_ptr server) : w_server(server), unknown_a2(0) {} shared_ptr CardSpecial::server() { auto s = this->w_server.lock(); if (!s) { throw runtime_error("server is deleted"); } return s; } shared_ptr CardSpecial::server() const { auto s = this->w_server.lock(); if (!s) { throw runtime_error("server is deleted"); } return s; } void CardSpecial::adjust_attack_damage_due_to_conditions( shared_ptr target_card, int16_t* inout_damage, uint16_t attacker_card_ref) { shared_ptr attacker_card = this->server()->card_for_set_card_ref(attacker_card_ref); auto attack_medium = attacker_card ? attacker_card->action_chain.chain.attack_medium : AttackMedium::UNKNOWN; for (size_t z = 0; z < 9; z++) { const auto& cond = target_card->action_chain.conditions[z]; if (cond.type == ConditionType::NONE) { continue; } if (this->card_ref_has_ability_trap(cond)) { continue; } if (!this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( cond.card_ref, target_card->get_card_ref(), attacker_card_ref, cond.card_definition_effect_index, attack_medium)) { continue; } switch (cond.type) { case ConditionType::WEAK_HIT_BLOCK: if (*inout_damage <= cond.value) { *inout_damage = 0; } break; case ConditionType::EXP_DECOY: { auto target_ps = target_card->player_state(); if (target_ps) { uint8_t target_team_id = target_ps->get_team_id(); int16_t exp_deduction = this->server()->team_exp[target_team_id]; if (exp_deduction < *inout_damage) { *inout_damage = *inout_damage - exp_deduction; this->server()->team_exp[target_team_id] = 0; } else { this->server()->team_exp[target_team_id] = exp_deduction - *inout_damage; exp_deduction = *inout_damage; *inout_damage = 0; } this->send_6xB4x06_for_exp_change( target_card, attacker_card_ref, -exp_deduction, true); this->compute_team_dice_boost(target_team_id); } break; } case ConditionType::UNKNOWN_73: if (cond.value <= *inout_damage) { *inout_damage = 0; } break; case ConditionType::HALFGUARD: if (cond.value <= *inout_damage) { *inout_damage /= 2; } break; default: break; } } } void CardSpecial::adjust_dice_boost_if_team_has_condition_52( uint8_t team_id, uint8_t* inout_dice_boost, shared_ptr card) { if (!card || (team_id == 0xFF) || !inout_dice_boost || (card->card_flags & 3)) { return; } auto ps = card->player_state(); if (!ps || (ps->get_team_id() != team_id)) { return; } for (size_t z = 0; z < 9; z++) { if (!this->card_ref_has_ability_trap(card->action_chain.conditions[z]) && (card->action_chain.conditions[z].type == ConditionType::UNKNOWN_52)) { *inout_dice_boost = *inout_dice_boost * card->action_chain.conditions[z].value8; } } } void CardSpecial::apply_action_conditions( uint8_t when, shared_ptr attacker_card, shared_ptr defender_card, uint32_t flags, const ActionState* as) { ActionState temp_as; if (attacker_card == defender_card) { temp_as = this->create_attack_state_from_card_action_chain(attacker_card); if (as) { temp_as = *as; } } else { temp_as = this->create_defense_state_for_card_pair_action_chains( attacker_card, defender_card); } this->apply_defense_conditions(temp_as, when, defender_card, flags); } bool CardSpecial::apply_attribute_guard_if_possible( uint32_t flags, CardClass card_class, shared_ptr card, uint16_t condition_giver_card_ref, uint16_t attacker_card_ref) { shared_ptr condition_giver_card = this->server()->card_for_set_card_ref(condition_giver_card_ref); if (condition_giver_card) { auto ce = condition_giver_card->get_definition(); if (ce && (ce->def.card_class() == card_class)) { if (flags & 2) { card->action_chain.reset(); } if (flags & 0x10) { card->action_metadata.defense_power = 99; card->action_metadata.defense_bonus = 0; } } } shared_ptr attacker_card = this->server()->card_for_set_card_ref(attacker_card_ref); if (attacker_card) { auto ce = attacker_card->get_definition(); if (ce && (ce->def.card_class() == card_class) && (flags & 0x10)) { card->action_metadata.defense_power = 99; card->action_metadata.defense_bonus = 0; } } return true; } bool CardSpecial::apply_defense_condition( uint8_t when, Condition* defender_cond, uint8_t cond_index, const ActionState& defense_state, shared_ptr defender_card, uint32_t flags, bool unknown_p8) { if (defender_cond->type == ConditionType::NONE) { return false; } auto orig_eff = this->original_definition_for_condition(*defender_cond); uint16_t attacker_card_ref = defense_state.attacker_card_ref; if (attacker_card_ref == 0xFFFF) { attacker_card_ref = defense_state.original_attacker_card_ref; } bool defender_has_ability_trap = this->card_ref_has_ability_trap(*defender_cond); if (!(flags & 4) || this->is_card_targeted_by_condition(*defender_cond, defense_state, defender_card)) { if ((when == 2) && (defender_cond->type == ConditionType::GUOM) && (flags & 4)) { CardShortStatus stat = defender_card->get_short_status(); if (stat.card_flags & 4) { this->server()->base()->log.debug("(when=2) @%04hX clearing GUOM from @%04hX", attacker_card_ref, defender_card->get_card_ref()); G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x0E); cmd.effect.target_card_ref = defender_card->get_card_ref(); cmd.effect.value = 0; cmd.effect.operation = -static_cast(defender_cond->type); cmd.effect.condition_index = cond_index; this->server()->send(cmd); this->apply_stat_deltas_to_card_from_condition_and_clear_cond( *defender_cond, defender_card); defender_card->send_6xB4x4E_4C_4D_if_needed(); return false; } } if ((when == 4) && (flags & 4) && !defender_has_ability_trap && (defender_cond->type == ConditionType::ACID)) { int16_t hp = defender_card->get_current_hp(); if (hp > 0) { this->server()->base()->log.debug("(when=2) @%04hX has ACID; removing 1 HP", defender_cond->card_ref.load()); this->send_6xB4x06_for_stat_delta( defender_card, defender_cond->card_ref, 0x20, -1, 0, 1); defender_card->set_current_hp(hp - 1); this->destroy_card_if_hp_zero(defender_card, defender_cond->condition_giver_card_ref); } } if (!orig_eff || (orig_eff->when != when)) { flags = flags & 0xFFFFFFFB; } if ((flags == 0) || defender_has_ability_trap) { return false; } DiceRoll dice_roll; dice_roll.client_id = defender_card->get_client_id(); dice_roll.unknown_a2 = 3; dice_roll.value = defender_cond->dice_roll_value; dice_roll.value_used_in_expr = false; uint8_t original_cond_flags = defender_cond->flags; auto astats = this->compute_attack_env_stats( defense_state, defender_card, dice_roll, defender_cond->card_ref, defender_cond->condition_giver_card_ref); string expr = orig_eff->expr; int16_t expr_value = this->evaluate_effect_expr(astats, expr.c_str(), dice_roll); this->execute_effect( *defender_cond, defender_card, expr_value, defender_cond->value, orig_eff->type, flags, attacker_card_ref); if (flags & 4) { if (!(defender_card->card_flags & 2)) { defender_card->compute_action_chain_results(true, false); } defender_card->action_chain.chain.card_ap = defender_card->ap; defender_card->action_chain.chain.card_tp = defender_card->tp; defender_card->send_6xB4x4E_4C_4D_if_needed(); } if (dice_roll.value_used_in_expr && !(original_cond_flags & 1) && !unknown_p8) { defender_cond->flags |= 1; G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x08; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x10); cmd.effect.target_card_ref = defender_cond->card_ref; cmd.effect.dice_roll_value = dice_roll.value; this->server()->send(cmd); } return true; } else { if (defender_cond->type != ConditionType::NONE) { G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x0D); cmd.effect.target_card_ref = defender_card->get_card_ref(); cmd.effect.value = 0; cmd.effect.operation = -static_cast(defender_cond->type); this->server()->send(cmd); } this->apply_stat_deltas_to_card_from_condition_and_clear_cond( *defender_cond, defender_card); defender_card->send_6xB4x4E_4C_4D_if_needed(); return false; } } bool CardSpecial::apply_defense_conditions( const ActionState& as, uint8_t when, shared_ptr 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); } return true; } bool CardSpecial::apply_stat_deltas_to_all_cards_from_all_conditions_with_card_ref( uint16_t card_ref) { bool ret = false; for (size_t client_id = 0; client_id < 4; client_id++) { auto ps = this->server()->get_player_state(client_id); if (!ps) { continue; } 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); } 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); } } } return ret; } bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond( Condition& cond, shared_ptr card) { ConditionType cond_type = cond.type; int16_t cond_value = clamp(cond.value, -99, 99); uint8_t cond_flags = cond.flags; uint16_t cond_card_ref = card->get_card_ref(); cond.clear(); switch (cond_type) { case ConditionType::UNKNOWN_0C: if (cond_flags & 2) { int16_t ap = clamp(card->ap, -99, 99); int16_t tp = clamp(card->tp, -99, 99); this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, tp - ap, 0, 0); this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, ap - tp, 0, 0); card->ap = tp; card->tp = ap; } break; case ConditionType::A_H_SWAP: if (cond_flags & 2) { int16_t ap = clamp(card->ap, -99, 99); int16_t hp = clamp(card->get_current_hp(), -99, 99); if (hp != ap) { this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, hp - ap, 0, 0); this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x20, ap - hp, 0, 0); card->set_current_hp(ap, 1, 1); card->ap = hp; this->destroy_card_if_hp_zero(card, cond_card_ref); } } break; case ConditionType::AP_OVERRIDE: if (cond_flags & 2) { // Note: The original code calls a function here that returns a // Condition pointer; however, the called function searches the card's // condition list and then ignores the result and unconditionally // returns null, completely obviating the non-null case here. We // implement the non-null case for documentation purposes, but it // appears to be completely dead code. It's unclear if this is a legit // bug in the original code, or if it was a debug feature or // late-development intentional change. Condition* other_cond = nullptr; // return_null???(card, ConditionType::AP_OVERRIDE); if (!other_cond) { this->send_6xB4x06_for_stat_delta( card, cond_card_ref, 0xA0, -cond_value, 0, 0); card->ap = max(card->ap - cond_value, 0); } else { other_cond->value = clamp(other_cond->value + cond_value, -99, 99); } } break; case ConditionType::TP_OVERRIDE: if (cond_flags & 2) { // Like AP_OVERRIDE above, the non-null case here is dead code in the // original code as well. Condition* other_cond = nullptr; // return_null???(card, ConditionType::TP_OVERRIDE) if (!other_cond) { this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, -cond_value, 0, 0); card->tp = max(card->tp - cond_value, 0); } else { other_cond->value = clamp(other_cond->value + cond_value, -99, 99); } } break; case ConditionType::MISC_AP_BONUSES: if (cond_flags & 2) { this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, -cond_value, 0, 0); card->ap = max(card->ap - cond_value, 0); } break; case ConditionType::MISC_TP_BONUSES: if (cond_flags & 2) { this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, -cond_value, 0, 0); card->tp = max(card->tp - cond_value, 0); } break; case ConditionType::AP_SILENCE: if (cond_flags & 2) { this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, cond_value, 0, 0); card->ap = max(card->ap + cond_value, 0); } break; case ConditionType::TP_SILENCE: if (cond_flags & 2) { this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, cond_value, 0, 0); card->tp = max(card->tp + cond_value, 0); } break; default: break; } return true; } bool CardSpecial::apply_stats_deltas_to_card_from_all_conditions_with_card_ref( uint16_t card_ref, shared_ptr card) { bool ret = false; for (ssize_t z = 8; z >= 0; z--) { auto& cond = card->action_chain.conditions[z]; if ((cond.type != ConditionType::NONE) && (cond.card_ref == card_ref)) { ret |= this->apply_stat_deltas_to_card_from_condition_and_clear_cond( cond, card); } } return ret; } bool CardSpecial::card_has_condition_with_ref( shared_ptr 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) || (card->action_chain.conditions[z].card_ref == card_ref))) { z++; } if (z >= 9) { return false; } return (match_card_ref != 0xFFFF) ? (card_ref == match_card_ref) : true; } bool CardSpecial::card_is_destroyed(shared_ptr card) const { if (card->card_flags & 3) { return true; } 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()); } void CardSpecial::compute_attack_ap( shared_ptr target_card, int16_t* out_value, uint16_t attacker_card_ref) { auto attacker_card = this->server()->card_for_set_card_ref(attacker_card_ref); AttackMedium attacker_sc_attack_medium = attacker_card ? attacker_card->action_chain.chain.attack_medium : AttackMedium::UNKNOWN; uint16_t target_card_ref = target_card->get_card_ref(); auto check_card = [&](shared_ptr card) -> void { if (!card || (card->card_flags & 3)) { return; } for (size_t cond_index = 0; cond_index < 9; cond_index++) { auto& cond = card->action_chain.conditions[cond_index]; if (cond.type == ConditionType::NONE || this->card_ref_has_ability_trap(cond) || !this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( card->action_chain.conditions[cond_index].card_ref, target_card->get_card_ref(), attacker_card_ref, card->action_chain.conditions[cond_index].card_definition_effect_index, attacker_sc_attack_medium)) { continue; } auto cond_type = card->action_chain.conditions[cond_index].type; if (((cond_type == ConditionType::UNKNOWN_5F) && (target_card_ref == card->action_chain.conditions[cond_index].condition_giver_card_ref)) || ((cond_type == ConditionType::UNKNOWN_60) && (target_card_ref == card->action_chain.conditions[cond_index].card_ref))) { *out_value = card->action_chain.conditions[cond_index].value8; } } }; for (size_t client_id = 0; client_id < 4; client_id++) { auto ps = this->server()->get_player_state(client_id); if (ps) { for (size_t set_index = 0; set_index < 8; set_index++) { check_card(ps->get_set_card(set_index)); } check_card(ps->get_sc_card()); } } if (attacker_card && attacker_card->get_attack_condition_value(ConditionType::UNKNOWN_7D, 0xFFFF, 0xFF, 0xFFFF, nullptr)) { *out_value = *out_value * 1.5f; } if (target_card && target_card->get_attack_condition_value(ConditionType::UNKNOWN_7D, 0xFFFF, 0xFF, 0xFFFF, nullptr)) { *out_value = 0; } } CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats( const ActionState& pa, shared_ptr card, const DiceRoll& dice_roll, uint16_t target_card_ref, uint16_t condition_giver_card_ref) { this->action_state = pa; auto attacker_card = this->server()->card_for_set_card_ref(pa.attacker_card_ref); if (!attacker_card && (pa.original_attacker_card_ref != 0xFFFF)) { attacker_card = this->server()->card_for_set_card_ref(pa.original_attacker_card_ref); } AttackEnvStats ast; auto ps = card->player_state(); ast.num_set_cards = ps->count_set_cards(); auto condition_giver_card = this->server()->card_for_set_card_ref(condition_giver_card_ref); auto target_card = this->server()->card_for_set_card_ref(target_card_ref); if (!target_card) { target_card = condition_giver_card; } size_t ps_num_set_cards = 0; for (size_t z = 0; z < 4; z++) { auto other_ps = this->server()->get_player_state(z); if (other_ps) { ps_num_set_cards += other_ps->count_set_cards(); } } 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; size_t target_team_num_set_cards = 0; size_t condition_giver_team_num_set_cards = 0; for (size_t z = 0; z < 4; z++) { auto other_ps = this->server()->get_player_state(z); if (other_ps) { if (target_card_team_id == other_ps->get_team_id()) { target_team_num_set_cards += other_ps->count_set_cards(); } else { condition_giver_team_num_set_cards += other_ps->count_set_cards(); } } } ast.target_team_num_set_cards = target_team_num_set_cards; ast.condition_giver_team_num_set_cards = condition_giver_team_num_set_cards; ast.num_native_creatures = this->get_all_set_cards_by_team_and_class( CardClass::NATIVE_CREATURE, 0xFF, true) .size(); ast.num_a_beast_creatures = this->get_all_set_cards_by_team_and_class( CardClass::A_BEAST_CREATURE, 0xFF, true) .size(); ast.num_machine_creatures = this->get_all_set_cards_by_team_and_class( CardClass::MACHINE_CREATURE, 0xFF, true) .size(); ast.num_dark_creatures = this->get_all_set_cards_by_team_and_class( CardClass::DARK_CREATURE, 0xFF, true) .size(); ast.num_sword_type_items = this->get_all_set_cards_by_team_and_class( CardClass::SWORD_ITEM, 0xFF, true) .size(); ast.num_gun_type_items = this->get_all_set_cards_by_team_and_class( CardClass::GUN_ITEM, 0xFF, true) .size(); ast.num_cane_type_items = this->get_all_set_cards_by_team_and_class( CardClass::CANE_ITEM, 0xFF, true) .size(); ast.num_sword_type_items_on_team = card ? this->get_all_set_cards_by_team_and_class(CardClass::SWORD_ITEM, card->get_team_id(), true).size() : 0; size_t num_item_or_creature_cards_in_hand = 0; for (size_t z = 0; z < 6; z++) { uint16_t card_ref = ps->card_ref_for_hand_index(z); if (card_ref == 0xFFFF) { continue; } auto ce = this->server()->definition_for_card_id(card_ref); if (ce && ((ce->def.type == CardType::ITEM) || (ce->def.type == CardType::CREATURE))) { num_item_or_creature_cards_in_hand++; } } ast.num_item_or_creature_cards_in_hand = num_item_or_creature_cards_in_hand; ast.num_destroyed_ally_fcs = card->num_destroyed_ally_fcs; // Note: The original implementation has dice_roll as optional, but since it's // provided at all callsites, we require it (and hence don't check for nullptr // here) ast.dice_roll_value1 = dice_roll.value; ast.dice_roll_value2 = dice_roll.value; ast.effective_ap = card->action_chain.chain.effective_ap; ast.effective_tp = card->action_chain.chain.effective_tp; ast.current_hp = card->get_current_hp(); ast.max_hp = card->get_max_hp(); ast.team_dice_boost = card ? this->server()->team_dice_boost[card->get_team_id()] : 0; 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)) ? 0 : attacker_card->action_chain.chain.damage; 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; ast.attack_bonus = card->action_metadata.attack_bonus; ast.last_attack_preliminary_damage = card->last_attack_preliminary_damage; ast.last_attack_damage = card->last_attack_final_damage; int32_t total_last_attack_damage; size_t last_attack_damage_count; this->sum_last_attack_damage(nullptr, &total_last_attack_damage, &last_attack_damage_count); ast.total_last_attack_damage = total_last_attack_damage; ast.last_attack_damage_count = last_attack_damage_count; if (!target_card) { ast.target_attack_bonus = 0; ast.target_current_hp = 0; } else { ast.target_attack_bonus = target_card->action_metadata.attack_bonus; ast.target_current_hp = target_card->get_current_hp(); } ast.player_num_destroyed_fcs = ps->num_destroyed_fcs; ast.player_num_atk_points = ps->get_atk_points(); auto ce = card->get_definition(); ast.card_cost = ce->def.self_cost; ast.defined_max_hp = ast.max_hp; size_t z; // Note: The (z < 9) conditions in these two loops are not present in the // original code. for (z = 0; ((target_card_ref != pa.attacker_card_ref) && (z < 9) && (pa.action_card_refs[z] != 0xFFFF)); z++) { } ast.action_cards_ap = 0; ast.action_cards_tp = 0; for (; (z < 9) && (pa.action_card_refs[z] != 0xFFFF); z++) { this->unknown_a2 = pa.action_card_refs[z]; auto ce = this->server()->definition_for_card_ref(pa.action_card_refs[z]); if (ce) { if (ce->def.ap.type != CardDefinition::Stat::Type::MINUS_STAT) { ast.action_cards_ap += ce->def.ap.stat; } if (ce->def.tp.type != CardDefinition::Stat::Type::MINUS_STAT) { ast.action_cards_tp += ce->def.tp.stat; } } } return ast; } shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( uint16_t target_card_ref, int unknown_p3, int unknown_p4, uint16_t attacker_card_ref, uint16_t set_card_ref, int unknown_p7, uint32_t* unknown_p9, uint8_t def_effect_index, uint32_t* unknown_p11, uint16_t sc_card_ref) { auto attacker_card = this->server()->card_for_set_card_ref(attacker_card_ref); auto target_card = this->server()->card_for_set_card_ref(target_card_ref); uint8_t target_client_id = client_id_for_card_ref(target_card_ref); uint8_t target_team_id = 0xFF; if (unknown_p9) { *unknown_p9 = 0; } if (target_card) { target_team_id = target_card->get_team_id(); } if (unknown_p11) { *unknown_p11 = 0; } Location target_card_loc; if (!target_card) { target_card_loc.x = 0; target_card_loc.y = 0; target_card_loc.direction = Direction::RIGHT; } else { this->get_card1_loc_with_card2_opposite_direction( &target_card_loc, target_card, attacker_card); } auto attack_medium = attacker_card ? attacker_card->action_chain.chain.attack_medium : AttackMedium::INVALID_FF; if ((this->server()->get_battle_phase() != BattlePhase::ACTION) || (this->server()->get_current_action_subphase() == ActionSubphase::ATTACK)) { return nullptr; } if (target_card_ref == attacker_card_ref) { return nullptr; } if (target_card_ref == set_card_ref) { return nullptr; } bool has_pierce = ((target_client_id != 0xFF) && attacker_card && (attacker_card->action_chain.check_flag(0x00002000 << target_client_id))); // Handle Parry if present if (target_card && !(target_card->card_flags & 3)) { for (size_t x = 0; x < 9; x++) { auto& cond = target_card->action_chain.conditions[x]; if ((unknown_p7 == 0) && this->card_ref_has_ability_trap(cond)) { continue; } if (cond.type == ConditionType::NONE) { continue; } if (!this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( target_card->action_chain.conditions[x].card_ref, target_card->get_card_ref(), attacker_card_ref, target_card->action_chain.conditions[x].card_definition_effect_index, attack_medium)) { continue; } if (target_card->action_chain.conditions[x].type != ConditionType::PARRY) { continue; } auto target_ps = target_card->player_state(); if (has_pierce || (unknown_p7 != 0) || !target_ps) { continue; } // Parry forwards the attack to a random FC within one tile of the // original target. Note that Sega's implementation (used here) hardcodes // the Gifoie card's ID (00D9) for compute_effective_range. // TODO: We should fix this so it doesn't rely on a fixed card definition. parray range; compute_effective_range(range, this->server()->base()->data_index, 0x00D9, target_card_loc, this->server()->base()->map_and_rules1); auto card_refs_in_parry_range = target_ps->get_all_cards_within_range( range, target_card_loc, 0xFF); // Filter out the attacker card ref, the set card ref, the original // target, and any SCs within the range vector candidate_card_refs; for (uint16_t card_ref : card_refs_in_parry_range) { if (attacker_card_ref == card_ref) { continue; } if (set_card_ref == card_ref) { continue; } if (target_card_ref == card_ref) { continue; } auto ce = this->server()->definition_for_card_ref(card_ref); if (ce && ((ce->def.type == CardType::HUNTERS_SC) || (ce->def.type == CardType::ARKZ_SC))) { continue; } candidate_card_refs.emplace_back(card_ref); } size_t num_candidates = candidate_card_refs.size(); 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 this->server()->card_for_set_card_ref( candidate_card_refs[(a + b) - ((a + b) / num_candidates) * num_candidates]); } } } // Note: Some vestigial functionality was removed here. The original code has // a parallel array of booleans that seem to specify a priority: if any of the // candidate cards has true in the priority array, then the first candidate // card with a true value is returned instead of a random entry from the // entire array. The original code only puts false values into the priority // array, effectively rendering it unused, so we've omitted it entirely. vector> candidate_cards; for (size_t client_id = 0; client_id < 4; client_id++) { auto other_ps = this->server()->get_player_state(client_id); if (!other_ps) { continue; } for (size_t set_index = 0; set_index < 8; set_index++) { auto other_set_card = other_ps->get_set_card(set_index); if (!other_set_card || (other_set_card->card_flags & 3)) { continue; } for (size_t z = 0; (z < 9) && (candidate_cards.size() < 36); z++) { auto& cond = other_set_card->action_chain.conditions[z]; if ((unknown_p7 == 0) && this->card_ref_has_ability_trap(cond)) { continue; } if (cond.type == ConditionType::NONE) { continue; } if (!this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( other_set_card->action_chain.conditions[z].card_ref, other_set_card->get_card_ref(), attacker_card_ref, other_set_card->action_chain.conditions[z].card_definition_effect_index, attack_medium)) { continue; } switch (other_set_card->action_chain.conditions[z].type) { case ConditionType::GUARD_CREATURE: if (!has_pierce && (unknown_p7 != 0) && ((unknown_p3 != 0) || (unknown_p4 != 0)) && (target_client_id == client_id) && target_card && target_card->get_definition()->def.is_sc()) { candidate_cards.emplace_back(other_set_card); } break; case ConditionType::DEFENDER: if (!has_pierce && (unknown_p7 == 0) && (unknown_p4 != 0) && (target_card_ref == other_set_card->action_chain.conditions[z].condition_giver_card_ref)) { candidate_cards.emplace_back(other_set_card); if (unknown_p11 && (def_effect_index != 0xFF) && (set_card_ref != 0xFFFF) && !this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( set_card_ref, sc_card_ref, other_set_card->get_card_ref(), def_effect_index, attack_medium)) { *unknown_p11 = 1; } } break; case ConditionType::UNKNOWN_39: if (!has_pierce && (unknown_p7 == 0) && (unknown_p3 != 0) && (target_card_ref == other_set_card->action_chain.conditions[z].condition_giver_card_ref)) { candidate_cards.emplace_back(other_set_card); } break; case ConditionType::SURVIVAL_DECOYS: if (!has_pierce && (unknown_p7 == 0) && attacker_card && (attacker_card->action_chain.chain.target_card_ref_count > 1) && (unknown_p3 != 0) && (other_set_card->get_team_id() == target_team_id)) { candidate_cards.emplace_back(other_set_card); } break; case ConditionType::REFLECT: if ((unknown_p7 == 0) && (unknown_p3 != 0)) { if (target_card_ref == other_set_card->action_chain.conditions[z].condition_giver_card_ref) { if (unknown_p9) { *unknown_p9 = 0; } return other_set_card; } else if (unknown_p9) { *unknown_p9 = 1; } } break; default: break; } } } auto other_sc = other_ps->get_sc_card(); if (other_sc && !(other_sc->card_flags & 3)) { for (size_t z = 0; (z < 9) && (candidate_cards.size() < 36); z++) { auto& cond = other_sc->action_chain.conditions[z]; if ((unknown_p7 == 0) && this->card_ref_has_ability_trap(cond)) { continue; } if (cond.type == ConditionType::NONE) { continue; } if (!this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( cond.card_ref, other_sc->get_card_ref(), attacker_card_ref, cond.card_definition_effect_index, attack_medium)) { continue; } switch (cond.type) { case ConditionType::GUARD_CREATURE: if (!has_pierce && (unknown_p7 != 0) && ((unknown_p3 != 0) || (unknown_p4 != 0)) && (target_client_id == client_id) && target_card && target_card->get_definition()->def.is_sc()) { candidate_cards.emplace_back(other_sc); } break; case ConditionType::DEFENDER: if (!has_pierce && (unknown_p7 == 0) && (unknown_p4 != 0) && (target_card_ref == cond.condition_giver_card_ref)) { candidate_cards.emplace_back(other_sc); if (unknown_p11 && (def_effect_index != 0xFF) && (set_card_ref != 0xFFFF) && !this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( set_card_ref, sc_card_ref, other_sc->get_card_ref(), def_effect_index, attack_medium)) { *unknown_p11 = 1; } } break; case ConditionType::UNKNOWN_39: if (!has_pierce && (unknown_p7 == 0) && (unknown_p3 != 0) && (target_card_ref == cond.condition_giver_card_ref)) { candidate_cards.emplace_back(other_sc); } break; case ConditionType::SURVIVAL_DECOYS: if (!has_pierce && (unknown_p7 == 0) && attacker_card && (attacker_card->action_chain.chain.target_card_ref_count > 1) && (unknown_p3 != 0) && (other_sc->get_team_id() == target_team_id)) { candidate_cards.emplace_back(other_sc); } break; case ConditionType::REFLECT: if ((unknown_p7 == 0) && (unknown_p3 != 0)) { if (target_card_ref == cond.condition_giver_card_ref) { if (unknown_p9) { *unknown_p9 = 0; } return other_sc; } else if (unknown_p9) { *unknown_p9 = 1; } } break; default: break; } } } } if (candidate_cards.empty()) { return nullptr; } // If the set card is a candidate (or the attacker is, if there's no set // card), don't redirect the attack at all for (size_t z = 0; z < candidate_cards.size(); z++) { auto candidate_card = candidate_cards[z]; uint16_t candidate_card_ref = candidate_card->get_card_ref(); if ((set_card_ref == candidate_card_ref) || ((set_card_ref == 0xFFFF) && (attacker_card_ref == candidate_card_ref))) { return nullptr; } } if (candidate_cards.size() == 1) { return candidate_cards[0]; } uint8_t index = 0; auto target_ps = target_card->player_state(); if (target_ps && (unknown_p7 == 0)) { uint8_t a = target_ps->roll_dice_with_effects(2); uint8_t b = target_ps->roll_dice_with_effects(1); index = (a + b) - ((a + b) / candidate_cards.size()) * candidate_cards.size(); } return candidate_cards[index]; } StatSwapType CardSpecial::compute_stat_swap_type(shared_ptr card) const { if (!card) { return StatSwapType::NONE; } StatSwapType ret = StatSwapType::NONE; for (size_t cond_index = 0; cond_index < 9; cond_index++) { auto& cond = card->action_chain.conditions[cond_index]; if (cond.type != ConditionType::NONE) { if (!this->card_ref_has_ability_trap(cond)) { if (cond.type == ConditionType::UNKNOWN_75) { if (ret == StatSwapType::A_H_SWAP) { ret = StatSwapType::NONE; } else { ret = StatSwapType::A_H_SWAP; } } else if (cond.type == ConditionType::A_T_SWAP) { if (ret == StatSwapType::A_T_SWAP) { ret = StatSwapType::NONE; } else { ret = StatSwapType::A_T_SWAP; } } } } } return ret; } void CardSpecial::compute_team_dice_boost(uint8_t team_id) { uint8_t value = this->server()->team_exp[team_id] / (this->server()->team_client_count[team_id] * 12); this->adjust_dice_boost_if_team_has_condition_52(team_id, &value, 0); this->server()->team_dice_boost[team_id] = min(value, 8); } bool CardSpecial::condition_has_when_20_or_21(const Condition& cond) const { auto ce = this->server()->definition_for_card_ref(cond.card_ref); if (!ce) { return false; } uint8_t when = ce->def.effects[cond.card_definition_effect_index].when; return ((when == 0x20) || (when == 0x21)); } size_t CardSpecial::count_action_cards_with_condition_for_all_current_attacks( ConditionType cond_type, 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); if (ps) { 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); } } } return ret; } size_t CardSpecial::count_action_cards_with_condition_for_current_attack( shared_ptr card, ConditionType cond_type, uint16_t card_ref) const { if (!card) { return 0; } size_t ret = 0; auto check_card_ref = [&](uint16_t other_card_ref) { if (other_card_ref == card_ref) { return; } auto ce = this->server()->definition_for_card_ref(other_card_ref); if (!ce) { return; } for (size_t cond_index = 0; cond_index < 3; cond_index++) { if (ce->def.effects[cond_index].type == ConditionType::NONE) { break; } if (ce->def.effects[cond_index].type == cond_type) { ret++; break; } } }; for (size_t z = 0; z < card->action_chain.chain.attack_action_card_ref_count; z++) { check_card_ref(card->action_chain.chain.attack_action_card_refs[z]); } for (size_t z = 0; z < card->action_metadata.defense_card_ref_count; z++) { check_card_ref(card->action_metadata.defense_card_refs[z]); } return ret; } size_t CardSpecial::count_cards_with_card_id_set_by_player_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); if (!ps) { continue; } 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)) { ret++; } } } return ret; } vector> CardSpecial::get_all_set_cards_by_team_and_class( CardClass card_class, uint8_t team_id, bool exclude_destroyed_cards) const { vector> ret; auto check_card = [&](shared_ptr card) -> void { if (card && (!exclude_destroyed_cards || !(card->card_flags & 2)) && (card->get_definition()->def.card_class() == card_class) && ((team_id == 0xFF) || (card->get_team_id() == team_id))) { ret.emplace_back(card); } }; for (size_t client_id = 0; client_id < 4; client_id++) { auto ps = this->server()->get_player_state(client_id); if (!ps) { continue; } check_card(ps->get_sc_card()); for (size_t set_index = 0; set_index < 8; set_index++) { check_card(ps->get_set_card(set_index)); } } return ret; } ActionState CardSpecial::create_attack_state_from_card_action_chain( shared_ptr 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); 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); } for (size_t z = 0; z < attacker_card->action_chain.chain.target_card_ref_count; z++) { ret.target_card_refs[z] = this->send_6xB4x06_if_card_ref_invalid( attacker_card->action_chain.chain.target_card_refs[z], 6); } } return ret; } ActionState CardSpecial::create_defense_state_for_card_pair_action_chains( shared_ptr attacker_card, shared_ptr defender_card) const { ActionState ret; if (defender_card && attacker_card) { size_t count = 0; for (size_t z = 0; z < defender_card->action_metadata.defense_card_ref_count; z++) { if ((defender_card->action_metadata.defense_card_refs[z] != 0xFFFF) && (defender_card->action_metadata.original_attacker_card_refs[z] == attacker_card->get_card_ref())) { ret.action_card_refs[count++] = this->send_6xB4x06_if_card_ref_invalid( defender_card->action_metadata.defense_card_refs[z], 7); } } } if (defender_card) { 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); } return ret; } void CardSpecial::destroy_card_if_hp_zero( shared_ptr 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)); } } bool CardSpecial::evaluate_effect_arg2_condition( const ActionState& as, shared_ptr card, const char* arg2_text, DiceRoll& dice_roll, uint16_t set_card_ref, uint16_t sc_card_ref, uint8_t random_percent, uint8_t when) const { // Note: In the original code, as and dice_roll were optional pointers, but // they are non-null at all callsites, so we've replaced them with references // (and eliminated the null checks within this function). uint16_t attacker_card_ref = as.attacker_card_ref; if (attacker_card_ref == 0xFFFF) { attacker_card_ref = as.original_attacker_card_ref; } auto set_card = this->server()->card_for_set_card_ref(set_card_ref); bool set_card_has_ability_trap = (set_card && (this->card_has_condition_with_ref(set_card, ConditionType::ABILITY_TRAP, 0xFFFF, 0xFFFF))); switch (arg2_text[0]) { case 'C': card = this->server()->card_for_set_card_ref(set_card_ref); if (!card) { card = this->server()->card_for_set_card_ref(sc_card_ref); } if (!card) { return false; } [[fallthrough]]; case 'c': { uint8_t ch1 = arg2_text[1] - '0'; uint8_t ch2 = arg2_text[2] - '0'; if ((ch1 > 9) || (ch2 > 9)) { return false; } auto ps = this->server()->get_player_state(client_id_for_card_ref(card->get_card_ref())); if (!ps) { return false; } for (size_t set_index = 0; set_index < 8; set_index++) { auto card = ps->get_set_card(set_index); if (!card) { continue; } auto ce = card->get_definition(); if (!ce) { continue; } for (size_t cond_index = 0; cond_index < 3; cond_index++) { if (ce->def.effects[cond_index].type == ConditionType::NONE) { break; } uint8_t arg2_command = ce->def.effects[cond_index].arg2[0]; if ((arg2_command == 'c') || (arg2_command == 'C')) { uint8_t other_ch1 = ce->def.effects[cond_index].arg2[1] - 0x30; if ((other_ch1 > 9)) { return false; } if (other_ch1 == ch2) { return true; } } } } return false; } case 'b': { auto attacker_card = this->server()->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; } uint8_t low = arg2_text[1] - '0'; uint8_t high = arg2_text[2] - '0'; if ((low < 10) && (high < 10)) { if (high < low) { uint8_t t = high; high = low; low = t; } dice_roll.value_used_in_expr = true; return ((low <= dice_roll.value) && (dice_roll.value <= high)); } return false; } case 'h': return (atoi(arg2_text + 1) <= card->get_current_hp()); case 'i': return (atoi(arg2_text + 1) >= card->get_current_hp()); case 'm': { auto attacker_card = this->server()->card_for_set_card_ref(attacker_card_ref); return (attacker_card && (attacker_card->action_chain.chain.damage >= atoi(arg2_text + 1))); } case 'n': switch (atoi(arg2_text + 1)) { case 0: return true; case 1: return (!card || (card->get_definition()->def.type == CardType::HUNTERS_SC)); case 2: for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { auto target_card = this->server()->card_for_set_card_ref(as.target_card_refs[z]); if (target_card && target_card->check_card_flag(2)) { return true; } } return false; case 3: for (size_t z = 0; z < 8; z++) { uint16_t action_card_ref = as.action_card_refs[z]; if (action_card_ref != 0xFFFF) { auto ce = this->server()->definition_for_card_ref(action_card_ref); if (card_class_is_tech_like(ce->def.card_class())) { return true; } } } return false; case 4: return card->action_chain.check_flag(0x0001E000); case 5: return card->action_chain.check_flag(0x00001E00); case 6: return (card->get_definition()->def.card_class() == CardClass::NATIVE_CREATURE); case 7: return (card->get_definition()->def.card_class() == CardClass::A_BEAST_CREATURE); case 8: return (card->get_definition()->def.card_class() == CardClass::MACHINE_CREATURE); case 9: return (card->get_definition()->def.card_class() == CardClass::DARK_CREATURE); case 10: return (card->get_definition()->def.card_class() == CardClass::SWORD_ITEM); case 11: return (card->get_definition()->def.card_class() == CardClass::GUN_ITEM); case 12: return (card->get_definition()->def.card_class() == CardClass::CANE_ITEM); case 13: { auto ce = card->get_definition(); return ((ce->def.card_class() == CardClass::GUARD_ITEM) || (ce->def.card_class() == CardClass::MAG_ITEM) || this->server()->ruler_server->find_condition_on_card_ref( card->get_card_ref(), ConditionType::GUARD_CREATURE, 0, 0, 0)); } case 14: return card->get_definition()->def.is_sc(); case 15: return ((card->action_chain.chain.attack_action_card_ref_count == 0) && (card->action_metadata.defense_card_ref_count == 0)); case 16: return this->server()->ruler_server->card_ref_is_aerial(card->get_card_ref()); case 17: { auto sc_card = this->server()->card_for_set_card_ref(sc_card_ref); int16_t this_ap = card->ap; int16_t other_ap = -1; if (!sc_card) { auto ce = this->server()->definition_for_card_ref(sc_card_ref); if (ce) { other_ap = ce->def.ap.stat; } } else { other_ap = sc_card->ap; } return (other_ap == this_ap); } case 18: for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { auto target_card = this->server()->card_for_set_card_ref(as.target_card_refs[z]); if (target_card && target_card->get_definition()->def.is_sc()) { return true; } } return false; case 19: return this->server()->ruler_server->find_condition_on_card_ref( card->get_card_ref(), ConditionType::PARALYZE, 0, 0, 0); case 20: return this->server()->ruler_server->find_condition_on_card_ref( card->get_card_ref(), ConditionType::FREEZE, 0, 0, 0); case 21: { uint8_t client_id = client_id_for_card_ref(sc_card_ref); if (client_id != 0xFF) { return card->action_chain.check_flag(0x00002000 << client_id); } return false; } case 22: { uint8_t client_id = client_id_for_card_ref(sc_card_ref); if (client_id != 0xFF) { return card->action_chain.check_flag(0x00000200 << client_id); } return false; } default: return false; } throw logic_error("this should be impossible"); case 'o': { uint8_t v = atoi(arg2_text + 1); if ((v / 10) == 1) { auto new_card = this->server()->card_for_set_card_ref(set_card_ref); if (!new_card) { new_card = this->server()->card_for_set_card_ref(sc_card_ref); } if (new_card) { card = new_card; } } return (this->find_condition_with_parameters( card, ConditionType::ANY, set_card_ref, ((v % 10) == 0) ? 0xFF : (v % 10)) != nullptr); } case 'r': return !set_card_has_ability_trap && (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')); } case 't': { auto set_card = this->server()->card_for_set_card_ref(set_card_ref); if (!set_card) { return false; } uint8_t v = atoi(arg2_text + 1); // TODO: Figure out what this logic actually does and rename the variables // or comment it appropriately. if (when == 4) { uint32_t y = set_card->unknown_a9 & 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))) { return true; } } return false; } default: return false; } throw logic_error("this should be impossible"); } int32_t CardSpecial::evaluate_effect_expr( const AttackEnvStats& ast, const char* expr, DiceRoll& dice_roll) const { // Note: This implementation is not based on the original code because the // original code was hard to follow - it used a look-behind approach with lots // of local variables instead of the look-ahead approach that this // implementation uses. Hopefully this implementation is easier to follow. vector> tokens; while (expr) { ExpressionTokenType type; int32_t value = 0; expr = this->get_next_expr_token(expr, &type, &value); if (expr) { if (type == ExpressionTokenType::SPACE) { throw runtime_error("expression contains space token"); } // Turn references into numbers, so only numbers and operators can appear // in the tokens vector if (type == ExpressionTokenType::REFERENCE) { if ((value == 1) || (value == 11)) { dice_roll.value_used_in_expr = true; } tokens.emplace_back(make_pair(ExpressionTokenType::NUMBER, ast.at(value))); } else { tokens.emplace_back(make_pair(type, value)); } } } // Operators are evaluated left-to-right - there are no operator precedence // rules int32_t value = 0; for (size_t token_index = 0; token_index < tokens.size(); token_index++) { auto token_type = tokens[token_index].first; int32_t token_value = tokens[token_index].second; if ((token_type == ExpressionTokenType::SPACE) || (token_type == ExpressionTokenType::REFERENCE)) { throw logic_error("space or reference token present in expr evaluation phase 2"); } if (token_type == ExpressionTokenType::NUMBER) { value = token_value; } else { if (token_index >= tokens.size() - 1) { throw runtime_error("no token on right side of binary operator"); } token_index++; auto right_token_type = tokens[token_index].first; auto right_value = tokens[token_index].second; if (right_token_type != ExpressionTokenType::NUMBER) { throw runtime_error("non-number, non-reference token on right side of operator"); } switch (token_type) { case ExpressionTokenType::ROUND_DIVIDE: value = lround(value / right_value); break; case ExpressionTokenType::SUBTRACT: value -= right_value; break; case ExpressionTokenType::ADD: value += right_value; break; case ExpressionTokenType::MULTIPLY: value *= right_value; break; case ExpressionTokenType::FLOOR_DIVIDE: value = floor(value / right_value); break; default: throw logic_error("invalid binary operator"); } } } return value; } bool CardSpecial::execute_effect( Condition& cond, shared_ptr card, int16_t expr_value, int16_t unknown_p5, ConditionType cond_type, uint unknown_p7, uint16_t attacker_card_ref) { int16_t clamped_expr_value = clamp(expr_value, -99, 99); int16_t clamped_unknown_p5 = clamp(unknown_p5, -99, 99); cond.value8 = clamped_expr_value; if (this->card_ref_has_ability_trap(cond)) { return false; } if (card->card_flags & 1) { return false; } if ((card->card_flags & 3) || (card->action_metadata.check_flag(0x10) && (cond.card_ref != card->get_card_ref()) && (cond.condition_giver_card_ref != card->get_card_ref()))) { unknown_p7 = unknown_p7 & 0xFFFFFFFB; } if (unknown_p7 == 0) { return false; } int16_t positive_expr_value = max(0, clamped_expr_value); clamped_unknown_p5 = max(0, clamped_unknown_p5); auto attacker_sc = this->server()->card_for_set_card_ref(attacker_card_ref); auto attack_medium = attacker_sc ? attacker_sc->action_chain.chain.attack_medium : AttackMedium::UNKNOWN; switch (cond_type) { case ConditionType::RAMPAGE: case ConditionType::IMMOBILE: case ConditionType::HOLD: case ConditionType::UNKNOWN_07: case ConditionType::GUOM: case ConditionType::PARALYZE: case ConditionType::PIERCE: case ConditionType::UNKNOWN_0F: case ConditionType::UNKNOWN_12: case ConditionType::UNKNOWN_13: case ConditionType::ACID: case ConditionType::UNKNOWN_15: case ConditionType::ABILITY_TRAP: case ConditionType::FREEZE: case ConditionType::MAJOR_PIERCE: case ConditionType::HEAVY_PIERCE: case ConditionType::MAJOR_RAMPAGE: case ConditionType::HEAVY_RAMPAGE: case ConditionType::DEF_DISABLE_BY_COST: default: return false; case ConditionType::UNKNOWN_39: case ConditionType::DEFENDER: case ConditionType::SURVIVAL_DECOYS: case ConditionType::EXP_DECOY: case ConditionType::SET_MV: case ConditionType::MV_BONUS: return true; case ConditionType::AP_BOOST: if (unknown_p7 & 1) { card->action_chain.chain.ap_effect_bonus = clamp( card->action_chain.chain.ap_effect_bonus + positive_expr_value, -99, 99); } return true; case ConditionType::MULTI_STRIKE: if (unknown_p7 & 1) { card->action_chain.chain.strike_count = positive_expr_value; } return true; case ConditionType::DAMAGE_MOD_1: if (unknown_p7 & 2) { card->action_chain.chain.damage = positive_expr_value; } return true; case ConditionType::TP_BOOST: if (unknown_p7 & 1) { card->action_chain.chain.tp_effect_bonus = clamp( card->action_chain.chain.tp_effect_bonus + positive_expr_value, -99, 99); } return true; case ConditionType::GIVE_DAMAGE: if ((unknown_p7 & 4) != 0) { int16_t current_hp = clamp(card->get_current_hp(), -99, 99); int16_t new_hp = clamp(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(new_hp, 0); if (new_hp != current_hp) { card->set_current_hp(new_hp); this->destroy_card_if_hp_zero(card, attacker_card_ref); } } return true; case ConditionType::UNKNOWN_0C: case ConditionType::A_T_SWAP_PERM: if (unknown_p7 & 4) { int16_t ap = clamp(card->ap, -99, 99); int16_t tp = clamp(card->tp, -99, 99); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0xA0, tp - ap, 0, 0); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x80, ap - tp, 0, 0); card->ap = tp; card->tp = ap; cond.flags |= 2; } return true; case ConditionType::A_H_SWAP: case ConditionType::A_H_SWAP_PERM: if (unknown_p7 & 4) { int16_t ap = clamp(card->ap, -99, 99); int16_t hp = clamp(card->get_current_hp(), -99, 99); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0xA0, hp - ap, 0, 0); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x20, ap - hp, 1, 0); cond.flags |= 2; if (ap != hp) { card->set_current_hp(ap); card->ap = hp; this->destroy_card_if_hp_zero(card, attacker_card_ref); } } return true; case ConditionType::HEAL: if (unknown_p7 & 4) { int16_t hp = clamp(card->get_current_hp(), -99, 99); int16_t new_hp = clamp(hp + positive_expr_value, -99, 99); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x20, new_hp - hp, 1, 1); if (new_hp != hp) { card->set_current_hp(new_hp); this->destroy_card_if_hp_zero(card, attacker_card_ref); } } return true; case ConditionType::RETURN_TO_HAND: if (unknown_p7 & 4) { uint8_t client_id = client_id_for_card_ref(card->get_card_ref()); if (client_id == 0xFF) { return false; } auto ps = this->server()->player_states[client_id]; if (!ps) { return false; } if ((card->card_flags & 2) || this->card_is_destroyed(card)) { return true; } this->send_6xB4x06_for_card_destroyed(card, attacker_card_ref); card->unknown_802380C0(); if (!ps->return_set_card_to_hand1(card->get_card_ref())) { return ps->discard_card_or_add_to_draw_pile(card->get_card_ref(), false); } } return false; case ConditionType::MIGHTY_KNUCKLE: { auto ps = card->player_state(); uint8_t atk = ps->get_atk_points(); if (unknown_p7 & 1) { card->action_chain.chain.ap_effect_bonus = clamp( card->action_chain.chain.ap_effect_bonus + clamped_unknown_p5, -99, 99); } if (unknown_p7 & 4) { ps->subtract_atk_points(atk); } return true; } case ConditionType::UNIT_BLOW: if (unknown_p7 & 1) { int16_t count = clamp(this->count_action_cards_with_condition_for_all_current_attacks(ConditionType::UNIT_BLOW, 0xFFFF), -99, 99); card->action_chain.chain.ap_effect_bonus = clamp(card->action_chain.chain.ap_effect_bonus + count * positive_expr_value, -99, 99); } return false; case ConditionType::CURSE: if (unknown_p7 & 4) { for (size_t z = 0; z < card->action_chain.chain.target_card_ref_count; z++) { auto target_card = this->server()->card_for_set_card_ref( card->action_chain.chain.target_card_refs[z]); if (target_card) { CardShortStatus stat = target_card->get_short_status(); if (stat.card_flags & 2) { int16_t hp = clamp(card->get_current_hp(), -99, 99); int16_t new_hp = max(0, hp - 1); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x20, -1, 0, 1); if (hp != new_hp) { card->set_current_hp(new_hp); this->destroy_card_if_hp_zero(card, attacker_card_ref); } } } } } return true; case ConditionType::COMBO_AP: if (unknown_p7 & 1) { int16_t count = clamp(this->count_action_cards_with_condition_for_all_current_attacks(ConditionType::COMBO_AP, 0xFFFF), -99, 99); card->action_chain.chain.ap_effect_bonus = clamp( card->action_chain.chain.ap_effect_bonus + count * count, -99, 99); } return false; case ConditionType::PIERCE_RAMPAGE_BLOCK: if (unknown_p7 & 4) { card->action_chain.set_flags(0x40); } if (unknown_p7 & 3) { card->action_chain.reset(); } return true; case ConditionType::ANTI_ABNORMALITY_1: if (unknown_p7 & 4) { for (ssize_t z = 8; z >= 0; z--) { auto& cond = card->action_chain.conditions[z]; if ((cond.type == ConditionType::IMMOBILE) || (cond.type == ConditionType::HOLD) || (cond.type == ConditionType::UNKNOWN_07) || (cond.type == ConditionType::GUOM) || (cond.type == ConditionType::PARALYZE) || (cond.type == ConditionType::UNKNOWN_13) || (cond.type == ConditionType::ACID) || (cond.type == ConditionType::UNKNOWN_15) || (cond.type == ConditionType::CURSE) || (cond.type == ConditionType::PIERCE_RAMPAGE_BLOCK) || (cond.type == ConditionType::FREEZE) || (cond.type == ConditionType::UNKNOWN_1E) || (cond.type == ConditionType::DROP)) { G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x0C); cmd.effect.target_card_ref = card->get_card_ref(); cmd.effect.value = 0; cmd.effect.operation = -static_cast(cond.type); cmd.effect.condition_index = z; this->server()->send(cmd); this->apply_stat_deltas_to_card_from_condition_and_clear_cond( cond, card); card->send_6xB4x4E_4C_4D_if_needed(); } } } return false; case ConditionType::UNKNOWN_1E: if (unknown_p7 & 4) { auto sc_card = this->server()->card_for_set_card_ref(attacker_card_ref); if (!sc_card || (sc_card->action_chain.chain.attack_medium == AttackMedium::PHYSICAL)) { int16_t hp = clamp(card->get_current_hp(), -99, 99); int16_t new_hp = lround(hp * 0.5f); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x20, new_hp - hp, 0, 1); if (new_hp != hp) { card->set_current_hp(new_hp); this->destroy_card_if_hp_zero(card, attacker_card_ref); } } } return true; case ConditionType::EXPLOSION: if (unknown_p7 & 0x40) { int16_t count = clamp(this->count_action_cards_with_condition_for_all_current_attacks(ConditionType::EXPLOSION, 0xFFFF), -99, 99); card->action_metadata.attack_bonus = clamp(count * count, -99, 99); } return false; case ConditionType::UNKNOWN_22: if (unknown_p7 & 4) { this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x80, positive_expr_value - card->tp, 0, 1); card->tp = positive_expr_value; } return true; case ConditionType::RETURN_TO_DECK: { if (!(unknown_p7 & 4)) { return true; } uint8_t client_id = client_id_for_card_ref(card->get_card_ref()); if (client_id == 0xFF) { return false; } auto ps = this->server()->player_states[client_id]; if (!ps) { return false; } card->unknown_802380C0(); return ps->discard_card_or_add_to_draw_pile(card->get_card_ref(), true); } case ConditionType::AP_LOSS: if (unknown_p7 & 1) { card->action_chain.chain.ap_effect_bonus = clamp( card->action_chain.chain.ap_effect_bonus - positive_expr_value, -99, 99); } return true; case ConditionType::BONUS_FROM_LEADER: if (unknown_p7 & 1) { clamped_unknown_p5 = this->count_cards_with_card_id_set_by_player_except_card_ref(expr_value, 0xFFFF) + (card->action_chain).chain.ap_effect_bonus; (card->action_chain).chain.ap_effect_bonus = clamp(clamped_unknown_p5, -99, 99); } return true; case ConditionType::FILIAL: { if (unknown_p7 & 4) { this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x20, positive_expr_value, 0, 1); if (positive_expr_value != 0) { int16_t hp = clamp(card->get_current_hp(), -99, 99); int16_t new_hp = clamp(hp + positive_expr_value, -99, 99); card->set_current_hp(new_hp, true, false); this->destroy_card_if_hp_zero(card, attacker_card_ref); } } return true; } case ConditionType::SNATCH: if (unknown_p7 & 4) { uint8_t attacker_client_id = client_id_for_card_ref(cond.card_ref); uint8_t target_client_id = client_id_for_card_ref(card->get_card_ref()); if ((attacker_client_id != 0xFF) && (target_client_id != 0xFF)) { auto attacker_ps = this->server()->player_states[attacker_client_id]; auto target_ps = this->server()->player_states[target_client_id]; if (attacker_ps && target_ps) { uint8_t attacker_team_id = attacker_ps->get_team_id(); uint8_t target_team_id = target_ps->get_team_id(); if (positive_expr_value < this->server()->team_exp[target_team_id]) { this->server()->team_exp[attacker_team_id] += positive_expr_value; this->server()->team_exp[target_team_id] -= positive_expr_value; } else { positive_expr_value = this->server()->team_exp[target_team_id]; this->server()->team_exp[attacker_team_id] += this->server()->team_exp[target_team_id]; this->server()->team_exp[target_team_id] = 0; } this->compute_team_dice_boost(attacker_team_id); this->compute_team_dice_boost(target_team_id); this->send_6xB4x06_for_exp_change(card, attacker_card_ref, -positive_expr_value, 1); this->server()->update_battle_state_flags_and_send_6xB4x03_if_needed(); } } } return true; case ConditionType::HAND_DISRUPTER: { if (unknown_p7 & 4) { auto ps = card->player_state(); for (; positive_expr_value > 0; positive_expr_value--) { size_t hand_size = ps->get_hand_size(); 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); if (card_ref != 0xFFFF) { ps->discard_ref_from_hand(card_ref); } } else { break; } } ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); } return true; } case ConditionType::DROP: if (unknown_p7 & 4) { auto ps = card->player_state(); if (ps) { uint8_t team_id = ps->get_team_id(); int16_t delta = 0; if (this->server()->team_exp[team_id] < 4) { this->server()->team_exp[team_id] = 0; } else { delta = -3; this->server()->team_exp[team_id] -= 3; } this->compute_team_dice_boost(team_id); this->send_6xB4x06_for_exp_change(card, attacker_card_ref, delta, 1); } } return true; case ConditionType::ACTION_DISRUPTER: if (unknown_p7 & 4) { 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]); } card->action_chain.chain.attack_action_card_ref_count = 0; } return true; case ConditionType::SET_HP: { if ((unknown_p7 & 4) && (card->action_metadata.defense_power < 99)) { int16_t hp = card->get_current_hp(); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x20, positive_expr_value - hp, 0, 1); if (hp != positive_expr_value) { card->set_current_hp(positive_expr_value, true, false); this->destroy_card_if_hp_zero(card, attacker_card_ref); } } return true; } case ConditionType::NATIVE_SHIELD: return this->apply_attribute_guard_if_possible( unknown_p7, CardClass::NATIVE_CREATURE, card, cond.condition_giver_card_ref, attacker_card_ref); case ConditionType::A_BEAST_SHIELD: return this->apply_attribute_guard_if_possible( unknown_p7, CardClass::A_BEAST_CREATURE, card, cond.condition_giver_card_ref, attacker_card_ref); case ConditionType::MACHINE_SHIELD: return this->apply_attribute_guard_if_possible( unknown_p7, CardClass::MACHINE_CREATURE, card, cond.condition_giver_card_ref, attacker_card_ref); case ConditionType::DARK_SHIELD: return this->apply_attribute_guard_if_possible( unknown_p7, CardClass::DARK_CREATURE, card, cond.condition_giver_card_ref, attacker_card_ref); case ConditionType::SWORD_SHIELD: return this->apply_attribute_guard_if_possible( unknown_p7, CardClass::SWORD_ITEM, card, cond.condition_giver_card_ref, attacker_card_ref); case ConditionType::GUN_SHIELD: return this->apply_attribute_guard_if_possible( unknown_p7, CardClass::GUN_ITEM, card, cond.condition_giver_card_ref, attacker_card_ref); case ConditionType::CANE_SHIELD: return this->apply_attribute_guard_if_possible( unknown_p7, CardClass::CANE_ITEM, card, cond.condition_giver_card_ref, attacker_card_ref); case ConditionType::UNKNOWN_38: { auto ps = card->player_state(); if (ps && (unknown_p7 & 4)) { ps->subtract_def_points(ps->get_def_points() - positive_expr_value); ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); } return true; } case ConditionType::GIVE_OR_TAKE_EXP: if (unknown_p7 & 4) { uint8_t client_id = client_id_for_card_ref(card->get_card_ref()); if ((client_id != 0xFF) && this->server()->player_states[client_id]) { uint8_t team_id = this->server()->player_states[client_id]->get_team_id(); int32_t existing_exp = this->server()->team_exp[team_id]; if ((clamped_expr_value + existing_exp) < 0) { clamped_expr_value = -existing_exp; this->server()->team_exp[team_id] = 0; } else { this->server()->team_exp[team_id] = existing_exp + clamped_expr_value; } this->send_6xB4x06_for_exp_change(card, attacker_card_ref, clamped_expr_value, 1); this->compute_team_dice_boost(team_id); this->server()->update_battle_state_flags_and_send_6xB4x03_if_needed(); } } return true; case ConditionType::UNKNOWN_3D: if (unknown_p7 & 4) { this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0xA0, positive_expr_value - card->ap, 0, 1); card->ap = positive_expr_value; } return true; case ConditionType::DEATH_COMPANION: if (attacker_sc && (unknown_p7 & 4)) { vector card_refs; card_refs.emplace_back(attacker_sc->get_card_ref()); if (attacker_sc != card) { card_refs.emplace_back(card->get_card_ref()); } for (uint16_t card_ref : card_refs) { auto sc_card = this->server()->card_for_set_card_ref(card_ref); if (sc_card && (sc_card->get_current_hp() > 0)) { if (this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( cond.card_ref, cond.condition_giver_card_ref, sc_card->get_card_ref(), cond.card_definition_effect_index, attack_medium)) { this->send_6xB4x06_for_stat_delta(sc_card, attacker_card_ref, 0x20, -sc_card->get_current_hp(), 0, 1); sc_card->set_current_hp(0); this->destroy_card_if_hp_zero(sc_card, attacker_card_ref); } } } } return false; case ConditionType::GROUP: if (unknown_p7 & 1) { auto ce = card->get_definition(); if (ce) { int16_t count = clamp( this->count_cards_with_card_id_set_by_player_except_card_ref(ce->def.card_id, card->get_card_ref()), -99, 99); card->action_chain.chain.ap_effect_bonus = clamp( card->action_chain.chain.ap_effect_bonus + count * positive_expr_value, -99, 99); } } return true; case ConditionType::BERSERK: if (unknown_p7 & 4) { int16_t hp = clamp(card->get_current_hp(), -99, 99); int16_t new_hp = clamp(hp - this->max_all_attack_bonuses(nullptr), -99, 99); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x20, new_hp - hp, 0, 1); new_hp = max(new_hp, 0); if (new_hp != hp) { card->set_current_hp(new_hp); this->destroy_card_if_hp_zero(card, attacker_card_ref); } } return true; case ConditionType::UNKNOWN_49: if (unknown_p7 & 4) { auto attacker_card = this->server()->card_for_set_card_ref(attacker_card_ref); if (attacker_card && (attacker_card != card)) { for (ssize_t z = 8; z >= 0; z--) { this->apply_stat_deltas_to_card_from_condition_and_clear_cond( attacker_card->action_chain.conditions[z], attacker_card); } for (size_t z = 0; z < 9; z++) { attacker_card->action_chain.conditions[z] = card->action_chain.conditions[z]; } for (size_t z = 0; z < 9; z++) { 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); } } } } return true; 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 = clamp(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 = clamp(card->tp + positive_expr_value, -99, 99); } return true; case ConditionType::COPY: if (unknown_p7 & 4) { auto attacker_card = this->server()->card_for_set_card_ref(attacker_card_ref); if (attacker_card && (attacker_card != card)) { int16_t new_ap = clamp((positive_expr_value < 51) ? (card->ap / 2) : card->ap, -99, 99); int16_t new_tp = clamp((positive_expr_value < 51) ? (card->tp / 2) : card->tp, -99, 99); this->send_6xB4x06_for_stat_delta( attacker_card, attacker_card_ref, 0xA0, new_ap - attacker_card->ap, 0, 0); this->send_6xB4x06_for_stat_delta( attacker_card, attacker_card_ref, 0x80, new_tp - attacker_card->tp, 0, 0); attacker_card->ap = new_ap; attacker_card->tp = new_tp; } } return true; case ConditionType::MISC_GUARDS: if (unknown_p7 & 8) { card->action_metadata.defense_bonus = clamp( positive_expr_value + card->action_metadata.defense_bonus, -99, 99); } return true; case ConditionType::AP_OVERRIDE: if ((unknown_p7 & 4) && !(cond.flags & 2)) { cond.value = clamp(positive_expr_value - card->ap, -99, 99); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0xA0, cond.value, 0, 0); card->ap = positive_expr_value; cond.flags |= 2; } return true; case ConditionType::TP_OVERRIDE: if ((unknown_p7 & 4) && !(cond.flags & 2)) { cond.value = clamp(positive_expr_value - card->tp, -99, 99); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x80, cond.value, 0, 0); card->tp = positive_expr_value; cond.flags |= 2; } return true; case ConditionType::SLAYERS_ASSASSINS: case ConditionType::UNKNOWN_64: case ConditionType::FORWARD_DAMAGE: if (unknown_p7 & 0x20) { card->action_metadata.attack_bonus = clamp( positive_expr_value + card->action_metadata.attack_bonus, -99, 99); } return true; case ConditionType::BLOCK_ATTACK: if (unknown_p7 & 4) { card->action_metadata.set_flags(0x10); } return true; case ConditionType::COMBO_TP: if (unknown_p7 & 1) { ssize_t count = this->count_cards_with_card_id_set_by_player_except_card_ref( expr_value, 0xFFFF); card->action_chain.chain.tp_effect_bonus = clamp( count + card->action_chain.chain.tp_effect_bonus, -99, 99); } return true; case ConditionType::MISC_AP_BONUSES: if ((unknown_p7 & 4) && !(cond.flags & 2)) { int16_t orig_ap = clamp(card->ap, -99, 99); card->ap = clamp(positive_expr_value + card->ap, 0, 99); cond.value = clamp(card->ap - orig_ap, -99, 99); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0xA0, cond.value, 0, 0); cond.flags |= 2; } return false; case ConditionType::MISC_TP_BONUSES: if ((unknown_p7 & 4) && !(cond.flags & 2)) { int16_t orig_tp = clamp(card->tp, -99, 99); card->tp = clamp(positive_expr_value + card->tp, 0, 99); cond.value = clamp(card->tp - orig_tp, -99, 99); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x80, cond.value, 0, 0); cond.flags |= 2; } return false; case ConditionType::MISC_DEFENSE_BONUSES: case ConditionType::WEAK_SPOT_INFLUENCE: if (unknown_p7 & 0x20) { card->action_metadata.attack_bonus = clamp( card->action_metadata.attack_bonus - positive_expr_value, 0, 99); } return true; case ConditionType::MOSTLY_HALFGUARDS: case ConditionType::DAMAGE_MODIFIER_2: if (unknown_p7 & 0x40) { card->action_metadata.attack_bonus = positive_expr_value; } return true; case ConditionType::PERIODIC_FIELD: if ((unknown_p7 & 0x40) && (static_cast(attack_medium) == ((this->server()->get_round_num() >> 1) & 1) + 1)) { card->action_metadata.attack_bonus = 0; } return true; case ConditionType::AP_SILENCE: if ((unknown_p7 & 4) && !(cond.flags & 2)) { int16_t prev_ap = clamp(card->ap, -99, 99); card->ap = clamp(card->ap - positive_expr_value, 0, 99); cond.value = clamp(prev_ap - card->ap, -99, 99); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0xA0, -cond.value, 0, 0); cond.flags |= 2; } return false; case ConditionType::TP_SILENCE: if ((unknown_p7 & 4) && !(cond.flags & 2)) { int16_t prev_ap = clamp(card->tp, -99, 99); card->tp = clamp(card->tp - positive_expr_value, 0, 99); cond.value = clamp(prev_ap - card->tp, -99, 99); this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x80, -cond.value, 0, 0); cond.flags |= 2; } return false; case ConditionType::RAMPAGE_AP_LOSS: if (unknown_p7 & 1) { card->action_chain.chain.tp_effect_bonus = clamp( card->action_chain.chain.tp_effect_bonus - positive_expr_value, -99, 99); } return true; case ConditionType::UNKNOWN_77: if (attacker_sc && (unknown_p7 & 4)) { vector card_refs; card_refs.emplace_back(attacker_sc->get_card_ref()); for (size_t z = 0; z < attacker_sc->action_chain.chain.target_card_ref_count; z++) { card_refs.emplace_back(attacker_sc->action_chain.chain.target_card_refs[z]); } for (uint16_t card_ref : card_refs) { auto set_card = this->server()->card_for_set_card_ref(card_ref); if (set_card && (set_card->get_current_hp() > 0)) { if (this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( cond.card_ref, cond.condition_giver_card_ref, set_card->get_card_ref(), cond.card_definition_effect_index, attack_medium)) { this->send_6xB4x06_for_stat_delta( set_card, attacker_card_ref, 0x20, -set_card->get_current_hp(), 0, 1); set_card->set_current_hp(0); this->destroy_card_if_hp_zero(set_card, attacker_card_ref); } } } } return false; } } const Condition* CardSpecial::find_condition_with_parameters( shared_ptr card, ConditionType cond_type, uint16_t set_card_ref, uint8_t def_effect_index) const { const Condition* ret = nullptr; uint8_t max_order = 9; for (size_t z = 0; z < 9; z++) { if (card->action_chain.conditions[z].type == ConditionType::NONE) { continue; } auto& cond = card->action_chain.conditions[z]; auto orig_eff = this->original_definition_for_condition(cond); if (!this->card_ref_has_ability_trap(cond) && ((cond_type == ConditionType::ANY) || (cond.type == cond_type)) && ((set_card_ref == 0xFFFF) || (cond.card_ref == set_card_ref)) && ((def_effect_index == 0xFF) || (orig_eff && (orig_eff->effect_num == def_effect_index))) && (!ret || (max_order < cond.order))) { max_order = cond.order; ret = &cond; } } return ret; } Condition* CardSpecial::find_condition_with_parameters( shared_ptr card, ConditionType cond_type, uint16_t set_card_ref, uint8_t def_effect_index) const { return const_cast(this->find_condition_with_parameters( static_cast>(card), cond_type, set_card_ref, def_effect_index)); } void CardSpecial::get_card1_loc_with_card2_opposite_direction( Location* out_loc, shared_ptr card1, shared_ptr card2) { if (card1) { if (!card2 || (static_cast(card2->facing_direction) & 0x80)) { *out_loc = card1->loc; } else if ((card2->loc.x == card1->loc.x) && (card2->loc.y == card1->loc.y)) { *out_loc = card1->loc; out_loc->direction = card2->facing_direction; } else { *out_loc = card1->loc; out_loc->direction = turn_around(card2->facing_direction); } } } uint16_t CardSpecial::get_card_id_with_effective_range( shared_ptr card1, uint16_t default_card_id, shared_ptr card2) const { if (card2 && !(static_cast(card2->facing_direction) & 0x80)) { return this->server()->ruler_server->get_card_id_with_effective_range( card1 ? card1->get_card_ref() : 0xFFFF, default_card_id, 0); } return default_card_id; } 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) { switch (type) { case StatSwapType::NONE: *effective_ap = ap; *effective_tp = tp; break; case StatSwapType::A_T_SWAP: *effective_ap = tp; *effective_tp = ap; break; case StatSwapType::A_H_SWAP: *effective_ap = hp; *effective_tp = tp; break; default: throw logic_error("invalid stat swap state"); } } const char* CardSpecial::get_next_expr_token( const char* expr, ExpressionTokenType* out_type, int32_t* out_value) const { switch (*expr) { case '\0': *out_type = ExpressionTokenType::SPACE; return nullptr; case ' ': *out_type = ExpressionTokenType::SPACE; return expr + 1; case '+': *out_type = ExpressionTokenType::ADD; return expr + 1; case '-': *out_type = ExpressionTokenType::SUBTRACT; return expr + 1; case '*': *out_type = ExpressionTokenType::MULTIPLY; return expr + 1; case '/': if (expr[1] == '/') { *out_type = ExpressionTokenType::FLOOR_DIVIDE; return expr + 2; } else { *out_type = ExpressionTokenType::ROUND_DIVIDE; return expr + 1; } } if ((*expr >= 'a') && (*expr <= 'z')) { string token_buf; for (; ('a' <= *expr) && (*expr < 'z'); expr++) { token_buf.push_back(*expr); } *out_type = ExpressionTokenType::SPACE; *out_value = 0x27; static const vector tokens = { "f", "d", "ap", "tp", "hp", "mhp", "dm", "tdm", "tf", "ac", "php", "dc", "cs", "a", "kap", "ktp", "dn", "hf", "df", "ff", "ef", "bi", "ab", "mc", "dk", "sa", "gn", "wd", "tt", "lv", "adm", "ddm", "sat", "edm", "ldm", "rdm", "fdm", "ndm", "ehp"}; for (size_t z = 0; z < tokens.size(); z++) { if (token_buf == tokens[z]) { *out_type = ExpressionTokenType::REFERENCE; *out_value = z; return expr; } } return expr; } if ((*expr >= '0') && (*expr <= '9')) { *out_type = ExpressionTokenType::NUMBER; *out_value = strtol(expr, const_cast(&expr), 10); return expr; } throw runtime_error("invalid card effect expression"); } vector> CardSpecial::get_targeted_cards_for_condition( uint16_t card_ref, uint8_t def_effect_index, uint16_t setter_card_ref, const ActionState& as, int16_t p_target_type, bool apply_usability_filters) const { vector> ret; uint8_t client_id = client_id_for_card_ref(card_ref); auto card1 = this->server()->card_for_set_card_ref(card_ref); if (!card1) { card1 = this->server()->card_for_set_card_ref(setter_card_ref); } auto card2 = this->server()->card_for_set_card_ref((as.attacker_card_ref == 0xFFFF) ? as.original_attacker_card_ref : as.attacker_card_ref); Location card1_loc; if (!card1) { card1_loc.x = 0; card1_loc.y = 0; card1_loc.direction = Direction::RIGHT; } else { this->get_card1_loc_with_card2_opposite_direction(&card1_loc, card1, card2); } AttackMedium attack_medium = card2 ? card2->action_chain.chain.attack_medium : AttackMedium::UNKNOWN; auto add_card_refs = [&](const vector& result_card_refs) -> void { for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); if (result_card) { ret.emplace_back(result_card); } } }; switch (p_target_type) { case 1: case 5: { auto result_card = this->server()->card_for_set_card_ref(setter_card_ref); if (result_card) { ret.emplace_back(result_card); } break; } case 2: 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 = this->server()->card_for_set_card_ref(as.target_card_refs[z]); if (result_card) { ret.emplace_back(result_card); } } } else if (card2) { ret.emplace_back(card2); } break; case 3: if (card1) { auto ce = this->server()->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (ce && ps) { uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2); parray range; compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); add_card_refs(ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM)); } } break; case 4: size_t z; for (z = 0; (z < 9) && (as.action_card_refs[z] != 0xFFFF) && (as.action_card_refs[z] != card_ref); z++) { } for (; (z < 9) && (as.action_card_refs[z] != 0xFFFF); z++) { auto result_card = this->server()->card_for_set_card_ref(as.action_card_refs[z]); if (result_card) { ret.emplace_back(result_card); } } break; case 6: ret = this->get_attacker_card_and_sc_if_item(as); break; case 7: { auto card = this->get_attacker_card(as); if (card) { ret.emplace_back(card); } break; } case 8: { auto card = this->sc_card_for_client_id(client_id); if (card) { ret.emplace_back(card); } break; } case 9: if (card1) { auto ce = this->server()->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (ce && ps) { uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2); parray range; compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); add_card_refs(ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id())); } } break; case 10: ret = this->find_all_cards_on_same_or_other_team(client_id, true); ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); break; case 11: ret = this->find_all_set_cards_on_client_team(client_id); ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); break; case 12: ret = this->find_all_cards_by_aerial_attribute(false); ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); break; case 13: ret = this->find_cards_by_condition_inc_exc(ConditionType::FREEZE); ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); break; case 14: ret = this->find_cards_in_hp_range(-1000, 3); ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); break; case 15: ret = this->get_all_set_cards(); ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); break; case 16: ret = this->find_cards_in_hp_range(8, 1000); ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); break; case 17: { auto result_card = this->server()->card_for_set_card_ref(card_ref); if (result_card) { ret.emplace_back(result_card); } break; } case 18: { auto card = this->sc_card_for_client_id(client_id); if (card) { ret.emplace_back(card); } break; } case 19: ret = this->find_all_sc_cards_of_class(CardClass::HU_SC); break; case 20: ret = this->find_all_sc_cards_of_class(CardClass::RA_SC); break; case 21: ret = this->find_all_sc_cards_of_class(CardClass::FO_SC); break; case 22: if (card1) { auto def = this->server()->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (def && ps) { // TODO: Again, Sega hardcodes the Gifoie card's ID here... we // should fix this eventually. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); 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 = this->server()->card_for_set_card_ref(result_card_ref); if (result_card && (result_card->get_definition()->def.type != CardType::ITEM) && (card1 != result_card)) { ret.emplace_back(result_card); } } if (card1) { ret.emplace_back(card1); } } } break; case 23: if (card1) { auto def = this->server()->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (def && ps) { // TODO: Again with the Gifoie hardcoding... uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); if (result_card && (result_card->get_definition()->def.type != CardType::ITEM)) { ret.emplace_back(result_card); } } } } break; case 24: ret = this->find_cards_by_condition_inc_exc(ConditionType::PARALYZE); break; case 25: ret = this->find_all_cards_by_aerial_attribute(true); break; case 26: ret = this->find_cards_damaged_by_at_least(1); break; case 27: ret = this->get_all_set_cards_by_team_and_class(CardClass::NATIVE_CREATURE, 0xFF, false); break; case 28: ret = this->get_all_set_cards_by_team_and_class(CardClass::A_BEAST_CREATURE, 0xFF, false); break; case 29: ret = this->get_all_set_cards_by_team_and_class(CardClass::MACHINE_CREATURE, 0xFF, false); break; case 30: ret = this->get_all_set_cards_by_team_and_class(CardClass::DARK_CREATURE, 0xFF, false); break; case 31: ret = this->get_all_set_cards_by_team_and_class(CardClass::SWORD_ITEM, 0xFF, false); break; case 32: ret = this->get_all_set_cards_by_team_and_class(CardClass::GUN_ITEM, 0xFF, false); break; case 33: ret = this->get_all_set_cards_by_team_and_class(CardClass::CANE_ITEM, 0xFF, false); break; case 34: 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 = this->server()->card_for_set_card_ref(as.target_card_refs[z]); 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()) { ret.emplace_back(card2); } break; case 35: if (card1) { auto def = this->server()->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (def && ps) { // TODO: Again with the Gifoie hardcoding... uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); if (result_card) { auto ce = result_card->get_definition(); if (ce->def.type == CardType::HUNTERS_SC) { bool should_add = true; for (uint16_t other_result_card_ref : result_card_refs) { if ((other_result_card_ref != result_card_ref) && (client_id_for_card_ref(other_result_card_ref) == client_id_for_card_ref(result_card_ref))) { should_add = false; break; } } if (should_add) { ret.emplace_back(result_card); } } else { ret.emplace_back(result_card); } } } } } break; case 36: 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 = this->server()->card_for_set_card_ref(as.target_card_refs[z]); 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()) { ret.emplace_back(card2); } break; case 37: ret = this->find_all_cards_on_same_or_other_team(client_id, false); ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); break; case 38: if (card1) { auto def = this->server()->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (def && ps) { // TODO: Yet another Gifoie hardcode location :( uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); 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 = this->server()->card_for_set_card_ref(result_card_ref); if (result_card && (result_card->get_definition()->def.type != CardType::ITEM) && (result_card->get_card_ref() != card_ref)) { ret.emplace_back(result_card); } } } } break; case 39: ret = this->find_all_set_cards_with_cost_in_range(4, 99); ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); break; case 40: ret = this->find_all_set_cards_with_cost_in_range(0, 3); ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); break; case 41: { auto ps = card1->player_state(); if (card1 && ps) { // TODO: Sigh. Gifoie again. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); if (result_card && (result_card != card1) && (result_card->get_card_ref() != card_ref) && (result_card->get_definition()->def.is_fc())) { ret.emplace_back(result_card); } } 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)) { bool already_in_ret = false; for (auto c : ret) { if (c == result_card) { already_in_ret = true; break; } } if (!already_in_ret) { ret.emplace_back(result_card); } } } } break; } case 42: { auto check_card = [&](shared_ptr result_card) -> void { if (result_card) { ret.emplace_back(result_card); auto ce = result_card->get_definition(); auto ps = result_card->player_state(); if ((ce->def.type == CardType::ITEM) && ps) { result_card = ps->get_sc_card(); if (result_card) { ret.emplace_back(result_card); } } } }; if (as.original_attacker_card_ref == 0xFFFF) { for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { check_card(this->server()->card_for_set_card_ref(as.target_card_refs[z])); } } else if (card2) { check_card(card2); } break; } case 43: for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { auto result_card = this->server()->card_for_set_card_ref(as.target_card_refs[z]); if (!result_card) { continue; } auto ce = result_card->get_definition(); auto ps = result_card->player_state(); if (ce && !ce->def.is_sc() && result_card->check_card_flag(2) && ps) { auto result_sc_card = ps->get_sc_card(); if (result_sc_card) { ret.emplace_back(result_sc_card); } } } break; case 44: { auto ps = this->server()->get_player_state(client_id); if (ps) { for (size_t z = 0; z < 8; z++) { auto result_card = ps->get_set_card(z); if (result_card) { ret.emplace_back(result_card); } } ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); } break; } case 45: this->sum_last_attack_damage(&ret, 0, 0); ret = this->filter_cards_by_range(ret, card1, card1_loc, card2); break; case 46: if (card1) { auto def = this->server()->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (def && ps) { // TODO: Yet another hardcoded card ID... but this time it's Cross // Slay instead of Gifoie uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x009C, card2); parray range; compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); if (result_card && (result_card->get_definition()->def.type != CardType::ITEM)) { ret.emplace_back(result_card); } } } } break; case 47: { uint8_t client_id = client_id_for_card_ref(as.original_attacker_card_ref); if (client_id != 0xFF) { auto card = this->sc_card_for_client_id(client_id); if (card) { ret.emplace_back(card); } } break; } case 48: if (card1) { auto ce = this->server()->definition_for_card_ref(card_ref); auto ps = card1->player_state(); if (ce && ps) { // TODO: Sigh. Gifoie. Sigh. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); if (result_card) { auto def = result_card->get_definition(); if (ce->def.type == CardType::HUNTERS_SC) { bool should_add = true; for (uint16_t other_result_card_ref : result_card_refs) { if (other_result_card_ref != result_card_ref) { if (client_id_for_card_ref(other_result_card_ref) == client_id_for_card_ref(result_card_ref)) { should_add = false; break; } } } if (should_add) { ret.emplace_back(result_card); } } else { ret.emplace_back(result_card); } } } } auto result_card = this->server()->card_for_set_card_ref(setter_card_ref); if (result_card) { ret.emplace_back(result_card); } } break; case 49: if (card1) { auto ps = card1->player_state(); if (ps) { // TODO: One more Gifoie here. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); 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 = this->server()->card_for_set_card_ref(result_card_ref); if (result_card && (result_card != card1) && (result_card->get_card_ref() != card_ref) && result_card->get_definition()->def.is_fc()) { ret.emplace_back(result_card); } } 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)) { bool should_add = true; for (auto c : ret) { if (c == result_card) { should_add = false; break; } } if (should_add) { ret.emplace_back(result_card); } } } } } } if (apply_usability_filters) { vector> filtered_ret; for (auto c : ret) { if (this->server()->ruler_server->check_usability_or_apply_condition_for_card_refs( card_ref, setter_card_ref, c->get_card_ref(), def_effect_index, attack_medium)) { filtered_ret.emplace_back(c); } } return filtered_ret; } else { return ret; } } vector> CardSpecial::get_targeted_cards_for_condition( uint16_t card_ref, uint8_t def_effect_index, uint16_t setter_card_ref, const ActionState& as, int16_t p_target_type, bool apply_usability_filters) { return this->server()->const_cast_set_cards_v(as_const(*this).get_targeted_cards_for_condition( card_ref, def_effect_index, setter_card_ref, as, p_target_type, apply_usability_filters)); } bool CardSpecial::is_card_targeted_by_condition( const Condition& cond, const ActionState& as, shared_ptr card) const { auto ce = this->server()->definition_for_card_ref(cond.card_ref); auto sc_card = this->server()->card_for_set_card_ref(cond.card_ref); if (cond.type != ConditionType::NONE) { if ((!sc_card || ((sc_card != card) && (sc_card->card_flags & 2))) && ce && ((ce->def.type == CardType::ITEM) || ce->def.is_sc()) && (cond.remaining_turns != 100) && (client_id_for_card_ref(card->get_card_ref()) == client_id_for_card_ref(cond.card_ref))) { return false; } if (cond.remaining_turns == 102) { if (sc_card && ((sc_card == card) || !(sc_card->card_flags & 2))) { auto target_cards = this->get_targeted_cards_for_condition( cond.card_ref, cond.card_definition_effect_index, cond.condition_giver_card_ref, as, atoi(&ce->def.effects[cond.card_definition_effect_index].arg3[1]), 0); for (auto c : target_cards) { if (c == card) { return true; } } } return false; } else { return true; } } return true; } void CardSpecial::on_card_set(shared_ptr ps, uint16_t card_ref) { auto sc_card = ps->get_sc_card(); uint16_t sc_card_ref = sc_card ? sc_card->get_card_ref() : 0xFFFF; ActionState as; this->unknown_8024C2B0(1, card_ref, as, sc_card_ref); } const CardDefinition::Effect* CardSpecial::original_definition_for_condition( const Condition& cond) const { auto ce = this->server()->definition_for_card_ref(cond.card_ref); if (!ce) { return nullptr; } const auto* eff = &ce->def.effects[cond.card_definition_effect_index]; return (eff->type == ConditionType::NONE) ? nullptr : eff; } bool CardSpecial::card_ref_has_ability_trap(const Condition& cond) const { auto card = this->server()->card_for_set_card_ref(cond.card_ref); if (!card) { return false; } else { return this->card_has_condition_with_ref( card, ConditionType::ABILITY_TRAP, 0xFFFF, 0xFFFF); } } void CardSpecial::send_6xB4x06_for_exp_change( shared_ptr card, uint16_t attacker_card_ref, uint8_t dice_roll_value, bool unknown_p5) const { G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x02; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 10); cmd.effect.target_card_ref = card->get_card_ref(); cmd.effect.value = 0; cmd.effect.dice_roll_value = dice_roll_value; cmd.effect.ap = clamp(card->ap, 0, 99); cmd.effect.current_hp = clamp(card->get_current_hp(), 0, 99); if (unknown_p5 == 0) { cmd.effect.current_hp |= 0x80; } // NOTE: The original code appears to have a copy/paste error here: if // card->tp > 99, then it sets cmd.effect.ap = 99 instead of cmd.effect.tp. // We implement the presumably intended behavior here instead. cmd.effect.tp = clamp(card->tp, 0, 99); this->server()->send(cmd); } void CardSpecial::send_6xB4x06_for_card_destroyed( shared_ptr destroyed_card, uint16_t attacker_card_ref) const { G_ApplyConditionEffect_GC_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.target_card_ref = destroyed_card->get_card_ref(); cmd.effect.value = 0; cmd.effect.operation = 0x7E; this->server()->send(cmd); } uint16_t CardSpecial::send_6xB4x06_if_card_ref_invalid( uint16_t card_ref, int16_t value) const { if (!this->server()->card_ref_is_empty_or_has_valid_card_id(card_ref)) { if (value != 0) { G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = 0xFFFF; cmd.effect.target_card_ref = 0xFFFF; cmd.effect.value = value; cmd.effect.operation = 0x7E; this->server()->send(cmd); } card_ref = 0xFFFF; } return card_ref; } void CardSpecial::send_6xB4x06_for_stat_delta( shared_ptr card, uint16_t attacker_card_ref, uint32_t flags, int16_t hp_delta, bool unknown_p6, bool unknown_p7) const { if (((hp_delta > 50) || (hp_delta < -50)) && (flags == 0x20)) { if (hp_delta < 0) { hp_delta = -card->get_current_hp(); } else { hp_delta = card->get_max_hp() - card->get_current_hp(); } } if (unknown_p6) { hp_delta = min(hp_delta + card->get_current_hp(), card->get_max_hp()) - card->get_current_hp(); if (hp_delta == 0) { return; } } G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; cmd.effect.flags = flags | 2; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 10); cmd.effect.target_card_ref = card->get_card_ref(); cmd.effect.value = -hp_delta; cmd.effect.ap = clamp(card->ap, 0, 99); cmd.effect.current_hp = clamp(card->get_current_hp(), 0, 99); cmd.effect.tp = clamp(card->tp, 0, 99); if (!unknown_p7) { cmd.effect.current_hp |= 0x80; } this->server()->send(cmd); } bool CardSpecial::should_cancel_condition_due_to_anti_abnormality( const CardDefinition::Effect& eff, shared_ptr card, uint16_t target_card_ref, uint16_t sc_card_ref) const { if (!card) { return false; } if ((card->card_flags & 3) || (card->action_metadata.check_flag(0x10) && (card->get_card_ref() != target_card_ref) && (card->get_card_ref() != sc_card_ref))) { return true; } auto ce = card->get_definition(); if (ce->def.is_sc() && (eff.type == ConditionType::FREEZE)) { return true; } switch (eff.type) { case ConditionType::IMMOBILE: case ConditionType::HOLD: case ConditionType::GUOM: case ConditionType::PARALYZE: case ConditionType::ACID: case ConditionType::CURSE: case ConditionType::FREEZE: case ConditionType::DROP: { const auto* cond = this->find_condition_with_parameters(card, ConditionType::ANTI_ABNORMALITY_2, 0xFFFF, 0xFF); return (cond != nullptr) || this->server()->ruler_server->card_ref_is_boss_sc(card->get_card_ref()); } default: return false; } } bool CardSpecial::should_return_card_ref_to_hand_on_destruction( uint16_t card_ref) const { if (card_ref == 0xFFFF) { return false; } uint8_t client_id = client_id_for_card_ref(card_ref); if (client_id == 0xFF) { return false; } auto ce = this->server()->definition_for_card_ref(card_ref); if (!ce) { return false; } auto ps = (client_id == 0xFF) ? nullptr : this->server()->get_player_state(client_id); if (!ps) { return false; } auto check_card = [&](shared_ptr card) -> bool { if (!card) { return false; } for (size_t cond_index = 0; cond_index < 9; cond_index++) { if (this->card_ref_has_ability_trap(card->action_chain.conditions[cond_index])) { 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)) { return true; } else if ((cond_type == ConditionType::REBORN) && !(card->card_flags & 3) && (ce->def.card_id == static_cast(card->action_chain.conditions[cond_index].value))) { return true; } } return false; }; for (size_t set_index = 0; set_index < 8; set_index++) { if (check_card(ps->get_set_card(set_index))) { return true; } } return check_card(ps->get_sc_card()); } size_t CardSpecial::sum_last_attack_damage( vector>* out_cards, int32_t* out_damage_sum, size_t* out_damage_count) const { size_t damage_count = 0; auto check_card = [&](shared_ptr c) -> void { if (c && (c->last_attack_final_damage > 0)) { if (out_damage_sum) { *out_damage_sum += c->last_attack_final_damage; } if (out_cards) { out_cards->emplace_back(c); } damage_count++; } }; for (size_t client_id = 0; client_id < 4; client_id++) { auto ps = this->server()->get_player_state(client_id); if (!ps) { continue; } check_card(ps->get_sc_card()); for (size_t set_index = 0; set_index < 8; set_index++) { check_card(ps->get_set_card(set_index)); } } if (out_damage_count) { *out_damage_count += damage_count; } return damage_count; } void CardSpecial::update_condition_orders(shared_ptr card) { vector cond_indexes; for (size_t z = 0; z < 9; z++) { if (card->action_chain.conditions[z].type != ConditionType::NONE) { cond_indexes.emplace_back(z); } } bool modified = true; while (modified) { modified = false; for (size_t index_offset = 0; index_offset < cond_indexes.size() - 1; index_offset++) { size_t this_index = cond_indexes[index_offset]; size_t next_index = cond_indexes[index_offset + 1]; uint8_t this_cond_order = card->action_chain.conditions[this_index].order; uint8_t next_cond_order = card->action_chain.conditions[next_index].order; if (next_cond_order < this_cond_order) { card->action_chain.conditions[this_index].order = next_cond_order; card->action_chain.conditions[next_index].order = this_cond_order; modified = true; } } } size_t cond_order = 0; for (size_t index : cond_indexes) { card->action_chain.conditions[index].order = cond_order++; } } int16_t CardSpecial::max_all_attack_bonuses(size_t* out_count) const { int16_t max_attack_bonus = 0; size_t num_attack_bonuses = 0; auto check_card = [&](shared_ptr c) { if (!c) { return; } if (c->action_metadata.attack_bonus > max_attack_bonus) { max_attack_bonus = c->action_metadata.attack_bonus; } if (c->action_metadata.attack_bonus > 0) { num_attack_bonuses++; } }; for (size_t client_id = 0; client_id < 4; client_id++) { auto ps = this->server()->get_player_state(client_id); if (ps) { check_card(ps->get_sc_card()); for (size_t set_index = 0; set_index < 8; set_index++) { check_card(ps->get_set_card(set_index)); } } } if (out_count) { *out_count = num_attack_bonuses; } return max_attack_bonus; } void CardSpecial::unknown_80244AA8(shared_ptr card) { ActionState as = this->create_attack_state_from_card_action_chain(card); for (size_t client_id = 0; client_id < 4; client_id++) { auto ps = this->server()->player_states[client_id]; if (ps) { auto other_card = ps->get_sc_card(); if (other_card) { this->clear_invalid_conditions_on_card(other_card, as); } for (size_t set_index = 0; set_index < 8; set_index++) { auto other_card = ps->get_set_card(set_index); if (other_card) { this->clear_invalid_conditions_on_card(other_card, as); } } } } this->apply_defense_conditions(as, 0x27, card, 4); this->unknown_8024C2B0(0x27, card->get_card_ref(), as, 0xFFFF); this->apply_defense_conditions(as, 0x13, card, 4); this->unknown_8024C2B0(0x13, card->get_card_ref(), as, 0xFFFF); } void CardSpecial::check_for_defense_interference( shared_ptr attacker_card, shared_ptr target_card, int16_t* inout_unknown_p4) { // Note: This check is not part of the original implementation. if (this->server()->base()->data_index->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) { return; } if (!inout_unknown_p4) { return; } if (target_card->get_current_hp() > *inout_unknown_p4) { return; } uint16_t ally_sc_card_ref = this->server()->ruler_server->get_ally_sc_card_ref( target_card->get_card_ref()); if (ally_sc_card_ref == 0xFFFF) { return; } auto ally_sc = this->server()->card_for_set_card_ref(ally_sc_card_ref); if (!ally_sc || (ally_sc->card_flags & 2)) { return; } uint8_t target_ally_client_id = client_id_for_card_ref(ally_sc_card_ref); if (target_ally_client_id == 0xFF) { return; } uint8_t target_client_id = client_id_for_card_ref(target_card->get_card_ref()); if (target_client_id == 0xFF) { return; } auto ally_hes = this->server()->ruler_server->get_hand_and_equip_state_for_client_id(target_ally_client_id); if (!ally_hes || !ally_hes->is_cpu_player) { return; } uint16_t target_card_id = this->server()->card_id_for_card_ref(target_card->get_card_ref()); if (target_card_id == 0xFFFF) { return; } uint16_t ally_sc_card_id = this->server()->card_id_for_card_ref(ally_sc_card_ref); if (ally_sc_card_id == 0xFFFF) { return; } auto target_ps = target_card->player_state(); if (!target_ps) { return; } if (target_ps->unknown_a17 >= 1) { return; } auto entry = get_interference_probability_entry( target_card_id, ally_sc_card_id, false); if (!entry || (this->server()->get_random(99) >= entry->defense_probability)) { return; } target_ps->unknown_a17++; G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card->get_card_ref(), 0x12); cmd.effect.target_card_ref = target_card->get_card_ref(); cmd.effect.value = 0; cmd.effect.operation = 0x7D; this->server()->send(cmd); if (inout_unknown_p4) { *inout_unknown_p4 = 0; target_card->action_metadata.set_flags(0x10); } } void CardSpecial::unknown_8024C2B0( uint32_t when, uint16_t set_card_ref, const ActionState& as, uint16_t sc_card_ref, bool apply_defense_condition_to_all_cards, uint16_t apply_defense_condition_to_card_ref) { set_card_ref = this->send_6xB4x06_if_card_ref_invalid(set_card_ref, 1); auto ce = this->server()->definition_for_card_ref(set_card_ref); if (!ce) { return; } uint16_t as_attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 2); if (as_attacker_card_ref == 0xFFFF) { as_attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(as.original_attacker_card_ref, 3); } G_ApplyConditionEffect_GC_Ep3_6xB4x06 dice_cmd; dice_cmd.effect.target_card_ref = set_card_ref; bool as_action_card_refs_contains_set_card_ref = false; bool as_action_card_refs_contains_duplicate_of_set_card = false; for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) { if (as.action_card_refs[z] == dice_cmd.effect.target_card_ref) { as_action_card_refs_contains_set_card_ref = true; break; } auto action_ce = this->server()->definition_for_card_ref(as.action_card_refs[z]); if (action_ce && (action_ce->def.card_id == action_ce->def.card_id)) { as_action_card_refs_contains_duplicate_of_set_card = true; } } bool unknown_v1 = as_action_card_refs_contains_duplicate_of_set_card && as_action_card_refs_contains_set_card_ref; uint8_t random_percent = this->server() ? this->server()->get_random(99) : 0; bool any_expr_used_dice_roll = false; DiceRoll dice_roll; uint8_t client_id = client_id_for_card_ref(dice_cmd.effect.target_card_ref); auto set_card_ps = (client_id == 0xFF) ? nullptr : this->server()->player_states[client_id]; dice_roll.value = 1; if (set_card_ps) { dice_roll.value = set_card_ps->roll_dice_with_effects(1); } dice_roll.client_id = client_id; dice_roll.unknown_a2 = 3; dice_roll.value_used_in_expr = 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++) { const auto& card_effect = ce->def.effects[def_effect_index]; if (card_effect.when != when) { continue; } { string as_s = as.str(); string eff_s = card_effect.str(); this->server()->base()->log.debug("(when=%" PRIu32 ") set=@%04hX sc=@%04hX as=%s att=@%04hX eff=%s", when, set_card_ref, sc_card_ref, as_s.c_str(), as_attacker_card_ref, eff_s.c_str()); } int16_t arg3_value = atoi(&card_effect.arg3[1]); auto targeted_cards = this->get_targeted_cards_for_condition( set_card_ref, def_effect_index, sc_card_ref, as, arg3_value, 1); bool all_targets_matched = false; if (!targeted_cards.empty() && ((card_effect.type == ConditionType::UNKNOWN_64) || (card_effect.type == ConditionType::MISC_DEFENSE_BONUSES) || (card_effect.type == ConditionType::MOSTLY_HALFGUARDS))) { size_t count = 0; for (size_t z = 0; z < targeted_cards.size(); z++) { dice_roll.value_used_in_expr = false; string arg2_text = card_effect.arg2; if (this->evaluate_effect_arg2_condition( as, targeted_cards[z], arg2_text.c_str(), dice_roll, set_card_ref, sc_card_ref, random_percent, when)) { count++; } if (dice_roll.value_used_in_expr) { any_expr_used_dice_roll = true; } } if (count == targeted_cards.size()) { auto set_card = this->server()->card_for_set_card_ref(set_card_ref); if (!set_card) { set_card = this->server()->card_for_set_card_ref(sc_card_ref); } targeted_cards.clear(); if (set_card != nullptr) { targeted_cards.emplace_back(set_card); } all_targets_matched = true; } else { targeted_cards.clear(); } } for (size_t z = 0; z < targeted_cards.size(); z++) { dice_roll.value_used_in_expr = false; string arg2_str = card_effect.arg2; if (all_targets_matched || this->evaluate_effect_arg2_condition( as, targeted_cards[z], arg2_str.c_str(), dice_roll, set_card_ref, sc_card_ref, random_percent, when)) { auto env_stats = this->compute_attack_env_stats( as, targeted_cards[z], dice_roll, set_card_ref, sc_card_ref); string expr_str = card_effect.expr; int16_t value = this->evaluate_effect_expr(env_stats, expr_str.c_str(), dice_roll); uint32_t unknown_v1 = 0; auto target_card = this->compute_replaced_target_based_on_conditions( targeted_cards[z]->get_card_ref(), 0, 1, as_attacker_card_ref, set_card_ref, 0, nullptr, def_effect_index, &unknown_v1, sc_card_ref); if (!target_card) { target_card = targeted_cards[z]; } ssize_t applied_cond_index = -1; if ((unknown_v1 == 0) && !this->should_cancel_condition_due_to_anti_abnormality(card_effect, target_card, dice_cmd.effect.target_card_ref, sc_card_ref)) { applied_cond_index = target_card->apply_abnormal_condition( card_effect, def_effect_index, dice_cmd.effect.target_card_ref, sc_card_ref, value, dice_roll.value, random_percent); // This debug_print call is in the original code. // this->debug_print(when, 4, &env_stats, "!set_abnormal..", target_card, card_effect.type); } if (applied_cond_index >= 0) { G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(as_attacker_card_ref, 0x14); cmd.effect.target_card_ref = target_card->get_card_ref(); cmd.effect.value = (target_card->action_chain).conditions[applied_cond_index].remaining_turns; cmd.effect.operation = static_cast(card_effect.type); this->server()->send(cmd); } if (dice_roll.value_used_in_expr) { target_card->action_chain.conditions[applied_cond_index].flags |= 1; } if ((applied_cond_index >= 0) && (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); } target_card->send_6xB4x4E_4C_4D_if_needed(0); } if (dice_roll.value_used_in_expr) { any_expr_used_dice_roll = true; } } } if (any_expr_used_dice_roll) { dice_cmd.effect.flags = 0x08; dice_cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid( as_attacker_card_ref, 0x15); dice_cmd.effect.dice_roll_value = dice_roll.value; this->server()->send(dice_cmd); } } vector> CardSpecial::get_all_set_cards() const { vector> ret; for (size_t client_id = 0; client_id < 4; client_id++) { auto ps = this->server()->get_player_state(client_id); if (ps) { for (size_t set_index = 0; set_index < 8; set_index++) { auto set_card = ps->get_set_card(set_index); if (set_card) { ret.emplace_back(set_card); } } } } return ret; } vector> CardSpecial::find_cards_by_condition_inc_exc( ConditionType include_cond, ConditionType exclude_cond, AssistEffect include_eff, AssistEffect exclude_eff) const { vector> ret; auto check_card = [&](uint8_t client_id, shared_ptr c) -> void { if (c) { bool should_include = false; bool should_exclude = false; for (size_t z = 0; z < 9; z++) { auto type = c->action_chain.conditions[z].type; if ((type == include_cond) || (include_cond == ConditionType::ANY_FF)) { should_include = true; } if ((type == exclude_cond) && (exclude_cond != ConditionType::NONE)) { should_exclude = true; } } size_t num_assists = this->server()->assist_server->compute_num_assist_effects_for_client(client_id); for (size_t z = 0; z < num_assists; z++) { auto eff = this->server()->assist_server->get_active_assist_by_index(z); if ((exclude_eff != AssistEffect::NONE) && ((include_eff == AssistEffect::ANY) || (include_eff == eff))) { should_include = true; } if ((exclude_eff != AssistEffect::NONE) && (exclude_eff == eff)) { should_exclude = true; } } if (should_include && !should_exclude) { ret.emplace_back(c); } } }; for (size_t client_id = 0; client_id < 4; client_id++) { auto ps = this->server()->get_player_state(client_id); if (!ps) { continue; } for (size_t set_index = 0; set_index < 8; set_index++) { check_card(client_id, ps->get_set_card(set_index)); } check_card(client_id, ps->get_sc_card()); } return ret; } void CardSpecial::clear_invalid_conditions_on_card( shared_ptr card, const ActionState& as) { for (size_t cond_index = 0; cond_index < 9; cond_index++) { auto& cond = card->action_chain.conditions[cond_index]; if (cond.type != ConditionType::NONE) { if (!this->is_card_targeted_by_condition(cond, as, card)) { if (cond.type != ConditionType::NONE) { G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = 0xFFFF; cmd.effect.target_card_ref = card->get_card_ref(); cmd.effect.value = 0; cmd.effect.operation = -static_cast(cond.type); this->server()->send(cmd); } this->apply_stat_deltas_to_card_from_condition_and_clear_cond(cond, card); card->send_6xB4x4E_4C_4D_if_needed(); } } } } const InterferenceProbabilityEntry* get_interference_probability_entry( uint16_t row_card_id, uint16_t column_card_id, bool is_attack) { static const InterferenceProbabilityEntry entries[] = { {0x0004, 0xFF, 0xFF}, {0x0002, 0x04, 0x00}, {0x0002, 0x00, 0x0F}, {0x0003, 0x03, 0x00}, {0x0003, 0x00, 0x0A}, {0x0006, 0x01, 0x00}, {0x0006, 0x00, 0x05}, {0x0111, 0x01, 0x00}, {0x0111, 0x00, 0x05}, {0x0001, 0x03, 0x00}, {0x0001, 0x00, 0x0A}, {0x0002, 0xFF, 0xFF}, {0x0004, 0x04, 0x00}, {0x0004, 0x00, 0x0F}, {0x0003, 0x06, 0x00}, {0x0003, 0x00, 0x14}, {0x0006, 0x04, 0x00}, {0x0006, 0x00, 0x0F}, {0x0003, 0xFF, 0xFF}, {0x0004, 0x04, 0x00}, {0x0004, 0x00, 0x0F}, {0x0002, 0x04, 0x00}, {0x0002, 0x00, 0x0F}, {0x0006, 0xFF, 0xFF}, {0x0002, 0x06, 0x00}, {0x0002, 0x00, 0x14}, {0x0111, 0xFF, 0xFF}, {0x0004, 0x01, 0x00}, {0x0004, 0x00, 0x05}, {0x0001, 0x06, 0x00}, {0x0001, 0x00, 0x14}, {0x0001, 0xFF, 0xFF}, {0x0111, 0x04, 0x00}, {0x0111, 0x00, 0x0F}, {0x0112, 0xFF, 0xFF}, {0x0113, 0x06, 0x00}, {0x0113, 0x00, 0x14}, {0x0110, 0x06, 0x00}, {0x0110, 0x00, 0x14}, {0x0114, 0x01, 0x00}, {0x0114, 0x00, 0x05}, {0x011D, 0x02, 0x00}, {0x011D, 0x00, 0x07}, {0x0113, 0xFF, 0xFF}, {0x0003, 0x03, 0x00}, {0x0003, 0x00, 0x0A}, {0x0112, 0x03, 0x00}, {0x0112, 0x00, 0x0A}, {0x0110, 0xFF, 0xFF}, {0x0005, 0x03, 0x00}, {0x0005, 0x00, 0x0A}, {0x0112, 0x04, 0x00}, {0x0112, 0x00, 0x0F}, {0x0005, 0xFF, 0xFF}, {0x0110, 0x03, 0x00}, {0x0110, 0x00, 0x0A}, {0x0114, 0xFF, 0xFF}, {0x0005, 0x03, 0x00}, {0x0005, 0x00, 0x0A}, {0x0110, 0x01, 0x00}, {0x0110, 0x00, 0x05}, {0x0115, 0x06, 0x00}, {0x0115, 0x00, 0x14}, {0x0115, 0xFF, 0xFF}, {0x0004, 0x01, 0x00}, {0x0004, 0x00, 0x05}, {0x0003, 0x01, 0x00}, {0x0003, 0x00, 0x05}, {0x0006, 0x01, 0x00}, {0x0006, 0x00, 0x05}, {0x0112, 0x01, 0x00}, {0x0112, 0x00, 0x05}, {0x0110, 0x01, 0x00}, {0x0110, 0x00, 0x05}, {0x0114, 0x04, 0x00}, {0x0114, 0x00, 0x0F}, {0x0008, 0xFF, 0xFF}, {0x0007, 0x06, 0x00}, {0x0007, 0x00, 0x14}, {0x0116, 0x01, 0x00}, {0x0116, 0x00, 0x05}, {0x011E, 0x03, 0x00}, {0x011E, 0x00, 0x0A}, {0x0118, 0x06, 0x00}, {0x0118, 0x00, 0x14}, {0x0007, 0xFF, 0xFF}, {0x0008, 0x06, 0x00}, {0x0008, 0x00, 0x14}, {0x0118, 0x01, 0x00}, {0x0118, 0x00, 0x05}, {0x011B, 0x03, 0x00}, {0x011B, 0x00, 0x0A}, {0x0116, 0xFF, 0xFF}, {0x0008, 0x01, 0x00}, {0x0008, 0x00, 0x05}, {0x011C, 0x03, 0x00}, {0x011C, 0x00, 0x0A}, {0x011A, 0xFF, 0xFF}, {0x0119, 0x04, 0x00}, {0x0119, 0x00, 0x0F}, {0x011D, 0x04, 0x00}, {0x011D, 0x00, 0x0F}, {0x0119, 0xFF, 0xFF}, {0x011A, 0x04, 0x00}, {0x011A, 0x00, 0x0F}, {0x011D, 0x04, 0x00}, {0x011D, 0x00, 0x0F}, {0x011D, 0xFF, 0xFF}, {0x0119, 0x04, 0x00}, {0x0119, 0x00, 0x0F}, {0x011A, 0x04, 0x00}, {0x011A, 0x00, 0x0F}, {0x0112, 0x01, 0x00}, {0x0112, 0x00, 0x07}, {0x011E, 0xFF, 0xFF}, {0x0008, 0x03, 0x00}, {0x0008, 0x00, 0x0A}, {0x0118, 0x06, 0x00}, {0x0118, 0x00, 0x14}, {0x011C, 0xFF, 0xFF}, {0x0116, 0x04, 0x00}, {0x0116, 0x00, 0x0F}, {0x011E, 0x01, 0x00}, {0x011E, 0x00, 0x05}, {0x0118, 0xFF, 0xFF}, {0x011E, 0x06, 0x00}, {0x011E, 0x00, 0x14}, {0x011B, 0xFF, 0xFF}, {0x0007, 0x03, 0x00}, {0x0007, 0x00, 0x0A}, {0x0117, 0x03, 0x00}, {0x0117, 0x00, 0x0A}, {0x011F, 0x06, 0x00}, {0x011F, 0x00, 0x14}, {0x0117, 0xFF, 0xFF}, {0x011F, 0x03, 0x00}, {0x011F, 0x00, 0x0A}, {0x011B, 0x04, 0x00}, {0x011B, 0x00, 0x0F}, {0x011F, 0xFF, 0xFF}, {0x0007, 0x01, 0x00}, {0x0007, 0x00, 0x05}, {0x011B, 0x06, 0x00}, {0x011B, 0x00, 0x14}, {0x0117, 0x04, 0x00}, {0x0117, 0x00, 0x0F}, }; constexpr size_t num_entries = sizeof(entries) / sizeof(entries[0]); const InterferenceProbabilityEntry* ret_entry = nullptr; int16_t current_max = -1; size_t logical_index = 0; uint16_t current_row_card_id = 0xFFFF; for (size_t z = 0; z < num_entries; z++) { 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)) { uint8_t v = is_attack ? entry.attack_probability : entry.defense_probability; if (current_max <= v) { ret_entry = &entry; current_max = v; } } logical_index++; } else { current_row_card_id = current_column_card_id; } } return ret_entry; } void CardSpecial::on_card_destroyed( shared_ptr attacker_card, shared_ptr 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); uint16_t destroyed_card_ref = destroyed_card->get_card_ref(); this->unknown_8024C2B0(5, destroyed_card_ref, defense_as, 0xFFFF); for (size_t z = 0; (z < 8) && (defense_as.action_card_refs[z] != 0xFFFF); z++) { this->unknown_8024C2B0( 5, defense_as.action_card_refs[z], defense_as, destroyed_card->get_card_ref()); } if (attacker_card) { for (size_t cond_index = 0; cond_index < 9; cond_index++) { auto& cond = attacker_card->action_chain.conditions[cond_index]; if (cond.type == ConditionType::CURSE) { this->execute_effect(cond, attacker_card, 0, 0, ConditionType::CURSE, 4, 0xFFFF); } } } this->send_6xB4x06_for_card_destroyed(destroyed_card, attack_as.attacker_card_ref); } vector> CardSpecial::find_cards_in_hp_range( int16_t min, int16_t max) const { vector> ret; for (size_t client_id = 0; client_id < 4; client_id++) { auto ps = this->server()->get_player_state(client_id); if (ps) { auto card = ps->get_sc_card(); if (card) { int16_t hp = card->get_current_hp(); if ((min <= hp) && (hp <= max)) { ret.emplace_back(card); } } for (size_t set_index = 0; set_index < 8; set_index++) { auto card = ps->get_set_card(set_index); if (card) { int16_t hp = card->get_current_hp(); if ((min <= hp) && (hp <= max)) { ret.emplace_back(card); } } } } } return ret; } vector> CardSpecial::find_all_cards_by_aerial_attribute(bool is_aerial) const { vector> ret; for (size_t client_id = 0; client_id < 4; client_id++) { auto ps = this->server()->get_player_state(client_id); if (ps) { for (size_t set_index = 0; set_index < 8; set_index++) { auto card = ps->get_set_card(set_index); if (card && (this->server()->ruler_server->card_ref_is_aerial(card->get_card_ref()) == is_aerial)) { ret.emplace_back(card); } } auto card = ps->get_sc_card(); if (card && (this->server()->ruler_server->card_ref_is_aerial(card->get_card_ref()) == is_aerial)) { ret.emplace_back(card); } } } return ret; } vector> CardSpecial::find_cards_damaged_by_at_least(int16_t damage) const { vector> ret; for (size_t client_id = 0; client_id < 4; client_id++) { auto ps = this->server()->get_player_state(client_id); if (ps) { for (size_t set_index = 0; set_index < 8; set_index++) { auto card = ps->get_set_card(set_index); if (card && (damage + card->get_current_hp() <= card->get_max_hp())) { ret.emplace_back(card); } } auto card = ps->get_sc_card(); if (card) { if (damage + card->get_current_hp() <= card->get_max_hp()) { ret.emplace_back(card); } } } } return ret; } vector> CardSpecial::find_all_set_cards_on_client_team(uint8_t client_id) const { vector> ret; auto ps = this->server()->get_player_state(client_id); if (!ps) { return ret; } for (size_t other_client_id = 0; other_client_id < 4; other_client_id++) { auto other_ps = this->server()->get_player_state(other_client_id); if (other_ps && (other_ps->get_team_id() == ps->get_team_id())) { for (size_t set_index = 0; set_index < 8; set_index++) { auto card = other_ps->get_set_card(set_index); if (card) { ret.emplace_back(card); } } } } return ret; } vector> CardSpecial::find_all_cards_on_same_or_other_team(uint8_t client_id, bool same_team) const { vector> ret; auto ps = this->server()->get_player_state(client_id); if (!ps) { return ret; } for (size_t other_client_id = 0; other_client_id < 4; other_client_id++) { auto other_ps = this->server()->get_player_state(other_client_id); if (other_ps) { bool should_collect = false; if (!same_team) { if ((other_ps->get_team_id() != 0xFF) && (ps->get_team_id() != other_ps->get_team_id())) { should_collect = true; } } else { if (ps->get_team_id() == other_ps->get_team_id()) { should_collect = true; } } if (should_collect) { auto card = other_ps->get_sc_card(); if (card) { ret.emplace_back(card); } for (size_t set_index = 0; set_index < 8; set_index++) { auto card = other_ps->get_set_card(set_index); if (card) { ret.emplace_back(card); } } } } } return ret; } shared_ptr CardSpecial::sc_card_for_client_id(uint8_t client_id) const { auto ps = this->server()->get_player_state(client_id); return ps ? ps->get_sc_card() : nullptr; } shared_ptr CardSpecial::get_attacker_card(const ActionState& as) const { uint32_t card_ref = as.attacker_card_ref; if (card_ref == 0xFFFF) { card_ref = as.original_attacker_card_ref; } auto card = this->server()->card_for_set_card_ref(card_ref); if (card) { auto ce = card->get_definition(); if ((ce->def.type == CardType::ITEM) || (ce->def.type == CardType::CREATURE)) { return card; } } return nullptr; } vector> CardSpecial::get_attacker_card_and_sc_if_item(const ActionState& as) const { vector> ret; uint16_t card_ref = as.attacker_card_ref; if (card_ref == 0xFFFF) { card_ref = as.original_attacker_card_ref; } auto card = this->server()->card_for_set_card_ref(card_ref); if (card) { auto ce = card->get_definition(); if (ce->def.type == CardType::ITEM) { auto ps = card->player_state(); if (ps) { ret.emplace_back(ps->get_sc_card()); } ret.emplace_back(card); } else { if ((ce->def.type == CardType::HUNTERS_SC) || (ce->def.type == CardType::ARKZ_SC) || (ce->def.type == CardType::CREATURE)) { ret.emplace_back(card); } } } return ret; } vector> CardSpecial::find_all_set_cards_with_cost_in_range(uint8_t min_cost, uint8_t max_cost) const { vector> ret; for (size_t client_id = 0; client_id < 4; client_id++) { auto ps = this->server()->get_player_state(client_id); if (ps) { for (size_t set_index = 0; set_index < 8; set_index++) { auto card = ps->get_set_card(set_index); if (card) { auto ce = card->get_definition(); if (ce && (min_cost <= ce->def.self_cost) && (ce->def.self_cost <= max_cost)) { ret.emplace_back(card); } } } } } return ret; } vector> CardSpecial::filter_cards_by_range( const vector>& cards, shared_ptr card1, const Location& card1_loc, shared_ptr card2) const { vector> ret; if (!card1 || cards.empty()) { return ret; } auto ps = card1->player_state(); if (!ps) { return ret; } // TODO: Remove hardcoded card ID here (Earthquake) uint16_t card_id = this->get_card_id_with_effective_range(card1, 0x00ED, card2); parray range; compute_effective_range(range, this->server()->base()->data_index, card_id, card1_loc, this->server()->base()->map_and_rules1); auto card_refs_in_range = ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM); for (auto card : cards) { if (!card || (card->get_card_ref() == 0xFFFF)) { continue; } for (uint16_t card_ref_in_range : card_refs_in_range) { if (card_ref_in_range == card->get_card_ref()) { ret.emplace_back(card); break; } } } return ret; } void CardSpecial::unknown_8024AAB8(const ActionState& as) { this->unknown_action_state_a1 = as; 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); if (card_ref == 0xFFFF) { break; } if (this->send_6xB4x06_if_card_ref_invalid(as.original_attacker_card_ref, 0x1F) == 0xFFFF) { this->unknown_8024C2B0( 1, as.action_card_refs[z], as, this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 0x21)); this->unknown_8024C2B0( 0xb, as.action_card_refs[z], as, this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 0x22)); } else { uint16_t card_ref = this->send_6xB4x06_if_card_ref_invalid(as.target_card_refs[0], 0x20); if (card_ref != 0xFFFF) { this->unknown_8024C2B0(1, as.action_card_refs[z], as, card_ref); this->unknown_8024C2B0(0x15, as.action_card_refs[z], as, card_ref); } } } if (as.original_attacker_card_ref == 0xffff) { uint16_t card_ref1 = this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 0x23); uint16_t card_ref2 = this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 0x25); this->unknown_8024C2B0(0x33, card_ref2, as, card_ref1); card_ref1 = this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 0x24); card_ref2 = this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 0x26); this->unknown_8024C2B0(0x34, card_ref2, as, card_ref1); for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { uint16_t card_ref = this->send_6xB4x06_if_card_ref_invalid( as.action_card_refs[z], 0x27); if (card_ref == 0xFFFF) { break; } this->unknown_8024C2B0(0x35, as.target_card_refs[z], as, as.attacker_card_ref); } } } void CardSpecial::unknown_80244BE4(shared_ptr card) { ActionState as = this->create_attack_state_from_card_action_chain(card); this->apply_defense_conditions(as, 9, card, 4); this->unknown_8024C2B0(9, card->get_card_ref(), as, 0xFFFF); this->apply_defense_conditions(as, 0x27, card, 4); this->unknown_8024C2B0(0x27, card->get_card_ref(), as, 0xFFFF); } void CardSpecial::unknown_80244CA8(shared_ptr card) { ActionState as; auto ps = card->player_state(); as.attacker_card_ref = card->get_card_ref(); as.action_card_refs = card->action_chain.chain.attack_action_card_refs; as.target_card_refs = card->action_chain.chain.target_card_refs; uint16_t sc_card_ref = 0xFFFF; if (ps) { auto sc_card = ps->get_sc_card(); if (sc_card) { sc_card_ref = sc_card->get_card_ref(); } } this->apply_defense_conditions(as, 0x46, card, 4); this->unknown_8024C2B0(0x46, card->get_card_ref(), as, sc_card_ref); if (ps->is_team_turn()) { this->apply_defense_conditions(as, 4, card, 4); this->unknown_8024C2B0(4, card->get_card_ref(), as, sc_card_ref); } } template void CardSpecial::unknown1_t( shared_ptr unknown_p2, const ActionState* existing_as) { ActionState as; if (!existing_as) { as = this->create_attack_state_from_card_action_chain(unknown_p2); } else { as = *existing_as; } this->apply_defense_conditions(as, When1, unknown_p2, 4); for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { auto card = this->server()->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); this->apply_defense_conditions(target_as, When1, card, 4); } } auto card = this->sc_card_for_card(unknown_p2); this->unknown_8024C2B0(When1, unknown_p2->get_card_ref(), as, card ? card->get_card_ref() : 0xFFFF); for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) { this->unknown_8024C2B0(When1, 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 card = this->server()->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); this->unknown_8024C2B0(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->unknown_8024C2B0(When1, target_as.action_card_refs[w], target_as, card->get_card_ref()); } } } } void CardSpecial::unknown_80249060(shared_ptr unknown_p2) { this->unknown1_t<0x0F, 0x0A>(unknown_p2); } void CardSpecial::unknown_80249254(shared_ptr unknown_p2) { if (unknown_p2->player_state()->is_team_turn()) { this->unknown1_t<0x0E, 0x0A>(unknown_p2); } } void CardSpecial::unknown_8024945C(shared_ptr unknown_p2, const ActionState& existing_as) { this->unknown1_t<0x0A, 0x0A>(unknown_p2, &existing_as); } void CardSpecial::unknown_8024966C(shared_ptr unknown_p2, const ActionState* existing_as) { ActionState as; if (!existing_as) { as = this->create_attack_state_from_card_action_chain(unknown_p2); } else { as = *existing_as; } auto card = this->sc_card_for_card(unknown_p2); uint16_t card_ref = card ? card->get_card_ref() : 0xFFFF; auto ce = unknown_p2->get_definition(); auto defender_card = (ce && (ce->def.type == CardType::ITEM) && card) ? card : unknown_p2; this->apply_defense_conditions(as, 0x3D, unknown_p2, 4); this->apply_defense_conditions(as, 0x3E, unknown_p2, 4); if (defender_card) { this->apply_defense_conditions(as, 0x22, defender_card, 4); } for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { auto 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->apply_defense_conditions(defense_as, 0x3D, card, 4); this->apply_defense_conditions(defense_as, 0x3F, card, 4); } } this->unknown_8024C2B0(0x3D, unknown_p2->get_card_ref(), as, card_ref); this->unknown_8024C2B0(0x3E, unknown_p2->get_card_ref(), as, card_ref); if (defender_card) { this->unknown_8024C2B0(0x22, defender_card->get_card_ref(), as, card_ref); } for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) { this->unknown_8024C2B0(0x3D, as.action_card_refs[z], as, unknown_p2->get_card_ref()); this->unknown_8024C2B0(0x3E, 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->unknown_8024C2B0(0x3D, card->get_card_ref(), defense_as, unknown_p2->get_card_ref()); this->unknown_8024C2B0(0x3F, card->get_card_ref(), defense_as, unknown_p2->get_card_ref()); } } } shared_ptr CardSpecial::sc_card_for_card(shared_ptr unknown_p2) { auto ps = unknown_p2->player_state(); return ps ? ps->get_sc_card() : nullptr; } void CardSpecial::unknown_8024A9D8(const ActionState& pa, uint16_t action_card_ref) { for (size_t z = 0; (z < 8) && (pa.action_card_refs[z] != 0xFFFF); z++) { if ((action_card_ref == 0xFFFF) || (action_card_ref == pa.action_card_refs[z])) { if (pa.original_attacker_card_ref == 0xFFFF) { this->unknown_8024C2B0(0x29, pa.action_card_refs[z], pa, pa.attacker_card_ref); this->unknown_8024C2B0(0x2A, pa.action_card_refs[z], pa, pa.attacker_card_ref); } else { this->unknown_8024C2B0(0x29, pa.action_card_refs[z], pa, pa.target_card_refs[0]); this->unknown_8024C2B0(0x2B, pa.action_card_refs[z], pa, pa.target_card_refs[0]); } } } } void CardSpecial::check_for_attack_interference(shared_ptr unknown_p2) { // Note: This check is not part of the original implementation. if (this->server()->base()->data_index->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) { return; } if (unknown_p2->action_chain.chain.damage <= 0) { return; } 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; } uint8_t ally_client_id = client_id_for_card_ref(ally_sc_card_ref); if (ally_client_id == 0xFF) { return; } auto ally_sc_card = this->server()->card_for_set_card_ref(ally_sc_card_ref); if (!ally_sc_card || (ally_sc_card->card_flags & 2)) { return; } uint8_t client_id = client_id_for_card_ref(unknown_p2->get_card_ref()); if (client_id == 0xFF) { return; } auto ally_hes = this->server()->ruler_server->get_hand_and_equip_state_for_client_id(ally_client_id); if (!ally_hes || !ally_hes->is_cpu_player) { return; } this->server()->ruler_server->get_hand_and_equip_state_for_client_id(client_id); auto ps = unknown_p2->player_state(); if (!ps || (ps->unknown_a16 >= 1)) { return; } uint16_t card_ref = unknown_p2->get_card_ref(); if ((unknown_p2->get_definition()->def.type == CardType::ITEM) && ps->get_sc_card()) { card_ref = ps->get_sc_card()->get_card_ref(); } uint16_t row_card_id = this->server()->card_id_for_card_ref(card_ref); if (row_card_id == 0xFFFF) { return; } uint16_t ally_sc_card_id = this->server()->card_id_for_card_ref(ally_sc_card_ref); if (ally_sc_card_id == 0xFFFF) { return; } const auto* entry = get_interference_probability_entry( row_card_id, ally_sc_card_id, true); if (!entry || (this->server()->get_random(99) >= entry->attack_probability)) { return; } ps->unknown_a16++; unknown_p2->action_chain.set_flags(0x100); G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid( unknown_p2->get_card_ref(), 0x11); cmd.effect.target_card_ref = unknown_p2->get_card_ref(); cmd.effect.value = 0; cmd.effect.operation = 0x7D; this->server()->send(cmd); } template void CardSpecial::unknown_t2(shared_ptr unknown_p2) { ActionState as = this->create_attack_state_from_card_action_chain(unknown_p2); auto sc_card = this->sc_card_for_card(unknown_p2); uint16_t sc_card_ref = 0xFFFF; if (sc_card) { sc_card_ref = sc_card->get_card_ref(); } auto defender_card = unknown_p2; if (unknown_p2->get_definition() && (unknown_p2->get_definition()->def.type == CardType::ITEM) && sc_card) { defender_card = sc_card; } this->apply_defense_conditions(as, When1, unknown_p2, 4); this->apply_defense_conditions(as, When2, unknown_p2, 4); if (defender_card) { this->apply_defense_conditions(as, When3, defender_card, 4); } for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) { auto set_card = this->server()->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->apply_defense_conditions(target_as, When1, set_card, 4); this->apply_defense_conditions(target_as, When4, set_card, 4); } } this->unknown_8024C2B0(When1, unknown_p2->get_card_ref(), as, sc_card_ref); this->unknown_8024C2B0(When2, unknown_p2->get_card_ref(), as, sc_card_ref); if (defender_card) { this->unknown_8024C2B0(When3, defender_card->get_card_ref(), as, sc_card_ref); } for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) { this->unknown_8024C2B0(When1, as.action_card_refs[z], as, unknown_p2->get_card_ref()); this->unknown_8024C2B0(When2, 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 = this->server()->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->unknown_8024C2B0(When1, set_card->get_card_ref(), target_as, unknown_p2->get_card_ref()); this->unknown_8024C2B0(When4, 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->unknown_8024C2B0(When1, target_as.action_card_refs[z], target_as, set_card->get_card_ref()); this->unknown_8024C2B0(When4, target_as.action_card_refs[z], target_as, set_card->get_card_ref()); } } } } void CardSpecial::unknown_8024997C(shared_ptr card) { return this->unknown_t2<0x03, 0x0D, 0x21, 0x17>(card); } void CardSpecial::unknown_8024A394(shared_ptr card) { return this->unknown_t2<0x02, 0x0C, 0x20, 0x16>(card); } bool CardSpecial::client_has_atk_dice_boost_condition(uint8_t client_id) { auto ps = this->server()->get_player_state(client_id); if (ps) { auto card = ps->get_sc_card(); if (card) { for (size_t z = 0; z < 9; z++) { if (!this->card_ref_has_ability_trap(card->action_chain.conditions[z]) && (card->action_chain.conditions[z].type == ConditionType::ATK_DICE_BOOST)) { return true; } } } for (size_t set_index = 0; set_index < 8; set_index++) { auto card = ps->get_set_card(set_index); if (card) { for (size_t z = 0; z < 9; z++) { if (!this->card_ref_has_ability_trap(card->action_chain.conditions[z]) && (card->action_chain.conditions[z].type == ConditionType::ATK_DICE_BOOST)) { return true; } } } } } return false; } void CardSpecial::unknown_8024A6DC( shared_ptr unknown_p2, shared_ptr 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->unknown_8024C2B0(1, as.action_card_refs[z], as, unknown_p3->get_card_ref()); this->unknown_8024C2B0(0x15, as.action_card_refs[z], as, unknown_p3->get_card_ref()); } } vector> CardSpecial::find_all_sc_cards_of_class( CardClass card_class) const { vector> ret; for (size_t z = 0; z < 4; z++) { auto ps = this->server()->get_player_state(z); if (ps) { auto sc_card = ps->get_sc_card(); if (sc_card && (sc_card->get_definition()->def.card_class() == card_class)) { ret.emplace_back(sc_card); } } } return ret; } } // namespace Episode3