diff --git a/Docs/Changelog_sm5.txt b/Docs/Changelog_sm5.txt
index a1c06e7e9f..d86e080bbc 100644
--- a/Docs/Changelog_sm5.txt
+++ b/Docs/Changelog_sm5.txt
@@ -4,6 +4,11 @@ The StepMania 5 Changelog covers all post-sm-ssc changes. For a list of changes
from StepMania 4 alpha 5 to sm-ssc v1.2.5, see Changelog_sm-ssc.txt.
________________________________________________________________________________
+2014/11/30
+----------
+* [NoteField] New functions added for controlling the receptor and ghost arrow
+ (explosion) flashes. [kyzentun]
+
2014/11/15
----------
* [Preferences] Default Fail Type preference mechanism changed internally again.
diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml
index ab33c6e73d..e7cd8b1cca 100644
--- a/Docs/Luadoc/Lua.xml
+++ b/Docs/Luadoc/Lua.xml
@@ -964,6 +964,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml
index c6209eb1d4..af966d38c1 100644
--- a/Docs/Luadoc/LuaDocumentation.xml
+++ b/Docs/Luadoc/LuaDocumentation.xml
@@ -2941,6 +2941,45 @@ save yourself some time, copy this for undocumented things:
Returns a table of noteskin names for the current gametype.
+
+
+ Makes the NoteField act as if a hold note was hit in the column, with the given score and bright setting.
+ The callback for DidHoldNote will not be called.
+
+
+ Makes the NoteField act as if a tap note was hit in the column, with the given score and bright setting.
+ The callback for DidTapNote will not be called.
+
+
+ Same as SetDidTapNoteCallback, but for hold notes. Uses HoldNoteScore instead of TapNoteScore.
+
+
+ Sets the function that the NoteField will call whenever a tap note is hit.
+ The callback function is passed the column, the TapNoteScore, and whether the explosion will be bright.
+ The callback function can return changed values for the NoteField to use instead of the ones that were passed.
+ Pass nil instead of a function to clear the callback.
+
+
+ Makes the NoteField act as if a press occurred in the column.
+ The callback for SetPressed will not be called.
+
+
+ Sets the function that the NoteField will call whenever a press occurs.
+ The callback function is passed the column for the press.
+ The callback function can return changed values for the NoteField to use instead of the ones that were passed.
+ Pass nil instead of a function to clear the callback.
+
+
+ Sets the function that the NoteField will call whenever a step occurs.
+ The callback function is passed the column and the TapNoteScore for the step.
+ The callback function can return changed values for the NoteField to use instead of the ones that were passed.
+ Pass nil instead of a function to clear the callback.
+
+
+ Makes the NoteField act as if a step occurred in the column with the given score.
+ The callback for Step will not be called.
+
+
Returns true if the first item in the row goes down.
diff --git a/src/NoteField.cpp b/src/NoteField.cpp
index a3e438c19f..ced4fb79ed 100644
--- a/src/NoteField.cpp
+++ b/src/NoteField.cpp
@@ -67,6 +67,11 @@ NoteField::NoteField()
m_iBeginMarker = m_iEndMarker = -1;
m_fPercentFadeToFail = -1;
+
+ m_StepCallback.SetFromNil();
+ m_SetPressedCallback.SetFromNil();
+ m_DidTapNoteCallback.SetFromNil();
+ m_DidHoldNoteCallback.SetFromNil();
}
NoteField::~NoteField()
@@ -1375,10 +1380,111 @@ void NoteField::FadeToFail()
m_fPercentFadeToFail = max( 0.0f, m_fPercentFadeToFail ); // this will slowly increase every Update()
// don't fade all over again if this is called twice
}
-void NoteField::Step( int iCol, TapNoteScore score ) { m_pCurDisplay->m_ReceptorArrowRow.Step( iCol, score ); }
-void NoteField::SetPressed( int iCol ) { m_pCurDisplay->m_ReceptorArrowRow.SetPressed( iCol ); }
-void NoteField::DidTapNote( int iCol, TapNoteScore score, bool bBright ) { m_pCurDisplay->m_GhostArrowRow.DidTapNote( iCol, score, bBright ); }
-void NoteField::DidHoldNote( int iCol, HoldNoteScore score, bool bBright ) { m_pCurDisplay->m_GhostArrowRow.DidHoldNote( iCol, score, bBright ); }
+
+// A few functions and macros to take care of processing the callback
+// return values, since the code would be identical in all of them. -Kyz
+
+#define OPEN_CALLBACK_BLOCK(member_name) \
+ if(!from_lua && !member_name.IsNil()) \
+ { \
+ Lua* L= LUA->Get(); \
+ member_name.PushSelf(L);
+
+#define OPEN_RUN_BLOCK(arg_count) \
+ RString error= "Error running callback: "; \
+ if(LuaHelpers::RunScriptOnStack(L, error, arg_count, arg_count, true)) \
+ {
+
+#define CLOSE_RUN_AND_CALLBACK_BLOCKS } lua_settop(L, 0); LUA->Release(L); }
+
+static void get_returned_column(Lua* L, int index, int& col)
+{
+ if(lua_isnumber(L, index))
+ {
+ // 1-indexed columns in lua
+ int tmpcol= lua_tonumber(L, index) - 1;
+ if(tmpcol < 0 || tmpcol >= GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer)
+ {
+ LuaHelpers::ReportScriptErrorFmt(
+ "Column returned by callback must be between 1 and %d "
+ "(GAMESTATE:GetCurrentStyle():ColumnsPerPlayer()).",
+ GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer);
+ }
+ else
+ {
+ col= tmpcol;
+ }
+ }
+}
+
+// Templated so it can be used for TNS and HNS. -Kyz
+template static void get_returned_score(Lua* L, int index, T& score)
+{
+ T maybe_score= Enum::Check(L, index, true, true);
+ if(maybe_score != EnumTraits::Invalid)
+ {
+ score= maybe_score;
+ }
+}
+
+static void get_returned_bright(Lua* L, int index, bool& bright)
+{
+ if(lua_isboolean(L, index))
+ {
+ bright= lua_toboolean(L, index);
+ }
+}
+
+void NoteField::Step(int col, TapNoteScore score, bool from_lua)
+{
+ OPEN_CALLBACK_BLOCK(m_StepCallback);
+ lua_pushnumber(L, col);
+ Enum::Push(L, score);
+ OPEN_RUN_BLOCK(2);
+ get_returned_column(L, 1, col);
+ get_returned_score(L, 2, score);
+ CLOSE_RUN_AND_CALLBACK_BLOCKS;
+ m_pCurDisplay->m_ReceptorArrowRow.Step(col, score);
+}
+void NoteField::SetPressed(int col, bool from_lua)
+{
+ OPEN_CALLBACK_BLOCK(m_SetPressedCallback);
+ lua_pushnumber(L, col);
+ OPEN_RUN_BLOCK(1);
+ get_returned_column(L, 1, col);
+ CLOSE_RUN_AND_CALLBACK_BLOCKS;
+ m_pCurDisplay->m_ReceptorArrowRow.SetPressed(col);
+}
+void NoteField::DidTapNote(int col, TapNoteScore score, bool bright, bool from_lua)
+{
+ OPEN_CALLBACK_BLOCK(m_DidTapNoteCallback);
+ lua_pushnumber(L, col);
+ Enum::Push(L, score);
+ lua_pushboolean(L, bright);
+ OPEN_RUN_BLOCK(3);
+ get_returned_column(L, 1, col);
+ get_returned_score(L, 2, score);
+ get_returned_bright(L, 3, bright);
+ CLOSE_RUN_AND_CALLBACK_BLOCKS;
+ m_pCurDisplay->m_GhostArrowRow.DidTapNote(col, score, bright);
+}
+void NoteField::DidHoldNote(int col, HoldNoteScore score, bool bright, bool from_lua)
+{
+ OPEN_CALLBACK_BLOCK(m_DidHoldNoteCallback);
+ lua_pushnumber(L, col);
+ Enum::Push(L, score);
+ lua_pushboolean(L, bright);
+ OPEN_RUN_BLOCK(3);
+ get_returned_column(L, 1, col);
+ get_returned_score(L, 2, score);
+ get_returned_bright(L, 3, bright);
+ CLOSE_RUN_AND_CALLBACK_BLOCKS;
+ m_pCurDisplay->m_GhostArrowRow.DidHoldNote(col, score, bright);
+}
+
+#undef OPEN_CALLBACK_BLOCK
+#undef OPEN_RUN_BLOCK
+#undef CLOSE_RUN_AND_CALLBACK_BLOCKS
void NoteField::HandleMessage( const Message &msg )
{
@@ -1391,6 +1497,98 @@ void NoteField::HandleMessage( const Message &msg )
ActorFrame::HandleMessage( msg );
}
+// lua start
+#include "LuaBinding.h"
+
+/** @brief Allow Lua to have access to the Notefield. */
+class LunaNoteField: public Luna
+{
+public:
+#define SET_CALLBACK_GENERIC(callback_name, member_name) \
+ static int callback_name(T* p, lua_State* L) \
+ { \
+ if(lua_isnoneornil(L, 1)) \
+ { \
+ p->member_name.SetFromNil(); \
+ } \
+ else if(lua_isfunction(L, 1)) \
+ { \
+ p->member_name.SetFromStack(L); \
+ } \
+ else \
+ { \
+ luaL_error(L, #callback_name "Callback argument must be nil (to clear the callback) or a function (to set the callback)."); \
+ } \
+ return 0; \
+ }
+ SET_CALLBACK_GENERIC(SetStepCallback, m_StepCallback);
+ SET_CALLBACK_GENERIC(SetSetPressedCallback, m_SetPressedCallback);
+ SET_CALLBACK_GENERIC(SetDidTapNoteCallback, m_DidTapNoteCallback);
+ SET_CALLBACK_GENERIC(SetDidHoldNoteCallback, m_DidHoldNoteCallback);
+#undef SET_CALLBACK_GENERIC
+
+ static int check_column(lua_State* L, int index)
+ {
+ // 1-indexed columns in lua
+ int col= IArg(1)-1;
+ if(col < 0 || col >= GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer)
+ {
+ luaL_error(L, "Column must be between 1 and %d "
+ "(GAMESTATE:GetCurrentStyle():ColumnsPerPlayer()).",
+ GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer);
+ }
+ return col;
+ }
+
+ static int Step(T* p, lua_State* L)
+ {
+ int col= check_column(L, 1);
+ TapNoteScore tns= Enum::Check(L, 2);
+ p->Step(col, tns, true);
+ return 0;
+ }
+
+ static int SetPressed(T* p, lua_State* L)
+ {
+ int col= check_column(L, 1);
+ p->SetPressed(col, true);
+ return 0;
+ }
+
+ static int DidTapNote(T* p, lua_State* L)
+ {
+ int col= check_column(L, 1);
+ TapNoteScore tns= Enum::Check(L, 2);
+ bool bright= BArg(3);
+ p->DidTapNote(col, tns, bright, true);
+ return 0;
+ }
+
+ static int DidHoldNote(T* p, lua_State* L)
+ {
+ int col= check_column(L, 1);
+ HoldNoteScore hns= Enum::Check(L, 2);
+ bool bright= BArg(3);
+ p->DidHoldNote(col, hns, bright, true);
+ return 0;
+ }
+
+ LunaNoteField()
+ {
+ ADD_METHOD(SetStepCallback);
+ ADD_METHOD(SetSetPressedCallback);
+ ADD_METHOD(SetDidTapNoteCallback);
+ ADD_METHOD(SetDidHoldNoteCallback);
+ ADD_METHOD(Step);
+ ADD_METHOD(SetPressed);
+ ADD_METHOD(DidTapNote);
+ ADD_METHOD(DidHoldNote);
+ }
+};
+
+LUA_REGISTER_DERIVED_CLASS(NoteField, ActorFrame)
+// lua end
+
/*
* (c) 2001-2004 Chris Danford
* All rights reserved.
diff --git a/src/NoteField.h b/src/NoteField.h
index f37023b2db..cfdf24be04 100644
--- a/src/NoteField.h
+++ b/src/NoteField.h
@@ -36,10 +36,20 @@ public:
void CacheAllUsedNoteSkins();
void FadeToFail();
- void Step( int iCol, TapNoteScore score );
- void SetPressed( int iCol );
- void DidTapNote( int iCol, TapNoteScore score, bool bBright );
- void DidHoldNote( int iCol, HoldNoteScore score, bool bBright );
+ void Step(int col, TapNoteScore score, bool from_lua= false);
+ void SetPressed(int col, bool from_lua= false);
+ void DidTapNote(int col, TapNoteScore score, bool bright, bool from_lua= false);
+ void DidHoldNote(int col, HoldNoteScore score, bool bright, bool from_lua= false);
+
+ virtual void PushSelf( lua_State *L );
+
+ // Allows the theme to modify the parameters to Step, SetPressed,
+ // DidTapNote, and DidHoldNote before they pass on to the ghost arrows or
+ // receptors. -Kyz
+ LuaReference m_StepCallback;
+ LuaReference m_SetPressedCallback;
+ LuaReference m_DidTapNoteCallback;
+ LuaReference m_DidHoldNoteCallback;
const PlayerState *GetPlayerState() const { return m_pPlayerState; }