diff --git a/SetupThrive.rb b/SetupThrive.rb index 23a602a6d11..30270d2fe5d 100755 --- a/SetupThrive.rb +++ b/SetupThrive.rb @@ -78,7 +78,7 @@ def parseExtraArgs leviathan = Leviathan.new( # Use this if you always want the latest commit # version: "develop", - version: "ebaf03d6086c5cac39b66a54093e3926e1c0c359", + version: "28cd2c52dd583e9537f1df32cac91d9b4ed2da2d", # Doesn't actually work, but leviathan doesn't install with sudo by # default, or install at all for that matter noInstallSudo: true diff --git a/src/ThriveGame.cpp b/src/ThriveGame.cpp index 8616c61eafd..153d73924a7 100644 --- a/src/ThriveGame.cpp +++ b/src/ThriveGame.cpp @@ -170,6 +170,12 @@ ThriveGame::ThriveGame() StaticGame = this; } +ThriveGame::ThriveGame(Leviathan::Engine* engine) : + Leviathan::ClientApplication(engine) +{ + StaticGame = this; +} + ThriveGame::~ThriveGame() { StaticGame = nullptr; diff --git a/src/ThriveGame.h b/src/ThriveGame.h index f0ff6c49037..a2c522bdf92 100644 --- a/src/ThriveGame.h +++ b/src/ThriveGame.h @@ -33,6 +33,10 @@ class ThriveGame : public Leviathan::ClientApplication, public ThriveCommon { public: ThriveGame(); + + //! \brief Version for tests with incomplete engine instance + ThriveGame(Leviathan::Engine* engine); + virtual ~ThriveGame(); // ------------------------------------ // diff --git a/src/microbe_stage/compound_cloud_system.cpp b/src/microbe_stage/compound_cloud_system.cpp index 1ca41d25519..49481653b09 100644 --- a/src/microbe_stage/compound_cloud_system.cpp +++ b/src/microbe_stage/compound_cloud_system.cpp @@ -633,6 +633,17 @@ std::tuple return std::make_tuple(localX, localY); } +Float3 + CompoundCloudSystem::calculateGridCenterForPlayerPos(const Float3& pos) +{ + // The gaps between the positions is used for calculations here. Otherwise + // all clouds get moved when the player moves + return Float3( + static_cast(std::round(pos.X / CLOUD_X_EXTENT)) * CLOUD_X_EXTENT, + 0, + static_cast(std::round(pos.Z / CLOUD_Y_EXTENT)) * CLOUD_Y_EXTENT); +} + // ------------------------------------ // void CompoundCloudSystem::Run(CellStageWorld& world) @@ -713,121 +724,176 @@ void ((((m_cloudTypes.size() + 4 - 1) / 4 * 4) / 4) * 9), "A CompoundCloud entity has mysteriously been destroyed"); - const auto moved = playerPos - m_cloudGridCenter; + // Calculate what our center should be + const auto targetCenter = calculateGridCenterForPlayerPos(playerPos); // TODO: because we no longer check if the player has moved at least a bit // it is possible that this gets triggered very often if the player spins - // around a cloud edge, but hopefully there isn't a performance problem and - // that case can just be ignored. - // Z is used here because these are world coordinates - if(std::abs(moved.X) > CLOUD_WIDTH || std::abs(moved.Z) > CLOUD_HEIGHT) { - - // Calculate the new center - if(moved.X < -CLOUD_WIDTH) { - m_cloudGridCenter -= Float3(CLOUD_WIDTH * 2, 0, 0); - } else if(moved.X > CLOUD_WIDTH) { - m_cloudGridCenter += Float3(CLOUD_WIDTH * 2, 0, 0); - } + // around a cloud edge. - if(moved.Z < -CLOUD_HEIGHT) { - m_cloudGridCenter -= Float3(0, 0, CLOUD_HEIGHT * 2); - } else if(moved.Z > CLOUD_HEIGHT) { - m_cloudGridCenter += Float3(0, 0, CLOUD_HEIGHT * 2); - } + // This needs an extra variable to track how much the player has moved + // auto moved = playerPos - m_cloudGridCenter; - // Calculate the new positions - const auto requiredCloudPositions{ - calculateGridPositions(m_cloudGridCenter)}; + if(m_cloudGridCenter != targetCenter) { + + m_cloudGridCenter = targetCenter; + applyNewCloudPositioning(); + } +} + +void + CompoundCloudSystem::applyNewCloudPositioning() +{ + // Calculate the new positions + const auto requiredCloudPositions{ + calculateGridPositions(m_cloudGridCenter)}; - // Reposition clouds according to the origin - // The max amount of clouds is that all need to be moved - const size_t MAX_FAR_CLOUDS = m_managedClouds.size(); + // Reposition clouds according to the origin + // The max amount of clouds is that all need to be moved + const size_t MAX_FAR_CLOUDS = m_managedClouds.size(); - // According to spec this check is superfluous, but it makes me feel - // better - if(m_tooFarAwayClouds.size() != MAX_FAR_CLOUDS) - m_tooFarAwayClouds.resize(MAX_FAR_CLOUDS); + // According to spec this check is superfluous, but it makes me feel + // better + if(m_tooFarAwayClouds.size() != MAX_FAR_CLOUDS) + m_tooFarAwayClouds.resize(MAX_FAR_CLOUDS); - size_t farAwayIndex = 0; + size_t farAwayIndex = 0; - // All clouds that aren't at one of the requiredCloudPositions needs to - // be moved - for(auto iter = m_managedClouds.begin(); iter != m_managedClouds.end(); - ++iter) { + // All clouds that aren't at one of the requiredCloudPositions needs to + // be moved. Also only one from each cloud group needs to be at each + // position + for(auto iter = m_managedClouds.begin(); iter != m_managedClouds.end(); + ++iter) { - const auto pos = iter->second->m_position; + const auto pos = iter->second->m_position; - bool matched = false; + bool matched = false; - // Check if it is at any of the valid positions - for(size_t i = 0; i < std::size(requiredCloudPositions); ++i) { + // Check if it is at any of the valid positions + for(size_t i = 0; i < std::size(requiredCloudPositions); ++i) { - const auto& requiredPos = requiredCloudPositions[i]; + const auto& requiredPos = requiredCloudPositions[i]; - // An exact check might work but just to be safe slight - // inaccuracy is allowed here - if((pos - requiredPos).HAddAbs() < Leviathan::EPSILON) { + // An exact check might work but just to be safe slight + // inaccuracy is allowed here + if((pos - requiredPos).HAddAbs() < Leviathan::EPSILON) { - matched = true; - break; - } - } + // TODO: this is probably not needed and can be removed + // // It also has to be the only cloud with the first + // // compound id that it has (this is used to filter out + // // multiple clouds of a group being at some position) + // bool duplicate = false; - if(!matched) { + // const auto toCheckID = iter->second->getCompoundId1(); - if(farAwayIndex >= MAX_FAR_CLOUDS) { + // for(auto iter2 = m_managedClouds.begin(); + // iter2 != m_managedClouds.end(); ++iter2) { - LOG_FATAL("CompoundCloudSystem: Logic error in calculating " - "far away clouds that need to move"); - break; - } + // if(iter == iter2) + // continue; + + // if(toCheckID == iter2->second->getCompoundId1()) { + // duplicate = true; + // break; + // } + // } - m_tooFarAwayClouds[farAwayIndex++] = iter->second; + // if(!duplicate) { + matched = true; + break; + //} } } - // Move clouds that are too far away - // We check through each position that should have a cloud and move one - // where there isn't one - size_t farAwayRepositionedIndex = 0; - - // Loop through the cloud groups - for(size_t c = 0; c < m_cloudTypes.size(); c += CLOUDS_IN_ONE) { - // Loop for moving clouds to all needed positions for each group - for(size_t i = 0; i < std::size(requiredCloudPositions); ++i) { - bool hasCloud = false; - const auto& requiredPos = requiredCloudPositions[i]; - for(auto iter = m_managedClouds.begin(); - iter != m_managedClouds.end(); ++iter) { - const auto pos = iter->second->m_position; - // An exact check might work but just to be safe slight - // inaccuracy is allowed here - if(((pos - requiredPos).HAddAbs() < Leviathan::EPSILON) && - (m_cloudTypes[c].id == - iter->second->getCompoundId1())) { + if(!matched) { + + if(farAwayIndex >= MAX_FAR_CLOUDS) { + + LOG_FATAL("CompoundCloudSystem: Logic error in calculating " + "far away clouds that need to move"); + break; + } + + m_tooFarAwayClouds[farAwayIndex++] = iter->second; + } + } + + // Move clouds that are too far away + // We check through each position that should have a cloud and move one + // where there isn't one. This also needs to take into account the cloud + // groups + + // Loop through the cloud groups + for(size_t c = 0; c < m_cloudTypes.size(); c += CLOUDS_IN_ONE) { + + const CompoundId groupType = m_cloudTypes[c].id; + + // Loop for moving clouds to all needed positions for each group + for(size_t i = 0; i < std::size(requiredCloudPositions); ++i) { + + bool hasCloud = false; + + const auto& requiredPos = requiredCloudPositions[i]; + + for(auto iter = m_managedClouds.begin(); + iter != m_managedClouds.end(); ++iter) { + + const auto pos = iter->second->m_position; + // An exact check might work but just to be safe slight + // inaccuracy is allowed here + if(((pos - requiredPos).HAddAbs() < Leviathan::EPSILON)) { + + // Check that the group of the cloud is correct + if(groupType == iter->second->getCompoundId1()) { hasCloud = true; break; } } + } + + if(hasCloud) + continue; + + bool filled = false; + + // We need to find a cloud from the right group + for(size_t checkReposition = 0; checkReposition < farAwayIndex; + ++checkReposition) { - if(hasCloud) - continue; + if(m_tooFarAwayClouds[checkReposition] && + m_tooFarAwayClouds[checkReposition]->getCompoundId1() == + groupType) { - if(farAwayRepositionedIndex >= farAwayIndex) { - LOG_FATAL("CompoundCloudSystem: Logic error in moving far " - "clouds (ran out), total to reposition: " + - std::to_string(farAwayIndex + 1) + - ", current index: " + - std::to_string(farAwayRepositionedIndex) + - ", position grid index: " + std::to_string(i)); + // Found a candidate + m_tooFarAwayClouds[checkReposition]->recycleToPosition( + requiredPos); + + // Set to null to skip on next scan + m_tooFarAwayClouds[checkReposition] = nullptr; + + filled = true; break; } + } - m_tooFarAwayClouds[farAwayRepositionedIndex++] - ->recycleToPosition(requiredPos); + if(!filled) { + LOG_FATAL("CompoundCloudSystem: Logic error in moving far " + "clouds, didn't find any to use for needed pos"); + break; } } } + + // TODO: this can be removed once this has been fully confirmed to work fine + // Errors about clouds that should have been moved but haven't been + for(size_t checkReposition = 0; checkReposition < farAwayIndex; + ++checkReposition) { + if(m_tooFarAwayClouds[checkReposition]) { + LOG_FATAL( + "CompoundCloudSystem: Logic error in moving far " + "clouds, a cloud that should have been moved wasn't moved"); + } + } } void diff --git a/src/microbe_stage/compound_cloud_system.h b/src/microbe_stage/compound_cloud_system.h index 3e7a550e557..45f83d2046b 100644 --- a/src/microbe_stage/compound_cloud_system.h +++ b/src/microbe_stage/compound_cloud_system.h @@ -479,6 +479,9 @@ class CompoundCloudSystem { }; } + static Float3 + calculateGridCenterForPlayerPos(const Float3& pos); + protected: //! \brief Removes deleted clouds from m_managedClouds void @@ -486,9 +489,18 @@ class CompoundCloudSystem { private: //! \brief Spawns and despawns the cloud entities around the player + //! \todo This should check if the player has moved at least 10 units to + //! avoid excessive recalculations if the player goes in a circle around a + //! cloud boundary void doSpawnCycle(CellStageWorld& world, const Float3& playerPos); + //! \brief This method handles moving the clouds + //! + //! This has been separated from doSpawnCycle to be more manageable + void + applyNewCloudPositioning(); + void _spawnCloud(CellStageWorld& world, const Float3& pos, diff --git a/src/microbe_stage/player_hover_info.cpp b/src/microbe_stage/player_hover_info.cpp index a2c2a01ba8a..6eafa2bce17 100644 --- a/src/microbe_stage/player_hover_info.cpp +++ b/src/microbe_stage/player_hover_info.cpp @@ -85,85 +85,94 @@ void auto microbeComponents = world.GetScriptComponentHolder("MicrobeComponent"); - // The world will keep this alive (this is released immediately to reduce - // chance of leaks) - microbeComponents->Release(); + // Only run when scripts are loaded + // TODO: move this to a sub function to not have this huge indended block + // here + if(microbeComponents) { - const auto stringType = - Leviathan::AngelScriptTypeIDResolver::Get( - Leviathan::GetCurrentGlobalScriptExecutor()); + // The world will keep this alive (this is released immediately to + // reduce chance of leaks) + microbeComponents->Release(); - // This is used to skip the player - auto controlledEntity = ThriveGame::Get()->playerData().activeCreature(); + const auto stringType = + Leviathan::AngelScriptTypeIDResolver::Get( + Leviathan::GetCurrentGlobalScriptExecutor()); - const auto& allSpecies = world.GetComponentIndex_SpeciesComponent(); + // This is used to skip the player + auto controlledEntity = + ThriveGame::Get()->playerData().activeCreature(); - auto& index = CachedComponents.GetIndex(); - for(auto iter = index.begin(); iter != index.end(); ++iter) { + const auto& allSpecies = world.GetComponentIndex_SpeciesComponent(); - const float distance = - (std::get<1>(*iter->second).Members._Position - lookPoint).Length(); + auto& index = CachedComponents.GetIndex(); + for(auto iter = index.begin(); iter != index.end(); ++iter) { - // Find only cells that have the mouse position within their membrane - if(distance > - std::get<0>(*iter->second).calculateEncompassingCircleRadius()) - continue; + const float distance = + (std::get<1>(*iter->second).Members._Position - lookPoint) + .Length(); - // Skip player - if(iter->first == controlledEntity) - continue; + // Find only cells that have the mouse position within their + // membrane + if(distance > + std::get<0>(*iter->second).calculateEncompassingCircleRadius()) + continue; - // Hovered over this. Find the name of the species - auto microbeComponent = microbeComponents->Find(iter->first); + // Skip player + if(iter->first == controlledEntity) + continue; - if(!microbeComponent) - continue; + // Hovered over this. Find the name of the species + auto microbeComponent = microbeComponents->Find(iter->first); - // We don't store the reference to the object. The holder will keep - // the reference alive while we work on it - microbeComponent->Release(); + if(!microbeComponent) + continue; - if(microbeComponent->GetPropertyCount() < 1) { + // We don't store the reference to the object. The holder will keep + // the reference alive while we work on it + microbeComponent->Release(); - LOG_ERROR("PlayerHoverInfoSystem: Run: MicrobeComponent object " - "has no properties"); - continue; - } + if(microbeComponent->GetPropertyCount() < 1) { - if(microbeComponent->GetPropertyTypeId(0) != stringType || - std::strncmp(microbeComponent->GetPropertyName(0), "speciesName", - sizeof("speciesName") - 1) != 0) { + LOG_ERROR("PlayerHoverInfoSystem: Run: MicrobeComponent object " + "has no properties"); + continue; + } - LOG_ERROR( - "PlayerHoverInfoSystem: Run: MicrobeComponent object doesn't " - "have \"string speciesName\" as the first property"); - continue; - } + if(microbeComponent->GetPropertyTypeId(0) != stringType || + std::strncmp(microbeComponent->GetPropertyName(0), + "speciesName", sizeof("speciesName") - 1) != 0) { - const auto* name = static_cast( - microbeComponent->GetAddressOfProperty(0)); + LOG_ERROR("PlayerHoverInfoSystem: Run: MicrobeComponent object " + "doesn't " + "have \"string speciesName\" as the first property"); + continue; + } - bool found = false; + const auto* name = static_cast( + microbeComponent->GetAddressOfProperty(0)); - for(const auto& tuple : allSpecies) { + bool found = false; - SpeciesComponent* species = std::get<1>(tuple); + for(const auto& tuple : allSpecies) { - if(species->name == *name) { + SpeciesComponent* species = std::get<1>(tuple); - hovered->PushValue( - std::make_unique(new Leviathan::StringBlock( - species->genus + " " + species->epithet))); - found = true; - break; + if(species->name == *name) { + + hovered->PushValue(std::make_unique( + new Leviathan::StringBlock( + species->genus + " " + species->epithet))); + found = true; + break; + } } - } - if(!found) { + if(!found) { - // If we can't find the species, assume that it is extinct - hovered->PushValue(std::make_unique( - new Leviathan::StringBlock("Extinct(" + *name + ")"))); + // If we can't find the species, assume that it is extinct + hovered->PushValue(std::make_unique( + new Leviathan::StringBlock("Extinct(" + *name + ")"))); + } } } diff --git a/test/test_clouds.cpp b/test/test_clouds.cpp index 3497be6aeaa..529fa4de049 100644 --- a/test/test_clouds.cpp +++ b/test/test_clouds.cpp @@ -152,6 +152,39 @@ TEST_CASE("Cloud local coordinate calculation math is right", "[microbe]") } } +TEST_CASE("CloudManager grid center calculation", "[microbe]") +{ + CHECK(CompoundCloudSystem::calculateGridCenterForPlayerPos( + Float3(0, 0, 0)) == Float3(0, 0, 0)); + + CHECK(CompoundCloudSystem::calculateGridCenterForPlayerPos( + Float3(CLOUD_WIDTH - 1, 0, 0)) == Float3(0, 0, 0)); + + CHECK(CompoundCloudSystem::calculateGridCenterForPlayerPos( + Float3(CLOUD_WIDTH, 0, 0)) == Float3(CLOUD_X_EXTENT, 0, 0)); + + CHECK(CompoundCloudSystem::calculateGridCenterForPlayerPos( + Float3(CLOUD_WIDTH + 1, 0, 0)) == Float3(CLOUD_X_EXTENT, 0, 0)); + + CHECK(CompoundCloudSystem::calculateGridCenterForPlayerPos( + Float3(CLOUD_WIDTH, 0, CLOUD_HEIGHT)) == + Float3(CLOUD_X_EXTENT, 0, CLOUD_Y_EXTENT)); + + CHECK(CompoundCloudSystem::calculateGridCenterForPlayerPos( + Float3(CLOUD_WIDTH, 0, CLOUD_HEIGHT * 2)) == + Float3(CLOUD_X_EXTENT, 0, CLOUD_Y_EXTENT)); + + CHECK(CompoundCloudSystem::calculateGridCenterForPlayerPos( + Float3(-CLOUD_X_EXTENT, 0, 0)) == Float3(-CLOUD_Y_EXTENT, 0, 0)); + + CHECK(CompoundCloudSystem::calculateGridCenterForPlayerPos( + Float3(-CLOUD_WIDTH, 0, -CLOUD_HEIGHT)) == + Float3(-CLOUD_X_EXTENT, 0, -CLOUD_Y_EXTENT)); + + CHECK(CompoundCloudSystem::calculateGridCenterForPlayerPos(Float3( + -CLOUD_WIDTH + 1, 0, -CLOUD_HEIGHT + 1)) == Float3(0, 0, 0)); +} + class CloudManagerTestsFixture { public: CloudManagerTestsFixture() @@ -204,9 +237,20 @@ class CloudManagerTestsFixture { return clouds; } + void + movePlayerXUnits(float amount) + { + // Move player + playerPos->Members._Position.X += amount; + playerPos->Marked = true; + + // And tick + world.Tick(1); + } + protected: Leviathan::Test::PartialEngine engine; - TestThriveGame thrive; + TestThriveGame thrive{&engine}; Leviathan::IDFactory ids; CellStageWorld world{nullptr}; @@ -215,22 +259,11 @@ class CloudManagerTestsFixture { ObjectID player = NULL_OBJECT; }; -TEST_CASE_METHOD(CloudManagerTestsFixture, - "Cloud manager creates and positions clouds with 1 compound type correctly", - "[microbe]") +template +auto + simpleCloudPosCheck(const std::vector& clouds, + const PosArrayT& targetPositions) { - setCloudsAndRunInitial( - {Compound{1, "a", true, true, false, Ogre::ColourValue(0, 1, 2, 3)}}); - - // Find the cloud entities - const auto clouds = findClouds(); - - CHECK(clouds.size() == 9); - - // Check that cloud positioning has worked - const auto targetPositions{ - CompoundCloudSystem::calculateGridPositions(Float3(0, 0, 0))}; - std::vector valid; valid.resize(targetPositions.size(), false); @@ -248,13 +281,164 @@ TEST_CASE_METHOD(CloudManagerTestsFixture, } } + return valid; +} + +//! A pretty inefficient but simple way to calculate how many clouds are at the +//! same pos and what types +auto + calculateCloudsAtSamePos(const std::vector& clouds) +{ + std::map> counts; + + for(auto cloud : clouds) { + + std::stringstream stream; + stream << cloud->getPosition(); + + if(counts.find(stream.str()) == counts.end()) { + counts.insert(std::make_pair(stream.str(), + std::vector{cloud->getCompoundId1()})); + } else { + counts[stream.str()].push_back(cloud->getCompoundId1()); + } + } + + return counts; +} + +template +void + checkCloudsAtPos( + const std::map>& counts, + const std::array& types) +{ + CAPTURE(types); + + for(const auto& pair : counts) { + CAPTURE(pair.first); + CAPTURE(pair.second); + + CHECK(pair.second.size() == types.size()); + + std::vector foundStatuses; + foundStatuses.resize(types.size(), false); + + for(auto id : pair.second) { + for(size_t i = 0; i < types.size(); ++i) { + if(id == types[i]) { + + CHECK(!foundStatuses[i]); + foundStatuses[i] = true; + break; + } + } + } + + CAPTURE(foundStatuses); + for(bool found : foundStatuses) + CHECK(found); + } +} + +template +auto + multiOverlapCloudPosCheck( + const std::vector& clouds, + const PosArrayT& targetPositions, + const std::array& cloudFirstTypes) +{ + std::vector> valid; + + std::array falseArray; + for(bool& entry : falseArray) + entry = false; + + valid.resize(targetPositions.size(), falseArray); + + for(size_t i = 0; i < targetPositions.size(); ++i) { + + CAPTURE(i); + + const auto& pos = targetPositions[i]; + + for(CompoundCloudComponent* cloud : clouds) { + + if(cloud->getPosition() == pos) { + + CAPTURE(cloud->getCompoundId1(), pos, cloudFirstTypes); + + bool matched = false; + + // Check which group it is + for(size_t targetTypeIndex = 0; + targetTypeIndex < cloudFirstTypes.size(); + ++targetTypeIndex) { + + if(cloud->getCompoundId1() == + cloudFirstTypes[targetTypeIndex]) { + + // Position check + CHECK(!valid[i][targetTypeIndex]); + valid[i][targetTypeIndex] = true; + matched = true; + break; + } + } + + CHECK(matched); + } + } + } + + return valid; +} + +template +void + multiCloudPositionCheckHelper( + const std::vector& clouds, + const Float3& playerPos, + const std::array& cloudFirstTypes) +{ + // There needs to be 2 clouds at each position + checkCloudsAtPos(calculateCloudsAtSamePos(clouds), cloudFirstTypes); + + // And then check that update succeeded + const auto valid = multiOverlapCloudPosCheck(clouds, + CompoundCloudSystem::calculateGridPositions( + CompoundCloudSystem::calculateGridCenterForPlayerPos(playerPos)), + cloudFirstTypes); + + for(const auto& entry : valid) { + CAPTURE(entry); + for(auto subentry : entry) + CHECK(subentry); + } +} + +TEST_CASE_METHOD(CloudManagerTestsFixture, + "Cloud manager creates and positions clouds with 1 compound type", + "[microbe]") +{ + setCloudsAndRunInitial( + {Compound{1, "a", true, true, false, Ogre::ColourValue(0, 1, 2, 3)}}); + + // Find the cloud entities + const auto clouds = findClouds(); + + CHECK(clouds.size() == 9); + + // Check that cloud positioning has worked + const auto valid = simpleCloudPosCheck( + clouds, CompoundCloudSystem::calculateGridPositions(Float3(0, 0, 0))); + for(bool entry : valid) CHECK(entry); } TEST_CASE_METHOD(CloudManagerTestsFixture, - "Cloud manager creates and positions clouds with 5 compound types " - "correctly", + "Cloud manager creates and positions clouds with 5 compound types", "[microbe]") { // This test assumes @@ -267,6 +451,10 @@ TEST_CASE_METHOD(CloudManagerTestsFixture, Compound{4, "d", true, true, false, Ogre::ColourValue(9, 10, 11, 1)}, Compound{5, "e", true, true, false, Ogre::ColourValue(12, 13, 14, 1)}}; + std::array cloudFirstTypes; + cloudFirstTypes[0] = types[0].id; + cloudFirstTypes[1] = types[4].id; + setCloudsAndRunInitial(types); // Find the cloud entities @@ -274,54 +462,88 @@ TEST_CASE_METHOD(CloudManagerTestsFixture, CHECK(clouds.size() == 18); + multiCloudPositionCheckHelper( + clouds, playerPos->Members._Position, cloudFirstTypes); +} + + +TEST_CASE_METHOD(CloudManagerTestsFixture, + "Cloud manager repositions on player move clouds with 1 compound type", + "[microbe]") +{ + constexpr auto PLAYER_MOVE_AMOUNT = 1000; + + setCloudsAndRunInitial( + {Compound{1, "a", true, true, false, Ogre::ColourValue(0, 1, 2, 3)}}); + + // Find the cloud entities + const auto clouds = findClouds(); + + CHECK(clouds.size() == 9); + // Check that cloud positioning has worked - const auto targetPositions{ - CompoundCloudSystem::calculateGridPositions(Float3(0, 0, 0))}; + auto valid = simpleCloudPosCheck( + clouds, CompoundCloudSystem::calculateGridPositions(Float3(0, 0, 0))); - std::vector> valid; - valid.resize(targetPositions.size(), {false, false}); + for(bool entry : valid) + CHECK(entry); - for(size_t i = 0; i < targetPositions.size(); ++i) { + // Move player + playerPos->Members._Position.X += PLAYER_MOVE_AMOUNT; + playerPos->Marked = true; - const auto& pos = targetPositions[i]; + // And tick + world.Tick(1); - for(CompoundCloudComponent* cloud : clouds) { + // And then check that update succeeded + valid = simpleCloudPosCheck( + clouds, CompoundCloudSystem::calculateGridPositions( + CompoundCloudSystem::calculateGridCenterForPlayerPos( + Float3(PLAYER_MOVE_AMOUNT, 0, 0)))); - if(cloud->getPosition() == pos) { + for(bool entry : valid) + CHECK(entry); +} - // First or second - CAPTURE(cloud->getCompoundId1()); - if(cloud->getCompoundId1() == types[0].id) { +TEST_CASE_METHOD(CloudManagerTestsFixture, + "Cloud manager repositions on player move clouds with 5 compound types", + "[microbe]") +{ + // This test assumes + static_assert(CLOUDS_IN_ONE == 4, "this test assumes this"); - // Make sure the rest of the cloud entries are also right - CHECK(cloud->getCompoundId2() == types[1].id); - CHECK(cloud->getCompoundId3() == types[2].id); - CHECK(cloud->getCompoundId4() == types[3].id); + const std::vector types{ + Compound{1, "a", true, true, false, Ogre::ColourValue(0, 1, 2, 1)}, + Compound{2, "b", true, true, false, Ogre::ColourValue(3, 4, 5, 1)}, + Compound{3, "c", true, true, false, Ogre::ColourValue(6, 7, 8, 1)}, + Compound{4, "d", true, true, false, Ogre::ColourValue(9, 10, 11, 1)}, + Compound{5, "e", true, true, false, Ogre::ColourValue(12, 13, 14, 1)}}; - // Position check - CHECK(!valid[i][0]); - valid[i][0] = true; + std::array cloudFirstTypes; + cloudFirstTypes[0] = types[0].id; + cloudFirstTypes[1] = types[4].id; - } else if(cloud->getCompoundId1() == types[4].id) { + setCloudsAndRunInitial(types); - // Make sure the rest of the cloud entries are also right - CHECK(cloud->getCompoundId2() == NULL_COMPOUND); - CHECK(cloud->getCompoundId3() == NULL_COMPOUND); - CHECK(cloud->getCompoundId4() == NULL_COMPOUND); + // Find the cloud entities + const auto clouds = findClouds(); - // Position check - CHECK(!valid[i][1]); - valid[i][1] = true; + CHECK(clouds.size() == 18); - } else { - FAIL_CHECK("cloud has unexpected id"); - } - } - } - } + multiCloudPositionCheckHelper( + clouds, playerPos->Members._Position, cloudFirstTypes); - for(const auto& entry : valid) { - CHECK(entry[0]); - CHECK(entry[1]); + // SECTION("Moving CLOUD_WIDTH/2 units") {} + + SECTION("Moving 1000 units") + { + constexpr auto PLAYER_MOVE_AMOUNT = 1000; + + movePlayerXUnits(PLAYER_MOVE_AMOUNT); + + multiCloudPositionCheckHelper( + clouds, playerPos->Members._Position, cloudFirstTypes); } + + // SECTION("Moving CLOUD_WIDTH * 1.5 units") {} } diff --git a/test/test_thrive_game.h b/test/test_thrive_game.h index 467c48d51ca..3bdfc364e15 100644 --- a/test/test_thrive_game.h +++ b/test/test_thrive_game.h @@ -10,7 +10,7 @@ namespace thrive { namespace test { //! \brief A test dummy for tests needing ThriveGame class TestThriveGame : public ThriveGame { public: - TestThriveGame() + TestThriveGame(Leviathan::Engine* engine) : ThriveGame(engine) { // We need to fake key configurations for things ApplicationConfiguration = new Leviathan::AppDef(true);