From a76708dd9fd1cbec2edae2cf5e8164ea470820b7 Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Tue, 6 Feb 2007 08:03:34 +0000 Subject: [PATCH] refactor NoteSkins --- stepmania/src/ActorUtil.cpp | 20 +-- stepmania/src/ActorUtil.h | 2 +- stepmania/src/GhostArrowRow.cpp | 2 +- stepmania/src/NoteDisplay.cpp | 38 +++--- stepmania/src/NoteSkinManager.cpp | 207 ++++++++++++++++++++++++------ stepmania/src/NoteSkinManager.h | 3 +- stepmania/src/ReceptorArrow.cpp | 2 +- 7 files changed, 197 insertions(+), 77 deletions(-) diff --git a/stepmania/src/ActorUtil.cpp b/stepmania/src/ActorUtil.cpp index f04ef9957e..952ca75707 100644 --- a/stepmania/src/ActorUtil.cpp +++ b/stepmania/src/ActorUtil.cpp @@ -260,14 +260,19 @@ namespace return NULL; } - XNode *pRet = ActorUtil::LoadXNodeFromStackShowErrors( L ); + XNode *pRet = NULL; + if( ActorUtil::LoadTableFromStackShowErrors(L) ) + pRet = XmlFileUtil::XNodeFromTable( L ); LUA->Release( L ); return pRet; } } -XNode *ActorUtil::LoadXNodeFromStackShowErrors( Lua *L ) +/* Run the function at the top of the stack, which returns an actor description + * table. If the table was returned, return true and leave it on the stack. + * If not, display an error, return false, and leave nothing on the stack. */ +bool ActorUtil::LoadTableFromStackShowErrors( Lua *L ) { LuaReference func; lua_pushvalue( L, -1 ); @@ -277,10 +282,9 @@ XNode *ActorUtil::LoadXNodeFromStackShowErrors( Lua *L ) if( !LuaHelpers::RunScriptOnStack(L, sError, 0, 1) ) { lua_pop( L, 1 ); - LUA->Release( L ); sError = ssprintf( "Lua runtime error: %s", sError.c_str() ); Dialog::OK( sError, "LUA_ERROR" ); - return NULL; + return false; } if( lua_type(L, -1) != LUA_TTABLE ) @@ -292,15 +296,11 @@ XNode *ActorUtil::LoadXNodeFromStackShowErrors( Lua *L ) lua_getinfo( L, ">nS", &debug ); sError = ssprintf( "%s: must return a table", debug.short_src ); - LUA->Release( L ); Dialog::OK( sError, "LUA_ERROR" ); - return NULL; + return false; } - - XNode *pRet = XmlFileUtil::XNodeFromTable( L ); - - return pRet; + return true; } /* diff --git a/stepmania/src/ActorUtil.h b/stepmania/src/ActorUtil.h index a6fac28bed..0fe36b1a81 100644 --- a/stepmania/src/ActorUtil.h +++ b/stepmania/src/ActorUtil.h @@ -71,7 +71,7 @@ namespace ActorUtil RString GetSourcePath( const XNode *pNode ); RString GetWhere( const XNode *pNode ); bool GetAttrPath( const XNode *pNode, const RString &sName, RString &sOut ); - XNode *LoadXNodeFromStackShowErrors( Lua *L ); + bool LoadTableFromStackShowErrors( Lua *L ); bool ResolvePath( RString &sPath, const RString &sName ); diff --git a/stepmania/src/GhostArrowRow.cpp b/stepmania/src/GhostArrowRow.cpp index dbc987d2ae..6b8438e88d 100644 --- a/stepmania/src/GhostArrowRow.cpp +++ b/stepmania/src/GhostArrowRow.cpp @@ -30,7 +30,7 @@ void GhostArrowRow::Load( const PlayerState* pPlayerState, float fYReverseOffset m_bHoldIsActive.push_back( false ); m_bHoldWasActive.push_back( false ); - m_Ghost.push_back( ActorUtil::MakeActor( NOTESKIN->GetPath(sButton, "tap explosion"), this ) ); + m_Ghost.push_back( NOTESKIN->LoadActor(sButton, "Explosion", this) ); m_Ghost[c]->SetName( "GhostArrow" ); } } diff --git a/stepmania/src/NoteDisplay.cpp b/stepmania/src/NoteDisplay.cpp index c3ebcdcfd4..e45c7efd82 100644 --- a/stepmania/src/NoteDisplay.cpp +++ b/stepmania/src/NoteDisplay.cpp @@ -135,25 +135,17 @@ static map g_NoteResource; static NoteResource *MakeNoteResource( const RString &sButton, const RString &sElement, bool bSpriteOnly ) { - RString sElementAndType = sElement; - NoteSkinAndPath nsap( NOTESKIN->GetCurrentNoteSkin(), NOTESKIN->GetPath(sButton, sElementAndType) ); + RString sElementAndType = ssprintf( "%s, %s", sButton.c_str(), sElement.c_str() ); + NoteSkinAndPath nsap( NOTESKIN->GetCurrentNoteSkin(), sElementAndType ); map::iterator it = g_NoteResource.find( nsap ); if( it == g_NoteResource.end() ) { NoteResource *pRes = new NoteResource( nsap ); - pRes->m_pActor = ActorUtil::MakeActor( nsap.sPath ); + pRes->m_pActor = NOTESKIN->LoadActor( sButton, sElement, NULL, bSpriteOnly ); ASSERT( pRes->m_pActor ); - /* Make sure pActor is a Sprite (or something derived from Sprite). */ - if( bSpriteOnly ) - { - Sprite *pSprite = dynamic_cast( pRes->m_pActor ); - if( pSprite == NULL ) - RageException::Throw( "%s: must be a Sprite", nsap.sPath.c_str() ); - } - g_NoteResource[nsap] = pRes; it = g_NoteResource.find( nsap ); } @@ -220,14 +212,14 @@ Sprite *NoteColorSprite::Get() } static const char *HoldTypeNames[] = { - "hold", - "roll", + "Hold", + "Roll", }; XToString( HoldType ); static const char *ActiveTypeNames[] = { - "active", - "inactive", + "Active", + "Inactive", }; XToString( ActiveType ); @@ -252,19 +244,19 @@ void NoteDisplay::Load( int iColNum, const PlayerState* pPlayerState, float fYRe cache->Load( sButton ); - m_TapNote.Load( sButton, "tap note" ); - m_TapMine.Load( sButton, "tap mine" ); - m_TapLift.Load( sButton, "tap lift" ); + m_TapNote.Load( sButton, "Tap Note" ); + m_TapMine.Load( sButton, "Tap Mine" ); + m_TapLift.Load( sButton, "Tap Lift" ); FOREACH_HoldType( ht ) { FOREACH_ActiveType( at ) { - m_HoldHead[ht][at].Load( sButton, HoldTypeToString(ht)+" head "+ActiveTypeToString(at) ); - m_HoldTopCap[ht][at].Load( sButton, HoldTypeToString(ht)+" topcap "+ActiveTypeToString(at) ); - m_HoldBody[ht][at].Load( sButton, HoldTypeToString(ht)+" body "+ActiveTypeToString(at) ); - m_HoldBottomCap[ht][at].Load( sButton, HoldTypeToString(ht)+" bottomcap "+ActiveTypeToString(at) ); - m_HoldTail[ht][at].Load( sButton, HoldTypeToString(ht)+" tail "+ActiveTypeToString(at) ); + m_HoldHead[ht][at].Load( sButton, HoldTypeToString(ht)+" Head "+ActiveTypeToString(at) ); + m_HoldTopCap[ht][at].Load( sButton, HoldTypeToString(ht)+" Topcap "+ActiveTypeToString(at) ); + m_HoldBody[ht][at].Load( sButton, HoldTypeToString(ht)+" Body "+ActiveTypeToString(at) ); + m_HoldBottomCap[ht][at].Load( sButton, HoldTypeToString(ht)+" Bottomcap "+ActiveTypeToString(at) ); + m_HoldTail[ht][at].Load( sButton, HoldTypeToString(ht)+" Tail "+ActiveTypeToString(at) ); } } } diff --git a/stepmania/src/NoteSkinManager.cpp b/stepmania/src/NoteSkinManager.cpp index fc40f0ef7a..7ce3de7dc5 100644 --- a/stepmania/src/NoteSkinManager.cpp +++ b/stepmania/src/NoteSkinManager.cpp @@ -11,6 +11,8 @@ #include "PrefsManager.h" #include "Foreach.h" #include "ActorUtil.h" +#include "XmlFileUtil.h" +#include "Sprite.h" #include @@ -18,8 +20,9 @@ NoteSkinManager* NOTESKIN = NULL; // global object accessable from anywhere in t const RString NOTESKINS_DIR = "NoteSkins/"; -const RString GLOBAL_BASE_NOTESKIN_DIR = NOTESKINS_DIR + "common/default/"; +const RString GAME_COMMON_NOTESKIN_NAME = "common"; const RString GAME_BASE_NOTESKIN_NAME = "default"; +const RString GLOBAL_BASE_DIR = NOTESKINS_DIR + GAME_COMMON_NOTESKIN_NAME + "/"; static map g_PathCache; struct NoteSkinData @@ -29,6 +32,8 @@ struct NoteSkinData // When looking for an element, search these dirs from head to tail. vector vsDirSearchOrder; + + LuaReference m_Loader; }; namespace @@ -88,38 +93,91 @@ void NoteSkinManager::LoadNoteSkinData( const RString &sNoteSkinName, NoteSkinDa data_out.metrics.Clear(); data_out.vsDirSearchOrder.clear(); - /* Load global NoteSkin defaults */ - data_out.metrics.ReadFile( GLOBAL_BASE_NOTESKIN_DIR+"metrics.ini" ); - data_out.vsDirSearchOrder.insert( data_out.vsDirSearchOrder.begin(), GLOBAL_BASE_NOTESKIN_DIR ); - - /* Load game NoteSkin defaults */ - data_out.metrics.ReadFile( GetNoteSkinDir(GAME_BASE_NOTESKIN_NAME)+"metrics.ini" ); - data_out.vsDirSearchOrder.insert( data_out.vsDirSearchOrder.begin(), GetNoteSkinDir(GAME_BASE_NOTESKIN_NAME) ); - /* Read the current NoteSkin and all of its fallbacks */ LoadNoteSkinDataRecursive( sNoteSkinName, data_out ); } -void NoteSkinManager::LoadNoteSkinDataRecursive( const RString &sNoteSkinName, NoteSkinData& data_out ) +void NoteSkinManager::LoadNoteSkinDataRecursive( const RString &sNoteSkinName_, NoteSkinData& data_out ) { - static int depth = 0; - depth++; - ASSERT_M( depth < 20, "Circular NoteSkin fallback references detected." ); + RString sNoteSkinName(sNoteSkinName_); - RString sDir = GetNoteSkinDir(sNoteSkinName); + int iDepth = 0; + bool bLoadedCommon = false; + bool bLoadedBase = false; + while(1) + { + ++iDepth; + ASSERT_M( iDepth < 20, "Circular NoteSkin fallback references detected." ); - // read global fallback the current NoteSkin (if any) - RString sFallback; - IniFile ini; - ini.ReadFile( sDir+"metrics.ini" ); + RString sDir = NOTESKINS_DIR + m_pCurGame->m_szName + "/" + sNoteSkinName + "/"; + if( !FILEMAN->IsADirectory(sDir) ) + { + sDir = GLOBAL_BASE_DIR + sNoteSkinName + "/"; + if( !FILEMAN->IsADirectory(sDir) ) + { + LOG->Trace( "NoteSkin \"%s\" references skin \"%s\" that is not present", + data_out.sName.c_str(), sNoteSkinName.c_str() ); + return; + } + } - if( ini.GetValue("Global","FallbackNoteSkin",sFallback) ) - LoadNoteSkinDataRecursive( sFallback, data_out ); + LOG->Trace( "LoadNoteSkinDataRecursive: %s (%s)", sNoteSkinName.c_str(), sDir.c_str() ); - data_out.metrics.ReadFile( sDir+"metrics.ini" ); - data_out.vsDirSearchOrder.insert( data_out.vsDirSearchOrder.begin(), sDir ); + // read global fallback the current NoteSkin (if any) + IniFile ini; + ini.ReadFile( sDir+"metrics.ini" ); - depth--; + if( !sNoteSkinName.CompareNoCase(GAME_BASE_NOTESKIN_NAME) ) + bLoadedBase = true; + if( !sNoteSkinName.CompareNoCase(GAME_COMMON_NOTESKIN_NAME) ) + bLoadedCommon = true; + + RString sFallback; + if( !ini.GetValue("Global","FallbackNoteSkin", sFallback) ) + { + if( !bLoadedBase ) + sFallback = GAME_BASE_NOTESKIN_NAME; + else if( !bLoadedCommon ) + sFallback = GAME_COMMON_NOTESKIN_NAME; + } + + XmlFileUtil::MergeIniUnder( &ini, &data_out.metrics ); + + data_out.vsDirSearchOrder.insert( data_out.vsDirSearchOrder.end(), sDir ); + + if( sFallback.empty() ) + break; + sNoteSkinName = sFallback; + } + + LuaReference refScript; + for( vector::reverse_iterator dir = data_out.vsDirSearchOrder.rbegin(); dir != data_out.vsDirSearchOrder.rend(); ++dir ) + { + RString sFile = *dir + "NoteSkin.lua"; + RString sScript; + if( !FILEMAN->IsAFile(sFile) ) + continue; + + if( !GetFileContents(sFile, sScript) ) + continue; + + LOG->Trace( "Load script \"%s\"", sFile.c_str() ); + + Lua *L = LUA->Get(); + RString sError; + refScript.PushSelf( L ); + if( !LuaHelpers::RunScript(L, sScript, "@" + sFile, sError, 1, 1) ) + { + LOG->Trace( "Error running %s: %s", sFile.c_str(), sError.c_str() ); + lua_pop( L, 1 ); + } + else + { + refScript.SetFromStack( L ); + } + LUA->Release( L ); + } + data_out.m_Loader = refScript; } @@ -160,13 +218,6 @@ bool NoteSkinManager::DoNoteSkinsExistForGame( const Game *pGame ) return !asSkinNames.empty(); } -RString NoteSkinManager::GetNoteSkinDir( const RString &sSkinName ) -{ - RString sGame = m_pCurGame->m_szName; - - return NOTESKINS_DIR + sGame + "/" + sSkinName + "/"; -} - void NoteSkinManager::GetAllNoteSkinNamesForGame( const Game *pGame, vector &AddTo ) { if( pGame == m_pCurGame ) @@ -241,8 +292,6 @@ try_again: { if( sButtonName.empty() ) sPath = GetPathFromDirAndFile( *iter, sElement ); - else if( *iter == GLOBAL_BASE_NOTESKIN_DIR ) - sPath = GetPathFromDirAndFile( *iter, "Fallback "+sElement ); else sPath = GetPathFromDirAndFile( *iter, sButtonName+" "+sElement ); if( !sPath.empty() ) @@ -251,17 +300,35 @@ try_again: if( sPath.empty() ) { + FOREACH_CONST( RString, data.vsDirSearchOrder, iter ) + { + if( !sButtonName.empty() ) + sPath = GetPathFromDirAndFile( *iter, "Fallback "+sElement ); + if( !sPath.empty() ) + break; // done searching + } + } + + if( sPath.empty() ) + { + RString sPaths; + FOREACH_CONST( RString, data.vsDirSearchOrder, dir ) + { + if( !sPaths.empty() ) + sPaths += ", "; + + sPaths += *dir; + } + RString message = ssprintf( - "The NoteSkin element \"%s %s\" could not be found in \"%s\", \"%s\", or \"%s\".", + "The NoteSkin element \"%s %s\" could not be found in any of the following directories:\n%s", sButtonName.c_str(), sElement.c_str(), - GetNoteSkinDir(m_sCurrentNoteSkin).c_str(), - GetNoteSkinDir(GAME_BASE_NOTESKIN_NAME).c_str(), - GLOBAL_BASE_NOTESKIN_DIR.c_str() ); + sPaths.c_str() ); if( Dialog::AbortRetryIgnore(message) == Dialog::retry ) { - FILEMAN->FlushDirCache( GetNoteSkinDir(m_sCurrentNoteSkin) ); - FILEMAN->FlushDirCache( GAME_BASE_NOTESKIN_NAME ); + FOREACH_CONST( RString, data.vsDirSearchOrder, dir ) + FILEMAN->FlushDirCache( *dir ); g_PathCache.clear(); goto try_again; } @@ -295,8 +362,8 @@ try_again: if( Dialog::AbortRetryIgnore(message) == Dialog::retry ) { - FILEMAN->FlushDirCache( GetNoteSkinDir(m_sCurrentNoteSkin) ); - FILEMAN->FlushDirCache( GAME_BASE_NOTESKIN_NAME ); + FOREACH_CONST( RString, data.vsDirSearchOrder, dir ) + FILEMAN->FlushDirCache( *dir ); g_PathCache.clear(); goto try_again; } @@ -311,6 +378,56 @@ try_again: return sPath; } +bool NoteSkinManager::PushActorTemplate( Lua *L, const RString &sButton, const RString &sElement, bool bSpriteOnly ) +{ + map::const_iterator iter = g_mapNameToData.find( m_sCurrentNoteSkin ); + ASSERT( iter != g_mapNameToData.end() ); + const NoteSkinData &data = iter->second; + + LuaThreadVariable varButton( "Button", sButton ); + LuaThreadVariable varElement( "Element", sElement ); + LuaThreadVariable varSpriteOnly( "SpriteOnly", LuaReference::Create(bSpriteOnly) ); + + ASSERT( !data.m_Loader.IsNil() ); + data.m_Loader.PushSelf( L ); + lua_remove( L, -2 ); + lua_getfield( L, -1, "Load" ); + + return ActorUtil::LoadTableFromStackShowErrors(L); +} + +Actor *NoteSkinManager::LoadActor( const RString &sButton, const RString &sElement, Actor *pParent, bool bSpriteOnly ) +{ + Lua *L = LUA->Get(); + + if( !PushActorTemplate(L, sButton, sElement, bSpriteOnly) ) + { + // ActorUtil will warn about the error + return new Actor; + } + + auto_ptr pNode( XmlFileUtil::XNodeFromTable(L) ); + if( pNode.get() == NULL ) + { + // XNode will warn about the error + return new Actor; + } + + LUA->Release( L ); + + Actor *pRet = ActorUtil::LoadFromNode( pNode.get(), pParent ); + + if( bSpriteOnly ) + { + /* Make sure pActor is a Sprite (or something derived from Sprite). */ + Sprite *pSprite = dynamic_cast( pRet ); + if( pSprite == NULL ) + LOG->Warn( "%s: %s %s must be a Sprite", m_sCurrentNoteSkin.c_str(), sButton.c_str(), sElement.c_str() ); + } + + return pRet; +} + RString NoteSkinManager::GetPathFromDirAndFile( const RString &sDir, const RString &sFileName ) { vector matches; // fill this with the possible files @@ -337,11 +454,21 @@ class LunaNoteSkinManager: public Luna public: static int GetPath( T* p, lua_State *L ) { lua_pushstring(L, p->GetPath(SArg(1),SArg(2)) ); return 1; } static int GetMetricA( T* p, lua_State *L ) { p->GetMetricA(SArg(1),SArg(2))->PushSelf(L); return 1; } + static int LoadActor( T* p, lua_State *L ) + { + RString sButton = SArg(1); + RString sElement = SArg(2); + if( !p->PushActorTemplate(L, sButton, sElement, false) ) + lua_pushnil( L ); + return 1; + } + LunaNoteSkinManager() { ADD_METHOD( GetPath ); ADD_METHOD( GetMetricA ); + ADD_METHOD( LoadActor ); } }; diff --git a/stepmania/src/NoteSkinManager.h b/stepmania/src/NoteSkinManager.h index 2e65a73df9..e397b524fa 100644 --- a/stepmania/src/NoteSkinManager.h +++ b/stepmania/src/NoteSkinManager.h @@ -27,6 +27,8 @@ public: const RString &GetCurrentNoteSkin() { return m_sCurrentNoteSkin; } RString GetPath( const RString &sButtonName, const RString &sElement ); + bool PushActorTemplate( Lua *L, const RString &sButton, const RString &sElement, bool bSpriteOnly ); + Actor *LoadActor( const RString &sButton, const RString &sElement, Actor *pParent = NULL, bool bSpriteOnly = false ); RString GetMetric( const RString &sButtonName, const RString &sValue ); int GetMetricI( const RString &sButtonName, const RString &sValueName ); @@ -38,7 +40,6 @@ public: void PushSelf( lua_State *L ); protected: - RString GetNoteSkinDir( const RString &sSkinName ); RString GetPathFromDirAndFile( const RString &sDir, const RString &sFileName ); void GetAllNoteSkinNamesForGame( const Game *pGame, vector &AddTo ); diff --git a/stepmania/src/ReceptorArrow.cpp b/stepmania/src/ReceptorArrow.cpp index 87ad743c73..3d4710a0f2 100644 --- a/stepmania/src/ReceptorArrow.cpp +++ b/stepmania/src/ReceptorArrow.cpp @@ -20,7 +20,7 @@ void ReceptorArrow::Load( const PlayerState* pPlayerState, int iColNo ) m_iColNo = iColNo; RString sButton = GAMESTATE->GetCurrentStyle()->ColToButtonName( iColNo ); - m_pReceptor.Load( NOTESKIN->GetPath(sButton,"receptor") ); + m_pReceptor.Load( NOTESKIN->LoadActor(sButton, "Receptor") ); this->AddChild( m_pReceptor ); bool bReverse = m_pPlayerState->m_PlayerOptions.GetCurrent().GetReversePercentForColumn(m_iColNo) > 0.5f;