You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

726 lines
24 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "hsConfig.h"
#include "hsWindows.h"
#include "plAvCallbackAction.h"
#include "plAvBrainCritter.h"
#include "plAvBrainHuman.h"
#include "plArmatureMod.h"
#include "plAvBehaviors.h"
#include "plAGAnim.h"
#include "plAGAnimInstance.h"
#include "plAvatarMgr.h"
#include "plgDispatch.h"
#include "plMessage/plAIMsg.h"
#include "plPipeline/plDebugText.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "plMath/plRandom.h"
#include "plNetClient/plNetClientMgr.h"
#include "plNetTransport/plNetTransportMember.h"
///////////////////////////////////////////////////////////////////////////////
static plRandom sRandom; // random number generator
const char kDefaultIdleAnimName[] = "Idle";
const char kDefaultIdleBehName[] = "Idle";
const char kDefaultRunAnimName[] = "Run";
const char kDefaultRunBehName[] = "Run";
const float kLoudSoundMultiplyer = 2.0f;
///////////////////////////////////////////////////////////////////////////////
class CritterBehavior : public plArmatureBehavior
{
friend class plAvBrainCritter;
public:
CritterBehavior(const std::string& name, bool randomStart = false, float fadeInLength = 2.f, float fadeOutLength = 2.f) : plArmatureBehavior(),
fAvMod(nil), fCritterBrain(nil), fName(name), fRandomStartPoint(randomStart), fFadeInLength(fadeInLength), fFadeOutLength(fadeOutLength) {}
virtual ~CritterBehavior() {}
void Init(plAGAnim* anim, hsBool loop, plAvBrainCritter* brain, plArmatureMod* body, UInt8 index)
{
plArmatureBehavior::Init(anim, loop, brain, body, index);
fAvMod = body;
fCritterBrain = brain;
fAnimName = anim->GetName();
}
virtual hsBool PreCondition(double time, float elapsed) {return true;}
hsScalar GetAnimLength() {return (fAnim->GetAnimation()->GetLength());}
void SetAnimTime(hsScalar time) {fAnim->SetCurrentTime(time, true);}
std::string Name() const {return fName;}
std::string AnimName() const {return fAnimName;}
bool RandomStartPoint() const {return fRandomStartPoint;}
float FadeInLength() const {return fFadeInLength;}
float FadeOutLength() const {return fFadeOutLength;}
protected:
virtual void IStart()
{
plArmatureBehavior::IStart();
fAvMod->SynchIfLocal(hsTimer::GetSysSeconds(), false);
}
virtual void IStop()
{
plArmatureBehavior::IStop();
fAvMod->SynchIfLocal(hsTimer::GetSysSeconds(), false);
}
plArmatureMod *fAvMod;
plAvBrainCritter *fCritterBrain;
std::string fName; // user-created name for this behavior, also used as the index into the brain's behavior map
std::string fAnimName; // physical animation's name, for reference
bool fRandomStartPoint; // do we want this behavior to start at a random frame every time we start it?
float fFadeInLength; // how long to fade in this behavior
float fFadeOutLength; // how long to fade out this behavior
};
///////////////////////////////////////////////////////////////////////////////
plAvBrainCritter::plAvBrainCritter(): fCallbackAction(nil), fCurMode(kIdle), fNextMode(kIdle), fFadingNextBehavior(true),
fLocallyControlled(false), fAvoidingAvatars(false), fFinalGoalPos(0, 0, 0), fImmediateGoalPos(0, 0, 0), fDotGoal(0),
fAngRight(0)
{
SightCone(hsScalarPI/2); // 90deg
StopDistance(1);
SightDistance(10);
HearingDistance(10);
}
plAvBrainCritter::~plAvBrainCritter()
{
for (int i = 0; i < fBehaviors.GetCount(); ++i)
{
delete fBehaviors[i];
fBehaviors[i] = nil;
}
delete fCallbackAction;
fCallbackAction = nil;
fUserBehaviors.clear();
fReceivers.clear();
}
///////////////////////////////////////////////////////////////////////////////
hsBool plAvBrainCritter::Apply(double time, hsScalar elapsed)
{
// update internal pathfinding variables
IEvalGoal();
if (fNextMode >= kIdle)
{
// next mode is set, fade out the previous mode and start up the new one
IFadeOutBehavior();
IStartBehavior();
}
else
IProcessBehavior(time, elapsed); // just continue with the currently running one
// update our controller to keep us turned and moving to where we want to go
fCallbackAction->RecalcVelocity(time, time - elapsed);
fCallbackAction->SetTurnStrength(IGetTurnStrength(time));
return plArmatureBrain::Apply(time, elapsed);
}
hsBool plAvBrainCritter::MsgReceive(plMessage* msg)
{
return plArmatureBrain::MsgReceive(msg);
}
///////////////////////////////////////////////////////////////////////////////
void plAvBrainCritter::Activate(plArmatureModBase* avMod)
{
plArmatureBrain::Activate(avMod);
// initialize our base "Run" and "Idle" behaviors
IInitBaseAnimations();
// create the controller if we haven't done so already
if (!fCallbackAction)
{
plSceneObject* avObj = fArmature->GetTarget(0);
plAGModifier* agMod = const_cast<plAGModifier*>(plAGModifier::ConvertNoRef(FindModifierByClass(avObj, plAGModifier::Index())));
plPhysicalControllerCore* controller = avMod->GetController();
fCallbackAction = TRACKED_NEW plWalkingController(avObj, agMod->GetApplicator(kAGPinTransform), controller);
fCallbackAction->ActivateController();
}
// tell people that care that we are good to go
plAIBrainCreatedMsg* brainCreated = TRACKED_NEW plAIBrainCreatedMsg(fArmature->GetKey());
plgDispatch::MsgSend(brainCreated);
}
void plAvBrainCritter::Deactivate()
{
plArmatureBrain::Deactivate();
}
void plAvBrainCritter::Suspend()
{
// fade out the previous behavior
CritterBehavior *behavior = (CritterBehavior*)fBehaviors[fCurMode];
behavior->SetStrength(0.f, fFadingNextBehavior ? behavior->FadeOutLength() : 0.f);
// fade in the idle
fNextMode = kIdle;
plArmatureBrain::Suspend();
}
void plAvBrainCritter::Resume()
{
// fade in the idle
fNextMode = kIdle;
fCallbackAction->Reset(false);
plArmatureBrain::Resume();
}
void plAvBrainCritter::AddBehavior(const std::string& animationName, const std::string& behaviorName, bool loop /* = true */, bool randomStartPos /* = true */,
float fadeInLen /* = 2.f */, float fadeOutLen /* = 2.f */)
{
// grab the animations
plAGAnim* anim = fAvMod->FindCustomAnim(animationName.c_str());
if (!anim)
return; // can't find it, die
// create the behavior and set it up
CritterBehavior* behavior = TRACKED_NEW CritterBehavior(behaviorName, randomStartPos, fadeInLen, fadeOutLen);
fBehaviors.Push(behavior);
behavior->Init(anim, loop, this, fAvMod, fBehaviors.Count() - 1);
fUserBehaviors[behaviorName].push_back(fBehaviors.Count() - 1);
}
void plAvBrainCritter::StartBehavior(const std::string& behaviorName, bool fade /* = true */)
{
// make sure the new behavior exists
if (fUserBehaviors.find(behaviorName) == fUserBehaviors.end())
return;
else
{
if (fUserBehaviors[behaviorName].size() == 0)
return;
}
// remember the fade request
fFadingNextBehavior = fade;
// pick our next behavior
fNextMode = IPickBehavior(behaviorName);
}
bool plAvBrainCritter::RunningBehavior(const std::string& behaviorName) const
{
// make sure the behavior exists
std::map<std::string, std::vector<int> >::const_iterator behaviorIterator = fUserBehaviors.find(behaviorName);
if (behaviorIterator == fUserBehaviors.end())
return false;
else
{
if (behaviorIterator->second.size() == 0)
return false;
}
// check all behaviors that use this tag and return true if we are running one of them
for (unsigned i = 0; i < behaviorIterator->second.size(); ++i)
{
if (fCurMode == behaviorIterator->second[i])
return true;
}
return false;
}
std::string plAvBrainCritter::BehaviorName(int behavior) const
{
if ((behavior >= fBehaviors.Count()) || (behavior < 0))
return "";
return ((CritterBehavior*)fBehaviors[behavior])->Name();
}
std::string plAvBrainCritter::AnimationName(int behavior) const
{
if ((behavior >= fBehaviors.Count()) || (behavior < 0))
return "";
return ((CritterBehavior*)fBehaviors[behavior])->AnimName();
}
std::string plAvBrainCritter::IdleBehaviorName() const
{
return kDefaultIdleBehName;
}
std::string plAvBrainCritter::RunBehaviorName() const
{
return kDefaultRunBehName;
}
void plAvBrainCritter::GoToGoal(hsPoint3 newGoal, bool avoidingAvatars /* = false */)
{
fFinalGoalPos = newGoal;
fAvoidingAvatars = avoidingAvatars;
fNextMode = IPickBehavior(kRun);
// TODO: Pathfinding here!
}
bool plAvBrainCritter::AtGoal() const
{
// we are at our goal if our distance from it is less then or equal to our stopping distance
hsPoint3 creaturePos;
hsQuat creatureRot;
fAvMod->GetPositionAndRotationSim(&creaturePos, &creatureRot);
hsVector3 finalGoalVec(creaturePos - fFinalGoalPos);
return (finalGoalVec.MagnitudeSquared() <= fStopDistanceSquared);
}
void plAvBrainCritter::SightCone(hsScalar coneRad)
{
fSightConeAngle = coneRad;
// calculate the minimum dot product for the cone of sight (angle/2 vector dotted with straight ahead)
hsVector3 straightVector(1, 0, 0), viewVector(1, 0, 0);
hsQuat rotation(fSightConeAngle/2, &hsVector3(0, 1, 0));
viewVector = hsVector3(rotation.Rotate(&viewVector));
viewVector.Normalize();
fSightConeDotMin = straightVector * viewVector;
}
void plAvBrainCritter::HearingDistance(hsScalar hearDis)
{
fHearingDistance = hearDis;
fHearingDistanceSquared = fHearingDistance * fHearingDistance;
fLoudHearingDistanceSquared = (fHearingDistance * kLoudSoundMultiplyer) * (fHearingDistance * kLoudSoundMultiplyer);
}
bool plAvBrainCritter::CanSeeAvatar(unsigned long id) const
{
plArmatureMod* avatar = plAvatarMgr::GetInstance()->FindAvatarByPlayerID(id);
if (avatar)
return ICanSeeAvatar(avatar);
return false;
}
bool plAvBrainCritter::CanHearAvatar(unsigned long id) const
{
plArmatureMod* avatar = plAvatarMgr::GetInstance()->FindAvatarByPlayerID(id);
if (avatar)
return ICanHearAvatar(avatar);
return false;
}
std::vector<unsigned long> plAvBrainCritter::PlayersICanSee() const
{
std::vector<unsigned long> allPlayers = IGetAgePlayerIDList();
std::vector<unsigned long> onesICanSee;
for (unsigned i = 0; i < allPlayers.size(); ++i)
{
if (CanSeeAvatar(allPlayers[i]))
onesICanSee.push_back(allPlayers[i]);
}
return onesICanSee;
}
std::vector<unsigned long> plAvBrainCritter::PlayersICanHear() const
{
std::vector<unsigned long> allPlayers = IGetAgePlayerIDList();
std::vector<unsigned long> onesICanHear;
for (unsigned i = 0; i < allPlayers.size(); ++i)
{
if (CanHearAvatar(allPlayers[i]))
onesICanHear.push_back(allPlayers[i]);
}
return onesICanHear;
}
hsVector3 plAvBrainCritter::VectorToPlayer(unsigned long id) const
{
plArmatureMod* avatar = plAvatarMgr::GetInstance()->FindAvatarByPlayerID(id);
if (!avatar)
return hsVector3(0, 0, 0);
hsPoint3 avPos;
hsQuat avRot;
avatar->GetPositionAndRotationSim(&avPos, &avRot);
hsPoint3 creaturePos;
hsQuat creatureRot;
fAvMod->GetPositionAndRotationSim(&creaturePos, &creatureRot);
return hsVector3(creaturePos - avPos);
}
void plAvBrainCritter::AddReceiver(const plKey key)
{
for (unsigned i = 0; i < fReceivers.size(); ++i)
{
if (fReceivers[i] == key)
return; // already in our list
}
fReceivers.push_back(key);
}
void plAvBrainCritter::RemoveReceiver(const plKey key)
{
for (unsigned i = 0; i < fReceivers.size(); ++i)
{
if (fReceivers[i] == key)
{
fReceivers.erase(fReceivers.begin() + i);
return;
}
}
return; // not found, do nothing
}
void plAvBrainCritter::DumpToDebugDisplay(int& x, int& y, int lineHeight, char* strBuf, plDebugText& debugTxt)
{
sprintf(strBuf, "Brain type: Critter");
debugTxt.DrawString(x, y, strBuf, 0, 255, 255);
y += lineHeight;
// extract the name from the behavior running
if (fBehaviors[fCurMode])
sprintf(strBuf, "Mode: %s", ((CritterBehavior*)(fBehaviors[fCurMode]))->Name().c_str());
else
sprintf(strBuf, "Mode: Unknown");
// draw it
debugTxt.DrawString(x, y, strBuf);
y += lineHeight;
for (int i = 0; i < fBehaviors.GetCount(); ++i)
fBehaviors[i]->DumpDebug(x, y, lineHeight, strBuf, debugTxt);
}
///////////////////////////////////////////////////////////////////////////////
hsBool plAvBrainCritter::IInitBaseAnimations()
{
// create the basic idle and run behaviors, and put them into our appropriate structures
plAGAnim* idle = fAvMod->FindCustomAnim(kDefaultIdleAnimName);
plAGAnim* run = fAvMod->FindCustomAnim(kDefaultRunAnimName);
hsAssert(idle, "Creature is missing idle animation");
hsAssert(run, "Creature is missing run animation");
fBehaviors.SetCountAndZero(kNumDefaultModes);
CritterBehavior* behavior;
if (idle)
{
fBehaviors[kIdle] = behavior = TRACKED_NEW CritterBehavior(kDefaultIdleBehName, true); // starts at a random start point each time
behavior->Init(idle, true, this, fAvMod, kIdle);
fUserBehaviors[kDefaultIdleBehName].push_back(kIdle);
}
if (run)
{
fBehaviors[kRun] = behavior = TRACKED_NEW CritterBehavior(kDefaultRunBehName);
behavior->Init(run, true, this, fAvMod, kRun);
fUserBehaviors[kDefaultRunBehName].push_back(kRun);
}
return true;
}
int plAvBrainCritter::IPickBehavior(int behavior) const
{
if ((behavior >= fBehaviors.Count()) || (behavior < 0))
return IPickBehavior(kDefaultIdleBehName); // do an idle if the behavior is invalid
CritterBehavior* behaviorObj = (CritterBehavior*)(fBehaviors[behavior]);
return IPickBehavior(behaviorObj->Name());
}
int plAvBrainCritter::IPickBehavior(const std::string& behavior) const
{
// make sure the behavior exists
std::map<std::string, std::vector<int> >::const_iterator behaviorIterator = fUserBehaviors.find(behavior);
if (behaviorIterator == fUserBehaviors.end())
{
if (behavior != kDefaultIdleBehName)
return IPickBehavior(kDefaultIdleBehName); // do an idle if the behavior is invalid
return -1; // can't recover from being unable to find an idle!
}
else
{
unsigned numBehaviors = behaviorIterator->second.size();
if (numBehaviors == 0)
{
if (behavior != kDefaultIdleBehName)
return IPickBehavior(kDefaultIdleBehName); // do an idle if the behavior is invalid
return -1; // can't recover from being unable to find an idle!
}
// pick our behavior
unsigned index = sRandom.RandRangeI(0, numBehaviors - 1);
return behaviorIterator->second[index];
}
}
void plAvBrainCritter::IFadeOutBehavior()
{
if ((fCurMode >= fBehaviors.Count()) || (fCurMode < 0))
return; // invalid fCurMode
// fade out currently playing behavior
CritterBehavior* behavior = (CritterBehavior*)fBehaviors[fCurMode];
behavior->SetStrength(0.f, fFadingNextBehavior ? behavior->FadeOutLength() : 0.f);
}
void plAvBrainCritter::IStartBehavior()
{
if ((fNextMode >= fBehaviors.Count()) || (fNextMode < 0))
return; // invalid fNextMode
// fade in our behavior
CritterBehavior* behavior = (CritterBehavior*)fBehaviors[fNextMode];
behavior->SetStrength(1.f, fFadingNextBehavior ? behavior->FadeInLength() : 0.f);
// if we start at a random point, do so
if (behavior->RandomStartPoint())
{
hsScalar newStart = sRandom.RandZeroToOne() * behavior->GetAnimLength();
behavior->SetAnimTime(newStart);
}
// clean up the internal variables
fCurMode = fNextMode;
fNextMode = -1;
}
void plAvBrainCritter::IProcessBehavior(double time, float elapsed)
{
// run the currently running behavior
CritterBehavior* behavior = (CritterBehavior*)fBehaviors[fCurMode];
behavior->SetStrength(1.f, fFadingNextBehavior ? behavior->FadeInLength() : 0.f);
behavior->Process(time, elapsed);
}
void plAvBrainCritter::IEvalGoal()
{
// TODO: Implement pathfinding logic here
// (for now, this runs directly towards the goal)
fImmediateGoalPos = fFinalGoalPos;
// where am I relative to my goal?
const plSceneObject* creatureObj = fArmature->GetTarget(0);
hsVector3 view(creatureObj->GetCoordinateInterface()->GetLocalToWorld().GetAxis(hsMatrix44::kView));
hsPoint3 creaturePos;
hsQuat creatureRot;
fAvMod->GetPositionAndRotationSim(&creaturePos, &creatureRot);
hsVector3 goalVec(creaturePos - fImmediateGoalPos);
goalVec.Normalize();
fDotGoal = goalVec * view; // 1 = directly facing, 0 = 90 deg off, -1 = facing away
// calculate a vector pointing to the creature's right
hsQuat invRot = creatureRot.Conjugate();
hsPoint3 globRight = invRot.Rotate(&kAvatarRight);
fAngRight = globRight.InnerProduct(goalVec); // dot product, 1 = goal is 90 to the right, 0 = goal is in front or behind, -1 = goal is 90 to the left
if (fAvoidingAvatars)
{
// check to see we can see anyone in our way (if we can't see them, we can't avoid them)
std::vector<plArmatureMod*> playersICanSee = IAvatarsICanSee();
for (unsigned i = 0; i < playersICanSee.size(); ++i)
{
hsPoint3 avPos;
hsQuat avRot;
playersICanSee[i]->GetPositionAndRotationSim(&avPos, &avRot);
hsVector3 avVec(creaturePos - avPos);
avVec.Normalize();
hsScalar dotAv = avVec * goalVec;
if (dotAv > 0.5f) // within a 45deg angle in front of us
{
// a player is in the way, so we will change our "goal" to a 90deg angle from the player
// then we stop searching, since any other players in the way will just produce the same (or similar) result
avVec.fZ = goalVec.fZ = 0.f;
goalVec = goalVec % avVec;
fAngRight = globRight.InnerProduct(goalVec);
break;
}
}
}
// are we at our final goal?
if (AtGoal())
{
if (RunningBehavior(kDefaultRunBehName)) // don't do anything if we're not running!
{
// we're close enough, stop running and pick an idle
fNextMode = IPickBehavior(kIdle);
// tell everyone who cares that we have arrived
for (unsigned i = 0; i < fReceivers.size(); ++i)
{
plAIArrivedAtGoalMsg* msg = TRACKED_NEW plAIArrivedAtGoalMsg(fArmature->GetKey(), fReceivers[i]);
msg->Goal(fFinalGoalPos);
msg->Send();
}
}
}
}
hsScalar plAvBrainCritter::IGetTurnStrength(double time) const
{
if (!RunningBehavior(kDefaultRunBehName))
return 0.0f;
// am I directly facing my goal?
if (fDotGoal < -0.98)
return 0.f;
if (fAngRight > 0.f)
return 1.f;
return -1.f;
}
std::vector<unsigned long> plAvBrainCritter::IGetAgePlayerIDList() const
{
// make a list of non-local players
std::vector<unsigned long> playerIDs;
std::map<unsigned long, bool> tempMap; // slightly hacky way to remove dups
plNetClientMgr* nc = plNetClientMgr::GetInstance();
for (int i = 0; i < nc->TransportMgr().GetNumMembers(); ++i)
{
plNetTransportMember* mbr = nc->TransportMgr().GetMember(i);
unsigned long id = mbr->GetPlayerID();
if (tempMap.find(id) == tempMap.end())
{
playerIDs.push_back(id);
tempMap[id] = true;
}
}
// add the local player if he isn't already in the list
unsigned long localID = nc->GetPlayerID();
if (tempMap.find(localID) == tempMap.end())
playerIDs.push_back(localID);
// return result
return playerIDs;
}
bool plAvBrainCritter::ICanSeeAvatar(plArmatureMod* avatar) const
{
// sight is a x deg cone in front of the critter, cuts off at a certain distance
hsPoint3 avPos;
hsQuat avRot;
avatar->GetPositionAndRotationSim(&avPos, &avRot);
hsPoint3 creaturePos;
hsQuat creatureRot;
fAvMod->GetPositionAndRotationSim(&creaturePos, &creatureRot);
hsVector3 avVec(creaturePos - avPos);
if (avVec.MagnitudeSquared() > fSightDistanceSquared)
return false; // too far away
avVec.Normalize();
const plSceneObject* creatureObj = fArmature->GetTarget(0);
hsVector3 view(creatureObj->GetCoordinateInterface()->GetLocalToWorld().GetAxis(hsMatrix44::kView));
hsScalar avDot = view * avVec;
if (avDot < fSightConeDotMin)
return false; // out of our cone of view
return true;
}
bool plAvBrainCritter::ICanHearAvatar(plArmatureMod* avatar) const
{
// check to see if the avatar is being loud (running or jumping)
bool isLoud = false;
plAvBrainHuman* humanBrain = plAvBrainHuman::ConvertNoRef(avatar->FindBrainByClass(plAvBrainHuman::Index()));
if (humanBrain)
{
isLoud = humanBrain->IsBehaviorPlaying(plAvBrainHuman::kRun) || humanBrain->IsBehaviorPlaying(plAvBrainHuman::kStandingJump) ||
humanBrain->IsBehaviorPlaying(plAvBrainHuman::kWalkingJump) || humanBrain->IsBehaviorPlaying(plAvBrainHuman::kRunningJump) ||
humanBrain->IsBehaviorPlaying(plAvBrainHuman::kGroundImpact) || humanBrain->IsBehaviorPlaying(plAvBrainHuman::kRunningImpact);
}
// hearing is 360 degrees around the critter, cuts off at a certain distance
hsPoint3 avPos;
hsQuat avRot;
avatar->GetPositionAndRotationSim(&avPos, &avRot);
hsPoint3 creaturePos;
hsQuat creatureRot;
fAvMod->GetPositionAndRotationSim(&creaturePos, &creatureRot);
hsVector3 avVec(creaturePos - avPos);
hsScalar distSq = avVec.MagnitudeSquared();
if (distSq <= fHearingDistanceSquared)
return true; // within our normal hearing distance
else if (isLoud && (distSq <= fLoudHearingDistanceSquared))
return true; // they are being loud, and within our loud hearing distance
return false;
}
std::vector<plArmatureMod*> plAvBrainCritter::IAvatarsICanSee() const
{
std::vector<unsigned long> allPlayers = IGetAgePlayerIDList();
std::vector<plArmatureMod*> onesICanSee;
for (unsigned i = 0; i < allPlayers.size(); ++i)
{
plArmatureMod* avatar = plAvatarMgr::GetInstance()->FindAvatarByPlayerID(allPlayers[i]);
if (!avatar)
continue;
if (ICanSeeAvatar(avatar))
onesICanSee.push_back(avatar);
}
return onesICanSee;
}
std::vector<plArmatureMod*> plAvBrainCritter::IAvatarsICanHear() const
{
std::vector<unsigned long> allPlayers = IGetAgePlayerIDList();
std::vector<plArmatureMod*> onesICanHear;
for (unsigned i = 0; i < allPlayers.size(); ++i)
{
plArmatureMod* avatar = plAvatarMgr::GetInstance()->FindAvatarByPlayerID(allPlayers[i]);
if (!avatar)
continue;
if (ICanHearAvatar(avatar))
onesICanHear.push_back(avatar);
}
return onesICanHear;
}