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