From b4bd8e82d9f0b07f765a47d418b7ca3580602fc4 Mon Sep 17 00:00:00 2001 From: AJ Kelly Date: Thu, 22 Nov 2012 12:42:12 -0600 Subject: [PATCH] Add various new bindings for ProfileManager, also GetGUID binding for Profile. [asorenson] --- Docs/Changelog_sm5.txt | 7 + Docs/credits.txt | 361 +- .../BGAnimations/ScreenCredits overlay.lua | 319 +- .../BGAnimations/ScreenCredits overlay.lua | 327 +- src/Profile.cpp | 4268 +++++++++-------- src/ProfileManager.cpp | 37 + 6 files changed, 2685 insertions(+), 2634 deletions(-) diff --git a/Docs/Changelog_sm5.txt b/Docs/Changelog_sm5.txt index b9d91cf003..3b23438ea4 100644 --- a/Docs/Changelog_sm5.txt +++ b/Docs/Changelog_sm5.txt @@ -4,6 +4,13 @@ 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. ________________________________________________________________________________ +2012/11/22 +---------- +* [ProfileManager] Added SaveProfile, SaveLocalProfile, GetLocalProfileIDs, + ProfileFromMemoryCardIsNew, GetSongNumTimesPlayed, LocalProfileIDToDir, and + GetLocalProfileDisplayNames Lua bindings. [Alfred Sorenson] +* [Profile] Add GetGUID Lua binding. [Alfred Sorenson] + 2012/07/26 ---------- * [ScreenDebugOverlay] Added BackgroundColor metric. [AJ] diff --git a/Docs/credits.txt b/Docs/credits.txt index d83939c271..f95ef81e09 100644 --- a/Docs/credits.txt +++ b/Docs/credits.txt @@ -1,179 +1,182 @@ -StepMania 5 credits (in no particular order) --------------------------------------------- -If you have any corrections to this list, let us know (via forums or IRC). - -==StepMania Team== -Chris Danford, Glenn Maynard, Steve Checkoway, et al - * Keeping StepMania 5 alive with changes, and providing a nice codebase for - us to work with. - -==the spinal shark collective== -AJ Kelly as freem - * (sm-ssc) Project Coordinator/Leader - * Main programming - * Various Lua scripts - * Some small theme edits here and there - * Noteskins (retro, retrobar*) - * Windows project file maintainer (mostly) - -Midiman - * Did most of the work on _fallback and default themes - * Some programming - * Noteskins (midi-*) - -shakesoda - * Programming - * Noteskins (delta, gamma, port of orbital to kb7) - * Theme edits - * Mac OS X primary maintainer - -==sm-ssc Team== -(in no particular order) - -Aldo_MX - * Code (Pump it Up delays, among others), ideas, and support. - -cerbo - * [SongManager] SetPreferredSongs() and SetPreferredCourses() Lua bindings - -Daisuke Master - * Programming - * Pump/simple and Pump/complex noteskins - * /Docs/Themerdocs/Noteskin elements Reference.txt - * Taking advantage of sm-ssc's new features :) - -FSX - * Various fixes. (see Changelogs for more details) - -Nicole Reid (okeeblow) - * Tested building sm-ssc on FreeBSD, provided fixes. - (fixes that still need to get implemented) - -Thai Pangsakulyanont (theDtTvB) - * Changes to BMS loading/playback behavior to make things work better. - -Jason Felds (Wolfman2000) - * [Player] PercentUntilColorCombo metric - * Mac OS X maintainer - * Various fixes/changes. (see Changelogs for more details) - -==other contributors== -(in no particular order) - -1a2a3a2a1a - * Various beat mode fixes - -Kaox - * Pump/default noteskin - -cesarmades - * Pump/cmd-* noteskins - -David Santamara Rogado (howl) - * Various patches (see Changelogs for more details and links) - -Kita K./D. Trucks - * default theme music - -kurisu - * Dance-threepanel gametype code (3.9; re-adapted for sm-ssc) - -v1toko - * Lead coder of StepNXA, which is where we got the original XMode code from. - -Macgravel - * Orbular noteskin (the default kb7 noteskin) - -hanubeki (@803832, formerly sy567) - * Small beginner helper fix - (http://www.stepmania.com/forums/showpost.php?p=158721&postcount=12) - * Lots of code changes from hanubeki-modified-sm-ssc: - http://code.google.com/r/hanubeki-modified-sm-ssc/ - -galopin - * Pump it Up Exceed PlayStation 2 USB dance mat support patch - (stepmania-devs mailing list 20091213 11:27 -0800 [Pacific Standard Time]) - -Henke37 - * [MovieTexture_FFMpeg] Stutter fix - (http://www.pasteall.org/11353/diff) - -corec - * [ArchHooks_Unix, MovieTexture_FFMpeg] Make sm-ssc work with system's FFMpeg. - (http://github.com/corec/cc-overlay/blob/master/games-arcade/sm-ssc/files/sm-ssc-ffmpeg-fixes-01.patch) - * [InputMapper] Enabling upper diagonal keys for Positive Gaming Impact dance mat - (http://ssc.ajworld.net/sm-ssc/bugtracker/view.php?id=137) - -gholms - * Make autogen.sh look for automake 1.11 - (http://ssc.ajworld.net/sm-ssc/bugtracker/view.php?id=140) - -juanelote - * [SongManager] GetSongGroupByIndex function - * [MusicWheel] JumpToNextGroup/JumpToPrevGroup logic modifications - -NitroX72 - * Pump/frame noteskin - -Tatsh - * A patch to fix building with system ffmpeg - (http://ssc.ajworld.net/sm-ssc/bugtracker/view.php?id=218) - -A.C (@waiei) - * Hybrid scoring mode + scoring fixes - * Various patches/fixes/bug reporting - -John_Reactor - * Polish translation - -DHalens - * Spanish translation - -桜為小鳩/Sakurana-Kobato (@sakuraponila) - * Scoring fixes - -@Niler_jp - * Japanese translation - -Deamon007 - * Dutch translation - -cvpcs - * Updated ffmpeg support to v0.10 - -djpohly - * Various patches/fixes - -==the beta testers== -[SSC Beta Testing Team] -KeithD -tweak/tweak62 - -[Friends of the Devs] -And4713 -HankPeters -okeeblow -kdaymea -taiyal - -[Members of Team Step Masters (http://www.team-stepmasters.net/foro/)] -Daisuke Master -Nueel/emmanuel virus -Fye -Bijou -Nek0 -Jib -Robe -Urqui -h4m573r -Torta - -[Private Beta Wave 1.x Testers] -FSX -Sniper257 -Wolfman2000 -NitroX72 -Wanny -Tio -Cerbo -Daisuke Master +StepMania 5 credits (in no particular order) +-------------------------------------------- +If you have any corrections to this list, let us know (via forums or IRC). + +==StepMania Team== +Chris Danford, Glenn Maynard, Steve Checkoway, et al + * Keeping StepMania 5 alive with changes, and providing a nice codebase for + us to work with. + +==the spinal shark collective== +AJ Kelly as freem + * (sm-ssc) Project Coordinator/Leader + * Main programming + * Various Lua scripts + * Some small theme edits here and there + * Noteskins (retro, retrobar*) + * Windows project file maintainer (mostly) + +Midiman + * Did most of the work on _fallback and default themes + * Some programming + * Noteskins (midi-*) + +shakesoda + * Programming + * Noteskins (delta, gamma, port of orbital to kb7) + * Theme edits + * Mac OS X primary maintainer + +==sm-ssc Team== +(in no particular order) + +Aldo_MX + * Code (Pump it Up delays, among others), ideas, and support. + +cerbo + * [SongManager] SetPreferredSongs() and SetPreferredCourses() Lua bindings + +Daisuke Master + * Programming + * Pump/simple and Pump/complex noteskins + * /Docs/Themerdocs/Noteskin elements Reference.txt + * Taking advantage of sm-ssc's new features :) + +FSX + * Various fixes. (see Changelogs for more details) + +Nicole Reid (okeeblow) + * Tested building sm-ssc on FreeBSD, provided fixes. + (fixes that still need to get implemented) + +Thai Pangsakulyanont (theDtTvB) + * Changes to BMS loading/playback behavior to make things work better. + +Jason Felds (Wolfman2000) + * [Player] PercentUntilColorCombo metric + * Mac OS X maintainer + * Various fixes/changes. (see Changelogs for more details) + +djpohly + * Various patches/fixes + +==other contributors== +(in no particular order) + +1a2a3a2a1a + * Various beat mode fixes + +Kaox + * Pump/default noteskin + +cesarmades + * Pump/cmd-* noteskins + +David Santamara Rogado (howl) + * Various patches (see Changelogs for more details and links) + +Kita K./D. Trucks + * default theme music + +kurisu + * Dance-threepanel gametype code (3.9; re-adapted for sm-ssc) + +v1toko + * Lead coder of StepNXA, which is where we got the original XMode code from. + +Macgravel + * Orbular noteskin (the default kb7 noteskin) + +hanubeki (@803832, formerly sy567) + * Small beginner helper fix + (http://www.stepmania.com/forums/showpost.php?p=158721&postcount=12) + * Lots of code changes from hanubeki-modified-sm-ssc: + http://code.google.com/r/hanubeki-modified-sm-ssc/ + +galopin + * Pump it Up Exceed PlayStation 2 USB dance mat support patch + (stepmania-devs mailing list 20091213 11:27 -0800 [Pacific Standard Time]) + +Henke37 + * [MovieTexture_FFMpeg] Stutter fix + (http://www.pasteall.org/11353/diff) + +corec + * [ArchHooks_Unix, MovieTexture_FFMpeg] Make sm-ssc work with system's FFMpeg. + (http://github.com/corec/cc-overlay/blob/master/games-arcade/sm-ssc/files/sm-ssc-ffmpeg-fixes-01.patch) + * [InputMapper] Enabling upper diagonal keys for Positive Gaming Impact dance mat + (http://ssc.ajworld.net/sm-ssc/bugtracker/view.php?id=137) + +gholms + * Make autogen.sh look for automake 1.11 + (http://ssc.ajworld.net/sm-ssc/bugtracker/view.php?id=140) + +juanelote + * [SongManager] GetSongGroupByIndex function + * [MusicWheel] JumpToNextGroup/JumpToPrevGroup logic modifications + +NitroX72 + * Pump/frame noteskin + +Tatsh + * A patch to fix building with system ffmpeg + (http://ssc.ajworld.net/sm-ssc/bugtracker/view.php?id=218) + +A.C (@waiei) + * Hybrid scoring mode + scoring fixes + * Various patches/fixes/bug reporting + +John_Reactor + * Polish translation + +DHalens + * Spanish translation + +桜為小鳩/Sakurana-Kobato (@sakuraponila) + * Scoring fixes + +@Niler_jp + * Japanese translation + +Deamon007 + * Dutch translation + +cvpcs + * Updated ffmpeg support to v0.10 + +Alfred Sorenson + * Various small additions + +==the beta testers== +[SSC Beta Testing Team] +KeithD +tweak/tweak62 + +[Friends of the Devs] +And4713 +HankPeters +okeeblow +kdaymea +taiyal + +[Members of Team Step Masters (http://www.team-stepmasters.net/foro/)] +Daisuke Master +Nueel/emmanuel virus +Fye +Bijou +Nek0 +Jib +Robe +Urqui +h4m573r +Torta + +[Private Beta Wave 1.x Testers] +FSX +Sniper257 +Wolfman2000 +NitroX72 +Wanny +Tio +Cerbo +Daisuke Master diff --git a/Themes/_fallback/BGAnimations/ScreenCredits overlay.lua b/Themes/_fallback/BGAnimations/ScreenCredits overlay.lua index b695311480..02a2299b0c 100644 --- a/Themes/_fallback/BGAnimations/ScreenCredits overlay.lua +++ b/Themes/_fallback/BGAnimations/ScreenCredits overlay.lua @@ -1,160 +1,161 @@ -local function Fooled() - local phrases = { - "hornswaggled", - "bamboozled", - "hoodwinked", - "swindled", - "duped", - "hoaxed", - "fleeced", - "shafted", - "caboodled", - "beguiled", - "finagled", - "two-timed", - "suckered", - "flimflammed" - } - return phrases[math.random(#phrases)] -end - -local ssc = { - "AJ Kelly as freem", - "Jonathan Payne (Midiman)", - "Colby Klein (shakesoda)", -} - -local sm_ssc = { - "Jason Felds (wolfman2000)", -- Timing Segments, Split Timing, optimization - "Thai Pangsakulyanont (theDtTvB)", -- BMS, Split Timing, optimization - "Alberto Ramos (Daisuke Master)", - "Jack Walstrom (FSX)", -} - -local stepmania = { - "Chris Danford", - "Glenn Maynard", - "Steve Checkoway", - -- and various other contributors -} - -local oitg = { - "infamouspat", - "Mark Cannon (vyhd)", -} - -local contrib = { - "Aldo Fregoso (Aldo_MX)", -- delays and much more. StepMania AMX creator - "A.C/@waiei", -- custom scoring fixes + Hybrid scoring - "cerbo", -- lua bindings and other fun stuff - "cesarmades", -- pump/cmd* noteskins - "Chris Eldridge (kurisu)", -- dance-threepanel stepstype support - "Christophe Goulet-LeBlanc (Kommisar)", -- songs - "corec", -- various fixes - "galopin", -- piu PlayStation2 usb mat support - "gholms", -- automake 1.11 support - "juanelote", -- SongManager:GetSongGroupByIndex, JumpToNext/PrevGroup logic mods - "Kaox", -- pump/default noteskin - "NitroX72", -- pump/frame noteskin - "Petriform", -- default theme music - "桜為小鳩/Sakurana-Kobato (@sakuraponila)", -- custom scoring fixes - "Samuel Kim (1a2a3a2a1a)", -- various beat mode fixes - "hanubeki (@803832)", -- beginner helper fix, among various other things - "v1toko", -- x-mode from StepNXA -} - -local translators = { - "John Reactor (Polish)", - "DHalens (Spanish)", - "@Niler_jp (Japanese)", - "Deamon007 (Dutch)" -} - -local thanks = { - "A Pseudonymous Coder", -- support - "Bill Shillito (DM Ashura)", -- Music (not yet though) - "cpubasic13", -- testing (a lot) - "Dreamwoods", - "Jason Bolt (LightningXCE)", - "Jousway", -- Noteskins - "Luizsan", -- creator of Delta theme - "Matt1360", -- Automake magic + oitg bro - "Renard", - "Ryan McKanna (Plaguefox)", - "Sta Kousin", --help with Japanese bug reports -} - -local shoutout = { - "The Lua team", -- lua project lead or some shit. super nerdy but oh hell. - "Mojang AB", -- minecraft forever -freem - "Wolfire Games", -- piles of inspiration - "NAKET Coder", - "Ciera Boyd", -- you bet your ass I'm putting my girlfriend in the credits - --Image(), -- we should have some logos probably to look super pro - "#KBO", - "Celestia Radio", -- LOVE AND TOLERANCE - "You showed us... your ultimate dance", -} - -local copyright = { - "StepMania is released under the terms of the MIT license.", - "If you paid for the program you've been " .. Fooled() .. ".", - "All content is the sole property of their respectful owners." -} - -local sections = { - { "the spinal shark collective (project lead)", ssc }, - { "sm-ssc Team", sm_ssc }, - { "StepMania Team", stepmania }, - { "OpenITG Team", oitg }, - { "Translators", translators }, - { "Other Contributors", contrib }, - { "Special Thanks", thanks }, - { "Shoutouts", shoutout }, - { "Copyright", copyright }, -} - --- To add people or sections modify the above. - -local lineOn = cmd(zoom,0.875;strokecolor,color("#444444");shadowcolor,color("#444444");shadowlength,3) -local sectionOn = cmd(diffuse,color("#88DDFF");strokecolor,color("#446688");shadowcolor,color("#446688");shadowlength,3) -local item_padding_start = 4; - -local creditScroller = Def.ActorScroller { - SecondsPerItem = 0.5; - NumItemsToDraw = 40; - TransformFunction = function( self, offset, itemIndex, numItems) - self:y(30*offset) - end; - OnCommand = cmd(scrollwithpadding,item_padding_start,15); -} - -local function AddLine( text, command ) - local text = Def.ActorFrame{ - LoadFont("Common normal")..{ - Text = text or ""; - OnCommand = command or lineOn; - } - } - table.insert( creditScroller, text ) -end - --- Add sections with padding. -for section in ivalues(sections) do - AddLine( section[1], sectionOn ) - for name in ivalues(section[2]) do - AddLine( name ) - end - AddLine() - AddLine() -end - -creditScroller.BeginCommand=function(self) - SCREENMAN:GetTopScreen():PostScreenMessage( 'SM_MenuTimer', (creditScroller.SecondsPerItem * (#creditScroller + item_padding_start) + 10) ); -end; - -return Def.ActorFrame{ - creditScroller..{ - InitCommand=cmd(CenterX;y,SCREEN_BOTTOM-64), - } +local function Fooled() + local phrases = { + "hornswaggled", + "bamboozled", + "hoodwinked", + "swindled", + "duped", + "hoaxed", + "fleeced", + "shafted", + "caboodled", + "beguiled", + "finagled", + "two-timed", + "suckered", + "flimflammed" + } + return phrases[math.random(#phrases)] +end + +local ssc = { + "AJ Kelly as freem", + "Jonathan Payne (Midiman)", + "Colby Klein (shakesoda)", +} + +local sm_ssc = { + "Jason Felds (wolfman2000)", -- Timing Segments, Split Timing, optimization + "Thai Pangsakulyanont (theDtTvB)", -- BMS, Split Timing, optimization + "Alberto Ramos (Daisuke Master)", + "Jack Walstrom (FSX)", +} + +local stepmania = { + "Chris Danford", + "Glenn Maynard", + "Steve Checkoway", + -- and various other contributors +} + +local oitg = { + "infamouspat", + "Mark Cannon (vyhd)", +} + +local contrib = { + "Aldo Fregoso (Aldo_MX)", -- delays and much more. StepMania AMX creator + "A.C/@waiei", -- custom scoring fixes + Hybrid scoring + "cerbo", -- lua bindings and other fun stuff + "cesarmades", -- pump/cmd* noteskins + "Chris Eldridge (kurisu)", -- dance-threepanel stepstype support + "Christophe Goulet-LeBlanc (Kommisar)", -- songs + "corec", -- various fixes + "galopin", -- piu PlayStation2 usb mat support + "gholms", -- automake 1.11 support + "juanelote", -- SongManager:GetSongGroupByIndex, JumpToNext/PrevGroup logic mods + "Kaox", -- pump/default noteskin + "NitroX72", -- pump/frame noteskin + "Petriform", -- default theme music + "桜為小鳩/Sakurana-Kobato (@sakuraponila)", -- custom scoring fixes + "Samuel Kim (1a2a3a2a1a)", -- various beat mode fixes + "hanubeki (@803832)", -- beginner helper fix, among various other things + "v1toko", -- x-mode from StepNXA + "Alfred Sorenson", -- new lua bindings +} + +local translators = { + "John Reactor (Polish)", + "DHalens (Spanish)", + "@Niler_jp (Japanese)", + "Deamon007 (Dutch)" +} + +local thanks = { + "A Pseudonymous Coder", -- support + "Bill Shillito (DM Ashura)", -- Music (not yet though) + "cpubasic13", -- testing (a lot) + "Dreamwoods", + "Jason Bolt (LightningXCE)", + "Jousway", -- Noteskins + "Luizsan", -- creator of Delta theme + "Matt1360", -- Automake magic + oitg bro + "Renard", + "Ryan McKanna (Plaguefox)", + "Sta Kousin", --help with Japanese bug reports +} + +local shoutout = { + "The Lua team", -- lua project lead or some shit. super nerdy but oh hell. + "Mojang AB", -- minecraft forever -freem + "Wolfire Games", -- piles of inspiration + "NAKET Coder", + "Ciera Boyd", -- you bet your ass I'm putting my girlfriend in the credits + --Image(), -- we should have some logos probably to look super pro + "#KBO", + "Celestia Radio", -- LOVE AND TOLERANCE + "You showed us... your ultimate dance", +} + +local copyright = { + "StepMania is released under the terms of the MIT license.", + "If you paid for the program you've been " .. Fooled() .. ".", + "All content is the sole property of their respectful owners." +} + +local sections = { + { "the spinal shark collective (project lead)", ssc }, + { "sm-ssc Team", sm_ssc }, + { "StepMania Team", stepmania }, + { "OpenITG Team", oitg }, + { "Translators", translators }, + { "Other Contributors", contrib }, + { "Special Thanks", thanks }, + { "Shoutouts", shoutout }, + { "Copyright", copyright }, +} + +-- To add people or sections modify the above. + +local lineOn = cmd(zoom,0.875;strokecolor,color("#444444");shadowcolor,color("#444444");shadowlength,3) +local sectionOn = cmd(diffuse,color("#88DDFF");strokecolor,color("#446688");shadowcolor,color("#446688");shadowlength,3) +local item_padding_start = 4; + +local creditScroller = Def.ActorScroller { + SecondsPerItem = 0.5; + NumItemsToDraw = 40; + TransformFunction = function( self, offset, itemIndex, numItems) + self:y(30*offset) + end; + OnCommand = cmd(scrollwithpadding,item_padding_start,15); +} + +local function AddLine( text, command ) + local text = Def.ActorFrame{ + LoadFont("Common normal")..{ + Text = text or ""; + OnCommand = command or lineOn; + } + } + table.insert( creditScroller, text ) +end + +-- Add sections with padding. +for section in ivalues(sections) do + AddLine( section[1], sectionOn ) + for name in ivalues(section[2]) do + AddLine( name ) + end + AddLine() + AddLine() +end + +creditScroller.BeginCommand=function(self) + SCREENMAN:GetTopScreen():PostScreenMessage( 'SM_MenuTimer', (creditScroller.SecondsPerItem * (#creditScroller + item_padding_start) + 10) ); +end; + +return Def.ActorFrame{ + creditScroller..{ + InitCommand=cmd(CenterX;y,SCREEN_BOTTOM-64), + } }; \ No newline at end of file diff --git a/Themes/default/BGAnimations/ScreenCredits overlay.lua b/Themes/default/BGAnimations/ScreenCredits overlay.lua index 416cd2dc7f..e9972e04a5 100644 --- a/Themes/default/BGAnimations/ScreenCredits overlay.lua +++ b/Themes/default/BGAnimations/ScreenCredits overlay.lua @@ -1,163 +1,164 @@ -local function Fooled() - local phrases = { - "hornswaggled", - "bamboozled", - "hoodwinked", - "swindled", - "duped", - "hoaxed", - "fleeced", - "shafted", - "caboodled", - "beguiled", - "finagled", - "two-timed", - "suckered", - "flimflammed" - } - return phrases[math.random(#phrases)] -end - -local ssc = { - "AJ Kelly as freem", - "Jonathan Payne (Midiman)", - "Colby Klein (shakesoda)", -} - -local sm_ssc = { - "Jason Felds (wolfman2000)", -- Timing Segments, Split Timing, optimization - "Thai Pangsakulyanont (theDtTvB)", -- BMS, Split Timing, optimization - "Alberto Ramos (Daisuke Master)", - "Jack Walstrom (FSX)", -} - -local stepmania = { - "Chris Danford", - "Glenn Maynard", - "Steve Checkoway", - -- and various other contributors -} - -local oitg = { - "infamouspat", - "Mark Cannon (vyhd)", -} - -local contrib = { - "Aldo Fregoso (Aldo_MX)", -- delays and much more. StepMania AMX creator - "A.C (@waiei)", -- custom scoring fixes + Hybrid scoring - "cerbo", -- lua bindings and other fun stuff - "cesarmades", -- pump/cmd* noteskins - "Chris Eldridge (kurisu)", -- dance-threepanel stepstype support - "Christophe Goulet-LeBlanc (Kommisar)", -- songs - "corec", -- various fixes - "galopin", -- piu PlayStation2 usb mat support - "gholms", -- automake 1.11 support - "juanelote", -- SongManager:GetSongGroupByIndex, JumpToNext/PrevGroup logic mods - "Kaox", -- pump/default noteskin - "NitroX72", -- pump/frame noteskin - "Petriform", -- default theme music - "桜為小鳩/Sakurana-Kobato (@sakuraponila)", -- custom scoring fixes - "Samuel Kim (1a2a3a2a1a)", -- various beat mode fixes - "hanubeki (@803832)", -- beginner helper fix, among various other things - "v1toko", -- x-mode from StepNXA -} - -local translators = { - "John Reactor (Polish)", - "DHalens (Spanish)", - "@Niler_jp (Japanese)", - "Deamon007 (Dutch)" -} - -local thanks = { - "A Pseudonymous Coder", -- support - "Bill Shillito (DM Ashura)", -- Music (not yet though) - "cpubasic13", -- testing (a lot) - "Dreamwoods", - "Jason Bolt (LightningXCE)", - "Jousway", -- Noteskins - "Luizsan", -- creator of Delta theme - "Matt1360", -- Automake magic + oitg bro - "Renard", - "Ryan McKanna (Plaguefox)", - "Sta Kousin", --help with Japanese bug reports -} - -local shoutout = { - "The Lua team", -- lua project lead or some shit. super nerdy but oh hell. - "Mojang AB", -- minecraft forever -freem - "Wolfire Games", -- piles of inspiration - "NAKET Coder", - "Ciera Boyd", -- you bet your ass I'm putting my girlfriend in the credits - --Image(), -- we should have some logos probably to look super pro - "#KBO", - "Celestia Radio", -- LOVE AND TOLERANCE - "You showed us... your ultimate dance!", -} - -local copyright = { - "StepMania is released under the terms of the MIT license.", - "If you paid for the program you've been " .. Fooled() .. ".", - "All content is the sole property of their respectful owners." -} - -local sections = { - { "the spinal shark collective (project lead)", ssc }, - { "sm-ssc Team", sm_ssc }, - { "StepMania Team", stepmania }, - { "OpenITG Team", oitg }, - { "Translators", translators }, - { "Other Contributors", contrib }, - { "Special Thanks", thanks }, - { "Shoutouts", shoutout }, - { "Copyright", copyright }, -} - --- To add people or sections modify the above. - -local lineOn = cmd(zoom,0.875;strokecolor,color("#444444");shadowcolor,color("#444444");shadowlength,3) -local sectionOn = cmd(diffuse,color("#88DDFF");strokecolor,color("#446688");shadowcolor,color("#446688");shadowlength,3) -local item_padding_start = 4; - -local creditScroller = Def.ActorScroller { - SecondsPerItem = 0.5; - NumItemsToDraw = 40; - TransformFunction = function( self, offset, itemIndex, numItems) - self:y(30*offset) - end; - OnCommand = cmd(scrollwithpadding,item_padding_start,15); -} - -local function AddLine( text, command ) - local text = Def.ActorFrame{ - LoadFont("Common normal")..{ - Text = text or ""; - OnCommand = command or lineOn; - } - } - table.insert( creditScroller, text ) -end - --- Add sections with padding. -for section in ivalues(sections) do - AddLine( section[1], sectionOn ) - for name in ivalues(section[2]) do - AddLine( name ) - end - AddLine() - AddLine() -end - -creditScroller.BeginCommand=function(self) - SCREENMAN:GetTopScreen():PostScreenMessage( 'SM_MenuTimer', (creditScroller.SecondsPerItem * (#creditScroller + item_padding_start) + 10) ); -end; - -return Def.ActorFrame{ - creditScroller..{ - InitCommand=cmd(CenterX;y,SCREEN_BOTTOM-64), - }, - LoadActor(THEME:GetPathB("ScreenWithMenuElements","background/_bg top"))..{ - InitCommand=cmd(Center), - }, -}; +local function Fooled() + local phrases = { + "hornswaggled", + "bamboozled", + "hoodwinked", + "swindled", + "duped", + "hoaxed", + "fleeced", + "shafted", + "caboodled", + "beguiled", + "finagled", + "two-timed", + "suckered", + "flimflammed" + } + return phrases[math.random(#phrases)] +end + +local ssc = { + "AJ Kelly as freem", + "Jonathan Payne (Midiman)", + "Colby Klein (shakesoda)", +} + +local sm_ssc = { + "Jason Felds (wolfman2000)", -- Timing Segments, Split Timing, optimization + "Thai Pangsakulyanont (theDtTvB)", -- BMS, Split Timing, optimization + "Alberto Ramos (Daisuke Master)", + "Jack Walstrom (FSX)", +} + +local stepmania = { + "Chris Danford", + "Glenn Maynard", + "Steve Checkoway", + -- and various other contributors +} + +local oitg = { + "infamouspat", + "Mark Cannon (vyhd)", +} + +local contrib = { + "Aldo Fregoso (Aldo_MX)", -- delays and much more. StepMania AMX creator + "A.C (@waiei)", -- custom scoring fixes + Hybrid scoring + "cerbo", -- lua bindings and other fun stuff + "cesarmades", -- pump/cmd* noteskins + "Chris Eldridge (kurisu)", -- dance-threepanel stepstype support + "Christophe Goulet-LeBlanc (Kommisar)", -- songs + "corec", -- various fixes + "galopin", -- piu PlayStation2 usb mat support + "gholms", -- automake 1.11 support + "juanelote", -- SongManager:GetSongGroupByIndex, JumpToNext/PrevGroup logic mods + "Kaox", -- pump/default noteskin + "NitroX72", -- pump/frame noteskin + "Petriform", -- default theme music + "桜為小鳩/Sakurana-Kobato (@sakuraponila)", -- custom scoring fixes + "Samuel Kim (1a2a3a2a1a)", -- various beat mode fixes + "hanubeki (@803832)", -- beginner helper fix, among various other things + "v1toko", -- x-mode from StepNXA + "Alfred Sorenson", -- new lua bindings +} + +local translators = { + "John Reactor (Polish)", + "DHalens (Spanish)", + "@Niler_jp (Japanese)", + "Deamon007 (Dutch)" +} + +local thanks = { + "A Pseudonymous Coder", -- support + "Bill Shillito (DM Ashura)", -- Music (not yet though) + "cpubasic13", -- testing (a lot) + "Dreamwoods", + "Jason Bolt (LightningXCE)", + "Jousway", -- Noteskins + "Luizsan", -- creator of Delta theme + "Matt1360", -- Automake magic + oitg bro + "Renard", + "Ryan McKanna (Plaguefox)", + "Sta Kousin", --help with Japanese bug reports +} + +local shoutout = { + "The Lua team", -- lua project lead or some shit. super nerdy but oh hell. + "Mojang AB", -- minecraft forever -freem + "Wolfire Games", -- piles of inspiration + "NAKET Coder", + "Ciera Boyd", -- you bet your ass I'm putting my girlfriend in the credits + --Image(), -- we should have some logos probably to look super pro + "#KBO", + "Celestia Radio", -- LOVE AND TOLERANCE + "You showed us... your ultimate dance!", +} + +local copyright = { + "StepMania is released under the terms of the MIT license.", + "If you paid for the program you've been " .. Fooled() .. ".", + "All content is the sole property of their respectful owners." +} + +local sections = { + { "the spinal shark collective (project lead)", ssc }, + { "sm-ssc Team", sm_ssc }, + { "StepMania Team", stepmania }, + { "OpenITG Team", oitg }, + { "Translators", translators }, + { "Other Contributors", contrib }, + { "Special Thanks", thanks }, + { "Shoutouts", shoutout }, + { "Copyright", copyright }, +} + +-- To add people or sections modify the above. + +local lineOn = cmd(zoom,0.875;strokecolor,color("#444444");shadowcolor,color("#444444");shadowlength,3) +local sectionOn = cmd(diffuse,color("#88DDFF");strokecolor,color("#446688");shadowcolor,color("#446688");shadowlength,3) +local item_padding_start = 4; + +local creditScroller = Def.ActorScroller { + SecondsPerItem = 0.5; + NumItemsToDraw = 40; + TransformFunction = function( self, offset, itemIndex, numItems) + self:y(30*offset) + end; + OnCommand = cmd(scrollwithpadding,item_padding_start,15); +} + +local function AddLine( text, command ) + local text = Def.ActorFrame{ + LoadFont("Common normal")..{ + Text = text or ""; + OnCommand = command or lineOn; + } + } + table.insert( creditScroller, text ) +end + +-- Add sections with padding. +for section in ivalues(sections) do + AddLine( section[1], sectionOn ) + for name in ivalues(section[2]) do + AddLine( name ) + end + AddLine() + AddLine() +end + +creditScroller.BeginCommand=function(self) + SCREENMAN:GetTopScreen():PostScreenMessage( 'SM_MenuTimer', (creditScroller.SecondsPerItem * (#creditScroller + item_padding_start) + 10) ); +end; + +return Def.ActorFrame{ + creditScroller..{ + InitCommand=cmd(CenterX;y,SCREEN_BOTTOM-64), + }, + LoadActor(THEME:GetPathB("ScreenWithMenuElements","background/_bg top"))..{ + InitCommand=cmd(Center), + }, +}; diff --git a/src/Profile.cpp b/src/Profile.cpp index 1ad2198154..90e63571e3 100644 --- a/src/Profile.cpp +++ b/src/Profile.cpp @@ -1,2133 +1,2135 @@ -#include "global.h" -#include "Profile.h" -#include "RageUtil.h" -#include "PrefsManager.h" -#include "XmlFile.h" -#include "IniFile.h" -#include "GameManager.h" -#include "RageLog.h" -#include "Song.h" -#include "SongManager.h" -#include "Steps.h" -#include "Course.h" -#include "ThemeManager.h" -#include "CryptManager.h" -#include "ProfileManager.h" -#include "RageFile.h" -#include "RageFileDriverDeflate.h" -#include "RageFileManager.h" -#include "LuaManager.h" -#include "UnlockManager.h" -#include "XmlFile.h" -#include "XmlFileUtil.h" -#include "Foreach.h" -#include "Bookkeeper.h" -#include "Game.h" -#include "CharacterManager.h" -#include "Character.h" - -const RString STATS_XML = "Stats.xml"; -const RString STATS_XML_GZ = "Stats.xml.gz"; -/** @brief The filename for where one can edit their personal profile information. */ -const RString EDITABLE_INI = "Editable.ini"; -/** @brief The filename containing the signature for STATS_XML's signature. */ -const RString DONT_SHARE_SIG = "DontShare.sig"; -const RString PUBLIC_KEY_FILE = "public.key"; -const RString SCREENSHOTS_SUBDIR = "Screenshots/"; -const RString EDIT_STEPS_SUBDIR = "Edits/"; -const RString EDIT_COURSES_SUBDIR = "EditCourses/"; -//const RString UPLOAD_SUBDIR = "Upload/"; -const RString RIVAL_SUBDIR = "Rivals/"; - -ThemeMetric SHOW_COIN_DATA( "Profile", "ShowCoinData" ); -static Preference g_bProfileDataCompress( "ProfileDataCompress", false ); -static ThemeMetric UNLOCK_AUTH_STRING( "Profile", "UnlockAuthString" ); -#define GUID_SIZE_BYTES 8 - -#define MAX_EDITABLE_INI_SIZE_BYTES 2*1024 // 2KB -#define MAX_PLAYER_STATS_XML_SIZE_BYTES \ - 400 /* Songs */ \ - * 5 /* Steps per Song */ \ - * 5 /* HighScores per Steps */ \ - * 1024 /* size in bytes of a HighScores XNode */ - -const unsigned int DEFAULT_WEIGHT_POUNDS = 120; - -#if defined(_MSC_VER) -#pragma warning (disable : 4706) // assignment within conditional expression -#endif - - -int Profile::HighScoresForASong::GetNumTimesPlayed() const -{ - int iCount = 0; - FOREACHM_CONST( StepsID, HighScoresForASteps, m_StepsHighScores, i ) - { - iCount += i->second.hsl.GetNumTimesPlayed(); - } - return iCount; -} - -int Profile::HighScoresForACourse::GetNumTimesPlayed() const -{ - int iCount = 0; - FOREACHM_CONST( TrailID, HighScoresForATrail, m_TrailHighScores, i ) - { - iCount += i->second.hsl.GetNumTimesPlayed(); - } - return iCount; -} - - -void Profile::InitEditableData() -{ - m_sDisplayName = ""; - m_sCharacterID = ""; - m_sLastUsedHighScoreName = ""; - m_iWeightPounds = 0; -} - -void Profile::ClearStats() -{ - // don't reset the Guid - RString sGuid = m_sGuid; - InitAll(); - m_sGuid = sGuid; -} - -RString Profile::MakeGuid() -{ - RString s; - s.reserve( GUID_SIZE_BYTES*2 ); - unsigned char buf[GUID_SIZE_BYTES]; - CryptManager::GetRandomBytes( buf, GUID_SIZE_BYTES ); - for( unsigned i=0; i vpCharacters; - CHARMAN->GetCharacters( vpCharacters ); - FOREACH_CONST( Character*, vpCharacters, c ) - { - if( (*c)->m_sCharacterID.CompareNoCase(m_sCharacterID)==0 ) - return *c; - } - return CHARMAN->GetDefaultCharacter(); -} - -void Profile::SetCharacter(const RString sCharacterID) -{ - if(CHARMAN->GetCharacterFromID(sCharacterID)) - m_sCharacterID = sCharacterID; -} - -static RString FormatCalories( float fCals ) -{ - return Commify((int)fCals) + " Cal"; -} - -int Profile::GetCalculatedWeightPounds() const -{ - if( m_iWeightPounds == 0 ) // weight not entered - return DEFAULT_WEIGHT_POUNDS; - else - return m_iWeightPounds; -} - -RString Profile::GetDisplayTotalCaloriesBurned() const -{ - return FormatCalories( m_fTotalCaloriesBurned ); -} - -RString Profile::GetDisplayTotalCaloriesBurnedToday() const -{ - float fCals = GetCaloriesBurnedToday(); - return FormatCalories( fCals ); -} - -float Profile::GetCaloriesBurnedToday() const -{ - DateTime now = DateTime::GetNowDate(); - return GetCaloriesBurnedForDay(now); -} - -int Profile::GetTotalNumSongsPassed() const -{ - int iTotal = 0; - FOREACH_ENUM( PlayMode, i ) - iTotal += m_iNumStagesPassedByPlayMode[i]; - return iTotal; -} - -int Profile::GetTotalStepsWithTopGrade( StepsType st, Difficulty d, Grade g ) const -{ - int iCount = 0; - - FOREACH_CONST( Song*, SONGMAN->GetAllSongs(), pSong ) - { - if( !(*pSong)->NormallyDisplayed() ) - continue; // skip - - FOREACH_CONST( Steps*, (*pSong)->GetAllSteps(), pSteps ) - { - if( (*pSteps)->m_StepsType != st ) - continue; // skip - - if( (*pSteps)->GetDifficulty() != d ) - continue; // skip - - const HighScoreList &hsl = GetStepsHighScoreList( *pSong, *pSteps ); - if( hsl.vHighScores.empty() ) - continue; // skip - - if( hsl.vHighScores[0].GetGrade() == g ) - iCount++; - } - } - - return iCount; -} - -int Profile::GetTotalTrailsWithTopGrade( StepsType st, CourseDifficulty d, Grade g ) const -{ - int iCount = 0; - - // add course high scores - vector vCourses; - SONGMAN->GetAllCourses( vCourses, false ); - FOREACH_CONST( Course*, vCourses, pCourse ) - { - // Don't count any course that has any entries that change over time. - if( !(*pCourse)->AllSongsAreFixed() ) - continue; - - vector vTrails; - Trail* pTrail = (*pCourse)->GetTrail( st, d ); - if( pTrail == NULL ) - continue; - - const HighScoreList &hsl = GetCourseHighScoreList( *pCourse, pTrail ); - if( hsl.vHighScores.empty() ) - continue; // skip - - if( hsl.vHighScores[0].GetGrade() == g ) - iCount++; - } - - return iCount; -} - -float Profile::GetSongsPossible( StepsType st, Difficulty dc ) const -{ - int iTotalSteps = 0; - - // add steps high scores - const vector &vSongs = SONGMAN->GetAllSongs(); - for( unsigned i=0; iNormallyDisplayed() ) - continue; // skip - - vector vSteps = pSong->GetAllSteps(); - for( unsigned j=0; jm_StepsType != st ) - continue; // skip - - if( pSteps->GetDifficulty() != dc ) - continue; // skip - - iTotalSteps++; - } - } - - return (float) iTotalSteps; -} - -float Profile::GetSongsActual( StepsType st, Difficulty dc ) const -{ - CHECKPOINT_M( ssprintf("Profile::GetSongsActual(%d,%d)",st,dc) ); - - float fTotalPercents = 0; - - // add steps high scores - FOREACHM_CONST( SongID, HighScoresForASong, m_SongHighScores, i ) - { - const SongID &id = i->first; - Song* pSong = id.ToSong(); - - CHECKPOINT_M( ssprintf("Profile::GetSongsActual: %p", pSong) ); - - // If the Song isn't loaded on the current machine, then we can't - // get radar values to compute dance points. - if( pSong == NULL ) - continue; - - if( !pSong->NormallyDisplayed() ) - continue; // skip - - CHECKPOINT_M( ssprintf("Profile::GetSongsActual: song %s", pSong->GetSongDir().c_str()) ); - const HighScoresForASong &hsfas = i->second; - - FOREACHM_CONST( StepsID, HighScoresForASteps, hsfas.m_StepsHighScores, j ) - { - const StepsID &sid = j->first; - Steps* pSteps = sid.ToSteps( pSong, true ); - CHECKPOINT_M( ssprintf("Profile::GetSongsActual: song %p, steps %p", pSong, pSteps) ); - - // If the Steps isn't loaded on the current machine, then we can't - // get radar values to compute dance points. - if( pSteps == NULL ) - continue; - - if( pSteps->m_StepsType != st ) - continue; - - CHECKPOINT_M( ssprintf("Profile::GetSongsActual: n %s = %p", sid.ToString().c_str(), pSteps) ); - if( pSteps->GetDifficulty() != dc ) - continue; // skip - CHECKPOINT; - - const HighScoresForASteps& h = j->second; - const HighScoreList& hsl = h.hsl; - - fTotalPercents += hsl.GetTopScore().GetPercentDP(); - } - CHECKPOINT; - } - - return fTotalPercents; -} - -float Profile::GetSongsPercentComplete( StepsType st, Difficulty dc ) const -{ - return GetSongsActual(st,dc) / GetSongsPossible(st,dc); -} - -static void GetHighScoreCourses( vector &vpCoursesOut ) -{ - vpCoursesOut.clear(); - - vector vpCourses; - SONGMAN->GetAllCourses( vpCourses, false ); - FOREACH_CONST( Course*, vpCourses, c ) - { - // Don't count any course that has any entries that change over time. - if( !(*c)->AllSongsAreFixed() ) - continue; - - vpCoursesOut.push_back( *c ); - } -} - -float Profile::GetCoursesPossible( StepsType st, CourseDifficulty cd ) const -{ - int iTotalTrails = 0; - - vector vpCourses; - GetHighScoreCourses( vpCourses ); - FOREACH_CONST( Course*, vpCourses, c ) - { - Trail* pTrail = (*c)->GetTrail(st,cd); - if( pTrail == NULL ) - continue; - - iTotalTrails++; - } - - return (float) iTotalTrails; -} - -float Profile::GetCoursesActual( StepsType st, CourseDifficulty cd ) const -{ - float fTotalPercents = 0; - - vector vpCourses; - GetHighScoreCourses( vpCourses ); - FOREACH_CONST( Course*, vpCourses, c ) - { - Trail *pTrail = (*c)->GetTrail( st, cd ); - if( pTrail == NULL ) - continue; - - const HighScoreList& hsl = GetCourseHighScoreList( *c, pTrail ); - fTotalPercents += hsl.GetTopScore().GetPercentDP(); - } - - return fTotalPercents; -} - -float Profile::GetCoursesPercentComplete( StepsType st, CourseDifficulty cd ) const -{ - return GetCoursesActual(st,cd) / GetCoursesPossible(st,cd); -} - -float Profile::GetSongsAndCoursesPercentCompleteAllDifficulties( StepsType st ) const -{ - float fActual = 0; - float fPossible = 0; - FOREACH_ENUM( Difficulty, d ) - { - fActual += GetSongsActual(st,d); - fPossible += GetSongsPossible(st,d); - } - FOREACH_ENUM( CourseDifficulty, d ) - { - fActual += GetCoursesActual(st,d); - fPossible += GetCoursesPossible(st,d); - } - return fActual / fPossible; -} - -int Profile::GetSongNumTimesPlayed( const Song* pSong ) const -{ - SongID songID; - songID.FromSong( pSong ); - return GetSongNumTimesPlayed( songID ); -} - -int Profile::GetSongNumTimesPlayed( const SongID& songID ) const -{ - const HighScoresForASong *hsSong = GetHighScoresForASong( songID ); - if( hsSong == NULL ) - return 0; - - int iTotalNumTimesPlayed = 0; - FOREACHM_CONST( StepsID, HighScoresForASteps, hsSong->m_StepsHighScores, j ) - { - const HighScoresForASteps &hsSteps = j->second; - - iTotalNumTimesPlayed += hsSteps.hsl.GetNumTimesPlayed(); - } - return iTotalNumTimesPlayed; -} - -/* - * Get the profile default modifiers. Return true if set, in which case sModifiersOut - * will be set. Return false if no modifier string is set, in which case the theme - * defaults should be used. Note that the null string means "no modifiers are active", - * which is distinct from no modifier string being set at all. - * - * In practice, we get the default modifiers from the theme the first time a game - * is played, and from the profile every time thereafter. - */ -bool Profile::GetDefaultModifiers( const Game* pGameType, RString &sModifiersOut ) const -{ - map::const_iterator it; - it = m_sDefaultModifiers.find( pGameType->m_szName ); - if( it == m_sDefaultModifiers.end() ) - return false; - sModifiersOut = it->second; - return true; -} - -void Profile::SetDefaultModifiers( const Game* pGameType, const RString &sModifiers ) -{ - if( sModifiers == "" ) - m_sDefaultModifiers.erase( pGameType->m_szName ); - else - m_sDefaultModifiers[pGameType->m_szName] = sModifiers; -} - -bool Profile::IsCodeUnlocked( RString sUnlockEntryID ) const -{ - return m_UnlockedEntryIDs.find( sUnlockEntryID ) != m_UnlockedEntryIDs.end(); -} - - -Song *Profile::GetMostPopularSong() const -{ - int iMaxNumTimesPlayed = 0; - SongID id; - FOREACHM_CONST( SongID, HighScoresForASong, m_SongHighScores, i ) - { - int iNumTimesPlayed = i->second.GetNumTimesPlayed(); - if( iNumTimesPlayed > iMaxNumTimesPlayed ) - { - id = i->first; - iMaxNumTimesPlayed = iNumTimesPlayed; - } - } - - return id.ToSong(); -} - -Course *Profile::GetMostPopularCourse() const -{ - int iMaxNumTimesPlayed = 0; - CourseID id; - FOREACHM_CONST( CourseID, HighScoresForACourse, m_CourseHighScores, i ) - { - int iNumTimesPlayed = i->second.GetNumTimesPlayed(); - if( iNumTimesPlayed > iMaxNumTimesPlayed ) - { - id = i->first; - iMaxNumTimesPlayed = iNumTimesPlayed; - } - } - - return id.ToCourse(); -} - -// Steps high scores -void Profile::AddStepsHighScore( const Song* pSong, const Steps* pSteps, HighScore hs, int &iIndexOut ) -{ - GetStepsHighScoreList(pSong,pSteps).AddHighScore( hs, iIndexOut, IsMachine() ); -} - -const HighScoreList& Profile::GetStepsHighScoreList( const Song* pSong, const Steps* pSteps ) const -{ - return ((Profile*)this)->GetStepsHighScoreList(pSong,pSteps); -} - -HighScoreList& Profile::GetStepsHighScoreList( const Song* pSong, const Steps* pSteps ) -{ - SongID songID; - songID.FromSong( pSong ); - - StepsID stepsID; - stepsID.FromSteps( pSteps ); - - HighScoresForASong &hsSong = m_SongHighScores[songID]; // operator[] inserts into map - HighScoresForASteps &hsSteps = hsSong.m_StepsHighScores[stepsID]; // operator[] inserts into map - - return hsSteps.hsl; -} - -int Profile::GetStepsNumTimesPlayed( const Song* pSong, const Steps* pSteps ) const -{ - return GetStepsHighScoreList(pSong,pSteps).GetNumTimesPlayed(); -} - -DateTime Profile::GetSongLastPlayedDateTime( const Song* pSong ) const -{ - SongID id; - id.FromSong( pSong ); - std::map::const_iterator iter = m_SongHighScores.find( id ); - - // don't call this unless has been played once - ASSERT( iter != m_SongHighScores.end() ); - ASSERT( !iter->second.m_StepsHighScores.empty() ); - - DateTime dtLatest; // starts out zeroed - FOREACHM_CONST( StepsID, HighScoresForASteps, iter->second.m_StepsHighScores, i ) - { - const HighScoreList &hsl = i->second.hsl; - if( hsl.GetNumTimesPlayed() == 0 ) - continue; - if( dtLatest < hsl.GetLastPlayed() ) - dtLatest = hsl.GetLastPlayed(); - } - return dtLatest; -} - -bool Profile::HasPassedSteps( const Song* pSong, const Steps* pSteps ) const -{ - const HighScoreList &hsl = GetStepsHighScoreList( pSong, pSteps ); - Grade grade = hsl.GetTopScore().GetGrade(); - switch( grade ) - { - case Grade_Failed: - case Grade_NoData: - return false; - default: - return true; - } -} - -bool Profile::HasPassedAnyStepsInSong( const Song* pSong ) const -{ - FOREACH_CONST( Steps*, pSong->GetAllSteps(), steps ) - { - if( HasPassedSteps( pSong, *steps ) ) - return true; - } - return false; -} - -void Profile::IncrementStepsPlayCount( const Song* pSong, const Steps* pSteps ) -{ - DateTime now = DateTime::GetNowDate(); - GetStepsHighScoreList(pSong,pSteps).IncrementPlayCount( now ); -} - -void Profile::GetGrades( const Song* pSong, StepsType st, int iCounts[NUM_Grade] ) const -{ - SongID songID; - songID.FromSong( pSong ); - - memset( iCounts, 0, sizeof(int)*NUM_Grade ); - const HighScoresForASong *hsSong = GetHighScoresForASong( songID ); - if( hsSong == NULL ) - return; - - FOREACH_ENUM( Grade,g) - { - FOREACHM_CONST( StepsID, HighScoresForASteps, hsSong->m_StepsHighScores, it ) - { - const StepsID &id = it->first; - if( !id.MatchesStepsType(st) ) - continue; - - const HighScoresForASteps &hsSteps = it->second; - if( hsSteps.hsl.GetTopScore().GetGrade() == g ) - iCounts[g]++; - } - } -} - -// Course high scores -void Profile::AddCourseHighScore( const Course* pCourse, const Trail* pTrail, HighScore hs, int &iIndexOut ) -{ - GetCourseHighScoreList(pCourse,pTrail).AddHighScore( hs, iIndexOut, IsMachine() ); -} - -const HighScoreList& Profile::GetCourseHighScoreList( const Course* pCourse, const Trail* pTrail ) const -{ - return ((Profile *)this)->GetCourseHighScoreList( pCourse, pTrail ); -} - -HighScoreList& Profile::GetCourseHighScoreList( const Course* pCourse, const Trail* pTrail ) -{ - CourseID courseID; - courseID.FromCourse( pCourse ); - - TrailID trailID; - trailID.FromTrail( pTrail ); - - HighScoresForACourse &hsCourse = m_CourseHighScores[courseID]; // operator[] inserts into map - HighScoresForATrail &hsTrail = hsCourse.m_TrailHighScores[trailID]; // operator[] inserts into map - - return hsTrail.hsl; -} - -int Profile::GetCourseNumTimesPlayed( const Course* pCourse ) const -{ - CourseID courseID; - courseID.FromCourse( pCourse ); - - return GetCourseNumTimesPlayed( courseID ); -} - -int Profile::GetCourseNumTimesPlayed( const CourseID &courseID ) const -{ - const HighScoresForACourse *hsCourse = GetHighScoresForACourse( courseID ); - if( hsCourse == NULL ) - return 0; - - int iTotalNumTimesPlayed = 0; - FOREACHM_CONST( TrailID, HighScoresForATrail, hsCourse->m_TrailHighScores, j ) - { - const HighScoresForATrail &hsTrail = j->second; - - iTotalNumTimesPlayed += hsTrail.hsl.GetNumTimesPlayed(); - } - return iTotalNumTimesPlayed; -} - -DateTime Profile::GetCourseLastPlayedDateTime( const Course* pCourse ) const -{ - CourseID id; - id.FromCourse( pCourse ); - std::map::const_iterator iter = m_CourseHighScores.find( id ); - - // don't call this unless has been played once - ASSERT( iter != m_CourseHighScores.end() ); - ASSERT( !iter->second.m_TrailHighScores.empty() ); - - DateTime dtLatest; // starts out zeroed - FOREACHM_CONST( TrailID, HighScoresForATrail, iter->second.m_TrailHighScores, i ) - { - const HighScoreList &hsl = i->second.hsl; - if( hsl.GetNumTimesPlayed() == 0 ) - continue; - if( dtLatest < hsl.GetLastPlayed() ) - dtLatest = hsl.GetLastPlayed(); - } - return dtLatest; -} - -void Profile::IncrementCoursePlayCount( const Course* pCourse, const Trail* pTrail ) -{ - DateTime now = DateTime::GetNowDate(); - GetCourseHighScoreList(pCourse,pTrail).IncrementPlayCount( now ); -} - -// Category high scores -void Profile::AddCategoryHighScore( StepsType st, RankingCategory rc, HighScore hs, int &iIndexOut ) -{ - m_CategoryHighScores[st][rc].AddHighScore( hs, iIndexOut, IsMachine() ); -} - -const HighScoreList& Profile::GetCategoryHighScoreList( StepsType st, RankingCategory rc ) const -{ - return ((Profile *)this)->m_CategoryHighScores[st][rc]; -} - -HighScoreList& Profile::GetCategoryHighScoreList( StepsType st, RankingCategory rc ) -{ - return m_CategoryHighScores[st][rc]; -} - -int Profile::GetCategoryNumTimesPlayed( StepsType st ) const -{ - int iNumTimesPlayed = 0; - FOREACH_ENUM( RankingCategory,rc ) - iNumTimesPlayed += m_CategoryHighScores[st][rc].GetNumTimesPlayed(); - return iNumTimesPlayed; -} - -void Profile::IncrementCategoryPlayCount( StepsType st, RankingCategory rc ) -{ - DateTime now = DateTime::GetNowDate(); - m_CategoryHighScores[st][rc].IncrementPlayCount( now ); -} - - -// Loading and saving -#define WARN_PARSER ShowWarningOrTrace( __FILE__, __LINE__, "Error parsing file.", true ) -#define WARN_AND_RETURN { WARN_PARSER; return; } -#define WARN_AND_CONTINUE { WARN_PARSER; continue; } -#define WARN_AND_BREAK { WARN_PARSER; break; } -#define WARN_M(m) ShowWarningOrTrace( __FILE__, __LINE__, RString("Error parsing file: ")+(m), true ) -#define WARN_AND_RETURN_M(m) { WARN_M(m); return; } -#define WARN_AND_CONTINUE_M(m) { WARN_M(m); continue; } -#define WARN_AND_BREAK_M(m) { WARN_M(m); break; } -#define LOAD_NODE(X) { \ - const XNode* X = xml->GetChild(#X); \ - if( X==NULL ) LOG->Warn("Failed to read section " #X); \ - else Load##X##FromNode(X); } - -ProfileLoadResult Profile::LoadAllFromDir( RString sDir, bool bRequireSignature ) -{ - CHECKPOINT; - - LOG->Trace( "Profile::LoadAllFromDir( %s )", sDir.c_str() ); - - ASSERT( sDir.Right(1) == "/" ); - - InitAll(); - - // Not critical if this fails - LoadEditableDataFromDir( sDir ); - - // Check for the existance of stats.xml - RString fn = sDir + STATS_XML; - bool bCompressed = false; - if( !IsAFile(fn) ) - { - // Check for the existance of stats.xml.gz - fn = sDir + STATS_XML_GZ; - bCompressed = true; - if( !IsAFile(fn) ) - return ProfileLoadResult_FailedNoProfile; - } - - int iError; - auto_ptr pFile( FILEMAN->Open(fn, RageFile::READ, iError) ); - if( pFile.get() == NULL ) - { - LOG->Trace( "Error opening %s: %s", fn.c_str(), strerror(iError) ); - return ProfileLoadResult_FailedTampered; - } - - if( bCompressed ) - { - RString sError; - uint32_t iCRC32; - RageFileObjInflate *pInflate = GunzipFile( pFile.release(), sError, &iCRC32 ); - if( pInflate == NULL ) - { - LOG->Trace( "Error opening %s: %s", fn.c_str(), sError.c_str() ); - return ProfileLoadResult_FailedTampered; - } - - pFile.reset( pInflate ); - } - - // Don't load unreasonably large stats.xml files. - if( !IsMachine() ) // only check stats coming from the player - { - int iBytes = pFile->GetFileSize(); - if( iBytes > MAX_PLAYER_STATS_XML_SIZE_BYTES ) - { - LOG->Warn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); - return ProfileLoadResult_FailedTampered; - } - } - - if( bRequireSignature ) - { - RString sStatsXmlSigFile = fn+SIGNATURE_APPEND; - RString sDontShareFile = sDir + DONT_SHARE_SIG; - - LOG->Trace( "Verifying don't share signature \"%s\" against \"%s\"", sDontShareFile.c_str(), sStatsXmlSigFile.c_str() ); - // verify the stats.xml signature with the "don't share" file - if( !CryptManager::VerifyFileWithFile(sStatsXmlSigFile, sDontShareFile) ) - { - LOG->Warn( "The don't share check for '%s' failed. Data will be ignored.", sStatsXmlSigFile.c_str() ); - return ProfileLoadResult_FailedTampered; - } - LOG->Trace( "Done." ); - - // verify stats.xml - LOG->Trace( "Verifying stats.xml signature" ); - if( !CryptManager::VerifyFileWithFile(fn, sStatsXmlSigFile) ) - { - LOG->Warn( "The signature check for '%s' failed. Data will be ignored.", fn.c_str() ); - return ProfileLoadResult_FailedTampered; - } - LOG->Trace( "Done." ); - } - - LOG->Trace( "Loading %s", fn.c_str() ); - XNode xml; - if( !XmlFileUtil::LoadFromFileShowErrors(xml, *pFile.get()) ) - return ProfileLoadResult_FailedTampered; - LOG->Trace( "Done." ); - - return LoadStatsXmlFromNode( &xml ); -} - -ProfileLoadResult Profile::LoadStatsXmlFromNode( const XNode *xml, bool bIgnoreEditable ) -{ - /* The placeholder stats.xml file has an tag. Don't load it, - * but don't warn about it. */ - if( xml->GetName() == "html" ) - return ProfileLoadResult_FailedNoProfile; - - if( xml->GetName() != "Stats" ) - { - WARN_M( xml->GetName() ); - return ProfileLoadResult_FailedTampered; - } - - // These are loaded from Editable, so we usually want to ignore them here. - RString sName = m_sDisplayName; - RString sCharacterID = m_sCharacterID; - RString sLastUsedHighScoreName = m_sLastUsedHighScoreName; - int iWeightPounds = m_iWeightPounds; - - LOAD_NODE( GeneralData ); - LOAD_NODE( SongScores ); - LOAD_NODE( CourseScores ); - LOAD_NODE( CategoryScores ); - LOAD_NODE( ScreenshotData ); - LOAD_NODE( CalorieData ); - - if( bIgnoreEditable ) - { - m_sDisplayName = sName; - m_sCharacterID = sCharacterID; - m_sLastUsedHighScoreName = sLastUsedHighScoreName; - m_iWeightPounds = iWeightPounds; - } - - return ProfileLoadResult_Success; -} - -bool Profile::SaveAllToDir( RString sDir, bool bSignData ) const -{ - m_sLastPlayedMachineGuid = PROFILEMAN->GetMachineProfile()->m_sGuid; - m_LastPlayedDate = DateTime::GetNowDate(); - - // Save editable.ini - SaveEditableDataToDir( sDir ); - - bool bSaved = SaveStatsXmlToDir( sDir, bSignData ); - - SaveStatsWebPageToDir( sDir ); - - // Empty directories if none exist. - if( ProfileManager::m_bProfileStepEdits ) - FILEMAN->CreateDir( sDir + EDIT_STEPS_SUBDIR ); - if( ProfileManager::m_bProfileCourseEdits ) - FILEMAN->CreateDir( sDir + EDIT_COURSES_SUBDIR ); - FILEMAN->CreateDir( sDir + SCREENSHOTS_SUBDIR ); - FILEMAN->CreateDir( sDir + RIVAL_SUBDIR ); - - return bSaved; -} - -XNode *Profile::SaveStatsXmlCreateNode() const -{ - XNode *xml = new XNode( "Stats" ); - - xml->AppendChild( SaveGeneralDataCreateNode() ); - xml->AppendChild( SaveSongScoresCreateNode() ); - xml->AppendChild( SaveCourseScoresCreateNode() ); - xml->AppendChild( SaveCategoryScoresCreateNode() ); - xml->AppendChild( SaveScreenshotDataCreateNode() ); - xml->AppendChild( SaveCalorieDataCreateNode() ); - if( SHOW_COIN_DATA.GetValue() && IsMachine() ) - xml->AppendChild( SaveCoinDataCreateNode() ); - - return xml; -} - -bool Profile::SaveStatsXmlToDir( RString sDir, bool bSignData ) const -{ - LOG->Trace( "SaveStatsXmlToDir: %s", sDir.c_str() ); - auto_ptr xml( SaveStatsXmlCreateNode() ); - - // Save stats.xml - RString fn = sDir + (g_bProfileDataCompress? STATS_XML_GZ:STATS_XML); - - { - RString sError; - RageFile f; - if( !f.Open(fn, RageFile::WRITE) ) - { - LOG->Warn( "Couldn't open %s for writing: %s", fn.c_str(), f.GetError().c_str() ); - return false; - } - - if( g_bProfileDataCompress ) - { - RageFileObjGzip gzip( &f ); - gzip.Start(); - if( !XmlFileUtil::SaveToFile( xml.get(), gzip, "", false ) ) - return false; - - if( gzip.Finish() == -1 ) - return false; - - /* After successfully saving STATS_XML_GZ, remove any stray STATS_XML. */ - if( FILEMAN->IsAFile(sDir + STATS_XML) ) - FILEMAN->Remove( sDir + STATS_XML ); - } - else - { - if( !XmlFileUtil::SaveToFile( xml.get(), f, "", false ) ) - return false; - - /* After successfully saving STATS_XML, remove any stray STATS_XML_GZ. */ - if( FILEMAN->IsAFile(sDir + STATS_XML_GZ) ) - FILEMAN->Remove( sDir + STATS_XML_GZ ); - } - } - - if( bSignData ) - { - RString sStatsXmlSigFile = fn+SIGNATURE_APPEND; - CryptManager::SignFileToFile(fn, sStatsXmlSigFile); - - // Save the "don't share" file - RString sDontShareFile = sDir + DONT_SHARE_SIG; - CryptManager::SignFileToFile(sStatsXmlSigFile, sDontShareFile); - } - - return true; -} - -void Profile::SaveEditableDataToDir( RString sDir ) const -{ - IniFile ini; - - ini.SetValue( "Editable", "DisplayName", m_sDisplayName ); - ini.SetValue( "Editable", "CharacterID", m_sCharacterID ); - ini.SetValue( "Editable", "LastUsedHighScoreName", m_sLastUsedHighScoreName ); - ini.SetValue( "Editable", "WeightPounds", m_iWeightPounds ); - - ini.WriteFile( sDir + EDITABLE_INI ); -} - -XNode* Profile::SaveGeneralDataCreateNode() const -{ - XNode* pGeneralDataNode = new XNode( "GeneralData" ); - - // TRICKY: These are write-only elements that are normally never read again. - // This data is required by other apps (like internet ranking), but is - // redundant to the game app. - pGeneralDataNode->AppendChild( "DisplayName", GetDisplayNameOrHighScoreName() ); - pGeneralDataNode->AppendChild( "CharacterID", m_sCharacterID ); - pGeneralDataNode->AppendChild( "LastUsedHighScoreName", m_sLastUsedHighScoreName ); - pGeneralDataNode->AppendChild( "WeightPounds", m_iWeightPounds ); - pGeneralDataNode->AppendChild( "IsMachine", IsMachine() ); - pGeneralDataNode->AppendChild( "IsWeightSet", m_iWeightPounds != 0 ); - - pGeneralDataNode->AppendChild( "Guid", m_sGuid ); - pGeneralDataNode->AppendChild( "SortOrder", SortOrderToString(m_SortOrder) ); - pGeneralDataNode->AppendChild( "LastDifficulty", DifficultyToString(m_LastDifficulty) ); - pGeneralDataNode->AppendChild( "LastCourseDifficulty", DifficultyToString(m_LastCourseDifficulty) ); - if( m_LastStepsType != StepsType_Invalid ) - pGeneralDataNode->AppendChild( "LastStepsType", GAMEMAN->GetStepsTypeInfo(m_LastStepsType).szName ); - pGeneralDataNode->AppendChild( m_lastSong.CreateNode() ); - pGeneralDataNode->AppendChild( m_lastCourse.CreateNode() ); - pGeneralDataNode->AppendChild( "TotalSessions", m_iTotalSessions ); - pGeneralDataNode->AppendChild( "TotalSessionSeconds", m_iTotalSessionSeconds ); - pGeneralDataNode->AppendChild( "TotalGameplaySeconds", m_iTotalGameplaySeconds ); - pGeneralDataNode->AppendChild( "TotalCaloriesBurned", m_fTotalCaloriesBurned ); - pGeneralDataNode->AppendChild( "GoalType", m_GoalType ); - pGeneralDataNode->AppendChild( "GoalCalories", m_iGoalCalories ); - pGeneralDataNode->AppendChild( "GoalSeconds", m_iGoalSeconds ); - pGeneralDataNode->AppendChild( "LastPlayedMachineGuid", m_sLastPlayedMachineGuid ); - pGeneralDataNode->AppendChild( "LastPlayedDate", m_LastPlayedDate.GetString() ); - pGeneralDataNode->AppendChild( "TotalDancePoints", m_iTotalDancePoints ); - pGeneralDataNode->AppendChild( "NumExtraStagesPassed", m_iNumExtraStagesPassed ); - pGeneralDataNode->AppendChild( "NumExtraStagesFailed", m_iNumExtraStagesFailed ); - pGeneralDataNode->AppendChild( "NumToasties", m_iNumToasties ); - pGeneralDataNode->AppendChild( "TotalTapsAndHolds", m_iTotalTapsAndHolds ); - pGeneralDataNode->AppendChild( "TotalJumps", m_iTotalJumps ); - pGeneralDataNode->AppendChild( "TotalHolds", m_iTotalHolds ); - pGeneralDataNode->AppendChild( "TotalRolls", m_iTotalRolls ); - pGeneralDataNode->AppendChild( "TotalMines", m_iTotalMines ); - pGeneralDataNode->AppendChild( "TotalHands", m_iTotalHands ); - pGeneralDataNode->AppendChild( "TotalLifts", m_iTotalLifts ); - - // Keep declared variables in a very local scope so they aren't - // accidentally used where they're not intended. There's a lot of - // copying and pasting in this code. - - { - XNode* pDefaultModifiers = pGeneralDataNode->AppendChild("DefaultModifiers"); - FOREACHM_CONST( RString, RString, m_sDefaultModifiers, it ) - pDefaultModifiers->AppendChild( it->first, it->second ); - } - - { - XNode* pUnlocks = pGeneralDataNode->AppendChild("Unlocks"); - FOREACHS_CONST( RString, m_UnlockedEntryIDs, it ) - { - XNode *pEntry = pUnlocks->AppendChild("UnlockEntry"); - RString sUnlockEntry = it->c_str(); - pEntry->AppendAttr( "UnlockEntryID", sUnlockEntry ); - if( !UNLOCK_AUTH_STRING.GetValue().empty() ) - { - RString sUnlockAuth = BinaryToHex( CRYPTMAN->GetMD5ForString(sUnlockEntry + UNLOCK_AUTH_STRING.GetValue()) ); - pEntry->AppendAttr( "Auth", sUnlockAuth ); - } - } - } - - { - XNode* pNumSongsPlayedByPlayMode = pGeneralDataNode->AppendChild("NumSongsPlayedByPlayMode"); - FOREACH_ENUM( PlayMode, pm ) - { - // Don't save unplayed PlayModes. - if( !m_iNumSongsPlayedByPlayMode[pm] ) - continue; - pNumSongsPlayedByPlayMode->AppendChild( PlayModeToString(pm), m_iNumSongsPlayedByPlayMode[pm] ); - } - } - - { - XNode* pNumSongsPlayedByStyle = pGeneralDataNode->AppendChild("NumSongsPlayedByStyle"); - FOREACHM_CONST( StyleID, int, m_iNumSongsPlayedByStyle, iter ) - { - const StyleID &s = iter->first; - int iNumPlays = iter->second; - - XNode *pStyleNode = s.CreateNode(); - pStyleNode->AppendAttr(XNode::TEXT_ATTRIBUTE, iNumPlays ); - - pNumSongsPlayedByStyle->AppendChild( pStyleNode ); - } - } - - { - XNode* pNumSongsPlayedByDifficulty = pGeneralDataNode->AppendChild("NumSongsPlayedByDifficulty"); - FOREACH_ENUM( Difficulty, dc ) - { - if( !m_iNumSongsPlayedByDifficulty[dc] ) - continue; - pNumSongsPlayedByDifficulty->AppendChild( DifficultyToString(dc), m_iNumSongsPlayedByDifficulty[dc] ); - } - } - - { - XNode* pNumSongsPlayedByMeter = pGeneralDataNode->AppendChild("NumSongsPlayedByMeter"); - for( int i=0; iAppendChild( ssprintf("Meter%d",i), m_iNumSongsPlayedByMeter[i] ); - } - } - - pGeneralDataNode->AppendChild( "NumTotalSongsPlayed", m_iNumTotalSongsPlayed ); - - { - XNode* pNumStagesPassedByPlayMode = pGeneralDataNode->AppendChild("NumStagesPassedByPlayMode"); - FOREACH_ENUM( PlayMode, pm ) - { - // Don't save unplayed PlayModes. - if( !m_iNumStagesPassedByPlayMode[pm] ) - continue; - pNumStagesPassedByPlayMode->AppendChild( PlayModeToString(pm), m_iNumStagesPassedByPlayMode[pm] ); - } - } - - { - XNode* pNumStagesPassedByGrade = pGeneralDataNode->AppendChild("NumStagesPassedByGrade"); - FOREACH_ENUM( Grade, g ) - { - if( !m_iNumStagesPassedByGrade[g] ) - continue; - pNumStagesPassedByGrade->AppendChild( GradeToString(g), m_iNumStagesPassedByGrade[g] ); - } - } - - // Load Lua UserTable from profile - if( !IsMachine() && m_UserTable.IsSet() ) - { - Lua *L = LUA->Get(); - m_UserTable.PushSelf( L ); - XNode* pUserTable = XmlFileUtil::XNodeFromTable( L ); - LUA->Release( L ); - - // XXX: XNodeFromTable returns a root node with the name "Layer". - pUserTable->m_sName = "UserTable"; - pGeneralDataNode->AppendChild( pUserTable ); - } - - return pGeneralDataNode; -} - -ProfileLoadResult Profile::LoadEditableDataFromDir( RString sDir ) -{ - RString fn = sDir + EDITABLE_INI; - - // Don't load unreasonably large editable.xml files. - int iBytes = FILEMAN->GetFileSizeInBytes( fn ); - if( iBytes > MAX_EDITABLE_INI_SIZE_BYTES ) - { - LOG->Warn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); - return ProfileLoadResult_FailedTampered; - } - - if( !IsAFile(fn) ) - return ProfileLoadResult_FailedNoProfile; - - IniFile ini; - ini.ReadFile( fn ); - - ini.GetValue( "Editable", "DisplayName", m_sDisplayName ); - ini.GetValue( "Editable", "CharacterID", m_sCharacterID ); - ini.GetValue( "Editable", "LastUsedHighScoreName", m_sLastUsedHighScoreName ); - ini.GetValue( "Editable", "WeightPounds", m_iWeightPounds ); - - // This is data that the user can change, so we have to validate it. - wstring wstr = RStringToWstring(m_sDisplayName); - if( wstr.size() > PROFILE_MAX_DISPLAY_NAME_LENGTH ) - wstr = wstr.substr(0, PROFILE_MAX_DISPLAY_NAME_LENGTH); - m_sDisplayName = WStringToRString(wstr); - // TODO: strip invalid chars? - if( m_iWeightPounds != 0 ) - CLAMP( m_iWeightPounds, 20, 1000 ); - - return ProfileLoadResult_Success; -} - -void Profile::LoadGeneralDataFromNode( const XNode* pNode ) -{ - ASSERT( pNode->GetName() == "GeneralData" ); - - RString s; - const XNode* pTemp; - - pNode->GetChildValue( "DisplayName", m_sDisplayName ); - pNode->GetChildValue( "CharacterID", m_sCharacterID ); - pNode->GetChildValue( "LastUsedHighScoreName", m_sLastUsedHighScoreName ); - pNode->GetChildValue( "WeightPounds", m_iWeightPounds ); - pNode->GetChildValue( "Guid", m_sGuid ); - pNode->GetChildValue( "SortOrder", s ); m_SortOrder = StringToSortOrder( s ); - pNode->GetChildValue( "LastDifficulty", s ); m_LastDifficulty = StringToDifficulty( s ); - pNode->GetChildValue( "LastCourseDifficulty", s ); m_LastCourseDifficulty = StringToDifficulty( s ); - pNode->GetChildValue( "LastStepsType", s ); m_LastStepsType = GAMEMAN->StringToStepsType( s ); - pTemp = pNode->GetChild( "Song" ); if( pTemp ) m_lastSong.LoadFromNode( pTemp ); - pTemp = pNode->GetChild( "Course" ); if( pTemp ) m_lastCourse.LoadFromNode( pTemp ); - pNode->GetChildValue( "TotalSessions", m_iTotalSessions ); - pNode->GetChildValue( "TotalSessionSeconds", m_iTotalSessionSeconds ); - pNode->GetChildValue( "TotalGameplaySeconds", m_iTotalGameplaySeconds ); - pNode->GetChildValue( "TotalCaloriesBurned", m_fTotalCaloriesBurned ); - pNode->GetChildValue( "GoalType", *ConvertValue(&m_GoalType) ); - pNode->GetChildValue( "GoalCalories", m_iGoalCalories ); - pNode->GetChildValue( "GoalSeconds", m_iGoalSeconds ); - pNode->GetChildValue( "LastPlayedMachineGuid", m_sLastPlayedMachineGuid ); - pNode->GetChildValue( "LastPlayedDate", s ); m_LastPlayedDate.FromString( s ); - pNode->GetChildValue( "TotalDancePoints", m_iTotalDancePoints ); - pNode->GetChildValue( "NumExtraStagesPassed", m_iNumExtraStagesPassed ); - pNode->GetChildValue( "NumExtraStagesFailed", m_iNumExtraStagesFailed ); - pNode->GetChildValue( "NumToasties", m_iNumToasties ); - pNode->GetChildValue( "TotalTapsAndHolds", m_iTotalTapsAndHolds ); - pNode->GetChildValue( "TotalJumps", m_iTotalJumps ); - pNode->GetChildValue( "TotalHolds", m_iTotalHolds ); - pNode->GetChildValue( "TotalRolls", m_iTotalRolls ); - pNode->GetChildValue( "TotalMines", m_iTotalMines ); - pNode->GetChildValue( "TotalHands", m_iTotalHands ); - pNode->GetChildValue( "TotalLifts", m_iTotalLifts ); - - { - const XNode* pDefaultModifiers = pNode->GetChild("DefaultModifiers"); - if( pDefaultModifiers ) - { - FOREACH_CONST_Child( pDefaultModifiers, game_type ) - { - game_type->GetTextValue( m_sDefaultModifiers[game_type->GetName()] ); - } - } - } - - { - const XNode* pUnlocks = pNode->GetChild("Unlocks"); - if( pUnlocks ) - { - FOREACH_CONST_Child( pUnlocks, unlock ) - { - RString sUnlockEntryID; - if( !unlock->GetAttrValue("UnlockEntryID",sUnlockEntryID) ) - continue; - - if( !UNLOCK_AUTH_STRING.GetValue().empty() ) - { - RString sUnlockAuth; - if( !unlock->GetAttrValue("Auth", sUnlockAuth) ) - continue; - - RString sExpectedUnlockAuth = BinaryToHex( CRYPTMAN->GetMD5ForString(sUnlockEntryID + UNLOCK_AUTH_STRING.GetValue()) ); - if( sUnlockAuth != sExpectedUnlockAuth ) - continue; - } - - m_UnlockedEntryIDs.insert( sUnlockEntryID ); - } - } - } - - { - const XNode* pNumSongsPlayedByPlayMode = pNode->GetChild("NumSongsPlayedByPlayMode"); - if( pNumSongsPlayedByPlayMode ) - FOREACH_ENUM( PlayMode, pm ) - pNumSongsPlayedByPlayMode->GetChildValue( PlayModeToString(pm), m_iNumSongsPlayedByPlayMode[pm] ); - } - - { - const XNode* pNumSongsPlayedByStyle = pNode->GetChild("NumSongsPlayedByStyle"); - if( pNumSongsPlayedByStyle ) - { - FOREACH_CONST_Child( pNumSongsPlayedByStyle, style ) - { - if( style->GetName() != "Style" ) - continue; - - StyleID sID; - sID.LoadFromNode( style ); - - if( !sID.IsValid() ) - WARN_AND_CONTINUE; - - style->GetTextValue( m_iNumSongsPlayedByStyle[sID] ); - } - } - } - - { - const XNode* pNumSongsPlayedByDifficulty = pNode->GetChild("NumSongsPlayedByDifficulty"); - if( pNumSongsPlayedByDifficulty ) - FOREACH_ENUM( Difficulty, dc ) - pNumSongsPlayedByDifficulty->GetChildValue( DifficultyToString(dc), m_iNumSongsPlayedByDifficulty[dc] ); - } - - { - const XNode* pNumSongsPlayedByMeter = pNode->GetChild("NumSongsPlayedByMeter"); - if( pNumSongsPlayedByMeter ) - for( int i=0; iGetChildValue( ssprintf("Meter%d",i), m_iNumSongsPlayedByMeter[i] ); - } - - pNode->GetChildValue("NumTotalSongsPlayed", m_iNumTotalSongsPlayed ); - - { - const XNode* pNumStagesPassedByGrade = pNode->GetChild("NumStagesPassedByGrade"); - if( pNumStagesPassedByGrade ) - FOREACH_ENUM( Grade, g ) - pNumStagesPassedByGrade->GetChildValue( GradeToString(g), m_iNumStagesPassedByGrade[g] ); - } - - { - const XNode* pNumStagesPassedByPlayMode = pNode->GetChild("NumStagesPassedByPlayMode"); - if( pNumStagesPassedByPlayMode ) - FOREACH_ENUM( PlayMode, pm ) - pNumStagesPassedByPlayMode->GetChildValue( PlayModeToString(pm), m_iNumStagesPassedByPlayMode[pm] ); - - } - - // Build the custom data table from the existing XNode. - if( !IsMachine() ) - { - const XNode *pUserTable = pNode->GetChild( "UserTable" ); - - Lua *L = LUA->Get(); - - // If we have custom data, load it. Otherwise, make a blank table. - if( pUserTable ) - LuaHelpers::CreateTableFromXNode( L, pUserTable ); - else - lua_newtable( L ); - - m_UserTable.SetFromStack( L ); - LUA->Release( L ); - } - -} - -void Profile::AddStepTotals( int iTotalTapsAndHolds, int iTotalJumps, int iTotalHolds, int iTotalRolls, int iTotalMines, int iTotalHands, int iTotalLifts, float fCaloriesBurned ) -{ - m_iTotalTapsAndHolds += iTotalTapsAndHolds; - m_iTotalJumps += iTotalJumps; - m_iTotalHolds += iTotalHolds; - m_iTotalRolls += iTotalRolls; - m_iTotalMines += iTotalMines; - m_iTotalHands += iTotalHands; - m_iTotalLifts += iTotalLifts; - - m_fTotalCaloriesBurned += fCaloriesBurned; - - DateTime date = DateTime::GetNowDate(); - m_mapDayToCaloriesBurned[date].fCals += fCaloriesBurned; -} - -XNode* Profile::SaveSongScoresCreateNode() const -{ - CHECKPOINT; - - const Profile* pProfile = this; - ASSERT( pProfile ); - - XNode* pNode = new XNode( "SongScores" ); - - FOREACHM_CONST( SongID, HighScoresForASong, m_SongHighScores, i ) - { - const SongID &songID = i->first; - const HighScoresForASong &hsSong = i->second; - - // skip songs that have never been played - if( pProfile->GetSongNumTimesPlayed(songID) == 0 ) - continue; - - XNode* pSongNode = pNode->AppendChild( songID.CreateNode() ); - - int jCheck2 = hsSong.m_StepsHighScores.size(); - int jCheck1 = 0; - FOREACHM_CONST( StepsID, HighScoresForASteps, hsSong.m_StepsHighScores, j ) - { - jCheck1++; - ASSERT( jCheck1 <= jCheck2 ); - const StepsID &stepsID = j->first; - const HighScoresForASteps &hsSteps = j->second; - - const HighScoreList &hsl = hsSteps.hsl; - - // skip steps that have never been played - if( hsl.GetNumTimesPlayed() == 0 ) - continue; - - XNode* pStepsNode = pSongNode->AppendChild( stepsID.CreateNode() ); - - pStepsNode->AppendChild( hsl.CreateNode() ); - } - } - - return pNode; -} - -void Profile::LoadSongScoresFromNode( const XNode* pSongScores ) -{ - CHECKPOINT; - - ASSERT( pSongScores->GetName() == "SongScores" ); - - FOREACH_CONST_Child( pSongScores, pSong ) - { - if( pSong->GetName() != "Song" ) - continue; - - SongID songID; - songID.LoadFromNode( pSong ); - if( !songID.IsValid() ) - continue; - - FOREACH_CONST_Child( pSong, pSteps ) - { - if( pSteps->GetName() != "Steps" ) - continue; - - StepsID stepsID; - stepsID.LoadFromNode( pSteps ); - if( !stepsID.IsValid() ) - WARN_AND_CONTINUE; - - const XNode *pHighScoreListNode = pSteps->GetChild("HighScoreList"); - if( pHighScoreListNode == NULL ) - WARN_AND_CONTINUE; - - HighScoreList &hsl = m_SongHighScores[songID].m_StepsHighScores[stepsID].hsl; - hsl.LoadFromNode( pHighScoreListNode ); - } - } -} - - -XNode* Profile::SaveCourseScoresCreateNode() const -{ - CHECKPOINT; - - const Profile* pProfile = this; - ASSERT( pProfile ); - - XNode* pNode = new XNode( "CourseScores" ); - - FOREACHM_CONST( CourseID, HighScoresForACourse, m_CourseHighScores, i ) - { - const CourseID &courseID = i->first; - const HighScoresForACourse &hsCourse = i->second; - - // skip courses that have never been played - if( pProfile->GetCourseNumTimesPlayed(courseID) == 0 ) - continue; - - XNode* pCourseNode = pNode->AppendChild( courseID.CreateNode() ); - - FOREACHM_CONST( TrailID, HighScoresForATrail, hsCourse.m_TrailHighScores, j ) - { - const TrailID &trailID = j->first; - const HighScoresForATrail &hsTrail = j->second; - - const HighScoreList &hsl = hsTrail.hsl; - - // skip steps that have never been played - if( hsl.GetNumTimesPlayed() == 0 ) - continue; - - XNode* pTrailNode = pCourseNode->AppendChild( trailID.CreateNode() ); - - pTrailNode->AppendChild( hsl.CreateNode() ); - } - } - - return pNode; -} - -void Profile::LoadCourseScoresFromNode( const XNode* pCourseScores ) -{ - CHECKPOINT; - - ASSERT( pCourseScores->GetName() == "CourseScores" ); - - vector vpAllCourses; - SONGMAN->GetAllCourses( vpAllCourses, true ); - - FOREACH_CONST_Child( pCourseScores, pCourse ) - { - if( pCourse->GetName() != "Course" ) - continue; - - CourseID courseID; - courseID.LoadFromNode( pCourse ); - if( !courseID.IsValid() ) - WARN_AND_CONTINUE; - - - // Backward compatability hack to fix importing scores of old style - // courses that weren't in group folder but have now been moved into - // a group folder: - // If the courseID doesn't resolve, then take the file name part of sPath - // and search for matches of just the file name. - { - Course *pC = courseID.ToCourse(); - if( pC == NULL ) - { - RString sDir, sFName, sExt; - splitpath( courseID.GetPath(), sDir, sFName, sExt ); - RString sFullFileName = sFName + sExt; - - FOREACH_CONST( Course*, vpAllCourses, c ) - { - RString sOther = (*c)->m_sPath.Right(sFullFileName.size()); - - if( sFullFileName.CompareNoCase(sOther) == 0 ) - { - pC = *c; - courseID.FromCourse( pC ); - break; - } - } - } - } - - - FOREACH_CONST_Child( pCourse, pTrail ) - { - if( pTrail->GetName() != "Trail" ) - continue; - - TrailID trailID; - trailID.LoadFromNode( pTrail ); - if( !trailID.IsValid() ) - WARN_AND_CONTINUE; - - const XNode *pHighScoreListNode = pTrail->GetChild("HighScoreList"); - if( pHighScoreListNode == NULL ) - WARN_AND_CONTINUE; - - HighScoreList &hsl = m_CourseHighScores[courseID].m_TrailHighScores[trailID].hsl; - hsl.LoadFromNode( pHighScoreListNode ); - } - } -} - -XNode* Profile::SaveCategoryScoresCreateNode() const -{ - CHECKPOINT; - - const Profile* pProfile = this; - ASSERT( pProfile ); - - XNode* pNode = new XNode( "CategoryScores" ); - - FOREACH_ENUM( StepsType,st ) - { - // skip steps types that have never been played - if( pProfile->GetCategoryNumTimesPlayed( st ) == 0 ) - continue; - - XNode* pStepsTypeNode = pNode->AppendChild( "StepsType" ); - pStepsTypeNode->AppendAttr( "Type", GAMEMAN->GetStepsTypeInfo(st).szName ); - - FOREACH_ENUM( RankingCategory,rc ) - { - // skip steps types/categories that have never been played - if( pProfile->GetCategoryHighScoreList(st,rc).GetNumTimesPlayed() == 0 ) - continue; - - XNode* pRankingCategoryNode = pStepsTypeNode->AppendChild( "RankingCategory" ); - pRankingCategoryNode->AppendAttr( "Type", RankingCategoryToString(rc) ); - - const HighScoreList &hsl = pProfile->GetCategoryHighScoreList( (StepsType)st, (RankingCategory)rc ); - - pRankingCategoryNode->AppendChild( hsl.CreateNode() ); - } - } - - return pNode; -} - -void Profile::LoadCategoryScoresFromNode( const XNode* pCategoryScores ) -{ - CHECKPOINT; - - ASSERT( pCategoryScores->GetName() == "CategoryScores" ); - - FOREACH_CONST_Child( pCategoryScores, pStepsType ) - { - if( pStepsType->GetName() != "StepsType" ) - continue; - - RString str; - if( !pStepsType->GetAttrValue( "Type", str ) ) - WARN_AND_CONTINUE; - StepsType st = GAMEMAN->StringToStepsType( str ); - if( st == StepsType_Invalid ) - WARN_AND_CONTINUE_M( str ); - - FOREACH_CONST_Child( pStepsType, pRadarCategory ) - { - if( pRadarCategory->GetName() != "RankingCategory" ) - continue; - - if( !pRadarCategory->GetAttrValue( "Type", str ) ) - WARN_AND_CONTINUE; - RankingCategory rc = StringToRankingCategory( str ); - if( rc == RankingCategory_Invalid ) - WARN_AND_CONTINUE_M( str ); - - const XNode *pHighScoreListNode = pRadarCategory->GetChild("HighScoreList"); - if( pHighScoreListNode == NULL ) - WARN_AND_CONTINUE; - - HighScoreList &hsl = this->GetCategoryHighScoreList( st, rc ); - hsl.LoadFromNode( pHighScoreListNode ); - } - } -} - -void Profile::SaveStatsWebPageToDir( RString sDir ) const -{ - ASSERT( PROFILEMAN ); -} - -void Profile::SaveMachinePublicKeyToDir( RString sDir ) const -{ - if( PREFSMAN->m_bSignProfileData && IsAFile(CRYPTMAN->GetPublicKeyFileName()) ) - FileCopy( CRYPTMAN->GetPublicKeyFileName(), sDir+PUBLIC_KEY_FILE ); -} - -void Profile::AddScreenshot( const Screenshot &screenshot ) -{ - m_vScreenshots.push_back( screenshot ); -} - -void Profile::LoadScreenshotDataFromNode( const XNode* pScreenshotData ) -{ - CHECKPOINT; - - ASSERT( pScreenshotData->GetName() == "ScreenshotData" ); - FOREACH_CONST_Child( pScreenshotData, pScreenshot ) - { - if( pScreenshot->GetName() != "Screenshot" ) - WARN_AND_CONTINUE_M( pScreenshot->GetName() ); - - Screenshot ss; - ss.LoadFromNode( pScreenshot ); - - m_vScreenshots.push_back( ss ); - } -} - -XNode* Profile::SaveScreenshotDataCreateNode() const -{ - CHECKPOINT; - - const Profile* pProfile = this; - ASSERT( pProfile ); - - XNode* pNode = new XNode( "ScreenshotData" ); - - FOREACH_CONST( Screenshot, m_vScreenshots, ss ) - { - pNode->AppendChild( ss->CreateNode() ); - } - - return pNode; -} - -void Profile::LoadCalorieDataFromNode( const XNode* pCalorieData ) -{ - CHECKPOINT; - - ASSERT( pCalorieData->GetName() == "CalorieData" ); - FOREACH_CONST_Child( pCalorieData, pCaloriesBurned ) - { - if( pCaloriesBurned->GetName() != "CaloriesBurned" ) - WARN_AND_CONTINUE_M( pCaloriesBurned->GetName() ); - - RString sDate; - if( !pCaloriesBurned->GetAttrValue("Date",sDate) ) - WARN_AND_CONTINUE; - DateTime date; - if( !date.FromString(sDate) ) - WARN_AND_CONTINUE_M( sDate ); - - float fCaloriesBurned = 0; - - pCaloriesBurned->GetTextValue(fCaloriesBurned); - - m_mapDayToCaloriesBurned[date].fCals = fCaloriesBurned; - } -} - -XNode* Profile::SaveCalorieDataCreateNode() const -{ - CHECKPOINT; - - const Profile* pProfile = this; - ASSERT( pProfile ); - - XNode* pNode = new XNode( "CalorieData" ); - - FOREACHM_CONST( DateTime, Calories, m_mapDayToCaloriesBurned, i ) - { - XNode* pCaloriesBurned = pNode->AppendChild( "CaloriesBurned", i->second.fCals ); - - pCaloriesBurned->AppendAttr( "Date", i->first.GetString() ); - } - - return pNode; -} - -float Profile::GetCaloriesBurnedForDay( DateTime day ) const -{ - day.StripTime(); - map::const_iterator i = m_mapDayToCaloriesBurned.find( day ); - if( i == m_mapDayToCaloriesBurned.end() ) - return 0; - else - return i->second.fCals; -} - -/* -static void SaveRecentScore( XNode* xml ) -{ - RString sDate = DateTime::GetNowDate().GetString(); - sDate.Replace(":","-"); - - RString sFileNameNoExtension = Profile::MakeUniqueFileNameNoExtension(UPLOAD_SUBDIR, sDate ); - RString fn = UPLOAD_SUBDIR + sFileNameNoExtension + ".xml"; - - RString sStatsXmlSigFile = fn+SIGNATURE_APPEND; - CryptManager::SignFileToFile(fn, sStatsXmlSigFile); -} - -XNode* Profile::HighScoreForASongAndSteps::CreateNode() const -{ - XNode* pNode = new XNode( "HighScoreForASongAndSteps" ); - - pNode->AppendChild( songID.CreateNode() ); - pNode->AppendChild( stepsID.CreateNode() ); - pNode->AppendChild( hs.CreateNode() ); - - return pNode; -} - -void Profile::SaveStepsRecentScore( const Song* pSong, const Steps* pSteps, HighScore hs ) -{ - ASSERT( pSong ); - ASSERT( pSteps ); - HighScoreForASongAndSteps h; - h.songID.FromSong( pSong ); - ASSERT( h.songID.IsValid() ); - h.stepsID.FromSteps( pSteps ); - ASSERT( h.stepsID.IsValid() ); - h.hs = hs; - - auto_ptr xml( new XNode("Stats") ); - xml->AppendChild( "MachineGuid", PROFILEMAN->GetMachineProfile()->m_sGuid ); - XNode *recent = xml->AppendChild( new XNode("RecentSongScores") ); - recent->AppendChild( h.CreateNode() ); - - SaveRecentScore( xml.get() ); -} - - -XNode* Profile::HighScoreForACourseAndTrail::CreateNode() const -{ - XNode* pNode = new XNode( "HighScoreForACourseAndTrail" ); - - pNode->AppendChild( courseID.CreateNode() ); - pNode->AppendChild( trailID.CreateNode() ); - pNode->AppendChild( hs.CreateNode() ); - - return pNode; -} - -void Profile::SaveCourseRecentScore( const Course* pCourse, const Trail* pTrail, HighScore hs ) -{ - HighScoreForACourseAndTrail h; - h.courseID.FromCourse( pCourse ); - h.trailID.FromTrail( pTrail ); - h.hs = hs; - - auto_ptr xml( new XNode("Stats") ); - xml->AppendChild( "MachineGuid", PROFILEMAN->GetMachineProfile()->m_sGuid ); - XNode *recent = xml->AppendChild( new XNode("RecentCourseScores") ); - recent->AppendChild( h.CreateNode() ); - SaveRecentScore( xml.get() ); -} -*/ -const Profile::HighScoresForASong *Profile::GetHighScoresForASong( const SongID& songID ) const -{ - map::const_iterator it; - it = m_SongHighScores.find( songID ); - if( it == m_SongHighScores.end() ) - return NULL; - return &it->second; -} - -const Profile::HighScoresForACourse *Profile::GetHighScoresForACourse( const CourseID& courseID ) const -{ - map::const_iterator it; - it = m_CourseHighScores.find( courseID ); - if( it == m_CourseHighScores.end() ) - return NULL; - return &it->second; -} - -bool Profile::IsMachine() const -{ - // TODO: Think of a better way to handle this - return this == PROFILEMAN->GetMachineProfile(); -} - - -XNode* Profile::SaveCoinDataCreateNode() const -{ - CHECKPOINT; - - const Profile* pProfile = this; - ASSERT( pProfile ); - - XNode* pNode = new XNode( "CoinData" ); - - { - int coins[NUM_LAST_DAYS]; - BOOKKEEPER->GetCoinsLastDays( coins ); - XNode* p = pNode->AppendChild( "LastDays" ); - for( int i=0; iAppendChild( LastDayToString(i), coins[i] ); - } - { - int coins[NUM_LAST_WEEKS]; - BOOKKEEPER->GetCoinsLastWeeks( coins ); - XNode* p = pNode->AppendChild( "LastWeeks" ); - for( int i=0; iAppendChild( LastWeekToString(i), coins[i] ); - } - { - int coins[DAYS_IN_WEEK]; - BOOKKEEPER->GetCoinsByDayOfWeek( coins ); - XNode* p = pNode->AppendChild( "DayOfWeek" ); - for( int i=0; iAppendChild( DayOfWeekToString(i), coins[i] ); - } - { - int coins[HOURS_IN_DAY]; - BOOKKEEPER->GetCoinsByHour( coins ); - XNode* p = pNode->AppendChild( "Hour" ); - for( int i=0; iAppendChild( HourInDayToString(i), coins[i] ); - } - - return pNode; -} - -void Profile::MoveBackupToDir( RString sFromDir, RString sToDir ) -{ - if( FILEMAN->IsAFile(sFromDir + STATS_XML) && - FILEMAN->IsAFile(sFromDir+STATS_XML+SIGNATURE_APPEND) ) - { - FILEMAN->Move( sFromDir+STATS_XML, sToDir+STATS_XML ); - FILEMAN->Move( sFromDir+STATS_XML+SIGNATURE_APPEND, sToDir+STATS_XML+SIGNATURE_APPEND ); - } - else if( FILEMAN->IsAFile(sFromDir + STATS_XML_GZ) && - FILEMAN->IsAFile(sFromDir+STATS_XML_GZ+SIGNATURE_APPEND) ) - { - FILEMAN->Move( sFromDir+STATS_XML_GZ, sToDir+STATS_XML ); - FILEMAN->Move( sFromDir+STATS_XML_GZ+SIGNATURE_APPEND, sToDir+STATS_XML+SIGNATURE_APPEND ); - } - - if( FILEMAN->IsAFile(sFromDir + EDITABLE_INI) ) - FILEMAN->Move( sFromDir+EDITABLE_INI, sToDir+EDITABLE_INI ); - if( FILEMAN->IsAFile(sFromDir + DONT_SHARE_SIG) ) - FILEMAN->Move( sFromDir+DONT_SHARE_SIG, sToDir+DONT_SHARE_SIG ); -} - -RString Profile::MakeUniqueFileNameNoExtension( RString sDir, RString sFileNameBeginning ) -{ - FILEMAN->FlushDirCache( sDir ); - // Find a file name for the screenshot - vector files; - GetDirListing( sDir + sFileNameBeginning+"*", files, false, false ); - sort( files.begin(), files.end() ); - - int iIndex = 0; - - for( int i = files.size()-1; i >= 0; --i ) - { - static Regex re( "^" + sFileNameBeginning + "([0-9]{5})\\....$" ); - vector matches; - if( !re.Compare( files[i], matches ) ) - continue; - - ASSERT( matches.size() == 1 ); - iIndex = StringToInt( matches[0] )+1; - break; - } - - return MakeFileNameNoExtension( sFileNameBeginning, iIndex ); -} - -RString Profile::MakeFileNameNoExtension( RString sFileNameBeginning, int iIndex ) -{ - return sFileNameBeginning + ssprintf( "%05d", iIndex ); -} - -// lua start -#include "LuaBinding.h" - -/** @brief Allow Lua to have access to the Profile. */ -class LunaProfile: public Luna -{ -public: - static int GetDisplayName( T* p, lua_State *L ) { lua_pushstring(L, p->m_sDisplayName ); return 1; } - static int GetLastUsedHighScoreName( T* p, lua_State *L ) { lua_pushstring(L, p->m_sLastUsedHighScoreName ); return 1; } - static int GetHighScoreList( T* p, lua_State *L ) - { - if( LuaBinding::CheckLuaObjectType(L, 1, "Song") ) - { - const Song *pSong = Luna::check(L,1); - const Steps *pSteps = Luna::check(L,2); - HighScoreList &hsl = p->GetStepsHighScoreList( pSong, pSteps ); - hsl.PushSelf( L ); - return 1; - } - else if( LuaBinding::CheckLuaObjectType(L, 1, "Course") ) - { - const Course *pCourse = Luna::check(L,1); - const Trail *pTrail = Luna::check(L,2); - HighScoreList &hsl = p->GetCourseHighScoreList( pCourse, pTrail ); - hsl.PushSelf( L ); - return 1; - } - - luaL_typerror( L, 1, "Song or Course" ); - return 0; - } - - static int GetCharacter( T* p, lua_State *L ) { p->GetCharacter()->PushSelf(L); return 1; } - static int SetCharacter( T* p, lua_State *L ) { p->SetCharacter(SArg(1)); return 0; } - static int GetWeightPounds( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iWeightPounds ); return 1; } - static int SetWeightPounds( T* p, lua_State *L ) { p->m_iWeightPounds = IArg(1); return 0; } - static int GetGoalType( T* p, lua_State *L ) { lua_pushnumber(L, p->m_GoalType ); return 1; } - static int SetGoalType( T* p, lua_State *L ) { p->m_GoalType = Enum::Check(L, 1); return 0; } - static int GetGoalCalories( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iGoalCalories ); return 1; } - static int SetGoalCalories( T* p, lua_State *L ) { p->m_iGoalCalories = IArg(1); return 0; } - static int GetGoalSeconds( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iGoalSeconds ); return 1; } - static int SetGoalSeconds( T* p, lua_State *L ) { p->m_iGoalSeconds = IArg(1); return 0; } - static int GetCaloriesBurnedToday( T* p, lua_State *L ) { lua_pushnumber(L, p->GetCaloriesBurnedToday() ); return 1; } - static int GetTotalNumSongsPlayed( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iNumTotalSongsPlayed ); return 1; } - static int IsCodeUnlocked( T* p, lua_State *L ) { lua_pushboolean(L, p->IsCodeUnlocked(SArg(1)) ); return 1; } - static int GetSongsActual( T* p, lua_State *L ) { lua_pushnumber(L, p->GetSongsActual(Enum::Check(L, 1),Enum::Check(L, 2)) ); return 1; } - static int GetCoursesActual( T* p, lua_State *L ) { lua_pushnumber(L, p->GetCoursesActual(Enum::Check(L, 1),Enum::Check(L, 2)) ); return 1; } - static int GetSongsPossible( T* p, lua_State *L ) { lua_pushnumber(L, p->GetSongsPossible(Enum::Check(L, 1),Enum::Check(L, 2)) ); return 1; } - static int GetCoursesPossible( T* p, lua_State *L ) { lua_pushnumber(L, p->GetCoursesPossible(Enum::Check(L, 1),Enum::Check(L, 2)) ); return 1; } - static int GetSongsPercentComplete( T* p, lua_State *L ) { lua_pushnumber(L, p->GetSongsPercentComplete(Enum::Check(L, 1),Enum::Check(L, 2)) ); return 1; } - static int GetCoursesPercentComplete( T* p, lua_State *L ) { lua_pushnumber(L, p->GetCoursesPercentComplete(Enum::Check(L, 1),Enum::Check(L, 2)) ); return 1; } - static int GetTotalStepsWithTopGrade( T* p, lua_State *L ) { lua_pushnumber(L, p->GetTotalStepsWithTopGrade(Enum::Check(L, 1),Enum::Check(L, 2),Enum::Check(L, 3)) ); return 1; } - static int GetTotalTrailsWithTopGrade( T* p, lua_State *L ) { lua_pushnumber(L, p->GetTotalTrailsWithTopGrade(Enum::Check(L, 1),Enum::Check(L, 2),Enum::Check(L, 3)) ); return 1; } - static int GetNumTotalSongsPlayed( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iNumTotalSongsPlayed ); return 1; } - static int GetTotalSessions( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalSessions ); return 1; } - static int GetTotalSessionSeconds( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalSessionSeconds ); return 1; } - static int GetTotalGameplaySeconds( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalGameplaySeconds ); return 1; } - static int GetSongsAndCoursesPercentCompleteAllDifficulties( T* p, lua_State *L ) { lua_pushnumber(L, p->GetSongsAndCoursesPercentCompleteAllDifficulties(Enum::Check(L, 1)) ); return 1; } - static int GetTotalCaloriesBurned( T* p, lua_State *L ) { lua_pushnumber(L, p->m_fTotalCaloriesBurned ); return 1; } - static int GetDisplayTotalCaloriesBurned( T* p, lua_State *L ) { lua_pushstring(L, p->GetDisplayTotalCaloriesBurned() ); return 1; } - static int GetMostPopularSong( T* p, lua_State *L ) - { - Song *p2 = p->GetMostPopularSong(); - if( p2 ) - p2->PushSelf(L); - else - lua_pushnil( L ); - return 1; - } - static int GetMostPopularCourse( T* p, lua_State *L ) - { - Course *p2 = p->GetMostPopularCourse(); - if( p2 ) - p2->PushSelf(L); - else - lua_pushnil( L ); - return 1; - } - static int GetSongNumTimesPlayed( T* p, lua_State *L ) - { - ASSERT( !lua_isnil(L,1) ); - Song *pS = Luna::check(L,1); - lua_pushnumber( L, p->GetSongNumTimesPlayed(pS) ); - return 1; - } - static int HasPassedAnyStepsInSong( T* p, lua_State *L ) - { - ASSERT( !lua_isnil(L,1) ); - Song *pS = Luna::check(L,1); - lua_pushboolean( L, p->HasPassedAnyStepsInSong(pS) ); - return 1; - } - static int GetNumToasties( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iNumToasties ); return 1; } - static int GetTotalTapsAndHolds( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalTapsAndHolds ); return 1; } - static int GetTotalJumps( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalJumps ); return 1; } - static int GetTotalHolds( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalHolds ); return 1; } - static int GetTotalRolls( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalRolls ); return 1; } - static int GetTotalMines( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalMines ); return 1; } - static int GetTotalHands( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalHands ); return 1; } - static int GetTotalLifts( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalLifts ); return 1; } - static int GetUserTable( T* p, lua_State *L ) { p->m_UserTable.PushSelf(L); return 1; } - static int GetLastPlayedSong( T* p, lua_State *L ) - { - Song *pS = p->m_lastSong.ToSong(); - if( pS ) - pS->PushSelf(L); - else - lua_pushnil( L ); - return 1; - } - static int GetLastPlayedCourse( T* p, lua_State *L ) - { - Course *pC = p->m_lastCourse.ToCourse(); - if( pC ) - pC->PushSelf(L); - else - lua_pushnil( L ); - return 1; - } - - LunaProfile() - { - ADD_METHOD( GetDisplayName ); - ADD_METHOD( GetLastUsedHighScoreName ); - ADD_METHOD( GetHighScoreList ); - ADD_METHOD( GetCharacter ); - ADD_METHOD( SetCharacter ); - ADD_METHOD( GetWeightPounds ); - ADD_METHOD( SetWeightPounds ); - ADD_METHOD( GetGoalType ); - ADD_METHOD( SetGoalType ); - ADD_METHOD( GetGoalCalories ); - ADD_METHOD( SetGoalCalories ); - ADD_METHOD( GetGoalSeconds ); - ADD_METHOD( SetGoalSeconds ); - ADD_METHOD( GetCaloriesBurnedToday ); - ADD_METHOD( GetTotalNumSongsPlayed ); - ADD_METHOD( IsCodeUnlocked ); - ADD_METHOD( GetSongsActual ); - ADD_METHOD( GetCoursesActual ); - ADD_METHOD( GetSongsPossible ); - ADD_METHOD( GetCoursesPossible ); - ADD_METHOD( GetSongsPercentComplete ); - ADD_METHOD( GetCoursesPercentComplete ); - ADD_METHOD( GetTotalStepsWithTopGrade ); - ADD_METHOD( GetTotalTrailsWithTopGrade ); - ADD_METHOD( GetNumTotalSongsPlayed ); - ADD_METHOD( GetTotalSessions ); - ADD_METHOD( GetTotalSessionSeconds ); - ADD_METHOD( GetTotalGameplaySeconds ); - ADD_METHOD( GetSongsAndCoursesPercentCompleteAllDifficulties ); - ADD_METHOD( GetTotalCaloriesBurned ); - ADD_METHOD( GetDisplayTotalCaloriesBurned ); - ADD_METHOD( GetMostPopularSong ); - ADD_METHOD( GetMostPopularCourse ); - ADD_METHOD( GetSongNumTimesPlayed ); - ADD_METHOD( HasPassedAnyStepsInSong ); - ADD_METHOD( GetNumToasties ); - ADD_METHOD( GetTotalTapsAndHolds ); - ADD_METHOD( GetTotalJumps ); - ADD_METHOD( GetTotalHolds ); - ADD_METHOD( GetTotalRolls ); - ADD_METHOD( GetTotalMines ); - ADD_METHOD( GetTotalHands ); - ADD_METHOD( GetTotalLifts ); - ADD_METHOD( GetUserTable ); - ADD_METHOD( GetLastPlayedSong ); - ADD_METHOD( GetLastPlayedCourse ); - } -}; - -LUA_REGISTER_CLASS( Profile ) -// lua end - - -/* - * (c) 2001-2004 Chris Danford - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, and/or sell copies of the Software, and to permit persons to - * whom the Software is furnished to do so, provided that the above - * copyright notice(s) and this permission notice appear in all copies of - * the Software and that both the above copyright notice(s) and this - * permission notice appear in supporting documentation. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF - * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS - * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT - * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS - * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ +#include "global.h" +#include "Profile.h" +#include "RageUtil.h" +#include "PrefsManager.h" +#include "XmlFile.h" +#include "IniFile.h" +#include "GameManager.h" +#include "RageLog.h" +#include "Song.h" +#include "SongManager.h" +#include "Steps.h" +#include "Course.h" +#include "ThemeManager.h" +#include "CryptManager.h" +#include "ProfileManager.h" +#include "RageFile.h" +#include "RageFileDriverDeflate.h" +#include "RageFileManager.h" +#include "LuaManager.h" +#include "UnlockManager.h" +#include "XmlFile.h" +#include "XmlFileUtil.h" +#include "Foreach.h" +#include "Bookkeeper.h" +#include "Game.h" +#include "CharacterManager.h" +#include "Character.h" + +const RString STATS_XML = "Stats.xml"; +const RString STATS_XML_GZ = "Stats.xml.gz"; +/** @brief The filename for where one can edit their personal profile information. */ +const RString EDITABLE_INI = "Editable.ini"; +/** @brief The filename containing the signature for STATS_XML's signature. */ +const RString DONT_SHARE_SIG = "DontShare.sig"; +const RString PUBLIC_KEY_FILE = "public.key"; +const RString SCREENSHOTS_SUBDIR = "Screenshots/"; +const RString EDIT_STEPS_SUBDIR = "Edits/"; +const RString EDIT_COURSES_SUBDIR = "EditCourses/"; +//const RString UPLOAD_SUBDIR = "Upload/"; +const RString RIVAL_SUBDIR = "Rivals/"; + +ThemeMetric SHOW_COIN_DATA( "Profile", "ShowCoinData" ); +static Preference g_bProfileDataCompress( "ProfileDataCompress", false ); +static ThemeMetric UNLOCK_AUTH_STRING( "Profile", "UnlockAuthString" ); +#define GUID_SIZE_BYTES 8 + +#define MAX_EDITABLE_INI_SIZE_BYTES 2*1024 // 2KB +#define MAX_PLAYER_STATS_XML_SIZE_BYTES \ + 400 /* Songs */ \ + * 5 /* Steps per Song */ \ + * 5 /* HighScores per Steps */ \ + * 1024 /* size in bytes of a HighScores XNode */ + +const unsigned int DEFAULT_WEIGHT_POUNDS = 120; + +#if defined(_MSC_VER) +#pragma warning (disable : 4706) // assignment within conditional expression +#endif + + +int Profile::HighScoresForASong::GetNumTimesPlayed() const +{ + int iCount = 0; + FOREACHM_CONST( StepsID, HighScoresForASteps, m_StepsHighScores, i ) + { + iCount += i->second.hsl.GetNumTimesPlayed(); + } + return iCount; +} + +int Profile::HighScoresForACourse::GetNumTimesPlayed() const +{ + int iCount = 0; + FOREACHM_CONST( TrailID, HighScoresForATrail, m_TrailHighScores, i ) + { + iCount += i->second.hsl.GetNumTimesPlayed(); + } + return iCount; +} + + +void Profile::InitEditableData() +{ + m_sDisplayName = ""; + m_sCharacterID = ""; + m_sLastUsedHighScoreName = ""; + m_iWeightPounds = 0; +} + +void Profile::ClearStats() +{ + // don't reset the Guid + RString sGuid = m_sGuid; + InitAll(); + m_sGuid = sGuid; +} + +RString Profile::MakeGuid() +{ + RString s; + s.reserve( GUID_SIZE_BYTES*2 ); + unsigned char buf[GUID_SIZE_BYTES]; + CryptManager::GetRandomBytes( buf, GUID_SIZE_BYTES ); + for( unsigned i=0; i vpCharacters; + CHARMAN->GetCharacters( vpCharacters ); + FOREACH_CONST( Character*, vpCharacters, c ) + { + if( (*c)->m_sCharacterID.CompareNoCase(m_sCharacterID)==0 ) + return *c; + } + return CHARMAN->GetDefaultCharacter(); +} + +void Profile::SetCharacter(const RString sCharacterID) +{ + if(CHARMAN->GetCharacterFromID(sCharacterID)) + m_sCharacterID = sCharacterID; +} + +static RString FormatCalories( float fCals ) +{ + return Commify((int)fCals) + " Cal"; +} + +int Profile::GetCalculatedWeightPounds() const +{ + if( m_iWeightPounds == 0 ) // weight not entered + return DEFAULT_WEIGHT_POUNDS; + else + return m_iWeightPounds; +} + +RString Profile::GetDisplayTotalCaloriesBurned() const +{ + return FormatCalories( m_fTotalCaloriesBurned ); +} + +RString Profile::GetDisplayTotalCaloriesBurnedToday() const +{ + float fCals = GetCaloriesBurnedToday(); + return FormatCalories( fCals ); +} + +float Profile::GetCaloriesBurnedToday() const +{ + DateTime now = DateTime::GetNowDate(); + return GetCaloriesBurnedForDay(now); +} + +int Profile::GetTotalNumSongsPassed() const +{ + int iTotal = 0; + FOREACH_ENUM( PlayMode, i ) + iTotal += m_iNumStagesPassedByPlayMode[i]; + return iTotal; +} + +int Profile::GetTotalStepsWithTopGrade( StepsType st, Difficulty d, Grade g ) const +{ + int iCount = 0; + + FOREACH_CONST( Song*, SONGMAN->GetAllSongs(), pSong ) + { + if( !(*pSong)->NormallyDisplayed() ) + continue; // skip + + FOREACH_CONST( Steps*, (*pSong)->GetAllSteps(), pSteps ) + { + if( (*pSteps)->m_StepsType != st ) + continue; // skip + + if( (*pSteps)->GetDifficulty() != d ) + continue; // skip + + const HighScoreList &hsl = GetStepsHighScoreList( *pSong, *pSteps ); + if( hsl.vHighScores.empty() ) + continue; // skip + + if( hsl.vHighScores[0].GetGrade() == g ) + iCount++; + } + } + + return iCount; +} + +int Profile::GetTotalTrailsWithTopGrade( StepsType st, CourseDifficulty d, Grade g ) const +{ + int iCount = 0; + + // add course high scores + vector vCourses; + SONGMAN->GetAllCourses( vCourses, false ); + FOREACH_CONST( Course*, vCourses, pCourse ) + { + // Don't count any course that has any entries that change over time. + if( !(*pCourse)->AllSongsAreFixed() ) + continue; + + vector vTrails; + Trail* pTrail = (*pCourse)->GetTrail( st, d ); + if( pTrail == NULL ) + continue; + + const HighScoreList &hsl = GetCourseHighScoreList( *pCourse, pTrail ); + if( hsl.vHighScores.empty() ) + continue; // skip + + if( hsl.vHighScores[0].GetGrade() == g ) + iCount++; + } + + return iCount; +} + +float Profile::GetSongsPossible( StepsType st, Difficulty dc ) const +{ + int iTotalSteps = 0; + + // add steps high scores + const vector &vSongs = SONGMAN->GetAllSongs(); + for( unsigned i=0; iNormallyDisplayed() ) + continue; // skip + + vector vSteps = pSong->GetAllSteps(); + for( unsigned j=0; jm_StepsType != st ) + continue; // skip + + if( pSteps->GetDifficulty() != dc ) + continue; // skip + + iTotalSteps++; + } + } + + return (float) iTotalSteps; +} + +float Profile::GetSongsActual( StepsType st, Difficulty dc ) const +{ + CHECKPOINT_M( ssprintf("Profile::GetSongsActual(%d,%d)",st,dc) ); + + float fTotalPercents = 0; + + // add steps high scores + FOREACHM_CONST( SongID, HighScoresForASong, m_SongHighScores, i ) + { + const SongID &id = i->first; + Song* pSong = id.ToSong(); + + CHECKPOINT_M( ssprintf("Profile::GetSongsActual: %p", pSong) ); + + // If the Song isn't loaded on the current machine, then we can't + // get radar values to compute dance points. + if( pSong == NULL ) + continue; + + if( !pSong->NormallyDisplayed() ) + continue; // skip + + CHECKPOINT_M( ssprintf("Profile::GetSongsActual: song %s", pSong->GetSongDir().c_str()) ); + const HighScoresForASong &hsfas = i->second; + + FOREACHM_CONST( StepsID, HighScoresForASteps, hsfas.m_StepsHighScores, j ) + { + const StepsID &sid = j->first; + Steps* pSteps = sid.ToSteps( pSong, true ); + CHECKPOINT_M( ssprintf("Profile::GetSongsActual: song %p, steps %p", pSong, pSteps) ); + + // If the Steps isn't loaded on the current machine, then we can't + // get radar values to compute dance points. + if( pSteps == NULL ) + continue; + + if( pSteps->m_StepsType != st ) + continue; + + CHECKPOINT_M( ssprintf("Profile::GetSongsActual: n %s = %p", sid.ToString().c_str(), pSteps) ); + if( pSteps->GetDifficulty() != dc ) + continue; // skip + CHECKPOINT; + + const HighScoresForASteps& h = j->second; + const HighScoreList& hsl = h.hsl; + + fTotalPercents += hsl.GetTopScore().GetPercentDP(); + } + CHECKPOINT; + } + + return fTotalPercents; +} + +float Profile::GetSongsPercentComplete( StepsType st, Difficulty dc ) const +{ + return GetSongsActual(st,dc) / GetSongsPossible(st,dc); +} + +static void GetHighScoreCourses( vector &vpCoursesOut ) +{ + vpCoursesOut.clear(); + + vector vpCourses; + SONGMAN->GetAllCourses( vpCourses, false ); + FOREACH_CONST( Course*, vpCourses, c ) + { + // Don't count any course that has any entries that change over time. + if( !(*c)->AllSongsAreFixed() ) + continue; + + vpCoursesOut.push_back( *c ); + } +} + +float Profile::GetCoursesPossible( StepsType st, CourseDifficulty cd ) const +{ + int iTotalTrails = 0; + + vector vpCourses; + GetHighScoreCourses( vpCourses ); + FOREACH_CONST( Course*, vpCourses, c ) + { + Trail* pTrail = (*c)->GetTrail(st,cd); + if( pTrail == NULL ) + continue; + + iTotalTrails++; + } + + return (float) iTotalTrails; +} + +float Profile::GetCoursesActual( StepsType st, CourseDifficulty cd ) const +{ + float fTotalPercents = 0; + + vector vpCourses; + GetHighScoreCourses( vpCourses ); + FOREACH_CONST( Course*, vpCourses, c ) + { + Trail *pTrail = (*c)->GetTrail( st, cd ); + if( pTrail == NULL ) + continue; + + const HighScoreList& hsl = GetCourseHighScoreList( *c, pTrail ); + fTotalPercents += hsl.GetTopScore().GetPercentDP(); + } + + return fTotalPercents; +} + +float Profile::GetCoursesPercentComplete( StepsType st, CourseDifficulty cd ) const +{ + return GetCoursesActual(st,cd) / GetCoursesPossible(st,cd); +} + +float Profile::GetSongsAndCoursesPercentCompleteAllDifficulties( StepsType st ) const +{ + float fActual = 0; + float fPossible = 0; + FOREACH_ENUM( Difficulty, d ) + { + fActual += GetSongsActual(st,d); + fPossible += GetSongsPossible(st,d); + } + FOREACH_ENUM( CourseDifficulty, d ) + { + fActual += GetCoursesActual(st,d); + fPossible += GetCoursesPossible(st,d); + } + return fActual / fPossible; +} + +int Profile::GetSongNumTimesPlayed( const Song* pSong ) const +{ + SongID songID; + songID.FromSong( pSong ); + return GetSongNumTimesPlayed( songID ); +} + +int Profile::GetSongNumTimesPlayed( const SongID& songID ) const +{ + const HighScoresForASong *hsSong = GetHighScoresForASong( songID ); + if( hsSong == NULL ) + return 0; + + int iTotalNumTimesPlayed = 0; + FOREACHM_CONST( StepsID, HighScoresForASteps, hsSong->m_StepsHighScores, j ) + { + const HighScoresForASteps &hsSteps = j->second; + + iTotalNumTimesPlayed += hsSteps.hsl.GetNumTimesPlayed(); + } + return iTotalNumTimesPlayed; +} + +/* + * Get the profile default modifiers. Return true if set, in which case sModifiersOut + * will be set. Return false if no modifier string is set, in which case the theme + * defaults should be used. Note that the null string means "no modifiers are active", + * which is distinct from no modifier string being set at all. + * + * In practice, we get the default modifiers from the theme the first time a game + * is played, and from the profile every time thereafter. + */ +bool Profile::GetDefaultModifiers( const Game* pGameType, RString &sModifiersOut ) const +{ + map::const_iterator it; + it = m_sDefaultModifiers.find( pGameType->m_szName ); + if( it == m_sDefaultModifiers.end() ) + return false; + sModifiersOut = it->second; + return true; +} + +void Profile::SetDefaultModifiers( const Game* pGameType, const RString &sModifiers ) +{ + if( sModifiers == "" ) + m_sDefaultModifiers.erase( pGameType->m_szName ); + else + m_sDefaultModifiers[pGameType->m_szName] = sModifiers; +} + +bool Profile::IsCodeUnlocked( RString sUnlockEntryID ) const +{ + return m_UnlockedEntryIDs.find( sUnlockEntryID ) != m_UnlockedEntryIDs.end(); +} + + +Song *Profile::GetMostPopularSong() const +{ + int iMaxNumTimesPlayed = 0; + SongID id; + FOREACHM_CONST( SongID, HighScoresForASong, m_SongHighScores, i ) + { + int iNumTimesPlayed = i->second.GetNumTimesPlayed(); + if( iNumTimesPlayed > iMaxNumTimesPlayed ) + { + id = i->first; + iMaxNumTimesPlayed = iNumTimesPlayed; + } + } + + return id.ToSong(); +} + +Course *Profile::GetMostPopularCourse() const +{ + int iMaxNumTimesPlayed = 0; + CourseID id; + FOREACHM_CONST( CourseID, HighScoresForACourse, m_CourseHighScores, i ) + { + int iNumTimesPlayed = i->second.GetNumTimesPlayed(); + if( iNumTimesPlayed > iMaxNumTimesPlayed ) + { + id = i->first; + iMaxNumTimesPlayed = iNumTimesPlayed; + } + } + + return id.ToCourse(); +} + +// Steps high scores +void Profile::AddStepsHighScore( const Song* pSong, const Steps* pSteps, HighScore hs, int &iIndexOut ) +{ + GetStepsHighScoreList(pSong,pSteps).AddHighScore( hs, iIndexOut, IsMachine() ); +} + +const HighScoreList& Profile::GetStepsHighScoreList( const Song* pSong, const Steps* pSteps ) const +{ + return ((Profile*)this)->GetStepsHighScoreList(pSong,pSteps); +} + +HighScoreList& Profile::GetStepsHighScoreList( const Song* pSong, const Steps* pSteps ) +{ + SongID songID; + songID.FromSong( pSong ); + + StepsID stepsID; + stepsID.FromSteps( pSteps ); + + HighScoresForASong &hsSong = m_SongHighScores[songID]; // operator[] inserts into map + HighScoresForASteps &hsSteps = hsSong.m_StepsHighScores[stepsID]; // operator[] inserts into map + + return hsSteps.hsl; +} + +int Profile::GetStepsNumTimesPlayed( const Song* pSong, const Steps* pSteps ) const +{ + return GetStepsHighScoreList(pSong,pSteps).GetNumTimesPlayed(); +} + +DateTime Profile::GetSongLastPlayedDateTime( const Song* pSong ) const +{ + SongID id; + id.FromSong( pSong ); + std::map::const_iterator iter = m_SongHighScores.find( id ); + + // don't call this unless has been played once + ASSERT( iter != m_SongHighScores.end() ); + ASSERT( !iter->second.m_StepsHighScores.empty() ); + + DateTime dtLatest; // starts out zeroed + FOREACHM_CONST( StepsID, HighScoresForASteps, iter->second.m_StepsHighScores, i ) + { + const HighScoreList &hsl = i->second.hsl; + if( hsl.GetNumTimesPlayed() == 0 ) + continue; + if( dtLatest < hsl.GetLastPlayed() ) + dtLatest = hsl.GetLastPlayed(); + } + return dtLatest; +} + +bool Profile::HasPassedSteps( const Song* pSong, const Steps* pSteps ) const +{ + const HighScoreList &hsl = GetStepsHighScoreList( pSong, pSteps ); + Grade grade = hsl.GetTopScore().GetGrade(); + switch( grade ) + { + case Grade_Failed: + case Grade_NoData: + return false; + default: + return true; + } +} + +bool Profile::HasPassedAnyStepsInSong( const Song* pSong ) const +{ + FOREACH_CONST( Steps*, pSong->GetAllSteps(), steps ) + { + if( HasPassedSteps( pSong, *steps ) ) + return true; + } + return false; +} + +void Profile::IncrementStepsPlayCount( const Song* pSong, const Steps* pSteps ) +{ + DateTime now = DateTime::GetNowDate(); + GetStepsHighScoreList(pSong,pSteps).IncrementPlayCount( now ); +} + +void Profile::GetGrades( const Song* pSong, StepsType st, int iCounts[NUM_Grade] ) const +{ + SongID songID; + songID.FromSong( pSong ); + + memset( iCounts, 0, sizeof(int)*NUM_Grade ); + const HighScoresForASong *hsSong = GetHighScoresForASong( songID ); + if( hsSong == NULL ) + return; + + FOREACH_ENUM( Grade,g) + { + FOREACHM_CONST( StepsID, HighScoresForASteps, hsSong->m_StepsHighScores, it ) + { + const StepsID &id = it->first; + if( !id.MatchesStepsType(st) ) + continue; + + const HighScoresForASteps &hsSteps = it->second; + if( hsSteps.hsl.GetTopScore().GetGrade() == g ) + iCounts[g]++; + } + } +} + +// Course high scores +void Profile::AddCourseHighScore( const Course* pCourse, const Trail* pTrail, HighScore hs, int &iIndexOut ) +{ + GetCourseHighScoreList(pCourse,pTrail).AddHighScore( hs, iIndexOut, IsMachine() ); +} + +const HighScoreList& Profile::GetCourseHighScoreList( const Course* pCourse, const Trail* pTrail ) const +{ + return ((Profile *)this)->GetCourseHighScoreList( pCourse, pTrail ); +} + +HighScoreList& Profile::GetCourseHighScoreList( const Course* pCourse, const Trail* pTrail ) +{ + CourseID courseID; + courseID.FromCourse( pCourse ); + + TrailID trailID; + trailID.FromTrail( pTrail ); + + HighScoresForACourse &hsCourse = m_CourseHighScores[courseID]; // operator[] inserts into map + HighScoresForATrail &hsTrail = hsCourse.m_TrailHighScores[trailID]; // operator[] inserts into map + + return hsTrail.hsl; +} + +int Profile::GetCourseNumTimesPlayed( const Course* pCourse ) const +{ + CourseID courseID; + courseID.FromCourse( pCourse ); + + return GetCourseNumTimesPlayed( courseID ); +} + +int Profile::GetCourseNumTimesPlayed( const CourseID &courseID ) const +{ + const HighScoresForACourse *hsCourse = GetHighScoresForACourse( courseID ); + if( hsCourse == NULL ) + return 0; + + int iTotalNumTimesPlayed = 0; + FOREACHM_CONST( TrailID, HighScoresForATrail, hsCourse->m_TrailHighScores, j ) + { + const HighScoresForATrail &hsTrail = j->second; + + iTotalNumTimesPlayed += hsTrail.hsl.GetNumTimesPlayed(); + } + return iTotalNumTimesPlayed; +} + +DateTime Profile::GetCourseLastPlayedDateTime( const Course* pCourse ) const +{ + CourseID id; + id.FromCourse( pCourse ); + std::map::const_iterator iter = m_CourseHighScores.find( id ); + + // don't call this unless has been played once + ASSERT( iter != m_CourseHighScores.end() ); + ASSERT( !iter->second.m_TrailHighScores.empty() ); + + DateTime dtLatest; // starts out zeroed + FOREACHM_CONST( TrailID, HighScoresForATrail, iter->second.m_TrailHighScores, i ) + { + const HighScoreList &hsl = i->second.hsl; + if( hsl.GetNumTimesPlayed() == 0 ) + continue; + if( dtLatest < hsl.GetLastPlayed() ) + dtLatest = hsl.GetLastPlayed(); + } + return dtLatest; +} + +void Profile::IncrementCoursePlayCount( const Course* pCourse, const Trail* pTrail ) +{ + DateTime now = DateTime::GetNowDate(); + GetCourseHighScoreList(pCourse,pTrail).IncrementPlayCount( now ); +} + +// Category high scores +void Profile::AddCategoryHighScore( StepsType st, RankingCategory rc, HighScore hs, int &iIndexOut ) +{ + m_CategoryHighScores[st][rc].AddHighScore( hs, iIndexOut, IsMachine() ); +} + +const HighScoreList& Profile::GetCategoryHighScoreList( StepsType st, RankingCategory rc ) const +{ + return ((Profile *)this)->m_CategoryHighScores[st][rc]; +} + +HighScoreList& Profile::GetCategoryHighScoreList( StepsType st, RankingCategory rc ) +{ + return m_CategoryHighScores[st][rc]; +} + +int Profile::GetCategoryNumTimesPlayed( StepsType st ) const +{ + int iNumTimesPlayed = 0; + FOREACH_ENUM( RankingCategory,rc ) + iNumTimesPlayed += m_CategoryHighScores[st][rc].GetNumTimesPlayed(); + return iNumTimesPlayed; +} + +void Profile::IncrementCategoryPlayCount( StepsType st, RankingCategory rc ) +{ + DateTime now = DateTime::GetNowDate(); + m_CategoryHighScores[st][rc].IncrementPlayCount( now ); +} + + +// Loading and saving +#define WARN_PARSER ShowWarningOrTrace( __FILE__, __LINE__, "Error parsing file.", true ) +#define WARN_AND_RETURN { WARN_PARSER; return; } +#define WARN_AND_CONTINUE { WARN_PARSER; continue; } +#define WARN_AND_BREAK { WARN_PARSER; break; } +#define WARN_M(m) ShowWarningOrTrace( __FILE__, __LINE__, RString("Error parsing file: ")+(m), true ) +#define WARN_AND_RETURN_M(m) { WARN_M(m); return; } +#define WARN_AND_CONTINUE_M(m) { WARN_M(m); continue; } +#define WARN_AND_BREAK_M(m) { WARN_M(m); break; } +#define LOAD_NODE(X) { \ + const XNode* X = xml->GetChild(#X); \ + if( X==NULL ) LOG->Warn("Failed to read section " #X); \ + else Load##X##FromNode(X); } + +ProfileLoadResult Profile::LoadAllFromDir( RString sDir, bool bRequireSignature ) +{ + CHECKPOINT; + + LOG->Trace( "Profile::LoadAllFromDir( %s )", sDir.c_str() ); + + ASSERT( sDir.Right(1) == "/" ); + + InitAll(); + + // Not critical if this fails + LoadEditableDataFromDir( sDir ); + + // Check for the existance of stats.xml + RString fn = sDir + STATS_XML; + bool bCompressed = false; + if( !IsAFile(fn) ) + { + // Check for the existance of stats.xml.gz + fn = sDir + STATS_XML_GZ; + bCompressed = true; + if( !IsAFile(fn) ) + return ProfileLoadResult_FailedNoProfile; + } + + int iError; + auto_ptr pFile( FILEMAN->Open(fn, RageFile::READ, iError) ); + if( pFile.get() == NULL ) + { + LOG->Trace( "Error opening %s: %s", fn.c_str(), strerror(iError) ); + return ProfileLoadResult_FailedTampered; + } + + if( bCompressed ) + { + RString sError; + uint32_t iCRC32; + RageFileObjInflate *pInflate = GunzipFile( pFile.release(), sError, &iCRC32 ); + if( pInflate == NULL ) + { + LOG->Trace( "Error opening %s: %s", fn.c_str(), sError.c_str() ); + return ProfileLoadResult_FailedTampered; + } + + pFile.reset( pInflate ); + } + + // Don't load unreasonably large stats.xml files. + if( !IsMachine() ) // only check stats coming from the player + { + int iBytes = pFile->GetFileSize(); + if( iBytes > MAX_PLAYER_STATS_XML_SIZE_BYTES ) + { + LOG->Warn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); + return ProfileLoadResult_FailedTampered; + } + } + + if( bRequireSignature ) + { + RString sStatsXmlSigFile = fn+SIGNATURE_APPEND; + RString sDontShareFile = sDir + DONT_SHARE_SIG; + + LOG->Trace( "Verifying don't share signature \"%s\" against \"%s\"", sDontShareFile.c_str(), sStatsXmlSigFile.c_str() ); + // verify the stats.xml signature with the "don't share" file + if( !CryptManager::VerifyFileWithFile(sStatsXmlSigFile, sDontShareFile) ) + { + LOG->Warn( "The don't share check for '%s' failed. Data will be ignored.", sStatsXmlSigFile.c_str() ); + return ProfileLoadResult_FailedTampered; + } + LOG->Trace( "Done." ); + + // verify stats.xml + LOG->Trace( "Verifying stats.xml signature" ); + if( !CryptManager::VerifyFileWithFile(fn, sStatsXmlSigFile) ) + { + LOG->Warn( "The signature check for '%s' failed. Data will be ignored.", fn.c_str() ); + return ProfileLoadResult_FailedTampered; + } + LOG->Trace( "Done." ); + } + + LOG->Trace( "Loading %s", fn.c_str() ); + XNode xml; + if( !XmlFileUtil::LoadFromFileShowErrors(xml, *pFile.get()) ) + return ProfileLoadResult_FailedTampered; + LOG->Trace( "Done." ); + + return LoadStatsXmlFromNode( &xml ); +} + +ProfileLoadResult Profile::LoadStatsXmlFromNode( const XNode *xml, bool bIgnoreEditable ) +{ + /* The placeholder stats.xml file has an tag. Don't load it, + * but don't warn about it. */ + if( xml->GetName() == "html" ) + return ProfileLoadResult_FailedNoProfile; + + if( xml->GetName() != "Stats" ) + { + WARN_M( xml->GetName() ); + return ProfileLoadResult_FailedTampered; + } + + // These are loaded from Editable, so we usually want to ignore them here. + RString sName = m_sDisplayName; + RString sCharacterID = m_sCharacterID; + RString sLastUsedHighScoreName = m_sLastUsedHighScoreName; + int iWeightPounds = m_iWeightPounds; + + LOAD_NODE( GeneralData ); + LOAD_NODE( SongScores ); + LOAD_NODE( CourseScores ); + LOAD_NODE( CategoryScores ); + LOAD_NODE( ScreenshotData ); + LOAD_NODE( CalorieData ); + + if( bIgnoreEditable ) + { + m_sDisplayName = sName; + m_sCharacterID = sCharacterID; + m_sLastUsedHighScoreName = sLastUsedHighScoreName; + m_iWeightPounds = iWeightPounds; + } + + return ProfileLoadResult_Success; +} + +bool Profile::SaveAllToDir( RString sDir, bool bSignData ) const +{ + m_sLastPlayedMachineGuid = PROFILEMAN->GetMachineProfile()->m_sGuid; + m_LastPlayedDate = DateTime::GetNowDate(); + + // Save editable.ini + SaveEditableDataToDir( sDir ); + + bool bSaved = SaveStatsXmlToDir( sDir, bSignData ); + + SaveStatsWebPageToDir( sDir ); + + // Empty directories if none exist. + if( ProfileManager::m_bProfileStepEdits ) + FILEMAN->CreateDir( sDir + EDIT_STEPS_SUBDIR ); + if( ProfileManager::m_bProfileCourseEdits ) + FILEMAN->CreateDir( sDir + EDIT_COURSES_SUBDIR ); + FILEMAN->CreateDir( sDir + SCREENSHOTS_SUBDIR ); + FILEMAN->CreateDir( sDir + RIVAL_SUBDIR ); + + return bSaved; +} + +XNode *Profile::SaveStatsXmlCreateNode() const +{ + XNode *xml = new XNode( "Stats" ); + + xml->AppendChild( SaveGeneralDataCreateNode() ); + xml->AppendChild( SaveSongScoresCreateNode() ); + xml->AppendChild( SaveCourseScoresCreateNode() ); + xml->AppendChild( SaveCategoryScoresCreateNode() ); + xml->AppendChild( SaveScreenshotDataCreateNode() ); + xml->AppendChild( SaveCalorieDataCreateNode() ); + if( SHOW_COIN_DATA.GetValue() && IsMachine() ) + xml->AppendChild( SaveCoinDataCreateNode() ); + + return xml; +} + +bool Profile::SaveStatsXmlToDir( RString sDir, bool bSignData ) const +{ + LOG->Trace( "SaveStatsXmlToDir: %s", sDir.c_str() ); + auto_ptr xml( SaveStatsXmlCreateNode() ); + + // Save stats.xml + RString fn = sDir + (g_bProfileDataCompress? STATS_XML_GZ:STATS_XML); + + { + RString sError; + RageFile f; + if( !f.Open(fn, RageFile::WRITE) ) + { + LOG->Warn( "Couldn't open %s for writing: %s", fn.c_str(), f.GetError().c_str() ); + return false; + } + + if( g_bProfileDataCompress ) + { + RageFileObjGzip gzip( &f ); + gzip.Start(); + if( !XmlFileUtil::SaveToFile( xml.get(), gzip, "", false ) ) + return false; + + if( gzip.Finish() == -1 ) + return false; + + /* After successfully saving STATS_XML_GZ, remove any stray STATS_XML. */ + if( FILEMAN->IsAFile(sDir + STATS_XML) ) + FILEMAN->Remove( sDir + STATS_XML ); + } + else + { + if( !XmlFileUtil::SaveToFile( xml.get(), f, "", false ) ) + return false; + + /* After successfully saving STATS_XML, remove any stray STATS_XML_GZ. */ + if( FILEMAN->IsAFile(sDir + STATS_XML_GZ) ) + FILEMAN->Remove( sDir + STATS_XML_GZ ); + } + } + + if( bSignData ) + { + RString sStatsXmlSigFile = fn+SIGNATURE_APPEND; + CryptManager::SignFileToFile(fn, sStatsXmlSigFile); + + // Save the "don't share" file + RString sDontShareFile = sDir + DONT_SHARE_SIG; + CryptManager::SignFileToFile(sStatsXmlSigFile, sDontShareFile); + } + + return true; +} + +void Profile::SaveEditableDataToDir( RString sDir ) const +{ + IniFile ini; + + ini.SetValue( "Editable", "DisplayName", m_sDisplayName ); + ini.SetValue( "Editable", "CharacterID", m_sCharacterID ); + ini.SetValue( "Editable", "LastUsedHighScoreName", m_sLastUsedHighScoreName ); + ini.SetValue( "Editable", "WeightPounds", m_iWeightPounds ); + + ini.WriteFile( sDir + EDITABLE_INI ); +} + +XNode* Profile::SaveGeneralDataCreateNode() const +{ + XNode* pGeneralDataNode = new XNode( "GeneralData" ); + + // TRICKY: These are write-only elements that are normally never read again. + // This data is required by other apps (like internet ranking), but is + // redundant to the game app. + pGeneralDataNode->AppendChild( "DisplayName", GetDisplayNameOrHighScoreName() ); + pGeneralDataNode->AppendChild( "CharacterID", m_sCharacterID ); + pGeneralDataNode->AppendChild( "LastUsedHighScoreName", m_sLastUsedHighScoreName ); + pGeneralDataNode->AppendChild( "WeightPounds", m_iWeightPounds ); + pGeneralDataNode->AppendChild( "IsMachine", IsMachine() ); + pGeneralDataNode->AppendChild( "IsWeightSet", m_iWeightPounds != 0 ); + + pGeneralDataNode->AppendChild( "Guid", m_sGuid ); + pGeneralDataNode->AppendChild( "SortOrder", SortOrderToString(m_SortOrder) ); + pGeneralDataNode->AppendChild( "LastDifficulty", DifficultyToString(m_LastDifficulty) ); + pGeneralDataNode->AppendChild( "LastCourseDifficulty", DifficultyToString(m_LastCourseDifficulty) ); + if( m_LastStepsType != StepsType_Invalid ) + pGeneralDataNode->AppendChild( "LastStepsType", GAMEMAN->GetStepsTypeInfo(m_LastStepsType).szName ); + pGeneralDataNode->AppendChild( m_lastSong.CreateNode() ); + pGeneralDataNode->AppendChild( m_lastCourse.CreateNode() ); + pGeneralDataNode->AppendChild( "TotalSessions", m_iTotalSessions ); + pGeneralDataNode->AppendChild( "TotalSessionSeconds", m_iTotalSessionSeconds ); + pGeneralDataNode->AppendChild( "TotalGameplaySeconds", m_iTotalGameplaySeconds ); + pGeneralDataNode->AppendChild( "TotalCaloriesBurned", m_fTotalCaloriesBurned ); + pGeneralDataNode->AppendChild( "GoalType", m_GoalType ); + pGeneralDataNode->AppendChild( "GoalCalories", m_iGoalCalories ); + pGeneralDataNode->AppendChild( "GoalSeconds", m_iGoalSeconds ); + pGeneralDataNode->AppendChild( "LastPlayedMachineGuid", m_sLastPlayedMachineGuid ); + pGeneralDataNode->AppendChild( "LastPlayedDate", m_LastPlayedDate.GetString() ); + pGeneralDataNode->AppendChild( "TotalDancePoints", m_iTotalDancePoints ); + pGeneralDataNode->AppendChild( "NumExtraStagesPassed", m_iNumExtraStagesPassed ); + pGeneralDataNode->AppendChild( "NumExtraStagesFailed", m_iNumExtraStagesFailed ); + pGeneralDataNode->AppendChild( "NumToasties", m_iNumToasties ); + pGeneralDataNode->AppendChild( "TotalTapsAndHolds", m_iTotalTapsAndHolds ); + pGeneralDataNode->AppendChild( "TotalJumps", m_iTotalJumps ); + pGeneralDataNode->AppendChild( "TotalHolds", m_iTotalHolds ); + pGeneralDataNode->AppendChild( "TotalRolls", m_iTotalRolls ); + pGeneralDataNode->AppendChild( "TotalMines", m_iTotalMines ); + pGeneralDataNode->AppendChild( "TotalHands", m_iTotalHands ); + pGeneralDataNode->AppendChild( "TotalLifts", m_iTotalLifts ); + + // Keep declared variables in a very local scope so they aren't + // accidentally used where they're not intended. There's a lot of + // copying and pasting in this code. + + { + XNode* pDefaultModifiers = pGeneralDataNode->AppendChild("DefaultModifiers"); + FOREACHM_CONST( RString, RString, m_sDefaultModifiers, it ) + pDefaultModifiers->AppendChild( it->first, it->second ); + } + + { + XNode* pUnlocks = pGeneralDataNode->AppendChild("Unlocks"); + FOREACHS_CONST( RString, m_UnlockedEntryIDs, it ) + { + XNode *pEntry = pUnlocks->AppendChild("UnlockEntry"); + RString sUnlockEntry = it->c_str(); + pEntry->AppendAttr( "UnlockEntryID", sUnlockEntry ); + if( !UNLOCK_AUTH_STRING.GetValue().empty() ) + { + RString sUnlockAuth = BinaryToHex( CRYPTMAN->GetMD5ForString(sUnlockEntry + UNLOCK_AUTH_STRING.GetValue()) ); + pEntry->AppendAttr( "Auth", sUnlockAuth ); + } + } + } + + { + XNode* pNumSongsPlayedByPlayMode = pGeneralDataNode->AppendChild("NumSongsPlayedByPlayMode"); + FOREACH_ENUM( PlayMode, pm ) + { + // Don't save unplayed PlayModes. + if( !m_iNumSongsPlayedByPlayMode[pm] ) + continue; + pNumSongsPlayedByPlayMode->AppendChild( PlayModeToString(pm), m_iNumSongsPlayedByPlayMode[pm] ); + } + } + + { + XNode* pNumSongsPlayedByStyle = pGeneralDataNode->AppendChild("NumSongsPlayedByStyle"); + FOREACHM_CONST( StyleID, int, m_iNumSongsPlayedByStyle, iter ) + { + const StyleID &s = iter->first; + int iNumPlays = iter->second; + + XNode *pStyleNode = s.CreateNode(); + pStyleNode->AppendAttr(XNode::TEXT_ATTRIBUTE, iNumPlays ); + + pNumSongsPlayedByStyle->AppendChild( pStyleNode ); + } + } + + { + XNode* pNumSongsPlayedByDifficulty = pGeneralDataNode->AppendChild("NumSongsPlayedByDifficulty"); + FOREACH_ENUM( Difficulty, dc ) + { + if( !m_iNumSongsPlayedByDifficulty[dc] ) + continue; + pNumSongsPlayedByDifficulty->AppendChild( DifficultyToString(dc), m_iNumSongsPlayedByDifficulty[dc] ); + } + } + + { + XNode* pNumSongsPlayedByMeter = pGeneralDataNode->AppendChild("NumSongsPlayedByMeter"); + for( int i=0; iAppendChild( ssprintf("Meter%d",i), m_iNumSongsPlayedByMeter[i] ); + } + } + + pGeneralDataNode->AppendChild( "NumTotalSongsPlayed", m_iNumTotalSongsPlayed ); + + { + XNode* pNumStagesPassedByPlayMode = pGeneralDataNode->AppendChild("NumStagesPassedByPlayMode"); + FOREACH_ENUM( PlayMode, pm ) + { + // Don't save unplayed PlayModes. + if( !m_iNumStagesPassedByPlayMode[pm] ) + continue; + pNumStagesPassedByPlayMode->AppendChild( PlayModeToString(pm), m_iNumStagesPassedByPlayMode[pm] ); + } + } + + { + XNode* pNumStagesPassedByGrade = pGeneralDataNode->AppendChild("NumStagesPassedByGrade"); + FOREACH_ENUM( Grade, g ) + { + if( !m_iNumStagesPassedByGrade[g] ) + continue; + pNumStagesPassedByGrade->AppendChild( GradeToString(g), m_iNumStagesPassedByGrade[g] ); + } + } + + // Load Lua UserTable from profile + if( !IsMachine() && m_UserTable.IsSet() ) + { + Lua *L = LUA->Get(); + m_UserTable.PushSelf( L ); + XNode* pUserTable = XmlFileUtil::XNodeFromTable( L ); + LUA->Release( L ); + + // XXX: XNodeFromTable returns a root node with the name "Layer". + pUserTable->m_sName = "UserTable"; + pGeneralDataNode->AppendChild( pUserTable ); + } + + return pGeneralDataNode; +} + +ProfileLoadResult Profile::LoadEditableDataFromDir( RString sDir ) +{ + RString fn = sDir + EDITABLE_INI; + + // Don't load unreasonably large editable.xml files. + int iBytes = FILEMAN->GetFileSizeInBytes( fn ); + if( iBytes > MAX_EDITABLE_INI_SIZE_BYTES ) + { + LOG->Warn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); + return ProfileLoadResult_FailedTampered; + } + + if( !IsAFile(fn) ) + return ProfileLoadResult_FailedNoProfile; + + IniFile ini; + ini.ReadFile( fn ); + + ini.GetValue( "Editable", "DisplayName", m_sDisplayName ); + ini.GetValue( "Editable", "CharacterID", m_sCharacterID ); + ini.GetValue( "Editable", "LastUsedHighScoreName", m_sLastUsedHighScoreName ); + ini.GetValue( "Editable", "WeightPounds", m_iWeightPounds ); + + // This is data that the user can change, so we have to validate it. + wstring wstr = RStringToWstring(m_sDisplayName); + if( wstr.size() > PROFILE_MAX_DISPLAY_NAME_LENGTH ) + wstr = wstr.substr(0, PROFILE_MAX_DISPLAY_NAME_LENGTH); + m_sDisplayName = WStringToRString(wstr); + // TODO: strip invalid chars? + if( m_iWeightPounds != 0 ) + CLAMP( m_iWeightPounds, 20, 1000 ); + + return ProfileLoadResult_Success; +} + +void Profile::LoadGeneralDataFromNode( const XNode* pNode ) +{ + ASSERT( pNode->GetName() == "GeneralData" ); + + RString s; + const XNode* pTemp; + + pNode->GetChildValue( "DisplayName", m_sDisplayName ); + pNode->GetChildValue( "CharacterID", m_sCharacterID ); + pNode->GetChildValue( "LastUsedHighScoreName", m_sLastUsedHighScoreName ); + pNode->GetChildValue( "WeightPounds", m_iWeightPounds ); + pNode->GetChildValue( "Guid", m_sGuid ); + pNode->GetChildValue( "SortOrder", s ); m_SortOrder = StringToSortOrder( s ); + pNode->GetChildValue( "LastDifficulty", s ); m_LastDifficulty = StringToDifficulty( s ); + pNode->GetChildValue( "LastCourseDifficulty", s ); m_LastCourseDifficulty = StringToDifficulty( s ); + pNode->GetChildValue( "LastStepsType", s ); m_LastStepsType = GAMEMAN->StringToStepsType( s ); + pTemp = pNode->GetChild( "Song" ); if( pTemp ) m_lastSong.LoadFromNode( pTemp ); + pTemp = pNode->GetChild( "Course" ); if( pTemp ) m_lastCourse.LoadFromNode( pTemp ); + pNode->GetChildValue( "TotalSessions", m_iTotalSessions ); + pNode->GetChildValue( "TotalSessionSeconds", m_iTotalSessionSeconds ); + pNode->GetChildValue( "TotalGameplaySeconds", m_iTotalGameplaySeconds ); + pNode->GetChildValue( "TotalCaloriesBurned", m_fTotalCaloriesBurned ); + pNode->GetChildValue( "GoalType", *ConvertValue(&m_GoalType) ); + pNode->GetChildValue( "GoalCalories", m_iGoalCalories ); + pNode->GetChildValue( "GoalSeconds", m_iGoalSeconds ); + pNode->GetChildValue( "LastPlayedMachineGuid", m_sLastPlayedMachineGuid ); + pNode->GetChildValue( "LastPlayedDate", s ); m_LastPlayedDate.FromString( s ); + pNode->GetChildValue( "TotalDancePoints", m_iTotalDancePoints ); + pNode->GetChildValue( "NumExtraStagesPassed", m_iNumExtraStagesPassed ); + pNode->GetChildValue( "NumExtraStagesFailed", m_iNumExtraStagesFailed ); + pNode->GetChildValue( "NumToasties", m_iNumToasties ); + pNode->GetChildValue( "TotalTapsAndHolds", m_iTotalTapsAndHolds ); + pNode->GetChildValue( "TotalJumps", m_iTotalJumps ); + pNode->GetChildValue( "TotalHolds", m_iTotalHolds ); + pNode->GetChildValue( "TotalRolls", m_iTotalRolls ); + pNode->GetChildValue( "TotalMines", m_iTotalMines ); + pNode->GetChildValue( "TotalHands", m_iTotalHands ); + pNode->GetChildValue( "TotalLifts", m_iTotalLifts ); + + { + const XNode* pDefaultModifiers = pNode->GetChild("DefaultModifiers"); + if( pDefaultModifiers ) + { + FOREACH_CONST_Child( pDefaultModifiers, game_type ) + { + game_type->GetTextValue( m_sDefaultModifiers[game_type->GetName()] ); + } + } + } + + { + const XNode* pUnlocks = pNode->GetChild("Unlocks"); + if( pUnlocks ) + { + FOREACH_CONST_Child( pUnlocks, unlock ) + { + RString sUnlockEntryID; + if( !unlock->GetAttrValue("UnlockEntryID",sUnlockEntryID) ) + continue; + + if( !UNLOCK_AUTH_STRING.GetValue().empty() ) + { + RString sUnlockAuth; + if( !unlock->GetAttrValue("Auth", sUnlockAuth) ) + continue; + + RString sExpectedUnlockAuth = BinaryToHex( CRYPTMAN->GetMD5ForString(sUnlockEntryID + UNLOCK_AUTH_STRING.GetValue()) ); + if( sUnlockAuth != sExpectedUnlockAuth ) + continue; + } + + m_UnlockedEntryIDs.insert( sUnlockEntryID ); + } + } + } + + { + const XNode* pNumSongsPlayedByPlayMode = pNode->GetChild("NumSongsPlayedByPlayMode"); + if( pNumSongsPlayedByPlayMode ) + FOREACH_ENUM( PlayMode, pm ) + pNumSongsPlayedByPlayMode->GetChildValue( PlayModeToString(pm), m_iNumSongsPlayedByPlayMode[pm] ); + } + + { + const XNode* pNumSongsPlayedByStyle = pNode->GetChild("NumSongsPlayedByStyle"); + if( pNumSongsPlayedByStyle ) + { + FOREACH_CONST_Child( pNumSongsPlayedByStyle, style ) + { + if( style->GetName() != "Style" ) + continue; + + StyleID sID; + sID.LoadFromNode( style ); + + if( !sID.IsValid() ) + WARN_AND_CONTINUE; + + style->GetTextValue( m_iNumSongsPlayedByStyle[sID] ); + } + } + } + + { + const XNode* pNumSongsPlayedByDifficulty = pNode->GetChild("NumSongsPlayedByDifficulty"); + if( pNumSongsPlayedByDifficulty ) + FOREACH_ENUM( Difficulty, dc ) + pNumSongsPlayedByDifficulty->GetChildValue( DifficultyToString(dc), m_iNumSongsPlayedByDifficulty[dc] ); + } + + { + const XNode* pNumSongsPlayedByMeter = pNode->GetChild("NumSongsPlayedByMeter"); + if( pNumSongsPlayedByMeter ) + for( int i=0; iGetChildValue( ssprintf("Meter%d",i), m_iNumSongsPlayedByMeter[i] ); + } + + pNode->GetChildValue("NumTotalSongsPlayed", m_iNumTotalSongsPlayed ); + + { + const XNode* pNumStagesPassedByGrade = pNode->GetChild("NumStagesPassedByGrade"); + if( pNumStagesPassedByGrade ) + FOREACH_ENUM( Grade, g ) + pNumStagesPassedByGrade->GetChildValue( GradeToString(g), m_iNumStagesPassedByGrade[g] ); + } + + { + const XNode* pNumStagesPassedByPlayMode = pNode->GetChild("NumStagesPassedByPlayMode"); + if( pNumStagesPassedByPlayMode ) + FOREACH_ENUM( PlayMode, pm ) + pNumStagesPassedByPlayMode->GetChildValue( PlayModeToString(pm), m_iNumStagesPassedByPlayMode[pm] ); + + } + + // Build the custom data table from the existing XNode. + if( !IsMachine() ) + { + const XNode *pUserTable = pNode->GetChild( "UserTable" ); + + Lua *L = LUA->Get(); + + // If we have custom data, load it. Otherwise, make a blank table. + if( pUserTable ) + LuaHelpers::CreateTableFromXNode( L, pUserTable ); + else + lua_newtable( L ); + + m_UserTable.SetFromStack( L ); + LUA->Release( L ); + } + +} + +void Profile::AddStepTotals( int iTotalTapsAndHolds, int iTotalJumps, int iTotalHolds, int iTotalRolls, int iTotalMines, int iTotalHands, int iTotalLifts, float fCaloriesBurned ) +{ + m_iTotalTapsAndHolds += iTotalTapsAndHolds; + m_iTotalJumps += iTotalJumps; + m_iTotalHolds += iTotalHolds; + m_iTotalRolls += iTotalRolls; + m_iTotalMines += iTotalMines; + m_iTotalHands += iTotalHands; + m_iTotalLifts += iTotalLifts; + + m_fTotalCaloriesBurned += fCaloriesBurned; + + DateTime date = DateTime::GetNowDate(); + m_mapDayToCaloriesBurned[date].fCals += fCaloriesBurned; +} + +XNode* Profile::SaveSongScoresCreateNode() const +{ + CHECKPOINT; + + const Profile* pProfile = this; + ASSERT( pProfile ); + + XNode* pNode = new XNode( "SongScores" ); + + FOREACHM_CONST( SongID, HighScoresForASong, m_SongHighScores, i ) + { + const SongID &songID = i->first; + const HighScoresForASong &hsSong = i->second; + + // skip songs that have never been played + if( pProfile->GetSongNumTimesPlayed(songID) == 0 ) + continue; + + XNode* pSongNode = pNode->AppendChild( songID.CreateNode() ); + + int jCheck2 = hsSong.m_StepsHighScores.size(); + int jCheck1 = 0; + FOREACHM_CONST( StepsID, HighScoresForASteps, hsSong.m_StepsHighScores, j ) + { + jCheck1++; + ASSERT( jCheck1 <= jCheck2 ); + const StepsID &stepsID = j->first; + const HighScoresForASteps &hsSteps = j->second; + + const HighScoreList &hsl = hsSteps.hsl; + + // skip steps that have never been played + if( hsl.GetNumTimesPlayed() == 0 ) + continue; + + XNode* pStepsNode = pSongNode->AppendChild( stepsID.CreateNode() ); + + pStepsNode->AppendChild( hsl.CreateNode() ); + } + } + + return pNode; +} + +void Profile::LoadSongScoresFromNode( const XNode* pSongScores ) +{ + CHECKPOINT; + + ASSERT( pSongScores->GetName() == "SongScores" ); + + FOREACH_CONST_Child( pSongScores, pSong ) + { + if( pSong->GetName() != "Song" ) + continue; + + SongID songID; + songID.LoadFromNode( pSong ); + if( !songID.IsValid() ) + continue; + + FOREACH_CONST_Child( pSong, pSteps ) + { + if( pSteps->GetName() != "Steps" ) + continue; + + StepsID stepsID; + stepsID.LoadFromNode( pSteps ); + if( !stepsID.IsValid() ) + WARN_AND_CONTINUE; + + const XNode *pHighScoreListNode = pSteps->GetChild("HighScoreList"); + if( pHighScoreListNode == NULL ) + WARN_AND_CONTINUE; + + HighScoreList &hsl = m_SongHighScores[songID].m_StepsHighScores[stepsID].hsl; + hsl.LoadFromNode( pHighScoreListNode ); + } + } +} + + +XNode* Profile::SaveCourseScoresCreateNode() const +{ + CHECKPOINT; + + const Profile* pProfile = this; + ASSERT( pProfile ); + + XNode* pNode = new XNode( "CourseScores" ); + + FOREACHM_CONST( CourseID, HighScoresForACourse, m_CourseHighScores, i ) + { + const CourseID &courseID = i->first; + const HighScoresForACourse &hsCourse = i->second; + + // skip courses that have never been played + if( pProfile->GetCourseNumTimesPlayed(courseID) == 0 ) + continue; + + XNode* pCourseNode = pNode->AppendChild( courseID.CreateNode() ); + + FOREACHM_CONST( TrailID, HighScoresForATrail, hsCourse.m_TrailHighScores, j ) + { + const TrailID &trailID = j->first; + const HighScoresForATrail &hsTrail = j->second; + + const HighScoreList &hsl = hsTrail.hsl; + + // skip steps that have never been played + if( hsl.GetNumTimesPlayed() == 0 ) + continue; + + XNode* pTrailNode = pCourseNode->AppendChild( trailID.CreateNode() ); + + pTrailNode->AppendChild( hsl.CreateNode() ); + } + } + + return pNode; +} + +void Profile::LoadCourseScoresFromNode( const XNode* pCourseScores ) +{ + CHECKPOINT; + + ASSERT( pCourseScores->GetName() == "CourseScores" ); + + vector vpAllCourses; + SONGMAN->GetAllCourses( vpAllCourses, true ); + + FOREACH_CONST_Child( pCourseScores, pCourse ) + { + if( pCourse->GetName() != "Course" ) + continue; + + CourseID courseID; + courseID.LoadFromNode( pCourse ); + if( !courseID.IsValid() ) + WARN_AND_CONTINUE; + + + // Backward compatability hack to fix importing scores of old style + // courses that weren't in group folder but have now been moved into + // a group folder: + // If the courseID doesn't resolve, then take the file name part of sPath + // and search for matches of just the file name. + { + Course *pC = courseID.ToCourse(); + if( pC == NULL ) + { + RString sDir, sFName, sExt; + splitpath( courseID.GetPath(), sDir, sFName, sExt ); + RString sFullFileName = sFName + sExt; + + FOREACH_CONST( Course*, vpAllCourses, c ) + { + RString sOther = (*c)->m_sPath.Right(sFullFileName.size()); + + if( sFullFileName.CompareNoCase(sOther) == 0 ) + { + pC = *c; + courseID.FromCourse( pC ); + break; + } + } + } + } + + + FOREACH_CONST_Child( pCourse, pTrail ) + { + if( pTrail->GetName() != "Trail" ) + continue; + + TrailID trailID; + trailID.LoadFromNode( pTrail ); + if( !trailID.IsValid() ) + WARN_AND_CONTINUE; + + const XNode *pHighScoreListNode = pTrail->GetChild("HighScoreList"); + if( pHighScoreListNode == NULL ) + WARN_AND_CONTINUE; + + HighScoreList &hsl = m_CourseHighScores[courseID].m_TrailHighScores[trailID].hsl; + hsl.LoadFromNode( pHighScoreListNode ); + } + } +} + +XNode* Profile::SaveCategoryScoresCreateNode() const +{ + CHECKPOINT; + + const Profile* pProfile = this; + ASSERT( pProfile ); + + XNode* pNode = new XNode( "CategoryScores" ); + + FOREACH_ENUM( StepsType,st ) + { + // skip steps types that have never been played + if( pProfile->GetCategoryNumTimesPlayed( st ) == 0 ) + continue; + + XNode* pStepsTypeNode = pNode->AppendChild( "StepsType" ); + pStepsTypeNode->AppendAttr( "Type", GAMEMAN->GetStepsTypeInfo(st).szName ); + + FOREACH_ENUM( RankingCategory,rc ) + { + // skip steps types/categories that have never been played + if( pProfile->GetCategoryHighScoreList(st,rc).GetNumTimesPlayed() == 0 ) + continue; + + XNode* pRankingCategoryNode = pStepsTypeNode->AppendChild( "RankingCategory" ); + pRankingCategoryNode->AppendAttr( "Type", RankingCategoryToString(rc) ); + + const HighScoreList &hsl = pProfile->GetCategoryHighScoreList( (StepsType)st, (RankingCategory)rc ); + + pRankingCategoryNode->AppendChild( hsl.CreateNode() ); + } + } + + return pNode; +} + +void Profile::LoadCategoryScoresFromNode( const XNode* pCategoryScores ) +{ + CHECKPOINT; + + ASSERT( pCategoryScores->GetName() == "CategoryScores" ); + + FOREACH_CONST_Child( pCategoryScores, pStepsType ) + { + if( pStepsType->GetName() != "StepsType" ) + continue; + + RString str; + if( !pStepsType->GetAttrValue( "Type", str ) ) + WARN_AND_CONTINUE; + StepsType st = GAMEMAN->StringToStepsType( str ); + if( st == StepsType_Invalid ) + WARN_AND_CONTINUE_M( str ); + + FOREACH_CONST_Child( pStepsType, pRadarCategory ) + { + if( pRadarCategory->GetName() != "RankingCategory" ) + continue; + + if( !pRadarCategory->GetAttrValue( "Type", str ) ) + WARN_AND_CONTINUE; + RankingCategory rc = StringToRankingCategory( str ); + if( rc == RankingCategory_Invalid ) + WARN_AND_CONTINUE_M( str ); + + const XNode *pHighScoreListNode = pRadarCategory->GetChild("HighScoreList"); + if( pHighScoreListNode == NULL ) + WARN_AND_CONTINUE; + + HighScoreList &hsl = this->GetCategoryHighScoreList( st, rc ); + hsl.LoadFromNode( pHighScoreListNode ); + } + } +} + +void Profile::SaveStatsWebPageToDir( RString sDir ) const +{ + ASSERT( PROFILEMAN ); +} + +void Profile::SaveMachinePublicKeyToDir( RString sDir ) const +{ + if( PREFSMAN->m_bSignProfileData && IsAFile(CRYPTMAN->GetPublicKeyFileName()) ) + FileCopy( CRYPTMAN->GetPublicKeyFileName(), sDir+PUBLIC_KEY_FILE ); +} + +void Profile::AddScreenshot( const Screenshot &screenshot ) +{ + m_vScreenshots.push_back( screenshot ); +} + +void Profile::LoadScreenshotDataFromNode( const XNode* pScreenshotData ) +{ + CHECKPOINT; + + ASSERT( pScreenshotData->GetName() == "ScreenshotData" ); + FOREACH_CONST_Child( pScreenshotData, pScreenshot ) + { + if( pScreenshot->GetName() != "Screenshot" ) + WARN_AND_CONTINUE_M( pScreenshot->GetName() ); + + Screenshot ss; + ss.LoadFromNode( pScreenshot ); + + m_vScreenshots.push_back( ss ); + } +} + +XNode* Profile::SaveScreenshotDataCreateNode() const +{ + CHECKPOINT; + + const Profile* pProfile = this; + ASSERT( pProfile ); + + XNode* pNode = new XNode( "ScreenshotData" ); + + FOREACH_CONST( Screenshot, m_vScreenshots, ss ) + { + pNode->AppendChild( ss->CreateNode() ); + } + + return pNode; +} + +void Profile::LoadCalorieDataFromNode( const XNode* pCalorieData ) +{ + CHECKPOINT; + + ASSERT( pCalorieData->GetName() == "CalorieData" ); + FOREACH_CONST_Child( pCalorieData, pCaloriesBurned ) + { + if( pCaloriesBurned->GetName() != "CaloriesBurned" ) + WARN_AND_CONTINUE_M( pCaloriesBurned->GetName() ); + + RString sDate; + if( !pCaloriesBurned->GetAttrValue("Date",sDate) ) + WARN_AND_CONTINUE; + DateTime date; + if( !date.FromString(sDate) ) + WARN_AND_CONTINUE_M( sDate ); + + float fCaloriesBurned = 0; + + pCaloriesBurned->GetTextValue(fCaloriesBurned); + + m_mapDayToCaloriesBurned[date].fCals = fCaloriesBurned; + } +} + +XNode* Profile::SaveCalorieDataCreateNode() const +{ + CHECKPOINT; + + const Profile* pProfile = this; + ASSERT( pProfile ); + + XNode* pNode = new XNode( "CalorieData" ); + + FOREACHM_CONST( DateTime, Calories, m_mapDayToCaloriesBurned, i ) + { + XNode* pCaloriesBurned = pNode->AppendChild( "CaloriesBurned", i->second.fCals ); + + pCaloriesBurned->AppendAttr( "Date", i->first.GetString() ); + } + + return pNode; +} + +float Profile::GetCaloriesBurnedForDay( DateTime day ) const +{ + day.StripTime(); + map::const_iterator i = m_mapDayToCaloriesBurned.find( day ); + if( i == m_mapDayToCaloriesBurned.end() ) + return 0; + else + return i->second.fCals; +} + +/* +static void SaveRecentScore( XNode* xml ) +{ + RString sDate = DateTime::GetNowDate().GetString(); + sDate.Replace(":","-"); + + RString sFileNameNoExtension = Profile::MakeUniqueFileNameNoExtension(UPLOAD_SUBDIR, sDate ); + RString fn = UPLOAD_SUBDIR + sFileNameNoExtension + ".xml"; + + RString sStatsXmlSigFile = fn+SIGNATURE_APPEND; + CryptManager::SignFileToFile(fn, sStatsXmlSigFile); +} + +XNode* Profile::HighScoreForASongAndSteps::CreateNode() const +{ + XNode* pNode = new XNode( "HighScoreForASongAndSteps" ); + + pNode->AppendChild( songID.CreateNode() ); + pNode->AppendChild( stepsID.CreateNode() ); + pNode->AppendChild( hs.CreateNode() ); + + return pNode; +} + +void Profile::SaveStepsRecentScore( const Song* pSong, const Steps* pSteps, HighScore hs ) +{ + ASSERT( pSong ); + ASSERT( pSteps ); + HighScoreForASongAndSteps h; + h.songID.FromSong( pSong ); + ASSERT( h.songID.IsValid() ); + h.stepsID.FromSteps( pSteps ); + ASSERT( h.stepsID.IsValid() ); + h.hs = hs; + + auto_ptr xml( new XNode("Stats") ); + xml->AppendChild( "MachineGuid", PROFILEMAN->GetMachineProfile()->m_sGuid ); + XNode *recent = xml->AppendChild( new XNode("RecentSongScores") ); + recent->AppendChild( h.CreateNode() ); + + SaveRecentScore( xml.get() ); +} + + +XNode* Profile::HighScoreForACourseAndTrail::CreateNode() const +{ + XNode* pNode = new XNode( "HighScoreForACourseAndTrail" ); + + pNode->AppendChild( courseID.CreateNode() ); + pNode->AppendChild( trailID.CreateNode() ); + pNode->AppendChild( hs.CreateNode() ); + + return pNode; +} + +void Profile::SaveCourseRecentScore( const Course* pCourse, const Trail* pTrail, HighScore hs ) +{ + HighScoreForACourseAndTrail h; + h.courseID.FromCourse( pCourse ); + h.trailID.FromTrail( pTrail ); + h.hs = hs; + + auto_ptr xml( new XNode("Stats") ); + xml->AppendChild( "MachineGuid", PROFILEMAN->GetMachineProfile()->m_sGuid ); + XNode *recent = xml->AppendChild( new XNode("RecentCourseScores") ); + recent->AppendChild( h.CreateNode() ); + SaveRecentScore( xml.get() ); +} +*/ +const Profile::HighScoresForASong *Profile::GetHighScoresForASong( const SongID& songID ) const +{ + map::const_iterator it; + it = m_SongHighScores.find( songID ); + if( it == m_SongHighScores.end() ) + return NULL; + return &it->second; +} + +const Profile::HighScoresForACourse *Profile::GetHighScoresForACourse( const CourseID& courseID ) const +{ + map::const_iterator it; + it = m_CourseHighScores.find( courseID ); + if( it == m_CourseHighScores.end() ) + return NULL; + return &it->second; +} + +bool Profile::IsMachine() const +{ + // TODO: Think of a better way to handle this + return this == PROFILEMAN->GetMachineProfile(); +} + + +XNode* Profile::SaveCoinDataCreateNode() const +{ + CHECKPOINT; + + const Profile* pProfile = this; + ASSERT( pProfile ); + + XNode* pNode = new XNode( "CoinData" ); + + { + int coins[NUM_LAST_DAYS]; + BOOKKEEPER->GetCoinsLastDays( coins ); + XNode* p = pNode->AppendChild( "LastDays" ); + for( int i=0; iAppendChild( LastDayToString(i), coins[i] ); + } + { + int coins[NUM_LAST_WEEKS]; + BOOKKEEPER->GetCoinsLastWeeks( coins ); + XNode* p = pNode->AppendChild( "LastWeeks" ); + for( int i=0; iAppendChild( LastWeekToString(i), coins[i] ); + } + { + int coins[DAYS_IN_WEEK]; + BOOKKEEPER->GetCoinsByDayOfWeek( coins ); + XNode* p = pNode->AppendChild( "DayOfWeek" ); + for( int i=0; iAppendChild( DayOfWeekToString(i), coins[i] ); + } + { + int coins[HOURS_IN_DAY]; + BOOKKEEPER->GetCoinsByHour( coins ); + XNode* p = pNode->AppendChild( "Hour" ); + for( int i=0; iAppendChild( HourInDayToString(i), coins[i] ); + } + + return pNode; +} + +void Profile::MoveBackupToDir( RString sFromDir, RString sToDir ) +{ + if( FILEMAN->IsAFile(sFromDir + STATS_XML) && + FILEMAN->IsAFile(sFromDir+STATS_XML+SIGNATURE_APPEND) ) + { + FILEMAN->Move( sFromDir+STATS_XML, sToDir+STATS_XML ); + FILEMAN->Move( sFromDir+STATS_XML+SIGNATURE_APPEND, sToDir+STATS_XML+SIGNATURE_APPEND ); + } + else if( FILEMAN->IsAFile(sFromDir + STATS_XML_GZ) && + FILEMAN->IsAFile(sFromDir+STATS_XML_GZ+SIGNATURE_APPEND) ) + { + FILEMAN->Move( sFromDir+STATS_XML_GZ, sToDir+STATS_XML ); + FILEMAN->Move( sFromDir+STATS_XML_GZ+SIGNATURE_APPEND, sToDir+STATS_XML+SIGNATURE_APPEND ); + } + + if( FILEMAN->IsAFile(sFromDir + EDITABLE_INI) ) + FILEMAN->Move( sFromDir+EDITABLE_INI, sToDir+EDITABLE_INI ); + if( FILEMAN->IsAFile(sFromDir + DONT_SHARE_SIG) ) + FILEMAN->Move( sFromDir+DONT_SHARE_SIG, sToDir+DONT_SHARE_SIG ); +} + +RString Profile::MakeUniqueFileNameNoExtension( RString sDir, RString sFileNameBeginning ) +{ + FILEMAN->FlushDirCache( sDir ); + // Find a file name for the screenshot + vector files; + GetDirListing( sDir + sFileNameBeginning+"*", files, false, false ); + sort( files.begin(), files.end() ); + + int iIndex = 0; + + for( int i = files.size()-1; i >= 0; --i ) + { + static Regex re( "^" + sFileNameBeginning + "([0-9]{5})\\....$" ); + vector matches; + if( !re.Compare( files[i], matches ) ) + continue; + + ASSERT( matches.size() == 1 ); + iIndex = StringToInt( matches[0] )+1; + break; + } + + return MakeFileNameNoExtension( sFileNameBeginning, iIndex ); +} + +RString Profile::MakeFileNameNoExtension( RString sFileNameBeginning, int iIndex ) +{ + return sFileNameBeginning + ssprintf( "%05d", iIndex ); +} + +// lua start +#include "LuaBinding.h" + +/** @brief Allow Lua to have access to the Profile. */ +class LunaProfile: public Luna +{ +public: + static int GetDisplayName( T* p, lua_State *L ) { lua_pushstring(L, p->m_sDisplayName ); return 1; } + static int GetLastUsedHighScoreName( T* p, lua_State *L ) { lua_pushstring(L, p->m_sLastUsedHighScoreName ); return 1; } + static int GetHighScoreList( T* p, lua_State *L ) + { + if( LuaBinding::CheckLuaObjectType(L, 1, "Song") ) + { + const Song *pSong = Luna::check(L,1); + const Steps *pSteps = Luna::check(L,2); + HighScoreList &hsl = p->GetStepsHighScoreList( pSong, pSteps ); + hsl.PushSelf( L ); + return 1; + } + else if( LuaBinding::CheckLuaObjectType(L, 1, "Course") ) + { + const Course *pCourse = Luna::check(L,1); + const Trail *pTrail = Luna::check(L,2); + HighScoreList &hsl = p->GetCourseHighScoreList( pCourse, pTrail ); + hsl.PushSelf( L ); + return 1; + } + + luaL_typerror( L, 1, "Song or Course" ); + return 0; + } + + static int GetCharacter( T* p, lua_State *L ) { p->GetCharacter()->PushSelf(L); return 1; } + static int SetCharacter( T* p, lua_State *L ) { p->SetCharacter(SArg(1)); return 0; } + static int GetWeightPounds( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iWeightPounds ); return 1; } + static int SetWeightPounds( T* p, lua_State *L ) { p->m_iWeightPounds = IArg(1); return 0; } + static int GetGoalType( T* p, lua_State *L ) { lua_pushnumber(L, p->m_GoalType ); return 1; } + static int SetGoalType( T* p, lua_State *L ) { p->m_GoalType = Enum::Check(L, 1); return 0; } + static int GetGoalCalories( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iGoalCalories ); return 1; } + static int SetGoalCalories( T* p, lua_State *L ) { p->m_iGoalCalories = IArg(1); return 0; } + static int GetGoalSeconds( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iGoalSeconds ); return 1; } + static int SetGoalSeconds( T* p, lua_State *L ) { p->m_iGoalSeconds = IArg(1); return 0; } + static int GetCaloriesBurnedToday( T* p, lua_State *L ) { lua_pushnumber(L, p->GetCaloriesBurnedToday() ); return 1; } + static int GetTotalNumSongsPlayed( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iNumTotalSongsPlayed ); return 1; } + static int IsCodeUnlocked( T* p, lua_State *L ) { lua_pushboolean(L, p->IsCodeUnlocked(SArg(1)) ); return 1; } + static int GetSongsActual( T* p, lua_State *L ) { lua_pushnumber(L, p->GetSongsActual(Enum::Check(L, 1),Enum::Check(L, 2)) ); return 1; } + static int GetCoursesActual( T* p, lua_State *L ) { lua_pushnumber(L, p->GetCoursesActual(Enum::Check(L, 1),Enum::Check(L, 2)) ); return 1; } + static int GetSongsPossible( T* p, lua_State *L ) { lua_pushnumber(L, p->GetSongsPossible(Enum::Check(L, 1),Enum::Check(L, 2)) ); return 1; } + static int GetCoursesPossible( T* p, lua_State *L ) { lua_pushnumber(L, p->GetCoursesPossible(Enum::Check(L, 1),Enum::Check(L, 2)) ); return 1; } + static int GetSongsPercentComplete( T* p, lua_State *L ) { lua_pushnumber(L, p->GetSongsPercentComplete(Enum::Check(L, 1),Enum::Check(L, 2)) ); return 1; } + static int GetCoursesPercentComplete( T* p, lua_State *L ) { lua_pushnumber(L, p->GetCoursesPercentComplete(Enum::Check(L, 1),Enum::Check(L, 2)) ); return 1; } + static int GetTotalStepsWithTopGrade( T* p, lua_State *L ) { lua_pushnumber(L, p->GetTotalStepsWithTopGrade(Enum::Check(L, 1),Enum::Check(L, 2),Enum::Check(L, 3)) ); return 1; } + static int GetTotalTrailsWithTopGrade( T* p, lua_State *L ) { lua_pushnumber(L, p->GetTotalTrailsWithTopGrade(Enum::Check(L, 1),Enum::Check(L, 2),Enum::Check(L, 3)) ); return 1; } + static int GetNumTotalSongsPlayed( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iNumTotalSongsPlayed ); return 1; } + static int GetTotalSessions( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalSessions ); return 1; } + static int GetTotalSessionSeconds( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalSessionSeconds ); return 1; } + static int GetTotalGameplaySeconds( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalGameplaySeconds ); return 1; } + static int GetSongsAndCoursesPercentCompleteAllDifficulties( T* p, lua_State *L ) { lua_pushnumber(L, p->GetSongsAndCoursesPercentCompleteAllDifficulties(Enum::Check(L, 1)) ); return 1; } + static int GetTotalCaloriesBurned( T* p, lua_State *L ) { lua_pushnumber(L, p->m_fTotalCaloriesBurned ); return 1; } + static int GetDisplayTotalCaloriesBurned( T* p, lua_State *L ) { lua_pushstring(L, p->GetDisplayTotalCaloriesBurned() ); return 1; } + static int GetMostPopularSong( T* p, lua_State *L ) + { + Song *p2 = p->GetMostPopularSong(); + if( p2 ) + p2->PushSelf(L); + else + lua_pushnil( L ); + return 1; + } + static int GetMostPopularCourse( T* p, lua_State *L ) + { + Course *p2 = p->GetMostPopularCourse(); + if( p2 ) + p2->PushSelf(L); + else + lua_pushnil( L ); + return 1; + } + static int GetSongNumTimesPlayed( T* p, lua_State *L ) + { + ASSERT( !lua_isnil(L,1) ); + Song *pS = Luna::check(L,1); + lua_pushnumber( L, p->GetSongNumTimesPlayed(pS) ); + return 1; + } + static int HasPassedAnyStepsInSong( T* p, lua_State *L ) + { + ASSERT( !lua_isnil(L,1) ); + Song *pS = Luna::check(L,1); + lua_pushboolean( L, p->HasPassedAnyStepsInSong(pS) ); + return 1; + } + static int GetNumToasties( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iNumToasties ); return 1; } + static int GetTotalTapsAndHolds( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalTapsAndHolds ); return 1; } + static int GetTotalJumps( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalJumps ); return 1; } + static int GetTotalHolds( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalHolds ); return 1; } + static int GetTotalRolls( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalRolls ); return 1; } + static int GetTotalMines( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalMines ); return 1; } + static int GetTotalHands( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalHands ); return 1; } + static int GetTotalLifts( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iTotalLifts ); return 1; } + static int GetUserTable( T* p, lua_State *L ) { p->m_UserTable.PushSelf(L); return 1; } + static int GetLastPlayedSong( T* p, lua_State *L ) + { + Song *pS = p->m_lastSong.ToSong(); + if( pS ) + pS->PushSelf(L); + else + lua_pushnil( L ); + return 1; + } + static int GetLastPlayedCourse( T* p, lua_State *L ) + { + Course *pC = p->m_lastCourse.ToCourse(); + if( pC ) + pC->PushSelf(L); + else + lua_pushnil( L ); + return 1; + } + DEFINE_METHOD( GetGUID, m_sGuid ); + + LunaProfile() + { + ADD_METHOD( GetDisplayName ); + ADD_METHOD( GetLastUsedHighScoreName ); + ADD_METHOD( GetHighScoreList ); + ADD_METHOD( GetCharacter ); + ADD_METHOD( SetCharacter ); + ADD_METHOD( GetWeightPounds ); + ADD_METHOD( SetWeightPounds ); + ADD_METHOD( GetGoalType ); + ADD_METHOD( SetGoalType ); + ADD_METHOD( GetGoalCalories ); + ADD_METHOD( SetGoalCalories ); + ADD_METHOD( GetGoalSeconds ); + ADD_METHOD( SetGoalSeconds ); + ADD_METHOD( GetCaloriesBurnedToday ); + ADD_METHOD( GetTotalNumSongsPlayed ); + ADD_METHOD( IsCodeUnlocked ); + ADD_METHOD( GetSongsActual ); + ADD_METHOD( GetCoursesActual ); + ADD_METHOD( GetSongsPossible ); + ADD_METHOD( GetCoursesPossible ); + ADD_METHOD( GetSongsPercentComplete ); + ADD_METHOD( GetCoursesPercentComplete ); + ADD_METHOD( GetTotalStepsWithTopGrade ); + ADD_METHOD( GetTotalTrailsWithTopGrade ); + ADD_METHOD( GetNumTotalSongsPlayed ); + ADD_METHOD( GetTotalSessions ); + ADD_METHOD( GetTotalSessionSeconds ); + ADD_METHOD( GetTotalGameplaySeconds ); + ADD_METHOD( GetSongsAndCoursesPercentCompleteAllDifficulties ); + ADD_METHOD( GetTotalCaloriesBurned ); + ADD_METHOD( GetDisplayTotalCaloriesBurned ); + ADD_METHOD( GetMostPopularSong ); + ADD_METHOD( GetMostPopularCourse ); + ADD_METHOD( GetSongNumTimesPlayed ); + ADD_METHOD( HasPassedAnyStepsInSong ); + ADD_METHOD( GetNumToasties ); + ADD_METHOD( GetTotalTapsAndHolds ); + ADD_METHOD( GetTotalJumps ); + ADD_METHOD( GetTotalHolds ); + ADD_METHOD( GetTotalRolls ); + ADD_METHOD( GetTotalMines ); + ADD_METHOD( GetTotalHands ); + ADD_METHOD( GetTotalLifts ); + ADD_METHOD( GetUserTable ); + ADD_METHOD( GetLastPlayedSong ); + ADD_METHOD( GetLastPlayedCourse ); + ADD_METHOD( GetGUID ); + } +}; + +LUA_REGISTER_CLASS( Profile ) +// lua end + + +/* + * (c) 2001-2004 Chris Danford + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/src/ProfileManager.cpp b/src/ProfileManager.cpp index a22b027ed1..e3118d735f 100644 --- a/src/ProfileManager.cpp +++ b/src/ProfileManager.cpp @@ -842,6 +842,35 @@ public: static int LastLoadWasTamperedOrCorrupt( T* p, lua_State *L ) { lua_pushboolean(L, p->LastLoadWasTamperedOrCorrupt(Enum::Check(L, 1)) ); return 1; } static int GetPlayerName( T* p, lua_State *L ) { PlayerNumber pn = Enum::Check(L, 1); lua_pushstring(L, p->GetPlayerName(pn)); return 1; } + static int LocalProfileIDToDir( T* p, lua_State *L ) + { + RString dir = USER_PROFILES_DIR + SArg(1) + "/"; + lua_pushstring( L, dir ); + return 1; + } + static int SaveProfile( T* p, lua_State *L ) { lua_pushboolean( L, p->SaveProfile(Enum::Check(L, 1)) ); return 1; } + static int SaveLocalProfile( T* p, lua_State *L ) { lua_pushboolean( L, p->SaveLocalProfile(SArg(1)) ); return 1; } + static int ProfileFromMemoryCardIsNew( T* p, lua_State *L ) { lua_pushboolean( L, p->ProfileFromMemoryCardIsNew(Enum::Check(L, 1)) ); return 1; } + static int GetSongNumTimesPlayed( T* p, lua_State *L ) + { + lua_pushnumber(L, p->GetSongNumTimesPlayed(Luna::check(L,1),Enum::Check(L, 2)) ); + return 1; + } + static int GetLocalProfileIDs( T* p, lua_State *L ) + { + vector vsProfileIDs; + p->GetLocalProfileIDs(vsProfileIDs); + LuaHelpers::CreateTableFromArray( vsProfileIDs, L ); + return 1; + } + static int GetLocalProfileDisplayNames( T* p, lua_State *L ) + { + vector vsProfileNames; + p->GetLocalProfileDisplayNames(vsProfileNames); + LuaHelpers::CreateTableFromArray( vsProfileNames, L ); + return 1; + } + LunaProfileManager() { ADD_METHOD( IsPersistentProfile ); @@ -858,6 +887,14 @@ public: ADD_METHOD( ProfileWasLoadedFromMemoryCard ); ADD_METHOD( LastLoadWasTamperedOrCorrupt ); ADD_METHOD( GetPlayerName ); + // + ADD_METHOD( SaveProfile ); + ADD_METHOD( SaveLocalProfile ); + ADD_METHOD( ProfileFromMemoryCardIsNew ); + ADD_METHOD( GetSongNumTimesPlayed ); + ADD_METHOD( GetLocalProfileIDs ); + ADD_METHOD( GetLocalProfileDisplayNames ); + ADD_METHOD( LocalProfileIDToDir ); } };