From 41f43ea3f5f730f806e5c3b4cd64fdd4401771f4 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Tue, 23 Aug 2022 11:01:19 +0900 Subject: [PATCH] Support unpacked MPQs from devilutionx-mpq-tools https://github.com/diasurgical/devilutionx-mpq-tools produces an unpacked MPQ with all the graphics converted to CLX and the unused files removed. This is primarily useful on RAM-constrained platforms, such as PS2, because it eliminates the MPQ overhead. Adds a build option to load from such unpacked directories instead of the MPQ. These directories are searched for in the same locations where the MPQs would be searched for otherwise. Example directory layout: * /usr/local/share/diasurgical/devilutionx/diabdat/ -- unpacked and converted diabdat.mpq * /usr/local/share/diasurgical/devilutionx/hellfire/ -- unpacked and converted hellfire MPQs (all of them merged into 1 directory) * /usr/local/share/diasurgical/devilutionx/fonts/ -- unpacked fonts.mpq * /usr/local/share/diasurgical/devilutionx/pl/ -- unpacked pl.mpq These directory structure is produced by calling `unpack_and_minify_mpq` --- CMake/Definitions.cmake | 1 + CMakeLists.txt | 1 + Source/DiabloUI/button.cpp | 2 +- Source/DiabloUI/credits.cpp | 6 +- Source/DiabloUI/diabloui.cpp | 29 ++++----- Source/DiabloUI/diabloui.h | 1 + Source/DiabloUI/dialogs.cpp | 6 +- Source/DiabloUI/mainmenu.cpp | 6 +- Source/DiabloUI/progress.cpp | 6 +- Source/DiabloUI/scrollbar.cpp | 6 +- Source/DiabloUI/selconn.cpp | 2 +- Source/DiabloUI/selgame.cpp | 2 +- Source/DiabloUI/selhero.cpp | 6 +- Source/DiabloUI/selok.cpp | 4 +- Source/DiabloUI/selstart.cpp | 2 +- Source/DiabloUI/settingsmenu.cpp | 4 +- Source/DiabloUI/title.cpp | 6 +- Source/control.cpp | 20 +++--- Source/cursor.cpp | 4 +- Source/debug.cpp | 2 +- Source/diablo.cpp | 19 ++++-- Source/discord/discord.cpp | 7 +- Source/doom.cpp | 2 +- Source/engine/assets.cpp | 52 +++++++++++---- Source/engine/clx_sprite.hpp | 20 +++++- Source/engine/load_cel.cpp | 17 ++++- Source/engine/load_cel.hpp | 6 ++ Source/engine/load_cl2.cpp | 15 ++++- Source/engine/load_cl2.hpp | 10 +++ Source/engine/load_clx.cpp | 6 +- Source/engine/load_pcx.cpp | 44 +++++++++++-- Source/engine/load_pcx.hpp | 12 +++- Source/engine/render/text_render.cpp | 2 +- Source/engine/sound.cpp | 2 +- Source/gmenu.cpp | 10 +-- Source/init.cpp | 97 +++++++++++++++++++++++++--- Source/init.h | 54 +++++++++++++++- Source/interfac.cpp | 29 +++++---- Source/inv.cpp | 8 +-- Source/items.cpp | 2 +- Source/levels/gendung.h | 4 +- Source/menu.cpp | 2 +- Source/minitext.cpp | 2 +- Source/misdat.cpp | 8 ++- Source/monster.cpp | 2 +- Source/objects.cpp | 2 +- Source/options.cpp | 4 +- Source/panels/info_box.cpp | 4 +- Source/panels/spell_book.cpp | 6 +- Source/panels/spell_icons.cpp | 4 +- Source/player.cpp | 40 ++++++------ Source/player.h | 36 +++++------ Source/towners.cpp | 32 ++++----- Source/utils/file_util.h | 6 ++ Source/utils/language.cpp | 2 +- Source/utils/paths.cpp | 14 ++-- Source/utils/paths.h | 6 ++ test/timedemo_test.cpp | 2 +- 58 files changed, 491 insertions(+), 215 deletions(-) diff --git a/CMake/Definitions.cmake b/CMake/Definitions.cmake index 8214193f9..b76fe164f 100644 --- a/CMake/Definitions.cmake +++ b/CMake/Definitions.cmake @@ -19,6 +19,7 @@ foreach( DEVILUTIONX_RESAMPLER_SPEEX DEVILUTIONX_RESAMPLER_SDL DEVILUTIONX_PALETTE_TRANSPARENCY_BLACK_16_LUT + UNPACKED_MPQS ) if(${def_name}) list(APPEND DEVILUTIONX_DEFINITIONS ${def_name}) diff --git a/CMakeLists.txt b/CMakeLists.txt index b86743938..57c4a752b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,6 +121,7 @@ RELEASE_OPTION(DEVILUTIONX_STATIC_CXX_STDLIB "Link C++ standard library statical include(MoldLinker) # Memory / performance trade-off options +option(UNPACKED_MPQS "Expect MPQs to be unpacked and the data converted with devilutionx-mpq-tools" OFF) option(DISABLE_STREAMING_MUSIC "Disable streaming music (to work around broken platform implementations)" OFF) mark_as_advanced(DISABLE_STREAMING_MUSIC) option(DISABLE_STREAMING_SOUNDS "Disable streaming sounds (to work around broken platform implementations)" OFF) diff --git a/Source/DiabloUI/button.cpp b/Source/DiabloUI/button.cpp index 1e0d770e4..3a702dbed 100644 --- a/Source/DiabloUI/button.cpp +++ b/Source/DiabloUI/button.cpp @@ -20,7 +20,7 @@ void LoadDialogButtonGraphics() { ButtonSprites = LoadOptionalClx("ui_art\\dvl_but_sml.clx"); if (!ButtonSprites) { - ButtonSprites = LoadPcxSpriteList("ui_art\\but_sml.pcx", 15); + ButtonSprites = LoadPcxSpriteList("ui_art\\but_sml", 15); } } diff --git a/Source/DiabloUI/credits.cpp b/Source/DiabloUI/credits.cpp index 52bc9da43..56aba5028 100644 --- a/Source/DiabloUI/credits.cpp +++ b/Source/DiabloUI/credits.cpp @@ -170,7 +170,7 @@ bool TextDialog(char const *const *text, std::size_t textLines) bool UiCreditsDialog() { ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\creditsw.clx"); - LoadBackgroundArt("ui_art\\credits.pcx"); + LoadBackgroundArt("ui_art\\credits"); return TextDialog(CreditLines, CreditLinesSize); } @@ -179,10 +179,10 @@ bool UiSupportDialog() { if (gbIsHellfire) { ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\supportw.clx"); - LoadBackgroundArt("ui_art\\support.pcx"); + LoadBackgroundArt("ui_art\\support"); } else { ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\creditsw.clx"); - LoadBackgroundArt("ui_art\\credits.pcx"); + LoadBackgroundArt("ui_art\\credits"); } return TextDialog(SupportLines, SupportLinesSize); diff --git a/Source/DiabloUI/diabloui.cpp b/Source/DiabloUI/diabloui.cpp index 3c9d165cd..a82faecd1 100644 --- a/Source/DiabloUI/diabloui.cpp +++ b/Source/DiabloUI/diabloui.cpp @@ -544,7 +544,7 @@ bool IsInsideRect(const SDL_Event &event, const SDL_Rect &rect) void LoadHeros() { constexpr unsigned PortraitHeight = 76; - ArtHero = LoadPcxSpriteList("ui_art\\heros.pcx", -static_cast(PortraitHeight)); + ArtHero = LoadPcxSpriteList("ui_art\\heros", -static_cast(PortraitHeight)); if (!ArtHero) return; const uint16_t numPortraits = ClxSpriteList { *ArtHero }.numSprites(); @@ -561,32 +561,25 @@ void LoadHeros() for (size_t i = 0; i <= enum_size::value; ++i) { char portraitPath[18]; - *BufCopy(portraitPath, "ui_art\\hero", i, ".pcx") = '\0'; - - SDL_RWops *handle = OpenAsset(portraitPath); - if (handle == nullptr) { - // Portrait overrides are optional, ignore the error and continue. - SDL_ClearError(); - continue; - } - ArtHeroOverrides[i] = PcxToClx(handle); + *BufCopy(portraitPath, "ui_art\\hero", i) = '\0'; + ArtHeroOverrides[i] = LoadPcx(portraitPath, /*transparentColor=*/std::nullopt, /*outPalette=*/nullptr, /*logError=*/false); } } void LoadUiGFX() { if (gbIsHellfire) { - ArtLogo = LoadPcxSpriteList("ui_art\\hf_logo2.pcx", /*numFrames=*/16, /*transparentColor=*/0); + ArtLogo = LoadPcxSpriteList("ui_art\\hf_logo2", /*numFrames=*/16, /*transparentColor=*/0); } else { - ArtLogo = LoadPcxSpriteList("ui_art\\smlogo.pcx", /*numFrames=*/15, /*transparentColor=*/250); + ArtLogo = LoadPcxSpriteList("ui_art\\smlogo", /*numFrames=*/15, /*transparentColor=*/250); } - DifficultyIndicator[0] = LoadPcx("ui_art\\radio1.pcx", /*transparentColor=*/0); - DifficultyIndicator[1] = LoadPcx("ui_art\\radio3.pcx", /*transparentColor=*/0); - ArtFocus[FOCUS_SMALL] = LoadPcxSpriteList("ui_art\\focus16.pcx", /*numFrames=*/8, /*transparentColor=*/250); - ArtFocus[FOCUS_MED] = LoadPcxSpriteList("ui_art\\focus.pcx", /*numFrames=*/8, /*transparentColor=*/250); - ArtFocus[FOCUS_BIG] = LoadPcxSpriteList("ui_art\\focus42.pcx", /*numFrames=*/8, /*transparentColor=*/250); + DifficultyIndicator[0] = LoadPcx("ui_art\\radio1", /*transparentColor=*/0); + DifficultyIndicator[1] = LoadPcx("ui_art\\radio3", /*transparentColor=*/0); + ArtFocus[FOCUS_SMALL] = LoadPcxSpriteList("ui_art\\focus16", /*numFrames=*/8, /*transparentColor=*/250); + ArtFocus[FOCUS_MED] = LoadPcxSpriteList("ui_art\\focus", /*numFrames=*/8, /*transparentColor=*/250); + ArtFocus[FOCUS_BIG] = LoadPcxSpriteList("ui_art\\focus42", /*numFrames=*/8, /*transparentColor=*/250); - ArtCursor = LoadPcx("ui_art\\cursor.pcx", /*transparentColor=*/0); + ArtCursor = LoadPcx("ui_art\\cursor", /*transparentColor=*/0); LoadHeros(); } diff --git a/Source/DiabloUI/diabloui.h b/Source/DiabloUI/diabloui.h index 6e49424c6..58e4d8089 100644 --- a/Source/DiabloUI/diabloui.h +++ b/Source/DiabloUI/diabloui.h @@ -7,6 +7,7 @@ #include "DiabloUI/ui_item.h" #include "engine/clx_sprite.hpp" +#include "engine/load_pcx.hpp" // IWYU pragma: export #include "player.h" #include "utils/display.h" diff --git a/Source/DiabloUI/dialogs.cpp b/Source/DiabloUI/dialogs.cpp index 2aaf31bcb..21f35a1b2 100644 --- a/Source/DiabloUI/dialogs.cpp +++ b/Source/DiabloUI/dialogs.cpp @@ -39,14 +39,14 @@ OptionalClxSprite LoadDialogSprite(bool hasCaption, bool isError) { constexpr uint8_t TransparentColor = 255; if (!hasCaption) { - ownedDialogSprite = LoadPcx(isError ? "ui_art\\srpopup.pcx" : "ui_art\\spopup.pcx", TransparentColor); + ownedDialogSprite = LoadPcx(isError ? "ui_art\\srpopup" : "ui_art\\spopup", TransparentColor); } else if (isError) { ownedDialogSprite = LoadOptionalClx("ui_art\\dvl_lrpopup.clx"); if (!ownedDialogSprite) { - ownedDialogSprite = LoadPcx("ui_art\\lrpopup.pcx", TransparentColor); + ownedDialogSprite = LoadPcx("ui_art\\lrpopup", TransparentColor); } } else { - ownedDialogSprite = LoadPcx("ui_art\\lpopup.pcx", TransparentColor); + ownedDialogSprite = LoadPcx("ui_art\\lpopup", TransparentColor); } if (!ownedDialogSprite) return std::nullopt; diff --git a/Source/DiabloUI/mainmenu.cpp b/Source/DiabloUI/mainmenu.cpp index b46193ba5..806603e7f 100644 --- a/Source/DiabloUI/mainmenu.cpp +++ b/Source/DiabloUI/mainmenu.cpp @@ -46,9 +46,9 @@ void MainmenuLoad(const char *name) if (!gbIsSpawn || gbIsHellfire) { if (gbIsHellfire) ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\mainmenuw.clx"); - LoadBackgroundArt("ui_art\\mainmenu.pcx"); + LoadBackgroundArt("ui_art\\mainmenu"); } else { - LoadBackgroundArt("ui_art\\swmmenu.pcx"); + LoadBackgroundArt("ui_art\\swmmenu"); } UiAddBackground(&vecMainMenuDialog); @@ -102,7 +102,7 @@ bool UiMainMenuDialog(const char *name, _mainmenu_selections *pdwResult, int att while (MainMenuResult == MAINMENU_NONE) { UiClearScreen(); UiPollAndRender(); - if (SDL_GetTicks() >= dwAttractTicks && (diabdat_mpq || hellfire_mpq)) { + if (SDL_GetTicks() >= dwAttractTicks && (HaveDiabdat() || HaveHellfire())) { MainMenuResult = MAINMENU_ATTRACT_MODE; } } diff --git a/Source/DiabloUI/progress.cpp b/Source/DiabloUI/progress.cpp index 9e1e50b56..55fc19808 100644 --- a/Source/DiabloUI/progress.cpp +++ b/Source/DiabloUI/progress.cpp @@ -30,14 +30,14 @@ void DialogActionCancel() void ProgressLoadBackground() { UiLoadBlackBackground(); - ArtPopupSm = LoadPcx("ui_art\\spopup.pcx"); - ArtProgBG = LoadPcx("ui_art\\prog_bg.pcx"); + ArtPopupSm = LoadPcx("ui_art\\spopup"); + ArtProgBG = LoadPcx("ui_art\\prog_bg"); } void ProgressLoadForeground() { LoadDialogButtonGraphics(); - ProgFil = LoadPcx("ui_art\\prog_fil.pcx"); + ProgFil = LoadPcx("ui_art\\prog_fil"); const Point uiPosition = GetUIRectangle().position; SDL_Rect rect3 = { (Sint16)(uiPosition.x + 265), (Sint16)(uiPosition.y + 267), DialogButtonWidth, DialogButtonHeight }; diff --git a/Source/DiabloUI/scrollbar.cpp b/Source/DiabloUI/scrollbar.cpp index d0da8d689..7ca5d587b 100644 --- a/Source/DiabloUI/scrollbar.cpp +++ b/Source/DiabloUI/scrollbar.cpp @@ -10,9 +10,9 @@ OptionalOwnedClxSpriteList ArtScrollBarArrow; void LoadScrollBar() { - ArtScrollBarBackground = LoadPcx("ui_art\\sb_bg.pcx"); - ArtScrollBarThumb = LoadPcx("ui_art\\sb_thumb.pcx"); - ArtScrollBarArrow = LoadPcxSpriteList("ui_art\\sb_arrow.pcx", 4); + ArtScrollBarBackground = LoadPcx("ui_art\\sb_bg"); + ArtScrollBarThumb = LoadPcx("ui_art\\sb_thumb"); + ArtScrollBarArrow = LoadPcxSpriteList("ui_art\\sb_arrow", 4); } void UnloadScrollBar() diff --git a/Source/DiabloUI/selconn.cpp b/Source/DiabloUI/selconn.cpp index 3c93214d2..f68048f49 100644 --- a/Source/DiabloUI/selconn.cpp +++ b/Source/DiabloUI/selconn.cpp @@ -35,7 +35,7 @@ void SelconnSelect(int value); void SelconnLoad() { - LoadBackgroundArt("ui_art\\selconn.pcx"); + LoadBackgroundArt("ui_art\\selconn"); #ifndef NONET #ifndef DISABLE_ZERO_TIER diff --git a/Source/DiabloUI/selgame.cpp b/Source/DiabloUI/selgame.cpp index e39c96d5e..47d5a0aa7 100644 --- a/Source/DiabloUI/selgame.cpp +++ b/Source/DiabloUI/selgame.cpp @@ -55,7 +55,7 @@ void selgame_FreeVectors() void selgame_Init() { - LoadBackgroundArt("ui_art\\selgame.pcx"); + LoadBackgroundArt("ui_art\\selgame"); LoadScrollBar(); } diff --git a/Source/DiabloUI/selhero.cpp b/Source/DiabloUI/selhero.cpp index 4b21e4fe4..35b7c0304 100644 --- a/Source/DiabloUI/selhero.cpp +++ b/Source/DiabloUI/selhero.cpp @@ -247,7 +247,7 @@ void RemoveSelHeroBackground() void AddSelHeroBackground() { - LoadBackgroundArt("ui_art\\selhero.pcx"); + LoadBackgroundArt("ui_art\\selhero"); vecSelHeroDialog.insert(vecSelHeroDialog.begin(), std::make_unique((*ArtBackground)[0], MakeSdlRect(0, GetUIRectangle().position.y, 0, 0), UiFlags::AlignCenter)); } @@ -255,7 +255,7 @@ void AddSelHeroBackground() void SelheroClassSelectorSelect(int value) { auto hClass = static_cast(vecSelHeroDlgItems[value]->m_value); - if (gbIsSpawn && (hClass == HeroClass::Rogue || hClass == HeroClass::Sorcerer || (hClass == HeroClass::Bard && !hfbard_mpq))) { + if (gbIsSpawn && (hClass == HeroClass::Rogue || hClass == HeroClass::Sorcerer || (hClass == HeroClass::Bard && !gbBard))) { RemoveSelHeroBackground(); UiSelOkDialog(nullptr, _("The Rogue and Sorcerer are only available in the full retail version of Diablo. Visit https://www.gog.com/game/diablo to purchase.").data(), false); AddSelHeroBackground(); @@ -350,7 +350,7 @@ void SelheroLoadSelect(int value) selhero_isSavegame = false; SelheroFree(); - LoadBackgroundArt("ui_art\\selgame.pcx"); + LoadBackgroundArt("ui_art\\selgame"); selgame_GameSelection_Select(0); } diff --git a/Source/DiabloUI/selok.cpp b/Source/DiabloUI/selok.cpp index 4d81b681d..131722b8d 100644 --- a/Source/DiabloUI/selok.cpp +++ b/Source/DiabloUI/selok.cpp @@ -46,9 +46,9 @@ void UiSelOkDialog(const char *title, const char *body, bool background) UiLoadBlackBackground(); } else { if (!gbIsSpawn) { - LoadBackgroundArt("ui_art\\mainmenu.pcx"); + LoadBackgroundArt("ui_art\\mainmenu"); } else { - LoadBackgroundArt("ui_art\\swmmenu.pcx"); + LoadBackgroundArt("ui_art\\swmmenu"); } } diff --git a/Source/DiabloUI/selstart.cpp b/Source/DiabloUI/selstart.cpp index e0742fbee..950097e13 100644 --- a/Source/DiabloUI/selstart.cpp +++ b/Source/DiabloUI/selstart.cpp @@ -33,7 +33,7 @@ void EscPressed() void UiSelStartUpGameOption() { ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\mainmenuw.clx"); - LoadBackgroundArt("ui_art\\mainmenu.pcx"); + LoadBackgroundArt("ui_art\\mainmenu"); UiAddBackground(&vecDialog); UiAddLogo(&vecDialog); diff --git a/Source/DiabloUI/settingsmenu.cpp b/Source/DiabloUI/settingsmenu.cpp index 5f1cfce01..613d25e93 100644 --- a/Source/DiabloUI/settingsmenu.cpp +++ b/Source/DiabloUI/settingsmenu.cpp @@ -46,9 +46,9 @@ enum class SpecialMenuEntry : int8_t { bool IsValidEntry(OptionEntryBase *pOptionEntry) { auto flags = pOptionEntry->GetFlags(); - if (HasAnyOf(flags, OptionEntryFlags::NeedDiabloMpq) && !diabdat_mpq) + if (HasAnyOf(flags, OptionEntryFlags::NeedDiabloMpq) && !HaveDiabdat()) return false; - if (HasAnyOf(flags, OptionEntryFlags::NeedHellfireMpq) && !hellfire_mpq) + if (HasAnyOf(flags, OptionEntryFlags::NeedHellfireMpq) && !HaveHellfire()) return false; return HasNoneOf(flags, OptionEntryFlags::Invisible | (gbIsHellfire ? OptionEntryFlags::OnlyDiablo : OptionEntryFlags::OnlyHellfire)); } diff --git a/Source/DiabloUI/title.cpp b/Source/DiabloUI/title.cpp index 71b01a320..d408e3ccd 100644 --- a/Source/DiabloUI/title.cpp +++ b/Source/DiabloUI/title.cpp @@ -19,11 +19,11 @@ std::vector> vecTitleScreen; void TitleLoad() { if (gbIsHellfire) { - LoadBackgroundArt("ui_art\\hf_logo1.pcx", 16); + LoadBackgroundArt("ui_art\\hf_logo1", 16); ArtBackgroundWidescreen = LoadOptionalClx("ui_art\\hf_titlew.clx"); } else { - LoadBackgroundArt("ui_art\\title.pcx"); - DiabloTitleLogo = LoadPcxSpriteList("ui_art\\logo.pcx", /*numFrames=*/15, /*transparentColor=*/250); + LoadBackgroundArt("ui_art\\title"); + DiabloTitleLogo = LoadPcxSpriteList("ui_art\\logo", /*numFrames=*/15, /*transparentColor=*/250); } } diff --git a/Source/control.cpp b/Source/control.cpp index f2b66280d..8b869de54 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -546,12 +546,12 @@ void InitControlPan() LoadCharPanel(); LoadSpellIcons(); { - const OwnedClxSpriteList sprite = LoadCel("ctrlpan\\panel8.cel", GetMainPanel().size.width); + const OwnedClxSpriteList sprite = LoadCel("ctrlpan\\panel8", GetMainPanel().size.width); ClxDraw(*pBtmBuff, { 0, (GetMainPanel().size.height + 16) - 1 }, sprite[0]); } { const Point bulbsPosition { 0, 87 }; - const OwnedClxSpriteList statusPanel = LoadCel("ctrlpan\\p8bulbs.cel", 88); + const OwnedClxSpriteList statusPanel = LoadCel("ctrlpan\\p8bulbs", 88); ClxDraw(*pLifeBuff, bulbsPosition, statusPanel[0]); ClxDraw(*pManaBuff, bulbsPosition, statusPanel[1]); } @@ -560,11 +560,11 @@ void InitControlPan() if (IsChatAvailable()) { if (!HeadlessMode) { { - const OwnedClxSpriteList sprite = LoadCel("ctrlpan\\talkpanl.cel", GetMainPanel().size.width); + const OwnedClxSpriteList sprite = LoadCel("ctrlpan\\talkpanl", GetMainPanel().size.width); ClxDraw(*pBtmBuff, { 0, (GetMainPanel().size.height + 16) * 2 - 1 }, sprite[0]); } - multiButtons = LoadCel("ctrlpan\\p8but2.cel", 33); - talkButtons = LoadCel("ctrlpan\\talkbutt.cel", 61); + multiButtons = LoadCel("ctrlpan\\p8but2", 33); + talkButtons = LoadCel("ctrlpan\\talkbutt", 61); } sgbPlrTalkTbl = 0; TalkMessage[0] = '\0'; @@ -577,10 +577,10 @@ void InitControlPan() lvlbtndown = false; if (!HeadlessMode) { LoadMainPanel(); - pPanelButtons = LoadCel("ctrlpan\\panel8bu.cel", 71); + pPanelButtons = LoadCel("ctrlpan\\panel8bu", 71); static const uint16_t CharButtonsFrameWidths[9] { 95, 41, 41, 41, 41, 41, 41, 41, 41 }; - pChrButtons = LoadCel("data\\charbut.cel", CharButtonsFrameWidths); + pChrButtons = LoadCel("data\\charbut", CharButtonsFrameWidths); } ClearPanBtn(); if (!IsChatAvailable()) @@ -588,7 +588,7 @@ void InitControlPan() else PanelButtonIndex = 8; if (!HeadlessMode) - pDurIcons = LoadCel("items\\duricons.cel", 32); + pDurIcons = LoadCel("items\\duricons", 32); for (bool &buttonEnabled : chrbtn) buttonEnabled = false; chrbtnactive = false; @@ -602,8 +602,8 @@ void InitControlPan() if (!HeadlessMode) { InitSpellBook(); - pQLogCel = LoadCel("data\\quest.cel", static_cast(SidePanelSize.width)); - pGBoxBuff = LoadCel("ctrlpan\\golddrop.cel", 261); + pQLogCel = LoadCel("data\\quest", static_cast(SidePanelSize.width)); + pGBoxBuff = LoadCel("ctrlpan\\golddrop", 261); } CloseGoldDrop(); dropGoldValue = 0; diff --git a/Source/cursor.cpp b/Source/cursor.cpp index 409335768..bf0aeb1be 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -132,9 +132,9 @@ int pcurs; void InitCursor() { assert(!pCursCels); - pCursCels = LoadCel("data\\inv\\objcurs.cel", InvItemWidth1); + pCursCels = LoadCel("data\\inv\\objcurs", InvItemWidth1); if (gbIsHellfire) - pCursCels2 = LoadCel("data\\inv\\objcurs2.cel", InvItemWidth2); + pCursCels2 = LoadCel("data\\inv\\objcurs2", InvItemWidth2); ClearCursor(); } diff --git a/Source/debug.cpp b/Source/debug.cpp index 9d79699d7..c2c1a315b 100644 --- a/Source/debug.cpp +++ b/Source/debug.cpp @@ -1027,7 +1027,7 @@ std::vector DebugCmdList = { void LoadDebugGFX() { - pSquareCel = LoadCel("data\\square.cel", 64); + pSquareCel = LoadCel("data\\square", 64); } void FreeDebugGFX() diff --git a/Source/diablo.cpp b/Source/diablo.cpp index cf1d45b48..6bb391d4a 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -959,8 +959,13 @@ void SetApplicationVersions() void CheckArchivesUpToDate() { +#ifdef UNPACKED_MPQS + const bool devilutionxMpqOutOfDate = false; + const bool fontsMpqOutOfDate = font_data_path && !FileExists(*font_data_path + "fonts" + DirectorySeparator + "12-4e.clx"); +#else const bool devilutionxMpqOutOfDate = devilutionx_mpq && !devilutionx_mpq->HasFile("data\\charbg.clx"); const bool fontsMpqOutOfDate = font_mpq && !font_mpq->HasFile("fonts\\12-4e.clx"); +#endif if (devilutionxMpqOutOfDate && fontsMpqOutOfDate) { app_fatal(_("Please update devilutionx.mpq and fonts.mpq to the latest version")); @@ -1093,37 +1098,37 @@ void LoadLvlGFX() pDungeonCels = LoadFileInMem("levels\\towndata\\town.cel"); pMegaTiles = LoadFileInMem("levels\\towndata\\town.til"); } - pSpecialCels = LoadCel("levels\\towndata\\towns.cel", SpecialCelWidth); + pSpecialCels = LoadCel("levels\\towndata\\towns", SpecialCelWidth); break; case DTYPE_CATHEDRAL: pDungeonCels = LoadFileInMem("levels\\l1data\\l1.cel"); pMegaTiles = LoadFileInMem("levels\\l1data\\l1.til"); - pSpecialCels = LoadCel("levels\\l1data\\l1s.cel", SpecialCelWidth); + pSpecialCels = LoadCel("levels\\l1data\\l1s", SpecialCelWidth); break; case DTYPE_CATACOMBS: pDungeonCels = LoadFileInMem("levels\\l2data\\l2.cel"); pMegaTiles = LoadFileInMem("levels\\l2data\\l2.til"); - pSpecialCels = LoadCel("levels\\l2data\\l2s.cel", SpecialCelWidth); + pSpecialCels = LoadCel("levels\\l2data\\l2s", SpecialCelWidth); break; case DTYPE_CAVES: pDungeonCels = LoadFileInMem("levels\\l3data\\l3.cel"); pMegaTiles = LoadFileInMem("levels\\l3data\\l3.til"); - pSpecialCels = LoadCel("levels\\l1data\\l1s.cel", SpecialCelWidth); + pSpecialCels = LoadCel("levels\\l1data\\l1s", SpecialCelWidth); break; case DTYPE_HELL: pDungeonCels = LoadFileInMem("levels\\l4data\\l4.cel"); pMegaTiles = LoadFileInMem("levels\\l4data\\l4.til"); - pSpecialCels = LoadCel("levels\\l2data\\l2s.cel", SpecialCelWidth); + pSpecialCels = LoadCel("levels\\l2data\\l2s", SpecialCelWidth); break; case DTYPE_NEST: pDungeonCels = LoadFileInMem("nlevels\\l6data\\l6.cel"); pMegaTiles = LoadFileInMem("nlevels\\l6data\\l6.til"); - pSpecialCels = LoadCel("levels\\l1data\\l1s.cel", SpecialCelWidth); + pSpecialCels = LoadCel("levels\\l1data\\l1s", SpecialCelWidth); break; case DTYPE_CRYPT: pDungeonCels = LoadFileInMem("nlevels\\l5data\\l5.cel"); pMegaTiles = LoadFileInMem("nlevels\\l5data\\l5.til"); - pSpecialCels = LoadCel("nlevels\\l5data\\l5s.cel", SpecialCelWidth); + pSpecialCels = LoadCel("nlevels\\l5data\\l5s", SpecialCelWidth); break; default: app_fatal("LoadLvlGFX"); diff --git a/Source/discord/discord.cpp b/Source/discord/discord.cpp index 88a9be7d9..a9a3e5c0d 100644 --- a/Source/discord/discord.cpp +++ b/Source/discord/discord.cpp @@ -110,11 +110,10 @@ std::string GetTooltipString() std::string GetPlayerAssetString() { - constexpr char CaseDistance = 'a' - 'A'; char chars[5] { - static_cast(CharChar[static_cast(MyPlayer->_pClass)] - CaseDistance), - static_cast(ArmourChar[tracked_data.playerGfx >> 4] - CaseDistance), - static_cast(WepChar[tracked_data.playerGfx & 0xF] - CaseDistance), + CharChar[static_cast(MyPlayer->_pClass)], + ArmourChar[tracked_data.playerGfx >> 4], + WepChar[tracked_data.playerGfx & 0xF], 'a', 's' }; diff --git a/Source/doom.cpp b/Source/doom.cpp index 07a311006..6002f1dd2 100644 --- a/Source/doom.cpp +++ b/Source/doom.cpp @@ -21,7 +21,7 @@ bool DoomFlag; void doom_init() { - DoomSprite = LoadCel("items\\map\\mapztown.cel", 640); + DoomSprite = LoadCel("items\\map\\mapztown", 640); DoomFlag = true; } diff --git a/Source/engine/assets.cpp b/Source/engine/assets.cpp index e0e55809c..690ff79a6 100644 --- a/Source/engine/assets.cpp +++ b/Source/engine/assets.cpp @@ -14,6 +14,39 @@ namespace devilution { namespace { +bool IsDebugLogging() +{ + return SDL_LOG_PRIORITY_DEBUG >= SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION); +} + +SDL_RWops *OpenOptionalRWops(const std::string &path) +{ + // SDL always logs an error in Debug mode. + // We check the file presence in Debug mode to avoid this. + if (IsDebugLogging() && !FileExists(path.c_str())) + return nullptr; + return SDL_RWFromFile(path.c_str(), "rb"); +}; + +#ifdef UNPACKED_MPQS +SDL_RWops *OpenUnpackedMpqFile(const std::string &relativePath) +{ + SDL_RWops *result = nullptr; + std::string tmpPath; + const auto at = [&](const std::optional &unpackedDir) -> bool { + if (!unpackedDir) + return false; + tmpPath.clear(); + tmpPath.reserve(unpackedDir->size() + relativePath.size()); + result = OpenOptionalRWops(tmpPath.append(*unpackedDir).append(relativePath)); + return result != nullptr; + }; + at(font_data_path) || at(lang_data_path) + || (gbIsHellfire && at(hellfire_data_path)) + || at(spawn_data_path) || at(diabdat_data_path); + return result; +} +#else bool OpenMpqFile(const char *filename, MpqArchive **archive, uint32_t *fileNumber) { const MpqArchive::FileHash fileHash = MpqArchive::CalculateFileHash(filename); @@ -28,6 +61,7 @@ bool OpenMpqFile(const char *filename, MpqArchive **archive, uint32_t *fileNumbe return at(font_mpq) || at(lang_mpq) || at(devilutionx_mpq) || (gbIsHellfire && (at(hfvoice_mpq) || at(hfmusic_mpq) || at(hfbarb_mpq) || at(hfbard_mpq) || at(hfmonk_mpq) || at(hellfire_mpq))) || at(spawn_mpq) || at(diabdat_mpq); } +#endif } // namespace @@ -43,32 +77,28 @@ SDL_RWops *OpenAsset(const char *filename, bool threadsafe) SDL_RWops *rwops; - // SDL always logs an error in Debug mode. - // We check the file presence in Debug mode to avoid this. - const bool isDebug = SDL_LOG_PRIORITY_DEBUG >= SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION); - - const auto loadFile = [&rwops, isDebug](const std::string &path) { - return (!isDebug || FileExists(path.c_str())) - && (rwops = SDL_RWFromFile(path.c_str(), "rb")) != nullptr; - }; - // Files in the `PrefPath()` directory can override MPQ contents. { const std::string path = paths::PrefPath() + relativePath; - if (loadFile(path)) { + if ((rwops = OpenOptionalRWops(path)) != nullptr) { LogVerbose("Loaded MPQ file override: {}", path); return rwops; } } // Load from all the MPQ archives. +#ifdef UNPACKED_MPQS + if ((rwops = OpenUnpackedMpqFile(relativePath)) != nullptr) + return rwops; +#else MpqArchive *archive; uint32_t fileNumber; if (OpenMpqFile(filename, &archive, &fileNumber)) return SDL_RWops_FromMpqFile(*archive, fileNumber, filename, threadsafe); +#endif // Load from the `/assets` directory next to the devilutionx binary. - if (loadFile(paths::AssetsPath() + relativePath)) + if ((rwops = OpenOptionalRWops(paths::AssetsPath() + relativePath))) return rwops; #if defined(__ANDROID__) || defined(__APPLE__) diff --git a/Source/engine/clx_sprite.hpp b/Source/engine/clx_sprite.hpp index bbfdb7a78..b451c27c5 100644 --- a/Source/engine/clx_sprite.hpp +++ b/Source/engine/clx_sprite.hpp @@ -442,6 +442,18 @@ inline ClxSpriteSheet::ClxSpriteSheet(const OwnedClxSpriteSheet &owned) class OwnedClxSpriteListOrSheet; class OptionalClxSpriteListOrSheet; +inline uint16_t GetNumListsFromClxListOrSheetBuffer(const uint8_t *data, size_t size) +{ + const uint32_t maybeNumFrames = LoadLE32(data); + + // If it is a number of frames, then the last frame offset will be equal to the size of the file. + if (LoadLE32(&data[maybeNumFrames * 4 + 4]) != size) + return maybeNumFrames / 4; + + // Not a sprite sheet. + return 0; +} + /** * @brief A CLX sprite list or a sprite sheet (list of lists). */ @@ -493,7 +505,13 @@ class OptionalOwnedClxSpriteListOrSheet; */ class OwnedClxSpriteListOrSheet { public: - explicit OwnedClxSpriteListOrSheet(std::unique_ptr &&data, uint16_t numLists = 0) + static OwnedClxSpriteListOrSheet FromBuffer(std::unique_ptr &&data, size_t size) + { + const uint16_t numLists = GetNumListsFromClxListOrSheetBuffer(data.get(), size); + return OwnedClxSpriteListOrSheet { std::move(data), numLists }; + } + + explicit OwnedClxSpriteListOrSheet(std::unique_ptr &&data, uint16_t numLists) : data_(std::move(data)) , num_lists_(numLists) { diff --git a/Source/engine/load_cel.cpp b/Source/engine/load_cel.cpp index 31800ca86..717a67851 100644 --- a/Source/engine/load_cel.cpp +++ b/Source/engine/load_cel.cpp @@ -4,19 +4,32 @@ #include #endif +#include "mpq/mpq_common.hpp" +#include "utils/str_cat.hpp" + +#ifdef UNPACKED_MPQS +#include "engine/load_clx.hpp" +#else #include "engine/load_file.hpp" #include "utils/cel_to_clx.hpp" +#endif namespace devilution { OwnedClxSpriteListOrSheet LoadCelListOrSheet(const char *pszName, PointerOrValue widthOrWidths) { + char path[MaxMpqPathSize]; + *BufCopy(path, pszName, DEVILUTIONX_CEL_EXT) = '\0'; +#ifdef UNPACKED_MPQS + return LoadClxListOrSheet(path); +#else size_t size; - std::unique_ptr data = LoadFileInMem(pszName, &size); + std::unique_ptr data = LoadFileInMem(path, &size); #ifdef DEBUG_CEL_TO_CL2_SIZE - std::cout << pszName; + std::cout << path; #endif return CelToClx(data.get(), size, widthOrWidths); +#endif } } // namespace devilution diff --git a/Source/engine/load_cel.hpp b/Source/engine/load_cel.hpp index 070e9ab64..289c31524 100644 --- a/Source/engine/load_cel.hpp +++ b/Source/engine/load_cel.hpp @@ -5,6 +5,12 @@ #include "engine/clx_sprite.hpp" #include "utils/pointer_value_union.hpp" +#ifdef UNPACKED_MPQS +#define DEVILUTIONX_CEL_EXT ".clx" +#else +#define DEVILUTIONX_CEL_EXT ".cel" +#endif + namespace devilution { OwnedClxSpriteListOrSheet LoadCelListOrSheet(const char *pszName, PointerOrValue widthOrWidths); diff --git a/Source/engine/load_cl2.cpp b/Source/engine/load_cl2.cpp index e366fe351..daf7f8469 100644 --- a/Source/engine/load_cl2.cpp +++ b/Source/engine/load_cl2.cpp @@ -3,16 +3,29 @@ #include #include +#include "mpq/mpq_common.hpp" +#include "utils/str_cat.hpp" + +#ifdef UNPACKED_MPQS +#include "engine/load_clx.hpp" +#else #include "engine/load_file.hpp" #include "utils/cl2_to_clx.hpp" +#endif namespace devilution { OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue widthOrWidths) { + char path[MaxMpqPathSize]; + *BufCopy(path, pszName, DEVILUTIONX_CL2_EXT) = '\0'; +#ifdef UNPACKED_MPQS + return LoadClxListOrSheet(path); +#else size_t size; - std::unique_ptr data = LoadFileInMem(pszName, &size); + std::unique_ptr data = LoadFileInMem(path, &size); return Cl2ToClx(std::move(data), size, widthOrWidths); +#endif } } // namespace devilution diff --git a/Source/engine/load_cl2.hpp b/Source/engine/load_cl2.hpp index 9caf1d6c1..104bb1832 100644 --- a/Source/engine/load_cl2.hpp +++ b/Source/engine/load_cl2.hpp @@ -12,6 +12,12 @@ #include "utils/pointer_value_union.hpp" #include "utils/static_vector.hpp" +#ifdef UNPACKED_MPQS +#define DEVILUTIONX_CL2_EXT ".clx" +#else +#define DEVILUTIONX_CL2_EXT ".cl2" +#endif + namespace devilution { OwnedClxSpriteListOrSheet LoadCl2ListOrSheet(const char *pszName, PointerOrValue widthOrWidths); @@ -31,15 +37,19 @@ OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref totalSize += size; } auto data = std::unique_ptr { new uint8_t[totalSize] }; +#ifndef UNPACKED_MPQS const PointerOrValue frameWidth { width }; +#endif size_t accumulatedSize = sheetHeaderSize; for (size_t i = 0; i < count; ++i) { const size_t size = fileSizes[i]; if (!files[i].Read(&data[accumulatedSize], size)) app_fatal(StrCat("Read failed: ", SDL_GetError())); WriteLE32(&data[i * 4], accumulatedSize); +#ifndef UNPACKED_MPQS [[maybe_unused]] const uint16_t numLists = Cl2ToClx(&data[accumulatedSize], size, frameWidth); assert(numLists == 0); +#endif accumulatedSize += size; } return OwnedClxSpriteSheet { std::move(data), static_cast(count) }; diff --git a/Source/engine/load_clx.cpp b/Source/engine/load_clx.cpp index 082e17db1..93fc2fb43 100644 --- a/Source/engine/load_clx.cpp +++ b/Source/engine/load_clx.cpp @@ -20,12 +20,14 @@ OptionalOwnedClxSpriteListOrSheet LoadOptionalClxListOrSheet(const char *path) std::unique_ptr data { new uint8_t[size] }; SDL_RWread(handle, data.get(), size, 1); SDL_RWclose(handle); - return OwnedClxSpriteListOrSheet { std::move(data) }; + return OwnedClxSpriteListOrSheet::FromBuffer(std::move(data), size); } OwnedClxSpriteListOrSheet LoadClxListOrSheet(const char *path) { - return OwnedClxSpriteListOrSheet { LoadFileInMem(path) }; + size_t size; + std::unique_ptr data = LoadFileInMem(path, &size); + return OwnedClxSpriteListOrSheet::FromBuffer(std::move(data), size); } } // namespace devilution diff --git a/Source/engine/load_pcx.cpp b/Source/engine/load_pcx.cpp index f2de79796..f1782a92a 100644 --- a/Source/engine/load_pcx.cpp +++ b/Source/engine/load_pcx.cpp @@ -1,6 +1,7 @@ #include "engine/load_pcx.hpp" #include +#include #include #include @@ -10,10 +11,18 @@ #include -#include "engine/assets.hpp" +#include "mpq/mpq_common.hpp" #include "utils/log.hpp" +#include "utils/str_cat.hpp" + +#ifdef UNPACKED_MPQS +#include "engine/load_clx.hpp" +#include "engine/load_file.hpp" +#else +#include "engine/assets.hpp" #include "utils/pcx.hpp" #include "utils/pcx_to_clx.hpp" +#endif #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" @@ -21,11 +30,37 @@ namespace devilution { -OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional transparentColor, SDL_Color *outPalette) +OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional transparentColor, SDL_Color *outPalette, bool logError) { - SDL_RWops *handle = OpenAsset(filename); + char path[MaxMpqPathSize]; + char *pathEnd = BufCopy(path, filename, DEVILUTIONX_PCX_EXT); + *pathEnd = '\0'; +#ifdef UNPACKED_MPQS + OptionalOwnedClxSpriteList result = LoadOptionalClx(path); + if (!result) { + if (logError) + LogError("Missing file: {}", path); + return result; + } + if (outPalette != nullptr) { + std::memcpy(pathEnd - 3, "pal", 3); + std::array palette; + LoadFileInMem(path, &palette[0], palette.size()); + for (unsigned i = 0; i < 256; i++) { + outPalette[i].r = palette[i * 3]; + outPalette[i].g = palette[i * 3 + 1]; + outPalette[i].b = palette[i * 3 + 2]; +#ifndef USE_SDL1 + outPalette[i].a = SDL_ALPHA_OPAQUE; +#endif + } + } + return result; +#else + SDL_RWops *handle = OpenAsset(path); if (handle == nullptr) { - LogError("Missing file: {}", filename); + if (logError) + LogError("Missing file: {}", path); return std::nullopt; } #ifdef DEBUG_PCX_TO_CL2_SIZE @@ -35,6 +70,7 @@ OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFrames if (!result) return std::nullopt; return result; +#endif } } // namespace devilution diff --git a/Source/engine/load_pcx.hpp b/Source/engine/load_pcx.hpp index 89c56189e..21387860f 100644 --- a/Source/engine/load_pcx.hpp +++ b/Source/engine/load_pcx.hpp @@ -7,6 +7,12 @@ #include "engine/clx_sprite.hpp" #include "utils/stdcompat/optional.hpp" +#ifdef UNPACKED_MPQS +#define DEVILUTIONX_PCX_EXT ".clx" +#else +#define DEVILUTIONX_PCX_EXT ".pcx" +#endif + namespace devilution { /** @@ -18,7 +24,7 @@ namespace devilution { * @param outPalette * @return OptionalOwnedClxSpriteList */ -OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr); +OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr, bool logError = true); /** * @brief Loads a PCX file as a CLX sprite list with a single sprite. @@ -28,9 +34,9 @@ OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFrames * @param outPalette * @return OptionalOwnedClxSpriteList */ -inline OptionalOwnedClxSpriteList LoadPcx(const char *filename, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr) +inline OptionalOwnedClxSpriteList LoadPcx(const char *filename, std::optional transparentColor = std::nullopt, SDL_Color *outPalette = nullptr, bool logError = true) { - return LoadPcxSpriteList(filename, 1, transparentColor, outPalette); + return LoadPcxSpriteList(filename, 1, transparentColor, outPalette, logError); } } // namespace devilution diff --git a/Source/engine/render/text_render.cpp b/Source/engine/render/text_render.cpp index b0ed5e7d0..8dba4abcb 100644 --- a/Source/engine/render/text_render.cpp +++ b/Source/engine/render/text_render.cpp @@ -414,7 +414,7 @@ int DoDrawString(const Surface &out, string_view text, Rectangle rect, Point &ch void LoadSmallSelectionSpinner() { - pSPentSpn2Cels = LoadCel("data\\pentspn2.cel", 12); + pSPentSpn2Cels = LoadCel("data\\pentspn2", 12); } void UnloadFonts() diff --git a/Source/engine/sound.cpp b/Source/engine/sound.cpp index c6582ec29..7c7542d45 100644 --- a/Source/engine/sound.cpp +++ b/Source/engine/sound.cpp @@ -259,7 +259,7 @@ void music_start(_music_id nTrack) music_stop(); if (!gbMusicOn) return; - if (spawn_mpq) + if (HaveSpawn()) trackPath = SpawnMusicTracks[nTrack]; else trackPath = MusicTracks[nTrack]; diff --git a/Source/gmenu.cpp b/Source/gmenu.cpp index db2b62044..f10508319 100644 --- a/Source/gmenu.cpp +++ b/Source/gmenu.cpp @@ -198,12 +198,12 @@ void gmenu_init_menu() return; if (gbIsHellfire) - sgpLogo = LoadCel("data\\hf_logo3.cel", 430); + sgpLogo = LoadCel("data\\hf_logo3", 430); else - sgpLogo = LoadCel("data\\diabsmal.cel", 296); - PentSpin_cel = LoadCel("data\\pentspin.cel", 48); - option_cel = LoadCel("data\\option.cel", SliderMarkerWidth); - optbar_cel = LoadCel("data\\optbar.cel", SliderValueBoxWidth); + sgpLogo = LoadCel("data\\diabsmal", 296); + PentSpin_cel = LoadCel("data\\pentspin", 48); + option_cel = LoadCel("data\\option", SliderMarkerWidth); + optbar_cel = LoadCel("data\\optbar", SliderValueBoxWidth); } bool gmenu_is_active() diff --git a/Source/init.cpp b/Source/init.cpp index ebf2115e8..3e85bbd80 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -20,6 +20,7 @@ #include "mpq/mpq_reader.hpp" #include "options.h" #include "pfile.h" +#include "utils/file_util.h" #include "utils/language.h" #include "utils/log.hpp" #include "utils/paths.h" @@ -36,14 +37,8 @@ namespace devilution { /** True if the game is the current active window */ bool gbActive; -/** A handle to an hellfire.mpq archive. */ -std::optional hellfire_mpq; /** The current input handler function */ EventHandler CurrentEventHandler; -/** A handle to the spawn.mpq archive. */ -std::optional spawn_mpq; -/** A handle to the diabdat.mpq archive. */ -std::optional diabdat_mpq; /** Indicate if we only have access to demo data */ bool gbIsSpawn; /** Indicate if we have loaded the Hellfire expansion data */ @@ -52,6 +47,17 @@ bool gbIsHellfire; bool gbVanilla; /** Whether the Hellfire mode is required (forced). */ bool forceHellfire; + +#ifdef UNPACKED_MPQS +std::optional spawn_data_path; +std::optional diabdat_data_path; +std::optional hellfire_data_path; +std::optional font_data_path; +std::optional lang_data_path; +#else +std::optional spawn_mpq; +std::optional diabdat_mpq; +std::optional hellfire_mpq; std::optional hfmonk_mpq; std::optional hfbard_mpq; std::optional hfbarb_mpq; @@ -60,9 +66,26 @@ std::optional hfvoice_mpq; std::optional devilutionx_mpq; std::optional lang_mpq; std::optional font_mpq; +#endif namespace { +#ifdef UNPACKED_MPQS +std::optional FindUnpackedMpqData(const std::vector &paths, string_view mpqName) +{ + std::string targetPath; + for (const std::string &path : paths) { + targetPath.clear(); + targetPath.reserve(path.size() + mpqName.size() + 1); + targetPath.append(path).append(mpqName) += DirectorySeparator; + if (FileExists(targetPath)) { + LogVerbose(" Found unpacked MPQ directory: {}", targetPath); + return targetPath; + } + } + return std::nullopt; +} +#else std::optional LoadMPQ(const std::vector &paths, string_view mpqName) { std::optional archive; @@ -84,6 +107,7 @@ std::optional LoadMPQ(const std::vector &paths, string_ return std::nullopt; } +#endif std::vector GetMPQSearchPaths() { @@ -145,6 +169,13 @@ void init_cleanup() sfile_write_stash(); } +#ifdef UNPACKED_MPQS + lang_data_path = std::nullopt; + font_data_path = std::nullopt; + hellfire_data_path = std::nullopt; + diabdat_data_path = std::nullopt; + spawn_data_path = std::nullopt; +#else spawn_mpq = std::nullopt; diabdat_mpq = std::nullopt; hellfire_mpq = std::nullopt; @@ -156,6 +187,7 @@ void init_cleanup() lang_mpq = std::nullopt; font_mpq = std::nullopt; devilutionx_mpq = std::nullopt; +#endif NetClose(); } @@ -164,31 +196,77 @@ void LoadCoreArchives() { auto paths = GetMPQSearchPaths(); +#ifdef UNPACKED_MPQS + font_data_path = FindUnpackedMpqData(paths, "fonts"); +#else // !UNPACKED_MPQS #if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__3DS__) && !defined(__SWITCH__) // Load devilutionx.mpq first to get the font file for error messages devilutionx_mpq = LoadMPQ(paths, "devilutionx.mpq"); #endif font_mpq = LoadMPQ(paths, "fonts.mpq"); // Extra fonts +#endif } void LoadLanguageArchive() { +#ifdef UNPACKED_MPQS + lang_data_path = std::nullopt; +#else lang_mpq = std::nullopt; +#endif string_view code = *sgOptions.Language.code; if (code != "en") { std::string langMpqName { code }; +#ifdef UNPACKED_MPQS + lang_data_path = FindUnpackedMpqData(GetMPQSearchPaths(), langMpqName); +#else langMpqName.append(".mpq"); - - auto paths = GetMPQSearchPaths(); - lang_mpq = LoadMPQ(paths, langMpqName); + lang_mpq = LoadMPQ(GetMPQSearchPaths(), langMpqName); +#endif } } void LoadGameArchives() { auto paths = GetMPQSearchPaths(); +#if UNPACKED_MPQS + diabdat_data_path = FindUnpackedMpqData(paths, "diabdat"); + if (!diabdat_data_path) { + spawn_data_path = FindUnpackedMpqData(paths, "spawn"); + if (spawn_data_path) + gbIsSpawn = true; + } + if (!HeadlessMode) { + SDL_RWops *handle = OpenAsset("ui_art\\title.clx"); + if (handle == nullptr) { + LogError("{}", SDL_GetError()); + InsertCDDlg(_("diabdat.mpq or spawn.mpq")); + } + SDL_RWclose(handle); + } + hellfire_data_path = FindUnpackedMpqData(paths, "hellfire"); + if (hellfire_data_path) + gbIsHellfire = true; + if (forceHellfire && !hellfire_data_path) + InsertCDDlg("hellfire"); + + const bool hasMonk = FileExists(*hellfire_data_path + "plrgfx/monk/mha/mhaas.clx"); + const bool hasMusic = FileExists(*hellfire_data_path + "music/dlvlf.wav") + || FileExists(*hellfire_data_path + "music/dlvlf.mp3"); + const bool hasVoice = FileExists(*hellfire_data_path + "sfx/hellfire/cowsut1.wav") + || FileExists(*hellfire_data_path + "sfx/hellfire/cowsut1.mp3"); + // Bard and barbarian are not currently supported in unpacked mode + // because they use the same paths as rogue and warrior. + gbBard = false; + gbBarbarian = false; + + if (gbIsHellfire && (!hasMonk || !hasMusic || !hasVoice)) { + UiErrorOkDialog(_("Some Hellfire MPQs are missing"), _("Not all Hellfire MPQs were found.\nPlease copy all the hf*.mpq files.")); + diablo_quit(1); + } +#else // !UNPACKED_MPQS diabdat_mpq = LoadMPQ(paths, "DIABDAT.MPQ"); if (!diabdat_mpq) { // DIABDAT.MPQ is uppercase on the original CD and the GOG version. @@ -229,6 +307,7 @@ void LoadGameArchives() UiErrorOkDialog(_("Some Hellfire MPQs are missing"), _("Not all Hellfire MPQs were found.\nPlease copy all the hf*.mpq files.")); diablo_quit(1); } +#endif } void init_create_window() diff --git a/Source/init.h b/Source/init.h index 75cf48190..2dd98e7c1 100644 --- a/Source/init.h +++ b/Source/init.h @@ -12,14 +12,25 @@ namespace devilution { extern bool gbActive; -extern std::optional hellfire_mpq; extern EventHandler CurrentEventHandler; -extern DVL_API_FOR_TEST std::optional spawn_mpq; -extern DVL_API_FOR_TEST std::optional diabdat_mpq; extern DVL_API_FOR_TEST bool gbIsSpawn; extern DVL_API_FOR_TEST bool gbIsHellfire; extern DVL_API_FOR_TEST bool gbVanilla; extern bool forceHellfire; + +#ifdef UNPACKED_MPQS +extern DVL_API_FOR_TEST std::optional spawn_data_path; +extern DVL_API_FOR_TEST std::optional diabdat_data_path; +extern std::optional hellfire_data_path; +extern std::optional font_data_path; +extern std::optional lang_data_path; +#else +/** A handle to the spawn.mpq archive. */ +extern DVL_API_FOR_TEST std::optional spawn_mpq; +/** A handle to the diabdat.mpq archive. */ +extern DVL_API_FOR_TEST std::optional diabdat_mpq; +/** A handle to an hellfire.mpq archive. */ +extern std::optional hellfire_mpq; extern std::optional hfmonk_mpq; extern std::optional hfbard_mpq; extern std::optional hfbarb_mpq; @@ -28,6 +39,43 @@ extern std::optional hfvoice_mpq; extern std::optional font_mpq; extern std::optional lang_mpq; extern std::optional devilutionx_mpq; +#endif + +inline bool HaveSpawn() +{ +#ifdef UNPACKED_MPQS + return bool(spawn_data_path); +#else + return bool(spawn_mpq); +#endif +} + +inline bool HaveDiabdat() +{ +#ifdef UNPACKED_MPQS + return bool(diabdat_data_path); +#else + return bool(diabdat_mpq); +#endif +} + +inline bool HaveHellfire() +{ +#ifdef UNPACKED_MPQS + return bool(hellfire_data_path); +#else + return bool(hellfire_mpq); +#endif +} + +inline bool HaveExtraFonts() +{ +#ifdef UNPACKED_MPQS + return bool(font_data_path); +#else + return bool(font_mpq); +#endif +} void init_cleanup(); void LoadCoreArchives(); diff --git a/Source/interfac.cpp b/Source/interfac.cpp index 9cfcad54e..0c4c1c701 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -14,6 +14,7 @@ #include "engine/dx.h" #include "engine/load_cel.hpp" #include "engine/load_clx.hpp" +#include "engine/load_pcx.hpp" #include "engine/palette.h" #include "engine/render/clx_render.hpp" #include "hwcursor.hpp" @@ -105,60 +106,60 @@ void LoadCutsceneBackground(interface_mode uMsg) switch (PickCutscene(uMsg)) { case CutStart: - ArtCutsceneWidescreen = LoadOptionalClx("gendata\\cutstartw.pcx"); - celPath = "gendata\\cutstart.cel"; + ArtCutsceneWidescreen = LoadOptionalClx("gendata\\cutstartw.clx"); + celPath = "gendata\\cutstart"; palPath = "gendata\\cutstart.pal"; progress_id = 1; break; case CutTown: - celPath = "gendata\\cuttt.cel"; + celPath = "gendata\\cuttt"; palPath = "gendata\\cuttt.pal"; progress_id = 1; break; case CutLevel1: - celPath = "gendata\\cutl1d.cel"; + celPath = "gendata\\cutl1d"; palPath = "gendata\\cutl1d.pal"; progress_id = 0; break; case CutLevel2: - celPath = "gendata\\cut2.cel"; + celPath = "gendata\\cut2"; palPath = "gendata\\cut2.pal"; progress_id = 2; break; case CutLevel3: - celPath = "gendata\\cut3.cel"; + celPath = "gendata\\cut3"; palPath = "gendata\\cut3.pal"; progress_id = 1; break; case CutLevel4: - celPath = "gendata\\cut4.cel"; + celPath = "gendata\\cut4"; palPath = "gendata\\cut4.pal"; progress_id = 1; break; case CutLevel5: - celPath = "nlevels\\cutl5.cel"; + celPath = "nlevels\\cutl5"; palPath = "nlevels\\cutl5.pal"; progress_id = 1; break; case CutLevel6: - celPath = "nlevels\\cutl6.cel"; + celPath = "nlevels\\cutl6"; palPath = "nlevels\\cutl6.pal"; progress_id = 1; break; case CutPortal: - ArtCutsceneWidescreen = LoadOptionalClx("gendata\\cutportlw.pcx"); - celPath = "gendata\\cutportl.cel"; + ArtCutsceneWidescreen = LoadOptionalClx("gendata\\cutportlw.clx"); + celPath = "gendata\\cutportl"; palPath = "gendata\\cutportl.pal"; progress_id = 1; break; case CutPortalRed: - ArtCutsceneWidescreen = LoadOptionalClx("gendata\\cutportrw.pcx"); - celPath = "gendata\\cutportr.cel"; + ArtCutsceneWidescreen = LoadOptionalClx("gendata\\cutportrw.clx"); + celPath = "gendata\\cutportr"; palPath = "gendata\\cutportr.pal"; progress_id = 1; break; case CutGate: - celPath = "gendata\\cutgate.cel"; + celPath = "gendata\\cutgate"; palPath = "gendata\\cutgate.pal"; progress_id = 1; break; diff --git a/Source/inv.cpp b/Source/inv.cpp index fbbdb9939..d5479c72e 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -1077,17 +1077,17 @@ void InitInv() switch (MyPlayer->_pClass) { case HeroClass::Warrior: case HeroClass::Barbarian: - pInvCels = LoadCel("data\\inv\\inv.cel", static_cast(SidePanelSize.width)); + pInvCels = LoadCel("data\\inv\\inv", static_cast(SidePanelSize.width)); break; case HeroClass::Rogue: case HeroClass::Bard: - pInvCels = LoadCel("data\\inv\\inv_rog.cel", static_cast(SidePanelSize.width)); + pInvCels = LoadCel("data\\inv\\inv_rog", static_cast(SidePanelSize.width)); break; case HeroClass::Sorcerer: - pInvCels = LoadCel("data\\inv\\inv_sor.cel", static_cast(SidePanelSize.width)); + pInvCels = LoadCel("data\\inv\\inv_sor", static_cast(SidePanelSize.width)); break; case HeroClass::Monk: - pInvCels = LoadCel(!gbIsSpawn ? "data\\inv\\inv_sor.cel" : "data\\inv\\inv.cel", static_cast(SidePanelSize.width)); + pInvCels = LoadCel(!gbIsSpawn ? "data\\inv\\inv_sor" : "data\\inv\\inv", static_cast(SidePanelSize.width)); break; } } diff --git a/Source/items.cpp b/Source/items.cpp index c9f6810fd..402c6bcda 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -2315,7 +2315,7 @@ void InitItemGFX() int itemTypes = gbIsHellfire ? ITEMTYPES : 35; for (int i = 0; i < itemTypes; i++) { - *BufCopy(arglist, "items\\", ItemDropNames[i], ".cel") = '\0'; + *BufCopy(arglist, "items\\", ItemDropNames[i]) = '\0'; itemanims[i] = LoadCel(arglist, ItemAnimWidth); } memset(UniqueItemFlags, 0, sizeof(UniqueItemFlags)); diff --git a/Source/levels/gendung.h b/Source/levels/gendung.h index 52fb818fb..f3bdac79f 100644 --- a/Source/levels/gendung.h +++ b/Source/levels/gendung.h @@ -198,8 +198,8 @@ extern DVL_API_FOR_TEST int8_t dCorpse[MAXDUNX][MAXDUNY]; extern DVL_API_FOR_TEST int8_t dObject[MAXDUNX][MAXDUNY]; /** * Contains the arch frame numbers of the map from the special tileset - * (e.g. "levels/l1data/l1s.cel"). Note, the special tileset of Tristram (i.e. - * "levels/towndata/towns.cel") contains trees rather than arches. + * (e.g. "levels/l1data/l1s"). Note, the special tileset of Tristram (i.e. + * "levels/towndata/towns") contains trees rather than arches. */ extern char dSpecial[MAXDUNX][MAXDUNY]; extern int themeCount; diff --git a/Source/menu.cpp b/Source/menu.cpp index 8cc7873e9..54c741202 100644 --- a/Source/menu.cpp +++ b/Source/menu.cpp @@ -165,7 +165,7 @@ void mainmenu_loop() done = true; break; case MAINMENU_ATTRACT_MODE: - if (gbIsSpawn && !diabdat_mpq) + if (gbIsSpawn && !HaveDiabdat()) done = false; else if (gbActive) PlayIntro(); diff --git a/Source/minitext.cpp b/Source/minitext.cpp index a6324b32d..868b3afb0 100644 --- a/Source/minitext.cpp +++ b/Source/minitext.cpp @@ -126,7 +126,7 @@ void FreeQuestText() void InitQuestText() { - pTextBoxCels = LoadCel("data\\textbox.cel", 591); + pTextBoxCels = LoadCel("data\\textbox", 591); } void InitQTextMsg(_speech_id m) diff --git a/Source/misdat.cpp b/Source/misdat.cpp index ca19a0b07..d11154799 100644 --- a/Source/misdat.cpp +++ b/Source/misdat.cpp @@ -7,7 +7,9 @@ #include "engine/load_cl2.hpp" #include "missiles.h" +#include "mpq/mpq_common.hpp" #include "utils/file_name_generator.hpp" +#include "utils/str_cat.hpp" namespace devilution { @@ -236,10 +238,12 @@ void MissileFileData::LoadGFX() if (name.empty()) return; - FileNameGenerator pathGenerator({ "missiles\\", name }, ".cl2"); if (animFAmt == 1) { - sprites.emplace(OwnedClxSpriteListOrSheet { LoadCl2(pathGenerator(), animWidth) }); + char path[MaxMpqPathSize]; + *BufCopy(path, "missiles\\", name) = '\0'; + sprites.emplace(OwnedClxSpriteListOrSheet { LoadCl2(path, animWidth) }); } else { + FileNameGenerator pathGenerator({ "missiles\\", name }, DEVILUTIONX_CL2_EXT); sprites.emplace(OwnedClxSpriteListOrSheet { LoadMultipleCl2Sheet<16>(pathGenerator, animFAmt, animWidth) }); } } diff --git a/Source/monster.cpp b/Source/monster.cpp index 4c32f2293..608d99050 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -3346,7 +3346,7 @@ void InitMonsterGFX(CMonster &monsterType) if (!HeadlessMode) { monsterType.animData = MultiFileLoader {}( numAnims, - FileNameWithCharAffixGenerator({ "monsters\\", monsterData.assetsSuffix }, ".cl2", Animletter), + FileNameWithCharAffixGenerator({ "monsters\\", monsterData.assetsSuffix }, DEVILUTIONX_CL2_EXT, Animletter), animOffsets.data(), hasAnim); } diff --git a/Source/objects.cpp b/Source/objects.cpp index b4567d1a7..875869b9a 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -3649,7 +3649,7 @@ void LoadLevelObjects(uint16_t filesWidths[65]) ObjFileList[numobjfiles] = static_cast(i); char filestr[32]; - *BufCopy(filestr, "objects\\", ObjMasterLoadList[i], ".cel") = '\0'; + *BufCopy(filestr, "objects\\", ObjMasterLoadList[i]) = '\0'; pObjCels[numobjfiles] = LoadCel(filestr, filesWidths[i]); numobjfiles++; } diff --git a/Source/options.cpp b/Source/options.cpp index 95d4e951a..7960b13cb 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -1155,7 +1155,7 @@ void OptionEntryLanguageCode::CheckLanguagesAreInitialized() const languages.emplace_back("hr", "Hrvatski"); languages.emplace_back("it", "Italiano"); - if (font_mpq) { + if (HaveExtraFonts()) { languages.emplace_back("ja", "日本語"); languages.emplace_back("ko", "한국어"); } @@ -1167,7 +1167,7 @@ void OptionEntryLanguageCode::CheckLanguagesAreInitialized() const languages.emplace_back("sv", "Svenska"); languages.emplace_back("uk", "Українська"); - if (font_mpq) { + if (HaveExtraFonts()) { languages.emplace_back("zh_CN", "汉语"); languages.emplace_back("zh_TW", "漢語"); } diff --git a/Source/panels/info_box.cpp b/Source/panels/info_box.cpp index 90af6a714..de535fd4c 100644 --- a/Source/panels/info_box.cpp +++ b/Source/panels/info_box.cpp @@ -9,8 +9,8 @@ OptionalOwnedClxSpriteList pSTextSlidCels; void InitInfoBoxGfx() { - pSTextSlidCels = LoadCel("data\\textslid.cel", 12); - pSTextBoxCels = LoadCel("data\\textbox2.cel", 271); + pSTextSlidCels = LoadCel("data\\textslid", 12); + pSTextBoxCels = LoadCel("data\\textbox2", 271); } void FreeInfoBoxGfx() diff --git a/Source/panels/spell_book.cpp b/Source/panels/spell_book.cpp index 794ead508..21510309b 100644 --- a/Source/panels/spell_book.cpp +++ b/Source/panels/spell_book.cpp @@ -81,9 +81,9 @@ spell_type GetSBookTrans(spell_id ii, bool townok) void InitSpellBook() { - pSpellBkCel = LoadCel("data\\spellbk.cel", static_cast(SidePanelSize.width)); - pSBkBtnCel = LoadCel("data\\spellbkb.cel", gbIsHellfire ? 61 : 76); - pSBkIconCels = LoadCel("data\\spelli2.cel", 37); + pSpellBkCel = LoadCel("data\\spellbk", static_cast(SidePanelSize.width)); + pSBkBtnCel = LoadCel("data\\spellbkb", gbIsHellfire ? 61 : 76); + pSBkIconCels = LoadCel("data\\spelli2", 37); Player &player = *MyPlayer; if (player._pClass == HeroClass::Warrior) { diff --git a/Source/panels/spell_icons.cpp b/Source/panels/spell_icons.cpp index 24e6f87d6..93bf92308 100644 --- a/Source/panels/spell_icons.cpp +++ b/Source/panels/spell_icons.cpp @@ -71,9 +71,9 @@ const char SpellITbl[] = { void LoadSpellIcons() { if (!gbIsHellfire) - pSpellCels = LoadCel("ctrlpan\\spelicon.cel", SPLICONLENGTH); + pSpellCels = LoadCel("ctrlpan\\spelicon", SPLICONLENGTH); else - pSpellCels = LoadCel("data\\spelicon.cel", SPLICONLENGTH); + pSpellCels = LoadCel("data\\spelicon", SPLICONLENGTH); SetSpellTrans(RSPLTYPE_SKILL); } diff --git a/Source/player.cpp b/Source/player.cpp index e43bdfae0..fd37c7dbc 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -185,12 +185,12 @@ constexpr int8_t PlrGFXAnimLens[enum_size::value][11] = { }; const char *const ClassPathTbl[] = { - "Warrior", - "Rogue", - "Sorceror", - "Monk", - "Rogue", - "Warrior", + "warrior", + "rogue", + "sorceror", + "monk", + "rogue", + "warrior", }; void PmChangeLightOff(Player &player) @@ -1701,9 +1701,9 @@ void CheckCheatStats(Player &player) HeroClass GetPlayerSpriteClass(HeroClass cls) { - if (cls == HeroClass::Bard && !hfbard_mpq) + if (cls == HeroClass::Bard && !gbBard) return HeroClass::Rogue; - if (cls == HeroClass::Barbarian && !hfbarb_mpq) + if (cls == HeroClass::Barbarian && !gbBarbarian) return HeroClass::Warrior; return cls; } @@ -2227,45 +2227,45 @@ void LoadPlrGFX(Player &player, player_graphic graphic) const char *szCel; switch (graphic) { case player_graphic::Stand: - szCel = "AS"; + szCel = "as"; if (leveltype == DTYPE_TOWN) - szCel = "ST"; + szCel = "st"; break; case player_graphic::Walk: - szCel = "AW"; + szCel = "aw"; if (leveltype == DTYPE_TOWN) - szCel = "WL"; + szCel = "wl"; break; case player_graphic::Attack: if (leveltype == DTYPE_TOWN) return; - szCel = "AT"; + szCel = "at"; break; case player_graphic::Hit: if (leveltype == DTYPE_TOWN) return; - szCel = "HT"; + szCel = "ht"; break; case player_graphic::Lightning: - szCel = "LM"; + szCel = "lm"; break; case player_graphic::Fire: - szCel = "FM"; + szCel = "fm"; break; case player_graphic::Magic: - szCel = "QM"; + szCel = "qm"; break; case player_graphic::Death: if (animWeaponId != PlayerWeaponGraphic::Unarmed) return; - szCel = "DT"; + szCel = "dt"; break; case player_graphic::Block: if (leveltype == DTYPE_TOWN) return; if (!player._pBlockFlag) return; - szCel = "BL"; + szCel = "bl"; break; default: app_fatal("PLR:2"); @@ -2276,7 +2276,7 @@ void LoadPlrGFX(Player &player, player_graphic graphic) char prefix[3] = { CharChar[static_cast(cls)], ArmourChar[player._pgfxnum >> 4], WepChar[static_cast(animWeaponId)] }; char pszName[256]; - *fmt::format_to(pszName, FMT_COMPILE(R"(plrgfx\{0}\{1}\{1}{2}.cl2)"), path, string_view(prefix, 3), szCel) = 0; + *fmt::format_to(pszName, FMT_COMPILE(R"(plrgfx\{0}\{1}\{1}{2})"), path, string_view(prefix, 3), szCel) = 0; const uint16_t animationWidth = GetPlayerSpriteWidth(cls, graphic, animWeaponId); animationData.sprites = LoadCl2Sheet(pszName, animationWidth); std::optional> trn = GetClassTRN(player); diff --git a/Source/player.h b/Source/player.h index a1bb2cbc4..b68cfdab6 100644 --- a/Source/player.h +++ b/Source/player.h @@ -164,31 +164,31 @@ use_enum_as_flags(SpellFlag); /** Maps from armor animation to letter used in graphic files. */ constexpr std::array ArmourChar = { - 'L', // light - 'M', // medium - 'H', // heavy + 'l', // light + 'm', // medium + 'h', // heavy }; /** Maps from weapon animation to letter used in graphic files. */ constexpr std::array WepChar = { - 'N', // unarmed - 'U', // no weapon + shield - 'S', // sword + no shield - 'D', // sword + shield - 'B', // bow - 'A', // axe - 'M', // blunt + no shield - 'H', // blunt + shield - 'T', // staff + 'n', // unarmed + 'u', // no weapon + shield + 's', // sword + no shield + 'd', // sword + shield + 'b', // bow + 'a', // axe + 'm', // blunt + no shield + 'h', // blunt + shield + 't', // staff }; /** Maps from player class to letter used in graphic files. */ constexpr std::array CharChar = { - 'W', // warrior - 'R', // rogue - 'S', // sorcerer - 'M', // monk - 'B', - 'C', + 'w', // warrior + 'r', // rogue + 's', // sorcerer + 'm', // monk + 'b', + 'c', }; /** diff --git a/Source/towners.cpp b/Source/towners.cpp index e152e2e59..9e0eee629 100644 --- a/Source/towners.cpp +++ b/Source/towners.cpp @@ -77,7 +77,7 @@ void InitSmith(Towner &towner, const TownerData &townerData) }; towner.animOrder = AnimOrder; towner.animOrderSize = sizeof(AnimOrder); - LoadTownerAnimations(towner, "towners\\smith\\smithn.cel", 16, 3); + LoadTownerAnimations(towner, "towners\\smith\\smithn", 16, 3); towner.name = _("Griswold the Blacksmith"); } @@ -99,7 +99,7 @@ void InitBarOwner(Towner &towner, const TownerData &townerData) }; towner.animOrder = AnimOrder; towner.animOrderSize = sizeof(AnimOrder); - LoadTownerAnimations(towner, "towners\\twnf\\twnfn.cel", 16, 3); + LoadTownerAnimations(towner, "towners\\twnf\\twnfn", 16, 3); towner.name = _("Ogden the Tavern owner"); } @@ -108,7 +108,7 @@ void InitTownDead(Towner &towner, const TownerData &townerData) towner._tAnimWidth = 96; towner.animOrder = nullptr; towner.animOrderSize = 0; - LoadTownerAnimations(towner, "towners\\butch\\deadguy.cel", 8, 6); + LoadTownerAnimations(towner, "towners\\butch\\deadguy", 8, 6); towner.name = _("Wounded Townsman"); } @@ -130,7 +130,7 @@ void InitWitch(Towner &towner, const TownerData &townerData) }; towner.animOrder = AnimOrder; towner.animOrderSize = sizeof(AnimOrder); - LoadTownerAnimations(towner, "towners\\townwmn1\\witch.cel", 19, 6); + LoadTownerAnimations(towner, "towners\\townwmn1\\witch", 19, 6); towner.name = _("Adria the Witch"); } @@ -139,7 +139,7 @@ void InitBarmaid(Towner &towner, const TownerData &townerData) towner._tAnimWidth = 96; towner.animOrder = nullptr; towner.animOrderSize = 0; - LoadTownerAnimations(towner, "towners\\townwmn1\\wmnn.cel", 18, 6); + LoadTownerAnimations(towner, "towners\\townwmn1\\wmnn", 18, 6); towner.name = _("Gillian the Barmaid"); } @@ -148,7 +148,7 @@ void InitBoy(Towner &towner, const TownerData &townerData) towner._tAnimWidth = 96; towner.animOrder = nullptr; towner.animOrderSize = 0; - LoadTownerAnimations(towner, "towners\\townboy\\pegkid1.cel", 20, 6); + LoadTownerAnimations(towner, "towners\\townboy\\pegkid1", 20, 6); towner.name = _("Wirt the Peg-legged boy"); } @@ -170,7 +170,7 @@ void InitHealer(Towner &towner, const TownerData &townerData) }; towner.animOrder = AnimOrder; towner.animOrderSize = sizeof(AnimOrder); - LoadTownerAnimations(towner, "towners\\healer\\healer.cel", 20, 6); + LoadTownerAnimations(towner, "towners\\healer\\healer", 20, 6); towner.name = _("Pepin the Healer"); } @@ -187,7 +187,7 @@ void InitTeller(Towner &towner, const TownerData &townerData) }; towner.animOrder = AnimOrder; towner.animOrderSize = sizeof(AnimOrder); - LoadTownerAnimations(towner, "towners\\strytell\\strytell.cel", 25, 3); + LoadTownerAnimations(towner, "towners\\strytell\\strytell", 25, 3); towner.name = _("Cain the Elder"); } @@ -203,7 +203,7 @@ void InitDrunk(Towner &towner, const TownerData &townerData) }; towner.animOrder = AnimOrder; towner.animOrderSize = sizeof(AnimOrder); - LoadTownerAnimations(towner, "towners\\drunk\\twndrunk.cel", 18, 3); + LoadTownerAnimations(towner, "towners\\drunk\\twndrunk", 18, 3); towner.name = _("Farnham the Drunk"); } @@ -238,15 +238,15 @@ void InitFarmer(Towner &towner, const TownerData &townerData) towner._tAnimWidth = 96; towner.animOrder = nullptr; towner.animOrderSize = 0; - LoadTownerAnimations(towner, "towners\\farmer\\farmrn2.cel", 15, 3); + LoadTownerAnimations(towner, "towners\\farmer\\farmrn2", 15, 3); towner.name = _("Lester the farmer"); } void InitCowFarmer(Towner &towner, const TownerData &townerData) { - const char *celPath = "towners\\farmer\\cfrmrn2.cel"; + const char *celPath = "towners\\farmer\\cfrmrn2"; if (Quests[Q_JERSEY]._qactive == QUEST_DONE) { - celPath = "towners\\farmer\\mfrmrn2.cel"; + celPath = "towners\\farmer\\mfrmrn2"; } towner._tAnimWidth = 96; towner.animOrder = nullptr; @@ -260,7 +260,7 @@ void InitGirl(Towner &towner, const TownerData &townerData) towner._tAnimWidth = 96; towner.animOrder = nullptr; towner.animOrderSize = 0; - LoadTownerAnimations(towner, "towners\\girl\\girlw1.cel", 20, 6); + LoadTownerAnimations(towner, "towners\\girl\\girlw1", 20, 6); towner.name = _("Celia"); } @@ -649,7 +649,7 @@ void TalkToCowFarmer(Player &player, Towner &cowFarmer) InitQTextMsg(TEXT_JERSEY8); quest._qactive = QUEST_DONE; auto curFrame = cowFarmer._tAnimFrame; - LoadTownerAnimations(cowFarmer, "towners\\farmer\\mfrmrn2.cel", 15, 3); + LoadTownerAnimations(cowFarmer, "towners\\farmer\\mfrmrn2", 15, 3); cowFarmer._tAnimFrame = std::min(curFrame, cowFarmer._tAnimLen - 1); return; } @@ -729,7 +729,7 @@ void TalkToGirl(Player &player, Towner &girl) quest._qlog = false; quest._qactive = QUEST_DONE; auto curFrame = girl._tAnimFrame; - LoadTownerAnimations(girl, "towners\\girl\\girls1.cel", 20, 6); + LoadTownerAnimations(girl, "towners\\girl\\girls1", 20, 6); girl._tAnimFrame = std::min(curFrame, girl._tAnimLen - 1); if (gbIsMultiplayer) NetSendCmdQuest(true, quest); @@ -820,7 +820,7 @@ void InitTowners() { assert(!CowSprites); - CowSprites.emplace(LoadCelSheet("towners\\animals\\cow.cel", 128)); + CowSprites.emplace(LoadCelSheet("towners\\animals\\cow", 128)); int i = 0; for (const auto &townerData : TownersData) { diff --git a/Source/utils/file_util.h b/Source/utils/file_util.h index b385a622b..cec0a7fde 100644 --- a/Source/utils/file_util.h +++ b/Source/utils/file_util.h @@ -12,6 +12,12 @@ namespace devilution { bool FileExists(const char *path); + +inline bool FileExists(const std::string &str) +{ + return FileExists(str.c_str()); +} + bool FileExistsAndIsWriteable(const char *path); bool GetFileSize(const char *path, std::uintmax_t *size); bool ResizeFile(const char *path, std::uintmax_t size); diff --git a/Source/utils/language.cpp b/Source/utils/language.cpp index adea252dd..b164f4421 100644 --- a/Source/utils/language.cpp +++ b/Source/utils/language.cpp @@ -326,7 +326,7 @@ void LanguageInitialize() translationKeys = nullptr; translationValues = nullptr; - if (IsSmallFontTall() && !font_mpq) { + if (IsSmallFontTall() && !HaveExtraFonts()) { UiErrorOkDialog( "Missing fonts.mpq", StrCat("fonts.mpq is required for locale \"", diff --git a/Source/utils/paths.cpp b/Source/utils/paths.cpp index 8186f147c..4f813bc10 100644 --- a/Source/utils/paths.cpp +++ b/Source/utils/paths.cpp @@ -21,6 +21,12 @@ #include "utils/sdl2_to_1_2_backports.h" #endif +#ifdef _WIN32 +#define DIRECTORY_SEPARATOR_STR "\\" +#else +#define DIRECTORY_SEPARATOR_STR "/" +#endif + namespace devilution { namespace paths { @@ -32,14 +38,6 @@ std::optional prefPath; std::optional configPath; std::optional assetsPath; -#ifdef _WIN32 -constexpr char DirectorySeparator = '\\'; -#define DIRECTORY_SEPARATOR_STR "\\" -#else -constexpr char DirectorySeparator = '/'; -#define DIRECTORY_SEPARATOR_STR "/" -#endif - void AddTrailingSlash(std::string &path) { if (!path.empty() && path.back() != DirectorySeparator) diff --git a/Source/utils/paths.h b/Source/utils/paths.h index 639059a74..98739f0ab 100644 --- a/Source/utils/paths.h +++ b/Source/utils/paths.h @@ -6,6 +6,12 @@ namespace devilution { +#ifdef _WIN32 +constexpr char DirectorySeparator = '\\'; +#else +constexpr char DirectorySeparator = '/'; +#endif + namespace paths { const std::string &BasePath(); diff --git a/test/timedemo_test.cpp b/test/timedemo_test.cpp index a625f5ed6..992e6df32 100644 --- a/test/timedemo_test.cpp +++ b/test/timedemo_test.cpp @@ -28,7 +28,7 @@ void RunTimedemo(std::string timedemoFolderName) // The tests need spawn.mpq or diabdat.mpq // Please provide them so that the tests can run successfully - ASSERT_TRUE(spawn_mpq || diabdat_mpq); + ASSERT_TRUE(HaveSpawn() || HaveDiabdat()); InitKeymapActions(); LoadOptions();