2026-03-01 12:16:08 +08:00
# include "stdafx.h"
# include "net.minecraft.h"
# include "net.minecraft.world.entity.h"
# include "net.minecraft.world.entity.animal.h"
# include "net.minecraft.world.entity.monster.h"
# include "net.minecraft.world.entity.player.h"
# include "net.minecraft.world.level.h"
# include "net.minecraft.world.level.biome.h"
# include "net.minecraft.world.level.material.h"
# include "net.minecraft.world.level.pathfinder.h"
# include "net.minecraft.world.level.tile.h"
# include "Difficulty.h"
# include "WeighedRandom.h"
# include "Level.h"
# include "ChunkPos.h"
# include "TilePos.h"
# include "..\Minecraft.Client\ServerLevel.h"
# include "MobSpawner.h"
# include "Dimension.h"
const int MobSpawner : : MIN_SPAWN_DISTANCE = 24 ;
TilePos MobSpawner : : getRandomPosWithin ( Level * level , int cx , int cz )
{
// 4J Stu - Added 1.2.3 but we don't need it as it was only used to access sections
// Leaving here though to help explain why chunk coords are not passed in rather than full coords
//LevelChunk *chunk = level->getChunk(cx, cz);
int x = cx * 16 + level - > random - > nextInt ( 16 ) ;
int y = level - > random - > nextInt ( level - > getHeight ( ) ) ;
int z = cz * 16 + level - > random - > nextInt ( 16 ) ;
return TilePos ( x , y , z ) ;
}
# ifdef __PSVITA__
// AP - See CustomMap.h for an explanation of this
CustomMap MobSpawner : : chunksToPoll ;
# else
unordered_map < ChunkPos , bool , ChunkPosKeyHash , ChunkPosKeyEq > MobSpawner : : chunksToPoll ;
# endif
const int MobSpawner : : tick ( ServerLevel * level , bool spawnEnemies , bool spawnFriendlies )
{
# ifndef _CONTENT_PACKAGE
#if 0
// PIX output for mob counters - generally disabling as Entity::countFlagsForPIX is reasonably expensive
if ( level - > dimension - > id = = 0 )
{
Entity : : countFlagsForPIX ( ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_WATERANIMAL , false ) , " eTYPE_WATERANIMAL " ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_ANIMALS_SPAWN_LIMIT_CHECK , false ) , " eTYPE_ANIMAL " ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_MONSTER , false ) , " eTYPE_MONSTER " ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_SQUID , true ) , " eTYPE_SQUID " ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_VILLAGER , true ) , " eTYPE_VILLAGER " ) ;
unsigned int totalCount [ 4 ] ;
unsigned int protectedCount [ 4 ] ;
unsigned int unprotectedCount [ 4 ] ;
unsigned int couldWanderCount [ 4 ] ;
totalCount [ 0 ] = level - > countInstanceOf ( eTYPE_COW , true , & protectedCount [ 0 ] , & couldWanderCount [ 0 ] ) ;
totalCount [ 1 ] = level - > countInstanceOf ( eTYPE_SHEEP , true , & protectedCount [ 1 ] , & couldWanderCount [ 1 ] ) ;
totalCount [ 2 ] = level - > countInstanceOf ( eTYPE_CHICKEN , true , & protectedCount [ 2 ] , & couldWanderCount [ 2 ] ) ;
totalCount [ 3 ] = level - > countInstanceOf ( eTYPE_PIG , true , & protectedCount [ 3 ] , & couldWanderCount [ 3 ] ) ;
for ( int i = 0 ; i < 4 ; i + + ) unprotectedCount [ i ] = totalCount [ i ] - protectedCount [ i ] ;
PIXAddNamedCounter ( unprotectedCount [ 0 ] , " eTYPE_COW (unprotected) " ) ;
PIXAddNamedCounter ( unprotectedCount [ 1 ] , " eTYPE_SHEEP (unprotected) " ) ;
PIXAddNamedCounter ( unprotectedCount [ 2 ] , " eTYPE_CHICKEN (unprotected) " ) ;
PIXAddNamedCounter ( unprotectedCount [ 3 ] , " eTYPE_PIG (unprotected) " ) ;
PIXAddNamedCounter ( protectedCount [ 0 ] , " eTYPE_COW (protected) " ) ;
PIXAddNamedCounter ( protectedCount [ 1 ] , " eTYPE_SHEEP (protected) " ) ;
PIXAddNamedCounter ( protectedCount [ 2 ] , " eTYPE_CHICKEN (protected) " ) ;
PIXAddNamedCounter ( protectedCount [ 3 ] , " eTYPE_PIG (protected) " ) ;
PIXAddNamedCounter ( couldWanderCount [ 0 ] , " eTYPE_COW (could wander) " ) ;
PIXAddNamedCounter ( couldWanderCount [ 1 ] , " eTYPE_SHEEP (could wander) " ) ;
PIXAddNamedCounter ( couldWanderCount [ 2 ] , " eTYPE_CHICKEN (could wander) " ) ;
PIXAddNamedCounter ( couldWanderCount [ 3 ] , " eTYPE_PIG (could wander) " ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_WOLF , true ) , " eTYPE_WOLF " ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_CREEPER , true ) , " eTYPE_CREEPER " ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_GIANT , true ) , " eTYPE_GIANT " ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_SKELETON , true ) , " eTYPE_SKELETON " ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_SPIDER , true ) , " eTYPE_SPIDER " ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_ZOMBIE , true ) , " eTYPE_ZOMBIE " ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_PIGZOMBIE , true ) , " eTYPE_PIGZOMBIE " ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_SLIME , true ) , " eTYPE_SLIME " ) ;
PIXAddNamedCounter ( level - > countInstanceOf ( eTYPE_GHAST , true ) , " eTYPE_GHAST " ) ;
}
# endif
# endif
if ( ! spawnEnemies & & ! spawnFriendlies )
{
return 0 ;
}
MemSect ( 20 ) ;
chunksToPoll . clear ( ) ;
2026-03-02 15:58:20 +07:00
2026-03-01 12:16:08 +08:00
#if 0
AUTO_VAR ( itEnd , level - > players . end ( ) ) ;
for ( AUTO_VAR ( it , level - > players . begin ( ) ) ; it ! = itEnd ; it + + )
{
2026-03-02 15:58:20 +07:00
std : : shared_ptr < Player > player = * it ; //level->players.at(i);
2026-03-01 12:16:08 +08:00
int xx = Mth : : floor ( player - > x / 16 ) ;
int zz = Mth : : floor ( player - > z / 16 ) ;
int r = 128 / 16 ;
for ( int x = - r ; x < = r ; x + + )
for ( int z = - r ; z < = r ; z + + )
{
chunksToPoll . insert ( ChunkPos ( x + xx , z + zz ) ) ;
}
}
# else
// 4J - rewritten to add chunks interleaved by player, and to add them from the centre outwards. We're going to be
// potentially adding less creatures than the original so that our count stays consistent with number of players added, so
// we want to make sure as best we can that the ones we do add are near the active players
int playerCount = ( int ) level - > players . size ( ) ;
int * xx = new int [ playerCount ] ;
int * zz = new int [ playerCount ] ;
for ( int i = 0 ; i < playerCount ; i + + )
{
2026-03-02 15:58:20 +07:00
std : : shared_ptr < Player > player = level - > players [ i ] ;
2026-03-01 12:16:08 +08:00
xx [ i ] = Mth : : floor ( player - > x / 16 ) ;
zz [ i ] = Mth : : floor ( player - > z / 16 ) ;
# ifdef __PSVITA__
chunksToPoll . insert ( ChunkPos ( xx [ i ] , zz [ i ] ) , false ) ;
# else
chunksToPoll . insert ( std : : pair < ChunkPos , bool > ( ChunkPos ( xx [ i ] , zz [ i ] ) , false ) ) ;
# endif
}
for ( int r = 1 ; r < = 8 ; r + + )
{
for ( int l = 0 ; l < ( r * 2 ) ; l + + )
{
for ( int i = 0 ; i < playerCount ; i + + )
{
bool edgeChunk = ( r = = 8 ) ;
// If this chunk isn't at the edge of the region for this player, then always store with a flag of false
// so that if it was at the edge of another player, then this will remove that
if ( ! edgeChunk )
{
# ifdef __PSVITA__
chunksToPoll . insert ( ChunkPos ( ( xx [ i ] - r ) + l , ( zz [ i ] - r ) ) , false ) ;
chunksToPoll . insert ( ChunkPos ( ( xx [ i ] + r ) , ( zz [ i ] - r ) + l ) , false ) ;
chunksToPoll . insert ( ChunkPos ( ( xx [ i ] + r ) - l , ( zz [ i ] + r ) ) , false ) ;
chunksToPoll . insert ( ChunkPos ( ( xx [ i ] - r ) , ( zz [ i ] + r ) - l ) , false ) ;
# else
chunksToPoll . insert ( std : : pair < ChunkPos , bool > ( ChunkPos ( ( xx [ i ] - r ) + l , ( zz [ i ] - r ) ) , false ) ) ;
chunksToPoll . insert ( std : : pair < ChunkPos , bool > ( ChunkPos ( ( xx [ i ] + r ) , ( zz [ i ] - r ) + l ) , false ) ) ;
chunksToPoll . insert ( std : : pair < ChunkPos , bool > ( ChunkPos ( ( xx [ i ] + r ) - l , ( zz [ i ] + r ) ) , false ) ) ;
chunksToPoll . insert ( std : : pair < ChunkPos , bool > ( ChunkPos ( ( xx [ i ] - r ) , ( zz [ i ] + r ) - l ) , false ) ) ;
# endif
}
else
{
# ifdef __PSVITA__
ChunkPos cp = ChunkPos ( ( xx [ i ] - r ) + l , ( zz [ i ] - r ) ) ;
if ( chunksToPoll . find ( cp ) ) chunksToPoll . insert ( cp , true ) ;
2026-03-02 15:58:20 +07:00
cp = ChunkPos ( ( xx [ i ] + r ) , ( zz [ i ] - r ) + l ) ;
2026-03-01 12:16:08 +08:00
if ( chunksToPoll . find ( cp ) ) chunksToPoll . insert ( cp , true ) ;
2026-03-02 15:58:20 +07:00
cp = ChunkPos ( ( xx [ i ] + r ) - l , ( zz [ i ] + r ) ) ;
2026-03-01 12:16:08 +08:00
if ( chunksToPoll . find ( cp ) ) chunksToPoll . insert ( cp , true ) ;
2026-03-02 15:58:20 +07:00
cp = ChunkPos ( ( xx [ i ] - r ) , ( zz [ i ] + r ) - l ) ;
2026-03-01 12:16:08 +08:00
if ( chunksToPoll . find ( cp ) ) chunksToPoll . insert ( cp , true ) ;
# else
ChunkPos cp = ChunkPos ( ( xx [ i ] - r ) + l , ( zz [ i ] - r ) ) ;
if ( chunksToPoll . find ( cp ) = = chunksToPoll . end ( ) ) chunksToPoll . insert ( std : : pair < ChunkPos , bool > ( cp , true ) ) ;
2026-03-02 15:58:20 +07:00
cp = ChunkPos ( ( xx [ i ] + r ) , ( zz [ i ] - r ) + l ) ;
2026-03-01 12:16:08 +08:00
if ( chunksToPoll . find ( cp ) = = chunksToPoll . end ( ) ) chunksToPoll . insert ( std : : pair < ChunkPos , bool > ( cp , true ) ) ;
2026-03-02 15:58:20 +07:00
cp = ChunkPos ( ( xx [ i ] + r ) - l , ( zz [ i ] + r ) ) ;
2026-03-01 12:16:08 +08:00
if ( chunksToPoll . find ( cp ) = = chunksToPoll . end ( ) ) chunksToPoll . insert ( std : : pair < ChunkPos , bool > ( cp , true ) ) ;
2026-03-02 15:58:20 +07:00
cp = ChunkPos ( ( xx [ i ] - r ) , ( zz [ i ] + r ) - l ) ;
2026-03-01 12:16:08 +08:00
if ( chunksToPoll . find ( cp ) = = chunksToPoll . end ( ) ) chunksToPoll . insert ( std : : pair < ChunkPos , bool > ( cp , true ) ) ;
# endif
}
}
}
}
delete [ ] xx ;
delete [ ] zz ;
# endif
MemSect ( 0 ) ;
int count = 0 ;
MemSect ( 31 ) ;
Pos * spawnPos = level - > getSharedSpawnPos ( ) ;
MemSect ( 0 ) ;
2026-03-02 15:58:20 +07:00
2026-03-01 12:16:08 +08:00
for ( unsigned int i = 0 ; i < MobCategory : : values . length ; i + + )
{
MobCategory * mobCategory = MobCategory : : values [ i ] ;
if ( ( mobCategory - > isFriendly ( ) & & ! spawnFriendlies ) | | ( ! mobCategory - > isFriendly ( ) & & ! spawnEnemies ) )
{
continue ;
}
// 4J - this is now quite different to the java version. We just have global max counts for the level whereas the original has a max per chunk that
// scales with the number of chunks to be polled.
int categoryCount = level - > countInstanceOf ( mobCategory - > getEnumBaseClass ( ) , mobCategory - > isSingleType ( ) ) ;
if ( categoryCount > = mobCategory - > getMaxInstancesPerLevel ( ) )
{
continue ;
}
# ifdef __PSVITA__
for ( int i = 0 ; i < chunksToPoll . end ( ) ; i + = 1 )
{
SCustomMapNode * it = chunksToPoll . get ( i ) ;
# else
AUTO_VAR ( itEndCTP , chunksToPoll . end ( ) ) ;
for ( AUTO_VAR ( it , chunksToPoll . begin ( ) ) ; it ! = itEndCTP ; it + + )
{
# endif
if ( it - > second )
{
// don't add mobs to edge chunks, to prevent adding mobs
// "outside" of the active playground
continue ;
}
ChunkPos * cp = ( ChunkPos * ) ( & it - > first ) ;
// 4J - don't let this actually create/load a chunk that isn't here already - we'll let the normal updateDirtyChunks etc. processes do that, so it can happen on another thread
if ( ! level - > hasChunk ( cp - > x , cp - > z ) ) continue ;
TilePos start = getRandomPosWithin ( level , cp - > x , cp - > z ) ;
int xStart = start . x ;
int yStart = start . y ;
int zStart = start . z ;
if ( level - > isSolidBlockingTile ( xStart , yStart , zStart ) ) continue ;
if ( level - > getMaterial ( xStart , yStart , zStart ) ! = mobCategory - > getSpawnPositionMaterial ( ) ) continue ;
int clusterSize = 0 ;
for ( int dd = 0 ; dd < 3 ; dd + + )
{
int x = xStart ;
int y = yStart ;
int z = zStart ;
int ss = 6 ;
Biome : : MobSpawnerData * currentMobType = NULL ;
for ( int ll = 0 ; ll < 4 ; ll + + )
{
x + = level - > random - > nextInt ( ss ) - level - > random - > nextInt ( ss ) ;
y + = level - > random - > nextInt ( 1 ) - level - > random - > nextInt ( 1 ) ;
z + = level - > random - > nextInt ( ss ) - level - > random - > nextInt ( ss ) ;
// int y = heightMap[x + z * w] + 1;
// 4J - don't let this actually create/load a chunk that isn't here already - we'll let the normal updateDirtyChunks etc. processes do that, so it can happen on another thread
if ( ! level - > hasChunkAt ( x , y , z ) ) continue ;
if ( isSpawnPositionOk ( mobCategory , level , x , y , z ) )
{
float xx = x + 0.5f ;
float yy = ( float ) y ;
float zz = z + 0.5f ;
if ( level - > getNearestPlayer ( xx , yy , zz , MIN_SPAWN_DISTANCE ) ! = NULL )
{
continue ;
}
else
{
float xd = xx - spawnPos - > x ;
float yd = yy - spawnPos - > y ;
float zd = zz - spawnPos - > z ;
float sd = xd * xd + yd * yd + zd * zd ;
if ( sd < MIN_SPAWN_DISTANCE * MIN_SPAWN_DISTANCE )
{
continue ;
}
}
if ( currentMobType = = NULL )
{
currentMobType = level - > getRandomMobSpawnAt ( mobCategory , x , y , z ) ;
if ( currentMobType = = NULL )
{
break ;
}
}
2026-03-02 15:58:20 +07:00
std : : shared_ptr < Mob > mob ;
2026-03-01 12:16:08 +08:00
// 4J - removed try/catch
// try
// {
MemSect ( 29 ) ;
//mob = type.mobClass.getConstructor(Level.class).newInstance(level);
mob = dynamic_pointer_cast < Mob > ( EntityIO : : newByEnumType ( currentMobType - > mobClass , level ) ) ;
MemSect ( 0 ) ;
// }
// catch (exception e)
// {
// // TODO 4J We can't print a stack trace, and the newInstance function doesn't throw an exception just now anyway
// //e.printStackTrace();
// return count;
// }
// 4J - If it is an animal or a monster, don't let any one type of mob represent more than 50% of the total amount of these things. This
// was added initially to stop flat lands being totally populated with slimes but seems like a generally good rule.
eINSTANCEOF mobType = mob - > GetType ( ) ;
if ( ( mobType & eTYPE_ANIMALS_SPAWN_LIMIT_CHECK ) | | ( mobType & eTYPE_MONSTER ) )
{
// even more special rule for ghasts, because filling up the nether with 25 of them is a bit unpleasant. In the java version they are
// only limited by the fact that the world fills up with pig zombies (the only other type of enemy mob in the nether) before them - they
// aren't actually even counted properly themselves
if ( mobType = = eTYPE_GHAST )
{
if ( level - > countInstanceOf ( mobType , true ) > = 4 ) continue ;
}
else if ( mobType = = eTYPE_ENDERMAN & & level - > dimension - > id = = 1 )
{
// Special rule for the end, as we only have Endermen (plus the dragon). Increase the spawnable counts based on level difficulty
int maxEndermen = mobCategory - > getMaxInstancesPerLevel ( ) ;
if ( level - > difficulty = = Difficulty : : NORMAL )
{
maxEndermen - = mobCategory - > getMaxInstancesPerLevel ( ) / 4 ;
}
else if ( level - > difficulty < = Difficulty : : EASY )
{
maxEndermen - = mobCategory - > getMaxInstancesPerLevel ( ) / 2 ;
}
if ( level - > countInstanceOf ( mobType , true ) > = maxEndermen ) continue ;
}
else if ( level - > countInstanceOf ( mobType , true ) > = ( mobCategory - > getMaxInstancesPerLevel ( ) / 2 ) ) continue ;
}
mob - > moveTo ( xx , yy , zz , level - > random - > nextFloat ( ) * 360 , 0 ) ;
if ( mob - > canSpawn ( ) )
{
// 4J - check if we are going to despawn straight away too, and don't add if we will - otherwise we'll be sending
// network packets for adding & removal that we don't need
mob - > checkDespawn ( ) ;
if ( ! mob - > removed )
{
clusterSize + + ;
categoryCount + + ;
mob - > setDespawnProtected ( ) ; // 4J added - default to protected against despawning
level - > addEntity ( mob ) ;
finalizeMobSettings ( mob , level , xx , yy , zz ) ;
mob - > finalizeMobSpawn ( ) ;
// 4J - change here so that we can't ever make more than the desired amount of entities in each priority. In the original java version
// depending on the random spawn positions being considered the only limit as to the number of entities created per category is the number
// of chunks to poll.
if ( categoryCount > = mobCategory - > getMaxInstancesPerLevel ( ) ) goto categoryLoop ;
if ( clusterSize > = mob - > getMaxSpawnClusterSize ( ) ) goto chunkLoop ;
}
}
count + = clusterSize ;
}
}
}
chunkLoop : continue ;
}
categoryLoop : continue ;
}
delete spawnPos ;
return count ;
}
bool MobSpawner : : isSpawnPositionOk ( MobCategory * category , Level * level , int x , int y , int z )
{
// 4J - don't let this actually create/load a chunk that isn't here already - we'll let the normal updateDirtyChunks etc. processes do that, so it can happen on another thread
if ( ! level - > hasChunkAt ( x , y , z ) ) return false ;
# ifdef __PSVITA__
// AP - added this for Vita. Make sure a new spawn point has 2 chunks around it. This will make sure monsters don't keep getting spawned on the edge preventing other new monsters
// from being spawned
int r = 32 ;
if ( ! level - > hasChunksAt ( x - r , 0 , z - r , x + r , 0 , z + r ) )
{
return false ;
}
# endif
if ( category - > getSpawnPositionMaterial ( ) = = Material : : water )
{
// 4J - changed to spawn water things only in deep water
int yo = 0 ;
int liquidCount = 0 ;
2026-03-02 15:58:20 +07:00
2026-03-01 12:16:08 +08:00
while ( ( y - yo ) > = 0 & & ( yo < 5 ) )
{
if ( level - > getMaterial ( x , y - yo , z ) - > isLiquid ( ) ) liquidCount + + ;
yo + + ;
}
// 4J - Sometimes deep water could be just a waterfall, so check that it's wide as well
bool inEnoughWater = false ;
if ( liquidCount = = 5 )
{
if ( level - > getMaterial ( x + 5 , y , z ) - > isLiquid ( ) & &
level - > getMaterial ( x - 5 , y , z ) - > isLiquid ( ) & &
level - > getMaterial ( x , y , z + 5 ) - > isLiquid ( ) & &
level - > getMaterial ( x , y , z - 5 ) - > isLiquid ( )
)
{
inEnoughWater = true ;
}
}
return inEnoughWater & & ! level - > isSolidBlockingTile ( x , y + 1 , z ) ;
}
else
{
if ( ! level - > isTopSolidBlocking ( x , y - 1 , z ) ) return false ;
int tt = level - > getTile ( x , y - 1 , z ) ;
return tt ! = Tile : : unbreakable_Id & & ! level - > isSolidBlockingTile ( x , y , z ) & & ! level - > getMaterial ( x , y , z ) - > isLiquid ( ) & & ! level - > isSolidBlockingTile ( x , y + 1 , z ) ;
}
}
2026-03-02 15:58:20 +07:00
void MobSpawner : : finalizeMobSettings ( std : : shared_ptr < Mob > mob , Level * level , float xx , float yy , float zz )
2026-03-01 12:16:08 +08:00
{
if ( dynamic_pointer_cast < Spider > ( mob ) ! = NULL & & level - > random - > nextInt ( 100 ) = = 0 )
{
2026-03-02 15:58:20 +07:00
std : : shared_ptr < Skeleton > skeleton = std : : shared_ptr < Skeleton > ( new Skeleton ( level ) ) ;
2026-03-01 12:16:08 +08:00
skeleton - > moveTo ( xx , yy , zz , mob - > yRot , 0 ) ;
level - > addEntity ( skeleton ) ;
skeleton - > ride ( mob ) ;
}
else if ( dynamic_pointer_cast < Sheep > ( mob ) ! = NULL )
{
( dynamic_pointer_cast < Sheep > ( mob ) ) - > setColor ( Sheep : : getSheepColor ( level - > random ) ) ;
}
else if ( dynamic_pointer_cast < Ozelot > ( mob ) ! = NULL )
{
if ( level - > random - > nextInt ( 7 ) = = 0 )
{
for ( int kitten = 0 ; kitten < 2 ; kitten + + )
{
2026-03-02 15:58:20 +07:00
std : : shared_ptr < Ozelot > ozelot = std : : shared_ptr < Ozelot > ( new Ozelot ( level ) ) ;
2026-03-01 12:16:08 +08:00
ozelot - > moveTo ( xx , yy , zz , mob - > yRot , 0 ) ;
ozelot - > setAge ( - 20 * 60 * 20 ) ;
level - > addEntity ( ozelot ) ;
}
}
}
}
// 4J Stu TODO This was an array of Class type. I haven't made a base Class type yet, but don't need to
// as this can be an array of Mob type?
eINSTANCEOF MobSpawner : : bedEnemies [ bedEnemyCount ] = {
eTYPE_SPIDER , eTYPE_ZOMBIE , eTYPE_SKELETON
} ;
2026-03-02 15:58:20 +07:00
bool MobSpawner : : attackSleepingPlayers ( Level * level , vector < std : : shared_ptr < Player > > * players )
2026-03-01 12:16:08 +08:00
{
bool somebodyWokeUp = false ;
PathFinder finder = PathFinder ( level , true , false , false , true ) ;
AUTO_VAR ( itEnd , players - > end ( ) ) ;
for ( AUTO_VAR ( it , players - > begin ( ) ) ; it ! = itEnd ; it + + )
{
2026-03-02 15:58:20 +07:00
std : : shared_ptr < Player > player = ( * it ) ;
2026-03-01 12:16:08 +08:00
bool nextPlayer = false ;
for ( int attemptCount = 0 ; attemptCount < 20 & & ! nextPlayer ; attemptCount + + ) \
{
// limit position within the range of the player
int x = Mth : : floor ( player - > x ) + level - > random - > nextInt ( 32 ) - level - > random - > nextInt ( 32 ) ;
int z = Mth : : floor ( player - > z ) + level - > random - > nextInt ( 32 ) - level - > random - > nextInt ( 32 ) ;
int yStart = Mth : : floor ( player - > y ) + level - > random - > nextInt ( 16 ) - level - > random - > nextInt ( 16 ) ;
if ( yStart < 1 )
{
yStart = 1 ;
}
else if ( yStart > Level : : maxBuildHeight )
{
yStart = Level : : maxBuildHeight ;
}
{
int type = level - > random - > nextInt ( bedEnemyCount ) ;
int y = yStart ;
while ( y > 2 & & ! level - > isTopSolidBlocking ( x , y - 1 , z ) )
{
y - - ;
}
while ( ! isSpawnPositionOk ( ( MobCategory * ) MobCategory : : monster , level , x , y , z ) & & y < ( yStart + 16 ) & & y < Level : : maxBuildHeight )
{
y + + ;
}
if ( y > = ( yStart + 16 ) | | y > = Level : : maxBuildHeight )
{
y = yStart ;
continue ;
}
else
{
float xx = x + 0.5f ;
float yy = ( float ) y ;
float zz = z + 0.5f ;
2026-03-02 15:58:20 +07:00
std : : shared_ptr < Mob > mob ;
2026-03-01 12:16:08 +08:00
// 4J - removed try/catch
// try
// {
//mob = classes[type].getConstructor(Level.class).newInstance(level);
// 4J - there was a classes array here which duplicated the bedEnemies array but have removed it
mob = dynamic_pointer_cast < Mob > ( EntityIO : : newByEnumType ( bedEnemies [ type ] , level ) ) ;
// }
// catch (exception e)
// {
// // TODO 4J Stu - We can't print a stack trace, and newInstance doesn't currently throw an exception anyway
// //e.printStackTrace();
// return somebodyWokeUp;
// }
// System.out.println("Placing night mob");
mob - > moveTo ( xx , yy , zz , level - > random - > nextFloat ( ) * 360 , 0 ) ;
// check if the mob can spawn at this location
if ( ! mob - > canSpawn ( ) )
{
continue ;
}
Pos * bedPos = BedTile : : findStandUpPosition ( level , Mth : : floor ( player - > x ) , Mth : : floor ( player - > y ) , Mth : : floor ( player - > z ) , 1 ) ;
if ( bedPos = = NULL )
{
// an unlikely case where the bed is
// completely blocked
bedPos = new Pos ( x , y + 1 , z ) ;
}
// 4J Stu - TU-1 hotfix
// Fix for #13152 - If the player sleeps in a bed next to a wall in an enclosed, well lit area they will be awoken by a monster
// The pathfinder should attempt to get close to the position that we will move the mob to,
// instead of the player who could be next to a wall. Otherwise the paths gets to the other
// side of the the wall, then moves the mob inside the building
//Path *findPath = finder.findPath(mob.get(), player.get(), 32.0f);
Path * findPath = finder . findPath ( mob . get ( ) , bedPos - > x , bedPos - > y , bedPos - > z , 32.0f ) ;
if ( findPath ! = NULL & & findPath - > getSize ( ) > 1 )
{
Node * last = findPath - > last ( ) ;
if ( abs ( last - > x - bedPos - > x ) < 1.5 & & abs ( last - > z - bedPos - > z ) < 1.5 & & abs ( last - > y - bedPos - > y ) < 1.5 )
{
// System.out.println("Found path!");
mob - > moveTo ( bedPos - > x + 0.5f , bedPos - > y , bedPos - > z + 0.5f , 0 , 0 ) ;
// the mob would maybe not be able to
// spawn here, but we ignore that now (we assume
// it walked here)
{
level - > addEntity ( mob ) ;
finalizeMobSettings ( mob , level , bedPos - > x + 0.5f , ( float ) bedPos - > y , bedPos - > z + 0.5f ) ;
mob - > finalizeMobSpawn ( ) ;
player - > stopSleepInBed ( true , false , false ) ;
// play a sound effect to scare the player
mob - > playAmbientSound ( ) ;
somebodyWokeUp = true ;
nextPlayer = true ;
}
}
delete findPath ;
}
delete bedPos ;
}
}
}
}
return somebodyWokeUp ;
}
void MobSpawner : : postProcessSpawnMobs ( Level * level , Biome * biome , int xo , int zo , int cellWidth , int cellHeight , Random * random )
{
// 4J - not for our version. Creates a few too many mobs.
#if 0
vector < Biome : : MobSpawnerData * > * mobs = biome - > getMobs ( MobCategory : : creature ) ;
if ( mobs - > empty ( ) )
{
return ;
}
while ( random - > nextFloat ( ) < biome - > getCreatureProbability ( ) )
{
Biome : : MobSpawnerData * type = ( Biome : : MobSpawnerData * ) WeighedRandom : : getRandomItem ( level - > random , ( ( vector < WeighedRandomItem * > * ) mobs ) ) ;
int count = type - > minCount + random - > nextInt ( 1 + type - > maxCount - type - > minCount ) ;
int x = xo + random - > nextInt ( cellWidth ) ;
int z = zo + random - > nextInt ( cellHeight ) ;
int startX = x , startZ = z ;
for ( int c = 0 ; c < count ; c + + )
{
bool success = false ;
for ( int attempts = 0 ; ! success & & attempts < 4 ; attempts + + )
{
// these mobs always spawn at the topmost position
int y = level - > getTopSolidBlock ( x , z ) ;
if ( isSpawnPositionOk ( MobCategory : : creature , level , x , y , z ) )
{
float xx = x + 0.5f ;
float yy = ( float ) y ;
float zz = z + 0.5f ;
2026-03-02 15:58:20 +07:00
std : : shared_ptr < Mob > mob ;
2026-03-01 12:16:08 +08:00
//try {
mob = dynamic_pointer_cast < Mob > ( EntityIO : : newByEnumType ( type - > mobClass , level ) ) ;
//} catch (Exception e) {
// e.printStackTrace();
// continue;
//}
// System.out.println("Placing night mob");
mob - > moveTo ( xx , yy , zz , random - > nextFloat ( ) * 360 , 0 ) ;
mob - > setDespawnProtected ( ) ;
level - > addEntity ( mob ) ;
finalizeMobSettings ( mob , level , xx , yy , zz ) ;
success = true ;
}
x + = random - > nextInt ( 5 ) - random - > nextInt ( 5 ) ;
z + = random - > nextInt ( 5 ) - random - > nextInt ( 5 ) ;
while ( x < xo | | x > = ( xo + cellWidth ) | | z < zo | | z > = ( zo + cellWidth ) )
{
x = startX + random - > nextInt ( 5 ) - random - > nextInt ( 5 ) ;
z = startZ + random - > nextInt ( 5 ) - random - > nextInt ( 5 ) ;
}
}
}
}
# endif
}