From 9116712851949e0ddca34de99d7b721f6cddd54b Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 12:34:58 -0400 Subject: [PATCH 001/281] New branch for C++11 prep and scout and whatnot. First step: remove project files that won't work for the future for sure. --- src/StepMania-net2008.vcproj | 8595 -------------------- src/libtomcrypt/libtomcrypt-net2003.vcproj | 527 -- src/libtomcrypt/libtomcrypt-net2005.vcproj | 778 -- src/libtomcrypt/libtomcrypt-net2008.vcproj | 911 --- src/libtommath/libtommath-net2003.vcproj | 473 -- src/libtommath/libtommath-net2005.vcproj | 644 -- src/libtommath/libtommath-net2008.vcproj | 773 -- src/sm5-net2008.sln | 61 - src/smpackage-net2003.sln | 55 - src/smpackage-net2003.vcproj | 1072 --- 10 files changed, 13889 deletions(-) delete mode 100644 src/StepMania-net2008.vcproj delete mode 100644 src/libtomcrypt/libtomcrypt-net2003.vcproj delete mode 100644 src/libtomcrypt/libtomcrypt-net2005.vcproj delete mode 100644 src/libtomcrypt/libtomcrypt-net2008.vcproj delete mode 100644 src/libtommath/libtommath-net2003.vcproj delete mode 100644 src/libtommath/libtommath-net2005.vcproj delete mode 100644 src/libtommath/libtommath-net2008.vcproj delete mode 100644 src/sm5-net2008.sln delete mode 100644 src/smpackage-net2003.sln delete mode 100644 src/smpackage-net2003.vcproj diff --git a/src/StepMania-net2008.vcproj b/src/StepMania-net2008.vcproj deleted file mode 100644 index 6b70917388..0000000000 --- a/src/StepMania-net2008.vcproj +++ /dev/null @@ -1,8595 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/libtomcrypt/libtomcrypt-net2003.vcproj b/src/libtomcrypt/libtomcrypt-net2003.vcproj deleted file mode 100644 index 7879540dc9..0000000000 --- a/src/libtomcrypt/libtomcrypt-net2003.vcproj +++ /dev/null @@ -1,527 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/libtomcrypt/libtomcrypt-net2005.vcproj b/src/libtomcrypt/libtomcrypt-net2005.vcproj deleted file mode 100644 index 7e5f643fa4..0000000000 --- a/src/libtomcrypt/libtomcrypt-net2005.vcproj +++ /dev/null @@ -1,778 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/libtomcrypt/libtomcrypt-net2008.vcproj b/src/libtomcrypt/libtomcrypt-net2008.vcproj deleted file mode 100644 index e23705d644..0000000000 --- a/src/libtomcrypt/libtomcrypt-net2008.vcproj +++ /dev/null @@ -1,911 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/libtommath/libtommath-net2003.vcproj b/src/libtommath/libtommath-net2003.vcproj deleted file mode 100644 index 0fbf2bbba5..0000000000 --- a/src/libtommath/libtommath-net2003.vcproj +++ /dev/null @@ -1,473 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/libtommath/libtommath-net2005.vcproj b/src/libtommath/libtommath-net2005.vcproj deleted file mode 100644 index 40b5c9bffd..0000000000 --- a/src/libtommath/libtommath-net2005.vcproj +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/libtommath/libtommath-net2008.vcproj b/src/libtommath/libtommath-net2008.vcproj deleted file mode 100644 index 171dc846b3..0000000000 --- a/src/libtommath/libtommath-net2008.vcproj +++ /dev/null @@ -1,773 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/sm5-net2008.sln b/src/sm5-net2008.sln deleted file mode 100644 index bacfc7118b..0000000000 --- a/src/sm5-net2008.sln +++ /dev/null @@ -1,61 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual Studio 2008 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "StepMania", "StepMania-net2008.vcproj", "{AF209DBD-24BD-4356-8DFE-41751B221195}" - ProjectSection(ProjectDependencies) = postProject - {FEDE5283-1518-4B1D-8124-5C54DB17EA57} = {FEDE5283-1518-4B1D-8124-5C54DB17EA57} - {14AA6C9A-9429-477A-B318-6CCFF10E4527} = {14AA6C9A-9429-477A-B318-6CCFF10E4527} - {F70DD0FE-A74A-42E7-B81F-E35C49FC9EB9} = {F70DD0FE-A74A-42E7-B81F-E35C49FC9EB9} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libtommath", "libtommath\libtommath-net2008.vcproj", "{FEDE5283-1518-4B1D-8124-5C54DB17EA57}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libtomcrypt", "libtomcrypt\libtomcrypt-net2008.vcproj", "{F70DD0FE-A74A-42E7-B81F-E35C49FC9EB9}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libglew", "..\extern\glew-1.5.8\libglew\libglew.vcproj", "{14AA6C9A-9429-477A-B318-6CCFF10E4527}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - FastDebug|Win32 = FastDebug|Win32 - Release|Win32 = Release|Win32 - Release-SSE2|Win32 = Release-SSE2|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {AF209DBD-24BD-4356-8DFE-41751B221195}.Debug|Win32.ActiveCfg = Debug|Win32 - {AF209DBD-24BD-4356-8DFE-41751B221195}.Debug|Win32.Build.0 = Debug|Win32 - {AF209DBD-24BD-4356-8DFE-41751B221195}.FastDebug|Win32.ActiveCfg = FastDebug|Win32 - {AF209DBD-24BD-4356-8DFE-41751B221195}.FastDebug|Win32.Build.0 = FastDebug|Win32 - {AF209DBD-24BD-4356-8DFE-41751B221195}.Release|Win32.ActiveCfg = Release|Win32 - {AF209DBD-24BD-4356-8DFE-41751B221195}.Release|Win32.Build.0 = Release|Win32 - {AF209DBD-24BD-4356-8DFE-41751B221195}.Release-SSE2|Win32.ActiveCfg = Release-SSE2|Win32 - {AF209DBD-24BD-4356-8DFE-41751B221195}.Release-SSE2|Win32.Build.0 = Release-SSE2|Win32 - {FEDE5283-1518-4B1D-8124-5C54DB17EA57}.Debug|Win32.ActiveCfg = Debug|Win32 - {FEDE5283-1518-4B1D-8124-5C54DB17EA57}.Debug|Win32.Build.0 = Debug|Win32 - {FEDE5283-1518-4B1D-8124-5C54DB17EA57}.FastDebug|Win32.ActiveCfg = Debug|Win32 - {FEDE5283-1518-4B1D-8124-5C54DB17EA57}.FastDebug|Win32.Build.0 = Debug|Win32 - {FEDE5283-1518-4B1D-8124-5C54DB17EA57}.Release|Win32.ActiveCfg = Release|Win32 - {FEDE5283-1518-4B1D-8124-5C54DB17EA57}.Release|Win32.Build.0 = Release|Win32 - {FEDE5283-1518-4B1D-8124-5C54DB17EA57}.Release-SSE2|Win32.ActiveCfg = Release-SSE2|Win32 - {FEDE5283-1518-4B1D-8124-5C54DB17EA57}.Release-SSE2|Win32.Build.0 = Release-SSE2|Win32 - {F70DD0FE-A74A-42E7-B81F-E35C49FC9EB9}.Debug|Win32.ActiveCfg = Debug|Win32 - {F70DD0FE-A74A-42E7-B81F-E35C49FC9EB9}.Debug|Win32.Build.0 = Debug|Win32 - {F70DD0FE-A74A-42E7-B81F-E35C49FC9EB9}.FastDebug|Win32.ActiveCfg = Debug|Win32 - {F70DD0FE-A74A-42E7-B81F-E35C49FC9EB9}.FastDebug|Win32.Build.0 = Debug|Win32 - {F70DD0FE-A74A-42E7-B81F-E35C49FC9EB9}.Release|Win32.ActiveCfg = Release|Win32 - {F70DD0FE-A74A-42E7-B81F-E35C49FC9EB9}.Release|Win32.Build.0 = Release|Win32 - {F70DD0FE-A74A-42E7-B81F-E35C49FC9EB9}.Release-SSE2|Win32.ActiveCfg = Release-SSE2|Win32 - {F70DD0FE-A74A-42E7-B81F-E35C49FC9EB9}.Release-SSE2|Win32.Build.0 = Release-SSE2|Win32 - {14AA6C9A-9429-477A-B318-6CCFF10E4527}.Debug|Win32.ActiveCfg = Debug|Win32 - {14AA6C9A-9429-477A-B318-6CCFF10E4527}.Debug|Win32.Build.0 = Debug|Win32 - {14AA6C9A-9429-477A-B318-6CCFF10E4527}.FastDebug|Win32.ActiveCfg = Debug|Win32 - {14AA6C9A-9429-477A-B318-6CCFF10E4527}.FastDebug|Win32.Build.0 = Debug|Win32 - {14AA6C9A-9429-477A-B318-6CCFF10E4527}.Release|Win32.ActiveCfg = Release|Win32 - {14AA6C9A-9429-477A-B318-6CCFF10E4527}.Release|Win32.Build.0 = Release|Win32 - {14AA6C9A-9429-477A-B318-6CCFF10E4527}.Release-SSE2|Win32.ActiveCfg = Release|Win32 - {14AA6C9A-9429-477A-B318-6CCFF10E4527}.Release-SSE2|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/src/smpackage-net2003.sln b/src/smpackage-net2003.sln deleted file mode 100644 index 5b6b502f62..0000000000 --- a/src/smpackage-net2003.sln +++ /dev/null @@ -1,55 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 8.00 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "smpackage", "smpackage-net2003.vcproj", "{6B369A92-8DDF-487C-A589-614987544197}" - ProjectSection(ProjectDependencies) = postProject - {F8FE2773-87CB-402F-8DC8-A80837C3E24C} = {F8FE2773-87CB-402F-8DC8-A80837C3E24C} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZipArchive", "smpackage\ZipArchive\ZipArchive-net2003.vcproj", "{F8FE2773-87CB-402F-8DC8-A80837C3E24C}" - ProjectSection(ProjectDependencies) = postProject - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfiguration) = preSolution - Debug = Debug - Release = Release - Static Debug = Static Debug - Static Release = Static Release - Unicode Debug = Unicode Debug - Unicode Release = Unicode Release - Unicode Static Release = Unicode Static Release - EndGlobalSection - GlobalSection(ProjectConfiguration) = postSolution - {6B369A92-8DDF-487C-A589-614987544197}.Debug.ActiveCfg = Debug|Win32 - {6B369A92-8DDF-487C-A589-614987544197}.Debug.Build.0 = Debug|Win32 - {6B369A92-8DDF-487C-A589-614987544197}.Release.ActiveCfg = Release|Win32 - {6B369A92-8DDF-487C-A589-614987544197}.Release.Build.0 = Release|Win32 - {6B369A92-8DDF-487C-A589-614987544197}.Static Debug.ActiveCfg = Debug|Win32 - {6B369A92-8DDF-487C-A589-614987544197}.Static Debug.Build.0 = Debug|Win32 - {6B369A92-8DDF-487C-A589-614987544197}.Static Release.ActiveCfg = Release|Win32 - {6B369A92-8DDF-487C-A589-614987544197}.Static Release.Build.0 = Release|Win32 - {6B369A92-8DDF-487C-A589-614987544197}.Unicode Debug.ActiveCfg = Debug|Win32 - {6B369A92-8DDF-487C-A589-614987544197}.Unicode Debug.Build.0 = Debug|Win32 - {6B369A92-8DDF-487C-A589-614987544197}.Unicode Release.ActiveCfg = Release|Win32 - {6B369A92-8DDF-487C-A589-614987544197}.Unicode Release.Build.0 = Release|Win32 - {6B369A92-8DDF-487C-A589-614987544197}.Unicode Static Release.ActiveCfg = Release|Win32 - {6B369A92-8DDF-487C-A589-614987544197}.Unicode Static Release.Build.0 = Release|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Debug.ActiveCfg = Debug|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Debug.Build.0 = Debug|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Release.ActiveCfg = Release|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Release.Build.0 = Release|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Static Debug.ActiveCfg = Static Debug|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Static Debug.Build.0 = Static Debug|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Static Release.ActiveCfg = Static Release|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Static Release.Build.0 = Static Release|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Unicode Debug.ActiveCfg = Unicode Debug|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Unicode Debug.Build.0 = Unicode Debug|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Unicode Release.ActiveCfg = Unicode Release|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Unicode Release.Build.0 = Unicode Release|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Unicode Static Release.ActiveCfg = Unicode Static Release|Win32 - {F8FE2773-87CB-402F-8DC8-A80837C3E24C}.Unicode Static Release.Build.0 = Unicode Static Release|Win32 - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - EndGlobalSection - GlobalSection(ExtensibilityAddIns) = postSolution - EndGlobalSection -EndGlobal diff --git a/src/smpackage-net2003.vcproj b/src/smpackage-net2003.vcproj deleted file mode 100644 index 8bbe33e746..0000000000 --- a/src/smpackage-net2003.vcproj +++ /dev/null @@ -1,1072 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From e1274052ed4f2676d9cc1469e88e796ea93b1380 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 12:46:06 -0400 Subject: [PATCH 002/281] Use the latest version of the C++11 compiler. Well, someone has to test it. Anyone know if this will support XP? --- src/StepMania-net2011.vcxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/StepMania-net2011.vcxproj b/src/StepMania-net2011.vcxproj index e6d804a087..bf4bf7ab64 100644 --- a/src/StepMania-net2011.vcxproj +++ b/src/StepMania-net2011.vcxproj @@ -30,25 +30,25 @@ Application false MultiByte - v110 + v120_CTP_Nov2012 Application false MultiByte - v110 + v120_CTP_Nov2012 Application false MultiByte - v110 + v120_CTP_Nov2012 Application false MultiByte - v110 + v120_CTP_Nov2012 From 4427ea0d915aa1a04da30a3d22f111f09a8c4588 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:08:15 -0400 Subject: [PATCH 003/281] Remove unused maros. Also, white space hell. --- src/Foreach.h | 137 +++++++++++++++++++++++--------------------------- 1 file changed, 63 insertions(+), 74 deletions(-) diff --git a/src/Foreach.h b/src/Foreach.h index 35d595c4fb..1734446069 100644 --- a/src/Foreach.h +++ b/src/Foreach.h @@ -1,74 +1,63 @@ -#ifndef Foreach_H -#define Foreach_H - -/** @brief General foreach loop iterating over a vector. */ -#define FOREACH( elemType, vect, var ) \ -for( vector::iterator var = (vect).begin(); var != (vect).end(); ++var ) -/** @brief General foreach loop iterating over a vector, using a constant iterator. */ -#define FOREACH_CONST( elemType, vect, var ) \ -for( vector::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) - -/** @brief General foreach loop iterating over a deque. */ -#define FOREACHD( elemType, vect, var ) \ -for( deque::iterator var = (vect).begin(); var != (vect).end(); ++var ) -/** @brief General foreach loop iterating over a deque, using a constant iterator. */ -#define FOREACHD_CONST( elemType, vect, var ) \ -for( deque::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) - -/** @brief General foreach loop iterating over a set. */ -#define FOREACHS( elemType, vect, var ) \ -for( set::iterator var = (vect).begin(); var != (vect).end(); ++var ) -/** @brief General foreach loop iterating over a set, using a constant iterator. */ -#define FOREACHS_CONST( elemType, vect, var ) \ -for( set::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) - -/** @brief General foreach loop iterating over a list. */ -#define FOREACHL( elemType, vect, var ) \ -for( list::iterator var = (vect).begin(); var != (vect).end(); ++var ) -/** @brief General foreach loop iterating over a list, using a constant iterator. */ -#define FOREACHL_CONST( elemType, vect, var ) \ -for( list::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) - -/** @brief General foreach loop iterating over a map. */ -#define FOREACHM( keyType, valType, vect, var ) \ -for( map::iterator var = (vect).begin(); var != (vect).end(); ++var ) -/** @brief General foreach loop iterating over a map, using a constant iterator. */ -#define FOREACHM_CONST( keyType, valType, vect, var ) \ -for( map::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) - -/** @brief General foreach loop iterating over a multimap. */ -#define FOREACHMM( keyType, valType, vect, var ) \ -for( multimap::iterator var = (vect).begin(); var != (vect).end(); ++var ) - -/** @brief General foreach loop iterating over a multimap, using a constant iterator. */ -#define FOREACHMM_CONST( keyType, valType, vect, var ) \ -for( multimap::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) - -#endif - -/** - * @file - * @author Chris Danford (c) 2004-2005 - * @section LICENSE - * 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. - */ +#ifndef Foreach_H +#define Foreach_H + +/** @brief General foreach loop iterating over a vector. */ +#define FOREACH( elemType, vect, var ) \ +for( vector::iterator var = (vect).begin(); var != (vect).end(); ++var ) +/** @brief General foreach loop iterating over a vector, using a constant iterator. */ +#define FOREACH_CONST( elemType, vect, var ) \ +for( vector::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) + +/** @brief General foreach loop iterating over a deque. */ +#define FOREACHD( elemType, vect, var ) \ +for( deque::iterator var = (vect).begin(); var != (vect).end(); ++var ) +/** @brief General foreach loop iterating over a deque, using a constant iterator. */ +#define FOREACHD_CONST( elemType, vect, var ) \ +for( deque::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) + +/** @brief General foreach loop iterating over a set. */ +#define FOREACHS( elemType, vect, var ) \ +for( set::iterator var = (vect).begin(); var != (vect).end(); ++var ) +/** @brief General foreach loop iterating over a set, using a constant iterator. */ +#define FOREACHS_CONST( elemType, vect, var ) \ +for( set::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) + +/** @brief General foreach loop iterating over a list, using a constant iterator. */ +#define FOREACHL_CONST( elemType, vect, var ) \ +for( list::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) + +/** @brief General foreach loop iterating over a map. */ +#define FOREACHM( keyType, valType, vect, var ) \ +for( map::iterator var = (vect).begin(); var != (vect).end(); ++var ) +/** @brief General foreach loop iterating over a map, using a constant iterator. */ +#define FOREACHM_CONST( keyType, valType, vect, var ) \ +for( map::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) + +#endif + +/** + * @file + * @author Chris Danford (c) 2004-2005 + * @section LICENSE + * 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. + */ From c89634d0d7b11d89f85f3f936a54361c194a2c1c Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:16:16 -0400 Subject: [PATCH 004/281] Replace macro with for each loop. ...again, blasted newlines. --- src/Foreach.h | 4 - src/RageSoundPosMap.cpp | 464 ++++++++++++++++++++-------------------- 2 files changed, 231 insertions(+), 237 deletions(-) diff --git a/src/Foreach.h b/src/Foreach.h index 1734446069..2816c18672 100644 --- a/src/Foreach.h +++ b/src/Foreach.h @@ -22,10 +22,6 @@ for( set::iterator var = (vect).begin(); var != (vect).end(); ++var ) #define FOREACHS_CONST( elemType, vect, var ) \ for( set::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) -/** @brief General foreach loop iterating over a list, using a constant iterator. */ -#define FOREACHL_CONST( elemType, vect, var ) \ -for( list::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) - /** @brief General foreach loop iterating over a map. */ #define FOREACHM( keyType, valType, vect, var ) \ for( map::iterator var = (vect).begin(); var != (vect).end(); ++var ) diff --git a/src/RageSoundPosMap.cpp b/src/RageSoundPosMap.cpp index d073666fe8..45208ee438 100644 --- a/src/RageSoundPosMap.cpp +++ b/src/RageSoundPosMap.cpp @@ -1,233 +1,231 @@ -#include "global.h" -#include "RageSoundPosMap.h" -#include "RageLog.h" -#include "RageUtil.h" -#include "RageTimer.h" -#include "Foreach.h" - -#include - -/* The number of frames we should keep pos_map data for. This being too high - * is mostly harmless; the data is small. */ -const int pos_map_backlog_frames = 100000; - -struct pos_map_t -{ - int64_t m_iSourceFrame; - int64_t m_iDestFrame; - int m_iFrames; - float m_fSourceToDestRatio; - - pos_map_t() { m_iSourceFrame = 0; m_iDestFrame = 0; m_iFrames = 0; m_fSourceToDestRatio = 1.0f; } -}; - -struct pos_map_impl -{ - list m_Queue; - void Cleanup(); -}; - -pos_map_queue::pos_map_queue() -{ - m_pImpl = new pos_map_impl; -} - -pos_map_queue::~pos_map_queue() -{ - delete m_pImpl; -} - -pos_map_queue::pos_map_queue( const pos_map_queue &cpy ) -{ - *this = cpy; - m_pImpl = new pos_map_impl( *cpy.m_pImpl ); -} - -pos_map_queue &pos_map_queue::operator=( const pos_map_queue &rhs ) -{ - delete m_pImpl; - m_pImpl = new pos_map_impl( *rhs.m_pImpl ); - return *this; -} - -void pos_map_queue::Insert( int64_t iSourceFrame, int iFrames, int64_t iDestFrame, float fSourceToDestRatio ) -{ - if( m_pImpl->m_Queue.size() ) - { - /* Optimization: If the last entry lines up with this new entry, just merge them. */ - pos_map_t &last = m_pImpl->m_Queue.back(); - if( last.m_iSourceFrame + last.m_iFrames == iSourceFrame && - last.m_fSourceToDestRatio == fSourceToDestRatio && - llabs(last.m_iDestFrame + lrintf(last.m_iFrames * last.m_fSourceToDestRatio) - iDestFrame) <= 1 ) - { - last.m_iFrames += iFrames; - - /* Make sure that m_Frames doesn't grow too large and overflow an int. */ - if( !m_pImpl->m_Queue.empty() && last.m_iFrames > pos_map_backlog_frames * 2 ) - { - /* - * Split this entry into two smaller entries. This will cause up to one - * sample of rounding error in m_iDestFrame. This will not accumulate; - * if we split again and it becomes two frames of error, the next Insert() - * will be beyond the tolerance of the above iDestFrame check, and new - * data will be added to a new entry. - */ - int iDeleteFrames = last.m_iFrames - pos_map_backlog_frames; - - pos_map_t next(last); - - last.m_iFrames = iDeleteFrames; - - next.m_iSourceFrame += iDeleteFrames; - next.m_iFrames -= iDeleteFrames; - next.m_iDestFrame += lrintf( iDeleteFrames * next.m_fSourceToDestRatio ); - - m_pImpl->m_Queue.push_back( next ); - } - - m_pImpl->Cleanup(); - - return; - } - } - - m_pImpl->m_Queue.push_back( pos_map_t() ); - pos_map_t &m = m_pImpl->m_Queue.back(); - m.m_iSourceFrame = iSourceFrame; - m.m_iDestFrame = iDestFrame; - m.m_iFrames = iFrames; - m.m_fSourceToDestRatio = fSourceToDestRatio; - - m_pImpl->Cleanup(); -} - -void pos_map_impl::Cleanup() -{ - /* Scan backwards until we have at least pos_map_backlog_frames. */ - list::iterator it = m_Queue.end(); - int iTotalFrames = 0; - while( iTotalFrames < pos_map_backlog_frames ) - { - if( it == m_Queue.begin() ) - break; - --it; - iTotalFrames += it->m_iFrames; - } - - m_Queue.erase( m_Queue.begin(), it ); -} - -int64_t pos_map_queue::Search( int64_t iSourceFrame, bool *bApproximate ) const -{ - if( bApproximate ) - *bApproximate = false; - - if( IsEmpty() ) - { - if( bApproximate ) - *bApproximate = true; - return 0; - } - - /* iSourceFrame is probably in pos_map. Search to figure out what position - * it maps to. */ - int64_t iClosestPosition = 0, iClosestPositionDist = INT_MAX; - const pos_map_t *pClosestBlock = &*m_pImpl->m_Queue.begin(); /* print only */ - FOREACHL_CONST( pos_map_t, m_pImpl->m_Queue, it ) - { - const pos_map_t &pm = *it; - - if( iSourceFrame >= pm.m_iSourceFrame && - iSourceFrame < pm.m_iSourceFrame+pm.m_iFrames ) - { - /* iSourceFrame lies in this block; it's an exact match. Figure - * out the exact position. */ - int iDiff = int(iSourceFrame - pm.m_iSourceFrame); - iDiff = lrintf( iDiff * pm.m_fSourceToDestRatio ); - return pm.m_iDestFrame + iDiff; - } - - /* See if the current position is close to the beginning of this block. */ - int64_t dist = llabs( pm.m_iSourceFrame - iSourceFrame ); - if( dist < iClosestPositionDist ) - { - iClosestPositionDist = dist; - pClosestBlock = ± - iClosestPosition = pm.m_iDestFrame; - } - - /* See if the current position is close to the end of this block. */ - dist = llabs( pm.m_iSourceFrame + pm.m_iFrames - iSourceFrame ); - if( dist < iClosestPositionDist ) - { - iClosestPositionDist = dist; - pClosestBlock = ± - iClosestPosition = pm.m_iDestFrame + lrintf( pm.m_iFrames * pm.m_fSourceToDestRatio ); - } - } - - /* - * The frame is out of the range of data we've actually sent. - * Return the closest position. - * - * There are three cases when this happens: - * 1. Before the first CommitPlayingPosition call. - * 2. After GetDataToPlay returns EOF and the sound has flushed, but before - * SoundStopped has been called. - * 3. Underflow; we'll be given a larger frame number than we know about. - */ -#if defined(WIN32) -#define LI "%I64i" -#elif defined(PRIi64) -#define LI "%" PRIi64 -#else -#define LI "%lli" -#endif - static RageTimer last; - if( last.PeekDeltaTime() >= 1.0f ) - { - last.GetDeltaTime(); - LOG->Trace( "Approximate sound time: driver frame " LI ", m_pImpl->m_Queue frame " LI ".." LI " (dist " LI "), closest position is " LI, - iSourceFrame, pClosestBlock->m_iDestFrame, pClosestBlock->m_iDestFrame+pClosestBlock->m_iFrames, - iClosestPositionDist, iClosestPosition ); - } - - if( bApproximate ) - *bApproximate = true; - return iClosestPosition; -} - -void pos_map_queue::Clear() -{ - m_pImpl->m_Queue.clear(); -} - -bool pos_map_queue::IsEmpty() const -{ - return m_pImpl->m_Queue.empty(); -} - -/* - * Copyright (c) 2002-2004 Glenn Maynard - * 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 "RageSoundPosMap.h" +#include "RageLog.h" +#include "RageUtil.h" +#include "RageTimer.h" +#include "Foreach.h" + +#include + +/* The number of frames we should keep pos_map data for. This being too high + * is mostly harmless; the data is small. */ +const int pos_map_backlog_frames = 100000; + +struct pos_map_t +{ + int64_t m_iSourceFrame; + int64_t m_iDestFrame; + int m_iFrames; + float m_fSourceToDestRatio; + + pos_map_t() { m_iSourceFrame = 0; m_iDestFrame = 0; m_iFrames = 0; m_fSourceToDestRatio = 1.0f; } +}; + +struct pos_map_impl +{ + list m_Queue; + void Cleanup(); +}; + +pos_map_queue::pos_map_queue() +{ + m_pImpl = new pos_map_impl; +} + +pos_map_queue::~pos_map_queue() +{ + delete m_pImpl; +} + +pos_map_queue::pos_map_queue( const pos_map_queue &cpy ) +{ + *this = cpy; + m_pImpl = new pos_map_impl( *cpy.m_pImpl ); +} + +pos_map_queue &pos_map_queue::operator=( const pos_map_queue &rhs ) +{ + delete m_pImpl; + m_pImpl = new pos_map_impl( *rhs.m_pImpl ); + return *this; +} + +void pos_map_queue::Insert( int64_t iSourceFrame, int iFrames, int64_t iDestFrame, float fSourceToDestRatio ) +{ + if( m_pImpl->m_Queue.size() ) + { + /* Optimization: If the last entry lines up with this new entry, just merge them. */ + pos_map_t &last = m_pImpl->m_Queue.back(); + if( last.m_iSourceFrame + last.m_iFrames == iSourceFrame && + last.m_fSourceToDestRatio == fSourceToDestRatio && + llabs(last.m_iDestFrame + lrintf(last.m_iFrames * last.m_fSourceToDestRatio) - iDestFrame) <= 1 ) + { + last.m_iFrames += iFrames; + + /* Make sure that m_Frames doesn't grow too large and overflow an int. */ + if( !m_pImpl->m_Queue.empty() && last.m_iFrames > pos_map_backlog_frames * 2 ) + { + /* + * Split this entry into two smaller entries. This will cause up to one + * sample of rounding error in m_iDestFrame. This will not accumulate; + * if we split again and it becomes two frames of error, the next Insert() + * will be beyond the tolerance of the above iDestFrame check, and new + * data will be added to a new entry. + */ + int iDeleteFrames = last.m_iFrames - pos_map_backlog_frames; + + pos_map_t next(last); + + last.m_iFrames = iDeleteFrames; + + next.m_iSourceFrame += iDeleteFrames; + next.m_iFrames -= iDeleteFrames; + next.m_iDestFrame += lrintf( iDeleteFrames * next.m_fSourceToDestRatio ); + + m_pImpl->m_Queue.push_back( next ); + } + + m_pImpl->Cleanup(); + + return; + } + } + + m_pImpl->m_Queue.push_back( pos_map_t() ); + pos_map_t &m = m_pImpl->m_Queue.back(); + m.m_iSourceFrame = iSourceFrame; + m.m_iDestFrame = iDestFrame; + m.m_iFrames = iFrames; + m.m_fSourceToDestRatio = fSourceToDestRatio; + + m_pImpl->Cleanup(); +} + +void pos_map_impl::Cleanup() +{ + /* Scan backwards until we have at least pos_map_backlog_frames. */ + list::iterator it = m_Queue.end(); + int iTotalFrames = 0; + while( iTotalFrames < pos_map_backlog_frames ) + { + if( it == m_Queue.begin() ) + break; + --it; + iTotalFrames += it->m_iFrames; + } + + m_Queue.erase( m_Queue.begin(), it ); +} + +int64_t pos_map_queue::Search( int64_t iSourceFrame, bool *bApproximate ) const +{ + if( bApproximate ) + *bApproximate = false; + + if( IsEmpty() ) + { + if( bApproximate ) + *bApproximate = true; + return 0; + } + + /* iSourceFrame is probably in pos_map. Search to figure out what position + * it maps to. */ + int64_t iClosestPosition = 0, iClosestPositionDist = INT_MAX; + const pos_map_t *pClosestBlock = &*m_pImpl->m_Queue.begin(); /* print only */ + for (pos_map_t const &pm : m_pImpl->m_Queue) + { + if( iSourceFrame >= pm.m_iSourceFrame && + iSourceFrame < pm.m_iSourceFrame+pm.m_iFrames ) + { + /* iSourceFrame lies in this block; it's an exact match. Figure + * out the exact position. */ + int iDiff = int(iSourceFrame - pm.m_iSourceFrame); + iDiff = lrintf( iDiff * pm.m_fSourceToDestRatio ); + return pm.m_iDestFrame + iDiff; + } + + /* See if the current position is close to the beginning of this block. */ + int64_t dist = llabs( pm.m_iSourceFrame - iSourceFrame ); + if( dist < iClosestPositionDist ) + { + iClosestPositionDist = dist; + pClosestBlock = ± + iClosestPosition = pm.m_iDestFrame; + } + + /* See if the current position is close to the end of this block. */ + dist = llabs( pm.m_iSourceFrame + pm.m_iFrames - iSourceFrame ); + if( dist < iClosestPositionDist ) + { + iClosestPositionDist = dist; + pClosestBlock = ± + iClosestPosition = pm.m_iDestFrame + lrintf( pm.m_iFrames * pm.m_fSourceToDestRatio ); + } + } + + /* + * The frame is out of the range of data we've actually sent. + * Return the closest position. + * + * There are three cases when this happens: + * 1. Before the first CommitPlayingPosition call. + * 2. After GetDataToPlay returns EOF and the sound has flushed, but before + * SoundStopped has been called. + * 3. Underflow; we'll be given a larger frame number than we know about. + */ +#if defined(WIN32) +#define LI "%I64i" +#elif defined(PRIi64) +#define LI "%" PRIi64 +#else +#define LI "%lli" +#endif + static RageTimer last; + if( last.PeekDeltaTime() >= 1.0f ) + { + last.GetDeltaTime(); + LOG->Trace( "Approximate sound time: driver frame " LI ", m_pImpl->m_Queue frame " LI ".." LI " (dist " LI "), closest position is " LI, + iSourceFrame, pClosestBlock->m_iDestFrame, pClosestBlock->m_iDestFrame+pClosestBlock->m_iFrames, + iClosestPositionDist, iClosestPosition ); + } + + if( bApproximate ) + *bApproximate = true; + return iClosestPosition; +} + +void pos_map_queue::Clear() +{ + m_pImpl->m_Queue.clear(); +} + +bool pos_map_queue::IsEmpty() const +{ + return m_pImpl->m_Queue.empty(); +} + +/* + * Copyright (c) 2002-2004 Glenn Maynard + * 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. + */ From 3758284cbaff285c35cae141e39dc44dfbb23748 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:17:32 -0400 Subject: [PATCH 005/281] The first of the set loops. --- src/ActorUtil.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ActorUtil.cpp b/src/ActorUtil.cpp index d7068672ab..bc61401c81 100644 --- a/src/ActorUtil.cpp +++ b/src/ActorUtil.cpp @@ -346,9 +346,8 @@ void ActorUtil::LoadAllCommandsFromName( Actor& actor, const RString &sMetricsGr set vsValueNames; THEME->GetMetricsThatBeginWith( sMetricsGroup, sName, vsValueNames ); - FOREACHS_CONST( RString, vsValueNames, v ) + for (RString const & sv : vsValueNames) { - const RString &sv = *v; static const RString sEnding = "Command"; if( EndsWith(sv,sEnding) ) { From ed0600fa3642724b8bd0ea3eb509a2dc180d4926 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:18:54 -0400 Subject: [PATCH 006/281] Another for loop to f--white space!! --- src/Course.cpp | 2368 ++++++++++++++++++++++++------------------------ 1 file changed, 1184 insertions(+), 1184 deletions(-) diff --git a/src/Course.cpp b/src/Course.cpp index 1b67acdcb8..212b9ce5a8 100644 --- a/src/Course.cpp +++ b/src/Course.cpp @@ -1,1184 +1,1184 @@ -#include -#include "global.h" -#include "Course.h" -#include "CourseLoaderCRS.h" -#include "Foreach.h" -#include "GameManager.h" -#include "GameState.h" -#include "LocalizedString.h" -#include "LuaManager.h" -#include "Preference.h" -#include "PrefsManager.h" -#include "ProfileManager.h" -#include "RageLog.h" -#include "Song.h" -#include "SongCacheIndex.h" -#include "Steps.h" -#include "ThemeManager.h" -#include "UnlockManager.h" -#include "Game.h" -#include "Style.h" - -static Preference MAX_SONGS_IN_EDIT_COURSE( "MaxSongsInEditCourse", -1 ); - -static const char *SongSortNames[] = { - "Randomize", - "MostPlays", - "FewestPlays", - "TopGrades", - "LowestGrades", -}; -XToString( SongSort ); -XToLocalizedString( SongSort ); - - -/* Maximum lower value of ranges when difficult: */ -const int MAX_BOTTOM_RANGE = 10; - -#define SORT_PREFERRED_COLOR THEME->GetMetricC("Course","SortPreferredColor") -#define SORT_LEVEL1_COLOR THEME->GetMetricC("Course","SortLevel1Color") -#define SORT_LEVEL2_COLOR THEME->GetMetricC("Course","SortLevel2Color") -#define SORT_LEVEL3_COLOR THEME->GetMetricC("Course","SortLevel3Color") -#define SORT_LEVEL4_COLOR THEME->GetMetricC("Course","SortLevel4Color") -#define SORT_LEVEL5_COLOR THEME->GetMetricC("Course","SortLevel5Color") - -//#define INCLUDE_BEGINNER_STEPS THEME->GetMetricB( "Course","IncludeBeginnerSteps" ); - -RString CourseEntry::GetTextDescription() const -{ - vector vsEntryDescription; - Song *pSong = songID.ToSong(); - if( pSong ) - vsEntryDescription.push_back( pSong->GetTranslitFullTitle() ); - else - vsEntryDescription.push_back( "Random" ); - if( !songCriteria.m_sGroupName.empty() ) - vsEntryDescription.push_back( songCriteria.m_sGroupName ); - if( songCriteria.m_bUseSongGenreAllowedList ) - vsEntryDescription.push_back( join(",",songCriteria.m_vsSongGenreAllowedList) ); - if( stepsCriteria.m_difficulty != Difficulty_Invalid && stepsCriteria.m_difficulty != Difficulty_Medium ) - vsEntryDescription.push_back( CourseDifficultyToLocalizedString(stepsCriteria.m_difficulty) ); - if( stepsCriteria.m_iLowMeter != -1 ) - vsEntryDescription.push_back( ssprintf("Low meter: %d", stepsCriteria.m_iLowMeter) ); - if( stepsCriteria.m_iHighMeter != -1 ) - vsEntryDescription.push_back( ssprintf("High meter: %d", stepsCriteria.m_iHighMeter) ); - if( songSort != SongSort_Randomize ) - vsEntryDescription.push_back( "Sort: %d" + SongSortToLocalizedString(songSort) ); - if( songSort != SongSort_Randomize && iChooseIndex != 0 ) - vsEntryDescription.push_back( "Choose " + FormatNumberAndSuffix(iChooseIndex) + " match" ); - int iNumModChanges = GetNumModChanges(); - if( iNumModChanges != 0 ) - vsEntryDescription.push_back( ssprintf("%d mod changes", iNumModChanges) ); - if( fGainSeconds != 0 ) - vsEntryDescription.push_back( ssprintf("Low meter: %.0f", fGainSeconds) ); - - RString s = join( ",", vsEntryDescription ); - return s; -} - -int CourseEntry::GetNumModChanges() const -{ - int iNumModChanges = 0; - if( !sModifiers.empty() ) - iNumModChanges++; - iNumModChanges += attacks.size(); - return iNumModChanges; -} - - -Course::Course(): m_bIsAutogen(false), m_sPath(""), m_sMainTitle(""), - m_sMainTitleTranslit(""), m_sSubTitle(""), m_sSubTitleTranslit(""), - m_sScripter(""), m_sDescription(""), m_sBannerPath(""), m_sBackgroundPath(""), - m_sCDTitlePath(""), m_sGroupName(""), m_bRepeat(false), m_fGoalSeconds(0), - m_bShuffle(false), m_iLives(-1), m_bSortByMeter(false), - m_bIncomplete(false), m_vEntries(), m_SortOrder_TotalDifficulty(0), - m_SortOrder_Ranking(0), m_LoadedFromProfile(ProfileSlot_Invalid), - m_TrailCache(), m_iTrailCacheSeed(0), m_RadarCache(), - m_setStyles(), m_CachedObject() -{ - FOREACH_ENUM( Difficulty,dc) - m_iCustomMeter[dc] = -1; -} - -CourseType Course::GetCourseType() const -{ - if( m_bRepeat ) - return COURSE_TYPE_ENDLESS; - if( m_iLives > 0 ) - return COURSE_TYPE_ONI; - if( !m_vEntries.empty() && m_vEntries[0].fGainSeconds > 0 ) - return COURSE_TYPE_SURVIVAL; - return COURSE_TYPE_NONSTOP; -} - -void Course::SetCourseType( CourseType ct ) -{ - if( GetCourseType() == ct ) - return; - - m_bRepeat = false; - m_iLives = -1; - if( !m_vEntries.empty() ) - m_vEntries[0].fGainSeconds = 0; - - switch( ct ) - { - default: - FAIL_M(ssprintf("Invalid course type: %i", ct)); - case COURSE_TYPE_NONSTOP: - break; - case COURSE_TYPE_ONI: - m_iLives = 4; - break; - case COURSE_TYPE_ENDLESS: - m_bRepeat = true; - break; - case COURSE_TYPE_SURVIVAL: - if( !m_vEntries.empty() ) - m_vEntries[0].fGainSeconds = 120; - break; - } -} - -PlayMode Course::GetPlayMode() const -{ - CourseType ct = GetCourseType(); - switch( ct ) - { - case COURSE_TYPE_ENDLESS: return PLAY_MODE_ENDLESS; - case COURSE_TYPE_ONI: return PLAY_MODE_ONI; - case COURSE_TYPE_SURVIVAL: return PLAY_MODE_ONI; - case COURSE_TYPE_NONSTOP: return PLAY_MODE_NONSTOP; - default: - FAIL_M(ssprintf("Invalid course type: %i", ct)); - } -} - -void Course::RevertFromDisk() -{ - // trying to catch invalid an Course - ASSERT( !m_sPath.empty() ); - - CourseLoaderCRS::LoadFromCRSFile( m_sPath, *this ); -} - -RString Course::GetCacheFilePath() const -{ - return SongCacheIndex::GetCacheFilePath( "Courses", m_sPath ); -} - -void Course::Init() -{ - m_bIsAutogen = false; - m_sPath = ""; - - m_sMainTitle = ""; - m_sMainTitleTranslit = ""; - m_sSubTitle = ""; - m_sSubTitleTranslit = ""; - m_sScripter = ""; - m_sDescription = ""; - - m_sBannerPath = ""; - m_sBackgroundPath = ""; - m_sCDTitlePath = ""; - m_sGroupName = ""; - - m_bRepeat = false; - m_fGoalSeconds = 0; - m_bShuffle = false; - - m_iLives = -1; - FOREACH_ENUM( Difficulty,dc) - m_iCustomMeter[dc] = -1; - m_bSortByMeter = false; - - m_vEntries.clear(); - - m_SortOrder_TotalDifficulty = 0; - m_SortOrder_Ranking = 0; - - m_LoadedFromProfile = ProfileSlot_Invalid; - m_bIncomplete = false; - m_TrailCache.clear(); - m_iTrailCacheSeed = 0; - m_RadarCache.clear(); -} - -bool Course::IsPlayableIn( StepsType st ) const -{ - // Stripped down version of GetTrailUnsorted - FOREACH_CONST( CourseEntry, m_vEntries, e ) - { - SongCriteria soc = e->songCriteria; - - Song *pSong = e->songID.ToSong(); - if( pSong ) - { - soc.m_bUseSongAllowedList = true; - soc.m_vpSongAllowedList.push_back( pSong ); - } - soc.m_Tutorial = SongCriteria::Tutorial_No; - soc.m_Locked = SongCriteria::Locked_Unlocked; - if( !soc.m_bUseSongAllowedList ) - soc.m_iMaxStagesForSong = 1; - - StepsCriteria stc = e->stepsCriteria; - stc.m_st = st; - stc.m_Locked = StepsCriteria::Locked_Unlocked; - - const bool bSameSongCriteria = e != m_vEntries.begin() && (e-1)->songCriteria == soc; - const bool bSameStepsCriteria = e != m_vEntries.begin() && (e-1)->stepsCriteria == stc; - - if( pSong ) - { - if( StepsUtil::HasMatching(pSong, stc) ) - return true; - } - else if( !(bSameSongCriteria && bSameStepsCriteria) ) - { - if( StepsUtil::HasMatching(soc, stc) ) - return true; - } - - } - - return false; -} - - -struct SortTrailEntry -{ - TrailEntry entry; - int SortMeter; - - SortTrailEntry(): entry(), SortMeter(0) {} - - bool operator< ( const SortTrailEntry &rhs ) const { return SortMeter < rhs.SortMeter; } -}; - -RString Course::GetDisplayMainTitle() const -{ - if( !PREFSMAN->m_bShowNativeLanguage ) - return GetTranslitMainTitle(); - return m_sMainTitle; -} - -RString Course::GetDisplaySubTitle() const -{ - if( !PREFSMAN->m_bShowNativeLanguage ) - return GetTranslitSubTitle(); - return m_sSubTitle; -} - -RString Course::GetDisplayFullTitle() const -{ - RString sTitle = GetDisplayMainTitle(); - RString sSubTitle = GetDisplaySubTitle(); - - if( !sSubTitle.empty() ) - sTitle += " " + sSubTitle; - return sTitle; -} - -RString Course::GetTranslitFullTitle() const -{ - RString sTitle = GetTranslitMainTitle(); - RString sSubTitle = GetTranslitSubTitle(); - - if( !sSubTitle.empty() ) - sTitle += " " + sSubTitle; - return sTitle; -} - -/* This is called by many simple functions, like Course::GetTotalSeconds, and may - * be called on all songs to sort. It can take time to execute, so we cache the - * results. Returned pointers remain valid for the lifetime of the Course. If the - * course difficulty doesn't exist, NULL is returned. */ -Trail* Course::GetTrail( StepsType st, CourseDifficulty cd ) const -{ - ASSERT( cd != Difficulty_Invalid ); - - // Check to see if the Trail cache is out of date - if( m_iTrailCacheSeed != GAMESTATE->m_iStageSeed ) - { - RegenerateNonFixedTrails(); - m_iTrailCacheSeed = GAMESTATE->m_iStageSeed; - } - - // Look in the Trail cache - { - TrailCache_t::iterator it = m_TrailCache.find( CacheEntry(st, cd) ); - if( it != m_TrailCache.end() ) - { - CacheData &cache = it->second; - if( cache.null ) - return NULL; - return &cache.trail; - } - } - - return GetTrailForceRegenCache( st, cd ); -} - -Trail* Course::GetTrailForceRegenCache( StepsType st, CourseDifficulty cd ) const -{ - // Construct a new Trail, add it to the cache, then return it. - CacheData &cache = m_TrailCache[ CacheEntry(st, cd) ]; - Trail &trail = cache.trail; - trail.Init(); - if( !GetTrailSorted(st, cd, trail) ) - { - // This course difficulty doesn't exist. - cache.null = true; - return NULL; - } - - // If we have cached RadarValues for this trail, insert them. - { - RadarCache_t::const_iterator it = m_RadarCache.find( CacheEntry( st, cd ) ); - if( it != m_RadarCache.end() ) - { - const RadarValues &rv = it->second; - trail.SetRadarValues(rv); - } - } - - cache.null = false; - return &cache.trail; -} - -bool Course::GetTrailSorted( StepsType st, CourseDifficulty cd, Trail &trail ) const -{ - if( !GetTrailUnsorted( st, cd, trail ) ) - return false; - - if( this->m_bSortByMeter ) - { - /* Sort according to Difficulty_Medium, since the order of songs - * must not change across difficulties. */ - Trail SortTrail; - if( cd == Difficulty_Medium ) - SortTrail = trail; - else - { - bool bOK = GetTrailUnsorted( st, Difficulty_Medium, SortTrail ); - - /* If we have any other difficulty, we must have Difficulty_Medium. */ - ASSERT( bOK ); - } - ASSERT_M( trail.m_vEntries.size() == SortTrail.m_vEntries.size(), - ssprintf("%i %i", int(trail.m_vEntries.size()), int(SortTrail.m_vEntries.size())) ); - - vector entries; - for( unsigned i = 0; i < trail.m_vEntries.size(); ++i ) - { - SortTrailEntry ste; - ste.entry = trail.m_vEntries[i]; - ste.SortMeter = SortTrail.m_vEntries[i].pSteps->GetMeter(); - entries.push_back( ste ); - } - - stable_sort( entries.begin(), entries.end() ); - for( unsigned i = 0; i < trail.m_vEntries.size(); ++i ) - trail.m_vEntries[i] = entries[i].entry; - } - - if( trail.m_vEntries.empty() ) - return false; - return true; -} - -// TODO: Move Course initialization after PROFILEMAN is created -static void CourseSortSongs( SongSort sort, vector &vpPossibleSongs, RandomGen &rnd ) -{ - switch( sort ) - { - DEFAULT_FAIL(sort); - case SongSort_Randomize: - random_shuffle( vpPossibleSongs.begin(), vpPossibleSongs.end(), rnd ); - break; - case SongSort_MostPlays: - if( PROFILEMAN ) - SongUtil::SortSongPointerArrayByNumPlays( vpPossibleSongs, PROFILEMAN->GetMachineProfile(), true ); // descending - break; - case SongSort_FewestPlays: - if( PROFILEMAN ) - SongUtil::SortSongPointerArrayByNumPlays( vpPossibleSongs, PROFILEMAN->GetMachineProfile(), false ); // ascending - break; - case SongSort_TopGrades: - if( PROFILEMAN ) - SongUtil::SortSongPointerArrayByGrades( vpPossibleSongs, true ); // descending - break; - case SongSort_LowestGrades: - if( PROFILEMAN ) - SongUtil::SortSongPointerArrayByGrades( vpPossibleSongs, false ); // ascending - break; - } -} - -namespace -{ - struct SongIsEqual - { - const Song *m_pSong; - SongIsEqual( const Song *pSong ) : m_pSong(pSong) { } - bool operator()( const SongAndSteps &sas ) const { return sas.pSong == m_pSong; } - }; -} - -bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) const -{ - trail.Init(); - - // XXX: Why are beginner and challenge excluded here? -Wolfman2000 - switch( cd ) - { - case Difficulty_Beginner: - return false; - case Difficulty_Challenge: - return false; - default: break; - } - - // Construct a new Trail, add it to the cache, then return it. - // Different seed for each course, but the same for the whole round: - RandomGen rnd( GAMESTATE->m_iStageSeed + GetHashForString(m_sMainTitle) ); - - vector tmp_entries; - if( m_bShuffle ) - { - /* Always randomize the same way per round. Otherwise, the displayed course - * will change every time it's viewed, and the displayed order will have no - * bearing on what you'll actually play. */ - tmp_entries = m_vEntries; - random_shuffle( tmp_entries.begin(), tmp_entries.end(), rnd ); - } - - const vector &entries = m_bShuffle ? tmp_entries:m_vEntries; - - // This can take some time, so don't fill it out unless we need it. - vector vSongsByMostPlayed; - vector AllSongsShuffled; - - trail.m_StepsType = st; - trail.m_CourseType = GetCourseType(); - trail.m_CourseDifficulty = cd; - - // Set to true if CourseDifficulty is able to change something. - bool bCourseDifficultyIsSignificant = (cd == Difficulty_Medium); - - vector vpAllPossibleSongs; - vector vSongAndSteps; - - // Resolve each entry to a Song and Steps. - FOREACH_CONST( CourseEntry, entries, e ) - { - SongAndSteps resolved; // fill this in - SongCriteria soc = e->songCriteria; - - Song *pSong = e->songID.ToSong(); - if( pSong ) - { - soc.m_bUseSongAllowedList = true; - soc.m_vpSongAllowedList.push_back( pSong ); - } - soc.m_Tutorial = SongCriteria::Tutorial_No; - soc.m_Locked = SongCriteria::Locked_Unlocked; - if( !soc.m_bUseSongAllowedList ) - soc.m_iMaxStagesForSong = 1; - - StepsCriteria stc = e->stepsCriteria; - stc.m_st = st; - stc.m_Locked = StepsCriteria::Locked_Unlocked; - - const bool bSameSongCriteria = e != entries.begin() && (e-1)->songCriteria == soc; - const bool bSameStepsCriteria = e != entries.begin() && (e-1)->stepsCriteria == stc; - - if( pSong ) - { - StepsUtil::GetAllMatching( pSong, stc, vSongAndSteps ); - } - else if( vSongAndSteps.empty() || !(bSameSongCriteria && bSameStepsCriteria) ) - { - vSongAndSteps.clear(); - StepsUtil::GetAllMatching( soc, stc, vSongAndSteps ); - } - - // It looks bad to have the same song 2x in a row in a randomly generated course. - // Don't allow the same song to be played 2x in a row, unless there's only - // one song in vpPossibleSongs. - if( trail.m_vEntries.size() > 0 && vSongAndSteps.size() > 1 ) - { - const TrailEntry &teLast = trail.m_vEntries.back(); - RemoveIf( vSongAndSteps, SongIsEqual(teLast.pSong) ); - } - - // if there are no songs to choose from, abort this CourseEntry - if( vSongAndSteps.empty() ) - continue; - - vector vpSongs; - typedef vector StepsVector; - map mapSongToSteps; - FOREACH_CONST( SongAndSteps, vSongAndSteps, sas ) - { - StepsVector &v = mapSongToSteps[sas->pSong]; - - v.push_back( sas->pSteps ); - if( v.size() == 1 ) - vpSongs.push_back( sas->pSong ); - } - - CourseSortSongs( e->songSort, vpSongs, rnd ); - - ASSERT( e->iChooseIndex >= 0 ); - if( e->iChooseIndex < int(vSongAndSteps.size()) ) - { - resolved.pSong = vpSongs[e->iChooseIndex]; - const vector &mappedSongs = mapSongToSteps[resolved.pSong]; - resolved.pSteps = mappedSongs[ RandomInt(mappedSongs.size()) ]; - } - else - { - continue; - } - - /* If we're not COURSE_DIFFICULTY_REGULAR, then we should be choosing steps that are - * either easier or harder than the base difficulty. If no such steps exist, then - * just use the one we already have. */ - Difficulty dc = resolved.pSteps->GetDifficulty(); - int iLowMeter = e->stepsCriteria.m_iLowMeter; - int iHighMeter = e->stepsCriteria.m_iHighMeter; - if( cd != Difficulty_Medium && !e->bNoDifficult ) - { - Difficulty new_dc = (Difficulty)(dc + cd - Difficulty_Medium); - new_dc = clamp( new_dc, (Difficulty)0, (Difficulty)(Difficulty_Edit-1) ); - /* - // re-edit this code to work using the metric. - Difficulty new_dc; - if( INCLUDE_BEGINNER_STEPS ) - { - // don't factor in the course difficulty if we're including - // beginner steps -aj - new_dc = clamp( dc, Difficulty_Beginner, (Difficulty)(Difficulty_Edit-1) ); - } - else - { - new_dc = (Difficulty)(dc + cd - Difficulty_Medium); - new_dc = clamp( new_dc, (Difficulty)0, (Difficulty)(Difficulty_Edit-1) ); - } - */ - - bool bChangedDifficulty = false; - if( new_dc != dc ) - { - Steps* pNewSteps = SongUtil::GetStepsByDifficulty( resolved.pSong, st, new_dc ); - if( pNewSteps ) - { - dc = new_dc; - resolved.pSteps = pNewSteps; - bChangedDifficulty = true; - bCourseDifficultyIsSignificant = true; - } - } - - /* Hack: We used to adjust low_meter/high_meter above while searching for - * songs. However, that results in a different song being chosen for - * difficult courses, which is bad when LockCourseDifficulties is disabled; - * each player can end up with a different song. Instead, choose based - * on the original range, bump the steps based on course difficulty, and - * then retroactively tweak the low_meter/high_meter so course displays - * line up. */ - if( e->stepsCriteria.m_difficulty == Difficulty_Invalid && bChangedDifficulty ) - { - /* Minimum and maximum to add to make the meter range contain the actual - * meter: */ - int iMinDist = resolved.pSteps->GetMeter() - iHighMeter; - int iMaxDist = resolved.pSteps->GetMeter() - iLowMeter; - - /* Clamp the possible adjustments to try to avoid going under 1 or over - * MAX_BOTTOM_RANGE. */ - iMinDist = min( max( iMinDist, -iLowMeter+1 ), iMaxDist ); - iMaxDist = max( min( iMaxDist, MAX_BOTTOM_RANGE-iHighMeter ), iMinDist ); - - int iAdd; - if( iMaxDist == iMinDist ) - iAdd = iMaxDist; - else - iAdd = rnd(iMaxDist-iMinDist) + iMinDist; - iLowMeter += iAdd; - iHighMeter += iAdd; - } - } - - TrailEntry te; - te.pSong = resolved.pSong; - te.pSteps = resolved.pSteps; - te.Modifiers = e->sModifiers; - te.Attacks = e->attacks; - te.bSecret = e->bSecret; - te.iLowMeter = iLowMeter; - te.iHighMeter = iHighMeter; - - /* If we chose based on meter (not difficulty), then store Difficulty_Invalid, so - * other classes can tell that we used meter. */ - if( e->stepsCriteria.m_difficulty == Difficulty_Invalid ) - { - te.dc = Difficulty_Invalid; - } - else - { - /* Otherwise, store the actual difficulty we got (post-course-difficulty). - * This may or may not be the same as e.difficulty. */ - te.dc = dc; - } - trail.m_vEntries.push_back( te ); - - // LOG->Trace( "Chose: %s, %d", te.pSong->GetSongDir().c_str(), te.pSteps->GetMeter() ); - - if( IsAnEdit() && MAX_SONGS_IN_EDIT_COURSE > 0 && - int(trail.m_vEntries.size()) >= MAX_SONGS_IN_EDIT_COURSE ) - { - break; - } - } - - /* Hack: If any entry was non-FIXED, or m_bShuffle is set, then radar values - * for this trail will be meaningless as they'll change every time. Pre-cache - * empty data. XXX: How can we do this cleanly, without propagating lots of - * otherwise unnecessary data (course entry types, m_bShuffle) to Trail, or - * storing a Course pointer in Trail (yuck)? */ - if( !AllSongsAreFixed() || m_bShuffle ) - { - trail.m_bRadarValuesCached = true; - trail.m_CachedRadarValues = RadarValues(); - } - - /* If we have a manually-entered meter for this difficulty, use it. */ - if( m_iCustomMeter[cd] != -1 ) - trail.m_iSpecifiedMeter = m_iCustomMeter[cd]; - - /* If the course difficulty never actually changed anything, then this difficulty - * is equivalent to Difficulty_Medium; it doesn't exist. */ - return bCourseDifficultyIsSignificant; -} - -void Course::GetTrails( vector &AddTo, StepsType st ) const -{ - FOREACH_ShownCourseDifficulty( cd ) - { - Trail *pTrail = GetTrail( st, cd ); - if( pTrail == NULL ) - continue; - AddTo.push_back( pTrail ); - } -} - -void Course::GetAllTrails( vector &AddTo ) const -{ - vector vStepsTypesToShow; - GAMEMAN->GetStepsTypesForGame( GAMESTATE->m_pCurGame, vStepsTypesToShow ); - FOREACH( StepsType, vStepsTypesToShow, st ) - { - GetTrails( AddTo, *st ); - } -} - -int Course::GetMeter( StepsType st, CourseDifficulty cd ) const -{ - if( m_iCustomMeter[cd] != -1 ) - return m_iCustomMeter[cd]; - const Trail* pTrail = GetTrail( st ); - if( pTrail != NULL ) - return pTrail->GetMeter(); - return 0; -} - -bool Course::HasMods() const -{ - FOREACH_CONST( CourseEntry, m_vEntries, e ) - { - if( !e->attacks.empty() ) - return true; - } - - return false; -} - -bool Course::HasTimedMods() const -{ - // What makes this different from the SM4 implementation is that - // HasTimedMods now searches for bGlobal in the attacks; if one of - // them is false, it has timed mods. Also returning false will probably - // take longer than expected. -aj - FOREACH_CONST( CourseEntry, m_vEntries, e ) - { - if( !e->attacks.empty() ) - { - for( unsigned s=0; s < e->attacks.size(); s++ ) - { - const Attack attack = e->attacks[s]; - if(!attack.bGlobal) - return true; - } - } - } - return false; -} - -bool Course::AllSongsAreFixed() const -{ - FOREACH_CONST( CourseEntry, m_vEntries, e ) - { - if( !e->IsFixedSong() ) - return false; - } - return true; -} - -const Style *Course::GetCourseStyle( const Game *pGame, int iNumPlayers ) const -{ - vector vpStyles; - GAMEMAN->GetCompatibleStyles( pGame, iNumPlayers, vpStyles ); - - for( int s=0; s < (int) vpStyles.size(); ++s ) - { - const Style *pStyle = vpStyles[s]; - FOREACHS_CONST( RString, m_setStyles, style ) - { - if( !style->CompareNoCase(pStyle->m_szName) ) - return pStyle; - } - } - return NULL; -} - -void Course::InvalidateTrailCache() -{ - m_TrailCache.clear(); -} - -void Course::Invalidate( const Song *pStaleSong ) -{ - FOREACH_CONST( CourseEntry, m_vEntries, e ) - { - Song *pSong = e->songID.ToSong(); - if( pSong == pStaleSong ) // a fixed entry that references the stale Song - { - RevertFromDisk(); - return; - } - } - - // Invalidate any Trails that contain this song. - // If we find a Trail that contains this song, then it's part of a - // non-fixed entry. So, regenerating the Trail will force different - // songs to be chosen. - FOREACH_ENUM( StepsType,st ) - { - FOREACH_ShownCourseDifficulty( cd ) - { - TrailCache_t::iterator it = m_TrailCache.find( CacheEntry(st, cd) ); - if( it == m_TrailCache.end() ) - continue; - CacheData &cache = it->second; - if( !cache.null ) - if( GetTrail( st, cd )->ContainsSong( pStaleSong ) ) - m_TrailCache.erase( it ); - } - } -} - -void Course::RegenerateNonFixedTrails() const -{ - // Only need to regen Trails if the Course has a random entry. - // We can create these Trails on demand because we don't - // calculate RadarValues for Trails with one or more non-fixed - // entry. - if( AllSongsAreFixed() ) - return; - - FOREACHM( CacheEntry, CacheData, m_TrailCache, e ) - { - const CacheEntry &ce = e->first; - GetTrailForceRegenCache( ce.first, ce.second ); - } -} - -RageColor Course::GetColor() const -{ - // FIXME: Calculate the meter. - int iMeter = 5; - - switch( PREFSMAN->m_CourseSortOrder ) - { - case COURSE_SORT_PREFERRED: - return SORT_PREFERRED_COLOR; //This will also be used for autogen'd courses in some cases. - - case COURSE_SORT_SONGS: - if( m_vEntries.size() >= 7 ) return SORT_LEVEL2_COLOR; - else if( m_vEntries.size() >= 4 ) return SORT_LEVEL4_COLOR; - else return SORT_LEVEL5_COLOR; - - case COURSE_SORT_METER: - if( !AllSongsAreFixed() ) return SORT_LEVEL1_COLOR; - else if( iMeter > 9 ) return SORT_LEVEL2_COLOR; - else if( iMeter >= 7 ) return SORT_LEVEL3_COLOR; - else if( iMeter >= 5 ) return SORT_LEVEL4_COLOR; - else return SORT_LEVEL5_COLOR; - - case COURSE_SORT_METER_SUM: - if( !AllSongsAreFixed() ) return SORT_LEVEL1_COLOR; - if( m_SortOrder_TotalDifficulty >= 40 ) return SORT_LEVEL2_COLOR; - if( m_SortOrder_TotalDifficulty >= 30 ) return SORT_LEVEL3_COLOR; - if( m_SortOrder_TotalDifficulty >= 20 ) return SORT_LEVEL4_COLOR; - else return SORT_LEVEL5_COLOR; - - case COURSE_SORT_RANK: - if( m_SortOrder_Ranking == 3 ) return SORT_LEVEL1_COLOR; - else if( m_SortOrder_Ranking == 2 ) return SORT_LEVEL3_COLOR; - else if( m_SortOrder_Ranking == 1 ) return SORT_LEVEL5_COLOR; - else return SORT_LEVEL4_COLOR; - default: - FAIL_M( ssprintf("Invalid course sort %d.", int(PREFSMAN->m_CourseSortOrder)) ); - return RageColor(1,1,1,1); // white; should never reach here - } -} - -bool Course::GetTotalSeconds( StepsType st, float& fSecondsOut ) const -{ - if( !AllSongsAreFixed() ) - return false; - - Trail* pTrail = GetTrail( st, Difficulty_Medium ); - - fSecondsOut = pTrail->GetLengthSeconds(); - return true; -} - -bool Course::CourseHasBestOrWorst() const -{ - FOREACH_CONST( CourseEntry, m_vEntries, e ) - { - if( e->iChooseIndex == SongSort_MostPlays && e->iChooseIndex != -1 ) - return true; - if( e->iChooseIndex == SongSort_FewestPlays && e->iChooseIndex != -1 ) - return true; - } - - return false; -} - -RString Course::GetBannerPath() const -{ - if( m_sBannerPath.empty() ) - return RString(); - if( m_sBannerPath[0] == '/' ) - return m_sBannerPath; - return Dirname(m_sPath) + m_sBannerPath; -} - -RString Course::GetBackgroundPath() const -{ - if( m_sBackgroundPath.empty() ) - return RString(); - if( m_sBackgroundPath[0] == '/' ) - return m_sBackgroundPath; - return Dirname(m_sPath) + m_sBackgroundPath; -} - -bool Course::HasBanner() const -{ - return GetBannerPath() != "" && IsAFile(GetBannerPath()); -} - -bool Course::HasBackground() const -{ - return GetBackgroundPath() != "" && IsAFile(GetBackgroundPath()); -} - -void Course::UpdateCourseStats( StepsType st ) -{ - m_SortOrder_TotalDifficulty = 0; - - // courses with random/players best-worst songs should go at the end - for(unsigned i = 0; i < m_vEntries.size(); i++) - { - Song *pSong = m_vEntries[i].songID.ToSong(); - if( pSong != NULL ) - continue; - - if ( m_SortOrder_Ranking == 2 ) - m_SortOrder_Ranking = 3; - m_SortOrder_TotalDifficulty = INT_MAX; - return; - } - - const Trail* pTrail = GetTrail( st, Difficulty_Medium ); - - m_SortOrder_TotalDifficulty += pTrail != NULL? pTrail->GetTotalMeter():0; - - // OPTIMIZATION: Ranking info isn't dependent on style, so call it - // sparingly. It's handled on startup and when themes change. - - LOG->Trace("%s: Total feet: %d", - this->m_sMainTitle.c_str(), - m_SortOrder_TotalDifficulty ); -} - -bool Course::IsRanking() const -{ - vector rankingsongs; - - split(THEME->GetMetric("ScreenRanking", "CoursesToShow"), ",", rankingsongs); - - for(unsigned i=0; i < rankingsongs.size(); i++) - if (rankingsongs[i].CompareNoCase(m_sPath)) - return true; - - return false; -} - -const CourseEntry *Course::FindFixedSong( const Song *pSong ) const -{ - FOREACH_CONST( CourseEntry, m_vEntries, e ) - { - const CourseEntry &entry = *e; - Song *lSong = entry.songID.ToSong(); - if( pSong == lSong ) - return &entry; - } - - return NULL; -} - -void Course::GetAllCachedTrails( vector &out ) -{ - TrailCache_t::iterator it; - for( it = m_TrailCache.begin(); it != m_TrailCache.end(); ++it ) - { - CacheData &cd = it->second; - if( !cd.null ) - out.push_back( &cd.trail ); - } -} - -bool Course::ShowInDemonstrationAndRanking() const -{ - // Don't show endless courses in Ranking. - // todo: make this a metric of course types not to show? -aj - return !IsEndless(); -} - -void Course::CalculateRadarValues() -{ - FOREACH_ENUM( StepsType,st ) - { - FOREACH_ENUM( CourseDifficulty,cd ) - { - // For courses that aren't fixed, the radar values are meaningless. - // Makes non-fixed courses have unknown radar values. - if( AllSongsAreFixed() ) - { - Trail *pTrail = GetTrail( st, cd ); - if( pTrail == NULL ) - continue; - RadarValues rv = pTrail->GetRadarValues(); - m_RadarCache[CacheEntry(st, cd)] = rv; - } - else - { - m_RadarCache[CacheEntry(st, cd)] = RadarValues(); - } - } - } -} - -bool Course::Matches( RString sGroup, RString sCourse ) const -{ - if( sGroup.size() && sGroup.CompareNoCase(this->m_sGroupName) != 0) - return false; - - RString sFile = m_sPath; - if( !sFile.empty() ) - { - sFile.Replace("\\","/"); - vector bits; - split( sFile, "/", bits ); - const RString &sLastBit = bits[bits.size()-1]; - if( sCourse.EqualsNoCase(sLastBit) ) - return true; - } - - if( sCourse.EqualsNoCase(this->GetTranslitFullTitle()) ) - return true; - - return false; -} - -// lua start -#include "LuaBinding.h" - -/** @brief Allow Lua to have access to the CourseEntry. */ -class LunaCourseEntry: public Luna -{ -public: - static int GetSong( T* p, lua_State *L ) - { - if( p->songID.ToSong() ) - p->songID.ToSong()->PushSelf(L); - else - lua_pushnil(L); - return 1; - } - DEFINE_METHOD( IsSecret, bSecret ); - DEFINE_METHOD( IsFixedSong, IsFixedSong() ); - DEFINE_METHOD( GetGainSeconds, fGainSeconds ); - DEFINE_METHOD( GetGainLives, iGainLives ); - DEFINE_METHOD( GetNormalModifiers, sModifiers ); - // GetTimedModifiers - table - DEFINE_METHOD( GetNumModChanges, GetNumModChanges() ); - DEFINE_METHOD( GetTextDescription, GetTextDescription() ); - - LunaCourseEntry() - { - ADD_METHOD( GetSong ); - // sm-ssc additions: - ADD_METHOD( IsSecret ); - ADD_METHOD( IsFixedSong ); - ADD_METHOD( GetGainSeconds ); - ADD_METHOD( GetGainLives ); - ADD_METHOD( GetNormalModifiers ); - //ADD_METHOD( GetTimedModifiers ); - ADD_METHOD( GetNumModChanges ); - ADD_METHOD( GetTextDescription ); - } -}; - -LUA_REGISTER_CLASS( CourseEntry ) - -// Now for the Course bindings: -/** @brief Allow Lua to have access to the Course. */ -class LunaCourse: public Luna -{ -public: - DEFINE_METHOD( GetPlayMode, GetPlayMode() ) - static int GetDisplayFullTitle( T* p, lua_State *L ) { lua_pushstring(L, p->GetDisplayFullTitle() ); return 1; } - static int GetTranslitFullTitle( T* p, lua_State *L ) { lua_pushstring(L, p->GetTranslitFullTitle() ); return 1; } - static int HasMods( T* p, lua_State *L ) { lua_pushboolean(L, p->HasMods() ); return 1; } - static int HasTimedMods( T* p, lua_State *L ) { lua_pushboolean( L, p->HasTimedMods() ); return 1; } - DEFINE_METHOD( GetCourseType, GetCourseType() ) - static int GetCourseEntry( T* p, lua_State *L ) { CourseEntry &ce = p->m_vEntries[IArg(1)]; ce.PushSelf(L); return 1; } - static int GetCourseEntries( T* p, lua_State *L ) - { - vector v; - for( unsigned i = 0; i < p->m_vEntries.size(); ++i ) - { - v.push_back(&p->m_vEntries[i]); - } - LuaHelpers::CreateTableFromArray( v, L ); - return 1; - } - static int GetAllTrails( T* p, lua_State *L ) - { - vector v; - p->GetAllTrails( v ); - LuaHelpers::CreateTableFromArray( v, L ); - return 1; - } - static int GetBannerPath( T* p, lua_State *L ) { RString s = p->GetBannerPath(); if( s.empty() ) return 0; LuaHelpers::Push(L, s); return 1; } - static int GetBackgroundPath( T* p, lua_State *L ) { RString s = p->GetBackgroundPath(); if( s.empty() ) return 0; LuaHelpers::Push(L, s); return 1; } - static int GetCourseDir( T* p, lua_State *L ) { lua_pushstring(L, p->m_sPath ); return 1; } - static int GetGroupName( T* p, lua_State *L ) { lua_pushstring(L, p->m_sGroupName ); return 1; } - static int IsAutogen( T* p, lua_State *L ) { lua_pushboolean(L, p->m_bIsAutogen ); return 1; } - static int GetEstimatedNumStages( T* p, lua_State *L ) { lua_pushnumber(L, p->GetEstimatedNumStages() ); return 1; } - static int GetScripter( T* p, lua_State *L ) { lua_pushstring(L, p->m_sScripter ); return 1; } - static int GetDescription( T* p, lua_State *L ) { lua_pushstring(L, p->m_sDescription ); return 1; } - static int GetTotalSeconds( T* p, lua_State *L ) - { - StepsType st = Enum::Check(L, 1); - float fTotalSeconds; - if( !p->GetTotalSeconds(st, fTotalSeconds) ) - lua_pushnil( L ); - else - lua_pushnumber( L, fTotalSeconds ); - return 1; - } - DEFINE_METHOD( IsEndless, IsEndless() ) - DEFINE_METHOD( IsNonstop, IsNonstop() ) - DEFINE_METHOD( IsOni, IsOni() ) - DEFINE_METHOD( GetGoalSeconds, m_fGoalSeconds ) - static int HasBanner( T* p, lua_State *L ) { lua_pushboolean(L, p->HasBanner() ); return 1; } - static int HasBackground( T* p, lua_State *L ) { lua_pushboolean(L, p->HasBackground() ); return 1; } - DEFINE_METHOD( IsAnEdit, IsAnEdit() ) - static int IsPlayableIn( T* p, lua_State *L ) - { - StepsType st = Enum::Check(L, 1); - lua_pushboolean(L, p->IsPlayableIn( st ) ); - return 1; - } - DEFINE_METHOD( IsRanking, IsRanking() ) - DEFINE_METHOD( AllSongsAreFixed, AllSongsAreFixed() ) - - LunaCourse() - { - ADD_METHOD( GetPlayMode ); - ADD_METHOD( GetDisplayFullTitle ); - ADD_METHOD( GetTranslitFullTitle ); - ADD_METHOD( HasMods ); - ADD_METHOD( HasTimedMods ); - ADD_METHOD( GetCourseType ); - ADD_METHOD( GetCourseEntry ); - ADD_METHOD( GetCourseEntries ); - ADD_METHOD( GetAllTrails ); - ADD_METHOD( GetBannerPath ); - ADD_METHOD( GetBackgroundPath ); - ADD_METHOD( GetCourseDir ); - ADD_METHOD( GetGroupName ); - ADD_METHOD( IsAutogen ); - ADD_METHOD( GetEstimatedNumStages ); - ADD_METHOD( GetScripter ); - ADD_METHOD( GetDescription ); - ADD_METHOD( GetTotalSeconds ); - ADD_METHOD( IsEndless ); - ADD_METHOD( IsNonstop ); - ADD_METHOD( IsOni ); - ADD_METHOD( GetGoalSeconds ); - ADD_METHOD( HasBanner ); - ADD_METHOD( HasBackground ); - ADD_METHOD( IsAnEdit ); - ADD_METHOD( IsPlayableIn ); - ADD_METHOD( IsRanking ); - ADD_METHOD( AllSongsAreFixed ); - } -}; - -LUA_REGISTER_CLASS( Course ) -// lua end - - -/* - * (c) 2001-2004 Chris Danford, Glenn Maynard - * 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 +#include "global.h" +#include "Course.h" +#include "CourseLoaderCRS.h" +#include "Foreach.h" +#include "GameManager.h" +#include "GameState.h" +#include "LocalizedString.h" +#include "LuaManager.h" +#include "Preference.h" +#include "PrefsManager.h" +#include "ProfileManager.h" +#include "RageLog.h" +#include "Song.h" +#include "SongCacheIndex.h" +#include "Steps.h" +#include "ThemeManager.h" +#include "UnlockManager.h" +#include "Game.h" +#include "Style.h" + +static Preference MAX_SONGS_IN_EDIT_COURSE( "MaxSongsInEditCourse", -1 ); + +static const char *SongSortNames[] = { + "Randomize", + "MostPlays", + "FewestPlays", + "TopGrades", + "LowestGrades", +}; +XToString( SongSort ); +XToLocalizedString( SongSort ); + + +/* Maximum lower value of ranges when difficult: */ +const int MAX_BOTTOM_RANGE = 10; + +#define SORT_PREFERRED_COLOR THEME->GetMetricC("Course","SortPreferredColor") +#define SORT_LEVEL1_COLOR THEME->GetMetricC("Course","SortLevel1Color") +#define SORT_LEVEL2_COLOR THEME->GetMetricC("Course","SortLevel2Color") +#define SORT_LEVEL3_COLOR THEME->GetMetricC("Course","SortLevel3Color") +#define SORT_LEVEL4_COLOR THEME->GetMetricC("Course","SortLevel4Color") +#define SORT_LEVEL5_COLOR THEME->GetMetricC("Course","SortLevel5Color") + +//#define INCLUDE_BEGINNER_STEPS THEME->GetMetricB( "Course","IncludeBeginnerSteps" ); + +RString CourseEntry::GetTextDescription() const +{ + vector vsEntryDescription; + Song *pSong = songID.ToSong(); + if( pSong ) + vsEntryDescription.push_back( pSong->GetTranslitFullTitle() ); + else + vsEntryDescription.push_back( "Random" ); + if( !songCriteria.m_sGroupName.empty() ) + vsEntryDescription.push_back( songCriteria.m_sGroupName ); + if( songCriteria.m_bUseSongGenreAllowedList ) + vsEntryDescription.push_back( join(",",songCriteria.m_vsSongGenreAllowedList) ); + if( stepsCriteria.m_difficulty != Difficulty_Invalid && stepsCriteria.m_difficulty != Difficulty_Medium ) + vsEntryDescription.push_back( CourseDifficultyToLocalizedString(stepsCriteria.m_difficulty) ); + if( stepsCriteria.m_iLowMeter != -1 ) + vsEntryDescription.push_back( ssprintf("Low meter: %d", stepsCriteria.m_iLowMeter) ); + if( stepsCriteria.m_iHighMeter != -1 ) + vsEntryDescription.push_back( ssprintf("High meter: %d", stepsCriteria.m_iHighMeter) ); + if( songSort != SongSort_Randomize ) + vsEntryDescription.push_back( "Sort: %d" + SongSortToLocalizedString(songSort) ); + if( songSort != SongSort_Randomize && iChooseIndex != 0 ) + vsEntryDescription.push_back( "Choose " + FormatNumberAndSuffix(iChooseIndex) + " match" ); + int iNumModChanges = GetNumModChanges(); + if( iNumModChanges != 0 ) + vsEntryDescription.push_back( ssprintf("%d mod changes", iNumModChanges) ); + if( fGainSeconds != 0 ) + vsEntryDescription.push_back( ssprintf("Low meter: %.0f", fGainSeconds) ); + + RString s = join( ",", vsEntryDescription ); + return s; +} + +int CourseEntry::GetNumModChanges() const +{ + int iNumModChanges = 0; + if( !sModifiers.empty() ) + iNumModChanges++; + iNumModChanges += attacks.size(); + return iNumModChanges; +} + + +Course::Course(): m_bIsAutogen(false), m_sPath(""), m_sMainTitle(""), + m_sMainTitleTranslit(""), m_sSubTitle(""), m_sSubTitleTranslit(""), + m_sScripter(""), m_sDescription(""), m_sBannerPath(""), m_sBackgroundPath(""), + m_sCDTitlePath(""), m_sGroupName(""), m_bRepeat(false), m_fGoalSeconds(0), + m_bShuffle(false), m_iLives(-1), m_bSortByMeter(false), + m_bIncomplete(false), m_vEntries(), m_SortOrder_TotalDifficulty(0), + m_SortOrder_Ranking(0), m_LoadedFromProfile(ProfileSlot_Invalid), + m_TrailCache(), m_iTrailCacheSeed(0), m_RadarCache(), + m_setStyles(), m_CachedObject() +{ + FOREACH_ENUM( Difficulty,dc) + m_iCustomMeter[dc] = -1; +} + +CourseType Course::GetCourseType() const +{ + if( m_bRepeat ) + return COURSE_TYPE_ENDLESS; + if( m_iLives > 0 ) + return COURSE_TYPE_ONI; + if( !m_vEntries.empty() && m_vEntries[0].fGainSeconds > 0 ) + return COURSE_TYPE_SURVIVAL; + return COURSE_TYPE_NONSTOP; +} + +void Course::SetCourseType( CourseType ct ) +{ + if( GetCourseType() == ct ) + return; + + m_bRepeat = false; + m_iLives = -1; + if( !m_vEntries.empty() ) + m_vEntries[0].fGainSeconds = 0; + + switch( ct ) + { + default: + FAIL_M(ssprintf("Invalid course type: %i", ct)); + case COURSE_TYPE_NONSTOP: + break; + case COURSE_TYPE_ONI: + m_iLives = 4; + break; + case COURSE_TYPE_ENDLESS: + m_bRepeat = true; + break; + case COURSE_TYPE_SURVIVAL: + if( !m_vEntries.empty() ) + m_vEntries[0].fGainSeconds = 120; + break; + } +} + +PlayMode Course::GetPlayMode() const +{ + CourseType ct = GetCourseType(); + switch( ct ) + { + case COURSE_TYPE_ENDLESS: return PLAY_MODE_ENDLESS; + case COURSE_TYPE_ONI: return PLAY_MODE_ONI; + case COURSE_TYPE_SURVIVAL: return PLAY_MODE_ONI; + case COURSE_TYPE_NONSTOP: return PLAY_MODE_NONSTOP; + default: + FAIL_M(ssprintf("Invalid course type: %i", ct)); + } +} + +void Course::RevertFromDisk() +{ + // trying to catch invalid an Course + ASSERT( !m_sPath.empty() ); + + CourseLoaderCRS::LoadFromCRSFile( m_sPath, *this ); +} + +RString Course::GetCacheFilePath() const +{ + return SongCacheIndex::GetCacheFilePath( "Courses", m_sPath ); +} + +void Course::Init() +{ + m_bIsAutogen = false; + m_sPath = ""; + + m_sMainTitle = ""; + m_sMainTitleTranslit = ""; + m_sSubTitle = ""; + m_sSubTitleTranslit = ""; + m_sScripter = ""; + m_sDescription = ""; + + m_sBannerPath = ""; + m_sBackgroundPath = ""; + m_sCDTitlePath = ""; + m_sGroupName = ""; + + m_bRepeat = false; + m_fGoalSeconds = 0; + m_bShuffle = false; + + m_iLives = -1; + FOREACH_ENUM( Difficulty,dc) + m_iCustomMeter[dc] = -1; + m_bSortByMeter = false; + + m_vEntries.clear(); + + m_SortOrder_TotalDifficulty = 0; + m_SortOrder_Ranking = 0; + + m_LoadedFromProfile = ProfileSlot_Invalid; + m_bIncomplete = false; + m_TrailCache.clear(); + m_iTrailCacheSeed = 0; + m_RadarCache.clear(); +} + +bool Course::IsPlayableIn( StepsType st ) const +{ + // Stripped down version of GetTrailUnsorted + FOREACH_CONST( CourseEntry, m_vEntries, e ) + { + SongCriteria soc = e->songCriteria; + + Song *pSong = e->songID.ToSong(); + if( pSong ) + { + soc.m_bUseSongAllowedList = true; + soc.m_vpSongAllowedList.push_back( pSong ); + } + soc.m_Tutorial = SongCriteria::Tutorial_No; + soc.m_Locked = SongCriteria::Locked_Unlocked; + if( !soc.m_bUseSongAllowedList ) + soc.m_iMaxStagesForSong = 1; + + StepsCriteria stc = e->stepsCriteria; + stc.m_st = st; + stc.m_Locked = StepsCriteria::Locked_Unlocked; + + const bool bSameSongCriteria = e != m_vEntries.begin() && (e-1)->songCriteria == soc; + const bool bSameStepsCriteria = e != m_vEntries.begin() && (e-1)->stepsCriteria == stc; + + if( pSong ) + { + if( StepsUtil::HasMatching(pSong, stc) ) + return true; + } + else if( !(bSameSongCriteria && bSameStepsCriteria) ) + { + if( StepsUtil::HasMatching(soc, stc) ) + return true; + } + + } + + return false; +} + + +struct SortTrailEntry +{ + TrailEntry entry; + int SortMeter; + + SortTrailEntry(): entry(), SortMeter(0) {} + + bool operator< ( const SortTrailEntry &rhs ) const { return SortMeter < rhs.SortMeter; } +}; + +RString Course::GetDisplayMainTitle() const +{ + if( !PREFSMAN->m_bShowNativeLanguage ) + return GetTranslitMainTitle(); + return m_sMainTitle; +} + +RString Course::GetDisplaySubTitle() const +{ + if( !PREFSMAN->m_bShowNativeLanguage ) + return GetTranslitSubTitle(); + return m_sSubTitle; +} + +RString Course::GetDisplayFullTitle() const +{ + RString sTitle = GetDisplayMainTitle(); + RString sSubTitle = GetDisplaySubTitle(); + + if( !sSubTitle.empty() ) + sTitle += " " + sSubTitle; + return sTitle; +} + +RString Course::GetTranslitFullTitle() const +{ + RString sTitle = GetTranslitMainTitle(); + RString sSubTitle = GetTranslitSubTitle(); + + if( !sSubTitle.empty() ) + sTitle += " " + sSubTitle; + return sTitle; +} + +/* This is called by many simple functions, like Course::GetTotalSeconds, and may + * be called on all songs to sort. It can take time to execute, so we cache the + * results. Returned pointers remain valid for the lifetime of the Course. If the + * course difficulty doesn't exist, NULL is returned. */ +Trail* Course::GetTrail( StepsType st, CourseDifficulty cd ) const +{ + ASSERT( cd != Difficulty_Invalid ); + + // Check to see if the Trail cache is out of date + if( m_iTrailCacheSeed != GAMESTATE->m_iStageSeed ) + { + RegenerateNonFixedTrails(); + m_iTrailCacheSeed = GAMESTATE->m_iStageSeed; + } + + // Look in the Trail cache + { + TrailCache_t::iterator it = m_TrailCache.find( CacheEntry(st, cd) ); + if( it != m_TrailCache.end() ) + { + CacheData &cache = it->second; + if( cache.null ) + return NULL; + return &cache.trail; + } + } + + return GetTrailForceRegenCache( st, cd ); +} + +Trail* Course::GetTrailForceRegenCache( StepsType st, CourseDifficulty cd ) const +{ + // Construct a new Trail, add it to the cache, then return it. + CacheData &cache = m_TrailCache[ CacheEntry(st, cd) ]; + Trail &trail = cache.trail; + trail.Init(); + if( !GetTrailSorted(st, cd, trail) ) + { + // This course difficulty doesn't exist. + cache.null = true; + return NULL; + } + + // If we have cached RadarValues for this trail, insert them. + { + RadarCache_t::const_iterator it = m_RadarCache.find( CacheEntry( st, cd ) ); + if( it != m_RadarCache.end() ) + { + const RadarValues &rv = it->second; + trail.SetRadarValues(rv); + } + } + + cache.null = false; + return &cache.trail; +} + +bool Course::GetTrailSorted( StepsType st, CourseDifficulty cd, Trail &trail ) const +{ + if( !GetTrailUnsorted( st, cd, trail ) ) + return false; + + if( this->m_bSortByMeter ) + { + /* Sort according to Difficulty_Medium, since the order of songs + * must not change across difficulties. */ + Trail SortTrail; + if( cd == Difficulty_Medium ) + SortTrail = trail; + else + { + bool bOK = GetTrailUnsorted( st, Difficulty_Medium, SortTrail ); + + /* If we have any other difficulty, we must have Difficulty_Medium. */ + ASSERT( bOK ); + } + ASSERT_M( trail.m_vEntries.size() == SortTrail.m_vEntries.size(), + ssprintf("%i %i", int(trail.m_vEntries.size()), int(SortTrail.m_vEntries.size())) ); + + vector entries; + for( unsigned i = 0; i < trail.m_vEntries.size(); ++i ) + { + SortTrailEntry ste; + ste.entry = trail.m_vEntries[i]; + ste.SortMeter = SortTrail.m_vEntries[i].pSteps->GetMeter(); + entries.push_back( ste ); + } + + stable_sort( entries.begin(), entries.end() ); + for( unsigned i = 0; i < trail.m_vEntries.size(); ++i ) + trail.m_vEntries[i] = entries[i].entry; + } + + if( trail.m_vEntries.empty() ) + return false; + return true; +} + +// TODO: Move Course initialization after PROFILEMAN is created +static void CourseSortSongs( SongSort sort, vector &vpPossibleSongs, RandomGen &rnd ) +{ + switch( sort ) + { + DEFAULT_FAIL(sort); + case SongSort_Randomize: + random_shuffle( vpPossibleSongs.begin(), vpPossibleSongs.end(), rnd ); + break; + case SongSort_MostPlays: + if( PROFILEMAN ) + SongUtil::SortSongPointerArrayByNumPlays( vpPossibleSongs, PROFILEMAN->GetMachineProfile(), true ); // descending + break; + case SongSort_FewestPlays: + if( PROFILEMAN ) + SongUtil::SortSongPointerArrayByNumPlays( vpPossibleSongs, PROFILEMAN->GetMachineProfile(), false ); // ascending + break; + case SongSort_TopGrades: + if( PROFILEMAN ) + SongUtil::SortSongPointerArrayByGrades( vpPossibleSongs, true ); // descending + break; + case SongSort_LowestGrades: + if( PROFILEMAN ) + SongUtil::SortSongPointerArrayByGrades( vpPossibleSongs, false ); // ascending + break; + } +} + +namespace +{ + struct SongIsEqual + { + const Song *m_pSong; + SongIsEqual( const Song *pSong ) : m_pSong(pSong) { } + bool operator()( const SongAndSteps &sas ) const { return sas.pSong == m_pSong; } + }; +} + +bool Course::GetTrailUnsorted( StepsType st, CourseDifficulty cd, Trail &trail ) const +{ + trail.Init(); + + // XXX: Why are beginner and challenge excluded here? -Wolfman2000 + switch( cd ) + { + case Difficulty_Beginner: + return false; + case Difficulty_Challenge: + return false; + default: break; + } + + // Construct a new Trail, add it to the cache, then return it. + // Different seed for each course, but the same for the whole round: + RandomGen rnd( GAMESTATE->m_iStageSeed + GetHashForString(m_sMainTitle) ); + + vector tmp_entries; + if( m_bShuffle ) + { + /* Always randomize the same way per round. Otherwise, the displayed course + * will change every time it's viewed, and the displayed order will have no + * bearing on what you'll actually play. */ + tmp_entries = m_vEntries; + random_shuffle( tmp_entries.begin(), tmp_entries.end(), rnd ); + } + + const vector &entries = m_bShuffle ? tmp_entries:m_vEntries; + + // This can take some time, so don't fill it out unless we need it. + vector vSongsByMostPlayed; + vector AllSongsShuffled; + + trail.m_StepsType = st; + trail.m_CourseType = GetCourseType(); + trail.m_CourseDifficulty = cd; + + // Set to true if CourseDifficulty is able to change something. + bool bCourseDifficultyIsSignificant = (cd == Difficulty_Medium); + + vector vpAllPossibleSongs; + vector vSongAndSteps; + + // Resolve each entry to a Song and Steps. + FOREACH_CONST( CourseEntry, entries, e ) + { + SongAndSteps resolved; // fill this in + SongCriteria soc = e->songCriteria; + + Song *pSong = e->songID.ToSong(); + if( pSong ) + { + soc.m_bUseSongAllowedList = true; + soc.m_vpSongAllowedList.push_back( pSong ); + } + soc.m_Tutorial = SongCriteria::Tutorial_No; + soc.m_Locked = SongCriteria::Locked_Unlocked; + if( !soc.m_bUseSongAllowedList ) + soc.m_iMaxStagesForSong = 1; + + StepsCriteria stc = e->stepsCriteria; + stc.m_st = st; + stc.m_Locked = StepsCriteria::Locked_Unlocked; + + const bool bSameSongCriteria = e != entries.begin() && (e-1)->songCriteria == soc; + const bool bSameStepsCriteria = e != entries.begin() && (e-1)->stepsCriteria == stc; + + if( pSong ) + { + StepsUtil::GetAllMatching( pSong, stc, vSongAndSteps ); + } + else if( vSongAndSteps.empty() || !(bSameSongCriteria && bSameStepsCriteria) ) + { + vSongAndSteps.clear(); + StepsUtil::GetAllMatching( soc, stc, vSongAndSteps ); + } + + // It looks bad to have the same song 2x in a row in a randomly generated course. + // Don't allow the same song to be played 2x in a row, unless there's only + // one song in vpPossibleSongs. + if( trail.m_vEntries.size() > 0 && vSongAndSteps.size() > 1 ) + { + const TrailEntry &teLast = trail.m_vEntries.back(); + RemoveIf( vSongAndSteps, SongIsEqual(teLast.pSong) ); + } + + // if there are no songs to choose from, abort this CourseEntry + if( vSongAndSteps.empty() ) + continue; + + vector vpSongs; + typedef vector StepsVector; + map mapSongToSteps; + FOREACH_CONST( SongAndSteps, vSongAndSteps, sas ) + { + StepsVector &v = mapSongToSteps[sas->pSong]; + + v.push_back( sas->pSteps ); + if( v.size() == 1 ) + vpSongs.push_back( sas->pSong ); + } + + CourseSortSongs( e->songSort, vpSongs, rnd ); + + ASSERT( e->iChooseIndex >= 0 ); + if( e->iChooseIndex < int(vSongAndSteps.size()) ) + { + resolved.pSong = vpSongs[e->iChooseIndex]; + const vector &mappedSongs = mapSongToSteps[resolved.pSong]; + resolved.pSteps = mappedSongs[ RandomInt(mappedSongs.size()) ]; + } + else + { + continue; + } + + /* If we're not COURSE_DIFFICULTY_REGULAR, then we should be choosing steps that are + * either easier or harder than the base difficulty. If no such steps exist, then + * just use the one we already have. */ + Difficulty dc = resolved.pSteps->GetDifficulty(); + int iLowMeter = e->stepsCriteria.m_iLowMeter; + int iHighMeter = e->stepsCriteria.m_iHighMeter; + if( cd != Difficulty_Medium && !e->bNoDifficult ) + { + Difficulty new_dc = (Difficulty)(dc + cd - Difficulty_Medium); + new_dc = clamp( new_dc, (Difficulty)0, (Difficulty)(Difficulty_Edit-1) ); + /* + // re-edit this code to work using the metric. + Difficulty new_dc; + if( INCLUDE_BEGINNER_STEPS ) + { + // don't factor in the course difficulty if we're including + // beginner steps -aj + new_dc = clamp( dc, Difficulty_Beginner, (Difficulty)(Difficulty_Edit-1) ); + } + else + { + new_dc = (Difficulty)(dc + cd - Difficulty_Medium); + new_dc = clamp( new_dc, (Difficulty)0, (Difficulty)(Difficulty_Edit-1) ); + } + */ + + bool bChangedDifficulty = false; + if( new_dc != dc ) + { + Steps* pNewSteps = SongUtil::GetStepsByDifficulty( resolved.pSong, st, new_dc ); + if( pNewSteps ) + { + dc = new_dc; + resolved.pSteps = pNewSteps; + bChangedDifficulty = true; + bCourseDifficultyIsSignificant = true; + } + } + + /* Hack: We used to adjust low_meter/high_meter above while searching for + * songs. However, that results in a different song being chosen for + * difficult courses, which is bad when LockCourseDifficulties is disabled; + * each player can end up with a different song. Instead, choose based + * on the original range, bump the steps based on course difficulty, and + * then retroactively tweak the low_meter/high_meter so course displays + * line up. */ + if( e->stepsCriteria.m_difficulty == Difficulty_Invalid && bChangedDifficulty ) + { + /* Minimum and maximum to add to make the meter range contain the actual + * meter: */ + int iMinDist = resolved.pSteps->GetMeter() - iHighMeter; + int iMaxDist = resolved.pSteps->GetMeter() - iLowMeter; + + /* Clamp the possible adjustments to try to avoid going under 1 or over + * MAX_BOTTOM_RANGE. */ + iMinDist = min( max( iMinDist, -iLowMeter+1 ), iMaxDist ); + iMaxDist = max( min( iMaxDist, MAX_BOTTOM_RANGE-iHighMeter ), iMinDist ); + + int iAdd; + if( iMaxDist == iMinDist ) + iAdd = iMaxDist; + else + iAdd = rnd(iMaxDist-iMinDist) + iMinDist; + iLowMeter += iAdd; + iHighMeter += iAdd; + } + } + + TrailEntry te; + te.pSong = resolved.pSong; + te.pSteps = resolved.pSteps; + te.Modifiers = e->sModifiers; + te.Attacks = e->attacks; + te.bSecret = e->bSecret; + te.iLowMeter = iLowMeter; + te.iHighMeter = iHighMeter; + + /* If we chose based on meter (not difficulty), then store Difficulty_Invalid, so + * other classes can tell that we used meter. */ + if( e->stepsCriteria.m_difficulty == Difficulty_Invalid ) + { + te.dc = Difficulty_Invalid; + } + else + { + /* Otherwise, store the actual difficulty we got (post-course-difficulty). + * This may or may not be the same as e.difficulty. */ + te.dc = dc; + } + trail.m_vEntries.push_back( te ); + + // LOG->Trace( "Chose: %s, %d", te.pSong->GetSongDir().c_str(), te.pSteps->GetMeter() ); + + if( IsAnEdit() && MAX_SONGS_IN_EDIT_COURSE > 0 && + int(trail.m_vEntries.size()) >= MAX_SONGS_IN_EDIT_COURSE ) + { + break; + } + } + + /* Hack: If any entry was non-FIXED, or m_bShuffle is set, then radar values + * for this trail will be meaningless as they'll change every time. Pre-cache + * empty data. XXX: How can we do this cleanly, without propagating lots of + * otherwise unnecessary data (course entry types, m_bShuffle) to Trail, or + * storing a Course pointer in Trail (yuck)? */ + if( !AllSongsAreFixed() || m_bShuffle ) + { + trail.m_bRadarValuesCached = true; + trail.m_CachedRadarValues = RadarValues(); + } + + /* If we have a manually-entered meter for this difficulty, use it. */ + if( m_iCustomMeter[cd] != -1 ) + trail.m_iSpecifiedMeter = m_iCustomMeter[cd]; + + /* If the course difficulty never actually changed anything, then this difficulty + * is equivalent to Difficulty_Medium; it doesn't exist. */ + return bCourseDifficultyIsSignificant; +} + +void Course::GetTrails( vector &AddTo, StepsType st ) const +{ + FOREACH_ShownCourseDifficulty( cd ) + { + Trail *pTrail = GetTrail( st, cd ); + if( pTrail == NULL ) + continue; + AddTo.push_back( pTrail ); + } +} + +void Course::GetAllTrails( vector &AddTo ) const +{ + vector vStepsTypesToShow; + GAMEMAN->GetStepsTypesForGame( GAMESTATE->m_pCurGame, vStepsTypesToShow ); + FOREACH( StepsType, vStepsTypesToShow, st ) + { + GetTrails( AddTo, *st ); + } +} + +int Course::GetMeter( StepsType st, CourseDifficulty cd ) const +{ + if( m_iCustomMeter[cd] != -1 ) + return m_iCustomMeter[cd]; + const Trail* pTrail = GetTrail( st ); + if( pTrail != NULL ) + return pTrail->GetMeter(); + return 0; +} + +bool Course::HasMods() const +{ + FOREACH_CONST( CourseEntry, m_vEntries, e ) + { + if( !e->attacks.empty() ) + return true; + } + + return false; +} + +bool Course::HasTimedMods() const +{ + // What makes this different from the SM4 implementation is that + // HasTimedMods now searches for bGlobal in the attacks; if one of + // them is false, it has timed mods. Also returning false will probably + // take longer than expected. -aj + FOREACH_CONST( CourseEntry, m_vEntries, e ) + { + if( !e->attacks.empty() ) + { + for( unsigned s=0; s < e->attacks.size(); s++ ) + { + const Attack attack = e->attacks[s]; + if(!attack.bGlobal) + return true; + } + } + } + return false; +} + +bool Course::AllSongsAreFixed() const +{ + FOREACH_CONST( CourseEntry, m_vEntries, e ) + { + if( !e->IsFixedSong() ) + return false; + } + return true; +} + +const Style *Course::GetCourseStyle( const Game *pGame, int iNumPlayers ) const +{ + vector vpStyles; + GAMEMAN->GetCompatibleStyles( pGame, iNumPlayers, vpStyles ); + + for( int s=0; s < (int) vpStyles.size(); ++s ) + { + const Style *pStyle = vpStyles[s]; + for (RString const &style : m_setStyles) + { + if( !style.CompareNoCase(pStyle->m_szName) ) + return pStyle; + } + } + return NULL; +} + +void Course::InvalidateTrailCache() +{ + m_TrailCache.clear(); +} + +void Course::Invalidate( const Song *pStaleSong ) +{ + FOREACH_CONST( CourseEntry, m_vEntries, e ) + { + Song *pSong = e->songID.ToSong(); + if( pSong == pStaleSong ) // a fixed entry that references the stale Song + { + RevertFromDisk(); + return; + } + } + + // Invalidate any Trails that contain this song. + // If we find a Trail that contains this song, then it's part of a + // non-fixed entry. So, regenerating the Trail will force different + // songs to be chosen. + FOREACH_ENUM( StepsType,st ) + { + FOREACH_ShownCourseDifficulty( cd ) + { + TrailCache_t::iterator it = m_TrailCache.find( CacheEntry(st, cd) ); + if( it == m_TrailCache.end() ) + continue; + CacheData &cache = it->second; + if( !cache.null ) + if( GetTrail( st, cd )->ContainsSong( pStaleSong ) ) + m_TrailCache.erase( it ); + } + } +} + +void Course::RegenerateNonFixedTrails() const +{ + // Only need to regen Trails if the Course has a random entry. + // We can create these Trails on demand because we don't + // calculate RadarValues for Trails with one or more non-fixed + // entry. + if( AllSongsAreFixed() ) + return; + + FOREACHM( CacheEntry, CacheData, m_TrailCache, e ) + { + const CacheEntry &ce = e->first; + GetTrailForceRegenCache( ce.first, ce.second ); + } +} + +RageColor Course::GetColor() const +{ + // FIXME: Calculate the meter. + int iMeter = 5; + + switch( PREFSMAN->m_CourseSortOrder ) + { + case COURSE_SORT_PREFERRED: + return SORT_PREFERRED_COLOR; //This will also be used for autogen'd courses in some cases. + + case COURSE_SORT_SONGS: + if( m_vEntries.size() >= 7 ) return SORT_LEVEL2_COLOR; + else if( m_vEntries.size() >= 4 ) return SORT_LEVEL4_COLOR; + else return SORT_LEVEL5_COLOR; + + case COURSE_SORT_METER: + if( !AllSongsAreFixed() ) return SORT_LEVEL1_COLOR; + else if( iMeter > 9 ) return SORT_LEVEL2_COLOR; + else if( iMeter >= 7 ) return SORT_LEVEL3_COLOR; + else if( iMeter >= 5 ) return SORT_LEVEL4_COLOR; + else return SORT_LEVEL5_COLOR; + + case COURSE_SORT_METER_SUM: + if( !AllSongsAreFixed() ) return SORT_LEVEL1_COLOR; + if( m_SortOrder_TotalDifficulty >= 40 ) return SORT_LEVEL2_COLOR; + if( m_SortOrder_TotalDifficulty >= 30 ) return SORT_LEVEL3_COLOR; + if( m_SortOrder_TotalDifficulty >= 20 ) return SORT_LEVEL4_COLOR; + else return SORT_LEVEL5_COLOR; + + case COURSE_SORT_RANK: + if( m_SortOrder_Ranking == 3 ) return SORT_LEVEL1_COLOR; + else if( m_SortOrder_Ranking == 2 ) return SORT_LEVEL3_COLOR; + else if( m_SortOrder_Ranking == 1 ) return SORT_LEVEL5_COLOR; + else return SORT_LEVEL4_COLOR; + default: + FAIL_M( ssprintf("Invalid course sort %d.", int(PREFSMAN->m_CourseSortOrder)) ); + return RageColor(1,1,1,1); // white; should never reach here + } +} + +bool Course::GetTotalSeconds( StepsType st, float& fSecondsOut ) const +{ + if( !AllSongsAreFixed() ) + return false; + + Trail* pTrail = GetTrail( st, Difficulty_Medium ); + + fSecondsOut = pTrail->GetLengthSeconds(); + return true; +} + +bool Course::CourseHasBestOrWorst() const +{ + FOREACH_CONST( CourseEntry, m_vEntries, e ) + { + if( e->iChooseIndex == SongSort_MostPlays && e->iChooseIndex != -1 ) + return true; + if( e->iChooseIndex == SongSort_FewestPlays && e->iChooseIndex != -1 ) + return true; + } + + return false; +} + +RString Course::GetBannerPath() const +{ + if( m_sBannerPath.empty() ) + return RString(); + if( m_sBannerPath[0] == '/' ) + return m_sBannerPath; + return Dirname(m_sPath) + m_sBannerPath; +} + +RString Course::GetBackgroundPath() const +{ + if( m_sBackgroundPath.empty() ) + return RString(); + if( m_sBackgroundPath[0] == '/' ) + return m_sBackgroundPath; + return Dirname(m_sPath) + m_sBackgroundPath; +} + +bool Course::HasBanner() const +{ + return GetBannerPath() != "" && IsAFile(GetBannerPath()); +} + +bool Course::HasBackground() const +{ + return GetBackgroundPath() != "" && IsAFile(GetBackgroundPath()); +} + +void Course::UpdateCourseStats( StepsType st ) +{ + m_SortOrder_TotalDifficulty = 0; + + // courses with random/players best-worst songs should go at the end + for(unsigned i = 0; i < m_vEntries.size(); i++) + { + Song *pSong = m_vEntries[i].songID.ToSong(); + if( pSong != NULL ) + continue; + + if ( m_SortOrder_Ranking == 2 ) + m_SortOrder_Ranking = 3; + m_SortOrder_TotalDifficulty = INT_MAX; + return; + } + + const Trail* pTrail = GetTrail( st, Difficulty_Medium ); + + m_SortOrder_TotalDifficulty += pTrail != NULL? pTrail->GetTotalMeter():0; + + // OPTIMIZATION: Ranking info isn't dependent on style, so call it + // sparingly. It's handled on startup and when themes change. + + LOG->Trace("%s: Total feet: %d", + this->m_sMainTitle.c_str(), + m_SortOrder_TotalDifficulty ); +} + +bool Course::IsRanking() const +{ + vector rankingsongs; + + split(THEME->GetMetric("ScreenRanking", "CoursesToShow"), ",", rankingsongs); + + for(unsigned i=0; i < rankingsongs.size(); i++) + if (rankingsongs[i].CompareNoCase(m_sPath)) + return true; + + return false; +} + +const CourseEntry *Course::FindFixedSong( const Song *pSong ) const +{ + FOREACH_CONST( CourseEntry, m_vEntries, e ) + { + const CourseEntry &entry = *e; + Song *lSong = entry.songID.ToSong(); + if( pSong == lSong ) + return &entry; + } + + return NULL; +} + +void Course::GetAllCachedTrails( vector &out ) +{ + TrailCache_t::iterator it; + for( it = m_TrailCache.begin(); it != m_TrailCache.end(); ++it ) + { + CacheData &cd = it->second; + if( !cd.null ) + out.push_back( &cd.trail ); + } +} + +bool Course::ShowInDemonstrationAndRanking() const +{ + // Don't show endless courses in Ranking. + // todo: make this a metric of course types not to show? -aj + return !IsEndless(); +} + +void Course::CalculateRadarValues() +{ + FOREACH_ENUM( StepsType,st ) + { + FOREACH_ENUM( CourseDifficulty,cd ) + { + // For courses that aren't fixed, the radar values are meaningless. + // Makes non-fixed courses have unknown radar values. + if( AllSongsAreFixed() ) + { + Trail *pTrail = GetTrail( st, cd ); + if( pTrail == NULL ) + continue; + RadarValues rv = pTrail->GetRadarValues(); + m_RadarCache[CacheEntry(st, cd)] = rv; + } + else + { + m_RadarCache[CacheEntry(st, cd)] = RadarValues(); + } + } + } +} + +bool Course::Matches( RString sGroup, RString sCourse ) const +{ + if( sGroup.size() && sGroup.CompareNoCase(this->m_sGroupName) != 0) + return false; + + RString sFile = m_sPath; + if( !sFile.empty() ) + { + sFile.Replace("\\","/"); + vector bits; + split( sFile, "/", bits ); + const RString &sLastBit = bits[bits.size()-1]; + if( sCourse.EqualsNoCase(sLastBit) ) + return true; + } + + if( sCourse.EqualsNoCase(this->GetTranslitFullTitle()) ) + return true; + + return false; +} + +// lua start +#include "LuaBinding.h" + +/** @brief Allow Lua to have access to the CourseEntry. */ +class LunaCourseEntry: public Luna +{ +public: + static int GetSong( T* p, lua_State *L ) + { + if( p->songID.ToSong() ) + p->songID.ToSong()->PushSelf(L); + else + lua_pushnil(L); + return 1; + } + DEFINE_METHOD( IsSecret, bSecret ); + DEFINE_METHOD( IsFixedSong, IsFixedSong() ); + DEFINE_METHOD( GetGainSeconds, fGainSeconds ); + DEFINE_METHOD( GetGainLives, iGainLives ); + DEFINE_METHOD( GetNormalModifiers, sModifiers ); + // GetTimedModifiers - table + DEFINE_METHOD( GetNumModChanges, GetNumModChanges() ); + DEFINE_METHOD( GetTextDescription, GetTextDescription() ); + + LunaCourseEntry() + { + ADD_METHOD( GetSong ); + // sm-ssc additions: + ADD_METHOD( IsSecret ); + ADD_METHOD( IsFixedSong ); + ADD_METHOD( GetGainSeconds ); + ADD_METHOD( GetGainLives ); + ADD_METHOD( GetNormalModifiers ); + //ADD_METHOD( GetTimedModifiers ); + ADD_METHOD( GetNumModChanges ); + ADD_METHOD( GetTextDescription ); + } +}; + +LUA_REGISTER_CLASS( CourseEntry ) + +// Now for the Course bindings: +/** @brief Allow Lua to have access to the Course. */ +class LunaCourse: public Luna +{ +public: + DEFINE_METHOD( GetPlayMode, GetPlayMode() ) + static int GetDisplayFullTitle( T* p, lua_State *L ) { lua_pushstring(L, p->GetDisplayFullTitle() ); return 1; } + static int GetTranslitFullTitle( T* p, lua_State *L ) { lua_pushstring(L, p->GetTranslitFullTitle() ); return 1; } + static int HasMods( T* p, lua_State *L ) { lua_pushboolean(L, p->HasMods() ); return 1; } + static int HasTimedMods( T* p, lua_State *L ) { lua_pushboolean( L, p->HasTimedMods() ); return 1; } + DEFINE_METHOD( GetCourseType, GetCourseType() ) + static int GetCourseEntry( T* p, lua_State *L ) { CourseEntry &ce = p->m_vEntries[IArg(1)]; ce.PushSelf(L); return 1; } + static int GetCourseEntries( T* p, lua_State *L ) + { + vector v; + for( unsigned i = 0; i < p->m_vEntries.size(); ++i ) + { + v.push_back(&p->m_vEntries[i]); + } + LuaHelpers::CreateTableFromArray( v, L ); + return 1; + } + static int GetAllTrails( T* p, lua_State *L ) + { + vector v; + p->GetAllTrails( v ); + LuaHelpers::CreateTableFromArray( v, L ); + return 1; + } + static int GetBannerPath( T* p, lua_State *L ) { RString s = p->GetBannerPath(); if( s.empty() ) return 0; LuaHelpers::Push(L, s); return 1; } + static int GetBackgroundPath( T* p, lua_State *L ) { RString s = p->GetBackgroundPath(); if( s.empty() ) return 0; LuaHelpers::Push(L, s); return 1; } + static int GetCourseDir( T* p, lua_State *L ) { lua_pushstring(L, p->m_sPath ); return 1; } + static int GetGroupName( T* p, lua_State *L ) { lua_pushstring(L, p->m_sGroupName ); return 1; } + static int IsAutogen( T* p, lua_State *L ) { lua_pushboolean(L, p->m_bIsAutogen ); return 1; } + static int GetEstimatedNumStages( T* p, lua_State *L ) { lua_pushnumber(L, p->GetEstimatedNumStages() ); return 1; } + static int GetScripter( T* p, lua_State *L ) { lua_pushstring(L, p->m_sScripter ); return 1; } + static int GetDescription( T* p, lua_State *L ) { lua_pushstring(L, p->m_sDescription ); return 1; } + static int GetTotalSeconds( T* p, lua_State *L ) + { + StepsType st = Enum::Check(L, 1); + float fTotalSeconds; + if( !p->GetTotalSeconds(st, fTotalSeconds) ) + lua_pushnil( L ); + else + lua_pushnumber( L, fTotalSeconds ); + return 1; + } + DEFINE_METHOD( IsEndless, IsEndless() ) + DEFINE_METHOD( IsNonstop, IsNonstop() ) + DEFINE_METHOD( IsOni, IsOni() ) + DEFINE_METHOD( GetGoalSeconds, m_fGoalSeconds ) + static int HasBanner( T* p, lua_State *L ) { lua_pushboolean(L, p->HasBanner() ); return 1; } + static int HasBackground( T* p, lua_State *L ) { lua_pushboolean(L, p->HasBackground() ); return 1; } + DEFINE_METHOD( IsAnEdit, IsAnEdit() ) + static int IsPlayableIn( T* p, lua_State *L ) + { + StepsType st = Enum::Check(L, 1); + lua_pushboolean(L, p->IsPlayableIn( st ) ); + return 1; + } + DEFINE_METHOD( IsRanking, IsRanking() ) + DEFINE_METHOD( AllSongsAreFixed, AllSongsAreFixed() ) + + LunaCourse() + { + ADD_METHOD( GetPlayMode ); + ADD_METHOD( GetDisplayFullTitle ); + ADD_METHOD( GetTranslitFullTitle ); + ADD_METHOD( HasMods ); + ADD_METHOD( HasTimedMods ); + ADD_METHOD( GetCourseType ); + ADD_METHOD( GetCourseEntry ); + ADD_METHOD( GetCourseEntries ); + ADD_METHOD( GetAllTrails ); + ADD_METHOD( GetBannerPath ); + ADD_METHOD( GetBackgroundPath ); + ADD_METHOD( GetCourseDir ); + ADD_METHOD( GetGroupName ); + ADD_METHOD( IsAutogen ); + ADD_METHOD( GetEstimatedNumStages ); + ADD_METHOD( GetScripter ); + ADD_METHOD( GetDescription ); + ADD_METHOD( GetTotalSeconds ); + ADD_METHOD( IsEndless ); + ADD_METHOD( IsNonstop ); + ADD_METHOD( IsOni ); + ADD_METHOD( GetGoalSeconds ); + ADD_METHOD( HasBanner ); + ADD_METHOD( HasBackground ); + ADD_METHOD( IsAnEdit ); + ADD_METHOD( IsPlayableIn ); + ADD_METHOD( IsRanking ); + ADD_METHOD( AllSongsAreFixed ); + } +}; + +LUA_REGISTER_CLASS( Course ) +// lua end + + +/* + * (c) 2001-2004 Chris Danford, Glenn Maynard + * 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. + */ From 46d3095877616e3fbcf54dffc5e81ad662d20a1e Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:21:08 -0400 Subject: [PATCH 007/281] Replace index version with for each version. I was already in this file: may as well. --- src/Course.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Course.cpp b/src/Course.cpp index 212b9ce5a8..75d32fce14 100644 --- a/src/Course.cpp +++ b/src/Course.cpp @@ -742,9 +742,8 @@ const Style *Course::GetCourseStyle( const Game *pGame, int iNumPlayers ) const vector vpStyles; GAMEMAN->GetCompatibleStyles( pGame, iNumPlayers, vpStyles ); - for( int s=0; s < (int) vpStyles.size(); ++s ) + for (Style const *pStyle : vpStyles) { - const Style *pStyle = vpStyles[s]; for (RString const &style : m_setStyles) { if( !style.CompareNoCase(pStyle->m_szName) ) From af5f4be15b659b51398761f4dbab3915a9d5401b Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:23:27 -0400 Subject: [PATCH 008/281] Handle the Preference for each set loops. --- src/Preference.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Preference.cpp b/src/Preference.cpp index 50792ed0b5..7929ec6ed0 100644 --- a/src/Preference.cpp +++ b/src/Preference.cpp @@ -34,29 +34,29 @@ IPreference *IPreference::GetPreferenceByName( const RString &sName ) void IPreference::LoadAllDefaults() { - FOREACHS_CONST( IPreference*, *m_Subscribers.m_pSubscribers, p ) - (*p)->LoadDefault(); + for (IPreference *p : *m_Subscribers.m_pSubscribers) + p->LoadDefault(); } void IPreference::ReadAllPrefsFromNode( const XNode* pNode, bool bIsStatic ) { ASSERT( pNode != NULL ); - FOREACHS_CONST( IPreference*, *m_Subscribers.m_pSubscribers, p ) - (*p)->ReadFrom( pNode, bIsStatic ); + for (IPreference *p : *m_Subscribers.m_pSubscribers) + p->ReadFrom( pNode, bIsStatic ); } void IPreference::SavePrefsToNode( XNode* pNode ) { - FOREACHS_CONST( IPreference*, *m_Subscribers.m_pSubscribers, p ) - (*p)->WriteTo( pNode ); + for (IPreference *p : *m_Subscribers.m_pSubscribers) + p->WriteTo( pNode ); } void IPreference::ReadAllDefaultsFromNode( const XNode* pNode ) { if( pNode == NULL ) return; - FOREACHS_CONST( IPreference*, *m_Subscribers.m_pSubscribers, p ) - (*p)->ReadDefaultFrom( pNode ); + for (IPreference *p : *m_Subscribers.m_pSubscribers) + p->ReadDefaultFrom( pNode ); } void IPreference::PushValue( lua_State *L ) const From eac4dc231d507e011de62d3167c328ba03e62a11 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:29:34 -0400 Subject: [PATCH 009/281] Handle the set loop here. --- src/Profile.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Profile.cpp b/src/Profile.cpp index 066a2f77f3..eb2c09ba04 100644 --- a/src/Profile.cpp +++ b/src/Profile.cpp @@ -1073,14 +1073,13 @@ XNode* Profile::SaveGeneralDataCreateNode() const { XNode* pUnlocks = pGeneralDataNode->AppendChild("Unlocks"); - FOREACHS_CONST( RString, m_UnlockedEntryIDs, it ) + for (RString const unlockEntry : m_UnlockedEntryIDs) { XNode *pEntry = pUnlocks->AppendChild("UnlockEntry"); - RString sUnlockEntry = it->c_str(); - pEntry->AppendAttr( "UnlockEntryID", sUnlockEntry ); + pEntry->AppendAttr( "UnlockEntryID", unlockEntry ); if( !UNLOCK_AUTH_STRING.GetValue().empty() ) { - RString sUnlockAuth = BinaryToHex( CRYPTMAN->GetMD5ForString(sUnlockEntry + UNLOCK_AUTH_STRING.GetValue()) ); + RString sUnlockAuth = BinaryToHex( CRYPTMAN->GetMD5ForString(unlockEntry + UNLOCK_AUTH_STRING.GetValue()) ); pEntry->AppendAttr( "Auth", sUnlockAuth ); } } From 4cff3766aa047aca5efd676344df050b5b9aa93f Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:31:47 -0400 Subject: [PATCH 010/281] Set loop (plus white space) --- src/SongUtil.cpp | 2401 +++++++++++++++++++++++----------------------- 1 file changed, 1200 insertions(+), 1201 deletions(-) diff --git a/src/SongUtil.cpp b/src/SongUtil.cpp index eb57e74288..2145f43e61 100644 --- a/src/SongUtil.cpp +++ b/src/SongUtil.cpp @@ -1,1201 +1,1200 @@ -#include "global.h" -#include "SongUtil.h" -#include "Song.h" -#include "Steps.h" -#include "GameState.h" -#include "Style.h" -#include "ProfileManager.h" -#include "Profile.h" -#include "PrefsManager.h" -#include "SongManager.h" -#include "XmlFile.h" -#include "Foreach.h" -#include "UnlockManager.h" -#include "ThemeMetric.h" -#include "LocalizedString.h" -#include "RageLog.h" -#include "GameManager.h" -#include "CommonMetrics.h" -#include "LuaBinding.h" -#include "EnumHelper.h" - -ThemeMetric SORT_BPM_DIVISION ( "MusicWheel", "SortBPMDivision" ); -ThemeMetric SORT_LENGTH_DIVISION ( "MusicWheel", "SortLengthDivision" ); -ThemeMetric SHOW_SECTIONS_IN_BPM_SORT ( "MusicWheel", "ShowSectionsInBPMSort" ); -ThemeMetric SHOW_SECTIONS_IN_LENGTH_SORT ( "MusicWheel", "ShowSectionsInLengthSort" ); - -bool SongCriteria::Matches( const Song *pSong ) const -{ - if( !m_sGroupName.empty() && m_sGroupName != pSong->m_sGroupName ) - return false; - - if( UNLOCKMAN->SongIsLocked(pSong) & LOCKED_DISABLED ) - return false; - - if( m_bUseSongGenreAllowedList ) - { - if( find(m_vsSongGenreAllowedList.begin(),m_vsSongGenreAllowedList.end(),pSong->m_sGenre) == m_vsSongGenreAllowedList.end() ) - return false; - } - - switch( m_Selectable ) - { - DEFAULT_FAIL(m_Selectable); - case Selectable_Yes: - if( UNLOCKMAN && UNLOCKMAN->SongIsLocked(pSong) & LOCKED_SELECTABLE ) - return false; - break; - case Selectable_No: - if( UNLOCKMAN && !(UNLOCKMAN->SongIsLocked(pSong) & LOCKED_SELECTABLE) ) - return false; - break; - case Selectable_DontCare: - break; - } - - if( m_bUseSongAllowedList ) - { - if( find(m_vpSongAllowedList.begin(),m_vpSongAllowedList.end(),pSong) == m_vpSongAllowedList.end() ) - return false; - } - - if( m_iMaxStagesForSong != -1 && GAMESTATE->GetNumStagesMultiplierForSong(pSong) > m_iMaxStagesForSong ) - return false; - - switch( m_Tutorial ) - { - DEFAULT_FAIL(m_Tutorial); - case Tutorial_Yes: - if( !pSong->IsTutorial() ) - return false; - break; - case Tutorial_No: - if( pSong->IsTutorial() ) - return false; - break; - case Tutorial_DontCare: - break; - } - - switch( m_Locked ) - { - DEFAULT_FAIL(m_Locked); - case Locked_Locked: - if( UNLOCKMAN && !(UNLOCKMAN->SongIsLocked(pSong) & LOCKED_LOCK) ) - return false; - break; - case Locked_Unlocked: - if( UNLOCKMAN && UNLOCKMAN->SongIsLocked(pSong) & LOCKED_LOCK ) - return false; - break; - case Locked_DontCare: - break; - } - - return true; -} - -void SongUtil::GetSteps( - const Song *pSong, - vector& arrayAddTo, - StepsType st, - Difficulty dc, - int iMeterLow, - int iMeterHigh, - const RString &sDescription, - const RString &sCredit, - bool bIncludeAutoGen, - unsigned uHash, - int iMaxToGet - ) -{ - if( !iMaxToGet ) - return; - - const vector &vpSteps = st == StepsType_Invalid ? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st); - for( unsigned i=0; iGetDifficulty() ) - continue; - if( iMeterLow != -1 && iMeterLow > pSteps->GetMeter() ) - continue; - if( iMeterHigh != -1 && iMeterHigh < pSteps->GetMeter() ) - continue; - if( sDescription.size() && sDescription != pSteps->GetDescription() ) - continue; - if( sCredit.size() && sCredit != pSteps->GetCredit() ) - continue; - if( uHash != 0 && uHash != pSteps->GetHash() ) - continue; - if( !bIncludeAutoGen && pSteps->IsAutogen() ) - continue; - - arrayAddTo.push_back( pSteps ); - - if( iMaxToGet != -1 ) - { - --iMaxToGet; - if( !iMaxToGet ) - break; - } - } -} - -Steps* SongUtil::GetOneSteps( - const Song *pSong, - StepsType st, - Difficulty dc, - int iMeterLow, - int iMeterHigh, - const RString &sDescription, - const RString &sCredit, - unsigned uHash, - bool bIncludeAutoGen - ) -{ - vector vpSteps; - GetSteps( pSong, vpSteps, st, dc, iMeterLow, iMeterHigh, sDescription, sCredit, bIncludeAutoGen, uHash, 1 ); // get max 1 - if( vpSteps.empty() ) - return NULL; - else - return vpSteps[0]; -} - -Steps* SongUtil::GetStepsByDifficulty( const Song *pSong, StepsType st, Difficulty dc, bool bIncludeAutoGen ) -{ - const vector& vpSteps = (st == StepsType_Invalid)? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st); - for( unsigned i=0; iGetDifficulty() ) - continue; - if( !bIncludeAutoGen && pSteps->IsAutogen() ) - continue; - - return pSteps; - } - - return NULL; -} - -Steps* SongUtil::GetStepsByMeter( const Song *pSong, StepsType st, int iMeterLow, int iMeterHigh ) -{ - const vector& vpSteps = (st == StepsType_Invalid)? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st); - for( unsigned i=0; i pSteps->GetMeter() ) - continue; - if( iMeterHigh < pSteps->GetMeter() ) - continue; - - return pSteps; - } - - return NULL; -} - -Steps* SongUtil::GetStepsByDescription( const Song *pSong, StepsType st, RString sDescription ) -{ - vector vNotes; - GetSteps( pSong, vNotes, st, Difficulty_Invalid, -1, -1, sDescription, "" ); - if( vNotes.size() == 0 ) - return NULL; - else - return vNotes[0]; -} - -Steps* SongUtil::GetStepsByCredit( const Song *pSong, StepsType st, RString sCredit ) -{ - vector vNotes; - GetSteps(pSong, vNotes, st, Difficulty_Invalid, -1, -1, "", sCredit ); - if( vNotes.size() == 0 ) - return NULL; - else - return vNotes[0]; -} - - -Steps* SongUtil::GetClosestNotes( const Song *pSong, StepsType st, Difficulty dc, bool bIgnoreLocked ) -{ - ASSERT( dc != Difficulty_Invalid ); - - const vector& vpSteps = (st == StepsType_Invalid)? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st); - Steps *pClosest = NULL; - int iClosestDistance = 999; - for( unsigned i=0; iGetDifficulty() == Difficulty_Edit && dc != Difficulty_Edit ) - continue; - if( bIgnoreLocked && UNLOCKMAN->StepsIsLocked(pSong,pSteps) ) - continue; - - int iDistance = abs(dc - pSteps->GetDifficulty()); - if( iDistance < iClosestDistance ) - { - pClosest = pSteps; - iClosestDistance = iDistance; - } - } - - return pClosest; -} - - -/* Make any duplicate difficulties edits. (Note that BMS files do a first pass - * on this; see BMSLoader::SlideDuplicateDifficulties.) */ -void SongUtil::AdjustDuplicateSteps( Song *pSong ) -{ - FOREACH_ENUM( StepsType, st ) - { - FOREACH_ENUM( Difficulty, dc ) - { - if( dc == Difficulty_Edit ) - continue; - - vector vSteps; - SongUtil::GetSteps( pSong, vSteps, st, dc ); - - /* Delete steps that are completely identical. This happened due to a - * bug in an earlier version. */ - DeleteDuplicateSteps( pSong, vSteps ); - - CHECKPOINT; - StepsUtil::SortNotesArrayByDifficulty( vSteps ); - CHECKPOINT; - for( unsigned k=1; kSetDifficulty( Difficulty_Edit ); - if( vSteps[k]->GetDescription() == "" ) - { - /* "Hard Edit" */ - RString EditName = Capitalize( DifficultyToString(dc) ) + " Edit"; - vSteps[k]->SetDescription( EditName ); - } - } - } - - /* XXX: Don't allow edits to have descriptions that look like regular difficulties. - * These are confusing, and they're ambiguous when passed to GetStepsByID. */ - } -} -/** - * @brief Remove the initial whitespace characters. - * @param s the string to left trim. - * @return the trimmed string. - */ -static RString RemoveInitialWhitespace( RString s ) -{ - size_t i = s.find_first_not_of(" \t\r\n"); - if( i != s.npos ) - s.erase( 0, i ); - return s; -} - -/* This is called within TidyUpData, before autogen notes are added. */ -void SongUtil::DeleteDuplicateSteps( Song *pSong, vector &vSteps ) -{ - /* vSteps have the same StepsType and Difficulty. Delete them if they have the - * same m_sDescription, m_sCredit, m_iMeter and SMNoteData. */ - CHECKPOINT; - for( unsigned i=0; iGetDescription() != s2->GetDescription() ) - continue; - if( s1->GetCredit() != s2->GetCredit() ) - continue; - if( s1->GetMeter() != s2->GetMeter() ) - continue; - /* Compare, ignoring whitespace. */ - RString sSMNoteData1; - s1->GetSMNoteData( sSMNoteData1 ); - RString sSMNoteData2; - s2->GetSMNoteData( sSMNoteData2 ); - if( RemoveInitialWhitespace(sSMNoteData1) != RemoveInitialWhitespace(sSMNoteData2) ) - continue; - - LOG->Trace("Removed %p duplicate steps in song \"%s\" with description \"%s\", step author \"%s\", and meter \"%i\"", - s2, pSong->GetSongDir().c_str(), s1->GetDescription().c_str(), s1->GetCredit().c_str(), s1->GetMeter() ); - - pSong->DeleteSteps( s2, false ); - - vSteps.erase(vSteps.begin()+j); - --j; - } - } -} - - -///////////////////////////////////// -// Sorting -///////////////////////////////////// - -static LocalizedString SORT_NOT_AVAILABLE( "Sort", "NotAvailable" ); -static LocalizedString SORT_OTHER ( "Sort", "Other" ); - -/* Just calculating GetNumTimesPlayed within the sort is pretty slow, so let's precompute - * it. (This could be generalized with a template.) */ -static map g_mapSongSortVal; - -static bool CompareSongPointersBySortValueAscending( const Song *pSong1, const Song *pSong2 ) -{ - return g_mapSongSortVal[pSong1] < g_mapSongSortVal[pSong2]; -} - -static bool CompareSongPointersBySortValueDescending( const Song *pSong1, const Song *pSong2 ) -{ - return g_mapSongSortVal[pSong1] > g_mapSongSortVal[pSong2]; -} - - -RString SongUtil::MakeSortString( RString s ) -{ - s.MakeUpper(); - - // Make sure that non-alphanumeric strings are placed at the very end. - if( s.size() > 0 ) - { - if( s[0] == '.' ) // like the song ".59" - s.erase(s.begin()); - if( (s[0] < 'A' || s[0] > 'Z') && (s[0] < '0' || s[0] > '9') ) - s = char(126) + s; - } - - return s; -} - -static bool CompareSongPointersByTitle( const Song *pSong1, const Song *pSong2 ) -{ - // Prefer transliterations to full titles - RString s1 = pSong1->GetTranslitMainTitle(); - RString s2 = pSong2->GetTranslitMainTitle(); - if( s1 == s2 ) - { - s1 = pSong1->GetTranslitSubTitle(); - s2 = pSong2->GetTranslitSubTitle(); - } - - s1 = SongUtil::MakeSortString(s1); - s2 = SongUtil::MakeSortString(s2); - - int ret = strcmp( s1, s2 ); - if(ret < 0) return true; - if(ret > 0) return false; - - /* The titles are the same. Ensure we get a consistent ordering - * by comparing the unique SongFilePaths. */ - return pSong1->GetSongFilePath().CompareNoCase(pSong2->GetSongFilePath()) < 0; -} - -void SongUtil::SortSongPointerArrayByTitle( vector &vpSongsInOut ) -{ - sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByTitle ); -} - -static bool CompareSongPointersByBPM( const Song *pSong1, const Song *pSong2 ) -{ - DisplayBpms bpms1, bpms2; - pSong1->GetDisplayBpms( bpms1 ); - pSong2->GetDisplayBpms( bpms2 ); - - if( bpms1.GetMax() < bpms2.GetMax() ) - return true; - if( bpms1.GetMax() > bpms2.GetMax() ) - return false; - - return CompareRStringsAsc( pSong1->GetSongFilePath(), pSong2->GetSongFilePath() ); -} - -void SongUtil::SortSongPointerArrayByBPM( vector &vpSongsInOut ) -{ - sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByBPM ); -} - -static bool CompareSongPointersByLength( const Song *pSong1, const Song *pSong2 ) -{ - float length1, length2; - length1 = pSong1->m_fMusicLengthSeconds; - length2 = pSong2->m_fMusicLengthSeconds; - - if( length1 < length2 ) - return true; - if( length1 > length2 ) - return false; - - return CompareRStringsAsc( pSong1->GetSongFilePath(), pSong2->GetSongFilePath() ); -} - -void SongUtil::SortSongPointerArrayByLength( vector &vpSongsInOut ) -{ - sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByLength ); -} - -void AppendOctal( int n, int digits, RString &out ) -{ - for( int p = digits-1; p >= 0; --p ) - { - const int shift = p*3; - int n2 = (n >> shift) & 0x7; - out.insert( out.end(), (char) (n2+'0') ); - } -} - -static bool CompDescending( const pair &a, const pair &b ) -{ - return a.second > b.second; -} -static bool CompAscending( const pair &a, const pair &b ) -{ - return a.second < b.second; -} - -void SongUtil::SortSongPointerArrayByGrades( vector &vpSongsInOut, bool bDescending ) -{ - /* Optimize by pre-writing a string to compare, since doing - * GetNumNotesWithGrade inside the sort is too slow. */ - typedef pair< Song *, RString > val; - vector vals; - vals.reserve( vpSongsInOut.size() ); - - for( unsigned i = 0; i < vpSongsInOut.size(); ++i ) - { - Song *pSong = vpSongsInOut[i]; - - int iCounts[NUM_Grade]; - const Profile *pProfile = PROFILEMAN->GetMachineProfile(); - ASSERT( pProfile != NULL ); - pProfile->GetGrades( pSong, GAMESTATE->GetCurrentStyle()->m_StepsType, iCounts ); - - RString foo; - foo.reserve(256); - for( int g=Grade_Tier01; g &vpSongsInOut ) -{ - for( unsigned i = 0; i < vpSongsInOut.size(); ++i ) - g_mapSongSortVal[vpSongsInOut[i]] = MakeSortString( vpSongsInOut[i]->GetTranslitArtist() ); - stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueAscending ); -} - -/* This is for internal use, not display; sorting by Unicode codepoints isn't very - * interesting for display. */ -void SongUtil::SortSongPointerArrayByDisplayArtist( vector &vpSongsInOut ) -{ - for( unsigned i = 0; i < vpSongsInOut.size(); ++i ) - g_mapSongSortVal[vpSongsInOut[i]] = MakeSortString( vpSongsInOut[i]->GetDisplayArtist() ); - stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueAscending ); -} - -static int CompareSongPointersByGenre(const Song *pSong1, const Song *pSong2) -{ - return pSong1->m_sGenre < pSong2->m_sGenre; -} - -void SongUtil::SortSongPointerArrayByGenre( vector &vpSongsInOut ) -{ - stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByGenre ); -} - -int SongUtil::CompareSongPointersByGroup(const Song *pSong1, const Song *pSong2) -{ - return pSong1->m_sGroupName < pSong2->m_sGroupName; -} - -static int CompareSongPointersByGroupAndTitle( const Song *pSong1, const Song *pSong2 ) -{ - const RString &sGroup1 = pSong1->m_sGroupName; - const RString &sGroup2 = pSong2->m_sGroupName; - - if( sGroup1 < sGroup2 ) - return true; - if( sGroup1 > sGroup2 ) - return false; - - /* Same group; compare by name. */ - return CompareSongPointersByTitle( pSong1, pSong2 ); -} - -void SongUtil::SortSongPointerArrayByGroupAndTitle( vector &vpSongsInOut ) -{ - sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByGroupAndTitle ); -} - -void SongUtil::SortSongPointerArrayByNumPlays( vector &vpSongsInOut, ProfileSlot slot, bool bDescending ) -{ - if( !PROFILEMAN->IsPersistentProfile(slot) ) - return; // nothing to do since we don't have data - Profile* pProfile = PROFILEMAN->GetProfile(slot); - SortSongPointerArrayByNumPlays( vpSongsInOut, pProfile, bDescending ); -} - -void SongUtil::SortSongPointerArrayByNumPlays( vector &vpSongsInOut, const Profile* pProfile, bool bDescending ) -{ - ASSERT( pProfile != NULL ); - for(unsigned i = 0; i < vpSongsInOut.size(); ++i) - g_mapSongSortVal[vpSongsInOut[i]] = ssprintf("%9i", pProfile->GetSongNumTimesPlayed(vpSongsInOut[i])); - stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), bDescending ? CompareSongPointersBySortValueDescending : CompareSongPointersBySortValueAscending ); - g_mapSongSortVal.clear(); -} - -RString SongUtil::GetSectionNameFromSongAndSort( const Song* pSong, SortOrder so ) -{ - if( pSong == NULL ) - return RString(); - - switch( so ) - { - case SORT_PREFERRED: - return SONGMAN->SongToPreferredSortSectionName( pSong ); - case SORT_GROUP: - // guaranteed not empty - return pSong->m_sGroupName; - case SORT_TITLE: - case SORT_ARTIST: - { - RString s; - switch( so ) - { - case SORT_TITLE: s = pSong->GetTranslitMainTitle(); break; - case SORT_ARTIST: s = pSong->GetTranslitArtist(); break; - default: - FAIL_M(ssprintf("Unexpected SortOrder: %i", so)); - } - s = MakeSortString(s); // resulting string will be uppercase - - if( s.empty() ) - return RString(); - else if( s[0] >= '0' && s[0] <= '9' ) - return "0-9"; - else if( s[0] < 'A' || s[0] > 'Z') - return SORT_OTHER.GetValue(); - else - return s.Left(1); - } - case SORT_GENRE: - if( !pSong->m_sGenre.empty() ) - return pSong->m_sGenre; - return SORT_NOT_AVAILABLE.GetValue(); - case SORT_BPM: - { - if( SHOW_SECTIONS_IN_BPM_SORT ) - { - const int iBPMGroupSize = SORT_BPM_DIVISION; - DisplayBpms bpms; - pSong->GetDisplayBpms( bpms ); - int iMaxBPM = (int)bpms.GetMax(); - iMaxBPM += iBPMGroupSize - (iMaxBPM%iBPMGroupSize) - 1; - return ssprintf("%03d-%03d",iMaxBPM-(iBPMGroupSize-1), iMaxBPM); - } - else - return RString(); - } - case SORT_LENGTH: - { - if( SHOW_SECTIONS_IN_LENGTH_SORT ) - { - const int iSortLengthSize = SORT_LENGTH_DIVISION; - int iMaxLength = (int)pSong->m_fMusicLengthSeconds; - iMaxLength += (iSortLengthSize - (iMaxLength%iSortLengthSize) - 1); - int iMinLength = iMaxLength - (iSortLengthSize-1); - return ssprintf( "%s-%s", SecondsToMMSS(iMinLength).c_str(), SecondsToMMSS(iMaxLength).c_str() ); - } - else - return RString(); - } - case SORT_POPULARITY: - case SORT_RECENT: - return RString(); - case SORT_TOP_GRADES: - { - int iCounts[NUM_Grade]; - PROFILEMAN->GetMachineProfile()->GetGrades( pSong, GAMESTATE->GetCurrentStyle()->m_StepsType, iCounts ); - - for( int i=Grade_Tier01; i 0 ) - return ssprintf( "%4s x %d", GradeToLocalizedString(g).c_str(), iCounts[i] ); - } - return GradeToLocalizedString( Grade_NoData ); - } - case SORT_BEGINNER_METER: - case SORT_EASY_METER: - case SORT_MEDIUM_METER: - case SORT_HARD_METER: - case SORT_CHALLENGE_METER: - case SORT_DOUBLE_EASY_METER: - case SORT_DOUBLE_MEDIUM_METER: - case SORT_DOUBLE_HARD_METER: - case SORT_DOUBLE_CHALLENGE_METER: - { - StepsType st; - Difficulty dc; - SongUtil::GetStepsTypeAndDifficultyFromSortOrder( so, st, dc ); - - Steps* pSteps = GetStepsByDifficulty(pSong,st,dc); - if( pSteps && !UNLOCKMAN->StepsIsLocked(pSong,pSteps) ) - return ssprintf("%02d", pSteps->GetMeter() ); - return SORT_NOT_AVAILABLE.GetValue(); - } - case SORT_MODE_MENU: - return RString(); - case SORT_ALL_COURSES: - case SORT_NONSTOP_COURSES: - case SORT_ONI_COURSES: - case SORT_ENDLESS_COURSES: - default: - FAIL_M(ssprintf("Invalid SortOrder: %i", so)); - } -} - -void SongUtil::SortSongPointerArrayBySectionName( vector &vpSongsInOut, SortOrder so ) -{ - RString sOther = SORT_OTHER.GetValue(); - for(unsigned i = 0; i < vpSongsInOut.size(); ++i) - { - RString val = GetSectionNameFromSongAndSort( vpSongsInOut[i], so ); - - // Make sure 0-9 comes first and OTHER comes last. - if( val == "0-9" ) val = "0"; - else if( val == sOther ) val = "2"; - else val = "1" + MakeSortString(val); - - g_mapSongSortVal[vpSongsInOut[i]] = val; - } - - stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueAscending ); - g_mapSongSortVal.clear(); -} - -void SongUtil::SortSongPointerArrayByStepsTypeAndMeter( vector &vpSongsInOut, StepsType st, Difficulty dc ) -{ - g_mapSongSortVal.clear(); - for(unsigned i = 0; i < vpSongsInOut.size(); ++i) - { - // Ignore locked steps. - const Steps* pSteps = GetClosestNotes( vpSongsInOut[i], st, dc, true ); - RString &s = g_mapSongSortVal[vpSongsInOut[i]]; - s = ssprintf("%03d", pSteps ? pSteps->GetMeter() : 0); - - /* pSteps may not be exactly the difficulty we want; for example, we - * may be sorting by Hard difficulty and a song may have no Hard steps. - * In this case, we can end up with unintuitive ties; for example, pSteps - * may be Medium with a meter of 5, which will sort it among the 5-meter - * Hard songs. Break the tie, by adding the difficulty to the sort as - * well. That way, we'll always put Medium 5s before Hard 5s. If all - * songs are using the preferred difficulty (dc), this will be a no-op. */ - s += ssprintf( "%c", (pSteps? pSteps->GetDifficulty():0) + '0' ); - - if( PREFSMAN->m_bSubSortByNumSteps ) - s += ssprintf("%06.0f",pSteps ? pSteps->GetRadarValues(PLAYER_1)[RadarCategory_TapsAndHolds] : 0); - } - stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueAscending ); -} - -void SongUtil::SortByMostRecentlyPlayedForMachine( vector &vpSongsInOut ) -{ - Profile *pProfile = PROFILEMAN->GetMachineProfile(); - - FOREACH_CONST( Song*, vpSongsInOut, s ) - { - int iNumTimesPlayed = pProfile->GetSongNumTimesPlayed( *s ); - RString val = iNumTimesPlayed ? pProfile->GetSongLastPlayedDateTime(*s).GetString() : "0"; - g_mapSongSortVal[*s] = val; - } - - stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueDescending ); - g_mapSongSortVal.clear(); -} - -bool SongUtil::IsEditDescriptionUnique( const Song* pSong, StepsType st, const RString &sPreferredDescription, const Steps *pExclude ) -{ - FOREACH_CONST( Steps*, pSong->GetAllSteps(), s ) - { - Steps *pSteps = *s; - - if( pSteps->GetDifficulty() != Difficulty_Edit ) - continue; - if( pSteps->m_StepsType != st ) - continue; - if( pSteps == pExclude ) - continue; - if( pSteps->GetDescription() == sPreferredDescription ) - return false; - } - return true; -} - -bool SongUtil::IsChartNameUnique( const Song* pSong, StepsType st, const RString &name, const Steps *pExclude ) -{ - FOREACH_CONST( Steps*, pSong->GetAllSteps(), s ) - { - Steps *pSteps = *s; - - if( pSteps->m_StepsType != st ) - continue; - if( pSteps == pExclude ) - continue; - if( pSteps->GetChartName() == name ) - return false; - } - return true; -} - -RString SongUtil::MakeUniqueEditDescription( const Song *pSong, StepsType st, const RString &sPreferredDescription ) -{ - if( IsEditDescriptionUnique( pSong, st, sPreferredDescription, NULL ) ) - return sPreferredDescription; - - RString sTemp; - - for( int i=0; i<1000; i++ ) - { - // make name "My Edit" -> "My Edit2" - RString sNum = ssprintf("%d", i+1); - sTemp = sPreferredDescription.Left( MAX_STEPS_DESCRIPTION_LENGTH - sNum.size() ) + sNum; - - if( IsEditDescriptionUnique(pSong, st, sTemp, NULL) ) - return sTemp; - } - - // Edit limit guards should prevent this - FAIL_M("Exceeded limit of 1000 edits per song"); -} - -static LocalizedString YOU_MUST_SUPPLY_NAME ( "SongUtil", "You must supply a name for your new edit." ); -static LocalizedString CHART_NAME_CONFLICTS ("SongUtil", "The name you chose conflicts with another chart. Please use a different name."); -static LocalizedString EDIT_NAME_CONFLICTS ( "SongUtil", "The name you chose conflicts with another edit. Please use a different name." ); -static LocalizedString CHART_NAME_CANNOT_CONTAIN ("SongUtil", "The chart name cannot contain any of the following characters: %s" ); -static LocalizedString EDIT_NAME_CANNOT_CONTAIN ( "SongUtil", "The edit name cannot contain any of the following characters: %s" ); - -bool SongUtil::ValidateCurrentEditStepsDescription( const RString &sAnswer, RString &sErrorOut ) -{ - Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; - Song *pSong = pSteps->m_pSong; - - ASSERT( pSteps->IsAnEdit() ); - - if( sAnswer.empty() ) - { - sErrorOut = YOU_MUST_SUPPLY_NAME; - return false; - } - - static const RString sInvalidChars = "\\/:*?\"<>|"; - if( strpbrk(sAnswer, sInvalidChars) != NULL ) - { - sErrorOut = ssprintf( EDIT_NAME_CANNOT_CONTAIN.GetValue(), sInvalidChars.c_str() ); - return false; - } - - // Steps name must be unique for this song. - vector v; - GetSteps( pSong, v, StepsType_Invalid, Difficulty_Edit ); - FOREACH_CONST( Steps*, v, s ) - { - if( pSteps == *s ) - continue; // don't compare name against ourself - - if( (*s)->GetDescription() == sAnswer ) - { - sErrorOut = EDIT_NAME_CONFLICTS; - return false; - } - } - - return true; -} - -bool SongUtil::ValidateCurrentStepsDescription( const RString &sAnswer, RString &sErrorOut ) -{ - if( sAnswer.empty() ) - return true; - - /* Don't allow duplicate edit names within the same StepsType; edit names - * uniquely identify the edit. */ - Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; - - // If unchanged: - if( pSteps->GetDescription() == sAnswer ) - return true; - - if( pSteps->IsAnEdit() ) - { - return SongUtil::ValidateCurrentEditStepsDescription( sAnswer, sErrorOut ); - } - - return true; -} - -bool SongUtil::ValidateCurrentStepsChartName(const RString &answer, RString &error) -{ - if (answer.empty()) return true; - - static const RString sInvalidChars = "\\/:*?\"<>|"; - if( strpbrk(answer, sInvalidChars) != NULL ) - { - error = ssprintf( CHART_NAME_CANNOT_CONTAIN.GetValue(), sInvalidChars.c_str() ); - return false; - } - - /* Don't allow duplicate title names within the same StepsType. - * We need some way of identifying the unique charts. */ - Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; - - if (pSteps->GetChartName() == answer) return true; - - // TODO next commit: borrow code from EditStepsDescription. - bool result = SongUtil::IsChartNameUnique(GAMESTATE->m_pCurSong, pSteps->m_StepsType, - answer, pSteps); - if (!result) - error = CHART_NAME_CONFLICTS; - return result; -} - -static LocalizedString AUTHOR_NAME_CANNOT_CONTAIN( "SongUtil", "The step author's name cannot contain any of the following characters: %s" ); - -bool SongUtil::ValidateCurrentStepsCredit( const RString &sAnswer, RString &sErrorOut ) -{ - if( sAnswer.empty() ) - return true; - - Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; - // If unchanged: - if( pSteps->GetCredit() == sAnswer ) - return true; - - // Borrow from EditDescription testing. Perhaps this should be abstracted? -Wolfman2000 - static const RString sInvalidChars = "\\/:*?\"<>|"; - if( strpbrk(sAnswer, sInvalidChars) != NULL ) - { - sErrorOut = ssprintf( AUTHOR_NAME_CANNOT_CONTAIN.GetValue(), sInvalidChars.c_str() ); - return false; - } - - return true; -} - -void SongUtil::GetAllSongGenres( vector &vsOut ) -{ - set genres; - FOREACH_CONST( Song*, SONGMAN->GetAllSongs(), song ) - { - if( !(*song)->m_sGenre.empty() ) - genres.insert( (*song)->m_sGenre ); - } - - FOREACHS_CONST( RString, genres, s ) - { - vsOut.push_back( *s ); - } -} - -void SongUtil::FilterSongs( const SongCriteria &sc, const vector &in, - vector &out, bool doCareAboutGame ) -{ - out.reserve( in.size() ); - FOREACH_CONST( Song*, in, s ) - { - if( sc.Matches( *s ) && (!doCareAboutGame || IsSongPlayable(*s) ) ) - { - - out.push_back( *s ); - } - } -} - -void SongUtil::GetPlayableStepsTypes( const Song *pSong, set &vOut ) -{ - vector vpPossibleStyles; - if( CommonMetrics::AUTO_SET_STYLE ) - GAMEMAN->GetCompatibleStyles( GAMESTATE->m_pCurGame, GAMESTATE->GetNumPlayersEnabled(), vpPossibleStyles ); - else - vpPossibleStyles.push_back( GAMESTATE->m_pCurStyle ); - - // Only allow OneSide Styles in Workout - if( GAMESTATE->m_bMultiplayer ) - { - for( int i=vpPossibleStyles.size()-1; i>=0; i-- ) - { - const Style *pStyle = vpPossibleStyles[i]; - switch( pStyle->m_StyleType ) - { - DEFAULT_FAIL( pStyle->m_StyleType ); - case StyleType_OnePlayerOneSide: - continue; - case StyleType_TwoPlayersTwoSides: - case StyleType_OnePlayerTwoSides: - case StyleType_TwoPlayersSharedSides: - vpPossibleStyles.erase( vpPossibleStyles.begin() + i ); - break; - } - } - } - - set vStepsTypes; - FOREACH( const Style*, vpPossibleStyles, s ) - vStepsTypes.insert( (*s)->m_StepsType ); - - /* filter out hidden StepsTypes, and remove steps that we don't have enough - * stages left to play. */ - // this being const may have caused some problems... -aj - const vector &vstToShow = CommonMetrics::STEPS_TYPES_TO_SHOW.GetValue(); - FOREACHS( StepsType, vStepsTypes, st ) - { - bool bShowThisStepsType = find( vstToShow.begin(), vstToShow.end(), *st ) != vstToShow.end(); - - int iNumPlayers = GAMESTATE->GetNumPlayersEnabled(); - iNumPlayers = max( iNumPlayers, 1 ); - - bool bEnoughStages = GAMESTATE->IsAnExtraStage() || - GAMESTATE->GetSmallestNumStagesLeftForAnyHumanPlayer() >= - GAMESTATE->GetNumStagesMultiplierForSong(pSong); - - if( bShowThisStepsType && bEnoughStages ) - vOut.insert( *st ); - } -} - -void SongUtil::GetPlayableSteps( const Song *pSong, vector &vOut ) -{ - set vStepsType; - GetPlayableStepsTypes( pSong, vStepsType ); - - FOREACHS( StepsType, vStepsType, st ) - SongUtil::GetSteps( pSong, vOut, *st ); - - StepsUtil::RemoveLockedSteps( pSong, vOut ); - StepsUtil::SortNotesArrayByDifficulty( vOut ); - StepsUtil::SortStepsByTypeAndDifficulty( vOut ); -} - -bool SongUtil::IsStepsTypePlayable( Song *pSong, StepsType st ) -{ - set vStepsType; - GetPlayableStepsTypes( pSong, vStepsType ); - return vStepsType.find( st ) != vStepsType.end(); -} - -bool SongUtil::IsStepsPlayable( Song *pSong, Steps *pSteps ) -{ - vector vpSteps; - GetPlayableSteps( pSong, vpSteps ); - return find( vpSteps.begin(), vpSteps.end(), pSteps ) != vpSteps.end(); -} - -bool SongUtil::IsSongPlayable( Song *s ) -{ - const vector & steps = s->GetAllSteps(); - // I'm sure there is a foreach loop, but I don't - FOREACH( Steps*, const_cast&>(steps), step ) - { - if (IsStepsPlayable(s, *step)) - { - return true; - } - } - - return false; -} - -bool SongUtil::GetStepsTypeAndDifficultyFromSortOrder( SortOrder so, StepsType &stOut, Difficulty &dcOut ) -{ - switch( so ) - { - case SORT_BEGINNER_METER: dcOut = Difficulty_Beginner; break; - case SORT_EASY_METER: dcOut = Difficulty_Easy; break; - case SORT_MEDIUM_METER: dcOut = Difficulty_Medium; break; - case SORT_HARD_METER: dcOut = Difficulty_Hard; break; - case SORT_CHALLENGE_METER: dcOut = Difficulty_Challenge; break; - case SORT_DOUBLE_EASY_METER: dcOut = Difficulty_Easy; break; - case SORT_DOUBLE_MEDIUM_METER: dcOut = Difficulty_Medium; break; - case SORT_DOUBLE_HARD_METER: dcOut = Difficulty_Hard; break; - case SORT_DOUBLE_CHALLENGE_METER: dcOut = Difficulty_Challenge; break; - default: - return false; - } - - switch( so ) - { - DEFAULT_FAIL( so ); - case SORT_BEGINNER_METER: - case SORT_EASY_METER: - case SORT_MEDIUM_METER: - case SORT_HARD_METER: - case SORT_CHALLENGE_METER: - stOut = GAMESTATE->GetCurrentStyle()->m_StepsType; - break; - case SORT_DOUBLE_EASY_METER: - case SORT_DOUBLE_MEDIUM_METER: - case SORT_DOUBLE_HARD_METER: - case SORT_DOUBLE_CHALLENGE_METER: - stOut = GAMESTATE->GetCurrentStyle()->m_StepsType; // in case we don't find any matches below - vector vpStyles; - GAMEMAN->GetStylesForGame(GAMESTATE->m_pCurGame,vpStyles); - FOREACH_CONST( const Style*, vpStyles, i ) - { - if( (*i)->m_StyleType == StyleType_OnePlayerTwoSides ) - { - // Ugly hack to ignore pump's half-double. - bool bContainsHalf = ((RString)(*i)->m_szName).find("half") != RString::npos; - if( bContainsHalf ) - continue; - stOut = (*i)->m_StepsType; - break; - } - } - } - - return true; -} - -////////////////////////////////// -// SongID -////////////////////////////////// - -void SongID::FromSong( const Song *p ) -{ - if( p ) - sDir = p->GetSongDir(); - else - sDir = ""; - - // HACK for backwards compatibility: - // Strip off leading "/". 2005/05/21 file layer changes added a leading slash. - if( sDir.Left(1) == "/" ) - sDir.erase( sDir.begin() ); - - m_Cache.Unset(); -} - -Song *SongID::ToSong() const -{ - Song *pRet = NULL; - if( !m_Cache.Get(&pRet) ) - { - // HACK for backwards compatibility: Re-add the leading "/". - // 2005/05/21 file layer changes added a leading slash. - RString sDir2 = sDir; - if( sDir2.Left(1) != "/" ) - sDir2 = "/" + sDir2; - - if( !sDir2.empty() ) - pRet = SONGMAN->GetSongFromDir( sDir2 ); - m_Cache.Set( pRet ); - } - return pRet; -} - -XNode* SongID::CreateNode() const -{ - XNode* pNode = new XNode( "Song" ); - pNode->AppendAttr( "Dir", sDir ); - return pNode; -} - -void SongID::LoadFromNode( const XNode* pNode ) -{ - ASSERT( pNode->GetName() == "Song" ); - pNode->GetAttrValue("Dir", sDir); - m_Cache.Unset(); -} - -RString SongID::ToString() const -{ - return sDir; -} - -bool SongID::IsValid() const -{ - return ToSong() != NULL; -} - -// lua start -#include "LuaBinding.h" - -namespace -{ - int GetPlayableSteps( lua_State *L ) - { - const Song *pSong = Luna::check( L, 1, true ); - vector vSteps; - SongUtil::GetPlayableSteps(pSong,vSteps); - LuaHelpers::CreateTableFromArray( vSteps, L ); - return 1; - } - int IsStepsTypePlayable( lua_State *L ) - { - Song *pSong = Luna::check( L, 1, true ); - StepsType st = Enum::Check(L, 2); - bool b = SongUtil::IsStepsTypePlayable( pSong, st ); - LuaHelpers::Push( L, b ); - return 1; - } - int IsStepsPlayable( lua_State *L ) - { - Song *pSong = Luna::check( L, 1, true ); - Steps *pSteps = Luna::check( L, 2, true ); - bool b = SongUtil::IsStepsPlayable( pSong, pSteps ); - LuaHelpers::Push( L, b ); - return 1; - } - - const luaL_Reg SongUtilTable[] = - { - LIST_METHOD( GetPlayableSteps ), - LIST_METHOD( IsStepsTypePlayable ), - LIST_METHOD( IsStepsPlayable ), - { NULL, NULL } - }; -} - -LUA_REGISTER_NAMESPACE( SongUtil ) - -/* - * (c) 2001-2004 Chris Danford, Glenn Maynard - * 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 "SongUtil.h" +#include "Song.h" +#include "Steps.h" +#include "GameState.h" +#include "Style.h" +#include "ProfileManager.h" +#include "Profile.h" +#include "PrefsManager.h" +#include "SongManager.h" +#include "XmlFile.h" +#include "Foreach.h" +#include "UnlockManager.h" +#include "ThemeMetric.h" +#include "LocalizedString.h" +#include "RageLog.h" +#include "GameManager.h" +#include "CommonMetrics.h" +#include "LuaBinding.h" +#include "EnumHelper.h" + +ThemeMetric SORT_BPM_DIVISION ( "MusicWheel", "SortBPMDivision" ); +ThemeMetric SORT_LENGTH_DIVISION ( "MusicWheel", "SortLengthDivision" ); +ThemeMetric SHOW_SECTIONS_IN_BPM_SORT ( "MusicWheel", "ShowSectionsInBPMSort" ); +ThemeMetric SHOW_SECTIONS_IN_LENGTH_SORT ( "MusicWheel", "ShowSectionsInLengthSort" ); + +bool SongCriteria::Matches( const Song *pSong ) const +{ + if( !m_sGroupName.empty() && m_sGroupName != pSong->m_sGroupName ) + return false; + + if( UNLOCKMAN->SongIsLocked(pSong) & LOCKED_DISABLED ) + return false; + + if( m_bUseSongGenreAllowedList ) + { + if( find(m_vsSongGenreAllowedList.begin(),m_vsSongGenreAllowedList.end(),pSong->m_sGenre) == m_vsSongGenreAllowedList.end() ) + return false; + } + + switch( m_Selectable ) + { + DEFAULT_FAIL(m_Selectable); + case Selectable_Yes: + if( UNLOCKMAN && UNLOCKMAN->SongIsLocked(pSong) & LOCKED_SELECTABLE ) + return false; + break; + case Selectable_No: + if( UNLOCKMAN && !(UNLOCKMAN->SongIsLocked(pSong) & LOCKED_SELECTABLE) ) + return false; + break; + case Selectable_DontCare: + break; + } + + if( m_bUseSongAllowedList ) + { + if( find(m_vpSongAllowedList.begin(),m_vpSongAllowedList.end(),pSong) == m_vpSongAllowedList.end() ) + return false; + } + + if( m_iMaxStagesForSong != -1 && GAMESTATE->GetNumStagesMultiplierForSong(pSong) > m_iMaxStagesForSong ) + return false; + + switch( m_Tutorial ) + { + DEFAULT_FAIL(m_Tutorial); + case Tutorial_Yes: + if( !pSong->IsTutorial() ) + return false; + break; + case Tutorial_No: + if( pSong->IsTutorial() ) + return false; + break; + case Tutorial_DontCare: + break; + } + + switch( m_Locked ) + { + DEFAULT_FAIL(m_Locked); + case Locked_Locked: + if( UNLOCKMAN && !(UNLOCKMAN->SongIsLocked(pSong) & LOCKED_LOCK) ) + return false; + break; + case Locked_Unlocked: + if( UNLOCKMAN && UNLOCKMAN->SongIsLocked(pSong) & LOCKED_LOCK ) + return false; + break; + case Locked_DontCare: + break; + } + + return true; +} + +void SongUtil::GetSteps( + const Song *pSong, + vector& arrayAddTo, + StepsType st, + Difficulty dc, + int iMeterLow, + int iMeterHigh, + const RString &sDescription, + const RString &sCredit, + bool bIncludeAutoGen, + unsigned uHash, + int iMaxToGet + ) +{ + if( !iMaxToGet ) + return; + + const vector &vpSteps = st == StepsType_Invalid ? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st); + for( unsigned i=0; iGetDifficulty() ) + continue; + if( iMeterLow != -1 && iMeterLow > pSteps->GetMeter() ) + continue; + if( iMeterHigh != -1 && iMeterHigh < pSteps->GetMeter() ) + continue; + if( sDescription.size() && sDescription != pSteps->GetDescription() ) + continue; + if( sCredit.size() && sCredit != pSteps->GetCredit() ) + continue; + if( uHash != 0 && uHash != pSteps->GetHash() ) + continue; + if( !bIncludeAutoGen && pSteps->IsAutogen() ) + continue; + + arrayAddTo.push_back( pSteps ); + + if( iMaxToGet != -1 ) + { + --iMaxToGet; + if( !iMaxToGet ) + break; + } + } +} + +Steps* SongUtil::GetOneSteps( + const Song *pSong, + StepsType st, + Difficulty dc, + int iMeterLow, + int iMeterHigh, + const RString &sDescription, + const RString &sCredit, + unsigned uHash, + bool bIncludeAutoGen + ) +{ + vector vpSteps; + GetSteps( pSong, vpSteps, st, dc, iMeterLow, iMeterHigh, sDescription, sCredit, bIncludeAutoGen, uHash, 1 ); // get max 1 + if( vpSteps.empty() ) + return NULL; + else + return vpSteps[0]; +} + +Steps* SongUtil::GetStepsByDifficulty( const Song *pSong, StepsType st, Difficulty dc, bool bIncludeAutoGen ) +{ + const vector& vpSteps = (st == StepsType_Invalid)? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st); + for( unsigned i=0; iGetDifficulty() ) + continue; + if( !bIncludeAutoGen && pSteps->IsAutogen() ) + continue; + + return pSteps; + } + + return NULL; +} + +Steps* SongUtil::GetStepsByMeter( const Song *pSong, StepsType st, int iMeterLow, int iMeterHigh ) +{ + const vector& vpSteps = (st == StepsType_Invalid)? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st); + for( unsigned i=0; i pSteps->GetMeter() ) + continue; + if( iMeterHigh < pSteps->GetMeter() ) + continue; + + return pSteps; + } + + return NULL; +} + +Steps* SongUtil::GetStepsByDescription( const Song *pSong, StepsType st, RString sDescription ) +{ + vector vNotes; + GetSteps( pSong, vNotes, st, Difficulty_Invalid, -1, -1, sDescription, "" ); + if( vNotes.size() == 0 ) + return NULL; + else + return vNotes[0]; +} + +Steps* SongUtil::GetStepsByCredit( const Song *pSong, StepsType st, RString sCredit ) +{ + vector vNotes; + GetSteps(pSong, vNotes, st, Difficulty_Invalid, -1, -1, "", sCredit ); + if( vNotes.size() == 0 ) + return NULL; + else + return vNotes[0]; +} + + +Steps* SongUtil::GetClosestNotes( const Song *pSong, StepsType st, Difficulty dc, bool bIgnoreLocked ) +{ + ASSERT( dc != Difficulty_Invalid ); + + const vector& vpSteps = (st == StepsType_Invalid)? pSong->GetAllSteps() : pSong->GetStepsByStepsType(st); + Steps *pClosest = NULL; + int iClosestDistance = 999; + for( unsigned i=0; iGetDifficulty() == Difficulty_Edit && dc != Difficulty_Edit ) + continue; + if( bIgnoreLocked && UNLOCKMAN->StepsIsLocked(pSong,pSteps) ) + continue; + + int iDistance = abs(dc - pSteps->GetDifficulty()); + if( iDistance < iClosestDistance ) + { + pClosest = pSteps; + iClosestDistance = iDistance; + } + } + + return pClosest; +} + + +/* Make any duplicate difficulties edits. (Note that BMS files do a first pass + * on this; see BMSLoader::SlideDuplicateDifficulties.) */ +void SongUtil::AdjustDuplicateSteps( Song *pSong ) +{ + FOREACH_ENUM( StepsType, st ) + { + FOREACH_ENUM( Difficulty, dc ) + { + if( dc == Difficulty_Edit ) + continue; + + vector vSteps; + SongUtil::GetSteps( pSong, vSteps, st, dc ); + + /* Delete steps that are completely identical. This happened due to a + * bug in an earlier version. */ + DeleteDuplicateSteps( pSong, vSteps ); + + CHECKPOINT; + StepsUtil::SortNotesArrayByDifficulty( vSteps ); + CHECKPOINT; + for( unsigned k=1; kSetDifficulty( Difficulty_Edit ); + if( vSteps[k]->GetDescription() == "" ) + { + /* "Hard Edit" */ + RString EditName = Capitalize( DifficultyToString(dc) ) + " Edit"; + vSteps[k]->SetDescription( EditName ); + } + } + } + + /* XXX: Don't allow edits to have descriptions that look like regular difficulties. + * These are confusing, and they're ambiguous when passed to GetStepsByID. */ + } +} +/** + * @brief Remove the initial whitespace characters. + * @param s the string to left trim. + * @return the trimmed string. + */ +static RString RemoveInitialWhitespace( RString s ) +{ + size_t i = s.find_first_not_of(" \t\r\n"); + if( i != s.npos ) + s.erase( 0, i ); + return s; +} + +/* This is called within TidyUpData, before autogen notes are added. */ +void SongUtil::DeleteDuplicateSteps( Song *pSong, vector &vSteps ) +{ + /* vSteps have the same StepsType and Difficulty. Delete them if they have the + * same m_sDescription, m_sCredit, m_iMeter and SMNoteData. */ + CHECKPOINT; + for( unsigned i=0; iGetDescription() != s2->GetDescription() ) + continue; + if( s1->GetCredit() != s2->GetCredit() ) + continue; + if( s1->GetMeter() != s2->GetMeter() ) + continue; + /* Compare, ignoring whitespace. */ + RString sSMNoteData1; + s1->GetSMNoteData( sSMNoteData1 ); + RString sSMNoteData2; + s2->GetSMNoteData( sSMNoteData2 ); + if( RemoveInitialWhitespace(sSMNoteData1) != RemoveInitialWhitespace(sSMNoteData2) ) + continue; + + LOG->Trace("Removed %p duplicate steps in song \"%s\" with description \"%s\", step author \"%s\", and meter \"%i\"", + s2, pSong->GetSongDir().c_str(), s1->GetDescription().c_str(), s1->GetCredit().c_str(), s1->GetMeter() ); + + pSong->DeleteSteps( s2, false ); + + vSteps.erase(vSteps.begin()+j); + --j; + } + } +} + + +///////////////////////////////////// +// Sorting +///////////////////////////////////// + +static LocalizedString SORT_NOT_AVAILABLE( "Sort", "NotAvailable" ); +static LocalizedString SORT_OTHER ( "Sort", "Other" ); + +/* Just calculating GetNumTimesPlayed within the sort is pretty slow, so let's precompute + * it. (This could be generalized with a template.) */ +static map g_mapSongSortVal; + +static bool CompareSongPointersBySortValueAscending( const Song *pSong1, const Song *pSong2 ) +{ + return g_mapSongSortVal[pSong1] < g_mapSongSortVal[pSong2]; +} + +static bool CompareSongPointersBySortValueDescending( const Song *pSong1, const Song *pSong2 ) +{ + return g_mapSongSortVal[pSong1] > g_mapSongSortVal[pSong2]; +} + + +RString SongUtil::MakeSortString( RString s ) +{ + s.MakeUpper(); + + // Make sure that non-alphanumeric strings are placed at the very end. + if( s.size() > 0 ) + { + if( s[0] == '.' ) // like the song ".59" + s.erase(s.begin()); + if( (s[0] < 'A' || s[0] > 'Z') && (s[0] < '0' || s[0] > '9') ) + s = char(126) + s; + } + + return s; +} + +static bool CompareSongPointersByTitle( const Song *pSong1, const Song *pSong2 ) +{ + // Prefer transliterations to full titles + RString s1 = pSong1->GetTranslitMainTitle(); + RString s2 = pSong2->GetTranslitMainTitle(); + if( s1 == s2 ) + { + s1 = pSong1->GetTranslitSubTitle(); + s2 = pSong2->GetTranslitSubTitle(); + } + + s1 = SongUtil::MakeSortString(s1); + s2 = SongUtil::MakeSortString(s2); + + int ret = strcmp( s1, s2 ); + if(ret < 0) return true; + if(ret > 0) return false; + + /* The titles are the same. Ensure we get a consistent ordering + * by comparing the unique SongFilePaths. */ + return pSong1->GetSongFilePath().CompareNoCase(pSong2->GetSongFilePath()) < 0; +} + +void SongUtil::SortSongPointerArrayByTitle( vector &vpSongsInOut ) +{ + sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByTitle ); +} + +static bool CompareSongPointersByBPM( const Song *pSong1, const Song *pSong2 ) +{ + DisplayBpms bpms1, bpms2; + pSong1->GetDisplayBpms( bpms1 ); + pSong2->GetDisplayBpms( bpms2 ); + + if( bpms1.GetMax() < bpms2.GetMax() ) + return true; + if( bpms1.GetMax() > bpms2.GetMax() ) + return false; + + return CompareRStringsAsc( pSong1->GetSongFilePath(), pSong2->GetSongFilePath() ); +} + +void SongUtil::SortSongPointerArrayByBPM( vector &vpSongsInOut ) +{ + sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByBPM ); +} + +static bool CompareSongPointersByLength( const Song *pSong1, const Song *pSong2 ) +{ + float length1, length2; + length1 = pSong1->m_fMusicLengthSeconds; + length2 = pSong2->m_fMusicLengthSeconds; + + if( length1 < length2 ) + return true; + if( length1 > length2 ) + return false; + + return CompareRStringsAsc( pSong1->GetSongFilePath(), pSong2->GetSongFilePath() ); +} + +void SongUtil::SortSongPointerArrayByLength( vector &vpSongsInOut ) +{ + sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByLength ); +} + +void AppendOctal( int n, int digits, RString &out ) +{ + for( int p = digits-1; p >= 0; --p ) + { + const int shift = p*3; + int n2 = (n >> shift) & 0x7; + out.insert( out.end(), (char) (n2+'0') ); + } +} + +static bool CompDescending( const pair &a, const pair &b ) +{ + return a.second > b.second; +} +static bool CompAscending( const pair &a, const pair &b ) +{ + return a.second < b.second; +} + +void SongUtil::SortSongPointerArrayByGrades( vector &vpSongsInOut, bool bDescending ) +{ + /* Optimize by pre-writing a string to compare, since doing + * GetNumNotesWithGrade inside the sort is too slow. */ + typedef pair< Song *, RString > val; + vector vals; + vals.reserve( vpSongsInOut.size() ); + + for( unsigned i = 0; i < vpSongsInOut.size(); ++i ) + { + Song *pSong = vpSongsInOut[i]; + + int iCounts[NUM_Grade]; + const Profile *pProfile = PROFILEMAN->GetMachineProfile(); + ASSERT( pProfile != NULL ); + pProfile->GetGrades( pSong, GAMESTATE->GetCurrentStyle()->m_StepsType, iCounts ); + + RString foo; + foo.reserve(256); + for( int g=Grade_Tier01; g &vpSongsInOut ) +{ + for( unsigned i = 0; i < vpSongsInOut.size(); ++i ) + g_mapSongSortVal[vpSongsInOut[i]] = MakeSortString( vpSongsInOut[i]->GetTranslitArtist() ); + stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueAscending ); +} + +/* This is for internal use, not display; sorting by Unicode codepoints isn't very + * interesting for display. */ +void SongUtil::SortSongPointerArrayByDisplayArtist( vector &vpSongsInOut ) +{ + for( unsigned i = 0; i < vpSongsInOut.size(); ++i ) + g_mapSongSortVal[vpSongsInOut[i]] = MakeSortString( vpSongsInOut[i]->GetDisplayArtist() ); + stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueAscending ); +} + +static int CompareSongPointersByGenre(const Song *pSong1, const Song *pSong2) +{ + return pSong1->m_sGenre < pSong2->m_sGenre; +} + +void SongUtil::SortSongPointerArrayByGenre( vector &vpSongsInOut ) +{ + stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByGenre ); +} + +int SongUtil::CompareSongPointersByGroup(const Song *pSong1, const Song *pSong2) +{ + return pSong1->m_sGroupName < pSong2->m_sGroupName; +} + +static int CompareSongPointersByGroupAndTitle( const Song *pSong1, const Song *pSong2 ) +{ + const RString &sGroup1 = pSong1->m_sGroupName; + const RString &sGroup2 = pSong2->m_sGroupName; + + if( sGroup1 < sGroup2 ) + return true; + if( sGroup1 > sGroup2 ) + return false; + + /* Same group; compare by name. */ + return CompareSongPointersByTitle( pSong1, pSong2 ); +} + +void SongUtil::SortSongPointerArrayByGroupAndTitle( vector &vpSongsInOut ) +{ + sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersByGroupAndTitle ); +} + +void SongUtil::SortSongPointerArrayByNumPlays( vector &vpSongsInOut, ProfileSlot slot, bool bDescending ) +{ + if( !PROFILEMAN->IsPersistentProfile(slot) ) + return; // nothing to do since we don't have data + Profile* pProfile = PROFILEMAN->GetProfile(slot); + SortSongPointerArrayByNumPlays( vpSongsInOut, pProfile, bDescending ); +} + +void SongUtil::SortSongPointerArrayByNumPlays( vector &vpSongsInOut, const Profile* pProfile, bool bDescending ) +{ + ASSERT( pProfile != NULL ); + for(unsigned i = 0; i < vpSongsInOut.size(); ++i) + g_mapSongSortVal[vpSongsInOut[i]] = ssprintf("%9i", pProfile->GetSongNumTimesPlayed(vpSongsInOut[i])); + stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), bDescending ? CompareSongPointersBySortValueDescending : CompareSongPointersBySortValueAscending ); + g_mapSongSortVal.clear(); +} + +RString SongUtil::GetSectionNameFromSongAndSort( const Song* pSong, SortOrder so ) +{ + if( pSong == NULL ) + return RString(); + + switch( so ) + { + case SORT_PREFERRED: + return SONGMAN->SongToPreferredSortSectionName( pSong ); + case SORT_GROUP: + // guaranteed not empty + return pSong->m_sGroupName; + case SORT_TITLE: + case SORT_ARTIST: + { + RString s; + switch( so ) + { + case SORT_TITLE: s = pSong->GetTranslitMainTitle(); break; + case SORT_ARTIST: s = pSong->GetTranslitArtist(); break; + default: + FAIL_M(ssprintf("Unexpected SortOrder: %i", so)); + } + s = MakeSortString(s); // resulting string will be uppercase + + if( s.empty() ) + return RString(); + else if( s[0] >= '0' && s[0] <= '9' ) + return "0-9"; + else if( s[0] < 'A' || s[0] > 'Z') + return SORT_OTHER.GetValue(); + else + return s.Left(1); + } + case SORT_GENRE: + if( !pSong->m_sGenre.empty() ) + return pSong->m_sGenre; + return SORT_NOT_AVAILABLE.GetValue(); + case SORT_BPM: + { + if( SHOW_SECTIONS_IN_BPM_SORT ) + { + const int iBPMGroupSize = SORT_BPM_DIVISION; + DisplayBpms bpms; + pSong->GetDisplayBpms( bpms ); + int iMaxBPM = (int)bpms.GetMax(); + iMaxBPM += iBPMGroupSize - (iMaxBPM%iBPMGroupSize) - 1; + return ssprintf("%03d-%03d",iMaxBPM-(iBPMGroupSize-1), iMaxBPM); + } + else + return RString(); + } + case SORT_LENGTH: + { + if( SHOW_SECTIONS_IN_LENGTH_SORT ) + { + const int iSortLengthSize = SORT_LENGTH_DIVISION; + int iMaxLength = (int)pSong->m_fMusicLengthSeconds; + iMaxLength += (iSortLengthSize - (iMaxLength%iSortLengthSize) - 1); + int iMinLength = iMaxLength - (iSortLengthSize-1); + return ssprintf( "%s-%s", SecondsToMMSS(iMinLength).c_str(), SecondsToMMSS(iMaxLength).c_str() ); + } + else + return RString(); + } + case SORT_POPULARITY: + case SORT_RECENT: + return RString(); + case SORT_TOP_GRADES: + { + int iCounts[NUM_Grade]; + PROFILEMAN->GetMachineProfile()->GetGrades( pSong, GAMESTATE->GetCurrentStyle()->m_StepsType, iCounts ); + + for( int i=Grade_Tier01; i 0 ) + return ssprintf( "%4s x %d", GradeToLocalizedString(g).c_str(), iCounts[i] ); + } + return GradeToLocalizedString( Grade_NoData ); + } + case SORT_BEGINNER_METER: + case SORT_EASY_METER: + case SORT_MEDIUM_METER: + case SORT_HARD_METER: + case SORT_CHALLENGE_METER: + case SORT_DOUBLE_EASY_METER: + case SORT_DOUBLE_MEDIUM_METER: + case SORT_DOUBLE_HARD_METER: + case SORT_DOUBLE_CHALLENGE_METER: + { + StepsType st; + Difficulty dc; + SongUtil::GetStepsTypeAndDifficultyFromSortOrder( so, st, dc ); + + Steps* pSteps = GetStepsByDifficulty(pSong,st,dc); + if( pSteps && !UNLOCKMAN->StepsIsLocked(pSong,pSteps) ) + return ssprintf("%02d", pSteps->GetMeter() ); + return SORT_NOT_AVAILABLE.GetValue(); + } + case SORT_MODE_MENU: + return RString(); + case SORT_ALL_COURSES: + case SORT_NONSTOP_COURSES: + case SORT_ONI_COURSES: + case SORT_ENDLESS_COURSES: + default: + FAIL_M(ssprintf("Invalid SortOrder: %i", so)); + } +} + +void SongUtil::SortSongPointerArrayBySectionName( vector &vpSongsInOut, SortOrder so ) +{ + RString sOther = SORT_OTHER.GetValue(); + for(unsigned i = 0; i < vpSongsInOut.size(); ++i) + { + RString val = GetSectionNameFromSongAndSort( vpSongsInOut[i], so ); + + // Make sure 0-9 comes first and OTHER comes last. + if( val == "0-9" ) val = "0"; + else if( val == sOther ) val = "2"; + else val = "1" + MakeSortString(val); + + g_mapSongSortVal[vpSongsInOut[i]] = val; + } + + stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueAscending ); + g_mapSongSortVal.clear(); +} + +void SongUtil::SortSongPointerArrayByStepsTypeAndMeter( vector &vpSongsInOut, StepsType st, Difficulty dc ) +{ + g_mapSongSortVal.clear(); + for(unsigned i = 0; i < vpSongsInOut.size(); ++i) + { + // Ignore locked steps. + const Steps* pSteps = GetClosestNotes( vpSongsInOut[i], st, dc, true ); + RString &s = g_mapSongSortVal[vpSongsInOut[i]]; + s = ssprintf("%03d", pSteps ? pSteps->GetMeter() : 0); + + /* pSteps may not be exactly the difficulty we want; for example, we + * may be sorting by Hard difficulty and a song may have no Hard steps. + * In this case, we can end up with unintuitive ties; for example, pSteps + * may be Medium with a meter of 5, which will sort it among the 5-meter + * Hard songs. Break the tie, by adding the difficulty to the sort as + * well. That way, we'll always put Medium 5s before Hard 5s. If all + * songs are using the preferred difficulty (dc), this will be a no-op. */ + s += ssprintf( "%c", (pSteps? pSteps->GetDifficulty():0) + '0' ); + + if( PREFSMAN->m_bSubSortByNumSteps ) + s += ssprintf("%06.0f",pSteps ? pSteps->GetRadarValues(PLAYER_1)[RadarCategory_TapsAndHolds] : 0); + } + stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueAscending ); +} + +void SongUtil::SortByMostRecentlyPlayedForMachine( vector &vpSongsInOut ) +{ + Profile *pProfile = PROFILEMAN->GetMachineProfile(); + + FOREACH_CONST( Song*, vpSongsInOut, s ) + { + int iNumTimesPlayed = pProfile->GetSongNumTimesPlayed( *s ); + RString val = iNumTimesPlayed ? pProfile->GetSongLastPlayedDateTime(*s).GetString() : "0"; + g_mapSongSortVal[*s] = val; + } + + stable_sort( vpSongsInOut.begin(), vpSongsInOut.end(), CompareSongPointersBySortValueDescending ); + g_mapSongSortVal.clear(); +} + +bool SongUtil::IsEditDescriptionUnique( const Song* pSong, StepsType st, const RString &sPreferredDescription, const Steps *pExclude ) +{ + FOREACH_CONST( Steps*, pSong->GetAllSteps(), s ) + { + Steps *pSteps = *s; + + if( pSteps->GetDifficulty() != Difficulty_Edit ) + continue; + if( pSteps->m_StepsType != st ) + continue; + if( pSteps == pExclude ) + continue; + if( pSteps->GetDescription() == sPreferredDescription ) + return false; + } + return true; +} + +bool SongUtil::IsChartNameUnique( const Song* pSong, StepsType st, const RString &name, const Steps *pExclude ) +{ + FOREACH_CONST( Steps*, pSong->GetAllSteps(), s ) + { + Steps *pSteps = *s; + + if( pSteps->m_StepsType != st ) + continue; + if( pSteps == pExclude ) + continue; + if( pSteps->GetChartName() == name ) + return false; + } + return true; +} + +RString SongUtil::MakeUniqueEditDescription( const Song *pSong, StepsType st, const RString &sPreferredDescription ) +{ + if( IsEditDescriptionUnique( pSong, st, sPreferredDescription, NULL ) ) + return sPreferredDescription; + + RString sTemp; + + for( int i=0; i<1000; i++ ) + { + // make name "My Edit" -> "My Edit2" + RString sNum = ssprintf("%d", i+1); + sTemp = sPreferredDescription.Left( MAX_STEPS_DESCRIPTION_LENGTH - sNum.size() ) + sNum; + + if( IsEditDescriptionUnique(pSong, st, sTemp, NULL) ) + return sTemp; + } + + // Edit limit guards should prevent this + FAIL_M("Exceeded limit of 1000 edits per song"); +} + +static LocalizedString YOU_MUST_SUPPLY_NAME ( "SongUtil", "You must supply a name for your new edit." ); +static LocalizedString CHART_NAME_CONFLICTS ("SongUtil", "The name you chose conflicts with another chart. Please use a different name."); +static LocalizedString EDIT_NAME_CONFLICTS ( "SongUtil", "The name you chose conflicts with another edit. Please use a different name." ); +static LocalizedString CHART_NAME_CANNOT_CONTAIN ("SongUtil", "The chart name cannot contain any of the following characters: %s" ); +static LocalizedString EDIT_NAME_CANNOT_CONTAIN ( "SongUtil", "The edit name cannot contain any of the following characters: %s" ); + +bool SongUtil::ValidateCurrentEditStepsDescription( const RString &sAnswer, RString &sErrorOut ) +{ + Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; + Song *pSong = pSteps->m_pSong; + + ASSERT( pSteps->IsAnEdit() ); + + if( sAnswer.empty() ) + { + sErrorOut = YOU_MUST_SUPPLY_NAME; + return false; + } + + static const RString sInvalidChars = "\\/:*?\"<>|"; + if( strpbrk(sAnswer, sInvalidChars) != NULL ) + { + sErrorOut = ssprintf( EDIT_NAME_CANNOT_CONTAIN.GetValue(), sInvalidChars.c_str() ); + return false; + } + + // Steps name must be unique for this song. + vector v; + GetSteps( pSong, v, StepsType_Invalid, Difficulty_Edit ); + FOREACH_CONST( Steps*, v, s ) + { + if( pSteps == *s ) + continue; // don't compare name against ourself + + if( (*s)->GetDescription() == sAnswer ) + { + sErrorOut = EDIT_NAME_CONFLICTS; + return false; + } + } + + return true; +} + +bool SongUtil::ValidateCurrentStepsDescription( const RString &sAnswer, RString &sErrorOut ) +{ + if( sAnswer.empty() ) + return true; + + /* Don't allow duplicate edit names within the same StepsType; edit names + * uniquely identify the edit. */ + Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; + + // If unchanged: + if( pSteps->GetDescription() == sAnswer ) + return true; + + if( pSteps->IsAnEdit() ) + { + return SongUtil::ValidateCurrentEditStepsDescription( sAnswer, sErrorOut ); + } + + return true; +} + +bool SongUtil::ValidateCurrentStepsChartName(const RString &answer, RString &error) +{ + if (answer.empty()) return true; + + static const RString sInvalidChars = "\\/:*?\"<>|"; + if( strpbrk(answer, sInvalidChars) != NULL ) + { + error = ssprintf( CHART_NAME_CANNOT_CONTAIN.GetValue(), sInvalidChars.c_str() ); + return false; + } + + /* Don't allow duplicate title names within the same StepsType. + * We need some way of identifying the unique charts. */ + Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; + + if (pSteps->GetChartName() == answer) return true; + + // TODO next commit: borrow code from EditStepsDescription. + bool result = SongUtil::IsChartNameUnique(GAMESTATE->m_pCurSong, pSteps->m_StepsType, + answer, pSteps); + if (!result) + error = CHART_NAME_CONFLICTS; + return result; +} + +static LocalizedString AUTHOR_NAME_CANNOT_CONTAIN( "SongUtil", "The step author's name cannot contain any of the following characters: %s" ); + +bool SongUtil::ValidateCurrentStepsCredit( const RString &sAnswer, RString &sErrorOut ) +{ + if( sAnswer.empty() ) + return true; + + Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; + // If unchanged: + if( pSteps->GetCredit() == sAnswer ) + return true; + + // Borrow from EditDescription testing. Perhaps this should be abstracted? -Wolfman2000 + static const RString sInvalidChars = "\\/:*?\"<>|"; + if( strpbrk(sAnswer, sInvalidChars) != NULL ) + { + sErrorOut = ssprintf( AUTHOR_NAME_CANNOT_CONTAIN.GetValue(), sInvalidChars.c_str() ); + return false; + } + + return true; +} + +void SongUtil::GetAllSongGenres( vector &vsOut ) +{ + set genres; + FOREACH_CONST( Song*, SONGMAN->GetAllSongs(), song ) + { + if( !(*song)->m_sGenre.empty() ) + genres.insert( (*song)->m_sGenre ); + } + for (RString const genre : genres) + { + vsOut.push_back( genre ); + } +} + +void SongUtil::FilterSongs( const SongCriteria &sc, const vector &in, + vector &out, bool doCareAboutGame ) +{ + out.reserve( in.size() ); + FOREACH_CONST( Song*, in, s ) + { + if( sc.Matches( *s ) && (!doCareAboutGame || IsSongPlayable(*s) ) ) + { + + out.push_back( *s ); + } + } +} + +void SongUtil::GetPlayableStepsTypes( const Song *pSong, set &vOut ) +{ + vector vpPossibleStyles; + if( CommonMetrics::AUTO_SET_STYLE ) + GAMEMAN->GetCompatibleStyles( GAMESTATE->m_pCurGame, GAMESTATE->GetNumPlayersEnabled(), vpPossibleStyles ); + else + vpPossibleStyles.push_back( GAMESTATE->m_pCurStyle ); + + // Only allow OneSide Styles in Workout + if( GAMESTATE->m_bMultiplayer ) + { + for( int i=vpPossibleStyles.size()-1; i>=0; i-- ) + { + const Style *pStyle = vpPossibleStyles[i]; + switch( pStyle->m_StyleType ) + { + DEFAULT_FAIL( pStyle->m_StyleType ); + case StyleType_OnePlayerOneSide: + continue; + case StyleType_TwoPlayersTwoSides: + case StyleType_OnePlayerTwoSides: + case StyleType_TwoPlayersSharedSides: + vpPossibleStyles.erase( vpPossibleStyles.begin() + i ); + break; + } + } + } + + set vStepsTypes; + FOREACH( const Style*, vpPossibleStyles, s ) + vStepsTypes.insert( (*s)->m_StepsType ); + + /* filter out hidden StepsTypes, and remove steps that we don't have enough + * stages left to play. */ + // this being const may have caused some problems... -aj + const vector &vstToShow = CommonMetrics::STEPS_TYPES_TO_SHOW.GetValue(); + FOREACHS( StepsType, vStepsTypes, st ) + { + bool bShowThisStepsType = find( vstToShow.begin(), vstToShow.end(), *st ) != vstToShow.end(); + + int iNumPlayers = GAMESTATE->GetNumPlayersEnabled(); + iNumPlayers = max( iNumPlayers, 1 ); + + bool bEnoughStages = GAMESTATE->IsAnExtraStage() || + GAMESTATE->GetSmallestNumStagesLeftForAnyHumanPlayer() >= + GAMESTATE->GetNumStagesMultiplierForSong(pSong); + + if( bShowThisStepsType && bEnoughStages ) + vOut.insert( *st ); + } +} + +void SongUtil::GetPlayableSteps( const Song *pSong, vector &vOut ) +{ + set vStepsType; + GetPlayableStepsTypes( pSong, vStepsType ); + + FOREACHS( StepsType, vStepsType, st ) + SongUtil::GetSteps( pSong, vOut, *st ); + + StepsUtil::RemoveLockedSteps( pSong, vOut ); + StepsUtil::SortNotesArrayByDifficulty( vOut ); + StepsUtil::SortStepsByTypeAndDifficulty( vOut ); +} + +bool SongUtil::IsStepsTypePlayable( Song *pSong, StepsType st ) +{ + set vStepsType; + GetPlayableStepsTypes( pSong, vStepsType ); + return vStepsType.find( st ) != vStepsType.end(); +} + +bool SongUtil::IsStepsPlayable( Song *pSong, Steps *pSteps ) +{ + vector vpSteps; + GetPlayableSteps( pSong, vpSteps ); + return find( vpSteps.begin(), vpSteps.end(), pSteps ) != vpSteps.end(); +} + +bool SongUtil::IsSongPlayable( Song *s ) +{ + const vector & steps = s->GetAllSteps(); + // I'm sure there is a foreach loop, but I don't + FOREACH( Steps*, const_cast&>(steps), step ) + { + if (IsStepsPlayable(s, *step)) + { + return true; + } + } + + return false; +} + +bool SongUtil::GetStepsTypeAndDifficultyFromSortOrder( SortOrder so, StepsType &stOut, Difficulty &dcOut ) +{ + switch( so ) + { + case SORT_BEGINNER_METER: dcOut = Difficulty_Beginner; break; + case SORT_EASY_METER: dcOut = Difficulty_Easy; break; + case SORT_MEDIUM_METER: dcOut = Difficulty_Medium; break; + case SORT_HARD_METER: dcOut = Difficulty_Hard; break; + case SORT_CHALLENGE_METER: dcOut = Difficulty_Challenge; break; + case SORT_DOUBLE_EASY_METER: dcOut = Difficulty_Easy; break; + case SORT_DOUBLE_MEDIUM_METER: dcOut = Difficulty_Medium; break; + case SORT_DOUBLE_HARD_METER: dcOut = Difficulty_Hard; break; + case SORT_DOUBLE_CHALLENGE_METER: dcOut = Difficulty_Challenge; break; + default: + return false; + } + + switch( so ) + { + DEFAULT_FAIL( so ); + case SORT_BEGINNER_METER: + case SORT_EASY_METER: + case SORT_MEDIUM_METER: + case SORT_HARD_METER: + case SORT_CHALLENGE_METER: + stOut = GAMESTATE->GetCurrentStyle()->m_StepsType; + break; + case SORT_DOUBLE_EASY_METER: + case SORT_DOUBLE_MEDIUM_METER: + case SORT_DOUBLE_HARD_METER: + case SORT_DOUBLE_CHALLENGE_METER: + stOut = GAMESTATE->GetCurrentStyle()->m_StepsType; // in case we don't find any matches below + vector vpStyles; + GAMEMAN->GetStylesForGame(GAMESTATE->m_pCurGame,vpStyles); + FOREACH_CONST( const Style*, vpStyles, i ) + { + if( (*i)->m_StyleType == StyleType_OnePlayerTwoSides ) + { + // Ugly hack to ignore pump's half-double. + bool bContainsHalf = ((RString)(*i)->m_szName).find("half") != RString::npos; + if( bContainsHalf ) + continue; + stOut = (*i)->m_StepsType; + break; + } + } + } + + return true; +} + +////////////////////////////////// +// SongID +////////////////////////////////// + +void SongID::FromSong( const Song *p ) +{ + if( p ) + sDir = p->GetSongDir(); + else + sDir = ""; + + // HACK for backwards compatibility: + // Strip off leading "/". 2005/05/21 file layer changes added a leading slash. + if( sDir.Left(1) == "/" ) + sDir.erase( sDir.begin() ); + + m_Cache.Unset(); +} + +Song *SongID::ToSong() const +{ + Song *pRet = NULL; + if( !m_Cache.Get(&pRet) ) + { + // HACK for backwards compatibility: Re-add the leading "/". + // 2005/05/21 file layer changes added a leading slash. + RString sDir2 = sDir; + if( sDir2.Left(1) != "/" ) + sDir2 = "/" + sDir2; + + if( !sDir2.empty() ) + pRet = SONGMAN->GetSongFromDir( sDir2 ); + m_Cache.Set( pRet ); + } + return pRet; +} + +XNode* SongID::CreateNode() const +{ + XNode* pNode = new XNode( "Song" ); + pNode->AppendAttr( "Dir", sDir ); + return pNode; +} + +void SongID::LoadFromNode( const XNode* pNode ) +{ + ASSERT( pNode->GetName() == "Song" ); + pNode->GetAttrValue("Dir", sDir); + m_Cache.Unset(); +} + +RString SongID::ToString() const +{ + return sDir; +} + +bool SongID::IsValid() const +{ + return ToSong() != NULL; +} + +// lua start +#include "LuaBinding.h" + +namespace +{ + int GetPlayableSteps( lua_State *L ) + { + const Song *pSong = Luna::check( L, 1, true ); + vector vSteps; + SongUtil::GetPlayableSteps(pSong,vSteps); + LuaHelpers::CreateTableFromArray( vSteps, L ); + return 1; + } + int IsStepsTypePlayable( lua_State *L ) + { + Song *pSong = Luna::check( L, 1, true ); + StepsType st = Enum::Check(L, 2); + bool b = SongUtil::IsStepsTypePlayable( pSong, st ); + LuaHelpers::Push( L, b ); + return 1; + } + int IsStepsPlayable( lua_State *L ) + { + Song *pSong = Luna::check( L, 1, true ); + Steps *pSteps = Luna::check( L, 2, true ); + bool b = SongUtil::IsStepsPlayable( pSong, pSteps ); + LuaHelpers::Push( L, b ); + return 1; + } + + const luaL_Reg SongUtilTable[] = + { + LIST_METHOD( GetPlayableSteps ), + LIST_METHOD( IsStepsTypePlayable ), + LIST_METHOD( IsStepsPlayable ), + { NULL, NULL } + }; +} + +LUA_REGISTER_NAMESPACE( SongUtil ) + +/* + * (c) 2001-2004 Chris Danford, Glenn Maynard + * 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. + */ From f66883aa1b397448ecaa52c38030f011b570df26 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:33:05 -0400 Subject: [PATCH 011/281] Forgot to utilize references. Still compiled, but wasn't the best move. --- src/Profile.cpp | 2 +- src/SongUtil.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Profile.cpp b/src/Profile.cpp index eb2c09ba04..e2a6c8720a 100644 --- a/src/Profile.cpp +++ b/src/Profile.cpp @@ -1073,7 +1073,7 @@ XNode* Profile::SaveGeneralDataCreateNode() const { XNode* pUnlocks = pGeneralDataNode->AppendChild("Unlocks"); - for (RString const unlockEntry : m_UnlockedEntryIDs) + for (RString const &unlockEntry : m_UnlockedEntryIDs) { XNode *pEntry = pUnlocks->AppendChild("UnlockEntry"); pEntry->AppendAttr( "UnlockEntryID", unlockEntry ); diff --git a/src/SongUtil.cpp b/src/SongUtil.cpp index 2145f43e61..477eb2a882 100644 --- a/src/SongUtil.cpp +++ b/src/SongUtil.cpp @@ -907,7 +907,7 @@ void SongUtil::GetAllSongGenres( vector &vsOut ) if( !(*song)->m_sGenre.empty() ) genres.insert( (*song)->m_sGenre ); } - for (RString const genre : genres) + for (RString const &genre : genres) { vsOut.push_back( genre ); } From 105d60326599a917fd7633f89908dee4ea76b050 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:34:03 -0400 Subject: [PATCH 012/281] Another loop, another white spaced file. --- src/LightsManager.cpp | 1042 ++++++++++++++++++++--------------------- 1 file changed, 521 insertions(+), 521 deletions(-) diff --git a/src/LightsManager.cpp b/src/LightsManager.cpp index a3efcea899..572ea03269 100644 --- a/src/LightsManager.cpp +++ b/src/LightsManager.cpp @@ -1,521 +1,521 @@ -#include "global.h" -#include "LightsManager.h" -#include "GameState.h" -#include "RageTimer.h" -#include "arch/Lights/LightsDriver.h" -#include "RageUtil.h" -#include "GameInput.h" // for GameController -#include "InputMapper.h" -#include "Game.h" -#include "PrefsManager.h" -#include "Actor.h" -#include "Preference.h" -#include "Foreach.h" -#include "GameManager.h" -#include "CommonMetrics.h" -#include "Style.h" - -const RString DEFAULT_LIGHTS_DRIVER = "PIUIO,SystemMessage,Export"; -static Preference g_sLightsDriver( "LightsDriver", "" ); // "" == DEFAULT_LIGHTS_DRIVER -Preference g_fLightsFalloffSeconds( "LightsFalloffSeconds", 0.1f ); -Preference g_fLightsAheadSeconds( "LightsAheadSeconds", 0.05f ); -static Preference g_bBlinkGameplayButtonLightsOnNote( "BlinkGameplayButtonLightsOnNote", false ); - -static ThemeMetric GAME_BUTTONS_TO_SHOW( "LightsManager", "GameButtonsToShow" ); - -static const char *CabinetLightNames[] = { - "MarqueeUpLeft", - "MarqueeUpRight", - "MarqueeLrLeft", - "MarqueeLrRight", - "BassLeft", - "BassRight", -}; -XToString( CabinetLight ); -StringToX( CabinetLight ); - -static const char *LightsModeNames[] = { - "Attract", - "Joining", - "MenuStartOnly", - "MenuStartAndDirections", - "Demonstration", - "Gameplay", - "Stage", - "Cleared", - "TestAutoCycle", - "TestManualCycle", -}; -XToString( LightsMode ); -LuaXType( LightsMode ); - -static void GetUsedGameInputs( vector &vGameInputsOut ) -{ - vGameInputsOut.clear(); - - vector asGameButtons; - split( GAME_BUTTONS_TO_SHOW.GetValue(), ",", asGameButtons ); - FOREACH_ENUM( GameController, gc ) - { - FOREACH_CONST( RString, asGameButtons, button ) - { - GameButton gb = StringToGameButton( INPUTMAPPER->GetInputScheme(), *button ); - if( gb != GameButton_Invalid ) - { - GameInput gi = GameInput( gc, gb ); - vGameInputsOut.push_back( gi ); - } - } - } - - set vGIs; - vector vStyles; - GAMEMAN->GetStylesForGame( GAMESTATE->m_pCurGame, vStyles ); - FOREACH( const Style*, vStyles, style ) - { - bool bFound = find( CommonMetrics::STEPS_TYPES_TO_SHOW.GetValue().begin(), CommonMetrics::STEPS_TYPES_TO_SHOW.GetValue().end(), (*style)->m_StepsType ) != CommonMetrics::STEPS_TYPES_TO_SHOW.GetValue().end(); - if( !bFound ) - continue; - FOREACH_PlayerNumber( pn ) - { - for( int iCol=0; iCol<(*style)->m_iColsPerPlayer; ++iCol ) - { - GameInput gi = (*style)->StyleInputToGameInput( iCol, pn ); - if( gi.IsValid() ) - { - vGIs.insert( gi ); - } - } - } - } - - FOREACHS_CONST( GameInput, vGIs, gi ) - vGameInputsOut.push_back( *gi ); -} - -LightsManager* LIGHTSMAN = NULL; // global and accessable from anywhere in our program - -LightsManager::LightsManager() -{ - ZERO( m_fSecsLeftInCabinetLightBlink ); - ZERO( m_fSecsLeftInGameButtonBlink ); - ZERO( m_fActorLights ); - ZERO( m_fSecsLeftInActorLightBlink ); - m_iQueuedCoinCounterPulses = 0; - m_CoinCounterTimer.SetZero(); - - m_LightsMode = LIGHTSMODE_JOINING; - RString sDriver = g_sLightsDriver.Get(); - if( sDriver.empty() ) - sDriver = DEFAULT_LIGHTS_DRIVER; - LightsDriver::Create( sDriver, m_vpDrivers ); - - SetLightsMode( LIGHTSMODE_ATTRACT ); -} - -LightsManager::~LightsManager() -{ - FOREACH( LightsDriver*, m_vpDrivers, iter ) - SAFE_DELETE( *iter ); - m_vpDrivers.clear(); -} - -// XXX: Allow themer to change these. (rewritten; who wrote original? -aj) -static const float g_fLightEffectRiseSeconds = 0.075f; -static const float g_fLightEffectFalloffSeconds = 0.35f; -static const float g_fCoinPulseTime = 0.100f; -void LightsManager::BlinkActorLight( CabinetLight cl ) -{ - m_fSecsLeftInActorLightBlink[cl] = g_fLightEffectRiseSeconds; -} - -float LightsManager::GetActorLightLatencySeconds() const -{ - return g_fLightEffectRiseSeconds; -} - -void LightsManager::Update( float fDeltaTime ) -{ - // Update actor effect lights. - FOREACH_CabinetLight( cl ) - { - float fTime = fDeltaTime; - float &fDuration = m_fSecsLeftInActorLightBlink[cl]; - if( fDuration > 0 ) - { - // The light has power left. Brighten it. - float fSeconds = min( fDuration, fTime ); - fDuration -= fSeconds; - fTime -= fSeconds; - fapproach( m_fActorLights[cl], 1, fSeconds / g_fLightEffectRiseSeconds ); - } - - if( fTime > 0 ) - { - // The light is out of power. Dim it. - fapproach( m_fActorLights[cl], 0, fTime / g_fLightEffectFalloffSeconds ); - } - - Actor::SetBGMLight( cl, m_fActorLights[cl] ); - } - - if( !IsEnabled() ) - return; - - // update lights falloff - { - FOREACH_CabinetLight( cl ) - fapproach( m_fSecsLeftInCabinetLightBlink[cl], 0, fDeltaTime ); - FOREACH_ENUM( GameController, gc ) - FOREACH_ENUM( GameButton, gb ) - fapproach( m_fSecsLeftInGameButtonBlink[gc][gb], 0, fDeltaTime ); - } - - // Set new lights state cabinet lights - { - ZERO( m_LightsState.m_bCabinetLights ); - ZERO( m_LightsState.m_bGameButtonLights ); - } - - { - m_LightsState.m_bCoinCounter = false; - if( !m_CoinCounterTimer.IsZero() ) - { - float fAgo = m_CoinCounterTimer.Ago(); - if( fAgo < g_fCoinPulseTime ) - m_LightsState.m_bCoinCounter = true; - else if( fAgo >= g_fCoinPulseTime * 2 ) - m_CoinCounterTimer.SetZero(); - } - else if( m_iQueuedCoinCounterPulses ) - { - m_CoinCounterTimer.Touch(); - --m_iQueuedCoinCounterPulses; - } - } - - if( m_LightsMode == LIGHTSMODE_TEST_AUTO_CYCLE ) - { - m_fTestAutoCycleCurrentIndex += fDeltaTime; - m_fTestAutoCycleCurrentIndex = fmodf( m_fTestAutoCycleCurrentIndex, NUM_CabinetLight*100 ); - } - - switch( m_LightsMode ) - { - DEFAULT_FAIL( m_LightsMode ); - - case LIGHTSMODE_ATTRACT: - { - int iSec = (int)RageTimer::GetTimeSinceStartFast(); - int iTopIndex = iSec % 4; - - // Aldo: Disabled this line, apparently it was a forgotten initialization - //CabinetLight cl = CabinetLight_Invalid; - - switch( iTopIndex ) - { - DEFAULT_FAIL( iTopIndex ); - case 0: m_LightsState.m_bCabinetLights[LIGHT_MARQUEE_UP_LEFT] = true; break; - case 1: m_LightsState.m_bCabinetLights[LIGHT_MARQUEE_LR_RIGHT] = true; break; - case 2: m_LightsState.m_bCabinetLights[LIGHT_MARQUEE_UP_RIGHT] = true; break; - case 3: m_LightsState.m_bCabinetLights[LIGHT_MARQUEE_LR_LEFT] = true; break; - } - - if( iTopIndex == 0 ) - { - m_LightsState.m_bCabinetLights[LIGHT_BASS_LEFT] = true; - m_LightsState.m_bCabinetLights[LIGHT_BASS_RIGHT] = true; - } - - break; - } - case LIGHTSMODE_MENU_START_ONLY: - case LIGHTSMODE_MENU_START_AND_DIRECTIONS: - case LIGHTSMODE_JOINING: - { - static int iLight; - - // if we've crossed a beat boundary, advance the light index - { - static float fLastBeat; - float fLightSongBeat = GAMESTATE->m_Position.m_fLightSongBeat; - - if( fracf(fLightSongBeat) < fracf(fLastBeat) ) - { - ++iLight; - wrap( iLight, 4 ); - } - - fLastBeat = fLightSongBeat; - } - - CabinetLight cl = CabinetLight_Invalid; - - switch( iLight ) - { - DEFAULT_FAIL( iLight ); - case 0: cl = LIGHT_MARQUEE_UP_LEFT; break; - case 1: cl = LIGHT_MARQUEE_LR_RIGHT; break; - case 2: cl = LIGHT_MARQUEE_UP_RIGHT; break; - case 3: cl = LIGHT_MARQUEE_LR_LEFT; break; - } - - m_LightsState.m_bCabinetLights[cl] = true; - - break; - } - - case LIGHTSMODE_DEMONSTRATION: - case LIGHTSMODE_GAMEPLAY: - { - FOREACH_CabinetLight( cl ) - m_LightsState.m_bCabinetLights[cl] = m_fSecsLeftInCabinetLightBlink[cl] > 0; - - break; - } - - case LIGHTSMODE_STAGE: - case LIGHTSMODE_ALL_CLEARED: - { - FOREACH_CabinetLight( cl ) - m_LightsState.m_bCabinetLights[cl] = true; - - break; - } - - case LIGHTSMODE_TEST_AUTO_CYCLE: - { - int iSec = GetTestAutoCycleCurrentIndex(); - - CabinetLight cl = CabinetLight(iSec % NUM_CabinetLight); - m_LightsState.m_bCabinetLights[cl] = true; - - break; - } - - case LIGHTSMODE_TEST_MANUAL_CYCLE: - { - CabinetLight cl = m_clTestManualCycleCurrent; - m_LightsState.m_bCabinetLights[cl] = true; - - break; - } - } - - - // Update game controller lights - switch( m_LightsMode ) - { - DEFAULT_FAIL( m_LightsMode ); - - case LIGHTSMODE_ALL_CLEARED: - case LIGHTSMODE_STAGE: - case LIGHTSMODE_JOINING: - { - FOREACH_ENUM( GameController, gc ) - { - if( GAMESTATE->m_bSideIsJoined[gc] ) - { - FOREACH_ENUM( GameButton, gb ) - m_LightsState.m_bGameButtonLights[gc][gb] = true; - } - } - - break; - } - - case LIGHTSMODE_MENU_START_ONLY: - case LIGHTSMODE_MENU_START_AND_DIRECTIONS: - { - float fLightSongBeat = GAMESTATE->m_Position.m_fLightSongBeat; - - /* Blink menu lights on the first half of the beat */ - if( fracf(fLightSongBeat) <= 0.5f ) - { - FOREACH_PlayerNumber( pn ) - { - if( !GAMESTATE->m_bSideIsJoined[pn] ) - continue; - - m_LightsState.m_bGameButtonLights[pn][GAME_BUTTON_START] = true; - - if( m_LightsMode == LIGHTSMODE_MENU_START_AND_DIRECTIONS ) - { - m_LightsState.m_bGameButtonLights[pn][GAME_BUTTON_MENULEFT] = true; - m_LightsState.m_bGameButtonLights[pn][GAME_BUTTON_MENURIGHT] = true; - } - } - } - - // fall through to blink on button presses - } - - case LIGHTSMODE_DEMONSTRATION: - case LIGHTSMODE_GAMEPLAY: - { - bool bGameplay = (m_LightsMode == LIGHTSMODE_DEMONSTRATION) || (m_LightsMode == LIGHTSMODE_GAMEPLAY); - - // Blink on notes during gameplay. - if( bGameplay && g_bBlinkGameplayButtonLightsOnNote ) - { - FOREACH_ENUM( GameController, gc ) - { - FOREACH_ENUM( GameButton, gb ) - { - m_LightsState.m_bGameButtonLights[gc][gb] = m_fSecsLeftInGameButtonBlink[gc][gb] > 0 ; - } - } - } - - // fall through to blink on button presses - } - - case LIGHTSMODE_ATTRACT: - { - // Blink on button presses. - FOREACH_ENUM( GameController, gc ) - { - FOREACH_GameButton_Custom( gb ) - { - bool bOn = INPUTMAPPER->IsBeingPressed( GameInput(gc,gb) ); - m_LightsState.m_bGameButtonLights[gc][gb] = bOn; - } - } - - break; - } - - case LIGHTSMODE_TEST_AUTO_CYCLE: - { - int index = GetTestAutoCycleCurrentIndex(); - - vector vGI; - GetUsedGameInputs( vGI ); - wrap( index, vGI.size() ); - - ZERO( m_LightsState.m_bGameButtonLights ); - - GameController gc = vGI[index].controller; - GameButton gb = vGI[index].button; - m_LightsState.m_bGameButtonLights[gc][gb] = true; - - break; - } - - case LIGHTSMODE_TEST_MANUAL_CYCLE: - { - ZERO( m_LightsState.m_bGameButtonLights ); - - vector vGI; - GetUsedGameInputs( vGI ); - - if( m_iControllerTestManualCycleCurrent != -1 ) - { - GameController gc = vGI[m_iControllerTestManualCycleCurrent].controller; - GameButton gb = vGI[m_iControllerTestManualCycleCurrent].button; - m_LightsState.m_bGameButtonLights[gc][gb] = true; - } - - break; - } - } - - // apply new light values we set above - FOREACH( LightsDriver*, m_vpDrivers, iter ) - (*iter)->Set( &m_LightsState ); -} - -void LightsManager::BlinkCabinetLight( CabinetLight cl ) -{ - m_fSecsLeftInCabinetLightBlink[cl] = g_fLightsFalloffSeconds; -} - -void LightsManager::BlinkGameButton( GameInput gi ) -{ - m_fSecsLeftInGameButtonBlink[gi.controller][gi.button] = g_fLightsFalloffSeconds; -} - -void LightsManager::SetLightsMode( LightsMode lm ) -{ - m_LightsMode = lm; - m_fTestAutoCycleCurrentIndex = 0; - m_clTestManualCycleCurrent = CabinetLight_Invalid; - m_iControllerTestManualCycleCurrent = -1; -} - -LightsMode LightsManager::GetLightsMode() -{ - return m_LightsMode; -} - -void LightsManager::ChangeTestCabinetLight( int iDir ) -{ - m_iControllerTestManualCycleCurrent = -1; - - enum_add( m_clTestManualCycleCurrent, iDir ); - wrap( *ConvertValue(&m_clTestManualCycleCurrent), NUM_CabinetLight ); -} - -void LightsManager::ChangeTestGameButtonLight( int iDir ) -{ - m_clTestManualCycleCurrent = CabinetLight_Invalid; - - vector vGI; - GetUsedGameInputs( vGI ); - - m_iControllerTestManualCycleCurrent += iDir; - wrap( m_iControllerTestManualCycleCurrent, vGI.size() ); -} - -CabinetLight LightsManager::GetFirstLitCabinetLight() -{ - FOREACH_CabinetLight( cl ) - { - if( m_LightsState.m_bCabinetLights[cl] ) - return cl; - } - return CabinetLight_Invalid; -} - -GameInput LightsManager::GetFirstLitGameButtonLight() -{ - FOREACH_ENUM( GameController, gc ) - { - FOREACH_ENUM( GameButton, gb ) - { - if( m_LightsState.m_bGameButtonLights[gc][gb] ) - return GameInput( gc, gb ); - } - } - return GameInput(); -} - -bool LightsManager::IsEnabled() const -{ - return m_vpDrivers.size() >= 1 || PREFSMAN->m_bDebugLights; -} - -/* - * (c) 2003-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 "LightsManager.h" +#include "GameState.h" +#include "RageTimer.h" +#include "arch/Lights/LightsDriver.h" +#include "RageUtil.h" +#include "GameInput.h" // for GameController +#include "InputMapper.h" +#include "Game.h" +#include "PrefsManager.h" +#include "Actor.h" +#include "Preference.h" +#include "Foreach.h" +#include "GameManager.h" +#include "CommonMetrics.h" +#include "Style.h" + +const RString DEFAULT_LIGHTS_DRIVER = "PIUIO,SystemMessage,Export"; +static Preference g_sLightsDriver( "LightsDriver", "" ); // "" == DEFAULT_LIGHTS_DRIVER +Preference g_fLightsFalloffSeconds( "LightsFalloffSeconds", 0.1f ); +Preference g_fLightsAheadSeconds( "LightsAheadSeconds", 0.05f ); +static Preference g_bBlinkGameplayButtonLightsOnNote( "BlinkGameplayButtonLightsOnNote", false ); + +static ThemeMetric GAME_BUTTONS_TO_SHOW( "LightsManager", "GameButtonsToShow" ); + +static const char *CabinetLightNames[] = { + "MarqueeUpLeft", + "MarqueeUpRight", + "MarqueeLrLeft", + "MarqueeLrRight", + "BassLeft", + "BassRight", +}; +XToString( CabinetLight ); +StringToX( CabinetLight ); + +static const char *LightsModeNames[] = { + "Attract", + "Joining", + "MenuStartOnly", + "MenuStartAndDirections", + "Demonstration", + "Gameplay", + "Stage", + "Cleared", + "TestAutoCycle", + "TestManualCycle", +}; +XToString( LightsMode ); +LuaXType( LightsMode ); + +static void GetUsedGameInputs( vector &vGameInputsOut ) +{ + vGameInputsOut.clear(); + + vector asGameButtons; + split( GAME_BUTTONS_TO_SHOW.GetValue(), ",", asGameButtons ); + FOREACH_ENUM( GameController, gc ) + { + FOREACH_CONST( RString, asGameButtons, button ) + { + GameButton gb = StringToGameButton( INPUTMAPPER->GetInputScheme(), *button ); + if( gb != GameButton_Invalid ) + { + GameInput gi = GameInput( gc, gb ); + vGameInputsOut.push_back( gi ); + } + } + } + + set vGIs; + vector vStyles; + GAMEMAN->GetStylesForGame( GAMESTATE->m_pCurGame, vStyles ); + FOREACH( const Style*, vStyles, style ) + { + bool bFound = find( CommonMetrics::STEPS_TYPES_TO_SHOW.GetValue().begin(), CommonMetrics::STEPS_TYPES_TO_SHOW.GetValue().end(), (*style)->m_StepsType ) != CommonMetrics::STEPS_TYPES_TO_SHOW.GetValue().end(); + if( !bFound ) + continue; + FOREACH_PlayerNumber( pn ) + { + for( int iCol=0; iCol<(*style)->m_iColsPerPlayer; ++iCol ) + { + GameInput gi = (*style)->StyleInputToGameInput( iCol, pn ); + if( gi.IsValid() ) + { + vGIs.insert( gi ); + } + } + } + } + + for (GameInput const &input : vGIs) + vGameInputsOut.push_back( input ); +} + +LightsManager* LIGHTSMAN = NULL; // global and accessable from anywhere in our program + +LightsManager::LightsManager() +{ + ZERO( m_fSecsLeftInCabinetLightBlink ); + ZERO( m_fSecsLeftInGameButtonBlink ); + ZERO( m_fActorLights ); + ZERO( m_fSecsLeftInActorLightBlink ); + m_iQueuedCoinCounterPulses = 0; + m_CoinCounterTimer.SetZero(); + + m_LightsMode = LIGHTSMODE_JOINING; + RString sDriver = g_sLightsDriver.Get(); + if( sDriver.empty() ) + sDriver = DEFAULT_LIGHTS_DRIVER; + LightsDriver::Create( sDriver, m_vpDrivers ); + + SetLightsMode( LIGHTSMODE_ATTRACT ); +} + +LightsManager::~LightsManager() +{ + FOREACH( LightsDriver*, m_vpDrivers, iter ) + SAFE_DELETE( *iter ); + m_vpDrivers.clear(); +} + +// XXX: Allow themer to change these. (rewritten; who wrote original? -aj) +static const float g_fLightEffectRiseSeconds = 0.075f; +static const float g_fLightEffectFalloffSeconds = 0.35f; +static const float g_fCoinPulseTime = 0.100f; +void LightsManager::BlinkActorLight( CabinetLight cl ) +{ + m_fSecsLeftInActorLightBlink[cl] = g_fLightEffectRiseSeconds; +} + +float LightsManager::GetActorLightLatencySeconds() const +{ + return g_fLightEffectRiseSeconds; +} + +void LightsManager::Update( float fDeltaTime ) +{ + // Update actor effect lights. + FOREACH_CabinetLight( cl ) + { + float fTime = fDeltaTime; + float &fDuration = m_fSecsLeftInActorLightBlink[cl]; + if( fDuration > 0 ) + { + // The light has power left. Brighten it. + float fSeconds = min( fDuration, fTime ); + fDuration -= fSeconds; + fTime -= fSeconds; + fapproach( m_fActorLights[cl], 1, fSeconds / g_fLightEffectRiseSeconds ); + } + + if( fTime > 0 ) + { + // The light is out of power. Dim it. + fapproach( m_fActorLights[cl], 0, fTime / g_fLightEffectFalloffSeconds ); + } + + Actor::SetBGMLight( cl, m_fActorLights[cl] ); + } + + if( !IsEnabled() ) + return; + + // update lights falloff + { + FOREACH_CabinetLight( cl ) + fapproach( m_fSecsLeftInCabinetLightBlink[cl], 0, fDeltaTime ); + FOREACH_ENUM( GameController, gc ) + FOREACH_ENUM( GameButton, gb ) + fapproach( m_fSecsLeftInGameButtonBlink[gc][gb], 0, fDeltaTime ); + } + + // Set new lights state cabinet lights + { + ZERO( m_LightsState.m_bCabinetLights ); + ZERO( m_LightsState.m_bGameButtonLights ); + } + + { + m_LightsState.m_bCoinCounter = false; + if( !m_CoinCounterTimer.IsZero() ) + { + float fAgo = m_CoinCounterTimer.Ago(); + if( fAgo < g_fCoinPulseTime ) + m_LightsState.m_bCoinCounter = true; + else if( fAgo >= g_fCoinPulseTime * 2 ) + m_CoinCounterTimer.SetZero(); + } + else if( m_iQueuedCoinCounterPulses ) + { + m_CoinCounterTimer.Touch(); + --m_iQueuedCoinCounterPulses; + } + } + + if( m_LightsMode == LIGHTSMODE_TEST_AUTO_CYCLE ) + { + m_fTestAutoCycleCurrentIndex += fDeltaTime; + m_fTestAutoCycleCurrentIndex = fmodf( m_fTestAutoCycleCurrentIndex, NUM_CabinetLight*100 ); + } + + switch( m_LightsMode ) + { + DEFAULT_FAIL( m_LightsMode ); + + case LIGHTSMODE_ATTRACT: + { + int iSec = (int)RageTimer::GetTimeSinceStartFast(); + int iTopIndex = iSec % 4; + + // Aldo: Disabled this line, apparently it was a forgotten initialization + //CabinetLight cl = CabinetLight_Invalid; + + switch( iTopIndex ) + { + DEFAULT_FAIL( iTopIndex ); + case 0: m_LightsState.m_bCabinetLights[LIGHT_MARQUEE_UP_LEFT] = true; break; + case 1: m_LightsState.m_bCabinetLights[LIGHT_MARQUEE_LR_RIGHT] = true; break; + case 2: m_LightsState.m_bCabinetLights[LIGHT_MARQUEE_UP_RIGHT] = true; break; + case 3: m_LightsState.m_bCabinetLights[LIGHT_MARQUEE_LR_LEFT] = true; break; + } + + if( iTopIndex == 0 ) + { + m_LightsState.m_bCabinetLights[LIGHT_BASS_LEFT] = true; + m_LightsState.m_bCabinetLights[LIGHT_BASS_RIGHT] = true; + } + + break; + } + case LIGHTSMODE_MENU_START_ONLY: + case LIGHTSMODE_MENU_START_AND_DIRECTIONS: + case LIGHTSMODE_JOINING: + { + static int iLight; + + // if we've crossed a beat boundary, advance the light index + { + static float fLastBeat; + float fLightSongBeat = GAMESTATE->m_Position.m_fLightSongBeat; + + if( fracf(fLightSongBeat) < fracf(fLastBeat) ) + { + ++iLight; + wrap( iLight, 4 ); + } + + fLastBeat = fLightSongBeat; + } + + CabinetLight cl = CabinetLight_Invalid; + + switch( iLight ) + { + DEFAULT_FAIL( iLight ); + case 0: cl = LIGHT_MARQUEE_UP_LEFT; break; + case 1: cl = LIGHT_MARQUEE_LR_RIGHT; break; + case 2: cl = LIGHT_MARQUEE_UP_RIGHT; break; + case 3: cl = LIGHT_MARQUEE_LR_LEFT; break; + } + + m_LightsState.m_bCabinetLights[cl] = true; + + break; + } + + case LIGHTSMODE_DEMONSTRATION: + case LIGHTSMODE_GAMEPLAY: + { + FOREACH_CabinetLight( cl ) + m_LightsState.m_bCabinetLights[cl] = m_fSecsLeftInCabinetLightBlink[cl] > 0; + + break; + } + + case LIGHTSMODE_STAGE: + case LIGHTSMODE_ALL_CLEARED: + { + FOREACH_CabinetLight( cl ) + m_LightsState.m_bCabinetLights[cl] = true; + + break; + } + + case LIGHTSMODE_TEST_AUTO_CYCLE: + { + int iSec = GetTestAutoCycleCurrentIndex(); + + CabinetLight cl = CabinetLight(iSec % NUM_CabinetLight); + m_LightsState.m_bCabinetLights[cl] = true; + + break; + } + + case LIGHTSMODE_TEST_MANUAL_CYCLE: + { + CabinetLight cl = m_clTestManualCycleCurrent; + m_LightsState.m_bCabinetLights[cl] = true; + + break; + } + } + + + // Update game controller lights + switch( m_LightsMode ) + { + DEFAULT_FAIL( m_LightsMode ); + + case LIGHTSMODE_ALL_CLEARED: + case LIGHTSMODE_STAGE: + case LIGHTSMODE_JOINING: + { + FOREACH_ENUM( GameController, gc ) + { + if( GAMESTATE->m_bSideIsJoined[gc] ) + { + FOREACH_ENUM( GameButton, gb ) + m_LightsState.m_bGameButtonLights[gc][gb] = true; + } + } + + break; + } + + case LIGHTSMODE_MENU_START_ONLY: + case LIGHTSMODE_MENU_START_AND_DIRECTIONS: + { + float fLightSongBeat = GAMESTATE->m_Position.m_fLightSongBeat; + + /* Blink menu lights on the first half of the beat */ + if( fracf(fLightSongBeat) <= 0.5f ) + { + FOREACH_PlayerNumber( pn ) + { + if( !GAMESTATE->m_bSideIsJoined[pn] ) + continue; + + m_LightsState.m_bGameButtonLights[pn][GAME_BUTTON_START] = true; + + if( m_LightsMode == LIGHTSMODE_MENU_START_AND_DIRECTIONS ) + { + m_LightsState.m_bGameButtonLights[pn][GAME_BUTTON_MENULEFT] = true; + m_LightsState.m_bGameButtonLights[pn][GAME_BUTTON_MENURIGHT] = true; + } + } + } + + // fall through to blink on button presses + } + + case LIGHTSMODE_DEMONSTRATION: + case LIGHTSMODE_GAMEPLAY: + { + bool bGameplay = (m_LightsMode == LIGHTSMODE_DEMONSTRATION) || (m_LightsMode == LIGHTSMODE_GAMEPLAY); + + // Blink on notes during gameplay. + if( bGameplay && g_bBlinkGameplayButtonLightsOnNote ) + { + FOREACH_ENUM( GameController, gc ) + { + FOREACH_ENUM( GameButton, gb ) + { + m_LightsState.m_bGameButtonLights[gc][gb] = m_fSecsLeftInGameButtonBlink[gc][gb] > 0 ; + } + } + } + + // fall through to blink on button presses + } + + case LIGHTSMODE_ATTRACT: + { + // Blink on button presses. + FOREACH_ENUM( GameController, gc ) + { + FOREACH_GameButton_Custom( gb ) + { + bool bOn = INPUTMAPPER->IsBeingPressed( GameInput(gc,gb) ); + m_LightsState.m_bGameButtonLights[gc][gb] = bOn; + } + } + + break; + } + + case LIGHTSMODE_TEST_AUTO_CYCLE: + { + int index = GetTestAutoCycleCurrentIndex(); + + vector vGI; + GetUsedGameInputs( vGI ); + wrap( index, vGI.size() ); + + ZERO( m_LightsState.m_bGameButtonLights ); + + GameController gc = vGI[index].controller; + GameButton gb = vGI[index].button; + m_LightsState.m_bGameButtonLights[gc][gb] = true; + + break; + } + + case LIGHTSMODE_TEST_MANUAL_CYCLE: + { + ZERO( m_LightsState.m_bGameButtonLights ); + + vector vGI; + GetUsedGameInputs( vGI ); + + if( m_iControllerTestManualCycleCurrent != -1 ) + { + GameController gc = vGI[m_iControllerTestManualCycleCurrent].controller; + GameButton gb = vGI[m_iControllerTestManualCycleCurrent].button; + m_LightsState.m_bGameButtonLights[gc][gb] = true; + } + + break; + } + } + + // apply new light values we set above + FOREACH( LightsDriver*, m_vpDrivers, iter ) + (*iter)->Set( &m_LightsState ); +} + +void LightsManager::BlinkCabinetLight( CabinetLight cl ) +{ + m_fSecsLeftInCabinetLightBlink[cl] = g_fLightsFalloffSeconds; +} + +void LightsManager::BlinkGameButton( GameInput gi ) +{ + m_fSecsLeftInGameButtonBlink[gi.controller][gi.button] = g_fLightsFalloffSeconds; +} + +void LightsManager::SetLightsMode( LightsMode lm ) +{ + m_LightsMode = lm; + m_fTestAutoCycleCurrentIndex = 0; + m_clTestManualCycleCurrent = CabinetLight_Invalid; + m_iControllerTestManualCycleCurrent = -1; +} + +LightsMode LightsManager::GetLightsMode() +{ + return m_LightsMode; +} + +void LightsManager::ChangeTestCabinetLight( int iDir ) +{ + m_iControllerTestManualCycleCurrent = -1; + + enum_add( m_clTestManualCycleCurrent, iDir ); + wrap( *ConvertValue(&m_clTestManualCycleCurrent), NUM_CabinetLight ); +} + +void LightsManager::ChangeTestGameButtonLight( int iDir ) +{ + m_clTestManualCycleCurrent = CabinetLight_Invalid; + + vector vGI; + GetUsedGameInputs( vGI ); + + m_iControllerTestManualCycleCurrent += iDir; + wrap( m_iControllerTestManualCycleCurrent, vGI.size() ); +} + +CabinetLight LightsManager::GetFirstLitCabinetLight() +{ + FOREACH_CabinetLight( cl ) + { + if( m_LightsState.m_bCabinetLights[cl] ) + return cl; + } + return CabinetLight_Invalid; +} + +GameInput LightsManager::GetFirstLitGameButtonLight() +{ + FOREACH_ENUM( GameController, gc ) + { + FOREACH_ENUM( GameButton, gb ) + { + if( m_LightsState.m_bGameButtonLights[gc][gb] ) + return GameInput( gc, gb ); + } + } + return GameInput(); +} + +bool LightsManager::IsEnabled() const +{ + return m_vpDrivers.size() >= 1 || PREFSMAN->m_bDebugLights; +} + +/* + * (c) 2003-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. + */ From 46d15c23dcd3b1d56e053aaffcb8f39063e82c08 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:36:19 -0400 Subject: [PATCH 013/281] Another loop, another white spaced file. --- src/MessageManager.cpp | 659 ++++++++++++++++++++--------------------- 1 file changed, 329 insertions(+), 330 deletions(-) diff --git a/src/MessageManager.cpp b/src/MessageManager.cpp index d1aac2c00f..a340c227f9 100644 --- a/src/MessageManager.cpp +++ b/src/MessageManager.cpp @@ -1,330 +1,329 @@ -#include "global.h" -#include "MessageManager.h" -#include "Foreach.h" -#include "RageUtil.h" -#include "RageThreads.h" -#include "EnumHelper.h" -#include "LuaManager.h" - -#include -#include - -MessageManager* MESSAGEMAN = NULL; // global and accessable from anywhere in our program - - -static const char *MessageIDNames[] = { - "CurrentGameChanged", - "CurrentStyleChanged", - "PlayModeChanged", - "CurrentSongChanged", - "CurrentStepsP1Changed", - "CurrentStepsP2Changed", - "CurrentCourseChanged", - "CurrentTrailP1Changed", - "CurrentTrailP2Changed", - "GameplayLeadInChanged", - "EditStepsTypeChanged", - "EditCourseDifficultyChanged", - "EditSourceStepsChanged", - "EditSourceStepsTypeChanged", - "PreferredStepsTypeChanged", - "PreferredDifficultyP1Changed", - "PreferredDifficultyP2Changed", - "PreferredCourseDifficultyP1Changed", - "PreferredCourseDifficultyP2Changed", - "EditCourseEntryIndexChanged", - "EditLocalProfileIDChanged", - "GoalCompleteP1", - "GoalCompleteP2", - "NoteCrossed", - "NoteWillCrossIn400Ms", - "NoteWillCrossIn800Ms", - "NoteWillCrossIn1200Ms", - "CardRemovedP1", - "CardRemovedP2", - "BeatCrossed", - "MenuUpP1", - "MenuUpP2", - "MenuDownP1", - "MenuDownP2", - "MenuLeftP1", - "MenuLeftP2", - "MenuRightP1", - "MenuRightP2", - "MenuStartP1", - "MenuStartP2", - "MenuSelectionChanged", - "PlayerJoined", - "PlayerUnjoined", - "AutosyncChanged", - "PreferredSongGroupChanged", - "PreferredCourseGroupChanged", - "SortOrderChanged", - "LessonTry1", - "LessonTry2", - "LessonTry3", - "LessonCleared", - "LessonFailed", - "StorageDevicesChanged", - "AutoJoyMappingApplied", - "ScreenChanged", - "SongModified", - "ScoreMultiplierChangedP1", - "ScoreMultiplierChangedP2", - "StarPowerChangedP1", - "StarPowerChangedP2", - "CurrentComboChangedP1", - "CurrentComboChangedP2", - "StarMeterChangedP1", - "StarMeterChangedP2", - "LifeMeterChangedP1", - "LifeMeterChangedP2", - "UpdateScreenHeader", - "LeftClick", - "RightClick", - "MiddleClick", - "MouseWheelUp", - "MouseWheelDown", -}; -XToString( MessageID ); - -static RageMutex g_Mutex( "MessageManager" ); - -typedef set SubscribersSet; -static map g_MessageToSubscribers; - -Message::Message( const RString &s ) -{ - m_sName = s; - m_pParams = new LuaTable; - m_bBroadcast = false; -} - -Message::Message( const RString &s, const LuaReference ¶ms ) -{ - m_sName = s; - m_bBroadcast = false; - Lua *L = LUA->Get(); - m_pParams = new LuaTable; // XXX: creates an extra table - params.PushSelf( L ); - m_pParams->SetFromStack( L ); - LUA->Release( L ); -// m_pParams = new LuaTable( params ); -} - -Message::~Message() -{ - delete m_pParams; -} - -void Message::PushParamTable( lua_State *L ) -{ - m_pParams->PushSelf( L ); -} - -void Message::SetParamTable( const LuaReference ¶ms ) -{ - Lua *L = LUA->Get(); - params.PushSelf( L ); - m_pParams->SetFromStack( L ); - LUA->Release( L ); -} - -const LuaReference &Message::GetParamTable() const -{ - return *m_pParams; -} - -void Message::GetParamFromStack( lua_State *L, const RString &sName ) const -{ - m_pParams->Get( L, sName ); -} - -void Message::SetParamFromStack( lua_State *L, const RString &sName ) -{ - m_pParams->Set( L, sName ); -} - -MessageManager::MessageManager() -{ - // Register with Lua. - { - Lua *L = LUA->Get(); - lua_pushstring( L, "MESSAGEMAN" ); - this->PushSelf( L ); - lua_settable( L, LUA_GLOBALSINDEX ); - LUA->Release( L ); - } -} - -MessageManager::~MessageManager() -{ - // Unregister with Lua. - LUA->UnsetGlobal( "MESSAGEMAN" ); -} - -void MessageManager::Subscribe( IMessageSubscriber* pSubscriber, const RString& sMessage ) -{ - LockMut(g_Mutex); - - SubscribersSet& subs = g_MessageToSubscribers[sMessage]; -#ifdef DEBUG - SubscribersSet::iterator iter = subs.find(pSubscriber); - ASSERT_M( iter == subs.end(), ssprintf("already subscribed to '%s'",sMessage.c_str()) ); -#endif - subs.insert( pSubscriber ); -} - -void MessageManager::Subscribe( IMessageSubscriber* pSubscriber, MessageID m ) -{ - Subscribe( pSubscriber, MessageIDToString(m) ); -} - -void MessageManager::Unsubscribe( IMessageSubscriber* pSubscriber, const RString& sMessage ) -{ - LockMut(g_Mutex); - - SubscribersSet& subs = g_MessageToSubscribers[sMessage]; - SubscribersSet::iterator iter = subs.find(pSubscriber); - ASSERT( iter != subs.end() ); - subs.erase( iter ); -} - -void MessageManager::Unsubscribe( IMessageSubscriber* pSubscriber, MessageID m ) -{ - Unsubscribe( pSubscriber, MessageIDToString(m) ); -} - -void MessageManager::Broadcast( Message &msg ) const -{ - msg.SetBroadcast(true); - - LockMut(g_Mutex); - - map::const_iterator iter = g_MessageToSubscribers.find( msg.GetName() ); - if( iter == g_MessageToSubscribers.end() ) - return; - - FOREACHS_CONST( IMessageSubscriber*, iter->second, p ) - { - IMessageSubscriber *pSub = *p; - pSub->HandleMessage( msg ); - } -} - -void MessageManager::Broadcast( const RString& sMessage ) const -{ - ASSERT( !sMessage.empty() ); - Message msg(sMessage); - Broadcast( msg ); -} - -void MessageManager::Broadcast( MessageID m ) const -{ - Broadcast( MessageIDToString(m) ); -} - -bool MessageManager::IsSubscribedToMessage( IMessageSubscriber* pSubscriber, const RString &sMessage ) const -{ - SubscribersSet& subs = g_MessageToSubscribers[sMessage]; - return subs.find( pSubscriber ) != subs.end(); -} - -void IMessageSubscriber::ClearMessages( const RString sMessage ) -{ -} - -MessageSubscriber::MessageSubscriber( const MessageSubscriber &cpy ): - IMessageSubscriber(cpy) -{ - FOREACH_CONST( RString, cpy.m_vsSubscribedTo, msg ) - this->SubscribeToMessage( *msg ); -} - -MessageSubscriber &MessageSubscriber::operator=(const MessageSubscriber &cpy) -{ - if(&cpy == this) - return *this; - - UnsubscribeAll(); - - FOREACH_CONST( RString, cpy.m_vsSubscribedTo, msg ) - this->SubscribeToMessage( *msg ); - - return *this; -} - -void MessageSubscriber::SubscribeToMessage( const RString &sMessageName ) -{ - MESSAGEMAN->Subscribe( this, sMessageName ); - m_vsSubscribedTo.push_back( sMessageName ); -} - -void MessageSubscriber::SubscribeToMessage( MessageID message ) -{ - MESSAGEMAN->Subscribe( this, message ); - m_vsSubscribedTo.push_back( MessageIDToString(message) ); -} - -void MessageSubscriber::UnsubscribeAll() -{ - FOREACH_CONST( RString, m_vsSubscribedTo, s ) - MESSAGEMAN->Unsubscribe( this, *s ); - m_vsSubscribedTo.clear(); -} - - -// lua start -#include "LuaBinding.h" - -/** @brief Allow Lua to have access to the MessageManager. */ -class LunaMessageManager: public Luna -{ -public: - static int Broadcast( T* p, lua_State *L ) - { - if( !lua_istable(L, 2) && !lua_isnoneornil(L, 2) ) - luaL_typerror( L, 2, "table or nil" ); - - LuaReference ParamTable; - lua_pushvalue( L, 2 ); - ParamTable.SetFromStack( L ); - - Message msg( SArg(1), ParamTable ); - p->Broadcast( msg ); - return 0; - } - - LunaMessageManager() - { - ADD_METHOD( Broadcast ); - } -}; - -LUA_REGISTER_CLASS( MessageManager ) -// lua end - -/* - * (c) 2003-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 "MessageManager.h" +#include "Foreach.h" +#include "RageUtil.h" +#include "RageThreads.h" +#include "EnumHelper.h" +#include "LuaManager.h" + +#include +#include + +MessageManager* MESSAGEMAN = NULL; // global and accessable from anywhere in our program + + +static const char *MessageIDNames[] = { + "CurrentGameChanged", + "CurrentStyleChanged", + "PlayModeChanged", + "CurrentSongChanged", + "CurrentStepsP1Changed", + "CurrentStepsP2Changed", + "CurrentCourseChanged", + "CurrentTrailP1Changed", + "CurrentTrailP2Changed", + "GameplayLeadInChanged", + "EditStepsTypeChanged", + "EditCourseDifficultyChanged", + "EditSourceStepsChanged", + "EditSourceStepsTypeChanged", + "PreferredStepsTypeChanged", + "PreferredDifficultyP1Changed", + "PreferredDifficultyP2Changed", + "PreferredCourseDifficultyP1Changed", + "PreferredCourseDifficultyP2Changed", + "EditCourseEntryIndexChanged", + "EditLocalProfileIDChanged", + "GoalCompleteP1", + "GoalCompleteP2", + "NoteCrossed", + "NoteWillCrossIn400Ms", + "NoteWillCrossIn800Ms", + "NoteWillCrossIn1200Ms", + "CardRemovedP1", + "CardRemovedP2", + "BeatCrossed", + "MenuUpP1", + "MenuUpP2", + "MenuDownP1", + "MenuDownP2", + "MenuLeftP1", + "MenuLeftP2", + "MenuRightP1", + "MenuRightP2", + "MenuStartP1", + "MenuStartP2", + "MenuSelectionChanged", + "PlayerJoined", + "PlayerUnjoined", + "AutosyncChanged", + "PreferredSongGroupChanged", + "PreferredCourseGroupChanged", + "SortOrderChanged", + "LessonTry1", + "LessonTry2", + "LessonTry3", + "LessonCleared", + "LessonFailed", + "StorageDevicesChanged", + "AutoJoyMappingApplied", + "ScreenChanged", + "SongModified", + "ScoreMultiplierChangedP1", + "ScoreMultiplierChangedP2", + "StarPowerChangedP1", + "StarPowerChangedP2", + "CurrentComboChangedP1", + "CurrentComboChangedP2", + "StarMeterChangedP1", + "StarMeterChangedP2", + "LifeMeterChangedP1", + "LifeMeterChangedP2", + "UpdateScreenHeader", + "LeftClick", + "RightClick", + "MiddleClick", + "MouseWheelUp", + "MouseWheelDown", +}; +XToString( MessageID ); + +static RageMutex g_Mutex( "MessageManager" ); + +typedef set SubscribersSet; +static map g_MessageToSubscribers; + +Message::Message( const RString &s ) +{ + m_sName = s; + m_pParams = new LuaTable; + m_bBroadcast = false; +} + +Message::Message( const RString &s, const LuaReference ¶ms ) +{ + m_sName = s; + m_bBroadcast = false; + Lua *L = LUA->Get(); + m_pParams = new LuaTable; // XXX: creates an extra table + params.PushSelf( L ); + m_pParams->SetFromStack( L ); + LUA->Release( L ); +// m_pParams = new LuaTable( params ); +} + +Message::~Message() +{ + delete m_pParams; +} + +void Message::PushParamTable( lua_State *L ) +{ + m_pParams->PushSelf( L ); +} + +void Message::SetParamTable( const LuaReference ¶ms ) +{ + Lua *L = LUA->Get(); + params.PushSelf( L ); + m_pParams->SetFromStack( L ); + LUA->Release( L ); +} + +const LuaReference &Message::GetParamTable() const +{ + return *m_pParams; +} + +void Message::GetParamFromStack( lua_State *L, const RString &sName ) const +{ + m_pParams->Get( L, sName ); +} + +void Message::SetParamFromStack( lua_State *L, const RString &sName ) +{ + m_pParams->Set( L, sName ); +} + +MessageManager::MessageManager() +{ + // Register with Lua. + { + Lua *L = LUA->Get(); + lua_pushstring( L, "MESSAGEMAN" ); + this->PushSelf( L ); + lua_settable( L, LUA_GLOBALSINDEX ); + LUA->Release( L ); + } +} + +MessageManager::~MessageManager() +{ + // Unregister with Lua. + LUA->UnsetGlobal( "MESSAGEMAN" ); +} + +void MessageManager::Subscribe( IMessageSubscriber* pSubscriber, const RString& sMessage ) +{ + LockMut(g_Mutex); + + SubscribersSet& subs = g_MessageToSubscribers[sMessage]; +#ifdef DEBUG + SubscribersSet::iterator iter = subs.find(pSubscriber); + ASSERT_M( iter == subs.end(), ssprintf("already subscribed to '%s'",sMessage.c_str()) ); +#endif + subs.insert( pSubscriber ); +} + +void MessageManager::Subscribe( IMessageSubscriber* pSubscriber, MessageID m ) +{ + Subscribe( pSubscriber, MessageIDToString(m) ); +} + +void MessageManager::Unsubscribe( IMessageSubscriber* pSubscriber, const RString& sMessage ) +{ + LockMut(g_Mutex); + + SubscribersSet& subs = g_MessageToSubscribers[sMessage]; + SubscribersSet::iterator iter = subs.find(pSubscriber); + ASSERT( iter != subs.end() ); + subs.erase( iter ); +} + +void MessageManager::Unsubscribe( IMessageSubscriber* pSubscriber, MessageID m ) +{ + Unsubscribe( pSubscriber, MessageIDToString(m) ); +} + +void MessageManager::Broadcast( Message &msg ) const +{ + msg.SetBroadcast(true); + + LockMut(g_Mutex); + + map::const_iterator iter = g_MessageToSubscribers.find( msg.GetName() ); + if( iter == g_MessageToSubscribers.end() ) + return; + + for (IMessageSubscriber *subscriber : iter->second) + { + subscriber->HandleMessage( msg ); + } +} + +void MessageManager::Broadcast( const RString& sMessage ) const +{ + ASSERT( !sMessage.empty() ); + Message msg(sMessage); + Broadcast( msg ); +} + +void MessageManager::Broadcast( MessageID m ) const +{ + Broadcast( MessageIDToString(m) ); +} + +bool MessageManager::IsSubscribedToMessage( IMessageSubscriber* pSubscriber, const RString &sMessage ) const +{ + SubscribersSet& subs = g_MessageToSubscribers[sMessage]; + return subs.find( pSubscriber ) != subs.end(); +} + +void IMessageSubscriber::ClearMessages( const RString sMessage ) +{ +} + +MessageSubscriber::MessageSubscriber( const MessageSubscriber &cpy ): + IMessageSubscriber(cpy) +{ + FOREACH_CONST( RString, cpy.m_vsSubscribedTo, msg ) + this->SubscribeToMessage( *msg ); +} + +MessageSubscriber &MessageSubscriber::operator=(const MessageSubscriber &cpy) +{ + if(&cpy == this) + return *this; + + UnsubscribeAll(); + + FOREACH_CONST( RString, cpy.m_vsSubscribedTo, msg ) + this->SubscribeToMessage( *msg ); + + return *this; +} + +void MessageSubscriber::SubscribeToMessage( const RString &sMessageName ) +{ + MESSAGEMAN->Subscribe( this, sMessageName ); + m_vsSubscribedTo.push_back( sMessageName ); +} + +void MessageSubscriber::SubscribeToMessage( MessageID message ) +{ + MESSAGEMAN->Subscribe( this, message ); + m_vsSubscribedTo.push_back( MessageIDToString(message) ); +} + +void MessageSubscriber::UnsubscribeAll() +{ + FOREACH_CONST( RString, m_vsSubscribedTo, s ) + MESSAGEMAN->Unsubscribe( this, *s ); + m_vsSubscribedTo.clear(); +} + + +// lua start +#include "LuaBinding.h" + +/** @brief Allow Lua to have access to the MessageManager. */ +class LunaMessageManager: public Luna +{ +public: + static int Broadcast( T* p, lua_State *L ) + { + if( !lua_istable(L, 2) && !lua_isnoneornil(L, 2) ) + luaL_typerror( L, 2, "table or nil" ); + + LuaReference ParamTable; + lua_pushvalue( L, 2 ); + ParamTable.SetFromStack( L ); + + Message msg( SArg(1), ParamTable ); + p->Broadcast( msg ); + return 0; + } + + LunaMessageManager() + { + ADD_METHOD( Broadcast ); + } +}; + +LUA_REGISTER_CLASS( MessageManager ) +// lua end + +/* + * (c) 2003-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. + */ From 048109dbd61529349b1ba26de31323932abdb979 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:40:22 -0400 Subject: [PATCH 014/281] Cleaner loops ahoy! --- src/ThemeManager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ThemeManager.cpp b/src/ThemeManager.cpp index 4a390ea901..f2ac2c6c61 100644 --- a/src/ThemeManager.cpp +++ b/src/ThemeManager.cpp @@ -431,8 +431,8 @@ void ThemeManager::ReloadSubscribers() // reload subscribers if( g_Subscribers.m_pSubscribers ) { - FOREACHS_CONST( IThemeMetric*, *g_Subscribers.m_pSubscribers, p ) - (*p)->Read(); + for (IThemeMetric *metric : *g_Subscribers.m_pSubscribers) + metric->Read(); } } @@ -440,8 +440,8 @@ void ThemeManager::ClearSubscribers() { if( g_Subscribers.m_pSubscribers ) { - FOREACHS_CONST( IThemeMetric*, *g_Subscribers.m_pSubscribers, p ) - (*p)->Clear(); + for (IThemeMetric *metric : *g_Subscribers.m_pSubscribers) + metric->Clear(); } } From 3ea1ab26a580778f7271ebe8daaab81041fde29d Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:42:19 -0400 Subject: [PATCH 015/281] Loops and...white space. I already regretted hgeol here. --- src/ScreenOptionsMasterPrefs.cpp | 1660 +++++++++++++++--------------- 1 file changed, 830 insertions(+), 830 deletions(-) diff --git a/src/ScreenOptionsMasterPrefs.cpp b/src/ScreenOptionsMasterPrefs.cpp index 516c0b2dd4..72d2b272e2 100644 --- a/src/ScreenOptionsMasterPrefs.cpp +++ b/src/ScreenOptionsMasterPrefs.cpp @@ -1,830 +1,830 @@ -#include "global.h" -#include "ScreenOptionsMasterPrefs.h" -#include "PrefsManager.h" -#include "ThemeManager.h" -#include "AnnouncerManager.h" -#include "NoteSkinManager.h" -#include "PlayerOptions.h" -#include "SongOptions.h" -#include "RageDisplay.h" -#include "RageUtil.h" -#include "GameManager.h" -#include "GameState.h" -#include "StepMania.h" -#include "Game.h" -#include "Foreach.h" -#include "GameConstantsAndTypes.h" -#include "DisplayResolutions.h" -#include "LocalizedString.h" -#include "SpecialFiles.h" -#include "RageLog.h" - -using namespace StringConversion; - -static void GetPrefsDefaultModifiers( PlayerOptions &po, SongOptions &so ) -{ - po.FromString( PREFSMAN->m_sDefaultModifiers ); - so.FromString( PREFSMAN->m_sDefaultModifiers ); -} - -static void SetPrefsDefaultModifiers( const PlayerOptions &po, const SongOptions &so ) -{ - vector as; - if( po.GetString() != "" ) - as.push_back( po.GetString() ); - if( so.GetString() != "" ) - as.push_back( so.GetString() ); - - PREFSMAN->m_sDefaultModifiers.Set( join(", ",as) ); -} - - -/* Ugly: the input values may be a different type than the mapping. For example, - * the mapping may be an enum, and value an int. This is because we don't - * have FromString/ToString for every enum type. Assume that the distance between - * T and U can be represented as a float. */ -template -int FindClosestEntry( T value, const U *mapping, unsigned cnt ) -{ - int iBestIndex = 0; - float best_dist = 0; - bool have_best = false; - - for( unsigned i = 0; i < cnt; ++i ) - { - const U val = mapping[i]; - float dist = value < val? (float)(val-value):(float)(value-val); - if( have_best && best_dist < dist ) - continue; - - have_best = true; - best_dist = dist; - - iBestIndex = i; - } - - if( have_best ) - return iBestIndex; - else - return 0; -} - -template -static void MoveMap( int &sel, T &opt, bool ToSel, const T *mapping, unsigned cnt ) -{ - if( ToSel ) - { - sel = FindClosestEntry( opt, mapping, cnt ); - } else { - // sel -> opt - opt = mapping[sel]; - } -} - -template -static void MoveMap( int &sel, IPreference &opt, bool ToSel, const T *mapping, unsigned cnt ) -{ - if( ToSel ) - { - RString sOpt = opt.ToString(); - // This should really be T, but we can't FromString an enum. - float val; - FromString( sOpt, val ); - sel = FindClosestEntry( val, mapping, cnt ); - } else { - // sel -> opt - RString sOpt = ToString( mapping[sel] ); - opt.FromString( sOpt ); - } -} - -template -static void MoveMap( int &sel, const ConfOption *pConfOption, bool ToSel, const T *mapping, unsigned cnt ) -{ - ASSERT( pConfOption != NULL ); - IPreference *pPref = IPreference::GetPreferenceByName( pConfOption->m_sPrefName ); - ASSERT_M( pPref != NULL, pConfOption->m_sPrefName ); - - MoveMap( sel, *pPref, ToSel, mapping, cnt ); -} - -template -static void MovePref( int &iSel, bool bToSel, const ConfOption *pConfOption ) -{ - IPreference *pPref = IPreference::GetPreferenceByName( pConfOption->m_sPrefName ); - ASSERT_M( pPref != NULL, pConfOption->m_sPrefName ); - - if( bToSel ) - { - // TODO: why not get the int directly from pPref? - // Why are we writing it to a string and then back? - T t; - FromString( pPref->ToString(), t ); - iSel = static_cast( t ); - } - else - { - pPref->FromString( ToString( static_cast( iSel ) ) ); - } -} - -template <> -void MovePref( int &iSel, bool bToSel, const ConfOption *pConfOption ) -{ - IPreference *pPref = IPreference::GetPreferenceByName( pConfOption->m_sPrefName ); - ASSERT_M( pPref != NULL, pConfOption->m_sPrefName ); - - if( bToSel ) - { - // TODO: why not get the int directly from pPref? - // Why are we writing it to a string and then back? - bool b; - FromString( pPref->ToString(), b ); - iSel = b ? 1 : 0; - } - else - { - // If we don't make a specific instantiation of MovePref, there is - // a compile warning here because of static_cast( iSel ) where - // iSel is an int. What is the best way to remove that compile warning? - pPref->FromString( ToString( iSel ? true : false ) ); - } -} - -static void MoveNop( int &iSel, bool bToSel, const ConfOption *pConfOption ) -{ - if( bToSel ) - iSel = 0; -} - -// TODO: Write GenerateValueList() function that can use ints and floats. -aj - -static void GameChoices( vector &out ) -{ - vector aGames; - GAMEMAN->GetEnabledGames( aGames ); - FOREACH( const Game*, aGames, g ) - { - RString sGameName = (*g)->m_szName; - out.push_back( sGameName ); - } -} - -static void GameSel( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - vector choices; - pConfOption->MakeOptionsList( choices ); - - if( ToSel ) - { - const RString sCurGameName = GAMESTATE->m_pCurGame->m_szName; - - sel = 0; - for(unsigned i = 0; i < choices.size(); ++i) - if( !stricmp(choices[i], sCurGameName) ) - sel = i; - } else { - vector aGames; - GAMEMAN->GetEnabledGames( aGames ); - StepMania::ChangeCurrentGame( aGames[sel] ); - /* Reload metrics to force a refresh of CommonMetrics::DIFFICULTIES_TO_SHOW, - * mainly if we're not switching themes. I'm not sure if this was the - * case going from theme to theme, but if it was, it should be fixed - * now. There's probably be a better way to do it, but I'm not sure - * what it'd be. -aj */ - THEME->ReloadMetrics(); - } -} - -static void LanguageChoices( vector &out ) -{ - vector vs; - THEME->GetLanguages( vs ); - SortRStringArray( vs, true ); - - FOREACH_CONST( RString, vs, s ) - { - const LanguageInfo *pLI = GetLanguageInfo( *s ); - if( pLI ) - out.push_back( THEME->GetString("NativeLanguageNames", pLI->szEnglishName) ); - else - out.push_back( *s ); - } -} - -static void Language( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - vector vs; - THEME->GetLanguages( vs ); - SortRStringArray( vs, true ); - - if( ToSel ) - { - sel = -1; - for( unsigned i=0; sel == -1 && i < vs.size(); ++i ) - if( !stricmp(vs[i], THEME->GetCurLanguage()) ) - sel = i; - - // If the current language doesn't exist, we'll show BASE_LANGUAGE, so select that. - for( unsigned i=0; sel == -1 && i < vs.size(); ++i ) - if( !stricmp(vs[i], SpecialFiles::BASE_LANGUAGE) ) - sel = i; - - if( sel == -1 ) - { - LOG->Warn( "Couldn't find language \"%s\" or fallback \"%s\"; using \"%s\"", - THEME->GetCurLanguage().c_str(), SpecialFiles::BASE_LANGUAGE.c_str(), vs[0].c_str() ); - sel = 0; - } - } else { - const RString &sNewLanguage = vs[sel]; - - PREFSMAN->m_sLanguage.Set( sNewLanguage ); - if( THEME->GetCurLanguage() != sNewLanguage ) - THEME->SwitchThemeAndLanguage( THEME->GetCurThemeName(), PREFSMAN->m_sLanguage, PREFSMAN->m_bPseudoLocalize ); - } -} - -static void ThemeChoices( vector &out ) -{ - THEME->GetSelectableThemeNames( out ); - FOREACH( RString, out, s ) - *s = THEME->GetThemeDisplayName( *s ); -} - -static void DisplayResolutionChoices( vector &out ) -{ - DisplayResolutions d; - DISPLAY->GetDisplayResolutions( d ); - - FOREACHS_CONST( DisplayResolution, d, iter ) - { - RString s = ssprintf("%dx%d", iter->iWidth, iter->iHeight); - out.push_back( s ); - } -} - -static void RequestedTheme( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - vector choices; - pConfOption->MakeOptionsList( choices ); - - vector vsThemeNames; - THEME->GetSelectableThemeNames( vsThemeNames ); - - if( ToSel ) - { - sel = 0; - for( unsigned i=1; im_sTheme.Get()) ) - sel = i; - } - else - { - const RString sNewTheme = vsThemeNames[sel]; - PREFSMAN->m_sTheme.Set( sNewTheme ); // OPT_APPLY_THEME will load the theme - } -} - -static LocalizedString OFF ("ScreenOptionsMasterPrefs","Off"); -static void AnnouncerChoices( vector &out ) -{ - ANNOUNCER->GetAnnouncerNames( out ); - out.insert( out.begin(), OFF ); -} - -static void Announcer( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - vector choices; - pConfOption->MakeOptionsList( choices ); - - if( ToSel ) - { - sel = 0; - for( unsigned i=1; iGetCurAnnouncerName()) ) - sel = i; - } - else - { - const RString sNewAnnouncer = sel? choices[sel]:RString(""); - ANNOUNCER->SwitchAnnouncer( sNewAnnouncer ); - PREFSMAN->m_sAnnouncer.Set( sNewAnnouncer ); - } -} - -static void DefaultNoteSkinChoices( vector &out ) -{ - NOTESKIN->GetNoteSkinNames( out ); -} - -static void DefaultNoteSkin( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - vector choices; - pConfOption->MakeOptionsList( choices ); - - if( ToSel ) - { - PlayerOptions po; - po.FromString( PREFSMAN->m_sDefaultModifiers ); - sel = 0; - for( unsigned i=0; i < choices.size(); i++ ) - if( !stricmp(choices[i], po.m_sNoteSkin) ) - sel = i; - } - else - { - PlayerOptions po; - SongOptions so; - GetPrefsDefaultModifiers( po, so ); - po.m_sNoteSkin = choices[sel]; - SetPrefsDefaultModifiers( po, so ); - } -} - -// Background options -static void BGBrightness( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - // TODO: I hate the way the list of numbers is duplicated here and where the - // option is created. Try to find a way to only use the same list once. - // Do that for all of these float and int lists. - const float mapping[] = { 0.0f,0.1f,0.2f,0.3f,0.4f,0.5f,0.6f,0.7f,0.8f,0.9f,1.0f }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void BGBrightnessNoZero( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const float mapping[] = { 0.1f,0.2f,0.3f,0.4f,0.5f,0.6f,0.7f,0.8f,0.9f,1.0f }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void BGBrightnessOrStatic( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const float mapping[] = { 0.5f,0.25f,0.5f,0.75f }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); - - IPreference *pSongBackgroundsPref = IPreference::GetPreferenceByName( "SongBackgrounds" ); - if( ToSel && pSongBackgroundsPref->ToString() == "0" ) - sel = 0; - if( !ToSel ) - pSongBackgroundsPref->FromString( sel? "1":"0" ); -} - -static void NumBackgrounds( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const int mapping[] = { 5,10,15,20 }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -// Input options -static void MusicWheelSwitchSpeed( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const int mapping[] = { 5, 10, 15, 25 }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void JointPremium( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const Premium mapping[] = { Premium_DoubleFor1Credit, Premium_2PlayersFor1Credit }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void SongsPerPlay( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const int mapping[] = { 1,2,3,4,5 }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void SongsPerPlayOrEventMode( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const int mapping[] = { 1,2,3,4,5,6 }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); - - if( ToSel && PREFSMAN->m_bEventMode ) - sel = 5; - if( !ToSel ) - PREFSMAN->m_bEventMode.Set( sel == 5 ); -} - -// Machine options -/** @brief Timing Window scale */ -static void TimingWindowScale( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - // StepMania 5 values (implemented 2008/03/12) - //const float mapping[] = { 2.0f,1.66f,1.33f,1.00f,0.75f,0.50f,0.25f }; - // StepMania 3.9 and 4.0 values: - const float mapping[] = { 1.50f,1.33f,1.16f,1.00f,0.84f,0.66f,0.50f,0.33f,0.20f }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -/** @brief Life Difficulty scale */ -static void LifeDifficulty( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - // StepMania 5 values (implemented 2008/03/12) - //const float mapping[] = { 2.0f,1.50f,1.00f,0.66f,0.33f }; - // StepMania 3.9 and 4.0 values: - //const float mapping[] = { 1.60f,1.40f,1.20f,1.00f,0.80f,0.60f,0.40f }; - // 3.9 modified so that L6 is L4 - const float mapping[] = { 1.20f,1.00f,0.80f,0.60f,0.40f,0.33f,0.25f }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - - -#include "LuaManager.h" -static int GetTimingDifficulty() -{ - int iTimingDifficulty = 0; - TimingWindowScale( iTimingDifficulty, true, ConfOption::Find("TimingWindowScale") ); - iTimingDifficulty++; // TimingDifficulty returns an index - return iTimingDifficulty; -} -LuaFunction( GetTimingDifficulty, GetTimingDifficulty() ); -static int GetLifeDifficulty() -{ - int iLifeDifficulty = 0; - LifeDifficulty( iLifeDifficulty, true, ConfOption::Find("LifeDifficulty") ); - iLifeDifficulty++; // LifeDifficulty returns an index - return iLifeDifficulty; -} -LuaFunction( GetLifeDifficulty, GetLifeDifficulty() ); - - -static void DefaultFailType( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - if( ToSel ) - { - PlayerOptions po; - po.FromString( PREFSMAN->m_sDefaultModifiers ); - sel = po.m_FailType; - } - else - { - PlayerOptions po; - SongOptions so; - GetPrefsDefaultModifiers( po, so ); - - switch( sel ) - { - case 0: po.m_FailType = PlayerOptions::FAIL_IMMEDIATE; break; - case 1: po.m_FailType = PlayerOptions::FAIL_IMMEDIATE_CONTINUE; break; - case 2: po.m_FailType = PlayerOptions::FAIL_AT_END; break; - case 3: po.m_FailType = PlayerOptions::FAIL_OFF; break; - default: - { - LOG->Warn("Invalid fail type %d! Going to use the default...", sel); - po.m_FailType = PlayerOptions::FAIL_IMMEDIATE; break; - } - } - - SetPrefsDefaultModifiers( po, so ); - } -} - -// Graphic options -struct res_t -{ - int w, h; - res_t(): w(0), h(0) { } - res_t( int w_, int h_ ): w(w_), h(h_) { } - res_t operator-( const res_t &rhs ) const - { - return res_t( w-rhs.w, h-rhs.h ); - } - - bool operator<( const res_t &rhs ) const - { - if( w != rhs.w ) - return w < rhs.w; - return h < rhs.h; - } - - // Ugly: allow convert to a float for FindClosestEntry. - operator float() const { return w * 5000.0f + h; } -}; - -static void DisplayResolutionM( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - vector v; - - DisplayResolutions d; - DISPLAY->GetDisplayResolutions( d ); - - FOREACHS_CONST( DisplayResolution, d, iter ) - { - v.push_back( res_t(iter->iWidth, iter->iHeight) ); - } - - res_t sel_res( PREFSMAN->m_iDisplayWidth, PREFSMAN->m_iDisplayHeight ); - MoveMap( sel, sel_res, ToSel, &v[0], v.size() ); - if( !ToSel ) - { - PREFSMAN->m_iDisplayWidth.Set( sel_res.w ); - PREFSMAN->m_iDisplayHeight.Set( sel_res.h ); - } -} - -static void DisplayColorDepth( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const int mapping[] = { 16,32 }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void MaxTextureResolution( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const int mapping[] = { 256,512,1024,2048 }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void TextureColorDepth( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const int mapping[] = { 16,32 }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void MovieColorDepth( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const int mapping[] = { 16,32 }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void RefreshRate( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const int mapping[] = { (int) REFRESH_DEFAULT,60,70,72,75,80,85,90,100,120,150 }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void DisplayAspectRatio( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const float mapping[] = { 3/4.f, 1, 4/3.0f, 5/4.0f, 16/10.0f, 16/9.f, 8/3.f }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -/* Simpler DisplayAspectRatio setting, which only offers "on" and "off". - * "On" can be 16:9 or 16:10. */ -static void WideScreen16_10( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const float mapping[] = { 4/3.0f, 16/10.0f }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void WideScreen16_9( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const float mapping[] = { 4/3.0f, 16/9.0f }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -// BackgroundCache code isn't live yet -aj -/* -static void BackgroundCache( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const BackgroundCache mapping[] = { BackgroundCacheMode_Off, BackgroundCacheMode_LowResPreload, BackgroundCacheMode_LowResLoadOnDemand, BackgroundCacheMode_Full }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} -*/ - -// Sound options -static void SoundVolume( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const float mapping[] = { 0.0f,0.1f,0.2f,0.3f,0.4f,0.5f,0.6f,0.7f,0.8f,0.9f,1.0f }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void SoundVolumeAttract( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const float mapping[] = { 0.0f,0.1f,0.2f,0.3f,0.4f,0.5f,0.6f,0.7f,0.8f,0.9f,1.0f }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void VisualDelaySeconds( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - const float mapping[] = { -0.125f,-0.1f,-0.075f,-0.05f,-0.025f,0.0f,0.025f,0.05f,0.075f,0.1f,0.125f }; - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static void GlobalOffsetSeconds( int &sel, bool ToSel, const ConfOption *pConfOption ) -{ - float mapping[41]; - for( int i = 0; i < 41; ++i ) - mapping[i] = SCALE( i, 0.0f, 40.0f, -0.1f, +0.1f ); - - MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); -} - -static vector g_ConfOptions; -static void InitializeConfOptions() -{ - if( !g_ConfOptions.empty() ) - return; - - // There are a couple ways of getting the current preference column or turning - // a new choice in the interface into a new preference. The easiest is when - // the interface choices are an exact mapping to the values the preference - // can be. In that case, the easiest thing to do is use MovePref. - // The next easiest case is when there is a hardcoded mapping that is not 1-1. - // In that case, you need to remap the result of - // MovePref to the correct mapping. Harder yet is when there is a - // float or a dynamic set of options, such as Language or Theme. - // Those require individual attention. -#define ADD(x) g_ConfOptions.push_back( x ) - // Select game - ADD( ConfOption( "Game", GameSel, GameChoices ) ); - g_ConfOptions.back().m_iEffects = OPT_RESET_GAME; - - // Appearance options - ADD( ConfOption( "Language", Language, LanguageChoices ) ); - ADD( ConfOption( "Theme", RequestedTheme, ThemeChoices ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_THEME; - - ADD( ConfOption( "Announcer", Announcer, AnnouncerChoices ) ); - ADD( ConfOption( "DefaultNoteSkin", DefaultNoteSkin, DefaultNoteSkinChoices ) ); - ADD( ConfOption( "ShowInstructions", MovePref, "Skip","Show") ); - ADD( ConfOption( "ShowCaution", MovePref, "Skip","Show") ); - ADD( ConfOption( "DancePointsForOni", MovePref, "Percent","Dance Points") ); - ADD( ConfOption( "ShowSelectGroup", MovePref, "All Music","Choose") ); - ADD( ConfOption( "MusicWheelUsesSections", MovePref, "Never","Always","Title Only") ); - ADD( ConfOption( "CourseSortOrder", MovePref, "Num Songs","Average Feet","Total Feet","Ranking") ); - ADD( ConfOption( "MoveRandomToEnd", MovePref, "No","Yes") ); - ADD( ConfOption( "ShowNativeLanguage", MovePref, "Romanization","Native Language") ); - ADD( ConfOption( "ShowLyrics", MovePref, "Hide","Show") ); - - // Misc options - ADD( ConfOption( "AutogenSteps", MovePref, "Off","On" ) ); - ADD( ConfOption( "OnlyPreferredDifficulties", MovePref, "Off","On" ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_SONG; - - ADD( ConfOption( "AutogenGroupCourses", MovePref, "Off","On" ) ); - ADD( ConfOption( "FastLoad", MovePref, "Off","On" ) ); - ADD( ConfOption( "FastLoadAdditionalSongs", MovePref, "Off","On" ) ); - - // Background options - ADD( ConfOption( "RandomBackgroundMode", MovePref, "Off","Animations","Random Movies" ) ); - ADD( ConfOption( "BGBrightness", BGBrightness, "|0%","|10%","|20%","|30%","|40%","|50%","|60%","|70%","|80%","|90%","|100%" ) ); - ADD( ConfOption( "BGBrightnessNoZero", BGBrightnessNoZero, "|10%","|20%","|30%","|40%","|50%","|60%","|70%","|80%","|90%","|100%" ) ); - g_ConfOptions.back().m_sPrefName = "BGBrightness"; - ADD( ConfOption( "BGBrightnessOrStatic", BGBrightnessOrStatic, "Disabled","25% Bright","50% Bright","75% Bright" ) ); - g_ConfOptions.back().m_sPrefName = "BGBrightness"; - ADD( ConfOption( "StretchBackgrounds", MovePref, "Off","On" ) ); - - ADD( ConfOption( "ShowDanger", MovePref, "Hide","Show" ) ); - ADD( ConfOption( "ShowDancingCharacters", MovePref, "Default to Off","Default to Random","Select" ) ); - ADD( ConfOption( "ShowBeginnerHelper", MovePref, "Off","On" ) ); - ADD( ConfOption( "NumBackgrounds", NumBackgrounds, "|5","|10","|15","|20" ) ); - - // Input options - ADD( ConfOption( "AutoMapOnJoyChange", MovePref, "Off","On (recommended)" ) ); - ADD( ConfOption( "OnlyDedicatedMenuButtons", MovePref, "Use Gameplay Buttons","Only Dedicated Buttons" ) ); - ADD( ConfOption( "AutoPlay", MovePref, "Off","On","CPU-Controlled" ) ); - ADD( ConfOption( "DelayedBack", MovePref, "Instant","Hold" ) ); - ADD( ConfOption( "ArcadeOptionsNavigation", MovePref, "StepMania Style","Arcade Style" ) ); - ADD( ConfOption( "MusicWheelSwitchSpeed", MusicWheelSwitchSpeed, "Slow","Normal","Fast","Really Fast" ) ); - - // Gameplay options - ADD( ConfOption( "Center1Player", MovePref, "Off","On" ) ); - ADD( ConfOption( "HiddenSongs", MovePref, "Off","On" ) ); - ADD( ConfOption( "EasterEggs", MovePref, "Off","On" ) ); - // W1 is Fantastic Timing - ADD( ConfOption( "AllowW1", MovePref, "Never","Courses Only","Always" ) ); - ADD( ConfOption( "AllowExtraStage", MovePref, "Off","On" ) ); - ADD( ConfOption( "PickExtraStage", MovePref, "Off","On" ) ); - ADD( ConfOption( "UseUnlockSystem", MovePref, "Off","On" ) ); - - // Machine options - ADD( ConfOption( "MenuTimer", MovePref, "Off","On" ) ); - ADD( ConfOption( "CoinMode", MovePref, "Home","Free Play" ) ); - g_ConfOptions.back().m_sPrefName = "CoinMode"; - - ADD( ConfOption( "SongsPerPlay", SongsPerPlay, "|1","|2","|3","|4","|5" ) ); - ADD( ConfOption( "SongsPerPlayOrEvent", SongsPerPlayOrEventMode,"|1","|2","|3","|4","|5","Event" ) ); - g_ConfOptions.back().m_sPrefName = "SongsPerPlay"; - - ADD( ConfOption( "EventMode", MovePref, "Off","On (recommended)" ) ); - ADD( ConfOption( "TimingWindowScale", TimingWindowScale, "|1","|2","|3","|4","|5","|6","|7","|8","Justice" ) ); - ADD( ConfOption( "LifeDifficulty", LifeDifficulty, "|1.2","|1.0","|0.8","|0.6","|0.4","|0.33","|0.25" ) ); - g_ConfOptions.back().m_sPrefName = "LifeDifficultyScale"; - ADD( ConfOption( "ProgressiveLifebar", MovePref, "Off","|1","|2","|3","|4","|5","|6","|7","|8") ); - ADD( ConfOption( "ProgressiveStageLifebar", MovePref, "Off","|1","|2","|3","|4","|5","|6","|7","|8","Insanity") ); - ADD( ConfOption( "ProgressiveNonstopLifebar", MovePref, "Off","|1","|2","|3","|4","|5","|6","|7","|8","Insanity") ); - ADD( ConfOption( "DefaultFailType", DefaultFailType, "Immediate","ImmediateContinue","EndOfSong","Off" ) ); - ADD( ConfOption( "Premium", MovePref, "Off","Double for 1 Credit","2 Players for 1 Credit" ) ); - ADD( ConfOption( "JointPremium", JointPremium, "Off","2 Players for 1 Credit" ) ); - g_ConfOptions.back().m_sPrefName = "Premium"; - ADD( ConfOption( "ShowSongOptions", MovePref, "Ask", "Hide","Show" ) ); - ADD( ConfOption( "PercentageScoring", MovePref, "Off","On" ) ); - ADD( ConfOption( "GetRankingName", MovePref, "Off", "On", "Ranking Songs" ) ); - - // Graphic options - ADD( ConfOption( "Windowed", MovePref, "Full Screen", "Windowed" ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; - ADD( ConfOption( "DisplayResolution", DisplayResolutionM, DisplayResolutionChoices ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS | OPT_APPLY_ASPECT_RATIO; - ADD( ConfOption( "DisplayAspectRatio", DisplayAspectRatio, "|3:4","|1:1","|4:3","|5:4","|16:10","|16:9","|8:3" ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS | OPT_APPLY_ASPECT_RATIO; // need OPT_APPLY_GRAPHICS because if windowed the width may change to make square pixels - ADD( ConfOption( "WideScreen16_10", WideScreen16_10, "Off", "On" ) ); - g_ConfOptions.back().m_sPrefName = "DisplayAspectRatio"; - g_ConfOptions.back().m_iEffects = OPT_APPLY_ASPECT_RATIO; - ADD( ConfOption( "WideScreen16_9", WideScreen16_9, "Off", "On" ) ); - g_ConfOptions.back().m_sPrefName = "DisplayAspectRatio"; - g_ConfOptions.back().m_iEffects = OPT_APPLY_ASPECT_RATIO; - ADD( ConfOption( "DisplayColorDepth", DisplayColorDepth, "16bit","32bit" ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; - ADD( ConfOption( "HighResolutionTextures", MovePref, "Auto","Force Off","Force On" ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; - ADD( ConfOption( "MaxTextureResolution", MaxTextureResolution, "|256","|512","|1024","|2048" ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; - ADD( ConfOption( "TextureColorDepth", TextureColorDepth, "16bit","32bit" ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; - ADD( ConfOption( "MovieColorDepth", MovieColorDepth, "16bit","32bit" ) ); - ADD( ConfOption( "DelayedTextureDelete", MovePref, "Off","On" ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; - ADD( ConfOption( "CelShadeModels", MovePref, "Off","On" ) ); - ADD( ConfOption( "SmoothLines", MovePref, "Off","On" ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; - ADD( ConfOption( "RefreshRate", RefreshRate, "Default","|60","|70","|72","|75","|80","|85","|90","|100","|120","|150" ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; - ADD( ConfOption( "Vsync", MovePref, "No", "Yes" ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; - ADD( ConfOption( "ShowStats", MovePref, "Off","On" ) ); - ADD( ConfOption( "ShowBanners", MovePref, "Off","On" ) ); - - // Sound options - ADD( ConfOption( "AttractSoundFrequency", MovePref, "Never","Always","2 Times","3 Times","4 Times","5 Times" ) ); - ADD( ConfOption( "SoundVolume", SoundVolume, "Silent","|10%","|20%","|30%","|40%","|50%","|60%","|70%","|80%","|90%","|100%" ) ); - g_ConfOptions.back().m_iEffects = OPT_APPLY_SOUND; - ADD( ConfOption( "SoundVolumeAttract", SoundVolumeAttract, "Silent","|10%","|20%","|30%","|40%","|50%","|60%","|70%","|80%","|90%","|100%" ) ); - ADD( ConfOption( "VisualDelaySeconds", VisualDelaySeconds, "|-5","|-4","|-3","|-2","|-1","|0","|+1","|+2","|+3","|+4","|+5" ) ); - { - ConfOption c( "GlobalOffsetSeconds", GlobalOffsetSeconds ); - for( int i = -100; i <= +100; i += 5 ) - c.AddOption( ssprintf("%+i ms", i) ); - ADD( c ); - } - ADD( ConfOption( "EnableAttackSounds", MovePref, "No","Yes" ) ); - ADD( ConfOption( "EnableMineHitSound", MovePref, "No","Yes" ) ); - - // Editor options - ADD( ConfOption( "EditorShowBGChangesPlay", MovePref, "Hide","Show") ); - - ADD( ConfOption( "Invalid", MoveNop, "|Invalid option") ); -} - -// Get a mask of effects to apply if the given option changes. -int ConfOption::GetEffects() const -{ - return m_iEffects | OPT_SAVE_PREFERENCES; -} - -ConfOption *ConfOption::Find( RString name ) -{ - InitializeConfOptions(); - for( unsigned i = 0; i < g_ConfOptions.size(); ++i ) - { - ConfOption *opt = &g_ConfOptions[i]; - RString match(opt->name); - if( match.CompareNoCase(name) ) - continue; - return opt; - } - - return NULL; -} - -void ConfOption::UpdateAvailableOptions() -{ - if( MakeOptionsListCB != NULL ) - { - names.clear(); - MakeOptionsListCB( names ); - } -} - -void ConfOption::MakeOptionsList( vector &out ) const -{ - out = names; -} - -/** - * @file - * @author Glenn Maynard (c) 2003-2004 - * @section LICENSE - * 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 "ScreenOptionsMasterPrefs.h" +#include "PrefsManager.h" +#include "ThemeManager.h" +#include "AnnouncerManager.h" +#include "NoteSkinManager.h" +#include "PlayerOptions.h" +#include "SongOptions.h" +#include "RageDisplay.h" +#include "RageUtil.h" +#include "GameManager.h" +#include "GameState.h" +#include "StepMania.h" +#include "Game.h" +#include "Foreach.h" +#include "GameConstantsAndTypes.h" +#include "DisplayResolutions.h" +#include "LocalizedString.h" +#include "SpecialFiles.h" +#include "RageLog.h" + +using namespace StringConversion; + +static void GetPrefsDefaultModifiers( PlayerOptions &po, SongOptions &so ) +{ + po.FromString( PREFSMAN->m_sDefaultModifiers ); + so.FromString( PREFSMAN->m_sDefaultModifiers ); +} + +static void SetPrefsDefaultModifiers( const PlayerOptions &po, const SongOptions &so ) +{ + vector as; + if( po.GetString() != "" ) + as.push_back( po.GetString() ); + if( so.GetString() != "" ) + as.push_back( so.GetString() ); + + PREFSMAN->m_sDefaultModifiers.Set( join(", ",as) ); +} + + +/* Ugly: the input values may be a different type than the mapping. For example, + * the mapping may be an enum, and value an int. This is because we don't + * have FromString/ToString for every enum type. Assume that the distance between + * T and U can be represented as a float. */ +template +int FindClosestEntry( T value, const U *mapping, unsigned cnt ) +{ + int iBestIndex = 0; + float best_dist = 0; + bool have_best = false; + + for( unsigned i = 0; i < cnt; ++i ) + { + const U val = mapping[i]; + float dist = value < val? (float)(val-value):(float)(value-val); + if( have_best && best_dist < dist ) + continue; + + have_best = true; + best_dist = dist; + + iBestIndex = i; + } + + if( have_best ) + return iBestIndex; + else + return 0; +} + +template +static void MoveMap( int &sel, T &opt, bool ToSel, const T *mapping, unsigned cnt ) +{ + if( ToSel ) + { + sel = FindClosestEntry( opt, mapping, cnt ); + } else { + // sel -> opt + opt = mapping[sel]; + } +} + +template +static void MoveMap( int &sel, IPreference &opt, bool ToSel, const T *mapping, unsigned cnt ) +{ + if( ToSel ) + { + RString sOpt = opt.ToString(); + // This should really be T, but we can't FromString an enum. + float val; + FromString( sOpt, val ); + sel = FindClosestEntry( val, mapping, cnt ); + } else { + // sel -> opt + RString sOpt = ToString( mapping[sel] ); + opt.FromString( sOpt ); + } +} + +template +static void MoveMap( int &sel, const ConfOption *pConfOption, bool ToSel, const T *mapping, unsigned cnt ) +{ + ASSERT( pConfOption != NULL ); + IPreference *pPref = IPreference::GetPreferenceByName( pConfOption->m_sPrefName ); + ASSERT_M( pPref != NULL, pConfOption->m_sPrefName ); + + MoveMap( sel, *pPref, ToSel, mapping, cnt ); +} + +template +static void MovePref( int &iSel, bool bToSel, const ConfOption *pConfOption ) +{ + IPreference *pPref = IPreference::GetPreferenceByName( pConfOption->m_sPrefName ); + ASSERT_M( pPref != NULL, pConfOption->m_sPrefName ); + + if( bToSel ) + { + // TODO: why not get the int directly from pPref? + // Why are we writing it to a string and then back? + T t; + FromString( pPref->ToString(), t ); + iSel = static_cast( t ); + } + else + { + pPref->FromString( ToString( static_cast( iSel ) ) ); + } +} + +template <> +void MovePref( int &iSel, bool bToSel, const ConfOption *pConfOption ) +{ + IPreference *pPref = IPreference::GetPreferenceByName( pConfOption->m_sPrefName ); + ASSERT_M( pPref != NULL, pConfOption->m_sPrefName ); + + if( bToSel ) + { + // TODO: why not get the int directly from pPref? + // Why are we writing it to a string and then back? + bool b; + FromString( pPref->ToString(), b ); + iSel = b ? 1 : 0; + } + else + { + // If we don't make a specific instantiation of MovePref, there is + // a compile warning here because of static_cast( iSel ) where + // iSel is an int. What is the best way to remove that compile warning? + pPref->FromString( ToString( iSel ? true : false ) ); + } +} + +static void MoveNop( int &iSel, bool bToSel, const ConfOption *pConfOption ) +{ + if( bToSel ) + iSel = 0; +} + +// TODO: Write GenerateValueList() function that can use ints and floats. -aj + +static void GameChoices( vector &out ) +{ + vector aGames; + GAMEMAN->GetEnabledGames( aGames ); + FOREACH( const Game*, aGames, g ) + { + RString sGameName = (*g)->m_szName; + out.push_back( sGameName ); + } +} + +static void GameSel( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + vector choices; + pConfOption->MakeOptionsList( choices ); + + if( ToSel ) + { + const RString sCurGameName = GAMESTATE->m_pCurGame->m_szName; + + sel = 0; + for(unsigned i = 0; i < choices.size(); ++i) + if( !stricmp(choices[i], sCurGameName) ) + sel = i; + } else { + vector aGames; + GAMEMAN->GetEnabledGames( aGames ); + StepMania::ChangeCurrentGame( aGames[sel] ); + /* Reload metrics to force a refresh of CommonMetrics::DIFFICULTIES_TO_SHOW, + * mainly if we're not switching themes. I'm not sure if this was the + * case going from theme to theme, but if it was, it should be fixed + * now. There's probably be a better way to do it, but I'm not sure + * what it'd be. -aj */ + THEME->ReloadMetrics(); + } +} + +static void LanguageChoices( vector &out ) +{ + vector vs; + THEME->GetLanguages( vs ); + SortRStringArray( vs, true ); + + FOREACH_CONST( RString, vs, s ) + { + const LanguageInfo *pLI = GetLanguageInfo( *s ); + if( pLI ) + out.push_back( THEME->GetString("NativeLanguageNames", pLI->szEnglishName) ); + else + out.push_back( *s ); + } +} + +static void Language( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + vector vs; + THEME->GetLanguages( vs ); + SortRStringArray( vs, true ); + + if( ToSel ) + { + sel = -1; + for( unsigned i=0; sel == -1 && i < vs.size(); ++i ) + if( !stricmp(vs[i], THEME->GetCurLanguage()) ) + sel = i; + + // If the current language doesn't exist, we'll show BASE_LANGUAGE, so select that. + for( unsigned i=0; sel == -1 && i < vs.size(); ++i ) + if( !stricmp(vs[i], SpecialFiles::BASE_LANGUAGE) ) + sel = i; + + if( sel == -1 ) + { + LOG->Warn( "Couldn't find language \"%s\" or fallback \"%s\"; using \"%s\"", + THEME->GetCurLanguage().c_str(), SpecialFiles::BASE_LANGUAGE.c_str(), vs[0].c_str() ); + sel = 0; + } + } else { + const RString &sNewLanguage = vs[sel]; + + PREFSMAN->m_sLanguage.Set( sNewLanguage ); + if( THEME->GetCurLanguage() != sNewLanguage ) + THEME->SwitchThemeAndLanguage( THEME->GetCurThemeName(), PREFSMAN->m_sLanguage, PREFSMAN->m_bPseudoLocalize ); + } +} + +static void ThemeChoices( vector &out ) +{ + THEME->GetSelectableThemeNames( out ); + FOREACH( RString, out, s ) + *s = THEME->GetThemeDisplayName( *s ); +} + +static void DisplayResolutionChoices( vector &out ) +{ + DisplayResolutions d; + DISPLAY->GetDisplayResolutions( d ); + + for (DisplayResolution const &display : d) + { + RString s = ssprintf("%dx%d", display.iWidth, display.iHeight); + out.push_back( s ); + } +} + +static void RequestedTheme( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + vector choices; + pConfOption->MakeOptionsList( choices ); + + vector vsThemeNames; + THEME->GetSelectableThemeNames( vsThemeNames ); + + if( ToSel ) + { + sel = 0; + for( unsigned i=1; im_sTheme.Get()) ) + sel = i; + } + else + { + const RString sNewTheme = vsThemeNames[sel]; + PREFSMAN->m_sTheme.Set( sNewTheme ); // OPT_APPLY_THEME will load the theme + } +} + +static LocalizedString OFF ("ScreenOptionsMasterPrefs","Off"); +static void AnnouncerChoices( vector &out ) +{ + ANNOUNCER->GetAnnouncerNames( out ); + out.insert( out.begin(), OFF ); +} + +static void Announcer( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + vector choices; + pConfOption->MakeOptionsList( choices ); + + if( ToSel ) + { + sel = 0; + for( unsigned i=1; iGetCurAnnouncerName()) ) + sel = i; + } + else + { + const RString sNewAnnouncer = sel? choices[sel]:RString(""); + ANNOUNCER->SwitchAnnouncer( sNewAnnouncer ); + PREFSMAN->m_sAnnouncer.Set( sNewAnnouncer ); + } +} + +static void DefaultNoteSkinChoices( vector &out ) +{ + NOTESKIN->GetNoteSkinNames( out ); +} + +static void DefaultNoteSkin( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + vector choices; + pConfOption->MakeOptionsList( choices ); + + if( ToSel ) + { + PlayerOptions po; + po.FromString( PREFSMAN->m_sDefaultModifiers ); + sel = 0; + for( unsigned i=0; i < choices.size(); i++ ) + if( !stricmp(choices[i], po.m_sNoteSkin) ) + sel = i; + } + else + { + PlayerOptions po; + SongOptions so; + GetPrefsDefaultModifiers( po, so ); + po.m_sNoteSkin = choices[sel]; + SetPrefsDefaultModifiers( po, so ); + } +} + +// Background options +static void BGBrightness( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + // TODO: I hate the way the list of numbers is duplicated here and where the + // option is created. Try to find a way to only use the same list once. + // Do that for all of these float and int lists. + const float mapping[] = { 0.0f,0.1f,0.2f,0.3f,0.4f,0.5f,0.6f,0.7f,0.8f,0.9f,1.0f }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void BGBrightnessNoZero( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const float mapping[] = { 0.1f,0.2f,0.3f,0.4f,0.5f,0.6f,0.7f,0.8f,0.9f,1.0f }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void BGBrightnessOrStatic( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const float mapping[] = { 0.5f,0.25f,0.5f,0.75f }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); + + IPreference *pSongBackgroundsPref = IPreference::GetPreferenceByName( "SongBackgrounds" ); + if( ToSel && pSongBackgroundsPref->ToString() == "0" ) + sel = 0; + if( !ToSel ) + pSongBackgroundsPref->FromString( sel? "1":"0" ); +} + +static void NumBackgrounds( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const int mapping[] = { 5,10,15,20 }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +// Input options +static void MusicWheelSwitchSpeed( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const int mapping[] = { 5, 10, 15, 25 }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void JointPremium( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const Premium mapping[] = { Premium_DoubleFor1Credit, Premium_2PlayersFor1Credit }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void SongsPerPlay( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const int mapping[] = { 1,2,3,4,5 }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void SongsPerPlayOrEventMode( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const int mapping[] = { 1,2,3,4,5,6 }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); + + if( ToSel && PREFSMAN->m_bEventMode ) + sel = 5; + if( !ToSel ) + PREFSMAN->m_bEventMode.Set( sel == 5 ); +} + +// Machine options +/** @brief Timing Window scale */ +static void TimingWindowScale( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + // StepMania 5 values (implemented 2008/03/12) + //const float mapping[] = { 2.0f,1.66f,1.33f,1.00f,0.75f,0.50f,0.25f }; + // StepMania 3.9 and 4.0 values: + const float mapping[] = { 1.50f,1.33f,1.16f,1.00f,0.84f,0.66f,0.50f,0.33f,0.20f }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +/** @brief Life Difficulty scale */ +static void LifeDifficulty( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + // StepMania 5 values (implemented 2008/03/12) + //const float mapping[] = { 2.0f,1.50f,1.00f,0.66f,0.33f }; + // StepMania 3.9 and 4.0 values: + //const float mapping[] = { 1.60f,1.40f,1.20f,1.00f,0.80f,0.60f,0.40f }; + // 3.9 modified so that L6 is L4 + const float mapping[] = { 1.20f,1.00f,0.80f,0.60f,0.40f,0.33f,0.25f }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + + +#include "LuaManager.h" +static int GetTimingDifficulty() +{ + int iTimingDifficulty = 0; + TimingWindowScale( iTimingDifficulty, true, ConfOption::Find("TimingWindowScale") ); + iTimingDifficulty++; // TimingDifficulty returns an index + return iTimingDifficulty; +} +LuaFunction( GetTimingDifficulty, GetTimingDifficulty() ); +static int GetLifeDifficulty() +{ + int iLifeDifficulty = 0; + LifeDifficulty( iLifeDifficulty, true, ConfOption::Find("LifeDifficulty") ); + iLifeDifficulty++; // LifeDifficulty returns an index + return iLifeDifficulty; +} +LuaFunction( GetLifeDifficulty, GetLifeDifficulty() ); + + +static void DefaultFailType( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + if( ToSel ) + { + PlayerOptions po; + po.FromString( PREFSMAN->m_sDefaultModifiers ); + sel = po.m_FailType; + } + else + { + PlayerOptions po; + SongOptions so; + GetPrefsDefaultModifiers( po, so ); + + switch( sel ) + { + case 0: po.m_FailType = PlayerOptions::FAIL_IMMEDIATE; break; + case 1: po.m_FailType = PlayerOptions::FAIL_IMMEDIATE_CONTINUE; break; + case 2: po.m_FailType = PlayerOptions::FAIL_AT_END; break; + case 3: po.m_FailType = PlayerOptions::FAIL_OFF; break; + default: + { + LOG->Warn("Invalid fail type %d! Going to use the default...", sel); + po.m_FailType = PlayerOptions::FAIL_IMMEDIATE; break; + } + } + + SetPrefsDefaultModifiers( po, so ); + } +} + +// Graphic options +struct res_t +{ + int w, h; + res_t(): w(0), h(0) { } + res_t( int w_, int h_ ): w(w_), h(h_) { } + res_t operator-( const res_t &rhs ) const + { + return res_t( w-rhs.w, h-rhs.h ); + } + + bool operator<( const res_t &rhs ) const + { + if( w != rhs.w ) + return w < rhs.w; + return h < rhs.h; + } + + // Ugly: allow convert to a float for FindClosestEntry. + operator float() const { return w * 5000.0f + h; } +}; + +static void DisplayResolutionM( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + vector v; + + DisplayResolutions d; + DISPLAY->GetDisplayResolutions( d ); + + for (DisplayResolution const &display : d) + { + v.push_back( res_t(display.iWidth, display.iHeight) ); + } + + res_t sel_res( PREFSMAN->m_iDisplayWidth, PREFSMAN->m_iDisplayHeight ); + MoveMap( sel, sel_res, ToSel, &v[0], v.size() ); + if( !ToSel ) + { + PREFSMAN->m_iDisplayWidth.Set( sel_res.w ); + PREFSMAN->m_iDisplayHeight.Set( sel_res.h ); + } +} + +static void DisplayColorDepth( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const int mapping[] = { 16,32 }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void MaxTextureResolution( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const int mapping[] = { 256,512,1024,2048 }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void TextureColorDepth( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const int mapping[] = { 16,32 }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void MovieColorDepth( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const int mapping[] = { 16,32 }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void RefreshRate( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const int mapping[] = { (int) REFRESH_DEFAULT,60,70,72,75,80,85,90,100,120,150 }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void DisplayAspectRatio( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const float mapping[] = { 3/4.f, 1, 4/3.0f, 5/4.0f, 16/10.0f, 16/9.f, 8/3.f }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +/* Simpler DisplayAspectRatio setting, which only offers "on" and "off". + * "On" can be 16:9 or 16:10. */ +static void WideScreen16_10( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const float mapping[] = { 4/3.0f, 16/10.0f }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void WideScreen16_9( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const float mapping[] = { 4/3.0f, 16/9.0f }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +// BackgroundCache code isn't live yet -aj +/* +static void BackgroundCache( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const BackgroundCache mapping[] = { BackgroundCacheMode_Off, BackgroundCacheMode_LowResPreload, BackgroundCacheMode_LowResLoadOnDemand, BackgroundCacheMode_Full }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} +*/ + +// Sound options +static void SoundVolume( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const float mapping[] = { 0.0f,0.1f,0.2f,0.3f,0.4f,0.5f,0.6f,0.7f,0.8f,0.9f,1.0f }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void SoundVolumeAttract( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const float mapping[] = { 0.0f,0.1f,0.2f,0.3f,0.4f,0.5f,0.6f,0.7f,0.8f,0.9f,1.0f }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void VisualDelaySeconds( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + const float mapping[] = { -0.125f,-0.1f,-0.075f,-0.05f,-0.025f,0.0f,0.025f,0.05f,0.075f,0.1f,0.125f }; + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static void GlobalOffsetSeconds( int &sel, bool ToSel, const ConfOption *pConfOption ) +{ + float mapping[41]; + for( int i = 0; i < 41; ++i ) + mapping[i] = SCALE( i, 0.0f, 40.0f, -0.1f, +0.1f ); + + MoveMap( sel, pConfOption, ToSel, mapping, ARRAYLEN(mapping) ); +} + +static vector g_ConfOptions; +static void InitializeConfOptions() +{ + if( !g_ConfOptions.empty() ) + return; + + // There are a couple ways of getting the current preference column or turning + // a new choice in the interface into a new preference. The easiest is when + // the interface choices are an exact mapping to the values the preference + // can be. In that case, the easiest thing to do is use MovePref. + // The next easiest case is when there is a hardcoded mapping that is not 1-1. + // In that case, you need to remap the result of + // MovePref to the correct mapping. Harder yet is when there is a + // float or a dynamic set of options, such as Language or Theme. + // Those require individual attention. +#define ADD(x) g_ConfOptions.push_back( x ) + // Select game + ADD( ConfOption( "Game", GameSel, GameChoices ) ); + g_ConfOptions.back().m_iEffects = OPT_RESET_GAME; + + // Appearance options + ADD( ConfOption( "Language", Language, LanguageChoices ) ); + ADD( ConfOption( "Theme", RequestedTheme, ThemeChoices ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_THEME; + + ADD( ConfOption( "Announcer", Announcer, AnnouncerChoices ) ); + ADD( ConfOption( "DefaultNoteSkin", DefaultNoteSkin, DefaultNoteSkinChoices ) ); + ADD( ConfOption( "ShowInstructions", MovePref, "Skip","Show") ); + ADD( ConfOption( "ShowCaution", MovePref, "Skip","Show") ); + ADD( ConfOption( "DancePointsForOni", MovePref, "Percent","Dance Points") ); + ADD( ConfOption( "ShowSelectGroup", MovePref, "All Music","Choose") ); + ADD( ConfOption( "MusicWheelUsesSections", MovePref, "Never","Always","Title Only") ); + ADD( ConfOption( "CourseSortOrder", MovePref, "Num Songs","Average Feet","Total Feet","Ranking") ); + ADD( ConfOption( "MoveRandomToEnd", MovePref, "No","Yes") ); + ADD( ConfOption( "ShowNativeLanguage", MovePref, "Romanization","Native Language") ); + ADD( ConfOption( "ShowLyrics", MovePref, "Hide","Show") ); + + // Misc options + ADD( ConfOption( "AutogenSteps", MovePref, "Off","On" ) ); + ADD( ConfOption( "OnlyPreferredDifficulties", MovePref, "Off","On" ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_SONG; + + ADD( ConfOption( "AutogenGroupCourses", MovePref, "Off","On" ) ); + ADD( ConfOption( "FastLoad", MovePref, "Off","On" ) ); + ADD( ConfOption( "FastLoadAdditionalSongs", MovePref, "Off","On" ) ); + + // Background options + ADD( ConfOption( "RandomBackgroundMode", MovePref, "Off","Animations","Random Movies" ) ); + ADD( ConfOption( "BGBrightness", BGBrightness, "|0%","|10%","|20%","|30%","|40%","|50%","|60%","|70%","|80%","|90%","|100%" ) ); + ADD( ConfOption( "BGBrightnessNoZero", BGBrightnessNoZero, "|10%","|20%","|30%","|40%","|50%","|60%","|70%","|80%","|90%","|100%" ) ); + g_ConfOptions.back().m_sPrefName = "BGBrightness"; + ADD( ConfOption( "BGBrightnessOrStatic", BGBrightnessOrStatic, "Disabled","25% Bright","50% Bright","75% Bright" ) ); + g_ConfOptions.back().m_sPrefName = "BGBrightness"; + ADD( ConfOption( "StretchBackgrounds", MovePref, "Off","On" ) ); + + ADD( ConfOption( "ShowDanger", MovePref, "Hide","Show" ) ); + ADD( ConfOption( "ShowDancingCharacters", MovePref, "Default to Off","Default to Random","Select" ) ); + ADD( ConfOption( "ShowBeginnerHelper", MovePref, "Off","On" ) ); + ADD( ConfOption( "NumBackgrounds", NumBackgrounds, "|5","|10","|15","|20" ) ); + + // Input options + ADD( ConfOption( "AutoMapOnJoyChange", MovePref, "Off","On (recommended)" ) ); + ADD( ConfOption( "OnlyDedicatedMenuButtons", MovePref, "Use Gameplay Buttons","Only Dedicated Buttons" ) ); + ADD( ConfOption( "AutoPlay", MovePref, "Off","On","CPU-Controlled" ) ); + ADD( ConfOption( "DelayedBack", MovePref, "Instant","Hold" ) ); + ADD( ConfOption( "ArcadeOptionsNavigation", MovePref, "StepMania Style","Arcade Style" ) ); + ADD( ConfOption( "MusicWheelSwitchSpeed", MusicWheelSwitchSpeed, "Slow","Normal","Fast","Really Fast" ) ); + + // Gameplay options + ADD( ConfOption( "Center1Player", MovePref, "Off","On" ) ); + ADD( ConfOption( "HiddenSongs", MovePref, "Off","On" ) ); + ADD( ConfOption( "EasterEggs", MovePref, "Off","On" ) ); + // W1 is Fantastic Timing + ADD( ConfOption( "AllowW1", MovePref, "Never","Courses Only","Always" ) ); + ADD( ConfOption( "AllowExtraStage", MovePref, "Off","On" ) ); + ADD( ConfOption( "PickExtraStage", MovePref, "Off","On" ) ); + ADD( ConfOption( "UseUnlockSystem", MovePref, "Off","On" ) ); + + // Machine options + ADD( ConfOption( "MenuTimer", MovePref, "Off","On" ) ); + ADD( ConfOption( "CoinMode", MovePref, "Home","Free Play" ) ); + g_ConfOptions.back().m_sPrefName = "CoinMode"; + + ADD( ConfOption( "SongsPerPlay", SongsPerPlay, "|1","|2","|3","|4","|5" ) ); + ADD( ConfOption( "SongsPerPlayOrEvent", SongsPerPlayOrEventMode,"|1","|2","|3","|4","|5","Event" ) ); + g_ConfOptions.back().m_sPrefName = "SongsPerPlay"; + + ADD( ConfOption( "EventMode", MovePref, "Off","On (recommended)" ) ); + ADD( ConfOption( "TimingWindowScale", TimingWindowScale, "|1","|2","|3","|4","|5","|6","|7","|8","Justice" ) ); + ADD( ConfOption( "LifeDifficulty", LifeDifficulty, "|1.2","|1.0","|0.8","|0.6","|0.4","|0.33","|0.25" ) ); + g_ConfOptions.back().m_sPrefName = "LifeDifficultyScale"; + ADD( ConfOption( "ProgressiveLifebar", MovePref, "Off","|1","|2","|3","|4","|5","|6","|7","|8") ); + ADD( ConfOption( "ProgressiveStageLifebar", MovePref, "Off","|1","|2","|3","|4","|5","|6","|7","|8","Insanity") ); + ADD( ConfOption( "ProgressiveNonstopLifebar", MovePref, "Off","|1","|2","|3","|4","|5","|6","|7","|8","Insanity") ); + ADD( ConfOption( "DefaultFailType", DefaultFailType, "Immediate","ImmediateContinue","EndOfSong","Off" ) ); + ADD( ConfOption( "Premium", MovePref, "Off","Double for 1 Credit","2 Players for 1 Credit" ) ); + ADD( ConfOption( "JointPremium", JointPremium, "Off","2 Players for 1 Credit" ) ); + g_ConfOptions.back().m_sPrefName = "Premium"; + ADD( ConfOption( "ShowSongOptions", MovePref, "Ask", "Hide","Show" ) ); + ADD( ConfOption( "PercentageScoring", MovePref, "Off","On" ) ); + ADD( ConfOption( "GetRankingName", MovePref, "Off", "On", "Ranking Songs" ) ); + + // Graphic options + ADD( ConfOption( "Windowed", MovePref, "Full Screen", "Windowed" ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; + ADD( ConfOption( "DisplayResolution", DisplayResolutionM, DisplayResolutionChoices ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS | OPT_APPLY_ASPECT_RATIO; + ADD( ConfOption( "DisplayAspectRatio", DisplayAspectRatio, "|3:4","|1:1","|4:3","|5:4","|16:10","|16:9","|8:3" ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS | OPT_APPLY_ASPECT_RATIO; // need OPT_APPLY_GRAPHICS because if windowed the width may change to make square pixels + ADD( ConfOption( "WideScreen16_10", WideScreen16_10, "Off", "On" ) ); + g_ConfOptions.back().m_sPrefName = "DisplayAspectRatio"; + g_ConfOptions.back().m_iEffects = OPT_APPLY_ASPECT_RATIO; + ADD( ConfOption( "WideScreen16_9", WideScreen16_9, "Off", "On" ) ); + g_ConfOptions.back().m_sPrefName = "DisplayAspectRatio"; + g_ConfOptions.back().m_iEffects = OPT_APPLY_ASPECT_RATIO; + ADD( ConfOption( "DisplayColorDepth", DisplayColorDepth, "16bit","32bit" ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; + ADD( ConfOption( "HighResolutionTextures", MovePref, "Auto","Force Off","Force On" ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; + ADD( ConfOption( "MaxTextureResolution", MaxTextureResolution, "|256","|512","|1024","|2048" ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; + ADD( ConfOption( "TextureColorDepth", TextureColorDepth, "16bit","32bit" ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; + ADD( ConfOption( "MovieColorDepth", MovieColorDepth, "16bit","32bit" ) ); + ADD( ConfOption( "DelayedTextureDelete", MovePref, "Off","On" ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; + ADD( ConfOption( "CelShadeModels", MovePref, "Off","On" ) ); + ADD( ConfOption( "SmoothLines", MovePref, "Off","On" ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; + ADD( ConfOption( "RefreshRate", RefreshRate, "Default","|60","|70","|72","|75","|80","|85","|90","|100","|120","|150" ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; + ADD( ConfOption( "Vsync", MovePref, "No", "Yes" ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_GRAPHICS; + ADD( ConfOption( "ShowStats", MovePref, "Off","On" ) ); + ADD( ConfOption( "ShowBanners", MovePref, "Off","On" ) ); + + // Sound options + ADD( ConfOption( "AttractSoundFrequency", MovePref, "Never","Always","2 Times","3 Times","4 Times","5 Times" ) ); + ADD( ConfOption( "SoundVolume", SoundVolume, "Silent","|10%","|20%","|30%","|40%","|50%","|60%","|70%","|80%","|90%","|100%" ) ); + g_ConfOptions.back().m_iEffects = OPT_APPLY_SOUND; + ADD( ConfOption( "SoundVolumeAttract", SoundVolumeAttract, "Silent","|10%","|20%","|30%","|40%","|50%","|60%","|70%","|80%","|90%","|100%" ) ); + ADD( ConfOption( "VisualDelaySeconds", VisualDelaySeconds, "|-5","|-4","|-3","|-2","|-1","|0","|+1","|+2","|+3","|+4","|+5" ) ); + { + ConfOption c( "GlobalOffsetSeconds", GlobalOffsetSeconds ); + for( int i = -100; i <= +100; i += 5 ) + c.AddOption( ssprintf("%+i ms", i) ); + ADD( c ); + } + ADD( ConfOption( "EnableAttackSounds", MovePref, "No","Yes" ) ); + ADD( ConfOption( "EnableMineHitSound", MovePref, "No","Yes" ) ); + + // Editor options + ADD( ConfOption( "EditorShowBGChangesPlay", MovePref, "Hide","Show") ); + + ADD( ConfOption( "Invalid", MoveNop, "|Invalid option") ); +} + +// Get a mask of effects to apply if the given option changes. +int ConfOption::GetEffects() const +{ + return m_iEffects | OPT_SAVE_PREFERENCES; +} + +ConfOption *ConfOption::Find( RString name ) +{ + InitializeConfOptions(); + for( unsigned i = 0; i < g_ConfOptions.size(); ++i ) + { + ConfOption *opt = &g_ConfOptions[i]; + RString match(opt->name); + if( match.CompareNoCase(name) ) + continue; + return opt; + } + + return NULL; +} + +void ConfOption::UpdateAvailableOptions() +{ + if( MakeOptionsListCB != NULL ) + { + names.clear(); + MakeOptionsListCB( names ); + } +} + +void ConfOption::MakeOptionsList( vector &out ) const +{ + out = names; +} + +/** + * @file + * @author Glenn Maynard (c) 2003-2004 + * @section LICENSE + * 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. + */ From 11fcbaec6498c277dcc5e84b2d3b9e84aedd533e Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:43:08 -0400 Subject: [PATCH 016/281] Another one bites the dust. --- src/Foreach.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Foreach.h b/src/Foreach.h index 2816c18672..3ae474e76c 100644 --- a/src/Foreach.h +++ b/src/Foreach.h @@ -18,9 +18,6 @@ for( deque::const_iterator var = (vect).begin(); var != (vect).end(); /** @brief General foreach loop iterating over a set. */ #define FOREACHS( elemType, vect, var ) \ for( set::iterator var = (vect).begin(); var != (vect).end(); ++var ) -/** @brief General foreach loop iterating over a set, using a constant iterator. */ -#define FOREACHS_CONST( elemType, vect, var ) \ -for( set::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) /** @brief General foreach loop iterating over a map. */ #define FOREACHM( keyType, valType, vect, var ) \ From e2e7b7f39ec836f3bf24539c70329502b872d50d Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:45:04 -0400 Subject: [PATCH 017/281] Handle the lone deque. --- src/Foreach.h | 7 ------- src/ThemeManager.cpp | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Foreach.h b/src/Foreach.h index 3ae474e76c..74f92467b1 100644 --- a/src/Foreach.h +++ b/src/Foreach.h @@ -8,13 +8,6 @@ for( vector::iterator var = (vect).begin(); var != (vect).end(); ++var #define FOREACH_CONST( elemType, vect, var ) \ for( vector::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) -/** @brief General foreach loop iterating over a deque. */ -#define FOREACHD( elemType, vect, var ) \ -for( deque::iterator var = (vect).begin(); var != (vect).end(); ++var ) -/** @brief General foreach loop iterating over a deque, using a constant iterator. */ -#define FOREACHD_CONST( elemType, vect, var ) \ -for( deque::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) - /** @brief General foreach loop iterating over a set. */ #define FOREACHS( elemType, vect, var ) \ for( set::iterator var = (vect).begin(); var != (vect).end(); ++var ) diff --git a/src/ThemeManager.cpp b/src/ThemeManager.cpp index f2ac2c6c61..35bb6eec6e 100644 --- a/src/ThemeManager.cpp +++ b/src/ThemeManager.cpp @@ -726,10 +726,10 @@ bool ThemeManager::GetPathInfoToAndFallback( PathInfo &out, ElementCategory cate int n = 100; while( n-- ) { - FOREACHD_CONST( Theme, g_vThemes, iter ) + for (Theme const &theme : g_vThemes) { // search with requested name - if( GetPathInfoToRaw( out, iter->sThemeName, category, sMetricsGroup, sElement ) ) + if( GetPathInfoToRaw( out, theme.sThemeName, category, sMetricsGroup, sElement ) ) return true; } From 2642ef77e407d670b9ba265a2c57f562bdc68bfc Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:51:13 -0400 Subject: [PATCH 018/281] Loop and spacing. --- src/NoteField.cpp | 2822 ++++++++++++++++++++++----------------------- 1 file changed, 1411 insertions(+), 1411 deletions(-) diff --git a/src/NoteField.cpp b/src/NoteField.cpp index d68f238ed2..550c136001 100644 --- a/src/NoteField.cpp +++ b/src/NoteField.cpp @@ -1,1411 +1,1411 @@ -#include "global.h" -#include "NoteField.h" -#include "RageUtil.h" -#include "GameConstantsAndTypes.h" -#include "ArrowEffects.h" -#include "GameManager.h" -#include "GameState.h" -#include "RageTimer.h" -#include "RageLog.h" -#include "RageMath.h" -#include "ThemeManager.h" -#include "NoteSkinManager.h" -#include "Song.h" -#include "ScreenDimensions.h" -#include "PlayerState.h" -#include "Style.h" -#include "CommonMetrics.h" -#include -#include "BackgroundUtil.h" -#include "Course.h" -#include "NoteData.h" -#include "RageDisplay.h" - -float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceAfterTargetsPixels ); -float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceBeforeTargetsPixels ); - -static ThemeMetric SHOW_BOARD( "NoteField", "ShowBoard" ); -static ThemeMetric SHOW_BEAT_BARS( "NoteField", "ShowBeatBars" ); -static ThemeMetric FADE_BEFORE_TARGETS_PERCENT( "NoteField", "FadeBeforeTargetsPercent" ); -static ThemeMetric BAR_MEASURE_ALPHA( "NoteField", "BarMeasureAlpha" ); -static ThemeMetric BAR_4TH_ALPHA( "NoteField", "Bar4thAlpha" ); -static ThemeMetric BAR_8TH_ALPHA( "NoteField", "Bar8thAlpha" ); -static ThemeMetric BAR_16TH_ALPHA( "NoteField", "Bar16thAlpha" ); -static ThemeMetric FADE_FAIL_TIME( "NoteField", "FadeFailTime" ); - -static RString RoutineNoteSkinName( size_t i ) { return ssprintf("RoutineNoteSkinP%i",int(i+1)); } -static ThemeMetric1D ROUTINE_NOTESKIN( "NoteField", RoutineNoteSkinName, NUM_PLAYERS ); - -NoteField::NoteField() -{ - m_pNoteData = NULL; - m_pCurDisplay = NULL; - - m_textMeasureNumber.LoadFromFont( THEME->GetPathF("NoteField","MeasureNumber") ); - m_textMeasureNumber.SetZoom( 1.0f ); - m_textMeasureNumber.SetShadowLength( 2 ); - m_textMeasureNumber.SetWrapWidthPixels( 300 ); - - m_rectMarkerBar.SetEffectDiffuseShift( 2, RageColor(1,1,1,0.5f), RageColor(0.5f,0.5f,0.5f,0.5f) ); - - m_sprBoard.Load( THEME->GetPathG("NoteField","board") ); - m_sprBoard->SetName("Board"); - m_sprBoard->PlayCommand( "On" ); - this->AddChild( m_sprBoard ); - - m_fBoardOffsetPixels = 0; - m_fCurrentBeatLastUpdate = -1; - m_fYPosCurrentBeatLastUpdate = -1; - this->SubscribeToMessage( Message_CurrentSongChanged ); - - m_sprBeatBars.Load( THEME->GetPathG("NoteField","bars") ); - m_sprBeatBars.StopAnimating(); - - m_iBeginMarker = m_iEndMarker = -1; - - m_fPercentFadeToFail = -1; -} - -NoteField::~NoteField() -{ - Unload(); -} - -void NoteField::Unload() -{ - for( map::iterator it = m_NoteDisplays.begin(); - it != m_NoteDisplays.end(); ++it ) - delete it->second; - m_NoteDisplays.clear(); - m_pCurDisplay = NULL; - memset( m_pDisplays, 0, sizeof(m_pDisplays) ); -} - -void NoteField::CacheNoteSkin( const RString &sNoteSkin_ ) -{ - RString sNoteSkinLower = sNoteSkin_; - sNoteSkinLower.MakeLower(); - - if( m_NoteDisplays.find(sNoteSkinLower) != m_NoteDisplays.end() ) - return; - - LockNoteSkin l( sNoteSkinLower ); - - LOG->Trace("NoteField::CacheNoteSkin: cache %s", sNoteSkinLower.c_str() ); - NoteDisplayCols *nd = new NoteDisplayCols( GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer ); - - for( int c=0; cGetCurrentStyle()->m_iColsPerPlayer; c++ ) - nd->display[c].Load( c, m_pPlayerState, m_fYReverseOffsetPixels ); - nd->m_ReceptorArrowRow.Load( m_pPlayerState, m_fYReverseOffsetPixels ); - nd->m_GhostArrowRow.Load( m_pPlayerState, m_fYReverseOffsetPixels ); - - m_NoteDisplays[ sNoteSkinLower ] = nd; -} - -void NoteField::UncacheNoteSkin( const RString &sNoteSkin_ ) -{ - RString sNoteSkinLower = sNoteSkin_; - sNoteSkinLower.MakeLower(); - - LOG->Trace("NoteField::CacheNoteSkin: release %s", sNoteSkinLower.c_str() ); - ASSERT_M( m_NoteDisplays.find(sNoteSkinLower) != m_NoteDisplays.end(), sNoteSkinLower ); - delete m_NoteDisplays[sNoteSkinLower]; - m_NoteDisplays.erase( sNoteSkinLower ); -} - -void NoteField::CacheAllUsedNoteSkins() -{ - // If we're in Routine mode, apply our per-player noteskins. - if( GAMESTATE->GetCurrentStyle()->m_StyleType == StyleType_TwoPlayersSharedSides ) - { - FOREACH_EnabledPlayer( pn ) - GAMESTATE->ApplyStageModifiers( pn, ROUTINE_NOTESKIN.GetValue(pn) ); - } - - /* Cache all note skins that we might need for the whole song, course or battle - * play, so we don't have to load them later (such as between course songs). */ - vector asSkinsLower; - GAMESTATE->GetAllUsedNoteSkins( asSkinsLower ); - asSkinsLower.push_back( m_pPlayerState->m_PlayerOptions.GetStage().m_sNoteSkin ); - FOREACH( RString, asSkinsLower, s ) - s->MakeLower(); - - for( unsigned i=0; i < asSkinsLower.size(); ++i ) - CacheNoteSkin( asSkinsLower[i] ); - - /* If we're changing note skins in the editor, we can have old note skins lying - * around. Remove them so they don't accumulate. */ - set setNoteSkinsToUnload; - FOREACHM( RString, NoteDisplayCols *, m_NoteDisplays, d ) - { - bool unused = find(asSkinsLower.begin(), asSkinsLower.end(), d->first) == asSkinsLower.end(); - if( unused ) - setNoteSkinsToUnload.insert( d->first ); - } - FOREACHS( RString, setNoteSkinsToUnload, s ) - UncacheNoteSkin( *s ); - - RString sCurrentNoteSkinLower = m_pPlayerState->m_PlayerOptions.GetCurrent().m_sNoteSkin; - sCurrentNoteSkinLower.MakeLower(); - - map::iterator it = m_NoteDisplays.find( sCurrentNoteSkinLower ); - ASSERT_M( it != m_NoteDisplays.end(), sCurrentNoteSkinLower ); - m_pCurDisplay = it->second; - memset( m_pDisplays, 0, sizeof(m_pDisplays) ); - - FOREACH_EnabledPlayer( pn ) - { - RString sNoteSkinLower = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetCurrent().m_sNoteSkin; - sNoteSkinLower.MakeLower(); - it = m_NoteDisplays.find( sNoteSkinLower ); - ASSERT_M( it != m_NoteDisplays.end(), sNoteSkinLower ); - m_pDisplays[pn] = it->second; - } -} - -void NoteField::Init( const PlayerState* pPlayerState, float fYReverseOffsetPixels ) -{ - m_pPlayerState = pPlayerState; - m_fYReverseOffsetPixels = fYReverseOffsetPixels; - CacheAllUsedNoteSkins(); -} - -void NoteField::Load( - const NoteData *pNoteData, - int iDrawDistanceAfterTargetsPixels, - int iDrawDistanceBeforeTargetsPixels ) -{ - ASSERT( pNoteData != NULL ); - m_pNoteData = pNoteData; - m_iDrawDistanceAfterTargetsPixels = iDrawDistanceAfterTargetsPixels; - m_iDrawDistanceBeforeTargetsPixels = iDrawDistanceBeforeTargetsPixels; - ASSERT( m_iDrawDistanceBeforeTargetsPixels >= m_iDrawDistanceAfterTargetsPixels ); - - m_fPercentFadeToFail = -1; - - //int i1 = m_pNoteData->GetNumTracks(); - //int i2 = GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; - ASSERT_M(m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer, - ssprintf("NumTracks %d = ColsPerPlayer %d",m_pNoteData->GetNumTracks(), - GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer)); - - // The NoteSkin may have changed at the beginning of a new course song. - RString sNoteSkinLower = m_pPlayerState->m_PlayerOptions.GetCurrent().m_sNoteSkin; - - /* XXX: Combination of good idea and bad idea to ensure courses load - * regardless of noteskin content. This may take a while to fix. */ - NoteDisplayCols *badIdea = m_pCurDisplay; - - if(sNoteSkinLower.empty()) - { - sNoteSkinLower = m_pPlayerState->m_PlayerOptions.GetPreferred().m_sNoteSkin; - - if(sNoteSkinLower.empty()) - { - sNoteSkinLower = "default"; - } - m_NoteDisplays.insert(pair (sNoteSkinLower, badIdea)); - } - - sNoteSkinLower.MakeLower(); - map::iterator it = m_NoteDisplays.find( sNoteSkinLower ); - ASSERT_M( it != m_NoteDisplays.end(), ssprintf("iterator != m_NoteDisplays.end() [sNoteSkinLower = %s]",sNoteSkinLower.c_str()) ); - memset( m_pDisplays, 0, sizeof(m_pDisplays) ); - FOREACH_EnabledPlayer( pn ) - { - sNoteSkinLower = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetCurrent().m_sNoteSkin; - - // XXX: Re-setup sNoteSkinLower. Unsure if inserting the skin again is needed. - if(sNoteSkinLower.empty()) - { - sNoteSkinLower = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetPreferred().m_sNoteSkin; - - if(sNoteSkinLower.empty()) - { - sNoteSkinLower = "default"; - } - m_NoteDisplays.insert(pair (sNoteSkinLower, badIdea)); - } - - sNoteSkinLower.MakeLower(); - it = m_NoteDisplays.find( sNoteSkinLower ); - ASSERT_M( it != m_NoteDisplays.end(), sNoteSkinLower ); - m_pDisplays[pn] = it->second; - } -} - -void NoteField::Update( float fDeltaTime ) -{ - if( m_bFirstUpdate ) - { - m_pCurDisplay->m_ReceptorArrowRow.PlayCommand( "On" ); - } - - ActorFrame::Update( fDeltaTime ); - - // update m_fBoardOffsetPixels, m_fCurrentBeatLastUpdate, m_fYPosCurrentBeatLastUpdate - const float fCurrentBeat = m_pPlayerState->GetDisplayedPosition().m_fSongBeat; - bool bTweeningOn = m_sprBoard->GetCurrentDiffuseAlpha() >= 0.98 && m_sprBoard->GetCurrentDiffuseAlpha() < 1.00; // HACK - if( !bTweeningOn && m_fCurrentBeatLastUpdate != -1 ) - { - const float fYOffsetLast = ArrowEffects::GetYOffset( m_pPlayerState, 0, m_fCurrentBeatLastUpdate ); - const float fYPosLast = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffsetLast, m_fYReverseOffsetPixels ); - const float fPixelDifference = fYPosLast - m_fYPosCurrentBeatLastUpdate; - - //LOG->Trace( "speed = %f, %f, %f, %f, %f, %f", fSpeed, fYOffsetAtCurrent, fYOffsetAtNext, fSecondsAtCurrent, fSecondsAtNext, fPixelDifference, fSecondsDifference ); - - m_fBoardOffsetPixels += fPixelDifference; - wrap( m_fBoardOffsetPixels, m_sprBoard->GetUnzoomedHeight() ); - } - m_fCurrentBeatLastUpdate = fCurrentBeat; - const float fYOffsetCurrent = ArrowEffects::GetYOffset( m_pPlayerState, 0, m_fCurrentBeatLastUpdate ); - m_fYPosCurrentBeatLastUpdate = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffsetCurrent, m_fYReverseOffsetPixels ); - - m_rectMarkerBar.Update( fDeltaTime ); - - NoteDisplayCols *cur = m_pCurDisplay; - - cur->m_ReceptorArrowRow.Update( fDeltaTime ); - cur->m_GhostArrowRow.Update( fDeltaTime ); - - if( m_fPercentFadeToFail >= 0 ) - m_fPercentFadeToFail = min( m_fPercentFadeToFail + fDeltaTime/FADE_FAIL_TIME, 1 ); - - // Update fade to failed - m_pCurDisplay->m_ReceptorArrowRow.SetFadeToFailPercent( m_fPercentFadeToFail ); - - NoteDisplay::Update( fDeltaTime ); - /* Update all NoteDisplays. Hack: We need to call this once per frame, not - * once per player. */ - // TODO: Remove use of PlayerNumber. - - PlayerNumber pn = m_pPlayerState->m_PlayerNumber; - if( pn == GAMESTATE->GetMasterPlayerNumber() ) - NoteDisplay::Update( fDeltaTime ); -} - -float NoteField::GetWidth() const -{ - const Style* pStyle = GAMESTATE->GetCurrentStyle(); - float fMinX, fMaxX; - // TODO: Remove use of PlayerNumber. - pStyle->GetMinAndMaxColX( m_pPlayerState->m_PlayerNumber, fMinX, fMaxX ); - - const float fYZoom = ArrowEffects::GetZoom( m_pPlayerState ); - return (fMaxX - fMinX + ARROW_SIZE) * fYZoom; -} - -void NoteField::DrawBeatBar( const float fBeat, BeatBarType type, int iMeasureIndex ) -{ - bool bIsMeasure = type == measure; - - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - - float fAlpha; - int iState; - - if( bIsMeasure ) - { - fAlpha = BAR_MEASURE_ALPHA; - iState = 0; - } - else - { - float fScrollSpeed = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollSpeed; - if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing > 0 ) - fScrollSpeed = 4; - switch( type ) - { - DEFAULT_FAIL( type ); - case measure: // handled above - case beat: // fall through - fAlpha = BAR_4TH_ALPHA; - iState = 1; - break; - case half_beat: - fAlpha = SCALE(fScrollSpeed,1.0f,2.0f,0.0f,BAR_8TH_ALPHA); - iState = 2; - break; - case quarter_beat: - fAlpha = SCALE(fScrollSpeed,2.0f,4.0f,0.0f,BAR_16TH_ALPHA); - iState = 3; - break; - } - CLAMP( fAlpha, 0, 1 ); - } - - float fWidth = GetWidth(); - float fFrameWidth = m_sprBeatBars.GetUnzoomedWidth(); - - m_sprBeatBars.SetX( 0 ); - m_sprBeatBars.SetY( fYPos ); - m_sprBeatBars.SetDiffuse( RageColor(1,1,1,fAlpha) ); - m_sprBeatBars.SetState( iState ); - m_sprBeatBars.SetCustomTextureRect( RectF(0,SCALE(iState,0.f,4.f,0.f,1.f), fWidth/fFrameWidth, SCALE(iState+1,0.f,4.f,0.f,1.f)) ); - m_sprBeatBars.SetZoomX( fWidth/m_sprBeatBars.GetUnzoomedWidth() ); - m_sprBeatBars.Draw(); - - - if( GAMESTATE->IsEditing() && bIsMeasure ) - { - int iMeasureNoDisplay = iMeasureIndex; - - m_textMeasureNumber.SetDiffuse( RageColor(1,1,1,1) ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,0) ); - m_textMeasureNumber.SetHorizAlign( align_right ); - m_textMeasureNumber.SetText( ssprintf("%d", iMeasureNoDisplay) ); - m_textMeasureNumber.SetXY( -fWidth/2, fYPos ); - m_textMeasureNumber.Draw(); - } -} - -void NoteField::DrawBoard( int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) -{ - // Draw the board centered on fYPosAt0 so that the board doesn't slide as - // the draw distance changes with modifiers. - const float fYPosAt0 = ArrowEffects::GetYPos( m_pPlayerState, 0, 0, m_fYReverseOffsetPixels ); - - // todo: make this an AutoActor instead? -aj - Sprite *pSprite = dynamic_cast( (Actor*)m_sprBoard ); - if( pSprite == NULL ) - RageException::Throw( "Board must be a Sprite" ); - - RectF rect = *pSprite->GetCurrentTextureCoordRect(); - const float fBoardGraphicHeightPixels = pSprite->GetUnzoomedHeight(); - float fTexCoordOffset = m_fBoardOffsetPixels / fBoardGraphicHeightPixels; - { - // top half - const float fHeight = iDrawDistanceBeforeTargetsPixels - iDrawDistanceAfterTargetsPixels; - const float fY = fYPosAt0 - ((iDrawDistanceBeforeTargetsPixels + iDrawDistanceAfterTargetsPixels) / 2.0f); - - pSprite->ZoomToHeight( fHeight ); - pSprite->SetY( fY ); - - // handle tex coord offset and fade - rect.top = -fTexCoordOffset-(iDrawDistanceBeforeTargetsPixels/fBoardGraphicHeightPixels); - rect.bottom = -fTexCoordOffset+(-iDrawDistanceAfterTargetsPixels/fBoardGraphicHeightPixels); - pSprite->SetCustomTextureRect( rect ); - float fFadeTop = FADE_BEFORE_TARGETS_PERCENT * iDrawDistanceBeforeTargetsPixels / (iDrawDistanceBeforeTargetsPixels-iDrawDistanceAfterTargetsPixels); - pSprite->SetFadeTop( fFadeTop ); - pSprite->Draw(); - } -} - -void NoteField::DrawMarkerBar( int iBeat ) -{ - float fBeat = NoteRowToBeat( iBeat ); - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - - m_rectMarkerBar.StretchTo( RectF(-GetWidth()/2, fYPos-ARROW_SIZE/2, GetWidth()/2, fYPos+ARROW_SIZE/2) ); - m_rectMarkerBar.Draw(); -} - -static ThemeMetric AREA_HIGHLIGHT_COLOR("NoteField", "AreaHighlightColor"); -void NoteField::DrawAreaHighlight( int iStartBeat, int iEndBeat ) -{ - float fStartBeat = NoteRowToBeat( iStartBeat ); - float fEndBeat = NoteRowToBeat( iEndBeat ); - float fDrawDistanceAfterTargetsPixels = ArrowEffects::GetYOffset( m_pPlayerState, 0, fStartBeat ); - float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fDrawDistanceAfterTargetsPixels, m_fYReverseOffsetPixels ); - float fDrawDistanceBeforeTargetsPixels = ArrowEffects::GetYOffset( m_pPlayerState, 0, fEndBeat ); - float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fDrawDistanceBeforeTargetsPixels, m_fYReverseOffsetPixels ); - - // The caller should have clamped these to reasonable values - ASSERT( fYStartPos > -1000 ); - ASSERT( fYEndPos < +5000 ); - - m_rectAreaHighlight.StretchTo( RectF(-GetWidth()/2, fYStartPos, GetWidth()/2, fYEndPos) ); - m_rectAreaHighlight.SetDiffuse( AREA_HIGHLIGHT_COLOR ); - m_rectAreaHighlight.Draw(); -} - -// todo: add DrawWarpAreaBG? -aj - -static ThemeMetric BPM_COLOR ( "NoteField", "BPMColor" ); -static ThemeMetric STOP_COLOR ( "NoteField", "StopColor" ); -static ThemeMetric DELAY_COLOR ( "NoteField", "DelayColor" ); -static ThemeMetric WARP_COLOR ( "NoteField", "WarpColor" ); -static ThemeMetric TIME_SIGNATURE_COLOR ( "NoteField", "TimeSignatureColor" ); -static ThemeMetric TICKCOUNT_COLOR ( "NoteField", "TickcountColor" ); -static ThemeMetric COMBO_COLOR ( "NoteField", "ComboColor" ); -static ThemeMetric LABEL_COLOR ( "NoteField", "LabelColor" ); -static ThemeMetric SPEED_COLOR ( "NoteField", "SpeedColor" ); -static ThemeMetric SCROLL_COLOR ( "NoteField", "ScrollColor" ); -static ThemeMetric FAKE_COLOR ("NoteField", "FakeColor" ); -static ThemeMetric BPM_IS_LEFT_SIDE ( "NoteField", "BPMIsLeftSide" ); -static ThemeMetric STOP_IS_LEFT_SIDE ( "NoteField", "StopIsLeftSide" ); -static ThemeMetric DELAY_IS_LEFT_SIDE ( "NoteField", "DelayIsLeftSide" ); -static ThemeMetric WARP_IS_LEFT_SIDE ( "NoteField", "WarpIsLeftSide" ); -static ThemeMetric TIME_SIGNATURE_IS_LEFT_SIDE ( "NoteField", "TimeSignatureIsLeftSide" ); -static ThemeMetric TICKCOUNT_IS_LEFT_SIDE ( "NoteField", "TickcountIsLeftSide" ); -static ThemeMetric COMBO_IS_LEFT_SIDE ( "NoteField", "ComboIsLeftSide" ); -static ThemeMetric LABEL_IS_LEFT_SIDE ( "NoteField", "LabelIsLeftSide" ); -static ThemeMetric SPEED_IS_LEFT_SIDE ( "NoteField", "SpeedIsLeftSide" ); -static ThemeMetric SCROLL_IS_LEFT_SIDE ( "NoteField", "ScrollIsLeftSide" ); -static ThemeMetric FAKE_IS_LEFT_SIDE ( "NoteField", "FakeIsLeftSide" ); -static ThemeMetric BPM_OFFSETX ( "NoteField", "BPMOffsetX" ); -static ThemeMetric STOP_OFFSETX ( "NoteField", "StopOffsetX" ); -static ThemeMetric DELAY_OFFSETX ( "NoteField", "DelayOffsetX" ); -static ThemeMetric WARP_OFFSETX ( "NoteField", "WarpOffsetX" ); -static ThemeMetric TIME_SIGNATURE_OFFSETX ( "NoteField", "TimeSignatureOffsetX" ); -static ThemeMetric TICKCOUNT_OFFSETX ( "NoteField", "TickcountOffsetX" ); -static ThemeMetric COMBO_OFFSETX ( "NoteField", "ComboOffsetX" ); -static ThemeMetric LABEL_OFFSETX ( "NoteField", "LabelOffsetX" ); -static ThemeMetric SPEED_OFFSETX ( "NoteField", "SpeedOffsetX" ); -static ThemeMetric SCROLL_OFFSETX ( "NoteField", "ScrollOffsetX" ); -static ThemeMetric FAKE_OFFSETX ( "NoteField", "FakeOffsetX" ); - -void NoteField::DrawBPMText( const float fBeat, const float fBPM ) -{ - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - const float xBase = GetWidth()/2.f; - const float xOffset = BPM_OFFSETX * fZoom; - - m_textMeasureNumber.SetZoom( fZoom ); - m_textMeasureNumber.SetHorizAlign( BPM_IS_LEFT_SIDE ? align_right : align_left ); - m_textMeasureNumber.SetDiffuse( BPM_COLOR ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); - m_textMeasureNumber.SetText( FloatToString(fBPM) ); - m_textMeasureNumber.SetXY( (BPM_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); - m_textMeasureNumber.Draw(); -} - -void NoteField::DrawFreezeText( const float fBeat, const float fSecs ) -{ - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - const float xBase = GetWidth()/2.f; - const float xOffset = STOP_OFFSETX * fZoom; - - m_textMeasureNumber.SetZoom( fZoom ); - m_textMeasureNumber.SetHorizAlign( STOP_IS_LEFT_SIDE ? align_right : align_left ); - m_textMeasureNumber.SetDiffuse( STOP_COLOR ); - m_textMeasureNumber.SetXY( (STOP_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); - m_textMeasureNumber.SetText( FloatToString(fSecs) ); - m_textMeasureNumber.Draw(); -} - -void NoteField::DrawDelayText( const float fBeat, const float fSecs ) -{ - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - const float xBase = GetWidth()/2.f; - const float xOffset = DELAY_OFFSETX * fZoom; - - m_textMeasureNumber.SetZoom( fZoom ); - m_textMeasureNumber.SetHorizAlign( DELAY_IS_LEFT_SIDE ? align_right : align_left ); - m_textMeasureNumber.SetDiffuse( DELAY_COLOR ); - m_textMeasureNumber.SetXY( (DELAY_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); - m_textMeasureNumber.SetText( FloatToString(fSecs) ); - m_textMeasureNumber.Draw(); -} - -void NoteField::DrawWarpText( const float fBeat, const float fNewBeat ) -{ - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - const float xBase = GetWidth()/2.f; - const float xOffset = WARP_OFFSETX * fZoom; - - m_textMeasureNumber.SetZoom( fZoom ); - m_textMeasureNumber.SetHorizAlign( WARP_IS_LEFT_SIDE ? align_right : align_left ); - m_textMeasureNumber.SetDiffuse( WARP_COLOR ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); - m_textMeasureNumber.SetText( FloatToString(fNewBeat) ); - m_textMeasureNumber.SetXY( (WARP_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); - m_textMeasureNumber.Draw(); -} - -void NoteField::DrawTimeSignatureText( const float fBeat, int iNumerator, int iDenominator ) -{ - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - const float xBase = GetWidth()/2.f; - const float xOffset = TIME_SIGNATURE_OFFSETX * fZoom; - - m_textMeasureNumber.SetZoom( fZoom ); - m_textMeasureNumber.SetHorizAlign( TIME_SIGNATURE_IS_LEFT_SIDE ? align_right : align_left ); - m_textMeasureNumber.SetDiffuse( TIME_SIGNATURE_COLOR ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); - m_textMeasureNumber.SetText( ssprintf("%d\n--\n%d", iNumerator, iDenominator) ); - m_textMeasureNumber.SetXY( (TIME_SIGNATURE_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); - m_textMeasureNumber.Draw(); -} - -void NoteField::DrawTickcountText( const float fBeat, int iTicks ) -{ - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - const float xBase = GetWidth()/2.f; - const float xOffset = TICKCOUNT_OFFSETX * fZoom; - - m_textMeasureNumber.SetZoom( fZoom ); - m_textMeasureNumber.SetHorizAlign( TICKCOUNT_IS_LEFT_SIDE ? align_right : align_left ); - m_textMeasureNumber.SetDiffuse( TICKCOUNT_COLOR ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); - m_textMeasureNumber.SetText( ssprintf("%d", iTicks) ); - m_textMeasureNumber.SetXY( (TICKCOUNT_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); - m_textMeasureNumber.Draw(); -} - -void NoteField::DrawComboText( const float fBeat, int iCombo, int iMiss ) -{ - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - const float xBase = GetWidth()/2.f; - const float xOffset = COMBO_OFFSETX * fZoom; - - m_textMeasureNumber.SetZoom( fZoom ); - m_textMeasureNumber.SetHorizAlign( COMBO_IS_LEFT_SIDE ? align_right : align_left ); - m_textMeasureNumber.SetDiffuse( COMBO_COLOR ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); - m_textMeasureNumber.SetText( ssprintf("%d/%d", iCombo, iMiss) ); - m_textMeasureNumber.SetXY( (COMBO_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); - m_textMeasureNumber.Draw(); -} - -void NoteField::DrawLabelText( const float fBeat, RString sLabel ) -{ - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - const float xBase = GetWidth()/2.f; - const float xOffset = LABEL_OFFSETX * fZoom; - - m_textMeasureNumber.SetZoom( fZoom ); - m_textMeasureNumber.SetHorizAlign( LABEL_IS_LEFT_SIDE ? align_right : align_left ); - m_textMeasureNumber.SetDiffuse( LABEL_COLOR ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); - m_textMeasureNumber.SetText( sLabel.c_str() ); - m_textMeasureNumber.SetXY( (LABEL_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); - m_textMeasureNumber.Draw(); -} - -void NoteField::DrawSpeedText( const float fBeat, float fPercent, float fWait, int iMode ) -{ - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - const float xBase = GetWidth()/2.f; - const float xOffset = SPEED_OFFSETX * fZoom; - - m_textMeasureNumber.SetZoom( fZoom ); - m_textMeasureNumber.SetHorizAlign( SPEED_IS_LEFT_SIDE ? align_right : align_left ); - m_textMeasureNumber.SetDiffuse( SPEED_COLOR ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); - m_textMeasureNumber.SetText( ssprintf("%s\n%s\n%s", FloatToString(fPercent).c_str(), (iMode == 1 ? "S" : "B"), FloatToString(fWait).c_str()) ); - m_textMeasureNumber.SetXY( (SPEED_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); - m_textMeasureNumber.Draw(); -} - -void NoteField::DrawScrollText( const float fBeat, float fPercent ) -{ - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - const float xBase = GetWidth()/2.f; - const float xOffset = SCROLL_OFFSETX * fZoom; - - m_textMeasureNumber.SetZoom( fZoom ); - m_textMeasureNumber.SetHorizAlign( SCROLL_IS_LEFT_SIDE ? align_right : align_left ); - m_textMeasureNumber.SetDiffuse( SCROLL_COLOR ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); - m_textMeasureNumber.SetText( FloatToString(fPercent) ); - m_textMeasureNumber.SetXY( (SCROLL_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); - m_textMeasureNumber.Draw(); -} - -void NoteField::DrawFakeText( const float fBeat, const float fNewBeat ) -{ - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - const float xBase = GetWidth()/2.f; - const float xOffset = FAKE_OFFSETX * fZoom; - - m_textMeasureNumber.SetZoom( fZoom ); - m_textMeasureNumber.SetHorizAlign( FAKE_IS_LEFT_SIDE ? align_right : align_left ); - m_textMeasureNumber.SetDiffuse( FAKE_COLOR ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); - m_textMeasureNumber.SetText( FloatToString(fNewBeat) ); - m_textMeasureNumber.SetXY( (FAKE_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); - m_textMeasureNumber.Draw(); -} - -void NoteField::DrawAttackText( const float fBeat, const Attack &attack ) -{ - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - - m_textMeasureNumber.SetZoom( fZoom ); - m_textMeasureNumber.SetHorizAlign( align_left ); - m_textMeasureNumber.SetDiffuse( RageColor(0,0.8f,0.8f,1) ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); - m_textMeasureNumber.SetText( attack.GetTextDescription() ); - m_textMeasureNumber.SetXY( +GetWidth()/2.f + 10*fZoom, fYPos ); - m_textMeasureNumber.Draw(); -} - -void NoteField::DrawBGChangeText( const float fBeat, const RString sNewBGName ) -{ - const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); - const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); - const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); - - m_textMeasureNumber.SetZoom( fZoom ); - m_textMeasureNumber.SetHorizAlign( align_left ); - m_textMeasureNumber.SetDiffuse( RageColor(0,1,0,1) ); - m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); - m_textMeasureNumber.SetText( sNewBGName ); - m_textMeasureNumber.SetXY( +GetWidth()/2.f, fYPos ); - m_textMeasureNumber.Draw(); -} - -static CacheNoteStat GetNumNotesFromBeginning( const PlayerState *pPlayerState, float beat ) -{ - // XXX: I realized that I have copied and pasted my binary search code 3 times already. - // how can we abstract this? - const vector &data = pPlayerState->m_CacheNoteStat; - int max = data.size() - 1; - int l = 0, r = max; - while( l <= r ) - { - int m = ( l + r ) / 2; - if( ( m == 0 || data[m].beat <= beat ) && ( m == max || beat < data[m + 1].beat ) ) - { - return data[m]; - } - else if( data[m].beat <= beat ) - { - l = m + 1; - } - else - { - r = m - 1; - } - } - CacheNoteStat dummy = { 0, 0, 0 }; - return dummy; -} - -static int GetNumNotesRange( const PlayerState* pPlayerState, float fLow, float fHigh ) -{ - CacheNoteStat low = GetNumNotesFromBeginning( pPlayerState, fLow ); - CacheNoteStat high = GetNumNotesFromBeginning( pPlayerState, fHigh ); - return high.notesUpper - low.notesLower; -} - -float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceAfterTargetsPixels ) -{ - - float fLow = 0, fHigh = pPlayerState->GetDisplayedPosition().m_fSongBeat; - - bool bHasCache = pPlayerState->m_CacheNoteStat.size() > 0; - - if( !bHasCache ) - { - fLow = fHigh - 4.0f; - } - - const int NUM_ITERATIONS = 24; - const int MAX_NOTES_AFTER = 64; - - float fFirstBeatToDraw = fLow; - - for( int i = 0; i < NUM_ITERATIONS; i ++ ) - { - - float fMid = (fLow + fHigh) / 2.0f; - - bool bIsPastPeakYOffset; - float fPeakYOffset; - float fYOffset = ArrowEffects::GetYOffset( pPlayerState, 0, fMid, fPeakYOffset, bIsPastPeakYOffset, true ); - - if( fYOffset < iDrawDistanceAfterTargetsPixels || ( bHasCache && GetNumNotesRange( pPlayerState, fMid, pPlayerState->GetDisplayedPosition().m_fSongBeat ) > MAX_NOTES_AFTER ) ) // off screen / too many notes - { - fFirstBeatToDraw = fMid; // move towards fSongBeat - fLow = fMid; - } - else // on screen, move away!! - { - fHigh = fMid; - } - - } - - return fFirstBeatToDraw; - -} - -float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceBeforeTargetsPixels ) -{ - // Probe for last note to draw. Worst case is 0.25x + boost. - // Adjust search distance so that notes don't pop onto the screen. - float fSearchDistance = 10; - float fLastBeatToDraw = pPlayerState->GetDisplayedPosition().m_fSongBeat+fSearchDistance; - float fSpeedMultiplier = pPlayerState->GetDisplayedTiming().GetDisplayedSpeedPercent(pPlayerState->GetDisplayedPosition().m_fSongBeatVisible, pPlayerState->GetDisplayedPosition().m_fMusicSecondsVisible); - - const int NUM_ITERATIONS = 20; - - bool bBoomerang; - { - const float* fAccels = pPlayerState->m_PlayerOptions.GetCurrent().m_fAccels; - bBoomerang = (fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0); - } - - for( int i=0; i iDrawDistanceBeforeTargetsPixels ) // off screen - fLastBeatToDraw -= fSearchDistance; - else // on screen - fLastBeatToDraw += fSearchDistance; - - fSearchDistance /= 2; - } - - if( fSpeedMultiplier < 0.75 ) - { - fLastBeatToDraw = min(fLastBeatToDraw, pPlayerState->GetDisplayedPosition().m_fSongBeat + 16); - } - - return fLastBeatToDraw; -} - -inline float NoteRowToVisibleBeat( const PlayerState *pPlayerState, int iRow ) -{ - return NoteRowToBeat(iRow); -} - -bool NoteField::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const -{ - // TRICKY: If boomerang is on, then ones in the range - // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible. - // Test to see if this beat is visible before drawing. - float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fBeat ); - if( fYOffset > iDrawDistanceBeforeTargetsPixels ) // off screen - return false; - if( fYOffset < iDrawDistanceAfterTargetsPixels ) // off screen - return false; - - return true; -} - -void NoteField::DrawPrimitives() -{ - //LOG->Trace( "NoteField::DrawPrimitives()" ); - - // This should be filled in on the first update. - ASSERT( m_pCurDisplay != NULL ); - - ArrowEffects::Update(); - - NoteDisplayCols *cur = m_pCurDisplay; - - const PlayerOptions ¤t_po = m_pPlayerState->m_PlayerOptions.GetCurrent(); - - // Adjust draw range depending on some effects - int iDrawDistanceAfterTargetsPixels = m_iDrawDistanceAfterTargetsPixels; - // HACK: If boomerang and centered are on, then we want to draw much - // earlier so that the notes don't pop on screen. - float fCenteredTimesBoomerang = - current_po.m_fScrolls[PlayerOptions::SCROLL_CENTERED] * - current_po.m_fAccels[PlayerOptions::ACCEL_BOOMERANG]; - iDrawDistanceAfterTargetsPixels += int(SCALE( fCenteredTimesBoomerang, 0.f, 1.f, 0.f, -SCREEN_HEIGHT/2 )); - int iDrawDistanceBeforeTargetsPixels = m_iDrawDistanceBeforeTargetsPixels; - - float fDrawScale = 1; - fDrawScale *= 1 + 0.5f * fabsf( current_po.m_fPerspectiveTilt ); - fDrawScale *= 1 + fabsf( current_po.m_fEffects[PlayerOptions::EFFECT_MINI] ); - - iDrawDistanceAfterTargetsPixels = (int)(iDrawDistanceAfterTargetsPixels * fDrawScale); - iDrawDistanceBeforeTargetsPixels = (int)(iDrawDistanceBeforeTargetsPixels * fDrawScale); - - - // Probe for first and last notes on the screen - float fFirstBeatToDraw = FindFirstDisplayedBeat( m_pPlayerState, iDrawDistanceAfterTargetsPixels ); - float fLastBeatToDraw = FindLastDisplayedBeat( m_pPlayerState, iDrawDistanceBeforeTargetsPixels ); - - m_pPlayerState->m_fLastDrawnBeat = fLastBeatToDraw; - - const int iFirstRowToDraw = BeatToNoteRow(fFirstBeatToDraw); - const int iLastRowToDraw = BeatToNoteRow(fLastBeatToDraw); - - //LOG->Trace( "start = %f.1, end = %f.1", fFirstBeatToDraw-fSongBeat, fLastBeatToDraw-fSongBeat ); - //LOG->Trace( "Drawing elements %d through %d", iFirstRowToDraw, iLastRowToDraw ); - -#define IS_ON_SCREEN( fBeat ) ( fFirstBeatToDraw <= (fBeat) && (fBeat) <= fLastBeatToDraw && IsOnScreen( fBeat, 0, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) ) - - // Draw board - if( SHOW_BOARD ) - { - DrawBoard( iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ); - } - - // Draw Receptors - { - cur->m_ReceptorArrowRow.Draw(); - } - - const TimingData *pTiming = &m_pPlayerState->GetDisplayedTiming(); - const vector* segs[NUM_TimingSegmentType]; - - FOREACH_TimingSegmentType( tst ) - segs[tst] = &(pTiming->GetTimingSegments(tst)); - - unsigned i = 0; - // Draw beat bars - if( ( GAMESTATE->IsEditing() || SHOW_BEAT_BARS ) && pTiming != NULL ) - { - const vector &tSigs = *segs[SEGMENT_TIME_SIG]; - int iMeasureIndex = 0; - for (i = 0; i < tSigs.size(); i++) - { - const TimeSignatureSegment *ts = ToTimeSignature(tSigs[i]); - int iSegmentEndRow = (i + 1 == tSigs.size()) ? iLastRowToDraw : tSigs[i+1]->GetRow(); - - // beat bars every 16th note - int iDrawBeatBarsEveryRows = BeatToNoteRow( ((float)ts->GetDen()) / 4 ) / 4; - - // In 4/4, every 16th beat bar is a measure - int iMeasureBarFrequency = ts->GetNum() * 4; - int iBeatBarsDrawn = 0; - - for( int j=ts->GetRow(); j < iSegmentEndRow; j += iDrawBeatBarsEveryRows ) - { - bool bMeasureBar = iBeatBarsDrawn % iMeasureBarFrequency == 0; - BeatBarType type = quarter_beat; - if( bMeasureBar ) - type = measure; - else if( iBeatBarsDrawn % 4 == 0 ) - type = beat; - else if( iBeatBarsDrawn % 2 == 0 ) - type = half_beat; - float fBeat = NoteRowToBeat(j); - - if( IS_ON_SCREEN(fBeat) ) - { - DrawBeatBar( fBeat, type, iMeasureIndex ); - } - - iBeatBarsDrawn++; - if( bMeasureBar ) - iMeasureIndex++; - } - } - } - - if( GAMESTATE->IsEditing() && pTiming != NULL ) - { - ASSERT(GAMESTATE->m_pCurSong != NULL); - - const TimingData &timing = *pTiming; - - // Scroll text - if( GAMESTATE->m_bIsUsingStepTiming ) - { - for (i = 0; i < segs[SEGMENT_SCROLL]->size(); i++) - { - ScrollSegment *seg = ToScroll( segs[SEGMENT_SCROLL]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) - { - float fBeat = seg->GetBeat(); - if( IS_ON_SCREEN(fBeat) ) - DrawScrollText( fBeat, seg->GetRatio() ); - } - } - } - - // BPM text - for (i = 0; i < segs[SEGMENT_BPM]->size(); i++) - { - const BPMSegment *seg = ToBPM( segs[SEGMENT_BPM]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) - { - float fBeat = seg->GetBeat(); - if( IS_ON_SCREEN(fBeat) ) - DrawBPMText( fBeat, seg->GetBPM() ); - } - } - - // Freeze text - for (i = 0; i < segs[SEGMENT_STOP]->size(); i++) - { - const StopSegment *seg = ToStop( segs[SEGMENT_STOP]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) - { - float fBeat = seg->GetBeat(); - if( IS_ON_SCREEN(fBeat) ) - DrawFreezeText( fBeat, seg->GetPause() ); - } - } - - // Delay text - for (i = 0; i < segs[SEGMENT_DELAY]->size(); i++) - { - const DelaySegment *seg = ToDelay( segs[SEGMENT_DELAY]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) - { - float fBeat = seg->GetBeat(); - if( IS_ON_SCREEN(fBeat) ) - DrawDelayText( fBeat, seg->GetPause() ); - } - } - - // Warp text - for (i = 0; i < segs[SEGMENT_WARP]->size(); i++) - { - const WarpSegment *seg = ToWarp( segs[SEGMENT_WARP]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) - { - float fBeat = seg->GetBeat(); - if( IS_ON_SCREEN(fBeat) ) - DrawWarpText( fBeat, seg->GetLength() ); - } - } - - // Time Signature text - for (i = 0; i < segs[SEGMENT_TIME_SIG]->size(); i++) - { - const TimeSignatureSegment *seg = ToTimeSignature( segs[SEGMENT_TIME_SIG]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) - { - float fBeat = seg->GetBeat(); - if( IS_ON_SCREEN(fBeat) ) - DrawTimeSignatureText( fBeat, seg->GetNum(), seg->GetDen() ); - } - } - - // Tickcount text - for (i = 0; i < segs[SEGMENT_TICKCOUNT]->size(); i++) - { - const TickcountSegment *seg = ToTickcount( segs[SEGMENT_TICKCOUNT]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) - { - float fBeat = seg->GetBeat(); - if( IS_ON_SCREEN(fBeat) ) - DrawTickcountText( fBeat, seg->GetTicks() ); - } - } - - // Combo text - for (i = 0; i < segs[SEGMENT_COMBO]->size(); i++) - { - const ComboSegment *seg = ToCombo( segs[SEGMENT_COMBO]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) - { - float fBeat = seg->GetBeat(); - if( IS_ON_SCREEN(fBeat) ) - DrawComboText( fBeat, seg->GetCombo(), seg->GetMissCombo() ); - } - } - - // Label text - for (i = 0; i < segs[SEGMENT_LABEL]->size(); i++) - { - const LabelSegment *seg = ToLabel( segs[SEGMENT_LABEL]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) - { - float fBeat = seg->GetBeat(); - if( IS_ON_SCREEN(fBeat) ) - DrawLabelText( fBeat, seg->GetLabel() ); - } - } - - // Speed text - for (i = 0; i < segs[SEGMENT_SPEED]->size(); i++) - { - const SpeedSegment *seg = ToSpeed( segs[SEGMENT_SPEED]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) - { - float fBeat = seg->GetBeat(); - if( IS_ON_SCREEN(fBeat) ) - DrawSpeedText(fBeat, seg->GetRatio(), - seg->GetDelay(), seg->GetUnit() ); - } - } - - // Fake text - for (i = 0; i < segs[SEGMENT_FAKE]->size(); i++) - { - const FakeSegment *seg = ToFake( segs[SEGMENT_FAKE]->at(i) ); - if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) - { - float fBeat = seg->GetBeat(); - if( IS_ON_SCREEN(fBeat) ) - DrawFakeText( fBeat, seg->GetLength() ); - } - } - - // Course mods text - const Course *pCourse = GAMESTATE->m_pCurCourse; - if( pCourse ) - { - ASSERT_M( GAMESTATE->m_iEditCourseEntryIndex >= 0 && GAMESTATE->m_iEditCourseEntryIndex < (int)pCourse->m_vEntries.size(), - ssprintf("%i",GAMESTATE->m_iEditCourseEntryIndex.Get()) ); - const CourseEntry &ce = pCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex]; - FOREACH_CONST( Attack, ce.attacks, a ) - { - float fSecond = a->fStartSecond; - float fBeat = timing.GetBeatFromElapsedTime( fSecond ); - - if( BeatToNoteRow(fBeat) >= iFirstRowToDraw && - BeatToNoteRow(fBeat) <= iLastRowToDraw) - { - if( IS_ON_SCREEN(fBeat) ) - DrawAttackText( fBeat, *a ); - } - } - } - else - { - AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ? - GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks : - GAMESTATE->m_pCurSong->m_Attacks; - // XXX: We're somehow getting here when attacks is null. Find the actual cause later. - if (&attacks) - { - FOREACH_CONST(Attack, attacks, a) - { - float fBeat = timing.GetBeatFromElapsedTime(a->fStartSecond); - if (BeatToNoteRow(fBeat) >= iFirstRowToDraw && - BeatToNoteRow(fBeat) <= iLastRowToDraw && - IS_ON_SCREEN(fBeat)) - { - this->DrawAttackText(fBeat, *a); - } - } - } - } - - if( !GAMESTATE->m_bIsUsingStepTiming ) - { - // BGChange text - EditMode mode = GAMESTATE->m_EditMode; - switch( mode ) - { - case EditMode_Home: - case EditMode_CourseMods: - case EditMode_Practice: - break; - case EditMode_Full: - { - vector::iterator iter[NUM_BackgroundLayer]; - FOREACH_BackgroundLayer( j ) - iter[j] = GAMESTATE->m_pCurSong->GetBackgroundChanges(j).begin(); - - while( 1 ) - { - float fLowestBeat = FLT_MAX; - vector viLowestIndex; - - FOREACH_BackgroundLayer( j ) - { - if( iter[j] == GAMESTATE->m_pCurSong->GetBackgroundChanges(j).end() ) - continue; - - float fBeat = iter[j]->m_fStartBeat; - if( fBeat < fLowestBeat ) - { - fLowestBeat = fBeat; - viLowestIndex.clear(); - viLowestIndex.push_back( j ); - } - else if( fBeat == fLowestBeat ) - { - viLowestIndex.push_back( j ); - } - } - - if( viLowestIndex.empty() ) - { - FOREACH_BackgroundLayer( j ) - ASSERT( iter[j] == GAMESTATE->m_pCurSong->GetBackgroundChanges(j).end() ); - break; - } - - if( IS_ON_SCREEN(fLowestBeat) ) - { - vector vsBGChanges; - FOREACH_CONST( BackgroundLayer, viLowestIndex, bl ) - { - ASSERT( iter[*bl] != GAMESTATE->m_pCurSong->GetBackgroundChanges(*bl).end() ); - const BackgroundChange& change = *iter[*bl]; - RString s = change.GetTextDescription(); - if( *bl!=0 ) - s = ssprintf("%d: ",*bl) + s; - vsBGChanges.push_back( s ); - } - DrawBGChangeText( fLowestBeat, join("\n",vsBGChanges) ); - } - FOREACH_CONST( BackgroundLayer, viLowestIndex, bl ) - iter[*bl]++; - } - } - break; - default: - FAIL_M(ssprintf("Invalid edit mode: %i", mode)); - } - } - - // Draw marker bars - if( m_iBeginMarker != -1 && m_iEndMarker != -1 ) - { - int iBegin = m_iBeginMarker; - int iEnd = m_iEndMarker; - CLAMP( iBegin, iFirstRowToDraw, iLastRowToDraw ); - CLAMP( iEnd, iFirstRowToDraw, iLastRowToDraw ); - DrawAreaHighlight( iBegin, iEnd ); - } - else if( m_iBeginMarker != -1 ) - { - if( m_iBeginMarker >= iFirstRowToDraw && - m_iBeginMarker <= iLastRowToDraw ) - DrawMarkerBar( m_iBeginMarker ); - } - else if( m_iEndMarker != -1 ) - { - if( m_iEndMarker >= iFirstRowToDraw && - m_iEndMarker <= iLastRowToDraw ) - DrawMarkerBar( m_iEndMarker ); - } - } - - // Optimization is very important here because there are so many arrows to draw. - // Draw the arrows in order of column. This minimizes texture switches and - // lets us draw in big batches. - - float fSelectedRangeGlow = SCALE( RageFastCos(RageTimer::GetTimeSinceStartFast()*2), -1, 1, 0.1f, 0.3f ); - - const Style* pStyle = GAMESTATE->GetCurrentStyle(); - ASSERT_M(m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer, - ssprintf("NumTracks %d != ColsPerPlayer %d",m_pNoteData->GetNumTracks(), - GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer)); - - for( int j=0; jGetNumTracks(); j++ ) // for each arrow column - { - const int c = pStyle->m_iColumnDrawOrder[j]; - - bool bAnyUpcomingInThisCol = false; - - // Draw all HoldNotes in this column (so that they appear under the tap notes) - { - NoteData::TrackMap::const_iterator begin, end; - m_pNoteData->GetTapNoteRangeInclusive( c, iFirstRowToDraw, iLastRowToDraw+1, begin, end ); - - for( ; begin != end; ++begin ) - { - const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, j); - if( tn.type != TapNote::hold_head ) - continue; // skip - - const HoldNoteResult &Result = tn.HoldResult; - if( Result.hns == HNS_Held ) // if this HoldNote was completed - continue; // don't draw anything - - int iStartRow = begin->first; - int iEndRow = iStartRow + tn.iDuration; - - // TRICKY: If boomerang is on, then all notes in the range - // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible. - // Test every note to make sure it's on screen before drawing - float fThrowAway; - bool bStartIsPastPeak = false; - bool bEndIsPastPeak = false; - float fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iStartRow), fThrowAway, bStartIsPastPeak ); - float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iEndRow), fThrowAway, bEndIsPastPeak ); - - bool bTailIsOnVisible = iDrawDistanceAfterTargetsPixels <= fEndYOffset && fEndYOffset <= iDrawDistanceBeforeTargetsPixels; - bool bHeadIsVisible = iDrawDistanceAfterTargetsPixels <= fStartYOffset && fStartYOffset <= iDrawDistanceBeforeTargetsPixels; - bool bStraddlingVisible = fStartYOffset <= iDrawDistanceAfterTargetsPixels && iDrawDistanceBeforeTargetsPixels <= fEndYOffset; - bool bStaddlingPeak = bStartIsPastPeak && !bEndIsPastPeak; - if( !(bTailIsOnVisible || bHeadIsVisible || bStraddlingVisible || bStaddlingPeak) ) - { - //LOG->Trace( "skip drawing this hold." ); - continue; // skip - } - - bool bIsAddition = (tn.source == TapNote::addition); - bool bIsHopoPossible = (tn.bHopoPossible); - bool bUseAdditionColoring = bIsAddition || bIsHopoPossible; - const bool bHoldGhostShowing = tn.HoldResult.bActive && tn.HoldResult.fLife > 0; - const bool bIsHoldingNote = tn.HoldResult.bHeld; - if( bHoldGhostShowing ) - m_pCurDisplay->m_GhostArrowRow.SetHoldShowing( c, tn ); - - ASSERT_M( NoteRowToBeat(iStartRow) > -2000, ssprintf("%i %i %i", iStartRow, iEndRow, c) ); - - bool bIsInSelectionRange = false; - if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 ) - bIsInSelectionRange = (m_iBeginMarker <= iStartRow && iEndRow < m_iEndMarker); - - NoteDisplayCols *displayCols = tn.pn == PLAYER_INVALID ? m_pCurDisplay : m_pDisplays[tn.pn]; - displayCols->display[c].DrawHold( tn, c, iStartRow, bIsHoldingNote, Result, bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, - m_fYReverseOffsetPixels, (float) iDrawDistanceAfterTargetsPixels, (float) iDrawDistanceBeforeTargetsPixels, iDrawDistanceBeforeTargetsPixels, FADE_BEFORE_TARGETS_PERCENT ); - - bool bNoteIsUpcoming = NoteRowToBeat(iStartRow) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat; - bAnyUpcomingInThisCol |= bNoteIsUpcoming; - } - } - - // Draw all TapNotes in this column - - // draw notes from furthest to closest - NoteData::TrackMap::const_iterator begin, end; - m_pNoteData->GetTapNoteRange( c, iFirstRowToDraw, iLastRowToDraw+1, begin, end ); - for( ; begin != end; ++begin ) - { - int q = begin->first; - const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, q); - - // Switch modified by Wolfman2000, tested by Saturn2888 - // Fixes hold head overlapping issue, but not the rolls. - switch( tn.type ) - { - case TapNote::empty: // no note here - { - continue; - } - case TapNote::hold_head: - { - //if (tn.subType == TapNote::hold_head_roll) - continue; // skip - } - default: break; - } - - // Don't draw hidden (fully judged) steps. - if( tn.result.bHidden ) - continue; - - // TRICKY: If boomerang is on, then all notes in the range - // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible. - // Test every note to make sure it's on screen before drawing. - if( !IsOnScreen( NoteRowToBeat(q), c, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) ) - continue; // skip - - ASSERT_M( NoteRowToBeat(q) > -2000, ssprintf("%i %i %i, %f %f", q, iLastRowToDraw, - iFirstRowToDraw, m_pPlayerState->GetDisplayedPosition().m_fSongBeat, m_pPlayerState->GetDisplayedPosition().m_fMusicSeconds) ); - - // See if there is a hold step that begins on this index. - // Only do this if the noteskin cares. - bool bHoldNoteBeginsOnThisBeat = false; - if( m_pCurDisplay->display[c].DrawHoldHeadForTapsOnSameRow() ) - { - for( int c2=0; c2GetNumTracks(); c2++ ) - { - const TapNote &tmp = m_pNoteData->GetTapNote(c2, q); - if(tmp.type == TapNote::hold_head && - tmp.subType == TapNote::hold_head_hold) - { - bHoldNoteBeginsOnThisBeat = true; - break; - } - } - } - - // do the same for a roll. - bool bRollNoteBeginsOnThisBeat = false; - if (m_pCurDisplay->display[c].DrawRollHeadForTapsOnSameRow() ) - { - for( int c2=0; c2GetNumTracks(); c2++ ) - { - const TapNote &tmp = m_pNoteData->GetTapNote(c2, q); - if(tmp.type == TapNote::hold_head && - tmp.subType == TapNote::hold_head_roll) - { - bRollNoteBeginsOnThisBeat = true; - break; - } - } - } - - bool bIsInSelectionRange = false; - if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 ) - bIsInSelectionRange = m_iBeginMarker<=q && qdisplay[c].DrawTap(tn, c, NoteRowToVisibleBeat(m_pPlayerState, q), - bHoldNoteBeginsOnThisBeat, bRollNoteBeginsOnThisBeat, - bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, - m_fYReverseOffsetPixels, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels, - FADE_BEFORE_TARGETS_PERCENT ); - - bool bNoteIsUpcoming = NoteRowToBeat(q) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat; - bAnyUpcomingInThisCol |= bNoteIsUpcoming; - - DISPLAY->ClearZBuffer(); - } - - cur->m_ReceptorArrowRow.SetNoteUpcoming( c, bAnyUpcomingInThisCol ); - } - - cur->m_GhostArrowRow.Draw(); -} - -void NoteField::FadeToFail() -{ - m_fPercentFadeToFail = max( 0.0f, m_fPercentFadeToFail ); // this will slowly increase every Update() - // don't fade all over again if this is called twice -} -void NoteField::Step( int iCol, TapNoteScore score ) { m_pCurDisplay->m_ReceptorArrowRow.Step( iCol, score ); } -void NoteField::SetPressed( int iCol ) { m_pCurDisplay->m_ReceptorArrowRow.SetPressed( iCol ); } -void NoteField::DidTapNote( int iCol, TapNoteScore score, bool bBright ) { m_pCurDisplay->m_GhostArrowRow.DidTapNote( iCol, score, bBright ); } -void NoteField::DidHoldNote( int iCol, HoldNoteScore score, bool bBright ) { m_pCurDisplay->m_GhostArrowRow.DidHoldNote( iCol, score, bBright ); } - -void NoteField::HandleMessage( const Message &msg ) -{ - if( msg == Message_CurrentSongChanged ) - { - m_fCurrentBeatLastUpdate = -1; - m_fYPosCurrentBeatLastUpdate = -1; - } - - ActorFrame::HandleMessage( msg ); -} - -/* - * (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 "NoteField.h" +#include "RageUtil.h" +#include "GameConstantsAndTypes.h" +#include "ArrowEffects.h" +#include "GameManager.h" +#include "GameState.h" +#include "RageTimer.h" +#include "RageLog.h" +#include "RageMath.h" +#include "ThemeManager.h" +#include "NoteSkinManager.h" +#include "Song.h" +#include "ScreenDimensions.h" +#include "PlayerState.h" +#include "Style.h" +#include "CommonMetrics.h" +#include +#include "BackgroundUtil.h" +#include "Course.h" +#include "NoteData.h" +#include "RageDisplay.h" + +float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceAfterTargetsPixels ); +float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceBeforeTargetsPixels ); + +static ThemeMetric SHOW_BOARD( "NoteField", "ShowBoard" ); +static ThemeMetric SHOW_BEAT_BARS( "NoteField", "ShowBeatBars" ); +static ThemeMetric FADE_BEFORE_TARGETS_PERCENT( "NoteField", "FadeBeforeTargetsPercent" ); +static ThemeMetric BAR_MEASURE_ALPHA( "NoteField", "BarMeasureAlpha" ); +static ThemeMetric BAR_4TH_ALPHA( "NoteField", "Bar4thAlpha" ); +static ThemeMetric BAR_8TH_ALPHA( "NoteField", "Bar8thAlpha" ); +static ThemeMetric BAR_16TH_ALPHA( "NoteField", "Bar16thAlpha" ); +static ThemeMetric FADE_FAIL_TIME( "NoteField", "FadeFailTime" ); + +static RString RoutineNoteSkinName( size_t i ) { return ssprintf("RoutineNoteSkinP%i",int(i+1)); } +static ThemeMetric1D ROUTINE_NOTESKIN( "NoteField", RoutineNoteSkinName, NUM_PLAYERS ); + +NoteField::NoteField() +{ + m_pNoteData = NULL; + m_pCurDisplay = NULL; + + m_textMeasureNumber.LoadFromFont( THEME->GetPathF("NoteField","MeasureNumber") ); + m_textMeasureNumber.SetZoom( 1.0f ); + m_textMeasureNumber.SetShadowLength( 2 ); + m_textMeasureNumber.SetWrapWidthPixels( 300 ); + + m_rectMarkerBar.SetEffectDiffuseShift( 2, RageColor(1,1,1,0.5f), RageColor(0.5f,0.5f,0.5f,0.5f) ); + + m_sprBoard.Load( THEME->GetPathG("NoteField","board") ); + m_sprBoard->SetName("Board"); + m_sprBoard->PlayCommand( "On" ); + this->AddChild( m_sprBoard ); + + m_fBoardOffsetPixels = 0; + m_fCurrentBeatLastUpdate = -1; + m_fYPosCurrentBeatLastUpdate = -1; + this->SubscribeToMessage( Message_CurrentSongChanged ); + + m_sprBeatBars.Load( THEME->GetPathG("NoteField","bars") ); + m_sprBeatBars.StopAnimating(); + + m_iBeginMarker = m_iEndMarker = -1; + + m_fPercentFadeToFail = -1; +} + +NoteField::~NoteField() +{ + Unload(); +} + +void NoteField::Unload() +{ + for( map::iterator it = m_NoteDisplays.begin(); + it != m_NoteDisplays.end(); ++it ) + delete it->second; + m_NoteDisplays.clear(); + m_pCurDisplay = NULL; + memset( m_pDisplays, 0, sizeof(m_pDisplays) ); +} + +void NoteField::CacheNoteSkin( const RString &sNoteSkin_ ) +{ + RString sNoteSkinLower = sNoteSkin_; + sNoteSkinLower.MakeLower(); + + if( m_NoteDisplays.find(sNoteSkinLower) != m_NoteDisplays.end() ) + return; + + LockNoteSkin l( sNoteSkinLower ); + + LOG->Trace("NoteField::CacheNoteSkin: cache %s", sNoteSkinLower.c_str() ); + NoteDisplayCols *nd = new NoteDisplayCols( GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer ); + + for( int c=0; cGetCurrentStyle()->m_iColsPerPlayer; c++ ) + nd->display[c].Load( c, m_pPlayerState, m_fYReverseOffsetPixels ); + nd->m_ReceptorArrowRow.Load( m_pPlayerState, m_fYReverseOffsetPixels ); + nd->m_GhostArrowRow.Load( m_pPlayerState, m_fYReverseOffsetPixels ); + + m_NoteDisplays[ sNoteSkinLower ] = nd; +} + +void NoteField::UncacheNoteSkin( const RString &sNoteSkin_ ) +{ + RString sNoteSkinLower = sNoteSkin_; + sNoteSkinLower.MakeLower(); + + LOG->Trace("NoteField::CacheNoteSkin: release %s", sNoteSkinLower.c_str() ); + ASSERT_M( m_NoteDisplays.find(sNoteSkinLower) != m_NoteDisplays.end(), sNoteSkinLower ); + delete m_NoteDisplays[sNoteSkinLower]; + m_NoteDisplays.erase( sNoteSkinLower ); +} + +void NoteField::CacheAllUsedNoteSkins() +{ + // If we're in Routine mode, apply our per-player noteskins. + if( GAMESTATE->GetCurrentStyle()->m_StyleType == StyleType_TwoPlayersSharedSides ) + { + FOREACH_EnabledPlayer( pn ) + GAMESTATE->ApplyStageModifiers( pn, ROUTINE_NOTESKIN.GetValue(pn) ); + } + + /* Cache all note skins that we might need for the whole song, course or battle + * play, so we don't have to load them later (such as between course songs). */ + vector asSkinsLower; + GAMESTATE->GetAllUsedNoteSkins( asSkinsLower ); + asSkinsLower.push_back( m_pPlayerState->m_PlayerOptions.GetStage().m_sNoteSkin ); + FOREACH( RString, asSkinsLower, s ) + s->MakeLower(); + + for( unsigned i=0; i < asSkinsLower.size(); ++i ) + CacheNoteSkin( asSkinsLower[i] ); + + /* If we're changing note skins in the editor, we can have old note skins lying + * around. Remove them so they don't accumulate. */ + set setNoteSkinsToUnload; + FOREACHM( RString, NoteDisplayCols *, m_NoteDisplays, d ) + { + bool unused = find(asSkinsLower.begin(), asSkinsLower.end(), d->first) == asSkinsLower.end(); + if( unused ) + setNoteSkinsToUnload.insert( d->first ); + } + for (RString const & skin : setNoteSkinsToUnload) + UncacheNoteSkin( skin ); + + RString sCurrentNoteSkinLower = m_pPlayerState->m_PlayerOptions.GetCurrent().m_sNoteSkin; + sCurrentNoteSkinLower.MakeLower(); + + map::iterator it = m_NoteDisplays.find( sCurrentNoteSkinLower ); + ASSERT_M( it != m_NoteDisplays.end(), sCurrentNoteSkinLower ); + m_pCurDisplay = it->second; + memset( m_pDisplays, 0, sizeof(m_pDisplays) ); + + FOREACH_EnabledPlayer( pn ) + { + RString sNoteSkinLower = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetCurrent().m_sNoteSkin; + sNoteSkinLower.MakeLower(); + it = m_NoteDisplays.find( sNoteSkinLower ); + ASSERT_M( it != m_NoteDisplays.end(), sNoteSkinLower ); + m_pDisplays[pn] = it->second; + } +} + +void NoteField::Init( const PlayerState* pPlayerState, float fYReverseOffsetPixels ) +{ + m_pPlayerState = pPlayerState; + m_fYReverseOffsetPixels = fYReverseOffsetPixels; + CacheAllUsedNoteSkins(); +} + +void NoteField::Load( + const NoteData *pNoteData, + int iDrawDistanceAfterTargetsPixels, + int iDrawDistanceBeforeTargetsPixels ) +{ + ASSERT( pNoteData != NULL ); + m_pNoteData = pNoteData; + m_iDrawDistanceAfterTargetsPixels = iDrawDistanceAfterTargetsPixels; + m_iDrawDistanceBeforeTargetsPixels = iDrawDistanceBeforeTargetsPixels; + ASSERT( m_iDrawDistanceBeforeTargetsPixels >= m_iDrawDistanceAfterTargetsPixels ); + + m_fPercentFadeToFail = -1; + + //int i1 = m_pNoteData->GetNumTracks(); + //int i2 = GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; + ASSERT_M(m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer, + ssprintf("NumTracks %d = ColsPerPlayer %d",m_pNoteData->GetNumTracks(), + GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer)); + + // The NoteSkin may have changed at the beginning of a new course song. + RString sNoteSkinLower = m_pPlayerState->m_PlayerOptions.GetCurrent().m_sNoteSkin; + + /* XXX: Combination of good idea and bad idea to ensure courses load + * regardless of noteskin content. This may take a while to fix. */ + NoteDisplayCols *badIdea = m_pCurDisplay; + + if(sNoteSkinLower.empty()) + { + sNoteSkinLower = m_pPlayerState->m_PlayerOptions.GetPreferred().m_sNoteSkin; + + if(sNoteSkinLower.empty()) + { + sNoteSkinLower = "default"; + } + m_NoteDisplays.insert(pair (sNoteSkinLower, badIdea)); + } + + sNoteSkinLower.MakeLower(); + map::iterator it = m_NoteDisplays.find( sNoteSkinLower ); + ASSERT_M( it != m_NoteDisplays.end(), ssprintf("iterator != m_NoteDisplays.end() [sNoteSkinLower = %s]",sNoteSkinLower.c_str()) ); + memset( m_pDisplays, 0, sizeof(m_pDisplays) ); + FOREACH_EnabledPlayer( pn ) + { + sNoteSkinLower = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetCurrent().m_sNoteSkin; + + // XXX: Re-setup sNoteSkinLower. Unsure if inserting the skin again is needed. + if(sNoteSkinLower.empty()) + { + sNoteSkinLower = GAMESTATE->m_pPlayerState[pn]->m_PlayerOptions.GetPreferred().m_sNoteSkin; + + if(sNoteSkinLower.empty()) + { + sNoteSkinLower = "default"; + } + m_NoteDisplays.insert(pair (sNoteSkinLower, badIdea)); + } + + sNoteSkinLower.MakeLower(); + it = m_NoteDisplays.find( sNoteSkinLower ); + ASSERT_M( it != m_NoteDisplays.end(), sNoteSkinLower ); + m_pDisplays[pn] = it->second; + } +} + +void NoteField::Update( float fDeltaTime ) +{ + if( m_bFirstUpdate ) + { + m_pCurDisplay->m_ReceptorArrowRow.PlayCommand( "On" ); + } + + ActorFrame::Update( fDeltaTime ); + + // update m_fBoardOffsetPixels, m_fCurrentBeatLastUpdate, m_fYPosCurrentBeatLastUpdate + const float fCurrentBeat = m_pPlayerState->GetDisplayedPosition().m_fSongBeat; + bool bTweeningOn = m_sprBoard->GetCurrentDiffuseAlpha() >= 0.98 && m_sprBoard->GetCurrentDiffuseAlpha() < 1.00; // HACK + if( !bTweeningOn && m_fCurrentBeatLastUpdate != -1 ) + { + const float fYOffsetLast = ArrowEffects::GetYOffset( m_pPlayerState, 0, m_fCurrentBeatLastUpdate ); + const float fYPosLast = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffsetLast, m_fYReverseOffsetPixels ); + const float fPixelDifference = fYPosLast - m_fYPosCurrentBeatLastUpdate; + + //LOG->Trace( "speed = %f, %f, %f, %f, %f, %f", fSpeed, fYOffsetAtCurrent, fYOffsetAtNext, fSecondsAtCurrent, fSecondsAtNext, fPixelDifference, fSecondsDifference ); + + m_fBoardOffsetPixels += fPixelDifference; + wrap( m_fBoardOffsetPixels, m_sprBoard->GetUnzoomedHeight() ); + } + m_fCurrentBeatLastUpdate = fCurrentBeat; + const float fYOffsetCurrent = ArrowEffects::GetYOffset( m_pPlayerState, 0, m_fCurrentBeatLastUpdate ); + m_fYPosCurrentBeatLastUpdate = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffsetCurrent, m_fYReverseOffsetPixels ); + + m_rectMarkerBar.Update( fDeltaTime ); + + NoteDisplayCols *cur = m_pCurDisplay; + + cur->m_ReceptorArrowRow.Update( fDeltaTime ); + cur->m_GhostArrowRow.Update( fDeltaTime ); + + if( m_fPercentFadeToFail >= 0 ) + m_fPercentFadeToFail = min( m_fPercentFadeToFail + fDeltaTime/FADE_FAIL_TIME, 1 ); + + // Update fade to failed + m_pCurDisplay->m_ReceptorArrowRow.SetFadeToFailPercent( m_fPercentFadeToFail ); + + NoteDisplay::Update( fDeltaTime ); + /* Update all NoteDisplays. Hack: We need to call this once per frame, not + * once per player. */ + // TODO: Remove use of PlayerNumber. + + PlayerNumber pn = m_pPlayerState->m_PlayerNumber; + if( pn == GAMESTATE->GetMasterPlayerNumber() ) + NoteDisplay::Update( fDeltaTime ); +} + +float NoteField::GetWidth() const +{ + const Style* pStyle = GAMESTATE->GetCurrentStyle(); + float fMinX, fMaxX; + // TODO: Remove use of PlayerNumber. + pStyle->GetMinAndMaxColX( m_pPlayerState->m_PlayerNumber, fMinX, fMaxX ); + + const float fYZoom = ArrowEffects::GetZoom( m_pPlayerState ); + return (fMaxX - fMinX + ARROW_SIZE) * fYZoom; +} + +void NoteField::DrawBeatBar( const float fBeat, BeatBarType type, int iMeasureIndex ) +{ + bool bIsMeasure = type == measure; + + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + + float fAlpha; + int iState; + + if( bIsMeasure ) + { + fAlpha = BAR_MEASURE_ALPHA; + iState = 0; + } + else + { + float fScrollSpeed = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollSpeed; + if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fTimeSpacing > 0 ) + fScrollSpeed = 4; + switch( type ) + { + DEFAULT_FAIL( type ); + case measure: // handled above + case beat: // fall through + fAlpha = BAR_4TH_ALPHA; + iState = 1; + break; + case half_beat: + fAlpha = SCALE(fScrollSpeed,1.0f,2.0f,0.0f,BAR_8TH_ALPHA); + iState = 2; + break; + case quarter_beat: + fAlpha = SCALE(fScrollSpeed,2.0f,4.0f,0.0f,BAR_16TH_ALPHA); + iState = 3; + break; + } + CLAMP( fAlpha, 0, 1 ); + } + + float fWidth = GetWidth(); + float fFrameWidth = m_sprBeatBars.GetUnzoomedWidth(); + + m_sprBeatBars.SetX( 0 ); + m_sprBeatBars.SetY( fYPos ); + m_sprBeatBars.SetDiffuse( RageColor(1,1,1,fAlpha) ); + m_sprBeatBars.SetState( iState ); + m_sprBeatBars.SetCustomTextureRect( RectF(0,SCALE(iState,0.f,4.f,0.f,1.f), fWidth/fFrameWidth, SCALE(iState+1,0.f,4.f,0.f,1.f)) ); + m_sprBeatBars.SetZoomX( fWidth/m_sprBeatBars.GetUnzoomedWidth() ); + m_sprBeatBars.Draw(); + + + if( GAMESTATE->IsEditing() && bIsMeasure ) + { + int iMeasureNoDisplay = iMeasureIndex; + + m_textMeasureNumber.SetDiffuse( RageColor(1,1,1,1) ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,0) ); + m_textMeasureNumber.SetHorizAlign( align_right ); + m_textMeasureNumber.SetText( ssprintf("%d", iMeasureNoDisplay) ); + m_textMeasureNumber.SetXY( -fWidth/2, fYPos ); + m_textMeasureNumber.Draw(); + } +} + +void NoteField::DrawBoard( int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) +{ + // Draw the board centered on fYPosAt0 so that the board doesn't slide as + // the draw distance changes with modifiers. + const float fYPosAt0 = ArrowEffects::GetYPos( m_pPlayerState, 0, 0, m_fYReverseOffsetPixels ); + + // todo: make this an AutoActor instead? -aj + Sprite *pSprite = dynamic_cast( (Actor*)m_sprBoard ); + if( pSprite == NULL ) + RageException::Throw( "Board must be a Sprite" ); + + RectF rect = *pSprite->GetCurrentTextureCoordRect(); + const float fBoardGraphicHeightPixels = pSprite->GetUnzoomedHeight(); + float fTexCoordOffset = m_fBoardOffsetPixels / fBoardGraphicHeightPixels; + { + // top half + const float fHeight = iDrawDistanceBeforeTargetsPixels - iDrawDistanceAfterTargetsPixels; + const float fY = fYPosAt0 - ((iDrawDistanceBeforeTargetsPixels + iDrawDistanceAfterTargetsPixels) / 2.0f); + + pSprite->ZoomToHeight( fHeight ); + pSprite->SetY( fY ); + + // handle tex coord offset and fade + rect.top = -fTexCoordOffset-(iDrawDistanceBeforeTargetsPixels/fBoardGraphicHeightPixels); + rect.bottom = -fTexCoordOffset+(-iDrawDistanceAfterTargetsPixels/fBoardGraphicHeightPixels); + pSprite->SetCustomTextureRect( rect ); + float fFadeTop = FADE_BEFORE_TARGETS_PERCENT * iDrawDistanceBeforeTargetsPixels / (iDrawDistanceBeforeTargetsPixels-iDrawDistanceAfterTargetsPixels); + pSprite->SetFadeTop( fFadeTop ); + pSprite->Draw(); + } +} + +void NoteField::DrawMarkerBar( int iBeat ) +{ + float fBeat = NoteRowToBeat( iBeat ); + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + + m_rectMarkerBar.StretchTo( RectF(-GetWidth()/2, fYPos-ARROW_SIZE/2, GetWidth()/2, fYPos+ARROW_SIZE/2) ); + m_rectMarkerBar.Draw(); +} + +static ThemeMetric AREA_HIGHLIGHT_COLOR("NoteField", "AreaHighlightColor"); +void NoteField::DrawAreaHighlight( int iStartBeat, int iEndBeat ) +{ + float fStartBeat = NoteRowToBeat( iStartBeat ); + float fEndBeat = NoteRowToBeat( iEndBeat ); + float fDrawDistanceAfterTargetsPixels = ArrowEffects::GetYOffset( m_pPlayerState, 0, fStartBeat ); + float fYStartPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fDrawDistanceAfterTargetsPixels, m_fYReverseOffsetPixels ); + float fDrawDistanceBeforeTargetsPixels = ArrowEffects::GetYOffset( m_pPlayerState, 0, fEndBeat ); + float fYEndPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fDrawDistanceBeforeTargetsPixels, m_fYReverseOffsetPixels ); + + // The caller should have clamped these to reasonable values + ASSERT( fYStartPos > -1000 ); + ASSERT( fYEndPos < +5000 ); + + m_rectAreaHighlight.StretchTo( RectF(-GetWidth()/2, fYStartPos, GetWidth()/2, fYEndPos) ); + m_rectAreaHighlight.SetDiffuse( AREA_HIGHLIGHT_COLOR ); + m_rectAreaHighlight.Draw(); +} + +// todo: add DrawWarpAreaBG? -aj + +static ThemeMetric BPM_COLOR ( "NoteField", "BPMColor" ); +static ThemeMetric STOP_COLOR ( "NoteField", "StopColor" ); +static ThemeMetric DELAY_COLOR ( "NoteField", "DelayColor" ); +static ThemeMetric WARP_COLOR ( "NoteField", "WarpColor" ); +static ThemeMetric TIME_SIGNATURE_COLOR ( "NoteField", "TimeSignatureColor" ); +static ThemeMetric TICKCOUNT_COLOR ( "NoteField", "TickcountColor" ); +static ThemeMetric COMBO_COLOR ( "NoteField", "ComboColor" ); +static ThemeMetric LABEL_COLOR ( "NoteField", "LabelColor" ); +static ThemeMetric SPEED_COLOR ( "NoteField", "SpeedColor" ); +static ThemeMetric SCROLL_COLOR ( "NoteField", "ScrollColor" ); +static ThemeMetric FAKE_COLOR ("NoteField", "FakeColor" ); +static ThemeMetric BPM_IS_LEFT_SIDE ( "NoteField", "BPMIsLeftSide" ); +static ThemeMetric STOP_IS_LEFT_SIDE ( "NoteField", "StopIsLeftSide" ); +static ThemeMetric DELAY_IS_LEFT_SIDE ( "NoteField", "DelayIsLeftSide" ); +static ThemeMetric WARP_IS_LEFT_SIDE ( "NoteField", "WarpIsLeftSide" ); +static ThemeMetric TIME_SIGNATURE_IS_LEFT_SIDE ( "NoteField", "TimeSignatureIsLeftSide" ); +static ThemeMetric TICKCOUNT_IS_LEFT_SIDE ( "NoteField", "TickcountIsLeftSide" ); +static ThemeMetric COMBO_IS_LEFT_SIDE ( "NoteField", "ComboIsLeftSide" ); +static ThemeMetric LABEL_IS_LEFT_SIDE ( "NoteField", "LabelIsLeftSide" ); +static ThemeMetric SPEED_IS_LEFT_SIDE ( "NoteField", "SpeedIsLeftSide" ); +static ThemeMetric SCROLL_IS_LEFT_SIDE ( "NoteField", "ScrollIsLeftSide" ); +static ThemeMetric FAKE_IS_LEFT_SIDE ( "NoteField", "FakeIsLeftSide" ); +static ThemeMetric BPM_OFFSETX ( "NoteField", "BPMOffsetX" ); +static ThemeMetric STOP_OFFSETX ( "NoteField", "StopOffsetX" ); +static ThemeMetric DELAY_OFFSETX ( "NoteField", "DelayOffsetX" ); +static ThemeMetric WARP_OFFSETX ( "NoteField", "WarpOffsetX" ); +static ThemeMetric TIME_SIGNATURE_OFFSETX ( "NoteField", "TimeSignatureOffsetX" ); +static ThemeMetric TICKCOUNT_OFFSETX ( "NoteField", "TickcountOffsetX" ); +static ThemeMetric COMBO_OFFSETX ( "NoteField", "ComboOffsetX" ); +static ThemeMetric LABEL_OFFSETX ( "NoteField", "LabelOffsetX" ); +static ThemeMetric SPEED_OFFSETX ( "NoteField", "SpeedOffsetX" ); +static ThemeMetric SCROLL_OFFSETX ( "NoteField", "ScrollOffsetX" ); +static ThemeMetric FAKE_OFFSETX ( "NoteField", "FakeOffsetX" ); + +void NoteField::DrawBPMText( const float fBeat, const float fBPM ) +{ + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); + const float xBase = GetWidth()/2.f; + const float xOffset = BPM_OFFSETX * fZoom; + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( BPM_IS_LEFT_SIDE ? align_right : align_left ); + m_textMeasureNumber.SetDiffuse( BPM_COLOR ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( FloatToString(fBPM) ); + m_textMeasureNumber.SetXY( (BPM_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); + m_textMeasureNumber.Draw(); +} + +void NoteField::DrawFreezeText( const float fBeat, const float fSecs ) +{ + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); + const float xBase = GetWidth()/2.f; + const float xOffset = STOP_OFFSETX * fZoom; + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( STOP_IS_LEFT_SIDE ? align_right : align_left ); + m_textMeasureNumber.SetDiffuse( STOP_COLOR ); + m_textMeasureNumber.SetXY( (STOP_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( FloatToString(fSecs) ); + m_textMeasureNumber.Draw(); +} + +void NoteField::DrawDelayText( const float fBeat, const float fSecs ) +{ + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); + const float xBase = GetWidth()/2.f; + const float xOffset = DELAY_OFFSETX * fZoom; + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( DELAY_IS_LEFT_SIDE ? align_right : align_left ); + m_textMeasureNumber.SetDiffuse( DELAY_COLOR ); + m_textMeasureNumber.SetXY( (DELAY_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( FloatToString(fSecs) ); + m_textMeasureNumber.Draw(); +} + +void NoteField::DrawWarpText( const float fBeat, const float fNewBeat ) +{ + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); + const float xBase = GetWidth()/2.f; + const float xOffset = WARP_OFFSETX * fZoom; + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( WARP_IS_LEFT_SIDE ? align_right : align_left ); + m_textMeasureNumber.SetDiffuse( WARP_COLOR ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( FloatToString(fNewBeat) ); + m_textMeasureNumber.SetXY( (WARP_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); + m_textMeasureNumber.Draw(); +} + +void NoteField::DrawTimeSignatureText( const float fBeat, int iNumerator, int iDenominator ) +{ + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); + const float xBase = GetWidth()/2.f; + const float xOffset = TIME_SIGNATURE_OFFSETX * fZoom; + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( TIME_SIGNATURE_IS_LEFT_SIDE ? align_right : align_left ); + m_textMeasureNumber.SetDiffuse( TIME_SIGNATURE_COLOR ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( ssprintf("%d\n--\n%d", iNumerator, iDenominator) ); + m_textMeasureNumber.SetXY( (TIME_SIGNATURE_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); + m_textMeasureNumber.Draw(); +} + +void NoteField::DrawTickcountText( const float fBeat, int iTicks ) +{ + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); + const float xBase = GetWidth()/2.f; + const float xOffset = TICKCOUNT_OFFSETX * fZoom; + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( TICKCOUNT_IS_LEFT_SIDE ? align_right : align_left ); + m_textMeasureNumber.SetDiffuse( TICKCOUNT_COLOR ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( ssprintf("%d", iTicks) ); + m_textMeasureNumber.SetXY( (TICKCOUNT_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); + m_textMeasureNumber.Draw(); +} + +void NoteField::DrawComboText( const float fBeat, int iCombo, int iMiss ) +{ + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); + const float xBase = GetWidth()/2.f; + const float xOffset = COMBO_OFFSETX * fZoom; + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( COMBO_IS_LEFT_SIDE ? align_right : align_left ); + m_textMeasureNumber.SetDiffuse( COMBO_COLOR ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( ssprintf("%d/%d", iCombo, iMiss) ); + m_textMeasureNumber.SetXY( (COMBO_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); + m_textMeasureNumber.Draw(); +} + +void NoteField::DrawLabelText( const float fBeat, RString sLabel ) +{ + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); + const float xBase = GetWidth()/2.f; + const float xOffset = LABEL_OFFSETX * fZoom; + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( LABEL_IS_LEFT_SIDE ? align_right : align_left ); + m_textMeasureNumber.SetDiffuse( LABEL_COLOR ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( sLabel.c_str() ); + m_textMeasureNumber.SetXY( (LABEL_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); + m_textMeasureNumber.Draw(); +} + +void NoteField::DrawSpeedText( const float fBeat, float fPercent, float fWait, int iMode ) +{ + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); + const float xBase = GetWidth()/2.f; + const float xOffset = SPEED_OFFSETX * fZoom; + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( SPEED_IS_LEFT_SIDE ? align_right : align_left ); + m_textMeasureNumber.SetDiffuse( SPEED_COLOR ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( ssprintf("%s\n%s\n%s", FloatToString(fPercent).c_str(), (iMode == 1 ? "S" : "B"), FloatToString(fWait).c_str()) ); + m_textMeasureNumber.SetXY( (SPEED_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); + m_textMeasureNumber.Draw(); +} + +void NoteField::DrawScrollText( const float fBeat, float fPercent ) +{ + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); + const float xBase = GetWidth()/2.f; + const float xOffset = SCROLL_OFFSETX * fZoom; + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( SCROLL_IS_LEFT_SIDE ? align_right : align_left ); + m_textMeasureNumber.SetDiffuse( SCROLL_COLOR ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( FloatToString(fPercent) ); + m_textMeasureNumber.SetXY( (SCROLL_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); + m_textMeasureNumber.Draw(); +} + +void NoteField::DrawFakeText( const float fBeat, const float fNewBeat ) +{ + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); + const float xBase = GetWidth()/2.f; + const float xOffset = FAKE_OFFSETX * fZoom; + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( FAKE_IS_LEFT_SIDE ? align_right : align_left ); + m_textMeasureNumber.SetDiffuse( FAKE_COLOR ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( FloatToString(fNewBeat) ); + m_textMeasureNumber.SetXY( (FAKE_IS_LEFT_SIDE ? -xBase - xOffset : xBase + xOffset), fYPos ); + m_textMeasureNumber.Draw(); +} + +void NoteField::DrawAttackText( const float fBeat, const Attack &attack ) +{ + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( align_left ); + m_textMeasureNumber.SetDiffuse( RageColor(0,0.8f,0.8f,1) ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( attack.GetTextDescription() ); + m_textMeasureNumber.SetXY( +GetWidth()/2.f + 10*fZoom, fYPos ); + m_textMeasureNumber.Draw(); +} + +void NoteField::DrawBGChangeText( const float fBeat, const RString sNewBGName ) +{ + const float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, 0, fBeat ); + const float fYPos = ArrowEffects::GetYPos( m_pPlayerState, 0, fYOffset, m_fYReverseOffsetPixels ); + const float fZoom = ArrowEffects::GetZoom( m_pPlayerState ); + + m_textMeasureNumber.SetZoom( fZoom ); + m_textMeasureNumber.SetHorizAlign( align_left ); + m_textMeasureNumber.SetDiffuse( RageColor(0,1,0,1) ); + m_textMeasureNumber.SetGlow( RageColor(1,1,1,RageFastCos(RageTimer::GetTimeSinceStartFast()*2)/2+0.5f) ); + m_textMeasureNumber.SetText( sNewBGName ); + m_textMeasureNumber.SetXY( +GetWidth()/2.f, fYPos ); + m_textMeasureNumber.Draw(); +} + +static CacheNoteStat GetNumNotesFromBeginning( const PlayerState *pPlayerState, float beat ) +{ + // XXX: I realized that I have copied and pasted my binary search code 3 times already. + // how can we abstract this? + const vector &data = pPlayerState->m_CacheNoteStat; + int max = data.size() - 1; + int l = 0, r = max; + while( l <= r ) + { + int m = ( l + r ) / 2; + if( ( m == 0 || data[m].beat <= beat ) && ( m == max || beat < data[m + 1].beat ) ) + { + return data[m]; + } + else if( data[m].beat <= beat ) + { + l = m + 1; + } + else + { + r = m - 1; + } + } + CacheNoteStat dummy = { 0, 0, 0 }; + return dummy; +} + +static int GetNumNotesRange( const PlayerState* pPlayerState, float fLow, float fHigh ) +{ + CacheNoteStat low = GetNumNotesFromBeginning( pPlayerState, fLow ); + CacheNoteStat high = GetNumNotesFromBeginning( pPlayerState, fHigh ); + return high.notesUpper - low.notesLower; +} + +float FindFirstDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceAfterTargetsPixels ) +{ + + float fLow = 0, fHigh = pPlayerState->GetDisplayedPosition().m_fSongBeat; + + bool bHasCache = pPlayerState->m_CacheNoteStat.size() > 0; + + if( !bHasCache ) + { + fLow = fHigh - 4.0f; + } + + const int NUM_ITERATIONS = 24; + const int MAX_NOTES_AFTER = 64; + + float fFirstBeatToDraw = fLow; + + for( int i = 0; i < NUM_ITERATIONS; i ++ ) + { + + float fMid = (fLow + fHigh) / 2.0f; + + bool bIsPastPeakYOffset; + float fPeakYOffset; + float fYOffset = ArrowEffects::GetYOffset( pPlayerState, 0, fMid, fPeakYOffset, bIsPastPeakYOffset, true ); + + if( fYOffset < iDrawDistanceAfterTargetsPixels || ( bHasCache && GetNumNotesRange( pPlayerState, fMid, pPlayerState->GetDisplayedPosition().m_fSongBeat ) > MAX_NOTES_AFTER ) ) // off screen / too many notes + { + fFirstBeatToDraw = fMid; // move towards fSongBeat + fLow = fMid; + } + else // on screen, move away!! + { + fHigh = fMid; + } + + } + + return fFirstBeatToDraw; + +} + +float FindLastDisplayedBeat( const PlayerState* pPlayerState, int iDrawDistanceBeforeTargetsPixels ) +{ + // Probe for last note to draw. Worst case is 0.25x + boost. + // Adjust search distance so that notes don't pop onto the screen. + float fSearchDistance = 10; + float fLastBeatToDraw = pPlayerState->GetDisplayedPosition().m_fSongBeat+fSearchDistance; + float fSpeedMultiplier = pPlayerState->GetDisplayedTiming().GetDisplayedSpeedPercent(pPlayerState->GetDisplayedPosition().m_fSongBeatVisible, pPlayerState->GetDisplayedPosition().m_fMusicSecondsVisible); + + const int NUM_ITERATIONS = 20; + + bool bBoomerang; + { + const float* fAccels = pPlayerState->m_PlayerOptions.GetCurrent().m_fAccels; + bBoomerang = (fAccels[PlayerOptions::ACCEL_BOOMERANG] != 0); + } + + for( int i=0; i iDrawDistanceBeforeTargetsPixels ) // off screen + fLastBeatToDraw -= fSearchDistance; + else // on screen + fLastBeatToDraw += fSearchDistance; + + fSearchDistance /= 2; + } + + if( fSpeedMultiplier < 0.75 ) + { + fLastBeatToDraw = min(fLastBeatToDraw, pPlayerState->GetDisplayedPosition().m_fSongBeat + 16); + } + + return fLastBeatToDraw; +} + +inline float NoteRowToVisibleBeat( const PlayerState *pPlayerState, int iRow ) +{ + return NoteRowToBeat(iRow); +} + +bool NoteField::IsOnScreen( float fBeat, int iCol, int iDrawDistanceAfterTargetsPixels, int iDrawDistanceBeforeTargetsPixels ) const +{ + // TRICKY: If boomerang is on, then ones in the range + // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible. + // Test to see if this beat is visible before drawing. + float fYOffset = ArrowEffects::GetYOffset( m_pPlayerState, iCol, fBeat ); + if( fYOffset > iDrawDistanceBeforeTargetsPixels ) // off screen + return false; + if( fYOffset < iDrawDistanceAfterTargetsPixels ) // off screen + return false; + + return true; +} + +void NoteField::DrawPrimitives() +{ + //LOG->Trace( "NoteField::DrawPrimitives()" ); + + // This should be filled in on the first update. + ASSERT( m_pCurDisplay != NULL ); + + ArrowEffects::Update(); + + NoteDisplayCols *cur = m_pCurDisplay; + + const PlayerOptions ¤t_po = m_pPlayerState->m_PlayerOptions.GetCurrent(); + + // Adjust draw range depending on some effects + int iDrawDistanceAfterTargetsPixels = m_iDrawDistanceAfterTargetsPixels; + // HACK: If boomerang and centered are on, then we want to draw much + // earlier so that the notes don't pop on screen. + float fCenteredTimesBoomerang = + current_po.m_fScrolls[PlayerOptions::SCROLL_CENTERED] * + current_po.m_fAccels[PlayerOptions::ACCEL_BOOMERANG]; + iDrawDistanceAfterTargetsPixels += int(SCALE( fCenteredTimesBoomerang, 0.f, 1.f, 0.f, -SCREEN_HEIGHT/2 )); + int iDrawDistanceBeforeTargetsPixels = m_iDrawDistanceBeforeTargetsPixels; + + float fDrawScale = 1; + fDrawScale *= 1 + 0.5f * fabsf( current_po.m_fPerspectiveTilt ); + fDrawScale *= 1 + fabsf( current_po.m_fEffects[PlayerOptions::EFFECT_MINI] ); + + iDrawDistanceAfterTargetsPixels = (int)(iDrawDistanceAfterTargetsPixels * fDrawScale); + iDrawDistanceBeforeTargetsPixels = (int)(iDrawDistanceBeforeTargetsPixels * fDrawScale); + + + // Probe for first and last notes on the screen + float fFirstBeatToDraw = FindFirstDisplayedBeat( m_pPlayerState, iDrawDistanceAfterTargetsPixels ); + float fLastBeatToDraw = FindLastDisplayedBeat( m_pPlayerState, iDrawDistanceBeforeTargetsPixels ); + + m_pPlayerState->m_fLastDrawnBeat = fLastBeatToDraw; + + const int iFirstRowToDraw = BeatToNoteRow(fFirstBeatToDraw); + const int iLastRowToDraw = BeatToNoteRow(fLastBeatToDraw); + + //LOG->Trace( "start = %f.1, end = %f.1", fFirstBeatToDraw-fSongBeat, fLastBeatToDraw-fSongBeat ); + //LOG->Trace( "Drawing elements %d through %d", iFirstRowToDraw, iLastRowToDraw ); + +#define IS_ON_SCREEN( fBeat ) ( fFirstBeatToDraw <= (fBeat) && (fBeat) <= fLastBeatToDraw && IsOnScreen( fBeat, 0, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) ) + + // Draw board + if( SHOW_BOARD ) + { + DrawBoard( iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ); + } + + // Draw Receptors + { + cur->m_ReceptorArrowRow.Draw(); + } + + const TimingData *pTiming = &m_pPlayerState->GetDisplayedTiming(); + const vector* segs[NUM_TimingSegmentType]; + + FOREACH_TimingSegmentType( tst ) + segs[tst] = &(pTiming->GetTimingSegments(tst)); + + unsigned i = 0; + // Draw beat bars + if( ( GAMESTATE->IsEditing() || SHOW_BEAT_BARS ) && pTiming != NULL ) + { + const vector &tSigs = *segs[SEGMENT_TIME_SIG]; + int iMeasureIndex = 0; + for (i = 0; i < tSigs.size(); i++) + { + const TimeSignatureSegment *ts = ToTimeSignature(tSigs[i]); + int iSegmentEndRow = (i + 1 == tSigs.size()) ? iLastRowToDraw : tSigs[i+1]->GetRow(); + + // beat bars every 16th note + int iDrawBeatBarsEveryRows = BeatToNoteRow( ((float)ts->GetDen()) / 4 ) / 4; + + // In 4/4, every 16th beat bar is a measure + int iMeasureBarFrequency = ts->GetNum() * 4; + int iBeatBarsDrawn = 0; + + for( int j=ts->GetRow(); j < iSegmentEndRow; j += iDrawBeatBarsEveryRows ) + { + bool bMeasureBar = iBeatBarsDrawn % iMeasureBarFrequency == 0; + BeatBarType type = quarter_beat; + if( bMeasureBar ) + type = measure; + else if( iBeatBarsDrawn % 4 == 0 ) + type = beat; + else if( iBeatBarsDrawn % 2 == 0 ) + type = half_beat; + float fBeat = NoteRowToBeat(j); + + if( IS_ON_SCREEN(fBeat) ) + { + DrawBeatBar( fBeat, type, iMeasureIndex ); + } + + iBeatBarsDrawn++; + if( bMeasureBar ) + iMeasureIndex++; + } + } + } + + if( GAMESTATE->IsEditing() && pTiming != NULL ) + { + ASSERT(GAMESTATE->m_pCurSong != NULL); + + const TimingData &timing = *pTiming; + + // Scroll text + if( GAMESTATE->m_bIsUsingStepTiming ) + { + for (i = 0; i < segs[SEGMENT_SCROLL]->size(); i++) + { + ScrollSegment *seg = ToScroll( segs[SEGMENT_SCROLL]->at(i) ); + if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + { + float fBeat = seg->GetBeat(); + if( IS_ON_SCREEN(fBeat) ) + DrawScrollText( fBeat, seg->GetRatio() ); + } + } + } + + // BPM text + for (i = 0; i < segs[SEGMENT_BPM]->size(); i++) + { + const BPMSegment *seg = ToBPM( segs[SEGMENT_BPM]->at(i) ); + if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + { + float fBeat = seg->GetBeat(); + if( IS_ON_SCREEN(fBeat) ) + DrawBPMText( fBeat, seg->GetBPM() ); + } + } + + // Freeze text + for (i = 0; i < segs[SEGMENT_STOP]->size(); i++) + { + const StopSegment *seg = ToStop( segs[SEGMENT_STOP]->at(i) ); + if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + { + float fBeat = seg->GetBeat(); + if( IS_ON_SCREEN(fBeat) ) + DrawFreezeText( fBeat, seg->GetPause() ); + } + } + + // Delay text + for (i = 0; i < segs[SEGMENT_DELAY]->size(); i++) + { + const DelaySegment *seg = ToDelay( segs[SEGMENT_DELAY]->at(i) ); + if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + { + float fBeat = seg->GetBeat(); + if( IS_ON_SCREEN(fBeat) ) + DrawDelayText( fBeat, seg->GetPause() ); + } + } + + // Warp text + for (i = 0; i < segs[SEGMENT_WARP]->size(); i++) + { + const WarpSegment *seg = ToWarp( segs[SEGMENT_WARP]->at(i) ); + if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + { + float fBeat = seg->GetBeat(); + if( IS_ON_SCREEN(fBeat) ) + DrawWarpText( fBeat, seg->GetLength() ); + } + } + + // Time Signature text + for (i = 0; i < segs[SEGMENT_TIME_SIG]->size(); i++) + { + const TimeSignatureSegment *seg = ToTimeSignature( segs[SEGMENT_TIME_SIG]->at(i) ); + if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + { + float fBeat = seg->GetBeat(); + if( IS_ON_SCREEN(fBeat) ) + DrawTimeSignatureText( fBeat, seg->GetNum(), seg->GetDen() ); + } + } + + // Tickcount text + for (i = 0; i < segs[SEGMENT_TICKCOUNT]->size(); i++) + { + const TickcountSegment *seg = ToTickcount( segs[SEGMENT_TICKCOUNT]->at(i) ); + if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + { + float fBeat = seg->GetBeat(); + if( IS_ON_SCREEN(fBeat) ) + DrawTickcountText( fBeat, seg->GetTicks() ); + } + } + + // Combo text + for (i = 0; i < segs[SEGMENT_COMBO]->size(); i++) + { + const ComboSegment *seg = ToCombo( segs[SEGMENT_COMBO]->at(i) ); + if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + { + float fBeat = seg->GetBeat(); + if( IS_ON_SCREEN(fBeat) ) + DrawComboText( fBeat, seg->GetCombo(), seg->GetMissCombo() ); + } + } + + // Label text + for (i = 0; i < segs[SEGMENT_LABEL]->size(); i++) + { + const LabelSegment *seg = ToLabel( segs[SEGMENT_LABEL]->at(i) ); + if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + { + float fBeat = seg->GetBeat(); + if( IS_ON_SCREEN(fBeat) ) + DrawLabelText( fBeat, seg->GetLabel() ); + } + } + + // Speed text + for (i = 0; i < segs[SEGMENT_SPEED]->size(); i++) + { + const SpeedSegment *seg = ToSpeed( segs[SEGMENT_SPEED]->at(i) ); + if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + { + float fBeat = seg->GetBeat(); + if( IS_ON_SCREEN(fBeat) ) + DrawSpeedText(fBeat, seg->GetRatio(), + seg->GetDelay(), seg->GetUnit() ); + } + } + + // Fake text + for (i = 0; i < segs[SEGMENT_FAKE]->size(); i++) + { + const FakeSegment *seg = ToFake( segs[SEGMENT_FAKE]->at(i) ); + if( seg->GetRow() >= iFirstRowToDraw && seg->GetRow() <= iLastRowToDraw ) + { + float fBeat = seg->GetBeat(); + if( IS_ON_SCREEN(fBeat) ) + DrawFakeText( fBeat, seg->GetLength() ); + } + } + + // Course mods text + const Course *pCourse = GAMESTATE->m_pCurCourse; + if( pCourse ) + { + ASSERT_M( GAMESTATE->m_iEditCourseEntryIndex >= 0 && GAMESTATE->m_iEditCourseEntryIndex < (int)pCourse->m_vEntries.size(), + ssprintf("%i",GAMESTATE->m_iEditCourseEntryIndex.Get()) ); + const CourseEntry &ce = pCourse->m_vEntries[GAMESTATE->m_iEditCourseEntryIndex]; + FOREACH_CONST( Attack, ce.attacks, a ) + { + float fSecond = a->fStartSecond; + float fBeat = timing.GetBeatFromElapsedTime( fSecond ); + + if( BeatToNoteRow(fBeat) >= iFirstRowToDraw && + BeatToNoteRow(fBeat) <= iLastRowToDraw) + { + if( IS_ON_SCREEN(fBeat) ) + DrawAttackText( fBeat, *a ); + } + } + } + else + { + AttackArray &attacks = GAMESTATE->m_bIsUsingStepTiming ? + GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks : + GAMESTATE->m_pCurSong->m_Attacks; + // XXX: We're somehow getting here when attacks is null. Find the actual cause later. + if (&attacks) + { + FOREACH_CONST(Attack, attacks, a) + { + float fBeat = timing.GetBeatFromElapsedTime(a->fStartSecond); + if (BeatToNoteRow(fBeat) >= iFirstRowToDraw && + BeatToNoteRow(fBeat) <= iLastRowToDraw && + IS_ON_SCREEN(fBeat)) + { + this->DrawAttackText(fBeat, *a); + } + } + } + } + + if( !GAMESTATE->m_bIsUsingStepTiming ) + { + // BGChange text + EditMode mode = GAMESTATE->m_EditMode; + switch( mode ) + { + case EditMode_Home: + case EditMode_CourseMods: + case EditMode_Practice: + break; + case EditMode_Full: + { + vector::iterator iter[NUM_BackgroundLayer]; + FOREACH_BackgroundLayer( j ) + iter[j] = GAMESTATE->m_pCurSong->GetBackgroundChanges(j).begin(); + + while( 1 ) + { + float fLowestBeat = FLT_MAX; + vector viLowestIndex; + + FOREACH_BackgroundLayer( j ) + { + if( iter[j] == GAMESTATE->m_pCurSong->GetBackgroundChanges(j).end() ) + continue; + + float fBeat = iter[j]->m_fStartBeat; + if( fBeat < fLowestBeat ) + { + fLowestBeat = fBeat; + viLowestIndex.clear(); + viLowestIndex.push_back( j ); + } + else if( fBeat == fLowestBeat ) + { + viLowestIndex.push_back( j ); + } + } + + if( viLowestIndex.empty() ) + { + FOREACH_BackgroundLayer( j ) + ASSERT( iter[j] == GAMESTATE->m_pCurSong->GetBackgroundChanges(j).end() ); + break; + } + + if( IS_ON_SCREEN(fLowestBeat) ) + { + vector vsBGChanges; + FOREACH_CONST( BackgroundLayer, viLowestIndex, bl ) + { + ASSERT( iter[*bl] != GAMESTATE->m_pCurSong->GetBackgroundChanges(*bl).end() ); + const BackgroundChange& change = *iter[*bl]; + RString s = change.GetTextDescription(); + if( *bl!=0 ) + s = ssprintf("%d: ",*bl) + s; + vsBGChanges.push_back( s ); + } + DrawBGChangeText( fLowestBeat, join("\n",vsBGChanges) ); + } + FOREACH_CONST( BackgroundLayer, viLowestIndex, bl ) + iter[*bl]++; + } + } + break; + default: + FAIL_M(ssprintf("Invalid edit mode: %i", mode)); + } + } + + // Draw marker bars + if( m_iBeginMarker != -1 && m_iEndMarker != -1 ) + { + int iBegin = m_iBeginMarker; + int iEnd = m_iEndMarker; + CLAMP( iBegin, iFirstRowToDraw, iLastRowToDraw ); + CLAMP( iEnd, iFirstRowToDraw, iLastRowToDraw ); + DrawAreaHighlight( iBegin, iEnd ); + } + else if( m_iBeginMarker != -1 ) + { + if( m_iBeginMarker >= iFirstRowToDraw && + m_iBeginMarker <= iLastRowToDraw ) + DrawMarkerBar( m_iBeginMarker ); + } + else if( m_iEndMarker != -1 ) + { + if( m_iEndMarker >= iFirstRowToDraw && + m_iEndMarker <= iLastRowToDraw ) + DrawMarkerBar( m_iEndMarker ); + } + } + + // Optimization is very important here because there are so many arrows to draw. + // Draw the arrows in order of column. This minimizes texture switches and + // lets us draw in big batches. + + float fSelectedRangeGlow = SCALE( RageFastCos(RageTimer::GetTimeSinceStartFast()*2), -1, 1, 0.1f, 0.3f ); + + const Style* pStyle = GAMESTATE->GetCurrentStyle(); + ASSERT_M(m_pNoteData->GetNumTracks() == GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer, + ssprintf("NumTracks %d != ColsPerPlayer %d",m_pNoteData->GetNumTracks(), + GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer)); + + for( int j=0; jGetNumTracks(); j++ ) // for each arrow column + { + const int c = pStyle->m_iColumnDrawOrder[j]; + + bool bAnyUpcomingInThisCol = false; + + // Draw all HoldNotes in this column (so that they appear under the tap notes) + { + NoteData::TrackMap::const_iterator begin, end; + m_pNoteData->GetTapNoteRangeInclusive( c, iFirstRowToDraw, iLastRowToDraw+1, begin, end ); + + for( ; begin != end; ++begin ) + { + const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, j); + if( tn.type != TapNote::hold_head ) + continue; // skip + + const HoldNoteResult &Result = tn.HoldResult; + if( Result.hns == HNS_Held ) // if this HoldNote was completed + continue; // don't draw anything + + int iStartRow = begin->first; + int iEndRow = iStartRow + tn.iDuration; + + // TRICKY: If boomerang is on, then all notes in the range + // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible. + // Test every note to make sure it's on screen before drawing + float fThrowAway; + bool bStartIsPastPeak = false; + bool bEndIsPastPeak = false; + float fStartYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iStartRow), fThrowAway, bStartIsPastPeak ); + float fEndYOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToVisibleBeat(m_pPlayerState, iEndRow), fThrowAway, bEndIsPastPeak ); + + bool bTailIsOnVisible = iDrawDistanceAfterTargetsPixels <= fEndYOffset && fEndYOffset <= iDrawDistanceBeforeTargetsPixels; + bool bHeadIsVisible = iDrawDistanceAfterTargetsPixels <= fStartYOffset && fStartYOffset <= iDrawDistanceBeforeTargetsPixels; + bool bStraddlingVisible = fStartYOffset <= iDrawDistanceAfterTargetsPixels && iDrawDistanceBeforeTargetsPixels <= fEndYOffset; + bool bStaddlingPeak = bStartIsPastPeak && !bEndIsPastPeak; + if( !(bTailIsOnVisible || bHeadIsVisible || bStraddlingVisible || bStaddlingPeak) ) + { + //LOG->Trace( "skip drawing this hold." ); + continue; // skip + } + + bool bIsAddition = (tn.source == TapNote::addition); + bool bIsHopoPossible = (tn.bHopoPossible); + bool bUseAdditionColoring = bIsAddition || bIsHopoPossible; + const bool bHoldGhostShowing = tn.HoldResult.bActive && tn.HoldResult.fLife > 0; + const bool bIsHoldingNote = tn.HoldResult.bHeld; + if( bHoldGhostShowing ) + m_pCurDisplay->m_GhostArrowRow.SetHoldShowing( c, tn ); + + ASSERT_M( NoteRowToBeat(iStartRow) > -2000, ssprintf("%i %i %i", iStartRow, iEndRow, c) ); + + bool bIsInSelectionRange = false; + if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 ) + bIsInSelectionRange = (m_iBeginMarker <= iStartRow && iEndRow < m_iEndMarker); + + NoteDisplayCols *displayCols = tn.pn == PLAYER_INVALID ? m_pCurDisplay : m_pDisplays[tn.pn]; + displayCols->display[c].DrawHold( tn, c, iStartRow, bIsHoldingNote, Result, bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, + m_fYReverseOffsetPixels, (float) iDrawDistanceAfterTargetsPixels, (float) iDrawDistanceBeforeTargetsPixels, iDrawDistanceBeforeTargetsPixels, FADE_BEFORE_TARGETS_PERCENT ); + + bool bNoteIsUpcoming = NoteRowToBeat(iStartRow) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat; + bAnyUpcomingInThisCol |= bNoteIsUpcoming; + } + } + + // Draw all TapNotes in this column + + // draw notes from furthest to closest + NoteData::TrackMap::const_iterator begin, end; + m_pNoteData->GetTapNoteRange( c, iFirstRowToDraw, iLastRowToDraw+1, begin, end ); + for( ; begin != end; ++begin ) + { + int q = begin->first; + const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, q); + + // Switch modified by Wolfman2000, tested by Saturn2888 + // Fixes hold head overlapping issue, but not the rolls. + switch( tn.type ) + { + case TapNote::empty: // no note here + { + continue; + } + case TapNote::hold_head: + { + //if (tn.subType == TapNote::hold_head_roll) + continue; // skip + } + default: break; + } + + // Don't draw hidden (fully judged) steps. + if( tn.result.bHidden ) + continue; + + // TRICKY: If boomerang is on, then all notes in the range + // [iFirstRowToDraw,iLastRowToDraw] aren't necessarily visible. + // Test every note to make sure it's on screen before drawing. + if( !IsOnScreen( NoteRowToBeat(q), c, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ) ) + continue; // skip + + ASSERT_M( NoteRowToBeat(q) > -2000, ssprintf("%i %i %i, %f %f", q, iLastRowToDraw, + iFirstRowToDraw, m_pPlayerState->GetDisplayedPosition().m_fSongBeat, m_pPlayerState->GetDisplayedPosition().m_fMusicSeconds) ); + + // See if there is a hold step that begins on this index. + // Only do this if the noteskin cares. + bool bHoldNoteBeginsOnThisBeat = false; + if( m_pCurDisplay->display[c].DrawHoldHeadForTapsOnSameRow() ) + { + for( int c2=0; c2GetNumTracks(); c2++ ) + { + const TapNote &tmp = m_pNoteData->GetTapNote(c2, q); + if(tmp.type == TapNote::hold_head && + tmp.subType == TapNote::hold_head_hold) + { + bHoldNoteBeginsOnThisBeat = true; + break; + } + } + } + + // do the same for a roll. + bool bRollNoteBeginsOnThisBeat = false; + if (m_pCurDisplay->display[c].DrawRollHeadForTapsOnSameRow() ) + { + for( int c2=0; c2GetNumTracks(); c2++ ) + { + const TapNote &tmp = m_pNoteData->GetTapNote(c2, q); + if(tmp.type == TapNote::hold_head && + tmp.subType == TapNote::hold_head_roll) + { + bRollNoteBeginsOnThisBeat = true; + break; + } + } + } + + bool bIsInSelectionRange = false; + if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 ) + bIsInSelectionRange = m_iBeginMarker<=q && qdisplay[c].DrawTap(tn, c, NoteRowToVisibleBeat(m_pPlayerState, q), + bHoldNoteBeginsOnThisBeat, bRollNoteBeginsOnThisBeat, + bUseAdditionColoring, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, + m_fYReverseOffsetPixels, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels, + FADE_BEFORE_TARGETS_PERCENT ); + + bool bNoteIsUpcoming = NoteRowToBeat(q) > m_pPlayerState->GetDisplayedPosition().m_fSongBeat; + bAnyUpcomingInThisCol |= bNoteIsUpcoming; + + DISPLAY->ClearZBuffer(); + } + + cur->m_ReceptorArrowRow.SetNoteUpcoming( c, bAnyUpcomingInThisCol ); + } + + cur->m_GhostArrowRow.Draw(); +} + +void NoteField::FadeToFail() +{ + m_fPercentFadeToFail = max( 0.0f, m_fPercentFadeToFail ); // this will slowly increase every Update() + // don't fade all over again if this is called twice +} +void NoteField::Step( int iCol, TapNoteScore score ) { m_pCurDisplay->m_ReceptorArrowRow.Step( iCol, score ); } +void NoteField::SetPressed( int iCol ) { m_pCurDisplay->m_ReceptorArrowRow.SetPressed( iCol ); } +void NoteField::DidTapNote( int iCol, TapNoteScore score, bool bBright ) { m_pCurDisplay->m_GhostArrowRow.DidTapNote( iCol, score, bBright ); } +void NoteField::DidHoldNote( int iCol, HoldNoteScore score, bool bBright ) { m_pCurDisplay->m_GhostArrowRow.DidHoldNote( iCol, score, bBright ); } + +void NoteField::HandleMessage( const Message &msg ) +{ + if( msg == Message_CurrentSongChanged ) + { + m_fCurrentBeatLastUpdate = -1; + m_fYPosCurrentBeatLastUpdate = -1; + } + + ActorFrame::HandleMessage( msg ); +} + +/* + * (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. + */ From 9c7111918d4ea1d59860b79f96477231e0ca7c83 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:52:46 -0400 Subject: [PATCH 019/281] Loop and whitespace. --- src/Player.cpp | 7118 ++++++++++++++++++++++++------------------------ 1 file changed, 3559 insertions(+), 3559 deletions(-) diff --git a/src/Player.cpp b/src/Player.cpp index 547a62d3b9..b6adb7bda4 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -1,3559 +1,3559 @@ -#include "global.h" -#include "Player.h" -#include "GameConstantsAndTypes.h" -#include "RageUtil.h" -#include "RageTimer.h" -#include "PrefsManager.h" -#include "GameManager.h" -#include "InputMapper.h" -#include "SongManager.h" -#include "GameState.h" -#include "ScoreKeeperNormal.h" -#include "RageLog.h" -#include "RageDisplay.h" -#include "ThemeManager.h" -#include "ScoreDisplay.h" -#include "LifeMeter.h" -#include "CombinedLifeMeter.h" -#include "PlayerAI.h" -#include "NoteField.h" -#include "NoteDataUtil.h" -#include "ScreenMessage.h" -#include "ScreenManager.h" -#include "StageStats.h" -#include "ActorUtil.h" -#include "ArrowEffects.h" -#include "Game.h" -#include "NetworkSyncManager.h" //used for sending timing offset -#include "DancingCharacters.h" -#include "ScreenDimensions.h" -#include "RageSoundManager.h" -#include "ThemeMetric.h" -#include "PlayerState.h" -#include "GameSoundManager.h" -#include "Style.h" -#include "MessageManager.h" -#include "ProfileManager.h" -#include "Profile.h" -#include "StatsManager.h" -#include "Song.h" -#include "Steps.h" -#include "GameCommand.h" -#include "LocalizedString.h" -#include "AdjustSync.h" - -RString ATTACK_DISPLAY_X_NAME( size_t p, size_t both_sides ); -void TimingWindowSecondsInit( size_t /*TimingWindow*/ i, RString &sNameOut, float &defaultValueOut ); - -/** - * @brief Helper class to ensure that each row is only judged once without taking too much memory. - */ -class JudgedRows -{ - vector m_vRows; - int m_iStart; - int m_iOffset; - - void Resize( size_t iMin ) - { - size_t iNewSize = max( 2*m_vRows.size(), iMin ); - vector vNewRows( m_vRows.begin() + m_iOffset, m_vRows.end() ); - vNewRows.reserve( iNewSize ); - vNewRows.insert( vNewRows.end(), m_vRows.begin(), m_vRows.begin() + m_iOffset ); - vNewRows.resize( iNewSize, false ); - m_vRows.swap( vNewRows ); - m_iOffset = 0; - } -public: - JudgedRows() : m_iStart(0), m_iOffset(0) { Resize( 32 ); } - // Returns true if the row has already been judged. - bool JudgeRow( int iRow ) - { - if( iRow < m_iStart ) - return true; - if( iRow >= m_iStart+int(m_vRows.size()) ) - Resize( iRow+1-m_iStart ); - const int iIndex = (iRow - m_iStart + m_iOffset) % m_vRows.size(); - const bool ret = m_vRows[iIndex]; - m_vRows[iIndex] = true; - while( m_vRows[m_iOffset] ) - { - m_vRows[m_iOffset] = false; - ++m_iStart; - if( ++m_iOffset >= int(m_vRows.size()) ) - m_iOffset -= m_vRows.size(); - } - return ret; - } - void Reset( int iStart ) - { - m_iStart = iStart; - m_iOffset = 0; - m_vRows.assign( m_vRows.size(), false ); - } -}; - - -RString ATTACK_DISPLAY_X_NAME( size_t p, size_t both_sides ) { return "AttackDisplayXOffset" + (both_sides ? RString("BothSides") : ssprintf("OneSideP%d",int(p+1)) ); } - -/** - * @brief Distance to search for a note in Step(), in seconds. - * - * TODO: This should be calculated based on the max size of the current judgment windows. */ -static const float StepSearchDistance = 1.0f; - -void TimingWindowSecondsInit( size_t /*TimingWindow*/ i, RString &sNameOut, float &defaultValueOut ) -{ - sNameOut = "TimingWindowSeconds" + TimingWindowToString( (TimingWindow)i ); - switch( i ) - { - default: - FAIL_M(ssprintf("Invalid timing window: %i", i)); - case TW_W1: defaultValueOut = 0.0225f; break; - case TW_W2: defaultValueOut = 0.045f; break; - case TW_W3: defaultValueOut = 0.090f; break; - case TW_W4: defaultValueOut = 0.135f; break; - case TW_W5: defaultValueOut = 0.180f; break; - case TW_Mine: defaultValueOut = 0.090f; break; // same as great - case TW_Hold: defaultValueOut = 0.250f; break; // allow enough time to take foot off and put back on - case TW_Roll: defaultValueOut = 0.500f; break; - case TW_Attack: defaultValueOut = 0.135f; break; - } -} - -static Preference m_fTimingWindowScale ( "TimingWindowScale", 1.0f ); -static Preference m_fTimingWindowAdd ( "TimingWindowAdd", 0 ); -static Preference1D m_fTimingWindowSeconds( TimingWindowSecondsInit, NUM_TimingWindow ); -static Preference m_fTimingWindowJump ( "TimingWindowJump", 0.25 ); -static Preference m_fMaxInputLatencySeconds ( "MaxInputLatencySeconds", 0.0 ); -static Preference g_bEnableAttackSoundPlayback ( "EnableAttackSounds", true ); -static Preference g_bEnableMineSoundPlayback ( "EnableMineHitSound", true ); - -Preference g_fTimingWindowHopo ( "TimingWindowHopo", 0.25 ); // max time between notes in a hopo chain -Preference g_fTimingWindowStrum ( "TimingWindowStrum", 0.1f ); // max time between strum and when the frets must match -/** @brief How much life is in a hold note when you start on it? */ -ThemeMetric INITIAL_HOLD_LIFE ( "Player", "InitialHoldLife" ); -/** - * @brief How much hold life is possible to have when holding a hold note? - * - * This was an sm-ssc addition. */ -ThemeMetric MAX_HOLD_LIFE ( "Player", "MaxHoldLife" ); -ThemeMetric PENALIZE_TAP_SCORE_NONE ( "Player", "PenalizeTapScoreNone" ); -ThemeMetric JUDGE_HOLD_NOTES_ON_SAME_ROW_TOGETHER ( "Player", "JudgeHoldNotesOnSameRowTogether" ); -/** - * @brief Does StepMania use checkpoint judgments when interacting with holds? - * - * If set to true, checkpoint judgments are in use, similar to the Pump It Up - * series. If set to false, no such judgments are used. */ -ThemeMetric HOLD_CHECKPOINTS ( "Player", "HoldCheckpoints" ); -/** - * @brief If using checkpoints, are the song's TickcountSegments used? - * - * If set to true, the checkpoints rely on the data found in the TickcountSegments. - * This is used via the #%TICKCOUNTS tag. */ -ThemeMetric CHECKPOINTS_USE_TICKCOUNTS ( "Player", "CheckpointsUseTickcounts" ); -/** - * @brief If using checkpoints, are the song's TimeSignatureSegments used? - * - * If set to true AND CheckpointsUseTickcounts is set to false, the TimeSignatureSegments - * are used to determine how often the checkpoints are found. - * - * It should be noted that this is a deprecated metric. */ -ThemeMetric CHECKPOINTS_USE_TIME_SIGNATURES ( "Player", "CheckpointsUseTimeSignatures" ); -ThemeMetric CHECKPOINTS_FLASH_ON_HOLD ( "Player", "CheckpointsFlashOnHold" ); // sm-ssc addition -ThemeMetric IMMEDIATE_HOLD_LET_GO ( "Player", "ImmediateHoldLetGo" ); -/** - * @brief Must a Player step on a hold head for a hold to activate? - * - * If set to true, the Player must step on a hold head in order for the hold to activate. - * If set to false, merely holding your foot down as the hold head approaches will suffice. */ -ThemeMetric REQUIRE_STEP_ON_HOLD_HEADS ( "Player", "RequireStepOnHoldHeads" ); -/** - * @brief Must a Player step on a mine for it to activate? - * - * If set to true, the Player must step on a mine for it to blow up. - * If set to false, merely holding your foot down as the mine approaches will suffice. */ -ThemeMetric REQUIRE_STEP_ON_MINES ( "Player", "RequireStepOnMines" ); -//ThemeMetric HOLD_TRIGGERS_TAP_NOTES ( "Player", "HoldTriggersTapNotes" ); // parastar stuff; leave in though -/** - * @brief Does repeatedly stepping on a roll to keep it alive increment the combo? - * - * If set to true, repeatedly stepping on a roll will increment the combo. - * If set to false, only the roll head causes the combo to be incremented. - * - * For those wishing to make a theme very accurate to In The Groove 2, set this to false. */ -ThemeMetric ROLL_BODY_INCREMENTS_COMBO ( "Player", "RollBodyIncrementsCombo" ); -/** - * @brief Are checkpoints and taps considered separate judgments? - * - * If set to true, they are considered separate. - * If set to false, they are considered the same. */ -ThemeMetric CHECKPOINTS_TAPS_SEPARATE_JUDGMENT ( "Player", "CheckpointsTapsSeparateJudgment" ); -/** - * @brief Do we score missed holds and rolls with HoldNoteScores? - * - * If set to true, missed holds and rolls are given LetGo judgments. - * If set to false, missed holds and rolls are given no judgment on the hold side of things. */ -ThemeMetric SCORE_MISSED_HOLDS_AND_ROLLS ( "Player", "ScoreMissedHoldsAndRolls" ); -/** @brief How much of the song/course must have gone by before a Player's combo is colored? */ -ThemeMetric PERCENT_UNTIL_COLOR_COMBO ( "Player", "PercentUntilColorCombo" ); -/** @brief How much combo must be earned before the announcer says "Combo Stopped"? */ -ThemeMetric COMBO_STOPPED_AT ( "Player", "ComboStoppedAt" ); -ThemeMetric ATTACK_RUN_TIME_RANDOM ( "Player", "AttackRunTimeRandom" ); -ThemeMetric ATTACK_RUN_TIME_MINE ( "Player", "AttackRunTimeMine" ); - -/** - * @brief What is our highest cap for mMods? - * - * If set to 0 or less, assume the song takes over. */ -ThemeMetric M_MOD_HIGH_CAP("Player", "MModHighCap"); - -/** @brief Will battle modes have their steps mirrored or kept the same? */ -ThemeMetric BATTLE_RAVE_MIRROR ( "Player", "BattleRaveMirror" ); - -float Player::GetWindowSeconds( TimingWindow tw ) -{ - float fSecs = m_fTimingWindowSeconds[tw]; - fSecs *= m_fTimingWindowScale; - fSecs += m_fTimingWindowAdd; - return fSecs; -} - -Player::Player( NoteData &nd, bool bVisibleParts ) : m_NoteData(nd) -{ - m_bLoaded = false; - - m_pPlayerState = NULL; - m_pPlayerStageStats = NULL; - m_fNoteFieldHeight = 0; - - m_pLifeMeter = NULL; - m_pCombinedLifeMeter = NULL; - m_pScoreDisplay = NULL; - m_pSecondaryScoreDisplay = NULL; - m_pPrimaryScoreKeeper = NULL; - m_pSecondaryScoreKeeper = NULL; - m_pInventory = NULL; - m_pIterNeedsTapJudging = NULL; - m_pIterNeedsHoldJudging = NULL; - m_pIterUncrossedRows = NULL; - m_pIterUnjudgedRows = NULL; - m_pIterUnjudgedMineRows = NULL; - - m_bPaused = false; - m_bDelay = false; - - m_pAttackDisplay = NULL; - if( bVisibleParts ) - { - m_pAttackDisplay = new AttackDisplay; - this->AddChild( m_pAttackDisplay ); - } - - PlayerAI::InitFromDisk(); - - m_pNoteField = NULL; - if( bVisibleParts ) - { - m_pNoteField = new NoteField; - m_pNoteField->SetName( "NoteField" ); - } - m_pJudgedRows = new JudgedRows; - - m_bSendJudgmentAndComboMessages = true; -} - -Player::~Player() -{ - SAFE_DELETE( m_pAttackDisplay ); - SAFE_DELETE( m_pNoteField ); - for( unsigned i = 0; i < m_vpHoldJudgment.size(); ++i ) - SAFE_DELETE( m_vpHoldJudgment[i] ); - SAFE_DELETE( m_pJudgedRows ); - SAFE_DELETE( m_pIterNeedsTapJudging ); - SAFE_DELETE( m_pIterNeedsHoldJudging ); - SAFE_DELETE( m_pIterUncrossedRows ); - SAFE_DELETE( m_pIterUnjudgedRows ); - SAFE_DELETE( m_pIterUnjudgedMineRows ); - -} - -/* Init() does the expensive stuff: load sounds and noteskins. Load() just loads a NoteData. */ -void Player::Init( - const RString &sType, - PlayerState* pPlayerState, - PlayerStageStats* pPlayerStageStats, - LifeMeter* pLM, - CombinedLifeMeter* pCombinedLM, - ScoreDisplay* pScoreDisplay, - ScoreDisplay* pSecondaryScoreDisplay, - Inventory* pInventory, - ScoreKeeper* pPrimaryScoreKeeper, - ScoreKeeper* pSecondaryScoreKeeper ) -{ - GRAY_ARROWS_Y_STANDARD.Load( sType, "ReceptorArrowsYStandard" ); - GRAY_ARROWS_Y_REVERSE.Load( sType, "ReceptorArrowsYReverse" ); - ATTACK_DISPLAY_X.Load( sType, ATTACK_DISPLAY_X_NAME, NUM_PLAYERS, 2 ); - ATTACK_DISPLAY_Y.Load( sType, "AttackDisplayY" ); - ATTACK_DISPLAY_Y_REVERSE.Load( sType, "AttackDisplayYReverse" ); - HOLD_JUDGMENT_Y_STANDARD.Load( sType, "HoldJudgmentYStandard" ); - HOLD_JUDGMENT_Y_REVERSE.Load( sType, "HoldJudgmentYReverse" ); - BRIGHT_GHOST_COMBO_THRESHOLD.Load( sType, "BrightGhostComboThreshold" ); - TAP_JUDGMENTS_UNDER_FIELD.Load( sType, "TapJudgmentsUnderField" ); - HOLD_JUDGMENTS_UNDER_FIELD.Load( sType, "HoldJudgmentsUnderField" ); - COMBO_UNDER_FIELD.Load( sType, "ComboUnderField" ); - DRAW_DISTANCE_AFTER_TARGET_PIXELS.Load( sType, "DrawDistanceAfterTargetsPixels" ); - DRAW_DISTANCE_BEFORE_TARGET_PIXELS.Load( sType, "DrawDistanceBeforeTargetsPixels" ); - - { - // Init judgment positions - bool bPlayerUsingBothSides = GAMESTATE->GetCurrentStyle()->GetUsesCenteredArrows(); - Actor TempJudgment; - TempJudgment.SetName( "Judgment" ); - ActorUtil::LoadCommand( TempJudgment, sType, "Transform" ); - - Actor TempCombo; - TempCombo.SetName( "Combo" ); - ActorUtil::LoadCommand( TempCombo, sType, "Transform" ); - - int iEnabledPlayerIndex = -1; - int iNumEnabledPlayers = 0; - if( GAMESTATE->m_bMultiplayer ) - { - FOREACH_EnabledMultiPlayer( p ) - { - if( p == pPlayerState->m_mp ) - iEnabledPlayerIndex = iNumEnabledPlayers; - iNumEnabledPlayers++; - } - } - else - { - FOREACH_EnabledPlayer( p ) - { - if( p == pPlayerState->m_PlayerNumber ) - iEnabledPlayerIndex = iNumEnabledPlayers; - iNumEnabledPlayers++; - } - } - - if( iNumEnabledPlayers == 0 ) // hack for ScreenHowToPlay where no players are joined - { - iEnabledPlayerIndex = 0; - iNumEnabledPlayers = 1; - } - - for( int i=0; im_PlayerNumber ); - msg.SetParam( "MultiPlayer", pPlayerState->m_mp ); - msg.SetParam( "iEnabledPlayerIndex", iEnabledPlayerIndex ); - msg.SetParam( "iNumEnabledPlayers", iNumEnabledPlayers ); - msg.SetParam( "bPlayerUsingBothSides", bPlayerUsingBothSides ); - msg.SetParam( "bReverse", !!i ); - msg.SetParam( "bCentered", !!j ); - - TempJudgment.HandleMessage( msg ); - m_tsJudgment[i][j] = TempJudgment.DestTweenState(); - - TempCombo.HandleMessage( msg ); - m_tsCombo[i][j] = TempCombo.DestTweenState(); - } - } - } - - this->SortByDrawOrder(); - - m_pPlayerState = pPlayerState; - m_pPlayerStageStats = pPlayerStageStats; - m_pLifeMeter = pLM; - m_pCombinedLifeMeter = pCombinedLM; - m_pScoreDisplay = pScoreDisplay; - m_pSecondaryScoreDisplay = pSecondaryScoreDisplay; - m_pInventory = pInventory; - m_pPrimaryScoreKeeper = pPrimaryScoreKeeper; - m_pSecondaryScoreKeeper = pSecondaryScoreKeeper; - - m_iLastSeenCombo = -1; - - // set initial life - if( m_pLifeMeter && m_pPlayerStageStats ) - { - float fLife = m_pLifeMeter->GetLife(); - m_pPlayerStageStats->SetLifeRecordAt( fLife, STATSMAN->m_CurStageStats.m_fStepsSeconds ); - } - - // TODO: Remove use of PlayerNumber. - PlayerNumber pn = m_pPlayerState->m_PlayerNumber; - - RageSoundLoadParams SoundParams; - SoundParams.m_bSupportPan = true; - m_soundMine.Load( THEME->GetPathS(sType,"mine"), true, &SoundParams ); - - /* Attacks can be launched in course modes and in battle modes. They both come - * here to play, but allow loading a different sound for different modes. */ - switch( GAMESTATE->m_PlayMode ) - { - case PLAY_MODE_RAVE: - case PLAY_MODE_BATTLE: - m_soundAttackLaunch.Load( THEME->GetPathS(sType,"battle attack launch"), true, &SoundParams ); - m_soundAttackEnding.Load( THEME->GetPathS(sType,"battle attack ending"), true, &SoundParams ); - break; - default: - m_soundAttackLaunch.Load( THEME->GetPathS(sType,"course attack launch"), true, &SoundParams ); - m_soundAttackEnding.Load( THEME->GetPathS(sType,"course attack ending"), true, &SoundParams ); - break; - } - - // calculate M-mod speed here, so we can adjust properly on a per-song basis. - // XXX: can we find a better location for this? - if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fMaxScrollBPM != 0 ) - { - DisplayBpms bpms; - - if( GAMESTATE->IsCourseMode() ) - { - ASSERT( GAMESTATE->m_pCurTrail[pn] != NULL ); - GAMESTATE->m_pCurTrail[pn]->GetDisplayBpms( bpms ); - } - else - { - ASSERT( GAMESTATE->m_pCurSong != NULL ); - GAMESTATE->m_pCurSong->GetDisplayBpms( bpms ); - } - - float fMaxBPM = 0; - - /* TODO: Find a way to not go above a certain BPM range - * for getting the max BPM. Otherwise, you get songs - * like Tsuhsuixamush, M550, 0.18x speed. Even slow - * speed readers would not generally find this fun. - * -Wolfman2000 - */ - - // all BPMs are listed and available, so try them first. - // get the maximum listed value for the song or course. - // if the BPMs are < 0, reset and get the actual values. - if( !bpms.IsSecret() ) - { - fMaxBPM = (M_MOD_HIGH_CAP > 0 ? - bpms.GetMaxWithin(M_MOD_HIGH_CAP) : - bpms.GetMax()); - fMaxBPM = max( 0, fMaxBPM ); - } - - // we can't rely on the displayed BPMs, so manually calculate. - if( fMaxBPM == 0 ) - { - float fThrowAway = 0; - - if( GAMESTATE->IsCourseMode() ) - { - FOREACH_CONST( TrailEntry, GAMESTATE->m_pCurTrail[pn]->m_vEntries, e ) - { - float fMaxForEntry; - if (M_MOD_HIGH_CAP > 0) - e->pSong->m_SongTiming.GetActualBPM( fThrowAway, fMaxForEntry, M_MOD_HIGH_CAP ); - else - e->pSong->m_SongTiming.GetActualBPM( fThrowAway, fMaxForEntry ); - fMaxBPM = max( fMaxForEntry, fMaxBPM ); - } - } - else - { - if (M_MOD_HIGH_CAP > 0) - GAMESTATE->m_pCurSong->m_SongTiming.GetActualBPM( fThrowAway, fMaxBPM, M_MOD_HIGH_CAP ); - else - GAMESTATE->m_pCurSong->m_SongTiming.GetActualBPM( fThrowAway, fMaxBPM ); - } - } - - ASSERT( fMaxBPM > 0 ); - - // set an X-mod equal to Mnum / fMaxBPM (e.g. M600 with 150 becomes 4x) - PO_GROUP_ASSIGN(m_pPlayerState->m_PlayerOptions, ModsLevel_Preferred, m_fScrollSpeed, - m_pPlayerState->m_PlayerOptions.GetPreferred().m_fMaxScrollBPM / fMaxBPM); - } - - float fBalance = GameSoundManager::GetPlayerBalance( pn ); - m_soundMine.SetProperty( "Pan", fBalance ); - m_soundAttackLaunch.SetProperty( "Pan", fBalance ); - m_soundAttackEnding.SetProperty( "Pan", fBalance ); - - - if( HasVisibleParts() ) - { - LuaThreadVariable var( "Player", LuaReference::Create(m_pPlayerState->m_PlayerNumber) ); - LuaThreadVariable var2( "MultiPlayer", LuaReference::Create(m_pPlayerState->m_mp) ); - - m_sprCombo.Load( THEME->GetPathG(sType,"combo") ); - m_sprCombo->SetName( "Combo" ); - m_pActorWithComboPosition = &*m_sprCombo; - this->AddChild( m_sprCombo ); - - // todo: allow for judgments to be loaded per-column a la pop'n? - // see how HoldJudgments are handled below for an example, though - // it would need more work. -aj - m_sprJudgment.Load( THEME->GetPathG(sType,"judgment") ); - m_sprJudgment->SetName( "Judgment" ); - m_pActorWithJudgmentPosition = &*m_sprJudgment; - this->AddChild( m_sprJudgment ); - } - else - { - m_pActorWithComboPosition = NULL; - m_pActorWithJudgmentPosition = NULL; - } - - // Load HoldJudgments - m_vpHoldJudgment.resize( GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer ); - for( int i = 0; i < GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; ++i ) - m_vpHoldJudgment[i] = NULL; - - if( HasVisibleParts() ) - { - for( int i = 0; i < GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; ++i ) - { - HoldJudgment *pJudgment = new HoldJudgment; - // xxx: assumes sprite; todo: don't force 1x2 -aj - pJudgment->Load( THEME->GetPathG("HoldJudgment","label 1x2") ); - m_vpHoldJudgment[i] = pJudgment; - this->AddChild( m_vpHoldJudgment[i] ); - } - } - - m_fNoteFieldHeight = GRAY_ARROWS_Y_REVERSE-GRAY_ARROWS_Y_STANDARD; - if( m_pNoteField ) - { - m_pNoteField->Init( m_pPlayerState, m_fNoteFieldHeight ); - ActorUtil::LoadAllCommands( *m_pNoteField, sType ); - } - - m_vbFretIsDown.resize( GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer ); - FOREACH( bool, m_vbFretIsDown, b ) - *b = false; - - m_fActiveRandomAttackStart = -1.0f; -} -/** - * @brief Determine if a TapNote needs a tap note style judgment. - * @param tn the TapNote in question. - * @return true if it does, false otherwise. */ -static bool NeedsTapJudging( const TapNote &tn ) -{ - switch( tn.type ) - { - DEFAULT_FAIL( tn.type ); - case TapNote::tap: - case TapNote::hold_head: - case TapNote::mine: - case TapNote::lift: - return tn.result.tns == TNS_None; - case TapNote::hold_tail: - case TapNote::attack: - case TapNote::autoKeysound: - case TapNote::fake: - case TapNote::empty: - return false; - } -} - -/** - * @brief Determine if a TapNote needs a hold note style judgment. - * @param tn the TapNote in question. - * @return true if it does, false otherwise. */ -static bool NeedsHoldJudging( const TapNote &tn ) -{ - switch( tn.type ) - { - DEFAULT_FAIL( tn.type ); - case TapNote::hold_head: - return tn.HoldResult.hns == HNS_None; - case TapNote::tap: - case TapNote::hold_tail: - case TapNote::mine: - case TapNote::lift: - case TapNote::attack: - case TapNote::autoKeysound: - case TapNote::fake: - case TapNote::empty: - return false; - } -} - -static void GenerateCacheDataStructure(PlayerState *pPlayerState, const NoteData ¬es) { - - pPlayerState->m_CacheDisplayedBeat.clear(); - - const vector vScrolls = pPlayerState->GetDisplayedTiming().GetTimingSegments( SEGMENT_SCROLL ); - - float displayedBeat = 0.0f; - float lastRealBeat = 0.0f; - float lastRatio = 1.0f; - for ( unsigned i = 0; i < vScrolls.size(); i++ ) - { - ScrollSegment *seg = ToScroll( vScrolls[i] ); - displayedBeat += ( seg->GetBeat() - lastRealBeat ) * lastRatio; - lastRealBeat = seg->GetBeat(); - lastRatio = seg->GetRatio(); - CacheDisplayedBeat c = { seg->GetBeat(), displayedBeat, seg->GetRatio() }; - pPlayerState->m_CacheDisplayedBeat.push_back( c ); - } - - pPlayerState->m_CacheNoteStat.clear(); - - NoteData::all_tracks_const_iterator it = notes.GetTapNoteRangeAllTracks( 0, MAX_NOTE_ROW, true ); - int count = 0, lastCount = 0; - for( ; !it.IsAtEnd(); ++it ) - { - for( int t = 0; t < notes.GetNumTracks(); t++ ) - { - if( notes.GetTapNote( t, it.Row() ) != TAP_EMPTY ) count ++; - } - CacheNoteStat c = { NoteRowToBeat(it.Row()), lastCount, count }; - lastCount = count; - pPlayerState->m_CacheNoteStat.push_back(c); - } - -} - -void Player::Load() -{ - m_bLoaded = true; - - m_LastTapNoteScore = TNS_None; - // The editor can start playing in the middle of the song. - const int iNoteRow = BeatToNoteRowNotRounded( m_pPlayerState->m_Position.m_fSongBeat ); - m_iFirstUncrossedRow = iNoteRow - 1; - m_pJudgedRows->Reset( iNoteRow ); - - // TODO: Remove use of PlayerNumber. - PlayerNumber pn = m_pPlayerState->m_PlayerNumber; - - bool bOniDead = GAMESTATE->m_SongOptions.GetStage().m_LifeType == SongOptions::LIFE_BATTERY && - (m_pPlayerStageStats == NULL || m_pPlayerStageStats->m_bFailed); - - /* The editor reuses Players ... so we really need to make sure everything - * is reset and not tweening. Perhaps ActorFrame should recurse to subactors; - * then we could just this->StopTweening()? -glenn */ - // hurr why don't you just set m_bPropagateCommands on it then -aj - if( m_sprJudgment ) - m_sprJudgment->PlayCommand("Reset"); - if( m_pPlayerStageStats ) - { - SetCombo( m_pPlayerStageStats->m_iCurCombo, m_pPlayerStageStats->m_iCurMissCombo ); // combo can persist between songs and games - } - if( m_pAttackDisplay ) - m_pAttackDisplay->Init( m_pPlayerState ); - - /* Don't re-init this; that'll reload graphics. Add a separate Reset() call - * if some ScoreDisplays need it. */ -// if( m_pScore ) -// m_pScore->Init( pn ); - - /* Apply transforms. */ - NoteDataUtil::TransformNoteData( m_NoteData, m_pPlayerState->m_PlayerOptions.GetStage(), GAMESTATE->GetCurrentStyle()->m_StepsType ); - - const Song* pSong = GAMESTATE->m_pCurSong; - - m_Timing = GAMESTATE->m_pCurSteps[pn]->GetTimingData(); - - // Generate some cache data structure. - GenerateCacheDataStructure(m_pPlayerState, m_NoteData); - - switch( GAMESTATE->m_PlayMode ) - { - case PLAY_MODE_RAVE: - case PLAY_MODE_BATTLE: - { - // ugly, ugly, ugly. Works only w/ dance. - // Why does this work only with dance? - Steve - // it has to do with there only being four cases. This is a lame - // workaround, but since only DDR has ever really implemented those - // modes, it's stayed like this. -aj - StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType; - NoteDataUtil::TransformNoteData( m_NoteData, m_pPlayerState->m_PlayerOptions.GetStage(), st ); - - if (BATTLE_RAVE_MIRROR) - { - // shuffle either p1 or p2 - static int count = 0; - switch( count ) - { - case 0: - case 3: - NoteDataUtil::Turn( m_NoteData, st, NoteDataUtil::left); - break; - case 1: - case 2: - NoteDataUtil::Turn( m_NoteData, st, NoteDataUtil::right); - break; - default: - FAIL_M(ssprintf("Count %i not in range 0-3", count)); - } - count++; - count %= 4; - } - break; - } - default: break; - } - - int iDrawDistanceAfterTargetsPixels = GAMESTATE->IsEditing() ? -100 : DRAW_DISTANCE_AFTER_TARGET_PIXELS; - int iDrawDistanceBeforeTargetsPixels = GAMESTATE->IsEditing() ? 400 : DRAW_DISTANCE_BEFORE_TARGET_PIXELS; - - float fNoteFieldMiddle = (GRAY_ARROWS_Y_STANDARD+GRAY_ARROWS_Y_REVERSE)/2; - - if( m_pNoteField && !bOniDead ) - { - m_pNoteField->SetY( fNoteFieldMiddle ); - m_pNoteField->Load( &m_NoteData, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ); - } - - bool bPlayerUsingBothSides = GAMESTATE->GetCurrentStyle()->GetUsesCenteredArrows(); - if( m_pAttackDisplay ) - m_pAttackDisplay->SetX( ATTACK_DISPLAY_X.GetValue(pn, bPlayerUsingBothSides) - 40 ); - // set this in Update //m_pAttackDisplay->SetY( bReverse ? ATTACK_DISPLAY_Y_REVERSE : ATTACK_DISPLAY_Y ); - - // set this in Update - //m_pJudgment->SetX( JUDGMENT_X.GetValue(pn,bPlayerUsingBothSides) ); - //m_pJudgment->SetY( bReverse ? JUDGMENT_Y_REVERSE : JUDGMENT_Y ); - - // Need to set Y positions of all these elements in Update since - // they change depending on PlayerOptions. - - - // Load keysounds. If sounds are already loaded (as in the editor), don't reload them. - // XXX: the editor will load several duplicate copies (in each NoteField), and each - // player will load duplicate sounds. Does this belong somewhere else (perhaps in - // a separate object, used alongside ScreenGameplay::m_pSoundMusic and ScreenEdit::m_pSoundMusic?) - // We don't have to load separate copies to set player fade: always make a copy, and set the - // fade on the copy. - RString sSongDir = pSong->GetSongDir(); - m_vKeysounds.resize( pSong->m_vsKeysoundFile.size() ); - - // parameters are invalid somehow... -aj - RageSoundLoadParams SoundParams; - SoundParams.m_bSupportPan = true; - - float fBalance = GameSoundManager::GetPlayerBalance( pn ); - for( unsigned i=0; im_vsKeysoundFile[i]; - RageSound& sound = m_vKeysounds[i]; - if( sound.GetLoadedFilePath() != sKeysoundFilePath ) - sound.Load( sKeysoundFilePath, true, &SoundParams ); - sound.SetProperty( "Pan", fBalance ); - sound.SetStopModeFromString( "stop" ); - } - - if( m_pPlayerStageStats ) - SendComboMessages( m_pPlayerStageStats->m_iCurCombo, m_pPlayerStageStats->m_iCurMissCombo ); - - SAFE_DELETE( m_pIterNeedsTapJudging ); - m_pIterNeedsTapJudging = new NoteData::all_tracks_iterator( m_NoteData.GetTapNoteRangeAllTracks(iNoteRow, MAX_NOTE_ROW) ); - - SAFE_DELETE( m_pIterNeedsHoldJudging ); - m_pIterNeedsHoldJudging = new NoteData::all_tracks_iterator( m_NoteData.GetTapNoteRangeAllTracks(iNoteRow, MAX_NOTE_ROW ) ); - - SAFE_DELETE( m_pIterUncrossedRows ); - m_pIterUncrossedRows = new NoteData::all_tracks_iterator( m_NoteData.GetTapNoteRangeAllTracks(iNoteRow, MAX_NOTE_ROW ) ); - - SAFE_DELETE( m_pIterUnjudgedRows ); - m_pIterUnjudgedRows = new NoteData::all_tracks_iterator( m_NoteData.GetTapNoteRangeAllTracks(iNoteRow, MAX_NOTE_ROW ) ); - - SAFE_DELETE( m_pIterUnjudgedMineRows ); - m_pIterUnjudgedMineRows = new NoteData::all_tracks_iterator( m_NoteData.GetTapNoteRangeAllTracks(iNoteRow, MAX_NOTE_ROW ) ); -} - -void Player::SendComboMessages( int iOldCombo, int iOldMissCombo ) -{ - const int iCurCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0; - if( iOldCombo > COMBO_STOPPED_AT && iCurCombo < COMBO_STOPPED_AT ) - { - SCREENMAN->PostMessageToTopScreen( SM_ComboStopped, 0 ); - } - - if( m_bSendJudgmentAndComboMessages ) - { - Message msg( "ComboChanged" ); - msg.SetParam( "Player", m_pPlayerState->m_PlayerNumber ); - msg.SetParam( "OldCombo", iOldCombo ); - msg.SetParam( "OldMissCombo", iOldMissCombo ); - if( m_pPlayerState ) - msg.SetParam( "PlayerState", LuaReference::CreateFromPush(*m_pPlayerState) ); - if( m_pPlayerStageStats ) - msg.SetParam( "PlayerStageStats", LuaReference::CreateFromPush(*m_pPlayerStageStats) ); - MESSAGEMAN->Broadcast( msg ); - } -} - -void Player::Update( float fDeltaTime ) -{ - const RageTimer now; - // Don't update if we haven't been loaded yet. - if( !m_bLoaded ) - return; - - //LOG->Trace( "Player::Update(%f)", fDeltaTime ); - - if( GAMESTATE->m_pCurSong==NULL || IsOniDead() ) - return; - - ActorFrame::Update( fDeltaTime ); - - if(m_pPlayerState->m_mp != MultiPlayer_Invalid) - { - /* In multiplayer, it takes too long to run player updates for every player each frame; - * with 32 players and three difficulties, we have 96 Players to update. Stagger these - * updates, by only updating a few players each update; since we don't have screen elements - * tightly tied to user actions in this mode, this doesn't degrade gameplay. Run 4 players - * per update, which means 12 Players in 3-difficulty mode. - */ - static int iCycle = 0; - iCycle = (iCycle + 1) % 8; - - if((m_pPlayerState->m_mp % 8) != iCycle) - return; - } - - const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat; - const int iSongRow = BeatToNoteRow( fSongBeat ); - - // Optimization: Don't spend time processing the things below that won't show - // if the Player doesn't show anything on the screen. - if( HasVisibleParts() ) - { - // Random Attack Mod - if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fRandAttack ) - { - float fCurrentGameTime = STATSMAN->m_CurStageStats.m_fGameplaySeconds; - - const float fAttackRunTime = ATTACK_RUN_TIME_RANDOM; - - // Don't start until 1 seconds into game, minimum - if( fCurrentGameTime > 1.0f ) - { - /* Update the attack if there are no others currently running. - * Note that we have a new one activate a little early; This is - * to have a bit of overlap rather than an abrupt change. */ - if( (fCurrentGameTime - m_fActiveRandomAttackStart) > (fAttackRunTime - 0.5f) ) - { - m_fActiveRandomAttackStart = fCurrentGameTime; - - Attack attRandomAttack; - attRandomAttack.sModifiers = ApplyRandomAttack(); - attRandomAttack.fSecsRemaining = fAttackRunTime; - m_pPlayerState->LaunchAttack( attRandomAttack ); - } - } - } - - if( g_bEnableAttackSoundPlayback ) - { - if( m_pPlayerState->m_bAttackBeganThisUpdate ) - m_soundAttackLaunch.Play(); - if( m_pPlayerState->m_bAttackEndedThisUpdate ) - m_soundAttackEnding.Play(); - } - - if( m_pNoteField ) - m_pNoteField->Update( fDeltaTime ); - - float fMiniPercent = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_MINI]; - float fTinyPercent = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_TINY]; - float fJudgmentZoom = min( powf(0.5f, fMiniPercent+fTinyPercent), 1.0f ); - - // Update Y positions - { - for( int c=0; cGetCurrentStyle()->m_iColsPerPlayer; c++ ) - { - float fPercentReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(c); - float fHoldJudgeYPos = SCALE( fPercentReverse, 0.f, 1.f, HOLD_JUDGMENT_Y_STANDARD, HOLD_JUDGMENT_Y_REVERSE ); - //float fGrayYPos = SCALE( fPercentReverse, 0.f, 1.f, GRAY_ARROWS_Y_STANDARD, GRAY_ARROWS_Y_REVERSE ); - - float fX = ArrowEffects::GetXPos( m_pPlayerState, c, 0 ); - const float fZ = ArrowEffects::GetZPos( m_pPlayerState, c, 0 ); - - m_vpHoldJudgment[c]->SetX( fX ); - m_vpHoldJudgment[c]->SetY( fHoldJudgeYPos ); - m_vpHoldJudgment[c]->SetZ( fZ ); - m_vpHoldJudgment[c]->SetZoom( fJudgmentZoom ); - } - } - - // NoteField accounts for reverse on its own now. - //if( m_pNoteField ) - // m_pNoteField->SetY( fGrayYPos ); - - const bool bReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(0) == 1; - float fPercentCentered = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fScrolls[PlayerOptions::SCROLL_CENTERED]; - - if( m_pActorWithJudgmentPosition != NULL ) - { - const Actor::TweenState &ts1 = m_tsJudgment[bReverse?1:0][0]; - const Actor::TweenState &ts2 = m_tsJudgment[bReverse?1:0][1]; - Actor::TweenState::MakeWeightedAverage( m_pActorWithJudgmentPosition->DestTweenState(), ts1, ts2, fPercentCentered ); - } - - if( m_pActorWithComboPosition != NULL ) - { - const Actor::TweenState &ts1 = m_tsCombo[bReverse?1:0][0]; - const Actor::TweenState &ts2 = m_tsCombo[bReverse?1:0][1]; - Actor::TweenState::MakeWeightedAverage( m_pActorWithComboPosition->DestTweenState(), ts1, ts2, fPercentCentered ); - } - - float fNoteFieldZoom = 1 - fMiniPercent*0.5f; - if( m_pNoteField ) - m_pNoteField->SetZoom( fNoteFieldZoom ); - if( m_pActorWithJudgmentPosition != NULL ) - m_pActorWithJudgmentPosition->SetZoom( m_pActorWithJudgmentPosition->GetZoom() * fJudgmentZoom ); - if( m_pActorWithComboPosition != NULL ) - m_pActorWithComboPosition->SetZoom( m_pActorWithComboPosition->GetZoom() * fJudgmentZoom ); - } - - // If we're paused, don't update tap or hold note logic, so hold notes can be released - // during pause. - if( m_bPaused ) - return; - - // Check for a strum miss - if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 && - m_pPlayerState->m_fLastStrumMusicSeconds + g_fTimingWindowStrum < m_pPlayerState->m_Position.m_fMusicSeconds ) - { - DoStrumMiss(); - } - - // update pressed flag - const int iNumCols = GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; - ASSERT_M( iNumCols <= MAX_COLS_PER_PLAYER, ssprintf("%i > %i", iNumCols, MAX_COLS_PER_PLAYER) ); - for( int col=0; col < iNumCols; ++col ) - { - ASSERT( m_pPlayerState != NULL ); - - // TODO: Remove use of PlayerNumber. - GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( col, m_pPlayerState->m_PlayerNumber ); - - bool bIsHoldingButton = INPUTMAPPER->IsBeingPressed( GameI ); - - // TODO: Make this work for non-human-controlled players - if( bIsHoldingButton && !GAMESTATE->m_bDemonstrationOrJukebox && m_pPlayerState->m_PlayerController==PC_HUMAN ) - if( m_pNoteField ) - m_pNoteField->SetPressed( col ); - } - - // handle Autoplay for rolls - if( m_pPlayerState->m_PlayerController != PC_HUMAN ) - { - for( int iTrack=0; iTrack= 0.5f ) - continue; - - Step( iTrack, iHeadRow, now, false, false ); - if( m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) - { - STATSMAN->m_CurStageStats.m_bUsedAutoplay = true; - if( m_pPlayerStageStats ) - m_pPlayerStageStats->m_bDisqualified = true; - } - } - } - - - // update HoldNotes logic - { - - // Fast forward to the first that needs hold judging. - { - NoteData::all_tracks_iterator &iter = *m_pIterNeedsHoldJudging; - while( !iter.IsAtEnd() && iter.Row() <= iSongRow && !NeedsHoldJudging(*iter) ) - ++iter; - } - - vector vHoldNotesToGradeTogether; - int iRowOfLastHoldNote = -1; - NoteData::all_tracks_iterator iter = *m_pIterNeedsHoldJudging; // copy - for( ; !iter.IsAtEnd() && iter.Row() <= iSongRow; ++iter ) - { - TapNote &tn = *iter; - if( tn.type != TapNote::hold_head ) - continue; - - int iTrack = iter.Track(); - int iRow = iter.Row(); - TrackRowTapNote trtn = { iTrack, iRow, &tn }; - - /* All holds must be of the same subType because fLife is handled - * in different ways depending on the SubType. Handle Rolls one at - * a time and don't mix with holds. */ - switch( tn.subType ) - { - DEFAULT_FAIL( tn.subType ); - case TapNote::hold_head_hold: - break; - case TapNote::hold_head_roll: - { - vector v; - v.push_back( trtn ); - UpdateHoldNotes( iSongRow, fDeltaTime, v ); - } - continue; // don't process this below - } - /* - case TapNote::hold_head_mine: - break; - */ - - if( iRow != iRowOfLastHoldNote || !JUDGE_HOLD_NOTES_ON_SAME_ROW_TOGETHER ) - { - if( !vHoldNotesToGradeTogether.empty() ) - { - //LOG->Trace( ssprintf("UpdateHoldNotes; %i != %i || !judge holds on same row together",iRow,iRowOfLastHoldNote) ); - UpdateHoldNotes( iSongRow, fDeltaTime, vHoldNotesToGradeTogether ); - vHoldNotesToGradeTogether.clear(); - } - } - iRowOfLastHoldNote = iRow; - vHoldNotesToGradeTogether.push_back( trtn ); - } - - if( !vHoldNotesToGradeTogether.empty() ) - { - //LOG->Trace("UpdateHoldNotes since !vHoldNotesToGradeTogether.empty()"); - UpdateHoldNotes( iSongRow, fDeltaTime, vHoldNotesToGradeTogether ); - vHoldNotesToGradeTogether.clear(); - } - } - - { - // Why was this originally "BeatToNoteRowNotRounded"? It should be rounded. -Chris - /* We want to send the crossed row message exactly when we cross the row--not - * .5 before the row. Use a very slow song (around 2 BPM) as a test case: without - * rounding, autoplay steps early. -glenn */ - const int iRowNow = BeatToNoteRowNotRounded( m_pPlayerState->m_Position.m_fSongBeat ); - if( iRowNow >= 0 ) - { - if( GAMESTATE->IsPlayerEnabled(m_pPlayerState) ) - { - if(m_pPlayerState->m_Position.m_bDelay) - { - if( !m_bDelay ) - m_bDelay = true; - } - else - { - if(m_bDelay) - { - if(m_pPlayerState->m_PlayerController != PC_HUMAN) - { - CrossedRows( iRowNow-1, now ); - } - m_bDelay = false; - } - CrossedRows( iRowNow, now ); - } - } - } - } - - // Check for completely judged rows. - UpdateJudgedRows(); - - // Check for TapNote misses - if (!GAMESTATE->m_bInStepEditor) - { - UpdateTapNotesMissedOlderThan( GetMaxStepDistanceSeconds() ); - } - // process transforms that are waiting to be applied - ApplyWaitingTransforms(); -} - -// Update a group of holds with shared scoring/life. All of these holds will have the same start row. -void Player::UpdateHoldNotes( int iSongRow, float fDeltaTime, vector &vTN ) -{ - ASSERT( !vTN.empty() ); - - //LOG->Trace("--------------------------------"); - /* - LOG->Trace("[Player::UpdateHoldNotes] begins"); - LOG->Trace( ssprintf("song row %i, deltaTime = %f",iSongRow,fDeltaTime) ); - */ - - int iStartRow = vTN[0].iRow; - int iMaxEndRow = INT_MIN; - int iFirstTrackWithMaxEndRow = -1; - - TapNote::SubType subType = TapNote::SubType_Invalid; - FOREACH( TrackRowTapNote, vTN, trtn ) - { - int iTrack = trtn->iTrack; - ASSERT( iStartRow == trtn->iRow ); - TapNote &tn = *trtn->pTN; - int iEndRow = iStartRow + tn.iDuration; - if( subType == TapNote::SubType_Invalid ) - subType = tn.subType; - - /* All holds must be of the same subType because fLife is handled - * in different ways depending on the SubType. */ - ASSERT( tn.subType == subType ); - - if( iEndRow > iMaxEndRow ) - { - iMaxEndRow = iEndRow; - iFirstTrackWithMaxEndRow = iTrack; - } - } - - ASSERT( iFirstTrackWithMaxEndRow != -1 ); - //LOG->Trace( ssprintf("start row: %i; max/end row: = %i",iStartRow,iMaxEndRow) ); - //LOG->Trace( ssprintf("first track with max end row = %i",iFirstTrackWithMaxEndRow) ); - //LOG->Trace( ssprintf("max end row - start row (in beats) = %f",NoteRowToBeat(iMaxEndRow)-NoteRowToBeat(iStartRow)) ); - - FOREACH( TrackRowTapNote, vTN, trtn ) - { - TapNote &tn = *trtn->pTN; - - // set hold flags so NoteField can do intelligent drawing - tn.HoldResult.bHeld = false; - tn.HoldResult.bActive = false; - - int iRow = trtn->iRow; - //LOG->Trace( ssprintf("this row: %i",iRow) ); - - // If the song beat is in the range of this hold: - if( iRow <= iSongRow && iRow <= iMaxEndRow ) - { - //LOG->Trace( ssprintf("overlap time before: %f",tn.HoldResult.fOverlappedTime) ); - tn.HoldResult.fOverlappedTime += fDeltaTime; - //LOG->Trace( ssprintf("overlap time after: %f",tn.HoldResult.fOverlappedTime) ); - } - else - { - //LOG->Trace( "overlap time = 0" ); - tn.HoldResult.fOverlappedTime = 0; - } - } - - HoldNoteScore hns = vTN[0].pTN->HoldResult.hns; - float fLife = vTN[0].pTN->HoldResult.fLife; - - if( hns != HNS_None ) // if this HoldNote already has a result - { - //LOG->Trace("hold note has a result, skipping."); - return; // we don't need to update the logic for this group - } - - //LOG->Trace("hold note doesn't already have result, let's check."); - - //LOG->Trace( ssprintf("[C++] hold note score: %s",HoldNoteScoreToString(hns).c_str()) ); - //LOG->Trace(ssprintf("[Player::UpdateHoldNotes] fLife = %f",fLife)); - - bool bSteppedOnHead = true; - bool bHeadJudged = true; - FOREACH( TrackRowTapNote, vTN, trtn ) - { - TapNote &tn = *trtn->pTN; - TapNoteScore tns = tn.result.tns; - //LOG->Trace( ssprintf("[C++] tap note score: %s",StringConversion::ToString(tns).c_str()) ); - - // TODO: When using JUDGE_HOLD_NOTES_ON_SAME_ROW_TOGETHER, require that the whole row of - // taps was hit before activating this group of holds. - /* Something about the logic in this section is causing 192nd steps to - * fail for some odd reason. -aj */ - bSteppedOnHead &= (tns != TNS_Miss && tns != TNS_None); // did they step on the start of this hold? - bHeadJudged &= (tns != TNS_None); // has this hold really even started yet? - - /* - if(bSteppedOnHead) - LOG->Trace("[Player::UpdateHoldNotes] player stepped on head"); - else - LOG->Trace("[Player::UpdateHoldNotes] player didn't step on the head"); - */ - } - - bool bInitiatedNote; - if( REQUIRE_STEP_ON_HOLD_HEADS ) - { - // XXX HACK: Miniholds (a 64th or 192nd length hold) will not always - // register as Held, even if you hit the note. This is considered a - // major roadblock to adoption, so until a proper fix is found, - // DON'T REMOVE THIS HACK! -aj - /*if( iMaxEndRow-iStartRow <= 4 ) - bInitiatedNote = true; - else*/ - bInitiatedNote = bSteppedOnHead; - } - else - { - bInitiatedNote = true; - bHeadJudged = true; - } - - bool bIsHoldingButton = true; - FOREACH( TrackRowTapNote, vTN, trtn ) - { - int iTrack = trtn->iTrack; - - // TODO: Remove use of PlayerNumber. - PlayerNumber pn = m_pPlayerState->m_PlayerNumber; - - if( m_pPlayerState->m_PlayerController != PC_HUMAN ) - { - // TODO: Make the CPU miss sometimes. - if( m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) - { - STATSMAN->m_CurStageStats.m_bUsedAutoplay = true; - if( m_pPlayerStageStats != NULL ) - m_pPlayerStageStats->m_bDisqualified = true; - } - } - else - { - GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( iTrack, pn ); - // this previously read as bIsHoldingButton &= - // was there a specific reason for this? - Friez - bIsHoldingButton &= INPUTMAPPER->IsBeingPressed( GameI, m_pPlayerState->m_mp ); - } - } - - if( bInitiatedNote && fLife != 0 && bHeadJudged ) - { - //LOG->Trace("[Player::UpdateHoldNotes] initiated note, fLife != 0"); - /* This hold note is not judged and we stepped on its head. - * Update iLastHeldRow. Do this even if we're a little beyond the end - * of the hold note, to make sure iLastHeldRow is clamped to iEndRow - * if the hold note is held all the way. */ - FOREACH( TrackRowTapNote, vTN, trtn ) - { - TapNote &tn = *trtn->pTN; - int iEndRow = iStartRow + tn.iDuration; - - //LOG->Trace(ssprintf("trying for min between iSongRow (%i) and iEndRow (%i) (duration %i)",iSongRow,iEndRow,tn.iDuration)); - trtn->pTN->HoldResult.iLastHeldRow = min( iSongRow, iEndRow ); - } - } - - // If the song beat is in the range of this hold: - if( iStartRow <= iSongRow && iStartRow <= iMaxEndRow && bHeadJudged ) - { - switch( subType ) - { - case TapNote::hold_head_hold: - FOREACH( TrackRowTapNote, vTN, trtn ) - { - TapNote &tn = *trtn->pTN; - - // set hold flag so NoteField can do intelligent drawing - tn.HoldResult.bHeld = bIsHoldingButton && bInitiatedNote; - tn.HoldResult.bActive = bInitiatedNote; - } - - if( bInitiatedNote && bIsHoldingButton ) - { - //LOG->Trace("bInitiatedNote && bIsHoldingButton; Increasing hold life to MAX_HOLD_LIFE"); - // Increase life - fLife = MAX_HOLD_LIFE; // was 1 -aj - } - else - { - /* - LOG->Trace("Checklist:"); - if(bInitiatedNote) - LOG->Trace("[X] Initiated Note"); - else - LOG->Trace("[ ] Initiated Note"); - - if(bIsHoldingButton) - LOG->Trace("[X] Holding Button"); - else - LOG->Trace("[ ] Holding Button"); - */ - - // Decrease life - //LOG->Trace("fLife before minus: %f",fLife); - fLife -= fDeltaTime/GetWindowSeconds(TW_Hold); - //LOG->Trace("fLife before clamp: %f",fLife); - fLife = max( fLife, 0 ); // clamp - //LOG->Trace("fLife after: %f",fLife); - } - break; - case TapNote::hold_head_roll: - FOREACH( TrackRowTapNote, vTN, trtn ) - { - TapNote &tn = *trtn->pTN; - tn.HoldResult.bHeld = true; - tn.HoldResult.bActive = bInitiatedNote; - } - - // give positive life in Step(), not here. - - // Decrease life - fLife -= fDeltaTime/GetWindowSeconds(TW_Roll); - fLife = max( fLife, 0 ); // clamp - break; - /* - case TapNote::hold_head_mine: - break; - */ - default: - FAIL_M(ssprintf("Invalid tap note subtype: %i", subType)); - } - } - - // TODO: Cap the active time passed to the score keeper to the actual start time and end time of the hold. - if( vTN[0].pTN->HoldResult.bActive ) - { - float fSecondsActiveSinceLastUpdate = fDeltaTime * GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; - if( m_pPrimaryScoreKeeper ) - m_pPrimaryScoreKeeper->HandleHoldActiveSeconds( fSecondsActiveSinceLastUpdate ); - if( m_pSecondaryScoreKeeper ) - m_pSecondaryScoreKeeper->HandleHoldActiveSeconds( fSecondsActiveSinceLastUpdate ); - } - - // check for LetGo. If the head was missed completely, don't count an LetGo. - /* Why? If you never step on the head, then it will be left as HNS_None, - * which doesn't seem correct. */ - if( IMMEDIATE_HOLD_LET_GO ) - { - if( bInitiatedNote && fLife == 0 && bHeadJudged ) // the player has not pressed the button for a long time! - { - //LOG->Trace("LetGo from life == 0 (did initiate hold)"); - hns = HNS_LetGo; - } - } - - // score hold notes that have passed - if( iSongRow >= iMaxEndRow && bHeadJudged ) - { - bool bLetGoOfHoldNote = false; - - /* Score rolls that end with fLife == 0 as LetGo, even if - * HOLD_CHECKPOINTS is on. Rolls don't have iCheckpointsMissed set, so, - * unless we check Life == 0, rolls would always be scored as Held. */ - bool bAllowHoldCheckpoints; - switch( subType ) - { - DEFAULT_FAIL( subType ); - case TapNote::hold_head_hold: - bAllowHoldCheckpoints = true; - break; - case TapNote::hold_head_roll: - bAllowHoldCheckpoints = false; - break; - /* - case TapNote::hold_head_mine: - bAllowHoldCheckpoints = true; - break; - */ - } - - if( HOLD_CHECKPOINTS && bAllowHoldCheckpoints ) - { - //LOG->Trace("(hold checkpoints are allowed and enabled.)"); - int iCheckpointsHit = 0; - int iCheckpointsMissed = 0; - FOREACH( TrackRowTapNote, vTN, v ) - { - iCheckpointsHit += v->pTN->HoldResult.iCheckpointsHit; - iCheckpointsMissed += v->pTN->HoldResult.iCheckpointsMissed; - } - bLetGoOfHoldNote = iCheckpointsMissed > 0 || iCheckpointsHit == 0; - - // TRICKY: If the hold is so short that it has no checkpoints, - // then mark it as Held if the head was stepped on. - if( iCheckpointsHit == 0 && iCheckpointsMissed == 0 ) - bLetGoOfHoldNote = !bSteppedOnHead; - - /* - if(bLetGoOfHoldNote) - LOG->Trace("let go of hold note, life is 0"); - else - LOG->Trace("did not let go of hold note :D"); - */ - } - else - { - //LOG->Trace("(hold checkpoints disabled.)"); - bLetGoOfHoldNote = fLife == 0; - /* - if(bLetGoOfHoldNote) - LOG->Trace("let go of hold note, life is 0"); - else - LOG->Trace("did not let go of hold note :D"); - */ - } - - if( bInitiatedNote ) - { - if(!bLetGoOfHoldNote) - { - //LOG->Trace("initiated note and didn't let go"); - fLife = 1; // xxx: should be MAX_HOLD_LIFE instead? -aj - hns = HNS_Held; - bool bBright = m_pPlayerStageStats && m_pPlayerStageStats->m_iCurCombo>(int)BRIGHT_GHOST_COMBO_THRESHOLD; - if( m_pNoteField ) - { - FOREACH( TrackRowTapNote, vTN, trtn ) - { - int iTrack = trtn->iTrack; - m_pNoteField->DidHoldNote( iTrack, HNS_Held, bBright ); // bright ghost flash - } - } - } - - else - { - //LOG->Trace("initiated note and let go :("); - } - } - else if( SCORE_MISSED_HOLDS_AND_ROLLS ) - { - hns = HNS_LetGo; - } - else - { - hns = HNS_None; - } - } - - float fLifeFraction = fLife / MAX_HOLD_LIFE; - - FOREACH( TrackRowTapNote, vTN, trtn ) - { - TapNote &tn = *trtn->pTN; - tn.HoldResult.fLife = fLife; - tn.HoldResult.hns = hns; - // Stop the playing keysound for the hold note. - // I think this causes crashes too. -aj (Possibly fixed. -?) - - Preference *pVolume = Preference::GetPreferenceByName("SoundVolume"); - float fVol = pVolume->Get(); - - if( tn.iKeysoundIndex >= 0 && tn.iKeysoundIndex < (int) m_vKeysounds.size() ) - { - float factor = (tn.subType == TapNote::hold_head_roll ? 2.0f * fLifeFraction : 10.0f * fLifeFraction - 8.5f); - m_vKeysounds[tn.iKeysoundIndex].SetProperty ("Volume", max(0.0f, min(1.0f, factor)) * fVol); - } - } - - if( hns != HNS_None ) - { - //LOG->Trace("tap note scoring time."); - TapNote &tn = *vTN[0].pTN; - SetHoldJudgment( tn.result.tns, tn.HoldResult.hns, iFirstTrackWithMaxEndRow ); - HandleHoldScore( tn ); - //LOG->Trace("hold result = %s",StringConversion::ToString(tn.HoldResult.hns).c_str()); - } - //LOG->Trace("[Player::UpdateHoldNotes] ends"); -} - -void Player::ApplyWaitingTransforms() -{ - for( unsigned j=0; jm_ModsToApply.size(); j++ ) - { - const Attack &mod = m_pPlayerState->m_ModsToApply[j]; - PlayerOptions po; - // if re-adding noteskin changes, blank out po.m_sNoteSkin. -aj - po.FromString( mod.sModifiers ); - - float fStartBeat, fEndBeat; - mod.GetRealtimeAttackBeats( GAMESTATE->m_pCurSong, m_pPlayerState, fStartBeat, fEndBeat ); - fEndBeat = min( fEndBeat, m_NoteData.GetLastBeat() ); - - LOG->Trace( "Applying transform '%s' from %f to %f to '%s'", mod.sModifiers.c_str(), fStartBeat, fEndBeat, - GAMESTATE->m_pCurSong->GetTranslitMainTitle().c_str() ); - - // if re-adding noteskin changes, this is one place to edit -aj - - NoteDataUtil::TransformNoteData( m_NoteData, po, GAMESTATE->GetCurrentStyle()->m_StepsType, BeatToNoteRow(fStartBeat), BeatToNoteRow(fEndBeat) ); - } - m_pPlayerState->m_ModsToApply.clear(); -} - -void Player::DrawPrimitives() -{ - // TODO: Remove use of PlayerNumber. - PlayerNumber pn = m_pPlayerState->m_PlayerNumber; - - // May have both players in doubles (for battle play); only draw primary player. - if( GAMESTATE->GetCurrentStyle()->m_StyleType == StyleType_OnePlayerTwoSides && - pn != GAMESTATE->GetMasterPlayerNumber() ) - return; - - // Draw these below everything else. - // xxx: if NoteField Board is enabled and COMBO_UNDER_FIELD, we really want - // the combo under the field but over the notefield board. -aj - if( COMBO_UNDER_FIELD && m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind == 0 ) - { - if( m_sprCombo ) - m_sprCombo->Draw(); - } - - if( m_pAttackDisplay ) - m_pAttackDisplay->Draw(); - - if( TAP_JUDGMENTS_UNDER_FIELD ) - DrawTapJudgments(); - - if( HOLD_JUDGMENTS_UNDER_FIELD ) - DrawHoldJudgments(); - - float fTilt = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fPerspectiveTilt; - float fSkew = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fSkew; - bool bReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(0)>0.5; - - DISPLAY->CameraPushMatrix(); - DISPLAY->PushMatrix(); - - float fCenterY = this->GetY()+(GRAY_ARROWS_Y_STANDARD+GRAY_ARROWS_Y_REVERSE)/2; - - DISPLAY->LoadMenuPerspective( 45, SCREEN_WIDTH, SCREEN_HEIGHT, SCALE(fSkew,0.f,1.f,this->GetX(),SCREEN_CENTER_X), fCenterY ); - - if( m_pNoteField && !IsOniDead() ) - { - float fOriginalY = m_pNoteField->GetY(); - - float fTiltDegrees = SCALE(fTilt,-1.f,+1.f,+30,-30) * (bReverse?-1:1); - - float fZoom = SCALE( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_MINI], 0.f, 1.f, 1.f, 0.5f ); - if( fTilt > 0 ) - fZoom *= SCALE( fTilt, 0.f, 1.f, 1.f, 0.9f ); - else - fZoom *= SCALE( fTilt, 0.f, -1.f, 1.f, 0.9f ); - - float fYOffset; - if( fTilt > 0 ) - fYOffset = SCALE( fTilt, 0.f, 1.f, 0.f, -45.f ) * (bReverse?-1:1); - else - fYOffset = SCALE( fTilt, 0.f, -1.f, 0.f, -20.f ) * (bReverse?-1:1); - - m_pNoteField->SetY( fOriginalY + fYOffset ); - m_pNoteField->SetZoom( fZoom ); - m_pNoteField->SetRotationX( fTiltDegrees ); - m_pNoteField->Draw(); - - m_pNoteField->SetY( fOriginalY ); - } - - DISPLAY->CameraPopMatrix(); - DISPLAY->PopMatrix(); - - // m_pNoteField->m_sprBoard->GetVisible() - if( !COMBO_UNDER_FIELD && m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind == 0 ) - if( m_sprCombo ) - m_sprCombo->Draw(); - - if( !(bool)TAP_JUDGMENTS_UNDER_FIELD ) - DrawTapJudgments(); - - if( !(bool)HOLD_JUDGMENTS_UNDER_FIELD ) - DrawHoldJudgments(); -} - -void Player::DrawTapJudgments() -{ - if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind > 0 ) - return; - - if( m_sprJudgment ) - m_sprJudgment->Draw(); -} - -void Player::DrawHoldJudgments() -{ - if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind > 0 ) - return; - - for( int c=0; cDraw(); -} - - -void Player::ChangeLife( TapNoteScore tns ) -{ - PlayerNumber pn = m_pPlayerState->m_PlayerNumber; - if( m_pLifeMeter ) - m_pLifeMeter->ChangeLife( tns ); - if( m_pCombinedLifeMeter ) - m_pCombinedLifeMeter->ChangeLife( pn, tns ); - - ChangeLifeRecord(); - - switch( tns ) - { - case TNS_None: - case TNS_Miss: - case TNS_CheckpointMiss: - case TNS_HitMine: - ++m_pPlayerState->m_iTapsMissedSinceLastHasteUpdate; - break; - default: - ++m_pPlayerState->m_iTapsHitSinceLastHasteUpdate; - break; - } -} - -void Player::ChangeLife( HoldNoteScore hns, TapNoteScore tns ) -{ - PlayerNumber pn = m_pPlayerState->m_PlayerNumber; - if( m_pLifeMeter ) - m_pLifeMeter->ChangeLife( hns, tns ); - if( m_pCombinedLifeMeter ) - m_pCombinedLifeMeter->ChangeLife( pn, hns, tns ); - - ChangeLifeRecord(); -} - -void Player::ChangeLifeRecord() -{ - PlayerNumber pn = m_pPlayerState->m_PlayerNumber; - float fLife = -1; - if( m_pLifeMeter ) - { - fLife = m_pLifeMeter->GetLife(); - } - else if( m_pCombinedLifeMeter ) - { - fLife = GAMESTATE->m_fTugLifePercentP1; - if( pn == PLAYER_2 ) - fLife = 1.0f - fLife; - } - if( fLife != -1 ) - if( m_pPlayerStageStats ) - m_pPlayerStageStats->SetLifeRecordAt( fLife, STATSMAN->m_CurStageStats.m_fStepsSeconds ); -} - -int Player::GetClosestNoteDirectional( int col, int iStartRow, int iEndRow, bool bAllowGraded, bool bForward ) const -{ - NoteData::const_iterator begin, end; - m_NoteData.GetTapNoteRange( col, iStartRow, iEndRow, begin, end ); - - if( !bForward ) - swap( begin, end ); - - while( begin != end ) - { - if( !bForward ) - --begin; - - // Is this the row we want? - do { - const TapNote &tn = begin->second; - if (!m_Timing->IsJudgableAtRow( begin->first )) - break; - // unsure if autoKeysounds should be excluded. -Wolfman2000 - if( tn.type == TapNote::empty || tn.type == TapNote::autoKeysound ) - break; - if( !bAllowGraded && tn.result.tns != TNS_None ) - break; - - return begin->first; - } while(0); - - if( bForward ) - ++begin; - } - - return -1; -} - -// Find the closest note to fBeat. -int Player::GetClosestNote( int col, int iNoteRow, int iMaxRowsAhead, int iMaxRowsBehind, bool bAllowGraded ) const -{ - // Start at iIndexStartLookingAt and search outward. - int iNextIndex = GetClosestNoteDirectional( col, iNoteRow, iNoteRow+iMaxRowsAhead, bAllowGraded, true ); - int iPrevIndex = GetClosestNoteDirectional( col, iNoteRow-iMaxRowsBehind, iNoteRow, bAllowGraded, false ); - - if( iNextIndex == -1 && iPrevIndex == -1 ) - return -1; - if( iNextIndex == -1 ) - return iPrevIndex; - if( iPrevIndex == -1 ) - return iNextIndex; - - // Get the current time, previous time, and next time. - float fNoteTime = m_pPlayerState->m_Position.m_fMusicSeconds ; - float fNextTime = m_Timing->GetElapsedTimeFromBeat(NoteRowToBeat(iNextIndex)); - float fPrevTime = m_Timing->GetElapsedTimeFromBeat(NoteRowToBeat(iPrevIndex)); - - /* Figure out which row is closer. */ - if( fabsf(fNoteTime-fNextTime) > fabsf(fNoteTime-fPrevTime) ) - return iPrevIndex; - else - return iNextIndex; -} - -int Player::GetClosestNonEmptyRowDirectional( int iStartRow, int iEndRow, bool /* bAllowGraded */, bool bForward ) const -{ - if( bForward ) - { - NoteData::all_tracks_iterator iter = m_NoteData.GetTapNoteRangeAllTracks( iStartRow, iEndRow ); - - while( !iter.IsAtEnd() ) - { - if( NoteDataWithScoring::IsRowCompletelyJudged(m_NoteData, iter.Row()) ) - { - ++iter; - continue; - } - if (!m_Timing->IsJudgableAtRow(iter.Row())) - { - ++iter; - continue; - } - return iter.Row(); - } - } - else - { - NoteData::all_tracks_reverse_iterator iter = m_NoteData.GetTapNoteRangeAllTracksReverse( iStartRow, iEndRow ); - - while( !iter.IsAtEnd() ) - { - if( NoteDataWithScoring::IsRowCompletelyJudged(m_NoteData, iter.Row()) ) - { - ++iter; - continue; - } - return iter.Row(); - } - } - - return -1; -} - -// Find the closest note to fBeat. -int Player::GetClosestNonEmptyRow( int iNoteRow, int iMaxRowsAhead, int iMaxRowsBehind, bool bAllowGraded ) const -{ - // Start at iIndexStartLookingAt and search outward. - int iNextRow = GetClosestNonEmptyRowDirectional( iNoteRow, iNoteRow+iMaxRowsAhead, bAllowGraded, true ); - int iPrevRow = GetClosestNonEmptyRowDirectional( iNoteRow-iMaxRowsBehind, iNoteRow, bAllowGraded, false ); - - if( iNextRow == -1 && iPrevRow == -1 ) - return -1; - if( iNextRow == -1 ) - return iPrevRow; - if( iPrevRow == -1 ) - return iNextRow; - - // Get the current time, previous time, and next time. - float fNoteTime = m_pPlayerState->m_Position.m_fMusicSeconds; - float fNextTime = m_Timing->GetElapsedTimeFromBeat(NoteRowToBeat(iNextRow)); - float fPrevTime = m_Timing->GetElapsedTimeFromBeat(NoteRowToBeat(iPrevRow)); - - /* Figure out which row is closer. */ - if( fabsf(fNoteTime-fNextTime) > fabsf(fNoteTime-fPrevTime) ) - return iPrevRow; - else - return iNextRow; -} - -bool Player::IsOniDead() const -{ - // If we're playing on oni and we've died, do nothing. - return GAMESTATE->m_SongOptions.GetCurrent().m_LifeType == SongOptions::LIFE_BATTERY && m_pPlayerStageStats && m_pPlayerStageStats->m_bFailed; -} - -void Player::Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease ) -{ - if( IsOniDead() ) - return; - - DEBUG_ASSERT_M( col >= 0 && col <= m_NoteData.GetNumTracks(), ssprintf("%i, %i", col, m_NoteData.GetNumTracks()) ); - - m_vbFretIsDown[ col ] = !bRelease; - - - // Handle changing fret during a strum - if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 ) - { - LOG->Trace( "StrumTry" ); - StepStrumHopo( col, row, tm, bHeld, bRelease, ButtonType_StrumFretsChanged ); - } - - // Handle hammer-ons and pull-offs - const float fPositionSeconds = m_pPlayerState->m_Position.m_fMusicSeconds - tm.Ago(); - int iHopoCol = -1; - bool bDoHopo = - m_pPlayerState->m_fLastHopoNoteMusicSeconds != -1 && - fPositionSeconds <= m_pPlayerState->m_fLastHopoNoteMusicSeconds + g_fTimingWindowHopo; - if( bDoHopo ) - { - // do a Hopo: - // - on pressed fret is no higher fret is held - // - on next lowest held fret when the highest held fret is released - bool bHigherFretIsDown = false; - for( int i=col+1; i=0; i-- ) - { - if( m_vbFretIsDown[i] ) - { - iHopoCol = i; - break; - } - } - if( iHopoCol == -1 ) - bDoHopo = false; - } - } - - if( bDoHopo ) - Hopo( iHopoCol, row, tm, bHeld, bRelease ); - - // Check if this fret breaks all active holds. - if( !bRelease ) - { - const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat; - const int iSongRow = BeatToNoteRow( fSongBeat ); - - int iMaxHoldCol = -1; - int iNumColsHeld = 0; - - // Score all active holds to NotHeld - for( int iTrack=0; iTracksecond; - if( tn.HoldResult.bActive ) - { - iMaxHoldCol = iTrack; - iNumColsHeld++; - } - } - } - - // Any frets to the right of an active hold will break the hold. - if( col > iMaxHoldCol || iNumColsHeld >= 2 ) - ScoreAllActiveHoldsLetGo(); - } -} - - -void Player::Strum( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease ) -{ - if( bRelease ) - return; - - if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 ) - { - DoStrumMiss(); - } - - m_pPlayerState->m_fLastStrumMusicSeconds = m_pPlayerState->m_Position.m_fMusicSeconds; - - StepStrumHopo( col, row, tm, bHeld, bRelease, ButtonType_StrumFretsChanged ); -} - -void Player::DoTapScoreNone() -{ - Message msg( "ScoreNone" ); - MESSAGEMAN->Broadcast( msg ); - - const int iOldCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0; - const int iOldMissCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurMissCombo : 0; - - /* The only real way to tell if a mine has been scored is if it has disappeared - * but this only works for hit mines so update the scores for avoided mines here. */ - if( m_pPrimaryScoreKeeper ) - m_pPrimaryScoreKeeper->HandleTapScoreNone(); - if( m_pSecondaryScoreKeeper ) - m_pSecondaryScoreKeeper->HandleTapScoreNone(); - - SendComboMessages( iOldCombo, iOldMissCombo ); - - - if( m_pLifeMeter ) - m_pLifeMeter->HandleTapScoreNone(); - // TODO: Remove use of PlayerNumber - PlayerNumber pn = PLAYER_INVALID; - if( m_pCombinedLifeMeter ) - m_pCombinedLifeMeter->HandleTapScoreNone( pn ); - - if( PENALIZE_TAP_SCORE_NONE ) - { - SetJudgment( TNS_Miss, -1, 0 ); - // the ScoreKeeper will subtract points later. - } -} - -void Player::DoStrumMiss() -{ - m_pPlayerState->m_fLastStrumMusicSeconds = -1; - DoTapScoreNone(); - - ScoreAllActiveHoldsLetGo(); -} - -void Player::ScoreAllActiveHoldsLetGo() -{ - if( PENALIZE_TAP_SCORE_NONE ) - { - const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat; - const int iSongRow = BeatToNoteRow( fSongBeat ); - - // Score all active holds to NotHeld - for( int iTrack=0; iTracksecond; - if( tn.HoldResult.bActive ) - { - tn.HoldResult.hns = HNS_LetGo; - tn.HoldResult.fLife = 0; - - SetHoldJudgment( tn.result.tns, tn.HoldResult.hns, iTrack ); - HandleHoldScore( tn ); - } - } - } - } -} - -void Player::PlayKeysound( const TapNote &tn, TapNoteScore score ) -{ - // tap note must have keysound - if( tn.iKeysoundIndex >= 0 && tn.iKeysoundIndex < (int) m_vKeysounds.size() ) - { - // handle a case for hold notes - if( tn.type == TapNote::hold_head ) - { - // if the hold is not already held - if( tn.HoldResult.hns == HNS_None ) - { - // if the hold is already activated - TapNoteScore tns = tn.result.tns; - if( tns != TNS_None && tns != TNS_Miss && score == TNS_None ) - { - // the sound must also be already playing - if( m_vKeysounds[tn.iKeysoundIndex].IsPlaying() ) - { - // if all of these conditions are met, don't play the sound. - return; - } - } - } - } - m_vKeysounds[tn.iKeysoundIndex].Play(); - Preference *pVolume = Preference::GetPreferenceByName("SoundVolume"); - float fVol = pVolume->Get(); - m_vKeysounds[tn.iKeysoundIndex].SetProperty ("Volume", fVol); - } -} - -void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease, Player::ButtonType pbt ) -{ - if( IsOniDead() ) - return; - - // Do everything that depends on a RageTimer here; - // set your breakpoints somewhere after this block. - const float fLastBeatUpdate = m_pPlayerState->m_Position.m_LastBeatUpdate.Ago(); - const float fPositionSeconds = m_pPlayerState->m_Position.m_fMusicSeconds - tm.Ago(); - const float fTimeSinceStep = tm.Ago(); - - switch( pbt ) - { - DEFAULT_FAIL(pbt); - case ButtonType_Step: - break; - case ButtonType_StrumFretsChanged: - case ButtonType_Hopo: - // releasing should hit regular notes, not lifts - bRelease = false; - break; - } - - float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat; - - if( GAMESTATE->m_pCurSong ) - { - fSongBeat = GAMESTATE->m_pCurSong->m_SongTiming.GetBeatFromElapsedTime( fPositionSeconds ); - - if( GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber] ) - fSongBeat = m_Timing->GetBeatFromElapsedTime( fPositionSeconds ); - } - - const int iSongRow = row == -1 ? BeatToNoteRow( fSongBeat ) : row; - - if( col != -1 && !bRelease ) - { - // Update roll life - // Let's not check the whole array every time. - // Instead, only check 1 beat back. Even 1 is overkill. - // Just update the life here and let Update judge the roll. - const int iStartCheckingAt = max( 0, iSongRow-BeatToNoteRow(1) ); - NoteData::TrackMap::iterator begin, end; - m_NoteData.GetTapNoteRangeInclusive( col, iStartCheckingAt, iSongRow+1, begin, end ); - for( ; begin != end; ++begin ) - { - TapNote &tn = begin->second; - if( tn.type != TapNote::hold_head ) - continue; - - switch( tn.subType ) - { - DEFAULT_FAIL( tn.subType ); - case TapNote::hold_head_hold: - continue; - case TapNote::hold_head_roll: - break; - } - - const int iRow = begin->first; - - HoldNoteScore hns = tn.HoldResult.hns; - if( hns != HNS_None ) // if this HoldNote already has a result - continue; // we don't need to update the logic for this one - - // if they got a bad score or haven't stepped on the corresponding tap yet - const TapNoteScore tns = tn.result.tns; - bool bInitiatedNote = true; - if( REQUIRE_STEP_ON_HOLD_HEADS ) - bInitiatedNote = tns != TNS_None && tns != TNS_Miss; // did they step on the start? - const int iEndRow = iRow + tn.iDuration; - - if( bInitiatedNote && tn.HoldResult.fLife != 0 ) - { - /* This hold note is not judged and we stepped on its head. Update iLastHeldRow. - * Do this even if we're a little beyond the end of the hold note, to make sure - * iLastHeldRow is clamped to iEndRow if the hold note is held all the way. */ - //LOG->Trace("setting iLastHeldRow to min of iSongRow (%i) and iEndRow (%i)",iSongRow,iEndRow); - tn.HoldResult.iLastHeldRow = min( iSongRow, iEndRow ); - } - - // If the song beat is in the range of this hold: - if( iRow <= iSongRow && iRow <= iEndRow ) - { - if( bInitiatedNote ) - { - // Increase life - tn.HoldResult.fLife = 1; - - if( ROLL_BODY_INCREMENTS_COMBO ) - { - // increment combo - const int iOldCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0; - const int iOldMissCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurMissCombo : 0; - - if( m_pPlayerStageStats ) - { - m_pPlayerStageStats->m_iCurCombo++; - m_pPlayerStageStats->m_iCurMissCombo = 0; - } - - SendComboMessages( iOldCombo, iOldMissCombo ); - if( m_pPlayerStageStats ) - SetCombo( m_pPlayerStageStats->m_iCurCombo, m_pPlayerStageStats->m_iCurMissCombo ); - - bool bBright = m_pPlayerStageStats && m_pPlayerStageStats->m_iCurCombo>(int)BRIGHT_GHOST_COMBO_THRESHOLD; - if( m_pNoteField ) - m_pNoteField->DidHoldNote( col, HNS_Held, bBright ); - } - } - break; - } - } - } - - // Count calories for this step, unless we're being called because a button - // is held over a mine or being released. - // TODO: Move calorie counting into a ScoreKeeper? - if( m_pPlayerStageStats && m_pPlayerState && !bHeld && !bRelease ) - { - // TODO: remove use of PlayerNumber - PlayerNumber pn = m_pPlayerState->m_PlayerNumber; - Profile *pProfile = PROFILEMAN->GetProfile( pn ); - - int iNumTracksHeld = 0; - for( int t=0; tGetCurrentStyle()->StyleInputToGameInput( t, pn ); - const float fSecsHeld = INPUTMAPPER->GetSecsHeld( GameI ); - if( fSecsHeld > 0 && fSecsHeld < m_fTimingWindowJump ) - iNumTracksHeld++; - } - - float fCals = 0; - switch( iNumTracksHeld ) - { - case 0: - // autoplay is on, or this is a computer player - iNumTracksHeld = 1; - // fall through - default: - { - float fCalsFor100Lbs = SCALE( iNumTracksHeld, 1, 2, 0.023f, 0.077f ); - float fCalsFor200Lbs = SCALE( iNumTracksHeld, 1, 2, 0.041f, 0.133f ); - fCals = SCALE( pProfile->GetCalculatedWeightPounds(), 100.f, 200.f, fCalsFor100Lbs, fCalsFor200Lbs ); - } - break; - } - - m_pPlayerStageStats->m_fCaloriesBurned += fCals; - m_pPlayerStageStats->m_iNumControllerSteps ++; - } - - // Check for step on a TapNote - /* XXX: This seems wrong. If a player steps twice quickly and two notes are - * close together in the same column then it is possible for the two notes - * to be graded out of order. - * Two possible fixes: - * 1. Adjust the fSongBeat (or the resulting note row) backward by - * iStepSearchRows and search forward two iStepSearchRows lengths, - * disallowing graded. This doesn't seem right because if a second note has - * passed, an earlier one should not be graded. - * 2. Clamp the distance searched backward to the previous row graded. - * Either option would fundamentally change the grading of two quick notes - * "jack hammers." Hmm. - */ - const int iStepSearchRows = max( - BeatToNoteRow( m_Timing->GetBeatFromElapsedTime( m_pPlayerState->m_Position.m_fMusicSeconds + StepSearchDistance ) ) - iSongRow, - iSongRow - BeatToNoteRow( m_Timing->GetBeatFromElapsedTime( m_pPlayerState->m_Position.m_fMusicSeconds - StepSearchDistance ) ) - ) + ROWS_PER_BEAT; - int iRowOfOverlappingNoteOrRow = row; - if( row == -1 ) - { - switch( pbt ) - { - DEFAULT_FAIL(pbt); - case ButtonType_StrumFretsChanged: - iRowOfOverlappingNoteOrRow = GetClosestNonEmptyRow( iSongRow, iStepSearchRows, iStepSearchRows, false ); - break; - case ButtonType_Hopo: - case ButtonType_Step: - iRowOfOverlappingNoteOrRow = GetClosestNote( col, iSongRow, iStepSearchRows, iStepSearchRows, false ); - break; - } - } - - // calculate TapNoteScore - TapNoteScore score = TNS_None; - - if( iRowOfOverlappingNoteOrRow != -1 ) - { - // compute the score for this hit - float fNoteOffset = 0.0f; - // we need this later if we are autosyncing - const float fStepBeat = NoteRowToBeat( iRowOfOverlappingNoteOrRow ); - const float fStepSeconds = m_Timing->GetElapsedTimeFromBeat(fStepBeat); - - if( row == -1 ) - { - // We actually stepped on the note this long ago: - //fTimeSinceStep - - /* GAMESTATE->m_fMusicSeconds is the music time as of GAMESTATE->m_LastBeatUpdate. Figure - * out what the music time is as of now. */ - const float fCurrentMusicSeconds = m_pPlayerState->m_Position.m_fMusicSeconds + (fLastBeatUpdate*GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate); - - // ... which means it happened at this point in the music: - const float fMusicSeconds = fCurrentMusicSeconds - fTimeSinceStep * GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; - - // The offset from the actual step in seconds: - fNoteOffset = (fStepSeconds - fMusicSeconds) / GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; // account for music rate - /* - LOG->Trace("step was %.3f ago, music is off by %f: %f vs %f, step was %f off", - fTimeSinceStep, GAMESTATE->m_LastBeatUpdate.Ago()/GAMESTATE->m_SongOptions.m_fMusicRate, - fStepSeconds, fMusicSeconds, fNoteOffset ); - */ - } - - const float fSecondsFromExact = fabsf( fNoteOffset ); - - TapNote tnDummy = TAP_ORIGINAL_TAP; - TapNote *pTN = NULL; - switch( pbt ) - { - DEFAULT_FAIL(pbt); - case ButtonType_StrumFretsChanged: - pTN = &tnDummy; - break; - case ButtonType_Hopo: - case ButtonType_Step: - NoteData::iterator iter = m_NoteData.FindTapNote( col, iRowOfOverlappingNoteOrRow ); - DEBUG_ASSERT( iter!= m_NoteData.end(col) ); - pTN = &iter->second; - break; - } - - switch( m_pPlayerState->m_PlayerController ) - { - case PC_HUMAN: - switch( pTN->type ) - { - case TapNote::mine: - // Stepped too close to mine? - if( !bRelease && ( REQUIRE_STEP_ON_MINES == !bHeld ) && - fSecondsFromExact <= GetWindowSeconds(TW_Mine) && - m_Timing->IsJudgableAtRow(iSongRow)) - score = TNS_HitMine; - break; - case TapNote::attack: - if( !bRelease && fSecondsFromExact <= GetWindowSeconds(TW_Attack) && !pTN->result.bHidden ) - score = AllowW1() ? TNS_W1 : TNS_W2; // sentinel - break; - case TapNote::hold_head: - // oh wow, this was causing the trigger before the hold heads - // bug. (It was fNoteOffset > 0.f before) -DaisuMaster - if( !REQUIRE_STEP_ON_HOLD_HEADS && ( fNoteOffset <= GetWindowSeconds( TW_W5 ) && GetWindowSeconds( TW_W5 ) != 0 ) ) - { - score = TNS_W1; - break; - } - // Fall through to default. - default: - if( (pTN->type == TapNote::lift) == bRelease ) - { - if( fSecondsFromExact <= GetWindowSeconds(TW_W1) ) score = TNS_W1; - else if( fSecondsFromExact <= GetWindowSeconds(TW_W2) ) score = TNS_W2; - else if( fSecondsFromExact <= GetWindowSeconds(TW_W3) ) score = TNS_W3; - else if( fSecondsFromExact <= GetWindowSeconds(TW_W4) ) score = TNS_W4; - else if( fSecondsFromExact <= GetWindowSeconds(TW_W5) ) score = TNS_W5; - } - break; - } - break; - - case PC_CPU: - case PC_AUTOPLAY: - score = PlayerAI::GetTapNoteScore( m_pPlayerState ); - - /* XXX: This doesn't make sense. - * Step should only be called in autoplay for hit notes. */ -#if 0 - // GetTapNoteScore always returns TNS_W1 in autoplay. - // If the step is far away, don't judge it. - if( m_pPlayerState->m_PlayerController == PC_AUTOPLAY && - fSecondsFromExact > GetWindowSeconds(TW_W5) ) - { - score = TNS_None; - break; - } -#endif - - // TRICKY: We're asking the AI to judge mines. Consider TNS_W4 and - // below as "mine was hit" and everything else as "mine was avoided" - if( pTN->type == TapNote::mine ) - { - // The CPU hits a lot of mines. Only consider hitting the - // first mine for a row. We know we're the first mine if - // there are are no mines to the left of us. - for( int t=0; t= get_to_avoid ) - return; // avoided - else - score = TNS_HitMine; - } - - if( pTN->type == TapNote::attack && score > TNS_W4 ) - score = TNS_W2; // sentinel - - /* AI will generate misses here. Don't handle a miss like a regular - * note because we want the judgment animation to appear delayed. - * Instead, return early if AI generated a miss, and let - * UpdateTapNotesMissedOlderThan() detect and handle the misses. */ - if( score == TNS_Miss ) - return; - - // Put some small, random amount in fNoteOffset so that demonstration - // show a mix of late and early. - Chris (StepMania r15628) - //fNoteOffset = randomf( -0.1f, 0.1f ); - // Since themes may use the offset in a visual graph, the above - // behavior is not the best thing to do. Instead, random numbers - // should be generated based on the TapNoteScore, so that they can - // logically match up with the current timing windows. -aj - { - float fWindowW1 = GetWindowSeconds(TW_W1); - float fWindowW2 = GetWindowSeconds(TW_W2); - float fWindowW3 = GetWindowSeconds(TW_W3); - float fWindowW4 = GetWindowSeconds(TW_W4); - float fWindowW5 = GetWindowSeconds(TW_W5); - - // W1 is the top judgment, there is no overlap. - if( score == TNS_W1 ) - fNoteOffset = randomf(-fWindowW1, fWindowW1); - else - { - // figure out overlap. - float fLowerBound = 0.0f; // negative upper limit - float fUpperBound = 0.0f; // positive lower limit - float fCompareWindow = 0.0f; // filled in here: - if( score == TNS_W2 ) - { - fLowerBound = -fWindowW1; - fUpperBound = fWindowW1; - fCompareWindow = fWindowW2; - } - else if( score == TNS_W3 ) - { - fLowerBound = -fWindowW2; - fUpperBound = fWindowW2; - fCompareWindow = fWindowW3; - } - else if( score == TNS_W4 ) - { - fLowerBound = -fWindowW3; - fUpperBound = fWindowW3; - fCompareWindow = fWindowW4; - } - else if( score == TNS_W5 ) - { - fLowerBound = -fWindowW4; - fUpperBound = fWindowW4; - fCompareWindow = fWindowW5; - } - float f1 = randomf(-fCompareWindow, fLowerBound); - float f2 = randomf(fUpperBound, fCompareWindow); - - if(randomf() * 100 >= 50) - fNoteOffset = f1; - else - fNoteOffset = f2; - } - } - - break; - - /* - case PC_REPLAY: - // based on where we are, see what grade to get. - score = PlayerAI::GetTapNoteScore( m_pPlayerState ); - // row is the current row, col is current column (track) - fNoteOffset = TapNoteOffset attribute - break; - */ - default: - FAIL_M(ssprintf("Invalid player controller type: %i", m_pPlayerState->m_PlayerController)); - } - - switch( pbt ) - { - DEFAULT_FAIL(pbt); - case ButtonType_StrumFretsChanged: - { - bool bNoteRowMatchesFrets = true; - int iFirstNoteCol = -1; - for( int i=0; iGetCurrentStyle()->m_iColsPerPlayer; i++ ) - { - const TapNote &tn = m_NoteData.GetTapNote( i, iRowOfOverlappingNoteOrRow ); - bool bIsNote = (tn.type != TapNote::empty); - if( iFirstNoteCol == -1 && bIsNote ) - iFirstNoteCol = i; - - // Extra notes to the left (higher up on the string) can be held without penalty. It's necessary to hold - // the extra frets or pull-offs. - if( iFirstNoteCol == -1 ) - continue; - - bool bNoteMatchesFret = m_vbFretIsDown[i] == bIsNote; - if( !bNoteMatchesFret ) - { - bNoteRowMatchesFrets = false; - break; - } - } - ASSERT( iFirstNoteCol != -1 ); - if( !bNoteRowMatchesFrets ) - { - score = TNS_None; - } - else - { - int iLastNoteCol = -1; - for( int i=GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer-1; i>=0; i-- ) - { - const TapNote &tn = m_NoteData.GetTapNote( i, iRowOfOverlappingNoteOrRow ); - bool bIsNote = (tn.type != TapNote::empty); - if( bIsNote ) - { - iLastNoteCol = i; - break; - } - } - - m_pPlayerState->m_fLastHopoNoteMusicSeconds = fStepSeconds; - m_pPlayerState->m_iLastHopoNoteCol = iLastNoteCol; - } - } - break; - case ButtonType_Hopo: - { - // only can hopo on a row with one note - if( m_NoteData.GetNumTapNotesInRow(iRowOfOverlappingNoteOrRow) != 1 ) - { - score = TNS_None; - break; - } - - // con't hopo on the same note 2x in a row - if( col == m_pPlayerState->m_iLastHopoNoteCol ) - { - score = TNS_None; - break; - } - - const TapNote &tn = m_NoteData.GetTapNote( col, iRowOfOverlappingNoteOrRow ); - ASSERT( tn.type != TapNote::empty ); - - int iRowsAgoLastNote = 100000; // TODO: find more reasonable value based on HOPO_CHAIN_SECONDS? - NoteData::all_tracks_reverse_iterator iter = m_NoteData.GetTapNoteRangeAllTracksReverse( iRowsAgoLastNote-iRowsAgoLastNote, iRowOfOverlappingNoteOrRow-1 ); - ASSERT( !iter.IsAtEnd() ); // there must have been a note that started the hopo - if( !NoteDataWithScoring::IsRowCompletelyJudged(m_NoteData, iter.Row()) ) - { - score = TNS_None; - break; - } - - const TapNoteResult &lastTNR = NoteDataWithScoring::LastTapNoteWithResult( m_NoteData, iter.Row() ).result; - if( lastTNR.tns <= TNS_Miss ) - { - score = TNS_None; - break; - } - - m_pPlayerState->m_fLastHopoNoteMusicSeconds = fStepSeconds; - m_pPlayerState->m_iLastHopoNoteCol = col; - } - break; - case ButtonType_Step: - break; - } - - // handle attack notes - if( pTN->type == TapNote::attack && score == TNS_W2 ) - { - score = TNS_None; // don't score this as anything - - m_soundAttackLaunch.Play(); - - // put attack in effect - Attack attack( - ATTACK_LEVEL_1, - -1, // now - pTN->fAttackDurationSeconds, - pTN->sAttackModifiers, - true, - false - ); - - // TODO: Remove use of PlayerNumber - PlayerNumber pnToAttack = OPPOSITE_PLAYER[m_pPlayerState->m_PlayerNumber]; - PlayerState *pPlayerStateToAttack = GAMESTATE->m_pPlayerState[pnToAttack]; - pPlayerStateToAttack->LaunchAttack( attack ); - - // remove all TapAttacks on this row - for( int t=0; tm_PlayerController == PC_HUMAN && score >= TNS_W3 ) - AdjustSync::HandleAutosync( fNoteOffset, fStepSeconds ); - - // Do game-specific and mode-specific score mapping. - score = GAMESTATE->GetCurrentGame()->MapTapNoteScore( score ); - if( score == TNS_W1 && !GAMESTATE->ShowW1() ) - score = TNS_W2; - - - if( score != TNS_None ) - { - switch( pbt ) - { - DEFAULT_FAIL(pbt); - case ButtonType_StrumFretsChanged: - for( int t=0; tresult.tns = score; - pTN->result.fTapNoteOffset = -fNoteOffset; - break; - } - } - - m_LastTapNoteScore = score; - if( GAMESTATE->GetCurrentGame()->m_bCountNotesSeparately ) - { - if( pTN->type != TapNote::mine ) - { - const bool bBlind = (m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind != 0); - // XXX: This is the wrong combo for shared players. - // STATSMAN->m_CurStageStats.m_Player[pn] might work, but could be wrong. - const bool bBright = ( m_pPlayerStageStats && m_pPlayerStageStats->m_iCurCombo > int(BRIGHT_GHOST_COMBO_THRESHOLD) ) || bBlind; - if( m_pNoteField ) - m_pNoteField->DidTapNote( col, bBlind? TNS_W1:score, bBright ); - if( score >= TNS_W3 || bBlind ) - HideNote( col, iRowOfOverlappingNoteOrRow ); - } - } - else if( NoteDataWithScoring::IsRowCompletelyJudged(m_NoteData, iRowOfOverlappingNoteOrRow) ) - { - FlashGhostRow( iRowOfOverlappingNoteOrRow ); - } - } - - // check for hopo end - if( score <= TNS_Miss ) - { - m_pPlayerState->ClearHopoState(); - } - - // check for strum end - if( score != TNS_None ) - { - switch( pbt ) - { - DEFAULT_FAIL(pbt); - case ButtonType_Step: - break; - case ButtonType_StrumFretsChanged: - m_pPlayerState->m_fLastStrumMusicSeconds = -1; - break; - case ButtonType_Hopo: - break; - } - } - - if( score == TNS_None ) - { - switch( pbt ) - { - DEFAULT_FAIL(pbt); - case ButtonType_Step: - DoTapScoreNone(); - break; - case ButtonType_StrumFretsChanged: - case ButtonType_Hopo: - break; - } - - } - - if( !bRelease ) - { - /* Search for keyed sounds separately. Play the nearest note. */ - /* XXX: This isn't quite right. As per the above XXX for iRowOfOverlappingNote, if iRowOfOverlappingNote - * is set to a previous note, the keysound could have changed and this would cause the wrong one to play, - * in essence playing two sounds in the opposite order. Maybe this should always perform the search. Still, - * even that doesn't seem quite right since it would then play the same (new) keysound twice which would - * sound wrong even though the notes were judged as being correct, above. Fixing the above problem would - * fix this one as well. */ - int iHeadRow; - if( iRowOfOverlappingNoteOrRow != -1 && score != TNS_None ) - { - // just pressing a note, use that row. - // in other words, iRowOfOverlappingNoteOrRow = iRowOfOverlappingNoteOrRow - } - else if ( m_NoteData.IsHoldNoteAtRow( col, iSongRow, &iHeadRow ) ) - { - // stepping on a hold, use it! - iRowOfOverlappingNoteOrRow = iHeadRow; - } - else - { - // or else find the closest note. - iRowOfOverlappingNoteOrRow = GetClosestNote( col, iSongRow, MAX_NOTE_ROW, MAX_NOTE_ROW, true ); - } - if( iRowOfOverlappingNoteOrRow != -1 ) - { - switch( pbt ) - { - DEFAULT_FAIL(pbt); - case ButtonType_StrumFretsChanged: - for( int i=0; iStep( iLastFret, score ); - } - break; - case ButtonType_Step: - m_pNoteField->Step( col, score ); - break; - case ButtonType_Hopo: - // no animation - break; - } - } - Message msg( "Step" ); - msg.SetParam( "PlayerNumber", m_pPlayerState->m_PlayerNumber ); - msg.SetParam( "MultiPlayer", m_pPlayerState->m_mp ); - msg.SetParam( "Column", col ); - MESSAGEMAN->Broadcast( msg ); - } -} - -void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds ) -{ - //LOG->Trace( "Steps::UpdateTapNotesMissedOlderThan(%f)", fMissIfOlderThanThisBeat ); - int iMissIfOlderThanThisRow; - const float fEarliestTime = m_pPlayerState->m_Position.m_fMusicSeconds - fMissIfOlderThanSeconds; - { - bool bFreeze, bDelay; - float fMissIfOlderThanThisBeat; - float fThrowAway; - int iWarpBeginRow; - float fWarpLength; - m_Timing->GetBeatAndBPSFromElapsedTime( fEarliestTime, fMissIfOlderThanThisBeat, fThrowAway, bFreeze, bDelay, iWarpBeginRow, fWarpLength ); - - iMissIfOlderThanThisRow = BeatToNoteRow( fMissIfOlderThanThisBeat ); - if( bFreeze || bDelay ) - { - /* If there is a freeze on iMissIfOlderThanThisIndex, include this index too. - * Otherwise we won't show misses for tap notes on freezes until the - * freeze finishes. */ - if( !bDelay ) - iMissIfOlderThanThisRow++; - } - } - - NoteData::all_tracks_iterator &iter = *m_pIterNeedsTapJudging; - - for( ; !iter.IsAtEnd() && iter.Row() < iMissIfOlderThanThisRow; ++iter ) - { - TapNote &tn = *iter; - - if( !NeedsTapJudging(tn) ) - continue; - - // Ignore all notes in WarpSegments or FakeSegments. - if (!m_Timing->IsJudgableAtRow(iter.Row())) - continue; - - if( tn.type == TapNote::mine ) - { - tn.result.tns = TNS_AvoidMine; - - /* The only real way to tell if a mine has been scored is if it has disappeared - * but this only works for hit mines so update the scores for avoided mines here. */ - if( m_pPrimaryScoreKeeper ) - m_pPrimaryScoreKeeper->HandleTapScore( tn ); - if( m_pSecondaryScoreKeeper ) - m_pSecondaryScoreKeeper->HandleTapScore( tn ); - } - else - { - tn.result.tns = TNS_Miss; - } - } -} - -void Player::UpdateJudgedRows() -{ - const int iEndRow = BeatToNoteRow( m_pPlayerState->m_Position.m_fSongBeat ); - bool bAllJudged = true; - const bool bSeparately = GAMESTATE->GetCurrentGame()->m_bCountNotesSeparately; - - { - NoteData::all_tracks_iterator iter = *m_pIterUnjudgedRows; - int iLastSeenRow = -1; - for( ; !iter.IsAtEnd() && iter.Row() <= iEndRow; ++iter ) - { - int iRow = iter.Row(); - - // Do not judge arrows in WarpSegments or FakeSegments - if (!m_Timing->IsJudgableAtRow(iRow)) - continue; - - if( iLastSeenRow != iRow ) - { - iLastSeenRow = iRow; - - // crossed a nonempty row - if( !NoteDataWithScoring::IsRowCompletelyJudged(m_NoteData, iRow) ) - { - bAllJudged = false; - continue; - } - if( bAllJudged ) - *m_pIterUnjudgedRows = iter; - if( m_pJudgedRows->JudgeRow(iRow) ) - continue; - const TapNoteResult &lastTNR = NoteDataWithScoring::LastTapNoteWithResult( m_NoteData, iRow ).result; - - if( lastTNR.tns < TNS_Miss ) - continue; - if( bSeparately ) - { - for( int iTrack = 0; iTrack < m_NoteData.GetNumTracks(); ++iTrack ) - { - const TapNote &tn = m_NoteData.GetTapNote( iTrack, iRow ); - if (tn.type == TapNote::empty || - tn.type == TapNote::mine || - tn.type == TapNote::autoKeysound) continue; - SetJudgment( tn.result.tns, iTrack, tn.result.fTapNoteOffset ); - } - } - else - { - SetJudgment( lastTNR.tns, m_NoteData.GetFirstTrackWithTapOrHoldHead(iRow), lastTNR.fTapNoteOffset ); - } - HandleTapRowScore( iRow ); - } - } - } - - // handle mines. - { - bAllJudged = true; - set setSounds; - NoteData::all_tracks_iterator iter = *m_pIterUnjudgedMineRows; // copy - int iLastSeenRow = -1; - for( ; !iter.IsAtEnd() && iter.Row() <= iEndRow; ++iter ) - { - int iRow = iter.Row(); - - // Do not worry about mines in WarpSegments or FakeSegments - if (!m_Timing->IsJudgableAtRow(iRow)) - continue; - - TapNote &tn = *iter; - - if( iRow != iLastSeenRow ) - { - iLastSeenRow = iRow; - if( bAllJudged ) - *m_pIterUnjudgedMineRows = iter; - } - - bool bMineNotHidden = tn.type == TapNote::mine && !tn.result.bHidden; - if( !bMineNotHidden ) - continue; - - switch( tn.result.tns ) - { - DEFAULT_FAIL( tn.result.tns ); - case TNS_None: - bAllJudged = false; - continue; - case TNS_AvoidMine: - SetMineJudgment( tn.result.tns ); - continue; - case TNS_HitMine: - SetMineJudgment( tn.result.tns ); - break; - } - if( m_pNoteField ) - m_pNoteField->DidTapNote( iter.Track(), tn.result.tns, false ); - - if( tn.iKeysoundIndex >= 0 && tn.iKeysoundIndex < (int) m_vKeysounds.size() ) - setSounds.insert( &m_vKeysounds[tn.iKeysoundIndex] ); - else if( g_bEnableMineSoundPlayback ) - setSounds.insert( &m_soundMine ); - - /* Attack Mines: - * Only difference is these launch an attack rather than affecting - * the lifebar. All the other mine impacts (score, dance points, - * etc.) are still applied. */ - if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_bTransforms[PlayerOptions::TRANSFORM_ATTACKMINES] ) - { - const float fAttackRunTime = ATTACK_RUN_TIME_MINE; - - Attack attMineAttack; - attMineAttack.sModifiers = ApplyRandomAttack(); - attMineAttack.fStartSecond = ATTACK_STARTS_NOW; - attMineAttack.fSecsRemaining = fAttackRunTime; - - m_pPlayerState->LaunchAttack( attMineAttack ); - } - else - ChangeLife( tn.result.tns ); - - if( m_pScoreDisplay ) - m_pScoreDisplay->OnJudgment( tn.result.tns ); - if( m_pSecondaryScoreDisplay ) - m_pSecondaryScoreDisplay->OnJudgment( tn.result.tns ); - - // Make sure hit mines affect the dance points. - if( m_pPrimaryScoreKeeper ) - m_pPrimaryScoreKeeper->HandleTapScore( tn ); - if( m_pSecondaryScoreKeeper ) - m_pSecondaryScoreKeeper->HandleTapScore( tn ); - tn.result.bHidden = true; - } - - FOREACHS( RageSound *, setSounds, s ) - { - // Only play one copy of each mine sound at a time per player. - (*s)->Stop(); - (*s)->Play(); - } - } -} - -void Player::FlashGhostRow( int iRow ) -{ - TapNoteScore lastTNS = NoteDataWithScoring::LastTapNoteWithResult( m_NoteData, iRow ).result.tns; - const bool bBlind = (m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind != 0); - const bool bBright = ( m_pPlayerStageStats && m_pPlayerStageStats->m_iCurCombo > int(BRIGHT_GHOST_COMBO_THRESHOLD) ) || bBlind; - - for( int iTrack = 0; iTrack < m_NoteData.GetNumTracks(); ++iTrack ) - { - const TapNote &tn = m_NoteData.GetTapNote( iTrack, iRow ); - - if( tn.type == TapNote::empty || tn.type == TapNote::mine || tn.type == TapNote::fake ) - continue; - if( m_pNoteField ) - m_pNoteField->DidTapNote( iTrack, lastTNS, bBright ); - if( lastTNS >= TNS_W3 || bBlind ) - HideNote( iTrack, iRow ); - } -} - -void Player::CrossedRows( int iLastRowCrossed, const RageTimer &now ) -{ - //LOG->Trace( "Player::CrossedRows %d %d", iFirstRowCrossed, iLastRowCrossed ); - - NoteData::all_tracks_iterator &iter = *m_pIterUncrossedRows; - int iLastSeenRow = -1; - for( ; !iter.IsAtEnd() && iter.Row() <= iLastRowCrossed; ++iter ) - { - // Apply InitialHoldLife. - TapNote &tn = *iter; - int iRow = iter.Row(); - int iTrack = iter.Track(); - switch( tn.type ) - { - case TapNote::hold_head: - { - tn.HoldResult.fLife = INITIAL_HOLD_LIFE; - if( !REQUIRE_STEP_ON_HOLD_HEADS ) - { - PlayerNumber pn = m_pPlayerState->m_PlayerNumber; - GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( iTrack, pn ); - if( PREFSMAN->m_fPadStickSeconds > 0.f ) - { - float fSecsHeld = INPUTMAPPER->GetSecsHeld( GameI, m_pPlayerState->m_mp ); - if( fSecsHeld >= PREFSMAN->m_fPadStickSeconds ) - Step( iTrack, -1, now - PREFSMAN->m_fPadStickSeconds, true, false ); - } - else if( INPUTMAPPER->IsBeingPressed(GameI, m_pPlayerState->m_mp) ) - { - Step( iTrack, -1, now, true, false ); - } - } - break; - } - case TapNote::mine: - { - // Hold the panel while crossing a mine will cause the mine to explode - // TODO: Remove use of PlayerNumber. - PlayerNumber pn = m_pPlayerState->m_PlayerNumber; - GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( iTrack, pn ); - if( PREFSMAN->m_fPadStickSeconds > 0 ) - { - float fSecsHeld = INPUTMAPPER->GetSecsHeld( GameI, m_pPlayerState->m_mp ); - if( fSecsHeld >= PREFSMAN->m_fPadStickSeconds ) - Step( iTrack, -1, now - PREFSMAN->m_fPadStickSeconds, true, false ); - } - else - { - if( INPUTMAPPER->IsBeingPressed(GameI, m_pPlayerState->m_mp) ) - Step( iTrack, iRow, now, true, false ); - } - break; - } - default: break; - } - - if( iRow != iLastSeenRow ) - { - // crossed a not-empty row - - // If we're doing random vanish, randomise notes on the fly. - if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fAppearances[PlayerOptions::APPEARANCE_RANDOMVANISH]==1 ) - RandomizeNotes( iRow ); - - // check to see if there's a note at the crossed row - if( m_pPlayerState->m_PlayerController != PC_HUMAN ) - { - if (tn.type != TapNote::empty && - tn.type != TapNote::fake && - tn.type != TapNote::autoKeysound && - tn.result.tns == TNS_None && - this->m_Timing->IsJudgableAtRow(iRow) ) - { - Step( iTrack, iRow, now, false, false ); - if( m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) - { - if( m_pPlayerStageStats ) - m_pPlayerStageStats->m_bDisqualified = true; - } - } - } - - // handle autokeysounds here (if not in the editor). - if (!GAMESTATE->m_bInStepEditor) - { - for (int t = 0; t < m_NoteData.GetNumTracks(); ++t) - { - const TapNote &tap = m_NoteData.GetTapNote(t, iRow); - if (tap.type == TapNote::autoKeysound) - { - PlayKeysound(tap, TNS_None); - } - } - } - } - } - - - /* Update hold checkpoints - * - * TODO: Move this to a separate function. */ - if( HOLD_CHECKPOINTS && m_pPlayerState->m_PlayerController != PC_AUTOPLAY ) - { - int iCheckpointFrequencyRows = ROWS_PER_BEAT/2; - if( CHECKPOINTS_USE_TICKCOUNTS ) - { - int tickCurrent = m_Timing->GetTickcountAtRow( iLastRowCrossed ); - // There are some charts that don't want tickcounts involved at all. - iCheckpointFrequencyRows = (tickCurrent > 0 ? ROWS_PER_BEAT / tickCurrent : 0); - } - else if( CHECKPOINTS_USE_TIME_SIGNATURES ) - { - TimeSignatureSegment * tSignature = m_Timing->GetTimeSignatureSegmentAtRow( iLastRowCrossed ); - - // Most songs are in 4/4 time. The frequency for checking tick counts should reflect that. - iCheckpointFrequencyRows = ROWS_PER_BEAT * tSignature->GetDen() / (tSignature->GetNum() * 4); - } - - if( iCheckpointFrequencyRows > 0 ) - { - // "the first row after the start of the range that lands on a beat" - int iFirstCheckpointInRange = ((m_iFirstUncrossedRow+iCheckpointFrequencyRows-1) - /iCheckpointFrequencyRows) * iCheckpointFrequencyRows; - - // "the last row or first row earlier that lands on a beat" - int iLastCheckpointInRange = ((iLastRowCrossed)/iCheckpointFrequencyRows) - * iCheckpointFrequencyRows; - - for( int r = iFirstCheckpointInRange; r <= iLastCheckpointInRange; r += iCheckpointFrequencyRows ) - { - //LOG->Trace( "%d...", r ); - vector viColsWithHold; - int iNumHoldsHeldThisRow = 0; - int iNumHoldsMissedThisRow = 0; - - // start at r-1 so that we consider holds whose end rows are equal to the checkpoint row - NoteData::all_tracks_iterator nIter = m_NoteData.GetTapNoteRangeAllTracks( r-1, r, true ); - for( ; !nIter.IsAtEnd(); ++nIter ) - { - TapNote &tn = *nIter; - if( tn.type != TapNote::hold_head ) - continue; - - int iStartRow = nIter.Row(); - int iEndRow = iStartRow + tn.iDuration; - int iTrack = nIter.Track(); - - // "the first row after the hold head that lands on a beat" - int iFirstCheckpointOfHold = ((iStartRow+iCheckpointFrequencyRows)/iCheckpointFrequencyRows) - * iCheckpointFrequencyRows; - - // "the end row or the first earlier row that lands on a beat" - int iLastCheckpointOfHold = ((iEndRow)/iCheckpointFrequencyRows) - * iCheckpointFrequencyRows; - - // count the end of the hold as a checkpoint - bool bHoldOverlapsRow = iFirstCheckpointOfHold <= r && r <= iLastCheckpointOfHold; - if( !bHoldOverlapsRow ) - continue; - - - - viColsWithHold.push_back( iTrack ); - - if( tn.HoldResult.fLife > 0 ) - { - ++iNumHoldsHeldThisRow; - ++tn.HoldResult.iCheckpointsHit; - } - else - { - ++iNumHoldsMissedThisRow; - ++tn.HoldResult.iCheckpointsMissed; - } - } - GAMESTATE->SetProcessedTimingData(this->m_Timing); - - // TODO: Find a better way of handling hold checkpoints with other taps. - if( !viColsWithHold.empty() && ( CHECKPOINTS_TAPS_SEPARATE_JUDGMENT || m_NoteData.GetNumTapNotesInRow( iLastRowCrossed ) == 0 ) ) - { - HandleHoldCheckpoint(r, - iNumHoldsHeldThisRow, - iNumHoldsMissedThisRow, - viColsWithHold ); - } - } - } - } - - m_iFirstUncrossedRow = iLastRowCrossed+1; -} - -void Player::RandomizeNotes( int iNoteRow ) -{ - // change the row to look ahead from based upon their speed mod - /* This is incorrect: if m_fScrollSpeed is 0.5, we'll never change - * any odd rows, and if it's 2, we'll shuffle each row twice. */ - int iNewNoteRow = iNoteRow + ROWS_PER_BEAT*2; - iNewNoteRow = int( iNewNoteRow / m_pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollSpeed ); - - int iNumOfTracks = m_NoteData.GetNumTracks(); - for( int t=0; t+1 < iNumOfTracks; t++ ) - { - /* Only swap a tap and an empty. */ - NoteData::iterator iter = m_NoteData.FindTapNote( t, iNewNoteRow ); - if( iter == m_NoteData.end(t) || iter->second.type != TapNote::tap ) - continue; - - const int iSwapWith = RandomInt( iNumOfTracks ); - - // Make sure this is empty. - if( m_NoteData.FindTapNote(iSwapWith, iNewNoteRow) != m_NoteData.end(iSwapWith) ) - continue; - - /* Make sure the destination row isn't in the middle of a hold. */ - if( m_NoteData.IsHoldNoteAtRow(iSwapWith, iNoteRow) ) - continue; - - m_NoteData.SetTapNote( iSwapWith, iNewNoteRow, iter->second ); - m_NoteData.RemoveTapNote( t, iter ); - } -} - -void Player::HandleTapRowScore( unsigned row ) -{ - bool bNoCheating = true; -#ifdef DEBUG - bNoCheating = false; -#endif - - // Do not score rows in WarpSegments or FakeSegments - if (!m_Timing->IsJudgableAtRow(row)) - return; - - if( GAMESTATE->m_bDemonstrationOrJukebox ) - bNoCheating = false; - // don't accumulate points if AutoPlay is on. - if( bNoCheating && m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) - return; - - TapNoteScore scoreOfLastTap = NoteDataWithScoring::LastTapNoteWithResult(m_NoteData, row).result.tns; - const int iOldCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0; - const int iOldMissCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurMissCombo : 0; - - if( scoreOfLastTap == TNS_Miss ) - m_LastTapNoteScore = TNS_Miss; - - for( int track = 0; track < m_NoteData.GetNumTracks(); ++track ) - { - const TapNote &tn = m_NoteData.GetTapNote( track, row ); - // Mines cannot be handled here. - if (tn.type == TapNote::empty || - tn.type == TapNote::fake || - tn.type == TapNote::mine || - tn.type == TapNote::autoKeysound) - continue; - if( m_pPrimaryScoreKeeper ) - m_pPrimaryScoreKeeper->HandleTapScore( tn ); - if( m_pSecondaryScoreKeeper ) - m_pSecondaryScoreKeeper->HandleTapScore( tn ); - } - - if( m_pPrimaryScoreKeeper != NULL ) - m_pPrimaryScoreKeeper->HandleTapRowScore( m_NoteData, row ); - if( m_pSecondaryScoreKeeper != NULL ) - m_pSecondaryScoreKeeper->HandleTapRowScore( m_NoteData, row ); - - const int iCurCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0; - const int iCurMissCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurMissCombo : 0; - - SendComboMessages( iOldCombo, iOldMissCombo ); - - if( m_pPlayerStageStats ) - { - SetCombo( iCurCombo, iCurMissCombo ); - } - -#define CROSSED( x ) (iOldCombo=x) - if ( CROSSED(100) ) - SCREENMAN->PostMessageToTopScreen( SM_100Combo, 0 ); - else if( CROSSED(200) ) - SCREENMAN->PostMessageToTopScreen( SM_200Combo, 0 ); - else if( CROSSED(300) ) - SCREENMAN->PostMessageToTopScreen( SM_300Combo, 0 ); - else if( CROSSED(400) ) - SCREENMAN->PostMessageToTopScreen( SM_400Combo, 0 ); - else if( CROSSED(500) ) - SCREENMAN->PostMessageToTopScreen( SM_500Combo, 0 ); - else if( CROSSED(600) ) - SCREENMAN->PostMessageToTopScreen( SM_600Combo, 0 ); - else if( CROSSED(700) ) - SCREENMAN->PostMessageToTopScreen( SM_700Combo, 0 ); - else if( CROSSED(800) ) - SCREENMAN->PostMessageToTopScreen( SM_800Combo, 0 ); - else if( CROSSED(900) ) - SCREENMAN->PostMessageToTopScreen( SM_900Combo, 0 ); - else if( CROSSED(1000)) - SCREENMAN->PostMessageToTopScreen( SM_1000Combo, 0 ); - else if( (iOldCombo / 100) < (iCurCombo / 100) && iCurCombo > 1000 ) - SCREENMAN->PostMessageToTopScreen( SM_ComboContinuing, 0 ); -#undef CROSSED - - // new max combo - if( m_pPlayerStageStats ) - m_pPlayerStageStats->m_iMaxCombo = max(m_pPlayerStageStats->m_iMaxCombo, iCurCombo); - - /* Use the real current beat, not the beat we've been passed. That's because - * we want to record the current life/combo to the current time; eg. if it's - * a MISS, the beat we're registering is in the past, but the life is changing - * now. We need to include time from previous songs in a course, so we - * can't use GAMESTATE->m_fMusicSeconds. Use fStepsSeconds instead. */ - if( m_pPlayerStageStats ) - m_pPlayerStageStats->UpdateComboList( STATSMAN->m_CurStageStats.m_fStepsSeconds, false ); - - if( m_pScoreDisplay ) - { - if( m_pPlayerStageStats ) - m_pScoreDisplay->SetScore( m_pPlayerStageStats->m_iScore ); - m_pScoreDisplay->OnJudgment( scoreOfLastTap ); - } - if( m_pSecondaryScoreDisplay ) - { - if( m_pPlayerStageStats ) - m_pSecondaryScoreDisplay->SetScore( m_pPlayerStageStats->m_iScore ); - m_pSecondaryScoreDisplay->OnJudgment( scoreOfLastTap ); - } - - ChangeLife( scoreOfLastTap ); -} - -void Player::HandleHoldCheckpoint(int iRow, - int iNumHoldsHeldThisRow, - int iNumHoldsMissedThisRow, - const vector &viColsWithHold ) -{ - bool bNoCheating = true; -#ifdef DEBUG - bNoCheating = false; -#endif - - // WarpSegments and FakeSegments aren't judged in any way. - if (!m_Timing->IsJudgableAtRow(iRow)) - return; - - // don't accumulate combo if AutoPlay is on. - if( bNoCheating && m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) - return; - - const int iOldCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0; - const int iOldMissCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurMissCombo : 0; - - if( m_pPrimaryScoreKeeper ) - m_pPrimaryScoreKeeper->HandleHoldCheckpointScore(m_NoteData, - iRow, - iNumHoldsHeldThisRow, - iNumHoldsMissedThisRow ); - if( m_pSecondaryScoreKeeper ) - m_pSecondaryScoreKeeper->HandleHoldCheckpointScore(m_NoteData, - iRow, - iNumHoldsHeldThisRow, - iNumHoldsMissedThisRow ); - - if( iNumHoldsMissedThisRow == 0 ) - { - // added for http://ssc.ajworld.net/sm-ssc/bugtracker/view.php?id=16 -aj - if( CHECKPOINTS_FLASH_ON_HOLD ) - { - FOREACH_CONST( int, viColsWithHold, i ) - { - bool bBright = m_pPlayerStageStats - && m_pPlayerStageStats->m_iCurCombo>(int)BRIGHT_GHOST_COMBO_THRESHOLD; - if( m_pNoteField ) - m_pNoteField->DidHoldNote( *i, HNS_Held, bBright ); - } - } - } - - SendComboMessages( iOldCombo, iOldMissCombo ); - - if( m_pPlayerStageStats ) - { - SetCombo( m_pPlayerStageStats->m_iCurCombo, m_pPlayerStageStats->m_iCurMissCombo ); - m_pPlayerStageStats->UpdateComboList( STATSMAN->m_CurStageStats.m_fStepsSeconds, false ); - } - - ChangeLife( iNumHoldsMissedThisRow == 0? TNS_CheckpointHit:TNS_CheckpointMiss ); - - SetJudgment( iNumHoldsMissedThisRow == 0? TNS_CheckpointHit:TNS_CheckpointMiss, viColsWithHold[0], 0 ); -} - -void Player::HandleHoldScore( const TapNote &tn ) -{ - HoldNoteScore holdScore = tn.HoldResult.hns; - TapNoteScore tapScore = tn.result.tns; - bool bNoCheating = true; -#ifdef DEBUG - bNoCheating = false; -#endif - - if( GAMESTATE->m_bDemonstrationOrJukebox ) - bNoCheating = false; - // don't accumulate points if AutoPlay is on. - if( bNoCheating && m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) - return; - - if( m_pPrimaryScoreKeeper ) - m_pPrimaryScoreKeeper->HandleHoldScore( tn ); - if( m_pSecondaryScoreKeeper ) - m_pSecondaryScoreKeeper->HandleHoldScore( tn ); - - if( m_pScoreDisplay ) - { - if( m_pPlayerStageStats ) - m_pScoreDisplay->SetScore( m_pPlayerStageStats->m_iScore ); - m_pScoreDisplay->OnJudgment( holdScore, tapScore ); - } - if( m_pSecondaryScoreDisplay ) - { - if( m_pPlayerStageStats ) - m_pSecondaryScoreDisplay->SetScore( m_pPlayerStageStats->m_iScore ); - m_pSecondaryScoreDisplay->OnJudgment( holdScore, tapScore ); - } - - ChangeLife( holdScore, tapScore ); -} - -float Player::GetMaxStepDistanceSeconds() -{ - float fMax = 0; - fMax = max( fMax, GetWindowSeconds(TW_W5) ); - fMax = max( fMax, GetWindowSeconds(TW_W4) ); - fMax = max( fMax, GetWindowSeconds(TW_W3) ); - fMax = max( fMax, GetWindowSeconds(TW_W2) ); - fMax = max( fMax, GetWindowSeconds(TW_W1) ); - float f = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate * fMax; - return f + m_fMaxInputLatencySeconds; -} - -void Player::FadeToFail() -{ - if( m_pNoteField ) - m_pNoteField->FadeToFail(); - - // clear miss combo - SetCombo( 0, 0 ); -} - -void Player::CacheAllUsedNoteSkins() -{ - if( m_pNoteField ) - m_pNoteField->CacheAllUsedNoteSkins(); -} - -void Player::SetMineJudgment( TapNoteScore tns ) -{ - if( m_bSendJudgmentAndComboMessages ) - { - Message msg("Judgment"); - msg.SetParam( "Player", m_pPlayerState->m_PlayerNumber ); - msg.SetParam( "TapNoteScore", tns ); - MESSAGEMAN->Broadcast( msg ); - if( m_pPlayerStageStats ) - { - SetCombo( m_pPlayerStageStats->m_iCurCombo, m_pPlayerStageStats->m_iCurMissCombo ); - } - } -} - -void Player::SetJudgment( TapNoteScore tns, int iTrack, float fTapNoteOffset ) -{ - if( m_bSendJudgmentAndComboMessages ) - { - Message msg("Judgment"); - msg.SetParam( "Player", m_pPlayerState->m_PlayerNumber ); - msg.SetParam( "MultiPlayer", m_pPlayerState->m_mp ); - msg.SetParam( "FirstTrack", iTrack ); - msg.SetParam( "TapNoteScore", tns ); - msg.SetParam( "Early", fTapNoteOffset < 0.0f ); - msg.SetParam( "TapNoteOffset", fTapNoteOffset ); - MESSAGEMAN->Broadcast( msg ); - } -} - -void Player::SetHoldJudgment( TapNoteScore tns, HoldNoteScore hns, int iTrack ) -{ - ASSERT( iTrack < (int)m_vpHoldJudgment.size() ); - if( m_vpHoldJudgment[iTrack] ) - m_vpHoldJudgment[iTrack]->SetHoldJudgment( hns ); - - if( m_bSendJudgmentAndComboMessages ) - { - Message msg("Judgment"); - msg.SetParam( "Player", m_pPlayerState->m_PlayerNumber ); - msg.SetParam( "MultiPlayer", m_pPlayerState->m_mp ); - msg.SetParam( "FirstTrack", iTrack ); - msg.SetParam( "TapNoteScore", tns ); - msg.SetParam( "HoldNoteScore", hns ); - MESSAGEMAN->Broadcast( msg ); - } -} - -void Player::SetCombo( int iCombo, int iMisses ) -{ - if( m_iLastSeenCombo == -1 ) // first update, don't set bIsMilestone=true - m_iLastSeenCombo = iCombo; - - bool b25Milestone = false; - bool b50Milestone = false; - bool b100Milestone = false; - bool b250Milestone = false; - bool b1000Milestone = false; - for( int i=m_iLastSeenCombo+1; i<=iCombo; i++ ) - { - if( i < 600 ) - { - b25Milestone |= ((i % 25) == 0); - b50Milestone |= ((i % 50) == 0); - b100Milestone |= ((i % 100) == 0); - b250Milestone |= ((i % 250) == 0); - } - else - { - b1000Milestone |= ((i % 200) == 0); - } - } - m_iLastSeenCombo = iCombo; - - if( b25Milestone ) - this->PlayCommand( "TwentyFiveMilestone"); - if( b50Milestone ) - this->PlayCommand( "FiftyMilestone"); - if( b100Milestone ) - this->PlayCommand( "HundredMilestone" ); - if( b250Milestone ) - this->PlayCommand( "TwoHundredFiftyMilestone"); - if( b1000Milestone ) - this->PlayCommand( "ThousandMilestone" ); - - /* Colored combo logic differs between Songs and Courses. - * Songs: - * The theme decides how far into the song the combo color should appear. - * (PERCENT_UNTIL_COLOR_COMBO) - * - * Courses: - * PERCENT_UNTIL_COLOR_COMBO refers to how long through the course the - * combo color should appear (scaling to the number of songs). This may - * not be desired behavior, however. -aj - * - * TODO: Add a metric that determines Course combo colors logic? - * Or possibly move the logic to a Lua function? -aj */ - bool bPastBeginning = false; - if( GAMESTATE->IsCourseMode() ) - { - int iSongIndexStartColoring = GAMESTATE->m_pCurCourse->GetEstimatedNumStages(); - iSongIndexStartColoring = - static_cast(floor(iSongIndexStartColoring*PERCENT_UNTIL_COLOR_COMBO)); - bPastBeginning = GAMESTATE->GetCourseSongIndex() >= iSongIndexStartColoring; - } - else - { - bPastBeginning = m_pPlayerState->m_Position.m_fMusicSeconds - > GAMESTATE->m_pCurSong->m_fMusicLengthSeconds * PERCENT_UNTIL_COLOR_COMBO; - } - - if( m_bSendJudgmentAndComboMessages ) - { - Message msg("Combo"); - if( iCombo ) - msg.SetParam( "Combo", iCombo ); - if( iMisses ) - msg.SetParam( "Misses", iMisses ); - if( bPastBeginning && m_pPlayerStageStats->FullComboOfScore(TNS_W1) ) - msg.SetParam( "FullComboW1", true ); - if( bPastBeginning && m_pPlayerStageStats->FullComboOfScore(TNS_W2) ) - msg.SetParam( "FullComboW2", true ); - if( bPastBeginning && m_pPlayerStageStats->FullComboOfScore(TNS_W3) ) - msg.SetParam( "FullComboW3", true ); - if( bPastBeginning && m_pPlayerStageStats->FullComboOfScore(TNS_W4) ) - msg.SetParam( "FullComboW4", true ); - this->HandleMessage( msg ); - } -} - -RString Player::ApplyRandomAttack() -{ - if( GAMESTATE->m_RandomAttacks.size() < 1 ) - return ""; - - //int iAttackToUse = rand() % GAMESTATE->m_RandomAttacks.size(); - DateTime now = DateTime::GetNowDate(); - int iSeed = now.tm_hour * now.tm_min * now.tm_sec * now.tm_mday; - RandomGen rnd( GAMESTATE->m_iStageSeed * iSeed ); - int iAttackToUse = rnd() % GAMESTATE->m_RandomAttacks.size(); - return GAMESTATE->m_RandomAttacks[iAttackToUse]; -} - -// lua start -#include "LuaBinding.h" - -/** @brief Allow Lua to have access to the Player. */ -class LunaPlayer: public Luna -{ -public: - static int SetActorWithJudgmentPosition( T* p, lua_State *L ) - { - Actor *pActor = Luna::check(L, 1); - p->SetActorWithJudgmentPosition(pActor); - return 0; - } - static int SetActorWithComboPosition( T* p, lua_State *L ) - { - Actor *pActor = Luna::check(L, 1); - p->SetActorWithComboPosition(pActor); - return 0; - } - static int GetPlayerTimingData( T* p, lua_State *L ) - { - p->GetPlayerTimingData().PushSelf(L); - return 1; - } - - LunaPlayer() - { - ADD_METHOD( SetActorWithJudgmentPosition ); - ADD_METHOD( SetActorWithComboPosition ); - ADD_METHOD( GetPlayerTimingData ); - } -}; - -LUA_REGISTER_DERIVED_CLASS( Player, ActorFrame ) -// lua end - -/* - * (c) 2001-2006 Chris Danford, Steve Checkoway - * 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 "Player.h" +#include "GameConstantsAndTypes.h" +#include "RageUtil.h" +#include "RageTimer.h" +#include "PrefsManager.h" +#include "GameManager.h" +#include "InputMapper.h" +#include "SongManager.h" +#include "GameState.h" +#include "ScoreKeeperNormal.h" +#include "RageLog.h" +#include "RageDisplay.h" +#include "ThemeManager.h" +#include "ScoreDisplay.h" +#include "LifeMeter.h" +#include "CombinedLifeMeter.h" +#include "PlayerAI.h" +#include "NoteField.h" +#include "NoteDataUtil.h" +#include "ScreenMessage.h" +#include "ScreenManager.h" +#include "StageStats.h" +#include "ActorUtil.h" +#include "ArrowEffects.h" +#include "Game.h" +#include "NetworkSyncManager.h" //used for sending timing offset +#include "DancingCharacters.h" +#include "ScreenDimensions.h" +#include "RageSoundManager.h" +#include "ThemeMetric.h" +#include "PlayerState.h" +#include "GameSoundManager.h" +#include "Style.h" +#include "MessageManager.h" +#include "ProfileManager.h" +#include "Profile.h" +#include "StatsManager.h" +#include "Song.h" +#include "Steps.h" +#include "GameCommand.h" +#include "LocalizedString.h" +#include "AdjustSync.h" + +RString ATTACK_DISPLAY_X_NAME( size_t p, size_t both_sides ); +void TimingWindowSecondsInit( size_t /*TimingWindow*/ i, RString &sNameOut, float &defaultValueOut ); + +/** + * @brief Helper class to ensure that each row is only judged once without taking too much memory. + */ +class JudgedRows +{ + vector m_vRows; + int m_iStart; + int m_iOffset; + + void Resize( size_t iMin ) + { + size_t iNewSize = max( 2*m_vRows.size(), iMin ); + vector vNewRows( m_vRows.begin() + m_iOffset, m_vRows.end() ); + vNewRows.reserve( iNewSize ); + vNewRows.insert( vNewRows.end(), m_vRows.begin(), m_vRows.begin() + m_iOffset ); + vNewRows.resize( iNewSize, false ); + m_vRows.swap( vNewRows ); + m_iOffset = 0; + } +public: + JudgedRows() : m_iStart(0), m_iOffset(0) { Resize( 32 ); } + // Returns true if the row has already been judged. + bool JudgeRow( int iRow ) + { + if( iRow < m_iStart ) + return true; + if( iRow >= m_iStart+int(m_vRows.size()) ) + Resize( iRow+1-m_iStart ); + const int iIndex = (iRow - m_iStart + m_iOffset) % m_vRows.size(); + const bool ret = m_vRows[iIndex]; + m_vRows[iIndex] = true; + while( m_vRows[m_iOffset] ) + { + m_vRows[m_iOffset] = false; + ++m_iStart; + if( ++m_iOffset >= int(m_vRows.size()) ) + m_iOffset -= m_vRows.size(); + } + return ret; + } + void Reset( int iStart ) + { + m_iStart = iStart; + m_iOffset = 0; + m_vRows.assign( m_vRows.size(), false ); + } +}; + + +RString ATTACK_DISPLAY_X_NAME( size_t p, size_t both_sides ) { return "AttackDisplayXOffset" + (both_sides ? RString("BothSides") : ssprintf("OneSideP%d",int(p+1)) ); } + +/** + * @brief Distance to search for a note in Step(), in seconds. + * + * TODO: This should be calculated based on the max size of the current judgment windows. */ +static const float StepSearchDistance = 1.0f; + +void TimingWindowSecondsInit( size_t /*TimingWindow*/ i, RString &sNameOut, float &defaultValueOut ) +{ + sNameOut = "TimingWindowSeconds" + TimingWindowToString( (TimingWindow)i ); + switch( i ) + { + default: + FAIL_M(ssprintf("Invalid timing window: %i", i)); + case TW_W1: defaultValueOut = 0.0225f; break; + case TW_W2: defaultValueOut = 0.045f; break; + case TW_W3: defaultValueOut = 0.090f; break; + case TW_W4: defaultValueOut = 0.135f; break; + case TW_W5: defaultValueOut = 0.180f; break; + case TW_Mine: defaultValueOut = 0.090f; break; // same as great + case TW_Hold: defaultValueOut = 0.250f; break; // allow enough time to take foot off and put back on + case TW_Roll: defaultValueOut = 0.500f; break; + case TW_Attack: defaultValueOut = 0.135f; break; + } +} + +static Preference m_fTimingWindowScale ( "TimingWindowScale", 1.0f ); +static Preference m_fTimingWindowAdd ( "TimingWindowAdd", 0 ); +static Preference1D m_fTimingWindowSeconds( TimingWindowSecondsInit, NUM_TimingWindow ); +static Preference m_fTimingWindowJump ( "TimingWindowJump", 0.25 ); +static Preference m_fMaxInputLatencySeconds ( "MaxInputLatencySeconds", 0.0 ); +static Preference g_bEnableAttackSoundPlayback ( "EnableAttackSounds", true ); +static Preference g_bEnableMineSoundPlayback ( "EnableMineHitSound", true ); + +Preference g_fTimingWindowHopo ( "TimingWindowHopo", 0.25 ); // max time between notes in a hopo chain +Preference g_fTimingWindowStrum ( "TimingWindowStrum", 0.1f ); // max time between strum and when the frets must match +/** @brief How much life is in a hold note when you start on it? */ +ThemeMetric INITIAL_HOLD_LIFE ( "Player", "InitialHoldLife" ); +/** + * @brief How much hold life is possible to have when holding a hold note? + * + * This was an sm-ssc addition. */ +ThemeMetric MAX_HOLD_LIFE ( "Player", "MaxHoldLife" ); +ThemeMetric PENALIZE_TAP_SCORE_NONE ( "Player", "PenalizeTapScoreNone" ); +ThemeMetric JUDGE_HOLD_NOTES_ON_SAME_ROW_TOGETHER ( "Player", "JudgeHoldNotesOnSameRowTogether" ); +/** + * @brief Does StepMania use checkpoint judgments when interacting with holds? + * + * If set to true, checkpoint judgments are in use, similar to the Pump It Up + * series. If set to false, no such judgments are used. */ +ThemeMetric HOLD_CHECKPOINTS ( "Player", "HoldCheckpoints" ); +/** + * @brief If using checkpoints, are the song's TickcountSegments used? + * + * If set to true, the checkpoints rely on the data found in the TickcountSegments. + * This is used via the #%TICKCOUNTS tag. */ +ThemeMetric CHECKPOINTS_USE_TICKCOUNTS ( "Player", "CheckpointsUseTickcounts" ); +/** + * @brief If using checkpoints, are the song's TimeSignatureSegments used? + * + * If set to true AND CheckpointsUseTickcounts is set to false, the TimeSignatureSegments + * are used to determine how often the checkpoints are found. + * + * It should be noted that this is a deprecated metric. */ +ThemeMetric CHECKPOINTS_USE_TIME_SIGNATURES ( "Player", "CheckpointsUseTimeSignatures" ); +ThemeMetric CHECKPOINTS_FLASH_ON_HOLD ( "Player", "CheckpointsFlashOnHold" ); // sm-ssc addition +ThemeMetric IMMEDIATE_HOLD_LET_GO ( "Player", "ImmediateHoldLetGo" ); +/** + * @brief Must a Player step on a hold head for a hold to activate? + * + * If set to true, the Player must step on a hold head in order for the hold to activate. + * If set to false, merely holding your foot down as the hold head approaches will suffice. */ +ThemeMetric REQUIRE_STEP_ON_HOLD_HEADS ( "Player", "RequireStepOnHoldHeads" ); +/** + * @brief Must a Player step on a mine for it to activate? + * + * If set to true, the Player must step on a mine for it to blow up. + * If set to false, merely holding your foot down as the mine approaches will suffice. */ +ThemeMetric REQUIRE_STEP_ON_MINES ( "Player", "RequireStepOnMines" ); +//ThemeMetric HOLD_TRIGGERS_TAP_NOTES ( "Player", "HoldTriggersTapNotes" ); // parastar stuff; leave in though +/** + * @brief Does repeatedly stepping on a roll to keep it alive increment the combo? + * + * If set to true, repeatedly stepping on a roll will increment the combo. + * If set to false, only the roll head causes the combo to be incremented. + * + * For those wishing to make a theme very accurate to In The Groove 2, set this to false. */ +ThemeMetric ROLL_BODY_INCREMENTS_COMBO ( "Player", "RollBodyIncrementsCombo" ); +/** + * @brief Are checkpoints and taps considered separate judgments? + * + * If set to true, they are considered separate. + * If set to false, they are considered the same. */ +ThemeMetric CHECKPOINTS_TAPS_SEPARATE_JUDGMENT ( "Player", "CheckpointsTapsSeparateJudgment" ); +/** + * @brief Do we score missed holds and rolls with HoldNoteScores? + * + * If set to true, missed holds and rolls are given LetGo judgments. + * If set to false, missed holds and rolls are given no judgment on the hold side of things. */ +ThemeMetric SCORE_MISSED_HOLDS_AND_ROLLS ( "Player", "ScoreMissedHoldsAndRolls" ); +/** @brief How much of the song/course must have gone by before a Player's combo is colored? */ +ThemeMetric PERCENT_UNTIL_COLOR_COMBO ( "Player", "PercentUntilColorCombo" ); +/** @brief How much combo must be earned before the announcer says "Combo Stopped"? */ +ThemeMetric COMBO_STOPPED_AT ( "Player", "ComboStoppedAt" ); +ThemeMetric ATTACK_RUN_TIME_RANDOM ( "Player", "AttackRunTimeRandom" ); +ThemeMetric ATTACK_RUN_TIME_MINE ( "Player", "AttackRunTimeMine" ); + +/** + * @brief What is our highest cap for mMods? + * + * If set to 0 or less, assume the song takes over. */ +ThemeMetric M_MOD_HIGH_CAP("Player", "MModHighCap"); + +/** @brief Will battle modes have their steps mirrored or kept the same? */ +ThemeMetric BATTLE_RAVE_MIRROR ( "Player", "BattleRaveMirror" ); + +float Player::GetWindowSeconds( TimingWindow tw ) +{ + float fSecs = m_fTimingWindowSeconds[tw]; + fSecs *= m_fTimingWindowScale; + fSecs += m_fTimingWindowAdd; + return fSecs; +} + +Player::Player( NoteData &nd, bool bVisibleParts ) : m_NoteData(nd) +{ + m_bLoaded = false; + + m_pPlayerState = NULL; + m_pPlayerStageStats = NULL; + m_fNoteFieldHeight = 0; + + m_pLifeMeter = NULL; + m_pCombinedLifeMeter = NULL; + m_pScoreDisplay = NULL; + m_pSecondaryScoreDisplay = NULL; + m_pPrimaryScoreKeeper = NULL; + m_pSecondaryScoreKeeper = NULL; + m_pInventory = NULL; + m_pIterNeedsTapJudging = NULL; + m_pIterNeedsHoldJudging = NULL; + m_pIterUncrossedRows = NULL; + m_pIterUnjudgedRows = NULL; + m_pIterUnjudgedMineRows = NULL; + + m_bPaused = false; + m_bDelay = false; + + m_pAttackDisplay = NULL; + if( bVisibleParts ) + { + m_pAttackDisplay = new AttackDisplay; + this->AddChild( m_pAttackDisplay ); + } + + PlayerAI::InitFromDisk(); + + m_pNoteField = NULL; + if( bVisibleParts ) + { + m_pNoteField = new NoteField; + m_pNoteField->SetName( "NoteField" ); + } + m_pJudgedRows = new JudgedRows; + + m_bSendJudgmentAndComboMessages = true; +} + +Player::~Player() +{ + SAFE_DELETE( m_pAttackDisplay ); + SAFE_DELETE( m_pNoteField ); + for( unsigned i = 0; i < m_vpHoldJudgment.size(); ++i ) + SAFE_DELETE( m_vpHoldJudgment[i] ); + SAFE_DELETE( m_pJudgedRows ); + SAFE_DELETE( m_pIterNeedsTapJudging ); + SAFE_DELETE( m_pIterNeedsHoldJudging ); + SAFE_DELETE( m_pIterUncrossedRows ); + SAFE_DELETE( m_pIterUnjudgedRows ); + SAFE_DELETE( m_pIterUnjudgedMineRows ); + +} + +/* Init() does the expensive stuff: load sounds and noteskins. Load() just loads a NoteData. */ +void Player::Init( + const RString &sType, + PlayerState* pPlayerState, + PlayerStageStats* pPlayerStageStats, + LifeMeter* pLM, + CombinedLifeMeter* pCombinedLM, + ScoreDisplay* pScoreDisplay, + ScoreDisplay* pSecondaryScoreDisplay, + Inventory* pInventory, + ScoreKeeper* pPrimaryScoreKeeper, + ScoreKeeper* pSecondaryScoreKeeper ) +{ + GRAY_ARROWS_Y_STANDARD.Load( sType, "ReceptorArrowsYStandard" ); + GRAY_ARROWS_Y_REVERSE.Load( sType, "ReceptorArrowsYReverse" ); + ATTACK_DISPLAY_X.Load( sType, ATTACK_DISPLAY_X_NAME, NUM_PLAYERS, 2 ); + ATTACK_DISPLAY_Y.Load( sType, "AttackDisplayY" ); + ATTACK_DISPLAY_Y_REVERSE.Load( sType, "AttackDisplayYReverse" ); + HOLD_JUDGMENT_Y_STANDARD.Load( sType, "HoldJudgmentYStandard" ); + HOLD_JUDGMENT_Y_REVERSE.Load( sType, "HoldJudgmentYReverse" ); + BRIGHT_GHOST_COMBO_THRESHOLD.Load( sType, "BrightGhostComboThreshold" ); + TAP_JUDGMENTS_UNDER_FIELD.Load( sType, "TapJudgmentsUnderField" ); + HOLD_JUDGMENTS_UNDER_FIELD.Load( sType, "HoldJudgmentsUnderField" ); + COMBO_UNDER_FIELD.Load( sType, "ComboUnderField" ); + DRAW_DISTANCE_AFTER_TARGET_PIXELS.Load( sType, "DrawDistanceAfterTargetsPixels" ); + DRAW_DISTANCE_BEFORE_TARGET_PIXELS.Load( sType, "DrawDistanceBeforeTargetsPixels" ); + + { + // Init judgment positions + bool bPlayerUsingBothSides = GAMESTATE->GetCurrentStyle()->GetUsesCenteredArrows(); + Actor TempJudgment; + TempJudgment.SetName( "Judgment" ); + ActorUtil::LoadCommand( TempJudgment, sType, "Transform" ); + + Actor TempCombo; + TempCombo.SetName( "Combo" ); + ActorUtil::LoadCommand( TempCombo, sType, "Transform" ); + + int iEnabledPlayerIndex = -1; + int iNumEnabledPlayers = 0; + if( GAMESTATE->m_bMultiplayer ) + { + FOREACH_EnabledMultiPlayer( p ) + { + if( p == pPlayerState->m_mp ) + iEnabledPlayerIndex = iNumEnabledPlayers; + iNumEnabledPlayers++; + } + } + else + { + FOREACH_EnabledPlayer( p ) + { + if( p == pPlayerState->m_PlayerNumber ) + iEnabledPlayerIndex = iNumEnabledPlayers; + iNumEnabledPlayers++; + } + } + + if( iNumEnabledPlayers == 0 ) // hack for ScreenHowToPlay where no players are joined + { + iEnabledPlayerIndex = 0; + iNumEnabledPlayers = 1; + } + + for( int i=0; im_PlayerNumber ); + msg.SetParam( "MultiPlayer", pPlayerState->m_mp ); + msg.SetParam( "iEnabledPlayerIndex", iEnabledPlayerIndex ); + msg.SetParam( "iNumEnabledPlayers", iNumEnabledPlayers ); + msg.SetParam( "bPlayerUsingBothSides", bPlayerUsingBothSides ); + msg.SetParam( "bReverse", !!i ); + msg.SetParam( "bCentered", !!j ); + + TempJudgment.HandleMessage( msg ); + m_tsJudgment[i][j] = TempJudgment.DestTweenState(); + + TempCombo.HandleMessage( msg ); + m_tsCombo[i][j] = TempCombo.DestTweenState(); + } + } + } + + this->SortByDrawOrder(); + + m_pPlayerState = pPlayerState; + m_pPlayerStageStats = pPlayerStageStats; + m_pLifeMeter = pLM; + m_pCombinedLifeMeter = pCombinedLM; + m_pScoreDisplay = pScoreDisplay; + m_pSecondaryScoreDisplay = pSecondaryScoreDisplay; + m_pInventory = pInventory; + m_pPrimaryScoreKeeper = pPrimaryScoreKeeper; + m_pSecondaryScoreKeeper = pSecondaryScoreKeeper; + + m_iLastSeenCombo = -1; + + // set initial life + if( m_pLifeMeter && m_pPlayerStageStats ) + { + float fLife = m_pLifeMeter->GetLife(); + m_pPlayerStageStats->SetLifeRecordAt( fLife, STATSMAN->m_CurStageStats.m_fStepsSeconds ); + } + + // TODO: Remove use of PlayerNumber. + PlayerNumber pn = m_pPlayerState->m_PlayerNumber; + + RageSoundLoadParams SoundParams; + SoundParams.m_bSupportPan = true; + m_soundMine.Load( THEME->GetPathS(sType,"mine"), true, &SoundParams ); + + /* Attacks can be launched in course modes and in battle modes. They both come + * here to play, but allow loading a different sound for different modes. */ + switch( GAMESTATE->m_PlayMode ) + { + case PLAY_MODE_RAVE: + case PLAY_MODE_BATTLE: + m_soundAttackLaunch.Load( THEME->GetPathS(sType,"battle attack launch"), true, &SoundParams ); + m_soundAttackEnding.Load( THEME->GetPathS(sType,"battle attack ending"), true, &SoundParams ); + break; + default: + m_soundAttackLaunch.Load( THEME->GetPathS(sType,"course attack launch"), true, &SoundParams ); + m_soundAttackEnding.Load( THEME->GetPathS(sType,"course attack ending"), true, &SoundParams ); + break; + } + + // calculate M-mod speed here, so we can adjust properly on a per-song basis. + // XXX: can we find a better location for this? + if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fMaxScrollBPM != 0 ) + { + DisplayBpms bpms; + + if( GAMESTATE->IsCourseMode() ) + { + ASSERT( GAMESTATE->m_pCurTrail[pn] != NULL ); + GAMESTATE->m_pCurTrail[pn]->GetDisplayBpms( bpms ); + } + else + { + ASSERT( GAMESTATE->m_pCurSong != NULL ); + GAMESTATE->m_pCurSong->GetDisplayBpms( bpms ); + } + + float fMaxBPM = 0; + + /* TODO: Find a way to not go above a certain BPM range + * for getting the max BPM. Otherwise, you get songs + * like Tsuhsuixamush, M550, 0.18x speed. Even slow + * speed readers would not generally find this fun. + * -Wolfman2000 + */ + + // all BPMs are listed and available, so try them first. + // get the maximum listed value for the song or course. + // if the BPMs are < 0, reset and get the actual values. + if( !bpms.IsSecret() ) + { + fMaxBPM = (M_MOD_HIGH_CAP > 0 ? + bpms.GetMaxWithin(M_MOD_HIGH_CAP) : + bpms.GetMax()); + fMaxBPM = max( 0, fMaxBPM ); + } + + // we can't rely on the displayed BPMs, so manually calculate. + if( fMaxBPM == 0 ) + { + float fThrowAway = 0; + + if( GAMESTATE->IsCourseMode() ) + { + FOREACH_CONST( TrailEntry, GAMESTATE->m_pCurTrail[pn]->m_vEntries, e ) + { + float fMaxForEntry; + if (M_MOD_HIGH_CAP > 0) + e->pSong->m_SongTiming.GetActualBPM( fThrowAway, fMaxForEntry, M_MOD_HIGH_CAP ); + else + e->pSong->m_SongTiming.GetActualBPM( fThrowAway, fMaxForEntry ); + fMaxBPM = max( fMaxForEntry, fMaxBPM ); + } + } + else + { + if (M_MOD_HIGH_CAP > 0) + GAMESTATE->m_pCurSong->m_SongTiming.GetActualBPM( fThrowAway, fMaxBPM, M_MOD_HIGH_CAP ); + else + GAMESTATE->m_pCurSong->m_SongTiming.GetActualBPM( fThrowAway, fMaxBPM ); + } + } + + ASSERT( fMaxBPM > 0 ); + + // set an X-mod equal to Mnum / fMaxBPM (e.g. M600 with 150 becomes 4x) + PO_GROUP_ASSIGN(m_pPlayerState->m_PlayerOptions, ModsLevel_Preferred, m_fScrollSpeed, + m_pPlayerState->m_PlayerOptions.GetPreferred().m_fMaxScrollBPM / fMaxBPM); + } + + float fBalance = GameSoundManager::GetPlayerBalance( pn ); + m_soundMine.SetProperty( "Pan", fBalance ); + m_soundAttackLaunch.SetProperty( "Pan", fBalance ); + m_soundAttackEnding.SetProperty( "Pan", fBalance ); + + + if( HasVisibleParts() ) + { + LuaThreadVariable var( "Player", LuaReference::Create(m_pPlayerState->m_PlayerNumber) ); + LuaThreadVariable var2( "MultiPlayer", LuaReference::Create(m_pPlayerState->m_mp) ); + + m_sprCombo.Load( THEME->GetPathG(sType,"combo") ); + m_sprCombo->SetName( "Combo" ); + m_pActorWithComboPosition = &*m_sprCombo; + this->AddChild( m_sprCombo ); + + // todo: allow for judgments to be loaded per-column a la pop'n? + // see how HoldJudgments are handled below for an example, though + // it would need more work. -aj + m_sprJudgment.Load( THEME->GetPathG(sType,"judgment") ); + m_sprJudgment->SetName( "Judgment" ); + m_pActorWithJudgmentPosition = &*m_sprJudgment; + this->AddChild( m_sprJudgment ); + } + else + { + m_pActorWithComboPosition = NULL; + m_pActorWithJudgmentPosition = NULL; + } + + // Load HoldJudgments + m_vpHoldJudgment.resize( GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer ); + for( int i = 0; i < GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; ++i ) + m_vpHoldJudgment[i] = NULL; + + if( HasVisibleParts() ) + { + for( int i = 0; i < GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; ++i ) + { + HoldJudgment *pJudgment = new HoldJudgment; + // xxx: assumes sprite; todo: don't force 1x2 -aj + pJudgment->Load( THEME->GetPathG("HoldJudgment","label 1x2") ); + m_vpHoldJudgment[i] = pJudgment; + this->AddChild( m_vpHoldJudgment[i] ); + } + } + + m_fNoteFieldHeight = GRAY_ARROWS_Y_REVERSE-GRAY_ARROWS_Y_STANDARD; + if( m_pNoteField ) + { + m_pNoteField->Init( m_pPlayerState, m_fNoteFieldHeight ); + ActorUtil::LoadAllCommands( *m_pNoteField, sType ); + } + + m_vbFretIsDown.resize( GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer ); + FOREACH( bool, m_vbFretIsDown, b ) + *b = false; + + m_fActiveRandomAttackStart = -1.0f; +} +/** + * @brief Determine if a TapNote needs a tap note style judgment. + * @param tn the TapNote in question. + * @return true if it does, false otherwise. */ +static bool NeedsTapJudging( const TapNote &tn ) +{ + switch( tn.type ) + { + DEFAULT_FAIL( tn.type ); + case TapNote::tap: + case TapNote::hold_head: + case TapNote::mine: + case TapNote::lift: + return tn.result.tns == TNS_None; + case TapNote::hold_tail: + case TapNote::attack: + case TapNote::autoKeysound: + case TapNote::fake: + case TapNote::empty: + return false; + } +} + +/** + * @brief Determine if a TapNote needs a hold note style judgment. + * @param tn the TapNote in question. + * @return true if it does, false otherwise. */ +static bool NeedsHoldJudging( const TapNote &tn ) +{ + switch( tn.type ) + { + DEFAULT_FAIL( tn.type ); + case TapNote::hold_head: + return tn.HoldResult.hns == HNS_None; + case TapNote::tap: + case TapNote::hold_tail: + case TapNote::mine: + case TapNote::lift: + case TapNote::attack: + case TapNote::autoKeysound: + case TapNote::fake: + case TapNote::empty: + return false; + } +} + +static void GenerateCacheDataStructure(PlayerState *pPlayerState, const NoteData ¬es) { + + pPlayerState->m_CacheDisplayedBeat.clear(); + + const vector vScrolls = pPlayerState->GetDisplayedTiming().GetTimingSegments( SEGMENT_SCROLL ); + + float displayedBeat = 0.0f; + float lastRealBeat = 0.0f; + float lastRatio = 1.0f; + for ( unsigned i = 0; i < vScrolls.size(); i++ ) + { + ScrollSegment *seg = ToScroll( vScrolls[i] ); + displayedBeat += ( seg->GetBeat() - lastRealBeat ) * lastRatio; + lastRealBeat = seg->GetBeat(); + lastRatio = seg->GetRatio(); + CacheDisplayedBeat c = { seg->GetBeat(), displayedBeat, seg->GetRatio() }; + pPlayerState->m_CacheDisplayedBeat.push_back( c ); + } + + pPlayerState->m_CacheNoteStat.clear(); + + NoteData::all_tracks_const_iterator it = notes.GetTapNoteRangeAllTracks( 0, MAX_NOTE_ROW, true ); + int count = 0, lastCount = 0; + for( ; !it.IsAtEnd(); ++it ) + { + for( int t = 0; t < notes.GetNumTracks(); t++ ) + { + if( notes.GetTapNote( t, it.Row() ) != TAP_EMPTY ) count ++; + } + CacheNoteStat c = { NoteRowToBeat(it.Row()), lastCount, count }; + lastCount = count; + pPlayerState->m_CacheNoteStat.push_back(c); + } + +} + +void Player::Load() +{ + m_bLoaded = true; + + m_LastTapNoteScore = TNS_None; + // The editor can start playing in the middle of the song. + const int iNoteRow = BeatToNoteRowNotRounded( m_pPlayerState->m_Position.m_fSongBeat ); + m_iFirstUncrossedRow = iNoteRow - 1; + m_pJudgedRows->Reset( iNoteRow ); + + // TODO: Remove use of PlayerNumber. + PlayerNumber pn = m_pPlayerState->m_PlayerNumber; + + bool bOniDead = GAMESTATE->m_SongOptions.GetStage().m_LifeType == SongOptions::LIFE_BATTERY && + (m_pPlayerStageStats == NULL || m_pPlayerStageStats->m_bFailed); + + /* The editor reuses Players ... so we really need to make sure everything + * is reset and not tweening. Perhaps ActorFrame should recurse to subactors; + * then we could just this->StopTweening()? -glenn */ + // hurr why don't you just set m_bPropagateCommands on it then -aj + if( m_sprJudgment ) + m_sprJudgment->PlayCommand("Reset"); + if( m_pPlayerStageStats ) + { + SetCombo( m_pPlayerStageStats->m_iCurCombo, m_pPlayerStageStats->m_iCurMissCombo ); // combo can persist between songs and games + } + if( m_pAttackDisplay ) + m_pAttackDisplay->Init( m_pPlayerState ); + + /* Don't re-init this; that'll reload graphics. Add a separate Reset() call + * if some ScoreDisplays need it. */ +// if( m_pScore ) +// m_pScore->Init( pn ); + + /* Apply transforms. */ + NoteDataUtil::TransformNoteData( m_NoteData, m_pPlayerState->m_PlayerOptions.GetStage(), GAMESTATE->GetCurrentStyle()->m_StepsType ); + + const Song* pSong = GAMESTATE->m_pCurSong; + + m_Timing = GAMESTATE->m_pCurSteps[pn]->GetTimingData(); + + // Generate some cache data structure. + GenerateCacheDataStructure(m_pPlayerState, m_NoteData); + + switch( GAMESTATE->m_PlayMode ) + { + case PLAY_MODE_RAVE: + case PLAY_MODE_BATTLE: + { + // ugly, ugly, ugly. Works only w/ dance. + // Why does this work only with dance? - Steve + // it has to do with there only being four cases. This is a lame + // workaround, but since only DDR has ever really implemented those + // modes, it's stayed like this. -aj + StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType; + NoteDataUtil::TransformNoteData( m_NoteData, m_pPlayerState->m_PlayerOptions.GetStage(), st ); + + if (BATTLE_RAVE_MIRROR) + { + // shuffle either p1 or p2 + static int count = 0; + switch( count ) + { + case 0: + case 3: + NoteDataUtil::Turn( m_NoteData, st, NoteDataUtil::left); + break; + case 1: + case 2: + NoteDataUtil::Turn( m_NoteData, st, NoteDataUtil::right); + break; + default: + FAIL_M(ssprintf("Count %i not in range 0-3", count)); + } + count++; + count %= 4; + } + break; + } + default: break; + } + + int iDrawDistanceAfterTargetsPixels = GAMESTATE->IsEditing() ? -100 : DRAW_DISTANCE_AFTER_TARGET_PIXELS; + int iDrawDistanceBeforeTargetsPixels = GAMESTATE->IsEditing() ? 400 : DRAW_DISTANCE_BEFORE_TARGET_PIXELS; + + float fNoteFieldMiddle = (GRAY_ARROWS_Y_STANDARD+GRAY_ARROWS_Y_REVERSE)/2; + + if( m_pNoteField && !bOniDead ) + { + m_pNoteField->SetY( fNoteFieldMiddle ); + m_pNoteField->Load( &m_NoteData, iDrawDistanceAfterTargetsPixels, iDrawDistanceBeforeTargetsPixels ); + } + + bool bPlayerUsingBothSides = GAMESTATE->GetCurrentStyle()->GetUsesCenteredArrows(); + if( m_pAttackDisplay ) + m_pAttackDisplay->SetX( ATTACK_DISPLAY_X.GetValue(pn, bPlayerUsingBothSides) - 40 ); + // set this in Update //m_pAttackDisplay->SetY( bReverse ? ATTACK_DISPLAY_Y_REVERSE : ATTACK_DISPLAY_Y ); + + // set this in Update + //m_pJudgment->SetX( JUDGMENT_X.GetValue(pn,bPlayerUsingBothSides) ); + //m_pJudgment->SetY( bReverse ? JUDGMENT_Y_REVERSE : JUDGMENT_Y ); + + // Need to set Y positions of all these elements in Update since + // they change depending on PlayerOptions. + + + // Load keysounds. If sounds are already loaded (as in the editor), don't reload them. + // XXX: the editor will load several duplicate copies (in each NoteField), and each + // player will load duplicate sounds. Does this belong somewhere else (perhaps in + // a separate object, used alongside ScreenGameplay::m_pSoundMusic and ScreenEdit::m_pSoundMusic?) + // We don't have to load separate copies to set player fade: always make a copy, and set the + // fade on the copy. + RString sSongDir = pSong->GetSongDir(); + m_vKeysounds.resize( pSong->m_vsKeysoundFile.size() ); + + // parameters are invalid somehow... -aj + RageSoundLoadParams SoundParams; + SoundParams.m_bSupportPan = true; + + float fBalance = GameSoundManager::GetPlayerBalance( pn ); + for( unsigned i=0; im_vsKeysoundFile[i]; + RageSound& sound = m_vKeysounds[i]; + if( sound.GetLoadedFilePath() != sKeysoundFilePath ) + sound.Load( sKeysoundFilePath, true, &SoundParams ); + sound.SetProperty( "Pan", fBalance ); + sound.SetStopModeFromString( "stop" ); + } + + if( m_pPlayerStageStats ) + SendComboMessages( m_pPlayerStageStats->m_iCurCombo, m_pPlayerStageStats->m_iCurMissCombo ); + + SAFE_DELETE( m_pIterNeedsTapJudging ); + m_pIterNeedsTapJudging = new NoteData::all_tracks_iterator( m_NoteData.GetTapNoteRangeAllTracks(iNoteRow, MAX_NOTE_ROW) ); + + SAFE_DELETE( m_pIterNeedsHoldJudging ); + m_pIterNeedsHoldJudging = new NoteData::all_tracks_iterator( m_NoteData.GetTapNoteRangeAllTracks(iNoteRow, MAX_NOTE_ROW ) ); + + SAFE_DELETE( m_pIterUncrossedRows ); + m_pIterUncrossedRows = new NoteData::all_tracks_iterator( m_NoteData.GetTapNoteRangeAllTracks(iNoteRow, MAX_NOTE_ROW ) ); + + SAFE_DELETE( m_pIterUnjudgedRows ); + m_pIterUnjudgedRows = new NoteData::all_tracks_iterator( m_NoteData.GetTapNoteRangeAllTracks(iNoteRow, MAX_NOTE_ROW ) ); + + SAFE_DELETE( m_pIterUnjudgedMineRows ); + m_pIterUnjudgedMineRows = new NoteData::all_tracks_iterator( m_NoteData.GetTapNoteRangeAllTracks(iNoteRow, MAX_NOTE_ROW ) ); +} + +void Player::SendComboMessages( int iOldCombo, int iOldMissCombo ) +{ + const int iCurCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0; + if( iOldCombo > COMBO_STOPPED_AT && iCurCombo < COMBO_STOPPED_AT ) + { + SCREENMAN->PostMessageToTopScreen( SM_ComboStopped, 0 ); + } + + if( m_bSendJudgmentAndComboMessages ) + { + Message msg( "ComboChanged" ); + msg.SetParam( "Player", m_pPlayerState->m_PlayerNumber ); + msg.SetParam( "OldCombo", iOldCombo ); + msg.SetParam( "OldMissCombo", iOldMissCombo ); + if( m_pPlayerState ) + msg.SetParam( "PlayerState", LuaReference::CreateFromPush(*m_pPlayerState) ); + if( m_pPlayerStageStats ) + msg.SetParam( "PlayerStageStats", LuaReference::CreateFromPush(*m_pPlayerStageStats) ); + MESSAGEMAN->Broadcast( msg ); + } +} + +void Player::Update( float fDeltaTime ) +{ + const RageTimer now; + // Don't update if we haven't been loaded yet. + if( !m_bLoaded ) + return; + + //LOG->Trace( "Player::Update(%f)", fDeltaTime ); + + if( GAMESTATE->m_pCurSong==NULL || IsOniDead() ) + return; + + ActorFrame::Update( fDeltaTime ); + + if(m_pPlayerState->m_mp != MultiPlayer_Invalid) + { + /* In multiplayer, it takes too long to run player updates for every player each frame; + * with 32 players and three difficulties, we have 96 Players to update. Stagger these + * updates, by only updating a few players each update; since we don't have screen elements + * tightly tied to user actions in this mode, this doesn't degrade gameplay. Run 4 players + * per update, which means 12 Players in 3-difficulty mode. + */ + static int iCycle = 0; + iCycle = (iCycle + 1) % 8; + + if((m_pPlayerState->m_mp % 8) != iCycle) + return; + } + + const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat; + const int iSongRow = BeatToNoteRow( fSongBeat ); + + // Optimization: Don't spend time processing the things below that won't show + // if the Player doesn't show anything on the screen. + if( HasVisibleParts() ) + { + // Random Attack Mod + if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fRandAttack ) + { + float fCurrentGameTime = STATSMAN->m_CurStageStats.m_fGameplaySeconds; + + const float fAttackRunTime = ATTACK_RUN_TIME_RANDOM; + + // Don't start until 1 seconds into game, minimum + if( fCurrentGameTime > 1.0f ) + { + /* Update the attack if there are no others currently running. + * Note that we have a new one activate a little early; This is + * to have a bit of overlap rather than an abrupt change. */ + if( (fCurrentGameTime - m_fActiveRandomAttackStart) > (fAttackRunTime - 0.5f) ) + { + m_fActiveRandomAttackStart = fCurrentGameTime; + + Attack attRandomAttack; + attRandomAttack.sModifiers = ApplyRandomAttack(); + attRandomAttack.fSecsRemaining = fAttackRunTime; + m_pPlayerState->LaunchAttack( attRandomAttack ); + } + } + } + + if( g_bEnableAttackSoundPlayback ) + { + if( m_pPlayerState->m_bAttackBeganThisUpdate ) + m_soundAttackLaunch.Play(); + if( m_pPlayerState->m_bAttackEndedThisUpdate ) + m_soundAttackEnding.Play(); + } + + if( m_pNoteField ) + m_pNoteField->Update( fDeltaTime ); + + float fMiniPercent = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_MINI]; + float fTinyPercent = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_TINY]; + float fJudgmentZoom = min( powf(0.5f, fMiniPercent+fTinyPercent), 1.0f ); + + // Update Y positions + { + for( int c=0; cGetCurrentStyle()->m_iColsPerPlayer; c++ ) + { + float fPercentReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(c); + float fHoldJudgeYPos = SCALE( fPercentReverse, 0.f, 1.f, HOLD_JUDGMENT_Y_STANDARD, HOLD_JUDGMENT_Y_REVERSE ); + //float fGrayYPos = SCALE( fPercentReverse, 0.f, 1.f, GRAY_ARROWS_Y_STANDARD, GRAY_ARROWS_Y_REVERSE ); + + float fX = ArrowEffects::GetXPos( m_pPlayerState, c, 0 ); + const float fZ = ArrowEffects::GetZPos( m_pPlayerState, c, 0 ); + + m_vpHoldJudgment[c]->SetX( fX ); + m_vpHoldJudgment[c]->SetY( fHoldJudgeYPos ); + m_vpHoldJudgment[c]->SetZ( fZ ); + m_vpHoldJudgment[c]->SetZoom( fJudgmentZoom ); + } + } + + // NoteField accounts for reverse on its own now. + //if( m_pNoteField ) + // m_pNoteField->SetY( fGrayYPos ); + + const bool bReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(0) == 1; + float fPercentCentered = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fScrolls[PlayerOptions::SCROLL_CENTERED]; + + if( m_pActorWithJudgmentPosition != NULL ) + { + const Actor::TweenState &ts1 = m_tsJudgment[bReverse?1:0][0]; + const Actor::TweenState &ts2 = m_tsJudgment[bReverse?1:0][1]; + Actor::TweenState::MakeWeightedAverage( m_pActorWithJudgmentPosition->DestTweenState(), ts1, ts2, fPercentCentered ); + } + + if( m_pActorWithComboPosition != NULL ) + { + const Actor::TweenState &ts1 = m_tsCombo[bReverse?1:0][0]; + const Actor::TweenState &ts2 = m_tsCombo[bReverse?1:0][1]; + Actor::TweenState::MakeWeightedAverage( m_pActorWithComboPosition->DestTweenState(), ts1, ts2, fPercentCentered ); + } + + float fNoteFieldZoom = 1 - fMiniPercent*0.5f; + if( m_pNoteField ) + m_pNoteField->SetZoom( fNoteFieldZoom ); + if( m_pActorWithJudgmentPosition != NULL ) + m_pActorWithJudgmentPosition->SetZoom( m_pActorWithJudgmentPosition->GetZoom() * fJudgmentZoom ); + if( m_pActorWithComboPosition != NULL ) + m_pActorWithComboPosition->SetZoom( m_pActorWithComboPosition->GetZoom() * fJudgmentZoom ); + } + + // If we're paused, don't update tap or hold note logic, so hold notes can be released + // during pause. + if( m_bPaused ) + return; + + // Check for a strum miss + if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 && + m_pPlayerState->m_fLastStrumMusicSeconds + g_fTimingWindowStrum < m_pPlayerState->m_Position.m_fMusicSeconds ) + { + DoStrumMiss(); + } + + // update pressed flag + const int iNumCols = GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; + ASSERT_M( iNumCols <= MAX_COLS_PER_PLAYER, ssprintf("%i > %i", iNumCols, MAX_COLS_PER_PLAYER) ); + for( int col=0; col < iNumCols; ++col ) + { + ASSERT( m_pPlayerState != NULL ); + + // TODO: Remove use of PlayerNumber. + GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( col, m_pPlayerState->m_PlayerNumber ); + + bool bIsHoldingButton = INPUTMAPPER->IsBeingPressed( GameI ); + + // TODO: Make this work for non-human-controlled players + if( bIsHoldingButton && !GAMESTATE->m_bDemonstrationOrJukebox && m_pPlayerState->m_PlayerController==PC_HUMAN ) + if( m_pNoteField ) + m_pNoteField->SetPressed( col ); + } + + // handle Autoplay for rolls + if( m_pPlayerState->m_PlayerController != PC_HUMAN ) + { + for( int iTrack=0; iTrack= 0.5f ) + continue; + + Step( iTrack, iHeadRow, now, false, false ); + if( m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) + { + STATSMAN->m_CurStageStats.m_bUsedAutoplay = true; + if( m_pPlayerStageStats ) + m_pPlayerStageStats->m_bDisqualified = true; + } + } + } + + + // update HoldNotes logic + { + + // Fast forward to the first that needs hold judging. + { + NoteData::all_tracks_iterator &iter = *m_pIterNeedsHoldJudging; + while( !iter.IsAtEnd() && iter.Row() <= iSongRow && !NeedsHoldJudging(*iter) ) + ++iter; + } + + vector vHoldNotesToGradeTogether; + int iRowOfLastHoldNote = -1; + NoteData::all_tracks_iterator iter = *m_pIterNeedsHoldJudging; // copy + for( ; !iter.IsAtEnd() && iter.Row() <= iSongRow; ++iter ) + { + TapNote &tn = *iter; + if( tn.type != TapNote::hold_head ) + continue; + + int iTrack = iter.Track(); + int iRow = iter.Row(); + TrackRowTapNote trtn = { iTrack, iRow, &tn }; + + /* All holds must be of the same subType because fLife is handled + * in different ways depending on the SubType. Handle Rolls one at + * a time and don't mix with holds. */ + switch( tn.subType ) + { + DEFAULT_FAIL( tn.subType ); + case TapNote::hold_head_hold: + break; + case TapNote::hold_head_roll: + { + vector v; + v.push_back( trtn ); + UpdateHoldNotes( iSongRow, fDeltaTime, v ); + } + continue; // don't process this below + } + /* + case TapNote::hold_head_mine: + break; + */ + + if( iRow != iRowOfLastHoldNote || !JUDGE_HOLD_NOTES_ON_SAME_ROW_TOGETHER ) + { + if( !vHoldNotesToGradeTogether.empty() ) + { + //LOG->Trace( ssprintf("UpdateHoldNotes; %i != %i || !judge holds on same row together",iRow,iRowOfLastHoldNote) ); + UpdateHoldNotes( iSongRow, fDeltaTime, vHoldNotesToGradeTogether ); + vHoldNotesToGradeTogether.clear(); + } + } + iRowOfLastHoldNote = iRow; + vHoldNotesToGradeTogether.push_back( trtn ); + } + + if( !vHoldNotesToGradeTogether.empty() ) + { + //LOG->Trace("UpdateHoldNotes since !vHoldNotesToGradeTogether.empty()"); + UpdateHoldNotes( iSongRow, fDeltaTime, vHoldNotesToGradeTogether ); + vHoldNotesToGradeTogether.clear(); + } + } + + { + // Why was this originally "BeatToNoteRowNotRounded"? It should be rounded. -Chris + /* We want to send the crossed row message exactly when we cross the row--not + * .5 before the row. Use a very slow song (around 2 BPM) as a test case: without + * rounding, autoplay steps early. -glenn */ + const int iRowNow = BeatToNoteRowNotRounded( m_pPlayerState->m_Position.m_fSongBeat ); + if( iRowNow >= 0 ) + { + if( GAMESTATE->IsPlayerEnabled(m_pPlayerState) ) + { + if(m_pPlayerState->m_Position.m_bDelay) + { + if( !m_bDelay ) + m_bDelay = true; + } + else + { + if(m_bDelay) + { + if(m_pPlayerState->m_PlayerController != PC_HUMAN) + { + CrossedRows( iRowNow-1, now ); + } + m_bDelay = false; + } + CrossedRows( iRowNow, now ); + } + } + } + } + + // Check for completely judged rows. + UpdateJudgedRows(); + + // Check for TapNote misses + if (!GAMESTATE->m_bInStepEditor) + { + UpdateTapNotesMissedOlderThan( GetMaxStepDistanceSeconds() ); + } + // process transforms that are waiting to be applied + ApplyWaitingTransforms(); +} + +// Update a group of holds with shared scoring/life. All of these holds will have the same start row. +void Player::UpdateHoldNotes( int iSongRow, float fDeltaTime, vector &vTN ) +{ + ASSERT( !vTN.empty() ); + + //LOG->Trace("--------------------------------"); + /* + LOG->Trace("[Player::UpdateHoldNotes] begins"); + LOG->Trace( ssprintf("song row %i, deltaTime = %f",iSongRow,fDeltaTime) ); + */ + + int iStartRow = vTN[0].iRow; + int iMaxEndRow = INT_MIN; + int iFirstTrackWithMaxEndRow = -1; + + TapNote::SubType subType = TapNote::SubType_Invalid; + FOREACH( TrackRowTapNote, vTN, trtn ) + { + int iTrack = trtn->iTrack; + ASSERT( iStartRow == trtn->iRow ); + TapNote &tn = *trtn->pTN; + int iEndRow = iStartRow + tn.iDuration; + if( subType == TapNote::SubType_Invalid ) + subType = tn.subType; + + /* All holds must be of the same subType because fLife is handled + * in different ways depending on the SubType. */ + ASSERT( tn.subType == subType ); + + if( iEndRow > iMaxEndRow ) + { + iMaxEndRow = iEndRow; + iFirstTrackWithMaxEndRow = iTrack; + } + } + + ASSERT( iFirstTrackWithMaxEndRow != -1 ); + //LOG->Trace( ssprintf("start row: %i; max/end row: = %i",iStartRow,iMaxEndRow) ); + //LOG->Trace( ssprintf("first track with max end row = %i",iFirstTrackWithMaxEndRow) ); + //LOG->Trace( ssprintf("max end row - start row (in beats) = %f",NoteRowToBeat(iMaxEndRow)-NoteRowToBeat(iStartRow)) ); + + FOREACH( TrackRowTapNote, vTN, trtn ) + { + TapNote &tn = *trtn->pTN; + + // set hold flags so NoteField can do intelligent drawing + tn.HoldResult.bHeld = false; + tn.HoldResult.bActive = false; + + int iRow = trtn->iRow; + //LOG->Trace( ssprintf("this row: %i",iRow) ); + + // If the song beat is in the range of this hold: + if( iRow <= iSongRow && iRow <= iMaxEndRow ) + { + //LOG->Trace( ssprintf("overlap time before: %f",tn.HoldResult.fOverlappedTime) ); + tn.HoldResult.fOverlappedTime += fDeltaTime; + //LOG->Trace( ssprintf("overlap time after: %f",tn.HoldResult.fOverlappedTime) ); + } + else + { + //LOG->Trace( "overlap time = 0" ); + tn.HoldResult.fOverlappedTime = 0; + } + } + + HoldNoteScore hns = vTN[0].pTN->HoldResult.hns; + float fLife = vTN[0].pTN->HoldResult.fLife; + + if( hns != HNS_None ) // if this HoldNote already has a result + { + //LOG->Trace("hold note has a result, skipping."); + return; // we don't need to update the logic for this group + } + + //LOG->Trace("hold note doesn't already have result, let's check."); + + //LOG->Trace( ssprintf("[C++] hold note score: %s",HoldNoteScoreToString(hns).c_str()) ); + //LOG->Trace(ssprintf("[Player::UpdateHoldNotes] fLife = %f",fLife)); + + bool bSteppedOnHead = true; + bool bHeadJudged = true; + FOREACH( TrackRowTapNote, vTN, trtn ) + { + TapNote &tn = *trtn->pTN; + TapNoteScore tns = tn.result.tns; + //LOG->Trace( ssprintf("[C++] tap note score: %s",StringConversion::ToString(tns).c_str()) ); + + // TODO: When using JUDGE_HOLD_NOTES_ON_SAME_ROW_TOGETHER, require that the whole row of + // taps was hit before activating this group of holds. + /* Something about the logic in this section is causing 192nd steps to + * fail for some odd reason. -aj */ + bSteppedOnHead &= (tns != TNS_Miss && tns != TNS_None); // did they step on the start of this hold? + bHeadJudged &= (tns != TNS_None); // has this hold really even started yet? + + /* + if(bSteppedOnHead) + LOG->Trace("[Player::UpdateHoldNotes] player stepped on head"); + else + LOG->Trace("[Player::UpdateHoldNotes] player didn't step on the head"); + */ + } + + bool bInitiatedNote; + if( REQUIRE_STEP_ON_HOLD_HEADS ) + { + // XXX HACK: Miniholds (a 64th or 192nd length hold) will not always + // register as Held, even if you hit the note. This is considered a + // major roadblock to adoption, so until a proper fix is found, + // DON'T REMOVE THIS HACK! -aj + /*if( iMaxEndRow-iStartRow <= 4 ) + bInitiatedNote = true; + else*/ + bInitiatedNote = bSteppedOnHead; + } + else + { + bInitiatedNote = true; + bHeadJudged = true; + } + + bool bIsHoldingButton = true; + FOREACH( TrackRowTapNote, vTN, trtn ) + { + int iTrack = trtn->iTrack; + + // TODO: Remove use of PlayerNumber. + PlayerNumber pn = m_pPlayerState->m_PlayerNumber; + + if( m_pPlayerState->m_PlayerController != PC_HUMAN ) + { + // TODO: Make the CPU miss sometimes. + if( m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) + { + STATSMAN->m_CurStageStats.m_bUsedAutoplay = true; + if( m_pPlayerStageStats != NULL ) + m_pPlayerStageStats->m_bDisqualified = true; + } + } + else + { + GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( iTrack, pn ); + // this previously read as bIsHoldingButton &= + // was there a specific reason for this? - Friez + bIsHoldingButton &= INPUTMAPPER->IsBeingPressed( GameI, m_pPlayerState->m_mp ); + } + } + + if( bInitiatedNote && fLife != 0 && bHeadJudged ) + { + //LOG->Trace("[Player::UpdateHoldNotes] initiated note, fLife != 0"); + /* This hold note is not judged and we stepped on its head. + * Update iLastHeldRow. Do this even if we're a little beyond the end + * of the hold note, to make sure iLastHeldRow is clamped to iEndRow + * if the hold note is held all the way. */ + FOREACH( TrackRowTapNote, vTN, trtn ) + { + TapNote &tn = *trtn->pTN; + int iEndRow = iStartRow + tn.iDuration; + + //LOG->Trace(ssprintf("trying for min between iSongRow (%i) and iEndRow (%i) (duration %i)",iSongRow,iEndRow,tn.iDuration)); + trtn->pTN->HoldResult.iLastHeldRow = min( iSongRow, iEndRow ); + } + } + + // If the song beat is in the range of this hold: + if( iStartRow <= iSongRow && iStartRow <= iMaxEndRow && bHeadJudged ) + { + switch( subType ) + { + case TapNote::hold_head_hold: + FOREACH( TrackRowTapNote, vTN, trtn ) + { + TapNote &tn = *trtn->pTN; + + // set hold flag so NoteField can do intelligent drawing + tn.HoldResult.bHeld = bIsHoldingButton && bInitiatedNote; + tn.HoldResult.bActive = bInitiatedNote; + } + + if( bInitiatedNote && bIsHoldingButton ) + { + //LOG->Trace("bInitiatedNote && bIsHoldingButton; Increasing hold life to MAX_HOLD_LIFE"); + // Increase life + fLife = MAX_HOLD_LIFE; // was 1 -aj + } + else + { + /* + LOG->Trace("Checklist:"); + if(bInitiatedNote) + LOG->Trace("[X] Initiated Note"); + else + LOG->Trace("[ ] Initiated Note"); + + if(bIsHoldingButton) + LOG->Trace("[X] Holding Button"); + else + LOG->Trace("[ ] Holding Button"); + */ + + // Decrease life + //LOG->Trace("fLife before minus: %f",fLife); + fLife -= fDeltaTime/GetWindowSeconds(TW_Hold); + //LOG->Trace("fLife before clamp: %f",fLife); + fLife = max( fLife, 0 ); // clamp + //LOG->Trace("fLife after: %f",fLife); + } + break; + case TapNote::hold_head_roll: + FOREACH( TrackRowTapNote, vTN, trtn ) + { + TapNote &tn = *trtn->pTN; + tn.HoldResult.bHeld = true; + tn.HoldResult.bActive = bInitiatedNote; + } + + // give positive life in Step(), not here. + + // Decrease life + fLife -= fDeltaTime/GetWindowSeconds(TW_Roll); + fLife = max( fLife, 0 ); // clamp + break; + /* + case TapNote::hold_head_mine: + break; + */ + default: + FAIL_M(ssprintf("Invalid tap note subtype: %i", subType)); + } + } + + // TODO: Cap the active time passed to the score keeper to the actual start time and end time of the hold. + if( vTN[0].pTN->HoldResult.bActive ) + { + float fSecondsActiveSinceLastUpdate = fDeltaTime * GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; + if( m_pPrimaryScoreKeeper ) + m_pPrimaryScoreKeeper->HandleHoldActiveSeconds( fSecondsActiveSinceLastUpdate ); + if( m_pSecondaryScoreKeeper ) + m_pSecondaryScoreKeeper->HandleHoldActiveSeconds( fSecondsActiveSinceLastUpdate ); + } + + // check for LetGo. If the head was missed completely, don't count an LetGo. + /* Why? If you never step on the head, then it will be left as HNS_None, + * which doesn't seem correct. */ + if( IMMEDIATE_HOLD_LET_GO ) + { + if( bInitiatedNote && fLife == 0 && bHeadJudged ) // the player has not pressed the button for a long time! + { + //LOG->Trace("LetGo from life == 0 (did initiate hold)"); + hns = HNS_LetGo; + } + } + + // score hold notes that have passed + if( iSongRow >= iMaxEndRow && bHeadJudged ) + { + bool bLetGoOfHoldNote = false; + + /* Score rolls that end with fLife == 0 as LetGo, even if + * HOLD_CHECKPOINTS is on. Rolls don't have iCheckpointsMissed set, so, + * unless we check Life == 0, rolls would always be scored as Held. */ + bool bAllowHoldCheckpoints; + switch( subType ) + { + DEFAULT_FAIL( subType ); + case TapNote::hold_head_hold: + bAllowHoldCheckpoints = true; + break; + case TapNote::hold_head_roll: + bAllowHoldCheckpoints = false; + break; + /* + case TapNote::hold_head_mine: + bAllowHoldCheckpoints = true; + break; + */ + } + + if( HOLD_CHECKPOINTS && bAllowHoldCheckpoints ) + { + //LOG->Trace("(hold checkpoints are allowed and enabled.)"); + int iCheckpointsHit = 0; + int iCheckpointsMissed = 0; + FOREACH( TrackRowTapNote, vTN, v ) + { + iCheckpointsHit += v->pTN->HoldResult.iCheckpointsHit; + iCheckpointsMissed += v->pTN->HoldResult.iCheckpointsMissed; + } + bLetGoOfHoldNote = iCheckpointsMissed > 0 || iCheckpointsHit == 0; + + // TRICKY: If the hold is so short that it has no checkpoints, + // then mark it as Held if the head was stepped on. + if( iCheckpointsHit == 0 && iCheckpointsMissed == 0 ) + bLetGoOfHoldNote = !bSteppedOnHead; + + /* + if(bLetGoOfHoldNote) + LOG->Trace("let go of hold note, life is 0"); + else + LOG->Trace("did not let go of hold note :D"); + */ + } + else + { + //LOG->Trace("(hold checkpoints disabled.)"); + bLetGoOfHoldNote = fLife == 0; + /* + if(bLetGoOfHoldNote) + LOG->Trace("let go of hold note, life is 0"); + else + LOG->Trace("did not let go of hold note :D"); + */ + } + + if( bInitiatedNote ) + { + if(!bLetGoOfHoldNote) + { + //LOG->Trace("initiated note and didn't let go"); + fLife = 1; // xxx: should be MAX_HOLD_LIFE instead? -aj + hns = HNS_Held; + bool bBright = m_pPlayerStageStats && m_pPlayerStageStats->m_iCurCombo>(int)BRIGHT_GHOST_COMBO_THRESHOLD; + if( m_pNoteField ) + { + FOREACH( TrackRowTapNote, vTN, trtn ) + { + int iTrack = trtn->iTrack; + m_pNoteField->DidHoldNote( iTrack, HNS_Held, bBright ); // bright ghost flash + } + } + } + + else + { + //LOG->Trace("initiated note and let go :("); + } + } + else if( SCORE_MISSED_HOLDS_AND_ROLLS ) + { + hns = HNS_LetGo; + } + else + { + hns = HNS_None; + } + } + + float fLifeFraction = fLife / MAX_HOLD_LIFE; + + FOREACH( TrackRowTapNote, vTN, trtn ) + { + TapNote &tn = *trtn->pTN; + tn.HoldResult.fLife = fLife; + tn.HoldResult.hns = hns; + // Stop the playing keysound for the hold note. + // I think this causes crashes too. -aj (Possibly fixed. -?) + + Preference *pVolume = Preference::GetPreferenceByName("SoundVolume"); + float fVol = pVolume->Get(); + + if( tn.iKeysoundIndex >= 0 && tn.iKeysoundIndex < (int) m_vKeysounds.size() ) + { + float factor = (tn.subType == TapNote::hold_head_roll ? 2.0f * fLifeFraction : 10.0f * fLifeFraction - 8.5f); + m_vKeysounds[tn.iKeysoundIndex].SetProperty ("Volume", max(0.0f, min(1.0f, factor)) * fVol); + } + } + + if( hns != HNS_None ) + { + //LOG->Trace("tap note scoring time."); + TapNote &tn = *vTN[0].pTN; + SetHoldJudgment( tn.result.tns, tn.HoldResult.hns, iFirstTrackWithMaxEndRow ); + HandleHoldScore( tn ); + //LOG->Trace("hold result = %s",StringConversion::ToString(tn.HoldResult.hns).c_str()); + } + //LOG->Trace("[Player::UpdateHoldNotes] ends"); +} + +void Player::ApplyWaitingTransforms() +{ + for( unsigned j=0; jm_ModsToApply.size(); j++ ) + { + const Attack &mod = m_pPlayerState->m_ModsToApply[j]; + PlayerOptions po; + // if re-adding noteskin changes, blank out po.m_sNoteSkin. -aj + po.FromString( mod.sModifiers ); + + float fStartBeat, fEndBeat; + mod.GetRealtimeAttackBeats( GAMESTATE->m_pCurSong, m_pPlayerState, fStartBeat, fEndBeat ); + fEndBeat = min( fEndBeat, m_NoteData.GetLastBeat() ); + + LOG->Trace( "Applying transform '%s' from %f to %f to '%s'", mod.sModifiers.c_str(), fStartBeat, fEndBeat, + GAMESTATE->m_pCurSong->GetTranslitMainTitle().c_str() ); + + // if re-adding noteskin changes, this is one place to edit -aj + + NoteDataUtil::TransformNoteData( m_NoteData, po, GAMESTATE->GetCurrentStyle()->m_StepsType, BeatToNoteRow(fStartBeat), BeatToNoteRow(fEndBeat) ); + } + m_pPlayerState->m_ModsToApply.clear(); +} + +void Player::DrawPrimitives() +{ + // TODO: Remove use of PlayerNumber. + PlayerNumber pn = m_pPlayerState->m_PlayerNumber; + + // May have both players in doubles (for battle play); only draw primary player. + if( GAMESTATE->GetCurrentStyle()->m_StyleType == StyleType_OnePlayerTwoSides && + pn != GAMESTATE->GetMasterPlayerNumber() ) + return; + + // Draw these below everything else. + // xxx: if NoteField Board is enabled and COMBO_UNDER_FIELD, we really want + // the combo under the field but over the notefield board. -aj + if( COMBO_UNDER_FIELD && m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind == 0 ) + { + if( m_sprCombo ) + m_sprCombo->Draw(); + } + + if( m_pAttackDisplay ) + m_pAttackDisplay->Draw(); + + if( TAP_JUDGMENTS_UNDER_FIELD ) + DrawTapJudgments(); + + if( HOLD_JUDGMENTS_UNDER_FIELD ) + DrawHoldJudgments(); + + float fTilt = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fPerspectiveTilt; + float fSkew = m_pPlayerState->m_PlayerOptions.GetCurrent().m_fSkew; + bool bReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(0)>0.5; + + DISPLAY->CameraPushMatrix(); + DISPLAY->PushMatrix(); + + float fCenterY = this->GetY()+(GRAY_ARROWS_Y_STANDARD+GRAY_ARROWS_Y_REVERSE)/2; + + DISPLAY->LoadMenuPerspective( 45, SCREEN_WIDTH, SCREEN_HEIGHT, SCALE(fSkew,0.f,1.f,this->GetX(),SCREEN_CENTER_X), fCenterY ); + + if( m_pNoteField && !IsOniDead() ) + { + float fOriginalY = m_pNoteField->GetY(); + + float fTiltDegrees = SCALE(fTilt,-1.f,+1.f,+30,-30) * (bReverse?-1:1); + + float fZoom = SCALE( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fEffects[PlayerOptions::EFFECT_MINI], 0.f, 1.f, 1.f, 0.5f ); + if( fTilt > 0 ) + fZoom *= SCALE( fTilt, 0.f, 1.f, 1.f, 0.9f ); + else + fZoom *= SCALE( fTilt, 0.f, -1.f, 1.f, 0.9f ); + + float fYOffset; + if( fTilt > 0 ) + fYOffset = SCALE( fTilt, 0.f, 1.f, 0.f, -45.f ) * (bReverse?-1:1); + else + fYOffset = SCALE( fTilt, 0.f, -1.f, 0.f, -20.f ) * (bReverse?-1:1); + + m_pNoteField->SetY( fOriginalY + fYOffset ); + m_pNoteField->SetZoom( fZoom ); + m_pNoteField->SetRotationX( fTiltDegrees ); + m_pNoteField->Draw(); + + m_pNoteField->SetY( fOriginalY ); + } + + DISPLAY->CameraPopMatrix(); + DISPLAY->PopMatrix(); + + // m_pNoteField->m_sprBoard->GetVisible() + if( !COMBO_UNDER_FIELD && m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind == 0 ) + if( m_sprCombo ) + m_sprCombo->Draw(); + + if( !(bool)TAP_JUDGMENTS_UNDER_FIELD ) + DrawTapJudgments(); + + if( !(bool)HOLD_JUDGMENTS_UNDER_FIELD ) + DrawHoldJudgments(); +} + +void Player::DrawTapJudgments() +{ + if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind > 0 ) + return; + + if( m_sprJudgment ) + m_sprJudgment->Draw(); +} + +void Player::DrawHoldJudgments() +{ + if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind > 0 ) + return; + + for( int c=0; cDraw(); +} + + +void Player::ChangeLife( TapNoteScore tns ) +{ + PlayerNumber pn = m_pPlayerState->m_PlayerNumber; + if( m_pLifeMeter ) + m_pLifeMeter->ChangeLife( tns ); + if( m_pCombinedLifeMeter ) + m_pCombinedLifeMeter->ChangeLife( pn, tns ); + + ChangeLifeRecord(); + + switch( tns ) + { + case TNS_None: + case TNS_Miss: + case TNS_CheckpointMiss: + case TNS_HitMine: + ++m_pPlayerState->m_iTapsMissedSinceLastHasteUpdate; + break; + default: + ++m_pPlayerState->m_iTapsHitSinceLastHasteUpdate; + break; + } +} + +void Player::ChangeLife( HoldNoteScore hns, TapNoteScore tns ) +{ + PlayerNumber pn = m_pPlayerState->m_PlayerNumber; + if( m_pLifeMeter ) + m_pLifeMeter->ChangeLife( hns, tns ); + if( m_pCombinedLifeMeter ) + m_pCombinedLifeMeter->ChangeLife( pn, hns, tns ); + + ChangeLifeRecord(); +} + +void Player::ChangeLifeRecord() +{ + PlayerNumber pn = m_pPlayerState->m_PlayerNumber; + float fLife = -1; + if( m_pLifeMeter ) + { + fLife = m_pLifeMeter->GetLife(); + } + else if( m_pCombinedLifeMeter ) + { + fLife = GAMESTATE->m_fTugLifePercentP1; + if( pn == PLAYER_2 ) + fLife = 1.0f - fLife; + } + if( fLife != -1 ) + if( m_pPlayerStageStats ) + m_pPlayerStageStats->SetLifeRecordAt( fLife, STATSMAN->m_CurStageStats.m_fStepsSeconds ); +} + +int Player::GetClosestNoteDirectional( int col, int iStartRow, int iEndRow, bool bAllowGraded, bool bForward ) const +{ + NoteData::const_iterator begin, end; + m_NoteData.GetTapNoteRange( col, iStartRow, iEndRow, begin, end ); + + if( !bForward ) + swap( begin, end ); + + while( begin != end ) + { + if( !bForward ) + --begin; + + // Is this the row we want? + do { + const TapNote &tn = begin->second; + if (!m_Timing->IsJudgableAtRow( begin->first )) + break; + // unsure if autoKeysounds should be excluded. -Wolfman2000 + if( tn.type == TapNote::empty || tn.type == TapNote::autoKeysound ) + break; + if( !bAllowGraded && tn.result.tns != TNS_None ) + break; + + return begin->first; + } while(0); + + if( bForward ) + ++begin; + } + + return -1; +} + +// Find the closest note to fBeat. +int Player::GetClosestNote( int col, int iNoteRow, int iMaxRowsAhead, int iMaxRowsBehind, bool bAllowGraded ) const +{ + // Start at iIndexStartLookingAt and search outward. + int iNextIndex = GetClosestNoteDirectional( col, iNoteRow, iNoteRow+iMaxRowsAhead, bAllowGraded, true ); + int iPrevIndex = GetClosestNoteDirectional( col, iNoteRow-iMaxRowsBehind, iNoteRow, bAllowGraded, false ); + + if( iNextIndex == -1 && iPrevIndex == -1 ) + return -1; + if( iNextIndex == -1 ) + return iPrevIndex; + if( iPrevIndex == -1 ) + return iNextIndex; + + // Get the current time, previous time, and next time. + float fNoteTime = m_pPlayerState->m_Position.m_fMusicSeconds ; + float fNextTime = m_Timing->GetElapsedTimeFromBeat(NoteRowToBeat(iNextIndex)); + float fPrevTime = m_Timing->GetElapsedTimeFromBeat(NoteRowToBeat(iPrevIndex)); + + /* Figure out which row is closer. */ + if( fabsf(fNoteTime-fNextTime) > fabsf(fNoteTime-fPrevTime) ) + return iPrevIndex; + else + return iNextIndex; +} + +int Player::GetClosestNonEmptyRowDirectional( int iStartRow, int iEndRow, bool /* bAllowGraded */, bool bForward ) const +{ + if( bForward ) + { + NoteData::all_tracks_iterator iter = m_NoteData.GetTapNoteRangeAllTracks( iStartRow, iEndRow ); + + while( !iter.IsAtEnd() ) + { + if( NoteDataWithScoring::IsRowCompletelyJudged(m_NoteData, iter.Row()) ) + { + ++iter; + continue; + } + if (!m_Timing->IsJudgableAtRow(iter.Row())) + { + ++iter; + continue; + } + return iter.Row(); + } + } + else + { + NoteData::all_tracks_reverse_iterator iter = m_NoteData.GetTapNoteRangeAllTracksReverse( iStartRow, iEndRow ); + + while( !iter.IsAtEnd() ) + { + if( NoteDataWithScoring::IsRowCompletelyJudged(m_NoteData, iter.Row()) ) + { + ++iter; + continue; + } + return iter.Row(); + } + } + + return -1; +} + +// Find the closest note to fBeat. +int Player::GetClosestNonEmptyRow( int iNoteRow, int iMaxRowsAhead, int iMaxRowsBehind, bool bAllowGraded ) const +{ + // Start at iIndexStartLookingAt and search outward. + int iNextRow = GetClosestNonEmptyRowDirectional( iNoteRow, iNoteRow+iMaxRowsAhead, bAllowGraded, true ); + int iPrevRow = GetClosestNonEmptyRowDirectional( iNoteRow-iMaxRowsBehind, iNoteRow, bAllowGraded, false ); + + if( iNextRow == -1 && iPrevRow == -1 ) + return -1; + if( iNextRow == -1 ) + return iPrevRow; + if( iPrevRow == -1 ) + return iNextRow; + + // Get the current time, previous time, and next time. + float fNoteTime = m_pPlayerState->m_Position.m_fMusicSeconds; + float fNextTime = m_Timing->GetElapsedTimeFromBeat(NoteRowToBeat(iNextRow)); + float fPrevTime = m_Timing->GetElapsedTimeFromBeat(NoteRowToBeat(iPrevRow)); + + /* Figure out which row is closer. */ + if( fabsf(fNoteTime-fNextTime) > fabsf(fNoteTime-fPrevTime) ) + return iPrevRow; + else + return iNextRow; +} + +bool Player::IsOniDead() const +{ + // If we're playing on oni and we've died, do nothing. + return GAMESTATE->m_SongOptions.GetCurrent().m_LifeType == SongOptions::LIFE_BATTERY && m_pPlayerStageStats && m_pPlayerStageStats->m_bFailed; +} + +void Player::Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease ) +{ + if( IsOniDead() ) + return; + + DEBUG_ASSERT_M( col >= 0 && col <= m_NoteData.GetNumTracks(), ssprintf("%i, %i", col, m_NoteData.GetNumTracks()) ); + + m_vbFretIsDown[ col ] = !bRelease; + + + // Handle changing fret during a strum + if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 ) + { + LOG->Trace( "StrumTry" ); + StepStrumHopo( col, row, tm, bHeld, bRelease, ButtonType_StrumFretsChanged ); + } + + // Handle hammer-ons and pull-offs + const float fPositionSeconds = m_pPlayerState->m_Position.m_fMusicSeconds - tm.Ago(); + int iHopoCol = -1; + bool bDoHopo = + m_pPlayerState->m_fLastHopoNoteMusicSeconds != -1 && + fPositionSeconds <= m_pPlayerState->m_fLastHopoNoteMusicSeconds + g_fTimingWindowHopo; + if( bDoHopo ) + { + // do a Hopo: + // - on pressed fret is no higher fret is held + // - on next lowest held fret when the highest held fret is released + bool bHigherFretIsDown = false; + for( int i=col+1; i=0; i-- ) + { + if( m_vbFretIsDown[i] ) + { + iHopoCol = i; + break; + } + } + if( iHopoCol == -1 ) + bDoHopo = false; + } + } + + if( bDoHopo ) + Hopo( iHopoCol, row, tm, bHeld, bRelease ); + + // Check if this fret breaks all active holds. + if( !bRelease ) + { + const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat; + const int iSongRow = BeatToNoteRow( fSongBeat ); + + int iMaxHoldCol = -1; + int iNumColsHeld = 0; + + // Score all active holds to NotHeld + for( int iTrack=0; iTracksecond; + if( tn.HoldResult.bActive ) + { + iMaxHoldCol = iTrack; + iNumColsHeld++; + } + } + } + + // Any frets to the right of an active hold will break the hold. + if( col > iMaxHoldCol || iNumColsHeld >= 2 ) + ScoreAllActiveHoldsLetGo(); + } +} + + +void Player::Strum( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease ) +{ + if( bRelease ) + return; + + if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 ) + { + DoStrumMiss(); + } + + m_pPlayerState->m_fLastStrumMusicSeconds = m_pPlayerState->m_Position.m_fMusicSeconds; + + StepStrumHopo( col, row, tm, bHeld, bRelease, ButtonType_StrumFretsChanged ); +} + +void Player::DoTapScoreNone() +{ + Message msg( "ScoreNone" ); + MESSAGEMAN->Broadcast( msg ); + + const int iOldCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0; + const int iOldMissCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurMissCombo : 0; + + /* The only real way to tell if a mine has been scored is if it has disappeared + * but this only works for hit mines so update the scores for avoided mines here. */ + if( m_pPrimaryScoreKeeper ) + m_pPrimaryScoreKeeper->HandleTapScoreNone(); + if( m_pSecondaryScoreKeeper ) + m_pSecondaryScoreKeeper->HandleTapScoreNone(); + + SendComboMessages( iOldCombo, iOldMissCombo ); + + + if( m_pLifeMeter ) + m_pLifeMeter->HandleTapScoreNone(); + // TODO: Remove use of PlayerNumber + PlayerNumber pn = PLAYER_INVALID; + if( m_pCombinedLifeMeter ) + m_pCombinedLifeMeter->HandleTapScoreNone( pn ); + + if( PENALIZE_TAP_SCORE_NONE ) + { + SetJudgment( TNS_Miss, -1, 0 ); + // the ScoreKeeper will subtract points later. + } +} + +void Player::DoStrumMiss() +{ + m_pPlayerState->m_fLastStrumMusicSeconds = -1; + DoTapScoreNone(); + + ScoreAllActiveHoldsLetGo(); +} + +void Player::ScoreAllActiveHoldsLetGo() +{ + if( PENALIZE_TAP_SCORE_NONE ) + { + const float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat; + const int iSongRow = BeatToNoteRow( fSongBeat ); + + // Score all active holds to NotHeld + for( int iTrack=0; iTracksecond; + if( tn.HoldResult.bActive ) + { + tn.HoldResult.hns = HNS_LetGo; + tn.HoldResult.fLife = 0; + + SetHoldJudgment( tn.result.tns, tn.HoldResult.hns, iTrack ); + HandleHoldScore( tn ); + } + } + } + } +} + +void Player::PlayKeysound( const TapNote &tn, TapNoteScore score ) +{ + // tap note must have keysound + if( tn.iKeysoundIndex >= 0 && tn.iKeysoundIndex < (int) m_vKeysounds.size() ) + { + // handle a case for hold notes + if( tn.type == TapNote::hold_head ) + { + // if the hold is not already held + if( tn.HoldResult.hns == HNS_None ) + { + // if the hold is already activated + TapNoteScore tns = tn.result.tns; + if( tns != TNS_None && tns != TNS_Miss && score == TNS_None ) + { + // the sound must also be already playing + if( m_vKeysounds[tn.iKeysoundIndex].IsPlaying() ) + { + // if all of these conditions are met, don't play the sound. + return; + } + } + } + } + m_vKeysounds[tn.iKeysoundIndex].Play(); + Preference *pVolume = Preference::GetPreferenceByName("SoundVolume"); + float fVol = pVolume->Get(); + m_vKeysounds[tn.iKeysoundIndex].SetProperty ("Volume", fVol); + } +} + +void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease, Player::ButtonType pbt ) +{ + if( IsOniDead() ) + return; + + // Do everything that depends on a RageTimer here; + // set your breakpoints somewhere after this block. + const float fLastBeatUpdate = m_pPlayerState->m_Position.m_LastBeatUpdate.Ago(); + const float fPositionSeconds = m_pPlayerState->m_Position.m_fMusicSeconds - tm.Ago(); + const float fTimeSinceStep = tm.Ago(); + + switch( pbt ) + { + DEFAULT_FAIL(pbt); + case ButtonType_Step: + break; + case ButtonType_StrumFretsChanged: + case ButtonType_Hopo: + // releasing should hit regular notes, not lifts + bRelease = false; + break; + } + + float fSongBeat = m_pPlayerState->m_Position.m_fSongBeat; + + if( GAMESTATE->m_pCurSong ) + { + fSongBeat = GAMESTATE->m_pCurSong->m_SongTiming.GetBeatFromElapsedTime( fPositionSeconds ); + + if( GAMESTATE->m_pCurSteps[m_pPlayerState->m_PlayerNumber] ) + fSongBeat = m_Timing->GetBeatFromElapsedTime( fPositionSeconds ); + } + + const int iSongRow = row == -1 ? BeatToNoteRow( fSongBeat ) : row; + + if( col != -1 && !bRelease ) + { + // Update roll life + // Let's not check the whole array every time. + // Instead, only check 1 beat back. Even 1 is overkill. + // Just update the life here and let Update judge the roll. + const int iStartCheckingAt = max( 0, iSongRow-BeatToNoteRow(1) ); + NoteData::TrackMap::iterator begin, end; + m_NoteData.GetTapNoteRangeInclusive( col, iStartCheckingAt, iSongRow+1, begin, end ); + for( ; begin != end; ++begin ) + { + TapNote &tn = begin->second; + if( tn.type != TapNote::hold_head ) + continue; + + switch( tn.subType ) + { + DEFAULT_FAIL( tn.subType ); + case TapNote::hold_head_hold: + continue; + case TapNote::hold_head_roll: + break; + } + + const int iRow = begin->first; + + HoldNoteScore hns = tn.HoldResult.hns; + if( hns != HNS_None ) // if this HoldNote already has a result + continue; // we don't need to update the logic for this one + + // if they got a bad score or haven't stepped on the corresponding tap yet + const TapNoteScore tns = tn.result.tns; + bool bInitiatedNote = true; + if( REQUIRE_STEP_ON_HOLD_HEADS ) + bInitiatedNote = tns != TNS_None && tns != TNS_Miss; // did they step on the start? + const int iEndRow = iRow + tn.iDuration; + + if( bInitiatedNote && tn.HoldResult.fLife != 0 ) + { + /* This hold note is not judged and we stepped on its head. Update iLastHeldRow. + * Do this even if we're a little beyond the end of the hold note, to make sure + * iLastHeldRow is clamped to iEndRow if the hold note is held all the way. */ + //LOG->Trace("setting iLastHeldRow to min of iSongRow (%i) and iEndRow (%i)",iSongRow,iEndRow); + tn.HoldResult.iLastHeldRow = min( iSongRow, iEndRow ); + } + + // If the song beat is in the range of this hold: + if( iRow <= iSongRow && iRow <= iEndRow ) + { + if( bInitiatedNote ) + { + // Increase life + tn.HoldResult.fLife = 1; + + if( ROLL_BODY_INCREMENTS_COMBO ) + { + // increment combo + const int iOldCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0; + const int iOldMissCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurMissCombo : 0; + + if( m_pPlayerStageStats ) + { + m_pPlayerStageStats->m_iCurCombo++; + m_pPlayerStageStats->m_iCurMissCombo = 0; + } + + SendComboMessages( iOldCombo, iOldMissCombo ); + if( m_pPlayerStageStats ) + SetCombo( m_pPlayerStageStats->m_iCurCombo, m_pPlayerStageStats->m_iCurMissCombo ); + + bool bBright = m_pPlayerStageStats && m_pPlayerStageStats->m_iCurCombo>(int)BRIGHT_GHOST_COMBO_THRESHOLD; + if( m_pNoteField ) + m_pNoteField->DidHoldNote( col, HNS_Held, bBright ); + } + } + break; + } + } + } + + // Count calories for this step, unless we're being called because a button + // is held over a mine or being released. + // TODO: Move calorie counting into a ScoreKeeper? + if( m_pPlayerStageStats && m_pPlayerState && !bHeld && !bRelease ) + { + // TODO: remove use of PlayerNumber + PlayerNumber pn = m_pPlayerState->m_PlayerNumber; + Profile *pProfile = PROFILEMAN->GetProfile( pn ); + + int iNumTracksHeld = 0; + for( int t=0; tGetCurrentStyle()->StyleInputToGameInput( t, pn ); + const float fSecsHeld = INPUTMAPPER->GetSecsHeld( GameI ); + if( fSecsHeld > 0 && fSecsHeld < m_fTimingWindowJump ) + iNumTracksHeld++; + } + + float fCals = 0; + switch( iNumTracksHeld ) + { + case 0: + // autoplay is on, or this is a computer player + iNumTracksHeld = 1; + // fall through + default: + { + float fCalsFor100Lbs = SCALE( iNumTracksHeld, 1, 2, 0.023f, 0.077f ); + float fCalsFor200Lbs = SCALE( iNumTracksHeld, 1, 2, 0.041f, 0.133f ); + fCals = SCALE( pProfile->GetCalculatedWeightPounds(), 100.f, 200.f, fCalsFor100Lbs, fCalsFor200Lbs ); + } + break; + } + + m_pPlayerStageStats->m_fCaloriesBurned += fCals; + m_pPlayerStageStats->m_iNumControllerSteps ++; + } + + // Check for step on a TapNote + /* XXX: This seems wrong. If a player steps twice quickly and two notes are + * close together in the same column then it is possible for the two notes + * to be graded out of order. + * Two possible fixes: + * 1. Adjust the fSongBeat (or the resulting note row) backward by + * iStepSearchRows and search forward two iStepSearchRows lengths, + * disallowing graded. This doesn't seem right because if a second note has + * passed, an earlier one should not be graded. + * 2. Clamp the distance searched backward to the previous row graded. + * Either option would fundamentally change the grading of two quick notes + * "jack hammers." Hmm. + */ + const int iStepSearchRows = max( + BeatToNoteRow( m_Timing->GetBeatFromElapsedTime( m_pPlayerState->m_Position.m_fMusicSeconds + StepSearchDistance ) ) - iSongRow, + iSongRow - BeatToNoteRow( m_Timing->GetBeatFromElapsedTime( m_pPlayerState->m_Position.m_fMusicSeconds - StepSearchDistance ) ) + ) + ROWS_PER_BEAT; + int iRowOfOverlappingNoteOrRow = row; + if( row == -1 ) + { + switch( pbt ) + { + DEFAULT_FAIL(pbt); + case ButtonType_StrumFretsChanged: + iRowOfOverlappingNoteOrRow = GetClosestNonEmptyRow( iSongRow, iStepSearchRows, iStepSearchRows, false ); + break; + case ButtonType_Hopo: + case ButtonType_Step: + iRowOfOverlappingNoteOrRow = GetClosestNote( col, iSongRow, iStepSearchRows, iStepSearchRows, false ); + break; + } + } + + // calculate TapNoteScore + TapNoteScore score = TNS_None; + + if( iRowOfOverlappingNoteOrRow != -1 ) + { + // compute the score for this hit + float fNoteOffset = 0.0f; + // we need this later if we are autosyncing + const float fStepBeat = NoteRowToBeat( iRowOfOverlappingNoteOrRow ); + const float fStepSeconds = m_Timing->GetElapsedTimeFromBeat(fStepBeat); + + if( row == -1 ) + { + // We actually stepped on the note this long ago: + //fTimeSinceStep + + /* GAMESTATE->m_fMusicSeconds is the music time as of GAMESTATE->m_LastBeatUpdate. Figure + * out what the music time is as of now. */ + const float fCurrentMusicSeconds = m_pPlayerState->m_Position.m_fMusicSeconds + (fLastBeatUpdate*GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate); + + // ... which means it happened at this point in the music: + const float fMusicSeconds = fCurrentMusicSeconds - fTimeSinceStep * GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; + + // The offset from the actual step in seconds: + fNoteOffset = (fStepSeconds - fMusicSeconds) / GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; // account for music rate + /* + LOG->Trace("step was %.3f ago, music is off by %f: %f vs %f, step was %f off", + fTimeSinceStep, GAMESTATE->m_LastBeatUpdate.Ago()/GAMESTATE->m_SongOptions.m_fMusicRate, + fStepSeconds, fMusicSeconds, fNoteOffset ); + */ + } + + const float fSecondsFromExact = fabsf( fNoteOffset ); + + TapNote tnDummy = TAP_ORIGINAL_TAP; + TapNote *pTN = NULL; + switch( pbt ) + { + DEFAULT_FAIL(pbt); + case ButtonType_StrumFretsChanged: + pTN = &tnDummy; + break; + case ButtonType_Hopo: + case ButtonType_Step: + NoteData::iterator iter = m_NoteData.FindTapNote( col, iRowOfOverlappingNoteOrRow ); + DEBUG_ASSERT( iter!= m_NoteData.end(col) ); + pTN = &iter->second; + break; + } + + switch( m_pPlayerState->m_PlayerController ) + { + case PC_HUMAN: + switch( pTN->type ) + { + case TapNote::mine: + // Stepped too close to mine? + if( !bRelease && ( REQUIRE_STEP_ON_MINES == !bHeld ) && + fSecondsFromExact <= GetWindowSeconds(TW_Mine) && + m_Timing->IsJudgableAtRow(iSongRow)) + score = TNS_HitMine; + break; + case TapNote::attack: + if( !bRelease && fSecondsFromExact <= GetWindowSeconds(TW_Attack) && !pTN->result.bHidden ) + score = AllowW1() ? TNS_W1 : TNS_W2; // sentinel + break; + case TapNote::hold_head: + // oh wow, this was causing the trigger before the hold heads + // bug. (It was fNoteOffset > 0.f before) -DaisuMaster + if( !REQUIRE_STEP_ON_HOLD_HEADS && ( fNoteOffset <= GetWindowSeconds( TW_W5 ) && GetWindowSeconds( TW_W5 ) != 0 ) ) + { + score = TNS_W1; + break; + } + // Fall through to default. + default: + if( (pTN->type == TapNote::lift) == bRelease ) + { + if( fSecondsFromExact <= GetWindowSeconds(TW_W1) ) score = TNS_W1; + else if( fSecondsFromExact <= GetWindowSeconds(TW_W2) ) score = TNS_W2; + else if( fSecondsFromExact <= GetWindowSeconds(TW_W3) ) score = TNS_W3; + else if( fSecondsFromExact <= GetWindowSeconds(TW_W4) ) score = TNS_W4; + else if( fSecondsFromExact <= GetWindowSeconds(TW_W5) ) score = TNS_W5; + } + break; + } + break; + + case PC_CPU: + case PC_AUTOPLAY: + score = PlayerAI::GetTapNoteScore( m_pPlayerState ); + + /* XXX: This doesn't make sense. + * Step should only be called in autoplay for hit notes. */ +#if 0 + // GetTapNoteScore always returns TNS_W1 in autoplay. + // If the step is far away, don't judge it. + if( m_pPlayerState->m_PlayerController == PC_AUTOPLAY && + fSecondsFromExact > GetWindowSeconds(TW_W5) ) + { + score = TNS_None; + break; + } +#endif + + // TRICKY: We're asking the AI to judge mines. Consider TNS_W4 and + // below as "mine was hit" and everything else as "mine was avoided" + if( pTN->type == TapNote::mine ) + { + // The CPU hits a lot of mines. Only consider hitting the + // first mine for a row. We know we're the first mine if + // there are are no mines to the left of us. + for( int t=0; t= get_to_avoid ) + return; // avoided + else + score = TNS_HitMine; + } + + if( pTN->type == TapNote::attack && score > TNS_W4 ) + score = TNS_W2; // sentinel + + /* AI will generate misses here. Don't handle a miss like a regular + * note because we want the judgment animation to appear delayed. + * Instead, return early if AI generated a miss, and let + * UpdateTapNotesMissedOlderThan() detect and handle the misses. */ + if( score == TNS_Miss ) + return; + + // Put some small, random amount in fNoteOffset so that demonstration + // show a mix of late and early. - Chris (StepMania r15628) + //fNoteOffset = randomf( -0.1f, 0.1f ); + // Since themes may use the offset in a visual graph, the above + // behavior is not the best thing to do. Instead, random numbers + // should be generated based on the TapNoteScore, so that they can + // logically match up with the current timing windows. -aj + { + float fWindowW1 = GetWindowSeconds(TW_W1); + float fWindowW2 = GetWindowSeconds(TW_W2); + float fWindowW3 = GetWindowSeconds(TW_W3); + float fWindowW4 = GetWindowSeconds(TW_W4); + float fWindowW5 = GetWindowSeconds(TW_W5); + + // W1 is the top judgment, there is no overlap. + if( score == TNS_W1 ) + fNoteOffset = randomf(-fWindowW1, fWindowW1); + else + { + // figure out overlap. + float fLowerBound = 0.0f; // negative upper limit + float fUpperBound = 0.0f; // positive lower limit + float fCompareWindow = 0.0f; // filled in here: + if( score == TNS_W2 ) + { + fLowerBound = -fWindowW1; + fUpperBound = fWindowW1; + fCompareWindow = fWindowW2; + } + else if( score == TNS_W3 ) + { + fLowerBound = -fWindowW2; + fUpperBound = fWindowW2; + fCompareWindow = fWindowW3; + } + else if( score == TNS_W4 ) + { + fLowerBound = -fWindowW3; + fUpperBound = fWindowW3; + fCompareWindow = fWindowW4; + } + else if( score == TNS_W5 ) + { + fLowerBound = -fWindowW4; + fUpperBound = fWindowW4; + fCompareWindow = fWindowW5; + } + float f1 = randomf(-fCompareWindow, fLowerBound); + float f2 = randomf(fUpperBound, fCompareWindow); + + if(randomf() * 100 >= 50) + fNoteOffset = f1; + else + fNoteOffset = f2; + } + } + + break; + + /* + case PC_REPLAY: + // based on where we are, see what grade to get. + score = PlayerAI::GetTapNoteScore( m_pPlayerState ); + // row is the current row, col is current column (track) + fNoteOffset = TapNoteOffset attribute + break; + */ + default: + FAIL_M(ssprintf("Invalid player controller type: %i", m_pPlayerState->m_PlayerController)); + } + + switch( pbt ) + { + DEFAULT_FAIL(pbt); + case ButtonType_StrumFretsChanged: + { + bool bNoteRowMatchesFrets = true; + int iFirstNoteCol = -1; + for( int i=0; iGetCurrentStyle()->m_iColsPerPlayer; i++ ) + { + const TapNote &tn = m_NoteData.GetTapNote( i, iRowOfOverlappingNoteOrRow ); + bool bIsNote = (tn.type != TapNote::empty); + if( iFirstNoteCol == -1 && bIsNote ) + iFirstNoteCol = i; + + // Extra notes to the left (higher up on the string) can be held without penalty. It's necessary to hold + // the extra frets or pull-offs. + if( iFirstNoteCol == -1 ) + continue; + + bool bNoteMatchesFret = m_vbFretIsDown[i] == bIsNote; + if( !bNoteMatchesFret ) + { + bNoteRowMatchesFrets = false; + break; + } + } + ASSERT( iFirstNoteCol != -1 ); + if( !bNoteRowMatchesFrets ) + { + score = TNS_None; + } + else + { + int iLastNoteCol = -1; + for( int i=GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer-1; i>=0; i-- ) + { + const TapNote &tn = m_NoteData.GetTapNote( i, iRowOfOverlappingNoteOrRow ); + bool bIsNote = (tn.type != TapNote::empty); + if( bIsNote ) + { + iLastNoteCol = i; + break; + } + } + + m_pPlayerState->m_fLastHopoNoteMusicSeconds = fStepSeconds; + m_pPlayerState->m_iLastHopoNoteCol = iLastNoteCol; + } + } + break; + case ButtonType_Hopo: + { + // only can hopo on a row with one note + if( m_NoteData.GetNumTapNotesInRow(iRowOfOverlappingNoteOrRow) != 1 ) + { + score = TNS_None; + break; + } + + // con't hopo on the same note 2x in a row + if( col == m_pPlayerState->m_iLastHopoNoteCol ) + { + score = TNS_None; + break; + } + + const TapNote &tn = m_NoteData.GetTapNote( col, iRowOfOverlappingNoteOrRow ); + ASSERT( tn.type != TapNote::empty ); + + int iRowsAgoLastNote = 100000; // TODO: find more reasonable value based on HOPO_CHAIN_SECONDS? + NoteData::all_tracks_reverse_iterator iter = m_NoteData.GetTapNoteRangeAllTracksReverse( iRowsAgoLastNote-iRowsAgoLastNote, iRowOfOverlappingNoteOrRow-1 ); + ASSERT( !iter.IsAtEnd() ); // there must have been a note that started the hopo + if( !NoteDataWithScoring::IsRowCompletelyJudged(m_NoteData, iter.Row()) ) + { + score = TNS_None; + break; + } + + const TapNoteResult &lastTNR = NoteDataWithScoring::LastTapNoteWithResult( m_NoteData, iter.Row() ).result; + if( lastTNR.tns <= TNS_Miss ) + { + score = TNS_None; + break; + } + + m_pPlayerState->m_fLastHopoNoteMusicSeconds = fStepSeconds; + m_pPlayerState->m_iLastHopoNoteCol = col; + } + break; + case ButtonType_Step: + break; + } + + // handle attack notes + if( pTN->type == TapNote::attack && score == TNS_W2 ) + { + score = TNS_None; // don't score this as anything + + m_soundAttackLaunch.Play(); + + // put attack in effect + Attack attack( + ATTACK_LEVEL_1, + -1, // now + pTN->fAttackDurationSeconds, + pTN->sAttackModifiers, + true, + false + ); + + // TODO: Remove use of PlayerNumber + PlayerNumber pnToAttack = OPPOSITE_PLAYER[m_pPlayerState->m_PlayerNumber]; + PlayerState *pPlayerStateToAttack = GAMESTATE->m_pPlayerState[pnToAttack]; + pPlayerStateToAttack->LaunchAttack( attack ); + + // remove all TapAttacks on this row + for( int t=0; tm_PlayerController == PC_HUMAN && score >= TNS_W3 ) + AdjustSync::HandleAutosync( fNoteOffset, fStepSeconds ); + + // Do game-specific and mode-specific score mapping. + score = GAMESTATE->GetCurrentGame()->MapTapNoteScore( score ); + if( score == TNS_W1 && !GAMESTATE->ShowW1() ) + score = TNS_W2; + + + if( score != TNS_None ) + { + switch( pbt ) + { + DEFAULT_FAIL(pbt); + case ButtonType_StrumFretsChanged: + for( int t=0; tresult.tns = score; + pTN->result.fTapNoteOffset = -fNoteOffset; + break; + } + } + + m_LastTapNoteScore = score; + if( GAMESTATE->GetCurrentGame()->m_bCountNotesSeparately ) + { + if( pTN->type != TapNote::mine ) + { + const bool bBlind = (m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind != 0); + // XXX: This is the wrong combo for shared players. + // STATSMAN->m_CurStageStats.m_Player[pn] might work, but could be wrong. + const bool bBright = ( m_pPlayerStageStats && m_pPlayerStageStats->m_iCurCombo > int(BRIGHT_GHOST_COMBO_THRESHOLD) ) || bBlind; + if( m_pNoteField ) + m_pNoteField->DidTapNote( col, bBlind? TNS_W1:score, bBright ); + if( score >= TNS_W3 || bBlind ) + HideNote( col, iRowOfOverlappingNoteOrRow ); + } + } + else if( NoteDataWithScoring::IsRowCompletelyJudged(m_NoteData, iRowOfOverlappingNoteOrRow) ) + { + FlashGhostRow( iRowOfOverlappingNoteOrRow ); + } + } + + // check for hopo end + if( score <= TNS_Miss ) + { + m_pPlayerState->ClearHopoState(); + } + + // check for strum end + if( score != TNS_None ) + { + switch( pbt ) + { + DEFAULT_FAIL(pbt); + case ButtonType_Step: + break; + case ButtonType_StrumFretsChanged: + m_pPlayerState->m_fLastStrumMusicSeconds = -1; + break; + case ButtonType_Hopo: + break; + } + } + + if( score == TNS_None ) + { + switch( pbt ) + { + DEFAULT_FAIL(pbt); + case ButtonType_Step: + DoTapScoreNone(); + break; + case ButtonType_StrumFretsChanged: + case ButtonType_Hopo: + break; + } + + } + + if( !bRelease ) + { + /* Search for keyed sounds separately. Play the nearest note. */ + /* XXX: This isn't quite right. As per the above XXX for iRowOfOverlappingNote, if iRowOfOverlappingNote + * is set to a previous note, the keysound could have changed and this would cause the wrong one to play, + * in essence playing two sounds in the opposite order. Maybe this should always perform the search. Still, + * even that doesn't seem quite right since it would then play the same (new) keysound twice which would + * sound wrong even though the notes were judged as being correct, above. Fixing the above problem would + * fix this one as well. */ + int iHeadRow; + if( iRowOfOverlappingNoteOrRow != -1 && score != TNS_None ) + { + // just pressing a note, use that row. + // in other words, iRowOfOverlappingNoteOrRow = iRowOfOverlappingNoteOrRow + } + else if ( m_NoteData.IsHoldNoteAtRow( col, iSongRow, &iHeadRow ) ) + { + // stepping on a hold, use it! + iRowOfOverlappingNoteOrRow = iHeadRow; + } + else + { + // or else find the closest note. + iRowOfOverlappingNoteOrRow = GetClosestNote( col, iSongRow, MAX_NOTE_ROW, MAX_NOTE_ROW, true ); + } + if( iRowOfOverlappingNoteOrRow != -1 ) + { + switch( pbt ) + { + DEFAULT_FAIL(pbt); + case ButtonType_StrumFretsChanged: + for( int i=0; iStep( iLastFret, score ); + } + break; + case ButtonType_Step: + m_pNoteField->Step( col, score ); + break; + case ButtonType_Hopo: + // no animation + break; + } + } + Message msg( "Step" ); + msg.SetParam( "PlayerNumber", m_pPlayerState->m_PlayerNumber ); + msg.SetParam( "MultiPlayer", m_pPlayerState->m_mp ); + msg.SetParam( "Column", col ); + MESSAGEMAN->Broadcast( msg ); + } +} + +void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds ) +{ + //LOG->Trace( "Steps::UpdateTapNotesMissedOlderThan(%f)", fMissIfOlderThanThisBeat ); + int iMissIfOlderThanThisRow; + const float fEarliestTime = m_pPlayerState->m_Position.m_fMusicSeconds - fMissIfOlderThanSeconds; + { + bool bFreeze, bDelay; + float fMissIfOlderThanThisBeat; + float fThrowAway; + int iWarpBeginRow; + float fWarpLength; + m_Timing->GetBeatAndBPSFromElapsedTime( fEarliestTime, fMissIfOlderThanThisBeat, fThrowAway, bFreeze, bDelay, iWarpBeginRow, fWarpLength ); + + iMissIfOlderThanThisRow = BeatToNoteRow( fMissIfOlderThanThisBeat ); + if( bFreeze || bDelay ) + { + /* If there is a freeze on iMissIfOlderThanThisIndex, include this index too. + * Otherwise we won't show misses for tap notes on freezes until the + * freeze finishes. */ + if( !bDelay ) + iMissIfOlderThanThisRow++; + } + } + + NoteData::all_tracks_iterator &iter = *m_pIterNeedsTapJudging; + + for( ; !iter.IsAtEnd() && iter.Row() < iMissIfOlderThanThisRow; ++iter ) + { + TapNote &tn = *iter; + + if( !NeedsTapJudging(tn) ) + continue; + + // Ignore all notes in WarpSegments or FakeSegments. + if (!m_Timing->IsJudgableAtRow(iter.Row())) + continue; + + if( tn.type == TapNote::mine ) + { + tn.result.tns = TNS_AvoidMine; + + /* The only real way to tell if a mine has been scored is if it has disappeared + * but this only works for hit mines so update the scores for avoided mines here. */ + if( m_pPrimaryScoreKeeper ) + m_pPrimaryScoreKeeper->HandleTapScore( tn ); + if( m_pSecondaryScoreKeeper ) + m_pSecondaryScoreKeeper->HandleTapScore( tn ); + } + else + { + tn.result.tns = TNS_Miss; + } + } +} + +void Player::UpdateJudgedRows() +{ + const int iEndRow = BeatToNoteRow( m_pPlayerState->m_Position.m_fSongBeat ); + bool bAllJudged = true; + const bool bSeparately = GAMESTATE->GetCurrentGame()->m_bCountNotesSeparately; + + { + NoteData::all_tracks_iterator iter = *m_pIterUnjudgedRows; + int iLastSeenRow = -1; + for( ; !iter.IsAtEnd() && iter.Row() <= iEndRow; ++iter ) + { + int iRow = iter.Row(); + + // Do not judge arrows in WarpSegments or FakeSegments + if (!m_Timing->IsJudgableAtRow(iRow)) + continue; + + if( iLastSeenRow != iRow ) + { + iLastSeenRow = iRow; + + // crossed a nonempty row + if( !NoteDataWithScoring::IsRowCompletelyJudged(m_NoteData, iRow) ) + { + bAllJudged = false; + continue; + } + if( bAllJudged ) + *m_pIterUnjudgedRows = iter; + if( m_pJudgedRows->JudgeRow(iRow) ) + continue; + const TapNoteResult &lastTNR = NoteDataWithScoring::LastTapNoteWithResult( m_NoteData, iRow ).result; + + if( lastTNR.tns < TNS_Miss ) + continue; + if( bSeparately ) + { + for( int iTrack = 0; iTrack < m_NoteData.GetNumTracks(); ++iTrack ) + { + const TapNote &tn = m_NoteData.GetTapNote( iTrack, iRow ); + if (tn.type == TapNote::empty || + tn.type == TapNote::mine || + tn.type == TapNote::autoKeysound) continue; + SetJudgment( tn.result.tns, iTrack, tn.result.fTapNoteOffset ); + } + } + else + { + SetJudgment( lastTNR.tns, m_NoteData.GetFirstTrackWithTapOrHoldHead(iRow), lastTNR.fTapNoteOffset ); + } + HandleTapRowScore( iRow ); + } + } + } + + // handle mines. + { + bAllJudged = true; + set setSounds; + NoteData::all_tracks_iterator iter = *m_pIterUnjudgedMineRows; // copy + int iLastSeenRow = -1; + for( ; !iter.IsAtEnd() && iter.Row() <= iEndRow; ++iter ) + { + int iRow = iter.Row(); + + // Do not worry about mines in WarpSegments or FakeSegments + if (!m_Timing->IsJudgableAtRow(iRow)) + continue; + + TapNote &tn = *iter; + + if( iRow != iLastSeenRow ) + { + iLastSeenRow = iRow; + if( bAllJudged ) + *m_pIterUnjudgedMineRows = iter; + } + + bool bMineNotHidden = tn.type == TapNote::mine && !tn.result.bHidden; + if( !bMineNotHidden ) + continue; + + switch( tn.result.tns ) + { + DEFAULT_FAIL( tn.result.tns ); + case TNS_None: + bAllJudged = false; + continue; + case TNS_AvoidMine: + SetMineJudgment( tn.result.tns ); + continue; + case TNS_HitMine: + SetMineJudgment( tn.result.tns ); + break; + } + if( m_pNoteField ) + m_pNoteField->DidTapNote( iter.Track(), tn.result.tns, false ); + + if( tn.iKeysoundIndex >= 0 && tn.iKeysoundIndex < (int) m_vKeysounds.size() ) + setSounds.insert( &m_vKeysounds[tn.iKeysoundIndex] ); + else if( g_bEnableMineSoundPlayback ) + setSounds.insert( &m_soundMine ); + + /* Attack Mines: + * Only difference is these launch an attack rather than affecting + * the lifebar. All the other mine impacts (score, dance points, + * etc.) are still applied. */ + if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_bTransforms[PlayerOptions::TRANSFORM_ATTACKMINES] ) + { + const float fAttackRunTime = ATTACK_RUN_TIME_MINE; + + Attack attMineAttack; + attMineAttack.sModifiers = ApplyRandomAttack(); + attMineAttack.fStartSecond = ATTACK_STARTS_NOW; + attMineAttack.fSecsRemaining = fAttackRunTime; + + m_pPlayerState->LaunchAttack( attMineAttack ); + } + else + ChangeLife( tn.result.tns ); + + if( m_pScoreDisplay ) + m_pScoreDisplay->OnJudgment( tn.result.tns ); + if( m_pSecondaryScoreDisplay ) + m_pSecondaryScoreDisplay->OnJudgment( tn.result.tns ); + + // Make sure hit mines affect the dance points. + if( m_pPrimaryScoreKeeper ) + m_pPrimaryScoreKeeper->HandleTapScore( tn ); + if( m_pSecondaryScoreKeeper ) + m_pSecondaryScoreKeeper->HandleTapScore( tn ); + tn.result.bHidden = true; + } + + for (RageSound *sound : setSounds) + { + // Only play one copy of each mine sound at a time per player. + sound->Stop(); + sound->Play(); + } + } +} + +void Player::FlashGhostRow( int iRow ) +{ + TapNoteScore lastTNS = NoteDataWithScoring::LastTapNoteWithResult( m_NoteData, iRow ).result.tns; + const bool bBlind = (m_pPlayerState->m_PlayerOptions.GetCurrent().m_fBlind != 0); + const bool bBright = ( m_pPlayerStageStats && m_pPlayerStageStats->m_iCurCombo > int(BRIGHT_GHOST_COMBO_THRESHOLD) ) || bBlind; + + for( int iTrack = 0; iTrack < m_NoteData.GetNumTracks(); ++iTrack ) + { + const TapNote &tn = m_NoteData.GetTapNote( iTrack, iRow ); + + if( tn.type == TapNote::empty || tn.type == TapNote::mine || tn.type == TapNote::fake ) + continue; + if( m_pNoteField ) + m_pNoteField->DidTapNote( iTrack, lastTNS, bBright ); + if( lastTNS >= TNS_W3 || bBlind ) + HideNote( iTrack, iRow ); + } +} + +void Player::CrossedRows( int iLastRowCrossed, const RageTimer &now ) +{ + //LOG->Trace( "Player::CrossedRows %d %d", iFirstRowCrossed, iLastRowCrossed ); + + NoteData::all_tracks_iterator &iter = *m_pIterUncrossedRows; + int iLastSeenRow = -1; + for( ; !iter.IsAtEnd() && iter.Row() <= iLastRowCrossed; ++iter ) + { + // Apply InitialHoldLife. + TapNote &tn = *iter; + int iRow = iter.Row(); + int iTrack = iter.Track(); + switch( tn.type ) + { + case TapNote::hold_head: + { + tn.HoldResult.fLife = INITIAL_HOLD_LIFE; + if( !REQUIRE_STEP_ON_HOLD_HEADS ) + { + PlayerNumber pn = m_pPlayerState->m_PlayerNumber; + GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( iTrack, pn ); + if( PREFSMAN->m_fPadStickSeconds > 0.f ) + { + float fSecsHeld = INPUTMAPPER->GetSecsHeld( GameI, m_pPlayerState->m_mp ); + if( fSecsHeld >= PREFSMAN->m_fPadStickSeconds ) + Step( iTrack, -1, now - PREFSMAN->m_fPadStickSeconds, true, false ); + } + else if( INPUTMAPPER->IsBeingPressed(GameI, m_pPlayerState->m_mp) ) + { + Step( iTrack, -1, now, true, false ); + } + } + break; + } + case TapNote::mine: + { + // Hold the panel while crossing a mine will cause the mine to explode + // TODO: Remove use of PlayerNumber. + PlayerNumber pn = m_pPlayerState->m_PlayerNumber; + GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( iTrack, pn ); + if( PREFSMAN->m_fPadStickSeconds > 0 ) + { + float fSecsHeld = INPUTMAPPER->GetSecsHeld( GameI, m_pPlayerState->m_mp ); + if( fSecsHeld >= PREFSMAN->m_fPadStickSeconds ) + Step( iTrack, -1, now - PREFSMAN->m_fPadStickSeconds, true, false ); + } + else + { + if( INPUTMAPPER->IsBeingPressed(GameI, m_pPlayerState->m_mp) ) + Step( iTrack, iRow, now, true, false ); + } + break; + } + default: break; + } + + if( iRow != iLastSeenRow ) + { + // crossed a not-empty row + + // If we're doing random vanish, randomise notes on the fly. + if( m_pPlayerState->m_PlayerOptions.GetCurrent().m_fAppearances[PlayerOptions::APPEARANCE_RANDOMVANISH]==1 ) + RandomizeNotes( iRow ); + + // check to see if there's a note at the crossed row + if( m_pPlayerState->m_PlayerController != PC_HUMAN ) + { + if (tn.type != TapNote::empty && + tn.type != TapNote::fake && + tn.type != TapNote::autoKeysound && + tn.result.tns == TNS_None && + this->m_Timing->IsJudgableAtRow(iRow) ) + { + Step( iTrack, iRow, now, false, false ); + if( m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) + { + if( m_pPlayerStageStats ) + m_pPlayerStageStats->m_bDisqualified = true; + } + } + } + + // handle autokeysounds here (if not in the editor). + if (!GAMESTATE->m_bInStepEditor) + { + for (int t = 0; t < m_NoteData.GetNumTracks(); ++t) + { + const TapNote &tap = m_NoteData.GetTapNote(t, iRow); + if (tap.type == TapNote::autoKeysound) + { + PlayKeysound(tap, TNS_None); + } + } + } + } + } + + + /* Update hold checkpoints + * + * TODO: Move this to a separate function. */ + if( HOLD_CHECKPOINTS && m_pPlayerState->m_PlayerController != PC_AUTOPLAY ) + { + int iCheckpointFrequencyRows = ROWS_PER_BEAT/2; + if( CHECKPOINTS_USE_TICKCOUNTS ) + { + int tickCurrent = m_Timing->GetTickcountAtRow( iLastRowCrossed ); + // There are some charts that don't want tickcounts involved at all. + iCheckpointFrequencyRows = (tickCurrent > 0 ? ROWS_PER_BEAT / tickCurrent : 0); + } + else if( CHECKPOINTS_USE_TIME_SIGNATURES ) + { + TimeSignatureSegment * tSignature = m_Timing->GetTimeSignatureSegmentAtRow( iLastRowCrossed ); + + // Most songs are in 4/4 time. The frequency for checking tick counts should reflect that. + iCheckpointFrequencyRows = ROWS_PER_BEAT * tSignature->GetDen() / (tSignature->GetNum() * 4); + } + + if( iCheckpointFrequencyRows > 0 ) + { + // "the first row after the start of the range that lands on a beat" + int iFirstCheckpointInRange = ((m_iFirstUncrossedRow+iCheckpointFrequencyRows-1) + /iCheckpointFrequencyRows) * iCheckpointFrequencyRows; + + // "the last row or first row earlier that lands on a beat" + int iLastCheckpointInRange = ((iLastRowCrossed)/iCheckpointFrequencyRows) + * iCheckpointFrequencyRows; + + for( int r = iFirstCheckpointInRange; r <= iLastCheckpointInRange; r += iCheckpointFrequencyRows ) + { + //LOG->Trace( "%d...", r ); + vector viColsWithHold; + int iNumHoldsHeldThisRow = 0; + int iNumHoldsMissedThisRow = 0; + + // start at r-1 so that we consider holds whose end rows are equal to the checkpoint row + NoteData::all_tracks_iterator nIter = m_NoteData.GetTapNoteRangeAllTracks( r-1, r, true ); + for( ; !nIter.IsAtEnd(); ++nIter ) + { + TapNote &tn = *nIter; + if( tn.type != TapNote::hold_head ) + continue; + + int iStartRow = nIter.Row(); + int iEndRow = iStartRow + tn.iDuration; + int iTrack = nIter.Track(); + + // "the first row after the hold head that lands on a beat" + int iFirstCheckpointOfHold = ((iStartRow+iCheckpointFrequencyRows)/iCheckpointFrequencyRows) + * iCheckpointFrequencyRows; + + // "the end row or the first earlier row that lands on a beat" + int iLastCheckpointOfHold = ((iEndRow)/iCheckpointFrequencyRows) + * iCheckpointFrequencyRows; + + // count the end of the hold as a checkpoint + bool bHoldOverlapsRow = iFirstCheckpointOfHold <= r && r <= iLastCheckpointOfHold; + if( !bHoldOverlapsRow ) + continue; + + + + viColsWithHold.push_back( iTrack ); + + if( tn.HoldResult.fLife > 0 ) + { + ++iNumHoldsHeldThisRow; + ++tn.HoldResult.iCheckpointsHit; + } + else + { + ++iNumHoldsMissedThisRow; + ++tn.HoldResult.iCheckpointsMissed; + } + } + GAMESTATE->SetProcessedTimingData(this->m_Timing); + + // TODO: Find a better way of handling hold checkpoints with other taps. + if( !viColsWithHold.empty() && ( CHECKPOINTS_TAPS_SEPARATE_JUDGMENT || m_NoteData.GetNumTapNotesInRow( iLastRowCrossed ) == 0 ) ) + { + HandleHoldCheckpoint(r, + iNumHoldsHeldThisRow, + iNumHoldsMissedThisRow, + viColsWithHold ); + } + } + } + } + + m_iFirstUncrossedRow = iLastRowCrossed+1; +} + +void Player::RandomizeNotes( int iNoteRow ) +{ + // change the row to look ahead from based upon their speed mod + /* This is incorrect: if m_fScrollSpeed is 0.5, we'll never change + * any odd rows, and if it's 2, we'll shuffle each row twice. */ + int iNewNoteRow = iNoteRow + ROWS_PER_BEAT*2; + iNewNoteRow = int( iNewNoteRow / m_pPlayerState->m_PlayerOptions.GetCurrent().m_fScrollSpeed ); + + int iNumOfTracks = m_NoteData.GetNumTracks(); + for( int t=0; t+1 < iNumOfTracks; t++ ) + { + /* Only swap a tap and an empty. */ + NoteData::iterator iter = m_NoteData.FindTapNote( t, iNewNoteRow ); + if( iter == m_NoteData.end(t) || iter->second.type != TapNote::tap ) + continue; + + const int iSwapWith = RandomInt( iNumOfTracks ); + + // Make sure this is empty. + if( m_NoteData.FindTapNote(iSwapWith, iNewNoteRow) != m_NoteData.end(iSwapWith) ) + continue; + + /* Make sure the destination row isn't in the middle of a hold. */ + if( m_NoteData.IsHoldNoteAtRow(iSwapWith, iNoteRow) ) + continue; + + m_NoteData.SetTapNote( iSwapWith, iNewNoteRow, iter->second ); + m_NoteData.RemoveTapNote( t, iter ); + } +} + +void Player::HandleTapRowScore( unsigned row ) +{ + bool bNoCheating = true; +#ifdef DEBUG + bNoCheating = false; +#endif + + // Do not score rows in WarpSegments or FakeSegments + if (!m_Timing->IsJudgableAtRow(row)) + return; + + if( GAMESTATE->m_bDemonstrationOrJukebox ) + bNoCheating = false; + // don't accumulate points if AutoPlay is on. + if( bNoCheating && m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) + return; + + TapNoteScore scoreOfLastTap = NoteDataWithScoring::LastTapNoteWithResult(m_NoteData, row).result.tns; + const int iOldCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0; + const int iOldMissCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurMissCombo : 0; + + if( scoreOfLastTap == TNS_Miss ) + m_LastTapNoteScore = TNS_Miss; + + for( int track = 0; track < m_NoteData.GetNumTracks(); ++track ) + { + const TapNote &tn = m_NoteData.GetTapNote( track, row ); + // Mines cannot be handled here. + if (tn.type == TapNote::empty || + tn.type == TapNote::fake || + tn.type == TapNote::mine || + tn.type == TapNote::autoKeysound) + continue; + if( m_pPrimaryScoreKeeper ) + m_pPrimaryScoreKeeper->HandleTapScore( tn ); + if( m_pSecondaryScoreKeeper ) + m_pSecondaryScoreKeeper->HandleTapScore( tn ); + } + + if( m_pPrimaryScoreKeeper != NULL ) + m_pPrimaryScoreKeeper->HandleTapRowScore( m_NoteData, row ); + if( m_pSecondaryScoreKeeper != NULL ) + m_pSecondaryScoreKeeper->HandleTapRowScore( m_NoteData, row ); + + const int iCurCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0; + const int iCurMissCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurMissCombo : 0; + + SendComboMessages( iOldCombo, iOldMissCombo ); + + if( m_pPlayerStageStats ) + { + SetCombo( iCurCombo, iCurMissCombo ); + } + +#define CROSSED( x ) (iOldCombo=x) + if ( CROSSED(100) ) + SCREENMAN->PostMessageToTopScreen( SM_100Combo, 0 ); + else if( CROSSED(200) ) + SCREENMAN->PostMessageToTopScreen( SM_200Combo, 0 ); + else if( CROSSED(300) ) + SCREENMAN->PostMessageToTopScreen( SM_300Combo, 0 ); + else if( CROSSED(400) ) + SCREENMAN->PostMessageToTopScreen( SM_400Combo, 0 ); + else if( CROSSED(500) ) + SCREENMAN->PostMessageToTopScreen( SM_500Combo, 0 ); + else if( CROSSED(600) ) + SCREENMAN->PostMessageToTopScreen( SM_600Combo, 0 ); + else if( CROSSED(700) ) + SCREENMAN->PostMessageToTopScreen( SM_700Combo, 0 ); + else if( CROSSED(800) ) + SCREENMAN->PostMessageToTopScreen( SM_800Combo, 0 ); + else if( CROSSED(900) ) + SCREENMAN->PostMessageToTopScreen( SM_900Combo, 0 ); + else if( CROSSED(1000)) + SCREENMAN->PostMessageToTopScreen( SM_1000Combo, 0 ); + else if( (iOldCombo / 100) < (iCurCombo / 100) && iCurCombo > 1000 ) + SCREENMAN->PostMessageToTopScreen( SM_ComboContinuing, 0 ); +#undef CROSSED + + // new max combo + if( m_pPlayerStageStats ) + m_pPlayerStageStats->m_iMaxCombo = max(m_pPlayerStageStats->m_iMaxCombo, iCurCombo); + + /* Use the real current beat, not the beat we've been passed. That's because + * we want to record the current life/combo to the current time; eg. if it's + * a MISS, the beat we're registering is in the past, but the life is changing + * now. We need to include time from previous songs in a course, so we + * can't use GAMESTATE->m_fMusicSeconds. Use fStepsSeconds instead. */ + if( m_pPlayerStageStats ) + m_pPlayerStageStats->UpdateComboList( STATSMAN->m_CurStageStats.m_fStepsSeconds, false ); + + if( m_pScoreDisplay ) + { + if( m_pPlayerStageStats ) + m_pScoreDisplay->SetScore( m_pPlayerStageStats->m_iScore ); + m_pScoreDisplay->OnJudgment( scoreOfLastTap ); + } + if( m_pSecondaryScoreDisplay ) + { + if( m_pPlayerStageStats ) + m_pSecondaryScoreDisplay->SetScore( m_pPlayerStageStats->m_iScore ); + m_pSecondaryScoreDisplay->OnJudgment( scoreOfLastTap ); + } + + ChangeLife( scoreOfLastTap ); +} + +void Player::HandleHoldCheckpoint(int iRow, + int iNumHoldsHeldThisRow, + int iNumHoldsMissedThisRow, + const vector &viColsWithHold ) +{ + bool bNoCheating = true; +#ifdef DEBUG + bNoCheating = false; +#endif + + // WarpSegments and FakeSegments aren't judged in any way. + if (!m_Timing->IsJudgableAtRow(iRow)) + return; + + // don't accumulate combo if AutoPlay is on. + if( bNoCheating && m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) + return; + + const int iOldCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0; + const int iOldMissCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurMissCombo : 0; + + if( m_pPrimaryScoreKeeper ) + m_pPrimaryScoreKeeper->HandleHoldCheckpointScore(m_NoteData, + iRow, + iNumHoldsHeldThisRow, + iNumHoldsMissedThisRow ); + if( m_pSecondaryScoreKeeper ) + m_pSecondaryScoreKeeper->HandleHoldCheckpointScore(m_NoteData, + iRow, + iNumHoldsHeldThisRow, + iNumHoldsMissedThisRow ); + + if( iNumHoldsMissedThisRow == 0 ) + { + // added for http://ssc.ajworld.net/sm-ssc/bugtracker/view.php?id=16 -aj + if( CHECKPOINTS_FLASH_ON_HOLD ) + { + FOREACH_CONST( int, viColsWithHold, i ) + { + bool bBright = m_pPlayerStageStats + && m_pPlayerStageStats->m_iCurCombo>(int)BRIGHT_GHOST_COMBO_THRESHOLD; + if( m_pNoteField ) + m_pNoteField->DidHoldNote( *i, HNS_Held, bBright ); + } + } + } + + SendComboMessages( iOldCombo, iOldMissCombo ); + + if( m_pPlayerStageStats ) + { + SetCombo( m_pPlayerStageStats->m_iCurCombo, m_pPlayerStageStats->m_iCurMissCombo ); + m_pPlayerStageStats->UpdateComboList( STATSMAN->m_CurStageStats.m_fStepsSeconds, false ); + } + + ChangeLife( iNumHoldsMissedThisRow == 0? TNS_CheckpointHit:TNS_CheckpointMiss ); + + SetJudgment( iNumHoldsMissedThisRow == 0? TNS_CheckpointHit:TNS_CheckpointMiss, viColsWithHold[0], 0 ); +} + +void Player::HandleHoldScore( const TapNote &tn ) +{ + HoldNoteScore holdScore = tn.HoldResult.hns; + TapNoteScore tapScore = tn.result.tns; + bool bNoCheating = true; +#ifdef DEBUG + bNoCheating = false; +#endif + + if( GAMESTATE->m_bDemonstrationOrJukebox ) + bNoCheating = false; + // don't accumulate points if AutoPlay is on. + if( bNoCheating && m_pPlayerState->m_PlayerController == PC_AUTOPLAY ) + return; + + if( m_pPrimaryScoreKeeper ) + m_pPrimaryScoreKeeper->HandleHoldScore( tn ); + if( m_pSecondaryScoreKeeper ) + m_pSecondaryScoreKeeper->HandleHoldScore( tn ); + + if( m_pScoreDisplay ) + { + if( m_pPlayerStageStats ) + m_pScoreDisplay->SetScore( m_pPlayerStageStats->m_iScore ); + m_pScoreDisplay->OnJudgment( holdScore, tapScore ); + } + if( m_pSecondaryScoreDisplay ) + { + if( m_pPlayerStageStats ) + m_pSecondaryScoreDisplay->SetScore( m_pPlayerStageStats->m_iScore ); + m_pSecondaryScoreDisplay->OnJudgment( holdScore, tapScore ); + } + + ChangeLife( holdScore, tapScore ); +} + +float Player::GetMaxStepDistanceSeconds() +{ + float fMax = 0; + fMax = max( fMax, GetWindowSeconds(TW_W5) ); + fMax = max( fMax, GetWindowSeconds(TW_W4) ); + fMax = max( fMax, GetWindowSeconds(TW_W3) ); + fMax = max( fMax, GetWindowSeconds(TW_W2) ); + fMax = max( fMax, GetWindowSeconds(TW_W1) ); + float f = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate * fMax; + return f + m_fMaxInputLatencySeconds; +} + +void Player::FadeToFail() +{ + if( m_pNoteField ) + m_pNoteField->FadeToFail(); + + // clear miss combo + SetCombo( 0, 0 ); +} + +void Player::CacheAllUsedNoteSkins() +{ + if( m_pNoteField ) + m_pNoteField->CacheAllUsedNoteSkins(); +} + +void Player::SetMineJudgment( TapNoteScore tns ) +{ + if( m_bSendJudgmentAndComboMessages ) + { + Message msg("Judgment"); + msg.SetParam( "Player", m_pPlayerState->m_PlayerNumber ); + msg.SetParam( "TapNoteScore", tns ); + MESSAGEMAN->Broadcast( msg ); + if( m_pPlayerStageStats ) + { + SetCombo( m_pPlayerStageStats->m_iCurCombo, m_pPlayerStageStats->m_iCurMissCombo ); + } + } +} + +void Player::SetJudgment( TapNoteScore tns, int iTrack, float fTapNoteOffset ) +{ + if( m_bSendJudgmentAndComboMessages ) + { + Message msg("Judgment"); + msg.SetParam( "Player", m_pPlayerState->m_PlayerNumber ); + msg.SetParam( "MultiPlayer", m_pPlayerState->m_mp ); + msg.SetParam( "FirstTrack", iTrack ); + msg.SetParam( "TapNoteScore", tns ); + msg.SetParam( "Early", fTapNoteOffset < 0.0f ); + msg.SetParam( "TapNoteOffset", fTapNoteOffset ); + MESSAGEMAN->Broadcast( msg ); + } +} + +void Player::SetHoldJudgment( TapNoteScore tns, HoldNoteScore hns, int iTrack ) +{ + ASSERT( iTrack < (int)m_vpHoldJudgment.size() ); + if( m_vpHoldJudgment[iTrack] ) + m_vpHoldJudgment[iTrack]->SetHoldJudgment( hns ); + + if( m_bSendJudgmentAndComboMessages ) + { + Message msg("Judgment"); + msg.SetParam( "Player", m_pPlayerState->m_PlayerNumber ); + msg.SetParam( "MultiPlayer", m_pPlayerState->m_mp ); + msg.SetParam( "FirstTrack", iTrack ); + msg.SetParam( "TapNoteScore", tns ); + msg.SetParam( "HoldNoteScore", hns ); + MESSAGEMAN->Broadcast( msg ); + } +} + +void Player::SetCombo( int iCombo, int iMisses ) +{ + if( m_iLastSeenCombo == -1 ) // first update, don't set bIsMilestone=true + m_iLastSeenCombo = iCombo; + + bool b25Milestone = false; + bool b50Milestone = false; + bool b100Milestone = false; + bool b250Milestone = false; + bool b1000Milestone = false; + for( int i=m_iLastSeenCombo+1; i<=iCombo; i++ ) + { + if( i < 600 ) + { + b25Milestone |= ((i % 25) == 0); + b50Milestone |= ((i % 50) == 0); + b100Milestone |= ((i % 100) == 0); + b250Milestone |= ((i % 250) == 0); + } + else + { + b1000Milestone |= ((i % 200) == 0); + } + } + m_iLastSeenCombo = iCombo; + + if( b25Milestone ) + this->PlayCommand( "TwentyFiveMilestone"); + if( b50Milestone ) + this->PlayCommand( "FiftyMilestone"); + if( b100Milestone ) + this->PlayCommand( "HundredMilestone" ); + if( b250Milestone ) + this->PlayCommand( "TwoHundredFiftyMilestone"); + if( b1000Milestone ) + this->PlayCommand( "ThousandMilestone" ); + + /* Colored combo logic differs between Songs and Courses. + * Songs: + * The theme decides how far into the song the combo color should appear. + * (PERCENT_UNTIL_COLOR_COMBO) + * + * Courses: + * PERCENT_UNTIL_COLOR_COMBO refers to how long through the course the + * combo color should appear (scaling to the number of songs). This may + * not be desired behavior, however. -aj + * + * TODO: Add a metric that determines Course combo colors logic? + * Or possibly move the logic to a Lua function? -aj */ + bool bPastBeginning = false; + if( GAMESTATE->IsCourseMode() ) + { + int iSongIndexStartColoring = GAMESTATE->m_pCurCourse->GetEstimatedNumStages(); + iSongIndexStartColoring = + static_cast(floor(iSongIndexStartColoring*PERCENT_UNTIL_COLOR_COMBO)); + bPastBeginning = GAMESTATE->GetCourseSongIndex() >= iSongIndexStartColoring; + } + else + { + bPastBeginning = m_pPlayerState->m_Position.m_fMusicSeconds + > GAMESTATE->m_pCurSong->m_fMusicLengthSeconds * PERCENT_UNTIL_COLOR_COMBO; + } + + if( m_bSendJudgmentAndComboMessages ) + { + Message msg("Combo"); + if( iCombo ) + msg.SetParam( "Combo", iCombo ); + if( iMisses ) + msg.SetParam( "Misses", iMisses ); + if( bPastBeginning && m_pPlayerStageStats->FullComboOfScore(TNS_W1) ) + msg.SetParam( "FullComboW1", true ); + if( bPastBeginning && m_pPlayerStageStats->FullComboOfScore(TNS_W2) ) + msg.SetParam( "FullComboW2", true ); + if( bPastBeginning && m_pPlayerStageStats->FullComboOfScore(TNS_W3) ) + msg.SetParam( "FullComboW3", true ); + if( bPastBeginning && m_pPlayerStageStats->FullComboOfScore(TNS_W4) ) + msg.SetParam( "FullComboW4", true ); + this->HandleMessage( msg ); + } +} + +RString Player::ApplyRandomAttack() +{ + if( GAMESTATE->m_RandomAttacks.size() < 1 ) + return ""; + + //int iAttackToUse = rand() % GAMESTATE->m_RandomAttacks.size(); + DateTime now = DateTime::GetNowDate(); + int iSeed = now.tm_hour * now.tm_min * now.tm_sec * now.tm_mday; + RandomGen rnd( GAMESTATE->m_iStageSeed * iSeed ); + int iAttackToUse = rnd() % GAMESTATE->m_RandomAttacks.size(); + return GAMESTATE->m_RandomAttacks[iAttackToUse]; +} + +// lua start +#include "LuaBinding.h" + +/** @brief Allow Lua to have access to the Player. */ +class LunaPlayer: public Luna +{ +public: + static int SetActorWithJudgmentPosition( T* p, lua_State *L ) + { + Actor *pActor = Luna::check(L, 1); + p->SetActorWithJudgmentPosition(pActor); + return 0; + } + static int SetActorWithComboPosition( T* p, lua_State *L ) + { + Actor *pActor = Luna::check(L, 1); + p->SetActorWithComboPosition(pActor); + return 0; + } + static int GetPlayerTimingData( T* p, lua_State *L ) + { + p->GetPlayerTimingData().PushSelf(L); + return 1; + } + + LunaPlayer() + { + ADD_METHOD( SetActorWithJudgmentPosition ); + ADD_METHOD( SetActorWithComboPosition ); + ADD_METHOD( GetPlayerTimingData ); + } +}; + +LUA_REGISTER_DERIVED_CLASS( Player, ActorFrame ) +// lua end + +/* + * (c) 2001-2006 Chris Danford, Steve Checkoway + * 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. + */ From 7356adefd7b2f25e9e3606c566198aaa77534f3f Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:54:28 -0400 Subject: [PATCH 020/281] Handle the set loops here. --- src/MusicWheel.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MusicWheel.cpp b/src/MusicWheel.cpp index 7c2999724c..13df51e7f2 100644 --- a/src/MusicWheel.cpp +++ b/src/MusicWheel.cpp @@ -460,9 +460,9 @@ void MusicWheel::GetSongList( vector &arraySongs, SortOrder so ) set vStepsType; SongUtil::GetPlayableStepsTypes( pSong, vStepsType ); - FOREACHS( StepsType, vStepsType, st ) + for (StepsType const &type : vStepsType) { - if(pSong->HasStepsType(*st)) + if(pSong->HasStepsType(type)) { arraySongs.push_back( pSong ); break; @@ -856,8 +856,8 @@ void MusicWheel::BuildWheelItemDatas( vector &arrayWheelIt WID.m_Flags.bEdits = false; set vStepsType; SongUtil::GetPlayableStepsTypes( WID.m_pSong, vStepsType ); - FOREACHS( StepsType, vStepsType, st ) - WID.m_Flags.bEdits |= WID.m_pSong->HasEdits( *st ); + for (StepsType const &type : vStepsType) + WID.m_Flags.bEdits |= WID.m_pSong->HasEdits( type ); WID.m_Flags.iStagesForSong = GameState::GetNumStagesMultiplierForSong( WID.m_pSong ); } else if( WID.m_pCourse != NULL ) From acb312fa14f72b01e7283e9ecb6f44a232fe878b Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:56:25 -0400 Subject: [PATCH 021/281] No need for indexer: use for each. Also no need to have a separate ref variable. --- src/MusicWheel.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/MusicWheel.cpp b/src/MusicWheel.cpp index 13df51e7f2..8e8dc86445 100644 --- a/src/MusicWheel.cpp +++ b/src/MusicWheel.cpp @@ -847,24 +847,23 @@ void MusicWheel::BuildWheelItemDatas( vector &arrayWheelIt } // init music status icons - for( unsigned i=0; im_pSong != NULL ) { - WID.m_Flags.bHasBeginnerOr1Meter = WID.m_pSong->IsEasy( GAMESTATE->GetCurrentStyle()->m_StepsType ) && SHOW_EASY_FLAG; - WID.m_Flags.bEdits = false; + WID->m_Flags.bHasBeginnerOr1Meter = WID->m_pSong->IsEasy( GAMESTATE->GetCurrentStyle()->m_StepsType ) && SHOW_EASY_FLAG; + WID->m_Flags.bEdits = false; set vStepsType; - SongUtil::GetPlayableStepsTypes( WID.m_pSong, vStepsType ); + SongUtil::GetPlayableStepsTypes( WID->m_pSong, vStepsType ); for (StepsType const &type : vStepsType) - WID.m_Flags.bEdits |= WID.m_pSong->HasEdits( type ); - WID.m_Flags.iStagesForSong = GameState::GetNumStagesMultiplierForSong( WID.m_pSong ); + WID->m_Flags.bEdits |= WID->m_pSong->HasEdits( type ); + WID->m_Flags.iStagesForSong = GameState::GetNumStagesMultiplierForSong( WID->m_pSong ); } - else if( WID.m_pCourse != NULL ) + else if( WID->m_pCourse != NULL ) { - WID.m_Flags.bHasBeginnerOr1Meter = false; - WID.m_Flags.bEdits = WID.m_pCourse->IsAnEdit(); - WID.m_Flags.iStagesForSong = 1; + WID->m_Flags.bHasBeginnerOr1Meter = false; + WID->m_Flags.bEdits = WID->m_pCourse->IsAnEdit(); + WID->m_Flags.iStagesForSong = 1; } } } From 1560244fdde6ceb65e4a125869cc96b96316bfbb Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:57:27 -0400 Subject: [PATCH 022/281] Loop this cleanly. --- src/LuaBinding.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LuaBinding.cpp b/src/LuaBinding.cpp index a264675164..df44868641 100644 --- a/src/LuaBinding.cpp +++ b/src/LuaBinding.cpp @@ -16,8 +16,8 @@ namespace /* Register base classes first. */ map mapToRegister; - FOREACHS( LuaBinding*, *m_Subscribers.m_pSubscribers, p ) - mapToRegister[(*p)->GetClassName()] = (*p); + for (LuaBinding *binding : *m_Subscribers.m_pSubscribers) + mapToRegister[binding->GetClassName()] = binding; set setRegisteredAlready; From e6ee027af073f0030451b89dd8b058e0f4fae629 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:58:51 -0400 Subject: [PATCH 023/281] Simplify the loop. --- src/LocalizedString.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/LocalizedString.cpp b/src/LocalizedString.cpp index bca6844c3a..6861c16a71 100644 --- a/src/LocalizedString.cpp +++ b/src/LocalizedString.cpp @@ -27,9 +27,8 @@ static LocalizedString::MakeLocalizer g_pMakeLocalizedStringImpl = LocalizedStri void LocalizedString::RegisterLocalizer( MakeLocalizer pFunc ) { g_pMakeLocalizedStringImpl = pFunc; - FOREACHS( LocalizedString*, *m_Subscribers.m_pSubscribers, l ) + for (LocalizedString *pLoc : *m_Subscribers.m_pSubscribers) { - LocalizedString *pLoc = *l; pLoc->CreateImpl(); } } From a11062a29a061d30fbba6c5093a9e7fcda85ea3c Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 13:59:36 -0400 Subject: [PATCH 024/281] Simpler loop. --- src/Preference.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Preference.cpp b/src/Preference.cpp index 7929ec6ed0..aa42be51ae 100644 --- a/src/Preference.cpp +++ b/src/Preference.cpp @@ -23,10 +23,10 @@ IPreference::~IPreference() IPreference *IPreference::GetPreferenceByName( const RString &sName ) { - FOREACHS( IPreference*, *m_Subscribers.m_pSubscribers, p ) + for (IPreference *p : *m_Subscribers.m_pSubscribers) { - if( !(*p)->GetName().CompareNoCase( sName ) ) - return *p; + if( !p->GetName().CompareNoCase( sName ) ) + return p; } return NULL; From dfe924a64d04ecf02b524d4609446784e2ab7d37 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 14:00:31 -0400 Subject: [PATCH 025/281] Simplify, be explicit. --- src/SongUtil.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SongUtil.cpp b/src/SongUtil.cpp index 477eb2a882..c167a83b13 100644 --- a/src/SongUtil.cpp +++ b/src/SongUtil.cpp @@ -963,9 +963,9 @@ void SongUtil::GetPlayableStepsTypes( const Song *pSong, set &vOut ) * stages left to play. */ // this being const may have caused some problems... -aj const vector &vstToShow = CommonMetrics::STEPS_TYPES_TO_SHOW.GetValue(); - FOREACHS( StepsType, vStepsTypes, st ) + for (StepsType const &type : vStepsTypes) { - bool bShowThisStepsType = find( vstToShow.begin(), vstToShow.end(), *st ) != vstToShow.end(); + bool bShowThisStepsType = find( vstToShow.begin(), vstToShow.end(), type ) != vstToShow.end(); int iNumPlayers = GAMESTATE->GetNumPlayersEnabled(); iNumPlayers = max( iNumPlayers, 1 ); @@ -975,7 +975,7 @@ void SongUtil::GetPlayableStepsTypes( const Song *pSong, set &vOut ) GAMESTATE->GetNumStagesMultiplierForSong(pSong); if( bShowThisStepsType && bEnoughStages ) - vOut.insert( *st ); + vOut.insert( type ); } } @@ -984,8 +984,8 @@ void SongUtil::GetPlayableSteps( const Song *pSong, vector &vOut ) set vStepsType; GetPlayableStepsTypes( pSong, vStepsType ); - FOREACHS( StepsType, vStepsType, st ) - SongUtil::GetSteps( pSong, vOut, *st ); + for (StepsType const &type : vStepsType) + SongUtil::GetSteps( pSong, vOut, type ); StepsUtil::RemoveLockedSteps( pSong, vOut ); StepsUtil::SortNotesArrayByDifficulty( vOut ); From 7e5bc7aa8928066a3a937ce9ff549347ed5d0246 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 14:02:11 -0400 Subject: [PATCH 026/281] All set loops handled. --- src/Foreach.h | 4 - src/RageDisplay_OGL.cpp | 5424 +++++++++++++------------- src/RageSoundReader_ChannelSplit.cpp | 544 +-- 3 files changed, 2984 insertions(+), 2988 deletions(-) diff --git a/src/Foreach.h b/src/Foreach.h index 74f92467b1..7fadd94350 100644 --- a/src/Foreach.h +++ b/src/Foreach.h @@ -8,10 +8,6 @@ for( vector::iterator var = (vect).begin(); var != (vect).end(); ++var #define FOREACH_CONST( elemType, vect, var ) \ for( vector::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) -/** @brief General foreach loop iterating over a set. */ -#define FOREACHS( elemType, vect, var ) \ -for( set::iterator var = (vect).begin(); var != (vect).end(); ++var ) - /** @brief General foreach loop iterating over a map. */ #define FOREACHM( keyType, valType, vect, var ) \ for( map::iterator var = (vect).begin(); var != (vect).end(); ++var ) diff --git a/src/RageDisplay_OGL.cpp b/src/RageDisplay_OGL.cpp index 41fe31c823..35767b7c96 100644 --- a/src/RageDisplay_OGL.cpp +++ b/src/RageDisplay_OGL.cpp @@ -1,2712 +1,2712 @@ -#include "global.h" - -#include "RageDisplay_OGL.h" -#include "RageDisplay_OGL_Helpers.h" -using namespace RageDisplay_Legacy_Helpers; - -#include "RageFile.h" -#include "RageSurface.h" -#include "RageSurfaceUtils.h" -#include "RageUtil.h" -#include "RageLog.h" -#include "RageTextureManager.h" -#include "RageMath.h" -#include "RageTypes.h" -#include "RageUtil.h" -#include "EnumHelper.h" -#include "Foreach.h" -#include "DisplayResolutions.h" -#include "LocalizedString.h" - -#include "arch/LowLevelWindow/LowLevelWindow.h" - -#include - -#if defined(WINDOWS) -#include -#endif - -#if defined(_MSC_VER) -#pragma comment(lib, "opengl32.lib") -#pragma comment(lib, "glu32.lib") -#endif - -#ifdef NO_GL_FLUSH -#define glFlush() -#endif - -// -// Globals -// - -static bool g_bReversePackedPixelsWorks = true; -static bool g_bColorIndexTableWorks = true; - -/* OpenGL system information that generally doesn't change at runtime. */ - -/* Range and granularity of points and lines: */ -static float g_line_range[2]; -static float g_point_range[2]; - -/* OpenGL version * 10: */ -static int g_glVersion; - -/* GLU version * 10: */ -static int g_gluVersion; - -static int g_iMaxTextureUnits = 0; - -/* We don't actually use normals (we don't turn on lighting), there's just - * no GL_T2F_C4F_V3F. */ -static const GLenum RageSpriteVertexFormat = GL_T2F_C4F_N3F_V3F; - -/* If we support texture matrix scaling, a handle to the vertex program: */ -static GLhandleARB g_bTextureMatrixShader = 0; - -static map g_mapRenderTargets; -static RenderTarget *g_pCurrentRenderTarget = NULL; - -static LowLevelWindow *g_pWind; - -static bool g_bInvertY = false; - -static void InvalidateObjects(); - -static RageDisplay::RagePixelFormatDesc PIXEL_FORMAT_DESC[NUM_RagePixelFormat] = { - { - /* R8G8B8A8 */ - 32, - { 0xFF000000, - 0x00FF0000, - 0x0000FF00, - 0x000000FF } - }, { - /* B8G8R8A8 */ - 32, - { 0x0000FF00, - 0x00FF0000, - 0xFF000000, - 0x000000FF } - }, { - /* R4G4B4A4 */ - 16, - { 0xF000, - 0x0F00, - 0x00F0, - 0x000F }, - }, { - /* R5G5B5A1 */ - 16, - { 0xF800, - 0x07C0, - 0x003E, - 0x0001 }, - }, { - /* R5G5B5X1 */ - 16, - { 0xF800, - 0x07C0, - 0x003E, - 0x0000 }, - }, { - /* R8G8B8 */ - 24, - { 0xFF0000, - 0x00FF00, - 0x0000FF, - 0x000000 } - }, { - /* Paletted */ - 8, - { 0,0,0,0 } /* N/A */ - }, { - /* B8G8R8 */ - 24, - { 0x0000FF, - 0x00FF00, - 0xFF0000, - 0x000000 } - }, { - /* A1R5G5B5 */ - 16, - { 0x7C00, - 0x03E0, - 0x001F, - 0x8000 }, - }, { - /* X1R5G5B5 */ - 16, - { 0x7C00, - 0x03E0, - 0x001F, - 0x0000 }, - } -}; - -/* g_GLPixFmtInfo is used for both texture formats and surface formats. For example, - * it's fine to ask for a RagePixelFormat_RGB5 texture, but to supply a surface matching - * RagePixelFormat_RGB8. OpenGL will simply discard the extra bits. - * - * It's possible for a format to be supported as a texture format but not as a - * surface format. For example, if packed pixels aren't supported, we can still - * use GL_RGB5_A1, but we'll have to convert to a supported surface pixel format - * first. It's not ideal, since we'll convert to RGBA8 and OGL will convert back, - * but it works fine. - */ -struct GLPixFmtInfo_t { - GLenum internalfmt; /* target format */ - GLenum format; /* target format */ - GLenum type; /* data format */ -} const g_GLPixFmtInfo[NUM_RagePixelFormat] = { - { - /* R8G8B8A8 */ - GL_RGBA8, - GL_RGBA, - GL_UNSIGNED_BYTE, - }, { - /* R8G8B8A8 */ - GL_RGBA8, - GL_BGRA, - GL_UNSIGNED_BYTE, - }, { - /* B4G4R4A4 */ - GL_RGBA4, - GL_RGBA, - GL_UNSIGNED_SHORT_4_4_4_4, - }, { - /* B5G5R5A1 */ - GL_RGB5_A1, - GL_RGBA, - GL_UNSIGNED_SHORT_5_5_5_1, - }, { - /* B5G5R5 */ - GL_RGB5, - GL_RGBA, - GL_UNSIGNED_SHORT_5_5_5_1, - }, { - /* B8G8R8 */ - GL_RGB8, - GL_RGB, - GL_UNSIGNED_BYTE, - }, { - /* Paletted */ - GL_COLOR_INDEX8_EXT, - GL_COLOR_INDEX, - GL_UNSIGNED_BYTE, - }, { - /* B8G8R8 */ - GL_RGB8, - GL_BGR, - GL_UNSIGNED_BYTE, - }, { - /* A1R5G5B5 (matches D3DFMT_A1R5G5B5) */ - GL_RGB5_A1, - GL_BGRA, - GL_UNSIGNED_SHORT_1_5_5_5_REV, - }, { - /* X1R5G5B5 */ - GL_RGB5, - GL_BGRA, - GL_UNSIGNED_SHORT_1_5_5_5_REV, - } -}; - - -static void FixLittleEndian() -{ -#if defined(ENDIAN_LITTLE) - static bool bInitialized = false; - if (bInitialized) - return; - bInitialized = true; - - for( int i = 0; i < NUM_RagePixelFormat; ++i ) - { - RageDisplay::RagePixelFormatDesc &pf = PIXEL_FORMAT_DESC[i]; - - /* OpenGL and RageSurface handle byte formats differently; we need - * to flip non-paletted masks to make them line up. */ - if (g_GLPixFmtInfo[i].type != GL_UNSIGNED_BYTE || pf.bpp == 8) - continue; - - for( int mask = 0; mask < 4; ++mask) - { - int m = pf.masks[mask]; - switch( pf.bpp ) - { - case 24: m = Swap24(m); break; - case 32: m = Swap32(m); break; - default: - FAIL_M(ssprintf("Unsupported BPP value: %i", pf.bpp)); - } - pf.masks[mask] = m; - } - } -#endif -} - -static void TurnOffHardwareVBO() -{ - if (GLEW_ARB_vertex_buffer_object) - { - glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); - glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); - } -} - -RageDisplay_Legacy::RageDisplay_Legacy() -{ - LOG->Trace( "RageDisplay_Legacy::RageDisplay_Legacy()" ); - LOG->MapLog("renderer", "Current renderer: OpenGL"); - - FixLittleEndian(); - RageDisplay_Legacy_Helpers::Init(); - - g_pWind = NULL; - g_bTextureMatrixShader = 0; -} - -RString GetInfoLog( GLhandleARB h ) -{ - GLint iLength; - glGetObjectParameterivARB( h, GL_OBJECT_INFO_LOG_LENGTH_ARB, &iLength ); - if (!iLength) - return RString(); - - GLcharARB *pInfoLog = new GLcharARB[iLength]; - glGetInfoLogARB( h, iLength, &iLength, pInfoLog ); - RString sRet = pInfoLog; - delete [] pInfoLog; - TrimRight( sRet ); - return sRet; -} - -GLhandleARB CompileShader( GLenum ShaderType, RString sFile, vector asDefines ) -{ - /* XXX: This would not be necessary if it wasn't for the special case for Cel. */ - if (ShaderType == GL_FRAGMENT_SHADER_ARB && !glewIsSupported("GL_VERSION_2_0")) - { - LOG->Warn("Fragment shaders not supported by driver. Some effects will not be available."); - return 0; - } - - RString sBuffer; - { - RageFile file; - if (!file.Open(sFile)) - { - LOG->Warn( "Error compiling shader %s: %s", sFile.c_str(), file.GetError().c_str() ); - return 0; - } - - if (file.Read(sBuffer, file.GetFileSize()) == -1) - { - LOG->Warn( "Error compiling shader %s: %s", sFile.c_str(), file.GetError().c_str() ); - return 0; - } - } - - LOG->Trace( "Compiling shader %s", sFile.c_str() ); - GLhandleARB hShader = glCreateShaderObjectARB( ShaderType ); - vector apData; - vector aiLength; - FOREACH( RString, asDefines, s ) - { - *s = ssprintf( "#define %s\n", s->c_str() ); - apData.push_back( s->data() ); - aiLength.push_back( s->size() ); - } - apData.push_back( "#line 1\n" ); - aiLength.push_back( 8 ); - - apData.push_back( sBuffer.data() ); - aiLength.push_back( sBuffer.size() ); - glShaderSourceARB( hShader, apData.size(), &apData[0], &aiLength[0] ); - - glCompileShaderARB( hShader ); - - RString sInfo = GetInfoLog( hShader ); - - GLint bCompileStatus = GL_FALSE; - glGetObjectParameterivARB( hShader, GL_OBJECT_COMPILE_STATUS_ARB, &bCompileStatus ); - if (!bCompileStatus) - { - LOG->Warn( "Error compiling shader %s:\n%s", sFile.c_str(), sInfo.c_str() ); - glDeleteObjectARB( hShader ); - return 0; - } - - if (!sInfo.empty()) - LOG->Trace( "Messages compiling shader %s:\n%s", sFile.c_str(), sInfo.c_str() ); - - return hShader; -} - -GLhandleARB LoadShader( GLenum ShaderType, RString sFile, vector asDefines ) -{ - /* Vertex shaders are supported by more hardware than fragment shaders. - * If this causes any trouble I will have to up the requirement for both - * of them to at least GL 2.0. Regardless we need basic GLSL support. - * -Colby */ - if (!glewIsSupported("GL_ARB_shading_language_100 GL_ARB_shader_objects") || - (ShaderType == GL_FRAGMENT_SHADER_ARB && !glewIsSupported("GL_VERSION_2_0")) || - (ShaderType == GL_VERTEX_SHADER_ARB && !glewIsSupported("GL_ARB_vertex_shader"))) - { - LOG->Warn("%s shaders not supported by driver. Some effects will not be available.", - (ShaderType == GL_FRAGMENT_SHADER_ARB) ? "Fragment" : "Vertex"); - return 0; - } - - // XXX: dumb, but I don't feel like refactoring ragedisplay for this. -Colby - GLhandleARB secondaryShader = 0; - if (sFile == "Data/Shaders/GLSL/Cel.vert") - secondaryShader = CompileShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Cel.frag", asDefines); - else if (sFile == "Data/Shaders/GLSL/Shell.vert") - secondaryShader = CompileShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Shell.frag", asDefines); - - GLhandleARB hShader = CompileShader( ShaderType, sFile, asDefines ); - if (hShader == 0) - return 0; - - GLhandleARB hProgram = glCreateProgramObjectARB(); - glAttachObjectARB( hProgram, hShader ); - - if (secondaryShader) - { - glAttachObjectARB( hProgram, secondaryShader ); - glDeleteObjectARB( secondaryShader ); - } - glDeleteObjectARB( hShader ); - - // Link the program. - glLinkProgramARB( hProgram ); - GLint bLinkStatus = false; - glGetObjectParameterivARB( hProgram, GL_OBJECT_LINK_STATUS_ARB, &bLinkStatus ); - - if (!bLinkStatus) - { - LOG->Warn( "Error linking shader %s: %s", sFile.c_str(), GetInfoLog(hProgram).c_str() ); - glDeleteObjectARB( hProgram ); - return 0; - } - return hProgram; -} - -static int g_iAttribTextureMatrixScale; - -static GLhandleARB g_bUnpremultiplyShader = 0; -static GLhandleARB g_bColorBurnShader = 0; -static GLhandleARB g_bColorDodgeShader = 0; -static GLhandleARB g_bVividLightShader = 0; -static GLhandleARB g_hHardMixShader = 0; -static GLhandleARB g_hOverlayShader = 0; -static GLhandleARB g_hScreenShader = 0; -static GLhandleARB g_hYUYV422Shader = 0; -static GLhandleARB g_gShellShader = 0; -static GLhandleARB g_gCelShader = 0; - -void InitShaders() -{ - // xxx: replace this with a ShaderManager or something that reads in - // the shaders and determines shader type by file extension. -aj - // argh shaders in stepmania are painful -colby - vector asDefines; - - // used for scrolling textures (I think) - g_bTextureMatrixShader = LoadShader( GL_VERTEX_SHADER_ARB, "Data/Shaders/GLSL/Texture matrix scaling.vert", asDefines ); - - // these two are for dancing characters and are both actually shader pairs - g_gShellShader = LoadShader( GL_VERTEX_SHADER_ARB, "Data/Shaders/GLSL/Shell.vert", asDefines ); - g_gCelShader = LoadShader( GL_VERTEX_SHADER_ARB, "Data/Shaders/GLSL/Cel.vert", asDefines ); - - // effects - g_bUnpremultiplyShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Unpremultiply.frag", asDefines ); - g_bColorBurnShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Color burn.frag", asDefines ); - g_bColorDodgeShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Color dodge.frag", asDefines ); - g_bVividLightShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Vivid light.frag", asDefines ); - g_hHardMixShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Hard mix.frag", asDefines ); - g_hOverlayShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Overlay.frag", asDefines ); - g_hScreenShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Screen.frag", asDefines ); - g_hYUYV422Shader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/YUYV422.frag", asDefines ); - - // Bind attributes. - if (g_bTextureMatrixShader) - { - FlushGLErrors(); - g_iAttribTextureMatrixScale = glGetAttribLocationARB( g_bTextureMatrixShader, "TextureMatrixScale" ); - if (g_iAttribTextureMatrixScale == -1) - { - LOG->Trace( "Scaling shader link failed: couldn't bind attribute \"TextureMatrixScale\"" ); - glDeleteObjectARB( g_bTextureMatrixShader ); - g_bTextureMatrixShader = 0; - } - else - { - AssertNoGLError(); - - /* Older Catalyst drivers seem to throw GL_INVALID_OPERATION here. */ - glVertexAttrib2fARB( g_iAttribTextureMatrixScale, 1, 1 ); - GLenum iError = glGetError(); - if (iError == GL_INVALID_OPERATION) - { - LOG->Trace( "Scaling shader failed: glVertexAttrib2fARB returned GL_INVALID_OPERATION" ); - glDeleteObjectARB( g_bTextureMatrixShader ); - g_bTextureMatrixShader = 0; - } - else - { - ASSERT_M( iError == GL_NO_ERROR, GLToString(iError) ); - } - } - } -} - -static LocalizedString OBTAIN_AN_UPDATED_VIDEO_DRIVER ( "RageDisplay_Legacy", "Obtain an updated driver from your video card manufacturer." ); -static LocalizedString GLDIRECT_IS_NOT_COMPATIBLE ( "RageDisplay_Legacy", "GLDirect was detected. GLDirect is not compatible with this game and should be disabled." ); -RString RageDisplay_Legacy::Init( const VideoModeParams &p, bool bAllowUnacceleratedRenderer ) -{ - g_pWind = LowLevelWindow::Create(); - - bool bIgnore = false; - RString sError = SetVideoMode( p, bIgnore ); - if (sError != "") - return sError; - - // Log driver details - g_pWind->LogDebugInformation(); - LOG->Info( "OGL Vendor: %s", glGetString(GL_VENDOR) ); - LOG->Info( "OGL Renderer: %s", glGetString(GL_RENDERER) ); - LOG->Info( "OGL Version: %s", glGetString(GL_VERSION) ); - LOG->Info( "OGL Max texture size: %i", GetMaxTextureSize() ); - LOG->Info( "OGL Texture units: %i", g_iMaxTextureUnits ); - LOG->Info( "GLU Version: %s", gluGetString(GLU_VERSION) ); - - /* Pretty-print the extension string: */ - LOG->Info( "OGL Extensions:" ); - { - const char *szExtensionString = (const char *) glGetString(GL_EXTENSIONS); - vector asExtensions; - split( szExtensionString, " ", asExtensions ); - sort( asExtensions.begin(), asExtensions.end() ); - size_t iNextToPrint = 0; - while( iNextToPrint < asExtensions.size() ) - { - size_t iLastToPrint = iNextToPrint; - RString sType; - for( size_t i = iNextToPrint; i asBits; - split( asExtensions[i], "_", asBits ); - RString sThisType; - if (asBits.size() > 2) - sThisType = join( "_", asBits.begin(), asBits.begin()+2 ); - if (i > iNextToPrint && sThisType != sType) - break; - sType = sThisType; - iLastToPrint = i; - } - - if (iNextToPrint == iLastToPrint) - { - LOG->Info( " %s", asExtensions[iNextToPrint].c_str() ); - ++iNextToPrint; - continue; - } - - RString sList = ssprintf( " %s: ", sType.c_str() ); - while( iNextToPrint <= iLastToPrint ) - { - vector asBits; - split( asExtensions[iNextToPrint], "_", asBits ); - RString sShortExt = join( "_", asBits.begin()+2, asBits.end() ); - sList += sShortExt; - if (iNextToPrint < iLastToPrint) - sList += ", "; - if (iNextToPrint == iLastToPrint || sList.size() + asExtensions[iNextToPrint+1].size() > 120) - { - LOG->Info( "%s", sList.c_str() ); - sList = " "; - } - ++iNextToPrint; - } - } - } - - if (g_pWind->IsSoftwareRenderer(sError)) - { - if (!bAllowUnacceleratedRenderer) - return sError + " " + OBTAIN_AN_UPDATED_VIDEO_DRIVER.GetValue() + "\n\n"; - LOG->Warn( "Low-performance OpenGL renderer: %s", sError.c_str() ); - } - -#if defined(_WINDOWS) - /* GLDirect is a Direct3D wrapper for OpenGL. It's rather buggy; and if in - * any case GLDirect can successfully render us, we should be able to do so - * too using Direct3D directly. (If we can't, it's a bug that we can work - * around--if GLDirect can do it, so can we!) */ - if (!strncmp( (const char *) glGetString(GL_RENDERER), "GLDirect", 8 )) - return GLDIRECT_IS_NOT_COMPATIBLE.GetValue() + "\n"; -#endif - - /* Log this, so if people complain that the radar looks bad on their - * system we can compare them: */ - glGetFloatv( GL_LINE_WIDTH_RANGE, g_line_range ); - glGetFloatv( GL_POINT_SIZE_RANGE, g_point_range ); - - return RString(); -} - -RageDisplay_Legacy::~RageDisplay_Legacy() -{ - delete g_pWind; -} - -void RageDisplay_Legacy::GetDisplayResolutions( DisplayResolutions &out ) const -{ - out.clear(); - g_pWind->GetDisplayResolutions( out ); -} - -static void CheckPalettedTextures() -{ - RString sError; - do - { - if (!GLEW_EXT_paletted_texture) - { - sError = "GL_EXT_paletted_texture missing"; - break; - } - - /* Check to see if paletted textures really work. */ - GLenum glTexFormat = g_GLPixFmtInfo[RagePixelFormat_PAL].internalfmt; - GLenum glImageFormat = g_GLPixFmtInfo[RagePixelFormat_PAL].format; - GLenum glImageType = g_GLPixFmtInfo[RagePixelFormat_PAL].type; - - int iBits = 8; - - FlushGLErrors(); -#define GL_CHECK_ERROR(f) \ -{ \ - GLenum glError = glGetError(); \ - if (glError != GL_NO_ERROR) { \ - sError = ssprintf(f " failed (%s)", GLToString(glError).c_str() ); \ - break; \ - } \ -} - - glTexImage2D( GL_PROXY_TEXTURE_2D, - 0, glTexFormat, - 16, 16, 0, - glImageFormat, glImageType, NULL ); - GL_CHECK_ERROR( "glTexImage2D" ); - - GLuint iFormat = 0; - glGetTexLevelParameteriv( GL_PROXY_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INTERNAL_FORMAT), (GLint *) &iFormat ); - GL_CHECK_ERROR( "glGetTexLevelParameteriv(GL_TEXTURE_INTERNAL_FORMAT)" ); - if (iFormat != glTexFormat) - { - sError = ssprintf( "Expected format %s, got %s instead", - GLToString(glTexFormat).c_str(), GLToString(iFormat).c_str() ); - break; - } - - GLubyte palette[256*4]; - memset(palette, 0, sizeof(palette)); - glColorTableEXT(GL_PROXY_TEXTURE_2D, GL_RGBA8, 256, GL_RGBA, GL_UNSIGNED_BYTE, palette); - GL_CHECK_ERROR( "glColorTableEXT" ); - - GLint iSize = 0; - glGetTexLevelParameteriv( GL_PROXY_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INDEX_SIZE_EXT), &iSize ); - GL_CHECK_ERROR( "glGetTexLevelParameteriv(GL_TEXTURE_INDEX_SIZE_EXT)" ); - if (iBits > iSize || iSize > 8) - { - sError = ssprintf( "Expected %i-bit palette, got a %i-bit one instead", iBits, int(iSize) ); - break; - } - - GLint iRealWidth = 0; - glGetColorTableParameterivEXT( GL_PROXY_TEXTURE_2D, GL_COLOR_TABLE_WIDTH, &iRealWidth ); - GL_CHECK_ERROR( "glGetColorTableParameterivEXT(GL_COLOR_TABLE_WIDTH)" ); - if (iRealWidth != 1 << iBits) - { - sError = ssprintf( "GL_COLOR_TABLE_WIDTH returned %i instead of %i", int(iRealWidth), 1 << iBits ); - break; - } - - GLint iRealFormat = 0; - glGetColorTableParameterivEXT( GL_PROXY_TEXTURE_2D, GL_COLOR_TABLE_FORMAT, &iRealFormat ); - GL_CHECK_ERROR( "glGetColorTableParameterivEXT(GL_COLOR_TABLE_FORMAT)" ); - if (iRealFormat != GL_RGBA8) - { - sError = ssprintf( "GL_COLOR_TABLE_FORMAT returned %s instead of GL_RGBA8", GLToString(iRealFormat).c_str() ); - break; - } - } while(0); -#undef GL_CHECK_ERROR - - if (sError == "") - return; - - /* If 8-bit palettes don't work, disable them entirely--don't trust 4-bit - * palettes if it can't even get 8-bit ones right. */ - glColorTableEXT = NULL; - glGetColorTableParameterivEXT = NULL; - LOG->Info( "Paletted textures disabled: %s.", sError.c_str() ); -} - -static void CheckReversePackedPixels() -{ - /* Try to create a texture. */ - FlushGLErrors(); - glTexImage2D( GL_PROXY_TEXTURE_2D, - 0, GL_RGBA, - 16, 16, 0, - GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, NULL ); - - const GLenum glError = glGetError(); - if (glError == GL_NO_ERROR) - { - g_bReversePackedPixelsWorks = true; - } - else - { - g_bReversePackedPixelsWorks = false; - LOG->Info( "GL_UNSIGNED_SHORT_1_5_5_5_REV failed (%s), disabled", - GLToString(glError).c_str() ); - } -} - -void SetupExtensions() -{ - const float fGLVersion = StringToFloat( (const char *) glGetString(GL_VERSION) ); - g_glVersion = lrintf( fGLVersion * 10 ); - - const float fGLUVersion = StringToFloat( (const char *) gluGetString(GLU_VERSION) ); - g_gluVersion = lrintf( fGLUVersion * 10 ); - - glewInit(); - - g_iMaxTextureUnits = 1; - if (GLEW_ARB_multitexture) - glGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, (GLint *) &g_iMaxTextureUnits ); - - CheckPalettedTextures(); - CheckReversePackedPixels(); - - { - GLint iMaxTableSize = 0; - glGetIntegerv( GL_MAX_PIXEL_MAP_TABLE, &iMaxTableSize ); - if (iMaxTableSize < 256) - { - /* The minimum GL_MAX_PIXEL_MAP_TABLE is 32; if it's not at least 256, - * we can't fit a palette in it, so we can't send paletted data as input - * for a non-paletted texture. */ - LOG->Info( "GL_MAX_PIXEL_MAP_TABLE is only %d", int(iMaxTableSize) ); - g_bColorIndexTableWorks = false; - } - else - { - g_bColorIndexTableWorks = true; - } - } -} - -void RageDisplay_Legacy::ResolutionChanged() -{ - //LOG->Warn( "RageDisplay_Legacy::ResolutionChanged" ); - - /* Clear any junk that's in the framebuffer. */ - if (BeginFrame()) - EndFrame(); - - RageDisplay::ResolutionChanged(); -} - -// Return true if mode change was successful. -// bNewDeviceOut is set true if a new device was created and textures -// need to be reloaded. -RString RageDisplay_Legacy::TryVideoMode( const VideoModeParams &p, bool &bNewDeviceOut ) -{ - //LOG->Warn( "RageDisplay_Legacy::TryVideoMode( %d, %d, %d, %d, %d, %d )", p.windowed, p.width, p.height, p.bpp, p.rate, p.vsync ); - - RString err; - err = g_pWind->TryVideoMode( p, bNewDeviceOut ); - if (err != "") - return err; // failed to set video mode - - /* Now that we've initialized, we can search for extensions. Do this before InvalidateObjects, - * since AllocateBuffers needs it. */ - SetupExtensions(); - - if (bNewDeviceOut) - { - /* We have a new OpenGL context, so we have to tell our textures that - * their OpenGL texture number is invalid. */ - if (TEXTUREMAN) - TEXTUREMAN->InvalidateTextures(); - - /* Delete all render targets. They may have associated resources other than - * the texture itself. */ - FOREACHM( unsigned, RenderTarget *, g_mapRenderTargets, rt ) - delete rt->second; - g_mapRenderTargets.clear(); - - /* Recreate all vertex buffers. */ - InvalidateObjects(); - - InitShaders(); - } - -// I'm not sure this is correct -Colby -#if defined(WINDOWS) - /* Set vsync the Windows way, if we can. (What other extensions are there - * to do this, for other archs?) */ - if( wglewIsSupported("WGL_EXT_swap_control") ) - wglSwapIntervalEXT(p.vsync); - else - return RString("The WGL_EXT_swap_control extension is not supported on your computer."); -#endif - - ResolutionChanged(); - - return RString(); // successfully set mode -} - -int RageDisplay_Legacy::GetMaxTextureSize() const -{ - GLint size; - glGetIntegerv( GL_MAX_TEXTURE_SIZE, &size ); - return size; -} - -bool RageDisplay_Legacy::BeginFrame() -{ - /* We do this in here, rather than ResolutionChanged, or we won't update the - * viewport for the concurrent rendering context. */ - int fWidth = g_pWind->GetActualVideoModeParams().width; - int fHeight = g_pWind->GetActualVideoModeParams().height; - - glViewport( 0, 0, fWidth, fHeight ); - - glClearColor( 0,0,0,0 ); - SetZWrite( true ); - glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); - - return RageDisplay::BeginFrame(); -} - -void RageDisplay_Legacy::EndFrame() -{ - glFlush(); - - FrameLimitBeforeVsync( g_pWind->GetActualVideoModeParams().rate ); - g_pWind->SwapBuffers(); - FrameLimitAfterVsync(); - - g_pWind->Update(); - - RageDisplay::EndFrame(); -} - -RageSurface* RageDisplay_Legacy::CreateScreenshot() -{ - int width = g_pWind->GetActualVideoModeParams().width; - int height = g_pWind->GetActualVideoModeParams().height; - - const RagePixelFormatDesc &desc = PIXEL_FORMAT_DESC[RagePixelFormat_RGBA8]; - RageSurface *image = CreateSurface( width, height, desc.bpp, - desc.masks[0], desc.masks[1], desc.masks[2], 0 ); - - DebugFlushGLErrors(); - - glReadBuffer( GL_FRONT ); - DebugAssertNoGLError(); - - glReadPixels( 0, 0, g_pWind->GetActualVideoModeParams().width, g_pWind->GetActualVideoModeParams().height, GL_RGBA, - GL_UNSIGNED_BYTE, image->pixels ); - DebugAssertNoGLError(); - - RageSurfaceUtils::FlipVertically( image ); - - return image; -} - -RageSurface *RageDisplay_Legacy::GetTexture( unsigned iTexture ) -{ - if (iTexture == 0) - return NULL; // XXX - - FlushGLErrors(); - - glBindTexture( GL_TEXTURE_2D, iTexture ); - GLint iHeight, iWidth, iAlphaBits; - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &iHeight ); - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &iWidth ); - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_ALPHA_SIZE, &iAlphaBits ); - int iFormat = iAlphaBits? RagePixelFormat_RGBA8:RagePixelFormat_RGB8; - - const RagePixelFormatDesc &desc = PIXEL_FORMAT_DESC[iFormat]; - RageSurface *pImage = CreateSurface( iWidth, iHeight, desc.bpp, - desc.masks[0], desc.masks[1], desc.masks[2], desc.masks[3] ); - - glGetTexImage( GL_TEXTURE_2D, 0, g_GLPixFmtInfo[iFormat].format, GL_UNSIGNED_BYTE, pImage->pixels ); - AssertNoGLError(); - - return pImage; -} - -VideoModeParams RageDisplay_Legacy::GetActualVideoModeParams() const -{ - return g_pWind->GetActualVideoModeParams(); -} - -static void SetupVertices( const RageSpriteVertex v[], int iNumVerts ) -{ - static float *Vertex, *Texture, *Normal; - static GLubyte *Color; - static int Size = 0; - if (iNumVerts > Size) - { - Size = iNumVerts; - delete [] Vertex; - delete [] Color; - delete [] Texture; - delete [] Normal; - Vertex = new float[Size*3]; - Color = new GLubyte[Size*4]; - Texture = new float[Size*2]; - Normal = new float[Size*3]; - } - - for( unsigned i = 0; i < unsigned(iNumVerts); ++i ) - { - Vertex[i*3+0] = v[i].p[0]; - Vertex[i*3+1] = v[i].p[1]; - Vertex[i*3+2] = v[i].p[2]; - Color[i*4+0] = v[i].c.r; - Color[i*4+1] = v[i].c.g; - Color[i*4+2] = v[i].c.b; - Color[i*4+3] = v[i].c.a; - Texture[i*2+0] = v[i].t[0]; - Texture[i*2+1] = v[i].t[1]; - Normal[i*3+0] = v[i].n[0]; - Normal[i*3+1] = v[i].n[1]; - Normal[i*3+2] = v[i].n[2]; - } - glEnableClientState( GL_VERTEX_ARRAY ); - glVertexPointer( 3, GL_FLOAT, 0, Vertex ); - - glEnableClientState( GL_COLOR_ARRAY ); - glColorPointer( 4, GL_UNSIGNED_BYTE, 0, Color ); - - glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 2, GL_FLOAT, 0, Texture ); - - if (GLEW_ARB_multitexture) - { - glClientActiveTextureARB( GL_TEXTURE1_ARB ); - glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 2, GL_FLOAT, 0, Texture ); - glClientActiveTextureARB( GL_TEXTURE0_ARB ); - } - - glEnableClientState( GL_NORMAL_ARRAY ); - glNormalPointer( GL_FLOAT, 0, Normal ); -} - -void RageDisplay_Legacy::SendCurrentMatrices() -{ - RageMatrix projection; - RageMatrixMultiply( &projection, GetCentering(), GetProjectionTop() ); - - if (g_bInvertY) - { - RageMatrix flip; - RageMatrixScale( &flip, +1, -1, +1 ); - RageMatrixMultiply( &projection, &flip, &projection ); - } - glMatrixMode( GL_PROJECTION ); - glLoadMatrixf( (const float*)&projection ); - - // OpenGL has just "modelView", whereas D3D has "world" and "view" - RageMatrix modelView; - RageMatrixMultiply( &modelView, GetViewTop(), GetWorldTop() ); - glMatrixMode( GL_MODELVIEW ); - glLoadMatrixf( (const float*)&modelView ); - - glMatrixMode( GL_TEXTURE ); - glLoadMatrixf( (const float*)GetTextureTop() ); -} - -class RageCompiledGeometrySWOGL : public RageCompiledGeometry -{ -public: - - void Allocate( const vector &vMeshes ) - { - /* Always allocate at least 1 entry, so &x[0] is valid. */ - m_vPosition.resize( max(1u, GetTotalVertices()) ); - m_vTexture.resize( max(1u, GetTotalVertices()) ); - m_vNormal.resize( max(1u, GetTotalVertices()) ); - m_vTexMatrixScale.resize( max(1u, GetTotalVertices()) ); - m_vTriangles.resize( max(1u, GetTotalTriangles()) ); - } - void Change( const vector &vMeshes ) - { - for( unsigned i=0; i &Vertices = mesh.Vertices; - const vector &Triangles = mesh.Triangles; - - for( unsigned j=0; jTrace( s ); - } - */ - - mat.m[3][0] = 0; - mat.m[3][1] = 0; - mat.m[3][2] = 0; - - glMatrixMode( GL_TEXTURE ); - glLoadMatrixf( (const float*)mat ); - } - - glDrawElements( - GL_TRIANGLES, - meshInfo.iTriangleCount*3, - GL_UNSIGNED_SHORT, - &m_vTriangles[0]+meshInfo.iTriangleStart ); - } - -protected: - vector m_vPosition; - vector m_vTexture; - vector m_vNormal; - vector m_vTriangles; - vector m_vTexMatrixScale; -}; - -class InvalidateObject; -static set g_InvalidateList; -class InvalidateObject -{ -public: - InvalidateObject() { g_InvalidateList.insert( this ); } - virtual ~InvalidateObject() { g_InvalidateList.erase( this ); } - virtual void Invalidate() = 0; -}; - -static void InvalidateObjects() -{ - FOREACHS( InvalidateObject*, g_InvalidateList, it ) - (*it)->Invalidate(); -} - -class RageCompiledGeometryHWOGL : public RageCompiledGeometrySWOGL, public InvalidateObject -{ -protected: - // vertex buffer object names - GLuint m_nPositions; - GLuint m_nTextureCoords; - GLuint m_nNormals; - GLuint m_nTriangles; - GLuint m_nTextureMatrixScale; - - void AllocateBuffers(); - void UploadData(); - -public: - RageCompiledGeometryHWOGL(); - ~RageCompiledGeometryHWOGL(); - - /* This is called when our OpenGL context is invalidated. */ - void Invalidate(); - - void Allocate( const vector &vMeshes ); - void Change( const vector &vMeshes ); - void Draw( int iMeshIndex ) const; -}; - -RageCompiledGeometryHWOGL::RageCompiledGeometryHWOGL() -{ - m_nPositions = 0; - m_nTextureCoords = 0; - m_nNormals = 0; - m_nTriangles = 0; - m_nTextureMatrixScale = 0; - - AllocateBuffers(); -} - -RageCompiledGeometryHWOGL::~RageCompiledGeometryHWOGL() -{ - DebugFlushGLErrors(); - - glDeleteBuffersARB( 1, &m_nPositions ); - DebugAssertNoGLError(); - glDeleteBuffersARB( 1, &m_nTextureCoords ); - DebugAssertNoGLError(); - glDeleteBuffersARB( 1, &m_nNormals ); - DebugAssertNoGLError(); - glDeleteBuffersARB( 1, &m_nTriangles ); - DebugAssertNoGLError(); - glDeleteBuffersARB( 1, &m_nTextureMatrixScale ); - DebugAssertNoGLError(); -} - -void RageCompiledGeometryHWOGL::AllocateBuffers() -{ - DebugFlushGLErrors(); - - if (!m_nPositions) - { - glGenBuffersARB( 1, &m_nPositions ); - DebugAssertNoGLError(); - } - - if (!m_nTextureCoords) - { - glGenBuffersARB( 1, &m_nTextureCoords ); - DebugAssertNoGLError(); - } - - if (!m_nNormals) - { - glGenBuffersARB( 1, &m_nNormals ); - DebugAssertNoGLError(); - } - - if (!m_nTriangles) - { - glGenBuffersARB( 1, &m_nTriangles ); - DebugAssertNoGLError(); - } - - if (!m_nTextureMatrixScale) - { - glGenBuffersARB( 1, &m_nTextureMatrixScale ); - DebugAssertNoGLError(); - } -} - -void RageCompiledGeometryHWOGL::UploadData() -{ - DebugFlushGLErrors(); - - glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_nPositions); - DebugAssertNoGLError(); - glBufferDataARB( - GL_ARRAY_BUFFER_ARB, - GetTotalVertices()*sizeof(RageVector3), - &m_vPosition[0], - GL_STATIC_DRAW_ARB); - DebugAssertNoGLError(); - - glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_nTextureCoords); - DebugAssertNoGLError(); - glBufferDataARB( - GL_ARRAY_BUFFER_ARB, - GetTotalVertices()*sizeof(RageVector2), - &m_vTexture[0], - GL_STATIC_DRAW_ARB); - DebugAssertNoGLError(); - - glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_nNormals); - DebugAssertNoGLError(); - glBufferDataARB( - GL_ARRAY_BUFFER_ARB, - GetTotalVertices()*sizeof(RageVector3), - &m_vNormal[0], - GL_STATIC_DRAW_ARB); - DebugAssertNoGLError(); - - glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, m_nTriangles); - DebugAssertNoGLError(); - glBufferDataARB( - GL_ELEMENT_ARRAY_BUFFER_ARB, - GetTotalTriangles()*sizeof(msTriangle), - &m_vTriangles[0], - GL_STATIC_DRAW_ARB); - DebugAssertNoGLError(); - - - if (m_bAnyNeedsTextureMatrixScale) - { - glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_nTextureMatrixScale); - DebugAssertNoGLError(); - glBufferDataARB( - GL_ARRAY_BUFFER_ARB, - GetTotalVertices()*sizeof(RageVector2), - &m_vTexMatrixScale[0], - GL_STATIC_DRAW_ARB); - DebugAssertNoGLError(); - } -} - -void RageCompiledGeometryHWOGL::Invalidate() -{ - /* Our vertex buffers no longer exist. Reallocate and reupload. */ - m_nPositions = 0; - m_nTextureCoords = 0; - m_nNormals = 0; - m_nTriangles = 0; - m_nTextureMatrixScale = 0; - AllocateBuffers(); - UploadData(); -} - -void RageCompiledGeometryHWOGL::Allocate( const vector &vMeshes ) -{ - DebugFlushGLErrors(); - - RageCompiledGeometrySWOGL::Allocate( vMeshes ); - glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nPositions ); - DebugAssertNoGLError(); - glBufferDataARB( - GL_ARRAY_BUFFER_ARB, - GetTotalVertices()*sizeof(RageVector3), - NULL, - GL_STATIC_DRAW_ARB ); - DebugAssertNoGLError(); - - glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nTextureCoords ); - DebugAssertNoGLError(); - glBufferDataARB( - GL_ARRAY_BUFFER_ARB, - GetTotalVertices()*sizeof(RageVector2), - NULL, - GL_STATIC_DRAW_ARB ); - DebugAssertNoGLError(); - - glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nNormals ); - DebugAssertNoGLError(); - glBufferDataARB( - GL_ARRAY_BUFFER_ARB, - GetTotalVertices()*sizeof(RageVector3), - NULL, - GL_STATIC_DRAW_ARB ); - DebugAssertNoGLError(); - - glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, m_nTriangles ); - DebugAssertNoGLError(); - glBufferDataARB( - GL_ELEMENT_ARRAY_BUFFER_ARB, - GetTotalTriangles()*sizeof(msTriangle), - NULL, - GL_STATIC_DRAW_ARB ); - DebugAssertNoGLError(); - - glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nTextureMatrixScale ); - DebugAssertNoGLError(); - glBufferDataARB( - GL_ARRAY_BUFFER_ARB, - GetTotalVertices()*sizeof(RageVector2), - NULL, - GL_STATIC_DRAW_ARB ); -} - -void RageCompiledGeometryHWOGL::Change( const vector &vMeshes ) -{ - RageCompiledGeometrySWOGL::Change( vMeshes ); - - UploadData(); -} - -void RageCompiledGeometryHWOGL::Draw( int iMeshIndex ) const -{ - DebugFlushGLErrors(); - - const MeshInfo& meshInfo = m_vMeshInfo[iMeshIndex]; - if (!meshInfo.iVertexCount || !meshInfo.iTriangleCount) - return; - - glEnableClientState(GL_VERTEX_ARRAY); - DebugAssertNoGLError(); - glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nPositions ); - DebugAssertNoGLError(); - glVertexPointer(3, GL_FLOAT, 0, NULL ); - DebugAssertNoGLError(); - - glDisableClientState(GL_COLOR_ARRAY); - DebugAssertNoGLError(); - - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - DebugAssertNoGLError(); - glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nTextureCoords ); - DebugAssertNoGLError(); - glTexCoordPointer(2, GL_FLOAT, 0, NULL); - DebugAssertNoGLError(); - - // TRICKY: Don't bind and send normals if lighting is disabled. This - // will save some effort transforming these values. - // XXX: We should keep track of these ourself and avoid glGet*() - GLboolean bLighting; - glGetBooleanv( GL_LIGHTING, &bLighting ); - GLboolean bTextureGenS; - glGetBooleanv( GL_TEXTURE_GEN_S, &bTextureGenS ); - GLboolean bTextureGenT; - glGetBooleanv( GL_TEXTURE_GEN_T, &bTextureGenT ); - - if (bLighting || bTextureGenS || bTextureGenT) - { - glEnableClientState(GL_NORMAL_ARRAY); - DebugAssertNoGLError(); - glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nNormals ); - DebugAssertNoGLError(); - glNormalPointer(GL_FLOAT, 0, NULL); - DebugAssertNoGLError(); - } - else - { - glDisableClientState(GL_NORMAL_ARRAY); - DebugAssertNoGLError(); - } - - if (meshInfo.m_bNeedsTextureMatrixScale) - { - if (g_bTextureMatrixShader != 0) - { - /* If we're using texture matrix scales, set up that buffer, too, and enable the - * vertex shader. This shader doesn't support all OpenGL state, so only enable it - * if we're using it. */ - glEnableVertexAttribArrayARB( g_iAttribTextureMatrixScale ); - DebugAssertNoGLError(); - glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nTextureMatrixScale ); - DebugAssertNoGLError(); - glVertexAttribPointerARB( g_iAttribTextureMatrixScale, 2, GL_FLOAT, false, 0, NULL ); - DebugAssertNoGLError(); - - glUseProgramObjectARB( g_bTextureMatrixShader ); - DebugAssertNoGLError(); - } - else - { - // Kill the texture translation. - // XXX: Change me to scale the translation by the TextureTranslationScale of the first vertex. - RageMatrix mat; - glGetFloatv( GL_TEXTURE_MATRIX , (float*)mat ); - - /* - for( int i=0; i<4; i++ ) - { - RString s; - for( int j=0; j<4; j++ ) - s += ssprintf( "%f ", mat.m[i][j] ); - LOG->Trace( s ); - } - */ - - mat.m[3][0] = 0; - mat.m[3][1] = 0; - mat.m[3][2] = 0; - - glMatrixMode( GL_TEXTURE ); - glLoadMatrixf( (const float*)mat ); - DebugAssertNoGLError(); - } - } - - glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, m_nTriangles ); - DebugAssertNoGLError(); - -#define BUFFER_OFFSET(o) ((char*)(o)) - - ASSERT( glDrawRangeElements != NULL ); - glDrawRangeElements( - GL_TRIANGLES, - meshInfo.iVertexStart, // minimum array index contained in indices - meshInfo.iVertexStart+meshInfo.iVertexCount-1, - // maximum array index contained in indices - meshInfo.iTriangleCount*3, // number of elements to be rendered - GL_UNSIGNED_SHORT, - BUFFER_OFFSET(meshInfo.iTriangleStart*sizeof(msTriangle)) ); - DebugAssertNoGLError(); - - if (meshInfo.m_bNeedsTextureMatrixScale && g_bTextureMatrixShader != 0) - { - glDisableVertexAttribArrayARB( g_iAttribTextureMatrixScale ); - glUseProgramObjectARB( 0 ); - } -} - -RageCompiledGeometry* RageDisplay_Legacy::CreateCompiledGeometry() -{ - if (GLEW_ARB_vertex_buffer_object) - return new RageCompiledGeometryHWOGL; - else - return new RageCompiledGeometrySWOGL; -} - -void RageDisplay_Legacy::DeleteCompiledGeometry( RageCompiledGeometry* p ) -{ - delete p; -} - -void RageDisplay_Legacy::DrawQuadsInternal( const RageSpriteVertex v[], int iNumVerts ) -{ - TurnOffHardwareVBO(); - SendCurrentMatrices(); - - SetupVertices( v, iNumVerts ); - glDrawArrays( GL_QUADS, 0, iNumVerts ); -} - -void RageDisplay_Legacy::DrawQuadStripInternal( const RageSpriteVertex v[], int iNumVerts ) -{ - TurnOffHardwareVBO(); - SendCurrentMatrices(); - - SetupVertices( v, iNumVerts ); - glDrawArrays( GL_QUAD_STRIP, 0, iNumVerts ); -} - -void RageDisplay_Legacy::DrawSymmetricQuadStripInternal( const RageSpriteVertex v[], int iNumVerts ) -{ - int iNumPieces = (iNumVerts-3)/3; - int iNumTriangles = iNumPieces*4; - int iNumIndices = iNumTriangles*3; - - // make a temporary index buffer - static vector vIndices; - unsigned uOldSize = vIndices.size(); - unsigned uNewSize = max(uOldSize,(unsigned)iNumIndices); - vIndices.resize( uNewSize ); - for( uint16_t i=(uint16_t)uOldSize/12; i<(uint16_t)iNumPieces; i++ ) - { - // { 1, 3, 0 } { 1, 4, 3 } { 1, 5, 4 } { 1, 2, 5 } - vIndices[i*12+0] = i*3+1; - vIndices[i*12+1] = i*3+3; - vIndices[i*12+2] = i*3+0; - vIndices[i*12+3] = i*3+1; - vIndices[i*12+4] = i*3+4; - vIndices[i*12+5] = i*3+3; - vIndices[i*12+6] = i*3+1; - vIndices[i*12+7] = i*3+5; - vIndices[i*12+8] = i*3+4; - vIndices[i*12+9] = i*3+1; - vIndices[i*12+10] = i*3+2; - vIndices[i*12+11] = i*3+5; - } - - TurnOffHardwareVBO(); - SendCurrentMatrices(); - - SetupVertices( v, iNumVerts ); - glDrawElements( - GL_TRIANGLES, - iNumIndices, - GL_UNSIGNED_SHORT, - &vIndices[0] ); -} - -void RageDisplay_Legacy::DrawFanInternal( const RageSpriteVertex v[], int iNumVerts ) -{ - TurnOffHardwareVBO(); - SendCurrentMatrices(); - - SetupVertices( v, iNumVerts ); - glDrawArrays( GL_TRIANGLE_FAN, 0, iNumVerts ); -} - -void RageDisplay_Legacy::DrawStripInternal( const RageSpriteVertex v[], int iNumVerts ) -{ - TurnOffHardwareVBO(); - SendCurrentMatrices(); - - SetupVertices( v, iNumVerts ); - glDrawArrays( GL_TRIANGLE_STRIP, 0, iNumVerts ); -} - -void RageDisplay_Legacy::DrawTrianglesInternal( const RageSpriteVertex v[], int iNumVerts ) -{ - TurnOffHardwareVBO(); - SendCurrentMatrices(); - - SetupVertices( v, iNumVerts ); - glDrawArrays( GL_TRIANGLES, 0, iNumVerts ); -} - -void RageDisplay_Legacy::DrawCompiledGeometryInternal( const RageCompiledGeometry *p, int iMeshIndex ) -{ - TurnOffHardwareVBO(); - SendCurrentMatrices(); - - p->Draw( iMeshIndex ); -} - -void RageDisplay_Legacy::DrawLineStripInternal( const RageSpriteVertex v[], int iNumVerts, float fLineWidth ) -{ - TurnOffHardwareVBO(); - - if (!GetActualVideoModeParams().bSmoothLines) - { - /* Fall back on the generic polygon-based line strip. */ - RageDisplay::DrawLineStripInternal(v, iNumVerts, fLineWidth ); - return; - } - - SendCurrentMatrices(); - - /* Draw a nice AA'd line loop. One problem with this is that point and line - * sizes don't always precisely match, which doesn't look quite right. - * It's worth it for the AA, though. */ - glEnable( GL_LINE_SMOOTH ); - - /* fLineWidth is in units relative to object space, but OpenGL line and point sizes - * are in raster units (actual pixels). Scale the line width by the average ratio; - * if object space is 640x480, and we have a 1280x960 window, we'll double the - * width. */ - { - const RageMatrix* pMat = GetProjectionTop(); - float fW = 2 / pMat->m[0][0]; - float fH = -2 / pMat->m[1][1]; - float fWidthVal = float(g_pWind->GetActualVideoModeParams().width) / fW; - float fHeightVal = float(g_pWind->GetActualVideoModeParams().height) / fH; - fLineWidth *= (fWidthVal + fHeightVal) / 2; - } - - /* Clamp the width to the hardware max for both lines and points (whichever - * is more restrictive). */ - fLineWidth = clamp( fLineWidth, g_line_range[0], g_line_range[1] ); - fLineWidth = clamp( fLineWidth, g_point_range[0], g_point_range[1] ); - - /* Hmm. The granularity of lines and points might be different; for example, - * if lines are .5 and points are .25, we might want to snap the width to the - * nearest .5, so the hardware doesn't snap them to different sizes. Does it - * matter? */ - glLineWidth( fLineWidth ); - - /* Draw the line loop: */ - SetupVertices( v, iNumVerts ); - glDrawArrays( GL_LINE_STRIP, 0, iNumVerts ); - - glDisable( GL_LINE_SMOOTH ); - - /* Round off the corners. This isn't perfect; the point is sometimes a little - * larger than the line, causing a small bump on the edge. Not sure how to fix - * that. */ - glPointSize( fLineWidth ); - - /* Hack: if the points will all be the same, we don't want to draw - * any points at all, since there's nothing to connect. That'll happen - * if both scale factors in the matrix are ~0. (Actually, I think - * it's true if two of the three scale factors are ~0, but we don't - * use this for anything 3d at the moment anyway ...) This is needed - * because points aren't scaled like regular polys--a zero-size point - * will still be drawn. */ - RageMatrix mat; - glGetFloatv( GL_MODELVIEW_MATRIX, (float*)mat ); - - if (mat.m[0][0] < 1e-5 && mat.m[1][1] < 1e-5) - return; - - glEnable( GL_POINT_SMOOTH ); - - SetupVertices( v, iNumVerts ); - glDrawArrays( GL_POINTS, 0, iNumVerts ); - - glDisable( GL_POINT_SMOOTH ); -} - -static bool SetTextureUnit( TextureUnit tu ) -{ - // If multitexture isn't supported, ignore all textures except for 0. - if (!GLEW_ARB_multitexture && tu != TextureUnit_1) - return false; - - if ((int) tu > g_iMaxTextureUnits) - return false; - glActiveTextureARB( enum_add2(GL_TEXTURE0_ARB, tu) ); - return true; -} - -void RageDisplay_Legacy::ClearAllTextures() -{ - FOREACH_ENUM( TextureUnit, i ) - SetTexture( i, 0 ); - - // HACK: Reset the active texture to 0. - // TODO: Change all texture functions to take a stage number. - if (GLEW_ARB_multitexture) - glActiveTextureARB(GL_TEXTURE0_ARB); -} - -int RageDisplay_Legacy::GetNumTextureUnits() -{ - if (GLEW_ARB_multitexture) - return 1; - else - return g_iMaxTextureUnits; -} - -void RageDisplay_Legacy::SetTexture( TextureUnit tu, unsigned iTexture ) -{ - if (!SetTextureUnit( tu )) - return; - - if (iTexture) - { - glEnable( GL_TEXTURE_2D ); - glBindTexture( GL_TEXTURE_2D, iTexture ); - } - else - { - glDisable( GL_TEXTURE_2D ); - } -} - -void RageDisplay_Legacy::SetTextureMode( TextureUnit tu, TextureMode tm ) -{ - if (!SetTextureUnit( tu )) - return; - - switch( tm ) - { - case TextureMode_Modulate: - glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - break; - case TextureMode_Add: - glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ); - break; - case TextureMode_Glow: - // the below function is glowmode,brighten: - if (!GLEW_ARB_texture_env_combine && !GLEW_EXT_texture_env_combine) - { - /* This is changing blend state, instead of texture state, which - * isn't great, but it's better than doing nothing. */ - glBlendFunc( GL_SRC_ALPHA, GL_ONE ); - return; - } - - // and this is glowmode,whiten: - // Source color is the diffuse color only: - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); - glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_COMBINE_RGB_EXT), GL_REPLACE); - glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_SOURCE0_RGB_EXT), GL_PRIMARY_COLOR_EXT); - - // Source alpha is texture alpha * diffuse alpha: - glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_COMBINE_ALPHA_EXT), GL_MODULATE); - glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_OPERAND0_ALPHA_EXT), GL_SRC_ALPHA); - glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_SOURCE0_ALPHA_EXT), GL_PRIMARY_COLOR_EXT); - glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_OPERAND1_ALPHA_EXT), GL_SRC_ALPHA); - glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_SOURCE1_ALPHA_EXT), GL_TEXTURE); - break; - } -} - -void RageDisplay_Legacy::SetTextureFiltering( TextureUnit tu, bool b ) -{ - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, b ? GL_LINEAR : GL_NEAREST); - - GLint iMinFilter; - if (b) - { - GLint iWidth1 = -1; - GLint iWidth2 = -1; - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &iWidth1); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 1, GL_TEXTURE_WIDTH, &iWidth2); - if (iWidth1 > 1 && iWidth2 != 0) - { - /* Mipmaps are enabled. */ - if (g_pWind->GetActualVideoModeParams().bTrilinearFiltering) - iMinFilter = GL_LINEAR_MIPMAP_LINEAR; - else - iMinFilter = GL_LINEAR_MIPMAP_NEAREST; - } - else - { - iMinFilter = GL_LINEAR; - } - } - else - { - iMinFilter = GL_NEAREST; - } - - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, iMinFilter ); -} - -void RageDisplay_Legacy::SetEffectMode( EffectMode effect ) -{ - if (!GLEW_ARB_fragment_program || !GLEW_ARB_shading_language_100 || !GLEW_ARB_shader_objects) - return; - - GLhandleARB hShader = 0; - switch (effect) - { - case EffectMode_Normal: hShader = 0; break; - case EffectMode_Unpremultiply: hShader = g_bUnpremultiplyShader; break; - case EffectMode_ColorBurn: hShader = g_bColorBurnShader; break; - case EffectMode_ColorDodge: hShader = g_bColorDodgeShader; break; - case EffectMode_VividLight: hShader = g_bVividLightShader; break; - case EffectMode_HardMix: hShader = g_hHardMixShader; break; - case EffectMode_Overlay: hShader = g_hOverlayShader; break; - case EffectMode_Screen: hShader = g_hScreenShader; break; - case EffectMode_YUYV422: hShader = g_hYUYV422Shader; break; - } - - DebugFlushGLErrors(); - glUseProgramObjectARB( hShader ); - if (hShader == 0) - return; - GLint iTexture1 = glGetUniformLocationARB( hShader, "Texture1" ); - GLint iTexture2 = glGetUniformLocationARB( hShader, "Texture2" ); - glUniform1iARB( iTexture1, 0 ); - glUniform1iARB( iTexture2, 1 ); - - if (effect == EffectMode_YUYV422) - { - GLint iTextureWidthUniform = glGetUniformLocationARB( hShader, "TextureWidth" ); - GLint iWidth; - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &iWidth ); - glUniform1iARB( iTextureWidthUniform, iWidth ); - } - - DebugAssertNoGLError(); -} - -bool RageDisplay_Legacy::IsEffectModeSupported( EffectMode effect ) -{ - switch( effect ) - { - case EffectMode_Normal: return true; - case EffectMode_Unpremultiply: return g_bUnpremultiplyShader != 0; - case EffectMode_ColorBurn: return g_bColorBurnShader != 0; - case EffectMode_ColorDodge: return g_bColorDodgeShader != 0; - case EffectMode_VividLight: return g_bVividLightShader != 0; - case EffectMode_HardMix: return g_hHardMixShader != 0; - case EffectMode_Overlay: return g_hOverlayShader != 0; - case EffectMode_Screen: return g_hScreenShader != 0; - case EffectMode_YUYV422: return g_hYUYV422Shader != 0; - } - - return false; -} - -void RageDisplay_Legacy::SetBlendMode( BlendMode mode ) -{ - glEnable(GL_BLEND); - - if (glBlendEquation != NULL) - { - if (mode == BLEND_INVERT_DEST) - glBlendEquation( GL_FUNC_SUBTRACT ); - else if (mode == BLEND_SUBTRACT) - glBlendEquation( GL_FUNC_REVERSE_SUBTRACT ); - else - glBlendEquation( GL_FUNC_ADD ); - } - - int iSourceRGB, iDestRGB; - int iSourceAlpha = GL_ONE, iDestAlpha = GL_ONE_MINUS_SRC_ALPHA; - switch( mode ) - { - case BLEND_NORMAL: - iSourceRGB = GL_SRC_ALPHA; iDestRGB = GL_ONE_MINUS_SRC_ALPHA; - break; - case BLEND_ADD: - iSourceRGB = GL_SRC_ALPHA; iDestRGB = GL_ONE; - break; - case BLEND_SUBTRACT: - iSourceRGB = GL_SRC_ALPHA; iDestRGB = GL_ONE_MINUS_SRC_ALPHA; - break; - case BLEND_MODULATE: - iSourceRGB = GL_ZERO; iDestRGB = GL_SRC_COLOR; - break; - case BLEND_COPY_SRC: - iSourceRGB = GL_ONE; iDestRGB = GL_ZERO; - iSourceAlpha = GL_ONE; iDestAlpha = GL_ZERO; - break; - case BLEND_ALPHA_MASK: - iSourceRGB = GL_ZERO; iDestRGB = GL_ONE; - iSourceAlpha = GL_ZERO; iDestAlpha = GL_SRC_ALPHA; - break; - case BLEND_ALPHA_KNOCK_OUT: - iSourceRGB = GL_ZERO; iDestRGB = GL_ONE; - iSourceAlpha = GL_ZERO; iDestAlpha = GL_ONE_MINUS_SRC_ALPHA; - break; - case BLEND_ALPHA_MULTIPLY: - iSourceRGB = GL_SRC_ALPHA; iDestRGB = GL_ZERO; - break; - case BLEND_WEIGHTED_MULTIPLY: - /* output = 2*(dst*src). 0.5,0.5,0.5 is identity; darker colors darken the image, - * and brighter colors lighten the image. */ - iSourceRGB = GL_DST_COLOR; iDestRGB = GL_SRC_COLOR; - break; - case BLEND_INVERT_DEST: - /* out = src - dst. The source color should almost always be #FFFFFF, to make it "1 - dst". */ - iSourceRGB = GL_ONE; iDestRGB = GL_ONE; - break; - case BLEND_NO_EFFECT: - iSourceRGB = GL_ZERO; iDestRGB = GL_ONE; - iSourceAlpha = GL_ZERO; iDestAlpha = GL_ONE; - break; - DEFAULT_FAIL( mode ); - } - - if (GLEW_EXT_blend_equation_separate) - glBlendFuncSeparateEXT( iSourceRGB, iDestRGB, iSourceAlpha, iDestAlpha ); - else - glBlendFunc( iSourceRGB, iDestRGB ); -} - -bool RageDisplay_Legacy::IsZWriteEnabled() const -{ - bool a; - glGetBooleanv( GL_DEPTH_WRITEMASK, (unsigned char*)&a ); - return a; -} - -bool RageDisplay_Legacy::IsZTestEnabled() const -{ - GLenum a; - glGetIntegerv( GL_DEPTH_FUNC, (GLint*)&a ); - return a != GL_ALWAYS; -} - -void RageDisplay_Legacy::ClearZBuffer() -{ - bool write = IsZWriteEnabled(); - SetZWrite( true ); - glClear( GL_DEPTH_BUFFER_BIT ); - SetZWrite( write ); -} - -void RageDisplay_Legacy::SetZWrite( bool b ) -{ - glDepthMask( b ); -} - -void RageDisplay_Legacy::SetZBias( float f ) -{ - float fNear = SCALE( f, 0.0f, 1.0f, 0.05f, 0.0f ); - float fFar = SCALE( f, 0.0f, 1.0f, 1.0f, 0.95f ); - - glDepthRange( fNear, fFar ); -} - -void RageDisplay_Legacy::SetZTestMode( ZTestMode mode ) -{ - glEnable( GL_DEPTH_TEST ); - switch( mode ) - { - case ZTEST_OFF: glDepthFunc( GL_ALWAYS ); break; - case ZTEST_WRITE_ON_PASS: glDepthFunc( GL_LEQUAL ); break; - case ZTEST_WRITE_ON_FAIL: glDepthFunc( GL_GREATER ); break; - default: - FAIL_M(ssprintf("Invalid ZTestMode: %i", mode)); - } -} - -void RageDisplay_Legacy::SetTextureWrapping( TextureUnit tu, bool b ) -{ - /* This should be per-texture-unit state, but it's per-texture state in OpenGl, - * so we'll behave incorrectly if the same texture is used in more than one texture - * unit simultaneously with different wrapping. */ - SetTextureUnit( tu ); - - GLenum mode = b ? GL_REPEAT : GL_CLAMP_TO_EDGE; - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mode ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mode ); -} - -void RageDisplay_Legacy::SetMaterial( - const RageColor &emissive, - const RageColor &ambient, - const RageColor &diffuse, - const RageColor &specular, - float shininess - ) -{ - // TRICKY: If lighting is off, then setting the material - // will have no effect. Even if lighting is off, we still - // want Models to have basic color and transparency. - // We can do this fake lighting by setting the vertex color. - // XXX: unintended: SetLighting must be called before SetMaterial - GLboolean bLighting; - glGetBooleanv( GL_LIGHTING, &bLighting ); - - if (bLighting) - { - glMaterialfv( GL_FRONT, GL_EMISSION, emissive ); - glMaterialfv( GL_FRONT, GL_AMBIENT, ambient ); - glMaterialfv( GL_FRONT, GL_DIFFUSE, diffuse ); - glMaterialfv( GL_FRONT, GL_SPECULAR, specular ); - glMaterialf( GL_FRONT, GL_SHININESS, shininess ); - } - else - { - RageColor c = diffuse; - c.r += emissive.r + ambient.r; - c.g += emissive.g + ambient.g; - c.b += emissive.b + ambient.b; - glColor4fv( c ); - } -} - -void RageDisplay_Legacy::SetLighting( bool b ) -{ - if (b) - glEnable(GL_LIGHTING); - else - glDisable(GL_LIGHTING); -} - -void RageDisplay_Legacy::SetLightOff( int index ) -{ - glDisable( GL_LIGHT0+index ); -} - -void RageDisplay_Legacy::SetLightDirectional( - int index, - const RageColor &ambient, - const RageColor &diffuse, - const RageColor &specular, - const RageVector3 &dir ) -{ - // Light coordinates are transformed by the modelview matrix, but - // we are being passed in world-space coords. - glPushMatrix(); - glLoadIdentity(); - - glEnable( GL_LIGHT0+index ); - glLightfv( GL_LIGHT0+index, GL_AMBIENT, ambient ); - glLightfv( GL_LIGHT0+index, GL_DIFFUSE, diffuse ); - glLightfv( GL_LIGHT0+index, GL_SPECULAR, specular ); - float position[4] = {dir.x, dir.y, dir.z, 0}; - glLightfv( GL_LIGHT0+index, GL_POSITION, position ); - - glPopMatrix(); -} - -void RageDisplay_Legacy::SetCullMode( CullMode mode ) -{ - if (mode != CULL_NONE) - glEnable(GL_CULL_FACE); - switch( mode ) - { - case CULL_BACK: - glCullFace( GL_BACK ); - break; - case CULL_FRONT: - glCullFace( GL_FRONT ); - break; - case CULL_NONE: - glDisable( GL_CULL_FACE ); - break; - default: - FAIL_M(ssprintf("Invalid CullMode: %i", mode)); - } -} - -const RageDisplay::RagePixelFormatDesc *RageDisplay_Legacy::GetPixelFormatDesc(RagePixelFormat pf) const -{ - ASSERT( pf < NUM_RagePixelFormat ); - return &PIXEL_FORMAT_DESC[pf]; -} - -bool RageDisplay_Legacy::SupportsThreadedRendering() -{ - return g_pWind->SupportsThreadedRendering(); -} - -void RageDisplay_Legacy::BeginConcurrentRenderingMainThread() -{ - g_pWind->BeginConcurrentRenderingMainThread(); -} - -void RageDisplay_Legacy::EndConcurrentRenderingMainThread() -{ - g_pWind->EndConcurrentRenderingMainThread(); -} - -void RageDisplay_Legacy::BeginConcurrentRendering() -{ - g_pWind->BeginConcurrentRendering(); - RageDisplay::BeginConcurrentRendering(); -} - -void RageDisplay_Legacy::EndConcurrentRendering() -{ - g_pWind->EndConcurrentRendering(); -} - -void RageDisplay_Legacy::DeleteTexture( unsigned iTexture ) -{ - if (iTexture == 0) - return; - - if (g_mapRenderTargets.find(iTexture) != g_mapRenderTargets.end()) - { - delete g_mapRenderTargets[iTexture]; - g_mapRenderTargets.erase( iTexture ); - return; - } - - DebugFlushGLErrors(); - glDeleteTextures( 1, reinterpret_cast(&iTexture) ); - DebugAssertNoGLError(); -} - - -RagePixelFormat RageDisplay_Legacy::GetImgPixelFormat( RageSurface* &img, bool &bFreeImg, int width, int height, bool bPalettedTexture ) -{ - RagePixelFormat pixfmt = FindPixelFormat( img->format->BitsPerPixel, img->format->Rmask, img->format->Gmask, img->format->Bmask, img->format->Amask ); - - /* If img is paletted, we're setting up a non-paletted texture, and color indexes - * are too small, depalettize. */ - bool bSupported = true; - if (!bPalettedTexture && img->fmt.BytesPerPixel == 1 && !g_bColorIndexTableWorks) - bSupported = false; - - if (pixfmt == RagePixelFormat_Invalid || !SupportsSurfaceFormat(pixfmt)) - bSupported = false; - - if (!bSupported) - { - /* The source isn't in a supported, known pixel format. We need to convert - * it ourself. Just convert it to RGBA8, and let OpenGL convert it back - * down to whatever the actual pixel format is. This is a very slow code - * path, which should almost never be used. */ - pixfmt = RagePixelFormat_RGBA8; - ASSERT( SupportsSurfaceFormat(pixfmt) ); - - const RagePixelFormatDesc *pfd = DISPLAY->GetPixelFormatDesc(pixfmt); - - RageSurface *imgconv = CreateSurface( img->w, img->h, - pfd->bpp, pfd->masks[0], pfd->masks[1], pfd->masks[2], pfd->masks[3] ); - RageSurfaceUtils::Blit( img, imgconv, width, height ); - img = imgconv; - bFreeImg = true; - } - else - { - bFreeImg = false; - } - - return pixfmt; -} - -/* If we're sending a paletted surface to a non-paletted texture, set the palette. */ -void SetPixelMapForSurface( int glImageFormat, int glTexFormat, const RageSurfacePalette *palette ) -{ - if (glImageFormat != GL_COLOR_INDEX || glTexFormat == GL_COLOR_INDEX8_EXT) - { - glPixelTransferi( GL_MAP_COLOR, false ); - return; - } - - GLushort buf[4][256]; - memset( buf, 0, sizeof(buf) ); - - for( int i = 0; i < palette->ncolors; ++i ) - { - buf[0][i] = SCALE( palette->colors[i].r, 0, 255, 0, 65535 ); - buf[1][i] = SCALE( palette->colors[i].g, 0, 255, 0, 65535 ); - buf[2][i] = SCALE( palette->colors[i].b, 0, 255, 0, 65535 ); - buf[3][i] = SCALE( palette->colors[i].a, 0, 255, 0, 65535 ); - } - - DebugFlushGLErrors(); - glPixelMapusv( GL_PIXEL_MAP_I_TO_R, 256, buf[0] ); - glPixelMapusv( GL_PIXEL_MAP_I_TO_G, 256, buf[1] ); - glPixelMapusv( GL_PIXEL_MAP_I_TO_B, 256, buf[2] ); - glPixelMapusv( GL_PIXEL_MAP_I_TO_A, 256, buf[3] ); - glPixelTransferi( GL_MAP_COLOR, true ); - DebugAssertNoGLError(); -} - -unsigned RageDisplay_Legacy::CreateTexture( - RagePixelFormat pixfmt, - RageSurface* pImg, - bool bGenerateMipMaps ) -{ - ASSERT( pixfmt < NUM_RagePixelFormat ); - - - /* Find the pixel format of the surface we've been given. */ - bool bFreeImg; - RagePixelFormat SurfacePixFmt = GetImgPixelFormat( pImg, bFreeImg, pImg->w, pImg->h, pixfmt == RagePixelFormat_PAL ); - ASSERT( SurfacePixFmt != RagePixelFormat_Invalid ); - - GLenum glTexFormat = g_GLPixFmtInfo[pixfmt].internalfmt; - GLenum glImageFormat = g_GLPixFmtInfo[SurfacePixFmt].format; - GLenum glImageType = g_GLPixFmtInfo[SurfacePixFmt].type; - - /* If the image is paletted, but we're not sending it to a paletted image, - * set up glPixelMap. */ - SetPixelMapForSurface( glImageFormat, glTexFormat, pImg->format->palette ); - - // HACK: OpenGL 1.2 types aren't available in GLU 1.3. Don't call GLU for mip - // mapping if we're using an OGL 1.2 type and don't have >= GLU 1.3. - // http://pyopengl.sourceforge.net/documentation/manual/gluBuild2DMipmaps.3G.html - if (bGenerateMipMaps && g_gluVersion < 13) - { - switch( pixfmt ) - { - // OpenGL 1.1 types - case RagePixelFormat_RGBA8: - case RagePixelFormat_RGB8: - case RagePixelFormat_PAL: - case RagePixelFormat_BGR8: - break; - // OpenGL 1.2 types - default: - LOG->Trace( "Can't generate mipmaps for type %s because GLU version %.1f is too old.", GLToString(glImageType).c_str(), g_gluVersion/10.f ); - bGenerateMipMaps = false; - break; - } - } - - SetTextureUnit( TextureUnit_1 ); - - // allocate OpenGL texture resource - unsigned int iTexHandle; - glGenTextures( 1, reinterpret_cast(&iTexHandle) ); - ASSERT( iTexHandle != 0 ); - - glBindTexture( GL_TEXTURE_2D, iTexHandle ); - - if (g_pWind->GetActualVideoModeParams().bAnisotropicFiltering && - GLEW_EXT_texture_filter_anisotropic ) - { - GLfloat fLargestSupportedAnisotropy; - glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargestSupportedAnisotropy ); - glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargestSupportedAnisotropy ); - } - - SetTextureFiltering( TextureUnit_1, true ); - SetTextureWrapping( TextureUnit_1, false ); - - glPixelStorei( GL_UNPACK_ROW_LENGTH, pImg->pitch / pImg->format->BytesPerPixel ); - - - if (pixfmt == RagePixelFormat_PAL) - { - /* The texture is paletted; set the texture palette. */ - GLubyte palette[256*4]; - memset( palette, 0, sizeof(palette) ); - int p = 0; - /* Copy the palette to the format OpenGL expects. */ - for( int i = 0; i < pImg->format->palette->ncolors; ++i ) - { - palette[p++] = pImg->format->palette->colors[i].r; - palette[p++] = pImg->format->palette->colors[i].g; - palette[p++] = pImg->format->palette->colors[i].b; - palette[p++] = pImg->format->palette->colors[i].a; - } - - /* Set the palette. */ - glColorTableEXT( GL_TEXTURE_2D, GL_RGBA8, 256, GL_RGBA, GL_UNSIGNED_BYTE, palette ); - - GLint iRealFormat = 0; - glGetColorTableParameterivEXT( GL_TEXTURE_2D, GL_COLOR_TABLE_FORMAT, &iRealFormat ); - ASSERT( iRealFormat == GL_RGBA8 ); - } - - LOG->Trace( "%s (format %s, %ix%i, format %s, type %s, pixfmt %i, imgpixfmt %i)", - bGenerateMipMaps? "gluBuild2DMipmaps":"glTexImage2D", - GLToString(glTexFormat).c_str(), - pImg->w, pImg->h, - GLToString(glImageFormat).c_str(), - GLToString(glImageType).c_str(), pixfmt, SurfacePixFmt ); - - DebugFlushGLErrors(); - - if (bGenerateMipMaps) - { - GLenum error = gluBuild2DMipmaps( - GL_TEXTURE_2D, glTexFormat, - pImg->w, pImg->h, - glImageFormat, glImageType, pImg->pixels ); - ASSERT_M( error == 0, (char *) gluErrorString(error) ); - } - else - { - glTexImage2D( - GL_TEXTURE_2D, 0, glTexFormat, - power_of_two(pImg->w), power_of_two(pImg->h), 0, - glImageFormat, glImageType, NULL ); - if (pImg->pixels) - glTexSubImage2D( GL_TEXTURE_2D, 0, - 0, 0, - pImg->w, pImg->h, - glImageFormat, glImageType, pImg->pixels ); - - DebugAssertNoGLError(); - } - - - /* Sanity check: */ - if (pixfmt == RagePixelFormat_PAL) - { - GLint iSize = 0; - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INDEX_SIZE_EXT), &iSize ); - if (iSize != 8) - RageException::Throw( "Thought paletted textures worked, but they don't." ); - } - - glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 ); - glFlush(); - - if (bFreeImg) - delete pImg; - return iTexHandle; -} - -struct RageTextureLock_OGL: public RageTextureLock, public InvalidateObject -{ -public: - RageTextureLock_OGL() - { - m_iTexHandle = 0; - m_iBuffer = 0; - - CreateObject(); - } - - ~RageTextureLock_OGL() - { - ASSERT( m_iTexHandle == 0 ); // locked! - glDeleteBuffersARB( 1, &m_iBuffer ); - } - - /* This is called when our OpenGL context is invalidated. */ - void Invalidate() - { - m_iTexHandle = 0; - } - - void Lock( unsigned iTexHandle, RageSurface *pSurface ) - { - ASSERT( m_iTexHandle == 0 ); - ASSERT( pSurface->pixels == NULL ); - - CreateObject(); - - m_iTexHandle = iTexHandle; - glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, m_iBuffer ); - - int iSize = pSurface->h * pSurface->pitch; - glBufferDataARB( GL_PIXEL_UNPACK_BUFFER_ARB, iSize, NULL, GL_STREAM_DRAW ); - - void *pSurfaceMemory = glMapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY ); - pSurface->pixels = (uint8_t *) pSurfaceMemory; - pSurface->pixels_owned = false; - } - - void Unlock( RageSurface *pSurface, bool bChanged ) - { - glUnmapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB ); - - pSurface->pixels = (uint8_t *) BUFFER_OFFSET(0); - - if (bChanged) - DISPLAY->UpdateTexture( m_iTexHandle, pSurface, 0, 0, pSurface->w, pSurface->h ); - - pSurface->pixels = NULL; - - m_iTexHandle = 0; - glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, 0 ); - } - -private: - void CreateObject() - { - if (m_iBuffer != 0) - return; - - DebugFlushGLErrors(); - glGenBuffersARB( 1, &m_iBuffer ); - DebugAssertNoGLError(); - } - - GLuint m_iBuffer; - - unsigned m_iTexHandle; -}; - -RageTextureLock *RageDisplay_Legacy::CreateTextureLock() -{ - if (!GLEW_ARB_pixel_buffer_object) - return NULL; - - return new RageTextureLock_OGL; -} - -void RageDisplay_Legacy::UpdateTexture( - unsigned iTexHandle, - RageSurface* pImg, - int iXOffset, int iYOffset, int iWidth, int iHeight ) -{ - glBindTexture( GL_TEXTURE_2D, iTexHandle ); - - bool bFreeImg; - RagePixelFormat SurfacePixFmt = GetImgPixelFormat( pImg, bFreeImg, iWidth, iHeight, false ); - - glPixelStorei( GL_UNPACK_ROW_LENGTH, pImg->pitch / pImg->format->BytesPerPixel ); - - GLenum glImageFormat = g_GLPixFmtInfo[SurfacePixFmt].format; - GLenum glImageType = g_GLPixFmtInfo[SurfacePixFmt].type; - - /* If the image is paletted, but we're not sending it to a paletted image, - * set up glPixelMap. */ - if (pImg->format->palette) - { - GLenum glTexFormat = 0; - glGetTexLevelParameteriv( GL_PROXY_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INTERNAL_FORMAT), (GLint *) &glTexFormat ); - SetPixelMapForSurface( glImageFormat, glTexFormat, pImg->format->palette ); - } - - glTexSubImage2D( GL_TEXTURE_2D, 0, - iXOffset, iYOffset, - iWidth, iHeight, - glImageFormat, glImageType, pImg->pixels ); - - /* Must unset PixelStore when we're done! */ - glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 ); - glFlush(); - - if (bFreeImg) - delete pImg; -} - -class RenderTarget_FramebufferObject: public RenderTarget -{ -public: - RenderTarget_FramebufferObject(); - ~RenderTarget_FramebufferObject(); - void Create( const RenderTargetParam ¶m, int &iTextureWidthOut, int &iTextureHeightOut ); - unsigned GetTexture() const { return m_iTexHandle; } - void StartRenderingTo(); - void FinishRenderingTo(); - - virtual bool InvertY() const { return true; } - -private: - unsigned int m_iFrameBufferHandle; - unsigned int m_iTexHandle; - unsigned int m_iDepthBufferHandle; -}; - -RenderTarget_FramebufferObject::RenderTarget_FramebufferObject() -{ - m_iFrameBufferHandle = 0; - m_iTexHandle = 0; - m_iDepthBufferHandle = 0; -} - -RenderTarget_FramebufferObject::~RenderTarget_FramebufferObject() -{ - if (m_iDepthBufferHandle) - glDeleteRenderbuffersEXT( 1, reinterpret_cast(&m_iDepthBufferHandle) ); - if (m_iFrameBufferHandle) - glDeleteFramebuffersEXT( 1, reinterpret_cast(&m_iFrameBufferHandle) ); - if (m_iTexHandle) - glDeleteTextures( 1, reinterpret_cast(&m_iTexHandle) ); -} - -void RenderTarget_FramebufferObject::Create( const RenderTargetParam ¶m, int &iTextureWidthOut, int &iTextureHeightOut ) -{ - m_Param = param; - - DebugFlushGLErrors(); - - // Allocate OpenGL texture resource - glGenTextures( 1, reinterpret_cast(&m_iTexHandle) ); - ASSERT( m_iTexHandle != 0 ); - - int iTextureWidth = power_of_two( param.iWidth ); - int iTextureHeight = power_of_two( param.iHeight ); - - iTextureWidthOut = iTextureWidth; - iTextureHeightOut = iTextureHeight; - - glBindTexture( GL_TEXTURE_2D, m_iTexHandle ); - GLenum internalformat; - GLenum type = param.bWithAlpha? GL_RGBA:GL_RGB; - if (param.bFloat && GLEW_ARB_texture_float) - internalformat = param.bWithAlpha? GL_RGBA16F_ARB:GL_RGB16F_ARB; - else - internalformat = param.bWithAlpha? GL_RGBA8:GL_RGB8; - - glTexImage2D( GL_TEXTURE_2D, 0, internalformat, - iTextureWidth, iTextureHeight, 0, type, GL_UNSIGNED_BYTE, NULL ); - DebugAssertNoGLError(); - - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); - glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); - glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - - /* Create the framebuffer object. */ - glGenFramebuffersEXT( 1, reinterpret_cast(&m_iFrameBufferHandle) ); - ASSERT( m_iFrameBufferHandle != 0 ); - - /* Attach the texture to it. */ - glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_iFrameBufferHandle ); - glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_iTexHandle, 0 ); - DebugAssertNoGLError(); - - /* Attach a depth buffer, if requested. */ - if (param.bWithDepthBuffer) - { - glGenRenderbuffersEXT( 1, reinterpret_cast(&m_iDepthBufferHandle) ); - ASSERT( m_iDepthBufferHandle != 0 ); - - glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT16, iTextureWidth, iTextureHeight ); - glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, m_iDepthBufferHandle ); - } - - GLenum status = glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT ); - switch(status) - { - case GL_FRAMEBUFFER_COMPLETE_EXT: - break; - case GL_FRAMEBUFFER_UNSUPPORTED_EXT: - FAIL_M( "GL_FRAMEBUFFER_UNSUPPORTED_EXT" ); - break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT" ); break; - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT" ); break; - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT" ); break; - case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT" ); break; - case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT" ); break; - case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT" ); break; - default: - FAIL_M(ssprintf("Unexpected GL framebuffer status: %i", status)); - } - - glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); -} - -void RenderTarget_FramebufferObject::StartRenderingTo() -{ - glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_iFrameBufferHandle ); -} - -void RenderTarget_FramebufferObject::FinishRenderingTo() -{ - glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); -} - -bool RageDisplay_Legacy::SupportsRenderToTexture() const -{ - return GLEW_EXT_framebuffer_object || g_pWind->SupportsRenderToTexture(); -} - -/* - * Render-to-texture can be implemented in several ways: the generic GL_ARB_pixel_buffer_object, - * or platform-specifically. PBO is not available on all hardware that supports RTT, - * particularly GeForce 2, but is simpler and faster when available. - */ - -unsigned RageDisplay_Legacy::CreateRenderTarget( const RenderTargetParam ¶m, int &iTextureWidthOut, int &iTextureHeightOut ) -{ - RenderTarget *pTarget; - if (GLEW_EXT_framebuffer_object) - pTarget = new RenderTarget_FramebufferObject; - else - pTarget = g_pWind->CreateRenderTarget(); - - pTarget->Create( param, iTextureWidthOut, iTextureHeightOut ); - - unsigned iTexture = pTarget->GetTexture(); - - ASSERT( g_mapRenderTargets.find(iTexture) == g_mapRenderTargets.end() ); - g_mapRenderTargets[iTexture] = pTarget; - return iTexture; -} - -void RageDisplay_Legacy::SetRenderTarget( unsigned iTexture, bool bPreserveTexture ) -{ - if (iTexture == 0) - { - g_bInvertY = false; - glFrontFace( GL_CCW ); - - /* Pop matrixes affected by SetDefaultRenderStates. */ - DISPLAY->CameraPopMatrix(); - - /* Reset the viewport. */ - int fWidth = g_pWind->GetActualVideoModeParams().width; - int fHeight = g_pWind->GetActualVideoModeParams().height; - glViewport( 0, 0, fWidth, fHeight ); - - if (g_pCurrentRenderTarget) - g_pCurrentRenderTarget->FinishRenderingTo(); - g_pCurrentRenderTarget = NULL; - return; - } - - /* If we already had a render target, disable it. */ - if (g_pCurrentRenderTarget != NULL) - SetRenderTarget(0, true); - - /* Enable the new render target. */ - ASSERT(g_mapRenderTargets.find(iTexture) != g_mapRenderTargets.end()); - RenderTarget *pTarget = g_mapRenderTargets[iTexture]; - pTarget->StartRenderingTo(); - g_pCurrentRenderTarget = pTarget; - - /* Set the viewport to the size of the render target. */ - glViewport(0, 0, pTarget->GetParam().iWidth, pTarget->GetParam().iHeight); - - /* If this render target implementation flips Y, compensate. Inverting will - * switch the winding order. */ - g_bInvertY = pTarget->InvertY(); - if (g_bInvertY) - glFrontFace(GL_CW); - - /* The render target may be in a different OpenGL context, so re-send - * state. Push matrixes affected by SetDefaultRenderStates. */ - DISPLAY->CameraPushMatrix(); - SetDefaultRenderStates(); - - /* Clear the texture, if requested. Always set the associated state, for - * consistency. */ - glClearColor(0,0,0,0); - SetZWrite(true); - - /* If bPreserveTexture is false, clear the render target. Only clear the depth - * buffer if the target has one; otherwise we're clearing the real depth buffer. */ - if (!bPreserveTexture) - { - int iBit = GL_COLOR_BUFFER_BIT; - if (pTarget->GetParam().bWithDepthBuffer) - iBit |= GL_DEPTH_BUFFER_BIT; - glClear(iBit); - } -} - -void RageDisplay_Legacy::SetPolygonMode(PolygonMode pm) -{ - GLenum m; - switch (pm) - { - case POLYGON_FILL: m = GL_FILL; break; - case POLYGON_LINE: m = GL_LINE; break; - default: - FAIL_M(ssprintf("Invalid PolygonMode: %i", pm)); - } - glPolygonMode(GL_FRONT_AND_BACK, m); -} - -void RageDisplay_Legacy::SetLineWidth(float fWidth) -{ - glLineWidth(fWidth); -} - -RString RageDisplay_Legacy::GetTextureDiagnostics(unsigned iTexture) const -{ - /* - s << (bGenerateMipMaps? "gluBuild2DMipmaps":"glTexImage2D"); - s << "(format " << GLToString(glTexFormat) << - ", " << pImg->w << "x" << pImg->h << - ", format " << GLToString(iFormat) << - ", type " << GLToString(glImageType) << - ", pixfmt " << pixfmt << - ", imgpixfmt " << SurfacePixFmt << - ")"; - LOG->Trace( "%s", s.str().c_str() ); - - glBindTexture( GL_TEXTURE_2D, iTexture ); - - GLint iWidth; - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GLenum(GL_TEXTURE_WIDTH), (GLint *) &iWidth ); - GLint iHeight; - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GLenum(GL_TEXTURE_HEIGHT), (GLint *) &iHeight ); - GLint iFormat; - glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INTERNAL_FORMAT), (GLint *) &iFormat ); - - GL_CHECK_ERROR( "glGetTexLevelParameteriv(GL_TEXTURE_INTERNAL_FORMAT)" ); - if (iFormat != glTexFormat) - { - sError = ssprintf( "Expected format %s, got %s instead", - GLToString(glTexFormat).c_str(), GLToString(iFormat).c_str() ); - break; - } -*/ - return RString(); -} - -/* - * XXX: Things like this only have to be set once per context - making - * SetDefault call These kinds of functions is wasteful. -Colby - */ -void RageDisplay_Legacy::SetAlphaTest(bool b) -{ - // Previously this was 0.01, rather than 0x01. - glAlphaFunc(GL_GREATER, 0.00390625 /* 1/256 */); - if (b) - glEnable(GL_ALPHA_TEST); - else - glDisable(GL_ALPHA_TEST); -} - - -/* - * Although we pair texture formats (eg. GL_RGB8) and surface formats - * (pairs of eg. GL_RGB8,GL_UNSIGNED_SHORT_5_5_5_1), it's possible for - * a format to be supported for a texture format but not a surface - * format. This is abstracted, so you don't need to know about this - * as a user calling CreateTexture. - * - * One case of this is if packed pixels aren't supported. We can still - * use 16-bit color modes, but we have to send it in 32-bit. Almost - * everything supports packed pixels. - * - * Another case of this is incomplete packed pixels support. Some implementations - * neglect GL_UNSIGNED_SHORT_*_REV. - */ -bool RageDisplay_Legacy::SupportsSurfaceFormat(RagePixelFormat pixfmt) -{ - switch (g_GLPixFmtInfo[pixfmt].type) - { - case GL_UNSIGNED_SHORT_1_5_5_5_REV: - return GLEW_EXT_bgra && g_bReversePackedPixelsWorks; - default: - return true; - } -} - - -bool RageDisplay_Legacy::SupportsTextureFormat(RagePixelFormat pixfmt, bool bRealtime) -{ - /* If we support a pixfmt for texture formats but not for surface formats, then - * we'll have to convert the texture to a supported surface format before uploading. - * This is too slow for dynamic textures. */ - if (bRealtime && !SupportsSurfaceFormat(pixfmt)) - return false; - - switch (g_GLPixFmtInfo[pixfmt].format) - { - case GL_COLOR_INDEX: - return glColorTableEXT && glGetColorTableParameterivEXT; - case GL_BGR: - case GL_BGRA: - return !!GLEW_EXT_bgra; - default: - return true; - } -} - -bool RageDisplay_Legacy::SupportsPerVertexMatrixScale() -{ - // Intel i915 on OSX 10.4.4 supports vertex programs but not hardware vertex buffers. - // Our software vertex rendering doesn't support vertex programs. - return glGenBuffersARB && g_bTextureMatrixShader != 0; -} - -void RageDisplay_Legacy::SetSphereEnvironmentMapping(TextureUnit tu, bool b) -{ - if (!SetTextureUnit(tu)) - return; - - if (b) - { - glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); - glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); - glEnable(GL_TEXTURE_GEN_S); - glEnable(GL_TEXTURE_GEN_T); - } - else - { - glDisable(GL_TEXTURE_GEN_S); - glDisable(GL_TEXTURE_GEN_T); - } -} - -GLint iCelTexture1, iCelTexture2 = 0; - -void RageDisplay_Legacy::SetCelShaded( int stage ) -{ - if (!GLEW_ARB_fragment_program && !GL_ARB_shading_language_100) - return; // not supported - - switch (stage) - { - case 1: - glUseProgramObjectARB(g_gShellShader); - break; - case 2: - glUseProgramObjectARB(g_gCelShader); - break; - default: - glUseProgramObjectARB(0); - break; - } -} - -/* - * Copyright (c) 2001-2011 Chris Danford, Glenn Maynard, Colby Klein - * 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 "RageDisplay_OGL.h" +#include "RageDisplay_OGL_Helpers.h" +using namespace RageDisplay_Legacy_Helpers; + +#include "RageFile.h" +#include "RageSurface.h" +#include "RageSurfaceUtils.h" +#include "RageUtil.h" +#include "RageLog.h" +#include "RageTextureManager.h" +#include "RageMath.h" +#include "RageTypes.h" +#include "RageUtil.h" +#include "EnumHelper.h" +#include "Foreach.h" +#include "DisplayResolutions.h" +#include "LocalizedString.h" + +#include "arch/LowLevelWindow/LowLevelWindow.h" + +#include + +#if defined(WINDOWS) +#include +#endif + +#if defined(_MSC_VER) +#pragma comment(lib, "opengl32.lib") +#pragma comment(lib, "glu32.lib") +#endif + +#ifdef NO_GL_FLUSH +#define glFlush() +#endif + +// +// Globals +// + +static bool g_bReversePackedPixelsWorks = true; +static bool g_bColorIndexTableWorks = true; + +/* OpenGL system information that generally doesn't change at runtime. */ + +/* Range and granularity of points and lines: */ +static float g_line_range[2]; +static float g_point_range[2]; + +/* OpenGL version * 10: */ +static int g_glVersion; + +/* GLU version * 10: */ +static int g_gluVersion; + +static int g_iMaxTextureUnits = 0; + +/* We don't actually use normals (we don't turn on lighting), there's just + * no GL_T2F_C4F_V3F. */ +static const GLenum RageSpriteVertexFormat = GL_T2F_C4F_N3F_V3F; + +/* If we support texture matrix scaling, a handle to the vertex program: */ +static GLhandleARB g_bTextureMatrixShader = 0; + +static map g_mapRenderTargets; +static RenderTarget *g_pCurrentRenderTarget = NULL; + +static LowLevelWindow *g_pWind; + +static bool g_bInvertY = false; + +static void InvalidateObjects(); + +static RageDisplay::RagePixelFormatDesc PIXEL_FORMAT_DESC[NUM_RagePixelFormat] = { + { + /* R8G8B8A8 */ + 32, + { 0xFF000000, + 0x00FF0000, + 0x0000FF00, + 0x000000FF } + }, { + /* B8G8R8A8 */ + 32, + { 0x0000FF00, + 0x00FF0000, + 0xFF000000, + 0x000000FF } + }, { + /* R4G4B4A4 */ + 16, + { 0xF000, + 0x0F00, + 0x00F0, + 0x000F }, + }, { + /* R5G5B5A1 */ + 16, + { 0xF800, + 0x07C0, + 0x003E, + 0x0001 }, + }, { + /* R5G5B5X1 */ + 16, + { 0xF800, + 0x07C0, + 0x003E, + 0x0000 }, + }, { + /* R8G8B8 */ + 24, + { 0xFF0000, + 0x00FF00, + 0x0000FF, + 0x000000 } + }, { + /* Paletted */ + 8, + { 0,0,0,0 } /* N/A */ + }, { + /* B8G8R8 */ + 24, + { 0x0000FF, + 0x00FF00, + 0xFF0000, + 0x000000 } + }, { + /* A1R5G5B5 */ + 16, + { 0x7C00, + 0x03E0, + 0x001F, + 0x8000 }, + }, { + /* X1R5G5B5 */ + 16, + { 0x7C00, + 0x03E0, + 0x001F, + 0x0000 }, + } +}; + +/* g_GLPixFmtInfo is used for both texture formats and surface formats. For example, + * it's fine to ask for a RagePixelFormat_RGB5 texture, but to supply a surface matching + * RagePixelFormat_RGB8. OpenGL will simply discard the extra bits. + * + * It's possible for a format to be supported as a texture format but not as a + * surface format. For example, if packed pixels aren't supported, we can still + * use GL_RGB5_A1, but we'll have to convert to a supported surface pixel format + * first. It's not ideal, since we'll convert to RGBA8 and OGL will convert back, + * but it works fine. + */ +struct GLPixFmtInfo_t { + GLenum internalfmt; /* target format */ + GLenum format; /* target format */ + GLenum type; /* data format */ +} const g_GLPixFmtInfo[NUM_RagePixelFormat] = { + { + /* R8G8B8A8 */ + GL_RGBA8, + GL_RGBA, + GL_UNSIGNED_BYTE, + }, { + /* R8G8B8A8 */ + GL_RGBA8, + GL_BGRA, + GL_UNSIGNED_BYTE, + }, { + /* B4G4R4A4 */ + GL_RGBA4, + GL_RGBA, + GL_UNSIGNED_SHORT_4_4_4_4, + }, { + /* B5G5R5A1 */ + GL_RGB5_A1, + GL_RGBA, + GL_UNSIGNED_SHORT_5_5_5_1, + }, { + /* B5G5R5 */ + GL_RGB5, + GL_RGBA, + GL_UNSIGNED_SHORT_5_5_5_1, + }, { + /* B8G8R8 */ + GL_RGB8, + GL_RGB, + GL_UNSIGNED_BYTE, + }, { + /* Paletted */ + GL_COLOR_INDEX8_EXT, + GL_COLOR_INDEX, + GL_UNSIGNED_BYTE, + }, { + /* B8G8R8 */ + GL_RGB8, + GL_BGR, + GL_UNSIGNED_BYTE, + }, { + /* A1R5G5B5 (matches D3DFMT_A1R5G5B5) */ + GL_RGB5_A1, + GL_BGRA, + GL_UNSIGNED_SHORT_1_5_5_5_REV, + }, { + /* X1R5G5B5 */ + GL_RGB5, + GL_BGRA, + GL_UNSIGNED_SHORT_1_5_5_5_REV, + } +}; + + +static void FixLittleEndian() +{ +#if defined(ENDIAN_LITTLE) + static bool bInitialized = false; + if (bInitialized) + return; + bInitialized = true; + + for( int i = 0; i < NUM_RagePixelFormat; ++i ) + { + RageDisplay::RagePixelFormatDesc &pf = PIXEL_FORMAT_DESC[i]; + + /* OpenGL and RageSurface handle byte formats differently; we need + * to flip non-paletted masks to make them line up. */ + if (g_GLPixFmtInfo[i].type != GL_UNSIGNED_BYTE || pf.bpp == 8) + continue; + + for( int mask = 0; mask < 4; ++mask) + { + int m = pf.masks[mask]; + switch( pf.bpp ) + { + case 24: m = Swap24(m); break; + case 32: m = Swap32(m); break; + default: + FAIL_M(ssprintf("Unsupported BPP value: %i", pf.bpp)); + } + pf.masks[mask] = m; + } + } +#endif +} + +static void TurnOffHardwareVBO() +{ + if (GLEW_ARB_vertex_buffer_object) + { + glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); + } +} + +RageDisplay_Legacy::RageDisplay_Legacy() +{ + LOG->Trace( "RageDisplay_Legacy::RageDisplay_Legacy()" ); + LOG->MapLog("renderer", "Current renderer: OpenGL"); + + FixLittleEndian(); + RageDisplay_Legacy_Helpers::Init(); + + g_pWind = NULL; + g_bTextureMatrixShader = 0; +} + +RString GetInfoLog( GLhandleARB h ) +{ + GLint iLength; + glGetObjectParameterivARB( h, GL_OBJECT_INFO_LOG_LENGTH_ARB, &iLength ); + if (!iLength) + return RString(); + + GLcharARB *pInfoLog = new GLcharARB[iLength]; + glGetInfoLogARB( h, iLength, &iLength, pInfoLog ); + RString sRet = pInfoLog; + delete [] pInfoLog; + TrimRight( sRet ); + return sRet; +} + +GLhandleARB CompileShader( GLenum ShaderType, RString sFile, vector asDefines ) +{ + /* XXX: This would not be necessary if it wasn't for the special case for Cel. */ + if (ShaderType == GL_FRAGMENT_SHADER_ARB && !glewIsSupported("GL_VERSION_2_0")) + { + LOG->Warn("Fragment shaders not supported by driver. Some effects will not be available."); + return 0; + } + + RString sBuffer; + { + RageFile file; + if (!file.Open(sFile)) + { + LOG->Warn( "Error compiling shader %s: %s", sFile.c_str(), file.GetError().c_str() ); + return 0; + } + + if (file.Read(sBuffer, file.GetFileSize()) == -1) + { + LOG->Warn( "Error compiling shader %s: %s", sFile.c_str(), file.GetError().c_str() ); + return 0; + } + } + + LOG->Trace( "Compiling shader %s", sFile.c_str() ); + GLhandleARB hShader = glCreateShaderObjectARB( ShaderType ); + vector apData; + vector aiLength; + FOREACH( RString, asDefines, s ) + { + *s = ssprintf( "#define %s\n", s->c_str() ); + apData.push_back( s->data() ); + aiLength.push_back( s->size() ); + } + apData.push_back( "#line 1\n" ); + aiLength.push_back( 8 ); + + apData.push_back( sBuffer.data() ); + aiLength.push_back( sBuffer.size() ); + glShaderSourceARB( hShader, apData.size(), &apData[0], &aiLength[0] ); + + glCompileShaderARB( hShader ); + + RString sInfo = GetInfoLog( hShader ); + + GLint bCompileStatus = GL_FALSE; + glGetObjectParameterivARB( hShader, GL_OBJECT_COMPILE_STATUS_ARB, &bCompileStatus ); + if (!bCompileStatus) + { + LOG->Warn( "Error compiling shader %s:\n%s", sFile.c_str(), sInfo.c_str() ); + glDeleteObjectARB( hShader ); + return 0; + } + + if (!sInfo.empty()) + LOG->Trace( "Messages compiling shader %s:\n%s", sFile.c_str(), sInfo.c_str() ); + + return hShader; +} + +GLhandleARB LoadShader( GLenum ShaderType, RString sFile, vector asDefines ) +{ + /* Vertex shaders are supported by more hardware than fragment shaders. + * If this causes any trouble I will have to up the requirement for both + * of them to at least GL 2.0. Regardless we need basic GLSL support. + * -Colby */ + if (!glewIsSupported("GL_ARB_shading_language_100 GL_ARB_shader_objects") || + (ShaderType == GL_FRAGMENT_SHADER_ARB && !glewIsSupported("GL_VERSION_2_0")) || + (ShaderType == GL_VERTEX_SHADER_ARB && !glewIsSupported("GL_ARB_vertex_shader"))) + { + LOG->Warn("%s shaders not supported by driver. Some effects will not be available.", + (ShaderType == GL_FRAGMENT_SHADER_ARB) ? "Fragment" : "Vertex"); + return 0; + } + + // XXX: dumb, but I don't feel like refactoring ragedisplay for this. -Colby + GLhandleARB secondaryShader = 0; + if (sFile == "Data/Shaders/GLSL/Cel.vert") + secondaryShader = CompileShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Cel.frag", asDefines); + else if (sFile == "Data/Shaders/GLSL/Shell.vert") + secondaryShader = CompileShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Shell.frag", asDefines); + + GLhandleARB hShader = CompileShader( ShaderType, sFile, asDefines ); + if (hShader == 0) + return 0; + + GLhandleARB hProgram = glCreateProgramObjectARB(); + glAttachObjectARB( hProgram, hShader ); + + if (secondaryShader) + { + glAttachObjectARB( hProgram, secondaryShader ); + glDeleteObjectARB( secondaryShader ); + } + glDeleteObjectARB( hShader ); + + // Link the program. + glLinkProgramARB( hProgram ); + GLint bLinkStatus = false; + glGetObjectParameterivARB( hProgram, GL_OBJECT_LINK_STATUS_ARB, &bLinkStatus ); + + if (!bLinkStatus) + { + LOG->Warn( "Error linking shader %s: %s", sFile.c_str(), GetInfoLog(hProgram).c_str() ); + glDeleteObjectARB( hProgram ); + return 0; + } + return hProgram; +} + +static int g_iAttribTextureMatrixScale; + +static GLhandleARB g_bUnpremultiplyShader = 0; +static GLhandleARB g_bColorBurnShader = 0; +static GLhandleARB g_bColorDodgeShader = 0; +static GLhandleARB g_bVividLightShader = 0; +static GLhandleARB g_hHardMixShader = 0; +static GLhandleARB g_hOverlayShader = 0; +static GLhandleARB g_hScreenShader = 0; +static GLhandleARB g_hYUYV422Shader = 0; +static GLhandleARB g_gShellShader = 0; +static GLhandleARB g_gCelShader = 0; + +void InitShaders() +{ + // xxx: replace this with a ShaderManager or something that reads in + // the shaders and determines shader type by file extension. -aj + // argh shaders in stepmania are painful -colby + vector asDefines; + + // used for scrolling textures (I think) + g_bTextureMatrixShader = LoadShader( GL_VERTEX_SHADER_ARB, "Data/Shaders/GLSL/Texture matrix scaling.vert", asDefines ); + + // these two are for dancing characters and are both actually shader pairs + g_gShellShader = LoadShader( GL_VERTEX_SHADER_ARB, "Data/Shaders/GLSL/Shell.vert", asDefines ); + g_gCelShader = LoadShader( GL_VERTEX_SHADER_ARB, "Data/Shaders/GLSL/Cel.vert", asDefines ); + + // effects + g_bUnpremultiplyShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Unpremultiply.frag", asDefines ); + g_bColorBurnShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Color burn.frag", asDefines ); + g_bColorDodgeShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Color dodge.frag", asDefines ); + g_bVividLightShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Vivid light.frag", asDefines ); + g_hHardMixShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Hard mix.frag", asDefines ); + g_hOverlayShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Overlay.frag", asDefines ); + g_hScreenShader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/Screen.frag", asDefines ); + g_hYUYV422Shader = LoadShader( GL_FRAGMENT_SHADER_ARB, "Data/Shaders/GLSL/YUYV422.frag", asDefines ); + + // Bind attributes. + if (g_bTextureMatrixShader) + { + FlushGLErrors(); + g_iAttribTextureMatrixScale = glGetAttribLocationARB( g_bTextureMatrixShader, "TextureMatrixScale" ); + if (g_iAttribTextureMatrixScale == -1) + { + LOG->Trace( "Scaling shader link failed: couldn't bind attribute \"TextureMatrixScale\"" ); + glDeleteObjectARB( g_bTextureMatrixShader ); + g_bTextureMatrixShader = 0; + } + else + { + AssertNoGLError(); + + /* Older Catalyst drivers seem to throw GL_INVALID_OPERATION here. */ + glVertexAttrib2fARB( g_iAttribTextureMatrixScale, 1, 1 ); + GLenum iError = glGetError(); + if (iError == GL_INVALID_OPERATION) + { + LOG->Trace( "Scaling shader failed: glVertexAttrib2fARB returned GL_INVALID_OPERATION" ); + glDeleteObjectARB( g_bTextureMatrixShader ); + g_bTextureMatrixShader = 0; + } + else + { + ASSERT_M( iError == GL_NO_ERROR, GLToString(iError) ); + } + } + } +} + +static LocalizedString OBTAIN_AN_UPDATED_VIDEO_DRIVER ( "RageDisplay_Legacy", "Obtain an updated driver from your video card manufacturer." ); +static LocalizedString GLDIRECT_IS_NOT_COMPATIBLE ( "RageDisplay_Legacy", "GLDirect was detected. GLDirect is not compatible with this game and should be disabled." ); +RString RageDisplay_Legacy::Init( const VideoModeParams &p, bool bAllowUnacceleratedRenderer ) +{ + g_pWind = LowLevelWindow::Create(); + + bool bIgnore = false; + RString sError = SetVideoMode( p, bIgnore ); + if (sError != "") + return sError; + + // Log driver details + g_pWind->LogDebugInformation(); + LOG->Info( "OGL Vendor: %s", glGetString(GL_VENDOR) ); + LOG->Info( "OGL Renderer: %s", glGetString(GL_RENDERER) ); + LOG->Info( "OGL Version: %s", glGetString(GL_VERSION) ); + LOG->Info( "OGL Max texture size: %i", GetMaxTextureSize() ); + LOG->Info( "OGL Texture units: %i", g_iMaxTextureUnits ); + LOG->Info( "GLU Version: %s", gluGetString(GLU_VERSION) ); + + /* Pretty-print the extension string: */ + LOG->Info( "OGL Extensions:" ); + { + const char *szExtensionString = (const char *) glGetString(GL_EXTENSIONS); + vector asExtensions; + split( szExtensionString, " ", asExtensions ); + sort( asExtensions.begin(), asExtensions.end() ); + size_t iNextToPrint = 0; + while( iNextToPrint < asExtensions.size() ) + { + size_t iLastToPrint = iNextToPrint; + RString sType; + for( size_t i = iNextToPrint; i asBits; + split( asExtensions[i], "_", asBits ); + RString sThisType; + if (asBits.size() > 2) + sThisType = join( "_", asBits.begin(), asBits.begin()+2 ); + if (i > iNextToPrint && sThisType != sType) + break; + sType = sThisType; + iLastToPrint = i; + } + + if (iNextToPrint == iLastToPrint) + { + LOG->Info( " %s", asExtensions[iNextToPrint].c_str() ); + ++iNextToPrint; + continue; + } + + RString sList = ssprintf( " %s: ", sType.c_str() ); + while( iNextToPrint <= iLastToPrint ) + { + vector asBits; + split( asExtensions[iNextToPrint], "_", asBits ); + RString sShortExt = join( "_", asBits.begin()+2, asBits.end() ); + sList += sShortExt; + if (iNextToPrint < iLastToPrint) + sList += ", "; + if (iNextToPrint == iLastToPrint || sList.size() + asExtensions[iNextToPrint+1].size() > 120) + { + LOG->Info( "%s", sList.c_str() ); + sList = " "; + } + ++iNextToPrint; + } + } + } + + if (g_pWind->IsSoftwareRenderer(sError)) + { + if (!bAllowUnacceleratedRenderer) + return sError + " " + OBTAIN_AN_UPDATED_VIDEO_DRIVER.GetValue() + "\n\n"; + LOG->Warn( "Low-performance OpenGL renderer: %s", sError.c_str() ); + } + +#if defined(_WINDOWS) + /* GLDirect is a Direct3D wrapper for OpenGL. It's rather buggy; and if in + * any case GLDirect can successfully render us, we should be able to do so + * too using Direct3D directly. (If we can't, it's a bug that we can work + * around--if GLDirect can do it, so can we!) */ + if (!strncmp( (const char *) glGetString(GL_RENDERER), "GLDirect", 8 )) + return GLDIRECT_IS_NOT_COMPATIBLE.GetValue() + "\n"; +#endif + + /* Log this, so if people complain that the radar looks bad on their + * system we can compare them: */ + glGetFloatv( GL_LINE_WIDTH_RANGE, g_line_range ); + glGetFloatv( GL_POINT_SIZE_RANGE, g_point_range ); + + return RString(); +} + +RageDisplay_Legacy::~RageDisplay_Legacy() +{ + delete g_pWind; +} + +void RageDisplay_Legacy::GetDisplayResolutions( DisplayResolutions &out ) const +{ + out.clear(); + g_pWind->GetDisplayResolutions( out ); +} + +static void CheckPalettedTextures() +{ + RString sError; + do + { + if (!GLEW_EXT_paletted_texture) + { + sError = "GL_EXT_paletted_texture missing"; + break; + } + + /* Check to see if paletted textures really work. */ + GLenum glTexFormat = g_GLPixFmtInfo[RagePixelFormat_PAL].internalfmt; + GLenum glImageFormat = g_GLPixFmtInfo[RagePixelFormat_PAL].format; + GLenum glImageType = g_GLPixFmtInfo[RagePixelFormat_PAL].type; + + int iBits = 8; + + FlushGLErrors(); +#define GL_CHECK_ERROR(f) \ +{ \ + GLenum glError = glGetError(); \ + if (glError != GL_NO_ERROR) { \ + sError = ssprintf(f " failed (%s)", GLToString(glError).c_str() ); \ + break; \ + } \ +} + + glTexImage2D( GL_PROXY_TEXTURE_2D, + 0, glTexFormat, + 16, 16, 0, + glImageFormat, glImageType, NULL ); + GL_CHECK_ERROR( "glTexImage2D" ); + + GLuint iFormat = 0; + glGetTexLevelParameteriv( GL_PROXY_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INTERNAL_FORMAT), (GLint *) &iFormat ); + GL_CHECK_ERROR( "glGetTexLevelParameteriv(GL_TEXTURE_INTERNAL_FORMAT)" ); + if (iFormat != glTexFormat) + { + sError = ssprintf( "Expected format %s, got %s instead", + GLToString(glTexFormat).c_str(), GLToString(iFormat).c_str() ); + break; + } + + GLubyte palette[256*4]; + memset(palette, 0, sizeof(palette)); + glColorTableEXT(GL_PROXY_TEXTURE_2D, GL_RGBA8, 256, GL_RGBA, GL_UNSIGNED_BYTE, palette); + GL_CHECK_ERROR( "glColorTableEXT" ); + + GLint iSize = 0; + glGetTexLevelParameteriv( GL_PROXY_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INDEX_SIZE_EXT), &iSize ); + GL_CHECK_ERROR( "glGetTexLevelParameteriv(GL_TEXTURE_INDEX_SIZE_EXT)" ); + if (iBits > iSize || iSize > 8) + { + sError = ssprintf( "Expected %i-bit palette, got a %i-bit one instead", iBits, int(iSize) ); + break; + } + + GLint iRealWidth = 0; + glGetColorTableParameterivEXT( GL_PROXY_TEXTURE_2D, GL_COLOR_TABLE_WIDTH, &iRealWidth ); + GL_CHECK_ERROR( "glGetColorTableParameterivEXT(GL_COLOR_TABLE_WIDTH)" ); + if (iRealWidth != 1 << iBits) + { + sError = ssprintf( "GL_COLOR_TABLE_WIDTH returned %i instead of %i", int(iRealWidth), 1 << iBits ); + break; + } + + GLint iRealFormat = 0; + glGetColorTableParameterivEXT( GL_PROXY_TEXTURE_2D, GL_COLOR_TABLE_FORMAT, &iRealFormat ); + GL_CHECK_ERROR( "glGetColorTableParameterivEXT(GL_COLOR_TABLE_FORMAT)" ); + if (iRealFormat != GL_RGBA8) + { + sError = ssprintf( "GL_COLOR_TABLE_FORMAT returned %s instead of GL_RGBA8", GLToString(iRealFormat).c_str() ); + break; + } + } while(0); +#undef GL_CHECK_ERROR + + if (sError == "") + return; + + /* If 8-bit palettes don't work, disable them entirely--don't trust 4-bit + * palettes if it can't even get 8-bit ones right. */ + glColorTableEXT = NULL; + glGetColorTableParameterivEXT = NULL; + LOG->Info( "Paletted textures disabled: %s.", sError.c_str() ); +} + +static void CheckReversePackedPixels() +{ + /* Try to create a texture. */ + FlushGLErrors(); + glTexImage2D( GL_PROXY_TEXTURE_2D, + 0, GL_RGBA, + 16, 16, 0, + GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, NULL ); + + const GLenum glError = glGetError(); + if (glError == GL_NO_ERROR) + { + g_bReversePackedPixelsWorks = true; + } + else + { + g_bReversePackedPixelsWorks = false; + LOG->Info( "GL_UNSIGNED_SHORT_1_5_5_5_REV failed (%s), disabled", + GLToString(glError).c_str() ); + } +} + +void SetupExtensions() +{ + const float fGLVersion = StringToFloat( (const char *) glGetString(GL_VERSION) ); + g_glVersion = lrintf( fGLVersion * 10 ); + + const float fGLUVersion = StringToFloat( (const char *) gluGetString(GLU_VERSION) ); + g_gluVersion = lrintf( fGLUVersion * 10 ); + + glewInit(); + + g_iMaxTextureUnits = 1; + if (GLEW_ARB_multitexture) + glGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, (GLint *) &g_iMaxTextureUnits ); + + CheckPalettedTextures(); + CheckReversePackedPixels(); + + { + GLint iMaxTableSize = 0; + glGetIntegerv( GL_MAX_PIXEL_MAP_TABLE, &iMaxTableSize ); + if (iMaxTableSize < 256) + { + /* The minimum GL_MAX_PIXEL_MAP_TABLE is 32; if it's not at least 256, + * we can't fit a palette in it, so we can't send paletted data as input + * for a non-paletted texture. */ + LOG->Info( "GL_MAX_PIXEL_MAP_TABLE is only %d", int(iMaxTableSize) ); + g_bColorIndexTableWorks = false; + } + else + { + g_bColorIndexTableWorks = true; + } + } +} + +void RageDisplay_Legacy::ResolutionChanged() +{ + //LOG->Warn( "RageDisplay_Legacy::ResolutionChanged" ); + + /* Clear any junk that's in the framebuffer. */ + if (BeginFrame()) + EndFrame(); + + RageDisplay::ResolutionChanged(); +} + +// Return true if mode change was successful. +// bNewDeviceOut is set true if a new device was created and textures +// need to be reloaded. +RString RageDisplay_Legacy::TryVideoMode( const VideoModeParams &p, bool &bNewDeviceOut ) +{ + //LOG->Warn( "RageDisplay_Legacy::TryVideoMode( %d, %d, %d, %d, %d, %d )", p.windowed, p.width, p.height, p.bpp, p.rate, p.vsync ); + + RString err; + err = g_pWind->TryVideoMode( p, bNewDeviceOut ); + if (err != "") + return err; // failed to set video mode + + /* Now that we've initialized, we can search for extensions. Do this before InvalidateObjects, + * since AllocateBuffers needs it. */ + SetupExtensions(); + + if (bNewDeviceOut) + { + /* We have a new OpenGL context, so we have to tell our textures that + * their OpenGL texture number is invalid. */ + if (TEXTUREMAN) + TEXTUREMAN->InvalidateTextures(); + + /* Delete all render targets. They may have associated resources other than + * the texture itself. */ + FOREACHM( unsigned, RenderTarget *, g_mapRenderTargets, rt ) + delete rt->second; + g_mapRenderTargets.clear(); + + /* Recreate all vertex buffers. */ + InvalidateObjects(); + + InitShaders(); + } + +// I'm not sure this is correct -Colby +#if defined(WINDOWS) + /* Set vsync the Windows way, if we can. (What other extensions are there + * to do this, for other archs?) */ + if( wglewIsSupported("WGL_EXT_swap_control") ) + wglSwapIntervalEXT(p.vsync); + else + return RString("The WGL_EXT_swap_control extension is not supported on your computer."); +#endif + + ResolutionChanged(); + + return RString(); // successfully set mode +} + +int RageDisplay_Legacy::GetMaxTextureSize() const +{ + GLint size; + glGetIntegerv( GL_MAX_TEXTURE_SIZE, &size ); + return size; +} + +bool RageDisplay_Legacy::BeginFrame() +{ + /* We do this in here, rather than ResolutionChanged, or we won't update the + * viewport for the concurrent rendering context. */ + int fWidth = g_pWind->GetActualVideoModeParams().width; + int fHeight = g_pWind->GetActualVideoModeParams().height; + + glViewport( 0, 0, fWidth, fHeight ); + + glClearColor( 0,0,0,0 ); + SetZWrite( true ); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + + return RageDisplay::BeginFrame(); +} + +void RageDisplay_Legacy::EndFrame() +{ + glFlush(); + + FrameLimitBeforeVsync( g_pWind->GetActualVideoModeParams().rate ); + g_pWind->SwapBuffers(); + FrameLimitAfterVsync(); + + g_pWind->Update(); + + RageDisplay::EndFrame(); +} + +RageSurface* RageDisplay_Legacy::CreateScreenshot() +{ + int width = g_pWind->GetActualVideoModeParams().width; + int height = g_pWind->GetActualVideoModeParams().height; + + const RagePixelFormatDesc &desc = PIXEL_FORMAT_DESC[RagePixelFormat_RGBA8]; + RageSurface *image = CreateSurface( width, height, desc.bpp, + desc.masks[0], desc.masks[1], desc.masks[2], 0 ); + + DebugFlushGLErrors(); + + glReadBuffer( GL_FRONT ); + DebugAssertNoGLError(); + + glReadPixels( 0, 0, g_pWind->GetActualVideoModeParams().width, g_pWind->GetActualVideoModeParams().height, GL_RGBA, + GL_UNSIGNED_BYTE, image->pixels ); + DebugAssertNoGLError(); + + RageSurfaceUtils::FlipVertically( image ); + + return image; +} + +RageSurface *RageDisplay_Legacy::GetTexture( unsigned iTexture ) +{ + if (iTexture == 0) + return NULL; // XXX + + FlushGLErrors(); + + glBindTexture( GL_TEXTURE_2D, iTexture ); + GLint iHeight, iWidth, iAlphaBits; + glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &iHeight ); + glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &iWidth ); + glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_ALPHA_SIZE, &iAlphaBits ); + int iFormat = iAlphaBits? RagePixelFormat_RGBA8:RagePixelFormat_RGB8; + + const RagePixelFormatDesc &desc = PIXEL_FORMAT_DESC[iFormat]; + RageSurface *pImage = CreateSurface( iWidth, iHeight, desc.bpp, + desc.masks[0], desc.masks[1], desc.masks[2], desc.masks[3] ); + + glGetTexImage( GL_TEXTURE_2D, 0, g_GLPixFmtInfo[iFormat].format, GL_UNSIGNED_BYTE, pImage->pixels ); + AssertNoGLError(); + + return pImage; +} + +VideoModeParams RageDisplay_Legacy::GetActualVideoModeParams() const +{ + return g_pWind->GetActualVideoModeParams(); +} + +static void SetupVertices( const RageSpriteVertex v[], int iNumVerts ) +{ + static float *Vertex, *Texture, *Normal; + static GLubyte *Color; + static int Size = 0; + if (iNumVerts > Size) + { + Size = iNumVerts; + delete [] Vertex; + delete [] Color; + delete [] Texture; + delete [] Normal; + Vertex = new float[Size*3]; + Color = new GLubyte[Size*4]; + Texture = new float[Size*2]; + Normal = new float[Size*3]; + } + + for( unsigned i = 0; i < unsigned(iNumVerts); ++i ) + { + Vertex[i*3+0] = v[i].p[0]; + Vertex[i*3+1] = v[i].p[1]; + Vertex[i*3+2] = v[i].p[2]; + Color[i*4+0] = v[i].c.r; + Color[i*4+1] = v[i].c.g; + Color[i*4+2] = v[i].c.b; + Color[i*4+3] = v[i].c.a; + Texture[i*2+0] = v[i].t[0]; + Texture[i*2+1] = v[i].t[1]; + Normal[i*3+0] = v[i].n[0]; + Normal[i*3+1] = v[i].n[1]; + Normal[i*3+2] = v[i].n[2]; + } + glEnableClientState( GL_VERTEX_ARRAY ); + glVertexPointer( 3, GL_FLOAT, 0, Vertex ); + + glEnableClientState( GL_COLOR_ARRAY ); + glColorPointer( 4, GL_UNSIGNED_BYTE, 0, Color ); + + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer( 2, GL_FLOAT, 0, Texture ); + + if (GLEW_ARB_multitexture) + { + glClientActiveTextureARB( GL_TEXTURE1_ARB ); + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer( 2, GL_FLOAT, 0, Texture ); + glClientActiveTextureARB( GL_TEXTURE0_ARB ); + } + + glEnableClientState( GL_NORMAL_ARRAY ); + glNormalPointer( GL_FLOAT, 0, Normal ); +} + +void RageDisplay_Legacy::SendCurrentMatrices() +{ + RageMatrix projection; + RageMatrixMultiply( &projection, GetCentering(), GetProjectionTop() ); + + if (g_bInvertY) + { + RageMatrix flip; + RageMatrixScale( &flip, +1, -1, +1 ); + RageMatrixMultiply( &projection, &flip, &projection ); + } + glMatrixMode( GL_PROJECTION ); + glLoadMatrixf( (const float*)&projection ); + + // OpenGL has just "modelView", whereas D3D has "world" and "view" + RageMatrix modelView; + RageMatrixMultiply( &modelView, GetViewTop(), GetWorldTop() ); + glMatrixMode( GL_MODELVIEW ); + glLoadMatrixf( (const float*)&modelView ); + + glMatrixMode( GL_TEXTURE ); + glLoadMatrixf( (const float*)GetTextureTop() ); +} + +class RageCompiledGeometrySWOGL : public RageCompiledGeometry +{ +public: + + void Allocate( const vector &vMeshes ) + { + /* Always allocate at least 1 entry, so &x[0] is valid. */ + m_vPosition.resize( max(1u, GetTotalVertices()) ); + m_vTexture.resize( max(1u, GetTotalVertices()) ); + m_vNormal.resize( max(1u, GetTotalVertices()) ); + m_vTexMatrixScale.resize( max(1u, GetTotalVertices()) ); + m_vTriangles.resize( max(1u, GetTotalTriangles()) ); + } + void Change( const vector &vMeshes ) + { + for( unsigned i=0; i &Vertices = mesh.Vertices; + const vector &Triangles = mesh.Triangles; + + for( unsigned j=0; jTrace( s ); + } + */ + + mat.m[3][0] = 0; + mat.m[3][1] = 0; + mat.m[3][2] = 0; + + glMatrixMode( GL_TEXTURE ); + glLoadMatrixf( (const float*)mat ); + } + + glDrawElements( + GL_TRIANGLES, + meshInfo.iTriangleCount*3, + GL_UNSIGNED_SHORT, + &m_vTriangles[0]+meshInfo.iTriangleStart ); + } + +protected: + vector m_vPosition; + vector m_vTexture; + vector m_vNormal; + vector m_vTriangles; + vector m_vTexMatrixScale; +}; + +class InvalidateObject; +static set g_InvalidateList; +class InvalidateObject +{ +public: + InvalidateObject() { g_InvalidateList.insert( this ); } + virtual ~InvalidateObject() { g_InvalidateList.erase( this ); } + virtual void Invalidate() = 0; +}; + +static void InvalidateObjects() +{ + for (InvalidateObject *it : g_InvalidateList) + it->Invalidate(); +} + +class RageCompiledGeometryHWOGL : public RageCompiledGeometrySWOGL, public InvalidateObject +{ +protected: + // vertex buffer object names + GLuint m_nPositions; + GLuint m_nTextureCoords; + GLuint m_nNormals; + GLuint m_nTriangles; + GLuint m_nTextureMatrixScale; + + void AllocateBuffers(); + void UploadData(); + +public: + RageCompiledGeometryHWOGL(); + ~RageCompiledGeometryHWOGL(); + + /* This is called when our OpenGL context is invalidated. */ + void Invalidate(); + + void Allocate( const vector &vMeshes ); + void Change( const vector &vMeshes ); + void Draw( int iMeshIndex ) const; +}; + +RageCompiledGeometryHWOGL::RageCompiledGeometryHWOGL() +{ + m_nPositions = 0; + m_nTextureCoords = 0; + m_nNormals = 0; + m_nTriangles = 0; + m_nTextureMatrixScale = 0; + + AllocateBuffers(); +} + +RageCompiledGeometryHWOGL::~RageCompiledGeometryHWOGL() +{ + DebugFlushGLErrors(); + + glDeleteBuffersARB( 1, &m_nPositions ); + DebugAssertNoGLError(); + glDeleteBuffersARB( 1, &m_nTextureCoords ); + DebugAssertNoGLError(); + glDeleteBuffersARB( 1, &m_nNormals ); + DebugAssertNoGLError(); + glDeleteBuffersARB( 1, &m_nTriangles ); + DebugAssertNoGLError(); + glDeleteBuffersARB( 1, &m_nTextureMatrixScale ); + DebugAssertNoGLError(); +} + +void RageCompiledGeometryHWOGL::AllocateBuffers() +{ + DebugFlushGLErrors(); + + if (!m_nPositions) + { + glGenBuffersARB( 1, &m_nPositions ); + DebugAssertNoGLError(); + } + + if (!m_nTextureCoords) + { + glGenBuffersARB( 1, &m_nTextureCoords ); + DebugAssertNoGLError(); + } + + if (!m_nNormals) + { + glGenBuffersARB( 1, &m_nNormals ); + DebugAssertNoGLError(); + } + + if (!m_nTriangles) + { + glGenBuffersARB( 1, &m_nTriangles ); + DebugAssertNoGLError(); + } + + if (!m_nTextureMatrixScale) + { + glGenBuffersARB( 1, &m_nTextureMatrixScale ); + DebugAssertNoGLError(); + } +} + +void RageCompiledGeometryHWOGL::UploadData() +{ + DebugFlushGLErrors(); + + glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_nPositions); + DebugAssertNoGLError(); + glBufferDataARB( + GL_ARRAY_BUFFER_ARB, + GetTotalVertices()*sizeof(RageVector3), + &m_vPosition[0], + GL_STATIC_DRAW_ARB); + DebugAssertNoGLError(); + + glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_nTextureCoords); + DebugAssertNoGLError(); + glBufferDataARB( + GL_ARRAY_BUFFER_ARB, + GetTotalVertices()*sizeof(RageVector2), + &m_vTexture[0], + GL_STATIC_DRAW_ARB); + DebugAssertNoGLError(); + + glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_nNormals); + DebugAssertNoGLError(); + glBufferDataARB( + GL_ARRAY_BUFFER_ARB, + GetTotalVertices()*sizeof(RageVector3), + &m_vNormal[0], + GL_STATIC_DRAW_ARB); + DebugAssertNoGLError(); + + glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, m_nTriangles); + DebugAssertNoGLError(); + glBufferDataARB( + GL_ELEMENT_ARRAY_BUFFER_ARB, + GetTotalTriangles()*sizeof(msTriangle), + &m_vTriangles[0], + GL_STATIC_DRAW_ARB); + DebugAssertNoGLError(); + + + if (m_bAnyNeedsTextureMatrixScale) + { + glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_nTextureMatrixScale); + DebugAssertNoGLError(); + glBufferDataARB( + GL_ARRAY_BUFFER_ARB, + GetTotalVertices()*sizeof(RageVector2), + &m_vTexMatrixScale[0], + GL_STATIC_DRAW_ARB); + DebugAssertNoGLError(); + } +} + +void RageCompiledGeometryHWOGL::Invalidate() +{ + /* Our vertex buffers no longer exist. Reallocate and reupload. */ + m_nPositions = 0; + m_nTextureCoords = 0; + m_nNormals = 0; + m_nTriangles = 0; + m_nTextureMatrixScale = 0; + AllocateBuffers(); + UploadData(); +} + +void RageCompiledGeometryHWOGL::Allocate( const vector &vMeshes ) +{ + DebugFlushGLErrors(); + + RageCompiledGeometrySWOGL::Allocate( vMeshes ); + glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nPositions ); + DebugAssertNoGLError(); + glBufferDataARB( + GL_ARRAY_BUFFER_ARB, + GetTotalVertices()*sizeof(RageVector3), + NULL, + GL_STATIC_DRAW_ARB ); + DebugAssertNoGLError(); + + glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nTextureCoords ); + DebugAssertNoGLError(); + glBufferDataARB( + GL_ARRAY_BUFFER_ARB, + GetTotalVertices()*sizeof(RageVector2), + NULL, + GL_STATIC_DRAW_ARB ); + DebugAssertNoGLError(); + + glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nNormals ); + DebugAssertNoGLError(); + glBufferDataARB( + GL_ARRAY_BUFFER_ARB, + GetTotalVertices()*sizeof(RageVector3), + NULL, + GL_STATIC_DRAW_ARB ); + DebugAssertNoGLError(); + + glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, m_nTriangles ); + DebugAssertNoGLError(); + glBufferDataARB( + GL_ELEMENT_ARRAY_BUFFER_ARB, + GetTotalTriangles()*sizeof(msTriangle), + NULL, + GL_STATIC_DRAW_ARB ); + DebugAssertNoGLError(); + + glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nTextureMatrixScale ); + DebugAssertNoGLError(); + glBufferDataARB( + GL_ARRAY_BUFFER_ARB, + GetTotalVertices()*sizeof(RageVector2), + NULL, + GL_STATIC_DRAW_ARB ); +} + +void RageCompiledGeometryHWOGL::Change( const vector &vMeshes ) +{ + RageCompiledGeometrySWOGL::Change( vMeshes ); + + UploadData(); +} + +void RageCompiledGeometryHWOGL::Draw( int iMeshIndex ) const +{ + DebugFlushGLErrors(); + + const MeshInfo& meshInfo = m_vMeshInfo[iMeshIndex]; + if (!meshInfo.iVertexCount || !meshInfo.iTriangleCount) + return; + + glEnableClientState(GL_VERTEX_ARRAY); + DebugAssertNoGLError(); + glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nPositions ); + DebugAssertNoGLError(); + glVertexPointer(3, GL_FLOAT, 0, NULL ); + DebugAssertNoGLError(); + + glDisableClientState(GL_COLOR_ARRAY); + DebugAssertNoGLError(); + + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + DebugAssertNoGLError(); + glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nTextureCoords ); + DebugAssertNoGLError(); + glTexCoordPointer(2, GL_FLOAT, 0, NULL); + DebugAssertNoGLError(); + + // TRICKY: Don't bind and send normals if lighting is disabled. This + // will save some effort transforming these values. + // XXX: We should keep track of these ourself and avoid glGet*() + GLboolean bLighting; + glGetBooleanv( GL_LIGHTING, &bLighting ); + GLboolean bTextureGenS; + glGetBooleanv( GL_TEXTURE_GEN_S, &bTextureGenS ); + GLboolean bTextureGenT; + glGetBooleanv( GL_TEXTURE_GEN_T, &bTextureGenT ); + + if (bLighting || bTextureGenS || bTextureGenT) + { + glEnableClientState(GL_NORMAL_ARRAY); + DebugAssertNoGLError(); + glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nNormals ); + DebugAssertNoGLError(); + glNormalPointer(GL_FLOAT, 0, NULL); + DebugAssertNoGLError(); + } + else + { + glDisableClientState(GL_NORMAL_ARRAY); + DebugAssertNoGLError(); + } + + if (meshInfo.m_bNeedsTextureMatrixScale) + { + if (g_bTextureMatrixShader != 0) + { + /* If we're using texture matrix scales, set up that buffer, too, and enable the + * vertex shader. This shader doesn't support all OpenGL state, so only enable it + * if we're using it. */ + glEnableVertexAttribArrayARB( g_iAttribTextureMatrixScale ); + DebugAssertNoGLError(); + glBindBufferARB( GL_ARRAY_BUFFER_ARB, m_nTextureMatrixScale ); + DebugAssertNoGLError(); + glVertexAttribPointerARB( g_iAttribTextureMatrixScale, 2, GL_FLOAT, false, 0, NULL ); + DebugAssertNoGLError(); + + glUseProgramObjectARB( g_bTextureMatrixShader ); + DebugAssertNoGLError(); + } + else + { + // Kill the texture translation. + // XXX: Change me to scale the translation by the TextureTranslationScale of the first vertex. + RageMatrix mat; + glGetFloatv( GL_TEXTURE_MATRIX , (float*)mat ); + + /* + for( int i=0; i<4; i++ ) + { + RString s; + for( int j=0; j<4; j++ ) + s += ssprintf( "%f ", mat.m[i][j] ); + LOG->Trace( s ); + } + */ + + mat.m[3][0] = 0; + mat.m[3][1] = 0; + mat.m[3][2] = 0; + + glMatrixMode( GL_TEXTURE ); + glLoadMatrixf( (const float*)mat ); + DebugAssertNoGLError(); + } + } + + glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, m_nTriangles ); + DebugAssertNoGLError(); + +#define BUFFER_OFFSET(o) ((char*)(o)) + + ASSERT( glDrawRangeElements != NULL ); + glDrawRangeElements( + GL_TRIANGLES, + meshInfo.iVertexStart, // minimum array index contained in indices + meshInfo.iVertexStart+meshInfo.iVertexCount-1, + // maximum array index contained in indices + meshInfo.iTriangleCount*3, // number of elements to be rendered + GL_UNSIGNED_SHORT, + BUFFER_OFFSET(meshInfo.iTriangleStart*sizeof(msTriangle)) ); + DebugAssertNoGLError(); + + if (meshInfo.m_bNeedsTextureMatrixScale && g_bTextureMatrixShader != 0) + { + glDisableVertexAttribArrayARB( g_iAttribTextureMatrixScale ); + glUseProgramObjectARB( 0 ); + } +} + +RageCompiledGeometry* RageDisplay_Legacy::CreateCompiledGeometry() +{ + if (GLEW_ARB_vertex_buffer_object) + return new RageCompiledGeometryHWOGL; + else + return new RageCompiledGeometrySWOGL; +} + +void RageDisplay_Legacy::DeleteCompiledGeometry( RageCompiledGeometry* p ) +{ + delete p; +} + +void RageDisplay_Legacy::DrawQuadsInternal( const RageSpriteVertex v[], int iNumVerts ) +{ + TurnOffHardwareVBO(); + SendCurrentMatrices(); + + SetupVertices( v, iNumVerts ); + glDrawArrays( GL_QUADS, 0, iNumVerts ); +} + +void RageDisplay_Legacy::DrawQuadStripInternal( const RageSpriteVertex v[], int iNumVerts ) +{ + TurnOffHardwareVBO(); + SendCurrentMatrices(); + + SetupVertices( v, iNumVerts ); + glDrawArrays( GL_QUAD_STRIP, 0, iNumVerts ); +} + +void RageDisplay_Legacy::DrawSymmetricQuadStripInternal( const RageSpriteVertex v[], int iNumVerts ) +{ + int iNumPieces = (iNumVerts-3)/3; + int iNumTriangles = iNumPieces*4; + int iNumIndices = iNumTriangles*3; + + // make a temporary index buffer + static vector vIndices; + unsigned uOldSize = vIndices.size(); + unsigned uNewSize = max(uOldSize,(unsigned)iNumIndices); + vIndices.resize( uNewSize ); + for( uint16_t i=(uint16_t)uOldSize/12; i<(uint16_t)iNumPieces; i++ ) + { + // { 1, 3, 0 } { 1, 4, 3 } { 1, 5, 4 } { 1, 2, 5 } + vIndices[i*12+0] = i*3+1; + vIndices[i*12+1] = i*3+3; + vIndices[i*12+2] = i*3+0; + vIndices[i*12+3] = i*3+1; + vIndices[i*12+4] = i*3+4; + vIndices[i*12+5] = i*3+3; + vIndices[i*12+6] = i*3+1; + vIndices[i*12+7] = i*3+5; + vIndices[i*12+8] = i*3+4; + vIndices[i*12+9] = i*3+1; + vIndices[i*12+10] = i*3+2; + vIndices[i*12+11] = i*3+5; + } + + TurnOffHardwareVBO(); + SendCurrentMatrices(); + + SetupVertices( v, iNumVerts ); + glDrawElements( + GL_TRIANGLES, + iNumIndices, + GL_UNSIGNED_SHORT, + &vIndices[0] ); +} + +void RageDisplay_Legacy::DrawFanInternal( const RageSpriteVertex v[], int iNumVerts ) +{ + TurnOffHardwareVBO(); + SendCurrentMatrices(); + + SetupVertices( v, iNumVerts ); + glDrawArrays( GL_TRIANGLE_FAN, 0, iNumVerts ); +} + +void RageDisplay_Legacy::DrawStripInternal( const RageSpriteVertex v[], int iNumVerts ) +{ + TurnOffHardwareVBO(); + SendCurrentMatrices(); + + SetupVertices( v, iNumVerts ); + glDrawArrays( GL_TRIANGLE_STRIP, 0, iNumVerts ); +} + +void RageDisplay_Legacy::DrawTrianglesInternal( const RageSpriteVertex v[], int iNumVerts ) +{ + TurnOffHardwareVBO(); + SendCurrentMatrices(); + + SetupVertices( v, iNumVerts ); + glDrawArrays( GL_TRIANGLES, 0, iNumVerts ); +} + +void RageDisplay_Legacy::DrawCompiledGeometryInternal( const RageCompiledGeometry *p, int iMeshIndex ) +{ + TurnOffHardwareVBO(); + SendCurrentMatrices(); + + p->Draw( iMeshIndex ); +} + +void RageDisplay_Legacy::DrawLineStripInternal( const RageSpriteVertex v[], int iNumVerts, float fLineWidth ) +{ + TurnOffHardwareVBO(); + + if (!GetActualVideoModeParams().bSmoothLines) + { + /* Fall back on the generic polygon-based line strip. */ + RageDisplay::DrawLineStripInternal(v, iNumVerts, fLineWidth ); + return; + } + + SendCurrentMatrices(); + + /* Draw a nice AA'd line loop. One problem with this is that point and line + * sizes don't always precisely match, which doesn't look quite right. + * It's worth it for the AA, though. */ + glEnable( GL_LINE_SMOOTH ); + + /* fLineWidth is in units relative to object space, but OpenGL line and point sizes + * are in raster units (actual pixels). Scale the line width by the average ratio; + * if object space is 640x480, and we have a 1280x960 window, we'll double the + * width. */ + { + const RageMatrix* pMat = GetProjectionTop(); + float fW = 2 / pMat->m[0][0]; + float fH = -2 / pMat->m[1][1]; + float fWidthVal = float(g_pWind->GetActualVideoModeParams().width) / fW; + float fHeightVal = float(g_pWind->GetActualVideoModeParams().height) / fH; + fLineWidth *= (fWidthVal + fHeightVal) / 2; + } + + /* Clamp the width to the hardware max for both lines and points (whichever + * is more restrictive). */ + fLineWidth = clamp( fLineWidth, g_line_range[0], g_line_range[1] ); + fLineWidth = clamp( fLineWidth, g_point_range[0], g_point_range[1] ); + + /* Hmm. The granularity of lines and points might be different; for example, + * if lines are .5 and points are .25, we might want to snap the width to the + * nearest .5, so the hardware doesn't snap them to different sizes. Does it + * matter? */ + glLineWidth( fLineWidth ); + + /* Draw the line loop: */ + SetupVertices( v, iNumVerts ); + glDrawArrays( GL_LINE_STRIP, 0, iNumVerts ); + + glDisable( GL_LINE_SMOOTH ); + + /* Round off the corners. This isn't perfect; the point is sometimes a little + * larger than the line, causing a small bump on the edge. Not sure how to fix + * that. */ + glPointSize( fLineWidth ); + + /* Hack: if the points will all be the same, we don't want to draw + * any points at all, since there's nothing to connect. That'll happen + * if both scale factors in the matrix are ~0. (Actually, I think + * it's true if two of the three scale factors are ~0, but we don't + * use this for anything 3d at the moment anyway ...) This is needed + * because points aren't scaled like regular polys--a zero-size point + * will still be drawn. */ + RageMatrix mat; + glGetFloatv( GL_MODELVIEW_MATRIX, (float*)mat ); + + if (mat.m[0][0] < 1e-5 && mat.m[1][1] < 1e-5) + return; + + glEnable( GL_POINT_SMOOTH ); + + SetupVertices( v, iNumVerts ); + glDrawArrays( GL_POINTS, 0, iNumVerts ); + + glDisable( GL_POINT_SMOOTH ); +} + +static bool SetTextureUnit( TextureUnit tu ) +{ + // If multitexture isn't supported, ignore all textures except for 0. + if (!GLEW_ARB_multitexture && tu != TextureUnit_1) + return false; + + if ((int) tu > g_iMaxTextureUnits) + return false; + glActiveTextureARB( enum_add2(GL_TEXTURE0_ARB, tu) ); + return true; +} + +void RageDisplay_Legacy::ClearAllTextures() +{ + FOREACH_ENUM( TextureUnit, i ) + SetTexture( i, 0 ); + + // HACK: Reset the active texture to 0. + // TODO: Change all texture functions to take a stage number. + if (GLEW_ARB_multitexture) + glActiveTextureARB(GL_TEXTURE0_ARB); +} + +int RageDisplay_Legacy::GetNumTextureUnits() +{ + if (GLEW_ARB_multitexture) + return 1; + else + return g_iMaxTextureUnits; +} + +void RageDisplay_Legacy::SetTexture( TextureUnit tu, unsigned iTexture ) +{ + if (!SetTextureUnit( tu )) + return; + + if (iTexture) + { + glEnable( GL_TEXTURE_2D ); + glBindTexture( GL_TEXTURE_2D, iTexture ); + } + else + { + glDisable( GL_TEXTURE_2D ); + } +} + +void RageDisplay_Legacy::SetTextureMode( TextureUnit tu, TextureMode tm ) +{ + if (!SetTextureUnit( tu )) + return; + + switch( tm ) + { + case TextureMode_Modulate: + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + break; + case TextureMode_Add: + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ); + break; + case TextureMode_Glow: + // the below function is glowmode,brighten: + if (!GLEW_ARB_texture_env_combine && !GLEW_EXT_texture_env_combine) + { + /* This is changing blend state, instead of texture state, which + * isn't great, but it's better than doing nothing. */ + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + return; + } + + // and this is glowmode,whiten: + // Source color is the diffuse color only: + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_COMBINE_RGB_EXT), GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_SOURCE0_RGB_EXT), GL_PRIMARY_COLOR_EXT); + + // Source alpha is texture alpha * diffuse alpha: + glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_COMBINE_ALPHA_EXT), GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_OPERAND0_ALPHA_EXT), GL_SRC_ALPHA); + glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_SOURCE0_ALPHA_EXT), GL_PRIMARY_COLOR_EXT); + glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_OPERAND1_ALPHA_EXT), GL_SRC_ALPHA); + glTexEnvi(GL_TEXTURE_ENV, GLenum(GL_SOURCE1_ALPHA_EXT), GL_TEXTURE); + break; + } +} + +void RageDisplay_Legacy::SetTextureFiltering( TextureUnit tu, bool b ) +{ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, b ? GL_LINEAR : GL_NEAREST); + + GLint iMinFilter; + if (b) + { + GLint iWidth1 = -1; + GLint iWidth2 = -1; + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &iWidth1); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 1, GL_TEXTURE_WIDTH, &iWidth2); + if (iWidth1 > 1 && iWidth2 != 0) + { + /* Mipmaps are enabled. */ + if (g_pWind->GetActualVideoModeParams().bTrilinearFiltering) + iMinFilter = GL_LINEAR_MIPMAP_LINEAR; + else + iMinFilter = GL_LINEAR_MIPMAP_NEAREST; + } + else + { + iMinFilter = GL_LINEAR; + } + } + else + { + iMinFilter = GL_NEAREST; + } + + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, iMinFilter ); +} + +void RageDisplay_Legacy::SetEffectMode( EffectMode effect ) +{ + if (!GLEW_ARB_fragment_program || !GLEW_ARB_shading_language_100 || !GLEW_ARB_shader_objects) + return; + + GLhandleARB hShader = 0; + switch (effect) + { + case EffectMode_Normal: hShader = 0; break; + case EffectMode_Unpremultiply: hShader = g_bUnpremultiplyShader; break; + case EffectMode_ColorBurn: hShader = g_bColorBurnShader; break; + case EffectMode_ColorDodge: hShader = g_bColorDodgeShader; break; + case EffectMode_VividLight: hShader = g_bVividLightShader; break; + case EffectMode_HardMix: hShader = g_hHardMixShader; break; + case EffectMode_Overlay: hShader = g_hOverlayShader; break; + case EffectMode_Screen: hShader = g_hScreenShader; break; + case EffectMode_YUYV422: hShader = g_hYUYV422Shader; break; + } + + DebugFlushGLErrors(); + glUseProgramObjectARB( hShader ); + if (hShader == 0) + return; + GLint iTexture1 = glGetUniformLocationARB( hShader, "Texture1" ); + GLint iTexture2 = glGetUniformLocationARB( hShader, "Texture2" ); + glUniform1iARB( iTexture1, 0 ); + glUniform1iARB( iTexture2, 1 ); + + if (effect == EffectMode_YUYV422) + { + GLint iTextureWidthUniform = glGetUniformLocationARB( hShader, "TextureWidth" ); + GLint iWidth; + glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &iWidth ); + glUniform1iARB( iTextureWidthUniform, iWidth ); + } + + DebugAssertNoGLError(); +} + +bool RageDisplay_Legacy::IsEffectModeSupported( EffectMode effect ) +{ + switch( effect ) + { + case EffectMode_Normal: return true; + case EffectMode_Unpremultiply: return g_bUnpremultiplyShader != 0; + case EffectMode_ColorBurn: return g_bColorBurnShader != 0; + case EffectMode_ColorDodge: return g_bColorDodgeShader != 0; + case EffectMode_VividLight: return g_bVividLightShader != 0; + case EffectMode_HardMix: return g_hHardMixShader != 0; + case EffectMode_Overlay: return g_hOverlayShader != 0; + case EffectMode_Screen: return g_hScreenShader != 0; + case EffectMode_YUYV422: return g_hYUYV422Shader != 0; + } + + return false; +} + +void RageDisplay_Legacy::SetBlendMode( BlendMode mode ) +{ + glEnable(GL_BLEND); + + if (glBlendEquation != NULL) + { + if (mode == BLEND_INVERT_DEST) + glBlendEquation( GL_FUNC_SUBTRACT ); + else if (mode == BLEND_SUBTRACT) + glBlendEquation( GL_FUNC_REVERSE_SUBTRACT ); + else + glBlendEquation( GL_FUNC_ADD ); + } + + int iSourceRGB, iDestRGB; + int iSourceAlpha = GL_ONE, iDestAlpha = GL_ONE_MINUS_SRC_ALPHA; + switch( mode ) + { + case BLEND_NORMAL: + iSourceRGB = GL_SRC_ALPHA; iDestRGB = GL_ONE_MINUS_SRC_ALPHA; + break; + case BLEND_ADD: + iSourceRGB = GL_SRC_ALPHA; iDestRGB = GL_ONE; + break; + case BLEND_SUBTRACT: + iSourceRGB = GL_SRC_ALPHA; iDestRGB = GL_ONE_MINUS_SRC_ALPHA; + break; + case BLEND_MODULATE: + iSourceRGB = GL_ZERO; iDestRGB = GL_SRC_COLOR; + break; + case BLEND_COPY_SRC: + iSourceRGB = GL_ONE; iDestRGB = GL_ZERO; + iSourceAlpha = GL_ONE; iDestAlpha = GL_ZERO; + break; + case BLEND_ALPHA_MASK: + iSourceRGB = GL_ZERO; iDestRGB = GL_ONE; + iSourceAlpha = GL_ZERO; iDestAlpha = GL_SRC_ALPHA; + break; + case BLEND_ALPHA_KNOCK_OUT: + iSourceRGB = GL_ZERO; iDestRGB = GL_ONE; + iSourceAlpha = GL_ZERO; iDestAlpha = GL_ONE_MINUS_SRC_ALPHA; + break; + case BLEND_ALPHA_MULTIPLY: + iSourceRGB = GL_SRC_ALPHA; iDestRGB = GL_ZERO; + break; + case BLEND_WEIGHTED_MULTIPLY: + /* output = 2*(dst*src). 0.5,0.5,0.5 is identity; darker colors darken the image, + * and brighter colors lighten the image. */ + iSourceRGB = GL_DST_COLOR; iDestRGB = GL_SRC_COLOR; + break; + case BLEND_INVERT_DEST: + /* out = src - dst. The source color should almost always be #FFFFFF, to make it "1 - dst". */ + iSourceRGB = GL_ONE; iDestRGB = GL_ONE; + break; + case BLEND_NO_EFFECT: + iSourceRGB = GL_ZERO; iDestRGB = GL_ONE; + iSourceAlpha = GL_ZERO; iDestAlpha = GL_ONE; + break; + DEFAULT_FAIL( mode ); + } + + if (GLEW_EXT_blend_equation_separate) + glBlendFuncSeparateEXT( iSourceRGB, iDestRGB, iSourceAlpha, iDestAlpha ); + else + glBlendFunc( iSourceRGB, iDestRGB ); +} + +bool RageDisplay_Legacy::IsZWriteEnabled() const +{ + bool a; + glGetBooleanv( GL_DEPTH_WRITEMASK, (unsigned char*)&a ); + return a; +} + +bool RageDisplay_Legacy::IsZTestEnabled() const +{ + GLenum a; + glGetIntegerv( GL_DEPTH_FUNC, (GLint*)&a ); + return a != GL_ALWAYS; +} + +void RageDisplay_Legacy::ClearZBuffer() +{ + bool write = IsZWriteEnabled(); + SetZWrite( true ); + glClear( GL_DEPTH_BUFFER_BIT ); + SetZWrite( write ); +} + +void RageDisplay_Legacy::SetZWrite( bool b ) +{ + glDepthMask( b ); +} + +void RageDisplay_Legacy::SetZBias( float f ) +{ + float fNear = SCALE( f, 0.0f, 1.0f, 0.05f, 0.0f ); + float fFar = SCALE( f, 0.0f, 1.0f, 1.0f, 0.95f ); + + glDepthRange( fNear, fFar ); +} + +void RageDisplay_Legacy::SetZTestMode( ZTestMode mode ) +{ + glEnable( GL_DEPTH_TEST ); + switch( mode ) + { + case ZTEST_OFF: glDepthFunc( GL_ALWAYS ); break; + case ZTEST_WRITE_ON_PASS: glDepthFunc( GL_LEQUAL ); break; + case ZTEST_WRITE_ON_FAIL: glDepthFunc( GL_GREATER ); break; + default: + FAIL_M(ssprintf("Invalid ZTestMode: %i", mode)); + } +} + +void RageDisplay_Legacy::SetTextureWrapping( TextureUnit tu, bool b ) +{ + /* This should be per-texture-unit state, but it's per-texture state in OpenGl, + * so we'll behave incorrectly if the same texture is used in more than one texture + * unit simultaneously with different wrapping. */ + SetTextureUnit( tu ); + + GLenum mode = b ? GL_REPEAT : GL_CLAMP_TO_EDGE; + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mode ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mode ); +} + +void RageDisplay_Legacy::SetMaterial( + const RageColor &emissive, + const RageColor &ambient, + const RageColor &diffuse, + const RageColor &specular, + float shininess + ) +{ + // TRICKY: If lighting is off, then setting the material + // will have no effect. Even if lighting is off, we still + // want Models to have basic color and transparency. + // We can do this fake lighting by setting the vertex color. + // XXX: unintended: SetLighting must be called before SetMaterial + GLboolean bLighting; + glGetBooleanv( GL_LIGHTING, &bLighting ); + + if (bLighting) + { + glMaterialfv( GL_FRONT, GL_EMISSION, emissive ); + glMaterialfv( GL_FRONT, GL_AMBIENT, ambient ); + glMaterialfv( GL_FRONT, GL_DIFFUSE, diffuse ); + glMaterialfv( GL_FRONT, GL_SPECULAR, specular ); + glMaterialf( GL_FRONT, GL_SHININESS, shininess ); + } + else + { + RageColor c = diffuse; + c.r += emissive.r + ambient.r; + c.g += emissive.g + ambient.g; + c.b += emissive.b + ambient.b; + glColor4fv( c ); + } +} + +void RageDisplay_Legacy::SetLighting( bool b ) +{ + if (b) + glEnable(GL_LIGHTING); + else + glDisable(GL_LIGHTING); +} + +void RageDisplay_Legacy::SetLightOff( int index ) +{ + glDisable( GL_LIGHT0+index ); +} + +void RageDisplay_Legacy::SetLightDirectional( + int index, + const RageColor &ambient, + const RageColor &diffuse, + const RageColor &specular, + const RageVector3 &dir ) +{ + // Light coordinates are transformed by the modelview matrix, but + // we are being passed in world-space coords. + glPushMatrix(); + glLoadIdentity(); + + glEnable( GL_LIGHT0+index ); + glLightfv( GL_LIGHT0+index, GL_AMBIENT, ambient ); + glLightfv( GL_LIGHT0+index, GL_DIFFUSE, diffuse ); + glLightfv( GL_LIGHT0+index, GL_SPECULAR, specular ); + float position[4] = {dir.x, dir.y, dir.z, 0}; + glLightfv( GL_LIGHT0+index, GL_POSITION, position ); + + glPopMatrix(); +} + +void RageDisplay_Legacy::SetCullMode( CullMode mode ) +{ + if (mode != CULL_NONE) + glEnable(GL_CULL_FACE); + switch( mode ) + { + case CULL_BACK: + glCullFace( GL_BACK ); + break; + case CULL_FRONT: + glCullFace( GL_FRONT ); + break; + case CULL_NONE: + glDisable( GL_CULL_FACE ); + break; + default: + FAIL_M(ssprintf("Invalid CullMode: %i", mode)); + } +} + +const RageDisplay::RagePixelFormatDesc *RageDisplay_Legacy::GetPixelFormatDesc(RagePixelFormat pf) const +{ + ASSERT( pf < NUM_RagePixelFormat ); + return &PIXEL_FORMAT_DESC[pf]; +} + +bool RageDisplay_Legacy::SupportsThreadedRendering() +{ + return g_pWind->SupportsThreadedRendering(); +} + +void RageDisplay_Legacy::BeginConcurrentRenderingMainThread() +{ + g_pWind->BeginConcurrentRenderingMainThread(); +} + +void RageDisplay_Legacy::EndConcurrentRenderingMainThread() +{ + g_pWind->EndConcurrentRenderingMainThread(); +} + +void RageDisplay_Legacy::BeginConcurrentRendering() +{ + g_pWind->BeginConcurrentRendering(); + RageDisplay::BeginConcurrentRendering(); +} + +void RageDisplay_Legacy::EndConcurrentRendering() +{ + g_pWind->EndConcurrentRendering(); +} + +void RageDisplay_Legacy::DeleteTexture( unsigned iTexture ) +{ + if (iTexture == 0) + return; + + if (g_mapRenderTargets.find(iTexture) != g_mapRenderTargets.end()) + { + delete g_mapRenderTargets[iTexture]; + g_mapRenderTargets.erase( iTexture ); + return; + } + + DebugFlushGLErrors(); + glDeleteTextures( 1, reinterpret_cast(&iTexture) ); + DebugAssertNoGLError(); +} + + +RagePixelFormat RageDisplay_Legacy::GetImgPixelFormat( RageSurface* &img, bool &bFreeImg, int width, int height, bool bPalettedTexture ) +{ + RagePixelFormat pixfmt = FindPixelFormat( img->format->BitsPerPixel, img->format->Rmask, img->format->Gmask, img->format->Bmask, img->format->Amask ); + + /* If img is paletted, we're setting up a non-paletted texture, and color indexes + * are too small, depalettize. */ + bool bSupported = true; + if (!bPalettedTexture && img->fmt.BytesPerPixel == 1 && !g_bColorIndexTableWorks) + bSupported = false; + + if (pixfmt == RagePixelFormat_Invalid || !SupportsSurfaceFormat(pixfmt)) + bSupported = false; + + if (!bSupported) + { + /* The source isn't in a supported, known pixel format. We need to convert + * it ourself. Just convert it to RGBA8, and let OpenGL convert it back + * down to whatever the actual pixel format is. This is a very slow code + * path, which should almost never be used. */ + pixfmt = RagePixelFormat_RGBA8; + ASSERT( SupportsSurfaceFormat(pixfmt) ); + + const RagePixelFormatDesc *pfd = DISPLAY->GetPixelFormatDesc(pixfmt); + + RageSurface *imgconv = CreateSurface( img->w, img->h, + pfd->bpp, pfd->masks[0], pfd->masks[1], pfd->masks[2], pfd->masks[3] ); + RageSurfaceUtils::Blit( img, imgconv, width, height ); + img = imgconv; + bFreeImg = true; + } + else + { + bFreeImg = false; + } + + return pixfmt; +} + +/* If we're sending a paletted surface to a non-paletted texture, set the palette. */ +void SetPixelMapForSurface( int glImageFormat, int glTexFormat, const RageSurfacePalette *palette ) +{ + if (glImageFormat != GL_COLOR_INDEX || glTexFormat == GL_COLOR_INDEX8_EXT) + { + glPixelTransferi( GL_MAP_COLOR, false ); + return; + } + + GLushort buf[4][256]; + memset( buf, 0, sizeof(buf) ); + + for( int i = 0; i < palette->ncolors; ++i ) + { + buf[0][i] = SCALE( palette->colors[i].r, 0, 255, 0, 65535 ); + buf[1][i] = SCALE( palette->colors[i].g, 0, 255, 0, 65535 ); + buf[2][i] = SCALE( palette->colors[i].b, 0, 255, 0, 65535 ); + buf[3][i] = SCALE( palette->colors[i].a, 0, 255, 0, 65535 ); + } + + DebugFlushGLErrors(); + glPixelMapusv( GL_PIXEL_MAP_I_TO_R, 256, buf[0] ); + glPixelMapusv( GL_PIXEL_MAP_I_TO_G, 256, buf[1] ); + glPixelMapusv( GL_PIXEL_MAP_I_TO_B, 256, buf[2] ); + glPixelMapusv( GL_PIXEL_MAP_I_TO_A, 256, buf[3] ); + glPixelTransferi( GL_MAP_COLOR, true ); + DebugAssertNoGLError(); +} + +unsigned RageDisplay_Legacy::CreateTexture( + RagePixelFormat pixfmt, + RageSurface* pImg, + bool bGenerateMipMaps ) +{ + ASSERT( pixfmt < NUM_RagePixelFormat ); + + + /* Find the pixel format of the surface we've been given. */ + bool bFreeImg; + RagePixelFormat SurfacePixFmt = GetImgPixelFormat( pImg, bFreeImg, pImg->w, pImg->h, pixfmt == RagePixelFormat_PAL ); + ASSERT( SurfacePixFmt != RagePixelFormat_Invalid ); + + GLenum glTexFormat = g_GLPixFmtInfo[pixfmt].internalfmt; + GLenum glImageFormat = g_GLPixFmtInfo[SurfacePixFmt].format; + GLenum glImageType = g_GLPixFmtInfo[SurfacePixFmt].type; + + /* If the image is paletted, but we're not sending it to a paletted image, + * set up glPixelMap. */ + SetPixelMapForSurface( glImageFormat, glTexFormat, pImg->format->palette ); + + // HACK: OpenGL 1.2 types aren't available in GLU 1.3. Don't call GLU for mip + // mapping if we're using an OGL 1.2 type and don't have >= GLU 1.3. + // http://pyopengl.sourceforge.net/documentation/manual/gluBuild2DMipmaps.3G.html + if (bGenerateMipMaps && g_gluVersion < 13) + { + switch( pixfmt ) + { + // OpenGL 1.1 types + case RagePixelFormat_RGBA8: + case RagePixelFormat_RGB8: + case RagePixelFormat_PAL: + case RagePixelFormat_BGR8: + break; + // OpenGL 1.2 types + default: + LOG->Trace( "Can't generate mipmaps for type %s because GLU version %.1f is too old.", GLToString(glImageType).c_str(), g_gluVersion/10.f ); + bGenerateMipMaps = false; + break; + } + } + + SetTextureUnit( TextureUnit_1 ); + + // allocate OpenGL texture resource + unsigned int iTexHandle; + glGenTextures( 1, reinterpret_cast(&iTexHandle) ); + ASSERT( iTexHandle != 0 ); + + glBindTexture( GL_TEXTURE_2D, iTexHandle ); + + if (g_pWind->GetActualVideoModeParams().bAnisotropicFiltering && + GLEW_EXT_texture_filter_anisotropic ) + { + GLfloat fLargestSupportedAnisotropy; + glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargestSupportedAnisotropy ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargestSupportedAnisotropy ); + } + + SetTextureFiltering( TextureUnit_1, true ); + SetTextureWrapping( TextureUnit_1, false ); + + glPixelStorei( GL_UNPACK_ROW_LENGTH, pImg->pitch / pImg->format->BytesPerPixel ); + + + if (pixfmt == RagePixelFormat_PAL) + { + /* The texture is paletted; set the texture palette. */ + GLubyte palette[256*4]; + memset( palette, 0, sizeof(palette) ); + int p = 0; + /* Copy the palette to the format OpenGL expects. */ + for( int i = 0; i < pImg->format->palette->ncolors; ++i ) + { + palette[p++] = pImg->format->palette->colors[i].r; + palette[p++] = pImg->format->palette->colors[i].g; + palette[p++] = pImg->format->palette->colors[i].b; + palette[p++] = pImg->format->palette->colors[i].a; + } + + /* Set the palette. */ + glColorTableEXT( GL_TEXTURE_2D, GL_RGBA8, 256, GL_RGBA, GL_UNSIGNED_BYTE, palette ); + + GLint iRealFormat = 0; + glGetColorTableParameterivEXT( GL_TEXTURE_2D, GL_COLOR_TABLE_FORMAT, &iRealFormat ); + ASSERT( iRealFormat == GL_RGBA8 ); + } + + LOG->Trace( "%s (format %s, %ix%i, format %s, type %s, pixfmt %i, imgpixfmt %i)", + bGenerateMipMaps? "gluBuild2DMipmaps":"glTexImage2D", + GLToString(glTexFormat).c_str(), + pImg->w, pImg->h, + GLToString(glImageFormat).c_str(), + GLToString(glImageType).c_str(), pixfmt, SurfacePixFmt ); + + DebugFlushGLErrors(); + + if (bGenerateMipMaps) + { + GLenum error = gluBuild2DMipmaps( + GL_TEXTURE_2D, glTexFormat, + pImg->w, pImg->h, + glImageFormat, glImageType, pImg->pixels ); + ASSERT_M( error == 0, (char *) gluErrorString(error) ); + } + else + { + glTexImage2D( + GL_TEXTURE_2D, 0, glTexFormat, + power_of_two(pImg->w), power_of_two(pImg->h), 0, + glImageFormat, glImageType, NULL ); + if (pImg->pixels) + glTexSubImage2D( GL_TEXTURE_2D, 0, + 0, 0, + pImg->w, pImg->h, + glImageFormat, glImageType, pImg->pixels ); + + DebugAssertNoGLError(); + } + + + /* Sanity check: */ + if (pixfmt == RagePixelFormat_PAL) + { + GLint iSize = 0; + glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INDEX_SIZE_EXT), &iSize ); + if (iSize != 8) + RageException::Throw( "Thought paletted textures worked, but they don't." ); + } + + glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 ); + glFlush(); + + if (bFreeImg) + delete pImg; + return iTexHandle; +} + +struct RageTextureLock_OGL: public RageTextureLock, public InvalidateObject +{ +public: + RageTextureLock_OGL() + { + m_iTexHandle = 0; + m_iBuffer = 0; + + CreateObject(); + } + + ~RageTextureLock_OGL() + { + ASSERT( m_iTexHandle == 0 ); // locked! + glDeleteBuffersARB( 1, &m_iBuffer ); + } + + /* This is called when our OpenGL context is invalidated. */ + void Invalidate() + { + m_iTexHandle = 0; + } + + void Lock( unsigned iTexHandle, RageSurface *pSurface ) + { + ASSERT( m_iTexHandle == 0 ); + ASSERT( pSurface->pixels == NULL ); + + CreateObject(); + + m_iTexHandle = iTexHandle; + glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, m_iBuffer ); + + int iSize = pSurface->h * pSurface->pitch; + glBufferDataARB( GL_PIXEL_UNPACK_BUFFER_ARB, iSize, NULL, GL_STREAM_DRAW ); + + void *pSurfaceMemory = glMapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY ); + pSurface->pixels = (uint8_t *) pSurfaceMemory; + pSurface->pixels_owned = false; + } + + void Unlock( RageSurface *pSurface, bool bChanged ) + { + glUnmapBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB ); + + pSurface->pixels = (uint8_t *) BUFFER_OFFSET(0); + + if (bChanged) + DISPLAY->UpdateTexture( m_iTexHandle, pSurface, 0, 0, pSurface->w, pSurface->h ); + + pSurface->pixels = NULL; + + m_iTexHandle = 0; + glBindBufferARB( GL_PIXEL_UNPACK_BUFFER_ARB, 0 ); + } + +private: + void CreateObject() + { + if (m_iBuffer != 0) + return; + + DebugFlushGLErrors(); + glGenBuffersARB( 1, &m_iBuffer ); + DebugAssertNoGLError(); + } + + GLuint m_iBuffer; + + unsigned m_iTexHandle; +}; + +RageTextureLock *RageDisplay_Legacy::CreateTextureLock() +{ + if (!GLEW_ARB_pixel_buffer_object) + return NULL; + + return new RageTextureLock_OGL; +} + +void RageDisplay_Legacy::UpdateTexture( + unsigned iTexHandle, + RageSurface* pImg, + int iXOffset, int iYOffset, int iWidth, int iHeight ) +{ + glBindTexture( GL_TEXTURE_2D, iTexHandle ); + + bool bFreeImg; + RagePixelFormat SurfacePixFmt = GetImgPixelFormat( pImg, bFreeImg, iWidth, iHeight, false ); + + glPixelStorei( GL_UNPACK_ROW_LENGTH, pImg->pitch / pImg->format->BytesPerPixel ); + + GLenum glImageFormat = g_GLPixFmtInfo[SurfacePixFmt].format; + GLenum glImageType = g_GLPixFmtInfo[SurfacePixFmt].type; + + /* If the image is paletted, but we're not sending it to a paletted image, + * set up glPixelMap. */ + if (pImg->format->palette) + { + GLenum glTexFormat = 0; + glGetTexLevelParameteriv( GL_PROXY_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INTERNAL_FORMAT), (GLint *) &glTexFormat ); + SetPixelMapForSurface( glImageFormat, glTexFormat, pImg->format->palette ); + } + + glTexSubImage2D( GL_TEXTURE_2D, 0, + iXOffset, iYOffset, + iWidth, iHeight, + glImageFormat, glImageType, pImg->pixels ); + + /* Must unset PixelStore when we're done! */ + glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 ); + glFlush(); + + if (bFreeImg) + delete pImg; +} + +class RenderTarget_FramebufferObject: public RenderTarget +{ +public: + RenderTarget_FramebufferObject(); + ~RenderTarget_FramebufferObject(); + void Create( const RenderTargetParam ¶m, int &iTextureWidthOut, int &iTextureHeightOut ); + unsigned GetTexture() const { return m_iTexHandle; } + void StartRenderingTo(); + void FinishRenderingTo(); + + virtual bool InvertY() const { return true; } + +private: + unsigned int m_iFrameBufferHandle; + unsigned int m_iTexHandle; + unsigned int m_iDepthBufferHandle; +}; + +RenderTarget_FramebufferObject::RenderTarget_FramebufferObject() +{ + m_iFrameBufferHandle = 0; + m_iTexHandle = 0; + m_iDepthBufferHandle = 0; +} + +RenderTarget_FramebufferObject::~RenderTarget_FramebufferObject() +{ + if (m_iDepthBufferHandle) + glDeleteRenderbuffersEXT( 1, reinterpret_cast(&m_iDepthBufferHandle) ); + if (m_iFrameBufferHandle) + glDeleteFramebuffersEXT( 1, reinterpret_cast(&m_iFrameBufferHandle) ); + if (m_iTexHandle) + glDeleteTextures( 1, reinterpret_cast(&m_iTexHandle) ); +} + +void RenderTarget_FramebufferObject::Create( const RenderTargetParam ¶m, int &iTextureWidthOut, int &iTextureHeightOut ) +{ + m_Param = param; + + DebugFlushGLErrors(); + + // Allocate OpenGL texture resource + glGenTextures( 1, reinterpret_cast(&m_iTexHandle) ); + ASSERT( m_iTexHandle != 0 ); + + int iTextureWidth = power_of_two( param.iWidth ); + int iTextureHeight = power_of_two( param.iHeight ); + + iTextureWidthOut = iTextureWidth; + iTextureHeightOut = iTextureHeight; + + glBindTexture( GL_TEXTURE_2D, m_iTexHandle ); + GLenum internalformat; + GLenum type = param.bWithAlpha? GL_RGBA:GL_RGB; + if (param.bFloat && GLEW_ARB_texture_float) + internalformat = param.bWithAlpha? GL_RGBA16F_ARB:GL_RGB16F_ARB; + else + internalformat = param.bWithAlpha? GL_RGBA8:GL_RGB8; + + glTexImage2D( GL_TEXTURE_2D, 0, internalformat, + iTextureWidth, iTextureHeight, 0, type, GL_UNSIGNED_BYTE, NULL ); + DebugAssertNoGLError(); + + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + + /* Create the framebuffer object. */ + glGenFramebuffersEXT( 1, reinterpret_cast(&m_iFrameBufferHandle) ); + ASSERT( m_iFrameBufferHandle != 0 ); + + /* Attach the texture to it. */ + glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_iFrameBufferHandle ); + glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_iTexHandle, 0 ); + DebugAssertNoGLError(); + + /* Attach a depth buffer, if requested. */ + if (param.bWithDepthBuffer) + { + glGenRenderbuffersEXT( 1, reinterpret_cast(&m_iDepthBufferHandle) ); + ASSERT( m_iDepthBufferHandle != 0 ); + + glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT16, iTextureWidth, iTextureHeight ); + glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, m_iDepthBufferHandle ); + } + + GLenum status = glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT ); + switch(status) + { + case GL_FRAMEBUFFER_COMPLETE_EXT: + break; + case GL_FRAMEBUFFER_UNSUPPORTED_EXT: + FAIL_M( "GL_FRAMEBUFFER_UNSUPPORTED_EXT" ); + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT" ); break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT" ); break; + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT" ); break; + case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT" ); break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT" ); break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: FAIL_M( "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT" ); break; + default: + FAIL_M(ssprintf("Unexpected GL framebuffer status: %i", status)); + } + + glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); +} + +void RenderTarget_FramebufferObject::StartRenderingTo() +{ + glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, m_iFrameBufferHandle ); +} + +void RenderTarget_FramebufferObject::FinishRenderingTo() +{ + glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 ); +} + +bool RageDisplay_Legacy::SupportsRenderToTexture() const +{ + return GLEW_EXT_framebuffer_object || g_pWind->SupportsRenderToTexture(); +} + +/* + * Render-to-texture can be implemented in several ways: the generic GL_ARB_pixel_buffer_object, + * or platform-specifically. PBO is not available on all hardware that supports RTT, + * particularly GeForce 2, but is simpler and faster when available. + */ + +unsigned RageDisplay_Legacy::CreateRenderTarget( const RenderTargetParam ¶m, int &iTextureWidthOut, int &iTextureHeightOut ) +{ + RenderTarget *pTarget; + if (GLEW_EXT_framebuffer_object) + pTarget = new RenderTarget_FramebufferObject; + else + pTarget = g_pWind->CreateRenderTarget(); + + pTarget->Create( param, iTextureWidthOut, iTextureHeightOut ); + + unsigned iTexture = pTarget->GetTexture(); + + ASSERT( g_mapRenderTargets.find(iTexture) == g_mapRenderTargets.end() ); + g_mapRenderTargets[iTexture] = pTarget; + return iTexture; +} + +void RageDisplay_Legacy::SetRenderTarget( unsigned iTexture, bool bPreserveTexture ) +{ + if (iTexture == 0) + { + g_bInvertY = false; + glFrontFace( GL_CCW ); + + /* Pop matrixes affected by SetDefaultRenderStates. */ + DISPLAY->CameraPopMatrix(); + + /* Reset the viewport. */ + int fWidth = g_pWind->GetActualVideoModeParams().width; + int fHeight = g_pWind->GetActualVideoModeParams().height; + glViewport( 0, 0, fWidth, fHeight ); + + if (g_pCurrentRenderTarget) + g_pCurrentRenderTarget->FinishRenderingTo(); + g_pCurrentRenderTarget = NULL; + return; + } + + /* If we already had a render target, disable it. */ + if (g_pCurrentRenderTarget != NULL) + SetRenderTarget(0, true); + + /* Enable the new render target. */ + ASSERT(g_mapRenderTargets.find(iTexture) != g_mapRenderTargets.end()); + RenderTarget *pTarget = g_mapRenderTargets[iTexture]; + pTarget->StartRenderingTo(); + g_pCurrentRenderTarget = pTarget; + + /* Set the viewport to the size of the render target. */ + glViewport(0, 0, pTarget->GetParam().iWidth, pTarget->GetParam().iHeight); + + /* If this render target implementation flips Y, compensate. Inverting will + * switch the winding order. */ + g_bInvertY = pTarget->InvertY(); + if (g_bInvertY) + glFrontFace(GL_CW); + + /* The render target may be in a different OpenGL context, so re-send + * state. Push matrixes affected by SetDefaultRenderStates. */ + DISPLAY->CameraPushMatrix(); + SetDefaultRenderStates(); + + /* Clear the texture, if requested. Always set the associated state, for + * consistency. */ + glClearColor(0,0,0,0); + SetZWrite(true); + + /* If bPreserveTexture is false, clear the render target. Only clear the depth + * buffer if the target has one; otherwise we're clearing the real depth buffer. */ + if (!bPreserveTexture) + { + int iBit = GL_COLOR_BUFFER_BIT; + if (pTarget->GetParam().bWithDepthBuffer) + iBit |= GL_DEPTH_BUFFER_BIT; + glClear(iBit); + } +} + +void RageDisplay_Legacy::SetPolygonMode(PolygonMode pm) +{ + GLenum m; + switch (pm) + { + case POLYGON_FILL: m = GL_FILL; break; + case POLYGON_LINE: m = GL_LINE; break; + default: + FAIL_M(ssprintf("Invalid PolygonMode: %i", pm)); + } + glPolygonMode(GL_FRONT_AND_BACK, m); +} + +void RageDisplay_Legacy::SetLineWidth(float fWidth) +{ + glLineWidth(fWidth); +} + +RString RageDisplay_Legacy::GetTextureDiagnostics(unsigned iTexture) const +{ + /* + s << (bGenerateMipMaps? "gluBuild2DMipmaps":"glTexImage2D"); + s << "(format " << GLToString(glTexFormat) << + ", " << pImg->w << "x" << pImg->h << + ", format " << GLToString(iFormat) << + ", type " << GLToString(glImageType) << + ", pixfmt " << pixfmt << + ", imgpixfmt " << SurfacePixFmt << + ")"; + LOG->Trace( "%s", s.str().c_str() ); + + glBindTexture( GL_TEXTURE_2D, iTexture ); + + GLint iWidth; + glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GLenum(GL_TEXTURE_WIDTH), (GLint *) &iWidth ); + GLint iHeight; + glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GLenum(GL_TEXTURE_HEIGHT), (GLint *) &iHeight ); + GLint iFormat; + glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GLenum(GL_TEXTURE_INTERNAL_FORMAT), (GLint *) &iFormat ); + + GL_CHECK_ERROR( "glGetTexLevelParameteriv(GL_TEXTURE_INTERNAL_FORMAT)" ); + if (iFormat != glTexFormat) + { + sError = ssprintf( "Expected format %s, got %s instead", + GLToString(glTexFormat).c_str(), GLToString(iFormat).c_str() ); + break; + } +*/ + return RString(); +} + +/* + * XXX: Things like this only have to be set once per context - making + * SetDefault call These kinds of functions is wasteful. -Colby + */ +void RageDisplay_Legacy::SetAlphaTest(bool b) +{ + // Previously this was 0.01, rather than 0x01. + glAlphaFunc(GL_GREATER, 0.00390625 /* 1/256 */); + if (b) + glEnable(GL_ALPHA_TEST); + else + glDisable(GL_ALPHA_TEST); +} + + +/* + * Although we pair texture formats (eg. GL_RGB8) and surface formats + * (pairs of eg. GL_RGB8,GL_UNSIGNED_SHORT_5_5_5_1), it's possible for + * a format to be supported for a texture format but not a surface + * format. This is abstracted, so you don't need to know about this + * as a user calling CreateTexture. + * + * One case of this is if packed pixels aren't supported. We can still + * use 16-bit color modes, but we have to send it in 32-bit. Almost + * everything supports packed pixels. + * + * Another case of this is incomplete packed pixels support. Some implementations + * neglect GL_UNSIGNED_SHORT_*_REV. + */ +bool RageDisplay_Legacy::SupportsSurfaceFormat(RagePixelFormat pixfmt) +{ + switch (g_GLPixFmtInfo[pixfmt].type) + { + case GL_UNSIGNED_SHORT_1_5_5_5_REV: + return GLEW_EXT_bgra && g_bReversePackedPixelsWorks; + default: + return true; + } +} + + +bool RageDisplay_Legacy::SupportsTextureFormat(RagePixelFormat pixfmt, bool bRealtime) +{ + /* If we support a pixfmt for texture formats but not for surface formats, then + * we'll have to convert the texture to a supported surface format before uploading. + * This is too slow for dynamic textures. */ + if (bRealtime && !SupportsSurfaceFormat(pixfmt)) + return false; + + switch (g_GLPixFmtInfo[pixfmt].format) + { + case GL_COLOR_INDEX: + return glColorTableEXT && glGetColorTableParameterivEXT; + case GL_BGR: + case GL_BGRA: + return !!GLEW_EXT_bgra; + default: + return true; + } +} + +bool RageDisplay_Legacy::SupportsPerVertexMatrixScale() +{ + // Intel i915 on OSX 10.4.4 supports vertex programs but not hardware vertex buffers. + // Our software vertex rendering doesn't support vertex programs. + return glGenBuffersARB && g_bTextureMatrixShader != 0; +} + +void RageDisplay_Legacy::SetSphereEnvironmentMapping(TextureUnit tu, bool b) +{ + if (!SetTextureUnit(tu)) + return; + + if (b) + { + glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); + glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); + glEnable(GL_TEXTURE_GEN_S); + glEnable(GL_TEXTURE_GEN_T); + } + else + { + glDisable(GL_TEXTURE_GEN_S); + glDisable(GL_TEXTURE_GEN_T); + } +} + +GLint iCelTexture1, iCelTexture2 = 0; + +void RageDisplay_Legacy::SetCelShaded( int stage ) +{ + if (!GLEW_ARB_fragment_program && !GL_ARB_shading_language_100) + return; // not supported + + switch (stage) + { + case 1: + glUseProgramObjectARB(g_gShellShader); + break; + case 2: + glUseProgramObjectARB(g_gCelShader); + break; + default: + glUseProgramObjectARB(0); + break; + } +} + +/* + * Copyright (c) 2001-2011 Chris Danford, Glenn Maynard, Colby Klein + * 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/RageSoundReader_ChannelSplit.cpp b/src/RageSoundReader_ChannelSplit.cpp index 7bc0e821ae..2500d99418 100644 --- a/src/RageSoundReader_ChannelSplit.cpp +++ b/src/RageSoundReader_ChannelSplit.cpp @@ -1,272 +1,272 @@ -/* - * Output a sound from the channels of another sound. - * - * This is intended for splitting a FileReader, and assumes the GetStreamToSourceRatio - * of the source is 1.0. - * - * If multiple readers are created, they should be read in parallel. If their source - * position drifts apart, the data between will be buffered to prevent seeking the source. - * - * The resulting sounds have as many channels as the largest destination channel - * specified; multiple sounds on the same channel are mixed together; empty - * channels are silent. - * - * Create two mono sounds from one stereo sound, one playing the left channel and - * one playing the right: - * RageSoundSplitter split(pSource); - * RageSoundReader_Split *pLeft = split.CreateSound(); - * pLeft->AddSourceChannelToSound( 0, 0 ); - * RageSoundReader_Split *pRight = split.CreateSound(); - * pRight->AddSourceChannelToSound( 1, 0 ); - * - * Convert a stereo sound to mono: - * RageSoundSplitter split(pSource); - * RageSoundReader_Split *pMono = split.CreateSound(); - * pMono->AddSourceChannelToSound( 0, 0 ); - * pMono->AddSourceChannelToSound( 1, 0 ); - * - * Convert a mono sound to stereo (RageSoundReader_Pan will do this faster): - * RageSoundSplitter split(pSource); - * RageSoundReader_Split *pStereo = split.CreateSound(); - * pStereo->AddSourceChannelToSound( 0, 0 ); - * pStereo->AddSourceChannelToSound( 0, 1 ); - */ - -#include "global.h" -#include "RageSoundReader_ChannelSplit.h" -#include "RageLog.h" -#include "RageUtil.h" -#include "RageSoundMixBuffer.h" -#include "RageSoundUtil.h" -#include "Foreach.h" -#include - -class RageSoundReader_Split; - -/* This class is refcounted, freed when all RageSoundReader_Split and RageSoundSplitter - * classes release it. */ -class RageSoundSplitterImpl -{ -public: - RageSoundSplitterImpl( RageSoundReader *pSource ) - { - m_iRefCount = 1; - m_pSource = pSource; - m_iBufferPositionFrames = 0; - } - - ~RageSoundSplitterImpl() - { - delete m_pSource; - } - - static void Release( RageSoundSplitterImpl *pImpl ) - { - --pImpl->m_iRefCount; - if( pImpl->m_iRefCount == 0 ) - delete pImpl; - } - - /* Request that m_sBuffer contain frames [iStartFrame,iStartFrame+iFrames). */ - int ReadBuffer(); - int m_iRefCount; - - RageSoundReader *m_pSource; - - set m_apSounds; - - /* m_sBuffer[0] corresponds to frame number m_iBufferPositionFrames. */ - int m_iBufferPositionFrames; - vector m_sBuffer; -}; - -int RageSoundReader_Split::GetLength() const { return m_pImpl->m_pSource->GetLength(); } -int RageSoundReader_Split::GetLength_Fast() const { return m_pImpl->m_pSource->GetLength_Fast(); } -int RageSoundReader_Split::GetSampleRate() const { return m_pImpl->m_pSource->GetSampleRate(); } -unsigned RageSoundReader_Split::GetNumChannels() const { return m_iNumOutputChannels; } -int RageSoundReader_Split::GetNextSourceFrame() const { return m_iPositionFrame; } -float RageSoundReader_Split::GetStreamToSourceRatio() const { return 1.0f; } -RString RageSoundReader_Split::GetError() const { return m_pImpl->m_pSource->GetError(); } - -RageSoundReader_Split::RageSoundReader_Split( RageSoundSplitterImpl *pImpl ) -{ - m_pImpl = pImpl; - ++m_pImpl->m_iRefCount; - m_pImpl->m_apSounds.insert( this ); - m_iNumOutputChannels = 0; - m_iPositionFrame = 0; - m_iRequestFrames = 0; -} - -RageSoundReader_Split::RageSoundReader_Split( const RageSoundReader_Split &cpy ): - RageSoundReader( cpy ) -{ - m_pImpl = cpy.m_pImpl; - ++m_pImpl->m_iRefCount; - m_pImpl->m_apSounds.insert( this ); - m_aChannels = cpy.m_aChannels; - m_iNumOutputChannels = cpy.m_iNumOutputChannels; - m_iPositionFrame = cpy.m_iPositionFrame; - m_iRequestFrames = cpy.m_iRequestFrames; -} - -RageSoundReader_Split::~RageSoundReader_Split() -{ - m_pImpl->m_apSounds.erase( this ); - RageSoundSplitterImpl::Release( m_pImpl ); -} - -int RageSoundReader_Split::SetPosition( int iFrame ) -{ - m_iPositionFrame = iFrame; - - /* We can't tell whether we're past EOF. We can't ReadBuffer here, because the - * other sounds using this same source probably haven't seeked yet, so seeking - * to the beginning of the file would buffer between there and the position - * of those sounds. Just return success, and we'll return EOF in Read if needed. */ - return 1; -} - -bool RageSoundReader_Split::SetProperty( const RString &sProperty, float fValue ) -{ - return m_pImpl->m_pSource->SetProperty( sProperty, fValue ); -} - -int RageSoundReader_Split::Read( float *pBuf, int iFrames ) -{ - m_iRequestFrames = iFrames; - int iRet = m_pImpl->ReadBuffer(); - - int iSamplesAvailable = m_pImpl->m_sBuffer.size(); - const float *pSrc = &m_pImpl->m_sBuffer[0]; - if( m_pImpl->m_iBufferPositionFrames < m_iPositionFrame ) - { - int iSkipFrames = m_iPositionFrame - m_pImpl->m_iBufferPositionFrames; - int iSkipSamples = iSkipFrames * m_pImpl->m_pSource->GetNumChannels(); - pSrc += iSkipSamples; - iSamplesAvailable -= iSkipSamples; - } - - int iFramesWanted = iFrames; - int iFramesAvailable = iSamplesAvailable / (m_pImpl->m_pSource->GetNumChannels()); - - /* Report any errors from Read() if we don't have any data buffered to - * return. If we do have data, finish returning it first. */ - if( iFramesAvailable == 0 && iRet < 0 ) - return iRet; - - iFramesAvailable = min( iFramesAvailable, iFramesWanted ); - - { - RageSoundMixBuffer mix; - for( int i = 0; i < (int) m_aChannels.size(); ++i ) - { - const ChannelMap &chan = m_aChannels[i]; - mix.SetWriteOffset( chan.m_iToChannel ); - mix.write( pSrc + chan.m_iFromChannel, iFramesAvailable, m_pImpl->m_pSource->GetNumChannels(), m_iNumOutputChannels ); - } - - mix.read( pBuf ); - } - - m_iPositionFrame += iFramesAvailable; - - /* We no longer need the data we requested. Clear our request, so the - * memory can be freed. */ - m_iRequestFrames = 0; - m_pImpl->ReadBuffer(); - return iFramesAvailable; -} - -int RageSoundSplitterImpl::ReadBuffer() -{ - /* Discard any bytes that are no longer requested by any sound. */ - int iMinFrameRequested = INT_MAX; - int iMaxFrameRequested = INT_MIN; - FOREACHS( RageSoundReader_Split *, m_apSounds, snd ) - { - iMinFrameRequested = min( iMinFrameRequested, (*snd)->m_iPositionFrame ); - iMaxFrameRequested = max( iMaxFrameRequested, (*snd)->m_iPositionFrame + (*snd)->m_iRequestFrames ); - } - - if( iMinFrameRequested > m_iBufferPositionFrames ) - { - int iEraseFrames = iMinFrameRequested - m_iBufferPositionFrames; - iEraseFrames = min( iEraseFrames, (int) m_sBuffer.size() ); - m_sBuffer.erase( m_sBuffer.begin(), m_sBuffer.begin() + iEraseFrames * m_pSource->GetNumChannels() ); - m_iBufferPositionFrames += iEraseFrames; - } - - if( iMinFrameRequested != m_iBufferPositionFrames ) - { - m_pSource->SetPosition( iMinFrameRequested ); - m_iBufferPositionFrames = iMinFrameRequested; - m_sBuffer.clear(); - } - - int iFramesBuffered = m_sBuffer.size() / m_pSource->GetNumChannels(); - - int iFramesToRead = iMaxFrameRequested - (m_iBufferPositionFrames + iFramesBuffered); - if( iFramesToRead <= 0 ) - return 1; // requested data already buffered - - int iSamplesToRead = iFramesToRead * m_pSource->GetNumChannels(); - int iOldSizeSamples = m_sBuffer.size(); - m_sBuffer.resize( iOldSizeSamples + iSamplesToRead ); - int iGotFrames = m_pSource->Read( &m_sBuffer[0] + iOldSizeSamples, iFramesToRead ); - if( iGotFrames < 0 ) - { - m_sBuffer.resize( iOldSizeSamples ); - return iGotFrames; - } - - int iGotSamples = iGotFrames * m_pSource->GetNumChannels(); - m_sBuffer.resize( iOldSizeSamples + iGotSamples ); - return 1; -} - -void RageSoundReader_Split::AddSourceChannelToSound( int iFromChannel, int iToChannel ) -{ - m_aChannels.push_back( ChannelMap(iFromChannel, iToChannel) ); - m_iNumOutputChannels = max( m_iNumOutputChannels, iToChannel + 1 ); -} - -RageSoundSplitter::RageSoundSplitter( RageSoundReader *pSource ) -{ - m_pImpl = new RageSoundSplitterImpl( pSource ); -} - -RageSoundSplitter::~RageSoundSplitter() -{ - RageSoundSplitterImpl::Release( m_pImpl ); -} - -RageSoundReader_Split *RageSoundSplitter::CreateSound() -{ - return new RageSoundReader_Split( m_pImpl ); -} - -/* - * Copyright (c) 2006 Glenn Maynard - * 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. - */ +/* + * Output a sound from the channels of another sound. + * + * This is intended for splitting a FileReader, and assumes the GetStreamToSourceRatio + * of the source is 1.0. + * + * If multiple readers are created, they should be read in parallel. If their source + * position drifts apart, the data between will be buffered to prevent seeking the source. + * + * The resulting sounds have as many channels as the largest destination channel + * specified; multiple sounds on the same channel are mixed together; empty + * channels are silent. + * + * Create two mono sounds from one stereo sound, one playing the left channel and + * one playing the right: + * RageSoundSplitter split(pSource); + * RageSoundReader_Split *pLeft = split.CreateSound(); + * pLeft->AddSourceChannelToSound( 0, 0 ); + * RageSoundReader_Split *pRight = split.CreateSound(); + * pRight->AddSourceChannelToSound( 1, 0 ); + * + * Convert a stereo sound to mono: + * RageSoundSplitter split(pSource); + * RageSoundReader_Split *pMono = split.CreateSound(); + * pMono->AddSourceChannelToSound( 0, 0 ); + * pMono->AddSourceChannelToSound( 1, 0 ); + * + * Convert a mono sound to stereo (RageSoundReader_Pan will do this faster): + * RageSoundSplitter split(pSource); + * RageSoundReader_Split *pStereo = split.CreateSound(); + * pStereo->AddSourceChannelToSound( 0, 0 ); + * pStereo->AddSourceChannelToSound( 0, 1 ); + */ + +#include "global.h" +#include "RageSoundReader_ChannelSplit.h" +#include "RageLog.h" +#include "RageUtil.h" +#include "RageSoundMixBuffer.h" +#include "RageSoundUtil.h" +#include "Foreach.h" +#include + +class RageSoundReader_Split; + +/* This class is refcounted, freed when all RageSoundReader_Split and RageSoundSplitter + * classes release it. */ +class RageSoundSplitterImpl +{ +public: + RageSoundSplitterImpl( RageSoundReader *pSource ) + { + m_iRefCount = 1; + m_pSource = pSource; + m_iBufferPositionFrames = 0; + } + + ~RageSoundSplitterImpl() + { + delete m_pSource; + } + + static void Release( RageSoundSplitterImpl *pImpl ) + { + --pImpl->m_iRefCount; + if( pImpl->m_iRefCount == 0 ) + delete pImpl; + } + + /* Request that m_sBuffer contain frames [iStartFrame,iStartFrame+iFrames). */ + int ReadBuffer(); + int m_iRefCount; + + RageSoundReader *m_pSource; + + set m_apSounds; + + /* m_sBuffer[0] corresponds to frame number m_iBufferPositionFrames. */ + int m_iBufferPositionFrames; + vector m_sBuffer; +}; + +int RageSoundReader_Split::GetLength() const { return m_pImpl->m_pSource->GetLength(); } +int RageSoundReader_Split::GetLength_Fast() const { return m_pImpl->m_pSource->GetLength_Fast(); } +int RageSoundReader_Split::GetSampleRate() const { return m_pImpl->m_pSource->GetSampleRate(); } +unsigned RageSoundReader_Split::GetNumChannels() const { return m_iNumOutputChannels; } +int RageSoundReader_Split::GetNextSourceFrame() const { return m_iPositionFrame; } +float RageSoundReader_Split::GetStreamToSourceRatio() const { return 1.0f; } +RString RageSoundReader_Split::GetError() const { return m_pImpl->m_pSource->GetError(); } + +RageSoundReader_Split::RageSoundReader_Split( RageSoundSplitterImpl *pImpl ) +{ + m_pImpl = pImpl; + ++m_pImpl->m_iRefCount; + m_pImpl->m_apSounds.insert( this ); + m_iNumOutputChannels = 0; + m_iPositionFrame = 0; + m_iRequestFrames = 0; +} + +RageSoundReader_Split::RageSoundReader_Split( const RageSoundReader_Split &cpy ): + RageSoundReader( cpy ) +{ + m_pImpl = cpy.m_pImpl; + ++m_pImpl->m_iRefCount; + m_pImpl->m_apSounds.insert( this ); + m_aChannels = cpy.m_aChannels; + m_iNumOutputChannels = cpy.m_iNumOutputChannels; + m_iPositionFrame = cpy.m_iPositionFrame; + m_iRequestFrames = cpy.m_iRequestFrames; +} + +RageSoundReader_Split::~RageSoundReader_Split() +{ + m_pImpl->m_apSounds.erase( this ); + RageSoundSplitterImpl::Release( m_pImpl ); +} + +int RageSoundReader_Split::SetPosition( int iFrame ) +{ + m_iPositionFrame = iFrame; + + /* We can't tell whether we're past EOF. We can't ReadBuffer here, because the + * other sounds using this same source probably haven't seeked yet, so seeking + * to the beginning of the file would buffer between there and the position + * of those sounds. Just return success, and we'll return EOF in Read if needed. */ + return 1; +} + +bool RageSoundReader_Split::SetProperty( const RString &sProperty, float fValue ) +{ + return m_pImpl->m_pSource->SetProperty( sProperty, fValue ); +} + +int RageSoundReader_Split::Read( float *pBuf, int iFrames ) +{ + m_iRequestFrames = iFrames; + int iRet = m_pImpl->ReadBuffer(); + + int iSamplesAvailable = m_pImpl->m_sBuffer.size(); + const float *pSrc = &m_pImpl->m_sBuffer[0]; + if( m_pImpl->m_iBufferPositionFrames < m_iPositionFrame ) + { + int iSkipFrames = m_iPositionFrame - m_pImpl->m_iBufferPositionFrames; + int iSkipSamples = iSkipFrames * m_pImpl->m_pSource->GetNumChannels(); + pSrc += iSkipSamples; + iSamplesAvailable -= iSkipSamples; + } + + int iFramesWanted = iFrames; + int iFramesAvailable = iSamplesAvailable / (m_pImpl->m_pSource->GetNumChannels()); + + /* Report any errors from Read() if we don't have any data buffered to + * return. If we do have data, finish returning it first. */ + if( iFramesAvailable == 0 && iRet < 0 ) + return iRet; + + iFramesAvailable = min( iFramesAvailable, iFramesWanted ); + + { + RageSoundMixBuffer mix; + for( int i = 0; i < (int) m_aChannels.size(); ++i ) + { + const ChannelMap &chan = m_aChannels[i]; + mix.SetWriteOffset( chan.m_iToChannel ); + mix.write( pSrc + chan.m_iFromChannel, iFramesAvailable, m_pImpl->m_pSource->GetNumChannels(), m_iNumOutputChannels ); + } + + mix.read( pBuf ); + } + + m_iPositionFrame += iFramesAvailable; + + /* We no longer need the data we requested. Clear our request, so the + * memory can be freed. */ + m_iRequestFrames = 0; + m_pImpl->ReadBuffer(); + return iFramesAvailable; +} + +int RageSoundSplitterImpl::ReadBuffer() +{ + /* Discard any bytes that are no longer requested by any sound. */ + int iMinFrameRequested = INT_MAX; + int iMaxFrameRequested = INT_MIN; + for (RageSoundReader_Split *snd : m_apSounds) + { + iMinFrameRequested = min( iMinFrameRequested, snd->m_iPositionFrame ); + iMaxFrameRequested = max( iMaxFrameRequested, snd->m_iPositionFrame + snd->m_iRequestFrames ); + } + + if( iMinFrameRequested > m_iBufferPositionFrames ) + { + int iEraseFrames = iMinFrameRequested - m_iBufferPositionFrames; + iEraseFrames = min( iEraseFrames, (int) m_sBuffer.size() ); + m_sBuffer.erase( m_sBuffer.begin(), m_sBuffer.begin() + iEraseFrames * m_pSource->GetNumChannels() ); + m_iBufferPositionFrames += iEraseFrames; + } + + if( iMinFrameRequested != m_iBufferPositionFrames ) + { + m_pSource->SetPosition( iMinFrameRequested ); + m_iBufferPositionFrames = iMinFrameRequested; + m_sBuffer.clear(); + } + + int iFramesBuffered = m_sBuffer.size() / m_pSource->GetNumChannels(); + + int iFramesToRead = iMaxFrameRequested - (m_iBufferPositionFrames + iFramesBuffered); + if( iFramesToRead <= 0 ) + return 1; // requested data already buffered + + int iSamplesToRead = iFramesToRead * m_pSource->GetNumChannels(); + int iOldSizeSamples = m_sBuffer.size(); + m_sBuffer.resize( iOldSizeSamples + iSamplesToRead ); + int iGotFrames = m_pSource->Read( &m_sBuffer[0] + iOldSizeSamples, iFramesToRead ); + if( iGotFrames < 0 ) + { + m_sBuffer.resize( iOldSizeSamples ); + return iGotFrames; + } + + int iGotSamples = iGotFrames * m_pSource->GetNumChannels(); + m_sBuffer.resize( iOldSizeSamples + iGotSamples ); + return 1; +} + +void RageSoundReader_Split::AddSourceChannelToSound( int iFromChannel, int iToChannel ) +{ + m_aChannels.push_back( ChannelMap(iFromChannel, iToChannel) ); + m_iNumOutputChannels = max( m_iNumOutputChannels, iToChannel + 1 ); +} + +RageSoundSplitter::RageSoundSplitter( RageSoundReader *pSource ) +{ + m_pImpl = new RageSoundSplitterImpl( pSource ); +} + +RageSoundSplitter::~RageSoundSplitter() +{ + RageSoundSplitterImpl::Release( m_pImpl ); +} + +RageSoundReader_Split *RageSoundSplitter::CreateSound() +{ + return new RageSoundReader_Split( m_pImpl ); +} + +/* + * Copyright (c) 2006 Glenn Maynard + * 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. + */ From 0182f746727a0ab54e7974eb4e9326319e8f2a13 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 14:48:30 -0400 Subject: [PATCH 027/281] Start working on the map loop--WHITE SPACE! --- src/NoteData.cpp | 2686 +++++++++++++++++++++++----------------------- 1 file changed, 1343 insertions(+), 1343 deletions(-) diff --git a/src/NoteData.cpp b/src/NoteData.cpp index 24bb051fea..fbbd15fb94 100644 --- a/src/NoteData.cpp +++ b/src/NoteData.cpp @@ -1,1343 +1,1343 @@ -/* - * NoteData is organized by: - * track - corresponds to different columns of notes on the screen - * row/index - corresponds to subdivisions of beats - */ - -#include "global.h" -#include "NoteData.h" -#include "RageUtil.h" -#include "RageLog.h" -#include "XmlFile.h" -#include "GameState.h" // blame radar calculations. -#include "Foreach.h" -#include "RageUtil_AutoPtr.h" - -REGISTER_CLASS_TRAITS( NoteData, new NoteData(*pCopy) ) - -void NoteData::Init() -{ - m_TapNotes = vector(); // ensure that the memory is freed -} - -void NoteData::SetNumTracks( int iNewNumTracks ) -{ - ASSERT( iNewNumTracks > 0 ); - - m_TapNotes.resize( iNewNumTracks ); -} - -bool NoteData::IsComposite() const -{ - for( int track = 0; track < GetNumTracks(); ++track ) - { - FOREACHM_CONST( int, TapNote, m_TapNotes[track], tn ) - if( tn->second.pn != PLAYER_INVALID ) - return true; - } - return false; -} - -// Clear (rowBegin,rowEnd). -void NoteData::ClearRangeForTrack( int rowBegin, int rowEnd, int iTrack ) -{ - // Optimization: if the range encloses everything, just clear the whole maps. - if( rowBegin == 0 && rowEnd == MAX_NOTE_ROW ) - { - m_TapNotes[iTrack].clear(); - return; - } - - /* If the range is empty, don't do anything. Otherwise, an empty range will - * cause hold notes to be split when they shouldn't be. */ - if( rowBegin == rowEnd ) - return; - - NoteData::TrackMap::iterator lBegin, lEnd; - GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, lBegin, lEnd ); - - if( lBegin != lEnd && lBegin->first < rowBegin && lBegin->first + lBegin->second.iDuration > rowEnd ) - { - /* A hold note overlaps the whole range. Truncate it, and add the - * remainder to the end. */ - TapNote tn1 = lBegin->second; - TapNote tn2 = tn1; - - int iEndRow = lBegin->first + tn1.iDuration; - int iRow = lBegin->first; - - tn1.iDuration = rowBegin - iRow; - tn2.iDuration = iEndRow - rowEnd; - - SetTapNote( iTrack, iRow, tn1 ); - SetTapNote( iTrack, rowEnd, tn2 ); - - // We may have invalidated our iterators. - GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, lBegin, lEnd ); - } - else if( lBegin != lEnd && lBegin->first < rowBegin ) - { - // A hold note overlaps the beginning of the range. Truncate it. - TapNote &tn1 = lBegin->second; - int iRow = lBegin->first; - tn1.iDuration = rowBegin - iRow; - - ++lBegin; - } - - if( lBegin != lEnd ) - { - NoteData::TrackMap::iterator prev = lEnd; - --prev; - TapNote tn = lBegin->second; - int iRow = prev->first; - if( tn.type == TapNote::hold_head && iRow + tn.iDuration > rowEnd ) - { - // A hold note overlaps the end of the range. Separate it. - SetTapNote( iTrack, iRow, TAP_EMPTY ); - - int iAdd = rowEnd - iRow; - tn.iDuration -= iAdd; - iRow += iAdd; - SetTapNote( iTrack, iRow, tn ); - lEnd = prev; - } - - // We may have invalidated our iterators. - GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, lBegin, lEnd ); - } - - m_TapNotes[iTrack].erase( lBegin, lEnd ); -} - -void NoteData::ClearRange( int rowBegin, int rowEnd ) -{ - for( int t=0; t < GetNumTracks(); ++t ) - ClearRangeForTrack( rowBegin, rowEnd, t ); -} - -void NoteData::ClearAll() -{ - for( int t=0; t rowFromEnd ) - return; // empty range - - const int rowToEnd = (rowFromEnd-rowFromBegin) + rowToBegin; - const int iMoveBy = rowToBegin-rowFromBegin; - - // Clear the region. - ClearRange( rowToBegin, rowToEnd ); - - for( int t=0; tsecond; - if( head.type == TapNote::empty ) - continue; - - if( head.type == TapNote::hold_head ) - { - int iStartRow = lBegin->first + iMoveBy; - int iEndRow = iStartRow + head.iDuration; - - iStartRow = clamp( iStartRow, rowToBegin, rowToEnd ); - iEndRow = clamp( iEndRow, rowToBegin, rowToEnd ); - - this->AddHoldNote( t, iStartRow, iEndRow, head ); - } - else - { - int iTo = lBegin->first + iMoveBy; - if( iTo >= rowToBegin && iTo <= rowToEnd ) - this->SetTapNote( t, iTo, head ); - } - } - } -} - -void NoteData::CopyAll( const NoteData& from ) -{ - *this = from; -} - -bool NoteData::IsRowEmpty( int row ) const -{ - for( int t=0; t& addTo ) const -{ - for( int t=0; t=0; t-- ) - { - if( GetTapNote( t, row ).type == TapNote::empty ) - { - iEmptyTrackOut = t; - return true; - } - } - return false; -} - -int NoteData::GetNumTracksWithTap( int row ) const -{ - int iNum = 0; - for( int t=0; t=0; t-- ) - { - const TapNote &tn = GetTapNote( t, row ); - if( tn.type == TapNote::tap || tn.type == TapNote::lift || tn.type == TapNote::hold_head ) - return t; - } - return -1; -} - -void NoteData::AddHoldNote( int iTrack, int iStartRow, int iEndRow, TapNote tn ) -{ - ASSERT( iStartRow>=0 && iEndRow>=0 ); - ASSERT_M( iEndRow >= iStartRow, ssprintf("EndRow %d < StartRow %d",iEndRow,iStartRow) ); - - /* Include adjacent (non-overlapping) hold notes, since we need to merge with them. */ - NoteData::TrackMap::iterator lBegin, lEnd; - GetTapNoteRangeInclusive( iTrack, iStartRow, iEndRow, lBegin, lEnd, true ); - - // Look for other hold notes that overlap and merge them into add. - for( iterator it = lBegin; it != lEnd; ++it ) - { - int iOtherRow = it->first; - const TapNote &tnOther = it->second; - if( tnOther.type == TapNote::hold_head ) - { - iStartRow = min( iStartRow, iOtherRow ); - iEndRow = max( iEndRow, iOtherRow + tnOther.iDuration ); - } - } - - tn.iDuration = iEndRow - iStartRow; - - // Remove everything in the range. - while( lBegin != lEnd ) - { - iterator next = lBegin; - ++next; - - RemoveTapNote( iTrack, lBegin ); - - lBegin = next; - } - - /* Additionally, if there's a tap note lying at the end of our range, - * remove it too. */ - SetTapNote( iTrack, iEndRow, TAP_EMPTY ); - - // add a tap note at the start of this hold - SetTapNote( iTrack, iStartRow, tn ); -} - -/* Determine if a hold note lies on the given spot. Return true if so. If - * pHeadRow is non-NULL, return the row of the head. */ -bool NoteData::IsHoldHeadOrBodyAtRow( int iTrack, int iRow, int *pHeadRow ) const -{ - const TapNote &tn = GetTapNote( iTrack, iRow ); - if( tn.type == TapNote::hold_head ) - { - if( pHeadRow != NULL ) - *pHeadRow = iRow; - return true; - } - - return IsHoldNoteAtRow( iTrack, iRow, pHeadRow ); -} - -/* Determine if a hold note lies on the given spot. Return true if so. If - * pHeadRow is non-NULL, return the row of the head. (Note that this returns - * false if a hold head lies on iRow itself.) */ -/* XXX: rename this to IsHoldBodyAtRow */ -bool NoteData::IsHoldNoteAtRow( int iTrack, int iRow, int *pHeadRow ) const -{ - int iDummy; - if( pHeadRow == NULL ) - pHeadRow = &iDummy; - - /* Starting at iRow, search upwards. If we find a TapNote::hold_head, we're within - * a hold. If we find a tap, mine or attack, we're not--those never lie - * within hold notes. Ignore autoKeysound. */ - FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE_REVERSE( *this, iTrack, r, 0, iRow ) - { - const TapNote &tn = GetTapNote( iTrack, r ); - switch( tn.type ) - { - case TapNote::hold_head: - if( tn.iDuration + r < iRow ) - return false; - *pHeadRow = r; - return true; - - case TapNote::tap: - case TapNote::mine: - case TapNote::attack: - case TapNote::lift: - case TapNote::fake: - return false; - - case TapNote::empty: - case TapNote::autoKeysound: - // ignore - continue; - DEFAULT_FAIL( tn.type ); - } - } - - return false; -} - -bool NoteData::IsEmpty() const -{ - for( int t=0; t < GetNumTracks(); t++ ) - { - int iRow = -1; - if( !GetNextTapNoteRowForTrack( t, iRow ) ) - continue; - - return false; - } - - return true; -} - -int NoteData::GetFirstRow() const -{ - int iEarliestRowFoundSoFar = -1; - - for( int t=0; t < GetNumTracks(); t++ ) - { - int iRow = -1; - if( !GetNextTapNoteRowForTrack( t, iRow ) ) - continue; - - if( iEarliestRowFoundSoFar == -1 ) - iEarliestRowFoundSoFar = iRow; - else - iEarliestRowFoundSoFar = min( iEarliestRowFoundSoFar, iRow ); - } - - if( iEarliestRowFoundSoFar == -1 ) // there are no notes - return 0; - - return iEarliestRowFoundSoFar; -} - -int NoteData::GetLastRow() const -{ - int iOldestRowFoundSoFar = 0; - - for( int t=0; t < GetNumTracks(); t++ ) - { - int iRow = MAX_NOTE_ROW; - if( !GetPrevTapNoteRowForTrack( t, iRow ) ) - continue; - - /* XXX: We might have a hold note near the end with autoplay sounds - * after it. Do something else with autoplay sounds ... */ - const TapNote &tn = GetTapNote( t, iRow ); - if( tn.type == TapNote::hold_head ) - iRow += tn.iDuration; - - iOldestRowFoundSoFar = max( iOldestRowFoundSoFar, iRow ); - } - - return iOldestRowFoundSoFar; -} - -bool NoteData::IsTap(const TapNote &tn, const int row) const -{ - return (tn.type != TapNote::empty && tn.type != TapNote::mine - && tn.type != TapNote::lift && tn.type != TapNote::fake - && tn.type != TapNote::autoKeysound - && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); -} - -bool NoteData::IsMine(const TapNote &tn, const int row) const -{ - return (tn.type == TapNote::mine - && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); -} - -bool NoteData::IsLift(const TapNote &tn, const int row) const -{ - return (tn.type == TapNote::lift - && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); -} - -bool NoteData::IsFake(const TapNote &tn, const int row) const -{ - return (tn.type == TapNote::fake - || !GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); -} - -int NoteData::GetNumTapNotes( int iStartIndex, int iEndIndex ) const -{ - int iNumNotes = 0; - for( int t=0; tIsTap(GetTapNote(t, r), r)) - iNumNotes++; - } - } - - return iNumNotes; -} - -int NoteData::GetNumTapNotesInRow( int iRow ) const -{ - int iNumNotes = 0; - for( int t=0; tIsTap(GetTapNote(t, iRow), iRow)) - iNumNotes++; - } - return iNumNotes; -} - -int NoteData::GetNumRowsWithTap( int iStartIndex, int iEndIndex ) const -{ - int iNumNotes = 0; - FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) - if( IsThereATapAtRow(r) && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r) ) - iNumNotes++; - - return iNumNotes; -} - -int NoteData::GetNumMines( int iStartIndex, int iEndIndex ) const -{ - int iNumMines = 0; - - for( int t=0; tIsMine(GetTapNote(t, r), r)) - iNumMines++; - } - - return iNumMines; -} - -int NoteData::GetNumRowsWithTapOrHoldHead( int iStartIndex, int iEndIndex ) const -{ - int iNumNotes = 0; - FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) - if( IsThereATapOrHoldHeadAtRow(r) && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r) ) - iNumNotes++; - - return iNumNotes; -} - -bool NoteData::RowNeedsAtLeastSimultaneousPresses( int iMinSimultaneousPresses, const int row ) const -{ - int iNumNotesThisIndex = 0; - for( int t=0; t= iMinSimultaneousPresses; -} - -int NoteData::GetNumRowsWithSimultaneousPresses( int iMinSimultaneousPresses, int iStartIndex, int iEndIndex ) const -{ - /* Count the number of times you have to use your hands. This includes - * three taps at the same time, a tap while two hold notes are being held, - * etc. Only count rows that have at least one tap note (hold heads count). - * Otherwise, every row of hold notes counts, so three simultaneous hold - * notes will count as hundreds of "hands". */ - int iNum = 0; - FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) - { - if( !RowNeedsAtLeastSimultaneousPresses(iMinSimultaneousPresses,r) ) - continue; - if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r)) - continue; - iNum++; - } - - return iNum; -} - -int NoteData::GetNumRowsWithSimultaneousTaps( int iMinTaps, int iStartIndex, int iEndIndex ) const -{ - int iNum = 0; - FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) - { - if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r)) - continue; - int iNumNotesThisIndex = 0; - for( int t=0; t= iMinTaps ) - iNum++; - } - - return iNum; -} - -int NoteData::GetNumHoldNotes( int iStartIndex, int iEndIndex ) const -{ - int iNumHolds = 0; - for( int t=0; tsecond.type != TapNote::hold_head || - lBegin->second.subType != TapNote::hold_head_hold ) - continue; - if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) - continue; - iNumHolds++; - } - } - return iNumHolds; -} - -int NoteData::GetNumRolls( int iStartIndex, int iEndIndex ) const -{ - int iNumRolls = 0; - for( int t=0; tsecond.type != TapNote::hold_head || - lBegin->second.subType != TapNote::hold_head_roll ) - continue; - if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) - continue; - iNumRolls++; - } - } - return iNumRolls; -} - -int NoteData::GetNumLifts( int iStartIndex, int iEndIndex ) const -{ - int iNumLifts = 0; - - for( int t=0; tIsLift(GetTapNote(t, r), r)) - iNumLifts++; - } - - return iNumLifts; -} - -int NoteData::GetNumFakes( int iStartIndex, int iEndIndex ) const -{ - int iNumFakes = 0; - - for( int t=0; tIsFake(GetTapNote(t, r), r)) - iNumFakes++; - } - - return iNumFakes; -} - -bool NoteData::IsPlayer1(const int track, const TapNote &tn) const -{ - if (this->IsComposite()) - { - return tn.pn == PLAYER_1; - } - return track < (this->GetNumTracks() / 2); -} - -pair NoteData::GetNumTapNotesTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - pair num(0, 0); - for( int t=0; tIsTap(tn, r)) - { - if (this->IsPlayer1(t, tn)) - num.first++; - else - num.second++; - } - } - } - return num; -} - -pair NoteData::GetNumRowsWithSimultaneousTapsTwoPlayer(int minTaps, - int startRow, - int endRow) const -{ - pair num(0, 0); - FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, startRow, endRow ) - { - pair found(0, 0); - for( int t=0; tIsTap(tn, r)) - { - if (this->IsPlayer1(t, tn)) - found.first++; - else - found.second++; - } - } - if (found.first >= minTaps) - num.first++; - if (found.second >= minTaps) - num.second++; - } - return num; -} - -pair NoteData::GetNumJumpsTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - return GetNumRowsWithSimultaneousTapsTwoPlayer( 2, iStartIndex, iEndIndex ); -} - -pair NoteData::GetNumHandsTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - return GetNumRowsWithSimultaneousTapsTwoPlayer( 3, iStartIndex, iEndIndex ); -} - -pair NoteData::GetNumQuadsTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - return GetNumRowsWithSimultaneousTapsTwoPlayer( 4, iStartIndex, iEndIndex ); -} - -pair NoteData::GetNumHoldNotesTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - pair num(0, 0); - for( int t=0; tsecond.type != TapNote::hold_head || - lBegin->second.subType != TapNote::hold_head_hold ) - continue; - if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) - continue; - if (this->IsPlayer1(t, lBegin->second)) - num.first++; - else - num.second++; - } - } - return num; -} - -pair NoteData::GetNumMinesTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - pair num(0, 0); - for( int t=0; tIsMine(tn, r)) - { - if (this->IsPlayer1(t, tn)) - num.first++; - else - num.second++; - } - } - } - return num; -} - -pair NoteData::GetNumRollsTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - pair num(0, 0); - for( int t=0; tsecond.type != TapNote::hold_head || - lBegin->second.subType != TapNote::hold_head_roll ) - continue; - if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) - continue; - if (this->IsPlayer1(t, lBegin->second)) - num.first++; - else - num.second++; - } - } - return num; -} - -pair NoteData::GetNumLiftsTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - pair num(0, 0); - for( int t=0; tIsLift(tn, r)) - { - if (this->IsPlayer1(t, tn)) - num.first++; - else - num.second++; - } - } - } - return num; -} - -pair NoteData::GetNumFakesTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - pair num(0, 0); - for( int t=0; tIsFake(tn, r)) - { - if (this->IsPlayer1(t, tn)) - num.first++; - else - num.second++; - } - } - } - return num; -} - -/* -int NoteData::GetNumMinefields( int iStartIndex, int iEndIndex ) const -{ - int iNumMinefields = 0; - for( int t=0; tsecond.type != TapNote::hold_head || - begin->second.subType != TapNote::hold_head_mine ) - continue; - iNumMinefields++; - } - } - return iNumMinefields; -} -*/ - -// -1 for iOriginalTracksToTakeFrom means no track -void NoteData::LoadTransformed( const NoteData& in, int iNewNumTracks, const int iOriginalTrackToTakeFrom[] ) -{ - // reset all notes - Init(); - - SetNumTracks( iNewNumTracks ); - - // copy tracks - for( int t=0; t= %i (#tracks) (taking from %i)", - iOriginalTrack, in.GetNumTracks(), iOriginalTrackToTakeFrom[t])); - - if( iOriginalTrack == -1 ) - continue; - m_TapNotes[t] = in.m_TapNotes[iOriginalTrack]; - } -} - -void NoteData::MoveTapNoteTrack( int dest, int src ) -{ - if(dest == src) return; - m_TapNotes[dest] = m_TapNotes[src]; - m_TapNotes[src].clear(); -} - -void NoteData::SetTapNote( int track, int row, const TapNote& t ) -{ - DEBUG_ASSERT( track>=0 && track& addTo ) -{ - for( int t=0; t viTracks; - viTracks.clear(); - GetTracksHeldAtRow( row, viTracks ); - return viTracks.size(); -} - -bool NoteData::GetNextTapNoteRowForTrack( int track, int &rowInOut ) const -{ - const TrackMap &mapTrack = m_TapNotes[track]; - - // lower_bound and upper_bound have the same effect here because duplicate - // keys aren't allowed. - - // lower_bound "finds the first element whose key is not less than k" (>=); - // upper_bound "finds the first element whose key greater than k". They don't - // have the same effect, but lower_bound(row+1) should equal upper_bound(row). -glenn - TrackMap::const_iterator iter = mapTrack.lower_bound( rowInOut+1 ); // "find the first note for which row+1 < key == false" - if( iter == mapTrack.end() ) - return false; - - ASSERT( iter->first > rowInOut ); - rowInOut = iter->first; - return true; -} - -bool NoteData::GetPrevTapNoteRowForTrack( int track, int &rowInOut ) const -{ - const TrackMap &mapTrack = m_TapNotes[track]; - - // Find the first note >= rowInOut. - TrackMap::const_iterator iter = mapTrack.lower_bound( rowInOut ); - - // If we're at the beginning, we can't move back any more. - if( iter == mapTrack.begin() ) - return false; - - // Move back by one. - --iter; - ASSERT( iter->first < rowInOut ); - rowInOut = iter->first; - return true; -} - -void NoteData::GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &lBegin, TrackMap::iterator &lEnd ) -{ - ASSERT_M( iTrack < GetNumTracks(), ssprintf("%i,%i", iTrack, GetNumTracks()) ); - TrackMap &mapTrack = m_TapNotes[iTrack]; - - if( iStartRow > iEndRow ) - { - lBegin = lEnd = mapTrack.end(); - return; - } - - if( iStartRow <= 0 ) - lBegin = mapTrack.begin(); // optimization - else if( iStartRow >= MAX_NOTE_ROW ) - lBegin = mapTrack.end(); // optimization - else - lBegin = mapTrack.lower_bound( iStartRow ); - - if( iEndRow <= 0 ) - lEnd = mapTrack.begin(); // optimization - else if( iEndRow >= MAX_NOTE_ROW ) - lEnd = mapTrack.end(); // optimization - else - lEnd = mapTrack.lower_bound( iEndRow ); -} - - -/* Include hold notes that overlap the edges. If a hold note completely surrounds the given - * range, included it, too. If bIncludeAdjacent is true, also include hold notes adjacent to, - * but not overlapping, the edge. */ -void NoteData::GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &lBegin, TrackMap::iterator &lEnd, bool bIncludeAdjacent ) -{ - GetTapNoteRange( iTrack, iStartRow, iEndRow, lBegin, lEnd ); - - if( lBegin != this->begin(iTrack) ) - { - iterator prev = Decrement(lBegin); - - const TapNote &tn = prev->second; - if( tn.type == TapNote::hold_head ) - { - int iHoldStartRow = prev->first; - int iHoldEndRow = iHoldStartRow + tn.iDuration; - if( bIncludeAdjacent ) - ++iHoldEndRow; - if( iHoldEndRow > iStartRow ) - { - // The previous note is a hold. - lBegin = prev; - } - } - } - - if( bIncludeAdjacent && lEnd != this->end(iTrack) ) - { - // Include the next note if it's a hold and starts on iEndRow. - const TapNote &tn = lEnd->second; - int iHoldStartRow = lEnd->first; - if( tn.type == TapNote::hold_head && iHoldStartRow == iEndRow ) - ++lEnd; - } -} - -void NoteData::GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &lBegin, TrackMap::iterator &lEnd ) -{ - GetTapNoteRange( iTrack, iStartRow, iEndRow, lBegin, lEnd ); - - // If end-1 is a hold_head, and extends beyond iEndRow, exclude it. - if( lBegin != lEnd && lEnd != this->begin(iTrack) ) - { - iterator prev = lEnd; - --prev; - if( prev->second.type == TapNote::hold_head ) - { - int localStartRow = prev->first; - const TapNote &tn = prev->second; - if( localStartRow + tn.iDuration >= iEndRow ) - lEnd = prev; - } - } -} - -void NoteData::GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &lBegin, TrackMap::const_iterator &lEnd ) const -{ - TrackMap::iterator const_begin, const_end; - const_cast(this)->GetTapNoteRange( iTrack, iStartRow, iEndRow, const_begin, const_end ); - lBegin = const_begin; - lEnd = const_end; -} - -void NoteData::GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &lBegin, TrackMap::const_iterator &lEnd, bool bIncludeAdjacent ) const -{ - TrackMap::iterator const_begin, const_end; - const_cast(this)->GetTapNoteRangeInclusive( iTrack, iStartRow, iEndRow, const_begin, const_end, bIncludeAdjacent ); - lBegin = const_begin; - lEnd = const_end; -} - -void NoteData::GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &lBegin, TrackMap::const_iterator &lEnd ) const -{ - TrackMap::iterator const_begin, const_end; - const_cast(this)->GetTapNoteRange( iTrack, iStartRow, iEndRow, const_begin, const_end ); - lBegin = const_begin; - lEnd = const_end; -} - - - -bool NoteData::GetNextTapNoteRowForAllTracks( int &rowInOut ) const -{ - int iClosestNextRow = MAX_NOTE_ROW; - bool bAnyHaveNextNote = false; - for( int t=0; tCreateNode(); - - p2->AppendAttr( "Track", iter.Track() ); - p2->AppendAttr( "Row", iter.Row() ); - p->AppendChild( p2 ); - } - return p; -} - -void NoteData::LoadFromNode( const XNode* pNode ) -{ - FAIL_M("NoteData::LoadFromNode() not implemented"); -} - -template -void NoteData::_all_tracks_iterator::Find( bool bReverse ) -{ - // If no notes can be found in the range, m_iTrack will stay -1 and IsAtEnd() will return true. - m_iTrack = -1; - if( bReverse ) - { - int iMaxRow = INT_MIN; - for( int iTrack = m_pNoteData->GetNumTracks() - 1; iTrack >= 0; --iTrack ) - { - iter &i( m_vCurrentIters[iTrack] ); - const iter &end = m_vEndIters[iTrack]; - if( i != end && i->first > iMaxRow ) - { - iMaxRow = i->first; - m_iTrack = iTrack; - } - } - } - else - { - - int iMinRow = INT_MAX; - for( int iTrack = 0; iTrack < m_pNoteData->GetNumTracks(); ++iTrack ) - { - iter &i = m_vCurrentIters[iTrack]; - const iter &end = m_vEndIters[iTrack]; - if( i != end && i->first < iMinRow ) - { - iMinRow = i->first; - m_iTrack = iTrack; - } - } - } -} - -template -NoteData::_all_tracks_iterator::_all_tracks_iterator( ND &nd, int iStartRow, int iEndRow, bool bReverse, bool bInclusive ) : - m_pNoteData(&nd), m_iTrack(0), m_bReverse(bReverse) -{ - ASSERT( m_pNoteData->GetNumTracks() > 0 ); - - for( int iTrack = 0; iTrack < m_pNoteData->GetNumTracks(); ++iTrack ) - { - iter begin, end; - if( bInclusive ) - m_pNoteData->GetTapNoteRangeInclusive( iTrack, iStartRow, iEndRow, begin, end ); - else - m_pNoteData->GetTapNoteRange( iTrack, iStartRow, iEndRow, begin, end ); - - m_vBeginIters.push_back( begin ); - m_vEndIters.push_back( end ); - - iter cur; - if( m_bReverse ) - { - cur = end; - if( cur != begin ) - cur--; - } - else - { - cur = begin; - } - m_vCurrentIters.push_back( cur ); - } - - Find( bReverse ); -} - -template -NoteData::_all_tracks_iterator::_all_tracks_iterator( const _all_tracks_iterator &other ) : -#define COPY_OTHER( x ) x( other.x ) - COPY_OTHER( m_pNoteData ), - COPY_OTHER( m_vBeginIters ), - COPY_OTHER( m_vCurrentIters ), - COPY_OTHER( m_vEndIters ), - COPY_OTHER( m_iTrack ), - COPY_OTHER( m_bReverse ) -#undef COPY_OTHER -{ -} - -template -NoteData::_all_tracks_iterator &NoteData::_all_tracks_iterator::operator++() // preincrement -{ - if( m_bReverse ) - { - if( m_vCurrentIters[m_iTrack] == m_vBeginIters[m_iTrack] ) - m_vCurrentIters[m_iTrack] = m_vEndIters[m_iTrack]; - else - --m_vCurrentIters[m_iTrack]; - } - else - { - ++m_vCurrentIters[m_iTrack]; - } - Find( m_bReverse ); - return *this; -} - -template -NoteData::_all_tracks_iterator NoteData::_all_tracks_iterator::operator++( int ) // postincrement -{ - _all_tracks_iterator ret( *this ); - operator++(); - return ret; -} -/* XXX: This doesn't satisfy the requirements that ++iter; --iter; is a no-op so it cannot be bidirectional for now. */ -#if 0 -template -NoteData::_all_tracks_iterator &NoteData::_all_tracks_iterator::operator--() // predecrement -{ - if( m_bReverse ) - { - ++m_vCurrentIters[m_iTrack]; - } - else - { - if( m_vCurrentIters[m_iTrack] == m_vEndIters[m_iTrack] ) - m_vCurrentIters[m_iTrack] = m_vEndIters[m_iTrack]; - else - --m_vCurrentIters[m_iTrack]; - } - Find( !m_bReverse ); - return *this; -} - -template -NoteData::_all_tracks_iterator NoteData::_all_tracks_iterator::operator--( int dummy ) // postdecrement -{ - _all_tracks_iterator ret( *this ); - operator--(); - return ret; -} -#endif - -// Explicit instantiation. -template class NoteData::_all_tracks_iterator; -template class NoteData::_all_tracks_iterator; - -/* - * (c) 2001-2004 Chris Danford, Glenn Maynard - * 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. - */ +/* + * NoteData is organized by: + * track - corresponds to different columns of notes on the screen + * row/index - corresponds to subdivisions of beats + */ + +#include "global.h" +#include "NoteData.h" +#include "RageUtil.h" +#include "RageLog.h" +#include "XmlFile.h" +#include "GameState.h" // blame radar calculations. +#include "Foreach.h" +#include "RageUtil_AutoPtr.h" + +REGISTER_CLASS_TRAITS( NoteData, new NoteData(*pCopy) ) + +void NoteData::Init() +{ + m_TapNotes = vector(); // ensure that the memory is freed +} + +void NoteData::SetNumTracks( int iNewNumTracks ) +{ + ASSERT( iNewNumTracks > 0 ); + + m_TapNotes.resize( iNewNumTracks ); +} + +bool NoteData::IsComposite() const +{ + for( int track = 0; track < GetNumTracks(); ++track ) + { + for (std::pair const &tn : m_TapNotes[track]) + if( tn.second.pn != PLAYER_INVALID ) + return true; + } + return false; +} + +// Clear (rowBegin,rowEnd). +void NoteData::ClearRangeForTrack( int rowBegin, int rowEnd, int iTrack ) +{ + // Optimization: if the range encloses everything, just clear the whole maps. + if( rowBegin == 0 && rowEnd == MAX_NOTE_ROW ) + { + m_TapNotes[iTrack].clear(); + return; + } + + /* If the range is empty, don't do anything. Otherwise, an empty range will + * cause hold notes to be split when they shouldn't be. */ + if( rowBegin == rowEnd ) + return; + + NoteData::TrackMap::iterator lBegin, lEnd; + GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, lBegin, lEnd ); + + if( lBegin != lEnd && lBegin->first < rowBegin && lBegin->first + lBegin->second.iDuration > rowEnd ) + { + /* A hold note overlaps the whole range. Truncate it, and add the + * remainder to the end. */ + TapNote tn1 = lBegin->second; + TapNote tn2 = tn1; + + int iEndRow = lBegin->first + tn1.iDuration; + int iRow = lBegin->first; + + tn1.iDuration = rowBegin - iRow; + tn2.iDuration = iEndRow - rowEnd; + + SetTapNote( iTrack, iRow, tn1 ); + SetTapNote( iTrack, rowEnd, tn2 ); + + // We may have invalidated our iterators. + GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, lBegin, lEnd ); + } + else if( lBegin != lEnd && lBegin->first < rowBegin ) + { + // A hold note overlaps the beginning of the range. Truncate it. + TapNote &tn1 = lBegin->second; + int iRow = lBegin->first; + tn1.iDuration = rowBegin - iRow; + + ++lBegin; + } + + if( lBegin != lEnd ) + { + NoteData::TrackMap::iterator prev = lEnd; + --prev; + TapNote tn = lBegin->second; + int iRow = prev->first; + if( tn.type == TapNote::hold_head && iRow + tn.iDuration > rowEnd ) + { + // A hold note overlaps the end of the range. Separate it. + SetTapNote( iTrack, iRow, TAP_EMPTY ); + + int iAdd = rowEnd - iRow; + tn.iDuration -= iAdd; + iRow += iAdd; + SetTapNote( iTrack, iRow, tn ); + lEnd = prev; + } + + // We may have invalidated our iterators. + GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, lBegin, lEnd ); + } + + m_TapNotes[iTrack].erase( lBegin, lEnd ); +} + +void NoteData::ClearRange( int rowBegin, int rowEnd ) +{ + for( int t=0; t < GetNumTracks(); ++t ) + ClearRangeForTrack( rowBegin, rowEnd, t ); +} + +void NoteData::ClearAll() +{ + for( int t=0; t rowFromEnd ) + return; // empty range + + const int rowToEnd = (rowFromEnd-rowFromBegin) + rowToBegin; + const int iMoveBy = rowToBegin-rowFromBegin; + + // Clear the region. + ClearRange( rowToBegin, rowToEnd ); + + for( int t=0; tsecond; + if( head.type == TapNote::empty ) + continue; + + if( head.type == TapNote::hold_head ) + { + int iStartRow = lBegin->first + iMoveBy; + int iEndRow = iStartRow + head.iDuration; + + iStartRow = clamp( iStartRow, rowToBegin, rowToEnd ); + iEndRow = clamp( iEndRow, rowToBegin, rowToEnd ); + + this->AddHoldNote( t, iStartRow, iEndRow, head ); + } + else + { + int iTo = lBegin->first + iMoveBy; + if( iTo >= rowToBegin && iTo <= rowToEnd ) + this->SetTapNote( t, iTo, head ); + } + } + } +} + +void NoteData::CopyAll( const NoteData& from ) +{ + *this = from; +} + +bool NoteData::IsRowEmpty( int row ) const +{ + for( int t=0; t& addTo ) const +{ + for( int t=0; t=0; t-- ) + { + if( GetTapNote( t, row ).type == TapNote::empty ) + { + iEmptyTrackOut = t; + return true; + } + } + return false; +} + +int NoteData::GetNumTracksWithTap( int row ) const +{ + int iNum = 0; + for( int t=0; t=0; t-- ) + { + const TapNote &tn = GetTapNote( t, row ); + if( tn.type == TapNote::tap || tn.type == TapNote::lift || tn.type == TapNote::hold_head ) + return t; + } + return -1; +} + +void NoteData::AddHoldNote( int iTrack, int iStartRow, int iEndRow, TapNote tn ) +{ + ASSERT( iStartRow>=0 && iEndRow>=0 ); + ASSERT_M( iEndRow >= iStartRow, ssprintf("EndRow %d < StartRow %d",iEndRow,iStartRow) ); + + /* Include adjacent (non-overlapping) hold notes, since we need to merge with them. */ + NoteData::TrackMap::iterator lBegin, lEnd; + GetTapNoteRangeInclusive( iTrack, iStartRow, iEndRow, lBegin, lEnd, true ); + + // Look for other hold notes that overlap and merge them into add. + for( iterator it = lBegin; it != lEnd; ++it ) + { + int iOtherRow = it->first; + const TapNote &tnOther = it->second; + if( tnOther.type == TapNote::hold_head ) + { + iStartRow = min( iStartRow, iOtherRow ); + iEndRow = max( iEndRow, iOtherRow + tnOther.iDuration ); + } + } + + tn.iDuration = iEndRow - iStartRow; + + // Remove everything in the range. + while( lBegin != lEnd ) + { + iterator next = lBegin; + ++next; + + RemoveTapNote( iTrack, lBegin ); + + lBegin = next; + } + + /* Additionally, if there's a tap note lying at the end of our range, + * remove it too. */ + SetTapNote( iTrack, iEndRow, TAP_EMPTY ); + + // add a tap note at the start of this hold + SetTapNote( iTrack, iStartRow, tn ); +} + +/* Determine if a hold note lies on the given spot. Return true if so. If + * pHeadRow is non-NULL, return the row of the head. */ +bool NoteData::IsHoldHeadOrBodyAtRow( int iTrack, int iRow, int *pHeadRow ) const +{ + const TapNote &tn = GetTapNote( iTrack, iRow ); + if( tn.type == TapNote::hold_head ) + { + if( pHeadRow != NULL ) + *pHeadRow = iRow; + return true; + } + + return IsHoldNoteAtRow( iTrack, iRow, pHeadRow ); +} + +/* Determine if a hold note lies on the given spot. Return true if so. If + * pHeadRow is non-NULL, return the row of the head. (Note that this returns + * false if a hold head lies on iRow itself.) */ +/* XXX: rename this to IsHoldBodyAtRow */ +bool NoteData::IsHoldNoteAtRow( int iTrack, int iRow, int *pHeadRow ) const +{ + int iDummy; + if( pHeadRow == NULL ) + pHeadRow = &iDummy; + + /* Starting at iRow, search upwards. If we find a TapNote::hold_head, we're within + * a hold. If we find a tap, mine or attack, we're not--those never lie + * within hold notes. Ignore autoKeysound. */ + FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE_REVERSE( *this, iTrack, r, 0, iRow ) + { + const TapNote &tn = GetTapNote( iTrack, r ); + switch( tn.type ) + { + case TapNote::hold_head: + if( tn.iDuration + r < iRow ) + return false; + *pHeadRow = r; + return true; + + case TapNote::tap: + case TapNote::mine: + case TapNote::attack: + case TapNote::lift: + case TapNote::fake: + return false; + + case TapNote::empty: + case TapNote::autoKeysound: + // ignore + continue; + DEFAULT_FAIL( tn.type ); + } + } + + return false; +} + +bool NoteData::IsEmpty() const +{ + for( int t=0; t < GetNumTracks(); t++ ) + { + int iRow = -1; + if( !GetNextTapNoteRowForTrack( t, iRow ) ) + continue; + + return false; + } + + return true; +} + +int NoteData::GetFirstRow() const +{ + int iEarliestRowFoundSoFar = -1; + + for( int t=0; t < GetNumTracks(); t++ ) + { + int iRow = -1; + if( !GetNextTapNoteRowForTrack( t, iRow ) ) + continue; + + if( iEarliestRowFoundSoFar == -1 ) + iEarliestRowFoundSoFar = iRow; + else + iEarliestRowFoundSoFar = min( iEarliestRowFoundSoFar, iRow ); + } + + if( iEarliestRowFoundSoFar == -1 ) // there are no notes + return 0; + + return iEarliestRowFoundSoFar; +} + +int NoteData::GetLastRow() const +{ + int iOldestRowFoundSoFar = 0; + + for( int t=0; t < GetNumTracks(); t++ ) + { + int iRow = MAX_NOTE_ROW; + if( !GetPrevTapNoteRowForTrack( t, iRow ) ) + continue; + + /* XXX: We might have a hold note near the end with autoplay sounds + * after it. Do something else with autoplay sounds ... */ + const TapNote &tn = GetTapNote( t, iRow ); + if( tn.type == TapNote::hold_head ) + iRow += tn.iDuration; + + iOldestRowFoundSoFar = max( iOldestRowFoundSoFar, iRow ); + } + + return iOldestRowFoundSoFar; +} + +bool NoteData::IsTap(const TapNote &tn, const int row) const +{ + return (tn.type != TapNote::empty && tn.type != TapNote::mine + && tn.type != TapNote::lift && tn.type != TapNote::fake + && tn.type != TapNote::autoKeysound + && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); +} + +bool NoteData::IsMine(const TapNote &tn, const int row) const +{ + return (tn.type == TapNote::mine + && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); +} + +bool NoteData::IsLift(const TapNote &tn, const int row) const +{ + return (tn.type == TapNote::lift + && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); +} + +bool NoteData::IsFake(const TapNote &tn, const int row) const +{ + return (tn.type == TapNote::fake + || !GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); +} + +int NoteData::GetNumTapNotes( int iStartIndex, int iEndIndex ) const +{ + int iNumNotes = 0; + for( int t=0; tIsTap(GetTapNote(t, r), r)) + iNumNotes++; + } + } + + return iNumNotes; +} + +int NoteData::GetNumTapNotesInRow( int iRow ) const +{ + int iNumNotes = 0; + for( int t=0; tIsTap(GetTapNote(t, iRow), iRow)) + iNumNotes++; + } + return iNumNotes; +} + +int NoteData::GetNumRowsWithTap( int iStartIndex, int iEndIndex ) const +{ + int iNumNotes = 0; + FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) + if( IsThereATapAtRow(r) && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r) ) + iNumNotes++; + + return iNumNotes; +} + +int NoteData::GetNumMines( int iStartIndex, int iEndIndex ) const +{ + int iNumMines = 0; + + for( int t=0; tIsMine(GetTapNote(t, r), r)) + iNumMines++; + } + + return iNumMines; +} + +int NoteData::GetNumRowsWithTapOrHoldHead( int iStartIndex, int iEndIndex ) const +{ + int iNumNotes = 0; + FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) + if( IsThereATapOrHoldHeadAtRow(r) && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r) ) + iNumNotes++; + + return iNumNotes; +} + +bool NoteData::RowNeedsAtLeastSimultaneousPresses( int iMinSimultaneousPresses, const int row ) const +{ + int iNumNotesThisIndex = 0; + for( int t=0; t= iMinSimultaneousPresses; +} + +int NoteData::GetNumRowsWithSimultaneousPresses( int iMinSimultaneousPresses, int iStartIndex, int iEndIndex ) const +{ + /* Count the number of times you have to use your hands. This includes + * three taps at the same time, a tap while two hold notes are being held, + * etc. Only count rows that have at least one tap note (hold heads count). + * Otherwise, every row of hold notes counts, so three simultaneous hold + * notes will count as hundreds of "hands". */ + int iNum = 0; + FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) + { + if( !RowNeedsAtLeastSimultaneousPresses(iMinSimultaneousPresses,r) ) + continue; + if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r)) + continue; + iNum++; + } + + return iNum; +} + +int NoteData::GetNumRowsWithSimultaneousTaps( int iMinTaps, int iStartIndex, int iEndIndex ) const +{ + int iNum = 0; + FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) + { + if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r)) + continue; + int iNumNotesThisIndex = 0; + for( int t=0; t= iMinTaps ) + iNum++; + } + + return iNum; +} + +int NoteData::GetNumHoldNotes( int iStartIndex, int iEndIndex ) const +{ + int iNumHolds = 0; + for( int t=0; tsecond.type != TapNote::hold_head || + lBegin->second.subType != TapNote::hold_head_hold ) + continue; + if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) + continue; + iNumHolds++; + } + } + return iNumHolds; +} + +int NoteData::GetNumRolls( int iStartIndex, int iEndIndex ) const +{ + int iNumRolls = 0; + for( int t=0; tsecond.type != TapNote::hold_head || + lBegin->second.subType != TapNote::hold_head_roll ) + continue; + if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) + continue; + iNumRolls++; + } + } + return iNumRolls; +} + +int NoteData::GetNumLifts( int iStartIndex, int iEndIndex ) const +{ + int iNumLifts = 0; + + for( int t=0; tIsLift(GetTapNote(t, r), r)) + iNumLifts++; + } + + return iNumLifts; +} + +int NoteData::GetNumFakes( int iStartIndex, int iEndIndex ) const +{ + int iNumFakes = 0; + + for( int t=0; tIsFake(GetTapNote(t, r), r)) + iNumFakes++; + } + + return iNumFakes; +} + +bool NoteData::IsPlayer1(const int track, const TapNote &tn) const +{ + if (this->IsComposite()) + { + return tn.pn == PLAYER_1; + } + return track < (this->GetNumTracks() / 2); +} + +pair NoteData::GetNumTapNotesTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + pair num(0, 0); + for( int t=0; tIsTap(tn, r)) + { + if (this->IsPlayer1(t, tn)) + num.first++; + else + num.second++; + } + } + } + return num; +} + +pair NoteData::GetNumRowsWithSimultaneousTapsTwoPlayer(int minTaps, + int startRow, + int endRow) const +{ + pair num(0, 0); + FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, startRow, endRow ) + { + pair found(0, 0); + for( int t=0; tIsTap(tn, r)) + { + if (this->IsPlayer1(t, tn)) + found.first++; + else + found.second++; + } + } + if (found.first >= minTaps) + num.first++; + if (found.second >= minTaps) + num.second++; + } + return num; +} + +pair NoteData::GetNumJumpsTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + return GetNumRowsWithSimultaneousTapsTwoPlayer( 2, iStartIndex, iEndIndex ); +} + +pair NoteData::GetNumHandsTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + return GetNumRowsWithSimultaneousTapsTwoPlayer( 3, iStartIndex, iEndIndex ); +} + +pair NoteData::GetNumQuadsTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + return GetNumRowsWithSimultaneousTapsTwoPlayer( 4, iStartIndex, iEndIndex ); +} + +pair NoteData::GetNumHoldNotesTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + pair num(0, 0); + for( int t=0; tsecond.type != TapNote::hold_head || + lBegin->second.subType != TapNote::hold_head_hold ) + continue; + if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) + continue; + if (this->IsPlayer1(t, lBegin->second)) + num.first++; + else + num.second++; + } + } + return num; +} + +pair NoteData::GetNumMinesTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + pair num(0, 0); + for( int t=0; tIsMine(tn, r)) + { + if (this->IsPlayer1(t, tn)) + num.first++; + else + num.second++; + } + } + } + return num; +} + +pair NoteData::GetNumRollsTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + pair num(0, 0); + for( int t=0; tsecond.type != TapNote::hold_head || + lBegin->second.subType != TapNote::hold_head_roll ) + continue; + if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) + continue; + if (this->IsPlayer1(t, lBegin->second)) + num.first++; + else + num.second++; + } + } + return num; +} + +pair NoteData::GetNumLiftsTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + pair num(0, 0); + for( int t=0; tIsLift(tn, r)) + { + if (this->IsPlayer1(t, tn)) + num.first++; + else + num.second++; + } + } + } + return num; +} + +pair NoteData::GetNumFakesTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + pair num(0, 0); + for( int t=0; tIsFake(tn, r)) + { + if (this->IsPlayer1(t, tn)) + num.first++; + else + num.second++; + } + } + } + return num; +} + +/* +int NoteData::GetNumMinefields( int iStartIndex, int iEndIndex ) const +{ + int iNumMinefields = 0; + for( int t=0; tsecond.type != TapNote::hold_head || + begin->second.subType != TapNote::hold_head_mine ) + continue; + iNumMinefields++; + } + } + return iNumMinefields; +} +*/ + +// -1 for iOriginalTracksToTakeFrom means no track +void NoteData::LoadTransformed( const NoteData& in, int iNewNumTracks, const int iOriginalTrackToTakeFrom[] ) +{ + // reset all notes + Init(); + + SetNumTracks( iNewNumTracks ); + + // copy tracks + for( int t=0; t= %i (#tracks) (taking from %i)", + iOriginalTrack, in.GetNumTracks(), iOriginalTrackToTakeFrom[t])); + + if( iOriginalTrack == -1 ) + continue; + m_TapNotes[t] = in.m_TapNotes[iOriginalTrack]; + } +} + +void NoteData::MoveTapNoteTrack( int dest, int src ) +{ + if(dest == src) return; + m_TapNotes[dest] = m_TapNotes[src]; + m_TapNotes[src].clear(); +} + +void NoteData::SetTapNote( int track, int row, const TapNote& t ) +{ + DEBUG_ASSERT( track>=0 && track& addTo ) +{ + for( int t=0; t viTracks; + viTracks.clear(); + GetTracksHeldAtRow( row, viTracks ); + return viTracks.size(); +} + +bool NoteData::GetNextTapNoteRowForTrack( int track, int &rowInOut ) const +{ + const TrackMap &mapTrack = m_TapNotes[track]; + + // lower_bound and upper_bound have the same effect here because duplicate + // keys aren't allowed. + + // lower_bound "finds the first element whose key is not less than k" (>=); + // upper_bound "finds the first element whose key greater than k". They don't + // have the same effect, but lower_bound(row+1) should equal upper_bound(row). -glenn + TrackMap::const_iterator iter = mapTrack.lower_bound( rowInOut+1 ); // "find the first note for which row+1 < key == false" + if( iter == mapTrack.end() ) + return false; + + ASSERT( iter->first > rowInOut ); + rowInOut = iter->first; + return true; +} + +bool NoteData::GetPrevTapNoteRowForTrack( int track, int &rowInOut ) const +{ + const TrackMap &mapTrack = m_TapNotes[track]; + + // Find the first note >= rowInOut. + TrackMap::const_iterator iter = mapTrack.lower_bound( rowInOut ); + + // If we're at the beginning, we can't move back any more. + if( iter == mapTrack.begin() ) + return false; + + // Move back by one. + --iter; + ASSERT( iter->first < rowInOut ); + rowInOut = iter->first; + return true; +} + +void NoteData::GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &lBegin, TrackMap::iterator &lEnd ) +{ + ASSERT_M( iTrack < GetNumTracks(), ssprintf("%i,%i", iTrack, GetNumTracks()) ); + TrackMap &mapTrack = m_TapNotes[iTrack]; + + if( iStartRow > iEndRow ) + { + lBegin = lEnd = mapTrack.end(); + return; + } + + if( iStartRow <= 0 ) + lBegin = mapTrack.begin(); // optimization + else if( iStartRow >= MAX_NOTE_ROW ) + lBegin = mapTrack.end(); // optimization + else + lBegin = mapTrack.lower_bound( iStartRow ); + + if( iEndRow <= 0 ) + lEnd = mapTrack.begin(); // optimization + else if( iEndRow >= MAX_NOTE_ROW ) + lEnd = mapTrack.end(); // optimization + else + lEnd = mapTrack.lower_bound( iEndRow ); +} + + +/* Include hold notes that overlap the edges. If a hold note completely surrounds the given + * range, included it, too. If bIncludeAdjacent is true, also include hold notes adjacent to, + * but not overlapping, the edge. */ +void NoteData::GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &lBegin, TrackMap::iterator &lEnd, bool bIncludeAdjacent ) +{ + GetTapNoteRange( iTrack, iStartRow, iEndRow, lBegin, lEnd ); + + if( lBegin != this->begin(iTrack) ) + { + iterator prev = Decrement(lBegin); + + const TapNote &tn = prev->second; + if( tn.type == TapNote::hold_head ) + { + int iHoldStartRow = prev->first; + int iHoldEndRow = iHoldStartRow + tn.iDuration; + if( bIncludeAdjacent ) + ++iHoldEndRow; + if( iHoldEndRow > iStartRow ) + { + // The previous note is a hold. + lBegin = prev; + } + } + } + + if( bIncludeAdjacent && lEnd != this->end(iTrack) ) + { + // Include the next note if it's a hold and starts on iEndRow. + const TapNote &tn = lEnd->second; + int iHoldStartRow = lEnd->first; + if( tn.type == TapNote::hold_head && iHoldStartRow == iEndRow ) + ++lEnd; + } +} + +void NoteData::GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &lBegin, TrackMap::iterator &lEnd ) +{ + GetTapNoteRange( iTrack, iStartRow, iEndRow, lBegin, lEnd ); + + // If end-1 is a hold_head, and extends beyond iEndRow, exclude it. + if( lBegin != lEnd && lEnd != this->begin(iTrack) ) + { + iterator prev = lEnd; + --prev; + if( prev->second.type == TapNote::hold_head ) + { + int localStartRow = prev->first; + const TapNote &tn = prev->second; + if( localStartRow + tn.iDuration >= iEndRow ) + lEnd = prev; + } + } +} + +void NoteData::GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &lBegin, TrackMap::const_iterator &lEnd ) const +{ + TrackMap::iterator const_begin, const_end; + const_cast(this)->GetTapNoteRange( iTrack, iStartRow, iEndRow, const_begin, const_end ); + lBegin = const_begin; + lEnd = const_end; +} + +void NoteData::GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &lBegin, TrackMap::const_iterator &lEnd, bool bIncludeAdjacent ) const +{ + TrackMap::iterator const_begin, const_end; + const_cast(this)->GetTapNoteRangeInclusive( iTrack, iStartRow, iEndRow, const_begin, const_end, bIncludeAdjacent ); + lBegin = const_begin; + lEnd = const_end; +} + +void NoteData::GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &lBegin, TrackMap::const_iterator &lEnd ) const +{ + TrackMap::iterator const_begin, const_end; + const_cast(this)->GetTapNoteRange( iTrack, iStartRow, iEndRow, const_begin, const_end ); + lBegin = const_begin; + lEnd = const_end; +} + + + +bool NoteData::GetNextTapNoteRowForAllTracks( int &rowInOut ) const +{ + int iClosestNextRow = MAX_NOTE_ROW; + bool bAnyHaveNextNote = false; + for( int t=0; tCreateNode(); + + p2->AppendAttr( "Track", iter.Track() ); + p2->AppendAttr( "Row", iter.Row() ); + p->AppendChild( p2 ); + } + return p; +} + +void NoteData::LoadFromNode( const XNode* pNode ) +{ + FAIL_M("NoteData::LoadFromNode() not implemented"); +} + +template +void NoteData::_all_tracks_iterator::Find( bool bReverse ) +{ + // If no notes can be found in the range, m_iTrack will stay -1 and IsAtEnd() will return true. + m_iTrack = -1; + if( bReverse ) + { + int iMaxRow = INT_MIN; + for( int iTrack = m_pNoteData->GetNumTracks() - 1; iTrack >= 0; --iTrack ) + { + iter &i( m_vCurrentIters[iTrack] ); + const iter &end = m_vEndIters[iTrack]; + if( i != end && i->first > iMaxRow ) + { + iMaxRow = i->first; + m_iTrack = iTrack; + } + } + } + else + { + + int iMinRow = INT_MAX; + for( int iTrack = 0; iTrack < m_pNoteData->GetNumTracks(); ++iTrack ) + { + iter &i = m_vCurrentIters[iTrack]; + const iter &end = m_vEndIters[iTrack]; + if( i != end && i->first < iMinRow ) + { + iMinRow = i->first; + m_iTrack = iTrack; + } + } + } +} + +template +NoteData::_all_tracks_iterator::_all_tracks_iterator( ND &nd, int iStartRow, int iEndRow, bool bReverse, bool bInclusive ) : + m_pNoteData(&nd), m_iTrack(0), m_bReverse(bReverse) +{ + ASSERT( m_pNoteData->GetNumTracks() > 0 ); + + for( int iTrack = 0; iTrack < m_pNoteData->GetNumTracks(); ++iTrack ) + { + iter begin, end; + if( bInclusive ) + m_pNoteData->GetTapNoteRangeInclusive( iTrack, iStartRow, iEndRow, begin, end ); + else + m_pNoteData->GetTapNoteRange( iTrack, iStartRow, iEndRow, begin, end ); + + m_vBeginIters.push_back( begin ); + m_vEndIters.push_back( end ); + + iter cur; + if( m_bReverse ) + { + cur = end; + if( cur != begin ) + cur--; + } + else + { + cur = begin; + } + m_vCurrentIters.push_back( cur ); + } + + Find( bReverse ); +} + +template +NoteData::_all_tracks_iterator::_all_tracks_iterator( const _all_tracks_iterator &other ) : +#define COPY_OTHER( x ) x( other.x ) + COPY_OTHER( m_pNoteData ), + COPY_OTHER( m_vBeginIters ), + COPY_OTHER( m_vCurrentIters ), + COPY_OTHER( m_vEndIters ), + COPY_OTHER( m_iTrack ), + COPY_OTHER( m_bReverse ) +#undef COPY_OTHER +{ +} + +template +NoteData::_all_tracks_iterator &NoteData::_all_tracks_iterator::operator++() // preincrement +{ + if( m_bReverse ) + { + if( m_vCurrentIters[m_iTrack] == m_vBeginIters[m_iTrack] ) + m_vCurrentIters[m_iTrack] = m_vEndIters[m_iTrack]; + else + --m_vCurrentIters[m_iTrack]; + } + else + { + ++m_vCurrentIters[m_iTrack]; + } + Find( m_bReverse ); + return *this; +} + +template +NoteData::_all_tracks_iterator NoteData::_all_tracks_iterator::operator++( int ) // postincrement +{ + _all_tracks_iterator ret( *this ); + operator++(); + return ret; +} +/* XXX: This doesn't satisfy the requirements that ++iter; --iter; is a no-op so it cannot be bidirectional for now. */ +#if 0 +template +NoteData::_all_tracks_iterator &NoteData::_all_tracks_iterator::operator--() // predecrement +{ + if( m_bReverse ) + { + ++m_vCurrentIters[m_iTrack]; + } + else + { + if( m_vCurrentIters[m_iTrack] == m_vEndIters[m_iTrack] ) + m_vCurrentIters[m_iTrack] = m_vEndIters[m_iTrack]; + else + --m_vCurrentIters[m_iTrack]; + } + Find( !m_bReverse ); + return *this; +} + +template +NoteData::_all_tracks_iterator NoteData::_all_tracks_iterator::operator--( int dummy ) // postdecrement +{ + _all_tracks_iterator ret( *this ); + operator--(); + return ret; +} +#endif + +// Explicit instantiation. +template class NoteData::_all_tracks_iterator; +template class NoteData::_all_tracks_iterator; + +/* + * (c) 2001-2004 Chris Danford, Glenn Maynard + * 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. + */ From 023deca293d6f5354761003415ef74e514ee0a99 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 14:50:26 -0400 Subject: [PATCH 028/281] Remove comments that aren't WHITE SPACE. ...this is going to be a running gag. --- src/NotesWriterJson.cpp | 458 ++++++++++++++++++++-------------------- 1 file changed, 228 insertions(+), 230 deletions(-) diff --git a/src/NotesWriterJson.cpp b/src/NotesWriterJson.cpp index e1b056a9f0..1499663105 100644 --- a/src/NotesWriterJson.cpp +++ b/src/NotesWriterJson.cpp @@ -1,230 +1,228 @@ -#include "global.h" -#include "NotesWriterJson.h" -#include "TimingData.h" -#include "json/value.h" -#include "JsonUtil.h" -#include "Song.h" -#include "BackgroundUtil.h" -#include "Steps.h" -#include "NoteData.h" -#include "GameManager.h" - -static void Serialize(const TimingSegment &seg, Json::Value &root) -{ - root["Beat"] = seg.GetBeat(); - if (seg.GetType() == SEGMENT_BPM) - { - root["BPM"] = static_cast(const_cast(seg)).GetBPM(); - } - else - { - root["Seconds"] = static_cast(const_cast(seg)).GetPause(); - } -} - -static void Serialize(const TimingData &td, Json::Value &root) -{ - JsonUtil::SerializeVectorPointers( td.GetTimingSegments(SEGMENT_BPM), Serialize, root["BpmSegments"] ); - JsonUtil::SerializeVectorPointers( td.GetTimingSegments(SEGMENT_STOP), Serialize, root["StopSegments"] ); -} - -static void Serialize(const LyricSegment &o, Json::Value &root) -{ - root["StartTime"] = (float)o.m_fStartTime; - root["Lyric"] = o.m_sLyric; - root["Color"] = o.m_Color.ToString(); -} - -static void Serialize(const BackgroundDef &o, Json::Value &root) -{ - root["Effect"] = o.m_sEffect; - root["File1"] = o.m_sFile1; - root["File2"] = o.m_sFile2; - root["Color1"] = o.m_sColor1; -} - -static void Serialize(const BackgroundChange &o, Json::Value &root ) -{ - Serialize( o.m_def, root["Def"] ); - root["StartBeat"] = o.m_fStartBeat; - root["Rate"] = o.m_fRate; - root["Transition"] = o.m_sTransition; -} - -static void Serialize( const TapNote &o, Json::Value &root ) -{ - root = Json::Value(Json::objectValue); - - if( o.type != TapNote::tap ) - root["Type"] = (int)o.type; - if( o.type == TapNote::hold_head ) - root["SubType"] = (int)o.subType; - //root["Source"] = (int)source; - if( !o.sAttackModifiers.empty() ) - root["AttackModifiers"] = o.sAttackModifiers; - if( o.fAttackDurationSeconds > 0 ) - root["AttackDurationSeconds"] = o.fAttackDurationSeconds; - if( o.iKeysoundIndex != -1 ) - root["KeysoundIndex"] = o.iKeysoundIndex; - if( o.iDuration > 0 ) - root["Duration"] = o.iDuration; - if( o.pn != PLAYER_INVALID ) - root["PlayerNumber"] = (int)o.pn; -} - -static void Serialize( const NoteData &o, Json::Value &root ) -{ - root = Json::Value(Json::arrayValue); - for(int t=0; t < o.GetNumTracks(); t++ ) - { - NoteData::TrackMap::const_iterator begin, end; - o.GetTapNoteRange( t, 0, MAX_NOTE_ROW, begin, end ); - //NoteData::TrackMap tm = o.GetTrack(t); - //FOREACHM_CONST( int, TapNote, tm, iter ) - for( ; begin != end; ++begin ) - { - int iRow = begin->first; - TapNote tn = begin->second; - root.resize( root.size()+1 ); - Json::Value &root2 = root[ root.size()-1 ]; - root2 = Json::Value(Json::arrayValue); - root2.resize(3); - root2[(unsigned)0] = NoteRowToBeat(iRow); - root2[1] = t; - Serialize( tn, root2[2] ); - } - } -} - -static void Serialize( const RadarValues &o, Json::Value &root ) -{ - FOREACH_ENUM( RadarCategory, rc ) - { - root[ RadarCategoryToString(rc) ] = o.m_Values.f[rc]; - } -} - -static void Serialize( const Steps &o, Json::Value &root ) -{ - root["StepsType"] = StringConversion::ToString(o.m_StepsType); - - o.Decompress(); - - NoteData nd; - o.GetNoteData( nd ); - Serialize( nd, root["NoteData"] ); - root["Hash"] = o.GetHash(); - root["Description"] = o.GetDescription(); - root["Difficulty"] = DifficultyToString(o.GetDifficulty()); - root["Meter"] = o.GetMeter(); - Serialize( o.GetRadarValues( PLAYER_1 ), root["RadarValues"] ); -} - - -bool NotesWriterJson::WriteSong( const RString &sFile, const Song &out, bool bWriteSteps ) -{ - Json::Value root; - root["SongDir"] = out.GetSongDir(); - root["GroupName"] = out.m_sGroupName; - root["Title"] = out.m_sMainTitle; - root["SubTitle"] = out.m_sSubTitle; - root["Artist"] = out.m_sArtist; - root["TitleTranslit"] = out.m_sMainTitleTranslit; - root["SubTitleTranslit"] = out.m_sSubTitleTranslit; - root["Genre"] = out.m_sGenre; - root["Credit"] = out.m_sCredit; - root["Banner"] = out.m_sBannerFile; - root["Background"] = out.m_sBackgroundFile; - root["LyricsFile"] = out.m_sLyricsFile; - root["CDTitle"] = out.m_sCDTitleFile; - root["Music"] = out.m_sMusicFile; - root["Offset"] = out.m_SongTiming.m_fBeat0OffsetInSeconds; - root["SampleStart"] = out.m_fMusicSampleStartSeconds; - root["SampleLength"] = out.m_fMusicSampleLengthSeconds; - if( out.m_SelectionDisplay == Song::SHOW_ALWAYS ) - root["Selectable"] = "YES"; - else if( out.m_SelectionDisplay == Song::SHOW_NEVER ) - root["Selectable"] = "NO"; - else - root["Selectable"] = "YES"; - - root["FirstBeat"] = out.GetFirstBeat(); - root["LastBeat"] = out.GetLastBeat(); - root["SongFileName"] = out.m_sSongFileName; - root["HasMusic"] = out.m_bHasMusic; - root["HasBanner"] = out.m_bHasBanner; - root["MusicLengthSeconds"] = out.m_fMusicLengthSeconds; - - root["DisplayBpmType"] = StringConversion::ToString(out.m_DisplayBPMType); - if( out.m_DisplayBPMType == DISPLAY_BPM_SPECIFIED ) - { - root["SpecifiedBpmMin"] = out.m_fSpecifiedBPMMin; - root["SpecifiedBpmMax"] = out.m_fSpecifiedBPMMax; - } - - Serialize( out.m_SongTiming, root["TimingData"] ); - JsonUtil::SerializeVectorObjects( out.m_LyricSegments, Serialize, root["LyricSegments"] ); - - { - Json::Value &root2 = root["BackgroundChanges"]; - FOREACH_BackgroundLayer( bl ) - { - Json::Value &root3 = root2[bl]; - const vector &vBgc = out.GetBackgroundChanges(bl); - JsonUtil::SerializeVectorObjects( vBgc, Serialize, root3 ); - } - } - - { - const vector &vBgc = out.GetForegroundChanges(); - JsonUtil::SerializeVectorObjects( vBgc, Serialize, root["ForegroundChanges"] ); - } - - JsonUtil::SerializeArrayValues( out.m_vsKeysoundFile, root["KeySounds"] ); - - if( bWriteSteps ) - { - vector vpSteps; - FOREACH_CONST( Steps*, out.GetAllSteps(), iter ) - { - if( (*iter)->IsAutogen() ) - continue; - vpSteps.push_back( *iter ); - } - JsonUtil::SerializeVectorPointers( vpSteps, Serialize, root["Charts"] ); - } - - return JsonUtil::WriteFile( root, sFile, false ); -} - -bool NotesWriterJson::WriteSteps( const RString &sFile, const Steps &out ) -{ - Json::Value root; - Serialize( out, root ); - return JsonUtil::WriteFile( root, sFile, false ); -} - -/* - * (c) 2001-2004 Chris Danford, Glenn Maynard - * 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 "NotesWriterJson.h" +#include "TimingData.h" +#include "json/value.h" +#include "JsonUtil.h" +#include "Song.h" +#include "BackgroundUtil.h" +#include "Steps.h" +#include "NoteData.h" +#include "GameManager.h" + +static void Serialize(const TimingSegment &seg, Json::Value &root) +{ + root["Beat"] = seg.GetBeat(); + if (seg.GetType() == SEGMENT_BPM) + { + root["BPM"] = static_cast(const_cast(seg)).GetBPM(); + } + else + { + root["Seconds"] = static_cast(const_cast(seg)).GetPause(); + } +} + +static void Serialize(const TimingData &td, Json::Value &root) +{ + JsonUtil::SerializeVectorPointers( td.GetTimingSegments(SEGMENT_BPM), Serialize, root["BpmSegments"] ); + JsonUtil::SerializeVectorPointers( td.GetTimingSegments(SEGMENT_STOP), Serialize, root["StopSegments"] ); +} + +static void Serialize(const LyricSegment &o, Json::Value &root) +{ + root["StartTime"] = (float)o.m_fStartTime; + root["Lyric"] = o.m_sLyric; + root["Color"] = o.m_Color.ToString(); +} + +static void Serialize(const BackgroundDef &o, Json::Value &root) +{ + root["Effect"] = o.m_sEffect; + root["File1"] = o.m_sFile1; + root["File2"] = o.m_sFile2; + root["Color1"] = o.m_sColor1; +} + +static void Serialize(const BackgroundChange &o, Json::Value &root ) +{ + Serialize( o.m_def, root["Def"] ); + root["StartBeat"] = o.m_fStartBeat; + root["Rate"] = o.m_fRate; + root["Transition"] = o.m_sTransition; +} + +static void Serialize( const TapNote &o, Json::Value &root ) +{ + root = Json::Value(Json::objectValue); + + if( o.type != TapNote::tap ) + root["Type"] = (int)o.type; + if( o.type == TapNote::hold_head ) + root["SubType"] = (int)o.subType; + //root["Source"] = (int)source; + if( !o.sAttackModifiers.empty() ) + root["AttackModifiers"] = o.sAttackModifiers; + if( o.fAttackDurationSeconds > 0 ) + root["AttackDurationSeconds"] = o.fAttackDurationSeconds; + if( o.iKeysoundIndex != -1 ) + root["KeysoundIndex"] = o.iKeysoundIndex; + if( o.iDuration > 0 ) + root["Duration"] = o.iDuration; + if( o.pn != PLAYER_INVALID ) + root["PlayerNumber"] = (int)o.pn; +} + +static void Serialize( const NoteData &o, Json::Value &root ) +{ + root = Json::Value(Json::arrayValue); + for(int t=0; t < o.GetNumTracks(); t++ ) + { + NoteData::TrackMap::const_iterator begin, end; + o.GetTapNoteRange( t, 0, MAX_NOTE_ROW, begin, end ); + for( ; begin != end; ++begin ) + { + int iRow = begin->first; + TapNote tn = begin->second; + root.resize( root.size()+1 ); + Json::Value &root2 = root[ root.size()-1 ]; + root2 = Json::Value(Json::arrayValue); + root2.resize(3); + root2[(unsigned)0] = NoteRowToBeat(iRow); + root2[1] = t; + Serialize( tn, root2[2] ); + } + } +} + +static void Serialize( const RadarValues &o, Json::Value &root ) +{ + FOREACH_ENUM( RadarCategory, rc ) + { + root[ RadarCategoryToString(rc) ] = o.m_Values.f[rc]; + } +} + +static void Serialize( const Steps &o, Json::Value &root ) +{ + root["StepsType"] = StringConversion::ToString(o.m_StepsType); + + o.Decompress(); + + NoteData nd; + o.GetNoteData( nd ); + Serialize( nd, root["NoteData"] ); + root["Hash"] = o.GetHash(); + root["Description"] = o.GetDescription(); + root["Difficulty"] = DifficultyToString(o.GetDifficulty()); + root["Meter"] = o.GetMeter(); + Serialize( o.GetRadarValues( PLAYER_1 ), root["RadarValues"] ); +} + + +bool NotesWriterJson::WriteSong( const RString &sFile, const Song &out, bool bWriteSteps ) +{ + Json::Value root; + root["SongDir"] = out.GetSongDir(); + root["GroupName"] = out.m_sGroupName; + root["Title"] = out.m_sMainTitle; + root["SubTitle"] = out.m_sSubTitle; + root["Artist"] = out.m_sArtist; + root["TitleTranslit"] = out.m_sMainTitleTranslit; + root["SubTitleTranslit"] = out.m_sSubTitleTranslit; + root["Genre"] = out.m_sGenre; + root["Credit"] = out.m_sCredit; + root["Banner"] = out.m_sBannerFile; + root["Background"] = out.m_sBackgroundFile; + root["LyricsFile"] = out.m_sLyricsFile; + root["CDTitle"] = out.m_sCDTitleFile; + root["Music"] = out.m_sMusicFile; + root["Offset"] = out.m_SongTiming.m_fBeat0OffsetInSeconds; + root["SampleStart"] = out.m_fMusicSampleStartSeconds; + root["SampleLength"] = out.m_fMusicSampleLengthSeconds; + if( out.m_SelectionDisplay == Song::SHOW_ALWAYS ) + root["Selectable"] = "YES"; + else if( out.m_SelectionDisplay == Song::SHOW_NEVER ) + root["Selectable"] = "NO"; + else + root["Selectable"] = "YES"; + + root["FirstBeat"] = out.GetFirstBeat(); + root["LastBeat"] = out.GetLastBeat(); + root["SongFileName"] = out.m_sSongFileName; + root["HasMusic"] = out.m_bHasMusic; + root["HasBanner"] = out.m_bHasBanner; + root["MusicLengthSeconds"] = out.m_fMusicLengthSeconds; + + root["DisplayBpmType"] = StringConversion::ToString(out.m_DisplayBPMType); + if( out.m_DisplayBPMType == DISPLAY_BPM_SPECIFIED ) + { + root["SpecifiedBpmMin"] = out.m_fSpecifiedBPMMin; + root["SpecifiedBpmMax"] = out.m_fSpecifiedBPMMax; + } + + Serialize( out.m_SongTiming, root["TimingData"] ); + JsonUtil::SerializeVectorObjects( out.m_LyricSegments, Serialize, root["LyricSegments"] ); + + { + Json::Value &root2 = root["BackgroundChanges"]; + FOREACH_BackgroundLayer( bl ) + { + Json::Value &root3 = root2[bl]; + const vector &vBgc = out.GetBackgroundChanges(bl); + JsonUtil::SerializeVectorObjects( vBgc, Serialize, root3 ); + } + } + + { + const vector &vBgc = out.GetForegroundChanges(); + JsonUtil::SerializeVectorObjects( vBgc, Serialize, root["ForegroundChanges"] ); + } + + JsonUtil::SerializeArrayValues( out.m_vsKeysoundFile, root["KeySounds"] ); + + if( bWriteSteps ) + { + vector vpSteps; + FOREACH_CONST( Steps*, out.GetAllSteps(), iter ) + { + if( (*iter)->IsAutogen() ) + continue; + vpSteps.push_back( *iter ); + } + JsonUtil::SerializeVectorPointers( vpSteps, Serialize, root["Charts"] ); + } + + return JsonUtil::WriteFile( root, sFile, false ); +} + +bool NotesWriterJson::WriteSteps( const RString &sFile, const Steps &out ) +{ + Json::Value root; + Serialize( out, root ); + return JsonUtil::WriteFile( root, sFile, false ); +} + +/* + * (c) 2001-2004 Chris Danford, Glenn Maynard + * 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. + */ From 62b6ef4bfc5322939875df19ac72b5eb655cd635 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 14:53:31 -0400 Subject: [PATCH 029/281] Utilize const with the pair of WHITE SPACE. ...sigh. --- src/BannerCache.cpp | 960 ++++++++++++++++++++++---------------------- 1 file changed, 480 insertions(+), 480 deletions(-) diff --git a/src/BannerCache.cpp b/src/BannerCache.cpp index da1ae51c17..e18ddc94bf 100644 --- a/src/BannerCache.cpp +++ b/src/BannerCache.cpp @@ -1,480 +1,480 @@ -#include "global.h" - -#include "BannerCache.h" -#include "Foreach.h" -#include "RageDisplay.h" -#include "RageUtil.h" -#include "RageLog.h" -#include "RageSurface_Load.h" -#include "SongCacheIndex.h" -#include "Sprite.h" -#include "PrefsManager.h" -#include "RageDisplay.h" -#include "RageTexture.h" -#include "RageTextureManager.h" -#include "RageSurface.h" -#include "RageSurfaceUtils.h" -#include "RageSurfaceUtils_Palettize.h" -#include "RageSurfaceUtils_Dither.h" -#include "RageSurfaceUtils_Zoom.h" -#include "SpecialFiles.h" - -#include "Banner.h" - -static Preference g_bPalettedBannerCache( "PalettedBannerCache", false ); - -/* Neither a global or a file scope static can be used for this because - * the order of initialization of nonlocal objects is unspecified. */ -//const RString BANNER_CACHE_INDEX = SpecialFiles::CACHE_DIR + "banners.cache"; -#define BANNER_CACHE_INDEX (SpecialFiles::CACHE_DIR + "banners.cache") - -/* Call CacheBanner to cache a banner by path. If the banner is already - * cached, it'll be recreated. This is efficient if the banner hasn't changed, - * but we still only do this in TidyUpData for songs. - * - * Call LoadBanner to load a cached banner into main memory. This will call - * CacheBanner only if needed. This will not do a date/size check; call CacheBanner - * directly if you need that. - * - * Call LoadCachedBanner to load a banner into a texture and retrieve an ID - * for it. You can check if the banner was actually preloaded by calling - * TEXTUREMAN->IsTextureRegistered() on the ID; it might not be if the banner cache - * is missing or disabled. - * - * Note that each cache entries has two hashes. The cache path is based soley - * on the pathname; this way, loading the cache doesn't have to do a stat on every - * banner. The full hash includes the file size and date, and is used only by - * CacheBanner to avoid doing extra work. - */ - -BannerCache *BANNERCACHE; - - -static map g_BannerPathToImage; -static int g_iDemandRefcount = 0; - -RString BannerCache::GetBannerCachePath( RString sBannerPath ) -{ - return SongCacheIndex::GetCacheFilePath( "Banners", sBannerPath ); -} - -/* If in on-demand mode, load all cached banners. This must be fast, so - * cache files will not be created if they don't exist; that should be done - * by CacheBanner or LoadBanner on startup. */ -void BannerCache::Demand() -{ - ++g_iDemandRefcount; - if( g_iDemandRefcount > 1 ) - return; - - if( PREFSMAN->m_BannerCache != BNCACHE_LOW_RES_LOAD_ON_DEMAND ) - return; - - FOREACH_CONST_Child( &BannerData, p ) - { - RString sBannerPath = p->GetName(); - - if( g_BannerPathToImage.find(sBannerPath) != g_BannerPathToImage.end() ) - continue; /* already loaded */ - - const RString sCachePath = GetBannerCachePath(sBannerPath); - RageSurface *pImage = RageSurfaceUtils::LoadSurface( sCachePath ); - if( pImage == NULL ) - { - continue; /* doesn't exist */ - } - - g_BannerPathToImage[sBannerPath] = pImage; - } -} - -/* Release banners loaded on demand. */ -void BannerCache::Undemand() -{ - --g_iDemandRefcount; - if( g_iDemandRefcount != 0 ) - return; - - if( PREFSMAN->m_BannerCache != BNCACHE_LOW_RES_LOAD_ON_DEMAND ) - return; - - UnloadAllBanners(); -} - -/* If in a low-res banner mode, load a low-res banner into memory, creating - * the cache file if necessary. Unlike CacheBanner(), the original file will - * not be examined unless the cached banner doesn't exist, so the banner will - * not be updated if the original file changes, for efficiency. */ -void BannerCache::LoadBanner( RString sBannerPath ) -{ - if( sBannerPath == "" ) - return; // nothing to do - if( PREFSMAN->m_BannerCache != BNCACHE_LOW_RES_PRELOAD && - PREFSMAN->m_BannerCache != BNCACHE_LOW_RES_LOAD_ON_DEMAND ) - return; - - /* Load it. */ - const RString sCachePath = GetBannerCachePath(sBannerPath); - - for( int tries = 0; tries < 2; ++tries ) - { - if( g_BannerPathToImage.find(sBannerPath) != g_BannerPathToImage.end() ) - return; /* already loaded */ - - CHECKPOINT_M( ssprintf( "BannerCache::LoadBanner: %s", sCachePath.c_str() ) ); - RageSurface *pImage = RageSurfaceUtils::LoadSurface( sCachePath ); - if( pImage == NULL ) - { - if( tries == 0 ) - { - /* The file doesn't exist. It's possible that the banner cache file is - * missing, so try to create it. Don't do this first, for efficiency. */ - LOG->Trace( "Cached banner load of '%s' ('%s') failed, trying to cache ...", sBannerPath.c_str(), sCachePath.c_str() ); - - /* Skip the up-to-date check; it failed to load, so it can't be up - * to date. */ - CacheBannerInternal( sBannerPath ); - continue; - } - else - { - LOG->Trace( "Cached banner load of '%s' ('%s') failed", sBannerPath.c_str(), sCachePath.c_str() ); - return; - } - } - - g_BannerPathToImage[sBannerPath] = pImage; - } -} - -void BannerCache::OutputStats() const -{ - int iTotalSize = 0; - FOREACHM_CONST( RString, RageSurface *, g_BannerPathToImage, it ) - { - const RageSurface *pImage = it->second; - const int iSize = pImage->pitch * pImage->h; - iTotalSize += iSize; - } - LOG->Info( "%i bytes of banners loaded", iTotalSize ); -} - -void BannerCache::UnloadAllBanners() -{ - FOREACHM( RString, RageSurface *, g_BannerPathToImage, it ) - delete it->second; - - g_BannerPathToImage.clear(); -} - -BannerCache::BannerCache() -{ - ReadFromDisk(); -} - -BannerCache::~BannerCache() -{ - UnloadAllBanners(); -} - -void BannerCache::ReadFromDisk() -{ - BannerData.ReadFile( BANNER_CACHE_INDEX ); // don't care if this fails -} - -struct BannerTexture: public RageTexture -{ - unsigned m_uTexHandle; - unsigned GetTexHandle() const { return m_uTexHandle; }; // accessed by RageDisplay - /* This is a reference to a pointer in g_BannerPathToImage. */ - RageSurface *&m_pImage; - int m_iWidth, m_iHeight; - - BannerTexture( RageTextureID id, RageSurface *&pImage, int iWidth, int iHeight ): - RageTexture(id), m_pImage(pImage), m_iWidth(iWidth), m_iHeight(iHeight) - { - Create(); - } - - ~BannerTexture() - { - Destroy(); - } - - void Create() - { - ASSERT( m_pImage != NULL ); - - /* The image is preprocessed; do as little work as possible. */ - - /* The source width is the width of the original file. */ - m_iSourceWidth = m_iWidth; - m_iSourceHeight = m_iHeight; - - /* The image width (within the texture) is always the entire texture. - * Only resize if the max texture size requires it; since these images - * are already scaled down, this shouldn't happen often. */ - if( m_pImage->w > DISPLAY->GetMaxTextureSize() || - m_pImage->h > DISPLAY->GetMaxTextureSize() ) - { - LOG->Warn( "Converted %s at runtime", GetID().filename.c_str() ); - int iWidth = min( m_pImage->w, DISPLAY->GetMaxTextureSize() ); - int iHeight = min( m_pImage->h, DISPLAY->GetMaxTextureSize() ); - RageSurfaceUtils::Zoom( m_pImage, iWidth, iHeight ); - } - - /* We did this when we cached it. */ - ASSERT( m_pImage->w == power_of_two(m_pImage->w) ); - ASSERT( m_pImage->h == power_of_two(m_pImage->h) ); - - m_iTextureWidth = m_iImageWidth = m_pImage->w; - m_iTextureHeight = m_iImageHeight = m_pImage->h; - - /* Find a supported texture format. If it happens to match the stored - * file, we won't have to do any conversion here, and that'll happen - * often with paletted images. */ - RagePixelFormat pf = m_pImage->format->BitsPerPixel == 8? RagePixelFormat_PAL: RagePixelFormat_RGB5A1; - if( !DISPLAY->SupportsTextureFormat(pf) ) - pf = RagePixelFormat_RGBA4; - - ASSERT( DISPLAY->SupportsTextureFormat(pf) ); - - ASSERT(m_pImage != NULL); - m_uTexHandle = DISPLAY->CreateTexture( pf, m_pImage, false ); - - CreateFrameRects(); - } - - void Destroy() - { - if( m_uTexHandle ) - DISPLAY->DeleteTexture( m_uTexHandle ); - m_uTexHandle = 0; - } - - void Reload() - { - Destroy(); - Create(); - } - - void Invalidate() - { - m_uTexHandle = 0; /* don't Destroy() */ - } -}; - -/* If a banner is cached, get its ID for use. */ -RageTextureID BannerCache::LoadCachedBanner( RString sBannerPath ) -{ - RageTextureID ID( GetBannerCachePath(sBannerPath) ); - - if( sBannerPath == "" ) - return ID; - - LOG->Trace( "BannerCache::LoadCachedBanner(%s): %s", sBannerPath.c_str(), ID.filename.c_str() ); - - /* Hack: make sure Banner::Load doesn't change our return value and end up - * reloading. */ - ID = Sprite::SongBannerTexture(ID); - - /* It's not in a texture. Do we have it loaded? */ - if( g_BannerPathToImage.find(sBannerPath) == g_BannerPathToImage.end() ) - { - /* Oops, the image is missing. Warn and continue. */ - LOG->Warn( "Banner cache for '%s' wasn't loaded", sBannerPath.c_str() ); - return ID; - } - - /* This is a reference to a pointer. BannerTexture's ctor may change it - * when converting; this way, the conversion will end up in the map so we - * only have to convert once. */ - RageSurface *&pImage = g_BannerPathToImage[sBannerPath]; - ASSERT( pImage != NULL ); - - int iSourceWidth = 0, iSourceHeight = 0; - BannerData.GetValue( sBannerPath, "Width", iSourceWidth ); - BannerData.GetValue( sBannerPath, "Height", iSourceHeight ); - if( iSourceWidth == 0 || iSourceHeight == 0 ) - { - LOG->UserLog( "Cache file", sBannerPath, "couldn't be loaded." ); - return ID; - } - - /* Is the banner already in a texture? */ - if( TEXTUREMAN->IsTextureRegistered(ID) ) - return ID; /* It's all set. */ - - LOG->Trace( "Loading banner texture %s; src %ix%i; image %ix%i", - ID.filename.c_str(), iSourceWidth, iSourceHeight, pImage->w, pImage->h ); - RageTexture *pTexture = new BannerTexture( ID, pImage, iSourceWidth, iSourceHeight ); - - ID.Policy = RageTextureID::TEX_VOLATILE; - TEXTUREMAN->RegisterTexture( ID, pTexture ); - TEXTUREMAN->UnloadTexture( pTexture ); - - return ID; -} - -static inline int closest( int num, int n1, int n2 ) -{ - if( abs(num - n1) > abs(num - n2) ) - return n2; - return n1; -} - -/* Create or update the banner cache file as necessary. If in preload mode, - * load the cache file, too. (This is done at startup.) */ -void BannerCache::CacheBanner( RString sBannerPath ) -{ - if( PREFSMAN->m_BannerCache != BNCACHE_LOW_RES_PRELOAD && - PREFSMAN->m_BannerCache != BNCACHE_LOW_RES_LOAD_ON_DEMAND ) - return; - - CHECKPOINT_M( sBannerPath ); - if( !DoesFileExist(sBannerPath) ) - return; - - const RString sCachePath = GetBannerCachePath(sBannerPath); - - /* Check the full file hash. If it's the loaded and identical, don't recache. */ - if( DoesFileExist(sCachePath) ) - { - bool bCacheUpToDate = PREFSMAN->m_bFastLoad; - if( !bCacheUpToDate ) - { - unsigned CurFullHash; - const unsigned FullHash = GetHashForFile( sBannerPath ); - if( BannerData.GetValue( sBannerPath, "FullHash", CurFullHash ) && CurFullHash == FullHash ) - bCacheUpToDate = true; - } - - if( bCacheUpToDate ) - { - /* It's identical. Just load it, if in preload. */ - if( PREFSMAN->m_BannerCache == BNCACHE_LOW_RES_PRELOAD ) - LoadBanner( sBannerPath ); - - return; - } - } - - /* The cache file doesn't exist, or is out of date. Cache it. This - * will also load the cache into memory if in PRELOAD. */ - CacheBannerInternal( sBannerPath ); -} - -void BannerCache::CacheBannerInternal( RString sBannerPath ) -{ - RString sError; - RageSurface *pImage = RageSurfaceUtils::LoadFile( sBannerPath, sError ); - if( pImage == NULL ) - { - LOG->UserLog( "Cache file", sBannerPath, "couldn't be loaded: %s", sError.c_str() ); - return; - } - - const int iSourceWidth = pImage->w, iSourceHeight = pImage->h; - - int iWidth = pImage->w / 2, iHeight = pImage->h / 2; -// int iWidth = pImage->w, iHeight = pImage->h; - - /* Round to the nearest power of two. This simplifies the actual texture load. */ - iWidth = closest( iWidth, power_of_two(iWidth), power_of_two(iWidth) / 2 ); - iHeight = closest( iHeight, power_of_two(iHeight), power_of_two(iHeight) / 2 ); - - /* Don't resize the image to less than 32 pixels in either dimension or the next - * power of two of the source (whichever is smaller); it's already very low res. */ - iWidth = max( iWidth, min(32, power_of_two(iSourceWidth)) ); - iHeight = max( iHeight, min(32, power_of_two(iSourceHeight)) ); - - //RageSurfaceUtils::ApplyHotPinkColorKey( pImage ); - - RageSurfaceUtils::Zoom( pImage, iWidth, iHeight ); - - /* - * When paletted banner cache is enabled, cached banners are paletted. Cached - * 32-bit banners take 1/16 as much memory, 16-bit banners take 1/8, and paletted - * banners take 1/4. - * - * When paletted banner cache is disabled, cached banners are stored in 16-bit - * RGBA. Cached 32-bit banners take 1/8 as much memory, cached 16-bit banners - * take 1/4, and cached paletted banners take 1/2. - * - * Paletted cache is disabled by default because palettization takes time, causing - * the initial cache run to take longer. Also, newer ATI hardware doesn't supported - * paletted textures, which would slow down runtime, because we have to depalettize - * on use. They'd still have the same memory benefits, though, since we only load - * one cached banner into a texture at once, and the speed hit may not matter on - * newer ATI cards. RGBA is safer, though. - */ - if( g_bPalettedBannerCache ) - { - if( pImage->fmt.BytesPerPixel != 1 ) - RageSurfaceUtils::Palettize( pImage ); - } - else - { - /* Dither to the final format. We use A1RGB5, since that's usually supported - * natively by both OpenGL and D3D. */ - RageSurface *dst = CreateSurface( pImage->w, pImage->h, 16, - 0x7C00, 0x03E0, 0x001F, 0x8000 ); - - /* OrderedDither is still faster than ErrorDiffusionDither, and - * these images are very small and only displayed briefly. */ - RageSurfaceUtils::OrderedDither( pImage, dst ); - delete pImage; - pImage = dst; - } - - const RString sCachePath = GetBannerCachePath(sBannerPath); - RageSurfaceUtils::SaveSurface( pImage, sCachePath ); - - /* If an old image is loaded, free it. */ - if( g_BannerPathToImage.find(sBannerPath) != g_BannerPathToImage.end() ) - { - RageSurface *oldimg = g_BannerPathToImage[sBannerPath]; - delete oldimg; - g_BannerPathToImage.erase(sBannerPath); - } - - if( PREFSMAN->m_BannerCache == BNCACHE_LOW_RES_PRELOAD ) - { - /* Keep it; we're just going to load it anyway. */ - g_BannerPathToImage[sBannerPath] = pImage; - } - else - delete pImage; - - /* Remember the original size. */ - BannerData.SetValue( sBannerPath, "Path", sCachePath ); - BannerData.SetValue( sBannerPath, "Width", iSourceWidth ); - BannerData.SetValue( sBannerPath, "Height", iSourceHeight ); - BannerData.SetValue( sBannerPath, "FullHash", GetHashForFile( sBannerPath ) ); - BannerData.WriteFile( BANNER_CACHE_INDEX ); -} - -/* - * (c) 2003 Glenn Maynard - * 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 "BannerCache.h" +#include "Foreach.h" +#include "RageDisplay.h" +#include "RageUtil.h" +#include "RageLog.h" +#include "RageSurface_Load.h" +#include "SongCacheIndex.h" +#include "Sprite.h" +#include "PrefsManager.h" +#include "RageDisplay.h" +#include "RageTexture.h" +#include "RageTextureManager.h" +#include "RageSurface.h" +#include "RageSurfaceUtils.h" +#include "RageSurfaceUtils_Palettize.h" +#include "RageSurfaceUtils_Dither.h" +#include "RageSurfaceUtils_Zoom.h" +#include "SpecialFiles.h" + +#include "Banner.h" + +static Preference g_bPalettedBannerCache( "PalettedBannerCache", false ); + +/* Neither a global or a file scope static can be used for this because + * the order of initialization of nonlocal objects is unspecified. */ +//const RString BANNER_CACHE_INDEX = SpecialFiles::CACHE_DIR + "banners.cache"; +#define BANNER_CACHE_INDEX (SpecialFiles::CACHE_DIR + "banners.cache") + +/* Call CacheBanner to cache a banner by path. If the banner is already + * cached, it'll be recreated. This is efficient if the banner hasn't changed, + * but we still only do this in TidyUpData for songs. + * + * Call LoadBanner to load a cached banner into main memory. This will call + * CacheBanner only if needed. This will not do a date/size check; call CacheBanner + * directly if you need that. + * + * Call LoadCachedBanner to load a banner into a texture and retrieve an ID + * for it. You can check if the banner was actually preloaded by calling + * TEXTUREMAN->IsTextureRegistered() on the ID; it might not be if the banner cache + * is missing or disabled. + * + * Note that each cache entries has two hashes. The cache path is based soley + * on the pathname; this way, loading the cache doesn't have to do a stat on every + * banner. The full hash includes the file size and date, and is used only by + * CacheBanner to avoid doing extra work. + */ + +BannerCache *BANNERCACHE; + + +static map g_BannerPathToImage; +static int g_iDemandRefcount = 0; + +RString BannerCache::GetBannerCachePath( RString sBannerPath ) +{ + return SongCacheIndex::GetCacheFilePath( "Banners", sBannerPath ); +} + +/* If in on-demand mode, load all cached banners. This must be fast, so + * cache files will not be created if they don't exist; that should be done + * by CacheBanner or LoadBanner on startup. */ +void BannerCache::Demand() +{ + ++g_iDemandRefcount; + if( g_iDemandRefcount > 1 ) + return; + + if( PREFSMAN->m_BannerCache != BNCACHE_LOW_RES_LOAD_ON_DEMAND ) + return; + + FOREACH_CONST_Child( &BannerData, p ) + { + RString sBannerPath = p->GetName(); + + if( g_BannerPathToImage.find(sBannerPath) != g_BannerPathToImage.end() ) + continue; /* already loaded */ + + const RString sCachePath = GetBannerCachePath(sBannerPath); + RageSurface *pImage = RageSurfaceUtils::LoadSurface( sCachePath ); + if( pImage == NULL ) + { + continue; /* doesn't exist */ + } + + g_BannerPathToImage[sBannerPath] = pImage; + } +} + +/* Release banners loaded on demand. */ +void BannerCache::Undemand() +{ + --g_iDemandRefcount; + if( g_iDemandRefcount != 0 ) + return; + + if( PREFSMAN->m_BannerCache != BNCACHE_LOW_RES_LOAD_ON_DEMAND ) + return; + + UnloadAllBanners(); +} + +/* If in a low-res banner mode, load a low-res banner into memory, creating + * the cache file if necessary. Unlike CacheBanner(), the original file will + * not be examined unless the cached banner doesn't exist, so the banner will + * not be updated if the original file changes, for efficiency. */ +void BannerCache::LoadBanner( RString sBannerPath ) +{ + if( sBannerPath == "" ) + return; // nothing to do + if( PREFSMAN->m_BannerCache != BNCACHE_LOW_RES_PRELOAD && + PREFSMAN->m_BannerCache != BNCACHE_LOW_RES_LOAD_ON_DEMAND ) + return; + + /* Load it. */ + const RString sCachePath = GetBannerCachePath(sBannerPath); + + for( int tries = 0; tries < 2; ++tries ) + { + if( g_BannerPathToImage.find(sBannerPath) != g_BannerPathToImage.end() ) + return; /* already loaded */ + + CHECKPOINT_M( ssprintf( "BannerCache::LoadBanner: %s", sCachePath.c_str() ) ); + RageSurface *pImage = RageSurfaceUtils::LoadSurface( sCachePath ); + if( pImage == NULL ) + { + if( tries == 0 ) + { + /* The file doesn't exist. It's possible that the banner cache file is + * missing, so try to create it. Don't do this first, for efficiency. */ + LOG->Trace( "Cached banner load of '%s' ('%s') failed, trying to cache ...", sBannerPath.c_str(), sCachePath.c_str() ); + + /* Skip the up-to-date check; it failed to load, so it can't be up + * to date. */ + CacheBannerInternal( sBannerPath ); + continue; + } + else + { + LOG->Trace( "Cached banner load of '%s' ('%s') failed", sBannerPath.c_str(), sCachePath.c_str() ); + return; + } + } + + g_BannerPathToImage[sBannerPath] = pImage; + } +} + +void BannerCache::OutputStats() const +{ + int iTotalSize = 0; + for (std::pair it : g_BannerPathToImage) + { + const RageSurface *pImage = it.second; + const int iSize = pImage->pitch * pImage->h; + iTotalSize += iSize; + } + LOG->Info( "%i bytes of banners loaded", iTotalSize ); +} + +void BannerCache::UnloadAllBanners() +{ + FOREACHM( RString, RageSurface *, g_BannerPathToImage, it ) + delete it->second; + + g_BannerPathToImage.clear(); +} + +BannerCache::BannerCache() +{ + ReadFromDisk(); +} + +BannerCache::~BannerCache() +{ + UnloadAllBanners(); +} + +void BannerCache::ReadFromDisk() +{ + BannerData.ReadFile( BANNER_CACHE_INDEX ); // don't care if this fails +} + +struct BannerTexture: public RageTexture +{ + unsigned m_uTexHandle; + unsigned GetTexHandle() const { return m_uTexHandle; }; // accessed by RageDisplay + /* This is a reference to a pointer in g_BannerPathToImage. */ + RageSurface *&m_pImage; + int m_iWidth, m_iHeight; + + BannerTexture( RageTextureID id, RageSurface *&pImage, int iWidth, int iHeight ): + RageTexture(id), m_pImage(pImage), m_iWidth(iWidth), m_iHeight(iHeight) + { + Create(); + } + + ~BannerTexture() + { + Destroy(); + } + + void Create() + { + ASSERT( m_pImage != NULL ); + + /* The image is preprocessed; do as little work as possible. */ + + /* The source width is the width of the original file. */ + m_iSourceWidth = m_iWidth; + m_iSourceHeight = m_iHeight; + + /* The image width (within the texture) is always the entire texture. + * Only resize if the max texture size requires it; since these images + * are already scaled down, this shouldn't happen often. */ + if( m_pImage->w > DISPLAY->GetMaxTextureSize() || + m_pImage->h > DISPLAY->GetMaxTextureSize() ) + { + LOG->Warn( "Converted %s at runtime", GetID().filename.c_str() ); + int iWidth = min( m_pImage->w, DISPLAY->GetMaxTextureSize() ); + int iHeight = min( m_pImage->h, DISPLAY->GetMaxTextureSize() ); + RageSurfaceUtils::Zoom( m_pImage, iWidth, iHeight ); + } + + /* We did this when we cached it. */ + ASSERT( m_pImage->w == power_of_two(m_pImage->w) ); + ASSERT( m_pImage->h == power_of_two(m_pImage->h) ); + + m_iTextureWidth = m_iImageWidth = m_pImage->w; + m_iTextureHeight = m_iImageHeight = m_pImage->h; + + /* Find a supported texture format. If it happens to match the stored + * file, we won't have to do any conversion here, and that'll happen + * often with paletted images. */ + RagePixelFormat pf = m_pImage->format->BitsPerPixel == 8? RagePixelFormat_PAL: RagePixelFormat_RGB5A1; + if( !DISPLAY->SupportsTextureFormat(pf) ) + pf = RagePixelFormat_RGBA4; + + ASSERT( DISPLAY->SupportsTextureFormat(pf) ); + + ASSERT(m_pImage != NULL); + m_uTexHandle = DISPLAY->CreateTexture( pf, m_pImage, false ); + + CreateFrameRects(); + } + + void Destroy() + { + if( m_uTexHandle ) + DISPLAY->DeleteTexture( m_uTexHandle ); + m_uTexHandle = 0; + } + + void Reload() + { + Destroy(); + Create(); + } + + void Invalidate() + { + m_uTexHandle = 0; /* don't Destroy() */ + } +}; + +/* If a banner is cached, get its ID for use. */ +RageTextureID BannerCache::LoadCachedBanner( RString sBannerPath ) +{ + RageTextureID ID( GetBannerCachePath(sBannerPath) ); + + if( sBannerPath == "" ) + return ID; + + LOG->Trace( "BannerCache::LoadCachedBanner(%s): %s", sBannerPath.c_str(), ID.filename.c_str() ); + + /* Hack: make sure Banner::Load doesn't change our return value and end up + * reloading. */ + ID = Sprite::SongBannerTexture(ID); + + /* It's not in a texture. Do we have it loaded? */ + if( g_BannerPathToImage.find(sBannerPath) == g_BannerPathToImage.end() ) + { + /* Oops, the image is missing. Warn and continue. */ + LOG->Warn( "Banner cache for '%s' wasn't loaded", sBannerPath.c_str() ); + return ID; + } + + /* This is a reference to a pointer. BannerTexture's ctor may change it + * when converting; this way, the conversion will end up in the map so we + * only have to convert once. */ + RageSurface *&pImage = g_BannerPathToImage[sBannerPath]; + ASSERT( pImage != NULL ); + + int iSourceWidth = 0, iSourceHeight = 0; + BannerData.GetValue( sBannerPath, "Width", iSourceWidth ); + BannerData.GetValue( sBannerPath, "Height", iSourceHeight ); + if( iSourceWidth == 0 || iSourceHeight == 0 ) + { + LOG->UserLog( "Cache file", sBannerPath, "couldn't be loaded." ); + return ID; + } + + /* Is the banner already in a texture? */ + if( TEXTUREMAN->IsTextureRegistered(ID) ) + return ID; /* It's all set. */ + + LOG->Trace( "Loading banner texture %s; src %ix%i; image %ix%i", + ID.filename.c_str(), iSourceWidth, iSourceHeight, pImage->w, pImage->h ); + RageTexture *pTexture = new BannerTexture( ID, pImage, iSourceWidth, iSourceHeight ); + + ID.Policy = RageTextureID::TEX_VOLATILE; + TEXTUREMAN->RegisterTexture( ID, pTexture ); + TEXTUREMAN->UnloadTexture( pTexture ); + + return ID; +} + +static inline int closest( int num, int n1, int n2 ) +{ + if( abs(num - n1) > abs(num - n2) ) + return n2; + return n1; +} + +/* Create or update the banner cache file as necessary. If in preload mode, + * load the cache file, too. (This is done at startup.) */ +void BannerCache::CacheBanner( RString sBannerPath ) +{ + if( PREFSMAN->m_BannerCache != BNCACHE_LOW_RES_PRELOAD && + PREFSMAN->m_BannerCache != BNCACHE_LOW_RES_LOAD_ON_DEMAND ) + return; + + CHECKPOINT_M( sBannerPath ); + if( !DoesFileExist(sBannerPath) ) + return; + + const RString sCachePath = GetBannerCachePath(sBannerPath); + + /* Check the full file hash. If it's the loaded and identical, don't recache. */ + if( DoesFileExist(sCachePath) ) + { + bool bCacheUpToDate = PREFSMAN->m_bFastLoad; + if( !bCacheUpToDate ) + { + unsigned CurFullHash; + const unsigned FullHash = GetHashForFile( sBannerPath ); + if( BannerData.GetValue( sBannerPath, "FullHash", CurFullHash ) && CurFullHash == FullHash ) + bCacheUpToDate = true; + } + + if( bCacheUpToDate ) + { + /* It's identical. Just load it, if in preload. */ + if( PREFSMAN->m_BannerCache == BNCACHE_LOW_RES_PRELOAD ) + LoadBanner( sBannerPath ); + + return; + } + } + + /* The cache file doesn't exist, or is out of date. Cache it. This + * will also load the cache into memory if in PRELOAD. */ + CacheBannerInternal( sBannerPath ); +} + +void BannerCache::CacheBannerInternal( RString sBannerPath ) +{ + RString sError; + RageSurface *pImage = RageSurfaceUtils::LoadFile( sBannerPath, sError ); + if( pImage == NULL ) + { + LOG->UserLog( "Cache file", sBannerPath, "couldn't be loaded: %s", sError.c_str() ); + return; + } + + const int iSourceWidth = pImage->w, iSourceHeight = pImage->h; + + int iWidth = pImage->w / 2, iHeight = pImage->h / 2; +// int iWidth = pImage->w, iHeight = pImage->h; + + /* Round to the nearest power of two. This simplifies the actual texture load. */ + iWidth = closest( iWidth, power_of_two(iWidth), power_of_two(iWidth) / 2 ); + iHeight = closest( iHeight, power_of_two(iHeight), power_of_two(iHeight) / 2 ); + + /* Don't resize the image to less than 32 pixels in either dimension or the next + * power of two of the source (whichever is smaller); it's already very low res. */ + iWidth = max( iWidth, min(32, power_of_two(iSourceWidth)) ); + iHeight = max( iHeight, min(32, power_of_two(iSourceHeight)) ); + + //RageSurfaceUtils::ApplyHotPinkColorKey( pImage ); + + RageSurfaceUtils::Zoom( pImage, iWidth, iHeight ); + + /* + * When paletted banner cache is enabled, cached banners are paletted. Cached + * 32-bit banners take 1/16 as much memory, 16-bit banners take 1/8, and paletted + * banners take 1/4. + * + * When paletted banner cache is disabled, cached banners are stored in 16-bit + * RGBA. Cached 32-bit banners take 1/8 as much memory, cached 16-bit banners + * take 1/4, and cached paletted banners take 1/2. + * + * Paletted cache is disabled by default because palettization takes time, causing + * the initial cache run to take longer. Also, newer ATI hardware doesn't supported + * paletted textures, which would slow down runtime, because we have to depalettize + * on use. They'd still have the same memory benefits, though, since we only load + * one cached banner into a texture at once, and the speed hit may not matter on + * newer ATI cards. RGBA is safer, though. + */ + if( g_bPalettedBannerCache ) + { + if( pImage->fmt.BytesPerPixel != 1 ) + RageSurfaceUtils::Palettize( pImage ); + } + else + { + /* Dither to the final format. We use A1RGB5, since that's usually supported + * natively by both OpenGL and D3D. */ + RageSurface *dst = CreateSurface( pImage->w, pImage->h, 16, + 0x7C00, 0x03E0, 0x001F, 0x8000 ); + + /* OrderedDither is still faster than ErrorDiffusionDither, and + * these images are very small and only displayed briefly. */ + RageSurfaceUtils::OrderedDither( pImage, dst ); + delete pImage; + pImage = dst; + } + + const RString sCachePath = GetBannerCachePath(sBannerPath); + RageSurfaceUtils::SaveSurface( pImage, sCachePath ); + + /* If an old image is loaded, free it. */ + if( g_BannerPathToImage.find(sBannerPath) != g_BannerPathToImage.end() ) + { + RageSurface *oldimg = g_BannerPathToImage[sBannerPath]; + delete oldimg; + g_BannerPathToImage.erase(sBannerPath); + } + + if( PREFSMAN->m_BannerCache == BNCACHE_LOW_RES_PRELOAD ) + { + /* Keep it; we're just going to load it anyway. */ + g_BannerPathToImage[sBannerPath] = pImage; + } + else + delete pImage; + + /* Remember the original size. */ + BannerData.SetValue( sBannerPath, "Path", sCachePath ); + BannerData.SetValue( sBannerPath, "Width", iSourceWidth ); + BannerData.SetValue( sBannerPath, "Height", iSourceHeight ); + BannerData.SetValue( sBannerPath, "FullHash", GetHashForFile( sBannerPath ) ); + BannerData.WriteFile( BANNER_CACHE_INDEX ); +} + +/* + * (c) 2003 Glenn Maynard + * 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. + */ From b6cbb511f8c22a2cb07f2f1c89e8604577c811f9 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 15:12:47 -0400 Subject: [PATCH 030/281] So much to convert... this may have worked better with auto. Still, I'd rather be explicit when possible. --- src/Profile.cpp | 95 ++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/src/Profile.cpp b/src/Profile.cpp index e2a6c8720a..3b2e3357a3 100644 --- a/src/Profile.cpp +++ b/src/Profile.cpp @@ -59,9 +59,9 @@ const unsigned int DEFAULT_WEIGHT_POUNDS = 120; int Profile::HighScoresForASong::GetNumTimesPlayed() const { int iCount = 0; - FOREACHM_CONST( StepsID, HighScoresForASteps, m_StepsHighScores, i ) + for (std::pair const &i : m_StepsHighScores) { - iCount += i->second.hsl.GetNumTimesPlayed(); + iCount += i.second.hsl.GetNumTimesPlayed(); } return iCount; } @@ -69,9 +69,9 @@ int Profile::HighScoresForASong::GetNumTimesPlayed() const int Profile::HighScoresForACourse::GetNumTimesPlayed() const { int iCount = 0; - FOREACHM_CONST( TrailID, HighScoresForATrail, m_TrailHighScores, i ) + for (std::pair const &i : m_TrailHighScores) { - iCount += i->second.hsl.GetNumTimesPlayed(); + iCount += i.second.hsl.GetNumTimesPlayed(); } return iCount; } @@ -339,10 +339,9 @@ float Profile::GetSongsActual( StepsType st, Difficulty dc ) const float fTotalPercents = 0; // add steps high scores - FOREACHM_CONST( SongID, HighScoresForASong, m_SongHighScores, i ) + for (std::pair const &id : m_SongHighScores) { - const SongID &id = i->first; - Song* pSong = id.ToSong(); + Song* pSong = id.first.ToSong(); CHECKPOINT_M( ssprintf("Profile::GetSongsActual: %p", pSong) ); @@ -355,11 +354,11 @@ float Profile::GetSongsActual( StepsType st, Difficulty dc ) const continue; // skip CHECKPOINT_M( ssprintf("Profile::GetSongsActual: song %s", pSong->GetSongDir().c_str()) ); - const HighScoresForASong &hsfas = i->second; + const HighScoresForASong &hsfas = id.second; - FOREACHM_CONST( StepsID, HighScoresForASteps, hsfas.m_StepsHighScores, j ) + for (std::pair const &j : hsfas.m_StepsHighScores) { - const StepsID &sid = j->first; + const StepsID &sid = j.first; Steps* pSteps = sid.ToSteps( pSong, true ); CHECKPOINT_M( ssprintf("Profile::GetSongsActual: song %p, steps %p", pSong, pSteps) ); @@ -376,7 +375,7 @@ float Profile::GetSongsActual( StepsType st, Difficulty dc ) const continue; // skip CHECKPOINT; - const HighScoresForASteps& h = j->second; + const HighScoresForASteps& h = j.second; const HighScoreList& hsl = h.hsl; fTotalPercents += hsl.GetTopScore().GetPercentDP(); @@ -481,9 +480,9 @@ int Profile::GetSongNumTimesPlayed( const SongID& songID ) const return 0; int iTotalNumTimesPlayed = 0; - FOREACHM_CONST( StepsID, HighScoresForASteps, hsSong->m_StepsHighScores, j ) + for (std::pair const &j : hsSong->m_StepsHighScores) { - const HighScoresForASteps &hsSteps = j->second; + const HighScoresForASteps &hsSteps = j.second; iTotalNumTimesPlayed += hsSteps.hsl.GetNumTimesPlayed(); } @@ -527,12 +526,12 @@ Song *Profile::GetMostPopularSong() const { int iMaxNumTimesPlayed = 0; SongID id; - FOREACHM_CONST( SongID, HighScoresForASong, m_SongHighScores, i ) + for (std::pair const &i : m_SongHighScores) { - int iNumTimesPlayed = i->second.GetNumTimesPlayed(); + int iNumTimesPlayed = i.second.GetNumTimesPlayed(); if( iNumTimesPlayed > iMaxNumTimesPlayed ) { - id = i->first; + id = i.first; iMaxNumTimesPlayed = iNumTimesPlayed; } } @@ -544,12 +543,12 @@ Course *Profile::GetMostPopularCourse() const { int iMaxNumTimesPlayed = 0; CourseID id; - FOREACHM_CONST( CourseID, HighScoresForACourse, m_CourseHighScores, i ) + for (std::pair const &i : m_CourseHighScores) { - int iNumTimesPlayed = i->second.GetNumTimesPlayed(); + int iNumTimesPlayed = i.second.GetNumTimesPlayed(); if( iNumTimesPlayed > iMaxNumTimesPlayed ) { - id = i->first; + id = i.first; iMaxNumTimesPlayed = iNumTimesPlayed; } } @@ -598,9 +597,9 @@ DateTime Profile::GetSongLastPlayedDateTime( const Song* pSong ) const ASSERT( !iter->second.m_StepsHighScores.empty() ); DateTime dtLatest; // starts out zeroed - FOREACHM_CONST( StepsID, HighScoresForASteps, iter->second.m_StepsHighScores, i ) + for (std::pair const &i : iter->second.m_StepsHighScores) { - const HighScoreList &hsl = i->second.hsl; + const HighScoreList &hsl = i.second.hsl; if( hsl.GetNumTimesPlayed() == 0 ) continue; if( dtLatest < hsl.GetLastPlayed() ) @@ -651,13 +650,13 @@ void Profile::GetGrades( const Song* pSong, StepsType st, int iCounts[NUM_Grade] FOREACH_ENUM( Grade,g) { - FOREACHM_CONST( StepsID, HighScoresForASteps, hsSong->m_StepsHighScores, it ) + for (std::pair const &it : hsSong->m_StepsHighScores) { - const StepsID &id = it->first; + const StepsID &id = it.first; if( !id.MatchesStepsType(st) ) continue; - const HighScoresForASteps &hsSteps = it->second; + const HighScoresForASteps &hsSteps = it.second; if( hsSteps.hsl.GetTopScore().GetGrade() == g ) iCounts[g]++; } @@ -704,9 +703,9 @@ int Profile::GetCourseNumTimesPlayed( const CourseID &courseID ) const return 0; int iTotalNumTimesPlayed = 0; - FOREACHM_CONST( TrailID, HighScoresForATrail, hsCourse->m_TrailHighScores, j ) + for (std::pair const &j : hsCourse->m_TrailHighScores) { - const HighScoresForATrail &hsTrail = j->second; + const HighScoresForATrail &hsTrail = j.second; iTotalNumTimesPlayed += hsTrail.hsl.GetNumTimesPlayed(); } @@ -724,9 +723,9 @@ DateTime Profile::GetCourseLastPlayedDateTime( const Course* pCourse ) const ASSERT( !iter->second.m_TrailHighScores.empty() ); DateTime dtLatest; // starts out zeroed - FOREACHM_CONST( TrailID, HighScoresForATrail, iter->second.m_TrailHighScores, i ) + for (std::pair const &i : iter->second.m_TrailHighScores) { - const HighScoreList &hsl = i->second.hsl; + const HighScoreList &hsl = i.second.hsl; if( hsl.GetNumTimesPlayed() == 0 ) continue; if( dtLatest < hsl.GetLastPlayed() ) @@ -1067,8 +1066,8 @@ XNode* Profile::SaveGeneralDataCreateNode() const { XNode* pDefaultModifiers = pGeneralDataNode->AppendChild("DefaultModifiers"); - FOREACHM_CONST( RString, RString, m_sDefaultModifiers, it ) - pDefaultModifiers->AppendChild( it->first, it->second ); + for (std::pair it : m_sDefaultModifiers) + pDefaultModifiers->AppendChild( it.first, it.second ); } { @@ -1098,10 +1097,10 @@ XNode* Profile::SaveGeneralDataCreateNode() const { XNode* pNumSongsPlayedByStyle = pGeneralDataNode->AppendChild("NumSongsPlayedByStyle"); - FOREACHM_CONST( StyleID, int, m_iNumSongsPlayedByStyle, iter ) + for (std::pair const iter : m_iNumSongsPlayedByStyle) { - const StyleID &s = iter->first; - int iNumPlays = iter->second; + const StyleID &s = iter.first; + int iNumPlays = iter.second; XNode *pStyleNode = s.CreateNode(); pStyleNode->AppendAttr(XNode::TEXT_ATTRIBUTE, iNumPlays ); @@ -1382,10 +1381,10 @@ XNode* Profile::SaveSongScoresCreateNode() const XNode* pNode = new XNode( "SongScores" ); - FOREACHM_CONST( SongID, HighScoresForASong, m_SongHighScores, i ) + for (std::pair const &i : m_SongHighScores) { - const SongID &songID = i->first; - const HighScoresForASong &hsSong = i->second; + const SongID &songID = i.first; + const HighScoresForASong &hsSong = i.second; // skip songs that have never been played if( pProfile->GetSongNumTimesPlayed(songID) == 0 ) @@ -1395,12 +1394,12 @@ XNode* Profile::SaveSongScoresCreateNode() const int jCheck2 = hsSong.m_StepsHighScores.size(); int jCheck1 = 0; - FOREACHM_CONST( StepsID, HighScoresForASteps, hsSong.m_StepsHighScores, j ) + for (std::pair const &j :hsSong.m_StepsHighScores) { jCheck1++; ASSERT( jCheck1 <= jCheck2 ); - const StepsID &stepsID = j->first; - const HighScoresForASteps &hsSteps = j->second; + const StepsID &stepsID = j.first; + const HighScoresForASteps &hsSteps = j.second; const HighScoreList &hsl = hsSteps.hsl; @@ -1463,10 +1462,10 @@ XNode* Profile::SaveCourseScoresCreateNode() const XNode* pNode = new XNode( "CourseScores" ); - FOREACHM_CONST( CourseID, HighScoresForACourse, m_CourseHighScores, i ) + for (std::pair const &i : m_CourseHighScores) { - const CourseID &courseID = i->first; - const HighScoresForACourse &hsCourse = i->second; + const CourseID &courseID = i.first; + const HighScoresForACourse &hsCourse = i.second; // skip courses that have never been played if( pProfile->GetCourseNumTimesPlayed(courseID) == 0 ) @@ -1474,10 +1473,10 @@ XNode* Profile::SaveCourseScoresCreateNode() const XNode* pCourseNode = pNode->AppendChild( courseID.CreateNode() ); - FOREACHM_CONST( TrailID, HighScoresForATrail, hsCourse.m_TrailHighScores, j ) + for (std::pair const &j : hsCourse.m_TrailHighScores) { - const TrailID &trailID = j->first; - const HighScoresForATrail &hsTrail = j->second; + const TrailID &trailID = j.first; + const HighScoresForATrail &hsTrail = j.second; const HighScoreList &hsl = hsTrail.hsl; @@ -1721,11 +1720,11 @@ XNode* Profile::SaveCalorieDataCreateNode() const XNode* pNode = new XNode( "CalorieData" ); - FOREACHM_CONST( DateTime, Calories, m_mapDayToCaloriesBurned, i ) + for (std::pair const &i : m_mapDayToCaloriesBurned) { - XNode* pCaloriesBurned = pNode->AppendChild( "CaloriesBurned", i->second.fCals ); + XNode* pCaloriesBurned = pNode->AppendChild( "CaloriesBurned", i.second.fCals ); - pCaloriesBurned->AppendAttr( "Date", i->first.GetString() ); + pCaloriesBurned->AppendAttr( "Date", i.first.GetString() ); } return pNode; From 0e91e5b32d559b2933ea9b77ba3d3a3edf4e020b Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 15:14:08 -0400 Subject: [PATCH 031/281] Another map to for each WHITE SPACE. --- src/InputFilter.cpp | 974 ++++++++++++++++++++++---------------------- 1 file changed, 487 insertions(+), 487 deletions(-) diff --git a/src/InputFilter.cpp b/src/InputFilter.cpp index bec28d0dcb..2149bfb005 100644 --- a/src/InputFilter.cpp +++ b/src/InputFilter.cpp @@ -1,487 +1,487 @@ -#include "global.h" -#include "InputFilter.h" -#include "RageLog.h" -#include "RageInput.h" -#include "RageUtil.h" -#include "RageThreads.h" -#include "Preference.h" -#include "Foreach.h" -// for mouse stuff: -aj -#include "PrefsManager.h" -#include "ScreenDimensions.h" - -#include -struct ButtonState -{ - ButtonState(); - bool m_BeingHeld; // actual current state - bool m_bLastReportedHeld; // last state reported by Update() - RString m_sComment; - float m_fSecsHeld; - DeviceInput m_DeviceInput; - - // Timestamp of m_BeingHeld changing. - RageTimer m_BeingHeldTime; - - // The time that we actually reported the last event (used for debouncing). - RageTimer m_LastReportTime; - - // The timestamp of the last reported change. Unlike m_BeingHeldTime, this - // value is debounced along with the input state. (This is the same as - // m_fSecsHeld, except this isn't affected by Update scaling.) - RageTimer m_LastInputTime; -}; - -struct DeviceButtonPair -{ - InputDevice device; - DeviceButton button; - DeviceButtonPair( InputDevice d, DeviceButton b ): device(d), button(b){ } - bool operator<( const DeviceButtonPair &other ) const - { - if( device != other.device ) - return device < other.device; - return button < other.button; - } -}; - -namespace -{ - /* Maintain a set of all interesting buttons: buttons which are being held - * down, or which were held down and need a RELEASE event. We use this to - * optimize InputFilter::Update, so we don't have to process every button - * we know about when most of them aren't in use. This set is protected - * by queuemutex. */ - typedef map ButtonStateMap; - ButtonStateMap g_ButtonStates; - ButtonState &GetButtonState( const DeviceInput &di ) - { - DeviceButtonPair db(di.device, di.button); - ButtonState &bs = g_ButtonStates[db]; - bs.m_DeviceInput.button = di.button; - bs.m_DeviceInput.device = di.device; - return bs; - } - - DeviceInputList g_CurrentState; - set g_DisableRepeat; -} - -/* Some input devices require debouncing. Do this on both press and release. - * After reporting a change in state, don't report another for the debounce - * period. If a button is reported pressed, report it. If the button is - * immediately reported released, wait a period before reporting it; if the - * button is repressed during that time, the release is never reported. - * The detail is important: if a button is pressed for 1ms and released, we must - * always report it, even if the debounce period is 10ms, since it might be a - * coin counter with a very short signal. The only time we discard events is if - * a button is pressed, released and then pressed again quickly. - * - * This delay in events is ordinarily not noticable, because we report initial - * presses and releases immediately. However, if a real press is ever delayed, - * this won't cause timing problems, because the event timestamp is preserved. */ -static Preference g_fInputDebounceTime( "InputDebounceTime", 0 ); - -InputFilter* INPUTFILTER = NULL; // global and accessable from anywhere in our program - -static const float TIME_BEFORE_REPEATS = 0.375f; - -static const float REPEATS_PER_SEC = 8; - -static float g_fTimeBeforeRepeats, g_fTimeBetweenRepeats; - - -InputFilter::InputFilter() -{ - queuemutex = new RageMutex("InputFilter"); - - Reset(); - ResetRepeatRate(); - - m_MouseCoords.fX = 0; - m_MouseCoords.fY = 0; - m_MouseCoords.fZ = 0; - - // Register with Lua. - { - Lua *L = LUA->Get(); - lua_pushstring( L, "INPUTFILTER" ); - this->PushSelf( L ); - lua_settable( L, LUA_GLOBALSINDEX ); - LUA->Release( L ); - } -} - -InputFilter::~InputFilter() -{ - delete queuemutex; - g_ButtonStates.clear(); - // Unregister with Lua. - LUA->UnsetGlobal( "INPUTFILTER" ); -} - -void InputFilter::Reset() -{ - FOREACH_InputDevice( i ) - ResetDevice( InputDevice(i) ); -} - -void InputFilter::SetRepeatRate( float fRepeatRate ) -{ - g_fTimeBetweenRepeats = 1/fRepeatRate; -} - -void InputFilter::SetRepeatDelay( float fDelay ) -{ - g_fTimeBeforeRepeats = fDelay; -} - -void InputFilter::ResetRepeatRate() -{ - SetRepeatRate( REPEATS_PER_SEC ); - SetRepeatDelay( TIME_BEFORE_REPEATS ); -} - -ButtonState::ButtonState(): - m_BeingHeldTime(RageZeroTimer), - m_LastReportTime(RageZeroTimer) -{ - m_BeingHeld = false; - m_bLastReportedHeld = false; - m_fSecsHeld = 0; -} - -void InputFilter::ButtonPressed( const DeviceInput &di ) -{ - LockMut(*queuemutex); - - if( di.ts.IsZero() ) - LOG->Warn( "InputFilter::ButtonPressed: zero timestamp is invalid" ); - - ASSERT_M( di.device < NUM_InputDevice, ssprintf("Invalid device %i", di.device) ); - ASSERT_M( di.button < NUM_DeviceButton, ssprintf("Invalid button %i", di.button) ); - - ButtonState &bs = GetButtonState( di ); - - // Flush any delayed input, like Update() (in case Update() isn't being called). - RageTimer now; - CheckButtonChange( bs, di, now ); - - bs.m_DeviceInput = di; - - bool Down = di.bDown; - if( bs.m_BeingHeld != Down ) - { - bs.m_BeingHeld = Down; - bs.m_BeingHeldTime = di.ts; - } - - // Try to report presses immediately. - MakeButtonStateList( g_CurrentState ); - CheckButtonChange( bs, di, now ); -} - -void InputFilter::SetButtonComment( const DeviceInput &di, const RString &sComment ) -{ - LockMut(*queuemutex); - ButtonState &bs = GetButtonState( di ); - bs.m_sComment = sComment; -} - -/** @brief Release all buttons on the given device. */ -void InputFilter::ResetDevice( InputDevice device ) -{ - LockMut(*queuemutex); - RageTimer now; - - const ButtonStateMap ButtonStates( g_ButtonStates ); - FOREACHM_CONST( DeviceButtonPair, ButtonState, ButtonStates, b ) - { - const DeviceButtonPair &db = b->first; - if( db.device == device ) - ButtonPressed( DeviceInput(device, db.button, 0, now) ); - } -} - -/** @brief Check for reportable presses. */ -void InputFilter::CheckButtonChange( ButtonState &bs, DeviceInput di, const RageTimer &now ) -{ - if( bs.m_BeingHeld == bs.m_bLastReportedHeld ) - return; - - /* If the last IET_FIRST_PRESS or IET_RELEASE event was sent too recently, - * wait a while before sending it. */ - if( now - bs.m_LastReportTime < g_fInputDebounceTime ) - return; - - bs.m_LastReportTime = now; - bs.m_bLastReportedHeld = bs.m_BeingHeld; - bs.m_fSecsHeld = 0; - bs.m_LastInputTime = bs.m_BeingHeldTime; - - di.ts = bs.m_BeingHeldTime; - if( !bs.m_bLastReportedHeld ) - di.level = 0; - - MakeButtonStateList( g_CurrentState ); - ReportButtonChange( di, bs.m_bLastReportedHeld? IET_FIRST_PRESS:IET_RELEASE ); - - if( !bs.m_bLastReportedHeld ) - g_DisableRepeat.erase( di ); -} - -void InputFilter::ReportButtonChange( const DeviceInput &di, InputEventType t ) -{ - queue.push_back( InputEvent() ); - InputEvent &ie = queue.back(); - ie.type = t; - ie.di = di; - - /* Include a list of all buttons that were pressed at the time of this event. - * We can create this efficiently using g_ButtonStates. Use a vector and not - * a map, for efficiency; most code will not use this information. Iterating - * over g_ButtonStates will be in DeviceInput order, so users can binary - * search this list (eg. std::lower_bound). */ - ie.m_ButtonState = g_CurrentState; -} - -void InputFilter::MakeButtonStateList( vector &aInputOut ) const -{ - aInputOut.clear(); - aInputOut.reserve( g_ButtonStates.size() ); - for( ButtonStateMap::const_iterator it = g_ButtonStates.begin(); it != g_ButtonStates.end(); ++it ) - { - const ButtonState &bs = it->second; - aInputOut.push_back( bs.m_DeviceInput ); - aInputOut.back().ts = bs.m_LastInputTime; - aInputOut.back().bDown = bs.m_bLastReportedHeld; - } -} - -void InputFilter::Update( float fDeltaTime ) -{ - RageTimer now; - - INPUTMAN->Update(); - - /* Make sure that nothing gets inserted while we do this, to prevent things - * like "key pressed, key release, key repeat". */ - LockMut(*queuemutex); - - DeviceInput di( InputDevice_Invalid, DeviceButton_Invalid, 1.0f, now ); - - MakeButtonStateList( g_CurrentState ); - - vector ButtonsToErase; - - FOREACHM( DeviceButtonPair, ButtonState, g_ButtonStates, b ) - { - di.device = b->first.device; - di.button = b->first.button; - ButtonState &bs = b->second; - - // Generate IET_FIRST_PRESS and IET_RELEASE events that were delayed. - CheckButtonChange( bs, di, now ); - - // Generate IET_REPEAT events. - if( !bs.m_bLastReportedHeld ) - { - // If the key isn't pressed, and hasn't been pressed for a while - // (so debouncing isn't interested in it), purge the entry. - if( now - bs.m_LastReportTime > g_fInputDebounceTime && - bs.m_DeviceInput.level == 0.0f ) - ButtonsToErase.push_back( b ); - continue; - } - - // If repeats are disabled for this button, skip. - if( g_DisableRepeat.find(di) != g_DisableRepeat.end() ) - continue; - - const float fOldHoldTime = bs.m_fSecsHeld; - bs.m_fSecsHeld += fDeltaTime; - const float fNewHoldTime = bs.m_fSecsHeld; - - if( fNewHoldTime <= g_fTimeBeforeRepeats ) - continue; - - float fRepeatTime; - if( fOldHoldTime < g_fTimeBeforeRepeats ) - { - fRepeatTime = g_fTimeBeforeRepeats; - } - else - { - float fAdjustedOldHoldTime = fOldHoldTime - g_fTimeBeforeRepeats; - float fAdjustedNewHoldTime = fNewHoldTime - g_fTimeBeforeRepeats; - if( int(fAdjustedOldHoldTime/g_fTimeBetweenRepeats) == int(fAdjustedNewHoldTime/g_fTimeBetweenRepeats) ) - continue; - fRepeatTime = ftruncf( fNewHoldTime, g_fTimeBetweenRepeats ); - } - - /* Set the timestamp to the exact time of the repeat. This way, as long - * as tab/` aren't being used, the timestamp will always increase steadily - * during repeats. */ - di.ts = bs.m_LastInputTime + fRepeatTime; - - ReportButtonChange( di, IET_REPEAT ); - } - - FOREACH( ButtonStateMap::iterator, ButtonsToErase, it ) - g_ButtonStates.erase( *it ); -} - -template -const T *FindItemBinarySearch( IT begin, IT end, const T &i ) -{ - IT it = lower_bound( begin, end, i ); - if( it == end || *it != i ) - return NULL; - - return &*it; -} - -bool InputFilter::IsBeingPressed( const DeviceInput &di, const DeviceInputList *pButtonState ) const -{ - LockMut(*queuemutex); - if( pButtonState == NULL ) - pButtonState = &g_CurrentState; - const DeviceInput *pDI = FindItemBinarySearch( pButtonState->begin(), pButtonState->end(), di ); - return pDI != NULL && pDI->bDown; -} - -float InputFilter::GetSecsHeld( const DeviceInput &di, const DeviceInputList *pButtonState ) const -{ - LockMut(*queuemutex); - if( pButtonState == NULL ) - pButtonState = &g_CurrentState; - const DeviceInput *pDI = FindItemBinarySearch( pButtonState->begin(), pButtonState->end(), di ); - if( pDI == NULL ) - return 0; - return pDI->ts.Ago(); -} - -float InputFilter::GetLevel( const DeviceInput &di, const DeviceInputList *pButtonState ) const -{ - LockMut(*queuemutex); - if( pButtonState == NULL ) - pButtonState = &g_CurrentState; - const DeviceInput *pDI = FindItemBinarySearch( pButtonState->begin(), pButtonState->end(), di ); - if( pDI == NULL ) - return 0.0f; - return pDI->level; -} - -RString InputFilter::GetButtonComment( const DeviceInput &di ) const -{ - LockMut(*queuemutex); - return GetButtonState( di ).m_sComment; -} - -void InputFilter::ResetKeyRepeat( const DeviceInput &di ) -{ - LockMut(*queuemutex); - GetButtonState( di ).m_fSecsHeld = 0; -} - -/** @brief Stop repeating the specified key until released. */ -void InputFilter::RepeatStopKey( const DeviceInput &di ) -{ - LockMut(*queuemutex); - - // If the button is up, do nothing. - ButtonState &bs = GetButtonState( di ); - if( !bs.m_bLastReportedHeld ) - return; - - g_DisableRepeat.insert( di ); -} - -void InputFilter::GetInputEvents( vector &array ) -{ - array.clear(); - LockMut(*queuemutex); - array.swap( queue ); -} - -void InputFilter::GetPressedButtons( vector &array ) const -{ - LockMut(*queuemutex); - array = g_CurrentState; -} - -void InputFilter::UpdateCursorLocation(float _fX, float _fY) -{ - m_MouseCoords.fX = _fX; - m_MouseCoords.fY = _fY; -} - -void InputFilter::UpdateMouseWheel(float _fZ) -{ - m_MouseCoords.fZ = _fZ; -} - -// lua start -#include "LuaBinding.h" - -/** @brief Allow Lua to have access to InputFilter. */ -class LunaInputFilter: public Luna -{ -public: - // todo: Should the input be locked to the theme's width/height instead of - // the window's width/height? -aj - static int GetMouseX( T* p, lua_State *L ){ - float fX = p->GetCursorX(); - // Scale input to the theme's dimensions - fX = SCALE( fX, 0, (PREFSMAN->m_iDisplayHeight * PREFSMAN->m_fDisplayAspectRatio), SCREEN_LEFT, SCREEN_RIGHT ); - lua_pushnumber( L, fX ); - return 1; - } - static int GetMouseY( T* p, lua_State *L ){ - float fY = p->GetCursorY(); - // Scale input to the theme's dimensions - fY = SCALE( fY, 0, PREFSMAN->m_iDisplayHeight, SCREEN_TOP, SCREEN_BOTTOM ); - lua_pushnumber( L, fY ); - return 1; - } - static int GetMouseWheel( T* p, lua_State *L ){ - float fZ = p->GetMouseWheel(); - lua_pushnumber( L, fZ ); - return 1; - } - - LunaInputFilter() - { - ADD_METHOD( GetMouseX ); - ADD_METHOD( GetMouseY ); - ADD_METHOD( GetMouseWheel ); - } -}; - -LUA_REGISTER_CLASS( InputFilter ) -// 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 "InputFilter.h" +#include "RageLog.h" +#include "RageInput.h" +#include "RageUtil.h" +#include "RageThreads.h" +#include "Preference.h" +#include "Foreach.h" +// for mouse stuff: -aj +#include "PrefsManager.h" +#include "ScreenDimensions.h" + +#include +struct ButtonState +{ + ButtonState(); + bool m_BeingHeld; // actual current state + bool m_bLastReportedHeld; // last state reported by Update() + RString m_sComment; + float m_fSecsHeld; + DeviceInput m_DeviceInput; + + // Timestamp of m_BeingHeld changing. + RageTimer m_BeingHeldTime; + + // The time that we actually reported the last event (used for debouncing). + RageTimer m_LastReportTime; + + // The timestamp of the last reported change. Unlike m_BeingHeldTime, this + // value is debounced along with the input state. (This is the same as + // m_fSecsHeld, except this isn't affected by Update scaling.) + RageTimer m_LastInputTime; +}; + +struct DeviceButtonPair +{ + InputDevice device; + DeviceButton button; + DeviceButtonPair( InputDevice d, DeviceButton b ): device(d), button(b){ } + bool operator<( const DeviceButtonPair &other ) const + { + if( device != other.device ) + return device < other.device; + return button < other.button; + } +}; + +namespace +{ + /* Maintain a set of all interesting buttons: buttons which are being held + * down, or which were held down and need a RELEASE event. We use this to + * optimize InputFilter::Update, so we don't have to process every button + * we know about when most of them aren't in use. This set is protected + * by queuemutex. */ + typedef map ButtonStateMap; + ButtonStateMap g_ButtonStates; + ButtonState &GetButtonState( const DeviceInput &di ) + { + DeviceButtonPair db(di.device, di.button); + ButtonState &bs = g_ButtonStates[db]; + bs.m_DeviceInput.button = di.button; + bs.m_DeviceInput.device = di.device; + return bs; + } + + DeviceInputList g_CurrentState; + set g_DisableRepeat; +} + +/* Some input devices require debouncing. Do this on both press and release. + * After reporting a change in state, don't report another for the debounce + * period. If a button is reported pressed, report it. If the button is + * immediately reported released, wait a period before reporting it; if the + * button is repressed during that time, the release is never reported. + * The detail is important: if a button is pressed for 1ms and released, we must + * always report it, even if the debounce period is 10ms, since it might be a + * coin counter with a very short signal. The only time we discard events is if + * a button is pressed, released and then pressed again quickly. + * + * This delay in events is ordinarily not noticable, because we report initial + * presses and releases immediately. However, if a real press is ever delayed, + * this won't cause timing problems, because the event timestamp is preserved. */ +static Preference g_fInputDebounceTime( "InputDebounceTime", 0 ); + +InputFilter* INPUTFILTER = NULL; // global and accessable from anywhere in our program + +static const float TIME_BEFORE_REPEATS = 0.375f; + +static const float REPEATS_PER_SEC = 8; + +static float g_fTimeBeforeRepeats, g_fTimeBetweenRepeats; + + +InputFilter::InputFilter() +{ + queuemutex = new RageMutex("InputFilter"); + + Reset(); + ResetRepeatRate(); + + m_MouseCoords.fX = 0; + m_MouseCoords.fY = 0; + m_MouseCoords.fZ = 0; + + // Register with Lua. + { + Lua *L = LUA->Get(); + lua_pushstring( L, "INPUTFILTER" ); + this->PushSelf( L ); + lua_settable( L, LUA_GLOBALSINDEX ); + LUA->Release( L ); + } +} + +InputFilter::~InputFilter() +{ + delete queuemutex; + g_ButtonStates.clear(); + // Unregister with Lua. + LUA->UnsetGlobal( "INPUTFILTER" ); +} + +void InputFilter::Reset() +{ + FOREACH_InputDevice( i ) + ResetDevice( InputDevice(i) ); +} + +void InputFilter::SetRepeatRate( float fRepeatRate ) +{ + g_fTimeBetweenRepeats = 1/fRepeatRate; +} + +void InputFilter::SetRepeatDelay( float fDelay ) +{ + g_fTimeBeforeRepeats = fDelay; +} + +void InputFilter::ResetRepeatRate() +{ + SetRepeatRate( REPEATS_PER_SEC ); + SetRepeatDelay( TIME_BEFORE_REPEATS ); +} + +ButtonState::ButtonState(): + m_BeingHeldTime(RageZeroTimer), + m_LastReportTime(RageZeroTimer) +{ + m_BeingHeld = false; + m_bLastReportedHeld = false; + m_fSecsHeld = 0; +} + +void InputFilter::ButtonPressed( const DeviceInput &di ) +{ + LockMut(*queuemutex); + + if( di.ts.IsZero() ) + LOG->Warn( "InputFilter::ButtonPressed: zero timestamp is invalid" ); + + ASSERT_M( di.device < NUM_InputDevice, ssprintf("Invalid device %i", di.device) ); + ASSERT_M( di.button < NUM_DeviceButton, ssprintf("Invalid button %i", di.button) ); + + ButtonState &bs = GetButtonState( di ); + + // Flush any delayed input, like Update() (in case Update() isn't being called). + RageTimer now; + CheckButtonChange( bs, di, now ); + + bs.m_DeviceInput = di; + + bool Down = di.bDown; + if( bs.m_BeingHeld != Down ) + { + bs.m_BeingHeld = Down; + bs.m_BeingHeldTime = di.ts; + } + + // Try to report presses immediately. + MakeButtonStateList( g_CurrentState ); + CheckButtonChange( bs, di, now ); +} + +void InputFilter::SetButtonComment( const DeviceInput &di, const RString &sComment ) +{ + LockMut(*queuemutex); + ButtonState &bs = GetButtonState( di ); + bs.m_sComment = sComment; +} + +/** @brief Release all buttons on the given device. */ +void InputFilter::ResetDevice( InputDevice device ) +{ + LockMut(*queuemutex); + RageTimer now; + + const ButtonStateMap ButtonStates( g_ButtonStates ); + for (std::pair const &b : ButtonStates) + { + const DeviceButtonPair &db = b.first; + if( db.device == device ) + ButtonPressed( DeviceInput(device, db.button, 0, now) ); + } +} + +/** @brief Check for reportable presses. */ +void InputFilter::CheckButtonChange( ButtonState &bs, DeviceInput di, const RageTimer &now ) +{ + if( bs.m_BeingHeld == bs.m_bLastReportedHeld ) + return; + + /* If the last IET_FIRST_PRESS or IET_RELEASE event was sent too recently, + * wait a while before sending it. */ + if( now - bs.m_LastReportTime < g_fInputDebounceTime ) + return; + + bs.m_LastReportTime = now; + bs.m_bLastReportedHeld = bs.m_BeingHeld; + bs.m_fSecsHeld = 0; + bs.m_LastInputTime = bs.m_BeingHeldTime; + + di.ts = bs.m_BeingHeldTime; + if( !bs.m_bLastReportedHeld ) + di.level = 0; + + MakeButtonStateList( g_CurrentState ); + ReportButtonChange( di, bs.m_bLastReportedHeld? IET_FIRST_PRESS:IET_RELEASE ); + + if( !bs.m_bLastReportedHeld ) + g_DisableRepeat.erase( di ); +} + +void InputFilter::ReportButtonChange( const DeviceInput &di, InputEventType t ) +{ + queue.push_back( InputEvent() ); + InputEvent &ie = queue.back(); + ie.type = t; + ie.di = di; + + /* Include a list of all buttons that were pressed at the time of this event. + * We can create this efficiently using g_ButtonStates. Use a vector and not + * a map, for efficiency; most code will not use this information. Iterating + * over g_ButtonStates will be in DeviceInput order, so users can binary + * search this list (eg. std::lower_bound). */ + ie.m_ButtonState = g_CurrentState; +} + +void InputFilter::MakeButtonStateList( vector &aInputOut ) const +{ + aInputOut.clear(); + aInputOut.reserve( g_ButtonStates.size() ); + for( ButtonStateMap::const_iterator it = g_ButtonStates.begin(); it != g_ButtonStates.end(); ++it ) + { + const ButtonState &bs = it->second; + aInputOut.push_back( bs.m_DeviceInput ); + aInputOut.back().ts = bs.m_LastInputTime; + aInputOut.back().bDown = bs.m_bLastReportedHeld; + } +} + +void InputFilter::Update( float fDeltaTime ) +{ + RageTimer now; + + INPUTMAN->Update(); + + /* Make sure that nothing gets inserted while we do this, to prevent things + * like "key pressed, key release, key repeat". */ + LockMut(*queuemutex); + + DeviceInput di( InputDevice_Invalid, DeviceButton_Invalid, 1.0f, now ); + + MakeButtonStateList( g_CurrentState ); + + vector ButtonsToErase; + + FOREACHM( DeviceButtonPair, ButtonState, g_ButtonStates, b ) + { + di.device = b->first.device; + di.button = b->first.button; + ButtonState &bs = b->second; + + // Generate IET_FIRST_PRESS and IET_RELEASE events that were delayed. + CheckButtonChange( bs, di, now ); + + // Generate IET_REPEAT events. + if( !bs.m_bLastReportedHeld ) + { + // If the key isn't pressed, and hasn't been pressed for a while + // (so debouncing isn't interested in it), purge the entry. + if( now - bs.m_LastReportTime > g_fInputDebounceTime && + bs.m_DeviceInput.level == 0.0f ) + ButtonsToErase.push_back( b ); + continue; + } + + // If repeats are disabled for this button, skip. + if( g_DisableRepeat.find(di) != g_DisableRepeat.end() ) + continue; + + const float fOldHoldTime = bs.m_fSecsHeld; + bs.m_fSecsHeld += fDeltaTime; + const float fNewHoldTime = bs.m_fSecsHeld; + + if( fNewHoldTime <= g_fTimeBeforeRepeats ) + continue; + + float fRepeatTime; + if( fOldHoldTime < g_fTimeBeforeRepeats ) + { + fRepeatTime = g_fTimeBeforeRepeats; + } + else + { + float fAdjustedOldHoldTime = fOldHoldTime - g_fTimeBeforeRepeats; + float fAdjustedNewHoldTime = fNewHoldTime - g_fTimeBeforeRepeats; + if( int(fAdjustedOldHoldTime/g_fTimeBetweenRepeats) == int(fAdjustedNewHoldTime/g_fTimeBetweenRepeats) ) + continue; + fRepeatTime = ftruncf( fNewHoldTime, g_fTimeBetweenRepeats ); + } + + /* Set the timestamp to the exact time of the repeat. This way, as long + * as tab/` aren't being used, the timestamp will always increase steadily + * during repeats. */ + di.ts = bs.m_LastInputTime + fRepeatTime; + + ReportButtonChange( di, IET_REPEAT ); + } + + FOREACH( ButtonStateMap::iterator, ButtonsToErase, it ) + g_ButtonStates.erase( *it ); +} + +template +const T *FindItemBinarySearch( IT begin, IT end, const T &i ) +{ + IT it = lower_bound( begin, end, i ); + if( it == end || *it != i ) + return NULL; + + return &*it; +} + +bool InputFilter::IsBeingPressed( const DeviceInput &di, const DeviceInputList *pButtonState ) const +{ + LockMut(*queuemutex); + if( pButtonState == NULL ) + pButtonState = &g_CurrentState; + const DeviceInput *pDI = FindItemBinarySearch( pButtonState->begin(), pButtonState->end(), di ); + return pDI != NULL && pDI->bDown; +} + +float InputFilter::GetSecsHeld( const DeviceInput &di, const DeviceInputList *pButtonState ) const +{ + LockMut(*queuemutex); + if( pButtonState == NULL ) + pButtonState = &g_CurrentState; + const DeviceInput *pDI = FindItemBinarySearch( pButtonState->begin(), pButtonState->end(), di ); + if( pDI == NULL ) + return 0; + return pDI->ts.Ago(); +} + +float InputFilter::GetLevel( const DeviceInput &di, const DeviceInputList *pButtonState ) const +{ + LockMut(*queuemutex); + if( pButtonState == NULL ) + pButtonState = &g_CurrentState; + const DeviceInput *pDI = FindItemBinarySearch( pButtonState->begin(), pButtonState->end(), di ); + if( pDI == NULL ) + return 0.0f; + return pDI->level; +} + +RString InputFilter::GetButtonComment( const DeviceInput &di ) const +{ + LockMut(*queuemutex); + return GetButtonState( di ).m_sComment; +} + +void InputFilter::ResetKeyRepeat( const DeviceInput &di ) +{ + LockMut(*queuemutex); + GetButtonState( di ).m_fSecsHeld = 0; +} + +/** @brief Stop repeating the specified key until released. */ +void InputFilter::RepeatStopKey( const DeviceInput &di ) +{ + LockMut(*queuemutex); + + // If the button is up, do nothing. + ButtonState &bs = GetButtonState( di ); + if( !bs.m_bLastReportedHeld ) + return; + + g_DisableRepeat.insert( di ); +} + +void InputFilter::GetInputEvents( vector &array ) +{ + array.clear(); + LockMut(*queuemutex); + array.swap( queue ); +} + +void InputFilter::GetPressedButtons( vector &array ) const +{ + LockMut(*queuemutex); + array = g_CurrentState; +} + +void InputFilter::UpdateCursorLocation(float _fX, float _fY) +{ + m_MouseCoords.fX = _fX; + m_MouseCoords.fY = _fY; +} + +void InputFilter::UpdateMouseWheel(float _fZ) +{ + m_MouseCoords.fZ = _fZ; +} + +// lua start +#include "LuaBinding.h" + +/** @brief Allow Lua to have access to InputFilter. */ +class LunaInputFilter: public Luna +{ +public: + // todo: Should the input be locked to the theme's width/height instead of + // the window's width/height? -aj + static int GetMouseX( T* p, lua_State *L ){ + float fX = p->GetCursorX(); + // Scale input to the theme's dimensions + fX = SCALE( fX, 0, (PREFSMAN->m_iDisplayHeight * PREFSMAN->m_fDisplayAspectRatio), SCREEN_LEFT, SCREEN_RIGHT ); + lua_pushnumber( L, fX ); + return 1; + } + static int GetMouseY( T* p, lua_State *L ){ + float fY = p->GetCursorY(); + // Scale input to the theme's dimensions + fY = SCALE( fY, 0, PREFSMAN->m_iDisplayHeight, SCREEN_TOP, SCREEN_BOTTOM ); + lua_pushnumber( L, fY ); + return 1; + } + static int GetMouseWheel( T* p, lua_State *L ){ + float fZ = p->GetMouseWheel(); + lua_pushnumber( L, fZ ); + return 1; + } + + LunaInputFilter() + { + ADD_METHOD( GetMouseX ); + ADD_METHOD( GetMouseY ); + ADD_METHOD( GetMouseWheel ); + } +}; + +LUA_REGISTER_CLASS( InputFilter ) +// 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. + */ From f710f850bf2d25b653e4aae02a48df674b7a060f Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 15:16:55 -0400 Subject: [PATCH 032/281] Demonstrate how auto is used for this. --- src/LuaManager.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/LuaManager.cpp b/src/LuaManager.cpp index 7bbe533d67..d3f95ff428 100644 --- a/src/LuaManager.cpp +++ b/src/LuaManager.cpp @@ -677,28 +677,28 @@ XNode *LuaHelpers::GetLuaInformation() } /* Classes */ - FOREACHM_CONST( RString, LClass, mClasses, c ) + for (auto const &c : mClasses) { XNode *pClassNode = pClassesNode->AppendChild( "Class" ); - pClassNode->AppendAttr( "name", c->first ); - if( !c->second.m_sBaseName.empty() ) - pClassNode->AppendAttr( "base", c->second.m_sBaseName ); - FOREACH_CONST( RString, c->second.m_vMethods, m ) + pClassNode->AppendAttr( "name", c.first ); + if( !c.second.m_sBaseName.empty() ) + pClassNode->AppendAttr( "base", c.second.m_sBaseName ); + for (RString const & m : c.second.m_vMethods) { XNode *pMethodNode = pClassNode->AppendChild( "Function" ); - pMethodNode->AppendAttr( "name", *m ); + pMethodNode->AppendAttr( "name", m ); } } /* Singletons */ - FOREACHM_CONST( RString, RString, mSingletons, s ) + for (auto const &s : mSingletons) { - if( mClasses.find(s->first) != mClasses.end() ) + if( mClasses.find(s.first) != mClasses.end() ) continue; XNode *pSingletonNode = pSingletonsNode->AppendChild( "Singleton" ); - pSingletonNode->AppendAttr( "name", s->first ); - pSingletonNode->AppendAttr( "class", s->second ); + pSingletonNode->AppendAttr( "name", s.first ); + pSingletonNode->AppendAttr( "class", s.second ); } /* Namespaces */ @@ -732,21 +732,21 @@ XNode *LuaHelpers::GetLuaInformation() } /* Constants, String Constants */ - FOREACHM_CONST( RString, float, mConstants, c ) + for (auto const &c : mConstants) { XNode *pConstantNode = pConstantsNode->AppendChild( "Constant" ); - pConstantNode->AppendAttr( "name", c->first ); - if( c->second == truncf(c->second) ) - pConstantNode->AppendAttr( "value", int(c->second) ); + pConstantNode->AppendAttr( "name", c.first ); + if( c.second == truncf(c.second) ) + pConstantNode->AppendAttr( "value", static_cast(c.second) ); else - pConstantNode->AppendAttr( "value", c->second ); + pConstantNode->AppendAttr( "value", c.second ); } - FOREACHM_CONST( RString, RString, mStringConstants, s ) + for (auto const &s : mStringConstants) { XNode *pConstantNode = pConstantsNode->AppendChild( "Constant" ); - pConstantNode->AppendAttr( "name", s->first ); - pConstantNode->AppendAttr( "value", ssprintf("'%s'", s->second.c_str()) ); + pConstantNode->AppendAttr( "name", s.first ); + pConstantNode->AppendAttr( "value", ssprintf("'%s'", s.second.c_str()) ); } return pLuaNode; From d5afc54f71bee71ecbb8cc8c3184f564a4e62916 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 15:17:58 -0400 Subject: [PATCH 033/281] Take care of the Prefs Manager WHITE SPACE. --- src/PrefsManager.cpp | 1256 +++++++++++++++++++++--------------------- 1 file changed, 628 insertions(+), 628 deletions(-) diff --git a/src/PrefsManager.cpp b/src/PrefsManager.cpp index 67045d80fb..9ea55aa8f6 100644 --- a/src/PrefsManager.cpp +++ b/src/PrefsManager.cpp @@ -1,628 +1,628 @@ -#include "global.h" -#include "PrefsManager.h" -#include "Foreach.h" -#include "IniFile.h" -#include "LuaManager.h" -#include "Preference.h" -#include "ProductInfo.h" -#include "RageDisplay.h" -#include "RageFile.h" -#include "RageUtil.h" -#include "RageLog.h" -#include "SpecialFiles.h" - -//DEFAULTS_INI_PATH = "Data/Defaults.ini"; // these can be overridden -//PREFERENCES_INI_PATH // overlay on Defaults.ini, contains the user's choices -//STATIC_INI_PATH = "Data/Static.ini"; // overlay on the 2 above, can't be overridden -//TYPE_TXT_FILE = "Data/Type.txt"; - -PrefsManager* PREFSMAN = NULL; // global and accessable from anywhere in our program - -static const char *MusicWheelUsesSectionsNames[] = { - "Never", - "Always", - "ABCOnly", -}; -XToString( MusicWheelUsesSections ); -StringToX( MusicWheelUsesSections ); -LuaXType( MusicWheelUsesSections ); - -static const char *AllowW1Names[] = { - "Never", - "CoursesOnly", - "Everywhere", -}; -XToString( AllowW1 ); -StringToX( AllowW1 ); -LuaXType( AllowW1 ); - -static const char *MaybeNames[] = { - "Ask", - "No", - "Yes", -}; -XToString( Maybe ); -StringToX( Maybe ); -LuaXType( Maybe ); - -static const char *GetRankingNameNames[] = { - "Off", - "On", - "List", -}; -XToString( GetRankingName ); -StringToX( GetRankingName ); -LuaXType( GetRankingName ); - - -static const char *RandomBackgroundModeNames[] = { - "Off", - "Animations", - "RandomMovies", -}; -XToString( RandomBackgroundMode ); -StringToX( RandomBackgroundMode ); -LuaXType( RandomBackgroundMode ); - -static const char *ShowDancingCharactersNames[] = { - "Off", - "Random", - "Select", -}; -XToString( ShowDancingCharacters ); -StringToX( ShowDancingCharacters ); -LuaXType( ShowDancingCharacters ); - -static const char *BannerCacheModeNames[] = { - "Off", - "LowResPreload", - "LowResLoadOnDemand", - "Full" -}; -XToString( BannerCacheMode ); -StringToX( BannerCacheMode ); -LuaXType( BannerCacheMode ); -/* -static const char *BackgroundCacheModeNames[] = { - "Off", - "LowResPreload", - "LowResLoadOnDemand", - "Full" -}; -XToString( BackgroundCacheMode ); -StringToX( BackgroundCacheMode ); -LuaXType( BackgroundCacheMode ); -*/ -static const char *HighResolutionTexturesNames[] = { - "Auto", - "ForceOff", - "ForceOn", -}; -XToString( HighResolutionTextures ); -StringToX( HighResolutionTextures ); -LuaXType( HighResolutionTextures ); - -static const char *AttractSoundFrequencyNames[] = { - "Never", - "EveryTime", - "Every2Times", - "Every3Times", - "Every4Times", - "Every5Times", -}; -XToString( AttractSoundFrequency ); -StringToX( AttractSoundFrequency ); -LuaXType( AttractSoundFrequency ); - -static const char *CourseSortOrdersNames[] = { - "Preferred", - "Songs", - "Meter", - "MeterSum", - "MeterRank", -}; -XToString( CourseSortOrders ); -StringToX( CourseSortOrders ); -LuaXType( CourseSortOrders ); - -// XXX: Fix fail bug? -/* static const char *DefaultFailTypeNames[] = { - "Immediate", - "ImmediateContinue", - "EndOfSong", - "Off", -}; -XToString( DefaultFailType ); -StringToX( DefaultFailType ); -LuaXType( DefaultFailType ); */ - -bool g_bAutoRestart = false; -#ifdef DEBUG -# define TRUE_IF_DEBUG true -#else -# define TRUE_IF_DEBUG false -#endif - -#if !defined(WITHOUT_NETWORKING) && defined(HAVE_VERSION_INFO) -extern unsigned long version_num; -#endif - -void ValidateDisplayAspectRatio( float &val ) -{ - if( val < 0 ) - val = 16/9.f; -} - -void ValidateSongsPerPlay( int &val ) -{ - CLAMP(val,0,MAX_SONGS_PER_PLAY); -} - -PrefsManager::PrefsManager() : - m_sCurrentGame ( "CurrentGame", "" ), - - m_sAnnouncer ( "Announcer", "" ), - m_sTheme ( "Theme", SpecialFiles::BASE_THEME_NAME ), - m_sDefaultModifiers ( "DefaultModifiers", "" ), - - m_bWindowed ( "Windowed", true ), - m_iDisplayWidth ( "DisplayWidth", 854 ), - m_iDisplayHeight ( "DisplayHeight", 480 ), - m_fDisplayAspectRatio ( "DisplayAspectRatio", 16/9.f, ValidateDisplayAspectRatio ), - m_iDisplayColorDepth ( "DisplayColorDepth", 16 ), - m_iTextureColorDepth ( "TextureColorDepth", 16 ), - m_iMovieColorDepth ( "MovieColorDepth", 16 ), - m_bStretchBackgrounds ( "StretchBackgrounds", false ), - m_HighResolutionTextures ( "HighResolutionTextures", HighResolutionTextures_Auto ), - m_iMaxTextureResolution ( "MaxTextureResolution", 2048 ), - m_iRefreshRate ( "RefreshRate", REFRESH_DEFAULT ), - m_bAllowMultitexture ( "AllowMultitexture", true ), - m_bShowStats ( "ShowStats", TRUE_IF_DEBUG), - m_bShowBanners ( "ShowBanners", true ), - m_bShowMouseCursor ( "ShowMouseCursor", true ), - - m_bHiddenSongs ( "HiddenSongs", false ), - m_bVsync ( "Vsync", true ), - m_bInterlaced ( "Interlaced", false ), - m_bPAL ( "PAL", false ), - m_bDelayedTextureDelete ( "DelayedTextureDelete", false ), - m_bDelayedModelDelete ( "DelayedModelDelete", false ), - m_BannerCache ( "BannerCache", BNCACHE_LOW_RES_PRELOAD ), - //m_BackgroundCache ( "BackgroundCache", BGCACHE_LOW_RES_PRELOAD ), - m_bFastLoad ( "FastLoad", true ), - m_bFastLoadAdditionalSongs ( "FastLoadAdditionalSongs", true ), - - m_bOnlyDedicatedMenuButtons ( "OnlyDedicatedMenuButtons", false ), - m_bMenuTimer ( "MenuTimer", false ), - - m_fLifeDifficultyScale ( "LifeDifficultyScale", 1.0f ), - - - m_iRegenComboAfterMiss ( "RegenComboAfterMiss", 5 ), - m_bMercifulDrain ( "MercifulDrain", false ), // negative life deltas are scaled by the players life percentage - m_bMinimum1FullSongInCourses ( "Minimum1FullSongInCourses", false ), // FEoS for 1st song, FailImmediate thereafter - m_bFailOffInBeginner ( "FailOffInBeginner", false ), - m_bFailOffForFirstStageEasy ( "FailOffForFirstStageEasy", false ), - m_bMercifulBeginner ( "MercifulBeginner", false ), - m_bMercifulSuperMeter ( "MercifulSuperMeter", true ), - m_bDelayedBack ( "DelayedBack", true ), - m_bShowInstructions ( "ShowInstructions", true ), - m_bShowCaution ( "ShowCaution", true ), - m_bShowNativeLanguage ( "ShowNativeLanguage", true ), - m_iArcadeOptionsNavigation ( "ArcadeOptionsNavigation", 0 ), - m_MusicWheelUsesSections ( "MusicWheelUsesSections", MusicWheelUsesSections_ALWAYS ), - m_iMusicWheelSwitchSpeed ( "MusicWheelSwitchSpeed", 15 ), - m_AllowW1 ( "AllowW1", ALLOW_W1_EVERYWHERE ), - m_bEventMode ( "EventMode", true ), - m_iSongsPerPlay ( "SongsPerPlay", 3, ValidateSongsPerPlay ), - m_ShowSongOptions ( "ShowSongOptions", Maybe_YES ), - m_bDancePointsForOni ( "DancePointsForOni", true ), - m_bPercentageScoring ( "PercentageScoring", false ), - m_fMinPercentageForMachineSongHighScore ( "MinPercentageForMachineSongHighScore", 0.0001f ), // This is for home, who cares how bad you do? - m_fMinPercentageForMachineCourseHighScore ( "MinPercentageForMachineCourseHighScore", 0.0001f ), // don't save course scores with 0 percentage - m_bDisqualification ( "Disqualification", false ), - m_bAutogenSteps ( "AutogenSteps", false ), - m_bAutogenGroupCourses ( "AutogenGroupCourses", true ), - m_bOnlyPreferredDifficulties ( "OnlyPreferredDifficulties", false ), - m_bBreakComboToGetItem ( "BreakComboToGetItem", false ), - m_bLockCourseDifficulties ( "LockCourseDifficulties", true ), - m_ShowDancingCharacters ( "ShowDancingCharacters", SDC_Random ), - m_bUseUnlockSystem ( "UseUnlockSystem", false ), - m_fGlobalOffsetSeconds ( "GlobalOffsetSeconds", 0 ), - m_iProgressiveLifebar ( "ProgressiveLifebar", 0 ), - m_iProgressiveStageLifebar ( "ProgressiveStageLifebar", 0 ), - m_iProgressiveNonstopLifebar ( "ProgressiveNonstopLifebar", 0 ), - m_bShowBeginnerHelper ( "ShowBeginnerHelper", false ), - m_bDisableScreenSaver ( "DisableScreenSaver", true ), - m_sLanguage ( "Language", "" ), // ThemeManager will deal with this invalid language - m_sMemoryCardProfileSubdir ( "MemoryCardProfileSubdir", PRODUCT_ID ), - m_iProductID ( "ProductID", 1 ), - m_iCenterImageTranslateX ( "CenterImageTranslateX", 0 ), - m_iCenterImageTranslateY ( "CenterImageTranslateY", 0 ), - m_fCenterImageAddWidth ( "CenterImageAddWidth", 0 ), - m_fCenterImageAddHeight ( "CenterImageAddHeight", 0 ), - m_AttractSoundFrequency ( "AttractSoundFrequency", ASF_EVERY_TIME ), - m_bAllowExtraStage ( "AllowExtraStage", true ), - m_iMaxHighScoresPerListForMachine ( "MaxHighScoresPerListForMachine", 10 ), - m_iMaxHighScoresPerListForPlayer ( "MaxHighScoresPerListForPlayer", 3 ), - m_bAllowMultipleHighScoreWithSameName ( "AllowMultipleHighScoreWithSameName", true ), - m_bCelShadeModels ( "CelShadeModels", false ), // Work-In-Progress.. disable by default. - m_bPreferredSortUsesGroups ( "PreferredSortUsesGroups", true ), - - m_fPadStickSeconds ( "PadStickSeconds", 0 ), - m_bForceMipMaps ( "ForceMipMaps", false ), - m_bTrilinearFiltering ( "TrilinearFiltering", false ), - m_bAnisotropicFiltering ( "AnisotropicFiltering", false ), - - m_bSignProfileData ( "SignProfileData", false ), - m_CourseSortOrder ( "CourseSortOrder", COURSE_SORT_SONGS ), - m_bSubSortByNumSteps ( "SubSortByNumSteps", false ), - m_GetRankingName ( "GetRankingName", RANKING_ON ), - m_sAdditionalSongFolders ( "AdditionalSongFolders", "" ), - m_sAdditionalCourseFolders ( "AdditionalCourseFolders", "" ), - m_sAdditionalFolders ( "AdditionalFolders", "" ), - m_sDefaultTheme ( "DefaultTheme", "default" ), - m_sLastSeenVideoDriver ( "LastSeenVideoDriver", "" ), - m_sVideoRenderers ( "VideoRenderers", "" ), // StepMania.cpp sets these on first run: - m_bSmoothLines ( "SmoothLines", false ), - m_iSoundWriteAhead ( "SoundWriteAhead", 0 ), - m_iSoundDevice ( "SoundDevice", "" ), - m_iSoundPreferredSampleRate ( "SoundPreferredSampleRate", 0 ), - m_sLightsStepsDifficulty ( "LightsStepsDifficulty", "medium" ), - m_bAllowUnacceleratedRenderer ( "AllowUnacceleratedRenderer", false ), - m_bThreadedInput ( "ThreadedInput", true ), - m_bThreadedMovieDecode ( "ThreadedMovieDecode", true ), - m_sTestInitialScreen ( "TestInitialScreen", "" ), - m_bDebugLights ( "DebugLights", false ), - m_bMonkeyInput ( "MonkeyInput", false ), - m_sMachineName ( "MachineName", "" ), - m_sCoursesToShowRanking ( "CoursesToShowRanking", "" ), - - m_bQuirksMode ( "QuirksMode", false ), - - /* Debug: */ - m_bLogToDisk ( "LogToDisk", true ), - m_bForceLogFlush ( "ForceLogFlush", false ), - m_bShowLogOutput ( "ShowLogOutput", false ), - m_bLogSkips ( "LogSkips", false ), - m_bLogCheckpoints ( "LogCheckpoints", false ), - m_bShowLoadingWindow ( "ShowLoadingWindow", true ), - m_bPseudoLocalize ( "PseudoLocalize", false ) - -#if !defined(WITHOUT_NETWORKING) - , - m_bEnableScoreboard ( "EnableScoreboard", true ) - - #if defined(HAVE_VERSION_INFO) - , - m_bUpdateCheckEnable ( "UpdateCheckEnable", true ) - // TODO - Aldo_MX: Use PREFSMAN->m_iUpdateCheckIntervalSeconds & PREFSMAN->m_iUpdateCheckLastCheckedSecond - //, - //m_iUpdateCheckIntervalSeconds ( "UpdateCheckIntervalSeconds", 86400 ), // 24 hours - //m_iUpdateCheckLastCheckedSecond ( "UpdateCheckLastCheckSecond", 0 ) - - // TODO - Aldo_MX: Write helpers in LuaManager.cpp to treat unsigned int/long like LUA Numbers - //, - //m_uUpdateCheckLastCheckedBuild ( "UpdateCheckLastCheckedBuild", version_num ) - #endif -#endif - -{ - Init(); - ReadPrefsFromDisk(); - - // Register with Lua. - { - Lua *L = LUA->Get(); - lua_pushstring( L, "PREFSMAN" ); - this->PushSelf( L ); - lua_settable( L, LUA_GLOBALSINDEX ); - LUA->Release( L ); - } -} -#undef TRUE_IF_DEBUG - -void PrefsManager::Init() -{ - IPreference::LoadAllDefaults(); - - m_mapGameNameToGamePrefs.clear(); -} - -PrefsManager::~PrefsManager() -{ - // Unregister with Lua. - LUA->UnsetGlobal( "PREFSMAN" ); -} - -void PrefsManager::SetCurrentGame( const RString &sGame ) -{ - if( m_sCurrentGame.Get() == sGame ) - return; // redundant - - if( !m_sCurrentGame.Get().empty() ) - StoreGamePrefs(); - - m_sCurrentGame.Set( sGame ); - - RestoreGamePrefs(); -} - -void PrefsManager::StoreGamePrefs() -{ - ASSERT( !m_sCurrentGame.Get().empty() ); - - // save off old values - GamePrefs &gp = m_mapGameNameToGamePrefs[m_sCurrentGame]; - gp.m_sAnnouncer = m_sAnnouncer; - gp.m_sTheme = m_sTheme; - gp.m_sDefaultModifiers = m_sDefaultModifiers; -} - -void PrefsManager::RestoreGamePrefs() -{ - ASSERT( !m_sCurrentGame.Get().empty() ); - - // load prefs - GamePrefs gp; - map::const_iterator iter = m_mapGameNameToGamePrefs.find( m_sCurrentGame ); - if( iter != m_mapGameNameToGamePrefs.end() ) - gp = iter->second; - - m_sAnnouncer .Set( gp.m_sAnnouncer ); - m_sTheme .Set( gp.m_sTheme ); - m_sDefaultModifiers .Set( gp.m_sDefaultModifiers ); - - // give Static.ini a chance to clobber the saved game prefs - ReadPrefsFromFile( SpecialFiles::STATIC_INI_PATH, GetPreferencesSection(), true ); -} - -PrefsManager::GamePrefs::GamePrefs() : m_sAnnouncer(""), m_sTheme(SpecialFiles::BASE_THEME_NAME), m_sDefaultModifiers("") {} - -void PrefsManager::ReadPrefsFromDisk() -{ - ReadDefaultsFromFile( SpecialFiles::DEFAULTS_INI_PATH, GetPreferencesSection() ); - IPreference::LoadAllDefaults(); - - ReadPrefsFromFile( SpecialFiles::PREFERENCES_INI_PATH, "Options", false ); - ReadGamePrefsFromIni( SpecialFiles::PREFERENCES_INI_PATH ); - ReadPrefsFromFile( SpecialFiles::STATIC_INI_PATH, GetPreferencesSection(), true ); - - if( !m_sCurrentGame.Get().empty() ) - RestoreGamePrefs(); -} - -void PrefsManager::ResetToFactoryDefaults() -{ - // clobber the users prefs by initing then applying defaults - Init(); - IPreference::LoadAllDefaults(); - ReadPrefsFromFile( SpecialFiles::STATIC_INI_PATH, GetPreferencesSection(), true ); - - SavePrefsToDisk(); -} - -void PrefsManager::ReadPrefsFromFile( const RString &sIni, const RString &sSection, bool bIsStatic ) -{ - IniFile ini; - if( !ini.ReadFile(sIni) ) - return; - - ReadPrefsFromIni( ini, sSection, bIsStatic ); -} - -static const RString GAME_SECTION_PREFIX = "Game-"; - -void PrefsManager::ReadPrefsFromIni( const IniFile &ini, const RString &sSection, bool bIsStatic ) -{ - // Apply our fallback recursively (if any) before applying ourself. - static int s_iDepth = 0; - s_iDepth++; - ASSERT( s_iDepth < 100 ); - RString sFallback; - if( ini.GetValue(sSection,"Fallback",sFallback) ) - { - ReadPrefsFromIni( ini, sFallback, bIsStatic ); - } - s_iDepth--; - - /* - IPreference *pPref = PREFSMAN->GetPreferenceByName( *sName ); - if( pPref == NULL ) - { - LOG->Warn( "Unknown preference in [%s]: %s", sClassName.c_str(), sName->c_str() ); - continue; - } - pPref->FromString( sVal ); - */ - - const XNode *pChild = ini.GetChild(sSection); - if( pChild ) - IPreference::ReadAllPrefsFromNode( pChild, bIsStatic ); -} - -void PrefsManager::ReadGamePrefsFromIni( const RString &sIni ) -{ - IniFile ini; - if( !ini.ReadFile(sIni) ) - return; - - FOREACH_CONST_Child( &ini, section ) - { - if( !BeginsWith(section->GetName(), GAME_SECTION_PREFIX) ) - continue; - - RString sGame = section->GetName().Right( section->GetName().length() - GAME_SECTION_PREFIX.length() ); - GamePrefs &gp = m_mapGameNameToGamePrefs[ sGame ]; - - // todo: read more prefs here? -aj - ini.GetValue( section->GetName(), "Announcer", gp.m_sAnnouncer ); - ini.GetValue( section->GetName(), "Theme", gp.m_sTheme ); - ini.GetValue( section->GetName(), "DefaultModifiers", gp.m_sDefaultModifiers ); - } -} - -void PrefsManager::ReadDefaultsFromFile( const RString &sIni, const RString &sSection ) -{ - IniFile ini; - if( !ini.ReadFile(sIni) ) - return; - - ReadDefaultsFromIni( ini, sSection ); -} - -void PrefsManager::ReadDefaultsFromIni( const IniFile &ini, const RString &sSection ) -{ - // Apply our fallback recursively (if any) before applying ourself. - // TODO: detect circular? - RString sFallback; - if( ini.GetValue(sSection,"Fallback",sFallback) ) - ReadDefaultsFromIni( ini, sFallback ); - - IPreference::ReadAllDefaultsFromNode( ini.GetChild(sSection) ); -} - -void PrefsManager::SavePrefsToDisk() -{ - IniFile ini; - SavePrefsToIni( ini ); - ini.WriteFile( SpecialFiles::PREFERENCES_INI_PATH ); -} - -void PrefsManager::SavePrefsToIni( IniFile &ini ) -{ - if( !m_sCurrentGame.Get().empty() ) - StoreGamePrefs(); - - XNode* pNode = ini.GetChild( "Options" ); - if( pNode == NULL ) - pNode = ini.AppendChild( "Options" ); - IPreference::SavePrefsToNode( pNode ); - - FOREACHM_CONST( RString, GamePrefs, m_mapGameNameToGamePrefs, iter ) - { - RString sSection = "Game-" + RString( iter->first ); - - // todo: write more values here? -aj - ini.SetValue( sSection, "Announcer", iter->second.m_sAnnouncer ); - ini.SetValue( sSection, "Theme", iter->second.m_sTheme ); - ini.SetValue( sSection, "DefaultModifiers", iter->second.m_sDefaultModifiers ); - } -} - - -RString PrefsManager::GetPreferencesSection() const -{ - RString sSection = "Options"; - - // OK if this fails - GetFileContents( SpecialFiles::TYPE_TXT_FILE, sSection, true ); - - // OK if this fails - GetCommandlineArgument( "Type", &sSection ); - - return sSection; -} - - -// lua start -#include "LuaBinding.h" - -/** @brief Allow Lua to have access to the PrefsManager. */ -class LunaPrefsManager: public Luna -{ -public: - static int GetPreference( T* p, lua_State *L ) - { - RString sName = SArg(1); - IPreference *pPref = IPreference::GetPreferenceByName( sName ); - if( pPref == NULL ) - { - LOG->Warn( "GetPreference: unknown preference \"%s\"", sName.c_str() ); - lua_pushnil( L ); - return 1; - } - - pPref->PushValue( L ); - return 1; - } - static int SetPreference( T* p, lua_State *L ) - { - RString sName = SArg(1); - - IPreference *pPref = IPreference::GetPreferenceByName( sName ); - if( pPref == NULL ) - { - LOG->Warn( "SetPreference: unknown preference \"%s\"", sName.c_str() ); - return 0; - } - - lua_pushvalue( L, 2 ); - pPref->SetFromStack( L ); - return 0; - } - static int SetPreferenceToDefault( T* p, lua_State *L ) - { - RString sName = SArg(1); - - IPreference *pPref = IPreference::GetPreferenceByName( sName ); - if( pPref == NULL ) - { - LOG->Warn( "SetPreferenceToDefault: unknown preference \"%s\"", sName.c_str() ); - return 0; - } - - pPref->LoadDefault(); - LOG->Trace( "Restored preference \"%s\" to default \"%s\"", sName.c_str(), pPref->ToString().c_str() ); - return 0; - } - static int PreferenceExists( T* p, lua_State *L ) - { - RString sName = SArg(1); - - IPreference *pPref = IPreference::GetPreferenceByName( sName ); - if( pPref == NULL ) - { - lua_pushboolean( L, false ); - return 1; - } - lua_pushboolean( L, true ); - return 1; - } - - LunaPrefsManager() - { - ADD_METHOD( GetPreference ); - ADD_METHOD( SetPreference ); - ADD_METHOD( SetPreferenceToDefault ); - ADD_METHOD( PreferenceExists ); - } -}; - -LUA_REGISTER_CLASS( PrefsManager ) -// lua end - -/* - * (c) 2001-2004 Chris Danford, Chris Gomez - * 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 "PrefsManager.h" +#include "Foreach.h" +#include "IniFile.h" +#include "LuaManager.h" +#include "Preference.h" +#include "ProductInfo.h" +#include "RageDisplay.h" +#include "RageFile.h" +#include "RageUtil.h" +#include "RageLog.h" +#include "SpecialFiles.h" + +//DEFAULTS_INI_PATH = "Data/Defaults.ini"; // these can be overridden +//PREFERENCES_INI_PATH // overlay on Defaults.ini, contains the user's choices +//STATIC_INI_PATH = "Data/Static.ini"; // overlay on the 2 above, can't be overridden +//TYPE_TXT_FILE = "Data/Type.txt"; + +PrefsManager* PREFSMAN = NULL; // global and accessable from anywhere in our program + +static const char *MusicWheelUsesSectionsNames[] = { + "Never", + "Always", + "ABCOnly", +}; +XToString( MusicWheelUsesSections ); +StringToX( MusicWheelUsesSections ); +LuaXType( MusicWheelUsesSections ); + +static const char *AllowW1Names[] = { + "Never", + "CoursesOnly", + "Everywhere", +}; +XToString( AllowW1 ); +StringToX( AllowW1 ); +LuaXType( AllowW1 ); + +static const char *MaybeNames[] = { + "Ask", + "No", + "Yes", +}; +XToString( Maybe ); +StringToX( Maybe ); +LuaXType( Maybe ); + +static const char *GetRankingNameNames[] = { + "Off", + "On", + "List", +}; +XToString( GetRankingName ); +StringToX( GetRankingName ); +LuaXType( GetRankingName ); + + +static const char *RandomBackgroundModeNames[] = { + "Off", + "Animations", + "RandomMovies", +}; +XToString( RandomBackgroundMode ); +StringToX( RandomBackgroundMode ); +LuaXType( RandomBackgroundMode ); + +static const char *ShowDancingCharactersNames[] = { + "Off", + "Random", + "Select", +}; +XToString( ShowDancingCharacters ); +StringToX( ShowDancingCharacters ); +LuaXType( ShowDancingCharacters ); + +static const char *BannerCacheModeNames[] = { + "Off", + "LowResPreload", + "LowResLoadOnDemand", + "Full" +}; +XToString( BannerCacheMode ); +StringToX( BannerCacheMode ); +LuaXType( BannerCacheMode ); +/* +static const char *BackgroundCacheModeNames[] = { + "Off", + "LowResPreload", + "LowResLoadOnDemand", + "Full" +}; +XToString( BackgroundCacheMode ); +StringToX( BackgroundCacheMode ); +LuaXType( BackgroundCacheMode ); +*/ +static const char *HighResolutionTexturesNames[] = { + "Auto", + "ForceOff", + "ForceOn", +}; +XToString( HighResolutionTextures ); +StringToX( HighResolutionTextures ); +LuaXType( HighResolutionTextures ); + +static const char *AttractSoundFrequencyNames[] = { + "Never", + "EveryTime", + "Every2Times", + "Every3Times", + "Every4Times", + "Every5Times", +}; +XToString( AttractSoundFrequency ); +StringToX( AttractSoundFrequency ); +LuaXType( AttractSoundFrequency ); + +static const char *CourseSortOrdersNames[] = { + "Preferred", + "Songs", + "Meter", + "MeterSum", + "MeterRank", +}; +XToString( CourseSortOrders ); +StringToX( CourseSortOrders ); +LuaXType( CourseSortOrders ); + +// XXX: Fix fail bug? +/* static const char *DefaultFailTypeNames[] = { + "Immediate", + "ImmediateContinue", + "EndOfSong", + "Off", +}; +XToString( DefaultFailType ); +StringToX( DefaultFailType ); +LuaXType( DefaultFailType ); */ + +bool g_bAutoRestart = false; +#ifdef DEBUG +# define TRUE_IF_DEBUG true +#else +# define TRUE_IF_DEBUG false +#endif + +#if !defined(WITHOUT_NETWORKING) && defined(HAVE_VERSION_INFO) +extern unsigned long version_num; +#endif + +void ValidateDisplayAspectRatio( float &val ) +{ + if( val < 0 ) + val = 16/9.f; +} + +void ValidateSongsPerPlay( int &val ) +{ + CLAMP(val,0,MAX_SONGS_PER_PLAY); +} + +PrefsManager::PrefsManager() : + m_sCurrentGame ( "CurrentGame", "" ), + + m_sAnnouncer ( "Announcer", "" ), + m_sTheme ( "Theme", SpecialFiles::BASE_THEME_NAME ), + m_sDefaultModifiers ( "DefaultModifiers", "" ), + + m_bWindowed ( "Windowed", true ), + m_iDisplayWidth ( "DisplayWidth", 854 ), + m_iDisplayHeight ( "DisplayHeight", 480 ), + m_fDisplayAspectRatio ( "DisplayAspectRatio", 16/9.f, ValidateDisplayAspectRatio ), + m_iDisplayColorDepth ( "DisplayColorDepth", 16 ), + m_iTextureColorDepth ( "TextureColorDepth", 16 ), + m_iMovieColorDepth ( "MovieColorDepth", 16 ), + m_bStretchBackgrounds ( "StretchBackgrounds", false ), + m_HighResolutionTextures ( "HighResolutionTextures", HighResolutionTextures_Auto ), + m_iMaxTextureResolution ( "MaxTextureResolution", 2048 ), + m_iRefreshRate ( "RefreshRate", REFRESH_DEFAULT ), + m_bAllowMultitexture ( "AllowMultitexture", true ), + m_bShowStats ( "ShowStats", TRUE_IF_DEBUG), + m_bShowBanners ( "ShowBanners", true ), + m_bShowMouseCursor ( "ShowMouseCursor", true ), + + m_bHiddenSongs ( "HiddenSongs", false ), + m_bVsync ( "Vsync", true ), + m_bInterlaced ( "Interlaced", false ), + m_bPAL ( "PAL", false ), + m_bDelayedTextureDelete ( "DelayedTextureDelete", false ), + m_bDelayedModelDelete ( "DelayedModelDelete", false ), + m_BannerCache ( "BannerCache", BNCACHE_LOW_RES_PRELOAD ), + //m_BackgroundCache ( "BackgroundCache", BGCACHE_LOW_RES_PRELOAD ), + m_bFastLoad ( "FastLoad", true ), + m_bFastLoadAdditionalSongs ( "FastLoadAdditionalSongs", true ), + + m_bOnlyDedicatedMenuButtons ( "OnlyDedicatedMenuButtons", false ), + m_bMenuTimer ( "MenuTimer", false ), + + m_fLifeDifficultyScale ( "LifeDifficultyScale", 1.0f ), + + + m_iRegenComboAfterMiss ( "RegenComboAfterMiss", 5 ), + m_bMercifulDrain ( "MercifulDrain", false ), // negative life deltas are scaled by the players life percentage + m_bMinimum1FullSongInCourses ( "Minimum1FullSongInCourses", false ), // FEoS for 1st song, FailImmediate thereafter + m_bFailOffInBeginner ( "FailOffInBeginner", false ), + m_bFailOffForFirstStageEasy ( "FailOffForFirstStageEasy", false ), + m_bMercifulBeginner ( "MercifulBeginner", false ), + m_bMercifulSuperMeter ( "MercifulSuperMeter", true ), + m_bDelayedBack ( "DelayedBack", true ), + m_bShowInstructions ( "ShowInstructions", true ), + m_bShowCaution ( "ShowCaution", true ), + m_bShowNativeLanguage ( "ShowNativeLanguage", true ), + m_iArcadeOptionsNavigation ( "ArcadeOptionsNavigation", 0 ), + m_MusicWheelUsesSections ( "MusicWheelUsesSections", MusicWheelUsesSections_ALWAYS ), + m_iMusicWheelSwitchSpeed ( "MusicWheelSwitchSpeed", 15 ), + m_AllowW1 ( "AllowW1", ALLOW_W1_EVERYWHERE ), + m_bEventMode ( "EventMode", true ), + m_iSongsPerPlay ( "SongsPerPlay", 3, ValidateSongsPerPlay ), + m_ShowSongOptions ( "ShowSongOptions", Maybe_YES ), + m_bDancePointsForOni ( "DancePointsForOni", true ), + m_bPercentageScoring ( "PercentageScoring", false ), + m_fMinPercentageForMachineSongHighScore ( "MinPercentageForMachineSongHighScore", 0.0001f ), // This is for home, who cares how bad you do? + m_fMinPercentageForMachineCourseHighScore ( "MinPercentageForMachineCourseHighScore", 0.0001f ), // don't save course scores with 0 percentage + m_bDisqualification ( "Disqualification", false ), + m_bAutogenSteps ( "AutogenSteps", false ), + m_bAutogenGroupCourses ( "AutogenGroupCourses", true ), + m_bOnlyPreferredDifficulties ( "OnlyPreferredDifficulties", false ), + m_bBreakComboToGetItem ( "BreakComboToGetItem", false ), + m_bLockCourseDifficulties ( "LockCourseDifficulties", true ), + m_ShowDancingCharacters ( "ShowDancingCharacters", SDC_Random ), + m_bUseUnlockSystem ( "UseUnlockSystem", false ), + m_fGlobalOffsetSeconds ( "GlobalOffsetSeconds", 0 ), + m_iProgressiveLifebar ( "ProgressiveLifebar", 0 ), + m_iProgressiveStageLifebar ( "ProgressiveStageLifebar", 0 ), + m_iProgressiveNonstopLifebar ( "ProgressiveNonstopLifebar", 0 ), + m_bShowBeginnerHelper ( "ShowBeginnerHelper", false ), + m_bDisableScreenSaver ( "DisableScreenSaver", true ), + m_sLanguage ( "Language", "" ), // ThemeManager will deal with this invalid language + m_sMemoryCardProfileSubdir ( "MemoryCardProfileSubdir", PRODUCT_ID ), + m_iProductID ( "ProductID", 1 ), + m_iCenterImageTranslateX ( "CenterImageTranslateX", 0 ), + m_iCenterImageTranslateY ( "CenterImageTranslateY", 0 ), + m_fCenterImageAddWidth ( "CenterImageAddWidth", 0 ), + m_fCenterImageAddHeight ( "CenterImageAddHeight", 0 ), + m_AttractSoundFrequency ( "AttractSoundFrequency", ASF_EVERY_TIME ), + m_bAllowExtraStage ( "AllowExtraStage", true ), + m_iMaxHighScoresPerListForMachine ( "MaxHighScoresPerListForMachine", 10 ), + m_iMaxHighScoresPerListForPlayer ( "MaxHighScoresPerListForPlayer", 3 ), + m_bAllowMultipleHighScoreWithSameName ( "AllowMultipleHighScoreWithSameName", true ), + m_bCelShadeModels ( "CelShadeModels", false ), // Work-In-Progress.. disable by default. + m_bPreferredSortUsesGroups ( "PreferredSortUsesGroups", true ), + + m_fPadStickSeconds ( "PadStickSeconds", 0 ), + m_bForceMipMaps ( "ForceMipMaps", false ), + m_bTrilinearFiltering ( "TrilinearFiltering", false ), + m_bAnisotropicFiltering ( "AnisotropicFiltering", false ), + + m_bSignProfileData ( "SignProfileData", false ), + m_CourseSortOrder ( "CourseSortOrder", COURSE_SORT_SONGS ), + m_bSubSortByNumSteps ( "SubSortByNumSteps", false ), + m_GetRankingName ( "GetRankingName", RANKING_ON ), + m_sAdditionalSongFolders ( "AdditionalSongFolders", "" ), + m_sAdditionalCourseFolders ( "AdditionalCourseFolders", "" ), + m_sAdditionalFolders ( "AdditionalFolders", "" ), + m_sDefaultTheme ( "DefaultTheme", "default" ), + m_sLastSeenVideoDriver ( "LastSeenVideoDriver", "" ), + m_sVideoRenderers ( "VideoRenderers", "" ), // StepMania.cpp sets these on first run: + m_bSmoothLines ( "SmoothLines", false ), + m_iSoundWriteAhead ( "SoundWriteAhead", 0 ), + m_iSoundDevice ( "SoundDevice", "" ), + m_iSoundPreferredSampleRate ( "SoundPreferredSampleRate", 0 ), + m_sLightsStepsDifficulty ( "LightsStepsDifficulty", "medium" ), + m_bAllowUnacceleratedRenderer ( "AllowUnacceleratedRenderer", false ), + m_bThreadedInput ( "ThreadedInput", true ), + m_bThreadedMovieDecode ( "ThreadedMovieDecode", true ), + m_sTestInitialScreen ( "TestInitialScreen", "" ), + m_bDebugLights ( "DebugLights", false ), + m_bMonkeyInput ( "MonkeyInput", false ), + m_sMachineName ( "MachineName", "" ), + m_sCoursesToShowRanking ( "CoursesToShowRanking", "" ), + + m_bQuirksMode ( "QuirksMode", false ), + + /* Debug: */ + m_bLogToDisk ( "LogToDisk", true ), + m_bForceLogFlush ( "ForceLogFlush", false ), + m_bShowLogOutput ( "ShowLogOutput", false ), + m_bLogSkips ( "LogSkips", false ), + m_bLogCheckpoints ( "LogCheckpoints", false ), + m_bShowLoadingWindow ( "ShowLoadingWindow", true ), + m_bPseudoLocalize ( "PseudoLocalize", false ) + +#if !defined(WITHOUT_NETWORKING) + , + m_bEnableScoreboard ( "EnableScoreboard", true ) + + #if defined(HAVE_VERSION_INFO) + , + m_bUpdateCheckEnable ( "UpdateCheckEnable", true ) + // TODO - Aldo_MX: Use PREFSMAN->m_iUpdateCheckIntervalSeconds & PREFSMAN->m_iUpdateCheckLastCheckedSecond + //, + //m_iUpdateCheckIntervalSeconds ( "UpdateCheckIntervalSeconds", 86400 ), // 24 hours + //m_iUpdateCheckLastCheckedSecond ( "UpdateCheckLastCheckSecond", 0 ) + + // TODO - Aldo_MX: Write helpers in LuaManager.cpp to treat unsigned int/long like LUA Numbers + //, + //m_uUpdateCheckLastCheckedBuild ( "UpdateCheckLastCheckedBuild", version_num ) + #endif +#endif + +{ + Init(); + ReadPrefsFromDisk(); + + // Register with Lua. + { + Lua *L = LUA->Get(); + lua_pushstring( L, "PREFSMAN" ); + this->PushSelf( L ); + lua_settable( L, LUA_GLOBALSINDEX ); + LUA->Release( L ); + } +} +#undef TRUE_IF_DEBUG + +void PrefsManager::Init() +{ + IPreference::LoadAllDefaults(); + + m_mapGameNameToGamePrefs.clear(); +} + +PrefsManager::~PrefsManager() +{ + // Unregister with Lua. + LUA->UnsetGlobal( "PREFSMAN" ); +} + +void PrefsManager::SetCurrentGame( const RString &sGame ) +{ + if( m_sCurrentGame.Get() == sGame ) + return; // redundant + + if( !m_sCurrentGame.Get().empty() ) + StoreGamePrefs(); + + m_sCurrentGame.Set( sGame ); + + RestoreGamePrefs(); +} + +void PrefsManager::StoreGamePrefs() +{ + ASSERT( !m_sCurrentGame.Get().empty() ); + + // save off old values + GamePrefs &gp = m_mapGameNameToGamePrefs[m_sCurrentGame]; + gp.m_sAnnouncer = m_sAnnouncer; + gp.m_sTheme = m_sTheme; + gp.m_sDefaultModifiers = m_sDefaultModifiers; +} + +void PrefsManager::RestoreGamePrefs() +{ + ASSERT( !m_sCurrentGame.Get().empty() ); + + // load prefs + GamePrefs gp; + map::const_iterator iter = m_mapGameNameToGamePrefs.find( m_sCurrentGame ); + if( iter != m_mapGameNameToGamePrefs.end() ) + gp = iter->second; + + m_sAnnouncer .Set( gp.m_sAnnouncer ); + m_sTheme .Set( gp.m_sTheme ); + m_sDefaultModifiers .Set( gp.m_sDefaultModifiers ); + + // give Static.ini a chance to clobber the saved game prefs + ReadPrefsFromFile( SpecialFiles::STATIC_INI_PATH, GetPreferencesSection(), true ); +} + +PrefsManager::GamePrefs::GamePrefs() : m_sAnnouncer(""), m_sTheme(SpecialFiles::BASE_THEME_NAME), m_sDefaultModifiers("") {} + +void PrefsManager::ReadPrefsFromDisk() +{ + ReadDefaultsFromFile( SpecialFiles::DEFAULTS_INI_PATH, GetPreferencesSection() ); + IPreference::LoadAllDefaults(); + + ReadPrefsFromFile( SpecialFiles::PREFERENCES_INI_PATH, "Options", false ); + ReadGamePrefsFromIni( SpecialFiles::PREFERENCES_INI_PATH ); + ReadPrefsFromFile( SpecialFiles::STATIC_INI_PATH, GetPreferencesSection(), true ); + + if( !m_sCurrentGame.Get().empty() ) + RestoreGamePrefs(); +} + +void PrefsManager::ResetToFactoryDefaults() +{ + // clobber the users prefs by initing then applying defaults + Init(); + IPreference::LoadAllDefaults(); + ReadPrefsFromFile( SpecialFiles::STATIC_INI_PATH, GetPreferencesSection(), true ); + + SavePrefsToDisk(); +} + +void PrefsManager::ReadPrefsFromFile( const RString &sIni, const RString &sSection, bool bIsStatic ) +{ + IniFile ini; + if( !ini.ReadFile(sIni) ) + return; + + ReadPrefsFromIni( ini, sSection, bIsStatic ); +} + +static const RString GAME_SECTION_PREFIX = "Game-"; + +void PrefsManager::ReadPrefsFromIni( const IniFile &ini, const RString &sSection, bool bIsStatic ) +{ + // Apply our fallback recursively (if any) before applying ourself. + static int s_iDepth = 0; + s_iDepth++; + ASSERT( s_iDepth < 100 ); + RString sFallback; + if( ini.GetValue(sSection,"Fallback",sFallback) ) + { + ReadPrefsFromIni( ini, sFallback, bIsStatic ); + } + s_iDepth--; + + /* + IPreference *pPref = PREFSMAN->GetPreferenceByName( *sName ); + if( pPref == NULL ) + { + LOG->Warn( "Unknown preference in [%s]: %s", sClassName.c_str(), sName->c_str() ); + continue; + } + pPref->FromString( sVal ); + */ + + const XNode *pChild = ini.GetChild(sSection); + if( pChild ) + IPreference::ReadAllPrefsFromNode( pChild, bIsStatic ); +} + +void PrefsManager::ReadGamePrefsFromIni( const RString &sIni ) +{ + IniFile ini; + if( !ini.ReadFile(sIni) ) + return; + + FOREACH_CONST_Child( &ini, section ) + { + if( !BeginsWith(section->GetName(), GAME_SECTION_PREFIX) ) + continue; + + RString sGame = section->GetName().Right( section->GetName().length() - GAME_SECTION_PREFIX.length() ); + GamePrefs &gp = m_mapGameNameToGamePrefs[ sGame ]; + + // todo: read more prefs here? -aj + ini.GetValue( section->GetName(), "Announcer", gp.m_sAnnouncer ); + ini.GetValue( section->GetName(), "Theme", gp.m_sTheme ); + ini.GetValue( section->GetName(), "DefaultModifiers", gp.m_sDefaultModifiers ); + } +} + +void PrefsManager::ReadDefaultsFromFile( const RString &sIni, const RString &sSection ) +{ + IniFile ini; + if( !ini.ReadFile(sIni) ) + return; + + ReadDefaultsFromIni( ini, sSection ); +} + +void PrefsManager::ReadDefaultsFromIni( const IniFile &ini, const RString &sSection ) +{ + // Apply our fallback recursively (if any) before applying ourself. + // TODO: detect circular? + RString sFallback; + if( ini.GetValue(sSection,"Fallback",sFallback) ) + ReadDefaultsFromIni( ini, sFallback ); + + IPreference::ReadAllDefaultsFromNode( ini.GetChild(sSection) ); +} + +void PrefsManager::SavePrefsToDisk() +{ + IniFile ini; + SavePrefsToIni( ini ); + ini.WriteFile( SpecialFiles::PREFERENCES_INI_PATH ); +} + +void PrefsManager::SavePrefsToIni( IniFile &ini ) +{ + if( !m_sCurrentGame.Get().empty() ) + StoreGamePrefs(); + + XNode* pNode = ini.GetChild( "Options" ); + if( pNode == NULL ) + pNode = ini.AppendChild( "Options" ); + IPreference::SavePrefsToNode( pNode ); + + for (auto const &iter : m_mapGameNameToGamePrefs) + { + RString sSection = "Game-" + RString( iter.first ); + + // todo: write more values here? -aj + ini.SetValue( sSection, "Announcer", iter.second.m_sAnnouncer ); + ini.SetValue( sSection, "Theme", iter.second.m_sTheme ); + ini.SetValue( sSection, "DefaultModifiers", iter.second.m_sDefaultModifiers ); + } +} + + +RString PrefsManager::GetPreferencesSection() const +{ + RString sSection = "Options"; + + // OK if this fails + GetFileContents( SpecialFiles::TYPE_TXT_FILE, sSection, true ); + + // OK if this fails + GetCommandlineArgument( "Type", &sSection ); + + return sSection; +} + + +// lua start +#include "LuaBinding.h" + +/** @brief Allow Lua to have access to the PrefsManager. */ +class LunaPrefsManager: public Luna +{ +public: + static int GetPreference( T* p, lua_State *L ) + { + RString sName = SArg(1); + IPreference *pPref = IPreference::GetPreferenceByName( sName ); + if( pPref == NULL ) + { + LOG->Warn( "GetPreference: unknown preference \"%s\"", sName.c_str() ); + lua_pushnil( L ); + return 1; + } + + pPref->PushValue( L ); + return 1; + } + static int SetPreference( T* p, lua_State *L ) + { + RString sName = SArg(1); + + IPreference *pPref = IPreference::GetPreferenceByName( sName ); + if( pPref == NULL ) + { + LOG->Warn( "SetPreference: unknown preference \"%s\"", sName.c_str() ); + return 0; + } + + lua_pushvalue( L, 2 ); + pPref->SetFromStack( L ); + return 0; + } + static int SetPreferenceToDefault( T* p, lua_State *L ) + { + RString sName = SArg(1); + + IPreference *pPref = IPreference::GetPreferenceByName( sName ); + if( pPref == NULL ) + { + LOG->Warn( "SetPreferenceToDefault: unknown preference \"%s\"", sName.c_str() ); + return 0; + } + + pPref->LoadDefault(); + LOG->Trace( "Restored preference \"%s\" to default \"%s\"", sName.c_str(), pPref->ToString().c_str() ); + return 0; + } + static int PreferenceExists( T* p, lua_State *L ) + { + RString sName = SArg(1); + + IPreference *pPref = IPreference::GetPreferenceByName( sName ); + if( pPref == NULL ) + { + lua_pushboolean( L, false ); + return 1; + } + lua_pushboolean( L, true ); + return 1; + } + + LunaPrefsManager() + { + ADD_METHOD( GetPreference ); + ADD_METHOD( SetPreference ); + ADD_METHOD( SetPreferenceToDefault ); + ADD_METHOD( PreferenceExists ); + } +}; + +LUA_REGISTER_CLASS( PrefsManager ) +// lua end + +/* + * (c) 2001-2004 Chris Danford, Chris Gomez + * 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. + */ From 7d56192e971fd322c8beba9f597f8b155f1bfbec Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 15:19:43 -0400 Subject: [PATCH 034/281] And back to normal with these loops. --- src/SongManager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SongManager.cpp b/src/SongManager.cpp index 4450f1db85..89e01d5d0b 100644 --- a/src/SongManager.cpp +++ b/src/SongManager.cpp @@ -563,8 +563,8 @@ RString SongManager::GetCourseGroupBannerPath( const RString &sCourseGroup ) con void SongManager::GetCourseGroupNames( vector &AddTo ) const { - FOREACHM_CONST( RString, CourseGroupInfo, m_mapCourseGroupToInfo, iter ) - AddTo.push_back( iter->first ); + for (std::pair const &iter : m_mapCourseGroupToInfo) + AddTo.push_back( iter.first ); } bool SongManager::DoesCourseGroupExist( const RString &sCourseGroup ) const @@ -575,9 +575,9 @@ bool SongManager::DoesCourseGroupExist( const RString &sCourseGroup ) const RageColor SongManager::GetCourseGroupColor( const RString &sCourseGroup ) const { int iIndex = 0; - FOREACHM_CONST( RString, CourseGroupInfo, m_mapCourseGroupToInfo, iter ) + for (std::pair const &iter : m_mapCourseGroupToInfo) { - if( iter->first == sCourseGroup ) + if( iter.first == sCourseGroup ) return SONG_GROUP_COLOR.GetValue( iIndex%NUM_SONG_GROUP_COLORS ); iIndex++; } From b6f2bf317c686e7774c7b2330804296c5dd13008 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 15:20:30 -0400 Subject: [PATCH 035/281] Where's my hg commit --amend command? --- src/SongManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SongManager.cpp b/src/SongManager.cpp index 89e01d5d0b..6735e8cd5f 100644 --- a/src/SongManager.cpp +++ b/src/SongManager.cpp @@ -563,7 +563,7 @@ RString SongManager::GetCourseGroupBannerPath( const RString &sCourseGroup ) con void SongManager::GetCourseGroupNames( vector &AddTo ) const { - for (std::pair const &iter : m_mapCourseGroupToInfo) + for (std::pair const &iter : m_mapCourseGroupToInfo) AddTo.push_back( iter.first ); } @@ -575,7 +575,7 @@ bool SongManager::DoesCourseGroupExist( const RString &sCourseGroup ) const RageColor SongManager::GetCourseGroupColor( const RString &sCourseGroup ) const { int iIndex = 0; - for (std::pair const &iter : m_mapCourseGroupToInfo) + for (std::pair const &iter : m_mapCourseGroupToInfo) { if( iter.first == sCourseGroup ) return SONG_GROUP_COLOR.GetValue( iIndex%NUM_SONG_GROUP_COLORS ); From 5dfc1bf27993e00b313895ee3adbb28312a9b646 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 15:21:12 -0400 Subject: [PATCH 036/281] Log with a loop, auto, and WHITE SPACE. --- src/RageLog.cpp | 898 ++++++++++++++++++++++++------------------------ 1 file changed, 449 insertions(+), 449 deletions(-) diff --git a/src/RageLog.cpp b/src/RageLog.cpp index d3dda497de..109dcfba6b 100644 --- a/src/RageLog.cpp +++ b/src/RageLog.cpp @@ -1,449 +1,449 @@ -#include "global.h" -#include "RageLog.h" -#include "RageUtil.h" -#include "RageTimer.h" -#include "RageFile.h" -#include "RageThreads.h" -#include "Foreach.h" - -#include -#if defined(_WINDOWS) -#include -#endif -#include - -RageLog* LOG; // global and accessable from anywhere in the program - -/* - * We have a couple log types and a couple logs. - * - * Traces are for very verbose debug information. Use them as much as you want. - * - * Warnings are for things that shouldn't happen, but can be dealt with. (If - * they can't be dealt with, use RageException::Throw, which will also send a - * warning.) - * - * Info is for important bits of information. These should be used selectively. - * Try to keep Info text dense; lots of information is fine, but try to keep - * it to a reasonable total length. - * - * log.txt receives all logs. This file can get rather large. - * - * info.txt receives warnings and infos. This file should be fairly small; small - * enough to be mailed without having to be edited or zipped, and small enough - * to be very easily read. - */ - -/* Map data names to logged data. - * - * This lets us keep individual bits of changing information to be present - * in crash logs. For example, we want to know which file was being processed - * if we crash during a texture load. However, we don't want to log every - * load, since there are a huge number, even for log.txt. We only want to - * know the current one, if any. - * - * So, when a texture begins loading, we do: - * LOG->MapLog("TextureManager::Load", "Loading foo.png"); - * and when it finishes loading without crashing, - * LOG->UnmapLog("TextureManager::Load"); - * - * Each time a mapped log changes, we update a block of static text to be put - * in info.txt, so we see "Loading foo.png". - * - * The identifier is never displayed, so we can use a simple local object to - * map/unmap, using any mechanism to generate unique IDs. */ -static map LogMaps; - -#define LOG_PATH "/Logs/log.txt" -#define INFO_PATH "/Logs/info.txt" -#define USER_PATH "/Logs/userlog.txt" - -static RageFile *g_fileLog, *g_fileInfo, *g_fileUserLog; - -/* Mutex writes to the files. Writing to files is not thread-aware, and this is the - * only place we write to the same file from multiple threads. */ -static RageMutex *g_Mutex; - -/* staticlog gets info.txt - * crashlog gets log.txt */ -enum -{ - /* If this is set, the message will also be written to info.txt. (info and warnings) */ - WRITE_TO_INFO = 0x01, - - /* If this is set, the message will also be written to userlog.txt. (user warnings only) */ - WRITE_TO_USER_LOG = 0x02, - - /* Whether this line should be loud when written to log.txt (warnings). */ - WRITE_LOUD = 0x04 -}; - -RageLog::RageLog(): m_bLogToDisk(false), m_bInfoToDisk(false), -m_bUserLogToDisk(false), m_bFlush(false), m_bShowLogOutput(false) -{ - g_fileLog = new RageFile; - g_fileInfo = new RageFile; - g_fileUserLog = new RageFile; - - g_Mutex = new RageMutex( "Log" ); -} - -RageLog::~RageLog() -{ - /* Add the mapped log data to info.txt. */ - const RString AdditionalLog = GetAdditionalLog(); - vector AdditionalLogLines; - split( AdditionalLog, "\n", AdditionalLogLines ); - for( unsigned i = 0; i < AdditionalLogLines.size(); ++i ) - { - Trim( AdditionalLogLines[i] ); - this->Info( "%s", AdditionalLogLines[i].c_str() ); - } - - Flush(); - SetShowLogOutput( false ); - g_fileLog->Close(); - g_fileInfo->Close(); - g_fileUserLog->Close(); - - SAFE_DELETE( g_Mutex ); - SAFE_DELETE( g_fileLog ); - SAFE_DELETE( g_fileInfo ); - SAFE_DELETE( g_fileUserLog ); -} - -void RageLog::SetLogToDisk( bool b ) -{ - if( m_bLogToDisk == b ) - return; - - m_bLogToDisk = b; - - if( !m_bLogToDisk ) - { - if( g_fileLog->IsOpen() ) - g_fileLog->Close(); - return; - } - - if( !g_fileLog->Open( LOG_PATH, RageFile::WRITE|RageFile::STREAMED ) ) - fprintf( stderr, "Couldn't open %s: %s\n", LOG_PATH, g_fileLog->GetError().c_str() ); -} - -void RageLog::SetInfoToDisk( bool b ) -{ - if( m_bInfoToDisk == b ) - return; - - m_bInfoToDisk = b; - - if( !m_bInfoToDisk ) - { - if( g_fileInfo->IsOpen() ) - g_fileInfo->Close(); - return; - } - - if( !g_fileInfo->Open( INFO_PATH, RageFile::WRITE|RageFile::STREAMED ) ) - fprintf( stderr, "Couldn't open %s: %s\n", INFO_PATH, g_fileInfo->GetError().c_str() ); -} - -void RageLog::SetUserLogToDisk( bool b ) -{ - if( m_bUserLogToDisk == b ) - return; - - m_bUserLogToDisk = b; - - if( !m_bUserLogToDisk ) - { - if( g_fileUserLog->IsOpen() ) - g_fileUserLog->Close(); - return; - } - if( !g_fileUserLog->Open(USER_PATH, RageFile::WRITE|RageFile::STREAMED) ) - fprintf( stderr, "Couldn't open %s: %s\n", USER_PATH, g_fileUserLog->GetError().c_str() ); -} - -void RageLog::SetFlushing( bool b ) -{ - m_bFlush = b; -} - -/* Enable or disable display of output to stdout, or a console window in Windows. */ -void RageLog::SetShowLogOutput( bool show ) -{ - m_bShowLogOutput = show; - -#if defined(WIN32) - if( m_bShowLogOutput ) - { - // create a new console window and attach standard handles - AllocConsole(); - freopen( "CONOUT$","wb", stdout ); - freopen( "CONOUT$","wb", stderr ); - } - else - { - FreeConsole(); - } -#endif -} - -void RageLog::Trace( const char *fmt, ... ) -{ - va_list va; - va_start( va, fmt ); - RString sBuff = vssprintf( fmt, va ); - va_end( va ); - - Write( 0, sBuff ); -} - -/* Use this for more important information; it'll always be included - * in crash dumps. */ -void RageLog::Info( const char *fmt, ... ) -{ - va_list va; - va_start( va, fmt ); - RString sBuff = vssprintf( fmt, va ); - va_end( va ); - - Write( WRITE_TO_INFO, sBuff ); -} - -void RageLog::Warn( const char *fmt, ... ) -{ - va_list va; - va_start( va, fmt ); - RString sBuff = vssprintf( fmt, va ); - va_end( va ); - - Write( WRITE_TO_INFO | WRITE_LOUD, sBuff ); -} - -void RageLog::UserLog( const RString &sType, const RString &sElement, const char *fmt, ... ) -{ - va_list va; - va_start( va, fmt ); - RString sBuf = vssprintf( fmt, va ); - va_end( va ); - - if( !sType.empty() ) - sBuf = ssprintf( "%s \"%s\" %s", sType.c_str(), sElement.c_str(), sBuf.c_str() ); - - Write( WRITE_TO_USER_LOG, sBuf ); -} - -void RageLog::Write( int where, const RString &sLine ) -{ - LockMut( *g_Mutex ); - - const char *const sWarningSeparator = "/////////////////////////////////////////"; - vector asLines; - split( sLine, "\n", asLines, false ); - if( where & WRITE_LOUD ) - { - if( m_bLogToDisk && g_fileLog->IsOpen() ) - g_fileLog->PutLine( sWarningSeparator ); - puts( sWarningSeparator ); - } - - RString sTimestamp = SecondsToMMSSMsMsMs( RageTimer::GetTimeSinceStart() ) + ": "; - RString sWarning; - if( where & WRITE_LOUD ) - sWarning = "WARNING: "; - - for( unsigned i = 0; i < asLines.size(); ++i ) - { - RString &sStr = asLines[i]; - - if( sWarning.size() ) - sStr.insert( 0, sWarning ); - - if( m_bShowLogOutput || (where&WRITE_TO_INFO) ) - puts(sStr); //fputws( (const wchar_t *)sStr.c_str(), stdout ); - if( where & WRITE_TO_INFO ) - AddToInfo( sStr ); - if( m_bLogToDisk && (where&WRITE_TO_INFO) && g_fileInfo->IsOpen() ) - g_fileInfo->PutLine( sStr ); - if( m_bUserLogToDisk && (where&WRITE_TO_USER_LOG) && g_fileUserLog->IsOpen() ) - g_fileUserLog->PutLine( sStr ); - - /* Add a timestamp to log.txt and RecentLogs, but not the rest of info.txt - * and stdout. */ - sStr.insert( 0, sTimestamp ); - - AddToRecentLogs( sStr ); - - if( m_bLogToDisk && g_fileLog->IsOpen() ) - g_fileLog->PutLine( sStr ); - } - - if( where & WRITE_LOUD ) - { - if( m_bLogToDisk && g_fileLog->IsOpen() && (where & WRITE_LOUD) ) - g_fileLog->PutLine( sWarningSeparator ); - puts( sWarningSeparator ); - } - if( m_bFlush || (where & WRITE_TO_INFO) ) - Flush(); -} - - -void RageLog::Flush() -{ - g_fileLog->Flush(); - g_fileInfo->Flush(); - g_fileUserLog->Flush(); -} - - -#define NEWLINE "\n" - -static char staticlog[1024*32]=""; -static unsigned staticlog_size = 0; -void RageLog::AddToInfo( const RString &str ) -{ - static bool limit_reached = false; - if( limit_reached ) - return; - - unsigned len = str.size() + strlen( NEWLINE ); - if( staticlog_size + len > sizeof(staticlog) ) - { - const RString txt( NEWLINE "Staticlog limit reached" NEWLINE ); - - const unsigned pos = min( staticlog_size, sizeof(staticlog) - txt.size() ); - memcpy( staticlog+pos, txt.data(), txt.size() ); - limit_reached = true; - return; - } - - memcpy( staticlog+staticlog_size, str.data(), str.size() ); - staticlog_size += str.size(); - memcpy( staticlog+staticlog_size, NEWLINE, strlen(NEWLINE) ); - staticlog_size += strlen( NEWLINE ); -} - -const char *RageLog::GetInfo() -{ - staticlog[ sizeof(staticlog)-1 ] = 0; - return staticlog; -} - -static const int BACKLOG_LINES = 10; -static char backlog[BACKLOG_LINES][1024]; -static int backlog_start=0, backlog_cnt=0; -void RageLog::AddToRecentLogs( const RString &str ) -{ - unsigned len = str.size(); - if( len > sizeof(backlog[backlog_start])-1 ) - len = sizeof(backlog[backlog_start])-1; - - strncpy( backlog[backlog_start], str, len ); - backlog[backlog_start] [ len ] = 0; - - backlog_start++; - if( backlog_start > backlog_cnt ) - backlog_cnt=backlog_start; - backlog_start %= BACKLOG_LINES; -} - -const char *RageLog::GetRecentLog( int n ) -{ - if( n >= BACKLOG_LINES || n >= backlog_cnt ) - return NULL; - - if( backlog_cnt == BACKLOG_LINES ) - { - n += backlog_start; - n %= BACKLOG_LINES; - } - /* Make sure it's terminated: */ - backlog[n][ sizeof(backlog[n])-1 ] = 0; - - return backlog[n]; -} - - -static char g_AdditionalLogStr[10240] = ""; -static int g_AdditionalLogSize = 0; - -void RageLog::UpdateMappedLog() -{ - RString str; - FOREACHM_CONST( RString, RString, LogMaps, i ) - str += ssprintf( "%s" NEWLINE, i->second.c_str() ); - - g_AdditionalLogSize = min( sizeof(g_AdditionalLogStr), str.size()+1 ); - memcpy( g_AdditionalLogStr, str.c_str(), g_AdditionalLogSize ); - g_AdditionalLogStr[ sizeof(g_AdditionalLogStr)-1 ] = 0; -} - -const char *RageLog::GetAdditionalLog() -{ - int size = min( g_AdditionalLogSize, (int) sizeof(g_AdditionalLogStr)-1 ); - g_AdditionalLogStr[size] = 0; - return g_AdditionalLogStr; -} - -void RageLog::MapLog( const RString &key, const char *fmt, ... ) -{ - RString s; - - va_list va; - va_start( va, fmt ); - s += vssprintf( fmt, va ); - va_end( va ); - - LogMaps[key] = s; - UpdateMappedLog(); -} - -void RageLog::UnmapLog( const RString &key ) -{ - LogMaps.erase( key ); - UpdateMappedLog(); -} - -void ShowWarningOrTrace( const char *file, int line, const char *message, bool bWarning ) -{ - /* Ignore everything up to and including the first "src/". */ - const char *temp = strstr( file, "src/" ); - if( temp ) - file = temp + 4; - - void (RageLog::*method)(const char *fmt, ...) = bWarning ? &RageLog::Warn : &RageLog::Trace; - - if( LOG ) - (LOG->*method)( "%s:%i: %s", file, line, message ); - else - fprintf( stderr, "%s:%i: %s\n", file, line, message ); -} - - -/* - * Copyright (c) 2001-2004 Chris Danford, Glenn Maynard - * 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 "RageLog.h" +#include "RageUtil.h" +#include "RageTimer.h" +#include "RageFile.h" +#include "RageThreads.h" +#include "Foreach.h" + +#include +#if defined(_WINDOWS) +#include +#endif +#include + +RageLog* LOG; // global and accessable from anywhere in the program + +/* + * We have a couple log types and a couple logs. + * + * Traces are for very verbose debug information. Use them as much as you want. + * + * Warnings are for things that shouldn't happen, but can be dealt with. (If + * they can't be dealt with, use RageException::Throw, which will also send a + * warning.) + * + * Info is for important bits of information. These should be used selectively. + * Try to keep Info text dense; lots of information is fine, but try to keep + * it to a reasonable total length. + * + * log.txt receives all logs. This file can get rather large. + * + * info.txt receives warnings and infos. This file should be fairly small; small + * enough to be mailed without having to be edited or zipped, and small enough + * to be very easily read. + */ + +/* Map data names to logged data. + * + * This lets us keep individual bits of changing information to be present + * in crash logs. For example, we want to know which file was being processed + * if we crash during a texture load. However, we don't want to log every + * load, since there are a huge number, even for log.txt. We only want to + * know the current one, if any. + * + * So, when a texture begins loading, we do: + * LOG->MapLog("TextureManager::Load", "Loading foo.png"); + * and when it finishes loading without crashing, + * LOG->UnmapLog("TextureManager::Load"); + * + * Each time a mapped log changes, we update a block of static text to be put + * in info.txt, so we see "Loading foo.png". + * + * The identifier is never displayed, so we can use a simple local object to + * map/unmap, using any mechanism to generate unique IDs. */ +static map LogMaps; + +#define LOG_PATH "/Logs/log.txt" +#define INFO_PATH "/Logs/info.txt" +#define USER_PATH "/Logs/userlog.txt" + +static RageFile *g_fileLog, *g_fileInfo, *g_fileUserLog; + +/* Mutex writes to the files. Writing to files is not thread-aware, and this is the + * only place we write to the same file from multiple threads. */ +static RageMutex *g_Mutex; + +/* staticlog gets info.txt + * crashlog gets log.txt */ +enum +{ + /* If this is set, the message will also be written to info.txt. (info and warnings) */ + WRITE_TO_INFO = 0x01, + + /* If this is set, the message will also be written to userlog.txt. (user warnings only) */ + WRITE_TO_USER_LOG = 0x02, + + /* Whether this line should be loud when written to log.txt (warnings). */ + WRITE_LOUD = 0x04 +}; + +RageLog::RageLog(): m_bLogToDisk(false), m_bInfoToDisk(false), +m_bUserLogToDisk(false), m_bFlush(false), m_bShowLogOutput(false) +{ + g_fileLog = new RageFile; + g_fileInfo = new RageFile; + g_fileUserLog = new RageFile; + + g_Mutex = new RageMutex( "Log" ); +} + +RageLog::~RageLog() +{ + /* Add the mapped log data to info.txt. */ + const RString AdditionalLog = GetAdditionalLog(); + vector AdditionalLogLines; + split( AdditionalLog, "\n", AdditionalLogLines ); + for( unsigned i = 0; i < AdditionalLogLines.size(); ++i ) + { + Trim( AdditionalLogLines[i] ); + this->Info( "%s", AdditionalLogLines[i].c_str() ); + } + + Flush(); + SetShowLogOutput( false ); + g_fileLog->Close(); + g_fileInfo->Close(); + g_fileUserLog->Close(); + + SAFE_DELETE( g_Mutex ); + SAFE_DELETE( g_fileLog ); + SAFE_DELETE( g_fileInfo ); + SAFE_DELETE( g_fileUserLog ); +} + +void RageLog::SetLogToDisk( bool b ) +{ + if( m_bLogToDisk == b ) + return; + + m_bLogToDisk = b; + + if( !m_bLogToDisk ) + { + if( g_fileLog->IsOpen() ) + g_fileLog->Close(); + return; + } + + if( !g_fileLog->Open( LOG_PATH, RageFile::WRITE|RageFile::STREAMED ) ) + fprintf( stderr, "Couldn't open %s: %s\n", LOG_PATH, g_fileLog->GetError().c_str() ); +} + +void RageLog::SetInfoToDisk( bool b ) +{ + if( m_bInfoToDisk == b ) + return; + + m_bInfoToDisk = b; + + if( !m_bInfoToDisk ) + { + if( g_fileInfo->IsOpen() ) + g_fileInfo->Close(); + return; + } + + if( !g_fileInfo->Open( INFO_PATH, RageFile::WRITE|RageFile::STREAMED ) ) + fprintf( stderr, "Couldn't open %s: %s\n", INFO_PATH, g_fileInfo->GetError().c_str() ); +} + +void RageLog::SetUserLogToDisk( bool b ) +{ + if( m_bUserLogToDisk == b ) + return; + + m_bUserLogToDisk = b; + + if( !m_bUserLogToDisk ) + { + if( g_fileUserLog->IsOpen() ) + g_fileUserLog->Close(); + return; + } + if( !g_fileUserLog->Open(USER_PATH, RageFile::WRITE|RageFile::STREAMED) ) + fprintf( stderr, "Couldn't open %s: %s\n", USER_PATH, g_fileUserLog->GetError().c_str() ); +} + +void RageLog::SetFlushing( bool b ) +{ + m_bFlush = b; +} + +/* Enable or disable display of output to stdout, or a console window in Windows. */ +void RageLog::SetShowLogOutput( bool show ) +{ + m_bShowLogOutput = show; + +#if defined(WIN32) + if( m_bShowLogOutput ) + { + // create a new console window and attach standard handles + AllocConsole(); + freopen( "CONOUT$","wb", stdout ); + freopen( "CONOUT$","wb", stderr ); + } + else + { + FreeConsole(); + } +#endif +} + +void RageLog::Trace( const char *fmt, ... ) +{ + va_list va; + va_start( va, fmt ); + RString sBuff = vssprintf( fmt, va ); + va_end( va ); + + Write( 0, sBuff ); +} + +/* Use this for more important information; it'll always be included + * in crash dumps. */ +void RageLog::Info( const char *fmt, ... ) +{ + va_list va; + va_start( va, fmt ); + RString sBuff = vssprintf( fmt, va ); + va_end( va ); + + Write( WRITE_TO_INFO, sBuff ); +} + +void RageLog::Warn( const char *fmt, ... ) +{ + va_list va; + va_start( va, fmt ); + RString sBuff = vssprintf( fmt, va ); + va_end( va ); + + Write( WRITE_TO_INFO | WRITE_LOUD, sBuff ); +} + +void RageLog::UserLog( const RString &sType, const RString &sElement, const char *fmt, ... ) +{ + va_list va; + va_start( va, fmt ); + RString sBuf = vssprintf( fmt, va ); + va_end( va ); + + if( !sType.empty() ) + sBuf = ssprintf( "%s \"%s\" %s", sType.c_str(), sElement.c_str(), sBuf.c_str() ); + + Write( WRITE_TO_USER_LOG, sBuf ); +} + +void RageLog::Write( int where, const RString &sLine ) +{ + LockMut( *g_Mutex ); + + const char *const sWarningSeparator = "/////////////////////////////////////////"; + vector asLines; + split( sLine, "\n", asLines, false ); + if( where & WRITE_LOUD ) + { + if( m_bLogToDisk && g_fileLog->IsOpen() ) + g_fileLog->PutLine( sWarningSeparator ); + puts( sWarningSeparator ); + } + + RString sTimestamp = SecondsToMMSSMsMsMs( RageTimer::GetTimeSinceStart() ) + ": "; + RString sWarning; + if( where & WRITE_LOUD ) + sWarning = "WARNING: "; + + for( unsigned i = 0; i < asLines.size(); ++i ) + { + RString &sStr = asLines[i]; + + if( sWarning.size() ) + sStr.insert( 0, sWarning ); + + if( m_bShowLogOutput || (where&WRITE_TO_INFO) ) + puts(sStr); //fputws( (const wchar_t *)sStr.c_str(), stdout ); + if( where & WRITE_TO_INFO ) + AddToInfo( sStr ); + if( m_bLogToDisk && (where&WRITE_TO_INFO) && g_fileInfo->IsOpen() ) + g_fileInfo->PutLine( sStr ); + if( m_bUserLogToDisk && (where&WRITE_TO_USER_LOG) && g_fileUserLog->IsOpen() ) + g_fileUserLog->PutLine( sStr ); + + /* Add a timestamp to log.txt and RecentLogs, but not the rest of info.txt + * and stdout. */ + sStr.insert( 0, sTimestamp ); + + AddToRecentLogs( sStr ); + + if( m_bLogToDisk && g_fileLog->IsOpen() ) + g_fileLog->PutLine( sStr ); + } + + if( where & WRITE_LOUD ) + { + if( m_bLogToDisk && g_fileLog->IsOpen() && (where & WRITE_LOUD) ) + g_fileLog->PutLine( sWarningSeparator ); + puts( sWarningSeparator ); + } + if( m_bFlush || (where & WRITE_TO_INFO) ) + Flush(); +} + + +void RageLog::Flush() +{ + g_fileLog->Flush(); + g_fileInfo->Flush(); + g_fileUserLog->Flush(); +} + + +#define NEWLINE "\n" + +static char staticlog[1024*32]=""; +static unsigned staticlog_size = 0; +void RageLog::AddToInfo( const RString &str ) +{ + static bool limit_reached = false; + if( limit_reached ) + return; + + unsigned len = str.size() + strlen( NEWLINE ); + if( staticlog_size + len > sizeof(staticlog) ) + { + const RString txt( NEWLINE "Staticlog limit reached" NEWLINE ); + + const unsigned pos = min( staticlog_size, sizeof(staticlog) - txt.size() ); + memcpy( staticlog+pos, txt.data(), txt.size() ); + limit_reached = true; + return; + } + + memcpy( staticlog+staticlog_size, str.data(), str.size() ); + staticlog_size += str.size(); + memcpy( staticlog+staticlog_size, NEWLINE, strlen(NEWLINE) ); + staticlog_size += strlen( NEWLINE ); +} + +const char *RageLog::GetInfo() +{ + staticlog[ sizeof(staticlog)-1 ] = 0; + return staticlog; +} + +static const int BACKLOG_LINES = 10; +static char backlog[BACKLOG_LINES][1024]; +static int backlog_start=0, backlog_cnt=0; +void RageLog::AddToRecentLogs( const RString &str ) +{ + unsigned len = str.size(); + if( len > sizeof(backlog[backlog_start])-1 ) + len = sizeof(backlog[backlog_start])-1; + + strncpy( backlog[backlog_start], str, len ); + backlog[backlog_start] [ len ] = 0; + + backlog_start++; + if( backlog_start > backlog_cnt ) + backlog_cnt=backlog_start; + backlog_start %= BACKLOG_LINES; +} + +const char *RageLog::GetRecentLog( int n ) +{ + if( n >= BACKLOG_LINES || n >= backlog_cnt ) + return NULL; + + if( backlog_cnt == BACKLOG_LINES ) + { + n += backlog_start; + n %= BACKLOG_LINES; + } + /* Make sure it's terminated: */ + backlog[n][ sizeof(backlog[n])-1 ] = 0; + + return backlog[n]; +} + + +static char g_AdditionalLogStr[10240] = ""; +static int g_AdditionalLogSize = 0; + +void RageLog::UpdateMappedLog() +{ + RString str; + for (auto const &i : LogMaps) + str += ssprintf( "%s" NEWLINE, i.second.c_str() ); + + g_AdditionalLogSize = min( sizeof(g_AdditionalLogStr), str.size()+1 ); + memcpy( g_AdditionalLogStr, str.c_str(), g_AdditionalLogSize ); + g_AdditionalLogStr[ sizeof(g_AdditionalLogStr)-1 ] = 0; +} + +const char *RageLog::GetAdditionalLog() +{ + int size = min( g_AdditionalLogSize, (int) sizeof(g_AdditionalLogStr)-1 ); + g_AdditionalLogStr[size] = 0; + return g_AdditionalLogStr; +} + +void RageLog::MapLog( const RString &key, const char *fmt, ... ) +{ + RString s; + + va_list va; + va_start( va, fmt ); + s += vssprintf( fmt, va ); + va_end( va ); + + LogMaps[key] = s; + UpdateMappedLog(); +} + +void RageLog::UnmapLog( const RString &key ) +{ + LogMaps.erase( key ); + UpdateMappedLog(); +} + +void ShowWarningOrTrace( const char *file, int line, const char *message, bool bWarning ) +{ + /* Ignore everything up to and including the first "src/". */ + const char *temp = strstr( file, "src/" ); + if( temp ) + file = temp + 4; + + void (RageLog::*method)(const char *fmt, ...) = bWarning ? &RageLog::Warn : &RageLog::Trace; + + if( LOG ) + (LOG->*method)( "%s:%i: %s", file, line, message ); + else + fprintf( stderr, "%s:%i: %s\n", file, line, message ); +} + + +/* + * Copyright (c) 2001-2004 Chris Danford, Glenn Maynard + * 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. + */ From 13ded3d92d49d2a3cc82cdf82a62fa955afb6976 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 15:22:27 -0400 Subject: [PATCH 037/281] Simplify the loop with auto and WHITE SPACE. --- src/RageTextureManager.cpp | 726 ++++++++++++++++++------------------- 1 file changed, 363 insertions(+), 363 deletions(-) diff --git a/src/RageTextureManager.cpp b/src/RageTextureManager.cpp index 8062cabe2e..b04f56030f 100644 --- a/src/RageTextureManager.cpp +++ b/src/RageTextureManager.cpp @@ -1,363 +1,363 @@ -/* - * Texture garbage collection policies: - * - * Default: When DelayedDelete is off, delete unused textures immediately. - * When on, only delete textures when we change themes, on DoDelayedDelete(). - * - * Volatile: Delete unused textures once they've been used at least once. Ignore - * DelayedDelete. - * - * This is for banners. We don't want to load all low-quality banners in - * memory at once, since it might be ten megs of textures, and we don't - * need to, since we can reload them very quickly. We don't want to keep - * high quality textures in memory, either, although it's unlikely that a - * player could actually view all banners long enough to transition to them - * all in the course of one song select screen. - * - * If a texture is loaded as DEFAULT that was already loaded as VOLATILE, DEFAULT - * overrides. - */ - -#include "global.h" -#include "RageTextureManager.h" -#include "RageBitmapTexture.h" -#include "arch/MovieTexture/MovieTexture.h" -#include "RageUtil.h" -#include "RageLog.h" -#include "RageDisplay.h" -#include "Foreach.h" - -#include - -RageTextureManager* TEXTUREMAN = NULL; - -namespace -{ - map m_mapPathToTexture; -}; - -RageTextureManager::RageTextureManager(): - m_iNoWarnAboutOddDimensions(0), - m_TexturePolicy(RageTextureID::TEX_DEFAULT) {} - -RageTextureManager::~RageTextureManager() -{ - FOREACHM( RageTextureID, RageTexture*, m_mapPathToTexture, i ) - { - RageTexture* pTexture = i->second; - if( pTexture->m_iRefCount ) - LOG->Trace( "TEXTUREMAN LEAK: '%s', RefCount = %d.", i->first.filename.c_str(), pTexture->m_iRefCount ); - SAFE_DELETE( pTexture ); - } -} - -void RageTextureManager::Update( float fDeltaTime ) -{ - FOREACHM( RageTextureID, RageTexture*, m_mapPathToTexture, i ) - { - RageTexture* pTexture = i->second; - pTexture->Update( fDeltaTime ); - } -} - -void RageTextureManager::AdjustTextureID( RageTextureID &ID ) const -{ - if( ID.iColorDepth == -1 ) - ID.iColorDepth = m_Prefs.m_iTextureColorDepth; - ID.iMaxSize = min( ID.iMaxSize, m_Prefs.m_iMaxTextureResolution ); - if( m_Prefs.m_bMipMaps ) - ID.bMipMaps = true; -} - -bool RageTextureManager::IsTextureRegistered( RageTextureID ID ) const -{ - AdjustTextureID(ID); - return m_mapPathToTexture.find(ID) != m_mapPathToTexture.end(); -} - -/* If you've set up a texture yourself, register it here so it can be referenced - * and deleted by ID. This takes ownership; the texture will be freed according to - * its GC policy. */ -void RageTextureManager::RegisterTexture( RageTextureID ID, RageTexture *pTexture ) -{ - AdjustTextureID(ID); - - /* Make sure we don't already have a texture with this ID. If we do, the - * caller should have used it. */ - std::map::iterator p = m_mapPathToTexture.find(ID); - if( p != m_mapPathToTexture.end() ) - { - /* Oops, found the texture. */ - RageException::Throw( "Custom texture \"%s\" already registered!", ID.filename.c_str() ); - } - - m_mapPathToTexture[ID] = pTexture; -} - -static const RString g_sDefaultTextureName = "__blank__"; -RageTextureID RageTextureManager::GetDefaultTextureID() -{ - return RageTextureID( g_sDefaultTextureName ); -} - -class RageTexture_Default: public RageTexture -{ -public: - RageTexture_Default(): - RageTexture( RageTextureID() ), m_uTexHandle(0) - { - m_iSourceWidth = m_iSourceHeight = 1; - m_iTextureWidth = m_iTextureHeight = 1; - m_iImageWidth = m_iImageHeight = 1; - CreateFrameRects(); - } - unsigned GetTexHandle() const { return m_uTexHandle; } - -private: - unsigned m_uTexHandle; -}; - -// Load and unload textures from disk. -RageTexture* RageTextureManager::LoadTextureInternal( RageTextureID ID ) -{ - CHECKPOINT_M( ssprintf( "RageTextureManager::LoadTexture(%s).", ID.filename.c_str() ) ); - - AdjustTextureID(ID); - - /* We could have two copies of the same bitmap if there are equivalent but - * different paths, e.g. "Bitmaps\me.bmp" and "..\Rage PC Edition\Bitmaps\me.bmp". */ - std::map::iterator p = m_mapPathToTexture.find(ID); - if( p != m_mapPathToTexture.end() ) - { - /* Found the texture. Just increase the refcount and return it. */ - RageTexture* pTexture = p->second; - pTexture->m_iRefCount++; - return pTexture; - } - - // The texture is not already loaded. Load it. - RString sExt = GetExtension( ID.filename ); - sExt.MakeLower(); - - RageTexture* pTexture; - if( ID.filename == g_sDefaultTextureName ) - pTexture = new RageTexture_Default; - else if( sExt == "ogv" || sExt == "avi" || sExt == "mpg" || sExt == "mpeg" ) - pTexture = RageMovieTexture::Create( ID ); - else - pTexture = new RageBitmapTexture( ID ); - - m_mapPathToTexture[ID] = pTexture; - - return pTexture; -} - -/* Load a normal texture. Use this call to actually use a texture. */ -RageTexture* RageTextureManager::LoadTexture( RageTextureID ID ) -{ - RageTexture* pTexture = LoadTextureInternal( ID ); - if( pTexture ) - pTexture->m_bWasUsed = true; - return pTexture; -} - -RageTexture* RageTextureManager::CopyTexture( RageTexture *pCopy ) -{ - ++pCopy->m_iRefCount; - return pCopy; -} - -void RageTextureManager::VolatileTexture( RageTextureID ID ) -{ - RageTexture* pTexture = LoadTextureInternal( ID ); - pTexture->GetPolicy() = min( pTexture->GetPolicy(), RageTextureID::TEX_VOLATILE ); - UnloadTexture( pTexture ); -} - -void RageTextureManager::UnloadTexture( RageTexture *t ) -{ - if( t == NULL ) - return; - - t->m_iRefCount--; - ASSERT_M( t->m_iRefCount >= 0, ssprintf("%i, %s", t->m_iRefCount, t->GetID().filename.c_str()) ); - - if( t->m_iRefCount ) - return; /* Can't unload textures that are still referenced. */ - - bool bDeleteThis = false; - - /* Always unload movies, so we don't waste time decoding. */ - if( t->IsAMovie() ) - bDeleteThis = true; - - /* Delete normal textures immediately unless m_bDelayedDelete is is on. */ - if( t->GetPolicy() == RageTextureID::TEX_DEFAULT && !m_Prefs.m_bDelayedDelete ) - bDeleteThis = true; - - /* Delete volatile textures after they've been used at least once. */ - if( t->GetPolicy() == RageTextureID::TEX_VOLATILE && t->m_bWasUsed ) - bDeleteThis = true; - - if( bDeleteThis ) - DeleteTexture( t ); -} - -void RageTextureManager::DeleteTexture( RageTexture *t ) -{ - ASSERT( t->m_iRefCount == 0 ); - LOG->Trace( "RageTextureManager: deleting '%s'.", t->GetID().filename.c_str() ); - - FOREACHM( RageTextureID, RageTexture*, m_mapPathToTexture, i ) - { - if( i->second == t ) - { - m_mapPathToTexture.erase( i ); // remove map entry - SAFE_DELETE( t ); // free the texture - return; - } - } - - FAIL_M("Tried to delete a texture that wasn't loaded"); -} - -void RageTextureManager::GarbageCollect( GCType type ) -{ - // Search for old textures with refcount==0 to unload - LOG->Trace("Performing texture garbage collection."); - - for( std::map::iterator i = m_mapPathToTexture.begin(); - i != m_mapPathToTexture.end(); ) - { - std::map::iterator j = i; - i++; - - RString sPath = j->first.filename; - RageTexture* t = j->second; - - if( t->m_iRefCount ) - continue; /* Can't unload textures that are still referenced. */ - - bool bDeleteThis = false; - if( type==screen_changed ) - { - RageTextureID::TexPolicy policy = t->GetPolicy(); - switch( policy ) - { - case RageTextureID::TEX_DEFAULT: - /* If m_bDelayedDelete, wait until delayed_delete. If !m_bDelayedDelete, - * it should have been deleted when it reached no references, but we - * might have just changed the preference. */ - if( !m_Prefs.m_bDelayedDelete ) - bDeleteThis = true; - break; - case RageTextureID::TEX_VOLATILE: - bDeleteThis = true; - break; - default: - FAIL_M(ssprintf("Invalid texture policy: %i", policy)); - } - } - - /* This happens when we change themes; free all textures. */ - if( type==delayed_delete ) - bDeleteThis = true; - - if( bDeleteThis ) - DeleteTexture( t ); - } -} - - -void RageTextureManager::ReloadAll() -{ - DisableOddDimensionWarning(); - - /* Let's get rid of all unreferenced textures, so we don't reload a - * ton of cached data that we're not necessarily going to use. */ - DoDelayedDelete(); - - FOREACHM( RageTextureID, RageTexture*, m_mapPathToTexture, i ) - { - i->second->Reload(); - } - - EnableOddDimensionWarning(); -} - -/* In some cases, changing the display mode will reset the rendering context, - * releasing all textures. We don't want to reload immediately if that happens, - * since we might be changing texture preferences too, which also may have to reload - * textures. Instead, tell all textures that their texture ID is invalid, so it - * doesn't try to free it later when we really do reload (since that ID might be - * associated with a different texture). Ack. */ -void RageTextureManager::InvalidateTextures() -{ - FOREACHM( RageTextureID, RageTexture*, m_mapPathToTexture, i ) - { - RageTexture* pTexture = i->second; - pTexture->Invalidate(); - } -} - -bool RageTextureManager::SetPrefs( RageTextureManagerPrefs prefs ) -{ - bool bNeedReload = false; - if( m_Prefs != prefs ) - bNeedReload = true; - - m_Prefs = prefs; - - ASSERT( m_Prefs.m_iTextureColorDepth==16 || m_Prefs.m_iTextureColorDepth==32 ); - ASSERT( m_Prefs.m_iMovieColorDepth==16 || m_Prefs.m_iMovieColorDepth==32 ); - return bNeedReload; -} - -void RageTextureManager::DiagnosticOutput() const -{ - unsigned iCount = distance( m_mapPathToTexture.begin(), m_mapPathToTexture.end() ); - LOG->Trace( "%u textures loaded:", iCount ); - - int iTotal = 0; - FOREACHM_CONST( RageTextureID, RageTexture*, m_mapPathToTexture, i ) - { - const RageTextureID &ID = i->first; - const RageTexture *pTex = i->second; - - RString sDiags = DISPLAY->GetTextureDiagnostics( pTex->GetTexHandle() ); - RString sStr = ssprintf( "%3ix%3i (%2i)", pTex->GetTextureHeight(), pTex->GetTextureWidth(), - pTex->m_iRefCount ); - - if( sDiags != "" ) - sStr += " " + sDiags; - - LOG->Trace( " %-40s %s", sStr.c_str(), Basename(ID.filename).c_str() ); - iTotal += pTex->GetTextureHeight() * pTex->GetTextureWidth(); - } - LOG->Trace( "total %3i texels", iTotal ); -} - -/* - * Copyright (c) 2001-2004 Chris Danford, Glenn Maynard - * 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. - */ +/* + * Texture garbage collection policies: + * + * Default: When DelayedDelete is off, delete unused textures immediately. + * When on, only delete textures when we change themes, on DoDelayedDelete(). + * + * Volatile: Delete unused textures once they've been used at least once. Ignore + * DelayedDelete. + * + * This is for banners. We don't want to load all low-quality banners in + * memory at once, since it might be ten megs of textures, and we don't + * need to, since we can reload them very quickly. We don't want to keep + * high quality textures in memory, either, although it's unlikely that a + * player could actually view all banners long enough to transition to them + * all in the course of one song select screen. + * + * If a texture is loaded as DEFAULT that was already loaded as VOLATILE, DEFAULT + * overrides. + */ + +#include "global.h" +#include "RageTextureManager.h" +#include "RageBitmapTexture.h" +#include "arch/MovieTexture/MovieTexture.h" +#include "RageUtil.h" +#include "RageLog.h" +#include "RageDisplay.h" +#include "Foreach.h" + +#include + +RageTextureManager* TEXTUREMAN = NULL; + +namespace +{ + map m_mapPathToTexture; +}; + +RageTextureManager::RageTextureManager(): + m_iNoWarnAboutOddDimensions(0), + m_TexturePolicy(RageTextureID::TEX_DEFAULT) {} + +RageTextureManager::~RageTextureManager() +{ + FOREACHM( RageTextureID, RageTexture*, m_mapPathToTexture, i ) + { + RageTexture* pTexture = i->second; + if( pTexture->m_iRefCount ) + LOG->Trace( "TEXTUREMAN LEAK: '%s', RefCount = %d.", i->first.filename.c_str(), pTexture->m_iRefCount ); + SAFE_DELETE( pTexture ); + } +} + +void RageTextureManager::Update( float fDeltaTime ) +{ + FOREACHM( RageTextureID, RageTexture*, m_mapPathToTexture, i ) + { + RageTexture* pTexture = i->second; + pTexture->Update( fDeltaTime ); + } +} + +void RageTextureManager::AdjustTextureID( RageTextureID &ID ) const +{ + if( ID.iColorDepth == -1 ) + ID.iColorDepth = m_Prefs.m_iTextureColorDepth; + ID.iMaxSize = min( ID.iMaxSize, m_Prefs.m_iMaxTextureResolution ); + if( m_Prefs.m_bMipMaps ) + ID.bMipMaps = true; +} + +bool RageTextureManager::IsTextureRegistered( RageTextureID ID ) const +{ + AdjustTextureID(ID); + return m_mapPathToTexture.find(ID) != m_mapPathToTexture.end(); +} + +/* If you've set up a texture yourself, register it here so it can be referenced + * and deleted by ID. This takes ownership; the texture will be freed according to + * its GC policy. */ +void RageTextureManager::RegisterTexture( RageTextureID ID, RageTexture *pTexture ) +{ + AdjustTextureID(ID); + + /* Make sure we don't already have a texture with this ID. If we do, the + * caller should have used it. */ + std::map::iterator p = m_mapPathToTexture.find(ID); + if( p != m_mapPathToTexture.end() ) + { + /* Oops, found the texture. */ + RageException::Throw( "Custom texture \"%s\" already registered!", ID.filename.c_str() ); + } + + m_mapPathToTexture[ID] = pTexture; +} + +static const RString g_sDefaultTextureName = "__blank__"; +RageTextureID RageTextureManager::GetDefaultTextureID() +{ + return RageTextureID( g_sDefaultTextureName ); +} + +class RageTexture_Default: public RageTexture +{ +public: + RageTexture_Default(): + RageTexture( RageTextureID() ), m_uTexHandle(0) + { + m_iSourceWidth = m_iSourceHeight = 1; + m_iTextureWidth = m_iTextureHeight = 1; + m_iImageWidth = m_iImageHeight = 1; + CreateFrameRects(); + } + unsigned GetTexHandle() const { return m_uTexHandle; } + +private: + unsigned m_uTexHandle; +}; + +// Load and unload textures from disk. +RageTexture* RageTextureManager::LoadTextureInternal( RageTextureID ID ) +{ + CHECKPOINT_M( ssprintf( "RageTextureManager::LoadTexture(%s).", ID.filename.c_str() ) ); + + AdjustTextureID(ID); + + /* We could have two copies of the same bitmap if there are equivalent but + * different paths, e.g. "Bitmaps\me.bmp" and "..\Rage PC Edition\Bitmaps\me.bmp". */ + std::map::iterator p = m_mapPathToTexture.find(ID); + if( p != m_mapPathToTexture.end() ) + { + /* Found the texture. Just increase the refcount and return it. */ + RageTexture* pTexture = p->second; + pTexture->m_iRefCount++; + return pTexture; + } + + // The texture is not already loaded. Load it. + RString sExt = GetExtension( ID.filename ); + sExt.MakeLower(); + + RageTexture* pTexture; + if( ID.filename == g_sDefaultTextureName ) + pTexture = new RageTexture_Default; + else if( sExt == "ogv" || sExt == "avi" || sExt == "mpg" || sExt == "mpeg" ) + pTexture = RageMovieTexture::Create( ID ); + else + pTexture = new RageBitmapTexture( ID ); + + m_mapPathToTexture[ID] = pTexture; + + return pTexture; +} + +/* Load a normal texture. Use this call to actually use a texture. */ +RageTexture* RageTextureManager::LoadTexture( RageTextureID ID ) +{ + RageTexture* pTexture = LoadTextureInternal( ID ); + if( pTexture ) + pTexture->m_bWasUsed = true; + return pTexture; +} + +RageTexture* RageTextureManager::CopyTexture( RageTexture *pCopy ) +{ + ++pCopy->m_iRefCount; + return pCopy; +} + +void RageTextureManager::VolatileTexture( RageTextureID ID ) +{ + RageTexture* pTexture = LoadTextureInternal( ID ); + pTexture->GetPolicy() = min( pTexture->GetPolicy(), RageTextureID::TEX_VOLATILE ); + UnloadTexture( pTexture ); +} + +void RageTextureManager::UnloadTexture( RageTexture *t ) +{ + if( t == NULL ) + return; + + t->m_iRefCount--; + ASSERT_M( t->m_iRefCount >= 0, ssprintf("%i, %s", t->m_iRefCount, t->GetID().filename.c_str()) ); + + if( t->m_iRefCount ) + return; /* Can't unload textures that are still referenced. */ + + bool bDeleteThis = false; + + /* Always unload movies, so we don't waste time decoding. */ + if( t->IsAMovie() ) + bDeleteThis = true; + + /* Delete normal textures immediately unless m_bDelayedDelete is is on. */ + if( t->GetPolicy() == RageTextureID::TEX_DEFAULT && !m_Prefs.m_bDelayedDelete ) + bDeleteThis = true; + + /* Delete volatile textures after they've been used at least once. */ + if( t->GetPolicy() == RageTextureID::TEX_VOLATILE && t->m_bWasUsed ) + bDeleteThis = true; + + if( bDeleteThis ) + DeleteTexture( t ); +} + +void RageTextureManager::DeleteTexture( RageTexture *t ) +{ + ASSERT( t->m_iRefCount == 0 ); + LOG->Trace( "RageTextureManager: deleting '%s'.", t->GetID().filename.c_str() ); + + FOREACHM( RageTextureID, RageTexture*, m_mapPathToTexture, i ) + { + if( i->second == t ) + { + m_mapPathToTexture.erase( i ); // remove map entry + SAFE_DELETE( t ); // free the texture + return; + } + } + + FAIL_M("Tried to delete a texture that wasn't loaded"); +} + +void RageTextureManager::GarbageCollect( GCType type ) +{ + // Search for old textures with refcount==0 to unload + LOG->Trace("Performing texture garbage collection."); + + for( std::map::iterator i = m_mapPathToTexture.begin(); + i != m_mapPathToTexture.end(); ) + { + std::map::iterator j = i; + i++; + + RString sPath = j->first.filename; + RageTexture* t = j->second; + + if( t->m_iRefCount ) + continue; /* Can't unload textures that are still referenced. */ + + bool bDeleteThis = false; + if( type==screen_changed ) + { + RageTextureID::TexPolicy policy = t->GetPolicy(); + switch( policy ) + { + case RageTextureID::TEX_DEFAULT: + /* If m_bDelayedDelete, wait until delayed_delete. If !m_bDelayedDelete, + * it should have been deleted when it reached no references, but we + * might have just changed the preference. */ + if( !m_Prefs.m_bDelayedDelete ) + bDeleteThis = true; + break; + case RageTextureID::TEX_VOLATILE: + bDeleteThis = true; + break; + default: + FAIL_M(ssprintf("Invalid texture policy: %i", policy)); + } + } + + /* This happens when we change themes; free all textures. */ + if( type==delayed_delete ) + bDeleteThis = true; + + if( bDeleteThis ) + DeleteTexture( t ); + } +} + + +void RageTextureManager::ReloadAll() +{ + DisableOddDimensionWarning(); + + /* Let's get rid of all unreferenced textures, so we don't reload a + * ton of cached data that we're not necessarily going to use. */ + DoDelayedDelete(); + + FOREACHM( RageTextureID, RageTexture*, m_mapPathToTexture, i ) + { + i->second->Reload(); + } + + EnableOddDimensionWarning(); +} + +/* In some cases, changing the display mode will reset the rendering context, + * releasing all textures. We don't want to reload immediately if that happens, + * since we might be changing texture preferences too, which also may have to reload + * textures. Instead, tell all textures that their texture ID is invalid, so it + * doesn't try to free it later when we really do reload (since that ID might be + * associated with a different texture). Ack. */ +void RageTextureManager::InvalidateTextures() +{ + FOREACHM( RageTextureID, RageTexture*, m_mapPathToTexture, i ) + { + RageTexture* pTexture = i->second; + pTexture->Invalidate(); + } +} + +bool RageTextureManager::SetPrefs( RageTextureManagerPrefs prefs ) +{ + bool bNeedReload = false; + if( m_Prefs != prefs ) + bNeedReload = true; + + m_Prefs = prefs; + + ASSERT( m_Prefs.m_iTextureColorDepth==16 || m_Prefs.m_iTextureColorDepth==32 ); + ASSERT( m_Prefs.m_iMovieColorDepth==16 || m_Prefs.m_iMovieColorDepth==32 ); + return bNeedReload; +} + +void RageTextureManager::DiagnosticOutput() const +{ + unsigned iCount = distance( m_mapPathToTexture.begin(), m_mapPathToTexture.end() ); + LOG->Trace( "%u textures loaded:", iCount ); + + int iTotal = 0; + for (auto const &i : m_mapPathToTexture) + { + const RageTextureID &ID = i.first; + const RageTexture *pTex = i.second; + + RString sDiags = DISPLAY->GetTextureDiagnostics( pTex->GetTexHandle() ); + RString sStr = ssprintf( "%3ix%3i (%2i)", pTex->GetTextureHeight(), pTex->GetTextureWidth(), + pTex->m_iRefCount ); + + if( sDiags != "" ) + sStr += " " + sDiags; + + LOG->Trace( " %-40s %s", sStr.c_str(), Basename(ID.filename).c_str() ); + iTotal += pTex->GetTextureHeight() * pTex->GetTextureWidth(); + } + LOG->Trace( "total %3i texels", iTotal ); +} + +/* + * Copyright (c) 2001-2004 Chris Danford, Glenn Maynard + * 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. + */ From 08218a3a85a535dd97159f42b7ba9c71e2b5a0c6 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 15:23:31 -0400 Subject: [PATCH 038/281] Another simplification. --- src/RageUtil.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RageUtil.cpp b/src/RageUtil.cpp index d3bdf32c9a..95c548a22d 100644 --- a/src/RageUtil.cpp +++ b/src/RageUtil.cpp @@ -1890,8 +1890,8 @@ void ReplaceEntityText( RString &sText, const map &m ) { RString sFind; - FOREACHM_CONST( char, RString, m, c ) - sFind.append( 1, c->first ); + for (std::pair const &c : m) + sFind.append( 1, c.first ); RString sRet; From 2a5227afbb24c7aaddc7c28a6d1b8b1c5c46b265 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 15:24:55 -0400 Subject: [PATCH 039/281] The last batch of WHITE SPACE loops. ...okay, enough of this running gag. --- src/Foreach.h | 3 - .../Win32/CrashHandlerNetworking.cpp | 1532 ++++++++--------- 2 files changed, 766 insertions(+), 769 deletions(-) diff --git a/src/Foreach.h b/src/Foreach.h index 7fadd94350..9a752c8930 100644 --- a/src/Foreach.h +++ b/src/Foreach.h @@ -11,9 +11,6 @@ for( vector::const_iterator var = (vect).begin(); var != (vect).end(); /** @brief General foreach loop iterating over a map. */ #define FOREACHM( keyType, valType, vect, var ) \ for( map::iterator var = (vect).begin(); var != (vect).end(); ++var ) -/** @brief General foreach loop iterating over a map, using a constant iterator. */ -#define FOREACHM_CONST( keyType, valType, vect, var ) \ -for( map::const_iterator var = (vect).begin(); var != (vect).end(); ++var ) #endif diff --git a/src/archutils/Win32/CrashHandlerNetworking.cpp b/src/archutils/Win32/CrashHandlerNetworking.cpp index a137df636c..c00e846b11 100644 --- a/src/archutils/Win32/CrashHandlerNetworking.cpp +++ b/src/archutils/Win32/CrashHandlerNetworking.cpp @@ -1,766 +1,766 @@ -#include "global.h" -#include "CrashHandlerNetworking.h" -#include "RageThreads.h" -#include "RageLog.h" -#include "RageThreads.h" -#include "RageTimer.h" -#include "RageUtil.h" -#include "Foreach.h" - -#if defined(WINDOWS) -#include -#include -#pragma comment(lib, "wsock32.lib") -#pragma comment(lib, "ws2_32.lib") -#endif - -/* This has an arch-like abstraction layout, since it's intended to become one. - * It'll be moved out of here as things get polished further. The Winsock and - * BSD implementations will be kept separate, since while they look similar, - * they're different in the details, especially when you want to cleanly support - * cancellation. - * - * Design notes: - * - Allow cancellation at any time. The only thing more annoying than a laggy - * network, is an application that won't immediately respond to cancelling. - * (This is even more important here, since we're using this after a crash.) - * - Assume some operations and/or some architectures are going to have problems - * doing everything nonblockingly, and do everything in a thread. This also allows - * more complex network interactions, since it doesn't need to maintain an equally - * complex state machine. - * - When an operation is cancelled or an error occurs, all operations until the - * next call to Close() become no-ops. This allows lenient error checking; it's - * guaranteed that further calls to Read or Write will not reset the error state. - * - The only function that may be called from another thread without serialization is - * Cancel(). - * - * All calls are blocking, except for: - * - * - Cancel(), which is always nonblocking. The operation in progress, if any, - * will be aborted immediately. (The only exception here is incomplete - * implementations, which may block); - * - Close(), if Shutdown() was called first, since the necessary blocking occurs - * during Shutdown; - * - Close(), if an error or cancellation occurred. - * - * Accepting incoming TCP connections is beyond the immediate scope of this interface - * (that would involve a second listener class, which would be a factory for NetworkStream). - * UDP is probably within the scope of this class, but there are some outstanding design - * issues and it's beyond the work I'm doing right now. */ - -class NetworkStream -{ -public: - enum ConnectionType - { - CONN_TCP, - CONN_UDP, - }; - NetworkStream() { } - virtual ~NetworkStream() { } - - // Open a connection. Must be in STATE_IDLE. - virtual void Open( const RString &sHost, int iPort, ConnectionType ct = CONN_TCP ) = 0; - - // Close down a connection. Returns to STATE_IDLE. - virtual void Close() = 0; - - // Wait for all sent data to be flushed, and shut down the connection. - virtual void Shutdown() = 0; - - /* Read data. Block until any data is received, then return all data - * available. Return the number of bytes read. The return value will always - * be >= 0, unless an error or cancellation occured. */ - virtual int Read( void *pBuffer, size_t iSize ) = 0; - - /* Write data to the socket. (Design note: we always write all of the data - * unless an error or cancellation occurs, and those states are checked - * with GetState(). If that happens, the number of bytes written is - * meaningless, since it may have simply been buffered and never sent. - * So, this function returns no value.) */ - virtual void Write( const void *pBuffer, size_t iSize ) = 0; - - /* Cancel the connection. This operation can clear an error state, aborts - * any blocking calls, never fails, and will always result in the socket - * being in STATE_CANCELLED. - * This is true even if the state was STATE_IDLE. For example, the user - * thread may be closing one connection on this object and opening another, - * and the UI thread may call Cancel() in between these operations. - * This operation is threadsafe: a socket can be cancelled from any thread - * while another thread is reading or writing. This is the only function - * that can be called without serialization. */ - virtual void Cancel() = 0; - - enum State - { - // The stream is closed, and is ready to be opened. - STATE_IDLE, - - // The stream is connected and able to send and receive data. - STATE_CONNECTED, - - /* The stream has been shut down on either end (either an EOF from - * the other end, or shutdown() being called). */ - STATE_SHUTDOWN, - - /* When in CANCELLED, all blocking calls fail immediately; this state must be - * cleared explicitly by calling Close(). */ - STATE_CANCELLED, - STATE_ERROR - }; - - State GetState() const { return m_State; } - RString GetError() const { return m_sError; } - -protected: - State m_State; - RString m_sError; -}; - -class NetworkStream_Win32: public NetworkStream -{ -public: - NetworkStream_Win32(); - ~NetworkStream_Win32(); - - void Open( const RString &sHost, int iPort, ConnectionType ct = CONN_TCP ); - void Shutdown(); - void Close(); - int Read( void *pBuffer, size_t iSize ); - void Write( const void *pBuffer, size_t iSize ); - - void Cancel(); - -private: - int WaitForCompletionOrCancellation( int iEvent ); - void SetError( const RString &sError ); - static RString WinSockErrorToString( int iError ); - - SOCKET m_Socket; - HANDLE m_hResolve; - HWND m_hResolveHwnd; - - // This event is signalled on cancellation, to wake us up if we're blocking. - HANDLE m_hCompletionEvent; - - RString m_sHost; - int m_iPort; - - RageMutex m_Mutex; -}; - -NetworkStream *CreateNetworkStream() -{ - static bool bInitted = false; - if( !bInitted ) - { - bInitted = true; - WSADATA WSAData; - WORD iVersionRequested = MAKEWORD(2,0); - if( WSAStartup(iVersionRequested, &WSAData) != 0 ) - return NULL; - } - - return new NetworkStream_Win32; -} - -/** @brief WinSock implementation of NetworkStream. */ -NetworkStream_Win32::NetworkStream_Win32(): - m_Mutex( "NetworkTCPSocket" ) -{ - m_iPort = -1; - m_State = STATE_IDLE; - m_Socket = NULL; -#if defined(WINDOWS) - m_hResolve = NULL; - m_hResolveHwnd = NULL; - m_hCompletionEvent = CreateEvent( NULL, true, false, NULL ); -#endif -} - -NetworkStream_Win32::~NetworkStream_Win32() -{ - Close(); - CloseHandle( m_hCompletionEvent ); -} - -/* Wait for the specified network event to occur, or cancellation, whichever - * happens first. On cancellation, return -1; on error, return the error - * code; on success, return 0. */ -int NetworkStream_Win32::WaitForCompletionOrCancellation( int iEvent ) -{ - while(1) - { - int iRet = WaitForSingleObject( m_hCompletionEvent, INFINITE ); - if( iRet != WAIT_OBJECT_0 ) - continue; - - m_Mutex.Lock(); - - // This will reset the event. Do this while we hold the lock. - WSANETWORKEVENTS events; - WSAEnumNetworkEvents( m_Socket, m_hCompletionEvent, &events ); - - // Was the event signalled due to cancellation? - if( m_State == STATE_CANCELLED ) - { - m_Mutex.Unlock(); - return -1; - } - - m_Mutex.Unlock(); - - // If the event didn't actually occur, keep waiting. - if( (events.lNetworkEvents & (1<h_addr_list[0]; - addr.sin_family = PF_INET; - addr.sin_port = htons( (uint16_t) iPort ); - - m_Mutex.Lock(); - m_Socket = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP ); - if( m_Socket == INVALID_SOCKET ) - { - int iError = WSAGetLastError(); - SetError( ssprintf("Error creating socket: %s", WinSockErrorToString(iError).c_str() ) ); - return; - } - - /* Set up the completion event to be signalled when these events occur. - * This also sets the socket to nonblocking. */ - WSAEventSelect( m_Socket, m_hCompletionEvent, FD_CONNECT|FD_READ|FD_WRITE|FD_CLOSE ); - - //fcntl( m_Socket, O_NONBLOCK, 1 ); - - // Start opening the connection. - int iResult = connect(m_Socket, (SOCKADDR *) &addr, sizeof(addr)); - - m_Mutex.Unlock(); - - // We expect EINPROGRESS/WSAEWOULDBLOCK. - if( iResult == SOCKET_ERROR ) - { - // Block until the connection attempt completes. - int iError = WSAGetLastError(); - if( iError == WSAEWOULDBLOCK ) - iError = WaitForCompletionOrCancellation( FD_CONNECT_BIT ); - if( iError ) - { - SetError( ssprintf("Couldn't connect: %s", WinSockErrorToString(iError).c_str() ) ); - return; - } - } - } - - m_State = STATE_CONNECTED; -} - -void NetworkStream_Win32::Close() -{ - if( m_State == STATE_IDLE ) - return; - - /* If we have an active, stable connection, make sure we flush any data - * completely before closing. If you don't want to do this, call Cancel() - * first. */ - Shutdown(); - - m_Mutex.Lock(); - if( m_State == STATE_CANCELLED ) - return; - - if( m_Socket != INVALID_SOCKET ) - closesocket( m_Socket ); - m_Socket = INVALID_SOCKET; - m_State = STATE_IDLE; - m_Mutex.Unlock(); -} - -void NetworkStream_Win32::Shutdown() -{ - /* If we're not in a normal, stable state (eg. an error occurred, or we were - * cancelled), don't wait to flush. */ - if( m_State != STATE_CONNECTED ) - return; - - shutdown( m_Socket, SD_BOTH ); - m_State = STATE_SHUTDOWN; -} - -void NetworkStream_Win32::Cancel() -{ - m_Mutex.Lock(); - - // Mark cancellation. - m_State = STATE_CANCELLED; - - // If resolving, abort the resolve. - if( m_hResolve != NULL ) - { - /* When we cancel the request, no message at all will be sent to the window, - * so we need to do it ourself to inform it that it was cancelled. Be sure - * to only do this on successful cancel. */ - if( WSACancelAsyncRequest(m_hResolve) == 0 ) - PostMessage( m_hResolveHwnd, WM_USER+1, 0, 0 ); - } - - // Break out if we're waiting in WaitForCompletionOrCancellation(). - SetEvent( m_hCompletionEvent ); - - m_Mutex.Unlock(); -} - -int NetworkStream_Win32::Read( void *pBuffer, size_t iSize ) -{ - if( m_State != STATE_CONNECTED ) - return 0; - - char *p = (char *) pBuffer; - int iRead = 0; - while( iSize > 0 ) - { - int iRet = recv( m_Socket, p, iSize, 0 ); - if( iRet > 0 ) - { - p += iRet; - iSize -= iRet; - iRead += iRet; - continue; - } - - if( iRet == 0 ) - { - // We hit EOF. - break; - } - - int iError = WSAGetLastError(); - if( iError == WSAEWOULDBLOCK ) - { - // There's no date to read. If we've already received some data, return it. - if( iRead > 0 ) - break; - - iError = WaitForCompletionOrCancellation( FD_READ_BIT ); - if( iError == 0 ) - continue; - } - - // If the other side closed the connection, just return EOF. - if( iError == WSAECONNRESET ) - break; - - /* If we're cancelled or hit an error while reading, do return the data - * we managed to get. Be sure not to overwrite CANCELLED with ERROR. */ - SetError( ssprintf("Error reading: %s", WinSockErrorToString(iError).c_str() ) ); - break; - } - - return iRead; -} - -void NetworkStream_Win32::Write( const void *pBuffer, size_t iSize ) -{ - if( m_State != STATE_CONNECTED ) - return; - - const char *p = (const char *) pBuffer; - while( iSize > 0 ) - { - int iRet = send( m_Socket, p, iSize, 0 ); - ASSERT( iRet != 0 ); - if( iRet > 0 ) - { - p += iRet; - iSize -= iRet; - continue; - } - - int iError = WSAGetLastError(); - if( iError == WSAEWOULDBLOCK ) - { - iError = WaitForCompletionOrCancellation( FD_WRITE_BIT ); - if( iError == 0 ) - continue; - } - - SetError( ssprintf("Error writing: %s", WinSockErrorToString(iError).c_str() ) ); - return; - } -} - -/** @brief Send a set of data over HTTP, as a POST form. */ -NetworkPostData::NetworkPostData(): - m_Mutex( "NetworkPostData" ) -{ - m_pStream = CreateNetworkStream(); -} - -NetworkPostData::~NetworkPostData() -{ - delete m_pStream; -} - -/** @brief Create a MIME multipart data block from the given set of fields. */ -void NetworkPostData::CreateMimeData( const map &mapNameToData, RString &sOut, RString &sMimeBoundaryOut ) -{ - // Find a non-conflicting mime boundary. - while(1) - { - sMimeBoundaryOut = ssprintf( "--%08i", rand() ); - FOREACHM_CONST( RString, RString, mapNameToData, d ) - if( d->second.find(sMimeBoundaryOut) != RString::npos ) - continue; - break; - } - - FOREACHM_CONST( RString, RString, mapNameToData, d ) - { - sOut += "--" + sMimeBoundaryOut + "\r\n"; - sOut += ssprintf( "Content-Disposition: form-data; name=\"%s\"\r\n", d->first.c_str() ); - sOut += "\r\n"; - sOut += d->second; - sOut += "\r\n"; - } - if( sOut.size() ) - sOut += "--" + sMimeBoundaryOut + "--\r\n"; - -} - -void NetworkPostData::HttpThread() -{ - RString sData, sMimeBoundary; - CreateMimeData( m_Data, sData, sMimeBoundary ); - - // Stick to HTTP/1.0, since the protocol is simpler. - RString sBuf = ssprintf( - "%s %s HTTP/1.0\r\n" - "Accept: */*\r\n" - "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\r\n" - "Host: %s\r\n" - "Cache-Control: no-cache\r\n", - sData.size()? "POST":"GET", - m_sPath.c_str(), - m_sHost.c_str() ); - - if( sData.size() ) - { - // sBuf += "Content-Type: application/x-www-form-urlencoded\r\n" - sBuf += "Content-Type: multipart/form-data; boundary=" + sMimeBoundary + "\r\n"; - sBuf += ssprintf( "Content-Length: %i\r\n", sData.size() ); - } - sBuf += "\r\n"; - - if( sData.size() ) - sBuf += sData; - - /* The "progress" is currently faked; it shows when we've connected, and when - * we've received data. We send and receive too little data to do more. */ - SetProgress( 0 ); - - // Begin connecting. - m_pStream->Open( m_sHost, m_iPort ); - SetProgress( 0.25f ); - - // Send the form. - m_pStream->Write( sBuf.data(), sBuf.size() ); - - // Read the result. - RString sResult; - while( m_pStream->GetState() == NetworkStream::STATE_CONNECTED ) - { - sBuf.clear(); - void *p = sBuf.GetBuffer( 1024 ); - int iGot = m_pStream->Read( p, 1024 ); - if( iGot >= 0 ) - sBuf.ReleaseBuffer( iGot ); - if( iGot <= 0 ) - break; - sResult += sBuf; - } - - SetProgress( 1.0f ); - - // Parse the results. - int iStart = 0, iSize = -1; - map mapHeaders; - while( 1 ) - { - split( sResult, "\n", iStart, iSize, false ); - if( iStart == (int) sResult.size() ) - break; - - RString sLine = sResult.substr( iStart, iSize ); - StripCrnl( sLine ); - if( sLine.empty() ) - { - m_sResult = sResult.substr( iStart+iSize+1 ); - break; - } - } - - m_bFinished = true; -} - -void NetworkPostData::Start( const RString &sHost, int iPort, const RString &sPath ) -{ - m_bFinished = false; - m_sHost = sHost; - m_iPort = iPort; - m_sPath = sPath; - m_sStatus.clear(); - m_fProgress = 0; - - m_Thread.SetName( "HTTP thread" ); - m_Thread.Create( HttpThread_Start, this ); -} - -void NetworkPostData::Cancel() -{ - m_pStream->Cancel(); - m_Thread.Wait(); -} - -bool NetworkPostData::IsFinished() -{ - if( !m_bFinished ) - return false; - - m_Thread.Wait(); - m_bFinished = false; - return true; -} - -RString NetworkPostData::GetStatus() const -{ - LockMut( m_Mutex ); - return m_sStatus; -} - -float NetworkPostData::GetProgress() const -{ - LockMut( m_Mutex ); - return m_fProgress; -} - -RString NetworkPostData::GetError() const -{ - return m_pStream->GetError(); -} - -void NetworkPostData::SetProgress( float fProgress ) -{ - m_Mutex.Lock(); - m_fProgress = fProgress; - m_Mutex.Unlock(); -} - -void NetworkPostData::SetData( const RString &sKey, const RString &sData ) -{ - m_Data[sKey] = sData; -} - -/* - * (c) 2006 Glenn Maynard - * 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 "CrashHandlerNetworking.h" +#include "RageThreads.h" +#include "RageLog.h" +#include "RageThreads.h" +#include "RageTimer.h" +#include "RageUtil.h" +#include "Foreach.h" + +#if defined(WINDOWS) +#include +#include +#pragma comment(lib, "wsock32.lib") +#pragma comment(lib, "ws2_32.lib") +#endif + +/* This has an arch-like abstraction layout, since it's intended to become one. + * It'll be moved out of here as things get polished further. The Winsock and + * BSD implementations will be kept separate, since while they look similar, + * they're different in the details, especially when you want to cleanly support + * cancellation. + * + * Design notes: + * - Allow cancellation at any time. The only thing more annoying than a laggy + * network, is an application that won't immediately respond to cancelling. + * (This is even more important here, since we're using this after a crash.) + * - Assume some operations and/or some architectures are going to have problems + * doing everything nonblockingly, and do everything in a thread. This also allows + * more complex network interactions, since it doesn't need to maintain an equally + * complex state machine. + * - When an operation is cancelled or an error occurs, all operations until the + * next call to Close() become no-ops. This allows lenient error checking; it's + * guaranteed that further calls to Read or Write will not reset the error state. + * - The only function that may be called from another thread without serialization is + * Cancel(). + * + * All calls are blocking, except for: + * + * - Cancel(), which is always nonblocking. The operation in progress, if any, + * will be aborted immediately. (The only exception here is incomplete + * implementations, which may block); + * - Close(), if Shutdown() was called first, since the necessary blocking occurs + * during Shutdown; + * - Close(), if an error or cancellation occurred. + * + * Accepting incoming TCP connections is beyond the immediate scope of this interface + * (that would involve a second listener class, which would be a factory for NetworkStream). + * UDP is probably within the scope of this class, but there are some outstanding design + * issues and it's beyond the work I'm doing right now. */ + +class NetworkStream +{ +public: + enum ConnectionType + { + CONN_TCP, + CONN_UDP, + }; + NetworkStream() { } + virtual ~NetworkStream() { } + + // Open a connection. Must be in STATE_IDLE. + virtual void Open( const RString &sHost, int iPort, ConnectionType ct = CONN_TCP ) = 0; + + // Close down a connection. Returns to STATE_IDLE. + virtual void Close() = 0; + + // Wait for all sent data to be flushed, and shut down the connection. + virtual void Shutdown() = 0; + + /* Read data. Block until any data is received, then return all data + * available. Return the number of bytes read. The return value will always + * be >= 0, unless an error or cancellation occured. */ + virtual int Read( void *pBuffer, size_t iSize ) = 0; + + /* Write data to the socket. (Design note: we always write all of the data + * unless an error or cancellation occurs, and those states are checked + * with GetState(). If that happens, the number of bytes written is + * meaningless, since it may have simply been buffered and never sent. + * So, this function returns no value.) */ + virtual void Write( const void *pBuffer, size_t iSize ) = 0; + + /* Cancel the connection. This operation can clear an error state, aborts + * any blocking calls, never fails, and will always result in the socket + * being in STATE_CANCELLED. + * This is true even if the state was STATE_IDLE. For example, the user + * thread may be closing one connection on this object and opening another, + * and the UI thread may call Cancel() in between these operations. + * This operation is threadsafe: a socket can be cancelled from any thread + * while another thread is reading or writing. This is the only function + * that can be called without serialization. */ + virtual void Cancel() = 0; + + enum State + { + // The stream is closed, and is ready to be opened. + STATE_IDLE, + + // The stream is connected and able to send and receive data. + STATE_CONNECTED, + + /* The stream has been shut down on either end (either an EOF from + * the other end, or shutdown() being called). */ + STATE_SHUTDOWN, + + /* When in CANCELLED, all blocking calls fail immediately; this state must be + * cleared explicitly by calling Close(). */ + STATE_CANCELLED, + STATE_ERROR + }; + + State GetState() const { return m_State; } + RString GetError() const { return m_sError; } + +protected: + State m_State; + RString m_sError; +}; + +class NetworkStream_Win32: public NetworkStream +{ +public: + NetworkStream_Win32(); + ~NetworkStream_Win32(); + + void Open( const RString &sHost, int iPort, ConnectionType ct = CONN_TCP ); + void Shutdown(); + void Close(); + int Read( void *pBuffer, size_t iSize ); + void Write( const void *pBuffer, size_t iSize ); + + void Cancel(); + +private: + int WaitForCompletionOrCancellation( int iEvent ); + void SetError( const RString &sError ); + static RString WinSockErrorToString( int iError ); + + SOCKET m_Socket; + HANDLE m_hResolve; + HWND m_hResolveHwnd; + + // This event is signalled on cancellation, to wake us up if we're blocking. + HANDLE m_hCompletionEvent; + + RString m_sHost; + int m_iPort; + + RageMutex m_Mutex; +}; + +NetworkStream *CreateNetworkStream() +{ + static bool bInitted = false; + if( !bInitted ) + { + bInitted = true; + WSADATA WSAData; + WORD iVersionRequested = MAKEWORD(2,0); + if( WSAStartup(iVersionRequested, &WSAData) != 0 ) + return NULL; + } + + return new NetworkStream_Win32; +} + +/** @brief WinSock implementation of NetworkStream. */ +NetworkStream_Win32::NetworkStream_Win32(): + m_Mutex( "NetworkTCPSocket" ) +{ + m_iPort = -1; + m_State = STATE_IDLE; + m_Socket = NULL; +#if defined(WINDOWS) + m_hResolve = NULL; + m_hResolveHwnd = NULL; + m_hCompletionEvent = CreateEvent( NULL, true, false, NULL ); +#endif +} + +NetworkStream_Win32::~NetworkStream_Win32() +{ + Close(); + CloseHandle( m_hCompletionEvent ); +} + +/* Wait for the specified network event to occur, or cancellation, whichever + * happens first. On cancellation, return -1; on error, return the error + * code; on success, return 0. */ +int NetworkStream_Win32::WaitForCompletionOrCancellation( int iEvent ) +{ + while(1) + { + int iRet = WaitForSingleObject( m_hCompletionEvent, INFINITE ); + if( iRet != WAIT_OBJECT_0 ) + continue; + + m_Mutex.Lock(); + + // This will reset the event. Do this while we hold the lock. + WSANETWORKEVENTS events; + WSAEnumNetworkEvents( m_Socket, m_hCompletionEvent, &events ); + + // Was the event signalled due to cancellation? + if( m_State == STATE_CANCELLED ) + { + m_Mutex.Unlock(); + return -1; + } + + m_Mutex.Unlock(); + + // If the event didn't actually occur, keep waiting. + if( (events.lNetworkEvents & (1<h_addr_list[0]; + addr.sin_family = PF_INET; + addr.sin_port = htons( (uint16_t) iPort ); + + m_Mutex.Lock(); + m_Socket = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP ); + if( m_Socket == INVALID_SOCKET ) + { + int iError = WSAGetLastError(); + SetError( ssprintf("Error creating socket: %s", WinSockErrorToString(iError).c_str() ) ); + return; + } + + /* Set up the completion event to be signalled when these events occur. + * This also sets the socket to nonblocking. */ + WSAEventSelect( m_Socket, m_hCompletionEvent, FD_CONNECT|FD_READ|FD_WRITE|FD_CLOSE ); + + //fcntl( m_Socket, O_NONBLOCK, 1 ); + + // Start opening the connection. + int iResult = connect(m_Socket, (SOCKADDR *) &addr, sizeof(addr)); + + m_Mutex.Unlock(); + + // We expect EINPROGRESS/WSAEWOULDBLOCK. + if( iResult == SOCKET_ERROR ) + { + // Block until the connection attempt completes. + int iError = WSAGetLastError(); + if( iError == WSAEWOULDBLOCK ) + iError = WaitForCompletionOrCancellation( FD_CONNECT_BIT ); + if( iError ) + { + SetError( ssprintf("Couldn't connect: %s", WinSockErrorToString(iError).c_str() ) ); + return; + } + } + } + + m_State = STATE_CONNECTED; +} + +void NetworkStream_Win32::Close() +{ + if( m_State == STATE_IDLE ) + return; + + /* If we have an active, stable connection, make sure we flush any data + * completely before closing. If you don't want to do this, call Cancel() + * first. */ + Shutdown(); + + m_Mutex.Lock(); + if( m_State == STATE_CANCELLED ) + return; + + if( m_Socket != INVALID_SOCKET ) + closesocket( m_Socket ); + m_Socket = INVALID_SOCKET; + m_State = STATE_IDLE; + m_Mutex.Unlock(); +} + +void NetworkStream_Win32::Shutdown() +{ + /* If we're not in a normal, stable state (eg. an error occurred, or we were + * cancelled), don't wait to flush. */ + if( m_State != STATE_CONNECTED ) + return; + + shutdown( m_Socket, SD_BOTH ); + m_State = STATE_SHUTDOWN; +} + +void NetworkStream_Win32::Cancel() +{ + m_Mutex.Lock(); + + // Mark cancellation. + m_State = STATE_CANCELLED; + + // If resolving, abort the resolve. + if( m_hResolve != NULL ) + { + /* When we cancel the request, no message at all will be sent to the window, + * so we need to do it ourself to inform it that it was cancelled. Be sure + * to only do this on successful cancel. */ + if( WSACancelAsyncRequest(m_hResolve) == 0 ) + PostMessage( m_hResolveHwnd, WM_USER+1, 0, 0 ); + } + + // Break out if we're waiting in WaitForCompletionOrCancellation(). + SetEvent( m_hCompletionEvent ); + + m_Mutex.Unlock(); +} + +int NetworkStream_Win32::Read( void *pBuffer, size_t iSize ) +{ + if( m_State != STATE_CONNECTED ) + return 0; + + char *p = (char *) pBuffer; + int iRead = 0; + while( iSize > 0 ) + { + int iRet = recv( m_Socket, p, iSize, 0 ); + if( iRet > 0 ) + { + p += iRet; + iSize -= iRet; + iRead += iRet; + continue; + } + + if( iRet == 0 ) + { + // We hit EOF. + break; + } + + int iError = WSAGetLastError(); + if( iError == WSAEWOULDBLOCK ) + { + // There's no date to read. If we've already received some data, return it. + if( iRead > 0 ) + break; + + iError = WaitForCompletionOrCancellation( FD_READ_BIT ); + if( iError == 0 ) + continue; + } + + // If the other side closed the connection, just return EOF. + if( iError == WSAECONNRESET ) + break; + + /* If we're cancelled or hit an error while reading, do return the data + * we managed to get. Be sure not to overwrite CANCELLED with ERROR. */ + SetError( ssprintf("Error reading: %s", WinSockErrorToString(iError).c_str() ) ); + break; + } + + return iRead; +} + +void NetworkStream_Win32::Write( const void *pBuffer, size_t iSize ) +{ + if( m_State != STATE_CONNECTED ) + return; + + const char *p = (const char *) pBuffer; + while( iSize > 0 ) + { + int iRet = send( m_Socket, p, iSize, 0 ); + ASSERT( iRet != 0 ); + if( iRet > 0 ) + { + p += iRet; + iSize -= iRet; + continue; + } + + int iError = WSAGetLastError(); + if( iError == WSAEWOULDBLOCK ) + { + iError = WaitForCompletionOrCancellation( FD_WRITE_BIT ); + if( iError == 0 ) + continue; + } + + SetError( ssprintf("Error writing: %s", WinSockErrorToString(iError).c_str() ) ); + return; + } +} + +/** @brief Send a set of data over HTTP, as a POST form. */ +NetworkPostData::NetworkPostData(): + m_Mutex( "NetworkPostData" ) +{ + m_pStream = CreateNetworkStream(); +} + +NetworkPostData::~NetworkPostData() +{ + delete m_pStream; +} + +/** @brief Create a MIME multipart data block from the given set of fields. */ +void NetworkPostData::CreateMimeData( const map &mapNameToData, RString &sOut, RString &sMimeBoundaryOut ) +{ + // Find a non-conflicting mime boundary. + while(1) + { + sMimeBoundaryOut = ssprintf( "--%08i", rand() ); + for (auto const &d : mapNameToData) + if( d.second.find(sMimeBoundaryOut) != RString::npos ) + continue; + break; + } + + for (auto const &d : mapNameToData) + { + sOut += "--" + sMimeBoundaryOut + "\r\n"; + sOut += ssprintf( "Content-Disposition: form-data; name=\"%s\"\r\n", d.first.c_str() ); + sOut += "\r\n"; + sOut += d.second; + sOut += "\r\n"; + } + if( sOut.size() ) + sOut += "--" + sMimeBoundaryOut + "--\r\n"; + +} + +void NetworkPostData::HttpThread() +{ + RString sData, sMimeBoundary; + CreateMimeData( m_Data, sData, sMimeBoundary ); + + // Stick to HTTP/1.0, since the protocol is simpler. + RString sBuf = ssprintf( + "%s %s HTTP/1.0\r\n" + "Accept: */*\r\n" + "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\r\n" + "Host: %s\r\n" + "Cache-Control: no-cache\r\n", + sData.size()? "POST":"GET", + m_sPath.c_str(), + m_sHost.c_str() ); + + if( sData.size() ) + { + // sBuf += "Content-Type: application/x-www-form-urlencoded\r\n" + sBuf += "Content-Type: multipart/form-data; boundary=" + sMimeBoundary + "\r\n"; + sBuf += ssprintf( "Content-Length: %i\r\n", sData.size() ); + } + sBuf += "\r\n"; + + if( sData.size() ) + sBuf += sData; + + /* The "progress" is currently faked; it shows when we've connected, and when + * we've received data. We send and receive too little data to do more. */ + SetProgress( 0 ); + + // Begin connecting. + m_pStream->Open( m_sHost, m_iPort ); + SetProgress( 0.25f ); + + // Send the form. + m_pStream->Write( sBuf.data(), sBuf.size() ); + + // Read the result. + RString sResult; + while( m_pStream->GetState() == NetworkStream::STATE_CONNECTED ) + { + sBuf.clear(); + void *p = sBuf.GetBuffer( 1024 ); + int iGot = m_pStream->Read( p, 1024 ); + if( iGot >= 0 ) + sBuf.ReleaseBuffer( iGot ); + if( iGot <= 0 ) + break; + sResult += sBuf; + } + + SetProgress( 1.0f ); + + // Parse the results. + int iStart = 0, iSize = -1; + map mapHeaders; + while( 1 ) + { + split( sResult, "\n", iStart, iSize, false ); + if( iStart == (int) sResult.size() ) + break; + + RString sLine = sResult.substr( iStart, iSize ); + StripCrnl( sLine ); + if( sLine.empty() ) + { + m_sResult = sResult.substr( iStart+iSize+1 ); + break; + } + } + + m_bFinished = true; +} + +void NetworkPostData::Start( const RString &sHost, int iPort, const RString &sPath ) +{ + m_bFinished = false; + m_sHost = sHost; + m_iPort = iPort; + m_sPath = sPath; + m_sStatus.clear(); + m_fProgress = 0; + + m_Thread.SetName( "HTTP thread" ); + m_Thread.Create( HttpThread_Start, this ); +} + +void NetworkPostData::Cancel() +{ + m_pStream->Cancel(); + m_Thread.Wait(); +} + +bool NetworkPostData::IsFinished() +{ + if( !m_bFinished ) + return false; + + m_Thread.Wait(); + m_bFinished = false; + return true; +} + +RString NetworkPostData::GetStatus() const +{ + LockMut( m_Mutex ); + return m_sStatus; +} + +float NetworkPostData::GetProgress() const +{ + LockMut( m_Mutex ); + return m_fProgress; +} + +RString NetworkPostData::GetError() const +{ + return m_pStream->GetError(); +} + +void NetworkPostData::SetProgress( float fProgress ) +{ + m_Mutex.Lock(); + m_fProgress = fProgress; + m_Mutex.Unlock(); +} + +void NetworkPostData::SetData( const RString &sKey, const RString &sData ) +{ + m_Data[sKey] = sData; +} + +/* + * (c) 2006 Glenn Maynard + * 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. + */ From 9ec7272416759e50befc5b67698661b22a568989 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 16:06:17 -0400 Subject: [PATCH 040/281] Restore the const nature of this variable. Lambdas can be taken advantage of. --- src/Player.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Player.cpp b/src/Player.cpp index b6adb7bda4..eea23b5d15 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -3004,20 +3004,20 @@ void Player::CrossedRows( int iLastRowCrossed, const RageTimer &now ) * TODO: Move this to a separate function. */ if( HOLD_CHECKPOINTS && m_pPlayerState->m_PlayerController != PC_AUTOPLAY ) { - int iCheckpointFrequencyRows = ROWS_PER_BEAT/2; - if( CHECKPOINTS_USE_TICKCOUNTS ) - { - int tickCurrent = m_Timing->GetTickcountAtRow( iLastRowCrossed ); - // There are some charts that don't want tickcounts involved at all. - iCheckpointFrequencyRows = (tickCurrent > 0 ? ROWS_PER_BEAT / tickCurrent : 0); - } - else if( CHECKPOINTS_USE_TIME_SIGNATURES ) - { - TimeSignatureSegment * tSignature = m_Timing->GetTimeSignatureSegmentAtRow( iLastRowCrossed ); - - // Most songs are in 4/4 time. The frequency for checking tick counts should reflect that. - iCheckpointFrequencyRows = ROWS_PER_BEAT * tSignature->GetDen() / (tSignature->GetNum() * 4); - } + int const iCheckpointFrequencyRows = [&]() { + if (CHECKPOINTS_USE_TICKCOUNTS) + { + int currentTick = m_Timing->GetTickcountAtRow(iLastRowCrossed); + // some charts don't want tickcounts involved. + return (currentTick > 0 ? ROWS_PER_BEAT / currentTick : 0); + } + if (CHECKPOINTS_USE_TIME_SIGNATURES) + { + TimeSignatureSegment const *seg = m_Timing->GetTimeSignatureSegmentAtRow(iLastRowCrossed); + return ROWS_PER_BEAT * seg->GetDen() / (seg->GetNum() * 4); + } + return ROWS_PER_BEAT / 2; + }(); if( iCheckpointFrequencyRows > 0 ) { From 6d57d2fbf50d2ab1cc692e2375d3a9ad0608f42c Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 23:50:37 -0400 Subject: [PATCH 041/281] Start working on the other map loops. --- src/NoteField.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NoteField.cpp b/src/NoteField.cpp index 550c136001..5cbfb59327 100644 --- a/src/NoteField.cpp +++ b/src/NoteField.cpp @@ -136,11 +136,11 @@ void NoteField::CacheAllUsedNoteSkins() /* If we're changing note skins in the editor, we can have old note skins lying * around. Remove them so they don't accumulate. */ set setNoteSkinsToUnload; - FOREACHM( RString, NoteDisplayCols *, m_NoteDisplays, d ) + for (std::pair d : m_NoteDisplays) { - bool unused = find(asSkinsLower.begin(), asSkinsLower.end(), d->first) == asSkinsLower.end(); + bool unused = find(asSkinsLower.begin(), asSkinsLower.end(), d.first) == asSkinsLower.end(); if( unused ) - setNoteSkinsToUnload.insert( d->first ); + setNoteSkinsToUnload.insert( d.first ); } for (RString const & skin : setNoteSkinsToUnload) UncacheNoteSkin( skin ); From a351621303ea36af915c412bf540a241f423ad8d Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 23:55:16 -0400 Subject: [PATCH 042/281] Handle the map loops here. Little concerned on this one due to deletion. Consider this a hopeful target for a smart pointer. --- src/OptionsList.cpp | 1497 +++++++++++++++++++++---------------------- 1 file changed, 748 insertions(+), 749 deletions(-) diff --git a/src/OptionsList.cpp b/src/OptionsList.cpp index 24c78e8e4e..4c4caf2746 100644 --- a/src/OptionsList.cpp +++ b/src/OptionsList.cpp @@ -1,749 +1,748 @@ -#include "global.h" -#include "OptionsList.h" -#include "GameState.h" -#include "RageLog.h" -#include "Course.h" -#include "SongUtil.h" -#include "StepsUtil.h" -#include "Style.h" -#include "InputEventPlus.h" -#include "CodeDetector.h" -#include "InputMapper.h" -#include "PlayerState.h" - -#define LINE(sLineName) THEME->GetMetric (m_sName,ssprintf("Line%s",sLineName.c_str())) -#define MAX_ITEMS_BEFORE_SPLIT THEME->GetMetricI(m_sName,"MaxItemsBeforeSplit") -#define ITEMS_SPLIT_WIDTH THEME->GetMetricF(m_sName,"ItemsSplitWidth") -#define DIRECT_LINES THEME->GetMetric (m_sName,"DirectLines") -#define TOP_MENUS THEME->GetMetric (m_sName,"TopMenus") - -static const RString RESET_ROW = "ResetOptions"; - -void OptionListRow::Load( OptionsList *pOptions, const RString &sType ) -{ - m_pOptions = pOptions; - ITEMS_SPACING_Y .Load(sType,"ItemsSpacingY"); - - m_Text.resize( 1 ); - m_Text[0].SetName( "Text" ); - m_Text[0].LoadFromFont( THEME->GetPathF(sType, "normal") ); - ActorUtil::LoadAllCommands( m_Text[0], sType ); - - m_Underlines.resize( 1 ); - m_Underlines[0].Load( THEME->GetPathG(sType, "underline") ); - m_Underlines[0]->SetName( "Underline" ); - ActorUtil::LoadAllCommands( *m_Underlines[0], sType ); - - m_Text[0].PlayCommand( "On" ); - m_Underlines[0]->PlayCommand( "On" ); -} - -// GetTitleForHandler -// can always use the title -// can have special speed row titles, note skin, or any SELECT_ONE - -void OptionListRow::SetFromHandler( const OptionRowHandler *pHandler ) -{ - this->FinishTweening(); - this->RemoveAllChildren(); - - if( pHandler == NULL ) - return; - - int iNum = max( pHandler->m_Def.m_vsChoices.size(), m_Text.size() )+1; - m_Text.resize( iNum, m_Text[0] ); - m_Underlines.resize( iNum, m_Underlines[0] ); - - for( unsigned i = 0; i < pHandler->m_Def.m_vsChoices.size(); ++i ) - { - // init underlines - this->AddChild( m_Underlines[i] ); - - // init text - this->AddChild( &m_Text[i] ); - } - - SetTextFromHandler( pHandler ); - - const unsigned iCnt = pHandler->m_Def.m_vsChoices.size(); - m_bItemsInTwoRows = (int) iCnt > MAX_ITEMS_BEFORE_SPLIT; - const float fWidth = ITEMS_SPLIT_WIDTH; - float fY = 0; - for( unsigned i = 0; i < iCnt; ++i ) - { - float fX = 0; - if( m_bItemsInTwoRows ) - { - if( (i % 2) == 0 ) - fX = -fWidth/2; - else - fX = +fWidth/2; - } - - // set the Y position of each item in the line - m_Text[i].SetXY( fX, fY ); - m_Underlines[i]->SetXY( fX, fY ); - - if( m_bItemsInTwoRows ) - m_Underlines[i]->PlayCommand( "SetTwoRows" ); - else - m_Underlines[i]->PlayCommand( "SetOneRow" ); - - if( !m_bItemsInTwoRows || (i % 2) == 1 || i+1 == iCnt ) - fY += ITEMS_SPACING_Y; - } - - int iExit = pHandler->m_Def.m_vsChoices.size(); - m_Text[iExit].SetText( "Exit" ); // XXX localize - m_Text[iExit].SetXY( 0, fY ); - this->AddChild( &m_Text[iExit] ); -} - -void OptionListRow::SetTextFromHandler( const OptionRowHandler *pHandler ) -{ - ASSERT( pHandler != NULL ); - for( unsigned i = 0; i < pHandler->m_Def.m_vsChoices.size(); ++i ) - { - // init text - RString sText = pHandler->GetThemedItemText( i ); - - RString sDest = pHandler->GetScreen( i ); - if( m_pOptions->m_setDirectRows.find(sDest) != m_pOptions->m_setDirectRows.end() && sDest.size() ) - { - const OptionRowHandler *pTarget = m_pOptions->m_Rows[sDest]; - if( pTarget->m_Def.m_selectType == SELECT_ONE ) - { - int iSelection = m_pOptions->GetOneSelection(sDest); - sText += ": " + pTarget->GetThemedItemText( iSelection ); - } - } - - m_Text[i].SetText( sText ); - } -} - -void OptionListRow::SetUnderlines( const vector &aSelections, const OptionRowHandler *pHandler ) -{ - for( unsigned i = 0; i < aSelections.size(); ++i ) - { - Actor *pActor = m_Underlines[i]; - - bool bSelected = aSelections[i]; - RString sDest = pHandler->GetScreen( i ); - if( sDest.size() ) - { - /* This is a submenu. Underline the row if its options have been changed - * from the default. */ - const OptionRowHandler *pTarget = m_pOptions->m_Rows[sDest]; - if( pTarget->m_Def.m_selectType == SELECT_ONE ) - { - int iSelection = m_pOptions->GetOneSelection(sDest); - const OptionRowHandler *lHandler = m_pOptions->m_Rows.find(sDest)->second; - int iDefault = lHandler->GetDefaultOption(); - if( iDefault != -1 && iSelection != iDefault ) - bSelected |= true; - } - else if( pTarget->m_Def.m_selectType == SELECT_MULTIPLE ) - { - const vector &bTargetSelections = m_pOptions->m_bSelections.find(sDest)->second; - for( unsigned j=0; jPlayCommand( bSelected?"Show":"Hide" ); - } -} - -void OptionListRow::PositionCursor( Actor *pCursor, int iSelection ) -{ - float fX = m_Text[iSelection].GetDestX(); - float fY = m_Text[iSelection].GetDestY(); - if( m_bItemsInTwoRows ) - pCursor->PlayCommand( "PositionTwoRows" ); - else - pCursor->PlayCommand( "PositionOneRow" ); - pCursor->SetXY( fX, fY ); -} - -OptionsList::OptionsList() -{ - m_iCurrentRow = 0; - m_pLinked = NULL; -} - -OptionsList::~OptionsList() -{ - FOREACHM( RString, OptionRowHandler *, m_Rows, hand ) - delete hand->second; -} - -void OptionsList::Load( RString sType, PlayerNumber pn ) -{ - TOP_MENU.Load( sType, "TopMenu" ); - - m_pn = pn; - m_bStartIsDown = false; - - m_Codes.Load( sType ); - - m_Cursor.Load( THEME->GetPathG(sType, "cursor") ); - m_Cursor->SetName( "Cursor" ); - ActorUtil::LoadAllCommands( *m_Cursor, sType ); - this->AddChild( m_Cursor ); - - vector asDirectLines; - split( DIRECT_LINES, ",", asDirectLines, true ); - FOREACH( RString, asDirectLines, s ) - m_setDirectRows.insert( *s ); - - vector setToLoad; - split( TOP_MENUS, ",", setToLoad ); - m_setTopMenus.insert( setToLoad.begin(), setToLoad.end() ); - - while( !setToLoad.empty() ) - { - RString sLineName = *setToLoad.begin(); - setToLoad.erase( setToLoad.begin() ); - - if( m_Rows.find(sLineName) != m_Rows.end() ) - continue; - - RString sRowCommands = LINE(sLineName); - Commands cmds; - ParseCommands( sRowCommands, cmds ); - - OptionRowHandler *pHand = OptionRowHandlerUtil::Make( cmds ); - if( pHand == NULL ) - RageException::Throw( "Invalid OptionRowHandler '%s' in %s::Line%s", cmds.GetOriginalCommandString().c_str(), m_sName.c_str(), sLineName.c_str() ); - - m_Rows[sLineName] = pHand; - m_asLoadedRows.push_back( sLineName ); - - for( size_t i = 0; i < pHand->m_Def.m_vsChoices.size(); ++i ) - { - RString sScreen = pHand->GetScreen(i); - if( !sScreen.empty() ) - setToLoad.push_back( sScreen ); - } - } - - for( int i = 0; i < 2; ++i ) - { - m_Row[i].SetName( "OptionsList" ); - m_Row[i].Load( this, "OptionsList" ); - ActorUtil::LoadAllCommands( m_Row[i], sType ); - this->AddChild( &m_Row[i] ); - } - - this->PlayCommand( "TweenOff" ); - this->FinishTweening(); -} - -void OptionsList::Reset() -{ - /* Import options. */ - FOREACHM( RString, OptionRowHandler *, m_Rows, hand ) - { - RString sLineName = hand->first; - ImportRow( sLineName ); - } -} - -void OptionsList::Open() -{ - this->PlayCommand( "Reset" ); - - Message msg("OptionsListOpened"); - msg.SetParam( "Player", m_pn ); - MESSAGEMAN->Broadcast( msg ); - - /* Push the initial menu. */ - ASSERT( m_asMenuStack.size() == 0 ); - Push( TOP_MENU ); - - this->FinishTweening(); - m_Row[!m_iCurrentRow].SetFromHandler( NULL ); - this->PlayCommand( "TweenOn" ); -} - -void OptionsList::Close() -{ - Message msg("OptionsListClosed"); - msg.SetParam( "Player", m_pn ); - MESSAGEMAN->Broadcast( msg ); - - m_bStartIsDown = false; - m_asMenuStack.clear(); - this->PlayCommand( "TweenOff" ); -} - -RString OptionsList::GetCurrentRow() const -{ - ASSERT( !m_asMenuStack.empty() ); // called while the menu was closed - return m_asMenuStack.back(); -} - -const OptionRowHandler *OptionsList::GetCurrentHandler() -{ - RString sCurrentRow = GetCurrentRow(); - return m_Rows[sCurrentRow]; -} - -int OptionsList::GetOneSelection( RString sRow, bool bAllowFail ) const -{ - map >::const_iterator it = m_bSelections.find(sRow); - ASSERT_M( it != m_bSelections.end(), sRow ); - const vector &bSelections = it->second; - for( unsigned i=0; i= 0 ) - { - if( iCurrentRow >= (int) pHandler->m_Def.m_vsChoices.size() ) - return; - RString sDest = pHandler->GetScreen( iCurrentRow ); - if( sDest.empty() ) - return; - - if( m_asMenuStack.size() == 1 ) - m_asMenuStack.push_back( sDest ); - else - m_asMenuStack.back() = sDest; - } - else - { - if( m_asMenuStack.size() == 1 ) - return; - - m_asMenuStack.pop_back(); - } - - SetDefaultCurrentRow(); - SwitchToCurrentRow(); - TweenOnCurrentRow( iDir > 0 ); -} - -void OptionsList::MoveItem( const RString &sRowName, int iMove ) -{ -} - -bool OptionsList::Input( const InputEventPlus &input ) -{ - Message msg(""); - if( m_Codes.InputMessage(input, msg) ) - this->HandleMessage( msg ); - - const OptionRowHandler *pHandler = GetCurrentHandler(); - - PlayerNumber pn = input.pn; - if( m_bStartIsDown ) - { - if( input.MenuI == GAME_BUTTON_LEFT || input.MenuI == GAME_BUTTON_RIGHT ) - { - if( input.type != IET_FIRST_PRESS ) - return false; - - m_bAcceptStartRelease = false; - - const RString &sCurrentRow = m_asMenuStack.back(); - vector &bSelections = m_bSelections[sCurrentRow]; - if( m_iMenuStackSelection == (int)bSelections.size() ) - return false; - - RString sDest = pHandler->GetScreen( m_iMenuStackSelection ); - if( m_setDirectRows.find(sDest) != m_setDirectRows.end() && sDest.size() ) - { - const OptionRowHandler *pTarget = m_Rows[sDest]; - vector &bTargetSelections = m_bSelections[sDest]; - - if( pTarget->m_Def.m_selectType == SELECT_ONE ) - { - int iSelection = GetOneSelection(sDest); - int iDir = (input.MenuI == GAME_BUTTON_RIGHT? +1:-1); - iSelection += iDir; - wrap( iSelection, bTargetSelections.size() ); - SelectItem( sDest, iSelection ); - - Message lMsg("OptionsListQuickChange"); - lMsg.SetParam( "Player", pn ); - lMsg.SetParam( "Direction", iDir ); - MESSAGEMAN->Broadcast( lMsg ); - } - } - return true; - } - } - - if( input.MenuI == GAME_BUTTON_LEFT ) - { - if( input.type == IET_RELEASE ) - return false; - - if( INPUTMAPPER->IsBeingPressed(GAME_BUTTON_RIGHT, pn) ) - { - if( input.type == IET_FIRST_PRESS ) - SwitchMenu( -1 ); - return true; - } - - --m_iMenuStackSelection; - wrap( m_iMenuStackSelection, pHandler->m_Def.m_vsChoices.size()+1 ); // +1 for exit row - PositionCursor(); - - Message lMsg("OptionsListLeft"); - lMsg.SetParam( "Player", input.pn ); - MESSAGEMAN->Broadcast( lMsg ); - return true; - } - else if( input.MenuI == GAME_BUTTON_RIGHT ) - { - if( input.type == IET_RELEASE ) - return false; - - if( INPUTMAPPER->IsBeingPressed(GAME_BUTTON_LEFT, pn) ) - { - if( input.type == IET_FIRST_PRESS ) - SwitchMenu( +1 ); - return true; - } - - ++m_iMenuStackSelection; - wrap( m_iMenuStackSelection, pHandler->m_Def.m_vsChoices.size()+1 ); // +1 for exit row - PositionCursor(); - - Message lMsg("OptionsListRight"); - lMsg.SetParam( "Player", input.pn ); - MESSAGEMAN->Broadcast( lMsg ); - return true; - } - else if( input.MenuI == GAME_BUTTON_START ) - { - if( input.type == IET_FIRST_PRESS ) - { - m_bStartIsDown = true; - m_bAcceptStartRelease = true; - return true; - } - if( input.type == IET_RELEASE ) - { - if( m_bAcceptStartRelease ) - Start(); - m_bStartIsDown = false; - } - - return true; - } - else if( input.MenuI == GAME_BUTTON_SELECT ) - { - if( input.type != IET_FIRST_PRESS ) - return false; -// if( input.type == IET_RELEASE ) - { - Close(); - return true; - } - return true; - } - return false; -} - -void OptionsList::SwitchToCurrentRow() -{ - m_iCurrentRow = !m_iCurrentRow; - - /* Set up the new row. */ - m_Row[m_iCurrentRow].SetFromHandler( GetCurrentHandler() ); - m_Row[m_iCurrentRow].SetUnderlines( m_bSelections[m_asMenuStack.back()], GetCurrentHandler() ); - PositionCursor(); - - Message msg("OptionsMenuChanged"); - msg.SetParam( "Player", m_pn ); - msg.SetParam( "Menu", m_asMenuStack.back() ); - MESSAGEMAN->Broadcast( msg ); -} - -/* After setting up a new row, tween it on. */ -void OptionsList::TweenOnCurrentRow( bool bForward ) -{ - OptionListRow &OldRow = m_Row[!m_iCurrentRow]; - OptionListRow &NewRow = m_Row[m_iCurrentRow]; - - /* Tween out the old row. */ - if( bForward ) - OldRow.PlayCommand( "TweenOutForward" ); - else - OldRow.PlayCommand( "TweenOutBackward" ); - - /* Tween in the old row. */ - if( bForward ) - NewRow.PlayCommand( "TweenInForward" ); - else - NewRow.PlayCommand( "TweenInBackward" ); -} - -void OptionsList::ImportRow( RString sRow ) -{ - vector aSelections[NUM_PLAYERS]; - vector vpns; - vpns.push_back( m_pn ); - OptionRowHandler *pHandler = m_Rows[sRow]; - aSelections[ m_pn ].resize( pHandler->m_Def.m_vsChoices.size() ); - pHandler->ImportOption( NULL, vpns, aSelections ); - m_bSelections[sRow] = aSelections[ m_pn ]; - - if( m_setTopMenus.find(sRow) != m_setTopMenus.end() ) - fill( m_bSelections[sRow].begin(), m_bSelections[sRow].end(), false ); -} - -void OptionsList::ExportRow( RString sRow ) -{ - if( m_setTopMenus.find(sRow) != m_setTopMenus.end() ) - return; - - vector aSelections[NUM_PLAYERS]; - aSelections[m_pn] = m_bSelections[sRow]; - - vector vpns; - vpns.push_back( m_pn ); - - m_Rows[sRow]->ExportOption( vpns, aSelections ); -} - -void OptionsList::SetDefaultCurrentRow() -{ - /* If all items on the row just point to other menus, default to 0. */ - m_iMenuStackSelection = 0; - - const RString &sCurrentRow = m_asMenuStack.back(); - const OptionRowHandler *pHandler = m_Rows.find(sCurrentRow)->second; - if( pHandler->m_Def.m_selectType == SELECT_ONE ) - { - /* One item is selected, so position the cursor on it. */ - m_iMenuStackSelection = GetOneSelection( sCurrentRow, true ); - if( m_iMenuStackSelection == -1 ) - m_iMenuStackSelection = 0; - } -} - -int OptionsList::FindScreenInHandler( const OptionRowHandler *pHandler, RString sScreen ) -{ - for( size_t i = 0; i < pHandler->m_Def.m_vsChoices.size(); ++i ) - { - if( pHandler->GetScreen(i) == sScreen ) - return i; - } - return -1; -} - -void OptionsList::Pop() -{ - if( m_asMenuStack.size() == 1 ) - { - Close(); - return; - } - - RString sLastMenu = m_asMenuStack.back(); - - m_asMenuStack.pop_back(); - - /* Choose the default option. */ - SetDefaultCurrentRow(); - - /* If the old menu exists as a target from the new menu, switch to it. */ - const OptionRowHandler *pHandler = GetCurrentHandler(); - int iIndex = FindScreenInHandler( pHandler, sLastMenu ); - if( iIndex != -1 ) - m_iMenuStackSelection = iIndex; - - SwitchToCurrentRow(); - TweenOnCurrentRow( false ); -} - -void OptionsList::Push( RString sDest ) -{ - m_asMenuStack.push_back( sDest ); - SetDefaultCurrentRow(); - SwitchToCurrentRow(); -} - -void OptionsList::SelectItem( const RString &sRowName, int iMenuItem ) -{ - const OptionRowHandler *pHandler = m_Rows[sRowName]; - vector &bSelections = m_bSelections[sRowName]; - - if( pHandler->m_Def.m_selectType == SELECT_MULTIPLE ) - { - bool bSelected = !bSelections[iMenuItem]; - bSelections[iMenuItem] = bSelected; - -// if( bSelected ) -// m_SoundToggleOn.Play(); -// else -// m_SoundToggleOff.Play(); - } - else // data.selectType != SELECT_MULTIPLE - { - fill( bSelections.begin(), bSelections.end(), false ); - bSelections[iMenuItem] = true; - } - - SelectionsChanged( sRowName ); - UpdateMenuFromSelections(); -} - -void OptionsList::SelectionsChanged( const RString &sRowName ) -{ - const OptionRowHandler *pHandler = m_Rows[sRowName]; - vector &bSelections = m_bSelections[sRowName]; - - if( pHandler->m_Def.m_bOneChoiceForAllPlayers && m_pLinked != NULL ) - { - vector &bLinkedSelections = m_pLinked->m_bSelections[sRowName]; - bLinkedSelections = bSelections; - - if( m_pLinked->IsOpened() ) - m_pLinked->UpdateMenuFromSelections(); - - m_pLinked->ExportRow( sRowName ); - } - - ExportRow( sRowName ); -} - -void OptionsList::UpdateMenuFromSelections() -{ - const vector &bCurrentSelections = m_bSelections.find(GetCurrentRow())->second; - m_Row[m_iCurrentRow].SetUnderlines( bCurrentSelections, GetCurrentHandler() ); - m_Row[m_iCurrentRow].SetTextFromHandler( GetCurrentHandler() ); -} - -bool OptionsList::Start() -{ - const OptionRowHandler *pHandler = GetCurrentHandler(); - const RString &sCurrentRow = m_asMenuStack.back(); - vector &bSelections = m_bSelections[sCurrentRow]; - if( m_iMenuStackSelection == (int)bSelections.size() ) - { - Pop(); - - Message msg("OptionsListPop"); - msg.SetParam( "Player", m_pn ); - MESSAGEMAN->Broadcast( msg ); - - return m_asMenuStack.empty(); - } - - { - RString sIconText; - GameCommand gc; - pHandler->GetIconTextAndGameCommand( m_iMenuStackSelection, sIconText, gc ); - if( gc.m_sName == RESET_ROW ) - { - GAMESTATE->m_pPlayerState[m_pn]->ResetToDefaultPlayerOptions( ModsLevel_Preferred ); - GAMESTATE->ResetToDefaultSongOptions( ModsLevel_Preferred ); - - /* Import options. */ - FOREACHM( RString, OptionRowHandler *, m_Rows, hand ) - { - ImportRow( hand->first ); - SelectionsChanged( hand->first ); - } - - UpdateMenuFromSelections(); - - Message msg("OptionsListReset"); - msg.SetParam( "Player", m_pn ); - MESSAGEMAN->Broadcast( msg ); - - return false; - } - } - - RString sDest = pHandler->GetScreen( m_iMenuStackSelection ); - if( sDest.size() ) - { - Push( sDest ); - TweenOnCurrentRow( true ); - - Message msg("OptionsListPush"); - msg.SetParam( "Player", m_pn ); - MESSAGEMAN->Broadcast( msg ); - - return false; - } - - SelectItem( GetCurrentRow(), m_iMenuStackSelection ); - - /* Move to the exit row. */ - m_iMenuStackSelection = (int)bSelections.size(); - PositionCursor(); - - Message msg("OptionsListStart"); - msg.SetParam( "Player", m_pn ); - MESSAGEMAN->Broadcast( msg ); - - return false; -} - -/* - * Copyright (c) 2006 Glenn Maynard - * 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 "OptionsList.h" +#include "GameState.h" +#include "RageLog.h" +#include "Course.h" +#include "SongUtil.h" +#include "StepsUtil.h" +#include "Style.h" +#include "InputEventPlus.h" +#include "CodeDetector.h" +#include "InputMapper.h" +#include "PlayerState.h" + +#define LINE(sLineName) THEME->GetMetric (m_sName,ssprintf("Line%s",sLineName.c_str())) +#define MAX_ITEMS_BEFORE_SPLIT THEME->GetMetricI(m_sName,"MaxItemsBeforeSplit") +#define ITEMS_SPLIT_WIDTH THEME->GetMetricF(m_sName,"ItemsSplitWidth") +#define DIRECT_LINES THEME->GetMetric (m_sName,"DirectLines") +#define TOP_MENUS THEME->GetMetric (m_sName,"TopMenus") + +static const RString RESET_ROW = "ResetOptions"; + +void OptionListRow::Load( OptionsList *pOptions, const RString &sType ) +{ + m_pOptions = pOptions; + ITEMS_SPACING_Y .Load(sType,"ItemsSpacingY"); + + m_Text.resize( 1 ); + m_Text[0].SetName( "Text" ); + m_Text[0].LoadFromFont( THEME->GetPathF(sType, "normal") ); + ActorUtil::LoadAllCommands( m_Text[0], sType ); + + m_Underlines.resize( 1 ); + m_Underlines[0].Load( THEME->GetPathG(sType, "underline") ); + m_Underlines[0]->SetName( "Underline" ); + ActorUtil::LoadAllCommands( *m_Underlines[0], sType ); + + m_Text[0].PlayCommand( "On" ); + m_Underlines[0]->PlayCommand( "On" ); +} + +// GetTitleForHandler +// can always use the title +// can have special speed row titles, note skin, or any SELECT_ONE + +void OptionListRow::SetFromHandler( const OptionRowHandler *pHandler ) +{ + this->FinishTweening(); + this->RemoveAllChildren(); + + if( pHandler == NULL ) + return; + + int iNum = max( pHandler->m_Def.m_vsChoices.size(), m_Text.size() )+1; + m_Text.resize( iNum, m_Text[0] ); + m_Underlines.resize( iNum, m_Underlines[0] ); + + for( unsigned i = 0; i < pHandler->m_Def.m_vsChoices.size(); ++i ) + { + // init underlines + this->AddChild( m_Underlines[i] ); + + // init text + this->AddChild( &m_Text[i] ); + } + + SetTextFromHandler( pHandler ); + + const unsigned iCnt = pHandler->m_Def.m_vsChoices.size(); + m_bItemsInTwoRows = (int) iCnt > MAX_ITEMS_BEFORE_SPLIT; + const float fWidth = ITEMS_SPLIT_WIDTH; + float fY = 0; + for( unsigned i = 0; i < iCnt; ++i ) + { + float fX = 0; + if( m_bItemsInTwoRows ) + { + if( (i % 2) == 0 ) + fX = -fWidth/2; + else + fX = +fWidth/2; + } + + // set the Y position of each item in the line + m_Text[i].SetXY( fX, fY ); + m_Underlines[i]->SetXY( fX, fY ); + + if( m_bItemsInTwoRows ) + m_Underlines[i]->PlayCommand( "SetTwoRows" ); + else + m_Underlines[i]->PlayCommand( "SetOneRow" ); + + if( !m_bItemsInTwoRows || (i % 2) == 1 || i+1 == iCnt ) + fY += ITEMS_SPACING_Y; + } + + int iExit = pHandler->m_Def.m_vsChoices.size(); + m_Text[iExit].SetText( "Exit" ); // XXX localize + m_Text[iExit].SetXY( 0, fY ); + this->AddChild( &m_Text[iExit] ); +} + +void OptionListRow::SetTextFromHandler( const OptionRowHandler *pHandler ) +{ + ASSERT( pHandler != NULL ); + for( unsigned i = 0; i < pHandler->m_Def.m_vsChoices.size(); ++i ) + { + // init text + RString sText = pHandler->GetThemedItemText( i ); + + RString sDest = pHandler->GetScreen( i ); + if( m_pOptions->m_setDirectRows.find(sDest) != m_pOptions->m_setDirectRows.end() && sDest.size() ) + { + const OptionRowHandler *pTarget = m_pOptions->m_Rows[sDest]; + if( pTarget->m_Def.m_selectType == SELECT_ONE ) + { + int iSelection = m_pOptions->GetOneSelection(sDest); + sText += ": " + pTarget->GetThemedItemText( iSelection ); + } + } + + m_Text[i].SetText( sText ); + } +} + +void OptionListRow::SetUnderlines( const vector &aSelections, const OptionRowHandler *pHandler ) +{ + for( unsigned i = 0; i < aSelections.size(); ++i ) + { + Actor *pActor = m_Underlines[i]; + + bool bSelected = aSelections[i]; + RString sDest = pHandler->GetScreen( i ); + if( sDest.size() ) + { + /* This is a submenu. Underline the row if its options have been changed + * from the default. */ + const OptionRowHandler *pTarget = m_pOptions->m_Rows[sDest]; + if( pTarget->m_Def.m_selectType == SELECT_ONE ) + { + int iSelection = m_pOptions->GetOneSelection(sDest); + const OptionRowHandler *lHandler = m_pOptions->m_Rows.find(sDest)->second; + int iDefault = lHandler->GetDefaultOption(); + if( iDefault != -1 && iSelection != iDefault ) + bSelected |= true; + } + else if( pTarget->m_Def.m_selectType == SELECT_MULTIPLE ) + { + const vector &bTargetSelections = m_pOptions->m_bSelections.find(sDest)->second; + for( unsigned j=0; jPlayCommand( bSelected?"Show":"Hide" ); + } +} + +void OptionListRow::PositionCursor( Actor *pCursor, int iSelection ) +{ + float fX = m_Text[iSelection].GetDestX(); + float fY = m_Text[iSelection].GetDestY(); + if( m_bItemsInTwoRows ) + pCursor->PlayCommand( "PositionTwoRows" ); + else + pCursor->PlayCommand( "PositionOneRow" ); + pCursor->SetXY( fX, fY ); +} + +OptionsList::OptionsList() +{ + m_iCurrentRow = 0; + m_pLinked = NULL; +} + +OptionsList::~OptionsList() +{ + for (std::pair hand : m_Rows) + delete hand.second; +} + +void OptionsList::Load( RString sType, PlayerNumber pn ) +{ + TOP_MENU.Load( sType, "TopMenu" ); + + m_pn = pn; + m_bStartIsDown = false; + + m_Codes.Load( sType ); + + m_Cursor.Load( THEME->GetPathG(sType, "cursor") ); + m_Cursor->SetName( "Cursor" ); + ActorUtil::LoadAllCommands( *m_Cursor, sType ); + this->AddChild( m_Cursor ); + + vector asDirectLines; + split( DIRECT_LINES, ",", asDirectLines, true ); + FOREACH( RString, asDirectLines, s ) + m_setDirectRows.insert( *s ); + + vector setToLoad; + split( TOP_MENUS, ",", setToLoad ); + m_setTopMenus.insert( setToLoad.begin(), setToLoad.end() ); + + while( !setToLoad.empty() ) + { + RString sLineName = *setToLoad.begin(); + setToLoad.erase( setToLoad.begin() ); + + if( m_Rows.find(sLineName) != m_Rows.end() ) + continue; + + RString sRowCommands = LINE(sLineName); + Commands cmds; + ParseCommands( sRowCommands, cmds ); + + OptionRowHandler *pHand = OptionRowHandlerUtil::Make( cmds ); + if( pHand == NULL ) + RageException::Throw( "Invalid OptionRowHandler '%s' in %s::Line%s", cmds.GetOriginalCommandString().c_str(), m_sName.c_str(), sLineName.c_str() ); + + m_Rows[sLineName] = pHand; + m_asLoadedRows.push_back( sLineName ); + + for( size_t i = 0; i < pHand->m_Def.m_vsChoices.size(); ++i ) + { + RString sScreen = pHand->GetScreen(i); + if( !sScreen.empty() ) + setToLoad.push_back( sScreen ); + } + } + + for( int i = 0; i < 2; ++i ) + { + m_Row[i].SetName( "OptionsList" ); + m_Row[i].Load( this, "OptionsList" ); + ActorUtil::LoadAllCommands( m_Row[i], sType ); + this->AddChild( &m_Row[i] ); + } + + this->PlayCommand( "TweenOff" ); + this->FinishTweening(); +} + +void OptionsList::Reset() +{ + /* Import options. */ + for (std::pair hand : m_Rows) + { + ImportRow(hand.first); + } +} + +void OptionsList::Open() +{ + this->PlayCommand( "Reset" ); + + Message msg("OptionsListOpened"); + msg.SetParam( "Player", m_pn ); + MESSAGEMAN->Broadcast( msg ); + + /* Push the initial menu. */ + ASSERT( m_asMenuStack.size() == 0 ); + Push( TOP_MENU ); + + this->FinishTweening(); + m_Row[!m_iCurrentRow].SetFromHandler( NULL ); + this->PlayCommand( "TweenOn" ); +} + +void OptionsList::Close() +{ + Message msg("OptionsListClosed"); + msg.SetParam( "Player", m_pn ); + MESSAGEMAN->Broadcast( msg ); + + m_bStartIsDown = false; + m_asMenuStack.clear(); + this->PlayCommand( "TweenOff" ); +} + +RString OptionsList::GetCurrentRow() const +{ + ASSERT( !m_asMenuStack.empty() ); // called while the menu was closed + return m_asMenuStack.back(); +} + +const OptionRowHandler *OptionsList::GetCurrentHandler() +{ + RString sCurrentRow = GetCurrentRow(); + return m_Rows[sCurrentRow]; +} + +int OptionsList::GetOneSelection( RString sRow, bool bAllowFail ) const +{ + map >::const_iterator it = m_bSelections.find(sRow); + ASSERT_M( it != m_bSelections.end(), sRow ); + const vector &bSelections = it->second; + for( unsigned i=0; i= 0 ) + { + if( iCurrentRow >= (int) pHandler->m_Def.m_vsChoices.size() ) + return; + RString sDest = pHandler->GetScreen( iCurrentRow ); + if( sDest.empty() ) + return; + + if( m_asMenuStack.size() == 1 ) + m_asMenuStack.push_back( sDest ); + else + m_asMenuStack.back() = sDest; + } + else + { + if( m_asMenuStack.size() == 1 ) + return; + + m_asMenuStack.pop_back(); + } + + SetDefaultCurrentRow(); + SwitchToCurrentRow(); + TweenOnCurrentRow( iDir > 0 ); +} + +void OptionsList::MoveItem( const RString &sRowName, int iMove ) +{ +} + +bool OptionsList::Input( const InputEventPlus &input ) +{ + Message msg(""); + if( m_Codes.InputMessage(input, msg) ) + this->HandleMessage( msg ); + + const OptionRowHandler *pHandler = GetCurrentHandler(); + + PlayerNumber pn = input.pn; + if( m_bStartIsDown ) + { + if( input.MenuI == GAME_BUTTON_LEFT || input.MenuI == GAME_BUTTON_RIGHT ) + { + if( input.type != IET_FIRST_PRESS ) + return false; + + m_bAcceptStartRelease = false; + + const RString &sCurrentRow = m_asMenuStack.back(); + vector &bSelections = m_bSelections[sCurrentRow]; + if( m_iMenuStackSelection == (int)bSelections.size() ) + return false; + + RString sDest = pHandler->GetScreen( m_iMenuStackSelection ); + if( m_setDirectRows.find(sDest) != m_setDirectRows.end() && sDest.size() ) + { + const OptionRowHandler *pTarget = m_Rows[sDest]; + vector &bTargetSelections = m_bSelections[sDest]; + + if( pTarget->m_Def.m_selectType == SELECT_ONE ) + { + int iSelection = GetOneSelection(sDest); + int iDir = (input.MenuI == GAME_BUTTON_RIGHT? +1:-1); + iSelection += iDir; + wrap( iSelection, bTargetSelections.size() ); + SelectItem( sDest, iSelection ); + + Message lMsg("OptionsListQuickChange"); + lMsg.SetParam( "Player", pn ); + lMsg.SetParam( "Direction", iDir ); + MESSAGEMAN->Broadcast( lMsg ); + } + } + return true; + } + } + + if( input.MenuI == GAME_BUTTON_LEFT ) + { + if( input.type == IET_RELEASE ) + return false; + + if( INPUTMAPPER->IsBeingPressed(GAME_BUTTON_RIGHT, pn) ) + { + if( input.type == IET_FIRST_PRESS ) + SwitchMenu( -1 ); + return true; + } + + --m_iMenuStackSelection; + wrap( m_iMenuStackSelection, pHandler->m_Def.m_vsChoices.size()+1 ); // +1 for exit row + PositionCursor(); + + Message lMsg("OptionsListLeft"); + lMsg.SetParam( "Player", input.pn ); + MESSAGEMAN->Broadcast( lMsg ); + return true; + } + else if( input.MenuI == GAME_BUTTON_RIGHT ) + { + if( input.type == IET_RELEASE ) + return false; + + if( INPUTMAPPER->IsBeingPressed(GAME_BUTTON_LEFT, pn) ) + { + if( input.type == IET_FIRST_PRESS ) + SwitchMenu( +1 ); + return true; + } + + ++m_iMenuStackSelection; + wrap( m_iMenuStackSelection, pHandler->m_Def.m_vsChoices.size()+1 ); // +1 for exit row + PositionCursor(); + + Message lMsg("OptionsListRight"); + lMsg.SetParam( "Player", input.pn ); + MESSAGEMAN->Broadcast( lMsg ); + return true; + } + else if( input.MenuI == GAME_BUTTON_START ) + { + if( input.type == IET_FIRST_PRESS ) + { + m_bStartIsDown = true; + m_bAcceptStartRelease = true; + return true; + } + if( input.type == IET_RELEASE ) + { + if( m_bAcceptStartRelease ) + Start(); + m_bStartIsDown = false; + } + + return true; + } + else if( input.MenuI == GAME_BUTTON_SELECT ) + { + if( input.type != IET_FIRST_PRESS ) + return false; +// if( input.type == IET_RELEASE ) + { + Close(); + return true; + } + return true; + } + return false; +} + +void OptionsList::SwitchToCurrentRow() +{ + m_iCurrentRow = !m_iCurrentRow; + + /* Set up the new row. */ + m_Row[m_iCurrentRow].SetFromHandler( GetCurrentHandler() ); + m_Row[m_iCurrentRow].SetUnderlines( m_bSelections[m_asMenuStack.back()], GetCurrentHandler() ); + PositionCursor(); + + Message msg("OptionsMenuChanged"); + msg.SetParam( "Player", m_pn ); + msg.SetParam( "Menu", m_asMenuStack.back() ); + MESSAGEMAN->Broadcast( msg ); +} + +/* After setting up a new row, tween it on. */ +void OptionsList::TweenOnCurrentRow( bool bForward ) +{ + OptionListRow &OldRow = m_Row[!m_iCurrentRow]; + OptionListRow &NewRow = m_Row[m_iCurrentRow]; + + /* Tween out the old row. */ + if( bForward ) + OldRow.PlayCommand( "TweenOutForward" ); + else + OldRow.PlayCommand( "TweenOutBackward" ); + + /* Tween in the old row. */ + if( bForward ) + NewRow.PlayCommand( "TweenInForward" ); + else + NewRow.PlayCommand( "TweenInBackward" ); +} + +void OptionsList::ImportRow( RString sRow ) +{ + vector aSelections[NUM_PLAYERS]; + vector vpns; + vpns.push_back( m_pn ); + OptionRowHandler *pHandler = m_Rows[sRow]; + aSelections[ m_pn ].resize( pHandler->m_Def.m_vsChoices.size() ); + pHandler->ImportOption( NULL, vpns, aSelections ); + m_bSelections[sRow] = aSelections[ m_pn ]; + + if( m_setTopMenus.find(sRow) != m_setTopMenus.end() ) + fill( m_bSelections[sRow].begin(), m_bSelections[sRow].end(), false ); +} + +void OptionsList::ExportRow( RString sRow ) +{ + if( m_setTopMenus.find(sRow) != m_setTopMenus.end() ) + return; + + vector aSelections[NUM_PLAYERS]; + aSelections[m_pn] = m_bSelections[sRow]; + + vector vpns; + vpns.push_back( m_pn ); + + m_Rows[sRow]->ExportOption( vpns, aSelections ); +} + +void OptionsList::SetDefaultCurrentRow() +{ + /* If all items on the row just point to other menus, default to 0. */ + m_iMenuStackSelection = 0; + + const RString &sCurrentRow = m_asMenuStack.back(); + const OptionRowHandler *pHandler = m_Rows.find(sCurrentRow)->second; + if( pHandler->m_Def.m_selectType == SELECT_ONE ) + { + /* One item is selected, so position the cursor on it. */ + m_iMenuStackSelection = GetOneSelection( sCurrentRow, true ); + if( m_iMenuStackSelection == -1 ) + m_iMenuStackSelection = 0; + } +} + +int OptionsList::FindScreenInHandler( const OptionRowHandler *pHandler, RString sScreen ) +{ + for( size_t i = 0; i < pHandler->m_Def.m_vsChoices.size(); ++i ) + { + if( pHandler->GetScreen(i) == sScreen ) + return i; + } + return -1; +} + +void OptionsList::Pop() +{ + if( m_asMenuStack.size() == 1 ) + { + Close(); + return; + } + + RString sLastMenu = m_asMenuStack.back(); + + m_asMenuStack.pop_back(); + + /* Choose the default option. */ + SetDefaultCurrentRow(); + + /* If the old menu exists as a target from the new menu, switch to it. */ + const OptionRowHandler *pHandler = GetCurrentHandler(); + int iIndex = FindScreenInHandler( pHandler, sLastMenu ); + if( iIndex != -1 ) + m_iMenuStackSelection = iIndex; + + SwitchToCurrentRow(); + TweenOnCurrentRow( false ); +} + +void OptionsList::Push( RString sDest ) +{ + m_asMenuStack.push_back( sDest ); + SetDefaultCurrentRow(); + SwitchToCurrentRow(); +} + +void OptionsList::SelectItem( const RString &sRowName, int iMenuItem ) +{ + const OptionRowHandler *pHandler = m_Rows[sRowName]; + vector &bSelections = m_bSelections[sRowName]; + + if( pHandler->m_Def.m_selectType == SELECT_MULTIPLE ) + { + bool bSelected = !bSelections[iMenuItem]; + bSelections[iMenuItem] = bSelected; + +// if( bSelected ) +// m_SoundToggleOn.Play(); +// else +// m_SoundToggleOff.Play(); + } + else // data.selectType != SELECT_MULTIPLE + { + fill( bSelections.begin(), bSelections.end(), false ); + bSelections[iMenuItem] = true; + } + + SelectionsChanged( sRowName ); + UpdateMenuFromSelections(); +} + +void OptionsList::SelectionsChanged( const RString &sRowName ) +{ + const OptionRowHandler *pHandler = m_Rows[sRowName]; + vector &bSelections = m_bSelections[sRowName]; + + if( pHandler->m_Def.m_bOneChoiceForAllPlayers && m_pLinked != NULL ) + { + vector &bLinkedSelections = m_pLinked->m_bSelections[sRowName]; + bLinkedSelections = bSelections; + + if( m_pLinked->IsOpened() ) + m_pLinked->UpdateMenuFromSelections(); + + m_pLinked->ExportRow( sRowName ); + } + + ExportRow( sRowName ); +} + +void OptionsList::UpdateMenuFromSelections() +{ + const vector &bCurrentSelections = m_bSelections.find(GetCurrentRow())->second; + m_Row[m_iCurrentRow].SetUnderlines( bCurrentSelections, GetCurrentHandler() ); + m_Row[m_iCurrentRow].SetTextFromHandler( GetCurrentHandler() ); +} + +bool OptionsList::Start() +{ + const OptionRowHandler *pHandler = GetCurrentHandler(); + const RString &sCurrentRow = m_asMenuStack.back(); + vector &bSelections = m_bSelections[sCurrentRow]; + if( m_iMenuStackSelection == (int)bSelections.size() ) + { + Pop(); + + Message msg("OptionsListPop"); + msg.SetParam( "Player", m_pn ); + MESSAGEMAN->Broadcast( msg ); + + return m_asMenuStack.empty(); + } + + { + RString sIconText; + GameCommand gc; + pHandler->GetIconTextAndGameCommand( m_iMenuStackSelection, sIconText, gc ); + if( gc.m_sName == RESET_ROW ) + { + GAMESTATE->m_pPlayerState[m_pn]->ResetToDefaultPlayerOptions( ModsLevel_Preferred ); + GAMESTATE->ResetToDefaultSongOptions( ModsLevel_Preferred ); + + /* Import options. */ + for (std::pair hand : m_Rows) + { + ImportRow( hand.first ); + SelectionsChanged( hand.first ); + } + + UpdateMenuFromSelections(); + + Message msg("OptionsListReset"); + msg.SetParam( "Player", m_pn ); + MESSAGEMAN->Broadcast( msg ); + + return false; + } + } + + RString sDest = pHandler->GetScreen( m_iMenuStackSelection ); + if( sDest.size() ) + { + Push( sDest ); + TweenOnCurrentRow( true ); + + Message msg("OptionsListPush"); + msg.SetParam( "Player", m_pn ); + MESSAGEMAN->Broadcast( msg ); + + return false; + } + + SelectItem( GetCurrentRow(), m_iMenuStackSelection ); + + /* Move to the exit row. */ + m_iMenuStackSelection = (int)bSelections.size(); + PositionCursor(); + + Message msg("OptionsListStart"); + msg.SetParam( "Player", m_pn ); + MESSAGEMAN->Broadcast( msg ); + + return false; +} + +/* + * Copyright (c) 2006 Glenn Maynard + * 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. + */ + From 7e810ef9f60b5d578859913272b9e7ac2c667855 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 23:56:11 -0400 Subject: [PATCH 043/281] Another map loop. --- src/Background.cpp | 1966 ++++++++++++++++++++++---------------------- 1 file changed, 983 insertions(+), 983 deletions(-) diff --git a/src/Background.cpp b/src/Background.cpp index e077454f31..7a1f453633 100644 --- a/src/Background.cpp +++ b/src/Background.cpp @@ -1,983 +1,983 @@ -#include "global.h" -#include "Background.h" -#include "RageUtil.h" -#include "GameConstantsAndTypes.h" -#include "RageTimer.h" -#include "RageLog.h" -#include "RageDisplay.h" -#include "RageTextureManager.h" -#include "GameState.h" -#include "PrefsManager.h" -#include "NoteTypes.h" -#include "Steps.h" -#include "DancingCharacters.h" -#include "BeginnerHelper.h" -#include "StatsManager.h" -#include "ScreenDimensions.h" -#include "ThemeMetric.h" -#include "PlayerState.h" -#include "ActorUtil.h" -#include -#include "XmlFile.h" -#include "XmlFileUtil.h" -#include "BackgroundUtil.h" -#include "Song.h" -#include "AutoActor.h" - -static ThemeMetric LEFT_EDGE ("Background","LeftEdge"); -static ThemeMetric TOP_EDGE ("Background","TopEdge"); -static ThemeMetric RIGHT_EDGE ("Background","RightEdge"); -static ThemeMetric BOTTOM_EDGE ("Background","BottomEdge"); -static ThemeMetric CLAMP_OUTPUT_PERCENT ("Background","ClampOutputPercent"); -static ThemeMetric SHOW_DANCING_CHARACTERS ("Background","ShowDancingCharacters"); -static ThemeMetric USE_STATIC_BG ("Background","UseStaticBackground"); - - -static Preference g_bShowDanger( "ShowDanger", true ); -static Preference g_fBGBrightness( "BGBrightness", 0.7f ); -static Preference g_RandomBackgroundMode( "RandomBackgroundMode", BGMODE_RANDOMMOVIES ); -static Preference g_iNumBackgrounds( "NumBackgrounds", 8 ); -static Preference g_bSongBackgrounds( "SongBackgrounds", true ); - -// Width of the region separating the left and right brightness areas: -static float g_fBackgroundCenterWidth = 40; - -class BrightnessOverlay: public ActorFrame -{ -public: - BrightnessOverlay(); - void Update( float fDeltaTime ); - - void FadeToActualBrightness(); - void SetActualBrightness(); - void Set( float fBrightness ); - -private: - Quad m_quadBGBrightness[NUM_PLAYERS]; - Quad m_quadBGBrightnessFade; -}; - -struct BackgroundTransition -{ - apActorCommands cmdLeaves; - apActorCommands cmdRoot; -}; - - -class BackgroundImpl : public ActorFrame -{ -public: - BackgroundImpl(); - ~BackgroundImpl(); - void Init(); - - virtual void LoadFromSong( const Song *pSong ); - virtual void Unload(); - - virtual void Update( float fDeltaTime ); - virtual void DrawPrimitives(); - - void FadeToActualBrightness() { m_Brightness.FadeToActualBrightness(); } - void SetBrightness( float fBrightness ) { m_Brightness.Set(fBrightness); } /* overrides pref and Cover */ - - DancingCharacters* GetDancingCharacters() { return m_pDancingCharacters; }; - - void GetLoadedBackgroundChanges( vector *pBackgroundChangesOut[NUM_BackgroundLayer] ); - -protected: - bool m_bInitted; - DancingCharacters* m_pDancingCharacters; - const Song *m_pSong; - map m_mapNameToTransition; - deque m_RandomBGAnimations; // random background to choose from. These may or may not be loaded into m_BGAnimations. - - void LoadFromRandom( float fFirstBeat, float fEndBeat, const BackgroundChange &change ); - bool IsDangerAllVisible(); - - class Layer - { - public: - Layer(); - void Unload(); - - // return true if created and added to m_BGAnimations - bool CreateBackground( const Song *pSong, const BackgroundDef &bd, Actor *pParent ); - // return def of the background that was created and added to m_BGAnimations. calls CreateBackground - BackgroundDef CreateRandomBGA( const Song *pSong, const RString &sEffect, deque &RandomBGAnimations, Actor *pParent ); - - int FindBGSegmentForBeat( float fBeat ) const; - void UpdateCurBGChange( const Song *pSong, float fLastMusicSeconds, float fCurrentTime, const map &mapNameToTransition ); - - map m_BGAnimations; - vector m_aBGChanges; - int m_iCurBGChangeIndex; - Actor *m_pCurrentBGA; - Actor *m_pFadingBGA; - }; - Layer m_Layer[NUM_BackgroundLayer]; - - float m_fLastMusicSeconds; - bool m_bDangerAllWasVisible; - - // cover up the edge of animations that might hang outside of the background rectangle - Quad m_quadBorderLeft, m_quadBorderTop, m_quadBorderRight, m_quadBorderBottom; - - BrightnessOverlay m_Brightness; - - BackgroundDef m_StaticBackgroundDef; -}; - - -static RageColor GetBrightnessColor( float fBrightnessPercent ) -{ - RageColor cBrightness = RageColor( 0,0,0,1-fBrightnessPercent ); - RageColor cClamp = RageColor( 0.5f,0.5f,0.5f,CLAMP_OUTPUT_PERCENT ); - - // blend the two colors above as if cBrightness is drawn, then cClamp drawn on top - cBrightness.a *= (1-cClamp.a); // premultiply alpha - - RageColor ret; - ret.a = cBrightness.a + cClamp.a; - ret.r = (cBrightness.r * cBrightness.a + cClamp.r * cClamp.a) / ret.a; - ret.g = (cBrightness.g * cBrightness.a + cClamp.g * cClamp.a) / ret.a; - ret.b = (cBrightness.b * cBrightness.a + cClamp.b * cClamp.a) / ret.a; - return ret; -} - -BackgroundImpl::BackgroundImpl() -{ - m_bInitted = false; - m_pDancingCharacters = NULL; - m_pSong = NULL; -} - -BackgroundImpl::Layer::Layer() -{ - m_iCurBGChangeIndex = -1; - m_pCurrentBGA = NULL; - m_pFadingBGA = NULL; -} - -void BackgroundImpl::Init() -{ - if( m_bInitted ) - return; - m_bInitted = true; - m_bDangerAllWasVisible = false; - m_StaticBackgroundDef = BackgroundDef(); - - if( !USE_STATIC_BG ) - { - m_StaticBackgroundDef.m_sColor1 = "#00000000"; - m_StaticBackgroundDef.m_sColor2 = "#00000000"; - } - - // load transitions - { - ASSERT( m_mapNameToTransition.empty() ); - vector vsPaths, vsNames; - BackgroundUtil::GetBackgroundTransitions( "", vsPaths, vsNames ); - for( unsigned i=0; im_bShowBeginnerHelper && BeginnerHelper::CanUse() && - GAMESTATE->m_pCurSteps[p] && GAMESTATE->m_pCurSteps[p]->GetDifficulty() == Difficulty_Beginner ) - bShowingBeginnerHelper = true; - } - - if( bOneOrMoreChars && !bShowingBeginnerHelper && SHOW_DANCING_CHARACTERS ) - m_pDancingCharacters = new DancingCharacters; - - RageColor c = GetBrightnessColor(0); - - m_quadBorderLeft.StretchTo( RectF(SCREEN_LEFT,SCREEN_TOP,LEFT_EDGE,SCREEN_BOTTOM) ); - m_quadBorderLeft.SetDiffuse( c ); - m_quadBorderTop.StretchTo( RectF(LEFT_EDGE,SCREEN_TOP,RIGHT_EDGE,TOP_EDGE) ); - m_quadBorderTop.SetDiffuse( c ); - m_quadBorderRight.StretchTo( RectF(RIGHT_EDGE,SCREEN_TOP,SCREEN_RIGHT,SCREEN_BOTTOM) ); - m_quadBorderRight.SetDiffuse( c ); - m_quadBorderBottom.StretchTo( RectF(LEFT_EDGE,BOTTOM_EDGE,RIGHT_EDGE,SCREEN_BOTTOM) ); - m_quadBorderBottom.SetDiffuse( c ); - - this->AddChild( &m_quadBorderLeft ); - this->AddChild( &m_quadBorderTop ); - this->AddChild( &m_quadBorderRight ); - this->AddChild( &m_quadBorderBottom ); - - this->AddChild( &m_Brightness ); -} - -BackgroundImpl::~BackgroundImpl() -{ - Unload(); - delete m_pDancingCharacters; -} - -void BackgroundImpl::Unload() -{ - FOREACH_BackgroundLayer( i ) - m_Layer[i].Unload(); - m_pSong = NULL; - m_fLastMusicSeconds = -9999; - m_RandomBGAnimations.clear(); -} - -void BackgroundImpl::Layer::Unload() -{ - FOREACHM( BackgroundDef, Actor*, m_BGAnimations, iter ) - delete iter->second; - m_BGAnimations.clear(); - m_aBGChanges.clear(); - - m_pCurrentBGA = NULL; - m_pFadingBGA = NULL; - m_iCurBGChangeIndex = -1; -} - -bool BackgroundImpl::Layer::CreateBackground( const Song *pSong, const BackgroundDef &bd, Actor *pParent ) -{ - ASSERT( m_BGAnimations.find(bd) == m_BGAnimations.end() ); - - // Resolve the background names - vector vsToResolve; - vsToResolve.push_back( bd.m_sFile1 ); - vsToResolve.push_back( bd.m_sFile2 ); - - vector vsResolved; - vsResolved.resize( vsToResolve.size() ); - vector vsResolvedRef; - vsResolvedRef.resize( vsToResolve.size() ); - - for( unsigned i=0; i vsPaths, vsThrowAway; - - // Look for BGAnims in the song dir - if( sToResolve == SONG_BACKGROUND_FILE ) - vsPaths.push_back( pSong->HasBackground() ? pSong->GetBackgroundPath() : THEME->GetPathG("Common","fallback background") ); - if( vsPaths.empty() ) BackgroundUtil::GetSongBGAnimations( pSong, sToResolve, vsPaths, vsThrowAway ); - if( vsPaths.empty() ) BackgroundUtil::GetSongMovies( pSong, sToResolve, vsPaths, vsThrowAway ); - if( vsPaths.empty() ) BackgroundUtil::GetSongBitmaps( pSong, sToResolve, vsPaths, vsThrowAway ); - if( vsPaths.empty() ) BackgroundUtil::GetGlobalBGAnimations( pSong, sToResolve, vsPaths, vsThrowAway ); - if( vsPaths.empty() ) BackgroundUtil::GetGlobalRandomMovies( pSong, sToResolve, vsPaths, vsThrowAway ); - - RString &sResolved = vsResolved[i]; - - if( !vsPaths.empty() ) - { - sResolved = vsPaths[0]; - } - else - { - // If the main background file is missing, we failed. - if( i == 0 ) - return false; - else - sResolved = "../"+ThemeManager::GetBlankGraphicPath(); - } - - ASSERT( !sResolved.empty() ); - - vsResolvedRef[i] = new LuaThreadVariable( ssprintf("File%d",i+1), sResolved ); - } - - RString sEffect = bd.m_sEffect; - if( sEffect.empty() ) - { - FileType ft = ActorUtil::GetFileType(vsResolved[0]); - switch( ft ) - { - default: - LOG->Warn( "CreateBackground() Unknown file type '%s'", vsResolved[0].c_str() ); - // fall through - case FT_Bitmap: - case FT_Movie: - sEffect = SBE_StretchNormal; - break; - case FT_Directory: - case FT_Lua: - case FT_Model: - sEffect = SBE_UpperLeft; - break; - } - } - ASSERT( !sEffect.empty() ); - - // Set Lua color globals - LuaThreadVariable sColor1( "Color1", bd.m_sColor1.empty() ? RString("#FFFFFFFF") : bd.m_sColor1 ); - LuaThreadVariable sColor2( "Color2", bd.m_sColor2.empty() ? RString("#FFFFFFFF") : bd.m_sColor2 ); - - // Resolve the effect file. - RString sEffectFile; - for( int i=0; i<2; i++ ) - { - vector vsPaths, vsThrowAway; - BackgroundUtil::GetBackgroundEffects( sEffect, vsPaths, vsThrowAway ); - if( vsPaths.empty() ) - { - LOG->Warn( "BackgroundEffect '%s' is missing.",sEffect.c_str() ); - sEffect = SBE_Centered; - } - else if( vsPaths.size() > 1 ) - { - LOG->Warn( "BackgroundEffect '%s' has more than one match.",sEffect.c_str() ); - sEffect = SBE_Centered; - } - else - { - sEffectFile = vsPaths[0]; - break; - } - } - ASSERT( !sEffectFile.empty() ); - - Actor *pActor = ActorUtil::MakeActor( sEffectFile ); - - ASSERT( pActor != NULL ); - m_BGAnimations[bd] = pActor; - - for( unsigned i=0; i &RandomBGAnimations, Actor *pParent ) -{ - if( g_RandomBackgroundMode == BGMODE_OFF ) - return BackgroundDef(); - - // Set to not show any BGChanges, whether scripted or random - if( GAMESTATE->m_SongOptions.GetCurrent().m_bStaticBackground ) - return BackgroundDef(); - - if( RandomBGAnimations.empty() ) - return BackgroundDef(); - - /* XXX: every time we fully loop, shuffle, so we don't play the same sequence - * over and over; and nudge the shuffle so the next one won't be a repeat */ - BackgroundDef bd = RandomBGAnimations.front(); - RandomBGAnimations.push_back( RandomBGAnimations.front() ); - RandomBGAnimations.pop_front(); - - if( !sEffect.empty() ) - bd.m_sEffect = sEffect; - - map::const_iterator iter = m_BGAnimations.find( bd ); - - // create the background if it's not already created - if( iter == m_BGAnimations.end() ) - { - bool bSuccess = CreateBackground( pSong, bd, pParent ); - ASSERT( bSuccess ); // we fed it valid files, so this shouldn't fail - } - return bd; -} - -void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const BackgroundChange &change ) -{ - int iStartRow = BeatToNoteRow(fFirstBeat); - int iEndRow = BeatToNoteRow(fEndBeat); - - const TimingData &timing = m_pSong->m_SongTiming; - - // change BG every time signature change or 4 measures - const vector &tSigs = timing.GetTimingSegments(SEGMENT_TIME_SIG); - - for (unsigned i = 0; i < tSigs.size(); i++) - { - TimeSignatureSegment *ts = static_cast(tSigs[i]); - int iSegmentEndRow = (i + 1 == tSigs.size()) ? iEndRow : tSigs[i+1]->GetRow(); - - - for(int j=max(ts->GetRow(),iStartRow); - jGetNoteRowsPerMeasure()) - { - // Don't fade. It causes frame rate dip, especially on slower machines. - BackgroundDef bd = m_Layer[0].CreateRandomBGA(m_pSong, - change.m_def.m_sEffect, - m_RandomBGAnimations, this); - if( !bd.IsEmpty() ) - { - BackgroundChange c = change; - c.m_def = bd; - c.m_fStartBeat = NoteRowToBeat(j); - m_Layer[0].m_aBGChanges.push_back( c ); - } - } - } - - // change BG every BPM change that is at the beginning of a measure - const vector &bpms = timing.GetTimingSegments(SEGMENT_BPM); - for( unsigned i=0; i(tSigs[j]); - if ((bpms[i]->GetRow() - ts->GetRow()) % ts->GetNoteRowsPerMeasure() == 0) - { - bAtBeginningOfMeasure = true; - break; - } - } - - if( !bAtBeginningOfMeasure ) - continue; // skip - - // start so that we don't create a BGChange right on top of fEndBeat - bool bInRange = bpms[i]->GetRow() >= iStartRow && bpms[i]->GetRow() < iEndRow; - if( !bInRange ) - continue; // skip - - BackgroundDef bd = m_Layer[0].CreateRandomBGA( m_pSong, change.m_def.m_sEffect, m_RandomBGAnimations, this ); - if( !bd.IsEmpty() ) - { - BackgroundChange c = change; - c.m_def.m_sFile1 = bd.m_sFile1; - c.m_def.m_sFile2 = bd.m_sFile2; - c.m_fStartBeat = bpms[i]->GetBeat(); - m_Layer[0].m_aBGChanges.push_back( c ); - } - } -} - -void BackgroundImpl::LoadFromSong( const Song* pSong ) -{ - Init(); - Unload(); - m_pSong = pSong; - m_StaticBackgroundDef.m_sFile1 = SONG_BACKGROUND_FILE; - - if( g_fBGBrightness == 0.0f ) - return; - - // Choose a bunch of backgrounds that we'll use for the random file marker - { - vector vsThrowAway, vsNames; - switch( g_RandomBackgroundMode ) - { - default: - ASSERT_M( 0, ssprintf("Invalid RandomBackgroundMode: %i", int(g_RandomBackgroundMode)) ); - break; - case BGMODE_OFF: - break; - case BGMODE_ANIMATIONS: - BackgroundUtil::GetGlobalBGAnimations( pSong, "", vsThrowAway, vsNames ); - break; - case BGMODE_RANDOMMOVIES: - BackgroundUtil::GetGlobalRandomMovies( pSong, "", vsThrowAway, vsNames, true, true ); - break; - } - - // Pick the same random items every time the song is played. - RandomGen rnd( GetHashForString(pSong->GetSongDir()) ); - random_shuffle( vsNames.begin(), vsNames.end(), rnd ); - int iSize = min( (int)g_iNumBackgrounds, (int)vsNames.size() ); - vsNames.resize( iSize ); - - FOREACH_CONST( RString, vsNames, s ) - { - BackgroundDef bd; - bd.m_sFile1 = *s; - m_RandomBGAnimations.push_back( bd ); - } - } - - /* Song backgrounds (even just background stills) can get very big; never keep them - * in memory. */ - RageTextureID::TexPolicy OldPolicy = TEXTUREMAN->GetDefaultTexturePolicy(); - TEXTUREMAN->SetDefaultTexturePolicy( RageTextureID::TEX_VOLATILE ); - - TEXTUREMAN->DisableOddDimensionWarning(); - - // Set to not show any BGChanges, whether scripted or random if m_bStaticBackground is on - if( !g_bSongBackgrounds || GAMESTATE->m_SongOptions.GetCurrent().m_bStaticBackground ) - { - // Backgrounds are disabled; just display the song background. - BackgroundChange change; - change.m_def = m_StaticBackgroundDef; - change.m_fStartBeat = 0; - m_Layer[0].m_aBGChanges.push_back( change ); - } - // If m_bRandomBGOnly is on, then we want to ignore the scripted BG in favour of randomly loaded BGs - else if( pSong->HasBGChanges() && !GAMESTATE->m_SongOptions.GetCurrent().m_bRandomBGOnly ) - { - FOREACH_BackgroundLayer( i ) - { - Layer &layer = m_Layer[i]; - - // Load all song-specified backgrounds - FOREACH_CONST( BackgroundChange, pSong->GetBackgroundChanges(i), bgc ) - { - BackgroundChange change = *bgc; - BackgroundDef &bd = change.m_def; - - bool bIsAlreadyLoaded = layer.m_BGAnimations.find(bd) != layer.m_BGAnimations.end(); - - if( bd.m_sFile1 != RANDOM_BACKGROUND_FILE && !bIsAlreadyLoaded ) - { - if( layer.CreateBackground( m_pSong, bd, this ) ) - { - ; // do nothing. Create was successful. - } - else - { - if( i == BACKGROUND_LAYER_1 ) - { - // The background was not found. Try to use a random one instead. - // Don't use the BackgroundDef's effect, because it may be an - // effect that requires 2 files, and random BGA will only supply one file - bd = layer.CreateRandomBGA( pSong, "", m_RandomBGAnimations, this ); - if( bd.IsEmpty() ) - bd = m_StaticBackgroundDef; - } - } - } - - if( !bd.IsEmpty() ) - layer.m_aBGChanges.push_back( change ); - } - } - } - else // pSong doesn't have an animation plan - { - Layer &layer = m_Layer[0]; - float firstBeat = pSong->GetFirstBeat(); - float lastBeat = pSong->GetLastBeat(); - - LoadFromRandom( firstBeat, lastBeat, BackgroundChange() ); - - // end showing the static song background - BackgroundChange change; - change.m_def = m_StaticBackgroundDef; - change.m_fStartBeat = lastBeat; - layer.m_aBGChanges.push_back( change ); - } - - // sort segments - FOREACH_BackgroundLayer( i ) - { - Layer &layer = m_Layer[i]; - BackgroundUtil::SortBackgroundChangesArray( layer.m_aBGChanges ); - } - - Layer &mainlayer = m_Layer[0]; - - /* If the first BGAnimation isn't negative, add a lead-in image showing - * the song background. */ - if( mainlayer.m_aBGChanges.empty() || mainlayer.m_aBGChanges.front().m_fStartBeat >= 0 ) - { - BackgroundChange change; - change.m_def = m_StaticBackgroundDef; - change.m_fStartBeat = -10000; - mainlayer.m_aBGChanges.insert( mainlayer.m_aBGChanges.begin(), change ); - } - - // If any BGChanges use the background image, load it. - bool bStaticBackgroundUsed = false; - FOREACH_BackgroundLayer( i ) - { - Layer &layer = m_Layer[i]; - FOREACH_CONST( BackgroundChange, layer.m_aBGChanges, bgc ) - { - const BackgroundDef &bd = bgc->m_def; - if( bd == m_StaticBackgroundDef ) - { - bStaticBackgroundUsed = true; - break; - } - } - if( bStaticBackgroundUsed ) - break; - } - - if( bStaticBackgroundUsed ) - { - bool bIsAlreadyLoaded = mainlayer.m_BGAnimations.find(m_StaticBackgroundDef) != mainlayer.m_BGAnimations.end(); - if( !bIsAlreadyLoaded ) - { - bool bSuccess = mainlayer.CreateBackground( m_pSong, m_StaticBackgroundDef, this ); - ASSERT( bSuccess ); - } - } - - // Look for the random file marker, and replace the segment with LoadFromRandom. - for( unsigned i=0; iGetLastBeat(); - if( i+1 < mainlayer.m_aBGChanges.size() ) - fEndBeat = mainlayer.m_aBGChanges[i+1].m_fStartBeat; - - mainlayer.m_aBGChanges.erase( mainlayer.m_aBGChanges.begin()+i ); - --i; - - LoadFromRandom( fStartBeat, fEndBeat, change ); - } - - // At this point, we shouldn't have any BGChanges to "". "" is an invalid name. - for( unsigned i=0; iEnableOddDimensionWarning(); - - if( m_pDancingCharacters ) - m_pDancingCharacters->LoadNextSong(); - - TEXTUREMAN->SetDefaultTexturePolicy( OldPolicy ); -} - -int BackgroundImpl::Layer::FindBGSegmentForBeat( float fBeat ) const -{ - if( m_aBGChanges.empty() ) - return -1; - if( fBeat < m_aBGChanges[0].m_fStartBeat ) - return -1; - - // assumption: m_aBGChanges are sorted by m_fStartBeat - int i; - for( i=m_aBGChanges.size()-1; i>=0; i-- ) - { - if( fBeat >= m_aBGChanges[i].m_fStartBeat ) - return i; - } - - return i; -} - -/* If the BG segment has changed, move focus to it. Send Update() calls. */ -void BackgroundImpl::Layer::UpdateCurBGChange( const Song *pSong, float fLastMusicSeconds, float fCurrentTime, const map &mapNameToTransition ) -{ - ASSERT( fCurrentTime != GameState::MUSIC_SECONDS_INVALID ); - - if( m_aBGChanges.size() == 0 ) - return; - - float fBeat, fBPS, fThrowAway; - bool bFreeze; - int iThrowAway; - pSong->m_SongTiming.GetBeatAndBPSFromElapsedTime( fCurrentTime, fBeat, fBPS, bFreeze, bFreeze, iThrowAway, fThrowAway ); - - // Calls to Update() should *not* be scaled by music rate; fCurrentTime is. Undo it. - const float fRate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; - - // Find the BGSegment we're in - const int i = FindBGSegmentForBeat( fBeat ); - - float fDeltaTime = fCurrentTime - fLastMusicSeconds; - if( i != -1 && i != m_iCurBGChangeIndex ) // we're changing backgrounds - { - //LOG->Trace( "old bga %d -> new bga %d (%s), %f, %f", m_iCurBGChangeIndex, i, m_aBGChanges[i].GetTextDescription().c_str(), m_aBGChanges[i].m_fStartBeat, fBeat ); - - BackgroundChange oldChange; - if( m_iCurBGChangeIndex != -1 ) - oldChange = m_aBGChanges[m_iCurBGChangeIndex]; - - m_iCurBGChangeIndex = i; - - const BackgroundChange& change = m_aBGChanges[i]; - - m_pFadingBGA = m_pCurrentBGA; - - map::const_iterator iter = m_BGAnimations.find( change.m_def ); - if( iter == m_BGAnimations.end() ) - { - XNode *pNode = change.m_def.CreateNode(); - RString xml = XmlFileUtil::GetXML( pNode ); - Trim( xml ); - LOG->Warn( "Tried to switch to a background that was never loaded:\n%s", xml.c_str() ); - SAFE_DELETE( pNode ); - return; - } - - m_pCurrentBGA = iter->second; - - if( m_pFadingBGA == m_pCurrentBGA ) - { - m_pFadingBGA = NULL; - //LOG->Trace( "bg didn't actually change. Ignoring." ); - } - else - { - if( m_pFadingBGA ) - { - m_pFadingBGA->PlayCommand( "LoseFocus" ); - - if( !change.m_sTransition.empty() ) - { - map::const_iterator lIter = mapNameToTransition.find( change.m_sTransition ); - ASSERT( lIter != mapNameToTransition.end() ); - const BackgroundTransition &bt = lIter->second; - m_pFadingBGA->RunCommandsOnLeaves( *bt.cmdLeaves ); - m_pFadingBGA->RunCommands( *bt.cmdRoot ); - } - } - } - - m_pCurrentBGA->SetUpdateRate( change.m_fRate ); - - m_pCurrentBGA->InitState(); - m_pCurrentBGA->PlayCommand( "On" ); - m_pCurrentBGA->PlayCommand( "GainFocus" ); - - /* How much time of this BGA have we skipped? (This happens with SetSeconds.) */ - const float fStartSecond = pSong->m_SongTiming.GetElapsedTimeFromBeat( change.m_fStartBeat ); - - /* This is affected by the music rate. */ - fDeltaTime = fCurrentTime - fStartSecond; - } - - if( m_pFadingBGA ) - { - if( m_pFadingBGA->GetTweenTimeLeft() == 0 ) - m_pFadingBGA = NULL; - } - - /* This is unaffected by the music rate. */ - float fDeltaTimeNoMusicRate = max( fDeltaTime / fRate, 0 ); - - if( m_pCurrentBGA ) - m_pCurrentBGA->Update( fDeltaTimeNoMusicRate ); - if( m_pFadingBGA ) - m_pFadingBGA->Update( fDeltaTimeNoMusicRate ); -} - -void BackgroundImpl::Update( float fDeltaTime ) -{ - ActorFrame::Update( fDeltaTime ); - - { - bool bVisible = IsDangerAllVisible(); - if( m_bDangerAllWasVisible != bVisible ) - MESSAGEMAN->Broadcast( bVisible? "ShowDangerAll":"HideDangerAll" ); - m_bDangerAllWasVisible = bVisible; - } - - if( m_pDancingCharacters ) - m_pDancingCharacters->Update( fDeltaTime ); - - FOREACH_BackgroundLayer( i ) - { - Layer &layer = m_Layer[i]; - layer.UpdateCurBGChange( m_pSong, m_fLastMusicSeconds, GAMESTATE->m_Position.m_fMusicSeconds, m_mapNameToTransition ); - } - m_fLastMusicSeconds = GAMESTATE->m_Position.m_fMusicSeconds; -} - -void BackgroundImpl::DrawPrimitives() -{ - if( g_fBGBrightness == 0.0f ) - return; - - if( IsDangerAllVisible() ) - { - // Since this only shows when DANGER is visible, it will flash red on it's own accord :) - if( m_pDancingCharacters ) - m_pDancingCharacters->m_bDrawDangerLight = true; - } - - { - if( m_pDancingCharacters ) - m_pDancingCharacters->m_bDrawDangerLight = false; - - FOREACH_BackgroundLayer( i ) - { - Layer &layer = m_Layer[i]; - if( layer.m_pCurrentBGA ) - layer.m_pCurrentBGA->Draw(); - if( layer.m_pFadingBGA ) - layer.m_pFadingBGA->Draw(); - } - } - - if( m_pDancingCharacters ) - { - m_pDancingCharacters->Draw(); - DISPLAY->ClearZBuffer(); - } - - ActorFrame::DrawPrimitives(); -} - -void BackgroundImpl::GetLoadedBackgroundChanges( vector *pBackgroundChangesOut[NUM_BackgroundLayer] ) -{ - FOREACH_BackgroundLayer( i ) - *pBackgroundChangesOut[i] = m_Layer[i].m_aBGChanges; -} - -bool BackgroundImpl::IsDangerAllVisible() -{ - // The players are never in danger in FAIL_OFF. - FOREACH_PlayerNumber( p ) - if( GAMESTATE->GetPlayerFailType(GAMESTATE->m_pPlayerState[p]) == PlayerOptions::FAIL_OFF ) - return false; - if( !g_bShowDanger ) - return false; - - /* Don't show it if everyone is already failing: it's already too late and it's - * annoying for it to show for the entire duration of a song. */ - if( STATSMAN->m_CurStageStats.AllFailed() ) - return false; - - return GAMESTATE->AllAreInDangerOrWorse(); -} - -BrightnessOverlay::BrightnessOverlay() -{ - float fQuadWidth = (RIGHT_EDGE-LEFT_EDGE)/2; - fQuadWidth -= g_fBackgroundCenterWidth/2; - - m_quadBGBrightness[0].StretchTo( RectF(LEFT_EDGE,TOP_EDGE,LEFT_EDGE+fQuadWidth,BOTTOM_EDGE) ); - m_quadBGBrightnessFade.StretchTo( RectF(LEFT_EDGE+fQuadWidth,TOP_EDGE,RIGHT_EDGE-fQuadWidth,BOTTOM_EDGE) ); - m_quadBGBrightness[1].StretchTo( RectF(RIGHT_EDGE-fQuadWidth,TOP_EDGE,RIGHT_EDGE,BOTTOM_EDGE) ); - - m_quadBGBrightness[0].SetName( "BrightnessOverlay" ); - ActorUtil::LoadAllCommands( m_quadBGBrightness[0], "Background" ); - this->AddChild( &m_quadBGBrightness[0] ); - - m_quadBGBrightness[1].SetName( "BrightnessOverlay" ); - ActorUtil::LoadAllCommands( m_quadBGBrightness[1], "Background" ); - this->AddChild( &m_quadBGBrightness[1] ); - - m_quadBGBrightnessFade.SetName( "BrightnessOverlay" ); - ActorUtil::LoadAllCommands( m_quadBGBrightnessFade, "Background" ); - this->AddChild( &m_quadBGBrightnessFade ); - - SetActualBrightness(); -} - -void BrightnessOverlay::Update( float fDeltaTime ) -{ - ActorFrame::Update( fDeltaTime ); - /* If we're actually playing, then we're past fades, etc; update the - * background brightness to follow Cover. */ - if( !GAMESTATE->m_bGameplayLeadIn ) - SetActualBrightness(); -} - -void BrightnessOverlay::SetActualBrightness() -{ - float fLeftBrightness = 1-GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetCurrent().m_fCover; - float fRightBrightness = 1-GAMESTATE->m_pPlayerState[PLAYER_2]->m_PlayerOptions.GetCurrent().m_fCover; - - float fBaseBGBrightness = g_fBGBrightness; - - // HACK: Always show training in full brightness - if( GAMESTATE->m_pCurSong && GAMESTATE->m_pCurSong->IsTutorial() ) - fBaseBGBrightness = 1.0f; - - fLeftBrightness *= fBaseBGBrightness; - fRightBrightness *= fBaseBGBrightness; - - if( !GAMESTATE->IsHumanPlayer(PLAYER_1) ) - fLeftBrightness = fRightBrightness; - if( !GAMESTATE->IsHumanPlayer(PLAYER_2) ) - fRightBrightness = fLeftBrightness; - - RageColor LeftColor = GetBrightnessColor(fLeftBrightness); - RageColor RightColor = GetBrightnessColor(fRightBrightness); - - m_quadBGBrightness[PLAYER_1].SetDiffuse( LeftColor ); - m_quadBGBrightness[PLAYER_2].SetDiffuse( RightColor ); - m_quadBGBrightnessFade.SetDiffuseLeftEdge( LeftColor ); - m_quadBGBrightnessFade.SetDiffuseRightEdge( RightColor ); -} - -void BrightnessOverlay::Set( float fBrightness ) -{ - RageColor c = GetBrightnessColor(fBrightness); - - FOREACH_PlayerNumber(pn) - m_quadBGBrightness[pn].SetDiffuse( c ); - m_quadBGBrightnessFade.SetDiffuse( c ); -} - -void BrightnessOverlay::FadeToActualBrightness() -{ - this->PlayCommand( "Fade" ); - SetActualBrightness(); -} - -Background::Background() { m_pImpl = new BackgroundImpl; this->AddChild(m_pImpl); } -Background::~Background() { SAFE_DELETE( m_pImpl ); } -void Background::Init() { m_pImpl->Init(); } -void Background::LoadFromSong( const Song *pSong ) { m_pImpl->LoadFromSong(pSong); } -void Background::Unload() { m_pImpl->Unload(); } -void Background::FadeToActualBrightness() { m_pImpl->FadeToActualBrightness(); } -void Background::SetBrightness( float fBrightness ) { m_pImpl->SetBrightness(fBrightness); } -DancingCharacters* Background::GetDancingCharacters() { return m_pImpl->GetDancingCharacters(); } -void Background::GetLoadedBackgroundChanges( vector *pBackgroundChangesOut[NUM_BackgroundLayer] ) { m_pImpl->GetLoadedBackgroundChanges(pBackgroundChangesOut); } - -/* - * (c) 2001-2004 Chris Danford, Ben Nordstrom - * 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 "Background.h" +#include "RageUtil.h" +#include "GameConstantsAndTypes.h" +#include "RageTimer.h" +#include "RageLog.h" +#include "RageDisplay.h" +#include "RageTextureManager.h" +#include "GameState.h" +#include "PrefsManager.h" +#include "NoteTypes.h" +#include "Steps.h" +#include "DancingCharacters.h" +#include "BeginnerHelper.h" +#include "StatsManager.h" +#include "ScreenDimensions.h" +#include "ThemeMetric.h" +#include "PlayerState.h" +#include "ActorUtil.h" +#include +#include "XmlFile.h" +#include "XmlFileUtil.h" +#include "BackgroundUtil.h" +#include "Song.h" +#include "AutoActor.h" + +static ThemeMetric LEFT_EDGE ("Background","LeftEdge"); +static ThemeMetric TOP_EDGE ("Background","TopEdge"); +static ThemeMetric RIGHT_EDGE ("Background","RightEdge"); +static ThemeMetric BOTTOM_EDGE ("Background","BottomEdge"); +static ThemeMetric CLAMP_OUTPUT_PERCENT ("Background","ClampOutputPercent"); +static ThemeMetric SHOW_DANCING_CHARACTERS ("Background","ShowDancingCharacters"); +static ThemeMetric USE_STATIC_BG ("Background","UseStaticBackground"); + + +static Preference g_bShowDanger( "ShowDanger", true ); +static Preference g_fBGBrightness( "BGBrightness", 0.7f ); +static Preference g_RandomBackgroundMode( "RandomBackgroundMode", BGMODE_RANDOMMOVIES ); +static Preference g_iNumBackgrounds( "NumBackgrounds", 8 ); +static Preference g_bSongBackgrounds( "SongBackgrounds", true ); + +// Width of the region separating the left and right brightness areas: +static float g_fBackgroundCenterWidth = 40; + +class BrightnessOverlay: public ActorFrame +{ +public: + BrightnessOverlay(); + void Update( float fDeltaTime ); + + void FadeToActualBrightness(); + void SetActualBrightness(); + void Set( float fBrightness ); + +private: + Quad m_quadBGBrightness[NUM_PLAYERS]; + Quad m_quadBGBrightnessFade; +}; + +struct BackgroundTransition +{ + apActorCommands cmdLeaves; + apActorCommands cmdRoot; +}; + + +class BackgroundImpl : public ActorFrame +{ +public: + BackgroundImpl(); + ~BackgroundImpl(); + void Init(); + + virtual void LoadFromSong( const Song *pSong ); + virtual void Unload(); + + virtual void Update( float fDeltaTime ); + virtual void DrawPrimitives(); + + void FadeToActualBrightness() { m_Brightness.FadeToActualBrightness(); } + void SetBrightness( float fBrightness ) { m_Brightness.Set(fBrightness); } /* overrides pref and Cover */ + + DancingCharacters* GetDancingCharacters() { return m_pDancingCharacters; }; + + void GetLoadedBackgroundChanges( vector *pBackgroundChangesOut[NUM_BackgroundLayer] ); + +protected: + bool m_bInitted; + DancingCharacters* m_pDancingCharacters; + const Song *m_pSong; + map m_mapNameToTransition; + deque m_RandomBGAnimations; // random background to choose from. These may or may not be loaded into m_BGAnimations. + + void LoadFromRandom( float fFirstBeat, float fEndBeat, const BackgroundChange &change ); + bool IsDangerAllVisible(); + + class Layer + { + public: + Layer(); + void Unload(); + + // return true if created and added to m_BGAnimations + bool CreateBackground( const Song *pSong, const BackgroundDef &bd, Actor *pParent ); + // return def of the background that was created and added to m_BGAnimations. calls CreateBackground + BackgroundDef CreateRandomBGA( const Song *pSong, const RString &sEffect, deque &RandomBGAnimations, Actor *pParent ); + + int FindBGSegmentForBeat( float fBeat ) const; + void UpdateCurBGChange( const Song *pSong, float fLastMusicSeconds, float fCurrentTime, const map &mapNameToTransition ); + + map m_BGAnimations; + vector m_aBGChanges; + int m_iCurBGChangeIndex; + Actor *m_pCurrentBGA; + Actor *m_pFadingBGA; + }; + Layer m_Layer[NUM_BackgroundLayer]; + + float m_fLastMusicSeconds; + bool m_bDangerAllWasVisible; + + // cover up the edge of animations that might hang outside of the background rectangle + Quad m_quadBorderLeft, m_quadBorderTop, m_quadBorderRight, m_quadBorderBottom; + + BrightnessOverlay m_Brightness; + + BackgroundDef m_StaticBackgroundDef; +}; + + +static RageColor GetBrightnessColor( float fBrightnessPercent ) +{ + RageColor cBrightness = RageColor( 0,0,0,1-fBrightnessPercent ); + RageColor cClamp = RageColor( 0.5f,0.5f,0.5f,CLAMP_OUTPUT_PERCENT ); + + // blend the two colors above as if cBrightness is drawn, then cClamp drawn on top + cBrightness.a *= (1-cClamp.a); // premultiply alpha + + RageColor ret; + ret.a = cBrightness.a + cClamp.a; + ret.r = (cBrightness.r * cBrightness.a + cClamp.r * cClamp.a) / ret.a; + ret.g = (cBrightness.g * cBrightness.a + cClamp.g * cClamp.a) / ret.a; + ret.b = (cBrightness.b * cBrightness.a + cClamp.b * cClamp.a) / ret.a; + return ret; +} + +BackgroundImpl::BackgroundImpl() +{ + m_bInitted = false; + m_pDancingCharacters = NULL; + m_pSong = NULL; +} + +BackgroundImpl::Layer::Layer() +{ + m_iCurBGChangeIndex = -1; + m_pCurrentBGA = NULL; + m_pFadingBGA = NULL; +} + +void BackgroundImpl::Init() +{ + if( m_bInitted ) + return; + m_bInitted = true; + m_bDangerAllWasVisible = false; + m_StaticBackgroundDef = BackgroundDef(); + + if( !USE_STATIC_BG ) + { + m_StaticBackgroundDef.m_sColor1 = "#00000000"; + m_StaticBackgroundDef.m_sColor2 = "#00000000"; + } + + // load transitions + { + ASSERT( m_mapNameToTransition.empty() ); + vector vsPaths, vsNames; + BackgroundUtil::GetBackgroundTransitions( "", vsPaths, vsNames ); + for( unsigned i=0; im_bShowBeginnerHelper && BeginnerHelper::CanUse() && + GAMESTATE->m_pCurSteps[p] && GAMESTATE->m_pCurSteps[p]->GetDifficulty() == Difficulty_Beginner ) + bShowingBeginnerHelper = true; + } + + if( bOneOrMoreChars && !bShowingBeginnerHelper && SHOW_DANCING_CHARACTERS ) + m_pDancingCharacters = new DancingCharacters; + + RageColor c = GetBrightnessColor(0); + + m_quadBorderLeft.StretchTo( RectF(SCREEN_LEFT,SCREEN_TOP,LEFT_EDGE,SCREEN_BOTTOM) ); + m_quadBorderLeft.SetDiffuse( c ); + m_quadBorderTop.StretchTo( RectF(LEFT_EDGE,SCREEN_TOP,RIGHT_EDGE,TOP_EDGE) ); + m_quadBorderTop.SetDiffuse( c ); + m_quadBorderRight.StretchTo( RectF(RIGHT_EDGE,SCREEN_TOP,SCREEN_RIGHT,SCREEN_BOTTOM) ); + m_quadBorderRight.SetDiffuse( c ); + m_quadBorderBottom.StretchTo( RectF(LEFT_EDGE,BOTTOM_EDGE,RIGHT_EDGE,SCREEN_BOTTOM) ); + m_quadBorderBottom.SetDiffuse( c ); + + this->AddChild( &m_quadBorderLeft ); + this->AddChild( &m_quadBorderTop ); + this->AddChild( &m_quadBorderRight ); + this->AddChild( &m_quadBorderBottom ); + + this->AddChild( &m_Brightness ); +} + +BackgroundImpl::~BackgroundImpl() +{ + Unload(); + delete m_pDancingCharacters; +} + +void BackgroundImpl::Unload() +{ + FOREACH_BackgroundLayer( i ) + m_Layer[i].Unload(); + m_pSong = NULL; + m_fLastMusicSeconds = -9999; + m_RandomBGAnimations.clear(); +} + +void BackgroundImpl::Layer::Unload() +{ + for (std::pair iter : m_BGAnimations) + delete iter.second; + m_BGAnimations.clear(); + m_aBGChanges.clear(); + + m_pCurrentBGA = NULL; + m_pFadingBGA = NULL; + m_iCurBGChangeIndex = -1; +} + +bool BackgroundImpl::Layer::CreateBackground( const Song *pSong, const BackgroundDef &bd, Actor *pParent ) +{ + ASSERT( m_BGAnimations.find(bd) == m_BGAnimations.end() ); + + // Resolve the background names + vector vsToResolve; + vsToResolve.push_back( bd.m_sFile1 ); + vsToResolve.push_back( bd.m_sFile2 ); + + vector vsResolved; + vsResolved.resize( vsToResolve.size() ); + vector vsResolvedRef; + vsResolvedRef.resize( vsToResolve.size() ); + + for( unsigned i=0; i vsPaths, vsThrowAway; + + // Look for BGAnims in the song dir + if( sToResolve == SONG_BACKGROUND_FILE ) + vsPaths.push_back( pSong->HasBackground() ? pSong->GetBackgroundPath() : THEME->GetPathG("Common","fallback background") ); + if( vsPaths.empty() ) BackgroundUtil::GetSongBGAnimations( pSong, sToResolve, vsPaths, vsThrowAway ); + if( vsPaths.empty() ) BackgroundUtil::GetSongMovies( pSong, sToResolve, vsPaths, vsThrowAway ); + if( vsPaths.empty() ) BackgroundUtil::GetSongBitmaps( pSong, sToResolve, vsPaths, vsThrowAway ); + if( vsPaths.empty() ) BackgroundUtil::GetGlobalBGAnimations( pSong, sToResolve, vsPaths, vsThrowAway ); + if( vsPaths.empty() ) BackgroundUtil::GetGlobalRandomMovies( pSong, sToResolve, vsPaths, vsThrowAway ); + + RString &sResolved = vsResolved[i]; + + if( !vsPaths.empty() ) + { + sResolved = vsPaths[0]; + } + else + { + // If the main background file is missing, we failed. + if( i == 0 ) + return false; + else + sResolved = "../"+ThemeManager::GetBlankGraphicPath(); + } + + ASSERT( !sResolved.empty() ); + + vsResolvedRef[i] = new LuaThreadVariable( ssprintf("File%d",i+1), sResolved ); + } + + RString sEffect = bd.m_sEffect; + if( sEffect.empty() ) + { + FileType ft = ActorUtil::GetFileType(vsResolved[0]); + switch( ft ) + { + default: + LOG->Warn( "CreateBackground() Unknown file type '%s'", vsResolved[0].c_str() ); + // fall through + case FT_Bitmap: + case FT_Movie: + sEffect = SBE_StretchNormal; + break; + case FT_Directory: + case FT_Lua: + case FT_Model: + sEffect = SBE_UpperLeft; + break; + } + } + ASSERT( !sEffect.empty() ); + + // Set Lua color globals + LuaThreadVariable sColor1( "Color1", bd.m_sColor1.empty() ? RString("#FFFFFFFF") : bd.m_sColor1 ); + LuaThreadVariable sColor2( "Color2", bd.m_sColor2.empty() ? RString("#FFFFFFFF") : bd.m_sColor2 ); + + // Resolve the effect file. + RString sEffectFile; + for( int i=0; i<2; i++ ) + { + vector vsPaths, vsThrowAway; + BackgroundUtil::GetBackgroundEffects( sEffect, vsPaths, vsThrowAway ); + if( vsPaths.empty() ) + { + LOG->Warn( "BackgroundEffect '%s' is missing.",sEffect.c_str() ); + sEffect = SBE_Centered; + } + else if( vsPaths.size() > 1 ) + { + LOG->Warn( "BackgroundEffect '%s' has more than one match.",sEffect.c_str() ); + sEffect = SBE_Centered; + } + else + { + sEffectFile = vsPaths[0]; + break; + } + } + ASSERT( !sEffectFile.empty() ); + + Actor *pActor = ActorUtil::MakeActor( sEffectFile ); + + ASSERT( pActor != NULL ); + m_BGAnimations[bd] = pActor; + + for( unsigned i=0; i &RandomBGAnimations, Actor *pParent ) +{ + if( g_RandomBackgroundMode == BGMODE_OFF ) + return BackgroundDef(); + + // Set to not show any BGChanges, whether scripted or random + if( GAMESTATE->m_SongOptions.GetCurrent().m_bStaticBackground ) + return BackgroundDef(); + + if( RandomBGAnimations.empty() ) + return BackgroundDef(); + + /* XXX: every time we fully loop, shuffle, so we don't play the same sequence + * over and over; and nudge the shuffle so the next one won't be a repeat */ + BackgroundDef bd = RandomBGAnimations.front(); + RandomBGAnimations.push_back( RandomBGAnimations.front() ); + RandomBGAnimations.pop_front(); + + if( !sEffect.empty() ) + bd.m_sEffect = sEffect; + + map::const_iterator iter = m_BGAnimations.find( bd ); + + // create the background if it's not already created + if( iter == m_BGAnimations.end() ) + { + bool bSuccess = CreateBackground( pSong, bd, pParent ); + ASSERT( bSuccess ); // we fed it valid files, so this shouldn't fail + } + return bd; +} + +void BackgroundImpl::LoadFromRandom( float fFirstBeat, float fEndBeat, const BackgroundChange &change ) +{ + int iStartRow = BeatToNoteRow(fFirstBeat); + int iEndRow = BeatToNoteRow(fEndBeat); + + const TimingData &timing = m_pSong->m_SongTiming; + + // change BG every time signature change or 4 measures + const vector &tSigs = timing.GetTimingSegments(SEGMENT_TIME_SIG); + + for (unsigned i = 0; i < tSigs.size(); i++) + { + TimeSignatureSegment *ts = static_cast(tSigs[i]); + int iSegmentEndRow = (i + 1 == tSigs.size()) ? iEndRow : tSigs[i+1]->GetRow(); + + + for(int j=max(ts->GetRow(),iStartRow); + jGetNoteRowsPerMeasure()) + { + // Don't fade. It causes frame rate dip, especially on slower machines. + BackgroundDef bd = m_Layer[0].CreateRandomBGA(m_pSong, + change.m_def.m_sEffect, + m_RandomBGAnimations, this); + if( !bd.IsEmpty() ) + { + BackgroundChange c = change; + c.m_def = bd; + c.m_fStartBeat = NoteRowToBeat(j); + m_Layer[0].m_aBGChanges.push_back( c ); + } + } + } + + // change BG every BPM change that is at the beginning of a measure + const vector &bpms = timing.GetTimingSegments(SEGMENT_BPM); + for( unsigned i=0; i(tSigs[j]); + if ((bpms[i]->GetRow() - ts->GetRow()) % ts->GetNoteRowsPerMeasure() == 0) + { + bAtBeginningOfMeasure = true; + break; + } + } + + if( !bAtBeginningOfMeasure ) + continue; // skip + + // start so that we don't create a BGChange right on top of fEndBeat + bool bInRange = bpms[i]->GetRow() >= iStartRow && bpms[i]->GetRow() < iEndRow; + if( !bInRange ) + continue; // skip + + BackgroundDef bd = m_Layer[0].CreateRandomBGA( m_pSong, change.m_def.m_sEffect, m_RandomBGAnimations, this ); + if( !bd.IsEmpty() ) + { + BackgroundChange c = change; + c.m_def.m_sFile1 = bd.m_sFile1; + c.m_def.m_sFile2 = bd.m_sFile2; + c.m_fStartBeat = bpms[i]->GetBeat(); + m_Layer[0].m_aBGChanges.push_back( c ); + } + } +} + +void BackgroundImpl::LoadFromSong( const Song* pSong ) +{ + Init(); + Unload(); + m_pSong = pSong; + m_StaticBackgroundDef.m_sFile1 = SONG_BACKGROUND_FILE; + + if( g_fBGBrightness == 0.0f ) + return; + + // Choose a bunch of backgrounds that we'll use for the random file marker + { + vector vsThrowAway, vsNames; + switch( g_RandomBackgroundMode ) + { + default: + ASSERT_M( 0, ssprintf("Invalid RandomBackgroundMode: %i", int(g_RandomBackgroundMode)) ); + break; + case BGMODE_OFF: + break; + case BGMODE_ANIMATIONS: + BackgroundUtil::GetGlobalBGAnimations( pSong, "", vsThrowAway, vsNames ); + break; + case BGMODE_RANDOMMOVIES: + BackgroundUtil::GetGlobalRandomMovies( pSong, "", vsThrowAway, vsNames, true, true ); + break; + } + + // Pick the same random items every time the song is played. + RandomGen rnd( GetHashForString(pSong->GetSongDir()) ); + random_shuffle( vsNames.begin(), vsNames.end(), rnd ); + int iSize = min( (int)g_iNumBackgrounds, (int)vsNames.size() ); + vsNames.resize( iSize ); + + FOREACH_CONST( RString, vsNames, s ) + { + BackgroundDef bd; + bd.m_sFile1 = *s; + m_RandomBGAnimations.push_back( bd ); + } + } + + /* Song backgrounds (even just background stills) can get very big; never keep them + * in memory. */ + RageTextureID::TexPolicy OldPolicy = TEXTUREMAN->GetDefaultTexturePolicy(); + TEXTUREMAN->SetDefaultTexturePolicy( RageTextureID::TEX_VOLATILE ); + + TEXTUREMAN->DisableOddDimensionWarning(); + + // Set to not show any BGChanges, whether scripted or random if m_bStaticBackground is on + if( !g_bSongBackgrounds || GAMESTATE->m_SongOptions.GetCurrent().m_bStaticBackground ) + { + // Backgrounds are disabled; just display the song background. + BackgroundChange change; + change.m_def = m_StaticBackgroundDef; + change.m_fStartBeat = 0; + m_Layer[0].m_aBGChanges.push_back( change ); + } + // If m_bRandomBGOnly is on, then we want to ignore the scripted BG in favour of randomly loaded BGs + else if( pSong->HasBGChanges() && !GAMESTATE->m_SongOptions.GetCurrent().m_bRandomBGOnly ) + { + FOREACH_BackgroundLayer( i ) + { + Layer &layer = m_Layer[i]; + + // Load all song-specified backgrounds + FOREACH_CONST( BackgroundChange, pSong->GetBackgroundChanges(i), bgc ) + { + BackgroundChange change = *bgc; + BackgroundDef &bd = change.m_def; + + bool bIsAlreadyLoaded = layer.m_BGAnimations.find(bd) != layer.m_BGAnimations.end(); + + if( bd.m_sFile1 != RANDOM_BACKGROUND_FILE && !bIsAlreadyLoaded ) + { + if( layer.CreateBackground( m_pSong, bd, this ) ) + { + ; // do nothing. Create was successful. + } + else + { + if( i == BACKGROUND_LAYER_1 ) + { + // The background was not found. Try to use a random one instead. + // Don't use the BackgroundDef's effect, because it may be an + // effect that requires 2 files, and random BGA will only supply one file + bd = layer.CreateRandomBGA( pSong, "", m_RandomBGAnimations, this ); + if( bd.IsEmpty() ) + bd = m_StaticBackgroundDef; + } + } + } + + if( !bd.IsEmpty() ) + layer.m_aBGChanges.push_back( change ); + } + } + } + else // pSong doesn't have an animation plan + { + Layer &layer = m_Layer[0]; + float firstBeat = pSong->GetFirstBeat(); + float lastBeat = pSong->GetLastBeat(); + + LoadFromRandom( firstBeat, lastBeat, BackgroundChange() ); + + // end showing the static song background + BackgroundChange change; + change.m_def = m_StaticBackgroundDef; + change.m_fStartBeat = lastBeat; + layer.m_aBGChanges.push_back( change ); + } + + // sort segments + FOREACH_BackgroundLayer( i ) + { + Layer &layer = m_Layer[i]; + BackgroundUtil::SortBackgroundChangesArray( layer.m_aBGChanges ); + } + + Layer &mainlayer = m_Layer[0]; + + /* If the first BGAnimation isn't negative, add a lead-in image showing + * the song background. */ + if( mainlayer.m_aBGChanges.empty() || mainlayer.m_aBGChanges.front().m_fStartBeat >= 0 ) + { + BackgroundChange change; + change.m_def = m_StaticBackgroundDef; + change.m_fStartBeat = -10000; + mainlayer.m_aBGChanges.insert( mainlayer.m_aBGChanges.begin(), change ); + } + + // If any BGChanges use the background image, load it. + bool bStaticBackgroundUsed = false; + FOREACH_BackgroundLayer( i ) + { + Layer &layer = m_Layer[i]; + FOREACH_CONST( BackgroundChange, layer.m_aBGChanges, bgc ) + { + const BackgroundDef &bd = bgc->m_def; + if( bd == m_StaticBackgroundDef ) + { + bStaticBackgroundUsed = true; + break; + } + } + if( bStaticBackgroundUsed ) + break; + } + + if( bStaticBackgroundUsed ) + { + bool bIsAlreadyLoaded = mainlayer.m_BGAnimations.find(m_StaticBackgroundDef) != mainlayer.m_BGAnimations.end(); + if( !bIsAlreadyLoaded ) + { + bool bSuccess = mainlayer.CreateBackground( m_pSong, m_StaticBackgroundDef, this ); + ASSERT( bSuccess ); + } + } + + // Look for the random file marker, and replace the segment with LoadFromRandom. + for( unsigned i=0; iGetLastBeat(); + if( i+1 < mainlayer.m_aBGChanges.size() ) + fEndBeat = mainlayer.m_aBGChanges[i+1].m_fStartBeat; + + mainlayer.m_aBGChanges.erase( mainlayer.m_aBGChanges.begin()+i ); + --i; + + LoadFromRandom( fStartBeat, fEndBeat, change ); + } + + // At this point, we shouldn't have any BGChanges to "". "" is an invalid name. + for( unsigned i=0; iEnableOddDimensionWarning(); + + if( m_pDancingCharacters ) + m_pDancingCharacters->LoadNextSong(); + + TEXTUREMAN->SetDefaultTexturePolicy( OldPolicy ); +} + +int BackgroundImpl::Layer::FindBGSegmentForBeat( float fBeat ) const +{ + if( m_aBGChanges.empty() ) + return -1; + if( fBeat < m_aBGChanges[0].m_fStartBeat ) + return -1; + + // assumption: m_aBGChanges are sorted by m_fStartBeat + int i; + for( i=m_aBGChanges.size()-1; i>=0; i-- ) + { + if( fBeat >= m_aBGChanges[i].m_fStartBeat ) + return i; + } + + return i; +} + +/* If the BG segment has changed, move focus to it. Send Update() calls. */ +void BackgroundImpl::Layer::UpdateCurBGChange( const Song *pSong, float fLastMusicSeconds, float fCurrentTime, const map &mapNameToTransition ) +{ + ASSERT( fCurrentTime != GameState::MUSIC_SECONDS_INVALID ); + + if( m_aBGChanges.size() == 0 ) + return; + + float fBeat, fBPS, fThrowAway; + bool bFreeze; + int iThrowAway; + pSong->m_SongTiming.GetBeatAndBPSFromElapsedTime( fCurrentTime, fBeat, fBPS, bFreeze, bFreeze, iThrowAway, fThrowAway ); + + // Calls to Update() should *not* be scaled by music rate; fCurrentTime is. Undo it. + const float fRate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; + + // Find the BGSegment we're in + const int i = FindBGSegmentForBeat( fBeat ); + + float fDeltaTime = fCurrentTime - fLastMusicSeconds; + if( i != -1 && i != m_iCurBGChangeIndex ) // we're changing backgrounds + { + //LOG->Trace( "old bga %d -> new bga %d (%s), %f, %f", m_iCurBGChangeIndex, i, m_aBGChanges[i].GetTextDescription().c_str(), m_aBGChanges[i].m_fStartBeat, fBeat ); + + BackgroundChange oldChange; + if( m_iCurBGChangeIndex != -1 ) + oldChange = m_aBGChanges[m_iCurBGChangeIndex]; + + m_iCurBGChangeIndex = i; + + const BackgroundChange& change = m_aBGChanges[i]; + + m_pFadingBGA = m_pCurrentBGA; + + map::const_iterator iter = m_BGAnimations.find( change.m_def ); + if( iter == m_BGAnimations.end() ) + { + XNode *pNode = change.m_def.CreateNode(); + RString xml = XmlFileUtil::GetXML( pNode ); + Trim( xml ); + LOG->Warn( "Tried to switch to a background that was never loaded:\n%s", xml.c_str() ); + SAFE_DELETE( pNode ); + return; + } + + m_pCurrentBGA = iter->second; + + if( m_pFadingBGA == m_pCurrentBGA ) + { + m_pFadingBGA = NULL; + //LOG->Trace( "bg didn't actually change. Ignoring." ); + } + else + { + if( m_pFadingBGA ) + { + m_pFadingBGA->PlayCommand( "LoseFocus" ); + + if( !change.m_sTransition.empty() ) + { + map::const_iterator lIter = mapNameToTransition.find( change.m_sTransition ); + ASSERT( lIter != mapNameToTransition.end() ); + const BackgroundTransition &bt = lIter->second; + m_pFadingBGA->RunCommandsOnLeaves( *bt.cmdLeaves ); + m_pFadingBGA->RunCommands( *bt.cmdRoot ); + } + } + } + + m_pCurrentBGA->SetUpdateRate( change.m_fRate ); + + m_pCurrentBGA->InitState(); + m_pCurrentBGA->PlayCommand( "On" ); + m_pCurrentBGA->PlayCommand( "GainFocus" ); + + /* How much time of this BGA have we skipped? (This happens with SetSeconds.) */ + const float fStartSecond = pSong->m_SongTiming.GetElapsedTimeFromBeat( change.m_fStartBeat ); + + /* This is affected by the music rate. */ + fDeltaTime = fCurrentTime - fStartSecond; + } + + if( m_pFadingBGA ) + { + if( m_pFadingBGA->GetTweenTimeLeft() == 0 ) + m_pFadingBGA = NULL; + } + + /* This is unaffected by the music rate. */ + float fDeltaTimeNoMusicRate = max( fDeltaTime / fRate, 0 ); + + if( m_pCurrentBGA ) + m_pCurrentBGA->Update( fDeltaTimeNoMusicRate ); + if( m_pFadingBGA ) + m_pFadingBGA->Update( fDeltaTimeNoMusicRate ); +} + +void BackgroundImpl::Update( float fDeltaTime ) +{ + ActorFrame::Update( fDeltaTime ); + + { + bool bVisible = IsDangerAllVisible(); + if( m_bDangerAllWasVisible != bVisible ) + MESSAGEMAN->Broadcast( bVisible? "ShowDangerAll":"HideDangerAll" ); + m_bDangerAllWasVisible = bVisible; + } + + if( m_pDancingCharacters ) + m_pDancingCharacters->Update( fDeltaTime ); + + FOREACH_BackgroundLayer( i ) + { + Layer &layer = m_Layer[i]; + layer.UpdateCurBGChange( m_pSong, m_fLastMusicSeconds, GAMESTATE->m_Position.m_fMusicSeconds, m_mapNameToTransition ); + } + m_fLastMusicSeconds = GAMESTATE->m_Position.m_fMusicSeconds; +} + +void BackgroundImpl::DrawPrimitives() +{ + if( g_fBGBrightness == 0.0f ) + return; + + if( IsDangerAllVisible() ) + { + // Since this only shows when DANGER is visible, it will flash red on it's own accord :) + if( m_pDancingCharacters ) + m_pDancingCharacters->m_bDrawDangerLight = true; + } + + { + if( m_pDancingCharacters ) + m_pDancingCharacters->m_bDrawDangerLight = false; + + FOREACH_BackgroundLayer( i ) + { + Layer &layer = m_Layer[i]; + if( layer.m_pCurrentBGA ) + layer.m_pCurrentBGA->Draw(); + if( layer.m_pFadingBGA ) + layer.m_pFadingBGA->Draw(); + } + } + + if( m_pDancingCharacters ) + { + m_pDancingCharacters->Draw(); + DISPLAY->ClearZBuffer(); + } + + ActorFrame::DrawPrimitives(); +} + +void BackgroundImpl::GetLoadedBackgroundChanges( vector *pBackgroundChangesOut[NUM_BackgroundLayer] ) +{ + FOREACH_BackgroundLayer( i ) + *pBackgroundChangesOut[i] = m_Layer[i].m_aBGChanges; +} + +bool BackgroundImpl::IsDangerAllVisible() +{ + // The players are never in danger in FAIL_OFF. + FOREACH_PlayerNumber( p ) + if( GAMESTATE->GetPlayerFailType(GAMESTATE->m_pPlayerState[p]) == PlayerOptions::FAIL_OFF ) + return false; + if( !g_bShowDanger ) + return false; + + /* Don't show it if everyone is already failing: it's already too late and it's + * annoying for it to show for the entire duration of a song. */ + if( STATSMAN->m_CurStageStats.AllFailed() ) + return false; + + return GAMESTATE->AllAreInDangerOrWorse(); +} + +BrightnessOverlay::BrightnessOverlay() +{ + float fQuadWidth = (RIGHT_EDGE-LEFT_EDGE)/2; + fQuadWidth -= g_fBackgroundCenterWidth/2; + + m_quadBGBrightness[0].StretchTo( RectF(LEFT_EDGE,TOP_EDGE,LEFT_EDGE+fQuadWidth,BOTTOM_EDGE) ); + m_quadBGBrightnessFade.StretchTo( RectF(LEFT_EDGE+fQuadWidth,TOP_EDGE,RIGHT_EDGE-fQuadWidth,BOTTOM_EDGE) ); + m_quadBGBrightness[1].StretchTo( RectF(RIGHT_EDGE-fQuadWidth,TOP_EDGE,RIGHT_EDGE,BOTTOM_EDGE) ); + + m_quadBGBrightness[0].SetName( "BrightnessOverlay" ); + ActorUtil::LoadAllCommands( m_quadBGBrightness[0], "Background" ); + this->AddChild( &m_quadBGBrightness[0] ); + + m_quadBGBrightness[1].SetName( "BrightnessOverlay" ); + ActorUtil::LoadAllCommands( m_quadBGBrightness[1], "Background" ); + this->AddChild( &m_quadBGBrightness[1] ); + + m_quadBGBrightnessFade.SetName( "BrightnessOverlay" ); + ActorUtil::LoadAllCommands( m_quadBGBrightnessFade, "Background" ); + this->AddChild( &m_quadBGBrightnessFade ); + + SetActualBrightness(); +} + +void BrightnessOverlay::Update( float fDeltaTime ) +{ + ActorFrame::Update( fDeltaTime ); + /* If we're actually playing, then we're past fades, etc; update the + * background brightness to follow Cover. */ + if( !GAMESTATE->m_bGameplayLeadIn ) + SetActualBrightness(); +} + +void BrightnessOverlay::SetActualBrightness() +{ + float fLeftBrightness = 1-GAMESTATE->m_pPlayerState[PLAYER_1]->m_PlayerOptions.GetCurrent().m_fCover; + float fRightBrightness = 1-GAMESTATE->m_pPlayerState[PLAYER_2]->m_PlayerOptions.GetCurrent().m_fCover; + + float fBaseBGBrightness = g_fBGBrightness; + + // HACK: Always show training in full brightness + if( GAMESTATE->m_pCurSong && GAMESTATE->m_pCurSong->IsTutorial() ) + fBaseBGBrightness = 1.0f; + + fLeftBrightness *= fBaseBGBrightness; + fRightBrightness *= fBaseBGBrightness; + + if( !GAMESTATE->IsHumanPlayer(PLAYER_1) ) + fLeftBrightness = fRightBrightness; + if( !GAMESTATE->IsHumanPlayer(PLAYER_2) ) + fRightBrightness = fLeftBrightness; + + RageColor LeftColor = GetBrightnessColor(fLeftBrightness); + RageColor RightColor = GetBrightnessColor(fRightBrightness); + + m_quadBGBrightness[PLAYER_1].SetDiffuse( LeftColor ); + m_quadBGBrightness[PLAYER_2].SetDiffuse( RightColor ); + m_quadBGBrightnessFade.SetDiffuseLeftEdge( LeftColor ); + m_quadBGBrightnessFade.SetDiffuseRightEdge( RightColor ); +} + +void BrightnessOverlay::Set( float fBrightness ) +{ + RageColor c = GetBrightnessColor(fBrightness); + + FOREACH_PlayerNumber(pn) + m_quadBGBrightness[pn].SetDiffuse( c ); + m_quadBGBrightnessFade.SetDiffuse( c ); +} + +void BrightnessOverlay::FadeToActualBrightness() +{ + this->PlayCommand( "Fade" ); + SetActualBrightness(); +} + +Background::Background() { m_pImpl = new BackgroundImpl; this->AddChild(m_pImpl); } +Background::~Background() { SAFE_DELETE( m_pImpl ); } +void Background::Init() { m_pImpl->Init(); } +void Background::LoadFromSong( const Song *pSong ) { m_pImpl->LoadFromSong(pSong); } +void Background::Unload() { m_pImpl->Unload(); } +void Background::FadeToActualBrightness() { m_pImpl->FadeToActualBrightness(); } +void Background::SetBrightness( float fBrightness ) { m_pImpl->SetBrightness(fBrightness); } +DancingCharacters* Background::GetDancingCharacters() { return m_pImpl->GetDancingCharacters(); } +void Background::GetLoadedBackgroundChanges( vector *pBackgroundChangesOut[NUM_BackgroundLayer] ) { m_pImpl->GetLoadedBackgroundChanges(pBackgroundChangesOut); } + +/* + * (c) 2001-2004 Chris Danford, Ben Nordstrom + * 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. + */ From 85dae04da5245dcfd3f9c1b522b6837346e367a1 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sat, 27 Apr 2013 23:58:35 -0400 Subject: [PATCH 044/281] Again, auto can come in handy. --- src/Course.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Course.cpp b/src/Course.cpp index 75d32fce14..66a79a6edf 100644 --- a/src/Course.cpp +++ b/src/Course.cpp @@ -798,9 +798,9 @@ void Course::RegenerateNonFixedTrails() const if( AllSongsAreFixed() ) return; - FOREACHM( CacheEntry, CacheData, m_TrailCache, e ) + for (auto const & e: m_TrailCache) { - const CacheEntry &ce = e->first; + const CacheEntry &ce = e.first; GetTrailForceRegenCache( ce.first, ce.second ); } } From 4eed642749f843903c46eeb7aa424b4028a60a24 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sun, 28 Apr 2013 00:00:00 -0400 Subject: [PATCH 045/281] Handle the float map loop. --- src/NotesWriterSM.cpp | 730 +++++++++++++++++++++--------------------- 1 file changed, 365 insertions(+), 365 deletions(-) diff --git a/src/NotesWriterSM.cpp b/src/NotesWriterSM.cpp index 8f131b9210..69c1df2c92 100644 --- a/src/NotesWriterSM.cpp +++ b/src/NotesWriterSM.cpp @@ -1,365 +1,365 @@ -#include "global.h" -#include -#include -#include "NotesWriterSM.h" -#include "BackgroundUtil.h" -#include "Foreach.h" -#include "GameManager.h" -#include "LocalizedString.h" -#include "NoteTypes.h" -#include "Profile.h" -#include "ProfileManager.h" -#include "RageFile.h" -#include "RageFileManager.h" -#include "RageLog.h" -#include "RageUtil.h" -#include "Song.h" -#include "Steps.h" -#include "ThemeMetric.h" - -ThemeMetric USE_CREDIT ( "NotesWriterSM", "DescriptionUsesCreditField" ); - -/** - * @brief Write out the common tags for .SM files. - * @param f the file in question. - * @param out the Song in question. */ -static void WriteGlobalTags( RageFile &f, Song &out ) -{ - TimingData &timing = out.m_SongTiming; - f.PutLine( ssprintf( "#TITLE:%s;", SmEscape(out.m_sMainTitle).c_str() ) ); - f.PutLine( ssprintf( "#SUBTITLE:%s;", SmEscape(out.m_sSubTitle).c_str() ) ); - f.PutLine( ssprintf( "#ARTIST:%s;", SmEscape(out.m_sArtist).c_str() ) ); - f.PutLine( ssprintf( "#TITLETRANSLIT:%s;", SmEscape(out.m_sMainTitleTranslit).c_str() ) ); - f.PutLine( ssprintf( "#SUBTITLETRANSLIT:%s;", SmEscape(out.m_sSubTitleTranslit).c_str() ) ); - f.PutLine( ssprintf( "#ARTISTTRANSLIT:%s;", SmEscape(out.m_sArtistTranslit).c_str() ) ); - f.PutLine( ssprintf( "#GENRE:%s;", SmEscape(out.m_sGenre).c_str() ) ); - f.PutLine( ssprintf( "#CREDIT:%s;", SmEscape(out.m_sCredit).c_str() ) ); - f.PutLine( ssprintf( "#BANNER:%s;", SmEscape(out.m_sBannerFile).c_str() ) ); - f.PutLine( ssprintf( "#BACKGROUND:%s;", SmEscape(out.m_sBackgroundFile).c_str() ) ); - f.PutLine( ssprintf( "#LYRICSPATH:%s;", SmEscape(out.m_sLyricsFile).c_str() ) ); - f.PutLine( ssprintf( "#CDTITLE:%s;", SmEscape(out.m_sCDTitleFile).c_str() ) ); - f.PutLine( ssprintf( "#MUSIC:%s;", SmEscape(out.m_sMusicFile).c_str() ) ); - f.PutLine( ssprintf( "#OFFSET:%.6f;", out.m_SongTiming.m_fBeat0OffsetInSeconds ) ); - f.PutLine( ssprintf( "#SAMPLESTART:%.6f;", out.m_fMusicSampleStartSeconds ) ); - f.PutLine( ssprintf( "#SAMPLELENGTH:%.6f;", out.m_fMusicSampleLengthSeconds ) ); - float specBeat = out.GetSpecifiedLastBeat(); - if( specBeat > 0 ) - f.PutLine( ssprintf("#LASTBEATHINT:%.6f;", specBeat) ); - - f.Write( "#SELECTABLE:" ); - switch(out.m_SelectionDisplay) - { - default: - FAIL_M(ssprintf("Invalid selection display: %i", out.m_SelectionDisplay)); - case Song::SHOW_ALWAYS: f.Write( "YES" ); break; - //case Song::SHOW_NONSTOP: f.Write( "NONSTOP" ); break; - case Song::SHOW_NEVER: f.Write( "NO" ); break; - } - f.PutLine( ";" ); - - switch( out.m_DisplayBPMType ) - { - case DISPLAY_BPM_ACTUAL: - // write nothing - break; - case DISPLAY_BPM_SPECIFIED: - if( out.m_fSpecifiedBPMMin == out.m_fSpecifiedBPMMax ) - f.PutLine( ssprintf( "#DISPLAYBPM:%.6f;", out.m_fSpecifiedBPMMin ) ); - else - f.PutLine( ssprintf( "#DISPLAYBPM:%.6f:%.6f;", - out.m_fSpecifiedBPMMin, out.m_fSpecifiedBPMMax ) ); - break; - case DISPLAY_BPM_RANDOM: - f.PutLine( ssprintf( "#DISPLAYBPM:*;" ) ); - break; - } - - - f.Write( "#BPMS:" ); - const vector &bpms = timing.GetTimingSegments(SEGMENT_BPM); - for( unsigned i=0; iGetBeat(), bs->GetBPM() ) ); - if( i != bpms.size()-1 ) - f.Write( "," ); - } - f.PutLine( ";" ); - - const vector &warps = timing.GetTimingSegments(SEGMENT_WARP); - unsigned wSize = warps.size(); - if( wSize > 0 ) - { - for( unsigned i=0; i < wSize; i++ ) - { - const WarpSegment *ws = static_cast(warps[i]); - int iRow = ws->GetRow(); - float fBPS = 60 / out.m_SongTiming.GetBPMAtRow(iRow); - float fSkip = fBPS * ws->GetLength(); - out.m_SongTiming.AddSegment( StopSegment(iRow, -fSkip) ); - } - } - - // TODO: make Delays into Stops that start one row before. - const vector &stops = timing.GetTimingSegments(SEGMENT_STOP); - const vector &delays = timing.GetTimingSegments(SEGMENT_DELAY); - - map allPauses; - for( unsigned i=0; i(fs->GetBeat(), fs->GetPause())); - - // erase stops with negative length - if( fs->GetPause() < 0 ) - timing.AddSegment( StopSegment(fs->GetRow(), 0) ); - } - // Delays can't be negative: thus, no effect. - FOREACH_CONST(TimingSegment *, delays, ss) - { - float fBeat = NoteRowToBeat( (*ss)->GetRow()-1 ); - float fPause = ToDelay(*ss)->GetPause(); - allPauses.insert( pair(fBeat, fPause) ); - } - - f.Write( "#STOPS:" ); - vector stopLines; - FOREACHM(float, float, allPauses, ap) - { - stopLines.push_back(ssprintf("%.6f=%.6f", ap->first, ap->second)); - } - f.PutLine(join(",\n", stopLines)); - - f.PutLine( ";" ); - - FOREACH_BackgroundLayer( b ) - { - if( b==0 ) - f.Write( "#BGCHANGES:" ); - else if( out.GetBackgroundChanges(b).empty() ) - continue; // skip - else - f.Write( ssprintf("#BGCHANGES%d:", b+1) ); - - FOREACH_CONST( BackgroundChange, out.GetBackgroundChanges(b), bgc ) - f.PutLine( (*bgc).ToString() +"," ); - - /* If there's an animation plan at all, add a dummy "-nosongbg-" tag to indicate that - * this file doesn't want a song BG entry added at the end. See SMLoader::TidyUpData. - * This tag will be removed on load. Add it at a very high beat, so it won't cause - * problems if loaded in older versions. */ - if( b==0 && !out.GetBackgroundChanges(b).empty() ) - f.PutLine( "99999=-nosongbg-=1.000=0=0=0 // don't automatically add -songbackground-" ); - f.PutLine( ";" ); - } - - if( out.GetForegroundChanges().size() ) - { - f.Write( "#FGCHANGES:" ); - FOREACH_CONST( BackgroundChange, out.GetForegroundChanges(), bgc ) - { - f.PutLine( (*bgc).ToString() +"," ); - } - f.PutLine( ";" ); - } - - f.Write( "#KEYSOUNDS:" ); - for( unsigned i=0; i &lines ) -{ - for( unsigned i = 0; i < lines.size(); ++i ) - TrimRight( lines[i] ); - - /* Skip leading blanks. */ - unsigned j = 0; - while( j < lines.size() && lines.size() == 0 ) - ++j; - - return join( "\r\n", lines.begin()+j, lines.end() ); -} - -/** - * @brief Retrieve the notes from the #NOTES tag. - * @param song the Song in question. - * @param in the Steps in question. - * @return the #NOTES tag. */ -static RString GetSMNotesTag( const Song &song, const Steps &in ) -{ - vector lines; - - lines.push_back( "" ); - // Escape to prevent some clown from making a comment of "\r\n;" - lines.push_back( ssprintf("//---------------%s - %s----------------", - GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName, SmEscape(in.GetDescription()).c_str()) ); - lines.push_back( song.m_vsKeysoundFile.empty() ? "#NOTES:" : "#NOTES2:" ); - lines.push_back( ssprintf( " %s:", GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName ) ); - RString desc = (USE_CREDIT ? in.GetCredit() : in.GetChartName()); - lines.push_back( ssprintf( " %s:", SmEscape(desc).c_str() ) ); - lines.push_back( ssprintf( " %s:", DifficultyToString(in.GetDifficulty()).c_str() ) ); - lines.push_back( ssprintf( " %d:", in.GetMeter() ) ); - - vector asRadarValues; - // OpenITG simfiles use 11 radar categories. - int categories = 11; - FOREACH_PlayerNumber( pn ) - { - const RadarValues &rv = in.GetRadarValues( pn ); - // Can't use the foreach anymore due to flexible radar lines. - for( RadarCategory rc = (RadarCategory)0; rc < categories; - enum_add( rc, 1 ) ) - { - asRadarValues.push_back( ssprintf("%.6f", rv[rc]) ); - } - } - lines.push_back( ssprintf( " %s:", join(",",asRadarValues).c_str() ) ); - - RString sNoteData; - in.GetSMNoteData( sNoteData ); - - split( sNoteData, "\n", lines, true ); - lines.push_back( ";" ); - - return JoinLineList( lines ); -} - -bool NotesWriterSM::Write( RString sPath, Song &out, const vector& vpStepsToSave ) -{ - int flags = RageFile::WRITE; - - flags |= RageFile::SLOW_FLUSH; - - RageFile f; - if( !f.Open( sPath, flags ) ) - { - LOG->UserLog( "Song file", sPath, "couldn't be opened for writing: %s", f.GetError().c_str() ); - return false; - } - - WriteGlobalTags( f, out ); - - FOREACH_CONST( Steps*, vpStepsToSave, s ) - { - const Steps* pSteps = *s; - RString sTag = GetSMNotesTag( out, *pSteps ); - f.PutLine( sTag ); - } - if( f.Flush() == -1 ) - return false; - - return true; -} - -void NotesWriterSM::GetEditFileContents( const Song *pSong, const Steps *pSteps, RString &sOut ) -{ - sOut = ""; - RString sDir = pSong->GetSongDir(); - - // "Songs/foo/bar"; strip off "Songs/". - vector asParts; - split( sDir, "/", asParts ); - if( asParts.size() ) - sDir = join( "/", asParts.begin()+1, asParts.end() ); - sOut += ssprintf( "#SONG:%s;\r\n", sDir.c_str() ); - sOut += GetSMNotesTag( *pSong, *pSteps ); -} - -RString NotesWriterSM::GetEditFileName( const Song *pSong, const Steps *pSteps ) -{ - /* Try to make a unique name. This isn't guaranteed. Edit descriptions are - * case-sensitive, filenames on disk are usually not, and we decimate certain - * characters for FAT filesystems. */ - RString sFile = pSong->GetTranslitFullTitle() + " - " + pSteps->GetDescription(); - - // HACK: - if( pSteps->m_StepsType == StepsType_dance_double ) - sFile += " (doubles)"; - - sFile += ".edit"; - - MakeValidFilename( sFile ); - return sFile; -} - -static LocalizedString DESTINATION_ALREADY_EXISTS ("NotesWriterSM", "Error renaming file. Destination file '%s' already exists."); -static LocalizedString ERROR_WRITING_FILE ("NotesWriterSM", "Error writing file '%s'."); -bool NotesWriterSM::WriteEditFileToMachine( const Song *pSong, Steps *pSteps, RString &sErrorOut ) -{ - RString sDir = PROFILEMAN->GetProfileDir( ProfileSlot_Machine ) + EDIT_STEPS_SUBDIR; - - RString sPath = sDir + GetEditFileName(pSong,pSteps); - - // Check to make sure that we're not clobering an existing file before opening. - bool bFileNameChanging = - pSteps->GetSavedToDisk() && - pSteps->GetFilename() != sPath; - if( bFileNameChanging && DoesFileExist(sPath) ) - { - sErrorOut = ssprintf( DESTINATION_ALREADY_EXISTS.GetValue(), sPath.c_str() ); - return false; - } - - RageFile f; - if( !f.Open(sPath, RageFile::WRITE | RageFile::SLOW_FLUSH) ) - { - sErrorOut = ssprintf( ERROR_WRITING_FILE.GetValue(), sPath.c_str() ); - return false; - } - - RString sTag; - GetEditFileContents( pSong, pSteps, sTag ); - if( f.PutLine(sTag) == -1 || f.Flush() == -1 ) - { - sErrorOut = ssprintf( ERROR_WRITING_FILE.GetValue(), sPath.c_str() ); - return false; - } - - /* If the file name of the edit has changed since the last save, then delete the old - * file after saving the new one. If we delete it first, then we'll lose data on error. */ - - if( bFileNameChanging ) - FILEMAN->Remove( pSteps->GetFilename() ); - pSteps->SetFilename( sPath ); - - return true; -} - -/* - * (c) 2001-2004 Chris Danford, Glenn Maynard - * 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 +#include +#include "NotesWriterSM.h" +#include "BackgroundUtil.h" +#include "Foreach.h" +#include "GameManager.h" +#include "LocalizedString.h" +#include "NoteTypes.h" +#include "Profile.h" +#include "ProfileManager.h" +#include "RageFile.h" +#include "RageFileManager.h" +#include "RageLog.h" +#include "RageUtil.h" +#include "Song.h" +#include "Steps.h" +#include "ThemeMetric.h" + +ThemeMetric USE_CREDIT ( "NotesWriterSM", "DescriptionUsesCreditField" ); + +/** + * @brief Write out the common tags for .SM files. + * @param f the file in question. + * @param out the Song in question. */ +static void WriteGlobalTags( RageFile &f, Song &out ) +{ + TimingData &timing = out.m_SongTiming; + f.PutLine( ssprintf( "#TITLE:%s;", SmEscape(out.m_sMainTitle).c_str() ) ); + f.PutLine( ssprintf( "#SUBTITLE:%s;", SmEscape(out.m_sSubTitle).c_str() ) ); + f.PutLine( ssprintf( "#ARTIST:%s;", SmEscape(out.m_sArtist).c_str() ) ); + f.PutLine( ssprintf( "#TITLETRANSLIT:%s;", SmEscape(out.m_sMainTitleTranslit).c_str() ) ); + f.PutLine( ssprintf( "#SUBTITLETRANSLIT:%s;", SmEscape(out.m_sSubTitleTranslit).c_str() ) ); + f.PutLine( ssprintf( "#ARTISTTRANSLIT:%s;", SmEscape(out.m_sArtistTranslit).c_str() ) ); + f.PutLine( ssprintf( "#GENRE:%s;", SmEscape(out.m_sGenre).c_str() ) ); + f.PutLine( ssprintf( "#CREDIT:%s;", SmEscape(out.m_sCredit).c_str() ) ); + f.PutLine( ssprintf( "#BANNER:%s;", SmEscape(out.m_sBannerFile).c_str() ) ); + f.PutLine( ssprintf( "#BACKGROUND:%s;", SmEscape(out.m_sBackgroundFile).c_str() ) ); + f.PutLine( ssprintf( "#LYRICSPATH:%s;", SmEscape(out.m_sLyricsFile).c_str() ) ); + f.PutLine( ssprintf( "#CDTITLE:%s;", SmEscape(out.m_sCDTitleFile).c_str() ) ); + f.PutLine( ssprintf( "#MUSIC:%s;", SmEscape(out.m_sMusicFile).c_str() ) ); + f.PutLine( ssprintf( "#OFFSET:%.6f;", out.m_SongTiming.m_fBeat0OffsetInSeconds ) ); + f.PutLine( ssprintf( "#SAMPLESTART:%.6f;", out.m_fMusicSampleStartSeconds ) ); + f.PutLine( ssprintf( "#SAMPLELENGTH:%.6f;", out.m_fMusicSampleLengthSeconds ) ); + float specBeat = out.GetSpecifiedLastBeat(); + if( specBeat > 0 ) + f.PutLine( ssprintf("#LASTBEATHINT:%.6f;", specBeat) ); + + f.Write( "#SELECTABLE:" ); + switch(out.m_SelectionDisplay) + { + default: + FAIL_M(ssprintf("Invalid selection display: %i", out.m_SelectionDisplay)); + case Song::SHOW_ALWAYS: f.Write( "YES" ); break; + //case Song::SHOW_NONSTOP: f.Write( "NONSTOP" ); break; + case Song::SHOW_NEVER: f.Write( "NO" ); break; + } + f.PutLine( ";" ); + + switch( out.m_DisplayBPMType ) + { + case DISPLAY_BPM_ACTUAL: + // write nothing + break; + case DISPLAY_BPM_SPECIFIED: + if( out.m_fSpecifiedBPMMin == out.m_fSpecifiedBPMMax ) + f.PutLine( ssprintf( "#DISPLAYBPM:%.6f;", out.m_fSpecifiedBPMMin ) ); + else + f.PutLine( ssprintf( "#DISPLAYBPM:%.6f:%.6f;", + out.m_fSpecifiedBPMMin, out.m_fSpecifiedBPMMax ) ); + break; + case DISPLAY_BPM_RANDOM: + f.PutLine( ssprintf( "#DISPLAYBPM:*;" ) ); + break; + } + + + f.Write( "#BPMS:" ); + const vector &bpms = timing.GetTimingSegments(SEGMENT_BPM); + for( unsigned i=0; iGetBeat(), bs->GetBPM() ) ); + if( i != bpms.size()-1 ) + f.Write( "," ); + } + f.PutLine( ";" ); + + const vector &warps = timing.GetTimingSegments(SEGMENT_WARP); + unsigned wSize = warps.size(); + if( wSize > 0 ) + { + for( unsigned i=0; i < wSize; i++ ) + { + const WarpSegment *ws = static_cast(warps[i]); + int iRow = ws->GetRow(); + float fBPS = 60 / out.m_SongTiming.GetBPMAtRow(iRow); + float fSkip = fBPS * ws->GetLength(); + out.m_SongTiming.AddSegment( StopSegment(iRow, -fSkip) ); + } + } + + // TODO: make Delays into Stops that start one row before. + const vector &stops = timing.GetTimingSegments(SEGMENT_STOP); + const vector &delays = timing.GetTimingSegments(SEGMENT_DELAY); + + map allPauses; + for( unsigned i=0; i(fs->GetBeat(), fs->GetPause())); + + // erase stops with negative length + if( fs->GetPause() < 0 ) + timing.AddSegment( StopSegment(fs->GetRow(), 0) ); + } + // Delays can't be negative: thus, no effect. + FOREACH_CONST(TimingSegment *, delays, ss) + { + float fBeat = NoteRowToBeat( (*ss)->GetRow()-1 ); + float fPause = ToDelay(*ss)->GetPause(); + allPauses.insert( pair(fBeat, fPause) ); + } + + f.Write( "#STOPS:" ); + vector stopLines; + for (std::pair ap : allPauses) + { + stopLines.push_back(ssprintf("%.6f=%.6f", ap.first, ap.second)); + } + f.PutLine(join(",\n", stopLines)); + + f.PutLine( ";" ); + + FOREACH_BackgroundLayer( b ) + { + if( b==0 ) + f.Write( "#BGCHANGES:" ); + else if( out.GetBackgroundChanges(b).empty() ) + continue; // skip + else + f.Write( ssprintf("#BGCHANGES%d:", b+1) ); + + FOREACH_CONST( BackgroundChange, out.GetBackgroundChanges(b), bgc ) + f.PutLine( (*bgc).ToString() +"," ); + + /* If there's an animation plan at all, add a dummy "-nosongbg-" tag to indicate that + * this file doesn't want a song BG entry added at the end. See SMLoader::TidyUpData. + * This tag will be removed on load. Add it at a very high beat, so it won't cause + * problems if loaded in older versions. */ + if( b==0 && !out.GetBackgroundChanges(b).empty() ) + f.PutLine( "99999=-nosongbg-=1.000=0=0=0 // don't automatically add -songbackground-" ); + f.PutLine( ";" ); + } + + if( out.GetForegroundChanges().size() ) + { + f.Write( "#FGCHANGES:" ); + FOREACH_CONST( BackgroundChange, out.GetForegroundChanges(), bgc ) + { + f.PutLine( (*bgc).ToString() +"," ); + } + f.PutLine( ";" ); + } + + f.Write( "#KEYSOUNDS:" ); + for( unsigned i=0; i &lines ) +{ + for( unsigned i = 0; i < lines.size(); ++i ) + TrimRight( lines[i] ); + + /* Skip leading blanks. */ + unsigned j = 0; + while( j < lines.size() && lines.size() == 0 ) + ++j; + + return join( "\r\n", lines.begin()+j, lines.end() ); +} + +/** + * @brief Retrieve the notes from the #NOTES tag. + * @param song the Song in question. + * @param in the Steps in question. + * @return the #NOTES tag. */ +static RString GetSMNotesTag( const Song &song, const Steps &in ) +{ + vector lines; + + lines.push_back( "" ); + // Escape to prevent some clown from making a comment of "\r\n;" + lines.push_back( ssprintf("//---------------%s - %s----------------", + GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName, SmEscape(in.GetDescription()).c_str()) ); + lines.push_back( song.m_vsKeysoundFile.empty() ? "#NOTES:" : "#NOTES2:" ); + lines.push_back( ssprintf( " %s:", GAMEMAN->GetStepsTypeInfo(in.m_StepsType).szName ) ); + RString desc = (USE_CREDIT ? in.GetCredit() : in.GetChartName()); + lines.push_back( ssprintf( " %s:", SmEscape(desc).c_str() ) ); + lines.push_back( ssprintf( " %s:", DifficultyToString(in.GetDifficulty()).c_str() ) ); + lines.push_back( ssprintf( " %d:", in.GetMeter() ) ); + + vector asRadarValues; + // OpenITG simfiles use 11 radar categories. + int categories = 11; + FOREACH_PlayerNumber( pn ) + { + const RadarValues &rv = in.GetRadarValues( pn ); + // Can't use the foreach anymore due to flexible radar lines. + for( RadarCategory rc = (RadarCategory)0; rc < categories; + enum_add( rc, 1 ) ) + { + asRadarValues.push_back( ssprintf("%.6f", rv[rc]) ); + } + } + lines.push_back( ssprintf( " %s:", join(",",asRadarValues).c_str() ) ); + + RString sNoteData; + in.GetSMNoteData( sNoteData ); + + split( sNoteData, "\n", lines, true ); + lines.push_back( ";" ); + + return JoinLineList( lines ); +} + +bool NotesWriterSM::Write( RString sPath, Song &out, const vector& vpStepsToSave ) +{ + int flags = RageFile::WRITE; + + flags |= RageFile::SLOW_FLUSH; + + RageFile f; + if( !f.Open( sPath, flags ) ) + { + LOG->UserLog( "Song file", sPath, "couldn't be opened for writing: %s", f.GetError().c_str() ); + return false; + } + + WriteGlobalTags( f, out ); + + FOREACH_CONST( Steps*, vpStepsToSave, s ) + { + const Steps* pSteps = *s; + RString sTag = GetSMNotesTag( out, *pSteps ); + f.PutLine( sTag ); + } + if( f.Flush() == -1 ) + return false; + + return true; +} + +void NotesWriterSM::GetEditFileContents( const Song *pSong, const Steps *pSteps, RString &sOut ) +{ + sOut = ""; + RString sDir = pSong->GetSongDir(); + + // "Songs/foo/bar"; strip off "Songs/". + vector asParts; + split( sDir, "/", asParts ); + if( asParts.size() ) + sDir = join( "/", asParts.begin()+1, asParts.end() ); + sOut += ssprintf( "#SONG:%s;\r\n", sDir.c_str() ); + sOut += GetSMNotesTag( *pSong, *pSteps ); +} + +RString NotesWriterSM::GetEditFileName( const Song *pSong, const Steps *pSteps ) +{ + /* Try to make a unique name. This isn't guaranteed. Edit descriptions are + * case-sensitive, filenames on disk are usually not, and we decimate certain + * characters for FAT filesystems. */ + RString sFile = pSong->GetTranslitFullTitle() + " - " + pSteps->GetDescription(); + + // HACK: + if( pSteps->m_StepsType == StepsType_dance_double ) + sFile += " (doubles)"; + + sFile += ".edit"; + + MakeValidFilename( sFile ); + return sFile; +} + +static LocalizedString DESTINATION_ALREADY_EXISTS ("NotesWriterSM", "Error renaming file. Destination file '%s' already exists."); +static LocalizedString ERROR_WRITING_FILE ("NotesWriterSM", "Error writing file '%s'."); +bool NotesWriterSM::WriteEditFileToMachine( const Song *pSong, Steps *pSteps, RString &sErrorOut ) +{ + RString sDir = PROFILEMAN->GetProfileDir( ProfileSlot_Machine ) + EDIT_STEPS_SUBDIR; + + RString sPath = sDir + GetEditFileName(pSong,pSteps); + + // Check to make sure that we're not clobering an existing file before opening. + bool bFileNameChanging = + pSteps->GetSavedToDisk() && + pSteps->GetFilename() != sPath; + if( bFileNameChanging && DoesFileExist(sPath) ) + { + sErrorOut = ssprintf( DESTINATION_ALREADY_EXISTS.GetValue(), sPath.c_str() ); + return false; + } + + RageFile f; + if( !f.Open(sPath, RageFile::WRITE | RageFile::SLOW_FLUSH) ) + { + sErrorOut = ssprintf( ERROR_WRITING_FILE.GetValue(), sPath.c_str() ); + return false; + } + + RString sTag; + GetEditFileContents( pSong, pSteps, sTag ); + if( f.PutLine(sTag) == -1 || f.Flush() == -1 ) + { + sErrorOut = ssprintf( ERROR_WRITING_FILE.GetValue(), sPath.c_str() ); + return false; + } + + /* If the file name of the edit has changed since the last save, then delete the old + * file after saving the new one. If we delete it first, then we'll lose data on error. */ + + if( bFileNameChanging ) + FILEMAN->Remove( pSteps->GetFilename() ); + pSteps->SetFilename( sPath ); + + return true; +} + +/* + * (c) 2001-2004 Chris Danford, Glenn Maynard + * 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. + */ From 7549f0dc45d6b9bdd2c996057b7cfe07b18cbcf1 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sun, 28 Apr 2013 00:00:54 -0400 Subject: [PATCH 046/281] Another loop. --- src/BannerCache.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BannerCache.cpp b/src/BannerCache.cpp index e18ddc94bf..92956a3796 100644 --- a/src/BannerCache.cpp +++ b/src/BannerCache.cpp @@ -161,8 +161,8 @@ void BannerCache::OutputStats() const void BannerCache::UnloadAllBanners() { - FOREACHM( RString, RageSurface *, g_BannerPathToImage, it ) - delete it->second; + for (std::pair it : g_BannerPathToImage) + delete it.second; g_BannerPathToImage.clear(); } From b296bcbd4af1ea1a287003a26e4c3ad1ba056c20 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sun, 28 Apr 2013 11:28:31 -0400 Subject: [PATCH 047/281] Enforce c++11 compiling. --- Xcode/stepmania_xcode4.3.xcodeproj/project.pbxproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Xcode/stepmania_xcode4.3.xcodeproj/project.pbxproj b/Xcode/stepmania_xcode4.3.xcodeproj/project.pbxproj index cfa3e28389..f45734200c 100644 --- a/Xcode/stepmania_xcode4.3.xcodeproj/project.pbxproj +++ b/Xcode/stepmania_xcode4.3.xcodeproj/project.pbxproj @@ -8352,6 +8352,7 @@ "-fconstant-cfstrings", "-fno-exceptions", "-falign-loops=16", + "-std=c++11", ); OTHER_LDFLAGS = "-lbz2"; PRODUCT_NAME = "StepMania (Debug)"; @@ -8869,6 +8870,7 @@ "-fconstant-cfstrings", "-fno-exceptions", "-falign-loops=16", + "-std=c++11", ); OTHER_LDFLAGS = "-lbz2"; PRODUCT_NAME = "StepMania (Performance-Debug)"; @@ -9116,6 +9118,7 @@ "-fconstant-cfstrings", "-fno-exceptions", "-falign-loops=16", + "-std=c++11", ); OTHER_LDFLAGS = "-lbz2"; PRODUCT_NAME = StepMania; From 8bc0b9385c419aa5661df7292c0290c95a34a07c Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sun, 28 Apr 2013 12:01:41 -0400 Subject: [PATCH 048/281] No longer auto casts to float. Do it manually. --- src/arch/LowLevelWindow/LowLevelWindow_MacOSX.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arch/LowLevelWindow/LowLevelWindow_MacOSX.mm b/src/arch/LowLevelWindow/LowLevelWindow_MacOSX.mm index f733deedaa..71a0d8cf4f 100644 --- a/src/arch/LowLevelWindow/LowLevelWindow_MacOSX.mm +++ b/src/arch/LowLevelWindow/LowLevelWindow_MacOSX.mm @@ -109,7 +109,7 @@ public: - (void) setParams:(NSValue *)params { const VideoModeParams &p = *(const VideoModeParams *)[params pointerValue]; - NSRect contentRect = { { 0, 0 }, { p.width, p.height } }; + NSRect contentRect = { { 0, 0 }, { static_cast(p.width), static_cast(p.height) } }; [m_Window setContentSize:contentRect.size]; [m_Window setTitle:[NSString stringWithUTF8String:p.sWindowTitle.c_str()]]; From 7a553e0aba5d195bd4018232fedd276884411c62 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sun, 28 Apr 2013 12:10:48 -0400 Subject: [PATCH 049/281] Use portable mode with libtomcrypt for now. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Activating C++11 turned on strict ansi mode it seems: it causes some issues with the non portable stuff. …is there perhaps a different library to use that is more updated with the times? --- Xcode/stepmania_xcode4.3.xcodeproj/project.pbxproj | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Xcode/stepmania_xcode4.3.xcodeproj/project.pbxproj b/Xcode/stepmania_xcode4.3.xcodeproj/project.pbxproj index f45734200c..6bc821b4a3 100644 --- a/Xcode/stepmania_xcode4.3.xcodeproj/project.pbxproj +++ b/Xcode/stepmania_xcode4.3.xcodeproj/project.pbxproj @@ -8324,6 +8324,7 @@ GCC_PREPROCESSOR_DEFINITIONS = ( _XOPEN_SOURCE, "DEBUG=1", + LTC_NO_ASM, ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = ( @@ -8640,6 +8641,7 @@ GCC_DYNAMIC_NO_PIC = YES; GCC_ENABLE_CPP_RTTI = NO; GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = LTC_NO_ASM; GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = NO; GCC_WARN_PROTOTYPE_CONVERSION = NO; INSTALL_PATH = /usr/local/lib; @@ -8659,8 +8661,8 @@ GCC_ENABLE_CPP_RTTI = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( - LTC_NO_ROLC, "DEBUG=1", + LTC_NO_ASM, ); GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = NO; GCC_WARN_PROTOTYPE_CONVERSION = NO; @@ -8842,6 +8844,7 @@ GCC_PREPROCESSOR_DEFINITIONS = ( _XOPEN_SOURCE, "DEBUG=1", + LTC_NO_ASM, ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = ( @@ -9047,6 +9050,7 @@ GCC_DYNAMIC_NO_PIC = YES; GCC_ENABLE_CPP_RTTI = NO; GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = LTC_NO_ASM; GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = NO; GCC_WARN_PROTOTYPE_CONVERSION = NO; INSTALL_PATH = /usr/local/lib; @@ -9090,7 +9094,10 @@ CONFIGURATION_BUILD_DIR = "$(SYMROOT)"; FULL_NAME = "$(PRODUCT_NAME).$(WRAPPER_EXTENSION)"; GCC_PREFIX_HEADER = "$(SRCROOT)/../src/archutils/Darwin/StepMania.pch"; - GCC_PREPROCESSOR_DEFINITIONS = _XOPEN_SOURCE; + GCC_PREPROCESSOR_DEFINITIONS = ( + _XOPEN_SOURCE, + LTC_NO_ASM, + ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = ( "$(SRCROOT)/../src", From 8502c59ee21f9e32472d516baf4b63849d25bb00 Mon Sep 17 00:00:00 2001 From: Jason Felds Date: Sun, 28 Apr 2013 16:24:32 -0400 Subject: [PATCH 050/281] Deal with the nested map loops. ...is there a better way to do this? --- src/GameState.cpp | 5208 ++++++++++++++++++++++----------------------- 1 file changed, 2604 insertions(+), 2604 deletions(-) diff --git a/src/GameState.cpp b/src/GameState.cpp index 02ba9cdb91..d1ac03ea86 100644 --- a/src/GameState.cpp +++ b/src/GameState.cpp @@ -1,2604 +1,2604 @@ -#include "global.h" -#include "GameState.h" -#include "Actor.h" -#include "AdjustSync.h" -#include "AnnouncerManager.h" -#include "Character.h" -#include "CharacterManager.h" -#include "CommonMetrics.h" -#include "Course.h" -#include "CryptManager.h" -#include "Foreach.h" -#include "Game.h" -#include "GameCommand.h" -#include "GameConstantsAndTypes.h" -#include "GameManager.h" -#include "GamePreferences.h" -#include "HighScore.h" -#include "LightsManager.h" -#include "LuaReference.h" -#include "MessageManager.h" -#include "MemoryCardManager.h" -#include "NoteSkinManager.h" -#include "PlayerState.h" -#include "PrefsManager.h" -#include "Profile.h" -#include "ProfileManager.h" -#include "RageFile.h" -#include "RageLog.h" -#include "RageUtil.h" -#include "Song.h" -#include "SongManager.h" -#include "SongUtil.h" -#include "StatsManager.h" -#include "Steps.h" -#include "Style.h" -#include "ThemeManager.h" -#include "UnlockManager.h" -#include "ScreenManager.h" -#include "Screen.h" - -#include -#include - -GameState* GAMESTATE = NULL; // global and accessable from anywhere in our program - -#define NAME_BLACKLIST_FILE "/Data/NamesBlacklist.txt" - -class GameStateMessageHandler: public MessageSubscriber -{ - void HandleMessage( const Message &msg ) - { - if( msg.GetName() == "RefreshCreditText" ) - { - RString sJoined; - FOREACH_HumanPlayer( pn ) - { - if( sJoined != "" ) - sJoined += ", "; - sJoined += ssprintf( "P%i", pn+1 ); - } - - if( sJoined == "" ) - sJoined = "none"; - - LOG->MapLog( "JOINED", "Players joined: %s", sJoined.c_str() ); - } - } -}; - -struct GameStateImpl -{ - GameStateMessageHandler m_Subscriber; - GameStateImpl() - { - m_Subscriber.SubscribeToMessage( "RefreshCreditText" ); - } -}; -static GameStateImpl *g_pImpl = NULL; - -ThemeMetric ALLOW_LATE_JOIN("GameState","AllowLateJoin"); -ThemeMetric USE_NAME_BLACKLIST("GameState","UseNameBlacklist"); - -ThemeMetric DEFAULT_SORT ("GameState","DefaultSort"); -SortOrder GetDefaultSort() -{ - return StringToSortOrder( DEFAULT_SORT ); -} -ThemeMetric DEFAULT_SONG ("GameState","DefaultSong"); -Song* GameState::GetDefaultSong() const -{ - SongID sid; - sid.FromString( DEFAULT_SONG ); - return sid.ToSong(); -} - -static const ThemeMetric EDIT_ALLOWED_FOR_EXTRA ("GameState","EditAllowedForExtra"); -static const ThemeMetric MIN_DIFFICULTY_FOR_EXTRA ("GameState","MinDifficultyForExtra"); -static const ThemeMetric GRADE_TIER_FOR_EXTRA_1 ("GameState","GradeTierForExtra1"); -static const ThemeMetric ALLOW_EXTRA_2 ("GameState","AllowExtra2"); -static const ThemeMetric GRADE_TIER_FOR_EXTRA_2 ("GameState","GradeTierForExtra2"); - -static ThemeMetric ARE_STAGE_PLAYER_MODS_FORCED ("GameState","AreStagePlayerModsForced"); -static ThemeMetric ARE_STAGE_SONG_MODS_FORCED ("GameState","AreStageSongModsForced"); - -static Preference g_Premium( "Premium", Premium_DoubleFor1Credit ); -Preference GameState::m_bAutoJoin( "AutoJoin", false ); - -GameState::GameState() : - processedTiming( NULL ), - m_pCurGame( Message_CurrentGameChanged ), - m_pCurStyle( Message_CurrentStyleChanged ), - m_PlayMode( Message_PlayModeChanged ), - m_sPreferredSongGroup( Message_PreferredSongGroupChanged ), - m_sPreferredCourseGroup( Message_PreferredCourseGroupChanged ), - m_PreferredStepsType( Message_PreferredStepsTypeChanged ), - m_PreferredDifficulty( Message_PreferredDifficultyP1Changed ), - m_PreferredCourseDifficulty( Message_PreferredCourseDifficultyP1Changed ), - m_SortOrder( Message_SortOrderChanged ), - m_pCurSong( Message_CurrentSongChanged ), - m_pCurSteps( Message_CurrentStepsP1Changed ), - m_pCurCourse( Message_CurrentCourseChanged ), - m_pCurTrail( Message_CurrentTrailP1Changed ), - m_bGameplayLeadIn( Message_GameplayLeadInChanged ), - m_bDidModeChangeNoteSkin( false ), - m_bIsUsingStepTiming( true ), - m_bInStepEditor( false ), - m_stEdit( Message_EditStepsTypeChanged ), - m_cdEdit( Message_EditCourseDifficultyChanged ), - m_pEditSourceSteps( Message_EditSourceStepsChanged ), - m_stEditSource( Message_EditSourceStepsTypeChanged ), - m_iEditCourseEntryIndex( Message_EditCourseEntryIndexChanged ), - m_sEditLocalProfileID( Message_EditLocalProfileIDChanged ) -{ - g_pImpl = new GameStateImpl; - - SetCurrentStyle( NULL ); - - m_pCurGame.Set( NULL ); - m_timeGameStarted.SetZero(); - m_bDemonstrationOrJukebox = false; - - m_iNumTimesThroughAttract = -1; // initial screen will bump this up to 0 - m_iStageSeed = m_iGameSeed = 0; - - m_PlayMode.Set( PlayMode_Invalid ); // used by IsPlayerEnabled before the first screen - FOREACH_PlayerNumber( p ) - m_bSideIsJoined[p] = false; // used by GetNumSidesJoined before the first screen - - FOREACH_PlayerNumber( p ) - { - m_pPlayerState[p] = new PlayerState; - m_pPlayerState[p]->m_PlayerNumber = p; - } - FOREACH_MultiPlayer( p ) - { - m_pMultiPlayerState[p] = new PlayerState; - m_pMultiPlayerState[p]->m_PlayerNumber = PLAYER_1; - m_pMultiPlayerState[p]->m_mp = p; - } - - m_Environment = new LuaTable; - - m_bDopefish = false; - - sExpandedSectionName = ""; - - // Don't reset yet; let the first screen do it, so we can use PREFSMAN and THEME. - //Reset(); - - // Register with Lua. - { - Lua *L = LUA->Get(); - lua_pushstring( L, "GAMESTATE" ); - this->PushSelf( L ); - lua_settable( L, LUA_GLOBALSINDEX ); - LUA->Release( L ); - } -} - -GameState::~GameState() -{ - // Unregister with Lua. - LUA->UnsetGlobal( "GAMESTATE" ); - - FOREACH_PlayerNumber( p ) - SAFE_DELETE( m_pPlayerState[p] ); - FOREACH_MultiPlayer( p ) - SAFE_DELETE( m_pMultiPlayerState[p] ); - - SAFE_DELETE( m_Environment ); - SAFE_DELETE( g_pImpl ); - SAFE_DELETE( processedTiming ); -} - -PlayerNumber GameState::GetMasterPlayerNumber() const -{ - return this->masterPlayerNumber; -} - -void GameState::SetMasterPlayerNumber(const PlayerNumber p) -{ - this->masterPlayerNumber = p; -} - -TimingData * GameState::GetProcessedTimingData() const -{ - return this->processedTiming; -} - -void GameState::SetProcessedTimingData(TimingData * t) -{ - this->processedTiming = t; -} - -void GameState::ApplyGameCommand( const RString &sCommand, PlayerNumber pn ) -{ - GameCommand m; - m.Load( 0, ParseCommands(sCommand) ); - - RString sWhy; - if( !m.IsPlayable(&sWhy) ) - RageException::Throw( "Can't apply mode \"%s\": %s", sCommand.c_str(), sWhy.c_str() ); - - if( pn == PLAYER_INVALID ) - m.ApplyToAllPlayers(); - else - m.Apply( pn ); -} - -void GameState::ApplyCmdline() -{ - // We need to join players before we can set the style. - RString sPlayer; - for( int i = 0; GetCommandlineArgument( "player", &sPlayer, i ); ++i ) - { - int pn = StringToInt( sPlayer )-1; - if( !IsAnInt( sPlayer ) || pn < 0 || pn >= NUM_PLAYERS ) - RageException::Throw( "Invalid argument \"--player=%s\".", sPlayer.c_str() ); - - JoinPlayer( (PlayerNumber) pn ); - } - - RString sMode; - for( int i = 0; GetCommandlineArgument( "mode", &sMode, i ); ++i ) - { - ApplyGameCommand( sMode ); - } -} - -void GameState::ResetPlayer( PlayerNumber pn ) -{ - m_PreferredStepsType.Set( StepsType_Invalid ); - m_PreferredDifficulty[pn].Set( Difficulty_Invalid ); - m_PreferredCourseDifficulty[pn].Set( Difficulty_Medium ); - m_iPlayerStageTokens[pn] = 0; - m_iAwardedExtraStages[pn] = 0; - m_pCurSteps[pn].Set( NULL ); - m_pCurTrail[pn].Set( NULL ); - m_pPlayerState[pn]->Reset(); - PROFILEMAN->UnloadProfile( pn ); - ResetPlayerOptions(pn); -} - -void GameState::ResetPlayerOptions( PlayerNumber pn ) -{ - PlayerOptions po; - GetDefaultPlayerOptions( po ); - m_pPlayerState[pn]->m_PlayerOptions.Assign( ModsLevel_Preferred, po ); -} - -void GameState::Reset() -{ - this->SetMasterPlayerNumber(PLAYER_INVALID); // must initialize for UnjoinPlayer - - FOREACH_PlayerNumber( pn ) - UnjoinPlayer( pn ); - - ASSERT( THEME != NULL ); - - m_timeGameStarted.SetZero(); - SetCurrentStyle( NULL ); - FOREACH_MultiPlayer( p ) - m_MultiPlayerStatus[p] = MultiPlayerStatus_NotJoined; - FOREACH_PlayerNumber( pn ) - MEMCARDMAN->UnlockCard( pn ); - m_bMultiplayer = false; - m_iNumMultiplayerNoteFields = 1; - *m_Environment = LuaTable(); - m_sPreferredSongGroup.Set( GROUP_ALL ); - m_sPreferredCourseGroup.Set( GROUP_ALL ); - m_bChangedFailTypeOnScreenSongOptions = false; - m_SortOrder.Set( SortOrder_Invalid ); - m_PreferredSortOrder = GetDefaultSort(); - m_PlayMode.Set( PlayMode_Invalid ); - m_EditMode = EditMode_Invalid; - m_bDemonstrationOrJukebox = false; - m_bJukeboxUsesModifiers = false; - m_iCurrentStageIndex = 0; - - m_bGameplayLeadIn.Set( false ); - m_iNumStagesOfThisSong = 0; - m_bLoadingNextSong = false; - - NOTESKIN->RefreshNoteSkinData( m_pCurGame ); - - m_iGameSeed = rand(); - m_iStageSeed = rand(); - - m_pCurSong.Set( GetDefaultSong() ); - m_pPreferredSong = NULL; - m_pCurCourse.Set( NULL ); - m_pPreferredCourse = NULL; - - FOREACH_MultiPlayer( p ) - m_pMultiPlayerState[p]->Reset(); - - m_SongOptions.Init(); - - ResetMusicStatistics(); - ResetStageStatistics(); - AdjustSync::ResetOriginalSyncData(); - - SONGMAN->UpdatePopular(); - SONGMAN->UpdateShuffled(); - - /* We may have cached trails from before everything was loaded (eg. from - * before SongManager::UpdatePopular could be called). Erase the cache. */ - SONGMAN->RegenerateNonFixedCourses(); - - STATSMAN->Reset(); - - FOREACH_PlayerNumber(p) - { - if( PREFSMAN->m_ShowDancingCharacters == SDC_Random ) - m_pCurCharacters[p] = CHARMAN->GetRandomCharacter(); - else - m_pCurCharacters[p] = CHARMAN->GetDefaultCharacter(); - ASSERT( m_pCurCharacters[p] != NULL ); - } - - m_bTemporaryEventMode = false; - - LIGHTSMAN->SetLightsMode( LIGHTSMODE_ATTRACT ); - - m_stEdit.Set( StepsType_Invalid ); - m_pEditSourceSteps.Set( NULL ); - m_stEditSource.Set( StepsType_Invalid ); - m_iEditCourseEntryIndex.Set( -1 ); - m_sEditLocalProfileID.Set( "" ); - - m_bBackedOutOfFinalStage = false; - m_bEarnedExtraStage = false; - sExpandedSectionName = ""; - - ApplyCmdline(); -} - -void GameState::JoinPlayer( PlayerNumber pn ) -{ - m_iPlayerStageTokens[pn] = PREFSMAN->m_iSongsPerPlay; - - m_bSideIsJoined[pn] = true; - - if( this->GetMasterPlayerNumber() == PLAYER_INVALID ) - this->SetMasterPlayerNumber(pn); - - // if first player to join, set start time - if( GetNumSidesJoined() == 1 ) - BeginGame(); - - // Count each player join as a play. - { - Profile* pMachineProfile = PROFILEMAN->GetMachineProfile(); - pMachineProfile->m_iTotalSessions++; - } - - // Set the current style to something appropriate for the new number of joined players. - if( ALLOW_LATE_JOIN && m_pCurStyle != NULL ) - { - const Style *pStyle; - // Only use one player for StyleType_OnePlayerTwoSides and StepsTypes - // that can only be played by one player (e.g. dance-solo, - // dance-threepanel, popn-nine). -aj - // XXX?: still shows joined player as "Insert Card". May not be an issue? -aj - if( m_pCurStyle->m_StyleType == StyleType_OnePlayerTwoSides || - m_pCurStyle->m_StepsType == StepsType_dance_solo || - m_pCurStyle->m_StepsType == StepsType_dance_threepanel || - m_pCurStyle->m_StepsType == StepsType_popn_nine ) - pStyle = GAMEMAN->GetFirstCompatibleStyle( m_pCurGame, 1, m_pCurStyle->m_StepsType ); - else - pStyle = GAMEMAN->GetFirstCompatibleStyle( m_pCurGame, GetNumSidesJoined(), m_pCurStyle->m_StepsType ); - - // use SetCurrentStyle in case of StyleType_OnePlayerTwoSides - SetCurrentStyle( pStyle ); - } - - Message msg( MessageIDToString(Message_PlayerJoined) ); - msg.SetParam( "Player", pn ); - MESSAGEMAN->Broadcast( msg ); -} - -void GameState::UnjoinPlayer( PlayerNumber pn ) -{ - m_bSideIsJoined[pn] = false; - m_iPlayerStageTokens[pn] = 0; - - ResetPlayer( pn ); - - if( this->GetMasterPlayerNumber() == pn ) - this->SetMasterPlayerNumber(GetFirstHumanPlayer()); - - /* Unjoin STATSMAN first, so steps used by this player are released - * and can be released by PROFILEMAN. */ - STATSMAN->UnjoinPlayer( pn ); - PROFILEMAN->UnloadProfile( pn ); - - Message msg( MessageIDToString(Message_PlayerUnjoined) ); - msg.SetParam( "Player", pn ); - MESSAGEMAN->Broadcast( msg ); - - // If there are no players left, reset some non-player-specific stuff, too. - if( this->GetMasterPlayerNumber() == PLAYER_INVALID ) - { - SongOptions so; - GetDefaultSongOptions( so ); - m_SongOptions.Assign( ModsLevel_Preferred, so ); - m_bDidModeChangeNoteSkin = false; - } -} - -/* xxx: handle multiplayer join? -aj */ - -namespace -{ - bool JoinInputInternal( PlayerNumber pn ) - { - if( !GAMESTATE->PlayersCanJoin() ) - return false; - - // If this side is already in, don't re-join. - if( GAMESTATE->m_bSideIsJoined[pn] ) - return false; - - GAMESTATE->JoinPlayer( pn ); - - return true; - } -}; - -// Handle an input that can join a player. Return true if the player joined. -bool GameState::JoinInput( PlayerNumber pn ) -{ - // When AutoJoin is enabled, join all players on a single start press. - if( GAMESTATE->m_bAutoJoin.Get() ) - return JoinPlayers(); - else - return JoinInputInternal( pn ); -} - -// Attempt to join all players, as if each player pressed Start. -bool GameState::JoinPlayers() -{ - bool bJoined = false; - FOREACH_PlayerNumber( pn ) - { - if( JoinInputInternal(pn) ) - bJoined = true; - } - return bJoined; -} - -/* Game flow: - * - * BeginGame() - the first player has joined; the game is starting. - * - * PlayersFinalized() - player memory cards are loaded; later joins won't have memory cards this stage - * - * BeginStage() - gameplay is beginning - * - * optional: CancelStage() - gameplay aborted (Back pressed), undo BeginStage and back up - * - * CommitStageStats() - gameplay is finished - * Saves STATSMAN->m_CurStageStats to the profiles, so profile information - * is up-to-date for Evaluation. - * - * FinishStage() - gameplay and evaluation is finished - * Clears data which was stored by CommitStageStats. */ -void GameState::BeginGame() -{ - m_timeGameStarted.Touch(); - - m_vpsNamesThatWereFilled.clear(); - - // Play attract on the ending screen, then on the ranking screen - // even if attract sounds are set to off. - m_iNumTimesThroughAttract = -1; - - FOREACH_PlayerNumber( pn ) - MEMCARDMAN->UnlockCard( pn ); -} - -void GameState::LoadProfiles( bool bLoadEdits ) -{ - // Unlock any cards that we might want to load. - FOREACH_HumanPlayer( pn ) - if( !PROFILEMAN->IsPersistentProfile(pn) ) - MEMCARDMAN->UnlockCard( pn ); - - MEMCARDMAN->WaitForCheckingToComplete(); - - FOREACH_HumanPlayer( pn ) - { - // If a profile is already loaded, this was already called. - if( PROFILEMAN->IsPersistentProfile(pn) ) - continue; - - MEMCARDMAN->MountCard( pn ); - bool bSuccess = PROFILEMAN->LoadFirstAvailableProfile( pn, bLoadEdits ); // load full profile - MEMCARDMAN->UnmountCard( pn ); - - if( !bSuccess ) - continue; - - // Lock the card on successful load, so we won't allow it to be changed. - MEMCARDMAN->LockCard( pn ); - - LoadCurrentSettingsFromProfile( pn ); - - Profile* pPlayerProfile = PROFILEMAN->GetProfile( pn ); - if( pPlayerProfile ) - pPlayerProfile->m_iTotalSessions++; - } -} - -void GameState::SavePlayerProfiles() -{ - FOREACH_HumanPlayer( pn ) - SavePlayerProfile( pn ); -} - -void GameState::SavePlayerProfile( PlayerNumber pn ) -{ - if( !PROFILEMAN->IsPersistentProfile(pn) ) - return; - - // AutoplayCPU should not save scores. -aj - // xxx: this MAY cause issues with Multiplayer. However, without a working - // Multiplayer build, we'll never know. -aj - if( m_pPlayerState[pn]->m_PlayerController != PC_HUMAN ) - return; - - bool bWasMemoryCard = PROFILEMAN->ProfileWasLoadedFromMemoryCard(pn); - if( bWasMemoryCard ) - MEMCARDMAN->MountCard( pn ); - PROFILEMAN->SaveProfile( pn ); - if( bWasMemoryCard ) - MEMCARDMAN->UnmountCard( pn ); -} - -bool GameState::HaveProfileToLoad() -{ - FOREACH_HumanPlayer( pn ) - { - // We won't load this profile if it's already loaded. - if( PROFILEMAN->IsPersistentProfile(pn) ) - continue; - - // If a memory card is inserted, we'l try to load it. - if( MEMCARDMAN->CardInserted(pn) ) - return true; - if( !PROFILEMAN->m_sDefaultLocalProfileID[pn].Get().empty() ) - return true; - } - - return false; -} - -bool GameState::HaveProfileToSave() -{ - FOREACH_HumanPlayer( pn ) - if( PROFILEMAN->IsPersistentProfile(pn) ) - return true; - return false; -} - -void GameState::SaveLocalData() -{ - PROFILEMAN->SaveMachineProfile(); -} - -int GameState::GetNumStagesMultiplierForSong( const Song* pSong ) -{ - int iNumStages = 1; - - ASSERT( pSong != NULL ); - if( pSong->IsMarathon() ) - iNumStages *= 3; - if( pSong->IsLong() ) - iNumStages *= 2; - - return iNumStages; -} - -int GameState::GetNumStagesForCurrentSongAndStepsOrCourse() const -{ - int iNumStagesOfThisSong = 1; - if( m_pCurSong ) - { - const Style *pStyle = m_pCurStyle; - int numSidesJoined = GetNumSidesJoined(); - if( pStyle == NULL ) - { - const Steps *pSteps = NULL; - if( this->GetMasterPlayerNumber() != PlayerNumber_Invalid ) - pSteps = m_pCurSteps[this->GetMasterPlayerNumber()]; - // Don't call GetFirstCompatibleStyle if numSidesJoined == 0. - // This happens because on SContinue when players are unjoined, - // pCurSteps will still be set while no players are joined. -Chris - if( pSteps && numSidesJoined > 0 ) - { - // If a style isn't set, use the style of the selected steps. - StepsType st = pSteps->m_StepsType; - pStyle = GAMEMAN->GetFirstCompatibleStyle( m_pCurGame, numSidesJoined, st ); - } - else - { - /* If steps aren't set either, pick any style for the number of - * joined players, or one player if no players are joined. */ - vector vpStyles; - int iJoined = max( numSidesJoined, 1 ); - GAMEMAN->GetCompatibleStyles( m_pCurGame, iJoined, vpStyles ); - ASSERT( !vpStyles.empty() ); - pStyle = vpStyles[0]; - } - } - /* Extra stages need to only count as one stage in case a multi-stage - * song is chosen. */ - if( IsAnExtraStage() ) - iNumStagesOfThisSong = 1; - else - iNumStagesOfThisSong = GameState::GetNumStagesMultiplierForSong( m_pCurSong ); - } - else if( m_pCurCourse ) - iNumStagesOfThisSong = PREFSMAN->m_iSongsPerPlay; - else - return -1; - - iNumStagesOfThisSong = max( iNumStagesOfThisSong, 1 ); - - return iNumStagesOfThisSong; -} - -// Called by ScreenGameplay. Set the length of the current song. -void GameState::BeginStage() -{ - if( m_bDemonstrationOrJukebox ) - return; - - // This should only be called once per stage. - if( m_iNumStagesOfThisSong != 0 ) - LOG->Warn( "XXX: m_iNumStagesOfThisSong == %i?", m_iNumStagesOfThisSong ); - - ResetStageStatistics(); - AdjustSync::ResetOriginalSyncData(); - - if( !ARE_STAGE_PLAYER_MODS_FORCED ) - { - FOREACH_PlayerNumber( p ) - { - ModsGroup &po = m_pPlayerState[p]->m_PlayerOptions; - po.Assign(ModsLevel_Stage, - m_pPlayerState[p]->m_PlayerOptions.GetPreferred()); - } - } - if( !ARE_STAGE_SONG_MODS_FORCED ) - m_SongOptions.Assign( ModsLevel_Stage, m_SongOptions.GetPreferred() ); - - STATSMAN->m_CurStageStats.m_fMusicRate = m_SongOptions.GetSong().m_fMusicRate; - m_iNumStagesOfThisSong = GetNumStagesForCurrentSongAndStepsOrCourse(); - ASSERT( m_iNumStagesOfThisSong != -1 ); - FOREACH_EnabledPlayer( p ) - { - // only do this check with human players, assume CPU players (Rave) - // always have tokens. -aj (this could probably be moved below, even.) - if( !IsEventMode() && !IsCpuPlayer(p) ) - ASSERT( m_iPlayerStageTokens[p] >= m_iNumStagesOfThisSong ); - m_iPlayerStageTokens[p] -= m_iNumStagesOfThisSong; - } - FOREACH_HumanPlayer( pn ) - if( CurrentOptionsDisqualifyPlayer(pn) ) - STATSMAN->m_CurStageStats.m_player[pn].m_bDisqualified = true; - m_bEarnedExtraStage = false; - m_sStageGUID = CryptManager::GenerateRandomUUID(); -} - -void GameState::CancelStage() -{ - FOREACH_CpuPlayer( p ) - { - switch( m_PlayMode ) - { - case PLAY_MODE_BATTLE: - case PLAY_MODE_RAVE: - m_iPlayerStageTokens[p] = PREFSMAN->m_iSongsPerPlay; - default: - break; - } - } - - FOREACH_EnabledPlayer( p ) - m_iPlayerStageTokens[p] += m_iNumStagesOfThisSong; - m_iNumStagesOfThisSong = 0; - ResetStageStatistics(); -} - -void GameState::CommitStageStats() -{ - if( m_bDemonstrationOrJukebox ) - return; - - STATSMAN->CommitStatsToProfiles( &STATSMAN->m_CurStageStats ); - - // Update TotalPlaySeconds. - int iPlaySeconds = max( 0, (int) m_timeGameStarted.GetDeltaTime() ); - - Profile* pMachineProfile = PROFILEMAN->GetMachineProfile(); - pMachineProfile->m_iTotalSessionSeconds += iPlaySeconds; - - FOREACH_HumanPlayer( p ) - { - Profile* pPlayerProfile = PROFILEMAN->GetProfile( p ); - if( pPlayerProfile ) - pPlayerProfile->m_iTotalSessionSeconds += iPlaySeconds; - } -} - -/* Called by ScreenSelectMusic (etc). Increment the stage counter if we just - * played a song. Might be called more than once. */ -void GameState::FinishStage() -{ - // Increment the stage counter. - const int iOldStageIndex = m_iCurrentStageIndex; - ++m_iCurrentStageIndex; - - m_iNumStagesOfThisSong = 0; - - EarnedExtraStage e = CalculateEarnedExtraStage(); - STATSMAN->m_CurStageStats.m_EarnedExtraStage = e; - if( e != EarnedExtraStage_No ) - { - LOG->Trace( "awarded extra stage" ); - FOREACH_HumanPlayer( p ) - { - // todo: unhardcode the extra stage limit? -aj - if( m_iAwardedExtraStages[p] < 2 ) - { - ++m_iAwardedExtraStages[p]; - ++m_iPlayerStageTokens[p]; - m_bEarnedExtraStage = true; - } - } - } - - if( m_bDemonstrationOrJukebox ) - return; - - // todo: simplify. profile saving is accomplished in ScreenProfileSave - // now; all this code does differently is save machine profile as well. -aj - if( IsEventMode() ) - { - const int iSaveProfileEvery = 3; - if( iOldStageIndex/iSaveProfileEvery < m_iCurrentStageIndex/iSaveProfileEvery ) - { - LOG->Trace( "Played %i stages; saving profiles ...", iSaveProfileEvery ); - PROFILEMAN->SaveMachineProfile(); - this->SavePlayerProfiles(); - } - } -} - -void GameState::LoadCurrentSettingsFromProfile( PlayerNumber pn ) -{ - if( !PROFILEMAN->IsPersistentProfile(pn) ) - return; - - const Profile *pProfile = PROFILEMAN->GetProfile(pn); - - // apply saved default modifiers if any - RString sModifiers; - if( pProfile->GetDefaultModifiers( m_pCurGame, sModifiers ) ) - { - /* We don't save negative preferences (eg. "no reverse"). If the theme - * sets a default of "reverse", and the player turns it off, we should - * set it off. However, don't reset modifiers that aren't saved by the - * profile, so we don't ignore unsaved modifiers when a profile is in use. */ - PO_GROUP_CALL( m_pPlayerState[pn]->m_PlayerOptions, ModsLevel_Preferred, ResetSavedPrefs ); - ApplyPreferredModifiers( pn, sModifiers ); - } - // Only set the sort order if it wasn't already set by a GameCommand (or by an earlier profile) - if( m_PreferredSortOrder == SortOrder_Invalid && pProfile->m_SortOrder != SortOrder_Invalid ) - m_PreferredSortOrder = pProfile->m_SortOrder; - if( pProfile->m_LastDifficulty != Difficulty_Invalid ) - m_PreferredDifficulty[pn].Set( pProfile->m_LastDifficulty ); - if( pProfile->m_LastCourseDifficulty != Difficulty_Invalid ) - m_PreferredCourseDifficulty[pn].Set( pProfile->m_LastCourseDifficulty ); - // Only set the PreferredStepsType if it wasn't already set by a GameCommand (or by an earlier profile) - if( m_PreferredStepsType == StepsType_Invalid && pProfile->m_LastStepsType != StepsType_Invalid ) - m_PreferredStepsType.Set( pProfile->m_LastStepsType ); - if( m_pPreferredSong == NULL ) - m_pPreferredSong = pProfile->m_lastSong.ToSong(); - if( m_pPreferredCourse == NULL ) - m_pPreferredCourse = pProfile->m_lastCourse.ToCourse(); -} - -void GameState::SaveCurrentSettingsToProfile( PlayerNumber pn ) -{ - if( !PROFILEMAN->IsPersistentProfile(pn) ) - return; - if( m_bDemonstrationOrJukebox ) - return; - - Profile* pProfile = PROFILEMAN->GetProfile(pn); - - pProfile->SetDefaultModifiers( m_pCurGame, m_pPlayerState[pn]->m_PlayerOptions.GetPreferred().GetSavedPrefsString() ); - if( IsSongSort(m_PreferredSortOrder) ) - pProfile->m_SortOrder = m_PreferredSortOrder; - if( m_PreferredDifficulty[pn] != Difficulty_Invalid ) - pProfile->m_LastDifficulty = m_PreferredDifficulty[pn]; - if( m_PreferredCourseDifficulty[pn] != Difficulty_Invalid ) - pProfile->m_LastCourseDifficulty = m_PreferredCourseDifficulty[pn]; - if( m_PreferredStepsType != StepsType_Invalid ) - pProfile->m_LastStepsType = m_PreferredStepsType; - if( m_pPreferredSong ) - pProfile->m_lastSong.FromSong( m_pPreferredSong ); - if( m_pPreferredCourse ) - pProfile->m_lastCourse.FromCourse( m_pPreferredCourse ); -} - -void GameState::Update( float fDelta ) -{ - m_SongOptions.Update( fDelta ); - - FOREACH_PlayerNumber( p ) - { - m_pPlayerState[p]->Update( fDelta ); - - if( !m_bGoalComplete[p] && IsGoalComplete(p) ) - { - m_bGoalComplete[p] = true; - MESSAGEMAN->Broadcast( (MessageID)(Message_GoalCompleteP1+p) ); - } - } - - if( GAMESTATE->m_pCurCourse ) - { - if( GAMESTATE->m_pCurCourse->m_fGoalSeconds > 0 && !m_bWorkoutGoalComplete ) - { - const StageStats &ssCurrent = STATSMAN->m_CurStageStats; - bool bGoalComplete = ssCurrent.m_fGameplaySeconds > GAMESTATE->m_pCurCourse->m_fGoalSeconds; - if( bGoalComplete ) - { - MESSAGEMAN->Broadcast( "WorkoutGoalComplete" ); - m_bWorkoutGoalComplete = true; - } - } - } -} - -void GameState::SetCurGame( const Game *pGame ) -{ - m_pCurGame.Set( pGame ); - RString sGame = pGame ? RString(pGame->m_szName) : RString(); - PREFSMAN->SetCurrentGame( sGame ); -} - -const float GameState::MUSIC_SECONDS_INVALID = -5000.0f; - -void GameState::ResetMusicStatistics() -{ - m_Position.Reset(); - m_LastPositionTimer.Touch(); - m_LastPositionSeconds = 0.0f; - - Actor::SetBGMTime( 0, 0, 0, 0 ); - - FOREACH_PlayerNumber( p ) - { - m_pPlayerState[p]->ClearHopoState(); - m_pPlayerState[p]->m_Position.Reset(); - } -} - -void GameState::ResetStageStatistics() -{ - STATSMAN->m_CurStageStats = StageStats(); - - RemoveAllActiveAttacks(); - FOREACH_PlayerNumber( p ) - m_pPlayerState[p]->RemoveAllInventory(); - m_fOpponentHealthPercent = 1; - m_fHasteRate = 0; - m_fLastHasteUpdateMusicSeconds = 0; - m_fAccumulatedHasteSeconds = 0; - m_fTugLifePercentP1 = 0.5f; - FOREACH_PlayerNumber( p ) - { - m_pPlayerState[p]->m_fSuperMeter = 0; - m_pPlayerState[p]->m_HealthState = HealthState_Alive; - - m_pPlayerState[p]->m_iLastPositiveSumOfAttackLevels = 0; - m_pPlayerState[p]->m_fSecondsUntilAttacksPhasedOut = 0; // PlayerAI not affected - - m_bGoalComplete[p] = false; - } - m_bWorkoutGoalComplete = false; - - - FOREACH_PlayerNumber( p ) - { - m_vLastStageAwards[p].clear(); - m_vLastPeakComboAwards[p].clear(); - } - - // Reset the round seed. Do this here and not in FinishStage so that players - // get new shuffle patterns if they Back out of gameplay and play again. - m_iStageSeed = rand(); -} - -void GameState::UpdateSongPosition( float fPositionSeconds, const TimingData &timing, const RageTimer ×tamp, bool bUpdatePlayers ) -{ - /* It's not uncommon to get a lot of duplicated positions from the sound - * driver, like so: 13.120953,13.130975,13.130975,13.130975,13.140998,... - * This causes visual stuttering of the arrows. To compensate, keep a - * RageTimer since the last change. */ - if (fPositionSeconds == m_LastPositionSeconds) - fPositionSeconds += m_LastPositionTimer.Ago(); - else - { - //LOG->Info("Time difference: %+f", - // m_LastPositionTimer.Ago() - (fPositionSeconds - m_LastPositionSeconds) - //); - m_LastPositionTimer.Touch(); - m_LastPositionSeconds = fPositionSeconds; - } - - m_Position.UpdateSongPosition( fPositionSeconds, timing, timestamp ); - - if( bUpdatePlayers ) - { - FOREACH_EnabledPlayer( pn ) - { - if( m_pCurSteps[pn] ) - { - m_pPlayerState[pn]->m_Position.UpdateSongPosition( fPositionSeconds, *m_pCurSteps[pn]->GetTimingData(), timestamp ); - Actor::SetPlayerBGMBeat( pn, m_pPlayerState[pn]->m_Position.m_fSongBeatVisible, m_pPlayerState[pn]->m_Position.m_fSongBeatNoOffset ); - } - } - } - Actor::SetBGMTime( GAMESTATE->m_Position.m_fMusicSecondsVisible, GAMESTATE->m_Position.m_fSongBeatVisible, fPositionSeconds, GAMESTATE->m_Position.m_fSongBeatNoOffset ); -// LOG->Trace( "m_fMusicSeconds = %f, m_fSongBeat = %f, m_fCurBPS = %f, m_bFreeze = %f", m_fMusicSeconds, m_fSongBeat, m_fCurBPS, m_bFreeze ); -} - -/* -update player position code goes here - */ - -float GameState::GetSongPercent( float beat ) const -{ - // 0 = first step; 1 = last step - float curTime = this->m_pCurSong->m_SongTiming.GetElapsedTimeFromBeat(beat); - return (curTime - m_pCurSong->GetFirstSecond()) / m_pCurSong->GetLastSecond(); -} - -int GameState::GetNumStagesLeft( PlayerNumber pn ) const -{ - return m_iPlayerStageTokens[pn]; -} - -int GameState::GetSmallestNumStagesLeftForAnyHumanPlayer() const -{ - if( IsEventMode() ) - return 999; - int iSmallest = INT_MAX; - FOREACH_HumanPlayer( p ) - iSmallest = min( iSmallest, m_iPlayerStageTokens[p] ); - return iSmallest; -} - -bool GameState::IsFinalStageForAnyHumanPlayer() const -{ - return GetSmallestNumStagesLeftForAnyHumanPlayer() == 1; -} - -bool GameState::IsAnExtraStage() const -{ - if( this->GetMasterPlayerNumber() == PlayerNumber_Invalid ) - return false; - return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[this->GetMasterPlayerNumber()] > 0; -} - -static ThemeMetric LOCK_EXTRA_STAGE_SELECTION("GameState","LockExtraStageSelection"); -bool GameState::IsAnExtraStageAndSelectionLocked() const -{ - return IsAnExtraStage() && LOCK_EXTRA_STAGE_SELECTION; -} - -bool GameState::IsExtraStage() const -{ - if( this->GetMasterPlayerNumber() == PlayerNumber_Invalid ) - return false; - return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[this->GetMasterPlayerNumber()] == 1; -} - -bool GameState::IsExtraStage2() const -{ - if( this->GetMasterPlayerNumber() == PlayerNumber_Invalid ) - return false; - return !IsEventMode() && !IsCourseMode() && m_iAwardedExtraStages[this->GetMasterPlayerNumber()] == 2; -} - -Stage GameState::GetCurrentStage() const -{ - if( m_bDemonstrationOrJukebox ) return Stage_Demo; - // "event" has precedence - else if( IsEventMode() ) return Stage_Event; - else if( m_PlayMode == PLAY_MODE_ONI ) return Stage_Oni; - else if( m_PlayMode == PLAY_MODE_NONSTOP ) return Stage_Nonstop; - else if( m_PlayMode == PLAY_MODE_ENDLESS ) return Stage_Endless; - else if( IsExtraStage() ) return Stage_Extra1; - else if( IsExtraStage2() ) return Stage_Extra2; - //else if( IsFinalStageForAnyHumanPlayer() ) return Stage_Final; - // above function behaves weirdly, it will always return final stage if any player is - // on final stage, rather than the last remaining player. The below method seems to make a bit more sense. - else if(m_iPlayerStageTokens[PLAYER_1] == 0 && m_iPlayerStageTokens[PLAYER_2] == 0) return Stage_Final; - else - { - switch( this->m_iCurrentStageIndex ) - { - case 0: return Stage_1st; - case 1: return Stage_2nd; - case 2: return Stage_3rd; - case 3: return Stage_4th; - case 4: return Stage_5th; - case 5: return Stage_6th; - default: return Stage_Next; - } - } -} - -int GameState::GetCourseSongIndex() const -{ - // iSongsPlayed includes the current song, so it's 1-based; subtract one. - if( GAMESTATE->m_bMultiplayer ) - { - FOREACH_EnabledMultiPlayer(mp) - return STATSMAN->m_CurStageStats.m_multiPlayer[mp].m_iSongsPlayed-1; - FAIL_M("At least one MultiPlayer must be joined."); - } - else - { - return STATSMAN->m_CurStageStats.m_player[this->GetMasterPlayerNumber()].m_iSongsPlayed-1; - } -} - -/* Hack: when we're loading a new course song, we want to display the new song - * number, even though we haven't started that song yet. */ -int GameState::GetLoadingCourseSongIndex() const -{ - int iIndex = GetCourseSongIndex(); - if( m_bLoadingNextSong ) - ++iIndex; - return iIndex; -} - -static LocalizedString PLAYER1 ("GameState","Player 1"); -static LocalizedString PLAYER2 ("GameState","Player 2"); -static LocalizedString CPU ("GameState","CPU"); -RString GameState::GetPlayerDisplayName( PlayerNumber pn ) const -{ - ASSERT( IsPlayerEnabled(pn) ); - const LocalizedString *pDefaultNames[] = { &PLAYER1, &PLAYER2 }; - if( IsHumanPlayer(pn) ) - { - if( !PROFILEMAN->GetPlayerName(pn).empty() ) - return PROFILEMAN->GetPlayerName(pn); - else - return pDefaultNames[pn]->GetValue(); - } - else - { - return CPU.GetValue(); - } -} - -bool GameState::PlayersCanJoin() const -{ - bool b = GetNumSidesJoined() == 0 || GetCurrentStyle() == NULL; // selecting a style finalizes the players - if( ALLOW_LATE_JOIN.IsLoaded() && ALLOW_LATE_JOIN ) - { - Screen *pScreen = SCREENMAN->GetTopScreen(); - if( pScreen ) - b |= pScreen->AllowLateJoin(); - } - return b; -} - -int GameState::GetNumSidesJoined() const -{ - int iNumSidesJoined = 0; - FOREACH_PlayerNumber( p ) - if( m_bSideIsJoined[p] ) - iNumSidesJoined++; // left side, and right side - return iNumSidesJoined; -} - -const Game* GameState::GetCurrentGame() -{ - ASSERT( m_pCurGame != NULL ); // the game must be set before calling this - return m_pCurGame; -} - -const Style* GameState::GetCurrentStyle() const -{ - return m_pCurStyle; -} - -void GameState::SetCurrentStyle( const Style *pStyle ) -{ - m_pCurStyle.Set( pStyle ); - if( INPUTMAPPER ) - { - if( GetCurrentStyle() && GetCurrentStyle()->m_StyleType == StyleType_OnePlayerTwoSides ) - INPUTMAPPER->SetJoinControllers( this->GetMasterPlayerNumber() ); - else - INPUTMAPPER->SetJoinControllers( PLAYER_INVALID ); - } -} - -bool GameState::IsPlayerEnabled( PlayerNumber pn ) const -{ - // In rave, all players are present. Non-human players are CPU controlled. - switch( m_PlayMode ) - { - case PLAY_MODE_BATTLE: - case PLAY_MODE_RAVE: - return true; - default: - return IsHumanPlayer(pn); - } -} - -bool GameState::IsMultiPlayerEnabled( MultiPlayer mp ) const -{ - return m_MultiPlayerStatus[ mp ] == MultiPlayerStatus_Joined; -} - -bool GameState::IsPlayerEnabled( const PlayerState* pPlayerState ) const -{ - if( pPlayerState->m_mp != MultiPlayer_Invalid ) - return IsMultiPlayerEnabled( pPlayerState->m_mp ); - if( pPlayerState->m_PlayerNumber != PLAYER_INVALID ) - return IsPlayerEnabled( pPlayerState->m_PlayerNumber ); - return false; -} - -int GameState::GetNumPlayersEnabled() const -{ - int count = 0; - FOREACH_EnabledPlayer( pn ) - count++; - return count; -} - -bool GameState::IsHumanPlayer( PlayerNumber pn ) const -{ - if( pn == PLAYER_INVALID ) - return false; - - if( GetCurrentStyle() == NULL ) // no style chosen - { - if( PlayersCanJoin() ) - return m_bSideIsJoined[pn]; // only allow input from sides that have already joined - else - return true; // if we can't join, then we're on a screen like MusicScroll or GameOver - } - - StyleType type = GetCurrentStyle()->m_StyleType; - switch( type ) - { - case StyleType_TwoPlayersTwoSides: - case StyleType_TwoPlayersSharedSides: - return true; - case StyleType_OnePlayerOneSide: - case StyleType_OnePlayerTwoSides: - return pn == this->GetMasterPlayerNumber(); - default: - FAIL_M(ssprintf("Invalid style type: %i", type)); - } -} - -int GameState::GetNumHumanPlayers() const -{ - int count = 0; - FOREACH_HumanPlayer( pn ) - count++; - return count; -} - -PlayerNumber GameState::GetFirstHumanPlayer() const -{ - FOREACH_HumanPlayer( pn ) - return pn; - return PLAYER_INVALID; -} - -PlayerNumber GameState::GetFirstDisabledPlayer() const -{ - FOREACH_PlayerNumber( pn ) - if( !IsPlayerEnabled(pn) ) - return pn; - return PLAYER_INVALID; -} - -bool GameState::IsCpuPlayer( PlayerNumber pn ) const -{ - return IsPlayerEnabled(pn) && !IsHumanPlayer(pn); -} - -bool GameState::AnyPlayersAreCpu() const -{ - FOREACH_CpuPlayer( pn ) - return true; - return false; -} - - -bool GameState::IsCourseMode() const -{ - switch(m_PlayMode) - { - case PLAY_MODE_ONI: - case PLAY_MODE_NONSTOP: - case PLAY_MODE_ENDLESS: - return true; - default: - return false; - } -} - -bool GameState::IsBattleMode() const -{ - switch( m_PlayMode ) - { - case PLAY_MODE_BATTLE: - return true; - default: - return false; - } -} - -EarnedExtraStage GameState::CalculateEarnedExtraStage() const -{ - if( IsEventMode() ) - return EarnedExtraStage_No; - - if( !PREFSMAN->m_bAllowExtraStage ) - return EarnedExtraStage_No; - - if( m_PlayMode != PLAY_MODE_REGULAR ) - return EarnedExtraStage_No; - - if( m_bBackedOutOfFinalStage ) - return EarnedExtraStage_No; - - if( GetSmallestNumStagesLeftForAnyHumanPlayer() > 0 ) - return EarnedExtraStage_No; - - if( m_iAwardedExtraStages[this->GetMasterPlayerNumber()] >= 2 ) - return EarnedExtraStage_No; - - FOREACH_EnabledPlayer( pn ) - { - Difficulty dc = m_pCurSteps[pn]->GetDifficulty(); - switch( dc ) - { - case Difficulty_Edit: - if( !EDIT_ALLOWED_FOR_EXTRA ) - continue; // can't use edit steps - break; - default: - if( dc < MIN_DIFFICULTY_FOR_EXTRA ) - continue; // not hard enough! - break; - } - - if( IsExtraStage() ) - { - if( ALLOW_EXTRA_2 && STATSMAN->m_CurStageStats.m_player[pn].GetGrade() <= GRADE_TIER_FOR_EXTRA_2 ) - return EarnedExtraStage_Extra2; - } - else if( STATSMAN->m_CurStageStats.m_player[pn].GetGrade() <= GRADE_TIER_FOR_EXTRA_1 ) - { - return EarnedExtraStage_Extra1; - } - } - - return EarnedExtraStage_No; -} - -PlayerNumber GameState::GetBestPlayer() const -{ - FOREACH_PlayerNumber( pn ) - if( GetStageResult(pn) == RESULT_WIN ) - return pn; - return PLAYER_INVALID; // draw -} - -StageResult GameState::GetStageResult( PlayerNumber pn ) const -{ - switch( m_PlayMode ) - { - case PLAY_MODE_BATTLE: - case PLAY_MODE_RAVE: - if( fabsf(m_fTugLifePercentP1 - 0.5f) < 0.0001f ) - return RESULT_DRAW; - switch( pn ) - { - case PLAYER_1: return (m_fTugLifePercentP1>=0.5f)?RESULT_WIN:RESULT_LOSE; - case PLAYER_2: return (m_fTugLifePercentP1<0.5f)?RESULT_WIN:RESULT_LOSE; - default: FAIL_M("Invalid player for battle! Aborting..."); return RESULT_LOSE; - } - default: break; - } - - StageResult win = RESULT_WIN; - FOREACH_PlayerNumber( p ) - { - if( p == pn ) - continue; - - // If anyone did just as well, at best it's a draw. - if( STATSMAN->m_CurStageStats.m_player[p].m_iActualDancePoints == STATSMAN->m_CurStageStats.m_player[pn].m_iActualDancePoints ) - win = RESULT_DRAW; - - // If anyone did better, we lost. - if( STATSMAN->m_CurStageStats.m_player[p].m_iActualDancePoints > STATSMAN->m_CurStageStats.m_player[pn].m_iActualDancePoints ) - return RESULT_LOSE; - } - return win; -} - -void GameState::GetDefaultPlayerOptions( PlayerOptions &po ) -{ - po.Init(); - po.FromString( PREFSMAN->m_sDefaultModifiers ); - po.FromString( CommonMetrics::DEFAULT_MODIFIERS ); - if( po.m_sNoteSkin.empty() ) - po.m_sNoteSkin = CommonMetrics::DEFAULT_NOTESKIN_NAME; -} - -void GameState::GetDefaultSongOptions( SongOptions &so ) -{ - so.Init(); - so.FromString( PREFSMAN->m_sDefaultModifiers ); - so.FromString( CommonMetrics::DEFAULT_MODIFIERS ); -} - -void GameState::ResetToDefaultSongOptions( ModsLevel l ) -{ - SongOptions so; - GetDefaultSongOptions( so ); - m_SongOptions.Assign( l, so ); -} - -void GameState::ApplyPreferredModifiers( PlayerNumber pn, RString sModifiers ) -{ - m_pPlayerState[pn]->m_PlayerOptions.FromString( ModsLevel_Preferred, sModifiers ); - m_SongOptions.FromString( ModsLevel_Preferred, sModifiers ); -} - -void GameState::ApplyStageModifiers( PlayerNumber pn, RString sModifiers ) -{ - m_pPlayerState[pn]->m_PlayerOptions.FromString( ModsLevel_Stage, sModifiers ); - m_SongOptions.FromString( ModsLevel_Stage, sModifiers ); -} - -void GameState::ClearStageModifiersIllegalForCourse() -{ - FOREACH_EnabledPlayer( pn ) - PO_GROUP_CALL( m_pPlayerState[pn]->m_PlayerOptions, ModsLevel_Stage, ResetSavedPrefsInvalidForCourse ); -} - -bool GameState::CurrentOptionsDisqualifyPlayer( PlayerNumber pn ) -{ - if( !PREFSMAN->m_bDisqualification ) - return false; - - if( !IsHumanPlayer(pn) ) - return false; - - const PlayerOptions &po = m_pPlayerState[pn]->m_PlayerOptions.GetPreferred(); - - // Check the stored player options for disqualify. Don't disqualify because - // of mods that were forced. - if( IsCourseMode() ) - return po.IsEasierForCourseAndTrail( m_pCurCourse, m_pCurTrail[pn] ); - else - return po.IsEasierForSongAndSteps( m_pCurSong, m_pCurSteps[pn], pn); -} - -/* reset noteskins (?) - * GameState::ResetNoteSkins() - * GameState::ResetNoteSkinsForPlayer( PlayerNumber pn ) - * - */ - -void GameState::GetAllUsedNoteSkins( vector &out ) const -{ - FOREACH_EnabledPlayer( pn ) - { - out.push_back( m_pPlayerState[pn]->m_PlayerOptions.GetCurrent().m_sNoteSkin ); - - // Add noteskins that are used in courses. - if( IsCourseMode() ) - { - const Trail *pTrail = m_pCurTrail[pn]; - ASSERT( pTrail != NULL ); - - FOREACH_CONST( TrailEntry, pTrail->m_vEntries, e ) - { - PlayerOptions po; - po.FromString( e->Modifiers ); - if( !po.m_sNoteSkin.empty() ) - out.push_back( po.m_sNoteSkin ); - } - } - } - - // Remove duplicates. - sort( out.begin(), out.end() ); - out.erase( unique( out.begin(), out.end() ), out.end() ); -} - -void GameState::RemoveAllActiveAttacks() // called on end of song -{ - FOREACH_PlayerNumber( p ) - m_pPlayerState[p]->RemoveActiveAttacks(); -} - -void GameState::AddStageToPlayer( PlayerNumber pn ) -{ - // Add one stage more to player (bonus) -cerbo - ++m_iPlayerStageTokens[pn]; -} - -template -void setmin( T &a, const T &b ) -{ - a = min(a, b); -} - -template -void setmax( T &a, const T &b ) -{ - a = max(a, b); -} - -PlayerOptions::FailType GameState::GetPlayerFailType( const PlayerState *pPlayerState ) const -{ - PlayerNumber pn = pPlayerState->m_PlayerNumber; - PlayerOptions::FailType ft = pPlayerState->m_PlayerOptions.GetCurrent().m_FailType; - - // If the player changed the fail mode explicitly, leave it alone. - if( m_bChangedFailTypeOnScreenSongOptions ) - return ft; - - if( IsCourseMode() ) - { - if( PREFSMAN->m_bMinimum1FullSongInCourses && GetCourseSongIndex()==0 ) - ft = max( ft, PlayerOptions::FAIL_IMMEDIATE_CONTINUE ); // take the least harsh of the two FailTypes - } - else - { - Difficulty dc = Difficulty_Invalid; - if( m_pCurSteps[pn] ) - dc = m_pCurSteps[pn]->GetDifficulty(); - - bool bFirstStage = false; - if( !IsEventMode() ) - bFirstStage |= m_iPlayerStageTokens[pPlayerState->m_PlayerNumber] == PREFSMAN->m_iSongsPerPlay-1; // HACK; -1 because this is called during gameplay - - // Easy and beginner are never harder than FAIL_IMMEDIATE_CONTINUE. - if( dc <= Difficulty_Easy ) - setmax( ft, PlayerOptions::FAIL_IMMEDIATE_CONTINUE ); - - if( dc <= Difficulty_Easy && bFirstStage && PREFSMAN->m_bFailOffForFirstStageEasy ) - setmax( ft, PlayerOptions::FAIL_OFF ); - - /* If beginner's steps were chosen, and this is the first stage, - * turn off failure completely. */ - if( dc == Difficulty_Beginner && bFirstStage ) - setmax( ft, PlayerOptions::FAIL_OFF ); - - if( dc == Difficulty_Beginner && PREFSMAN->m_bFailOffInBeginner ) - setmax( ft, PlayerOptions::FAIL_OFF ); - } - - return ft; -} - -bool GameState::ShowW1() const -{ - AllowW1 pref = PREFSMAN->m_AllowW1; - switch( pref ) - { - case ALLOW_W1_NEVER: return false; - case ALLOW_W1_COURSES_ONLY: return IsCourseMode(); - case ALLOW_W1_EVERYWHERE: return true; - default: - FAIL_M(ssprintf("Invalid AllowW1 preference: %i", pref)); - } -} - - -static ThemeMetric PROFILE_RECORD_FEATS("GameState","ProfileRecordFeats"); -static ThemeMetric CATEGORY_RECORD_FEATS("GameState","CategoryRecordFeats"); -void GameState::GetRankingFeats( PlayerNumber pn, vector &asFeatsOut ) const -{ - if( !IsHumanPlayer(pn) ) - return; - - Profile *pProf = PROFILEMAN->GetProfile(pn); - - // Check for feats even if the PlayMode is rave or battle because the player - // may have made high scores then switched modes. - CHECKPOINT_M( ssprintf("PlayMode %i", int(m_PlayMode)) ); - switch( m_PlayMode ) - { - case PLAY_MODE_REGULAR: - case PLAY_MODE_BATTLE: - case PLAY_MODE_RAVE: - { - CHECKPOINT; - - StepsType st = GetCurrentStyle()->m_StepsType; - - // Find unique Song and Steps combinations that were played. - // We must keep only the unique combination or else we'll double-count - // high score markers. - vector vSongAndSteps; - - for( unsigned i=0; im_vPlayedStageStats.size(); i++ ) - { - CHECKPOINT_M( ssprintf("%u/%i", i, (int)STATSMAN->m_vPlayedStageStats.size() ) ); - SongAndSteps sas; - ASSERT( !STATSMAN->m_vPlayedStageStats[i].m_vpPlayedSongs.empty() ); - sas.pSong = STATSMAN->m_vPlayedStageStats[i].m_vpPlayedSongs[0]; - ASSERT( sas.pSong != NULL ); - if( STATSMAN->m_vPlayedStageStats[i].m_player[pn].m_vpPossibleSteps.empty() ) - continue; - sas.pSteps = STATSMAN->m_vPlayedStageStats[i].m_player[pn].m_vpPossibleSteps[0]; - ASSERT( sas.pSteps != NULL ); - vSongAndSteps.push_back( sas ); - } - CHECKPOINT; - - sort( vSongAndSteps.begin(), vSongAndSteps.end() ); - - vector::iterator toDelete = unique( vSongAndSteps.begin(), vSongAndSteps.end() ); - vSongAndSteps.erase(toDelete, vSongAndSteps.end()); - - CHECKPOINT; - for( unsigned i=0; iGetMachineProfile()->GetStepsHighScoreList(pSong,pSteps); - for( unsigned j=0; jGetTranslitMainTitle().c_str(), DifficultyToString(pSteps->GetDifficulty()).c_str() ); - feat.pStringToFill = hs.GetNameMutable(); - feat.grade = hs.GetGrade(); - feat.fPercentDP = hs.GetPercentDP(); - feat.iScore = hs.GetScore(); - - if( pSong->HasBanner() ) - feat.Banner = pSong->GetBannerPath(); - - asFeatsOut.push_back( feat ); - } - } - - // Find Personal Records - if( pProf && PROFILE_RECORD_FEATS ) - { - HighScoreList &hsl = pProf->GetStepsHighScoreList(pSong,pSteps); - for( unsigned j=0; jGetTranslitMainTitle().c_str(), DifficultyToString(pSteps->GetDifficulty()).c_str() ); - feat.pStringToFill = hs.GetNameMutable(); - feat.grade = hs.GetGrade(); - feat.fPercentDP = hs.GetPercentDP(); - feat.iScore = hs.GetScore(); - - // XXX: temporary hack - // Why is this here? -aj - if( pSong->HasBackground() ) - feat.Banner = pSong->GetBackgroundPath(); - // if( pSong->HasBanner() ) - // feat.Banner = pSong->GetBannerPath(); - - asFeatsOut.push_back( feat ); - } - } - } - - CHECKPOINT; - StageStats stats; - STATSMAN->GetFinalEvalStageStats( stats ); - - - // Find Machine Category Records - FOREACH_ENUM( RankingCategory, rc ) - { - if( !CATEGORY_RECORD_FEATS ) - continue; - HighScoreList &hsl = PROFILEMAN->GetMachineProfile()->GetCategoryHighScoreList( st, rc ); - for( unsigned j=0; jGetCategoryHighScoreList( st, rc ); - for( unsigned j=0; jm_CourseDifficulty; - - // Find Machine Records - { - Profile* pProfile = PROFILEMAN->GetMachineProfile(); - HighScoreList &hsl = pProfile->GetCourseHighScoreList( pCourse, pTrail ); - for( unsigned i=0; iGetDisplayFullTitle().c_str() ); - if( cd != Difficulty_Medium ) - feat.Feat += " " + CourseDifficultyToLocalizedString(cd); - feat.pStringToFill = hs.GetNameMutable(); - feat.grade = Grade_NoData; - feat.iScore = hs.GetScore(); - feat.fPercentDP = hs.GetPercentDP(); - if( pCourse->HasBanner() ) - feat.Banner = pCourse->GetBannerPath(); - asFeatsOut.push_back( feat ); - } - } - - // Find Personal Records - if( PROFILE_RECORD_FEATS && PROFILEMAN->IsPersistentProfile(pn) ) - { - HighScoreList &hsl = pProf->GetCourseHighScoreList( pCourse, pTrail ); - for( unsigned i=0; iGetDisplayFullTitle().c_str() ); - feat.pStringToFill = hs.GetNameMutable(); - feat.grade = Grade_NoData; - feat.iScore = hs.GetScore(); - feat.fPercentDP = hs.GetPercentDP(); - if( pCourse->HasBanner() ) - feat.Banner = pCourse->GetBannerPath(); - asFeatsOut.push_back( feat ); - } - } - } - break; - default: - FAIL_M(ssprintf("Invalid play mode: %i", int(m_PlayMode))); - } -} - -bool GameState::AnyPlayerHasRankingFeats() const -{ - vector vFeats; - FOREACH_PlayerNumber( p ) - { - GetRankingFeats( p, vFeats ); - if( !vFeats.empty() ) - return true; - } - return false; -} - -void GameState::StoreRankingName( PlayerNumber pn, RString sName ) -{ - sName.MakeUpper(); - - if( USE_NAME_BLACKLIST ) - { - RageFile file; - if( file.Open(NAME_BLACKLIST_FILE) ) - { - RString sLine; - - while( !file.AtEOF() ) - { - if( file.GetLine(sLine) == -1 ) - { - LOG->Warn( "Error reading \"%s\": %s", NAME_BLACKLIST_FILE, file.GetError().c_str() ); - break; - } - - sLine.MakeUpper(); - if( !sLine.empty() && sName.find(sLine) != string::npos ) // name contains a bad word - { - LOG->Trace( "entered '%s' matches blacklisted item '%s'", sName.c_str(), sLine.c_str() ); - sName = ""; - break; - } - } - } - } - - vector aFeats; - GetRankingFeats( pn, aFeats ); - - for( unsigned i=0; iGetMachineProfile(); - - if( !PREFSMAN->m_bAllowMultipleHighScoreWithSameName ) - { - // erase all but the highest score for each name - FOREACHM( SongID, Profile::HighScoresForASong, pProfile->m_SongHighScores, iter ) - FOREACHM( StepsID, Profile::HighScoresForASteps, iter->second.m_StepsHighScores, iter2 ) - iter2->second.hsl.RemoveAllButOneOfEachName(); - - FOREACHM( CourseID, Profile::HighScoresForACourse, pProfile->m_CourseHighScores, iter ) - FOREACHM( TrailID, Profile::HighScoresForATrail, iter->second.m_TrailHighScores, iter2 ) - iter2->second.hsl.RemoveAllButOneOfEachName(); - } - - // clamp high score sizes - FOREACHM( SongID, Profile::HighScoresForASong, pProfile->m_SongHighScores, iter ) - FOREACHM( StepsID, Profile::HighScoresForASteps, iter->second.m_StepsHighScores, iter2 ) - iter2->second.hsl.ClampSize( true ); - - FOREACHM( CourseID, Profile::HighScoresForACourse, pProfile->m_CourseHighScores, iter ) - FOREACHM( TrailID, Profile::HighScoresForATrail, iter->second.m_TrailHighScores, iter2 ) - iter2->second.hsl.ClampSize( true ); -} - -bool GameState::AllAreInDangerOrWorse() const -{ - FOREACH_EnabledPlayer( p ) - if( m_pPlayerState[p]->m_HealthState < HealthState_Danger ) - return false; - return true; -} - -bool GameState::OneIsHot() const -{ - FOREACH_EnabledPlayer( p ) - if( m_pPlayerState[p]->m_HealthState == HealthState_Hot ) - return true; - return false; -} - -bool GameState::IsTimeToPlayAttractSounds() const -{ - // m_iNumTimesThroughAttract will be -1 from the first attract screen after - // the end of a game until the next time FIRST_ATTRACT_SCREEN is reached. - // Play attract sounds for this sort span of time regardless of - // m_AttractSoundFrequency because it's awkward to have the machine go - // silent immediately after the end of a game. - if( m_iNumTimesThroughAttract == -1 ) - return true; - - if( PREFSMAN->m_AttractSoundFrequency == ASF_NEVER ) - return false; - - // play attract sounds once every m_iAttractSoundFrequency times through - if( (m_iNumTimesThroughAttract % PREFSMAN->m_AttractSoundFrequency)==0 ) - return true; - - return false; -} - -void GameState::VisitAttractScreen( const RString sScreenName ) -{ - if( sScreenName == CommonMetrics::FIRST_ATTRACT_SCREEN.GetValue() ) - m_iNumTimesThroughAttract++; -} - -bool GameState::DifficultiesLocked() const -{ - if( m_PlayMode == PLAY_MODE_RAVE ) - return true; - if( IsCourseMode() ) - return PREFSMAN->m_bLockCourseDifficulties; - if( GetCurrentStyle()->m_bLockDifficulties ) - return true; - return false; -} - -bool GameState::ChangePreferredDifficultyAndStepsType( PlayerNumber pn, Difficulty dc, StepsType st ) -{ - m_PreferredDifficulty[pn].Set( dc ); - m_PreferredStepsType.Set( st ); - if( DifficultiesLocked() ) - FOREACH_PlayerNumber( p ) - if( p != pn ) - m_PreferredDifficulty[p].Set( m_PreferredDifficulty[pn] ); - - return true; -} - -/* When only displaying difficulties in DIFFICULTIES_TO_SHOW, use GetClosestShownDifficulty - * to find which difficulty to show, and ChangePreferredDifficulty(pn, dir) to change - * difficulty. */ -bool GameState::ChangePreferredDifficulty( PlayerNumber pn, int dir ) -{ - const vector &v = CommonMetrics::DIFFICULTIES_TO_SHOW.GetValue(); - - Difficulty d = GetClosestShownDifficulty(pn); - while( 1 ) - { - d = enum_add2( d, dir ); - if( d < 0 || d >= NUM_Difficulty ) - return false; - if( find(v.begin(), v.end(), d) != v.end() ) - break; // found - } - - m_PreferredDifficulty[pn].Set( d ); - return true; -} - -/* The user may be set to prefer a difficulty that isn't always shown; typically, - * Difficulty_Edit. Return the closest shown difficulty <= m_PreferredDifficulty. */ -Difficulty GameState::GetClosestShownDifficulty( PlayerNumber pn ) const -{ - const vector &v = CommonMetrics::DIFFICULTIES_TO_SHOW.GetValue(); - - Difficulty iClosest = (Difficulty) 0; - int iClosestDist = -1; - FOREACH_CONST( Difficulty, v, dc ) - { - int iDist = m_PreferredDifficulty[pn] - *dc; - if( iDist < 0 ) - continue; - if( iClosestDist != -1 && iDist > iClosestDist ) - continue; - iClosestDist = iDist; - iClosest = *dc; - } - - return iClosest; -} - -bool GameState::ChangePreferredCourseDifficultyAndStepsType( PlayerNumber pn, CourseDifficulty cd, StepsType st ) -{ - m_PreferredCourseDifficulty[pn].Set( cd ); - m_PreferredStepsType.Set( st ); - if( PREFSMAN->m_bLockCourseDifficulties ) - FOREACH_PlayerNumber( p ) - if( p != pn ) - m_PreferredCourseDifficulty[p].Set( m_PreferredCourseDifficulty[pn] ); - - return true; -} - -bool GameState::ChangePreferredCourseDifficulty( PlayerNumber pn, int dir ) -{ - /* If we have a course selected, only choose among difficulties available in the course. */ - const Course *pCourse = m_pCurCourse; - - const vector &v = CommonMetrics::COURSE_DIFFICULTIES_TO_SHOW.GetValue(); - - CourseDifficulty cd = m_PreferredCourseDifficulty[pn]; - while( 1 ) - { - cd = enum_add2( cd, dir ); - if( cd < 0 || cd >= NUM_Difficulty ) - return false; - if( find(v.begin(),v.end(),cd) == v.end() ) - continue; /* not available */ - if( !pCourse || pCourse->GetTrail( GetCurrentStyle()->m_StepsType, cd ) ) - break; - } - - return ChangePreferredCourseDifficulty( pn, cd ); -} - -bool GameState::IsCourseDifficultyShown( CourseDifficulty cd ) -{ - const vector &v = CommonMetrics::COURSE_DIFFICULTIES_TO_SHOW.GetValue(); - return find(v.begin(), v.end(), cd) != v.end(); -} - -Difficulty GameState::GetEasiestStepsDifficulty() const -{ - Difficulty dc = Difficulty_Invalid; - FOREACH_HumanPlayer( p ) - { - if( m_pCurSteps[p] == NULL ) - { - LOG->Warn( "GetEasiestStepsDifficulty called but p%i hasn't chosen notes", p+1 ); - continue; - } - dc = min( dc, m_pCurSteps[p]->GetDifficulty() ); - } - return dc; -} - -Difficulty GameState::GetHardestStepsDifficulty() const -{ - Difficulty dc = Difficulty_Beginner; - FOREACH_HumanPlayer( p ) - { - if( m_pCurSteps[p] == NULL ) - { - LOG->Warn( "GetHardestStepsDifficulty called but p%i hasn't chosen notes", p+1 ); - continue; - } - dc = max( dc, m_pCurSteps[p]->GetDifficulty() ); - } - return dc; -} - -bool GameState::IsEventMode() const -{ - return m_bTemporaryEventMode || PREFSMAN->m_bEventMode; -} - -CoinMode GameState::GetCoinMode() const -{ - return GamePreferences::m_CoinMode; -} - -ThemeMetric DISABLE_PREMIUM_IN_EVENT_MODE("GameState","DisablePremiumInEventMode"); -Premium GameState::GetPremium() const -{ - return DISABLE_PREMIUM_IN_EVENT_MODE ? Premium_Off : g_Premium; -} - -float GameState::GetGoalPercentComplete( PlayerNumber pn ) -{ - const Profile *pProfile = PROFILEMAN->GetProfile(pn); - const StageStats &ssCurrent = STATSMAN->m_CurStageStats; - const PlayerStageStats &pssCurrent = ssCurrent.m_player[pn]; - - float fActual = 0; - float fGoal = 0; - switch( pProfile->m_GoalType ) - { - case GoalType_Calories: - fActual = pssCurrent.m_fCaloriesBurned; - fGoal = (float)pProfile->m_iGoalCalories; - break; - case GoalType_Time: - fActual = ssCurrent.m_fGameplaySeconds; - fGoal = (float)pProfile->m_iGoalSeconds; - break; - case GoalType_None: - return 0; // never complete - default: - FAIL_M(ssprintf("Invalid GoalType: %i", pProfile->m_GoalType)); - } - if( fGoal == 0 ) - return 0; - else - return fActual / fGoal; -} - -bool GameState::PlayerIsUsingModifier( PlayerNumber pn, const RString &sModifier ) -{ - PlayerOptions po = m_pPlayerState[pn]->m_PlayerOptions.GetCurrent(); - SongOptions so = m_SongOptions.GetCurrent(); - po.FromString( sModifier ); - so.FromString( sModifier ); - - return po == m_pPlayerState[pn]->m_PlayerOptions.GetCurrent() && so == m_SongOptions.GetCurrent(); -} - -Profile* GameState::GetEditLocalProfile() -{ - if( m_sEditLocalProfileID.Get().empty() ) - return NULL; - return PROFILEMAN->GetLocalProfile( m_sEditLocalProfileID ); -} - - -PlayerNumber GetNextHumanPlayer( PlayerNumber pn ) -{ - for( enum_add(pn, 1); pn < NUM_PLAYERS; enum_add(pn, 1) ) - if( GAMESTATE->IsHumanPlayer(pn) ) - return pn; - return PLAYER_INVALID; -} - -PlayerNumber GetNextEnabledPlayer( PlayerNumber pn ) -{ - for( enum_add(pn, 1); pn < NUM_PLAYERS; enum_add(pn, 1) ) - if( GAMESTATE->IsPlayerEnabled(pn) ) - return pn; - return PLAYER_INVALID; -} - -PlayerNumber GetNextCpuPlayer( PlayerNumber pn ) -{ - for( enum_add(pn, 1); pn < NUM_PLAYERS; enum_add(pn, 1) ) - if( GAMESTATE->IsCpuPlayer(pn) ) - return pn; - return PLAYER_INVALID; -} - -PlayerNumber GetNextPotentialCpuPlayer( PlayerNumber pn ) -{ - for( enum_add(pn, 1); pn < NUM_PLAYERS; enum_add(pn, 1) ) - if( !GAMESTATE->IsHumanPlayer(pn) ) - return pn; - return PLAYER_INVALID; -} - -MultiPlayer GetNextEnabledMultiPlayer( MultiPlayer mp ) -{ - for( enum_add(mp, 1); mp < NUM_MultiPlayer; enum_add(mp, 1) ) - if( GAMESTATE->IsMultiPlayerEnabled(mp) ) - return mp; - return MultiPlayer_Invalid; -} - - - -// lua start -#include "LuaBinding.h" -#include "Game.h" - -/** @brief Allow Lua to have access to the GameState. */ -class LunaGameState: public Luna -{ -public: - DEFINE_METHOD( IsPlayerEnabled, IsPlayerEnabled(Enum::Check(L, 1)) ) - DEFINE_METHOD( IsHumanPlayer, IsHumanPlayer(Enum::Check(L, 1)) ) - DEFINE_METHOD( GetPlayerDisplayName, GetPlayerDisplayName(Enum::Check(L, 1)) ) - DEFINE_METHOD( GetMasterPlayerNumber, GetMasterPlayerNumber() ) - DEFINE_METHOD( GetMultiplayer, m_bMultiplayer ) - static int SetMultiplayer( T* p, lua_State *L ) - { - p->m_bMultiplayer = BArg(1); - return 0; - } - DEFINE_METHOD( InStepEditor, m_bInStepEditor ); - DEFINE_METHOD( GetNumMultiplayerNoteFields, m_iNumMultiplayerNoteFields ) - DEFINE_METHOD( ShowW1, ShowW1() ) - - static int SetNumMultiplayerNoteFields( T* p, lua_State *L ) - { - p->m_iNumMultiplayerNoteFields = IArg(1); - return 0; - } - static int GetPlayerState( T* p, lua_State *L ) - { - PlayerNumber pn = Enum::Check(L, 1); - p->m_pPlayerState[pn]->PushSelf(L); - return 1; - } - static int GetMultiPlayerState( T* p, lua_State *L ) - { - MultiPlayer mp = Enum::Check(L, 1); - p->m_pMultiPlayerState[mp]->PushSelf(L); - return 1; - } - static int ApplyGameCommand( T* p, lua_State *L ) - { - PlayerNumber pn = PLAYER_INVALID; - if( lua_gettop(L) >= 2 && !lua_isnil(L,2) ) - pn = Enum::Check(L, 2); - p->ApplyGameCommand(SArg(1),pn); - return 0; - } - static int GetCurrentSong( T* p, lua_State *L ) { if(p->m_pCurSong) p->m_pCurSong->PushSelf(L); else lua_pushnil(L); return 1; } - static int SetCurrentSong( T* p, lua_State *L ) - { - if( lua_isnil(L,1) ) { p->m_pCurSong.Set( NULL ); } - else { Song *pS = Luna::check( L, 1, true ); p->m_pCurSong.Set( pS ); } - return 0; - } - static int GetCurrentSteps( T* p, lua_State *L ) - { - PlayerNumber pn = Enum::Check(L, 1); - Steps *pSteps = p->m_pCurSteps[pn]; - if( pSteps ) { pSteps->PushSelf(L); } - else { lua_pushnil(L); } - return 1; - } - static int SetCurrentSteps( T* p, lua_State *L ) - { - PlayerNumber pn = Enum::Check(L, 1); - if( lua_isnil(L,2) ) { p->m_pCurSteps[pn].Set( NULL ); } - else { Steps *pS = Luna::check(L,2); p->m_pCurSteps[pn].Set( pS ); } - ASSERT(p->m_pCurSteps[pn]->m_StepsType == p->m_pCurStyle->m_StepsType); - - // Why Broadcast again? This is double-broadcasting. -Chris - MESSAGEMAN->Broadcast( (MessageID)(Message_CurrentStepsP1Changed+pn) ); - return 0; - } - static int GetCurrentCourse( T* p, lua_State *L ) { if(p->m_pCurCourse) p->m_pCurCourse->PushSelf(L); else lua_pushnil(L); return 1; } - static int SetCurrentCourse( T* p, lua_State *L ) - { - if( lua_isnil(L,1) ) { p->m_pCurCourse.Set( NULL ); } - else { Course *pC = Luna::check(L,1); p->m_pCurCourse.Set( pC ); } - return 0; - } - static int GetCurrentTrail( T* p, lua_State *L ) - { - PlayerNumber pn = Enum::Check(L, 1); - Trail *pTrail = p->m_pCurTrail[pn]; - if( pTrail ) { pTrail->PushSelf(L); } - else { lua_pushnil(L); } - return 1; - } - static int SetCurrentTrail( T* p, lua_State *L ) - { - PlayerNumber pn = Enum::Check(L, 1); - if( lua_isnil(L,2) ) { p->m_pCurTrail[pn].Set( NULL ); } - else { Trail *pS = Luna::check(L,2); p->m_pCurTrail[pn].Set( pS ); } - MESSAGEMAN->Broadcast( (MessageID)(Message_CurrentTrailP1Changed+pn) ); - return 0; - } - static int GetPreferredSong( T* p, lua_State *L ) { if(p->m_pPreferredSong) p->m_pPreferredSong->PushSelf(L); else lua_pushnil(L); return 1; } - static int SetPreferredSong( T* p, lua_State *L ) - { - if( lua_isnil(L,1) ) { p->m_pPreferredSong = NULL; } - else { Song *pS = Luna::check(L,1); p->m_pPreferredSong = pS; } - return 0; - } - static int SetTemporaryEventMode( T* p, lua_State *L ) { p->m_bTemporaryEventMode = BArg(1); return 0; } - static int Env( T* p, lua_State *L ) { p->m_Environment->PushSelf(L); return 1; } - static int GetEditSourceSteps( T* p, lua_State *L ) - { - Steps *pSteps = p->m_pEditSourceSteps; - if( pSteps ) { pSteps->PushSelf(L); } - else { lua_pushnil(L); } - return 1; - } - static int SetPreferredDifficulty( T* p, lua_State *L ) - { - PlayerNumber pn = Enum::Check( L, 1 ); - Difficulty dc = Enum::Check( L, 2 ); - p->m_PreferredDifficulty[pn].Set( dc ); - return 0; - } - DEFINE_METHOD( GetPreferredDifficulty, m_PreferredDifficulty[Enum::Check(L, 1)] ) - DEFINE_METHOD( AnyPlayerHasRankingFeats, AnyPlayerHasRankingFeats() ) - DEFINE_METHOD( IsCourseMode, IsCourseMode() ) - DEFINE_METHOD( IsBattleMode, IsBattleMode() ) - DEFINE_METHOD( IsDemonstration, m_bDemonstrationOrJukebox ) - DEFINE_METHOD( GetPlayMode, m_PlayMode ) - DEFINE_METHOD( GetSortOrder, m_SortOrder ) - DEFINE_METHOD( GetCurrentStageIndex, m_iCurrentStageIndex ) - DEFINE_METHOD( IsGoalComplete, IsGoalComplete(Enum::Check(L, 1)) ) - DEFINE_METHOD( PlayerIsUsingModifier, PlayerIsUsingModifier(Enum::Check(L, 1), SArg(2)) ) - DEFINE_METHOD( GetCourseSongIndex, GetCourseSongIndex() ) - DEFINE_METHOD( GetLoadingCourseSongIndex, GetLoadingCourseSongIndex() ) - DEFINE_METHOD( GetSmallestNumStagesLeftForAnyHumanPlayer, GetSmallestNumStagesLeftForAnyHumanPlayer() ) - DEFINE_METHOD( IsAnExtraStage, IsAnExtraStage() ) - DEFINE_METHOD( IsExtraStage, IsExtraStage() ) - DEFINE_METHOD( IsExtraStage2, IsExtraStage2() ) - DEFINE_METHOD( GetCurrentStage, GetCurrentStage() ) - DEFINE_METHOD( HasEarnedExtraStage, HasEarnedExtraStage() ) - DEFINE_METHOD( GetEarnedExtraStage, GetEarnedExtraStage() ) - DEFINE_METHOD( GetEasiestStepsDifficulty, GetEasiestStepsDifficulty() ) - DEFINE_METHOD( GetHardestStepsDifficulty, GetHardestStepsDifficulty() ) - DEFINE_METHOD( IsEventMode, IsEventMode() ) - DEFINE_METHOD( GetNumPlayersEnabled, GetNumPlayersEnabled() ) - /*DEFINE_METHOD( GetSongBeat, m_Position.m_fSongBeat ) - DEFINE_METHOD( GetSongBeatVisible, m_Position.m_fSongBeatVisible ) - DEFINE_METHOD( GetSongBPS, m_Position.m_fCurBPS ) - DEFINE_METHOD( GetSongFreeze, m_Position.m_bFreeze ) - DEFINE_METHOD( GetSongDelay, m_Position.m_bDelay )*/ - static int GetSongPosition( T* p, lua_State *L ) - { - p->m_Position.PushSelf(L); - return 1; - } - DEFINE_METHOD( GetGameplayLeadIn, m_bGameplayLeadIn ) - DEFINE_METHOD( IsSideJoined, m_bSideIsJoined[Enum::Check(L, 1)] ) - DEFINE_METHOD( PlayersCanJoin, PlayersCanJoin() ) - DEFINE_METHOD( GetNumSidesJoined, GetNumSidesJoined() ) - DEFINE_METHOD( GetCoinMode, GetCoinMode() ) - DEFINE_METHOD( GetPremium, GetPremium() ) - DEFINE_METHOD( GetSongOptionsString, m_SongOptions.GetCurrent().GetString() ) - static int GetSongOptions( T* p, lua_State *L ) - { - ModsLevel m = Enum::Check( L, 1 ); - RString s = p->m_SongOptions.Get(m).GetString(); - LuaHelpers::Push( L, s ); - return 1; - } - static int GetDefaultSongOptions( T* p, lua_State *L ) - { - SongOptions so; - p->GetDefaultSongOptions( so ); - lua_pushstring(L, so.GetString()); - return 1; - } - static int ApplyStageModifiers( T* p, lua_State *L ) - { - p->ApplyStageModifiers( Enum::Check(L, 1), SArg(2) ); - return 0; - } - static int ApplyPreferredModifiers( T* p, lua_State *L ) - { - p->ApplyPreferredModifiers( Enum::Check(L, 1), SArg(2) ); - return 0; - } - static int ClearStageModifiersIllegalForCourse( T* p, lua_State *L ) - { - p->ClearStageModifiersIllegalForCourse(); - return 0; - } - static int SetSongOptions( T* p, lua_State *L ) - { - ModsLevel m = Enum::Check( L, 1 ); - - SongOptions so; - - so.FromString( SArg(2) ); - p->m_SongOptions.Assign( m, so ); - return 0; - } - static int GetStageResult( T* p, lua_State *L ) - { - PlayerNumber pn = Enum::Check(L, 1); - LuaHelpers::Push( L, p->GetStageResult(pn) ); - return 1; - } - static int IsWinner( T* p, lua_State *L ) - { - PlayerNumber pn = Enum::Check(L, 1); - lua_pushboolean(L, p->GetStageResult(pn)==RESULT_WIN); return 1; - } - static int IsDraw( T* p, lua_State *L ) - { - lua_pushboolean(L, p->GetStageResult(PLAYER_1)==RESULT_DRAW); return 1; - } - static int GetCurrentGame( T* p, lua_State *L ) { const_cast(p->GetCurrentGame())->PushSelf( L ); return 1; } - //static int SetCurrentGame( T* p, lua_State *L ) { p->SetCurrentGame( GAMEMAN->StringToGame( SArg(1) ) ); return 0; } - DEFINE_METHOD( GetEditCourseEntryIndex, m_iEditCourseEntryIndex ) - DEFINE_METHOD( GetEditLocalProfileID, m_sEditLocalProfileID.Get() ) - static int GetEditLocalProfile( T* p, lua_State *L ) - { - Profile *pProfile = p->GetEditLocalProfile(); - if( pProfile ) - pProfile->PushSelf(L); - else - lua_pushnil( L ); - return 1; - } - - static int GetCurrentStepsCredits( T* t, lua_State *L ) - { - const Song* pSong = t->m_pCurSong; - if( pSong == NULL ) - return 0; - - // use a vector and not a set so that ordering is maintained - vector vpStepsToShow; - FOREACH_HumanPlayer( p ) - { - const Steps* pSteps = GAMESTATE->m_pCurSteps[p]; - if( pSteps == NULL ) - return 0; - bool bAlreadyAdded = find( vpStepsToShow.begin(), vpStepsToShow.end(), pSteps ) != vpStepsToShow.end(); - if( !bAlreadyAdded ) - vpStepsToShow.push_back( pSteps ); - } - - for( unsigned i=0; im_StepsType, pSteps->GetDifficulty(), CourseType_Invalid ) ); - - lua_pushstring( L, sDifficulty ); - lua_pushstring( L, pSteps->GetDescription() ); - } - - return vpStepsToShow.size()*2; - } - - static int SetPreferredSongGroup( T* p, lua_State *L ) { p->m_sPreferredSongGroup.Set( SArg(1) ); return 0; } - DEFINE_METHOD( GetPreferredSongGroup, m_sPreferredSongGroup.Get() ); - static int GetHumanPlayers( T* p, lua_State *L ) - { - vector vHP; - FOREACH_HumanPlayer( pn ) - vHP.push_back( pn ); - LuaHelpers::CreateTableFromArray( vHP, L ); - return 1; - } - static int GetEnabledPlayers(T* , lua_State *L ) - { - vector vEP; - FOREACH_EnabledPlayer( pn ) - vEP.push_back( pn ); - LuaHelpers::CreateTableFromArray( vEP, L ); - return 1; - } - static int GetCurrentStyle( T* p, lua_State *L ) - { - Style *pStyle = const_cast