diff --git a/Avara.xcodeproj/project.pbxproj b/Avara.xcodeproj/project.pbxproj index 4ea751e33..5b283cb9d 100644 --- a/Avara.xcodeproj/project.pbxproj +++ b/Avara.xcodeproj/project.pbxproj @@ -7,12 +7,22 @@ objects = { /* Begin PBXBuildFile section */ + 944F2F702B323F1900856E53 /* LocalAssetRepository.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 944F2F6F2B323F1900856E53 /* LocalAssetRepository.cpp */; }; + 944F2F712B323F1900856E53 /* LocalAssetRepository.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 944F2F6F2B323F1900856E53 /* LocalAssetRepository.cpp */; }; + 9465BFD22B30C2A00050681C /* AssetManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9465BFD12B30C2A00050681C /* AssetManager.cpp */; }; + 9465BFD32B30C2A00050681C /* AssetManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9465BFD12B30C2A00050681C /* AssetManager.cpp */; }; + 9465BFD72B30EF680050681C /* PackageManifest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9465BFD52B30EF680050681C /* PackageManifest.cpp */; }; + 9465BFD82B30EF680050681C /* PackageManifest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9465BFD52B30EF680050681C /* PackageManifest.cpp */; }; + 948CBF142B335A1400147E80 /* BaseAssetStorage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 948CBF132B335A1400147E80 /* BaseAssetStorage.cpp */; }; + 948CBF152B335A1400147E80 /* BaseAssetStorage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 948CBF132B335A1400147E80 /* BaseAssetStorage.cpp */; }; + 94924EAF2B3A894900197378 /* OggFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94924EAE2B3A894900197378 /* OggFile.cpp */; }; + 94924EB02B3A894900197378 /* OggFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94924EAE2B3A894900197378 /* OggFile.cpp */; }; 94F17DDD29920D90001F5950 /* ARGBColor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 94F17DDC29920D90001F5950 /* ARGBColor.cpp */; }; E20752EF2A003EC600DCC210 /* Tags.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E20752EE2A003EC600DCC210 /* Tags.cpp */; }; E20A3C562991CC72005741F7 /* KeyFuncs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E20A3C552991CC72005741F7 /* KeyFuncs.cpp */; }; + E2210CC12A1C9D9C000712AA /* Tags.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E20752EE2A003EC600DCC210 /* Tags.cpp */; }; E247DE242A0EB51B001CA630 /* PlayerRatingsSimpleElo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E247DE222A0EB51B001CA630 /* PlayerRatingsSimpleElo.cpp */; }; E247DE252A0EB51B001CA630 /* PlayerRatingsSimpleElo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E247DE222A0EB51B001CA630 /* PlayerRatingsSimpleElo.cpp */; }; - E2210CC12A1C9D9C000712AA /* Tags.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E20752EE2A003EC600DCC210 /* Tags.cpp */; }; E517F053299713DB0036B206 /* tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E2B62D7D2997052B00600401 /* tests.cpp */; }; E517F054299713DB0036B206 /* CBasicSound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E5890C7529895118007A875D /* CBasicSound.cpp */; }; E517F055299713DB0036B206 /* CRateSound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E5890C7029895118007A875D /* CRateSound.cpp */; }; @@ -355,6 +365,18 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 944F2F6D2B32379C00856E53 /* AssetRepository.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AssetRepository.h; sourceTree = ""; }; + 944F2F6E2B323E2100856E53 /* LocalAssetRepository.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocalAssetRepository.h; sourceTree = ""; }; + 944F2F6F2B323F1900856E53 /* LocalAssetRepository.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = LocalAssetRepository.cpp; sourceTree = ""; }; + 9465BFD02B30C2790050681C /* AssetManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AssetManager.h; sourceTree = ""; }; + 9465BFD12B30C2A00050681C /* AssetManager.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AssetManager.cpp; sourceTree = ""; }; + 9465BFD52B30EF680050681C /* PackageManifest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PackageManifest.cpp; sourceTree = ""; }; + 9465BFD92B30EFB40050681C /* PackageManifest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PackageManifest.h; sourceTree = ""; }; + 948CBF112B33581D00147E80 /* AssetStorage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AssetStorage.h; sourceTree = ""; }; + 948CBF122B3359D700147E80 /* BaseAssetStorage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BaseAssetStorage.h; sourceTree = ""; }; + 948CBF132B335A1400147E80 /* BaseAssetStorage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = BaseAssetStorage.cpp; sourceTree = ""; }; + 94924EAD2B3A893900197378 /* OggFile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OggFile.h; sourceTree = ""; }; + 94924EAE2B3A894900197378 /* OggFile.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = OggFile.cpp; sourceTree = ""; }; 94F17DDB2991FACE001F5950 /* ARGBColor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARGBColor.h; sourceTree = ""; }; 94F17DDC29920D90001F5950 /* ARGBColor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ARGBColor.cpp; sourceTree = ""; }; E20752ED29FF1EC500DCC210 /* Tags.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Tags.h; sourceTree = ""; }; @@ -1005,6 +1027,23 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 9465BFCF2B30C2130050681C /* assets */ = { + isa = PBXGroup; + children = ( + 9465BFD12B30C2A00050681C /* AssetManager.cpp */, + 9465BFD02B30C2790050681C /* AssetManager.h */, + 9465BFD52B30EF680050681C /* PackageManifest.cpp */, + 9465BFD92B30EFB40050681C /* PackageManifest.h */, + 944F2F6D2B32379C00856E53 /* AssetRepository.h */, + 948CBF112B33581D00147E80 /* AssetStorage.h */, + 944F2F6F2B323F1900856E53 /* LocalAssetRepository.cpp */, + 944F2F6E2B323E2100856E53 /* LocalAssetRepository.h */, + 948CBF132B335A1400147E80 /* BaseAssetStorage.cpp */, + 948CBF122B3359D700147E80 /* BaseAssetStorage.h */, + ); + path = assets; + sourceTree = ""; + }; E5890B1B29894DB3007A875D = { isa = PBXGroup; children = ( @@ -1044,6 +1083,7 @@ children = ( E5890C7929895118007A875D /* Avara.cpp */, E2B62D7D2997052B00600401 /* tests.cpp */, + 9465BFCF2B30C2130050681C /* assets */, E5890C6E29895118007A875D /* audio */, E5890C9729895118007A875D /* base */, E5890C5F29895118007A875D /* bsp */, @@ -1337,6 +1377,8 @@ E5890C7729895118007A875D /* CSoundMixer.cpp */, E5890C7229895118007A875D /* CSoundMixer.h */, E5890C7429895118007A875D /* DopplerPlug.cpp */, + 94924EAE2B3A894900197378 /* OggFile.cpp */, + 94924EAD2B3A893900197378 /* OggFile.h */, E5890C7629895118007A875D /* SoundSystemDefines.h */, ); path = audio; @@ -2002,6 +2044,7 @@ E517F057299713DB0036B206 /* CSoundMixer.cpp in Sources */, E517F058299713DB0036B206 /* DopplerPlug.cpp in Sources */, E517F059299713DB0036B206 /* CBaseObject.cpp in Sources */, + 948CBF152B335A1400147E80 /* BaseAssetStorage.cpp in Sources */, E517F05A299713DB0036B206 /* CDirectObject.cpp in Sources */, E517F05B299713DB0036B206 /* CTagBase.cpp in Sources */, E517F05C299713DB0036B206 /* CBSPPart.cpp in Sources */, @@ -2069,6 +2112,7 @@ E517F09A299713DB0036B206 /* CSolidActor.cpp in Sources */, E517F09B299713DB0036B206 /* CSoundActor.cpp in Sources */, E517F09C299713DB0036B206 /* CSphereActor.cpp in Sources */, + 94924EB02B3A894900197378 /* OggFile.cpp in Sources */, E517F09D299713DB0036B206 /* CSwitchActor.cpp in Sources */, E517F09E299713DB0036B206 /* CTeleporter.cpp in Sources */, E517F09F299713DB0036B206 /* CTextActor.cpp in Sources */, @@ -2078,10 +2122,13 @@ E517F0A3299713DB0036B206 /* CWallActor.cpp in Sources */, E517F0A4299713DB0036B206 /* CWallDoor.cpp in Sources */, E517F0A5299713DB0036B206 /* CWallSolid.cpp in Sources */, + 9465BFD82B30EF680050681C /* PackageManifest.cpp in Sources */, E517F0A6299713DB0036B206 /* CWeapon.cpp in Sources */, E517F0A7299713DB0036B206 /* CWorldShader.cpp in Sources */, E517F0A8299713DB0036B206 /* CYonBox.cpp in Sources */, + 944F2F712B323F1900856E53 /* LocalAssetRepository.cpp in Sources */, E517F0A9299713DB0036B206 /* CYonSphere.cpp in Sources */, + 9465BFD32B30C2A00050681C /* AssetManager.cpp in Sources */, E517F0AA299713DB0036B206 /* CZombieActor.cpp in Sources */, E517F0AB299713DB0036B206 /* LinkLoose.cpp in Sources */, E517F0AC299713DB0036B206 /* CApplication.cpp in Sources */, @@ -2163,6 +2210,7 @@ E5890CF129895118007A875D /* CSliverPart.cpp in Sources */, E5890CD329895118007A875D /* CUfo.cpp in Sources */, E5890D0A29895118007A875D /* ColorManager.cpp in Sources */, + 948CBF142B335A1400147E80 /* BaseAssetStorage.cpp in Sources */, E5890CDD29895118007A875D /* CLogicAnd.cpp in Sources */, E5890CFC29895118007A875D /* CScaledBSP.cpp in Sources */, E5890CD429895118007A875D /* CSmart.cpp in Sources */, @@ -2230,6 +2278,7 @@ E5890EF929895124007A875D /* nanogui_resources.cpp in Sources */, E5890CBD29895118007A875D /* CScout.cpp in Sources */, E5890CA429895118007A875D /* AvaraTCP.cpp in Sources */, + 94924EAF2B3A894900197378 /* OggFile.cpp in Sources */, E5890EFB29895124007A875D /* darwin.mm in Sources */, E5890CBC29895118007A875D /* CAbstractMissile.cpp in Sources */, E5890CD229895118007A875D /* CBall.cpp in Sources */, @@ -2239,10 +2288,13 @@ E5890CC029895118007A875D /* CGoal.cpp in Sources */, E5890CA929895118007A875D /* CHuffProcessor.cpp in Sources */, E5890EF729895124007A875D /* popup.cpp in Sources */, + 9465BFD72B30EF680050681C /* PackageManifest.cpp in Sources */, E5890CBA29895118007A875D /* CWeapon.cpp in Sources */, E247DE242A0EB51B001CA630 /* PlayerRatingsSimpleElo.cpp in Sources */, E5890CE429895118007A875D /* CAreaActor.cpp in Sources */, + 944F2F702B323F1900856E53 /* LocalAssetRepository.cpp in Sources */, E5890D0929895118007A875D /* CNetworkWindow.cpp in Sources */, + 9465BFD22B30C2A00050681C /* AssetManager.cpp in Sources */, E5890EFA29895124007A875D /* glcanvas.cpp in Sources */, E5890CB029895118007A875D /* RamFiles.cpp in Sources */, E5890CD929895118007A875D /* CRandomIncarnator.cpp in Sources */, diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 51281f6bc..a9de67d0c 100755 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -6,10 +6,10 @@ The following people have contributed to this port: * Jonathan Voss <@jonathan-voss> - sound system port, tests, fps improvements * grimm <@shrizza> - user interface, levels * Andy Halstead <@assertivist> - graphics and geometry fixes, level set format -* Ryan Herriman <@rherriman> - numerous levels, level set format, color handling, colorblind mode, custom hull colors, Aftershock support, networking fixes +* Ryan Herriman <@rherriman> - numerous levels, level set format, color handling, colorblind mode, custom hull colors, Aftershock support, networking fixes, asset management * Rob Marlin <@ScarletSwordfish> - level sets, icon, designs, playtesting * Ryan Sommers <@ryansommers> - playtesting, QA * Eric Blenkush <@blenkush> - user interface, spectator mode, scoreboard, bug fixes -* Tom Anderson <@tra> - critical networking fixes and updates +* Tom Anderson <@tra> - critical networking fixes and updates, fps improvements * Jack Carlson <@JackCarlson> - user interface, bug fixes, playtesting * Ben Darling <@Ymihere03> - user interface, bug fixes, playtesting diff --git a/levels/avaraline-quirks-mode/set.json b/levels/avaraline-quirks-mode/set.json index 23027fb03..11f2872f0 100644 --- a/levels/avaraline-quirks-mode/set.json +++ b/levels/avaraline-quirks-mode/set.json @@ -12,7 +12,7 @@ "Message": "The unwanted stepchild of all Baghdad clones." }, { - "Alf": "banzai.alf", + "Alf": "banzai-4mkf.alf", "Name": "Banzai [4MKF]", "Message": "Take a leap of faith. Four minute killfest version." }, @@ -128,66 +128,42 @@ "Loop Start": 0, "Loop End": 0, "Loop Count": 0, - "Data offset": 24, - "Base Rate": 1.0092774853131914, - "Sound": 0, - "Ogg": "3000.ogg", - "Wav": "3000.wav" + "Base Rate": 1.0092774853131914 }, "3001": { "Version": 2, "Loop Start": 0, "Loop End": 0, "Loop Count": 0, - "Data offset": 24, - "Base Rate": 1.0092774853131914, - "Sound": 0, - "Ogg": "3001.ogg", - "Wav": "3001.wav" + "Base Rate": 1.0092774853131914 }, "3002": { "Version": 2, "Loop Start": 0, "Loop End": 0, "Loop Count": 0, - "Data offset": 24, - "Base Rate": 1.0092774853131914, - "Sound": 0, - "Ogg": "3002.ogg", - "Wav": "3002.wav" + "Base Rate": 1.0092774853131914 }, "3003": { "Version": 2, "Loop Start": 0, "Loop End": 0, "Loop Count": 0, - "Data offset": 24, - "Base Rate": 1.0092774853131914, - "Sound": 0, - "Ogg": "3003.ogg", - "Wav": "3003.wav" + "Base Rate": 1.0092774853131914 }, "3004": { "Version": 2, "Loop Start": 0, "Loop End": 0, "Loop Count": 0, - "Data offset": 24, - "Base Rate": 1.0092774853131914, - "Sound": 0, - "Ogg": "3004.ogg", - "Wav": "3004.wav" + "Base Rate": 1.0092774853131914 }, "3005": { "Version": 2, "Loop Start": 0, "Loop End": 0, "Loop Count": 0, - "Data offset": 24, - "Base Rate": 1.0092774853131914, - "Sound": 0, - "Ogg": "3005.ogg", - "Wav": "3005.wav" + "Base Rate": 1.0092774853131914 } }, "HULL": { diff --git a/src/assets/AssetManager.cpp b/src/assets/AssetManager.cpp new file mode 100644 index 000000000..50c8a1d92 --- /dev/null +++ b/src/assets/AssetManager.cpp @@ -0,0 +1,611 @@ +#include "AssetManager.h" +#include "BaseAssetStorage.h" +#include "LocalAssetRepository.h" + +#include "LevelLoader.h" + +#include +#include +#include +#include + +template +void SimpleAssetCache::RemoveAll(std::vector &packageList) +{ + for (auto const &pkg : packageList) { + this->erase(pkg); + } +}; + +template +void AssetCache::RemoveAll(std::vector &packageList) +{ + std::vector cacheHits = {}; + for (auto const &[id, asset] : *this) { + for (auto &pkg : packageList) { + if (pkg == asset.packageName) { + cacheHits.push_back(id); + } + } + } + for (auto &id : cacheHits) { + this->erase(id); + } +}; + +std::shared_ptr _baseStorage = BaseAssetStorage::GetInstance(); +std::shared_ptr _localStorage = LocalAssetRepository::GetInstance(); +std::shared_ptr _localRepo = LocalAssetRepository::GetInstance(); + +// Initialize static variables. +BasePackage AssetManager::basePackage = BasePackage::Avara; +std::vector AssetManager::externalPackages = {}; +std::shared_ptr AssetManager::baseStorage = _baseStorage; +std::shared_ptr AssetManager::assetStorage = _localStorage; +std::vector> AssetManager::repositoryStack = { + _localRepo +}; +SimpleAssetCache AssetManager::manifestCache = {}; +SimpleAssetCache AssetManager::avarascriptCache = {}; +AssetCache AssetManager::bspCache = {}; +AssetCache AssetManager::sndCache = {}; +AssetCache AssetManager::hullCache = {}; + +std::vector AssetManager::GetAvailablePackages() +{ + std::set uniquePkgs = {}; + for (auto const &repo : repositoryStack) { + for (auto const &pkg : *repo->GetPackageList()) { + uniquePkgs.insert(pkg); + } + } + + std::vector pkgs(uniquePkgs.begin(), uniquePkgs.end()); + return pkgs; +} + +std::optional AssetManager::GetResolvedAlfPath(std::string relativePath) +{ + // Attempt to load from packages in priority order. + for (auto const &pkg : externalPackages) { + auto path = GetAlfPath(pkg, relativePath); + std::ifstream file(path); + if (file.good()) { + return path; + } + } + + // Attempt to fall back to the base package. + auto path = GetAlfPath(NoPackage, relativePath); + std::ifstream file(path); + if (file.good()) { + return path; + } + + return std::optional{}; +} + +std::optional> AssetManager::GetManifest(MaybePackage package) +{ + if (manifestCache.count(package) > 0) { + return manifestCache.at(package); + } + + // If it isn't loaded, *don't* call `LoadManifest` to prevent it from being cached. It's + // most likely being requested for UI purposes. + std::string path = GetManifestPath(package); + std::ifstream file(path); + if (file.good()) { + return std::make_shared(nlohmann::json::parse(file)); + } + + return std::optional>{}; +} + +std::vector> AssetManager::GetAllScripts() +{ + std::vector> scripts = {}; + + // Quick note, the scripts should already be loaded thanks to `Init`, `SwitchBasePackage`, and + // `BuildDependencyList`. We don't need to try and load them here! + + // Add the base script first. + if (avarascriptCache.count(NoPackage) > 0) { + scripts.push_back(avarascriptCache.at(NoPackage)); + } + + // Add scripts from the other packages--in reverse order! + for (auto pkg = externalPackages.rbegin(); pkg != externalPackages.rend(); ++pkg) { + if (avarascriptCache.count(*pkg) > 0) { + scripts.push_back(avarascriptCache.at(*pkg)); + } + } + + return scripts; +} + +std::optional> AssetManager::GetBsp(int16_t id) +{ + if (bspCache.count(id) > 0) { + return bspCache.at(id).data; + } + + LoadBsp(id); + if (bspCache.count(id) > 0) { + return bspCache.at(id).data; + } + + return std::optional>{}; +} + +std::optional> AssetManager::GetOgg(int16_t id) +{ + if (sndCache.count(id) > 0) { + return sndCache.at(id).data; + } + + LoadOgg(id); + if (sndCache.count(id) > 0) { + return sndCache.at(id).data; + } + + return std::optional>{}; +} + +std::optional> AssetManager::GetHull(int16_t id) +{ + if (hullCache.count(id) > 0) { + return hullCache.at(id).data; + } + + LoadHull(id); + if (hullCache.count(id) > 0) { + return hullCache.at(id).data; + } + + return std::optional>{}; +} + +void AssetManager::Init() +{ + LoadManifest(NoPackage); + LoadScript(NoPackage); +} + +OSErr AssetManager::LoadLevel(std::string packageName, std::string relativePath, std::string &levelName) +{ + if (externalPackages.size() == 0 || externalPackages[0] != packageName) { + SwitchContext(packageName); + } + + if (manifestCache.count(packageName) == 0) { + return fnfErr; + } + + auto manifest = manifestCache.at(packageName); + std::optional ledi; + for (auto const &level : manifest->levelDirectory) { + if (level.alfPath == relativePath) { + ledi = level; + } + } + if (ledi) { + BasePackage newBase = ledi->useAftershock + ? BasePackage::Aftershock + : BasePackage::Avara; + if (basePackage != newBase) { + SwitchBasePackage(newBase); + } + levelName = ledi->levelName; + } else { + return fnfErr; + } + + std::optional alfPath = GetResolvedAlfPath(ledi->alfPath); + if (!alfPath) { + return fnfErr; + } + + bool wasSuccessful = LoadALF(*alfPath); + if (!wasSuccessful) { + return fnfErr; + } + + return noErr; +} + +bool AssetManager::PackageInStorage(std::string packageName) +{ + return (bool)GetPackagePath(packageName); +} + +std::string AssetManager::GetBasePackagePath(BasePackage basePackage) throw() +{ + std::stringstream path; + path << baseStorage->GetRootPath(); + switch (basePackage) { + case BasePackage::Avara: + // No-op. + break; + case BasePackage::Aftershock: + path << PATHSEP << "aftershock"; + break; + default: + throw std::invalid_argument("No defined path for base package"); + } + return path.str(); +} + +std::optional AssetManager::GetPackagePath(std::string packageName) +{ + std::stringstream path; + path << assetStorage->GetRootPath() << PATHSEP << packageName; + + std::stringstream manifestPath; + manifestPath << path.str() << PATHSEP << MANIFESTFILE; + + std::ifstream testFile(manifestPath.str()); + return testFile.good() + ? path.str() + : std::optional{}; +} + +std::string AssetManager::GetFullPath(MaybePackage package, std::string relativePath) +{ +#ifdef _WIN32 + // Ensure path separators are appropriate for Windows. + std::regex pattern("/"); + relativePath = std::regex_replace(relativePath, pattern, PATHSEP); +#endif + std::stringstream path; + if (package) { + path << assetStorage->GetRootPath() << PATHSEP; + path << *package << PATHSEP << relativePath; + } else { + path << GetBasePackagePath(basePackage) << PATHSEP << relativePath; + } + return path.str(); +} + +std::string AssetManager::GetManifestPath(MaybePackage package) +{ + return GetFullPath(package, MANIFESTFILE); +} + +std::string AssetManager::GetScriptPath(MaybePackage package) +{ + return GetFullPath(package, "default.avarascript"); +} + +std::string AssetManager::GetAlfPath(MaybePackage package, std::string relativePath) +{ + std::stringstream path; + path << "alf" << PATHSEP << relativePath; + return GetFullPath(package, path.str()); +} + +std::string AssetManager::GetBspPath(MaybePackage package, int16_t id) +{ + std::stringstream relativePath; + relativePath << "bsps" << PATHSEP << id << ".json"; + return GetFullPath(package, relativePath.str()); +} + +std::string AssetManager::GetOggPath(MaybePackage package, int16_t id) +{ + std::stringstream relativePath; + relativePath << "ogg" << PATHSEP << id << ".ogg"; + return GetFullPath(package, relativePath.str()); +} + +void AssetManager::LoadManifest(MaybePackage package) +{ + std::string path = GetManifestPath(package); + std::ifstream file(path); + if (file.good()) { + auto manifest = std::make_shared(nlohmann::json::parse(file)); + manifestCache.insert_or_assign(package, manifest); + } +} + +void AssetManager::LoadScript(MaybePackage package) +{ + std::string path = GetScriptPath(package); + std::ifstream file(path); + if (file.good()) { + std::stringstream script; + script << file.rdbuf(); + + avarascriptCache.insert_or_assign(package, std::make_shared(script.str())); + } +} + +void AssetManager::LoadBsp(int16_t id) +{ + // Attempt to retrieve from packages in priority order. + for (auto const &pkg : externalPackages) { + std::string path = GetBspPath(pkg, id); + std::ifstream file(path); + if (file.good()) { + Asset asset; + asset.data = std::make_shared(nlohmann::json::parse(file));; + asset.packageName = pkg; + bspCache.insert_or_assign(id, asset); + return; + } + } + + // Fallback to base package. + std::string path = GetBspPath(NoPackage, id); + std::ifstream file(path); + if (file.good()) { + Asset asset; + asset.data = std::make_shared(nlohmann::json::parse(file));; + asset.packageName = NoPackage; + bspCache.insert_or_assign(id, asset); + } +} + +void AssetManager::LoadOgg(int16_t id) +{ + // Note that all manifests should already be loaded due to `Init`, `SwitchBasePackage`, and + // `BuildDependencyList`. + + std::optional path = {}; + std::optional hsnd = {}; + + // Track which package has the highest priority for this particular asset. This is tricky for + // oggs since the asset is split into two parts, the actual ogg file and the HSND metadata. + // We need to know what the highest priority is so the cache can be cleared appropriately when + // needed later on. + size_t highestPriorityPkgIdx = externalPackages.size(); + + // Attempt to retrieve from packages in priority order. + size_t idx = 0; + for (auto const &pkg : externalPackages) { + std::string testPath = GetOggPath(pkg, id); + std::ifstream testFile(testPath); + if (testFile.good()) { + path = testPath; + highestPriorityPkgIdx = std::min(idx, highestPriorityPkgIdx); + } + + auto manifest = manifestCache.at(pkg); + if (manifest->hsndResources.count(id) > 0) { + hsnd = manifest->hsndResources.at(id); + highestPriorityPkgIdx = std::min(idx, highestPriorityPkgIdx); + } + + if (path && hsnd) { + break; + } + + idx++; + } + + // Fallback to base package. + if (!path) { + std::string testPath = GetOggPath(NoPackage, id); + std::ifstream testFile(testPath); + if (testFile.good()) { + path = testPath; + } + } + + if (!hsnd) { + auto manifest = manifestCache.at(NoPackage); + if (manifest->hsndResources.count(id) > 0) { + hsnd = manifest->hsndResources.at(id); + } else { + // If we *still* don't have an HSND, default to 129 in the base package. + hsnd = manifest->hsndResources.at(129); + } + } + + if (path && hsnd) { + Asset asset; + asset.data = std::make_shared(*path, *hsnd); + asset.packageName = (highestPriorityPkgIdx < externalPackages.size()) + ? externalPackages[highestPriorityPkgIdx] + : NoPackage; + sndCache.insert_or_assign(id, asset); + } +} + +void AssetManager::LoadHull(int16_t id) +{ + // Note that all manifests should already be loaded due to `Init`, `SwitchBasePackage`, and + // `BuildDependencyList`. + + // Attempt to retrieve from packages in priority order. + for (auto const &pkg : externalPackages) { + auto manifest = manifestCache.at(pkg); + if (manifest->hullResources.count(id) > 0) { + Asset asset; + asset.data = std::make_shared(manifest->hullResources.at(id)); + asset.packageName = pkg; + hullCache.insert_or_assign(id, asset); + return; + } + } + + // Fallback to base package. + auto manifest = manifestCache.at(NoPackage); + if (manifest->hullResources.count(id) > 0) { + Asset asset; + asset.data = std::make_shared(manifest->hullResources.at(id)); + asset.packageName = NoPackage; + hullCache.insert_or_assign(id, asset); + } +} + +void AssetManager::SwitchBasePackage(BasePackage newBase) +{ + std::vector packageList = {NoPackage}; + manifestCache.RemoveAll(packageList); + avarascriptCache.RemoveAll(packageList); + bspCache.RemoveAll(packageList); + sndCache.RemoveAll(packageList); + hullCache.RemoveAll(packageList); + + basePackage = newBase; + + LoadManifest(NoPackage); + LoadScript(NoPackage); +} + +void AssetManager::SwitchContext(std::string packageName) +{ + std::vector newContext = {}; + + BuildDependencyList(packageName, newContext); + + std::vector needsCacheClear = {}; + for (auto const &pkg : externalPackages) { + if (std::find(newContext.begin(), newContext.end(), pkg) == newContext.end()) { + needsCacheClear.push_back(pkg); + } + } + + if (needsCacheClear.size() > 0) { + manifestCache.RemoveAll(needsCacheClear); + avarascriptCache.RemoveAll(needsCacheClear); + bspCache.RemoveAll(needsCacheClear); + sndCache.RemoveAll(needsCacheClear); + hullCache.RemoveAll(needsCacheClear); + } + + externalPackages = newContext; + + ReviewPriorities(bspCache); + ReviewPriorities(sndCache); + ReviewPriorities(hullCache); +} + +void AssetManager::BuildDependencyList(std::string currentPackage, std::vector &list) +{ + // Try and load the manifest if it isn't already in the cache. + if (manifestCache.count(currentPackage) == 0) { + LoadManifest(currentPackage); + } + + // Ditto for the avarascript. + if (avarascriptCache.count(currentPackage) == 0) { + LoadScript(currentPackage); + } + + // Technically, LoadManifest might have failed, so we need to check the cache again before we + // try to read from it. If there's no manifest, technically there's nothing more to do and we + // can't recurse further down this branch. + if (manifestCache.count(currentPackage) > 0) { + // Add this package to the list as long as it isn't in the list already. + if (std::find(list.begin(), list.end(), currentPackage) == list.end()) { + list.push_back(currentPackage); + } + + auto manifest = manifestCache.at(currentPackage); + for (auto const &req : manifest->requiredPackages) { + BuildDependencyList(req.packageName, list); + } + } +} + +template <> +void AssetManager::ReviewPriorities(AssetCache &cache) +{ + std::vector needsRemoval = {}; + for (auto const &[id, asset] : cache) { + MaybePackage assetPkg = asset.packageName; + for (auto const &pkg : externalPackages) { + if (assetPkg == pkg) { + // We've reached the point in the package list where the cached asset is of equal + // or higher priority than the remaining packages, so we can stop looking for this + // particular asset ID. + break; + } + + std::string path = GetBspPath(pkg, id); + std::ifstream testFile(path); + if (testFile.good()) { + needsRemoval.push_back(id); + + // We've found a higher priority asset than the one in the cache, so we can stop + // looking for this particular asset ID. + break; + } + } + } + for (auto &id : needsRemoval) { + cache.erase(id); + } +}; + +template <> +void AssetManager::ReviewPriorities(AssetCache &cache) +{ + std::vector needsRemoval = {}; + for (auto const &[id, asset] : cache) { + MaybePackage assetPkg = asset.packageName; + bool foundHsnd = false; + bool foundOgg = false; + for (auto const &pkg : externalPackages) { + if (assetPkg == pkg) { + // We've reached the point in the package list where the cached asset is of equal + // or higher priority than the remaining packages, so we can stop looking for this + // particular asset ID. + break; + } + + auto manifest = manifestCache.at(pkg); + if (manifest->hsndResources.count(id) > 0) { + foundHsnd = true; + } + + std::string path = GetOggPath(pkg, id); + std::ifstream testFile(path); + if (testFile.good()) { + foundOgg = true; + } + + if (foundHsnd || foundOgg) { + needsRemoval.push_back(id); + + // We've found a higher priority asset than the one in the cache, so we can stop + // looking for this particular asset ID. + break; + } + } + } + for (auto &id : needsRemoval) { + cache.erase(id); + } +} + +template <> +void AssetManager::ReviewPriorities(AssetCache &cache) +{ + std::vector needsRemoval = {}; + for (auto const &[id, asset] : cache) { + MaybePackage assetPkg = asset.packageName; + for (auto const &pkg : externalPackages) { + if (assetPkg == pkg) { + // We've reached the point in the package list where the cached asset is of equal + // or higher priority than the remaining packages, so we can stop looking for this + // particular asset ID. + break; + } + + auto manifest = manifestCache.at(pkg); + if (manifest->hullResources.count(id) > 0) { + needsRemoval.push_back(id); + + // We've found a higher priority asset than the one in the cache, so we can stop + // looking for this particular asset ID. + break; + } + } + } + for (auto &id : needsRemoval) { + cache.erase(id); + } +}; diff --git a/src/assets/AssetManager.h b/src/assets/AssetManager.h new file mode 100644 index 000000000..a9470a24f --- /dev/null +++ b/src/assets/AssetManager.h @@ -0,0 +1,316 @@ +#pragma once +#include "AssetStorage.h" +#include "AssetRepository.h" +#include "PackageManifest.h" +#include "OggFile.h" +#include "PlayerConfig.h" +#include "Types.h" + +#include + +#include +#include +#include +#include +#include + +#ifdef __has_include +# if __has_include() // Check for a standard library +# include +# elif __has_include() // Check for an experimental version +# include // Check if __has_include is present +# else // Not found at all +# error "Missing " +# endif +#endif + +// Path separator +#if defined(_WIN32) +#define PATHSEP "\\" +#else +#define PATHSEP "/" +#endif + +// Package structure +#define MANIFESTFILE "set.json" + +enum struct BasePackage { Avara, Aftershock }; + +// If there is no package name, the data belongs to the base package. +typedef std::optional MaybePackage; +#define NoPackage std::optional{} + +template +class SimpleAssetCache : public std::map> { +public: + /** + * Remove any items from the cache that belong to any of the packages in `packageList`. + * + * @param packageList The list of package names we are looking to remove from the cache. + */ + void RemoveAll(std::vector &packageList); +}; + +template +class Asset { +public: + std::shared_ptr data; + + MaybePackage packageName = NoPackage; +}; + +template +class AssetCache : public std::map> { +public: + /** + * Remove any items from the cache that belong to any of the packages in `packageList`. + * + * @param packageList The list of package names we are looking to remove from the cache. + */ + void RemoveAll(std::vector &packageList); +}; + +class AssetManager { +public: + /** + * Get an exhaustive list of all available packages (excluding base packages). + * + * @return the list of available packages + */ + static std::vector GetAvailablePackages(); + + /** + * Get the full filesystem path for an ALF file, if it is available. + * + * @param relativePath The relative path to look for. + * @return the full path to the ALF file + */ + static std::optional GetResolvedAlfPath(std::string relativePath); + + /** + * Get the manifest for the specified package. + * + * @param package The package we want the manifest for. + */ + static std::optional> GetManifest(MaybePackage package); + + /** + * Get the default scripts for all loaded packages in the order in which they should run. + * + * @return the default scripts + */ + static std::vector> GetAllScripts(); + + /** + * Get the BSP with the provided resource ID, if available. + * + * @param id The resource ID. + * @return the BSP json + */ + static std::optional> GetBsp(int16_t id); + + /** + * Get the OGG with the provided resource ID, if available. + * + * @param id The resource ID. + * @return the OGG data + */ + static std::optional> GetOgg(int16_t id); + + /** + * Get the hull with the provided resource ID, if available. + * + * @param id The resource ID. + * @return the hull configuration + */ + static std::optional> GetHull(int16_t id); + + /** + * Run important operations at application start. + */ + static void Init(); + + /** + * Attempt to load the level with the provided relative path from the provided package name. + * + * @param packageName The package we want to load from. + * @param relativePath The relative path to an ALF file. + * @param[out] levelName The name of the level as defined by the package's manifest. + * @return a status code indicating either no error or that the file was not found + */ + static OSErr LoadLevel(std::string packageName, std::string relativePath, std::string &levelName); + + /** + * Checks to see if a package with the given name is stored locally. + * + * @param packageName The package we want to search for. + * @return whether the package is stored locally + */ + static bool PackageInStorage(std::string packageName); +private: + AssetManager() {} + + static BasePackage basePackage; + static std::vector externalPackages; + static std::shared_ptr baseStorage; + static std::shared_ptr assetStorage; + static std::vector> repositoryStack; + static SimpleAssetCache manifestCache; + static SimpleAssetCache avarascriptCache; + static AssetCache bspCache; + static AssetCache sndCache; + static AssetCache hullCache; + + /** + * Get the filesystem path for the specified base package. + * + * @param basePackage The base package we want the path for. + * @throws std::invalid_argument Thrown when there is no path defined for the base package. + * @return the path to the base package + */ + static std::string GetBasePackagePath(BasePackage basePackage) throw(); + + /** + * Get the filesystem path for the specified package, if it's available. + * + * @param packageName The package we want the path for. + * @return the path to the package, if available + */ + static std::optional GetPackagePath(std::string packageName); + + /** + * Get the filesystem path for the specified relative path for the specified package. + * + * @param package The package we want the path for. + * @param relativePath The relative path within that package. + * @return the full path + */ + static std::string GetFullPath(MaybePackage package, std::string relativePath); + + /** + * Get the filesystem path for the specified package's manifest file. + * + * @param package The package we want the path for. + * @return the path to the manifest file + */ + static std::string GetManifestPath(MaybePackage package); + + /** + * Get the filesystem path for the specified package's default script file. + * + * @param package The package we want the path for. + * @return the path to the default script file + */ + static std::string GetScriptPath(MaybePackage package); + + /** + * Get the filesystem path for an ALF file within the specified package. + * + * @param package The package we want the path for. + * @return the path to the ALF file + */ + static std::string GetAlfPath(MaybePackage package, std::string relativePath); + + /** + * Get the filesystem path for a BSP file with the specified id and package. + * + * @param package The package we want the path for. + * @param id The BSP's resource ID. + * @return the path to the BSP file + */ + static std::string GetBspPath(MaybePackage package, int16_t id); + + /** + * Get the filesystem path for an OGG file with the specified id and package. + * + * @param package The package we want the path for. + * @param id The OGG's resource ID. + * @return the path to the OGG file + */ + static std::string GetOggPath(MaybePackage package, int16_t id); + + /** + * Load the specified package's manifest file. + * + * @param package The package whose manifest we want to load. + */ + static void LoadManifest(MaybePackage package); + + /** + * Load the specified package's default script file. + * + * @param package The package whose default script we want to load. + */ + static void LoadScript(MaybePackage package); + + /** + * Load the specified BSP json file. + * + * @param id The resource ID of the BSP. + */ + static void LoadBsp(int16_t id); + + /** + * Load the specified OGG file. + * + * @param id The resource ID of the OGG. + */ + static void LoadOgg(int16_t id); + + /** + * Load the specified hull configuration. (Despite its name, nothing is actually loaded + * here--merely put into the hull cache to speed future lookups.) + * + * @param id The resource ID of the hull configuration. + */ + static void LoadHull(int16_t id); + + /** + * Change which base package is being used (e.g. Avara vs. Aftershock). If it differs from the + * current base package, the cached assets from the current base are removed. + * + * @param newBase The base package we want to use. + */ + static void SwitchBasePackage(BasePackage newBase); + + /** + * Change which package is being used. If it differs from the current package, the cached + * assets from the current package and any of its unneeded dependencies are removed. + * + * @param packageName The package we want to use. + */ + static void SwitchContext(std::string packageName); + + /** + * Recursively build a list of dependencies for the specified package. The dependencies are + * appended to the provided list in the order in which they are encountered. As a side effect, + * any packages encountered will have their manifest and avarascript files loaded and cached if + * they are not already. + * + * @param currentPackage The package we are currently evaluating. + * @param list The list we are in the process of modifying. + */ + static void BuildDependencyList(std::string currentPackage, std::vector &list); + + /** + * Compare items currently in the provided cache with the list of external packages, and remove + * items from the cache that should be overridden by higher priority packages. + * + * @param cache The cache to review. + * @tparam T The type of the assets in the cache. + */ + template + static void ReviewPriorities(AssetCache &cache); + + /** @copydoc */ + template <> + void ReviewPriorities(AssetCache &cache); + + /** @copydoc */ + template <> + void ReviewPriorities(AssetCache &cache); + + /** @copydoc */ + template <> + void ReviewPriorities(AssetCache &cache); +}; diff --git a/src/assets/AssetRepository.h b/src/assets/AssetRepository.h new file mode 100644 index 000000000..778ce12d0 --- /dev/null +++ b/src/assets/AssetRepository.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include + +class AssetRepository { +public: + /** + * Get the list of available packages in this repository. + * + * @return the list of available packages + */ + virtual std::shared_ptr> GetPackageList() = 0; +}; diff --git a/src/assets/AssetStorage.h b/src/assets/AssetStorage.h new file mode 100644 index 000000000..b4c6a7c0b --- /dev/null +++ b/src/assets/AssetStorage.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class AssetStorage { +public: + /** + * Get the root filesystem path for this storage destination. + * + * @return the filesystem path + */ + virtual std::string GetRootPath() = 0; +}; diff --git a/src/assets/BaseAssetStorage.cpp b/src/assets/BaseAssetStorage.cpp new file mode 100644 index 000000000..478f87456 --- /dev/null +++ b/src/assets/BaseAssetStorage.cpp @@ -0,0 +1,17 @@ +#include "BaseAssetStorage.h" +#include "AssetManager.h" + +#include +#include + +std::shared_ptr BaseAssetStorage::GetInstance() { + static auto instance = std::make_shared(BaseAssetStorage()); + return instance; +}; + +std::string BaseAssetStorage::GetRootPath() +{ + std::stringstream path; + path << SDL_GetBasePath() << "rsrc"; + return path.str(); +} diff --git a/src/assets/BaseAssetStorage.h b/src/assets/BaseAssetStorage.h new file mode 100644 index 000000000..1a86a19b7 --- /dev/null +++ b/src/assets/BaseAssetStorage.h @@ -0,0 +1,20 @@ +#pragma once + +#include "AssetStorage.h" + +#include +#include + +class BaseAssetStorage final: public AssetStorage { +public: + static std::shared_ptr GetInstance(); + + /** + * Get the root filesystem path for this storage destination. + * + * @return the filesystem path + */ + std::string GetRootPath(); +private: + BaseAssetStorage() {}; +}; diff --git a/src/assets/LocalAssetRepository.cpp b/src/assets/LocalAssetRepository.cpp new file mode 100644 index 000000000..bc707759f --- /dev/null +++ b/src/assets/LocalAssetRepository.cpp @@ -0,0 +1,68 @@ +#include "LocalAssetRepository.h" +#include "AssetManager.h" + +#define CUTE_FILES_IMPLEMENTATION +#include + +#include + +#include +#include + +std::shared_ptr LocalAssetRepository::GetInstance() { + static auto instance = std::make_shared(LocalAssetRepository()); + return instance; +}; + +std::shared_ptr> LocalAssetRepository::GetPackageList() +{ + if (!populatedList) { + BuildPackageList(); + } + return packageList; +} + +std::string LocalAssetRepository::GetRootPath() +{ + std::stringstream path; + path << SDL_GetBasePath() << "levels"; + return path.str(); +} + +void LocalAssetRepository::Refresh() +{ + packageList->clear(); + populatedList = false; +} + +void LocalAssetRepository::BuildPackageList() +{ + std::string rootPath(GetRootPath()); + + cf_dir_t dir; + cf_dir_open(&dir, rootPath.c_str()); + + while (dir.has_next) { + cf_file_t file; + cf_read_file(&dir, &file); + std::string filename = std::string(file.name); + if (file.is_dir > 0 && + filename.size() >= 2 && + filename.compare(0, 1, ".") != 0 && + filename.compare(0, 2, "..") != 0) { + // This is a directory, check to see if there's a manifest inside. + std::stringstream manifestPath; + manifestPath << rootPath << PATHSEP << filename << PATHSEP << MANIFESTFILE; + + std::ifstream testFile(manifestPath.str()); + if (testFile.good()) { + packageList->push_back(filename); + } + } + cf_dir_next(&dir); + } + + cf_dir_close(&dir); + + populatedList = true; +}; diff --git a/src/assets/LocalAssetRepository.h b/src/assets/LocalAssetRepository.h new file mode 100644 index 000000000..e86c00e03 --- /dev/null +++ b/src/assets/LocalAssetRepository.h @@ -0,0 +1,34 @@ +#pragma once + +#include "AssetRepository.h" +#include "AssetStorage.h" + +class LocalAssetRepository final: public AssetRepository, public AssetStorage { +public: + static std::shared_ptr GetInstance(); + + /** + * Get the list of available packages in this repository. + * + * @return the list of available packages + */ + std::shared_ptr> GetPackageList(); + + /** + * Get the root filesystem path for this storage destination. + * + * @return the filesystem path + */ + std::string GetRootPath(); + + /** + * Refresh the list of available packages. + */ + void Refresh(); +private: + bool populatedList = false; + std::shared_ptr> packageList = std::make_shared>(); + + LocalAssetRepository() {}; + void BuildPackageList(); +}; diff --git a/src/assets/PackageManifest.cpp b/src/assets/PackageManifest.cpp new file mode 100644 index 000000000..8a19755a0 --- /dev/null +++ b/src/assets/PackageManifest.cpp @@ -0,0 +1,76 @@ +#include "PackageManifest.h" + +PackageManifest::PackageManifest(nlohmann::json json) +{ + if (json.find("REQD") != json.end()) { + for (auto &item : json["REQD"].items()) { + nlohmann::json rawReq = item.value(); + std::string pkgPath = rawReq.value("Package", ""); + // TODO: Support version constraints? + if (!pkgPath.empty() && + pkgPath.rfind(".", 0) != 0 && + pkgPath.find("..") == std::string::npos && + pkgPath.find("/") == std::string::npos && + pkgPath.find("\\") == std::string::npos) { + PackageRequirement req; + req.packageName = pkgPath; + requiredPackages.push_back(req); + } + } + } + + if (json.find("LEDI") != json.end()) { + for (auto &item : json["LEDI"].items()) { + nlohmann::json rawLedi = item.value(); + LevelDirectoryEntry level; + level.levelName = rawLedi.value("Name", ""); + level.levelInfo = rawLedi.value("Message", ""); + level.alfPath = rawLedi.value("Alf", ""); + level.useAftershock = rawLedi.value("Aftershock", false); + levelDirectory.push_back(level); + } + } + + if (json.find("HSND") != json.end()) { + for (auto const &[idString, rawHsnd] : json["HSND"].items()) { + int tmp(std::stoi(idString)); + if (tmp >= static_cast(INT16_MIN) && tmp <= static_cast(INT16_MAX)) { + int16_t id = static_cast(tmp); + HSNDRecord hsnd; + hsnd.versNum = rawHsnd.value("Version", 1); + hsnd.loopStart = rawHsnd.value("Loop Start", 0); + hsnd.loopEnd = rawHsnd.value("Loop End", 0); + hsnd.loopCount = rawHsnd.value("Loop Count", 0); + hsnd.baseRate = ToFixed(rawHsnd.value("Base Rate", 1)); + hsndResources.insert_or_assign(id, hsnd); + } + } + } + + if (json.find("HULL") != json.end()) { + for (auto const &[idString, rawHull] : json["HULL"].items()) { + int tmp(std::stoi(idString)); + if (tmp >= static_cast(INT16_MIN) && tmp <= static_cast(INT16_MAX)) { + int16_t id = static_cast(tmp); + HullConfigRecord hull; + hull.hullBSP = rawHull.value("Hull Res ID", 0); + hull.maxMissiles = rawHull.value("Max Missiles", 0); + hull.maxGrenades = rawHull.value("Max Grenades", 0); + hull.maxBoosters = rawHull.value("Max boosters", 0); + hull.mass = ToFixed(rawHull.value("Mass", 0)); + hull.energyRatio = ToFixed(rawHull.value("Max Energy", 0)); + hull.energyChargeRatio = ToFixed(rawHull.value("Energy Charge", 0)); + hull.shieldsRatio = ToFixed(rawHull.value("Max Shields", 0)); + hull.shieldsChargeRatio = ToFixed(rawHull.value("Shield Charge", 0)); + hull.minShotRatio = ToFixed(rawHull.value("Min Shot", 0)); + hull.maxShotRatio = ToFixed(rawHull.value("Max Shot", 0)); + hull.shotChargeRatio = ToFixed(rawHull.value("Shot Charge", 0)); + hull.rideHeight = ToFixed(rawHull.value("Riding Height", 0)); + hull.accelerationRatio = ToFixed(rawHull.value("Acceleration", 0)); + hull.jumpPowerRatio = ToFixed(rawHull.value("Jump Power", 0)); + + hullResources.insert_or_assign(id, hull); + } + } + } +} diff --git a/src/assets/PackageManifest.h b/src/assets/PackageManifest.h new file mode 100644 index 000000000..3db6f2b5f --- /dev/null +++ b/src/assets/PackageManifest.h @@ -0,0 +1,30 @@ +#pragma once +#include "OggFile.h" +#include "PlayerConfig.h" + +#include + +#include +#include +#include + +struct PackageRequirement { + std::string packageName; +}; + +struct LevelDirectoryEntry { + std::string levelName; + std::string levelInfo; + std::string alfPath; + bool useAftershock = false; +}; + +class PackageManifest { +public: + PackageManifest(nlohmann::json json); + + std::vector requiredPackages = {}; + std::vector levelDirectory = {}; + std::map hsndResources = {}; + std::map hullResources = {}; +}; diff --git a/src/audio/CBasicSound.cpp b/src/audio/CBasicSound.cpp index 17f2d816d..46d63ef7c 100644 --- a/src/audio/CBasicSound.cpp +++ b/src/audio/CBasicSound.cpp @@ -126,9 +126,8 @@ void CBasicSound::SetControlLink(SoundLink *linkPtr) { void CBasicSound::Reset() { motionLink = NULL; controlLink = NULL; - itsSamples = NULL; + itsSamples = nullptr; sampleLen = 0; - sampleData = NULL; loopStart = 0; loopEnd = 0; loopCount[0] = loopCount[1] = 0; @@ -137,30 +136,26 @@ void CBasicSound::Reset() { distanceDelay = true; } -void CBasicSound::UseSamplePtr(Sample *samples, int numSamples) { +void CBasicSound::UseSamples(std::shared_ptr theSample) { + if (theSample != nullptr) { + itsSamples = theSample; + sampleLen = static_cast(itsSamples->samples.size()); + loopStart = itsSamples->hsnd.loopStart; + loopEnd = itsSamples->hsnd.loopEnd; + loopCount[0] = loopCount[1] = itsSamples->hsnd.loopCount; + } else { + itsSamples = nullptr; + sampleLen = 0; + loopStart = 0; + loopEnd = 0; + loopCount[0] = loopCount[1] = 0; // Don't loop. + } + currentCount[0].i = 0; currentCount[0].f = 0; currentCount[1].i = 0; currentCount[1].f = 0; - - sampleLen = numSamples; - sampleData = samples; - loopStart = 0; - loopEnd = 0; - loopCount[0] = loopCount[1] = 0; // Don't loop. -} - -void CBasicSound::UseSamples(SampleHeaderHandle theSample) { - if (theSample) { - itsSamples = theSample; - UseSamplePtr(sizeof(SampleHeader) + (Sample *)*theSample, (*theSample)->len); - loopStart = (*itsSamples)->loopStart; - loopEnd = (*itsSamples)->loopEnd; - loopCount[0] = loopCount[1] = (*itsSamples)->loopCount; - } else { - UseSamplePtr(NULL, 0); - } } void CBasicSound::CalculatePosition(int32_t t) { @@ -302,6 +297,7 @@ int16_t CBasicSound::CalcVolume(int16_t theChannel) { void CBasicSound::WriteFrame(int16_t theChannel, int16_t volumeAllowed) { //Sample *s; + //Sample *sampleData = *itsSamples->samples; //WordSample *d; //SampleConvert *converter; int thisCount; diff --git a/src/audio/CBasicSound.h b/src/audio/CBasicSound.h index 3d62ce8a0..fe21a8d73 100644 --- a/src/audio/CBasicSound.h +++ b/src/audio/CBasicSound.h @@ -9,8 +9,11 @@ #pragma once #include "CDirectObject.h" +#include "OggFile.h" #include "SoundSystemDefines.h" +#include + class CSoundMixer; class CSoundHub; @@ -22,7 +25,7 @@ class CBasicSound : public CDirectObject { int16_t hubId; CSoundHub *itsHub; - SampleHeaderHandle itsSamples; + std::shared_ptr itsSamples; CBasicSound *nextSound; CSoundMixer *itsMixer; @@ -31,7 +34,6 @@ class CBasicSound : public CDirectObject { int32_t loopStart; int32_t loopEnd; int32_t loopCount[2]; - Sample *sampleData; int32_t squareAcc[2]; // Distance squared Fixed dSquare; // Distance squared as a Fixed. @@ -58,8 +60,7 @@ class CBasicSound : public CDirectObject { virtual void Start(); virtual void Reset(); - virtual void UseSamplePtr(Sample *samples, int numSamples); - virtual void UseSamples(SampleHeaderHandle theSample); + virtual void UseSamples(std::shared_ptr theSample); virtual void Release(); virtual void SetVolume(Fixed vol); diff --git a/src/audio/CRateSound.cpp b/src/audio/CRateSound.cpp index abd720990..e12e9be47 100644 --- a/src/audio/CRateSound.cpp +++ b/src/audio/CRateSound.cpp @@ -19,8 +19,8 @@ void CRateSound::Reset() { masterRate = FIX1; } -void CRateSound::UseSamplePtr(Sample *samples, int numSamples) { - CBasicSound::UseSamplePtr(samples, numSamples); +void CRateSound::UseSamples(std::shared_ptr theSample) { + CBasicSound::UseSamples(theSample); SetRate(FIX1); } @@ -29,10 +29,11 @@ void CRateSound::UseSamplePtr(Sample *samples, int numSamples) { void CRateSound::SetRate(Fixed aRate) { Fixed adjustedRate; - if (itsSamples) - adjustedRate = FMul((*itsSamples)->baseRate, aRate); - else + if (itsSamples != nullptr) { + adjustedRate = FMul(itsSamples->hsnd.baseRate, aRate); + } else { adjustedRate = aRate; + } adjustedRate = FMul(adjustedRate, itsMixer->standardRate); @@ -169,7 +170,7 @@ void CRateSound::WriteFrame(int16_t theChannel, int16_t volumeAllowed) { while (thisCount > 0) { if (loopCopy) { - didCount = RateMixer(sampleData, d, converter, thisCount, loopEnd, &ind, rateCopy); + didCount = RateMixer(itsSamples, d, converter, thisCount, loopEnd, &ind, rateCopy); d += didCount; thisCount -= didCount; @@ -179,7 +180,7 @@ void CRateSound::WriteFrame(int16_t theChannel, int16_t volumeAllowed) { ind.i += loopStart - loopEnd; } } else { - RateMixer(sampleData, d, converter, thisCount, sampleLen, &ind, rateCopy); + RateMixer(itsSamples, d, converter, thisCount, sampleLen, &ind, rateCopy); thisCount = 0; } } diff --git a/src/audio/CRateSound.h b/src/audio/CRateSound.h index 813fb58cd..c0488d463 100644 --- a/src/audio/CRateSound.h +++ b/src/audio/CRateSound.h @@ -9,6 +9,7 @@ #pragma once #include "CBasicSound.h" +#include "OggFile.h" class CRateSound : public CBasicSound { public: @@ -19,7 +20,7 @@ class CRateSound : public CBasicSound { Boolean chanDoneFlags[2]; virtual void Reset(); - virtual void UseSamplePtr(Sample *samples, int numSamples); + virtual void UseSamples(std::shared_ptr theSample); virtual void FirstFrame(); virtual void WriteFrame(int16_t theChannel, int16_t volumeAllowed); virtual int16_t CalcVolume(int16_t theChannel); // Return volume diff --git a/src/audio/CSoundHub.cpp b/src/audio/CSoundHub.cpp index 60f4412c2..845812d1e 100644 --- a/src/audio/CSoundHub.cpp +++ b/src/audio/CSoundHub.cpp @@ -9,6 +9,7 @@ #include "CSoundHub.h" +#include "AssetManager.h" #include "CBasicSound.h" #include "CHuffProcessor.h" #include "CRateSound.h" @@ -41,22 +42,13 @@ void CSoundHubImpl::CreateSound(short kind) { } void CSoundHubImpl::Restock(CBasicSound *aSound) { - SampleHeaderHandle aSample; - aSound->nextSound = soundList[aSound->hubId]; soundList[aSound->hubId] = aSound; - if (aSound->itsSamples) { - aSample = aSound->itsSamples; - if (--(*aSample)->refCount == 0) { - HUnlock((Handle)aSample); - } - - aSound->itsSamples = NULL; - } + aSound->itsSamples = nullptr; } -CBasicSound *CSoundHubImpl::Aquire(short kind) { +CBasicSound *CSoundHubImpl::Acquire(short kind) { CBasicSound *aSound; if (soundList[kind] == NULL) @@ -85,7 +77,6 @@ void CSoundHubImpl::ISoundHub(short numOfEachKind, short initialLinks) { } muteFlag = false; - sampleList = NULL; itsCompressor = new CHuffProcessor; itsCompressor->Open(); @@ -102,207 +93,8 @@ void CSoundHubImpl::AttachMixer(CSoundMixer *aMixer) { muteFlag = itsMixer->maxChannels == 0; } -SampleHeaderHandle CSoundHubImpl::LoadSample(short resId) { - SampleHeaderHandle aSample; - SampleHeaderPtr sampP; - - aSample = sampleList; - - while (aSample) { - sampP = *aSample; - if (sampP->resId == resId) { - sampP->flags = 0; - break; - } - aSample = sampP->nextSample; - } - - if (!aSample) { - FreeOldSamples(); - //FreeUnusedSamples(); - aSample = LoadSampleHeaderFromSetJSON(resId, sampleList); - if (aSample) sampleList = aSample; - } - - return aSample; -} - -SampleHeaderHandle CSoundHubImpl::LoadSampleLegacy(short resId) { - - SampleHeaderHandle aSample; - SampleHeaderPtr sampP; - - aSample = sampleList; - - while (aSample) { - sampP = *aSample; - if (sampP->resId == resId) { - sampP->flags = 0; - break; - } - aSample = sampP->nextSample; - } - - if (!aSample) { - Handle compressedData; - - compressedData = GetResource(HSOUNDRESTYPE, resId); - if (compressedData) { - int len; - //short tryCount; - Ptr soundData; - //float base; - HSNDRecord *ir; - - // MoveHHi(compressedData); - HLock(compressedData); - - ir = (HSNDRecord *)*compressedData; - - ir->versNum = ntohl(ir->versNum); - ir->loopStart = ntohl(ir->loopStart); - ir->loopEnd = ntohl(ir->loopEnd); - ir->loopCount = ntohl(ir->loopCount); - ir->dataOffset = ntohl(ir->dataOffset); - if (ir->versNum >= 2) { - ir->baseRate = ntohl(ir->baseRate); - //base = ir->baseRate / 65536.0; - } - //else { - // base = 1.0; - //} - - soundData = ir->dataOffset + *compressedData; - len = itsCompressor->GetUncompressedLen(soundData); - - //SDL_Log("HSNDRecord versNum=%d, loopStart=%d, loopEnd=%d, loopCount=%d, dataOffset=%d, baseRate=%f, len=%i\n", - //ir->versNum, ir->loopStart, ir->loopEnd, ir->loopCount, ir->dataOffset, base, len); - - aSample = (SampleHeaderHandle)NewHandle(sizeof(SampleHeader) + len); - if (!aSample) { - FreeOldSamples(); - aSample = (SampleHeaderHandle)NewHandle(sizeof(SampleHeader) + len); - - if (!aSample) { - FreeUnusedSamples(); - aSample = (SampleHeaderHandle)NewHandle(sizeof(SampleHeader) + len); - } - } - - if (aSample) { - uint8_t value; - uint8_t *p; - size_t i; - - sampP = *aSample; - sampP->resId = resId; - sampP->refCount = 0; - sampP->flags = 0; - sampP->len = len; - sampP->loopStart = ir->loopStart; - sampP->loopEnd = ir->loopEnd; - sampP->loopCount = ir->loopCount; - sampP->nextSample = sampleList; - - if (ir->versNum < 2) { - sampP->baseRate = FIX1; - } else { - sampP->baseRate = ir->baseRate; - } - - sampleList = aSample; - - HLock((Handle)aSample); - p = sizeof(SampleHeader) + (unsigned char *)sampP; - itsCompressor->Uncompress(soundData, (Ptr)p); - HUnlock((Handle)aSample); - - value = 128 >> (8 - BITSPERSAMPLE); - for (i = 0; i < len; i++) { - value += *p; - *p++ = value & (0xFF >> (8 - BITSPERSAMPLE)); - } - } - - ReleaseResource(compressedData); - } - } - - return aSample; -} - -SampleHeaderHandle CSoundHubImpl::PreLoadSample(short resId) { - if (muteFlag) - return NULL; - else - return LoadSample(resId); -} - -SampleHeaderHandle CSoundHubImpl::RequestSample(short resId) { - SampleHeaderHandle aSample; - SampleHeaderPtr p; - - aSample = LoadSample(resId); - if (aSample) { - p = *aSample; - if (p->refCount++ == 0) { - HLock((Handle)aSample); - } - } - - return aSample; -} - -void CSoundHubImpl::FreeUnusedSamples() { - SampleHeaderHandle aSample, nextSample, *prevP; - - prevP = &sampleList; - - aSample = sampleList; - while (aSample) { - nextSample = (*aSample)->nextSample; - if ((*aSample)->refCount == 0) { - GetHandleSize((Handle)aSample); - DisposeHandle((Handle)aSample); - *prevP = nextSample; - } else { - prevP = &(*aSample)->nextSample; - } - aSample = nextSample; - } -} - -void CSoundHubImpl::FreeOldSamples() { - SampleHeaderHandle aSample, nextSample, *prevP; - - prevP = &sampleList; - - aSample = sampleList; - while (aSample) { - nextSample = (*aSample)->nextSample; - if ((*aSample)->refCount == 0 && ((*aSample)->flags & kOldSampleFlag)) { - GetHandleSize((Handle)aSample); - DisposeHandle((Handle)aSample); - *prevP = nextSample; - } else { - prevP = &(*aSample)->nextSample; - } - aSample = nextSample; - } -} -void CSoundHubImpl::FlagOldSamples() { - SampleHeaderHandle aSample; - - aSample = sampleList; - while (aSample) { - (*aSample)->flags |= kOldSampleFlag; - aSample = (*aSample)->nextSample; - } -} - void CSoundHubImpl::Dispose() { CBasicSound *aSound, *nextSound; - SampleHeaderHandle aSample, nextSample; short i; if (itsMixer) { @@ -318,13 +110,6 @@ void CSoundHubImpl::Dispose() { } } - aSample = sampleList; - while (aSample) { - nextSample = (*aSample)->nextSample; - DisposeHandle((Handle)aSample); - aSample = nextSample; - } - itsCompressor->Dispose(); DisposeSoundLinks(); CDirectObject::Dispose(); @@ -332,16 +117,20 @@ void CSoundHubImpl::Dispose() { CBasicSound *CSoundHubImpl::GetSoundSampler(short kind, short resId) { CBasicSound *aSound; - SampleHeaderHandle theSamples; + std::shared_ptr theSamples; - aSound = Aquire(kind); + aSound = Acquire(kind); if (aSound) { aSound->itsMixer = itsMixer; - if (muteFlag) - theSamples = NULL; - else - theSamples = RequestSample(resId); + if (muteFlag) { + theSamples = nullptr; + } else { + auto maybeOgg = AssetManager::GetOgg(resId); + theSamples = (maybeOgg) + ? *maybeOgg + : nullptr; + } aSound->UseSamples(theSamples); aSound->SetRate(FIX1); } diff --git a/src/audio/CSoundHub.h b/src/audio/CSoundHub.h index c925f4d64..bfd01a4ef 100644 --- a/src/audio/CSoundHub.h +++ b/src/audio/CSoundHub.h @@ -10,8 +10,11 @@ #pragma once #include "CBasicSound.h" #include "CDirectObject.h" +#include "OggFile.h" #include "SoundSystemDefines.h" +#include + #define EXTRASOUNDLINKCOUNT 32 enum { hubBasic, hubRate, hubSoundKinds }; @@ -27,17 +30,8 @@ class CSoundHub { virtual void AttachMixer(CSoundMixer *aMixer) = 0; //virtual void CreateSound(short kind) = 0; - virtual SampleHeaderHandle LoadSample(short resId) = 0; - virtual SampleHeaderHandle LoadSampleLegacy(short resId) = 0; - virtual SampleHeaderHandle PreLoadSample(short resId) = 0; - virtual SampleHeaderHandle RequestSample(short resId) = 0; - virtual void FreeUnusedSamples() = 0; - - virtual void FreeOldSamples() = 0; - virtual void FlagOldSamples() = 0; - virtual void Restock(CBasicSound *aSound) = 0; - //virtual CBasicSound *Aquire(short kind) = 0; + //virtual CBasicSound *Acquire(short kind) = 0; virtual CBasicSound *GetSoundSampler(short kind, short resId) = 0; //virtual void CreateSoundLinks(short n) = 0; @@ -61,7 +55,6 @@ class CSoundHubImpl : public CDirectObject, public CSoundHub { CHuffProcessor *itsCompressor; CSoundMixer *itsMixer; CBasicSound *soundList[hubSoundKinds]; - SampleHeaderHandle sampleList; Ptr soundLinkStorage; SoundLink *firstFreeLink; @@ -71,17 +64,8 @@ class CSoundHubImpl : public CDirectObject, public CSoundHub { virtual void AttachMixer(CSoundMixer *aMixer); virtual void CreateSound(short kind); - virtual SampleHeaderHandle LoadSample(short resId); - virtual SampleHeaderHandle LoadSampleLegacy(short resId); - virtual SampleHeaderHandle PreLoadSample(short resId); - virtual SampleHeaderHandle RequestSample(short resId); - virtual void FreeUnusedSamples(); - - virtual void FreeOldSamples(); - virtual void FlagOldSamples(); - virtual void Restock(CBasicSound *aSound); - virtual CBasicSound *Aquire(short kind); + virtual CBasicSound *Acquire(short kind); virtual CBasicSound *GetSoundSampler(short kind, short resId); virtual void CreateSoundLinks(short n); diff --git a/src/audio/DopplerPlug.cpp b/src/audio/DopplerPlug.cpp index 2275815cb..d864a3867 100644 --- a/src/audio/DopplerPlug.cpp +++ b/src/audio/DopplerPlug.cpp @@ -9,8 +9,11 @@ #define DOPPLERPLUG +#include "OggFile.h" #include "SoundSystemDefines.h" +#include + /* The RateMixer function is used by sound channels that have a playing rate other than 1.0. The samples are stored at source. The copying @@ -18,7 +21,7 @@ we reach endOffset. The record pointed to by 'current' is updated at the end and the number of samples not yet written is returned. */ -int16_t RateMixer(Sample *source, +int16_t RateMixer(std::shared_ptr source, WordSample *dest, WordSample *converter, int16_t outCount, @@ -36,7 +39,7 @@ int16_t RateMixer(Sample *source, offset = current->i - endOffset; fracOffset = current->f; - source += endOffset; + //source += endOffset; if (offset < 0) { rate = theRate; @@ -44,7 +47,8 @@ int16_t RateMixer(Sample *source, rate >>= 16; do { - *(dest++) += converter[source[offset]]; + //*(dest++) += converter[source[offset]]; + *(dest++) += converter[source->samples[endOffset + offset]]; fracOffset = fracRate + (unsigned short)fracOffset; offset += (fracOffset >> 16) + rate; } while (--i && offset < 0); diff --git a/src/audio/OggFile.cpp b/src/audio/OggFile.cpp new file mode 100644 index 000000000..2171b2ca0 --- /dev/null +++ b/src/audio/OggFile.cpp @@ -0,0 +1,34 @@ +#include "OggFile.h" + +#include "FastMat.h" + +#include + +OggFile::OggFile(std::string path, HSNDRecord hsnd) +{ + this->hsnd = hsnd; + + if (this->hsnd.versNum <= 1) { + this->hsnd.baseRate = FIX1; + } + + samples = {}; + + int error; + stb_vorbis *v = stb_vorbis_open_filename(path.c_str(), &error, NULL); + const size_t numSamples = stb_vorbis_stream_length_in_samples(v); + samples.reserve(numSamples); + for(;;) { + const size_t buffa_length = 512; + int16_t buffa[buffa_length]; + uint8_t sample; + int n = stb_vorbis_get_samples_short_interleaved(v, 1, buffa, buffa_length); + if (n == 0) break; + for (size_t i = 0; i < buffa_length; ++i) { + // it is a mystery + sample = (buffa[i] + INT16_MAX + 1) >> 9; + samples.push_back(sample); + } + } + stb_vorbis_close(v); +} diff --git a/src/audio/OggFile.h b/src/audio/OggFile.h new file mode 100644 index 000000000..28296de34 --- /dev/null +++ b/src/audio/OggFile.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Types.h" + +#include + +typedef struct { + uint32_t versNum; + uint32_t loopStart; + uint32_t loopEnd; + uint32_t loopCount; + UnsignedFixed baseRate; +} HSNDRecord; + +class OggFile { +public: + HSNDRecord hsnd; + std::vector samples; + + OggFile(std::string path, HSNDRecord hsnd); +}; diff --git a/src/audio/SoundSystemDefines.h b/src/audio/SoundSystemDefines.h index 112a1e519..5a6160c39 100644 --- a/src/audio/SoundSystemDefines.h +++ b/src/audio/SoundSystemDefines.h @@ -9,8 +9,11 @@ #pragma once +#include "OggFile.h" #include "Types.h" +#include + #define HSOUNDRESTYPE 'HSND' #define BITSPERSAMPLE 7 #define SAMPLERANGE (1 << BITSPERSAMPLE) @@ -61,36 +64,9 @@ typedef struct { } SoundLink; -struct SampleHeader { - int16_t resId; - int16_t refCount; - uint32_t len; - uint32_t loopStart; - uint32_t loopEnd; - uint32_t loopCount; - UnsignedFixed baseRate; - struct SampleHeader **nextSample; - int16_t flags; -}; - -enum { kOldSampleFlag = 1 }; - -typedef struct SampleHeader SampleHeader; -typedef SampleHeader *SampleHeaderPtr; -typedef SampleHeaderPtr *SampleHeaderHandle; - typedef WordSample SampleConvert[SAMPLERANGE]; -typedef struct { - uint32_t versNum; - uint32_t loopStart; - uint32_t loopEnd; - uint32_t loopCount; - uint32_t dataOffset; - UnsignedFixed baseRate; -} HSNDRecord; - -int16_t RateMixer(Sample *source, +int16_t RateMixer(std::shared_ptr source, WordSample *dest, WordSample *converter, int16_t outCount, diff --git a/src/bsp/CBSPPart.cpp b/src/bsp/CBSPPart.cpp index ae7aa4f0b..07e3743ef 100644 --- a/src/bsp/CBSPPart.cpp +++ b/src/bsp/CBSPPart.cpp @@ -7,6 +7,7 @@ Modified: Monday, September 9, 1996, 00:15 */ +#include "AssetManager.h" #include "AvaraGL.h" #include "CBSPPart.h" @@ -30,8 +31,6 @@ Vector **bspPointTemp = 0; ARGBColor ***bspColorLookupTable = 0; -using json = nlohmann::json; - void CBSPPart::IBSPPart(short resId) { //SDL_Log("Loading BSP: %s\n", bspName); lightSeed = 0; @@ -50,8 +49,8 @@ void CBSPPart::IBSPPart(short resId) { yon = FIX(500); // 500 m sets the flags above and forgets to set the values. userFlags = 0; - json doc = GetBSPJSON(resId); - if (doc == nullptr) { + auto json = AssetManager::GetBsp(resId); + if (!json) { colorCount = 0; polyCount = 0; pointCount = 0; @@ -59,6 +58,7 @@ void CBSPPart::IBSPPart(short resId) { } // Fill in some default values in case values are missing. + auto doc = **json; doc.emplace("radius1", 0.0); doc.emplace("radius2", 0.0); doc.emplace("center", json::array({0.0, 0.0, 0.0})); @@ -101,7 +101,7 @@ void CBSPPart::IBSPPart(short resId) { polyTable = std::make_unique(polyCount); for (uint16_t i = 0; i < colorCount; i++) { - json value = doc["colors"][i]; + nlohmann::json value = doc["colors"][i]; ARGBColor color = ARGBColor::Parse(value) .value_or(ARGBColor(0x00ffffff)); // Fallback to invisible "white." origColorTable[i] = color; @@ -118,7 +118,7 @@ void CBSPPart::IBSPPart(short resId) { CheckForAlpha(); for (uint32_t i = 0; i < pointCount; i++) { - json pt = doc["points"][i]; + nlohmann::json pt = doc["points"][i]; pointTable[i][0] = ToFixed(pt[0]); pointTable[i][1] = ToFixed(pt[1]); pointTable[i][2] = ToFixed(pt[2]); @@ -128,14 +128,14 @@ void CBSPPart::IBSPPart(short resId) { totalPoints = 0; for (uint32_t i = 0; i < polyCount; i++) { - json poly = doc["polys"][i]; - json pt; + nlohmann::json poly = doc["polys"][i]; + nlohmann::json pt; // Color polyTable[i].colorIdx = static_cast(poly["color"]); // Normal - json norms = doc["normals"]; + nlohmann::json norms = doc["normals"]; int idx = poly["normal"]; - json norm = norms[idx]; + nlohmann::json norm = norms[idx]; polyTable[i].normal[0] = norm[0]; polyTable[i].normal[1] = norm[1]; polyTable[i].normal[2] = norm[2]; diff --git a/src/compat/Resource.cpp b/src/compat/Resource.cpp index b3a700f90..2b84d43ff 100644 --- a/src/compat/Resource.cpp +++ b/src/compat/Resource.cpp @@ -6,53 +6,19 @@ #include #include -#include -#include #include #include -#include -#include - -#define CUTE_FILES_IMPLEMENTATION -#include - -#include #ifndef PATH_MAX #define PATH_MAX 260 #endif -using json = nlohmann::json; - std::string sdlBasePath = ""; static std::string defaultResource(std::string(SDL_GetBasePath()) + "rsrc/Avara.r"); static std::string currentResource(""); -static std::string currentBaseDir("rsrc"); - -static std::vector currentExtPkgDirs({}); - -static std::string currentLevelDir(""); - -void UseBaseFolder(std::string folder) { - currentBaseDir = folder; - LoadDefaultOggFiles(); -} - -void AddExternalPackage(std::string folder) { - currentExtPkgDirs.push_back(folder); -} - -void ClearExternalPackages() { - currentExtPkgDirs.clear(); -} - -void UseLevelFolder(std::string folder) { - currentLevelDir = folder; -} - void UseResFile(std::string filename) { currentResource.assign(std::string(SDL_GetBasePath()) + filename); } @@ -224,448 +190,3 @@ void BundlePath(const char *rel, char *dest) { void BundlePath(std::stringstream &buffa, char *dest) { return BundlePath(buffa.str().c_str(), dest); } - -struct AvaraDirListEntry { - int8_t is_dir; - std::string file_name; - std::string full_path; -}; - -std::map level_sets; -std::vector set_name_list; - -bool listingDone = false; - -std::vector LevelDirNameListing() { - if (!listingDone) - LevelDirListing(); - return set_name_list; -} - -const char* PathForLevelSet(std::string set) { - if (!listingDone) - LevelDirListing(); - return level_sets.at(set).full_path.c_str(); -} - -void LevelDirListing() { - cf_dir_t dir; - char ldir[PATH_MAX]; - BundlePath(LEVELDIR, ldir); - cf_dir_open(&dir, ldir); - - std::vector raw_dir_listing; - - while (dir.has_next) { - cf_file_t file; - cf_read_file(&dir, &file); - AvaraDirListEntry entry; - entry.is_dir = file.is_dir; - entry.file_name = std::string(file.name); - raw_dir_listing.push_back(entry); - cf_dir_next(&dir); - } - cf_dir_close(&dir); - // sort directory listing alphabetically - std::sort(raw_dir_listing.begin(), raw_dir_listing.end(), - [](AvaraDirListEntry &a, AvaraDirListEntry &b) -> bool { - return a.file_name < b.file_name; }); - - for (std::vector::iterator it = raw_dir_listing.begin(); it != raw_dir_listing.end(); ++it) { - auto file_str = it->file_name; - auto is_dir = it->is_dir; - if (file_str.size() >= 2) { - - if (file_str.compare(0, 1, ".") != 0 && file_str.compare(0, 2, "..") != 0 && - is_dir > 0) { - // this is a directory, try to see if there's a manifest inside - - std::stringstream ss; - ss << LEVELDIR << PATHSEP << file_str << PATHSEP << SETFILE; - char manf_path[PATH_MAX]; - BundlePath(ss, manf_path); - if (cf_file_exists(manf_path)) { - // we found a set json file so add it (as version 2) - char manf_full_path[PATH_MAX]; - BundlePath(file_str.c_str(), manf_full_path); - it->full_path = manf_full_path; - level_sets.insert(std::make_pair(file_str, (*it))); - set_name_list.push_back(file_str); - } - } - } - } - listingDone = true; -}; - -json LoadLevelListFromJSON(std::string set) { - return GetManifestJSON(set)["LEDI"]; -} - -json GetDefaultManifestJSON() { - std::stringstream setManifestName; - setManifestName << currentBaseDir << PATHSEP << SETFILE; - char * setManifestPath = new char [PATH_MAX]; - BundlePath(setManifestName, setManifestPath); - std::ifstream setManifestFile((std::string(setManifestPath))); - delete [] setManifestPath; - return json::parse(setManifestFile); -} - -json GetManifestJSON(std::string set) { - if (set.length() < 1) return GetDefaultManifestJSON(); - std::stringstream setManifestName; - setManifestName << LEVELDIR << PATHSEP << set << PATHSEP << SETFILE; - char setManifestPath[PATH_MAX]; - BundlePath(setManifestName, setManifestPath); - std::ifstream setManifestFile(setManifestPath); - if (setManifestFile.fail()) { - SDL_Log("Couldn't read %s", setManifestName.str().c_str()); - return -1; - } - - return json::parse(setManifestFile); -} - -void LoadHullFromSetJSON(HullConfigRecord *hull, short resId) { - std::string key = std::to_string(resId); - json hullJson = GetKeyFromSetJSON("HULL", key, "129"); - - hull->hullBSP = (short)hullJson["Hull Res ID"]; - hull->maxMissiles = (short)hullJson["Max Missiles"]; - hull->maxGrenades = (short)hullJson["Max Grenades"]; - hull->maxBoosters = (short)hullJson["Max boosters"]; - hull->mass = ToFixed(hullJson["Mass"]); - hull->energyRatio = ToFixed(hullJson["Max Energy"]); - hull->energyChargeRatio = ToFixed(hullJson["Energy Charge"]); - hull->shieldsRatio = ToFixed(hullJson["Max Shields"]); - hull->shieldsChargeRatio = ToFixed(hullJson["Shield Charge"]); - hull->minShotRatio = ToFixed(hullJson["Min Shot"]); - hull->maxShotRatio = ToFixed(hullJson["Max Shot"]); - hull->shotChargeRatio = ToFixed(hullJson["Shot Charge"]); - hull->rideHeight = ToFixed(hullJson["Riding Height"]); - hull->accelerationRatio = ToFixed(hullJson["Acceleration"]); - hull->jumpPowerRatio = ToFixed(hullJson["Jump Power"]); -} - -bool GetBSPPath(int resId, char* dest) { - std::stringstream relPath; - bool found = false; - - // First, check for the resource in the levelset directory. - relPath << LEVELDIR << PATHSEP << currentLevelDir << PATHSEP; - relPath << BSPSDIR << PATHSEP << resId << BSPSEXT; - BundlePath(relPath, dest); - std::ifstream testFile(dest); - if (!testFile.fail()) { - found = true; - } - - // Haven't found the BSP file yet, fall back and check required packages. - if (!found) { - for (auto &pkg : currentExtPkgDirs) { - relPath.str(""); - relPath << LEVELDIR << PATHSEP << pkg << PATHSEP; - relPath << BSPSDIR << PATHSEP << resId << BSPSEXT; - BundlePath(relPath, dest); - std::ifstream testFile(dest); - if (!testFile.fail()) { - found = true; - } - } - } - - // Still haven't found the BSP file, finally try the base BSP directory. - if (!found) { - relPath.str(""); - relPath << currentBaseDir << PATHSEP << BSPSDIR << PATHSEP << resId << BSPSEXT; - BundlePath(relPath, dest); - std::ifstream testFile(dest); - if (!testFile.fail()) { - found = true; - } - } - return found; -} - -std::map bspCash; - -json GetBSPJSON(int resId) { - char bspPath[PATH_MAX]; - bool found = GetBSPPath(resId, bspPath); - - if (bspCash.count(bspPath) < 1) { - std::ifstream infile(bspPath); - if (!infile.fail()) { - //SDL_Log("Loading BSP: %s", bspPath); - bspCash[bspPath] = json::parse(infile); - } - } - - if (!found || bspCash.count(bspPath) < 1) { - SDL_Log("*** Failed to load BSP %s (id: %d)\n", bspPath, resId); - return nullptr; - } - - return bspCash[bspPath]; -} - -bool HasBSP(int resId) { - char bspPath[PATH_MAX]; - // Presume that parsing the JSON will succeed. - return GetBSPPath(resId, bspPath); -} - -std::string GetALFPath(std::string alfname) { - std::stringstream buffa; - buffa << LEVELDIR << PATHSEP << currentLevelDir << PATHSEP; - buffa << ALFDIR << PATHSEP << alfname; - char alfpath[PATH_MAX]; - BundlePath(buffa.str().c_str(), alfpath); - - std::ifstream testFile(alfpath); - if (testFile.fail()) { - // Check external packages! - for (auto &pkg : currentExtPkgDirs) { - buffa.str(""); - buffa << LEVELDIR << PATHSEP << pkg << PATHSEP; - buffa << ALFDIR << PATHSEP << alfname; - char altalfpath[PATH_MAX]; - BundlePath(buffa.str().c_str(), altalfpath); - testFile.open(altalfpath); - if (!testFile.fail()) { - return std::string(altalfpath); - } - } - } - - return std::string(alfpath); -} - -std::string GetDefaultScript() { - std::stringstream buffa; - buffa << LEVELDIR << PATHSEP << currentLevelDir << PATHSEP; - buffa << DEFAULTSCRIPT; - char temp [PATH_MAX]; - BundlePath(buffa, temp); - auto path = std::string(temp); - std::ifstream t(path); - if (t.good()) { - std::string defaultscript; - t.seekg(0, std::ios::end); - defaultscript.reserve(t.tellg()); - t.seekg(0, std::ios::beg); - - defaultscript.assign((std::istreambuf_iterator(t)), - std::istreambuf_iterator()); - return defaultscript; - } - - return ""; -} - -std::vector GetExternalScripts() { - std::vector scripts = {}; - std::stringstream buffa; - - // Note that we are retrieving the scripts in reverse order, here! - for (auto pkg = currentExtPkgDirs.rbegin(); pkg != currentExtPkgDirs.rend(); ++pkg) { - buffa.str(""); - buffa << LEVELDIR << PATHSEP << *pkg << PATHSEP; - buffa << DEFAULTSCRIPT; - char temp[PATH_MAX]; - BundlePath(buffa.str().c_str(), temp); - std::ifstream t(temp); - if (t.good()) { - std::string defaultscript; - t.seekg(0, std::ios::end); - defaultscript.reserve(t.tellg()); - t.seekg(0, std::ios::beg); - - defaultscript.assign((std::istreambuf_iterator(t)), - std::istreambuf_iterator()); - scripts.push_back(defaultscript); - } else { - SDL_Log("There was an error opening %s", temp); - } - } - - return scripts; -} - -std::string GetBaseScript() { - std::stringstream buffa; - buffa << currentBaseDir << PATHSEP << DEFAULTSCRIPT; - char basepath [PATH_MAX]; - BundlePath(buffa, basepath); - auto path = std::string(basepath); - std::ifstream t(path); - if (t.good()) { - std::string defaultscript; - t.seekg(0, std::ios::end); - defaultscript.reserve(t.tellg()); - t.seekg(0, std::ios::beg); - - defaultscript.assign((std::istreambuf_iterator(t)), - std::istreambuf_iterator()); - return defaultscript; - } - else { - - SDL_Log("There was an error opening %s", path.c_str()); - } - - return ""; -} - -nlohmann::json GetKeyFromSetJSON(std::string rsrc, std::string key, std::string default_id) { - nlohmann::json manifest = GetManifestJSON(currentLevelDir); - nlohmann::json target = NULL; - if (manifest != -1 && manifest.find(rsrc) != manifest.end() && - manifest[rsrc].find(key) != manifest[rsrc].end()) - return manifest[rsrc][key]; - else { - manifest = GetDefaultManifestJSON(); - if (manifest.find(rsrc) != manifest.end() && - manifest[rsrc].find(key) != manifest[rsrc].end()) - return manifest[rsrc][key]; - else if (manifest.find(rsrc) != manifest.end() && - manifest[rsrc].find(default_id) != manifest[rsrc].end()) - return manifest[rsrc][default_id]; - else - return -1; - } -} -#include -typedef std::vector SoundCashSound; -typedef std::map SoundCash; -SoundCash app_sound_cash; -SoundCash level_sound_cash; -std::string current_base_sound_cash_dir; -std::string current_level_sound_cash_dir; - -void LoadOggFile(short resId, std::string filename, SoundCash &cash) { - if (cash.count(resId) > 0) return; - - std::stringstream buffa; - buffa << LEVELDIR << PATHSEP << currentLevelDir << PATHSEP; - buffa << OGGDIR << PATHSEP << filename; - char fullpath[PATH_MAX]; - BundlePath(buffa.str().c_str(), fullpath); - std::ifstream t(fullpath); - if(!t.good()) { - std::stringstream temp; - buffa.swap(temp); - buffa << currentBaseDir << PATHSEP; - buffa << OGGDIR << PATHSEP << filename; - BundlePath(buffa.str().c_str(), fullpath); - } - - //SDL_Log("Loading %s", fullpath); - - int error; - stb_vorbis *v = stb_vorbis_open_filename(fullpath, &error, NULL); - - //stb_vorbis_info info = stb_vorbis_get_info(v); - //SDL_Log("%d channels, %d samples/sec\n", info.channels, info.sample_rate); - - auto sound = SoundCashSound(); - - for(;;) { - const size_t buffa_length = 512; - int16_t buffa[buffa_length]; - uint8_t sample; - int n = stb_vorbis_get_samples_short_interleaved(v, 1, buffa, buffa_length); - if (n == 0) break; - for (size_t i = 0; i < buffa_length; ++i) { - // it is a mystery - sample = (buffa[i] + INT16_MAX + 1) >> 9; - sound.push_back(sample); - } - } - cash[resId] = sound; - stb_vorbis_close(v); -} - -void LoadOggFiles(nlohmann::json &manifest, SoundCash &target_cash) { - size_t loaded = 0; - if (manifest != -1 && manifest.find("HSND") != manifest.end()) { - for (auto &hsnd: manifest["HSND"].items()) { - LoadOggFile(stoi(hsnd.key()), hsnd.value()["Ogg"], target_cash); - loaded++; - } - } - if (loaded > 0) { - SDL_Log("Loaded %zu sounds", loaded); - } -} - -void LoadDefaultOggFiles() { - if (current_base_sound_cash_dir.compare(currentBaseDir) == 0) return; - app_sound_cash.clear(); - current_base_sound_cash_dir = currentBaseDir; - SDL_Log("Loading default sounds..."); - nlohmann::json manifest = GetDefaultManifestJSON(); - LoadOggFiles(manifest, app_sound_cash); -} - -void LoadLevelOggFiles(std::string set) { - if (current_level_sound_cash_dir.compare(set) == 0) return; - level_sound_cash.clear(); - current_level_sound_cash_dir = set; - nlohmann::json manifest = GetManifestJSON(set); - LoadOggFiles(manifest, level_sound_cash); -} - -SampleHeaderHandle LoadSampleHeaderFromSetJSON(short resId, SampleHeaderHandle sampleList) { - - std::string key = std::to_string(resId); - nlohmann::json hsndJson = GetKeyFromSetJSON("HSND", key, "129"); - int8_t version = hsndJson["Version"]; - SoundCash cash; - - size_t found = 0; - - if (level_sound_cash.count(resId) > 0) { - cash = level_sound_cash; - found++; - } - else if (app_sound_cash.count(resId) > 0) { - cash = app_sound_cash; - found++; - } - - if (!found) return NULL; - - Fixed arate = FIX1; - if (version > 1) - arate = ToFixed((float)hsndJson["Base Rate"]); - - SampleHeaderHandle aSample; - SampleHeaderPtr sampP; - size_t len = cash[resId].size(); - - aSample = (SampleHeaderHandle)NewHandle(sizeof(SampleHeader) + len); - - sampP = *aSample; - sampP->baseRate = arate; - sampP->resId = resId; - sampP->refCount = 0; - sampP->flags = 0; - sampP->loopStart = hsndJson["Loop Start"]; - sampP->loopEnd = hsndJson["Loop End"]; - sampP->loopCount = hsndJson["Loop Count"]; - sampP->nextSample = sampleList; - sampP->len = (uint32_t)len; - - uint8_t *p; - HLock((Handle)aSample); - p = sizeof(SampleHeader) + (uint8_t*)sampP; - - for (size_t i = 0; i < len; ++i) { - *p++ = cash[resId][i]; - } - - HUnlock((Handle)aSample); - return aSample; -} diff --git a/src/compat/Resource.h b/src/compat/Resource.h index b26145c03..4f400c513 100644 --- a/src/compat/Resource.h +++ b/src/compat/Resource.h @@ -2,11 +2,9 @@ #include "Types.h" #include "PlayerConfig.h" -#include "SoundSystemDefines.h" +#include #include -#include -#include // path separator #if defined(_WIN32) @@ -15,21 +13,7 @@ #define PATHSEP "/" #endif -// files stuff -#define LEVELDIR "levels" -#define SETFILE "set.json" -#define ALFDIR "alf" -#define BSPSDIR "bsps" -#define BSPSEXT ".json" -#define DEFAULTSCRIPT "default.avarascript" -#define OGGDIR "ogg" -#define WAVDIR "wav" - void UseResFile(std::string filename); -void UseBaseFolder(std::string folder); -void AddExternalPackage(std::string folder); -void ClearExternalPackages(); -void UseLevelFolder(std::string folder); std::string OSTypeString(OSType t); OSType StringOSType(std::string s); Handle GetResource(OSType theType, short theID); @@ -42,23 +26,3 @@ void DetachResource(Handle theResource); void BundlePath(const char *rel, char *dest); void BundlePath(std::stringstream &buffa, char *dest); - -void LevelDirListing(); -std::vector LevelDirNameListing(); -int8_t GetVersionForLevelSet(std::string levelset); - -nlohmann::json LoadLevelListFromJSON(std::string set); -nlohmann::json GetManifestJSON(std::string set); -nlohmann::json GetKeyFromSetJSON(std::string rsrc, std::string key, std::string default_id); - -nlohmann::json GetBSPJSON(int resId); -bool HasBSP(int resId); -std::string GetALFPath(std::string alfname); -std::string GetDefaultScript(); -std::vector GetExternalScripts(); -std::string GetBaseScript(); -void LoadHullFromSetJSON(HullConfigRecord *hull, short resId); -void LoadDefaultOggFiles(); -void LoadLevelOggFiles(std::string set); -void LoadOggFile(short resId, const char* filename); -SampleHeaderHandle LoadSampleHeaderFromSetJSON(short resId, SampleHeaderHandle sampleList); diff --git a/src/game/CAbstractActor.cpp b/src/game/CAbstractActor.cpp index 94f32a9ed..7dfd49cb4 100644 --- a/src/game/CAbstractActor.cpp +++ b/src/game/CAbstractActor.cpp @@ -436,9 +436,6 @@ CAbstractActor *CAbstractActor::EndScript() { teamColor = ReadLongVar(iTeam) % (kMaxTeamColors + 1); teamMask = 1 << teamColor; - gHub->PreLoadSample(blastSound); - gHub->PreLoadSample(hitSoundId); - partScale = ReadFixedVar(iScale); partYon = ReadFixedVar(iYon); @@ -446,7 +443,11 @@ CAbstractActor *CAbstractActor::EndScript() { friction = ReadFixedVar(iFriction); stepSound = ReadLongVar(iStepSound); - gHub->LoadSample(stepSound); + + // Preload sounds. + auto _ = AssetManager::GetOgg(blastSound); + _ = AssetManager::GetOgg(hitSoundId); + _ = AssetManager::GetOgg(stepSound); return this; } diff --git a/src/game/CAbstractActor.h b/src/game/CAbstractActor.h index 9cc89df2e..daa5ab4c6 100644 --- a/src/game/CAbstractActor.h +++ b/src/game/CAbstractActor.h @@ -8,6 +8,7 @@ */ #pragma once +#include "AssetManager.h" #include "CAvaraGame.h" #include "CDirectObject.h" #include "ColorManager.h" diff --git a/src/game/CAbstractMissile.cpp b/src/game/CAbstractMissile.cpp index 07bb70a2b..fb7e22122 100644 --- a/src/game/CAbstractMissile.cpp +++ b/src/game/CAbstractMissile.cpp @@ -31,8 +31,8 @@ void CAbstractMissile::IAbstractMissile(CDepot *theDepot) { } void CAbstractMissile::PreLoadSounds() { - gHub->PreLoadSample(GROUNDHITSOUNDID); - gHub->PreLoadSample(soundResId); + auto _ = AssetManager::GetOgg(GROUNDHITSOUNDID); + _ = AssetManager::GetOgg(soundResId); } void CAbstractMissile::Deactivate() { diff --git a/src/game/CAbstractPlayer.cpp b/src/game/CAbstractPlayer.cpp index 30a51e5e1..6dc1f3ab5 100644 --- a/src/game/CAbstractPlayer.cpp +++ b/src/game/CAbstractPlayer.cpp @@ -253,9 +253,10 @@ CAbstractActor *CAbstractPlayer::EndScript() { loseSound = ReadLongVar(iLoseSound); loseVolume = ReadFixedVar(iLoseVolume); - gHub->PreLoadSample(incarnateSound); - gHub->PreLoadSample(winSound); - gHub->PreLoadSample(loseSound); + // Preload sounds. + auto _ = AssetManager::GetOgg(incarnateSound); + _ = AssetManager::GetOgg(winSound); + _ = AssetManager::GetOgg(loseSound); lives = ReadLongVar(iLives); diff --git a/src/game/CAvaraApp.cpp b/src/game/CAvaraApp.cpp index c8cadb6ba..e391ca043 100755 --- a/src/game/CAvaraApp.cpp +++ b/src/game/CAvaraApp.cpp @@ -11,6 +11,7 @@ #include "CAvaraApp.h" +#include "AssetManager.h" #include "AvaraGL.h" #include "AvaraScoreInterface.h" #include "AvaraTCP.h" @@ -26,7 +27,6 @@ #include "LevelLoader.h" #include "Parser.h" #include "Preferences.h" -#include "Resource.h" #include "System.h" #include "InfoMessages.h" #include "Messages.h" @@ -68,6 +68,7 @@ void TrackerPinger(CAvaraAppImpl *app) { } CAvaraAppImpl::CAvaraAppImpl() : CApplication("Avara") { + AssetManager::Init(); itsGame = std::make_unique(Get(kFrameTimeTag)); gCurrentGame = itsGame.get(); itsGame->IAvaraGame(this); @@ -112,8 +113,6 @@ CAvaraAppImpl::CAvaraAppImpl() : CApplication("Avara") { trackerThread = new std::thread(TrackerPinger, this); trackerThread->detach(); - LoadDefaultOggFiles(); - // register and handle text commands itsTui = new CommandManager(this); @@ -270,50 +269,13 @@ OSErr CAvaraAppImpl::LoadLevel(std::string set, std::string levelTag, CPlayerMan itsGame->LevelReset(false); gCurrentGame = itsGame.get(); itsGame->loadedSet = set; - UseLevelFolder(set); - - OSErr result = fnfErr; - json setManifest = GetManifestJSON(set); - if (setManifest == -1) return result; - if (setManifest.find("LEDI") == setManifest.end()) return result; - - if (setManifest.find("REQD") == setManifest.end()) { - ClearExternalPackages(); - } else { - for (auto &ext : setManifest["REQD"].items()) { - json pkg = ext.value(); - std::string pkgPath = pkg.value("Package", ""); - // TODO: Support version constraints? - if (!pkgPath.empty() && - pkgPath.rfind(".", 0) != 0 && - pkgPath.find("..") == std::string::npos && - pkgPath.find("/") == std::string::npos && - pkgPath.find("\\") == std::string::npos) { - AddExternalPackage(pkgPath); - } - } - } - - json ledi = NULL; - for (auto &ld : setManifest["LEDI"].items()) { - if (ld.value()["Alf"] == levelTag) - ledi = ld.value(); - } - if (ledi == NULL) return result; - - if (ledi.contains("Aftershock") && ledi["Aftershock"] == true) { - UseBaseFolder("rsrc/aftershock"); - } else { - UseBaseFolder("rsrc"); - } - - LoadLevelOggFiles(set); - if(LoadALF(GetALFPath(levelTag))) result = noErr; + std::string levelName; + OSErr result = AssetManager::LoadLevel(set, levelTag, levelName); if (result == noErr) { playerWindow->RepopulateHullOptions(); - itsGame->loadedLevel = ledi["Name"]; + itsGame->loadedLevel = levelName; itsGame->loadedFilename = levelTag; itsGame->loadedTags = Tags::GetTagsForLevel(Tags::LevelURL(itsGame->loadedSet, itsGame->loadedLevel)); std::string msgStr = "Loaded"; diff --git a/src/game/CAvaraGame.cpp b/src/game/CAvaraGame.cpp index 8f79e0eac..b0423ecfb 100755 --- a/src/game/CAvaraGame.cpp +++ b/src/game/CAvaraGame.cpp @@ -17,6 +17,7 @@ #define INIT_ADVANCE (TIMING_GRAIN) */ +#include "AssetManager.h" #include "AvaraGL.h" #include "CAvaraGame.h" #include "CAvaraApp.h" @@ -524,7 +525,7 @@ void CAvaraGame::RunActorFrameActions() { } void CAvaraGame::ChangeDirectoryFile() { - gHub->FreeUnusedSamples(); + // No-op. } void CAvaraGame::LevelReset(Boolean clearReset) { @@ -533,8 +534,6 @@ void CAvaraGame::LevelReset(Boolean clearReset) { gameStatus = kAbortStatus; - gHub->FlagOldSamples(); - loadedLevel = ""; loadedDesigner = ""; loadedInfo = ""; @@ -657,11 +656,11 @@ void CAvaraGame::EndScript() { groundFriction = ReadFixedVar(iDefaultFriction); gravityRatio = ReadFixedVar(iGravity); groundStepSound = ReadLongVar(iGroundStepSound); - gHub->LoadSample(groundStepSound); - itsDepot->EndScript(); + // Preload sounds. + auto _ = AssetManager::GetOgg(groundStepSound); - gHub->FreeOldSamples(); + itsDepot->EndScript(); scoreKeeper->EndScript(); } diff --git a/src/game/CBall.cpp b/src/game/CBall.cpp index 852dcb278..f60dd7e1e 100644 --- a/src/game/CBall.cpp +++ b/src/game/CBall.cpp @@ -149,19 +149,21 @@ CAbstractActor *CBall::EndScript() { buzzSound = ReadLongVar(iSound); buzzVolume = ReadFixedVar(iVolume); - gHub->PreLoadSample(buzzSound); ejectSound = ReadLongVar(iEjectSound); ejectVolume = ReadFixedVar(iEjectVolume); - gHub->PreLoadSample(ejectSound); reprogramSound = ReadLongVar(iChangeOwnerSound); reprogramVolume = ReadFixedVar(iChangeOwnerVolume); - gHub->PreLoadSample(reprogramSound); snapSound = ReadLongVar(iSnapSound); snapVolume = ReadFixedVar(iSnapVolume); - gHub->PreLoadSample(snapSound); + + // Preload sounds. + auto _ = AssetManager::GetOgg(buzzSound); + _ = AssetManager::GetOgg(ejectSound); + _ = AssetManager::GetOgg(reprogramSound); + _ = AssetManager::GetOgg(snapSound); pitchZ = FMul(ejectPower, FDegCos(ejectPitch)); pitchY = FMul(ejectPower, FDegSin(ejectPitch)); diff --git a/src/game/CDoorActor.cpp b/src/game/CDoorActor.cpp index 12ceb8558..55e4b67c6 100644 --- a/src/game/CDoorActor.cpp +++ b/src/game/CDoorActor.cpp @@ -8,6 +8,7 @@ */ // #define ENABLE_FPS_DEBUG // uncomment if you want to see FPS_DEBUG output for this file +#include "AssetManager.h" #include "CDoorActor.h" #include "CSmartPart.h" @@ -150,7 +151,8 @@ CAbstractActor *CDoorActor::EndScript() { resId = ReadLongVar(iShape); - if (HasBSP(resId)) { + auto bsp = AssetManager::GetBsp(resId); + if (bsp) { //CBSPWorld *theWorld; RegisterReceiver(&openActivator, ReadLongVar(iOpenMsg)); @@ -164,9 +166,10 @@ CAbstractActor *CDoorActor::EndScript() { closeSoundId = ReadLongVar(iCloseSound); stopSoundId = ReadLongVar(iStopSound); - gHub->PreLoadSample(openSoundId); - gHub->PreLoadSample(closeSoundId); - gHub->PreLoadSample(stopSoundId); + // Preload sounds. + auto _ = AssetManager::GetOgg(openSoundId); + _ = AssetManager::GetOgg(closeSoundId); + _ = AssetManager::GetOgg(stopSoundId); openCounter = 0; closeCounter = 0; diff --git a/src/game/CGoal.cpp b/src/game/CGoal.cpp index 4fe182be9..741afdb47 100644 --- a/src/game/CGoal.cpp +++ b/src/game/CGoal.cpp @@ -7,6 +7,7 @@ Modified: Wednesday, September 4, 1996, 00:06 */ +#include "AssetManager.h" #include "CGoal.h" #include "CBall.h" @@ -52,11 +53,14 @@ CAbstractActor *CGoal::EndScript() { goalSound = ReadLongVar(iSound); goalVolume = ReadFixedVar(iVolume); - gHub->PreLoadSample(goalSound); + + // Preload sounds. + auto _ = AssetManager::GetOgg(goalSound); resId = ReadLongVar(iShape); - if (HasBSP(resId)) { + auto bsp = AssetManager::GetBsp(resId); + if (bsp) { LoadPartWithColors(0, resId); partList[0]->Reset(); partList[0]->RotateZ(ReadFixedVar(iRoll)); diff --git a/src/game/CGoody.cpp b/src/game/CGoody.cpp index 92c6db0c0..9a845e7ee 100644 --- a/src/game/CGoody.cpp +++ b/src/game/CGoody.cpp @@ -102,9 +102,10 @@ CAbstractActor *CGoody::EndScript() { closeSoundId = ReadLongVar(iCloseSound); volume = ReadFixedVar(iVolume); - gHub->PreLoadSample(soundId); - gHub->PreLoadSample(openSoundId); - gHub->PreLoadSample(closeSoundId); + // Preload sounds. + auto _ = AssetManager::GetOgg(soundId); + _ = AssetManager::GetOgg(openSoundId); + _ = AssetManager::GetOgg(closeSoundId); isActive = kIsInactive; frequency = ReadLongVar(iFrequency); diff --git a/src/game/CGrenade.cpp b/src/game/CGrenade.cpp index 5dbe34e10..df519026d 100644 --- a/src/game/CGrenade.cpp +++ b/src/game/CGrenade.cpp @@ -287,5 +287,5 @@ void CGrenade::ShowTarget() { void CGrenade::PreLoadSounds() { CWeapon::PreLoadSounds(); - gHub->PreLoadSample(201); + auto _ = AssetManager::GetOgg(201); } diff --git a/src/game/CHologramActor.cpp b/src/game/CHologramActor.cpp index bd199a719..3f1e4d944 100644 --- a/src/game/CHologramActor.cpp +++ b/src/game/CHologramActor.cpp @@ -7,6 +7,7 @@ Modified: Friday, March 29, 1996, 10:44 */ +#include "AssetManager.h" #include "CHologramActor.h" #include "CBSPPart.h" @@ -27,7 +28,8 @@ CAbstractActor *CHologramActor::EndScript() { resId = ReadLongVar(iShape); - if (HasBSP(resId)) { + auto bsp = AssetManager::GetBsp(resId); + if (bsp) { CBSPWorld *theWorld; CBSPPart *thePart; diff --git a/src/game/CMineActor.cpp b/src/game/CMineActor.cpp index 23b35769c..0df7d5f43 100644 --- a/src/game/CMineActor.cpp +++ b/src/game/CMineActor.cpp @@ -76,7 +76,10 @@ CAbstractActor *CMineActor::EndScript() { activateSound = ReadLongVar(iActivateSound); activateVolume = ReadFixedVar(iActivateVolume); - gHub->PreLoadSample(activateSound); + + // Preload sounds. + auto _ = AssetManager::GetOgg(activateSound); + RegisterReceiver(&activator, ReadLongVar(iBoom)); radius = ReadFixedVar(iRange); diff --git a/src/game/CNetManager.cpp b/src/game/CNetManager.cpp index 161c3424a..a51051577 100644 --- a/src/game/CNetManager.cpp +++ b/src/game/CNetManager.cpp @@ -11,6 +11,7 @@ #include "CNetManager.h" +#include "AssetManager.h" #include "CApplication.h" #include "CAvaraGame.h" #include "CPlayerManager.h" @@ -658,8 +659,7 @@ void CNetManager::ResumeGame() { // Pull grenade/missile/booster counts from HULL resource long hullRes = ReadLongVar(iHull01 + config.hullType); - HullConfigRecord hull; - LoadHullFromSetJSON(&hull, hullRes); + HullConfigRecord hull = **AssetManager::GetHull(hullRes); config.numGrenades = hull.maxGrenades; config.numMissiles = hull.maxMissiles; config.numBoosters = hull.maxBoosters; diff --git a/src/game/CParasite.cpp b/src/game/CParasite.cpp index cf671e4d1..3553bdf05 100644 --- a/src/game/CParasite.cpp +++ b/src/game/CParasite.cpp @@ -47,7 +47,9 @@ void CParasite::BeginScript() { CAbstractActor *CParasite::EndScript() { if (CRealMovers::EndScript()) { - gHub->PreLoadSample(clampSound = ReadLongVar(iSound)); + // Preload sounds. + auto _ = AssetManager::GetOgg(clampSound = ReadLongVar(iSound)); + clampVolume = ReadFixedVar(iVolume); maxPower = ReadFixedVar(iMaxPower); energyDrain = ReadFixedVar(iDrain); diff --git a/src/game/CScout.cpp b/src/game/CScout.cpp index 3905022af..00c1245be 100644 --- a/src/game/CScout.cpp +++ b/src/game/CScout.cpp @@ -48,7 +48,9 @@ void CScout::IScout(CAbstractPlayer *thePlayer, short theTeam, ARGBColor longTea partList[0]->ReplaceColor(*ColorManager::getMarkerColor(0), longTeamColor); hitSoundId = 220; - gHub->PreLoadSample(hitSoundId); + + // Preload sounds. + auto _ = AssetManager::GetOgg(hitSoundId); glow = 0; } diff --git a/src/game/CSmart.cpp b/src/game/CSmart.cpp index e2190e4ba..73e913887 100644 --- a/src/game/CSmart.cpp +++ b/src/game/CSmart.cpp @@ -421,7 +421,7 @@ if (IsClassicInterval()) { void CSmart::PreLoadSounds() { CWeapon::PreLoadSounds(); - gHub->PreLoadSample(201); + auto _ = AssetManager::GetOgg(201); } bool CSmart::IsClassicInterval() { diff --git a/src/game/CSolidActor.cpp b/src/game/CSolidActor.cpp index 4e9c774a1..0c8ec8519 100644 --- a/src/game/CSolidActor.cpp +++ b/src/game/CSolidActor.cpp @@ -7,6 +7,7 @@ Modified: Monday, February 26, 1996, 16:00 */ +#include "AssetManager.h" #include "CSolidActor.h" #include "CSmartPart.h" @@ -28,7 +29,8 @@ CAbstractActor *CSolidActor::EndScript() { resId = ReadLongVar(iShape); - if (HasBSP(resId)) { + auto bsp = AssetManager::GetBsp(resId); + if (bsp) { LoadPartWithColors(0, resId); partList[0]->Reset(); partList[0]->RotateZ(ReadFixedVar(iRoll)); diff --git a/src/game/CSoundActor.cpp b/src/game/CSoundActor.cpp index c31712ef9..aaf954d2e 100644 --- a/src/game/CSoundActor.cpp +++ b/src/game/CSoundActor.cpp @@ -50,7 +50,8 @@ CAbstractActor *CSoundActor::EndScript() { rate = ReadFixedVar(iRate); if (!isAmbient || (itsGame->soundSwitches & kAmbientSoundToggle)) - gHub->PreLoadSample(soundId); + // Preload sounds. + auto _ = AssetManager::GetOgg(soundId); if (isPlaced) { itsSoundLink = gHub->GetSoundLink(); diff --git a/src/game/CTeleporter.cpp b/src/game/CTeleporter.cpp index 4c580c705..8765d4dfa 100644 --- a/src/game/CTeleporter.cpp +++ b/src/game/CTeleporter.cpp @@ -84,7 +84,9 @@ CAbstractActor *CTeleporter::EndScript() { soundId = ReadLongVar(iSound); volume = ReadFixedVar(iVolume); - gHub->PreLoadSample(soundId); + + // Preload sounds. + auto _ = AssetManager::GetOgg(soundId); options = ReadLongVar(iSpinFlag) ? kSpinOption : 0; options |= ReadLongVar(iFragmentFlag) ? kFragmentOption : 0; diff --git a/src/game/CTextActor.cpp b/src/game/CTextActor.cpp index 457afb073..449d9eeec 100644 --- a/src/game/CTextActor.cpp +++ b/src/game/CTextActor.cpp @@ -58,7 +58,9 @@ CAbstractActor *CTextActor::EndScript() { soundId = ReadLongVar(iSound); soundVol = ReadFixedVar(iVolume); - gHub->PreLoadSample(soundId); + + // Preload sounds. + auto _ = AssetManager::GetOgg(soundId); return this; } else { diff --git a/src/game/CWalkerActor.cpp b/src/game/CWalkerActor.cpp index ba1acbbdd..9545ca793 100644 --- a/src/game/CWalkerActor.cpp +++ b/src/game/CWalkerActor.cpp @@ -11,6 +11,7 @@ #include "CWalkerActor.h" +#include "AssetManager.h" #include "AvaraDefines.h" #include "CBSPWorld.h" #include "CDepot.h" @@ -770,7 +771,7 @@ void CWalkerActor::ReceiveConfig(PlayerConfigRecord *config) { hullRes = ReadLongVar(iHull01 + hullRes); - LoadHullFromSetJSON(&hullConfig, hullRes); + hullConfig = **AssetManager::GetHull(hullRes); hullRes = hullConfig.hullBSP; itsGame->itsWorld->RemovePart(viewPortPart); diff --git a/src/game/CWallActor.cpp b/src/game/CWallActor.cpp index 9c8d3e791..3e7ecf698 100644 --- a/src/game/CWallActor.cpp +++ b/src/game/CWallActor.cpp @@ -114,7 +114,9 @@ void CWallActor::MakeWallFromRect(Rect *theRect, Fixed height, short decimateWal itsGame->AddActor(this); stepSound = ReadLongVar(iStepSound); - gHub->LoadSample(stepSound); + + // Preload sounds. + auto _ = AssetManager::GetOgg(stepSound); lastWallActor = this; diff --git a/src/game/CWeapon.cpp b/src/game/CWeapon.cpp index 9fdab9960..5ea3430bb 100644 --- a/src/game/CWeapon.cpp +++ b/src/game/CWeapon.cpp @@ -156,7 +156,7 @@ void CWeapon::PostMortemBlast(short scoreTeam, short scoreColor, Boolean doDispo } void CWeapon::PreLoadSounds() { - gHub->PreLoadSample(blastSound); + auto _ = AssetManager::GetOgg(blastSound); } void CWeapon::Accelerate(Fixed *direction) { diff --git a/src/game/PlayerConfig.h b/src/game/PlayerConfig.h index 5666f14f0..ac847ad4e 100644 --- a/src/game/PlayerConfig.h +++ b/src/game/PlayerConfig.h @@ -10,6 +10,7 @@ #pragma once #include "ARGBColor.h" #include "ColorManager.h" +#include "FastMat.h" struct PlayerConfigRecord { short numGrenades {}; diff --git a/src/gui/CLevelWindow.cpp b/src/gui/CLevelWindow.cpp index 9ae18c17b..21840de5d 100644 --- a/src/gui/CLevelWindow.cpp +++ b/src/gui/CLevelWindow.cpp @@ -1,28 +1,23 @@ #include "CLevelWindow.h" +#include "AssetManager.h" #include "CAvaraApp.h" #include "CAvaraGame.h" #include "CNetManager.h" #include "Preferences.h" -#include "Resource.h" - -#include CLevelWindow::CLevelWindow(CApplication *app) : CWindow(app, "Levels") { // Searches "levels/" directory alongside application. // will eventually use level search API - levelSets = LevelDirNameListing(); + levelSets = AssetManager::GetAvailablePackages(); json sets = app->Get(kRecentSets); json levels = app->Get(kRecentLevels); if (sets.size() == levels.size()) { for (unsigned i = 0; i < sets.size(); ++i) { std::string set = sets.at(i); - std::stringstream subDir; - subDir << LEVELDIR << PATHSEP << set; - char subDirPath[PATH_MAX]; - BundlePath(subDir, subDirPath); - if (cf_file_exists(subDirPath)) { + auto exists = AssetManager::PackageInStorage(set); + if (exists) { recentSets.push_back(set); recentLevels.push_back(levels.at(i)); } @@ -129,11 +124,11 @@ void CLevelWindow::SelectSet(std::string set) { levelIntros.clear(); levelTags.clear(); - nlohmann::json ledis = LoadLevelListFromJSON(set); - for (auto &ld : ledis.items()) { - levelNames.push_back(ld.value()["Name"]); - levelIntros.push_back(ld.value()["Message"]); - levelTags.push_back(ld.value()["Alf"]); + auto manifest = *AssetManager::GetManifest(set); + for (auto const &ledi : manifest->levelDirectory) { + levelNames.push_back(ledi.levelName); + levelIntros.push_back(ledi.levelInfo); + levelTags.push_back(ledi.alfPath); } levelBox->setItems(levelNames, levelIntros); levelBox->setEnabled(true); diff --git a/src/level/LevelLoader.cpp b/src/level/LevelLoader.cpp index 1b5e67622..7a6f8df72 100644 --- a/src/level/LevelLoader.cpp +++ b/src/level/LevelLoader.cpp @@ -9,13 +9,13 @@ #include "LevelLoader.h" +#include "AssetManager.h" #include "AvaraGL.h" #include "CAvaraGame.h" #include "CWallActor.h" #include "FastMat.h" #include "Memory.h" #include "Parser.h" -#include "Resource.h" #include "pugixml.hpp" #include @@ -288,13 +288,13 @@ struct ALFWalker: pugi::xml_tree_walker { void handle_enum(pugi::xml_node& node) { std::stringstream script; script << "enum " << node.attribute("start").value() << " " << node.attribute("vars").value() << " end"; - RunThis((StringPtr)script.str().c_str()); + RunThis(script.str()); } void handle_unique(pugi::xml_node& node) { std::stringstream script; script << "unique " << node.attribute("vars").value() << " end"; - RunThis((StringPtr)script.str().c_str()); + RunThis(script.str()); } void handle_set(pugi::xml_node& node) { @@ -308,11 +308,11 @@ struct ALFWalker: pugi::xml_tree_walker { } std::string result = script.str(); if (wrote && result.length() > 0) - RunThis((StringPtr)result.c_str()); + RunThis(result); } void handle_script(pugi::xml_node& node) { - RunThis((StringPtr)node.child_value()); + RunThis(std::string(node.child_value())); } void handle_wall(pugi::xml_node& node) { @@ -320,7 +320,7 @@ struct ALFWalker: pugi::xml_tree_walker { if (!y.empty()) { std::stringstream script; script << "wa = " << y << "\n"; - RunThis((StringPtr)script.str().c_str()); + RunThis(script.str()); } CWallActor *theWall = new CWallActor; theWall->IAbstractActor(); @@ -340,13 +340,15 @@ struct ALFWalker: pugi::xml_tree_walker { // Ensure path separators are appropriate for the current platform. std::regex pattern("\\\\|/"); path = std::regex_replace(path, pattern, PATHSEP); - path = GetALFPath(path); - pugi::xml_document shard; - pugi::xml_parse_result result = shard.load_file(path.c_str()); - if (result) { - ALFWalker includeWalker(depth + 1); - shard.traverse(includeWalker); + std::optional maybePath = AssetManager::GetResolvedAlfPath(path); + if (maybePath) { + pugi::xml_document shard; + pugi::xml_parse_result result = shard.load_file(maybePath->c_str()); + if (result) { + ALFWalker includeWalker(depth + 1); + shard.traverse(includeWalker); + } } } } @@ -360,7 +362,7 @@ struct ALFWalker: pugi::xml_tree_walker { script << attr << " = " << value << "\n"; } script << "end"; - RunThis((StringPtr)script.str().c_str()); + RunThis(script.str()); } private: diff --git a/src/level/Parser.cpp b/src/level/Parser.cpp index 1c0476f7e..4558c1a45 100644 --- a/src/level/Parser.cpp +++ b/src/level/Parser.cpp @@ -9,6 +9,7 @@ #include "Parser.h" +#include "AssetManager.h" #include "CAbstractActor.h" #include "CStringDictionary.h" #include "PascalStrings.h" @@ -23,7 +24,6 @@ #include "CApplication.h" #include "FastMat.h" -#include "Resource.h" #include "Types.h" #define STACKSIZE 256 @@ -988,11 +988,13 @@ char *fixedString(unsigned char *s) { return fixed; } -void RunThis(unsigned char *script) { +void RunThis(std::string script) { LexSymbol statement; + StringPtr scriptPtr = (StringPtr)script.c_str(); + #ifdef DEBUGPARSER - char *formattedScript = fixedString(script); + char *formattedScript = fixedString(scriptPtr); SDL_Log("Running script:\n%s\n", formattedScript); std::free(formattedScript); #endif @@ -1014,7 +1016,7 @@ void RunThis(unsigned char *script) { } */ - SetupCompiler(script); + SetupCompiler(scriptPtr); LexRead(&parserVar.lookahead); do { @@ -1055,27 +1057,12 @@ void AllocParser() { currentActor = NULL; - // Load default script from "base" resource pack (Avara, Aftershock). - std::string base = GetBaseScript(); - if (base.length() > 0) { - RunThis((StringPtr)base.c_str()); - } - - // Load default script from "external" resource packs required by the - // current level set. (If multiple are defined, they are loaded in - // *reverse* order.) - std::vector ext = GetExternalScripts(); - for (auto &extScript : ext) { - if (extScript.length() > 0) { - RunThis((StringPtr)extScript.c_str()); + std::vector> scripts = AssetManager::GetAllScripts(); + for (auto const &script : scripts) { + if (script->length() > 0) { + RunThis(*script); } } - - // Load default script for the current level set. - std::string def = GetDefaultScript(); - if (def.length() > 0) { - RunThis((StringPtr)def.c_str()); - } } void DeallocParser() { diff --git a/src/level/Parser.h b/src/level/Parser.h index c571cd91b..40a633ec4 100644 --- a/src/level/Parser.h +++ b/src/level/Parser.h @@ -11,6 +11,8 @@ #include "ARGBColor.h" #include "Types.h" +#include + #ifdef __has_include # if __has_include() // Check for a standard library # include @@ -103,7 +105,7 @@ void LoadLevel(short whichLevel); double EvalVariable(long token, Boolean forceCalc); void WriteVariable(long token, double value); -void RunThis(StringPtr theScript); +void RunThis(std::string theScript); void AllocParser(); void DeallocParser(); void InitParser(); diff --git a/src/tests.cpp b/src/tests.cpp index 0a1970011..298919c4e 100644 --- a/src/tests.cpp +++ b/src/tests.cpp @@ -1,5 +1,6 @@ #include "gtest/gtest.h" #include +#include "AssetManager.h" #include "CAvaraApp.h" #include "CBSPPart.h" #include "CPlayerManager.h" @@ -131,6 +132,8 @@ class TestNetManager : public CNetManager { class TestApp : public CAvaraApp { public: TestApp() { + AssetManager::Init(); + // Silence SDL logging with a no-op callback. SDL_LogSetOutputFunction([](void *d, int c, SDL_LogPriority p, const char *m){}, nullptr); } diff --git a/src/tui/CommandManager.cpp b/src/tui/CommandManager.cpp index cacdbd9c4..10a113296 100644 --- a/src/tui/CommandManager.cpp +++ b/src/tui/CommandManager.cpp @@ -1,6 +1,7 @@ #include "CommandManager.h" +#include "AssetManager.h" #include "CAvaraApp.h" #include "CAvaraGame.h" #include "CPlayerManager.h" @@ -8,7 +9,6 @@ #include "CScoreKeeper.h" #include "CommDefs.h" // kdEveryone -#include "Resource.h" // LevelDirNameListing #include "Debug.h" // Debug::methods #include // std::random_device #include "Tags.h" @@ -371,17 +371,17 @@ bool CommandManager::LoadNamedLevel(VectorOfArgs vargs) { } static int loadNumber = 0; - std::vector levelSets = LevelDirNameListing(); + std::vector levelSets = AssetManager::GetAvailablePackages(); std::string levelSubstr = join_with(vargs, " "); std::transform(levelSubstr.begin(), levelSubstr.end(),levelSubstr.begin(), ::toupper); std::vector> bestLevels = {}; for(std::string set : levelSets) { - nlohmann::json ledis = LoadLevelListFromJSON(set); - for (auto &ld : ledis.items()) { - std::string level = ld.value()["Name"].get(); - std::string levelUpper = ld.value()["Name"].get(); - std::transform(levelUpper.begin(), levelUpper.end(),levelUpper.begin(), ::toupper); + auto manifest = *AssetManager::GetManifest(set); + for (auto &ledi : manifest->levelDirectory) { + std::string level = ledi.levelName; + std::string levelUpper = ledi.levelName; + std::transform(levelUpper.begin(), levelUpper.end(), levelUpper.begin(), ::toupper); // find levelSubstr anywhere within the level name if(levelUpper.find(levelSubstr) != std::string::npos) { @@ -435,14 +435,14 @@ bool CommandManager::LoadRandomLevel(VectorOfArgs matchArgs) { } } } else { - for (auto setName : LevelDirNameListing()) { + for (auto setName : AssetManager::GetAvailablePackages()) { if (setName.find(matchStr, 0) != std::string::npos) { - nlohmann::json levels = LoadLevelListFromJSON(setName); - for (auto level : levels) { + auto manifest = *AssetManager::GetManifest(setName); + for (auto level : manifest->levelDirectory) { if (addLevels) { - allLevels.insert(Tags::LevelURL(setName, level.at("Name"))); + allLevels.insert(Tags::LevelURL(setName, level.levelName)); } else { - allLevels.erase(Tags::LevelURL(setName, level.at("Name"))); + allLevels.erase(Tags::LevelURL(setName, level.levelName)); } } }