285 lines
10 KiB
C++
Executable File
285 lines
10 KiB
C++
Executable File
#include "MobSpawner.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "Level.h"
|
|
#include "biome/Biome.h"
|
|
#include "material/Material.h"
|
|
#include "../entity/EntityTypes.h"
|
|
#include "../entity/MobFactory.h"
|
|
#include "../entity/MobCategory.h"
|
|
#include "../entity/player/Player.h"
|
|
|
|
//#include "../entity/animal/Sheep.h"
|
|
//#include "tile/BedTile.h"
|
|
|
|
std::map<ChunkPos, bool> MobSpawner::chunksToPoll;
|
|
|
|
static int _bedEnemies[] = {
|
|
MobTypes::Spider,
|
|
MobTypes::Zombie,
|
|
MobTypes::Skeleton,
|
|
MobTypes::PigZombie
|
|
};
|
|
|
|
static const std::vector<int> bedEnemies(_bedEnemies, _bedEnemies + sizeof(_bedEnemies) / sizeof(_bedEnemies[0]));
|
|
|
|
/*static*/
|
|
int MobSpawner::tick(Level* level, bool spawnEnemies, bool spawnFriendlies) {
|
|
|
|
//return 0;
|
|
|
|
if (!spawnEnemies && !spawnFriendlies) {
|
|
return 0;
|
|
}
|
|
|
|
chunksToPoll.clear();
|
|
// Add all chunks as a quick-and-dirty test
|
|
// (The code above is the same as in the java version)
|
|
// This code goes over the whole map one "row" at a time
|
|
|
|
// Spawn friendlies == loop over whole map, and disable Monster spawning this tick
|
|
if (spawnFriendlies) {
|
|
spawnEnemies = false;
|
|
for (int i = 0; i < 256; ++i)
|
|
chunksToPoll.insert( std::make_pair( ChunkPos(i>>4, i&15), false) );
|
|
|
|
} else {
|
|
// Only spawn mobs, check around one player per tick (@todo: optimize the "count instances of"?)
|
|
static unsigned int _pid = 0;
|
|
if (++_pid >= level->players.size()) _pid = 0;
|
|
if (level->players.size()) {
|
|
Player* p = level->players[_pid];
|
|
int xx = Mth::floor(p->x / 16);
|
|
int zz = Mth::floor(p->z / 16);
|
|
int r = 128 / 16;
|
|
for (int x = -r; x <= r; x++)
|
|
for (int z = -r; z <= r; z++) {
|
|
const int cx = xx + x;
|
|
const int cz = zz + z;
|
|
if (cx >= 0 && cx < 16 && cz >= 0 && cz < 16)
|
|
chunksToPoll.insert(std::make_pair(ChunkPos(cx, cz), false ));
|
|
}
|
|
}
|
|
}
|
|
|
|
int count = 0;
|
|
Pos spawnPos = level->getSharedSpawnPos();
|
|
|
|
for (int i = 0; i < MobCategory::numValues; ++i) {
|
|
const MobCategory& mobCategory = *MobCategory::values[i];
|
|
|
|
if ((mobCategory.isFriendly() && !spawnFriendlies) || (!mobCategory.isFriendly() && !spawnEnemies))
|
|
continue;
|
|
|
|
int numMobs = level->countInstanceOfBaseType(mobCategory.getBaseClassId());
|
|
if (numMobs > mobCategory.getMaxInstancesPerLevel())
|
|
continue;
|
|
//LOGI("NumMobs: %d of Category: %d\n", numMobs, mobCategory.getBaseClassId());
|
|
chunkLoop:
|
|
for(std::map<ChunkPos, bool>::iterator it = chunksToPoll.begin(); it != chunksToPoll.end(); ++it) {
|
|
const ChunkPos& cp = it->first;
|
|
TilePos start = getRandomPosWithin(level, cp.x * 16, cp.z * 16);
|
|
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;
|
|
int maxCreatureCount = 999;
|
|
int currentCreatureCount = 0;
|
|
|
|
for (int ll = 0; ll < 4; ll++) {
|
|
if (currentCreatureCount > maxCreatureCount)
|
|
break;
|
|
|
|
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;
|
|
|
|
if (isSpawnPositionOk(mobCategory, level, x, y, z)) {
|
|
float xx = (float)x + 0.5f;
|
|
float yy = (float)y;
|
|
float zz = (float)z + 0.5f;
|
|
if (level->getNearestPlayer(xx, yy, zz, (float)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;
|
|
}
|
|
}
|
|
|
|
static Stopwatch sw;
|
|
sw.start();
|
|
|
|
if (!currentMobType.isValid()) {
|
|
currentMobType = level->getRandomMobSpawnAt(mobCategory, x, y, z);
|
|
if (!currentMobType.isValid())
|
|
break;
|
|
|
|
// Don't allow monster to spawn (much) more than their defined
|
|
// probability weight.
|
|
if (&mobCategory == &MobCategory::monster) {
|
|
int typeCount = level->countInstanceOfType(currentMobType.mobClassId);
|
|
int typeMax = (int)(1.5f * currentMobType.randomWeight * mobCategory.getMaxInstancesPerLevel()) / Biome::defaultTotalEnemyWeight;
|
|
//LOGI("Has %d (max %d) of type: %d\n", typeCount, typeMax, currentMobType.mobClassId);
|
|
if (typeCount >= typeMax)
|
|
break;
|
|
}
|
|
|
|
maxCreatureCount = currentMobType.minCount + level->random.nextInt(1 + currentMobType.maxCount - currentMobType.minCount);
|
|
}
|
|
|
|
Mob* tmp = MobFactory::getStaticTestMob(currentMobType.mobClassId, level);
|
|
if (!tmp) continue;
|
|
|
|
tmp->moveTo(xx, yy, zz, 0, 0);
|
|
if (!tmp->canSpawn()) continue;
|
|
|
|
Mob* mob = MobFactory::CreateMob(currentMobType.mobClassId, level);
|
|
if (!mob) continue;
|
|
|
|
if (addMob(level, mob, xx, yy, zz, level->random.nextFloat() * 360, 0, false)) {
|
|
++currentCreatureCount;
|
|
if (++clusterSize >= mob->getMaxSpawnClusterSize()) goto chunkLoop;
|
|
}
|
|
else
|
|
delete mob;
|
|
|
|
count += clusterSize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/*static*/
|
|
void MobSpawner::postProcessSpawnMobs(Level* level, Biome* biome, int xo, int zo, int cellWidth, int cellHeight, Random* random) {
|
|
|
|
//return;
|
|
|
|
Biome::MobList mobs = biome->getMobs(MobCategory::creature);
|
|
if (mobs.empty()) {
|
|
return;
|
|
}
|
|
|
|
while (random->nextFloat() < biome->getCreatureProbability()) {
|
|
|
|
Biome::MobSpawnerData* type = (Biome::MobSpawnerData*) WeighedRandom::getRandomItem(&level->random, 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 = (float)x + 0.5f;
|
|
float yy = (float)y;
|
|
float zz = (float)z + 0.5f;
|
|
|
|
Mob* mob = MobFactory::CreateMob(type->mobClassId, level);
|
|
if (!mob) continue;
|
|
|
|
// System.out.println("Placing night mob");
|
|
mob->moveTo(xx, yy, zz, random->nextFloat() * 360, 0);
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*static*/
|
|
TilePos MobSpawner::getRandomPosWithin(Level* level, int xo, int zo) {
|
|
int x = xo + level->random.nextInt(16);
|
|
int y = level->random.nextInt(Level::DEPTH); //@note: level->depth);
|
|
int z = zo + level->random.nextInt(16);
|
|
|
|
return TilePos(x, y, z);
|
|
}
|
|
|
|
/*static*/
|
|
bool MobSpawner::isSpawnPositionOk(const MobCategory& category, Level* level, int x, int y, int z) {
|
|
if (category.getSpawnPositionMaterial() == Material::water) {
|
|
return level->getMaterial(x, y, z)->isLiquid() && !level->isSolidBlockingTile(x, y + 1, z);
|
|
} else {
|
|
return level->isSolidBlockingTile(x, y - 1, z) && !level->isSolidBlockingTile(x, y, z) && !level->getMaterial(x, y, z)->isLiquid() && !level->isSolidBlockingTile(x, y + 1, z);
|
|
}
|
|
}
|
|
|
|
/*static*/
|
|
void MobSpawner::finalizeMobSettings(Mob* mob, Level* level, float xx, float yy, float zz) {
|
|
// @todo
|
|
// if (mob instanceof Spider && level->random->nextInt(100) == 0) {
|
|
// Skeleton skeleton = /*new*/ Skeleton(level);
|
|
// skeleton.moveTo(xx, yy, zz, mob.yRot, 0);
|
|
// level->addEntity(skeleton);
|
|
// skeleton.ride(mob);
|
|
// } else if (mob instanceof Sheep) {
|
|
// ((Sheep) mob).setColor(Sheep.getSheepColor(level->random));
|
|
// }
|
|
|
|
if (mob->getEntityTypeId() == MobTypes::Sheep) {
|
|
((Sheep*) mob)->setColor(Sheep::getSheepColor(&level->random));
|
|
}
|
|
|
|
makeBabyMob(mob, 0.5f);
|
|
}
|
|
|
|
/*static*/
|
|
bool MobSpawner::addMob(Level* level, Mob* mob, float xx, float yy, float zz, float yRot, float xRot, bool force)
|
|
{
|
|
mob->moveTo(xx, yy, zz, yRot, xRot);
|
|
|
|
if (force || mob->canSpawn()) {
|
|
level->addEntity(mob);
|
|
finalizeMobSettings(mob, level, xx, yy, zz);
|
|
return true;
|
|
} else {
|
|
//LOGI("Couldn't add the entity\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void MobSpawner::makeBabyMob( Mob* mob, float probability ) {
|
|
static Random babyRandom(98495119L);
|
|
if (MobTypes::BaseCreature == mob->getCreatureBaseType()) {
|
|
if (babyRandom.nextFloat() < probability)
|
|
((Animal*)mob)->setAge(-20 * 60 * SharedConstants::TicksPerSecond);
|
|
}
|
|
}
|