Browse Source

Add support for MPQ packed mods and turn Hellfire into one

pull/7944/head
Anders Jenbo 11 months ago
parent
commit
3fb8be385b
  1. 1
      CMake/Assets.cmake
  2. 1
      Source/CMakeLists.txt
  3. 1
      Source/DiabloUI/selstart.cpp
  4. 2
      Source/DiabloUI/settingsmenu.cpp
  5. 18
      Source/diablo.cpp
  6. 6
      Source/discord/discord.cpp
  7. 35
      Source/engine/assets.cpp
  8. 3
      Source/engine/assets.hpp
  9. 16
      Source/game_mode.cpp
  10. 5
      Source/game_mode.hpp
  11. 21
      Source/lua/lua.cpp
  12. 4
      Source/lua/lua.hpp
  13. 2
      Source/lua/modules/audio.cpp
  14. 18
      Source/lua/modules/hellfire.cpp
  15. 9
      Source/lua/modules/hellfire.hpp
  16. 18
      Source/options.cpp
  17. 5
      Source/options.h
  18. 4
      assets/lua/mods/Hellfire/init.lua

1
CMake/Assets.cmake

@ -151,6 +151,7 @@ set(devilutionx_assets
lua/devilutionx/events.lua lua/devilutionx/events.lua
lua/inspect.lua lua/inspect.lua
lua/mods/clock/init.lua lua/mods/clock/init.lua
lua/mods/Hellfire/init.lua
lua/repl_prelude.lua lua/repl_prelude.lua
nlevels/cutl5w.clx nlevels/cutl5w.clx
nlevels/cutl6w.clx nlevels/cutl6w.clx

1
Source/CMakeLists.txt

@ -107,6 +107,7 @@ set(libdevilutionx_SRCS
lua/autocomplete.cpp lua/autocomplete.cpp
lua/lua.cpp lua/lua.cpp
lua/modules/audio.cpp lua/modules/audio.cpp
lua/modules/hellfire.cpp
lua/modules/dev.cpp lua/modules/dev.cpp
lua/modules/dev/display.cpp lua/modules/dev/display.cpp
lua/modules/dev/items.cpp lua/modules/dev/items.cpp

1
Source/DiabloUI/selstart.cpp

@ -19,6 +19,7 @@ void ItemSelected(size_t value)
{ {
auto option = static_cast<StartUpGameMode>(vecDialogItems[value]->m_value); auto option = static_cast<StartUpGameMode>(vecDialogItems[value]->m_value);
GetOptions().GameMode.gameMode.SetValue(option); GetOptions().GameMode.gameMode.SetValue(option);
GetOptions().Mods.SetHellfireEnabled(option == StartUpGameMode::Hellfire);
SaveOptions(); SaveOptions();
endMenu = true; endMenu = true;
} }

2
Source/DiabloUI/settingsmenu.cpp

@ -68,8 +68,6 @@ bool IsValidEntry(OptionEntryBase *pOptionEntry)
auto flags = pOptionEntry->GetFlags(); auto flags = pOptionEntry->GetFlags();
if (HasAnyOf(flags, OptionEntryFlags::NeedDiabloMpq) && !HaveIntro()) if (HasAnyOf(flags, OptionEntryFlags::NeedDiabloMpq) && !HaveIntro())
return false; return false;
if (HasAnyOf(flags, OptionEntryFlags::NeedHellfireMpq) && !HaveHellfire())
return false;
return HasNoneOf(flags, OptionEntryFlags::Invisible | (gbIsHellfire ? OptionEntryFlags::OnlyDiablo : OptionEntryFlags::OnlyHellfire)); return HasNoneOf(flags, OptionEntryFlags::Invisible | (gbIsHellfire ? OptionEntryFlags::OnlyDiablo : OptionEntryFlags::OnlyHellfire));
} }

18
Source/diablo.cpp

@ -1192,10 +1192,18 @@ void DiabloInit()
{ {
if (forceSpawn || *GetOptions().GameMode.shareware) if (forceSpawn || *GetOptions().GameMode.shareware)
gbIsSpawn = true; gbIsSpawn = true;
if (forceDiablo || *GetOptions().GameMode.gameMode == StartUpGameMode::Diablo)
gbIsHellfire = false; bool wasHellfireDiscovered = false;
if (forceHellfire) if (!forceDiablo && !forceHellfire)
gbIsHellfire = true; wasHellfireDiscovered = (HaveHellfire() && *GetOptions().GameMode.gameMode == StartUpGameMode::Ask);
bool enableHellfire = forceHellfire || wasHellfireDiscovered;
if (!forceDiablo && *GetOptions().GameMode.gameMode == StartUpGameMode::Hellfire) { // Migrate legacy options
GetOptions().GameMode.gameMode.SetValue(StartUpGameMode::Diablo);
enableHellfire = true;
}
if (forceDiablo || enableHellfire) {
GetOptions().Mods.SetHellfireEnabled(enableHellfire);
}
gbIsHellfireSaveGame = gbIsHellfire; gbIsHellfireSaveGame = gbIsHellfire;
@ -1213,7 +1221,7 @@ void DiabloInit()
UiInitialize(); UiInitialize();
was_ui_init = true; was_ui_init = true;
if (gbIsHellfire && !forceHellfire && *GetOptions().GameMode.gameMode == StartUpGameMode::Ask) { if (wasHellfireDiscovered) {
UiSelStartUpGameOption(); UiSelStartUpGameOption();
if (!gbIsHellfire) { if (!gbIsHellfire) {
// Reinitialize the UI Elements because we changed the game // Reinitialize the UI Elements because we changed the game

6
Source/discord/discord.cpp

@ -17,9 +17,9 @@
#include <fmt/format.h> #include <fmt/format.h>
#include "config.h" #include "config.h"
#include "game_mode.hpp"
#include "levels/gendung.h" #include "levels/gendung.h"
#include "levels/setmaps.h" #include "levels/setmaps.h"
#include "lua/lua.hpp"
#include "multi.h" #include "multi.h"
#include "panels/charpanel.hpp" #include "panels/charpanel.hpp"
#include "player.h" #include "player.h"
@ -29,11 +29,11 @@
namespace devilution { namespace devilution {
namespace { namespace {
void IsHellfireChanged() void ModChanged()
{ {
discord_manager::UpdateMenu(true); discord_manager::UpdateMenu(true);
} }
const auto IsHellfireChangedHandler = (AddIsHellfireChangeHandler(IsHellfireChanged), true); const auto ModChangedHandler = (AddModsChangedHandler(ModChanged), true);
} // namespace } // namespace
namespace discord_manager { namespace discord_manager {

35
Source/engine/assets.cpp

@ -3,7 +3,6 @@
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <string_view>
#include "appfat.h" #include "appfat.h"
#include "game_mode.hpp" #include "game_mode.hpp"
@ -76,9 +75,6 @@ bool FindMpqFile(std::string_view filename, MpqArchive **archive, uint32_t *file
// Iterate over archives in reverse order to prefer files from high priority archives // Iterate over archives in reverse order to prefer files from high priority archives
for (auto rit = MpqArchives.rbegin(); rit != MpqArchives.rend(); ++rit) { for (auto rit = MpqArchives.rbegin(); rit != MpqArchives.rend(); ++rit) {
int priority = rit->first;
if (!gbIsHellfire && priority >= 8000 && priority < 9000)
continue;
if (rit->second->GetFileNumber(fileHash, *fileNumber)) { if (rit->second->GetFileNumber(fileHash, *fileNumber)) {
*archive = rit->second.get(); *archive = rit->second.get();
return true; return true;
@ -423,8 +419,6 @@ void LoadGameArchives()
} }
} }
hellfire_data_path = FindUnpackedMpqData(paths, "hellfire"); hellfire_data_path = FindUnpackedMpqData(paths, "hellfire");
if (hellfire_data_path)
gbIsHellfire = true;
if (forceHellfire && !hellfire_data_path) if (forceHellfire && !hellfire_data_path)
InsertCDDlg("hellfire"); InsertCDDlg("hellfire");
#else // !UNPACKED_MPQS #else // !UNPACKED_MPQS
@ -442,15 +436,11 @@ void LoadGameArchives()
} }
} }
if (HasHellfireMpq)
gbIsHellfire = true;
if (forceHellfire && !HasHellfireMpq) if (forceHellfire && !HasHellfireMpq)
InsertCDDlg("hellfire.mpq"); InsertCDDlg("hellfire.mpq");
LoadMPQ(paths, "hfbard.mpq", 8110); LoadMPQ(paths, "hfbard.mpq", 8110);
LoadMPQ(paths, "hfbarb.mpq", 8120); LoadMPQ(paths, "hfbarb.mpq", 8120);
#endif #endif
if (gbIsHellfire)
LoadHellfireArchives();
} }
void LoadHellfireArchives() void LoadHellfireArchives()
@ -473,4 +463,29 @@ void LoadHellfireArchives()
DisplayFatalErrorAndExit(_("Some Hellfire MPQs are missing"), _("Not all Hellfire MPQs were found.\nPlease copy all the hf*.mpq files.")); DisplayFatalErrorAndExit(_("Some Hellfire MPQs are missing"), _("Not all Hellfire MPQs were found.\nPlease copy all the hf*.mpq files."));
} }
void UnloadModArchives()
{
#ifndef UNPACKED_MPQS
for (auto it = MpqArchives.begin(); it != MpqArchives.end();) {
if ((it->first >= 8000 && it->first < 9000) || it->first >= 10000) {
it = MpqArchives.erase(it); // erase returns the next valid iterator
} else {
++it;
}
}
#endif
}
void LoadModArchives(std::span<const std::string_view> modnames)
{
#ifndef UNPACKED_MPQS
int priority = 10000;
const std::string modsPath = StrCat(paths::PrefPath(), "mods/");
for (std::string_view modname : modnames) {
LoadMPQ({ modsPath }, StrCat(modname, ".mpq"), priority);
priority++;
}
#endif
}
} // namespace devilution } // namespace devilution

3
Source/engine/assets.hpp

@ -5,6 +5,7 @@
#include <cstdio> #include <cstdio>
#include <map> #include <map>
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <string_view> #include <string_view>
@ -297,6 +298,8 @@ void LoadCoreArchives();
void LoadLanguageArchive(); void LoadLanguageArchive();
void LoadGameArchives(); void LoadGameArchives();
void LoadHellfireArchives(); void LoadHellfireArchives();
void UnloadModArchives();
void LoadModArchives(std::span<const std::string_view> modnames);
#ifdef UNPACKED_MPQS #ifdef UNPACKED_MPQS
#ifdef BUILD_TESTING #ifdef BUILD_TESTING

16
Source/game_mode.cpp

@ -6,17 +6,6 @@
namespace devilution { namespace devilution {
namespace { namespace {
std::vector<tl::function_ref<void()>> IsHellfireChangeHandlers;
void OptionGameModeChanged()
{
gbIsHellfire = *GetOptions().GameMode.gameMode == StartUpGameMode::Hellfire;
for (tl::function_ref<void()> handler : IsHellfireChangeHandlers) {
handler();
}
}
const auto OptionChangeHandlerGameMode = (GetOptions().GameMode.gameMode.SetValueChangedCallback(OptionGameModeChanged), true);
void OptionSharewareChanged() void OptionSharewareChanged()
{ {
gbIsSpawn = *GetOptions().GameMode.shareware; gbIsSpawn = *GetOptions().GameMode.shareware;
@ -29,9 +18,4 @@ bool gbIsHellfire;
bool gbVanilla; bool gbVanilla;
bool forceHellfire; bool forceHellfire;
void AddIsHellfireChangeHandler(tl::function_ref<void()> callback)
{
IsHellfireChangeHandlers.push_back(callback);
}
} // namespace devilution } // namespace devilution

5
Source/game_mode.hpp

@ -1,7 +1,5 @@
#pragma once #pragma once
#include <function_ref.hpp>
#include "utils/attributes.h" #include "utils/attributes.h"
namespace devilution { namespace devilution {
@ -15,7 +13,4 @@ extern DVL_API_FOR_TEST bool gbVanilla;
/** Whether the Hellfire mode is required (forced). */ /** Whether the Hellfire mode is required (forced). */
extern bool forceHellfire; extern bool forceHellfire;
/** Adds a handler to be called then `gbIsHellfire` changes after the initial startup. */
void AddIsHellfireChangeHandler(tl::function_ref<void()> callback);
} // namespace devilution } // namespace devilution

21
Source/lua/lua.cpp

@ -11,6 +11,7 @@
#include "appfat.h" #include "appfat.h"
#include "engine/assets.hpp" #include "engine/assets.hpp"
#include "lua/modules/audio.hpp" #include "lua/modules/audio.hpp"
#include "lua/modules/hellfire.hpp"
#include "lua/modules/i18n.hpp" #include "lua/modules/i18n.hpp"
#include "lua/modules/items.hpp" #include "lua/modules/items.hpp"
#include "lua/modules/log.hpp" #include "lua/modules/log.hpp"
@ -42,6 +43,8 @@ struct LuaState {
std::optional<LuaState> CurrentLuaState; std::optional<LuaState> CurrentLuaState;
std::vector<tl::function_ref<void()>> IsModChangeHandlers;
// A Lua function that we use to generate a `require` implementation. // A Lua function that we use to generate a `require` implementation.
constexpr std::string_view RequireGenSrc = R"lua( constexpr std::string_view RequireGenSrc = R"lua(
function requireGen(env, loaded, loadFn) function requireGen(env, loaded, loadFn)
@ -198,17 +201,32 @@ sol::environment CreateLuaSandbox()
return sandbox; return sandbox;
} }
void AddModsChangedHandler(tl::function_ref<void()> callback)
{
IsModChangeHandlers.push_back(callback);
}
void LuaReloadActiveMods() void LuaReloadActiveMods()
{ {
// Loaded without a sandbox. // Loaded without a sandbox.
CurrentLuaState->events = RunScript(/*env=*/std::nullopt, "devilutionx.events", /*optional=*/false); CurrentLuaState->events = RunScript(/*env=*/std::nullopt, "devilutionx.events", /*optional=*/false);
CurrentLuaState->commonPackages["devilutionx.events"] = CurrentLuaState->events; CurrentLuaState->commonPackages["devilutionx.events"] = CurrentLuaState->events;
for (std::string_view modname : GetOptions().Mods.GetActiveModList()) { gbIsHellfire = false;
UnloadModArchives();
std::vector<std::string_view> modnames = GetOptions().Mods.GetActiveModList();
LoadModArchives(modnames);
for (std::string_view modname : modnames) {
std::string packageName = StrCat("mods.", modname, ".init"); std::string packageName = StrCat("mods.", modname, ".init");
RunScript(CreateLuaSandbox(), packageName, /*optional=*/true); RunScript(CreateLuaSandbox(), packageName, /*optional=*/true);
} }
for (tl::function_ref<void()> handler : IsModChangeHandlers) {
handler();
}
LuaEvent("LoadModsComplete"); LuaEvent("LoadModsComplete");
} }
@ -243,6 +261,7 @@ void LuaInitialize()
"devilutionx.player", LuaPlayerModule(lua), "devilutionx.player", LuaPlayerModule(lua),
"devilutionx.render", LuaRenderModule(lua), "devilutionx.render", LuaRenderModule(lua),
"devilutionx.towners", LuaTownersModule(lua), "devilutionx.towners", LuaTownersModule(lua),
"devilutionx.hellfire", LuaHellfireModule(lua),
"devilutionx.message", [](std::string_view text) { EventPlrMsg(text, UiFlags::ColorRed); }, "devilutionx.message", [](std::string_view text) { EventPlrMsg(text, UiFlags::ColorRed); },
// This package is loaded without a sandbox: // This package is loaded without a sandbox:
"inspect", RunScript(/*env=*/std::nullopt, "inspect", /*optional=*/false)); "inspect", RunScript(/*env=*/std::nullopt, "inspect", /*optional=*/false));

4
Source/lua/lua.hpp

@ -3,6 +3,7 @@
#include <string_view> #include <string_view>
#include <expected.hpp> #include <expected.hpp>
#include <function_ref.hpp>
#include <sol/forward.hpp> #include <sol/forward.hpp>
namespace devilution { namespace devilution {
@ -15,4 +16,7 @@ sol::state &GetLuaState();
sol::environment CreateLuaSandbox(); sol::environment CreateLuaSandbox();
sol::object SafeCallResult(sol::protected_function_result result, bool optional); sol::object SafeCallResult(sol::protected_function_result result, bool optional);
/** Adds a handler to be called when mods status changes after the initial startup. */
void AddModsChangedHandler(tl::function_ref<void()> callback);
} // namespace devilution } // namespace devilution

2
Source/lua/modules/audio.cpp

@ -1,4 +1,4 @@
#include "lua/modules/render.hpp" #include "lua/modules/audio.hpp"
#include <sol/sol.hpp> #include <sol/sol.hpp>

18
Source/lua/modules/hellfire.cpp

@ -0,0 +1,18 @@
#include "lua/modules/hellfire.hpp"
#include <sol/sol.hpp>
#include "engine/assets.hpp"
#include "lua/metadoc.hpp"
namespace devilution {
sol::table LuaHellfireModule(sol::state_view &lua)
{
sol::table table = lua.create_table();
SetWithSignature(table, "loadData", "()", []() { LoadHellfireArchives(); });
SetWithSignature(table, "enable", "()", []() { gbIsHellfire = true; });
return table;
}
} // namespace devilution

9
Source/lua/modules/hellfire.hpp

@ -0,0 +1,9 @@
#pragma once
#include <sol/sol.hpp>
namespace devilution {
sol::table LuaHellfireModule(sol::state_view &lua);
} // namespace devilution

18
Source/options.cpp

@ -65,6 +65,10 @@ void DiscoverMods()
// Add mods available by default: // Add mods available by default:
std::unordered_set<std::string> modNames = { "clock" }; std::unordered_set<std::string> modNames = { "clock" };
if (HaveHellfire()) {
modNames.insert("Hellfire");
}
// Check if the mods directory exists. // Check if the mods directory exists.
const std::string modsPath = StrCat(paths::PrefPath(), "mods"); const std::string modsPath = StrCat(paths::PrefPath(), "mods");
if (DirectoryExists(modsPath.c_str())) { if (DirectoryExists(modsPath.c_str())) {
@ -414,7 +418,7 @@ std::string_view OptionCategoryBase::GetDescription() const
GameModeOptions::GameModeOptions() GameModeOptions::GameModeOptions()
: OptionCategoryBase("GameMode", N_("Game Mode"), N_("Game Mode Settings")) : OptionCategoryBase("GameMode", N_("Game Mode"), N_("Game Mode Settings"))
, gameMode("Game", OptionEntryFlags::NeedHellfireMpq | OptionEntryFlags::RecreateUI, N_("Game Mode"), N_("Play Diablo or Hellfire."), StartUpGameMode::Ask, , gameMode("Game", OptionEntryFlags::Invisible, N_("Game Mode"), N_("Play Diablo or Hellfire."), StartUpGameMode::Ask,
{ {
{ StartUpGameMode::Diablo, N_("Diablo") }, { StartUpGameMode::Diablo, N_("Diablo") },
// Ask is missing, because we want to hide it from UI-Settings. // Ask is missing, because we want to hide it from UI-Settings.
@ -1542,6 +1546,16 @@ void ModOptions::RemoveModEntry(const std::string &modName)
}); });
} }
void ModOptions::SetHellfireEnabled(bool enableHellfire)
{
for (auto &modEntry : GetModEntries()) {
if (modEntry.name == "Hellfire") {
modEntry.enabled.SetValue(enableHellfire);
break;
}
}
}
std::forward_list<ModOptions::ModEntry> &ModOptions::GetModEntries() std::forward_list<ModOptions::ModEntry> &ModOptions::GetModEntries()
{ {
if (modEntries) if (modEntries)
@ -1559,7 +1573,7 @@ std::forward_list<ModOptions::ModEntry> &ModOptions::GetModEntries()
ModOptions::ModEntry::ModEntry(std::string_view name) ModOptions::ModEntry::ModEntry(std::string_view name)
: name(name) : name(name)
, enabled(this->name, OptionEntryFlags::None, this->name.c_str(), "", false) , enabled(this->name, OptionEntryFlags::RecreateUI, this->name.c_str(), "", false)
{ {
} }

5
Source/options.h

@ -116,8 +116,6 @@ enum class OptionEntryFlags : uint8_t {
RecreateUI = 1 << 5, RecreateUI = 1 << 5,
/** @brief diablo.mpq must be present. */ /** @brief diablo.mpq must be present. */
NeedDiabloMpq = 1 << 6, NeedDiabloMpq = 1 << 6,
/** @brief hellfire.mpq must be present. */
NeedHellfireMpq = 1 << 7,
}; };
use_enum_as_flags(OptionEntryFlags); use_enum_as_flags(OptionEntryFlags);
@ -832,6 +830,7 @@ struct ModOptions : OptionCategoryBase {
std::vector<OptionEntryBase *> GetEntries() override; std::vector<OptionEntryBase *> GetEntries() override;
void AddModEntry(const std::string &modName); void AddModEntry(const std::string &modName);
void RemoveModEntry(const std::string &modName); void RemoveModEntry(const std::string &modName);
void SetHellfireEnabled(bool enableHellfire);
private: private:
struct ModEntry { struct ModEntry {
@ -868,6 +867,7 @@ struct Options {
{ {
return { return {
&Language, &Language,
&Mods,
&GameMode, &GameMode,
&StartUp, &StartUp,
&Graphics, &Graphics,
@ -880,7 +880,6 @@ struct Options {
&Chat, &Chat,
&Keymapper, &Keymapper,
&Padmapper, &Padmapper,
&Mods,
}; };
} }
}; };

4
assets/lua/mods/Hellfire/init.lua

@ -0,0 +1,4 @@
local hellfire = require("devilutionx.hellfire")
hellfire.loadData()
hellfire.enable()
Loading…
Cancel
Save