/*==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" // subclasses a havok object; must be in first include section #include "plAvBrainHuman.h" #include "plAvBrainClimb.h" #include "plAvBrainDrive.h" #include "plAvBrainGeneric.h" #include "plAvBrainSwim.h" #include "plArmatureMod.h" #include "plAGModifier.h" #include "plMatrixChannel.h" #include "plAvTask.h" #include "plAvTaskBrain.h" #include "plAvTaskSeek.h" #include "plAGAnim.h" #include "plAGAnimInstance.h" #include "plAvatarMgr.h" #include "plAnimStage.h" #include "plAvatarClothing.h" #include "hsTimer.h" #include "hsGeometry3.h" #include "float.h" #include "plPipeline.h" #include "plgDispatch.h" #include "hsQuat.h" #include "plPhysical.h" #include "plStatusLog/plStatusLog.h" #include "pnNetCommon/plNetApp.h" #include "pnSceneObject/plCoordinateInterface.h" #include "plInputCore/plAvatarInputInterface.h" #include "plInputCore/plInputDevice.h" #include "plMath/plRandom.h" #include "plPipeline/plDebugText.h" #include "plNetClient/plNetLinkingMgr.h" #include "plMessage/plAvatarMsg.h" #include "plMessage/plClimbMsg.h" #include "plMessage/plInputEventMsg.h" #include "plMessage/plLOSHitMsg.h" #include "plMessage/plLOSRequestMsg.h" #include "plMessage/plSimStateMsg.h" #include "plMessage/plSwimMsg.h" #include "plMessage/plAgeLoadedMsg.h" #include "pnMessage/plWarpMsg.h" #include "pnMessage/plProxyDrawMsg.h" #include "plMessage/plRideAnimatedPhysMsg.h" float plAvBrainHuman::fWalkTimeToMaxTurn = .3f; float plAvBrainHuman::fRunTimeToMaxTurn = .1f; float plAvBrainHuman::fWalkMaxTurnSpeed = 2.0f; float plAvBrainHuman::fRunMaxTurnSpeed = 1.7; plAvBrainHuman::TurnCurve plAvBrainHuman::fWalkTurnCurve = plAvBrainHuman::kTurnExponential; plAvBrainHuman::TurnCurve plAvBrainHuman::fRunTurnCurve = plAvBrainHuman::kTurnExponential; const hsScalar plAvBrainHuman::kAirTimePanicThreshold = 10; // seconds void plAvBrainHuman::SetTimeToMaxTurn(float time, hsBool walk) { if (walk) fWalkTimeToMaxTurn = time; else fRunTimeToMaxTurn = time; } float plAvBrainHuman::GetTimeToMaxTurn(hsBool walk) { return (walk ? fWalkTimeToMaxTurn : fRunTimeToMaxTurn); } void plAvBrainHuman::SetMaxTurnSpeed(float radsPerSec, hsBool walk) { if (walk) fWalkMaxTurnSpeed = radsPerSec; else fRunMaxTurnSpeed = radsPerSec; } float plAvBrainHuman::GetMaxTurnSpeed(hsBool walk) { return (walk ? fWalkMaxTurnSpeed : fRunMaxTurnSpeed); } void plAvBrainHuman::SetTurnCurve(TurnCurve curve, hsBool walk) { if (walk) fWalkTurnCurve = curve; else fRunTurnCurve = curve; } plAvBrainHuman::TurnCurve plAvBrainHuman::GetTurnCurve(hsBool walk) { return (walk ? fWalkTurnCurve : fRunTurnCurve); } plAvBrainHuman::plAvBrainHuman(bool isActor /* = false */) : fHandleAGMod(nil), fStartedTurning(-1.0f), fCallbackAction(nil), fPreconditions(0), fIsActor(isActor) { } hsBool plAvBrainHuman::Apply(double timeNow, hsScalar elapsed) { #ifndef _DEBUG try { #endif // SetTurnStrength runs first to make sure it's set to a sane value // (or cleared). RunStandardBehaviors may overwrite it. fCallbackAction->SetTurnStrength(IGetTurnStrength(timeNow)); RunStandardBehaviors(timeNow, elapsed); fCallbackAction->RecalcVelocity(timeNow, timeNow - elapsed, (fPreconditions & plHBehavior::kBehaviorTypeNeedsRecalcMask)); plArmatureBrain::Apply(timeNow, elapsed); #ifndef _DEBUG } catch (...) { // just catch all the crashes on exit... plStatusLog *log = plAvatarMgr::GetInstance()->GetLog(); log->AddLine("plAvBrainHuman::Apply - crash caught"); } #endif return true; } void plAvBrainHuman::Activate(plArmatureModBase *avMod) { plArmatureBrain::Activate(avMod); IInitBoneMap(); IInitAnimations(); 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(); } plSceneObject *avSO = fAvMod->GetTarget(0); hsBool isLocal = avSO->IsLocallyOwned(); if (fAvMod->GetClothingOutfit() && fAvMod->GetClothingOutfit()->fGroup != plClothingMgr::kClothingBaseNoOptions) { if (fAvMod->IsLocalAvatar()) fAvMod->GetClothingOutfit()->ReadFromVault(); else { fAvMod->GetClothingOutfit()->WearDefaultClothing(); fAvMod->GetClothingOutfit()->ForceUpdate(true); } } if (fAvMod == plAvatarMgr::GetInstance()->GetLocalAvatar()) plAvatarInputInterface::GetInstance()->ForceAlwaysRun(plKeyboardDevice::GetInstance()->IsCapsLockKeyOn() != 0); } void plAvBrainHuman::IInitBoneMap() { struct tuple { HumanBoneID fID; const char * fName; }; tuple tupleMap[] = { { Pelvis, "Bone_Root" }, // left leg { LThigh, "Bone_LThigh" }, { LCalf, "Bone_LCalf" }, { LFoot, "Bone_LFoot" }, { LFootPrint, "Print_L Foot" }, { LToe0, "Bone_LToe" }, // right leg { RThigh, "Bone_RThigh" }, { RCalf, "Bone_RCalf" }, { RFoot, "Bone_RFoot" }, { RFootPrint, "Print_R Foot" }, { RToe0, "Bone_RToe" }, // spine and head, starting at base of spine { Spine, "Bone_Spine0" }, { TrunkPrint, "Print_Trunk" }, { Spine1, "Bone_Spine1" }, { Spine2, "Bone_Spine2" }, { Neck, "Bone_Neck" }, { Head, "Bone_Head" }, { Jaw, "Bone_Jaw" }, // left face bones { LMouthLower, "Bone_LMouthLower" }, { RMouthLower, "Bone_RMouthLower" }, { LBrowInner, "Bone_LBrowInner" }, { LBrowOuter, "Bone_LBrowOuter" }, { LCheek, "Bone_LCheek" }, { LEye, "Bone_LEye" }, { LEyeLid01, "Bone_LEyeLid1" }, { LEyeLid02, "Bone_LEyeLid2" }, { LMouthCorner, "Bone_LMouthCorner" }, { LMouthUpper, "Bone_LMouthUpper" }, // right face bones { RBrowInner, "Bone_RBrowInner" }, { RBrowOuter, "Bone_RBrowOuter" }, { RCheek, "Bone_RCheek" }, { REye, "Bone_REye" }, { REyeLid01, "Bone_REyeLid1" }, { REyeLid02, "Bone_REyeLid2" }, { RMouthCorner, "Bone_RMouthCorner" }, { RMouthUpper, "Bone_RMouthUpper" }, // Left Arm { LClavicle, "Bone_LClavicle" }, { LUpperArm, "Bone_LUpperArm" }, { LForearm, "Bone_LForearm" }, { LHand, "Bone_LHand" }, { LHandPrint, "Print_L Hand" }, { LMiddleFinger1, "Bone_LMiddle1" }, { LMiddleFinger2, "Bone_LMiddle2" }, { LMiddleFinger3, "Bone_LMiddle3" }, { LPinkyFinger1, "Bone_LPinky1" }, { LPinkyFinger2, "Bone_LPinky2" }, { LPinkyFinger3, "Bone_LPinky3" }, { LPointerFinger1, "Bone_LPointer1" }, { LPointerFinger2, "Bone_LPointer2" }, { LPointerFinger3, "Bone_LPointer3" }, { LRingFinger1, "Bone_LRing1" }, { LRingFinger2, "Bone_LRing2" }, { LRingFinger3, "Bone_LRing3" }, { LThumb1, "Bone_LThumb1" }, { LThumb2, "Bone_LThumb2" }, { LThumb3, "Bone_LThumb3" }, // Right Arm { RClavicle, "Bone_RClavicle" }, { RUpperArm, "Bone_RUpperArm" }, { RForearm, "Bone_RForearm" }, { RHand, "Bone_RHand" }, { RHandPrint, "Print_R Hand" }, { RMiddleFinger1, "Bone_RMiddle1" }, { RMiddleFinger2, "Bone_RMiddle2" }, { RMiddleFinger3, "Bone_RMiddle3" }, { RPinkyFinger1, "Bone_RPinky1" }, { RPinkyFinger2, "Bone_RPinky2" }, { RPinkyFinger3, "Bone_RPinky3" }, { RPointerFinger1, "Bone_RPointer1" }, { RPointerFinger2, "Bone_RPointer2" }, { RPointerFinger3, "Bone_RPointer3" }, { RRingFinger1, "Bone_RRing1" }, { RRingFinger2, "Bone_RRing2" }, { RRingFinger3, "Bone_RRing3" }, { RThumb1, "Bone_RThumb1" }, { RThumb2, "Bone_RThumb2" }, { RThumb3, "Bone_RThumb3" }, }; int numTuples = sizeof(tupleMap) / sizeof(tuple); for(int i = 0; i < numTuples; i++) { HumanBoneID id = tupleMap[i].fID; const char * name = tupleMap[i].fName; const plSceneObject * bone = this->fAvMod->FindBone(name); if( bone ) { fAvMod->AddBoneMapping(id, bone); } else hsStatusMessageF("Couldn't find standard bone %s.", name); } } plAvBrainHuman::~plAvBrainHuman() { int i; for (i = 0; i < fBehaviors.GetCount(); i++) delete fBehaviors[i]; fBehaviors.Reset(); delete fCallbackAction; fCallbackAction = nil; } void plAvBrainHuman::Deactivate() { // fAvMod will be nil here when exporting. if (fAvMod) plAvatarMgr::GetInstance()->RemoveAvatar(fAvMod); // unregister plArmatureBrain::Deactivate(); } void plAvBrainHuman::Suspend() { // Kind of hacky... but this is a rather rare case. // If the user lets up on the PushToTalk key in another brain // we'll miss the message to take off the animation. char *chatAnimName = fAvMod->MakeAnimationName("Talk"); plAGAnimInstance *anim = fAvMod->FindAnimInstance(chatAnimName); delete [] chatAnimName; if (anim) anim->FadeAndDetach(0, 1); IdleOnly(); plArmatureBrain::Suspend(); } void plAvBrainHuman::Resume() { // If we were in another brain when the key was pressed, we missed it. if (fAvMod->GetInputFlag(S_PUSH_TO_TALK)) IChatOn(); fCallbackAction->Reset(false); plArmatureBrain::Resume(); } hsBool plAvBrainHuman::IHandleControlMsg(plControlEventMsg* msg) { ControlEventCode moveCode = msg->GetControlCode(); if( msg->ControlActivated() ) { switch(moveCode) { case B_CONTROL_TOGGLE_PHYSICAL: { #ifndef PLASMA_EXTERNAL_RELEASE // external clients can't go non-physical plAvBrainDrive *driver = TRACKED_NEW plAvBrainDrive(20, 1); fAvMod->PushBrain(driver); #endif return true; } break; case S_PUSH_TO_TALK: fAvMod->SetInputFlag(S_PUSH_TO_TALK, true); IChatOn(); return true; break; } } else { switch(moveCode) { case S_PUSH_TO_TALK: fAvMod->SetInputFlag(S_PUSH_TO_TALK, false); IChatOff(); return true; break; } } return false; } bool plAvBrainHuman::IsMovingForward() { if ((fBehaviors.Count() <= kWalk) || (fBehaviors.Count() <= kRun)) return false; // behaviors aren't set up yet return (fBehaviors[kWalk]->GetStrength() > 0 || fBehaviors[kRun]->GetStrength() > 0); } bool plAvBrainHuman::IsBehaviorPlaying(int behavior) { if ((behavior < 0) || (behavior >= fBehaviors.Count())) return false; if (!fBehaviors[behavior]) return false; return (fBehaviors[behavior]->GetStrength() > 0); } void plAvBrainHuman::Write(hsStream *stream, hsResMgr *mgr) { plArmatureBrain::Write(stream, mgr); stream->WriteBool(fIsActor); } void plAvBrainHuman::Read(hsStream *stream, hsResMgr *mgr) { plArmatureBrain::Read(stream, mgr); fIsActor = stream->ReadBool(); } hsBool plAvBrainHuman::MsgReceive(plMessage * msg) { plControlEventMsg *ctrlMsg = plControlEventMsg::ConvertNoRef(msg); if (ctrlMsg) { return IHandleControlMsg(ctrlMsg); } plClimbMsg *climb = plClimbMsg::ConvertNoRef(msg); if (climb) { return IHandleClimbMsg(climb); } plSwimMsg *swim = plSwimMsg::ConvertNoRef(msg); if (swim) { if (swim->GetIsEntering()) { plAvBrainSwim *swimBrain = TRACKED_NEW plAvBrainSwim(); swimBrain->MsgReceive(swim); fAvMod->PushBrain(swimBrain); } else { hsStatusMessage("Got non-entering swim message. Discarding."); } return true; } plRideAnimatedPhysMsg *ride = plRideAnimatedPhysMsg::ConvertNoRef(msg); if(ride) { if(ride->Entering()) { //plAvBrainRideAnimatedPhysical *rideBrain = TRACKED_NEW plAvBrainRideAnimatedPhysical(); //fAvMod->PushBrain(rideBrain); delete fCallbackAction; plSceneObject* avObj = fArmature->GetTarget(0); plAGModifier* agMod = const_cast(plAGModifier::ConvertNoRef(FindModifierByClass(avObj, plAGModifier::Index()))); plPhysicalControllerCore* controller = fAvMod->GetController(); fCallbackAction= TRACKED_NEW plRidingAnimatedPhysicalController(avObj, agMod->GetApplicator(kAGPinTransform), controller); fCallbackAction->ActivateController(); } else { delete fCallbackAction; plSceneObject* avObj = fArmature->GetTarget(0); plAGModifier* agMod = const_cast(plAGModifier::ConvertNoRef(FindModifierByClass(avObj, plAGModifier::Index()))); plPhysicalControllerCore* controller = fAvMod->GetController(); fCallbackAction= TRACKED_NEW plWalkingController(avObj, agMod->GetApplicator(kAGPinTransform), controller); fCallbackAction->ActivateController(); //hsStatusMessage("Got an exiting ride animated physical message"); } } return plArmatureBrain::MsgReceive(msg); } hsBool plAvBrainHuman::IHandleClimbMsg(plClimbMsg *msg) { bool isStartClimb = msg->fCommand == plClimbMsg::kStartClimbing; if(isStartClimb) { // let's build a seek task to get us to the attach point plKey seekTarget = msg->fTarget; plAvTaskSeek *seekTask = TRACKED_NEW plAvTaskSeek(seekTarget); QueueTask(seekTask); // now a brain task to start the actual climb. plAvBrainClimb::Mode startMode; switch(msg->fDirection) { case plClimbMsg::kUp: startMode = plAvBrainClimb::kMountingUp; break; case plClimbMsg::kDown: startMode = plAvBrainClimb::kMountingDown; break; case plClimbMsg::kLeft: startMode = plAvBrainClimb::kMountingLeft; break; case plClimbMsg::kRight: startMode = plAvBrainClimb::kMountingRight; break; } plAvBrainClimb *brain = TRACKED_NEW plAvBrainClimb(startMode); plAvTaskBrain *brainTask = TRACKED_NEW plAvTaskBrain(brain); QueueTask(brainTask); } // ** potentially controversial: // It's fairly easy for a human brain to hit a climb trigger - like when falling off a wall. // When this happens, We should just eat the message to keep it from travelling any further. // The argument against is that there might be a climb brain that actually wants the message, // but if that were true the message would have been given to that climb brain first. return true; } hsScalar plAvBrainHuman::IGetTurnStrength(double timeNow) { float result = 0.f; float timeToMaxTurn, maxTurnSpeed; plAvBrainHuman::TurnCurve turnCurve; if (fAvMod->FastKeyDown()) { timeToMaxTurn = fRunTimeToMaxTurn; maxTurnSpeed = fRunMaxTurnSpeed; turnCurve = fRunTurnCurve; } else { timeToMaxTurn = fWalkTimeToMaxTurn; maxTurnSpeed = fWalkMaxTurnSpeed; turnCurve = fWalkTurnCurve; } plArmatureBehavior * turnLeft = fBehaviors.Count() >= kMovingTurnLeft ? fBehaviors[kMovingTurnLeft] : nil; plArmatureBehavior * turnRight = fBehaviors.Count() >= kMovingTurnRight ? fBehaviors[kMovingTurnRight] : nil; hsScalar turnLeftStrength = turnLeft ? turnLeft->GetStrength() : 0.f; hsScalar turnRightStrength = turnRight ? turnRight->GetStrength() : 0.f; // Turning based on keypress if ((turnLeftStrength > 0.f) || (turnRightStrength > 0.f) || (!fCallbackAction->IsOnGround() && !fCallbackAction->IsControlledFlight()) ) { float t = (float)(timeNow - fStartedTurning); float turnSpeed; if(t > timeToMaxTurn) { turnSpeed = maxTurnSpeed; } else { float n = t / timeToMaxTurn; // normalize switch(turnCurve) { case kTurnLinear: // linear turnSpeed = n * maxTurnSpeed; break; case kTurnExponential: // exponential turnSpeed = (n * n) * maxTurnSpeed; break; case kTurnLogarithmic: // logarithmic turnSpeed = n > .1 ? log10(n * 10) * maxTurnSpeed : .00001f; break; default: hsAssert(false, "What the heck?"); turnSpeed = 0.0f; } } result += fAvMod->GetKeyTurnStrength() * turnSpeed; } if (!fCallbackAction->IsControlledFlight()) result += fAvMod->GetAnalogTurnStrength() * maxTurnSpeed; return result; } hsBool plAvBrainHuman::IHandleTaskMsg(plAvTaskMsg *msg) { if(plAvSeekMsg * seekM = plAvSeekMsg::ConvertNoRef(msg)) { // seek and subclasses always have a seek first if(seekM->fSmartSeek) { // use smart seek plAvTaskSeek * seek = TRACKED_NEW plAvTaskSeek(seekM); QueueTask(seek); } else if (!seekM->fNoSeek) { // use dumb seek plAvSeekTask *seek = TRACKED_NEW plAvSeekTask(seekM->fSeekPoint, seekM->fAlignType, seekM->fAnimName); QueueTask(seek); } // else don't seek at all. plAvOneShotMsg * oneshotM = plAvOneShotMsg::ConvertNoRef(msg); if(oneshotM) { // if it's a oneshot, add the oneshot task as well plAvOneShotTask *oneshot = TRACKED_NEW plAvOneShotTask(oneshotM, fAvMod, this); QueueTask(oneshot); } } else if (plAvPushBrainMsg *pushM = plAvPushBrainMsg::ConvertNoRef(msg)) { plAvTaskBrain * push = TRACKED_NEW plAvTaskBrain(pushM->fBrain); QueueTask(push); } else if (plAvPopBrainMsg *popM = plAvPopBrainMsg::ConvertNoRef(msg)) { plAvTaskBrain * pop = TRACKED_NEW plAvTaskBrain(); QueueTask(pop); } else if (plAvTaskMsg *taskM = plAvTaskMsg::ConvertNoRef(msg)) { plAvTask *task = taskM->GetTask(); QueueTask(task); } else { hsStatusMessageF("Couldn't recognize task message type.\n"); return plArmatureBrain::IHandleTaskMsg(msg); } return true; } void plAvBrainHuman::ResetIdle() { if (fBehaviors.Count() > kIdle) fBehaviors[kIdle]->Rewind(); } void plAvBrainHuman::IdleOnly(bool instantOff) { if (!fCallbackAction) return; hsScalar rate = instantOff ? 0.f : 1.f; int i; for (i = kWalk; i < fBehaviors.GetCount(); i++) fBehaviors[i]->SetStrength(0, rate); } bool plAvBrainHuman::IsMovementZeroBlend() { int i; for (i = 0; i < fBehaviors.GetCount(); i++) { if (i == kIdle || i == kFall) continue; if (fBehaviors[i]->GetStrength() > 0) return false; } return true; } void plAvBrainHuman::TurnToPoint(hsPoint3 point) { if (!fCallbackAction->IsOnGround() || IsRunningTask() || fAvMod->GetCurrentBrain() != this || !IsMovementZeroBlend()) return; hsPoint3 avPos; fAvMod->GetTarget(0)->GetCoordinateInterface()->GetLocalToWorld().GetTranslate(&avPos); const plCoordinateInterface* subworldCI = nil; if (fAvMod->GetController()) subworldCI = fAvMod->GetController()->GetSubworldCI(); if (subworldCI) { point = subworldCI->GetWorldToLocal() * point; avPos = subworldCI->GetWorldToLocal() * avPos; } plAvSeekMsg *msg = TRACKED_NEW plAvSeekMsg(nil, fAvMod->GetKey(), nil, 1.f, true); hsClearBits(msg->fFlags, plAvSeekMsg::kSeekFlagForce3rdPersonOnStart); hsSetBits(msg->fFlags, plAvSeekMsg::kSeekFlagNoWarpOnTimeout | plAvSeekMsg::kSeekFlagRotationOnly); msg->fTargetLookAt = point; msg->fTargetPos = avPos; msg->SetBCastFlag(plMessage::kNetPropagate); msg->Send(); } void plAvBrainHuman::IChatOn() { char *chatAnimName = fAvMod->MakeAnimationName("Talk"); // check that we aren't adding this twice... if (!fAvMod->FindAnimInstance(chatAnimName)) { plKey avKey = fAvMod->GetKey(); plAvAnimTask *animTask = TRACKED_NEW plAvAnimTask(chatAnimName, 0.0, 1.0, 1.0, 0.0, true, true, true); if (animTask) { plAvTaskMsg *taskMsg = TRACKED_NEW plAvTaskMsg(avKey, avKey, animTask); taskMsg->SetBCastFlag(plMessage::kNetPropagate); taskMsg->Send(); } } delete [] chatAnimName; } void plAvBrainHuman::IChatOff() { char *chatAnimName = fAvMod->MakeAnimationName("Talk"); plKey avKey = fAvMod->GetKey(); plAvAnimTask *animTask = TRACKED_NEW plAvAnimTask(chatAnimName, -1.0); if (animTask) { plAvTaskMsg *taskMsg = TRACKED_NEW plAvTaskMsg(avKey, avKey, animTask); taskMsg->SetBCastFlag(plMessage::kNetPropagate); taskMsg->Send(); } delete[] chatAnimName; } hsBool plAvBrainHuman::IInitAnimations() { hsBool result = false; plAGAnim *idle = fAvMod->FindCustomAnim("Idle"); plAGAnim *walk = fAvMod->FindCustomAnim("Walk"); plAGAnim *run = fAvMod->FindCustomAnim("Run"); plAGAnim *walkBack = fAvMod->FindCustomAnim("WalkBack"); plAGAnim *stepLeft = fAvMod->FindCustomAnim("StepLeft"); plAGAnim *stepRight = fAvMod->FindCustomAnim("StepRight"); plAGAnim *standingLeft = fAvMod->FindCustomAnim("TurnLeft"); plAGAnim *standingRight = fAvMod->FindCustomAnim("TurnRight"); plAGAnim *fall = fAvMod->FindCustomAnim("Fall"); plAGAnim *standJump = fAvMod->FindCustomAnim("StandingJump"); plAGAnim *walkJump = fAvMod->FindCustomAnim("WalkingJump"); plAGAnim *runJump = fAvMod->FindCustomAnim("RunningJump"); plAGAnim *groundImpact = fAvMod->FindCustomAnim("GroundImpact"); plAGAnim *runningImpact = fAvMod->FindCustomAnim("RunningImpact"); plAGAnim *movingLeft = nil; // fAvMod->FindCustomAnim("LeanLeft"); plAGAnim *movingRight = nil; // fAvMod->FindCustomAnim("LeanRight"); plAGAnim *pushWalk = fAvMod->FindCustomAnim("BallPushWalk"); //plAGAnim *pushIdle = fAvMod->FindCustomAnim("BallPushIdle"); const float kDefaultFade = 3.0; // most animations fade in and out in 1/4 of a second. if (idle && walk && run && walkBack && standingLeft && standingRight && stepLeft && stepRight) { plHBehavior *behavior; fBehaviors.SetCountAndZero(kHuBehaviorMax); fBehaviors[kIdle] = behavior = TRACKED_NEW Idle; behavior->Init(idle, true, this, fAvMod, kDefaultFade, kDefaultFade, kIdle, plHBehavior::kBehaviorTypeIdle); behavior->SetStrength(1.f, 0.f); fBehaviors[kWalk] = behavior = TRACKED_NEW Walk; behavior->Init(walk, true, this, fAvMod, kDefaultFade, 5.f, kWalk, plHBehavior::kBehaviorTypeWalk); fBehaviors[kRun] = behavior = TRACKED_NEW Run; behavior->Init(run, true, this, fAvMod, kDefaultFade, 2.0, kRun, plHBehavior::kBehaviorTypeRun); fBehaviors[kWalkBack] = behavior = TRACKED_NEW WalkBack; behavior->Init(walkBack, true, this, fAvMod, kDefaultFade, kDefaultFade, kWalkBack, plHBehavior::kBehaviorTypeWalkBack); fBehaviors[kStandingTurnLeft] = behavior = TRACKED_NEW StandingTurnLeft; behavior->Init(standingLeft, true, this, fAvMod, 3.0f, 6.0f, kStandingTurnLeft, plHBehavior::kBehaviorTypeTurnLeft); fBehaviors[kStandingTurnRight] = behavior = TRACKED_NEW StandingTurnRight; behavior->Init(standingRight, true, this, fAvMod, 3.0f, 6.0f, kStandingTurnRight, plHBehavior::kBehaviorTypeTurnRight); fBehaviors[kStepLeft] = behavior = TRACKED_NEW StepLeft; behavior->Init(stepLeft, true, this, fAvMod, kDefaultFade, kDefaultFade, kStepLeft, plHBehavior::kBehaviorTypeSidestepLeft); fBehaviors[kStepRight] = behavior = TRACKED_NEW StepRight; behavior->Init(stepRight, true, this, fAvMod, kDefaultFade, kDefaultFade, kStepRight, plHBehavior::kBehaviorTypeSidestepRight); // Warning: Changing the blend times of the jump animations will affect the path you take, because until we're fully blended, // we won't be using the full motion defined in the animation. This isn't an issue for standing jump, but you need to be // aware of it for the walk/run jumps. fBehaviors[kFall] = behavior = TRACKED_NEW Fall; behavior->Init(fall, true, this, fAvMod, 1.0f, 10, kFall, plHBehavior::kBehaviorTypeFall); fBehaviors[kStandingJump] = behavior = TRACKED_NEW StandingJump; behavior->Init(standJump, false, this, fAvMod, kDefaultFade, kDefaultFade, kStandingJump, plHBehavior::kBehaviorTypeStandingJump); fBehaviors[kWalkingJump] = behavior = TRACKED_NEW WalkingJump; behavior->Init(walkJump, false, this, fAvMod, 10, 3.0, kWalkingJump, plHBehavior::kBehaviorTypeWalkingJump); fBehaviors[kRunningJump] = behavior = TRACKED_NEW RunningJump; behavior->Init(runJump, false, this, fAvMod, 10, 2.0, kRunningJump, plHBehavior::kBehaviorTypeRunningJump); fBehaviors[kGroundImpact] = behavior = TRACKED_NEW GroundImpact; behavior->Init(groundImpact, false, this, fAvMod, 6.0f, kDefaultFade, kGroundImpact, plHBehavior::kBehaviorTypeGroundImpact); fBehaviors[kRunningImpact] = behavior = TRACKED_NEW RunningImpact; behavior->Init(runningImpact, false, this, fAvMod, 6.0f, kDefaultFade, kRunningImpact, plHBehavior::kBehaviorTypeRunningImpact); fBehaviors[kMovingTurnLeft] = behavior = TRACKED_NEW MovingTurnLeft; behavior->Init(movingLeft, true, this, fAvMod, kDefaultFade, kDefaultFade, kMovingTurnLeft, plHBehavior::kBehaviorTypeMovingTurnLeft); fBehaviors[kMovingTurnRight] = behavior = TRACKED_NEW MovingTurnRight; behavior->Init(movingRight, true, this, fAvMod, kDefaultFade, kDefaultFade, kMovingTurnRight, plHBehavior::kBehaviorTypeMovingTurnRight); fBehaviors[kPushWalk] = behavior = TRACKED_NEW PushWalk; behavior->Init(pushWalk, true, this, fAvMod, kDefaultFade, kDefaultFade, kPushWalk, plHBehavior::kBehaviorTypePushWalk); //fBehaviors[kPushIdle] = behavior = TRACKED_NEW PushIdle; //behavior->Init(pushIdle, true, this, fAvMod, kDefaultFade, kDefaultFade, kPushIdle, plHBehavior::kBehaviorTypePushIdle); result = true; } return result; } hsBool plAvBrainHuman::RunStandardBehaviors(double timeNow, float elapsed) { int i; for (i = 0; i < fBehaviors.GetCount(); i++) { plHBehavior *behavior = (plHBehavior*)fBehaviors[i]; if (behavior->PreCondition(timeNow, elapsed)) { behavior->SetStrength(1.f, behavior->fFadeIn); behavior->Process(timeNow, elapsed); fPreconditions |= behavior->GetType(); } else { behavior->SetStrength(0.f, behavior->fFadeOut); fPreconditions &= ~behavior->GetType(); } } return true; } void plAvBrainHuman::SetStartedTurning(double when) { fStartedTurning = when; } void plAvBrainHuman::Spawn(double timeNow) { plArmatureBrain::Spawn(timeNow); IdleOnly(true); // reset any behavior state we may have accumulated while waiting to spawn // IdleOnly will set the blends of all anims to zero, and setting the blends will tell the AGModifier // that it needs to compile. Trouble is, the modifier only checks once per frame. MoveViaAnimation // works on the physics timestep, and will get called before compilation happens. It will go straight // to the old compiled channel, ignore the blends and still move via any anim that was playing before // we linked (only for the first frame). // // Trouble is, that first frame is usually a large physics step which we don't fully resolve. This means // we could miss our spawn point, or worse, spawn into collision geometry and fall through. // // So we force the modifier to recompile. if (fAvMod) fAvMod->Compile(timeNow); } hsBool plAvBrainHuman::LeaveAge() { plPhysicalControllerCore* controller = fAvMod->GetController(); if(!controller->BehavingLikeAnAnimatedPhysical()) { controller->BehaveLikeAnimatedPhysical(true); delete fCallbackAction; plSceneObject* avObj = fArmature->GetTarget(0); plAGModifier* agMod = const_cast(plAGModifier::ConvertNoRef(FindModifierByClass(avObj, plAGModifier::Index()))); fCallbackAction= TRACKED_NEW plWalkingController(avObj, agMod->GetApplicator(kAGPinTransform), controller); fCallbackAction->ActivateController(); } plArmatureBrain::LeaveAge(); // pin the physical so it doesn't fall when the world is deleted fAvMod->EnablePhysics(false); // this will get set to true when we hit ground fCallbackAction->Reset(true); return false; } void plAvBrainHuman::DumpToDebugDisplay(int &x, int &y, int lineHeight, char *strBuf, plDebugText &debugTxt) { sprintf(strBuf, "Brain type: Human"); debugTxt.DrawString(x, y, strBuf); y += lineHeight; const char *grounded = fCallbackAction->IsOnGround() ? "yes" : "no"; const char *falseGrounded = fCallbackAction->IsOnFalseGround() ? "yes" : "no"; const char *pushing = (fCallbackAction->GetPushingPhysical() ? (fCallbackAction->GetFacingPushingPhysical() ? "facing" : "behind") : "none"); sprintf(strBuf, "Ground: %3s, FalseGround: %3s, AirTime: %5.2f (Peak: %5.2f), PushingPhys: %6s", grounded, falseGrounded, fCallbackAction->GetAirTime(), fCallbackAction->GetImpactTime(), pushing); debugTxt.DrawString(x, y, strBuf); y += lineHeight; int i; //strBuf[0] = '\0'; //for (i = 0; i < 32; i++) // strcat(strBuf, fPreconditions & (0x1 << i) ? "1" : "0"); //debugTxt.DrawString(x, y, strBuf); //y += lineHeight; for (i = 0; i < fBehaviors.GetCount(); i++) fBehaviors[i]->DumpDebug(x, y, lineHeight, strBuf, debugTxt); debugTxt.DrawString(x, y, "Tasks:"); y += lineHeight; if(fCurTask) { debugTxt.DrawString(x, y, "Current task:"); y += lineHeight; int indentedX = x + 4; fCurTask->DumpDebug("-", indentedX, y, lineHeight, strBuf, debugTxt); } int tasks = fTaskQueue.size(); if(tasks > 0) { debugTxt.DrawString(x, y, "Tasks in the Queue:"); y += lineHeight; int indentedX = x + 4; for (int i = 0; i < tasks; i++) { plAvTask *each = fTaskQueue[i]; each->DumpDebug("-", indentedX, y, lineHeight, strBuf, debugTxt); } } } ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////// // BEHAVIOR plHBehavior::plHBehavior() : fAvMod(nil), fHuBrain(nil), fFadeIn(1.0f), fFadeOut(-1.0f), fMaxBlend(1.0f) { } plHBehavior::~plHBehavior() { } void plHBehavior::Init(plAGAnim *anim, hsBool loop, plAvBrainHuman *brain, plArmatureMod *body, float fadeIn, float fadeOut, UInt8 index, UInt32 type /* = 0 */) { plArmatureBehavior::Init(anim, loop, brain, body, index); fAvMod = body; fHuBrain = brain; fFadeIn = fadeIn; fFadeOut = fadeOut; fType = type; fStartMsgSent = false; // start message hasn't been sent yet fStopMsgSent = false; // stop message hasn't been sent yet } void plHBehavior::IStart() { plArmatureBehavior::IStart(); fAvMod->SynchIfLocal(hsTimer::GetSysSeconds(), false); if (!fStartMsgSent) fAvMod->IFireBehaviorNotify(fType); fStartMsgSent = true; // we just sent a start message fStopMsgSent = false; // we haven't sent a stop message yet } void plHBehavior::IStop() { plArmatureBehavior::IStop(); fAvMod->SynchIfLocal(hsTimer::GetSysSeconds(), false); if (!fStopMsgSent) fAvMod->IFireBehaviorNotify(fType, false); fStartMsgSent = false; // we haven't sent a start message yet fStopMsgSent = true; // we just sent a stop message } static plRandom sRandom; void Idle::IStart() { plHBehavior::IStart(); if (fAnim) { hsScalar newStart = sRandom.RandZeroToOne() * fAnim->GetAnimation()->GetLength(); fAnim->SetCurrentTime(newStart, true); } } hsBool Run::PreCondition(double time, float elapsed) { if (fAnim) { if (fAvMod->ForwardKeyDown() && fAvMod->FastKeyDown() && fHuBrain->fCallbackAction->IsOnGround() && (!fHuBrain->fCallbackAction->GetPushingPhysical() || !fHuBrain->fCallbackAction->GetFacingPushingPhysical())) return true; } return false; } hsBool Walk::PreCondition(double time, float elapsed) { if (fAnim) { if (fAvMod->ForwardKeyDown() && !fAvMod->FastKeyDown() && fHuBrain->fCallbackAction->IsOnGround() && (!fHuBrain->fCallbackAction->GetPushingPhysical() || !fHuBrain->fCallbackAction->GetFacingPushingPhysical())) return true; } return false; } hsBool WalkBack::PreCondition(double time, float elapsed) { if (fAnim) { if (fAvMod->BackwardKeyDown() && !fAvMod->ForwardKeyDown() && fHuBrain->fCallbackAction->IsOnGround() && (!fHuBrain->fCallbackAction->GetPushingPhysical() || fHuBrain->fCallbackAction->GetFacingPushingPhysical())) return true; } return false; } hsBool StepLeft::PreCondition(double time, float elapsed) { if (fAnim) { return ((fAvMod->StrafeLeftKeyDown() || (fAvMod->StrafeKeyDown() && fAvMod->TurnLeftKeyDown())) && !(fAvMod->StrafeRightKeyDown() || (fAvMod->StrafeKeyDown() && fAvMod->TurnRightKeyDown())) && !(fAvMod->ForwardKeyDown() || fAvMod->BackwardKeyDown()) && fHuBrain->fCallbackAction->IsOnGround()); } return false; } hsBool StepRight::PreCondition(double time, float elapsed) { if (fAnim) { return ((fAvMod->StrafeRightKeyDown() || (fAvMod->StrafeKeyDown() && fAvMod->TurnRightKeyDown())) && !(fAvMod->StrafeLeftKeyDown() || (fAvMod->StrafeKeyDown() && fAvMod->TurnLeftKeyDown())) && !(fAvMod->ForwardKeyDown() || fAvMod->BackwardKeyDown()) && fHuBrain->fCallbackAction->IsOnGround()); } return false; } hsBool StandingTurnLeft::PreCondition(double time, float elapsed) { if (fAnim) { if (fAvMod->TurnLeftKeyDown() && !fAvMod->TurnRightKeyDown() && !fAvMod->StrafeKeyDown() && !fAvMod->ForwardKeyDown() && !fAvMod->BackwardKeyDown()) { return true; } } return false; } hsBool StandingTurnRight::PreCondition(double time, float elapsed) { if (fAnim) { if (fAvMod->TurnRightKeyDown() && !fAvMod->TurnLeftKeyDown() && !fAvMod->StrafeKeyDown() && !fAvMod->ForwardKeyDown() && !fAvMod->BackwardKeyDown()) { return true; } } return false; } void MovingTurn::IStart() { plHBehavior::IStart(); fHuBrain->SetStartedTurning(hsTimer::GetSysSeconds()); } hsBool MovingTurnLeft::PreCondition(double time, float elapsed) { if (fAvMod->GetTurnStrength() > 0) { if (fHuBrain->fCallbackAction->IsOnGround() && (fAvMod->ForwardKeyDown() || fAvMod->BackwardKeyDown()) && (!fHuBrain->fCallbackAction->GetPushingPhysical() || !fHuBrain->fCallbackAction->GetFacingPushingPhysical())) return true; } return false; } hsBool MovingTurnRight::PreCondition(double time, float elapsed) { if (fAvMod->GetTurnStrength() < 0) { if (fHuBrain->fCallbackAction->IsOnGround() && (fAvMod->ForwardKeyDown() || fAvMod->BackwardKeyDown()) && (!fHuBrain->fCallbackAction->GetPushingPhysical() || !fHuBrain->fCallbackAction->GetFacingPushingPhysical())) return true; } return false; } void Jump::IStart() { fHuBrain->fCallbackAction->EnableControlledFlight(true); plHBehavior::IStart(); } void Jump::IStop() { fHuBrain->fCallbackAction->EnableControlledFlight(false); plHBehavior::IStop(); } hsBool StandingJump::PreCondition(double time, float elapsed) { if (fAnim) { if (GetStrength() > 0.f) { if (!fHuBrain->fCallbackAction->IsControlledFlight() || fAnim->GetTimeConvert()->WorldToAnimTimeNoUpdate(time) >= fAnim->GetTimeConvert()->GetEnd()) { return false; } return !fAnim->IsFinished(); } else { if (fAvMod->JumpKeyDown() && !fAvMod->ForwardKeyDown() && fAnim->GetBlend() == 0.0f && fHuBrain->fCallbackAction->IsOnGround()) { if (fAvMod->ConsumeJump()) return true; } } } return false; } hsBool WalkingJump::PreCondition(double time, float elapsed) { if (fAnim) { if (GetStrength() > 0.f) { if (!fHuBrain->fCallbackAction->IsControlledFlight() || fAnim->GetTimeConvert()->WorldToAnimTimeNoUpdate(time) >= fAnim->GetTimeConvert()->GetEnd()) { return false; } return !fAnim->IsFinished(); } else { if (fAvMod->JumpKeyDown() && !fAvMod->FastKeyDown() && fAvMod->ForwardKeyDown() && fAnim->GetBlend() == 0.0f && fHuBrain->fCallbackAction->IsOnGround() && (!fHuBrain->fCallbackAction->GetPushingPhysical() || !fHuBrain->fCallbackAction->GetFacingPushingPhysical())) { if (fAvMod->ConsumeJump()) return true; } } } return false; } hsBool RunningJump::PreCondition(double time, float elapsed) { if (fAnim) { if (GetStrength() > 0.f) { if (!fHuBrain->fCallbackAction->IsControlledFlight() || fAnim->GetTimeConvert()->WorldToAnimTimeNoUpdate(time) >= fAnim->GetTimeConvert()->GetEnd()) { return false; } return !fAnim->IsFinished(); } else { if (fAvMod->JumpKeyDown() && fAvMod->ForwardKeyDown() && fAvMod->FastKeyDown() && fAnim->GetBlend() == 0.0f && fHuBrain->fCallbackAction->IsOnGround() && (!fHuBrain->fCallbackAction->GetPushingPhysical() || !fHuBrain->fCallbackAction->GetFacingPushingPhysical())) { if (fAvMod->ConsumeJump()) return true; } } } return false; } static const float kRunningImpactThresh = -1.0f; static const float kFullImpactVel = 30.0f; // At this velocity (or greater) we blend the impact at full strength. static const float kMinImpactVel = 10.f; // If we just test IsOnGround(), we do a lot of impacts while running down stairs, so the impact // behaviors have a more forgiving threshold. static const float kMinAirTime = .5f; RunningImpact::RunningImpact() : fDuration(0.0f) {} hsBool RunningImpact::PreCondition(double time, float elapsed) { if (fDuration > 0.0f) fDuration = fDuration - elapsed; else if (fHuBrain->fCallbackAction->IsOnGround() && fHuBrain->fCallbackAction->GetImpactTime() > kMinAirTime) { if (fHuBrain->fCallbackAction->GetImpactVelocity().fZ < -kMinImpactVel) { if (fHuBrain->fCallbackAction->GetImpactVelocity().fY < kRunningImpactThresh) { fMaxBlend = 0.5f + (0.5f * (-fHuBrain->fCallbackAction->GetImpactVelocity().fZ / (kFullImpactVel - kMinImpactVel))); if (fMaxBlend > 1) fMaxBlend = 1; fDuration = 1.0f / fFadeIn; } } } return(fDuration > 0.0f); } void RunningImpact::IStop() { fDuration = 0.0f; plHBehavior::IStop(); } GroundImpact::GroundImpact() : fDuration(0.0f) {} hsBool GroundImpact::PreCondition(double time, float elapsed) { bool result = false; if (fDuration > 0.0f) fDuration = fDuration - elapsed; else if (fHuBrain->fCallbackAction->IsOnGround() && fHuBrain->fCallbackAction->GetImpactTime() > kMinAirTime) { if (fHuBrain->fCallbackAction->GetImpactVelocity().fZ < -kMinImpactVel) { if (fHuBrain->fCallbackAction->GetImpactVelocity().fY >= kRunningImpactThresh) { fMaxBlend = 0.5f + (0.5f * (-fHuBrain->fCallbackAction->GetImpactVelocity().fZ / (kFullImpactVel - kMinImpactVel))); if (fMaxBlend > 1) fMaxBlend = 1; fDuration = 1.0f / fFadeIn; } } } return(fDuration > 0.0f); } void GroundImpact::IStop() { fDuration = 0.0f; plHBehavior::IStop(); } hsBool Fall::PreCondition(double time, float elapsed) { return !fHuBrain->fCallbackAction->IsOnGround() && fHuBrain->fCallbackAction->HitGroundInThisAge(); } void Fall::Process(double time, float elapsed) { // We don't see remote players panic link (from timeouts) because we don't know if they're // really falling, or if our understanding of their physical location is just not up-to-date. if (plAvatarMgr::GetInstance()->GetLocalAvatar() == fAvMod) { if (fAnim && fAnim->GetBlend() > 0.8) { float panicThresh = plAvBrainHuman::kAirTimePanicThreshold; if (panicThresh > 0.0f && fHuBrain->fCallbackAction->GetAirTime() > panicThresh) { fHuBrain->IdleOnly(); // clear the fall state; we're going somewhere new fAvMod->PanicLink(); } } } } extern float QuatAngleDiff(const hsQuat &a, const hsQuat &b); void Push::Process(double time, float elapsed) { hsQuat rot; hsPoint3 pos; fAvMod->GetPositionAndRotationSim(&pos, &rot); hsPoint3 lookAt; fHuBrain->fCallbackAction->GetPushingPhysical()->GetPositionSim(lookAt); hsVector3 up(0.f, 0.f, 1.f); hsScalar angle = hsATan2(lookAt.fY - pos.fY, lookAt.fX - pos.fX) + hsScalarPI / 2; hsQuat targRot(angle, &up); const hsScalar kTurnSpeed = 3.f; hsScalar angDiff = QuatAngleDiff(rot, targRot); hsScalar turnSpeed = (angDiff > elapsed * kTurnSpeed ? kTurnSpeed : angDiff / elapsed); hsQuat invRot = targRot.Conjugate(); hsPoint3 globFwd = invRot.Rotate(&kAvatarForward); globFwd = rot.Rotate(&globFwd); if (globFwd.fX < 0) fHuBrain->fCallbackAction->SetTurnStrength(-turnSpeed); else fHuBrain->fCallbackAction->SetTurnStrength(turnSpeed); } //hsBool PushIdle::PreCondition(double time, float elapsed) //{ // return (fHuBrain->fCallbackAction->GetPushingPhysical() && // fHuBrain->fCallbackAction->IsOnGround() && // !fAvMod->TurnLeftKeyDown() && !fAvMod->TurnRightKeyDown() // && fAvMod->GetTurnStrength() == 0); //} hsBool PushWalk::PreCondition(double time, float elapsed) { return (fHuBrain->fCallbackAction->GetPushingPhysical() && fHuBrain->fCallbackAction->GetFacingPushingPhysical() && fHuBrain->fCallbackAction->IsOnGround() && fAvMod->ForwardKeyDown()); } ///////////////////////////////////////////////////////////////////////////////////////// // // UTIL FUNCTIONS // ///////////////////////////////////////////////////////////////////////////////////////// bool PushSimpleMultiStage(plArmatureMod *avatar, const char *enterAnim, const char *idleAnim, const char *exitAnim, bool netPropagate, bool autoExit, plAGAnim::BodyUsage bodyUsage, plAvBrainGeneric::BrainType type /* = kGeneric */) { plAvBrainHuman *huBrain = plAvBrainHuman::ConvertNoRef(avatar->FindBrainByClass(plAvBrainHuman::Index())); const char *names[3] = {enterAnim, idleAnim, exitAnim}; if (!huBrain || !huBrain->fCallbackAction->IsOnGround() || !huBrain->fCallbackAction->HitGroundInThisAge() || huBrain->IsRunningTask() || !avatar->IsPhysicsEnabled() || avatar->FindMatchingGenericBrain(names, 3)) return false; // XXX if (type == plAvBrainGeneric::kSit || type == plAvBrainGeneric::kSitOnGround) { plAvBrainSwim *swimBrain = plAvBrainSwim::ConvertNoRef(avatar->GetCurrentBrain()); if (swimBrain && !swimBrain->IsWalking()) return false; } // if autoExit is true, then we will immediately exit the idle loop when the user hits a move // key. otherwise, we'll loop until someone sends a message telling us explicitly to advance plAnimStage::AdvanceType idleAdvance = autoExit ? plAnimStage::kAdvanceOnMove : plAnimStage::kAdvanceNone; plAnimStageVec *v = TRACKED_NEW plAnimStageVec; plAnimStage *s1 = TRACKED_NEW plAnimStage(enterAnim, 0, plAnimStage::kForwardAuto, plAnimStage::kBackNone, plAnimStage::kAdvanceAuto, plAnimStage::kRegressNone, 0); v->push_back(s1); plAnimStage *s2 = TRACKED_NEW plAnimStage(idleAnim, 0, plAnimStage::kForwardAuto, plAnimStage::kBackNone, idleAdvance, plAnimStage::kRegressNone, -1); v->push_back(s2); plAnimStage *s3 = TRACKED_NEW plAnimStage(exitAnim, 0, plAnimStage::kForwardAuto, plAnimStage::kBackNone, plAnimStage::kAdvanceAuto, plAnimStage::kRegressNone, 0); v->push_back(s3); plAvBrainGeneric *b = TRACKED_NEW plAvBrainGeneric(v, nil, nil, nil, plAvBrainGeneric::kExitAnyTask | plAvBrainGeneric::kExitNewBrain, 2.0f, 2.0f, plAvBrainGeneric::kMoveStandstill); b->SetBodyUsage(bodyUsage); b->SetType(type); plAvTaskBrain *bt = TRACKED_NEW plAvTaskBrain(b); plAvTaskMsg *btm = TRACKED_NEW plAvTaskMsg(plAvatarMgr::GetInstance()->GetKey(), avatar->GetKey(), bt); if(netPropagate) btm->SetBCastFlag(plMessage::kNetPropagate); btm->Send(); return true; } bool AvatarEmote(plArmatureMod *avatar, const char *emoteName) { bool result = false; char *fullName = avatar->MakeAnimationName(emoteName); plAGAnim *anim = plAGAnim::FindAnim(fullName); plEmoteAnim *emote = plEmoteAnim::ConvertNoRef(anim); hsBool alreadyActive = avatar->FindAnimInstance(fullName) != nil; plAvBrainHuman *huBrain = plAvBrainHuman::ConvertNoRef(avatar->FindBrainByClass(plAvBrainHuman::Index())); delete[] fullName; // XXX plAvBrainSwim *swimBrain = plAvBrainSwim::ConvertNoRef(avatar->GetCurrentBrain()); if (swimBrain && swimBrain->IsSwimming()) return false; if (huBrain && huBrain->fCallbackAction->IsOnGround() && huBrain->fCallbackAction->HitGroundInThisAge() && !huBrain->IsRunningTask() && emote && !alreadyActive && avatar->IsPhysicsEnabled()) { plKey avKey = avatar->GetKey(); float fadeIn = emote->GetFadeIn(); float fadeOut = emote->GetFadeOut(); plAnimStage *s1 = TRACKED_NEW plAnimStage(emoteName, 0, plAnimStage::kForwardAuto, plAnimStage::kBackNone, plAnimStage::kAdvanceOnMove, plAnimStage::kRegressNone, 0); plAnimStageVec *v = TRACKED_NEW plAnimStageVec; v->push_back(s1); plAvBrainGeneric *b = TRACKED_NEW plAvBrainGeneric(v, nil, nil, nil, plAvBrainGeneric::kExitAnyInput | plAvBrainGeneric::kExitNewBrain | plAvBrainGeneric::kExitAnyTask, 2.0f, 2.0f, huBrain->IsActor() ? plAvBrainGeneric::kMoveRelative : plAvBrainGeneric::kMoveStandstill); b->SetType(plAvBrainGeneric::kEmote); b->SetBodyUsage(emote->GetBodyUsage()); plAvTaskBrain *bt = TRACKED_NEW plAvTaskBrain(b); plAvTaskMsg *btm = TRACKED_NEW plAvTaskMsg(plAvatarMgr::GetInstance()->GetKey(), avKey, bt); btm->SetBCastFlag(plMessage::kNetPropagate); btm->Send(); result = true; } return result; }