diff --git a/Sources/Plasma/FeatureLib/pfPython/cyAvatar.cpp b/Sources/Plasma/FeatureLib/pfPython/cyAvatar.cpp index f656d75e..0708e757 100644 --- a/Sources/Plasma/FeatureLib/pfPython/cyAvatar.cpp +++ b/Sources/Plasma/FeatureLib/pfPython/cyAvatar.cpp @@ -356,6 +356,69 @@ void cyAvatar::RunBehaviorAndReply(pyKey& behKey, pyKey& replyKey, bool netForce } +///////////////////////////////////////////////////////////////////////////// +// +// Function : RunCoopAnim +// PARAMETERS : targetKey - target avatar pyKey +// activeAvatarAnim - animation name +// targetAvatarAnim - animation name +// range - how far away are we allowed to be? (default in glue: 6) +// dist - how close shall the avatar move? (default in glue: 3) +// move - shall he move at all? (default in glue: true) +// +// PURPOSE : Seek near another avatar and run animations on both +// +bool cyAvatar::RunCoopAnim(pyKey& targetKey, plString activeAvatarAnim, plString targetAvatarAnim, float range, float dist, bool move) +{ + if (fRecvr.Count() > 0 && fRecvr[0]) { + // get the participating avatars + plArmatureMod* activeAv = plAvatarMgr::FindAvatar(fRecvr[0]); + plArmatureMod* targetAv = plAvatarMgr::FindAvatar(targetKey.getKey()); + activeAvatarAnim = activeAv->MakeAnimationName(activeAvatarAnim); + targetAvatarAnim = targetAv->MakeAnimationName(targetAvatarAnim); + + if (activeAv && targetAv) { + // set seek position and rotation of the avatars + hsPoint3 avPos, targetPos; + activeAv->GetPositionAndRotationSim(&avPos, nullptr); + targetAv->GetPositionAndRotationSim(&targetPos, nullptr); + hsVector3 av2target(&targetPos, &avPos); //targetPos - avPos + if (av2target.Magnitude() > range) + return false; + av2target.Normalize(); + if (move) + avPos = targetPos - dist * av2target; + + // create the messages and let one task queue the next + const int bcastToNetMods = plMessage::kNetPropagate | plMessage::kNetForce | plMessage::kPropagateToModifiers; + plAvOneShotMsg *avAnim = new plAvOneShotMsg(nullptr, fRecvr[0], fRecvr[0], 0.f, true, activeAvatarAnim, false, false); + avAnim->SetBCastFlag(bcastToNetMods); + + plAvOneShotMsg *targetAnim = new plAvOneShotMsg(nullptr, targetKey.getKey(), targetKey.getKey(), 0.f, true, targetAvatarAnim, false, false); + targetAnim->SetBCastFlag(bcastToNetMods); + targetAnim->fFinishMsg = avAnim; + + plAvSeekMsg *targetSeek = new plAvSeekMsg(nullptr, targetKey.getKey(), nullptr, 0.f, true); + targetSeek->SetBCastFlag(bcastToNetMods); + targetSeek->fTargetPos = targetPos; + targetSeek->fTargetLookAt = avPos; + targetSeek->fFinishMsg = targetAnim; + + plAvSeekMsg *avSeek = new plAvSeekMsg(nullptr, fRecvr[0], nullptr, 0.f, true); + avSeek->SetBCastFlag(bcastToNetMods); + avSeek->fTargetPos = avPos; + avSeek->fTargetLookAt = targetPos; + avSeek->fFinishMsg = targetSeek; + + // start the circus, messages are processed "backwards" + avSeek->Send(); + return true; + } + } + return false; +} + + ///////////////////////////////////////////////////////////////////////////// // // Function : NextStage @@ -1761,6 +1824,19 @@ bool cyAvatar::ExitPBMode() return IExitTopmostGenericMode(); } +///////////////////////////////////////////////////////////////////////////// +// +// Function : EnterAnimMode +// PARAMETERS : animName - string +// +// PURPOSE : Makes the avatar enter a custom anim loop. +// +bool cyAvatar::EnterAnimMode(const plString& animName) +{ + plArmatureMod* fAvMod = plAvatarMgr::GetInstance()->GetLocalAvatar(); + return PushRepeatEmote(fAvMod, animName); +} + int cyAvatar::GetCurrentMode() { diff --git a/Sources/Plasma/FeatureLib/pfPython/cyAvatar.h b/Sources/Plasma/FeatureLib/pfPython/cyAvatar.h index f35d3df2..e18a4b6c 100644 --- a/Sources/Plasma/FeatureLib/pfPython/cyAvatar.h +++ b/Sources/Plasma/FeatureLib/pfPython/cyAvatar.h @@ -105,6 +105,7 @@ public: // oneShot Avatar virtual void RunBehavior(pyKey &behKey, bool netForce, bool netProp); virtual void RunBehaviorAndReply(pyKey& behKey, pyKey& replyKey, bool netForce, bool netProp); + virtual bool RunCoopAnim(pyKey& targetKey, plString activeAvatarAnim, plString targetAvatarAnim, float range, float dist, bool move); // for the multistage behaviors virtual void NextStage(pyKey &behKey, float transTime, bool setTime, float newTime, @@ -511,7 +512,16 @@ public: // : more specific in future version // static bool ExitPBMode(); - + + ///////////////////////////////////////////////////////////////////////////// + // + // Function : EnterAnimMode + // PARAMETERS : animName - string + // + // PURPOSE : Makes the avatar enter a custom anim loop. + // + static bool EnterAnimMode(const plString& animName); + ///////////////////////////////////////////////////////////////////////////// // // Function : GetCurrentMode() diff --git a/Sources/Plasma/FeatureLib/pfPython/cyAvatarGlue.cpp b/Sources/Plasma/FeatureLib/pfPython/cyAvatarGlue.cpp index cac713c6..8d931036 100644 --- a/Sources/Plasma/FeatureLib/pfPython/cyAvatarGlue.cpp +++ b/Sources/Plasma/FeatureLib/pfPython/cyAvatarGlue.cpp @@ -139,6 +139,27 @@ PYTHON_METHOD_DEFINITION(ptAvatar, runBehaviorSetNotify, args) PYTHON_RETURN_NONE; } +PYTHON_METHOD_DEFINITION(ptAvatar, runCoopAnim, args) +{ + PyObject* keyObj; + PyObject* animAv1; + PyObject* animAv2; + float range = 6; + float dist = 3; + bool move = true; + if (!PyArg_ParseTuple(args, "OOO|ffb", &keyObj, &animAv1, &animAv2, &range, &dist, &move) || !pyKey::Check(keyObj) || + !PyString_CheckEx(animAv1) || !PyString_CheckEx(animAv2)) + { + PyErr_SetString(PyExc_TypeError, "runCoopAnim expects a ptkey and two strings and an optional float and boolean"); + PYTHON_RETURN_ERROR; + } + + pyKey* key = pyKey::ConvertFrom(keyObj); + const plString& animName1 = PyString_AsStringEx(animAv1); + const plString& animName2 = PyString_AsStringEx(animAv2); + PYTHON_RETURN_BOOL(self->fThis->RunCoopAnim(*key, animName1, animName2, range, dist, move)); +} + PYTHON_METHOD_DEFINITION(ptAvatar, nextStage, args) { PyObject* keyObj = NULL; @@ -629,6 +650,7 @@ PYTHON_START_METHODS_TABLE(ptAvatar) PYTHON_METHOD(ptAvatar, oneShot, "Params: seekKey,duration,usePhysicsFlag,animationName,drivableFlag,reversibleFlag\nPlays a one-shot animation on the avatar"), PYTHON_METHOD(ptAvatar, runBehavior, "Params: behaviorKey,netForceFlag\nRuns a behavior on the avatar. Can be a single or multi-stage behavior."), PYTHON_METHOD(ptAvatar, runBehaviorSetNotify, "Params: behaviorKey,replyKey,netForceFlag\nSame as runBehavior, except send notifications to specified keyed object"), + PYTHON_METHOD(ptAvatar, runCoopAnim, "Params: targetKey,activeAvatarAnim,targetAvatarAnim,dist,move\nSeek near another avatar and run animations on both."), PYTHON_METHOD(ptAvatar, nextStage, "Params: behaviorKey,transitionTime,setTimeFlag,newTime,SetDirectionFlag,isForward,netForce\nTells a multistage behavior to go to the next stage (Why does Matt like so many parameters?)"), PYTHON_METHOD(ptAvatar, previousStage, "Params: behaviorKey,transitionTime,setTimeFlag,newTime,SetDirectionFlag,isForward,netForce\nTells a multistage behavior to go to the previous stage"), PYTHON_METHOD(ptAvatar, gotoStage, "Params: behaviorKey,stage,transitionTime,setTimeFlag,newTime,SetDirectionFlag,isForward,netForce\nTells a multistage behavior to go to a particular stage"), @@ -777,6 +799,19 @@ PYTHON_GLOBAL_METHOD_DEFINITION_NOARGS(PtAvatarExitAFK, "Tells the local avatar PYTHON_RETURN_BOOL(cyAvatar::ExitAFKMode()); } +PYTHON_GLOBAL_METHOD_DEFINITION(PtAvatarEnterAnimMode, args, "Params: animName\nEnter a custom anim loop (netpropagated)") +{ + PyObject* animNameObj; + if (!PyArg_ParseTuple(args, "O", &animNameObj) || !PyString_CheckEx(animNameObj)) + { + PyErr_SetString(PyExc_TypeError, "PtAvatarEnterAnimMode expects a string"); + PYTHON_RETURN_ERROR; + } + + plString animName = PyString_AsStringEx(animNameObj); + PYTHON_RETURN_BOOL(cyAvatar::EnterAnimMode(animName)); +} + PYTHON_BASIC_GLOBAL_METHOD_DEFINITION(PtDisableMovementKeys, cyAvatar::DisableMovementControls, "Disable avatar movement input") PYTHON_BASIC_GLOBAL_METHOD_DEFINITION(PtEnableMovementKeys, cyAvatar::EnableMovementControls, "Enable avatar movement input") PYTHON_BASIC_GLOBAL_METHOD_DEFINITION(PtDisableMouseMovement, cyAvatar::DisableMouseMovement, "Disable avatar mouse movement input") @@ -870,6 +905,7 @@ void cyAvatar::AddPlasmaMethods(std::vector &methods) PYTHON_GLOBAL_METHOD_NOARGS(methods, PtAvatarExitUsePersBook); PYTHON_GLOBAL_METHOD_NOARGS(methods, PtAvatarEnterAFK); PYTHON_GLOBAL_METHOD_NOARGS(methods, PtAvatarExitAFK); + PYTHON_GLOBAL_METHOD(methods, PtAvatarEnterAnimMode); // Suspend avatar input PYTHON_BASIC_GLOBAL_METHOD(methods, PtDisableMovementKeys); diff --git a/Sources/Plasma/PubUtilLib/plAvatar/plAvBrainHuman.cpp b/Sources/Plasma/PubUtilLib/plAvatar/plAvBrainHuman.cpp index 2eff36c6..d7957df1 100644 --- a/Sources/Plasma/PubUtilLib/plAvatar/plAvBrainHuman.cpp +++ b/Sources/Plasma/PubUtilLib/plAvatar/plAvBrainHuman.cpp @@ -1358,13 +1358,11 @@ bool PushWalk::PreCondition(double time, float elapsed) // ///////////////////////////////////////////////////////////////////////////////////////// -bool PushSimpleMultiStage(plArmatureMod *avatar, const char *enterAnim, const char *idleAnim, const char *exitAnim, - bool netPropagate, bool autoExit, plAGAnim::BodyUsage bodyUsage, plAvBrainGeneric::BrainType type /* = kGeneric */) +static bool CanPushGenericBrain(plArmatureMod* avatar, const char** anims, size_t numAnims, plAvBrainGeneric::BrainType type) { plAvBrainHuman *huBrain = plAvBrainHuman::ConvertNoRef(avatar->FindBrainByClass(plAvBrainHuman::Index())); - const char *names[3] = {enterAnim, idleAnim, exitAnim}; if (!huBrain || !huBrain->fWalkingStrategy->IsOnGround() || !huBrain->fWalkingStrategy->HitGroundInThisAge() || huBrain->IsRunningTask() || - !avatar->IsPhysicsEnabled() || avatar->FindMatchingGenericBrain(names, 3)) + !avatar->IsPhysicsEnabled() || avatar->FindMatchingGenericBrain(anims, numAnims)) return false; // XXX @@ -1375,6 +1373,17 @@ bool PushSimpleMultiStage(plArmatureMod *avatar, const char *enterAnim, const ch return false; } + // still here??? W00T! + return true; +} + +bool PushSimpleMultiStage(plArmatureMod *avatar, const char *enterAnim, const char *idleAnim, const char *exitAnim, + bool netPropagate, bool autoExit, plAGAnim::BodyUsage bodyUsage, plAvBrainGeneric::BrainType type /* = kGeneric */) +{ + const char* names[3] = {enterAnim, idleAnim, exitAnim}; + if (!CanPushGenericBrain(avatar, names, arrsize(names), type)) + 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; @@ -1412,6 +1421,33 @@ bool PushSimpleMultiStage(plArmatureMod *avatar, const char *enterAnim, const ch return true; } +bool PushRepeatEmote(plArmatureMod* avatar, const plString& anim) +{ + const char* names[1] = { anim.c_str() }; + if (!CanPushGenericBrain(avatar, names, arrsize(names), plAvBrainGeneric::kGeneric)) + return false; + + plAnimStageVec* v = new plAnimStageVec; + plAnimStage* theStage = new plAnimStage(anim, 0, + plAnimStage::kForwardAuto, plAnimStage::kBackNone, + plAnimStage::kAdvanceOnMove, plAnimStage::kRegressNone, + -1); + v->push_back(theStage); + + plAvBrainGeneric* b = new plAvBrainGeneric(v, nullptr, nullptr, nullptr, plAvBrainGeneric::kExitAnyTask | plAvBrainGeneric::kExitNewBrain, + 2.0f, 2.0f, plAvBrainGeneric::kMoveStandstill); + + b->SetBodyUsage(plAGAnim::kBodyFull); + b->SetType(plAvBrainGeneric::kGeneric); + + plAvTaskBrain* bt = new plAvTaskBrain(b); + plAvTaskMsg* btm = new plAvTaskMsg(plAvatarMgr::GetInstance()->GetKey(), avatar->GetKey(), bt); + btm->SetBCastFlag(plMessage::kNetPropagate, true); + btm->Send(); + + return true; +} + bool AvatarEmote(plArmatureMod *avatar, const char *emoteName) { bool result = false; diff --git a/Sources/Plasma/PubUtilLib/plAvatar/plAvBrainHuman.h b/Sources/Plasma/PubUtilLib/plAvatar/plAvBrainHuman.h index eca77155..6ce8b9f4 100644 --- a/Sources/Plasma/PubUtilLib/plAvatar/plAvBrainHuman.h +++ b/Sources/Plasma/PubUtilLib/plAvatar/plAvBrainHuman.h @@ -402,7 +402,7 @@ public: bool PushSimpleMultiStage(plArmatureMod *avatar, const char *enterAnim, const char *idleAnim, const char *exitAnim, bool netPropagate, bool autoExit, plAGAnim::BodyUsage bodyUsage, plAvBrainGeneric::BrainType type = plAvBrainGeneric::kGeneric); - +bool PushRepeatEmote(plArmatureMod* avatar, const plString& anim); bool AvatarEmote(plArmatureMod *avatar, const char *emoteName); diff --git a/Sources/Plasma/PubUtilLib/plAvatar/plAvTaskSeek.cpp b/Sources/Plasma/PubUtilLib/plAvatar/plAvTaskSeek.cpp index 255e5702..ed36367d 100644 --- a/Sources/Plasma/PubUtilLib/plAvatar/plAvTaskSeek.cpp +++ b/Sources/Plasma/PubUtilLib/plAvatar/plAvTaskSeek.cpp @@ -101,7 +101,7 @@ bool plAvTaskSeek::fLogProcess = false; void plAvTaskSeek::IInitDefaults() { - fSeekObject = nil; + fSeekObject = nullptr; fMovingTarget = false; fAlign = kAlignHandle; fAnimName = ""; @@ -114,7 +114,8 @@ void plAvTaskSeek::IInitDefaults() fMaxSidleAngle = kDefaultMaxSidleAngle; fFlags = kSeekFlagForce3rdPersonOnStart; fState = kSeekRunNormal; - fNotifyFinishedKey = nil; + fNotifyFinishedKey = nullptr; + fFinishMsg = nullptr; } // plAvTaskSeek ------------ // ------------- @@ -158,6 +159,7 @@ plAvTaskSeek::plAvTaskSeek(plAvSeekMsg *msg) fFlags &= ~kSeekFlagRotationOnly; fNotifyFinishedKey = msg->fFinishKey; + fFinishMsg = msg->fFinishMsg; } // plAvTaskSeek ------------------------ @@ -303,6 +305,9 @@ void plAvTaskSeek::Finish(plArmatureMod *avatar, plArmatureBrain *brain, double //inform controller we are done seeking if (avatar->GetController()) avatar->GetController()->SetSeek(false); + + if (fFinishMsg) + fFinishMsg->Send(); } void plAvTaskSeek::LeaveAge(plArmatureMod *avatar) diff --git a/Sources/Plasma/PubUtilLib/plAvatar/plAvTaskSeek.h b/Sources/Plasma/PubUtilLib/plAvatar/plAvTaskSeek.h index cc22f277..a2cca8ab 100644 --- a/Sources/Plasma/PubUtilLib/plAvatar/plAvTaskSeek.h +++ b/Sources/Plasma/PubUtilLib/plAvatar/plAvTaskSeek.h @@ -156,6 +156,7 @@ protected: plString fAnimName; // an (optional) anim to use to line up our target // so you can say "seek to a place where your hand // will be here after you play animation foo" + plMessage* fFinishMsg; hsPoint3 fPosition; // our current position hsQuat fRotation; // our current rotation diff --git a/Sources/Plasma/PubUtilLib/plMessage/plAvatarMsg.cpp b/Sources/Plasma/PubUtilLib/plMessage/plAvatarMsg.cpp index 66c10785..12b47c4a 100644 --- a/Sources/Plasma/PubUtilLib/plMessage/plAvatarMsg.cpp +++ b/Sources/Plasma/PubUtilLib/plMessage/plAvatarMsg.cpp @@ -174,12 +174,13 @@ void plAvTaskMsg::Write(hsStream *stream, hsResMgr *mgr) // CTOR() plAvSeekMsg::plAvSeekMsg() : plAvTaskMsg(), - fSeekPoint(nil), + fSeekPoint(nullptr), fDuration(0), fSmartSeek(true), fAlignType(kAlignHandle), fNoSeek(false), - fFlags(kSeekFlagForce3rdPersonOnStart) + fFlags(kSeekFlagForce3rdPersonOnStart), + fFinishMsg(nullptr) { } @@ -198,7 +199,8 @@ plAvSeekMsg::plAvSeekMsg(const plKey& sender, const plKey& receiver, fAlignType(alignType), fNoSeek(noSeek), fFlags(flags), - fFinishKey(finishKey) + fFinishKey(finishKey), + fFinishMsg(nullptr) { } diff --git a/Sources/Plasma/PubUtilLib/plMessage/plAvatarMsg.h b/Sources/Plasma/PubUtilLib/plMessage/plAvatarMsg.h index e09bf431..d9447179 100644 --- a/Sources/Plasma/PubUtilLib/plMessage/plAvatarMsg.h +++ b/Sources/Plasma/PubUtilLib/plMessage/plAvatarMsg.h @@ -218,6 +218,7 @@ public: plAvAlignment fAlignType; uint8_t fFlags; plKey fFinishKey; + plMessage* fFinishMsg; }; class plAvTaskSeekDoneMsg : public plAvatarMsg