You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Mesh Editor: Minor refactoring to improve extensibility
- Mesh editor commands no longer use class default objects - Tweaked some internal functions to make them more general purpose #rb none [CL 3428862 by Mike Fricker in Dev-Geometry branch]
This commit is contained in:
@@ -10,6 +10,24 @@
|
||||
#define LOCTEXT_NAMESPACE "MeshEditorCommands"
|
||||
|
||||
|
||||
namespace MeshEditorCommands
|
||||
{
|
||||
const TArray<UMeshEditorCommand*>& Get()
|
||||
{
|
||||
static UMeshEditorCommandList* MeshEditorCommandList = nullptr;
|
||||
if( MeshEditorCommandList == nullptr )
|
||||
{
|
||||
MeshEditorCommandList = NewObject<UMeshEditorCommandList>();
|
||||
MeshEditorCommandList->AddToRoot();
|
||||
|
||||
MeshEditorCommandList->HarvestMeshEditorCommands();
|
||||
}
|
||||
|
||||
return MeshEditorCommandList->MeshEditorCommands;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FUIAction UMeshEditorInstantCommand::MakeUIAction( IMeshEditorModeUIContract& MeshEditorMode )
|
||||
{
|
||||
const EEditableMeshElementType ElementType = GetElementType();
|
||||
@@ -86,15 +104,11 @@ void FMeshEditorCommonCommands::RegisterCommands()
|
||||
UI_COMMAND(SetPolygonSelectionMode, "Set Polygon Selection Mode", "Sets the selection mode so that only polygons will be selected.", EUserInterfaceActionType::None, FInputChord(EKeys::Three));
|
||||
UI_COMMAND(SetAnySelectionMode, "Set Any Selection Mode", "Sets the selection mode so that any element type may be selected.", EUserInterfaceActionType::None, FInputChord(EKeys::Four));
|
||||
|
||||
for( TObjectIterator<UMeshEditorCommand> CommandCDOIter( RF_NoFlags ); CommandCDOIter; ++CommandCDOIter )
|
||||
for( UMeshEditorCommand* Command : MeshEditorCommands::Get() )
|
||||
{
|
||||
UMeshEditorCommand* CommandCDO = *CommandCDOIter;
|
||||
if( !( CommandCDO->GetClass()->GetClassFlags() & CLASS_Abstract ) )
|
||||
if( Command->GetElementType() == EEditableMeshElementType::Invalid )
|
||||
{
|
||||
if( CommandCDO->GetElementType() == EEditableMeshElementType::Invalid )
|
||||
{
|
||||
CommandCDO->RegisterUICommand( this );
|
||||
}
|
||||
Command->RegisterUICommand( this );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,15 +144,11 @@ void FMeshEditorVertexCommands::RegisterCommands()
|
||||
|
||||
UI_COMMAND(WeldVertices, "Weld Vertices", "Weld the selected vertices, keeping the first selected vertex.", EUserInterfaceActionType::Button, FInputChord());
|
||||
|
||||
for( TObjectIterator<UMeshEditorCommand> CommandCDOIter( RF_NoFlags ); CommandCDOIter; ++CommandCDOIter )
|
||||
for( UMeshEditorCommand* Command : MeshEditorCommands::Get() )
|
||||
{
|
||||
UMeshEditorCommand* CommandCDO = *CommandCDOIter;
|
||||
if( !( CommandCDO->GetClass()->GetClassFlags() & CLASS_Abstract ) )
|
||||
if( Command->GetElementType() == EEditableMeshElementType::Vertex )
|
||||
{
|
||||
if( CommandCDO->GetElementType() == EEditableMeshElementType::Vertex )
|
||||
{
|
||||
CommandCDO->RegisterUICommand( this );
|
||||
}
|
||||
Command->RegisterUICommand( this );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,15 +169,11 @@ void FMeshEditorEdgeCommands::RegisterCommands()
|
||||
|
||||
UI_COMMAND(SelectEdgeLoop, "Select Edge Loop", "Select the edge loops which contain the selected edges.", EUserInterfaceActionType::Button, FInputChord(EKeys::Two, EModifierKey::Shift));
|
||||
|
||||
for( TObjectIterator<UMeshEditorCommand> CommandCDOIter( RF_NoFlags ); CommandCDOIter; ++CommandCDOIter )
|
||||
for( UMeshEditorCommand* Command : MeshEditorCommands::Get() )
|
||||
{
|
||||
UMeshEditorCommand* CommandCDO = *CommandCDOIter;
|
||||
if( !( CommandCDO->GetClass()->GetClassFlags() & CLASS_Abstract ) )
|
||||
if( Command->GetElementType() == EEditableMeshElementType::Edge )
|
||||
{
|
||||
if( CommandCDO->GetElementType() == EEditableMeshElementType::Edge )
|
||||
{
|
||||
CommandCDO->RegisterUICommand( this );
|
||||
}
|
||||
Command->RegisterUICommand( this );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,17 +195,28 @@ void FMeshEditorPolygonCommands::RegisterCommands()
|
||||
UI_COMMAND(TriangulatePolygon, "Triangulate Polygon", "Triangulate the currently selected polygons.", EUserInterfaceActionType::Button, FInputChord(EKeys::T));
|
||||
UI_COMMAND(AssignMaterial, "Assign Material", "Assigns the highlighted material in the Content Browser to the currently selected polygons.", EUserInterfaceActionType::Button, FInputChord(EKeys::M));
|
||||
|
||||
for( UMeshEditorCommand* Command : MeshEditorCommands::Get() )
|
||||
{
|
||||
if( Command->GetElementType() == EEditableMeshElementType::Polygon )
|
||||
{
|
||||
Command->RegisterUICommand( this );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void UMeshEditorCommandList::HarvestMeshEditorCommands()
|
||||
{
|
||||
MeshEditorCommands.Reset();
|
||||
for( TObjectIterator<UMeshEditorCommand> CommandCDOIter( RF_NoFlags ); CommandCDOIter; ++CommandCDOIter )
|
||||
{
|
||||
UMeshEditorCommand* CommandCDO = *CommandCDOIter;
|
||||
if( !( CommandCDO->GetClass()->GetClassFlags() & CLASS_Abstract ) )
|
||||
{
|
||||
if( CommandCDO->GetElementType() == EEditableMeshElementType::Polygon )
|
||||
{
|
||||
CommandCDO->RegisterUICommand( this );
|
||||
}
|
||||
MeshEditorCommands.Add( NewObject<UMeshEditorCommand>( this, CommandCDO->GetClass() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -575,39 +575,35 @@ void FMeshEditorMode::BindCommands()
|
||||
RegisterPolygonCommand( MeshEditorPolygonCommands.TriangulatePolygon, FExecuteAction::CreateLambda( [this] { TriangulateSelectedPolygons(); } ) );
|
||||
RegisterPolygonCommand( MeshEditorPolygonCommands.AssignMaterial, FExecuteAction::CreateLambda( [this] { AssignSelectedMaterialToSelectedPolygons(); } ) );
|
||||
|
||||
for( TObjectIterator<UMeshEditorCommand> CommandCDOIter( RF_NoFlags ); CommandCDOIter; ++CommandCDOIter )
|
||||
for( UMeshEditorCommand* Command : MeshEditorCommands::Get() )
|
||||
{
|
||||
UMeshEditorCommand* CommandCDO = *CommandCDOIter;
|
||||
if( !( CommandCDO->GetClass()->GetClassFlags() & CLASS_Abstract ) )
|
||||
switch( Command->GetElementType() )
|
||||
{
|
||||
switch( CommandCDO->GetElementType() )
|
||||
{
|
||||
case EEditableMeshElementType::Invalid:
|
||||
{
|
||||
// Common action
|
||||
FUIAction UIAction = CommandCDO->MakeUIAction( *this );
|
||||
CommonActions.Emplace( CommandCDO->GetUICommandInfo(), UIAction );
|
||||
VertexActions.Emplace( CommandCDO->GetUICommandInfo(), UIAction );
|
||||
EdgeActions.Emplace( CommandCDO->GetUICommandInfo(), UIAction );
|
||||
PolygonActions.Emplace( CommandCDO->GetUICommandInfo(), UIAction );
|
||||
}
|
||||
break;
|
||||
case EEditableMeshElementType::Invalid:
|
||||
{
|
||||
// Common action
|
||||
FUIAction UIAction = Command->MakeUIAction( *this );
|
||||
CommonActions.Emplace( Command->GetUICommandInfo(), UIAction );
|
||||
VertexActions.Emplace( Command->GetUICommandInfo(), UIAction );
|
||||
EdgeActions.Emplace( Command->GetUICommandInfo(), UIAction );
|
||||
PolygonActions.Emplace( Command->GetUICommandInfo(), UIAction );
|
||||
}
|
||||
break;
|
||||
|
||||
case EEditableMeshElementType::Vertex:
|
||||
VertexActions.Emplace( CommandCDO->GetUICommandInfo(), CommandCDO->MakeUIAction( *this ) );
|
||||
break;
|
||||
case EEditableMeshElementType::Vertex:
|
||||
VertexActions.Emplace( Command->GetUICommandInfo(), Command->MakeUIAction( *this ) );
|
||||
break;
|
||||
|
||||
case EEditableMeshElementType::Edge:
|
||||
EdgeActions.Emplace( CommandCDO->GetUICommandInfo(), CommandCDO->MakeUIAction( *this ) );
|
||||
break;
|
||||
case EEditableMeshElementType::Edge:
|
||||
EdgeActions.Emplace( Command->GetUICommandInfo(), Command->MakeUIAction( *this ) );
|
||||
break;
|
||||
|
||||
case EEditableMeshElementType::Polygon:
|
||||
PolygonActions.Emplace( CommandCDO->GetUICommandInfo(), CommandCDO->MakeUIAction( *this ) );
|
||||
break;
|
||||
case EEditableMeshElementType::Polygon:
|
||||
PolygonActions.Emplace( Command->GetUICommandInfo(), Command->MakeUIAction( *this ) );
|
||||
break;
|
||||
|
||||
default:
|
||||
check( 0 );
|
||||
}
|
||||
default:
|
||||
check( 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3249,16 +3245,16 @@ void FMeshEditorMode::UpdateActiveAction( const bool bIsActionFinishing )
|
||||
{
|
||||
// Check for registered commands that are active right now
|
||||
bool bFoundValidCommand = false;
|
||||
for( TObjectIterator<UMeshEditorEditCommand> CommandCDOIter( RF_NoFlags ); CommandCDOIter; ++CommandCDOIter )
|
||||
for( UMeshEditorCommand* Command : MeshEditorCommands::Get() )
|
||||
{
|
||||
UMeshEditorEditCommand* CommandCDO = *CommandCDOIter;
|
||||
if( !( CommandCDO->GetClass()->GetClassFlags() & CLASS_Abstract ) )
|
||||
UMeshEditorEditCommand* EditCommand = Cast<UMeshEditorEditCommand>( Command );
|
||||
if( EditCommand != nullptr )
|
||||
{
|
||||
if( ActiveAction == CommandCDO->GetCommandName() )
|
||||
if( ActiveAction == EditCommand->GetCommandName() )
|
||||
{
|
||||
CommandCDO->ApplyDuringDrag( *this, ActiveActionInteractor );
|
||||
EditCommand->ApplyDuringDrag( *this, ActiveActionInteractor );
|
||||
|
||||
bIsMovingSelectedMeshElements = CommandCDO->NeedsDraggingInitiated();
|
||||
bIsMovingSelectedMeshElements = EditCommand->NeedsDraggingInitiated();
|
||||
|
||||
// Should always only be one candidate
|
||||
bFoundValidCommand = true;
|
||||
@@ -3457,11 +3453,12 @@ void FMeshEditorMode::GetSelectedMeshesAndElements( EEditableMeshElementType Ele
|
||||
}
|
||||
|
||||
|
||||
void FMeshEditorMode::FindEdgeSplitUnderInteractor( UViewportInteractor* ViewportInteractor, const UEditableMesh* EditableMesh, const TArray<FMeshElement>& EdgeElements, TArray<float>& OutSplits )
|
||||
bool FMeshEditorMode::FindEdgeSplitUnderInteractor( UViewportInteractor* ViewportInteractor, const UEditableMesh* EditableMesh, const TArray<FMeshElement>& EdgeElements, FEdgeID& OutClosestEdgeID, float& OutSplit )
|
||||
{
|
||||
check( ViewportInteractor != nullptr );
|
||||
|
||||
OutSplits.Reset();
|
||||
OutClosestEdgeID = FEdgeID::Invalid;
|
||||
bool bFoundSplit = false;
|
||||
|
||||
// Figure out where to split based on where the interactor is aiming. We'll look at all of the
|
||||
// selected edges, and choose a split offset based on the closest point along one of those edges
|
||||
@@ -3513,11 +3510,14 @@ void FMeshEditorMode::FindEdgeSplitUnderInteractor( UViewportInteractor* Viewpor
|
||||
1.0f );
|
||||
}
|
||||
|
||||
OutSplits.Reset();
|
||||
OutSplits.Add( ProgressAlongEdge );
|
||||
bFoundSplit = true;
|
||||
OutClosestEdgeID = EdgeID;
|
||||
OutSplit = ProgressAlongEdge;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bFoundSplit;
|
||||
}
|
||||
|
||||
|
||||
@@ -4016,10 +4016,10 @@ void FMeshEditorMode::OnViewportInteractionInputAction( FEditorViewportClient& V
|
||||
}
|
||||
else
|
||||
{
|
||||
for( TObjectIterator<UMeshEditorEditCommand> CommandCDOIter( RF_NoFlags ); CommandCDOIter; ++CommandCDOIter )
|
||||
for( UMeshEditorCommand* Command : MeshEditorCommands::Get() )
|
||||
{
|
||||
UMeshEditorEditCommand* CommandCDO = *CommandCDOIter;
|
||||
if( !( CommandCDO->GetClass()->GetClassFlags() & CLASS_Abstract ) )
|
||||
UMeshEditorEditCommand* EditCommand = Cast<UMeshEditorEditCommand>( Command );
|
||||
if( EditCommand != nullptr )
|
||||
{
|
||||
FName EquippedAction = NAME_None;
|
||||
switch( SelectedMeshElementType )
|
||||
@@ -4037,15 +4037,15 @@ void FMeshEditorMode::OnViewportInteractionInputAction( FEditorViewportClient& V
|
||||
break;
|
||||
}
|
||||
|
||||
const EEditableMeshElementType CommandElementType = CommandCDO->GetElementType();
|
||||
if( ( CommandElementType == SelectedMeshElementType || CommandElementType == EEditableMeshElementType::Invalid || CommandElementType == EEditableMeshElementType::Any ) &&
|
||||
EquippedAction == CommandCDO->GetCommandName() )
|
||||
const EEditableMeshElementType CommandElementType = EditCommand->GetElementType();
|
||||
if( ( CommandElementType == SelectedMeshElementType || CommandElementType == EEditableMeshElementType::Invalid || CommandElementType == EEditableMeshElementType::Any ) &&
|
||||
EquippedAction == EditCommand->GetCommandName() )
|
||||
{
|
||||
if( CommandCDO->TryStartingToDrag( *this, ViewportInteractor ) )
|
||||
if( EditCommand->TryStartingToDrag( *this, ViewportInteractor ) )
|
||||
{
|
||||
StartAction( EquippedAction, ViewportInteractor, CommandCDO->NeedsHoverLocation(), CommandCDO->GetUndoText() );
|
||||
StartAction( EquippedAction, ViewportInteractor, EditCommand->NeedsHoverLocation(), EditCommand->GetUndoText() );
|
||||
|
||||
if( CommandCDO->NeedsDraggingInitiated() )
|
||||
if( EditCommand->NeedsDraggingInitiated() )
|
||||
{
|
||||
bWantToStartMoving = true;
|
||||
}
|
||||
@@ -4909,13 +4909,9 @@ void FMeshEditorMode::MakeVRRadialMenuActionsMenu(FMenuBuilder& MenuBuilder, TSh
|
||||
);
|
||||
}
|
||||
|
||||
for( TObjectIterator<UMeshEditorCommand> CommandCDOIter( RF_NoFlags ); CommandCDOIter; ++CommandCDOIter )
|
||||
for( UMeshEditorCommand* Command : MeshEditorCommands::Get() )
|
||||
{
|
||||
UMeshEditorCommand* CommandCDO = *CommandCDOIter;
|
||||
if( !( CommandCDO->GetClass()->GetClassFlags() & CLASS_Abstract ) )
|
||||
{
|
||||
CommandCDO->AddToVRRadialMenuActionsMenu( *this, MenuBuilder, CommandList, FMeshEditorStyle::GetStyleSetName(), VRMode );
|
||||
}
|
||||
Command->AddToVRRadialMenuActionsMenu( *this, MenuBuilder, CommandList, FMeshEditorStyle::GetStyleSetName(), VRMode );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ protected:
|
||||
virtual void DeselectAllMeshElements() override;
|
||||
virtual void DeselectMeshElements( const TArray<FMeshElement>& MeshElementsToDeselect ) override;
|
||||
virtual void DeselectMeshElements( const TMap<UEditableMesh*, TArray<FMeshElement>>& MeshElementsToDeselect ) override;
|
||||
virtual void FindEdgeSplitUnderInteractor( UViewportInteractor* ViewportInteractor, const UEditableMesh* EditableMesh, const TArray<FMeshElement>& EdgeElements, TArray<float>& OutSplits ) override;
|
||||
virtual bool FindEdgeSplitUnderInteractor( UViewportInteractor* ViewportInteractor, const UEditableMesh* EditableMesh, const TArray<FMeshElement>& EdgeElements, FEdgeID& OutClosestEdgeID, float& OutSplit ) override;
|
||||
virtual class UViewportInteractor* GetActiveActionInteractor() override
|
||||
{
|
||||
return ActiveActionInteractor;
|
||||
|
||||
@@ -52,8 +52,8 @@ public:
|
||||
/** Commits all selected meshes */
|
||||
virtual void CommitSelectedMeshes() = 0;
|
||||
|
||||
/** Given an interactor and a mesh, finds edges under the interactor along with their exact split position (progress along the edge) */
|
||||
virtual void FindEdgeSplitUnderInteractor( class UViewportInteractor* ViewportInteractor, const UEditableMesh* EditableMesh, const TArray<FMeshElement>& EdgeElements, TArray<float>& OutSplits ) = 0;
|
||||
/** Given an interactor and a mesh, finds edges under the interactor along with their exact split position (progress along the edge). Returns true if we found a split position. */
|
||||
virtual bool FindEdgeSplitUnderInteractor( class UViewportInteractor* ViewportInteractor, const UEditableMesh* EditableMesh, const TArray<FMeshElement>& EdgeElements, FEdgeID& OutClosestEdgeID, float& OutSplit ) = 0;
|
||||
|
||||
/** When performing an interactive action that was initiated using an interactor, this is the interactor that was used. */
|
||||
virtual class UViewportInteractor* GetActiveActionInteractor() = 0;
|
||||
|
||||
@@ -251,3 +251,26 @@ public:
|
||||
TSharedPtr<FUICommandInfo> AssignMaterial;
|
||||
};
|
||||
|
||||
|
||||
UCLASS()
|
||||
class UMeshEditorCommandList : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
void HarvestMeshEditorCommands();
|
||||
|
||||
/** All of the mesh editor commands that were registered at startup */
|
||||
UPROPERTY()
|
||||
TArray<UMeshEditorCommand*> MeshEditorCommands;
|
||||
};
|
||||
|
||||
|
||||
namespace MeshEditorCommands
|
||||
{
|
||||
extern const TArray<UMeshEditorCommand*>& Get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -42,25 +42,13 @@ void UInsertEdgeLoopCommand::ApplyDuringDrag( IMeshEditorModeEditingContract& Me
|
||||
const TArray<FMeshElement>& EdgeElements = MeshAndEdges.Value;
|
||||
|
||||
// Figure out where to add the loop along the edge
|
||||
static TArray<float> Splits;
|
||||
MeshEditorMode.FindEdgeSplitUnderInteractor( MeshEditorMode.GetActiveActionInteractor(), EditableMesh, EdgeElements, /* Out */ Splits );
|
||||
FEdgeID ClosestEdgeID = FEdgeID::Invalid;
|
||||
float Split = 0.0f;
|
||||
const bool bFoundSplit = MeshEditorMode.FindEdgeSplitUnderInteractor( MeshEditorMode.GetActiveActionInteractor(), EditableMesh, EdgeElements, /* Out */ ClosestEdgeID, /* Out */ Split );
|
||||
|
||||
// Insert the edge loop
|
||||
if( Splits.Num() > 0 )
|
||||
if( bFoundSplit )
|
||||
{
|
||||
// @todo mesheditor edgeloop: Test code
|
||||
if( false )
|
||||
{
|
||||
if( Splits[ 0 ] > 0.25f )
|
||||
{
|
||||
Splits.Insert( FMath::Max( Splits[ 0 ] - 0.2f, 0.0f ), 0 );
|
||||
}
|
||||
if( Splits.Last() < 0.75f )
|
||||
{
|
||||
Splits.Add( FMath::Min( Splits.Last() + 0.2f, 1.0f ) );
|
||||
}
|
||||
}
|
||||
|
||||
for( const FMeshElement& EdgeMeshElement : EdgeElements )
|
||||
{
|
||||
const FEdgeID EdgeID( EdgeMeshElement.ElementAddress.ElementID );
|
||||
@@ -68,6 +56,9 @@ void UInsertEdgeLoopCommand::ApplyDuringDrag( IMeshEditorModeEditingContract& Me
|
||||
static TArray<FEdgeID> NewEdgeIDs;
|
||||
NewEdgeIDs.Reset();
|
||||
|
||||
static TArray<float> Splits; // @todo mesheditor edgeloop: Add support for inserting multiple splits at once!
|
||||
Splits.Reset();
|
||||
Splits.Add( Split );
|
||||
EditableMesh->InsertEdgeLoop( EdgeID, Splits, /* Out */ NewEdgeIDs );
|
||||
|
||||
// Select all of the new edges that were created by inserting the loop
|
||||
|
||||
@@ -26,6 +26,8 @@ bool USplitEdgeCommand::TryStartingToDrag( IMeshEditorModeEditingContract& MeshE
|
||||
// Figure out what to split
|
||||
MeshEditorMode.GetSelectedMeshesAndEdges( /* Out */ SplitEdgeMeshesAndEdgesToSplit );
|
||||
|
||||
SplitEdgeSplitList.Reset();
|
||||
|
||||
if( SplitEdgeMeshesAndEdgesToSplit.Num() > 0 )
|
||||
{
|
||||
for( auto& MeshAndEdges : SplitEdgeMeshesAndEdgesToSplit )
|
||||
@@ -34,12 +36,15 @@ bool USplitEdgeCommand::TryStartingToDrag( IMeshEditorModeEditingContract& MeshE
|
||||
const TArray<FMeshElement>& EdgeElements = MeshAndEdges.Value;
|
||||
|
||||
// Figure out where to split
|
||||
MeshEditorMode.FindEdgeSplitUnderInteractor( ViewportInteractor, EditableMesh, EdgeElements, /* Out */ SplitEdgeSplitList );
|
||||
FEdgeID ClosestEdgeID = FEdgeID::Invalid;
|
||||
float Split = 0.0f;
|
||||
const bool bFoundSplit = MeshEditorMode.FindEdgeSplitUnderInteractor( ViewportInteractor, EditableMesh, EdgeElements, /* Out */ ClosestEdgeID, /* Out */ Split );
|
||||
|
||||
// Split the edges
|
||||
if( SplitEdgeSplitList.Num() > 0 )
|
||||
if( bFoundSplit )
|
||||
{
|
||||
SplitEdgeSplitList.Add( Split );
|
||||
bHaveEdge = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user