summaryrefslogtreecommitdiffstats
path: root/src/Mobs/Villager.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Mobs/Villager.cpp313
1 files changed, 242 insertions, 71 deletions
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);