SL-704 - made avatar hierarchy more consistent by adding lluiavatar for viewer-local avs used in upload previews
parent
0d2d6b42ba
commit
4b439ff968
|
|
@ -227,7 +227,7 @@ protected:
|
|||
** RENDERING
|
||||
**/
|
||||
public:
|
||||
BOOL mIsDummy; // for special views
|
||||
BOOL mIsDummy; // for special views and animated object controllers; local to viewer
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Morph masks
|
||||
|
|
|
|||
|
|
@ -601,6 +601,7 @@ set(viewer_SOURCE_FILES
|
|||
lltransientfloatermgr.cpp
|
||||
lltranslate.cpp
|
||||
lltwitterconnect.cpp
|
||||
lluiavatar.cpp
|
||||
lluilistener.cpp
|
||||
lluploaddialog.cpp
|
||||
llurl.cpp
|
||||
|
|
@ -1216,6 +1217,7 @@ set(viewer_HEADER_FILES
|
|||
lltranslate.h
|
||||
lltwitterconnect.h
|
||||
lluiconstants.h
|
||||
lluiavatar.h
|
||||
lluilistener.h
|
||||
lluploaddialog.h
|
||||
lluploadfloaterobservers.h
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ LLControlAvatar::LLControlAvatar(const LLUUID& id, const LLPCode pcode, LLViewer
|
|||
mGlobalScale(1.0f),
|
||||
mMarkedForDeath(false)
|
||||
{
|
||||
mIsDummy = TRUE;
|
||||
mIsControlAvatar = true;
|
||||
mEnableDefaultMotions = false;
|
||||
}
|
||||
|
|
@ -56,6 +57,14 @@ void LLControlAvatar::initInstance()
|
|||
// avatar mesh content since it's not used. For now we just clean some
|
||||
// things up after the fact in releaseMeshData().
|
||||
LLVOAvatar::initInstance();
|
||||
|
||||
// AXON mSpecialRenderMode here is probably wrong, need to review.
|
||||
mSpecialRenderMode = 1;
|
||||
|
||||
createDrawable(&gPipeline);
|
||||
updateJointLODs();
|
||||
updateGeometry(mDrawable);
|
||||
hideSkirt();
|
||||
}
|
||||
|
||||
void LLControlAvatar::matchVolumeTransform()
|
||||
|
|
@ -185,13 +194,6 @@ LLControlAvatar *LLControlAvatar::createControlAvatar(LLVOVolume *obj)
|
|||
LLControlAvatar *cav = (LLControlAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), CO_FLAG_CONTROL_AVATAR);
|
||||
|
||||
cav->mRootVolp = obj;
|
||||
|
||||
cav->createDrawable(&gPipeline);
|
||||
cav->mIsDummy = TRUE;
|
||||
cav->mSpecialRenderMode = 1;
|
||||
cav->updateJointLODs();
|
||||
cav->updateGeometry(cav->mDrawable);
|
||||
cav->hideSkirt();
|
||||
|
||||
// Sync up position/rotation with object
|
||||
cav->matchVolumeTransform();
|
||||
|
|
|
|||
|
|
@ -467,7 +467,7 @@ void LLDrawPoolAvatar::renderShadow(S32 pass)
|
|||
}
|
||||
LLVOAvatar *avatarp = (LLVOAvatar *)facep->getDrawable()->getVObj().get();
|
||||
|
||||
if (avatarp->isDead() || (avatarp->mIsDummy && !avatarp->isControlAvatar()) || avatarp->mDrawable.isNull())
|
||||
if (avatarp->isDead() || avatarp->isUIAvatar() || avatarp->mDrawable.isNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1042,14 +1042,8 @@ LLPreviewAnimation::LLPreviewAnimation(S32 width, S32 height) : LLViewerDynamicT
|
|||
mCameraPitch = 0.f;
|
||||
mCameraZoom = 1.f;
|
||||
|
||||
mDummyAvatar = (LLVOAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion());
|
||||
mDummyAvatar->createDrawable(&gPipeline);
|
||||
mDummyAvatar->mIsDummy = TRUE;
|
||||
mDummyAvatar = (LLVOAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), LLViewerObject::CO_FLAG_UI_AVATAR);
|
||||
mDummyAvatar->mSpecialRenderMode = 1;
|
||||
mDummyAvatar->setPositionAgent(LLVector3::zero);
|
||||
mDummyAvatar->slamPosition();
|
||||
mDummyAvatar->updateJointLODs();
|
||||
mDummyAvatar->updateGeometry(mDummyAvatar->mDrawable);
|
||||
mDummyAvatar->startMotion(ANIM_AGENT_STAND, BASE_ANIM_TIME_OFFSET);
|
||||
mDummyAvatar->hideSkirt();
|
||||
|
||||
|
|
|
|||
|
|
@ -566,15 +566,8 @@ LLImagePreviewAvatar::LLImagePreviewAvatar(S32 width, S32 height) : LLViewerDyna
|
|||
mCameraPitch = 0.f;
|
||||
mCameraZoom = 1.f;
|
||||
|
||||
mDummyAvatar = (LLVOAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion());
|
||||
mDummyAvatar->createDrawable(&gPipeline);
|
||||
mDummyAvatar->mIsDummy = TRUE;
|
||||
mDummyAvatar = (LLVOAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), LLViewerObject::CO_FLAG_UI_AVATAR);
|
||||
mDummyAvatar->mSpecialRenderMode = 2;
|
||||
mDummyAvatar->setPositionAgent(LLVector3::zero);
|
||||
mDummyAvatar->slamPosition();
|
||||
mDummyAvatar->updateJointLODs();
|
||||
mDummyAvatar->updateGeometry(mDummyAvatar->mDrawable);
|
||||
// gPipeline.markVisible(mDummyAvatar->mDrawable, *LLViewerCamera::getInstance());
|
||||
|
||||
mTextureName = 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3451,16 +3451,11 @@ void LLModelPreview::update()
|
|||
//-----------------------------------------------------------------------------
|
||||
void LLModelPreview::createPreviewAvatar( void )
|
||||
{
|
||||
mPreviewAvatar = (LLVOAvatar*)gObjectList.createObjectViewer( LL_PCODE_LEGACY_AVATAR, gAgent.getRegion() );
|
||||
mPreviewAvatar = (LLVOAvatar*)gObjectList.createObjectViewer( LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), LLViewerObject::CO_FLAG_UI_AVATAR );
|
||||
if ( mPreviewAvatar )
|
||||
{
|
||||
mPreviewAvatar->createDrawable( &gPipeline );
|
||||
mPreviewAvatar->mIsDummy = TRUE;
|
||||
mPreviewAvatar->mSpecialRenderMode = 1;
|
||||
mPreviewAvatar->setPositionAgent( LLVector3::zero );
|
||||
mPreviewAvatar->slamPosition();
|
||||
mPreviewAvatar->updateJointLODs();
|
||||
mPreviewAvatar->updateGeometry( mPreviewAvatar->mDrawable );
|
||||
mPreviewAvatar->startMotion( ANIM_AGENT_STAND );
|
||||
mPreviewAvatar->hideSkirt();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* @file lluiavatar.cpp
|
||||
* @brief Implementation for special dummy avatar used in some UI views
|
||||
*
|
||||
* $LicenseInfo:firstyear=2017&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2017, 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 "lluiavatar.h"
|
||||
#include "llagent.h" // Get state values from here
|
||||
#include "llviewerobjectlist.h"
|
||||
#include "pipeline.h"
|
||||
#include "llanimationstates.h"
|
||||
#include "llviewercontrol.h"
|
||||
#include "llmeshrepository.h"
|
||||
#include "llviewerregion.h"
|
||||
|
||||
LLUIAvatar::LLUIAvatar(const LLUUID& id, const LLPCode pcode, LLViewerRegion* regionp) :
|
||||
LLVOAvatar(id, pcode, regionp)
|
||||
{
|
||||
mIsDummy = TRUE;
|
||||
mIsUIAvatar = true;
|
||||
}
|
||||
|
||||
// virtual
|
||||
LLUIAvatar::~LLUIAvatar()
|
||||
{
|
||||
}
|
||||
|
||||
// virtual
|
||||
void LLUIAvatar::initInstance()
|
||||
{
|
||||
LLVOAvatar::initInstance();
|
||||
|
||||
createDrawable( &gPipeline );
|
||||
setPositionAgent(LLVector3::zero);
|
||||
slamPosition();
|
||||
updateJointLODs();
|
||||
updateGeometry(mDrawable);
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* @file lluiavatar.h
|
||||
* @brief Special dummy avatar used in some UI views
|
||||
*
|
||||
* $LicenseInfo:firstyear=2017&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2017, 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$
|
||||
*/
|
||||
|
||||
#ifndef LL_UIAVATAR_H
|
||||
#define LL_UIAVATAR_H
|
||||
|
||||
#include "llvoavatar.h"
|
||||
#include "llvovolume.h"
|
||||
|
||||
class LLUIAvatar:
|
||||
public LLVOAvatar
|
||||
{
|
||||
LOG_CLASS(LLUIAvatar);
|
||||
|
||||
public:
|
||||
LLUIAvatar(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp);
|
||||
virtual void initInstance(); // Called after construction to initialize the class.
|
||||
virtual ~LLUIAvatar();
|
||||
};
|
||||
|
||||
#endif //LL_CONTROLAVATAR_H
|
||||
|
|
@ -70,6 +70,7 @@
|
|||
#include "llselectmgr.h"
|
||||
#include "llrendersphere.h"
|
||||
#include "lltooldraganddrop.h"
|
||||
#include "lluiavatar.h"
|
||||
#include "llviewercamera.h"
|
||||
#include "llviewertexturelist.h"
|
||||
#include "llviewerinventory.h"
|
||||
|
|
@ -169,10 +170,16 @@ LLViewerObject *LLViewerObject::createObject(const LLUUID &id, const LLPCode pco
|
|||
}
|
||||
else if (flags & CO_FLAG_CONTROL_AVATAR)
|
||||
{
|
||||
LLControlAvatar *avatar = new LLControlAvatar(id, pcode, regionp);
|
||||
avatar->initInstance();
|
||||
res = avatar;
|
||||
LLControlAvatar *control_avatar = new LLControlAvatar(id, pcode, regionp);
|
||||
control_avatar->initInstance();
|
||||
res = control_avatar;
|
||||
}
|
||||
else if (flags & CO_FLAG_UI_AVATAR)
|
||||
{
|
||||
LLUIAvatar *ui_avatar = new LLUIAvatar(id, pcode, regionp);
|
||||
ui_avatar->initInstance();
|
||||
res = ui_avatar;
|
||||
}
|
||||
else
|
||||
{
|
||||
LLVOAvatar *avatar = new LLVOAvatar(id, pcode, regionp);
|
||||
|
|
|
|||
|
|
@ -706,6 +706,10 @@ public:
|
|||
|
||||
virtual bool isAnimatedObject() const;
|
||||
|
||||
// Flags for createObject
|
||||
static const S32 CO_FLAG_CONTROL_AVATAR = 1 << 0;
|
||||
static const S32 CO_FLAG_UI_AVATAR = 1 << 1;
|
||||
|
||||
protected:
|
||||
LLPointer<LLControlAvatar> mControlAvatar;
|
||||
|
||||
|
|
@ -719,9 +723,6 @@ protected:
|
|||
// updateInventory.
|
||||
void doUpdateInventory(LLPointer<LLViewerInventoryItem>& item, U8 key, bool is_new);
|
||||
|
||||
// Flags for createObject
|
||||
static const S32 CO_FLAG_CONTROL_AVATAR = 1 << 1;
|
||||
|
||||
static LLViewerObject *createObject(const LLUUID &id, LLPCode pcode, LLViewerRegion *regionp, S32 flags = 0);
|
||||
|
||||
BOOL setData(const U8 *datap, const U32 data_size);
|
||||
|
|
|
|||
|
|
@ -670,6 +670,7 @@ LLVOAvatar::LLVOAvatar(const LLUUID& id,
|
|||
mCachedMuteListUpdateTime(0),
|
||||
mCachedInMuteList(false),
|
||||
mIsControlAvatar(false),
|
||||
mIsUIAvatar(false),
|
||||
mEnableDefaultMotions(true)
|
||||
{
|
||||
LL_DEBUGS("AvatarRender") << "LLVOAvatar Constructor (0x" << this << ") id:" << mID << LL_ENDL;
|
||||
|
|
@ -1975,8 +1976,7 @@ void LLVOAvatar::resetSkeleton(bool reset_animations)
|
|||
//-----------------------------------------------------------------------------
|
||||
void LLVOAvatar::releaseMeshData()
|
||||
{
|
||||
if (sInstances.size() < AVATAR_RELEASE_THRESHOLD ||
|
||||
(mIsDummy && !isControlAvatar()))
|
||||
if (sInstances.size() < AVATAR_RELEASE_THRESHOLD || isUIAvatar())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -3685,7 +3685,7 @@ void LLVOAvatar::computeUpdatePeriod()
|
|||
if (mDrawable.notNull()
|
||||
&& isVisible()
|
||||
&& (!isSelf() || visually_muted)
|
||||
&& (!mIsDummy || isControlAvatar())
|
||||
&& !isUIAvatar()
|
||||
&& sUseImpostors
|
||||
&& !mNeedsAnimUpdate
|
||||
&& !sFreezeCounter)
|
||||
|
|
@ -3901,8 +3901,7 @@ void LLVOAvatar::updateOrientation(LLAgent& agent, F32 speed, F32 delta_time)
|
|||
// ------------------------------------------------------------------------
|
||||
void LLVOAvatar::updateTimeStep()
|
||||
{
|
||||
bool is_pure_dummy = mIsDummy && !isControlAvatar();
|
||||
if (!isSelf() && !is_pure_dummy) // ie, non-self avatars, and animated objects will be affected.
|
||||
if (!isSelf() && !isUIAvatar()) // ie, non-self avatars, and animated objects will be affected.
|
||||
{
|
||||
// Note that sInstances counts animated objects and
|
||||
// standard avatars in the same bucket. Is this desirable?
|
||||
|
|
@ -4059,10 +4058,10 @@ void LLVOAvatar::updateRootPositionAndRotation(LLAgent& agent, F32 speed, bool w
|
|||
// LLControlAvatar and mIsDummy is true. Avatar is a purely
|
||||
// viewer-side entity with no representation on the simulator.
|
||||
//
|
||||
// 4) Avatar is a "dummy" avatar used in some areas of the UI, such as
|
||||
// when previewing uploaded animations. Class is LLVOAvatar, and
|
||||
// mIsDummy is true. Avatar is purely viewer-side with no
|
||||
// representation on the simulator.
|
||||
// 4) Avatar is a UI avatar used in some areas of the UI, such as when
|
||||
// previewing uploaded animations. Class is LLUIAvatar, and mIsDummy
|
||||
// is true. Avatar is purely viewer-side with no representation on the
|
||||
// simulator.
|
||||
//
|
||||
//------------------------------------------------------------------------
|
||||
BOOL LLVOAvatar::updateCharacter(LLAgent &agent)
|
||||
|
|
@ -4115,7 +4114,6 @@ BOOL LLVOAvatar::updateCharacter(LLAgent &agent)
|
|||
|
||||
//--------------------------------------------------------------------
|
||||
// change animation time quanta based on avatar render load
|
||||
// AXON how should control avs be handled here?
|
||||
//--------------------------------------------------------------------
|
||||
// SL-763 the time step quantization does not currently work.
|
||||
//updateTimeStep();
|
||||
|
|
@ -4623,12 +4621,11 @@ U32 LLVOAvatar::renderSkinned()
|
|||
}
|
||||
|
||||
BOOL first_pass = TRUE;
|
||||
bool is_pure_dummy = mIsDummy && !isControlAvatar();
|
||||
if (!LLDrawPoolAvatar::sSkipOpaque)
|
||||
{
|
||||
if (!isSelf() || gAgent.needsRenderHead() || LLPipeline::sShadowRender)
|
||||
{
|
||||
if (isTextureVisible(TEX_HEAD_BAKED) || is_pure_dummy)
|
||||
if (isTextureVisible(TEX_HEAD_BAKED) || isUIAvatar())
|
||||
{
|
||||
LLViewerJoint* head_mesh = getViewerJoint(MESH_ID_HEAD);
|
||||
if (head_mesh)
|
||||
|
|
@ -4638,7 +4635,7 @@ U32 LLVOAvatar::renderSkinned()
|
|||
first_pass = FALSE;
|
||||
}
|
||||
}
|
||||
if (isTextureVisible(TEX_UPPER_BAKED) || is_pure_dummy)
|
||||
if (isTextureVisible(TEX_UPPER_BAKED) || isUIAvatar())
|
||||
{
|
||||
LLViewerJoint* upper_mesh = getViewerJoint(MESH_ID_UPPER_BODY);
|
||||
if (upper_mesh)
|
||||
|
|
@ -4648,7 +4645,7 @@ U32 LLVOAvatar::renderSkinned()
|
|||
first_pass = FALSE;
|
||||
}
|
||||
|
||||
if (isTextureVisible(TEX_LOWER_BAKED) || is_pure_dummy)
|
||||
if (isTextureVisible(TEX_LOWER_BAKED) || isUIAvatar())
|
||||
{
|
||||
LLViewerJoint* lower_mesh = getViewerJoint(MESH_ID_LOWER_BODY);
|
||||
if (lower_mesh)
|
||||
|
|
@ -4677,7 +4674,7 @@ U32 LLVOAvatar::renderSkinned()
|
|||
U32 LLVOAvatar::renderTransparent(BOOL first_pass)
|
||||
{
|
||||
U32 num_indices = 0;
|
||||
if( isWearingWearableType( LLWearableType::WT_SKIRT ) && (mIsDummy || isTextureVisible(TEX_SKIRT_BAKED)) )
|
||||
if( isWearingWearableType( LLWearableType::WT_SKIRT ) && (isUIAvatar() || isTextureVisible(TEX_SKIRT_BAKED)) )
|
||||
{
|
||||
gGL.setAlphaRejectSettings(LLRender::CF_GREATER, 0.25f);
|
||||
LLViewerJoint* skirt_mesh = getViewerJoint(MESH_ID_SKIRT);
|
||||
|
|
@ -4753,9 +4750,7 @@ U32 LLVOAvatar::renderRigid()
|
|||
gGL.setAlphaRejectSettings(LLRender::CF_GREATER, 0.5f);
|
||||
}
|
||||
|
||||
bool is_pure_dummy = mIsDummy && !isControlAvatar();
|
||||
|
||||
if (isTextureVisible(TEX_EYES_BAKED) || is_pure_dummy)
|
||||
if (isTextureVisible(TEX_EYES_BAKED) || isUIAvatar())
|
||||
{
|
||||
LLViewerJoint* eyeball_left = getViewerJoint(MESH_ID_EYEBALL_LEFT);
|
||||
LLViewerJoint* eyeball_right = getViewerJoint(MESH_ID_EYEBALL_RIGHT);
|
||||
|
|
@ -6165,8 +6160,7 @@ F32 LLVOAvatar::getTimeDilation()
|
|||
//-----------------------------------------------------------------------------
|
||||
F32 LLVOAvatar::getPixelArea() const
|
||||
{
|
||||
// AXON UPDATE FOR CONTROL AVATARS
|
||||
if (mIsDummy && !isControlAvatar())
|
||||
if (isUIAvatar())
|
||||
{
|
||||
return 100000.f;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ public:
|
|||
virtual bool isSelf() const { return false; } // True if this avatar is for this viewer's agent
|
||||
|
||||
virtual bool isControlAvatar() const { return mIsControlAvatar; } // True if this avatar is a control av (no associated user)
|
||||
virtual bool isUIAvatar() const { return mIsUIAvatar; } // True if this avatar is a supplemental av used in some UI views (no associated user)
|
||||
|
||||
private: //aligned members
|
||||
LL_ALIGN_16(LLVector4a mImpostorExtents[2]);
|
||||
|
|
@ -461,6 +462,7 @@ public:
|
|||
//--------------------------------------------------------------------
|
||||
public:
|
||||
bool mIsControlAvatar;
|
||||
bool mIsUIAvatar;
|
||||
bool mEnableDefaultMotions;
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -3419,18 +3419,6 @@ bool LLVOVolume::isAnimatedObject() const
|
|||
LLVOVolume *root_vol = (LLVOVolume*)getRootEdit();
|
||||
bool root_is_animated_flag = root_vol->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG;
|
||||
return root_is_animated_flag;
|
||||
#if 0
|
||||
if (root_is_animated_flag)
|
||||
{
|
||||
bool root_can_be_animated = root_vol->canBeAnimatedObject();
|
||||
bool this_can_be_animated = (root_vol == this) ? root_can_be_animated : canBeAnimatedObject();
|
||||
if (this_can_be_animated && root_can_be_animated)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Called any time parenting changes for a volume. Update flags and
|
||||
|
|
|
|||
Loading…
Reference in New Issue