diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml
index e833ab7e51..b5759dae61 100644
--- a/Docs/Luadoc/Lua.xml
+++ b/Docs/Luadoc/Lua.xml
@@ -1161,6 +1161,9 @@
+
+
+
diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml
index 04a394ac5a..39bbac6690 100644
--- a/Docs/Luadoc/LuaDocumentation.xml
+++ b/Docs/Luadoc/LuaDocumentation.xml
@@ -3517,6 +3517,15 @@ save yourself some time, copy this for undocumented things:
If the optional second argument is passed, sets the speed at which the transition occurs.
MMods are not tweenable or settable on ScreenGameplay. Use XMods if you need such an effect.
+
+ Use 1-16 in place of 'n' to move a specific column.
+
+
+ Use 1-16 in place of 'n' to move a specific column.
+
+
+ Use 1-16 in place of 'n' to move a specific column.
+
diff --git a/src/ArrowEffects.cpp b/src/ArrowEffects.cpp
index e5170e0576..c5313daa1d 100644
--- a/src/ArrowEffects.cpp
+++ b/src/ArrowEffects.cpp
@@ -675,6 +675,33 @@ float ArrowEffects::ReceptorGetRotationY( const PlayerState* pPlayerState )
return fRotation;
}
+float ArrowEffects::GetMoveX(int iCol)
+{
+ const float* fMoves = curr_options->m_fMovesX;
+ float f = 0;
+ if( fMoves[iCol] != 0 )
+ f += ARROW_SIZE * fMoves[iCol];
+ return f;
+}
+
+float ArrowEffects::GetMoveY(int iCol)
+{
+ const float* fMoves = curr_options->m_fMovesY;
+ float f = 0;
+ if( fMoves[iCol] != 0 )
+ f += ARROW_SIZE * fMoves[iCol];
+ return f;
+}
+
+float ArrowEffects::GetMoveZ(int iCol)
+{
+ const float* fMoves = curr_options->m_fMovesZ;
+ float f = 0;
+ if( fMoves[iCol] != 0 )
+ f += ARROW_SIZE * fMoves[iCol];
+ return f;
+}
+
#define CENTER_LINE_Y 160 // from fYOffset == 0
#define FADE_DIST_Y 40
diff --git a/src/ArrowEffects.h b/src/ArrowEffects.h
index 9568f19846..bb95e54cc1 100644
--- a/src/ArrowEffects.h
+++ b/src/ArrowEffects.h
@@ -29,9 +29,9 @@ public:
static void GetXYZPos(const PlayerState* player_state, int col, float y_offset, float y_reverse_offset, RageVector3& ret, bool with_reverse= true)
{
- ret.x= GetXPos(player_state, col, y_offset);
- ret.y= GetYPos(col, y_offset, y_reverse_offset, with_reverse);
- ret.z= GetZPos(col, y_offset);
+ ret.x= GetMoveX(col) + GetXPos(player_state, col, y_offset);
+ ret.y= GetMoveY(col) + GetYPos(col, y_offset, y_reverse_offset, with_reverse);
+ ret.z= GetMoveZ(col) + GetZPos(col, y_offset);
}
/**
@@ -62,6 +62,10 @@ public:
static float ReceptorGetRotationX( const PlayerState* pPlayerState);
static float ReceptorGetRotationY( const PlayerState* pPlayerState);
+
+ static float GetMoveX(int iCol);
+ static float GetMoveY(int iCol);
+ static float GetMoveZ(int iCol);
// fXPos is a horizontal position in pixels relative to the center of the field.
// This depends on the column of the arrow and possibly the Arrow effect and
diff --git a/src/NoteDisplay.cpp b/src/NoteDisplay.cpp
index 7f97a7484d..6623895073 100644
--- a/src/NoteDisplay.cpp
+++ b/src/NoteDisplay.cpp
@@ -884,10 +884,10 @@ void NoteDisplay::DrawHoldPart(vector &vpSpr,
switch(column_args.pos_handler->m_spline_mode)
{
case NCSM_Disabled:
- ae_pos.y= fY;
+ ae_pos.y= fY + ArrowEffects::GetMoveY(column_args.column);
break;
case NCSM_Offset:
- ae_pos.y= fY;
+ ae_pos.y= fY + ArrowEffects::GetMoveY(column_args.column);
column_args.pos_handler->EvalDerivForBeat(column_args.song_beat, cur_beat, sp_pos_forward);
RageVec3Normalize(&sp_pos_forward, &sp_pos_forward);
break;
diff --git a/src/PlayerOptions.cpp b/src/PlayerOptions.cpp
index 35bc19e8f6..859ac76c58 100644
--- a/src/PlayerOptions.cpp
+++ b/src/PlayerOptions.cpp
@@ -76,6 +76,10 @@ void PlayerOptions::Init()
ZERO( m_bTransforms );
m_bMuteOnError = false;
m_sNoteSkin = "";
+ ZERO( m_fMovesX ); ONE( m_SpeedfMovesX );
+ ZERO( m_fMovesY ); ONE( m_SpeedfMovesY );
+ ZERO( m_fMovesZ ); ONE( m_SpeedfMovesZ );
+
}
void PlayerOptions::Approach( const PlayerOptions& other, float fDeltaSeconds )
@@ -110,6 +114,12 @@ void PlayerOptions::Approach( const PlayerOptions& other, float fDeltaSeconds )
APPROACH( fSkew );
APPROACH( fPassmark );
APPROACH( fRandomSpeed );
+ for( int i=0; i<16; i++)
+ APPROACH( fMovesX[i] );
+ for( int i=0; i<16; i++)
+ APPROACH( fMovesY[i] );
+ for( int i=0; i<16; i++)
+ APPROACH( fMovesZ[i] );
DO_COPY( m_bSetScrollSpeed );
for( int i=0; i &AddTo, bool bForceNoteSkin ) const
AddPart( AddTo, m_fEffects[EFFECT_XMODE], "XMode" );
AddPart( AddTo, m_fEffects[EFFECT_TWIRL], "Twirl" );
AddPart( AddTo, m_fEffects[EFFECT_ROLL], "Roll" );
+
+ for( int i=0; i<16; i++)
+ {
+ RString s = ssprintf( "MoveX%d", i+1 );
+
+ AddPart( AddTo, m_fMovesX[i], s );
+ s = ssprintf( "MoveY%d", i+1 );
+ AddPart( AddTo, m_fMovesY[i], s );
+ s = ssprintf( "MoveZ%d", i+1 );
+ AddPart( AddTo, m_fMovesZ[i], s );
+ }
AddPart( AddTo, m_fAppearances[APPEARANCE_HIDDEN], "Hidden" );
AddPart( AddTo, m_fAppearances[APPEARANCE_HIDDEN_OFFSET], "HiddenOffset" );
@@ -565,6 +586,20 @@ bool PlayerOptions::FromOneModString( const RString &sOneMod, RString &sErrorOut
}
else if( sBit == "muteonerror" ) m_bMuteOnError = on;
else if( sBit == "random" ) ChooseRandomModifiers();
+
+ else if( sBit.find("move") != sBit.npos)
+ {
+ for (int i=0; i<16; i++)
+ {
+ RString s = ssprintf( "movex%d", i+1 );
+ if( sBit == s) SET_FLOAT( fMovesX[i] )
+ s = ssprintf( "movey%d", i+1 );
+ if( sBit == s) SET_FLOAT( fMovesY[i] )
+ s = ssprintf( "movez%d", i+1 );
+ if( sBit == s) SET_FLOAT( fMovesZ[i] )
+ }
+ }
+
// deprecated mods/left in for compatibility
else if( sBit == "converge" ) SET_FLOAT( fScrolls[SCROLL_CENTERED] )
// end of the list
@@ -811,6 +846,12 @@ bool PlayerOptions::operator==( const PlayerOptions &other ) const
COMPARE(m_bTurns[i]);
for( int i = 0; i < PlayerOptions::NUM_TRANSFORMS; ++i )
COMPARE(m_bTransforms[i]);
+ for( int i = 0; i < 16; ++i )
+ COMPARE(m_fMovesX[i]);
+ for( int i = 0; i < 16; ++i )
+ COMPARE(m_fMovesY[i]);
+ for( int i = 0; i < 16; ++i )
+ COMPARE(m_fMovesZ[i]);
#undef COMPARE
return true;
}
@@ -869,6 +910,18 @@ PlayerOptions& PlayerOptions::operator=(PlayerOptions const& other)
{
CPY(m_bTransforms[i]);
}
+ for( int i = 0; i < 16; ++i )
+ {
+ CPY(m_fMovesX[i]);
+ }
+ for( int i = 0; i < 16; ++i )
+ {
+ CPY(m_fMovesY[i]);
+ }
+ for( int i = 0; i < 16; ++i )
+ {
+ CPY(m_fMovesZ[i]);
+ }
#undef CPY
#undef CPY_SPEED
return *this;
@@ -1144,6 +1197,11 @@ public:
FLOAT_INTERFACE(Tilt, PerspectiveTilt, true);
FLOAT_INTERFACE(Passmark, Passmark, true); // Passmark is not sanity checked to the [0, 1] range because LifeMeterBar::IsFailing is the only thing that uses it, and it's used in a <= test. Any theme passing a value outside the [0, 1] range probably expects the result they get. -Kyz
FLOAT_INTERFACE(RandomSpeed, RandomSpeed, true);
+
+ MULTICOL_FLOAT_INTERFACE(MoveX, MovesX, true);
+ MULTICOL_FLOAT_INTERFACE(MoveY, MovesY, true);
+ MULTICOL_FLOAT_INTERFACE(MoveZ, MovesZ, true);
+
BOOL_INTERFACE(TurnNone, Turns[PlayerOptions::TURN_NONE]);
BOOL_INTERFACE(Mirror, Turns[PlayerOptions::TURN_MIRROR]);
BOOL_INTERFACE(Backwards, Turns[PlayerOptions::TURN_BACKWARDS]);
@@ -1567,6 +1625,11 @@ public:
ADD_METHOD(NoQuads);
ADD_METHOD(NoStretch);
ADD_METHOD(MuteOnError);
+
+ ADD_MULTICOL_METHOD(MoveX);
+ ADD_MULTICOL_METHOD(MoveY);
+ ADD_MULTICOL_METHOD(MoveZ);
+
ADD_METHOD(NoteSkin);
ADD_METHOD(FailSetting);
diff --git a/src/PlayerOptions.h b/src/PlayerOptions.h
index cd5ff88615..63074919d1 100644
--- a/src/PlayerOptions.h
+++ b/src/PlayerOptions.h
@@ -71,6 +71,9 @@ public:
ZERO( m_fAppearances ); ONE( m_SpeedfAppearances );
ZERO( m_fScrolls ); ONE( m_SpeedfScrolls );
ZERO( m_bTurns ); ZERO( m_bTransforms );
+ ZERO( m_fMovesX ); ONE( m_SpeedfMovesX );
+ ZERO( m_fMovesY ); ONE( m_SpeedfMovesY );
+ ZERO( m_fMovesZ ); ONE( m_SpeedfMovesZ );
};
void Init();
void Approach( const PlayerOptions& other, float fDeltaSeconds );
@@ -219,6 +222,10 @@ public:
float m_fPassmark, m_SpeedfPassmark;
float m_fRandomSpeed, m_SpeedfRandomSpeed;
+ /* The maximum column number is 16.*/
+ float m_fMovesX[16], m_SpeedfMovesX[16];
+ float m_fMovesY[16], m_SpeedfMovesY[16];
+ float m_fMovesZ[16], m_SpeedfMovesZ[16];
bool m_bTurns[NUM_TURNS];
bool m_bTransforms[NUM_TRANSFORMS];
@@ -257,6 +264,41 @@ public:
bool IsEasierForCourseAndTrail( Course* pCourse, Trail* pTrail ) const;
};
+#define ADD_MULTICOL_METHOD( method_name) \
+ ADD_METHOD( method_name##1 ); \
+ ADD_METHOD( method_name##2 ); \
+ ADD_METHOD( method_name##3 ); \
+ ADD_METHOD( method_name##4 ); \
+ ADD_METHOD( method_name##5 ); \
+ ADD_METHOD( method_name##6 ); \
+ ADD_METHOD( method_name##7 ); \
+ ADD_METHOD( method_name##8 ); \
+ ADD_METHOD( method_name##9 ); \
+ ADD_METHOD( method_name##10 ); \
+ ADD_METHOD( method_name##11 ); \
+ ADD_METHOD( method_name##12 ); \
+ ADD_METHOD( method_name##13 ); \
+ ADD_METHOD( method_name##14 ); \
+ ADD_METHOD( method_name##15 ); \
+ ADD_METHOD( method_name##16 );
+#define MULTICOL_FLOAT_INTERFACE(func_name, member, valid) \
+ FLOAT_INTERFACE(func_name##1, member[0], valid); \
+ FLOAT_INTERFACE(func_name##2, member[1], valid); \
+ FLOAT_INTERFACE(func_name##3, member[2], valid); \
+ FLOAT_INTERFACE(func_name##4, member[3], valid); \
+ FLOAT_INTERFACE(func_name##5, member[4], valid); \
+ FLOAT_INTERFACE(func_name##6, member[5], valid); \
+ FLOAT_INTERFACE(func_name##7, member[6], valid); \
+ FLOAT_INTERFACE(func_name##8, member[7], valid); \
+ FLOAT_INTERFACE(func_name##9, member[8], valid); \
+ FLOAT_INTERFACE(func_name##10, member[9], valid); \
+ FLOAT_INTERFACE(func_name##11, member[10], valid); \
+ FLOAT_INTERFACE(func_name##12, member[11], valid); \
+ FLOAT_INTERFACE(func_name##13, member[12], valid); \
+ FLOAT_INTERFACE(func_name##14, member[13], valid); \
+ FLOAT_INTERFACE(func_name##15, member[14], valid); \
+ FLOAT_INTERFACE(func_name##16, member[15], valid);
+
#endif
/*