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/inspect.lua
lua/mods/clock/init.lua
lua/mods/Hellfire/init.lua
lua/repl_prelude.lua
nlevels/cutl5w.clx
nlevels/cutl6w.clx

1
Source/CMakeLists.txt

@ -107,6 +107,7 @@ set(libdevilutionx_SRCS
lua/autocomplete.cpp
lua/lua.cpp
lua/modules/audio.cpp
lua/modules/hellfire.cpp
lua/modules/dev.cpp
lua/modules/dev/display.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);
GetOptions().GameMode.gameMode.SetValue(option);
GetOptions().Mods.SetHellfireEnabled(option == StartUpGameMode::Hellfire);
SaveOptions();
endMenu = true;
}

2
Source/DiabloUI/settingsmenu.cpp

@ -68,8 +68,6 @@ bool IsValidEntry(OptionEntryBase *pOptionEntry)
auto flags = pOptionEntry->GetFlags();
if (HasAnyOf(flags, OptionEntryFlags::NeedDiabloMpq) && !HaveIntro())
return false;
if (HasAnyOf(flags, OptionEntryFlags::NeedHellfireMpq) && !HaveHellfire())
return false;
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)
gbIsSpawn = true;
if (forceDiablo || *GetOptions().GameMode.gameMode == StartUpGameMode::Diablo)
gbIsHellfire = false;
if (forceHellfire)
gbIsHellfire = true;
bool wasHellfireDiscovered = false;
if (!forceDiablo && !forceHellfire)
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;
@ -1213,7 +1221,7 @@ void DiabloInit()
UiInitialize();
was_ui_init = true;
if (gbIsHellfire && !forceHellfire && *GetOptions().GameMode.gameMode == StartUpGameMode::Ask) {
if (wasHellfireDiscovered) {
UiSelStartUpGameOption();
if (!gbIsHellfire) {
// Reinitialize the UI Elements because we changed the game

6
Source/discord/discord.cpp

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

35
Source/engine/assets.cpp

@ -3,7 +3,6 @@
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <string_view>
#include "appfat.h"
#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
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)) {
*archive = rit->second.get();
return true;
@ -423,8 +419,6 @@ void LoadGameArchives()
}
}
hellfire_data_path = FindUnpackedMpqData(paths, "hellfire");
if (hellfire_data_path)
gbIsHellfire = true;
if (forceHellfire && !hellfire_data_path)
InsertCDDlg("hellfire");
#else // !UNPACKED_MPQS
@ -442,15 +436,11 @@ void LoadGameArchives()
}
}
if (HasHellfireMpq)
gbIsHellfire = true;
if (forceHellfire && !HasHellfireMpq)
InsertCDDlg("hellfire.mpq");
LoadMPQ(paths, "hfbard.mpq", 8110);
LoadMPQ(paths, "hfbarb.mpq", 8120);
#endif
if (gbIsHellfire)
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."));
}
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

3
Source/engine/assets.hpp

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

16
Source/game_mode.cpp

@ -6,17 +6,6 @@
namespace devilution {
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()
{
gbIsSpawn = *GetOptions().GameMode.shareware;
@ -29,9 +18,4 @@ bool gbIsHellfire;
bool gbVanilla;
bool forceHellfire;
void AddIsHellfireChangeHandler(tl::function_ref<void()> callback)
{
IsHellfireChangeHandlers.push_back(callback);
}
} // namespace devilution

5
Source/game_mode.hpp

@ -1,7 +1,5 @@
#pragma once
#include <function_ref.hpp>
#include "utils/attributes.h"
namespace devilution {
@ -15,7 +13,4 @@ extern DVL_API_FOR_TEST bool gbVanilla;
/** Whether the Hellfire mode is required (forced). */
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

21
Source/lua/lua.cpp

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

4
Source/lua/lua.hpp

@ -3,6 +3,7 @@
#include <string_view>
#include <expected.hpp>
#include <function_ref.hpp>
#include <sol/forward.hpp>
namespace devilution {
@ -15,4 +16,7 @@ sol::state &GetLuaState();
sol::environment CreateLuaSandbox();
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

2
Source/lua/modules/audio.cpp

@ -1,4 +1,4 @@
#include "lua/modules/render.hpp"
#include "lua/modules/audio.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:
std::unordered_set<std::string> modNames = { "clock" };
if (HaveHellfire()) {
modNames.insert("Hellfire");
}
// Check if the mods directory exists.
const std::string modsPath = StrCat(paths::PrefPath(), "mods");
if (DirectoryExists(modsPath.c_str())) {
@ -414,7 +418,7 @@ std::string_view OptionCategoryBase::GetDescription() const
GameModeOptions::GameModeOptions()
: 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") },
// 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()
{
if (modEntries)
@ -1559,7 +1573,7 @@ std::forward_list<ModOptions::ModEntry> &ModOptions::GetModEntries()
ModOptions::ModEntry::ModEntry(std::string_view 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,
/** @brief diablo.mpq must be present. */
NeedDiabloMpq = 1 << 6,
/** @brief hellfire.mpq must be present. */
NeedHellfireMpq = 1 << 7,
};
use_enum_as_flags(OptionEntryFlags);
@ -832,6 +830,7 @@ struct ModOptions : OptionCategoryBase {
std::vector<OptionEntryBase *> GetEntries() override;
void AddModEntry(const std::string &modName);
void RemoveModEntry(const std::string &modName);
void SetHellfireEnabled(bool enableHellfire);
private:
struct ModEntry {
@ -868,6 +867,7 @@ struct Options {
{
return {
&Language,
&Mods,
&GameMode,
&StartUp,
&Graphics,
@ -880,7 +880,6 @@ struct Options {
&Chat,
&Keymapper,
&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