/*==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 . 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 "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 "plPhysicalControllerCore.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->fWalkingStrategy->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); }