/** * @file aoengine.cpp * @brief The core Animation Overrider engine * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2011, Zi Ree @ Second Life * * 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 */ #include "llviewerprecompiledheaders.h" #include "roles_constants.h" #include "aoengine.h" #include "aoset.h" #include "llagent.h" #include "llagentcamera.h" #include "llanimationstates.h" #include "llassetstorage.h" #include "llcommon.h" #include "llinventoryfunctions.h" // for ROOT_FIRESTORM_FOLDER #include "llinventorymodel.h" #include "llinventoryobserver.h" #include "llnotificationsutil.h" #include "llstring.h" #include "llvfs.h" #include "llviewercontrol.h" #include "llviewerinventory.h" //#define ROOT_FIRESTORM_FOLDER "#Firestorm" //moved to llinventoryfunctions.h #define ROOT_AO_FOLDER "#AO" #include const F32 INVENTORY_POLLING_INTERVAL=5.0f; AOEngine::AOEngine() : LLSingleton(), mCurrentSet(0), mDefaultSet(0), mEnabled(FALSE), mInMouselook(FALSE), mUnderWater(FALSE), mImportSet(0), mImportCategory(LLUUID::null), mAOFolder(LLUUID::null), mLastMotion(ANIM_AGENT_STAND), mLastOverriddenMotion(ANIM_AGENT_STAND) { } AOEngine::~AOEngine() { clear(false); } void AOEngine::init() { enable(mEnabled); } // static void AOEngine::onLoginComplete() { AOEngine::instance().init(); } void AOEngine::clear( bool aFromTimer ) { mOldSets.insert( mOldSets.end(), mSets.begin(), mSets.end() ); mSets.clear(); mCurrentSet=0; // FIRE-3801; We cannot delete any AOSet object if we're called from a timer tick. AOSet is derived from LLEventTimer and destruction will // fail in ~LLInstanceTracker when a destructor runs during iteration. if( !aFromTimer ) { std::for_each( mOldSets.begin(), mOldSets.end(), DeletePointer( ) ); mOldSets.clear(); std::for_each( mOldImportSets.begin(), mOldImportSets.end(), DeletePointer() ); mOldImportSets.clear(); } } void AOEngine::stopAllStandVariants() { lldebugs << "stopping all STAND variants." << llendl; gAgent.sendAnimationRequest(ANIM_AGENT_STAND_1,ANIM_REQUEST_STOP); gAgent.sendAnimationRequest(ANIM_AGENT_STAND_2,ANIM_REQUEST_STOP); gAgent.sendAnimationRequest(ANIM_AGENT_STAND_3,ANIM_REQUEST_STOP); gAgent.sendAnimationRequest(ANIM_AGENT_STAND_4,ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_STAND_1); gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_STAND_2); gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_STAND_3); gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_STAND_4); } void AOEngine::stopAllSitVariants() { lldebugs << "stopping all SIT variants." << llendl; gAgent.sendAnimationRequest(ANIM_AGENT_SIT_FEMALE,ANIM_REQUEST_STOP); gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC,ANIM_REQUEST_STOP); gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GROUND,ANIM_REQUEST_STOP); gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GROUND_CONSTRAINED,ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_SIT_FEMALE); gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_SIT_GENERIC); gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_SIT_GROUND); gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_SIT_GROUND_CONSTRAINED); } void AOEngine::setLastMotion(LLUUID motion) { if(motion!=ANIM_AGENT_TYPE) mLastMotion=motion; } void AOEngine::setLastOverriddenMotion(LLUUID motion) { if(motion!=ANIM_AGENT_TYPE) mLastOverriddenMotion=motion; } BOOL AOEngine::foreignAnimations() { for(LLVOAvatar::AnimSourceIterator sourceIterator=gAgentAvatarp->mAnimationSources.begin(); sourceIterator!=gAgentAvatarp->mAnimationSources.end();sourceIterator++) { if(sourceIterator->first!=gAgent.getID()) return TRUE; } return FALSE; } LLUUID AOEngine::mapSwimming(LLUUID motion) { S32 stateNum; if(motion==ANIM_AGENT_HOVER) stateNum=AOSet::Floating; else if(motion==ANIM_AGENT_FLY) stateNum=AOSet::SwimmingForward; else if(motion==ANIM_AGENT_HOVER_UP) stateNum=AOSet::SwimmingUp; else if(motion==ANIM_AGENT_HOVER_DOWN) stateNum=AOSet::SwimmingDown; else return LLUUID::null; AOSet::AOState* state=mCurrentSet->getState(stateNum); return mCurrentSet->getAnimationForState(state); } void AOEngine::checkBelowWater(BOOL yes) { if(mUnderWater==yes) return; // only restart underwater/above water motion if the overridden motion is the one currently playing if(mLastMotion!=mLastOverriddenMotion) return; gAgent.sendAnimationRequest(override(mLastOverriddenMotion,FALSE),ANIM_REQUEST_STOP); mUnderWater=yes; gAgent.sendAnimationRequest(override(mLastOverriddenMotion,TRUE),ANIM_REQUEST_START); } void AOEngine::enable(BOOL yes) { lldebugs << "using " << mLastMotion << " enable " << yes << llendl; mEnabled=yes; if(!mCurrentSet) { lldebugs << "enable(" << yes << ") without animation set loaded." << llendl; return; } AOSet::AOState* state=mCurrentSet->getStateByRemapID(mLastMotion); if(mEnabled) { if(state && !state->mAnimations.empty()) { lldebugs << "Enabling animation state " << state->mName << llendl; gAgent.sendAnimationRequest(mLastOverriddenMotion,ANIM_REQUEST_STOP); LLUUID animation=override(mLastMotion,TRUE); if(animation.isNull()) return; if(mLastMotion==ANIM_AGENT_STAND) { stopAllStandVariants(); } else if(mLastMotion==ANIM_AGENT_WALK) { lldebugs << "Last motion was a WALK, stopping all variants." << llendl; gAgent.sendAnimationRequest(ANIM_AGENT_WALK_NEW,ANIM_REQUEST_STOP); gAgent.sendAnimationRequest(ANIM_AGENT_FEMALE_WALK,ANIM_REQUEST_STOP); gAgent.sendAnimationRequest(ANIM_AGENT_FEMALE_WALK_NEW,ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_WALK_NEW); gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_FEMALE_WALK); gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_FEMALE_WALK_NEW); } else if(mLastMotion==ANIM_AGENT_RUN) { lldebugs << "Last motion was a RUN, stopping all variants." << llendl; gAgent.sendAnimationRequest(ANIM_AGENT_RUN_NEW,ANIM_REQUEST_STOP); gAgent.sendAnimationRequest(ANIM_AGENT_FEMALE_RUN_NEW,ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_RUN_NEW); gAgentAvatarp->LLCharacter::stopMotion(ANIM_AGENT_FEMALE_RUN_NEW); } else if(mLastMotion==ANIM_AGENT_SIT) { stopAllSitVariants(); gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC,ANIM_REQUEST_START); } else llwarns << "Unhandled last motion id " << mLastMotion << llendl; gAgent.sendAnimationRequest(animation,ANIM_REQUEST_START); } } else { gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC,ANIM_REQUEST_STOP); // stop all overriders, catch leftovers for(S32 index=0;indexgetState(index); if(state) { LLUUID animation=state->mCurrentAnimationID; if(animation.notNull()) { lldebugs << "Stopping leftover animation from state " << state->mName << llendl; gAgent.sendAnimationRequest(animation,ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(animation); state->mCurrentAnimationID.setNull(); } } else lldebugs << "state "<< index <<" returned NULL." << llendl; } if(!foreignAnimations()) gAgent.sendAnimationRequest(mLastMotion,ANIM_REQUEST_START); mCurrentSet->stopTimer(); } } void AOEngine::setStateCycleTimer(const AOSet::AOState* state) { F32 timeout=state->mCycleTime; lldebugs << "Setting cycle timeout for state " << state->mName << " of " << timeout << llendl; if(timeout>0.0f) mCurrentSet->startTimer(timeout); } const LLUUID AOEngine::override(const LLUUID pMotion,BOOL start) { LLUUID animation; LLUUID motion=pMotion; if(!mEnabled) { if(start && mCurrentSet) { const AOSet::AOState* state=mCurrentSet->getStateByRemapID(motion); if(state) { setLastMotion(motion); lldebugs << "(disabled AO) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << llendl; if(!state->mAnimations.empty()) { setLastOverriddenMotion(motion); lldebugs << "(disabled AO) setting last overridden motion id to " << gAnimLibrary.animationName(mLastOverriddenMotion) << llendl; } } } return animation; } if(mSets.empty()) { lldebugs << "No sets loaded. Skipping overrider." << llendl; return animation; } if(!mCurrentSet) { lldebugs << "No current AO set chosen. Skipping overrider." << llendl; return animation; } // we don't distinguish between these two if(motion==ANIM_AGENT_SIT_GROUND_CONSTRAINED) motion=ANIM_AGENT_SIT_GROUND; AOSet::AOState* state=mCurrentSet->getStateByRemapID(motion); if(!state) { lldebugs << "No current AO state for motion " << motion << " (" << gAnimLibrary.animationName(motion) << ")." << llendl; if(!gAnimLibrary.animStateToString(motion) && !start) { state=mCurrentSet->getStateByRemapID(mLastOverriddenMotion); if(state && state->mCurrentAnimationID==motion) { lldebugs << "Stop requested for current overridden animation UUID " << motion << " - Skipping." << llendl; } else { lldebugs << "Stop requested for unknown UUID " << motion << " - Stopping it just in case." << llendl; gAgent.sendAnimationRequest(motion,ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(motion); } } return animation; } mCurrentSet->stopTimer(); if(start) { // Disable start stands in Mouselook if(mCurrentSet->getMouselookDisable() && motion==ANIM_AGENT_STAND && mInMouselook) { setLastMotion(motion); lldebugs << "(enabled AO, mouselook stand stopped) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << llendl; return animation; } // Do not start override sits if not selected if(!mCurrentSet->getSitOverride() && motion==ANIM_AGENT_SIT) { setLastMotion(motion); lldebugs << "(enabled AO, sit override stopped) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << llendl; return animation; } setLastMotion(motion); lldebugs << "(enabled AO) setting last motion id to " << gAnimLibrary.animationName(mLastMotion) << llendl; if(!state->mAnimations.empty()) { setLastOverriddenMotion(motion); lldebugs << "(enabled AO) setting last overridden motion id to " << gAnimLibrary.animationName(mLastOverriddenMotion) << llendl; } // do not remember typing as set-wide motion if(motion!=ANIM_AGENT_TYPE) mCurrentSet->setMotion(motion); mUnderWater=gAgentAvatarp->mBelowWater; if(mUnderWater) animation=mapSwimming(motion); if(animation.isNull()) animation=mCurrentSet->getAnimationForState(state); if(state->mCurrentAnimationID.notNull()) { lldebugs << "Previous animation for state " << gAnimLibrary.animationName(motion) << " was not stopped, but we were asked to start a new one. Killing old animation." << llendl; gAgent.sendAnimationRequest(state->mCurrentAnimationID,ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(state->mCurrentAnimationID); } state->mCurrentAnimationID=animation; lldebugs << "overriding " << gAnimLibrary.animationName(motion) << " with " << animation << " in state " << state->mName << " of set " << mCurrentSet->getName() << " (" << mCurrentSet << ")" << llendl; setStateCycleTimer(state); if(motion==ANIM_AGENT_SIT) { // Use ANIM_AGENT_SIT_GENERIC, so we don't create an overrider loop with ANIM_AGENT_SIT // while still having a base sitting pose to cover up cycle points gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC,ANIM_REQUEST_START); if(mCurrentSet->getSmart()) mSitCancelTimer.oneShot(); } // special treatment for "transient animations" because the viewer needs the Linden animation to know the agent's state else if(motion==ANIM_AGENT_SIT_GROUND || motion==ANIM_AGENT_PRE_JUMP || motion==ANIM_AGENT_STANDUP || motion==ANIM_AGENT_LAND || motion==ANIM_AGENT_MEDIUM_LAND) { gAgent.sendAnimationRequest(animation,ANIM_REQUEST_START); return LLUUID::null; } } else { animation=state->mCurrentAnimationID; state->mCurrentAnimationID.setNull(); // for typing animaiton, just return the stored animation, reset the state timer, and don't memorize anything else if(motion==ANIM_AGENT_TYPE) { AOSet::AOState* previousState=mCurrentSet->getStateByRemapID(mLastMotion); if(previousState) setStateCycleTimer(previousState); return animation; } if(motion!=mCurrentSet->getMotion()) { llwarns << "trying to stop-override motion " << gAnimLibrary.animationName(motion) << " but the current set has motion " << gAnimLibrary.animationName(mCurrentSet->getMotion()) << llendl; return animation; } mCurrentSet->setMotion(LLUUID::null); // again, special treatment for "transient" animations to make sure our own animation gets stopped properly if( motion==ANIM_AGENT_SIT_GROUND || motion==ANIM_AGENT_PRE_JUMP || motion==ANIM_AGENT_STANDUP || motion==ANIM_AGENT_LAND || motion==ANIM_AGENT_MEDIUM_LAND) { gAgent.sendAnimationRequest(animation,ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(animation); setStateCycleTimer(state); return LLUUID::null; } // stop the underlying Linden Lab motion, in case it's still running. // frequently happens with sits, so we keep it only for those currently. if(mLastMotion==ANIM_AGENT_SIT) stopAllSitVariants(); lldebugs << "stopping cycle timer for motion " << gAnimLibrary.animationName(motion) << " using animation " << animation << " in state " << state->mName << llendl; } return animation; } void AOEngine::checkSitCancel() { if(foreignAnimations()) { LLUUID animation=mCurrentSet->getStateByRemapID(ANIM_AGENT_SIT)->mCurrentAnimationID; if(animation.notNull()) { lldebugs << "Stopping sit animation due to foreign animations running" << llendl; gAgent.sendAnimationRequest(animation,ANIM_REQUEST_STOP); // remove cycle point cover-up gAgent.sendAnimationRequest(ANIM_AGENT_SIT_GENERIC,ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(animation); mSitCancelTimer.stop(); // stop cycle tiemr mCurrentSet->stopTimer(); } } } void AOEngine::cycleTimeout(const AOSet* set) { if(!mEnabled) return; if(set!=mCurrentSet) { llwarns << "cycleTimeout for set " << set->getName() << " but current set is " << mCurrentSet->getName() << llendl; return; } cycle(CycleAny); } void AOEngine::cycle(eCycleMode cycleMode) { if(!mCurrentSet) { lldebugs << "cycle without set." << llendl; return; } LLUUID motion=mCurrentSet->getMotion(); // assume stand if no motion is registered, happens after login when the avatar hasn't moved at all yet if(motion.isNull()) motion=ANIM_AGENT_STAND; // do not cycle if we're sitting and sit-override is off else if(motion==ANIM_AGENT_SIT && !mCurrentSet->getSitOverride()) return; // do not cycle if we're standing and mouselook stand override is disabled while being in mouselook else if(motion==ANIM_AGENT_STAND && mCurrentSet->getMouselookDisable() && mInMouselook) return; AOSet::AOState* state=mCurrentSet->getStateByRemapID(motion); if(!state) { lldebugs << "cycle without state." << llendl; return; } if(!state->mAnimations.size()) { lldebugs << "cycle without animations in state." << llendl; return; } // make sure we disable cycling only for timed cycle, so manual cycling still works, even with cycling switched off if(!state->mCycle && cycleMode==CycleAny) { lldebugs << "cycle timeout, but state is set to not cycling." << llendl; return; } LLUUID oldAnimation=state->mCurrentAnimationID; LLUUID animation; if(cycleMode==CycleAny) { animation=mCurrentSet->getAnimationForState(state); } else { if(cycleMode==CyclePrevious) { if(state->mCurrentAnimation==0) state->mCurrentAnimation=state->mAnimations.size()-1; else state->mCurrentAnimation--; } else if(cycleMode==CycleNext) { state->mCurrentAnimation++; if(state->mCurrentAnimation==state->mAnimations.size()) state->mCurrentAnimation=0; } animation=state->mAnimations[state->mCurrentAnimation].mAssetUUID; } // don't do anything if the animation didn't change if(animation==oldAnimation) return; state->mCurrentAnimationID=animation; if(!animation.isNull()) { lldebugs << "requesting animation start for motion " << gAnimLibrary.animationName(motion) << ": " << animation << llendl; gAgent.sendAnimationRequest(animation,ANIM_REQUEST_START); } else lldebugs << "overrider came back with NULL animation for motion " << gAnimLibrary.animationName(motion) << "." << llendl; if(!oldAnimation.isNull()) { lldebugs << "Cycling state " << state->mName << " - stopping animation " << oldAnimation << llendl; gAgent.sendAnimationRequest(oldAnimation,ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(oldAnimation); } } void AOEngine::updateSortOrder(AOSet::AOState* state) { for(U32 index=0;indexmAnimations.size();index++) { U32 sortOrder=state->mAnimations[index].mSortOrder; if(sortOrder!=index) { std::ostringstream numStr(""); numStr << index; lldebugs << "sort order is " << sortOrder << " but index is " << index << ", setting sort order description: " << numStr.str() << llendl; state->mAnimations[index].mSortOrder=index; LLViewerInventoryItem* item=gInventory.getItem(state->mAnimations[index].mInventoryUUID); if(!item) { llwarns << "NULL inventory item found while trying to copy " << state->mAnimations[index].mInventoryUUID << llendl; continue; } LLPointer newItem= new LLViewerInventoryItem(item); newItem->setDescription(numStr.str()); newItem->setComplete(TRUE); newItem->updateServer(FALSE); gInventory.updateItem(newItem); } } } LLUUID AOEngine::addSet(const std::string name,BOOL reload) { if(mAOFolder.isNull()) { llwarns << ROOT_AO_FOLDER << " folder not there yet. Requesting recreation." << llendl; tick(); return LLUUID::null; } BOOL wasProtected=gSavedPerAccountSettings.getBOOL("ProtectAOFolders"); gSavedPerAccountSettings.setBOOL("ProtectAOFolders",FALSE); lldebugs << "adding set folder " << name << llendl; LLUUID newUUID=gInventory.createNewCategory(mAOFolder,LLFolderType::FT_NONE,name); gSavedPerAccountSettings.setBOOL("ProtectAOFolders",wasProtected); if(reload) mTimerCollection.enableReloadTimer(TRUE); return newUUID; } BOOL AOEngine::createAnimationLink(const AOSet* set,AOSet::AOState* state,const LLInventoryItem* item) { lldebugs << "Asset ID " << item->getAssetUUID() << " inventory id " << item->getUUID() << " category id " << state->mInventoryUUID << llendl; if(state->mInventoryUUID.isNull()) { lldebugs << "no " << state->mName << " folder yet. Creating ..." << llendl; gInventory.createNewCategory(set->getInventoryUUID(),LLFolderType::FT_NONE,state->mName); lldebugs << "looking for folder to get UUID ..." << llendl; LLUUID newStateFolderUUID; LLInventoryModel::item_array_t* items; LLInventoryModel::cat_array_t* cats; gInventory.getDirectDescendentsOf(set->getInventoryUUID(),cats,items); for(S32 index=0;indexcount();index++) { if(cats->get(index)->getName().compare(state->mName)==0) { lldebugs << "UUID found!" << llendl; newStateFolderUUID=cats->get(index)->getUUID(); state->mInventoryUUID=newStateFolderUUID; break; } } } if(state->mInventoryUUID.isNull()) { lldebugs << "state inventory UUID not found, failing." << llendl; return FALSE; } link_inventory_item( gAgent.getID(), item->getUUID(), state->mInventoryUUID, item->getName(), item->getDescription(), LLAssetType::AT_LINK, NULL); return TRUE; } BOOL AOEngine::addAnimation(const AOSet* set,AOSet::AOState* state,const LLInventoryItem* item,BOOL reload) { AOSet::AOAnimation anim; anim.mAssetUUID=item->getAssetUUID(); anim.mInventoryUUID=item->getUUID(); anim.mName=item->getName(); anim.mSortOrder=state->mAnimations.size()+1; state->mAnimations.push_back(anim); BOOL wasProtected=gSavedPerAccountSettings.getBOOL("ProtectAOFolders"); gSavedPerAccountSettings.setBOOL("ProtectAOFolders",FALSE); createAnimationLink(set,state,item); gSavedPerAccountSettings.setBOOL("ProtectAOFolders",wasProtected); if(reload) mTimerCollection.enableReloadTimer(TRUE); return TRUE; } // needs a three-step process, since purge of categories only seems to work from trash void AOEngine::purgeFolder(LLUUID uuid) { // unprotect it BOOL wasProtected=gSavedPerAccountSettings.getBOOL("ProtectAOFolders"); gSavedPerAccountSettings.setBOOL("ProtectAOFolders",FALSE); // trash it remove_category(&gInventory,uuid); // clean it gInventory.purgeDescendentsOf(uuid); gInventory.notifyObservers(); // purge it gInventory.purgeObject(uuid); gInventory.notifyObservers(); // protect it gSavedPerAccountSettings.setBOOL("ProtectAOFolders",wasProtected); } BOOL AOEngine::removeSet(AOSet* set) { purgeFolder(set->getInventoryUUID()); mTimerCollection.enableReloadTimer(TRUE); return TRUE; } BOOL AOEngine::removeAnimation(const AOSet* set,AOSet::AOState* state,S32 index) { S32 numOfAnimations=state->mAnimations.size(); if(numOfAnimations==0) return FALSE; lldebugs << __LINE__ << " purging: " << state->mAnimations[index].mInventoryUUID << llendl; gInventory.purgeObject(state->mAnimations[index].mInventoryUUID); // item->getUUID()); gInventory.notifyObservers(); state->mAnimations.erase(state->mAnimations.begin()+index); if(state->mAnimations.size()==0) { lldebugs << "purging folder " << state->mName << " from inventory because it's empty." << llendl; LLInventoryModel::item_array_t* items; LLInventoryModel::cat_array_t* cats; gInventory.getDirectDescendentsOf(set->getInventoryUUID(),cats,items); for(S32 index=0;indexcount();index++) { std::vector params; LLStringUtil::getTokens(cats->get(index)->getName(),params,":"); std::string stateName=params[0]; if(state->mName.compare(stateName)==0) { lldebugs << "folder found: " << cats->get(index)->getName() << " purging uuid " << cats->get(index)->getUUID() << llendl; purgeFolder(cats->get(index)->getUUID()); state->mInventoryUUID.setNull(); break; } } } else updateSortOrder(state); return TRUE; } BOOL AOEngine::swapWithPrevious(AOSet::AOState* state,S32 index) { S32 numOfAnimations=state->mAnimations.size(); if(numOfAnimations<2 || index==0) return FALSE; AOSet::AOAnimation tmpAnim=state->mAnimations[index]; state->mAnimations.erase(state->mAnimations.begin()+index); state->mAnimations.insert(state->mAnimations.begin()+index-1,tmpAnim); updateSortOrder(state); return TRUE; } BOOL AOEngine::swapWithNext(AOSet::AOState* state,S32 index) { S32 numOfAnimations=state->mAnimations.size(); if(numOfAnimations<2 || index==(numOfAnimations-1)) return FALSE; AOSet::AOAnimation tmpAnim=state->mAnimations[index]; state->mAnimations.erase(state->mAnimations.begin()+index); state->mAnimations.insert(state->mAnimations.begin()+index+1,tmpAnim); updateSortOrder(state); return TRUE; } void AOEngine::reloadStateAnimations(AOSet::AOState* state) { LLInventoryModel::item_array_t* items; LLInventoryModel::cat_array_t* dummy; state->mAnimations.clear(); gInventory.getDirectDescendentsOf(state->mInventoryUUID,dummy,items); for(S32 num=0;numcount();num++) { lldebugs << "Found animation link " << items->get(num)->LLInventoryItem::getName() << " desc " << items->get(num)->LLInventoryItem::getDescription() << " asset " << items->get(num)->getAssetUUID() << llendl; AOSet::AOAnimation anim; anim.mAssetUUID=items->get(num)->getAssetUUID(); LLViewerInventoryItem* linkedItem=items->get(num)->getLinkedItem(); if(linkedItem==0) { llwarns << "linked item for link " << items->get(num)->LLInventoryItem::getName() << " not found (broken link). Skipping." << llendl; continue; } anim.mName=linkedItem->LLInventoryItem::getName(); anim.mInventoryUUID=items->get(num)->getUUID(); S32 sortOrder; if(!LLStringUtil::convertToS32(items->get(num)->LLInventoryItem::getDescription(),sortOrder)) sortOrder=-1; anim.mSortOrder=sortOrder; lldebugs << "current sort order is " << sortOrder << llendl; if(sortOrder==-1) { llwarns << "sort order was unknown so append to the end of the list" << llendl; state->mAnimations.push_back(anim); } else { BOOL inserted=FALSE; for(U32 index=0;indexmAnimations.size();index++) { if(state->mAnimations[index].mSortOrder>sortOrder) { lldebugs << "inserting at index " << index << llendl; state->mAnimations.insert(state->mAnimations.begin()+index,anim); inserted=TRUE; break; } } if(!inserted) { lldebugs << "not inserted yet, appending to the list instead" << llendl; state->mAnimations.push_back(anim); } } lldebugs << "Animation count now: " << state->mAnimations.size() << llendl; } updateSortOrder(state); } void AOEngine::update() { if(mAOFolder.isNull()) return; LLInventoryModel::cat_array_t* categories; LLInventoryModel::item_array_t* items; BOOL allComplete=TRUE; mTimerCollection.enableSettingsTimer(FALSE); gInventory.getDirectDescendentsOf(mAOFolder,categories,items); for(S32 index=0;indexcount();index++) { LLViewerInventoryCategory* currentCategory=categories->get(index); const std::string& setFolderName=currentCategory->getName(); std::vector params; LLStringUtil::getTokens(setFolderName,params,":"); AOSet* newSet=getSetByName(params[0]); if(newSet==0) { lldebugs << "Adding set " << setFolderName << " to AO." << llendl; newSet=new AOSet(currentCategory->getUUID()); newSet->setName(params[0]); mSets.push_back(newSet); } else { if(newSet->getComplete()) { lldebugs << "Set " << params[0] << " already complete. Skipping." << llendl; continue; } lldebugs << "Updating set " << setFolderName << " in AO." << llendl; } allComplete=FALSE; for(U32 num=1;numsetSitOverride(TRUE); else if(params[num]=="SM") newSet->setSmart(TRUE); else if(params[num]=="DM") newSet->setMouselookDisable(TRUE); else if(params[num]=="**") { mDefaultSet=newSet; mCurrentSet=newSet; } else llwarns << "Unknown AO set option " << params[num] << llendl; } if(gInventory.isCategoryComplete(currentCategory->getUUID())) { lldebugs << "Set " << params[0] << " is complete, reading states ..." << llendl; LLInventoryModel::cat_array_t* stateCategories; gInventory.getDirectDescendentsOf(currentCategory->getUUID(),stateCategories,items); newSet->setComplete(TRUE); for(S32 index=0;indexcount();index++) { std::vector params; LLStringUtil::getTokens(stateCategories->get(index)->getName(),params,":"); std::string stateName=params[0]; AOSet::AOState* state=newSet->getStateByName(stateName); if(state==NULL) { llwarns << "Unknown state " << stateName << ". Skipping." << llendl; continue; } lldebugs << "Reading state " << stateName << llendl; state->mInventoryUUID=stateCategories->get(index)->getUUID(); for(U32 num=1;nummCycle=TRUE; lldebugs << "Cycle on" << llendl; } else if(params[num]=="RN") { state->mRandom=TRUE; lldebugs << "Random on" << llendl; } else if(params[num].substr(0,2)=="CT") { LLStringUtil::convertToS32(params[num].substr(2,params[num].size()-2),state->mCycleTime); lldebugs << "Cycle Time specified:" << state->mCycleTime << llendl; } else llwarns << "Unknown AO set option " << params[num] << llendl; } if(!gInventory.isCategoryComplete(state->mInventoryUUID)) { lldebugs << "State category " << stateName << " is incomplete, fetching descendents" << llendl; gInventory.fetchDescendentsOf(state->mInventoryUUID); allComplete=FALSE; newSet->setComplete(FALSE); continue; } reloadStateAnimations(state); } } else { lldebugs << "Set " << params[0] << " is incomplete, fetching descendents" << llendl; gInventory.fetchDescendentsOf(currentCategory->getUUID()); } } if(allComplete) { mEnabled=gSavedPerAccountSettings.getBOOL("UseAO"); if(!mCurrentSet && !mSets.empty()) { lldebugs << "No default set defined, choosing the first in the list." << llendl; selectSet(mSets[0]); } mTimerCollection.enableInventoryTimer(FALSE); mTimerCollection.enableSettingsTimer(TRUE); llwarns << "sending update signal" << llendl; mUpdatedSignal(); enable(mEnabled); } } void AOEngine::reload( bool aFromTimer ) { BOOL wasEnabled=mEnabled; mTimerCollection.enableReloadTimer(FALSE); if(wasEnabled) enable(FALSE); gAgent.stopCurrentAnimations(); mLastOverriddenMotion=ANIM_AGENT_STAND; clear( aFromTimer ); mAOFolder.setNull(); mTimerCollection.enableInventoryTimer(TRUE); tick(); if(wasEnabled) enable(TRUE); } AOSet* AOEngine::getSetByName(const std::string name) { AOSet* found=0; for(U32 index=0;indexgetName().compare(name)==0) { found=mSets[index]; break; } } return found; } const std::string AOEngine::getCurrentSetName() const { if(mCurrentSet) return mCurrentSet->getName(); return std::string(); } const AOSet* AOEngine::getDefaultSet() const { return mDefaultSet; } void AOEngine::selectSet(AOSet* set) { if(mEnabled && mCurrentSet) { AOSet::AOState* state=mCurrentSet->getStateByRemapID(mLastOverriddenMotion); if(state) { gAgent.sendAnimationRequest(state->mCurrentAnimationID,ANIM_REQUEST_STOP); state->mCurrentAnimationID.setNull(); mCurrentSet->stopTimer(); } } mCurrentSet=set; if(mEnabled) { lldebugs << "enabling with motion " << gAnimLibrary.animationName(mLastMotion) << llendl; gAgent.sendAnimationRequest(override(mLastMotion,TRUE),ANIM_REQUEST_START); } } AOSet* AOEngine::selectSetByName(const std::string name) { AOSet* set=getSetByName(name); if(set) { selectSet(set); return set; } llwarns << "Could not find AO set " << name << llendl; return NULL; } const std::vector AOEngine::getSetList() const { return mSets; } void AOEngine::saveSet(const AOSet* set) { if(!set) return; std::string setParams=set->getName(); if(set->getSitOverride()) setParams+=":SO"; if(set->getSmart()) setParams+=":SM"; if(set->getMouselookDisable()) setParams+=":DM"; if(set==mDefaultSet) setParams+=":**"; /* // This works fine, but LL seems to have added a few helper functions in llinventoryfunctions.h // so let's make use of them. This code is just for reference LLViewerInventoryCategory* cat=gInventory.getCategory(set->getInventoryUUID()); llwarns << cat << llendl; cat->rename(setParams); cat->updateServer(FALSE); gInventory.addChangedMask(LLInventoryObserver::LABEL, cat->getUUID()); gInventory.notifyObservers(); */ BOOL wasProtected=gSavedPerAccountSettings.getBOOL("ProtectAOFolders"); gSavedPerAccountSettings.setBOOL("ProtectAOFolders",FALSE); rename_category(&gInventory,set->getInventoryUUID(),setParams); gSavedPerAccountSettings.setBOOL("ProtectAOFolders",wasProtected); llwarns << "sending update signal" << llendl; mUpdatedSignal(); } BOOL AOEngine::renameSet(AOSet* set,const std::string name) { if(name.empty() || name.find(":")!=std::string::npos) return FALSE; set->setName(name); set->setDirty(TRUE); return TRUE; } void AOEngine::saveState(const AOSet::AOState* state) { std::string stateParams=state->mName; F32 time=state->mCycleTime; if(time>0.0) { std::ostringstream timeStr; timeStr << ":CT" << state->mCycleTime; stateParams+=timeStr.str(); } if(state->mCycle) stateParams+=":CY"; if(state->mRandom) stateParams+=":RN"; BOOL wasProtected=gSavedPerAccountSettings.getBOOL("ProtectAOFolders"); gSavedPerAccountSettings.setBOOL("ProtectAOFolders",FALSE); rename_category(&gInventory,state->mInventoryUUID,stateParams); gSavedPerAccountSettings.setBOOL("ProtectAOFolders",wasProtected); } void AOEngine::saveSettings() { for(U32 index=0;indexgetDirty()) { saveSet(set); llwarns << "dirty set saved " << set->getName() << llendl; set->setDirty(FALSE); } for(S32 stateIndex=0;stateIndexgetState(stateIndex); if(state->mDirty) { saveState(state); llwarns << "dirty state saved " << state->mName << llendl; state->mDirty=FALSE; } } } } void AOEngine::inMouselook(BOOL yes) { if(mInMouselook==yes) return; mInMouselook=yes; if(!mCurrentSet) return; if(!mCurrentSet->getMouselookDisable()) return; if(!mEnabled) return; if(mLastMotion!=ANIM_AGENT_STAND) return; if(yes) { AOSet::AOState* state=mCurrentSet->getState(AOSet::Standing); if(!state) return; LLUUID animation=state->mCurrentAnimationID; if(!animation.isNull()) { gAgent.sendAnimationRequest(animation,ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(animation); state->mCurrentAnimationID.setNull(); lldebugs << " stopped animation " << animation << " in state " << state->mName << llendl; } gAgent.sendAnimationRequest(ANIM_AGENT_STAND,ANIM_REQUEST_START); } else { stopAllStandVariants(); gAgent.sendAnimationRequest(override(ANIM_AGENT_STAND,TRUE),ANIM_REQUEST_START); } } void AOEngine::setDefaultSet(AOSet* set) { mDefaultSet=set; for(U32 index=0;indexsetDirty(TRUE); } void AOEngine::setOverrideSits(AOSet* set,BOOL yes) { set->setSitOverride(yes); set->setDirty(TRUE); if(mCurrentSet!=set) return; if(mLastMotion!=ANIM_AGENT_SIT) return; if(yes) { stopAllSitVariants(); gAgent.sendAnimationRequest(override(ANIM_AGENT_SIT,TRUE),ANIM_REQUEST_START); } else { AOSet::AOState* state=mCurrentSet->getState(AOSet::Sitting); if(!state) return; LLUUID animation=state->mCurrentAnimationID; if(!animation.isNull()) { gAgent.sendAnimationRequest(animation,ANIM_REQUEST_STOP); gAgentAvatarp->LLCharacter::stopMotion(animation); state->mCurrentAnimationID.setNull(); } gAgent.sendAnimationRequest(ANIM_AGENT_SIT,ANIM_REQUEST_START); } } void AOEngine::setSmart(AOSet* set,BOOL yes) { set->setSmart(yes); set->setDirty(TRUE); } void AOEngine::setDisableStands(AOSet* set,BOOL yes) { set->setMouselookDisable(yes); set->setDirty(TRUE); if(mCurrentSet!=set) return; // make sure an update happens if needed mInMouselook=!gAgentCamera.cameraMouselook(); inMouselook(!mInMouselook); } void AOEngine::setCycle(AOSet::AOState* state,BOOL yes) { state->mCycle=yes; state->mDirty=TRUE; } void AOEngine::setRandomize(AOSet::AOState* state,BOOL yes) { state->mRandom=yes; state->mDirty=TRUE; } void AOEngine::setCycleTime(AOSet::AOState* state,F32 time) { state->mCycleTime=time; state->mDirty=TRUE; } void AOEngine::tick() { const LLUUID categoryID=gInventory.findCategoryByName(ROOT_FIRESTORM_FOLDER); if(categoryID.isNull()) { llwarns << "no " << ROOT_FIRESTORM_FOLDER << " folder yet. Creating ..." << llendl; gInventory.createNewCategory(gInventory.getRootFolderID(),LLFolderType::FT_NONE,ROOT_FIRESTORM_FOLDER); mAOFolder.setNull(); } else { LLInventoryModel::cat_array_t* categories; LLInventoryModel::item_array_t* items; gInventory.getDirectDescendentsOf(categoryID,categories,items); lldebugs << "cat " << categories->count() << " items " << items->count() << llendl; for(S32 index=0;indexcount();index++) { const std::string& catName=categories->get(index)->getName(); if(catName.compare(ROOT_AO_FOLDER)==0) { mAOFolder=categories->get(index)->getUUID(); break; } } if(mAOFolder.isNull()) { llwarns << "no " << ROOT_AO_FOLDER << " folder yet. Creating ..." << llendl; gInventory.createNewCategory(categoryID,LLFolderType::FT_NONE,ROOT_AO_FOLDER); } else { llwarns << "AO basic folder structure intact." << llendl; update(); } } } BOOL AOEngine::importNotecard(const LLInventoryItem* item) { if(item) { llwarns << "importing AO notecard: " << item->getName() << llendl; if(getSetByName(item->getName())) { LLNotificationsUtil::add("AOImportSetAlreadyExists", LLSD()); return FALSE; } if(!gAgent.allowOperation(PERM_COPY,item->getPermissions(),GP_OBJECT_MANIPULATE) && !gAgent.isGodlike()) { LLNotificationsUtil::add("AOImportPermissionDenied", LLSD()); return FALSE; } if(item->getAssetUUID().notNull()) { mImportSet=new AOSet(item->getParentUUID()); if(!mImportSet) { LLNotificationsUtil::add("AOImportCreateSetFailed", LLSD()); return FALSE; } mImportSet->setName(item->getName()); LLUUID* newUUID=new LLUUID(item->getAssetUUID()); const LLHost sourceSim=LLHost::invalid; gAssetStorage->getInvItemAsset ( sourceSim, gAgent.getID(), gAgent.getSessionID(), item->getPermissions().getOwner(), LLUUID::null, item->getUUID(), item->getAssetUUID(), item->getType(), &onNotecardLoadComplete, (void*) newUUID, TRUE ); return TRUE; } } return FALSE; } // static void AOEngine::onNotecardLoadComplete( LLVFS* vfs,const LLUUID& assetUUID,LLAssetType::EType type, void* userdata,S32 status,LLExtStat extStatus) { if(status!=LL_ERR_NOERR) { // AOImportDownloadFailed LLNotificationsUtil::add("AOImportDownloadFailed", LLSD()); // NULL tells the importer to cancel all operations and free the import set memory AOEngine::instance().parseNotecard(NULL); return; } lldebugs << "Downloading import notecard complete." << llendl; S32 notecardSize=vfs->getSize(assetUUID,type); char* buffer=new char[notecardSize]; vfs->getData(assetUUID,type,(U8*) buffer,0,notecardSize); AOEngine::instance().parseNotecard(buffer); } void AOEngine::parseNotecard(const char* buffer) { lldebugs << "parsing import notecard" << llendl; BOOL isValid=FALSE; if(!buffer) { llwarns << "buffer==NULL - aborting import" << llendl; // NOTE: cleanup is always the same, needs streamlining delete mImportSet; mImportSet=0; mUpdatedSignal(); return; } std::string text(buffer); delete buffer; std::vector lines; LLStringUtil::getTokens(text,lines,"\n"); S32 found=-1; for(U32 index=0;indexgetInventoryUUID()); if(!importCategory) { LLNotificationsUtil::add("AOImportNoFolder", LLSD()); delete mImportSet; mImportSet=0; mUpdatedSignal(); return; } std::map animationMap; LLInventoryModel::cat_array_t* dummy; LLInventoryModel::item_array_t* items; gInventory.getDirectDescendentsOf(mImportSet->getInventoryUUID(),dummy,items); for(U32 index=0;indexsize();index++) { animationMap[items->get(index)->getName()]=items->get(index)->getUUID(); lldebugs << "animation " << items->get(index)->getName() << " has inventory UUID " << animationMap[items->get(index)->getName()] << llendl; } // [ State ]Anim1|Anim2|Anim3 for(U32 index=found+1;index FIRE-3801; skip comments to reduce spam to local chat. continue; if(line.find("[")!=0) { LLSD args; args["LINE"]=(S32) index; LLNotificationsUtil::add("AOImportNoStatePrefix",args); continue; } U32 endTag=line.find("]"); if(endTag==std::string::npos) { LLSD args; args["LINE"]=(S32) index; LLNotificationsUtil::add("AOImportNoValidDelimiter",args); continue; } std::string stateName=line.substr(1,endTag-1); LLStringUtil::trim(stateName); AOSet::AOState* newState=mImportSet->getStateByName(stateName); if(!newState) { LLSD args; args["NAME"]=stateName; LLNotificationsUtil::add("AOImportStateNameNotFound",args); continue; } std::string animationLine=line.substr(endTag+1); std::vector animationList; LLStringUtil::getTokens(animationLine,animationList,"|,"); for(U32 animIndex=0;animIndexmAnimations.push_back(animation); isValid=TRUE; } } if(!isValid) { LLNotificationsUtil::add("AOImportInvalid",LLSD()); // NOTE: cleanup is always the same, needs streamlining delete mImportSet; mImportSet=0; mUpdatedSignal(); return; } mTimerCollection.enableImportTimer(TRUE); mImportRetryCount=0; processImport(false); } void AOEngine::processImport( bool aFromTimer ) { if(mImportCategory.isNull()) { mImportCategory=addSet(mImportSet->getName(),FALSE); if(mImportCategory.isNull()) { mImportRetryCount++; if(mImportRetryCount==5) { // NOTE: cleanup is the same as at the end of this function. Needs streamlining. mTimerCollection.enableImportTimer(FALSE); delete mImportSet; mImportSet=0; mImportCategory.setNull(); mUpdatedSignal(); LLSD args; args["NAME"]=mImportSet->getName(); LLNotificationsUtil::add("AOImportAbortCreateSet",args); } else { LLSD args; args["NAME"]=mImportSet->getName(); LLNotificationsUtil::add("AOImportRetryCreateSet",args); } return; } mImportSet->setInventoryUUID(mImportCategory); } BOOL allComplete=TRUE; for(S32 index=0;indexgetState(index); if(state->mAnimations.size()) { allComplete=FALSE; lldebugs << "state " << state->mName << " still has animations to link." << llendl; for(S32 animationIndex=state->mAnimations.size()-1;animationIndex>=0;animationIndex--) { lldebugs << "linking animation " << state->mAnimations[animationIndex].mName << llendl; if(createAnimationLink(mImportSet,state,gInventory.getItem(state->mAnimations[animationIndex].mInventoryUUID))) { lldebugs << "link success, size "<< state->mAnimations.size() << ", removing animation " << (*(state->mAnimations.begin()+animationIndex)).mName << " from import state" << llendl; state->mAnimations.erase(state->mAnimations.begin()+animationIndex); lldebugs << "deleted, size now: " << state->mAnimations.size() << llendl; } else { LLSD args; args["NAME"]=state->mAnimations[animationIndex].mName; LLNotificationsUtil::add("AOImportLinkFailed",args); } } } } if(allComplete) { mTimerCollection.enableImportTimer(FALSE); mOldImportSets.push_back( mImportSet ); // FIRE-3801; Cannot delete here, or LLInstanceTracker gets upset. Just remember and delete mOldImportSets once we can. mImportSet=0; mImportCategory.setNull(); reload( aFromTimer ); } } const LLUUID AOEngine::getAOFolder() { return mAOFolder; } // ---------------------------------------------------- AOSitCancelTimer::AOSitCancelTimer() : LLEventTimer(0.1f), mTickCount(0) { mEventTimer.stop(); } AOSitCancelTimer::~AOSitCancelTimer() { } void AOSitCancelTimer::oneShot() { mTickCount=0; mEventTimer.start(); } void AOSitCancelTimer::stop() { mEventTimer.stop(); } BOOL AOSitCancelTimer::tick() { mTickCount++; AOEngine::instance().checkSitCancel(); if(mTickCount==10) mEventTimer.stop(); return FALSE; } // ---------------------------------------------------- AOTimerCollection::AOTimerCollection() : LLEventTimer(INVENTORY_POLLING_INTERVAL), mInventoryTimer(TRUE), mSettingsTimer(FALSE), mReloadTimer(FALSE), mImportTimer(FALSE) { updateTimers(); } AOTimerCollection::~AOTimerCollection() { } BOOL AOTimerCollection::tick() { if(mInventoryTimer) { lldebugs << "Inventory timer tick()" << llendl; AOEngine::instance().tick(); } if(mSettingsTimer) { lldebugs << "Settings timer tick()" << llendl; AOEngine::instance().saveSettings(); } if(mReloadTimer) { lldebugs << "Reload timer tick()" << llendl; AOEngine::instance().reload(true); } if(mImportTimer) { lldebugs << "Import timer tick()" << llendl; AOEngine::instance().processImport(true); } // always return FALSE or the LLEventTimer will be deleted -> crash return FALSE; } void AOTimerCollection::enableInventoryTimer(BOOL yes) { mInventoryTimer=yes; updateTimers(); } void AOTimerCollection::enableSettingsTimer(BOOL yes) { mSettingsTimer=yes; updateTimers(); } void AOTimerCollection::enableReloadTimer(BOOL yes) { mReloadTimer=yes; updateTimers(); } void AOTimerCollection::enableImportTimer(BOOL yes) { mImportTimer=yes; updateTimers(); } void AOTimerCollection::updateTimers() { if(!mInventoryTimer && !mSettingsTimer && !mReloadTimer && !mImportTimer) { lldebugs << "no timer needed, stopping internal timer." << llendl; mEventTimer.stop(); } else { lldebugs << "timer needed, starting internal timer." << llendl; mEventTimer.start(); } }