You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

828 lines
24 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "HeadSpin.h"
// singular
#include "plAvatarTasks.h"
// local
#include "plArmatureMod.h"
#include "plSeekPointMod.h"
#include "plAvBrainHuman.h"
#include "plAGAnim.h"
#include "plAGAnimInstance.h"
#include "plAGModifier.h"
#include "plMatrixChannel.h"
#include "plAvCallbackAction.h"
#include "plAvatarMgr.h"
// global
// other
#include "plgDispatch.h"
#include "plMessage/plAvatarMsg.h"
#include "plMessage/plAnimCmdMsg.h"
#include "plMessage/plOneShotCallbacks.h"
#include "plMessage/plConsoleMsg.h"
#include "pnKeyedObject/plKey.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "plPipeline/plDebugText.h"
#include "plInputCore/plInputInterfaceMgr.h"
#include "plNetClient/plNetClientMgr.h"
#include "plNetCommon/plNetCommon.h"
#include "plMessage/plLinkToAgeMsg.h"
#include "pfMessage/pfKIMsg.h"
// for console hack
bool plAvOneShotTask::fForce3rdPerson = true;
#include "pnMessage/plCameraMsg.h"
/////////////
//
// PLAVTASK
// Abstract definition for the avatar task class
//
/////////////
plAvTask::plAvTask()
{
}
// START
bool plAvTask::Start(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
{
return true; // true indicates the task has started succesfully
}
// PROCESS
bool plAvTask::Process(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
{
return false;
}
// Finish -----------------------------------------------------------------------------------
// -------
void plAvTask::Finish(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
{
}
// DUMPDEBUG
void plAvTask::DumpDebug(const char *name, int &x, int&y, int lineHeight, char *strBuf, plDebugText &debugTxt)
{
debugTxt.DrawString(x, y, "<anonymous task>");
y += lineHeight;
}
// READ
void plAvTask::Read(hsStream *stream, hsResMgr *mgr)
{
plCreatable::Read(stream, mgr);
}
// WRITE
void plAvTask::Write(hsStream *stream, hsResMgr *mgr)
{
plCreatable::Write(stream, mgr);
}
void plAvTask::ILimitPlayersInput(plArmatureMod *avatar)
{
// make sure this is the local avatar we are talking about
if (avatar == plAvatarMgr::GetInstance()->GetLocalAvatar())
{
plInputInterfaceMgr::GetInstance()->ForceCursorHidden(true);
// tell the KI to be disabled while we are busy
pfKIMsg* msg = new pfKIMsg(pfKIMsg::kTempDisableKIandBB);
plgDispatch::MsgSend( msg );
}
}
void plAvTask::IUndoLimitPlayersInput(plArmatureMod *avatar)
{
// make sure this is the local avatar we are talking about
if (avatar == plAvatarMgr::GetInstance()->GetLocalAvatar())
{
plInputInterfaceMgr::GetInstance()->ForceCursorHidden(false);
// tell the KI to be re-enabled
pfKIMsg* msg = new pfKIMsg(pfKIMsg::kTempEnableKIandBB);
plgDispatch::MsgSend( msg );
}
}
/////////////
//
// AVSEEKTASK
//
/////////////
// CTOR default
plAvSeekTask::plAvSeekTask()
: fAnimName(nil),
fAlign(kAlignHandle),
fDuration(0.25f),
fTarget(nil),
fAnimInstance(nil),
fTargetTime(nil),
fPhysicalAtStart(false),
fCleanup(false)
{
}
// CTOR target, align, animName
plAvSeekTask::plAvSeekTask(plKey target, plAvAlignment align, const char *animName)
: fAlign(align),
fDuration(0.25f),
fTarget(target),
fAnimInstance(nil),
fTargetTime(nil),
fPhysicalAtStart(false),
fCleanup(false)
{
fAnimName = hsStrcpy(animName);
}
// CTOR target
plAvSeekTask::plAvSeekTask(plKey target)
: fAnimName(nil),
fAlign(kAlignHandle),
fDuration(0.25f),
fTarget(target),
fAnimInstance(nil),
fTargetTime(nil),
fPhysicalAtStart(false),
fCleanup(false)
{
}
void GetPositionAndRotation(hsMatrix44 transform, hsScalarTriple *position, hsQuat *rotation)
{
hsPoint3 p = (hsPoint3)transform.GetTranslate();
position->fX = p.fX; position->fY = p.fY; position->fZ = p.fZ;
transform.RemoveScale();
rotation->SetFromMatrix(&transform);
rotation->Normalize();
float angle;
hsVector3 axis;
rotation->GetAngleAxis(&angle, &axis);
}
// START
// Adjust our goal time based on our duration and the current time
bool plAvSeekTask::Start(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
{
fTargetTime = time + fDuration; // clock starts now....
fPhysicalAtStart = avatar->IsPhysicsEnabled();
avatar->EnablePhysics(false); // always turn physics off for seek
plAvBrainHuman *huBrain = plAvBrainHuman::ConvertNoRef(brain);
if(huBrain)
huBrain->IdleOnly();
ILimitPlayersInput(avatar);
if (!fTarget || !fTarget->ObjectIsLoaded())
{
fCleanup = true;
return true;
}
plSceneObject* seekTarget = plSceneObject::ConvertNoRef(fTarget->ObjectIsLoaded());
hsMatrix44 targetL2W = seekTarget->GetLocalToWorld();
const plCoordinateInterface* subworldCI = nil;
if (avatar->GetController())
subworldCI = avatar->GetController()->GetSubworldCI();
if (subworldCI)
targetL2W = subworldCI->GetWorldToLocal() * targetL2W;
switch(fAlign)
{
// just match our handle to the target matrix
case kAlignHandle:
// targetL2Sim is already correct
break;
// match our handle to the target matrix at the end of the given animation
case kAlignHandleAnimEnd:
{
hsMatrix44 adjustment;
plAGAnim *anim = avatar->FindCustomAnim(fAnimName);
GetStartToEndTransform(anim, nil, &adjustment, _TEMP_CONVERT_FROM_LITERAL("Handle")); // actually getting end-to-start
targetL2W = targetL2W * adjustment;
}
break;
default:
break;
};
GetPositionAndRotation(targetL2W, &fTargetPosition, &fTargetRotation);
Process(avatar, brain, time, elapsed);
return true;
}
// CALCHANDLETARGETPOSITION
void CalcHandleTargetPosition(hsMatrix44 &result, plSceneObject *handle, plSceneObject *target, hsMatrix44 &bodyToHandle)
{
hsMatrix44 targetToWorld = target->GetLocalToWorld();
result = bodyToHandle * targetToWorld;
}
// CALCHANDLETARGETPOSITION
// where should I move my insertion point so that my bodyRoot lines up with the target?
void CalcHandleTargetPosition(hsMatrix44 &result, plSceneObject *insert, plSceneObject *target, plSceneObject *bodyRoot)
{
hsMatrix44 bodyToHandle = bodyRoot->GetLocalToParent();
CalcHandleTargetPosition(result, insert, target, bodyToHandle);
}
// PROCESS
// Move closer to the goal position and orientation
bool plAvSeekTask::Process(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
{
hsQuat rotation;
hsPoint3 position;
avatar->GetPositionAndRotationSim(&position, &rotation);
// We've had a history of odd bugs caused by assuming a rotation quat is normalized.
// This line here seems to be fixing one of them. (Avatars scaling oddly when smart seeking.)
rotation.Normalize();
double timeToGo = fTargetTime - time - elapsed; // time from *beginning* of this interval to the goal
if (fCleanup)
{
avatar->EnablePhysics( fPhysicalAtStart );
IUndoLimitPlayersInput(avatar);
return false; // we're done processing
}
else if(timeToGo < .01)
{
fTargetRotation.Normalize();
avatar->SetPositionAndRotationSim(&fTargetPosition, &fTargetRotation);
fCleanup = true; // we're going to wait one frame for the transform to propagate
return true; // still running until next frame/cleanup
}
else
{
hsPoint3 posToGo = fTargetPosition - position; // vec from here to the goal
float thisPercentage = (float)(elapsed / timeToGo);
hsPoint3 newPosition = position + posToGo * thisPercentage;
hsQuat newRotation;
newRotation.SetFromSlerp(rotation, fTargetRotation, thisPercentage);
newRotation.Normalize();
avatar->SetPositionAndRotationSim(&newPosition, &newRotation);
return true; // we're still processing
}
}
void plAvSeekTask::LeaveAge(plArmatureMod *avatar)
{
fTarget = nil;
fCleanup = true;
}
///////////////////
//
// PLAVANIMTASK
//
///////////////////
// CTOR default
plAvAnimTask::plAvAnimTask()
: fInitialBlend(0.0f),
fTargetBlend(0.0f),
fFadeSpeed(0.0f),
fSetTime(0.0f),
fStart(false),
fLoop(false),
fAttach(false),
fAnimInstance(nil)
{
}
// CTOR animName, initialBlend, targetBlend, fadeSpeed, start, loop, attach
plAvAnimTask::plAvAnimTask(const plString &animName,
float initialBlend,
float targetBlend,
float fadeSpeed,
float setTime,
bool start,
bool loop,
bool attach)
: fAnimName(animName),
fInitialBlend(initialBlend),
fTargetBlend(targetBlend),
fFadeSpeed(fadeSpeed),
fSetTime(setTime),
fStart(start),
fLoop(loop),
fAttach(attach),
fAnimInstance(nil)
{
}
// CTOR animName, fadeSpeed, attach
plAvAnimTask::plAvAnimTask(const plString &animName, float fadeSpeed, bool attach)
: fAnimName(animName),
fInitialBlend(0.0f),
fTargetBlend(0.0f),
fFadeSpeed(fadeSpeed),
fSetTime(0.0f),
fStart(false),
fLoop(false),
fAttach(attach),
fAnimInstance(nil)
{
}
// START
bool plAvAnimTask::Start(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
{
bool result = false;
if(fAttach)
{
plAGAnimInstance * aInstance = avatar->FindOrAttachInstance(fAnimName, fInitialBlend);
if(aInstance)
{
if(fStart)
aInstance->Start(fStart);
if(fSetTime > 0)
aInstance->SetCurrentTime(fSetTime, true);
if(fTargetBlend > fInitialBlend)
{
aInstance->Fade(fTargetBlend, fFadeSpeed);
}
aInstance->SetLoop(fLoop);
result = true;
}
else
{
hsStatusMessageF("Couldn't find animation <%s> for plAvAnimTask: will try again", fAnimName.c_str());
}
}
else
{
fAnimInstance = avatar->FindAnimInstance(fAnimName);
if(fAnimInstance)
{
// start fading towards zero
fAnimInstance->Fade(0.0, fFadeSpeed);
}
// if we started the fade, we're done and ready to process
// if we couldn't find the animation, we're still done.
result = true;
}
return result;
}
// PROCESS
bool plAvAnimTask::Process(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
{
// the only reason we need this function is to watch the animation until it fades out
bool result = false;
if(fAttach)
{
// we finished in the Start() function
}
else
{
if(fAnimInstance)
{
if(fAnimInstance->GetBlend() < 0.1)
{
avatar->DetachAnimation(fAnimInstance);
}
else
{
// still waiting for the fadeout; keep the task alive
result = true;
}
}
}
return result;
}
// LEAVEAGE
void plAvAnimTask::LeaveAge(plArmatureMod *avatar)
{
// if we are supposed to be removing the animation anyway, kill it completely on link out
if (!fAttach)
{
fAnimInstance = avatar->FindAnimInstance(fAnimName);
if(fAnimInstance)
avatar->DetachAnimation(fAnimInstance);
}
}
// READ
void plAvAnimTask::Read(hsStream *stream, hsResMgr *mgr)
{
fAnimName = stream->ReadSafeString_TEMP();
fInitialBlend = stream->ReadLEScalar();
fTargetBlend = stream->ReadLEScalar();
fFadeSpeed = stream->ReadLEScalar();
fSetTime = stream->ReadLEScalar();
fStart = stream->ReadBool();
fLoop = stream->ReadBool();
fAttach = stream->ReadBool();
}
// WRITE
void plAvAnimTask::Write(hsStream *stream, hsResMgr *mgr)
{
stream->WriteSafeString(fAnimName);
stream->WriteLEScalar(fInitialBlend);
stream->WriteLEScalar(fTargetBlend);
stream->WriteLEScalar(fFadeSpeed);
stream->WriteLEScalar(fSetTime);
stream->WriteBool(fStart);
stream->WriteBool(fLoop);
stream->WriteBool(fAttach);
}
////////////////
//
// AVONESHOTTASK
// OBSOLETE -- DEPRECATED
//
////////////////
void plAvOneShotTask::InitDefaults()
{
fBackwards = false;
fDisableLooping = false;
fDisablePhysics = true;
fMoveHandle = false;
fAnimInstance = nil;
fDrivable = false;
fReversible = false;
fEnablePhysicsAtEnd = false;
fDetachAnimation = false;
fIgnore = false;
fCallbacks = nil;
fWaitFrames = 0;
}
// CTOR default
plAvOneShotTask::plAvOneShotTask()
{
InitDefaults();
}
// CTOR (animName, drivable, reversible)
// this construct is typically used when you want to create a one-shot task as part of a sequence
// of tasks
// it's different than the message-based constructor in that fDetachAnimation and fMoveHandle default to false
plAvOneShotTask::plAvOneShotTask(const plString &animName, bool drivable, bool reversible, plOneShotCallbacks *callbacks)
{
InitDefaults();
fDrivable = drivable;
fReversible = reversible;
fCallbacks = callbacks;
// we're going to use this sometime in the future, better ref it so someone else doesn't release it
hsRefCnt_SafeRef(fCallbacks);
fAnimName = animName;
}
// CTOR (plAvOneShotMsg, plArmatureMod)
// this constructor is typically used when we're doing a classic, isolated one-shot
// fDetachAnimation and fMoveHandle both default to *true*
plAvOneShotTask::plAvOneShotTask (plAvOneShotMsg *msg, plArmatureMod *avatar, plArmatureBrain *brain)
{
InitDefaults();
fDrivable = msg->fDrivable;
fReversible = msg->fReversible;
fCallbacks = msg->fCallbacks;
fDetachAnimation = true;
fMoveHandle = true;
// we're going to use this sometime in the future, better ref it so someone else doesn't release it
hsRefCnt_SafeRef(fCallbacks);
fAnimName = msg->fAnimName;
}
// DTOR
plAvOneShotTask::~plAvOneShotTask()
{
hsRefCnt_SafeUnRef(fCallbacks);
}
// START
bool plAvOneShotTask::Start(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
{
bool result = false;
if (fIgnore)
return true;
plAGMasterMod * master = avatar;
fAnimInstance = master->AttachAnimationBlended(fAnimName, 0);
fDetachAnimation = true;
if(fAnimInstance)
{
fEnablePhysicsAtEnd = (avatar->IsPhysicsEnabled() && fDisablePhysics);
if (fEnablePhysicsAtEnd)
{
// Must do the physics re-enable through a callback so that it happens before the "done" callback and we don't
// step over some script's attempt to disable physics again.
plAvatarPhysicsEnableCallbackMsg *epMsg = new plAvatarPhysicsEnableCallbackMsg(avatar->GetKey(), kStop, 0, 0, 0, 0);
fAnimInstance->GetTimeConvert()->AddCallback(epMsg);
hsRefCnt_SafeUnRef(epMsg);
}
if (fCallbacks)
{
fAnimInstance->AttachCallbacks(fCallbacks);
// ok, we're done with it, release it back to the river
hsRefCnt_SafeUnRef(fCallbacks);
fCallbacks = nil;
}
fAnimInstance->SetBlend(1.0f);
fAnimInstance->SetSpeed(1.0f);
plAnimTimeConvert *atc = fAnimInstance->GetTimeConvert();
if (fBackwards)
atc->Backwards();
if (fDisableLooping)
atc->Loop(false);
fAnimInstance->SetCurrentTime(fBackwards ? atc->GetEnd() : atc->GetBegin(), true);
fAnimInstance->Start(time);
fWaitFrames = 2; // wait two frames after animation finishes before finalizing
if (fDisablePhysics)
avatar->EnablePhysics(false);
ILimitPlayersInput(avatar);
// this is for a console command hack
if (plAvOneShotTask::fForce3rdPerson && avatar->IsLocalAvatar())
{
// create message
plCameraMsg* pMsg = new plCameraMsg;
pMsg->SetBCastFlag(plMessage::kBCastByExactType);
pMsg->SetBCastFlag(plMessage::kNetPropagate, false);
pMsg->SetCmd(plCameraMsg::kResponderSetThirdPerson);
plgDispatch::MsgSend( pMsg ); // whoosh... off it goes
}
fMoveHandle = (fAnimInstance->GetAnimation()->GetChannel(_TEMP_CONVERT_FROM_LITERAL("Handle")) != nil);
if(fMoveHandle)
{
plMatrixDifferenceApp *differ = avatar->GetRootAnimator();
differ->Reset(time); // throw away any old state
differ->Enable(true);
}
avatar->ApplyAnimations(time, elapsed);
result = true;
}
else
{
plString buf = plString::Format("Oneshot: Can't find animation <%s>; all bets are off.", fAnimName.c_str());
hsAssert(false, buf.c_str());
result = true;
}
return result;
}
// PROCESS
bool plAvOneShotTask::Process(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
{
// *** if we are under mouse control, adjust it here
avatar->ApplyAnimations(time, elapsed);
if(fAnimInstance)
{
if(fAnimInstance->IsFinished())
{
const plAGAnim * animation = fAnimInstance->GetAnimation();
double endTime = (fBackwards ? animation->GetStart() : animation->GetEnd());
fAnimInstance->SetCurrentTime((float)endTime);
avatar->ApplyAnimations(time, elapsed);
if(--fWaitFrames == 0)
{
plSceneObject *handle = avatar->GetTarget(0);
avatar->DetachAnimation(fAnimInstance);
avatar->GetRootAnimator()->Enable(false);
plAvBrainHuman *humanBrain = plAvBrainHuman::ConvertNoRef(brain);
if(fEnablePhysicsAtEnd)
{
#if 0//ndef PLASMA_EXTERNAL_RELEASE
if (!humanBrain || humanBrain->fCallbackAction->HitGroundInThisAge())
{
// For some reason, calling CheckValidPosition at the beginning of
// an age can cause detectors to incorrectly report collisions. So
// we only call this if we're in the age.
//
// It's only debugging code anyway to help the artist check that
// their oneshot doesn't end while penetrating geometry.
char *overlaps = nil;
if (avatar->GetPhysical())
avatar->GetPhysical()->CheckValidPosition(&overlaps);
if (overlaps)
{
char *buffy = new char[64 + strlen(overlaps)];
sprintf(buffy, "Oneshot ends overlapping %s", overlaps);
plConsoleMsg *showLine = new plConsoleMsg( plConsoleMsg::kAddLine, buffy );
showLine->Send();
delete[] overlaps;
delete[] buffy;
}
}
#endif
}
if (humanBrain)
humanBrain->ResetIdle();
IUndoLimitPlayersInput(avatar);
// this is for a console command hack
if (plAvOneShotTask::fForce3rdPerson && avatar->IsLocalAvatar())
{
// create message
plCameraMsg* pMsg = new plCameraMsg;
pMsg->SetBCastFlag(plMessage::kBCastByExactType);
pMsg->SetBCastFlag(plMessage::kNetPropagate, false);
pMsg->SetCmd(plCameraMsg::kResponderUndoThirdPerson);
plgDispatch::MsgSend( pMsg ); // whoosh... off it goes
}
return false;
}
else
return true; // still running; waiting for fWaitFrames == 0
}
else
return true;
}
else
return false;
}
void plAvOneShotTask::LeaveAge(plArmatureMod *avatar)
{
if (fAnimInstance)
fAnimInstance->Stop();
if (fEnablePhysicsAtEnd)
avatar->EnablePhysics(true);
IUndoLimitPlayersInput(avatar);
fIgnore = true;
}
void plAvOneShotTask::SetAnimName(const plString &name)
{
fAnimName = name;
}
//////////////////////
//
// PLAVONESHOTLINKTASK
//
//////////////////////
plAvOneShotLinkTask::plAvOneShotLinkTask() : plAvOneShotTask(),
fMarkerTime(-1),
fStartTime(0),
fLinkFired(false)
{
fDisablePhysics = false;
}
plAvOneShotLinkTask::~plAvOneShotLinkTask()
{
}
// task protocol
bool plAvOneShotLinkTask::Start(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
{
bool result = plAvOneShotTask::Start(avatar, brain, time, elapsed);
fStartTime = time;
if (fAnimInstance && !fMarkerName.IsNull())
{
const plATCAnim *anim = plATCAnim::ConvertNoRef(fAnimInstance->GetAnimation());
if (anim)
{
// GetMarker returns -1 if the marker isn't found
fMarkerTime = anim->GetMarker(fMarkerName);
}
}
return result;
}
bool plAvOneShotLinkTask::Process(plArmatureMod *avatar, plArmatureBrain *brain, double time, float elapsed)
{
bool result = plAvOneShotTask::Process(avatar, brain, time, elapsed);
if (fIgnore)
return result;
if (avatar->GetTarget(0) == plNetClientApp::GetInstance()->GetLocalPlayer())
{
if (!fLinkFired && (fStartTime + fMarkerTime < time))
{
avatar->ILinkToPersonalAge();
avatar->EnablePhysics(false, plArmatureMod::kDisableReasonLinking);
fLinkFired = true;
}
}
return result;
}
void plAvOneShotLinkTask::Write(hsStream *stream, hsResMgr *mgr)
{
plAvOneShotTask::Write(stream, mgr);
stream->WriteSafeString(fAnimName);
stream->WriteSafeString(fMarkerName);
}
void plAvOneShotLinkTask::Read(hsStream *stream, hsResMgr *mgr)
{
plAvOneShotTask::Read(stream, mgr);
fAnimName = stream->ReadSafeString_TEMP();
fMarkerName = stream->ReadSafeString_TEMP();
}
void plAvOneShotLinkTask::SetMarkerName(const plString &name)
{
fMarkerName = name;
}