aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.World/MobSpawner.cpp
diff options
context:
space:
mode:
authordaoge_cmd <3523206925@qq.com>2026-03-01 12:16:08 +0800
committerdaoge_cmd <3523206925@qq.com>2026-03-01 12:16:08 +0800
commitb691c43c44ff180d10e7d4a9afc83b98551ff586 (patch)
tree3e9849222cbc6ba49f2f1fc6e5fe7179632c7390 /Minecraft.World/MobSpawner.cpp
parentdef8cb415354ac390b7e89052a50605285f1aca9 (diff)
Initial commit
Diffstat (limited to 'Minecraft.World/MobSpawner.cpp')
-rw-r--r--Minecraft.World/MobSpawner.cpp651
1 files changed, 651 insertions, 0 deletions
diff --git a/Minecraft.World/MobSpawner.cpp b/Minecraft.World/MobSpawner.cpp
new file mode 100644
index 00000000..d2b4f6fd
--- /dev/null
+++ b/Minecraft.World/MobSpawner.cpp
@@ -0,0 +1,651 @@
+#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();
+
+#if 0
+ AUTO_VAR(itEnd, level->players.end());
+ for (AUTO_VAR(it, level->players.begin()); it != itEnd; it++)
+ {
+ shared_ptr<Player> player = *it; //level->players.at(i);
+ 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++)
+ {
+ shared_ptr<Player> player = level->players[i];
+ 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);
+ cp = ChunkPos( ( xx[i] + r ), ( zz[i] - r ) + l );
+ if( chunksToPoll.find( cp ) ) chunksToPoll.insert(cp, true);
+ cp = ChunkPos( ( xx[i] + r ) - l , ( zz[i] + r ));
+ if( chunksToPoll.find( cp ) ) chunksToPoll.insert(cp, true);
+ cp = ChunkPos( ( xx[i] - r ), ( zz[i] + r ) - l);
+ 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));
+ cp = ChunkPos( ( xx[i] + r ), ( zz[i] - r ) + l );
+ if( chunksToPoll.find( cp ) == chunksToPoll.end() ) chunksToPoll.insert(std::pair<ChunkPos,bool>(cp, true));
+ cp = ChunkPos( ( xx[i] + r ) - l , ( zz[i] + r ));
+ if( chunksToPoll.find( cp ) == chunksToPoll.end() ) chunksToPoll.insert(std::pair<ChunkPos,bool>(cp, true));
+ cp = ChunkPos( ( xx[i] - r ), ( zz[i] + r ) - l);
+ 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);
+
+ 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;
+ }
+ }
+
+ shared_ptr<Mob> mob;
+ // 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;
+
+ 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);
+ }
+}
+
+
+void MobSpawner::finalizeMobSettings(shared_ptr<Mob> mob, Level *level, float xx, float yy, float zz)
+{
+ if (dynamic_pointer_cast<Spider>( mob ) != NULL && level->random->nextInt(100) == 0)
+ {
+ shared_ptr<Skeleton> skeleton = shared_ptr<Skeleton>( new Skeleton(level) );
+ 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++)
+ {
+ shared_ptr<Ozelot> ozelot = shared_ptr<Ozelot>(new Ozelot(level));
+ 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
+};
+
+
+bool MobSpawner::attackSleepingPlayers(Level *level, vector<shared_ptr<Player> > *players)
+{
+
+ 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++)
+ {
+ shared_ptr<Player> player = (*it);
+
+ 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;
+
+ shared_ptr<Mob> mob;
+// 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;
+
+ shared_ptr<Mob> mob;
+ //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
+}