From 7aa77940083020301c976df457490359133df877 Mon Sep 17 00:00:00 2001 From: PowerfulBacon <26465327+PowerfulBacon@users.noreply.github.com> Date: Tue, 15 Mar 2022 12:44:40 +0000 Subject: [PATCH 01/16] Continues the completion of the region system --- GJ2022/Game/GameWorld/Regions/Region.cs | 23 +++++++++++ .../Game/GameWorld/Regions/RegionContainer.cs | 15 +++++++ .../Game/GameWorld/Regions/WorldRegionList.cs | 40 +++++++++++++++++-- 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 GJ2022/Game/GameWorld/Regions/RegionContainer.cs diff --git a/GJ2022/Game/GameWorld/Regions/Region.cs b/GJ2022/Game/GameWorld/Regions/Region.cs index dd7ef36..5a01911 100644 --- a/GJ2022/Game/GameWorld/Regions/Region.cs +++ b/GJ2022/Game/GameWorld/Regions/Region.cs @@ -6,6 +6,29 @@ namespace GJ2022.Game.GameWorld.Regions { + /// + /// Region system: + /// ======== + /// + /// A region determines a section of the world in which all points are accessible to each other. + /// + /// The world will be broken down into 8x8 chunks and each one designated regions. + /// If there is non passable turfs within any of the 8x8 chunks that split the region into pieces, then many regions can be generated for 1 of these. + /// + /// Region Rules: + /// ========= + /// - For each point within a region, there must exist a valid path between those 2 points without leaving that region + /// - A region shares at least 1 superparent with any region that can be accessed by it. + /// - At least 2 regions exist for each parent region. A region will never be an own child. + /// + /// Region Properties: + /// ========= + /// - Checking if a valid path exists between 2 points can be done by getting the regions of the 2 points and seeing if they have a shared super-parent. + /// + /// Region Functionality: + /// ========= + /// - + /// public class Region { diff --git a/GJ2022/Game/GameWorld/Regions/RegionContainer.cs b/GJ2022/Game/GameWorld/Regions/RegionContainer.cs new file mode 100644 index 0000000..cf80a80 --- /dev/null +++ b/GJ2022/Game/GameWorld/Regions/RegionContainer.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GJ2022.Game.GameWorld.Regions +{ + public class RegionContainer + { + + + + } +} diff --git a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs index 8d701b2..16bc3a9 100644 --- a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs +++ b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs @@ -1,4 +1,5 @@ -using System; +using GJ2022.Utility.MathConstructs; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -10,9 +11,42 @@ public class WorldRegionList { /// - /// List of regions with index corresponding to their level + /// The nxn area in which the first level regions are /// - public List> regions = new List>(); + public const int REGION_PRIMARY_LEVEL_SIZE = 8; + + /// + /// nxn regions will exist within a parent region. + /// For a 2x2 region then with a 4ax4a world (where a is the primary level size) there will be 3 region sets: + /// - 16 primary regions + /// - 4 parent regions, each holding 4 primary regions + /// - 1 super region which holds the 4 parent regions + /// (Since there is only 1 super region, there is no need for a super super region in this example.) + /// + public const int REGION_CHILDREN_SIZE = 2; + + /// + /// Position based binary list for regions, allows for relatively quick finding of locations at positions as well as infinite non-continuous insertion + /// requiring O(n) memory with O(log(n)) access time. + /// + public PositionBasedBinaryList regions = new PositionBasedBinaryList(); + + /// + /// Generates the region that contains the provided X,Y world coordinates. + /// + public void GenerateWorldSection(int x, int y) + { + //WARNING: Integer divison rounds towards 0. + //Positive values (8, 16, 24, 32 etc..) should work fine, however it needs to be ensured that (-1 and 1) don't get assigned to the same region, + //since we don't want region (x, y), (-x, y), (x, -y), (-x, -y) to all be assigned to region (0, 0) + int regionX = (int)Math.Floor((float)x / REGION_PRIMARY_LEVEL_SIZE); + int regionY = (int)Math.Floor((float)y / REGION_PRIMARY_LEVEL_SIZE); + //Check if the region exists already + if (regions.Get(regionX, regionY) != null) + return; + //The region doesn't exist, so we need to generate one + + } } } From 8efeedef169dab1de3f7931cb786e7b3bada090d Mon Sep 17 00:00:00 2001 From: PowerfulBacon <26465327+PowerfulBacon@users.noreply.github.com> Date: Tue, 15 Mar 2022 12:46:13 +0000 Subject: [PATCH 02/16] Adds in a todo comment :) --- GJ2022/Game/GameWorld/Regions/WorldRegionList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs index 16bc3a9..f440c0b 100644 --- a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs +++ b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs @@ -45,7 +45,7 @@ public void GenerateWorldSection(int x, int y) if (regions.Get(regionX, regionY) != null) return; //The region doesn't exist, so we need to generate one - + //TODO } } From fade70deff3247b7b0d4271a0de676c5d7a247e1 Mon Sep 17 00:00:00 2001 From: PowerfulBacon <26465327+PowerfulBacon@users.noreply.github.com> Date: Tue, 15 Mar 2022 17:44:09 +0000 Subject: [PATCH 03/16] Adds in a unit test that shows that I can successfully calculate the parent level of regions --- GJ2022.Tests/GJ2022.Tests.csproj | 1 + GJ2022.Tests/SuperTest.cs | 143 ++++++++++++++++++ GJ2022/Game/GameWorld/Regions/Region.cs | 17 ++- .../Game/GameWorld/Regions/RegionContainer.cs | 2 - .../Game/GameWorld/Regions/WorldRegionList.cs | 24 ++- 5 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 GJ2022.Tests/SuperTest.cs diff --git a/GJ2022.Tests/GJ2022.Tests.csproj b/GJ2022.Tests/GJ2022.Tests.csproj index 0e45ef8..7fd710e 100644 --- a/GJ2022.Tests/GJ2022.Tests.csproj +++ b/GJ2022.Tests/GJ2022.Tests.csproj @@ -173,6 +173,7 @@ + diff --git a/GJ2022.Tests/SuperTest.cs b/GJ2022.Tests/SuperTest.cs new file mode 100644 index 0000000..108969c --- /dev/null +++ b/GJ2022.Tests/SuperTest.cs @@ -0,0 +1,143 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GJ2022.Tests +{ + [TestClass] + public class SuperTest + { + + [TestMethod] + public void DoTest() + { + List<(int, int, int)> testValues = new List<(int, int, int)>() { + //First row + (0, 0, 0), + (1, 0, 1), + (2, 0, 2), + (3, 0, 2), + (4, 0, 3), + (5, 0, 3), + (6, 0, 3), + (7, 0, 3), + + (0, 1, 1), + (1, 1, 0), + (2, 1, 2), + (3, 1, 2), + (4, 1, 3), + (5, 1, 3), + (6, 1, 3), + (7, 1, 3), + + (0, 2, 2), + (1, 2, 2), + (2, 2, 0), + (3, 2, 1), + (4, 2, 3), + (5, 2, 3), + (6, 2, 3), + (7, 2, 3), + + (0, 3, 2), + (1, 3, 2), + (2, 3, 1), + (3, 3, 0), + (4, 3, 3), + (5, 3, 3), + (6, 3, 3), + (7, 3, 3), + + (0, 4, 3), + (1, 4, 3), + (2, 4, 3), + (3, 4, 3), + (4, 4, 0), + (5, 4, 1), + (6, 4, 2), + (7, 4, 2), + + (0, 5, 3), + (1, 5, 3), + (2, 5, 3), + (3, 5, 3), + (4, 5, 1), + (5, 5, 0), + (6, 5, 2), + (7, 5, 2), + + (0, 6, 3), + (1, 6, 3), + (2, 6, 3), + (3, 6, 3), + (4, 6, 2), + (5, 6, 2), + (6, 6, 0), + (7, 6, 1), + + (0, 7, 3), + (1, 7, 3), + (2, 7, 3), + (3, 7, 3), + (4, 7, 2), + (5, 7, 2), + (6, 7, 1), + (7, 7, 0), + + (7, 8, 4), + (11, 8, 2), + (9, 13, 3), + }; + + bool failed = false; + + foreach ((int, int, int) testValue in testValues) + { + if (GetResult(testValue.Item1, testValue.Item2) != testValue.Item3) + { + Log.WriteLine($"Fail: ({testValue.Item1}, {testValue.Item2}): Expected: {testValue.Item3} Actual: {GetResult(testValue.Item1, testValue.Item2)}"); + failed = true; + } + } + + if (failed) + Assert.Fail("Failed for at least 1 point"); + + } + + /// + /// Recursive algorithm to calculate the parent level of regions. + /// Recursion depth should never pass 1 in theory, however hits 2 for (1,0) and (0,1) + /// + private int GetResult(int x, int y) + { + if (x == y) + return 0; + int logx = (int)Math.Ceiling(Math.Log(x + 1, 2)); + int logy = (int)Math.Ceiling(Math.Log(y + 1, 2)); + int powx = (int)Math.Pow(2, logx - 1); + int powy = (int)Math.Pow(2, logy - 1); + int maxpow = Math.Max(powx, powy); + //Bottom Left + if (y >= maxpow && x < maxpow) + { + return logy; + } + //Top Right + else if (x >= maxpow && y < maxpow) + { + return logx; + } + //Bottom Right + else + { + return GetResult(x - powx, y - powy); + } + } + + } +} diff --git a/GJ2022/Game/GameWorld/Regions/Region.cs b/GJ2022/Game/GameWorld/Regions/Region.cs index 5a01911..8dae1bc 100644 --- a/GJ2022/Game/GameWorld/Regions/Region.cs +++ b/GJ2022/Game/GameWorld/Regions/Region.cs @@ -19,7 +19,7 @@ namespace GJ2022.Game.GameWorld.Regions /// ========= /// - For each point within a region, there must exist a valid path between those 2 points without leaving that region /// - A region shares at least 1 superparent with any region that can be accessed by it. - /// - At least 2 regions exist for each parent region. A region will never be an own child. + /// - Regions can be only children as long as they have a parent up high enough that has 2 children. /// /// Region Properties: /// ========= @@ -27,7 +27,12 @@ namespace GJ2022.Game.GameWorld.Regions /// /// Region Functionality: /// ========= - /// - + /// When a new region is created we need to create parent regions if neccessary. + /// To do this, we need to locate any regions that we are attached to directly. + /// We then need to calculate the level of our shared parent. + /// We then traverse up both trees until we hit the top level, or the level of the shared parent. + /// We then create parents where necessary until we hit the shared parent. + /// Both nodes should now have the same superparent /// public class Region { @@ -39,6 +44,14 @@ public class Region //A height of 0 indicates that this region is the parent of actual world positions. public int Height { get; } + //The X position of the region + //The world coordinated of the left divided by the primary level size + public int X { get; } + + //The Y position of the region + //The world coordinated of the bottom divided by the primary level size + public int Y { get; } + /// /// Instantiate an instance of region based on a parent instance. /// diff --git a/GJ2022/Game/GameWorld/Regions/RegionContainer.cs b/GJ2022/Game/GameWorld/Regions/RegionContainer.cs index cf80a80..978e54c 100644 --- a/GJ2022/Game/GameWorld/Regions/RegionContainer.cs +++ b/GJ2022/Game/GameWorld/Regions/RegionContainer.cs @@ -9,7 +9,5 @@ namespace GJ2022.Game.GameWorld.Regions public class RegionContainer { - - } } diff --git a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs index f440c0b..7c0a1b7 100644 --- a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs +++ b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs @@ -31,6 +31,17 @@ public class WorldRegionList /// public PositionBasedBinaryList regions = new PositionBasedBinaryList(); + /// + /// Contains a list that uses the index as the depth of the region and holds a position based binary list that holds the regions containers + /// stored at that location. + /// Example: + /// [0] = {(0, 0), (1, 0), (0, 1), (1, 1)} + /// [1] = {(0, 0)} + /// Where the coordinates represent keys of the position based binary list. + /// The associated value would be a list of regions within that section of the world. + /// + public List> regionContainersByPosition = new List>(); + /// /// Generates the region that contains the provided X,Y world coordinates. /// @@ -45,7 +56,18 @@ public void GenerateWorldSection(int x, int y) if (regions.Get(regionX, regionY) != null) return; //The region doesn't exist, so we need to generate one - //TODO + + } + + /// + /// Returns the level at which 2 nodes would in theory have a shared parent. + /// + /// The first region + /// The second region + /// An integer value representing the theoretical level that 2 regions would have a shared parent on. If 2 nodes are siblings, 1 is returned. If the nodes are the same 0 is returned. + private int GetSharedParentLevel(Region region, Region sibling) + { + throw new NotImplementedException(); } } From 1663c71a820c3f9cddc1cafbc0b2485b4993e6e8 Mon Sep 17 00:00:00 2001 From: PowerfulBacon <26465327+PowerfulBacon@users.noreply.github.com> Date: Tue, 15 Mar 2022 17:52:31 +0000 Subject: [PATCH 04/16] Update SuperTest.cs --- GJ2022.Tests/SuperTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/GJ2022.Tests/SuperTest.cs b/GJ2022.Tests/SuperTest.cs index 108969c..0aec4b2 100644 --- a/GJ2022.Tests/SuperTest.cs +++ b/GJ2022.Tests/SuperTest.cs @@ -91,6 +91,9 @@ public void DoTest() (7, 8, 4), (11, 8, 2), (9, 13, 3), + + (8+16, 9+16, 1), + (8+16, 10+16, 2), }; bool failed = false; From 0df3a20065fd46862ae0dafb9dd230fe4e84bd29 Mon Sep 17 00:00:00 2001 From: PowerfulBacon <26465327+PowerfulBacon@users.noreply.github.com> Date: Tue, 15 Mar 2022 17:53:59 +0000 Subject: [PATCH 05/16] Update SuperTest.cs --- GJ2022.Tests/SuperTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/GJ2022.Tests/SuperTest.cs b/GJ2022.Tests/SuperTest.cs index 0aec4b2..a41c7f5 100644 --- a/GJ2022.Tests/SuperTest.cs +++ b/GJ2022.Tests/SuperTest.cs @@ -115,6 +115,8 @@ public void DoTest() /// /// Recursive algorithm to calculate the parent level of regions. /// Recursion depth should never pass 1 in theory, however hits 2 for (1,0) and (0,1) + /// + /// I am sure there is a way to mathematically optimise this but I am not sure how /// private int GetResult(int x, int y) { From 57f1d3d0682434b50be79ac5a9d9aff0a5d6e98c Mon Sep 17 00:00:00 2001 From: PowerfulBacon <26465327+PowerfulBacon@users.noreply.github.com> Date: Tue, 15 Mar 2022 17:59:07 +0000 Subject: [PATCH 06/16] Update SuperTest.cs --- GJ2022.Tests/SuperTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GJ2022.Tests/SuperTest.cs b/GJ2022.Tests/SuperTest.cs index a41c7f5..1a9e56b 100644 --- a/GJ2022.Tests/SuperTest.cs +++ b/GJ2022.Tests/SuperTest.cs @@ -137,7 +137,7 @@ private int GetResult(int x, int y) { return logx; } - //Bottom Right + //Bottom Right, translate so that we aren't the top right anymore else { return GetResult(x - powx, y - powy); From 8b0ff383c2844970deb56394f47dbc7df0ebc877 Mon Sep 17 00:00:00 2001 From: PowerfulBacon <26465327+PowerfulBacon@users.noreply.github.com> Date: Wed, 16 Mar 2022 14:34:59 +0000 Subject: [PATCH 07/16] Updates the unit tests --- GJ2022.Tests/RegionTests.cs | 103 +++++++++++++++++++++++++ GJ2022.Tests/SuperTest.cs | 148 ------------------------------------ 2 files changed, 103 insertions(+), 148 deletions(-) delete mode 100644 GJ2022.Tests/SuperTest.cs diff --git a/GJ2022.Tests/RegionTests.cs b/GJ2022.Tests/RegionTests.cs index e888510..5a70efb 100644 --- a/GJ2022.Tests/RegionTests.cs +++ b/GJ2022.Tests/RegionTests.cs @@ -18,5 +18,108 @@ public void TestRegionConnectivity() } + [TestMethod] + public void TestSharedParentImplementation() + { + List<(int, int, int)> testValues = new List<(int, int, int)>() { + //First row + (0, 0, 0), + (1, 0, 1), + (2, 0, 2), + (3, 0, 2), + (4, 0, 3), + (5, 0, 3), + (6, 0, 3), + (7, 0, 3), + + (0, 1, 1), + (1, 1, 0), + (2, 1, 2), + (3, 1, 2), + (4, 1, 3), + (5, 1, 3), + (6, 1, 3), + (7, 1, 3), + + (0, 2, 2), + (1, 2, 2), + (2, 2, 0), + (3, 2, 1), + (4, 2, 3), + (5, 2, 3), + (6, 2, 3), + (7, 2, 3), + + (0, 3, 2), + (1, 3, 2), + (2, 3, 1), + (3, 3, 0), + (4, 3, 3), + (5, 3, 3), + (6, 3, 3), + (7, 3, 3), + + (0, 4, 3), + (1, 4, 3), + (2, 4, 3), + (3, 4, 3), + (4, 4, 0), + (5, 4, 1), + (6, 4, 2), + (7, 4, 2), + + (0, 5, 3), + (1, 5, 3), + (2, 5, 3), + (3, 5, 3), + (4, 5, 1), + (5, 5, 0), + (6, 5, 2), + (7, 5, 2), + + (0, 6, 3), + (1, 6, 3), + (2, 6, 3), + (3, 6, 3), + (4, 6, 2), + (5, 6, 2), + (6, 6, 0), + (7, 6, 1), + + (0, 7, 3), + (1, 7, 3), + (2, 7, 3), + (3, 7, 3), + (4, 7, 2), + (5, 7, 2), + (6, 7, 1), + (7, 7, 0), + + (7, 8, 4), + (11, 8, 2), + (9, 13, 3), + + (8+16, 9+16, 1), + (8+16, 10+16, 2), + }; + + bool failed = false; + + WorldRegionList wrl = new WorldRegionList(); + + foreach ((int, int, int) testValue in testValues) + { + if (wrl.GetSharedParentLevel(testValue.Item1, testValue.Item2) != testValue.Item3) + { + Log.WriteLine($"Fail: ({testValue.Item1}, {testValue.Item2}): Expected: {testValue.Item3} Actual: {GetResult(testValue.Item1, testValue.Item2)}"); + failed = true; + } + } + + if (failed) + Assert.Fail("Failed for at least 1 point"); + + } + } } diff --git a/GJ2022.Tests/SuperTest.cs b/GJ2022.Tests/SuperTest.cs deleted file mode 100644 index 1a9e56b..0000000 --- a/GJ2022.Tests/SuperTest.cs +++ /dev/null @@ -1,148 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace GJ2022.Tests -{ - [TestClass] - public class SuperTest - { - - [TestMethod] - public void DoTest() - { - List<(int, int, int)> testValues = new List<(int, int, int)>() { - //First row - (0, 0, 0), - (1, 0, 1), - (2, 0, 2), - (3, 0, 2), - (4, 0, 3), - (5, 0, 3), - (6, 0, 3), - (7, 0, 3), - - (0, 1, 1), - (1, 1, 0), - (2, 1, 2), - (3, 1, 2), - (4, 1, 3), - (5, 1, 3), - (6, 1, 3), - (7, 1, 3), - - (0, 2, 2), - (1, 2, 2), - (2, 2, 0), - (3, 2, 1), - (4, 2, 3), - (5, 2, 3), - (6, 2, 3), - (7, 2, 3), - - (0, 3, 2), - (1, 3, 2), - (2, 3, 1), - (3, 3, 0), - (4, 3, 3), - (5, 3, 3), - (6, 3, 3), - (7, 3, 3), - - (0, 4, 3), - (1, 4, 3), - (2, 4, 3), - (3, 4, 3), - (4, 4, 0), - (5, 4, 1), - (6, 4, 2), - (7, 4, 2), - - (0, 5, 3), - (1, 5, 3), - (2, 5, 3), - (3, 5, 3), - (4, 5, 1), - (5, 5, 0), - (6, 5, 2), - (7, 5, 2), - - (0, 6, 3), - (1, 6, 3), - (2, 6, 3), - (3, 6, 3), - (4, 6, 2), - (5, 6, 2), - (6, 6, 0), - (7, 6, 1), - - (0, 7, 3), - (1, 7, 3), - (2, 7, 3), - (3, 7, 3), - (4, 7, 2), - (5, 7, 2), - (6, 7, 1), - (7, 7, 0), - - (7, 8, 4), - (11, 8, 2), - (9, 13, 3), - - (8+16, 9+16, 1), - (8+16, 10+16, 2), - }; - - bool failed = false; - - foreach ((int, int, int) testValue in testValues) - { - if (GetResult(testValue.Item1, testValue.Item2) != testValue.Item3) - { - Log.WriteLine($"Fail: ({testValue.Item1}, {testValue.Item2}): Expected: {testValue.Item3} Actual: {GetResult(testValue.Item1, testValue.Item2)}"); - failed = true; - } - } - - if (failed) - Assert.Fail("Failed for at least 1 point"); - - } - - /// - /// Recursive algorithm to calculate the parent level of regions. - /// Recursion depth should never pass 1 in theory, however hits 2 for (1,0) and (0,1) - /// - /// I am sure there is a way to mathematically optimise this but I am not sure how - /// - private int GetResult(int x, int y) - { - if (x == y) - return 0; - int logx = (int)Math.Ceiling(Math.Log(x + 1, 2)); - int logy = (int)Math.Ceiling(Math.Log(y + 1, 2)); - int powx = (int)Math.Pow(2, logx - 1); - int powy = (int)Math.Pow(2, logy - 1); - int maxpow = Math.Max(powx, powy); - //Bottom Left - if (y >= maxpow && x < maxpow) - { - return logy; - } - //Top Right - else if (x >= maxpow && y < maxpow) - { - return logx; - } - //Bottom Right, translate so that we aren't the top right anymore - else - { - return GetResult(x - powx, y - powy); - } - } - - } -} From 9b7eed0e12ed32e296e67d5254b95e52f78a8071 Mon Sep 17 00:00:00 2001 From: PowerfulBacon <26465327+PowerfulBacon@users.noreply.github.com> Date: Wed, 16 Mar 2022 17:06:54 +0000 Subject: [PATCH 08/16] Successful region implementation --- GJ2022.Tests/GJ2022.Tests.csproj | 1 - GJ2022.Tests/RegionTests.cs | 25 ++- GJ2022/Game/GameWorld/Regions/Region.cs | 13 +- .../Game/GameWorld/Regions/WorldRegionList.cs | 179 +++++++++++++++++- 4 files changed, 208 insertions(+), 10 deletions(-) diff --git a/GJ2022.Tests/GJ2022.Tests.csproj b/GJ2022.Tests/GJ2022.Tests.csproj index 7fd710e..0e45ef8 100644 --- a/GJ2022.Tests/GJ2022.Tests.csproj +++ b/GJ2022.Tests/GJ2022.Tests.csproj @@ -173,7 +173,6 @@ - diff --git a/GJ2022.Tests/RegionTests.cs b/GJ2022.Tests/RegionTests.cs index 5a70efb..3c550d4 100644 --- a/GJ2022.Tests/RegionTests.cs +++ b/GJ2022.Tests/RegionTests.cs @@ -13,9 +13,25 @@ public class RegionTests { [TestMethod] - public void TestRegionConnectivity() + public void TestRegionWorldSection() { - + WorldRegionList wrl = new WorldRegionList(); + //Test pathing within a region + wrl.GenerateWorldSection(0, 0); + Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(7, 7)), "Failed to locate valid path within own region"); + //Test pathing to an adjacent region + wrl.GenerateWorldSection(8, 0); + Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(8, 0)), "Failed to locate valid path within adjacent region"); + //Test invalid pathing + wrl.GenerateWorldSection(24, 0); + Assert.IsFalse(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(24, 0)), "Failed to identify that a path shouldn't exist between 2 non-connected regions"); + //Test connection + wrl.GenerateWorldSection(16, 0); + Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(24, 0)), "Failed to identify completed path between a line of 4 regions"); + //Test 2 dimension functionality + wrl.GenerateWorldSection(24, 16); + wrl.GenerateWorldSection(24, 8); + Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(24, 16)), "Failed to identify completed path in a 2D grid"); } [TestMethod] @@ -109,9 +125,10 @@ public void TestSharedParentImplementation() foreach ((int, int, int) testValue in testValues) { - if (wrl.GetSharedParentLevel(testValue.Item1, testValue.Item2) != testValue.Item3) + int result = wrl.GetSharedParentLevel(testValue.Item1, testValue.Item2); + if (result != testValue.Item3) { - Log.WriteLine($"Fail: ({testValue.Item1}, {testValue.Item2}): Expected: {testValue.Item3} Actual: {GetResult(testValue.Item1, testValue.Item2)}"); + Log.WriteLine($"Fail: ({testValue.Item1}, {testValue.Item2}): Expected: {testValue.Item3} Actual: {result}"); failed = true; } } diff --git a/GJ2022/Game/GameWorld/Regions/Region.cs b/GJ2022/Game/GameWorld/Regions/Region.cs index 8dae1bc..8174e2e 100644 --- a/GJ2022/Game/GameWorld/Regions/Region.cs +++ b/GJ2022/Game/GameWorld/Regions/Region.cs @@ -37,8 +37,12 @@ namespace GJ2022.Game.GameWorld.Regions public class Region { + private static int count = 0; + + public int Id { get; } = count++; + //The parent region - public Region Parent { get; private set; } + public Region Parent { get; set; } //The region height (How many parent's up are we) //A height of 0 indicates that this region is the parent of actual world positions. @@ -52,6 +56,13 @@ public class Region //The world coordinated of the bottom divided by the primary level size public int Y { get; } + public Region(int x, int y, int height = 0) + { + X = x; + Y = y; + Height = height; + } + /// /// Instantiate an instance of region based on a parent instance. /// diff --git a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs index 7c0a1b7..7da5a3e 100644 --- a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs +++ b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs @@ -42,6 +42,14 @@ public class WorldRegionList /// public List> regionContainersByPosition = new List>(); + /// + /// Print the world region list + /// + public void Print() + { + Log.WriteLine(regions, LogType.DEBUG); + } + /// /// Generates the region that contains the provided X,Y world coordinates. /// @@ -52,11 +60,142 @@ public void GenerateWorldSection(int x, int y) //since we don't want region (x, y), (-x, y), (x, -y), (-x, -y) to all be assigned to region (0, 0) int regionX = (int)Math.Floor((float)x / REGION_PRIMARY_LEVEL_SIZE); int regionY = (int)Math.Floor((float)y / REGION_PRIMARY_LEVEL_SIZE); + Log.WriteLine($"Generating region ({regionX}, {regionY})"); //Check if the region exists already - if (regions.Get(regionX, regionY) != null) + if (regions.Get(x, y) != null) return; //The region doesn't exist, so we need to generate one - + bool[,] processedNodes = new bool[REGION_PRIMARY_LEVEL_SIZE, REGION_PRIMARY_LEVEL_SIZE]; + //Check each node and flood fill a region if necessary + for (int nodeX = 0; nodeX < REGION_PRIMARY_LEVEL_SIZE; nodeX++) + { + for (int nodeY = 0; nodeY < REGION_PRIMARY_LEVEL_SIZE; nodeY++) + { + //This node has already been assigned a region + if (processedNodes[nodeX, nodeY]) + continue; + //Get the real world coordinates + int realX = regionX * REGION_PRIMARY_LEVEL_SIZE + nodeX; + int realY = regionY * REGION_PRIMARY_LEVEL_SIZE + nodeY; + //This position is solid, ignore (We don't need to update processedNodes, since we will never again access it) + if (World.IsSolid(realX, realY)) + continue; + //Create a new region and flood fill outwards + Region createdRegion = new Region(regionX, regionY); + Log.WriteLine($"Created new region {createdRegion.Id} at ({regionX}, {regionY})"); + //Have the position join this region + regions.Add(realX, realY, createdRegion); + Log.WriteLine($"({realX}, {realY}) joined region {createdRegion.Id}"); + //During this process, get the adjacent regions so we can match up parents + List adjacentRegions = new List(); + //Processing queue + Queue> toProcess = new Queue>(); + toProcess.Enqueue(new Vector(nodeX, nodeY)); + //While we still have places to floor fill + while (toProcess.Count > 0) + { + //Dequeue + Vector relativePosition = toProcess.Dequeue(); + //If current node is already processed, skip + if (processedNodes[relativePosition.X, relativePosition.Y]) + continue; + //Calculate the world position of the current node + Vector worldPosition = new Vector(regionX * REGION_PRIMARY_LEVEL_SIZE, regionY * REGION_PRIMARY_LEVEL_SIZE) + relativePosition; + //Set node processed + processedNodes[relativePosition.X, relativePosition.Y] = true; + //If current node is a wall, skip + if (World.IsSolid(worldPosition.X, worldPosition.Y)) + continue; + //Join the region + regions.Add(worldPosition.X, worldPosition.Y, createdRegion); + Log.WriteLine($"({worldPosition.X}, {worldPosition.Y}) joined region {createdRegion.Id}"); + //Check if we have any adjacent regions + Region adjacent; + //Add adjacent nodes (Assuming they are within bounds) + if (relativePosition.X < REGION_PRIMARY_LEVEL_SIZE - 1) + toProcess.Enqueue(new Vector(relativePosition.X + 1, relativePosition.Y)); + else if ((adjacent = regions.Get(worldPosition.X + 1, worldPosition.Y)) != null && !adjacentRegions.Contains(adjacent)) + adjacentRegions.Add(adjacent); + if (relativePosition.X > 0) + toProcess.Enqueue(new Vector(relativePosition.X - 1, relativePosition.Y)); + else if ((adjacent = regions.Get(worldPosition.X - 1, worldPosition.Y)) != null && !adjacentRegions.Contains(adjacent)) + adjacentRegions.Add(adjacent); + if (relativePosition.Y < REGION_PRIMARY_LEVEL_SIZE - 1) + toProcess.Enqueue(new Vector(relativePosition.X, relativePosition.Y + 1)); + else if ((adjacent = regions.Get(worldPosition.X, worldPosition.Y + 1)) != null && !adjacentRegions.Contains(adjacent)) + adjacentRegions.Add(adjacent); + if (relativePosition.Y > 0) + toProcess.Enqueue(new Vector(relativePosition.X, relativePosition.Y - 1)); + else if ((adjacent = regions.Get(worldPosition.X, worldPosition.Y - 1)) != null && !adjacentRegions.Contains(adjacent)) + adjacentRegions.Add(adjacent); + } + //Now we have assigned all tiles within our region to be associated to this region + //We now need to calculate parent regions with all adjacent regions + foreach (Region adjacentRegion in adjacentRegions) + { + Log.WriteLine($"Joining region {createdRegion.Id} to {adjacentRegion.Id}"); + //The level of the shared parent + int sharedParentLevel = GetSharedParentLevel(createdRegion, adjacentRegion); + Log.WriteLine($"Shared parent level: {sharedParentLevel}"); + //Since these regions are adjacent, they should share a parent at the shared parent level. + //Get pointers for the current region + Region leftParent = createdRegion; + Region rightParent = adjacentRegion; + //Iterate upwards, creating parent regions where none exist + for (int level = 1; level <= sharedParentLevel - 1; level++) + { + //Create the parent if either are null + if (leftParent.Parent == null) + { + //Parent position is the integer division result of any + //child position divided by the region child size + //We can work this out by doing integer division with the position of + //The top level child and region children size ^ depth + leftParent.Parent = new Region( + createdRegion.X / (int)Math.Pow(REGION_CHILDREN_SIZE, level), + createdRegion.Y / (int)Math.Pow(REGION_CHILDREN_SIZE, level), + level); + Log.WriteLine($"Created new parent node for {leftParent.Id}, parent node {leftParent.Parent.Id} at depth {level}"); + Log.WriteLine($"Joined {leftParent.Id} --> {leftParent.Parent.Id}"); + } + if (rightParent.Parent == null) + { + rightParent.Parent = new Region( + adjacentRegion.X / (int)Math.Pow(REGION_CHILDREN_SIZE, level), + adjacentRegion.Y / (int)Math.Pow(REGION_CHILDREN_SIZE, level), + level); + Log.WriteLine($"Created new parent node for {rightParent.Id}, parent node {rightParent.Parent.Id} at depth {level}"); + Log.WriteLine($"Joined {rightParent.Id} --> {rightParent.Parent.Id}"); + } + //Get the left and right parent + leftParent = leftParent.Parent; + rightParent = rightParent.Parent; + } + //Now we are at a shared parent level, check if anything exists + //If so, then attach both to the shared parent + //If not, then create a new shared parent and attach both to it + if (rightParent.Parent != null) + { + leftParent.Parent = rightParent.Parent; + Log.WriteLine($"Joined {leftParent.Id} --> {rightParent.Parent.Id}"); + } + else + { + if (leftParent.Parent == null) + { + leftParent.Parent = new Region( + createdRegion.X / (int)Math.Pow(REGION_CHILDREN_SIZE, sharedParentLevel), + createdRegion.Y / (int)Math.Pow(REGION_CHILDREN_SIZE, sharedParentLevel), + sharedParentLevel); + Log.WriteLine($"Created new parent node {leftParent.Parent.Id} at depth {sharedParentLevel}"); + Log.WriteLine($"Joined {leftParent.Id} --> {leftParent.Parent.Id}"); + } + rightParent.Parent = leftParent.Parent; + Log.WriteLine($"Joined {rightParent.Id} --> {leftParent.Parent.Id}"); + } + } + } + } } /// @@ -65,9 +204,41 @@ public void GenerateWorldSection(int x, int y) /// The first region /// The second region /// An integer value representing the theoretical level that 2 regions would have a shared parent on. If 2 nodes are siblings, 1 is returned. If the nodes are the same 0 is returned. - private int GetSharedParentLevel(Region region, Region sibling) + public int GetSharedParentLevel(Region region, Region sibling) + { + return Math.Max(GetSharedParentLevel(region.X, sibling.X), GetSharedParentLevel(region.Y, sibling.Y)); + } + + /// + /// Recursive algorithm to calculate the parent level of regions. + /// Recursion depth should never pass 1 in theory, however hits 2 for (1,0) and (0,1) + /// + /// I am sure there is a way to mathematically optimise this but I am not sure how + /// + public int GetSharedParentLevel(int x, int y) { - throw new NotImplementedException(); + if (x == y) + return 0; + int logx = (int)Math.Ceiling(Math.Log(x + 1, 2)); + int logy = (int)Math.Ceiling(Math.Log(y + 1, 2)); + int powx = (int)Math.Pow(2, logx - 1); + int powy = (int)Math.Pow(2, logy - 1); + int maxpow = Math.Max(powx, powy); + //Bottom Left + if (y >= maxpow && x < maxpow) + { + return logy; + } + //Top Right + else if (x >= maxpow && y < maxpow) + { + return logx; + } + //Bottom Right, translate so that we aren't the top right anymore + else + { + return GetSharedParentLevel(x - powx, y - powy); + } } } From 447d6fd85e9db00a32d5715c7d0bade8c43b594d Mon Sep 17 00:00:00 2001 From: PowerfulBacon <26465327+PowerfulBacon@users.noreply.github.com> Date: Wed, 16 Mar 2022 17:47:38 +0000 Subject: [PATCH 09/16] Adds in optional logging --- .../Game/GameWorld/Regions/WorldRegionList.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs index 7da5a3e..76985c8 100644 --- a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs +++ b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs @@ -1,4 +1,6 @@ -using GJ2022.Utility.MathConstructs; +//#define REGION_LOGGING + +using GJ2022.Utility.MathConstructs; using System; using System.Collections.Generic; using System.Linq; @@ -60,7 +62,9 @@ public void GenerateWorldSection(int x, int y) //since we don't want region (x, y), (-x, y), (x, -y), (-x, -y) to all be assigned to region (0, 0) int regionX = (int)Math.Floor((float)x / REGION_PRIMARY_LEVEL_SIZE); int regionY = (int)Math.Floor((float)y / REGION_PRIMARY_LEVEL_SIZE); +#if REGION_LOGGING Log.WriteLine($"Generating region ({regionX}, {regionY})"); +#endif //Check if the region exists already if (regions.Get(x, y) != null) return; @@ -82,10 +86,11 @@ public void GenerateWorldSection(int x, int y) continue; //Create a new region and flood fill outwards Region createdRegion = new Region(regionX, regionY); +#if REGION_LOGGING Log.WriteLine($"Created new region {createdRegion.Id} at ({regionX}, {regionY})"); +#endif //Have the position join this region regions.Add(realX, realY, createdRegion); - Log.WriteLine($"({realX}, {realY}) joined region {createdRegion.Id}"); //During this process, get the adjacent regions so we can match up parents List adjacentRegions = new List(); //Processing queue @@ -108,7 +113,6 @@ public void GenerateWorldSection(int x, int y) continue; //Join the region regions.Add(worldPosition.X, worldPosition.Y, createdRegion); - Log.WriteLine($"({worldPosition.X}, {worldPosition.Y}) joined region {createdRegion.Id}"); //Check if we have any adjacent regions Region adjacent; //Add adjacent nodes (Assuming they are within bounds) @@ -133,10 +137,14 @@ public void GenerateWorldSection(int x, int y) //We now need to calculate parent regions with all adjacent regions foreach (Region adjacentRegion in adjacentRegions) { +#if REGION_LOGGING Log.WriteLine($"Joining region {createdRegion.Id} to {adjacentRegion.Id}"); +#endif //The level of the shared parent int sharedParentLevel = GetSharedParentLevel(createdRegion, adjacentRegion); +#if REGION_LOGGING Log.WriteLine($"Shared parent level: {sharedParentLevel}"); +#endif //Since these regions are adjacent, they should share a parent at the shared parent level. //Get pointers for the current region Region leftParent = createdRegion; @@ -155,8 +163,10 @@ public void GenerateWorldSection(int x, int y) createdRegion.X / (int)Math.Pow(REGION_CHILDREN_SIZE, level), createdRegion.Y / (int)Math.Pow(REGION_CHILDREN_SIZE, level), level); +#if REGION_LOGGING Log.WriteLine($"Created new parent node for {leftParent.Id}, parent node {leftParent.Parent.Id} at depth {level}"); Log.WriteLine($"Joined {leftParent.Id} --> {leftParent.Parent.Id}"); +#endif } if (rightParent.Parent == null) { @@ -164,8 +174,12 @@ public void GenerateWorldSection(int x, int y) adjacentRegion.X / (int)Math.Pow(REGION_CHILDREN_SIZE, level), adjacentRegion.Y / (int)Math.Pow(REGION_CHILDREN_SIZE, level), level); +#if REGION_LOGGING Log.WriteLine($"Created new parent node for {rightParent.Id}, parent node {rightParent.Parent.Id} at depth {level}"); - Log.WriteLine($"Joined {rightParent.Id} --> {rightParent.Parent.Id}"); + Log.WriteLine($"Joined {rightParent.Id} --> {rightParent.Parent.Id}"); t depth { level} + "); + Log.WriteLine($"Joined {leftParent.Id} --> {leftParent.Parent.Id}"); +#endif } //Get the left and right parent leftParent = leftParent.Parent; @@ -177,7 +191,9 @@ public void GenerateWorldSection(int x, int y) if (rightParent.Parent != null) { leftParent.Parent = rightParent.Parent; +#if REGION_LOGGING Log.WriteLine($"Joined {leftParent.Id} --> {rightParent.Parent.Id}"); +#endif } else { @@ -187,11 +203,15 @@ public void GenerateWorldSection(int x, int y) createdRegion.X / (int)Math.Pow(REGION_CHILDREN_SIZE, sharedParentLevel), createdRegion.Y / (int)Math.Pow(REGION_CHILDREN_SIZE, sharedParentLevel), sharedParentLevel); +#if REGION_LOGGING Log.WriteLine($"Created new parent node {leftParent.Parent.Id} at depth {sharedParentLevel}"); Log.WriteLine($"Joined {leftParent.Id} --> {leftParent.Parent.Id}"); +#endif } rightParent.Parent = leftParent.Parent; +#if REGION_LOGGING Log.WriteLine($"Joined {rightParent.Id} --> {leftParent.Parent.Id}"); +#endif } } } From 2eb8d4f305e95924cea3c9b0e7bac0831ec0a602 Mon Sep 17 00:00:00 2001 From: PowerfulBacon <> Date: Wed, 16 Mar 2022 22:56:11 +0000 Subject: [PATCH 10/16] Removes unused content --- .../Game/GameWorld/Regions/RegionContainer.cs | 13 ------------- .../Game/GameWorld/Regions/WorldRegionList.cs | 19 +------------------ 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 GJ2022/Game/GameWorld/Regions/RegionContainer.cs diff --git a/GJ2022/Game/GameWorld/Regions/RegionContainer.cs b/GJ2022/Game/GameWorld/Regions/RegionContainer.cs deleted file mode 100644 index 978e54c..0000000 --- a/GJ2022/Game/GameWorld/Regions/RegionContainer.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace GJ2022.Game.GameWorld.Regions -{ - public class RegionContainer - { - - } -} diff --git a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs index 76985c8..6184515 100644 --- a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs +++ b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs @@ -33,24 +33,7 @@ public class WorldRegionList /// public PositionBasedBinaryList regions = new PositionBasedBinaryList(); - /// - /// Contains a list that uses the index as the depth of the region and holds a position based binary list that holds the regions containers - /// stored at that location. - /// Example: - /// [0] = {(0, 0), (1, 0), (0, 1), (1, 1)} - /// [1] = {(0, 0)} - /// Where the coordinates represent keys of the position based binary list. - /// The associated value would be a list of regions within that section of the world. - /// - public List> regionContainersByPosition = new List>(); - - /// - /// Print the world region list - /// - public void Print() - { - Log.WriteLine(regions, LogType.DEBUG); - } + /// /// Generates the region that contains the provided X,Y world coordinates. From 72e75f03efcfb76b2a6e90ce0e2e7e0990653b9d Mon Sep 17 00:00:00 2001 From: PowerfulBacon <> Date: Fri, 18 Mar 2022 00:30:17 +0000 Subject: [PATCH 11/16] Comments --- GJ2022/Game/GameWorld/Regions/Region.cs | 24 +++++++++++++++++++ .../Game/GameWorld/Regions/WorldRegionList.cs | 10 +++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/GJ2022/Game/GameWorld/Regions/Region.cs b/GJ2022/Game/GameWorld/Regions/Region.cs index 8174e2e..f5ff781 100644 --- a/GJ2022/Game/GameWorld/Regions/Region.cs +++ b/GJ2022/Game/GameWorld/Regions/Region.cs @@ -33,12 +33,21 @@ namespace GJ2022.Game.GameWorld.Regions /// We then traverse up both trees until we hit the top level, or the level of the shared parent. /// We then create parents where necessary until we hit the shared parent. /// Both nodes should now have the same superparent + /// + /// When removing a region: + /// ========= + /// Check if we have created a new region by blocking 2 nodes from each other within a region. + /// Create a new region if we need, setting the tiles that are in this region to be adjacent to this region + /// Locate the adjacent regions to the updated region + /// Calculate which adjacencies no longer exist but are represented in the data structure + /// Seperate the tree structure /// public class Region { private static int count = 0; + //The unique ID of this region, for debugging purposes public int Id { get; } = count++; //The parent region @@ -56,6 +65,21 @@ public class Region //The world coordinated of the bottom divided by the primary level size public int Y { get; } + //A list of adjacent regions to this region + //This is likely to be null for any region that isn't a primary + //level region. + //If this region isn't a primary region, then the adjacent regions is calculated + //by performing the union operation on the children's linked regions parents. + //TODO + public List AdjacentRegions { get; set; } + + /// + /// Instantiates a region based on an X and Y position, setting the region's height + /// to the height parameter + /// + /// The X value of the bottom left part of region + /// The Y value of the bottom left part of the region + /// The height of the node in the tree data structure public Region(int x, int y, int height = 0) { X = x; diff --git a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs index 6184515..a1b7328 100644 --- a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs +++ b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs @@ -33,7 +33,15 @@ public class WorldRegionList /// public PositionBasedBinaryList regions = new PositionBasedBinaryList(); - + /// + /// Update a node to no longer be solid + /// + public void SetNodeNonSolid(int x, int y) + { + //Calculate if we need to do anything at all within this region + //If we are now seperated from the rest of our region create + //a new region to represent this area and calculate adjacencies + } /// /// Generates the region that contains the provided X,Y world coordinates. From 59802e1adf7351b23ebe89f4dba04442cee2670d Mon Sep 17 00:00:00 2001 From: PowerfulBacon <26465327+PowerfulBacon@users.noreply.github.com> Date: Fri, 18 Mar 2022 13:43:37 +0000 Subject: [PATCH 12/16] Prepares writting the comments for this stuff --- .../Game/GameWorld/Regions/WorldRegionList.cs | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs index 6184515..cdff669 100644 --- a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs +++ b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs @@ -33,7 +33,45 @@ public class WorldRegionList /// public PositionBasedBinaryList regions = new PositionBasedBinaryList(); - + /// + /// Sets a node in the world to be solid + /// + /// The X position of the node to set to be solid + /// The Y position of the node to set to be solid + public void SetNodeSolid(int x, int y) + { + //Get and remove the node's region + Region affectedRegion = regions.Get(x, y); + regions.Remove(x, y); + //Calculate if we need to take any action at all + if (!NodeSolidRequiresUpdate(affectedRegion, x, y)) + return; + } + + /// + /// Returns true if the position at X, Y will cause a region to + /// be subdivided. + /// General Theory: + /// If we have only 1 open side return false. + /// If we have more than 1 open side, choose any side. + /// Flood fill that side outwards applying a unique ID to all nodes that are flood filled. + /// Once completed, go to the original node and ensure that all adjacent nodes have the same ID from what we + /// flood filled. + /// If any open adjacent nodes didn't get tagged by the flood fill, it means our region has + /// been subdivided. + /// + private bool NodeSolidRequiresUpdate(Region actingRegion, int x, int y) + { + //Flood fill all nodes in the region and see if we can reconnect with ourselfs + //Flood fill with IDs, giving unique values to north, south, east and west + int[,] floodFilledIds = new int[REGION_PRIMARY_LEVEL_SIZE, REGION_PRIMARY_LEVEL_SIZE]; + Queue> updateQueue = new Queue>(); + //Locate an adjacent, open node. + //Insert the first node + + //We did not re-encounter ourselves, so we need to update + return true; + } /// /// Generates the region that contains the provided X,Y world coordinates. From a1a613f0612841a63c7db2fc70a09a3bfd95dac0 Mon Sep 17 00:00:00 2001 From: PowerfulBacon <> Date: Fri, 18 Mar 2022 21:10:34 +0000 Subject: [PATCH 13/16] Updates world to be non-static --- GJ2022.Tests/RegionTests.cs | 67 +++++++++- .../Component_AtmosphericBlocker.cs | 8 +- .../Components/Generic/Component_Tracked.cs | 12 +- .../Component_InternalEntity_Hardsuit.cs | 2 +- GJ2022/Entities/Areas/Area.cs | 6 +- GJ2022/Entities/Debug/DebugCanister.cs | 2 +- GJ2022/Entities/Items/Item.cs | 16 +-- GJ2022/Entities/Markers/Marker.cs | 6 +- GJ2022/Entities/Markers/MiningMarker.cs | 8 +- GJ2022/Entities/Pawns/Health/Bodies/Body.cs | 2 +- GJ2022/Entities/Pawns/Pawn.cs | 8 +- GJ2022/Entities/Pawns/PawnBreathing.cs | 2 +- GJ2022/Entities/Structures/Fire.cs | 6 +- .../Structures/Power/AreaPowerController.cs | 8 +- .../Structures/Power/PacmanGenerator.cs | 4 +- .../Entities/Structures/Power/PowerConduit.cs | 6 +- GJ2022/Entities/Structures/Structure.cs | 6 +- GJ2022/Entities/Turfs/Turf.cs | 10 +- GJ2022/Game/GameWorld/Regions/Region.cs | 2 +- GJ2022/Game/GameWorld/World.cs | 122 +++++++++--------- GJ2022/Game/Power/Powernet.cs | 2 +- GJ2022/Managers/Stockpile/StockpileManager.cs | 4 +- .../PawnBehaviours/PawnActions/HaulItems.cs | 20 +-- .../PawnBehaviours/PawnActions/MineAction.cs | 8 +- GJ2022/PawnBehaviours/PawnActions/SmeltOre.cs | 8 +- GJ2022/Subsystems/AtmosphericsSystem.cs | 74 +++++------ GJ2022/Subsystems/MouseCollisionSubsystem.cs | 2 +- GJ2022/Subsystems/PathfindingSystem.cs | 28 ++-- GJ2022/Subsystems/PawnControllerSystem.cs | 2 +- GJ2022/Subsystems/Subsystem.cs | 2 +- GJ2022/UserInterface/UserInterfaceCreator.cs | 4 +- 31 files changed, 262 insertions(+), 195 deletions(-) diff --git a/GJ2022.Tests/RegionTests.cs b/GJ2022.Tests/RegionTests.cs index 3c550d4..ace0183 100644 --- a/GJ2022.Tests/RegionTests.cs +++ b/GJ2022.Tests/RegionTests.cs @@ -1,4 +1,6 @@ -using GJ2022.Game.GameWorld.Regions; +using GJ2022.Entities.Turfs; +using GJ2022.Game.GameWorld; +using GJ2022.Game.GameWorld.Current.Regions; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; @@ -12,6 +14,69 @@ namespace GJ2022.Tests public class RegionTests { + [TestMethod] + public void TestRegionCreation() + { + //Generate a wall + Turf solidTurf = new Turf(); + solidTurf.SetProperty("Solid", true); + //Reset the world + World.Current = new World(); + World.Current.SetTurf(4, 0, solidTurf); + World.Current.SetTurf(4, 1, solidTurf); + World.Current.SetTurf(4, 2, solidTurf); + World.Current.SetTurf(4, 3, solidTurf); + World.Current.SetTurf(4, 4, solidTurf); + World.Current.SetTurf(4, 5, solidTurf); + World.Current.SetTurf(4, 6, solidTurf); + World.Current.SetTurf(4, 7, solidTurf); + WorldRegionList wrl = new WorldRegionList(); + wrl.GenerateWorldSection(0, 0); + Region left = null; + for (int x = 0; x < 4; x++) + { + for (int y = 0; y < 8; y++) + { + if (left == null) + left = wrl.regions.Get(x, y); + Assert.AreEqual(left, wrl.regions.Get(x, y), "Expected left region to be the same"); + } + } + for (int x = 5; x < 8; x++) + { + for (int y = 0; y < 8; y++) + { + Assert.AreNotEqual(left, wrl.regions.Get(x, y), "Expected right region to be different"); + } + } + } + + [TestMethod] + public void TestRegionUpdateRequirements() + { + //Generate a wall + Turf solidTurf = new Turf(); + solidTurf.SetProperty("Solid", true); + //Reset the world + World.Current = new World(); + World.Current.SetTurf(4, 0, solidTurf); + World.Current.SetTurf(4, 1, solidTurf); + World.Current.SetTurf(4, 2, solidTurf); + World.Current.SetTurf(4, 3, solidTurf); + World.Current.SetTurf(4, 5, solidTurf); + World.Current.SetTurf(4, 6, solidTurf); + World.Current.SetTurf(4, 7, solidTurf); + WorldRegionList wrl = new WorldRegionList(); + wrl.GenerateWorldSection(0, 0); + Assert.IsTrue(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(0, 0), 4, 4)); + Assert.IsFalse(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(0, 0), 3, 1)); + Assert.IsFalse(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(0, 0), 5, 5)); + Assert.IsFalse(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(0, 0), 4, 6)); + Assert.IsFalse(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(0, 0), 3, 3)); + Assert.IsTrue(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(0, 0), 5, 4)); + Assert.IsTrue(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(0, 0), 3, 4)); + } + [TestMethod] public void TestRegionWorldSection() { diff --git a/GJ2022/Components/Atmospherics/Component_AtmosphericBlocker.cs b/GJ2022/Components/Atmospherics/Component_AtmosphericBlocker.cs index 701818a..9edf563 100644 --- a/GJ2022/Components/Atmospherics/Component_AtmosphericBlocker.cs +++ b/GJ2022/Components/Atmospherics/Component_AtmosphericBlocker.cs @@ -18,22 +18,22 @@ public override void OnComponentAdd() { Entity parent = Parent as Entity; blockingPosition = parent.Position; - World.AddAtmosphericBlocker(blockingPosition.X, blockingPosition.Y); + World.Current.AddAtmosphericBlocker(blockingPosition.X, blockingPosition.Y); Parent.RegisterSignal(Signal.SIGNAL_ENTITY_MOVED, -1, ParentMoveReact); } public override void OnComponentRemove() { - World.RemoveAtmosphericBlock(blockingPosition.X, blockingPosition.Y); + World.Current.RemoveAtmosphericBlock(blockingPosition.X, blockingPosition.Y); Parent.UnregisterSignal(Signal.SIGNAL_ENTITY_MOVED, ParentMoveReact); } private object ParentMoveReact(object source, params object[] parameters) { - World.RemoveAtmosphericBlock(blockingPosition.X, blockingPosition.Y); + World.Current.RemoveAtmosphericBlock(blockingPosition.X, blockingPosition.Y); Entity parent = Parent as Entity; blockingPosition = parent.Position; - World.AddAtmosphericBlocker(blockingPosition.X, blockingPosition.Y); + World.Current.AddAtmosphericBlocker(blockingPosition.X, blockingPosition.Y); return null; } diff --git a/GJ2022/Components/Generic/Component_Tracked.cs b/GJ2022/Components/Generic/Component_Tracked.cs index 555d5e0..73a7002 100644 --- a/GJ2022/Components/Generic/Component_Tracked.cs +++ b/GJ2022/Components/Generic/Component_Tracked.cs @@ -25,7 +25,7 @@ public override void OnComponentAdd() //Start tracking trackedX = (int)parent.Position.X; trackedY = (int)parent.Position.Y; - World.AddThing(Key, trackedX, trackedY, Parent); + World.Current.AddThing(Key, trackedX, trackedY, Parent); Parent.RegisterSignal(Signal.SIGNAL_ENTITY_MOVED, -1, OnParentMoved); Parent.RegisterSignal(Signal.SIGNAL_ENTITY_LOCATION, -1, OnLocationChanged); } @@ -36,7 +36,7 @@ public override void OnComponentRemove() Entity parent = Parent as Entity; //No track will exist if (parent.Location == null) - World.RemoveThing(Key, trackedX, trackedY, Parent); + World.Current.RemoveThing(Key, trackedX, trackedY, Parent); Parent.UnregisterSignal(Signal.SIGNAL_ENTITY_MOVED, OnParentMoved); Parent.UnregisterSignal(Signal.SIGNAL_ENTITY_LOCATION, OnLocationChanged); } @@ -48,14 +48,14 @@ private object OnLocationChanged(object source, params object[] parameters) //Remove the old track if(newLocation != null && oldLocation == null) - World.RemoveThing(Key, trackedX, trackedY, Parent); + World.Current.RemoveThing(Key, trackedX, trackedY, Parent); //Start tracking if (newLocation == null && oldLocation != null) { Entity parent = Parent as Entity; trackedX = (int)parent.Position.X; trackedY = (int)parent.Position.Y; - World.AddThing(Key, trackedX, trackedY, Parent); + World.Current.AddThing(Key, trackedX, trackedY, Parent); } return null; } @@ -68,11 +68,11 @@ private object OnParentMoved(object source, params object[] parameters) if (parent.Location != null) return null; //Remove the old track - World.RemoveThing(Key, trackedX, trackedY, Parent); + World.Current.RemoveThing(Key, trackedX, trackedY, Parent); //Start tracking trackedX = (int)parent.Position.X; trackedY = (int)parent.Position.Y; - World.AddThing(Key, trackedX, trackedY, Parent); + World.Current.AddThing(Key, trackedX, trackedY, Parent); return null; } diff --git a/GJ2022/Components/Items/Component_InternalEntity_Hardsuit.cs b/GJ2022/Components/Items/Component_InternalEntity_Hardsuit.cs index c9bae23..63b232e 100644 --- a/GJ2022/Components/Items/Component_InternalEntity_Hardsuit.cs +++ b/GJ2022/Components/Items/Component_InternalEntity_Hardsuit.cs @@ -59,7 +59,7 @@ private object OnPawnMoved(object source, params object[] parmeters) private bool ShouldDeployHelmet(Pawn pawn) { - Turf turf = World.GetTurf((int)pawn.Position.X, (int)pawn.Position.Y); + Turf turf = World.Current.GetTurf((int)pawn.Position.X, (int)pawn.Position.Y); Atmosphere locatedAtmosphere = turf?.Atmosphere?.ContainedAtmosphere; if (locatedAtmosphere == null) return true; diff --git a/GJ2022/Entities/Areas/Area.cs b/GJ2022/Entities/Areas/Area.cs index b523749..1531e5f 100644 --- a/GJ2022/Entities/Areas/Area.cs +++ b/GJ2022/Entities/Areas/Area.cs @@ -16,15 +16,15 @@ public Area() : base() public override bool Destroy() { Destroyed = true; - World.SetArea((int)Position.X, (int)Position.Y, null); + World.Current.SetArea((int)Position.X, (int)Position.Y, null); return base.Destroy(); } public override void Initialize(Vector initializePosition) { base.Initialize(initializePosition); - World.GetArea((int)initializePosition.X, (int)initializePosition.Y)?.Destroy(); - World.SetArea((int)initializePosition.X, (int)initializePosition.Y, this); + World.Current.GetArea((int)initializePosition.X, (int)initializePosition.Y)?.Destroy(); + World.Current.SetArea((int)initializePosition.X, (int)initializePosition.Y, this); } } } diff --git a/GJ2022/Entities/Debug/DebugCanister.cs b/GJ2022/Entities/Debug/DebugCanister.cs index c2b2493..fd8ed75 100644 --- a/GJ2022/Entities/Debug/DebugCanister.cs +++ b/GJ2022/Entities/Debug/DebugCanister.cs @@ -14,7 +14,7 @@ public class DebugCanister : Structure public DebugCanister(Vector position) : base(position, Layers.LAYER_STRUCTURE) { - Turf turf = World.GetTurf((int)position.X, (int)position.Y); + Turf turf = World.Current.GetTurf((int)position.X, (int)position.Y); if (turf == null) return; if (turf.Atmosphere == null) diff --git a/GJ2022/Entities/Items/Item.cs b/GJ2022/Entities/Items/Item.cs index a4dc324..f7854dc 100644 --- a/GJ2022/Entities/Items/Item.cs +++ b/GJ2022/Entities/Items/Item.cs @@ -44,8 +44,8 @@ public override bool Destroy() { base.Destroy(); Destroyed = true; - World.RemoveItem((int)Position.X, (int)Position.Y, this); - World.GetArea((int)Position.X, (int)Position.Y)?.SendSignal(Signal.SIGNAL_AREA_CONTENTS_REMOVED, this); + World.Current.RemoveItem((int)Position.X, (int)Position.Y, this); + World.Current.GetArea((int)Position.X, (int)Position.Y)?.SendSignal(Signal.SIGNAL_AREA_CONTENTS_REMOVED, this); //Handle inventory removal if (Location is Pawn holder) { @@ -63,11 +63,11 @@ public void OnMoved(Vector oldPosition) { if ((int)oldPosition.X == (int)Position.X && (int)oldPosition.Y == (int)Position.Y) return; - World.RemoveItem((int)oldPosition.X, (int)oldPosition.Y, this); - World.AddItem((int)Position.X, (int)Position.Y, this); + World.Current.RemoveItem((int)oldPosition.X, (int)oldPosition.Y, this); + World.Current.AddItem((int)Position.X, (int)Position.Y, this); //Calculate stockpile - World.GetArea((int)oldPosition.X, (int)oldPosition.Y)?.SendSignal(Signal.SIGNAL_AREA_CONTENTS_REMOVED, this); - World.GetArea((int)Position.X, (int)Position.Y)?.SendSignal(Signal.SIGNAL_AREA_CONTENTS_ADDED, this); + World.Current.GetArea((int)oldPosition.X, (int)oldPosition.Y)?.SendSignal(Signal.SIGNAL_AREA_CONTENTS_REMOVED, this); + World.Current.GetArea((int)Position.X, (int)Position.Y)?.SendSignal(Signal.SIGNAL_AREA_CONTENTS_ADDED, this); } public void OnMoved(Entity oldLocation) @@ -80,8 +80,8 @@ public void OnMoved(Entity oldLocation) return; } MouseCollisionSubsystem.Singleton.StopTracking(this); - World.RemoveItem((int)Position.X, (int)Position.Y, this); - World.GetArea((int)Position.X, (int)Position.Y)?.SendSignal(Signal.SIGNAL_AREA_CONTENTS_REMOVED, this); + World.Current.RemoveItem((int)Position.X, (int)Position.Y, this); + World.Current.GetArea((int)Position.X, (int)Position.Y)?.SendSignal(Signal.SIGNAL_AREA_CONTENTS_REMOVED, this); } public void UpdateCount() diff --git a/GJ2022/Entities/Markers/Marker.cs b/GJ2022/Entities/Markers/Marker.cs index cc7ad1d..431d1e9 100644 --- a/GJ2022/Entities/Markers/Marker.cs +++ b/GJ2022/Entities/Markers/Marker.cs @@ -17,19 +17,19 @@ public Marker(Vector position, float layer) : base(position, layer) Destroy(); return; } - Marker marker = World.GetMarker((int)position.X, (int)position.Y); + Marker marker = World.Current.GetMarker((int)position.X, (int)position.Y); if (marker != null) { marker.Destroy(); return; } - World.SetMarker((int)position.X, (int)position.Y, this); + World.Current.SetMarker((int)position.X, (int)position.Y, this); } public override bool Destroy() { Destroyed = true; - World.SetMarker((int)Position.X, (int)Position.Y, null); + World.Current.SetMarker((int)Position.X, (int)Position.Y, null); return base.Destroy(); } diff --git a/GJ2022/Entities/Markers/MiningMarker.cs b/GJ2022/Entities/Markers/MiningMarker.cs index 6c26c48..8e4467b 100644 --- a/GJ2022/Entities/Markers/MiningMarker.cs +++ b/GJ2022/Entities/Markers/MiningMarker.cs @@ -19,17 +19,17 @@ public MiningMarker(Vector position) : base(position, Layers.LAYER_MARKER if (Destroyed) return; //Register signal - World.GetTurf((int)Position.X, (int)Position.Y).RegisterSignal(Signal.SIGNAL_ENTITY_DESTROYED, 0, DestroyMarker); + World.Current.GetTurf((int)Position.X, (int)Position.Y).RegisterSignal(Signal.SIGNAL_ENTITY_DESTROYED, 0, DestroyMarker); } public override bool IsValidPosition() { - return World.GetThings("Mine", (int)Position.X, (int)Position.Y).Count > 0; + return World.Current.GetThings("Mine", (int)Position.X, (int)Position.Y).Count > 0; } public override bool Destroy() { - World.GetTurf((int)Position.X, (int)Position.Y)?.UnregisterSignal(Signal.SIGNAL_ENTITY_DESTROYED, DestroyMarker); + World.Current.GetTurf((int)Position.X, (int)Position.Y)?.UnregisterSignal(Signal.SIGNAL_ENTITY_DESTROYED, DestroyMarker); return base.Destroy(); } @@ -45,7 +45,7 @@ public void HandleAction(Pawn pawn) { new AudioSource().PlaySound($"effects/picaxe{World.Random.Next(1, 4)}.wav", Position.X, Position.Y); //Perform mining - World.GetTurf((int)Position.X, (int)Position.Y)?.SendSignal(Signal.SIGNAL_ENTITY_MINE, pawn); + World.Current.GetTurf((int)Position.X, (int)Position.Y)?.SendSignal(Signal.SIGNAL_ENTITY_MINE, pawn); if(!Destroyed) Destroy(); } diff --git a/GJ2022/Entities/Pawns/Health/Bodies/Body.cs b/GJ2022/Entities/Pawns/Health/Bodies/Body.cs index f031f3d..89df290 100644 --- a/GJ2022/Entities/Pawns/Health/Bodies/Body.cs +++ b/GJ2022/Entities/Pawns/Health/Bodies/Body.cs @@ -164,7 +164,7 @@ public void Process(float deltaTime) //Process bleeding ProcessBleeding(deltaTime); //Process pressure damage (TODO: If not protected via pressure resistant clothing) - Turf location = World.GetTurf((int)Parent.Position.X, (int)Parent.Position.Y); + Turf location = World.Current.GetTurf((int)Parent.Position.X, (int)Parent.Position.Y); float pressure = location?.Atmosphere?.ContainedAtmosphere.KiloPascalPressure ?? 0; for (int i = InsertedLimbs.Count - 1; i >= 0; i--) { diff --git a/GJ2022/Entities/Pawns/Pawn.cs b/GJ2022/Entities/Pawns/Pawn.cs index 892e396..d3ca002 100644 --- a/GJ2022/Entities/Pawns/Pawn.cs +++ b/GJ2022/Entities/Pawns/Pawn.cs @@ -195,7 +195,7 @@ private void TraversePath(float deltaTime, float _speed = -1) private float CalculateSpeed() { //If we have gravity return movement factor - if (World.HasGravity(Position)) + if (World.Current.HasGravity(Position)) return 0.04f * PawnBody.Movement; //Return regular speed return 4f; @@ -278,15 +278,15 @@ public void OnMoved(Vector oldPosition) { if ((int)oldPosition.X == (int)Position.X && (int)oldPosition.Y == (int)Position.Y) return; - World.RemovePawn((int)oldPosition.X, (int)oldPosition.Y, this); - World.AddPawn((int)Position.X, (int)Position.Y, this); + World.Current.RemovePawn((int)oldPosition.X, (int)oldPosition.Y, this); + World.Current.AddPawn((int)Position.X, (int)Position.Y, this); } public void OnMoved(Entity oldLocation) { if (oldLocation == Location) return; - World.RemovePawn((int)Position.X, (int)Position.Y, this); + World.Current.RemovePawn((int)Position.X, (int)Position.Y, this); } } diff --git a/GJ2022/Entities/Pawns/PawnBreathing.cs b/GJ2022/Entities/Pawns/PawnBreathing.cs index b5af8c6..c9b1148 100644 --- a/GJ2022/Entities/Pawns/PawnBreathing.cs +++ b/GJ2022/Entities/Pawns/PawnBreathing.cs @@ -32,7 +32,7 @@ public Atmosphere GetBreathSource() } } //Return atmosphere at the current location - return World.GetTurf((int)Position.X, (int)Position.Y)?.Atmosphere?.ContainedAtmosphere; + return World.Current.GetTurf((int)Position.X, (int)Position.Y)?.Atmosphere?.ContainedAtmosphere; } /// diff --git a/GJ2022/Entities/Structures/Fire.cs b/GJ2022/Entities/Structures/Fire.cs index 1c0e25e..e62f552 100644 --- a/GJ2022/Entities/Structures/Fire.cs +++ b/GJ2022/Entities/Structures/Fire.cs @@ -49,7 +49,7 @@ public override bool Destroy() public void Process(float deltaTime) { //Get the current turf - Turf turf = World.GetTurf((int)Position.X, (int)Position.Y); + Turf turf = World.Current.GetTurf((int)Position.X, (int)Position.Y); //Check turf atmos if (turf == null || turf.Atmosphere == null) { @@ -67,7 +67,7 @@ public void Process(float deltaTime) return; } //Hurt pawns - foreach (Pawn pawn in World.GetPawns(turf.X, turf.Y)) + foreach (Pawn pawn in World.Current.GetPawns(turf.X, turf.Y)) { pawn.PawnBody.ApplyDamageRandomly(new Burn(0.5f * deltaTime)); } @@ -94,7 +94,7 @@ public void Process(float deltaTime) //Go through directions Vector spreadDirction = directions[i]; //Check for atmospheric flow allowance - if (!World.AllowsAtmosphericFlow(x + spreadDirction[0], y + spreadDirction[1])) + if (!World.Current.AllowsAtmosphericFlow(x + spreadDirction[0], y + spreadDirction[1])) return; //Check for fires if (GlobalFires.Get(x + spreadDirction[0], y + spreadDirction[1]) != null) diff --git a/GJ2022/Entities/Structures/Power/AreaPowerController.cs b/GJ2022/Entities/Structures/Power/AreaPowerController.cs index 5438510..31fd2c1 100644 --- a/GJ2022/Entities/Structures/Power/AreaPowerController.cs +++ b/GJ2022/Entities/Structures/Power/AreaPowerController.cs @@ -40,8 +40,8 @@ public AreaPowerController(Vector position, Directions direction) : base( offset = new Vector(0, -offsetAmount); break; } - World.AddAreaPowerController((int)position.X, (int)position.Y, this); - World.AddPowernetInteractor((int)position.X, (int)position.Y, PowernetInteractor); + World.Current.AddAreaPowerController((int)position.X, (int)position.Y, this); + World.Current.AddPowernetInteractor((int)position.X, (int)position.Y, PowernetInteractor); Position += offset; //TODO: Move this to a definition file insertedCell = EntityCreator.CreateEntity("Cell_Standard", position); @@ -64,8 +64,8 @@ public AreaPowerController(Vector position, Directions direction) : base( public override bool Destroy() { Position -= offset; - World.RemoveAreaPowerController((int)Position.X, (int)Position.Y, this); - World.RemovePowernetInteractor((int)Position.X, (int)Position.Y, PowernetInteractor); + World.Current.RemoveAreaPowerController((int)Position.X, (int)Position.Y, this); + World.Current.RemovePowernetInteractor((int)Position.X, (int)Position.Y, PowernetInteractor); PowerProcessingSystem.Singleton.StopProcessing(this); return base.Destroy(); } diff --git a/GJ2022/Entities/Structures/Power/PacmanGenerator.cs b/GJ2022/Entities/Structures/Power/PacmanGenerator.cs index 5cf4144..96c14d4 100644 --- a/GJ2022/Entities/Structures/Power/PacmanGenerator.cs +++ b/GJ2022/Entities/Structures/Power/PacmanGenerator.cs @@ -17,7 +17,7 @@ class PacmanGenerator : Structure, IProcessable public PacmanGenerator(Vector position) : base(position, Layers.LAYER_STRUCTURE) { - World.AddPowernetInteractor((int)Position.X, (int)Position.Y, PowernetInteractor); + World.Current.AddPowernetInteractor((int)Position.X, (int)Position.Y, PowernetInteractor); PowerProcessingSystem.Singleton.StartProcessing(this); } @@ -31,7 +31,7 @@ public PacmanGenerator(Vector position) : base(position, Layers.LAYER_STR public override bool Destroy() { - World.RemovePowernetInteractor((int)Position.X, (int)Position.Y, PowernetInteractor); + World.Current.RemovePowernetInteractor((int)Position.X, (int)Position.Y, PowernetInteractor); PowerProcessingSystem.Singleton.StopProcessing(this); return base.Destroy(); } diff --git a/GJ2022/Entities/Structures/Power/PowerConduit.cs b/GJ2022/Entities/Structures/Power/PowerConduit.cs index 3de2c30..9d32edf 100644 --- a/GJ2022/Entities/Structures/Power/PowerConduit.cs +++ b/GJ2022/Entities/Structures/Power/PowerConduit.cs @@ -26,7 +26,7 @@ public PowerConduit(Vector position) : base(position, Layers.LAYER_CONDUI textObjectOffset = new Vector(0, -0.6f); attachedTextObject = new TextObject($"{Powernet?.PowernetId}", Colour.White, Position + textObjectOffset, TextObject.PositionModes.WORLD_POSITION, 0.4f); AddNode(); - World.SetPowerCable((int)position.X, (int)position.Y, this); + World.Current.SetPowerCable((int)position.X, (int)position.Y, this); } } @@ -61,7 +61,7 @@ public bool Destroy(bool initialized = true) if (!base.Destroy()) return false; RemoveNode(); - World.SetPowerCable((int)Position.X, (int)Position.Y, null); + World.Current.SetPowerCable((int)Position.X, (int)Position.Y, null); return true; } @@ -212,7 +212,7 @@ public void UpdateIconState() private PowerConduit LocateConduit(int x, int y) { - List locatedStructures = World.GetStructures(x, y); + List locatedStructures = World.Current.GetStructures(x, y); for (int i = locatedStructures.Count - 1; i >= 0; i = Math.Min(i - 1, locatedStructures.Count - 1)) { Structure structure = locatedStructures[i]; diff --git a/GJ2022/Entities/Structures/Structure.cs b/GJ2022/Entities/Structures/Structure.cs index 9b6afae..fb186de 100644 --- a/GJ2022/Entities/Structures/Structure.cs +++ b/GJ2022/Entities/Structures/Structure.cs @@ -15,7 +15,7 @@ public Structure() : base() public override void Initialize(Vector initializePosition) { - World.AddStructure((int)initializePosition.X, (int)initializePosition.Y, this); + World.Current.AddStructure((int)initializePosition.X, (int)initializePosition.Y, this); base.Initialize(initializePosition); } @@ -23,7 +23,7 @@ public override void Initialize(Vector initializePosition) public Structure(Vector position, float layer) : base(position, layer) { //Add the structure to the world list - World.AddStructure((int)position.X, (int)position.Y, this); + World.Current.AddStructure((int)position.X, (int)position.Y, this); } public bool Destroyed { get; private set; } = false; @@ -31,7 +31,7 @@ public Structure(Vector position, float layer) : base(position, layer) public override bool Destroy() { Destroyed = true; - World.RemoveStructure((int)Position.X, (int)Position.Y, this); + World.Current.RemoveStructure((int)Position.X, (int)Position.Y, this); return base.Destroy(); } } diff --git a/GJ2022/Entities/Turfs/Turf.cs b/GJ2022/Entities/Turfs/Turf.cs index 87edc08..130d379 100644 --- a/GJ2022/Entities/Turfs/Turf.cs +++ b/GJ2022/Entities/Turfs/Turf.cs @@ -46,15 +46,15 @@ public override void Initialize(Vector initializePosition) //Set the position to update the renderable Position = new Vector(X, Y); //Destroy the old turf - Turf oldTurf = World.GetTurf(X, Y); + Turf oldTurf = World.Current.GetTurf(X, Y); //Set the new turf oldTurf?.Destroy(true); - World.SetTurf(X, Y, this); + World.Current.SetTurf(X, Y, this); //Set the direction Direction = Directions.NONE; //Atmos flow blocking if (!AllowAtmosphericFlow) - World.AddAtmosphericBlocker(X, Y, false); + World.Current.AddAtmosphericBlocker(X, Y, false); //Tell the atmos system a turf was created / changed at this location if (oldTurf == null) AtmosphericsSystem.Singleton.OnTurfCreated(this); @@ -79,7 +79,7 @@ public bool Destroy(bool changed) #endif //Atmos flow blocking if (!AllowAtmosphericFlow) - World.RemoveAtmosphericBlock(X, Y, false); + World.Current.RemoveAtmosphericBlock(X, Y, false); //If we weren't changed, destroy the turf if (!changed) AtmosphericsSystem.Singleton.OnTurfDestroyed(this); @@ -89,7 +89,7 @@ public bool Destroy(bool changed) public override bool Destroy() { //Dereference - World.SetTurf(X, Y, null); + World.Current.SetTurf(X, Y, null); //Set destroyed Destroyed = true; return base.Destroy(); diff --git a/GJ2022/Game/GameWorld/Regions/Region.cs b/GJ2022/Game/GameWorld/Regions/Region.cs index f5ff781..d2079e7 100644 --- a/GJ2022/Game/GameWorld/Regions/Region.cs +++ b/GJ2022/Game/GameWorld/Regions/Region.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace GJ2022.Game.GameWorld.Regions +namespace GJ2022.Game.GameWorld.Current.Regions { /// /// Region system: diff --git a/GJ2022/Game/GameWorld/World.cs b/GJ2022/Game/GameWorld/World.cs index c686b2e..c32ed91 100644 --- a/GJ2022/Game/GameWorld/World.cs +++ b/GJ2022/Game/GameWorld/World.cs @@ -17,9 +17,11 @@ namespace GJ2022.Game.GameWorld { - public static class World + public class World { + public static World Current { get; set; } = new World(); + public static Random Random { get; } = new Random(); public static int EntitiesCreated { get; set; } = 0; @@ -43,76 +45,76 @@ public IntegerReference(int value) /// is the position based binary list containing the details of the /// things being tracked. /// - public static Dictionary>> TrackedComponentHandlers = new Dictionary>>(); + public Dictionary>> TrackedComponentHandlers = new Dictionary>>(); //Dictionary of turfs in the world - public static PositionBasedBinaryList WorldTurfs = new PositionBasedBinaryList(); + public PositionBasedBinaryList WorldTurfs = new PositionBasedBinaryList(); //Dictionary of areas in the world - public static PositionBasedBinaryList WorldAreas = new PositionBasedBinaryList(); + public PositionBasedBinaryList WorldAreas = new PositionBasedBinaryList(); //Dictionary of markers in the world - public static PositionBasedBinaryList WorldMarkers = new PositionBasedBinaryList(); + public PositionBasedBinaryList WorldMarkers = new PositionBasedBinaryList(); //Dictionary of power cables in the world - public static PositionBasedBinaryList PowerCables = new PositionBasedBinaryList(); + public PositionBasedBinaryList PowerCables = new PositionBasedBinaryList(); //Collection of interactors with the powernet. - public static PositionBasedBinaryList> PowernetInteractors = new PositionBasedBinaryList>(); + public PositionBasedBinaryList> PowernetInteractors = new PositionBasedBinaryList>(); //Dictionary containing all items in the world at a specified position. //When an item moves, it needs to be updated in this list. - public static PositionBasedBinaryList> WorldItems = new PositionBasedBinaryList>(); + public PositionBasedBinaryList> WorldItems = new PositionBasedBinaryList>(); //Dictionary containing all structures in the world - public static PositionBasedBinaryList> WorldStructures = new PositionBasedBinaryList>(); + public PositionBasedBinaryList> WorldStructures = new PositionBasedBinaryList>(); //Dictionary containing all mobs in the world - public static PositionBasedBinaryList> WorldPawns = new PositionBasedBinaryList>(); + public PositionBasedBinaryList> WorldPawns = new PositionBasedBinaryList>(); //Dictionary containing all mobs in the world - public static PositionBasedBinaryList> AreaPowerControllers = new PositionBasedBinaryList>(); + public PositionBasedBinaryList> AreaPowerControllers = new PositionBasedBinaryList>(); //An integer storing the amount of atmospheric blocking things at this location - private static PositionBasedBinaryList AtmosphericBlockers = new PositionBasedBinaryList(); + private PositionBasedBinaryList AtmosphericBlockers = new PositionBasedBinaryList(); //====================== // In range detectors //====================== - public static bool HasThingInRange(string thingGroup, int x, int y, int range, BinaryList>.BinaryListValidityCheckDelegate conditionalCheck = null) + public bool HasThingInRange(string thingGroup, int x, int y, int range, BinaryList>.BinaryListValidityCheckDelegate conditionalCheck = null) { if (!TrackedComponentHandlers.ContainsKey(thingGroup)) return false; return TrackedComponentHandlers[thingGroup].ElementsInRange(x - range, y - range, x + range, y + range, 0, -1, conditionalCheck); } - public static bool HasMarkerInRange(int x, int y, int range, BinaryList.BinaryListValidityCheckDelegate conditionalCheck = null) + public bool HasMarkerInRange(int x, int y, int range, BinaryList.BinaryListValidityCheckDelegate conditionalCheck = null) { return WorldMarkers.ElementsInRange(x - range, y - range, x + range, y + range, 0, -1, conditionalCheck); } - public static bool HasTurfInRange(int x, int y, int range, BinaryList.BinaryListValidityCheckDelegate conditionalCheck = null) + public bool HasTurfInRange(int x, int y, int range, BinaryList.BinaryListValidityCheckDelegate conditionalCheck = null) { return WorldTurfs.ElementsInRange(x - range, y - range, x + range, y + range, 0, -1, conditionalCheck); } - public static bool HasAreaInRange(int x, int y, int range, BinaryList.BinaryListValidityCheckDelegate conditionalCheck = null) + public bool HasAreaInRange(int x, int y, int range, BinaryList.BinaryListValidityCheckDelegate conditionalCheck = null) { return WorldAreas.ElementsInRange(x - range, y - range, x + range, y + range, 0, -1, conditionalCheck); } - public static bool HasItemsInRange(int x, int y, int range, BinaryList>.BinaryListValidityCheckDelegate conditionalCheck = null) + public bool HasItemsInRange(int x, int y, int range, BinaryList>.BinaryListValidityCheckDelegate conditionalCheck = null) { return WorldItems.ElementsInRange(x - range, y - range, x + range, y + range, 0, -1, conditionalCheck); } - public static bool HasStructuresInRange(int x, int y, int range, BinaryList>.BinaryListValidityCheckDelegate conditionalCheck = null) + public bool HasStructuresInRange(int x, int y, int range, BinaryList>.BinaryListValidityCheckDelegate conditionalCheck = null) { return WorldStructures.ElementsInRange(x - range, y - range, x + range, y + range, 0, -1, conditionalCheck); } - public static bool HasPawnsInRange(int x, int y, int range, BinaryList>.BinaryListValidityCheckDelegate conditionalCheck = null) + public bool HasPawnsInRange(int x, int y, int range, BinaryList>.BinaryListValidityCheckDelegate conditionalCheck = null) { return WorldPawns.ElementsInRange(x - range, y - range, x + range, y + range, 0, -1, conditionalCheck); } @@ -122,7 +124,7 @@ public static bool HasPawnsInRange(int x, int y, int range, BinaryList GetSpiralThings(string thingGroup, int original_x, int original_y, int range) + public List GetSpiralThings(string thingGroup, int original_x, int original_y, int range) where T : IComponentHandler { List output = new List(); @@ -150,7 +152,7 @@ public static List GetSpiralThings(string thingGroup, int original_x, int /// NOTE: It would be better to just iterate all the items (although that would /// require a distance check) /// - public static List GetSprialMarkers(int original_x, int original_y, int range) + public List GetSprialMarkers(int original_x, int original_y, int range) { List output = new List(); for (int r = 0; r <= range; r++) @@ -172,7 +174,7 @@ public static List GetSprialMarkers(int original_x, int original_y, int /// /// Get spiral items, ordered by distance from the origin /// - public static List GetSprialAreas(int original_x, int original_y, int range) + public List GetSprialAreas(int original_x, int original_y, int range) { List output = new List(); for (int r = 0; r <= range; r++) @@ -194,7 +196,7 @@ public static List GetSprialAreas(int original_x, int original_y, int rang /// /// Get spiral items, ordered by distance from the origin /// - public static List GetSprialItems(int original_x, int original_y, int range) + public List GetSprialItems(int original_x, int original_y, int range) { List output = new List(); for (int r = 0; r <= range; r++) @@ -216,7 +218,7 @@ public static List GetSprialItems(int original_x, int original_y, int rang /// /// Get spiral structures, ordered by distance from the origin /// - public static List GetSprialStructure(int original_x, int original_y, int range) + public List GetSprialStructure(int original_x, int original_y, int range) { List output = new List(); for (int r = 0; r <= range; r++) @@ -239,7 +241,7 @@ public static List GetSprialStructure(int original_x, int original_y, // Atmospheric Blockers //====================== - public static void AddAtmosphericBlocker(int x, int y, bool updateAtmos = true) + public void AddAtmosphericBlocker(int x, int y, bool updateAtmos = true) { IntegerReference reference = AtmosphericBlockers.Get(x, y); if (reference == null) @@ -252,7 +254,7 @@ public static void AddAtmosphericBlocker(int x, int y, bool updateAtmos = true) reference.Value++; } - public static void RemoveAtmosphericBlock(int x, int y, bool updateAtmos = true) + public void RemoveAtmosphericBlock(int x, int y, bool updateAtmos = true) { IntegerReference reference = AtmosphericBlockers.Get(x, y); if (reference == null) @@ -270,7 +272,7 @@ public static void RemoveAtmosphericBlock(int x, int y, bool updateAtmos = true) // Atmospheric Flow //====================== - public static bool AllowsAtmosphericFlow(int x, int y) + public bool AllowsAtmosphericFlow(int x, int y) { return AtmosphericBlockers.Get(x, y) == null; } @@ -279,7 +281,7 @@ public static bool AllowsAtmosphericFlow(int x, int y) // Things //====================== - public static List GetThings(string thingGroup, int x, int y) + public List GetThings(string thingGroup, int x, int y) { if (!TrackedComponentHandlers.ContainsKey(thingGroup)) return new List() { }; @@ -289,7 +291,7 @@ public static List GetThings(string thingGroup, int x, int y) /// /// Add an pawn to the world list /// - public static void AddThing(string thingGroup, int x, int y, IComponentHandler thing) + public void AddThing(string thingGroup, int x, int y, IComponentHandler thing) { if (!TrackedComponentHandlers.ContainsKey(thingGroup)) TrackedComponentHandlers.Add(thingGroup, new PositionBasedBinaryList>()); @@ -303,7 +305,7 @@ public static void AddThing(string thingGroup, int x, int y, IComponentHandler t /// /// Remove the pawn from the world list /// - public static bool RemoveThing(string thingGroup, int x, int y, IComponentHandler thing) + public bool RemoveThing(string thingGroup, int x, int y, IComponentHandler thing) { if (!TrackedComponentHandlers.ContainsKey(thingGroup)) return false; @@ -320,7 +322,7 @@ public static bool RemoveThing(string thingGroup, int x, int y, IComponentHandle // APCs //====================== - public static List GetAreaPowerControllers(int x, int y) + public List GetAreaPowerControllers(int x, int y) { return AreaPowerControllers.Get(x, y) ?? new List() { }; } @@ -328,7 +330,7 @@ public static List GetAreaPowerControllers(int x, int y) /// /// Add an pawn to the world list /// - public static void AddAreaPowerController(int x, int y, AreaPowerController apc) + public void AddAreaPowerController(int x, int y, AreaPowerController apc) { List located = AreaPowerControllers.Get(x, y); if (located != null) @@ -340,7 +342,7 @@ public static void AddAreaPowerController(int x, int y, AreaPowerController apc) /// /// Remove the pawn from the world list /// - public static bool RemoveAreaPowerController(int x, int y, AreaPowerController apc) + public bool RemoveAreaPowerController(int x, int y, AreaPowerController apc) { List located = AreaPowerControllers.Get(x, y); if (located == null) @@ -355,7 +357,7 @@ public static bool RemoveAreaPowerController(int x, int y, AreaPowerController a // Pawns //====================== - public static List GetPawns(int x, int y) + public List GetPawns(int x, int y) { return WorldPawns.Get(x, y) ?? new List() { }; } @@ -363,7 +365,7 @@ public static List GetPawns(int x, int y) /// /// Add an pawn to the world list /// - public static void AddPawn(int x, int y, Pawn pawn) + public void AddPawn(int x, int y, Pawn pawn) { List located = WorldPawns.Get(x, y); if (located != null) @@ -375,7 +377,7 @@ public static void AddPawn(int x, int y, Pawn pawn) /// /// Remove the pawn from the world list /// - public static bool RemovePawn(int x, int y, Pawn pawn) + public bool RemovePawn(int x, int y, Pawn pawn) { List located = WorldPawns.Get(x, y); if (located == null) @@ -390,7 +392,7 @@ public static bool RemovePawn(int x, int y, Pawn pawn) // Structures //====================== - public static List GetStructures(int x, int y) + public List GetStructures(int x, int y) { return WorldStructures.Get(x, y) ?? new List() { }; } @@ -398,7 +400,7 @@ public static List GetStructures(int x, int y) /// /// Add an structure to the world list /// - public static void AddStructure(int x, int y, Structure structure) + public void AddStructure(int x, int y, Structure structure) { List located = WorldStructures.Get(x, y); if (located != null) @@ -410,7 +412,7 @@ public static void AddStructure(int x, int y, Structure structure) /// /// Remove the istructuretem from the world list /// - public static bool RemoveStructure(int x, int y, Structure structure) + public bool RemoveStructure(int x, int y, Structure structure) { List located = WorldStructures.Get(x, y); if (located == null) @@ -425,7 +427,7 @@ public static bool RemoveStructure(int x, int y, Structure structure) // All Entities //====================== - public static List GetEntities(int x, int y) + public List GetEntities(int x, int y) { List output = new List(); output.AddRange(GetItems(x, y)); @@ -444,7 +446,7 @@ public static List GetEntities(int x, int y) // Items //====================== - public static List GetItems(int x, int y) + public List GetItems(int x, int y) { return WorldItems.Get(x, y) ?? new List() { }; } @@ -452,7 +454,7 @@ public static List GetItems(int x, int y) /// /// Add an item to the world list /// - public static void AddItem(int x, int y, Item item) + public void AddItem(int x, int y, Item item) { List located = WorldItems.Get(x, y); if (located != null) @@ -464,7 +466,7 @@ public static void AddItem(int x, int y, Item item) /// /// Remove the item from the world list /// - public static bool RemoveItem(int x, int y, Item item) + public bool RemoveItem(int x, int y, Item item) { List located = WorldItems.Get(x, y); if (located == null) @@ -482,7 +484,7 @@ public static bool RemoveItem(int x, int y, Item item) /// /// Get the area at the specified location. /// - public static PowerConduit GetPowerCable(int x, int y) + public PowerConduit GetPowerCable(int x, int y) { return PowerCables.Get(x, y); } @@ -490,7 +492,7 @@ public static PowerConduit GetPowerCable(int x, int y) /// /// Set the area at the specified location /// - public static void SetPowerCable(int x, int y, PowerConduit cable) + public void SetPowerCable(int x, int y, PowerConduit cable) { if (cable == null) PowerCables.Remove(x, y); @@ -510,7 +512,7 @@ public static void SetPowerCable(int x, int y, PowerConduit cable) // Powernet Interactors //====================== - public static List GetPowernetInteractors(int x, int y) + public List GetPowernetInteractors(int x, int y) { return PowernetInteractors.Get(x, y) ?? new List() { }; } @@ -518,7 +520,7 @@ public static List GetPowernetInteractors(int x, int y) /// /// Add an structure to the world list /// - public static void AddPowernetInteractor(int x, int y, PowernetInteractor interactor) + public void AddPowernetInteractor(int x, int y, PowernetInteractor interactor) { List located = PowernetInteractors.Get(x, y); if (located != null) @@ -537,7 +539,7 @@ public static void AddPowernetInteractor(int x, int y, PowernetInteractor intera /// /// Remove the istructuretem from the world list /// - public static bool RemovePowernetInteractor(int x, int y, PowernetInteractor interactor) + public bool RemovePowernetInteractor(int x, int y, PowernetInteractor interactor) { List located = PowernetInteractors.Get(x, y); if (located == null) @@ -555,7 +557,7 @@ public static bool RemovePowernetInteractor(int x, int y, PowernetInteractor int /// /// Get the area at the specified location. /// - public static Area GetArea(int x, int y) + public Area GetArea(int x, int y) { return WorldAreas.Get(x, y); } @@ -563,7 +565,7 @@ public static Area GetArea(int x, int y) /// /// Set the area at the specified location /// - public static void SetArea(int x, int y, Area area) + public void SetArea(int x, int y, Area area) { if (area == null) WorldAreas.Remove(x, y); @@ -578,7 +580,7 @@ public static void SetArea(int x, int y, Area area) /// /// Get the turf at the specified location. /// - public static Marker GetMarker(int x, int y) + public Marker GetMarker(int x, int y) { return WorldMarkers.Get(x, y); } @@ -586,7 +588,7 @@ public static Marker GetMarker(int x, int y) /// /// Set the turfs /// - public static void SetMarker(int x, int y, Marker marker) + public void SetMarker(int x, int y, Marker marker) { if (marker == null) WorldMarkers.Remove(x, y); @@ -601,7 +603,7 @@ public static void SetMarker(int x, int y, Marker marker) /// /// Get the turf at the specified location. /// - public static Turf GetTurf(int x, int y) + public Turf GetTurf(int x, int y) { return WorldTurfs.Get(x, y); } @@ -609,7 +611,7 @@ public static Turf GetTurf(int x, int y) /// /// Set the turfs /// - public static void SetTurf(int x, int y, Turf turf) + public void SetTurf(int x, int y, Turf turf) { if (turf == null) WorldTurfs.Remove(x, y); @@ -621,12 +623,12 @@ public static void SetTurf(int x, int y, Turf turf) // Surrounded detected //====================== - public static bool IsLocationFullyEnclosed(int x, int y) + public bool IsLocationFullyEnclosed(int x, int y) { return IsSolid(x, y + 1) && IsSolid(x + 1, y) && IsSolid(x, y - 1) && IsSolid(x - 1, y); } - public static Vector? GetFreeAdjacentLocation(int x, int y) + public Vector? GetFreeAdjacentLocation(int x, int y) { if (!IsSolid(x, y + 1)) return new Vector(x, y + 1); @@ -643,7 +645,7 @@ public static bool IsLocationFullyEnclosed(int x, int y) // Solidity //====================== - public static bool IsSolid(Vector position) + public bool IsSolid(Vector position) { return IsSolid(position.X, position.Y); } @@ -651,7 +653,7 @@ public static bool IsSolid(Vector position) /// /// Check if a position is solid or not /// - public static bool IsSolid(int x, int y) + public bool IsSolid(int x, int y) { Turf locatedTurf = GetTurf(x, y); //TODO: Proper ISolid + directional solidity @@ -662,12 +664,12 @@ public static bool IsSolid(int x, int y) // Gravity //====================== - public static bool HasGravity(Vector position) + public bool HasGravity(Vector position) { return HasGravity(position.X, position.Y); } - public static bool HasGravity(int x, int y) + public bool HasGravity(int x, int y) { //Structure = gravity if (GetStructures(x, y).Count > 0) diff --git a/GJ2022/Game/Power/Powernet.cs b/GJ2022/Game/Power/Powernet.cs index bee1f20..3240ec4 100644 --- a/GJ2022/Game/Power/Powernet.cs +++ b/GJ2022/Game/Power/Powernet.cs @@ -77,7 +77,7 @@ public void Adopt(Powernet powernet) conduit.attachedTextObject.Text = $"{PowernetId}"; conduits.Add(conduit); //Update attached powernets - foreach (PowernetInteractor interactor in World.GetPowernetInteractors((int)conduit.Position.X, (int)conduit.Position.Y)) + foreach (PowernetInteractor interactor in World.Current.GetPowernetInteractors((int)conduit.Position.X, (int)conduit.Position.Y)) { interactor.AttachedPowernet = this; } diff --git a/GJ2022/Managers/Stockpile/StockpileManager.cs b/GJ2022/Managers/Stockpile/StockpileManager.cs index 7974686..153d0b6 100644 --- a/GJ2022/Managers/Stockpile/StockpileManager.cs +++ b/GJ2022/Managers/Stockpile/StockpileManager.cs @@ -58,7 +58,7 @@ public static Item LocateItemInStockpile(Type wantedType) public static void AddStockpileArea(Vector position) { //Add all items at this position to the stockpile - foreach (Item item in World.GetItems((int)position.X, (int)position.Y)) + foreach (Item item in World.Current.GetItems((int)position.X, (int)position.Y)) { AddItem(item); } @@ -66,7 +66,7 @@ public static void AddStockpileArea(Vector position) public static void RemoveStockpileArea(Vector position) { - foreach (Item item in World.GetItems((int)position.X, (int)position.Y)) + foreach (Item item in World.Current.GetItems((int)position.X, (int)position.Y)) { RemoveItem(item); } diff --git a/GJ2022/PawnBehaviours/PawnActions/HaulItems.cs b/GJ2022/PawnBehaviours/PawnActions/HaulItems.cs index e18e9cc..947ecee 100644 --- a/GJ2022/PawnBehaviours/PawnActions/HaulItems.cs +++ b/GJ2022/PawnBehaviours/PawnActions/HaulItems.cs @@ -46,21 +46,21 @@ public override bool CanPerform(PawnBehaviour parent) { if (parent.Owner.InCrit) return false; - if (World.HasAreaInRange((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 40, (Area area) => + if (World.Current.HasAreaInRange((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 40, (Area area) => { if (unreachablePositions.Contains(area.Position)) return false; - if (World.GetThings("Stockpile", (int)area.Position.X, (int)area.Position.Y).Count > 0) + if (World.Current.GetThings("Stockpile", (int)area.Position.X, (int)area.Position.Y).Count > 0) return false; return true; }) - && World.HasItemsInRange((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 40, (List toCheck) => + && World.Current.HasItemsInRange((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 40, (List toCheck) => { foreach (Item item in toCheck) { if (unreachablePositions.Contains(item.Position)) continue; - if (World.GetThings("Stockpile", (int)item.Position.X, (int)item.Position.Y).Count > 0) + if (World.Current.GetThings("Stockpile", (int)item.Position.X, (int)item.Position.Y).Count > 0) continue; return true; } @@ -166,7 +166,7 @@ private void GracefullyDeliver(PawnBehaviour parent) StoreHeldItems(parent); } //Check if the claimed area location is still empty - else if (World.GetItems((int)claimedArea.Position.X, (int)claimedArea.Position.Y).Count > 0) + else if (World.Current.GetItems((int)claimedArea.Position.X, (int)claimedArea.Position.Y).Count > 0) { //Redeliver items StoreHeldItems(parent); @@ -193,7 +193,7 @@ private void LocateOrStoreItems(PawnBehaviour parent) return; } //Attempt to locate items - List itemsToSearch = World.GetSprialItems((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 40); + List itemsToSearch = World.Current.GetSprialItems((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 40); Item targetItem = null; if (forcedTarget == null) { @@ -211,7 +211,7 @@ private void LocateOrStoreItems(PawnBehaviour parent) continue; //Check if the item //is in a stockpile, if it is, skip it - if (World.GetThings("Stockpile", (int)item.Position.X, (int)item.Position.Y).Count > 0) + if (World.Current.GetThings("Stockpile", (int)item.Position.X, (int)item.Position.Y).Count > 0) continue; //Looks like the item is valid! targetItem = item; @@ -220,7 +220,7 @@ private void LocateOrStoreItems(PawnBehaviour parent) } else { - if (!forcedTarget.IsClaimed && forcedTarget.Location == null && !unreachablePositions.Contains(forcedTarget.Position) && World.GetThings("Stockpile", (int)forcedTarget.Position.X, (int)forcedTarget.Position.Y).Count == 0) + if (!forcedTarget.IsClaimed && forcedTarget.Location == null && !unreachablePositions.Contains(forcedTarget.Position) && World.Current.GetThings("Stockpile", (int)forcedTarget.Position.X, (int)forcedTarget.Position.Y).Count == 0) targetItem = forcedTarget; } //No item was located @@ -257,7 +257,7 @@ private void StoreHeldItems(PawnBehaviour parent) } Area freeStockpileArea = null; //Extend range for forced haul - foreach (Area area in World.GetSpiralThings("Stockpile", (int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, forcedTarget == null ? 60 : 120)) + foreach (Area area in World.Current.GetSpiralThings("Stockpile", (int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, forcedTarget == null ? 60 : 120)) { //If the area is claimed, skip it if (area.IsClaimed) @@ -266,7 +266,7 @@ private void StoreHeldItems(PawnBehaviour parent) if (unreachablePositions.Contains(area.Position)) continue; //If the area has items in it, skip it - if (World.GetItems((int)area.Position.X, (int)area.Position.Y).Count > 0) + if (World.Current.GetItems((int)area.Position.X, (int)area.Position.Y).Count > 0) continue; //A good stockpile area freeStockpileArea = area; diff --git a/GJ2022/PawnBehaviours/PawnActions/MineAction.cs b/GJ2022/PawnBehaviours/PawnActions/MineAction.cs index 6302d56..372c391 100644 --- a/GJ2022/PawnBehaviours/PawnActions/MineAction.cs +++ b/GJ2022/PawnBehaviours/PawnActions/MineAction.cs @@ -31,7 +31,7 @@ public override bool CanPerform(PawnBehaviour parent) if (parent.Owner.InCrit) return false; //Quick check - return World.HasMarkerInRange((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 60, (Marker toCheck) => + return World.Current.HasMarkerInRange((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 60, (Marker toCheck) => { return !unreachableLocations.Contains(toCheck.Position); }); @@ -64,7 +64,7 @@ public override void OnActionStart(PawnBehaviour parent) return; } //Go towards the blueprint - Vector? targetPosition = World.GetFreeAdjacentLocation((int)target.Position.X, (int)target.Position.Y); + Vector? targetPosition = World.Current.GetFreeAdjacentLocation((int)target.Position.X, (int)target.Position.Y); if (targetPosition != null) parent.Owner.MoveTowardsPosition(targetPosition.Value); else @@ -111,7 +111,7 @@ public override void PerformProcess(PawnBehaviour parent) /// private MiningMarker LocateValidMarker(PawnBehaviour parent) { - foreach (Marker marker in World.GetSprialMarkers((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 60)) + foreach (Marker marker in World.Current.GetSprialMarkers((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 60)) { if (!(marker is MiningMarker)) continue; @@ -122,7 +122,7 @@ private MiningMarker LocateValidMarker(PawnBehaviour parent) if (marker.IsClaimed || marker.Destroyed) continue; //Is the marker completed surrounded? - if (World.IsLocationFullyEnclosed((int)marker.Position.X, (int)marker.Position.Y)) + if (World.Current.IsLocationFullyEnclosed((int)marker.Position.X, (int)marker.Position.Y)) continue; //Return the marker return marker as MiningMarker; diff --git a/GJ2022/PawnBehaviours/PawnActions/SmeltOre.cs b/GJ2022/PawnBehaviours/PawnActions/SmeltOre.cs index 7b6d1dc..d90b7fc 100644 --- a/GJ2022/PawnBehaviours/PawnActions/SmeltOre.cs +++ b/GJ2022/PawnBehaviours/PawnActions/SmeltOre.cs @@ -34,7 +34,7 @@ public override bool CanPerform(PawnBehaviour parent) { if (parent.Owner.InCrit) return false; - if (World.HasThingInRange("Furnace", (int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 20, (List area) => + if (World.Current.HasThingInRange("Furnace", (int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 20, (List area) => { foreach (IComponentHandler structure in area) { @@ -46,7 +46,7 @@ public override bool CanPerform(PawnBehaviour parent) } return false; }) - && World.HasItemsInRange((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 40, (List toCheck) => + && World.Current.HasItemsInRange((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 40, (List toCheck) => { foreach (Item item in toCheck) { @@ -183,7 +183,7 @@ public override void PerformProcess(PawnBehaviour parent) private Entity LocateValidFurnace(PawnBehaviour parent) { - foreach (Entity structure in World.GetSpiralThings("Furnace", (int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 20)) + foreach (Entity structure in World.Current.GetSpiralThings("Furnace", (int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 20)) { //If we marked this location as unreachable, ignore it. if (unreachableLocations.Contains(structure.Position)) @@ -205,7 +205,7 @@ private Entity LocateValidFurnace(PawnBehaviour parent) /// private IronOre LocateMaterials(PawnBehaviour parent) { - foreach (Item item in World.GetSprialItems((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 40)) + foreach (Item item in World.Current.GetSprialItems((int)parent.Owner.Position.X, (int)parent.Owner.Position.Y, 40)) { //If we marked this location as unreachable, ignore it. if (unreachableLocations.Contains(item.Position)) diff --git a/GJ2022/Subsystems/AtmosphericsSystem.cs b/GJ2022/Subsystems/AtmosphericsSystem.cs index 03b8d14..dbad14d 100644 --- a/GJ2022/Subsystems/AtmosphericsSystem.cs +++ b/GJ2022/Subsystems/AtmosphericsSystem.cs @@ -52,20 +52,20 @@ public override void Fire(Window window) public void OnTurfDestroyed(Turf destroyedTurf) { //Do nothing if nothing changed. - if (!destroyedTurf.AllowAtmosphericFlow && !World.AllowsAtmosphericFlow(destroyedTurf.X, destroyedTurf.Y)) + if (!destroyedTurf.AllowAtmosphericFlow && !World.Current.AllowsAtmosphericFlow(destroyedTurf.X, destroyedTurf.Y)) return; List blocksToClear = new List(); //If we didn't allow atmospheric flow, collect all surrounding atmospheres and merge them with null if (!destroyedTurf.AllowAtmosphericFlow) { AtmosphericBlock atmos; - if ((atmos = World.GetTurf(destroyedTurf.X, destroyedTurf.Y + 1).Atmosphere) != null && !blocksToClear.Contains(atmos)) + if ((atmos = World.Current.GetTurf(destroyedTurf.X, destroyedTurf.Y + 1).Atmosphere) != null && !blocksToClear.Contains(atmos)) blocksToClear.Add(atmos); - if ((atmos = World.GetTurf(destroyedTurf.X + 1, destroyedTurf.Y).Atmosphere) != null && !blocksToClear.Contains(atmos)) + if ((atmos = World.Current.GetTurf(destroyedTurf.X + 1, destroyedTurf.Y).Atmosphere) != null && !blocksToClear.Contains(atmos)) blocksToClear.Add(atmos); - if ((atmos = World.GetTurf(destroyedTurf.X, destroyedTurf.Y - 1).Atmosphere) != null && !blocksToClear.Contains(atmos)) + if ((atmos = World.Current.GetTurf(destroyedTurf.X, destroyedTurf.Y - 1).Atmosphere) != null && !blocksToClear.Contains(atmos)) blocksToClear.Add(atmos); - if ((atmos = World.GetTurf(destroyedTurf.X - 1, destroyedTurf.Y).Atmosphere) != null && !blocksToClear.Contains(atmos)) + if ((atmos = World.Current.GetTurf(destroyedTurf.X - 1, destroyedTurf.Y).Atmosphere) != null && !blocksToClear.Contains(atmos)) blocksToClear.Add(atmos); } else @@ -88,19 +88,19 @@ public void OnAtmosBlockingChange(int x, int y, bool flowBlocked) AtmosphericBlock atmos; Turf turf; bool spaceAdjacent = false; - if ((atmos = (turf = World.GetTurf(x, y + 1))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) + if ((atmos = (turf = World.Current.GetTurf(x, y + 1))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) blocksToMerge.Add(atmos); spaceAdjacent = spaceAdjacent || turf == null || (turf.AllowAtmosphericFlow && atmos == null); - if ((atmos = (turf = World.GetTurf(x + 1, y))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) + if ((atmos = (turf = World.Current.GetTurf(x + 1, y))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) blocksToMerge.Add(atmos); spaceAdjacent = spaceAdjacent || turf == null || (turf.AllowAtmosphericFlow && atmos == null); - if ((atmos = (turf = World.GetTurf(x, y - 1))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) + if ((atmos = (turf = World.Current.GetTurf(x, y - 1))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) blocksToMerge.Add(atmos); spaceAdjacent = spaceAdjacent || turf == null || (turf.AllowAtmosphericFlow && atmos == null); - if ((atmos = (turf = World.GetTurf(x - 1, y))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) + if ((atmos = (turf = World.Current.GetTurf(x - 1, y))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) blocksToMerge.Add(atmos); spaceAdjacent = spaceAdjacent || turf == null || (turf.AllowAtmosphericFlow && atmos == null); - Turf locatedTurf = World.GetTurf(x, y); + Turf locatedTurf = World.Current.GetTurf(x, y); //At this point if located turf is null, we don't need to do anything else if (locatedTurf == null) return; @@ -137,7 +137,7 @@ public void OnAtmosBlockingChange(int x, int y, bool flowBlocked) else { //Atmos flow allowed -> disallowed - Turf locatedTurf = World.GetTurf(x, y); + Turf locatedTurf = World.Current.GetTurf(x, y); if (locatedTurf != null) { if (locatedTurf.Atmosphere != null) @@ -146,13 +146,13 @@ public void OnAtmosBlockingChange(int x, int y, bool flowBlocked) } //Generate new atmospheric blocks Turf turf; - if ((turf = World.GetTurf(x + 1, y)) != null && turf.AllowAtmosphericFlow) + if ((turf = World.Current.GetTurf(x + 1, y)) != null && turf.AllowAtmosphericFlow) processingDeltas.Add(x + 1, y, turf); - if ((turf = World.GetTurf(x, y + 1)) != null && turf.AllowAtmosphericFlow) + if ((turf = World.Current.GetTurf(x, y + 1)) != null && turf.AllowAtmosphericFlow) processingDeltas.Add(x, y + 1, turf); - if ((turf = World.GetTurf(x - 1, y)) != null && turf.AllowAtmosphericFlow) + if ((turf = World.Current.GetTurf(x - 1, y)) != null && turf.AllowAtmosphericFlow) processingDeltas.Add(x - 1, y, turf); - if ((turf = World.GetTurf(x, y - 1)) != null && turf.AllowAtmosphericFlow) + if ((turf = World.Current.GetTurf(x, y - 1)) != null && turf.AllowAtmosphericFlow) processingDeltas.Add(x, y - 1, turf); } } @@ -161,7 +161,7 @@ public void OnAtmosBlockingChange(int x, int y, bool flowBlocked) public void OnTurfChanged(Turf oldTurf, Turf newTurf) { //atmos flow is allowed in the new location if the new turf allows flow and the world allows flow - bool newAtmosphericFlowAllowed = newTurf.AllowAtmosphericFlow && World.AllowsAtmosphericFlow(newTurf.X, newTurf.Y); + bool newAtmosphericFlowAllowed = newTurf.AllowAtmosphericFlow && World.Current.AllowsAtmosphericFlow(newTurf.X, newTurf.Y); //Atmosflow allowed => allowed - inherit the old turfs atmosphere if (oldTurf.AllowAtmosphericFlow && newAtmosphericFlowAllowed) { @@ -178,13 +178,13 @@ public void OnTurfChanged(Turf oldTurf, Turf newTurf) //The new atmosphere created by processing delta needs to get the air from the old atmosphere oldTurf.Atmosphere?.RemoveTurf(oldTurf); Turf turf; - if ((turf = World.GetTurf(newTurf.X + 1, newTurf.Y)) != null && turf.AllowAtmosphericFlow) + if ((turf = World.Current.GetTurf(newTurf.X + 1, newTurf.Y)) != null && turf.AllowAtmosphericFlow) processingDeltas.Add(newTurf.X + 1, newTurf.Y, turf); - if ((turf = World.GetTurf(newTurf.X, newTurf.Y + 1)) != null && turf.AllowAtmosphericFlow) + if ((turf = World.Current.GetTurf(newTurf.X, newTurf.Y + 1)) != null && turf.AllowAtmosphericFlow) processingDeltas.Add(newTurf.X, newTurf.Y + 1, turf); - if ((turf = World.GetTurf(newTurf.X - 1, newTurf.Y)) != null && turf.AllowAtmosphericFlow) + if ((turf = World.Current.GetTurf(newTurf.X - 1, newTurf.Y)) != null && turf.AllowAtmosphericFlow) processingDeltas.Add(newTurf.X - 1, newTurf.Y, turf); - if ((turf = World.GetTurf(newTurf.X, newTurf.Y - 1)) != null && turf.AllowAtmosphericFlow) + if ((turf = World.Current.GetTurf(newTurf.X, newTurf.Y - 1)) != null && turf.AllowAtmosphericFlow) processingDeltas.Add(newTurf.X, newTurf.Y - 1, turf); } //Atmosflow disallowed => allowed - Merge surrounding atmospheres @@ -196,16 +196,16 @@ public void OnTurfChanged(Turf oldTurf, Turf newTurf) AtmosphericBlock atmos; Turf turf; bool spaceAdjacent = false; - if ((atmos = (turf = World.GetTurf(newTurf.X, newTurf.Y + 1))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) + if ((atmos = (turf = World.Current.GetTurf(newTurf.X, newTurf.Y + 1))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) blocksToMerge.Add(atmos); spaceAdjacent = spaceAdjacent || turf == null || (turf.AllowAtmosphericFlow && atmos == null); - if ((atmos = (turf = World.GetTurf(newTurf.X + 1, newTurf.Y))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) + if ((atmos = (turf = World.Current.GetTurf(newTurf.X + 1, newTurf.Y))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) blocksToMerge.Add(atmos); spaceAdjacent = spaceAdjacent || turf == null || (turf.AllowAtmosphericFlow && atmos == null); - if ((atmos = (turf = World.GetTurf(newTurf.X, newTurf.Y - 1))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) + if ((atmos = (turf = World.Current.GetTurf(newTurf.X, newTurf.Y - 1))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) blocksToMerge.Add(atmos); spaceAdjacent = spaceAdjacent || turf == null || (turf.AllowAtmosphericFlow && atmos == null); - if ((atmos = (turf = World.GetTurf(newTurf.X - 1, newTurf.Y))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) + if ((atmos = (turf = World.Current.GetTurf(newTurf.X - 1, newTurf.Y))?.Atmosphere) != null && !blocksToMerge.Contains(atmos)) blocksToMerge.Add(atmos); spaceAdjacent = spaceAdjacent || turf == null || (turf.AllowAtmosphericFlow && atmos == null); //Merge all surrounding atmospheres into 1 @@ -248,13 +248,13 @@ public void OnTurfCreated(Turf createdTurf) if (!createdTurf.AllowAtmosphericFlow) { Turf turf; - if ((turf = World.GetTurf(createdTurf.X + 1, createdTurf.Y)) != null && turf.AllowAtmosphericFlow) + if ((turf = World.Current.GetTurf(createdTurf.X + 1, createdTurf.Y)) != null && turf.AllowAtmosphericFlow) processingDeltas.Add(createdTurf.X + 1, createdTurf.Y, turf); - if ((turf = World.GetTurf(createdTurf.X, createdTurf.Y + 1)) != null && turf.AllowAtmosphericFlow) + if ((turf = World.Current.GetTurf(createdTurf.X, createdTurf.Y + 1)) != null && turf.AllowAtmosphericFlow) processingDeltas.Add(createdTurf.X, createdTurf.Y + 1, turf); - if ((turf = World.GetTurf(createdTurf.X - 1, createdTurf.Y)) != null && turf.AllowAtmosphericFlow) + if ((turf = World.Current.GetTurf(createdTurf.X - 1, createdTurf.Y)) != null && turf.AllowAtmosphericFlow) processingDeltas.Add(createdTurf.X - 1, createdTurf.Y, turf); - if ((turf = World.GetTurf(createdTurf.X, createdTurf.Y - 1)) != null && turf.AllowAtmosphericFlow) + if ((turf = World.Current.GetTurf(createdTurf.X, createdTurf.Y - 1)) != null && turf.AllowAtmosphericFlow) processingDeltas.Add(createdTurf.X, createdTurf.Y - 1, turf); } //If the new turf allows atmospheric flow then add it to the processing queue @@ -278,10 +278,10 @@ public void OnTurfCreated(Turf createdTurf) private AtmosphericBlock FindMergeBlock(Turf createdTurf) { //Get surrounding turfs - Turf above = World.GetTurf(createdTurf.X, createdTurf.Y + 1); - Turf right = World.GetTurf(createdTurf.X + 1, createdTurf.Y); - Turf below = World.GetTurf(createdTurf.X, createdTurf.Y - 1); - Turf left = World.GetTurf(createdTurf.X - 1, createdTurf.Y); + Turf above = World.Current.GetTurf(createdTurf.X, createdTurf.Y + 1); + Turf right = World.Current.GetTurf(createdTurf.X + 1, createdTurf.Y); + Turf below = World.Current.GetTurf(createdTurf.X, createdTurf.Y - 1); + Turf left = World.Current.GetTurf(createdTurf.X - 1, createdTurf.Y); //Check invalid //If any turf is null, then atmos will be null if (above == null || right == null || below == null || left == null) @@ -334,7 +334,7 @@ private void ProcessDelta(int x, int y, Turf processing) //Get the tile we should search Vector searchTile = searchRadiusEdgeTiles.Dequeue(); //Check this tile - Turf locatedTurf = World.GetTurf(searchTile[0], searchTile[1]); + Turf locatedTurf = World.Current.GetTurf(searchTile[0], searchTile[1]); //If space, then we lose all of our gas and atmosphere :( if (locatedTurf == null) { @@ -355,25 +355,25 @@ private void ProcessDelta(int x, int y, Turf processing) } //Add surrounding tiles that allow gas flow //Check north - if (World.AllowsAtmosphericFlow(searchTile[0], searchTile[1] + 1) && !searchedTiles.Get(searchTile[0], searchTile[1] + 1)) + if (World.Current.AllowsAtmosphericFlow(searchTile[0], searchTile[1] + 1) && !searchedTiles.Get(searchTile[0], searchTile[1] + 1)) { searchedTiles.Add(searchTile[0], searchTile[1] + 1, true); searchRadiusEdgeTiles.Enqueue(new Vector(searchTile[0], searchTile[1] + 1)); } //Check east - if (World.AllowsAtmosphericFlow(searchTile[0] + 1, searchTile[1]) && !searchedTiles.Get(searchTile[0] + 1, searchTile[1])) + if (World.Current.AllowsAtmosphericFlow(searchTile[0] + 1, searchTile[1]) && !searchedTiles.Get(searchTile[0] + 1, searchTile[1])) { searchedTiles.Add(searchTile[0] + 1, searchTile[1], true); searchRadiusEdgeTiles.Enqueue(new Vector(searchTile[0] + 1, searchTile[1])); } //Check south - if (World.AllowsAtmosphericFlow(searchTile[0], searchTile[1] - 1) && !searchedTiles.Get(searchTile[0], searchTile[1] - 1)) + if (World.Current.AllowsAtmosphericFlow(searchTile[0], searchTile[1] - 1) && !searchedTiles.Get(searchTile[0], searchTile[1] - 1)) { searchedTiles.Add(searchTile[0], searchTile[1] - 1, true); searchRadiusEdgeTiles.Enqueue(new Vector(searchTile[0], searchTile[1] - 1)); } //Check west - if (World.AllowsAtmosphericFlow(searchTile[0] - 1, searchTile[1]) && !searchedTiles.Get(searchTile[0] - 1, searchTile[1])) + if (World.Current.AllowsAtmosphericFlow(searchTile[0] - 1, searchTile[1]) && !searchedTiles.Get(searchTile[0] - 1, searchTile[1])) { searchedTiles.Add(searchTile[0] - 1, searchTile[1], true); searchRadiusEdgeTiles.Enqueue(new Vector(searchTile[0] - 1, searchTile[1])); diff --git a/GJ2022/Subsystems/MouseCollisionSubsystem.cs b/GJ2022/Subsystems/MouseCollisionSubsystem.cs index 85f197c..4606196 100644 --- a/GJ2022/Subsystems/MouseCollisionSubsystem.cs +++ b/GJ2022/Subsystems/MouseCollisionSubsystem.cs @@ -85,7 +85,7 @@ public override void Fire(Window window) int x = worldTile[0]; int y = worldTile[1]; //Get clickable things at these world coordinates - foreach (Entity entity in World.GetEntities(x, y)) + foreach (Entity entity in World.Current.GetEntities(x, y)) { if (mousePressed) (entity as IMousePress)?.OnPressed(); diff --git a/GJ2022/Subsystems/PathfindingSystem.cs b/GJ2022/Subsystems/PathfindingSystem.cs index d7f6332..0be7e4b 100644 --- a/GJ2022/Subsystems/PathfindingSystem.cs +++ b/GJ2022/Subsystems/PathfindingSystem.cs @@ -85,13 +85,13 @@ public static void ProcessPathImmediate(Vector start, Vector end) private void ProcessPath(PathfindingRequest request) { //If the end of the request is blocked, don't both - if (World.IsSolid(request.End)) + if (World.Current.IsSolid(request.End)) { request.failedDelegate?.Invoke(); return; } //If the start has no pressure and the end does, allow low-pressure pathfinding - Atmosphere startAtmos = World.GetTurf(request.Start[0], request.Start[1])?.Atmosphere?.ContainedAtmosphere; + Atmosphere startAtmos = World.Current.GetTurf(request.Start[0], request.Start[1])?.Atmosphere?.ContainedAtmosphere; if (startAtmos == null || startAtmos.KiloPascalPressure < 50) { request.ignoringHazards |= PawnHazards.HAZARD_LOW_PRESSURE; @@ -145,13 +145,13 @@ private void AddSurroundingNodes(PathNode current, PawnHazards ignoringHazards, CheckAddNode(current, ref processData, new Vector(0, -1), ConnectingDirections.SOUTH); if ((validDirections & ConnectingDirections.WEST) != 0) CheckAddNode(current, ref processData, new Vector(-1, 0), ConnectingDirections.WEST); - if ((validDirections & ConnectingDirections.NORTH_EAST) == ConnectingDirections.NORTH_EAST && !World.IsSolid(current.Position + new Vector(1, 1))) + if ((validDirections & ConnectingDirections.NORTH_EAST) == ConnectingDirections.NORTH_EAST && !World.Current.IsSolid(current.Position + new Vector(1, 1))) CheckAddNode(current, ref processData, new Vector(1, 1), ConnectingDirections.NORTH_EAST); - if ((validDirections & ConnectingDirections.SOUTH_EAST) == ConnectingDirections.SOUTH_EAST && !World.IsSolid(current.Position + new Vector(1, -1))) + if ((validDirections & ConnectingDirections.SOUTH_EAST) == ConnectingDirections.SOUTH_EAST && !World.Current.IsSolid(current.Position + new Vector(1, -1))) CheckAddNode(current, ref processData, new Vector(1, -1), ConnectingDirections.SOUTH_EAST); - if ((validDirections & ConnectingDirections.SOUTH_WEST) == ConnectingDirections.SOUTH_WEST && !World.IsSolid(current.Position + new Vector(-1, -1))) + if ((validDirections & ConnectingDirections.SOUTH_WEST) == ConnectingDirections.SOUTH_WEST && !World.Current.IsSolid(current.Position + new Vector(-1, -1))) CheckAddNode(current, ref processData, new Vector(-1, -1), ConnectingDirections.SOUTH_WEST); - if ((validDirections & ConnectingDirections.NORTH_WEST) == ConnectingDirections.NORTH_WEST && !World.IsSolid(current.Position + new Vector(-1, 1))) + if ((validDirections & ConnectingDirections.NORTH_WEST) == ConnectingDirections.NORTH_WEST && !World.Current.IsSolid(current.Position + new Vector(-1, 1))) CheckAddNode(current, ref processData, new Vector(-1, 1), ConnectingDirections.NORTH_WEST); } @@ -202,25 +202,25 @@ private ConnectingDirections GetValidDirections(PathfindingProcessData processDa //Check north if (!IsPointChecked(processData, position + new Vector(0, 1), ConnectingDirections.NORTH_SOUTH, ignoringHazards) && position.Y < processData.MaximumY - && !World.IsSolid(position + new Vector(0, 1)) + && !World.Current.IsSolid(position + new Vector(0, 1)) && HazardCheck(ignoringHazards, position, source, ConnectingDirections.NORTH)) connectingDirections |= ConnectingDirections.NORTH; //Check east if (!IsPointChecked(processData, position + new Vector(1, 0), ConnectingDirections.EAST_WEST, ignoringHazards) && position.X < processData.MaximumX - && !World.IsSolid(position + new Vector(1, 0)) + && !World.Current.IsSolid(position + new Vector(1, 0)) && HazardCheck(ignoringHazards, position, source, ConnectingDirections.EAST)) connectingDirections |= ConnectingDirections.EAST; //Check south if (!IsPointChecked(processData, position + new Vector(0, -1), ConnectingDirections.NORTH_SOUTH, ignoringHazards) && position.Y > processData.MinimumY - && !World.IsSolid(position + new Vector(0, -1)) + && !World.Current.IsSolid(position + new Vector(0, -1)) && HazardCheck(ignoringHazards, position, source, ConnectingDirections.SOUTH)) connectingDirections |= ConnectingDirections.SOUTH; //Check west if (!IsPointChecked(processData, position + new Vector(-1, 0), ConnectingDirections.EAST_WEST, ignoringHazards) && position.X > processData.MinimumX - && !World.IsSolid(position + new Vector(-1, 0)) + && !World.Current.IsSolid(position + new Vector(-1, 0)) && HazardCheck(ignoringHazards, position, source, ConnectingDirections.WEST)) connectingDirections |= ConnectingDirections.WEST; //Return valid directions @@ -236,7 +236,7 @@ private bool IsPointChecked(PathfindingProcessData processData, Vector posi if (!processData.ProcessedPoints.ContainsKey(position)) return false; //If this location has gravity, then we checked it - if (World.HasGravity(position)) + if (World.Current.HasGravity(position)) return true; //Check if we have scanned the incomming direction ConnectingDirections scannedDirections = processData.ProcessedPoints[position]; @@ -254,7 +254,7 @@ private bool LowPressureCheck(PawnHazards ignoringHazards, Vector position, if ((ignoringHazards & PawnHazards.HAZARD_LOW_PRESSURE) != 0) return true; //Check for pressure (50 should be safe) - return World.GetTurf(position.X, position.Y)?.Atmosphere?.ContainedAtmosphere?.KiloPascalPressure > 50; + return World.Current.GetTurf(position.X, position.Y)?.Atmosphere?.ContainedAtmosphere?.KiloPascalPressure > 50; } private bool BreathCheck(PawnHazards ignoringHazards, Vector position, Vector source, ConnectingDirections direction) @@ -263,7 +263,7 @@ private bool BreathCheck(PawnHazards ignoringHazards, Vector position, Vect if ((ignoringHazards & PawnHazards.HAZARD_BREATH) != 0) return true; //Check air - if (World.GetTurf(position.X, position.Y)?.Atmosphere?.ContainedAtmosphere?.GetMoles(Oxygen.Singleton) > 0.02f) + if (World.Current.GetTurf(position.X, position.Y)?.Atmosphere?.ContainedAtmosphere?.GetMoles(Oxygen.Singleton) > 0.02f) return true; //Area has no air return false; @@ -275,7 +275,7 @@ private bool GravityCheck(PawnHazards ignoringHazards, Vector position, Vec if ((ignoringHazards & PawnHazards.HAZARD_GRAVITY) != 0) return true; //Place has gravity - if (World.HasGravity(position)) + if (World.Current.HasGravity(position)) return true; //We can move in a straight line without gravity int delta_x = position.X - source[0]; diff --git a/GJ2022/Subsystems/PawnControllerSystem.cs b/GJ2022/Subsystems/PawnControllerSystem.cs index 6c7aa7f..8f2a835 100644 --- a/GJ2022/Subsystems/PawnControllerSystem.cs +++ b/GJ2022/Subsystems/PawnControllerSystem.cs @@ -30,7 +30,7 @@ public void SelectPawn(Pawn target) public static void QueueBlueprint(Vector position, Blueprint blueprint, int layer) { //Check if the blueprint is redundant - if (World.GetTurf((int)position.X, (int)position.Y)?.GetType() == blueprint.BlueprintDetail.CreatedType) + if (World.Current.GetTurf((int)position.X, (int)position.Y)?.GetType() == blueprint.BlueprintDetail.CreatedType) { blueprint.Destroy(); return; diff --git a/GJ2022/Subsystems/Subsystem.cs b/GJ2022/Subsystems/Subsystem.cs index f460fb2..19c9d7c 100644 --- a/GJ2022/Subsystems/Subsystem.cs +++ b/GJ2022/Subsystems/Subsystem.cs @@ -182,7 +182,7 @@ private void Update(Window window) stopwatch = new Stopwatch(); stopwatch.Start(); //Check if the world has been initialized yet. - if (/*World.Initialized*/ true) + if (/*World.Current.Initialized*/ true) { //Check the subsystem fires. if ((SubsystemFlags & SubsystemFlags.NO_FIRE) == 0) diff --git a/GJ2022/UserInterface/UserInterfaceCreator.cs b/GJ2022/UserInterface/UserInterfaceCreator.cs index 1c84ed7..29498ad 100644 --- a/GJ2022/UserInterface/UserInterfaceCreator.cs +++ b/GJ2022/UserInterface/UserInterfaceCreator.cs @@ -99,9 +99,9 @@ public static void CreateUserInterface() CreateEntitySpawnDD(); }, () => { - foreach(string key in World.TrackedComponentHandlers.Keys) + foreach(string key in World.Current.TrackedComponentHandlers.Keys) { - Log.WriteLine($"{key} => {World.TrackedComponentHandlers[key]}", LogType.DEBUG); + Log.WriteLine($"{key} => {World.Current.TrackedComponentHandlers[key]}", LogType.DEBUG); } }, }); From ce34c4ac0fbac51542c343de10b65b8d0b917a2f Mon Sep 17 00:00:00 2001 From: PowerfulBacon <> Date: Fri, 18 Mar 2022 21:22:19 +0000 Subject: [PATCH 14/16] Updates the unit tests, fixes some namespace auto replace issues. --- GJ2022.Tests/RegionTests.cs | 72 +++++++++++++++- GJ2022/Game/GameWorld/Regions/Region.cs | 6 +- .../Game/GameWorld/Regions/WorldRegionList.cs | 85 +++++++++++++++++-- 3 files changed, 153 insertions(+), 10 deletions(-) diff --git a/GJ2022.Tests/RegionTests.cs b/GJ2022.Tests/RegionTests.cs index ace0183..14e5da5 100644 --- a/GJ2022.Tests/RegionTests.cs +++ b/GJ2022.Tests/RegionTests.cs @@ -1,6 +1,6 @@ using GJ2022.Entities.Turfs; using GJ2022.Game.GameWorld; -using GJ2022.Game.GameWorld.Current.Regions; +using GJ2022.Game.GameWorld.Regions; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; @@ -14,6 +14,72 @@ namespace GJ2022.Tests public class RegionTests { + [TestMethod] + public void TestValidPathThatGetsBlocked() + { + //Reset the world + World.Current = new World(); + //Generate a wall with a hole in it + Turf solidTurf = new Turf(); + solidTurf.SetProperty("Solid", true); + World.Current.SetTurf(13, 0, solidTurf); + World.Current.SetTurf(13, 1, solidTurf); + World.Current.SetTurf(13, 2, solidTurf); + World.Current.SetTurf(13, 3, solidTurf); + World.Current.SetTurf(13, 5, solidTurf); + World.Current.SetTurf(13, 6, solidTurf); + World.Current.SetTurf(13, 7, solidTurf); + //Create a world region list + WorldRegionList wrl = new WorldRegionList(); + wrl.GenerateWorldSection(0, 0); + wrl.GenerateWorldSection(8, 0); + wrl.GenerateWorldSection(16, 0); + //Test accessability + Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(5, 5)), "There should exist a valid path from region (0, 0) to (5, 5)"); + Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(10, 5)), "There should exist a valid path from region (0, 0) to (10, 5)"); + Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(14, 5)), "There should exist a valid path from region (0, 0) to (14, 5)"); + Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(18, 5)), "There should exist a valid path from region (0, 0) to (18, 5)"); + Assert.IsTrue(wrl.regions.Get(14, 5).HasPath(wrl.regions.Get(18, 5)), "There should exist a valid path from region (14, 5) to (18, 5)"); + //Block off the wall + World.Current.SetTurf(13, 4, solidTurf); + wrl.SetNodeSolid(13, 4); + Log.WriteLine("World has been divided"); + Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(5, 5)), "There should still exist a valid path from region (0, 0) to (5, 5)"); + Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(10, 5)), "There should still exist a valid path from region (0, 0) to (10, 5)"); + Assert.IsFalse(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(14, 5)), "There should no longer exist a valid path from region (0, 0) to (14, 5)"); + Assert.IsFalse(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(18, 5)), "There should no longer exist a valid path from region (0, 0) to (18, 5)"); + Assert.IsTrue(wrl.regions.Get(14, 5).HasPath(wrl.regions.Get(18, 5)), "There should still exist a valid path from region (14, 5) to (18, 5)"); + } + + [TestMethod] + public void TestVerticalWall() + { + //Reset the world + World.Current = new World(); + //Generate a wall + Turf solidTurf = new Turf(); + solidTurf.SetProperty("Solid", true); + World.Current.SetTurf(13, 0, solidTurf); + World.Current.SetTurf(13, 1, solidTurf); + World.Current.SetTurf(13, 2, solidTurf); + World.Current.SetTurf(13, 3, solidTurf); + World.Current.SetTurf(13, 4, solidTurf); + World.Current.SetTurf(13, 5, solidTurf); + World.Current.SetTurf(13, 6, solidTurf); + World.Current.SetTurf(13, 7, solidTurf); + //Create a world region list + WorldRegionList wrl = new WorldRegionList(); + wrl.GenerateWorldSection(0, 0); + wrl.GenerateWorldSection(8, 0); + wrl.GenerateWorldSection(16, 0); + //Test accessability + Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(5, 5)), "There should exist a valid path from region (0, 0) to (5, 5)"); + Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(10, 5)), "There should exist a valid path from region (0, 0) to (10, 5)"); + Assert.IsFalse(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(14, 5)), "There should not exist a valid path from region (0, 0) to (14, 5)"); + Assert.IsFalse(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(18, 5)), "There should not exist a valid path from region (0, 0) to (18, 5)"); + Assert.IsTrue(wrl.regions.Get(14, 5).HasPath(wrl.regions.Get(18, 5)), "There should exist a valid path from region (14, 5) to (18, 5)"); + } + [TestMethod] public void TestRegionCreation() { @@ -42,6 +108,10 @@ public void TestRegionCreation() Assert.AreEqual(left, wrl.regions.Get(x, y), "Expected left region to be the same"); } } + for (int y = 0; y < 8; y++) + { + Assert.AreEqual(null, wrl.regions.Get(4, y), "Expected center line to be null"); + } for (int x = 5; x < 8; x++) { for (int y = 0; y < 8; y++) diff --git a/GJ2022/Game/GameWorld/Regions/Region.cs b/GJ2022/Game/GameWorld/Regions/Region.cs index d2079e7..ddd7e79 100644 --- a/GJ2022/Game/GameWorld/Regions/Region.cs +++ b/GJ2022/Game/GameWorld/Regions/Region.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace GJ2022.Game.GameWorld.Current.Regions +namespace GJ2022.Game.GameWorld.Regions { /// /// Region system: @@ -147,5 +147,9 @@ public bool HasPath(Region other) return false; } + public override string ToString() + { + return $"Region {Id}"; + } } } diff --git a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs index cdff669..b01f55e 100644 --- a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs +++ b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs @@ -34,7 +34,8 @@ public class WorldRegionList public PositionBasedBinaryList regions = new PositionBasedBinaryList(); /// - /// Sets a node in the world to be solid + /// Sets a node in the world to be solid. + /// Time complexity of O(REGION_PRIMARY_LEVEL_SIZE^2) /// /// The X position of the node to set to be solid /// The Y position of the node to set to be solid @@ -46,6 +47,9 @@ public void SetNodeSolid(int x, int y) //Calculate if we need to take any action at all if (!NodeSolidRequiresUpdate(affectedRegion, x, y)) return; + //Calculate the region's current adjacencies O(REGION_PRIMARY_LEVEL_SIZE) + //An update is required, subdivide the region + } /// @@ -59,18 +63,83 @@ public void SetNodeSolid(int x, int y) /// flood filled. /// If any open adjacent nodes didn't get tagged by the flood fill, it means our region has /// been subdivided. + /// O(REGION_PRIMARY_LEVEL_SIZE^2) /// - private bool NodeSolidRequiresUpdate(Region actingRegion, int x, int y) + public bool NodeSolidRequiresUpdate(Region actingRegion, int x, int y) { + //Get the relative X and Y with a safe mod operation + int relX = ((x % REGION_PRIMARY_LEVEL_SIZE) + REGION_PRIMARY_LEVEL_SIZE) % REGION_PRIMARY_LEVEL_SIZE; + int relY = ((y % REGION_PRIMARY_LEVEL_SIZE) + REGION_PRIMARY_LEVEL_SIZE) % REGION_PRIMARY_LEVEL_SIZE; //Flood fill all nodes in the region and see if we can reconnect with ourselfs //Flood fill with IDs, giving unique values to north, south, east and west - int[,] floodFilledIds = new int[REGION_PRIMARY_LEVEL_SIZE, REGION_PRIMARY_LEVEL_SIZE]; + bool[,] floodFilledIds = new bool[REGION_PRIMARY_LEVEL_SIZE, REGION_PRIMARY_LEVEL_SIZE]; + floodFilledIds[relX, relY] = true; Queue> updateQueue = new Queue>(); //Locate an adjacent, open node. //Insert the first node - - //We did not re-encounter ourselves, so we need to update - return true; + if (relX < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(relX + 1, relY)) + { + updateQueue.Enqueue(new Vector(relX + 1, relY)); + } + else if (relX > 0 && !World.Current.IsSolid(relX - 1, relY)) + { + updateQueue.Enqueue(new Vector(relX - 1, relY)); + } + else if (relY < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(relX, relY + 1)) + { + updateQueue.Enqueue(new Vector(relX, relY + 1)); + } + else if (relY > 0 && !World.Current.IsSolid(relX, relY - 1)) + { + updateQueue.Enqueue(new Vector(relX, relY - 1)); + } + else + { + //We have no non-solid adjacent nodes, so we do not need to make an update + return false; + } + //Perform the flood fill operation + while (updateQueue.Count > 0) + { + Vector current = updateQueue.Dequeue(); + //Mark the current node + floodFilledIds[current.X, current.Y] = true; + //Add adjacent, non-solid, unmarked nodes + if (current.X < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(current.X + 1, current.Y) && !floodFilledIds[current.X + 1, current.Y]) + { + updateQueue.Enqueue(new Vector(current.X + 1, current.Y)); + } + if (current.X > 0 && !World.Current.IsSolid(current.X - 1, current.Y) && !floodFilledIds[current.X - 1, current.Y]) + { + updateQueue.Enqueue(new Vector(current.X - 1, current.Y)); + } + if (current.Y < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(current.X, current.Y + 1) && !floodFilledIds[current.X, current.Y + 1]) + { + updateQueue.Enqueue(new Vector(current.X, current.Y + 1)); + } + if (current.Y > 0 && !World.Current.IsSolid(current.X, current.Y - 1) && !floodFilledIds[current.X, current.Y - 1]) + { + updateQueue.Enqueue(new Vector(current.X, current.Y - 1)); + } + } + //Require an update if all adjacent, non-solid nodes are not marked + if (relX < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(relX + 1, relY) && !floodFilledIds[relX + 1, relY]) + { + return true; + } + else if (relX > 0 && !World.Current.IsSolid(relX - 1, relY) && !floodFilledIds[relX - 1, relY]) + { + return true; + } + else if (relY < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(relX, relY + 1) && !floodFilledIds[relX, relY + 1]) + { + return true; + } + else if (relY > 0 && !World.Current.IsSolid(relX, relY - 1) && !floodFilledIds[relX, relY - 1]) + { + return true; + } + return false; } /// @@ -103,7 +172,7 @@ public void GenerateWorldSection(int x, int y) int realX = regionX * REGION_PRIMARY_LEVEL_SIZE + nodeX; int realY = regionY * REGION_PRIMARY_LEVEL_SIZE + nodeY; //This position is solid, ignore (We don't need to update processedNodes, since we will never again access it) - if (World.IsSolid(realX, realY)) + if (World.Current.IsSolid(realX, realY)) continue; //Create a new region and flood fill outwards Region createdRegion = new Region(regionX, regionY); @@ -130,7 +199,7 @@ public void GenerateWorldSection(int x, int y) //Set node processed processedNodes[relativePosition.X, relativePosition.Y] = true; //If current node is a wall, skip - if (World.IsSolid(worldPosition.X, worldPosition.Y)) + if (World.Current.IsSolid(worldPosition.X, worldPosition.Y)) continue; //Join the region regions.Add(worldPosition.X, worldPosition.Y, createdRegion); From c7727462478477122b86515bdceec2632469a864 Mon Sep 17 00:00:00 2001 From: PowerfulBacon <> Date: Fri, 18 Mar 2022 23:34:49 +0000 Subject: [PATCH 15/16] Some region progress --- GJ2022.Tests/RegionTests.cs | 42 +- .../Game/GameWorld/Regions/WorldRegionList.cs | 448 +++++++++++------- 2 files changed, 297 insertions(+), 193 deletions(-) diff --git a/GJ2022.Tests/RegionTests.cs b/GJ2022.Tests/RegionTests.cs index 14e5da5..e016947 100644 --- a/GJ2022.Tests/RegionTests.cs +++ b/GJ2022.Tests/RegionTests.cs @@ -17,18 +17,18 @@ public class RegionTests [TestMethod] public void TestValidPathThatGetsBlocked() { - //Reset the world - World.Current = new World(); - //Generate a wall with a hole in it + //Generate a wall Turf solidTurf = new Turf(); solidTurf.SetProperty("Solid", true); - World.Current.SetTurf(13, 0, solidTurf); - World.Current.SetTurf(13, 1, solidTurf); - World.Current.SetTurf(13, 2, solidTurf); - World.Current.SetTurf(13, 3, solidTurf); - World.Current.SetTurf(13, 5, solidTurf); - World.Current.SetTurf(13, 6, solidTurf); - World.Current.SetTurf(13, 7, solidTurf); + //Reset the world + World.Current = new World(); + World.Current.SetTurf(12, 0, solidTurf); + World.Current.SetTurf(12, 1, solidTurf); + World.Current.SetTurf(12, 2, solidTurf); + World.Current.SetTurf(12, 3, solidTurf); + World.Current.SetTurf(12, 5, solidTurf); + World.Current.SetTurf(12, 6, solidTurf); + World.Current.SetTurf(12, 7, solidTurf); //Create a world region list WorldRegionList wrl = new WorldRegionList(); wrl.GenerateWorldSection(0, 0); @@ -40,9 +40,11 @@ public void TestValidPathThatGetsBlocked() Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(14, 5)), "There should exist a valid path from region (0, 0) to (14, 5)"); Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(18, 5)), "There should exist a valid path from region (0, 0) to (18, 5)"); Assert.IsTrue(wrl.regions.Get(14, 5).HasPath(wrl.regions.Get(18, 5)), "There should exist a valid path from region (14, 5) to (18, 5)"); + //Check this + Assert.IsTrue(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(12, 4), 12, 4)); //Block off the wall - World.Current.SetTurf(13, 4, solidTurf); - wrl.SetNodeSolid(13, 4); + World.Current.SetTurf(12, 4, solidTurf); + wrl.SetNodeSolid(12, 4); Log.WriteLine("World has been divided"); Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(5, 5)), "There should still exist a valid path from region (0, 0) to (5, 5)"); Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(10, 5)), "There should still exist a valid path from region (0, 0) to (10, 5)"); @@ -145,6 +147,22 @@ public void TestRegionUpdateRequirements() Assert.IsFalse(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(0, 0), 3, 3)); Assert.IsTrue(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(0, 0), 5, 4)); Assert.IsTrue(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(0, 0), 3, 4)); + wrl.GenerateWorldSection(8, 0); + Assert.IsFalse(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(12, 4), 12, 4)); + World.Current.SetTurf(12, 0, solidTurf); + World.Current.SetTurf(12, 1, solidTurf); + World.Current.SetTurf(12, 2, solidTurf); + World.Current.SetTurf(12, 3, solidTurf); + World.Current.SetTurf(12, 5, solidTurf); + World.Current.SetTurf(12, 6, solidTurf); + World.Current.SetTurf(12, 7, solidTurf); + Assert.IsTrue(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(12, 4), 12, 4)); + Assert.IsFalse(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(11, 1), 11, 1)); + Assert.IsFalse(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(8, 0), 13, 5)); + Assert.IsFalse(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(8, 0), 12, 6)); + Assert.IsFalse(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(8, 0), 11, 3)); + Assert.IsTrue(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(8, 0), 13, 4)); + Assert.IsTrue(wrl.NodeSolidRequiresUpdate(wrl.regions.Get(8, 0), 11, 4)); } [TestMethod] diff --git a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs index b01f55e..e32cc95 100644 --- a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs +++ b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs @@ -1,4 +1,4 @@ -//#define REGION_LOGGING +#define REGION_LOGGING using GJ2022.Utility.MathConstructs; using System; @@ -9,6 +9,12 @@ namespace GJ2022.Game.GameWorld.Regions { + /// + /// Pretty complicated way of splitting the world into a region based tree + /// structure so that impossible path finding can be avoided, saving us from attempting + /// to calculate paths where there is none, resulting in every world tile needing to + /// be examined. + /// public class WorldRegionList { @@ -45,11 +51,252 @@ public void SetNodeSolid(int x, int y) Region affectedRegion = regions.Get(x, y); regions.Remove(x, y); //Calculate if we need to take any action at all + Log.WriteLine("Checking..."); if (!NodeSolidRequiresUpdate(affectedRegion, x, y)) return; - //Calculate the region's current adjacencies O(REGION_PRIMARY_LEVEL_SIZE) - //An update is required, subdivide the region + Log.WriteLine("Requires update!"); + //Calculate the span of the subdivided region + //(Note that it is possible to subdivide into multiple regions at once (4 regions at max)) + List createdRegions = new List(); + List adjacentRegions = new List(); + //Calculate required values + //Calculate region X and Y + int regionX = (int)Math.Floor((float)x / REGION_PRIMARY_LEVEL_SIZE); + int regionY = (int)Math.Floor((float)y / REGION_PRIMARY_LEVEL_SIZE); + //Calcualte relative X and Y + int relX = x - regionX * REGION_PRIMARY_LEVEL_SIZE; + int relY = y - regionY * REGION_PRIMARY_LEVEL_SIZE; + //Check each adjacent tile to the changed node + //Check if that tile is part of one of the regions we created already + //If not, create a new region at that location + if (relX < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(x + 1, y)) + { + createdRegions.Add(FloodFillCreateRegion(x + 1, y, ref adjacentRegions)); + } + if (relX > 0 && !World.Current.IsSolid(x - 1, y) && !createdRegions.Contains(regions.Get(x - 1, y))) + { + createdRegions.Add(FloodFillCreateRegion(x - 1, y, ref adjacentRegions)); + } + if (relY < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(x, y + 1) && !createdRegions.Contains(regions.Get(x, y + 1))) + { + createdRegions.Add(FloodFillCreateRegion(x, y + 1, ref adjacentRegions)); + } + if (relY > 0 && !World.Current.IsSolid(x, y - 1) && !createdRegions.Contains(regions.Get(x, y - 1))) + { + createdRegions.Add(FloodFillCreateRegion(x, y - 1, ref adjacentRegions)); + } + //Recalculate adjacencies + foreach (Region r in createdRegions) + { + Log.WriteLine($"Created region {r.Id}"); + } + foreach (Region r in adjacentRegions) + { + Log.WriteLine($"Adjacent to {r.Id}"); + } + } + + /// + /// Creates a region at {x, y} and flood fills the region outwards, + /// generating parents and adjacent regions where necessary. + /// + /// The X world coordinate of where to create the new region + /// The Y world coordinate of where to create the new region + /// The created region + private Region FloodFillCreateRegion(int x, int y, ref List adjacentRegions) + { + bool[,] nullRegion = null; + return FloodFillCreateRegion(x, y, ref nullRegion, ref adjacentRegions); + } + /// + /// Creates a region at {x, y} and flood fills the region outwards, + /// generating parents and adjacent regions where necessary. + /// + /// The X world coordinate of where to create the new region + /// The Y world coordinate of where to create the new region + /// A 2D array that will be set to indicate which nodes have been processed. + /// A list of the regions adjacent to the created Region + /// The created region + private Region FloodFillCreateRegion(int x, int y, ref bool[,] processedNodes, ref List adjacentRegions) + { + if (processedNodes == null) + processedNodes = new bool[REGION_PRIMARY_LEVEL_SIZE, REGION_PRIMARY_LEVEL_SIZE]; + //Calculate region X and Y + int regionX = (int)Math.Floor((float)x / REGION_PRIMARY_LEVEL_SIZE); + int regionY = (int)Math.Floor((float)y / REGION_PRIMARY_LEVEL_SIZE); + //Calcualte relative X and Y + int relX = x - regionX * REGION_PRIMARY_LEVEL_SIZE; + int relY = y - regionY * REGION_PRIMARY_LEVEL_SIZE; + //Create a new region and flood fill outwards + Region createdRegion = new Region(regionX, regionY); +#if REGION_LOGGING + Log.WriteLine($"Created new region {createdRegion.Id} at ({regionX}, {regionY})"); +#endif + //Have the position join this region + regions.Add(x, y, createdRegion); + //During this process, get the adjacent regions so we can match up parents + if(adjacentRegions == null) + adjacentRegions = new List(); + //Processing queue + Queue> toProcess = new Queue>(); + toProcess.Enqueue(new Vector(relX, relY)); + //While we still have places to floor fill + while (toProcess.Count > 0) + { + //Dequeue + Vector relativePosition = toProcess.Dequeue(); + //If current node is already processed, skip + if (processedNodes[relativePosition.X, relativePosition.Y]) + continue; + //Calculate the world position of the current node + Vector worldPosition = new Vector(regionX * REGION_PRIMARY_LEVEL_SIZE, regionY * REGION_PRIMARY_LEVEL_SIZE) + relativePosition; + //Set node processed + processedNodes[relativePosition.X, relativePosition.Y] = true; + //If current node is a wall, skip + if (World.Current.IsSolid(worldPosition.X, worldPosition.Y)) + continue; + //Join the region + regions.Add(worldPosition.X, worldPosition.Y, createdRegion); + //Check if we have any adjacent regions + Region adjacent; + //Add adjacent nodes (Assuming they are within bounds) + if (relativePosition.X < REGION_PRIMARY_LEVEL_SIZE - 1) + toProcess.Enqueue(new Vector(relativePosition.X + 1, relativePosition.Y)); + else if ((adjacent = regions.Get(worldPosition.X + 1, worldPosition.Y)) != null && !adjacentRegions.Contains(adjacent)) + adjacentRegions.Add(adjacent); + if (relativePosition.X > 0) + toProcess.Enqueue(new Vector(relativePosition.X - 1, relativePosition.Y)); + else if ((adjacent = regions.Get(worldPosition.X - 1, worldPosition.Y)) != null && !adjacentRegions.Contains(adjacent)) + adjacentRegions.Add(adjacent); + if (relativePosition.Y < REGION_PRIMARY_LEVEL_SIZE - 1) + toProcess.Enqueue(new Vector(relativePosition.X, relativePosition.Y + 1)); + else if ((adjacent = regions.Get(worldPosition.X, worldPosition.Y + 1)) != null && !adjacentRegions.Contains(adjacent)) + adjacentRegions.Add(adjacent); + if (relativePosition.Y > 0) + toProcess.Enqueue(new Vector(relativePosition.X, relativePosition.Y - 1)); + else if ((adjacent = regions.Get(worldPosition.X, worldPosition.Y - 1)) != null && !adjacentRegions.Contains(adjacent)) + adjacentRegions.Add(adjacent); + } + //Now we have assigned all tiles within our region to be associated to this region + //We now need to calculate parent regions with all adjacent regions + foreach (Region adjacentRegion in adjacentRegions) + { +#if REGION_LOGGING + Log.WriteLine($"Joining region {createdRegion.Id} to {adjacentRegion.Id}"); +#endif + //The level of the shared parent + int sharedParentLevel = GetSharedParentLevel(createdRegion, adjacentRegion); +#if REGION_LOGGING + Log.WriteLine($"Shared parent level: {sharedParentLevel}"); +#endif + //Since these regions are adjacent, they should share a parent at the shared parent level. + //Get pointers for the current region + Region leftParent = createdRegion; + Region rightParent = adjacentRegion; + //Iterate upwards, creating parent regions where none exist + for (int level = 1; level <= sharedParentLevel - 1; level++) + { + //Create the parent if either are null + if (leftParent.Parent == null) + { + //Parent position is the integer division result of any + //child position divided by the region child size + //We can work this out by doing integer division with the position of + //The top level child and region children size ^ depth + leftParent.Parent = new Region( + createdRegion.X / (int)Math.Pow(REGION_CHILDREN_SIZE, level), + createdRegion.Y / (int)Math.Pow(REGION_CHILDREN_SIZE, level), + level); +#if REGION_LOGGING + Log.WriteLine($"Created new parent node for {leftParent.Id}, parent node {leftParent.Parent.Id} at depth {level}"); + Log.WriteLine($"Joined {leftParent.Id} --> {leftParent.Parent.Id}"); +#endif + } + if (rightParent.Parent == null) + { + rightParent.Parent = new Region( + adjacentRegion.X / (int)Math.Pow(REGION_CHILDREN_SIZE, level), + adjacentRegion.Y / (int)Math.Pow(REGION_CHILDREN_SIZE, level), + level); +#if REGION_LOGGING + Log.WriteLine($"Created new parent node for {rightParent.Id}, parent node {rightParent.Parent.Id} at depth {level}"); + Log.WriteLine($"Joined {rightParent.Id} --> {rightParent.Parent.Id}"); + Log.WriteLine($"Joined {leftParent.Id} --> {leftParent.Parent.Id}"); +#endif + } + //Get the left and right parent + leftParent = leftParent.Parent; + rightParent = rightParent.Parent; + } + //Now we are at a shared parent level, check if anything exists + //If so, then attach both to the shared parent + //If not, then create a new shared parent and attach both to it + if (rightParent.Parent != null) + { + leftParent.Parent = rightParent.Parent; +#if REGION_LOGGING + Log.WriteLine($"Joined {leftParent.Id} --> {rightParent.Parent.Id}"); +#endif + } + else + { + if (leftParent.Parent == null) + { + leftParent.Parent = new Region( + createdRegion.X / (int)Math.Pow(REGION_CHILDREN_SIZE, sharedParentLevel), + createdRegion.Y / (int)Math.Pow(REGION_CHILDREN_SIZE, sharedParentLevel), + sharedParentLevel); +#if REGION_LOGGING + Log.WriteLine($"Created new parent node {leftParent.Parent.Id} at depth {sharedParentLevel}"); + Log.WriteLine($"Joined {leftParent.Id} --> {leftParent.Parent.Id}"); +#endif + } + rightParent.Parent = leftParent.Parent; +#if REGION_LOGGING + Log.WriteLine($"Joined {rightParent.Id} --> {leftParent.Parent.Id}"); +#endif + } + } + return createdRegion; + } + + /// + /// Generates the region that contains the provided X,Y world coordinates. + /// + public void GenerateWorldSection(int x, int y) + { + //WARNING: Integer divison rounds towards 0. + //Positive values (8, 16, 24, 32 etc..) should work fine, however it needs to be ensured that (-1 and 1) don't get assigned to the same region, + //since we don't want region (x, y), (-x, y), (x, -y), (-x, -y) to all be assigned to region (0, 0) + int regionX = (int)Math.Floor((float)x / REGION_PRIMARY_LEVEL_SIZE); + int regionY = (int)Math.Floor((float)y / REGION_PRIMARY_LEVEL_SIZE); +#if REGION_LOGGING + Log.WriteLine($"Generating region ({regionX}, {regionY})"); +#endif + //Check if the region exists already + if (regions.Get(x, y) != null) + return; + //The region doesn't exist, so we need to generate one + bool[,] processedNodes = new bool[REGION_PRIMARY_LEVEL_SIZE, REGION_PRIMARY_LEVEL_SIZE]; + //Check each node and flood fill a region if necessary + for (int nodeX = 0; nodeX < REGION_PRIMARY_LEVEL_SIZE; nodeX++) + { + for (int nodeY = 0; nodeY < REGION_PRIMARY_LEVEL_SIZE; nodeY++) + { + //This node has already been assigned a region + if (processedNodes[nodeX, nodeY]) + continue; + //Get the real world coordinates + int realX = regionX * REGION_PRIMARY_LEVEL_SIZE + nodeX; + int realY = regionY * REGION_PRIMARY_LEVEL_SIZE + nodeY; + //This position is solid, ignore (We don't need to update processedNodes, since we will never again access it) + if (World.Current.IsSolid(realX, realY)) + continue; + List adjacentRegions = null; + FloodFillCreateRegion(realX, realY, ref processedNodes, ref adjacentRegions); + } + } } /// @@ -67,6 +314,11 @@ public void SetNodeSolid(int x, int y) /// public bool NodeSolidRequiresUpdate(Region actingRegion, int x, int y) { + //Calculate region X and Y + int regionX = (int)Math.Floor((float)x / REGION_PRIMARY_LEVEL_SIZE); + int regionY = (int)Math.Floor((float)y / REGION_PRIMARY_LEVEL_SIZE); + int regionOffsetX = regionX * REGION_PRIMARY_LEVEL_SIZE; + int regionOffsetY = regionY * REGION_PRIMARY_LEVEL_SIZE; //Get the relative X and Y with a safe mod operation int relX = ((x % REGION_PRIMARY_LEVEL_SIZE) + REGION_PRIMARY_LEVEL_SIZE) % REGION_PRIMARY_LEVEL_SIZE; int relY = ((y % REGION_PRIMARY_LEVEL_SIZE) + REGION_PRIMARY_LEVEL_SIZE) % REGION_PRIMARY_LEVEL_SIZE; @@ -77,19 +329,19 @@ public bool NodeSolidRequiresUpdate(Region actingRegion, int x, int y) Queue> updateQueue = new Queue>(); //Locate an adjacent, open node. //Insert the first node - if (relX < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(relX + 1, relY)) + if (relX < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(x + 1, y)) { updateQueue.Enqueue(new Vector(relX + 1, relY)); } - else if (relX > 0 && !World.Current.IsSolid(relX - 1, relY)) + else if (relX > 0 && !World.Current.IsSolid(x - 1, y)) { updateQueue.Enqueue(new Vector(relX - 1, relY)); } - else if (relY < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(relX, relY + 1)) + else if (relY < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(x, y + 1)) { updateQueue.Enqueue(new Vector(relX, relY + 1)); } - else if (relY > 0 && !World.Current.IsSolid(relX, relY - 1)) + else if (relY > 0 && !World.Current.IsSolid(x, y - 1)) { updateQueue.Enqueue(new Vector(relX, relY - 1)); } @@ -105,209 +357,43 @@ public bool NodeSolidRequiresUpdate(Region actingRegion, int x, int y) //Mark the current node floodFilledIds[current.X, current.Y] = true; //Add adjacent, non-solid, unmarked nodes - if (current.X < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(current.X + 1, current.Y) && !floodFilledIds[current.X + 1, current.Y]) + if (current.X < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(regionOffsetX + current.X + 1, regionOffsetY + current.Y) && !floodFilledIds[current.X + 1, current.Y]) { updateQueue.Enqueue(new Vector(current.X + 1, current.Y)); } - if (current.X > 0 && !World.Current.IsSolid(current.X - 1, current.Y) && !floodFilledIds[current.X - 1, current.Y]) + if (current.X > 0 && !World.Current.IsSolid(regionOffsetX + current.X - 1, regionOffsetY + current.Y) && !floodFilledIds[current.X - 1, current.Y]) { updateQueue.Enqueue(new Vector(current.X - 1, current.Y)); } - if (current.Y < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(current.X, current.Y + 1) && !floodFilledIds[current.X, current.Y + 1]) + if (current.Y < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(regionOffsetX + current.X, regionOffsetY + current.Y + 1) && !floodFilledIds[current.X, current.Y + 1]) { updateQueue.Enqueue(new Vector(current.X, current.Y + 1)); } - if (current.Y > 0 && !World.Current.IsSolid(current.X, current.Y - 1) && !floodFilledIds[current.X, current.Y - 1]) + if (current.Y > 0 && !World.Current.IsSolid(regionOffsetX + current.X, regionOffsetY + current.Y - 1) && !floodFilledIds[current.X, current.Y - 1]) { updateQueue.Enqueue(new Vector(current.X, current.Y - 1)); } } //Require an update if all adjacent, non-solid nodes are not marked - if (relX < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(relX + 1, relY) && !floodFilledIds[relX + 1, relY]) + if (relX < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(x + 1, y) && !floodFilledIds[relX + 1, relY]) { return true; } - else if (relX > 0 && !World.Current.IsSolid(relX - 1, relY) && !floodFilledIds[relX - 1, relY]) + else if (relX > 0 && !World.Current.IsSolid(x - 1, y) && !floodFilledIds[relX - 1, relY]) { return true; } - else if (relY < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(relX, relY + 1) && !floodFilledIds[relX, relY + 1]) + else if (relY < REGION_PRIMARY_LEVEL_SIZE - 1 && !World.Current.IsSolid(x, y + 1) && !floodFilledIds[relX, relY + 1]) { return true; } - else if (relY > 0 && !World.Current.IsSolid(relX, relY - 1) && !floodFilledIds[relX, relY - 1]) + else if (relY > 0 && !World.Current.IsSolid(x, y - 1) && !floodFilledIds[relX, relY - 1]) { return true; } return false; } - /// - /// Generates the region that contains the provided X,Y world coordinates. - /// - public void GenerateWorldSection(int x, int y) - { - //WARNING: Integer divison rounds towards 0. - //Positive values (8, 16, 24, 32 etc..) should work fine, however it needs to be ensured that (-1 and 1) don't get assigned to the same region, - //since we don't want region (x, y), (-x, y), (x, -y), (-x, -y) to all be assigned to region (0, 0) - int regionX = (int)Math.Floor((float)x / REGION_PRIMARY_LEVEL_SIZE); - int regionY = (int)Math.Floor((float)y / REGION_PRIMARY_LEVEL_SIZE); -#if REGION_LOGGING - Log.WriteLine($"Generating region ({regionX}, {regionY})"); -#endif - //Check if the region exists already - if (regions.Get(x, y) != null) - return; - //The region doesn't exist, so we need to generate one - bool[,] processedNodes = new bool[REGION_PRIMARY_LEVEL_SIZE, REGION_PRIMARY_LEVEL_SIZE]; - //Check each node and flood fill a region if necessary - for (int nodeX = 0; nodeX < REGION_PRIMARY_LEVEL_SIZE; nodeX++) - { - for (int nodeY = 0; nodeY < REGION_PRIMARY_LEVEL_SIZE; nodeY++) - { - //This node has already been assigned a region - if (processedNodes[nodeX, nodeY]) - continue; - //Get the real world coordinates - int realX = regionX * REGION_PRIMARY_LEVEL_SIZE + nodeX; - int realY = regionY * REGION_PRIMARY_LEVEL_SIZE + nodeY; - //This position is solid, ignore (We don't need to update processedNodes, since we will never again access it) - if (World.Current.IsSolid(realX, realY)) - continue; - //Create a new region and flood fill outwards - Region createdRegion = new Region(regionX, regionY); -#if REGION_LOGGING - Log.WriteLine($"Created new region {createdRegion.Id} at ({regionX}, {regionY})"); -#endif - //Have the position join this region - regions.Add(realX, realY, createdRegion); - //During this process, get the adjacent regions so we can match up parents - List adjacentRegions = new List(); - //Processing queue - Queue> toProcess = new Queue>(); - toProcess.Enqueue(new Vector(nodeX, nodeY)); - //While we still have places to floor fill - while (toProcess.Count > 0) - { - //Dequeue - Vector relativePosition = toProcess.Dequeue(); - //If current node is already processed, skip - if (processedNodes[relativePosition.X, relativePosition.Y]) - continue; - //Calculate the world position of the current node - Vector worldPosition = new Vector(regionX * REGION_PRIMARY_LEVEL_SIZE, regionY * REGION_PRIMARY_LEVEL_SIZE) + relativePosition; - //Set node processed - processedNodes[relativePosition.X, relativePosition.Y] = true; - //If current node is a wall, skip - if (World.Current.IsSolid(worldPosition.X, worldPosition.Y)) - continue; - //Join the region - regions.Add(worldPosition.X, worldPosition.Y, createdRegion); - //Check if we have any adjacent regions - Region adjacent; - //Add adjacent nodes (Assuming they are within bounds) - if (relativePosition.X < REGION_PRIMARY_LEVEL_SIZE - 1) - toProcess.Enqueue(new Vector(relativePosition.X + 1, relativePosition.Y)); - else if ((adjacent = regions.Get(worldPosition.X + 1, worldPosition.Y)) != null && !adjacentRegions.Contains(adjacent)) - adjacentRegions.Add(adjacent); - if (relativePosition.X > 0) - toProcess.Enqueue(new Vector(relativePosition.X - 1, relativePosition.Y)); - else if ((adjacent = regions.Get(worldPosition.X - 1, worldPosition.Y)) != null && !adjacentRegions.Contains(adjacent)) - adjacentRegions.Add(adjacent); - if (relativePosition.Y < REGION_PRIMARY_LEVEL_SIZE - 1) - toProcess.Enqueue(new Vector(relativePosition.X, relativePosition.Y + 1)); - else if ((adjacent = regions.Get(worldPosition.X, worldPosition.Y + 1)) != null && !adjacentRegions.Contains(adjacent)) - adjacentRegions.Add(adjacent); - if (relativePosition.Y > 0) - toProcess.Enqueue(new Vector(relativePosition.X, relativePosition.Y - 1)); - else if ((adjacent = regions.Get(worldPosition.X, worldPosition.Y - 1)) != null && !adjacentRegions.Contains(adjacent)) - adjacentRegions.Add(adjacent); - } - //Now we have assigned all tiles within our region to be associated to this region - //We now need to calculate parent regions with all adjacent regions - foreach (Region adjacentRegion in adjacentRegions) - { -#if REGION_LOGGING - Log.WriteLine($"Joining region {createdRegion.Id} to {adjacentRegion.Id}"); -#endif - //The level of the shared parent - int sharedParentLevel = GetSharedParentLevel(createdRegion, adjacentRegion); -#if REGION_LOGGING - Log.WriteLine($"Shared parent level: {sharedParentLevel}"); -#endif - //Since these regions are adjacent, they should share a parent at the shared parent level. - //Get pointers for the current region - Region leftParent = createdRegion; - Region rightParent = adjacentRegion; - //Iterate upwards, creating parent regions where none exist - for (int level = 1; level <= sharedParentLevel - 1; level++) - { - //Create the parent if either are null - if (leftParent.Parent == null) - { - //Parent position is the integer division result of any - //child position divided by the region child size - //We can work this out by doing integer division with the position of - //The top level child and region children size ^ depth - leftParent.Parent = new Region( - createdRegion.X / (int)Math.Pow(REGION_CHILDREN_SIZE, level), - createdRegion.Y / (int)Math.Pow(REGION_CHILDREN_SIZE, level), - level); -#if REGION_LOGGING - Log.WriteLine($"Created new parent node for {leftParent.Id}, parent node {leftParent.Parent.Id} at depth {level}"); - Log.WriteLine($"Joined {leftParent.Id} --> {leftParent.Parent.Id}"); -#endif - } - if (rightParent.Parent == null) - { - rightParent.Parent = new Region( - adjacentRegion.X / (int)Math.Pow(REGION_CHILDREN_SIZE, level), - adjacentRegion.Y / (int)Math.Pow(REGION_CHILDREN_SIZE, level), - level); -#if REGION_LOGGING - Log.WriteLine($"Created new parent node for {rightParent.Id}, parent node {rightParent.Parent.Id} at depth {level}"); - Log.WriteLine($"Joined {rightParent.Id} --> {rightParent.Parent.Id}"); t depth { level} - "); - Log.WriteLine($"Joined {leftParent.Id} --> {leftParent.Parent.Id}"); -#endif - } - //Get the left and right parent - leftParent = leftParent.Parent; - rightParent = rightParent.Parent; - } - //Now we are at a shared parent level, check if anything exists - //If so, then attach both to the shared parent - //If not, then create a new shared parent and attach both to it - if (rightParent.Parent != null) - { - leftParent.Parent = rightParent.Parent; -#if REGION_LOGGING - Log.WriteLine($"Joined {leftParent.Id} --> {rightParent.Parent.Id}"); -#endif - } - else - { - if (leftParent.Parent == null) - { - leftParent.Parent = new Region( - createdRegion.X / (int)Math.Pow(REGION_CHILDREN_SIZE, sharedParentLevel), - createdRegion.Y / (int)Math.Pow(REGION_CHILDREN_SIZE, sharedParentLevel), - sharedParentLevel); -#if REGION_LOGGING - Log.WriteLine($"Created new parent node {leftParent.Parent.Id} at depth {sharedParentLevel}"); - Log.WriteLine($"Joined {leftParent.Id} --> {leftParent.Parent.Id}"); -#endif - } - rightParent.Parent = leftParent.Parent; -#if REGION_LOGGING - Log.WriteLine($"Joined {rightParent.Id} --> {leftParent.Parent.Id}"); -#endif - } - } - } - } - } - /// /// Returns the level at which 2 nodes would in theory have a shared parent. /// From 369ed0bcabded0ad72c1e1f7a8c6e19e6c97f744 Mon Sep 17 00:00:00 2001 From: PowerfulBacon <> Date: Sat, 19 Mar 2022 20:33:55 +0000 Subject: [PATCH 16/16] Almost finishes this --- GJ2022.Tests/RegionTests.cs | 2 + .../PositionBasedBinaryListUnitTests.cs | 29 ++++++++++++++ .../Game/GameWorld/Regions/WorldRegionList.cs | 6 +-- .../MathConstructs/PositionBasedBinaryList.cs | 38 +++++++++++++++---- 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/GJ2022.Tests/RegionTests.cs b/GJ2022.Tests/RegionTests.cs index e016947..084fad2 100644 --- a/GJ2022.Tests/RegionTests.cs +++ b/GJ2022.Tests/RegionTests.cs @@ -46,6 +46,8 @@ public void TestValidPathThatGetsBlocked() World.Current.SetTurf(12, 4, solidTurf); wrl.SetNodeSolid(12, 4); Log.WriteLine("World has been divided"); + Log.WriteLine("World status:"); + Log.WriteLine(wrl.regions); Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(5, 5)), "There should still exist a valid path from region (0, 0) to (5, 5)"); Assert.IsTrue(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(10, 5)), "There should still exist a valid path from region (0, 0) to (10, 5)"); Assert.IsFalse(wrl.regions.Get(0, 0).HasPath(wrl.regions.Get(14, 5)), "There should no longer exist a valid path from region (0, 0) to (14, 5)"); diff --git a/GJ2022.Tests/UtilityTests/PositionBasedBinaryListUnitTests.cs b/GJ2022.Tests/UtilityTests/PositionBasedBinaryListUnitTests.cs index 63b16d4..bf62b07 100644 --- a/GJ2022.Tests/UtilityTests/PositionBasedBinaryListUnitTests.cs +++ b/GJ2022.Tests/UtilityTests/PositionBasedBinaryListUnitTests.cs @@ -7,6 +7,35 @@ namespace GJ2022.Tests.UtilityTests public class PositionBasedBinaryListUnitTests { + [TestMethod] + [ExpectedException(typeof(System.IndexOutOfRangeException))] + public void TestInsertionFailure() + { + PositionBasedBinaryList randomList = new PositionBasedBinaryList(); + randomList.Add(0, 0, 1); + randomList.Add(0, 0, 2); + } + + [TestMethod] + public void TestReplace() + { + PositionBasedBinaryList randomList = new PositionBasedBinaryList(); + randomList.Add(0, 0, 1); + randomList.Add(0, 1, 2); + randomList.Add(0, 2, 4); + randomList.Add(0, 3, 5); + randomList.Add(0, 1, 3, true); + randomList.Add(0, 2, 3, true); + //Account for both cases of midpoint rounding + Assert.AreEqual(3, randomList.Get(0, 1), "Expected 3 at (0, 1)"); + Assert.AreEqual(3, randomList.Get(0, 2), "Expected 3 at (0, 2)"); + for (int i = 0; i < 4; i++) + { + randomList.TakeFirst(); + } + Assert.IsFalse(randomList.HasElements, $"Expected list to only contain 4 elements, actually contains {randomList}"); + } + [TestMethod] public void TestTakeFirst() { diff --git a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs index e32cc95..bdde0c4 100644 --- a/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs +++ b/GJ2022/Game/GameWorld/Regions/WorldRegionList.cs @@ -85,7 +85,7 @@ public void SetNodeSolid(int x, int y) { createdRegions.Add(FloodFillCreateRegion(x, y - 1, ref adjacentRegions)); } - //Recalculate adjacencies + //Recalculate adjacencies of the created regions foreach (Region r in createdRegions) { Log.WriteLine($"Created region {r.Id}"); @@ -134,7 +134,7 @@ private Region FloodFillCreateRegion(int x, int y, ref bool[,] processedNodes, r Log.WriteLine($"Created new region {createdRegion.Id} at ({regionX}, {regionY})"); #endif //Have the position join this region - regions.Add(x, y, createdRegion); + regions.Add(x, y, createdRegion, true); //During this process, get the adjacent regions so we can match up parents if(adjacentRegions == null) adjacentRegions = new List(); @@ -157,7 +157,7 @@ private Region FloodFillCreateRegion(int x, int y, ref bool[,] processedNodes, r if (World.Current.IsSolid(worldPosition.X, worldPosition.Y)) continue; //Join the region - regions.Add(worldPosition.X, worldPosition.Y, createdRegion); + regions.Add(worldPosition.X, worldPosition.Y, createdRegion, true); //Check if we have any adjacent regions Region adjacent; //Add adjacent nodes (Assuming they are within bounds) diff --git a/GJ2022/Utility/MathConstructs/PositionBasedBinaryList.cs b/GJ2022/Utility/MathConstructs/PositionBasedBinaryList.cs index 9009345..077a986 100644 --- a/GJ2022/Utility/MathConstructs/PositionBasedBinaryList.cs +++ b/GJ2022/Utility/MathConstructs/PositionBasedBinaryList.cs @@ -42,7 +42,7 @@ public int Length() return binaryListElements.Count; } - public int Add(int key, T toAdd, int start = 0, int _end = -1) + public int Add(int key, T toAdd, bool replace = false, int start = 0, int _end = -1) { //No elements, just add if (binaryListElements.Count == 0) @@ -62,19 +62,43 @@ public int Add(int key, T toAdd, int start = 0, int _end = -1) { //Check if the midpoint is too small or too large BinaryListElement convergedPoint = binaryListElements.ElementAt(midPoint); + //Check regular cases if (convergedPoint.key > key) - binaryListElements.Insert(midPoint, new BinaryListElement(key, toAdd)); + { + //Check next element is what we were looking for + if (midPoint > 0 && binaryListElements.ElementAt(midPoint - 1).key == key) + { + if (replace) + binaryListElements.ElementAt(midPoint - 1).value = toAdd; + else + throw new IndexOutOfRangeException($"Duplicate element added to binary list at {key}"); + } + else + binaryListElements.Insert(midPoint, new BinaryListElement(key, toAdd)); + } + else if (convergedPoint.key < key) + if (midPoint < binaryListElements.Count - 1 && binaryListElements.ElementAt(midPoint + 1).key == key) + { + if (replace) + binaryListElements.ElementAt(midPoint + 1).value = toAdd; + else + throw new IndexOutOfRangeException($"Duplicate element added to binary list at {key}"); + } + else + binaryListElements.Insert(midPoint + 1, new BinaryListElement(key, toAdd)); + else if (replace) + convergedPoint.value = toAdd; else - binaryListElements.Insert(midPoint + 1, new BinaryListElement(key, toAdd)); + throw new IndexOutOfRangeException($"Duplicate element added to binary list at {key}"); return -1; } //Locate the element at the midpoint BinaryListElement current = binaryListElements.ElementAt(midPoint); //Perform next search if (current.key > key) - return Add(key, toAdd, start, Math.Max(midPoint - 1, 0)); + return Add(key, toAdd, replace, start, Math.Max(midPoint - 1, 0)); else - return Add(key, toAdd, midPoint + 1, end); + return Add(key, toAdd, replace, midPoint + 1, end); } public void Remove(int key) @@ -170,7 +194,7 @@ public T First() return list.First().First(); } - public void Add(int x, int y, T element) + public void Add(int x, int y, T element, bool replace = false) { //Locate the X element BinaryList located = list.ElementWithKey(x); @@ -182,7 +206,7 @@ public void Add(int x, int y, T element) list.Add(x, located); } //Add the y element - located.Add(y, element); + located.Add(y, element, replace); } public void Remove(int x, int y)