/*==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"
// 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
#include "hsUtils.h"
// 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
hsBool plAvOneShotTask::fForce3rdPerson = true;
#include "../pnMessage/plCameraMsg.h"
/////////////
//
// PLAVTASK
// Abstract definition for the avatar task class
//
/////////////
plAvTask::plAvTask()
{
}
// START
hsBool plAvTask::Start(plArmatureMod *avatar, plArmatureBrain *brain, double time, hsScalar elapsed)
{
return true; // true indicates the task has started succesfully
}
// PROCESS
hsBool plAvTask::Process(plArmatureMod *avatar, plArmatureBrain *brain, double time, hsScalar elapsed)
{
return false;
}
// Finish -----------------------------------------------------------------------------------
// -------
void plAvTask::Finish(plArmatureMod *avatar, plArmatureBrain *brain, double time, hsScalar elapsed)
{
}
// DUMPDEBUG
void plAvTask::DumpDebug(const char *name, int &x, int&y, int lineHeight, char *strBuf, plDebugText &debugTxt)
{
debugTxt.DrawString(x, y, "");
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 = TRACKED_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 = TRACKED_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
hsBool plAvSeekTask::Start(plArmatureMod *avatar, plArmatureBrain *brain, double time, hsScalar 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, "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
hsBool plAvSeekTask::Process(plArmatureMod *avatar, plArmatureBrain *brain, double time, hsScalar 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()
: fAnimName(nil),
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 char *animName,
hsScalar initialBlend,
hsScalar targetBlend,
hsScalar fadeSpeed,
hsScalar setTime,
hsBool start,
hsBool loop,
hsBool attach)
: fInitialBlend(initialBlend),
fTargetBlend(targetBlend),
fFadeSpeed(fadeSpeed),
fSetTime(setTime),
fStart(start),
fLoop(loop),
fAttach(attach),
fAnimInstance(nil)
{
if(animName)
fAnimName = hsStrcpy(animName);
}
// CTOR animName, fadeSpeed, attach
plAvAnimTask::plAvAnimTask(const char *animName, hsScalar fadeSpeed, hsBool attach)
: fInitialBlend(0.0f),
fTargetBlend(0.0f),
fFadeSpeed(fadeSpeed),
fSetTime(0.0f),
fStart(false),
fLoop(false),
fAttach(attach),
fAnimInstance(nil)
{
if(animName)
fAnimName = hsStrcpy(animName);
}
// DTOR
plAvAnimTask::~plAvAnimTask()
{
if(fAnimName)
{
delete[] fAnimName;
fAnimName = nil;
}
}
// START
hsBool plAvAnimTask::Start(plArmatureMod *avatar, plArmatureBrain *brain, double time, hsScalar elapsed)
{
hsBool 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);
}
}
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
hsBool plAvAnimTask::Process(plArmatureMod *avatar, plArmatureBrain *brain, double time, hsScalar elapsed)
{
// the only reason we need this function is to watch the animation until it fades out
hsBool 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();
fInitialBlend = stream->ReadSwapScalar();
fTargetBlend = stream->ReadSwapScalar();
fFadeSpeed = stream->ReadSwapScalar();
fSetTime = stream->ReadSwapScalar();
fStart = stream->ReadBool();
fLoop = stream->ReadBool();
fAttach = stream->ReadBool();
}
// WRITE
void plAvAnimTask::Write(hsStream *stream, hsResMgr *mgr)
{
stream->WriteSafeString(fAnimName);
stream->WriteSwapScalar(fInitialBlend);
stream->WriteSwapScalar(fTargetBlend);
stream->WriteSwapScalar(fFadeSpeed);
stream->WriteSwapScalar(fSetTime);
stream->WriteBool(fStart);
stream->WriteBool(fLoop);
stream->WriteBool(fAttach);
}
////////////////
//
// AVONESHOTTASK
// OBSOLETE -- DEPRECATED
//
////////////////
void plAvOneShotTask::InitDefaults()
{
fBackwards = false;
fDisableLooping = false;
fDisablePhysics = true;
fAnimName = nil;
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 char *animName, hsBool drivable, hsBool 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 = hsStrcpy(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 = hsStrcpy(msg->fAnimName);
}
// DTOR
plAvOneShotTask::~plAvOneShotTask()
{
if(fAnimName)
delete[] fAnimName;
hsRefCnt_SafeUnRef(fCallbacks);
}
// START
hsBool plAvOneShotTask::Start(plArmatureMod *avatar, plArmatureBrain *brain, double time, hsScalar elapsed)
{
hsBool 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 = TRACKED_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 = TRACKED_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("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
{
char buf[256];
sprintf(buf, "Oneshot: Can't find animation <%s>; all bets are off.", fAnimName);
hsAssert(false, buf);
result = true;
}
return result;
}
// PROCESS
hsBool plAvOneShotTask::Process(plArmatureMod *avatar, plArmatureBrain *brain, double time, hsScalar 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((hsScalar)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 = TRACKED_NEW char[64 + strlen(overlaps)];
sprintf(buffy, "Oneshot ends overlapping %s", overlaps);
plConsoleMsg *showLine = TRACKED_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 = TRACKED_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(char *name)
{
delete [] fAnimName;
fAnimName = hsStrcpy(name);
}
//////////////////////
//
// PLAVONESHOTLINKTASK
//
//////////////////////
plAvOneShotLinkTask::plAvOneShotLinkTask() : plAvOneShotTask(),
fMarkerName(nil),
fMarkerTime(-1),
fStartTime(0),
fLinkFired(false)
{
fDisablePhysics = false;
}
plAvOneShotLinkTask::~plAvOneShotLinkTask()
{
delete [] fMarkerName;
}
// task protocol
hsBool plAvOneShotLinkTask::Start(plArmatureMod *avatar, plArmatureBrain *brain, double time, hsScalar elapsed)
{
hsBool result = plAvOneShotTask::Start(avatar, brain, time, elapsed);
fStartTime = time;
if (fAnimInstance && fMarkerName)
{
const plATCAnim *anim = plATCAnim::ConvertNoRef(fAnimInstance->GetAnimation());
if (anim)
{
// GetMarker returns -1 if the marker isn't found
fMarkerTime = anim->GetMarker(fMarkerName);
}
}
return result;
}
hsBool plAvOneShotLinkTask::Process(plArmatureMod *avatar, plArmatureBrain *brain, double time, hsScalar elapsed)
{
hsBool 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();
fMarkerName = stream->ReadSafeString();
}
void plAvOneShotLinkTask::SetMarkerName(char *name)
{
delete [] fMarkerName;
fMarkerName = hsStrcpy(name);
}