Browse Source

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`
pull/4599/head
Gleb Mazovetskiy 4 years ago
parent
commit
41f43ea3f5
  1. 1
      CMake/Definitions.cmake
  2. 1
      CMakeLists.txt
  3. 2
      Source/DiabloUI/button.cpp
  4. 6
      Source/DiabloUI/credits.cpp
  5. 29
      Source/DiabloUI/diabloui.cpp
  6. 1
      Source/DiabloUI/diabloui.h
  7. 6
      Source/DiabloUI/dialogs.cpp
  8. 6
      Source/DiabloUI/mainmenu.cpp
  9. 6
      Source/DiabloUI/progress.cpp
  10. 6
      Source/DiabloUI/scrollbar.cpp
  11. 2
      Source/DiabloUI/selconn.cpp
  12. 2
      Source/DiabloUI/selgame.cpp
  13. 6
      Source/DiabloUI/selhero.cpp
  14. 4
      Source/DiabloUI/selok.cpp
  15. 2
      Source/DiabloUI/selstart.cpp
  16. 4
      Source/DiabloUI/settingsmenu.cpp
  17. 6
      Source/DiabloUI/title.cpp
  18. 20
      Source/control.cpp
  19. 4
      Source/cursor.cpp
  20. 2
      Source/debug.cpp
  21. 19
      Source/diablo.cpp
  22. 7
      Source/discord/discord.cpp
  23. 2
      Source/doom.cpp
  24. 52
      Source/engine/assets.cpp
  25. 20
      Source/engine/clx_sprite.hpp
  26. 17
      Source/engine/load_cel.cpp
  27. 6
      Source/engine/load_cel.hpp
  28. 15
      Source/engine/load_cl2.cpp
  29. 10
      Source/engine/load_cl2.hpp
  30. 6
      Source/engine/load_clx.cpp
  31. 44
      Source/engine/load_pcx.cpp
  32. 12
      Source/engine/load_pcx.hpp
  33. 2
      Source/engine/render/text_render.cpp
  34. 2
      Source/engine/sound.cpp
  35. 10
      Source/gmenu.cpp
  36. 97
      Source/init.cpp
  37. 54
      Source/init.h
  38. 29
      Source/interfac.cpp
  39. 8
      Source/inv.cpp
  40. 2
      Source/items.cpp
  41. 4
      Source/levels/gendung.h
  42. 2
      Source/menu.cpp
  43. 2
      Source/minitext.cpp
  44. 8
      Source/misdat.cpp
  45. 2
      Source/monster.cpp
  46. 2
      Source/objects.cpp
  47. 4
      Source/options.cpp
  48. 4
      Source/panels/info_box.cpp
  49. 6
      Source/panels/spell_book.cpp
  50. 4
      Source/panels/spell_icons.cpp
  51. 40
      Source/player.cpp
  52. 36
      Source/player.h
  53. 32
      Source/towners.cpp
  54. 6
      Source/utils/file_util.h
  55. 2
      Source/utils/language.cpp
  56. 14
      Source/utils/paths.cpp
  57. 6
      Source/utils/paths.h
  58. 2
      test/timedemo_test.cpp

1
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})

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

2
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);
}
}

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

29
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<int>(PortraitHeight));
ArtHero = LoadPcxSpriteList("ui_art\\heros", -static_cast<int>(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<HeroClass>::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();
}

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

6
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;

6
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;
}
}

6
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 };

6
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()

2
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

2
Source/DiabloUI/selgame.cpp

@ -55,7 +55,7 @@ void selgame_FreeVectors()
void selgame_Init()
{
LoadBackgroundArt("ui_art\\selgame.pcx");
LoadBackgroundArt("ui_art\\selgame");
LoadScrollBar();
}

6
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<UiImageClx>((*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<HeroClass>(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);
}

4
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");
}
}

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

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

6
Source/DiabloUI/title.cpp

@ -19,11 +19,11 @@ std::vector<std::unique_ptr<UiItemBase>> 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);
}
}

20
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<uint16_t>(SidePanelSize.width));
pGBoxBuff = LoadCel("ctrlpan\\golddrop.cel", 261);
pQLogCel = LoadCel("data\\quest", static_cast<uint16_t>(SidePanelSize.width));
pGBoxBuff = LoadCel("ctrlpan\\golddrop", 261);
}
CloseGoldDrop();
dropGoldValue = 0;

4
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();
}

2
Source/debug.cpp

@ -1027,7 +1027,7 @@ std::vector<DebugCmdItem> DebugCmdList = {
void LoadDebugGFX()
{
pSquareCel = LoadCel("data\\square.cel", 64);
pSquareCel = LoadCel("data\\square", 64);
}
void FreeDebugGFX()

19
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<MegaTile>("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<MegaTile>("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<MegaTile>("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<MegaTile>("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<MegaTile>("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<MegaTile>("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<MegaTile>("nlevels\\l5data\\l5.til");
pSpecialCels = LoadCel("nlevels\\l5data\\l5s.cel", SpecialCelWidth);
pSpecialCels = LoadCel("nlevels\\l5data\\l5s", SpecialCelWidth);
break;
default:
app_fatal("LoadLvlGFX");

7
Source/discord/discord.cpp

@ -110,11 +110,10 @@ std::string GetTooltipString()
std::string GetPlayerAssetString()
{
constexpr char CaseDistance = 'a' - 'A';
char chars[5] {
static_cast<char>(CharChar[static_cast<int>(MyPlayer->_pClass)] - CaseDistance),
static_cast<char>(ArmourChar[tracked_data.playerGfx >> 4] - CaseDistance),
static_cast<char>(WepChar[tracked_data.playerGfx & 0xF] - CaseDistance),
CharChar[static_cast<int>(MyPlayer->_pClass)],
ArmourChar[tracked_data.playerGfx >> 4],
WepChar[tracked_data.playerGfx & 0xF],
'a',
's'
};

2
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;
}

52
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<std::string> &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__)

20
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<uint8_t[]> &&data, uint16_t numLists = 0)
static OwnedClxSpriteListOrSheet FromBuffer(std::unique_ptr<uint8_t[]> &&data, size_t size)
{
const uint16_t numLists = GetNumListsFromClxListOrSheetBuffer(data.get(), size);
return OwnedClxSpriteListOrSheet { std::move(data), numLists };
}
explicit OwnedClxSpriteListOrSheet(std::unique_ptr<uint8_t[]> &&data, uint16_t numLists)
: data_(std::move(data))
, num_lists_(numLists)
{

17
Source/engine/load_cel.cpp

@ -4,19 +4,32 @@
#include <iostream>
#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<uint16_t> widthOrWidths)
{
char path[MaxMpqPathSize];
*BufCopy(path, pszName, DEVILUTIONX_CEL_EXT) = '\0';
#ifdef UNPACKED_MPQS
return LoadClxListOrSheet(path);
#else
size_t size;
std::unique_ptr<uint8_t[]> data = LoadFileInMem<uint8_t>(pszName, &size);
std::unique_ptr<uint8_t[]> data = LoadFileInMem<uint8_t>(path, &size);
#ifdef DEBUG_CEL_TO_CL2_SIZE
std::cout << pszName;
std::cout << path;
#endif
return CelToClx(data.get(), size, widthOrWidths);
#endif
}
} // namespace devilution

6
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<uint16_t> widthOrWidths);

15
Source/engine/load_cl2.cpp

@ -3,16 +3,29 @@
#include <memory>
#include <utility>
#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<uint16_t> widthOrWidths)
{
char path[MaxMpqPathSize];
*BufCopy(path, pszName, DEVILUTIONX_CL2_EXT) = '\0';
#ifdef UNPACKED_MPQS
return LoadClxListOrSheet(path);
#else
size_t size;
std::unique_ptr<uint8_t[]> data = LoadFileInMem<uint8_t>(pszName, &size);
std::unique_ptr<uint8_t[]> data = LoadFileInMem<uint8_t>(path, &size);
return Cl2ToClx(std::move(data), size, widthOrWidths);
#endif
}
} // namespace devilution

10
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<uint16_t> widthOrWidths);
@ -31,15 +37,19 @@ OwnedClxSpriteSheet LoadMultipleCl2Sheet(tl::function_ref<const char *(size_t)>
totalSize += size;
}
auto data = std::unique_ptr<uint8_t[]> { new uint8_t[totalSize] };
#ifndef UNPACKED_MPQS
const PointerOrValue<uint16_t> 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<uint16_t>(count) };

6
Source/engine/load_clx.cpp

@ -20,12 +20,14 @@ OptionalOwnedClxSpriteListOrSheet LoadOptionalClxListOrSheet(const char *path)
std::unique_ptr<uint8_t[]> 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<uint8_t>(path) };
size_t size;
std::unique_ptr<uint8_t[]> data = LoadFileInMem<uint8_t>(path, &size);
return OwnedClxSpriteListOrSheet::FromBuffer(std::move(data), size);
}
} // namespace devilution

44
Source/engine/load_pcx.cpp

@ -1,6 +1,7 @@
#include "engine/load_pcx.hpp"
#include <cstddef>
#include <cstring>
#include <memory>
#include <utility>
@ -10,10 +11,18 @@
#include <SDL.h>
#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<uint8_t> transparentColor, SDL_Color *outPalette)
OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional<uint8_t> 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<uint8_t, 256 * 3> 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

12
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<uint8_t> transparentColor = std::nullopt, SDL_Color *outPalette = nullptr);
OptionalOwnedClxSpriteList LoadPcxSpriteList(const char *filename, int numFramesOrFrameHeight, std::optional<uint8_t> 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<uint8_t> transparentColor = std::nullopt, SDL_Color *outPalette = nullptr)
inline OptionalOwnedClxSpriteList LoadPcx(const char *filename, std::optional<uint8_t> 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

2
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()

2
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];

10
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()

97
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<MpqArchive> hellfire_mpq;
/** The current input handler function */
EventHandler CurrentEventHandler;
/** A handle to the spawn.mpq archive. */
std::optional<MpqArchive> spawn_mpq;
/** A handle to the diabdat.mpq archive. */
std::optional<MpqArchive> 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<std::string> spawn_data_path;
std::optional<std::string> diabdat_data_path;
std::optional<std::string> hellfire_data_path;
std::optional<std::string> font_data_path;
std::optional<std::string> lang_data_path;
#else
std::optional<MpqArchive> spawn_mpq;
std::optional<MpqArchive> diabdat_mpq;
std::optional<MpqArchive> hellfire_mpq;
std::optional<MpqArchive> hfmonk_mpq;
std::optional<MpqArchive> hfbard_mpq;
std::optional<MpqArchive> hfbarb_mpq;
@ -60,9 +66,26 @@ std::optional<MpqArchive> hfvoice_mpq;
std::optional<MpqArchive> devilutionx_mpq;
std::optional<MpqArchive> lang_mpq;
std::optional<MpqArchive> font_mpq;
#endif
namespace {
#ifdef UNPACKED_MPQS
std::optional<std::string> FindUnpackedMpqData(const std::vector<std::string> &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<MpqArchive> LoadMPQ(const std::vector<std::string> &paths, string_view mpqName)
{
std::optional<MpqArchive> archive;
@ -84,6 +107,7 @@ std::optional<MpqArchive> LoadMPQ(const std::vector<std::string> &paths, string_
return std::nullopt;
}
#endif
std::vector<std::string> 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()

54
Source/init.h

@ -12,14 +12,25 @@
namespace devilution {
extern bool gbActive;
extern std::optional<MpqArchive> hellfire_mpq;
extern EventHandler CurrentEventHandler;
extern DVL_API_FOR_TEST std::optional<MpqArchive> spawn_mpq;
extern DVL_API_FOR_TEST std::optional<MpqArchive> 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<std::string> spawn_data_path;
extern DVL_API_FOR_TEST std::optional<std::string> diabdat_data_path;
extern std::optional<std::string> hellfire_data_path;
extern std::optional<std::string> font_data_path;
extern std::optional<std::string> lang_data_path;
#else
/** A handle to the spawn.mpq archive. */
extern DVL_API_FOR_TEST std::optional<MpqArchive> spawn_mpq;
/** A handle to the diabdat.mpq archive. */
extern DVL_API_FOR_TEST std::optional<MpqArchive> diabdat_mpq;
/** A handle to an hellfire.mpq archive. */
extern std::optional<MpqArchive> hellfire_mpq;
extern std::optional<MpqArchive> hfmonk_mpq;
extern std::optional<MpqArchive> hfbard_mpq;
extern std::optional<MpqArchive> hfbarb_mpq;
@ -28,6 +39,43 @@ extern std::optional<MpqArchive> hfvoice_mpq;
extern std::optional<MpqArchive> font_mpq;
extern std::optional<MpqArchive> lang_mpq;
extern std::optional<MpqArchive> 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();

29
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;

8
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<uint16_t>(SidePanelSize.width));
pInvCels = LoadCel("data\\inv\\inv", static_cast<uint16_t>(SidePanelSize.width));
break;
case HeroClass::Rogue:
case HeroClass::Bard:
pInvCels = LoadCel("data\\inv\\inv_rog.cel", static_cast<uint16_t>(SidePanelSize.width));
pInvCels = LoadCel("data\\inv\\inv_rog", static_cast<uint16_t>(SidePanelSize.width));
break;
case HeroClass::Sorcerer:
pInvCels = LoadCel("data\\inv\\inv_sor.cel", static_cast<uint16_t>(SidePanelSize.width));
pInvCels = LoadCel("data\\inv\\inv_sor", static_cast<uint16_t>(SidePanelSize.width));
break;
case HeroClass::Monk:
pInvCels = LoadCel(!gbIsSpawn ? "data\\inv\\inv_sor.cel" : "data\\inv\\inv.cel", static_cast<uint16_t>(SidePanelSize.width));
pInvCels = LoadCel(!gbIsSpawn ? "data\\inv\\inv_sor" : "data\\inv\\inv", static_cast<uint16_t>(SidePanelSize.width));
break;
}
}

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

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

2
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();

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

8
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) });
}
}

2
Source/monster.cpp

@ -3346,7 +3346,7 @@ void InitMonsterGFX(CMonster &monsterType)
if (!HeadlessMode) {
monsterType.animData = MultiFileLoader<MaxAnims> {}(
numAnims,
FileNameWithCharAffixGenerator({ "monsters\\", monsterData.assetsSuffix }, ".cl2", Animletter),
FileNameWithCharAffixGenerator({ "monsters\\", monsterData.assetsSuffix }, DEVILUTIONX_CL2_EXT, Animletter),
animOffsets.data(),
hasAnim);
}

2
Source/objects.cpp

@ -3649,7 +3649,7 @@ void LoadLevelObjects(uint16_t filesWidths[65])
ObjFileList[numobjfiles] = static_cast<object_graphic_id>(i);
char filestr[32];
*BufCopy(filestr, "objects\\", ObjMasterLoadList[i], ".cel") = '\0';
*BufCopy(filestr, "objects\\", ObjMasterLoadList[i]) = '\0';
pObjCels[numobjfiles] = LoadCel(filestr, filesWidths[i]);
numobjfiles++;
}

4
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", "漢語");
}

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

6
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<uint16_t>(SidePanelSize.width));
pSBkBtnCel = LoadCel("data\\spellbkb.cel", gbIsHellfire ? 61 : 76);
pSBkIconCels = LoadCel("data\\spelli2.cel", 37);
pSpellBkCel = LoadCel("data\\spellbk", static_cast<uint16_t>(SidePanelSize.width));
pSBkBtnCel = LoadCel("data\\spellbkb", gbIsHellfire ? 61 : 76);
pSBkIconCels = LoadCel("data\\spelli2", 37);
Player &player = *MyPlayer;
if (player._pClass == HeroClass::Warrior) {

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

40
Source/player.cpp

@ -185,12 +185,12 @@ constexpr int8_t PlrGFXAnimLens[enum_size<HeroClass>::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<std::size_t>(cls)], ArmourChar[player._pgfxnum >> 4], WepChar[static_cast<std::size_t>(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<std::array<uint8_t, 256>> trn = GetClassTRN(player);

36
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<char, 4> 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<char, 9> 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<char, 6> CharChar = {
'W', // warrior
'R', // rogue
'S', // sorcerer
'M', // monk
'B',
'C',
'w', // warrior
'r', // rogue
's', // sorcerer
'm', // monk
'b',
'c',
};
/**

32
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<uint8_t>(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<uint8_t>(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) {

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

2
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 \"",

14
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<std::string> prefPath;
std::optional<std::string> configPath;
std::optional<std::string> 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)

6
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();

2
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();

Loading…
Cancel
Save