|
|
|
/*==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/>.
|
|
|
|
|
|
|
|
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*>(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<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();
|
|
|
|
}
|
|
|
|
|
|
|
|
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), up(0, 1, 0);
|
|
|
|
hsQuat rotation(fSightConeAngle/2, &up);
|
|
|
|
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<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 = 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<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())
|
|
|
|
{
|
|
|
|
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<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();
|
|
|
|
|
|
|
|
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<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));
|
|
|
|
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<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;
|
|
|
|
}
|