241 lines
7.7 KiB
C++
241 lines
7.7 KiB
C++
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
#include "ScriptTest.h"
|
|
#include "common/Capability.h"
|
|
#include "common/ECS.h"
|
|
#include "weapon/Weapon.h"
|
|
#include "weapon/WeaponLogic.h"
|
|
|
|
using namespace eve::weapon;
|
|
|
|
namespace {
|
|
|
|
const char* kDefs = R"JSON(
|
|
[{"id":"rifle","logic":"hitscan","damage":25,"range":500,"spread":0.5,
|
|
"fireMode":"single","cooldown":0.2,
|
|
"ammo":{"mag":30,"reserve":90,"reload":2.0}},
|
|
{"id":"cannon","logic":"projectile","damage":320,"penetration":260,"range":1200,
|
|
"fireMode":"single","cooldown":4.0,
|
|
"ammo":{"mag":1,"reserve":40,"reload":6.0},
|
|
"projectile":{"type":"shell","speed":900,"gravity":1.0,"aoe":3.0}},
|
|
{"id":"smg","logic":"hitscan","fireMode":"burst","cooldown":0.05,
|
|
"burst":{"size":3,"interval":0.06},
|
|
"ammo":{"mag":30,"reserve":-1,"reload":1.0}},
|
|
{"id":"noreload","logic":"hitscan","ammo":{"mag":5,"reserve":0,"reload":1.0}}]
|
|
)JSON";
|
|
|
|
int countWeaponView() {
|
|
int n = 0;
|
|
auto view = ecs::View<WeaponEntity, WeaponEntity::Identity>();
|
|
for (auto it = view.begin(); it != view.end(); ++it) ++n;
|
|
return n;
|
|
}
|
|
|
|
class StubProjectileService : public IProjectileService {
|
|
public:
|
|
int spawns = 0;
|
|
void spawnProjectile(const WeaponEntity&, const FireRequest&) override { ++spawns; }
|
|
};
|
|
|
|
class CountingLogic : public IWeaponLogic {
|
|
public:
|
|
int fires = 0;
|
|
const char* name() const override { return "test.counting"; }
|
|
bool canFire(const WeaponEntity&) const override { return true; }
|
|
void fire(WeaponEntity&, const FireRequest&) override { ++fires; }
|
|
void update(WeaponEntity&, float) override {}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("weapon.defs.registerJson") {
|
|
Weapon mod;
|
|
CHECK_EQ(mod.registerWeaponsFromJson(kDefs), 4);
|
|
CHECK(mod.hasWeaponDefinition("cannon"));
|
|
CHECK_EQ(mod.getWeaponDefinitionLogic("cannon"), std::string("projectile"));
|
|
CHECK_EQ(mod.getWeaponDefinitionDamage("cannon"), 320.f);
|
|
CHECK_EQ(mod.getWeaponDefinitionRange("cannon"), 1200.f);
|
|
CHECK_EQ(mod.registerWeaponsFromJson("[{\"name\":\"no-id\"}]"), 0);
|
|
CHECK_EQ(mod.registerWeaponsFromJson("not json"), 0);
|
|
CHECK_EQ(mod.getWeaponDefinitionLogic("missing"), std::string{});
|
|
}
|
|
|
|
TEST_CASE("weapon.factory.newWeapon") {
|
|
Weapon mod;
|
|
mod.registerWeaponsFromJson(kDefs);
|
|
const int before = countWeaponView();
|
|
|
|
WeaponEntity* w = mod.newWeapon("cannon");
|
|
REQUIRE(w != nullptr);
|
|
CHECK_EQ(w->state()->magAmmo, 1);
|
|
CHECK_EQ(w->state()->reserveAmmo, 40);
|
|
CHECK_EQ(w->identity()->defId, std::string("cannon"));
|
|
CHECK_EQ(mod.newWeapon("missing"), nullptr);
|
|
CHECK_EQ(countWeaponView(), before + 1);
|
|
}
|
|
|
|
TEST_CASE("weapon.fire.cooldownAndAmmo") {
|
|
Weapon mod;
|
|
mod.registerWeaponsFromJson(kDefs);
|
|
WeaponEntity* w = mod.newWeapon("rifle");
|
|
REQUIRE(w != nullptr);
|
|
mod.clearEvents();
|
|
|
|
CHECK(mod.fireAt(w, 100.f, 0.f, 0.f));
|
|
CHECK_EQ(w->state()->magAmmo, 29);
|
|
CHECK_EQ(mod.getEventCount(), 1);
|
|
CHECK_EQ(mod.getEventType(0), std::string("fire"));
|
|
|
|
CHECK(!mod.fireAt(w, 100.f, 0.f, 0.f)); // 冷却中
|
|
mod.update(0.1f);
|
|
CHECK(!mod.fireAt(w, 100.f, 0.f, 0.f));
|
|
mod.update(0.15f); // 累计 0.25s > cooldown 0.2s
|
|
CHECK(mod.fireAt(w, 100.f, 0.f, 0.f));
|
|
CHECK_EQ(w->state()->magAmmo, 28);
|
|
}
|
|
|
|
TEST_CASE("weapon.fire.emptyAndReload") {
|
|
Weapon mod;
|
|
mod.registerWeaponsFromJson(kDefs);
|
|
WeaponEntity* w = mod.newWeapon("cannon");
|
|
REQUIRE(w != nullptr);
|
|
mod.clearEvents();
|
|
|
|
CHECK(mod.fireAt(w, 0.f, 0.f, 0.f));
|
|
CHECK_EQ(w->state()->magAmmo, 0);
|
|
CHECK_EQ(mod.getEventCount(), 2);
|
|
CHECK_EQ(mod.getEventType(0), std::string("fire"));
|
|
CHECK_EQ(mod.getEventType(1), std::string("empty"));
|
|
|
|
mod.clearEvents();
|
|
mod.startReload(w);
|
|
CHECK(w->state()->reloading);
|
|
CHECK_EQ(mod.getEventCount(), 1);
|
|
CHECK_EQ(mod.getEventType(0), std::string("reload_start"));
|
|
|
|
mod.update(3.0f);
|
|
CHECK(w->state()->reloading);
|
|
mod.update(3.0f); // 累计 6s = reloadTime
|
|
CHECK(!w->state()->reloading);
|
|
CHECK_EQ(w->state()->magAmmo, 1);
|
|
CHECK_EQ(w->state()->reserveAmmo, 39);
|
|
CHECK_EQ(mod.getEventCount(), 2);
|
|
CHECK_EQ(mod.getEventType(1), std::string("reload_end"));
|
|
}
|
|
|
|
TEST_CASE("weapon.fire.noReserveCannotReload") {
|
|
Weapon mod;
|
|
mod.registerWeaponsFromJson(kDefs);
|
|
WeaponEntity* w = mod.newWeapon("noreload");
|
|
REQUIRE(w != nullptr);
|
|
for (int i = 0; i < 5; ++i) CHECK(mod.fireAt(w, 0.f, 0.f, 0.f));
|
|
CHECK_EQ(w->state()->magAmmo, 0);
|
|
mod.clearEvents();
|
|
mod.startReload(w);
|
|
CHECK(!w->state()->reloading);
|
|
CHECK_EQ(mod.getEventCount(), 0);
|
|
}
|
|
|
|
TEST_CASE("weapon.fire.burst") {
|
|
Weapon mod;
|
|
mod.registerWeaponsFromJson(kDefs);
|
|
WeaponEntity* w = mod.newWeapon("smg");
|
|
REQUIRE(w != nullptr);
|
|
mod.clearEvents();
|
|
|
|
CHECK(mod.fireAt(w, 0.f, 0.f, 0.f));
|
|
CHECK_EQ(w->state()->magAmmo, 29);
|
|
mod.update(0.07f); // 续发 2
|
|
mod.update(0.07f); // 续发 3
|
|
CHECK_EQ(w->state()->magAmmo, 27);
|
|
CHECK_EQ(mod.getEventCount(), 3);
|
|
for (int i = 0; i < 3; ++i) CHECK_EQ(mod.getEventType(i), std::string("fire"));
|
|
}
|
|
|
|
TEST_CASE("weapon.logic.customRegistration") {
|
|
CountingLogic logic;
|
|
Weapon::registerLogic(&logic);
|
|
CHECK(Weapon::getLogicCount() >= 3);
|
|
|
|
Weapon mod;
|
|
CHECK_EQ(mod.registerWeaponsFromJson("[{\"id\":\"count\",\"logic\":\"test.counting\"}]"), 1);
|
|
WeaponEntity* w = mod.newWeapon("count");
|
|
REQUIRE(w != nullptr);
|
|
CHECK(mod.fireAt(w, 0.f, 0.f, 0.f));
|
|
CHECK_EQ(logic.fires, 1);
|
|
}
|
|
|
|
TEST_CASE("weapon.projectile.usesService") {
|
|
StubProjectileService svc;
|
|
eve::cap::provide<IProjectileService>(&svc);
|
|
|
|
Weapon mod;
|
|
mod.registerWeaponsFromJson(kDefs);
|
|
WeaponEntity* cannon = mod.newWeapon("cannon");
|
|
WeaponEntity* rifle = mod.newWeapon("rifle");
|
|
REQUIRE(cannon != nullptr);
|
|
REQUIRE(rifle != nullptr);
|
|
|
|
CHECK(mod.fireAt(cannon, 0.f, 0.f, 0.f));
|
|
CHECK_EQ(svc.spawns, 1);
|
|
CHECK(mod.fireAt(rifle, 0.f, 0.f, 0.f));
|
|
CHECK_EQ(svc.spawns, 1); // 直射不生成投射物
|
|
|
|
eve::cap::revoke<IProjectileService>(&svc);
|
|
}
|
|
|
|
TEST_CASE("weapon.mount.limitsAndAim") {
|
|
Weapon mod;
|
|
mod.registerWeaponsFromJson(kDefs);
|
|
WeaponMountEntity* m = mod.newMount("turret");
|
|
REQUIRE(m != nullptr);
|
|
mod.mountSetLimits(m, -90.f, 90.f, -10.f, 20.f, 45.f, 0.f);
|
|
|
|
WeaponEntity* w = mod.newWeapon("cannon");
|
|
CHECK(mod.mountAttachWeapon(m, w));
|
|
mod.mountAimAt(m, 200.f, 50.f);
|
|
CHECK_EQ(w->aim()->desiredYaw, 90.f); // 夹到限位
|
|
CHECK_EQ(w->aim()->desiredPitch, 20.f);
|
|
|
|
mod.update(1.0f); // turnSpeed 45°/s
|
|
CHECK_EQ(w->aim()->yaw, 45.f);
|
|
mod.update(1.0f);
|
|
CHECK_EQ(w->aim()->yaw, 90.f);
|
|
CHECK_EQ(m->state()->yaw, 90.f); // 挂点跟随武器
|
|
|
|
mod.mountDestroy(m);
|
|
mod.mountAimAt(m, 0.f, 0.f);
|
|
CHECK_EQ(w->aim()->desiredYaw, 90.f); // 已毁挂点不再瞄准
|
|
}
|
|
|
|
TEST_CASE("weapon.mount.aimsWithoutWeapon") {
|
|
Weapon mod;
|
|
WeaponMountEntity* m = mod.newMount("pintle");
|
|
REQUIRE(m != nullptr);
|
|
mod.mountSetLimits(m, -45.f, 45.f, -5.f, 5.f, 0.f, 0.f);
|
|
mod.mountAimAt(m, 100.f, 20.f);
|
|
CHECK_EQ(m->state()->yaw, 45.f);
|
|
CHECK_EQ(m->state()->pitch, 5.f);
|
|
}
|
|
|
|
static const char* kViewScript = R"SQ(
|
|
function testWeaponCppView() {
|
|
local weapon = eve.Weapon()
|
|
weapon.registerWeaponsFromJson("[{\"id\":\"pistol\",\"ammo\":{\"mag\":12}}]")
|
|
local w = weapon.newWeapon("pistol")
|
|
if (w == null) return false
|
|
local all = eve.view(eve.WeaponEntity)
|
|
foreach (e in all) {
|
|
if (e.getId() == w.getId()) return true
|
|
}
|
|
return false
|
|
}
|
|
)SQ";
|
|
|
|
UnitSciptTest(WeaponViewScriptTest, kViewScript);
|
|
|
|
TEST_CASE_FIXTURE(WeaponViewScriptTest, "weapon.script.viewSeesNewWeapon") {
|
|
CHECK(vm.callFunc(vm.findFunc("testWeaponCppView"), vm).toBool());
|
|
}
|