From b691c43c44ff180d10e7d4a9afc83b98551ff586 Mon Sep 17 00:00:00 2001 From: daoge_cmd <3523206925@qq.com> Date: Sun, 1 Mar 2026 12:16:08 +0800 Subject: Initial commit --- Minecraft.World/MobSpawner.cpp | 651 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 651 insertions(+) create mode 100644 Minecraft.World/MobSpawner.cpp (limited to 'Minecraft.World/MobSpawner.cpp') 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 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 = *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 = 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(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( ( xx[i] - r ) + l , ( zz[i] - r ) ), false)); + chunksToPoll.insert(std::pair(ChunkPos( ( xx[i] + r ) , ( zz[i] - r ) + l ), false)); + chunksToPoll.insert(std::pair(ChunkPos( ( xx[i] + r ) - l , ( zz[i] + r ) ), false)); + chunksToPoll.insert(std::pair(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(cp, true)); + cp = ChunkPos( ( xx[i] + r ), ( zz[i] - r ) + l ); + if( chunksToPoll.find( cp ) == chunksToPoll.end() ) chunksToPoll.insert(std::pair(cp, true)); + cp = ChunkPos( ( xx[i] + r ) - l , ( zz[i] + r )); + if( chunksToPoll.find( cp ) == chunksToPoll.end() ) chunksToPoll.insert(std::pair(cp, true)); + cp = ChunkPos( ( xx[i] - r ), ( zz[i] + r ) - l); + if( chunksToPoll.find( cp ) == chunksToPoll.end() ) chunksToPoll.insert(std::pair(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; + // 4J - removed try/catch +// try +// { + MemSect(29); + //mob = type.mobClass.getConstructor(Level.class).newInstance(level); + mob = dynamic_pointer_cast(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, Level *level, float xx, float yy, float zz) +{ + if (dynamic_pointer_cast( mob ) != NULL && level->random->nextInt(100) == 0) + { + shared_ptr skeleton = shared_ptr( new Skeleton(level) ); + skeleton->moveTo(xx, yy, zz, mob->yRot, 0); + level->addEntity(skeleton); + skeleton->ride(mob); + } + else if (dynamic_pointer_cast( mob ) != NULL) + { + (dynamic_pointer_cast( mob ))->setColor(Sheep::getSheepColor(level->random)); + } + else if (dynamic_pointer_cast( mob ) != NULL) + { + if (level->random->nextInt(7) == 0) + { + for (int kitten = 0; kitten < 2; kitten++) + { + shared_ptr ozelot = shared_ptr(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 > *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 = (*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; +// 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(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 *mobs = biome->getMobs(MobCategory::creature); + if (mobs->empty()) + { + return; + } + + while (random->nextFloat() < biome->getCreatureProbability()) + { + Biome::MobSpawnerData *type = (Biome::MobSpawnerData *) WeighedRandom::getRandomItem(level->random, ((vector *)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; + //try { + mob = dynamic_pointer_cast( 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 +} -- cgit v1.2.3