diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml
index 10a8f62a7b..d9c79eeab1 100644
--- a/Docs/Luadoc/Lua.xml
+++ b/Docs/Luadoc/Lua.xml
@@ -1289,6 +1289,7 @@
+
diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml
index 28f00cb28a..60637943ae 100644
--- a/Docs/Luadoc/LuaDocumentation.xml
+++ b/Docs/Luadoc/LuaDocumentation.xml
@@ -4125,6 +4125,7 @@ a,b = options:Boost(5)
+
If the player is using Incoming ((positive skew and negative tilt) or (negative skew and positive tilt)), returns the value of skew and its approach_speed.
Returns nil otherwise.
diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini
index 9a5ac78344..76fb9fcad1 100644
--- a/Themes/_fallback/Languages/en.ini
+++ b/Themes/_fallback/Languages/en.ini
@@ -892,6 +892,7 @@ SuddenDeath=Sudden Death
SuddenOffset=Sudden Offset
SuperShuffle=Cement Mixer
SoftShuffle=Soft Shuffle
+HyperShuffle=Hyper Shuffle
Swap Sides=Swap Sides
Swap Up/Down=Swap Up/Down
Sync Machine=Sync Machine
diff --git a/Themes/_fallback/metrics.ini b/Themes/_fallback/metrics.ini
index 6e650f8e27..3a8358aabe 100644
--- a/Themes/_fallback/metrics.ini
+++ b/Themes/_fallback/metrics.ini
@@ -2464,7 +2464,7 @@ Appearance,4="mod,blink;name,Blink"
# Appearance,5="mod,blink;name,Blink"
# Appearance,6="mod,randomvanish;name,R.Vanish"
-Turn="7;selectmultiple"
+Turn="8;selectmultiple"
TurnDefault="mod,no turn"
Turn,1="mod,mirror;name,Mirror"
Turn,2="mod,backwards;name,Backwards"
@@ -2473,6 +2473,7 @@ Turn,4="mod,right;name,Right"
Turn,5="mod,shuffle;name,Shuffle"
Turn,6="mod,supershuffle;name,SuperShuffle"
Turn,7="mod,softshuffle;name,SoftShuffle"
+Turn,8="mod,hypershuffle;name,HyperShuffle"
# Turn="6"
# TurnDefault="mod,no turn"
diff --git a/src/NoteDataUtil.cpp b/src/NoteDataUtil.cpp
index 330b3f0b58..a10f9ace59 100644
--- a/src/NoteDataUtil.cpp
+++ b/src/NoteDataUtil.cpp
@@ -1855,6 +1855,142 @@ static void GetTrackMapping( StepsType st, NoteDataUtil::TrackMapping tt, int Nu
}
}
+static void HyperShuffleNotes( NoteData &inout, int iStartIndex, int iEndIndex)
+{
+ int iNumTracks = inout.GetNumTracks();
+
+ std::vector viHoldEndRows(iNumTracks, -1);
+
+ // A "free track" is a track that is not part of a hold and is therefore
+ // free to be shuffled into.
+ int iFreeTracks = iNumTracks;
+
+ // If this is the first row, there cannot be any active holds yet.
+ if ( iStartIndex != 0 )
+ {
+ // Search for any hold notes that might be present at the first row.
+ // Once the loop below gets going, it keeps track of hold notes itself.
+ for ( int track = 0; track < iNumTracks; track++ )
+ {
+ int iTargetRow = iStartIndex;
+
+ if ( inout.GetPrevTapNoteRowForTrack(track, iTargetRow) )
+ {
+ const TapNote &tn = inout.GetTapNote(track, iTargetRow);
+
+ // Check for a hold that ends on or after this row.
+ if ( tn.type == TapNoteType_HoldHead )
+ {
+ int iHoldEndRow = iTargetRow + tn.iDuration;
+
+ if ( iHoldEndRow >= iStartIndex )
+ {
+ viHoldEndRows[track] = iHoldEndRow;
+ iFreeTracks--;
+ }
+ }
+ }
+ }
+ }
+
+ // Stores the list of tracks that can have notes placed into them.
+ // This will be shuffled so that notes can be placed in different tracks.
+ std::vector viTargetTracks;
+ // Stores the list of tap notes that need to be placed.
+ std::vector vtnTargetTaps;
+ viTargetTracks.reserve(iNumTracks);
+ vtnTargetTaps.reserve(iNumTracks);
+
+ FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, r, iStartIndex, iEndIndex )
+ {
+ viTargetTracks.clear();
+ vtnTargetTaps.clear();
+ int iActualTaps = 0;
+
+ for( int track=0; track iHoldEndRow)
+ {
+ iHoldEndRow = -1;
+ viHoldEndRows[track] = iHoldEndRow;
+ iFreeTracks++;
+ }
+ }
+
+ const TapNote tn1 = inout.GetTapNote(track, r);
+ switch( tn1.type )
+ {
+ case TapNoteType_HoldTail:
+ ASSERT_M(0, ssprintf("Saw a HoldTail during HyperShuffleNotes (row %d, track %d)", r, track));
+
+ // If asserts are off, treat this HoldTail as if it were an empty note.
+ // If they aren't off, this code can't be reached.
+ inout.SetTapNote(track, r, TAP_EMPTY);
+
+ // Fallthrough
+ case TapNoteType_Empty:
+ // Empty tap notes don't get directly placed in the shuffle table.
+ // Instead, if they aren't in a hold, they get added to the list
+ // of selectable tracks. Later on, new empty tap notes will be
+ // created to pad the list of taps out to the required number.
+ if( iHoldEndRow == -1 )
+ viTargetTracks.push_back(track);
+ break;
+ case TapNoteType_AutoKeysound:
+ case TapNoteType_Tap:
+ case TapNoteType_Lift:
+ case TapNoteType_Mine:
+ case TapNoteType_Attack:
+ case TapNoteType_Fake:
+ case TapNoteType_HoldHead:
+ vtnTargetTaps.push_back(tn1);
+ iActualTaps++;
+
+ if (iHoldEndRow == -1 )
+ viTargetTracks.push_back(track);
+ else
+ {
+ // This track won't be part of the shuffle table, so if it
+ // is not cleared now it will not get cleared later.
+ inout.SetTapNote(track, r, TAP_EMPTY);
+ }
+ break;
+ case TapNoteType_Invalid:
+ default:
+ ASSERT(0);
+ }
+ }
+
+ // Do the padding described above.
+ for(int i = iActualTaps; i < iFreeTracks; i++)
+ vtnTargetTaps.push_back(TAP_EMPTY);
+
+ std::random_shuffle(viTargetTracks.begin(), viTargetTracks.end(), g_RandomNumberGenerator);
+
+ // Go through the tracks in their shuffled order and drop tap notes.
+ for(int i = 0; i < viTargetTracks.size(); i++)
+ {
+ const int targetTrack = viTargetTracks[i];
+ const TapNote current_tn = vtnTargetTaps[i];
+ inout.SetTapNote(targetTrack, r, current_tn);
+
+ // If it's a hold, mark this track occupied by a hold until its end.
+ if( current_tn.type == TapNoteType_HoldHead )
+ {
+ ASSERT_M(viHoldEndRows[targetTrack] == -1,
+ ssprintf("Tried to insert a hold into another hold (row %d, track %d)", r, targetTrack));
+ iFreeTracks--;
+ viHoldEndRows[targetTrack] = r + current_tn.iDuration;
+ }
+ }
+ }
+}
+
static void SuperShuffleTaps( NoteData &inout, int iStartIndex, int iEndIndex )
{
/*
@@ -1935,14 +2071,28 @@ static void SuperShuffleTaps( NoteData &inout, int iStartIndex, int iEndIndex )
void NoteDataUtil::Turn( NoteData &inout, StepsType st, TrackMapping tt, int iStartIndex, int iEndIndex )
{
- int iTakeFromTrack[MAX_NOTE_TRACKS]; // New track "t" will take from old track iTakeFromTrack[t]
- GetTrackMapping( st, tt, inout.GetNumTracks(), iTakeFromTrack );
-
NoteData tempNoteData;
- tempNoteData.LoadTransformed( inout, inout.GetNumTracks(), iTakeFromTrack );
- if( tt == super_shuffle )
- SuperShuffleTaps( tempNoteData, iStartIndex, iEndIndex );
+ if( tt == hyper_shuffle )
+ {
+ // HyperShuffle doesn't require a column transformation beforehand, so there's no point
+ // in wasting time doing that.
+ tempNoteData.CopyAll(inout);
+ HyperShuffleNotes( tempNoteData, iStartIndex, iEndIndex );
+ }
+ else
+ {
+ // Calculate how the columns should be rearranged, and then do it.
+ int iTakeFromTrack[MAX_NOTE_TRACKS]; // New track "t" will take from old track iTakeFromTrack[t]
+ GetTrackMapping( st, tt, inout.GetNumTracks(), iTakeFromTrack );
+
+ tempNoteData.LoadTransformed( inout, inout.GetNumTracks(), iTakeFromTrack );
+
+ // SuperShuffle doesn't affect holds, it instead relies on a regular shuffle to do them first.
+ // Then it SuperShuffles everything else.
+ if( tt == super_shuffle )
+ SuperShuffleTaps( tempNoteData, iStartIndex, iEndIndex );
+ }
inout.CopyAll( tempNoteData );
inout.RevalidateATIs(vector(), false);
@@ -2785,6 +2935,7 @@ void NoteDataUtil::TransformNoteData( NoteData &nd, TimingData const& timing_dat
if( po.m_bTurns[PlayerOptions::TURN_SHUFFLE] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::shuffle, iStartIndex, iEndIndex );
if( po.m_bTurns[PlayerOptions::TURN_SOFT_SHUFFLE] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::soft_shuffle, iStartIndex, iEndIndex );
if( po.m_bTurns[PlayerOptions::TURN_SUPER_SHUFFLE] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::super_shuffle, iStartIndex, iEndIndex );
+ if( po.m_bTurns[PlayerOptions::TURN_HYPER_SHUFFLE] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::hyper_shuffle, iStartIndex, iEndIndex );
nd.RevalidateATIs(vector(), false);
}
diff --git a/src/NoteDataUtil.h b/src/NoteDataUtil.h
index a855ddd2a6..3b09aeb059 100644
--- a/src/NoteDataUtil.h
+++ b/src/NoteDataUtil.h
@@ -107,6 +107,7 @@ namespace NoteDataUtil
shuffle,
soft_shuffle,
super_shuffle,
+ hyper_shuffle,
stomp,
swap_up_down,
NUM_TRACK_MAPPINGS
diff --git a/src/PlayerOptions.cpp b/src/PlayerOptions.cpp
index 128fb03c9d..f57b503247 100644
--- a/src/PlayerOptions.cpp
+++ b/src/PlayerOptions.cpp
@@ -492,6 +492,7 @@ void PlayerOptions::GetMods( vector &AddTo, bool bForceNoteSkin ) const
if( m_bTurns[TURN_SHUFFLE] ) AddTo.push_back( "Shuffle" );
if( m_bTurns[TURN_SOFT_SHUFFLE] ) AddTo.push_back( "SoftShuffle" );
if( m_bTurns[TURN_SUPER_SHUFFLE] ) AddTo.push_back( "SuperShuffle" );
+ if( m_bTurns[TURN_HYPER_SHUFFLE] ) AddTo.push_back( "HyperShuffle" );
if( m_bTransforms[TRANSFORM_NOHOLDS] ) AddTo.push_back( "NoHolds" );
if( m_bTransforms[TRANSFORM_NOROLLS] ) AddTo.push_back( "NoRolls" );
@@ -1027,6 +1028,7 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut
else if( sBit == "shuffle" ) m_bTurns[TURN_SHUFFLE] = on;
else if( sBit == "softshuffle" ) m_bTurns[TURN_SOFT_SHUFFLE] = on;
else if( sBit == "supershuffle" ) m_bTurns[TURN_SUPER_SHUFFLE] = on;
+ else if( sBit == "hypershuffle" ) m_bTurns[TURN_HYPER_SHUFFLE] = on;
else if( sBit == "little" ) m_bTransforms[TRANSFORM_LITTLE] = on;
else if( sBit == "wide" ) m_bTransforms[TRANSFORM_WIDE] = on;
else if( sBit == "big" ) m_bTransforms[TRANSFORM_BIG] = on;
@@ -2009,6 +2011,7 @@ public:
BOOL_INTERFACE(Shuffle, Turns[PlayerOptions::TURN_SHUFFLE]);
BOOL_INTERFACE(SoftShuffle, Turns[PlayerOptions::TURN_SOFT_SHUFFLE]);
BOOL_INTERFACE(SuperShuffle, Turns[PlayerOptions::TURN_SUPER_SHUFFLE]);
+ BOOL_INTERFACE(HyperShuffle, Turns[PlayerOptions::TURN_HYPER_SHUFFLE]);
BOOL_INTERFACE(NoHolds, Transforms[PlayerOptions::TRANSFORM_NOHOLDS]);
BOOL_INTERFACE(NoRolls, Transforms[PlayerOptions::TRANSFORM_NOROLLS]);
BOOL_INTERFACE(NoMines, Transforms[PlayerOptions::TRANSFORM_NOMINES]);
@@ -2554,6 +2557,7 @@ public:
ADD_METHOD(Shuffle);
ADD_METHOD(SoftShuffle);
ADD_METHOD(SuperShuffle);
+ ADD_METHOD(HyperShuffle);
ADD_METHOD(NoHolds);
ADD_METHOD(NoRolls);
ADD_METHOD(NoMines);
diff --git a/src/PlayerOptions.h b/src/PlayerOptions.h
index 6a0dfd9ad5..7e8729ecbe 100644
--- a/src/PlayerOptions.h
+++ b/src/PlayerOptions.h
@@ -292,6 +292,7 @@ public:
TURN_SHUFFLE, /**< Some of the arrow columns are changed throughout the whole song. */
TURN_SOFT_SHUFFLE, /**< Only shuffle arrow columns on an axis of symmetry. */
TURN_SUPER_SHUFFLE, /**< Every arrow is placed on a random column. */
+ TURN_HYPER_SHUFFLE, /**< Every arrow is placed on a random column, but more fairly than SuperShuffle. */
NUM_TURNS
};
enum Transform {
diff --git a/src/ScreenEdit.cpp b/src/ScreenEdit.cpp
index 12b252c6c5..ba5f905039 100644
--- a/src/ScreenEdit.cpp
+++ b/src/ScreenEdit.cpp
@@ -842,7 +842,7 @@ static MenuDef g_AlterMenu(
EditMode_Practice, true, true, 0,
"4th","8th","12th","16th","24th","32nd","48th","64th","192nd"),
MenuRowDef(ScreenEdit::turn, "Turn", true,
- EditMode_Practice, true, true, 0, "Left","Right","Mirror","Backwards","Shuffle","SuperShuffle" ),
+ EditMode_Practice, true, true, 0, "Left","Right","Mirror","Backwards","Shuffle","SuperShuffle","HyperShuffle" ),
MenuRowDef(ScreenEdit::transform, "Transform", true,
EditMode_Practice, true, true, 0, "NoHolds","NoMines","Little","Wide",
"Big","Quick","Skippy","Mines","Echo","Stomp","Planted","Floored",
@@ -5167,6 +5167,7 @@ void ScreenEdit::HandleAlterMenuChoice(AlterMenuChoice c, const vector &ans
case turn_backwards: NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::backwards ); break;
case shuffle: NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::shuffle ); break;
case super_shuffle: NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::super_shuffle ); break;
+ case hyper_shuffle: NoteDataUtil::Turn( m_Clipboard, st, NoteDataUtil::hyper_shuffle ); break;
}
HandleAreaMenuChoice( paste_at_begin_marker, false );
diff --git a/src/ScreenEdit.h b/src/ScreenEdit.h
index 1872bc9ecf..758a585f4a 100644
--- a/src/ScreenEdit.h
+++ b/src/ScreenEdit.h
@@ -487,6 +487,7 @@ public:
turn_backwards, /**< Turn the notes as if you were facing away from the machine. */
shuffle, /**< Replace one column with another column. */
super_shuffle, /**< Replace each note individually. */
+ hyper_shuffle, /**< Replace each note individually but more fairly. */
NUM_TURN_TYPES
};
enum TransformType