Files
dota_factory/src/test/ShipyardTest.cpp

215 lines
5.9 KiB
C++

#include "catch.hpp"
#include "Building.h"
#include "BuildingSystem.h"
#include "BuildingType.h"
#include "ConfigLoader.h"
#include "EntityAdmin.h"
#include "FactionComponent.h"
#include "GameConfig.h"
#include "ItemType.h"
#include "Rotation.h"
#include "ShipIdentityComponent.h"
#include "ShipSystem.h"
#include "Simulation.h"
#include "Tick.h"
static GameConfig loadConfig()
{
return ConfigLoader::loadFromDirectory(CONFIG_DIR);
}
static const ShipDef* findAvailableSchematic(const GameConfig& cfg)
{
for (const ShipDef& def : cfg.ships.ships)
{
if (def.unlockAtStationLevel == -1 && !def.schematic.materials.empty())
{
return &def;
}
}
return nullptr;
}
static const BuildingDef* findShipyardDef(const GameConfig& cfg)
{
for (const BuildingDef& def : cfg.buildings.buildings)
{
if (def.type == BuildingType::Shipyard)
{
return &def;
}
}
return nullptr;
}
static BuildingId placeShipyard(Simulation& sim, const BuildingDef& yardDef)
{
return sim.buildings().placeImmediate(
BuildingType::Shipyard,
yardDef.surfaceMask,
QPoint(0, 0),
Rotation::East);
}
static int countShips(Simulation& sim)
{
int n = 0;
sim.admin().forEach<ShipIdentityComponent>(
[&n](entt::entity /*e*/, const ShipIdentityComponent& /*si*/) { ++n; });
return n;
}
static void fillMaterials(Simulation& sim, BuildingId yardId, const ShipDef& def)
{
sim.buildings().forEachBuilding([&](Building& b)
{
if (b.id != yardId)
{
return;
}
for (const RecipeIngredient& ing : def.schematic.materials)
{
b.inputBuffer.counts[ItemType{ing.item}] = ing.amount;
}
});
}
// ---------------------------------------------------------------------------
// Shipyard production
// ---------------------------------------------------------------------------
TEST_CASE("Shipyard: spawns a player ship after production cycle completes",
"[shipyard]")
{
Simulation sim(loadConfig(), 42);
const ShipDef* def = findAvailableSchematic(sim.config());
REQUIRE(def != nullptr);
const BuildingDef* yardDef = findShipyardDef(sim.config());
REQUIRE(yardDef != nullptr);
const int shipsBefore = countShips(sim);
const BuildingId yardId = placeShipyard(sim, *yardDef);
REQUIRE(yardId != kInvalidBuildingId);
sim.buildings().setRecipe(yardId, def->id);
fillMaterials(sim, yardId, *def);
// First tick: materials consumed, production cycle starts — no ship yet.
sim.tick();
REQUIRE(countShips(sim) == shipsBefore);
// Tick until the cycle completes.
const Tick cycleTicks = secondsToTicks(def->schematic.productionTimeSeconds);
for (Tick i = 1; i < cycleTicks; ++i)
{
sim.tick();
}
REQUIRE(countShips(sim) == shipsBefore);
// Final tick: cycle completes, ship spawns.
sim.tick();
REQUIRE(countShips(sim) == shipsBefore + 1);
bool foundPlayerShip = false;
sim.admin().forEach<ShipIdentityComponent, FactionComponent>(
[&](entt::entity /*e*/, const ShipIdentityComponent& si, const FactionComponent& f)
{
if (!f.isEnemy && si.schematicId == def->id)
{
foundPlayerShip = true;
}
});
REQUIRE(foundPlayerShip);
}
TEST_CASE("Shipyard: does not spawn without a schematic set", "[shipyard]")
{
Simulation sim(loadConfig(), 42);
const BuildingDef* yardDef = findShipyardDef(sim.config());
REQUIRE(yardDef != nullptr);
const int shipsBefore = countShips(sim);
placeShipyard(sim, *yardDef);
sim.tick();
REQUIRE(countShips(sim) == shipsBefore);
}
TEST_CASE("Shipyard: does not spawn with insufficient materials", "[shipyard]")
{
Simulation sim(loadConfig(), 42);
const ShipDef* def = findAvailableSchematic(sim.config());
REQUIRE(def != nullptr);
const BuildingDef* yardDef = findShipyardDef(sim.config());
REQUIRE(yardDef != nullptr);
const int shipsBefore = countShips(sim);
const BuildingId yardId = placeShipyard(sim, *yardDef);
sim.buildings().setRecipe(yardId, def->id);
// Materials remain at zero (default after setRecipe); no cycle starts.
const Tick cycleTicks = secondsToTicks(def->schematic.productionTimeSeconds);
for (Tick i = 0; i <= cycleTicks; ++i)
{
sim.tick();
}
REQUIRE(countShips(sim) == shipsBefore);
}
TEST_CASE("Shipyard: spawns a second ship after materials replenished", "[shipyard]")
{
Simulation sim(loadConfig(), 42);
const ShipDef* def = findAvailableSchematic(sim.config());
REQUIRE(def != nullptr);
const BuildingDef* yardDef = findShipyardDef(sim.config());
REQUIRE(yardDef != nullptr);
const BuildingId yardId = placeShipyard(sim, *yardDef);
sim.buildings().setRecipe(yardId, def->id);
const Tick cycleTicks = secondsToTicks(def->schematic.productionTimeSeconds);
// First cycle: capture count immediately after the spawn tick.
fillMaterials(sim, yardId, *def);
for (Tick i = 0; i <= cycleTicks; ++i)
{
sim.tick();
}
const int after1 = countShips(sim);
// Second cycle: capture count immediately after the next spawn tick.
fillMaterials(sim, yardId, *def);
for (Tick i = 0; i <= cycleTicks; ++i)
{
sim.tick();
}
const int after2 = countShips(sim);
// After each cycle one ship was added; ships from prior cycles may have died
// from enemy fire, so we only assert the most-recent spawn is still present.
REQUIRE(after2 >= 1);
// Verify the shipyard production field cleared (i.e. the cycle completed
// and is not still running).
bool productionCleared = false;
for (const Building& b : sim.buildings().allBuildings())
{
if (b.id == yardId)
{
productionCleared = !b.production.has_value();
break;
}
}
REQUIRE(productionCleared);
}