implement scrap and ship skeleton
This commit is contained in:
@@ -9,4 +9,6 @@ add_files(
|
||||
BeltSystemTest.cpp
|
||||
SurfaceMaskTest.cpp
|
||||
BuildingTest.cpp
|
||||
ShipTest.cpp
|
||||
ScrapTest.cpp
|
||||
)
|
||||
|
||||
83
src/test/ScrapTest.cpp
Normal file
83
src/test/ScrapTest.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "catch.hpp"
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
#include "EntityId.h"
|
||||
#include "Scrap.h"
|
||||
#include "ScrapSystem.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Spawn
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("ScrapSystem: spawn returns a findable scrap with correct fields", "[scrap]")
|
||||
{
|
||||
EntityId nextId = 1;
|
||||
ScrapSystem ss([&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id = ss.spawn(QVector2D(3.0f, 4.0f), 5, 100);
|
||||
const Scrap* s = ss.findScrap(id);
|
||||
|
||||
REQUIRE(s != nullptr);
|
||||
REQUIRE(s->amount == 5);
|
||||
REQUIRE(s->despawnAt == 100);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Despawn timing
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("ScrapSystem: scrap still present one tick before despawnAt", "[scrap]")
|
||||
{
|
||||
EntityId nextId = 1;
|
||||
ScrapSystem ss([&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id = ss.spawn(QVector2D(0.0f, 0.0f), 1, 50);
|
||||
|
||||
ss.tickDespawn(49);
|
||||
REQUIRE(ss.findScrap(id) != nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("ScrapSystem: scrap removed at despawnAt tick", "[scrap]")
|
||||
{
|
||||
EntityId nextId = 1;
|
||||
ScrapSystem ss([&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id = ss.spawn(QVector2D(0.0f, 0.0f), 1, 50);
|
||||
|
||||
ss.tickDespawn(50);
|
||||
REQUIRE(ss.findScrap(id) == nullptr);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Selective removal
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("ScrapSystem: tickDespawn removes only expired scraps", "[scrap]")
|
||||
{
|
||||
EntityId nextId = 1;
|
||||
ScrapSystem ss([&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId earlyId = ss.spawn(QVector2D(0.0f, 0.0f), 1, 30);
|
||||
const EntityId lateId = ss.spawn(QVector2D(1.0f, 0.0f), 2, 60);
|
||||
|
||||
ss.tickDespawn(30);
|
||||
|
||||
REQUIRE(ss.findScrap(earlyId) == nullptr);
|
||||
REQUIRE(ss.findScrap(lateId) != nullptr);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Entity ids
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("ScrapSystem: spawned scraps receive strictly increasing entity ids", "[scrap]")
|
||||
{
|
||||
EntityId nextId = 1;
|
||||
ScrapSystem ss([&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id1 = ss.spawn(QVector2D(0.0f, 0.0f), 1, 100);
|
||||
const EntityId id2 = ss.spawn(QVector2D(1.0f, 0.0f), 2, 200);
|
||||
|
||||
REQUIRE(id2 > id1);
|
||||
}
|
||||
194
src/test/ShipTest.cpp
Normal file
194
src/test/ShipTest.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
#include "catch.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QVector2D>
|
||||
|
||||
#include "ConfigLoader.h"
|
||||
#include "EntityId.h"
|
||||
#include "Ship.h"
|
||||
#include "ShipSystem.h"
|
||||
#include "Tick.h"
|
||||
|
||||
static GameConfig loadConfig()
|
||||
{
|
||||
return ConfigLoader::loadFromDirectory(DOTA_FACTORY_CONFIG_DIR);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Combat ship
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("ShipSystem: interceptor spawn has weapon and threatResponse, no cargo or repair",
|
||||
"[ship]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
EntityId nextId = 1;
|
||||
ShipSystem ss(cfg, [&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id = ss.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||
const Ship* ship = ss.findShip(id);
|
||||
|
||||
REQUIRE(ship != nullptr);
|
||||
REQUIRE(ship->weapon.has_value());
|
||||
REQUIRE(ship->threatResponse.has_value());
|
||||
REQUIRE_FALSE(ship->cargo.has_value());
|
||||
REQUIRE_FALSE(ship->repairTool.has_value());
|
||||
REQUIRE_FALSE(ship->repairBehavior.has_value());
|
||||
REQUIRE_FALSE(ship->scrapCollector.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("ShipSystem: interceptor level 1 stats match config formulas", "[ship]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
EntityId nextId = 1;
|
||||
ShipSystem ss(cfg, [&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id = ss.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||
const Ship* ship = ss.findShip(id);
|
||||
|
||||
REQUIRE(ship != nullptr);
|
||||
// hp_formula = "40 + 5*x" at x=1 → 45
|
||||
REQUIRE(ship->maxHp == Approx(45.0f));
|
||||
REQUIRE(ship->hp == Approx(45.0f));
|
||||
// damage_formula = "10 + 2*x" at x=1 → 12
|
||||
REQUIRE(ship->weapon->damage == Approx(12.0f));
|
||||
// attack_range_formula = "150"
|
||||
REQUIRE(ship->weapon->range == Approx(150.0f));
|
||||
// threatResponse.engagementRange mirrors weapon range
|
||||
REQUIRE(ship->threatResponse->engagementRange == Approx(150.0f));
|
||||
// cooldownTicks starts at 0
|
||||
REQUIRE(ship->weapon->cooldownTicks == Approx(0.0f));
|
||||
}
|
||||
|
||||
TEST_CASE("ShipSystem: interceptor level 5 hp matches formula", "[ship]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
EntityId nextId = 1;
|
||||
ShipSystem ss(cfg, [&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id = ss.spawn("interceptor", 5, QVector2D(0.0f, 0.0f));
|
||||
const Ship* ship = ss.findShip(id);
|
||||
|
||||
// hp_formula = "40 + 5*x" at x=5 → 65
|
||||
REQUIRE(ship->maxHp == Approx(65.0f));
|
||||
}
|
||||
|
||||
TEST_CASE("ShipSystem: interceptor level 0 speedPerTick matches formula / kTickRateHz", "[ship]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
EntityId nextId = 1;
|
||||
ShipSystem ss(cfg, [&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id = ss.spawn("interceptor", 0, QVector2D(0.0f, 0.0f));
|
||||
const Ship* ship = ss.findShip(id);
|
||||
|
||||
// speed_formula = "200 + 5*x" at x=0 → 200; speedPerTick = 200/30
|
||||
const float expected = 200.0f / static_cast<float>(kTickRateHz);
|
||||
REQUIRE(ship->speedPerTick == Approx(expected));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Salvage ship
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("ShipSystem: salvage_ship spawn has cargo and scrapCollector, no weapon",
|
||||
"[ship]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
EntityId nextId = 1;
|
||||
ShipSystem ss(cfg, [&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id = ss.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f));
|
||||
const Ship* ship = ss.findShip(id);
|
||||
|
||||
REQUIRE(ship != nullptr);
|
||||
REQUIRE(ship->cargo.has_value());
|
||||
REQUIRE(ship->scrapCollector.has_value());
|
||||
REQUIRE_FALSE(ship->weapon.has_value());
|
||||
REQUIRE_FALSE(ship->repairTool.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("ShipSystem: salvage_ship cargo capacity matches config", "[ship]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
EntityId nextId = 1;
|
||||
ShipSystem ss(cfg, [&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id = ss.spawn("salvage_ship", 1, QVector2D(0.0f, 0.0f));
|
||||
const Ship* ship = ss.findShip(id);
|
||||
|
||||
// cargo_capacity = 10
|
||||
REQUIRE(ship->cargo->capacity == 10);
|
||||
REQUIRE(ship->cargo->current == 0);
|
||||
REQUIRE(ship->scrapCollector->deliveryBay == kInvalidEntityId);
|
||||
REQUIRE_FALSE(ship->scrapCollector->scrapTarget.has_value());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Repair ship
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("ShipSystem: repair_ship spawn has repairTool and repairBehavior, no weapon",
|
||||
"[ship]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
EntityId nextId = 1;
|
||||
ShipSystem ss(cfg, [&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id = ss.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
||||
const Ship* ship = ss.findShip(id);
|
||||
|
||||
REQUIRE(ship != nullptr);
|
||||
REQUIRE(ship->repairTool.has_value());
|
||||
REQUIRE(ship->repairBehavior.has_value());
|
||||
REQUIRE_FALSE(ship->weapon.has_value());
|
||||
REQUIRE_FALSE(ship->cargo.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("ShipSystem: repair_ship level 1 repair stats match config formulas", "[ship]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
EntityId nextId = 1;
|
||||
ShipSystem ss(cfg, [&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id = ss.spawn("repair_ship", 1, QVector2D(0.0f, 0.0f));
|
||||
const Ship* ship = ss.findShip(id);
|
||||
|
||||
// repair_rate_formula = "5 + x" at x=1 → 6
|
||||
REQUIRE(ship->repairTool->ratePerTick == Approx(6.0f));
|
||||
// repair_range_formula = "80"
|
||||
REQUIRE(ship->repairTool->range == Approx(80.0f));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Entity ids and removal
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("ShipSystem: spawned ships receive strictly increasing entity ids", "[ship]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
EntityId nextId = 1;
|
||||
ShipSystem ss(cfg, [&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id1 = ss.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||
const EntityId id2 = ss.spawn("salvage_ship", 1, QVector2D(1.0f, 0.0f));
|
||||
|
||||
REQUIRE(id2 > id1);
|
||||
}
|
||||
|
||||
TEST_CASE("ShipSystem: despawn removes the ship", "[ship]")
|
||||
{
|
||||
const GameConfig cfg = loadConfig();
|
||||
EntityId nextId = 1;
|
||||
ShipSystem ss(cfg, [&nextId]() { return nextId++; });
|
||||
|
||||
const EntityId id = ss.spawn("interceptor", 1, QVector2D(0.0f, 0.0f));
|
||||
REQUIRE(ss.findShip(id) != nullptr);
|
||||
|
||||
ss.despawn(id);
|
||||
REQUIRE(ss.findShip(id) == nullptr);
|
||||
}
|
||||
Reference in New Issue
Block a user