#include "test_mock_actor.h" #include "algo.h" #include "rand.h" #include "doctest.h" static Game_Actor MakeActor(int id) { return Game_Actor(id); } static Game_Enemy& MakeEnemy(int id) { auto& tp = lcf::Data::troops[0]; tp.members.resize(8); tp.members[id - 1].enemy_id = id; Main_Data::game_enemyparty->ResetBattle(1); auto& enemy = (*Main_Data::game_enemyparty)[id - 1]; return enemy; } TEST_SUITE_BEGIN("Algo"); static void testRowAdj(lcf::rpg::SaveActor::RowType row, bool offense, bool none, bool back, bool surround, bool pincers) { REQUIRE_EQ(none, Algo::IsRowAdjusted(row, lcf::rpg::System::BattleCondition_none, offense)); REQUIRE_EQ(none, Algo::IsRowAdjusted(row, lcf::rpg::System::BattleCondition_initiative, offense)); REQUIRE_EQ(back, Algo::IsRowAdjusted(row, lcf::rpg::System::BattleCondition_back, offense)); REQUIRE_EQ(surround, Algo::IsRowAdjusted(row, lcf::rpg::System::BattleCondition_surround, offense)); REQUIRE_EQ(pincers, Algo::IsRowAdjusted(row, lcf::rpg::System::BattleCondition_pincers, offense)); } TEST_CASE("RowAdj") { const MockActor m; SUBCASE("front") { auto row = lcf::rpg::SaveActor::RowType_front; SUBCASE("offsense") { testRowAdj(row, true, true, false, true, false); } SUBCASE("defense") { testRowAdj(row, false, false, true, true, false); } } SUBCASE("back") { auto row = lcf::rpg::SaveActor::RowType_back; SUBCASE("offsense") { testRowAdj(row, true, false, true, true, false); } SUBCASE("defense") { testRowAdj(row, false, true, false, true, false); } } } static void testRowAdjBattler(const Game_Battler& battler, bool offense, bool bug, bool none, bool back, bool surround, bool pincers) { REQUIRE_EQ(none, Algo::IsRowAdjusted(battler, lcf::rpg::System::BattleCondition_none, offense, bug)); REQUIRE_EQ(none, Algo::IsRowAdjusted(battler, lcf::rpg::System::BattleCondition_initiative, offense, bug)); REQUIRE_EQ(back, Algo::IsRowAdjusted(battler, lcf::rpg::System::BattleCondition_back, offense, bug)); REQUIRE_EQ(surround, Algo::IsRowAdjusted(battler, lcf::rpg::System::BattleCondition_surround, offense, bug)); REQUIRE_EQ(pincers, Algo::IsRowAdjusted(battler, lcf::rpg::System::BattleCondition_pincers, offense, bug)); } TEST_CASE("RowAdjBattler") { const MockActor m; SUBCASE("actor") { auto actor = MakeActor(1); SUBCASE("front") { actor.SetBattleRow(lcf::rpg::SaveActor::RowType_front); SUBCASE("offsense") { testRowAdjBattler(actor, true, true, true, false, true, false); testRowAdjBattler(actor, true, false, true, false, true, false); } SUBCASE("defense") { testRowAdjBattler(actor, false, true, false, true, true, false); testRowAdjBattler(actor, false, false, false, true, true, false); } } SUBCASE("back") { actor.SetBattleRow(lcf::rpg::SaveActor::RowType_back); SUBCASE("offsense") { testRowAdjBattler(actor, true, true, false, true, true, false); testRowAdjBattler(actor, true, false, false, true, true, false); } SUBCASE("defense") { testRowAdjBattler(actor, false, true, true, false, true, false); testRowAdjBattler(actor, false, false, true, false, true, false); } } } SUBCASE("enemy") { auto enemy = MakeEnemy(1); SUBCASE("offsense") { testRowAdjBattler(enemy, true, true, true, false, true, false); testRowAdjBattler(enemy, true, false, false, false, false, false); } SUBCASE("defense") { testRowAdjBattler(enemy, false, true, false, true, true, false); testRowAdjBattler(enemy, false, false, false, false, false, false); } } } TEST_CASE("Variance") { SUBCASE("0 var disabled") { REQUIRE_EQ(Algo::VarianceAdjustEffect(100, 0), 100); } SUBCASE(">0") { SUBCASE("max") { Rand::LockGuard lk(INT32_MAX); for (int var = 1; var <= 10; ++var) { REQUIRE_EQ(100 + 5 * var, Algo::VarianceAdjustEffect(100, var)); } } SUBCASE("min") { Rand::LockGuard lk(INT32_MIN); for (int var = 1; var <= 10; ++var) { REQUIRE_EQ(100 - 5 * var, Algo::VarianceAdjustEffect(100, var)); } } } SUBCASE("one") { SUBCASE("max") { Rand::LockGuard lk(INT32_MAX); REQUIRE_EQ(2, Algo::VarianceAdjustEffect(1, 10)); } SUBCASE("min") { Rand::LockGuard lk(INT32_MIN); REQUIRE_EQ(1, Algo::VarianceAdjustEffect(1, 10)); } } SUBCASE("zero") { SUBCASE("modern") { const MockActor m(Player::EngineEnglish); REQUIRE_FALSE(Player::IsLegacy()); REQUIRE_EQ(Algo::VarianceAdjustEffect(0, 0), 0); REQUIRE_EQ(Algo::VarianceAdjustEffect(0, 1), 0); } SUBCASE("legacy") { const MockActor m(Player::EngineRpg2k); REQUIRE(Player::IsLegacy()); SUBCASE("max") { Rand::LockGuard lk(INT32_MAX); REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 0)); REQUIRE_EQ(1, Algo::VarianceAdjustEffect(0, 1)); } SUBCASE("min") { Rand::LockGuard lk(INT32_MIN); REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 0)); REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 1)); } } } SUBCASE("neg never varies") { REQUIRE_EQ(Algo::VarianceAdjustEffect(-100, 0), -100); } } void makeAgiSkills() { // Does physical evasion auto* s = MakeDBSkill(1, 90, 0, 0, 0, 0); s->scope = lcf::rpg::Skill::Scope_enemy; s->failure_message = 3; s = MakeDBSkill(2, 90, 0, 0, 0, 0); s->scope = lcf::rpg::Skill::Scope_enemies; s->failure_message = 3; // All ignore physical evasion s = MakeDBSkill(3, 90, 0, 0, 0, 0); s->scope = lcf::rpg::Skill::Scope_self; s->failure_message = 3; s = MakeDBSkill(4, 90, 0, 0, 0, 0); s->scope = lcf::rpg::Skill::Scope_ally; s->failure_message = 3; s = MakeDBSkill(5, 90, 0, 0, 0, 0); s->scope = lcf::rpg::Skill::Scope_party; s->failure_message = 3; s = MakeDBSkill(6, 90, 0, 0, 0, 0); s->scope = lcf::rpg::Skill::Scope_enemy; s->failure_message = 0; } decltype(auto) MakeAgiEnemy(int id, int agi) { MakeDBEnemy(id, 1, 1, 1, 1, 1, agi); return MakeEnemy(id); } decltype(auto) MakeAgiActor(int id, int agi) { auto actor = MakeActor(id); actor.SetBaseAgi(agi); return actor; } static void testAgi(int src, int tgt, int res) { auto source = MakeAgiActor(1, src); auto target = MakeAgiEnemy(1, tgt); REQUIRE_EQ(res, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(res, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); REQUIRE_EQ(res, Algo::CalcSkillToHit(source, target, lcf::Data::skills[1])); for (int i = 2; i < 6; ++i) { CAPTURE(i); REQUIRE_EQ(90, Algo::CalcSkillToHit(source, target, lcf::Data::skills[i])); } } TEST_CASE("HitRateAgi") { const MockActor m; makeAgiSkills(); SUBCASE("100_100") { testAgi(100, 100, 90); } SUBCASE("100_50") { testAgi(100, 50, 92); } SUBCASE("50_100") { testAgi(50, 100, 85); } SUBCASE("10_1") { testAgi(10, 1, 94); } SUBCASE("10_0") { testAgi(10, 0, 94); } SUBCASE("0_1") { testAgi(0, 1, 90); } } static void testStates(Game_Battler& source, Game_Battler& target, int base) { SUBCASE("Cannot act") { target.AddState(2, true); REQUIRE_EQ(100, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(100, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); } SUBCASE("Avoid attacks") { target.AddState(3, true); REQUIRE_EQ(0, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true)); // RPG_RT bug REQUIRE_EQ(base, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); } SUBCASE("Avoid attacks and cannot act") { target.AddState(2, true); target.AddState(3, true); REQUIRE_EQ(0, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true)); // RPG_RT bug REQUIRE_EQ(100, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); } } TEST_CASE("HitRateStates") { const MockActor m; makeAgiSkills(); lcf::Data::states[2 - 1].restriction = lcf::rpg::State::Restriction_do_nothing; lcf::Data::states[3 - 1].avoid_attacks = true; lcf::Data::states[4 - 1].reduce_hit_ratio = 50; auto source = MakeAgiActor(1, 100); auto target = MakeAgiEnemy(1, 100); testStates(source, target, 90); SUBCASE("source blind50") { source.AddState(4, true); REQUIRE_EQ(45, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(45, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); testStates(source, target, 45); } } static void testHitRateRow(Game_Battler& source, Game_Battler& target, int none, int back, int surround, int pincer, int back_no_bug, int surround_no_bug) { REQUIRE_EQ(none, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(none, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_initiative, true)); REQUIRE_EQ(back, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_back, true)); REQUIRE_EQ(surround, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_surround, true)); REQUIRE_EQ(pincer, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_pincers, true)); REQUIRE_EQ(none, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, false)); REQUIRE_EQ(none, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_initiative, false)); REQUIRE_EQ(back_no_bug, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_back, false)); REQUIRE_EQ(surround_no_bug, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_surround, false)); REQUIRE_EQ(pincer, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_pincers, false)); } TEST_CASE("HitRateArmorAndRow2k3") { const MockActor m(Player::EngineRpg2k3); makeAgiSkills(); MakeDBEquip(1, lcf::rpg::Item::Type_armor, 0, 0, 0, 0, 100, 0, false, false, false, false, false, true, false, false); auto source = MakeAgiActor(1, 100); SUBCASE("actor target") { auto target = MakeAgiActor(2, 100); SUBCASE("Front") { target.SetBattleRow(lcf::rpg::SaveActor::RowType_front); SUBCASE("no armor") { testHitRateRow(source, target, 90, 65, 65, 90, 65, 65); REQUIRE_EQ(90, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); } SUBCASE("phys eva up") { target.SetEquipment(3, 1); REQUIRE(target.HasPhysicalEvasionUp()); testHitRateRow(source, target, 65, 40, 40, 65, 40, 40); REQUIRE_EQ(65, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); } } SUBCASE("Back") { target.SetBattleRow(lcf::rpg::SaveActor::RowType_back); SUBCASE("no armor") { testHitRateRow(source, target, 65, 90, 65, 90, 90, 65); REQUIRE_EQ(90, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); } SUBCASE("phys eva up") { target.SetEquipment(3, 1); REQUIRE(target.HasPhysicalEvasionUp()); testHitRateRow(source, target, 40, 65, 40, 65, 65, 40); REQUIRE_EQ(65, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); } } } SUBCASE("enemy target") { auto target = MakeAgiEnemy(2, 100); testHitRateRow(source, target, 90, 65, 65, 90, 90, 90); REQUIRE_EQ(90, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); } } static void testHitRateRow2k(Game_Battler& source, Game_Battler& target, int rate) { testHitRateRow(source, target, rate, rate, rate, rate, rate, rate); } TEST_CASE("HitRateArmorAndRow2k") { const MockActor m(Player::EngineRpg2k); makeAgiSkills(); MakeDBEquip(1, lcf::rpg::Item::Type_armor, 0, 0, 0, 0, 100, 0, false, false, false, false, false, true, false, false); auto source = MakeAgiActor(1, 100); SUBCASE("actor target") { auto target = MakeAgiActor(2, 100); SUBCASE("no armor") { testHitRateRow2k(source, target, 90); REQUIRE_EQ(90, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); } SUBCASE("phys eva up") { target.SetEquipment(3, 1); REQUIRE(target.HasPhysicalEvasionUp()); testHitRateRow2k(source, target, 65); REQUIRE_EQ(65, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); } } SUBCASE("enemy target") { auto target = MakeAgiEnemy(2, 100); testHitRateRow2k(source, target, 90); REQUIRE_EQ(90, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); } } TEST_CASE("HitRateWeapons") { const MockActor m(Player::EngineRpg2k3); MakeDBEquip(1, lcf::rpg::Item::Type_weapon, 0, 0, 0, 0, 90, 0, false, false, false, true, false, false, false, false); MakeDBEquip(2, lcf::rpg::Item::Type_weapon, 0, 0, 0, 50, 90, 0, false, false, false, false, false, false, false, false); auto source = MakeAgiActor(1, 100); auto target = MakeAgiEnemy(1, 200); source.SetEquipment(1, 1); source.SetEquipment(2, 2); REQUIRE_EQ(source.GetAgi(Game_Battler::WeaponAll), 150); REQUIRE(source.AttackIgnoresEvasion(Game_Battler::WeaponAll)); REQUIRE_EQ(90, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(source.GetAgi(Game_Battler::WeaponNone), 100); REQUIRE_FALSE(source.AttackIgnoresEvasion(Game_Battler::WeaponNone)); REQUIRE_EQ(85, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponNone, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(source.GetAgi(Game_Battler::WeaponPrimary), 100); REQUIRE(source.AttackIgnoresEvasion(Game_Battler::WeaponPrimary)); REQUIRE_EQ(90, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponPrimary, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(source.GetAgi(Game_Battler::WeaponSecondary), 150); REQUIRE_FALSE(source.AttackIgnoresEvasion(Game_Battler::WeaponSecondary)); REQUIRE_EQ(88, Algo::CalcNormalAttackToHit(source, target, Game_Battler::WeaponSecondary, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(100, Algo::CalcSkillToHit(source, target, lcf::Data::skills[0])); } TEST_CASE("CritRate") { const MockActor m(Player::EngineRpg2k3); lcf::Data::enemies[0].critical_hit = true; lcf::Data::enemies[0].critical_hit_chance = 30; lcf::Data::enemies[1].critical_hit = true; lcf::Data::enemies[1].critical_hit_chance = 30; MakeDBEquip(1, lcf::rpg::Item::Type_weapon)->critical_hit = 50; MakeDBEquip(2, lcf::rpg::Item::Type_weapon)->critical_hit = 80; MakeDBEquip(3, lcf::rpg::Item::Type_armor)->prevent_critical = true; SUBCASE("actor -> actor - always fails") { auto source = MakeActor(1); auto target = MakeActor(2); REQUIRE_GT(source.GetCriticalHitChance(), 0.0f); REQUIRE_EQ(0, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponAll)); } SUBCASE("enemy -> enemy - always fails") { auto source = MakeEnemy(1); auto target = MakeEnemy(2); REQUIRE_GT(source.GetCriticalHitChance(), 0.0f); REQUIRE_EQ(0, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponAll)); } SUBCASE("actor -> enemy") { auto source = MakeActor(1); auto target = MakeEnemy(1); REQUIRE_GT(source.GetCriticalHitChance(), 0.0f); SUBCASE("baseline") { REQUIRE_EQ(3, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponAll)); REQUIRE_EQ(3, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponNone)); REQUIRE_EQ(3, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponPrimary)); REQUIRE_EQ(3, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponSecondary)); } SUBCASE("weapons") { source.SetEquipment(1, 1); source.SetEquipment(2, 2); REQUIRE_EQ(83, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponAll)); REQUIRE_EQ(3, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponNone)); REQUIRE_EQ(53, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponPrimary)); REQUIRE_EQ(83, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponSecondary)); } } SUBCASE("enemy -> actor") { auto source = MakeEnemy(1); auto target = MakeActor(1); REQUIRE_GT(source.GetCriticalHitChance(), 0.0f); SUBCASE("baseline") { REQUIRE_EQ(3, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponAll)); REQUIRE_EQ(3, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponNone)); REQUIRE_EQ(3, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponPrimary)); REQUIRE_EQ(3, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponSecondary)); } SUBCASE("armor prevents critical") { target.SetEquipment(3, 3); REQUIRE_EQ(0, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponAll)); REQUIRE_EQ(0, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponNone)); REQUIRE_EQ(0, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponPrimary)); REQUIRE_EQ(0, Algo::CalcCriticalHitChance(source, target, Game_Battler::WeaponSecondary)); } } } decltype(auto) MakeStatEnemy(int id, int atk, int def, int spi) { MakeDBEnemy(id, 1, 1, atk, def, spi, 1); return MakeEnemy(id); } decltype(auto) MakeStatActor(int id, int atk, int def, int spi) { auto actor = MakeActor(id); actor.SetBaseAtk(atk); actor.SetBaseDef(def); actor.SetBaseSpi(spi); return actor; } TEST_CASE("DefendAdjustment") { const MockActor m; SUBCASE("no strong") { auto target = MakeActor(1); SUBCASE("baseline") { REQUIRE_EQ(100, Algo::AdjustDamageForDefend(100, target)); } SUBCASE("defend") { target.SetIsDefending(true); REQUIRE_EQ(50, Algo::AdjustDamageForDefend(100, target)); } } SUBCASE("strong") { lcf::Data::actors[0].super_guard = true; auto target = MakeActor(1); SUBCASE("baseline") { REQUIRE_EQ(100, Algo::AdjustDamageForDefend(100, target)); } SUBCASE("defend") { target.SetIsDefending(true); REQUIRE_EQ(25, Algo::AdjustDamageForDefend(100, target)); } } } TEST_CASE("SelfDestructEffect") { const MockActor m; auto target = MakeStatActor(1, 0, 100, 0); SUBCASE("100_100") { auto source = MakeStatEnemy(1, 100, 0, 0); REQUIRE_EQ(50, Algo::CalcSelfDestructEffect(source, target, false)); } SUBCASE("200_100") { auto source = MakeStatEnemy(1, 200, 0, 0); REQUIRE_EQ(150, Algo::CalcSelfDestructEffect(source, target, false)); } SUBCASE("1_100") { auto source = MakeStatEnemy(1, 1, 0, 0); REQUIRE_EQ(0, Algo::CalcSelfDestructEffect(source, target, false)); } SUBCASE("150_100") { auto source = MakeStatEnemy(1, 150, 0, 0); REQUIRE_EQ(100, Algo::CalcSelfDestructEffect(source, target, false)); } } static void testSkillStats(int power, int phys, int mag, Game_Battler& source, Game_Battler& target, int dmg, int heal) { MakeDBSkill(1, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_enemy; MakeDBSkill(2, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_enemies; MakeDBSkill(3, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_self; MakeDBSkill(4, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_ally; MakeDBSkill(5, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_party; MakeDBSkill(6, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_enemy; SetDBSkillAttribute(6, 1, true); MakeDBSkill(7, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_enemies; SetDBSkillAttribute(7, 1, true); MakeDBSkill(8, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_self; SetDBSkillAttribute(8, 1, true); MakeDBSkill(9, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_ally; SetDBSkillAttribute(9, 1, true); MakeDBSkill(10, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_party; SetDBSkillAttribute(10, 1, true); MakeDBSkill(11, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_enemy; lcf::Data::skills[10].ignore_defense = true; MakeDBSkill(12, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_enemies; lcf::Data::skills[11].ignore_defense = true; MakeDBSkill(13, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_self; lcf::Data::skills[12].ignore_defense = true; MakeDBSkill(14, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_ally; lcf::Data::skills[13].ignore_defense = true; MakeDBSkill(15, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_party; lcf::Data::skills[14].ignore_defense = true; MakeDBSkill(16, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_enemy; SetDBSkillAttribute(16, 1, true); lcf::Data::skills[15].ignore_defense = true; MakeDBSkill(17, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_enemies; SetDBSkillAttribute(17, 1, true); lcf::Data::skills[16].ignore_defense = true; MakeDBSkill(18, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_self; SetDBSkillAttribute(18, 1, true); lcf::Data::skills[17].ignore_defense = true; MakeDBSkill(19, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_ally; SetDBSkillAttribute(19, 1, true); lcf::Data::skills[18].ignore_defense = true; MakeDBSkill(20, 100, power, phys, mag, 0)->scope = lcf::rpg::Skill::Scope_party; SetDBSkillAttribute(20, 1, true); lcf::Data::skills[19].ignore_defense = true; SUBCASE("baseline") { REQUIRE_EQ(dmg, Algo::CalcSkillEffect(source, target, lcf::Data::skills[0], false)); REQUIRE_EQ(dmg, Algo::CalcSkillEffect(source, target, lcf::Data::skills[1], false)); REQUIRE_EQ(heal, Algo::CalcSkillEffect(source, target, lcf::Data::skills[2], false)); REQUIRE_EQ(heal, Algo::CalcSkillEffect(source, target, lcf::Data::skills[3], false)); REQUIRE_EQ(heal, Algo::CalcSkillEffect(source, target, lcf::Data::skills[4], false)); } SUBCASE("attr2x") { REQUIRE_EQ(dmg * 2, Algo::CalcSkillEffect(source, target, lcf::Data::skills[5], false)); REQUIRE_EQ(dmg * 2, Algo::CalcSkillEffect(source, target, lcf::Data::skills[6], false)); REQUIRE_EQ(heal * 2, Algo::CalcSkillEffect(source, target, lcf::Data::skills[7], false)); REQUIRE_EQ(heal * 2, Algo::CalcSkillEffect(source, target, lcf::Data::skills[8], false)); REQUIRE_EQ(heal * 2, Algo::CalcSkillEffect(source, target, lcf::Data::skills[9], false)); } SUBCASE("ignore_defense") { REQUIRE_EQ(heal, Algo::CalcSkillEffect(source, target, lcf::Data::skills[10], false)); REQUIRE_EQ(heal, Algo::CalcSkillEffect(source, target, lcf::Data::skills[11], false)); REQUIRE_EQ(heal, Algo::CalcSkillEffect(source, target, lcf::Data::skills[12], false)); REQUIRE_EQ(heal, Algo::CalcSkillEffect(source, target, lcf::Data::skills[13], false)); REQUIRE_EQ(heal, Algo::CalcSkillEffect(source, target, lcf::Data::skills[14], false)); } SUBCASE("ignore_defense+attr2x") { REQUIRE_EQ(heal * 2, Algo::CalcSkillEffect(source, target, lcf::Data::skills[15], false)); REQUIRE_EQ(heal * 2, Algo::CalcSkillEffect(source, target, lcf::Data::skills[16], false)); REQUIRE_EQ(heal * 2, Algo::CalcSkillEffect(source, target, lcf::Data::skills[17], false)); REQUIRE_EQ(heal * 2, Algo::CalcSkillEffect(source, target, lcf::Data::skills[18], false)); REQUIRE_EQ(heal * 2, Algo::CalcSkillEffect(source, target, lcf::Data::skills[19], false)); } } TEST_CASE("SkillEffect") { const MockActor m; MakeDBAttribute(1, lcf::rpg::Attribute::Type_magical, 200, 200, 200, 200, 200); SUBCASE("100/0/120 -> 0/100/90") { auto source = MakeStatActor(1, 100, 0, 120); auto target = MakeStatActor(1, 0, 100, 90); SUBCASE("10/10/10") { testSkillStats(10, 10, 10, source, target, 54, 90); } SUBCASE("0/10/10") { testSkillStats(0, 10, 10, source, target, 44, 80); } SUBCASE("10/0/10") { testSkillStats(10, 0, 10, source, target, 29, 40); } SUBCASE("10/10/0") { testSkillStats(10, 10, 0, source, target, 35, 60); } SUBCASE("0/0/0") { testSkillStats(0, 0, 0, source, target, 0, 0); } } SUBCASE("10/0/10 -> 0/100/100") { auto source = MakeStatActor(1, 10, 0, 10); auto target = MakeStatActor(1, 0, 100, 100); SUBCASE("0/10/10") { testSkillStats(0, 10, 10, source, target, 0, 7); } } } static void testEnemyAttackEnemy(Game_Battler& source, Game_Battler& target, int dmg, int crit) { for (int wid = -1; wid <= 2; ++wid) { for (int cid = 0; cid <= 4; ++cid) { CAPTURE(wid); CAPTURE(cid); SUBCASE("no crit") { REQUIRE_EQ(dmg, Algo::CalcNormalAttackEffect(source, target, Game_Battler::Weapon(wid), false, false, lcf::rpg::System::BattleCondition(cid), true)); REQUIRE_EQ(dmg, Algo::CalcNormalAttackEffect(source, target, Game_Battler::Weapon(wid), false, false, lcf::rpg::System::BattleCondition(cid), false)); } SUBCASE("crit") { REQUIRE_EQ(crit, Algo::CalcNormalAttackEffect(source, target, Game_Battler::Weapon(wid), true, false, lcf::rpg::System::BattleCondition(cid), true)); REQUIRE_EQ(crit, Algo::CalcNormalAttackEffect(source, target, Game_Battler::Weapon(wid), true, false, lcf::rpg::System::BattleCondition(cid), false)); } } } } static void testActorAttackRow(Game_Battler& source, Game_Battler& target, int none, int back, int surround, int pincers, int back_no_bug, int surround_no_bug) { REQUIRE_EQ(none, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(none, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_initiative, true)); REQUIRE_EQ(back, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_back, true)); REQUIRE_EQ(surround, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_surround, true)); REQUIRE_EQ(pincers, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_pincers, true)); REQUIRE_EQ(none, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_none, false)); REQUIRE_EQ(none, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_initiative, false)); REQUIRE_EQ(back_no_bug, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_back, false)); REQUIRE_EQ(surround_no_bug, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_surround, false)); REQUIRE_EQ(pincers, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_pincers, false)); } static void TestNormalAttack(int engine) { const MockActor m(engine); lcf::Data::enemies[1].attribute_ranks[0] = 2; MakeDBAttribute(1, lcf::rpg::Attribute::Type_physical, 200, 200, 200, 200, 200); const bool is2k3 = Player::IsRPG2k3(); SUBCASE("enemy 120/0 -> enemy 0/90") { auto source = MakeStatEnemy(1, 120, 0, 0); auto target = MakeStatEnemy(2, 0, 90, 0); REQUIRE_EQ(source.GetAtk(), 120); REQUIRE_EQ(target.GetDef(), 90); SUBCASE("baseline") { testEnemyAttackEnemy(source, target, 38, 114); } SUBCASE("charged") { source.SetCharged(true); testEnemyAttackEnemy(source, target, 76, 114); } } SUBCASE("enemy 0/0 -> enemy 0/100") { auto source = MakeStatEnemy(1, 0, 0, 0); auto target = MakeStatEnemy(2, 0, 100, 0); REQUIRE_EQ(0, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_none, true)); } SUBCASE("enemy 9999/0 -> enemy 0/0") { auto source = MakeStatEnemy(1, 9999, 0, 0); auto target = MakeStatEnemy(2, 0, 0, 0); REQUIRE_EQ(4999, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_none, true)); } SUBCASE("actor 120/0 -> enemy 0/90") { auto source = MakeStatActor(1, 120, 0, 0); auto target = MakeStatEnemy(1, 0, 90, 0); REQUIRE_EQ(source.GetAtk(), 120); REQUIRE_EQ(target.GetDef(), 90); SUBCASE("front row") { source.SetBattleRow(lcf::rpg::SaveActor::RowType_front); if (is2k3) { testActorAttackRow(source, target, 47, 28, 35, 38, 38, 47); } else { testActorAttackRow(source, target, 38, 38, 38, 38, 38, 38); } } SUBCASE("back row") { source.SetBattleRow(lcf::rpg::SaveActor::RowType_back); if (is2k3) { testActorAttackRow(source, target, 38, 35, 35, 38, 47, 47); } else { testActorAttackRow(source, target, 38, 38, 38, 38, 38, 38); } } SUBCASE("weapons and attributes") { MakeDBEquip(1, lcf::rpg::Item::Type_weapon, 20, 0, 0, 0, 0); MakeDBEquip(2, lcf::rpg::Item::Type_weapon, 0, 0, 0, 0, 0); SetDBItemAttribute(2, 1, true); REQUIRE(lcf::Data::items[1].attribute_set[0]); source.SetEquipment(1, 1); source.SetEquipment(2, 2); REQUIRE_EQ(source.GetWeaponId(), 1); REQUIRE_EQ(source.GetShieldId(), 2); if (is2k3) { REQUIRE_EQ(140, source.GetAtk(Game_Battler::WeaponAll)); REQUIRE_EQ(120, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(47, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponNone, false, false, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(60, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponPrimary, false, false, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(94, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponSecondary, false, false, lcf::rpg::System::BattleCondition_none, true)); } else { REQUIRE_EQ(140, source.GetAtk(Game_Battler::WeaponAll)); REQUIRE_EQ(96, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, false, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(38, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponNone, false, false, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(48, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponPrimary, false, false, lcf::rpg::System::BattleCondition_none, true)); REQUIRE_EQ(76, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponSecondary, false, false, lcf::rpg::System::BattleCondition_none, true)); } } } SUBCASE("actor 120/0 -> actor 0/90") { auto source = MakeStatActor(1, 120, 0, 0); auto target = MakeStatActor(2, 0, 90, 0); REQUIRE_EQ(source.GetAtk(), 120); REQUIRE_EQ(target.GetDef(), 90); SUBCASE("source front") { source.SetBattleRow(lcf::rpg::SaveActor::RowType_front); SUBCASE("target front") { target.SetBattleRow(lcf::rpg::SaveActor::RowType_front); if (is2k3) { testActorAttackRow(source, target, 47, 28, 35, 38, 28, 35); } else { testActorAttackRow(source, target, 38, 38, 38, 38, 38, 38); } } SUBCASE("target back") { target.SetBattleRow(lcf::rpg::SaveActor::RowType_back); if (is2k3) { testActorAttackRow(source, target, 35, 38, 35, 38, 38, 35); } else { testActorAttackRow(source, target, 38, 38, 38, 38, 38, 38); } } } SUBCASE("source back") { source.SetBattleRow(lcf::rpg::SaveActor::RowType_back); SUBCASE("target front") { target.SetBattleRow(lcf::rpg::SaveActor::RowType_front); if (is2k3) { testActorAttackRow(source, target, 38, 35, 35, 38, 35, 35); } else { testActorAttackRow(source, target, 38, 38, 38, 38, 38, 38); } } SUBCASE("target back") { target.SetBattleRow(lcf::rpg::SaveActor::RowType_back); if (is2k3) { testActorAttackRow(source, target, 28, 47, 35, 38, 47, 35); } else { testActorAttackRow(source, target, 38, 38, 38, 38, 38, 38); } } } } SUBCASE("enemy 120/0 -> actor 0/90") { auto source = MakeStatEnemy(1, 120, 0, 0); auto target = MakeStatActor(1, 0, 90, 0); REQUIRE_EQ(source.GetAtk(), 120); REQUIRE_EQ(target.GetDef(), 90); SUBCASE("target front") { target.SetBattleRow(lcf::rpg::SaveActor::RowType_front); if (is2k3) { testActorAttackRow(source, target, 38, 28, 28, 38, 28, 28); } else { testActorAttackRow(source, target, 38, 38, 38, 38, 38, 38); } } SUBCASE("target back") { target.SetBattleRow(lcf::rpg::SaveActor::RowType_back); if (is2k3) { testActorAttackRow(source, target, 28, 38, 28, 38, 38, 28); } else { testActorAttackRow(source, target, 38, 38, 38, 38, 38, 38); } } } } TEST_CASE("NormalAttackEffect") { SUBCASE("2k") { TestNormalAttack(Player::EngineRpg2k | Player::EngineEnglish); } SUBCASE("2k3") { TestNormalAttack(Player::EngineRpg2k3 | Player::EngineEnglish); } } TEST_CASE("NormalAttackVariance") { const MockActor m; SUBCASE("enemy 120/0 -> enemy 0/90") { auto source = MakeStatEnemy(1, 120, 0, 0); auto target = MakeStatEnemy(2, 0, 90, 0); REQUIRE_EQ(source.GetAtk(), 120); REQUIRE_EQ(target.GetDef(), 90); SUBCASE("max") { Rand::LockGuard lk(INT32_MAX); REQUIRE_EQ(46, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, true, lcf::rpg::System::BattleCondition_none, false)); } SUBCASE("min") { Rand::LockGuard lk(INT32_MIN); REQUIRE_EQ(31, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, true, lcf::rpg::System::BattleCondition_none, false)); } } } static void testSkillVar(Game_Battler& source, Game_Battler& target, int var, int dmg_low, int dmg_high, int heal_low, int heal_high) { CAPTURE(var); auto* skill1 = MakeDBSkill(1, 100, 10, 10, 10, var); skill1->scope = lcf::rpg::Skill::Scope_enemy; auto* skill2 = MakeDBSkill(2, 100, 10, 10, 10, var); skill2->scope = lcf::rpg::Skill::Scope_ally; SUBCASE("max") { Rand::LockGuard lk(INT32_MAX); REQUIRE_EQ(dmg_high, Algo::CalcSkillEffect(source, target, *skill1, true)); REQUIRE_EQ(heal_high, Algo::CalcSkillEffect(source, target, *skill2, true)); } SUBCASE("min") { Rand::LockGuard lk(INT32_MIN); REQUIRE_EQ(dmg_low, Algo::CalcSkillEffect(source, target, *skill1, true)); REQUIRE_EQ(heal_low, Algo::CalcSkillEffect(source, target, *skill2, true)); } } TEST_CASE("SkillEffectVariance") { const MockActor m; auto source = MakeStatActor(1, 100, 0, 120); auto target = MakeStatActor(1, 0, 100, 90); SUBCASE("0") { testSkillVar(source, target, 0, 54, 54, 90, 90); } SUBCASE("1") { testSkillVar(source, target, 1, 52, 57, 86, 95); } SUBCASE("2") { testSkillVar(source, target, 2, 49, 59, 81, 99); } SUBCASE("10") { testSkillVar(source, target, 10, 27, 81, 45, 135); } } TEST_CASE("SelfDestructVariance") { const MockActor m; auto target = MakeStatActor(1, 0, 100, 0); auto source = MakeStatEnemy(1, 150, 0, 0); SUBCASE("max") { Rand::LockGuard lk(INT32_MAX); REQUIRE_EQ(120, Algo::CalcSelfDestructEffect(source, target, true)); } SUBCASE("min") { Rand::LockGuard lk(INT32_MIN); REQUIRE_EQ(80, Algo::CalcSelfDestructEffect(source, target, true)); } } TEST_CASE("SkillCost") { const MockActor m; auto* skill = MakeDBSkill(1, 90, 0, 0, 0, 0); SUBCASE("9") { skill->sp_cost = 9; skill->sp_type = lcf::rpg::Skill::SpType_cost; REQUIRE_EQ(9, Algo::CalcSkillCost(*skill, 100, false)); REQUIRE_EQ(5, Algo::CalcSkillCost(*skill, 100, true)); } SUBCASE("10") { skill->sp_cost = 10; skill->sp_type = lcf::rpg::Skill::SpType_cost; REQUIRE_EQ(10, Algo::CalcSkillCost(*skill, 100, false)); REQUIRE_EQ(5, Algo::CalcSkillCost(*skill, 100, true)); } SUBCASE("49%") { skill->sp_percent = 49; skill->sp_type = lcf::rpg::Skill::SpType_percent; REQUIRE_EQ(49, Algo::CalcSkillCost(*skill, 100, false)); REQUIRE_EQ(24, Algo::CalcSkillCost(*skill, 100, true)); } SUBCASE("50%") { skill->sp_percent = 50; skill->sp_type = lcf::rpg::Skill::SpType_percent; REQUIRE_EQ(50, Algo::CalcSkillCost(*skill, 100, false)); REQUIRE_EQ(25, Algo::CalcSkillCost(*skill, 100, true)); } } TEST_SUITE_END();