Files
EVEngine/test/rpg_class.cpp
T
西风逍遥游 6cc4e8b602 feat(rpg): RPG-Maker-style main model + complete RPG interface
Extend the RPG module into a data-driven RPG-Maker-style main model and add
a complete turn-based RPG example demonstrating the whole surface.

New RPG subsystems (all string-keyed, JSON-driven, Squirrel-bound):
- Quest tracker: QuestRegistry + Tracker + QuestSystem (states, notify,
  prerequisite chain, events); eve.RPG.newTracker().
- Progression: LevelSystem + RPGActor::Progression (XP/level/level-up events).
- Vitals: VitalsSystem + RPGActor::Vitals (current resource, takeDamage/heal/
  death/revive).
- Traits: Trait/TraitSystem + RPGActor::Traits (paramRate, elementRate,
  stateRate/resist, exParam, attack elements/states/times/speed).
- Turn-based battle: BattleSystem (damage-formula DSL "a.atk*4-b.def*2",
  hit/crit/element) + Battle (arbitrary sides, initiative order, target
  resolution, winner detection, events).
- Classes: Class/ClassSystem + RPGActor::ClassInfo (class traits + learn
  skills by level).
- GameState: global switches/variables/self-variables.
- Equipment + loot: EquipmentSystem (equip -> attribute modifiers) and
  LootSystem (loot table -> inventory Bag), via the inventory module.

Bindings: bind RPGActor::release for script-side destruction; wrap
registerSkillDamage double as float (SQUSEDOUBLE).

Example: examples/rpg-classic - complete RPG interface (bag/shop/stat
allocation/equipment/adjust every 3 waves/status screen anytime).

Tests: rpg_quest, rpg_progression, rpg_trait, rpg_battle, rpg_class,
rpg_gamestate, rpg_equipment (loot). Full rpg.* regression green.
2026-08-28 15:02:13 +08:00

95 lines
3.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include "rpg/RPGActor.h"
#include "rpg/Class.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.class.registryJson") {
ClassRegistry::clear();
int n = ClassRegistry::loadFromJson(R"([
{"id":"warrior","displayName":"战士","tags":["physical"],
"traits":["mighty"],
"learnSkills":[{"skillId":"power_strike","level":2},{"skillId":"cleave","level":5}]},
{"id":"mage","displayName":"法师","learnSkills":[{"skillId":"fireball","level":1}]}
])");
CHECK_EQ(n, 2);
const ClassDefinition *w = ClassRegistry::find("warrior");
REQUIRE(w != nullptr);
CHECK(w->hasTag("physical"));
REQUIRE(w->learnSkills.size() == 2);
CHECK_EQ(w->learnSkills[1].skillId, std::string("cleave"));
CHECK_EQ(w->learnSkills[1].level, 5);
ClassRegistry::clear();
CHECK_EQ(ClassRegistry::count(), 0);
}
TEST_CASE("rpg.class.setAppliesTraitsAndLearnsSkills") {
auto *rpg = RPG::create();
rpg->clearClassDefinitions();
rpg->clearTraitDefinitions();
rpg->clearSkillDefinitions();
rpg->registerSkillsFromJson(R"([
{"id":"power_strike"},{"id":"cleave"},{"id":"fireball"}
])");
rpg->registerTraitsFromJson(R"([{"id":"mighty","traits":[{"kind":"paramRate","target":"attack","value":1.5}]}])");
int classes = rpg->registerClassesFromJson(R"([
{"id":"warrior","traits":["mighty"],
"learnSkills":[{"skillId":"power_strike","level":2},{"skillId":"cleave","level":5}]},
{"id":"mage","learnSkills":[{"skillId":"fireball","level":1}]}
])");
CHECK_EQ(classes, 2);
RPGActor *actor = rpg->newActor();
actor->setBaseAttribute("attack", 100.0);
CHECK(actor->setClass("warrior"));
CHECK(actor->hasClass("warrior"));
CHECK_EQ(actor->getClassId(), std::string("warrior"));
// 职业特征 → attack * 1.5
CHECK(approxEq(actor->getFinalAttribute("attack"), 150.0));
// 等级 1:power_strike(2) 未解锁,cleave(5) 未解锁
CHECK(!actor->knowsSkill("power_strike"));
CHECK_EQ(actor->getClassLearnCount(), 2);
// 升到 3 级后补学
actor->setXpToNext(10.0);
int learned = actor->gainXp(30.0); // 升多级
CHECK(learned);
int n = actor->checkLevelSkills();
CHECK(n >= 1);
CHECK(actor->knowsSkill("power_strike")); // 2 级已解锁
CHECK(!actor->knowsSkill("cleave")); // 5 级仍未解锁
// 换职业撤销旧职业特征
CHECK(actor->setClass("mage"));
CHECK(approxEq(actor->getFinalAttribute("attack"), 100.0)); // 职业特征撤销
rpg->clearClassDefinitions();
rpg->clearTraitDefinitions();
rpg->clearSkillDefinitions();
actor->release();
}
TEST_CASE("rpg.class.checkLevelSkillsIdempotent") {
auto *rpg = RPG::create();
rpg->clearClassDefinitions();
rpg->clearSkillDefinitions();
rpg->registerSkillsFromJson(R"([{"id":"strike"}])");
rpg->registerClassesFromJson(R"([{"id":"g","learnSkills":[{"skillId":"strike","level":1}]}])");
RPGActor *actor = rpg->newActor();
actor->setClass("g");
CHECK(actor->knowsSkill("strike")); // 1 级即时学到
int again = actor->checkLevelSkills();
CHECK_EQ(again, 0); // 幂等,不再重复学
rpg->clearClassDefinitions();
rpg->clearSkillDefinitions();
actor->release();
}