diff --git a/Source/DiabloUI/dialogs.cpp b/Source/DiabloUI/dialogs.cpp index ed1b3e9ec..8b125b76b 100644 --- a/Source/DiabloUI/dialogs.cpp +++ b/Source/DiabloUI/dialogs.cpp @@ -62,6 +62,9 @@ bool Init(std::string_view caption, std::string_view text, bool error, bool rend LogError("{}", SDL_GetError()); } } + if (!IsHardwareCursor() && !ArtCursor) { + ArtCursor = LoadPcx("ui_art\\cursor", /*transparentColor=*/0); + } LoadDialogButtonGraphics(); OptionalClxSprite dialogSprite = LoadDialogSprite(!caption.empty(), error); diff --git a/Source/appfat.cpp b/Source/appfat.cpp index 8eed34ed9..6b281b955 100644 --- a/Source/appfat.cpp +++ b/Source/appfat.cpp @@ -44,13 +44,18 @@ void FreeDlg() SNetDestroy(); } +[[noreturn]] void DisplayFatalErrorAndExit(std::string_view title, std::string_view body) +{ + FreeDlg(); + UiErrorOkDialog(title, body); + diablo_quit(1); +} + } // namespace void app_fatal(std::string_view str) { - FreeDlg(); - UiErrorOkDialog(_("Error"), str); - diablo_quit(1); + DisplayFatalErrorAndExit(_("Error"), str); } #ifdef _DEBUG @@ -62,32 +67,27 @@ void assert_fail(int nLineNo, const char *pszFile, const char *pszFail) void ErrDlg(const char *title, std::string_view error, std::string_view logFilePath, int logLineNr) { - FreeDlg(); - - std::string text = fmt::format(fmt::runtime(_(/* TRANSLATORS: Error message that displays relevant information for bug report */ "{:s}\n\nThe error occurred at: {:s} line {:d}")), error, logFilePath, logLineNr); - - UiErrorOkDialog(title, text); - diablo_quit(1); + DisplayFatalErrorAndExit( + title, + fmt::format(fmt::runtime(_(/* TRANSLATORS: Error message that displays relevant information for bug report */ "{:s}\n\nThe error occurred at: {:s} line {:d}")), + error, logFilePath, logLineNr)); } void InsertCDDlg(std::string_view archiveName) { - std::string text = fmt::format( - fmt::runtime(_("Unable to open main data archive ({:s}).\n" - "\n" - "Make sure that it is in the game folder.")), - archiveName); - - UiErrorOkDialog(_("Data File Error"), text); - diablo_quit(1); + DisplayFatalErrorAndExit(_("Data File Error"), + fmt::format(fmt::runtime(_("Unable to open main data archive ({:s}).\n" + "\n" + "Make sure that it is in the game folder.")), + archiveName)); } void DirErrorDlg(std::string_view error) { - std::string text = fmt::format(fmt::runtime(_(/* TRANSLATORS: Error when Program is not allowed to write data */ "Unable to write to location:\n{:s}")), error); - - UiErrorOkDialog(_("Read-Only Directory Error"), text); - diablo_quit(1); + DisplayFatalErrorAndExit( + _("Read-Only Directory Error"), + fmt::format(fmt::runtime(_(/* TRANSLATORS: Error when Program is not allowed to write data */ "Unable to write to location:\n{:s}")), + error)); } } // namespace devilution diff --git a/Source/control.cpp b/Source/control.cpp index dfe576592..d8ade43b8 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -56,6 +56,7 @@ #include "utils/screen_reader.hpp" #include "utils/sdl_geometry.h" #include "utils/sdl_ptrs.h" +#include "utils/status_macros.hpp" #include "utils/str_case.hpp" #include "utils/str_cat.hpp" #include "utils/string_or_view.hpp" @@ -876,22 +877,22 @@ void UpdateLifeManaPercent() MyPlayer->UpdateHitPointPercentage(); } -void InitMainPanel() +tl::expected InitMainPanel() { if (!HeadlessMode) { BottomBuffer.emplace(GetMainPanel().size.width, (GetMainPanel().size.height + PanelPaddingHeight) * (IsChatAvailable() ? 2 : 1)); pManaBuff.emplace(88, 88); pLifeBuff.emplace(88, 88); - LoadCharPanel(); - LoadLargeSpellIcons(); + RETURN_IF_ERROR(LoadCharPanel()); + RETURN_IF_ERROR(LoadLargeSpellIcons()); { - const OwnedClxSpriteList sprite = LoadCel("ctrlpan\\panel8", GetMainPanel().size.width); + ASSIGN_OR_RETURN(const OwnedClxSpriteList sprite, LoadCelWithStatus("ctrlpan\\panel8", GetMainPanel().size.width)); ClxDraw(*BottomBuffer, { 0, (GetMainPanel().size.height + PanelPaddingHeight) - 1 }, sprite[0]); } { const Point bulbsPosition { 0, 87 }; - const OwnedClxSpriteList statusPanel = LoadCel("ctrlpan\\p8bulbs", 88); + ASSIGN_OR_RETURN(const OwnedClxSpriteList statusPanel, LoadCelWithStatus("ctrlpan\\p8bulbs", 88)); ClxDraw(*pLifeBuff, bulbsPosition, statusPanel[0]); ClxDraw(*pManaBuff, bulbsPosition, statusPanel[1]); } @@ -901,7 +902,7 @@ void InitMainPanel() if (IsChatAvailable()) { if (!HeadlessMode) { { - const OwnedClxSpriteList sprite = LoadCel("ctrlpan\\talkpanl", GetMainPanel().size.width); + ASSIGN_OR_RETURN(const OwnedClxSpriteList sprite, LoadCelWithStatus("ctrlpan\\talkpanl", GetMainPanel().size.width)); ClxDraw(*BottomBuffer, { 0, (GetMainPanel().size.height + PanelPaddingHeight) * 2 - 1 }, sprite[0]); } multiButtons = LoadCel("ctrlpan\\p8but2", 33); @@ -917,11 +918,11 @@ void InitMainPanel() MainPanelFlag = false; LevelButtonDown = false; if (!HeadlessMode) { - LoadMainPanel(); - pMainPanelButtons = LoadCel("ctrlpan\\panel8bu", 71); + RETURN_IF_ERROR(LoadMainPanel()); + ASSIGN_OR_RETURN(pMainPanelButtons, LoadCelWithStatus("ctrlpan\\panel8bu", 71)); static const uint16_t CharButtonsFrameWidths[9] { 95, 41, 41, 41, 41, 41, 41, 41, 41 }; - pChrButtons = LoadCel("data\\charbut", CharButtonsFrameWidths); + ASSIGN_OR_RETURN(pChrButtons, LoadCelWithStatus("data\\charbut", CharButtonsFrameWidths)); } ResetMainPanelButtons(); if (!HeadlessMode) @@ -939,14 +940,16 @@ void InitMainPanel() if (!HeadlessMode) { InitSpellBook(); - pQLogCel = LoadCel("data\\quest", static_cast(SidePanelSize.width)); - GoldBoxBuffer = LoadCel("ctrlpan\\golddrop", 261); + ASSIGN_OR_RETURN(pQLogCel, LoadCelWithStatus("data\\quest", static_cast(SidePanelSize.width))); + ASSIGN_OR_RETURN(GoldBoxBuffer, LoadCelWithStatus("ctrlpan\\golddrop", 261)); } CloseGoldDrop(); CalculatePanelAreas(); if (!HeadlessMode) InitModifierHints(); + + return {}; } void DrawMainPanel(const Surface &out) diff --git a/Source/control.h b/Source/control.h index d99cddb86..5a757bc19 100644 --- a/Source/control.h +++ b/Source/control.h @@ -8,9 +8,11 @@ #include #include #include +#include #include #include +#include #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" @@ -127,7 +129,7 @@ void DrawFlaskValues(const Surface &out, Point pos, int currValue, int maxValue) */ void UpdateLifeManaPercent(); -void InitMainPanel(); +tl::expected InitMainPanel(); void DrawMainPanel(const Surface &out); /** diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 65342a561..7615268c7 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -90,6 +90,8 @@ #include "utils/parse_int.hpp" #include "utils/paths.h" #include "utils/screen_reader.hpp" +#include "utils/sdl_thread.h" +#include "utils/status_macros.hpp" #include "utils/str_cat.hpp" #include "utils/utf8.hpp" @@ -1269,68 +1271,77 @@ void DiabloDeinit() SDL_Quit(); } -void LoadLvlGFX() +tl::expected LoadLvlGFX() { assert(pDungeonCels == nullptr); constexpr int SpecialCelWidth = 64; + const auto loadAll = [](const char *cel, const char *til, const char *special) -> tl::expected { + ASSIGN_OR_RETURN(pDungeonCels, LoadFileInMemWithStatus(cel)); + ASSIGN_OR_RETURN(pMegaTiles, LoadFileInMemWithStatus(til)); + ASSIGN_OR_RETURN(pSpecialCels, LoadCelWithStatus(special, SpecialCelWidth)); + return {}; + }; + switch (leveltype) { case DTYPE_TOWN: if (gbIsHellfire) { - pDungeonCels = LoadFileInMem("nlevels\\towndata\\town.cel"); - pMegaTiles = LoadFileInMem("nlevels\\towndata\\town.til"); - } else { - pDungeonCels = LoadFileInMem("levels\\towndata\\town.cel"); - pMegaTiles = LoadFileInMem("levels\\towndata\\town.til"); + return loadAll( + "nlevels\\towndata\\town.cel", + "nlevels\\towndata\\town.til", + "levels\\towndata\\towns"); } - pSpecialCels = LoadCel("levels\\towndata\\towns", SpecialCelWidth); - break; + return loadAll( + "levels\\towndata\\town.cel", + "levels\\towndata\\town.til", + "levels\\towndata\\towns"); case DTYPE_CATHEDRAL: - pDungeonCels = LoadFileInMem("levels\\l1data\\l1.cel"); - pMegaTiles = LoadFileInMem("levels\\l1data\\l1.til"); - pSpecialCels = LoadCel("levels\\l1data\\l1s", SpecialCelWidth); - break; + return loadAll( + "levels\\l1data\\l1.cel", + "levels\\l1data\\l1.til", + "levels\\l1data\\l1s"); case DTYPE_CATACOMBS: - pDungeonCels = LoadFileInMem("levels\\l2data\\l2.cel"); - pMegaTiles = LoadFileInMem("levels\\l2data\\l2.til"); - pSpecialCels = LoadCel("levels\\l2data\\l2s", SpecialCelWidth); - break; + return loadAll( + "levels\\l2data\\l2.cel", + "levels\\l2data\\l2.til", + "levels\\l2data\\l2s"); case DTYPE_CAVES: - pDungeonCels = LoadFileInMem("levels\\l3data\\l3.cel"); - pMegaTiles = LoadFileInMem("levels\\l3data\\l3.til"); - pSpecialCels = LoadCel("levels\\l1data\\l1s", SpecialCelWidth); - break; + return loadAll( + "levels\\l3data\\l3.cel", + "levels\\l3data\\l3.til", + "levels\\l1data\\l1s"); case DTYPE_HELL: - pDungeonCels = LoadFileInMem("levels\\l4data\\l4.cel"); - pMegaTiles = LoadFileInMem("levels\\l4data\\l4.til"); - pSpecialCels = LoadCel("levels\\l2data\\l2s", SpecialCelWidth); - break; + return loadAll( + "levels\\l4data\\l4.cel", + "levels\\l4data\\l4.til", + "levels\\l2data\\l2s"); case DTYPE_NEST: - pDungeonCels = LoadFileInMem("nlevels\\l6data\\l6.cel"); - pMegaTiles = LoadFileInMem("nlevels\\l6data\\l6.til"); - pSpecialCels = LoadCel("levels\\l1data\\l1s", SpecialCelWidth); - break; + return loadAll( + "nlevels\\l6data\\l6.cel", + "nlevels\\l6data\\l6.til", + "levels\\l1data\\l1s"); case DTYPE_CRYPT: - pDungeonCels = LoadFileInMem("nlevels\\l5data\\l5.cel"); - pMegaTiles = LoadFileInMem("nlevels\\l5data\\l5.til"); - pSpecialCels = LoadCel("nlevels\\l5data\\l5s", SpecialCelWidth); - break; + return loadAll( + "nlevels\\l5data\\l5.cel", + "nlevels\\l5data\\l5.til", + "nlevels\\l5data\\l5s"); default: - app_fatal("LoadLvlGFX"); + return tl::make_unexpected("LoadLvlGFX"); } } -void LoadAllGFX() +tl::expected LoadAllGFX() { IncProgress(); #if !defined(USE_SDL1) && !defined(__vita__) InitVirtualGamepadGFX(); #endif IncProgress(); - InitObjectGFX(); + RETURN_IF_ERROR(InitObjectGFX()); IncProgress(); - InitMissileGFX(gbIsHellfire); + RETURN_IF_ERROR(InitMissileGFX(gbIsHellfire)); IncProgress(); + return {}; } /** @@ -2911,7 +2922,7 @@ void LoadGameLevelStash() gbIsHellfireSaveGame = isHellfireSaveGame; } -void LoadGameLevelDungeon(bool firstflag, lvl_entry lvldir, const Player &myPlayer) +tl::expected LoadGameLevelDungeon(bool firstflag, lvl_entry lvldir, const Player &myPlayer) { if (firstflag || lvldir == ENTRY_LOAD || !myPlayer._pLvlVisited[currlevel] || gbIsMultiplayer) { HoldThemeRooms(); @@ -2922,7 +2933,7 @@ void LoadGameLevelDungeon(bool firstflag, lvl_entry lvldir, const Player &myPlay IncProgress(); - InitMonsters(); + RETURN_IF_ERROR(InitMonsters()); InitItems(); CreateThemeRooms(); @@ -2943,16 +2954,17 @@ void LoadGameLevelDungeon(bool firstflag, lvl_entry lvldir, const Player &myPlay } else { HoldThemeRooms(); InitGolems(); - InitMonsters(); + RETURN_IF_ERROR(InitMonsters()); InitMissiles(); InitCorpses(); IncProgress(); - LoadLevel(); + RETURN_IF_ERROR(LoadLevel()); IncProgress(); } + return {}; } void LoadGameLevelSyncPlayerEntry(lvl_entry lvldir) @@ -3006,7 +3018,7 @@ void LoadGameLevelSetVisited() } } -void LoadGameLevelTown(bool firstflag, lvl_entry lvldir, const Player &myPlayer) +tl::expected LoadGameLevelTown(bool firstflag, lvl_entry lvldir, const Player &myPlayer) { for (int i = 0; i < MAXDUNX; i++) { // NOLINT(modernize-loop-convert) for (int j = 0; j < MAXDUNY; j++) { @@ -3022,7 +3034,7 @@ void LoadGameLevelTown(bool firstflag, lvl_entry lvldir, const Player &myPlayer) IncProgress(); if (!firstflag && lvldir != ENTRY_LOAD && myPlayer._pLvlVisited[currlevel] && !gbIsMultiplayer) - LoadLevel(); + RETURN_IF_ERROR(LoadLevel()); if (gbIsMultiplayer) DeltaLoadLevel(); @@ -3031,22 +3043,23 @@ void LoadGameLevelTown(bool firstflag, lvl_entry lvldir, const Player &myPlayer) for (int x = 0; x < DMAXX; x++) for (int y = 0; y < DMAXY; y++) UpdateAutomapExplorer({ x, y }, MAP_EXP_SELF); + return {}; } -void LoadGameLevelSetLevel(bool firstflag, lvl_entry lvldir, const Player &myPlayer) +tl::expected LoadGameLevelSetLevel(bool firstflag, lvl_entry lvldir, const Player &myPlayer) { LoadSetMap(); IncProgress(); - GetLevelMTypes(); + RETURN_IF_ERROR(GetLevelMTypes()); IncProgress(); InitGolems(); - InitMonsters(); + RETURN_IF_ERROR(InitMonsters()); IncProgress(); if (!HeadlessMode) { #if !defined(USE_SDL1) && !defined(__vita__) InitVirtualGamepadGFX(); #endif - InitMissileGFX(gbIsHellfire); + RETURN_IF_ERROR(InitMissileGFX(gbIsHellfire)); IncProgress(); } InitCorpses(); @@ -3071,7 +3084,7 @@ void LoadGameLevelSetLevel(bool firstflag, lvl_entry lvldir, const Player &myPla InitItems(); SavePreLighting(); } else { - LoadLevel(); + RETURN_IF_ERROR(LoadLevel()); } if (gbIsMultiplayer) { DeltaLoadLevel(); @@ -3082,9 +3095,10 @@ void LoadGameLevelSetLevel(bool firstflag, lvl_entry lvldir, const Player &myPla PlayDungMsgs(); InitMissiles(); IncProgress(); + return {}; } -void LoadGameLevelStandardLevel(bool firstflag, lvl_entry lvldir, const Player &myPlayer) +tl::expected LoadGameLevelStandardLevel(bool firstflag, lvl_entry lvldir, const Player &myPlayer) { CreateLevel(lvldir); @@ -3093,10 +3107,10 @@ void LoadGameLevelStandardLevel(bool firstflag, lvl_entry lvldir, const Player & SetRndSeedForDungeonLevel(); if (leveltype != DTYPE_TOWN) { - GetLevelMTypes(); + RETURN_IF_ERROR(GetLevelMTypes()); InitThemes(); if (!HeadlessMode) - LoadAllGFX(); + RETURN_IF_ERROR(LoadAllGFX()); } else if (!HeadlessMode) { IncProgress(); @@ -3106,7 +3120,7 @@ void LoadGameLevelStandardLevel(bool firstflag, lvl_entry lvldir, const Player & IncProgress(); - InitMissileGFX(gbIsHellfire); + RETURN_IF_ERROR(InitMissileGFX(gbIsHellfire)); IncProgress(); IncProgress(); @@ -3144,6 +3158,7 @@ void LoadGameLevelStandardLevel(bool firstflag, lvl_entry lvldir, const Player & ResyncMPQuests(); else ResyncQuests(); + return {}; } void LoadGameLevelCrypt() @@ -3166,7 +3181,7 @@ void LoadGameLevelCalculateCursor() CheckCursMove(); } -void LoadGameLevel(bool firstflag, lvl_entry lvldir) +tl::expected LoadGameLevel(bool firstflag, lvl_entry lvldir) { _music_id neededTrack = GetLevelMusic(leveltype); @@ -3177,13 +3192,13 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir) IncProgress(); - LoadTrns(); + RETURN_IF_ERROR(LoadTrns()); MakeLightTable(); - LoadLevelSOLData(); + RETURN_IF_ERROR(LoadLevelSOLData()); IncProgress(); - LoadLvlGFX(); + RETURN_IF_ERROR(LoadLvlGFX()); SetDungeonMicros(); ClearClxDrawCache(); @@ -3216,9 +3231,9 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir) Player &myPlayer = *MyPlayer; if (setlevel) { - LoadGameLevelSetLevel(firstflag, lvldir, myPlayer); + RETURN_IF_ERROR(LoadGameLevelSetLevel(firstflag, lvldir, myPlayer)); } else { - LoadGameLevelStandardLevel(firstflag, lvldir, myPlayer); + RETURN_IF_ERROR(LoadGameLevelStandardLevel(firstflag, lvldir, myPlayer)); } SyncPortals(); @@ -3228,7 +3243,7 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir) IncProgress(); if (firstflag) { - InitMainPanel(); + RETURN_IF_ERROR(InitMainPanel()); } IncProgress(); @@ -3250,6 +3265,7 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir) CompleteProgress(); LoadGameLevelCalculateCursor(); + return {}; } bool game_loop(bool bStartup) diff --git a/Source/diablo.h b/Source/diablo.h index dad7b00e6..a4063ad9a 100644 --- a/Source/diablo.h +++ b/Source/diablo.h @@ -95,7 +95,7 @@ void diablo_focus_pause(); void diablo_focus_unpause(); bool PressEscKey(); void DisableInputEventHandler(const SDL_Event &event, uint16_t modState); -void LoadGameLevel(bool firstflag, lvl_entry lvldir); +tl::expected LoadGameLevel(bool firstflag, lvl_entry lvldir); bool IsDiabloAlive(bool playSFX); void PrintScreen(SDL_Keycode vkey); diff --git a/Source/engine/assets.cpp b/Source/engine/assets.cpp index 691ebc356..3647ab2bb 100644 --- a/Source/engine/assets.cpp +++ b/Source/engine/assets.cpp @@ -233,4 +233,9 @@ tl::expected LoadAsset(std::string_view path) return AssetData { std::move(data), size }; } +std::string FailedToOpenFileErrorMessage(std::string_view path, std::string_view error) +{ + return fmt::format(fmt::runtime(_("Failed to open file:\n{:s}\n\n{:s}\n\nThe MPQ file(s) might be damaged. Please check the file integrity.")), path, error); +} + } // namespace devilution diff --git a/Source/engine/assets.hpp b/Source/engine/assets.hpp index 9a5ccdcf6..a0e5a41b6 100644 --- a/Source/engine/assets.hpp +++ b/Source/engine/assets.hpp @@ -221,9 +221,11 @@ struct AssetHandle { }; #endif +std::string FailedToOpenFileErrorMessage(std::string_view path, std::string_view error); + [[noreturn]] inline void FailedToOpenFileError(std::string_view path, std::string_view error) { - app_fatal(fmt::format(fmt::runtime(_("Failed to open file:\n{:s}\n\n{:s}\n\nThe MPQ file(s) might be damaged. Please check the file integrity.")), path, error)); + app_fatal(FailedToOpenFileErrorMessage(path, error)); } inline bool ValidatAssetRef(std::string_view path, const AssetRef &ref) diff --git a/Source/engine/load_cel.cpp b/Source/engine/load_cel.cpp index c9879c40b..b3d11511e 100644 --- a/Source/engine/load_cel.cpp +++ b/Source/engine/load_cel.cpp @@ -2,12 +2,17 @@ #include #include +#include #ifdef DEBUG_CEL_TO_CL2_SIZE #include #endif +#include + +#include "appfat.h" #include "mpq/mpq_common.hpp" +#include "utils/status_macros.hpp" #include "utils/str_cat.hpp" #ifdef UNPACKED_MPQS @@ -19,15 +24,15 @@ namespace devilution { -OwnedClxSpriteListOrSheet LoadCelListOrSheet(const char *pszName, PointerOrValue widthOrWidths) +tl::expected LoadCelListOrSheetWithStatus(const char *pszName, PointerOrValue widthOrWidths) { char path[MaxMpqPathSize]; *BufCopy(path, pszName, DEVILUTIONX_CEL_EXT) = '\0'; #ifdef UNPACKED_MPQS - return LoadClxListOrSheet(path); + return LoadClxListOrSheetWithStatus(path); #else size_t size; - std::unique_ptr data = LoadFileInMem(path, &size); + ASSIGN_OR_RETURN(std::unique_ptr data, LoadFileInMemWithStatus(path, &size)); #ifdef DEBUG_CEL_TO_CL2_SIZE std::cout << path; #endif @@ -35,4 +40,11 @@ OwnedClxSpriteListOrSheet LoadCelListOrSheet(const char *pszName, PointerOrValue #endif } +OwnedClxSpriteListOrSheet LoadCelListOrSheet(const char *pszName, PointerOrValue widthOrWidths) +{ + tl::expected result = LoadCelListOrSheetWithStatus(pszName, widthOrWidths); + if (DVL_PREDICT_FALSE(!result.has_value())) app_fatal(result.error()); + return std::move(result).value(); +} + } // namespace devilution diff --git a/Source/engine/load_cel.hpp b/Source/engine/load_cel.hpp index 289c31524..83739a040 100644 --- a/Source/engine/load_cel.hpp +++ b/Source/engine/load_cel.hpp @@ -1,9 +1,13 @@ #pragma once #include +#include + +#include #include "engine/clx_sprite.hpp" #include "utils/pointer_value_union.hpp" +#include "utils/status_macros.hpp" #ifdef UNPACKED_MPQS #define DEVILUTIONX_CEL_EXT ".clx" @@ -13,6 +17,8 @@ namespace devilution { +tl::expected LoadCelListOrSheetWithStatus(const char *pszName, PointerOrValue widthOrWidths); + OwnedClxSpriteListOrSheet LoadCelListOrSheet(const char *pszName, PointerOrValue widthOrWidths); inline OwnedClxSpriteList LoadCel(const char *pszName, uint16_t width) @@ -20,11 +26,23 @@ inline OwnedClxSpriteList LoadCel(const char *pszName, uint16_t width) return LoadCelListOrSheet(pszName, PointerOrValue { width }).list(); } +inline tl::expected LoadCelWithStatus(const char *pszName, uint16_t width) +{ + ASSIGN_OR_RETURN(OwnedClxSpriteListOrSheet result, LoadCelListOrSheetWithStatus(pszName, PointerOrValue { width })); + return std::move(result).list(); +} + inline OwnedClxSpriteList LoadCel(const char *pszName, const uint16_t *widths) { return LoadCelListOrSheet(pszName, PointerOrValue { widths }).list(); } +inline tl::expected LoadCelWithStatus(const char *pszName, const uint16_t *widths) +{ + ASSIGN_OR_RETURN(OwnedClxSpriteListOrSheet result, LoadCelListOrSheetWithStatus(pszName, PointerOrValue { widths })); + return std::move(result).list(); +} + inline OwnedClxSpriteSheet LoadCelSheet(const char *pszName, uint16_t width) { return LoadCelListOrSheet(pszName, PointerOrValue { width }).sheet(); diff --git a/Source/engine/load_cl2.cpp b/Source/engine/load_cl2.cpp index a8deb6991..e1730f730 100644 --- a/Source/engine/load_cl2.cpp +++ b/Source/engine/load_cl2.cpp @@ -5,6 +5,7 @@ #include #include "mpq/mpq_common.hpp" +#include "utils/status_macros.hpp" #include "utils/str_cat.hpp" #ifdef UNPACKED_MPQS @@ -16,17 +17,24 @@ namespace devilution { -OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue widthOrWidths) +tl::expected LoadCl2ListOrSheetWithStatus(const char *pszName, PointerOrValue widthOrWidths) { char path[MaxMpqPathSize]; *BufCopy(path, pszName, DEVILUTIONX_CL2_EXT) = '\0'; #ifdef UNPACKED_MPQS - return LoadClxListOrSheet(path); + return LoadClxListOrSheetWithStatus(path); #else size_t size; - std::unique_ptr data = LoadFileInMem(path, &size); + ASSIGN_OR_RETURN(std::unique_ptr data, LoadFileInMemWithStatus(path, &size)); return Cl2ToClx(std::move(data), size, widthOrWidths); #endif } +OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue widthOrWidths) +{ + tl::expected result = LoadCl2ListOrSheetWithStatus(pszName, widthOrWidths); + if (!result.has_value()) app_fatal(result.error()); + return std::move(result).value(); +} + } // namespace devilution diff --git a/Source/engine/load_cl2.hpp b/Source/engine/load_cl2.hpp index 95dec272b..85bf46e1a 100644 --- a/Source/engine/load_cl2.hpp +++ b/Source/engine/load_cl2.hpp @@ -3,7 +3,9 @@ #include #include #include +#include +#include #include #include "appfat.h" @@ -14,6 +16,7 @@ #include "utils/endian.hpp" #include "utils/pointer_value_union.hpp" #include "utils/static_vector.hpp" +#include "utils/status_macros.hpp" #include "utils/str_cat.hpp" #ifdef UNPACKED_MPQS @@ -24,10 +27,11 @@ namespace devilution { +tl::expected LoadCl2ListOrSheetWithStatus(const char *pszName, PointerOrValue widthOrWidths); OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue widthOrWidths); template -OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref filenames, size_t count, uint16_t width) +tl::expected LoadMultipleCl2Sheet(tl::function_ref filenames, size_t count, uint16_t width) { StaticVector, MaxCount> paths; StaticVector files; @@ -70,6 +74,12 @@ OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref #endif } +inline tl::expected LoadCl2WithStatus(const char *pszName, uint16_t width) +{ + ASSIGN_OR_RETURN(OwnedClxSpriteListOrSheet result, LoadCl2ListOrSheetWithStatus(pszName, PointerOrValue { width })); + return std::move(result).list(); +} + inline OwnedClxSpriteList LoadCl2(const char *pszName, uint16_t width) { return LoadCl2ListOrSheet(pszName, PointerOrValue { width }).list(); diff --git a/Source/engine/load_clx.cpp b/Source/engine/load_clx.cpp index 6326a04ae..2e016ddae 100644 --- a/Source/engine/load_clx.cpp +++ b/Source/engine/load_clx.cpp @@ -8,6 +8,7 @@ #include "utils/sdl2_to_1_2_backports.h" #endif +#include "appfat.h" #include "engine/assets.hpp" #include "engine/load_file.hpp" @@ -28,11 +29,19 @@ OptionalOwnedClxSpriteListOrSheet LoadOptionalClxListOrSheet(const char *path) return OwnedClxSpriteListOrSheet::FromBuffer(std::move(data), size); } -OwnedClxSpriteListOrSheet LoadClxListOrSheet(const char *path) +tl::expected LoadClxListOrSheetWithStatus(const char *path) { size_t size; - std::unique_ptr data = LoadFileInMem(path, &size); - return OwnedClxSpriteListOrSheet::FromBuffer(std::move(data), size); + tl::expected, std::string> data = LoadFileInMemWithStatus(path, &size); + if (!data.has_value()) return tl::make_unexpected(std::move(data).error()); + return OwnedClxSpriteListOrSheet::FromBuffer(std::move(data).value(), size); +} + +OwnedClxSpriteListOrSheet LoadClxListOrSheet(const char *path) +{ + tl::expected result = LoadClxListOrSheetWithStatus(path); + if (!result.has_value()) app_fatal(result.error()); + return std::move(result).value(); } } // namespace devilution diff --git a/Source/engine/load_clx.hpp b/Source/engine/load_clx.hpp index f53476e77..5a74037cc 100644 --- a/Source/engine/load_clx.hpp +++ b/Source/engine/load_clx.hpp @@ -1,16 +1,29 @@ #pragma once +#include + +#include + #include "clx_sprite.hpp" +#include "utils/status_macros.hpp" namespace devilution { OwnedClxSpriteListOrSheet LoadClxListOrSheet(const char *path); +tl::expected LoadClxListOrSheetWithStatus(const char *path); + inline OwnedClxSpriteList LoadClx(const char *path) { return LoadClxListOrSheet(path).list(); } +inline tl::expected LoadClxWithStatus(const char *path) +{ + ASSIGN_OR_RETURN(OwnedClxSpriteListOrSheet result, LoadClxListOrSheetWithStatus(path)); + return std::move(result).list(); +} + inline OwnedClxSpriteSheet LoadClxSheet(const char *path) { return LoadClxListOrSheet(path).sheet(); diff --git a/Source/engine/load_file.hpp b/Source/engine/load_file.hpp index 1c91b2ef6..84827ff67 100644 --- a/Source/engine/load_file.hpp +++ b/Source/engine/load_file.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "appfat.h" @@ -18,24 +19,49 @@ namespace devilution { template -void LoadFileInMem(const char *path, T *data) +tl::expected LoadFileInMemWithStatus(const char *path, T *data) { size_t size; AssetHandle handle = OpenAsset(path, size); - if (!ValidateHandle(path, handle)) - return; - if ((size % sizeof(T)) != 0) - app_fatal(StrCat("File size does not align with type\n", path)); - handle.read(data, size); + if (!handle.ok()) { + if (HeadlessMode) return {}; + return tl::make_unexpected(FailedToOpenFileErrorMessage(path, handle.error())); + } + if ((size % sizeof(T)) != 0) { + return tl::make_unexpected(StrCat("File size does not align with type\n", path)); + } + if (!handle.read(data, size)) { + return tl::make_unexpected("handle.read failed"); + } + return {}; } template -void LoadFileInMem(const char *path, T *data, std::size_t count) +void LoadFileInMem(const char *path, T *data) +{ + const tl::expected result = LoadFileInMemWithStatus(path, data); + if (!result.has_value()) app_fatal(result.error()); +} + +template +tl::expected LoadFileInMemWithStatus(const char *path, T *data, std::size_t count) { AssetHandle handle = OpenAsset(path); - if (!ValidateHandle(path, handle)) - return; - handle.read(data, count * sizeof(T)); + if (!handle.ok()) { + if (HeadlessMode) return {}; + return tl::make_unexpected(FailedToOpenFileErrorMessage(path, handle.error())); + } + if (!handle.read(data, count * sizeof(T))) { + return tl::make_unexpected("handle.read failed"); + } + return {}; +} + +template +void LoadFileInMem(const char *path, T *data, std::size_t count) +{ + tl::expected result = LoadFileInMemWithStatus(path, data, count); + if (!result.has_value()) app_fatal(result.error()); } template @@ -45,34 +71,53 @@ bool LoadOptionalFileInMem(const char *path, T *data, std::size_t count) return handle.ok() && handle.read(data, count * sizeof(T)); } +template +tl::expected LoadFileInMemWithStatus(const char *path, std::array &data) +{ + return LoadFileInMemWithStatus(path, data.data(), N); +} + template void LoadFileInMem(const char *path, std::array &data) { LoadFileInMem(path, data.data(), N); } -/** - * @brief Load a file in to a buffer - * @param path Path of file - * @param numRead Number of T elements read - * @return Buffer with content of file - */ template -std::unique_ptr LoadFileInMem(const char *path, std::size_t *numRead = nullptr) +tl::expected, std::string> LoadFileInMemWithStatus(const char *path, std::size_t *numRead = nullptr) { size_t size; AssetHandle handle = OpenAsset(path, size); - if (!ValidateHandle(path, handle)) - return nullptr; - if ((size % sizeof(T)) != 0) - app_fatal(StrCat("File size does not align with type\n", path)); + if (!handle.ok()) { + if (HeadlessMode) return {}; + return tl::make_unexpected(FailedToOpenFileErrorMessage(path, handle.error())); + } + if ((size % sizeof(T)) != 0) { + return tl::make_unexpected(StrCat("File size does not align with type\n", path)); + } if (numRead != nullptr) *numRead = size / sizeof(T); std::unique_ptr buf { new T[size / sizeof(T)] }; - handle.read(buf.get(), size); - return buf; + if (!handle.read(buf.get(), size)) { + return tl::make_unexpected("handle.read failed"); + } + return { std::move(buf) }; +} + +/** + * @brief Load a file in to a buffer + * @param path Path of file + * @param numRead Number of T elements read + * @return Buffer with content of file + */ +template +std::unique_ptr LoadFileInMem(const char *path, std::size_t *numRead = nullptr) +{ + tl::expected, std::string> result = LoadFileInMemWithStatus(path, numRead); + if (!result.has_value()) app_fatal(result.error()); + return std::move(result).value(); } /** diff --git a/Source/engine/sound.cpp b/Source/engine/sound.cpp index b91258818..235f1d61c 100644 --- a/Source/engine/sound.cpp +++ b/Source/engine/sound.cpp @@ -11,8 +11,10 @@ #include #include #include +#include #include +#include #include "engine/assets.hpp" #include "init.h" @@ -20,6 +22,7 @@ #include "utils/log.hpp" #include "utils/math.h" #include "utils/sdl_mutex.h" +#include "utils/status_macros.hpp" #include "utils/stdcompat/shared_ptr_array.hpp" #include "utils/str_cat.hpp" #include "utils/stubs.h" @@ -46,7 +49,7 @@ std::string GetMp3Path(const char *path) return mp3Path; } -bool LoadAudioFile(const char *path, bool stream, bool errorDialog, SoundSample &result) +tl::expected LoadAudioFile(const char *path, bool stream, SoundSample &result) { bool isMp3 = true; std::string foundPath = GetMp3Path(path); @@ -56,8 +59,9 @@ bool LoadAudioFile(const char *path, bool stream, bool errorDialog, SoundSample foundPath = path; isMp3 = false; } - if (!ref.ok()) - ErrDlg("Audio file not found", StrCat(path, "\n", SDL_GetError(), "\n"), __FILE__, __LINE__); + if (!ref.ok()) { + return tl::make_unexpected(StrCat("Audio file not found\n", path, "\n", SDL_GetError(), "\n" __FILE__ ":", __LINE__)); + } #ifdef STREAM_ALL_AUDIO_MIN_FILE_SIZE #if STREAM_ALL_AUDIO_MIN_FILE_SIZE == 0 @@ -73,10 +77,7 @@ bool LoadAudioFile(const char *path, bool stream, bool errorDialog, SoundSample if (stream) { if (result.SetChunkStream(foundPath, isMp3, /*logErrors=*/true) != 0) { - if (errorDialog) { - ErrDlg("Failed to load audio file", StrCat(foundPath, "\n", SDL_GetError(), "\n"), __FILE__, __LINE__); - } - return false; + return tl::make_unexpected(StrCat("Failed to load audio file\n", foundPath, "\n", SDL_GetError(), "\n" __FILE__ ":", __LINE__)); } } else { #if !defined(STREAM_ALL_AUDIO_MIN_FILE_SIZE) || STREAM_ALL_AUDIO_MIN_FILE_SIZE == 0 @@ -84,24 +85,18 @@ bool LoadAudioFile(const char *path, bool stream, bool errorDialog, SoundSample #endif AssetHandle handle = OpenAsset(std::move(ref)); if (!handle.ok()) { - if (errorDialog) - ErrDlg("Failed to load audio file", StrCat(foundPath, "\n", SDL_GetError(), "\n"), __FILE__, __LINE__); - return false; + return tl::make_unexpected(StrCat("Failed to load audio file\n", foundPath, "\n", SDL_GetError(), "\n" __FILE__ ":", __LINE__)); } auto waveFile = MakeArraySharedPtr(size); if (!handle.read(waveFile.get(), size)) { - if (errorDialog) - ErrDlg("Failed to read file", StrCat(foundPath, ": ", SDL_GetError()), __FILE__, __LINE__); - return false; + return tl::make_unexpected(StrCat("Failed to read file\n", foundPath, ": ", SDL_GetError(), __FILE__ ":", __LINE__)); } const int error = result.SetChunk(waveFile, size, isMp3); if (error != 0) { - if (errorDialog) - ErrSdl(); - return false; + return tl::make_unexpected(SDL_GetError()); } } - return true; + return {}; } std::list> duplicateSounds; @@ -185,16 +180,23 @@ void snd_play_snd(TSnd *pSnd, int lVolume, int lPan) pSnd->start_tc = tc; } -std::unique_ptr sound_file_load(const char *path, bool stream) +tl::expected, std::string> SoundFileLoadWithStatus(const char *path, bool stream) { auto snd = std::make_unique(); snd->start_tc = SDL_GetTicks() - 80 - 1; #ifndef NOSOUND - LoadAudioFile(path, stream, /*errorDialog=*/true, snd->DSB); + RETURN_IF_ERROR(LoadAudioFile(path, stream, snd->DSB)); #endif return snd; } +std::unique_ptr sound_file_load(const char *path, bool stream) +{ + tl::expected, std::string> result = SoundFileLoadWithStatus(path, stream); + if (!result.has_value()) app_fatal(result.error()); + return std::move(result).value(); +} + TSnd::~TSnd() { if (DSB.IsLoaded()) @@ -281,7 +283,7 @@ void music_start(_music_id nTrack) #else const bool stream = true; #endif - if (!LoadAudioFile(trackPath, stream, /*errorDialog=*/false, music)) { + if (!LoadAudioFile(trackPath, stream, music).has_value()) { music_stop(); return; } diff --git a/Source/engine/sound.h b/Source/engine/sound.h index 6ca096314..d03793202 100644 --- a/Source/engine/sound.h +++ b/Source/engine/sound.h @@ -9,6 +9,8 @@ #include #include +#include + #include "levels/gendung.h" #include "utils/attributes.h" @@ -56,6 +58,7 @@ extern _music_id sgnMusicTrack; void ClearDuplicateSounds(); void snd_play_snd(TSnd *pSnd, int lVolume, int lPan); std::unique_ptr sound_file_load(const char *path, bool stream = false); +tl::expected, std::string> SoundFileLoadWithStatus(const char *path, bool stream = false); void snd_init(); void snd_deinit(); _music_id GetLevelMusic(dungeon_type dungeonType); diff --git a/Source/engine/sound_stubs.cpp b/Source/engine/sound_stubs.cpp index f766e9376..45f062b13 100644 --- a/Source/engine/sound_stubs.cpp +++ b/Source/engine/sound_stubs.cpp @@ -11,6 +11,7 @@ _music_id sgnMusicTrack = NUM_MUSIC; void ClearDuplicateSounds() { } void snd_play_snd(TSnd *pSnd, int lVolume, int lPan) { } std::unique_ptr sound_file_load(const char *path, bool stream) { return nullptr; } +tl::expected, std::string> SoundFileLoadWithStatus(const char *path, bool stream) { return nullptr; } TSnd::~TSnd() { } void snd_init() { } void snd_deinit() { } diff --git a/Source/gamemenu.cpp b/Source/gamemenu.cpp index 0a983288e..e2e5aa87c 100644 --- a/Source/gamemenu.cpp +++ b/Source/gamemenu.cpp @@ -310,7 +310,9 @@ void gamemenu_load_game(bool /*bActivate*/) DeactivateVirtualGamepad(); FreeVirtualGamepadTextures(); #endif - LoadGame(false); + if (tl::expected result = LoadGame(false); !result.has_value()) { + app_fatal(result.error()); + } #if !defined(USE_SDL1) && !defined(__vita__) if (renderer != nullptr) { InitVirtualGamepadTextures(*renderer); diff --git a/Source/interfac.cpp b/Source/interfac.cpp index 2dde740af..a43cac914 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -6,8 +6,10 @@ #include #include +#include #include +#include #include "control.h" #include "engine.h" @@ -236,11 +238,12 @@ void DoLoad(interface_mode uMsg) IncProgress(); Player &myPlayer = *MyPlayer; + tl::expected loadResult; switch (uMsg) { case WM_DIABLOADGAME: IncProgress(2); - LoadGame(true); - IncProgress(2); + loadResult = LoadGame(true); + if (loadResult.has_value()) IncProgress(2); break; case WM_DIABNEWGAME: myPlayer.pOriginalCathedral = !gbIsHellfire; @@ -249,8 +252,8 @@ void DoLoad(interface_mode uMsg) IncProgress(); pfile_remove_temp_files(); IncProgress(); - LoadGameLevel(true, ENTRY_MAIN); - IncProgress(); + loadResult = LoadGameLevel(true, ENTRY_MAIN); + if (loadResult.has_value()) IncProgress(); break; case WM_DIABNEXTLVL: IncProgress(); @@ -265,8 +268,8 @@ void DoLoad(interface_mode uMsg) currlevel = myPlayer.plrlevel; leveltype = GetLevelType(currlevel); IncProgress(); - LoadGameLevel(false, ENTRY_MAIN); - IncProgress(); + loadResult = LoadGameLevel(false, ENTRY_MAIN); + if (loadResult.has_value()) IncProgress(); break; case WM_DIABPREVLVL: IncProgress(); @@ -281,8 +284,8 @@ void DoLoad(interface_mode uMsg) leveltype = GetLevelType(currlevel); assert(myPlayer.isOnActiveLevel()); IncProgress(); - LoadGameLevel(false, ENTRY_PREV); - IncProgress(); + loadResult = LoadGameLevel(false, ENTRY_PREV); + if (loadResult.has_value()) IncProgress(); break; case WM_DIABSETLVL: // Note: ReturnLevel, ReturnLevelType and ReturnLvlPosition is only set to ensure vanilla compatibility @@ -301,8 +304,8 @@ void DoLoad(interface_mode uMsg) currlevel = static_cast(setlvlnum); FreeGameMem(); IncProgress(); - LoadGameLevel(false, ENTRY_SETLVL); - IncProgress(); + loadResult = LoadGameLevel(false, ENTRY_SETLVL); + if (loadResult.has_value()) IncProgress(); break; case WM_DIABRTNLVL: IncProgress(); @@ -317,8 +320,8 @@ void DoLoad(interface_mode uMsg) IncProgress(); currlevel = GetMapReturnLevel(); leveltype = GetLevelType(currlevel); - LoadGameLevel(false, ENTRY_RTNLVL); - IncProgress(); + loadResult = LoadGameLevel(false, ENTRY_RTNLVL); + if (loadResult.has_value()) IncProgress(); break; case WM_DIABWARPLVL: IncProgress(); @@ -331,8 +334,8 @@ void DoLoad(interface_mode uMsg) FreeGameMem(); GetPortalLevel(); IncProgress(); - LoadGameLevel(false, ENTRY_WARPLVL); - IncProgress(); + loadResult = LoadGameLevel(false, ENTRY_WARPLVL); + if (loadResult.has_value()) IncProgress(); break; case WM_DIABTOWNWARP: IncProgress(); @@ -347,8 +350,8 @@ void DoLoad(interface_mode uMsg) currlevel = myPlayer.plrlevel; leveltype = GetLevelType(currlevel); IncProgress(); - LoadGameLevel(false, ENTRY_TWARPDN); - IncProgress(); + loadResult = LoadGameLevel(false, ENTRY_TWARPDN); + if (loadResult.has_value()) IncProgress(); break; case WM_DIABTWARPUP: IncProgress(); @@ -362,8 +365,8 @@ void DoLoad(interface_mode uMsg) currlevel = myPlayer.plrlevel; leveltype = GetLevelType(currlevel); IncProgress(); - LoadGameLevel(false, ENTRY_TWARPUP); - IncProgress(); + loadResult = LoadGameLevel(false, ENTRY_TWARPUP); + if (loadResult.has_value()) IncProgress(); break; case WM_DIABRETOWN: IncProgress(); @@ -378,11 +381,20 @@ void DoLoad(interface_mode uMsg) currlevel = myPlayer.plrlevel; leveltype = GetLevelType(currlevel); IncProgress(); - LoadGameLevel(false, ENTRY_MAIN); - IncProgress(); + loadResult = LoadGameLevel(false, ENTRY_MAIN); + if (loadResult.has_value()) IncProgress(); break; default: - app_fatal("Unknown progress mode"); + loadResult = tl::make_unexpected("Unknown progress mode"); + break; + } + + if (!loadResult.has_value()) { + SDL_Event event; + event.type = CustomEventToSdlEvent(WM_ERROR); + event.user.data1 = new std::string(std::move(loadResult).error()); + SDL_PushEvent(&event); + return; } SDL_Event event; @@ -413,6 +425,9 @@ void ProgressEventHandler(const SDL_Event &event, uint16_t modState) drawnProgress = sgdwProgress; } break; + case WM_ERROR: + app_fatal(*static_cast(event.user.data1)); + break; case WM_DONE: { // We may have loaded a new palette. // Temporarily switch back to the load screen palette for fade out. diff --git a/Source/interfac.h b/Source/interfac.h index 9b445c538..a5316f519 100644 --- a/Source/interfac.h +++ b/Source/interfac.h @@ -28,6 +28,7 @@ enum interface_mode : uint8_t { // Asynchronous loading events. WM_PROGRESS, + WM_ERROR, WM_DONE, WM_FIRST = WM_DIABNEXTLVL, diff --git a/Source/levels/gendung.cpp b/Source/levels/gendung.cpp index 8f5d9e6da..ae53779e9 100644 --- a/Source/levels/gendung.cpp +++ b/Source/levels/gendung.cpp @@ -4,10 +4,12 @@ #include #include #include +#include #include #include #include +#include #include "engine/clx_sprite.hpp" #include "engine/load_file.hpp" @@ -24,6 +26,7 @@ #include "options.h" #include "utils/bitset2d.hpp" #include "utils/log.hpp" +#include "utils/status_macros.hpp" namespace devilution { @@ -426,17 +429,18 @@ void CreateDungeon(uint32_t rseed, lvl_entry entry) Make_SetPC(SetPiece); } -void LoadLevelSOLData() +tl::expected LoadLevelSOLData() { switch (leveltype) { case DTYPE_TOWN: - if (gbIsHellfire) - LoadFileInMem("nlevels\\towndata\\town.sol", SOLData); - else - LoadFileInMem("levels\\towndata\\town.sol", SOLData); + if (gbIsHellfire) { + RETURN_IF_ERROR(LoadFileInMemWithStatus("nlevels\\towndata\\town.sol", SOLData)); + } else { + RETURN_IF_ERROR(LoadFileInMemWithStatus("levels\\towndata\\town.sol", SOLData)); + } break; case DTYPE_CATHEDRAL: - LoadFileInMem("levels\\l1data\\l1.sol", SOLData); + RETURN_IF_ERROR(LoadFileInMemWithStatus("levels\\l1data\\l1.sol", SOLData)); // Fix incorrectly marked arched tiles SOLData[9] |= TileProperties::BlockLight | TileProperties::BlockMissile; SOLData[15] |= TileProperties::BlockLight | TileProperties::BlockMissile; @@ -464,28 +468,29 @@ void LoadLevelSOLData() SOLData[450] |= TileProperties::BlockLight | TileProperties::BlockMissile; break; case DTYPE_CATACOMBS: - LoadFileInMem("levels\\l2data\\l2.sol", SOLData); + RETURN_IF_ERROR(LoadFileInMemWithStatus("levels\\l2data\\l2.sol", SOLData)); break; case DTYPE_CAVES: - LoadFileInMem("levels\\l3data\\l3.sol", SOLData); + RETURN_IF_ERROR(LoadFileInMemWithStatus("levels\\l3data\\l3.sol", SOLData)); // The graphics for tile 48 sub-tile 171 frame 461 are partly incorrect, as they // have a few pixels that should belong to the solid tile 49 instead. // Marks the sub-tile as "BlockMissile" to avoid treating it as a floor during rendering. SOLData[170] |= TileProperties::BlockMissile; break; case DTYPE_HELL: - LoadFileInMem("levels\\l4data\\l4.sol", SOLData); + RETURN_IF_ERROR(LoadFileInMemWithStatus("levels\\l4data\\l4.sol", SOLData)); SOLData[210] = TileProperties::None; // Tile is incorrectly marked as being solid break; case DTYPE_NEST: - LoadFileInMem("nlevels\\l6data\\l6.sol", SOLData); + RETURN_IF_ERROR(LoadFileInMemWithStatus("nlevels\\l6data\\l6.sol", SOLData)); break; case DTYPE_CRYPT: - LoadFileInMem("nlevels\\l5data\\l5.sol", SOLData); + RETURN_IF_ERROR(LoadFileInMemWithStatus("nlevels\\l5data\\l5.sol", SOLData)); break; default: - app_fatal("LoadLevelSOLData"); + return tl::make_unexpected("LoadLevelSOLData"); } + return {}; } void SetDungeonMicros() diff --git a/Source/levels/gendung.h b/Source/levels/gendung.h index f582c1850..f6f8ede9c 100644 --- a/Source/levels/gendung.h +++ b/Source/levels/gendung.h @@ -325,7 +325,7 @@ struct Miniset { return HasAnyOf(SOLData[dPiece[coords.x][coords.y]], property); } -void LoadLevelSOLData(); +tl::expected LoadLevelSOLData(); void SetDungeonMicros(); void DRLG_InitTrans(); void DRLG_MRectTrans(WorldTilePosition origin, WorldTilePosition extent); diff --git a/Source/lighting.cpp b/Source/lighting.cpp index 6b7ea2876..32429c468 100644 --- a/Source/lighting.cpp +++ b/Source/lighting.cpp @@ -8,6 +8,9 @@ #include #include #include +#include + +#include #include "automap.h" #include "diablo.h" @@ -15,6 +18,7 @@ #include "engine/points_in_rectangle_range.hpp" #include "player.h" #include "utils/attributes.h" +#include "utils/status_macros.hpp" namespace devilution { @@ -260,11 +264,11 @@ void DoVision(Point position, uint8_t radius, MapExplorationType doAutomap, bool } } -void LoadTrns() +tl::expected LoadTrns() { - LoadFileInMem("plrgfx\\infra.trn", InfravisionTable); - LoadFileInMem("plrgfx\\stone.trn", StoneTable); - LoadFileInMem("gendata\\pause.trn", PauseTable); + RETURN_IF_ERROR(LoadFileInMemWithStatus("plrgfx\\infra.trn", InfravisionTable)); + RETURN_IF_ERROR(LoadFileInMemWithStatus("plrgfx\\stone.trn", StoneTable)); + return LoadFileInMemWithStatus("gendata\\pause.trn", PauseTable); } void MakeLightTable() diff --git a/Source/lighting.h b/Source/lighting.h index fbef5c923..9e5e0be19 100644 --- a/Source/lighting.h +++ b/Source/lighting.h @@ -11,6 +11,7 @@ #include #include +#include #include #include "automap.h" @@ -65,7 +66,7 @@ void DoUnLight(Point position, uint8_t radius); void DoLighting(Point position, uint8_t radius, DisplacementOf offset); void DoUnVision(Point position, uint8_t radius); void DoVision(Point position, uint8_t radius, MapExplorationType doAutomap, bool visible); -void LoadTrns(); +tl::expected LoadTrns(); void MakeLightTable(); #ifdef _DEBUG void ToggleLighting(); diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 2e65c9421..93dbda4d9 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -9,9 +9,11 @@ #include #include #include +#include #include #include +#include #include #include "automap.h" @@ -37,6 +39,7 @@ #include "utils/algorithm/container.hpp" #include "utils/endian.hpp" #include "utils/language.h" +#include "utils/status_macros.hpp" namespace devilution { @@ -1911,7 +1914,7 @@ void SaveLevel(SaveWriter &saveWriter, LevelConversionData *levelConversionData) myPlayer._pSLvlVisited[setlvlnum] = true; } -void LoadLevel(LevelConversionData *levelConversionData) +tl::expected LoadLevel(LevelConversionData *levelConversionData) { char szName[MaxMpqPathSize]; std::optional archive = OpenSaveArchive(gSaveNumber); @@ -1920,7 +1923,7 @@ void LoadLevel(LevelConversionData *levelConversionData) GetPermLevelNames(szName); LoadHelper file(std::move(archive), szName); if (!file.IsValid()) - app_fatal(_("Unable to open save file archive")); + return tl::make_unexpected(std::string(_("Unable to open save file archive"))); if (leveltype != DTYPE_TOWN) { for (int j = 0; j < MAXDUNY; j++) { @@ -1948,7 +1951,7 @@ void LoadLevel(LevelConversionData *levelConversionData) } if (!gbSkipSync) { for (size_t i = 0; i < ActiveMonsterCount; i++) - SyncMonsterAnim(Monsters[ActiveMonsters[i]]); + RETURN_IF_ERROR(SyncMonsterAnim(Monsters[ActiveMonsters[i]])); } for (int &objectId : ActiveObjects) objectId = file.NextLE(); @@ -2011,6 +2014,7 @@ void LoadLevel(LevelConversionData *levelConversionData) if (player.plractive && player.isOnActiveLevel()) Lights[player.lightId].hasChanged = true; } + return {}; } const int DiabloItemSaveSize = 368; @@ -2018,7 +2022,7 @@ const int HellfireItemSaveSize = 372; } // namespace -void ConvertLevels(SaveWriter &saveWriter) +tl::expected ConvertLevels(SaveWriter &saveWriter) { // Backup current level state bool tmpSetlevel = setlevel; @@ -2037,7 +2041,7 @@ void ConvertLevels(SaveWriter &saveWriter) leveltype = GetLevelType(currlevel); LevelConversionData levelConversionData; - LoadLevel(&levelConversionData); + RETURN_IF_ERROR(LoadLevel(&levelConversionData)); SaveLevel(saveWriter, &levelConversionData); } @@ -2057,7 +2061,7 @@ void ConvertLevels(SaveWriter &saveWriter) continue; LevelConversionData levelConversionData; - LoadLevel(&levelConversionData); + RETURN_IF_ERROR(LoadLevel(&levelConversionData)); SaveLevel(saveWriter, &levelConversionData); } @@ -2068,6 +2072,7 @@ void ConvertLevels(SaveWriter &saveWriter) setlvlnum = tmpSetlvlnum; currlevel = tmpCurrlevel; leveltype = tmpLeveltype; + return {}; } void RemoveInvalidItem(Item &item) @@ -2349,16 +2354,18 @@ void RemoveEmptyInventory(Player &player) } } -void LoadGame(bool firstflag) +tl::expected LoadGame(bool firstflag) { FreeGameMem(); LoadHelper file(OpenSaveArchive(gSaveNumber), "game"); - if (!file.IsValid()) - app_fatal(_("Unable to open save file archive")); + if (!file.IsValid()) { + return tl::make_unexpected(std::string(_("Unable to open save file archive"))); + } - if (!IsHeaderValid(file.NextLE())) - app_fatal(_("Invalid save file")); + if (!IsHeaderValid(file.NextLE())) { + return tl::make_unexpected(std::string(_("Invalid save file"))); + } if (gbIsHellfireSaveGame) { giNumberOfLevels = 25; @@ -2388,8 +2395,9 @@ void LoadGame(bool firstflag) int tmpNummissiles = file.NextBE(); int tmpNobjects = file.NextBE(); - if (!gbIsHellfire && IsAnyOf(leveltype, DTYPE_NEST, DTYPE_CRYPT)) - app_fatal(_("Player is on a Hellfire only level")); + if (!gbIsHellfire && IsAnyOf(leveltype, DTYPE_NEST, DTYPE_CRYPT)) { + return tl::make_unexpected(std::string(_("Player is on a Hellfire only level"))); + } for (uint8_t i = 0; i < giNumberOfLevels; i++) { DungeonSeeds[i] = file.NextBE(); @@ -2411,11 +2419,11 @@ void LoadGame(bool firstflag) LoadPortal(&file, i); if (gbIsHellfireSaveGame != gbIsHellfire) { - pfile_convert_levels(); + RETURN_IF_ERROR(pfile_convert_levels()); RemoveEmptyInventory(myPlayer); } - LoadGameLevel(firstflag, ENTRY_LOAD); + RETURN_IF_ERROR(LoadGameLevel(firstflag, ENTRY_LOAD)); SetPlrAnims(myPlayer); SyncPlrAnim(myPlayer); @@ -2444,7 +2452,7 @@ void LoadGame(bool firstflag) // For petrified monsters, the data in missile.var1 must be used to // load the appropriate animation data for the monster in missile.var2 for (size_t i = 0; i < ActiveMonsterCount; i++) - SyncMonsterAnim(Monsters[ActiveMonsters[i]]); + RETURN_IF_ERROR(SyncMonsterAnim(Monsters[ActiveMonsters[i]])); for (int &objectId : ActiveObjects) objectId = file.NextLE(); for (int &objectId : AvailableObjects) @@ -2559,6 +2567,7 @@ void LoadGame(bool firstflag) } gbIsHellfireSaveGame = gbIsHellfire; + return {}; } void SaveHeroItems(SaveWriter &saveWriter, Player &player) @@ -2810,9 +2819,9 @@ void SaveLevel(SaveWriter &saveWriter) SaveLevel(saveWriter, nullptr); } -void LoadLevel() +tl::expected LoadLevel() { - LoadLevel(nullptr); + return LoadLevel(nullptr); } } // namespace devilution diff --git a/Source/loadsave.h b/Source/loadsave.h index 023c4090e..0d139e143 100644 --- a/Source/loadsave.h +++ b/Source/loadsave.h @@ -7,6 +7,8 @@ #include +#include + #include "pfile.h" #include "player.h" #include "utils/attributes.h" @@ -34,14 +36,14 @@ void RemoveEmptyInventory(Player &player); * @brief Load game state * @param firstflag Can be set to false if we are simply reloading the current game */ -void LoadGame(bool firstflag); +tl::expected LoadGame(bool firstflag); void SaveHotkeys(SaveWriter &saveWriter, const Player &player); void SaveHeroItems(SaveWriter &saveWriter, Player &player); void SaveGameData(SaveWriter &saveWriter); void SaveGame(); void SaveLevel(SaveWriter &saveWriter); -void LoadLevel(); -void ConvertLevels(SaveWriter &saveWriter); +tl::expected LoadLevel(); +tl::expected ConvertLevels(SaveWriter &saveWriter); void LoadStash(); void SaveStash(SaveWriter &stashWriter); diff --git a/Source/lua/modules/dev/monsters.cpp b/Source/lua/modules/dev/monsters.cpp index 00855ebd2..4d3e869da 100644 --- a/Source/lua/modules/dev/monsters.cpp +++ b/Source/lua/modules/dev/monsters.cpp @@ -58,7 +58,9 @@ std::string DebugCmdSpawnUniqueMonster(std::string name, std::optional if (!found) { if (LevelMonsterTypeCount == MaxLvlMTypes) LevelMonsterTypeCount--; // we are running out of monster types, so override last used monster type - id = AddMonsterType(uniqueIndex, PLACE_SCATTER); + tl::expected idResult = AddMonsterType(uniqueIndex, PLACE_SCATTER); + if (!idResult.has_value()) return std::move(idResult).error(); + id = idResult.value(); CMonster &monsterType = LevelMonsterTypes[id]; InitMonsterGFX(monsterType); monsterType.corpseId = 1; @@ -131,7 +133,9 @@ std::string DebugCmdSpawnMonster(std::string name, std::optional count if (!found) { if (LevelMonsterTypeCount == MaxLvlMTypes) LevelMonsterTypeCount--; // we are running out of monster types, so override last used monster type - id = AddMonsterType(static_cast<_monster_id>(mtype), PLACE_SCATTER); + tl::expected idResult = AddMonsterType(static_cast<_monster_id>(mtype), PLACE_SCATTER); + if (!idResult.has_value()) return std::move(idResult).error(); + id = idResult.value(); CMonster &monsterType = LevelMonsterTypes[id]; InitMonsterGFX(monsterType); monsterType.corpseId = 1; diff --git a/Source/misdat.cpp b/Source/misdat.cpp index d00677971..62ed3f604 100644 --- a/Source/misdat.cpp +++ b/Source/misdat.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -17,6 +18,7 @@ #include "missiles.h" #include "mpq/mpq_common.hpp" #include "utils/file_name_generator.hpp" +#include "utils/status_macros.hpp" #include "utils/str_cat.hpp" #ifdef UNPACKED_MPQS @@ -397,28 +399,31 @@ uint8_t MissileFileData::animLen(uint8_t dir) const return MissileAnimLengths[animLenIdx][dir]; } -void MissileFileData::LoadGFX() +tl::expected MissileFileData::LoadGFX() { if (sprites) - return; + return {}; if (name[0] == '\0') - return; + return {}; #ifdef UNPACKED_MPQS char path[MaxMpqPathSize]; *BufCopy(path, "missiles\\", name, ".clx") = '\0'; - sprites.emplace(LoadClxListOrSheet(path)); + ASSIGN_OR_RETURN(sprites, LoadClxListOrSheetWithStatus(path)); #else if (animFAmt == 1) { char path[MaxMpqPathSize]; *BufCopy(path, "missiles\\", name) = '\0'; - sprites.emplace(OwnedClxSpriteListOrSheet { LoadCl2(path, animWidth) }); + ASSIGN_OR_RETURN(OwnedClxSpriteList spriteList, LoadCl2WithStatus(path, animWidth)); + sprites.emplace(OwnedClxSpriteListOrSheet { std::move(spriteList) }); } else { FileNameGenerator pathGenerator({ "missiles\\", name }, DEVILUTIONX_CL2_EXT); - sprites.emplace(OwnedClxSpriteListOrSheet { LoadMultipleCl2Sheet<16>(pathGenerator, animFAmt, animWidth) }); + ASSIGN_OR_RETURN(OwnedClxSpriteSheet spriteSheet, LoadMultipleCl2Sheet<16>(pathGenerator, animFAmt, animWidth)); + sprites.emplace(OwnedClxSpriteListOrSheet { std::move(spriteSheet) }); } #endif + return {}; } MissileFileData &GetMissileSpriteData(MissileGraphicID graphicId) @@ -437,18 +442,19 @@ const MissileData &GetMissileData(MissileID missileId) return MissilesData[static_cast>(missileId)]; } -void InitMissileGFX(bool loadHellfireGraphics) +tl::expected InitMissileGFX(bool loadHellfireGraphics) { if (HeadlessMode) - return; + return {}; for (size_t mi = 0; mi < MissileSpriteData.size(); ++mi) { if (!loadHellfireGraphics && mi >= static_cast(MissileGraphicID::HorkSpawn)) break; if (MissileSpriteData[mi].flags == MissileGraphicsFlags::MonsterOwned) continue; - MissileSpriteData[mi].LoadGFX(); + RETURN_IF_ERROR(MissileSpriteData[mi].LoadGFX()); } + return {}; } void FreeMissileGFX() diff --git a/Source/misdat.h b/Source/misdat.h index 17fc2c843..4d50bb9b7 100644 --- a/Source/misdat.h +++ b/Source/misdat.h @@ -10,6 +10,8 @@ #include #include +#include + #include "effects.h" #include "engine/clx_sprite.hpp" #include "spelldat.h" @@ -183,7 +185,7 @@ struct MissileFileData { [[nodiscard]] uint8_t animDelay(uint8_t dir) const; [[nodiscard]] uint8_t animLen(uint8_t dir) const; - void LoadGFX(); + tl::expected LoadGFX(); void FreeGFX() { @@ -209,7 +211,7 @@ MissileFileData &GetMissileSpriteData(MissileGraphicID graphicId); void LoadMissileData(); -void InitMissileGFX(bool loadHellfireGraphics = false); +tl::expected InitMissileGFX(bool loadHellfireGraphics = false); void FreeMissileGFX(); } // namespace devilution diff --git a/Source/monster.cpp b/Source/monster.cpp index 311a899cc..b09331f25 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -12,8 +12,10 @@ #include #include #include +#include #include +#include #include #include @@ -48,6 +50,7 @@ #include "utils/language.h" #include "utils/log.hpp" #include "utils/static_vector.hpp" +#include "utils/status_macros.hpp" #include "utils/str_cat.hpp" #include "utils/utf8.hpp" @@ -405,7 +408,7 @@ Point GetUniqueMonstPosition(UniqueMonsterType uniqindex) return position; } -void PlaceUniqueMonst(UniqueMonsterType uniqindex, size_t minionType, int bosspacksize) +tl::expected PlaceUniqueMonst(UniqueMonsterType uniqindex, size_t minionType, int bosspacksize) { const auto &uniqueMonsterData = UniqueMonstersData[static_cast(uniqindex)]; const size_t typeIndex = GetMonsterTypeIndex(uniqueMonsterData.mtype); @@ -414,7 +417,7 @@ void PlaceUniqueMonst(UniqueMonsterType uniqindex, size_t minionType, int bosspa Monster &monster = Monsters[ActiveMonsterCount]; ActiveMonsterCount++; - PrepareUniqueMonst(monster, uniqindex, minionType, bosspacksize, uniqueMonsterData); + return PrepareUniqueMonst(monster, uniqindex, minionType, bosspacksize, uniqueMonsterData); } void ClearMVars(Monster &monster) @@ -445,7 +448,7 @@ void ClrAllMonsters() } } -void PlaceUniqueMonsters() +tl::expected PlaceUniqueMonsters() { for (size_t u = 0; u < UniqueMonstersData.size(); ++u) { if (UniqueMonstersData[u].mlevel != currlevel) @@ -467,21 +470,22 @@ void PlaceUniqueMonsters() if (uniqueType == UniqueMonsterType::WarlordOfBlood && Quests[Q_WARLORD]._qactive == QUEST_NOTAVAIL) continue; - PlaceUniqueMonst(uniqueType, minionType, 8); + RETURN_IF_ERROR(PlaceUniqueMonst(uniqueType, minionType, 8)); } + return {}; } -void PlaceQuestMonsters() +tl::expected PlaceQuestMonsters() { if (!setlevel) { if (Quests[Q_BUTCHER].IsAvailable()) { - PlaceUniqueMonst(UniqueMonsterType::Butcher, 0, 0); + RETURN_IF_ERROR(PlaceUniqueMonst(UniqueMonsterType::Butcher, 0, 0)); } if (currlevel == Quests[Q_SKELKING]._qlevel && UseMultiplayerQuests()) { for (size_t i = 0; i < LevelMonsterTypeCount; i++) { if (IsSkel(LevelMonsterTypes[i].type)) { - PlaceUniqueMonst(UniqueMonsterType::SkeletonKing, i, 30); + RETURN_IF_ERROR(PlaceUniqueMonst(UniqueMonsterType::SkeletonKing, i, 30)); break; } } @@ -489,40 +493,40 @@ void PlaceQuestMonsters() if (Quests[Q_LTBANNER].IsAvailable()) { auto dunData = LoadFileInMem("levels\\l1data\\banner1.dun"); - SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld()); + RETURN_IF_ERROR(SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld())); } if (Quests[Q_BLOOD].IsAvailable()) { auto dunData = LoadFileInMem("levels\\l2data\\blood2.dun"); - SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld()); + RETURN_IF_ERROR(SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld())); } if (Quests[Q_BLIND].IsAvailable()) { auto dunData = LoadFileInMem("levels\\l2data\\blind2.dun"); - SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld()); + RETURN_IF_ERROR(SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld())); } if (Quests[Q_ANVIL].IsAvailable()) { auto dunData = LoadFileInMem("levels\\l3data\\anvil.dun"); - SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld() + Displacement { 2, 2 }); + RETURN_IF_ERROR(SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld() + Displacement { 2, 2 })); } if (Quests[Q_WARLORD].IsAvailable()) { auto dunData = LoadFileInMem("levels\\l4data\\warlord.dun"); - SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld()); - AddMonsterType(UniqueMonsterType::WarlordOfBlood, PLACE_SCATTER); + RETURN_IF_ERROR(SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld())); + RETURN_IF_ERROR(AddMonsterType(UniqueMonsterType::WarlordOfBlood, PLACE_SCATTER)); } if (Quests[Q_VEIL].IsAvailable()) { - AddMonsterType(UniqueMonsterType::Lachdan, PLACE_SCATTER); + RETURN_IF_ERROR(AddMonsterType(UniqueMonsterType::Lachdan, PLACE_SCATTER)); } if (Quests[Q_ZHAR].IsAvailable() && zharlib == -1) { Quests[Q_ZHAR]._qactive = QUEST_NOTAVAIL; } if (currlevel == Quests[Q_BETRAYER]._qlevel && UseMultiplayerQuests()) { - AddMonsterType(UniqueMonsterType::Lazarus, PLACE_UNIQUE); - AddMonsterType(UniqueMonsterType::RedVex, PLACE_UNIQUE); - PlaceUniqueMonst(UniqueMonsterType::Lazarus, 0, 0); - PlaceUniqueMonst(UniqueMonsterType::RedVex, 0, 0); - PlaceUniqueMonst(UniqueMonsterType::BlackJade, 0, 0); + RETURN_IF_ERROR(AddMonsterType(UniqueMonsterType::Lazarus, PLACE_UNIQUE)); + RETURN_IF_ERROR(AddMonsterType(UniqueMonsterType::RedVex, PLACE_UNIQUE)); + RETURN_IF_ERROR(PlaceUniqueMonst(UniqueMonsterType::Lazarus, 0, 0)); + RETURN_IF_ERROR(PlaceUniqueMonst(UniqueMonsterType::RedVex, 0, 0)); + RETURN_IF_ERROR(PlaceUniqueMonst(UniqueMonsterType::BlackJade, 0, 0)); auto dunData = LoadFileInMem("levels\\l4data\\vile1.dun"); - SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld()); + RETURN_IF_ERROR(SetMapMonsters(dunData.get(), SetPiece.position.megaToWorld())); } if (currlevel == 24) { @@ -538,38 +542,40 @@ void PlaceQuestMonsters() } } if (UberDiabloMonsterIndex == -1) - PlaceUniqueMonst(UniqueMonsterType::NaKrul, 0, 0); + RETURN_IF_ERROR(PlaceUniqueMonst(UniqueMonsterType::NaKrul, 0, 0)); } } else if (setlvlnum == SL_SKELKING) { - PlaceUniqueMonst(UniqueMonsterType::SkeletonKing, 0, 0); + RETURN_IF_ERROR(PlaceUniqueMonst(UniqueMonsterType::SkeletonKing, 0, 0)); } else if (setlvlnum == SL_VILEBETRAYER) { - AddMonsterType(UniqueMonsterType::Lazarus, PLACE_UNIQUE); - AddMonsterType(UniqueMonsterType::RedVex, PLACE_UNIQUE); - AddMonsterType(UniqueMonsterType::BlackJade, PLACE_UNIQUE); - PlaceUniqueMonst(UniqueMonsterType::Lazarus, 0, 0); - PlaceUniqueMonst(UniqueMonsterType::RedVex, 0, 0); - PlaceUniqueMonst(UniqueMonsterType::BlackJade, 0, 0); + RETURN_IF_ERROR(AddMonsterType(UniqueMonsterType::Lazarus, PLACE_UNIQUE)); + RETURN_IF_ERROR(AddMonsterType(UniqueMonsterType::RedVex, PLACE_UNIQUE)); + RETURN_IF_ERROR(AddMonsterType(UniqueMonsterType::BlackJade, PLACE_UNIQUE)); + RETURN_IF_ERROR(PlaceUniqueMonst(UniqueMonsterType::Lazarus, 0, 0)); + RETURN_IF_ERROR(PlaceUniqueMonst(UniqueMonsterType::RedVex, 0, 0)); + RETURN_IF_ERROR(PlaceUniqueMonst(UniqueMonsterType::BlackJade, 0, 0)); } + return {}; } -void LoadDiabMonsts() +tl::expected LoadDiabMonsts() { { - auto dunData = LoadFileInMem("levels\\l4data\\diab1.dun"); - SetMapMonsters(dunData.get(), DiabloQuad1.megaToWorld()); + ASSIGN_OR_RETURN(auto dunData, LoadFileInMemWithStatus("levels\\l4data\\diab1.dun")); + RETURN_IF_ERROR(SetMapMonsters(dunData.get(), DiabloQuad1.megaToWorld())); } { - auto dunData = LoadFileInMem("levels\\l4data\\diab2a.dun"); - SetMapMonsters(dunData.get(), DiabloQuad2.megaToWorld()); + ASSIGN_OR_RETURN(auto dunData, LoadFileInMemWithStatus("levels\\l4data\\diab2a.dun")); + RETURN_IF_ERROR(SetMapMonsters(dunData.get(), DiabloQuad2.megaToWorld())); } { - auto dunData = LoadFileInMem("levels\\l4data\\diab3a.dun"); - SetMapMonsters(dunData.get(), DiabloQuad3.megaToWorld()); + ASSIGN_OR_RETURN(auto dunData, LoadFileInMemWithStatus("levels\\l4data\\diab3a.dun")); + RETURN_IF_ERROR(SetMapMonsters(dunData.get(), DiabloQuad3.megaToWorld())); } { - auto dunData = LoadFileInMem("levels\\l4data\\diab4a.dun"); - SetMapMonsters(dunData.get(), DiabloQuad4.megaToWorld()); + ASSIGN_OR_RETURN(auto dunData, LoadFileInMemWithStatus("levels\\l4data\\diab4a.dun")); + RETURN_IF_ERROR(SetMapMonsters(dunData.get(), DiabloQuad4.megaToWorld())); } + return {}; } void DeleteMonster(size_t activeIndex) @@ -3126,7 +3132,7 @@ void EnsureMonsterIndexIsActive(size_t monsterId) } // namespace -size_t AddMonsterType(_monster_id type, placeflag placeflag) +tl::expected AddMonsterType(_monster_id type, placeflag placeflag) { const size_t typeIndex = GetMonsterTypeIndex(type); CMonster &monsterType = LevelMonsterTypes[typeIndex]; @@ -3147,21 +3153,22 @@ size_t AddMonsterType(_monster_id type, placeflag placeflag) } } - InitMonsterSND(monsterType); + RETURN_IF_ERROR(InitMonsterSND(monsterType)); } monsterType.placeFlags |= placeflag; return typeIndex; } -void InitTRNForUniqueMonster(Monster &monster) +tl::expected InitTRNForUniqueMonster(Monster &monster) { char filestr[64]; *BufCopy(filestr, R"(monsters\monsters\)", UniqueMonstersData[static_cast(monster.uniqueType)].mTrnName, ".trn") = '\0'; - monster.uniqueMonsterTRN = LoadFileInMem(filestr); + ASSIGN_OR_RETURN(monster.uniqueMonsterTRN, LoadFileInMemWithStatus(filestr)); + return {}; } -void PrepareUniqueMonst(Monster &monster, UniqueMonsterType monsterType, size_t minionType, int bosspacksize, const UniqueMonsterData &uniqueMonsterData) +tl::expected PrepareUniqueMonst(Monster &monster, UniqueMonsterType monsterType, size_t minionType, int bosspacksize, const UniqueMonsterData &uniqueMonsterData) { monster.uniqueType = monsterType; monster.maxHitPoints = uniqueMonsterData.mmaxhp << 6; @@ -3219,7 +3226,7 @@ void PrepareUniqueMonst(Monster &monster, UniqueMonsterType monsterType, size_t monster.maxDamageSpecial = 4 * monster.maxDamageSpecial + 6; } - InitTRNForUniqueMonster(monster); + RETURN_IF_ERROR(InitTRNForUniqueMonster(monster)); monster.uniqTrans = uniquetrans++; if (uniqueMonsterData.customToHit != 0) { @@ -3251,6 +3258,7 @@ void PrepareUniqueMonst(Monster &monster, UniqueMonsterType monsterType, size_t monster.flags &= ~MFLAG_ALLOW_SPECIAL; monster.mode = MonsterMode::Stand; } + return {}; } void InitLevelMonsters() @@ -3270,46 +3278,46 @@ void InitLevelMonsters() uniquetrans = 0; } -void GetLevelMTypes() +tl::expected GetLevelMTypes() { - AddMonsterType(MT_GOLEM, PLACE_SPECIAL); + RETURN_IF_ERROR(AddMonsterType(MT_GOLEM, PLACE_SPECIAL)); if (currlevel == 16) { - AddMonsterType(MT_ADVOCATE, PLACE_SCATTER); - AddMonsterType(MT_RBLACK, PLACE_SCATTER); - AddMonsterType(MT_DIABLO, PLACE_SPECIAL); - return; + RETURN_IF_ERROR(AddMonsterType(MT_ADVOCATE, PLACE_SCATTER)); + RETURN_IF_ERROR(AddMonsterType(MT_RBLACK, PLACE_SCATTER)); + RETURN_IF_ERROR(AddMonsterType(MT_DIABLO, PLACE_SPECIAL)); + return {}; } if (currlevel == 18) - AddMonsterType(MT_HORKSPWN, PLACE_SCATTER); + RETURN_IF_ERROR(AddMonsterType(MT_HORKSPWN, PLACE_SCATTER)); if (currlevel == 19) { - AddMonsterType(MT_HORKSPWN, PLACE_SCATTER); - AddMonsterType(MT_HORKDMN, PLACE_UNIQUE); + RETURN_IF_ERROR(AddMonsterType(MT_HORKSPWN, PLACE_SCATTER)); + RETURN_IF_ERROR(AddMonsterType(MT_HORKDMN, PLACE_UNIQUE)); } if (currlevel == 20) - AddMonsterType(MT_DEFILER, PLACE_UNIQUE); + RETURN_IF_ERROR(AddMonsterType(MT_DEFILER, PLACE_UNIQUE)); if (currlevel == 24) { - AddMonsterType(MT_ARCHLICH, PLACE_SCATTER); - AddMonsterType(MT_NAKRUL, PLACE_SPECIAL); + RETURN_IF_ERROR(AddMonsterType(MT_ARCHLICH, PLACE_SCATTER)); + RETURN_IF_ERROR(AddMonsterType(MT_NAKRUL, PLACE_SPECIAL)); } if (!setlevel) { if (Quests[Q_BUTCHER].IsAvailable()) - AddMonsterType(MT_CLEAVER, PLACE_SPECIAL); + RETURN_IF_ERROR(AddMonsterType(MT_CLEAVER, PLACE_SPECIAL)); if (Quests[Q_GARBUD].IsAvailable()) - AddMonsterType(UniqueMonsterType::Garbud, PLACE_UNIQUE); + RETURN_IF_ERROR(AddMonsterType(UniqueMonsterType::Garbud, PLACE_UNIQUE)); if (Quests[Q_ZHAR].IsAvailable()) - AddMonsterType(UniqueMonsterType::Zhar, PLACE_UNIQUE); + RETURN_IF_ERROR(AddMonsterType(UniqueMonsterType::Zhar, PLACE_UNIQUE)); if (Quests[Q_LTBANNER].IsAvailable()) - AddMonsterType(UniqueMonsterType::SnotSpill, PLACE_UNIQUE); + RETURN_IF_ERROR(AddMonsterType(UniqueMonsterType::SnotSpill, PLACE_UNIQUE)); if (Quests[Q_VEIL].IsAvailable()) - AddMonsterType(UniqueMonsterType::Lachdan, PLACE_UNIQUE); + RETURN_IF_ERROR(AddMonsterType(UniqueMonsterType::Lachdan, PLACE_UNIQUE)); if (Quests[Q_WARLORD].IsAvailable()) - AddMonsterType(UniqueMonsterType::WarlordOfBlood, PLACE_UNIQUE); + RETURN_IF_ERROR(AddMonsterType(UniqueMonsterType::WarlordOfBlood, PLACE_UNIQUE)); if (UseMultiplayerQuests() && currlevel == Quests[Q_SKELKING]._qlevel) { - AddMonsterType(MT_SKING, PLACE_UNIQUE); + RETURN_IF_ERROR(AddMonsterType(MT_SKING, PLACE_UNIQUE)); int skeletonTypeCount = 0; _monster_id skeltypes[NUM_MTYPES]; @@ -3319,7 +3327,7 @@ void GetLevelMTypes() skeltypes[skeletonTypeCount++] = skeletonType; } - AddMonsterType(skeltypes[GenerateRnd(skeletonTypeCount)], PLACE_SCATTER); + RETURN_IF_ERROR(AddMonsterType(skeltypes[GenerateRnd(skeletonTypeCount)], PLACE_SCATTER)); } _monster_id typelist[MaxMonsters]; @@ -3344,21 +3352,22 @@ void GetLevelMTypes() if (nt != 0) { int i = GenerateRnd(nt); - AddMonsterType(typelist[i], PLACE_SCATTER); + RETURN_IF_ERROR(AddMonsterType(typelist[i], PLACE_SCATTER)); typelist[i] = typelist[--nt]; } } } else { if (setlvlnum == SL_SKELKING) { - AddMonsterType(MT_SKING, PLACE_UNIQUE); + RETURN_IF_ERROR(AddMonsterType(MT_SKING, PLACE_UNIQUE)); } } + return {}; } -void InitMonsterSND(CMonster &monsterType) +tl::expected InitMonsterSND(CMonster &monsterType) { if (!gbSndInited) - return; + return {}; const char *prefixes[] { "a", // Attack @@ -3378,15 +3387,16 @@ void InitMonsterSND(CMonster &monsterType) for (int j = 0; j < 2; j++) { char path[64]; *BufCopy(path, "monsters\\", soundSuffix, prefix, j + 1, ".wav") = '\0'; - monsterType.sounds[i][j] = sound_file_load(path); + ASSIGN_OR_RETURN(monsterType.sounds[i][j], SoundFileLoadWithStatus(path)); } } + return {}; } -void InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData) +tl::expected InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData) { if (HeadlessMode) - return; + return {}; const _monster_id mtype = monsterType.type; const MonsterData &monsterData = MonstersData[mtype]; @@ -3413,52 +3423,54 @@ void InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData) } if (IsAnyOf(mtype, MT_NMAGMA, MT_YMAGMA, MT_BMAGMA, MT_WMAGMA)) - GetMissileSpriteData(MissileGraphicID::MagmaBall).LoadGFX(); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::MagmaBall).LoadGFX()); if (IsAnyOf(mtype, MT_STORM, MT_RSTORM, MT_STORML, MT_MAEL)) - GetMissileSpriteData(MissileGraphicID::ThinLightning).LoadGFX(); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::ThinLightning).LoadGFX()); if (mtype == MT_SNOWWICH) { - GetMissileSpriteData(MissileGraphicID::BloodStarBlue).LoadGFX(); - GetMissileSpriteData(MissileGraphicID::BloodStarBlueExplosion).LoadGFX(); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::BloodStarBlue).LoadGFX()); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::BloodStarBlueExplosion).LoadGFX()); } if (mtype == MT_HLSPWN) { - GetMissileSpriteData(MissileGraphicID::BloodStarRed).LoadGFX(); - GetMissileSpriteData(MissileGraphicID::BloodStarRedExplosion).LoadGFX(); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::BloodStarRed).LoadGFX()); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::BloodStarRedExplosion).LoadGFX()); } if (mtype == MT_SOLBRNR) { - GetMissileSpriteData(MissileGraphicID::BloodStarYellow).LoadGFX(); - GetMissileSpriteData(MissileGraphicID::BloodStarYellowExplosion).LoadGFX(); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::BloodStarYellow).LoadGFX()); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::BloodStarYellowExplosion).LoadGFX()); } if (IsAnyOf(mtype, MT_NACID, MT_RACID, MT_BACID, MT_XACID, MT_SPIDLORD)) { - GetMissileSpriteData(MissileGraphicID::Acid).LoadGFX(); - GetMissileSpriteData(MissileGraphicID::AcidSplat).LoadGFX(); - GetMissileSpriteData(MissileGraphicID::AcidPuddle).LoadGFX(); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::Acid).LoadGFX()); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::AcidSplat).LoadGFX()); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::AcidPuddle).LoadGFX()); } if (mtype == MT_LICH) { - GetMissileSpriteData(MissileGraphicID::OrangeFlare).LoadGFX(); - GetMissileSpriteData(MissileGraphicID::OrangeFlareExplosion).LoadGFX(); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::OrangeFlare).LoadGFX()); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::OrangeFlareExplosion).LoadGFX()); } if (mtype == MT_ARCHLICH) { - GetMissileSpriteData(MissileGraphicID::YellowFlare).LoadGFX(); - GetMissileSpriteData(MissileGraphicID::YellowFlareExplosion).LoadGFX(); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::YellowFlare).LoadGFX()); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::YellowFlareExplosion).LoadGFX()); } if (IsAnyOf(mtype, MT_PSYCHORB, MT_BONEDEMN)) - GetMissileSpriteData(MissileGraphicID::BlueFlare2).LoadGFX(); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::BlueFlare2).LoadGFX()); if (mtype == MT_NECRMORB) { - GetMissileSpriteData(MissileGraphicID::RedFlare).LoadGFX(); - GetMissileSpriteData(MissileGraphicID::RedFlareExplosion).LoadGFX(); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::RedFlare).LoadGFX()); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::RedFlareExplosion).LoadGFX()); } if (mtype == MT_PSYCHORB) - GetMissileSpriteData(MissileGraphicID::BlueFlareExplosion).LoadGFX(); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::BlueFlareExplosion).LoadGFX()); if (mtype == MT_BONEDEMN) - GetMissileSpriteData(MissileGraphicID::BlueFlareExplosion2).LoadGFX(); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::BlueFlareExplosion2).LoadGFX()); if (mtype == MT_DIABLO) - GetMissileSpriteData(MissileGraphicID::DiabloApocalypseBoom).LoadGFX(); + RETURN_IF_ERROR(GetMissileSpriteData(MissileGraphicID::DiabloApocalypseBoom).LoadGFX()); + + return {}; } -void InitAllMonsterGFX() +tl::expected InitAllMonsterGFX() { if (HeadlessMode) - return; + return {}; using LevelMonsterTypeIndices = StaticVector; std::vector monstersBySprite(GetNumMonsterSprites()); @@ -3478,12 +3490,12 @@ void InitAllMonsterGFX() for (size_t i = 1; i < monsterTypes.size(); ++i) { MonsterSpritesData spritesDataCopy { std::unique_ptr { new std::byte[spritesDataSize] }, spritesData.offsets }; memcpy(spritesDataCopy.data.get(), spritesData.data.get(), spritesDataSize); - InitMonsterGFX(LevelMonsterTypes[monsterTypes[i]], std::move(spritesDataCopy)); + RETURN_IF_ERROR(InitMonsterGFX(LevelMonsterTypes[monsterTypes[i]], std::move(spritesDataCopy))); } LogVerbose("Loaded monster graphics: {:15s} {:>4d} KiB x{:d}", firstMonster.data().spritePath(), spritesDataSize / 1024, monsterTypes.size()); totalUniqueBytes += spritesDataSize; totalBytes += spritesDataSize * monsterTypes.size(); - InitMonsterGFX(firstMonster, std::move(spritesData)); + RETURN_IF_ERROR(InitMonsterGFX(firstMonster, std::move(spritesData))); } LogVerbose(" Total monster graphics: {:>4d} KiB {:>4d} KiB", totalUniqueBytes / 1024, totalBytes / 1024); @@ -3492,9 +3504,10 @@ void InitAllMonsterGFX() for (size_t i = 0; i < ActiveMonsterCount; i++) { Monster &monster = Monsters[ActiveMonsters[i]]; if (!monster.animInfo.sprites) - SyncMonsterAnim(monster); + RETURN_IF_ERROR(SyncMonsterAnim(monster)); } } + return {}; } void WeakenNaKrul() @@ -3519,7 +3532,7 @@ void InitGolems() } } -void InitMonsters() +tl::expected InitMonsters() { if (!gbIsSpawn && !setlevel && currlevel == 16) LoadDiabMonsts(); @@ -3534,10 +3547,10 @@ void InitMonsters() } } if (!gbIsSpawn) - PlaceQuestMonsters(); + RETURN_IF_ERROR(PlaceQuestMonsters()); if (!setlevel) { if (!gbIsSpawn) - PlaceUniqueMonsters(); + RETURN_IF_ERROR(PlaceUniqueMonsters()); size_t na = 0; for (int s = 16; s < 96; s++) { for (int t = 16; t < 96; t++) { @@ -3577,12 +3590,12 @@ void InitMonsters() } } - InitAllMonsterGFX(); + return InitAllMonsterGFX(); } -void SetMapMonsters(const uint16_t *dunData, Point startPosition) +tl::expected SetMapMonsters(const uint16_t *dunData, Point startPosition) { - AddMonsterType(MT_GOLEM, PLACE_SPECIAL); + RETURN_IF_ERROR(AddMonsterType(MT_GOLEM, PLACE_SPECIAL)); if (setlevel) for (int i = 0; i < MAX_PLRS; i++) AddMonster(GolemHoldingCell, Direction::South, 0, false); @@ -3600,11 +3613,12 @@ void SetMapMonsters(const uint16_t *dunData, Point startPosition) for (WorldTileCoord i = 0; i < size.width; i++) { auto monsterId = static_cast(SDL_SwapLE16(monsterLayer[j * size.width + i])); if (monsterId != 0) { - const size_t typeIndex = AddMonsterType(MonstConvTbl[monsterId - 1], PLACE_SPECIAL); + ASSIGN_OR_RETURN(const size_t typeIndex, AddMonsterType(MonstConvTbl[monsterId - 1], PLACE_SPECIAL)); PlaceMonster(ActiveMonsterCount++, typeIndex, startPosition + Displacement { i, j }); } } } + return {}; } Monster *AddMonster(Point position, Direction dir, size_t typeIndex, bool inMap) @@ -4232,18 +4246,18 @@ bool LineClear(tl::function_ref clear, Point startPoint, Point endP return position == endPoint; } -void SyncMonsterAnim(Monster &monster) +tl::expected SyncMonsterAnim(Monster &monster) { #ifdef _DEBUG // fix for saves with debug monsters having type originally not on the level CMonster &monsterType = LevelMonsterTypes[monster.levelType]; if (monsterType.corpseId == 0) { - InitMonsterGFX(monsterType); + RETURN_IF_ERROR(InitMonsterGFX(monsterType)); monsterType.corpseId = 1; } #endif if (monster.isUnique()) { - InitTRNForUniqueMonster(monster); + RETURN_IF_ERROR(InitTRNForUniqueMonster(monster)); } MonsterGraphic graphic = MonsterGraphic::Stand; @@ -4285,6 +4299,7 @@ void SyncMonsterAnim(Monster &monster) } monster.changeAnimationData(graphic); + return {}; } void M_FallenFear(Point position) diff --git a/Source/monster.h b/Source/monster.h index a4d722431..1e8efb267 100644 --- a/Source/monster.h +++ b/Source/monster.h @@ -10,7 +10,9 @@ #include #include +#include +#include #include #include "engine.h" @@ -479,21 +481,21 @@ extern size_t ActiveMonsterCount; extern int MonsterKillCounts[NUM_MTYPES]; extern bool sgbSaveSoundOn; -void PrepareUniqueMonst(Monster &monster, UniqueMonsterType monsterType, size_t miniontype, int bosspacksize, const UniqueMonsterData &uniqueMonsterData); +tl::expected PrepareUniqueMonst(Monster &monster, UniqueMonsterType monsterType, size_t miniontype, int bosspacksize, const UniqueMonsterData &uniqueMonsterData); void InitLevelMonsters(); -void GetLevelMTypes(); -size_t AddMonsterType(_monster_id type, placeflag placeflag); -inline size_t AddMonsterType(UniqueMonsterType uniqueType, placeflag placeflag) +tl::expected GetLevelMTypes(); +tl::expected AddMonsterType(_monster_id type, placeflag placeflag); +inline tl::expected AddMonsterType(UniqueMonsterType uniqueType, placeflag placeflag) { return AddMonsterType(UniqueMonstersData[static_cast(uniqueType)].mtype, placeflag); } -void InitMonsterSND(CMonster &monsterType); -void InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData = {}); -void InitAllMonsterGFX(); +tl::expected InitMonsterSND(CMonster &monsterType); +tl::expected InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData = {}); +tl::expected InitAllMonsterGFX(); void WeakenNaKrul(); void InitGolems(); -void InitMonsters(); -void SetMapMonsters(const uint16_t *dunData, Point startPosition); +tl::expected InitMonsters(); +tl::expected SetMapMonsters(const uint16_t *dunData, Point startPosition); Monster *AddMonster(Point position, Direction dir, size_t mtype, bool inMap); /** * @brief Spawns a new monsters (dynamically/not on level load). @@ -534,7 +536,7 @@ bool DirOK(const Monster &monster, Direction mdir); bool PosOkMissile(Point position); bool LineClearMissile(Point startPoint, Point endPoint); bool LineClear(tl::function_ref clear, Point startPoint, Point endPoint); -void SyncMonsterAnim(Monster &monster); +tl::expected SyncMonsterAnim(Monster &monster); void M_FallenFear(Point position); void PrintMonstHistory(int mt); void PrintUniqueHistory(); diff --git a/Source/objects.cpp b/Source/objects.cpp index 77234d982..6dacad65e 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -7,9 +7,11 @@ #include #include #include +#include #include +#include #include #include "DiabloUI/ui_flags.hpp" @@ -3671,10 +3673,10 @@ bool IsItemBlockingObjectAtPosition(Point position) return false; } -void LoadLevelObjects(uint16_t filesWidths[65]) +tl::expected LoadLevelObjects(uint16_t filesWidths[65]) { if (HeadlessMode) - return; + return {}; for (const ObjectData objectData : AllObjects) { if (leveltype == objectData.olvltype) { @@ -3690,12 +3692,13 @@ void LoadLevelObjects(uint16_t filesWidths[65]) ObjFileList[numobjfiles] = static_cast(i); char filestr[32]; *BufCopy(filestr, "objects\\", ObjMasterLoadList[i]) = '\0'; - pObjCels[numobjfiles] = LoadCel(filestr, filesWidths[i]); + ASSIGN_OR_RETURN(pObjCels[numobjfiles], LoadCelWithStatus(filestr, filesWidths[i])); numobjfiles++; } + return {}; } -void InitObjectGFX() +tl::expected InitObjectGFX() { uint16_t filesWidths[65] = {}; @@ -3728,7 +3731,7 @@ void InitObjectGFX() } } - LoadLevelObjects(filesWidths); + return LoadLevelObjects(filesWidths); } void FreeObjectGFX() diff --git a/Source/objects.h b/Source/objects.h index fb8822232..576787d15 100644 --- a/Source/objects.h +++ b/Source/objects.h @@ -7,6 +7,9 @@ #include #include +#include + +#include #include "cursor.h" #include "engine/clx_sprite.hpp" @@ -322,7 +325,7 @@ inline Object &ObjectAtPosition(Point position) */ bool IsItemBlockingObjectAtPosition(Point position); -void InitObjectGFX(); +tl::expected InitObjectGFX(); void FreeObjectGFX(); void AddL1Objs(int x1, int y1, int x2, int y2); void AddL2Objs(int x1, int y1, int x2, int y2); diff --git a/Source/panels/charpanel.cpp b/Source/panels/charpanel.cpp index 5b49fe9f6..67d0c5c82 100644 --- a/Source/panels/charpanel.cpp +++ b/Source/panels/charpanel.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -20,6 +21,7 @@ #include "utils/enum_traits.h" #include "utils/format_int.hpp" #include "utils/language.h" +#include "utils/status_macros.hpp" #include "utils/str_cat.hpp" #include "utils/surface_to_clx.hpp" @@ -270,17 +272,17 @@ void DrawStatButtons(const Surface &out) } // namespace -void LoadCharPanel() +tl::expected LoadCharPanel() { - OptionalOwnedClxSpriteList background = LoadClx("data\\charbg.clx"); + ASSIGN_OR_RETURN(OptionalOwnedClxSpriteList background, LoadClxWithStatus("data\\charbg.clx")); OwnedSurface out((*background)[0].width(), (*background)[0].height()); RenderClxSprite(out, (*background)[0], { 0, 0 }); background = std::nullopt; { - OwnedClxSpriteList boxLeft = LoadClx("data\\boxleftend.clx"); - OwnedClxSpriteList boxMiddle = LoadClx("data\\boxmiddle.clx"); - OwnedClxSpriteList boxRight = LoadClx("data\\boxrightend.clx"); + ASSIGN_OR_RETURN(OwnedClxSpriteList boxLeft, LoadClxWithStatus("data\\boxleftend.clx")); + ASSIGN_OR_RETURN(OwnedClxSpriteList boxMiddle, LoadClxWithStatus("data\\boxmiddle.clx")); + ASSIGN_OR_RETURN(OwnedClxSpriteList boxRight, LoadClxWithStatus("data\\boxrightend.clx")); const bool isSmallFontTall = IsSmallFontTall(); const int attributeHeadersY = isSmallFontTall ? 112 : 114; @@ -298,6 +300,7 @@ void LoadCharPanel() } Panel = SurfaceToClx(out); + return {}; } void FreeCharPanel() diff --git a/Source/panels/charpanel.hpp b/Source/panels/charpanel.hpp index a6aef2881..947b6e1ef 100644 --- a/Source/panels/charpanel.hpp +++ b/Source/panels/charpanel.hpp @@ -1,5 +1,9 @@ #pragma once +#include + +#include + #include "engine/clx_sprite.hpp" #include "engine/surface.hpp" @@ -8,7 +12,7 @@ namespace devilution { extern OptionalOwnedClxSpriteList pChrButtons; void DrawChr(const Surface &); -void LoadCharPanel(); +tl::expected LoadCharPanel(); void FreeCharPanel(); } // namespace devilution diff --git a/Source/panels/mainpanel.cpp b/Source/panels/mainpanel.cpp index 05423fef3..fbab7f567 100644 --- a/Source/panels/mainpanel.cpp +++ b/Source/panels/mainpanel.cpp @@ -2,6 +2,9 @@ #include #include +#include + +#include #include "control.h" #include "engine/clx_sprite.hpp" @@ -12,6 +15,7 @@ #include "utils/language.h" #include "utils/sdl_compat.h" #include "utils/sdl_geometry.h" +#include "utils/status_macros.hpp" #include "utils/surface_to_clx.hpp" namespace devilution { @@ -66,12 +70,12 @@ void RenderMainButton(const Surface &out, int buttonId, std::string_view text, i } // namespace -void LoadMainPanel() +tl::expected LoadMainPanel() { std::optional out; constexpr uint16_t NumButtonSprites = 6; { - OptionalOwnedClxSpriteList background = LoadClx("data\\panel8bucp.clx"); + ASSIGN_OR_RETURN(OptionalOwnedClxSpriteList background, LoadClxWithStatus("data\\panel8bucp.clx")); out.emplace((*background)[0].width(), (*background)[0].height() * NumButtonSprites); int y = 0; for (ClxSprite sprite : ClxSpriteList(*background)) { @@ -134,6 +138,7 @@ void LoadMainPanel() PanelButtonDownGrime = std::nullopt; PanelButtonGrime = std::nullopt; PanelButton = std::nullopt; + return {}; } void FreeMainPanel() diff --git a/Source/panels/mainpanel.hpp b/Source/panels/mainpanel.hpp index 0b9bf70f8..a0b788ccd 100644 --- a/Source/panels/mainpanel.hpp +++ b/Source/panels/mainpanel.hpp @@ -1,5 +1,9 @@ #pragma once +#include + +#include + #include "engine/clx_sprite.hpp" namespace devilution { @@ -7,7 +11,7 @@ namespace devilution { extern OptionalOwnedClxSpriteList PanelButtonDown; extern OptionalOwnedClxSpriteList TalkButton; -void LoadMainPanel(); +tl::expected LoadMainPanel(); void FreeMainPanel(); } // namespace devilution diff --git a/Source/panels/spell_book.cpp b/Source/panels/spell_book.cpp index 081490000..23ead32ef 100644 --- a/Source/panels/spell_book.cpp +++ b/Source/panels/spell_book.cpp @@ -2,7 +2,9 @@ #include #include +#include +#include #include #include "control.h" @@ -20,6 +22,7 @@ #include "player.h" #include "spelldat.h" #include "utils/language.h" +#include "utils/status_macros.hpp" namespace devilution { @@ -130,11 +133,11 @@ StringOrView GetSpellPowerText(SpellID spell, int spellLevel) } // namespace -void InitSpellBook() +tl::expected InitSpellBook() { - spellBookBackground = LoadCel("data\\spellbk", static_cast(SidePanelSize.width)); - spellBookButtons = LoadCel("data\\spellbkb", SpellBookButtonWidth()); - LoadSmallSpellIcons(); + ASSIGN_OR_RETURN(spellBookBackground, LoadCelWithStatus("data\\spellbk", static_cast(SidePanelSize.width))); + ASSIGN_OR_RETURN(spellBookButtons, LoadCelWithStatus("data\\spellbkb", SpellBookButtonWidth())); + return LoadSmallSpellIcons(); } void FreeSpellBook() diff --git a/Source/panels/spell_book.hpp b/Source/panels/spell_book.hpp index d65c1a742..9d15381e4 100644 --- a/Source/panels/spell_book.hpp +++ b/Source/panels/spell_book.hpp @@ -1,11 +1,15 @@ #pragma once +#include + +#include + #include "engine/clx_sprite.hpp" #include "engine/surface.hpp" namespace devilution { -void InitSpellBook(); +tl::expected InitSpellBook(); void FreeSpellBook(); void CheckSBook(); void DrawSpellBook(const Surface &out); diff --git a/Source/panels/spell_icons.cpp b/Source/panels/spell_icons.cpp index b1b0e9dea..f14670078 100644 --- a/Source/panels/spell_icons.cpp +++ b/Source/panels/spell_icons.cpp @@ -84,24 +84,25 @@ const SpellIcon SpellITbl[] = { } // namespace -void LoadLargeSpellIcons() +tl::expected LoadLargeSpellIcons() { if (!gbIsHellfire) { #ifdef UNPACKED_MPQS - LargeSpellIcons = LoadClx("ctrlpan\\spelicon_fg.clx"); - LargeSpellIconsBackground = LoadClx("ctrlpan\\spelicon_bg.clx"); + ASSIGN_OR_RETURN(LargeSpellIcons, LoadClxWithStatus("ctrlpan\\spelicon_fg.clx")); + ASSIGN_OR_RETURN(LargeSpellIconsBackground, LoadClxWithStatus("ctrlpan\\spelicon_bg.clx")); #else - LargeSpellIcons = LoadCel("ctrlpan\\spelicon", SPLICONLENGTH); + ASSIGN_OR_RETURN(LargeSpellIcons, LoadCelWithStatus("ctrlpan\\spelicon", SPLICONLENGTH)); #endif } else { #ifdef UNPACKED_MPQS - LargeSpellIcons = LoadClx("data\\spelicon_fg.clx"); - LargeSpellIconsBackground = LoadClx("data\\spelicon_bg.clx"); + ASSIGN_OR_RETURN(LargeSpellIcons, LoadClxWithStatus("data\\spelicon_fg.clx")); + ASSIGN_OR_RETURN(LargeSpellIconsBackground, LoadClxWithStatus("data\\spelicon_bg.clx")); #else - LargeSpellIcons = LoadCel("data\\spelicon", SPLICONLENGTH); + ASSIGN_OR_RETURN(LargeSpellIcons, LoadCelWithStatus("data\\spelicon", SPLICONLENGTH)); #endif } SetSpellTrans(SpellType::Skill); + return {}; } void FreeLargeSpellIcons() @@ -112,14 +113,15 @@ void FreeLargeSpellIcons() LargeSpellIcons = std::nullopt; } -void LoadSmallSpellIcons() +tl::expected LoadSmallSpellIcons() { #ifdef UNPACKED_MPQS - SmallSpellIcons = LoadClx("data\\spelli2_fg.clx"); - SmallSpellIconsBackground = LoadClx("data\\spelli2_bg.clx"); + ASSIGN_OR_RETURN(SmallSpellIcons, LoadClxWithStatus("data\\spelli2_fg.clx")); + ASSIGN_OR_RETURN(SmallSpellIconsBackground, LoadClxWithStatus("data\\spelli2_bg.clx")); #else - SmallSpellIcons = LoadCel("data\\spelli2", 37); + ASSIGN_OR_RETURN(SmallSpellIcons, LoadCelWithStatus("data\\spelli2", 37)); #endif + return {}; } void FreeSmallSpellIcons() diff --git a/Source/panels/spell_icons.hpp b/Source/panels/spell_icons.hpp index 3a303c0c0..8d3d2e357 100644 --- a/Source/panels/spell_icons.hpp +++ b/Source/panels/spell_icons.hpp @@ -1,6 +1,9 @@ #pragma once #include +#include + +#include #include "engine/clx_sprite.hpp" #include "engine/point.hpp" @@ -106,10 +109,10 @@ void DrawSmallSpellIconBorder(const Surface &out, Point position); */ void SetSpellTrans(SpellType t); -void LoadLargeSpellIcons(); +tl::expected LoadLargeSpellIcons(); void FreeLargeSpellIcons(); -void LoadSmallSpellIcons(); +tl::expected LoadSmallSpellIcons(); void FreeSmallSpellIcons(); } // namespace devilution diff --git a/Source/pfile.cpp b/Source/pfile.cpp index a9d7ac5eb..33d2f216e 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include "codec.h" @@ -778,10 +779,10 @@ void pfile_save_level() SaveLevel(saveWriter); } -void pfile_convert_levels() +tl::expected pfile_convert_levels() { SaveWriter saveWriter = GetSaveWriter(gSaveNumber); - ConvertLevels(saveWriter); + return ConvertLevels(saveWriter); } void pfile_remove_temp_files() diff --git a/Source/pfile.h b/Source/pfile.h index 87fbf54d9..df10fca08 100644 --- a/Source/pfile.h +++ b/Source/pfile.h @@ -6,6 +6,9 @@ #pragma once #include +#include + +#include #include "DiabloUI/diabloui.h" #include "player.h" @@ -122,7 +125,7 @@ bool pfile_ui_save_create(_uiheroinfo *heroinfo); bool pfile_delete_save(_uiheroinfo *heroInfo); void pfile_read_player_from_save(uint32_t saveNum, Player &player); void pfile_save_level(); -void pfile_convert_levels(); +tl::expected pfile_convert_levels(); void pfile_remove_temp_files(); std::unique_ptr pfile_read(const char *pszName, size_t *pdwLen); void pfile_update(bool forceSave); diff --git a/Source/utils/attributes.h b/Source/utils/attributes.h index b14787c01..9a7121d0c 100644 --- a/Source/utils/attributes.h +++ b/Source/utils/attributes.h @@ -11,6 +11,12 @@ #define DVL_HAVE_ATTRIBUTE(x) 0 #endif +#ifdef __has_builtin +#define DVL_HAVE_BUILTIN(x) __has_builtin(x) +#else +#define DVL_HAVE_BUILTIN(x) 0 +#endif + #if DVL_HAVE_ATTRIBUTE(format) || (defined(__GNUC__) && !defined(__clang__)) #define DVL_PRINTF_ATTRIBUTE(fmtargnum, firstarg) \ __attribute__((__format__(__printf__, fmtargnum, firstarg))) @@ -110,3 +116,11 @@ #else #define DVL_UNREACHABLE() #endif + +#if DVL_HAVE_BUILTIN(__builtin_expect) +#define DVL_PREDICT_FALSE(x) (__builtin_expect(false || (x), false)) +#define DVL_PREDICT_TRUE(x) (__builtin_expect(false || (x), true)) +#else +#define DVL_PREDICT_FALSE(x) (x) +#define DVL_PREDICT_TRUE(x) (x) +#endif diff --git a/Source/utils/sdl_thread.h b/Source/utils/sdl_thread.h index 10fae419e..336a8ec5e 100644 --- a/Source/utils/sdl_thread.h +++ b/Source/utils/sdl_thread.h @@ -1,66 +1,69 @@ -#pragma once - -#include -#include -#ifdef USE_SDL1 -#include "utils/sdl2_to_1_2_backports.h" -#endif -#include "appfat.h" - -namespace devilution { - -namespace this_sdl_thread { -inline SDL_threadID get_id() -{ - return SDL_GetThreadID(nullptr); -} -} // namespace this_sdl_thread - -class SdlThread final { - static int SDLCALL ThreadTranslate(void *ptr); - static void ThreadDeleter(SDL_Thread *thread); - - std::unique_ptr thread { nullptr, ThreadDeleter }; - -public: - SdlThread(int(SDLCALL *handler)(void *), void *data) -#ifdef USE_SDL1 - : thread(SDL_CreateThread(handler, data), ThreadDeleter) -#else - : thread(SDL_CreateThread(handler, nullptr, data), ThreadDeleter) -#endif - { - if (thread == nullptr) - ErrSdl(); - } - - explicit SdlThread(void (*handler)(void)) - : SdlThread(ThreadTranslate, (void *)handler) - { - } - - SdlThread() = default; - - bool joinable() const - { - return thread != nullptr; - } - - SDL_threadID get_id() const - { - return SDL_GetThreadID(thread.get()); - } - - void join() - { - if (!joinable()) - return; - if (get_id() == this_sdl_thread::get_id()) - app_fatal("Thread joined from within itself"); - - SDL_WaitThread(thread.get(), nullptr); - thread.release(); - } -}; - -} // namespace devilution +#pragma once + +#include + +#include + +#ifdef USE_SDL1 +#include "utils/sdl2_to_1_2_backports.h" +#endif +#include "appfat.h" +#include "utils/attributes.h" + +namespace devilution { + +namespace this_sdl_thread { +inline SDL_threadID get_id() +{ + return SDL_GetThreadID(nullptr); +} +} // namespace this_sdl_thread + +class SdlThread final { + static int SDLCALL ThreadTranslate(void *ptr); + static void ThreadDeleter(SDL_Thread *thread); + + std::unique_ptr thread { nullptr, ThreadDeleter }; + +public: + SdlThread(int(SDLCALL *handler)(void *), void *data) +#ifdef USE_SDL1 + : thread(SDL_CreateThread(handler, data), ThreadDeleter) +#else + : thread(SDL_CreateThread(handler, nullptr, data), ThreadDeleter) +#endif + { + if (thread == nullptr) + ErrSdl(); + } + + explicit SdlThread(void (*handler)(void)) + : SdlThread(ThreadTranslate, (void *)handler) + { + } + + SdlThread() = default; + + bool joinable() const + { + return thread != nullptr; + } + + SDL_threadID get_id() const + { + return SDL_GetThreadID(thread.get()); + } + + void join() + { + if (!joinable()) + return; + if (get_id() == this_sdl_thread::get_id()) + app_fatal("Thread joined from within itself"); + + SDL_WaitThread(thread.get(), nullptr); + thread.release(); + } +}; + +} // namespace devilution diff --git a/Source/utils/status_macros.hpp b/Source/utils/status_macros.hpp new file mode 100644 index 000000000..2b8766c3b --- /dev/null +++ b/Source/utils/status_macros.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "utils/attributes.h" + +#define RETURN_IF_ERROR(expr) \ + if (auto result = expr; DVL_PREDICT_FALSE(!result.has_value())) { \ + return tl::make_unexpected(std::move(result).error()); \ + } + +#define STATUS_MACROS_CONCAT_NAME_INNER(x, y) x##y +#define STATUS_MACROS_CONCAT_NAME(x, y) STATUS_MACROS_CONCAT_NAME_INNER(x, y) + +#define ASSIGN_OR_RETURN_IMPL(result, lhs, rhs) \ + auto result = rhs; /* NOLINT(bugprone-macro-parentheses): assignment */ \ + if (DVL_PREDICT_FALSE(!result.has_value())) { /* NOLINT(bugprone-macro-parentheses): assignment */ \ + return tl::make_unexpected(std::move(result).error()); \ + } \ + lhs = std::move(result).value(); /* NOLINT(bugprone-macro-parentheses): assignment */ + +#define ASSIGN_OR_RETURN(lhs, rhs) \ + ASSIGN_OR_RETURN_IMPL( \ + STATUS_MACROS_CONCAT_NAME(_result, __COUNTER__), lhs, rhs)