diff --git a/src/common/HighScoreManager.cxx b/src/common/HighScoreManager.cxx new file mode 100644 index 000000000..a92ab1ad7 --- /dev/null +++ b/src/common/HighScoreManager.cxx @@ -0,0 +1,197 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2020 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#include "OSystem.hxx" +//#include "Props.hxx" +#include "PropsSet.hxx" +#include "Console.hxx" +#include "Launcher.hxx" +#include "System.hxx" + +#include "HighScoreManager.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +HighScoreManager::HighScoreManager(OSystem& osystem) + : myOSystem(osystem) +{ +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Properties& HighScoreManager::properties(Properties& props) const +{ + if (myOSystem.hasConsole()) + { + props = myOSystem.console().properties(); + } + else + { + const string& md5 = myOSystem.launcher().selectedRomMD5(); + myOSystem.propSet().getMD5(md5, props); + } + return props; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Int32 HighScoreManager::numVariations() +{ + Properties props; + string numVariations = properties(props).get(PropType::Cart_Variations); + + return (numVariations == EmptyString) ? 1 : std::min(stoi(numVariations), 256); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Int32 HighScoreManager::numPlayers() +{ + Properties props; + string numPlayers = properties(props).get(PropType::Cart_Players); + + return (numPlayers == EmptyString) ? 1 : std::min(stoi(numPlayers), 4); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool HighScoreManager::parseAddresses(Int32& variation, Int32& player, Int32 scores[]) +{ + variation = 1; player = 0; scores[0] = 0; + + if (!myOSystem.hasConsole()) + return false; + + System& system = myOSystem.console().system(); + + // Formats (all optional): + // 2, ; score addresses per player + // 1, ; score multiplier + // B, ; score format (BCD, HEX) + // B, ; variation format (BCD, HEX) + // 0, ; add to variation + // Addresses (in hex): + // n*p-times xx, ; score addresses for each player, high to low + // xx, ; variation address (if more than 1 variation) + // xx, ; player address (if more than 1 player) + // TODO: + // - variation bits (Centipede) + // - player bits (Asteroids, Space Invaders) + // - score swaps (Asteroids) + Properties props; + string formats = properties(props).get(PropType::Cart_Formats); + string addresses = properties(props).get(PropType::Cart_Addresses); + char scoreFormat; + char varFormat; + Int16 addr; + Int32 varAdd, numScoreAddr, scoreMult; + + // Since istringstream swallows whitespace, we have to make the + // delimiters be spaces + std::replace(formats.begin(), formats.end(), ',', ' '); + std::replace(formats.begin(), formats.end(), '|', ' '); + std::replace(addresses.begin(), addresses.end(), ',', ' '); + std::replace(addresses.begin(), addresses.end(), '|', ' '); + istringstream addrBuf(addresses); + istringstream formatBuf(formats); + + // 1. retrieve formats + if (!(formatBuf >> numScoreAddr)) + numScoreAddr = 2; + if (!(formatBuf >> scoreMult)) + scoreMult = 1; + if (!(formatBuf >> scoreFormat)) + scoreFormat = 'B'; + if (!(formatBuf >> varFormat)) + varFormat = 'B'; + if (!(formatBuf >> varAdd)) + varAdd = 0; + + // 2. retrieve current scores for all players + for (int i = 0; i < numPlayers(); ++i) + { + Int32 totalScore = 0; + + for (int j = 0; j < numScoreAddr; ++j) + { + Int32 score; + + if (!(addrBuf >> std::hex >> addr)) + return false; + + totalScore *= (scoreFormat == 'B') ? 100 : 256; + + score = system.peek(addr); + if (scoreFormat == 'B') + score = (score >> 4) * 10 + score % 16; + totalScore += score; + } + scores[i] = totalScore * scoreMult; + } + + // 3. retrieve current variation (0..255) + if (numVariations() == 1) + return true; + + if (!(addrBuf >> std::hex >> addr)) + return false; + variation = system.peek(addr); + if (varFormat == 'B') + variation = (variation >> 4) * 10 + variation % 16; + variation += varAdd; + variation = std::min(variation, numVariations()); + + // 4. retrieve current player (0..3) + if (numPlayers() == 1) + return true; + + if (!(addrBuf >> std::hex >> addr)) + return false; + + player = system.peek(addr); + player = std::min(player, numPlayers() - 1); + + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Int32 HighScoreManager::variation() +{ + Int32 variation, player, scores[4]; + + if (parseAddresses(variation, player, scores)) + return variation; + + return -1; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Int32 HighScoreManager::player() +{ + Int32 variation, player, scores[4]; + + if (parseAddresses(variation, player, scores)) + return player; + + return -1; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Int32 HighScoreManager::score() +{ + Int32 variation, player, scores[4]; + + if (parseAddresses(variation, player, scores)) + return scores[std::min(player, Int32(3))]; + + return -1; +} \ No newline at end of file diff --git a/src/common/HighScoreManager.hxx b/src/common/HighScoreManager.hxx new file mode 100644 index 000000000..af2181066 --- /dev/null +++ b/src/common/HighScoreManager.hxx @@ -0,0 +1,56 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2020 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef HIGHSCORE_MANAGER_HXX +#define HIGHSCORE_MANAGER_HXX + +class OSystem; + +class HighScoreManager +{ + +public: + HighScoreManager(OSystem& osystem); + virtual ~HighScoreManager() = default; + + /* + Methods for returning high score related variables + Return -1 if undefined + */ + Int32 numVariations(); + Int32 numPlayers(); + Int32 variation(); + Int32 player(); + Int32 score(); + +private: + Properties& properties(Properties& props) const; + bool parseAddresses(Int32& variation, Int32& player, Int32 scores[]); + +private: + // Reference to the osystem object + OSystem& myOSystem; + +private: + // Following constructors and assignment operators not supported + HighScoreManager() = delete; + HighScoreManager(const HighScoreManager&) = delete; + HighScoreManager(HighScoreManager&&) = delete; + HighScoreManager& operator=(const HighScoreManager&) = delete; + HighScoreManager& operator=(HighScoreManager&&) = delete; +}; +#endif diff --git a/src/common/module.mk b/src/common/module.mk index f7a228189..e1f804b3f 100644 --- a/src/common/module.mk +++ b/src/common/module.mk @@ -6,6 +6,7 @@ MODULE_OBJS := \ src/common/FBSurfaceSDL2.o \ src/common/FrameBufferSDL2.o \ src/common/FSNodeZIP.o \ + src/common/HighScoreManager.o \ src/common/JoyMap.o \ src/common/KeyMap.o \ src/common/Logger.o \ diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index a19af8f9a..5cef206b0 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -1145,152 +1145,6 @@ void Console::stateChanged(EventHandlerState state) // only the CompuMate used to care about state changes } - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Int32 Console::numVariations() -{ - string numVariations = myProperties.get(PropType::Cart_Variations); - - return (numVariations == EmptyString) ? 1 : std::min(stoi(numVariations), 256); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Int32 Console::numPlayers() -{ - string numPlayers = myProperties.get(PropType::Cart_Players); - - return (numPlayers == EmptyString) ? 1 : std::min(stoi(numPlayers), 4); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool Console::parseAdresses(Int32& variation, Int32& player, Int32 scores[]) -{ - // Formats (all optional): - // 2, ; score addresses per player - // 1, ; score multiplier - // B, ; score format (BCD, HEX) - // B, ; variation format (BCD, HEX) - // 0, ; add to variation - // Addresses (in hex): - // n*p-times xx, ; score addresses for each player, high to low - // xx, ; variation address (if more than 1 variation) - // xx, ; player address (if more than 1 player) - // TODO: - // - variation bits (Centipede) - // - player bits (Asteroids) - // - score swaps (Asteroids) - - - string formats = myProperties.get(PropType::Cart_Formats); - string addresses = myProperties.get(PropType::Cart_Addresses); - char scoreFormat; - char varFormat; - Int16 addr; - Int32 varAdd, numScoreAddr, scoreMult; - - // Since istringstream swallows whitespace, we have to make the - // delimiters be spaces - std::replace(formats.begin(), formats.end(), ',', ' '); - std::replace(formats.begin(), formats.end(), '|', ' '); - std::replace(addresses.begin(), addresses.end(), ',', ' '); - std::replace(addresses.begin(), addresses.end(), '|', ' '); - istringstream addrBuf(addresses); - istringstream formatBuf(formats); - - // 1. retrieve formats - if (!(formatBuf >> numScoreAddr)) - numScoreAddr = 2; - if (!(formatBuf >> scoreMult)) - scoreMult = 1; - if (!(formatBuf >> scoreFormat)) - scoreFormat = 'B'; - if (!(formatBuf >> varFormat)) - varFormat = 'B'; - if (!(formatBuf >> varAdd)) - varAdd = 0; - - // 2. retrieve current scores for all players - for (int i = 0; i < numPlayers(); ++i) - { - Int32 totalScore = 0; - - for (int j = 0; j < numScoreAddr; ++j) - { - Int32 score; - - if (!(addrBuf >> std::hex >> addr)) - return false; - - totalScore *= (scoreFormat == 'B') ? 100 : 256; - - score = mySystem->peek(addr); - if (scoreFormat == 'B') - score = (score >> 4) * 10 + score % 16; - totalScore += score; - } - scores[i] = totalScore * scoreMult; - } - - variation = 1; player = 0; - - // 3. retrieve current variation (0..255) - if (numVariations() == 1) - return true; - - if (!(addrBuf >> std::hex >> addr)) - return false; - variation = mySystem->peek(addr); - if (varFormat == 'B') - variation = (variation >> 4) * 10 + variation % 16; - variation += varAdd; - variation = std::min(variation, numVariations()); - - // 4. retrieve current player (0..3) - if (numPlayers() == 1) - return true; - - if (!(addrBuf >> std::hex >> addr)) - return false; - - player = mySystem->peek(addr); - player = std::min(player, numPlayers() - 1); - - return true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Int32 Console::variation() -{ - Int32 variation, player, scores[4]; - - if (parseAdresses(variation, player, scores)) - return variation; - - return -1; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Int32 Console::player() -{ - Int32 variation, player, scores[4]; - - if (parseAdresses(variation, player, scores)) - return player; - - return -1; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Int32 Console::score() -{ - Int32 variation, player, scores[4]; - - if (parseAdresses(variation, player, scores)) - return scores[std::min(player, Int32(3))]; - - return -1; -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PaletteArray Console::ourNTSCPalette = { 0x000000, 0, 0x4a4a4a, 0, 0x6f6f6f, 0, 0x8e8e8e, 0, diff --git a/src/emucore/Console.hxx b/src/emucore/Console.hxx index c46d5780e..eb85025a6 100644 --- a/src/emucore/Console.hxx +++ b/src/emucore/Console.hxx @@ -326,16 +326,6 @@ class Console : public Serializable, public ConsoleIO */ void setTIAProperties(); - /* - Methods for returning high score related variables - Return -1 if undefined - */ - Int32 numVariations(); - Int32 numPlayers(); - Int32 variation(); - Int32 player(); - Int32 score(); - private: /** * Define console timing based on current display format @@ -452,8 +442,6 @@ class Console : public Serializable, public ConsoleIO // The audio settings AudioSettings& myAudioSettings; - bool parseAdresses(Int32& variation, Int32& player, Int32 scores[]); - // Table of RGB values for NTSC, PAL and SECAM static PaletteArray ourNTSCPalette; static PaletteArray ourPALPalette; diff --git a/src/emucore/DefProps.hxx b/src/emucore/DefProps.hxx index 21a21d2ac..fc2d7b4f8 100644 --- a/src/emucore/DefProps.hxx +++ b/src/emucore/DefProps.hxx @@ -25,7 +25,7 @@ regenerated and the application recompiled. */ -static constexpr uInt32 DEF_PROPS_SIZE = 2994; +static constexpr uInt32 DEF_PROPS_SIZE = 2979; static constexpr BSPF::array2D DefProps = {{ { "000509d1ed2b8d30a9d94be1b3b5febb", "Greg Zumwalt", "", "Jungle Jane (2003) (Greg Zumwalt) (Hack)", "Hack of Pitfall!", "Hack", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, @@ -3007,21 +3007,6 @@ static constexpr BSPF::array2D DefProps = {{ { "dca90ea1084a2fdbe300d7178ca1a138", "Imagic, Dennis Koble", "IA3000P", "Trick Shot (1982) (Imagic) (PAL) [a]", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "YES", "", "", "", "", "" }, { "dca941dab5c6f859b71883b13ade9744", "", "", "Hangman Pac-Man Biglist2 (Hack)", "Hack of Hangman", "Hack", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, { "dcba0e33aa4aed67630a4b292386f405", "Retroactive", "", "Qb (V2.08) (Half Speed Version) (NTSC) (2001) (Retroactive)", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "YES", "", "", "", "", "" }, - { "dcc2956c7a39fdbf1e861fc5c595da0d", "M Network - INTV - APh Technological Consulting, David Rolfe", "MT5664", "Frogs and Flies (1982) (M Network)", "AKA Frogs 'n' Flies", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, - { "dcec46a98f45b193f07239611eb878c2", "", "", "Bars and Text Demo (PD)", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, - { "dd08e18cfee87a0e7fc19a684b36e124", "Atari - GCC, Kevin Osborn", "CX2689, CX2689P", "Kangaroo (1983) (Atari) (PAL) [a]", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, - { "dd0cbe5351551a538414fb9e37fc56e8", "Xonox - K-Tel Software - Product Guild, Anthony R. Henderson", "99006, 6220", "Sir Lancelot (1983) (Xonox) (PAL)", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, - { "dd0de0f61af2a2a4878e377b880a3933", "SOLID Corp. (D. Scott Williamson)", "CX2655-013", "Star Castle 2600 (SolidCorp) [013]", "http://starcastle2600.blogspot.com/p/star-castle-2600-story.html", "Homebrew", "", "", "", "", "", "", "", "", "", "", "", "", "", "YES", "", "", "", "", "" }, - { "dd10b5ee37fdbf909423f2998a1f3179", "", "", "Space Instigators (V1.9) (21-10-2002) (CT)", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, - { "dd13a16d14100819f79b1ce3a5bf499c", "Thomas Jentzsch", "", "Missile Control - Atari Mouse Hack v1.15 (PAL) (TJ)", "Uses Atari Mouse Controller", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, - { "dd1422ffd538e2e33b339ebeef4f259d", "Atari, Michael Sierchio", "", "Football Demo (1982) (Atari)", "", "Prototype", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, - { "dd17711a30ad60109c8beace0d4a76e8", "", "", "Karate (Unknown) (PAL)", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, - { "dd1842ba0f3f9d94dccb21eaa0f069b7", "Bit Corporation", "R320", "Defender (32 in 1) (BitCorp) (Hack)", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, - { "dd45e370aceff765f1e72c619efd4399", "Bit Corporation", "PG201", "Sea Monster (1982) (BitCorp)", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, - { "dd4f4e0fbd81762533e39e6f5b55bb3a", "Thomas Jentzsch", "", "Turbo WIP (TJ)", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, - { "dd7598b8bcb81590428900f71b720efb", "Xonox - K-Tel Software - Computer Magic", "99005, 6220, 6250", "Robin Hood (1983) (Xonox) (PAL)", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, - { "dd7884b4f93cab423ac471aa1935e3df", "Atari, Brad Stewart - Sears", "CX2649, 49-75163", "Asteroids (1981) (Atari)", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "YES", "", "2", "66", "2,10,b,h,1", "bd,be,c0,c1,80,c7" }, - { "dd8a2124d4eda200df715c698a6ea887", "Starpath Corporation, Stephen H. Landrum", "AR-4400", "Dragonstomper (3 of 3) (1982) (Starpath)", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "" }, }}; #endif diff --git a/src/emucore/EventHandler.cxx b/src/emucore/EventHandler.cxx index fb251af72..74d0138c0 100644 --- a/src/emucore/EventHandler.cxx +++ b/src/emucore/EventHandler.cxx @@ -35,6 +35,7 @@ #include "StateManager.hxx" #include "RewindManager.hxx" #include "TimerManager.hxx" +#include "HighScoreManager.hxx" #include "Switches.hxx" #include "M6532.hxx" #include "MouseControl.hxx" @@ -721,7 +722,7 @@ void EventHandler::handleEvent(Event::Type event, Int32 value, bool repeated) if (pressed) { ostringstream msg; - msg << "Score: " << myOSystem.console().score(); + msg << "Score: " << myOSystem.highScore().score(); myOSystem.frameBuffer().showMessage(msg.str()); } return; @@ -730,7 +731,7 @@ void EventHandler::handleEvent(Event::Type event, Int32 value, bool repeated) if (pressed) { ostringstream msg; - msg << "Variation: " << myOSystem.console().variation(); + msg << "Variation: " << myOSystem.highScore().variation(); myOSystem.frameBuffer().showMessage(msg.str()); } return; diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 638265910..69a9e9468 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -58,6 +58,7 @@ #include "Random.hxx" #include "StateManager.hxx" #include "TimerManager.hxx" +#include "HighScoreManager.hxx" #include "Version.hxx" #include "TIA.hxx" #include "DispatchResult.hxx" @@ -153,6 +154,7 @@ bool OSystem::create() myStateManager = make_unique(*this); myTimerManager = make_unique(); + myHighScoreManager = make_unique(*this); myAudioSettings = make_unique(*mySettings); // Create the sound object; the sound subsystem isn't actually diff --git a/src/emucore/OSystem.hxx b/src/emucore/OSystem.hxx index 123bc92cb..f29b46702 100644 --- a/src/emucore/OSystem.hxx +++ b/src/emucore/OSystem.hxx @@ -27,6 +27,7 @@ class Random; class Sound; class StateManager; class TimerManager; +class HighScoreManager; class EmulationWorker; class AudioSettings; #ifdef CHEATCODE_SUPPORT @@ -148,6 +149,13 @@ class OSystem */ AudioSettings& audioSettings() { return *myAudioSettings; } + /** + Get the high score manager of the system. + + @return The highscore manager object + */ + HighScoreManager& highScore() const { return *myHighScoreManager; } + /** Get the state manager of the system. @@ -517,6 +525,9 @@ class OSystem // Pointer to the TimerManager object unique_ptr myTimerManager; + // Pointer to the HighScoreManager object + unique_ptr myHighScoreManager; + // Indicates whether ROM launcher was ever opened during this run bool myLauncherUsed{false}; diff --git a/src/gui/GameInfoDialog.cxx b/src/gui/GameInfoDialog.cxx index 9ed16a231..66e8fefe9 100644 --- a/src/gui/GameInfoDialog.cxx +++ b/src/gui/GameInfoDialog.cxx @@ -66,7 +66,7 @@ GameInfoDialog::GameInfoDialog( StaticTextWidget* t; // Set real dimensions - setSize(53 * fontWidth + 8, + setSize(HBORDER * 2 + 53 * fontWidth, 8 * (lineHeight + VGAP) + 1 * (infoLineHeight + VGAP) + VBORDER * 2 + _th + buttonHeight + fontHeight + ifont.getLineHeight() + 20, max_w, max_h); @@ -78,7 +78,7 @@ GameInfoDialog::GameInfoDialog( ////////////////////////////////////////////////////////////////////////////// // 1) Emulation properties - tabID = myTab->addTab("Emulation"); + tabID = myTab->addTab("Emulation", TabWidget::AUTO_WIDTH); ypos = VBORDER; @@ -159,7 +159,7 @@ GameInfoDialog::GameInfoDialog( ////////////////////////////////////////////////////////////////////////////// // 2) Console properties wid.clear(); - tabID = myTab->addTab("Console"); + tabID = myTab->addTab(" Console ", TabWidget::AUTO_WIDTH); xpos = HBORDER; ypos = VBORDER; lwidth = font.getStringWidth(GUI::RIGHT_DIFFICULTY + " "); @@ -202,7 +202,7 @@ GameInfoDialog::GameInfoDialog( ////////////////////////////////////////////////////////////////////////////// // 3) Controller properties wid.clear(); - tabID = myTab->addTab("Controllers"); + tabID = myTab->addTab("Controllers", TabWidget::AUTO_WIDTH); ctrls.clear(); VarList::push_back(ctrls, "Auto-detect", "AUTO"); @@ -304,7 +304,7 @@ GameInfoDialog::GameInfoDialog( ////////////////////////////////////////////////////////////////////////////// // 4) Cartridge properties wid.clear(); - tabID = myTab->addTab("Cartridge"); + tabID = myTab->addTab("Cartridge", TabWidget::AUTO_WIDTH); xpos = HBORDER; ypos = VBORDER; lwidth = font.getStringWidth("Manufacturer "); @@ -348,6 +348,198 @@ GameInfoDialog::GameInfoDialog( // Add items for tab 3 addToFocusList(wid, myTab, tabID); + ////////////////////////////////////////////////////////////////////////////// + // 4) High Scores properties + wid.clear(); + tabID = myTab->addTab("High Scores", TabWidget::AUTO_WIDTH); + + xpos = HBORDER; ypos = VBORDER; + lwidth = font.getStringWidth("Variations "); + + myHighScores = new CheckboxWidget(myTab, font, xpos, ypos + 1, "Enable High Scores", kHiScoresChanged); + + xpos += 20; ypos += lineHeight + VGAP; + + items.clear(); + VarList::push_back(items, "1", "1"); + VarList::push_back(items, "2", "2"); + VarList::push_back(items, "3", "3"); + VarList::push_back(items, "4", "4"); + pwidth = font.getStringWidth("4"); + + myPlayersLabel = new StaticTextWidget(myTab, font, xpos, ypos + 1, lwidth, fontHeight, "Players"); + myPlayers = new PopUpWidget(myTab, font, xpos + lwidth, ypos, pwidth, lineHeight, items, "", 0, kPlayersChanged); + wid.push_back(myPlayers); + + int awidth = font.getStringWidth("FFFF") + 4; + EditableWidget::TextFilter fAddr = [](char c) { + return (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9'); + }; + int rwidth = font.getStringWidth("FF") + 4; + + myPlayersAddressLabel = new StaticTextWidget(myTab, font, myPlayers->getRight() + 16, ypos + 1, "Address "); + myPlayersAddress = new EditTextWidget(myTab, font, myPlayersAddressLabel->getRight(), ypos - 1, awidth, lineHeight); + myPlayersAddress->setTextFilter(fAddr); + wid.push_back(myPlayersAddress); + myPlayersAddressVal = new EditTextWidget(myTab, font, myPlayersAddress->getRight() + 2, ypos - 1, rwidth, lineHeight, "56"); + myPlayersAddressVal->setEditable(false); + + ypos += lineHeight + VGAP; + + fwidth = font.getStringWidth("255") + 5; + myVariationsLabel = new StaticTextWidget(myTab, font, xpos, ypos + 1, lwidth, fontHeight, "Variations"); + myVariations = new EditTextWidget(myTab, font, xpos + lwidth, ypos - 1, fwidth, lineHeight); + wid.push_back(myVariations); + + myVarAddressLabel = new StaticTextWidget(myTab, font, myPlayersAddressLabel->getLeft(), ypos + 1, "Address "); + myVarAddress = new EditTextWidget(myTab, font, myVarAddressLabel->getRight(), ypos - 1, awidth, lineHeight); + myVarAddress->setTextFilter(fAddr); + wid.push_back(myVarAddress); + myVarAddressVal = new EditTextWidget(myTab, font, myVarAddress->getRight() + 2, ypos - 1, rwidth, lineHeight, "56"); + myVarAddressVal->setEditable(false); + + myVarFormat = new CheckboxWidget(myTab, font, myVarAddressVal->getRight() + 16, ypos + 1, "BCD", kVarFormatChanged); + wid.push_back(myVarFormat); + + myVarZeroBased = new CheckboxWidget(myTab, font, myVarFormat->getRight() + 16, ypos + 1, "0-based", kVar0BasedChanged); + wid.push_back(myVarZeroBased); + + ypos += lineHeight + VGAP; + + items.clear(); + VarList::push_back(items, "1", "1"); + VarList::push_back(items, "2", "2"); + VarList::push_back(items, "3", "3"); + VarList::push_back(items, "4", "4"); + VarList::push_back(items, "5", "5"); + VarList::push_back(items, "6", "6"); + + myScoresLabel = new StaticTextWidget(myTab, font, xpos, ypos + 1, lwidth, fontHeight, "Scores"); + + xpos += 20; ypos += lineHeight + VGAP; + + myScoreDigitsLabel = new StaticTextWidget(myTab, font, xpos, ypos + 1, "Digits "); + myScoreDigits = new PopUpWidget(myTab, font, myScoreDigitsLabel->getRight(), ypos, pwidth, lineHeight, + items, "", 0, kScoreDigitsChanged); + wid.push_back(myScoreDigits); + + items.clear(); + VarList::push_back(items, "1", "1"); + VarList::push_back(items, "10", "10"); + VarList::push_back(items, "100", "100"); + pwidth = font.getStringWidth("100"); + + myScoreMultLabel = new StaticTextWidget(myTab, font, myScoreDigits->getRight() + 20, ypos + 1, "Multiplier "); + myScoreMult = new PopUpWidget(myTab, font, myScoreMultLabel->getRight(), ypos, pwidth, lineHeight, + items, "", 0, kScoreMultChanged); + wid.push_back(myScoreMult); + + myScoreFormat = new CheckboxWidget(myTab, font, myVarFormat->getLeft(), ypos + 1, "BCD", kScoreFormatChanged); + wid.push_back(myScoreFormat); + + ypos += lineHeight + VGAP; + + myP1AddressLabel = new StaticTextWidget(myTab, font, xpos, ypos + 1, "P1 Addresses "); + myP1Address0 = new EditTextWidget(myTab, font, myP1AddressLabel->getRight(), ypos - 1, awidth, lineHeight); + myP1Address0->setTextFilter(fAddr); + wid.push_back(myP1Address0); + myP1Address0Val = new EditTextWidget(myTab, font, myP1Address0->getRight() + 2, ypos - 1, rwidth, lineHeight, "12"); + myP1Address0Val->setEditable(false); + + myP1Address1 = new EditTextWidget(myTab, font, myP1Address0Val->getRight() + 16, ypos - 1, awidth, lineHeight); + myP1Address1->setTextFilter(fAddr); + wid.push_back(myP1Address1); + myP1Address1Val = new EditTextWidget(myTab, font, myP1Address1->getRight() + 2, ypos - 1, rwidth, lineHeight, "34"); + myP1Address1Val->setEditable(false); + + myP1Address2 = new EditTextWidget(myTab, font, myP1Address1Val->getRight() + 16, ypos - 1, awidth, lineHeight); + myP1Address2->setTextFilter(fAddr); + wid.push_back(myP1Address2); + myP1Address2Val = new EditTextWidget(myTab, font, myP1Address2->getRight() + 2, ypos - 1, rwidth, lineHeight, "56"); + myP1Address2Val->setEditable(false); + + ypos += lineHeight + VGAP; + + myP2AddressLabel = new StaticTextWidget(myTab, font, xpos, ypos + 1, "P2 Addresses "); + myP2Address0 = new EditTextWidget(myTab, font, myP2AddressLabel->getRight(), ypos - 1, awidth, lineHeight); + myP2Address0->setTextFilter(fAddr); + wid.push_back(myP2Address0); + myP2Address0Val = new EditTextWidget(myTab, font, myP2Address0->getRight() + 2, ypos - 1, rwidth, lineHeight, "12"); + myP2Address0Val->setEditable(false); + + myP2Address1 = new EditTextWidget(myTab, font, myP2Address0Val->getRight() + 16, ypos - 1, awidth, lineHeight); + myP2Address1->setTextFilter(fAddr); + wid.push_back(myP2Address1); + myP2Address1Val = new EditTextWidget(myTab, font, myP2Address1->getRight() + 2, ypos - 1, rwidth, lineHeight, "34"); + myP2Address1Val->setEditable(false); + + myP2Address2 = new EditTextWidget(myTab, font, myP2Address1Val->getRight() + 16, ypos - 1, awidth, lineHeight); + myP2Address2->setTextFilter(fAddr); + wid.push_back(myP2Address2); + myP2Address2Val = new EditTextWidget(myTab, font, myP2Address2->getRight() + 2, ypos - 1, rwidth, lineHeight, "56"); + myP2Address2Val->setEditable(false); + + ypos += lineHeight + VGAP; + + myP3AddressLabel = new StaticTextWidget(myTab, font, xpos, ypos + 1, "P3 Addresses "); + myP3Address0 = new EditTextWidget(myTab, font, myP3AddressLabel->getRight(), ypos - 1, awidth, lineHeight); + myP3Address0->setTextFilter(fAddr); + wid.push_back(myP3Address0); + myP3Address0Val = new EditTextWidget(myTab, font, myP3Address0->getRight() + 2, ypos - 1, rwidth, lineHeight, ""); + myP3Address0Val->setEditable(false); + + myP3Address1 = new EditTextWidget(myTab, font, myP3Address0Val->getRight() + 16, ypos - 1, awidth, lineHeight); + myP3Address1->setTextFilter(fAddr); + wid.push_back(myP3Address1); + myP3Address1Val = new EditTextWidget(myTab, font, myP3Address1->getRight() + 2, ypos - 1, rwidth, lineHeight, ""); + myP3Address1Val->setEditable(false); + + myP3Address2 = new EditTextWidget(myTab, font, myP3Address1Val->getRight() + 16, ypos - 1, awidth, lineHeight); + myP3Address2->setTextFilter(fAddr); + wid.push_back(myP3Address2); + myP3Address2Val = new EditTextWidget(myTab, font, myP3Address2->getRight() + 2, ypos - 1, rwidth, lineHeight, ""); + myP3Address2Val->setEditable(false); + + ypos += lineHeight + VGAP; + + myP4AddressLabel = new StaticTextWidget(myTab, font, xpos, ypos + 1, "P4 Addresses "); + myP4Address0 = new EditTextWidget(myTab, font, myP4AddressLabel->getRight(), ypos - 1, awidth, lineHeight); + myP4Address0->setTextFilter(fAddr); + wid.push_back(myP4Address0); + myP4Address0Val = new EditTextWidget(myTab, font, myP4Address0->getRight() + 2, ypos - 1, rwidth, lineHeight, ""); + myP4Address0Val->setEditable(false); + + myP4Address1 = new EditTextWidget(myTab, font, myP4Address0Val->getRight() + 16, ypos - 1, awidth, lineHeight); + myP4Address1->setTextFilter(fAddr); + wid.push_back(myP4Address1); + myP4Address1Val = new EditTextWidget(myTab, font, myP4Address1->getRight() + 2, ypos - 1, rwidth, lineHeight, ""); + myP4Address1Val->setEditable(false); + + myP4Address2 = new EditTextWidget(myTab, font, myP4Address1Val->getRight() + 16, ypos - 1, awidth, lineHeight); + myP4Address2->setTextFilter(fAddr); + wid.push_back(myP4Address2); + myP4Address2Val = new EditTextWidget(myTab, font, myP4Address2->getRight() + 2, ypos - 1, rwidth, lineHeight, ""); + myP4Address2Val->setEditable(false); + + + /*ypos += lineHeight + VGAP; + fwidth = _w - lwidth - HBORDER * 2 - 22; + new StaticTextWidget(myTab, font, xpos, ypos + 1, lwidth, fontHeight, "Formats"); + myFormats = new EditTextWidget(myTab, font, xpos + lwidth, ypos - 1, + fwidth, lineHeight); + wid.push_back(myVariations); + + ypos += lineHeight + VGAP; + new StaticTextWidget(myTab, font, xpos, ypos + 1, lwidth, fontHeight, "Addresses"); + myAddresses = new EditTextWidget(myTab, font, xpos + lwidth, ypos - 1, + fwidth, lineHeight); + wid.push_back(myVariations);*/ + + + // Add items for tab 3 + addToFocusList(wid, myTab, tabID); + + ////////////////////////////////////////////////////////////////////////////// // Activate the first tab myTab->setActiveTab(0); @@ -374,6 +566,7 @@ void GameInfoDialog::loadConfig() loadConsoleProperties(myGameProperties); loadControllerProperties(myGameProperties); loadCartridgeProperties(myGameProperties); + loadHighScoresProperties(myGameProperties); myTab->loadConfig(); } @@ -540,6 +733,20 @@ void GameInfoDialog::loadCartridgeProperties(const Properties& props) myNote->setText(props.get(PropType::Cart_Note)); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void GameInfoDialog::loadHighScoresProperties(const Properties& props) +{ + //bool enable = instance().hasConsole() && instance().console().par + + myHighScores->setState(true); + myPlayers->setSelected(props.get(PropType::Cart_Players)); + myVariations->setText(props.get(PropType::Cart_Variations)); + + + + handleHighScoresWidgets(); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void GameInfoDialog::saveConfig() { @@ -635,6 +842,10 @@ void GameInfoDialog::setDefaults() loadCartridgeProperties(defaultProperties); break; + case 4: // High Scores properties + loadHighScoresProperties(defaultProperties); + break; + default: // make the compiler happy break; } @@ -745,6 +956,93 @@ void GameInfoDialog::eraseEEPROM() } } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void GameInfoDialog::handleHighScoresWidgets() +{ + bool enable = myHighScores->getState(); + int players = myPlayers->getSelected() + 1; + bool enablePlayers = enable && players > 1; + bool enableVars = enable && myVariations->getText() > "1"; + int numAddr = (myScoreDigits->getSelected() - myScoreMult->getSelected() + 2) / 2; + bool enable1 = enable && numAddr >= 2; + bool enable2 = enable && numAddr >= 3; + + myPlayersLabel->setEnabled(enable); + myPlayers->setEnabled(enable); + myPlayersAddressLabel->setEnabled(enablePlayers); + myPlayersAddress->setEnabled(enablePlayers); + myPlayersAddress->setEditable(enablePlayers); + myPlayersAddressVal->setEnabled(enablePlayers); + + myVariationsLabel->setEnabled(enable); + myVariations->setEnabled(enable); + myVariations->setEditable(enable); + myVarAddressLabel->setEnabled(enableVars); + myVarAddress->setEnabled(enableVars); + myVarAddress->setEditable(enableVars); + myVarAddressVal->setEnabled(enableVars); + myVarFormat->setEnabled(enableVars); + myVarZeroBased->setEnabled(enableVars); + + myScoresLabel->setEnabled(enable); + myScoreDigitsLabel->setEnabled(enable); + myScoreDigits->setEnabled(enable); + myScoreFormat->setEnabled(enable); + myScoreMultLabel->setEnabled(enable); + myScoreMult->setEnabled(enable); + + myP1AddressLabel->setEnabled(enable); + myP1Address0->setEnabled(enable); + myP1Address0->setEditable(enable); + myP1Address0Val->setEnabled(enable); + myP1Address1->setEnabled(enable1); + myP1Address1->setEditable(enable1); + myP1Address1Val->setEnabled(enable1); + myP1Address2->setEnabled(enable2); + myP1Address2->setEditable(enable2); + myP1Address2Val->setEnabled(enable2); + + enable &= players >= 2; + enable1 &= enable; enable2 &= enable; + myP2AddressLabel->setEnabled(enable); + myP2Address0->setEnabled(enable); + myP2Address0->setEditable(enable); + myP2Address0Val->setEnabled(enable); + myP2Address1->setEnabled(enable1); + myP2Address1->setEditable(enable1); + myP2Address1Val->setEnabled(enable1); + myP2Address2->setEnabled(enable2); + myP2Address2->setEditable(enable2); + myP2Address2Val->setEnabled(enable2); + + enable &= players >= 3; + enable1 &= enable; enable2 &= enable; + myP3AddressLabel->setEnabled(enable); + myP3Address0->setEnabled(enable); + myP3Address0->setEditable(enable); + myP3Address0Val->setEnabled(enable); + myP3Address1->setEnabled(enable1); + myP3Address1->setEditable(enable1); + myP3Address1Val->setEnabled(enable1); + myP3Address2->setEnabled(enable2); + myP3Address2->setEditable(enable2); + myP3Address2Val->setEnabled(enable2); + + enable &= players >= 4; + enable1 &= enable; enable2 &= enable; + myP4AddressLabel->setEnabled(enable); + myP4Address0->setEnabled(enable); + myP4Address0->setEditable(enable); + myP4Address0Val->setEnabled(enable); + myP4Address1->setEnabled(enable1); + myP4Address1->setEditable(enable1); + myP4Address1->setEditable(enable1); + myP4Address1Val->setEnabled(enable1); + myP4Address2->setEnabled(enable2); + myP4Address2->setEditable(enable2); + myP4Address2Val->setEnabled(enable2); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void GameInfoDialog::handleCommand(CommandSender* sender, int cmd, int data, int id) @@ -812,6 +1110,13 @@ void GameInfoDialog::handleCommand(CommandSender* sender, int cmd, break; } + case kHiScoresChanged: + case kPlayersChanged: + case kScoreDigitsChanged: + case kScoreMultChanged: + handleHighScoresWidgets(); + break; + default: Dialog::handleCommand(sender, cmd, data, 0); break; diff --git a/src/gui/GameInfoDialog.hxx b/src/gui/GameInfoDialog.hxx index 8704a13d3..bf32707aa 100644 --- a/src/gui/GameInfoDialog.hxx +++ b/src/gui/GameInfoDialog.hxx @@ -53,9 +53,12 @@ class GameInfoDialog : public Dialog, public CommandSender void loadControllerProperties(const Properties& props); // load the properties for the 'Cartridge' tab void loadCartridgeProperties(const Properties& props); + // load the properties for the 'High Scores' tab + void loadHighScoresProperties(const Properties& props); void updateControllerStates(); void eraseEEPROM(); + void handleHighScoresWidgets(); private: TabWidget* myTab{nullptr}; @@ -102,6 +105,57 @@ class GameInfoDialog : public Dialog, public CommandSender EditTextWidget* myRarity{nullptr}; EditTextWidget* myNote{nullptr}; + // High Scores properties + CheckboxWidget* myHighScores{nullptr}; + StaticTextWidget* myPlayersLabel{ nullptr }; + PopUpWidget* myPlayers{nullptr}; + StaticTextWidget* myPlayersAddressLabel{ nullptr }; + EditTextWidget* myPlayersAddress{ nullptr }; + EditTextWidget* myPlayersAddressVal{ nullptr }; + + StaticTextWidget* myVariationsLabel{ nullptr }; + EditTextWidget* myVariations{nullptr}; + StaticTextWidget* myVarAddressLabel{ nullptr }; + EditTextWidget* myVarAddress{ nullptr }; + EditTextWidget* myVarAddressVal{ nullptr }; + CheckboxWidget* myVarFormat{ nullptr }; + CheckboxWidget* myVarZeroBased{ nullptr }; + + StaticTextWidget* myScoresLabel{ nullptr }; + StaticTextWidget* myScoreDigitsLabel{ nullptr }; + PopUpWidget* myScoreDigits{nullptr}; + StaticTextWidget* myScoreMultLabel{ nullptr }; + PopUpWidget* myScoreMult{nullptr}; + CheckboxWidget* myScoreFormat{nullptr}; + StaticTextWidget* myP1AddressLabel{nullptr}; + EditTextWidget* myP1Address0{nullptr}; + EditTextWidget* myP1Address0Val{ nullptr }; + EditTextWidget* myP1Address1{nullptr}; + EditTextWidget* myP1Address1Val{ nullptr }; + EditTextWidget* myP1Address2{nullptr}; + EditTextWidget* myP1Address2Val{ nullptr }; + StaticTextWidget* myP2AddressLabel{nullptr}; + EditTextWidget* myP2Address0{nullptr}; + EditTextWidget* myP2Address0Val{ nullptr }; + EditTextWidget* myP2Address1{nullptr}; + EditTextWidget* myP2Address1Val{ nullptr }; + EditTextWidget* myP2Address2{nullptr}; + EditTextWidget* myP2Address2Val{ nullptr }; + StaticTextWidget* myP3AddressLabel{nullptr}; + EditTextWidget* myP3Address0{nullptr}; + EditTextWidget* myP3Address0Val{ nullptr }; + EditTextWidget* myP3Address1{nullptr}; + EditTextWidget* myP3Address1Val{ nullptr }; + EditTextWidget* myP3Address2{nullptr}; + EditTextWidget* myP3Address2Val{ nullptr }; + StaticTextWidget* myP4AddressLabel{nullptr}; + EditTextWidget* myP4Address0{nullptr}; + EditTextWidget* myP4Address0Val{ nullptr }; + EditTextWidget* myP4Address1{nullptr}; + EditTextWidget* myP4Address1Val{ nullptr }; + EditTextWidget* myP4Address2{nullptr}; + EditTextWidget* myP4Address2Val{ nullptr }; + enum { kVCenterChanged = 'Vcch', kPhosphorChanged = 'PPch', @@ -110,6 +164,13 @@ class GameInfoDialog : public Dialog, public CommandSender kRightCChanged = 'RCch', kMCtrlChanged = 'MCch', kEEButtonPressed = 'EEgb', + kHiScoresChanged = 'HSch', + kPlayersChanged = 'Plch', + kVar0BasedChanged = 'VZch', + kVarFormatChanged = 'VFch', + kScoreDigitsChanged = 'SDch', + kScoreMultChanged = 'SMch', + kScoreFormatChanged = 'SFch' }; // Game properties for currently loaded ROM diff --git a/src/windows/Stella.vcxproj b/src/windows/Stella.vcxproj index b3aa05584..91b64d36b 100644 --- a/src/windows/Stella.vcxproj +++ b/src/windows/Stella.vcxproj @@ -382,6 +382,7 @@ + @@ -1088,6 +1089,7 @@ + diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters index b14161f0f..4535ca61d 100644 --- a/src/windows/Stella.vcxproj.filters +++ b/src/windows/Stella.vcxproj.filters @@ -1005,6 +1005,9 @@ Source Files\gui + + Source Files + @@ -2054,6 +2057,9 @@ Header Files\gui + + Header Files +