diff --git a/Themes/_fallback/Scripts/02 Branches.lua b/Themes/_fallback/Scripts/02 Branches.lua index 81f19ec563..0732a42da1 100644 --- a/Themes/_fallback/Scripts/02 Branches.lua +++ b/Themes/_fallback/Scripts/02 Branches.lua @@ -182,8 +182,7 @@ Branch = { GameplayScreen = function() return IsRoutine() and "ScreenGameplayShared" or "ScreenGameplay" end, - AfterGameplay = function() - -- pick an evaluation screen based on settings. + EvaluationScreen= function() if IsNetSMOnline() then return "ScreenNetEvaluation" else @@ -191,6 +190,27 @@ Branch = { return "ScreenEvaluationNormal" end end, + AfterGameplay = function() + -- pick an evaluation screen based on settings. + if THEME:GetMetric("ScreenHeartEntry", "HeartEntryEnabled") then + local go_to_heart= false + for i, pn in ipairs(GAMESTATE:GetEnabledPlayers()) do + local profile= PROFILEMAN:GetProfile(pn) + if profile and profile:GetIgnoreStepCountCalories() then + go_to_heart= true + end + end + if go_to_heart then + return "ScreenHeartEntry" + end + return Branch.EvaluationScreen() + else + return Branch.EvaluationScreen() + end + end, + AfterHeartEntry= function() + return Branch.EvaluationScreen() + end, AfterEvaluation = function() if GAMESTATE:IsCourseMode() then return "ScreenProfileSave" diff --git a/Themes/_fallback/metrics.ini b/Themes/_fallback/metrics.ini index f8c33cec10..088bde938a 100644 --- a/Themes/_fallback/metrics.ini +++ b/Themes/_fallback/metrics.ini @@ -3547,6 +3547,19 @@ Class="ScreenGameplayShared" Fallback="ScreenGameplay" PlayerType="PlayerShared" +[ScreenHeartEntry] +Class="ScreenWithMenuElements" +Fallback="ScreenWithMenuElements" +PrevScreen=Branch.AfterHeartEntry() +NextScreen=Branch.AfterHeartEntry() +HeartEntryEnabled=false +PlayMusic=false +ShowHeader=false +ShowFooter=false +ShowCreditDisplay=false +TimerSeconds=-1 +TimerOnCommand=visible,false + [ScreenEvaluation] Class="ScreenEvaluation" Fallback="ScreenWithMenuElements" diff --git a/Themes/default/BGAnimations/ScreenHeartEntry overlay.lua b/Themes/default/BGAnimations/ScreenHeartEntry overlay.lua new file mode 100644 index 0000000000..ee48a98e61 --- /dev/null +++ b/Themes/default/BGAnimations/ScreenHeartEntry overlay.lua @@ -0,0 +1,220 @@ +-- The positions of the numbers on the numpad. +local numpad_poses= { + {-24, -24}, {0, -24}, {24, -24}, + {-24, 0}, {0, 0}, {24, 0}, + {-24, 24}, {0, 24}, {24, 24}, + {-24, 48}, {0, 48}, {24, 48}} +-- Some special escape sequences are converted to special characters. +local done_text= "&start;" +local back_text= "←" +local numpad_nums= {7, 8, 9, 4, 5, 6, 1, 2, 3, 0, done_text, back_text} +if MonthOfYear() == 3 and DayOfMonth() == 1 then + for i= 1, #numpad_nums do + local a= math.random(1, #numpad_nums) + local b= math.random(1, #numpad_nums) + numpad_nums[a], numpad_nums[b]= numpad_nums[b], numpad_nums[a] + end +end + +-- Each of these will be filled with an entry for each player that is +-- entering their heart rate. +-- This way, the actors are stored in convenient tables, so we don't have to go +-- through an inconvenient and inefficient chain of GetChild calls when we need +-- to change one of them. +local cursor_poses= {} +local cursor_quads= {} +local values= {} +local value_texts= {} +local done_status= {} + +-- This creates the actor that will show the numpad, prompt, and value for a +-- player. The PlayerNumber is passed in so that it can add its parts to the +-- tables above. +local function entry_pad(name, x, y, pn) + local args= { + Name= name, InitCommand= cmd(xy, x, y), + -- The cursor actor. First in the frame so it will be behind the text. + Def.Quad{ + Name= "cursor", InitCommand= function(self) + self:setsize(16, 24) + self:diffuse(PlayerDarkColor(pn)) + cursor_quads[pn]= self + end + }, + Def.BitmapText{ + Name= "rate_label", Font= "Common Normal", + Text= THEME:GetString("ScreenHeartEntry", "Heart Rate"), + InitCommand= cmd(xy, 0, -72; diffuse, Color.White) + }, + Def.BitmapText{ + Name= "value", Font= "Common Normal", Text= "0", + InitCommand= function(self) + self:xy(0, -48) + self:diffuse(PlayerColor(pn)) + value_texts[pn]= self + end + } + } + -- A loop is used to add each of the numbers on the numpad to shorten the + -- code and make it easier to add numbers later. + for i, num in ipairs(numpad_nums) do + args[#args+1]= Def.BitmapText{ + Name= "num"..i, Font= "Common Normal", Text= num, + InitCommand= cmd(xy, numpad_poses[i][1], numpad_poses[i][2]; diffuse, Color.White) + } + end + return Def.ActorFrame(args) +end + +-- A function to update the cursor position for a player. +local function update_cursor(pn) + local pos= numpad_poses[cursor_poses[pn]] + cursor_quads[pn]:stoptweening() + cursor_quads[pn]:linear(.1) + cursor_quads[pn]:xy(pos[1], pos[2]) +end + +local function pad_input(pn, button) + if button == "Start" then + local num= numpad_nums[cursor_poses[pn]] + local as_num= tonumber(num) + if as_num then + local new_value= (values[pn] * 10) + as_num + if new_value < 300 then + values[pn]= new_value + if new_value > 100 then + cursor_poses[pn]= 11 + update_cursor(pn) + end + value_texts[pn]:settext(values[pn]) + else + SOUND:PlayOnce(THEME:GetPathS("Common", "Invalid")) + end + return false + else + if num == done_text then + done_status[pn]= true + return true + elseif num == back_text then + values[pn]= math.floor(values[pn]/10) + value_texts[pn]:settext(values[pn]) + return false + end + end + else + local adds= { + Left= {2, -1, -1}, + Right= {1, 1, -2}, + MenuLeft= {-1, -1, -1}, + MenuRight= {1, 1, 1}, + Up= {9, -3, -3, -3}, + Down= {3, 3, 3, -9} + } + adds.MenuUp= adds.Up + adds.MenuDown= adds.Down + local lr_buttons= { + Left= true, Right= true, MenuLeft= true, MenuRight= true} + if adds[button] then + local index= 0 + if lr_buttons[button] then + index= ((cursor_poses[pn] - 1) % 3) + 1 + else + index= math.ceil(cursor_poses[pn] / 3) + end + local new_pos= cursor_poses[pn] + adds[button][index] + if new_pos < 1 then new_pos= #numpad_nums end + if new_pos > #numpad_nums then new_pos= 1 end + cursor_poses[pn]= new_pos + update_cursor(pn) + return false + end + end + return false +end + +local function input(event) + local pn= event.PlayerNumber + Trace("input recieved") + if not pn then return end + Trace("has a pn") + if event.type == "InputEventType_Release" then return end + Trace("not a release") + if not cursor_poses[pn] then return end + Trace("has a cursor") + local done= pad_input(pn, event.button) + if done then + local all_done= true + for i, status in pairs(done_status) do + if not status then all_done= false break end + end + if all_done then + for pn, value in pairs(values) do + local profile= PROFILEMAN:GetProfile(pn) + if profile and profile:GetIgnoreStepCountCalories() then + local calories= profile:CalculateCaloriesFromHeartRate( + values[pn], GAMESTATE:GetLastGameplayDuration()) + profile:AddCaloriesToDailyTotal(calories) + end + end + SOUND:PlayOnce(THEME:GetPathS("Common", "Start")) + SCREENMAN:GetTopScreen():StartTransitioningScreen("SM_GoToNextScreen") + end + end +end + +local heart_xs= { + [PLAYER_1]= SCREEN_WIDTH * .25, + [PLAYER_2]= SCREEN_WIDTH * .75, +} + +local timer_text +local function timer_update(self) + local time= math.floor((self:GetSecsIntoEffect() % 60) * 10) / 10 + if time < 10 then + timer_text:settext(("0%.1f"):format(time)) + else + timer_text:settext(("%.1f"):format(time)) + end +end + +local args= { + Def.ActorFrame{ + Name= "timer", + InitCommand= function(self) + self:effectperiod(2^16) + timer_text= self:GetChild("timer_text") + self:SetUpdateFunction(timer_update) + end, + OnCommand= function(self) + SCREENMAN:GetTopScreen():AddInputCallback(input) + end, + Def.BitmapText{ + Name= "timer_text", Font= "Common Normal", Text= "00.0", + InitCommand= cmd(xy, SCREEN_CENTER_X, SCREEN_CENTER_Y; diffuse, Color.White) + } + }, + Def.BitmapText{ + Name= "explanation", Font= "Common Normal", + Text= THEME:GetString("ScreenHeartEntry", "Enter Heart Rate"), + InitCommand= cmd(xy, SCREEN_CENTER_X, SCREEN_CENTER_Y-120; diffuse, Color.White)}, + Def.BitmapText{ + Name= "song_len_label", Font= "Common Normal", + Text= THEME:GetString("ScreenHeartEntry", "Song Length"), + InitCommand= cmd(xy, SCREEN_CENTER_X, SCREEN_CENTER_Y-72; diffuse, Color.White)}, + Def.BitmapText{ + Name= "song_len", Font= "Common Normal", + Text= SecondsToMMSS(GAMESTATE:GetLastGameplayDuration()), + InitCommand= cmd(xy, SCREEN_CENTER_X, SCREEN_CENTER_Y-48; diffuse, Color.White)}, +} + +for i, pn in ipairs(GAMESTATE:GetEnabledPlayers()) do + local profile= PROFILEMAN:GetProfile(pn) + if profile and profile:GetIgnoreStepCountCalories() then + cursor_poses[pn]= 5 + values[pn]= 0 + done_status[pn]= false + args[#args+1]= entry_pad(pn, heart_xs[pn], SCREEN_CENTER_Y, pn) + end +end + +return Def.ActorFrame(args) diff --git a/Themes/default/Languages/en.ini b/Themes/default/Languages/en.ini index ed9b7f2368..59926aa903 100644 --- a/Themes/default/Languages/en.ini +++ b/Themes/default/Languages/en.ini @@ -85,6 +85,11 @@ HelpText= [ScreenGameplay] HelpText= +[ScreenHeartEntry] +Enter Heart Rate=Enter Heart Rate +Song Length=Song Length +Heart Rate=Heart Rate + [ScreenEvaluation] HelpText=&BACK; Exit &START; Move On &MENULEFT;+&MENURIGHT; or &SELECT; Snapshot LifeDifficulty=Life Difficulty: %s diff --git a/Themes/default/metrics.ini b/Themes/default/metrics.ini index 36ed3d2482..aa132ac658 100644 --- a/Themes/default/metrics.ini +++ b/Themes/default/metrics.ini @@ -1628,6 +1628,9 @@ ActiveAttackListP2OffCommand= [ScreenGameplayShared] +[ScreenHeartEntry] +HeartEntryEnabled=true + [ScreenEvaluation] Class="ScreenEvaluation" Fallback="ScreenWithMenuElements" diff --git a/src/GameState.cpp b/src/GameState.cpp index 370cdb6ef3..d937406d19 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -2406,6 +2406,7 @@ public: p->m_Position.PushSelf(L); return 1; } + DEFINE_METHOD( GetLastGameplayDuration, m_DanceDuration ) DEFINE_METHOD( GetGameplayLeadIn, m_bGameplayLeadIn ) DEFINE_METHOD( GetCoins, m_iCoins ) DEFINE_METHOD( IsSideJoined, m_bSideIsJoined[Enum::Check(L, 1)] ) @@ -2815,6 +2816,7 @@ public: ADD_METHOD( GetSongFreeze ); ADD_METHOD( GetSongDelay );*/ ADD_METHOD( GetSongPosition ); + ADD_METHOD( GetLastGameplayDuration ); ADD_METHOD( GetGameplayLeadIn ); ADD_METHOD( GetCoins ); ADD_METHOD( IsSideJoined ); diff --git a/src/GameState.h b/src/GameState.h index 3c463e0e69..3343165893 100644 --- a/src/GameState.h +++ b/src/GameState.h @@ -267,6 +267,10 @@ public: float m_fLastHasteUpdateMusicSeconds; float m_fAccumulatedHasteSeconds; + // used by themes that support heart rate entry. + RageTimer m_DanceStartTime; + float m_DanceDuration; + // Random Attacks & Attack Mines vector m_RandomAttacks; diff --git a/src/ScreenGameplay.cpp b/src/ScreenGameplay.cpp index 2c1a96e36e..7ded853626 100644 --- a/src/ScreenGameplay.cpp +++ b/src/ScreenGameplay.cpp @@ -2507,6 +2507,8 @@ void ScreenGameplay::HandleScreenMessage( const ScreenMessage SM ) else SOUND->PlayOnceFromAnnouncer( "gameplay here we go normal" ); + GAMESTATE->m_DanceStartTime.Touch(); + m_Go.StartTransitioning( SM_None ); GAMESTATE->m_bGameplayLeadIn.Set( false ); m_DancingState = STATE_DANCING; // STATE CHANGE! Now the user is allowed to press Back @@ -2556,6 +2558,7 @@ void ScreenGameplay::HandleScreenMessage( const ScreenMessage SM ) } else if( SM == SM_LeaveGameplay ) { + GAMESTATE->m_DanceDuration= GAMESTATE->m_DanceStartTime.Ago(); // update dancing characters for win / lose DancingCharacters *pDancers = NULL; if( m_pSongBackground )