summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Server/Plugins/APIDump/APIDesc.lua24
-rw-r--r--src/ChunkMap.cpp12
-rw-r--r--src/ChunkMap.h4
-rw-r--r--src/Defines.cpp26
-rw-r--r--src/Defines.h2
-rw-r--r--src/Entities/Pawn.cpp17
-rw-r--r--src/Entities/Pickup.cpp105
-rw-r--r--src/Entities/Pickup.h2
-rw-r--r--src/Entities/Player.cpp2
-rw-r--r--src/Mobs/Villager.cpp313
-rw-r--r--src/Mobs/Villager.h61
-rw-r--r--src/World.cpp4
-rw-r--r--src/World.h2
-rw-r--r--src/WorldStorage/NBTChunkSerializer.cpp3
-rw-r--r--src/WorldStorage/WSSAnvil.cpp52
15 files changed, 479 insertions, 150 deletions
diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua
index e2b0043a1..a6b6a957d 100644
--- a/Server/Plugins/APIDump/APIDesc.lua
+++ b/Server/Plugins/APIDump/APIDesc.lua
@@ -10183,8 +10183,8 @@ a_Player:OpenWindow(Window);
Params =
{
{
- Name = "Player",
- Type = "cPlayer",
+ Name = "Entity",
+ Type = "cEntity",
},
},
Returns =
@@ -10194,7 +10194,7 @@ a_Player:OpenWindow(Window);
Type = "boolean",
},
},
- Notes = "Tries to make the player collect the pickup. Returns true if the pickup was collected, at least partially.",
+ Notes = "Tries to make the entity collect the pickup. Returns true if the pickup was collected, at least partially.",
},
GetAge =
{
@@ -18877,6 +18877,24 @@ end
},
Notes = "Returns true if the specified item type is any kind of a tool (axe, hoe, pickaxe, shovel or FIXME: sword)",
},
+ IsVillagerFood =
+ {
+ IsStatic = true,
+ Params =
+ {
+ {
+ Name = "ItemType",
+ Type = "number",
+ },
+ },
+ Returns =
+ {
+ {
+ Type = "boolean",
+ },
+ },
+ Notes = "Returns true if the specified item type is any kind of a pickable food by a villager (potato, carrot, wheat, bread and any kind of seeds).",
+ }
},
AdditionalInfo =
{
diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp
index 9e39338de..c3139ed9c 100644
--- a/src/ChunkMap.cpp
+++ b/src/ChunkMap.cpp
@@ -439,14 +439,14 @@ void cChunkMap::FastSetBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLET
-void cChunkMap::CollectPickupsByPlayer(cPlayer & a_Player)
+void cChunkMap::CollectPickupsByEntity(cEntity & a_Entity)
{
cCSLock Lock(m_CSChunks);
- auto BoundingBox = a_Player.GetBoundingBox();
+ auto BoundingBox = a_Entity.GetBoundingBox();
BoundingBox.Expand(1, 0.5, 1);
- ForEachEntityInBox(BoundingBox, [&a_Player](cEntity & Entity)
+ ForEachEntityInBox(BoundingBox, [&a_Entity](cEntity & Entity)
{
// Only pickups and projectiles can be picked up:
if (Entity.IsPickup())
@@ -456,11 +456,11 @@ void cChunkMap::CollectPickupsByPlayer(cPlayer & a_Player)
(*itr)->GetUniqueID(), a_Player->GetName().c_str(), SqrDist
);
*/
- static_cast<cPickup &>(Entity).CollectedBy(a_Player);
+ static_cast<cPickup &>(Entity).CollectedBy(a_Entity);
}
- else if (Entity.IsProjectile())
+ else if (Entity.IsProjectile() && a_Entity.IsPlayer())
{
- static_cast<cProjectileEntity &>(Entity).CollectedBy(a_Player);
+ static_cast<cProjectileEntity &>(Entity).CollectedBy(static_cast<cPlayer&>(a_Entity));
}
// The entities will MarkDirty when they Destroy themselves
diff --git a/src/ChunkMap.h b/src/ChunkMap.h
index 611f08cc9..578c49b8a 100644
--- a/src/ChunkMap.h
+++ b/src/ChunkMap.h
@@ -102,8 +102,8 @@ public:
If the chunk is invalid, the operation is ignored silently. */
void FastSetBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta);
- /** Makes the specified player collect all the pickups around them. */
- void CollectPickupsByPlayer(cPlayer & a_Player);
+ /** Makes the specified entity collect all the pickups around them. */
+ void CollectPickupsByEntity(cEntity & a_Entity);
BLOCKTYPE GetBlock (Vector3i a_BlockPos) const;
NIBBLETYPE GetBlockMeta (Vector3i a_BlockPos) const;
diff --git a/src/Defines.cpp b/src/Defines.cpp
index 02b9f28d5..aa5e46995 100644
--- a/src/Defines.cpp
+++ b/src/Defines.cpp
@@ -623,3 +623,29 @@ bool ItemCategory::IsHorseArmor(short a_ItemType)
}
}
}
+
+
+
+
+
+bool ItemCategory::IsVillagerFood(short a_ItemType)
+{
+ switch (a_ItemType)
+ {
+ case E_ITEM_CARROT:
+ case E_ITEM_POTATO:
+ case E_ITEM_BREAD:
+ case E_ITEM_BEETROOT:
+ case E_ITEM_SEEDS:
+ case E_ITEM_BEETROOT_SEEDS:
+ case E_ITEM_WHEAT:
+ {
+ return true;
+ }
+
+ default:
+ {
+ return false;
+ }
+ }
+}
diff --git a/src/Defines.h b/src/Defines.h
index 1dbcdda34..899bc8c2d 100644
--- a/src/Defines.h
+++ b/src/Defines.h
@@ -675,6 +675,8 @@ namespace ItemCategory
bool IsArmor(short a_ItemType);
bool IsHorseArmor(short a_ItemType);
+
+ bool IsVillagerFood(short a_ItemType);
}
// tolua_end
diff --git a/src/Entities/Pawn.cpp b/src/Entities/Pawn.cpp
index a1c3a7610..e227dca6f 100644
--- a/src/Entities/Pawn.cpp
+++ b/src/Entities/Pawn.cpp
@@ -102,6 +102,23 @@ void cPawn::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
return;
}
HandleFalling();
+
+ // Handle item pickup
+ if (m_Health > 0)
+ {
+ if (IsPlayer())
+ {
+ m_World->CollectPickupsByEntity(*this);
+ }
+ else if (IsMob())
+ {
+ cMonster & Mob = static_cast<cMonster &>(*this);
+ if (Mob.CanPickUpLoot())
+ {
+ m_World->CollectPickupsByEntity(*this);
+ }
+ }
+ }
}
diff --git a/src/Entities/Pickup.cpp b/src/Entities/Pickup.cpp
index fd2cc3a4b..bbc3313da 100644
--- a/src/Entities/Pickup.cpp
+++ b/src/Entities/Pickup.cpp
@@ -7,10 +7,12 @@
#include "Pickup.h"
#include "Player.h"
+#include "../Mobs/Villager.h"
#include "../ClientHandle.h"
#include "../World.h"
#include "../Server.h"
#include "../Bindings/PluginManager.h"
+#include "../Registries/Items.h"
#include "../Root.h"
#include "../Chunk.h"
@@ -209,7 +211,7 @@ bool cPickup::DoTakeDamage(TakeDamageInfo & a_TDI)
-bool cPickup::CollectedBy(cPlayer & a_Dest)
+bool cPickup::CollectedBy(cEntity & a_Dest)
{
if (m_bCollected)
{
@@ -217,6 +219,12 @@ bool cPickup::CollectedBy(cPlayer & a_Dest)
return false; // It's already collected!
}
+ // This type of entity can't pickup items
+ if (!a_Dest.IsPawn())
+ {
+ return false;
+ }
+
// Two seconds if player created the pickup (vomiting), half a second if anything else
if (m_Timer < (m_bIsPlayerCreated ? std::chrono::seconds(2) : std::chrono::milliseconds(500)))
{
@@ -224,43 +232,84 @@ bool cPickup::CollectedBy(cPlayer & a_Dest)
return false; // Not old enough
}
- // If the player is a spectator, he cannot collect anything
- if (a_Dest.IsGameModeSpectator())
+ // Checking for villagers
+ if (!a_Dest.IsPlayer() && a_Dest.IsMob())
{
- return false;
- }
- if (cRoot::Get()->GetPluginManager()->CallHookCollectingPickup(a_Dest, *this))
- {
- // LOG("Pickup %d cannot be collected by \"%s\", because a plugin has said no.", m_UniqueID, a_Dest->GetName().c_str());
- return false;
- }
+ auto & Mob = static_cast<cMonster &>(a_Dest);
+ if (Mob.GetMobType() == mtVillager)
+ {
+ // Villagers only pickup food
+ if (!ItemCategory::IsVillagerFood(m_Item.m_ItemType))
+ {
+ return false;
+ }
+
+ auto & Villager = static_cast<cVillager &>(Mob);
+ int NumAdded = Villager.GetInventory().AddItem(m_Item);
+ if (NumAdded > 0)
+ {
+ m_Item.m_ItemCount -= NumAdded;
+ m_World->BroadcastCollectEntity(*this, a_Dest, static_cast<unsigned>(NumAdded));
+
+ // Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;)
+ m_World->BroadcastSoundEffect("entity.item.pickup", GetPosition(), 0.3f, (1.2f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64));
+ if (m_Item.m_ItemCount <= 0)
+ {
+ // All of the pickup has been collected, schedule the pickup for destroying
+ m_bCollected = true;
+ }
+ m_Timer = std::chrono::milliseconds(0);
+ return true;
+ }
+ // Pickup cannot be collected because the entity has not enough space
+ return false;
+ }
- int NumAdded = a_Dest.GetInventory().AddItem(m_Item);
- if (NumAdded > 0)
+ }
+ else if (a_Dest.IsPlayer())
{
- // Check achievements
- switch (m_Item.m_ItemType)
+
+ auto & Player = static_cast<cPlayer &>(a_Dest);
+
+ // If the player is a spectator, he cannot collect anything
+ if (Player.IsGameModeSpectator())
{
- case E_BLOCK_LOG: a_Dest.AwardAchievement(CustomStatistic::AchMineWood); break;
- case E_ITEM_LEATHER: a_Dest.AwardAchievement(CustomStatistic::AchKillCow); break;
- case E_ITEM_DIAMOND: a_Dest.AwardAchievement(CustomStatistic::AchDiamonds); break;
- case E_ITEM_BLAZE_ROD: a_Dest.AwardAchievement(CustomStatistic::AchBlazeRod); break;
- default: break;
+ return false;
}
- m_Item.m_ItemCount -= NumAdded;
- m_World->BroadcastCollectEntity(*this, a_Dest, static_cast<unsigned>(NumAdded));
+ if (cRoot::Get()->GetPluginManager()->CallHookCollectingPickup(Player, *this))
+ {
+ // LOG("Pickup %d cannot be collected by \"%s\", because a plugin has said no.", m_UniqueID, a_Dest->GetName().c_str());
+ return false;
+ }
- // Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;)
- m_World->BroadcastSoundEffect("entity.item.pickup", GetPosition(), 0.3f, (1.2f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64));
- if (m_Item.m_ItemCount <= 0)
+ int NumAdded = Player.GetInventory().AddItem(m_Item);
+ if (NumAdded > 0)
{
- // All of the pickup has been collected, schedule the pickup for destroying
- m_bCollected = true;
+ // Check achievements
+ switch (m_Item.m_ItemType)
+ {
+ case E_BLOCK_LOG: Player.AwardAchievement(CustomStatistic::AchMineWood); break;
+ case E_ITEM_LEATHER: Player.AwardAchievement(CustomStatistic::AchKillCow); break;
+ case E_ITEM_DIAMOND: Player.AwardAchievement(CustomStatistic::AchDiamonds); break;
+ case E_ITEM_BLAZE_ROD: Player.AwardAchievement(CustomStatistic::AchBlazeRod); break;
+ default: break;
+ }
+
+ m_Item.m_ItemCount -= NumAdded;
+ m_World->BroadcastCollectEntity(*this, a_Dest, static_cast<unsigned>(NumAdded));
+
+ // Also send the "pop" sound effect with a somewhat random pitch (fast-random using EntityID ;)
+ m_World->BroadcastSoundEffect("entity.item.pickup", GetPosition(), 0.3f, (1.2f + (static_cast<float>((GetUniqueID() * 23) % 32)) / 64));
+ if (m_Item.m_ItemCount <= 0)
+ {
+ // All of the pickup has been collected, schedule the pickup for destroying
+ m_bCollected = true;
+ }
+ m_Timer = std::chrono::milliseconds(0);
+ return true;
}
- m_Timer = std::chrono::milliseconds(0);
- return true;
}
// LOG("Pickup %d cannot be collected by \"%s\", because there's no space in the inventory.", a_Dest->GetName().c_str(), m_UniqueID);
diff --git a/src/Entities/Pickup.h b/src/Entities/Pickup.h
index 73540a64e..c995055ff 100644
--- a/src/Entities/Pickup.h
+++ b/src/Entities/Pickup.h
@@ -33,7 +33,7 @@ public: // tolua_export
virtual void SpawnOn(cClientHandle & a_ClientHandle) override;
- bool CollectedBy(cPlayer & a_Dest); // tolua_export
+ bool CollectedBy(cEntity & a_Dest); // tolua_export
virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override;
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index 1d3bad306..b0ab94874 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -3213,8 +3213,6 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
if (m_Health > 0) // make sure player is alive
{
- m_World->CollectPickupsByPlayer(*this);
-
if ((m_EatingFinishTick >= 0_tick) && (m_EatingFinishTick <= m_World->GetWorldAge()))
{
FinishEating();
diff --git a/src/Mobs/Villager.cpp b/src/Mobs/Villager.cpp
index 46dd613f1..aa5409e4d 100644
--- a/src/Mobs/Villager.cpp
+++ b/src/Mobs/Villager.cpp
@@ -15,7 +15,8 @@ cVillager::cVillager(eVillagerType VillagerType) :
Super("Villager", mtVillager, "entity.villager.hurt", "entity.villager.death", "entity.villager.ambient", 0.6f, 1.95f),
m_ActionCountDown(-1),
m_Type(VillagerType),
- m_VillagerAction(false)
+ m_FarmerAction(faIdling),
+ m_Inventory(8, 1)
{
}
@@ -60,46 +61,12 @@ void cVillager::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
return;
}
- if (m_ActionCountDown > -1)
- {
- m_ActionCountDown--;
- if (m_ActionCountDown == 0)
- {
- switch (m_Type)
- {
- case vtFarmer:
- {
- HandleFarmerPlaceCrops();
- }
- }
- }
- return;
- }
-
- if (m_VillagerAction)
- {
- switch (m_Type)
- {
- case vtFarmer:
- {
- HandleFarmerTryHarvestCrops();
- }
- }
- m_VillagerAction = false;
- return;
- }
-
- // Don't always try to do a special action. Each tick has 1% to do a special action.
- if (GetRandomProvider().RandBool(0.99))
- {
- return;
- }
-
switch (m_Type)
{
case vtFarmer:
{
- HandleFarmerPrepareFarmCrops();
+ TickFarmer();
+ break;
}
}
}
@@ -130,50 +97,111 @@ void cVillager::KilledBy(TakeDamageInfo & a_TDI)
////////////////////////////////////////////////////////////////////////////////
// Farmer functions:
-void cVillager::HandleFarmerPrepareFarmCrops()
+void cVillager::TickFarmer()
{
+
+ // Don't harvest crops if you must not
if (!m_World->VillagersShouldHarvestCrops())
{
return;
}
- cBlockArea Surrounding;
+ // This is to prevent undefined behaviors
+ if (m_FinalDestination.y <= 0)
+ {
+ return;
+ }
- // Read a 11x7x11 area:
+ if (!IsIdling())
+ {
+ // Forcing the farmer to go to work spots.
+ MoveToPosition(static_cast<Vector3d>(m_CropsPos) + Vector3d(0.5, 0, 0.5));
+
+ // Forcing the farmer to look at the work spots.
+ Vector3d Direction = (m_FinalDestination - (GetPosition() + Vector3d(0, 1.6, 0))); // We get the direction from the eyes of the farmer to the work spot.
+ Direction.Normalize();
+ SetPitch(std::asin(-Direction.y) / M_PI * 180);
+ }
+
+ // Updating the timer
+ if (m_ActionCountDown > -1)
+ {
+ m_ActionCountDown--;
+ }
+
+ // Searching for work in blocks where the farmer goes.
+ if (IsHarvestable(m_FinalDestination.Floor()))
+ {
+ m_CropsPos = m_FinalDestination.Floor();
+ m_FarmerAction = faHarvesting;
+ HandleFarmerTryHarvestCrops();
+ return;
+ }
+ else if (IsPlantable(m_FinalDestination.Floor()) && CanPlantCrops())
+ {
+ m_CropsPos = m_FinalDestination.Floor();
+ m_FarmerAction = faPlanting;
+ HandleFarmerTryPlaceCrops();
+ return;
+ }
+ else
+ {
+ m_FarmerAction = faIdling; // Returning to idling.
+ }
+
+
+ // Don't always try to do a special action. Each tick has 10% to do a special action.
+ if (GetRandomProvider().RandBool(FARMER_SPECIAL_ACTION_CHANCE))
+ {
+ ScanAreaForWork();
+ }
+
+}
+
+
+
+
+
+void cVillager::ScanAreaForWork()
+{
+
+ auto Pos = GetPosition().Floor();
+ auto MinPos = Pos - FARMER_SCAN_CROPS_DIST;
+ auto MaxPos = Pos + FARMER_SCAN_CROPS_DIST;
+
+ // Read area to be checked for crops.
+ cBlockArea Surrounding;
Surrounding.Read(
*m_World,
- FloorC(GetPosX()) - 5,
- FloorC(GetPosX()) + 6,
- FloorC(GetPosY()) - 3,
- FloorC(GetPosY()) + 4,
- FloorC(GetPosZ()) - 5,
- FloorC(GetPosZ()) + 6
+ MinPos, MaxPos
);
- for (int I = 0; I < 5; I++)
+ for (int I = 0; I < FARMER_RANDOM_TICK_SPEED; I++)
{
- for (int Y = 0; Y < 6; Y++)
+ for (int Y = MinPos.y; Y <= MaxPos.y; Y++)
{
// Pick random coordinates and check for crops.
- int X = m_World->GetTickRandomNumber(11);
- int Z = m_World->GetTickRandomNumber(11);
+ Vector3i CandidatePos(MinPos.x + m_World->GetTickRandomNumber(MaxPos.x - MinPos.x - 1), Y, MinPos.z + m_World->GetTickRandomNumber(MaxPos.z - MinPos.z - 1));
- // A villager can't farm this.
- if (!IsBlockFarmable(Surrounding.GetRelBlockType(X, Y, Z)))
+ // A villager can harvest this.
+ if (IsHarvestable(CandidatePos))
{
- continue;
+ m_CropsPos = CandidatePos;
+ m_FarmerAction = faHarvesting;
+ MoveToPosition(static_cast<Vector3d>(m_CropsPos) + Vector3d(0.5, 0, 0.5));
+ return;
}
- if (Surrounding.GetRelBlockMeta(X, Y, Z) != 0x7)
+ // A villager can plant this.
+ else if (IsPlantable(CandidatePos) && CanPlantCrops())
{
- continue;
+ m_CropsPos = CandidatePos;
+ m_FarmerAction = faHarvesting;
+ MoveToPosition(static_cast<Vector3d>(m_CropsPos) + Vector3d(0.5, 0, 0.5));
+ return;
}
- m_VillagerAction = true;
- m_CropsPos = Vector3i(static_cast<int>(GetPosX()) + X - 5, static_cast<int>(GetPosY()) + Y - 3, static_cast<int>(GetPosZ()) + Z - 5);
- MoveToPosition(Vector3d(m_CropsPos.x + 0.5, m_CropsPos.y + 0.0, m_CropsPos.z + 0.5));
- return;
- } // for Y loop.
- } // Repeat the procces 5 times.
+ } // for Y
+ } // Repeat the proccess according to the random tick speed.
}
@@ -182,15 +210,22 @@ void cVillager::HandleFarmerPrepareFarmCrops()
void cVillager::HandleFarmerTryHarvestCrops()
{
- // Harvest the crops if the villager isn't moving and if the crops are closer then 2 blocks.
- if (!m_PathfinderActivated && (GetPosition() - m_CropsPos).Length() < 2)
+ if (m_ActionCountDown > 0)
+ {
+ // The farmer is still on cooldown
+ return;
+ }
+
+ // Harvest the crops if it is closer than 1 block.
+ if ((GetPosition() - m_CropsPos).Length() < 1)
{
// Check if the blocks didn't change while the villager was walking to the coordinates.
- BLOCKTYPE CropBlock = m_World->GetBlock(m_CropsPos);
- if (IsBlockFarmable(CropBlock) && m_World->GetBlockMeta(m_CropsPos) == 0x7)
+ if (IsHarvestable(m_CropsPos))
{
+ m_World->BroadcastSoundParticleEffect(EffectID::PARTICLE_BLOCK_BREAK, m_CropsPos, m_World->GetBlock(m_CropsPos));
m_World->DropBlockAsPickups(m_CropsPos, this, nullptr);
- m_ActionCountDown = 20;
+ // Applying 0.5 second cooldown.
+ m_ActionCountDown = 10;
}
}
}
@@ -199,12 +234,113 @@ void cVillager::HandleFarmerTryHarvestCrops()
-void cVillager::HandleFarmerPlaceCrops()
+void cVillager::CheckForNearbyCrops()
{
+
+ // Search for adjacent crops
+
+ constexpr std::array<Vector3i, 4> Directions = { Vector3i{0, 0, -1}, {0, 0, 1}, {1, 0, 0}, {-1, 0, 0} };
+
+ for (Vector3i Direction : Directions)
+ {
+ if (IsHarvestable(m_CropsPos + Direction))
+ {
+ m_CropsPos += Direction;
+ m_FarmerAction = faHarvesting;
+ MoveToPosition(static_cast<Vector3d>(m_CropsPos) + Vector3d(0.5, 0, 0.5));
+ return;
+ }
+ else if (IsPlantable(m_CropsPos + Direction) && CanPlantCrops())
+ {
+ m_CropsPos += Direction;
+ m_FarmerAction = faPlanting;
+ MoveToPosition(static_cast<Vector3d>(m_CropsPos) + Vector3d(0.5, 0, 0.5));
+ return;
+ }
+
+ }
+
+ // There is no more work to do around the previous crops.
+ m_FarmerAction = faIdling;
+}
+
+
+
+
+
+void cVillager::HandleFarmerTryPlaceCrops()
+{
+
+ if ((GetPosition() - m_CropsPos).Length() > 1)
+ {
+ // The farmer is still to far from the final destination
+ return;
+ }
+
+ if (m_ActionCountDown > 0)
+ {
+ // The farmer is still on cooldown
+ return;
+ }
+
// Check if there is still farmland at the spot where the crops were.
- if (m_World->GetBlock(m_CropsPos.addedY(-1)) == E_BLOCK_FARMLAND)
+ if (IsPlantable(m_CropsPos))
{
- m_World->SetBlock(m_CropsPos, E_BLOCK_CROPS, 0);
+ // Finding the item to use to plant a crop
+ int TargetSlot = -1;
+ BLOCKTYPE CropBlockType = E_BLOCK_AIR;
+
+ for (int I = 0; I < m_Inventory.GetWidth() && TargetSlot < 0; I++)
+ {
+ const cItem & Slot = m_Inventory.GetSlot(I);
+ switch (Slot.m_ItemType)
+ {
+ case E_ITEM_SEEDS:
+ {
+ TargetSlot = I;
+ CropBlockType = E_BLOCK_CROPS;
+ break;
+ }
+
+ case E_ITEM_BEETROOT_SEEDS:
+ {
+ TargetSlot = I;
+ CropBlockType = E_BLOCK_BEETROOTS;
+ break;
+ }
+
+ case E_ITEM_POTATO:
+ {
+ TargetSlot = I;
+ CropBlockType = E_BLOCK_POTATOES;
+ break;
+ }
+
+ case E_ITEM_CARROT:
+ {
+ TargetSlot = I;
+ CropBlockType = E_BLOCK_CARROTS;
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ // Removing item from villager inventory
+ m_Inventory.RemoveOneItem(TargetSlot);
+
+ // Placing crop block
+ m_World->SetBlock(m_CropsPos, CropBlockType, 0);
+
+ // Applying 1 second cooldown
+ m_ActionCountDown = 20;
+
+ // Try to do the same with adjacent crops.
+ CheckForNearbyCrops();
}
}
@@ -212,16 +348,33 @@ void cVillager::HandleFarmerPlaceCrops()
-bool cVillager::IsBlockFarmable(BLOCKTYPE a_BlockType)
+bool cVillager::CanPlantCrops()
+{
+ return m_Inventory.HasItems(cItem(E_ITEM_SEEDS)) ||
+ m_Inventory.HasItems(cItem(E_ITEM_BEETROOT_SEEDS)) ||
+ m_Inventory.HasItems(cItem(E_ITEM_POTATO)) ||
+ m_Inventory.HasItems(cItem(E_ITEM_CARROT));
+}
+
+
+
+
+
+bool cVillager::IsBlockFarmable(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
{
switch (a_BlockType)
{
case E_BLOCK_BEETROOTS:
+ {
+ // The crop must have fully grown up.
+ return a_BlockMeta == 0x03;
+ }
case E_BLOCK_CROPS:
case E_BLOCK_POTATOES:
case E_BLOCK_CARROTS:
{
- return true;
+ // The crop must have fully grown up.
+ return a_BlockMeta == 0x07;
}
default: return false;
}
@@ -231,6 +384,24 @@ bool cVillager::IsBlockFarmable(BLOCKTYPE a_BlockType)
+bool cVillager::IsHarvestable(Vector3i a_CropsPos)
+{
+ return IsBlockFarmable(m_World->GetBlock(a_CropsPos), m_World->GetBlockMeta(a_CropsPos));
+}
+
+
+
+
+
+bool cVillager::IsPlantable(Vector3i a_CropsPos)
+{
+ return (m_World->GetBlock(a_CropsPos.addedY(-1)) == E_BLOCK_FARMLAND) && (m_World->GetBlock(a_CropsPos) == E_BLOCK_AIR);
+}
+
+
+
+
+
cVillager::eVillagerType cVillager::GetRandomProfession()
{
int Profession = GetRandomProvider().RandInt(cVillager::eVillagerType::vtMax - 1);
diff --git a/src/Mobs/Villager.h b/src/Mobs/Villager.h
index 4ac06765c..ef1c9e70f 100644
--- a/src/Mobs/Villager.h
+++ b/src/Mobs/Villager.h
@@ -3,6 +3,7 @@
#include "PassiveMonster.h"
#include "../Blocks/ChunkInterface.h"
+#include "../Inventory.h"
@@ -38,30 +39,68 @@ public:
virtual void KilledBy (TakeDamageInfo & a_TDI) override;
// cVillager functions
- /** return true if the given blocktype are: crops, potatoes or carrots. */
- bool IsBlockFarmable(BLOCKTYPE a_BlockType);
+ /** Returns the villager hidden inventory (8 slots). */
+ cItemGrid & GetInventory(void) { return m_Inventory; }
+ const cItemGrid & GetInventory(void) const { return m_Inventory; }
+
+ /** Returns true if the given blocktype are: crops, potatoes or carrots and they have full grown up. */
+ bool IsBlockFarmable(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta);
+
+ /** Returns true if the block at the given location is a fully grown up crop. */
+ bool IsHarvestable(Vector3i a_CropsPos);
+
+ /** Returns true if seeds can be planted at a given location. */
+ bool IsPlantable(Vector3i a_CropsPos);
// Farmer functions
- /** Searches in a 11x7x11 area for crops. If it found some it will navigate to them. */
- void HandleFarmerPrepareFarmCrops();
+ enum eFarmerAction
+ {
+ faIdling,
+ faPlanting,
+ faHarvesting,
+ } ;
+
+ static const int FARMER_RANDOM_TICK_SPEED = 5;
+ /** With 10% chance, it takes about 20 seconds to find a spot. */
+ static constexpr double FARMER_SPECIAL_ACTION_CHANCE = 0.1;
+ /** This distance from the Villager makes for a 31x3x31 area. */
+ static constexpr Vector3i FARMER_SCAN_CROPS_DIST {15, 1, 15};
- /** Looks if the farmer has reached it's destination, and if it's still crops and the destination is closer then 2 blocks it will harvest them. */
+ /** Tick function for farmers. */
+ void TickFarmer();
+
+ /** Searches in a 31x3x31 area to harvest crops or spaces to plant crops. If it found some it will navigate to them. */
+ void ScanAreaForWork();
+
+ /** Looks if the farmer has reached it's destination, and if it's still crops and the destination is closer then 1 block it will harvest them. */
void HandleFarmerTryHarvestCrops();
- /** Replaces the crops he harvested. */
- void HandleFarmerPlaceCrops();
+ /** Looks if the farmer has reached it's destination, and if it's still non obstructed farmland and the destination is closer then 1 block it will plant crops. */
+ void HandleFarmerTryPlaceCrops();
+
+ /** Checking for harvesting or planting nearby crops */
+ void CheckForNearbyCrops();
+
+ /** Returns whether the farmer has crops in his inventory to plant. */
+ bool CanPlantCrops();
+
+ /** Returns whether the farmer is not working. */
+ bool IsIdling()
+ {
+ return m_FarmerAction == faIdling;
+ }
// Get and set functions.
- int GetVilType(void) const { return m_Type; }
- Vector3i GetCropsPos(void) const { return m_CropsPos; }
- bool DoesHaveActionActivated(void) const { return m_VillagerAction; }
+ int GetVilType(void) const { return m_Type; }
+ eFarmerAction GetFarmerAction(void) const { return m_FarmerAction; }
private:
int m_ActionCountDown;
int m_Type;
- bool m_VillagerAction;
+ eFarmerAction m_FarmerAction;
Vector3i m_CropsPos;
+ cItemGrid m_Inventory;
} ;
diff --git a/src/World.cpp b/src/World.cpp
index 94fc0ba00..8ed179050 100644
--- a/src/World.cpp
+++ b/src/World.cpp
@@ -2241,9 +2241,9 @@ void cWorld::QueueUnloadUnusedChunks(void)
-void cWorld::CollectPickupsByPlayer(cPlayer & a_Player)
+void cWorld::CollectPickupsByEntity(cEntity & a_Entity)
{
- m_ChunkMap.CollectPickupsByPlayer(a_Player);
+ m_ChunkMap.CollectPickupsByEntity(a_Entity);
}
diff --git a/src/World.h b/src/World.h
index eab6422f4..84c184819 100644
--- a/src/World.h
+++ b/src/World.h
@@ -234,7 +234,7 @@ public:
/** Queues a task to unload unused chunks onto the tick thread. The prefferred way of unloading. */
void QueueUnloadUnusedChunks(void); // tolua_export
- void CollectPickupsByPlayer(cPlayer & a_Player);
+ void CollectPickupsByEntity(cEntity & a_Entity);
/** Calls the callback for each player in the list; returns true if all players processed, false if the callback aborted by returning true */
virtual bool ForEachPlayer(cPlayerListCallback a_Callback) override; // >> EXPORTED IN MANUALBINDINGS <<
diff --git a/src/WorldStorage/NBTChunkSerializer.cpp b/src/WorldStorage/NBTChunkSerializer.cpp
index e96acccef..dde6c299c 100644
--- a/src/WorldStorage/NBTChunkSerializer.cpp
+++ b/src/WorldStorage/NBTChunkSerializer.cpp
@@ -893,6 +893,9 @@ public:
const cVillager *Villager = static_cast<const cVillager *>(a_Monster);
mWriter.AddInt("Profession", Villager->GetVilType());
mWriter.AddInt("Age", Villager->GetAge());
+ mWriter.BeginList("Inventory", TAG_Compound);
+ AddItemGrid(Villager->GetInventory());
+ mWriter.EndList();
break;
}
case mtWither:
diff --git a/src/WorldStorage/WSSAnvil.cpp b/src/WorldStorage/WSSAnvil.cpp
index dbbd03daf..dfefb74d3 100644
--- a/src/WorldStorage/WSSAnvil.cpp
+++ b/src/WorldStorage/WSSAnvil.cpp
@@ -1663,29 +1663,29 @@ void cWSSAnvil::LoadEntityFromNBT(cEntityList & a_Entities, const cParsedNBT & a
case mtHoglin: return LoadHoglinFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
case mtHusk: return LoadHuskFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
case mtIllusioner: return LoadIllusionerFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtIronGolem: return LoadVillagerFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtLlama: return LoadIronGolemFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtMagmaCube: return LoadLlamaFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtMooshroom: return LoadMagmaCubeFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtMule: return LoadMooshroomFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtOcelot: return LoadMuleFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtPanda: return LoadOcelotFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtParrot: return LoadPandaFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtPhantom: return LoadParrotFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtPig: return LoadPhantomFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtPiglin: return LoadPigFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtPiglinBrute: return LoadPiglinFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtPillager: return LoadPiglinBruteFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtPolarBear: return LoadPillagerFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtPufferfish: return LoadPolarBearFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtRabbit: return LoadPufferfishFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtRavager: return LoadRabbitFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtSalmon: return LoadRavagerFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtSheep: return LoadSalmonFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtShulker: return LoadSheepFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtSilverfish: return LoadShulkerFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtSkeleton: return LoadSilverfishFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
- case mtSkeletonHorse: return LoadSkeletonFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtIronGolem: return LoadIronGolemFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtLlama: return LoadLlamaFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtMagmaCube: return LoadMagmaCubeFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtMooshroom: return LoadMooshroomFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtMule: return LoadMuleFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtOcelot: return LoadOcelotFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtPanda: return LoadPandaFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtParrot: return LoadParrotFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtPhantom: return LoadPhantomFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtPig: return LoadPigFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtPiglin: return LoadPiglinFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtPiglinBrute: return LoadPiglinBruteFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtPillager: return LoadPillagerFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtPolarBear: return LoadPolarBearFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtPufferfish: return LoadPufferfishFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtRabbit: return LoadRabbitFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtRavager: return LoadRavagerFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtSalmon: return LoadSalmonFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtSheep: return LoadSheepFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtShulker: return LoadShulkerFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtSilverfish: return LoadSilverfishFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtSkeleton: return LoadSkeletonFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
+ case mtSkeletonHorse: return LoadSkeletonHorseFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
case mtSlime: return LoadSlimeFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
case mtSnowGolem: return LoadSnowGolemFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
case mtSpider: return LoadSpiderFromNBT(a_Entities, a_NBT, a_EntityTagIdx);
@@ -3270,6 +3270,12 @@ void cWSSAnvil::LoadVillagerFromNBT(cEntityList & a_Entities, const cParsedNBT &
Monster->SetAge(Age);
}
+ int InventoryIdx = a_NBT.FindChildByName(a_TagIdx, "Inventory");
+ if (InventoryIdx > 0)
+ {
+ LoadItemGridFromNBT(Monster->GetInventory(), a_NBT, InventoryIdx);
+ }
+
a_Entities.emplace_back(std::move(Monster));
}