diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml
index 2066bf875e..4fd6aa5f36 100644
--- a/Docs/Luadoc/Lua.xml
+++ b/Docs/Luadoc/Lua.xml
@@ -1455,6 +1455,7 @@
+
@@ -1466,6 +1467,7 @@
+
diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml
index 5dd55ca0d6..dede17b00c 100644
--- a/Docs/Luadoc/LuaDocumentation.xml
+++ b/Docs/Luadoc/LuaDocumentation.xml
@@ -4307,6 +4307,9 @@ save yourself some time, copy this for undocumented things:
Sets the NextScreen value to name.
+
+ Sets the PrevScreen value to name.
+
[02 Other.lua] Gets a string from the current Screen in the current language.
@@ -4322,6 +4325,9 @@ save yourself some time, copy this for undocumented things:
+
+ This should behave identically to the normal back button behavior. This function is for the pause menu to use when the player forfeits or restarts, so that a score isn't saved.
+
Returns true if a single has its NoteField centered.
diff --git a/Themes/default/BGAnimations/ScreenEvaluation overlay/default.lua b/Themes/default/BGAnimations/ScreenEvaluation overlay/default.lua
index 813de9833f..c0e3d065d9 100644
--- a/Themes/default/BGAnimations/ScreenEvaluation overlay/default.lua
+++ b/Themes/default/BGAnimations/ScreenEvaluation overlay/default.lua
@@ -65,4 +65,15 @@ t[#t+1] = Def.ActorFrame {
InitCommand=cmd(hide_if,not GAMESTATE:IsPlayerEnabled(PLAYER_2);x,WideScale(math.floor(SCREEN_CENTER_X*1.7)+8,math.floor(SCREEN_CENTER_X*1.5)+8);y,SCREEN_CENTER_Y-20);
CreateStats( PLAYER_2 );
};
+
+if gameplay_pause_count > 0 then
+ t[#t+1]= Def.BitmapText{
+ Font= "Common Normal",
+ Text= THEME:GetString("PauseMenu", "pause_count") .. ": " .. gameplay_pause_count,
+ InitCommand= function(self)
+ self:xy(_screen.cx, 375):diffuse(Color.White):zoom(.75)
+ end
+ }
+end
+
return t
diff --git a/Themes/default/BGAnimations/ScreenGameplay overlay.lua b/Themes/default/BGAnimations/ScreenGameplay overlay.lua
index 116f950fab..60d0d9f580 100644
--- a/Themes/default/BGAnimations/ScreenGameplay overlay.lua
+++ b/Themes/default/BGAnimations/ScreenGameplay overlay.lua
@@ -37,4 +37,5 @@ if GAMESTATE:GetCurrentCourse() then
end;
end;
t.InitCommand=cmd(SetUpdateFunction,UpdateTime);
+t[#t+1]= LoadActor(THEME:GetPathG("", "pause_menu"))
return t
diff --git a/Themes/default/Graphics/pause_menu.lua b/Themes/default/Graphics/pause_menu.lua
new file mode 100644
index 0000000000..452460c79c
--- /dev/null
+++ b/Themes/default/Graphics/pause_menu.lua
@@ -0,0 +1,197 @@
+gameplay_pause_count= 0
+
+local pause_buttons= {Start= true, Select= true, Back= true}
+local pause_double_tap_time= .5
+local pause_press_times= {}
+local screen_gameplay= false
+local menu_items= {[PLAYER_1]= {}, [PLAYER_2]= {}}
+local menu_frames= {}
+local menu_choices= {
+ "continue_playing",
+ "restart_song",
+ "forfeit_song",
+}
+if GAMESTATE:IsCourseMode() then
+ menu_choices= {
+ "continue_playing",
+ "skip_song",
+ "forfeit_course",
+ }
+end
+local menu_spacing= 32
+local menu_bg_width= _screen.w * .4
+local menu_text_width= _screen.w * .35
+local menu_x= {[PLAYER_1]= _screen.w*.25, [PLAYER_2]= _screen.w*.75}
+local menu_y= _screen.cy - (#menu_choices * .5 * menu_spacing)
+local current_menu_choice= {}
+local menu_is_showing= {}
+local enabled_players= {}
+
+local function create_menu_item(pn, x, y, item_name)
+ return Def.BitmapText{
+ Font= "Common Normal", Text= THEME:GetString("PauseMenu", item_name),
+ InitCommand= function(self)
+ self:xy(x, y)
+ table.insert(menu_items[pn], self)
+ self:playcommand("LoseFocus")
+ end,
+ LoseFocusCommand= function(self)
+ self:stopeffect():rotationz(0)
+ end,
+ GainFocusCommand= function(self)
+ self:wag():effectperiod(2):effectmagnitude(0, 0, 5)
+ end,
+ }
+end
+
+local function create_menu_frame(pn, x, y)
+ local frame= Def.ActorFrame{
+ InitCommand= function(self)
+ self:xy(x, y):playcommand("Hide")
+ menu_frames[pn]= self
+ end,
+ ShowCommand= function(self)
+ self:visible(true)
+ end,
+ HideCommand= function(self)
+ self:visible(false)
+ end,
+ Def.Quad{
+ InitCommand= function(self)
+ self:setsize(menu_bg_width, menu_spacing * (#menu_choices + 1))
+ :y(-menu_spacing):vertalign(top)
+ :diffuse{0, 0, 0, .25}
+ :playcommand("Hide")
+ end,
+ },
+ }
+ for i, choice in ipairs(menu_choices) do
+ frame[#frame+1]= create_menu_item(pn, 0, (i-1)*menu_spacing, choice)
+ end
+ return frame
+end
+
+local function backout(screen)
+ screen_gameplay:SetPrevScreenName(screen):begin_backing_out()
+end
+
+local function show_menu(pn)
+ menu_frames[pn]:playcommand("Show")
+ for i, item in ipairs(menu_items[pn]) do
+ item:playcommand("LoseFocus")
+ end
+ current_menu_choice[pn]= 1
+ menu_items[pn][current_menu_choice[pn]]:playcommand("GainFocus")
+ menu_is_showing[pn]= true
+end
+
+local function close_menu(pn)
+ menu_frames[pn]:playcommand("Hide")
+ menu_is_showing[pn]= false
+ local stay_paused= false
+ for pn, showing in pairs(menu_is_showing) do
+ if showing then
+ stay_paused= true
+ end
+ end
+ if not stay_paused then
+ screen_gameplay:PauseGame(false)
+ end
+end
+
+local choice_actions= {
+ continue_playing= function(pn)
+ close_menu(pn)
+ end,
+ restart_song= function(pn)
+ backout("ScreenStageInformation")
+ end,
+ forfeit_song= function(pn)
+ backout(SelectMusicOrCourse())
+ end,
+ skip_song= function(pn)
+ screen_gameplay:PostScreenMessage("SM_NotesEnded", 0)
+ end,
+ forfeit_course= function(pn)
+ backout(SelectMusicOrCourse())
+ end,
+}
+
+local menu_actions= {
+ Start= function(pn)
+ local choice_name= menu_choices[current_menu_choice[pn]]
+ if choice_actions[choice_name] then
+ choice_actions[choice_name](pn)
+ end
+ end,
+ Left= function(pn)
+ if current_menu_choice[pn] > 1 then
+ menu_items[pn][current_menu_choice[pn]]:playcommand("LoseFocus")
+ current_menu_choice[pn]= current_menu_choice[pn] - 1
+ menu_items[pn][current_menu_choice[pn]]:playcommand("GainFocus")
+ end
+ end,
+ Right= function(pn)
+ if current_menu_choice[pn] < #menu_choices then
+ menu_items[pn][current_menu_choice[pn]]:playcommand("LoseFocus")
+ current_menu_choice[pn]= current_menu_choice[pn] + 1
+ menu_items[pn][current_menu_choice[pn]]:playcommand("GainFocus")
+ end
+ end,
+}
+menu_actions.Up= menu_actions.Left
+menu_actions.Down= menu_actions.Right
+menu_actions.MenuLeft= menu_actions.Left
+menu_actions.MenuRight= menu_actions.Right
+menu_actions.MenuUp= menu_actions.Up
+menu_actions.MenuDown= menu_actions.Down
+
+local function input(event)
+ local pn= event.PlayerNumber
+ if not enabled_players[pn] then return end
+ local button= event.GameButton
+ if not button then return end
+ if event.type == "InputEventType_Release" then return end
+ local is_paused= screen_gameplay:IsPaused()
+ if is_paused then
+ if menu_is_showing[pn] then
+ if menu_actions[button] then
+ menu_actions[button](pn)
+ return true
+ end
+ else
+ if pause_buttons[button] then
+ show_menu(pn)
+ return true
+ end
+ end
+ else
+ if event.type ~= "InputEventType_FirstPress" then return end
+ if pause_buttons[button] then
+ if pause_press_times[pn] and
+ GetTimeSinceStart() - pause_press_times[pn] <= pause_double_tap_time then
+ gameplay_pause_count= gameplay_pause_count + 1
+ screen_gameplay:PauseGame(true)
+ show_menu(pn)
+ else
+ pause_press_times[pn]= GetTimeSinceStart()
+ end
+ else
+ pause_press_times[pn]= nil
+ end
+ end
+end
+
+local frame= Def.ActorFrame{
+ OnCommand= function(self)
+ screen_gameplay= SCREENMAN:GetTopScreen()
+ screen_gameplay:AddInputCallback(input)
+ end,
+}
+
+for i, pn in ipairs(GAMESTATE:GetEnabledPlayers()) do
+ enabled_players[pn]= true
+ frame[#frame+1]= create_menu_frame(pn, menu_x[pn], menu_y)
+end
+
+return frame
diff --git a/Themes/default/Languages/en.ini b/Themes/default/Languages/en.ini
index 4ff4543f29..9d09ff9bf9 100644
--- a/Themes/default/Languages/en.ini
+++ b/Themes/default/Languages/en.ini
@@ -85,6 +85,14 @@ HelpText=
[ScreenGameplay]
HelpText=
+[PauseMenu]
+continue_playing=Continue Playing
+forfeit_course=Forfeit Course
+forfeit_song=Forfeit Song
+pause_count=Pause Count
+restart_song=Restart Song
+skip_song=Skip Song
+
[ScreenHeartEntry]
Enter Heart Rate=Enter Heart Rate
Song Length=Song Length
diff --git a/Themes/default/metrics.ini b/Themes/default/metrics.ini
index 51a71e6e8d..17f42c5fba 100644
--- a/Themes/default/metrics.ini
+++ b/Themes/default/metrics.ini
@@ -1608,7 +1608,13 @@ ActiveAttackListP2Y=
ActiveAttackListP2OnCommand=visible,false
ActiveAttackListP2OffCommand=
#
-
+# Disable the normal start and select button actions to enable a custom pause
+# menu
+GivingUpGoesToPrevScreen=false
+UnpauseWithStart=false
+StartGivesUp=false
+BackGivesUp=false
+SelectSkipsSong=false
[ScreenGameplayShared]
diff --git a/src/Screen.cpp b/src/Screen.cpp
index 6558a6fb16..c3910009c5 100644
--- a/src/Screen.cpp
+++ b/src/Screen.cpp
@@ -273,6 +273,11 @@ void Screen::SetNextScreenName(RString const& name)
m_sNextScreen= name;
}
+void Screen::SetPrevScreenName(RString const& name)
+{
+ m_sPrevScreen= name;
+}
+
RString Screen::GetPrevScreen() const
{
if( !m_sPrevScreen.empty() )
@@ -411,6 +416,7 @@ public:
static int GetNextScreenName( T* p, lua_State *L ) { lua_pushstring(L, p->GetNextScreenName() ); return 1; }
static int SetNextScreenName( T* p, lua_State *L ) { p->SetNextScreenName(SArg(1)); COMMON_RETURN_SELF; }
static int GetPrevScreenName( T* p, lua_State *L ) { lua_pushstring(L, p->GetPrevScreen() ); return 1; }
+ static int SetPrevScreenName( T* p, lua_State *L ) { p->SetPrevScreenName(SArg(1)); COMMON_RETURN_SELF; }
static int lockinput( T* p, lua_State *L ) { p->SetLockInputSecs(FArg(1)); COMMON_RETURN_SELF; }
DEFINE_METHOD( GetScreenType, GetScreenType() )
@@ -447,6 +453,7 @@ public:
ADD_METHOD( GetNextScreenName );
ADD_METHOD( SetNextScreenName );
ADD_METHOD( GetPrevScreenName );
+ ADD_METHOD( SetPrevScreenName );
ADD_METHOD( PostScreenMessage );
ADD_METHOD( lockinput );
ADD_METHOD( GetScreenType );
diff --git a/src/Screen.h b/src/Screen.h
index 0bea3cd95b..3ed00bdf50 100644
--- a/src/Screen.h
+++ b/src/Screen.h
@@ -124,6 +124,7 @@ public:
RString GetNextScreenName() const;
RString GetPrevScreen() const;
void SetNextScreenName(RString const& name);
+ void SetPrevScreenName(RString const& name);
bool PassInputToLua(const InputEventPlus& input);
void AddInputCallbackFromStack(lua_State* L);
diff --git a/src/ScreenGameplay.cpp b/src/ScreenGameplay.cpp
index 950d379403..118fe29bec 100644
--- a/src/ScreenGameplay.cpp
+++ b/src/ScreenGameplay.cpp
@@ -1959,9 +1959,7 @@ void ScreenGameplay::Update( float fDeltaTime )
pi->GetPlayerStageStats()->m_bFailed |= bAllHumanHaveBigMissCombo;
pi->GetPlayerStageStats()->m_bDisqualified |= bGiveUpTimerFired; // Don't disqualify if failing for miss combo. The player should still be eligable for a high score on courses.
}
-
ResetGiveUpTimers(false);
-
if(GIVING_UP_GOES_TO_PREV_SCREEN && !m_skipped_song)
{
BeginBackingOutFromGameplay();
@@ -3267,6 +3265,11 @@ public:
FLOAT_TABLE_INTERFACE(HasteAddAmounts, HasteAddAmounts, AddAmountsValid);
FLOAT_NO_SPEED_INTERFACE(HasteTimeBetweenUpdates, HasteTimeBetweenUpdates, (v > 0));
FLOAT_NO_SPEED_INTERFACE(HasteLifeSwitchPoint, HasteLifeSwitchPoint, (v >= 0 && v <= 1));
+ static int begin_backing_out(T* p, lua_State* L)
+ {
+ p->BeginBackingOutFromGameplay();
+ COMMON_RETURN_SELF;
+ }
static int GetTrueBPS(T* p, lua_State* L)
{
PlayerNumber pn= Enum::Check(L, 1);
@@ -3293,6 +3296,7 @@ public:
ADD_METHOD( HasteAddAmounts );
ADD_METHOD( HasteTimeBetweenUpdates );
ADD_METHOD( HasteLifeSwitchPoint );
+ ADD_METHOD(begin_backing_out);
ADD_METHOD( GetTrueBPS );
}
};
diff --git a/src/ScreenGameplay.h b/src/ScreenGameplay.h
index 799d78786b..4c78bc3f33 100644
--- a/src/ScreenGameplay.h
+++ b/src/ScreenGameplay.h
@@ -176,6 +176,7 @@ public:
void FailFadeRemovePlayer(PlayerInfo* pi);
void FailFadeRemovePlayer(PlayerNumber pn);
+ void BeginBackingOutFromGameplay();
vector m_HasteTurningPoints; // Values at which the meaning of GAMESTATE->m_fHasteRate changes.
vector m_HasteAddAmounts; // Amounts that are added to speed depending on what turning point has been passed.
@@ -225,7 +226,6 @@ protected:
void PlayAnnouncer( const RString &type, float fSeconds ) { PlayAnnouncer(type, fSeconds, &m_fTimeSinceLastDancingComment); }
void UpdateLights();
void SendCrossedMessages();
- void BeginBackingOutFromGameplay();
void PlayTicks();
void UpdateSongPosition( float fDeltaTime );