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.
742 lines
24 KiB
742 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/>. |
|
|
|
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 "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; |
|
}
|
|
|