/*==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==*/
/////////////////////////////////////////////////////////////////////////////////////////
//
// INCLUDES
//
/////////////////////////////////////////////////////////////////////////////////////////
//#include
//#include
#include "plAntiGravAction.h" // descends from Havok class, so must be included first, like havok objects
// singular
#include "plAvBrainSwim.h"
// local
#include "plArmatureMod.h"
#include "plAvBehaviors.h"
#include "plAvBrainHuman.h"
#include "plAGAnim.h"
#include "plAvBrainDrive.h"
#include "plMatrixChannel.h"
#include "plSwimRegion.h"
#include "plAvatarTasks.h"
#include "plArmatureEffects.h"
#include "plAvTaskBrain.h"
// global
#include "hsQuat.h"
#include "hsTimer.h"
#include "plPhysical.h"
#include "plPhysicalControllerCore.h"
#include "plAvCallbackAction.h"
// other
#include "../plPhysical/plCollisionDetector.h"
#include "../plPipeline/plDebugText.h"
#include "../plMessage/plAvatarMsg.h"
#include "../plMessage/plSwimMsg.h"
#include "../plMessage/plLOSRequestMsg.h"
#include "../plMessage/plLOSHitMsg.h"
#include "../plMessage/plInputEventMsg.h"
#include "../plMessage/plSimStateMsg.h"
#include "../pnMessage/plCameraMsg.h"
#include "../pfMessage/plArmatureEffectMsg.h"
class plSwimBehavior : public plArmatureBehavior
{
friend class plAvBrainSwim;
public:
plSwimBehavior() : fAvMod(nil), fSwimBrain(nil) {}
virtual ~plSwimBehavior() {}
void Init(plAGAnim *anim, hsBool loop, plAvBrainSwim *brain, plArmatureMod *body, UInt8 index)
{
plArmatureBehavior::Init(anim, loop, brain, body, index);
fAvMod = body;
fSwimBrain = brain;
}
virtual hsBool PreCondition(double time, float elapsed) { return true; }
protected:
virtual void IStart()
{
plArmatureBehavior::IStart();
fAvMod->SynchIfLocal(hsTimer::GetSysSeconds(), false);
}
virtual void IStop()
{
plArmatureBehavior::IStop();
fAvMod->SynchIfLocal(hsTimer::GetSysSeconds(), false);
}
plArmatureMod *fAvMod;
plAvBrainSwim *fSwimBrain;
};
class SwimForward: public plSwimBehavior
{
public:
/** Walk key is down, fast key is not down */
virtual hsBool PreCondition(double time, float elapsed)
{
return (fAvMod->ForwardKeyDown() && !fAvMod->FastKeyDown());
}
};
class SwimForwardFast: public plSwimBehavior
{
public:
virtual hsBool PreCondition(double time, float elapsed)
{
return (fAvMod->ForwardKeyDown() && fAvMod->FastKeyDown());
}
};
class SwimBack : public plSwimBehavior
{
public:
virtual hsBool PreCondition(double time, float elapsed)
{
return (fAvMod->BackwardKeyDown());
}
};
class TreadWater: public plSwimBehavior
{
};
class SwimLeft : public plSwimBehavior
{
public:
virtual hsBool PreCondition(double time, float elapsed)
{
return ((fAvMod->StrafeLeftKeyDown() || (fAvMod->StrafeKeyDown() && fAvMod->TurnLeftKeyDown())) &&
!(fAvMod->StrafeRightKeyDown() || (fAvMod->StrafeKeyDown() && fAvMod->TurnRightKeyDown())) &&
!(fAvMod->ForwardKeyDown() || fAvMod->BackwardKeyDown()));
}
};
class SwimRight : public plSwimBehavior
{
public:
virtual hsBool PreCondition(double time, float elapsed)
{
return ((fAvMod->StrafeRightKeyDown() || (fAvMod->StrafeKeyDown() && fAvMod->TurnRightKeyDown())) &&
!(fAvMod->StrafeLeftKeyDown() || (fAvMod->StrafeKeyDown() && fAvMod->TurnLeftKeyDown())) &&
!(fAvMod->ForwardKeyDown() || fAvMod->BackwardKeyDown()));
}
};
class SwimTurn: public plSwimBehavior
{
public:
virtual void Process(double time, float elapsed)
{
static const float maxTurnSpeed = 1.0f; // radians per second;
static const float timeToMaxTurn = 0.5f;
static const float incPerSec = maxTurnSpeed / timeToMaxTurn;
// hsAssert(0, "fixme physx");
float oldSpeed = fabs(fSwimBrain->fCallbackAction->GetTurnStrength());
float thisInc = elapsed * incPerSec;
float newSpeed = __min(oldSpeed + thisInc, maxTurnSpeed);
fSwimBrain->fCallbackAction->SetTurnStrength(newSpeed * fAvMod->GetKeyTurnStrength() + fAvMod->GetAnalogTurnStrength());
// the turn is actually applied during PhysicsUpdate
}
virtual void IStop()
{
// hsAssert(0, "fixme physx");
if (fSwimBrain->fCallbackAction)
fSwimBrain->fCallbackAction->SetTurnStrength(0.0f);
plSwimBehavior::IStop();
}
};
class SwimTurnLeft : public SwimTurn
{
public:
virtual hsBool PreCondition(double time, float elapsed)
{
return (fAvMod->GetTurnStrength() > 0 && (fAvMod->ForwardKeyDown() || fAvMod->BackwardKeyDown()));
}
};
class SwimTurnRight : public SwimTurn
{
public:
virtual hsBool PreCondition(double time, float elapsed)
{
return (fAvMod->GetTurnStrength() < 0 && (fAvMod->ForwardKeyDown() || fAvMod->BackwardKeyDown()));
}
};
class TreadTurnLeft : public plSwimBehavior
{
public:
virtual hsBool PreCondition(double time, float elapsed)
{
return (fAvMod->TurnLeftKeyDown() && !fAvMod->ForwardKeyDown() && !fAvMod->BackwardKeyDown());
}
};
class TreadTurnRight : public plSwimBehavior
{
public:
virtual hsBool PreCondition(double time, float elapsed)
{
return (fAvMod->TurnRightKeyDown() && !fAvMod->ForwardKeyDown() && !fAvMod->BackwardKeyDown());
}
};
///////////////////////////////////////////////////////////////////////////////////////////
const hsScalar plAvBrainSwim::kMinSwimDepth = 4.0f;
plAvBrainSwim::plAvBrainSwim() :
fCallbackAction(nil),
fMode(kWalking),
fSurfaceDistance(0.f)
{
fSurfaceProbeMsg = TRACKED_NEW plLOSRequestMsg();
fSurfaceProbeMsg->SetReportType(plLOSRequestMsg::kReportHitOrMiss);
fSurfaceProbeMsg->SetRequestType(plSimDefs::kLOSDBSwimRegion);
fSurfaceProbeMsg->SetTestType(plLOSRequestMsg::kTestAny);
fSurfaceProbeMsg->SetRequestID(plArmatureMod::kAvatarLOSSwimSurface);
}
plAvBrainSwim::~plAvBrainSwim()
{
if(fCallbackAction)
{
IDetachAction();
delete fCallbackAction;
fCallbackAction=nil;
}
fSurfaceProbeMsg->UnRef();
int i;
for (i = 0; i < fBehaviors.GetCount(); i++)
delete fBehaviors[i];
}
hsBool plAvBrainSwim::Apply(double time, hsScalar elapsed)
{
IProbeSurface();
if (fMode == kWalking)
{
if (fSurfaceDistance >= 0.f)
{
fMode = kWading;
plAvBrainHuman *huBrain = plAvBrainHuman::ConvertNoRef(fAvMod->GetNextBrain(this));
// hsAssert(0, "fixme physx");
if (huBrain && !huBrain->fCallbackAction->IsOnGround())
{
// We're jumping in! Trigger splash effect (sound)
plArmatureEffectMsg *msg = TRACKED_NEW plArmatureEffectMsg(fAvMod->GetArmatureEffects()->GetKey(), kTime);
msg->fEventTime = (hsScalar)time;
msg->fTriggerIdx = plArmatureMod::kImpact;
plEventCallbackInterceptMsg *iMsg = TRACKED_NEW plEventCallbackInterceptMsg;
iMsg->AddReceiver(fAvMod->GetArmatureEffects()->GetKey());
iMsg->fEventTime = (hsScalar)time;
iMsg->fEvent = kTime;
iMsg->SetMessage(msg);
iMsg->Send();
}
}
}
plArmatureBrain *nextBrain = fAvMod->GetNextBrain(this);
if (fMode == kWading)
{
if (fSurfaceDistance > kMinSwimDepth && fSurfaceDistance < 100.0f)
IStartSwimming(true);
else if (fSurfaceDistance < 0.f)
fMode = kWalking;
}
int i;
if (fMode == kWalking || fMode == kWading || nextBrain->IsRunningTask())
{
nextBrain->Apply(time, elapsed); // Let brain below process for us
for (i = 0; i < kSwimBehaviorMax; i++)
fBehaviors[i]->SetStrength(0.f, 2.f);
}
else if (fMode == kAbort)
return false;
else
{
if (fMode == kSwimming2D)
{
IProcessSwimming2D(time, elapsed);
// The contact check is so that if buoyancy bobs us a little too high, we don't
// switch to wading only to fall again.
// hsAssert(0, "fixme physx");
if (fSurfaceDistance < kMinSwimDepth-.5 && fCallbackAction->HadContacts())
IStartWading();
}
else if (fMode == kSwimming3D)
IProcessSwimming3D(time, elapsed);
}
return plArmatureBrain::Apply(time, elapsed);
}
hsBool plAvBrainSwim::MsgReceive(plMessage *msg)
{
plLOSHitMsg *losHit = plLOSHitMsg::ConvertNoRef(msg);
if (losHit)
{
if (losHit->fRequestID == plArmatureMod::kAvatarLOSSwimSurface)
{
plSwimRegionInterface *region = nil;
if (!losHit->fNoHit)
{
plSceneObject *hitObj = plSceneObject::ConvertNoRef(losHit->fObj->ObjectIsLoaded());
region = hitObj ? plSwimRegionInterface::ConvertNoRef(hitObj->GetGenericInterface(plSwimRegionInterface::Index())) : nil;
//100-fDistance because of casting the ray from above to get around physxs Raycast requirments
fSurfaceDistance = 100.f-losHit->fDistance;
}
else
fSurfaceDistance = -100.f;
// hsAssert(0, "fixme physx");
if (fCallbackAction)
{
if (region)
fCallbackAction->SetSurface(region, fArmature->GetTarget(0)->GetLocalToWorld().GetTranslate().fZ + fSurfaceDistance);
else
fCallbackAction->SetSurface(nil, 0.f);
}
return true;
}
}
plSwimMsg *swimMsg = plSwimMsg::ConvertNoRef(msg);
if (swimMsg)
{
if (swimMsg->GetIsLeaving())
fMode = kAbort;
return true;
}
plControlEventMsg *ctrlMsg = plControlEventMsg::ConvertNoRef(msg);
if (ctrlMsg)
return IHandleControlMsg(ctrlMsg);
if (fMode == kWalking || fMode == kWading)
return fAvMod->GetNextBrain(this)->MsgReceive(msg);
if (plAvSeekMsg *seekM = plAvSeekMsg::ConvertNoRef(msg))
{
// seek and subclasses always have a seek first
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);
}
return true;
}
if (plArmatureBrain::MsgReceive(msg))
return true;
if (fMode == kWading) // Things like LOS need to go to the human brain below us.
return fAvMod->GetNextBrain(this)->MsgReceive(msg);
return false;
}
void plAvBrainSwim::Activate(plArmatureModBase* avMod)
{
plArmatureBrain::Activate(avMod);
IInitAnimations();
fSurfaceProbeMsg->SetSender(fAvMod->GetKey());
// turn our underlying brain back on until we're all the way in the water.
fAvMod->GetNextBrain(this)->Resume();
}
void plAvBrainSwim::Deactivate()
{
plArmatureBrain::Deactivate();
IDetachAction();
}
void plAvBrainSwim::Suspend()
{
if (fMode == kSwimming2D)
IDetachAction();
}
void plAvBrainSwim::Resume()
{
if (fMode == kSwimming2D)
IAttachAction();
}
bool plAvBrainSwim::IsWalking()
{
return fMode == kWalking;
}
bool plAvBrainSwim::IsWading()
{
return fMode == kWading;
}
bool plAvBrainSwim::IsSwimming()
{
return (fMode == kSwimming2D || fMode == kSwimming3D);
}
void plAvBrainSwim::IStartWading()
{
plArmatureBrain *nextBrain = fAvMod->GetNextBrain(this);
nextBrain->Resume();
fMode = kWading;
int i;
for (i = 0; i < fBehaviors.GetCount(); i++)
fBehaviors[i]->SetStrength(0.f, 2.f);
IDetachAction();
if (fAvMod->IsLocalAvatar())
{
plCameraMsg* pMsg = TRACKED_NEW plCameraMsg;
pMsg->SetBCastFlag(plMessage::kBCastByExactType);
pMsg->SetBCastFlag(plMessage::kNetPropagate, false);
pMsg->SetCmd(plCameraMsg::kResponderUndoThirdPerson);
pMsg->Send();
}
}
void plAvBrainSwim::IStartSwimming(bool is2D)
{
// if we *were* wading, the next brain will be running as well. turn it off
// if we weren't wading, there's no harm in suspending it redundantly.
plArmatureBrain *nextBrain = fAvMod->GetNextBrain(this);
nextBrain->Suspend();
IAttachAction();
if (is2D)
fMode = kSwimming2D;
else
fMode = kSwimming3D;
if (fAvMod->IsLocalAvatar())
{
plCameraMsg* pMsg = TRACKED_NEW plCameraMsg;
pMsg->SetBCastFlag(plMessage::kBCastByExactType);
pMsg->SetBCastFlag(plMessage::kNetPropagate, false);
pMsg->SetCmd(plCameraMsg::kResponderSetThirdPerson);
pMsg->Send();
}
}
hsBool plAvBrainSwim::IProcessSwimming2D(double time, float elapsed)
{
int i;
for (i = 0; i < fBehaviors.GetCount(); i++)
{
plSwimBehavior *behavior = (plSwimBehavior*)fBehaviors[i];
if (behavior->PreCondition(time, elapsed) && !IsRunningTask())
{
behavior->SetStrength(1.f, 2.f);
behavior->Process(time, elapsed);
}
else
behavior->SetStrength(0.f, 2.f);
}
// hsAssert(0, "fixme physx");
fCallbackAction->RecalcVelocity(time, time - elapsed);
return true;
}
hsBool plAvBrainSwim::IProcessSwimming3D(double time, float elapsed)
{
fAvMod->ApplyAnimations(time, elapsed);
return true;
}
hsBool plAvBrainSwim::IInitAnimations()
{
plAGAnim *treadWater = fAvMod->FindCustomAnim("SwimIdle");
plAGAnim *swimForward = fAvMod->FindCustomAnim("SwimSlow");
plAGAnim *swimForwardFast = fAvMod->FindCustomAnim("SwimFast");
plAGAnim *swimBack = fAvMod->FindCustomAnim("SwimBackward");
plAGAnim *swimLeft = fAvMod->FindCustomAnim("SideSwimLeft");
plAGAnim *swimRight = fAvMod->FindCustomAnim("SideSwimRight");
plAGAnim *treadWaterLeft = fAvMod->FindCustomAnim("TreadWaterTurnLeft");
plAGAnim *treadWaterRight = fAvMod->FindCustomAnim("TreadWaterTurnRight");
static const float defaultFade = 2.0f;
fBehaviors.SetCountAndZero(kSwimBehaviorMax);
plSwimBehavior *behavior;
fBehaviors[kTreadWater] = behavior = TRACKED_NEW TreadWater;
behavior->Init(treadWater, true, this, fAvMod, kTreadWater);
fBehaviors[kSwimForward] = behavior = TRACKED_NEW SwimForward;
behavior->Init(swimForward, true, this, fAvMod, kSwimForward);
fBehaviors[kSwimForwardFast] = behavior = TRACKED_NEW SwimForwardFast;
behavior->Init(swimForwardFast, true, this, fAvMod, kSwimForwardFast);
fBehaviors[kSwimBack] = behavior = TRACKED_NEW SwimBack;
behavior->Init(swimBack, true, this, fAvMod, kSwimBack);
fBehaviors[kSwimLeft] = behavior = TRACKED_NEW SwimLeft;
behavior->Init(swimLeft, true, this, fAvMod, kSwimLeft);
fBehaviors[kSwimRight] = behavior = TRACKED_NEW SwimRight;
behavior->Init(swimRight, true, this, fAvMod, kSwimRight);
fBehaviors[kSwimTurnLeft] = behavior = TRACKED_NEW SwimTurnLeft;
behavior->Init(nil, true, this, fAvMod, kSwimTurnLeft);
fBehaviors[kSwimTurnRight] = behavior = TRACKED_NEW SwimTurnRight;
behavior->Init(nil, true, this, fAvMod, kSwimTurnRight);
fBehaviors[kTreadTurnLeft] = behavior = TRACKED_NEW TreadTurnLeft;
behavior->Init(treadWaterLeft, true, this, fAvMod, kTreadTurnLeft);
fBehaviors[kTreadTurnRight] = behavior = TRACKED_NEW TreadTurnRight;
behavior->Init(treadWaterRight, true, this, fAvMod, kTreadTurnRight);
return true;
}
bool plAvBrainSwim::IAttachAction()
{
bool result = false;
if(fAvMod)
{
// hsAssert(0, "fixme physx");
plPhysicalControllerCore *physical = fAvMod->GetController();
if (physical)
{
if (!fCallbackAction)
{
plSceneObject * avObj = fArmature->GetTarget(0);
plAGModifier *agMod = const_cast(plAGModifier::ConvertNoRef(FindModifierByClass(avObj, plAGModifier::Index())));
fCallbackAction = new plSwimmingController(avObj, agMod->GetApplicator(kAGPinTransform),physical);
// physical->AttachAction(fCallbackAction, true, false);
result = true;
}
else
{
fCallbackAction->ActivateController();
}
}
}
return result;
}
bool plAvBrainSwim::IDetachAction()
{
bool result = false;
if (fCallbackAction)
{
// hsAssert(0, "fixme physx");
// plPhysical *physical = fAvMod->GetPhysical();
//
// if(physical)
// {
// physical->RemoveAction(fCallbackAction);
// result = true; // there was an action and we removed it
// }
// TODO: We get a compiler warning about deleting a pointer to an
// undefined class. So, who knows what the code is actually doing.
// Seems bad. Just putting a note in here for whoever fixes the
// physx issue.
//delete fCallbackAction;
//fCallbackAction = nil;
}
return result;
}
void plAvBrainSwim::IProbeSurface()
{
hsPoint3 ourPos = fAvMod->GetTarget(0)->GetLocalToWorld().GetTranslate();
hsPoint3 up = ourPos;
up.fZ += 100;
fSurfaceProbeMsg->SetFrom(up);
fSurfaceProbeMsg->SetTo(ourPos);
fSurfaceProbeMsg->SendAndKeep();
}
hsBool plAvBrainSwim::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;
}
}
return false;
}
void plAvBrainSwim::DumpToDebugDisplay(int &x, int &y, int lineHeight, char *strBuf, plDebugText &debugTxt)
{
sprintf(strBuf, "Brain type: Swim");
debugTxt.DrawString(x, y, strBuf, 0, 255, 255);
y += lineHeight;
switch(fMode) {
case kWading:
sprintf(strBuf, "Mode: Wading");
break;
case kSwimming2D:
sprintf(strBuf, "Mode: Swimming2D");
break;
case kSwimming3D:
sprintf(strBuf, "Mode: Swimming3D");
break;
case kAbort:
sprintf(strBuf, "Mode: Abort (you should never see this)");
break;
}
debugTxt.DrawString(x, y, strBuf);
y += lineHeight;
// hsAssert(0, "fixme physx");
// float buoy = fCallbackAction? fCallbackAction->GetBuoyancy() : 0.0f;
// sprintf(strBuf, "Distance to surface: %f Buoyancy: %f", fSurfaceDistance, buoy);
// debugTxt.DrawString(x, y, strBuf);
// y += lineHeight;
//
// hsVector3 linV;
// fAvMod->GetPhysical()->GetLinearVelocitySim(linV);
// hsVector3 angV;
// fAvMod->GetPhysical()->GetAngularVelocitySim(angV);
// hsScalar angle = angV.fZ > 0 ? angV.Magnitude() : -angV.Magnitude();
// sprintf(strBuf, "Velocity: Linear (%5.2f, %5.2f, %5.2f), Angular %5.2f", linV.fX, linV.fY, linV.fZ, angle);
// debugTxt.DrawString(x, y, strBuf);
// y += lineHeight;
int i;
for (i = 0; i < fBehaviors.GetCount(); i++)
fBehaviors[i]->DumpDebug(x, y, lineHeight, strBuf, debugTxt);
}