Refactor.
Finding out which songs were random was broken: if a non-static
song (eg. player's best) was missing ("BEST99999"), m_entries
doesn't necessarily match one-to-one with the results of GetStageInfo.
This commit is contained in:
@@ -178,23 +178,22 @@ void BPMDisplay::SetBPM( const Song* pSong )
|
|||||||
void BPMDisplay::SetBPM( const Course* pCourse )
|
void BPMDisplay::SetBPM( const Course* pCourse )
|
||||||
{
|
{
|
||||||
ASSERT( pCourse );
|
ASSERT( pCourse );
|
||||||
vector<Song*> vSongs;
|
|
||||||
vector<Notes*> vNotes;
|
|
||||||
vector<CString> vsModifiers;
|
|
||||||
pCourse->GetStageInfo( vSongs, vNotes, vsModifiers, GAMESTATE->GetCurrentStyleDef()->m_NotesType );
|
|
||||||
|
|
||||||
ASSERT( vSongs.size() );
|
vector<Course::Info> ci;
|
||||||
|
pCourse->GetCourseInfo( GAMESTATE->GetCurrentStyleDef()->m_NotesType, ci );
|
||||||
|
|
||||||
|
ASSERT( ci.size() );
|
||||||
|
|
||||||
vector<float> BPMS;
|
vector<float> BPMS;
|
||||||
for( unsigned i = 0; i < vSongs.size(); ++i )
|
for( unsigned i = 0; i < ci.size(); ++i )
|
||||||
{
|
{
|
||||||
if( pCourse->IsMysterySong(i) )
|
if( ci[i].Random )
|
||||||
{
|
{
|
||||||
BPMS.push_back( -1 );
|
BPMS.push_back( -1 );
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Song *pSong = vSongs[i];
|
Song *pSong = ci[i].Song;
|
||||||
ASSERT( pSong );
|
ASSERT( pSong );
|
||||||
switch( pSong->m_DisplayBPMType )
|
switch( pSong->m_DisplayBPMType )
|
||||||
{
|
{
|
||||||
|
|||||||
+76
-128
@@ -370,82 +370,98 @@ void Course::AutogenNonstopFromGroup( CString sGroupName, vector<Song*> &apSongs
|
|||||||
*/
|
*/
|
||||||
bool Course::HasDifficult( NotesType nt ) const
|
bool Course::HasDifficult( NotesType nt ) const
|
||||||
{
|
{
|
||||||
if( ContainsAnyMysterySongs() )
|
/* Check to see if any songs would change if difficult. */
|
||||||
{
|
|
||||||
for( unsigned i=0; i<m_entries.size(); i++ )
|
|
||||||
{
|
|
||||||
if( m_entries[i].type != Entry::random && m_entries[i].type != Entry::random_within_group )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* CHALLENGE doesn't get any harder. */
|
|
||||||
if( m_entries[i].difficulty == DIFFICULTY_CHALLENGE )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* 10..10 doesn't get any harder. */
|
|
||||||
if( m_entries[i].difficulty == DIFFICULTY_INVALID &&
|
|
||||||
m_entries[i].low_meter == MAX_BOTTOM_RANGE &&
|
|
||||||
m_entries[i].high_meter == MAX_BOTTOM_RANGE )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We don't have any random songs. Check to see if any songs would change
|
|
||||||
* if difficult. */
|
|
||||||
const bool OldDifficulty = GAMESTATE->m_bDifficultCourses;
|
const bool OldDifficulty = GAMESTATE->m_bDifficultCourses;
|
||||||
|
|
||||||
vector<Song*> vSongs, vHardSongs;
|
vector<Info> Normal, Hard;
|
||||||
vector<Notes*> vNotes, vHardNotes;
|
|
||||||
vector<CString> vsIgnore;
|
|
||||||
|
|
||||||
GAMESTATE->m_bDifficultCourses = false;
|
GAMESTATE->m_bDifficultCourses = false;
|
||||||
GetStageInfo( vSongs, vNotes, vsIgnore, nt );
|
GetCourseInfo( nt, Normal );
|
||||||
GAMESTATE->m_bDifficultCourses = true;
|
GAMESTATE->m_bDifficultCourses = true;
|
||||||
GetStageInfo( vHardSongs, vHardNotes, vsIgnore, nt );
|
GetCourseInfo( nt, Hard );
|
||||||
GAMESTATE->m_bDifficultCourses = OldDifficulty;
|
GAMESTATE->m_bDifficultCourses = OldDifficulty;
|
||||||
|
|
||||||
if( vSongs.size() != vHardSongs.size() )
|
if( Normal.size() != Hard.size() )
|
||||||
return true; /* it changed */
|
return true; /* it changed */
|
||||||
|
|
||||||
for( unsigned i=0; i<vSongs.size(); i++ )
|
for( unsigned i=0; i<Normal.size(); i++ )
|
||||||
if( vSongs[i] != vHardSongs[i] || vNotes[i] != vHardNotes[i] )
|
{
|
||||||
|
if( Normal[i].CourseIndex != Hard[i].CourseIndex )
|
||||||
|
return true; /* it changed */
|
||||||
|
|
||||||
|
if( Normal[i].Random )
|
||||||
|
{
|
||||||
|
const Entry &e = m_entries[ Normal[i].CourseIndex ];
|
||||||
|
|
||||||
|
/* Difficulties under CHALLENGE change by getting harder. */
|
||||||
|
if( e.difficulty < DIFFICULTY_CHALLENGE )
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
/* Meters under MAX_BOTTOM_RANGE..MAX_BOTTOM_RANGE change by getting harder. */
|
||||||
|
if( e.difficulty != DIFFICULTY_INVALID &&
|
||||||
|
e.low_meter < MAX_BOTTOM_RANGE &&
|
||||||
|
e.high_meter < MAX_BOTTOM_RANGE )
|
||||||
|
return true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( Normal[i].Song != Hard[i].Song || Normal[i].Notes != Hard[i].Notes )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Course::IsPlayableIn( NotesType nt ) const
|
bool Course::IsPlayableIn( NotesType nt ) const
|
||||||
{
|
{
|
||||||
/* XXX: Is this good enough? This needs to be guaranteed: if we say
|
vector<Info> ci;
|
||||||
* a course is playable and it's not, we'll crash in ScreenGameplay. */
|
GetCourseInfo( nt, ci );
|
||||||
|
return ci.size() > 0;
|
||||||
vector<Song*> vSongs;
|
|
||||||
vector<Notes*> vNotes;
|
|
||||||
vector<CString> vsModifiers;
|
|
||||||
GetStageInfo( vSongs, vNotes, vsModifiers, nt);
|
|
||||||
|
|
||||||
return vNotes.size() > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Course::GetStageInfo(
|
bool Course::GetFirstStageInfo(
|
||||||
vector<Song*>& vSongsOut,
|
Song*& pSongOut,
|
||||||
vector<Notes*>& vNotesOut,
|
Notes*& pNotesOut,
|
||||||
vector<CString>& vsModifiersOut,
|
CString& sModifiersOut,
|
||||||
NotesType nt ) const
|
NotesType nt ) const
|
||||||
|
{
|
||||||
|
vector<Info> ci;
|
||||||
|
GetCourseInfo( nt, ci );
|
||||||
|
|
||||||
|
if( ci.empty() )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
pSongOut = ci[0].Song;
|
||||||
|
pNotesOut = ci[0].Notes;
|
||||||
|
sModifiersOut = ci[0].Modifiers;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Course::GetCourseInfo( NotesType nt, vector<Course::Info> &ci ) const
|
||||||
{
|
{
|
||||||
vector<Entry> entries = m_entries;
|
vector<Entry> entries = m_entries;
|
||||||
|
|
||||||
if( m_bRandomize )
|
if( m_bRandomize )
|
||||||
random_shuffle( entries.begin(), entries.end() );
|
random_shuffle( entries.begin(), entries.end() );
|
||||||
|
|
||||||
vector<Song*> vSongsByMostPlayed;
|
vector<Song*> vSongsByMostPlayed = SONGMAN->GetBestSongs();
|
||||||
|
// filter out songs that don't have both medium and hard steps and long ver sons
|
||||||
|
for( int j=vSongsByMostPlayed.size()-1; j>=0; j-- )
|
||||||
|
{
|
||||||
|
Song* pSong = vSongsByMostPlayed[j];
|
||||||
|
if( pSong->m_fMusicLengthSeconds > PREFSMAN->m_fLongVerSongSeconds ||
|
||||||
|
pSong->m_fMusicLengthSeconds > PREFSMAN->m_fMarathonVerSongSeconds ||
|
||||||
|
!pSong->GetNotes(nt, DIFFICULTY_MEDIUM, PREFSMAN->m_bAutogenMissingTypes) ||
|
||||||
|
!pSong->GetNotes(nt, DIFFICULTY_HARD, PREFSMAN->m_bAutogenMissingTypes) )
|
||||||
|
vSongsByMostPlayed.erase( vSongsByMostPlayed.begin()+j );
|
||||||
|
}
|
||||||
|
|
||||||
vector<Song*> AllSongsShuffled = SONGMAN->GetAllSongs();
|
vector<Song*> AllSongsShuffled = SONGMAN->GetAllSongs();
|
||||||
random_shuffle( AllSongsShuffled.begin(), AllSongsShuffled.end() );
|
random_shuffle( AllSongsShuffled.begin(), AllSongsShuffled.end() );
|
||||||
int CurSong = 0; /* Current offset into AllSongsShuffled */
|
int CurSong = 0; /* Current offset into AllSongsShuffled */
|
||||||
|
|
||||||
|
ci.clear();
|
||||||
|
|
||||||
for( unsigned i=0; i<entries.size(); i++ )
|
for( unsigned i=0; i<entries.size(); i++ )
|
||||||
{
|
{
|
||||||
const Entry& e = entries[i];
|
const Entry& e = entries[i];
|
||||||
@@ -500,26 +516,6 @@ void Course::GetStageInfo(
|
|||||||
case Entry::best:
|
case Entry::best:
|
||||||
case Entry::worst:
|
case Entry::worst:
|
||||||
{
|
{
|
||||||
if(vSongsByMostPlayed.size() == 0)
|
|
||||||
{
|
|
||||||
/* Probably the first time getting here; fill it in just once. */
|
|
||||||
/* XXX: This is still expensive enough to cause a noticable
|
|
||||||
* frame drop when scrolling over best/worst entries. */
|
|
||||||
vSongsByMostPlayed = SONGMAN->GetAllSongs();
|
|
||||||
SortSongPointerArrayByMostPlayed( vSongsByMostPlayed );
|
|
||||||
|
|
||||||
// filter out songs that don't have both medium and hard steps and long ver sons
|
|
||||||
for( int j=vSongsByMostPlayed.size()-1; j>=0; j-- )
|
|
||||||
{
|
|
||||||
Song* pSong = vSongsByMostPlayed[j];
|
|
||||||
if( pSong->m_fMusicLengthSeconds > PREFSMAN->m_fLongVerSongSeconds ||
|
|
||||||
pSong->m_fMusicLengthSeconds > PREFSMAN->m_fMarathonVerSongSeconds ||
|
|
||||||
!pSong->GetNotes(nt, DIFFICULTY_MEDIUM, PREFSMAN->m_bAutogenMissingTypes) ||
|
|
||||||
!pSong->GetNotes(nt, DIFFICULTY_HARD, PREFSMAN->m_bAutogenMissingTypes) )
|
|
||||||
vSongsByMostPlayed.erase( vSongsByMostPlayed.begin()+j );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( e.players_index >= (int)vSongsByMostPlayed.size() )
|
if( e.players_index >= (int)vSongsByMostPlayed.size() )
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -567,36 +563,13 @@ void Course::GetStageInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vSongsOut.push_back( pSong );
|
Info cinfo;
|
||||||
vNotesOut.push_back( pNotes );
|
cinfo.Song = pSong;
|
||||||
vsModifiersOut.push_back( e.modifiers );
|
cinfo.Notes = pNotes;
|
||||||
|
cinfo.Modifiers = e.modifiers;
|
||||||
|
cinfo.Random = ( e.type == Entry::random || e.type == Entry::random_within_group );
|
||||||
|
ci.push_back( cinfo );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Course::GetFirstStageInfo(
|
|
||||||
Song*& pSongOut,
|
|
||||||
Notes*& pNotesOut,
|
|
||||||
CString& sModifiersOut,
|
|
||||||
NotesType nt ) const
|
|
||||||
{
|
|
||||||
vector<Song*> vSongs;
|
|
||||||
vector<Notes*> vNotes;
|
|
||||||
vector<CString> vsModifiers;
|
|
||||||
|
|
||||||
GetStageInfo(
|
|
||||||
vSongs,
|
|
||||||
vNotes,
|
|
||||||
vsModifiers,
|
|
||||||
nt );
|
|
||||||
if( vSongs.empty() )
|
|
||||||
return false;
|
|
||||||
|
|
||||||
pSongOut = vSongs[0];
|
|
||||||
pNotesOut = vNotes[0];
|
|
||||||
sModifiersOut = vsModifiers[0];
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RageColor Course::GetColor() const
|
RageColor Course::GetColor() const
|
||||||
@@ -610,18 +583,6 @@ RageColor Course::GetColor() const
|
|||||||
return RageColor(0,1,0,1); // green
|
return RageColor(0,1,0,1); // green
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Course::IsMysterySong( int stage ) const
|
|
||||||
{
|
|
||||||
switch( m_entries[stage].type )
|
|
||||||
{
|
|
||||||
case Entry::fixed: return false;
|
|
||||||
case Entry::random: return true;
|
|
||||||
case Entry::random_within_group: return true;
|
|
||||||
case Entry::best: return false;
|
|
||||||
case Entry::worst: return false;
|
|
||||||
default: ASSERT(0); return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Difficulty Course::GetDifficulty( int stage ) const
|
Difficulty Course::GetDifficulty( int stage ) const
|
||||||
{
|
{
|
||||||
@@ -646,31 +607,18 @@ void Course::GetMeterRange( int stage, int& iMeterLowOut, int& iMeterHighOut ) c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Course::ContainsAnyMysterySongs() const
|
|
||||||
{
|
|
||||||
for( unsigned i=0; i<m_entries.size(); i++ )
|
|
||||||
if( IsMysterySong(i) )
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Course::GetTotalSeconds( float& fSecondsOut ) const
|
bool Course::GetTotalSeconds( float& fSecondsOut ) const
|
||||||
{
|
{
|
||||||
if( ContainsAnyMysterySongs() )
|
vector<Info> ci;
|
||||||
return false;
|
GetCourseInfo( NOTES_TYPE_DANCE_SINGLE, ci );
|
||||||
|
|
||||||
vector<Song*> vSongsOut;
|
|
||||||
vector<Notes*> vNotesOut;
|
|
||||||
vector<CString> vsModifiersOut;
|
|
||||||
GetStageInfo(
|
|
||||||
vSongsOut,
|
|
||||||
vNotesOut,
|
|
||||||
vsModifiersOut,
|
|
||||||
NOTES_TYPE_DANCE_SINGLE ); // doesn't matter
|
|
||||||
|
|
||||||
fSecondsOut = 0;
|
fSecondsOut = 0;
|
||||||
for( unsigned i=0; i<vSongsOut.size(); i++ )
|
for( unsigned i=0; i<ci.size(); i++ )
|
||||||
fSecondsOut += vSongsOut[i]->m_fMusicLengthSeconds;
|
{
|
||||||
|
if( ci[i].Random )
|
||||||
|
return false;
|
||||||
|
fSecondsOut += ci[i].Song->m_fMusicLengthSeconds;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-3
@@ -59,10 +59,23 @@ public:
|
|||||||
bool m_bDifficult; // only make something difficult once
|
bool m_bDifficult; // only make something difficult once
|
||||||
int m_iLives; // -1 means use bar life meter
|
int m_iLives; // -1 means use bar life meter
|
||||||
|
|
||||||
|
struct Info
|
||||||
|
{
|
||||||
|
Info(): Song(NULL), Notes(NULL), Random(false) { }
|
||||||
|
Song* Song;
|
||||||
|
Notes* Notes;
|
||||||
|
CString Modifiers;
|
||||||
|
bool Random;
|
||||||
|
/* Corresponding entry in m_entries: */
|
||||||
|
int CourseIndex;
|
||||||
|
};
|
||||||
|
// Dereferences course_entries and returns only the playable Songs and Notes
|
||||||
|
void GetCourseInfo( NotesType nt, vector<Info> &ci ) const;
|
||||||
|
|
||||||
int GetEstimatedNumStages() const { return m_entries.size(); }
|
int GetEstimatedNumStages() const { return m_entries.size(); }
|
||||||
bool HasDifficult( NotesType nt ) const;
|
bool HasDifficult( NotesType nt ) const;
|
||||||
bool IsPlayableIn( NotesType nt ) const;
|
bool IsPlayableIn( NotesType nt ) const;
|
||||||
void GetStageInfo( // Derefrences course_entries and returns only the playable Songs and Notes
|
void GetStageInfo(
|
||||||
vector<Song*>& vSongsOut,
|
vector<Song*>& vSongsOut,
|
||||||
vector<Notes*>& vNotesOut,
|
vector<Notes*>& vNotesOut,
|
||||||
vector<CString>& vsModifiersOut,
|
vector<CString>& vsModifiersOut,
|
||||||
@@ -73,10 +86,8 @@ public:
|
|||||||
CString& sModifiersOut,
|
CString& sModifiersOut,
|
||||||
NotesType nt ) const;
|
NotesType nt ) const;
|
||||||
RageColor GetColor() const;
|
RageColor GetColor() const;
|
||||||
bool IsMysterySong( int stage ) const;
|
|
||||||
Difficulty GetDifficulty( int stage ) const;
|
Difficulty GetDifficulty( int stage ) const;
|
||||||
void GetMeterRange( int stage, int& iMeterLowOut, int& iMeterHighOut ) const;
|
void GetMeterRange( int stage, int& iMeterLowOut, int& iMeterHighOut ) const;
|
||||||
bool ContainsAnyMysterySongs() const;
|
|
||||||
bool GetTotalSeconds( float& fSecondsOut ) const;
|
bool GetTotalSeconds( float& fSecondsOut ) const;
|
||||||
|
|
||||||
bool IsNonstop() const { return !m_bRepeat && m_iLives <= 0; } // use bar life meter
|
bool IsNonstop() const { return !m_bRepeat && m_iLives <= 0; } // use bar life meter
|
||||||
|
|||||||
@@ -54,32 +54,27 @@ void CourseContentsList::SetFromCourse( Course* pCourse )
|
|||||||
|
|
||||||
m_iNumContents = 0;
|
m_iNumContents = 0;
|
||||||
|
|
||||||
vector<Song*> vSongs;
|
vector<Course::Info> ci;
|
||||||
vector<Notes*> vNotes;
|
pCourse->GetCourseInfo( GAMESTATE->GetCurrentStyleDef()->m_NotesType, ci );
|
||||||
vector<CString> vsModifiers;
|
|
||||||
pCourse->GetStageInfo( vSongs, vNotes, vsModifiers, GAMESTATE->GetCurrentStyleDef()->m_NotesType );
|
|
||||||
|
|
||||||
for( int i=0; i<min((int)vSongs.size(), MAX_TOTAL_CONTENTS); i++ )
|
for( int i=0; i<min((int)ci.size(), MAX_TOTAL_CONTENTS); i++ )
|
||||||
{
|
{
|
||||||
CourseEntryDisplay& display = m_CourseContentDisplays[m_iNumContents];
|
CourseEntryDisplay& display = m_CourseContentDisplays[m_iNumContents];
|
||||||
|
|
||||||
if( pCourse->IsMysterySong(i) )
|
if( ci[i].Random )
|
||||||
{
|
{
|
||||||
Difficulty dc = pCourse->GetDifficulty(i);
|
Difficulty dc = pCourse->GetDifficulty(i);
|
||||||
int iMeterLow, iMeterHigh;
|
int iMeterLow, iMeterHigh;
|
||||||
pCourse->GetMeterRange(i, iMeterLow, iMeterHigh);
|
pCourse->GetMeterRange(i, iMeterLow, iMeterHigh);
|
||||||
|
|
||||||
if( dc == DIFFICULTY_INVALID )
|
if( dc == DIFFICULTY_INVALID )
|
||||||
display.LoadFromMeterRange( m_iNumContents+1, iMeterLow, iMeterHigh, vsModifiers[i] );
|
display.LoadFromMeterRange( m_iNumContents+1, iMeterLow, iMeterHigh, ci[i].Modifiers );
|
||||||
else
|
else
|
||||||
display.LoadFromDifficulty( m_iNumContents+1, dc, vsModifiers[i] );
|
display.LoadFromDifficulty( m_iNumContents+1, dc, ci[i].Modifiers );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Song* pSong = vSongs[i];
|
display.LoadFromSongAndNotes( m_iNumContents+1, ci[i].Song, ci[i].Notes, ci[i].Modifiers );
|
||||||
Notes* pNotes = vNotes[i];
|
|
||||||
CString sModifiers = vsModifiers[i];
|
|
||||||
display.LoadFromSongAndNotes( m_iNumContents+1, pSong, pNotes, vsModifiers[i] );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_iNumContents ++;
|
m_iNumContents ++;
|
||||||
|
|||||||
@@ -145,12 +145,17 @@ ScreenGameplay::ScreenGameplay( bool bDemonstration ) : Screen("ScreenGameplay")
|
|||||||
//
|
//
|
||||||
if( GAMESTATE->IsCourseMode() )
|
if( GAMESTATE->IsCourseMode() )
|
||||||
{
|
{
|
||||||
Course* pCourse = GAMESTATE->m_pCurCourse;
|
vector<Course::Info> ci;
|
||||||
pCourse->GetStageInfo( m_apSongsQueue, m_apNotesQueue[0], m_asModifiersQueue[0], GAMESTATE->GetCurrentStyleDef()->m_NotesType );
|
GAMESTATE->m_pCurCourse->GetCourseInfo( GAMESTATE->GetCurrentStyleDef()->m_NotesType, ci );
|
||||||
for( int p=1; p<NUM_PLAYERS; p++ )
|
for( int p=0; p<NUM_PLAYERS; p++ )
|
||||||
{
|
{
|
||||||
m_apNotesQueue[p] = m_apNotesQueue[0];
|
m_apNotesQueue[p].clear();
|
||||||
m_asModifiersQueue[p] = m_asModifiersQueue[0];
|
m_asModifiersQueue[p].clear();
|
||||||
|
for( unsigned c=0; c<ci.size(); ++c )
|
||||||
|
{
|
||||||
|
m_apNotesQueue[p].push_back( ci[c].Notes );
|
||||||
|
m_asModifiersQueue[p].push_back( ci[c].Modifiers );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user