Browse Source

Async load: Handle errors

pull/7523/head
Gleb Mazovetskiy 1 year ago committed by Anders Jenbo
parent
commit
7b0558146e
  1. 3
      Source/DiabloUI/dialogs.cpp
  2. 42
      Source/appfat.cpp
  3. 25
      Source/control.cpp
  4. 4
      Source/control.h
  5. 132
      Source/diablo.cpp
  6. 2
      Source/diablo.h
  7. 5
      Source/engine/assets.cpp
  8. 4
      Source/engine/assets.hpp
  9. 18
      Source/engine/load_cel.cpp
  10. 18
      Source/engine/load_cel.hpp
  11. 14
      Source/engine/load_cl2.cpp
  12. 12
      Source/engine/load_cl2.hpp
  13. 15
      Source/engine/load_clx.cpp
  14. 13
      Source/engine/load_clx.hpp
  15. 91
      Source/engine/load_file.hpp
  16. 42
      Source/engine/sound.cpp
  17. 3
      Source/engine/sound.h
  18. 1
      Source/engine/sound_stubs.cpp
  19. 4
      Source/gamemenu.cpp
  20. 57
      Source/interfac.cpp
  21. 1
      Source/interfac.h
  22. 29
      Source/levels/gendung.cpp
  23. 2
      Source/levels/gendung.h
  24. 12
      Source/lighting.cpp
  25. 3
      Source/lighting.h
  26. 45
      Source/loadsave.cpp
  27. 8
      Source/loadsave.h
  28. 8
      Source/lua/modules/dev/monsters.cpp
  29. 24
      Source/misdat.cpp
  30. 6
      Source/misdat.h
  31. 227
      Source/monster.cpp
  32. 22
      Source/monster.h
  33. 13
      Source/objects.cpp
  34. 5
      Source/objects.h
  35. 13
      Source/panels/charpanel.cpp
  36. 6
      Source/panels/charpanel.hpp
  37. 9
      Source/panels/mainpanel.cpp
  38. 6
      Source/panels/mainpanel.hpp
  39. 11
      Source/panels/spell_book.cpp
  40. 6
      Source/panels/spell_book.hpp
  41. 24
      Source/panels/spell_icons.cpp
  42. 7
      Source/panels/spell_icons.hpp
  43. 5
      Source/pfile.cpp
  44. 5
      Source/pfile.h
  45. 14
      Source/utils/attributes.h
  46. 135
      Source/utils/sdl_thread.h
  47. 22
      Source/utils/status_macros.hpp

3
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);

42
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

25
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<void, std::string> 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<uint16_t>(SidePanelSize.width));
GoldBoxBuffer = LoadCel("ctrlpan\\golddrop", 261);
ASSIGN_OR_RETURN(pQLogCel, LoadCelWithStatus("data\\quest", static_cast<uint16_t>(SidePanelSize.width)));
ASSIGN_OR_RETURN(GoldBoxBuffer, LoadCelWithStatus("ctrlpan\\golddrop", 261));
}
CloseGoldDrop();
CalculatePanelAreas();
if (!HeadlessMode)
InitModifierHints();
return {};
}
void DrawMainPanel(const Surface &out)

4
Source/control.h

@ -8,9 +8,11 @@
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <SDL.h>
#include <expected.hpp>
#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<void, std::string> InitMainPanel();
void DrawMainPanel(const Surface &out);
/**

132
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<void, std::string> LoadLvlGFX()
{
assert(pDungeonCels == nullptr);
constexpr int SpecialCelWidth = 64;
const auto loadAll = [](const char *cel, const char *til, const char *special) -> tl::expected<void, std::string> {
ASSIGN_OR_RETURN(pDungeonCels, LoadFileInMemWithStatus(cel));
ASSIGN_OR_RETURN(pMegaTiles, LoadFileInMemWithStatus<MegaTile>(til));
ASSIGN_OR_RETURN(pSpecialCels, LoadCelWithStatus(special, SpecialCelWidth));
return {};
};
switch (leveltype) {
case DTYPE_TOWN:
if (gbIsHellfire) {
pDungeonCels = LoadFileInMem("nlevels\\towndata\\town.cel");
pMegaTiles = LoadFileInMem<MegaTile>("nlevels\\towndata\\town.til");
} else {
pDungeonCels = LoadFileInMem("levels\\towndata\\town.cel");
pMegaTiles = LoadFileInMem<MegaTile>("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<MegaTile>("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<MegaTile>("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<MegaTile>("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<MegaTile>("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<MegaTile>("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<MegaTile>("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<void, std::string> 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<void, std::string> 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<void, std::string> 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<void, std::string> 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<void, std::string> 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<void, std::string> 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)

2
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<void, std::string> LoadGameLevel(bool firstflag, lvl_entry lvldir);
bool IsDiabloAlive(bool playSFX);
void PrintScreen(SDL_Keycode vkey);

5
Source/engine/assets.cpp

@ -233,4 +233,9 @@ tl::expected<AssetData, std::string> 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

4
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)

18
Source/engine/load_cel.cpp

@ -2,12 +2,17 @@
#include <cstddef>
#include <cstdint>
#include <string>
#ifdef DEBUG_CEL_TO_CL2_SIZE
#include <iostream>
#endif
#include <expected.hpp>
#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<uint16_t> widthOrWidths)
tl::expected<OwnedClxSpriteListOrSheet, std::string> LoadCelListOrSheetWithStatus(const char *pszName, PointerOrValue<uint16_t> 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<uint8_t[]> data = LoadFileInMem<uint8_t>(path, &size);
ASSIGN_OR_RETURN(std::unique_ptr<uint8_t[]> data, LoadFileInMemWithStatus<uint8_t>(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<uint16_t> widthOrWidths)
{
tl::expected<OwnedClxSpriteListOrSheet, std::string> result = LoadCelListOrSheetWithStatus(pszName, widthOrWidths);
if (DVL_PREDICT_FALSE(!result.has_value())) app_fatal(result.error());
return std::move(result).value();
}
} // namespace devilution

18
Source/engine/load_cel.hpp

@ -1,9 +1,13 @@
#pragma once
#include <cstdint>
#include <string>
#include <expected.hpp>
#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<OwnedClxSpriteListOrSheet, std::string> LoadCelListOrSheetWithStatus(const char *pszName, PointerOrValue<uint16_t> widthOrWidths);
OwnedClxSpriteListOrSheet LoadCelListOrSheet(const char *pszName, PointerOrValue<uint16_t> 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<uint16_t> { width }).list();
}
inline tl::expected<OwnedClxSpriteList, std::string> LoadCelWithStatus(const char *pszName, uint16_t width)
{
ASSIGN_OR_RETURN(OwnedClxSpriteListOrSheet result, LoadCelListOrSheetWithStatus(pszName, PointerOrValue<uint16_t> { width }));
return std::move(result).list();
}
inline OwnedClxSpriteList LoadCel(const char *pszName, const uint16_t *widths)
{
return LoadCelListOrSheet(pszName, PointerOrValue<uint16_t> { widths }).list();
}
inline tl::expected<OwnedClxSpriteList, std::string> LoadCelWithStatus(const char *pszName, const uint16_t *widths)
{
ASSIGN_OR_RETURN(OwnedClxSpriteListOrSheet result, LoadCelListOrSheetWithStatus(pszName, PointerOrValue<uint16_t> { widths }));
return std::move(result).list();
}
inline OwnedClxSpriteSheet LoadCelSheet(const char *pszName, uint16_t width)
{
return LoadCelListOrSheet(pszName, PointerOrValue<uint16_t> { width }).sheet();

14
Source/engine/load_cl2.cpp

@ -5,6 +5,7 @@
#include <utility>
#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<uint16_t> widthOrWidths)
tl::expected<OwnedClxSpriteListOrSheet, std::string> LoadCl2ListOrSheetWithStatus(const char *pszName, PointerOrValue<uint16_t> 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<uint8_t[]> data = LoadFileInMem<uint8_t>(path, &size);
ASSIGN_OR_RETURN(std::unique_ptr<uint8_t[]> data, LoadFileInMemWithStatus<uint8_t>(path, &size));
return Cl2ToClx(std::move(data), size, widthOrWidths);
#endif
}
OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue<uint16_t> widthOrWidths)
{
tl::expected<OwnedClxSpriteListOrSheet, std::string> result = LoadCl2ListOrSheetWithStatus(pszName, widthOrWidths);
if (!result.has_value()) app_fatal(result.error());
return std::move(result).value();
}
} // namespace devilution

12
Source/engine/load_cl2.hpp

@ -3,7 +3,9 @@
#include <array>
#include <cstdint>
#include <cstring>
#include <string>
#include <expected.hpp>
#include <function_ref.hpp>
#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<OwnedClxSpriteListOrSheet, std::string> LoadCl2ListOrSheetWithStatus(const char *pszName, PointerOrValue<uint16_t> widthOrWidths);
OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue<uint16_t> widthOrWidths);
template <size_t MaxCount>
OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref<const char *(size_t)> filenames, size_t count, uint16_t width)
tl::expected<OwnedClxSpriteSheet, std::string> LoadMultipleCl2Sheet(tl::function_ref<const char *(size_t)> filenames, size_t count, uint16_t width)
{
StaticVector<std::array<char, MaxMpqPathSize>, MaxCount> paths;
StaticVector<AssetRef, MaxCount> files;
@ -70,6 +74,12 @@ OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref<const char *(size_t)>
#endif
}
inline tl::expected<OwnedClxSpriteList, std::string> LoadCl2WithStatus(const char *pszName, uint16_t width)
{
ASSIGN_OR_RETURN(OwnedClxSpriteListOrSheet result, LoadCl2ListOrSheetWithStatus(pszName, PointerOrValue<uint16_t> { width }));
return std::move(result).list();
}
inline OwnedClxSpriteList LoadCl2(const char *pszName, uint16_t width)
{
return LoadCl2ListOrSheet(pszName, PointerOrValue<uint16_t> { width }).list();

15
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<OwnedClxSpriteListOrSheet, std::string> LoadClxListOrSheetWithStatus(const char *path)
{
size_t size;
std::unique_ptr<uint8_t[]> data = LoadFileInMem<uint8_t>(path, &size);
return OwnedClxSpriteListOrSheet::FromBuffer(std::move(data), size);
tl::expected<std::unique_ptr<uint8_t[]>, std::string> data = LoadFileInMemWithStatus<uint8_t>(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<OwnedClxSpriteListOrSheet, std::string> result = LoadClxListOrSheetWithStatus(path);
if (!result.has_value()) app_fatal(result.error());
return std::move(result).value();
}
} // namespace devilution

13
Source/engine/load_clx.hpp

@ -1,16 +1,29 @@
#pragma once
#include <string>
#include <expected.hpp>
#include "clx_sprite.hpp"
#include "utils/status_macros.hpp"
namespace devilution {
OwnedClxSpriteListOrSheet LoadClxListOrSheet(const char *path);
tl::expected<OwnedClxSpriteListOrSheet, std::string> LoadClxListOrSheetWithStatus(const char *path);
inline OwnedClxSpriteList LoadClx(const char *path)
{
return LoadClxListOrSheet(path).list();
}
inline tl::expected<OwnedClxSpriteList, std::string> 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();

91
Source/engine/load_file.hpp

@ -6,6 +6,7 @@
#include <cstring>
#include <memory>
#include <expected.hpp>
#include <fmt/core.h>
#include "appfat.h"
@ -18,24 +19,49 @@
namespace devilution {
template <typename T>
void LoadFileInMem(const char *path, T *data)
tl::expected<void, std::string> 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 <typename T>
void LoadFileInMem(const char *path, T *data, std::size_t count)
void LoadFileInMem(const char *path, T *data)
{
const tl::expected<void, std::string> result = LoadFileInMemWithStatus<T>(path, data);
if (!result.has_value()) app_fatal(result.error());
}
template <typename T>
tl::expected<void, std::string> 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 <typename T>
void LoadFileInMem(const char *path, T *data, std::size_t count)
{
tl::expected<void, std::string> result = LoadFileInMemWithStatus<T>(path, data, count);
if (!result.has_value()) app_fatal(result.error());
}
template <typename T>
@ -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 <typename T, std::size_t N>
tl::expected<void, std::string> LoadFileInMemWithStatus(const char *path, std::array<T, N> &data)
{
return LoadFileInMemWithStatus(path, data.data(), N);
}
template <typename T, std::size_t N>
void LoadFileInMem(const char *path, std::array<T, N> &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 <typename T = std::byte>
std::unique_ptr<T[]> LoadFileInMem(const char *path, std::size_t *numRead = nullptr)
tl::expected<std::unique_ptr<T[]>, 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<T[]> 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 <typename T = std::byte>
std::unique_ptr<T[]> LoadFileInMem(const char *path, std::size_t *numRead = nullptr)
{
tl::expected<std::unique_ptr<T[]>, std::string> result = LoadFileInMemWithStatus<T>(path, numRead);
if (!result.has_value()) app_fatal(result.error());
return std::move(result).value();
}
/**

42
Source/engine/sound.cpp

@ -11,8 +11,10 @@
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <SDL.h>
#include <expected.hpp>
#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<void, std::string> 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<std::uint8_t>(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<std::unique_ptr<SoundSample>> duplicateSounds;
@ -185,16 +180,23 @@ void snd_play_snd(TSnd *pSnd, int lVolume, int lPan)
pSnd->start_tc = tc;
}
std::unique_ptr<TSnd> sound_file_load(const char *path, bool stream)
tl::expected<std::unique_ptr<TSnd>, std::string> SoundFileLoadWithStatus(const char *path, bool stream)
{
auto snd = std::make_unique<TSnd>();
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<TSnd> sound_file_load(const char *path, bool stream)
{
tl::expected<std::unique_ptr<TSnd>, 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;
}

3
Source/engine/sound.h

@ -9,6 +9,8 @@
#include <memory>
#include <string>
#include <expected.hpp>
#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<TSnd> sound_file_load(const char *path, bool stream = false);
tl::expected<std::unique_ptr<TSnd>, std::string> SoundFileLoadWithStatus(const char *path, bool stream = false);
void snd_init();
void snd_deinit();
_music_id GetLevelMusic(dungeon_type dungeonType);

1
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<TSnd> sound_file_load(const char *path, bool stream) { return nullptr; }
tl::expected<std::unique_ptr<TSnd>, std::string> SoundFileLoadWithStatus(const char *path, bool stream) { return nullptr; }
TSnd::~TSnd() { }
void snd_init() { }
void snd_deinit() { }

4
Source/gamemenu.cpp

@ -310,7 +310,9 @@ void gamemenu_load_game(bool /*bActivate*/)
DeactivateVirtualGamepad();
FreeVirtualGamepadTextures();
#endif
LoadGame(false);
if (tl::expected<void, std::string> result = LoadGame(false); !result.has_value()) {
app_fatal(result.error());
}
#if !defined(USE_SDL1) && !defined(__vita__)
if (renderer != nullptr) {
InitVirtualGamepadTextures(*renderer);

57
Source/interfac.cpp

@ -6,8 +6,10 @@
#include <cstdint>
#include <optional>
#include <string>
#include <SDL.h>
#include <expected.hpp>
#include "control.h"
#include "engine.h"
@ -236,11 +238,12 @@ void DoLoad(interface_mode uMsg)
IncProgress();
Player &myPlayer = *MyPlayer;
tl::expected<void, std::string> 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<uint8_t>(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<std::string>("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<std::string *>(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.

1
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,

29
Source/levels/gendung.cpp

@ -4,10 +4,12 @@
#include <cstdint>
#include <memory>
#include <stack>
#include <string>
#include <utility>
#include <vector>
#include <ankerl/unordered_dense.h>
#include <expected.hpp>
#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<void, std::string> 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()

2
Source/levels/gendung.h

@ -325,7 +325,7 @@ struct Miniset {
return HasAnyOf(SOLData[dPiece[coords.x][coords.y]], property);
}
void LoadLevelSOLData();
tl::expected<void, std::string> LoadLevelSOLData();
void SetDungeonMicros();
void DRLG_InitTrans();
void DRLG_MRectTrans(WorldTilePosition origin, WorldTilePosition extent);

12
Source/lighting.cpp

@ -8,6 +8,9 @@
#include <algorithm>
#include <cstdint>
#include <numeric>
#include <string>
#include <expected.hpp>
#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<void, std::string> 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()

3
Source/lighting.h

@ -11,6 +11,7 @@
#include <type_traits>
#include <vector>
#include <expected.hpp>
#include <function_ref.hpp>
#include "automap.h"
@ -65,7 +66,7 @@ void DoUnLight(Point position, uint8_t radius);
void DoLighting(Point position, uint8_t radius, DisplacementOf<int8_t> offset);
void DoUnVision(Point position, uint8_t radius);
void DoVision(Point position, uint8_t radius, MapExplorationType doAutomap, bool visible);
void LoadTrns();
tl::expected<void, std::string> LoadTrns();
void MakeLightTable();
#ifdef _DEBUG
void ToggleLighting();

45
Source/loadsave.cpp

@ -9,9 +9,11 @@
#include <cstdint>
#include <cstring>
#include <numeric>
#include <string>
#include <SDL.h>
#include <ankerl/unordered_dense.h>
#include <expected.hpp>
#include <fmt/core.h>
#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<void, std::string> LoadLevel(LevelConversionData *levelConversionData)
{
char szName[MaxMpqPathSize];
std::optional<SaveReader> 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<int8_t>();
@ -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<void, std::string> 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<void, std::string> 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<uint32_t>()))
app_fatal(_("Invalid save file"));
if (!IsHeaderValid(file.NextLE<uint32_t>())) {
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<int32_t>();
int tmpNobjects = file.NextBE<int32_t>();
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<uint32_t>();
@ -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<int8_t>();
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<void, std::string> LoadLevel()
{
LoadLevel(nullptr);
return LoadLevel(nullptr);
}
} // namespace devilution

8
Source/loadsave.h

@ -7,6 +7,8 @@
#include <cstdint>
#include <expected.hpp>
#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<void, std::string> 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<void, std::string> LoadLevel();
tl::expected<void, std::string> ConvertLevels(SaveWriter &saveWriter);
void LoadStash();
void SaveStash(SaveWriter &stashWriter);

8
Source/lua/modules/dev/monsters.cpp

@ -58,7 +58,9 @@ std::string DebugCmdSpawnUniqueMonster(std::string name, std::optional<unsigned>
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<size_t, std::string> 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<unsigned> 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<size_t, std::string> 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;

24
Source/misdat.cpp

@ -7,6 +7,7 @@
#include <array>
#include <cstdint>
#include <string>
#include <vector>
#include <expected.hpp>
@ -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<void, std::string> 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<std::underlying_type_t<MissileID>>(missileId)];
}
void InitMissileGFX(bool loadHellfireGraphics)
tl::expected<void, std::string> InitMissileGFX(bool loadHellfireGraphics)
{
if (HeadlessMode)
return;
return {};
for (size_t mi = 0; mi < MissileSpriteData.size(); ++mi) {
if (!loadHellfireGraphics && mi >= static_cast<uint8_t>(MissileGraphicID::HorkSpawn))
break;
if (MissileSpriteData[mi].flags == MissileGraphicsFlags::MonsterOwned)
continue;
MissileSpriteData[mi].LoadGFX();
RETURN_IF_ERROR(MissileSpriteData[mi].LoadGFX());
}
return {};
}
void FreeMissileGFX()

6
Source/misdat.h

@ -10,6 +10,8 @@
#include <string>
#include <type_traits>
#include <expected.hpp>
#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<void, std::string> LoadGFX();
void FreeGFX()
{
@ -209,7 +211,7 @@ MissileFileData &GetMissileSpriteData(MissileGraphicID graphicId);
void LoadMissileData();
void InitMissileGFX(bool loadHellfireGraphics = false);
tl::expected<void, std::string> InitMissileGFX(bool loadHellfireGraphics = false);
void FreeMissileGFX();
} // namespace devilution

227
Source/monster.cpp

@ -12,8 +12,10 @@
#include <algorithm>
#include <array>
#include <numeric>
#include <string>
#include <string_view>
#include <expected.hpp>
#include <fmt/core.h>
#include <fmt/format.h>
@ -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<void, std::string> PlaceUniqueMonst(UniqueMonsterType uniqindex, size_t minionType, int bosspacksize)
{
const auto &uniqueMonsterData = UniqueMonstersData[static_cast<size_t>(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<void, std::string> 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<void, std::string> 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<uint16_t>("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<uint16_t>("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<uint16_t>("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<uint16_t>("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<uint16_t>("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<uint16_t>("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<void, std::string> LoadDiabMonsts()
{
{
auto dunData = LoadFileInMem<uint16_t>("levels\\l4data\\diab1.dun");
SetMapMonsters(dunData.get(), DiabloQuad1.megaToWorld());
ASSIGN_OR_RETURN(auto dunData, LoadFileInMemWithStatus<uint16_t>("levels\\l4data\\diab1.dun"));
RETURN_IF_ERROR(SetMapMonsters(dunData.get(), DiabloQuad1.megaToWorld()));
}
{
auto dunData = LoadFileInMem<uint16_t>("levels\\l4data\\diab2a.dun");
SetMapMonsters(dunData.get(), DiabloQuad2.megaToWorld());
ASSIGN_OR_RETURN(auto dunData, LoadFileInMemWithStatus<uint16_t>("levels\\l4data\\diab2a.dun"));
RETURN_IF_ERROR(SetMapMonsters(dunData.get(), DiabloQuad2.megaToWorld()));
}
{
auto dunData = LoadFileInMem<uint16_t>("levels\\l4data\\diab3a.dun");
SetMapMonsters(dunData.get(), DiabloQuad3.megaToWorld());
ASSIGN_OR_RETURN(auto dunData, LoadFileInMemWithStatus<uint16_t>("levels\\l4data\\diab3a.dun"));
RETURN_IF_ERROR(SetMapMonsters(dunData.get(), DiabloQuad3.megaToWorld()));
}
{
auto dunData = LoadFileInMem<uint16_t>("levels\\l4data\\diab4a.dun");
SetMapMonsters(dunData.get(), DiabloQuad4.megaToWorld());
ASSIGN_OR_RETURN(auto dunData, LoadFileInMemWithStatus<uint16_t>("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<size_t, std::string> 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<void, std::string> InitTRNForUniqueMonster(Monster &monster)
{
char filestr[64];
*BufCopy(filestr, R"(monsters\monsters\)", UniqueMonstersData[static_cast<size_t>(monster.uniqueType)].mTrnName, ".trn") = '\0';
monster.uniqueMonsterTRN = LoadFileInMem<uint8_t>(filestr);
ASSIGN_OR_RETURN(monster.uniqueMonsterTRN, LoadFileInMemWithStatus<uint8_t>(filestr));
return {};
}
void PrepareUniqueMonst(Monster &monster, UniqueMonsterType monsterType, size_t minionType, int bosspacksize, const UniqueMonsterData &uniqueMonsterData)
tl::expected<void, std::string> 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<void, std::string> 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<void, std::string> 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<void, std::string> 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<void, std::string> InitAllMonsterGFX()
{
if (HeadlessMode)
return;
return {};
using LevelMonsterTypeIndices = StaticVector<size_t, 8>;
std::vector<LevelMonsterTypeIndices> monstersBySprite(GetNumMonsterSprites());
@ -3478,12 +3490,12 @@ void InitAllMonsterGFX()
for (size_t i = 1; i < monsterTypes.size(); ++i) {
MonsterSpritesData spritesDataCopy { std::unique_ptr<std::byte[]> { 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<void, std::string> 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<void, std::string> 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<uint8_t>(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<bool(Point)> clear, Point startPoint, Point endP
return position == endPoint;
}
void SyncMonsterAnim(Monster &monster)
tl::expected<void, std::string> 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)

22
Source/monster.h

@ -10,7 +10,9 @@
#include <array>
#include <functional>
#include <string>
#include <expected.hpp>
#include <function_ref.hpp>
#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<void, std::string> 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<void, std::string> GetLevelMTypes();
tl::expected<size_t, std::string> AddMonsterType(_monster_id type, placeflag placeflag);
inline tl::expected<size_t, std::string> AddMonsterType(UniqueMonsterType uniqueType, placeflag placeflag)
{
return AddMonsterType(UniqueMonstersData[static_cast<size_t>(uniqueType)].mtype, placeflag);
}
void InitMonsterSND(CMonster &monsterType);
void InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData = {});
void InitAllMonsterGFX();
tl::expected<void, std::string> InitMonsterSND(CMonster &monsterType);
tl::expected<void, std::string> InitMonsterGFX(CMonster &monsterType, MonsterSpritesData &&spritesData = {});
tl::expected<void, std::string> InitAllMonsterGFX();
void WeakenNaKrul();
void InitGolems();
void InitMonsters();
void SetMapMonsters(const uint16_t *dunData, Point startPosition);
tl::expected<void, std::string> InitMonsters();
tl::expected<void, std::string> 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<bool(Point)> clear, Point startPoint, Point endPoint);
void SyncMonsterAnim(Monster &monster);
tl::expected<void, std::string> SyncMonsterAnim(Monster &monster);
void M_FallenFear(Point position);
void PrintMonstHistory(int mt);
void PrintUniqueHistory();

13
Source/objects.cpp

@ -7,9 +7,11 @@
#include <cmath>
#include <cstdint>
#include <ctime>
#include <string>
#include <algorithm>
#include <expected.hpp>
#include <fmt/core.h>
#include "DiabloUI/ui_flags.hpp"
@ -3671,10 +3673,10 @@ bool IsItemBlockingObjectAtPosition(Point position)
return false;
}
void LoadLevelObjects(uint16_t filesWidths[65])
tl::expected<void, std::string> 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<object_graphic_id>(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<void, std::string> InitObjectGFX()
{
uint16_t filesWidths[65] = {};
@ -3728,7 +3731,7 @@ void InitObjectGFX()
}
}
LoadLevelObjects(filesWidths);
return LoadLevelObjects(filesWidths);
}
void FreeObjectGFX()

5
Source/objects.h

@ -7,6 +7,9 @@
#include <cmath>
#include <cstdint>
#include <string>
#include <expected.hpp>
#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<void, std::string> InitObjectGFX();
void FreeObjectGFX();
void AddL1Objs(int x1, int y1, int x2, int y2);
void AddL2Objs(int x1, int y1, int x2, int y2);

13
Source/panels/charpanel.cpp

@ -5,6 +5,7 @@
#include <algorithm>
#include <string>
#include <expected.hpp>
#include <fmt/format.h>
#include <function_ref.hpp>
@ -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<void, std::string> 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()

6
Source/panels/charpanel.hpp

@ -1,5 +1,9 @@
#pragma once
#include <string>
#include <expected.hpp>
#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<void, std::string> LoadCharPanel();
void FreeCharPanel();
} // namespace devilution

9
Source/panels/mainpanel.cpp

@ -2,6 +2,9 @@
#include <cstdint>
#include <optional>
#include <string>
#include <expected.hpp>
#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<void, std::string> LoadMainPanel()
{
std::optional<OwnedSurface> 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()

6
Source/panels/mainpanel.hpp

@ -1,5 +1,9 @@
#pragma once
#include <string>
#include <expected.hpp>
#include "engine/clx_sprite.hpp"
namespace devilution {
@ -7,7 +11,7 @@ namespace devilution {
extern OptionalOwnedClxSpriteList PanelButtonDown;
extern OptionalOwnedClxSpriteList TalkButton;
void LoadMainPanel();
tl::expected<void, std::string> LoadMainPanel();
void FreeMainPanel();
} // namespace devilution

11
Source/panels/spell_book.cpp

@ -2,7 +2,9 @@
#include <cstdint>
#include <optional>
#include <string>
#include <expected.hpp>
#include <fmt/format.h>
#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<void, std::string> InitSpellBook()
{
spellBookBackground = LoadCel("data\\spellbk", static_cast<uint16_t>(SidePanelSize.width));
spellBookButtons = LoadCel("data\\spellbkb", SpellBookButtonWidth());
LoadSmallSpellIcons();
ASSIGN_OR_RETURN(spellBookBackground, LoadCelWithStatus("data\\spellbk", static_cast<uint16_t>(SidePanelSize.width)));
ASSIGN_OR_RETURN(spellBookButtons, LoadCelWithStatus("data\\spellbkb", SpellBookButtonWidth()));
return LoadSmallSpellIcons();
}
void FreeSpellBook()

6
Source/panels/spell_book.hpp

@ -1,11 +1,15 @@
#pragma once
#include <string>
#include <expected.hpp>
#include "engine/clx_sprite.hpp"
#include "engine/surface.hpp"
namespace devilution {
void InitSpellBook();
tl::expected<void, std::string> InitSpellBook();
void FreeSpellBook();
void CheckSBook();
void DrawSpellBook(const Surface &out);

24
Source/panels/spell_icons.cpp

@ -84,24 +84,25 @@ const SpellIcon SpellITbl[] = {
} // namespace
void LoadLargeSpellIcons()
tl::expected<void, std::string> 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<void, std::string> 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()

7
Source/panels/spell_icons.hpp

@ -1,6 +1,9 @@
#pragma once
#include <cstdint>
#include <string>
#include <expected.hpp>
#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<void, std::string> LoadLargeSpellIcons();
void FreeLargeSpellIcons();
void LoadSmallSpellIcons();
tl::expected<void, std::string> LoadSmallSpellIcons();
void FreeSmallSpellIcons();
} // namespace devilution

5
Source/pfile.cpp

@ -10,6 +10,7 @@
#include <string_view>
#include <ankerl/unordered_dense.h>
#include <expected.hpp>
#include <fmt/core.h>
#include "codec.h"
@ -778,10 +779,10 @@ void pfile_save_level()
SaveLevel(saveWriter);
}
void pfile_convert_levels()
tl::expected<void, std::string> pfile_convert_levels()
{
SaveWriter saveWriter = GetSaveWriter(gSaveNumber);
ConvertLevels(saveWriter);
return ConvertLevels(saveWriter);
}
void pfile_remove_temp_files()

5
Source/pfile.h

@ -6,6 +6,9 @@
#pragma once
#include <cstdint>
#include <string>
#include <expected.hpp>
#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<void, std::string> pfile_convert_levels();
void pfile_remove_temp_files();
std::unique_ptr<std::byte[]> pfile_read(const char *pszName, size_t *pdwLen);
void pfile_update(bool forceSave);

14
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

135
Source/utils/sdl_thread.h

@ -1,66 +1,69 @@
#pragma once
#include <SDL.h>
#include <memory>
#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<SDL_Thread, void (*)(SDL_Thread *)> 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 <memory>
#include <SDL.h>
#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<SDL_Thread, void (*)(SDL_Thread *)> 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

22
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)
Loading…
Cancel
Save