diff --git a/doomsday/engine/api/doomsday.h b/doomsday/engine/api/doomsday.h index 1a7df15e25..7c15e8f0d4 100644 --- a/doomsday/engine/api/doomsday.h +++ b/doomsday/engine/api/doomsday.h @@ -134,7 +134,7 @@ struct font_s; * @note Game registration order defines the order of the automatic game * identification/selection logic. */ -gameid_t DD_DefineGame(const GameDef* definition); +gameid_t DD_DefineGame(GameDef const* definition); /** * Retrieves the game identifier for a previously defined game. @@ -143,7 +143,7 @@ gameid_t DD_DefineGame(const GameDef* definition); * @param identityKey Identity key of the game. * @return Game identifier. */ -gameid_t DD_GameIdForKey(const char* identityKey); +gameid_t DD_GameIdForKey(char const* identityKey); /** * Registers a new resource for the specified game. diff --git a/doomsday/engine/engine.pro b/doomsday/engine/engine.pro index c4c4114180..9453d2e485 100644 --- a/doomsday/engine/engine.pro +++ b/doomsday/engine/engine.pro @@ -175,6 +175,7 @@ DENG_HEADERS += \ portable/include/dam_file.h \ portable/include/dam_main.h \ portable/include/dd_def.h \ + portable/include/dd_games.h \ portable/include/dd_help.h \ portable/include/dd_input.h \ portable/include/dd_loop.h \ @@ -479,6 +480,7 @@ SOURCES += \ portable/src/con_main.c \ portable/src/dam_file.c \ portable/src/dam_main.c \ + portable/src/dd_games.cpp \ portable/src/dd_help.c \ portable/src/dd_init.cpp \ portable/src/dd_input.c \ diff --git a/doomsday/engine/portable/include/abstractresource.h b/doomsday/engine/portable/include/abstractresource.h index 8b4aeab9fa..4df6dfa840 100644 --- a/doomsday/engine/portable/include/abstractresource.h +++ b/doomsday/engine/portable/include/abstractresource.h @@ -26,6 +26,10 @@ #include "uri.h" +#ifdef __cplusplus +extern "C" { +#endif + /** * AbstractResource. (Record) Stores high-level metadata for a known resource. * @@ -104,4 +108,8 @@ ddstring_t* const* AbstractResource_IdentityKeys(AbstractResource* resource); Uri* const* AbstractResource_SearchPaths(AbstractResource* resource); +#ifdef __cplusplus +} // extern "C" +#endif + #endif /* LIBDENG_ABSTRACTRESOURCE_H */ diff --git a/doomsday/engine/portable/include/dd_games.h b/doomsday/engine/portable/include/dd_games.h new file mode 100644 index 0000000000..d28437446e --- /dev/null +++ b/doomsday/engine/portable/include/dd_games.h @@ -0,0 +1,131 @@ +/** + * @file dd_games.h + * + * The Game collection. + * + * @ingroup core + * + * @author Copyright © 2003-2012 Jaakko Keränen + * @author Copyright © 2005-2012 Daniel Swanson + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef LIBDENG_GAMES_H +#define LIBDENG_GAMES_H + +#include +#include "game.h" + +struct gameinfo_s; +struct gamedef_s; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WIN32 +extern GETGAMEAPI GetGameAPI; +#endif + +/// Currently active game. +extern Game* theGame; + +/// The special "null-game" object instance. +extern Game* nullGame; + +void Games_Shutdown(void); + +/// @return Total number of registered games. +int Games_Count(void); + +/// @return Number of games marked as currently playable. +int Games_NumPlayable(void); + +/** + * @param game Game instance. + * @return Unique identifier associated with @a game. + */ +gameid_t Games_Id(Game* game); + +/** + * @return Game associated with unique index @a idx else @c NULL. + */ +Game* Games_ByIndex(int idx); + +/** + * @return Game associated with @a identityKey else @c NULL. + */ +Game* Games_ByIdentityKey(char const* identityKey); + +/** + * Is this the special "null-game" object (not a real playable game). + * @todo Implement a proper null-game object for this. + */ +boolean Games_IsNullObject(Game const* game); + +/// @return The first playable game in the collection according to registration order. +Game* Games_FirstPlayable(void); + +/** + * Print a game mode banner with rulers. + */ +void Games_PrintBanner(Game* game); + +/** + * Print the list of resources for @a Game. + * + * @param game Game to list resources of. + * @param printStatus @c true= Include the current availability/load status + * of each resource. + * @param rflags Only consider resources whose @ref resourceFlags match + * this value. If @c <0 the flags are ignored. + */ +void Games_PrintResources(Game* game, boolean printStatus, int rflags); + +/** + * @defgroup printGameFlags Print Game Flags. + */ +///@{ +#define PGF_BANNER 0x1 +#define PGF_STATUS 0x2 +#define PGF_LIST_STARTUP_RESOURCES 0x4 +#define PGF_LIST_OTHER_RESOURCES 0x8 + +#define PGF_EVERYTHING (PGF_BANNER|PGF_STATUS|PGF_LIST_STARTUP_RESOURCES|PGF_LIST_OTHER_RESOURCES) +///@} + +/** + * Print extended information about game @a info. + * @param info Game record to be printed. + * @param flags &see printGameFlags + */ +void Games_Print(Game* game, int flags); + +boolean DD_GameInfo(struct gameinfo_s* info); + +void DD_AddGameResource(gameid_t gameId, resourceclass_t rclass, int rflags, char const* _names, void* params); + +gameid_t DD_DefineGame(struct gamedef_s const* def); + +gameid_t DD_GameIdForKey(char const* identityKey); + +D_CMD(ListGames); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* LIBDENG_GAMES_H */ diff --git a/doomsday/engine/portable/include/dd_main.h b/doomsday/engine/portable/include/dd_main.h index 0621527968..e7b658a19f 100644 --- a/doomsday/engine/portable/include/dd_main.h +++ b/doomsday/engine/portable/include/dd_main.h @@ -31,8 +31,7 @@ #include "dd_types.h" #include "dd_plugin.h" -#ifndef __cplusplus // Kludge: these aren't yet C++ compatible -# include "game.h" +#ifndef __cplusplus // Kludge: this isn't yet C++ compatible # include "textures.h" #endif #include "sys_direc.h" @@ -72,9 +71,6 @@ extern finaleid_t titleFinale; extern GETGAMEAPI GetGameAPI; #endif -/// Currently active game. -extern struct Game_s* theGame; - int DD_EarlyInit(void); void DD_FinishInitializationAfterWindowReady(void); boolean DD_Init(void); @@ -82,6 +78,9 @@ boolean DD_Init(void); /// @return @c true if shutdown is in progress. boolean DD_IsShuttingDown(void); +/// @return @c true iff there is presently a game loaded. +boolean DD_GameLoaded(void); + void DD_CheckTimeDemo(void); void DD_UpdateEngineState(void); @@ -121,47 +120,6 @@ materialid_t DD_MaterialForTextureUniqueId(texturenamespaceid_t texNamespaceId, const char* value_Str(int val); -/// @return @c true iff there is presently a game loaded. -boolean DD_GameLoaded(void); - -/// @return Current number of Game records. -int DD_GameCount(void); - -/** - * @return Game associated with unique index @a idx else @c NULL. - */ -struct Game_s* DD_GameByIndex(int idx); - -/** - * @return Game associated with @a identityKey else @c NULL. - */ -struct Game_s* DD_GameByIdentityKey(const char* identityKey); - -/** - * Is this the special "null-game" object (not a real playable game). - * \todo Implement a proper null-game object for this. - */ -boolean DD_IsNullGame(const struct Game_s* game); - -/** - * @defgroup printGameFlags Print Game Flags. - * @{ - */ -#define PGF_BANNER 0x1 -#define PGF_STATUS 0x2 -#define PGF_LIST_STARTUP_RESOURCES 0x4 -#define PGF_LIST_OTHER_RESOURCES 0x8 - -#define PGF_EVERYTHING (PGF_BANNER|PGF_STATUS|PGF_LIST_STARTUP_RESOURCES|PGF_LIST_OTHER_RESOURCES) -/**@}*/ - -/** - * Print extended information about game @a info. - * @param info Game record to be printed. - * @param flags &see printGameFlags - */ -void DD_PrintGame(struct Game_s* game, int flags); - /** * Frees the info structures for all registered games. */ @@ -171,7 +129,6 @@ D_CMD(Load); D_CMD(Unload); D_CMD(Reset); D_CMD(ReloadGame); -D_CMD(ListGames); #ifdef __cplusplus } // extern "C" diff --git a/doomsday/engine/portable/include/de_base.h b/doomsday/engine/portable/include/de_base.h index 109f6b9804..ae5d0aa800 100644 --- a/doomsday/engine/portable/include/de_base.h +++ b/doomsday/engine/portable/include/de_base.h @@ -44,6 +44,7 @@ #include "dd_def.h" #include "dd_share.h" #include "dd_api.h" +#include "dd_games.h" #include "dd_plugin.h" #include "dd_main.h" #include "dd_input.h" diff --git a/doomsday/engine/portable/include/game.h b/doomsday/engine/portable/include/game.h index 46485b9020..f7333820f9 100644 --- a/doomsday/engine/portable/include/game.h +++ b/doomsday/engine/portable/include/game.h @@ -76,6 +76,8 @@ struct AbstractResource_s* Game_AddResource(Game* game, resourceclass_t rclass, */ boolean Game_IsRequiredResource(Game* game, const char* absolutePath); +boolean Game_AllStartupResourcesFound(Game* game); + /** * Change the identfier of the plugin associated with this. * @param pluginId New identifier. diff --git a/doomsday/engine/portable/src/con_data.c b/doomsday/engine/portable/src/con_data.c index 30b283f39c..5a4abef996 100644 --- a/doomsday/engine/portable/src/con_data.c +++ b/doomsday/engine/portable/src/con_data.c @@ -390,7 +390,7 @@ static void updateKnownWords(void) PathDirectory_Iterate2_Const(cvarDirectory, PCF_NO_BRANCH, NULL, PATHDIRECTORY_NOHASH, countVariable, &countCVarParams); // Build the known words table. - numKnownWords = numUniqueNamedCCmds + countCVarParams.count + numCAliases + DD_GameCount(); + numKnownWords = numUniqueNamedCCmds + countCVarParams.count + numCAliases + Games_Count(); len = sizeof(knownword_t) * numKnownWords; knownWords = realloc(knownWords, len); memset(knownWords, 0, len); @@ -430,10 +430,10 @@ static void updateKnownWords(void) } // Add games? - gameCount = DD_GameCount(); + gameCount = Games_Count(); for(i = 0; i < gameCount; ++i) { - Game* game = DD_GameByIndex(i+1); + Game* game = Games_ByIndex(i+1); knownWords[c].type = WT_GAME; knownWords[c].data = game; @@ -1593,10 +1593,10 @@ static void printHelpAbout(const char* query) if(found == 0) // Perhaps a game? { - Game* game = DD_GameByIdentityKey(query); + Game* game = Games_ByIdentityKey(query); if(game) { - DD_PrintGame(game, PGF_EVERYTHING); + Games_Print(game, PGF_EVERYTHING); found = true; } } diff --git a/doomsday/engine/portable/src/dd_games.cpp b/doomsday/engine/portable/src/dd_games.cpp new file mode 100644 index 0000000000..9f7e0ba86c --- /dev/null +++ b/doomsday/engine/portable/src/dd_games.cpp @@ -0,0 +1,428 @@ +/** + * @file dd_games.cpp + * Game collection. @ingroup core + * + * @author Copyright © 2012 Jaakko Keränen + * @author Copyright © 2012 Daniel Swanson + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include "de_base.h" +#include "de_console.h" +#include "dd_games.h" + +#include "abstractresource.h" +#include "fs_util.h" + +extern "C" { + +Game* theGame; // Currently active game. +Game* nullGame; // Special "null-game" object. + +} + +// Game collection. +static Game** games; +static int gamesCount; + +static int gameIndex(Game const* game) +{ + if(game && !Games_IsNullObject(game)) + { + for(int i = 0; i < gamesCount; ++i) + { + if(game == games[i]) + return i+1; + } + } + return 0; +} + +static Game* findGameForId(gameid_t gameId) +{ + if(gameId > 0 && gameId <= gamesCount) + return games[gameId-1]; + return NULL; // Not found. +} + +static Game* findGameForIdentityKey(char const* identityKey) +{ + DENG_ASSERT(identityKey && identityKey[0]); + for(int i = 0; i < gamesCount; ++i) + { + Game* game = games[i]; + if(!stricmp(Str_Text(Game_IdentityKey(game)), identityKey)) + return game; + } + return NULL; // Not found. +} + +void Games_Shutdown(void) +{ + if(games) + { + int i; + for(i = 0; i < gamesCount; ++i) + { + Game_Delete(games[i]); + } + free(games); games = 0; + gamesCount = 0; + } + + if(nullGame) + { + Game_Delete(nullGame); + nullGame = NULL; + } + theGame = NULL; +} + +int Games_NumPlayable(void) +{ + int count = 0; + for(int i = 0; i < gamesCount; ++i) + { + Game* game = games[i]; + if(!Game_AllStartupResourcesFound(game)) continue; + ++count; + } + return count; +} + +Game* Games_FirstPlayable(void) +{ + for(int i = 0; i < gamesCount; ++i) + { + Game* game = games[i]; + if(Game_AllStartupResourcesFound(game)) return game; + } + return NULL; +} + +static Game* addGame(Game* game) +{ + if(game) + { + games = (Game**) M_Realloc(games, sizeof(*games) * ++gamesCount); + if(!games) Con_Error("addGame: Failed on allocation of %lu bytes enlarging Game list.", (unsigned long) (sizeof(*games) * gamesCount)); + games[gamesCount-1] = game; + } + return game; +} + +int Games_Count(void) +{ + return gamesCount; +} + +Game* Games_ByIndex(int idx) +{ + if(idx > 0 && idx <= gamesCount) + return games[idx-1]; + return NULL; +} + +Game* Games_ByIdentityKey(char const* identityKey) +{ + if(identityKey && identityKey[0]) + return findGameForIdentityKey(identityKey); + return NULL; +} + +gameid_t Games_Id(Game* game) +{ + if(!game || game == nullGame) return 0; // Invalid id. + return (gameid_t)gameIndex(game); +} + +boolean Games_IsNullObject(Game const* game) +{ + if(!game) return false; + return game == nullGame; +} + +/** + * @todo This has been moved here so that strings like the game title and author can + * be overridden (e.g., via DEHACKED). Make it so! + */ +void Games_PrintBanner(Game* game) +{ + DENG_ASSERT(game); + Con_PrintRuler(); + Con_FPrintf(CPF_WHITE | CPF_CENTER, "%s\n", Str_Text(Game_Title(game))); + Con_PrintRuler(); +} + +void Games_PrintResources(Game* game, boolean printStatus, int rflags) +{ + if(!game) return; + + size_t count = 0; + for(uint i = 0; i < RESOURCECLASS_COUNT; ++i) + { + AbstractResource* const* records = Game_Resources(game, (resourceclass_t)i, 0); + if(!records) continue; + + for(AbstractResource* const* recordIt = records; *recordIt; recordIt++) + { + AbstractResource* rec = *recordIt; + + if(rflags >= 0 && (rflags & AbstractResource_ResourceFlags(rec))) + { + AbstractResource_Print(rec, printStatus); + count += 1; + } + } + } + + if(count == 0) + Con_Printf(" None\n"); +} + +void Games_Print(Game* game, int flags) +{ + if(!game) return; + + if(Games_IsNullObject(game)) + flags &= ~PGF_BANNER; + +#if _DEBUG + Con_Printf("pluginid:%i data:\"%s\" defs:\"%s\"\n", (int)Game_PluginId(game), + F_PrettyPath(Str_Text(Game_DataPath(game))), + F_PrettyPath(Str_Text(Game_DefsPath(game)))); +#endif + + if(flags & PGF_BANNER) + Games_PrintBanner(game); + + if(!(flags & PGF_BANNER)) + Con_Printf("Game: %s - ", Str_Text(Game_Title(game))); + else + Con_Printf("Author: "); + Con_Printf("%s\n", Str_Text(Game_Author(game))); + Con_Printf("IdentityKey: %s\n", Str_Text(Game_IdentityKey(game))); + + if(flags & PGF_LIST_STARTUP_RESOURCES) + { + Con_Printf("Startup resources:\n"); + Games_PrintResources(game, (flags & PGF_STATUS) != 0, RF_STARTUP); + } + + if(flags & PGF_LIST_OTHER_RESOURCES) + { + Con_Printf("Other resources:\n"); + Con_Printf(" "); + Games_PrintResources(game, /*(flags & PGF_STATUS) != 0*/false, 0); + } + + if(flags & PGF_STATUS) + Con_Printf("Status: %s\n", theGame == game? "Loaded" : + Game_AllStartupResourcesFound(game)? "Complete/Playable" : + "Incomplete/Not playable"); +} + +static void populateGameInfo(GameInfo* info, Game* game) +{ + info->identityKey = Str_Text(Game_IdentityKey(game)); + info->title = Str_Text(Game_Title(game)); + info->author = Str_Text(Game_Author(game)); +} + +/// @note Part of the Doomsday public API. +boolean DD_GameInfo(GameInfo* info) +{ + if(!info) + { +#if _DEBUG + Con_Message("Warning: DD_GameInfo: Received invalid info (=NULL), ignoring."); +#endif + return false; + } + + memset(info, 0, sizeof(*info)); + + if(DD_GameLoaded()) + { + populateGameInfo(info, theGame); + return true; + } + +#if _DEBUG + Con_Message("DD_GameInfo: Warning, no game currently loaded - returning false.\n"); +#endif + return false; +} + +/// @note Part of the Doomsday public API. +void DD_AddGameResource(gameid_t gameId, resourceclass_t rclass, int rflags, + char const* _names, void* params) +{ + Game* game = findGameForId(gameId); + AbstractResource* rec; + ddstring_t name; + ddstring_t str; + char const* p; + + if(!game) + Con_Error("DD_AddGameResource: Error, unknown game id %i.", gameId); + if(!VALID_RESOURCE_CLASS(rclass)) + Con_Error("DD_AddGameResource: Unknown resource class %i.", (int)rclass); + if(!_names || !_names[0] || !strcmp(_names, ";")) + Con_Error("DD_AddGameResource: Invalid name argument."); + if(0 == (rec = AbstractResource_New(rclass, rflags))) + Con_Error("DD_AddGameResource: Unknown error occured during AbstractResource::Construct."); + + // Add a name list to the game record. + Str_Init(&str); + Str_Set(&str, _names); + // Ensure the name list has the required terminating semicolon. + if(Str_RAt(&str, 0) != ';') + Str_Append(&str, ";"); + + p = Str_Text(&str); + Str_Init(&name); + while((p = Str_CopyDelim2(&name, p, ';', CDF_OMIT_DELIMITER))) + { + AbstractResource_AddName(rec, &name); + } + Str_Free(&name); + + if(params) + switch(rclass) + { + case RC_PACKAGE: { + // Add an auto-identification file identityKey list to the game record. + ddstring_t identityKey; + const char* p; + + // Ensure the identityKey list has the required terminating semicolon. + Str_Set(&str, (const char*) params); + if(Str_RAt(&str, 0) != ';') + Str_Append(&str, ";"); + + Str_Init(&identityKey); + p = Str_Text(&str); + while((p = Str_CopyDelim2(&identityKey, p, ';', CDF_OMIT_DELIMITER))) + { + AbstractResource_AddIdentityKey(rec, &identityKey); + } + + Str_Free(&identityKey); + break; + } + default: break; + } + + Game_AddResource(game, rclass, rec); + + Str_Free(&str); +} + +/// @note Part of the Doomsday public API. +gameid_t DD_DefineGame(GameDef const* def) +{ + if(!def) + { +#if _DEBUG + Con_Message("Warning:DD_DefineGame: Received invalid GameDef (=NULL), ignoring."); +#endif + return 0; // Invalid id. + } + + // Game mode identity keys must be unique. Ensure that is the case. + if(findGameForIdentityKey(def->identityKey)) + { +#if _DEBUG + Con_Message("Warning:DD_DefineGame: Failed adding game \"%s\", identity key '%s' already in use, ignoring.\n", def->defaultTitle, def->identityKey); +#endif + return 0; // Invalid id. + } + + // Add this game to our records. + Game* game = addGame(Game_FromDef(def)); + if(game) + { + Game_SetPluginId(game, DD_PluginIdForActiveHook()); + return Games_Id(game); + } + return 0; // Invalid id. +} + +/// @note Part of the Doomsday public API. +gameid_t DD_GameIdForKey(const char* identityKey) +{ + Game* game = findGameForIdentityKey(identityKey); + if(game) return Games_Id(game); + + DEBUG_Message(("Warning:DD_GameIdForKey: Game \"%s\" not defined.\n", identityKey)); + return 0; // Invalid id. +} + +static int C_DECL compareGameByName(const void* a, const void* b) +{ + return stricmp(Str_Text(Game_Title(*(Game**)a)), Str_Text(Game_Title(*(Game**)b))); +} + +D_CMD(ListGames) +{ + DENG_UNUSED(src); + DENG_UNUSED(argc); + DENG_UNUSED(argv); + + const int numAvailableGames = gamesCount; + if(numAvailableGames) + { + int i, numCompleteGames = 0; + Game** gamePtrs; + + Con_FPrintf(CPF_YELLOW, "Registered Games:\n"); + Con_Printf("Key: '!'= Incomplete/Not playable '*'= Loaded\n"); + Con_PrintRuler(); + + // Sort a copy of games so we get a nice alphabetical list. + gamePtrs = (Game**) M_Malloc(gamesCount * sizeof *gamePtrs); + if(!gamePtrs) Con_Error("CCmdListGames: Failed on allocation of %lu bytes for sorted Game list.", (unsigned long) (gamesCount * sizeof *gamePtrs)); + + memcpy(gamePtrs, games, gamesCount * sizeof *gamePtrs); + qsort(gamePtrs, gamesCount, sizeof *gamePtrs, compareGameByName); + + for(i = 0; i < gamesCount; ++i) + { + Game* game = gamePtrs[i]; + + Con_Printf(" %s %-16s %s (%s)\n", theGame == game? "*" : + !Game_AllStartupResourcesFound(game)? "!" : " ", + Str_Text(Game_IdentityKey(game)), Str_Text(Game_Title(game)), + Str_Text(Game_Author(game))); + + if(Game_AllStartupResourcesFound(game)) + numCompleteGames++; + } + + Con_PrintRuler(); + Con_Printf("%i of %i games playable.\n", numCompleteGames, numAvailableGames); + Con_Printf("Use the 'load' command to load a game. For example: \"load gamename\".\n"); + + M_Free(gamePtrs); + } + else + { + Con_Printf("No Registered Games.\n"); + } + + return true; +} diff --git a/doomsday/engine/portable/src/dd_main.c b/doomsday/engine/portable/src/dd_main.c index 3786fcd51e..1b0a5536d1 100644 --- a/doomsday/engine/portable/src/dd_main.c +++ b/doomsday/engine/portable/src/dd_main.c @@ -50,7 +50,6 @@ #include "abstractresource.h" #include "displaymode.h" #include "filedirectory.h" -#include "game.h" #include "m_misc.h" #include "resourcenamespace.h" #include "texture.h" @@ -95,13 +94,6 @@ finaleid_t titleFinale; static ddstring_t** sessionResourceFileList; static size_t numSessionResourceFileList; -Game* theGame; // Currently active game. - -// Game collection. -static Game** games; -static int gamesCount; -static Game* nullGame; // Special "null-game" object. - D_CMD(CheckForUpdates) { Con_Message("Checking for available updates...\n"); @@ -161,40 +153,6 @@ void DD_Register(void) FI_Register(); } -static int gameIndex(const Game* game) -{ - int i; - if(game && !DD_IsNullGame(game)) - { - for(i = 0; i < gamesCount; ++i) - { - if(game == games[i]) - return i+1; - } - } - return 0; -} - -static Game* findGameForId(gameid_t gameId) -{ - if(gameId > 0 && gameId <= gamesCount) - return games[gameId-1]; - return NULL; // Not found. -} - -static Game* findGameForIdentityKey(const char* identityKey) -{ - int i; - DENG_ASSERT(identityKey && identityKey[0]); - for(i = 0; i < gamesCount; ++i) - { - Game* game = games[i]; - if(!stricmp(Str_Text(Game_IdentityKey(game)), identityKey)) - return game; - } - return NULL; // Not found. -} - static void addToPathList(ddstring_t*** list, size_t* listSize, const char* rawPath) { ddstring_t* newPath = Str_New(); @@ -204,7 +162,7 @@ static void addToPathList(ddstring_t*** list, size_t* listSize, const char* rawP F_FixSlashes(newPath, newPath); F_ExpandBasePath(newPath, newPath); - *list = realloc(*list, sizeof(**list) * ++(*listSize)); + *list = M_Realloc(*list, sizeof(**list) * ++(*listSize)); (*list)[(*listSize)-1] = newPath; } @@ -241,216 +199,21 @@ static void destroyPathList(ddstring_t*** list, size_t* listSize) size_t i; for(i = 0; i < *listSize; ++i) Str_Delete((*list)[i]); - free(*list); *list = 0; + M_Free(*list); *list = 0; } *listSize = 0; } -static Game* addGame(Game* game) -{ - if(game) - { - games = (Game**)realloc(games, sizeof(*games) * ++gamesCount); - if(!games) Con_Error("addGame: Failed on allocation of %lu bytes enlarging Game list.", (unsigned long) (sizeof(*games) * gamesCount)); - games[gamesCount-1] = game; - } - return game; -} - boolean DD_GameLoaded(void) { - return !DD_IsNullGame(theGame); -} - -int DD_GameCount(void) -{ - return gamesCount; -} - -Game* DD_GameByIndex(int idx) -{ - if(idx > 0 && idx <= gamesCount) - return games[idx-1]; - return NULL; -} - -Game* DD_GameByIdentityKey(const char* identityKey) -{ - if(identityKey && identityKey[0]) - return findGameForIdentityKey(identityKey); - return NULL; -} - -gameid_t DD_GameId(Game* game) -{ - if(!game || game == nullGame) return 0; // Invalid id. - return (gameid_t)gameIndex(game); -} - -boolean DD_IsNullGame(const Game* game) -{ - if(!game) return false; - return game == nullGame; -} - -static void populateGameInfo(GameInfo* info, Game* game) -{ - info->identityKey = Str_Text(Game_IdentityKey(game)); - info->title = Str_Text(Game_Title(game)); - info->author = Str_Text(Game_Author(game)); -} - -boolean DD_GameInfo(GameInfo* info) -{ - if(!info) - { -#if _DEBUG - Con_Message("Warning:DD_GameInfo: Received invalid info (=NULL), ignoring."); -#endif - return false; - } - - memset(info, 0, sizeof(*info)); - - if(DD_GameLoaded()) - { - populateGameInfo(info, theGame); - return true; - } - -#if _DEBUG - Con_Message("DD_GameInfo: Warning, no game currently loaded - returning false.\n"); -#endif - return false; -} - -void DD_AddGameResource(gameid_t gameId, resourceclass_t rclass, int rflags, - const char* _names, void* params) -{ - Game* game = findGameForId(gameId); - AbstractResource* rec; - ddstring_t name; - ddstring_t str; - const char* p; - - if(!game) - Con_Error("DD_AddGameResource: Error, unknown game id %i.", gameId); - if(!VALID_RESOURCE_CLASS(rclass)) - Con_Error("DD_AddGameResource: Unknown resource class %i.", (int)rclass); - if(!_names || !_names[0] || !strcmp(_names, ";")) - Con_Error("DD_AddGameResource: Invalid name argument."); - if(0 == (rec = AbstractResource_New(rclass, rflags))) - Con_Error("DD_AddGameResource: Unknown error occured during AbstractResource::Construct."); - - // Add a name list to the game record. - Str_Init(&str); - Str_Set(&str, _names); - // Ensure the name list has the required terminating semicolon. - if(Str_RAt(&str, 0) != ';') - Str_Append(&str, ";"); - - p = Str_Text(&str); - Str_Init(&name); - while((p = Str_CopyDelim2(&name, p, ';', CDF_OMIT_DELIMITER))) - { - AbstractResource_AddName(rec, &name); - } - Str_Free(&name); - - if(params) - switch(rclass) - { - case RC_PACKAGE: { - // Add an auto-identification file identityKey list to the game record. - ddstring_t identityKey; - const char* p; - - // Ensure the identityKey list has the required terminating semicolon. - Str_Set(&str, (const char*) params); - if(Str_RAt(&str, 0) != ';') - Str_Append(&str, ";"); - - Str_Init(&identityKey); - p = Str_Text(&str); - while((p = Str_CopyDelim2(&identityKey, p, ';', CDF_OMIT_DELIMITER))) - { - AbstractResource_AddIdentityKey(rec, &identityKey); - } - - Str_Free(&identityKey); - break; - } - default: break; - } - - Game_AddResource(game, rclass, rec); - - Str_Free(&str); -} - -gameid_t DD_DefineGame(const GameDef* def) -{ - Game* game; - - if(!def) - { -#if _DEBUG - Con_Message("Warning:DD_DefineGame: Received invalid GameDef (=NULL), ignoring."); -#endif - return 0; // Invalid id. - } - - // Game mode identity keys must be unique. Ensure that is the case. - if(findGameForIdentityKey(def->identityKey)) - { -#if _DEBUG - Con_Message("Warning:DD_DefineGame: Failed adding game \"%s\", identity key '%s' already in use, ignoring.\n", def->defaultTitle, def->identityKey); -#endif - return 0; // Invalid id. - } - - // Add this game to our records. - game = addGame(Game_FromDef(def)); - if(game) - { - Game_SetPluginId(game, DD_PluginIdForActiveHook()); - return DD_GameId(game); - } - return 0; // Invalid id. -} - -gameid_t DD_GameIdForKey(const char* identityKey) -{ - Game* game = findGameForIdentityKey(identityKey); - if(game) - { - return DD_GameId(game); - } - DEBUG_Message(("Warning:DD_GameIdForKey: Game \"%s\" not defined.\n", identityKey)); - return 0; // Invalid id. + return !Games_IsNullObject(theGame); } void DD_DestroyGames(void) { destroyPathList(&sessionResourceFileList, &numSessionResourceFileList); - if(games) - { - int i; - for(i = 0; i < gamesCount; ++i) - { - Game_Delete(games[i]); - } - free(games); games = 0; - gamesCount = 0; - } - - if(nullGame) - { - Game_Delete(nullGame); - nullGame = NULL; - } - theGame = NULL; + Games_Shutdown(); } /** @@ -612,112 +375,6 @@ static void locateGameStartupResources(Game* game) } } -static boolean allGameStartupResourcesFound(Game* game) -{ - if(!DD_IsNullGame(game)) - { - uint i; - for(i = 0; i < RESOURCECLASS_COUNT; ++i) - { - AbstractResource* const* records = Game_Resources(game, (resourceclass_t)i, 0); - AbstractResource* const* recordIt; - - if(records) - for(recordIt = records; *recordIt; recordIt++) - { - AbstractResource* rec = *recordIt; - const int flags = AbstractResource_ResourceFlags(rec); - - if((flags & RF_STARTUP) && !(flags & RF_FOUND)) - return false; - } - } - } - return true; -} - -/** - * Print a game mode banner with rulers. - * @todo This has been moved here so that strings like the game - * title and author can be overridden (e.g., via DEHACKED). Make it so! - */ -static void printGameBanner(Game* game) -{ - DENG_ASSERT(game); - Con_PrintRuler(); - Con_FPrintf(CPF_WHITE | CPF_CENTER, "%s\n", Str_Text(Game_Title(game))); - Con_PrintRuler(); -} - -static void printGameResources(Game* game, boolean printStatus, int rflags) -{ - size_t count = 0; - uint i; - - if(!game) return; - - for(i = 0; i < RESOURCECLASS_COUNT; ++i) - { - AbstractResource* const* records = Game_Resources(game, (resourceclass_t)i, 0); - AbstractResource* const* recordIt; - - if(records) - for(recordIt = records; *recordIt; recordIt++) - { - AbstractResource* rec = *recordIt; - - if((rflags & RF_STARTUP) == (AbstractResource_ResourceFlags(rec) & RF_STARTUP)) - { - AbstractResource_Print(rec, printStatus); - count += 1; - } - } - } - - if(count == 0) - Con_Printf(" None\n"); -} - -void DD_PrintGame(Game* game, int flags) -{ - if(DD_IsNullGame(game)) - flags &= ~PGF_BANNER; - -#if _DEBUG - Con_Printf("pluginid:%i data:\"%s\" defs:\"%s\"\n", (int)Game_PluginId(game), - F_PrettyPath(Str_Text(Game_DataPath(game))), - F_PrettyPath(Str_Text(Game_DefsPath(game)))); -#endif - - if(flags & PGF_BANNER) - printGameBanner(game); - - if(!(flags & PGF_BANNER)) - Con_Printf("Game: %s - ", Str_Text(Game_Title(game))); - else - Con_Printf("Author: "); - Con_Printf("%s\n", Str_Text(Game_Author(game))); - Con_Printf("IdentityKey: %s\n", Str_Text(Game_IdentityKey(game))); - - if(flags & PGF_LIST_STARTUP_RESOURCES) - { - Con_Printf("Startup resources:\n"); - printGameResources(game, (flags & PGF_STATUS) != 0, RF_STARTUP); - } - - if(flags & PGF_LIST_OTHER_RESOURCES) - { - Con_Printf("Other resources:\n"); - Con_Printf(" "); - printGameResources(game, /*(flags & PGF_STATUS) != 0*/false, 0); - } - - if(flags & PGF_STATUS) - Con_Printf("Status: %s\n", theGame == game? "Loaded" : - allGameStartupResourcesFound(game)? "Complete/Playable" : - "Incomplete/Not playable"); -} - /** * (f_allresourcepaths_callback_t) */ @@ -865,7 +522,7 @@ static int DD_LoadGameStartupResourcesWorker(void* parameters) if(p->initiatedBusyMode) Con_SetProgress(50); - if(!DD_IsNullGame(theGame)) + if(!Games_IsNullObject(theGame)) { ddstring_t temp; @@ -929,7 +586,7 @@ static int DD_LoadAddonResourcesWorker(void* parameters) if(p->initiatedBusyMode) Con_SetProgress(50); - if(!DD_IsNullGame(theGame)) + if(!Games_IsNullObject(theGame)) { /** * Phase 3: Add real files from the Auto directory. @@ -984,8 +641,8 @@ static int DD_ActivateGameWorker(void* parameters) Con_SetProgress(50); // Now that resources have been located we can begin to initialize the game. - if(!DD_IsNullGame(theGame) && gx.PreInit) - gx.PreInit(DD_GameId(theGame)); + if(!Games_IsNullObject(theGame) && gx.PreInit) + gx.PreInit(Games_Id(theGame)); if(p->initiatedBusyMode) Con_SetProgress(100); @@ -1013,7 +670,7 @@ static int DD_ActivateGameWorker(void* parameters) Str_Free(&tmp); } - if(!isDedicated && !DD_IsNullGame(theGame)) + if(!isDedicated && !Games_IsNullObject(theGame)) { // Apply default control bindings for this game. B_BindGameDefaults(); @@ -1194,7 +851,7 @@ boolean DD_ChangeGame2(Game* game, boolean allowReload) Materials_Shutdown(); VERBOSE( - if(!DD_IsNullGame(game)) + if(!Games_IsNullObject(game)) { Con_Message("Selecting game '%s'...\n", Str_Text(Game_IdentityKey(game))); } @@ -1261,7 +918,7 @@ boolean DD_ChangeGame2(Game* game, boolean allowReload) p.initiatedBusyMode = !BusyMode_Active(); - if(!DD_IsNullGame(theGame)) + if(!Games_IsNullObject(theGame)) { // Tell the plugin it is being loaded. /// @todo Must this be done in the main thread? @@ -1271,7 +928,7 @@ boolean DD_ChangeGame2(Game* game, boolean allowReload) } /// @kludge Use more appropriate task names when unloading a game. - if(DD_IsNullGame(game)) + if(Games_IsNullObject(game)) { gameChangeTasks[0].name = "Unloading game..."; gameChangeTasks[3].name = "Switching to ringzero..."; @@ -1283,9 +940,9 @@ boolean DD_ChangeGame2(Game* game, boolean allowReload) // Process any GL-related tasks we couldn't while Busy. Rend_ParticleLoadExtraTextures(); - if(!DD_IsNullGame(theGame)) + if(!Games_IsNullObject(theGame)) { - printGameBanner(theGame); + Games_PrintBanner(theGame); } else { @@ -1329,29 +986,6 @@ static void DD_AutoLoad(void) } } -static int countPlayableGames(void) -{ - int i, count = 0; - for(i = 0; i < gamesCount; ++i) - { - Game* game = games[i]; - if(!allGameStartupResourcesFound(game)) continue; - ++count; - } - return count; -} - -static Game* findFirstPlayableGame(void) -{ - int i; - for(i = 0; i < gamesCount; ++i) - { - Game* game = games[i]; - if(allGameStartupResourcesFound(game)) return game; - } - return NULL; -} - /** * Attempt to determine which game is to be played. * @@ -1362,18 +996,18 @@ Game* DD_AutoselectGame(void) if(CommandLine_CheckWith("-game", 1)) { const char* identityKey = CommandLine_Next(); - Game* game = findGameForIdentityKey(identityKey); + Game* game = Games_ByIdentityKey(identityKey); - if(game && allGameStartupResourcesFound(game)) + if(game && Game_AllStartupResourcesFound(game)) { return game; } } // If but one lonely game; select it. - if(countPlayableGames() == 1) + if(Games_NumPlayable() == 1) { - return findFirstPlayableGame(); + return Games_FirstPlayable(); } // We don't know what to do. @@ -1429,16 +1063,17 @@ int DD_EarlyInit(void) static int DD_LocateAllGameResourcesWorker(void* parameters) { int i; - for(i = 0; i < gamesCount; ++i) + DENG_UNUSED(parameters); + for(i = 0; i < Games_Count(); ++i) { - Game* game = games[i]; + Game* game = Games_ByIndex(i); VERBOSE( Con_Printf("Locating resources for \"%s\"...\n", Str_Text(Game_Title(game))) ) locateGameStartupResources(game); - Con_SetProgress((i+1) * 200/gamesCount -1); + Con_SetProgress((i+1) * 200/Games_Count() -1); - VERBOSE( DD_PrintGame(game, PGF_LIST_STARTUP_RESOURCES|PGF_STATUS) ) + VERBOSE( Games_Print(game, PGF_LIST_STARTUP_RESOURCES|PGF_STATUS) ) } BusyMode_WorkerEnd(); return 0; @@ -2457,13 +2092,13 @@ D_CMD(Load) } // Are we loading a game? - game = findGameForIdentityKey(Str_Text(&searchPath)); + game = Games_ByIdentityKey(Str_Text(&searchPath)); if(game) { - if(!allGameStartupResourcesFound(game)) + if(!Game_AllStartupResourcesFound(game)) { Con_Message("Failed to locate all required startup resources:\n"); - printGameResources(game, true, RF_STARTUP); + Games_PrintResources(game, true, RF_STARTUP); Con_Message("%s (%s) cannot be loaded.\n", Str_Text(Game_Title(game)), Str_Text(Game_IdentityKey(game))); Str_Free(&searchPath); return true; @@ -2534,7 +2169,7 @@ D_CMD(Unload) } // Unload the current game if specified. - if(argc == 2 && (game = findGameForIdentityKey(Str_Text(&searchPath))) != 0) + if(argc == 2 && (game = Games_ByIdentityKey(Str_Text(&searchPath))) != 0) { Str_Free(&searchPath); if(DD_GameLoaded()) @@ -2580,57 +2215,6 @@ D_CMD(ReloadGame) return true; } -static int C_DECL compareGameByName(const void* a, const void* b) -{ - return stricmp(Str_Text(Game_Title(*(Game**)a)), Str_Text(Game_Title(*(Game**)b))); -} - -D_CMD(ListGames) -{ - const int numAvailableGames = gamesCount; - if(numAvailableGames) - { - int i, numCompleteGames = 0; - Game** gamePtrs; - - Con_FPrintf(CPF_YELLOW, "Registered Games:\n"); - Con_Printf("Key: '!'= Incomplete/Not playable '*'= Loaded\n"); - Con_PrintRuler(); - - // Sort a copy of games so we get a nice alphabetical list. - gamePtrs = (Game**)malloc(gamesCount * sizeof *gamePtrs); - if(!gamePtrs) Con_Error("CCmdListGames: Failed on allocation of %lu bytes for sorted Game list.", (unsigned long) (gamesCount * sizeof *gamePtrs)); - - memcpy(gamePtrs, games, gamesCount * sizeof *gamePtrs); - qsort(gamePtrs, gamesCount, sizeof *gamePtrs, compareGameByName); - - for(i = 0; i < gamesCount; ++i) - { - Game* game = gamePtrs[i]; - - Con_Printf(" %s %-16s %s (%s)\n", theGame == game? "*" : - !allGameStartupResourcesFound(game)? "!" : " ", - Str_Text(Game_IdentityKey(game)), Str_Text(Game_Title(game)), - Str_Text(Game_Author(game))); - - if(allGameStartupResourcesFound(game)) - numCompleteGames++; - } - - Con_PrintRuler(); - Con_Printf("%i of %i games playable.\n", numCompleteGames, numAvailableGames); - Con_Printf("Use the 'load' command to load a game. For example: \"load gamename\".\n"); - - free(gamePtrs); - } - else - { - Con_Printf("No Registered Games.\n"); - } - - return true; -} - #ifdef UNIX /** * Some routines not available on the *nix platform. diff --git a/doomsday/engine/portable/src/game.c b/doomsday/engine/portable/src/game.c index 56a22e6cd5..0f367f7e5d 100644 --- a/doomsday/engine/portable/src/game.c +++ b/doomsday/engine/portable/src/game.c @@ -211,6 +211,27 @@ boolean Game_IsRequiredResource(Game* game, const char* absolutePath) return false; } +boolean Game_AllStartupResourcesFound(Game* game) +{ + uint i; + for(i = 0; i < RESOURCECLASS_COUNT; ++i) + { + AbstractResource* const* records = Game_Resources(game, (resourceclass_t)i, 0); + AbstractResource* const* recordIt; + + if(records) + for(recordIt = records; *recordIt; recordIt++) + { + AbstractResource* rec = *recordIt; + const int flags = AbstractResource_ResourceFlags(rec); + + if((flags & RF_STARTUP) && !(flags & RF_FOUND)) + return false; + } + } + return true; +} + pluginid_t Game_SetPluginId(Game* g, pluginid_t pluginId) { assert(g); diff --git a/doomsday/engine/portable/src/uri.c b/doomsday/engine/portable/src/uri.c index 04837619b7..4bd33023f8 100644 --- a/doomsday/engine/portable/src/uri.c +++ b/doomsday/engine/portable/src/uri.c @@ -137,7 +137,7 @@ static boolean resolveUri(const Uri* uri, ddstring_t* dest) { /// \note DataPath already has ending '/'. Game* game = theGame; - if(DD_IsNullGame(game)) + if(Games_IsNullObject(game)) goto parseEnded; Str_PartAppend(dest, Str_Text(Game_DataPath(game)), 0, Str_Length(Game_DataPath(game))-1); } @@ -145,14 +145,14 @@ static boolean resolveUri(const Uri* uri, ddstring_t* dest) { /// \note DefsPath already has ending '/'. Game* game = theGame; - if(DD_IsNullGame(game)) + if(Games_IsNullObject(game)) goto parseEnded; Str_PartAppend(dest, Str_Text(Game_DefsPath(game)), 0, Str_Length(Game_DefsPath(game))-1); } else if(!Str_CompareIgnoreCase(&part, "Game.IdentityKey")) { Game* game = theGame; - if(DD_IsNullGame(game)) + if(Games_IsNullObject(game)) goto parseEnded; Str_Append(dest, Str_Text(Game_IdentityKey(game))); }