Index: ps/trunk/source/simulation2/components/CCmpTerritoryManager.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpTerritoryManager.cpp +++ ps/trunk/source/simulation2/components/CCmpTerritoryManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -548,6 +548,9 @@ m_Territories->set(i, j, owner | TERRITORY_CONNECTED_MASK); + if (m_CostGrid->get(i, j) < m_ImpassableCost) + ++m_TerritoryCellCounts[owner]; + FLOODFILL(i, j, // Don't expand non-owner tiles, or tiles that already have a connected mask if (m_Territories->get(nx, nz) != owner) @@ -580,7 +583,7 @@ u8 CCmpTerritoryManager::GetTerritoryPercentage(player_id_t player) { - if (player <= 0 || static_cast(player) >= m_TerritoryCellCounts.size()) + if (player <= 0 || (m_Territories && static_cast(player) >= m_TerritoryCellCounts.size())) return 0; CalculateTerritories(); @@ -791,11 +794,18 @@ player_id_t thisOwner = m_Territories->get(i, j) & TERRITORY_PLAYER_MASK; + u8 bitmask = m_Territories->get(i, j); + u8 blinking = bitmask & TERRITORY_BLINKING_MASK; + if (enable && !blinking) + m_Territories->set(i, j, bitmask | TERRITORY_BLINKING_MASK); + else if (!enable && blinking) + m_Territories->set(i, j, bitmask & ~TERRITORY_BLINKING_MASK); + FLOODFILL(i, j, - u8 bitmask = m_Territories->get(nx, nz); + bitmask = m_Territories->get(nx, nz); if ((bitmask & TERRITORY_PLAYER_MASK) != thisOwner) continue; - u8 blinking = bitmask & TERRITORY_BLINKING_MASK; + blinking = bitmask & TERRITORY_BLINKING_MASK; if (enable && !blinking) m_Territories->set(nx, nz, bitmask | TERRITORY_BLINKING_MASK); else if (!enable && blinking) Index: ps/trunk/source/simulation2/components/tests/test_TerritoryManager.h =================================================================== --- ps/trunk/source/simulation2/components/tests/test_TerritoryManager.h +++ ps/trunk/source/simulation2/components/tests/test_TerritoryManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -17,11 +17,126 @@ #include "simulation2/system/ComponentTest.h" +#include "maths/Matrix3D.h" #include "ps/CStr.h" #include "graphics/Terrain.h" #include "graphics/TerritoryBoundary.h" #include "simulation2/helpers/Grid.h" #include "simulation2/components/ICmpTerritoryManager.h" +#include "simulation2/components/ICmpPlayerManager.h" +#include "simulation2/components/ICmpTerritoryInfluence.h" +#include "simulation2/components/ICmpOwnership.h" + +class MockPathfinderTerrMan : public ICmpPathfinder +{ +public: + DEFAULT_MOCK_COMPONENT() + + // Test data + Grid m_PassabilityGrid; + + virtual pass_class_t GetPassabilityClass(const std::string&) const override { return 0; } + virtual const Grid& GetPassabilityGrid() override { return m_PassabilityGrid; } + + // Irrelevant part of the mock. + virtual void GetPassabilityClasses(std::map&) const override {} + virtual void GetPassabilityClasses(std::map&, std::map&) const override {} + virtual entity_pos_t GetClearance(pass_class_t) const override { return entity_pos_t::FromInt(1); } + virtual entity_pos_t GetMaximumClearance() const override { return entity_pos_t::FromInt(1); } + virtual const GridUpdateInformation& GetAIPathfinderDirtinessInformation() const override { static GridUpdateInformation gridInfo; return gridInfo; } + virtual void FlushAIPathfinderDirtinessInformation() override {} + virtual Grid ComputeShoreGrid(bool = false) override { return Grid {}; } + virtual u32 ComputePathAsync(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t, entity_id_t) override { return 1; } + virtual void ComputePathImmediate(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t, WaypointPath&) const override {} + virtual u32 ComputeShortPathAsync(entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t, bool, entity_id_t, entity_id_t) override { return 1; } + virtual WaypointPath ComputeShortPathImmediate(const ShortPathRequest&) const override { return WaypointPath(); } + virtual void SetDebugPath(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t) override {} + virtual bool IsGoalReachable(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t) override { return false; } + virtual bool CheckMovement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, pass_class_t) const override { return false; } + virtual ICmpObstruction::EFoundationCheck CheckUnitPlacement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, pass_class_t, bool = false) const override { return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; } + virtual ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_id_t, pass_class_t) const override { return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; } + virtual ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_id_t, pass_class_t, bool) const override { return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; } + virtual void SetDebugOverlay(bool) override {} + virtual void SetHierDebugOverlay(bool) override {} + virtual void SendRequestedPaths() override {} + virtual void StartProcessingMoves(bool) override {} + virtual void UpdateGrid() override {} + virtual void GetDebugData(u32&, double&, Grid&) const override {} + virtual void SetAtlasOverlay(bool, pass_class_t = 0) override {} +}; + +class MockPlayerMgrTerrMan : public ICmpPlayerManager +{ +public: + DEFAULT_MOCK_COMPONENT() + + int32_t GetNumPlayers() override { return 2; } + entity_id_t GetPlayerByID(int32_t id) override { return id + 1; } +}; + +class MockTerrInfTerrMan : public ICmpTerritoryInfluence +{ +public: + DEFAULT_MOCK_COMPONENT() + + bool IsRoot() const override { return true; }; + u16 GetWeight() const override { return 10; }; + u32 GetRadius() const override { return m_Radius; }; + + u32 m_Radius = 0; +}; + +class MockOwnershipTerrMan : public ICmpOwnership +{ +public: + DEFAULT_MOCK_COMPONENT() + + player_id_t GetOwner() const override { return 1; }; + void SetOwner(player_id_t) override {}; + void SetOwnerQuiet(player_id_t) override {}; +}; + +class MockPositionTerrMan : public ICmpPosition +{ +public: + DEFAULT_MOCK_COMPONENT() + + void SetTurretParent(entity_id_t, const CFixedVector3D&) override {} + entity_id_t GetTurretParent() const override { return INVALID_ENTITY; } + void UpdateTurretPosition() override {} + std::set* GetTurrets() override { return nullptr; } + bool IsInWorld() const override { return true; } + void MoveOutOfWorld() override {} + void MoveTo(entity_pos_t, entity_pos_t) override {} + void MoveAndTurnTo(entity_pos_t, entity_pos_t, entity_angle_t) override {} + void JumpTo(entity_pos_t, entity_pos_t) override {} + void SetHeightOffset(entity_pos_t) override {} + entity_pos_t GetHeightOffset() const override { return entity_pos_t::Zero(); } + void SetHeightFixed(entity_pos_t) override {} + entity_pos_t GetHeightFixed() const override { return entity_pos_t::Zero(); } + entity_pos_t GetHeightAtFixed(entity_pos_t, entity_pos_t) const override { return entity_pos_t::Zero(); } + bool IsHeightRelative() const override { return true; } + void SetHeightRelative(bool) override {} + bool CanFloat() const override { return false; } + void SetFloating(bool) override {} + void SetActorFloating(bool) override {} + void SetConstructionProgress(fixed) override {} + CFixedVector3D GetPosition() const override { return m_Pos; } + CFixedVector2D GetPosition2D() const override { return CFixedVector2D(m_Pos.X, m_Pos.Z); } + CFixedVector3D GetPreviousPosition() const override { return CFixedVector3D(); } + CFixedVector2D GetPreviousPosition2D() const override { return CFixedVector2D(); } + fixed GetTurnRate() const override { return fixed::Zero(); } + void TurnTo(entity_angle_t) override {} + void SetYRotation(entity_angle_t) override {} + void SetXZRotation(entity_angle_t, entity_angle_t) override {} + CFixedVector3D GetRotation() const override { return CFixedVector3D(); } + fixed GetDistanceTravelled() const override { return fixed::Zero(); } + void GetInterpolatedPosition2D(float, float&, float&, float&) const override {} + CMatrix3D GetInterpolatedTransform(float) const override { return CMatrix3D(); } + + CFixedVector3D m_Pos; +}; + class TestCmpTerritoryManager : public CxxTest::TestSuite { @@ -29,11 +144,58 @@ void setUp() { CxxTest::setAbortTestOnFail(true); + g_VFS = CreateVfs(); + TS_ASSERT_OK(g_VFS->Mount(L"", DataDir() / "mods" / "_test.sim" / "", VFS_MOUNT_MUST_EXIST)); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY)); + CXeromyces::Startup(); } void tearDown() { + CXeromyces::Terminate(); + g_VFS.reset(); + DeleteDirectory(DataDir()/"_testcache"); + } + + // Regression test for D5181 / fix for rP27673 issue + void test_calculate_territories_uninitialised() + { + ComponentTestHelper test(g_ScriptContext); + ICmpTerritoryManager* cmp = test.Add(CID_TerritoryManager, "", SYSTEM_ENTITY); + + MockPathfinderTerrMan pathfinder; + test.AddMock(SYSTEM_ENTITY, IID_Pathfinder, pathfinder); + + MockPlayerMgrTerrMan playerMan; + test.AddMock(SYSTEM_ENTITY, IID_PlayerManager, playerMan); + pathfinder.m_PassabilityGrid.resize(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * 5, ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * 5); + + MockTerrInfTerrMan terrInf; + test.AddMock(5, IID_TerritoryInfluence, terrInf); + MockOwnershipTerrMan ownership; + test.AddMock(5, IID_Ownership, ownership); + MockPositionTerrMan position; + test.AddMock(5, IID_Position, position); + + position.m_Pos = CFixedVector3D( + entity_pos_t::FromInt(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * 5 + ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE / 2), + entity_pos_t::FromInt(1), + entity_pos_t::FromInt(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * 5 + ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE / 2) + ); + terrInf.m_Radius = 1; + + TS_ASSERT_EQUALS(cmp->GetTerritoryPercentage(0), 0); + TS_ASSERT_EQUALS(cmp->GetTerritoryPercentage(1), 4); // 5*5 = 25 -> 1 tile out of 25 is 4% + TS_ASSERT_EQUALS(cmp->GetTerritoryPercentage(2), 0); + + terrInf.m_Radius = ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * 10; + + test.HandleMessage(cmp, CMessageTerrainChanged(0, 0, 0, 0), true); + + TS_ASSERT_EQUALS(cmp->GetTerritoryPercentage(0), 0); + TS_ASSERT_EQUALS(cmp->GetTerritoryPercentage(1), 100); + TS_ASSERT_EQUALS(cmp->GetTerritoryPercentage(2), 0); } void test_boundaries()