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.
95 lines
3.4 KiB
C++
95 lines
3.4 KiB
C++
#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();
|
||
} |