/*==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 . 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::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 >::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 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 = 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 >::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 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 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)); 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 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; }