diff options
author | daniel0916 <theschokolps@gmail.com> | 2014-04-07 20:12:17 +0200 |
---|---|---|
committer | daniel0916 <theschokolps@gmail.com> | 2014-04-07 20:12:17 +0200 |
commit | 2e9754ac1cf0537c12ab7974cf55c451c0724540 (patch) | |
tree | 713c5b8c8f22f77893b30b9c8cefca4a7c491483 /src/Mobs | |
parent | Fixed merge conflict (diff) | |
parent | Fixed some more minor issues with the redstone simulator. (diff) | |
download | cuberite-2e9754ac1cf0537c12ab7974cf55c451c0724540.tar cuberite-2e9754ac1cf0537c12ab7974cf55c451c0724540.tar.gz cuberite-2e9754ac1cf0537c12ab7974cf55c451c0724540.tar.bz2 cuberite-2e9754ac1cf0537c12ab7974cf55c451c0724540.tar.lz cuberite-2e9754ac1cf0537c12ab7974cf55c451c0724540.tar.xz cuberite-2e9754ac1cf0537c12ab7974cf55c451c0724540.tar.zst cuberite-2e9754ac1cf0537c12ab7974cf55c451c0724540.zip |
Diffstat (limited to 'src/Mobs')
45 files changed, 1271 insertions, 370 deletions
diff --git a/src/Mobs/AggressiveMonster.cpp b/src/Mobs/AggressiveMonster.cpp index cc7e7da2b..0901f85a9 100644 --- a/src/Mobs/AggressiveMonster.cpp +++ b/src/Mobs/AggressiveMonster.cpp @@ -4,17 +4,15 @@ #include "AggressiveMonster.h" #include "../World.h" -#include "../Vector3f.h" #include "../Entities/Player.h" -#include "../MersenneTwister.h" +#include "../Tracer.h" cAggressiveMonster::cAggressiveMonster(const AString & a_ConfigName, eType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height) : - super(a_ConfigName, a_MobType, a_SoundHurt, a_SoundDeath, a_Width, a_Height), - m_ChaseTime(999999) + super(a_ConfigName, a_MobType, a_SoundHurt, a_SoundDeath, a_Width, a_Height) { m_EMPersonality = AGGRESSIVE; } @@ -27,32 +25,23 @@ cAggressiveMonster::cAggressiveMonster(const AString & a_ConfigName, eType a_Mob void cAggressiveMonster::InStateChasing(float a_Dt) { super::InStateChasing(a_Dt); - m_ChaseTime += a_Dt; + if (m_Target != NULL) { if (m_Target->IsPlayer()) { - cPlayer * Player = (cPlayer *) m_Target; - if (Player->IsGameModeCreative()) + if (((cPlayer *)m_Target)->IsGameModeCreative()) { m_EMState = IDLE; return; } } - Vector3f Pos = Vector3f( GetPosition() ); - Vector3f Their = Vector3f( m_Target->GetPosition() ); - if ((Their - Pos).Length() <= m_AttackRange) + if (((float)m_FinalDestination.x != (float)m_Target->GetPosX()) || ((float)m_FinalDestination.z != (float)m_Target->GetPosZ())) { - Attack(a_Dt); + MoveToPosition(m_Target->GetPosition()); } - MoveToPosition(Their + Vector3f(0, 0.65f, 0)); } - else if (m_ChaseTime > 5.f) - { - m_ChaseTime = 0; - m_EMState = IDLE; - } } @@ -61,8 +50,11 @@ void cAggressiveMonster::InStateChasing(float a_Dt) void cAggressiveMonster::EventSeePlayer(cEntity * a_Entity) { - super::EventSeePlayer(a_Entity); - m_EMState = CHASING; + if (!((cPlayer *)a_Entity)->IsGameModeCreative()) + { + super::EventSeePlayer(a_Entity); + m_EMState = CHASING; + } } @@ -73,25 +65,44 @@ void cAggressiveMonster::Tick(float a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); - m_SeePlayerInterval += a_Dt; + if (m_EMState == CHASING) + { + CheckEventLostPlayer(); + } + else + { + CheckEventSeePlayer(); + } + + if (m_Target == NULL) + return; - if (m_SeePlayerInterval > 1) + cTracer LineOfSight(GetWorld()); + Vector3d AttackDirection(m_Target->GetPosition() - GetPosition()); + + if (ReachedFinalDestination() && !LineOfSight.Trace(GetPosition(), AttackDirection, (int)AttackDirection.Length())) { - int rem = m_World->GetTickRandomNumber(3) + 1; // Check most of the time but miss occasionally + // Attack if reached destination, target isn't null, and have a clear line of sight to target (so won't attack through walls) + Attack(a_Dt / 1000); + } +} - m_SeePlayerInterval = 0.0; - if (rem >= 2) - { - if (m_EMState == CHASING) - { - CheckEventLostPlayer(); - } - else - { - CheckEventSeePlayer(); - } - } + + + + +void cAggressiveMonster::Attack(float a_Dt) +{ + m_AttackInterval += a_Dt * m_AttackRate; + + if ((m_Target != NULL) && (m_AttackInterval > 3.0)) + { + // Setting this higher gives us more wiggle room for attackrate + m_AttackInterval = 0.0; + m_Target->TakeDamage(dtMobAttack, this, m_AttackDamage, 0); } } + + diff --git a/src/Mobs/AggressiveMonster.h b/src/Mobs/AggressiveMonster.h index 5a0d93f3d..152260f95 100644 --- a/src/Mobs/AggressiveMonster.h +++ b/src/Mobs/AggressiveMonster.h @@ -13,16 +13,15 @@ class cAggressiveMonster : typedef cMonster super; public: + cAggressiveMonster(const AString & a_ConfigName, eType a_MobType, const AString & a_SoundHurt, const AString & a_SoundDeath, double a_Width, double a_Height); virtual void Tick (float a_Dt, cChunk & a_Chunk) override; virtual void InStateChasing(float a_Dt) override; virtual void EventSeePlayer(cEntity *) override; + virtual void Attack(float a_Dt); - -protected: - float m_ChaseTime; } ; diff --git a/src/Mobs/Bat.cpp b/src/Mobs/Bat.cpp index b9c82996b..1417ddd9e 100644 --- a/src/Mobs/Bat.cpp +++ b/src/Mobs/Bat.cpp @@ -2,7 +2,7 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Bat.h" -#include "../Vector3d.h" +#include "../Vector3.h" #include "../Chunk.h" diff --git a/src/Mobs/Blaze.cpp b/src/Mobs/Blaze.cpp index f9c05b17a..84ff8929b 100644 --- a/src/Mobs/Blaze.cpp +++ b/src/Mobs/Blaze.cpp @@ -19,7 +19,11 @@ cBlaze::cBlaze(void) : void cBlaze::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 1, E_ITEM_BLAZE_ROD); + if ((a_Killer != NULL) && (a_Killer->IsPlayer() || a_Killer->IsA("cWolf"))) + { + int LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + AddRandomDropItem(a_Drops, 0, 1 + LootingLevel, E_ITEM_BLAZE_ROD); + } } @@ -49,4 +53,4 @@ void cBlaze::Attack(float a_Dt) m_AttackInterval = 0.0; // ToDo: Shoot 3 fireballs instead of 1. } -}
\ No newline at end of file +} diff --git a/src/Mobs/Blaze.h b/src/Mobs/Blaze.h index cdb3a1306..5970451c7 100644 --- a/src/Mobs/Blaze.h +++ b/src/Mobs/Blaze.h @@ -20,7 +20,3 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; virtual void Attack(float a_Dt) override; } ; - - - - diff --git a/src/Mobs/Cavespider.cpp b/src/Mobs/Cavespider.cpp index aba1ff9f5..94e93283d 100644 --- a/src/Mobs/Cavespider.cpp +++ b/src/Mobs/Cavespider.cpp @@ -31,8 +31,16 @@ void cCavespider::Tick(float a_Dt, cChunk & a_Chunk) void cCavespider::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 2, E_ITEM_STRING); - AddRandomDropItem(a_Drops, 0, 1, E_ITEM_SPIDER_EYE); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_STRING); + if ((a_Killer != NULL) && (a_Killer->IsPlayer() || a_Killer->IsA("cWolf"))) + { + AddRandomUncommonDropItem(a_Drops, 33.0f, E_ITEM_SPIDER_EYE); + } } diff --git a/src/Mobs/Chicken.cpp b/src/Mobs/Chicken.cpp index 087fd088a..f7e44238f 100644 --- a/src/Mobs/Chicken.cpp +++ b/src/Mobs/Chicken.cpp @@ -9,7 +9,6 @@ - cChicken::cChicken(void) : super("Chicken", mtChicken, "mob.chicken.hurt", "mob.chicken.hurt", 0.3, 0.4), m_EggDropTimer(0) @@ -49,8 +48,13 @@ void cChicken::Tick(float a_Dt, cChunk & a_Chunk) void cChicken::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 2, E_ITEM_FEATHER); - a_Drops.push_back(cItem(IsOnFire() ? E_ITEM_COOKED_CHICKEN : E_ITEM_RAW_CHICKEN, 1)); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_FEATHER); + AddRandomDropItem(a_Drops, 1, 1, IsOnFire() ? E_ITEM_COOKED_CHICKEN : E_ITEM_RAW_CHICKEN); } diff --git a/src/Mobs/Chicken.h b/src/Mobs/Chicken.h index 979c4d8a0..a4c1d6b9e 100644 --- a/src/Mobs/Chicken.h +++ b/src/Mobs/Chicken.h @@ -19,8 +19,9 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; virtual void Tick(float a_Dt, cChunk & a_Chunk) override; -private: + virtual const cItem GetFollowedItem(void) const override { return cItem(E_ITEM_SEEDS); } +private: int m_EggDropTimer; } ; diff --git a/src/Mobs/Cow.cpp b/src/Mobs/Cow.cpp index 9eb74dac2..9914df6b5 100644 --- a/src/Mobs/Cow.cpp +++ b/src/Mobs/Cow.cpp @@ -21,8 +21,13 @@ cCow::cCow(void) : void cCow::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 2, E_ITEM_LEATHER); - AddRandomDropItem(a_Drops, 1, 3, IsOnFire() ? E_ITEM_STEAK : E_ITEM_RAW_BEEF); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_LEATHER); + AddRandomDropItem(a_Drops, 1, 3 + LootingLevel, IsOnFire() ? E_ITEM_STEAK : E_ITEM_RAW_BEEF); } @@ -41,5 +46,3 @@ void cCow::OnRightClicked(cPlayer & a_Player) } } - - diff --git a/src/Mobs/Cow.h b/src/Mobs/Cow.h index 0391d4a31..973171ab5 100644 --- a/src/Mobs/Cow.h +++ b/src/Mobs/Cow.h @@ -19,6 +19,9 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; virtual void OnRightClicked(cPlayer & a_Player) override; + + virtual const cItem GetFollowedItem(void) const override { return cItem(E_ITEM_WHEAT); } + } ; diff --git a/src/Mobs/Creeper.cpp b/src/Mobs/Creeper.cpp index 4e11ae13e..3471b4cf1 100644 --- a/src/Mobs/Creeper.cpp +++ b/src/Mobs/Creeper.cpp @@ -3,6 +3,8 @@ #include "Creeper.h" #include "../World.h" +#include "../Entities/ProjectileEntity.h" +#include "../Entities/Player.h" @@ -11,7 +13,9 @@ cCreeper::cCreeper(void) : super("Creeper", mtCreeper, "mob.creeper.say", "mob.creeper.say", 0.6, 1.8), m_bIsBlowing(false), - m_bIsCharged(false) + m_bIsCharged(false), + m_BurnedWithFlintAndSteel(false), + m_ExplodingTimer(0) { } @@ -19,11 +23,52 @@ cCreeper::cCreeper(void) : +void cCreeper::Tick(float a_Dt, cChunk & a_Chunk) +{ + super::Tick(a_Dt, a_Chunk); + + if (!ReachedFinalDestination() && !m_BurnedWithFlintAndSteel) + { + m_ExplodingTimer = 0; + m_bIsBlowing = false; + m_World->BroadcastEntityMetadata(*this); + } + else + { + if (m_bIsBlowing) + { + m_ExplodingTimer += 1; + } + + if (m_ExplodingTimer == 30) + { + m_World->DoExplosionAt((m_bIsCharged ? 5 : 3), GetPosX(), GetPosY(), GetPosZ(), false, esMonster, this); + Destroy(); + } + } +} + + + + + void cCreeper::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 2, E_ITEM_GUNPOWDER); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_GUNPOWDER); - // TODO Check if killed by a skeleton, then drop random music disk + if ((a_Killer != NULL) && (a_Killer->IsProjectile())) + { + if (((cMonster *)((cProjectileEntity *)a_Killer)->GetCreator())->GetMobType() == mtSkeleton) + { + // 12 music discs. TickRand starts from 0, so range = 11. Disk IDs start at 2256, so add that. There. + AddRandomDropItem(a_Drops, 1, 1, (short)m_World->GetTickRandomNumber(11) + 2256); + } + } } @@ -45,3 +90,35 @@ void cCreeper::DoTakeDamage(TakeDamageInfo & a_TDI) + +void cCreeper::Attack(float a_Dt) +{ + UNUSED(a_Dt); + + if (!m_bIsBlowing) + { + m_World->BroadcastSoundEffect("game.tnt.primed", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 1.f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + m_bIsBlowing = true; + m_World->BroadcastEntityMetadata(*this); + } +} + + + + + +void cCreeper::OnRightClicked(cPlayer & a_Player) +{ + if ((a_Player.GetEquippedItem().m_ItemType == E_ITEM_FLINT_AND_STEEL)) + { + if (!a_Player.IsGameModeCreative()) + { + a_Player.UseEquippedItem(); + } + m_World->BroadcastSoundEffect("game.tnt.primed", (int)GetPosX() * 8, (int)GetPosY() * 8, (int)GetPosZ() * 8, 1.f, (float)(0.75 + ((float)((GetUniqueID() * 23) % 32)) / 64)); + m_bIsBlowing = true; + m_World->BroadcastEntityMetadata(*this); + m_BurnedWithFlintAndSteel = true; + } +} + diff --git a/src/Mobs/Creeper.h b/src/Mobs/Creeper.h index c3d4edeae..9abca369b 100644 --- a/src/Mobs/Creeper.h +++ b/src/Mobs/Creeper.h @@ -19,13 +19,17 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; virtual void DoTakeDamage(TakeDamageInfo & a_TDI) override; + virtual void Attack(float a_Dt) override; + virtual void Tick(float a_Dt, cChunk & a_Chunk) override; + virtual void OnRightClicked(cPlayer & a_Player) override; bool IsBlowing(void) const {return m_bIsBlowing; } bool IsCharged(void) const {return m_bIsCharged; } private: - bool m_bIsBlowing, m_bIsCharged; + bool m_bIsBlowing, m_bIsCharged, m_BurnedWithFlintAndSteel; + int m_ExplodingTimer; } ; diff --git a/src/Mobs/Enderman.cpp b/src/Mobs/Enderman.cpp index a784131e4..becc99a86 100644 --- a/src/Mobs/Enderman.cpp +++ b/src/Mobs/Enderman.cpp @@ -21,7 +21,12 @@ cEnderman::cEnderman(void) : void cEnderman::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 1, E_ITEM_ENDER_PEARL); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + AddRandomDropItem(a_Drops, 0, 1 + LootingLevel, E_ITEM_ENDER_PEARL); } diff --git a/src/Mobs/Ghast.cpp b/src/Mobs/Ghast.cpp index 96a29b2d8..fe18f5e76 100644 --- a/src/Mobs/Ghast.cpp +++ b/src/Mobs/Ghast.cpp @@ -18,8 +18,13 @@ cGhast::cGhast(void) : void cGhast::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 2, E_ITEM_GUNPOWDER); - AddRandomDropItem(a_Drops, 0, 1, E_ITEM_GHAST_TEAR); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_GUNPOWDER); + AddRandomDropItem(a_Drops, 0, 1 + LootingLevel, E_ITEM_GHAST_TEAR); } diff --git a/src/Mobs/Horse.cpp b/src/Mobs/Horse.cpp index bb9a4e3f6..9d130301f 100644 --- a/src/Mobs/Horse.cpp +++ b/src/Mobs/Horse.cpp @@ -140,7 +140,12 @@ void cHorse::OnRightClicked(cPlayer & a_Player) void cHorse::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 2, E_ITEM_LEATHER); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_LEATHER); if (m_bIsSaddled) { a_Drops.push_back(cItem(E_ITEM_SADDLE, 1)); diff --git a/src/Mobs/IronGolem.cpp b/src/Mobs/IronGolem.cpp index 47c961098..dae4615e4 100644 --- a/src/Mobs/IronGolem.cpp +++ b/src/Mobs/IronGolem.cpp @@ -18,7 +18,9 @@ cIronGolem::cIronGolem(void) : void cIronGolem::GetDrops(cItems & a_Drops, cEntity * a_Killer) { + UNUSED(a_Killer); AddRandomDropItem(a_Drops, 0, 5, E_ITEM_IRON); + AddRandomDropItem(a_Drops, 0, 2, E_BLOCK_FLOWER); } diff --git a/src/Mobs/IronGolem.h b/src/Mobs/IronGolem.h index d49ff4cab..41c60438c 100644 --- a/src/Mobs/IronGolem.h +++ b/src/Mobs/IronGolem.h @@ -18,6 +18,10 @@ public: CLASS_PROTODEF(cIronGolem); virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; + + // Iron golems do not drown + virtual void HandleAir(void) override {} + virtual void SetSwimState(cChunk & a_Chunk) override {} } ; diff --git a/src/Mobs/Magmacube.cpp b/src/Mobs/Magmacube.cpp index 86447ff6b..05405f082 100644 --- a/src/Mobs/Magmacube.cpp +++ b/src/Mobs/Magmacube.cpp @@ -19,7 +19,11 @@ cMagmaCube::cMagmaCube(int a_Size) : void cMagmaCube::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 1, E_ITEM_MAGMA_CREAM); + UNUSED(a_Killer); + if (GetSize() > 1) + { + AddRandomUncommonDropItem(a_Drops, 25.0f, E_ITEM_MAGMA_CREAM); + } } diff --git a/src/Mobs/Monster.cpp b/src/Mobs/Monster.cpp index 98b6c1d28..aa6071515 100644 --- a/src/Mobs/Monster.cpp +++ b/src/Mobs/Monster.cpp @@ -8,14 +8,9 @@ #include "../World.h" #include "../Entities/Player.h" #include "../Entities/ExpOrb.h" -#include "../Defines.h" #include "../MonsterConfig.h" #include "../MersenneTwister.h" -#include "../Vector3f.h" -#include "../Vector3i.h" -#include "../Vector3d.h" -#include "../Tracer.h" #include "../Chunk.h" #include "../FastRandom.h" @@ -74,21 +69,25 @@ cMonster::cMonster(const AString & a_ConfigName, eType a_MobType, const AString : super(etMonster, a_Width, a_Height) , m_EMState(IDLE) , m_EMPersonality(AGGRESSIVE) - , m_SightDistance(25) , m_Target(NULL) - , m_AttackRate(3) - , m_IdleInterval(0) , m_bMovingToDestination(false) - , m_DestinationTime( 0 ) - , m_DestroyTimer( 0 ) - , m_Jump(0) + , m_LastGroundHeight(POSY_TOINT) + , m_IdleInterval(0) + , m_DestroyTimer(0) , m_MobType(a_MobType) , m_SoundHurt(a_SoundHurt) , m_SoundDeath(a_SoundDeath) - , m_SeePlayerInterval (0) - , m_AttackDamage(1.0f) - , m_AttackRange(2.0f) + , m_AttackRate(3) + , m_AttackDamage(1) + , m_AttackRange(2) , m_AttackInterval(0) + , m_SightDistance(25) + , m_DropChanceWeapon(0.085f) + , m_DropChanceHelmet(0.085f) + , m_DropChanceChestplate(0.085f) + , m_DropChanceLeggings(0.085f) + , m_DropChanceBoots(0.085f) + , m_CanPickUpLoot(true) , m_BurnsInDaylight(false) { if (!a_ConfigName.empty()) @@ -110,11 +109,119 @@ void cMonster::SpawnOn(cClientHandle & a_Client) -void cMonster::MoveToPosition( const Vector3f & a_Position ) +void cMonster::TickPathFinding() +{ + const int PosX = (int)floor(GetPosX()); + const int PosY = (int)floor(GetPosY()); + const int PosZ = (int)floor(GetPosZ()); + + m_FinalDestination.y = (double)FindFirstNonAirBlockPosition(m_FinalDestination.x, m_FinalDestination.z); + + std::vector<Vector3d> m_PotentialCoordinates; + m_TraversedCoordinates.push_back(Vector3i(PosX, PosY, PosZ)); + + static const struct // Define which directions to try to move to + { + int x, z; + } gCrossCoords[] = + { + { 1, 0}, + {-1, 0}, + { 0, 1}, + { 0,-1}, + } ; + + if ((PosY - 1 < 0) || (PosY + 2 > cChunkDef::Height) /* PosY + 1 will never be true if PosY + 2 is not */) + { + // Too low/high, can't really do anything + FinishPathFinding(); + return; + } + + for (size_t i = 0; i < ARRAYCOUNT(gCrossCoords); i++) + { + if (IsCoordinateInTraversedList(Vector3i(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ))) + { + continue; + } + + BLOCKTYPE BlockAtY = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ); + BLOCKTYPE BlockAtYP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 1, gCrossCoords[i].z + PosZ); + BLOCKTYPE BlockAtYPP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 2, gCrossCoords[i].z + PosZ); + BLOCKTYPE BlockAtYM = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY - 1, gCrossCoords[i].z + PosZ); + + if ((!cBlockInfo::IsSolid(BlockAtY)) && (!cBlockInfo::IsSolid(BlockAtYP)) && (!IsBlockLava(BlockAtYM)) && (BlockAtY != E_BLOCK_FENCE) && (BlockAtY != E_BLOCK_FENCE_GATE)) + { + m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY, gCrossCoords[i].z + PosZ)); + } + else if ((cBlockInfo::IsSolid(BlockAtY)) && (!cBlockInfo::IsSolid(BlockAtYP)) && (!cBlockInfo::IsSolid(BlockAtYPP)) && (!IsBlockLava(BlockAtYM)) && (BlockAtY != E_BLOCK_FENCE) && (BlockAtY != E_BLOCK_FENCE_GATE)) + { + m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY + 1, gCrossCoords[i].z + PosZ)); + } + } + + if (!m_PotentialCoordinates.empty()) + { + Vector3f ShortestCoords = m_PotentialCoordinates.front(); + for (std::vector<Vector3d>::const_iterator itr = m_PotentialCoordinates.begin(); itr != m_PotentialCoordinates.end(); ++itr) + { + Vector3f Distance = m_FinalDestination - ShortestCoords; + Vector3f Distance2 = m_FinalDestination - *itr; + if (Distance.SqrLength() > Distance2.SqrLength()) + { + ShortestCoords = *itr; + } + } + + m_Destination = ShortestCoords; + m_Destination.z += 0.5f; + m_Destination.x += 0.5f; + } + else + { + FinishPathFinding(); + } +} + + + + + +void cMonster::MoveToPosition(const Vector3f & a_Position) { + FinishPathFinding(); + + m_FinalDestination = a_Position; m_bMovingToDestination = true; + TickPathFinding(); +} - m_Destination = a_Position; + + + + +void cMonster::MoveToPosition(const Vector3d & a_Position) +{ + FinishPathFinding(); + + m_FinalDestination = a_Position; + m_bMovingToDestination = true; + TickPathFinding(); +} + + + +bool cMonster::IsCoordinateInTraversedList(Vector3i a_Coords) +{ + for (std::vector<Vector3i>::const_iterator itr = m_TraversedCoordinates.begin(); itr != m_TraversedCoordinates.end(); ++itr) + { + if (itr->Equals(a_Coords)) + { + return true; + } + } + + return false; } @@ -123,9 +230,10 @@ void cMonster::MoveToPosition( const Vector3f & a_Position ) bool cMonster::ReachedDestination() { - Vector3f Distance = (m_Destination) - GetPosition(); - if( Distance.SqrLength() < 2.f ) + if ((m_Destination - GetPosition()).Length() < 0.5f) + { return true; + } return false; } @@ -133,6 +241,19 @@ bool cMonster::ReachedDestination() +bool cMonster::ReachedFinalDestination() +{ + if ((GetPosition() - m_FinalDestination).Length() <= m_AttackRange) + { + return true; + } + + return false; +} + + + + void cMonster::Tick(float a_Dt, cChunk & a_Chunk) { @@ -149,25 +270,35 @@ void cMonster::Tick(float a_Dt, cChunk & a_Chunk) return; } + if ((m_Target != NULL) && m_Target->IsDestroyed()) + m_Target = NULL; + // Burning in daylight HandleDaylightBurning(a_Chunk); - - HandlePhysics(a_Dt,a_Chunk); - BroadcastMovementUpdate(); a_Dt /= 1000; if (m_bMovingToDestination) { - Vector3f Pos( GetPosition() ); - Vector3f Distance = m_Destination - Pos; - if( !ReachedDestination() ) + if (m_bOnGround) + { + m_Destination.y = FindFirstNonAirBlockPosition(m_Destination.x, m_Destination.z); + + if (DoesPosYRequireJump((int)floor(m_Destination.y))) + { + m_bOnGround = false; + AddPosY(1.5); // Jump!! + } + } + + Vector3f Distance = m_Destination - GetPosition(); + if(!ReachedDestination() && !ReachedFinalDestination()) // If we haven't reached any sort of destination, move { Distance.y = 0; Distance.Normalize(); - Distance *= 3; - SetSpeedX( Distance.x ); - SetSpeedZ( Distance.z ); + Distance *= 5; + SetSpeedX(Distance.x); + SetSpeedZ(Distance.z); if (m_EMState == ESCAPING) { //Runs Faster when escaping :D otherwise they just walk away @@ -177,40 +308,19 @@ void cMonster::Tick(float a_Dt, cChunk & a_Chunk) } else { - m_bMovingToDestination = false; - } - - if( GetSpeed().SqrLength() > 0.f ) - { - if( m_bOnGround ) + if (ReachedFinalDestination()) // If we have reached the ultimate, final destination, stop pathfinding and attack if appropriate + { + FinishPathFinding(); + } + else { - Vector3f NormSpeed = Vector3f(GetSpeed()).NormalizeCopy(); - Vector3f NextBlock = Vector3f( GetPosition() ) + NormSpeed; - int NextHeight; - if (!m_World->TryGetHeight((int)NextBlock.x, (int)NextBlock.z, NextHeight)) - { - // The chunk at NextBlock is not loaded - return; - } - if( NextHeight > (GetPosY() - 1.0) && (NextHeight - GetPosY()) < 2.5 ) - { - m_bOnGround = false; - SetSpeedY(5.f); // Jump!! - } + TickPathFinding(); // We have reached the next point in our path, calculate another point } } } - Vector3d Distance = m_Destination - GetPosition(); - if (Distance.SqrLength() > 0.1f) - { - double Rotation, Pitch; - Distance.Normalize(); - VectorToEuler( Distance.x, Distance.y, Distance.z, Rotation, Pitch ); - SetHeadYaw (Rotation); - SetYaw( Rotation ); - SetPitch( -Pitch ); - } + SetPitchAndYawFromDestination(); + HandleFalling(); switch (m_EMState) { @@ -219,21 +329,113 @@ void cMonster::Tick(float a_Dt, cChunk & a_Chunk) // If enemy passive we ignore checks for player visibility InStateIdle(a_Dt); break; - } - + } case CHASING: { // If we do not see a player anymore skip chasing action InStateChasing(a_Dt); break; - } - + } case ESCAPING: { InStateEscaping(a_Dt); break; } } // switch (m_EMState) + + BroadcastMovementUpdate(); +} + + + + +void cMonster::SetPitchAndYawFromDestination() +{ + Vector3d FinalDestination = m_FinalDestination; + if (m_Target != NULL) + { + if (m_Target->IsPlayer()) + { + FinalDestination.y = ((cPlayer *)m_Target)->GetStance(); + } + else + { + FinalDestination.y = GetHeight(); + } + } + + Vector3d Distance = FinalDestination - GetPosition(); + if (Distance.SqrLength() > 0.1f) + { + { + double Rotation, Pitch; + Distance.Normalize(); + VectorToEuler(Distance.x, Distance.y, Distance.z, Rotation, Pitch); + SetHeadYaw(Rotation); + SetPitch(-Pitch); + } + + { + Vector3d BodyDistance = m_Destination - GetPosition(); + double Rotation, Pitch; + Distance.Normalize(); + VectorToEuler(BodyDistance.x, BodyDistance.y, BodyDistance.z, Rotation, Pitch); + SetYaw(Rotation); + } + } +} + + + + +void cMonster::HandleFalling() +{ + if (m_bOnGround) + { + int Damage = (m_LastGroundHeight - POSY_TOINT) - 3; + + if (Damage > 0) + { + TakeDamage(dtFalling, NULL, Damage, Damage, 0); + + // Fall particles + GetWorld()->BroadcastSoundParticleEffect(2006, POSX_TOINT, POSY_TOINT - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */); + } + + m_LastGroundHeight = (int)floor(GetPosY()); + } +} + + + + +int cMonster::FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ) +{ + int PosY = (int)floor(GetPosY()); + + if (PosY < 0) + PosY = 0; + else if (PosY > cChunkDef::Height) + PosY = cChunkDef::Height; + + if (!cBlockInfo::IsSolid(m_World->GetBlock((int)floor(a_PosX), PosY, (int)floor(a_PosZ)))) + { + while (!cBlockInfo::IsSolid(m_World->GetBlock((int)floor(a_PosX), PosY, (int)floor(a_PosZ))) && (PosY > 0)) + { + PosY--; + } + + return PosY + 1; + } + else + { + while (cBlockInfo::IsSolid(m_World->GetBlock((int)floor(a_PosX), PosY, (int)floor(a_PosZ))) && (PosY < cChunkDef::Height)) + { + PosY++; + } + + return PosY; + } } @@ -244,11 +446,13 @@ void cMonster::Tick(float a_Dt, cChunk & a_Chunk) void cMonster::DoTakeDamage(TakeDamageInfo & a_TDI) { super::DoTakeDamage(a_TDI); - if((m_SoundHurt != "") && (m_Health > 0)) m_World->BroadcastSoundEffect(m_SoundHurt, (int)(GetPosX() * 8), (int)(GetPosY() * 8), (int)(GetPosZ() * 8), 1.0f, 0.8f); + + if((m_SoundHurt != "") && (m_Health > 0)) + m_World->BroadcastSoundEffect(m_SoundHurt, (int)(GetPosX() * 8), (int)(GetPosY() * 8), (int)(GetPosZ() * 8), 1.0f, 0.8f); + if (a_TDI.Attacker != NULL) { m_Target = a_TDI.Attacker; - AddReference(m_Target); } } @@ -330,55 +534,12 @@ void cMonster::KilledBy(cEntity * a_Killer) -//----State Logic - -const char *cMonster::GetState() -{ - switch(m_EMState) - { - case IDLE: return "Idle"; - case ATTACKING: return "Attacking"; - case CHASING: return "Chasing"; - default: return "Unknown"; - } -} - - - - - -// for debugging -void cMonster::SetState(const AString & a_State) -{ - if (a_State.compare("Idle") == 0) - { - m_EMState = IDLE; - } - else if (a_State.compare("Attacking") == 0) - { - m_EMState = ATTACKING; - } - else if (a_State.compare("Chasing") == 0) - { - m_EMState = CHASING; - } - else - { - LOGD("cMonster::SetState(): Invalid state"); - ASSERT(!"Invalid state"); - } -} - - - - - //Checks to see if EventSeePlayer should be fired //monster sez: Do I see the player void cMonster::CheckEventSeePlayer(void) { // TODO: Rewrite this to use cWorld's DoWithPlayers() - cPlayer * Closest = FindClosestPlayer(); + cPlayer * Closest = m_World->FindClosestPlayer(GetPosition(), (float)m_SightDistance, false); if (Closest != NULL) { @@ -391,14 +552,10 @@ void cMonster::CheckEventSeePlayer(void) void cMonster::CheckEventLostPlayer(void) -{ - Vector3f pos; - cTracer LineOfSight(GetWorld()); - +{ if (m_Target != NULL) { - pos = m_Target->GetPosition(); - if ((pos - GetPosition()).Length() > m_SightDistance || LineOfSight.Trace(GetPosition(),(pos - GetPosition()), (int)(pos - GetPosition()).Length())) + if ((m_Target->GetPosition() - GetPosition()).Length() > m_SightDistance) { EventLosePlayer(); } @@ -418,7 +575,6 @@ void cMonster::CheckEventLostPlayer(void) void cMonster::EventSeePlayer(cEntity * a_SeenPlayer) { m_Target = a_SeenPlayer; - AddReference(m_Target); } @@ -427,7 +583,6 @@ void cMonster::EventSeePlayer(cEntity * a_SeenPlayer) void cMonster::EventLosePlayer(void) { - Dereference(m_Target); m_Target = NULL; m_EMState = IDLE; } @@ -436,28 +591,35 @@ void cMonster::EventLosePlayer(void) -// What to do if in Idle State void cMonster::InStateIdle(float a_Dt) { + if (m_bMovingToDestination) + { + return; // Still getting there + } + m_IdleInterval += a_Dt; + if (m_IdleInterval > 1) { - // at this interval the results are predictable + // At this interval the results are predictable int rem = m_World->GetTickRandomNumber(6) + 1; - // LOGD("Moving: int: %3.3f rem: %i",idle_interval,rem); - m_IdleInterval -= 1; // So nothing gets dropped when the server hangs for a few seconds - Vector3f Dist; - Dist.x = (float)(m_World->GetTickRandomNumber(10) - 5); - Dist.z = (float)(m_World->GetTickRandomNumber(10) - 5); + m_IdleInterval -= 1; // So nothing gets dropped when the server hangs for a few seconds + + Vector3d Dist; + Dist.x = (double)m_World->GetTickRandomNumber(10) - 5; + Dist.z = (double)m_World->GetTickRandomNumber(10) - 5; + if ((Dist.SqrLength() > 2) && (rem >= 3)) { - m_Destination.x = (float)(GetPosX() + Dist.x); - m_Destination.z = (float)(GetPosZ() + Dist.z); - int PosY; - if (m_World->TryGetHeight((int)m_Destination.x, (int)m_Destination.z, PosY)) + Vector3d Destination(GetPosX() + Dist.x, 0, GetPosZ() + Dist.z); + + int NextHeight = FindFirstNonAirBlockPosition(Destination.x, Destination.z); + + if (IsNextYPosReachable(NextHeight)) { - m_Destination.y = (float)PosY + 1.2f; - MoveToPosition(m_Destination); + Destination.y = NextHeight; + MoveToPosition(Destination); } } } @@ -500,33 +662,6 @@ void cMonster::InStateEscaping(float a_Dt) -// Do attack here -// a_Dt is passed so we can set attack rate -void cMonster::Attack(float a_Dt) -{ - m_AttackInterval += a_Dt * m_AttackRate; - if ((m_Target != NULL) && (m_AttackInterval > 3.0)) - { - // Setting this higher gives us more wiggle room for attackrate - m_AttackInterval = 0.0; - ((cPawn *)m_Target)->TakeDamage(*this); - } -} - - - - - -// Checks for Players close by and if they are visible return the closest -cPlayer * cMonster::FindClosestPlayer(void) -{ - return m_World->FindClosestPlayer(GetPosition(), m_SightDistance); -} - - - - - void cMonster::GetMonsterConfig(const AString & a_Name) { cRoot::Get()->GetMonsterConfig()->AssignAttributes(this, a_Name); @@ -536,42 +671,6 @@ void cMonster::GetMonsterConfig(const AString & a_Name) -void cMonster::SetAttackRate(int ar) -{ - m_AttackRate = (float)ar; -} - - - - - -void cMonster::SetAttackRange(float ar) -{ - m_AttackRange = ar; -} - - - - - -void cMonster::SetAttackDamage(float ad) -{ - m_AttackDamage = ad; -} - - - - - -void cMonster::SetSightDistance(float sd) -{ - m_SightDistance = sd; -} - - - - - AString cMonster::MobTypeToString(cMonster::eType a_MobType) { // Mob types aren't sorted, so we need to search linearly: @@ -635,6 +734,8 @@ cMonster::eType cMonster::StringToMobType(const AString & a_Name) cMonster::eFamily cMonster::FamilyFromType(eType a_Type) { + // Passive-agressive mobs are counted in mob spawning code as passive + switch (a_Type) { case mtBat: return mfAmbient; @@ -659,6 +760,7 @@ cMonster::eFamily cMonster::FamilyFromType(eType a_Type) case mtSquid: return mfWater; case mtVillager: return mfPassive; case mtWitch: return mfHostile; + case mtWither: return mfHostile; case mtWolf: return mfHostile; case mtZombie: return mfHostile; case mtZombiePigman: return mfHostile; @@ -699,7 +801,7 @@ cMonster * cMonster::NewMonsterFromType(cMonster::eType a_MobType) case mtMagmaCube: case mtSlime: { - toReturn = new cSlime (Random.NextInt(2) + 1); + toReturn = new cSlime(Random.NextInt(2) + 1); break; } case mtSkeleton: @@ -787,6 +889,76 @@ void cMonster::AddRandomDropItem(cItems & a_Drops, unsigned int a_Min, unsigned +void cMonster::AddRandomUncommonDropItem(cItems & a_Drops, float a_Chance, short a_Item, short a_ItemHealth) +{ + MTRand r1; + int Count = r1.randInt() % 1000; + if (Count < (a_Chance * 10)) + { + a_Drops.push_back(cItem(a_Item, 1, a_ItemHealth)); + } +} + + + + + +void cMonster::AddRandomRareDropItem(cItems & a_Drops, cItems & a_Items, short a_LootingLevel) +{ + MTRand r1; + int Count = r1.randInt() % 200; + if (Count < (5 + a_LootingLevel)) + { + int Rare = r1.randInt() % a_Items.Size(); + a_Drops.push_back(a_Items.at(Rare)); + } +} + + + + + +void cMonster::AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel) +{ + MTRand r1; + if (r1.randInt() % 200 < ((m_DropChanceHelmet * 200) + (a_LootingLevel * 2))) + { + if (!GetEquippedHelmet().IsEmpty()) a_Drops.push_back(GetEquippedHelmet()); + } + + if (r1.randInt() % 200 < ((m_DropChanceChestplate * 200) + (a_LootingLevel * 2))) + { + if (!GetEquippedChestplate().IsEmpty()) a_Drops.push_back(GetEquippedChestplate()); + } + + if (r1.randInt() % 200 < ((m_DropChanceLeggings * 200) + (a_LootingLevel * 2))) + { + if (!GetEquippedLeggings().IsEmpty()) a_Drops.push_back(GetEquippedLeggings()); + } + + if (r1.randInt() % 200 < ((m_DropChanceBoots * 200) + (a_LootingLevel * 2))) + { + if (!GetEquippedBoots().IsEmpty()) a_Drops.push_back(GetEquippedBoots()); + } +} + + + + + +void cMonster::AddRandomWeaponDropItem(cItems & a_Drops, short a_LootingLevel) +{ + MTRand r1; + if (r1.randInt() % 200 < ((m_DropChanceWeapon * 200) + (a_LootingLevel * 2))) + { + if (!GetEquippedWeapon().IsEmpty()) a_Drops.push_back(GetEquippedWeapon()); + } +} + + + + + void cMonster::HandleDaylightBurning(cChunk & a_Chunk) { if (!m_BurnsInDaylight) @@ -803,6 +975,13 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk) int RelX = (int)floor(GetPosX()) - GetChunkX() * cChunkDef::Width; int RelZ = (int)floor(GetPosZ()) - GetChunkZ() * cChunkDef::Width; + + if (!a_Chunk.IsLightValid()) + { + m_World->QueueLightChunk(GetChunkX(), GetChunkZ()); + return; + } + if ( (a_Chunk.GetSkyLight(RelX, RelY, RelZ) == 15) && // In the daylight (a_Chunk.GetBlock(RelX, RelY, RelZ) != E_BLOCK_SOULSAND) && // Not on soulsand diff --git a/src/Mobs/Monster.h b/src/Mobs/Monster.h index dafb33574..776426a0d 100644 --- a/src/Mobs/Monster.h +++ b/src/Mobs/Monster.h @@ -5,12 +5,12 @@ #include "../Defines.h" #include "../BlockID.h" #include "../Item.h" +#include "../Enchantments.h" -class Vector3f; class cClientHandle; class cWorld; @@ -74,8 +74,6 @@ public: enum MState{ATTACKING, IDLE, CHASING, ESCAPING} m_EMState; enum MPersonality{PASSIVE,AGGRESSIVE,COWARDLY} m_EMPersonality; - float m_SightDistance; - /** Creates the mob object. * If a_ConfigName is not empty, the configuration is loaded using GetMonsterConfig() * a_MobType is the type of the mob (also used in the protocol ( http://wiki.vg/Entities#Mobs , 2012_12_22)) @@ -94,20 +92,16 @@ public: virtual void KilledBy(cEntity * a_Killer) override; virtual void MoveToPosition(const Vector3f & a_Position); + virtual void MoveToPosition(const Vector3d & a_Position); // tolua_export virtual bool ReachedDestination(void); // tolua_begin eType GetMobType(void) const {return m_MobType; } eFamily GetMobFamily(void) const; // tolua_end - - - const char * GetState(); - void SetState(const AString & str); virtual void CheckEventSeePlayer(void); virtual void EventSeePlayer(cEntity * a_Player); - virtual cPlayer * FindClosestPlayer(); // non static is easier. also virtual so other mobs can implement their own searching algo /// Reads the monster configuration for the specified monster name and assigns it to this object. void GetMonsterConfig(const AString & a_Name); @@ -119,13 +113,24 @@ public: virtual void InStateChasing (float a_Dt); virtual void InStateEscaping(float a_Dt); - virtual void Attack(float a_Dt); + int GetAttackRate() { return (int)m_AttackRate; } + void SetAttackRate(float a_AttackRate) { m_AttackRate = a_AttackRate; } + void SetAttackRange(int a_AttackRange) { m_AttackRange = a_AttackRange; } + void SetAttackDamage(int a_AttackDamage) { m_AttackDamage = a_AttackDamage; } + void SetSightDistance(int a_SightDistance) { m_SightDistance = a_SightDistance; } - int GetAttackRate(){return (int)m_AttackRate;} - void SetAttackRate(int ar); - void SetAttackRange(float ar); - void SetAttackDamage(float ad); - void SetSightDistance(float sd); + float GetDropChanceWeapon() { return m_DropChanceWeapon; } + float GetDropChanceHelmet() { return m_DropChanceHelmet; } + float GetDropChanceChestplate() { return m_DropChanceChestplate; } + float GetDropChanceLeggings() { return m_DropChanceLeggings; } + float GetDropChanceBoots() { return m_DropChanceBoots; } + bool CanPickUpLoot() { return m_CanPickUpLoot; } + void SetDropChanceWeapon(float a_DropChanceWeapon) { m_DropChanceWeapon = a_DropChanceWeapon; } + void SetDropChanceHelmet(float a_DropChanceHelmet) { m_DropChanceHelmet = a_DropChanceHelmet; } + void SetDropChanceChestplate(float a_DropChanceChestplate) { m_DropChanceChestplate = a_DropChanceChestplate; } + void SetDropChanceLeggings(float a_DropChanceLeggings) { m_DropChanceLeggings = a_DropChanceLeggings; } + void SetDropChanceBoots(float a_DropChanceBoots) { m_DropChanceBoots = a_DropChanceBoots; } + void SetCanPickUpLoot(bool a_CanPickUpLoot) { m_CanPickUpLoot = a_CanPickUpLoot; } /// Sets whether the mob burns in daylight. Only evaluated at next burn-decision tick void SetBurnsInDaylight(bool a_BurnsInDaylight) { m_BurnsInDaylight = a_BurnsInDaylight; } @@ -159,34 +164,101 @@ public: protected: - cEntity * m_Target; - float m_AttackRate; - float m_IdleInterval; + /* ======= PATHFINDING ======= */ - Vector3f m_Destination; + /** A pointer to the entity this mobile is aiming to reach */ + cEntity * m_Target; + /** Coordinates of the next position that should be reached */ + Vector3d m_Destination; + /** Coordinates for the ultimate, final destination. */ + Vector3d m_FinalDestination; + /** Returns if the ultimate, final destination has been reached */ + bool ReachedFinalDestination(void); + + /** Stores if mobile is currently moving towards the ultimate, final destination */ bool m_bMovingToDestination; - bool m_bPassiveAggressive; + /** Finds the first non-air block position (not the highest, as cWorld::GetHeight does) + If current Y is nonsolid, goes down to try to find a solid block, then returns that + 1 + If current Y is solid, goes up to find first nonsolid block, and returns that */ + int FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ); + /** Returns if a monster can actually reach a given height by jumping or walking */ + inline bool IsNextYPosReachable(int a_PosY) + { + return ( + (a_PosY <= (int)floor(GetPosY())) || + DoesPosYRequireJump(a_PosY) + ); + } + /** Returns if a monster can reach a given height by jumping */ + inline bool DoesPosYRequireJump(int a_PosY) + { + return ((a_PosY > (int)floor(GetPosY())) && (a_PosY == (int)floor(GetPosY()) + 1)); + } + + /** A semi-temporary list to store the traversed coordinates during active pathfinding so we don't visit them again */ + std::vector<Vector3i> m_TraversedCoordinates; + /** Returns if coordinate is in the traversed list */ + bool IsCoordinateInTraversedList(Vector3i a_Coords); + + /** Finds the next place to go + This is based on the ultimate, final destination and the current position, as well as the traversed coordinates, and any environmental hazards */ + void TickPathFinding(void); + /** Finishes a pathfinding task, be it due to failure or something else */ + inline void FinishPathFinding(void) + { + m_TraversedCoordinates.clear(); + m_bMovingToDestination = false; + } + /** Sets the body yaw and head yaw/pitch based on next/ultimate destinations */ + void SetPitchAndYawFromDestination(void); + + /* =========================== */ + /* ========= FALLING ========= */ - float m_DestinationTime; + virtual void HandleFalling(void); + int m_LastGroundHeight; + /* =========================== */ + + float m_IdleInterval; float m_DestroyTimer; - float m_Jump; eType m_MobType; AString m_SoundHurt; AString m_SoundDeath; - float m_SeePlayerInterval; - float m_AttackDamage; - float m_AttackRange; + float m_AttackRate; + int m_AttackDamage; + int m_AttackRange; float m_AttackInterval; + int m_SightDistance; + + float m_DropChanceWeapon; + float m_DropChanceHelmet; + float m_DropChanceChestplate; + float m_DropChanceLeggings; + float m_DropChanceBoots; + bool m_CanPickUpLoot; + void HandleDaylightBurning(cChunk & a_Chunk); bool m_BurnsInDaylight; - void AddRandomDropItem(cItems & a_Drops, unsigned int a_Min, unsigned int a_Max, short a_Item, short a_ItemHealth = 0); + /** Adds a random number of a_Item between a_Min and a_Max to itemdrops a_Drops*/ + void AddRandomDropItem(cItems & a_Drops, unsigned int a_Min, unsigned int a_Max, short a_Item, short a_ItemHealth = 0); + + /** Adds a item a_Item with the chance of a_Chance (in percent) to itemdrops a_Drops*/ + void AddRandomUncommonDropItem(cItems & a_Drops, float a_Chance, short a_Item, short a_ItemHealth = 0); + + /** Adds one rare item out of the list of rare items a_Items modified by the looting level a_LootingLevel(I-III or custom) to the itemdrop a_Drops*/ + void AddRandomRareDropItem(cItems & a_Drops, cItems & a_Items, short a_LootingLevel); + + /** Adds armor that is equipped with the chance saved in m_DropChance[...] (this will be greter than 1 if piccked up or 0.085 + (0.01 per LootingLevel) if born with) to the drop*/ + void AddRandomArmorDropItem(cItems & a_Drops, short a_LootingLevel); + + /** Adds weapon that is equipped with the chance saved in m_DropChance[...] (this will be greter than 1 if piccked up or 0.085 + (0.01 per LootingLevel) if born with) to the drop*/ + void AddRandomWeaponDropItem(cItems & a_Drops, short a_LootingLevel); - void HandleDaylightBurning(cChunk & a_Chunk); } ; // tolua_export diff --git a/src/Mobs/Mooshroom.cpp b/src/Mobs/Mooshroom.cpp index 940e2db44..81bd3e3b4 100644 --- a/src/Mobs/Mooshroom.cpp +++ b/src/Mobs/Mooshroom.cpp @@ -2,12 +2,12 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Mooshroom.h" +#include "../Entities/Player.h" -// TODO: Milk Cow @@ -24,10 +24,52 @@ cMooshroom::cMooshroom(void) : void cMooshroom::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 2, E_ITEM_LEATHER); - AddRandomDropItem(a_Drops, 1, 3, IsOnFire() ? E_ITEM_STEAK : E_ITEM_RAW_BEEF); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_LEATHER); + AddRandomDropItem(a_Drops, 1, 3 + LootingLevel, IsOnFire() ? E_ITEM_STEAK : E_ITEM_RAW_BEEF); } + +void cMooshroom::OnRightClicked(cPlayer & a_Player) +{ + switch (a_Player.GetEquippedItem().m_ItemType) + { + case E_ITEM_BUCKET: + { + if (!a_Player.IsGameModeCreative()) + { + a_Player.GetInventory().RemoveOneEquippedItem(); + a_Player.GetInventory().AddItem(E_ITEM_MILK); + } + } break; + case E_ITEM_BOWL: + { + if (!a_Player.IsGameModeCreative()) + { + a_Player.GetInventory().RemoveOneEquippedItem(); + a_Player.GetInventory().AddItem(E_ITEM_MUSHROOM_SOUP); + } + } break; + case E_ITEM_SHEARS: + { + if (!a_Player.IsGameModeCreative()) + { + a_Player.UseEquippedItem(); + } + + cItems Drops; + Drops.push_back(cItem(E_BLOCK_RED_MUSHROOM, 5, 0)); + m_World->SpawnItemPickups(Drops, GetPosX(), GetPosY(), GetPosZ(), 10); + m_World->SpawnMob(GetPosX(), GetPosY(), GetPosZ(), cMonster::mtCow); + Destroy(); + } break; + } +} + diff --git a/src/Mobs/Mooshroom.h b/src/Mobs/Mooshroom.h index 73f6348b6..16f6c8248 100644 --- a/src/Mobs/Mooshroom.h +++ b/src/Mobs/Mooshroom.h @@ -18,6 +18,9 @@ public: CLASS_PROTODEF(cMooshroom); virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; + virtual void OnRightClicked(cPlayer & a_Player) override; + + virtual const cItem GetFollowedItem(void) const override { return cItem(E_ITEM_WHEAT); } } ; diff --git a/src/Mobs/PassiveAggressiveMonster.cpp b/src/Mobs/PassiveAggressiveMonster.cpp index 28de65905..4b45f9a2a 100644 --- a/src/Mobs/PassiveAggressiveMonster.cpp +++ b/src/Mobs/PassiveAggressiveMonster.cpp @@ -25,8 +25,7 @@ void cPassiveAggressiveMonster::DoTakeDamage(TakeDamageInfo & a_TDI) if ((m_Target != NULL) && (m_Target->IsPlayer())) { - cPlayer * Player = (cPlayer *) m_Target; - if (Player->GetGameMode() != 1) + if (!((cPlayer *)m_Target)->IsGameModeCreative()) { m_EMState = CHASING; } diff --git a/src/Mobs/PassiveMonster.cpp b/src/Mobs/PassiveMonster.cpp index 91ceb5a53..904cd63cc 100644 --- a/src/Mobs/PassiveMonster.cpp +++ b/src/Mobs/PassiveMonster.cpp @@ -2,9 +2,8 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "PassiveMonster.h" -#include "../MersenneTwister.h" #include "../World.h" - +#include "../Entities/Player.h" @@ -36,19 +35,22 @@ void cPassiveMonster::Tick(float a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); - m_SeePlayerInterval += a_Dt; - - if (m_SeePlayerInterval > 1) // Check every second + if (m_EMState == ESCAPING) { - int rem = m_World->GetTickRandomNumber(3) + 1; // Check most of the time but miss occasionally - - m_SeePlayerInterval = 0.0; - if (rem >= 2) + CheckEventLostPlayer(); + } + cItem FollowedItem = GetFollowedItem(); + if (FollowedItem.IsEmpty()) + { + return; + } + cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), (float)m_SightDistance); + if (a_Closest_Player != NULL) + { + if (a_Closest_Player->GetEquippedItem().IsEqual(FollowedItem)) { - if (m_EMState == ESCAPING) - { - CheckEventLostPlayer(); - } + Vector3d PlayerPos = a_Closest_Player->GetPosition(); + MoveToPosition(PlayerPos); } } } diff --git a/src/Mobs/PassiveMonster.h b/src/Mobs/PassiveMonster.h index 14a6be6b1..0b3c155da 100644 --- a/src/Mobs/PassiveMonster.h +++ b/src/Mobs/PassiveMonster.h @@ -19,6 +19,9 @@ public: /// When hit by someone, run away virtual void DoTakeDamage(TakeDamageInfo & a_TDI) override; + /** Returns the item that the animal of this class follows when a player holds it in hand + Return an empty item not to follow (default). */ + virtual const cItem GetFollowedItem(void) const { return cItem(); } } ; diff --git a/src/Mobs/Pig.cpp b/src/Mobs/Pig.cpp index 0871a38a9..e862f5aaa 100644 --- a/src/Mobs/Pig.cpp +++ b/src/Mobs/Pig.cpp @@ -21,7 +21,12 @@ cPig::cPig(void) : void cPig::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 1, 3, IsOnFire() ? E_ITEM_COOKED_PORKCHOP : E_ITEM_RAW_PORKCHOP); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + AddRandomDropItem(a_Drops, 1, 3 + LootingLevel, IsOnFire() ? E_ITEM_COOKED_PORKCHOP : E_ITEM_RAW_PORKCHOP); if (m_bIsSaddled) { a_Drops.push_back(cItem(E_ITEM_SADDLE, 1)); @@ -73,5 +78,3 @@ void cPig::OnRightClicked(cPlayer & a_Player) - - diff --git a/src/Mobs/Pig.h b/src/Mobs/Pig.h index 4fd0d8db8..d434324c1 100644 --- a/src/Mobs/Pig.h +++ b/src/Mobs/Pig.h @@ -19,6 +19,9 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; virtual void OnRightClicked(cPlayer & a_Player) override; + + virtual const cItem GetFollowedItem(void) const override { return cItem(E_ITEM_CARROT); } + bool IsSaddled(void) const { return m_bIsSaddled; } private: diff --git a/src/Mobs/Sheep.cpp b/src/Mobs/Sheep.cpp index bda4ccff8..c64360153 100644 --- a/src/Mobs/Sheep.cpp +++ b/src/Mobs/Sheep.cpp @@ -13,7 +13,8 @@ cSheep::cSheep(int a_Color) : super("Sheep", mtSheep, "mob.sheep.say", "mob.sheep.say", 0.6, 1.3), m_IsSheared(false), - m_WoolColor(a_Color) + m_WoolColor(a_Color), + m_TimeToStopEating(-1) { } @@ -60,3 +61,50 @@ void cSheep::OnRightClicked(cPlayer & a_Player) m_World->BroadcastEntityMetadata(*this); } } + + + + + +void cSheep::Tick(float a_Dt, cChunk & a_Chunk) +{ + super::Tick(a_Dt, a_Chunk); + int PosX = POSX_TOINT; + int PosY = POSY_TOINT - 1; + int PosZ = POSZ_TOINT; + + if ((PosY <= 0) || (PosY > cChunkDef::Height)) + { + return; + } + + if (m_TimeToStopEating > 0) + { + m_bMovingToDestination = false; // The sheep should not move when he's eating + m_TimeToStopEating--; + + if (m_TimeToStopEating == 0) + { + if (m_World->GetBlock(PosX, PosY, PosZ) == E_BLOCK_GRASS) // Make sure grass hasn't been destroyed in the meantime + { + // The sheep ate the grass so we change it to dirt + m_World->SetBlock(PosX, PosY, PosZ, E_BLOCK_DIRT, 0); + GetWorld()->BroadcastSoundParticleEffect(2001, PosX, PosY, PosX, E_BLOCK_GRASS); + m_IsSheared = false; + m_World->BroadcastEntityMetadata(*this); + } + } + } + else + { + if (m_World->GetTickRandomNumber(600) == 1) + { + if (m_World->GetBlock(PosX, PosY, PosZ) == E_BLOCK_GRASS) + { + m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_SHEEP_EATING); + m_TimeToStopEating = 40; + } + } + } +} + diff --git a/src/Mobs/Sheep.h b/src/Mobs/Sheep.h index 8293a2c05..402e8e61c 100644 --- a/src/Mobs/Sheep.h +++ b/src/Mobs/Sheep.h @@ -19,6 +19,10 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; virtual void OnRightClicked(cPlayer & a_Player) override; + virtual void Tick(float a_Dt, cChunk & a_Chunk) override; + + virtual const cItem GetFollowedItem(void) const override { return cItem(E_ITEM_WHEAT); } + bool IsSheared(void) const { return m_IsSheared; } int GetFurColor(void) const { return m_WoolColor; } @@ -26,6 +30,7 @@ private: bool m_IsSheared; int m_WoolColor; + int m_TimeToStopEating; } ; diff --git a/src/Mobs/Skeleton.cpp b/src/Mobs/Skeleton.cpp index 509c2191e..1685f40c5 100644 --- a/src/Mobs/Skeleton.cpp +++ b/src/Mobs/Skeleton.cpp @@ -20,8 +20,26 @@ cSkeleton::cSkeleton(bool IsWither) : void cSkeleton::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 2, E_ITEM_ARROW); - AddRandomDropItem(a_Drops, 0, 2, E_ITEM_BONE); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + if (IsWither()) + { + AddRandomUncommonDropItem(a_Drops, 33.0f, E_ITEM_COAL); + cItems RareDrops; + RareDrops.Add(cItem(E_ITEM_HEAD, 1, 1)); + AddRandomRareDropItem(a_Drops, RareDrops, LootingLevel); + } + else + { + AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_ARROW); + + } + AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_BONE); + AddRandomArmorDropItem(a_Drops, LootingLevel); + AddRandomWeaponDropItem(a_Drops, LootingLevel); } @@ -30,15 +48,18 @@ void cSkeleton::GetDrops(cItems & a_Drops, cEntity * a_Killer) void cSkeleton::MoveToPosition(const Vector3f & a_Position) { - m_Destination = a_Position; - // If the destination is in the sun and if it is not night AND the skeleton isn't on fire then block the movement. - if (!IsOnFire() && m_World->GetTimeOfDay() < 13187 && m_World->GetBlockSkyLight((int) a_Position.x, (int) a_Position.y, (int) a_Position.z) == 15) + if ( + !IsOnFire() && + (m_World->GetTimeOfDay() < 13187) && + (m_World->GetBlockSkyLight((int) a_Position.x, (int) a_Position.y, (int) a_Position.z) == 15) + ) { m_bMovingToDestination = false; return; } - m_bMovingToDestination = true; + + super::MoveToPosition(a_Position); } @@ -67,4 +88,4 @@ void cSkeleton::Attack(float a_Dt) m_World->BroadcastSpawnEntity(*Arrow); m_AttackInterval = 0.0; } -}
\ No newline at end of file +} diff --git a/src/Mobs/Slime.cpp b/src/Mobs/Slime.cpp index 19f376c21..52a52bb39 100644 --- a/src/Mobs/Slime.cpp +++ b/src/Mobs/Slime.cpp @@ -20,8 +20,15 @@ cSlime::cSlime(int a_Size) : void cSlime::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - // TODO: only when tiny - AddRandomDropItem(a_Drops, 0, 2, E_ITEM_SLIMEBALL); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + if (GetSize() == 1) + { + AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_SLIMEBALL); + } } diff --git a/src/Mobs/SnowGolem.cpp b/src/Mobs/SnowGolem.cpp index 06021cca5..c1979a495 100644 --- a/src/Mobs/SnowGolem.cpp +++ b/src/Mobs/SnowGolem.cpp @@ -19,7 +19,8 @@ cSnowGolem::cSnowGolem(void) : void cSnowGolem::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 5, E_ITEM_SNOWBALL); + UNUSED(a_Killer); + AddRandomDropItem(a_Drops, 0, 15, E_ITEM_SNOWBALL); } @@ -29,7 +30,7 @@ void cSnowGolem::GetDrops(cItems & a_Drops, cEntity * a_Killer) void cSnowGolem::Tick(float a_Dt, cChunk & a_Chunk) { super::Tick(a_Dt, a_Chunk); - if (IsBiomeNoDownfall((EMCSBiome) m_World->GetBiomeAt((int) floor(GetPosX()), (int) floor(GetPosZ())) )) + if (IsBiomeNoDownfall(m_World->GetBiomeAt((int) floor(GetPosX()), (int) floor(GetPosZ())) )) { TakeDamage(*this); } @@ -37,7 +38,7 @@ void cSnowGolem::Tick(float a_Dt, cChunk & a_Chunk) { BLOCKTYPE BlockBelow = m_World->GetBlock((int) floor(GetPosX()), (int) floor(GetPosY()) - 1, (int) floor(GetPosZ())); BLOCKTYPE Block = m_World->GetBlock((int) floor(GetPosX()), (int) floor(GetPosY()), (int) floor(GetPosZ())); - if (Block == E_BLOCK_AIR && g_BlockIsSolid[BlockBelow]) + if (Block == E_BLOCK_AIR && cBlockInfo::IsSolid(BlockBelow)) { m_World->SetBlock((int) floor(GetPosX()), (int) floor(GetPosY()), (int) floor(GetPosZ()), E_BLOCK_SNOW, 0); } diff --git a/src/Mobs/Spider.cpp b/src/Mobs/Spider.cpp index b19a5dcef..8b978ff6b 100644 --- a/src/Mobs/Spider.cpp +++ b/src/Mobs/Spider.cpp @@ -18,8 +18,16 @@ cSpider::cSpider(void) : void cSpider::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 2, E_ITEM_STRING); - AddRandomDropItem(a_Drops, 0, 1, E_ITEM_SPIDER_EYE); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_STRING); + if ((a_Killer != NULL) && (a_Killer->IsPlayer() || a_Killer->IsA("cWolf"))) + { + AddRandomUncommonDropItem(a_Drops, 33.0f, E_ITEM_SPIDER_EYE); + } } diff --git a/src/Mobs/Squid.cpp b/src/Mobs/Squid.cpp index a311108ae..bd0e141a0 100644 --- a/src/Mobs/Squid.cpp +++ b/src/Mobs/Squid.cpp @@ -2,7 +2,7 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Squid.h" -#include "../Vector3d.h" +#include "../Vector3.h" #include "../Chunk.h" @@ -21,7 +21,12 @@ cSquid::cSquid(void) : void cSquid::GetDrops(cItems & a_Drops, cEntity * a_Killer) { // Drops 0-3 Ink Sacs - AddRandomDropItem(a_Drops, 0, 3, E_ITEM_DYE, E_META_DYE_BLACK); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + AddRandomDropItem(a_Drops, 0, 3 + LootingLevel, E_ITEM_DYE, E_META_DYE_BLACK); } @@ -43,7 +48,8 @@ void cSquid::Tick(float a_Dt, cChunk & a_Chunk) } int RelX = (int)floor(Pos.x) - a_Chunk.GetPosX() * cChunkDef::Width; int RelZ = (int)floor(Pos.z) - a_Chunk.GetPosZ() * cChunkDef::Width; - if (!IsBlockWater(a_Chunk.GetBlock(RelX, RelY, RelZ)) && !IsOnFire()) + BLOCKTYPE BlockType; + if (a_Chunk.UnboundedRelGetBlockType(RelX, RelY, RelZ, BlockType) && !IsBlockWater(BlockType) && !IsOnFire()) { // Burn for 10 ticks, then decide again StartBurning(10); diff --git a/src/Mobs/Squid.h b/src/Mobs/Squid.h index ad299b95c..a9dba8b70 100644 --- a/src/Mobs/Squid.h +++ b/src/Mobs/Squid.h @@ -21,6 +21,9 @@ public: virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; + // Squids do not drown (or float) + virtual void HandleAir(void) override {} + virtual void SetSwimState(cChunk & a_Chunk) override {} } ; diff --git a/src/Mobs/Villager.cpp b/src/Mobs/Villager.cpp index 7f89fb6cc..bbd8d6aaa 100644 --- a/src/Mobs/Villager.cpp +++ b/src/Mobs/Villager.cpp @@ -3,6 +3,9 @@ #include "Villager.h" #include "../World.h" +#include "../BlockArea.h" +#include "../Blocks/BlockHandler.h" +#include "../BlockInServerPluginInterface.h" @@ -10,7 +13,9 @@ cVillager::cVillager(eVillagerType VillagerType) : super("Villager", mtVillager, "", "", 0.6, 1.8), - m_Type(VillagerType) + m_ActionCountDown(-1), + m_Type(VillagerType), + m_VillagerAction(false) { } @@ -21,7 +26,7 @@ cVillager::cVillager(eVillagerType VillagerType) : void cVillager::DoTakeDamage(TakeDamageInfo & a_TDI) { super::DoTakeDamage(a_TDI); - if (a_TDI.Attacker->IsPlayer()) + if ((a_TDI.Attacker != NULL) && a_TDI.Attacker->IsPlayer()) { if (m_World->GetTickRandomNumber(5) == 3) { @@ -33,3 +38,155 @@ void cVillager::DoTakeDamage(TakeDamageInfo & a_TDI) + +void cVillager::Tick(float a_Dt, cChunk & a_Chunk) +{ + super::Tick(a_Dt, a_Chunk); + + 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 (m_World->GetTickRandomNumber(99) != 0) + { + return; + } + + switch (m_Type) + { + case vtFarmer: + { + HandleFarmerPrepareFarmCrops(); + } + } +} + + + + +//////////////////////////////////////////////////////////////////////////////// +// Farmer functions. +void cVillager::HandleFarmerPrepareFarmCrops() +{ + if (!m_World->VillagersShouldHarvestCrops()) + { + return; + } + + cBlockArea Surrounding; + /// Read a 11x7x11 area. + Surrounding.Read( + m_World, + (int) GetPosX() - 5, + (int) GetPosX() + 5, + (int) GetPosY() - 3, + (int) GetPosY() + 3, + (int) GetPosZ() - 5, + (int) GetPosZ() + 5 + ); + + for (int I = 0; I < 5; I++) + { + for (int Y = 0; Y < 6; Y++) + { + // Pick random coordinates and check for crops. + int X = m_World->GetTickRandomNumber(11); + int Z = m_World->GetTickRandomNumber(11); + + // A villager can't farm this. + if (!IsBlockFarmable(Surrounding.GetRelBlockType(X, Y, Z))) + { + continue; + } + if (Surrounding.GetRelBlockMeta(X, Y, Z) != 0x7) + { + continue; + } + + m_VillagerAction = true; + m_CropsPos = Vector3i((int) GetPosX() + X - 5, (int) GetPosY() + Y - 3, (int) GetPosZ() + Z - 5); + MoveToPosition(Vector3f((float) (m_CropsPos.x + 0.5), (float) m_CropsPos.y, (float) (m_CropsPos.z + 0.5))); + return; + } // for Y loop. + } // Repeat the procces 5 times. +} + + + + + +void cVillager::HandleFarmerTryHarvestCrops() +{ + // Harvest the crops if the villager isn't moving and if the crops are closer then 2 blocks. + if (!m_bMovingToDestination && (GetPosition() - m_CropsPos).Length() < 2) + { + // Check if the blocks didn't change while the villager was walking to the coordinates. + BLOCKTYPE CropBlock = m_World->GetBlock(m_CropsPos.x, m_CropsPos.y, m_CropsPos.z); + if (IsBlockFarmable(CropBlock) && m_World->GetBlockMeta(m_CropsPos.x, m_CropsPos.y, m_CropsPos.z) == 0x7) + { + cBlockHandler * Handler = cBlockInfo::GetHandler(CropBlock); + cChunkInterface ChunkInterface(m_World->GetChunkMap()); + cBlockInServerPluginInterface PluginInterface(*m_World); + Handler->DropBlock(ChunkInterface, *m_World, PluginInterface, this, m_CropsPos.x, m_CropsPos.y, m_CropsPos.z); + m_World->SetBlock(m_CropsPos.x, m_CropsPos.y, m_CropsPos.z, E_BLOCK_AIR, 0); + m_ActionCountDown = 20; + } + } +} + + + + +void cVillager::HandleFarmerPlaceCrops() +{ + // Check if there is still farmland at the spot where the crops were. + if (m_World->GetBlock(m_CropsPos.x, m_CropsPos.y - 1, m_CropsPos.z) == E_BLOCK_FARMLAND) + { + m_World->SetBlock(m_CropsPos.x, m_CropsPos.y, m_CropsPos.z, E_BLOCK_CROPS, 0); + } +} + + + + + +bool cVillager::IsBlockFarmable(BLOCKTYPE a_BlockType) +{ + switch (a_BlockType) + { + case E_BLOCK_CROPS: + case E_BLOCK_POTATOES: + case E_BLOCK_CARROTS: + { + return true; + } + } + return false; +} + diff --git a/src/Mobs/Villager.h b/src/Mobs/Villager.h index 4cd9aaa8e..5bba4d4ba 100644 --- a/src/Mobs/Villager.h +++ b/src/Mobs/Villager.h @@ -29,12 +29,36 @@ public: CLASS_PROTODEF(cVillager); + // cEntity overrides virtual void DoTakeDamage(TakeDamageInfo & a_TDI) override; - int GetVilType(void) const { return m_Type; } + virtual void Tick (float a_Dt, cChunk & a_Chunk) override; + + // cVillager functions + /** return true if the given blocktype are: crops, potatoes or carrots.*/ + bool IsBlockFarmable(BLOCKTYPE a_BlockType); + + ////////////////////////////////////////////////////////////////// + // Farmer functions + /** It searches in a 11x7x11 area for crops. If it found some it will navigate to them.*/ + void HandleFarmerPrepareFarmCrops(); + + /** 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.*/ + void HandleFarmerTryHarvestCrops(); + + /** Replaces the crops he harvested.*/ + void HandleFarmerPlaceCrops(); + + // 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; } private: + int m_ActionCountDown; int m_Type; + bool m_VillagerAction; + Vector3i m_CropsPos; } ; diff --git a/src/Mobs/Witch.cpp b/src/Mobs/Witch.cpp index 25d27041f..6956f7b7a 100644 --- a/src/Mobs/Witch.cpp +++ b/src/Mobs/Witch.cpp @@ -18,13 +18,28 @@ cWitch::cWitch(void) : void cWitch::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 6, E_ITEM_GLASS_BOTTLE); - AddRandomDropItem(a_Drops, 0, 6, E_ITEM_GLOWSTONE_DUST); - AddRandomDropItem(a_Drops, 0, 6, E_ITEM_GUNPOWDER); - AddRandomDropItem(a_Drops, 0, 6, E_ITEM_REDSTONE_DUST); - AddRandomDropItem(a_Drops, 0, 6, E_ITEM_SPIDER_EYE); - AddRandomDropItem(a_Drops, 0, 6, E_ITEM_STICK); - AddRandomDropItem(a_Drops, 0, 6, E_ITEM_SUGAR); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + MTRand r1; + int DropTypeCount = (r1.randInt() % 3) + 1; + for (int i = 0; i < DropTypeCount; i++) + { + int DropType = r1.randInt() % 7; + switch (DropType) + { + case 0: AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_GLASS_BOTTLE); break; + case 1: AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_GLOWSTONE_DUST); break; + case 2: AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_GUNPOWDER); break; + case 3: AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_REDSTONE_DUST); break; + case 4: AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_SPIDER_EYE); break; + case 5: AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_STICK); break; + case 6: AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_SUGAR); break; + } + } + AddRandomWeaponDropItem(a_Drops, LootingLevel); } diff --git a/src/Mobs/Witch.h b/src/Mobs/Witch.h index 4e637beea..51c63322a 100644 --- a/src/Mobs/Witch.h +++ b/src/Mobs/Witch.h @@ -2,6 +2,7 @@ #pragma once #include "AggressiveMonster.h" +#include "../MersenneTwister.h" diff --git a/src/Mobs/Wither.cpp b/src/Mobs/Wither.cpp index c46e0beab..8f5d28b68 100644 --- a/src/Mobs/Wither.cpp +++ b/src/Mobs/Wither.cpp @@ -2,14 +2,90 @@ #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules #include "Wither.h" +#include "../World.h" cWither::cWither(void) : - super("Wither", mtWither, "mob.wither.hurt", "mob.wither.death", 0.9, 4.0) + super("Wither", mtWither, "mob.wither.hurt", "mob.wither.death", 0.9, 4.0), + m_InvulnerableTicks(220) { + SetMaxHealth(300); +} + + + + + +bool cWither::IsArmored(void) const +{ + return GetHealth() <= (GetMaxHealth() / 2); +} + + + + + +bool cWither::Initialize(cWorld * a_World) +{ + // Set health before BroadcastSpawnEntity() + SetHealth(GetMaxHealth() / 3); + + return super::Initialize(a_World); +} + + + + + +void cWither::DoTakeDamage(TakeDamageInfo & a_TDI) +{ + if (a_TDI.DamageType == dtDrowning) + { + return; + } + + if (m_InvulnerableTicks > 0) + { + return; + } + + if (IsArmored() && (a_TDI.DamageType == dtRangedAttack)) + { + return; + } + + super::DoTakeDamage(a_TDI); +} + + + + + +void cWither::Tick(float a_Dt, cChunk & a_Chunk) +{ + super::Tick(a_Dt, a_Chunk); + + if (m_InvulnerableTicks > 0) + { + unsigned int NewTicks = m_InvulnerableTicks - 1; + + if (NewTicks == 0) + { + m_World->DoExplosionAt(7.0, GetPosX(), GetPosY(), GetPosZ(), false, esWitherBirth, this); + } + + m_InvulnerableTicks = NewTicks; + + if ((NewTicks % 10) == 0) + { + Heal(10); + } + } + + m_World->BroadcastEntityMetadata(*this); } diff --git a/src/Mobs/Wither.h b/src/Mobs/Wither.h index 56effc6bb..bc78bfaad 100644 --- a/src/Mobs/Wither.h +++ b/src/Mobs/Wither.h @@ -16,8 +16,25 @@ public: cWither(void); CLASS_PROTODEF(cWither); + + unsigned int GetNumInvulnerableTicks(void) const { return m_InvulnerableTicks; } + + void SetNumInvulnerableTicks(unsigned int a_Ticks) { m_InvulnerableTicks = a_Ticks; } + + /** Returns whether the wither is invulnerable to arrows. */ + bool IsArmored(void) const; + // cEntity overrides + virtual bool Initialize(cWorld * a_World) override; virtual void GetDrops(cItems & a_Drops, cEntity * a_Killer = NULL) override; + virtual void DoTakeDamage(TakeDamageInfo & a_TDI) override; + virtual void Tick(float a_Dt, cChunk & a_Chunk) override; + +private: + + /** The number of ticks of invulnerability left after being initially created. Zero once invulnerability has expired. */ + unsigned int m_InvulnerableTicks; + } ; diff --git a/src/Mobs/Wolf.cpp b/src/Mobs/Wolf.cpp index 3d4e97c80..0d3619166 100644 --- a/src/Mobs/Wolf.cpp +++ b/src/Mobs/Wolf.cpp @@ -4,6 +4,7 @@ #include "Wolf.h" #include "../World.h" #include "../Entities/Player.h" +#include "../Items/ItemHandler.h" @@ -37,6 +38,26 @@ void cWolf::DoTakeDamage(TakeDamageInfo & a_TDI) +void cWolf::Attack(float a_Dt) +{ + UNUSED(a_Dt); + + if ((m_Target != NULL) && (m_Target->IsPlayer())) + { + if (((cPlayer *)m_Target)->GetName() != m_OwnerName) + { + super::Attack(a_Dt); + } + } + else + { + super::Attack(a_Dt); + } +} + + + + void cWolf::OnRightClicked(cPlayer & a_Player) { @@ -55,32 +76,55 @@ void cWolf::OnRightClicked(cPlayer & a_Player) SetIsTame(true); SetOwner(a_Player.GetName()); m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_WOLF_TAMED); + m_World->BroadcastParticleEffect("heart", (float) GetPosX(), (float) GetPosY(), (float) GetPosZ(), 0, 0, 0, 0, 5); } else { m_World->BroadcastEntityStatus(*this, ENTITY_STATUS_WOLF_TAMING); + m_World->BroadcastParticleEffect("smoke", (float) GetPosX(), (float) GetPosY(), (float) GetPosZ(), 0, 0, 0, 0, 5); } } } else if (IsTame()) { - if (a_Player.GetName() == m_OwnerName) // Is the player the owner of the dog? + switch (a_Player.GetEquippedItem().m_ItemType) { - if (a_Player.GetEquippedItem().m_ItemType == E_ITEM_DYE) + case E_ITEM_RAW_BEEF: + case E_ITEM_STEAK: + case E_ITEM_RAW_PORKCHOP: + case E_ITEM_COOKED_PORKCHOP: + case E_ITEM_RAW_CHICKEN: + case E_ITEM_COOKED_CHICKEN: + case E_ITEM_ROTTEN_FLESH: { - SetCollarColor(15 - a_Player.GetEquippedItem().m_ItemDamage); - if (!a_Player.IsGameModeCreative()) + if (m_Health < m_MaxHealth) { - a_Player.GetInventory().RemoveOneEquippedItem(); + Heal(ItemHandler(a_Player.GetEquippedItem().m_ItemType)->GetFoodInfo().FoodLevel); + if (!a_Player.IsGameModeCreative()) + { + a_Player.GetInventory().RemoveOneEquippedItem(); + } } - } - else if (IsSitting()) + break; + } + case E_ITEM_DYE: { - SetIsSitting(false); + if (a_Player.GetName() == m_OwnerName) // Is the player the owner of the dog? + { + SetCollarColor(15 - a_Player.GetEquippedItem().m_ItemDamage); + if (!a_Player.IsGameModeCreative()) + { + a_Player.GetInventory().RemoveOneEquippedItem(); + } + } + break; } - else + default: { - SetIsSitting(true); + if (a_Player.GetName() == m_OwnerName) // Is the player the owner of the dog? + { + SetIsSitting(!IsSitting()); + } } } } @@ -103,12 +147,7 @@ void cWolf::Tick(float a_Dt, cChunk & a_Chunk) super::Tick(a_Dt, a_Chunk); } - if (IsSitting()) - { - m_bMovingToDestination = false; - } - - cPlayer * a_Closest_Player = FindClosestPlayer(); + cPlayer * a_Closest_Player = m_World->FindClosestPlayer(GetPosition(), (float)m_SightDistance); if (a_Closest_Player != NULL) { switch (a_Closest_Player->GetEquippedItem().m_ItemType) @@ -119,16 +158,23 @@ void cWolf::Tick(float a_Dt, cChunk & a_Chunk) case E_ITEM_RAW_CHICKEN: case E_ITEM_COOKED_CHICKEN: case E_ITEM_ROTTEN_FLESH: + case E_ITEM_RAW_PORKCHOP: + case E_ITEM_COOKED_PORKCHOP: { if (!IsBegging()) { SetIsBegging(true); m_World->BroadcastEntityMetadata(*this); } - Vector3f a_NewDestination = a_Closest_Player->GetPosition(); - a_NewDestination.y = a_NewDestination.y + 1; // Look at the head of the player, not his feet. - m_Destination = Vector3f(a_NewDestination); - m_bMovingToDestination = false; + + m_FinalDestination = a_Closest_Player->GetPosition(); // So that we will look at a player holding food + + // Don't move to the player if the wolf is sitting. + if (!IsSitting()) + { + MoveToPosition(a_Closest_Player->GetPosition()); + } + break; } default: @@ -142,10 +188,14 @@ void cWolf::Tick(float a_Dt, cChunk & a_Chunk) } } - if (IsTame()) + if (IsTame() && !IsSitting()) { TickFollowPlayer(); } + else if (IsSitting()) + { + m_bMovingToDestination = false; + } } @@ -163,23 +213,21 @@ void cWolf::TickFollowPlayer() return false; } public: - Vector3f OwnerPos; + Vector3d OwnerPos; } Callback; + if (m_World->DoWithPlayer(m_OwnerName, Callback)) { - // The player is present in the world, follow them: + // The player is present in the world, follow him: double Distance = (Callback.OwnerPos - GetPosition()).Length(); - if (Distance < 3) - { - m_bMovingToDestination = false; - } - else if ((Distance > 30) && (!IsSitting())) + if (Distance > 30) { + Callback.OwnerPos.y = FindFirstNonAirBlockPosition(Callback.OwnerPos.x, Callback.OwnerPos.z); TeleportToCoords(Callback.OwnerPos.x, Callback.OwnerPos.y, Callback.OwnerPos.z); } else { - m_Destination = Callback.OwnerPos; + MoveToPosition(Callback.OwnerPos); } } } diff --git a/src/Mobs/Wolf.h b/src/Mobs/Wolf.h index 040e2cf7a..9e5ad03c7 100644 --- a/src/Mobs/Wolf.h +++ b/src/Mobs/Wolf.h @@ -22,6 +22,7 @@ public: virtual void OnRightClicked(cPlayer & a_Player) override; virtual void Tick(float a_Dt, cChunk & a_Chunk) override; virtual void TickFollowPlayer(); + virtual void Attack(float a_Dt) override; // Get functions bool IsSitting (void) const { return m_IsSitting; } diff --git a/src/Mobs/Zombie.cpp b/src/Mobs/Zombie.cpp index a046fcc92..f19e096ee 100644 --- a/src/Mobs/Zombie.cpp +++ b/src/Mobs/Zombie.cpp @@ -23,9 +23,19 @@ cZombie::cZombie(bool a_IsVillagerZombie) : void cZombie::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 2, E_ITEM_ROTTEN_FLESH); - - // TODO: Rare drops + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + AddRandomDropItem(a_Drops, 0, 2 + LootingLevel, E_ITEM_ROTTEN_FLESH); + cItems RareDrops; + RareDrops.Add(cItem(E_ITEM_IRON)); + RareDrops.Add(cItem(E_ITEM_CARROT)); + RareDrops.Add(cItem(E_ITEM_POTATO)); + AddRandomRareDropItem(a_Drops, RareDrops, LootingLevel); + AddRandomArmorDropItem(a_Drops, LootingLevel); + AddRandomWeaponDropItem(a_Drops, LootingLevel); } @@ -34,15 +44,18 @@ void cZombie::GetDrops(cItems & a_Drops, cEntity * a_Killer) void cZombie::MoveToPosition(const Vector3f & a_Position) { - m_Destination = a_Position; - - // If the destination is in the sun and if it is not night AND the skeleton isn't on fire then block the movement. - if ((m_World->GetBlockSkyLight((int) a_Position.x, (int) a_Position.y, (int) a_Position.z) == 15) && (m_World->GetTimeOfDay() < 13187) && !IsOnFire()) + // If the destination is in the sun and if it is not night AND the zombie isn't on fire then block the movement. + if ( + !IsOnFire() && + (m_World->GetTimeOfDay() < 13187) && + (m_World->GetBlockSkyLight((int)a_Position.x, (int)a_Position.y, (int)a_Position.z) == 15) + ) { m_bMovingToDestination = false; return; } - m_bMovingToDestination = true; + + super::MoveToPosition(a_Position); } diff --git a/src/Mobs/Zombiepigman.cpp b/src/Mobs/Zombiepigman.cpp index 6ac89ed4c..a0142b566 100644 --- a/src/Mobs/Zombiepigman.cpp +++ b/src/Mobs/Zombiepigman.cpp @@ -19,10 +19,19 @@ cZombiePigman::cZombiePigman(void) : void cZombiePigman::GetDrops(cItems & a_Drops, cEntity * a_Killer) { - AddRandomDropItem(a_Drops, 0, 1, E_ITEM_ROTTEN_FLESH); - AddRandomDropItem(a_Drops, 0, 1, E_ITEM_GOLD_NUGGET); + int LootingLevel = 0; + if (a_Killer != NULL) + { + LootingLevel = a_Killer->GetEquippedWeapon().m_Enchantments.GetLevel(cEnchantments::enchLooting); + } + AddRandomDropItem(a_Drops, 0, 1 + LootingLevel, E_ITEM_ROTTEN_FLESH); + AddRandomDropItem(a_Drops, 0, 1 + LootingLevel, E_ITEM_GOLD_NUGGET); - // TODO: Rare drops + cItems RareDrops; + RareDrops.Add(cItem(E_ITEM_GOLD)); + AddRandomRareDropItem(a_Drops, RareDrops, LootingLevel); + AddRandomArmorDropItem(a_Drops, LootingLevel); + AddRandomWeaponDropItem(a_Drops, LootingLevel); } |