From 718eb227abe3b9a0e5277d663e32c6e10d51ab52 Mon Sep 17 00:00:00 2001
From: Howaner <franzi.moos@googlemail.com>
Date: Fri, 19 Sep 2014 23:00:54 +0200
Subject: Implemented mob spawner.

---
 src/BlockEntities/BlockEntity.cpp      |   6 +-
 src/BlockEntities/MobSpawnerEntity.cpp | 310 ++++++++++++++++++++++++++++++---
 src/BlockEntities/MobSpawnerEntity.h   |  50 +++---
 3 files changed, 323 insertions(+), 43 deletions(-)

(limited to 'src/BlockEntities')

diff --git a/src/BlockEntities/BlockEntity.cpp b/src/BlockEntities/BlockEntity.cpp
index 05ad03a3d..a969f493e 100644
--- a/src/BlockEntities/BlockEntity.cpp
+++ b/src/BlockEntities/BlockEntity.cpp
@@ -14,10 +14,11 @@
 #include "FlowerPotEntity.h"
 #include "FurnaceEntity.h"
 #include "HopperEntity.h"
+#include "MobHeadEntity.h"
+#include "MobSpawnerEntity.h"
 #include "JukeboxEntity.h"
 #include "NoteEntity.h"
 #include "SignEntity.h"
-#include "MobHeadEntity.h"
 
 
 
@@ -35,8 +36,9 @@ cBlockEntity * cBlockEntity::CreateByBlockType(BLOCKTYPE a_BlockType, NIBBLETYPE
 		case E_BLOCK_ENDER_CHEST:   return new cEnderChestEntity  (a_BlockX, a_BlockY, a_BlockZ, a_World);
 		case E_BLOCK_FLOWER_POT:    return new cFlowerPotEntity   (a_BlockX, a_BlockY, a_BlockZ, a_World);
 		case E_BLOCK_FURNACE:       return new cFurnaceEntity     (a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta, a_World);
-		case E_BLOCK_HEAD:          return new cMobHeadEntity     (a_BlockX, a_BlockY, a_BlockZ, a_World);
 		case E_BLOCK_HOPPER:        return new cHopperEntity      (a_BlockX, a_BlockY, a_BlockZ, a_World);
+		case E_BLOCK_HEAD:          return new cMobHeadEntity     (a_BlockX, a_BlockY, a_BlockZ, a_World);
+		case E_BLOCK_MOB_SPAWNER:   return new cMobSpawnerEntity  (a_BlockX, a_BlockY, a_BlockZ, a_World);
 		case E_BLOCK_JUKEBOX:       return new cJukeboxEntity     (a_BlockX, a_BlockY, a_BlockZ, a_World);
 		case E_BLOCK_LIT_FURNACE:   return new cFurnaceEntity     (a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta, a_World);
 		case E_BLOCK_SIGN_POST:     return new cSignEntity        (a_BlockType, a_BlockX, a_BlockY, a_BlockZ, a_World);
diff --git a/src/BlockEntities/MobSpawnerEntity.cpp b/src/BlockEntities/MobSpawnerEntity.cpp
index 1db1aad9b..2b963c3f9 100644
--- a/src/BlockEntities/MobSpawnerEntity.cpp
+++ b/src/BlockEntities/MobSpawnerEntity.cpp
@@ -2,22 +2,22 @@
 #include "Globals.h"  // NOTE: MSVC stupidness requires this to be the same across all modules
 
 #include "MobSpawnerEntity.h"
-#include "../World.h"
 #include "json/json.h"
 
+#include "../World.h"
+#include "../FastRandom.h"
+#include "../MobSpawner.h"
+#include "../Items/ItemSpawnEgg.h"
+
 
 
 
 
 cMobSpawnerEntity::cMobSpawnerEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World)
 	: super(E_BLOCK_MOB_SPAWNER, a_BlockX, a_BlockY, a_BlockZ, a_World)
-	, m_EntityName("Pig")
-	, m_SpawnDelay(20)
-	, m_MinSpawnDelay(200)
-	, m_MaxSpawnDelay(800)
-	, m_MaxNearbyEntities(6)
-	, m_ActivatingRange(16)
-	, m_SpawnRange(4)
+	, m_Entity(cMonster::mtPig)
+	, m_SpawnDelay(100)
+	, m_IsActive(false)
 {
 }
 
@@ -25,37 +25,170 @@ cMobSpawnerEntity::cMobSpawnerEntity(int a_BlockX, int a_BlockY, int a_BlockZ, c
 
 
 
-cMobSpawnerEntity::~cMobSpawnerEntity()
+void cMobSpawnerEntity::SendTo(cClientHandle & a_Client)
 {
-	
+	a_Client.SendUpdateBlockEntity(*this);
 }
 
 
 
 
 
-bool cMobSpawnerEntity::Tick(float a_Dt, cChunk & a_Chunk)
+void cMobSpawnerEntity::UsedBy(cPlayer * a_Player)
+{
+	if (a_Player->GetEquippedItem().m_ItemType == E_ITEM_SPAWN_EGG)
+	{
+		cMonster::eType MonsterType = cItemSpawnEggHandler::ItemDamageToMonsterType(a_Player->GetEquippedItem().m_ItemDamage);
+		if (MonsterType == cMonster::mtInvalidType)
+		{
+			return;
+		}
+
+		m_Entity = MonsterType;
+		ResetTimer();
+		if (!a_Player->IsGameModeCreative())
+		{
+			a_Player->GetInventory().RemoveOneEquippedItem();
+		}
+		LOGD("Changed monster spawner entity to %s!", GetEntityName().c_str());
+	}
+}
+
+
+
+
+
+void cMobSpawnerEntity::UpdateActiveState(void)
 {
-	
+	if (GetNearbyPlayersNum() > 0)
+	{
+		m_IsActive = true;
+	}
+	else
+	{
+		m_IsActive = false;
+	}
 }
 
 
 
 
 
-void cMobSpawnerEntity::UsedBy(cPlayer * a_Player)
+bool cMobSpawnerEntity::Tick(float a_Dt, cChunk & a_Chunk)
 {
-	if (IsPlayingRecord())
+	// Update the active flag every 5 seconds
+	if ((m_World->GetWorldAge() % 100) == 0)
+	{
+		UpdateActiveState();
+	}
+
+	if (!m_IsActive)
+	{
+		return false;
+	}
+
+	if (m_SpawnDelay <= 0)
 	{
-		EjectRecord();
+		SpawnEntity();
+		return true;
 	}
 	else
 	{
-		const cItem & HeldItem = a_Player->GetEquippedItem();
-		if (PlayRecord(HeldItem.m_ItemType))
+		m_SpawnDelay--;
+	}
+	return false;
+}
+
+
+
+
+
+void cMobSpawnerEntity::ResetTimer(void)
+{
+	m_SpawnDelay = 200 + m_World->GetTickRandomNumber(600);
+	m_World->BroadcastBlockEntity(m_PosX, m_PosY, m_PosZ);
+}
+
+
+
+
+
+void cMobSpawnerEntity::SpawnEntity(void)
+{
+	int NearbyEntities = GetNearbyEntityNum(m_Entity);
+	if (NearbyEntities >= 6)
+	{
+		ResetTimer();
+		return;
+	}
+
+	class cCallback : public cChunkCallback
+	{
+	public:
+		cCallback(int a_RelX, int a_RelY, int a_RelZ, cMonster::eType a_MobType, int a_NearbyEntitiesNum) :
+			m_RelX(a_RelX),
+			m_RelY(a_RelY),
+			m_RelZ(a_RelZ),
+			m_MobType(a_MobType),
+			m_NearbyEntitiesNum(a_NearbyEntitiesNum)
 		{
-			a_Player->GetInventory().RemoveOneEquippedItem();
 		}
+
+		virtual bool Item(cChunk * a_Chunk)
+		{
+			cFastRandom Random;
+
+			bool EntitiesSpawned = false;
+			for (size_t i = 0; i < 4; i++)
+			{
+				if (m_NearbyEntitiesNum >= 6)
+				{
+					break;
+				}
+
+				int RelX = (int) (m_RelX + (double)(Random.NextFloat() - Random.NextFloat()) * 4.0);
+				int RelY = m_RelY + Random.NextInt(3) - 1;
+				int RelZ = (int) (m_RelZ + (double)(Random.NextFloat() - Random.NextFloat()) * 4.0);
+
+				cChunk * Chunk = a_Chunk->GetRelNeighborChunkAdjustCoords(RelX, RelZ);
+				if ((Chunk == NULL) || !Chunk->IsValid())
+				{
+					continue;
+				}
+				EMCSBiome Biome = Chunk->GetBiomeAt(RelX, RelZ);
+
+				if (cMobSpawner::CanSpawnHere(Chunk, RelX, RelY, RelZ, m_MobType, Biome))
+				{
+					double PosX = Chunk->GetPosX() * cChunkDef::Width + RelX;
+					double PosZ = Chunk->GetPosZ() * cChunkDef::Width + RelZ;
+
+					cMonster * Monster = cMonster::NewMonsterFromType(m_MobType);
+					if (Monster == NULL)
+					{
+						continue;
+					}
+
+					Monster->SetPosition(PosX, RelY, PosZ);
+					Monster->SetYaw(Random.NextFloat() * 360.0f);
+					if (Chunk->GetWorld()->SpawnMobFinalize(Monster) != cMonster::mtInvalidType)
+					{
+						EntitiesSpawned = true;
+						Chunk->BroadcastSoundParticleEffect(2004, (int)(PosX * 8.0), (int)(RelY * 8.0), (int)(PosZ * 8.0), 0);
+						m_NearbyEntitiesNum++;
+					}
+				}
+			}
+			return EntitiesSpawned;
+		}
+	protected:
+		int m_RelX, m_RelY, m_RelZ;
+		cMonster::eType m_MobType;
+		int m_NearbyEntitiesNum;
+	} Callback(m_RelX, m_PosY, m_RelZ, m_Entity, NearbyEntities);
+
+	if (m_World->DoWithChunk(GetChunkX(), GetChunkZ(), Callback))
+	{
+		ResetTimer();
 	}
 }
 
@@ -69,8 +202,6 @@ bool cMobSpawnerEntity::LoadFromJson(const Json::Value & a_Value)
 	m_PosY = a_Value.get("y", 0).asInt();
 	m_PosZ = a_Value.get("z", 0).asInt();
 
-	m_Record = a_Value.get("Record", 0).asInt();
-
 	return true;
 }
 
@@ -83,8 +214,145 @@ void cMobSpawnerEntity::SaveToJson(Json::Value & a_Value)
 	a_Value["x"] = m_PosX;
 	a_Value["y"] = m_PosY;
 	a_Value["z"] = m_PosZ;
+}
+
+
+
+
+
+AString cMobSpawnerEntity::GetEntityName() const
+{
+	switch (m_Entity)
+	{
+		case cMonster::mtBat:          return "Bat";
+		case cMonster::mtBlaze:        return "Blaze";
+		case cMonster::mtCaveSpider:   return "CaveSpider";
+		case cMonster::mtChicken:      return "Chicken";
+		case cMonster::mtCow:          return "Cow";
+		case cMonster::mtCreeper:      return "Creeper";
+		case cMonster::mtEnderDragon:  return "EnderDragon";
+		case cMonster::mtEnderman:     return "Enderman";
+		case cMonster::mtGhast:        return "Ghast";
+		case cMonster::mtGiant:        return "Giant";
+		case cMonster::mtHorse:        return "EntityHorse";
+		case cMonster::mtIronGolem:    return "VillagerGolem";
+		case cMonster::mtMagmaCube:    return "LavaSlime";
+		case cMonster::mtMooshroom:    return "MushroomCow";
+		case cMonster::mtOcelot:       return "Ozelot";
+		case cMonster::mtPig:          return "Pig";
+		case cMonster::mtSheep:        return "Sheep";
+		case cMonster::mtSilverfish:   return "Silverfish";
+		case cMonster::mtSkeleton:     return "Skeleton";
+		case cMonster::mtSlime:        return "Slime";
+		case cMonster::mtSnowGolem:    return "SnowMan";
+		case cMonster::mtSpider:       return "Spider";
+		case cMonster::mtSquid:        return "Squid";
+		case cMonster::mtVillager:     return "Villager";
+		case cMonster::mtWitch:        return "Witch";
+		case cMonster::mtWither:       return "WitherBoss";
+		case cMonster::mtWolf:         return "Wolf";
+		case cMonster::mtZombie:       return "Zombie";
+		case cMonster::mtZombiePigman: return "PigZombie";
+		default:
+		{
+			ASSERT(!"Unknown monster type!");
+			return "Pig";
+		}
+	}
+}
+
+
+
+
+
+int cMobSpawnerEntity::GetNearbyPlayersNum(void)
+{
+	Vector3d SpawnerPos(m_PosX + 0.5, m_PosY + 0.5, m_PosZ + 0.5);
+	int NumPlayers = 0;
+
+	class cCallback : public cChunkDataCallback
+	{
+	public:
+		cCallback(Vector3d a_SpawnerPos, int & a_NumPlayers) :
+			m_SpawnerPos(a_SpawnerPos),
+			m_NumPlayers(a_NumPlayers)
+		{
+		}
+
+		virtual void Entity(cEntity * a_Entity) override
+		{
+			if (!a_Entity->IsPlayer())
+			{
+				return;
+			}
+
+			if ((m_SpawnerPos - a_Entity->GetPosition()).Length() <= 16)
+			{
+				m_NumPlayers++;
+			}
+		}
+
+	protected:
+		Vector3d m_SpawnerPos;
+		int & m_NumPlayers;
+	} Callback(SpawnerPos, NumPlayers);
+
+	int ChunkX = GetChunkX();
+	int ChunkZ = GetChunkZ();
+	m_World->ForEachChunkInRect(ChunkX - 1, ChunkX + 1, ChunkZ - 1, ChunkZ + 1, Callback);
+
+	return NumPlayers;
+}
+
+
+
+
+
+int cMobSpawnerEntity::GetNearbyEntityNum(cMonster::eType a_EntityType)
+{
+	Vector3d SpawnerPos(m_PosX + 0.5, m_PosY + 0.5, m_PosZ + 0.5);
+	int NumEntities = 0;
+
+	class cCallback : public cChunkDataCallback
+	{
+	public:
+		cCallback(Vector3d a_SpawnerPos, cMonster::eType a_EntityType, int & a_NumEntities) :
+			m_SpawnerPos(a_SpawnerPos),
+			m_EntityType(a_EntityType),
+			m_NumEntities(a_NumEntities)
+		{
+		}
+
+		virtual void Entity(cEntity * a_Entity) override
+		{
+			if (!a_Entity->IsMob())
+			{
+				return;
+			}
+
+			cMonster * Mob = (cMonster *)a_Entity;
+			if (Mob->GetMobType() != m_EntityType)
+			{
+				return;
+			}
+
+			if (((m_SpawnerPos - a_Entity->GetPosition()).Length() <= 8) && (Diff(m_SpawnerPos.y, a_Entity->GetPosY()) <= 4.0))
+			{
+				m_NumEntities++;
+			}
+		}
+
+	protected:
+		Vector3d m_SpawnerPos;
+		cMonster::eType m_EntityType;
+		int & m_NumEntities;
+	} Callback(SpawnerPos, a_EntityType, NumEntities);
+
+	int ChunkX = GetChunkX();
+	int ChunkZ = GetChunkZ();
+	m_World->ForEachChunkInRect(ChunkX - 1, ChunkX + 1, ChunkZ - 1, ChunkZ + 1, Callback);
 
-	a_Value["Record"] = m_Record;
+	return NumEntities;
 }
 
 
diff --git a/src/BlockEntities/MobSpawnerEntity.h b/src/BlockEntities/MobSpawnerEntity.h
index b173214a5..b12a97d65 100644
--- a/src/BlockEntities/MobSpawnerEntity.h
+++ b/src/BlockEntities/MobSpawnerEntity.h
@@ -28,41 +28,51 @@ public:
 	// tolua_end
 	
 	cMobSpawnerEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cWorld * a_World);
-	virtual ~cMobSpawnerEntity();
-
-	bool LoadFromJson(const Json::Value & a_Value);
-	virtual void SaveToJson(Json::Value & a_Value) override;
 
+	virtual void SendTo(cClientHandle & a_Client) override;
+	virtual void UsedBy(cPlayer * a_Player) override;
 	virtual bool Tick(float a_Dt, cChunk & a_Chunk) override;
 
 	// tolua_begin
 
-	/** Returns the entity who will be spawn by this mob spawner. */
-	const AString & GetEntityName(void) const { return m_EntityName; }
+	/** Upate the active flag from the mob spawner. This function will called every 5 seconds from the Tick() function. */
+	void UpdateActiveState(void);
+
+	/** Sets the spawn delay to a new random value. */
+	void ResetTimer(void);
+
+	/** Spawns the entity. This function automaticly change the spawn delay! */
+	void SpawnEntity(void);
+
+	/** Returns the entity type who will be spawn by this mob spawner. */
+	cMonster::eType GetEntity(void) const { return m_Entity; }
+
+	/** Sets the entity type who will be spawn by this mob spawner. */
+	void SetEntity(cMonster::eType a_EntityType) { m_Entity = a_EntityType; }
+
+	/** Returns the entity name. (Required by the protocol) */
+	AString GetEntityName(void) const;
+
+	/** Returns the spawn delay. */
+	int GetSpawnDelay(void) const { return m_SpawnDelay; }
+
+	int GetNearbyPlayersNum(void);
+	int GetNearbyEntityNum(cMonster::eType a_EntityType);
 
 	// tolua_end
 
+	bool LoadFromJson(const Json::Value & a_Value);
+	virtual void SaveToJson(Json::Value & a_Value) override;
+
 	static const char * GetClassStatic(void) { return "cMobSpawnerEntity"; }
-	
-	virtual void UsedBy(cPlayer * a_Player) override;
-	virtual void SendTo(cClientHandle &) override {}
 
 private:
 	/** The entity to spawn. */
-	AString m_EntityName;
+	cMonster::eType m_Entity;
 
 	int m_SpawnDelay;
-	int m_MinSpawnDelay;
-	int m_MaxSpawnDelay;
-
-	/** The mob spawner spawns only mobs when the count of nearby entities (without players) is lesser than this number. */
-	short m_MaxNearbyEntities;
-
-	/** The mob spawner spawns only mobs when a player is in the range of the mob spawner. */
-	short m_ActivatingRange;
 
-	/** The range coefficient for spawning entities around. */
-	short m_SpawnRange;
+	bool m_IsActive;
 } ;  // tolua_end
 
 
-- 
cgit v1.2.3