diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 33e075227..88759a18b 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -237,6 +237,7 @@ add_library(reminecraftpe-core STATIC world/entity/Rocket.cpp world/entity/EntityFactory.cpp world/entity/MobFactory.cpp + world/entity/MobSpawner.cpp world/entity/Chicken.cpp world/entity/Cow.cpp world/entity/Creeper.cpp diff --git a/source/client/app/NinecraftApp.cpp b/source/client/app/NinecraftApp.cpp index 864162c0c..8e0921cea 100644 --- a/source/client/app/NinecraftApp.cpp +++ b/source/client/app/NinecraftApp.cpp @@ -9,6 +9,7 @@ #include "NinecraftApp.hpp" #include "world/item/Item.hpp" #include "world/entity/MobCategory.hpp" +#include "world/entity/MobFactory.hpp" #include "client/player/input/Multitouch.hpp" #include "client/gui/screens/StartMenuScreen.hpp" #include "client/renderer/FoliageColor.hpp" @@ -155,6 +156,7 @@ void NinecraftApp::_initAll() Material::initMaterials(); EntityTypeDescriptor::initDescriptors(); // custom MobCategory::initMobCategories(); + MobFactory::initMobLists(); Tile::initTiles(); Item::initItems(); Biome::initBiomes(); diff --git a/source/world/entity/EntityCategories.cpp b/source/world/entity/EntityCategories.cpp index e7239c8ee..cbaeda556 100644 --- a/source/world/entity/EntityCategories.cpp +++ b/source/world/entity/EntityCategories.cpp @@ -1,5 +1,28 @@ #include "EntityCategories.hpp" +const EntityCategories::CategoriesMask EntityCategories::maskEnums[] = +{ + ENTITY, + MOB, + PATHFINDER_MOB, + UNKNOWN, + MONSTER, + ANIMAL, + WATER_ANIMAL, + TAMABLE_ANIMAL, + AMBIENT, + UNDEAD_MOB, + ZOMBIE_MONSTER, + ARTHROPOD, + MINECART, + SKELETON_MONSTER, + EQUINE_ANIMAL, + PROJECTILE, + ABSTRACT_ARROW, + VILLAGER_BASE +}; +const int EntityCategories::maskEnumCount = sizeof(EntityCategories::maskEnums) / sizeof(const EntityCategories::CategoriesMask); + EntityCategories::EntityCategories(CategoriesMask categoriesMask) { m_categoriesMask = categoriesMask; diff --git a/source/world/entity/EntityCategories.hpp b/source/world/entity/EntityCategories.hpp index c95d73a09..16a701c00 100644 --- a/source/world/entity/EntityCategories.hpp +++ b/source/world/entity/EntityCategories.hpp @@ -24,6 +24,8 @@ class EntityCategories ABSTRACT_ARROW = 1<<15 | PROJECTILE, VILLAGER_BASE = 1<<16 | PATHFINDER_MOB }; + static const CategoriesMask maskEnums[]; + static const int maskEnumCount; public: EntityCategories(CategoriesMask = ENTITY); diff --git a/source/world/entity/MobCategory.cpp b/source/world/entity/MobCategory.cpp index 96f8d8779..acc7ea72c 100644 --- a/source/world/entity/MobCategory.cpp +++ b/source/world/entity/MobCategory.cpp @@ -5,12 +5,14 @@ MobCategory MobCategory::monster = MobCategory(EntityCategories(EntityCategories::MONSTER), 10, 20, nullptr, false); MobCategory MobCategory::creature = MobCategory(EntityCategories(EntityCategories::ANIMAL), 10, 15, nullptr, true); MobCategory MobCategory::waterCreature = MobCategory(EntityCategories(EntityCategories::WATER_ANIMAL), 5, 10, nullptr, true); -const MobCategory MobCategory::values[] = { - MobCategory::monster, - MobCategory::creature, - MobCategory::waterCreature + +const MobCategory* MobCategory::all[] = { + &MobCategory::monster, + &MobCategory::creature, + //MobCategory::waterCreature }; -const int MobCategory::numValues = sizeof(MobCategory::values) / sizeof(MobCategory); + +const int MobCategory::allCount = sizeof(MobCategory::all) / sizeof(const MobCategory*); MobCategory::MobCategory(const EntityCategories& baseType, int unknown, int max, const Material* material, bool friendly) : m_baseType(baseType) @@ -26,4 +28,4 @@ void MobCategory::initMobCategories() MobCategory::monster.m_pSpawnPositionMaterial = Material::air; MobCategory::creature.m_pSpawnPositionMaterial = Material::air; MobCategory::waterCreature.m_pSpawnPositionMaterial = Material::water; -} \ No newline at end of file +} diff --git a/source/world/entity/MobCategory.hpp b/source/world/entity/MobCategory.hpp index d90b6c4b3..f50a4e277 100644 --- a/source/world/entity/MobCategory.hpp +++ b/source/world/entity/MobCategory.hpp @@ -9,8 +9,8 @@ class MobCategory static MobCategory monster; static MobCategory creature; static MobCategory waterCreature; - static const MobCategory values[]; - static const int numValues; + static const MobCategory* all[]; + static const int allCount; private: MobCategory(const EntityCategories&, int, int, const Material*, bool); @@ -24,8 +24,9 @@ class MobCategory const Material* getSpawnPositionMaterial() const { return m_pSpawnPositionMaterial; } bool isFriendly() const { return m_bIsFriendly; } + private: - const EntityCategories& m_baseType; + const EntityCategories m_baseType; int field_4; int m_maxInstancesPerChunk; const Material* m_pSpawnPositionMaterial; diff --git a/source/world/entity/MobFactory.cpp b/source/world/entity/MobFactory.cpp index afa536d7d..60a84cced 100644 --- a/source/world/entity/MobFactory.cpp +++ b/source/world/entity/MobFactory.cpp @@ -24,6 +24,13 @@ #define ENT(enumType, classType) case EntityType::enumType: return new classType(level); + +// format: ID, spawnrate +std::map monsterList; +std::map creatureList; +std::map waterCreatureList; +std::map nullCreatureList; + Mob* MobFactory::CreateMob(EntityType::ID entityType, Level *level) { switch (entityType) @@ -35,4 +42,30 @@ Mob* MobFactory::CreateMob(EntityType::ID entityType, Level *level) } } +void MobFactory::initMobLists() +{ + // format: ID, spawnrate + + monsterList.insert(std::make_pair(EntityType::SPIDER, 10)); + monsterList.insert(std::make_pair(EntityType::ZOMBIE, 10)); + monsterList.insert(std::make_pair(EntityType::SKELETON, 10)); + monsterList.insert(std::make_pair(EntityType::CREEPER, 10)); + //monsterList.insert(std::make_pair(EntityType::SLIME, 10)); + + creatureList.insert(std::make_pair(EntityType::SHEEP, 12)); + creatureList.insert(std::make_pair(EntityType::PIG, 10)); + creatureList.insert(std::make_pair(EntityType::CHICKEN, 10)); + creatureList.insert(std::make_pair(EntityType::COW, 8)); + + waterCreatureList.insert(std::make_pair(EntityType::SQUID, 10)); +} + +const std::map& MobFactory::GetMobListOfCategory(const EntityCategories& category) +{ + EntityCategories::CategoriesMask mask = category.getCategoryMask(); + return mask == EntityCategories::MONSTER ? monsterList : + mask == EntityCategories::ANIMAL ? creatureList : + nullCreatureList; +} + #undef ENT \ No newline at end of file diff --git a/source/world/entity/MobFactory.hpp b/source/world/entity/MobFactory.hpp index 0a9a078aa..669ef1071 100644 --- a/source/world/entity/MobFactory.hpp +++ b/source/world/entity/MobFactory.hpp @@ -3,8 +3,12 @@ #include "EntityType.hpp" #include "Mob.hpp" +class MobCategory; + class MobFactory { -public: +public: + static void initMobLists(); static Mob* CreateMob(EntityType::ID entityType, Level *level); + static const std::map& GetMobListOfCategory(const EntityCategories& category); }; \ No newline at end of file diff --git a/source/world/entity/MobSpawner.cpp b/source/world/entity/MobSpawner.cpp new file mode 100644 index 000000000..16aae39cc --- /dev/null +++ b/source/world/entity/MobSpawner.cpp @@ -0,0 +1,195 @@ + +#include "world/entity/MobSpawner.hpp" +#include "world/entity/MobFactory.hpp" + +#define MOB_SPAWNER_HOSTILE_BRIGHTNESS 7 +#define MOB_SPAWNER_FRIENDLY_BRIGHTNESS 9 + +void MobSpawner::tick(Level& level, bool allowHostile, bool allowFriendly) +{ + if (!allowHostile && !allowFriendly) + return; + + chunksToPoll.clear(); + + for (std::vector::const_iterator it = level.m_players.begin(); it != level.m_players.end(); ++it) + { + const Player* player = *it; + int cx = Mth::floor(player->m_pos.x / 16.0f); + int cz = Mth::floor(player->m_pos.z / 16.0f); + + for (int dx = -8; dx <= 8; ++dx) + { + for (int dz = -8; dz <= 8; ++dz) + { + chunksToPoll.insert(ChunkPos(cx + dx, cz + dz)); + } + } + } + + int totalSpawned = 0; + + for (int i = 0; i < MobCategory::allCount; i++) + { + const MobCategory& category = *MobCategory::all[i]; + const EntityCategories& baseType = category.getBaseType(); + bool isFriendly = category.isFriendly(); + + // good mobs don't spawn after dark, otherwise they will crowd around torches like beta + if (!level.isDay() && isFriendly) + continue; + + if ((isFriendly && !allowFriendly) || (!isFriendly && !allowHostile)) + continue; + + if (level.getEntityCount(baseType) <= (category.getMaxInstancesPerChunk() * (int)chunksToPoll.size() / 256)) + { + for (std::set::iterator it = chunksToPoll.begin(); it != chunksToPoll.end(); ++it) + { + const ChunkPos& pos = *it; + + const std::map& spawnList = MobFactory::GetMobListOfCategory(baseType); + + if (spawnList.empty()) + continue; + + EntityType::ID entityID = spawnList.begin()->first; + + int spawnWeight = 1; // make sure it starts with 1 so arithmetic exception doesn't occur + + for (std::map::const_iterator it = spawnList.begin(); it != spawnList.end(); ++it) + { + spawnWeight += it->second; + } + + int randomRate = level.m_random.nextInt(spawnWeight); + + for (std::map::const_iterator it = spawnList.begin(); it != spawnList.end(); ++it) + { + randomRate -= it->second; + if (randomRate < 0) + { + entityID = it->first; + break; + } + } + + int idx = level.m_random.nextInt((int)spawnList.size()); + TilePos tpos = getRandomPosWithin(level, pos.x * 16, pos.z * 16); + + if (level.isSolidTile(tpos) || level.getMaterial(tpos) != category.getSpawnPositionMaterial()) + continue; + + int spawned = 0; + for (int i = 0; i < 3; ++i) + { + TilePos tp(tpos); + + if (spawned == -1) + break; + + for (int j = 0; j < 4; ++j) + { + tp.x += level.m_random.nextInt(6) - level.m_random.nextInt(6); + tp.y += level.m_random.nextInt(1) - level.m_random.nextInt(1); + tp.z += level.m_random.nextInt(6) - level.m_random.nextInt(6); + + if (IsSpawnPositionOk(category, level, tp)) + { + Vec3 pPos(tp.x + 0.5, tp.y, tp.z + 0.5); + + if (!level.getNearestPlayer(pPos, 24.0f, false)) + { + Vec3 dPos = pPos - level.getSharedSpawnPos(); + + if (dPos.lengthSqr() >= 576.0f) + { + Mob* entity = MobFactory::CreateMob(entityID, &level); + if (!entity) + break; + + entity->moveTo(pPos, Vec2(level.m_random.nextFloat() * 360.0f, 0.0f)); + + if (entity->canSpawn()) + { + ++spawned; + level.addEntity(entity); + FinalizeMobSettings(entity, level, pPos); + if (spawned >= entity->getMaxSpawnClusterSize()) + { + totalSpawned += spawned; + spawned = -1; + break; + } + } + } + } + } + } + } + + if (spawned != -1) + totalSpawned += spawned; + } + } + } + +} + +TilePos MobSpawner::getRandomPosWithin(Level& level, int chunkX, int chunkZ) +{ + int px = level.m_random.nextInt(16) + chunkX; + int py = level.m_random.nextInt(128); + int pz = level.m_random.nextInt(16) + chunkZ; + return TilePos(px, py, pz); +} + +//todo: bool? +int MobSpawner::AddMob(Level& level, Mob *mob, const Vec3& pos, const Vec2& rot) +{ + if (!mob) + return 0; + + if (!mob->canSpawn() || !mob->isAlive()) + return 0; + + mob->moveTo(pos, rot); + level.addEntity(mob); + FinalizeMobSettings(mob, level, pos); + + return 1; +} + +bool MobSpawner::IsSpawnPositionOk(const MobCategory& category, Level& level, const TilePos& pos) +{ + if (category.getSpawnPositionMaterial() == Material::water) + return level.getMaterial(pos)->isLiquid() && !level.isSolidTile(pos.above()); + + return level.isSolidTile(pos.below()) && !level.isSolidTile(pos) && !level.getMaterial(pos)->isLiquid() && !level.isSolidTile(pos.above()); +} + +void MobSpawner::FinalizeMobSettings(Mob *mob, Level& level, const Vec3& pos) +{ + if (!mob) + return; + + //mob->finalizeMobSpawn(); + MakeBabyMob(mob, level); +} + + +void MobSpawner::MakeBabyMob(Mob *mob, Level& level) +{ + level.m_random.setSeed(0x5deea8f); + + if (mob->isBaby()) + return; + + // todo +} + + +void MobSpawner::PostProcessSpawnMobs(Level& level, Biome& biome, const Vec3& pos) +{ + // empty (0.7.1) +} \ No newline at end of file diff --git a/source/world/entity/MobSpawner.hpp b/source/world/entity/MobSpawner.hpp new file mode 100644 index 000000000..74fede30f --- /dev/null +++ b/source/world/entity/MobSpawner.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "world/tile/Tile.hpp" +#include "world/entity/Entity.hpp" +#include "world/entity/MobCategory.hpp" +#include "world/level/Level.hpp" +#include "world/level/levelgen/chunk/LevelChunk.hpp" +#include "world/level/levelgen/chunk/ChunkSource.hpp" +#include "world/level/storage/LevelStorageSource.hpp" +#include "world/level/storage/LevelSource.hpp" +#include "world/level/storage/LevelData.hpp" +#include "world/level/path/PathFinder.hpp" + +class MobSpawner { +public: + static bool IsSpawnPositionOk(const MobCategory& category, Level& level, const TilePos& pos); + static void FinalizeMobSettings(Mob* mob, Level& level, const Vec3& pos); + static void MakeBabyMob(Mob* mob, Level& level); + static void PostProcessSpawnMobs(Level& level, Biome& biome, const Vec3& pos); + static int AddMob(Level& level, Mob* mob, const Vec3& pos, const Vec2& rot = Vec2::ZERO); + + TilePos getRandomPosWithin(Level& level, int chunkX, int chunkZ); + void tick(Level& level, bool allowHostile, bool allowFriendly); +private: + + std::set chunksToPoll; +}; \ No newline at end of file diff --git a/source/world/level/Level.cpp b/source/world/level/Level.cpp index ada020483..bf351afbd 100644 --- a/source/world/level/Level.cpp +++ b/source/world/level/Level.cpp @@ -62,6 +62,7 @@ Level::Level(LevelStorage* pStor, const std::string& name, const LevelSettings& m_pDimension->init(this); m_pPathFinder = new PathFinder(); + m_pMobSpawner = new MobSpawner(); m_pChunkSource = createChunkSource(); updateSkyBrightness(); @@ -72,6 +73,7 @@ Level::~Level() SAFE_DELETE(m_pChunkSource); SAFE_DELETE(m_pDimension); SAFE_DELETE(m_pPathFinder); + SAFE_DELETE(m_pMobSpawner); const size_t size = m_entities.size(); for (size_t i = 0; i < size; i++) @@ -770,15 +772,33 @@ void Level::setTilesDirty(const TilePos& min, const TilePos& max) void Level::entityAdded(Entity* pEnt) { + // save for a bit + EntityCategories::CategoriesMask mask = pEnt->getDescriptor().getCategories().getCategoryMask(); + for (int i = 0; i < EntityCategories::maskEnumCount; i++ ) + { + EntityCategories::CategoriesMask category = EntityCategories::maskEnums[i]; + if ((mask & category) == category) + m_entityCountsByCategory[category]++; + } + for (std::vector::iterator it = m_levelListeners.begin(); it != m_levelListeners.end(); it++) { LevelListener* pListener = *it; pListener->entityAdded(pEnt); } + } void Level::entityRemoved(Entity* pEnt) { + EntityCategories::CategoriesMask mask = pEnt->getDescriptor().getCategories().getCategoryMask(); + for (int i = 0; i < EntityCategories::maskEnumCount; i++ ) + { + EntityCategories::CategoriesMask category = EntityCategories::maskEnums[i]; + if ((mask & category) == category) + m_entityCountsByCategory[category]--; + } + for (std::vector::iterator it = m_levelListeners.begin(); it != m_levelListeners.end(); it++) { LevelListener* pListener = *it; @@ -1590,6 +1610,7 @@ int LASTTICKED = 0; void Level::tick() { + m_pMobSpawner->tick(*this, m_difficulty > 0, true); m_pChunkSource->tick(); #ifdef ENH_RUN_DAY_NIGHT_CYCLE @@ -1939,3 +1960,8 @@ float Level::getSunAngle(float f) const { return (float(M_PI) * getTimeOfDay(f)) * 2; } + +int Level::getEntityCount(const EntityCategories& category) +{ + return m_entityCountsByCategory[category.getCategoryMask()]; +} diff --git a/source/world/level/Level.hpp b/source/world/level/Level.hpp index 62c5554d0..7356e49c6 100644 --- a/source/world/level/Level.hpp +++ b/source/world/level/Level.hpp @@ -8,7 +8,8 @@ #pragma once -#include +#include +#include #ifndef _USE_MATH_DEFINES #define _USE_MATH_DEFINES #endif @@ -17,6 +18,7 @@ #include "client/renderer/LightUpdate.hpp" #include "world/tile/Tile.hpp" #include "world/entity/Entity.hpp" +#include "world/entity/MobSpawner.hpp" #include "world/level/TileChange.hpp" #include "world/level/levelgen/chunk/LevelChunk.hpp" #include "world/level/levelgen/chunk/ChunkSource.hpp" @@ -34,6 +36,8 @@ class Level; class LevelListener; class RakNetInstance; +class MobSpawner; + typedef std::vector EntityVector; typedef std::vector AABBVector; @@ -190,6 +194,9 @@ class Level : public LevelSource bool hasDirectSignal(const TilePos& pos) const; bool hasNeighborSignal(const TilePos& pos) const; + + int getEntityCount(const EntityCategories&); + #ifdef ENH_IMPROVED_SAVING void saveUnsavedChunks(); #endif @@ -227,5 +234,8 @@ class Level : public LevelSource uint8_t field_B0C; int field_B10; PathFinder* m_pPathFinder; + MobSpawner* m_pMobSpawner; + + std::map m_entityCountsByCategory; };