From fec64bb91c03c5e872a8f6fbc1a253f341373072 Mon Sep 17 00:00:00 2001 From: Persson-dev <66266021+Persson-dev@users.noreply.github.com> Date: Wed, 29 Dec 2021 20:28:41 +0100 Subject: Improved farmer AI & Fixed entity loading functions (#5351) * Allow villagers to pickup items * Add farmer villager harvesting * Use of auto keyword * Using for loop to check adjacent crops * Show particules when farmer harvest * Fix area comment * Move constants to header file * Removing unnecessary semicolon * Initialization of CropBlockType variable * Apply 12xx12 suggestion * Fixing area constant size * Refactor bounding box calculation, use vectors. * Add Api documentation * Update lua docs * Rework farmer ai * Fixing lua docs notes * Add missing capitalisation * Add villagers inventory save * Fixing loading entities from disk inconsistencies * Add farmer harvest animation * Fix beetroots grow state Co-authored-by: Alexander Harkness --- src/Mobs/Villager.cpp | 313 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 242 insertions(+), 71 deletions(-) (limited to 'src/Mobs/Villager.cpp') 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(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(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(m_CropsPos) + Vector3d(0.5, 0, 0.5)); + return; } - m_VillagerAction = true; - m_CropsPos = Vector3i(static_cast(GetPosX()) + X - 5, static_cast(GetPosY()) + Y - 3, static_cast(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 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(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(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); -- cgit v1.2.3