Files
EVEngine/test/rpg_progression.cpp

151 lines
4.7 KiB
C++

#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include "rpg/RPGActor.h"
#include "rpg/LevelSystem.h"
#include "rpg/VitalsSystem.h"
#include "rpg/RPG.h"
#include <cmath>
using namespace eve::rpg;
namespace {
bool approxEq(double a, double b, double eps = 1e-9) { return std::abs(a - b) < eps; }
} // namespace
TEST_CASE("rpg.progression.levelAndXp") {
RPGActor *actor = RPGActor::createActor();
REQUIRE(actor != nullptr);
CHECK_EQ(actor->getLevel(), 1);
CHECK_EQ(actor->getXp(), 0.0);
CHECK_EQ(actor->getXpToNext(), 100.0);
actor->setXpToNext(100.0);
actor->gainXp(50.0);
CHECK_EQ(actor->getLevel(), 1);
CHECK(approxEq(actor->getXp(), 50.0));
actor->gainXp(60.0); // 110 -> 升 1 级,剩 10
CHECK_EQ(actor->getLevel(), 2);
CHECK(approxEq(actor->getXp(), 10.0));
CHECK(approxEq(actor->getXpToNext(), 120.0)); // 100 * 1.2
actor->release();
}
TEST_CASE("rpg.progression.levelUpEventsViaFacade") {
auto *rpg = RPG::create();
rpg->update(0.f); // 清掉其它用例遗留的静态事件
RPGActor *actor = rpg->newActor();
REQUIRE(actor != nullptr);
actor->setXpToNext(50.0);
CHECK(!actor->gainXp(20.0));
CHECK(actor->gainXp(50.0)); // 升到 2
rpg->update(0.f);
CHECK(rpg->getLevelUpEventCount() == 1);
CHECK(rpg->getLevelUpEventActor(0) == actor);
CHECK_EQ(rpg->getLevelUpEventPreviousLevel(0), 1);
CHECK_EQ(rpg->getLevelUpEventNewLevel(0), 2);
actor->release();
}
TEST_CASE("rpg.progression.multiLevelUpSingleGain") {
RPGActor *actor = RPGActor::createActor();
actor->setXpToNext(10.0);
CHECK(actor->gainXp(100.0)); // 连升多级
CHECK(actor->getLevel() > 1);
// 不变式:升级后经验必低于当前阈值
CHECK(actor->getXp() < actor->getXpToNext());
actor->release();
}
TEST_CASE("rpg.vitals.damageHealDeathRevive") {
RPGActor *actor = RPGActor::createActor();
actor->setBaseAttribute("hp", 100.0);
CHECK(approxEq(actor->getMax("hp"), 100.0));
actor->setCurrent("hp", 80.0);
CHECK(approxEq(actor->getCurrent("hp"), 80.0));
CHECK(!actor->isDead("hp"));
// 超出上限的 set 会被夹到 max
actor->setCurrent("hp", 999.0);
CHECK(approxEq(actor->getCurrent("hp"), 100.0));
// 伤害夹到 0,超量不把当前值打穿负数
actor->setCurrent("hp", 50.0);
double applied = actor->takeDamage("hp", 30.0, "slime");
CHECK(approxEq(applied, 30.0));
CHECK(approxEq(actor->getCurrent("hp"), 20.0));
applied = actor->takeDamage("hp", 999.0, "slime");
CHECK(approxEq(applied, 20.0));
CHECK(approxEq(actor->getCurrent("hp"), 0.0));
CHECK(actor->isDead("hp"));
// 死亡后再受伤不生效
applied = actor->takeDamage("hp", 10.0, "slime");
CHECK(approxEq(applied, 0.0));
// 复活到上限
actor->revive("hp");
CHECK(approxEq(actor->getCurrent("hp"), 100.0));
CHECK(!actor->isDead("hp"));
// 治疗夹到 max
actor->setCurrent("hp", 30.0);
double healed = actor->heal("hp", 50.0);
CHECK(approxEq(healed, 50.0));
healed = actor->heal("hp", 999.0);
CHECK(approxEq(healed, 20.0));
CHECK(approxEq(actor->getCurrent("hp"), 100.0));
actor->release();
}
TEST_CASE("rpg.vitals.eventsViaFacade") {
auto *rpg = RPG::create();
rpg->update(0.f); // 清掉其它用例遗留的静态事件
RPGActor *actor = rpg->newActor();
actor->setBaseAttribute("hp", 100.0);
actor->setCurrent("hp", 100.0);
actor->takeDamage("hp", 100.0, "boss"); // 归零死亡
rpg->update(0.f);
CHECK_EQ(rpg->getVitalsEventCount(), 2); // damage + death
bool sawDamage = false, sawDeath = false;
for (int i = 0; i < rpg->getVitalsEventCount(); ++i) {
if (rpg->getVitalsEventAction(i) == "damage" && rpg->getVitalsEventResource(i) == "hp") {
sawDamage = true;
CHECK(rpg->getVitalsEventActor(i) == actor);
CHECK(approxEq(rpg->getVitalsEventAmount(i), 100.0));
CHECK_EQ(rpg->getVitalsEventSource(i), std::string("boss"));
}
if (rpg->getVitalsEventAction(i) == "death") sawDeath = true;
}
CHECK(sawDamage);
CHECK(sawDeath);
actor->release();
}
TEST_CASE("rpg.progression.checkpointRestoreIsValidatedAndAtomic") {
auto *actor = RPGActor::createActor();
REQUIRE(actor != nullptr);
actor->setXpToNext(50.0);
auto restored = actor->restoreProgression(4, 12.0, 86.4);
REQUIRE(restored.ok());
CHECK_EQ(actor->getLevel(), 4);
CHECK_EQ(actor->getXp(), 12.0);
CHECK_EQ(actor->getXpToNext(), 86.4);
auto invalid = actor->restoreProgression(0, 100.0, 10.0);
CHECK(!invalid.ok());
CHECK_EQ(actor->getLevel(), 4);
CHECK_EQ(actor->getXp(), 12.0);
CHECK_EQ(actor->getXpToNext(), 86.4);
actor->release();
}