diff --git a/Courses/Default/Jupiter.crs b/Courses/Default/Jupiter.crs new file mode 100644 index 0000000000..85dbbc9cf5 --- /dev/null +++ b/Courses/Default/Jupiter.crs @@ -0,0 +1,8 @@ +#COURSE:Jupiter; +#SCRIPTER:Midiman; + +#GAINSECONDS:120; +#SONG:*:Hard; + +#GAINSECONDS:25; +#SONG:*:Hard; \ No newline at end of file diff --git a/Docs/Changelog_SSCformat.txt b/Docs/Changelog_SSCformat.txt index 20088bb92b..45901aaabb 100644 --- a/Docs/Changelog_SSCformat.txt +++ b/Docs/Changelog_SSCformat.txt @@ -9,6 +9,11 @@ change to JSON, but it is unsure if this will be done. Implement .ssc at your own risk. ________________________________________________________________________________ +[v0.74] - Wolfman2000 +* Add #CHARTNAME tag for the title of the chart. +* Older files will have their #DESCRIPTION tag content copied to #CHARTNAME + on load. + [v0.73] - Wolfman2000 * Use #FIRSTSECOND, #LASTSECOND, and #LASTSECONDHINT instead of the BEAT equivalents. diff --git a/Docs/Changelog_sm5.txt b/Docs/Changelog_sm5.txt index e956b42c3a..d6f436c9a3 100644 --- a/Docs/Changelog_sm5.txt +++ b/Docs/Changelog_sm5.txt @@ -11,6 +11,12 @@ StepMania 5.0 ????????? | 20110??? 2011/07/07 ---------- * [ScreenEdit] Fix the NoMines transformation bug, issue 363. [Wolfman2000] +* [CourseLoaderCRS] Don't load BEST# and WORST# where # is greater than the + number of songs installed. Fixes issue 373. [AJ] +* [NotesAllSSC] Add the #CHARTNAME tag to the Steps. #DESCRIPTION is now for + more detailed descriptions, or perhaps some short hand lingo. All files + that used #DESCRIPTION will have their values copied to #CHARTNAME on + load. [Wolfman2000] 2011/07/06 ---------- diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index a3bf6efb82..bbb1e57e54 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -1382,6 +1382,7 @@ + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index 3163e5b0da..7de1cc82a3 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -3701,6 +3701,9 @@ Returns the author that made that particular Steps pattern. + + Returns the Steps chart name. + Returns the Chart Style for this Steps. diff --git a/NoteSkins/dance/midi-note/NoteSkin.lua b/NoteSkins/dance/midi-note/NoteSkin.lua index d821c2740b..0d596ec4ef 100644 --- a/NoteSkins/dance/midi-note/NoteSkin.lua +++ b/NoteSkins/dance/midi-note/NoteSkin.lua @@ -20,14 +20,14 @@ ret.Redir = function(sButton, sElement) if sElement == "Hold Head Active" or sElement == "Roll Head Active" then - sElement = "Hold Head Active"; + sElement = "Tap Note"; end -- Test if sElement == "Hold Head Inactive" or sElement == "Roll Head Inactive" then - sElement = "Hold Head Inactive"; + sElement = "Tap Note"; end if sElement == "Tap Fake" diff --git a/Themes/_fallback/BGAnimations/ScreenNameEntryTraditional underlay/default.lua b/Themes/_fallback/BGAnimations/ScreenNameEntryTraditional underlay/default.lua new file mode 100644 index 0000000000..994e3caf01 --- /dev/null +++ b/Themes/_fallback/BGAnimations/ScreenNameEntryTraditional underlay/default.lua @@ -0,0 +1,284 @@ +function MakeScoreList(StagesAgo) + local c; + local Stats; + local PlayerStats = { } + local children = { + LoadActor( "difficulty cap 9x1" ) .. { + Name="DifficultyP1"; + InitCommand = cmd(x,-66;pause;halign,1); + OnCommand=cmd(zoomx,0;sleep,0.3;bounceend,0.3;zoomx,1); + OffCommand=cmd(bouncebegin,0.1;zoomx,0); + }; + LoadActor( "difficulty cap 9x1" ) .. { + Name="DifficultyP2"; + InitCommand = cmd(x,66;pause;halign,1;zoomx,0); + OnCommand=cmd(zoomx,0;sleep,0.3;bounceend,0.3;zoomx,-1); + OffCommand=cmd(bouncebegin,0.1;zoomx,0); + }; + LoadActor( "banner mask" ) .. { + Name="BannerMask"; + InitCommand=cmd(clearzbuffer,true;zwrite,true;blend,"BlendMode_NoEffect"); + OnCommand=cmd(zoom,0;bounceend,0.3;zoom,1); + OffCommand=cmd(bouncebegin,0.3;zoom,0); + }; + Def.Sprite { + Name = "Banner"; + InitCommand = cmd(setsize,300,100;zoom,0.5;ztest,true); + OnCommand=cmd(zoom,0;bounceend,0.3;zoom,0.5); + OffCommand=cmd(bouncebegin,0.3;zoom,0); + }; + LoadActor( "banner frame" ) .. { + Name="BannerFrame"; + OnCommand=cmd(zoom,0;bounceend,0.3;zoom,1); + OffCommand=cmd(bouncebegin,0.3;zoom,0); + }; + + LoadActor( "side score frame" ) .. { + Name="ScoreFrameP1"; + InitCommand=cmd(); + OnCommand=cmd(x,-266;addx,-250;diffusealpha,1;decelerate,0.3;addx,250); + OffCommand=cmd(linear,0.3;addx,-150;diffusealpha,0); + }; + LoadActor( "side score frame" ) .. { + Name="ScoreFrameP2"; + InitCommand=cmd(zoomx,-1); + OnCommand=cmd(x,266;addx,250;diffusealpha,1;decelerate,0.3;addx,-250); + OffCommand=cmd(linear,0.3;addx,150;diffusealpha,0); + }; + + LoadFont( "_regra bold 30px" ) .. { + Name="PlayerScoreP1"; + InitCommand=cmd(x,-135;diffuse,color("#fdf991");shadowlength,0;zoom,0.6); + OnCommand=cmd(x,-135;addx,50;zoom,0;diffusealpha,0;sleep,0.1;accelerate,0.3;addx,-50;diffusealpha,1;zoom,0.6); + OffCommand=cmd(linear,0.3;diffusealpha,0); + }; + LoadFont( "_regra bold 30px" ) .. { + Name="PlayerScoreP2"; + InitCommand=cmd(x,135;diffuse,color("#abfe8e");shadowlength,0;zoom,0.6); + OnCommand=cmd(x,135;addx,-50;zoom,0;diffusealpha,0;sleep,0.1;accelerate,0.3;addx,50;diffusealpha,1;zoom,0.6); + OffCommand=cmd(linear,0.3;diffusealpha,0); + }; + + LoadFont( "_regra bold 30px" ) .. { + Name="MachineBestP1"; + InitCommand=cmd(diffuse,color("#ffffff");shadowlength,0;zoom,0.5); + OnCommand=cmd(x,-250;addx,-250;diffusealpha,1;decelerate,0.3;addx,250); + OffCommand=cmd(linear,0.3;addx,-150;diffusealpha,0); + }; + LoadFont( "_regra bold 30px" ) .. { + Name="MachineBestP2"; + InitCommand=cmd(diffuse,color("#ffffff");shadowlength,0;zoom,0.5); + OnCommand=cmd(x,250;addx,250;diffusealpha,1;decelerate,0.3;addx,-250); + OffCommand=cmd(linear,0.3;addx,150;diffusealpha,0); + }; + + }; + + return Def.ActorFrame { + children = children; + InitCommand = function(self) + c = self:GetChildren(); + c.PlayerScore = {}; + c.PlayerScore[PLAYER_1] = c.PlayerScoreP1; + c.PlayerScore[PLAYER_2] = c.PlayerScoreP2; + c.MachineBest = {}; + c.MachineBest[PLAYER_1] = c.MachineBestP1; + c.MachineBest[PLAYER_2] = c.MachineBestP2; + c.ScoreFrame = {}; + c.ScoreFrame[PLAYER_1] = c.ScoreFrameP1; + c.ScoreFrame[PLAYER_2] = c.ScoreFrameP2; + c.Difficulty = {}; + c.Difficulty[PLAYER_1] = c.DifficultyP1; + c.Difficulty[PLAYER_2] = c.DifficultyP2; + end; + + BeginCommand = function(self) + Stats = STATSMAN:GetPlayedStageStats(StagesAgo); + if not Stats then + self:visible(false); + return; + end; + self:visible(true); + + PlayerStats[PLAYER_1] = Stats:GetPlayerStageStats(PLAYER_1); + PlayerStats[PLAYER_2] = Stats:GetPlayerStageStats(PLAYER_2); + + local Current; + local CurrentPart = {} + if GAMESTATE:IsCourseMode() then + Current = GAMESTATE:GetCurrentCourse(); + CurrentPart[PLAYER_1] = GAMESTATE:GetCurrentTrail( PLAYER_1 ); + CurrentPart[PLAYER_2] = GAMESTATE:GetCurrentTrail( PLAYER_2 ); + else + Current = Stats:GetPlayedSongs()[1]; + CurrentPart[PLAYER_1] = PlayerStats[PLAYER_1]:GetPlayedSteps()[1]; + CurrentPart[PLAYER_2] = PlayerStats[PLAYER_2]:GetPlayedSteps()[1]; + end + assert(Current); + + local sBannerPath = Current:GetBannerPath() or THEME:GetPathG("Common","fallback banner"); + c.Banner:LoadBanner( sBannerPath ); + + for pn in ivalues(PlayerNumber) do + local visible = GAMESTATE:IsHumanPlayer(pn) and CurrentPart[pn] ~= nil; -- was present for this song + c.MachineBest[pn]:visible( visible ); + c.PlayerScore[pn]:visible( visible ); + c.ScoreFrame[pn]:visible( visible ); + c.Difficulty[pn]:visible( visible ); + + local MachineProfile = PROFILEMAN:GetMachineProfile(); + if visible then + assert(CurrentPart[pn]); + + c.Difficulty[pn]:SetDifficultyAndStepsTypeFrame( CurrentPart[pn] ); + + local hsl = MachineProfile:GetHighScoreList( Current, CurrentPart[pn] ); + assert( hsl ); + + -- Find the first high score that isn't one that we're entering now. + local HighScores = hsl:GetHighScores(); + local hs; + for h in ivalues(HighScores) do + if not hs and not h:IsFillInMarker() then + hs = h; + end + end + + if hs then + local fMachineScore = hs:GetPercentDP() or 0; + local sName = hs:GetName() or ""; + local sText = sName .. " " .. FormatPercentScore( fMachineScore ); + c.MachineBest[pn]:settext( sText ); + else + c.ScoreFrame[pn]:visible( false ); + end + + local fPlayerScore = PlayerStats[pn]:GetPercentDancePoints(); + local sText = FormatPercentScore( fPlayerScore ); + c.PlayerScore[pn]:settext( sText ); + local colors = { + [PLAYER_1] = color("#fdf991"), + [PLAYER_2] = color("#abfe8e"), + }; + if Stats:PlayerHasHighScore(pn) then + colors = { + [PLAYER_1] = color("#feef01"), + [PLAYER_2] = color("#0ffc03"), + }; + end + c.PlayerScore[pn]:diffuse(colors[pn]); + end; + end; + end; + }; +end + +local children = { + LoadActor( "center frame" ) .. { + InitCommand=cmd(x,SCREEN_CENTER_X;y,SCREEN_CENTER_Y+9); + OnCommand=cmd(fadetop,0.3; croptop,1; linear,0.5; croptop,-0.3); + OffCommand=cmd(linear,0.5; croptop,1); + }; + LoadActor( "line" ) .. { + InitCommand=cmd(x,SCREEN_CENTER_X;SetWidth,336;y,SCREEN_CENTER_Y+65;fadeleft,0.3; faderight,0.3); + OnCommand=cmd(cropleft,.5;cropright,.5; + sleep,0.2; + linear,0.3; + cropleft,-.2;cropright,-.2); + + OffCommand=cmd(linear,0.3;cropleft,.5;cropright,.5); + }; +}; + +for i = 1,5 do + children[#children+1] = + MakeScoreList(i) .. { + Name="Row" .. tostring(i); + InitCommand=cmd(x,SCREEN_CENTER_X); + }; +end + +children[#children+1] = + LoadActor( THEME:GetPathB("","_shared underlay") ) .. { OnCommand=cmd(); }; + +children[#children+1] = LoadActor( "keyboard" ) .. { + InitCommand=cmd(x,SCREEN_CENTER_X;y,SCREEN_CENTER_Y+80); + BeginCommand = cmd(visible,SCREENMAN:GetTopScreen():GetAnyEntering()); +}; + +for pn in ivalues(PlayerNumber) do + local FrameX = pn == PLAYER_1 and (SCREEN_CENTER_X-265) or (SCREEN_CENTER_X+267); + local P1 = pn == PLAYER_1 and 1 or -1; + children[#children+1] = LoadActor( "name frame" ) .. { + InitCommand=cmd(y,SCREEN_CENTER_Y+119; + zoomx,pn == PLAYER_1 and 1.0 or -1.0); + BeginCommand=cmd(visible,SCREENMAN:GetTopScreen():GetEnteringName(pn)); + OnCommand=cmd(finishtweening;x,FrameX;addx,-200*P1;zoom,0;bounceend,0.3;zoom,1;addx,200*P1); + OffCommand=cmd(stoptweening;bouncebegin,0.3;zoom,0;addx,-200*P1); + }; + + children[#children+1] = LoadFont( "_regra bold 30px" ) .. { + Name="PlayerText"; + InitCommand=cmd(x,pn == PLAYER_1 and (SCREEN_CENTER_X-WideScale(310,330)) or (SCREEN_CENTER_X+WideScale(198,204)); + halign,0; + y,SCREEN_CENTER_Y+115;shadowlength,0;zoom,1); + BeginCommand=cmd(visible,SCREENMAN:GetTopScreen():GetEnteringName(pn)); + OnCommand=cmd(finishtweening;diffusealpha,0;sleep,0.1;linear,0.3;diffusealpha,1); + OffCommand=cmd(stoptweening;bouncebegin,0.3;zoom,0;addx,-200*P1); + EntryChangedMessageCommand=function(self,params) + if params.PlayerNumber ~= pn then + return; + end; + self:settext( params.Text ); + end; + }; +end; + +return Def.ActorFrame { + children = children; + + BeginCommand = function(self) + local StagesPlayed = STATSMAN:GetStagesPlayed(); + local c = self:GetChildren(); + + local fYCenter = SCREEN_CENTER_Y-60; + local fYMinSpacing = 44; + local fYMaxSpacing = 70; + local fYSpacing = scale(StagesPlayed, 4, 5, fYMaxSpacing, fYMinSpacing); + fYSpacing = clamp(fYSpacing, fYMinSpacing, fYMaxSpacing); + fYSpacing = fYSpacing + math.mod(fYSpacing, 2); -- even + fYSpacing = -fYSpacing; + + local fYStart = fYSpacing*(StagesPlayed-1)/2; + local fYEnd = -fYSpacing*(StagesPlayed-1)/2; + for i = 1,StagesPlayed do + local Name = "Row" .. tostring(i); + local row = c[Name]; + assert(row); + local fY = fYCenter + fYEnd + (i-1)*fYSpacing; + row:y( fY ); + end + + local Timer = SCREENMAN:GetTopScreen():GetChild("Timer"); + if not SCREENMAN:GetTopScreen():GetAnyEntering() then + Timer:setseconds(5); + Timer:silent(true); + else + Timer:setseconds(25); + Timer:silent(false); + end + end; + + OnCommand = function(self) + local StagesPlayed = STATSMAN:GetStagesPlayed(); + local c = self:GetChildren(); + for i = 1,StagesPlayed do + local Name = "Row" .. tostring(i); + local row = c[Name]; + assert(row); + + row:hibernate(0.3+(i/StagesPlayed)*0.3); + end + end; +}; + diff --git a/Themes/_fallback/BGAnimations/ScreenNameEntryTraditional underlay/keyboard.lua b/Themes/_fallback/BGAnimations/ScreenNameEntryTraditional underlay/keyboard.lua new file mode 100644 index 0000000000..052c378720 --- /dev/null +++ b/Themes/_fallback/BGAnimations/ScreenNameEntryTraditional underlay/keyboard.lua @@ -0,0 +1,279 @@ +local function distance( x1, y1, x2, y2 ) + return math.pow( math.pow(x1-x2, 2) + math.pow(y1-y2, 2), 0.5 ); +end + +local children = { +}; + +local Letters = { + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", + "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "?", "!", + "BACK", "ENTER" +}; + +local LetterIndexes = { }; +for i, l in ipairs(Letters) do + LetterIndexes[l] = i; +end + +local MapNameToLetter = {}; +local MapLetterToName = {}; +local letter = LoadFont( "_regra bold 30px" ) .. { + InitCommand=cmd(zoom,0.8;shadowlength,0); + PulseCommand = function(self) + self:finishtweening(); + local z = self:GetZoomX(); + (cmd(accelerate,0.15;zoom,1.2;decelerate,0.15;zoom,z))(self); + end; +}; + +for l in ivalues(Letters) do + local Name = "letter " .. l; + MapNameToLetter[Name] = l; + MapLetterToName[l] = Name; + children[#children+1] = letter .. { + Name = Name; + Text = l; + }; + if l == "BACK" or l == "ENTER" then + children[#children].File = THEME:GetPathF("", "_venacti 26px normal"); + end; +end; +children[#children+1] = LoadActor( THEME:GetPathS(Var "LoadingScreen", "type key") ) .. { Name = "Type"; SupportPan = true; } +children[#children+1] = LoadActor( THEME:GetPathS(Var "LoadingScreen", "back") ) .. { Name = "Back"; SupportPan = true; } +children[#children+1] = LoadActor( THEME:GetPathS(Var "LoadingScreen", "enter") ) .. { Name = "Enter"; SupportPan = true; } +children[#children+1] = LoadActor( THEME:GetPathS(Var "LoadingScreen", "move cursor") ) .. { Name = "Move"; SupportPan = true; } + +local CursorFiles = { + [PLAYER_1] = "Cursor P1", + [PLAYER_2] = "Cursor P2" +}; + +for pn in ivalues(PlayerNumber) do + children[#children+1] = LoadActor( THEME:GetPathG("_frame", "1D"), + { 4/10, 2/10, 4/10 }, + LoadActor(CursorFiles[pn]) + ) .. { + Name = CursorFiles[pn]; + BeginCommand = cmd(visible,SCREENMAN:GetTopScreen():GetEnteringName(pn)); + OnCommand = cmd( + zoom,0; + rotationz,-360*2; + sleep,0.55; + decelerate,0.5; + zoom,1; + rotationz,0; + ); + OffCommand = cmd(sleep,0.3;queuecommand,"TweenOff"); + PlayerFinishedMessageCommand = function(self,param) + if param.PlayerNumber ~= pn then return end + self:playcommand("TweenOff" ); + end; + TweenOffCommand = function(self,param) + (cmd( + accelerate,0.25; + zoomx,0; + ))(self); + end; + }; +end + +local c; +local Keys = {}; +local Selection = {}; +local PlayerX = {}; +local PlayerY = {}; +return Def.ActorFrame { + children = children; + BeginCommand = function(self) + c = self:GetChildren(); + c.Cursors = {}; + c.Cursors[PLAYER_1] = c["Cursor P1"]; + c.Cursors[PLAYER_2] = c["Cursor P2"]; + + for l in ivalues(Letters) do + local Name = MapLetterToName[l]; + assert(Name); + Keys[l] = { + Text = c[Name]; + Width = c[Name]:GetWidth(); + Height = c[Name]:GetHeight(); + }; + assert(c[Name], Name); + end; + + -- Position letters. + local RowWidths = {}; + local fX = 0; + local fXPadding = 4; + local fY = 0; + local iRow = 1; + for l in ivalues(Letters) do + if l == "O" or l == "0" or l == "BACK" + then + fX = 0; + fY = fY + 30; + iRow = iRow + 1; + end; + Keys[l].Row = iRow; + Keys[l].Text:x(fX + Keys[l].Width/2); + Keys[l].Text:y(fY + Keys[l].Height/2); + fX = fX + Keys[l].Width; + RowWidths[iRow] = fX; + fX = fX + fXPadding; + end; + for l in ivalues(Letters) do + local iRow = Keys[l].Row; + Keys[l].Text:addx(-RowWidths[iRow]/2); + end; + + Keys["BACK"].Text:x(-140); + Keys["ENTER"].Text:x(140); + + for pn in ivalues(PlayerNumber) do + local sName = SCREENMAN:GetTopScreen():GetSelection(pn); + local DefaultKey = "A" + if #sName > 0 then + DefaultKey = "ENTER" + end + + self:playcommand("SelectKey", { Key = DefaultKey, PlayerNumber = pn }); + c.Cursors[pn]:finishtweening(); + end + end; + + CodeMessageCommand = function(self, param) + local pn = param.PlayerNumber; + if not SCREENMAN:GetTopScreen():GetAnyStillEntering() and param.Name == "Enter" then + if SCREENMAN:GetTopScreen():Finish(pn) then + c.Enter:play(); + end + return; + end + if SCREENMAN:GetTopScreen():GetFinalized( pn ) then + return; + end + + if param.Name == "Left" or param.Name == "Right" then + local iDir = param.Name == "Left" and -1 or 1; + + local idx = LetterIndexes[Selection[pn]]; + idx = idx + iDir; + idx = math.mod(idx+#Letters-1, #Letters)+1; + self:playcommand("SelectKey", { Key = Letters[idx], PlayerNumber = pn }); + c.Move:playforplayer(pn); + return; + end + + if param.Name == "JumpToEnter" then + self:playcommand("SelectKey", { Key = "ENTER", PlayerNumber = pn }); + c.Move:playforplayer(pn); + return; + end + if param.Name == "Backspace" then + if SCREENMAN:GetTopScreen():Backspace(pn) then + c.Back:playforplayer(pn); + end + return; + end + + if param.Name == "NextRow" or param.Name == "PrevRow" then + local iDir = param.Name == "PrevRow" and -1 or 1; + local idx = LetterIndexes[Selection[pn]]; + local iRow = Keys[Selection[pn]].Row; + iRow = iRow + iDir; + iRow = math.mod(iRow+4-1, 4)+1; + + local NearestLetter + local NearestLetterDistance + for l in ivalues(Letters) do + if iRow == Keys[l].Row then + local Text = Keys[l].Text; + + local fDist = distance( PlayerX[pn], PlayerY[pn], + Text:GetX(), Text:GetY() ); + if not NearestLetterDistance or fDist < NearestLetterDistance then + NearestLetterDistance = fDist; + NearestLetter = l; + end + end + end + assert( NearestLetter ); + self:playcommand("SelectKey", { Key = NearestLetter, PlayerNumber = pn, NoStore = true }); + + c.Move:playforplayer(pn); + + return; + end + + if param.Name == "Enter" then + if Selection[pn] == "BACK" then + if SCREENMAN:GetTopScreen():Backspace(pn) then + Keys[Selection[pn]].Text:playcommand( "Pulse" ); + c.Back:playforplayer(pn); + end + elseif Selection[pn] == "ENTER" then + if SCREENMAN:GetTopScreen():Finish(pn) then + Keys[Selection[pn]].Text:playcommand( "Pulse" ); + c.Enter:playforplayer(pn); + end + else + local key = Selection[pn]; -- EnterKey may change this + if SCREENMAN:GetTopScreen():EnterKey(pn, key) then + Keys[key].Text:playcommand( "Pulse" ); + c.Type:playforplayer(pn); + end + end + end + end; + + SelectKeyMessageCommand = function(self, param) + local key = param.Key; + local pn = param.PlayerNumber; + if not Keys[key] then return end + + Selection[pn] = key; + c.Cursors[pn]:stoptweening(); + c.Cursors[pn]:playcommand( "SetSize", { Width=Keys[key].Width+6; tween=cmd(stoptweening;linear,0.10); } ); + c.Cursors[pn]:x( Keys[key].Text:GetX() ); + c.Cursors[pn]:y( Keys[key].Text:GetY() ); + if not param.NoStore then + PlayerX[pn] = Keys[key].Text:GetX(); + PlayerY[pn] = Keys[key].Text:GetY(); + end + + -- SCREENMAN:GetTopScreen():EnterKey(PLAYER_2,"x"); + end; + MenuTimerExpiredMessageCommand = function(self, param) + for pn in ivalues(PlayerNumber) do + SCREENMAN:GetTopScreen():Finish(pn); + end + c.Enter:play(); + end; + + OnCommand = function(self, param) + for key in ivalues(Letters) do + local fDist = distance( Keys[key].Text:GetX(), Keys[key].Text:GetY(), 0, 100 ); + + local f = cmd( + diffusealpha,0;zoom,0; + sleep,0.25 + (fDist / 400); + decelerate,0.5; + diffusealpha,1;zoom,0.8); + f(Keys[key].Text); + end + end; + + OffCommand = function(self, param) + for key in ivalues(Letters) do + local fDist = distance( Keys[key].Text:GetX(), Keys[key].Text:GetY(), 0, 100 ); + + local f = cmd( + sleep,0.0 + (fDist / 400); + decelerate,0.5; + diffusealpha,0;zoom,0); + f(Keys[key].Text); + end + end; +}; diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index 4068618b8d..8cd1019f9d 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -802,6 +802,7 @@ CelShadeModels=Cel-shaded Models Center Image=Center Image Chaos=Chaos Characters=Characters +Chart Name=Chart Name Chart Style=Chart Style Choose=Choose Clear Bookkeeping Data=Clear Bookkeeping Data @@ -1256,6 +1257,7 @@ Enter a new artist.=Enter a new artist. Enter a new genre.=Enter a new genre. Enter a new credit.=Enter a new credit. Enter a new description.=Enter a new description. +Enter a new chart name.=Enter the name/title of this chart. Enter a new chart style.=Enter a new chart style (e.g. "Pad", "Keyboard"). Enter a new meter.=Enter the difficulty rating for this chart. Enter the author who made this step pattern.=Enter the author who made this step pattern. @@ -1291,6 +1293,7 @@ Snap to=Snap to Selection beat=Selection beat Difficulty=Difficulty Description=Description +Chart Name=Chart Name Chart Style=Chart Style Step Author=Step Author Main title=Main title @@ -1680,7 +1683,9 @@ The folder "%s" appears to be a song folder. All song folders must reside in a [SongUtil] You must supply a name for your new edit.=You must supply a name for your new edit. +The name you chose conflicts with another chart. Please use a different name.=The name you chose conflicts with another chart. Please use a different name. The name you chose conflicts with another edit. Please use a different name.=The name you chose conflicts with another edit. Please use a different name. +The chart name cannot contain any of the following characters: %s=The chart name cannot contain any of the following characters: %s The edit name cannot contain any of the following characters: %s=The edit name can not contain any of the following characters: %s The step author's name cannot contain any of the following characters: %s=The step author's name cannot contain any of the following characters: %s diff --git a/Themes/_fallback/Scripts/02 Branches.lua b/Themes/_fallback/Scripts/02 Branches.lua index a605a9b2a0..8463b2283a 100644 --- a/Themes/_fallback/Scripts/02 Branches.lua +++ b/Themes/_fallback/Scripts/02 Branches.lua @@ -193,4 +193,7 @@ Branch = { Network = function() return IsNetConnected() and "ScreenTitleMenu" or "ScreenTitleMenu" end, + AfterSaveSummary = function() + return GAMESTATE:AnyPlayerHasRankingFeats() and "ScreenNameEntryTraditional" or "ScreenGameOver" + end, } diff --git a/Themes/_fallback/metrics.ini b/Themes/_fallback/metrics.ini index 4bdb7bf1ca..5749ed105f 100644 --- a/Themes/_fallback/metrics.ini +++ b/Themes/_fallback/metrics.ini @@ -413,6 +413,8 @@ SortLevel5Color=color("0,1,0,1") # Custom system that lets you rename certain classes of difficulties to # something else. Mostly for custom games and game emulation, PIU for example. +Names= + [DifficultyList] # A list that shows difficulties in a song. CapitalizeDifficultyNames=false @@ -964,8 +966,8 @@ StopOffsetX=50 DelayOffsetX=120 WarpOffsetX=90 TimeSignatureOffsetX=30 -TickcountOffsetX=70 -ComboOffsetX=50 +TickcountOffsetX=50 +ComboOffsetX=70 LabelOffsetX=130 SpeedOffsetX=30 ScrollOffsetX=100 @@ -3218,6 +3220,9 @@ PlayerType="PlayerShared" Class="ScreenEvaluation" Fallback="ScreenWithMenuElements" # +NextScreen=Branch.AfterEvaluation() +PrevScreen=Branch.AfterEvaluation() +# LightsMode="LightsMode_MenuStartOnly" # Summary=false @@ -3303,30 +3308,34 @@ DetailLineFormat="%3d/%3d" [ScreenEvaluationNormal] Fallback="ScreenEvaluation" # -NextScreen="ScreenProfileSave" +NextScreen=Branch.AfterEvaluation() +PrevScreen=Branch.AfterEvaluation() [ScreenEvaluationSummary] Fallback="ScreenEvaluation" # -NextScreen="ScreenSelectMusic" +NextScreen=Branch.AfterSummary() # Summary=true -# [ScreenNameEntry] # !!! # Class="ScreenNameEntry" Fallback="ScreenWithMenuElementsBlank" +# TimerX=SCREEN_CENTER_X+0 TimerY=SCREEN_CENTER_Y-210 +# CategoryY=SCREEN_CENTER_Y+190 CategoryZoom=0.7 +# CharsZoomSmall=1.0 CharsZoomLarge=1.5 CharsSpacingY=40 CharsChoices=" ABCDEFGHIJKLMNOPQRSTUVWXYZ" ScrollingCharsCommand=diffuse,0.6,0.8,0.8,1 SelectedCharsCommand=diffuse,0.8,1,1,1 +# ReceptorArrowsY=SCREEN_CENTER_Y-140 NumCharsToDrawBehind=2 NumCharsToDrawTotal=10 @@ -3336,7 +3345,9 @@ TimerSeconds=24 TimerStealth=false ShowStyleIcon=false MaxRankingNameLength=4 +# NextScreen="ScreenProfileSave" +# PlayerP1OnePlayerOneSideX=SCREEN_CENTER_X-160 PlayerP2OnePlayerOneSideX=SCREEN_CENTER_X+160 PlayerP1TwoPlayersTwoSidesX=SCREEN_CENTER_X-160 @@ -3344,6 +3355,31 @@ PlayerP2TwoPlayersTwoSidesX=SCREEN_CENTER_X+160 PlayerP1OnePlayerTwoSidesX=SCREEN_CENTER_X PlayerP2OnePlayerTwoSidesX=SCREEN_CENTER_X +[ScreenNameEntryTraditional] +Class="ScreenNameEntryTraditional" +Fallback="ScreenWithMenuElements" +# +TimerSeconds=30 +# +RepeatRate=15 +RepeatDelay=1/4 +# +NextScreen="ScreenProfileSaveSummary" +# +HelpText="Enter your name!" +# +CancelTransitionsOut=true +MaxRankingNameLength=4 +CodeNames="Backspace,Left,Right,NextRow1=NextRow,NextRow2=NextRow,PrevRow,JumpToEnter,Enter" +CodeLeft="+MenuLeft" +CodeRight="+MenuRight" +CodePrevRow="+MenuUp" +CodeNextRow1="+MenuDown" +CodeNextRow2="Select,~Select" +CodeBackspace="@Select-MenuLeft" +CodeJumpToEnter="@Select-Start" +CodeEnter="Start" + [ScreenProfileSave] Class="ScreenProfileSave" Fallback="ScreenWithMenuElementsBlank" @@ -3356,8 +3392,8 @@ TimerSeconds=-1 [ScreenProfileSaveSummary] Fallback="ScreenProfileSave" # -NextScreen="ScreenGameOver" -PrevScreen="ScreenGameOver" +NextScreen=Branch.AfterSaveSummary() +PrevScreen=Branch.AfterSaveSummary() [ScreenGameOver] Class="ScreenEnding" @@ -3590,6 +3626,7 @@ DifficultyFormat="%s:\n %s\n" RoutinePlayerFormat="%s: %d\n" DescriptionFormat="%s:\n %s\n" StepAuthorFormat="%s:\n %s\n" +ChartNameFormat="%s:\n %s\n" ChartStyleFormat="%s:\n %s\n" MainTitleFormat="%s:\n %s\n" SubtitleFormat="%s:\n %s\n" diff --git a/autoconf/m4/opengl.m4 b/autoconf/m4/opengl.m4 index bc6187698b..e982d76c75 100644 --- a/autoconf/m4/opengl.m4 +++ b/autoconf/m4/opengl.m4 @@ -1,36 +1,36 @@ AC_DEFUN([SM_X_WITH_OPENGL], [ - AC_PATH_X - - XCFLAGS= - XLIBS= + AC_PATH_X - if test "$no_x" != "yes"; then + XCFLAGS= + XLIBS= + + if test "$no_x" != "yes"; then if test -n "$x_includes"; then - XCFLAGS="-I$x_includes" + XCFLAGS="-I$x_includes" fi if test -n "$x_libraries"; then - XLIBS="-L$x_libraries" + XLIBS="-L$x_libraries" fi - XLIBS +="-lX11" + XLIBS+="-lX11" if test -n "$x_includes"; then - # See if we can compile X applications without using $XCFLAGS. - AC_MSG_CHECKING(if $XCFLAGS is really necessary) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[]])],[XCFLAGS= + # See if we can compile X applications without using $XCFLAGS. + AC_MSG_CHECKING(if $XCFLAGS is really necessary) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[]])],[XCFLAGS= AC_MSG_RESULT(no)],[AC_MSG_RESULT(yes)]) fi # Check for libXtst. AC_CHECK_LIB(Xtst, XTestQueryExtension, - XLIBS="$XLIBS -lXtst" - [AC_DEFINE(HAVE_LIBXTST, 1, [libXtst available])], - , - [$XLIBS]) + XLIBS="$XLIBS -lXtst" + [AC_DEFINE(HAVE_LIBXTST, 1, [libXtst available])], + , + [$XLIBS]) AC_DEFINE(HAVE_X11, 1, [X11 libraries present]) - fi + fi # Check for Xrandr # Can someone fix this for me? This is producing bizarre warnings from @@ -55,15 +55,15 @@ AC_DEFUN([SM_X_WITH_OPENGL], AM_CONDITIONAL(HAVE_X11, test "$no_x" != "yes") - AC_SUBST(XCFLAGS) - AC_SUBST(XLIBS) + AC_SUBST(XCFLAGS) + AC_SUBST(XLIBS) - # Check for libGL and libGLU. - AC_CHECK_LIB(GL, glPushMatrix, XLIBS="$XLIBS -lGL", + # Check for libGL and libGLU. + AC_CHECK_LIB(GL, glPushMatrix, XLIBS="$XLIBS -lGL", AC_MSG_ERROR([No OpenGL library could be found.]), [$XLIBS]) - AC_CHECK_LIB(GLU, gluGetString, XLIBS="$XLIBS -lGLU", + AC_CHECK_LIB(GLU, gluGetString, XLIBS="$XLIBS -lGLU", AC_MSG_ERROR([No GLU library could be found.]), [$XLIBS]) - AC_SUBST(XLIBS) + AC_SUBST(XLIBS) ]) diff --git a/src/CourseLoaderCRS.cpp b/src/CourseLoaderCRS.cpp index 77a055eb56..d4ae2a026e 100644 --- a/src/CourseLoaderCRS.cpp +++ b/src/CourseLoaderCRS.cpp @@ -163,18 +163,39 @@ bool CourseLoaderCRS::LoadFromMsd( const RString &sPath, const MsdFile &msd, Cou // infer entry::Type from the first param // todo: make sure these aren't generating bogus entries due // to a lack of songs. -aj + int iNumSongs = SONGMAN->GetNumSongs(); LOG->Trace("[CourseLoaderCRS] sParams[1] = %s",sParams[1].c_str()); // most played if( sParams[1].Left(strlen("BEST")) == "BEST" ) { - new_entry.iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1; + int iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1; + if( iChooseIndex > iNumSongs ) + { + // looking up a song that doesn't exist. + LOG->UserLog( "Course file", sPath, "is trying to load BEST%i with only %i songs installed. " + "This entry will be ignored.", iChooseIndex, iNumSongs); + out.m_bIncomplete = true; + continue; // skip this #SONG + } + + new_entry.iChooseIndex = iChooseIndex; CLAMP( new_entry.iChooseIndex, 0, 500 ); new_entry.songSort = SongSort_MostPlays; } // least played else if( sParams[1].Left(strlen("WORST")) == "WORST" ) { - new_entry.iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("WORST")) ) - 1; + int iChooseIndex = StringToInt( sParams[1].Right(sParams[1].size()-strlen("BEST")) ) - 1; + if( iChooseIndex > iNumSongs ) + { + // looking up a song that doesn't exist. + LOG->UserLog( "Course file", sPath, "is trying to load WORST%i with only %i songs installed. " + "This entry will be ignored.", iChooseIndex, iNumSongs); + out.m_bIncomplete = true; + continue; // skip this #SONG + } + + new_entry.iChooseIndex = iChooseIndex; CLAMP( new_entry.iChooseIndex, 0, 500 ); new_entry.songSort = SongSort_FewestPlays; } diff --git a/src/MusicWheel.cpp b/src/MusicWheel.cpp index 47394c8eef..5c7d9a6b6b 100644 --- a/src/MusicWheel.cpp +++ b/src/MusicWheel.cpp @@ -1405,12 +1405,8 @@ RString MusicWheel::JumpToNextGroup() for(unsigned i = 0 ; i < iNumGroups ; i++) { - RString sCurSongGroup = SONGMAN->GetSongGroupByIndex(i); - - if( m_sExpandedSectionName == sCurSongGroup ) + if( m_sExpandedSectionName == SONGMAN->GetSongGroupByIndex(i) ) { - if( SONGMAN->GetSongsOfCurrentGame(sCurSongGroup).size() == 0 ) continue; - if ( i < iNumGroups - 1 ) return SONGMAN->GetSongGroupByIndex(i+1); else @@ -1455,12 +1451,8 @@ RString MusicWheel::JumpToPrevGroup() for(unsigned i = 0 ; i < iNumGroups ; i++) { - RString sCurSongGroup = SONGMAN->GetSongGroupByIndex(i); - - if( m_sExpandedSectionName == sCurSongGroup ) + if( m_sExpandedSectionName == SONGMAN->GetSongGroupByIndex(i) ) { - if( SONGMAN->GetSongsOfCurrentGame(sCurSongGroup).size() == 0 ) continue; - if ( i > 0 ) return SONGMAN->GetSongGroupByIndex(i-1); else diff --git a/src/NotesLoaderSM.cpp b/src/NotesLoaderSM.cpp index ffe564269c..e770978675 100644 --- a/src/NotesLoaderSM.cpp +++ b/src/NotesLoaderSM.cpp @@ -78,6 +78,7 @@ void SMLoader::LoadFromTokens( out.m_StepsType = GAMEMAN->StringToStepsType( sStepsType ); out.SetDescription( sDescription ); out.SetCredit( sDescription ); // this is often used for both. + out.SetChartName(sDescription); // yeah, one more for good measure. out.SetDifficulty( StringToDifficulty(sDifficulty) ); // Handle hacks that originated back when StepMania didn't have diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index 258d1ae5d7..e10f1162a9 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -145,7 +145,6 @@ void SSCLoader::ProcessScrolls( TimingData &out, const RString sParam ) } } - bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCache ) { LOG->Trace( "Song::LoadFromSSCFile(%s)", sPath.c_str() ); @@ -446,6 +445,10 @@ bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach } case GETTING_STEP_INFO: { + if (sValueName == "CHARTNAME") + { + pNewNotes->SetChartName(sParams[1]); + } if( sValueName=="STEPSTYPE" ) { pNewNotes->m_StepsType = GAMEMAN->StringToStepsType( sParams[1] ); @@ -458,6 +461,11 @@ bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach else if( sValueName=="DESCRIPTION" ) { + if (out.m_fVersion < VERSION_CHART_NAME_TAG) + { + pNewNotes->SetChartName(sParams[1]); + } + // TODO: Make this the else clause? pNewNotes->SetDescription( sParams[1] ); } diff --git a/src/NotesLoaderSSC.h b/src/NotesLoaderSSC.h index 286a849ab2..5b35cc5c4c 100644 --- a/src/NotesLoaderSSC.h +++ b/src/NotesLoaderSSC.h @@ -28,6 +28,8 @@ const float VERSION_WARP_SEGMENT = 0.56f; const float VERSION_SPLIT_TIMING = 0.7f; /** @brief The version that moved the step's Offset higher up. */ const float VERSION_OFFSET_BEFORE_ATTACK = 0.72f; +/** @brief The version that introduced the Chart Name tag. */ +const float VERSION_CHART_NAME_TAG = 0.74f; /** * @brief The SSCLoader handles all of the parsing needed for .ssc files. diff --git a/src/NotesWriterSM.cpp b/src/NotesWriterSM.cpp index 48db17d8ff..cbcea62ef0 100644 --- a/src/NotesWriterSM.cpp +++ b/src/NotesWriterSM.cpp @@ -226,7 +226,7 @@ static RString GetSMNotesTag( const Song &song, const Steps &in ) GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName, SmEscape(in.GetDescription()).c_str()) ); lines.push_back( song.m_vsKeysoundFile.empty() ? "#NOTES:" : "#NOTES2:" ); lines.push_back( ssprintf( " %s:", GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName ) ); - RString desc = (USE_CREDIT ? in.GetCredit() : in.GetDescription()); + RString desc = (USE_CREDIT ? in.GetCredit() : in.GetChartName()); lines.push_back( ssprintf( " %s:", SmEscape(desc).c_str() ) ); lines.push_back( ssprintf( " %s:", DifficultyToString(in.GetDifficulty()).c_str() ) ); lines.push_back( ssprintf( " %d:", in.GetMeter() ) ); diff --git a/src/NotesWriterSSC.cpp b/src/NotesWriterSSC.cpp index bad8a1690e..db5bc40d50 100644 --- a/src/NotesWriterSSC.cpp +++ b/src/NotesWriterSSC.cpp @@ -302,6 +302,7 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa lines.push_back( ssprintf("//---------------%s - %s----------------", GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName, SmEscape(in.GetDescription()).c_str()) ); lines.push_back( "#NOTEDATA:;" ); // our new separator. + lines.push_back( ssprintf( "#CHARTNAME:%s:", SmEscape(in.GetChartName()).c_str())); lines.push_back( ssprintf( "#STEPSTYPE:%s;", GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName ) ); lines.push_back( ssprintf( "#DESCRIPTION:%s;", SmEscape(in.GetDescription()).c_str() ) ); lines.push_back( ssprintf( "#CHARTSTYLE:%s;", SmEscape(in.GetChartStyle()).c_str() ) ); diff --git a/src/ScreenEdit.cpp b/src/ScreenEdit.cpp index 804c6d20f6..013295415d 100644 --- a/src/ScreenEdit.cpp +++ b/src/ScreenEdit.cpp @@ -600,6 +600,7 @@ static MenuDef g_StepsInformation( "ScreenMiniMenuStepsInformation", MenuRowDef( ScreenEdit::difficulty, "Difficulty", true, EditMode_Practice, true, true, 0, NULL ), MenuRowDef( ScreenEdit::meter, "Meter", true, EditMode_Practice, true, false, 0, NULL ), + MenuRowDef( ScreenEdit::chartname, "Chart Name", true, EditMode_Practice, true, true, 0, NULL ), MenuRowDef( ScreenEdit::description, "Description", true, EditMode_Practice, true, true, 0, NULL ), MenuRowDef( ScreenEdit::chartstyle, "Chart Style", true, EditMode_Practice, true, true, 0, NULL ), MenuRowDef( ScreenEdit::step_credit, "Step Author", true, EditMode_Practice, true, true, 0, NULL ), @@ -1121,6 +1122,7 @@ static LocalizedString NOTES("ScreenEdit", "%s notes"); static LocalizedString SELECTION_BEAT("ScreenEdit", "Selection beat"); static LocalizedString DIFFICULTY("ScreenEdit", "Difficulty"); static LocalizedString ROUTINE_PLAYER("ScreenEdit", "Routine Player"); +static LocalizedString CHART_NAME("ScreenEdit", "Chart Name"); static LocalizedString DESCRIPTION("ScreenEdit", "Description"); static LocalizedString CHART_STYLE("ScreenEdit", "Chart Style"); static LocalizedString STEP_AUTHOR("ScreenEdit", "Step Author"); @@ -1151,6 +1153,7 @@ static ThemeMetric SELECTION_BEAT_UNFINISHED_FORMAT("ScreenEdit", "Sele static ThemeMetric SELECTION_BEAT_END_FORMAT("ScreenEdit", "SelectionBeatEndFormat"); static ThemeMetric DIFFICULTY_FORMAT("ScreenEdit", "DifficultyFormat"); static ThemeMetric ROUTINE_PLAYER_FORMAT("ScreenEdit", "RoutinePlayerFormat"); +static ThemeMetric CHART_NAME_FORMAT("ScreenEdit", "ChartNameFormat"); static ThemeMetric DESCRIPTION_FORMAT("ScreenEdit", "DescriptionFormat"); static ThemeMetric CHART_STYLE_FORMAT("ScreenEdit", "ChartStyleFormat"); static ThemeMetric STEP_AUTHOR_FORMAT("ScreenEdit", "StepAuthorFormat"); @@ -1222,7 +1225,8 @@ void ScreenEdit::UpdateTextInfo() sText += ssprintf( DIFFICULTY_FORMAT.GetValue(), DIFFICULTY.GetValue().c_str(), DifficultyToString( m_pSteps->GetDifficulty() ).c_str() ); if ( m_InputPlayerNumber != PLAYER_INVALID ) sText += ssprintf( ROUTINE_PLAYER_FORMAT.GetValue(), ROUTINE_PLAYER.GetValue().c_str(), m_InputPlayerNumber + 1 ); - sText += ssprintf( DESCRIPTION_FORMAT.GetValue(), DESCRIPTION.GetValue().c_str(), m_pSteps->GetDescription().c_str() ); + //sText += ssprintf( DESCRIPTION_FORMAT.GetValue(), DESCRIPTION.GetValue().c_str(), m_pSteps->GetDescription().c_str() ); + sText += ssprintf( CHART_NAME_FORMAT.GetValue(), CHART_NAME.GetValue().c_str(), m_pSteps->GetChartName().c_str() ); sText += ssprintf( STEP_AUTHOR_FORMAT.GetValue(), STEP_AUTHOR.GetValue().c_str(), m_pSteps->GetCredit().c_str() ); //sText += ssprintf( CHART_STYLE_FORMAT.GetValue(), CHART_STYLE.GetValue().c_str(), m_pSteps->GetChartStyle().c_str() ); sText += ssprintf( MAIN_TITLE_FORMAT.GetValue(), MAIN_TITLE.GetValue().c_str(), m_pSong->m_sMainTitle.c_str() ); @@ -3385,6 +3389,12 @@ static void ChangeDescription( const RString &sNew ) pSteps->SetDescription(sNew); } +static void ChangeChartName( const RString &sNew ) +{ + Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; + pSteps->SetChartName(sNew); +} + static void ChangeChartStyle( const RString &sNew ) { Steps* pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; @@ -3657,6 +3667,8 @@ void ScreenEdit::HandleMainMenuChoice( MainMenuChoice c, const vector &iAns g_StepsInformation.rows[meter].SetOneUnthemedChoice( ssprintf("%d", pSteps->GetMeter()) ); g_StepsInformation.rows[meter].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Home); g_StepsInformation.rows[predict_meter].SetOneUnthemedChoice( ssprintf("%.2f",pSteps->PredictMeter()) ); + g_StepsInformation.rows[chartname].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full); + g_StepsInformation.rows[chartname].SetOneUnthemedChoice(pSteps->GetChartName()); g_StepsInformation.rows[description].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full); g_StepsInformation.rows[description].SetOneUnthemedChoice( pSteps->GetDescription() ); g_StepsInformation.rows[chartstyle].bEnabled = (EDIT_MODE.GetValue() >= EditMode_Full); @@ -4253,6 +4265,7 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, const vector &iAns } static LocalizedString ENTER_NEW_DESCRIPTION( "ScreenEdit", "Enter a new description." ); +static LocalizedString ENTER_NEW_CHART_NAME("ScreenEdit", "Enter a new chart name."); static LocalizedString ENTER_NEW_CHART_STYLE( "ScreenEdit", "Enter a new chart style." ); static LocalizedString ENTER_NEW_STEP_AUTHOR( "ScreenEdit", "Enter the author who made this step pattern." ); static LocalizedString ENTER_NEW_METER( "ScreenEdit", "Enter a new meter." ); @@ -4264,17 +4277,27 @@ void ScreenEdit::HandleStepsInformationChoice( StepsInformationChoice c, const v switch( c ) { - case description: - ScreenTextEntry::TextEntry( - SM_None, - ENTER_NEW_DESCRIPTION, - m_pSteps->GetDescription(), - MAX_STEPS_DESCRIPTION_LENGTH, - SongUtil::ValidateCurrentStepsDescription, - ChangeDescription, - NULL - ); - break; + case chartname: + { + ScreenTextEntry::TextEntry(SM_None, + ENTER_NEW_CHART_NAME, + m_pSteps->GetChartName(), + MAX_STEPS_DESCRIPTION_LENGTH, + SongUtil::ValidateCurrentStepsChartName, + ChangeChartName, + NULL); + break; + } + case description: + { + ScreenTextEntry::TextEntry(SM_None, + ENTER_NEW_DESCRIPTION, + m_pSteps->GetDescription(), + MAX_STEPS_DESCRIPTION_LENGTH, + SongUtil::ValidateCurrentStepsDescription, + ChangeDescription,NULL); + break; + } case chartstyle: ScreenTextEntry::TextEntry( SM_None, diff --git a/src/ScreenEdit.h b/src/ScreenEdit.h index 38f0a4bbad..416b71b1f5 100644 --- a/src/ScreenEdit.h +++ b/src/ScreenEdit.h @@ -503,8 +503,9 @@ public: enum StepsInformationChoice { - difficulty, - meter, + difficulty, /**< What is the difficulty of this chart? */ + meter, /**< What is the numerical rating of this chart? */ + chartname, /**< What is the name of this chart? */ description, /**< What is the description of this chart? */ chartstyle, /**< How is this chart meant to be played? */ step_credit, /**< Who wrote this individual chart? */ diff --git a/src/Song.cpp b/src/Song.cpp index 97d62a728b..4c695b8c42 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -41,7 +41,7 @@ * @brief The internal version of the cache for StepMania. * * Increment this value to invalidate the current cache. */ -const int FILE_CACHE_VERSION = 187; +const int FILE_CACHE_VERSION = 188; /** @brief How long does a song sample last by default? */ const float DEFAULT_MUSIC_SAMPLE_LENGTH = 12.f; diff --git a/src/Song.h b/src/Song.h index 1858a136a0..2931e6d82e 100644 --- a/src/Song.h +++ b/src/Song.h @@ -20,7 +20,7 @@ void FixupPath( RString &path, const RString &sSongPath ); RString GetSongAssetPath( RString sPath, const RString &sSongPath ); /** @brief The version of the .ssc file format. */ -const static float STEPFILE_VERSION_NUMBER = 0.73f; +const static float STEPFILE_VERSION_NUMBER = 0.74f; /** @brief How many edits for this song can each profile have? */ const int MAX_EDITS_PER_SONG_PER_PROFILE = 15; @@ -39,14 +39,6 @@ enum BackgroundLayer BACKGROUND_LAYER_Invalid }; -/** @brief The different ways of displaying the BPM. */ -enum DisplayBPM -{ - DISPLAY_BPM_ACTUAL, /**< Display the song's actual BPM. */ - DISPLAY_BPM_SPECIFIED, /**< Display a specified value or values. */ - DISPLAY_BPM_RANDOM /**< Display a random selection of BPMs. */ -}; - /** @brief A custom foreach loop for the different background layers. */ #define FOREACH_BackgroundLayer( bl ) FOREACH_ENUM( BackgroundLayer, bl ) diff --git a/src/SongUtil.cpp b/src/SongUtil.cpp index 36f9aa72ee..506b489b0e 100644 --- a/src/SongUtil.cpp +++ b/src/SongUtil.cpp @@ -749,6 +749,22 @@ bool SongUtil::IsEditDescriptionUnique( const Song* pSong, StepsType st, const R return true; } +bool SongUtil::IsChartNameUnique( const Song* pSong, StepsType st, const RString &name, const Steps *pExclude ) +{ + FOREACH_CONST( Steps*, pSong->GetAllSteps(), s ) + { + Steps *pSteps = *s; + + if( pSteps->m_StepsType != st ) + continue; + if( pSteps == pExclude ) + continue; + if( pSteps->GetChartName() == name ) + return false; + } + return true; +} + RString SongUtil::MakeUniqueEditDescription( const Song *pSong, StepsType st, const RString &sPreferredDescription ) { if( IsEditDescriptionUnique( pSong, st, sPreferredDescription, NULL ) ) @@ -772,8 +788,11 @@ RString SongUtil::MakeUniqueEditDescription( const Song *pSong, StepsType st, co } static LocalizedString YOU_MUST_SUPPLY_NAME ( "SongUtil", "You must supply a name for your new edit." ); +static LocalizedString CHART_NAME_CONFLICTS ("SongUtil", "The name you chose conflicts with another chart. Please use a different name."); static LocalizedString EDIT_NAME_CONFLICTS ( "SongUtil", "The name you chose conflicts with another edit. Please use a different name." ); +static LocalizedString CHART_NAME_CANNOT_CONTAIN ("SongUtil", "The chart name cannot contain any of the following characters: %s" ); static LocalizedString EDIT_NAME_CANNOT_CONTAIN ( "SongUtil", "The edit name cannot contain any of the following characters: %s" ); + bool SongUtil::ValidateCurrentEditStepsDescription( const RString &sAnswer, RString &sErrorOut ) { Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; @@ -833,6 +852,31 @@ bool SongUtil::ValidateCurrentStepsDescription( const RString &sAnswer, RString return true; } +bool SongUtil::ValidateCurrentStepsChartName(const RString &answer, RString &error) +{ + if (answer.empty()) return true; + + static const RString sInvalidChars = "\\/:*?\"<>|"; + if( strpbrk(answer, sInvalidChars) != NULL ) + { + error = ssprintf( CHART_NAME_CANNOT_CONTAIN.GetValue(), sInvalidChars.c_str() ); + return false; + } + + /* Don't allow duplicate title names within the same StepsType. + * We need some way of identifying the unique charts. */ + Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; + + if (pSteps->GetChartName() == answer) return true; + + // TODO next commit: borrow code from EditStepsDescription. + bool result = SongUtil::IsChartNameUnique(GAMESTATE->m_pCurSong, pSteps->m_StepsType, + answer, pSteps); + if (!result) + error = CHART_NAME_CONFLICTS; + return result; +} + static LocalizedString AUTHOR_NAME_CANNOT_CONTAIN( "SongUtil", "The step author's name cannot contain any of the following characters: %s" ); bool SongUtil::ValidateCurrentStepsCredit( const RString &sAnswer, RString &sErrorOut ) diff --git a/src/SongUtil.h b/src/SongUtil.h index 4ecbcb8687..f09bf2ff8c 100644 --- a/src/SongUtil.h +++ b/src/SongUtil.h @@ -159,10 +159,12 @@ namespace SongUtil * @return true if it is unique, false otherwise. */ bool IsEditDescriptionUnique( const Song* pSong, StepsType st, const RString &sPreferredDescription, const Steps *pExclude ); + bool IsChartNameUnique( const Song* pSong, StepsType st, const RString &name, const Steps *pExclude ); RString MakeUniqueEditDescription( const Song* pSong, StepsType st, const RString &sPreferredDescription ); bool ValidateCurrentEditStepsDescription( const RString &sAnswer, RString &sErrorOut ); bool ValidateCurrentStepsDescription( const RString &sAnswer, RString &sErrorOut ); bool ValidateCurrentStepsCredit( const RString &sAnswer, RString &sErrorOut ); + bool ValidateCurrentStepsChartName(const RString &answer, RString &error); void GetAllSongGenres( vector &vsOut ); /** diff --git a/src/Steps.cpp b/src/Steps.cpp index 1b8f932020..3ff6c97499 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -34,12 +34,29 @@ Steps::Steps(): m_StepsType(StepsType_Invalid), m_sDescription(""), m_sChartStyle(""), m_Difficulty(Difficulty_Invalid), m_iMeter(0), m_bAreCachedRadarValuesJustLoaded(false), - m_sCredit("") {} + m_sCredit(""), displayBPMType(DISPLAY_BPM_ACTUAL), + specifiedBPMMin(0), specifiedBPMMax(0) {} Steps::~Steps() { } +void Steps::GetDisplayBpms( DisplayBpms &AddTo ) const +{ + if( this->GetDisplayBPM() == DISPLAY_BPM_SPECIFIED ) + { + AddTo.Add( this->GetMinBPM() ); + AddTo.Add( this->GetMaxBPM() ); + } + else + { + float fMinBPM, fMaxBPM; + this->m_Timing.GetActualBPM( fMinBPM, fMaxBPM ); + AddTo.Add( fMinBPM ); + AddTo.Add( fMaxBPM ); + } +} + bool Steps::HasAttacks() const { return !this->m_Attacks.empty(); @@ -507,6 +524,44 @@ public: lua_pushstring( L, out ); return 1; } + + static int GetChartName(T *p, lua_State *L) + { + lua_pushstring(L, p->GetChartName()); + return 1; + } + + static int GetDisplayBpms( T* p, lua_State *L ) + { + DisplayBpms temp; + p->GetDisplayBpms(temp); + float fMin = temp.GetMin(); + float fMax = temp.GetMax(); + vector fBPMs; + fBPMs.push_back( fMin ); + fBPMs.push_back( fMax ); + LuaHelpers::CreateTableFromArray(fBPMs, L); + return 1; + } + static int IsDisplayBpmSecret( T* p, lua_State *L ) + { + DisplayBpms temp; + p->GetDisplayBpms(temp); + lua_pushboolean( L, temp.IsSecret() ); + return 1; + } + static int IsDisplayBpmConstant( T* p, lua_State *L ) + { + DisplayBpms temp; + p->GetDisplayBpms(temp); + lua_pushboolean( L, temp.BpmIsConstant() ); + return 1; + } + static int IsDisplayBpmRandom( T* p, lua_State *L ) + { + lua_pushboolean( L, p->GetDisplayBPM() == DISPLAY_BPM_RANDOM ); + return 1; + } LunaSteps() { @@ -521,12 +576,17 @@ public: ADD_METHOD( HasAttacks ); ADD_METHOD( GetRadarValues ); ADD_METHOD( GetTimingData ); + ADD_METHOD( GetChartName ); //ADD_METHOD( GetSMNoteData ); ADD_METHOD( GetStepsType ); ADD_METHOD( IsAnEdit ); ADD_METHOD( IsAutogen ); ADD_METHOD( IsAPlayerEdit ); ADD_METHOD( UsesSplitTiming ); + ADD_METHOD( GetDisplayBpms ); + ADD_METHOD( IsDisplayBpmSecret ); + ADD_METHOD( IsDisplayBpmConstant ); + ADD_METHOD( IsDisplayBpmRandom ); } }; diff --git a/src/Steps.h b/src/Steps.h index 08cbd1d357..a6a4aded2e 100644 --- a/src/Steps.h +++ b/src/Steps.h @@ -22,6 +22,14 @@ struct lua_State; */ const int MAX_STEPS_DESCRIPTION_LENGTH = 255; +/** @brief The different ways of displaying the BPM. */ +enum DisplayBPM +{ + DISPLAY_BPM_ACTUAL, /**< Display the song's actual BPM. */ + DISPLAY_BPM_SPECIFIED, /**< Display a specified value or values. */ + DISPLAY_BPM_RANDOM /**< Display a random selection of BPMs. */ +}; + /** * @brief Holds note information for a Song. * @@ -98,6 +106,16 @@ public: /** @brief The stringified list of attacks. */ vector m_sAttackString; + RString GetChartName() const + { + return parent ? Real()->GetChartName() : this->chartName; + } + + void SetChartName(const RString name) + { + this->chartName = name; + } + void SetFilename( RString fn ) { m_sFilename = fn; } RString GetFilename() const { return m_sFilename; } void SetSavedToDisk( bool b ) { DeAutogen(); m_bSavedToDisk = b; } @@ -151,6 +169,36 @@ public: * @brief Determine if the Steps use Split Timing by comparing the Song it's in. * @return true if the Step and Song use different timings, false otherwise. */ bool UsesSplitTiming() const; + + void SetDisplayBPM(const DisplayBPM type) + { + this->displayBPMType = type; + } + + DisplayBPM GetDisplayBPM() const + { + return this->displayBPMType; + } + + void SetMinBPM(const float f) + { + this->specifiedBPMMin = f; + } + float GetMinBPM() const + { + return this->specifiedBPMMin; + } + + void SetMaxBPM(const float f) + { + this->specifiedBPMMax = f; + } + float GetMaxBPM() const + { + return this->specifiedBPMMax; + } + + void GetDisplayBpms( DisplayBpms &addTo) const; private: inline const Steps *Real() const { return parent ? parent : this; } @@ -194,6 +242,19 @@ private: bool m_bAreCachedRadarValuesJustLoaded; /** @brief The name of the person who created the Steps. */ RString m_sCredit; + + /** @brief The name of the chart. */ + RString chartName; + + /** @brief How is the BPM displayed for this chart? */ + DisplayBPM displayBPMType; + /** @brief What is the minimum specified BPM? */ + float specifiedBPMMin; + /** + * @brief What is the maximum specified BPM? + * + * If this is a range, then min should not be equal to max. */ + float specifiedBPMMax; }; #endif