/*==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 .
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
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 "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_t index)
{
plArmatureBehavior::Init(anim, loop, brain, body, index);
fAvMod = body;
fCritterBrain = brain;
fAnimName = anim->GetName();
}
virtual hsBool PreCondition(double time, float elapsed) {return true;}
float GetAnimLength() {return (fAnim->GetAnimation()->GetLength());}
void SetAnimTime(float time) {fAnim->SetCurrentTime(time, true);}
std::string Name() const {return fName;}
plString 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
plString 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(M_PI/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, float 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::ConvertNoRef(FindModifierByClass(avObj, plAGModifier::Index())));
plPhysicalControllerCore* controller = avMod->GetController();
fCallbackAction = new plWalkingController(avObj, agMod->GetApplicator(kAGPinTransform), controller);
fCallbackAction->ActivateController();
}
// tell people that care that we are good to go
plAIBrainCreatedMsg* brainCreated = 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 = 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 >::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();
}
plString plAvBrainCritter::AnimationName(int behavior) const
{
if ((behavior >= fBehaviors.Count()) || (behavior < 0))
return _TEMP_CONVERT_FROM_LITERAL("");
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(float 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(float 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 plAvBrainCritter::PlayersICanSee() const
{
std::vector allPlayers = IGetAgePlayerIDList();
std::vector onesICanSee;
for (unsigned i = 0; i < allPlayers.size(); ++i)
{
if (CanSeeAvatar(allPlayers[i]))
onesICanSee.push_back(allPlayers[i]);
}
return onesICanSee;
}
std::vector plAvBrainCritter::PlayersICanHear() const
{
std::vector allPlayers = IGetAgePlayerIDList();
std::vector 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 = 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 = 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 >::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())
{
float 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 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();
float 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 = new plAIArrivedAtGoalMsg(fArmature->GetKey(), fReceivers[i]);
msg->Goal(fFinalGoalPos);
msg->Send();
}
}
}
}
float 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 plAvBrainCritter::IGetAgePlayerIDList() const
{
// make a list of non-local players
std::vector playerIDs;
std::map 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));
float 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);
float 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 plAvBrainCritter::IAvatarsICanSee() const
{
std::vector allPlayers = IGetAgePlayerIDList();
std::vector 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 plAvBrainCritter::IAvatarsICanHear() const
{
std::vector allPlayers = IGetAgePlayerIDList();
std::vector 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;
}