summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/Chunk.cpp2
-rw-r--r--src/Entities/Entity.cpp47
-rw-r--r--src/Entities/Entity.h26
-rw-r--r--src/Entities/Player.cpp7
-rw-r--r--src/Entities/Player.h2
-rw-r--r--src/NetherPortalScanner.cpp290
-rw-r--r--src/NetherPortalScanner.h75
8 files changed, 431 insertions, 20 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c68795bb3..429087ae2 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -54,6 +54,7 @@ SET (SRCS
MobProximityCounter.cpp
MobSpawner.cpp
MonsterConfig.cpp
+ NetherPortalScanner.cpp
OverridesSettingsRepository.cpp
ProbabDistrib.cpp
RankManager.cpp
@@ -126,6 +127,7 @@ SET (HDRS
MobProximityCounter.h
MobSpawner.h
MonsterConfig.h
+ NetherPortalScanner.h
OverridesSettingsRepository.h
ProbabDistrib.h
RankManager.h
diff --git a/src/Chunk.cpp b/src/Chunk.cpp
index 7af669163..2e83f2b72 100644
--- a/src/Chunk.cpp
+++ b/src/Chunk.cpp
@@ -640,6 +640,7 @@ void cChunk::Tick(std::chrono::milliseconds a_Dt)
else if ((*itr)->IsWorldTravellingFrom(m_World))
{
// Remove all entities that are travelling to another world
+ LOGD("Removing entity from [%d, %d] that's travelling between worlds.", m_PosX, m_PosZ);
MarkDirty();
(*itr)->SetWorldTravellingFrom(nullptr);
itr = m_Entities.erase(itr);
@@ -695,7 +696,6 @@ void cChunk::MoveEntityToNewChunk(cEntity * a_Entity)
}
ASSERT(Neighbor != this); // Moving into the same chunk? wtf?
-
Neighbor->AddEntity(a_Entity);
class cMover :
diff --git a/src/Entities/Entity.cpp b/src/Entities/Entity.cpp
index 91eb0744a..108f79e82 100644
--- a/src/Entities/Entity.cpp
+++ b/src/Entities/Entity.cpp
@@ -13,6 +13,7 @@
#include "Player.h"
#include "Items/ItemHandler.h"
#include "../FastRandom.h"
+#include "../NetherPortalScanner.h"
@@ -42,6 +43,7 @@ cEntity::cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, d
m_WorldTravellingFrom(nullptr),
m_EntityType(a_EntityType),
m_World(nullptr),
+ m_IsWorldChangeScheduled(false),
m_IsFireproof(false),
m_TicksSinceLastBurnDamage(0),
m_TicksSinceLastLavaDamage(0),
@@ -1260,9 +1262,26 @@ void cEntity::DetectCacti(void)
+void cEntity::ScheduleMoveToWorld(cWorld * a_World, Vector3d a_NewPosition)
+{
+ m_NewWorld = a_World;
+ m_NewWorldPosition = a_NewPosition;
+ m_IsWorldChangeScheduled = true;
+}
+
+
+
bool cEntity::DetectPortal()
{
+ // If somebody scheduled a world change with ScheduleMoveToWorld, change worlds now.
+ if (m_IsWorldChangeScheduled)
+ {
+ m_IsWorldChangeScheduled = false;
+ MoveToWorld(m_NewWorld, false, m_NewWorldPosition);
+ return true;
+ }
+
if (GetWorld()->GetDimension() == dimOverworld)
{
if (GetWorld()->GetLinkedNetherWorldName().empty() && GetWorld()->GetLinkedEndWorldName().empty())
@@ -1312,8 +1331,15 @@ bool cEntity::DetectPortal()
// Send a respawn packet before world is loaded / generated so the client isn't left in limbo
((cPlayer *)this)->GetClientHandle()->SendRespawn(dimOverworld);
}
-
- return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName()), false);
+
+ Vector3d TargetPos = GetPosition();
+ TargetPos.x *= 8.0;
+ TargetPos.z *= 8.0;
+
+ cWorld * TargetWorld = cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedOverworldName(), dimNether, GetWorld()->GetName());
+ LOGD("Jumping nether -> overworld");
+ new cNetherPortalScanner(this, TargetWorld, TargetPos, 256);
+ return false;
}
else
{
@@ -1329,8 +1355,15 @@ bool cEntity::DetectPortal()
((cPlayer *)this)->AwardAchievement(achEnterPortal);
((cPlayer *)this)->GetClientHandle()->SendRespawn(dimNether);
}
-
- return MoveToWorld(cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedNetherWorldName(), dimNether, GetWorld()->GetName()), false);
+
+ Vector3d TargetPos = GetPosition();
+ TargetPos.x /= 8.0;
+ TargetPos.z /= 8.0;
+
+ cWorld * TargetWorld = cRoot::Get()->CreateAndInitializeWorld(GetWorld()->GetLinkedNetherWorldName(), dimNether, GetWorld()->GetName());
+ LOGD("Jumping overworld -> nether");
+ new cNetherPortalScanner(this, TargetWorld, TargetPos, 128);
+ return false;
}
}
case E_BLOCK_END_PORTAL:
@@ -1392,7 +1425,7 @@ bool cEntity::DetectPortal()
-bool cEntity::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn)
+bool cEntity::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition)
{
UNUSED(a_ShouldSendRespawn);
ASSERT(a_World != nullptr);
@@ -1414,6 +1447,8 @@ bool cEntity::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn)
SetWorldTravellingFrom(GetWorld()); // cChunk::Tick() handles entity removal
GetWorld()->BroadcastDestroyEntity(*this);
+ SetPosition(a_NewPosition);
+
// Queue add to new world
a_World->AddEntity(this);
cWorld * OldWorld = cRoot::Get()->GetWorld(GetWorld()->GetName()); // Required for the hook HOOK_ENTITY_CHANGED_WORLD
@@ -1438,7 +1473,7 @@ bool cEntity::MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn)
return false;
}
- return DoMoveToWorld(World, a_ShouldSendRespawn);
+ return DoMoveToWorld(World, a_ShouldSendRespawn, GetPosition());
}
diff --git a/src/Entities/Entity.h b/src/Entities/Entity.h
index 58d1287e7..8d1d62ddf 100644
--- a/src/Entities/Entity.h
+++ b/src/Entities/Entity.h
@@ -392,15 +392,20 @@ public:
/// Teleports to the coordinates specified
virtual void TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ);
+ /// Schedules a MoveToWorld call to occur on the next Tick of the entity
+ void ScheduleMoveToWorld(cWorld * a_World, Vector3d a_NewPosition);
+
+ bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) { return DoMoveToWorld(a_World, a_ShouldSendRespawn, a_NewPosition); }
+
/** Moves entity to specified world, taking a world pointer */
- bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn = true) { return DoMoveToWorld(a_World, a_ShouldSendRespawn); }
+ bool MoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn = true) { return MoveToWorld(a_World, a_ShouldSendRespawn, GetPosition()); }
/** Moves entity to specified world, taking a world name */
bool MoveToWorld(const AString & a_WorldName, bool a_ShouldSendRespawn = true);
// tolua_end
- virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn);
+ virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition);
/** Returns if the entity is travelling away from a specified world */
bool IsWorldTravellingFrom(cWorld * a_World) const { return (m_WorldTravellingFrom == a_World); }
@@ -530,23 +535,28 @@ protected:
eEntityType m_EntityType;
cWorld * m_World;
+
+ /** State variables for ScheduleMoveToWorld. */
+ bool m_IsWorldChangeScheduled;
+ cWorld * m_NewWorld;
+ Vector3d m_NewWorldPosition;
- /// Whether the entity is capable of taking fire or lava damage.
+ /** Whether the entity is capable of taking fire or lava damage. */
bool m_IsFireproof;
- /// Time, in ticks, since the last damage dealt by being on fire. Valid only if on fire (IsOnFire())
+ /** Time, in ticks, since the last damage dealt by being on fire. Valid only if on fire (IsOnFire()) */
int m_TicksSinceLastBurnDamage;
- /// Time, in ticks, since the last damage dealt by standing in lava. Reset to zero when moving out of lava.
+ /** Time, in ticks, since the last damage dealt by standing in lava. Reset to zero when moving out of lava. */
int m_TicksSinceLastLavaDamage;
- /// Time, in ticks, since the last damage dealt by standing in fire. Reset to zero when moving out of fire.
+ /** Time, in ticks, since the last damage dealt by standing in fire. Reset to zero when moving out of fire. */
int m_TicksSinceLastFireDamage;
- /// Time, in ticks, until the entity extinguishes its fire
+ /** Time, in ticks, until the entity extinguishes its fire */
int m_TicksLeftBurning;
- /// Time, in ticks, since the last damage dealt by the void. Reset to zero when moving out of the void.
+ /** Time, in ticks, since the last damage dealt by the void. Reset to zero when moving out of the void. */
int m_TicksSinceLastVoidDamage;
/** Does the actual speed-setting. The default implementation just sets the member variable value;
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index e3e3fac4f..0ca560d75 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -1620,10 +1620,7 @@ void cPlayer::TossItems(const cItems & a_Items)
}
-
-
-
-bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn)
+bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition)
{
ASSERT(a_World != nullptr);
@@ -1653,6 +1650,8 @@ bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn)
SetWorldTravellingFrom(GetWorld()); // cChunk handles entity removal
GetWorld()->RemovePlayer(this, false);
+ SetPosition(a_NewPosition);
+
// Queue adding player to the new world, including all the necessary adjustments to the object
a_World->AddPlayer(this);
cWorld * OldWorld = cRoot::Get()->GetWorld(GetWorld()->GetName()); // Required for the hook HOOK_ENTITY_CHANGED_WORLD
diff --git a/src/Entities/Player.h b/src/Entities/Player.h
index 799990bf2..dffb61677 100644
--- a/src/Entities/Player.h
+++ b/src/Entities/Player.h
@@ -358,7 +358,7 @@ public:
/** Moves the player to the specified world.
Returns true if successful, false on failure (world not found). */
- virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn) override;
+ virtual bool DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d a_NewPosition) override;
/** Saves all player data, such as inventory, to JSON */
bool SaveToDisk(void);
diff --git a/src/NetherPortalScanner.cpp b/src/NetherPortalScanner.cpp
new file mode 100644
index 000000000..fe563509d
--- /dev/null
+++ b/src/NetherPortalScanner.cpp
@@ -0,0 +1,290 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "NetherPortalScanner.h"
+#include "Vector3.h"
+#include "Entities/Entity.h"
+#include "World.h"
+
+
+
+
+
+cNetherPortalScanner::cNetherPortalScanner(cEntity * a_MovingEntity, cWorld * a_DestinationWorld, Vector3d a_DestPosition, int a_MaxY) :
+ m_Entity(a_MovingEntity),
+ m_World(a_DestinationWorld),
+ m_FoundPortal(false),
+ m_BuildPlatform(true),
+ m_Dir(Direction::X),
+ m_PortalLoc(a_DestPosition.Floor()),
+ m_Position(a_DestPosition),
+ m_MaxY(a_MaxY)
+{
+ int MinX = FloorC((m_Position.x - SearchRadius) / cChunkDef::Width);
+ int MinZ = FloorC((m_Position.z - SearchRadius) / cChunkDef::Width);
+ int MaxX = CeilC((m_Position.x + SearchRadius) / cChunkDef::Width);
+ int MaxZ = CeilC((m_Position.z + SearchRadius) / cChunkDef::Width);
+ for (int x = MinX; x < MaxX; x++)
+ {
+ for (int z = MinZ; z < MaxZ; z++)
+ {
+ Add(x, z);
+ }
+ }
+ Enable(*a_DestinationWorld->GetChunkMap());
+}
+
+
+
+
+
+void cNetherPortalScanner::OnChunkAvailable(int a_ChunkX, int a_ChunkZ)
+{
+ cChunkDef::BlockTypes blocks;
+ m_World->GetChunkBlockTypes(a_ChunkX, a_ChunkZ, blocks);
+
+ // Iterate through all of the blocks in the chunk
+ for (unsigned int i = 0; i < cChunkDef::NumBlocks; i++)
+ {
+ if (blocks[i] == E_BLOCK_NETHER_PORTAL)
+ {
+ Vector3i Coordinate = cChunkDef::IndexToCoordinate(i);
+ Vector3d PortalLoc = Vector3d(Coordinate.x + a_ChunkX * cChunkDef::Width, Coordinate.y, Coordinate.z + a_ChunkZ * cChunkDef::Width);
+ if (!m_FoundPortal)
+ {
+ m_FoundPortal = true;
+ m_PortalLoc = PortalLoc;
+ }
+ else
+ {
+ if ((PortalLoc - m_Position).SqrLength() < (m_PortalLoc - m_Position).SqrLength())
+ {
+ m_FoundPortal = true;
+ m_PortalLoc = PortalLoc;
+ }
+ }
+ }
+ }
+}
+
+
+
+
+
+bool cNetherPortalScanner::IsValidBuildLocation(Vector3i a_BlockPos)
+{
+ // Check the base
+ for (int i = 0; i < SearchSolidBaseWidth; i++)
+ {
+ for (int j = 0; j < PortalLength; j++)
+ {
+ BLOCKTYPE blocktype = m_World->GetBlock(a_BlockPos.x + i, a_BlockPos.y, a_BlockPos.z + j);
+ if (!cBlockInfo::IsSolid(blocktype))
+ {
+ return false;
+ }
+
+ // Check the airspace
+ for (int k = 1; k < PortalHeight; k++)
+ {
+ blocktype = m_World->GetBlock(a_BlockPos.x + i, a_BlockPos.y + k, a_BlockPos.z + j);
+ if (blocktype != E_BLOCK_AIR)
+ {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+
+
+
+
+bool cNetherPortalScanner::OnAllChunksAvailable(void)
+{
+ if (m_FoundPortal)
+ {
+ // Find the bottom of this portal
+ while (m_World->GetBlock(m_PortalLoc.x, m_PortalLoc.y, m_PortalLoc.z) == E_BLOCK_NETHER_PORTAL)
+ {
+ m_PortalLoc.y -= 1.0;
+ }
+ m_PortalLoc.y += 1.0;
+
+ // Figure out which way the portal is facing
+ int BXP = m_World->GetBlock(m_PortalLoc.x + 1, m_PortalLoc.y, m_PortalLoc.z);
+ int BXM = m_World->GetBlock(m_PortalLoc.x - 1, m_PortalLoc.y, m_PortalLoc.z);
+ if ((BXP == E_BLOCK_NETHER_PORTAL) || (BXM == E_BLOCK_NETHER_PORTAL))
+ {
+ // The long axis is along X
+ m_Dir = Direction::X;
+ }
+ else
+ {
+ // The long axis is along Z
+ m_Dir = Direction::Y;
+ }
+ }
+ else
+ {
+ // Scan the area for a suitable location
+ int minx = FloorC(m_Position.x) - BuildSearchRadius;
+ int minz = FloorC(m_Position.z) - BuildSearchRadius;
+ int maxx = FloorC(m_Position.x) + BuildSearchRadius;
+ int maxz = FloorC(m_Position.z) + BuildSearchRadius;
+ int maxy = m_MaxY;
+ std::vector<Vector3i> Possibilities;
+ int x, y, z;
+ for (y = 0; y < maxy - PortalHeight; y++)
+ {
+ for (x = minx; x < maxx - PortalLength; x++)
+ {
+ for (z = minz; z < maxz - SearchSolidBaseWidth; z++)
+ {
+ Vector3i Location = Vector3i(x, y, z);
+ if (IsValidBuildLocation(Location))
+ {
+ Possibilities.push_back(Vector3i(x, y, z));
+ }
+ }
+ }
+ }
+
+ if (Possibilities.size() > 0)
+ {
+ m_BuildPlatform = false;
+
+ // Find the nearest
+ double DistanceToClosest = (Possibilities[0] - m_Position).SqrLength();
+ Vector3i Closest = Possibilities[0];
+ for (const auto & itr : Possibilities)
+ {
+ double Distance = (itr - m_Position).SqrLength();
+ if (Distance < DistanceToClosest)
+ {
+ DistanceToClosest = Distance;
+ Closest = itr;
+ }
+ }
+
+ m_PortalLoc = Closest;
+ }
+ }
+ return true;
+}
+
+
+
+
+
+void cNetherPortalScanner::BuildNetherPortal(Vector3i a_Location, Direction a_Direction, bool a_IncludePlatform)
+{
+ int x = a_Location.x;
+ int y = a_Location.y;
+ int z = a_Location.z;
+
+ // Clear a 3x4x4 area starting right above the base
+ for (int i = 0; i < SearchSolidBaseWidth; i++)
+ {
+ for (int j = 0; j < PortalLength; j++)
+ {
+ for (int k = 1; k < PortalHeight; k++)
+ {
+ if (a_Direction == Direction::Y)
+ {
+ m_World->SetBlock(x + i, y + k, z + j, E_BLOCK_AIR, 0);
+ }
+ else if (a_Direction == Direction::X)
+ {
+ m_World->SetBlock(x + j, y + k, z + i, E_BLOCK_AIR, 0);
+ }
+ }
+ }
+ }
+
+ // Put in an obsidian base
+ if (a_IncludePlatform)
+ {
+ for (int j = 0; j < PortalLength; j++)
+ {
+ // +2 on the short axis because that's where we deposit the entity
+ if (a_Direction == Direction::Y)
+ {
+ m_World->SetBlock(x + 2, y, z + j, E_BLOCK_OBSIDIAN, 0);
+ }
+ else if (a_Direction == Direction::X)
+ {
+ m_World->SetBlock(x + j, y, z + 2, E_BLOCK_OBSIDIAN, 0);
+ }
+ }
+ }
+
+ // Build an obsidian frame
+ for (int i = 0; i < PortalHeight; i++)
+ {
+ if (a_Direction == Direction::Y)
+ {
+ m_World->SetBlock(x + 1, y + i, z, E_BLOCK_OBSIDIAN, 0);
+ m_World->SetBlock(x + 1, y + i, z + 3, E_BLOCK_OBSIDIAN, 0);
+ }
+ else if (a_Direction == Direction::X)
+ {
+ m_World->SetBlock(x, y + i, z + 1, E_BLOCK_OBSIDIAN, 0);
+ m_World->SetBlock(x + 3, y + i, z + 1, E_BLOCK_OBSIDIAN, 0);
+ }
+ }
+ for (int i = 0; i < PortalLength; i++)
+ {
+ if (a_Direction == Direction::Y)
+ {
+ m_World->SetBlock(x + 1, y + 4, z + i, E_BLOCK_OBSIDIAN, 0);
+ m_World->SetBlock(x + 1, y, z + i, E_BLOCK_OBSIDIAN, 0);
+ }
+ else if (a_Direction == Direction::X)
+ {
+ m_World->SetBlock(x + i, y + 4, z + 1, E_BLOCK_OBSIDIAN, 0);
+ m_World->SetBlock(x + i, y, z + 1, E_BLOCK_OBSIDIAN, 0);
+ }
+ }
+
+ // Fill the frame (place a fire in the bottom)
+ m_World->SetBlock(x + 1, y + 1, z + 1, E_BLOCK_FIRE, 0);
+}
+
+
+
+
+
+void cNetherPortalScanner::OnDisabled(void)
+{
+ // Now we actually move the player
+ if (!m_FoundPortal)
+ {
+ // Build a new nether portal.
+ LOGD("Building nether portal at {%d, %d, %d}", m_PortalLoc.x, m_PortalLoc.y, m_PortalLoc.z);
+ BuildNetherPortal(m_PortalLoc, m_Dir, m_BuildPlatform);
+ m_PortalLoc.x += 1;
+ m_PortalLoc.y += 2;
+ m_PortalLoc.z += 1;
+ }
+
+ // Put the entity near the opening
+ Vector3d Position = m_PortalLoc;
+ if (m_Dir == Direction::Y)
+ {
+ Position.x += OutOffset;
+ Position.z += AcrossOffset;
+ }
+ else if (m_Dir == Direction::X)
+ {
+ Position.x += AcrossOffset;
+ Position.z += OutOffset;
+ }
+
+ LOGD("Placing player at {%f, %f, %f}", Position.x, Position.y, Position.z);
+ m_Entity->ScheduleMoveToWorld(m_World, Position);
+ delete this;
+}
+
diff --git a/src/NetherPortalScanner.h b/src/NetherPortalScanner.h
new file mode 100644
index 000000000..89ffd7d0e
--- /dev/null
+++ b/src/NetherPortalScanner.h
@@ -0,0 +1,75 @@
+
+#pragma once
+
+#include "Vector3.h"
+#include "ChunkStay.h"
+
+
+class cEntity;
+class cWorld;
+
+
+
+
+
+// This is the chunk stay which finds nearby nether portals
+class cNetherPortalScanner : public cChunkStay
+{
+public:
+ cNetherPortalScanner(cEntity * a_MovingEntity, cWorld * a_DestinationWorld, Vector3d a_DestPosition, int a_MaxY);
+ virtual void OnChunkAvailable(int a_ChunkX, int a_ChunkY) override;
+ virtual bool OnAllChunksAvailable(void) override;
+ virtual void OnDisabled(void) override;
+
+ enum class Direction
+ {
+ X,
+ Y
+ };
+
+private:
+
+ /** Length and height, including the obsidian. */
+ static const int PortalLength = 4;
+ static const int PortalHeight = 5;
+
+ static const int SearchRadius = 128;
+ static const int BuildSearchRadius = 16;
+
+ /** The width of a solid base to search for when building. */
+ static const int SearchSolidBaseWidth = 3;
+
+ /** Where to place the player out from the face and across the face */
+ const double OutOffset = 1.5;
+ const double AcrossOffset = 0.5;
+
+ /** Builds a portal. */
+ void BuildNetherPortal(Vector3i a_Location, Direction a_Direction, bool a_IncludePlatform);
+
+ /** Whether the given location is a valid location to build a portal. */
+ bool IsValidBuildLocation(Vector3i a_BlockPosition);
+
+ /** The entity that's being moved. */
+ cEntity * m_Entity;
+
+ /** The world we're moving the entity to. */
+ cWorld * m_World;
+
+ /** Whether we found a portal during the loading of the chunks. */
+ bool m_FoundPortal;
+
+ /** Whether to build a platform. True if we couldn't build the portal on solid ground */
+ bool m_BuildPlatform;
+
+ /** The direction of the portal. */
+ Direction m_Dir;
+
+ /** The position of the pre-existing portal. */
+ Vector3i m_PortalLoc;
+
+ /** The center of the search area */
+ Vector3d m_Position;
+
+ /** The maximum Y to scan to */
+ int m_MaxY;
+};