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

@ -37,7 +37,7 @@ public:
} ;
bool Results[] = {true, true, true, false, true, false};
double LineCoeffs[] = {2, 0.25, 0.5, 0, 0.25, 0};
for (size_t i = 0; i < ARRAYCOUNT(LineDefs) / 2; i++)
{
double LineCoeff;
@ -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)
@ -269,10 +279,10 @@ bool cBoundingBox::CalcLineIntersection(const Vector3d & a_Min, const Vector3d &
a_Face = BLOCK_FACE_NONE; // No faces hit
return true;
}
eBlockFace Face = BLOCK_FACE_NONE;
double Coeff = Vector3d::NO_INTERSECTION;
// Check each individual bbox face for intersection with the line, remember the one with the lowest coeff
double c = a_Line1.LineCoeffToXYPlane(a_Line2, a_Min.z);
if ((c >= 0) && (c < Coeff) && IsInside(a_Min, a_Max, a_Line1 + (a_Line2 - a_Line1) * c))
@ -310,13 +320,13 @@ bool cBoundingBox::CalcLineIntersection(const Vector3d & a_Min, const Vector3d &
Face = (a_Line1.x > a_Line2.x) ? BLOCK_FACE_XP : BLOCK_FACE_XM;
Coeff = c;
}
if (Coeff >= Vector3d::NO_INTERSECTION)
{
// There has been no intersection
return false;
}
a_LineCoeff = Coeff;
a_Face = Face;
return true;

View File

@ -27,56 +27,57 @@ 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 */
void Move(double a_OffX, double a_OffY, double a_OffZ);
/** Moves the entire boundingbox by the specified offset */
void Move(const Vector3d & a_Off);
/** Expands the bounding box by the specified amount in each direction (so the box becomes larger by 2 * Expand in each direction) */
void Expand(double a_ExpandX, double a_ExpandY, double a_ExpandZ);
/** Returns true if the two bounding boxes intersect */
bool DoesIntersect(const cBoundingBox & a_Other);
/** Returns the union of the two bounding boxes */
cBoundingBox Union(const cBoundingBox & a_Other);
/** Returns true if the point is inside the bounding box */
bool IsInside(const Vector3d & a_Point);
/** Returns true if the point is inside the bounding box */
bool IsInside(double a_X, double a_Y, double a_Z);
/** Returns true if a_Other is inside this bounding box */
bool IsInside(cBoundingBox & a_Other);
/** Returns true if a boundingbox specified by a_Min and a_Max is inside this bounding box */
bool IsInside(const Vector3d & a_Min, const Vector3d & a_Max);
/** Returns true if the specified point is inside the bounding box specified by its min / max corners */
static bool IsInside(const Vector3d & a_Min, const Vector3d & a_Max, const Vector3d & a_Point);
/** Returns true if the specified point is inside the bounding box specified by its min / max corners */
static bool IsInside(const Vector3d & a_Min, const Vector3d & a_Max, double a_X, double a_Y, double a_Z);
/** Returns true if this bounding box is intersected by the line specified by its two points
Also calculates the distance along the line in which the intersection occurs (0 .. 1)
Only forward collisions (a_LineCoeff >= 0) are returned. */
bool CalcLineIntersection(const Vector3d & a_Line1, const Vector3d & a_Line2, double & a_LineCoeff, eBlockFace & a_Face);
/** Returns true if the specified bounding box is intersected by the line specified by its two points
Also calculates the distance along the line in which the intersection occurs (0 .. 1) and the face hit (BLOCK_FACE_ constants)
Only forward collisions (a_LineCoeff >= 0) are returned. */
static bool CalcLineIntersection(const Vector3d & a_Min, const Vector3d & a_Max, const Vector3d & a_Line1, const Vector3d & a_Line2, double & a_LineCoeff, eBlockFace & a_Face);
// tolua_end
/** Calculates the intersection of the two bounding boxes; returns true if nonempty */
bool Intersect(const cBoundingBox & a_Other, cBoundingBox & a_Intersection);
double GetMinX(void) const { return m_Min.x; }
double GetMinY(void) const { return m_Min.y; }
double GetMinZ(void) const { return m_Min.z; }
@ -84,14 +85,14 @@ public:
double GetMaxX(void) const { return m_Max.x; }
double GetMaxY(void) const { return m_Max.y; }
double GetMaxZ(void) const { return m_Max.z; }
const Vector3d & GetMin(void) const { return m_Min; }
const Vector3d & GetMax(void) const { return m_Max; }
protected:
Vector3d m_Min;
Vector3d m_Max;
} ; // tolua_export

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())
{
m_IsAngry = true;
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;
return false;
}
}
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);
if (Wolf->GetOwnerUUID() == GetOwnerUUID())
{
m_Target = nullptr; // Our owner attacked one of their wolves. Abort attacking wolf.
}
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())
{
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;
} ;