summaryrefslogtreecommitdiff
path: root/indra/newview/llgesturemgr.cpp
diff options
context:
space:
mode:
authorAnsariel <ansariel.hiller@phoenixviewer.com>2024-05-22 19:04:52 +0200
committerAnsariel <ansariel.hiller@phoenixviewer.com>2024-05-22 19:04:52 +0200
commit1b67dd855c41f5a0cda7ec2a68d98071986ca703 (patch)
treeab243607f74f78200787bba5b9b88f07ef1b966f /indra/newview/llgesturemgr.cpp
parent6d6eabca44d08d5b97bfe3e941d2b9687c2246ea (diff)
parente1623bb276f83a43ce7a197e388720c05bdefe61 (diff)
Merge remote-tracking branch 'origin/main' into DRTVWR-600-maint-A
# Conflicts: # autobuild.xml # indra/cmake/CMakeLists.txt # indra/cmake/GoogleMock.cmake # indra/llaudio/llaudioengine_fmodstudio.cpp # indra/llaudio/llaudioengine_fmodstudio.h # indra/llaudio/lllistener_fmodstudio.cpp # indra/llaudio/lllistener_fmodstudio.h # indra/llaudio/llstreamingaudio_fmodstudio.cpp # indra/llaudio/llstreamingaudio_fmodstudio.h # indra/llcharacter/llmultigesture.cpp # indra/llcharacter/llmultigesture.h # indra/llimage/llimage.cpp # indra/llimage/llimagepng.cpp # indra/llimage/llimageworker.cpp # indra/llimage/tests/llimageworker_test.cpp # indra/llmessage/tests/llmockhttpclient.h # indra/llprimitive/llgltfmaterial.h # indra/llrender/llfontfreetype.cpp # indra/llui/llcombobox.cpp # indra/llui/llfolderview.cpp # indra/llui/llfolderviewmodel.h # indra/llui/lllineeditor.cpp # indra/llui/lllineeditor.h # indra/llui/lltextbase.cpp # indra/llui/lltextbase.h # indra/llui/lltexteditor.cpp # indra/llui/lltextvalidate.cpp # indra/llui/lltextvalidate.h # indra/llui/lluictrl.h # indra/llui/llview.cpp # indra/llwindow/llwindowmacosx.cpp # indra/newview/app_settings/settings.xml # indra/newview/llappearancemgr.cpp # indra/newview/llappearancemgr.h # indra/newview/llavatarpropertiesprocessor.cpp # indra/newview/llavatarpropertiesprocessor.h # indra/newview/llbreadcrumbview.cpp # indra/newview/llbreadcrumbview.h # indra/newview/llbreastmotion.cpp # indra/newview/llbreastmotion.h # indra/newview/llconversationmodel.h # indra/newview/lldensityctrl.cpp # indra/newview/lldensityctrl.h # indra/newview/llface.inl # indra/newview/llfloatereditsky.cpp # indra/newview/llfloatereditwater.cpp # indra/newview/llfloateremojipicker.h # indra/newview/llfloaterimsessiontab.cpp # indra/newview/llfloaterprofiletexture.cpp # indra/newview/llfloaterprofiletexture.h # indra/newview/llgesturemgr.cpp # indra/newview/llgesturemgr.h # indra/newview/llimpanel.cpp # indra/newview/llimpanel.h # indra/newview/llinventorybridge.cpp # indra/newview/llinventorybridge.h # indra/newview/llinventoryclipboard.cpp # indra/newview/llinventoryclipboard.h # indra/newview/llinventoryfunctions.cpp # indra/newview/llinventoryfunctions.h # indra/newview/llinventorygallery.cpp # indra/newview/lllistbrowser.cpp # indra/newview/lllistbrowser.h # indra/newview/llpanelobjectinventory.cpp # indra/newview/llpanelprofile.cpp # indra/newview/llpanelprofile.h # indra/newview/llpreviewgesture.cpp # indra/newview/llsavedsettingsglue.cpp # indra/newview/llsavedsettingsglue.h # indra/newview/lltooldraganddrop.cpp # indra/newview/llurllineeditorctrl.cpp # indra/newview/llvectorperfoptions.cpp # indra/newview/llvectorperfoptions.h # indra/newview/llviewerparceloverlay.cpp # indra/newview/llviewertexlayer.cpp # indra/newview/llviewertexturelist.cpp # indra/newview/macmain.h # indra/test/test.cpp
Diffstat (limited to 'indra/newview/llgesturemgr.cpp')
-rw-r--r--indra/newview/llgesturemgr.cpp3095
1 files changed, 1580 insertions, 1515 deletions
diff --git a/indra/newview/llgesturemgr.cpp b/indra/newview/llgesturemgr.cpp
index 20b3c94232..fcafd949c0 100644
--- a/indra/newview/llgesturemgr.cpp
+++ b/indra/newview/llgesturemgr.cpp
@@ -1,1515 +1,1580 @@
-/**
- * @file llgesturemgr.cpp
- * @brief Manager for playing gestures on the viewer
- *
- * $LicenseInfo:firstyear=2004&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include "llviewerprecompiledheaders.h"
-
-#include "llgesturemgr.h"
-
-// system
-#include <functional>
-#include <algorithm>
-
-// library
-#include "llaudioengine.h"
-#include "lldatapacker.h"
-#include "llfloaterreg.h"
-#include "llinventory.h"
-#include "llkeyframemotion.h"
-#include "llmultigesture.h"
-#include "llnotificationsutil.h"
-#include "llstl.h"
-#include "llstring.h" // todo: remove
-#include "llfilesystem.h"
-#include "message.h"
-
-// newview
-#include "llagent.h"
-#include "lldelayedgestureerror.h"
-#include "llinventorymodel.h"
-#include "llviewermessage.h"
-#include "llvoavatarself.h"
-#include "llviewerstats.h"
-#include "llfloaterimnearbychat.h"
-#include "llappearancemgr.h"
-#include "llgesturelistener.h"
-
-// Longest time, in seconds, to wait for all animations to stop playing
-const F32 MAX_WAIT_ANIM_SECS = 30.f;
-
-// Lightweight constructor.
-// init() does the heavy lifting.
-LLGestureMgr::LLGestureMgr()
-: mValid(false),
- mPlaying(),
- mActive(),
- mLoadingCount(0)
-{
- gInventory.addObserver(this);
- mListener.reset(new LLGestureListener());
-}
-
-
-// We own the data for gestures, so clean them up.
-LLGestureMgr::~LLGestureMgr()
-{
- item_map_t::iterator it;
- for (it = mActive.begin(); it != mActive.end(); ++it)
- {
- LLMultiGesture* gesture = (*it).second;
-
- delete gesture;
- gesture = NULL;
- }
- gInventory.removeObserver(this);
-}
-
-
-void LLGestureMgr::init()
-{
- // TODO
-}
-
-void LLGestureMgr::changed(U32 mask)
-{
- LLInventoryFetchItemsObserver::changed(mask);
-
- if (mask & LLInventoryObserver::GESTURE)
- {
- // If there was a gesture label changed, update all the names in the
- // active gestures and then notify observers
- if (mask & LLInventoryObserver::LABEL)
- {
- for(item_map_t::iterator it = mActive.begin(); it != mActive.end(); ++it)
- {
- if(it->second)
- {
- LLViewerInventoryItem* item = gInventory.getItem(it->first);
- if(item)
- {
- it->second->mName = item->getName();
- }
- }
- }
- notifyObservers();
- }
- // If there was a gesture added or removed notify observers
- // STRUCTURE denotes that the inventory item has been moved
- // In the case of deleting gesture, it is moved to the trash
- else if(mask & LLInventoryObserver::ADD ||
- mask & LLInventoryObserver::REMOVE ||
- mask & LLInventoryObserver::STRUCTURE)
- {
- notifyObservers();
- }
- }
-}
-
-
-// Use this version when you have the item_id but not the asset_id,
-// and you KNOW the inventory is loaded.
-void LLGestureMgr::activateGesture(const LLUUID& item_id)
-{
- LLViewerInventoryItem* item = gInventory.getItem(item_id);
- if (!item) return;
- if (item->getType() != LLAssetType::AT_GESTURE)
- return;
-
- LLUUID asset_id = item->getAssetUUID();
-
- mLoadingCount = 1;
- mDeactivateSimilarNames.clear();
-
- const bool inform_server = true;
- const bool deactivate_similar = false;
- activateGestureWithAsset(item_id, asset_id, inform_server, deactivate_similar);
-}
-
-
-void LLGestureMgr::activateGestures(LLViewerInventoryItem::item_array_t& items)
-{
- // Load up the assets
- S32 count = 0;
- LLViewerInventoryItem::item_array_t::const_iterator it;
- for (it = items.begin(); it != items.end(); ++it)
- {
- LLViewerInventoryItem* item = *it;
-
- if (isGestureActive(item->getUUID()))
- {
- continue;
- }
- else
- { // Make gesture active and persistent through login sessions. -Aura 07-12-06
- activateGesture(item->getUUID());
- }
-
- count++;
- }
-
- mLoadingCount = count;
- mDeactivateSimilarNames.clear();
-
- for (it = items.begin(); it != items.end(); ++it)
- {
- LLViewerInventoryItem* item = *it;
-
- if (isGestureActive(item->getUUID()))
- {
- continue;
- }
-
- // Don't inform server, we'll do that in bulk
- const bool no_inform_server = false;
- const bool deactivate_similar = true;
- activateGestureWithAsset(item->getUUID(), item->getAssetUUID(),
- no_inform_server,
- deactivate_similar);
- }
-
- // Inform the database of this change
- LLMessageSystem* msg = gMessageSystem;
-
- bool start_message = true;
-
- for (it = items.begin(); it != items.end(); ++it)
- {
- LLViewerInventoryItem* item = *it;
-
- if (isGestureActive(item->getUUID()))
- {
- continue;
- }
-
- if (start_message)
- {
- msg->newMessage("ActivateGestures");
- msg->nextBlock("AgentData");
- msg->addUUID("AgentID", gAgent.getID());
- msg->addUUID("SessionID", gAgent.getSessionID());
- msg->addU32("Flags", 0x0);
- start_message = false;
- }
-
- msg->nextBlock("Data");
- msg->addUUID("ItemID", item->getUUID());
- msg->addUUID("AssetID", item->getAssetUUID());
- msg->addU32("GestureFlags", 0x0);
-
- if (msg->getCurrentSendTotal() > MTUBYTES)
- {
- gAgent.sendReliableMessage();
- start_message = true;
- }
- }
-
- if (!start_message)
- {
- gAgent.sendReliableMessage();
- }
-}
-
-
-struct LLLoadInfo
-{
- LLUUID mItemID;
- bool mInformServer;
- bool mDeactivateSimilar;
-};
-
-// If inform_server is true, will send a message upstream to update
-// the user_gesture_active table.
-/**
- * It will load a gesture from remote storage
- */
-void LLGestureMgr::activateGestureWithAsset(const LLUUID& item_id,
- const LLUUID& asset_id,
- bool inform_server,
- bool deactivate_similar)
-{
- const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
-
- if( !gAssetStorage )
- {
- LL_WARNS() << "LLGestureMgr::activateGestureWithAsset without valid gAssetStorage" << LL_ENDL;
- return;
- }
- // If gesture is already active, nothing to do.
- if (isGestureActive(item_id))
- {
- LL_WARNS() << "Tried to loadGesture twice " << item_id << LL_ENDL;
- return;
- }
-
-// if (asset_id.isNull())
-// {
-// LL_WARNS() << "loadGesture() - gesture has no asset" << LL_ENDL;
-// return;
-// }
-
- // For now, put NULL into the item map. We'll build a gesture
- // class object when the asset data arrives.
- mActive[base_item_id] = NULL;
-
- // Copy the UUID
- if (asset_id.notNull())
- {
- LLLoadInfo* info = new LLLoadInfo;
- info->mItemID = base_item_id;
- info->mInformServer = inform_server;
- info->mDeactivateSimilar = deactivate_similar;
-
- const bool high_priority = true;
- gAssetStorage->getAssetData(asset_id,
- LLAssetType::AT_GESTURE,
- onLoadComplete,
- (void*)info,
- high_priority);
- }
- else
- {
- notifyObservers();
- }
-}
-
-
-void notify_update_label(const LLUUID& base_item_id)
-{
- gInventory.addChangedMask(LLInventoryObserver::LABEL, base_item_id);
- LLGestureMgr::instance().notifyObservers();
-}
-
-void LLGestureMgr::deactivateGesture(const LLUUID& item_id)
-{
- const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
- item_map_t::iterator it = mActive.find(base_item_id);
- if (it == mActive.end())
- {
- LL_WARNS() << "deactivateGesture for inactive gesture " << item_id << LL_ENDL;
- return;
- }
-
- // mActive owns this gesture pointer, so clean up memory.
- LLMultiGesture* gesture = (*it).second;
-
- // Can be NULL gestures in the map
- if (gesture)
- {
- stopGesture(gesture);
-
- delete gesture;
- gesture = NULL;
- }
-
- mActive.erase(it);
-
- // Inform the database of this change
- LLMessageSystem* msg = gMessageSystem;
- msg->newMessage("DeactivateGestures");
- msg->nextBlock("AgentData");
- msg->addUUID("AgentID", gAgent.getID());
- msg->addUUID("SessionID", gAgent.getSessionID());
- msg->addU32("Flags", 0x0);
-
- msg->nextBlock("Data");
- msg->addUUID("ItemID", item_id);
- msg->addU32("GestureFlags", 0x0);
-
- gAgent.sendReliableMessage();
-
- LLPointer<LLInventoryCallback> cb =
- new LLBoostFuncInventoryCallback(no_op_inventory_func,
- boost::bind(notify_update_label,base_item_id));
-
- LLAppearanceMgr::instance().removeCOFItemLinks(base_item_id, cb);
-}
-
-
-void LLGestureMgr::deactivateSimilarGestures(LLMultiGesture* in, const LLUUID& in_item_id)
-{
- const LLUUID& base_in_item_id = gInventory.getLinkedItemID(in_item_id);
- uuid_vec_t gest_item_ids;
-
- // Deactivate all gestures that match
- item_map_t::iterator it;
- for (it = mActive.begin(); it != mActive.end(); )
- {
- const LLUUID& item_id = (*it).first;
- LLMultiGesture* gest = (*it).second;
-
- // Don't deactivate the gesture we are looking for duplicates of
- // (for replaceGesture)
- if (!gest || item_id == base_in_item_id)
- {
- // legal, can have null pointers in list
- ++it;
- }
- else if ((!gest->mTrigger.empty() && gest->mTrigger == in->mTrigger)
- || (gest->mKey != KEY_NONE && gest->mKey == in->mKey && gest->mMask == in->mMask))
- {
- gest_item_ids.push_back(item_id);
-
- stopGesture(gest);
-
- delete gest;
- gest = NULL;
-
- mActive.erase(it++);
- gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id);
-
- }
- else
- {
- ++it;
- }
- }
-
- // Inform database of the change
- LLMessageSystem* msg = gMessageSystem;
- bool start_message = true;
- uuid_vec_t::const_iterator vit = gest_item_ids.begin();
- while (vit != gest_item_ids.end())
- {
- if (start_message)
- {
- msg->newMessage("DeactivateGestures");
- msg->nextBlock("AgentData");
- msg->addUUID("AgentID", gAgent.getID());
- msg->addUUID("SessionID", gAgent.getSessionID());
- msg->addU32("Flags", 0x0);
- start_message = false;
- }
-
- msg->nextBlock("Data");
- msg->addUUID("ItemID", *vit);
- msg->addU32("GestureFlags", 0x0);
-
- if (msg->getCurrentSendTotal() > MTUBYTES)
- {
- gAgent.sendReliableMessage();
- start_message = true;
- }
-
- ++vit;
- }
-
- if (!start_message)
- {
- gAgent.sendReliableMessage();
- }
-
- // Add to the list of names for the user.
- for (vit = gest_item_ids.begin(); vit != gest_item_ids.end(); ++vit)
- {
- LLViewerInventoryItem* item = gInventory.getItem(*vit);
- if (!item) continue;
-
- mDeactivateSimilarNames.append(item->getName());
- mDeactivateSimilarNames.append("\n");
- }
-
- notifyObservers();
-}
-
-
-bool LLGestureMgr::isGestureActive(const LLUUID& item_id)
-{
- const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
- item_map_t::iterator it = mActive.find(base_item_id);
- return (it != mActive.end());
-}
-
-
-bool LLGestureMgr::isGesturePlaying(const LLUUID& item_id)
-{
- const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
-
- item_map_t::iterator it = mActive.find(base_item_id);
- if (it == mActive.end()) return false;
-
- LLMultiGesture* gesture = (*it).second;
- if (!gesture) return false;
-
- return gesture->mPlaying;
-}
-
-bool LLGestureMgr::isGesturePlaying(LLMultiGesture* gesture)
-{
- if(!gesture)
- {
- return false;
- }
-
- return gesture->mPlaying;
-}
-
-void LLGestureMgr::replaceGesture(const LLUUID& item_id, LLMultiGesture* new_gesture, const LLUUID& asset_id)
-{
- const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
-
- item_map_t::iterator it = mActive.find(base_item_id);
- if (it == mActive.end())
- {
- LL_WARNS() << "replaceGesture for inactive gesture " << base_item_id << LL_ENDL;
- return;
- }
-
- LLMultiGesture* old_gesture = (*it).second;
- stopGesture(old_gesture);
-
- mActive.erase(base_item_id);
-
- mActive[base_item_id] = new_gesture;
-
- // replaceGesture(const LLUUID& item_id, const LLUUID& new_asset_id)
- // replaces ids without repalcing gesture
- if (old_gesture != new_gesture)
- {
- delete old_gesture;
- old_gesture = NULL;
- }
-
- if (asset_id.notNull())
- {
- mLoadingCount = 1;
- mDeactivateSimilarNames.clear();
-
- LLLoadInfo* info = new LLLoadInfo;
- info->mItemID = base_item_id;
- info->mInformServer = true;
- info->mDeactivateSimilar = false;
-
- const bool high_priority = true;
- gAssetStorage->getAssetData(asset_id,
- LLAssetType::AT_GESTURE,
- onLoadComplete,
- (void*)info,
- high_priority);
- }
-
- notifyObservers();
-}
-
-void LLGestureMgr::replaceGesture(const LLUUID& item_id, const LLUUID& new_asset_id)
-{
- const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
-
- item_map_t::iterator it = LLGestureMgr::instance().mActive.find(base_item_id);
- if (it == mActive.end())
- {
- LL_WARNS() << "replaceGesture for inactive gesture " << base_item_id << LL_ENDL;
- return;
- }
-
- // mActive owns this gesture pointer, so clean up memory.
- LLMultiGesture* gesture = (*it).second;
- LLGestureMgr::instance().replaceGesture(base_item_id, gesture, new_asset_id);
-}
-
-void LLGestureMgr::playGesture(LLMultiGesture* gesture)
-{
- if (!gesture) return;
-
- // Reset gesture to first step
- gesture->mCurrentStep = 0;
-
- // Add to list of playing
- gesture->mPlaying = true;
- mPlaying.push_back(gesture);
-
- // Load all needed assets to minimize the delays
- // when gesture is playing.
- for (std::vector<LLGestureStep*>::iterator steps_it = gesture->mSteps.begin();
- steps_it != gesture->mSteps.end();
- ++steps_it)
- {
- LLGestureStep* step = *steps_it;
- switch(step->getType())
- {
- case STEP_ANIMATION:
- {
- LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step;
- const LLUUID& anim_id = anim_step->mAnimAssetID;
-
- // Don't request the animation if this step stops it or if it is already in the cache
- if (!(anim_id.isNull()
- || anim_step->mFlags & ANIM_FLAG_STOP
- || gAssetStorage->hasLocalAsset(anim_id, LLAssetType::AT_ANIMATION)))
- {
- mLoadingAssets.insert(anim_id);
-
- LLUUID* id = new LLUUID(gAgentID);
- gAssetStorage->getAssetData(anim_id,
- LLAssetType::AT_ANIMATION,
- onAssetLoadComplete,
- (void *)id,
- true);
- }
- break;
- }
- case STEP_SOUND:
- {
- LLGestureStepSound* sound_step = (LLGestureStepSound*)step;
- const LLUUID& sound_id = sound_step->mSoundAssetID;
- if (!(sound_id.isNull()
- || gAssetStorage->hasLocalAsset(sound_id, LLAssetType::AT_SOUND)))
- {
- mLoadingAssets.insert(sound_id);
-
- gAssetStorage->getAssetData(sound_id,
- LLAssetType::AT_SOUND,
- onAssetLoadComplete,
- NULL,
- true);
- }
- break;
- }
- case STEP_CHAT:
- case STEP_WAIT:
- case STEP_EOF:
- {
- break;
- }
- default:
- {
- LL_WARNS() << "Unknown gesture step type: " << step->getType() << LL_ENDL;
- }
- }
- }
-
- // And get it going
- stepGesture(gesture);
-
- notifyObservers();
-}
-
-
-// Convenience function that looks up the item_id for you.
-void LLGestureMgr::playGesture(const LLUUID& item_id)
-{
- const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
-
- item_map_t::iterator it = mActive.find(base_item_id);
- if (it == mActive.end()) return;
-
- LLMultiGesture* gesture = (*it).second;
- if (!gesture) return;
-
- playGesture(gesture);
-}
-
-
-// Iterates through space delimited tokens in string, triggering any gestures found.
-// Generates a revised string that has the found tokens replaced by their replacement strings
-// and (as a minor side effect) has multiple spaces in a row replaced by single spaces.
-bool LLGestureMgr::triggerAndReviseString(const std::string &utf8str, std::string* revised_string)
-{
- std::string tokenized = utf8str;
-
- bool found_gestures = false;
- bool first_token = true;
-
- typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
- boost::char_separator<char> sep(" ");
- tokenizer tokens(tokenized, sep);
- tokenizer::iterator token_iter;
-
- for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
- {
- const char* cur_token = token_iter->c_str();
- LLMultiGesture* gesture = NULL;
-
- // Only pay attention to the first gesture in the string.
- if( !found_gestures )
- {
- // collect gestures that match
- std::vector <LLMultiGesture *> matching;
- item_map_t::iterator it;
- for (it = mActive.begin(); it != mActive.end(); ++it)
- {
- gesture = (*it).second;
-
- // Gesture asset data might not have arrived yet
- if (!gesture) continue;
-
- if (LLStringUtil::compareInsensitive(gesture->mTrigger, cur_token) == 0)
- {
- matching.push_back(gesture);
- }
-
- gesture = NULL;
- }
-
-
- if (matching.size() > 0)
- {
- // choose one at random
- {
- S32 random = ll_rand(matching.size());
-
- gesture = matching[random];
-
- playGesture(gesture);
-
- if (!gesture->mReplaceText.empty())
- {
- if( !first_token )
- {
- if (revised_string)
- revised_string->append( " " );
- }
-
- // Don't muck with the user's capitalization if we don't have to.
- if( LLStringUtil::compareInsensitive(cur_token, gesture->mReplaceText) == 0)
- {
- if (revised_string)
- revised_string->append( cur_token );
- }
- else
- {
- if (revised_string)
- revised_string->append( gesture->mReplaceText );
- }
- }
- found_gestures = true;
- }
- }
- }
-
- if(!gesture)
- {
- // This token doesn't match a gesture. Pass it through to the output.
- if( !first_token )
- {
- if (revised_string)
- revised_string->append( " " );
- }
- if (revised_string)
- revised_string->append( cur_token );
- }
-
- first_token = false;
- gesture = NULL;
- }
- return found_gestures;
-}
-
-
-bool LLGestureMgr::triggerGesture(KEY key, MASK mask)
-{
- std::vector <LLMultiGesture *> matching;
- item_map_t::iterator it;
-
- // collect matching gestures
- for (it = mActive.begin(); it != mActive.end(); ++it)
- {
- LLMultiGesture* gesture = (*it).second;
-
- // asset data might not have arrived yet
- if (!gesture) continue;
-
- if (gesture->mKey == key
- && gesture->mMask == mask)
- {
- matching.push_back(gesture);
- }
- }
-
- // choose one and play it
- if (matching.size() > 0)
- {
- U32 random = ll_rand(matching.size());
-
- LLMultiGesture* gesture = matching[random];
-
- playGesture(gesture);
- return true;
- }
- return false;
-}
-
-
-S32 LLGestureMgr::getPlayingCount() const
-{
- return mPlaying.size();
-}
-
-
-struct IsGesturePlaying
-{
- bool operator()(const LLMultiGesture* gesture) const
- {
- return bool(gesture->mPlaying);
- }
-};
-
-void LLGestureMgr::update()
-{
- S32 i;
- for (i = 0; i < (S32)mPlaying.size(); ++i)
- {
- stepGesture(mPlaying[i]);
- }
-
- // Clear out gestures that are done, by moving all the
- // ones that are still playing to the front.
- std::vector<LLMultiGesture*>::iterator new_end;
- new_end = std::partition(mPlaying.begin(),
- mPlaying.end(),
- IsGesturePlaying());
-
- // Something finished playing
- if (new_end != mPlaying.end())
- {
- // Delete the completed gestures that want deletion
- std::vector<LLMultiGesture*>::iterator it;
- for (it = new_end; it != mPlaying.end(); ++it)
- {
- LLMultiGesture* gesture = *it;
-
- if (gesture->mDoneCallback)
- {
- gesture->mDoneCallback(gesture, gesture->mCallbackData);
-
- // callback might have deleted gesture, can't
- // rely on this pointer any more
- gesture = NULL;
- }
- }
-
- // And take done gestures out of the playing list
- mPlaying.erase(new_end, mPlaying.end());
-
- notifyObservers();
- }
-}
-
-
-// Run all steps until you're either done or hit a wait.
-void LLGestureMgr::stepGesture(LLMultiGesture* gesture)
-{
- if (!gesture)
- {
- return;
- }
- if (!isAgentAvatarValid() || hasLoadingAssets(gesture)) return;
-
- // Of the ones that started playing, have any stopped?
-
- std::set<LLUUID>::iterator gest_it;
- for (gest_it = gesture->mPlayingAnimIDs.begin();
- gest_it != gesture->mPlayingAnimIDs.end();
- )
- {
- // look in signaled animations (simulator's view of what is
- // currently playing.
- LLVOAvatar::AnimIterator play_it = gAgentAvatarp->mSignaledAnimations.find(*gest_it);
- if (play_it != gAgentAvatarp->mSignaledAnimations.end())
- {
- ++gest_it;
- }
- else
- {
- // not found, so not currently playing or scheduled to play
- // delete from the triggered set
- gesture->mPlayingAnimIDs.erase(gest_it++);
- }
- }
-
- // Of all the animations that we asked the sim to start for us,
- // pick up the ones that have actually started.
- for (gest_it = gesture->mRequestedAnimIDs.begin();
- gest_it != gesture->mRequestedAnimIDs.end();
- )
- {
- LLVOAvatar::AnimIterator play_it = gAgentAvatarp->mSignaledAnimations.find(*gest_it);
- if (play_it != gAgentAvatarp->mSignaledAnimations.end())
- {
- // Hooray, this animation has started playing!
- // Copy into playing.
- gesture->mPlayingAnimIDs.insert(*gest_it);
- gesture->mRequestedAnimIDs.erase(gest_it++);
- }
- else
- {
- // nope, not playing yet
- ++gest_it;
- }
- }
-
- // Run the current steps
- bool waiting = false;
- while (!waiting && gesture->mPlaying)
- {
- // Get the current step, if there is one.
- // Otherwise enter the waiting at end state.
- LLGestureStep* step = NULL;
- if (gesture->mCurrentStep < (S32)gesture->mSteps.size())
- {
- step = gesture->mSteps[gesture->mCurrentStep];
- llassert(step != NULL);
- }
- else
- {
- // step stays null, we're off the end
- gesture->mWaitingAtEnd = true;
- }
-
-
- // If we're waiting at the end, wait for all gestures to stop
- // playing.
- // TODO: Wait for all sounds to complete as well.
- if (gesture->mWaitingAtEnd)
- {
- // Neither do we have any pending requests, nor are they
- // still playing.
- if ((gesture->mRequestedAnimIDs.empty()
- && gesture->mPlayingAnimIDs.empty()))
- {
- // all animations are done playing
- gesture->mWaitingAtEnd = false;
- gesture->mPlaying = false;
- }
- else
- {
- waiting = true;
- }
- continue;
- }
-
- // If we're waiting on our animations to stop, poll for
- // completion.
- if (gesture->mWaitingAnimations)
- {
- // Neither do we have any pending requests, nor are they
- // still playing.
- if ((gesture->mRequestedAnimIDs.empty()
- && gesture->mPlayingAnimIDs.empty()))
- {
- // all animations are done playing
- gesture->mWaitingAnimations = false;
- gesture->mCurrentStep++;
- }
- else if (gesture->mWaitTimer.getElapsedTimeF32() > MAX_WAIT_ANIM_SECS)
- {
- // we've waited too long for an animation
- LL_INFOS("GestureMgr") << "Waited too long for animations to stop, continuing gesture."
- << LL_ENDL;
- gesture->mWaitingAnimations = false;
- gesture->mCurrentStep++;
- }
- else
- {
- waiting = true;
- }
- continue;
- }
-
- // If we're waiting a fixed amount of time, check for timer
- // expiration.
- if (gesture->mWaitingTimer)
- {
- // We're waiting for a certain amount of time to pass
- LLGestureStepWait* wait_step = (LLGestureStepWait*)step;
-
- F32 elapsed = gesture->mWaitTimer.getElapsedTimeF32();
- if (elapsed > wait_step->mWaitSeconds)
- {
- // wait is done, continue execution
- gesture->mWaitingTimer = false;
- gesture->mCurrentStep++;
- }
- else
- {
- // we're waiting, so execution is done for now
- waiting = true;
- }
- continue;
- }
-
- // Not waiting, do normal execution
- runStep(gesture, step);
- }
-}
-
-
-void LLGestureMgr::runStep(LLMultiGesture* gesture, LLGestureStep* step)
-{
- switch(step->getType())
- {
- case STEP_ANIMATION:
- {
- LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step;
- if (anim_step->mAnimAssetID.isNull())
- {
- gesture->mCurrentStep++;
- }
-
- if (anim_step->mFlags & ANIM_FLAG_STOP)
- {
- gAgent.sendAnimationRequest(anim_step->mAnimAssetID, ANIM_REQUEST_STOP);
- // remove it from our request set in case we just requested it
- std::set<LLUUID>::iterator set_it = gesture->mRequestedAnimIDs.find(anim_step->mAnimAssetID);
- if (set_it != gesture->mRequestedAnimIDs.end())
- {
- gesture->mRequestedAnimIDs.erase(set_it);
- }
- }
- else
- {
- gAgent.sendAnimationRequest(anim_step->mAnimAssetID, ANIM_REQUEST_START);
- // Indicate that we've requested this animation to play as
- // part of this gesture (but it won't start playing for at
- // least one round-trip to simulator).
- gesture->mRequestedAnimIDs.insert(anim_step->mAnimAssetID);
- }
- gesture->mCurrentStep++;
- break;
- }
- case STEP_SOUND:
- {
- LLGestureStepSound* sound_step = (LLGestureStepSound*)step;
- const LLUUID& sound_id = sound_step->mSoundAssetID;
- const F32 volume = 1.f;
- send_sound_trigger(sound_id, volume);
- gesture->mCurrentStep++;
- break;
- }
- case STEP_CHAT:
- {
- LLGestureStepChat* chat_step = (LLGestureStepChat*)step;
- std::string chat_text = chat_step->mChatText;
- // Don't animate the nodding, as this might not blend with
- // other playing animations.
-
- const bool animate = false;
-
- (LLFloaterReg::getTypedInstance<LLFloaterIMNearbyChat>("nearby_chat"))->
- sendChatFromViewer(chat_text, CHAT_TYPE_NORMAL, animate);
-
- gesture->mCurrentStep++;
- break;
- }
- case STEP_WAIT:
- {
- LLGestureStepWait* wait_step = (LLGestureStepWait*)step;
- if (wait_step->mFlags & WAIT_FLAG_TIME)
- {
- gesture->mWaitingTimer = true;
- gesture->mWaitTimer.reset();
- }
- else if (wait_step->mFlags & WAIT_FLAG_ALL_ANIM)
- {
- gesture->mWaitingAnimations = true;
- // Use the wait timer as a deadlock breaker for animation
- // waits.
- gesture->mWaitTimer.reset();
- }
- else
- {
- gesture->mCurrentStep++;
- }
- // Don't increment instruction pointer until wait is complete.
- break;
- }
- default:
- {
- break;
- }
- }
-}
-
-
-// static
-void LLGestureMgr::onLoadComplete(const LLUUID& asset_uuid,
- LLAssetType::EType type,
- void* user_data, S32 status, LLExtStat ext_status)
-{
- LLLoadInfo* info = (LLLoadInfo*)user_data;
-
- LLUUID item_id = info->mItemID;
- bool inform_server = info->mInformServer;
- bool deactivate_similar = info->mDeactivateSimilar;
-
- delete info;
- info = NULL;
- LLGestureMgr& self = LLGestureMgr::instance();
- self.mLoadingCount--;
-
- if (0 == status)
- {
- LLFileSystem file(asset_uuid, type, LLFileSystem::READ);
- S32 size = file.getSize();
-
- std::vector<char> buffer(size+1);
-
- file.read((U8*)&buffer[0], size);
- // ensure there's a trailing NULL so strlen will work.
- buffer[size] = '\0';
-
- LLMultiGesture* gesture = new LLMultiGesture();
-
- LLDataPackerAsciiBuffer dp(&buffer[0], size+1);
- bool ok = gesture->deserialize(dp);
-
- if (ok)
- {
- if (deactivate_similar)
- {
- self.deactivateSimilarGestures(gesture, item_id);
-
- // Display deactivation message if this was the last of the bunch.
- if (self.mLoadingCount == 0
- && self.mDeactivateSimilarNames.length() > 0)
- {
- // we're done with this set of deactivations
- LLSD args;
- args["NAMES"] = self.mDeactivateSimilarNames;
- LLNotificationsUtil::add("DeactivatedGesturesTrigger", args);
- }
- }
-
- LLViewerInventoryItem* item = gInventory.getItem(item_id);
- if(item)
- {
- gesture->mName = item->getName();
- }
- else
- {
- // Watch this item and set gesture name when item exists in inventory
- self.setFetchID(item_id);
- self.startFetch();
- }
-
- item_map_t::iterator it = self.mActive.find(item_id);
- if (it == self.mActive.end())
- {
- // Gesture is supposed to be present, active, but NULL
- LL_DEBUGS("GestureMgr") << "Gesture " << item_id << " not found in active list" << LL_ENDL;
- }
- else
- {
- LLMultiGesture* old_gesture = (*it).second;
- if (old_gesture && old_gesture != gesture)
- {
- LL_DEBUGS("GestureMgr") << "Received dupplicate " << item_id << " callback" << LL_ENDL;
- // In case somebody managest to activate, deactivate and
- // then activate gesture again, before asset finishes loading.
- // LLLoadInfo will have a different pointer, asset storage will
- // see it as a different request, resulting in two callbacks.
-
- // deactivateSimilarGestures() did not turn this one off
- // because of matching item_id
- self.stopGesture(old_gesture);
-
- self.mActive.erase(item_id);
- delete old_gesture;
- old_gesture = NULL;
- }
- }
-
- self.mActive[item_id] = gesture;
-
- // Everything has been successful. Add to the active list.
- gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id);
-
- if (inform_server)
- {
- // Inform the database of this change
- LLMessageSystem* msg = gMessageSystem;
- msg->newMessage("ActivateGestures");
- msg->nextBlock("AgentData");
- msg->addUUID("AgentID", gAgent.getID());
- msg->addUUID("SessionID", gAgent.getSessionID());
- msg->addU32("Flags", 0x0);
-
- msg->nextBlock("Data");
- msg->addUUID("ItemID", item_id);
- msg->addUUID("AssetID", asset_uuid);
- msg->addU32("GestureFlags", 0x0);
-
- gAgent.sendReliableMessage();
- }
- callback_map_t::iterator i_cb = self.mCallbackMap.find(item_id);
-
- if(i_cb != self.mCallbackMap.end())
- {
- i_cb->second(gesture);
- self.mCallbackMap.erase(i_cb);
- }
-
- self.notifyObservers();
- }
- else
- {
- LL_WARNS("GestureMgr") << "Unable to load gesture" << LL_ENDL;
-
- item_map_t::iterator it = self.mActive.find(item_id);
- if (it != self.mActive.end())
- {
- LLMultiGesture* old_gesture = (*it).second;
- if (old_gesture)
- {
- // Shouldn't happen, just in case
- LL_WARNS("GestureMgr") << "Gesture " << item_id << " existed when it shouldn't" << LL_ENDL;
-
- self.stopGesture(old_gesture);
- delete old_gesture;
- old_gesture = NULL;
- }
- self.mActive.erase(item_id);
- }
-
- delete gesture;
- gesture = NULL;
- }
- }
- else
- {
- if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status ||
- LL_ERR_FILE_EMPTY == status)
- {
- LLDelayedGestureError::gestureMissing( item_id );
- }
- else
- {
- LLDelayedGestureError::gestureFailedToLoad( item_id );
- }
-
- LL_WARNS("GestureMgr") << "Problem loading gesture: " << status << LL_ENDL;
-
- item_map_t::iterator it = self.mActive.find(item_id);
- if (it != self.mActive.end())
- {
- LLMultiGesture* old_gesture = (*it).second;
- if (old_gesture)
- {
- // Shouldn't happen, just in case
- LL_WARNS("GestureMgr") << "Gesture " << item_id << " existed when it shouldn't" << LL_ENDL;
-
- self.stopGesture(old_gesture);
- delete old_gesture;
- old_gesture = NULL;
- }
- self.mActive.erase(item_id);
- }
- }
-}
-
-// static
-void LLGestureMgr::onAssetLoadComplete(const LLUUID& asset_uuid,
- LLAssetType::EType type,
- void* user_data, S32 status, LLExtStat ext_status)
-{
- LLGestureMgr& self = LLGestureMgr::instance();
-
- // Complete the asset loading process depending on the type and
- // remove the asset id from pending downloads list.
- switch(type)
- {
- case LLAssetType::AT_ANIMATION:
- {
- LLKeyframeMotion::onLoadComplete(asset_uuid, type, user_data, status, ext_status);
-
- self.mLoadingAssets.erase(asset_uuid);
-
- break;
- }
- case LLAssetType::AT_SOUND:
- {
- LLAudioEngine::assetCallback(asset_uuid, type, user_data, status, ext_status);
-
- self.mLoadingAssets.erase(asset_uuid);
-
- break;
- }
- default:
- {
- LL_WARNS() << "Unexpected asset type: " << type << LL_ENDL;
-
- // We don't want to return from this callback without
- // an animation or sound callback being fired
- // and *user_data handled to avoid memory leaks.
- llassert(type == LLAssetType::AT_ANIMATION || type == LLAssetType::AT_SOUND);
- }
- }
-}
-
-// static
-bool LLGestureMgr::hasLoadingAssets(LLMultiGesture* gesture)
-{
- LLGestureMgr& self = LLGestureMgr::instance();
-
- for (std::vector<LLGestureStep*>::iterator steps_it = gesture->mSteps.begin();
- steps_it != gesture->mSteps.end();
- ++steps_it)
- {
- LLGestureStep* step = *steps_it;
- switch(step->getType())
- {
- case STEP_ANIMATION:
- {
- LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step;
- const LLUUID& anim_id = anim_step->mAnimAssetID;
-
- if (!(anim_id.isNull()
- || anim_step->mFlags & ANIM_FLAG_STOP
- || self.mLoadingAssets.find(anim_id) == self.mLoadingAssets.end()))
- {
- return true;
- }
- break;
- }
- case STEP_SOUND:
- {
- LLGestureStepSound* sound_step = (LLGestureStepSound*)step;
- const LLUUID& sound_id = sound_step->mSoundAssetID;
-
- if (!(sound_id.isNull()
- || self.mLoadingAssets.find(sound_id) == self.mLoadingAssets.end()))
- {
- return true;
- }
- break;
- }
- case STEP_CHAT:
- case STEP_WAIT:
- case STEP_EOF:
- {
- break;
- }
- default:
- {
- LL_WARNS() << "Unknown gesture step type: " << step->getType() << LL_ENDL;
- }
- }
- }
-
- return false;
-}
-
-void LLGestureMgr::stopGesture(LLMultiGesture* gesture)
-{
- if (!gesture) return;
-
- // Stop any animations that this gesture is currently playing
- std::set<LLUUID>::const_iterator set_it;
- for (set_it = gesture->mRequestedAnimIDs.begin(); set_it != gesture->mRequestedAnimIDs.end(); ++set_it)
- {
- const LLUUID& anim_id = *set_it;
- gAgent.sendAnimationRequest(anim_id, ANIM_REQUEST_STOP);
- }
- for (set_it = gesture->mPlayingAnimIDs.begin(); set_it != gesture->mPlayingAnimIDs.end(); ++set_it)
- {
- const LLUUID& anim_id = *set_it;
- gAgent.sendAnimationRequest(anim_id, ANIM_REQUEST_STOP);
- }
-
- std::vector<LLMultiGesture*>::iterator it;
- it = std::find(mPlaying.begin(), mPlaying.end(), gesture);
- while (it != mPlaying.end())
- {
- mPlaying.erase(it);
- it = std::find(mPlaying.begin(), mPlaying.end(), gesture);
- }
-
- gesture->reset();
-
- if (gesture->mDoneCallback)
- {
- gesture->mDoneCallback(gesture, gesture->mCallbackData);
-
- // callback might have deleted gesture, can't
- // rely on this pointer any more
- gesture = NULL;
- }
-
- notifyObservers();
-}
-
-
-void LLGestureMgr::stopGesture(const LLUUID& item_id)
-{
- const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
-
- item_map_t::iterator it = mActive.find(base_item_id);
- if (it == mActive.end()) return;
-
- LLMultiGesture* gesture = (*it).second;
- if (!gesture) return;
-
- stopGesture(gesture);
-}
-
-
-void LLGestureMgr::addObserver(LLGestureManagerObserver* observer)
-{
- mObservers.push_back(observer);
-}
-
-void LLGestureMgr::removeObserver(LLGestureManagerObserver* observer)
-{
- std::vector<LLGestureManagerObserver*>::iterator it;
- it = std::find(mObservers.begin(), mObservers.end(), observer);
- if (it != mObservers.end())
- {
- mObservers.erase(it);
- }
-}
-
-// Call this method when it's time to update everyone on a new state.
-// Copy the list because an observer could respond by removing itself
-// from the list.
-void LLGestureMgr::notifyObservers()
-{
- LL_DEBUGS() << "LLGestureMgr::notifyObservers" << LL_ENDL;
-
- for(std::vector<LLGestureManagerObserver*>::iterator iter = mObservers.begin();
- iter != mObservers.end();
- ++iter)
- {
- LLGestureManagerObserver* observer = (*iter);
- observer->changed();
- }
-}
-
-bool LLGestureMgr::matchPrefix(const std::string& in_str, std::string* out_str)
-{
- S32 in_len = in_str.length();
-
- //return whole trigger, if received text equals to it
- item_map_t::iterator it;
- for (it = mActive.begin(); it != mActive.end(); ++it)
- {
- LLMultiGesture* gesture = (*it).second;
- if (gesture)
- {
- const std::string& trigger = gesture->getTrigger();
- if (!LLStringUtil::compareInsensitive(in_str, trigger))
- {
- *out_str = trigger;
- return true;
- }
- }
- }
-
- //return common chars, if more than one trigger matches the prefix
- std::string rest_of_match = "";
- std::string buf = "";
- for (it = mActive.begin(); it != mActive.end(); ++it)
- {
- LLMultiGesture* gesture = (*it).second;
- if (gesture)
- {
- const std::string& trigger = gesture->getTrigger();
-
- if (in_len > (S32)trigger.length())
- {
- // too short, bail out
- continue;
- }
-
- std::string trigger_trunc = trigger;
- LLStringUtil::truncate(trigger_trunc, in_len);
- if (!LLStringUtil::compareInsensitive(in_str, trigger_trunc))
- {
- if (rest_of_match.compare("") == 0)
- {
- rest_of_match = trigger.substr(in_str.size());
- }
- std::string cur_rest_of_match = trigger.substr(in_str.size());
- buf = "";
- S32 i=0;
-
- while (i<rest_of_match.length() && i<cur_rest_of_match.length())
- {
- if (rest_of_match[i]==cur_rest_of_match[i])
- {
- buf.push_back(rest_of_match[i]);
- }
- else
- {
- if(i==0)
- {
- rest_of_match = "";
- }
- break;
- }
- i++;
- }
- if (rest_of_match.compare("") == 0)
- {
- return true;
- }
- if (buf.compare("") != 0)
- {
- rest_of_match = buf;
- }
-
- }
- }
- }
-
- if (rest_of_match.compare("") != 0)
- {
- *out_str = in_str+rest_of_match;
- return true;
- }
-
- return false;
-}
-
-
-void LLGestureMgr::getItemIDs(uuid_vec_t* ids)
-{
- item_map_t::const_iterator it;
- for (it = mActive.begin(); it != mActive.end(); ++it)
- {
- ids->push_back(it->first);
- }
-}
-
-void LLGestureMgr::done()
-{
- bool notify = false;
- for(item_map_t::iterator it = mActive.begin(); it != mActive.end(); ++it)
- {
- if(it->second && it->second->mName.empty())
- {
- LLViewerInventoryItem* item = gInventory.getItem(it->first);
- if(item)
- {
- it->second->mName = item->getName();
- notify = true;
- }
- }
- }
- if(notify)
- {
- notifyObservers();
- }
-}
-
-
+/**
+ * @file llgesturemgr.cpp
+ * @brief Manager for playing gestures on the viewer
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llgesturemgr.h"
+
+// system
+#include <functional>
+#include <algorithm>
+
+// library
+#include "llaudioengine.h"
+#include "lldatapacker.h"
+#include "llfloaterreg.h"
+#include "llinventory.h"
+#include "llkeyframemotion.h"
+#include "llmultigesture.h"
+#include "llnotificationsutil.h"
+#include "llstl.h"
+#include "llstring.h" // todo: remove
+#include "llfilesystem.h"
+#include "message.h"
+
+// newview
+#include "llagent.h"
+#include "lldelayedgestureerror.h"
+#include "llinventorymodel.h"
+#include "llviewermessage.h"
+#include "llvoavatarself.h"
+#include "llviewerstats.h"
+#include "llfloaterimnearbychat.h"
+#include "llappearancemgr.h"
+#include "llgesturelistener.h"
+
+// Longest time, in seconds, to wait for all animations to stop playing
+const F32 MAX_WAIT_ANIM_SECS = 30.f;
+// Longest time, in seconds, to wait for a key release.
+// This should be relatively long, but not too long. 10 minutes is enough
+const F32 MAX_WAIT_KEY_SECS = 60.f * 10.f;
+
+// Lightweight constructor.
+// init() does the heavy lifting.
+LLGestureMgr::LLGestureMgr()
+: mValid(false),
+ mPlaying(),
+ mActive(),
+ mLoadingCount(0)
+{
+ gInventory.addObserver(this);
+ mListener.reset(new LLGestureListener());
+}
+
+
+// We own the data for gestures, so clean them up.
+LLGestureMgr::~LLGestureMgr()
+{
+ item_map_t::iterator it;
+ for (it = mActive.begin(); it != mActive.end(); ++it)
+ {
+ LLMultiGesture* gesture = (*it).second;
+
+ delete gesture;
+ gesture = NULL;
+ }
+ gInventory.removeObserver(this);
+}
+
+
+void LLGestureMgr::init()
+{
+ // TODO
+}
+
+void LLGestureMgr::changed(U32 mask)
+{
+ LLInventoryFetchItemsObserver::changed(mask);
+
+ if (mask & LLInventoryObserver::GESTURE)
+ {
+ // If there was a gesture label changed, update all the names in the
+ // active gestures and then notify observers
+ if (mask & LLInventoryObserver::LABEL)
+ {
+ for(item_map_t::iterator it = mActive.begin(); it != mActive.end(); ++it)
+ {
+ if(it->second)
+ {
+ LLViewerInventoryItem* item = gInventory.getItem(it->first);
+ if(item)
+ {
+ it->second->mName = item->getName();
+ }
+ }
+ }
+ notifyObservers();
+ }
+ // If there was a gesture added or removed notify observers
+ // STRUCTURE denotes that the inventory item has been moved
+ // In the case of deleting gesture, it is moved to the trash
+ else if(mask & LLInventoryObserver::ADD ||
+ mask & LLInventoryObserver::REMOVE ||
+ mask & LLInventoryObserver::STRUCTURE)
+ {
+ notifyObservers();
+ }
+ }
+}
+
+
+// Use this version when you have the item_id but not the asset_id,
+// and you KNOW the inventory is loaded.
+void LLGestureMgr::activateGesture(const LLUUID& item_id)
+{
+ LLViewerInventoryItem* item = gInventory.getItem(item_id);
+ if (!item) return;
+ if (item->getType() != LLAssetType::AT_GESTURE)
+ return;
+
+ LLUUID asset_id = item->getAssetUUID();
+
+ mLoadingCount = 1;
+ mDeactivateSimilarNames.clear();
+
+ const bool inform_server = true;
+ const bool deactivate_similar = false;
+ activateGestureWithAsset(item_id, asset_id, inform_server, deactivate_similar);
+}
+
+
+void LLGestureMgr::activateGestures(LLViewerInventoryItem::item_array_t& items)
+{
+ // Load up the assets
+ S32 count = 0;
+ LLViewerInventoryItem::item_array_t::const_iterator it;
+ for (it = items.begin(); it != items.end(); ++it)
+ {
+ LLViewerInventoryItem* item = *it;
+
+ if (isGestureActive(item->getUUID()))
+ {
+ continue;
+ }
+ else
+ { // Make gesture active and persistent through login sessions. -Aura 07-12-06
+ activateGesture(item->getUUID());
+ }
+
+ count++;
+ }
+
+ mLoadingCount = count;
+ mDeactivateSimilarNames.clear();
+
+ for (it = items.begin(); it != items.end(); ++it)
+ {
+ LLViewerInventoryItem* item = *it;
+
+ if (isGestureActive(item->getUUID()))
+ {
+ continue;
+ }
+
+ // Don't inform server, we'll do that in bulk
+ const bool no_inform_server = false;
+ const bool deactivate_similar = true;
+ activateGestureWithAsset(item->getUUID(), item->getAssetUUID(),
+ no_inform_server,
+ deactivate_similar);
+ }
+
+ // Inform the database of this change
+ LLMessageSystem* msg = gMessageSystem;
+
+ bool start_message = true;
+
+ for (it = items.begin(); it != items.end(); ++it)
+ {
+ LLViewerInventoryItem* item = *it;
+
+ if (isGestureActive(item->getUUID()))
+ {
+ continue;
+ }
+
+ if (start_message)
+ {
+ msg->newMessage("ActivateGestures");
+ msg->nextBlock("AgentData");
+ msg->addUUID("AgentID", gAgent.getID());
+ msg->addUUID("SessionID", gAgent.getSessionID());
+ msg->addU32("Flags", 0x0);
+ start_message = false;
+ }
+
+ msg->nextBlock("Data");
+ msg->addUUID("ItemID", item->getUUID());
+ msg->addUUID("AssetID", item->getAssetUUID());
+ msg->addU32("GestureFlags", 0x0);
+
+ if (msg->getCurrentSendTotal() > MTUBYTES)
+ {
+ gAgent.sendReliableMessage();
+ start_message = true;
+ }
+ }
+
+ if (!start_message)
+ {
+ gAgent.sendReliableMessage();
+ }
+}
+
+
+struct LLLoadInfo
+{
+ LLUUID mItemID;
+ bool mInformServer;
+ bool mDeactivateSimilar;
+};
+
+// If inform_server is true, will send a message upstream to update
+// the user_gesture_active table.
+/**
+ * It will load a gesture from remote storage
+ */
+void LLGestureMgr::activateGestureWithAsset(const LLUUID& item_id,
+ const LLUUID& asset_id,
+ bool inform_server,
+ bool deactivate_similar)
+{
+ const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
+
+ if( !gAssetStorage )
+ {
+ LL_WARNS() << "LLGestureMgr::activateGestureWithAsset without valid gAssetStorage" << LL_ENDL;
+ return;
+ }
+ // If gesture is already active, nothing to do.
+ if (isGestureActive(item_id))
+ {
+ LL_WARNS() << "Tried to loadGesture twice " << item_id << LL_ENDL;
+ return;
+ }
+
+// if (asset_id.isNull())
+// {
+// LL_WARNS() << "loadGesture() - gesture has no asset" << LL_ENDL;
+// return;
+// }
+
+ // For now, put NULL into the item map. We'll build a gesture
+ // class object when the asset data arrives.
+ mActive[base_item_id] = NULL;
+
+ // Copy the UUID
+ if (asset_id.notNull())
+ {
+ LLLoadInfo* info = new LLLoadInfo;
+ info->mItemID = base_item_id;
+ info->mInformServer = inform_server;
+ info->mDeactivateSimilar = deactivate_similar;
+
+ const bool high_priority = true;
+ gAssetStorage->getAssetData(asset_id,
+ LLAssetType::AT_GESTURE,
+ onLoadComplete,
+ (void*)info,
+ high_priority);
+ }
+ else
+ {
+ notifyObservers();
+ }
+}
+
+
+void notify_update_label(const LLUUID& base_item_id)
+{
+ gInventory.addChangedMask(LLInventoryObserver::LABEL, base_item_id);
+ LLGestureMgr::instance().notifyObservers();
+}
+
+void LLGestureMgr::deactivateGesture(const LLUUID& item_id)
+{
+ const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
+ item_map_t::iterator it = mActive.find(base_item_id);
+ if (it == mActive.end())
+ {
+ LL_WARNS() << "deactivateGesture for inactive gesture " << item_id << LL_ENDL;
+ return;
+ }
+
+ // mActive owns this gesture pointer, so clean up memory.
+ LLMultiGesture* gesture = (*it).second;
+
+ // Can be NULL gestures in the map
+ if (gesture)
+ {
+ stopGesture(gesture);
+
+ delete gesture;
+ gesture = NULL;
+ }
+
+ mActive.erase(it);
+
+ // Inform the database of this change
+ LLMessageSystem* msg = gMessageSystem;
+ msg->newMessage("DeactivateGestures");
+ msg->nextBlock("AgentData");
+ msg->addUUID("AgentID", gAgent.getID());
+ msg->addUUID("SessionID", gAgent.getSessionID());
+ msg->addU32("Flags", 0x0);
+
+ msg->nextBlock("Data");
+ msg->addUUID("ItemID", item_id);
+ msg->addU32("GestureFlags", 0x0);
+
+ gAgent.sendReliableMessage();
+
+ LLPointer<LLInventoryCallback> cb =
+ new LLBoostFuncInventoryCallback(no_op_inventory_func,
+ boost::bind(notify_update_label,base_item_id));
+
+ LLAppearanceMgr::instance().removeCOFItemLinks(base_item_id, cb);
+}
+
+
+void LLGestureMgr::deactivateSimilarGestures(LLMultiGesture* in, const LLUUID& in_item_id)
+{
+ const LLUUID& base_in_item_id = gInventory.getLinkedItemID(in_item_id);
+ uuid_vec_t gest_item_ids;
+
+ // Deactivate all gestures that match
+ item_map_t::iterator it;
+ for (it = mActive.begin(); it != mActive.end(); )
+ {
+ const LLUUID& item_id = (*it).first;
+ LLMultiGesture* gest = (*it).second;
+
+ // Don't deactivate the gesture we are looking for duplicates of
+ // (for replaceGesture)
+ if (!gest || item_id == base_in_item_id)
+ {
+ // legal, can have null pointers in list
+ ++it;
+ }
+ else if ((!gest->mTrigger.empty() && gest->mTrigger == in->mTrigger)
+ || (gest->mKey != KEY_NONE && gest->mKey == in->mKey && gest->mMask == in->mMask))
+ {
+ gest_item_ids.push_back(item_id);
+
+ stopGesture(gest);
+
+ delete gest;
+ gest = NULL;
+
+ mActive.erase(it++);
+ gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id);
+
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ // Inform database of the change
+ LLMessageSystem* msg = gMessageSystem;
+ bool start_message = true;
+ uuid_vec_t::const_iterator vit = gest_item_ids.begin();
+ while (vit != gest_item_ids.end())
+ {
+ if (start_message)
+ {
+ msg->newMessage("DeactivateGestures");
+ msg->nextBlock("AgentData");
+ msg->addUUID("AgentID", gAgent.getID());
+ msg->addUUID("SessionID", gAgent.getSessionID());
+ msg->addU32("Flags", 0x0);
+ start_message = false;
+ }
+
+ msg->nextBlock("Data");
+ msg->addUUID("ItemID", *vit);
+ msg->addU32("GestureFlags", 0x0);
+
+ if (msg->getCurrentSendTotal() > MTUBYTES)
+ {
+ gAgent.sendReliableMessage();
+ start_message = true;
+ }
+
+ ++vit;
+ }
+
+ if (!start_message)
+ {
+ gAgent.sendReliableMessage();
+ }
+
+ // Add to the list of names for the user.
+ for (vit = gest_item_ids.begin(); vit != gest_item_ids.end(); ++vit)
+ {
+ LLViewerInventoryItem* item = gInventory.getItem(*vit);
+ if (!item) continue;
+
+ mDeactivateSimilarNames.append(item->getName());
+ mDeactivateSimilarNames.append("\n");
+ }
+
+ notifyObservers();
+}
+
+
+bool LLGestureMgr::isGestureActive(const LLUUID& item_id)
+{
+ const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
+ item_map_t::iterator it = mActive.find(base_item_id);
+ return (it != mActive.end());
+}
+
+
+bool LLGestureMgr::isGesturePlaying(const LLUUID& item_id)
+{
+ const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
+
+ item_map_t::iterator it = mActive.find(base_item_id);
+ if (it == mActive.end()) return false;
+
+ LLMultiGesture* gesture = (*it).second;
+ if (!gesture) return false;
+
+ return gesture->mPlaying;
+}
+
+bool LLGestureMgr::isGesturePlaying(LLMultiGesture* gesture)
+{
+ if(!gesture)
+ {
+ return false;
+ }
+
+ return gesture->mPlaying;
+}
+
+void LLGestureMgr::replaceGesture(const LLUUID& item_id, LLMultiGesture* new_gesture, const LLUUID& asset_id)
+{
+ const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
+
+ item_map_t::iterator it = mActive.find(base_item_id);
+ if (it == mActive.end())
+ {
+ LL_WARNS() << "replaceGesture for inactive gesture " << base_item_id << LL_ENDL;
+ return;
+ }
+
+ LLMultiGesture* old_gesture = (*it).second;
+ stopGesture(old_gesture);
+
+ mActive.erase(base_item_id);
+
+ mActive[base_item_id] = new_gesture;
+
+ // replaceGesture(const LLUUID& item_id, const LLUUID& new_asset_id)
+ // replaces ids without repalcing gesture
+ if (old_gesture != new_gesture)
+ {
+ delete old_gesture;
+ old_gesture = NULL;
+ }
+
+ if (asset_id.notNull())
+ {
+ mLoadingCount = 1;
+ mDeactivateSimilarNames.clear();
+
+ LLLoadInfo* info = new LLLoadInfo;
+ info->mItemID = base_item_id;
+ info->mInformServer = true;
+ info->mDeactivateSimilar = false;
+
+ const bool high_priority = true;
+ gAssetStorage->getAssetData(asset_id,
+ LLAssetType::AT_GESTURE,
+ onLoadComplete,
+ (void*)info,
+ high_priority);
+ }
+
+ notifyObservers();
+}
+
+void LLGestureMgr::replaceGesture(const LLUUID& item_id, const LLUUID& new_asset_id)
+{
+ const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
+
+ item_map_t::iterator it = LLGestureMgr::instance().mActive.find(base_item_id);
+ if (it == mActive.end())
+ {
+ LL_WARNS() << "replaceGesture for inactive gesture " << base_item_id << LL_ENDL;
+ return;
+ }
+
+ // mActive owns this gesture pointer, so clean up memory.
+ LLMultiGesture* gesture = (*it).second;
+ LLGestureMgr::instance().replaceGesture(base_item_id, gesture, new_asset_id);
+}
+
+void LLGestureMgr::playGesture(LLMultiGesture* gesture, bool fromKeyPress)
+{
+ if (!gesture) return;
+
+ // Reset gesture to first step
+ gesture->mCurrentStep = 0;
+ gesture->mTriggeredByKey = fromKeyPress;
+
+ // Add to list of playing
+ gesture->mPlaying = true;
+ mPlaying.push_back(gesture);
+
+ // Load all needed assets to minimize the delays
+ // when gesture is playing.
+ for (std::vector<LLGestureStep*>::iterator steps_it = gesture->mSteps.begin();
+ steps_it != gesture->mSteps.end();
+ ++steps_it)
+ {
+ LLGestureStep* step = *steps_it;
+ switch(step->getType())
+ {
+ case STEP_ANIMATION:
+ {
+ LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step;
+ const LLUUID& anim_id = anim_step->mAnimAssetID;
+
+ // Don't request the animation if this step stops it or if it is already in the cache
+ if (!(anim_id.isNull()
+ || anim_step->mFlags & ANIM_FLAG_STOP
+ || gAssetStorage->hasLocalAsset(anim_id, LLAssetType::AT_ANIMATION)))
+ {
+ mLoadingAssets.insert(anim_id);
+
+ LLUUID* id = new LLUUID(gAgentID);
+ gAssetStorage->getAssetData(anim_id,
+ LLAssetType::AT_ANIMATION,
+ onAssetLoadComplete,
+ (void *)id,
+ true);
+ }
+ break;
+ }
+ case STEP_SOUND:
+ {
+ LLGestureStepSound* sound_step = (LLGestureStepSound*)step;
+ const LLUUID& sound_id = sound_step->mSoundAssetID;
+ if (!(sound_id.isNull()
+ || gAssetStorage->hasLocalAsset(sound_id, LLAssetType::AT_SOUND)))
+ {
+ mLoadingAssets.insert(sound_id);
+
+ gAssetStorage->getAssetData(sound_id,
+ LLAssetType::AT_SOUND,
+ onAssetLoadComplete,
+ NULL,
+ true);
+ }
+ break;
+ }
+ case STEP_CHAT:
+ case STEP_WAIT:
+ case STEP_EOF:
+ {
+ break;
+ }
+ default:
+ {
+ LL_WARNS() << "Unknown gesture step type: " << step->getType() << LL_ENDL;
+ }
+ }
+ }
+
+ // And get it going
+ stepGesture(gesture);
+
+ notifyObservers();
+}
+
+
+// Convenience function that looks up the item_id for you.
+void LLGestureMgr::playGesture(const LLUUID& item_id)
+{
+ const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
+
+ item_map_t::iterator it = mActive.find(base_item_id);
+ if (it == mActive.end()) return;
+
+ LLMultiGesture* gesture = (*it).second;
+ if (!gesture) return;
+
+ playGesture(gesture);
+}
+
+
+// Iterates through space delimited tokens in string, triggering any gestures found.
+// Generates a revised string that has the found tokens replaced by their replacement strings
+// and (as a minor side effect) has multiple spaces in a row replaced by single spaces.
+bool LLGestureMgr::triggerAndReviseString(const std::string &utf8str, std::string* revised_string)
+{
+ std::string tokenized = utf8str;
+
+ bool found_gestures = false;
+ bool first_token = true;
+
+ typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
+ boost::char_separator<char> sep(" ");
+ tokenizer tokens(tokenized, sep);
+ tokenizer::iterator token_iter;
+
+ for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
+ {
+ const char* cur_token = token_iter->c_str();
+ LLMultiGesture* gesture = NULL;
+
+ // Only pay attention to the first gesture in the string.
+ if( !found_gestures )
+ {
+ // collect gestures that match
+ std::vector <LLMultiGesture *> matching;
+ item_map_t::iterator it;
+ for (it = mActive.begin(); it != mActive.end(); ++it)
+ {
+ gesture = (*it).second;
+
+ // Gesture asset data might not have arrived yet
+ if (!gesture) continue;
+
+ if (LLStringUtil::compareInsensitive(gesture->mTrigger, cur_token) == 0)
+ {
+ matching.push_back(gesture);
+ }
+
+ gesture = NULL;
+ }
+
+
+ if (matching.size() > 0)
+ {
+ // choose one at random
+ {
+ S32 random = ll_rand(matching.size());
+
+ gesture = matching[random];
+
+ playGesture(gesture);
+
+ if (!gesture->mReplaceText.empty())
+ {
+ if( !first_token )
+ {
+ if (revised_string)
+ revised_string->append( " " );
+ }
+
+ // Don't muck with the user's capitalization if we don't have to.
+ if( LLStringUtil::compareInsensitive(cur_token, gesture->mReplaceText) == 0)
+ {
+ if (revised_string)
+ revised_string->append( cur_token );
+ }
+ else
+ {
+ if (revised_string)
+ revised_string->append( gesture->mReplaceText );
+ }
+ }
+ found_gestures = true;
+ }
+ }
+ }
+
+ if(!gesture)
+ {
+ // This token doesn't match a gesture. Pass it through to the output.
+ if( !first_token )
+ {
+ if (revised_string)
+ revised_string->append( " " );
+ }
+ if (revised_string)
+ revised_string->append( cur_token );
+ }
+
+ first_token = false;
+ gesture = NULL;
+ }
+ return found_gestures;
+}
+
+
+bool LLGestureMgr::triggerGesture(KEY key, MASK mask)
+{
+ std::vector <LLMultiGesture *> matching;
+ item_map_t::iterator it;
+
+ // collect matching gestures
+ for (it = mActive.begin(); it != mActive.end(); ++it)
+ {
+ LLMultiGesture* gesture = (*it).second;
+
+ // asset data might not have arrived yet
+ if (!gesture) continue;
+
+ if (gesture->mKey == key
+ && gesture->mMask == mask
+ && gesture->mWaitingKeyRelease == false)
+ {
+ matching.push_back(gesture);
+ }
+ }
+
+ // choose one and play it
+ if (matching.size() > 0)
+ {
+ U32 random = ll_rand(matching.size());
+
+ LLMultiGesture* gesture = matching[random];
+
+ playGesture(gesture, true);
+ return true;
+ }
+ return false;
+}
+
+
+bool LLGestureMgr::triggerGestureRelease(KEY key, MASK mask)
+{
+ std::vector <LLMultiGesture *> matching;
+ item_map_t::iterator it;
+
+ // collect matching gestures
+ for (it = mActive.begin(); it != mActive.end(); ++it)
+ {
+ LLMultiGesture* gesture = (*it).second;
+
+ // asset data might not have arrived yet
+ if (!gesture) continue;
+
+ if (gesture->mKey == key
+ && gesture->mMask == mask)
+ {
+ gesture->mKeyReleased = true;
+ }
+ }
+
+ //If we found one, block. Otherwise tell them it's free to go.
+ return matching.size() > 0;
+}
+
+
+S32 LLGestureMgr::getPlayingCount() const
+{
+ return mPlaying.size();
+}
+
+
+struct IsGesturePlaying
+{
+ bool operator()(const LLMultiGesture* gesture) const
+ {
+ return bool(gesture->mPlaying);
+ }
+};
+
+void LLGestureMgr::update()
+{
+ S32 i;
+ for (i = 0; i < (S32)mPlaying.size(); ++i)
+ {
+ stepGesture(mPlaying[i]);
+ }
+
+ // Clear out gestures that are done, by moving all the
+ // ones that are still playing to the front.
+ std::vector<LLMultiGesture*>::iterator new_end;
+ new_end = std::partition(mPlaying.begin(),
+ mPlaying.end(),
+ IsGesturePlaying());
+
+ // Something finished playing
+ if (new_end != mPlaying.end())
+ {
+ // Delete the completed gestures that want deletion
+ std::vector<LLMultiGesture*>::iterator it;
+ for (it = new_end; it != mPlaying.end(); ++it)
+ {
+ LLMultiGesture* gesture = *it;
+
+ if (gesture->mDoneCallback)
+ {
+ gesture->mDoneCallback(gesture, gesture->mCallbackData);
+
+ // callback might have deleted gesture, can't
+ // rely on this pointer any more
+ gesture = NULL;
+ }
+ }
+
+ // And take done gestures out of the playing list
+ mPlaying.erase(new_end, mPlaying.end());
+
+ notifyObservers();
+ }
+}
+
+
+// Run all steps until you're either done or hit a wait.
+void LLGestureMgr::stepGesture(LLMultiGesture* gesture)
+{
+ if (!gesture)
+ {
+ return;
+ }
+ if (!isAgentAvatarValid() || hasLoadingAssets(gesture)) return;
+
+ // Of the ones that started playing, have any stopped?
+
+ std::set<LLUUID>::iterator gest_it;
+ for (gest_it = gesture->mPlayingAnimIDs.begin();
+ gest_it != gesture->mPlayingAnimIDs.end();
+ )
+ {
+ // look in signaled animations (simulator's view of what is
+ // currently playing.
+ LLVOAvatar::AnimIterator play_it = gAgentAvatarp->mSignaledAnimations.find(*gest_it);
+ if (play_it != gAgentAvatarp->mSignaledAnimations.end())
+ {
+ ++gest_it;
+ }
+ else
+ {
+ // not found, so not currently playing or scheduled to play
+ // delete from the triggered set
+ gesture->mPlayingAnimIDs.erase(gest_it++);
+ }
+ }
+
+ // Of all the animations that we asked the sim to start for us,
+ // pick up the ones that have actually started.
+ for (gest_it = gesture->mRequestedAnimIDs.begin();
+ gest_it != gesture->mRequestedAnimIDs.end();
+ )
+ {
+ LLVOAvatar::AnimIterator play_it = gAgentAvatarp->mSignaledAnimations.find(*gest_it);
+ if (play_it != gAgentAvatarp->mSignaledAnimations.end())
+ {
+ // Hooray, this animation has started playing!
+ // Copy into playing.
+ gesture->mPlayingAnimIDs.insert(*gest_it);
+ gesture->mRequestedAnimIDs.erase(gest_it++);
+ }
+ else
+ {
+ // nope, not playing yet
+ ++gest_it;
+ }
+ }
+
+ // Run the current steps
+ bool waiting = false;
+ while (!waiting && gesture->mPlaying)
+ {
+ // Get the current step, if there is one.
+ // Otherwise enter the waiting at end state.
+ LLGestureStep* step = NULL;
+ if (gesture->mCurrentStep < (S32)gesture->mSteps.size())
+ {
+ step = gesture->mSteps[gesture->mCurrentStep];
+ llassert(step != NULL);
+ }
+ else
+ {
+ // step stays null, we're off the end
+ gesture->mWaitingAtEnd = true;
+ }
+
+
+ // If we're waiting at the end, wait for all gestures to stop
+ // playing.
+ // TODO: Wait for all sounds to complete as well.
+ if (gesture->mWaitingAtEnd)
+ {
+ // Neither do we have any pending requests, nor are they
+ // still playing.
+ if ((gesture->mRequestedAnimIDs.empty()
+ && gesture->mPlayingAnimIDs.empty()))
+ {
+ // all animations are done playing
+ gesture->mWaitingAtEnd = false;
+ gesture->mPlaying = false;
+ }
+ else
+ {
+ waiting = true;
+ }
+ continue;
+ }
+
+ // If we're waiting a fixed amount of time, check for timer
+ // expiration.
+ if (gesture->mWaitingKeyRelease)
+ {
+ // We're waiting for a certain amount of time to pass
+ if (gesture->mKeyReleased)
+ {
+ // wait is done, continue execution
+ gesture->mWaitingKeyRelease = false;
+ gesture->mCurrentStep++;
+ }
+ else if (gesture->mWaitTimer.getElapsedTimeF32() > MAX_WAIT_KEY_SECS)
+ {
+ LL_INFOS("GestureMgr") << "Waited too long for key release, continuing gesture."
+ << LL_ENDL;
+ gesture->mWaitingKeyRelease = false;
+ gesture->mCurrentStep++;
+ }
+ else
+ {
+ // we're waiting, so execution is done for now
+ waiting = true;
+ }
+ continue;
+ }
+
+ // If we're waiting on our animations to stop, poll for
+ // completion.
+ if (gesture->mWaitingAnimations)
+ {
+ // Neither do we have any pending requests, nor are they
+ // still playing.
+ if ((gesture->mRequestedAnimIDs.empty()
+ && gesture->mPlayingAnimIDs.empty()))
+ {
+ // all animations are done playing
+ gesture->mWaitingAnimations = false;
+ gesture->mCurrentStep++;
+ }
+ else if (gesture->mWaitTimer.getElapsedTimeF32() > MAX_WAIT_ANIM_SECS)
+ {
+ // we've waited too long for an animation
+ LL_INFOS("GestureMgr") << "Waited too long for animations to stop, continuing gesture."
+ << LL_ENDL;
+ gesture->mWaitingAnimations = false;
+ gesture->mCurrentStep++;
+ }
+ else
+ {
+ waiting = true;
+ }
+ continue;
+ }
+
+ // If we're waiting a fixed amount of time, check for timer
+ // expiration.
+ if (gesture->mWaitingTimer)
+ {
+ // We're waiting for a certain amount of time to pass
+ LLGestureStepWait* wait_step = (LLGestureStepWait*)step;
+
+ F32 elapsed = gesture->mWaitTimer.getElapsedTimeF32();
+ if (elapsed > wait_step->mWaitSeconds)
+ {
+ // wait is done, continue execution
+ gesture->mWaitingTimer = false;
+ gesture->mCurrentStep++;
+ }
+ else
+ {
+ // we're waiting, so execution is done for now
+ waiting = true;
+ }
+ continue;
+ }
+
+ // Not waiting, do normal execution
+ runStep(gesture, step);
+ }
+}
+
+
+void LLGestureMgr::runStep(LLMultiGesture* gesture, LLGestureStep* step)
+{
+ switch(step->getType())
+ {
+ case STEP_ANIMATION:
+ {
+ LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step;
+ if (anim_step->mAnimAssetID.isNull())
+ {
+ gesture->mCurrentStep++;
+ }
+
+ if (anim_step->mFlags & ANIM_FLAG_STOP)
+ {
+ gAgent.sendAnimationRequest(anim_step->mAnimAssetID, ANIM_REQUEST_STOP);
+ // remove it from our request set in case we just requested it
+ std::set<LLUUID>::iterator set_it = gesture->mRequestedAnimIDs.find(anim_step->mAnimAssetID);
+ if (set_it != gesture->mRequestedAnimIDs.end())
+ {
+ gesture->mRequestedAnimIDs.erase(set_it);
+ }
+ }
+ else
+ {
+ gAgent.sendAnimationRequest(anim_step->mAnimAssetID, ANIM_REQUEST_START);
+ // Indicate that we've requested this animation to play as
+ // part of this gesture (but it won't start playing for at
+ // least one round-trip to simulator).
+ gesture->mRequestedAnimIDs.insert(anim_step->mAnimAssetID);
+ }
+ gesture->mCurrentStep++;
+ break;
+ }
+ case STEP_SOUND:
+ {
+ LLGestureStepSound* sound_step = (LLGestureStepSound*)step;
+ const LLUUID& sound_id = sound_step->mSoundAssetID;
+ const F32 volume = 1.f;
+ send_sound_trigger(sound_id, volume);
+ gesture->mCurrentStep++;
+ break;
+ }
+ case STEP_CHAT:
+ {
+ LLGestureStepChat* chat_step = (LLGestureStepChat*)step;
+ std::string chat_text = chat_step->mChatText;
+ // Don't animate the nodding, as this might not blend with
+ // other playing animations.
+
+ const bool animate = false;
+
+ (LLFloaterReg::getTypedInstance<LLFloaterIMNearbyChat>("nearby_chat"))->
+ sendChatFromViewer(chat_text, CHAT_TYPE_NORMAL, animate);
+
+ gesture->mCurrentStep++;
+ break;
+ }
+ case STEP_WAIT:
+ {
+ LLGestureStepWait* wait_step = (LLGestureStepWait*)step;
+ if (gesture->mTriggeredByKey // Only wait here IF we were triggered by a key!
+ && gesture->mWaitingKeyRelease == false // We can only do this once! Prevent gestures infinitely running
+ && wait_step->mFlags & WAIT_FLAG_KEY_RELEASE)
+ {
+ // Lets wait for the key release first so we don't hold up re-presses
+ gesture->mWaitingKeyRelease = true;
+ gesture->mKeyReleased = false;
+ // Use the wait timer as a deadlock breaker for key release waits.
+ gesture->mWaitTimer.reset();
+ }
+ else if (wait_step->mFlags & WAIT_FLAG_TIME)
+ {
+ gesture->mWaitingTimer = true;
+ gesture->mWaitTimer.reset();
+ }
+ else if (wait_step->mFlags & WAIT_FLAG_ALL_ANIM)
+ {
+ gesture->mWaitingAnimations = true;
+ // Use the wait timer as a deadlock breaker for animation waits.
+ gesture->mWaitTimer.reset();
+ }
+ else
+ {
+ gesture->mCurrentStep++;
+ }
+ // Don't increment instruction pointer until wait is complete.
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+}
+
+
+// static
+void LLGestureMgr::onLoadComplete(const LLUUID& asset_uuid,
+ LLAssetType::EType type,
+ void* user_data, S32 status, LLExtStat ext_status)
+{
+ LLLoadInfo* info = (LLLoadInfo*)user_data;
+
+ LLUUID item_id = info->mItemID;
+ bool inform_server = info->mInformServer;
+ bool deactivate_similar = info->mDeactivateSimilar;
+
+ delete info;
+ info = NULL;
+ LLGestureMgr& self = LLGestureMgr::instance();
+ self.mLoadingCount--;
+
+ if (0 == status)
+ {
+ LLFileSystem file(asset_uuid, type, LLFileSystem::READ);
+ S32 size = file.getSize();
+
+ std::vector<char> buffer(size+1);
+
+ file.read((U8*)&buffer[0], size);
+ // ensure there's a trailing NULL so strlen will work.
+ buffer[size] = '\0';
+
+ LLMultiGesture* gesture = new LLMultiGesture();
+
+ LLDataPackerAsciiBuffer dp(&buffer[0], size+1);
+ bool ok = gesture->deserialize(dp);
+
+ if (ok)
+ {
+ if (deactivate_similar)
+ {
+ self.deactivateSimilarGestures(gesture, item_id);
+
+ // Display deactivation message if this was the last of the bunch.
+ if (self.mLoadingCount == 0
+ && self.mDeactivateSimilarNames.length() > 0)
+ {
+ // we're done with this set of deactivations
+ LLSD args;
+ args["NAMES"] = self.mDeactivateSimilarNames;
+ LLNotificationsUtil::add("DeactivatedGesturesTrigger", args);
+ }
+ }
+
+ LLViewerInventoryItem* item = gInventory.getItem(item_id);
+ if(item)
+ {
+ gesture->mName = item->getName();
+ }
+ else
+ {
+ // Watch this item and set gesture name when item exists in inventory
+ self.setFetchID(item_id);
+ self.startFetch();
+ }
+
+ item_map_t::iterator it = self.mActive.find(item_id);
+ if (it == self.mActive.end())
+ {
+ // Gesture is supposed to be present, active, but NULL
+ LL_DEBUGS("GestureMgr") << "Gesture " << item_id << " not found in active list" << LL_ENDL;
+ }
+ else
+ {
+ LLMultiGesture* old_gesture = (*it).second;
+ if (old_gesture && old_gesture != gesture)
+ {
+ LL_DEBUGS("GestureMgr") << "Received dupplicate " << item_id << " callback" << LL_ENDL;
+ // In case somebody managest to activate, deactivate and
+ // then activate gesture again, before asset finishes loading.
+ // LLLoadInfo will have a different pointer, asset storage will
+ // see it as a different request, resulting in two callbacks.
+
+ // deactivateSimilarGestures() did not turn this one off
+ // because of matching item_id
+ self.stopGesture(old_gesture);
+
+ self.mActive.erase(item_id);
+ delete old_gesture;
+ old_gesture = NULL;
+ }
+ }
+
+ self.mActive[item_id] = gesture;
+
+ // Everything has been successful. Add to the active list.
+ gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id);
+
+ if (inform_server)
+ {
+ // Inform the database of this change
+ LLMessageSystem* msg = gMessageSystem;
+ msg->newMessage("ActivateGestures");
+ msg->nextBlock("AgentData");
+ msg->addUUID("AgentID", gAgent.getID());
+ msg->addUUID("SessionID", gAgent.getSessionID());
+ msg->addU32("Flags", 0x0);
+
+ msg->nextBlock("Data");
+ msg->addUUID("ItemID", item_id);
+ msg->addUUID("AssetID", asset_uuid);
+ msg->addU32("GestureFlags", 0x0);
+
+ gAgent.sendReliableMessage();
+ }
+ callback_map_t::iterator i_cb = self.mCallbackMap.find(item_id);
+
+ if(i_cb != self.mCallbackMap.end())
+ {
+ i_cb->second(gesture);
+ self.mCallbackMap.erase(i_cb);
+ }
+
+ self.notifyObservers();
+ }
+ else
+ {
+ LL_WARNS("GestureMgr") << "Unable to load gesture" << LL_ENDL;
+
+ item_map_t::iterator it = self.mActive.find(item_id);
+ if (it != self.mActive.end())
+ {
+ LLMultiGesture* old_gesture = (*it).second;
+ if (old_gesture)
+ {
+ // Shouldn't happen, just in case
+ LL_WARNS("GestureMgr") << "Gesture " << item_id << " existed when it shouldn't" << LL_ENDL;
+
+ self.stopGesture(old_gesture);
+ delete old_gesture;
+ old_gesture = NULL;
+ }
+ self.mActive.erase(item_id);
+ }
+
+ delete gesture;
+ gesture = NULL;
+ }
+ }
+ else
+ {
+ if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status ||
+ LL_ERR_FILE_EMPTY == status)
+ {
+ LLDelayedGestureError::gestureMissing( item_id );
+ }
+ else
+ {
+ LLDelayedGestureError::gestureFailedToLoad( item_id );
+ }
+
+ LL_WARNS("GestureMgr") << "Problem loading gesture: " << status << LL_ENDL;
+
+ item_map_t::iterator it = self.mActive.find(item_id);
+ if (it != self.mActive.end())
+ {
+ LLMultiGesture* old_gesture = (*it).second;
+ if (old_gesture)
+ {
+ // Shouldn't happen, just in case
+ LL_WARNS("GestureMgr") << "Gesture " << item_id << " existed when it shouldn't" << LL_ENDL;
+
+ self.stopGesture(old_gesture);
+ delete old_gesture;
+ old_gesture = NULL;
+ }
+ self.mActive.erase(item_id);
+ }
+ }
+}
+
+// static
+void LLGestureMgr::onAssetLoadComplete(const LLUUID& asset_uuid,
+ LLAssetType::EType type,
+ void* user_data, S32 status, LLExtStat ext_status)
+{
+ LLGestureMgr& self = LLGestureMgr::instance();
+
+ // Complete the asset loading process depending on the type and
+ // remove the asset id from pending downloads list.
+ switch(type)
+ {
+ case LLAssetType::AT_ANIMATION:
+ {
+ LLKeyframeMotion::onLoadComplete(asset_uuid, type, user_data, status, ext_status);
+
+ self.mLoadingAssets.erase(asset_uuid);
+
+ break;
+ }
+ case LLAssetType::AT_SOUND:
+ {
+ LLAudioEngine::assetCallback(asset_uuid, type, user_data, status, ext_status);
+
+ self.mLoadingAssets.erase(asset_uuid);
+
+ break;
+ }
+ default:
+ {
+ LL_WARNS() << "Unexpected asset type: " << type << LL_ENDL;
+
+ // We don't want to return from this callback without
+ // an animation or sound callback being fired
+ // and *user_data handled to avoid memory leaks.
+ llassert(type == LLAssetType::AT_ANIMATION || type == LLAssetType::AT_SOUND);
+ }
+ }
+}
+
+// static
+bool LLGestureMgr::hasLoadingAssets(LLMultiGesture* gesture)
+{
+ LLGestureMgr& self = LLGestureMgr::instance();
+
+ for (std::vector<LLGestureStep*>::iterator steps_it = gesture->mSteps.begin();
+ steps_it != gesture->mSteps.end();
+ ++steps_it)
+ {
+ LLGestureStep* step = *steps_it;
+ switch(step->getType())
+ {
+ case STEP_ANIMATION:
+ {
+ LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step;
+ const LLUUID& anim_id = anim_step->mAnimAssetID;
+
+ if (!(anim_id.isNull()
+ || anim_step->mFlags & ANIM_FLAG_STOP
+ || self.mLoadingAssets.find(anim_id) == self.mLoadingAssets.end()))
+ {
+ return true;
+ }
+ break;
+ }
+ case STEP_SOUND:
+ {
+ LLGestureStepSound* sound_step = (LLGestureStepSound*)step;
+ const LLUUID& sound_id = sound_step->mSoundAssetID;
+
+ if (!(sound_id.isNull()
+ || self.mLoadingAssets.find(sound_id) == self.mLoadingAssets.end()))
+ {
+ return true;
+ }
+ break;
+ }
+ case STEP_CHAT:
+ case STEP_WAIT:
+ case STEP_EOF:
+ {
+ break;
+ }
+ default:
+ {
+ LL_WARNS() << "Unknown gesture step type: " << step->getType() << LL_ENDL;
+ }
+ }
+ }
+
+ return false;
+}
+
+void LLGestureMgr::stopGesture(LLMultiGesture* gesture)
+{
+ if (!gesture) return;
+
+ // Stop any animations that this gesture is currently playing
+ std::set<LLUUID>::const_iterator set_it;
+ for (set_it = gesture->mRequestedAnimIDs.begin(); set_it != gesture->mRequestedAnimIDs.end(); ++set_it)
+ {
+ const LLUUID& anim_id = *set_it;
+ gAgent.sendAnimationRequest(anim_id, ANIM_REQUEST_STOP);
+ }
+ for (set_it = gesture->mPlayingAnimIDs.begin(); set_it != gesture->mPlayingAnimIDs.end(); ++set_it)
+ {
+ const LLUUID& anim_id = *set_it;
+ gAgent.sendAnimationRequest(anim_id, ANIM_REQUEST_STOP);
+ }
+
+ std::vector<LLMultiGesture*>::iterator it;
+ it = std::find(mPlaying.begin(), mPlaying.end(), gesture);
+ while (it != mPlaying.end())
+ {
+ mPlaying.erase(it);
+ it = std::find(mPlaying.begin(), mPlaying.end(), gesture);
+ }
+
+ gesture->reset();
+
+ if (gesture->mDoneCallback)
+ {
+ gesture->mDoneCallback(gesture, gesture->mCallbackData);
+
+ // callback might have deleted gesture, can't
+ // rely on this pointer any more
+ gesture = NULL;
+ }
+
+ notifyObservers();
+}
+
+
+void LLGestureMgr::stopGesture(const LLUUID& item_id)
+{
+ const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
+
+ item_map_t::iterator it = mActive.find(base_item_id);
+ if (it == mActive.end()) return;
+
+ LLMultiGesture* gesture = (*it).second;
+ if (!gesture) return;
+
+ stopGesture(gesture);
+}
+
+
+void LLGestureMgr::addObserver(LLGestureManagerObserver* observer)
+{
+ mObservers.push_back(observer);
+}
+
+void LLGestureMgr::removeObserver(LLGestureManagerObserver* observer)
+{
+ std::vector<LLGestureManagerObserver*>::iterator it;
+ it = std::find(mObservers.begin(), mObservers.end(), observer);
+ if (it != mObservers.end())
+ {
+ mObservers.erase(it);
+ }
+}
+
+// Call this method when it's time to update everyone on a new state.
+// Copy the list because an observer could respond by removing itself
+// from the list.
+void LLGestureMgr::notifyObservers()
+{
+ LL_DEBUGS() << "LLGestureMgr::notifyObservers" << LL_ENDL;
+
+ for(std::vector<LLGestureManagerObserver*>::iterator iter = mObservers.begin();
+ iter != mObservers.end();
+ ++iter)
+ {
+ LLGestureManagerObserver* observer = (*iter);
+ observer->changed();
+ }
+}
+
+bool LLGestureMgr::matchPrefix(const std::string& in_str, std::string* out_str)
+{
+ S32 in_len = in_str.length();
+
+ //return whole trigger, if received text equals to it
+ item_map_t::iterator it;
+ for (it = mActive.begin(); it != mActive.end(); ++it)
+ {
+ LLMultiGesture* gesture = (*it).second;
+ if (gesture)
+ {
+ const std::string& trigger = gesture->getTrigger();
+ if (!LLStringUtil::compareInsensitive(in_str, trigger))
+ {
+ *out_str = trigger;
+ return true;
+ }
+ }
+ }
+
+ //return common chars, if more than one trigger matches the prefix
+ std::string rest_of_match = "";
+ std::string buf = "";
+ for (it = mActive.begin(); it != mActive.end(); ++it)
+ {
+ LLMultiGesture* gesture = (*it).second;
+ if (gesture)
+ {
+ const std::string& trigger = gesture->getTrigger();
+
+ if (in_len > (S32)trigger.length())
+ {
+ // too short, bail out
+ continue;
+ }
+
+ std::string trigger_trunc = trigger;
+ LLStringUtil::truncate(trigger_trunc, in_len);
+ if (!LLStringUtil::compareInsensitive(in_str, trigger_trunc))
+ {
+ if (rest_of_match.compare("") == 0)
+ {
+ rest_of_match = trigger.substr(in_str.size());
+ }
+ std::string cur_rest_of_match = trigger.substr(in_str.size());
+ buf = "";
+ S32 i=0;
+
+ while (i<rest_of_match.length() && i<cur_rest_of_match.length())
+ {
+ if (rest_of_match[i]==cur_rest_of_match[i])
+ {
+ buf.push_back(rest_of_match[i]);
+ }
+ else
+ {
+ if(i==0)
+ {
+ rest_of_match = "";
+ }
+ break;
+ }
+ i++;
+ }
+ if (rest_of_match.compare("") == 0)
+ {
+ return true;
+ }
+ if (buf.compare("") != 0)
+ {
+ rest_of_match = buf;
+ }
+
+ }
+ }
+ }
+
+ if (rest_of_match.compare("") != 0)
+ {
+ *out_str = in_str+rest_of_match;
+ return true;
+ }
+
+ return false;
+}
+
+
+void LLGestureMgr::getItemIDs(uuid_vec_t* ids)
+{
+ item_map_t::const_iterator it;
+ for (it = mActive.begin(); it != mActive.end(); ++it)
+ {
+ ids->push_back(it->first);
+ }
+}
+
+void LLGestureMgr::done()
+{
+ bool notify = false;
+ for(item_map_t::iterator it = mActive.begin(); it != mActive.end(); ++it)
+ {
+ if(it->second && it->second->mName.empty())
+ {
+ LLViewerInventoryItem* item = gInventory.getItem(it->first);
+ if(item)
+ {
+ it->second->mName = item->getName();
+ notify = true;
+ }
+ }
+ }
+ if(notify)
+ {
+ notifyObservers();
+ }
+}
+
+