#include "../Platform/stdafx.h" #include "../Headers/net.minecraft.h" #include "../Headers/net.minecraft.world.entity.h" #include "../Headers/net.minecraft.world.entity.animal.h" #include "../Headers/net.minecraft.world.entity.monster.h" #include "../Headers/net.minecraft.world.entity.player.h" #include "../Headers/net.minecraft.world.level.h" #include "../Headers/net.minecraft.world.level.biome.h" #include "../Headers/net.minecraft.world.level.material.h" #include "../Headers/net.minecraft.world.level.pathfinder.h" #include "../Headers/net.minecraft.world.level.tile.h" #include "../Util/Difficulty.h" #include "../Util/WeighedRandom.h" #include "../Level/Level.h" #include "../Level/ChunkPos.h" #include "../Level/TilePos.h" #include "../../Minecraft.Client/Level/ServerLevel.h" #include "MobSpawner.h" #include "../Level/Dimensions/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); } std::unordered_map MobSpawner::chunksToPoll; const int MobSpawner::tick(ServerLevel* level, bool spawnEnemies, bool spawnFriendlies, bool spawnPersistent) { #if !defined(_CONTENT_PACKAGE) #endif if (!spawnEnemies && !spawnFriendlies && !spawnPersistent) { return 0; } MemSect(20); chunksToPoll.clear(); // 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++) { std::shared_ptr player = level->players[i]; xx[i] = Mth::floor(player->x / 16); zz[i] = Mth::floor(player->z / 16); chunksToPoll.insert( std::pair(ChunkPos(xx[i], zz[i]), false)); } 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) { 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)); } 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)); } } } } delete[] xx; delete[] zz; 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) || (mobCategory->isPersistent() && !spawnPersistent)) { continue; } // 4J - early out for non-main dimensions, if spawning anything friendly if (mobCategory->isFriendly()) { if (level->dimension->id != 0) { 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; } auto itEndCTP = chunksToPoll.end(); for (auto it = chunksToPoll.begin(); it != itEndCTP; it++) { 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 = nullptr; MobGroupData* groupData = nullptr; 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) != nullptr) { 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 == nullptr) { currentMobType = level->getRandomMobSpawnAt( mobCategory, x, y, z); if (currentMobType == nullptr) { break; } } std::shared_ptr mob; // 4J - removed try/catch // try // { MemSect(29); // mob = // type.mobClass.getConstructor(Level.class).newInstance(level); mob = std::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); groupData = mob->finalizeMobSpawn(groupData); // 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; 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::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. }