From f836f2bdaf4e793041bccdd9eae965d554e792cb Mon Sep 17 00:00:00 2001 From: Chaim Gingold Date: Fri, 19 May 2017 16:59:03 -0700 Subject: [PATCH] Per game settings; persist for anim and pinball --- assets/config/AnimWorld.xml | 2 +- assets/config/PinballWorld.xml | 2 +- src/App/TablaApp.cpp | 81 ++++++++++++++++++++---- src/App/TablaApp.h | 7 +- src/Utilities/xml.h | 9 ++- src/Vision/LightLink.cpp | 5 -- src/Worlds/AnimWorld/AnimWorld.cpp | 28 +++++++- src/Worlds/AnimWorld/AnimWorld.h | 7 +- src/Worlds/GameWorld/GameWorld.h | 28 ++++++-- src/Worlds/PinballWorld/PinballWorld.cpp | 28 +++++++- src/Worlds/PinballWorld/PinballWorld.h | 6 ++ 11 files changed, 169 insertions(+), 34 deletions(-) diff --git a/assets/config/AnimWorld.xml b/assets/config/AnimWorld.xml index 9d62601..47dbda3 100644 --- a/assets/config/AnimWorld.xml +++ b/assets/config/AnimWorld.xml @@ -35,7 +35,7 @@ - -1 0 + -1 0 85 20 50 diff --git a/assets/config/PinballWorld.xml b/assets/config/PinballWorld.xml index ab6528d..cf54f53 100644 --- a/assets/config/PinballWorld.xml +++ b/assets/config/PinballWorld.xml @@ -42,7 +42,7 @@ 1 - -1 0 + -1 0 .05 4 diff --git a/src/App/TablaApp.cpp b/src/App/TablaApp.cpp index f748de6..aa48755 100644 --- a/src/App/TablaApp.cpp +++ b/src/App/TablaApp.cpp @@ -50,16 +50,26 @@ fs::path TablaApp::getDocsPath() const return getDocumentsDirectory() / kDocumentsDirectoryName ; } +fs::path TablaApp::getUserGameSettingsPath() const +{ + return getDocsPath() / "game-settings"; +} + fs::path TablaApp::getUserLightLinkFilePath() const { return getDocsPath() / "LightLink.xml" ; } -fs::path TablaApp::getUserSettingsFilePath() const +fs::path TablaApp::getUserSettingsFilePathForApp() const { return getDocsPath() / "settings.xml" ; } +fs::path TablaApp::getUserSettingsFilePathForGame( string game ) const +{ + return getUserGameSettingsPath() / (game + ".xml") ; +} + void TablaApp::setup() { cout << getAppPath() << endl; @@ -73,8 +83,9 @@ void TablaApp::setup() // enumerate hardware enumerateDisplaysAndCamerasToConsole(); - // make docs folder if needed + // make docs folders if needed if ( !fs::exists(getDocsPath()) ) fs::create_directory(getDocsPath()); + if ( !fs::exists(getUserGameSettingsPath()) ) fs::create_directory(getUserGameSettingsPath()); // app config mFileWatch.loadXml( hotloadableAssetPath("config") / "app.xml", [this]( XmlTree xml ) { @@ -101,7 +112,7 @@ void TablaApp::setup() // user settings mPipelineStageSelection = "undistorted"; // default (don't want it in header) - mFileWatch.loadXml( getUserSettingsFilePath(), [this]( XmlTree xml ) + mFileWatch.loadXml( getUserSettingsFilePathForApp(), [this]( XmlTree xml ) { if ( xml.hasChild("settings") ) { loadUserSettingsFromXml(xml.getChild("settings")); @@ -541,20 +552,47 @@ void TablaApp::loadGame( string systemName ) cout << "loadGame: " << mGameWorld->getSystemName() << endl; // get config xml - fs::path xmlConfigPath = getXmlConfigPathForGame(mGameWorld->getSystemName()) ; - - mFileWatch.loadXml( xmlConfigPath, [xmlConfigPath,this]( XmlTree xml ) { - // if we had already loaded this once, we stomp that old lambda and force a reload now. + fs::path xmlConfigPath = getXmlConfigPathForGame(mGameWorld->getSystemName()) ; - // why conditional? - // make sure we aren't hotloading xml from game we aren't running anymore - if ( xmlConfigPath == getXmlConfigPathForGame(mGameWorld->getSystemName()) ) + mFileWatch.loadXml( xmlConfigPath, [xmlConfigPath,this]( XmlTree xml ) + { + // if we had already loaded this once, we stomp that old lambda and force a reload now. + + // why conditional? + // make sure we aren't hotloading xml from game we aren't running anymore + if ( xmlConfigPath == getXmlConfigPathForGame(mGameWorld->getSystemName()) ) + { + // set params from xml + setGameWorldXmlParams(xml); + } + }); + } + + // get settings xml + { + fs::path xmlSettingsPath = getUserSettingsFilePathForGame(mGameWorld->getSystemName()) ; + + // init settings? + if ( !fs::exists(xmlSettingsPath) ) { - // set params from xml - setGameWorldXmlParams(xml); + mGameWorld->initSettings(); + mGameWorld->setAreUserSettingsDirty(); // so we save them... } - }); + + // load and watch... + mFileWatch.loadXml( xmlSettingsPath, [xmlSettingsPath,this]( XmlTree xml ) + { + // if we had already loaded this once, we stomp that old lambda and force a reload now. + + // why conditional? + // make sure we aren't hotloading xml from game we aren't running anymore + if ( xmlSettingsPath == getUserSettingsFilePathForGame(mGameWorld->getSystemName()) ) + { + mGameWorld->setUserSettings( xml ); + } + }); + } // set world bounds mGameWorld->setWorldBoundsPoly( getWorldBoundsPoly() ); @@ -767,6 +805,7 @@ void TablaApp::update() mGameWorld->setMousePosInWorld(getMousePosInWorld()); // TODO: figure out if it actually contains it... mGameWorld->update(); mGameWorld->prepareToDraw(); + maybeSaveGameWorldSettings(); } if (mAVClacker>0.f) mAVClacker = max(0.f,mAVClacker-.1f); @@ -1211,7 +1250,21 @@ XmlTree TablaApp::getUserSettingsXml() const void TablaApp::saveUserSettings() { XmlTree t = getUserSettingsXml(); - t.write( writeFile(getUserSettingsFilePath()) ); + t.write( writeFile(getUserSettingsFilePathForApp()) ); +} + +void TablaApp::maybeSaveGameWorldSettings() const +{ + if ( mGameWorld && mGameWorld->getAreUserSettingsDirty() ) + { + fs::path path = getUserSettingsFilePathForGame(mGameWorld->getSystemName()); + + XmlTree settingsXml = mGameWorld->getUserSettings(); + + settingsXml.write( writeFile(path) ); + + mGameWorld->setAreUserSettingsDirty(false); + } } void TablaApp::saveCameraImageToDisk() diff --git a/src/App/TablaApp.h b/src/App/TablaApp.h index 062cf0d..0a957ad 100644 --- a/src/App/TablaApp.h +++ b/src/App/TablaApp.h @@ -199,8 +199,10 @@ class TablaApp : public App { // paths fs::path getDocsPath() const; + fs::path getUserGameSettingsPath() const; fs::path getUserLightLinkFilePath() const; - fs::path getUserSettingsFilePath() const; + fs::path getUserSettingsFilePathForApp() const; + fs::path getUserSettingsFilePathForGame( string ) const; string getOverloadedAssetPath() const; // calculates it (from arguments, env vars) string mOverloadedAssetPath; // => is stored here @@ -213,7 +215,8 @@ class TablaApp : public App { void loadUserSettingsFromXml( XmlTree ); XmlTree getUserSettingsXml() const; void saveUserSettings(); - + void maybeSaveGameWorldSettings() const; + // misc void saveCameraImageToDisk(); diff --git a/src/Utilities/xml.h b/src/Utilities/xml.h index 105a889..da05cdf 100644 --- a/src/Utilities/xml.h +++ b/src/Utilities/xml.h @@ -206,11 +206,16 @@ inline bool getVec2sFromXml( const XmlTree &xml, string name, vec2 var[], int le else return false; } +inline string vecToString( vec2 v ) +{ + return toString(v.x) + " " + toString(v.y); +} + inline XmlTree vec2sToXml( string name, vector vs ) { XmlTree xml(name,""); for( auto v : vs ) { - xml.push_back( XmlTree("v", toString(v.x) + " " + toString(v.y) ) ); + xml.push_back( XmlTree("v", vecToString(v) ) ); } return xml; } @@ -220,7 +225,7 @@ inline XmlTree vec2sToXml( string name, const vec2* vs, int n ) XmlTree xml(name,""); for ( int i=0; i AnimWorld::getOrientationVecs() const { map m; @@ -78,6 +102,8 @@ void AnimWorld::setOrientationVec ( string name, vec2 value ) { mTimeVec = value; } + + setAreUserSettingsDirty(); } void AnimWorld::printAnims( const AnimSeqMap& as ) diff --git a/src/Worlds/AnimWorld/AnimWorld.h b/src/Worlds/AnimWorld/AnimWorld.h index 1cbb704..98f5c5d 100644 --- a/src/Worlds/AnimWorld/AnimWorld.h +++ b/src/Worlds/AnimWorld/AnimWorld.h @@ -94,9 +94,13 @@ class AnimWorld : public GameWorld string getSystemName() const override { return "AnimWorld"; } void setParams( XmlTree ) override; - void updateVision( const Vision::Output&, Pipeline& ) override; + + void initSettings() override; + XmlTree getUserSettings() const override; + void setUserSettings( XmlTree ) override; void update() override; + void updateVision( const Vision::Output&, Pipeline& ) override; void draw( DrawType ) override; void drawMouseDebugInfo( vec2 ) override; @@ -122,6 +126,7 @@ class AnimWorld : public GameWorld Rectf getFrameBBoxInLocalSpace( const Frame& ) const; float mAnimTime; // our local animation time, in case we want to pause, etc... + vec2 mDefaultTimeVec=vec2(1,0); vec2 mTimeVec=vec2(1,0); float mWorldUnitsToSeconds=10.f; float mAnimLengthQuantizeToSec=.5f; diff --git a/src/Worlds/GameWorld/GameWorld.h b/src/Worlds/GameWorld/GameWorld.h index a0cb86a..24ed40c 100644 --- a/src/Worlds/GameWorld/GameWorld.h +++ b/src/Worlds/GameWorld/GameWorld.h @@ -23,29 +23,44 @@ class GameWorld public: virtual ~GameWorld() {} // make sure children can overload destructor + + // names used for loading associated files and showing to user virtual string getSystemName() const { return "GameWorld"; } // for xml param block name virtual string getUserName() const { return getSystemName(); } + + // load hard coded configuration/tuning xml data + virtual void setParams( XmlTree ){} + void setVisionParams( Vision::Params p ) { mVisionParams=p; } Vision::Params getVisionParams() const { return mVisionParams; } // app automatically loads xml params for GameWorld and sets them. // it then gets them when they are needed. - virtual void setParams( XmlTree ){} - virtual void updateVision( const Vision::Output&, Pipeline& ){} // probably lower freq than update() + // user settings + virtual void initSettings() {} // called if there is no settings file on disk + virtual XmlTree getUserSettings() const { return XmlTree(); } + virtual void setUserSettings( XmlTree ) {} + // tell system that settings changed so it can save them; (and it can tell us it saved them) + void setAreUserSettingsDirty( bool dirty=true ) { mUserSettingsDirty=dirty; } + bool getAreUserSettingsDirty() { return mUserSettingsDirty; } + + // world bounds + orientation void setWorldBoundsPoly( PolyLine2 ); // only calls didChange if it is different PolyLine2 getWorldBoundsPoly() const { return mWorldBoundsPoly; } virtual void worldBoundsPolyDidChange(){} - virtual map getOrientationVecs() const { return map(); } - virtual void setOrientationVec ( string, vec2 ) {} - vec2 getRandomPointInWorldBoundsPoly() const; // a little lamely special case; // might be more useful to have something like random point on contour, // and then pick whether you want a hole or not hole. // randomPointOnContour() + virtual map getOrientationVecs() const { return map(); } + virtual void setOrientationVec ( string, vec2 ) {} + + + // event handling enum class DrawType { Projector, @@ -56,6 +71,7 @@ class GameWorld virtual void gameWillLoad(){} virtual void update(){} // called each frame + virtual void updateVision( const Vision::Output&, Pipeline& ){} // probably lower freq than update() virtual void prepareToDraw() {} // called once per frame, before all draw calls. virtual void draw( DrawType ){} // might be called many times per frame @@ -77,7 +93,7 @@ class GameWorld Vision::Params mVisionParams; PolyLine2 mWorldBoundsPoly; vec2 mMousePosInWorld; - + bool mUserSettingsDirty=false; }; typedef std::shared_ptr GameWorldRef; diff --git a/src/Worlds/PinballWorld/PinballWorld.cpp b/src/Worlds/PinballWorld/PinballWorld.cpp index 5b12949..1815191 100644 --- a/src/Worlds/PinballWorld/PinballWorld.cpp +++ b/src/Worlds/PinballWorld/PinballWorld.cpp @@ -71,7 +71,7 @@ void PinballWorld::setParams( XmlTree xml ) getXml(xml, "MaxScreenShake", mMaxScreenShake); // playfield - getXml(xml, "UpVec", mUpVec ); + getXml(xml, "DefaultUpVec", mDefaultUpVec ); getXml(xml, "Gravity", mGravity ); getXml(xml, "BallReclaimAreaHeight", mBallReclaimAreaHeight ); @@ -81,6 +81,30 @@ void PinballWorld::setParams( XmlTree xml ) } } +void PinballWorld::initSettings() +{ + mUpVec = mDefaultUpVec; +} + +XmlTree PinballWorld::getUserSettings() const +{ + XmlTree xml("settings",""); + + xml.push_back( XmlTree("UpVec", vecToString(mUpVec)) ); + + return xml; +} + +void PinballWorld::setUserSettings( XmlTree settingsXml ) +{ + if ( settingsXml.hasChild("settings") ) + { + XmlTree xml = settingsXml.getChild("settings"); + + getXml(xml, "UpVec", mUpVec ); + } +} + Rectf PinballWorld::toPlayfieldBoundingBox ( const PolyLine2 &poly ) const { std::vector pts = poly.getPoints(); @@ -494,6 +518,8 @@ void PinballWorld::setOrientationVec ( string name, vec2 value ) { mUpVec = value; } + + setAreUserSettingsDirty(); } void PinballWorld::keyDown( KeyEvent event ) diff --git a/src/Worlds/PinballWorld/PinballWorld.h b/src/Worlds/PinballWorld/PinballWorld.h index 0fe22f6..cf3aee2 100644 --- a/src/Worlds/PinballWorld/PinballWorld.h +++ b/src/Worlds/PinballWorld/PinballWorld.h @@ -29,6 +29,10 @@ class PinballWorld : public BallWorld ~PinballWorld(); void setParams( XmlTree ) override; + + void initSettings() override; + XmlTree getUserSettings() const override; + void setUserSettings( XmlTree ) override; string getSystemName() const override { return "PinballWorld"; } @@ -112,6 +116,8 @@ class PinballWorld : public BallWorld FileWatch mFileWatch; // params + vec2 mDefaultUpVec = vec2(0,1); + vec2 mUpVec = vec2(0,1); float mGravity=0.1f; float mBallReclaimAreaHeight = 10.f;