1
0

Improved tamed wolf pack cooperation and projectile reactions

This commit is contained in:
LogicParrot 2016-01-22 20:55:46 +02:00
parent 30b95fcc4e
commit 439b3304f4
13 changed files with 214 additions and 83 deletions

View File

@ -191,7 +191,7 @@ MaxHealth=26
SightDistance=25.0
[Wolf]
AttackDamage=4.0
AttackDamage=8.0
AttackRange=2.0
AttackRate=1
MaxHealth=20

View File

@ -106,6 +106,16 @@ cBoundingBox::cBoundingBox(const Vector3d & a_Pos, double a_Radius, double a_Hei
cBoundingBox::cBoundingBox(const Vector3d & a_Pos, double a_CubeLength) :
m_Min(a_Pos.x - a_CubeLength / 2, a_Pos.y - a_CubeLength / 2, a_Pos.z - a_CubeLength / 2),
m_Max(a_Pos.x + a_CubeLength / 2, a_Pos.y + a_CubeLength / 2, a_Pos.z + a_CubeLength / 2)
{
}
cBoundingBox::cBoundingBox(const cBoundingBox & a_Orig) :
m_Min(a_Orig.m_Min),
m_Max(a_Orig.m_Max)

View File

@ -27,6 +27,7 @@ public:
cBoundingBox(double a_MinX, double a_MaxX, double a_MinY, double a_MaxY, double a_MinZ, double a_MaxZ);
cBoundingBox(const Vector3d & a_Min, const Vector3d & a_Max);
cBoundingBox(const Vector3d & a_Pos, double a_Radius, double a_Height);
cBoundingBox(const Vector3d & a_Pos, double a_CubeLength);
cBoundingBox(const cBoundingBox & a_Orig);
/** Moves the entire boundingbox by the specified offset */

View File

@ -1642,7 +1642,7 @@ void cClientHandle::HandleUseEntity(UInt32 a_TargetEntityID, bool a_IsLeftClick)
m_Me->AddFoodExhaustion(0.3);
if (a_Entity->IsPawn())
{
m_Me->NotifyFriendlyWolves(static_cast<cPawn*>(a_Entity));
m_Me->NotifyNearbyWolves(static_cast<cPawn*>(a_Entity), true);
}
return true;
}

View File

@ -133,7 +133,7 @@ void cArrowEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos)
}
// a_EntityHit.TakeDamage(dtRangedAttack, this, Damage, KnockbackAmount); // TODO fix knockback.
a_EntityHit.TakeDamage(dtRangedAttack, this, Damage, 0); // Until knockback is fixed.
a_EntityHit.TakeDamage(dtRangedAttack, GetCreatorUniqueID(), Damage, 0); // Until knockback is fixed.
if (IsOnFire() && !a_EntityHit.IsSubmerged() && !a_EntityHit.IsSwimming())
{

View File

@ -238,6 +238,47 @@ void cEntity::TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_R
void cEntity::TakeDamage(eDamageType a_DamageType, UInt32 a_AttackerID, int a_RawDamage, double a_KnockbackAmount)
{
class cNotifyWolves : public cEntityCallback
{
public:
cEntity * m_Entity;
eDamageType m_DamageType;
int m_RawDamage;
double m_KnockbackAmount;
virtual bool Item(cEntity * a_Attacker) override
{
cPawn * Attacker;
if (a_Attacker->IsPawn())
{
Attacker = static_cast<cPawn*>(a_Attacker);
}
else
{
Attacker = nullptr;
}
int FinalDamage = m_RawDamage - m_Entity->GetArmorCoverAgainst(Attacker, m_DamageType, m_RawDamage);
m_Entity->TakeDamage(m_DamageType, Attacker, m_RawDamage, FinalDamage, m_KnockbackAmount);
return true;
}
} Callback;
Callback.m_Entity = this;
Callback.m_DamageType = a_DamageType;
Callback.m_RawDamage = a_RawDamage;
Callback.m_KnockbackAmount = a_KnockbackAmount;
m_World->DoWithEntityByID(a_AttackerID, Callback);
}
void cEntity::TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_RawDamage, int a_FinalDamage, double a_KnockbackAmount)
{
TakeDamageInfo TDI;

View File

@ -263,6 +263,9 @@ public:
/** Makes this entity take the specified damage. The final damage is calculated using current armor, then DoTakeDamage() called */
void TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_RawDamage, double a_KnockbackAmount);
/** Makes this entity take the specified damage. The final damage is calculated using current armor, then DoTakeDamage() called */
void TakeDamage(eDamageType a_DamageType, UInt32 a_Attacker, int a_RawDamage, double a_KnockbackAmount);
/** Makes this entity take the specified damage. The values are packed into a TDI, knockback calculated, then sent through DoTakeDamage() */
void TakeDamage(eDamageType a_DamageType, cEntity * a_Attacker, int a_RawDamage, int a_FinalDamage, double a_KnockbackAmount);

View File

@ -862,7 +862,7 @@ bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI)
{
if (a_TDI.Attacker->IsPawn())
{
NotifyFriendlyWolves(static_cast<cPawn*>(a_TDI.Attacker));
NotifyNearbyWolves(static_cast<cPawn*>(a_TDI.Attacker), true);
}
}
m_Stats.AddValue(statDamageTaken, FloorC<StatValue>(a_TDI.FinalDamage * 10 + 0.5));
@ -875,7 +875,7 @@ bool cPlayer::DoTakeDamage(TakeDamageInfo & a_TDI)
void cPlayer::NotifyFriendlyWolves(cPawn * a_Opponent)
void cPlayer::NotifyNearbyWolves(cPawn * a_Opponent, bool a_IsPlayerInvolved)
{
ASSERT(a_Opponent != nullptr);
class LookForWolves : public cEntityCallback
@ -883,10 +883,12 @@ void cPlayer::NotifyFriendlyWolves(cPawn * a_Opponent)
public:
cPlayer * m_Player;
cPawn * m_Attacker;
bool m_IsPlayerInvolved;
LookForWolves(cPlayer * a_Me, cPawn * a_MyAttacker) :
LookForWolves(cPlayer * a_Me, cPawn * a_MyAttacker, bool a_PlayerInvolved) :
m_Player(a_Me),
m_Attacker(a_MyAttacker)
m_Attacker(a_MyAttacker),
m_IsPlayerInvolved(a_PlayerInvolved)
{
}
@ -898,14 +900,14 @@ void cPlayer::NotifyFriendlyWolves(cPawn * a_Opponent)
if (Mob->GetMobType() == mtWolf)
{
cWolf * Wolf = static_cast<cWolf*>(Mob);
Wolf->NearbyPlayerIsFighting(m_Player, m_Attacker);
Wolf->ReceiveNearbyFightInfo(m_Player->GetUUID(), m_Attacker, m_IsPlayerInvolved);
}
}
return false;
}
} Callback(this, a_Opponent);
} Callback(this, a_Opponent, a_IsPlayerInvolved);
m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), 16, 16), Callback);
m_World->ForEachEntityInBox(cBoundingBox(GetPosition(), 16), Callback);
}

View File

@ -499,8 +499,11 @@ public:
Assumes that all the blocks are in currently loaded chunks. */
bool PlaceBlocks(const sSetBlockVector & a_Blocks);
/** Notify friendly wolves that we took damage or did damage to an entity so that they might assist us. */
void NotifyFriendlyWolves(cPawn * a_Opponent);
/** Notify nearby wolves that the player or one of the player's wolves took damage or did damage to an entity
@param a_Opponent the opponent we're fighting.
@param a_IsPlayerInvolved Should be true if the player took or did damage, and false if one of the player's wolves took or did damage.
*/
void NotifyNearbyWolves(cPawn * a_Opponent, bool a_IsPlayerInvolved);
// cEntity overrides:
virtual bool IsCrouched (void) const override { return m_IsCrouched; }

View File

@ -314,26 +314,24 @@ void cProjectileEntity::OnHitSolidBlock(const Vector3d & a_HitPos, eBlockFace a_
void cProjectileEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d & a_HitPos)
{
if (a_EntityHit.IsPawn() && (GetCreatorName() != "")) // If we're hitting a mob or a player and we were created by a player
{
UNUSED(a_HitPos);
// If we were created by a player and we hit a pawn, notify attacking player's wolves
if (a_EntityHit.IsPawn() && (GetCreatorName() != ""))
{
class cNotifyWolves : public cEntityCallback
{
public:
cPawn * m_EntityHit;
cNotifyWolves(cPawn * a_Entity) :
m_EntityHit(a_Entity)
virtual bool Item(cEntity * a_Hitter) override
{
}
virtual bool Item(cEntity * a_Player) override
{
static_cast<cPlayer*>(a_Player)->NotifyFriendlyWolves(m_EntityHit);
static_cast<cPlayer*>(a_Hitter)->NotifyNearbyWolves(m_EntityHit, true);
return true;
}
} Callback(static_cast<cPawn*>(&a_EntityHit));
} Callback;
Callback.m_EntityHit = static_cast<cPawn*>(&a_EntityHit);
m_World->DoWithEntityByID(GetCreatorUniqueID(), Callback);
}
}

View File

@ -41,7 +41,7 @@ void cThrownSnowballEntity::OnHitEntity(cEntity & a_EntityHit, const Vector3d &
}
}
// TODO: If entity is Ender Crystal, destroy it
a_EntityHit.TakeDamage(dtRangedAttack, this, TotalDamage, 1);
a_EntityHit.TakeDamage(dtRangedAttack, GetCreatorUniqueID(), TotalDamage, 1);
m_DestroyTimer = 5;
}

View File

@ -18,7 +18,8 @@ cWolf::cWolf(void) :
m_IsBegging(false),
m_IsAngry(false),
m_OwnerName(""),
m_CollarColor(E_META_DYE_ORANGE)
m_CollarColor(E_META_DYE_ORANGE),
m_NotificationCooldown(0)
{
m_RelativeWalkSpeed = 2;
}
@ -29,15 +30,42 @@ cWolf::cWolf(void) :
bool cWolf::DoTakeDamage(TakeDamageInfo & a_TDI)
{
cEntity * PreviousTarget = m_Target;
if (!super::DoTakeDamage(a_TDI))
{
return false;
}
if (!m_IsTame)
if ((a_TDI.Attacker != nullptr) && a_TDI.Attacker->IsPawn())
{
cPawn * Pawn = static_cast<cPawn*>(m_Target);
if (Pawn->IsPlayer())
{
if (m_IsTame)
{
if ((static_cast<cPlayer*>(Pawn)->GetUUID() == m_OwnerUUID))
{
m_Target = PreviousTarget; // Do not attack owner
}
else
{
SetIsSitting(false);
NotifyAlliesOfFight(static_cast<cPawn*>(a_TDI.Attacker));
}
}
else
{
m_IsAngry = true;
}
}
else if (m_IsTame)
{
SetIsSitting(false);
NotifyAlliesOfFight(static_cast<cPawn*>(a_TDI.Attacker));
}
}
m_World->BroadcastEntityMetadata(*this); // Broadcast health and possibly angry face
return true;
}
@ -46,56 +74,94 @@ bool cWolf::DoTakeDamage(TakeDamageInfo & a_TDI)
void cWolf::NotifyAlliesOfFight(cPawn * a_Opponent)
{
if (GetOwnerName() == "")
{
return;
}
m_NotificationCooldown = 15;
class cCallback : public cPlayerListCallback
{
virtual bool Item(cPlayer * a_Player) override
{
a_Player->NotifyNearbyWolves(m_Opponent, false);
return false;
}
public:
cPawn * m_Opponent;
} Callback;
Callback.m_Opponent = a_Opponent;
m_World->DoWithPlayerByUUID(m_OwnerUUID, Callback);
}
bool cWolf::Attack(std::chrono::milliseconds a_Dt)
{
UNUSED(a_Dt);
if ((m_Target != nullptr) && (m_Target->IsPlayer()))
{
if (static_cast<cPlayer *>(m_Target)->GetName() != m_OwnerName)
{
return super::Attack(a_Dt);
}
else
if (static_cast<cPlayer *>(m_Target)->GetUUID() == m_OwnerUUID)
{
m_Target = nullptr;
}
}
else
{
return super::Attack(a_Dt);
}
return false;
}
}
NotifyAlliesOfFight(static_cast<cPawn*>(m_Target));
return super::Attack(a_Dt);
}
void cWolf::NearbyPlayerIsFighting(cPlayer * a_Player, cPawn * a_Opponent)
void cWolf::ReceiveNearbyFightInfo(AString a_PlayerID, cPawn * a_Opponent, bool a_IsPlayerInvolved)
{
if (a_Opponent == nullptr)
if (
(a_Opponent == nullptr) || IsSitting() || (!IsTame()) ||
(!a_Opponent->IsPawn()) || (a_PlayerID != m_OwnerUUID)
)
{
return;
}
if ((m_Target == nullptr) && (a_Player->GetName() == m_OwnerName) && !IsSitting() && (a_Opponent->IsPawn()))
// If we already have a target
if (m_Target != nullptr)
{
m_Target = a_Opponent;
if (m_Target->IsPlayer() && static_cast<cPlayer *>(m_Target)->GetName() == m_OwnerName)
// If a wolf is asking for help and we already have a target, do nothing
if (!a_IsPlayerInvolved)
{
m_Target = nullptr; // Our owner has hurt himself, avoid attacking them.
return;
}
if (m_Target->IsMob() && static_cast<cMonster *>(m_Target)->GetMobType() == mtWolf)
// If a player is asking for help and we already have a target,
// there's a 50% chance of helping and a 50% chance of doing nothing
// This helps spread a wolf pack's targets over several mobs
else if (m_World->GetTickRandomNumber(9)> 4)
{
cWolf * Wolf = static_cast<cWolf *>(m_Target);
return;
}
}
if (a_Opponent->IsPlayer() && static_cast<cPlayer *>(a_Opponent)->GetUUID() == m_OwnerUUID)
{
return; // Our owner has hurt himself, avoid attacking them.
}
if (a_Opponent->IsMob() && static_cast<cMonster *>(a_Opponent)->GetMobType() == mtWolf)
{
cWolf * Wolf = static_cast<cWolf *>(a_Opponent);
if (Wolf->GetOwnerUUID() == GetOwnerUUID())
{
m_Target = nullptr; // Our owner attacked one of their wolves. Abort attacking wolf.
}
return; // Our owner attacked one of their wolves. Abort attacking wolf.
}
}
m_Target = a_Opponent;
}
@ -156,7 +222,7 @@ void cWolf::OnRightClicked(cPlayer & a_Player)
}
case E_ITEM_DYE:
{
if (a_Player.GetName() == m_OwnerName) // Is the player the owner of the dog?
if (a_Player.GetUUID() == m_OwnerUUID) // Is the player the owner of the dog?
{
SetCollarColor(a_Player.GetEquippedItem().m_ItemDamage);
if (!a_Player.IsGameModeCreative())
@ -168,7 +234,7 @@ void cWolf::OnRightClicked(cPlayer & a_Player)
}
default:
{
if (a_Player.GetName() == m_OwnerName) // Is the player the owner of the dog?
if (a_Player.GetUUID() == m_OwnerUUID) // Is the player the owner of the dog?
{
SetIsSitting(!IsSitting());
}
@ -188,6 +254,10 @@ void cWolf::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
if (!IsAngry())
{
cMonster::Tick(a_Dt, a_Chunk);
if (m_NotificationCooldown > 0)
{
m_NotificationCooldown -= 1;
}
}
else
{
@ -275,13 +345,13 @@ void cWolf::TickFollowPlayer()
virtual bool Item(cPlayer * a_Player) override
{
OwnerPos = a_Player->GetPosition();
return false;
return true;
}
public:
Vector3d OwnerPos;
} Callback;
if (m_World->DoWithPlayer(m_OwnerName, Callback))
if (m_World->DoWithPlayerByUUID(m_OwnerUUID, Callback))
{
// The player is present in the world, follow him:
double Distance = (Callback.OwnerPos - GetPosition()).Length();

View File

@ -18,6 +18,7 @@ public:
CLASS_PROTODEF(cWolf)
void NotifyAlliesOfFight(cPawn * a_Opponent);
virtual bool DoTakeDamage(TakeDamageInfo & a_TDI) override;
virtual void OnRightClicked(cPlayer & a_Player) override;
virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override;
@ -45,13 +46,14 @@ public:
m_OwnerUUID = a_NewOwnerUUID;
}
/** Notfies the wolf that the player a_Player is being attacked by a_Attacker.
The wolf will then defend the player by attacking a_Attacker if all these conditions are met:
- a_Player is the wolf's owner.
- The wolf is not already attacking a mob.
- The wolf is not sitting.
This is called by cPlayer::NotifyFriendlyWolves whenever a player takes or deals damage and a wolf is nearby. */
void NearbyPlayerIsFighting(cPlayer * a_Player, cPawn * a_Opponent);
/** Notfies the wolf of a nearby fight.
The wolf may then decide to attack a_Opponent.
If a_IsPlayer is true, then the player whose ID is a_PlayerID is fighting a_Opponent
If false, then a wolf owned by the player whose ID is a_PlayerID is fighting a_Opponent
@param a_PlayerID The ID of the fighting player, or the ID of the owner whose wolf is fighting.
@param a_Opponent The opponent who is being faught.
@param a_IsPlayerInvolved Whether the fighter a player or a wolf. */
void ReceiveNearbyFightInfo(AString a_PlayerID, cPawn * a_Opponent, bool a_IsPlayerInvolved);
virtual void InStateIdle(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override;
@ -64,6 +66,7 @@ protected:
AString m_OwnerName;
AString m_OwnerUUID;
int m_CollarColor;
int m_NotificationCooldown;
} ;