Files
PanteraPolnocy 48c057cfe6 Heavy FS AO system modifications
NEEDS TESTING. Despite my best efforts and deeming this beta-ready it may still contain some bugs.

- Independent tracks per animation state - multiple of these can be played at the same time
- Animation groups - again, at the same time, but only as grouped with selected animations
- "Always played" quasi-state that can be used for bento ears, tails etc.
- Context menus on the animations list
- Tried to keep it backwards compatible

Credits given where credits are due:
Poked Zi Ree few times for opinions, suggestions and ideas - thanks!

Signed-off-by: PanteraPolnocy <panterapolnocy@gmail.com>
2026-06-11 18:43:42 +02:00

381 lines
10 KiB
C++

/**
* @file aoset.cpp
* @brief Implementation of an Animation Overrider set of animations
*
* $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
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "aoengine.h"
#include "aoset.h"
#include "llanimationstates.h"
#include "llinventorymodel.h"
AOSet::AOSet(const LLUUID& inventoryID)
: LLEventTimer(10000.0f),
mInventoryID(inventoryID),
mName("** New AO Set **"),
mSitOverride(false),
mSmart(false),
mMouselookStandDisable(false),
mComplete(false),
mDirty(false),
mCurrentMotion(LLUUID())
{
LL_DEBUGS("AOEngine") << "Creating new AO set: " << this << LL_ENDL;
// ZHAO names first, alternate names following, separated by | characters
// keep number and order in sync with the enum in the declaration
const std::string stateNames[AOSTATES_MAX]=
{
"Standing|Stand.1|Stand.2|Stand.3",
"Walking|Walk.N",
"Running",
"Sitting|Sit.N",
"Sitting On Ground|Sit.G",
"Crouching|Crouch",
"Crouch Walking|Walk.C",
"Landing|Land.N",
"Soft Landing",
"Standing Up|Stand.U",
"Falling",
"Flying Down|Hover.D",
"Flying Up|Hover.U",
"Flying|Fly.N",
"Flying Slow",
"Hovering|Hover.N",
"Jumping|Jump.N",
"Pre Jumping|Jump.P",
"Turning Right|Turn.R",
"Turning Left|Turn.L",
"Typing",
"Floating|Swim.H",
"Swimming Forward|Swim.N",
"Swimming Up|Swim.U",
"Swimming Down|Swim.D",
"Always"
};
// keep number and order in sync with the enum in the declaration
const LLUUID stateUUIDs[AOSTATES_MAX]=
{
ANIM_AGENT_STAND,
ANIM_AGENT_WALK,
ANIM_AGENT_RUN,
ANIM_AGENT_SIT,
ANIM_AGENT_SIT_GROUND_CONSTRAINED,
ANIM_AGENT_CROUCH,
ANIM_AGENT_CROUCHWALK,
ANIM_AGENT_LAND,
ANIM_AGENT_MEDIUM_LAND,
ANIM_AGENT_STANDUP,
ANIM_AGENT_FALLDOWN,
ANIM_AGENT_HOVER_DOWN,
ANIM_AGENT_HOVER_UP,
ANIM_AGENT_FLY,
ANIM_AGENT_FLYSLOW,
ANIM_AGENT_HOVER,
ANIM_AGENT_JUMP,
ANIM_AGENT_PRE_JUMP,
ANIM_AGENT_TURNRIGHT,
ANIM_AGENT_TURNLEFT,
ANIM_AGENT_TYPE,
ANIM_AGENT_HOVER, // needs special treatment
ANIM_AGENT_FLY, // needs special treatment
ANIM_AGENT_HOVER_UP, // needs special treatment
ANIM_AGENT_HOVER_DOWN, // needs special treatment
LLUUID::null // "Always" has no motion to override
};
for (S32 index = 0; index < AOSTATES_MAX; ++index)
{
std::vector<std::string> stateNameList;
LLStringUtil::getTokens(stateNames[index], stateNameList, "|");
mStates[index].mName = stateNameList[0]; // for quick reference
mStates[index].mAlternateNames = stateNameList; // to get all possible names, including mName
mStates[index].mNum = index;
mStates[index].mRemapID = stateUUIDs[index];
mStates[index].mInventoryUUID = LLUUID::null;
mStates[index].mCurrentAnimation = 0;
mStates[index].mCurrentAnimationIDs.clear();
mStates[index].mCycle = false;
mStates[index].mRandom = false;
mStates[index].mCycleTime = 0;
mStates[index].mDirty = false;
mStateNames.emplace_back(stateNameList[0]);
}
stopTimer();
}
AOSet::~AOSet()
{
LL_DEBUGS("AOEngine") << "Set deleted: " << this << LL_ENDL;
}
AOSet::AOState* AOSet::getState(S32 eName)
{
return &mStates[eName];
}
AOSet::AOState* AOSet::getStateByName(const std::string& name)
{
for (S32 index = 0; index < AOSTATES_MAX; ++index)
{
AOState* state = &mStates[index];
for (auto names = 0; names < state->mAlternateNames.size(); ++names)
{
if (state->mAlternateNames[names].compare(name) == 0)
{
return state;
}
}
}
return nullptr;
}
AOSet::AOState* AOSet::getStateByRemapID(const LLUUID& id)
{
// "Always" state has a null remap id and must never be picked up by motion mapping
if (id.isNull())
{
return nullptr;
}
LLUUID remap_id = id;
if (remap_id == ANIM_AGENT_SIT_GROUND)
{
remap_id = ANIM_AGENT_SIT_GROUND_CONSTRAINED;
}
for (S32 index = 0; index < AOSTATES_MAX; ++index)
{
if (mStates[index].mRemapID == remap_id)
{
return &mStates[index];
}
}
return nullptr;
}
LLUUID AOSet::resolveAssetUUID(AOAnimation& anim)
{
if (anim.mAssetUUID.isNull())
{
LL_DEBUGS("AOEngine") << "Asset UUID for animation " << anim.mName << " not yet known, try to find it." << LL_ENDL;
if (LLViewerInventoryItem* item = gInventory.getItem(anim.mInventoryUUID))
{
LL_DEBUGS("AOEngine") << "Found asset UUID for animation: " << item->getAssetUUID() << " - Updating AOAnimation.mAssetUUID" << LL_ENDL;
anim.mAssetUUID = item->getAssetUUID();
}
else
{
LL_DEBUGS("AOEngine") << "Inventory UUID " << anim.mInventoryUUID << " for animation " << anim.mName << " still returns no asset." << LL_ENDL;
}
}
return anim.mAssetUUID;
}
void AOSet::getAnimationsForState(AOState* state, uuid_vec_t& animations) const
{
if (!state)
{
return;
}
if (auto numOfSteps = state->mSteps.size(); numOfSteps > 0)
{
if (state->mCycle)
{
if (state->mRandom)
{
state->mCurrentAnimation = (U32)(ll_frand() * numOfSteps);
LL_DEBUGS("AOEngine") << "randomly chosen step " << state->mCurrentAnimation << " of " << numOfSteps << LL_ENDL;
}
else
{
state->mCurrentAnimation++;
if (state->mCurrentAnimation >= state->mSteps.size())
{
state->mCurrentAnimation = 0;
}
LL_DEBUGS("AOEngine") << "cycle step " << state->mCurrentAnimation << " of " << numOfSteps << LL_ENDL;
}
}
// steps can shrink through editing, keep the index in range
if (state->mCurrentAnimation >= state->mSteps.size())
{
state->mCurrentAnimation = 0;
}
for (AOAnimation& anim : state->mSteps[state->mCurrentAnimation].mMembers)
{
if (LLUUID assetId = resolveAssetUUID(anim); assetId.notNull())
{
animations.emplace_back(assetId);
}
}
}
else
{
LL_DEBUGS("AOEngine") << "animation state has no animations assigned" << LL_ENDL;
}
}
LLUUID AOSet::getAnimationForTrack(AOTrack& track) const
{
if (auto numOfAnimations = track.mAnimations.size(); numOfAnimations > 0)
{
if (track.mCycle)
{
if (track.mRandom)
{
track.mCurrentAnimation = (U32)(ll_frand() * numOfAnimations);
LL_DEBUGS("AOEngine") << "track " << track.mName << ": randomly chosen " << track.mCurrentAnimation << " of " << numOfAnimations << LL_ENDL;
}
else
{
track.mCurrentAnimation++;
if (track.mCurrentAnimation >= track.mAnimations.size())
{
track.mCurrentAnimation = 0;
}
LL_DEBUGS("AOEngine") << "track " << track.mName << ": cycle " << track.mCurrentAnimation << " of " << numOfAnimations << LL_ENDL;
}
}
if (track.mCurrentAnimation >= track.mAnimations.size())
{
track.mCurrentAnimation = 0;
}
return resolveAssetUUID(track.mAnimations[track.mCurrentAnimation]);
}
return LLUUID::null;
}
void AOSet::startTimer(F32 timeout)
{
mEventTimer.stop();
mPeriod = timeout;
mEventTimer.start();
LL_DEBUGS("AOEngine") << "Starting state timer for " << getName() << " at " << timeout << LL_ENDL;
}
void AOSet::stopTimer()
{
LL_DEBUGS("AOEngine") << "State timer for " << getName() << " stopped." << LL_ENDL;
mEventTimer.stop();
}
void AOSet::resetTimer()
{
LL_DEBUGS("AOEngine") << "Resetting timer for " << getName() << "." << LL_ENDL;
mEventTimer.reset();
}
bool AOSet::tick()
{
AOEngine::instance().cycleTimeout(this);
return false;
}
const LLUUID& AOSet::getInventoryUUID() const
{
return mInventoryID;
}
void AOSet::setInventoryUUID(const LLUUID& inventoryID)
{
mInventoryID = inventoryID;
}
const std::string& AOSet::getName() const
{
return mName;
}
void AOSet::setName(std::string_view name)
{
mName = name;
}
bool AOSet::getSitOverride() const
{
return mSitOverride;
}
void AOSet::setSitOverride(bool override_sit)
{
mSitOverride = override_sit;
}
bool AOSet::getSmart() const
{
return mSmart;
}
void AOSet::setSmart(bool smart)
{
mSmart = smart;
}
bool AOSet::getMouselookStandDisable() const
{
return mMouselookStandDisable;
}
void AOSet::setMouselookStandDisable(bool disable)
{
mMouselookStandDisable = disable;
}
bool AOSet::getComplete() const
{
return mComplete;
}
void AOSet::setComplete(bool complete)
{
mComplete = complete;
}
bool AOSet::getDirty() const
{
return mDirty;
}
void AOSet::setDirty(bool dirty)
{
mDirty = dirty;
}
void AOSet::setMotion(const LLUUID& motion)
{
mCurrentMotion = motion;
}
const LLUUID& AOSet::getMotion() const
{
return mCurrentMotion;
}