From a57b6ce57b672e592e62edfebdb3fdfe6393c14a Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Fri, 30 Dec 2022 00:33:20 -0800 Subject: [PATCH] ep3 debugging helpers --- src/Episode3/CardSpecial.cc | 12 + src/Episode3/DataIndex.cc | 303 ++++++++++++++---------- src/Episode3/DataIndex.hh | 8 + src/Episode3/PlayerStateSubordinates.cc | 199 ++++++++++++++++ src/Episode3/PlayerStateSubordinates.hh | 16 ++ src/Episode3/Server.cc | 14 ++ src/Episode3/Server.hh | 8 + 7 files changed, 433 insertions(+), 127 deletions(-) diff --git a/src/Episode3/CardSpecial.cc b/src/Episode3/CardSpecial.cc index 9a255cd8..7c003762 100644 --- a/src/Episode3/CardSpecial.cc +++ b/src/Episode3/CardSpecial.cc @@ -257,6 +257,8 @@ bool CardSpecial::apply_defense_condition( 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); @@ -276,6 +278,8 @@ bool CardSpecial::apply_defense_condition( (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); @@ -3502,6 +3506,13 @@ void CardSpecial::unknown_8024C2B0( 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); @@ -3570,6 +3581,7 @@ void CardSpecial::unknown_8024C2B0( 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); } diff --git a/src/Episode3/DataIndex.cc b/src/Episode3/DataIndex.cc index 248ae34d..c329249f 100644 --- a/src/Episode3/DataIndex.cc +++ b/src/Episode3/DataIndex.cc @@ -18,6 +18,25 @@ namespace Episode3 { +const char* name_for_attack_medium(AttackMedium medium) { + switch (medium) { + case AttackMedium::UNKNOWN: + return "UNKNOWN"; + case AttackMedium::PHYSICAL: + return "PHYSICAL"; + case AttackMedium::TECH: + return "TECH"; + case AttackMedium::UNKNOWN_03: + return "UNKNOWN_03"; + case AttackMedium::INVALID_FF: + return "INVALID_FF"; + default: + return "__INVALID__"; + } +} + + + Location::Location() : Location(0, 0) { } Location::Location(uint8_t x, uint8_t y) : Location(x, y, Direction::RIGHT) { } Location::Location(uint8_t x, uint8_t y, Direction direction) @@ -33,6 +52,11 @@ bool Location::operator!=(const Location& other) const { return !this->operator==(other); } +std::string Location::str() const { + return string_printf("Location[x=%hhu, y=%hhu, dir=%s, u=%hhu]", + this->x, this->y, name_for_direction(this->direction), this->unused); +} + void Location::clear() { this->x = 0; this->y = 0; @@ -107,7 +131,7 @@ const char* name_for_direction(Direction d) { case Direction::INVALID_FF: return "INVALID_FF"; default: - return "__unknown__"; + return "__INVALID__"; } } @@ -283,134 +307,159 @@ struct ConditionDescription { }; static const vector description_for_condition_type({ - /* 0x00 */ {false, nullptr, nullptr}, - /* 0x01 */ {true, "AP Boost", "Temporarily increase AP by N"}, - /* 0x02 */ {false, "Rampage", "Rampage"}, - /* 0x03 */ {true, "Multi Strike", "Duplicate attack N times"}, - /* 0x04 */ {true, "Damage Modifier 1", "Set attack damage / AP to N after action cards applied (step 1)"}, - /* 0x05 */ {false, "Immobile", "Give Immobile condition"}, - /* 0x06 */ {false, "Hold", "Give Hold condition"}, - /* 0x07 */ {false, nullptr, nullptr}, - /* 0x08 */ {true, "TP Boost", "Add N TP temporarily during attack"}, - /* 0x09 */ {true, "Give Damage", "Cause direct N HP loss"}, - /* 0x0A */ {false, "Guom", "Give Guom condition"}, - /* 0x0B */ {false, "Paralyze", "Give Paralysis condition"}, - /* 0x0C */ {false, nullptr, nullptr}, - /* 0x0D */ {false, "A/H Swap", "Swap AP and HP temporarily"}, - /* 0x0E */ {false, "Pierce", "Attack SC directly even if they have items equipped"}, - /* 0x0F */ {false, nullptr, nullptr}, - /* 0x10 */ {true, "Heal", "Increase HP by N"}, - /* 0x11 */ {false, "Return to Hand", "Return card to hand"}, - /* 0x12 */ {false, nullptr, nullptr}, - /* 0x13 */ {false, nullptr, nullptr}, - /* 0x14 */ {false, "Acid", "Give Acid condition"}, - /* 0x15 */ {false, nullptr, nullptr}, - /* 0x16 */ {true, "Mighty Knuckle", "Temporarily increase AP by N, and set ATK dice to zero"}, - /* 0x17 */ {true, "Unit Blow", "Temporarily increase AP by N * number of this card set within phase"}, - /* 0x18 */ {false, "Curse", "Give Curse condition"}, - /* 0x19 */ {false, "Combo (AP)", "Temporarily increase AP by number of this card set within phase"}, - /* 0x1A */ {false, "Pierce/Rampage Block", "Block attack if Pierce/Rampage (?)"}, - /* 0x1B */ {false, "Ability Trap", "Temporarily disable opponent abilities"}, - /* 0x1C */ {false, "Freeze", "Give Freeze condition"}, - /* 0x1D */ {false, "Anti-Abnormality", "Cure all conditions"}, - /* 0x1E */ {false, nullptr, nullptr}, - /* 0x1F */ {false, "Explosion", "Damage all SCs and FCs by number of this same card set * 2"}, - /* 0x20 */ {false, nullptr, nullptr}, - /* 0x21 */ {false, nullptr, nullptr}, - /* 0x22 */ {false, nullptr, nullptr}, - /* 0x23 */ {false, "Return to Deck", "Cancel discard and move to bottom of deck instead"}, - /* 0x24 */ {false, "Aerial", "Give Aerial status"}, - /* 0x25 */ {true, "AP Loss", "Make attacker temporarily lose N AP during defense"}, - /* 0x26 */ {true, "Bonus From Leader", "Gain AP equal to the number of cards of type N on the field"}, - /* 0x27 */ {false, "Free Maneuver", "Enable movement over occupied tiles"}, - /* 0x28 */ {false, "Haste", "Make move actions free"}, - /* 0x29 */ {true, "Clone", "Make setting this card free if at least one card of type N is already on the field"}, - /* 0x2A */ {true, "DEF Disable by Cost", "Disable use of any defense cards costing between (N / 10) and (N % 10) points, inclusive"}, - /* 0x2B */ {true, "Filial", "Increase controlling SC\'s HP by N when this card is destroyed"}, - /* 0x2C */ {true, "Snatch", "Steal N EXP during attack"}, - /* 0x2D */ {true, "Hand Disrupter", "Discard N cards from hand immediately"}, - /* 0x2E */ {false, "Drop", "Give Drop condition"}, - /* 0x2F */ {false, "Action Disrupter", "Destroy all action cards used by attacker"}, - /* 0x30 */ {true, "Set HP", "Set HP to N"}, - /* 0x31 */ {false, "Native Shield", "Block attacks from Native creatures"}, - /* 0x32 */ {false, "A.Beast Shield", "Block attacks from A.Beast creatures"}, - /* 0x33 */ {false, "Machine Shield", "Block attacks from Machine creatures"}, - /* 0x34 */ {false, "Dark Shield", "Block attacks from Dark creatures"}, - /* 0x35 */ {false, "Sword Shield", "Block attacks from Sword items"}, - /* 0x36 */ {false, "Gun Shield", "Block attacks from Gun items"}, - /* 0x37 */ {false, "Cane Shield", "Block attacks from Cane items"}, - /* 0x38 */ {false, nullptr, nullptr}, - /* 0x39 */ {false, nullptr, nullptr}, - /* 0x3A */ {false, "Defender", "Make attacks go to setter of this card instead of original target"}, - /* 0x3B */ {false, "Survival Decoys", "Redirect damage for multi-sided attack"}, - /* 0x3C */ {true, "Give/Take EXP", "Give N EXP, or take if N is negative"}, - /* 0x3D */ {false, nullptr, nullptr}, - /* 0x3E */ {false, "Death Companion", "If this card has 1 or 2 HP, set its HP to N"}, - /* 0x3F */ {true, "EXP Decoy", "If defender has EXP, lose EXP instead of getting damage when attacked"}, - /* 0x40 */ {true, "Set MV", "Set MV to N"}, - /* 0x41 */ {true, "Group", "Temporarily increase AP by N * number of this card on field, excluding itself"}, - /* 0x42 */ {false, "Berserk", "User of this card receives the same damage as target, and isn\'t helped by target\'s defense cards"}, - /* 0x43 */ {false, "Guard Creature", "Attacks on controlling SC damage this card instead"}, - /* 0x44 */ {false, "Tech", "Technique cards cost 1 fewer ATK point"}, - /* 0x45 */ {false, "Big Swing", "Increase all attacking ATK costs by 1"}, - /* 0x46 */ {false, nullptr, nullptr}, - /* 0x47 */ {false, "Shield Weapon", "Limit attacker\'s choice of target to guard items"}, - /* 0x48 */ {false, "ATK Dice Boost", "Increase ATK dice roll by 1"}, - /* 0x49 */ {false, nullptr, nullptr}, - /* 0x4A */ {false, "Major Pierce", "If SC has over half of max HP, attacks target SC instead of equipped items"}, - /* 0x4B */ {false, "Heavy Pierce", "If SC has 3 or more items equipped, attacks target SC instead of equipped items"}, - /* 0x4C */ {false, "Major Rampage", "If SC has over half of max HP, attacks target SC and all equipped items"}, - /* 0x4D */ {false, "Heavy Rampage", "If SC has 3 or more items equipped, attacks target SC and all equipped items"}, - /* 0x4E */ {true, "AP Growth", "Permanently increase AP by N"}, - /* 0x4F */ {true, "TP Growth", "Permanently increase TP by N"}, - /* 0x50 */ {true, "Reborn", "If any card of type N is on the field, this card goes to the hand when destroyed instead of being discarded"}, - /* 0x51 */ {true, "Copy", "Temporarily set AP/TP to N percent (or 100% if N is 0) of opponent\'s values"}, - /* 0x52 */ {false, nullptr, nullptr}, - /* 0x53 */ {true, "Misc. Guards", "Add N to card\'s defense value"}, - /* 0x54 */ {true, "AP Override", "Set AP to N temporarily"}, - /* 0x55 */ {true, "TP Override", "Set TP to N temporarily"}, - /* 0x56 */ {false, "Return", "Return card to hand on destruction instead of discarding"}, - /* 0x57 */ {false, "A/T Swap Perm", "Permanently swap AP and TP"}, - /* 0x58 */ {false, "A/H Swap Perm", "Permanently swap AP and HP"}, - /* 0x59 */ {true, "Slayers/Assassins", "Temporarily increase AP during attack"}, - /* 0x5A */ {false, "Anti-Abnormality", "Remove all conditions"}, - /* 0x5B */ {false, "Fixed Range", "Use SC\'s range instead of weapon or attack card ranges"}, - /* 0x5C */ {false, "Elude", "SC does not lose HP when equipped items are destroyed"}, - /* 0x5D */ {false, "Parry", "Forward attack to a random FC within one tile of original target, excluding attacker and original target"}, - /* 0x5E */ {false, "Block Attack", "Completely block attack"}, - /* 0x5F */ {false, nullptr, nullptr}, - /* 0x60 */ {false, nullptr, nullptr}, - /* 0x61 */ {true, "Combo (TP)", "Gain TP equal to the number of cards of type N on the field"}, - /* 0x62 */ {true, "Misc. AP Bonuses", "Temporarily increase AP by N"}, - /* 0x63 */ {true, "Misc. TP Bonuses", "Temporarily increase TP by N"}, - /* 0x64 */ {false, nullptr, nullptr}, - /* 0x65 */ {true, "Misc. Defense Bonuses", "Decrease damage by N"}, - /* 0x66 */ {true, "Mostly Halfguards", "Reduce damage from incoming attack by N"}, - /* 0x67 */ {false, "Periodic Field", "Swap immunity to tech or physical attacks"}, - /* 0x68 */ {false, "FC Limit by Count", "Change FC limit from 8 ATK points total to 4 FCs total"}, - /* 0x69 */ {false, nullptr, nullptr}, - /* 0x6A */ {true, "MV Bonus", "Increase MV by N"}, - /* 0x6B */ {true, "Forward Damage", "Give N damage back to attacker during defense (?) (TODO)"}, - /* 0x6C */ {true, "Weak Spot / Influence", "Temporarily decrease AP by N"}, - /* 0x6D */ {true, "Damage Modifier 2", "Set attack damage / AP after action cards applied (step 2)"}, - /* 0x6E */ {true, "Weak Hit Block", "Block all attacks of N damage or less"}, - /* 0x6F */ {true, "AP Silence", "Temporarily decrease AP of opponent by N"}, - /* 0x70 */ {true, "TP Silence", "Temporarily decrease TP of opponent by N"}, - /* 0x71 */ {false, "A/T Swap", "Temporarily swap AP and TP"}, - /* 0x72 */ {true, "Halfguard", "Halve damage from attacks that would inflict N or more damage"}, - /* 0x73 */ {false, nullptr, nullptr}, - /* 0x74 */ {true, "Rampage AP Loss", "Temporarily reduce AP by N"}, - /* 0x75 */ {false, nullptr, nullptr}, - /* 0x76 */ {false, "Reflect", "Generate reverse attack"}, - /* 0x77 */ {false, nullptr, nullptr}, - /* 0x78 */ {false, nullptr, nullptr}, // Treated as "any condition" in find functions - /* 0x79 */ {false, nullptr, nullptr}, - /* 0x7A */ {false, nullptr, nullptr}, - /* 0x7B */ {false, nullptr, nullptr}, - /* 0x7C */ {false, nullptr, nullptr}, - /* 0x7D */ {false, nullptr, nullptr}, + /* 0x00 */ {false, "NONE", nullptr}, + /* 0x01 */ {true, "AP_BOOST", "Temporarily increase AP by N"}, + /* 0x02 */ {false, "RAMPAGE", "Rampage"}, + /* 0x03 */ {true, "MULTI_STRIKE", "Duplicate attack N times"}, + /* 0x04 */ {true, "DAMAGE_MOD_1", "Set attack damage / AP to N after action cards applied (step 1)"}, + /* 0x05 */ {false, "IMMOBILE", "Give Immobile condition"}, + /* 0x06 */ {false, "HOLD", "Give Hold condition"}, + /* 0x07 */ {false, "UNKNOWN_07", nullptr}, + /* 0x08 */ {true, "TP_BOOST", "Add N TP temporarily during attack"}, + /* 0x09 */ {true, "GIVE_DAMAGE", "Cause direct N HP loss"}, + /* 0x0A */ {false, "GUOM", "Give Guom condition"}, + /* 0x0B */ {false, "PARALYZE", "Give Paralysis condition"}, + /* 0x0C */ {false, "UNKNOWN_0C", nullptr}, + /* 0x0D */ {false, "A_H_SWAP", "Swap AP and HP temporarily"}, + /* 0x0E */ {false, "PIERCE", "Attack SC directly even if they have items equipped"}, + /* 0x0F */ {false, "UNKNOWN_0F", nullptr}, + /* 0x10 */ {true, "HEAL", "Increase HP by N"}, + /* 0x11 */ {false, "RETURN_TO_HAND", "Return card to hand"}, + /* 0x12 */ {false, "UNKNOWN_12", nullptr}, + /* 0x13 */ {false, "UNKNOWN_13", nullptr}, + /* 0x14 */ {false, "ACID", "Give Acid condition"}, + /* 0x15 */ {false, "UNKNOWN_15", nullptr}, + /* 0x16 */ {true, "MIGHTY_KNUCKLE", "Temporarily increase AP by N, and set ATK dice to zero"}, + /* 0x17 */ {true, "UNIT_BLOW", "Temporarily increase AP by N * number of this card set within phase"}, + /* 0x18 */ {false, "CURSE", "Give Curse condition"}, + /* 0x19 */ {false, "COMBO_AP", "Temporarily increase AP by number of this card set within phase"}, + /* 0x1A */ {false, "PIERCE_RAMPAGE_BLOCK", "Block attack if Pierce/Rampage (?)"}, + /* 0x1B */ {false, "ABILITY_TRAP", "Temporarily disable opponent abilities"}, + /* 0x1C */ {false, "FREEZE", "Give Freeze condition"}, + /* 0x1D */ {false, "ANTI_ABNORMALITY_1", "Cure all conditions"}, + /* 0x1E */ {false, "UNKNOWN_1E", nullptr}, + /* 0x1F */ {false, "EXPLOSION", "Damage all SCs and FCs by number of this same card set * 2"}, + /* 0x20 */ {false, "UNKNOWN_20", nullptr}, + /* 0x21 */ {false, "UNKNOWN_21", nullptr}, + /* 0x22 */ {false, "UNKNOWN_22", nullptr}, + /* 0x23 */ {false, "RETURN_TO_DECK", "Cancel discard and move to bottom of deck instead"}, + /* 0x24 */ {false, "AERIAL", "Give Aerial status"}, + /* 0x25 */ {true, "AP_LOSS", "Make attacker temporarily lose N AP during defense"}, + /* 0x26 */ {true, "BONUS_FROM_LEADER", "Gain AP equal to the number of cards of type N on the field"}, + /* 0x27 */ {false, "FREE_MANEUVER", "Enable movement over occupied tiles"}, + /* 0x28 */ {false, "HASTE", "Make move actions free"}, + /* 0x29 */ {true, "CLONE", "Make setting this card free if at least one card of type N is already on the field"}, + /* 0x2A */ {true, "DEF_DISABLE_BY_COST", "Disable use of any defense cards costing between (N / 10) and (N % 10) points, inclusive"}, + /* 0x2B */ {true, "FILIAL", "Increase controlling SC\'s HP by N when this card is destroyed"}, + /* 0x2C */ {true, "SNATCH", "Steal N EXP during attack"}, + /* 0x2D */ {true, "HAND_DISRUPTER", "Discard N cards from hand immediately"}, + /* 0x2E */ {false, "DROP", "Give Drop condition"}, + /* 0x2F */ {false, "ACTION_DISRUPTER", "Destroy all action cards used by attacker"}, + /* 0x30 */ {true, "SET_HP", "Set HP to N"}, + /* 0x31 */ {false, "NATIVE_SHIELD", "Block attacks from Native creatures"}, + /* 0x32 */ {false, "A_BEAST_SHIELD", "Block attacks from A.Beast creatures"}, + /* 0x33 */ {false, "MACHINE_SHIELD", "Block attacks from Machine creatures"}, + /* 0x34 */ {false, "DARK_SHIELD", "Block attacks from Dark creatures"}, + /* 0x35 */ {false, "SWORD_SHIELD", "Block attacks from Sword items"}, + /* 0x36 */ {false, "GUN_SHIELD", "Block attacks from Gun items"}, + /* 0x37 */ {false, "CANE_SHIELD", "Block attacks from Cane items"}, + /* 0x38 */ {false, "UNKNOWN_38", nullptr}, + /* 0x39 */ {false, "UNKNOWN_39", nullptr}, + /* 0x3A */ {false, "DEFENDER", "Make attacks go to setter of this card instead of original target"}, + /* 0x3B */ {false, "SURVIVAL_DECOYS", "Redirect damage for multi-sided attack"}, + /* 0x3C */ {true, "GIVE_OR_TAKE_EXP", "Give N EXP, or take if N is negative"}, + /* 0x3D */ {false, "UNKNOWN_3D", nullptr}, + /* 0x3E */ {false, "DEATH_COMPANION", "If this card has 1 or 2 HP, set its HP to N"}, + /* 0x3F */ {true, "EXP_DECOY", "If defender has EXP, lose EXP instead of getting damage when attacked"}, + /* 0x40 */ {true, "SET_MV", "Set MV to N"}, + /* 0x41 */ {true, "GROUP", "Temporarily increase AP by N * number of this card on field, excluding itself"}, + /* 0x42 */ {false, "BERSERK", "User of this card receives the same damage as target, and isn\'t helped by target\'s defense cards"}, + /* 0x43 */ {false, "GUARD_CREATURE", "Attacks on controlling SC damage this card instead"}, + /* 0x44 */ {false, "TECH", "Technique cards cost 1 fewer ATK point"}, + /* 0x45 */ {false, "BIG_SWING", "Increase all attacking ATK costs by 1"}, + /* 0x46 */ {false, "UNKNOWN_46", nullptr}, + /* 0x47 */ {false, "SHIELD_WEAPON", "Limit attacker\'s choice of target to guard items"}, + /* 0x48 */ {false, "ATK_DICE_BOOST", "Increase ATK dice roll by 1"}, + /* 0x49 */ {false, "UNKNOWN_49", nullptr}, + /* 0x4A */ {false, "MAJOR_PIERCE", "If SC has over half of max HP, attacks target SC instead of equipped items"}, + /* 0x4B */ {false, "HEAVY_PIERCE", "If SC has 3 or more items equipped, attacks target SC instead of equipped items"}, + /* 0x4C */ {false, "MAJOR_RAMPAGE", "If SC has over half of max HP, attacks target SC and all equipped items"}, + /* 0x4D */ {false, "HEAVY_RAMPAGE", "If SC has 3 or more items equipped, attacks target SC and all equipped items"}, + /* 0x4E */ {true, "AP_GROWTH", "Permanently increase AP by N"}, + /* 0x4F */ {true, "TP_GROWTH", "Permanently increase TP by N"}, + /* 0x50 */ {true, "REBORN", "If any card of type N is on the field, this card goes to the hand when destroyed instead of being discarded"}, + /* 0x51 */ {true, "COPY", "Temporarily set AP/TP to N percent (or 100% if N is 0) of opponent\'s values"}, + /* 0x52 */ {false, "UNKNOWN_52", nullptr}, + /* 0x53 */ {true, "MISC_GUARDS", "Add N to card\'s defense value"}, + /* 0x54 */ {true, "AP_OVERRIDE", "Set AP to N temporarily"}, + /* 0x55 */ {true, "TP_OVERRIDE", "Set TP to N temporarily"}, + /* 0x56 */ {false, "RETURN", "Return card to hand on destruction instead of discarding"}, + /* 0x57 */ {false, "A_T_SWAP_PERM", "Permanently swap AP and TP"}, + /* 0x58 */ {false, "A_H_SWAP_PERM", "Permanently swap AP and HP"}, + /* 0x59 */ {true, "SLAYERS_ASSASSINS", "Temporarily increase AP during attack"}, + /* 0x5A */ {false, "ANTI_ABNORMALITY_2", "Remove all conditions"}, + /* 0x5B */ {false, "FIXED_RANGE", "Use SC\'s range instead of weapon or attack card ranges"}, + /* 0x5C */ {false, "ELUDE", "SC does not lose HP when equipped items are destroyed"}, + /* 0x5D */ {false, "PARRY", "Forward attack to a random FC within one tile of original target, excluding attacker and original target"}, + /* 0x5E */ {false, "BLOCK_ATTACK", "Completely block attack"}, + /* 0x5F */ {false, "UNKNOWN_5F", nullptr}, + /* 0x60 */ {false, "UNKNOWN_60", nullptr}, + /* 0x61 */ {true, "COMBO_TP", "Gain TP equal to the number of cards of type N on the field"}, + /* 0x62 */ {true, "MISC_AP_BONUSES", "Temporarily increase AP by N"}, + /* 0x63 */ {true, "MISC_TP_BONUSES", "Temporarily increase TP by N"}, + /* 0x64 */ {false, "UNKNOWN_64", nullptr}, + /* 0x65 */ {true, "MISC_DEFENSE_BONUSES", "Decrease damage by N"}, + /* 0x66 */ {true, "MOSTLY_HALFGUARDS", "Reduce damage from incoming attack by N"}, + /* 0x67 */ {false, "PERIODIC_FIELD", "Swap immunity to tech or physical attacks"}, + /* 0x68 */ {false, "FC_LIMIT_BY_COUNT", "Change FC limit from 8 ATK points total to 4 FCs total"}, + /* 0x69 */ {false, "UNKNOWN_69", nullptr}, + /* 0x6A */ {true, "MV_BONUS", "Increase MV by N"}, + /* 0x6B */ {true, "FORWARD_DAMAGE", "Give N damage back to attacker during defense (?) (TODO)"}, + /* 0x6C */ {true, "WEAK_SPOT_INFLUENCE", "Temporarily decrease AP by N"}, + /* 0x6D */ {true, "DAMAGE_MODIFIER_2", "Set attack damage / AP after action cards applied (step 2)"}, + /* 0x6E */ {true, "WEAK_HIT_BLOCK", "Block all attacks of N damage or less"}, + /* 0x6F */ {true, "AP_SILENCE", "Temporarily decrease AP of opponent by N"}, + /* 0x70 */ {true, "TP_SILENCE", "Temporarily decrease TP of opponent by N"}, + /* 0x71 */ {false, "A_T_SWAP", "Temporarily swap AP and TP"}, + /* 0x72 */ {true, "HALFGUARD", "Halve damage from attacks that would inflict N or more damage"}, + /* 0x73 */ {false, "UNKNOWN_73", nullptr}, + /* 0x74 */ {true, "RAMPAGE_AP_LOSS", "Temporarily reduce AP by N"}, + /* 0x75 */ {false, "UNKNOWN_75", nullptr}, + /* 0x76 */ {false, "REFLECT", "Generate reverse attack"}, + /* 0x77 */ {false, "UNKNOWN_77", nullptr}, + /* 0x78 */ {false, "ANY", nullptr}, // Treated as "any condition" in find functions + /* 0x79 */ {false, "UNKNOWN_79", nullptr}, + /* 0x7A */ {false, "UNKNOWN_7A", nullptr}, + /* 0x7B */ {false, "UNKNOWN_7B", nullptr}, + /* 0x7C */ {false, "UNKNOWN_7C", nullptr}, + /* 0x7D */ {false, "UNKNOWN_7D", nullptr}, }); +const char* name_for_condition_type(ConditionType cond_type) { + try { + return description_for_condition_type.at(static_cast(cond_type)).name; + } catch (const out_of_range&) { + return "__INVALID__"; + } +} + + + +const char* name_for_action_subphase(ActionSubphase subphase) { + switch (subphase) { + case ActionSubphase::ATTACK: + return "ATTACK"; + case ActionSubphase::DEFENSE: + return "DEFENSE"; + case ActionSubphase::INVALID_FF: + return "INVALID_FF"; + default: + return "__INVALID__"; + } +} + + + void CardDefinition::Stat::decode_code() { this->type = static_cast(this->code / 1000); int16_t value = this->code - (this->type * 1000); diff --git a/src/Episode3/DataIndex.hh b/src/Episode3/DataIndex.hh index 92a19f09..5cc6100b 100644 --- a/src/Episode3/DataIndex.hh +++ b/src/Episode3/DataIndex.hh @@ -58,6 +58,8 @@ enum class AttackMedium : uint8_t { INVALID_FF = 0xFF, }; +const char* name_for_attack_medium(AttackMedium medium); + enum class CriterionCode : uint8_t { NONE = 0x00, HU_CLASS_SC = 0x01, @@ -293,6 +295,8 @@ enum class ConditionType : uint8_t { ANY_FF = 0xFF, // Used as a wildcard in some search functions }; +const char* name_for_condition_type(ConditionType cond_type); + enum class AssistEffect : uint16_t { NONE = 0x0000, DICE_HALF = 0x0001, @@ -389,6 +393,8 @@ enum class ActionSubphase : uint8_t { INVALID_FF = 0xFF, }; +const char* name_for_action_subphase(ActionSubphase subphase); + enum class SetupPhase : uint8_t { REGISTRATION = 0, STARTER_ROLLS = 1, @@ -434,6 +440,8 @@ struct Location { bool operator==(const Location& other) const; bool operator!=(const Location& other) const; + std::string str() const; + void clear(); void clear_FF(); } __attribute__((packed)); diff --git a/src/Episode3/PlayerStateSubordinates.cc b/src/Episode3/PlayerStateSubordinates.cc index 0b16c606..147ca50a 100644 --- a/src/Episode3/PlayerStateSubordinates.cc +++ b/src/Episode3/PlayerStateSubordinates.cc @@ -8,6 +8,24 @@ namespace Episode3 { +template +std::string string_for_refs(const parray& card_refs) { + string ret = "["; + for (size_t z = 0; z < Count; z++) { + if (card_refs[z] != 0xFFFF) { + ret += string_printf("%zu:@$%04X ", z, card_refs[z].load()); + } + } + if (!ret.empty()) { + ret.back() = ']'; // Replace the ' ' from the last added item + } else { + ret.push_back(']'); + } + return ret; +} + + + Condition::Condition() { this->clear(); } @@ -63,6 +81,26 @@ void Condition::clear_FF() { this->unknown_a8 = 0xFF; } +std::string Condition::str() const { + return string_printf( + "Condition[type=%s, turns=%hhu, a_arg=%hhd, dice=%hhu, flags=%02hhX, " + "def_eff_index=%hhu, ref=@%04hX, value=%hd, giver_ref=@%04hX " + "percent=%hhu value8=%hd order=%hu a8=%hu]", + name_for_condition_type(this->type), + this->remaining_turns, + this->a_arg_value, + this->dice_roll_value, + this->flags, + this->card_definition_effect_index, + this->card_ref.load(), + this->value.load(), + this->condition_giver_card_ref.load(), + this->random_percent, + this->value8, + this->order, + this->unknown_a8); +} + EffectResult::EffectResult() { @@ -82,6 +120,23 @@ void EffectResult::clear() { this->dice_roll_value = 0; } +std::string EffectResult::str() const { + return string_printf( + "EffectResult[att_ref=@%04hX, target_ref=@%04hX, value=%hhd, " + "cur_hp=%hhd, ap=%hhd, tp=%hhd, flags=%02hhX, op=%hhd, " + "cond_index=%hhu, dice=%hhu]", + this->attacker_card_ref.load(), + this->target_card_ref.load(), + this->value, + this->current_hp, + this->ap, + this->tp, + this->flags, + this->operation, + this->condition_index, + this->dice_roll_value); +} + CardShortStatus::CardShortStatus() { @@ -101,6 +156,20 @@ bool CardShortStatus::operator!=(const CardShortStatus& other) const { return !this->operator==(other); } +std::string CardShortStatus::str() const { + string loc_s = this->loc.str(); + return string_printf( + "CardShortStatus[ref=@%04hX, cur_hp=%hd, flags=%08" PRIX32 ", loc=%s, " + "u1=%04hX, max_hp=%hhd, u2=%hhu]", + this->card_ref.load(), + this->current_hp.load(), + this->card_flags.load(), + loc_s.c_str(), + this->unused1.load(), + this->max_hp, + this->unused2); +} + void CardShortStatus::clear() { this->card_ref = 0xFFFF; this->current_hp = 0; @@ -138,6 +207,23 @@ void ActionState::clear() { this->action_card_refs.clear(0xFFFF); } +std::string ActionState::str() const { + string target_refs_s = string_for_refs(this->target_card_refs); + string action_refs_s = string_for_refs(this->action_card_refs); + return string_printf( + "ActionState[client=%hu, u=%hhu, facing=%s, attacker_ref=@%04hX, " + "def_ref=@%04hX, target_refs=%s, action_refs=%s, " + "orig_attacker_ref=@%04hX]", + this->client_id.load(), + this->unused, + name_for_direction(this->facing_direction), + this->attacker_card_ref.load(), + this->defense_card_ref.load(), + target_refs_s.c_str(), + action_refs_s.c_str(), + this->original_attacker_card_ref.load()); +} + ActionChain::ActionChain() { @@ -171,6 +257,40 @@ bool ActionChain::operator!=(const ActionChain& other) const { return !this->operator==(other); } +std::string ActionChain::str() const { + string attack_action_card_refs_s = string_for_refs(this->attack_action_card_refs); + string target_card_refs_s = string_for_refs(this->target_card_refs); + return string_printf( + "ActionChain[eff_ap=%hhd, eff_tp=%hhd, ap_bonus=%hhd, damage=%hhd, " + "acting_ref=@%04hX, unknown_ref_a3=@%04hX, " + "attack_action_refs=%s, attack_action_ref_count=%hhu, " + "medium=%s, target_ref_count=%hhu, subphase=%s, " + "strikes=%hhu, damage_mult=%hhd, attack_num=%hhu, " + "tp_bonus=%hhd, u1=%hhu, u2=%hhu, card_ap=%hhd, " + "card_tp=%hhd, flags=%08" PRIX32 ", target_refs=%s]", + this->effective_ap, + this->effective_tp, + this->ap_effect_bonus, + this->damage, + this->acting_card_ref.load(), + this->unknown_card_ref_a3.load(), + attack_action_card_refs_s.c_str(), + this->attack_action_card_ref_count, + name_for_attack_medium(this->attack_medium), + this->target_card_ref_count, + name_for_action_subphase(this->action_subphase), + this->strike_count, + this->damage_multiplier, + this->attack_number, + this->tp_effect_bonus, + this->unused1, + this->unused2, + this->card_ap, + this->card_tp, + this->flags.load(), + target_card_refs_s.c_str()); +} + void ActionChain::clear() { this->effective_ap = 0; this->effective_tp = 0; @@ -232,6 +352,23 @@ bool ActionChainWithConds::operator!=(const ActionChainWithConds& other) const { return !this->operator==(other); } +std::string ActionChainWithConds::str() const { + string ret = "ActionChainWithConds[chain="; + ret += this->chain.str(); + ret += ", conds=["; + for (size_t z = 0; z < this->conditions.size(); z++) { + if (this->conditions[z].type != ConditionType::NONE) { + if (ret.back() != '=') { + ret += ", "; + } + ret += string_printf("%zu:", z); + ret += this->conditions[z].str(); + } + } + ret += "]]"; + return ret; +} + void ActionChainWithConds::clear() { this->chain.effective_ap = 0; this->chain.effective_tp = 0; @@ -381,6 +518,28 @@ bool ActionMetadata::operator!=(const ActionMetadata& other) const { return !this->operator==(other); } +std::string ActionMetadata::str() const { + string target_card_refs_s = string_for_refs(this->target_card_refs); + string defense_card_refs_s = string_for_refs(this->defense_card_refs); + string original_attacker_card_refs_s = string_for_refs(this->original_attacker_card_refs); + return string_printf( + "ActionMetadata[ref=@%04hX, target_ref_count=%hhu, def_ref_count=%hhu, " + "subphase=%s, def_power=%hhd, def_bonus=%hhd, " + "att_bonus=%hhd, flags=%08" PRIX32 ", target_refs=%s, " + "defense_refs=%s, original_attacker_refs=%s]", + this->card_ref.load(), + this->target_card_ref_count, + this->defense_card_ref_count, + name_for_action_subphase(this->action_subphase), + this->defense_power, + this->defense_bonus, + this->attack_bonus, + this->flags.load(), + target_card_refs_s.c_str(), + defense_card_refs_s.c_str(), + original_attacker_card_refs_s.c_str()); +} + void ActionMetadata::clear() { this->card_ref = 0xFFFF; this->target_card_ref_count = 0; @@ -457,6 +616,46 @@ HandAndEquipState::HandAndEquipState() { this->clear(); } +std::string HandAndEquipState::str() const { + string hand_card_refs_s = string_for_refs(this->hand_card_refs); + string set_card_refs_s = string_for_refs(this->set_card_refs); + string hand_card_refs2_s = string_for_refs(this->hand_card_refs2); + string set_card_refs2_s = string_for_refs(this->set_card_refs2); + return string_printf( + "HandAndEquipState[dice=[%hhu, %hhu], atk=%hhu, def=%hhu, atk2=%hhu, " + "a1=%hhu, total_set_cost=%hhu, is_cpu=%hhu, " + "assist_flags=%08" PRIX32 ", hand_refs=%s, " + "assist_ref=@%04hX, set_refs=%s, sc_ref=@%04hX, " + "hand_refs2=%s, set_refs2=%s, assist_ref2=@%04hX, " + "assist_set_num=%hu, assist_card_id=%04hX, " + "assist_turns=%hhu, assit_dely=%hhu, atk_bonus=%hhu, " + "def_bonus=%hhu, u2=[%hhu, %hhu]]", + this->dice_results[0], + this->dice_results[1], + this->atk_points, + this->def_points, + this->atk_points2, + this->unknown_a1, + this->total_set_cards_cost, + this->is_cpu_player, + this->assist_flags.load(), + hand_card_refs_s.c_str(), + this->assist_card_ref.load(), + set_card_refs_s.c_str(), + this->sc_card_ref.load(), + hand_card_refs2_s.c_str(), + set_card_refs2_s.c_str(), + this->assist_card_ref2.load(), + this->assist_card_set_number.load(), + this->assist_card_id.load(), + this->assist_remaining_turns, + this->assist_delay_turns, + this->atk_bonuses, + this->def_bonuses, + this->unused2[0], + this->unused2[1]); +} + void HandAndEquipState::clear() { this->dice_results.clear(0); this->atk_points = 0; diff --git a/src/Episode3/PlayerStateSubordinates.hh b/src/Episode3/PlayerStateSubordinates.hh index a12f7e39..21caad85 100644 --- a/src/Episode3/PlayerStateSubordinates.hh +++ b/src/Episode3/PlayerStateSubordinates.hh @@ -36,6 +36,8 @@ struct Condition { void clear(); void clear_FF(); + + std::string str() const; } __attribute__((packed)); struct EffectResult { @@ -54,6 +56,8 @@ struct EffectResult { bool operator==(const EffectResult& other) const; bool operator!=(const EffectResult& other) const; + std::string str() const; + void clear(); } __attribute__((packed)); @@ -72,6 +76,8 @@ struct CardShortStatus { void clear(); void clear_FF(); + + std::string str() const; } __attribute__((packed)); struct ActionState { @@ -89,6 +95,8 @@ struct ActionState { bool operator!=(const ActionState& other) const; void clear(); + + std::string str() const; } __attribute__((packed)); struct ActionChain { @@ -120,6 +128,8 @@ struct ActionChain { void clear(); void clear_FF(); + + std::string str() const; } __attribute__((packed)); struct ActionChainWithConds { @@ -153,6 +163,8 @@ struct ActionChainWithConds { void set_action_subphase_from_card(std::shared_ptr card); bool unknown_8024DEC4() const; + + std::string str() const; } __attribute__((packed)); struct ActionMetadata { @@ -172,6 +184,8 @@ struct ActionMetadata { bool operator==(const ActionMetadata& other) const; bool operator!=(const ActionMetadata& other) const; + std::string str() const; + void clear(); void clear_FF(); @@ -218,6 +232,8 @@ struct HandAndEquipState { void clear(); void clear_FF(); + + std::string str() const; } __attribute__((packed)); struct PlayerStats { diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index 7b6108b2..3cb8dda6 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -38,6 +38,7 @@ ServerBase::ServerBase( shared_ptr map_if_tournament) : lobby(lobby), data_index(data_index), + log(lobby->log.prefix + "[Ep3::Server] "), random_seed(random_seed), is_tournament(!!map_if_tournament), last_chosen_map(map_if_tournament) { } @@ -244,6 +245,17 @@ void Server::send_commands_for_joining_spectator(Channel& c) const { } } +__attribute__((format(printf, 2, 3))) +void Server::log_debug(const char* fmt, ...) const { + auto l = this->base()->lobby.lock(); + if (l && (this->base()->data_index->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) { + va_list va; + va_start(va, fmt); + this->base()->log.info_v(fmt, va); + va_end(va); + } +} + __attribute__((format(printf, 2, 3))) void Server::send_debug_message_printf(const char* fmt, ...) const { auto l = this->base()->lobby.lock(); @@ -252,6 +264,7 @@ void Server::send_debug_message_printf(const char* fmt, ...) const { va_start(va, fmt); std::string buf = string_vprintf(fmt, va); va_end(va); + this->base()->log.info("%s", buf.c_str()); std::u16string decoded = decode_sjis(buf); send_text_message(l, decoded.c_str()); } @@ -265,6 +278,7 @@ void Server::send_info_message_printf(const char* fmt, ...) const { va_start(va, fmt); std::string buf = string_vprintf(fmt, va); va_end(va); + this->base()->log.info("%s", buf.c_str()); std::u16string decoded = decode_sjis(buf); send_text_message(l, decoded.c_str()); } diff --git a/src/Episode3/Server.hh b/src/Episode3/Server.hh index 5452cdfd..970e9706 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -27,6 +27,10 @@ namespace Episode3 { * in these files map very closely to how their server implementation was * written; notable differences (due to necessary environment differences or bug * fixes) are described in the comments therein. + * + * Some debugging functions have been added which are not part of the original + * implementation. Notably, this applies to functions like debug message senders + * and loggers and all str() functions. * * There are likely undiscovered bugs in this code, some originally written by * Sega, but more written by me as I manually transcribed and updated this code. @@ -74,6 +78,7 @@ public: std::weak_ptr lobby; std::shared_ptr data_index; + PrefixedLogger log; uint32_t random_seed; bool is_tournament; std::shared_ptr last_chosen_map; @@ -117,6 +122,9 @@ public: void send_commands_for_joining_spectator(Channel& ch) const; + __attribute__((format(printf, 2, 3))) + void log_debug(const char* fmt, ...) const; + __attribute__((format(printf, 2, 3))) void send_debug_message_printf(const char* fmt, ...) const; __attribute__((format(printf, 2, 3)))